{"format":1,"manifest":{"name":"database-runner","publisher":"vyloxi","version":"1.0.0","displayName":"Database Runner","description":"Run the selected SQL or the statement under the cursor on PostgreSQL or SQLite with the command line client, and read the result in a panel.","engines":{"ferrum":"^0.1.0"},"categories":["Tools"],"icon":"icon.png","repository":"https://github.com/voukengdongmofrankysteve/ferrum-market","license":"MIT","main":"extension.js","activationEvents":["onCommand:dbRunner.run","onCommand:dbRunner.runFile","onCommand:dbRunner.listTables","onCommand:dbRunner.describeTable","onCommand:dbRunner.previewTable","onCommand:dbRunner.countRows"],"permissions":["terminal","workspace.read","workspace.write"],"contributes":{"commands":[{"command":"dbRunner.run","title":"Run Statement or Selection","category":"Database Runner"},{"command":"dbRunner.runFile","title":"Run the Whole File","category":"Database Runner"},{"command":"dbRunner.listTables","title":"List Tables","category":"Database Runner"},{"command":"dbRunner.describeTable","title":"Describe a Table...","category":"Database Runner"},{"command":"dbRunner.previewTable","title":"Show the First 20 Rows of a Table...","category":"Database Runner"},{"command":"dbRunner.countRows","title":"Count Rows of a Table...","category":"Database Runner"}],"keybindings":[{"command":"dbRunner.run","key":"Ctrl+Alt+Enter"}],"configuration":{"title":"Database Runner","properties":{"dbRunner.engine":{"type":"string","enum":["postgres","sqlite","mysql"],"default":"postgres","description":"The database: postgres (psql), sqlite (sqlite3) or mysql (runs in the terminal)."},"dbRunner.target":{"type":"string","default":"","description":"PostgreSQL: a connection URL such as postgresql://user:pass@localhost:5432/mydb. SQLite: the path of the database file."},"dbRunner.mysqlArgs":{"type":"string","default":"","description":"MySQL only: the arguments for the mysql command, for example \"-h 127.0.0.1 -u root -p mydb\" (-p asks for the password in the terminal)."},"dbRunner.timeoutSeconds":{"type":"number","default":30,"description":"How long to wait for a result before giving up."}}}}},"files":{"icon.png":{"encoding":"base64","content":"iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAeQ0lEQVR42u19a5Ac1ZXmd+69mVmPrla31Aj0AMQiBEg8DMZgCQw2fmDYcRibaWwCbA/2zkQ45sGsY8ya8cS0wDvrHU9MrBfH2rMxu95hbc966fVgmMEgmAULT7CWeQkbS7yNAL1ar1Z3V1dV5r337I+s6q6qzszKqq4W0KqMIFB3Z1U+vnvOPY/vnEPo8BgeHpajd99tQcQAcMXIo4r3/t+zjc+bmO2FMPoCtrwSQqxia5kAiv82BrjFBTn6Fxx5DrfzRamvlXRyWx+p/ZHjP8IAEwlia3eTEHsEiWcg8bRL7uN0Rmbn1ttv1+GJTMPXXy9GR0dNJzhS258YGRHYvJlrwF/+e3eca8rTN7Hlqxm8TjmeJ5QDEgIEQEinrZfQGhCO+XULkDj695zyWq1WSbqPcew5HHO+NTq8T2thdQAd+BUQXiSiBzyZ+f5jf/+Xv6otBGzeTLj9drtgC2B4+G45Onq9AYD3ffarHzGwX4Y1VzrZvJDKgXKzkI5nhXJYkCAABCLqDvic8GtOvcioTma5bQQZna0Xjv0Ax55eu3fLALFlwzbQZHRF6EoZVgfwy0ULEo9A0F/94n9/86EZzdyGNqB2pX7jTbedQ4RvCEFXKy8L5Wbg5vq1VI4AQGAmZhv/VrgOFGrxjmfO4ei1UHtRlPASm67DURqh4RzuTNVz3BvlxnMo4pvm3AtHQkQAQMRgsNGBrZSmlAnKCColMNsHLHDrk3ff+RzABGwmoLU2oFR7fXVFbfzMbZuJ7G3Kzbpett+6ff0shBTMlmBtAqop9/nU6r4FUBz3F+5gn+cOTIMkVc+ptVzU9sa1d0wEImJrjfWLk+SXJoX2yz4DX39i9Fub02oDSgP++2780xVG2v+hlHuV8rLI9C8zUjmSrWmx9y4O4DsCvxvAzwG/+XnDxUBCwGhtypNHpPFL0EGwxRhx81P3fHNvq0UQuwCuuGJEbd16u95441feQ5LuVa67IpMf1F7fEjkr8QsH/Hz2+UUPfNT3VDWCX5w05eJRZQJ/r9H4+JP33PlEDcvUC6C2akLwscXxcoP5geVaup5io9MhygsNfPwi6wz4Y6Duuw78XEOESMIEvp4+ekgF5dIRa3HVk/fc+UScJhBz7b0RMTo6at57w60Xh+BnB/uWnWSk4yrWQYuXxzP/a3VaxA+zP3HtRSSpe4584ZxgcSdLPUefFfsxblzs3OI+uMU9MM+Az3Hgt7w5AlsDoRyVGxwyjucNCsFb3j38BxePjo6akZER0UIDjAjgdr7k07ctl479pZPNL+8bPMkIISWzSTbbu7HPdyjxyVL/Fkp8mmdibu1kprk5rr8HBpGAtcZMHzkg/fL0mHDEedt+eOdYFUQbqQGGh3cQwBCO/Z5yM8vz/SfoePBTSnyC1M9X4udIW4J2OaYSP+fhkiU+Wt3HvVuOgaH+HgjMDCGUzPYv047rLbe+/V51e28AUjYGeW43m24qjThu5guZvkHtZPMqFvyeS9ehgccJX5mkTTlB4uMOC6EcQSBtjT5j5bp388M/+rtHh4eH5Y4dO3hGA4T7/vVm041/diYgvqrcrPX6+kM3rwH8eElsS+JbSn0nEp8kvjw/8NuW+CSpT7B+YiWeW0h8vJPH1sLNF6RyPQvCVzcN33JmvT0gGu+vcqeTyTqZ/kFmy9R4xc6BRzvAY2GBb0vddxV4Tr6jlMC3lvqIy1tLXmGAlZdxAh3c2bBEavH9TZ+97QOC6JHskmUm0zco2er0CbSeS7cALl3Uqe0BX69NSUiUi0dNeeKIZOIrf3H3tx4dHh6WAhgNTwuCP1deFm6ugBnwey7dW+jSNUp8++DXa9PqVpAtQHkeOAj+vMENvOzm29bbwP4yN7BMZAvLiI0GiHou3dvCpetA4mOuQ0KgPHmES0cPW1fK8/5l9Fs7BAAYX3/OzeSkmykYtjYZ/J5Ld4xdunbVffx12DKcTJ9xvJysaPM5AKD1IyNu/4tTT+f6l27ID55oma3ouXRvR5cOHRFe5vxIwpbGD4jS1JFfTzsnXagKr5rTicRa6WYAgqhlmHpZunkaeMcK+LYMSQYRC+m6IKK1BTF2ulLav1y4nqcczzJDzIDfy9K9vS37NIuMo7cBqTyrHM8zfnC5YrKXCKUglMNz9+aeS/eOBL7FMwmlmISC4fIlylje4AkJIkmYoXItIPBvJ8t+XsC/HSz7VuxSjn4eEkRCgCxvUAJYVQWWug18z6V7GwEPgGeeKXTzmLBKEYlVQikQEXHVAFz8wKfj371jLPvUwNcMQSIhFQTRKsVsmri3PZdusQBfiynExUHYWlY1EjJ38JJ6Lt1b6dJ1AvycOBipnkv3znXp2ga+FoWtC/OodKHbnkv3dnXp0gMf/Rc1L+B7Lt1b7NJ1CjzP/Kd6Lt073aVrtyaj8V2oqEqTtxr4hmo4qr8+V/9GjaA33DI1gc519XhU/Ss3nUNNflD989LsW6Cme4h9mKa/1dVD8tsB+LrTVKv07LFw6YgAQWH8wRiGqas6ilX1c4oooypr6/CLK9ZETGEoN6lZ6kToG7+PBEHJsGyemWG5WwZeOu1Sux1qMALfQpeOwhpH+L5GqaIhCCjkMijks/O27Hk+rlbDOumePx9ojfHJaWhr4SqFjOtUSfp8TICPMQLfCpeOIYVAJQhQ8TVOXr4Ul51/OjacthKnrliKpYV8p+0runPwfK7NM7x8W82tEAiCCKWKj117D+H51/biZ9tfxPOv7QXAyGc8GMtdNPBS9s2oLYAZBkoaA2Ve6j78pRSEiWIJK5YN4PO/tRGXnb8WuYyLxX705TI4YbAfF60/DTd89L3Y/sLr+O59j+GZF3ahP5+t7sZ8TICvKR3VWS+dzg08QYSJqTIuPe90/MmNH8bS/lDajbUgUHV/oncgvLU8ioUggT2lQ/jpge0omQo2LluP9f1rQo1QxU8KgXefvQYXnHkK/us/PIofbtkG11UQRPEAdw34ejeQo/h3C+DScfjQE8USPvn+C3DLp66cAV4IASnEO1y+CZYZggQe2v8k/mLnD3DEnwSB8J1X/hE3n/ZR/P7pHwcTQ1QXuLUWRIQv/vYHceqKIfzl392PjOd0bNmn80Q5rjp4YYmXNbV/6Xmn45ZPXQnLDObQFiC88w8Os2zYUzqE/7DzByibCoa8JVjqFpCTHr7z8r147MAvQaAZ20AIARBBa4NrLj0f/+YTV+Do1DSkFC2Kcerw4HalHlELYGEZt0SESqBx0tAS3HrTR2Z8WaLFAH14WA7jA1sPPovD/iRyKoPAamg2ECTgCgf/tPf/Rfb9UUrCGIubrrkUV1x4FianSuFWsIDA1/4sjkUtHRFQ8gP83scvw0AhV1WViwf8+qNkKqCqF9CgHUAoWz9+A6EwSPWHn/4w8lkP1nBTfKI7wIe/teH9MUG02ufnW0sHYlSCAKeeuBSXnrc2LFsmseiAr2H13qXr4QoJC4YkCUECkgRKtoLLhs6NDSULQbCWsXr5Urz33LUolisQ1OY+31Li524roqvqPiKmIEigVA5w+bvOQMZVoapchMIvSMAyY33/qfidNR/F4cokJnQRRV3C/vJhXLn8Aly78jIwkrUfM/CRjefMvKOaxHOqEr144DnSnuBaNpAXNEsniLDhtJWL3s8XFHoCv7/2WqzvX4MH9m1D2fi4dOgcXLf6ciiSM9tBXEicCDjzlBXoz2fhBxpCiPj4fwvPjVt6EFwLBHWj1encDxED2hoU8h7WrFw2IymhB2QBgIQQvOgWAYAPLH8XPrD8XRFbRSP41loSQrAN3WEBAAP9OaxcPogXX9uHbEZES3+LDGjLpFH1bhQvJP+OGNZa9GdyGOjL2qpKs0RQYtbvX3QbQjsWTvU9UPX/xlgLJSWW9fcJYy11D/jIzFNdJJAXgHjJ4YrX1hqt9S7A84VA4fDhw1t8399TKBTWc9iD5rg8mJmFEE6pVHojCILpFStWXE9hAyddKvunCiGyc9riJsQhOgnlq2QDr4tUayEqALKHDh26f2ho6Laq5DvzSLctJgfCApjauXPn3rPOOutLAEpE9RZ5J8Cni+qKY9IeZfZwisXiGIASAK/64Hyc/2cRNuuiN998c7cNMaHZd9euZY8ULXN45l9qTh+giGTEfPh3s2uEFYCJ1aec8umXXnrpyNjY2K8nJiYyWuvjWgMopeB5nhkYGOg/77zzvgRrKxCCFkrim3s1Ka5F5TgF23XexEsAgFm7du0ta9eutegdzbZjxVobRDednx/wDR30edblVIcnilD5QeQEwdgEEkIHxEuKJ7tN9fCOtQeoUdQ4hWWfMnNb/YcUYQLqyEQRSluLg0cnwV4B/TkXUgpYy12speOEJFTvSFL1yXt8u8CH2UowMDE5jbEjE6gEGopCGgYmiiWUymUMFHLIeW41/GiRlpmSvsCid7TDIu8G8LVcg+9rHJ4oouRrsA0jkqo+GRFogwOHJ5HLuBgoZOEo2agNOq6l4xaRqd6RPpCD1pzMJuCNsThydBoTxRKstZBSwczmAhro8wABxVIFZd9Hfy6LQj4TZqqMTUcFjUko5DOerbo8FLP3ieNIX1CdC4i6sDCklABgw4QRt3Dp4ky02YTT1HQFRyeL1bwCVQ1+booENvjyBCEIzMCRyWkUyxX05zLIZ90Z2lNa4MOmVATf1+KWv/5fp5w0NGD/9OZriEGQUlT5CCAAPoAJANPNL2WRgp8BUACQrWGvtSGlJL57z0+x7bmXed/hcc9VMkYjcCzwFE4OQans4+hUCaWKH0qXiOYaqqRggxBAEGgcHJ/CVMlBfz6LjOeE1MeUBgqFAW56/vX92Sdf2IUVQ0vwu9dePtvOGjhQBV8vxrxAzAuaBlABkAOwHEBWKYnHn30J/+3Hj0IIguuoUBDbBN73NSaKJUxNl8MALCGxF7GKpDXUrTqisGyq5Aco+wEynkJ/LhsWNRCqGiE5XclgeK6E5+Tx/Qce5xMGC3TtFRdMGWv3yjBELI8T8JvDv0eNtZNSiBO3v7Br6b//239gz1FwXQVjLHUCfLFUgbUWgiiGd9HYjFO06njJM3tKldpVDjB2eAJjRyZQqgRhWZegqGhjQ/jY2vBmLRM99/LucQCvE6BtmA84nsBv0sAgAHteeWPfvqOT0+Q4imbBbyLpzMAT4iEEhRr6yCT2HhzHZLEEJNDtGoL53JAN5Lq4MyVa9rUvL1V8lCs+PMdBPuchn3GrufBoi18QYark46L1a4p/9oXf2hNmQsXx7iiylALaGOe6D11y8PDElPrvP946tCTvsbHcWK5aNdKlCKfETld8TBbLKFd8WFtdEAnAxzkSqnmIQdo6utrFKkGA8niASSWRy7jIZT04UswwZWv6LjAGJwz06ZEQfLI2tDF6B6CkZGut+t1PfnDsuZfeyD75/G/yOc+dccFDLQsYYzFZ9DE1XUbFD2qsXsRlDtKU+on2hxg0/lire/O1xpHJaew7eBQHj06hVAlm/FAhBPzA4Hc+tunAQCHvB9qIHvjRb/aPb/rX+7Oua61lCBHu436gcfjoFPYeGMfB8UlU/ABElAh82uEaiqOqIjtoj0JVo4PBmJquoDhdhqMU8lkPrqOwYtkSfdUlGyYACKlkLyrUdEghYIwVp606oXzhWWumH3v6+T4iYLJYgh/oqg1V07zxLFFupwJ6ti6gji483yEGVdeDiBBojYmpEv9mz0Gcc/rKSddxdKB1T/hbHO+/eP34ngOHcagq7bUEDjV3d4iS+lYYNsSWuOoFtJpXk6aXfQQnnYggBBERcP4Zq6fDuJDoSX9scUjID9zwr06u9GU9y+BqACc+lJ4a+DkBP67rEjbf/ncxN0dgaKNRyLp89mkrK+GaELDW2iobmKw9vmkBTZ6QBEAnrxgKVi1fGry4a6+X9ZyYQPD8uoxFM4K6BHz9FxjLKOQ8u3xpf438aYUQfdVr6541OGOMkxCY1NZACWmHBvrMzlftggBf/4PqqBNWGs55XX8ey1xzaVgIFA4dOvSgMWZPPp/fYK3Vx3EgqMYKfj0IglLICiZTE5zGuR2d9TCMB5/nZgMx0yya5wd8FCOIYQDkDx069GCPFRz58qZ27tw5tm7dWV8CMEGNXX27DnxMj6CEHvNtAJ8wYUqWSqV9CJMhg9UEUO+ojvB9880331y37ixqNO64rR6GycDHdApNtOxTdqhK5AfMapvJlStX3/DSSy+N91jB0axghq3MxPa6Anwyhirelpgn8M0/ih4rOA0r2NRYwa3a9rQCPlbqG7J1TUZgmgL0doCn2FhkjxWckhWctnE0t4VjczaQU+zz7Up8sm3X8/3Sjn7tGvDRhqRq2Sq+Sz1u0VP4XVgH3DXgeW6z6PoWma0s+/TtzGvhY9tjBSNtsymZUHc5f+AT3MB61BldGGJAs95FIZepsYKl7e0B0eBbC0dK28gK7g7wcaV+Kmqg87zUPTcGLgQRKpVA3PDVb5980tAS/V++/Lk94cP2CCFobCRNjlL6P373nhN/vv3F7Bv7D3petafSgoz14YZ28Qlz7ufZy56IoI2hZ17YlSs+62NZf5+544vX7WZY1dMDDeAH9/70icH//L2fDAlB8DwHQta1h2HuisQ3u4Jy4NTzNgs3A+lmgahSsC4NMXAdiVzG5aef35X1HIWLN5w+ba0lIjquwa+2hOHHntpR+NI37loJgshm3LCXVBtueTtjfYgINqjA6goEp7Y+Y9R94sqcvcEqK5gCbfD8a3u8ntw3xt9ffmOfe3iyKJUSbIylVH3hOIEKlhJDkdzlMxn4pKGEzR+VgjBZLON9F6wrfvNPPrM7dDd6W4CUAoHW8vPXXnnwj2+85uD4xDRJIm4JPM8P+JkSsk4knltNGuO5/e8qgcaJywr6W1/+7B4AFHYJ7ykAAHCUYmut+nefv3bs/RdtKE5Ol6mxSQjHvtvW+3xEeLgu9iPSfSgN8HFZq5DMWPY1/u0NVx9YNtDnB4EWsod+ZBT+a3/46f35rFfto5gMfLTUc0JMZi4jSLT60HyArwW3K77B6hOX6k988KKQFSxVLyoUsRVoY8SZa1aWN52/brpYqkQOj2it7uNWV3QbYJFk2bcP/NzmxkSCy36AD128fjLjVlnBPeFHdCYoVPu//eFN49REz+9on48o9WvGcE42kDuabJ3Q1ZqZiICL1p/WYwWnZAW/66w1lSX5rK1UhSWuIr8bbXtU1DDBbo0nIzC0tSjkPF5/2qoeKxjpWMGrVgwFK04cDFnBrooIAKZr5MVps4HMLbpQtD2Xbnaip7WMQtazywd7rOC2WMGDBbPz1aq3zNx14DGnRUxXplTFFJPYsGk0eqzg9Kxgw6kSbomMILTuJKo6HXDMaVLB1DA4qMcKbpsVnODSdQx8fSOQOcOjuwR8PKOxxwpOyQpOW5PB7Rrp0dnAdC5du0ZI0433WMFokxXcTeARTdZW7Q0kbB/4CLphjxWchhWcEMFre8vmKEZQpA1gGzuVp+Kkc3LTw+gv6rGCW7GCm2L28wU+rtyvqTiUugN8VSUlpPp7vl9H3OBOCkORWM6v4gcSdg58ez1uewfSNoHtFvDNWwCn8jcxj/Fk6LGC0SYruCHnwvMHvgF8qusRxB25dOmHGFQXWyHfYwW3xwpGB5Z9+0a6mpeB18qDsOGcwErFF5+69c6TTzphQH/nK1/osYIRzwr++t/+nxMf3/589o09BzzPVU3vuXvA1/Z91e5ImFTtzJsGR4es4NdyU9srGOrvM1/7g0/t1kY7rujxAgCg7AeUcR3940e2Dfynu+4bEkIg4zpQUVND5w08N3QZm23RbpP5Y/GjS+aOi4/6W9ZzMDTQx3f9088G77pv65DrqMBYC2Psca3y/UBTxnX0MztfzX/lr//nSdmMi/58hsNG0ZxQKshtD4+eKQGoU9JyySnnbpaOB+l5kcYGtxNeTMpAhziTlAL/vO25Pj8IxPsuPHtKCOJAa8HV1qiLnSZurYW1DGMMKSVZSmF+/M8/H/jiHX+zqlgqS9dRmDMxNLVlD6Qp7iUiGF2BNQHRyZde/6bK9q1yC0sZzNT2lCpOP8SgtiUAwNFiGe9/99nFr31xeP8Za1aW6oIgZIxdfPlBBkiKusaM4KNT085f/M3dJ3zvvp8Ouq6Co+LmNXU4sDO2qptYT4+TrhR308mXDm9T2cLFbt9SC7DoDHgktjRHZGdM4snpMuUzHm86f13xkx+6ZPyCM9eUV5+0TC/iWmIaO3xUPf/KG969j2zrf/jxZwv7Do3LQj7TaF91Y59PnOdEVk+PC1OZ/oUCxK+Z7cXMhuPFrg3gU1qfxjD1ZT0Yw/Twz3/V9/DPf9XXn8/YlcuXBkOD/doYQ5S6VA2dF1GmarJMqUfmxX2bIOJiqSJ37R5zxyenRaA1sp6LJX1ZmBorasGBr3lnzGGYnn6tIHib1cHNbDSRdLoAfHquWq3/bV/OYwaTr7V4cddeb+eru73m6XlzMOBOmyW0HnPTVf4dN7bMdxyJjKeQyzhs2ZKxtjsuXZomHrXFbANiawAS25Qg7zGjKxWrA08pD3OGeXcD+IRthcEwlimcXkHIuk6tQw51XfPE1tKlN2jn1Z+HAYYFM8Mw07EFHnU0vUBY7VfIlY+p/InlVyZ242Wr/Q0Ma+cWiywc8JE56jD0Sd3ti5NyEvoCES87ydJ1DnwLI51h2WhB4JeXTuZfETtGR30m3M9Gg4220S8iwsBLMzWcU/SyR4rO1jF9bmOyWAtTS8dJBRbxFTzxvX6SJrK38uWTSvqTazzZGMvGACzu37Fj1BcAIMm9ywRlYyolSTOxoYhHTA08z2uIQezNzxP4zmrpkitr2gKeWwGPBQM+VP8CNihLG5QN4N4VRgKHh+Xr//KDHWztz6z2ia0x8c/fPvBz1H3KeQQLAXxntXRJwPP8gUerd9uqe0vSu236GmONNT5Ztj/bt/2+HcCwnEnHSCXusLoC7U8DJJoWZOfAt9XLnpOAR5eBT1dLh9TAozPgOT3w3Gb4valGDyYowZoAJNUdAIBhQGB01GBkROx67O5HjTEP6UpJWq1Ny73oWAGPFi+pY+DT1dJxauC5S8DPNfA6B75W4wlYY4wNypKNeWj/Uz95FBgRGB01oun5/8gGlUCXJwhJTQraBh6pgI+07Lm9XgQLAjyOJfBR+3z7wDc06yLBxp8iG/iBVPgjAMBIHR8dW7cyhofl5JbRA/0nny1A+ACIjFSeaCCSt+HSLVyr055L1yo+wQ1jewRMpWRsUJZk7df2Pr3lRxgelvj2ty3mlGQND0uMjtrVm4a3CMf9sNM3oIVwVGTzqAUbYvAOAX4BsnTdBL6WeWOttS4dVTYIHt7/7JargGEBjJpodu7oKAOAY+RnrPbHdPGoYmsM5kTley7dW+7SNfVljATfWqPLk8roYIyN+5kqyJxEz7YYGaHfbPvhfhh8TPvlI/7UYcnWGhC1B3zPpVs4ly6xe0to8bMxRhfHpQ0qR4j5Y2PP/eN+jIxQc6Y1Ov0XbgVm9cbr38NktyjHG1T5fi2kq3hOPX/3hxi03/iwO8mahSJeps3Spc1DJHVuIQhYHWhdnlJWV44Q6Kp92x98ooYpEDkvPuK44gqFrVv16o2feA9I3iuks0Jm8lp5eclsqbtDDBYr8B0ka9rd5+v8fAKxrpSMDaaV1XovAR/ft/3BJ2pYIq4qNfLYtctieFhObPnRmwNrzvx7Y/gcWL3OmoCEdA0JKea+yHlY9scK+I5q6d6mwM+6eeF+X5oSVpcFG7PFSL5m7JktL2B4WOInPzFIqEVLPupUx6qN120GcJtUniu8jFVOjkkIUa8RuN2X1HPpOgB+pqkUs7XWBCWyQUVY7ftgfH3/s1s2V8GT9RZ/ZwsADexhXr3x+nMA+w0iulo4LoR0ILyMJqEEQMRgam3czY6oS2/Vp5NCfovdufQSn7yFcjxcoflnjLVBWVkTwOoAAD8AI2/d/8v7n6vjV9o01ajpjzptcPKm6z7CjC8D9krhZASEhFAuhFCWhOLqmOvqTPE2JX5ehMfOwJ/voEzuMA4y9zLNy6faxImZ2WiyVgvWAdgaWF2xIPGIEPirvU89+FAzRkhZjtzmMSKAzQyEoeLVlw+fy4G9ia29GuB1QjkeCVlreQaSclaC0tD6UhSnHDtVj7a4gJxqn4/XZByFjjHV4dE2BN0EFQK9SCQesNL7/thT9/5q9uwRAm5vi1DbOfm6GjWcecQrRtRq3nE2+7yJwBda1heAeSURrQoZiI3XShaIFHy+Llj2yZdun+XUVhV0a0OTCURs7W4Q7SGiZ4jk01bw42N9F+/E1tv1DIbDw6Idqa8//j+px5GyQxmKjwAAAABJRU5ErkJggg=="},"extension.js":{"encoding":"utf8","content":"// Database Runner: run the SQL you selected (or the statement under the cursor) against PostgreSQL or SQLite using the command line\n// client you already have (psql, sqlite3), and read the result in a panel. For MySQL it runs the query in the terminal.\n// The query is written to .ferrum/db/query.sql and the result is read back from .ferrum/db/out.txt.\n\nconst DIR = '.ferrum/db';\nconst QUERY = DIR + '/query.sql';\nconst OUT = DIR + '/out.txt';\nconst MARK = '__FERRUM_DONE__';\n\n/** The statement that contains the cursor. Statements end at `;`, outside quotes and comments. */\nfunction statementAt(text, offset) {\n  const spans = [];\n  let start = 0;\n  let i = 0;\n  let q = '';\n  while (i < text.length) {\n    const c = text[i];\n    const n = text[i + 1];\n    if (q) {\n      if (c === q) { if (n === q) i++; else q = ''; }\n    } else if (c === \"'\" || c === '\"' || c === '`') q = c;\n    else if (c === '-' && n === '-') { while (i < text.length && text[i] !== '\\n') i++; continue; }\n    else if (c === '/' && n === '*') { const e = text.indexOf('*/', i + 2); i = e < 0 ? text.length : e + 1; }\n    else if (c === '$' && /^\\$[A-Za-z_]*\\$/.test(text.slice(i))) {\n      const tag = /^\\$[A-Za-z_]*\\$/.exec(text.slice(i))[0];\n      const e = text.indexOf(tag, i + tag.length);\n      i = e < 0 ? text.length : e + tag.length - 1;\n    } else if (c === ';') { spans.push([start, i + 1]); start = i + 1; }\n    i++;\n  }\n  if (text.slice(start).trim()) spans.push([start, text.length]);\n  if (!spans.length) return '';\n  let hit = spans.find(([a, b]) => offset >= a && offset <= b);\n  // A cursor on blank space before a statement runs that statement.\n  if (!hit) hit = spans.find(([a]) => offset < a) || spans[spans.length - 1];\n  return text.slice(hit[0], hit[1]).trim();\n}\n\n/** SQL that deletes data or schema, or updates and deletes with no WHERE. The person is asked before it runs. */\nfunction risky(sql) {\n  const s = sql.replace(/--.*$/gm, '').replace(/\\/\\*[\\s\\S]*?\\*\\//g, '');\n  return s.split(';').some((st) => /\\b(drop|truncate|alter)\\b/i.test(st) || (/\\b(delete\\s+from|update)\\b/i.test(st) && !/\\bwhere\\b/i.test(st)));\n}\n\nconst safeTable =(s) => (/^[A-Za-z_][\\w.$]{0,80}$/.test(String(s || '').trim()) ? String(s).trim() : null);\nconst badArg = (s) => /[\"\\n\\r$`]/.test(String(s));\n\n/** The query file and the command for an engine. Returns { file, command, capture }. */\nfunction plan(engine, target, sql, extra = {}) {\n  if (engine === 'postgres') {\n    return {\n      file: sql.replace(/\\s*$/, '\\n') + '\\\\qecho ' + MARK + '\\n',\n      command: `psql \"${target}\" -X -f \"${QUERY}\" -o \"${OUT}\"`,\n      capture: true,\n    };\n  }\n  if (engine === 'sqlite') {\n    return {\n      file: sql.replace(/\\s*$/, '\\n'),\n      command: `sqlite3 \"${target}\" \".headers on\" \".mode column\" \".output ${OUT}\" \".read ${QUERY}\" \".print ${MARK}\" \".output stdout\"`,\n      capture: true,\n    };\n  }\n  return { file: sql.replace(/\\s*$/, '\\n'), command: `mysql ${extra.args || ''} -e \"source ${QUERY}\"`.replace(/\\s+/g, ' '), capture: false };\n}\n\nfunction cleanResult(text) {\n  return String(text).replace(new RegExp(MARK + '\\\\s*$'), '').replace(/\\s+$/, '');\n}\n\nconst esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n\nfunction resultPage(sql, out, ms, note) {\n  const lines = out ? out.split('\\n').length : 0;\n  return `<style>\n  body { font-family: var(--ui); } h3 { margin: 0 0 4px; } .dim { color: var(--fg-dim); font-size: 12px; }\n  pre { font: 12.5px/1.55 var(--mono, monospace); background: var(--raised, rgba(128,128,128,.12)); border-radius: 6px; padding: 10px 12px; overflow: auto; margin: 10px 0; white-space: pre; }\n  details { margin-top: 12px; } summary { cursor: pointer; color: var(--fg-dim); }\n</style>\n<h3>Result</h3>\n<div class=\"dim\">${esc(note || '')}${ms !== null ? (note ? ' · ' : '') + ms + ' ms' : ''}${lines ? ' · ' + lines + ' line' + (lines === 1 ? '' : 's') : ''}</div>\n<pre>${out ? esc(out) : '(no output: the statement ran and returned nothing)'}</pre>\n<details><summary>The SQL that ran</summary><pre>${esc(sql)}</pre></details>`;\n}\n\nexports._test = { statementAt, plan, cleanResult, safeTable, badArg, risky, MARK };\n\nexports.activate = function (context) {\n  let panel = null;\n  const reg = (id, fn) => context.subscriptions.push(ferrum.commands.register(id, fn));\n  const setting = (k) => String(ferrum.settings.get('dbRunner.' + k) || '').trim();\n  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));\n\n  async function ensureReady() {\n    if (!ferrum.workspace.root) { ferrum.window.showMessage('Open a folder first; the query files are kept in .ferrum/db.', 'warn'); return null; }\n    const engine = setting('engine') || 'postgres';\n    const target = engine === 'mysql' ? setting('mysqlArgs') : setting('target');\n    if (engine !== 'mysql' && !target) {\n      ferrum.window.showMessage(engine === 'postgres' ? 'Set the connection in the settings: dbRunner.target, for example postgresql://user:pass@localhost:5432/mydb' : 'Set the SQLite file in the settings: dbRunner.target, for example data/app.sqlite', 'warn');\n      return null;\n    }\n    if (badArg(target)) { ferrum.window.showMessage('The connection has characters I will not put in a command (a quote, $ or a backtick).', 'warn'); return null; }\n    return { engine, target };\n  }\n\n  async function show(html) {\n    if (panel) return panel.setHtml(html);\n    panel = await ferrum.window.showPanel({ title: 'Query result', html });\n    panel.onDidClose(() => (panel = null));\n  }\n\n  async function execute(sql) {\n    const cfg = await ensureReady();\n    if (!cfg) return;\n    if (!sql.trim()) return ferrum.window.showMessage('There is no SQL to run.', 'warn');\n    if (risky(sql) && !(await ferrum.window.confirm('This SQL can destroy data:\\n\\n' + sql.trim().slice(0, 300) + '\\n\\nRun it on ' + (cfg.engine === 'mysql' ? 'your MySQL server' : cfg.target.replace(/\\/\\/[^@/]*@/, '//***@')) + '?', 'Run it'))) return;\n    const p = plan(cfg.engine, cfg.target, sql, { args: cfg.target });\n    await ferrum.workspace.writeFile(QUERY, p.file);\n    if (!p.capture) {\n      await ferrum.terminal.run(p.command);\n      return ferrum.window.showMessage('Running in the terminal. The result of MySQL queries stays there.');\n    }\n    await ferrum.workspace.writeFile(OUT, '');\n    const t0 = Date.now();\n    await ferrum.terminal.run(p.command);\n    const limit = Math.max(5, Number(ferrum.settings.get('dbRunner.timeoutSeconds')) || 30) * 1000;\n    let text = '';\n    while (Date.now() - t0 < limit) {\n      await sleep(450);\n      try { text = await ferrum.workspace.readFile(OUT); } catch { text = ''; }\n      if (text.includes(MARK)) break;\n    }\n    const done = text.includes(MARK);\n    const body = cleanResult(text);\n    await show(resultPage(sql, body, done ? Date.now() - t0 : null, done ? 'Finished' : 'No result after ' + Math.round(limit / 1000) + ' s. Look at the terminal: the query may still be running or have failed.'));\n    if (!done) ferrum.window.showMessage('No result yet. Check the terminal for an error.', 'warn');\n  }\n\n  reg('dbRunner.run', async () => {\n    const doc = await ferrum.editor.active();\n    if (!doc) return ferrum.window.showMessage('Open a SQL file first.', 'warn');\n    let sql = doc.selectedText;\n    if (!sql) {\n      const lines = doc.text.split('\\n');\n      const { line, col } = doc.selection.start;\n      const offset = lines.slice(0, line).reduce((n, l) => n + l.length + 1, 0) + col;\n      sql = statementAt(doc.text, offset);\n    }\n    await execute(sql);\n  });\n  reg('dbRunner.runFile', async () => {\n    const doc = await ferrum.editor.active();\n    if (!doc) return ferrum.window.showMessage('Open a SQL file first.', 'warn');\n    await execute(doc.text);\n  });\n  reg('dbRunner.listTables', async () => {\n    const e = setting('engine') || 'postgres';\n    await execute(e === 'sqlite' ? \"SELECT name AS table_name FROM sqlite_master WHERE type = 'table' ORDER BY name;\" : e === 'mysql' ? 'SHOW TABLES;' : \"SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema NOT IN ('pg_catalog','information_schema') ORDER BY 1, 2;\");\n  });\n  reg('dbRunner.describeTable', async () => {\n    const t = safeTable(await ferrum.window.showInputBox({ prompt: 'Table name' }));\n    if (!t) return;\n    const e = setting('engine') || 'postgres';\n    await execute(e === 'sqlite' ? 'PRAGMA table_info(' + t + ');' : e === 'mysql' ? 'DESCRIBE ' + t + ';' : \"SELECT column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_name = '\" + t.split('.').pop() + \"' ORDER BY ordinal_position;\");\n  });\n  reg('dbRunner.countRows', async () => {\n    const t = safeTable(await ferrum.window.showInputBox({ prompt: 'Table name' }));\n    if (t) await execute('SELECT COUNT(*) AS rows FROM ' + t + ';');\n  });\n  reg('dbRunner.previewTable', async () => {\n    const t = safeTable(await ferrum.window.showInputBox({ prompt: 'Table name' }));\n    if (t) await execute('SELECT * FROM ' + t + ' LIMIT 20;');\n  });\n};\n\nexports.deactivate = function () {};\n"},"README.md":{"encoding":"utf8","content":"# Database Runner\n\nSelect some SQL (or just put the cursor in a statement) and press **Ctrl+Alt+Enter**. The query runs with the **client you already have installed**, and the result appears in a panel.\n\n| Engine | Needs | Result |\n| --- | --- | --- |\n| PostgreSQL | `psql` on your PATH | In a panel |\n| SQLite | `sqlite3` on your PATH | In a panel |\n| MySQL | `mysql` on your PATH | In the terminal only |\n\n## How it works, honestly\n\nThe extension cannot read the terminal. So it writes your SQL to `.ferrum/db/query.sql`, types the `psql` or `sqlite3` command in the terminal (you see it), and the client writes its output to `.ferrum/db/out.txt`, which the extension reads back. If the query fails, nothing arrives: the panel says so and the error is in the terminal. For MySQL there is no safe way to capture the output, so it runs in the terminal.\n\n## Safety\n\n- Anything that can destroy data (**DROP**, **TRUNCATE**, **ALTER**, or **UPDATE/DELETE with no WHERE**) shows the SQL and asks first.\n- The connection comes from your settings. A password inside a connection URL is typed in the terminal command: prefer a read-only database user, or a `.pgpass` file.\n- The settings cannot contain quotes, `$` or backticks.\n\nHandy commands: **List Tables**, **Describe a Table...**, **Show the First 20 Rows**, **Count Rows**.\n\n## Permissions\n\n**terminal**, **workspace.read** and **workspace.write** (for the two files in `.ferrum/db`).\n"}}}