From: larrybr Date: Fri, 18 Mar 2022 16:00:07 +0000 (+0000) Subject: Further migration to centralized error reporting X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=00c94befaeb96b69984361880329a92844f7d944;p=thirdparty%2Fsqlite.git Further migration to centralized error reporting FossilOrigin-Name: 898088008e2d15f89941db433f743594aea1351f353c12ce3932a39902dfb161 --- 00c94befaeb96b69984361880329a92844f7d944 diff --cc manifest index 40f6a930f6,bb70984547..6be225a162 --- a/manifest +++ b/manifest @@@ -1,5 -1,5 +1,5 @@@ - C For\sCLI,\sbegin\scentralizing\sargument\schecking/complaining. - D 2022-03-15T10:07:56.877 -C Make\sCLI\scomplain\sabout\sincomplete\sinput\sat\sEOF.\sFix\sfor\sregression\sreported\sat\shttps://sqlite.org/forum/forumpost/718f489a43be3197 -D 2022-03-15T17:57:42.959 ++C Further\smigration\sto\scentralized\serror\sreporting ++D 2022-03-18T16:00:07.658 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@@ -555,12 -553,11 +555,12 @@@ F src/random.c 097dc8b31b8fba5a9aca1697 F src/resolve.c ea935b87d6fb36c78b70cdc7b28561dc8f33f2ef37048389549c7b5ef9b0ba5e F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/select.c 4890a3cfee0bc60ff231c3a44db37968859ab0be156983dbcc0c096109832cdd - F src/shell.c.in af1a255482cd315901eb75134838dc9403fc04f8d755b7e75e2a85ee47df9a87 - F src/shext_linkage.h aba0d02f92fdea48904bf0b0805df9140a6b5dc20e9e76dad866e0568abaf458 - F src/sqlite.h.in e82ac380b307659d0892f502b742f825504e78729f4edaadce946003b9c00816 -F src/shell.c.in ec4b952f7d9c02919e8e8394be16bcbff55c105fd080895602d10fc8fa5328b8 ++F src/shell.c.in e1214665c269fad77b3cb6cb679939337a7259fecf189276b398a88d4653d4d6 ++F src/shext_linkage.h 7c8ac3970d81c20c752e58860cc1a0f4521889152766a6cbcbd2734fcba6232d + F src/sqlite.h.in 5845213799feca09cd69d18ff841a85fe0df31021f46aaa1797e703e80dc1d70 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h a95cb9ed106e3d39e2118e4dcc15a14faec3fa50d0093425083d340d9dfd96e6 - F src/sqliteInt.h 4a8b7d685781df55ce546b6c72bdbd72bb23ca93f59b6d70d81ed6df22607d75 + F src/sqliteInt.h 2ce7d868630ccd70ffd4b15d46b59ccf7daf89198993b62ed6e4a165d3511280 F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@@ -605,7 -602,6 +605,7 @@@ F src/test_quota.h 2a8ad1952d1d2ca9af0c F src/test_rtree.c 671f3fae50ff116ef2e32a3bf1fe21b5615b4b7b F src/test_schema.c f5d6067dfc2f2845c4dd56df63e66ee826fb23877855c785f75cc2ca83fd0c1b F src/test_server.c a2615049954cbb9cfb4a62e18e2f0616e4dc38fe - F src/test_shellext.c 295696a0f7e8663f80c9e63e90ce2dba7e42739a4e0d498e040797a5610cbd60 ++F src/test_shellext.c 049533eae21ff0c041ff97d2681f3005e612fc33b3824ef7b6525a719180115e F src/test_sqllog.c 540feaea7280cd5f926168aee9deb1065ae136d0bbbe7361e2ef3541783e187a F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e F src/test_syscall.c 1073306ba2e9bfc886771871a13d3de281ed3939 @@@ -1392,15 -1388,14 +1392,15 @@@ F test/sharedA.test 49d87ec54ab640fbbc3 F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 - F test/shell1.test 2a8d73538f672c5b5b238ba2fa15e4fbaea47454f372dd5b683be51727af33cc - F test/shell2.test aed8773dd236517149a6abaeccf94f066e5a0c871108e8d21889122132dd51ba -F test/shell1.test b224e0793c5f48aa3749e65d8c64b93a30731bd206f2e41e6c5f1bee1bdb16c6 -F test/shell2.test 7a3a23a9f57b99453f1679b1fe8072cb30e382a622874c0c4d97695fadb0a787 -F test/shell3.test a50628ab1d78d90889d9d3f32fb2c084ee15674771e96afe954aaa0accd1de3c -F test/shell4.test 8f6c0fce4abed19a8a7f7262517149812a04caa905d01bdc8f5e92573504b759 -F test/shell5.test 0a9920d81fae28c45cd5dbd1deb809487a23c5f4b422a49f9d31c85f926d4a9c ++F test/shell1.test f78eb024b6466668d281beed69267ba7bc3d76e9244c7d2c6b4bfe9c292b27f4 ++F test/shell2.test fc6bb55f5ceaaffa284cb994aa00fd56f7ead09949c9db01c3846d65a76a7748 +F test/shell3.test 4ddea2bd182e7e03249911b23ae249e7cb8a91cdc86e695198725affabe8ecd3 - F test/shell4.test 867e0675d7b096d6b93de534541e07c7f5ffafa3e6612695ecf55180802e1115 - F test/shell5.test 21f3d54a0bc73588efe023c2af846d48d04420d8df7f4ee0fc465719fcbad756 ++F test/shell4.test b232688061cce531f42ec067f3b5760e31d12409e566e2ae230951036dd156f1 ++F test/shell5.test 04b46462b3297de7195aaed99a0cd99fdb9f8de211a96abea46a81db55c5ef77 F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3 F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f F test/shell8.test 388471d16e4de767333107e30653983f186232c0e863f4490bb230419e830aae - F test/shell9.test 9c6512384f4dcdaddd7c0efd0b1bd54cdac3c46dd82d2b30f877d90e666d7a2c ++F test/shell9.test 24042184644d4fd04e4b81885d7001e7e76d6826992242be72246009b3530eda F test/shmlock.test 3dbf017d34ab0c60abe6a44e447d3552154bd0c87b41eaf5ceacd408dd13fda5 F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5 @@@ -1949,8 -1944,8 +1949,8 @@@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a9 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 - P 9be5e0b503f63dbbf1375460ce74a0aad6f202c2e5b2a748bba1d3af82c7d2d8 - R 859f2736e1cb4ffb3cf8130c1ca2c5f2 -P fff1243b594c190d15f14b7ca4e60d23519cd15134f275991c685966fcc24145 -R 419d7db42dfecc265d3cd4cb01355313 ++P 3457f87c5db7a28eb6a47223f7853cd066245edfe1882feabbdb8ce9555f872e 72029cf7cdb266703cc8716102dbba8e6f2666e1f47409f42c39528795757b73 ++R e4977d7e43e565456c0cd3ce58d731ce U larrybr - Z 1a5305d5e77e7119519f6839e7b8c711 -Z e6c264c989ed1b7dd7a09ef4a9447bb1 ++Z 7cb1fae332b9ca509f4fbb346c55037d # Remove this line to create a well-formed Fossil manifest. diff --cc manifest.uuid index 706134ee08,a0ac00e2f8..4f13331843 --- a/manifest.uuid +++ b/manifest.uuid @@@ -1,1 -1,1 +1,1 @@@ - 3457f87c5db7a28eb6a47223f7853cd066245edfe1882feabbdb8ce9555f872e -72029cf7cdb266703cc8716102dbba8e6f2666e1f47409f42c39528795757b73 ++898088008e2d15f89941db433f743594aea1351f353c12ce3932a39902dfb161 diff --cc src/shell.c.in index 5a4aafe85f,c0b5898e6e..4823c234ff --- a/src/shell.c.in +++ b/src/shell.c.in @@@ -247,8 -229,6 +247,11 @@@ static void setTextMode(FILE *file, in # define setTextMode(X,Y) #endif ++/* Use a shorter form of this ubiquitously used, (varargs) API: */ ++#define smprintf sqlite3_mprintf ++ +static unsigned numCommands; +static FILE *currentOutputFile(ShellExState *p); /* True if the timer is enabled */ static int enableTimer = 0; @@@ -474,7 -453,7 +477,7 @@@ void utf8_printf(FILE *out, const char char *z1 = sqlite3_vmprintf(zFormat, ap); char *z2 = sqlite3_win32_utf8_to_mbcs_v2(z1, 0); sqlite3_free(z1); -- fputs(z2, out); ++ if( z2 ) fputs(z2, out); sqlite3_free(z2); }else{ vfprintf(out, zFormat, ap); @@@ -974,9 -867,9 +977,8 @@@ static char *shellFakeSchema char *zDiv = "("; int nRow = 0; - SHELL_ASSIGN_OOM_CHECK - (zSql, sqlite3_mprintf("PRAGMA \"%w\".table_info=%Q;", - zSchema ? zSchema : "main", zName)); - zSql = sqlite3_mprintf("PRAGMA \"%w\".table_info=%Q;", - zSchema ? zSchema : "main", zName); - shell_check_oom(zSql); ++ SHELL_ASSIGN_OOM_CHECK(zSql, smprintf("PRAGMA \"%w\".table_info=%Q;", ++ zSchema ? zSchema : "main", zName)); sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); initText(&s); @@@ -1021,10 -914,10 +1023,9 @@@ static void shellModuleSchema char *zFake; UNUSED_PARAMETER(nVal); zName = (const char*)sqlite3_value_text(apVal[0]); - zFake = zName ? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0; + zFake = zName? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0; if( zFake ){ -- sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake), -- -1, sqlite3_free); ++ sqlite3_result_text(pCtx, smprintf("/* %s */", zFake), -1, sqlite3_free); free(zFake); } } @@@ -1076,9 -969,9 +1077,9 @@@ static void shellAddSchemaName if( zSchema ){ char cQuote = quoteChar(zSchema); if( cQuote && sqlite3_stricmp(zSchema,"temp")!=0 ){ -- z = sqlite3_mprintf("%.*s \"%w\".%s", n+7, zIn, zSchema, zIn+n+8); ++ z = smprintf("%.*s \"%w\".%s", n+7, zIn, zSchema, zIn+n+8); }else{ -- z = sqlite3_mprintf("%.*s %s.%s", n+7, zIn, zSchema, zIn+n+8); ++ z = smprintf("%.*s %s.%s", n+7, zIn, zSchema, zIn+n+8); } } if( zName @@@ -1086,9 -979,9 +1087,9 @@@ && (zFake = shellFakeSchema(db, zSchema, zName))!=0 ){ if( z==0 ){ -- z = sqlite3_mprintf("%s\n/* %s */", zIn, zFake); ++ z = smprintf("%s\n/* %s */", zIn, zFake); }else{ -- z = sqlite3_mprintf("%z\n/* %s */", z, zFake); ++ z = smprintf("%z\n/* %s */", z, zFake); } free(zFake); } @@@ -1632,7 -1368,7 +1633,7 @@@ static void editFunc if( zTempFile==0 ){ sqlite3_uint64 r = 0; sqlite3_randomness(sizeof(r), &r); -- zTempFile = sqlite3_mprintf("temp%llx", r); ++ zTempFile = smprintf("temp%llx", r); if( zTempFile==0 ){ sqlite3_result_error_nomem(context); return; @@@ -1661,7 -1397,7 +1662,7 @@@ sqlite3_result_error(context, "edit() could not write the whole file", -1); goto edit_func_end; } -- zCmd = sqlite3_mprintf("%s \"%s\"", zEditor, zTempFile); ++ zCmd = smprintf("%s \"%s\"", zEditor, zTempFile); if( zCmd==0 ){ sqlite3_result_error_nomem(context); goto edit_func_end; @@@ -2090,8 -1749,8 +2091,8 @@@ static void output_csv(ShellExState *ps break; } } - if( i==0 || strstr(z, p->colSeparator)!=0 ){ - char *zQuoted = sqlite3_mprintf("\"%w\"", z); + if( i==0 || strstr(z, zColSep)!=0 ){ - char *zQuoted = sqlite3_mprintf("\"%w\"", z); ++ char *zQuoted = smprintf("\"%w\"", z); shell_check_oom(zQuoted); utf8_printf(out, "%s", zQuoted); sqlite3_free(zQuoted); @@@ -2507,10 -2143,10 +2508,10 @@@ static int shell_callback if( sqlite3_strlike("CREATE VIEW%", azArg[0], 0)==0 || sqlite3_strlike("CREATE TRIG%", azArg[0], 0)==0 ){ - utf8_printf(p->out, "%s;\n", azArg[0]); + utf8_printf(out, "%s;\n", azArg[0]); break; } -- z = sqlite3_mprintf("%s", azArg[0]); ++ z = smprintf("%s", azArg[0]); shell_check_oom(z); j = 0; for(i=0; IsSpace(z[i]); i++){} @@@ -2636,38 -2272,38 +2637,38 @@@ } case MODE_Insert: { if( azArg==0 ) break; - utf8_printf(p->out,"INSERT INTO %s",p->zDestTable); - if( p->showHeader ){ - raw_printf(p->out,"("); + utf8_printf(out,"INSERT INTO %s",psx->zDestTable); + if( psi->showHeader ){ + raw_printf(out,"("); for(i=0; i0 ) raw_printf(p->out, ","); + if( i>0 ) raw_printf(out, ","); if( quoteChar(azCol[i]) ){ -- char *z = sqlite3_mprintf("\"%w\"", azCol[i]); ++ char *z = smprintf("\"%w\"", azCol[i]); shell_check_oom(z); - utf8_printf(p->out, "%s", z); + utf8_printf(out, "%s", z); sqlite3_free(z); }else{ - raw_printf(p->out, "%s", azCol[i]); + raw_printf(out, "%s", azCol[i]); } } - raw_printf(p->out,")"); + raw_printf(out,")"); } - p->cnt++; + psx->resultCount++; for(i=0; iout, i>0 ? "," : " VALUES("); + raw_printf(out, i>0 ? "," : " VALUES("); if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ - utf8_printf(p->out,"NULL"); + utf8_printf(out,"NULL"); }else if( aiType && aiType[i]==SQLITE_TEXT ){ - if( ShellHasFlag(p, SHFLG_Newlines) ){ - output_quoted_string(p->out, azArg[i]); + if( ShellHasFlag(psx, SHFLG_Newlines) ){ + output_quoted_string(out, azArg[i]); }else{ - output_quoted_escaped_string(p->out, azArg[i]); + output_quoted_escaped_string(out, azArg[i]); } }else if( aiType && aiType[i]==SQLITE_INTEGER ){ - utf8_printf(p->out,"%s", azArg[i]); + utf8_printf(out,"%s", azArg[i]); }else if( aiType && aiType[i]==SQLITE_FLOAT ){ char z[50]; - double r = sqlite3_column_double(p->pStmt, i); + double r = sqlite3_column_double(psi->pStmt, i); sqlite3_uint64 ur; memcpy(&ur,&r,sizeof(r)); if( ur==0x7ff0000000000000LL ){ @@@ -2924,29 -2560,25 +2925,29 @@@ static char *shell_error_context(const || zSql==0 || (iOffset = sqlite3_error_offset(db))<0 ){ - zMsg = sqlite3_mprintf(""); - return sqlite3_mprintf(""); - } - while( iOffset>50 ){ - iOffset--; - zSql++; - while( (zSql[0]&0xc0)==0x80 ){ zSql++; iOffset--; } - } - len = strlen(zSql); - if( len>78 ){ - len = 78; - while( (zSql[len]&0xc0)==0x80 ) len--; - } - zCode = sqlite3_mprintf("%.*s", len, zSql); - for(i=0; zCode[i]; i++){ if( IsSpace(zSql[i]) ) zCode[i] = ' '; } - if( iOffset<25 ){ - zMsg = sqlite3_mprintf("\n %z\n %*s^--- error here", zCode, iOffset, ""); ++ zMsg = smprintf(""); }else{ - zMsg = sqlite3_mprintf("\n %z\n %*serror here ---^", zCode, iOffset-14, ""); + while( iOffset>50 ){ + iOffset--; + zSql++; + while( (zSql[0]&0xc0)==0x80 ){ zSql++; iOffset--; } + } + len = strlen(zSql); + if( len>78 ){ + len = 78; + while( (zSql[len]&0xc0)==0x80 ) len--; + } - zCode = sqlite3_mprintf("%.*s", len, zSql); ++ zCode = smprintf("%.*s", len, zSql); + for(i=0; zCode[i]; i++){ if( IsSpace(zSql[i]) ) zCode[i] = ' '; } + if( iOffset<25 ){ - zMsg = sqlite3_mprintf("\n %z\n %*s^--- error here", ++ zMsg = smprintf("\n %z\n %*s^--- error here", + zCode, iOffset, ""); + }else{ - zMsg = sqlite3_mprintf("\n %z\n %*serror here ---^", ++ zMsg = smprintf("\n %z\n %*serror here ---^", + zCode, iOffset-14, ""); + } } + shell_check_oom(zMsg); return zMsg; } @@@ -3703,14 -3290,14 +3704,14 @@@ static char *translateForDisplayAndDup static char *quoted_column(sqlite3_stmt *pStmt, int i){ switch( sqlite3_column_type(pStmt, i) ){ case SQLITE_NULL: { -- return sqlite3_mprintf("NULL"); ++ return smprintf("NULL"); } case SQLITE_INTEGER: case SQLITE_FLOAT: { -- return sqlite3_mprintf("%s",sqlite3_column_text(pStmt,i)); ++ return smprintf("%s",sqlite3_column_text(pStmt,i)); } case SQLITE_TEXT: { -- return sqlite3_mprintf("%Q",sqlite3_column_text(pStmt,i)); ++ return smprintf("%Q",sqlite3_column_text(pStmt,i)); } case SQLITE_BLOB: { int j; @@@ -4103,79 -3695,64 +4104,16 @@@ static int expertFinish return rc; } --/* --** Implementation of ".expert" dot command. --*/ --static int expertDotCommand( - ShellInState *psi, /* Current shell tool state */ - ShellState *pState, /* Current shell tool state */ -- char **azArg, /* Array of arguments passed to dot command */ -- int nArg /* Number of entries in azArg[] */ --){ -- int rc = SQLITE_OK; -- char *zErr = 0; -- int i; -- int iSample = 0; -- - if( psi->bSafeMode ){ - raw_printf(STD_ERR, - "Cannot run experimental commands such as \"%s\" in safe mode\n", - azArg[0]); - return 1; - } - assert( psi->expert.pExpert==0 ); - memset(&psi->expert, 0, sizeof(ExpertInfo)); - assert( pState->expert.pExpert==0 ); - memset(&pState->expert, 0, sizeof(ExpertInfo)); -- -- for(i=1; rc==SQLITE_OK && i=2 && 0==strncmp(z, "-verbose", n) ){ - psi->expert.bVerbose = 1; - pState->expert.bVerbose = 1; -- } -- else if( n>=2 && 0==strncmp(z, "-sample", n) ){ -- if( i==(nArg-1) ){ - raw_printf(STD_ERR, "option requires an argument: %s\n", z); - raw_printf(stderr, "option requires an argument: %s\n", z); -- rc = SQLITE_ERROR; -- }else{ -- iSample = (int)integerValue(azArg[++i]); -- if( iSample<0 || iSample>100 ){ - raw_printf(STD_ERR, "value out of range: %s\n", azArg[i]); - raw_printf(stderr, "value out of range: %s\n", azArg[i]); -- rc = SQLITE_ERROR; -- } -- } -- } -- else{ - raw_printf(STD_ERR, "unknown option: %s\n", z); - raw_printf(stderr, "unknown option: %s\n", z); -- rc = SQLITE_ERROR; -- } -- } -- -- if( rc==SQLITE_OK ){ - psi->expert.pExpert = sqlite3_expert_new(DBI(psi), &zErr); - if( psi->expert.pExpert==0 ){ - raw_printf(STD_ERR, "sqlite3_expert_new: %s\n", - zErr ? zErr : "out of memory"); - pState->expert.pExpert = sqlite3_expert_new(pState->db, &zErr); - if( pState->expert.pExpert==0 ){ - raw_printf(stderr, "sqlite3_expert_new: %s\n", zErr ? zErr : "out of memory"); -- rc = SQLITE_ERROR; -- }else{ -- sqlite3_expert_config( - psi->expert.pExpert, EXPERT_CONFIG_SAMPLE, iSample - pState->expert.pExpert, EXPERT_CONFIG_SAMPLE, iSample -- ); -- } -- } - - return rc; - } - sqlite3_free(zErr); +#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ - return rc; +/* This saves little code and source volume, but provides a nice breakpoint. +** It is called when input is ready to be run, or would be run if it was +** not about to dumped as a no-op. Someday, a tracing facility may enhance +** this function's output to show where the input group has originated. +*/ +static void echo_group_input(ShellInState *psi, const char *zDo){ + if( (psi->shellFlgs & SHFLG_Echo)!=0 ) printf("%s\n", zDo); } -#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ /* ** Execute a statement or set of statements. Print @@@ -4242,10 -3821,10 +4180,10 @@@ static int shell_exec int triggerEQP = 0; disable_debug_trace_modes(); sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, -1, &triggerEQP); - if( pArg->autoEQP>=AUTOEQP_trigger ){ + if( psi->autoEQP>=AUTOEQP_trigger ){ sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 1, 0); } -- zEQP = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", zStmtSql); ++ zEQP = smprintf("EXPLAIN QUERY PLAN %s", zStmtSql); shell_check_oom(zEQP); rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0); if( rc==SQLITE_OK ){ @@@ -4261,9 -3840,9 +4199,9 @@@ } sqlite3_finalize(pExplain); sqlite3_free(zEQP); - if( pArg->autoEQP>=AUTOEQP_full ){ + if( psi->autoEQP>=AUTOEQP_full ){ /* Also do an EXPLAIN for ".eqp full" mode */ -- zEQP = sqlite3_mprintf("EXPLAIN %s", zStmtSql); ++ zEQP = smprintf("EXPLAIN %s", zStmtSql); shell_check_oom(zEQP); rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0); if( rc==SQLITE_OK ){ @@@ -4372,11 -3952,12 +4310,11 @@@ static char **tableColumnList(sqlite3 * int nAlloc = 0; int nPK = 0; /* Number of PRIMARY KEY columns seen */ int isIPK = 0; /* True if one PRIMARY KEY column of type INTEGER */ - int preserveRowid = ShellHasFlag(p, SHFLG_PreserveRowid); int rc; -- zSql = sqlite3_mprintf("PRAGMA table_info=%Q", zTab); ++ zSql = smprintf("PRAGMA table_info=%Q", zTab); shell_check_oom(zSql); - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); if( rc ) return 0; while( sqlite3_step(pStmt)==SQLITE_ROW ){ @@@ -4385,7 -3966,7 +4323,7 @@@ azCol = sqlite3_realloc(azCol, nAlloc*sizeof(azCol[0])); shell_check_oom(azCol); } -- azCol[++nCol] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 1)); ++ azCol[++nCol] = smprintf("%s", sqlite3_column_text(pStmt, 1)); shell_check_oom(azCol[nCol]); if( sqlite3_column_int(pStmt, 5) ){ nPK++; @@@ -4418,10 -3999,10 +4356,10 @@@ ** there is a "pk" entry in "PRAGMA index_list". There will be ** no "pk" index if the PRIMARY KEY really is an alias for the ROWID. */ -- zSql = sqlite3_mprintf("SELECT 1 FROM pragma_index_list(%Q)" -- " WHERE origin='pk'", zTab); ++ zSql = smprintf("SELECT 1 FROM pragma_index_list(%Q)" ++ " WHERE origin='pk'", zTab); shell_check_oom(zSql); - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); if( rc ){ freeColumnList(azCol); @@@ -4505,16 -4085,16 +4443,15 @@@ static int dump_callback(void *pArg, in /* no-op */ }else if( strncmp(zSql, "CREATE VIRTUAL TABLE", 20)==0 ){ char *zIns; - if( !p->writableSchema ){ - raw_printf(p->out, "PRAGMA writable_schema=ON;\n"); - p->writableSchema = 1; - } - zIns = sqlite3_mprintf( - "INSERT INTO sqlite_schema(type,name,tbl_name,rootpage,sql)" - "VALUES('table','%q','%q',0,'%q');", - zTable, zTable, zSql); + if( !psi->writableSchema ){ + raw_printf(psi->out, "PRAGMA writable_schema=ON;\n"); + psi->writableSchema = 1; + } - zIns = sqlite3_mprintf( - "INSERT INTO sqlite_schema(type,name,tbl_name,rootpage,sql)" - "VALUES('table','%q','%q',0,'%q');", - zTable, zTable, zSql); ++ zIns = smprintf("INSERT INTO sqlite_schema(type,name,tbl_name,rootpage,sql)" ++ "VALUES('table','%q','%q',0,'%q');", ++ zTable, zTable, zSql); shell_check_oom(zIns); - utf8_printf(p->out, "%s\n", zIns); + utf8_printf(psi->out, "%s\n", zIns); sqlite3_free(zIns); return 0; }else{ @@@ -4629,631 -4208,427 +4566,631 @@@ static int run_schema_dump_query return rc; } +/* Configure help text generation to have coalesced secondary help lines + * with trailing newlines on all help lines. This allow help text to be + * representable as an array of two C-strings per meta-command. + */ +DISPATCH_CONFIG[ + HELP_COALESCE=1 +]; +#define HELP_TEXT_FMTP ".%s" +#define HELP_TEXT_FMTS "%s" +/* Above HELP_COALESCE config and HELP_TEXT_FMT PP vars must track. + * Alternative is 0, ".%s\n" and "%s\n" . + */ + +/* Forward references */ +static int showHelp(FILE *out, const char *zPattern, ShellExState *); - static int process_input(ShellInState *psx); ++static DotCmdRC process_input(ShellInState *psx); +static MetaCommand *builtInCommand(int ix); + /* -** Text of help messages. +** Read the content of file zName into memory obtained from sqlite3_malloc64() +** and return a pointer to the buffer. The caller is responsible for freeing +** the memory. +** +** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes +** read. ** -** The help text for each individual command begins with a line that starts -** with ".". Subsequent lines are supplimental information. +** For convenience, a nul-terminator byte is always appended to the data read +** from the file before the buffer is returned. This byte is not included in +** the final value of (*pnByte), if applicable. ** -** There must be two or more spaces between the end of the command and the -** start of the description of what that command does. +** NULL is returned if any error is encountered. The final value of *pnByte +** is undefined in this case. */ -static const char *(azHelp[]) = { -#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) - ".archive ... Manage SQL archives", - " Each command must have exactly one of the following options:", - " -c, --create Create a new archive", - " -u, --update Add or update files with changed mtime", - " -i, --insert Like -u but always add even if unchanged", - " -r, --remove Remove files from archive", - " -t, --list List contents of archive", - " -x, --extract Extract files from archive", - " Optional arguments:", - " -v, --verbose Print each filename as it is processed", - " -f FILE, --file FILE Use archive FILE (default is current db)", - " -a FILE, --append FILE Open FILE using the apndvfs VFS", - " -C DIR, --directory DIR Read/extract files from directory DIR", - " -g, --glob Use glob matching for names in archive", - " -n, --dryrun Show the SQL that would have occurred", - " Examples:", - " .ar -cf ARCHIVE foo bar # Create ARCHIVE from files foo and bar", - " .ar -tf ARCHIVE # List members of ARCHIVE", - " .ar -xvf ARCHIVE # Verbosely extract files from ARCHIVE", - " See also:", - " http://sqlite.org/cli.html#sqlite_archive_support", -#endif -#ifndef SQLITE_OMIT_AUTHORIZATION - ".auth ON|OFF Show authorizer callbacks", +static char *readFile(const char *zName, int *pnByte){ + FILE *in = fopen(zName, "rb"); + long nIn; + size_t nRead; + char *pBuf; + if( in==0 ) return 0; + fseek(in, 0, SEEK_END); + nIn = ftell(in); + rewind(in); + pBuf = sqlite3_malloc64( nIn+1 ); + if( pBuf==0 ){ fclose(in); return 0; } + nRead = fread(pBuf, nIn, 1, in); + fclose(in); + if( nRead!=1 ){ + sqlite3_free(pBuf); + return 0; + } + pBuf[nIn] = 0; + if( pnByte ) *pnByte = nIn; + return pBuf; +} + +#if defined(SQLITE_ENABLE_SESSION) +/* +** Close a single OpenSession object and release all of its associated +** resources. +*/ +static void session_close(OpenSession *pSession){ + int i; + sqlite3session_delete(pSession->p); + sqlite3_free(pSession->zName); + for(i=0; inFilter; i++){ + sqlite3_free(pSession->azFilter[i]); + } + sqlite3_free(pSession->azFilter); + memset(pSession, 0, sizeof(OpenSession)); +} #endif - ".backup ?DB? FILE Backup DB (default \"main\") to FILE", - " Options:", - " --append Use the appendvfs", - " --async Write to FILE without journal and fsync()", - ".bail on|off Stop after hitting an error. Default OFF", - ".binary on|off Turn binary output on or off. Default OFF", - ".cd DIRECTORY Change the working directory to DIRECTORY", - ".changes on|off Show number of rows changed by SQL", - ".check GLOB Fail if output since .testcase does not match", - ".clone NEWDB Clone data into NEWDB from the existing database", - ".connection [close] [#] Open or close an auxiliary database connection", - ".databases List names and files of attached databases", - ".dbconfig ?op? ?val? List or change sqlite3_db_config() options", - ".dbinfo ?DB? Show status information about the database", - ".dump ?OBJECTS? Render database content as SQL", - " Options:", - " --data-only Output only INSERT statements", - " --newlines Allow unescaped newline characters in output", - " --nosys Omit system tables (ex: \"sqlite_stat1\")", - " --preserve-rowids Include ROWID values in the output", - " OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump", - " Additional LIKE patterns can be given in subsequent arguments", - ".echo on|off Turn command echo on or off", - ".eqp on|off|full|... Enable or disable automatic EXPLAIN QUERY PLAN", - " Other Modes:", -#ifdef SQLITE_DEBUG - " test Show raw EXPLAIN QUERY PLAN output", - " trace Like \"full\" but enable \"PRAGMA vdbe_trace\"", -#endif - " trigger Like \"full\" but also show trigger bytecode", - ".excel Display the output of next command in spreadsheet", - " --bom Put a UTF8 byte-order mark on intermediate file", - ".exit ?CODE? Exit this program with return-code CODE", - ".expert EXPERIMENTAL. Suggest indexes for queries", - ".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto", - ".filectrl CMD ... Run various sqlite3_file_control() operations", - " --schema SCHEMA Use SCHEMA instead of \"main\"", - " --help Show CMD details", - ".fullschema ?--indent? Show schema and the content of sqlite_stat tables", - ".headers on|off Turn display of headers on or off", - ".help ?-all? ?PATTERN? Show help text for PATTERN", - ".import FILE TABLE Import data from FILE into TABLE", - " Options:", - " --ascii Use \\037 and \\036 as column and row separators", - " --csv Use , and \\n as column and row separators", - " --skip N Skip the first N rows of input", - " --schema S Target table to be S.TABLE", - " -v \"Verbose\" - increase auxiliary output", - " Notes:", - " * If TABLE does not exist, it is created. The first row of input", - " determines the column names.", - " * If neither --csv or --ascii are used, the input mode is derived", - " from the \".mode\" output mode", - " * If FILE begins with \"|\" then it is a command that generates the", - " input text.", -#ifndef SQLITE_OMIT_TEST_CONTROL - ".imposter INDEX TABLE Create imposter table TABLE on index INDEX", -#endif - ".indexes ?TABLE? Show names of indexes", - " If TABLE is specified, only show indexes for", - " tables matching TABLE using the LIKE operator.", -#ifdef SQLITE_ENABLE_IOTRACE - ".iotrace FILE Enable I/O diagnostic logging to FILE", -#endif - ".limit ?LIMIT? ?VAL? Display or change the value of an SQLITE_LIMIT", - ".lint OPTIONS Report potential schema issues.", - " Options:", - " fkey-indexes Find missing foreign key indexes", -#ifndef SQLITE_OMIT_LOAD_EXTENSION - ".load FILE ?ENTRY? Load an extension library", -#endif - ".log FILE|off Turn logging on or off. FILE can be stderr/stdout", - ".mode MODE ?OPTIONS? Set output mode", - " MODE is one of:", - " ascii Columns/rows delimited by 0x1F and 0x1E", - " box Tables using unicode box-drawing characters", - " csv Comma-separated values", - " column Output in columns. (See .width)", - " html HTML code", - " insert SQL insert statements for TABLE", - " json Results in a JSON array", - " line One value per line", - " list Values delimited by \"|\"", - " markdown Markdown table format", - " qbox Shorthand for \"box --width 60 --quote\"", - " quote Escape answers as for SQL", - " table ASCII-art table", - " tabs Tab-separated values", - " tcl TCL list elements", - " OPTIONS: (for columnar modes or insert mode):", - " --wrap N Wrap output lines to no longer than N characters", - " --wordwrap B Wrap or not at word boundaries per B (on/off)", - " --ww Shorthand for \"--wordwrap 1\"", - " --quote Quote output text as SQL literals", - " --noquote Do not quote output text", - " TABLE The name of SQL table used for \"insert\" mode", - ".nonce STRING Suspend safe mode for one command if nonce matches", - ".nullvalue STRING Use STRING in place of NULL values", - ".once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE", - " If FILE begins with '|' then open as a pipe", - " --bom Put a UTF8 byte-order mark at the beginning", - " -e Send output to the system text editor", - " -x Send output as CSV to a spreadsheet (same as \".excel\")", - ".open ?OPTIONS? ?FILE? Close existing database and reopen FILE", - " Options:", - " --append Use appendvfs to append database to the end of FILE", -#ifndef SQLITE_OMIT_DESERIALIZE - " --deserialize Load into memory using sqlite3_deserialize()", - " --hexdb Load the output of \"dbtotxt\" as an in-memory db", - " --maxsize N Maximum size for --hexdb or --deserialized database", -#endif - " --new Initialize FILE to an empty database", - " --nofollow Do not follow symbolic links", - " --readonly Open FILE readonly", - " --zip FILE is a ZIP archive", - ".output ?FILE? Send output to FILE or stdout if FILE is omitted", - " If FILE begins with '|' then open it as a pipe.", - " Options:", - " --bom Prefix output with a UTF8 byte-order mark", - " -e Send output to the system text editor", - " -x Send output as CSV to a spreadsheet", - ".parameter CMD ... Manage SQL parameter bindings", - " clear Erase all bindings", - " init Initialize the TEMP table that holds bindings", - " list List the current parameter bindings", - " set PARAMETER VALUE Given SQL parameter PARAMETER a value of VALUE", - " PARAMETER should start with one of: $ : @ ?", - " unset PARAMETER Remove PARAMETER from the binding table", - ".print STRING... Print literal STRING", -#ifndef SQLITE_OMIT_PROGRESS_CALLBACK - ".progress N Invoke progress handler after every N opcodes", - " --limit N Interrupt after N progress callbacks", - " --once Do no more than one progress interrupt", - " --quiet|-q No output except at interrupts", - " --reset Reset the count for each input and interrupt", -#endif - ".prompt MAIN CONTINUE Replace the standard prompts", - ".quit Exit this program", - ".read FILE Read input from FILE or command output", - " If FILE begins with \"|\", it is a command that generates the input.", -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) - ".recover Recover as much data as possible from corrupt db.", - " --freelist-corrupt Assume the freelist is corrupt", - " --recovery-db NAME Store recovery metadata in database file NAME", - " --lost-and-found TABLE Alternative name for the lost-and-found table", - " --no-rowids Do not attempt to recover rowid values", - " that are not also INTEGER PRIMARY KEYs", -#endif - ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE", - ".save ?OPTIONS? FILE Write database to FILE (an alias for .backup ...)", - ".scanstats on|off Turn sqlite3_stmt_scanstatus() metrics on or off", - ".schema ?PATTERN? Show the CREATE statements matching PATTERN", - " Options:", - " --indent Try to pretty-print the schema", - " --nosys Omit objects whose names start with \"sqlite_\"", - ".selftest ?OPTIONS? Run tests defined in the SELFTEST table", - " Options:", - " --init Create a new SELFTEST table", - " -v Verbose output", - ".separator COL ?ROW? Change the column and row separators", + +/* +** Close all OpenSession objects and release all associated resources. +*/ #if defined(SQLITE_ENABLE_SESSION) - ".session ?NAME? CMD ... Create or control sessions", - " Subcommands:", - " attach TABLE Attach TABLE", - " changeset FILE Write a changeset into FILE", - " close Close one session", - " enable ?BOOLEAN? Set or query the enable bit", - " filter GLOB... Reject tables matching GLOBs", - " indirect ?BOOLEAN? Mark or query the indirect status", - " isempty Query whether the session is empty", - " list List currently open session names", - " open DB NAME Open a new session on DB", - " patchset FILE Write a patchset into FILE", - " If ?NAME? is omitted, the first defined session is used.", -#endif - ".sha3sum ... Compute a SHA3 hash of database content", - " Options:", - " --schema Also hash the sqlite_schema table", - " --sha3-224 Use the sha3-224 algorithm", - " --sha3-256 Use the sha3-256 algorithm (default)", - " --sha3-384 Use the sha3-384 algorithm", - " --sha3-512 Use the sha3-512 algorithm", - " Any other argument is a LIKE pattern for tables to hash", -#ifndef SQLITE_NOHAVE_SYSTEM - ".shell CMD ARGS... Run CMD ARGS... in a system shell", -#endif - ".show Show the current values for various settings", - ".stats ?ARG? Show stats or turn stats on or off", - " off Turn off automatic stat display", - " on Turn on automatic stat display", - " stmt Show statement stats", - " vmstep Show the virtual machine step count only", -#ifndef SQLITE_NOHAVE_SYSTEM - ".system CMD ARGS... Run CMD ARGS... in a system shell", -#endif - ".tables ?TABLE? List names of tables matching LIKE pattern TABLE", - ".testcase NAME Begin redirecting output to 'testcase-out.txt'", - ".testctrl CMD ... Run various sqlite3_test_control() operations", - " Run \".testctrl\" with no arguments for details", - ".timeout MS Try opening locked tables for MS milliseconds", - ".timer on|off Turn SQL timer on or off", -#ifndef SQLITE_OMIT_TRACE - ".trace ?OPTIONS? Output each SQL statement as it is run", - " FILE Send output to FILE", - " stdout Send output to stdout", - " stderr Send output to stderr", - " off Disable tracing", - " --expanded Expand query parameters", -#ifdef SQLITE_ENABLE_NORMALIZE - " --normalized Normal the SQL statements", +static void session_close_all(ShellInState *psi, int i){ + int j; + struct AuxDb *pAuxDb = i<0 ? psi->pAuxDb : &psi->aAuxDb[i]; + for(j=0; jnSession; j++){ + session_close(&pAuxDb->aSession[j]); + } + pAuxDb->nSession = 0; +} +#else +# define session_close_all(X,Y) #endif - " --plain Show SQL as it is input", - " --stmt Trace statement execution (SQLITE_TRACE_STMT)", - " --profile Profile statements (SQLITE_TRACE_PROFILE)", - " --row Trace each row (SQLITE_TRACE_ROW)", - " --close Trace connection close (SQLITE_TRACE_CLOSE)", -#endif /* SQLITE_OMIT_TRACE */ -#ifdef SQLITE_DEBUG - ".unmodule NAME ... Unregister virtual table modules", - " --allexcept Unregister everything except those named", + +/* +** Implementation of the xFilter function for an open session. Omit +** any tables named by ".session filter" but let all other table through. +*/ +#if defined(SQLITE_ENABLE_SESSION) +static int session_filter(void *pCtx, const char *zTab){ + OpenSession *pSession = (OpenSession*)pCtx; + int i; + for(i=0; inFilter; i++){ + if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0; + } + return 1; +} #endif - ".vfsinfo ?AUX? Information about the top-level VFS", - ".vfslist List all available VFSes", - ".vfsname ?AUX? Print the name of the VFS stack", - ".width NUM1 NUM2 ... Set minimum column widths for columnar output", - " Negative values right-justify", -}; -/* -** Output help text. +/* +** Try to deduce the type of file for zName based on its content. Return +** one of the SHELL_OPEN_* constants. +** +** If the file does not exist or is empty but its name looks like a ZIP +** archive and the dfltZip flag is true, then assume it is a ZIP archive. +** Otherwise, assume an ordinary database regardless of the filename if +** the type cannot be determined from content. +*/ +u8 deduceDatabaseType(const char *zName, int dfltZip){ + FILE *f = fopen(zName, "rb"); + size_t n; + u8 rc = SHELL_OPEN_UNSPEC; + char zBuf[100]; + if( f==0 ){ + if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){ + return SHELL_OPEN_ZIPFILE; + }else{ + return SHELL_OPEN_NORMAL; + } + } + n = fread(zBuf, 16, 1, f); + if( n==1 && memcmp(zBuf, "SQLite format 3", 16)==0 ){ + fclose(f); + return SHELL_OPEN_NORMAL; + } + fseek(f, -25, SEEK_END); + n = fread(zBuf, 25, 1, f); + if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){ + rc = SHELL_OPEN_APPENDVFS; + }else{ + fseek(f, -22, SEEK_END); + n = fread(zBuf, 22, 1, f); + if( n==1 && zBuf[0]==0x50 && zBuf[1]==0x4b && zBuf[2]==0x05 + && zBuf[3]==0x06 ){ + rc = SHELL_OPEN_ZIPFILE; + }else if( n==0 && dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){ + rc = SHELL_OPEN_ZIPFILE; + } + } + fclose(f); + return rc; +} + +#ifndef SQLITE_OMIT_DESERIALIZE +/* +** Reconstruct an in-memory database using the output from the "dbtotxt" +** program. Read content from the file in p->aAuxDb[].zDbFilename. +** If p->aAuxDb[].zDbFilename is 0, then read from the present input. +*/ +static unsigned char *readHexDb(ShellInState *psi, int *pnData){ + unsigned char *a = 0; + int n = 0; + int pgsz = 0; + int iOffset = 0; + int j, k, nlError; + int rc; + static const char *zEndMarker = "| end "; + const char *zDbFilename = psi->pAuxDb->zDbFilename; + /* Need next two objects only if redirecting input to get the hex. */ + InSource inRedir = INSOURCE_FILE_REDIR(0, zDbFilename, psi->pInSource); + unsigned int x[16]; + char zLine[1000]; + if( zDbFilename ){ + inRedir.inFile = fopen(zDbFilename, "r"); + if( inRedir.inFile==0 ){ + utf8_printf(STD_ERR, "cannot open \"%s\" for reading\n", zDbFilename); + return 0; + } + psi->pInSource = &inRedir; + }else{ + /* Will read hex DB lines inline from present input, without redirect. */ + if( INSOURCE_IS_INTERACTIVE(psi->pInSource) ){ + printf("Reading hex DB from \"%s\", until end-marker input like:\n%s\n", + psi->pInSource->zSourceSay, zEndMarker); + fflush(STD_OUT); + } + } + *pnData = 0; + if( strLineGet(zLine,sizeof(zLine), psi->pInSource)==0 ) goto readHexDb_error; + rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz); + if( rc!=2 ) goto readHexDb_error; + if( n<0 ) goto readHexDb_error; + if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ) goto readHexDb_error; + n = (n+pgsz-1)&~(pgsz-1); /* Round n up to the next multiple of pgsz */ + a = sqlite3_malloc( n ? n : 1 ); + shell_check_oom(a); + memset(a, 0, n); + if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){ + utf8_printf(STD_ERR, "invalid pagesize\n"); + goto readHexDb_error; + } + while( strLineGet(zLine,sizeof(zLine), psi->pInSource)!=0 ){ + rc = sscanf(zLine, "| page %d offset %d", &j, &k); + if( rc==2 ){ + iOffset = k; + continue; + } + if( strncmp(zLine, zEndMarker, 6)==0 ){ + break; + } + rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + &j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7], + &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]); + if( rc==17 ){ + k = iOffset+j; + if( k+16<=n && k>=0 ){ + int ii; + for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff; + } + } + } + *pnData = n; /* Record success and size. */ + readHexDb_cleanup: + if( psi->pInSource==&inRedir ){ + fclose( inRedir.inFile ); + psi->pInSource = inRedir.pFrom; + } + return a; + + readHexDb_error: + nlError = psi->pInSource->lineno; + if( psi->pInSource!=&inRedir ){ + /* Since taking input inline, consume through its end marker. */ + while( strLineGet(zLine, sizeof(zLine), psi->pInSource)!=0 ){ + if(strncmp(zLine, zEndMarker, 6)==0 ) break; + } + } + sqlite3_free(a); + a = 0; + utf8_printf(STD_ERR,"Error on line %d within --hexdb input\n", nlError); + goto readHexDb_cleanup; +} +#endif /* SQLITE_OMIT_DESERIALIZE */ + +/* +** Scalar function "shell_int32". The first argument to this function +** must be a blob. The second a non-negative integer. This function +** reads and returns a 32-bit big-endian integer from byte +** offset (4*) of the blob. +*/ +static void shellInt32( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const unsigned char *pBlob; + int nBlob; + int iInt; + + UNUSED_PARAMETER(argc); + nBlob = sqlite3_value_bytes(argv[0]); + pBlob = (const unsigned char*)sqlite3_value_blob(argv[0]); + iInt = sqlite3_value_int(argv[1]); + + if( iInt>=0 && (iInt+1)*4<=nBlob ){ + const unsigned char *a = &pBlob[iInt*4]; + sqlite3_int64 iVal = ((sqlite3_int64)a[0]<<24) + + ((sqlite3_int64)a[1]<<16) + + ((sqlite3_int64)a[2]<< 8) + + ((sqlite3_int64)a[3]<< 0); + sqlite3_result_int64(context, iVal); + } +} + +/* +** Scalar function "shell_idquote(X)" returns string X quoted as an identifier, +** using "..." with internal double-quote characters doubled. +*/ +static void shellIdQuote( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zName = (const char*)sqlite3_value_text(argv[0]); + UNUSED_PARAMETER(argc); + if( zName ){ - char *z = sqlite3_mprintf("\"%w\"", zName); ++ char *z = smprintf("\"%w\"", zName); + sqlite3_result_text(context, z, -1, sqlite3_free); + } +} + +/* +** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X. +*/ +static void shellUSleepFunc( + sqlite3_context *context, + int argcUnused, + sqlite3_value **argv +){ + int sleep = sqlite3_value_int(argv[0]); + (void)argcUnused; + sqlite3_sleep(sleep/1000); + sqlite3_result_int(context, sleep); +} + +/* +** Scalar function "shell_escape_crnl" used by the .recover command. +** The argument passed to this function is the output of built-in +** function quote(). If the first character of the input is "'", +** indicating that the value passed to quote() was a text value, +** then this function searches the input for "\n" and "\r" characters +** and adds a wrapper similar to the following: +** +** replace(replace(, '\n', char(10), '\r', char(13)); +** +** Or, if the first character of the input is not "'", then a copy +** of the input is returned. +*/ +static void shellEscapeCrnl( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zText = (const char*)sqlite3_value_text(argv[0]); + UNUSED_PARAMETER(argc); + if( zText && zText[0]=='\'' ){ + int nText = sqlite3_value_bytes(argv[0]); + int i; + char zBuf1[20]; + char zBuf2[20]; + const char *zNL = 0; + const char *zCR = 0; + int nCR = 0; + int nNL = 0; + + for(i=0; zText[i]; i++){ + if( zNL==0 && zText[i]=='\n' ){ + zNL = unused_string(zText, "\\n", "\\012", zBuf1); + nNL = (int)strlen(zNL); + } + if( zCR==0 && zText[i]=='\r' ){ + zCR = unused_string(zText, "\\r", "\\015", zBuf2); + nCR = (int)strlen(zCR); + } + } + + if( zNL || zCR ){ + int iOut = 0; + i64 nMax = (nNL > nCR) ? nNL : nCR; + i64 nAlloc = nMax * nText + (nMax+64)*2; + char *zOut = (char*)sqlite3_malloc64(nAlloc); + if( zOut==0 ){ + sqlite3_result_error_nomem(context); + return; + } + + if( zNL && zCR ){ + memcpy(&zOut[iOut], "replace(replace(", 16); + iOut += 16; + }else{ + memcpy(&zOut[iOut], "replace(", 8); + iOut += 8; + } + for(i=0; zText[i]; i++){ + if( zText[i]=='\n' ){ + memcpy(&zOut[iOut], zNL, nNL); + iOut += nNL; + }else if( zText[i]=='\r' ){ + memcpy(&zOut[iOut], zCR, nCR); + iOut += nCR; + }else{ + zOut[iOut] = zText[i]; + iOut++; + } + } + + if( zNL ){ + memcpy(&zOut[iOut], ",'", 2); iOut += 2; + memcpy(&zOut[iOut], zNL, nNL); iOut += nNL; + memcpy(&zOut[iOut], "', char(10))", 12); iOut += 12; + } + if( zCR ){ + memcpy(&zOut[iOut], ",'", 2); iOut += 2; + memcpy(&zOut[iOut], zCR, nCR); iOut += nCR; + memcpy(&zOut[iOut], "', char(13))", 12); iOut += 12; + } + + sqlite3_result_text(context, zOut, iOut, SQLITE_TRANSIENT); + sqlite3_free(zOut); + return; + } + } + + sqlite3_result_value(context, argv[0]); +} + +/* Flags for open_db(). ** -** zPattern describes the set of commands for which help text is provided. -** If zPattern is NULL, then show all commands, but only give a one-line -** description of each. +** The default behavior of open_db() is to exit(1) if the database fails to +** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error +** but still returns without calling exit. ** -** Return the number of matches. +** The OPEN_DB_ZIPFILE flag causes open_db() to prefer to open files as a +** ZIP archive if the file does not exist or is empty and its name matches +** the *.zip pattern. */ -static int showHelp(FILE *out, const char *zPattern){ - int i = 0; - int j = 0; - int n = 0; - char *zPat; - if( zPattern==0 - || zPattern[0]=='0' - || strcmp(zPattern,"-a")==0 - || strcmp(zPattern,"-all")==0 - || strcmp(zPattern,"--all")==0 - ){ - /* Show all commands, but only one line per command */ - if( zPattern==0 ) zPattern = ""; - for(i=0; ipAuxDb->zDbFilename; + if( psi->openMode==SHELL_OPEN_UNSPEC ){ + if( zDbFilename==0 || zDbFilename[0]==0 ){ + psi->openMode = SHELL_OPEN_NORMAL; + }else{ + psi->openMode = deduceDatabaseType(psi->pAuxDb->zDbFilename, + (openFlags & OPEN_DB_ZIPFILE)!=0); } } - }else{ - /* Look for commands that for which zPattern is an exact prefix */ - zPat = sqlite3_mprintf(".%s*", zPattern); - shell_check_oom(zPat); - for(i=0; iopenMode ){ + case SHELL_OPEN_APPENDVFS: { + sqlite3_open_v2 + (zDbFilename, pDb, + SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|psi->openFlags, + "apndvfs"); + break; + } + case SHELL_OPEN_HEXDB: + case SHELL_OPEN_DESERIALIZE: { + sqlite3_open(0, pDb); + break; + } + case SHELL_OPEN_ZIPFILE: { + sqlite3_open(":memory:", pDb); + break; + } + case SHELL_OPEN_READONLY: { + sqlite3_open_v2(zDbFilename, pDb, + SQLITE_OPEN_READONLY|psi->openFlags, 0); + break; + } + case SHELL_OPEN_UNSPEC: + case SHELL_OPEN_NORMAL: { + sqlite3_open_v2(zDbFilename, pDb, + SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|psi->openFlags, 0); + break; } } - sqlite3_free(zPat); - if( n ){ - if( n==1 ){ - /* when zPattern is a prefix of exactly one command, then include the - ** details of that command, which should begin at offset j */ - while( jopenMode==SHELL_OPEN_ZIPFILE ){ - char *zSql = sqlite3_mprintf( - "CREATE VIRTUAL TABLE zip USING zipfile(%Q);", zDbFilename); ++ char *zSql = smprintf("CREATE VIRTUAL TABLE zip USING zipfile(%Q);", ++ zDbFilename); + shell_check_oom(zSql); + sqlite3_exec(DBX(psx), zSql, 0, 0, 0); + sqlite3_free(zSql); + } +#ifndef SQLITE_OMIT_DESERIALIZE + else + if( psi->openMode==SHELL_OPEN_DESERIALIZE + || psi->openMode==SHELL_OPEN_HEXDB ){ + int rc; + int nData = 0; + unsigned char *aData; + if( psi->openMode==SHELL_OPEN_DESERIALIZE ){ + aData = (unsigned char*)readFile(zDbFilename, &nData); + }else{ + aData = readHexDb(psi, &nData); + if( aData==0 ){ + return; } - i = j; - n++; + } + rc = sqlite3_deserialize(DBX(psx), "main", aData, nData, nData, + SQLITE_DESERIALIZE_RESIZEABLE | + SQLITE_DESERIALIZE_FREEONCLOSE); + if( rc ){ + utf8_printf(STD_ERR, "Error: sqlite3_deserialize() returns %d\n", rc); + } + if( psi->szMax>0 ){ + sqlite3_file_control(DBX(psx), "main", SQLITE_FCNTL_SIZE_LIMIT, + &psi->szMax); } } - sqlite3_free(zPat); +#endif } - return n; -} - -/* Forward reference */ -static int process_input(ShellState *p); - -/* -** Read the content of file zName into memory obtained from sqlite3_malloc64() -** and return a pointer to the buffer. The caller is responsible for freeing -** the memory. -** -** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes -** read. -** -** For convenience, a nul-terminator byte is always appended to the data read -** from the file before the buffer is returned. This byte is not included in -** the final value of (*pnByte), if applicable. -** -** NULL is returned if any error is encountered. The final value of *pnByte -** is undefined in this case. -*/ -static char *readFile(const char *zName, int *pnByte){ - FILE *in = fopen(zName, "rb"); - long nIn; - size_t nRead; - char *pBuf; - if( in==0 ) return 0; - fseek(in, 0, SEEK_END); - nIn = ftell(in); - rewind(in); - pBuf = sqlite3_malloc64( nIn+1 ); - if( pBuf==0 ){ fclose(in); return 0; } - nRead = fread(pBuf, nIn, 1, in); - fclose(in); - if( nRead!=1 ){ - sqlite3_free(pBuf); - return 0; + if( psi->bSafeModeFuture && DBX(psx)!=0 ){ + sqlite3_set_authorizer(DBX(psx), safeModeAuth, psx); } - pBuf[nIn] = 0; - if( pnByte ) *pnByte = nIn; - return pBuf; } -#if defined(SQLITE_ENABLE_SESSION) /* -** Close a single OpenSession object and release all of its associated -** resources. +** Attempt to close the databaes connection. Report errors. */ -static void session_close(OpenSession *pSession){ - int i; - sqlite3session_delete(pSession->p); - sqlite3_free(pSession->zName); - for(i=0; inFilter; i++){ - sqlite3_free(pSession->azFilter[i]); +void close_db(sqlite3 *db){ + int rc; + if( db==globalDb ){ + sqlite3_mutex_enter(pGlobalDbLock); + globalDb = 0; + rc = sqlite3_close(db); + sqlite3_mutex_leave(pGlobalDbLock); + }else{ + rc = sqlite3_close(db); + } + if( rc ){ + utf8_printf(STD_ERR, "Error: sqlite3_close() returns %d: %s\n", + rc, sqlite3_errmsg(db)); } - sqlite3_free(pSession->azFilter); - memset(pSession, 0, sizeof(OpenSession)); } -#endif +#if HAVE_READLINE || HAVE_EDITLINE /* -** Close all OpenSession objects and release all associated resources. +** Readline completion callbacks */ -#if defined(SQLITE_ENABLE_SESSION) -static void session_close_all(ShellState *p, int i){ - int j; - struct AuxDb *pAuxDb = i<0 ? p->pAuxDb : &p->aAuxDb[i]; - for(j=0; jnSession; j++){ - session_close(&pAuxDb->aSession[j]); +static char *readline_completion_generator(const char *text, int state){ + static sqlite3_stmt *pStmt = 0; + char *zRet; + if( state==0 ){ + char *zSql; + sqlite3_finalize(pStmt); - zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase" - " FROM completion(%Q) ORDER BY 1", text); ++ zSql = smprintf("SELECT DISTINCT candidate COLLATE nocase" ++ " FROM completion(%Q) ORDER BY 1", text); + shell_check_oom(zSql); + sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); } - pAuxDb->nSession = 0; + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + const char *z = (const char*)sqlite3_column_text(pStmt,0); + if( z!=0 ){ + zRet = strdup(z); + shell_check_oom(zRet); + } + }else{ + sqlite3_finalize(pStmt); + pStmt = 0; + zRet = 0; + } + return zRet; +} +static char **readline_completion(const char *zText, int iStart, int iEnd){ + rl_attempted_completion_over = 1; + return rl_completion_matches(zText, readline_completion_generator); } -#else -# define session_close_all(X,Y) -#endif +#elif HAVE_LINENOISE /* -** Implementation of the xFilter function for an open session. Omit -** any tables named by ".session filter" but let all other table through. +** Linenoise completion callback */ -#if defined(SQLITE_ENABLE_SESSION) -static int session_filter(void *pCtx, const char *zTab){ - OpenSession *pSession = (OpenSession*)pCtx; - int i; - for(i=0; inFilter; i++){ - if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0; +static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){ + int nLine = strlen30(zLine); + int i, iStart; + sqlite3_stmt *pStmt = 0; + char *zSql; + char zBuf[1000]; + + if( nLine>sizeof(zBuf)-30 ) return; + if( zLine[0]=='.' || zLine[0]=='#') return; + for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){} + if( i==nLine-1 ) return; + iStart = i+1; + memcpy(zBuf, zLine, iStart); - zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase" - " FROM completion(%Q,%Q) ORDER BY 1", - &zLine[iStart], zLine); ++ zSql = smprintf("SELECT DISTINCT candidate COLLATE nocase" ++ " FROM completion(%Q,%Q) ORDER BY 1", ++ &zLine[iStart], zLine); + shell_check_oom(zSql); + sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */ + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0); + int nCompletion = sqlite3_column_bytes(pStmt, 0); + if( iStart+nCompletion < sizeof(zBuf)-1 && zCompletion ){ + memcpy(zBuf+iStart, zCompletion, nCompletion+1); + linenoiseAddCompletion(lc, zBuf); + } } - return 1; + sqlite3_finalize(pStmt); } #endif @@@ -5363,453 -4813,410 +5300,451 @@@ static void output_file_close(FILE *f) } /* -** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X. +** Try to open an output file. The names "stdout" and "stderr" are +** recognized and do the right thing. NULL is returned if the output +** filename is "off". */ -static void shellUSleepFunc( - sqlite3_context *context, - int argcUnused, - sqlite3_value **argv -){ - int sleep = sqlite3_value_int(argv[0]); - (void)argcUnused; - sqlite3_sleep(sleep/1000); - sqlite3_result_int(context, sleep); +static FILE *output_file_open(const char *zFile, int bTextMode){ + FILE *f; + if( strcmp(zFile,"stdout")==0 ){ + f = STD_OUT; + }else if( strcmp(zFile, "stderr")==0 ){ + f = STD_ERR; + }else if( strcmp(zFile, "off")==0 ){ + f = 0; + }else{ + f = fopen(zFile, bTextMode ? "w" : "wb"); + if( f==0 ){ + utf8_printf(STD_ERR, "Error: cannot open \"%s\"\n", zFile); + } + } + return f; } +#ifndef SQLITE_OMIT_TRACE /* -** Scalar function "shell_escape_crnl" used by the .recover command. -** The argument passed to this function is the output of built-in -** function quote(). If the first character of the input is "'", -** indicating that the value passed to quote() was a text value, -** then this function searches the input for "\n" and "\r" characters -** and adds a wrapper similar to the following: -** -** replace(replace(, '\n', char(10), '\r', char(13)); -** -** Or, if the first character of the input is not "'", then a copy -** of the input is returned. +** A routine for handling output from sqlite3_trace(). */ -static void shellEscapeCrnl( - sqlite3_context *context, - int argc, - sqlite3_value **argv +static int sql_trace_callback( + unsigned mType, /* The trace type */ + void *pArg, /* The shell state pointer */ + void *pP, /* Usually a pointer to sqlite_stmt */ + void *pX /* Auxiliary output */ ){ - const char *zText = (const char*)sqlite3_value_text(argv[0]); - UNUSED_PARAMETER(argc); - if( zText && zText[0]=='\'' ){ - int nText = sqlite3_value_bytes(argv[0]); - int i; - char zBuf1[20]; - char zBuf2[20]; - const char *zNL = 0; - const char *zCR = 0; - int nCR = 0; - int nNL = 0; - - for(i=0; zText[i]; i++){ - if( zNL==0 && zText[i]=='\n' ){ - zNL = unused_string(zText, "\\n", "\\012", zBuf1); - nNL = (int)strlen(zNL); - } - if( zCR==0 && zText[i]=='\r' ){ - zCR = unused_string(zText, "\\r", "\\015", zBuf2); - nCR = (int)strlen(zCR); - } - } - - if( zNL || zCR ){ - int iOut = 0; - i64 nMax = (nNL > nCR) ? nNL : nCR; - i64 nAlloc = nMax * nText + (nMax+64)*2; - char *zOut = (char*)sqlite3_malloc64(nAlloc); - if( zOut==0 ){ - sqlite3_result_error_nomem(context); - return; - } - - if( zNL && zCR ){ - memcpy(&zOut[iOut], "replace(replace(", 16); - iOut += 16; - }else{ - memcpy(&zOut[iOut], "replace(", 8); - iOut += 8; - } - for(i=0; zText[i]; i++){ - if( zText[i]=='\n' ){ - memcpy(&zOut[iOut], zNL, nNL); - iOut += nNL; - }else if( zText[i]=='\r' ){ - memcpy(&zOut[iOut], zCR, nCR); - iOut += nCR; - }else{ - zOut[iOut] = zText[i]; - iOut++; - } - } - - if( zNL ){ - memcpy(&zOut[iOut], ",'", 2); iOut += 2; - memcpy(&zOut[iOut], zNL, nNL); iOut += nNL; - memcpy(&zOut[iOut], "', char(10))", 12); iOut += 12; + ShellInState *psi = (ShellInState*)pArg; + sqlite3_stmt *pStmt; + const char *zSql; + int nSql; + if( psi->traceOut==0 ) return 0; + if( mType==SQLITE_TRACE_CLOSE ){ + utf8_printf(psi->traceOut, "-- closing database connection\n"); + return 0; + } + if( mType!=SQLITE_TRACE_ROW && ((const char*)pX)[0]=='-' ){ + zSql = (const char*)pX; + }else{ + pStmt = (sqlite3_stmt*)pP; + switch( psi->eTraceType ){ + case SHELL_TRACE_EXPANDED: { + zSql = sqlite3_expanded_sql(pStmt); + break; } - if( zCR ){ - memcpy(&zOut[iOut], ",'", 2); iOut += 2; - memcpy(&zOut[iOut], zCR, nCR); iOut += nCR; - memcpy(&zOut[iOut], "', char(13))", 12); iOut += 12; +#ifdef SQLITE_ENABLE_NORMALIZE + case SHELL_TRACE_NORMALIZED: { + zSql = sqlite3_normalized_sql(pStmt); + break; + } +#endif + default: { + zSql = sqlite3_sql(pStmt); + break; } - - sqlite3_result_text(context, zOut, iOut, SQLITE_TRANSIENT); - sqlite3_free(zOut); - return; } } - - sqlite3_result_value(context, argv[0]); + if( zSql==0 ) return 0; + nSql = strlen30(zSql); + while( nSql>0 && zSql[nSql-1]==';' ){ nSql--; } + switch( mType ){ + case SQLITE_TRACE_ROW: + case SQLITE_TRACE_STMT: { + utf8_printf(psi->traceOut, "%.*s;\n", nSql, zSql); + break; + } + case SQLITE_TRACE_PROFILE: { + sqlite3_int64 nNanosec = *(sqlite3_int64*)pX; + utf8_printf(psi->traceOut, "%.*s; -- %lld ns\n", nSql, zSql, nNanosec); + break; + } + } + return 0; } +#endif -/* Flags for open_db(). -** -** The default behavior of open_db() is to exit(1) if the database fails to -** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error -** but still returns without calling exit. -** -** The OPEN_DB_ZIPFILE flag causes open_db() to prefer to open files as a -** ZIP archive if the file does not exist or is empty and its name matches -** the *.zip pattern. +/* +** A no-op routine that runs with the ".breakpoint" doc-command. This is +** a useful spot to set a debugger breakpoint. */ -#define OPEN_DB_KEEPALIVE 0x001 /* Return after error if true */ -#define OPEN_DB_ZIPFILE 0x002 /* Open as ZIP if name matches *.zip */ +static void test_breakpoint(void){ + static int nCall = 0; + nCall++; +} /* -** Make sure the database is open. If it is not, then open it. If -** the database fails to open, print an error message and exit. +** An object used to read a CSV and other files for import. */ -static void open_db(ShellState *p, int openFlags){ - if( p->db==0 ){ - const char *zDbFilename = p->pAuxDb->zDbFilename; - if( p->openMode==SHELL_OPEN_UNSPEC ){ - if( zDbFilename==0 || zDbFilename[0]==0 ){ - p->openMode = SHELL_OPEN_NORMAL; - }else{ - p->openMode = (u8)deduceDatabaseType(zDbFilename, - (openFlags & OPEN_DB_ZIPFILE)!=0); - } - } - switch( p->openMode ){ - case SHELL_OPEN_APPENDVFS: { - sqlite3_open_v2(zDbFilename, &p->db, - SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, "apndvfs"); - break; - } - case SHELL_OPEN_HEXDB: - case SHELL_OPEN_DESERIALIZE: { - sqlite3_open(0, &p->db); - break; +typedef struct ImportCtx ImportCtx; +struct ImportCtx { + const char *zFile; /* Name of the input file */ + FILE *in; /* Read the CSV text from this input stream */ + int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close in */ + char *z; /* Accumulated text for a field */ + int n; /* Number of bytes in z */ + int nAlloc; /* Space allocated for z[] */ + int nLine; /* Current line number */ + int nRow; /* Number of rows imported */ + int nErr; /* Number of errors encountered */ + int bNotFirst; /* True if one or more bytes already read */ + int cTerm; /* Character that terminated the most recent field */ + int cColSep; /* The column separator character. (Usually ",") */ + int cRowSep; /* The row separator character. (Usually "\n") */ +}; + +/* Clean up resourced used by an ImportCtx */ +static void import_cleanup(ImportCtx *p){ + if( p->in!=0 && p->xCloser!=0 ){ + p->xCloser(p->in); + p->in = 0; + } + sqlite3_free(p->z); + p->z = 0; +} + +/* Append a single byte to z[] */ +static void import_append_char(ImportCtx *p, int c){ + if( p->n+1>=p->nAlloc ){ + p->nAlloc += p->nAlloc + 100; + p->z = sqlite3_realloc64(p->z, p->nAlloc); + shell_check_oom(p->z); + } + p->z[p->n++] = (char)c; +} + +/* Read a single field of CSV text. Compatible with rfc4180 and extended +** with the option of having a separator other than ",". +** +** + Input comes from p->in. +** + Store results in p->z of length p->n. Space to hold p->z comes +** from sqlite3_malloc64(). +** + Use p->cSep as the column separator. The default is ",". +** + Use p->rSep as the row separator. The default is "\n". +** + Keep track of the line number in p->nLine. +** + Store the character that terminates the field in p->cTerm. Store +** EOF on end-of-file. +** + Report syntax errors on stderr +*/ +static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){ + int c; + int cSep = p->cColSep; + int rSep = p->cRowSep; + p->n = 0; + c = fgetc(p->in); + if( c==EOF || seenInterrupt ){ + p->cTerm = EOF; + return 0; + } + if( c=='"' ){ + int pc, ppc; + int startLine = p->nLine; + int cQuote = c; + pc = ppc = 0; + while( 1 ){ + c = fgetc(p->in); + if( c==rSep ) p->nLine++; + if( c==cQuote ){ + if( pc==cQuote ){ + pc = 0; + continue; + } } - case SHELL_OPEN_ZIPFILE: { - sqlite3_open(":memory:", &p->db); + if( (c==cSep && pc==cQuote) + || (c==rSep && pc==cQuote) + || (c==rSep && pc=='\r' && ppc==cQuote) + || (c==EOF && pc==cQuote) + ){ + do{ p->n--; }while( p->z[p->n]!=cQuote ); + p->cTerm = c; break; } - case SHELL_OPEN_READONLY: { - sqlite3_open_v2(zDbFilename, &p->db, - SQLITE_OPEN_READONLY|p->openFlags, 0); - break; + if( pc==cQuote && c!='\r' ){ + utf8_printf(STD_ERR, "%s:%d: unescaped %c character\n", + p->zFile, p->nLine, cQuote); } - case SHELL_OPEN_UNSPEC: - case SHELL_OPEN_NORMAL: { - sqlite3_open_v2(zDbFilename, &p->db, - SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, 0); + if( c==EOF ){ + utf8_printf(STD_ERR, "%s:%d: unterminated %c-quoted field\n", + p->zFile, startLine, cQuote); + p->cTerm = c; break; } + import_append_char(p, c); + ppc = pc; + pc = c; } - globalDb = p->db; - if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ - utf8_printf(stderr,"Error: unable to open database \"%s\": %s\n", - zDbFilename, sqlite3_errmsg(p->db)); - if( openFlags & OPEN_DB_KEEPALIVE ){ - sqlite3_open(":memory:", &p->db); - return; + }else{ + /* If this is the first field being parsed and it begins with the + ** UTF-8 BOM (0xEF BB BF) then skip the BOM */ + if( (c&0xff)==0xef && p->bNotFirst==0 ){ + import_append_char(p, c); + c = fgetc(p->in); + if( (c&0xff)==0xbb ){ + import_append_char(p, c); + c = fgetc(p->in); + if( (c&0xff)==0xbf ){ + p->bNotFirst = 1; + p->n = 0; + return csv_read_one_field(p); + } } - exit(1); } -#ifndef SQLITE_OMIT_LOAD_EXTENSION - sqlite3_enable_load_extension(p->db, 1); -#endif - sqlite3_fileio_init(p->db, 0, 0); - sqlite3_shathree_init(p->db, 0, 0); - sqlite3_completion_init(p->db, 0, 0); - sqlite3_uint_init(p->db, 0, 0); - sqlite3_decimal_init(p->db, 0, 0); - sqlite3_regexp_init(p->db, 0, 0); - sqlite3_ieee_init(p->db, 0, 0); - sqlite3_series_init(p->db, 0, 0); -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) - sqlite3_dbdata_init(p->db, 0, 0); -#endif -#ifdef SQLITE_HAVE_ZLIB - sqlite3_zipfile_init(p->db, 0, 0); - sqlite3_sqlar_init(p->db, 0, 0); -#endif - sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0, - shellAddSchemaName, 0, 0); - sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0, - shellModuleSchema, 0, 0); - sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p, - shellPutsFunc, 0, 0); - sqlite3_create_function(p->db, "shell_escape_crnl", 1, SQLITE_UTF8, 0, - shellEscapeCrnl, 0, 0); - sqlite3_create_function(p->db, "shell_int32", 2, SQLITE_UTF8, 0, - shellInt32, 0, 0); - sqlite3_create_function(p->db, "shell_idquote", 1, SQLITE_UTF8, 0, - shellIdQuote, 0, 0); - sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0, - shellUSleepFunc, 0, 0); -#ifndef SQLITE_NOHAVE_SYSTEM - sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0, - editFunc, 0, 0); - sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0, - editFunc, 0, 0); -#endif - if( p->openMode==SHELL_OPEN_ZIPFILE ){ - char *zSql = sqlite3_mprintf( - "CREATE VIRTUAL TABLE zip USING zipfile(%Q);", zDbFilename); - shell_check_oom(zSql); - sqlite3_exec(p->db, zSql, 0, 0, 0); - sqlite3_free(zSql); + while( c!=EOF && c!=cSep && c!=rSep ){ + import_append_char(p, c); + c = fgetc(p->in); } -#ifndef SQLITE_OMIT_DESERIALIZE - else - if( p->openMode==SHELL_OPEN_DESERIALIZE || p->openMode==SHELL_OPEN_HEXDB ){ - int rc; - int nData = 0; - unsigned char *aData; - if( p->openMode==SHELL_OPEN_DESERIALIZE ){ - aData = (unsigned char*)readFile(zDbFilename, &nData); - }else{ - aData = readHexDb(p, &nData); - if( aData==0 ){ - return; - } - } - rc = sqlite3_deserialize(p->db, "main", aData, nData, nData, - SQLITE_DESERIALIZE_RESIZEABLE | - SQLITE_DESERIALIZE_FREEONCLOSE); - if( rc ){ - utf8_printf(stderr, "Error: sqlite3_deserialize() returns %d\n", rc); - } - if( p->szMax>0 ){ - sqlite3_file_control(p->db, "main", SQLITE_FCNTL_SIZE_LIMIT, &p->szMax); - } + if( c==rSep ){ + p->nLine++; + if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--; } -#endif - } - if( p->bSafeModePersist && p->db!=0 ){ - sqlite3_set_authorizer(p->db, safeModeAuth, p); + p->cTerm = c; } + if( p->z ) p->z[p->n] = 0; + p->bNotFirst = 1; + return p->z; } -/* -** Attempt to close the databaes connection. Report errors. -*/ -void close_db(sqlite3 *db){ - int rc = sqlite3_close(db); - if( rc ){ - utf8_printf(stderr, "Error: sqlite3_close() returns %d: %s\n", - rc, sqlite3_errmsg(db)); - } -} - -#if HAVE_READLINE || HAVE_EDITLINE -/* -** Readline completion callbacks +/* Read a single field of ASCII delimited text. +** +** + Input comes from p->in. +** + Store results in p->z of length p->n. Space to hold p->z comes +** from sqlite3_malloc64(). +** + Use p->cSep as the column separator. The default is "\x1F". +** + Use p->rSep as the row separator. The default is "\x1E". +** + Keep track of the row number in p->nLine. +** + Store the character that terminates the field in p->cTerm. Store +** EOF on end-of-file. +** + Report syntax errors on stderr */ -static char *readline_completion_generator(const char *text, int state){ - static sqlite3_stmt *pStmt = 0; - char *zRet; - if( state==0 ){ - char *zSql; - sqlite3_finalize(pStmt); - zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase" - " FROM completion(%Q) ORDER BY 1", text); - shell_check_oom(zSql); - sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); +static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p){ + int c; + int cSep = p->cColSep; + int rSep = p->cRowSep; + p->n = 0; + c = fgetc(p->in); + if( c==EOF || seenInterrupt ){ + p->cTerm = EOF; + return 0; } - if( sqlite3_step(pStmt)==SQLITE_ROW ){ - const char *z = (const char*)sqlite3_column_text(pStmt,0); - zRet = z ? strdup(z) : 0; - }else{ - sqlite3_finalize(pStmt); - pStmt = 0; - zRet = 0; + while( c!=EOF && c!=cSep && c!=rSep ){ + import_append_char(p, c); + c = fgetc(p->in); } - return zRet; -} -static char **readline_completion(const char *zText, int iStart, int iEnd){ - rl_attempted_completion_over = 1; - return rl_completion_matches(zText, readline_completion_generator); + if( c==rSep ){ + p->nLine++; + } + p->cTerm = c; + if( p->z ) p->z[p->n] = 0; + return p->z; } -#elif HAVE_LINENOISE /* -** Linenoise completion callback +** Try to transfer data for table zTable. If an error is seen while +** moving forward, try to go backwards. The backwards movement won't +** work for WITHOUT ROWID tables. */ -static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){ - int nLine = strlen30(zLine); - int i, iStart; - sqlite3_stmt *pStmt = 0; - char *zSql; - char zBuf[1000]; +static void tryToCloneData( + ShellExState *psx, + sqlite3 *newDb, + const char *zTable +){ + sqlite3_stmt *pQuery = 0; + sqlite3_stmt *pInsert = 0; + char *zQuery = 0; + char *zInsert = 0; + int rc; + int i, j, n; + int nTable = strlen30(zTable); + int k = 0; + int cnt = 0; + const int spinRate = 10000; - zQuery = sqlite3_mprintf("SELECT * FROM \"%w\"", zTable); - if( nLine>sizeof(zBuf)-30 ) return; - if( zLine[0]=='.' || zLine[0]=='#') return; - for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){} - if( i==nLine-1 ) return; - iStart = i+1; - memcpy(zBuf, zLine, iStart); - zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase" - " FROM completion(%Q,%Q) ORDER BY 1", - &zLine[iStart], zLine); - shell_check_oom(zSql); - sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); - sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */ - while( sqlite3_step(pStmt)==SQLITE_ROW ){ - const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0); - int nCompletion = sqlite3_column_bytes(pStmt, 0); - if( iStart+nCompletion < sizeof(zBuf)-1 && zCompletion ){ - memcpy(zBuf+iStart, zCompletion, nCompletion+1); - linenoiseAddCompletion(lc, zBuf); - } ++ zQuery = smprintf("SELECT * FROM \"%w\"", zTable); + shell_check_oom(zQuery); + rc = sqlite3_prepare_v2(DBX(psx), zQuery, -1, &pQuery, 0); + if( rc ){ + utf8_printf(STD_ERR, "Error %d: %s on [%s]\n", + sqlite3_extended_errcode(DBX(psx)), sqlite3_errmsg(DBX(psx)), + zQuery); + goto end_data_xfer; } - sqlite3_finalize(pStmt); -} -#endif - -/* -** Do C-language style dequoting. -** -** \a -> alarm -** \b -> backspace -** \t -> tab -** \n -> newline -** \v -> vertical tab -** \f -> form feed -** \r -> carriage return -** \s -> space -** \" -> " -** \' -> ' -** \\ -> backslash -** \NNN -> ascii character NNN in octal -*/ -static void resolve_backslashes(char *z){ - int i, j; - char c; - while( *z && *z!='\\' ) z++; - for(i=j=0; (c = z[i])!=0; i++, j++){ - if( c=='\\' && z[i+1]!=0 ){ - c = z[++i]; - if( c=='a' ){ - c = '\a'; - }else if( c=='b' ){ - c = '\b'; - }else if( c=='t' ){ - c = '\t'; - }else if( c=='n' ){ - c = '\n'; - }else if( c=='v' ){ - c = '\v'; - }else if( c=='f' ){ - c = '\f'; - }else if( c=='r' ){ - c = '\r'; - }else if( c=='"' ){ - c = '"'; - }else if( c=='\'' ){ - c = '\''; - }else if( c=='\\' ){ - c = '\\'; - }else if( c>='0' && c<='7' ){ - c -= '0'; - if( z[i+1]>='0' && z[i+1]<='7' ){ - i++; - c = (c<<3) + z[i] - '0'; - if( z[i+1]>='0' && z[i+1]<='7' ){ - i++; - c = (c<<3) + z[i] - '0'; + n = sqlite3_column_count(pQuery); + zInsert = sqlite3_malloc64(200 + nTable + n*3); + shell_check_oom(zInsert); + sqlite3_snprintf(200+nTable,zInsert, + "INSERT OR IGNORE INTO \"%s\" VALUES(?", zTable); + i = strlen30(zInsert); + for(j=1; j=0; i++){} - }else{ - for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){} +static void tryToCloneSchema( + ShellExState *psx, + sqlite3 *newDb, + const char *zWhere, + void (*xForEach)(ShellExState*,sqlite3*,const char*) +){ + sqlite3_stmt *pQuery = 0; + char *zQuery = 0; + int rc; + const unsigned char *zName; + const unsigned char *zSql; + char *zErrMsg = 0; + - zQuery = sqlite3_mprintf("SELECT name, sql FROM sqlite_schema" - " WHERE %s", zWhere); ++ zQuery = smprintf("SELECT name, sql FROM sqlite_schema WHERE %s", zWhere); + shell_check_oom(zQuery); + rc = sqlite3_prepare_v2(DBX(psx), zQuery, -1, &pQuery, 0); + if( rc ){ + utf8_printf(STD_ERR, "Error: (%d) %s on [%s]\n", + sqlite3_extended_errcode(DBX(psx)), + sqlite3_errmsg(DBX(psx)), zQuery); + goto end_schema_xfer; } - if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff); - if( sqlite3_stricmp(zArg, "on")==0 || sqlite3_stricmp(zArg,"yes")==0 ){ - return 1; + while( sqlite3_step(pQuery)==SQLITE_ROW ){ + zName = sqlite3_column_text(pQuery, 0); + zSql = sqlite3_column_text(pQuery, 1); + if( zName==0 || zSql==0 ) continue; + /* Consider directing this output to current output. */ + fprintf(STD_OUT, "%s... ", zName); fflush(STD_OUT); + sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); + if( zErrMsg ){ + utf8_printf(STD_ERR, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql); + sqlite3_free(zErrMsg); + zErrMsg = 0; + } + if( xForEach ){ + xForEach(psx, newDb, (const char*)zName); + } + /* Consider directing this output to current output. */ + fprintf(STD_OUT, "done\n"); } - if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){ - return 0; + if( rc!=SQLITE_DONE ){ + sqlite3_finalize(pQuery); + sqlite3_free(zQuery); - zQuery = sqlite3_mprintf("SELECT name, sql FROM sqlite_schema" - " WHERE %s ORDER BY rowid DESC", zWhere); ++ zQuery = smprintf("SELECT name, sql FROM sqlite_schema" ++ " WHERE %s ORDER BY rowid DESC", zWhere); + shell_check_oom(zQuery); + rc = sqlite3_prepare_v2(DBX(psx), zQuery, -1, &pQuery, 0); + if( rc ){ + utf8_printf(STD_ERR, "Error: (%d) %s on [%s]\n", + sqlite3_extended_errcode(DBX(psx)), + sqlite3_errmsg(DBX(psx)), zQuery); + goto end_schema_xfer; + } + while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){ + zName = sqlite3_column_text(pQuery, 0); + zSql = sqlite3_column_text(pQuery, 1); + if( zName==0 || zSql==0 ) continue; + /* Consider directing ... */ + fprintf(STD_OUT, "%s... ", zName); fflush(STD_OUT); + sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); + if( zErrMsg ){ + utf8_printf(STD_ERR, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql); + sqlite3_free(zErrMsg); + zErrMsg = 0; + } + if( xForEach ){ + xForEach(psx, newDb, (const char*)zName); + } + /* Consider directing ... */ + fprintf(STD_OUT, "done\n"); + } } - utf8_printf(stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n", - zArg); - return 0; +end_schema_xfer: + sqlite3_finalize(pQuery); + sqlite3_free(zQuery); } /* @@@ -5840,545 -5231,483 +5775,545 @@@ static void tryToClone(ShellExState *ps } /* -** Close an output file, assuming it is not stderr or stdout -*/ -static void output_file_close(FILE *f){ - if( f && f!=stdout && f!=stderr ) fclose(f); -} - -/* -** Try to open an output file. The names "stdout" and "stderr" are -** recognized and do the right thing. NULL is returned if the output -** filename is "off". +** Change the output file back to stdout. +** +** If the psi->doXdgOpen flag is set, that means the output was being +** redirected to a temporary file named by psi->zTempFile. In that case, +** launch start/open/xdg-open on that temporary file. */ -static FILE *output_file_open(const char *zFile, int bTextMode){ - FILE *f; - if( strcmp(zFile,"stdout")==0 ){ - f = stdout; - }else if( strcmp(zFile, "stderr")==0 ){ - f = stderr; - }else if( strcmp(zFile, "off")==0 ){ - f = 0; +static void output_reset(ShellInState *psi){ + if( psi->outfile[0]=='|' ){ +#ifndef SQLITE_OMIT_POPEN + pclose(psi->out); +#endif }else{ - f = fopen(zFile, bTextMode ? "w" : "wb"); - if( f==0 ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile); + output_file_close(psi->out); +#ifndef SQLITE_NOHAVE_SYSTEM + if( psi->doXdgOpen ){ + const char *zXdgOpenCmd = +#if defined(_WIN32) + "start"; +#elif defined(__APPLE__) + "open"; +#else + "xdg-open"; +#endif + char *zCmd; - zCmd = sqlite3_mprintf("%s %s", zXdgOpenCmd, psi->zTempFile); ++ zCmd = smprintf("%s %s", zXdgOpenCmd, psi->zTempFile); + if( system(zCmd) ){ + utf8_printf(STD_ERR, "Failed: [%s]\n", zCmd); + }else{ + /* Give the start/open/xdg-open command some time to get + ** going before we continue, and potential delete the + ** psi->zTempFile data file out from under it */ + sqlite3_sleep(2000); + } + sqlite3_free(zCmd); + outputModePop(psi); + psi->doXdgOpen = 0; } +#endif /* !defined(SQLITE_NOHAVE_SYSTEM) */ } - return f; + psi->outfile[0] = 0; + psi->out = STD_OUT; } -#ifndef SQLITE_OMIT_TRACE /* -** A routine for handling output from sqlite3_trace(). +** Run an SQL command and return the single integer result. +** No parameter binding is done. */ -static int sql_trace_callback( - unsigned mType, /* The trace type */ - void *pArg, /* The ShellState pointer */ - void *pP, /* Usually a pointer to sqlite_stmt */ - void *pX /* Auxiliary output */ -){ - ShellState *p = (ShellState*)pArg; +static int db_int(sqlite3 *db, const char *zSql){ sqlite3_stmt *pStmt; - const char *zSql; - int nSql; - if( p->traceOut==0 ) return 0; - if( mType==SQLITE_TRACE_CLOSE ){ - utf8_printf(p->traceOut, "-- closing database connection\n"); - return 0; - } - if( mType!=SQLITE_TRACE_ROW && ((const char*)pX)[0]=='-' ){ - zSql = (const char*)pX; - }else{ - pStmt = (sqlite3_stmt*)pP; - switch( p->eTraceType ){ - case SHELL_TRACE_EXPANDED: { - zSql = sqlite3_expanded_sql(pStmt); - break; - } -#ifdef SQLITE_ENABLE_NORMALIZE - case SHELL_TRACE_NORMALIZED: { - zSql = sqlite3_normalized_sql(pStmt); - break; - } -#endif - default: { - zSql = sqlite3_sql(pStmt); - break; - } - } - } - if( zSql==0 ) return 0; - nSql = strlen30(zSql); - while( nSql>0 && zSql[nSql-1]==';' ){ nSql--; } - switch( mType ){ - case SQLITE_TRACE_ROW: - case SQLITE_TRACE_STMT: { - utf8_printf(p->traceOut, "%.*s;\n", nSql, zSql); - break; - } - case SQLITE_TRACE_PROFILE: { - sqlite3_int64 nNanosec = *(sqlite3_int64*)pX; - utf8_printf(p->traceOut, "%.*s; -- %lld ns\n", nSql, zSql, nNanosec); - break; - } + int res = 0; + sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){ + res = sqlite3_column_int(pStmt,0); } - return 0; + sqlite3_finalize(pStmt); + return res; } -#endif /* -** A no-op routine that runs with the ".breakpoint" doc-command. This is -** a useful spot to set a debugger breakpoint. +** Run an SQL command and return the single text result, +** Parameter binding is done iff bBind is true. +** The return must be freed by caller using sqlite3_free(). */ -static void test_breakpoint(void){ - static int nCall = 0; - nCall++; +static char *db_text(sqlite3 *db, const char *zSql, int bBind){ + sqlite3_stmt *pStmt; + char *zRes = 0; + sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + if( pStmt ){ + if( bBind ) bind_prepared_stmt(db, pStmt); + if( sqlite3_step(pStmt)==SQLITE_ROW ){ - zRes = sqlite3_mprintf("%s", sqlite3_column_text(pStmt,0)); ++ zRes = smprintf("%s", sqlite3_column_text(pStmt,0)); + shell_check_oom(zRes); + } + } + sqlite3_finalize(pStmt); + return zRes; } /* -** An object used to read a CSV and other files for import. +** Convert a 2-byte or 4-byte big-endian integer into a native integer */ -typedef struct ImportCtx ImportCtx; -struct ImportCtx { - const char *zFile; /* Name of the input file */ - FILE *in; /* Read the CSV text from this input stream */ - int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close in */ - char *z; /* Accumulated text for a field */ - int n; /* Number of bytes in z */ - int nAlloc; /* Space allocated for z[] */ - int nLine; /* Current line number */ - int nRow; /* Number of rows imported */ - int nErr; /* Number of errors encountered */ - int bNotFirst; /* True if one or more bytes already read */ - int cTerm; /* Character that terminated the most recent field */ - int cColSep; /* The column separator character. (Usually ",") */ - int cRowSep; /* The row separator character. (Usually "\n") */ -}; - -/* Clean up resourced used by an ImportCtx */ -static void import_cleanup(ImportCtx *p){ - if( p->in!=0 && p->xCloser!=0 ){ - p->xCloser(p->in); - p->in = 0; - } - sqlite3_free(p->z); - p->z = 0; +static unsigned int get2byteInt(unsigned char *a){ + return (a[0]<<8) + a[1]; } - -/* Append a single byte to z[] */ -static void import_append_char(ImportCtx *p, int c){ - if( p->n+1>=p->nAlloc ){ - p->nAlloc += p->nAlloc + 100; - p->z = sqlite3_realloc64(p->z, p->nAlloc); - shell_check_oom(p->z); - } - p->z[p->n++] = (char)c; +static unsigned int get4byteInt(unsigned char *a){ + return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3]; } -/* Read a single field of CSV text. Compatible with rfc4180 and extended -** with the option of having a separator other than ",". +/* +** Implementation of the ".dbinfo" command. ** -** + Input comes from p->in. -** + Store results in p->z of length p->n. Space to hold p->z comes -** from sqlite3_malloc64(). -** + Use p->cSep as the column separator. The default is ",". -** + Use p->rSep as the row separator. The default is "\n". -** + Keep track of the line number in p->nLine. -** + Store the character that terminates the field in p->cTerm. Store -** EOF on end-of-file. -** + Report syntax errors on stderr +** Return 1 on error, 2 to exit, and 0 otherwise. */ -static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){ - int c; - int cSep = p->cColSep; - int rSep = p->cRowSep; - p->n = 0; - c = fgetc(p->in); - if( c==EOF || seenInterrupt ){ - p->cTerm = EOF; - return 0; - } - if( c=='"' ){ - int pc, ppc; - int startLine = p->nLine; - int cQuote = c; - pc = ppc = 0; - while( 1 ){ - c = fgetc(p->in); - if( c==rSep ) p->nLine++; - if( c==cQuote ){ - if( pc==cQuote ){ - pc = 0; - continue; - } - } - if( (c==cSep && pc==cQuote) - || (c==rSep && pc==cQuote) - || (c==rSep && pc=='\r' && ppc==cQuote) - || (c==EOF && pc==cQuote) - ){ - do{ p->n--; }while( p->z[p->n]!=cQuote ); - p->cTerm = c; - break; - } - if( pc==cQuote && c!='\r' ){ - utf8_printf(stderr, "%s:%d: unescaped %c character\n", - p->zFile, p->nLine, cQuote); - } - if( c==EOF ){ - utf8_printf(stderr, "%s:%d: unterminated %c-quoted field\n", - p->zFile, startLine, cQuote); - p->cTerm = c; - break; - } - import_append_char(p, c); - ppc = pc; - pc = c; - } +static int shell_dbinfo_command(ShellExState *psx, int nArg, char **azArg){ + static const struct { const char *zName; int ofst; } aField[] = { + { "file change counter:", 24 }, + { "database page count:", 28 }, + { "freelist page count:", 36 }, + { "schema cookie:", 40 }, + { "schema format:", 44 }, + { "default cache size:", 48 }, + { "autovacuum top root:", 52 }, + { "incremental vacuum:", 64 }, + { "text encoding:", 56 }, + { "user version:", 60 }, + { "application id:", 68 }, + { "software version:", 96 }, + }; + static const struct { const char *zName; const char *zSql; } aQuery[] = { + { "number of tables:", + "SELECT count(*) FROM %s WHERE type='table'" }, + { "number of indexes:", + "SELECT count(*) FROM %s WHERE type='index'" }, + { "number of triggers:", + "SELECT count(*) FROM %s WHERE type='trigger'" }, + { "number of views:", + "SELECT count(*) FROM %s WHERE type='view'" }, + { "schema size:", + "SELECT total(length(sql)) FROM %s" }, + }; + int i, rc; + unsigned iDataVersion; + char *zSchemaTab; + char *zDb = nArg>=2 ? azArg[1] : "main"; + sqlite3_stmt *pStmt = 0; + unsigned char aHdr[100]; + FILE *out = ISS(psx)->out; + + open_db(psx, 0); + if( DBX(psx)==0 ) return 1; + rc = sqlite3_prepare_v2(DBX(psx), + "SELECT data FROM sqlite_dbpage(?1) WHERE pgno=1", + -1, &pStmt, 0); + if( rc ){ + utf8_printf(STD_ERR, "error: %s\n", sqlite3_errmsg(DBX(psx))); + sqlite3_finalize(pStmt); + return 1; + } + sqlite3_bind_text(pStmt, 1, zDb, -1, SQLITE_STATIC); + if( sqlite3_step(pStmt)==SQLITE_ROW + && sqlite3_column_bytes(pStmt,0)>100 + ){ + memcpy(aHdr, sqlite3_column_blob(pStmt,0), 100); + sqlite3_finalize(pStmt); }else{ - /* If this is the first field being parsed and it begins with the - ** UTF-8 BOM (0xEF BB BF) then skip the BOM */ - if( (c&0xff)==0xef && p->bNotFirst==0 ){ - import_append_char(p, c); - c = fgetc(p->in); - if( (c&0xff)==0xbb ){ - import_append_char(p, c); - c = fgetc(p->in); - if( (c&0xff)==0xbf ){ - p->bNotFirst = 1; - p->n = 0; - return csv_read_one_field(p); - } + raw_printf(STD_ERR, "unable to read database header\n"); + sqlite3_finalize(pStmt); + return 1; + } + i = get2byteInt(aHdr+16); + if( i==1 ) i = 65536; + utf8_printf(out, "%-20s %d\n", "database page size:", i); + utf8_printf(out, "%-20s %d\n", "write format:", aHdr[18]); + utf8_printf(out, "%-20s %d\n", "read format:", aHdr[19]); + utf8_printf(out, "%-20s %d\n", "reserved bytes:", aHdr[20]); + for(i=0; iin); - } - if( c==rSep ){ - p->nLine++; - if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--; - } - p->cTerm = c; + raw_printf(out, "\n"); } - if( p->z ) p->z[p->n] = 0; - p->bNotFirst = 1; - return p->z; + if( zDb==0 ){ - zSchemaTab = sqlite3_mprintf("main.sqlite_schema"); ++ zSchemaTab = smprintf("main.sqlite_schema"); + }else if( strcmp(zDb,"temp")==0 ){ - zSchemaTab = sqlite3_mprintf("%s", "sqlite_temp_schema"); ++ zSchemaTab = smprintf("%s", "sqlite_temp_schema"); + }else{ - zSchemaTab = sqlite3_mprintf("\"%w\".sqlite_schema", zDb); ++ zSchemaTab = smprintf("\"%w\".sqlite_schema", zDb); + } + for(i=0; iin. -** + Store results in p->z of length p->n. Space to hold p->z comes -** from sqlite3_malloc64(). -** + Use p->cSep as the column separator. The default is "\x1F". -** + Use p->rSep as the row separator. The default is "\x1E". -** + Keep track of the row number in p->nLine. -** + Store the character that terminates the field in p->cTerm. Store -** EOF on end-of-file. -** + Report syntax errors on stderr +/* +** Print the current sqlite3_errmsg() value to stderr and return 1. */ -static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p){ - int c; - int cSep = p->cColSep; - int rSep = p->cRowSep; - p->n = 0; - c = fgetc(p->in); - if( c==EOF || seenInterrupt ){ - p->cTerm = EOF; - return 0; - } - while( c!=EOF && c!=cSep && c!=rSep ){ - import_append_char(p, c); - c = fgetc(p->in); - } - if( c==rSep ){ - p->nLine++; - } - p->cTerm = c; - if( p->z ) p->z[p->n] = 0; - return p->z; +static int shellDatabaseError(sqlite3 *db){ + const char *zErr = sqlite3_errmsg(db); + utf8_printf(STD_ERR, "Error: %s\n", zErr); + return 1; } /* -** Try to transfer data for table zTable. If an error is seen while -** moving forward, try to go backwards. The backwards movement won't -** work for WITHOUT ROWID tables. +** Compare the pattern in zGlob[] against the text in z[]. Return TRUE +** if they match and FALSE (0) if they do not match. +** +** Globbing rules: +** +** '*' Matches any sequence of zero or more characters. +** +** '?' Matches exactly one character. +** +** [...] Matches one character from the enclosed list of +** characters. +** +** [^...] Matches one character not in the enclosed list. +** +** '#' Matches any sequence of one or more digits with an +** optional + or - sign in front +** +** ' ' Any span of whitespace matches any other span of +** whitespace. +** +** Extra whitespace at the end of z[] is ignored. */ -static void tryToCloneData( - ShellState *p, - sqlite3 *newDb, - const char *zTable -){ - sqlite3_stmt *pQuery = 0; - sqlite3_stmt *pInsert = 0; - char *zQuery = 0; - char *zInsert = 0; - int rc; - int i, j, n; - int nTable = strlen30(zTable); - int k = 0; - int cnt = 0; - const int spinRate = 10000; +static int testcase_glob(const char *zGlob, const char *z){ + int c, c2; + int invert; + int seen; - zQuery = sqlite3_mprintf("SELECT * FROM \"%w\"", zTable); - shell_check_oom(zQuery); - rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); - if( rc ){ - utf8_printf(stderr, "Error %d: %s on [%s]\n", - sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), - zQuery); - goto end_data_xfer; - } - n = sqlite3_column_count(pQuery); - zInsert = sqlite3_malloc64(200 + nTable + n*3); - shell_check_oom(zInsert); - sqlite3_snprintf(200+nTable,zInsert, - "INSERT OR IGNORE INTO \"%s\" VALUES(?", zTable); - i = strlen30(zInsert); - for(j=1; jdb, zQuery, -1, &pQuery, 0); - if( rc ){ - utf8_printf(stderr, "Warning: cannot step \"%s\" backwards", zTable); - break; + return 0; + }else if( c=='?' ){ + if( (*(z++))==0 ) return 0; + }else if( c=='[' ){ + int prior_c = 0; + seen = 0; + invert = 0; + c = *(z++); + if( c==0 ) return 0; + c2 = *(zGlob++); + if( c2=='^' ){ + invert = 1; + c2 = *(zGlob++); + } + if( c2==']' ){ + if( c==']' ) seen = 1; + c2 = *(zGlob++); + } + while( c2 && c2!=']' ){ + if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){ + c2 = *(zGlob++); + if( c>=prior_c && c<=c2 ) seen = 1; + prior_c = 0; + }else{ + if( c==c2 ){ + seen = 1; + } + prior_c = c2; + } + c2 = *(zGlob++); + } + if( c2==0 || (seen ^ invert)==0 ) return 0; + }else if( c=='#' ){ + if( (z[0]=='-' || z[0]=='+') && IsDigit(z[1]) ) z++; + if( !IsDigit(z[0]) ) return 0; + z++; + while( IsDigit(z[0]) ){ z++; } + }else{ + if( c!=(*(z++)) ) return 0; } - } /* End for(k=0...) */ + } + return *skipWhite(z)==0; +} -end_data_xfer: - sqlite3_finalize(pQuery); - sqlite3_finalize(pInsert); - sqlite3_free(zQuery); - sqlite3_free(zInsert); +/* +** Compare the string as a command-line option with either one or two +** initial "-" characters. +*/ +static int optionMatch(const char *zStr, const char *zOpt){ + if( zStr[0]!='-' ) return 0; + zStr++; + if( zStr[0]=='-' ) zStr++; + return strcmp(zStr, zOpt)==0; } +/* +** Delete a file. +*/ +int shellDeleteFile(const char *zFilename){ + int rc; +#ifdef _WIN32 + wchar_t *z = sqlite3_win32_utf8_to_unicode(zFilename); + rc = _wunlink(z); + sqlite3_free(z); +#else + rc = unlink(zFilename); +#endif + return rc; +} /* -** Try to transfer all rows of the schema that match zWhere. For -** each row, invoke xForEach() on the object defined by that row. -** If an error is encountered while moving forward through the -** sqlite_schema table, try again moving backwards. +** Try to delete the temporary file (if there is one) and free the +** memory used to hold the name of the temp file. */ -static void tryToCloneSchema( - ShellState *p, - sqlite3 *newDb, - const char *zWhere, - void (*xForEach)(ShellState*,sqlite3*,const char*) +static void clearTempFile(ShellInState *psi){ + if( psi->zTempFile==0 ) return; + if( psi->doXdgOpen ) return; + if( shellDeleteFile(psi->zTempFile) ) return; + sqlite3_free(psi->zTempFile); + psi->zTempFile = 0; +} + +/* +** Create a new temp file name with the given suffix. +*/ +static void newTempFile(ShellInState *psi, const char *zSuffix){ + clearTempFile(psi); + sqlite3_free(psi->zTempFile); + psi->zTempFile = 0; + if( DBI(psi) ){ + sqlite3_file_control(DBI(psi), 0, SQLITE_FCNTL_TEMPFILENAME, + &psi->zTempFile); + } + if( psi->zTempFile==0 ){ + /* If DB is an in-memory database then the TEMPFILENAME file-control + ** will not work and we will need to fallback to guessing */ + char *zTemp; + sqlite3_uint64 r; + sqlite3_randomness(sizeof(r), &r); + zTemp = getenv("TEMP"); + if( zTemp==0 ) zTemp = getenv("TMP"); + if( zTemp==0 ){ +#ifdef _WIN32 + zTemp = "\\tmp"; +#else + zTemp = "/tmp"; +#endif + } - psi->zTempFile = sqlite3_mprintf("%s/temp%llx.%s", zTemp, r, zSuffix); ++ psi->zTempFile = smprintf("%s/temp%llx.%s", zTemp, r, zSuffix); + }else{ - psi->zTempFile = sqlite3_mprintf("%z.%s", psi->zTempFile, zSuffix); ++ psi->zTempFile = smprintf("%z.%s", psi->zTempFile, zSuffix); + } + shell_check_oom(psi->zTempFile); +} + +/* +** The implementation of SQL scalar function fkey_collate_clause(), used +** by the ".lint fkey-indexes" command. This scalar function is always +** called with four arguments - the parent table name, the parent column name, +** the child table name and the child column name. +** +** fkey_collate_clause('parent-tab', 'parent-col', 'child-tab', 'child-col') +** +** If either of the named tables or columns do not exist, this function +** returns an empty string. An empty string is also returned if both tables +** and columns exist but have the same default collation sequence. Or, +** if both exist but the default collation sequences are different, this +** function returns the string " COLLATE ", where +** is the default collation sequence of the parent column. +*/ +static void shellFkeyCollateClause( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal ){ - sqlite3_stmt *pQuery = 0; - char *zQuery = 0; + sqlite3 *db = sqlite3_context_db_handle(pCtx); + const char *zParent; + const char *zParentCol; + const char *zParentSeq; + const char *zChild; + const char *zChildCol; + const char *zChildSeq = 0; /* Initialize to avoid false-positive warning */ int rc; - const unsigned char *zName; - const unsigned char *zSql; - char *zErrMsg = 0; - zQuery = sqlite3_mprintf("SELECT name, sql FROM sqlite_schema" - " WHERE %s", zWhere); - shell_check_oom(zQuery); - rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); - if( rc ){ - utf8_printf(stderr, "Error: (%d) %s on [%s]\n", - sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), - zQuery); - goto end_schema_xfer; + assert( nVal==4 ); + zParent = (const char*)sqlite3_value_text(apVal[0]); + zParentCol = (const char*)sqlite3_value_text(apVal[1]); + zChild = (const char*)sqlite3_value_text(apVal[2]); + zChildCol = (const char*)sqlite3_value_text(apVal[3]); + + sqlite3_result_text(pCtx, "", -1, SQLITE_STATIC); + rc = sqlite3_table_column_metadata( + db, "main", zParent, zParentCol, 0, &zParentSeq, 0, 0, 0 + ); + if( rc==SQLITE_OK ){ + rc = sqlite3_table_column_metadata( + db, "main", zChild, zChildCol, 0, &zChildSeq, 0, 0, 0 + ); } - while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){ - zName = sqlite3_column_text(pQuery, 0); - zSql = sqlite3_column_text(pQuery, 1); - if( zName==0 || zSql==0 ) continue; - printf("%s... ", zName); fflush(stdout); - sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); - if( zErrMsg ){ - utf8_printf(stderr, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql); - sqlite3_free(zErrMsg); - zErrMsg = 0; - } - if( xForEach ){ - xForEach(p, newDb, (const char*)zName); + + if( rc==SQLITE_OK && sqlite3_stricmp(zParentSeq, zChildSeq) ){ - char *z = sqlite3_mprintf(" COLLATE %s", zParentSeq); ++ char *z = smprintf(" COLLATE %s", zParentSeq); + sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT); + sqlite3_free(z); + } +} + +#if !defined SQLITE_OMIT_VIRTUALTABLE +static void shellPrepare( + sqlite3 *db, + int *pRc, + const char *zSql, + sqlite3_stmt **ppStmt +){ + *ppStmt = 0; + if( *pRc==SQLITE_OK ){ + int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0); + if( rc!=SQLITE_OK ){ + raw_printf(STD_ERR, "sql error: %s (%d)\n", + sqlite3_errmsg(db), sqlite3_errcode(db) + ); + *pRc = rc; } - printf("done\n"); } - if( rc!=SQLITE_DONE ){ - sqlite3_finalize(pQuery); - sqlite3_free(zQuery); - zQuery = sqlite3_mprintf("SELECT name, sql FROM sqlite_schema" - " WHERE %s ORDER BY rowid DESC", zWhere); - shell_check_oom(zQuery); - rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); - if( rc ){ - utf8_printf(stderr, "Error: (%d) %s on [%s]\n", - sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), - zQuery); - goto end_schema_xfer; +} + +/* +** Create a prepared statement using printf-style arguments for the SQL. +** +** This routine is could be marked "static". But it is not always used, +** depending on compile-time options. By omitting the "static", we avoid +** nuisance compiler warnings about "defined but not used". +*/ +void shellPreparePrintf( + sqlite3 *db, + int *pRc, + sqlite3_stmt **ppStmt, + const char *zFmt, + ... +){ + *ppStmt = 0; + if( *pRc==SQLITE_OK ){ + va_list ap; + char *z; + va_start(ap, zFmt); + z = sqlite3_vmprintf(zFmt, ap); + va_end(ap); + if( z==0 ){ + *pRc = SQLITE_NOMEM; + }else{ + shellPrepare(db, pRc, z, ppStmt); + sqlite3_free(z); } - while( sqlite3_step(pQuery)==SQLITE_ROW ){ - zName = sqlite3_column_text(pQuery, 0); - zSql = sqlite3_column_text(pQuery, 1); - if( zName==0 || zSql==0 ) continue; - printf("%s... ", zName); fflush(stdout); - sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); - if( zErrMsg ){ - utf8_printf(stderr, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql); - sqlite3_free(zErrMsg); - zErrMsg = 0; - } - if( xForEach ){ - xForEach(p, newDb, (const char*)zName); + } +} + +/* Finalize the prepared statement created using shellPreparePrintf(). +** +** This routine is could be marked "static". But it is not always used, +** depending on compile-time options. By omitting the "static", we avoid +** nuisance compiler warnings about "defined but not used". +*/ +void shellFinalize( + int *pRc, + sqlite3_stmt *pStmt +){ + if( pStmt ){ + sqlite3 *db = sqlite3_db_handle(pStmt); + int rc = sqlite3_finalize(pStmt); + if( *pRc==SQLITE_OK ){ + if( rc!=SQLITE_OK ){ + raw_printf(STD_ERR, "SQL error: %s\n", sqlite3_errmsg(db)); } - printf("done\n"); + *pRc = rc; } } -end_schema_xfer: - sqlite3_finalize(pQuery); - sqlite3_free(zQuery); } -/* -** Open a new database file named "zNewDb". Try to recover as much information -** as possible out of the main database (which might be corrupt) and write it -** into zNewDb. +/* Reset the prepared statement created using shellPreparePrintf(). +** +** This routine is could be marked "static". But it is not always used, +** depending on compile-time options. By omitting the "static", we avoid +** nuisance compiler warnings about "defined but not used". */ -static void tryToClone(ShellState *p, const char *zNewDb){ - int rc; - sqlite3 *newDb = 0; - if( access(zNewDb,0)==0 ){ - utf8_printf(stderr, "File \"%s\" already exists.\n", zNewDb); - return; - } - rc = sqlite3_open(zNewDb, &newDb); - if( rc ){ - utf8_printf(stderr, "Cannot create output database: %s\n", - sqlite3_errmsg(newDb)); - }else{ - sqlite3_exec(p->db, "PRAGMA writable_schema=ON;", 0, 0, 0); - sqlite3_exec(newDb, "BEGIN EXCLUSIVE;", 0, 0, 0); - tryToCloneSchema(p, newDb, "type='table'", tryToCloneData); - tryToCloneSchema(p, newDb, "type!='table'", 0); - sqlite3_exec(newDb, "COMMIT;", 0, 0, 0); - sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0); +void shellReset( + int *pRc, + sqlite3_stmt *pStmt +){ + int rc = sqlite3_reset(pStmt); + if( *pRc==SQLITE_OK ){ + if( rc!=SQLITE_OK ){ + sqlite3 *db = sqlite3_db_handle(pStmt); + raw_printf(STD_ERR, "SQL error: %s\n", sqlite3_errmsg(db)); + } + *pRc = rc; } - close_db(newDb); +} +#endif /* !defined SQLITE_OMIT_VIRTUALTABLE */ + +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) +/****************************************************************************** +** The ".archive" or ".ar" command. +*/ +/* +** Structure representing a single ".ar" command. +*/ +typedef struct ArCommand ArCommand; +struct ArCommand { + u8 eCmd; /* An AR_CMD_* value */ + u8 bVerbose; /* True if --verbose */ + u8 bZip; /* True if the archive is a ZIP */ + u8 bDryRun; /* True if --dry-run */ + u8 bAppend; /* True if --append */ + u8 bGlob; /* True if --glob */ + u8 fromCmdLine; /* Run from -A instead of .archive */ + int nArg; /* Number of command arguments */ + char *zSrcTable; /* "sqlar", "zipfile($file)" or "zip" */ + const char *zFile; /* --file argument, or NULL */ + const char *zDir; /* --directory argument, or NULL */ + char **azArg; /* Array of command arguments */ + ShellExState *p; /* Shell state */ + FILE *out; /* Where to put normal messages or info */ + sqlite3 *db; /* Database containing the archive */ +}; + +/* +** Print a usage message for the .ar command to stderr and return SQLITE_ERROR. +*/ +static int arUsage(FILE *f, ArCommand *pAr){ + showHelp(f,"archive", pAr->p); + return SQLITE_ERROR; } /* @@@ -6461,688 -5779,566 +6396,681 @@@ static int arProcessSwitch(ArCommand *p } /* -** Implementation of the ".dbinfo" command. -** -** Return 1 on error, 2 to exit, and 0 otherwise. +** Parse the command line for an ".ar" command. The results are written into +** structure (*pAr). DCR_Ok is returned if the command line is parsed +** successfully, otherwise an error message is written to stderr and an +** appropriate DCR_* argument error is returned. */ -static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ - static const struct { const char *zName; int ofst; } aField[] = { - { "file change counter:", 24 }, - { "database page count:", 28 }, - { "freelist page count:", 36 }, - { "schema cookie:", 40 }, - { "schema format:", 44 }, - { "default cache size:", 48 }, - { "autovacuum top root:", 52 }, - { "incremental vacuum:", 64 }, - { "text encoding:", 56 }, - { "user version:", 60 }, - { "application id:", 68 }, - { "software version:", 96 }, - }; - static const struct { const char *zName; const char *zSql; } aQuery[] = { - { "number of tables:", - "SELECT count(*) FROM %s WHERE type='table'" }, - { "number of indexes:", - "SELECT count(*) FROM %s WHERE type='index'" }, - { "number of triggers:", - "SELECT count(*) FROM %s WHERE type='trigger'" }, - { "number of views:", - "SELECT count(*) FROM %s WHERE type='view'" }, - { "schema size:", - "SELECT total(length(sql)) FROM %s" }, +static DotCmdRC arParseCommand( + char **azArg, /* Array of arguments passed to dot command */ + int nArg, /* Number of entries in azArg[] */ + ArCommand *pAr /* Populate this object */ +){ + struct ArSwitch { + const char *zLong; + char cShort; + u8 eSwitch; + u8 bArg; + } aSwitch[] = { + { "create", 'c', AR_CMD_CREATE, 0 }, + { "extract", 'x', AR_CMD_EXTRACT, 0 }, + { "insert", 'i', AR_CMD_INSERT, 0 }, + { "list", 't', AR_CMD_LIST, 0 }, + { "remove", 'r', AR_CMD_REMOVE, 0 }, + { "update", 'u', AR_CMD_UPDATE, 0 }, + { "help", 'h', AR_CMD_HELP, 0 }, + { "verbose", 'v', AR_SWITCH_VERBOSE, 0 }, + { "file", 'f', AR_SWITCH_FILE, 1 }, + { "append", 'a', AR_SWITCH_APPEND, 1 }, + { "directory", 'C', AR_SWITCH_DIRECTORY, 1 }, + { "dryrun", 'n', AR_SWITCH_DRYRUN, 0 }, + { "glob", 'g', AR_SWITCH_GLOB, 0 }, }; - int i, rc; - unsigned iDataVersion; - char *zSchemaTab; - char *zDb = nArg>=2 ? azArg[1] : "main"; - sqlite3_stmt *pStmt = 0; - unsigned char aHdr[100]; - open_db(p, 0); - if( p->db==0 ) return 1; - rc = sqlite3_prepare_v2(p->db, - "SELECT data FROM sqlite_dbpage(?1) WHERE pgno=1", - -1, &pStmt, 0); - if( rc ){ - utf8_printf(stderr, "error: %s\n", sqlite3_errmsg(p->db)); - sqlite3_finalize(pStmt); - return 1; - } - sqlite3_bind_text(pStmt, 1, zDb, -1, SQLITE_STATIC); - if( sqlite3_step(pStmt)==SQLITE_ROW - && sqlite3_column_bytes(pStmt,0)>100 - ){ - memcpy(aHdr, sqlite3_column_blob(pStmt,0), 100); - sqlite3_finalize(pStmt); + int nSwitch = sizeof(aSwitch) / sizeof(struct ArSwitch); + struct ArSwitch *pEnd = &aSwitch[nSwitch]; + DotCmdRC rv = DCR_Ok; + + if( nArg<=1 ){ + utf8_printf(STD_ERR, "Error: Wrong number of arguments to \"%s\".\n", + azArg[0]); + if( stdin_is_interactive ){ + utf8_printf(STD_ERR, "Usage:\n"); + arUsage(STD_ERR, pAr); - return DCR_TooFew; ++ return DCR_CmdErred; + } }else{ - raw_printf(stderr, "unable to read database header\n"); - sqlite3_finalize(pStmt); - return 1; - } - i = get2byteInt(aHdr+16); - if( i==1 ) i = 65536; - utf8_printf(p->out, "%-20s %d\n", "database page size:", i); - utf8_printf(p->out, "%-20s %d\n", "write format:", aHdr[18]); - utf8_printf(p->out, "%-20s %d\n", "read format:", aHdr[19]); - utf8_printf(p->out, "%-20s %d\n", "reserved bytes:", aHdr[20]); - for(i=0; iout, "%-20s %u", aField[i].zName, val); - switch( ofst ){ - case 56: { - if( val==1 ) raw_printf(p->out, " (utf8)"); - if( val==2 ) raw_printf(p->out, " (utf16le)"); - if( val==3 ) raw_printf(p->out, " (utf16be)"); + char *z = azArg[1]; + if( z[0]!='-' ){ + /* Traditional style [tar] invocation */ + int i; + int iArg = 2; + for(i=0; z[i]; i++){ + const char *zArg = 0; + struct ArSwitch *pOpt; + for(pOpt=&aSwitch[0]; pOptcShort ) break; + } + if( pOpt==pEnd ){ + arErrorMsg(pAr, "unrecognized option: %c", z[i]); + return DCR_Unknown|i; + } + if( pOpt->bArg ){ + if( iArg>=nArg ){ + arErrorMsg(pAr, "option requires an argument: %c",z[i]); + return DCR_Unpaired|i; + } + zArg = azArg[iArg++]; + } + rv = arProcessSwitch(pAr, pOpt->eSwitch, zArg); + if( rv!=DCR_Ok ) return rv; + } + pAr->nArg = nArg-iArg; + if( pAr->nArg>0 ){ + pAr->azArg = &azArg[iArg]; + } + }else{ + /* Non-traditional invocation */ + int iArg; + for(iArg=1; iArgazArg = &azArg[iArg]; + pAr->nArg = nArg-iArg; + break; + } + n = strlen30(z); + + if( z[1]!='-' ){ + int i; + /* One or more short options */ + for(i=1; icShort ) break; + } + if( pOpt==pEnd ){ + arErrorMsg(pAr, "unrecognized option: %c", z[i]); + return DCR_Unknown|iArg; + } + if( pOpt->bArg ){ + if( i<(n-1) ){ + zArg = &z[i+1]; + i = n; + }else{ + if( iArg>=(nArg-1) ){ + arErrorMsg(pAr, "option requires an argument: %c", z[i]); + return DCR_Unpaired|iArg; + } + zArg = azArg[++iArg]; + } + } + rv = arProcessSwitch(pAr, pOpt->eSwitch, zArg); + if( rv!=DCR_Ok ) return rv; + } + }else if( z[2]=='\0' ){ + /* A -- option, indicating that all remaining command line words + ** are command arguments. */ + pAr->azArg = &azArg[iArg+1]; + pAr->nArg = nArg-iArg-1; + break; + }else{ + /* A long option */ + const char *zArg = 0; /* Argument for option, if any */ + struct ArSwitch *pMatch = 0; /* Matching option */ + struct ArSwitch *pOpt; /* Iterator */ + for(pOpt=&aSwitch[0]; pOptzLong; + if( (n-2)<=strlen30(zLong) && 0==memcmp(&z[2], zLong, n-2) ){ + if( pMatch ){ + arErrorMsg(pAr, "ambiguous option: %s",z); + return DCR_Ambiguous|iArg; + }else{ + pMatch = pOpt; + } + } + } + + if( pMatch==0 ){ + arErrorMsg(pAr, "unrecognized option: %s", z); + return DCR_Unknown|iArg; + } + if( pMatch->bArg ){ + if( iArg>=(nArg-1) ){ + arErrorMsg(pAr, "option requires an argument: %s", z); + return DCR_Unpaired|iArg; + } + zArg = azArg[++iArg]; + } + if( arProcessSwitch(pAr, pMatch->eSwitch, zArg) ) return SQLITE_ERROR; + } } } - raw_printf(p->out, "\n"); - } - if( zDb==0 ){ - zSchemaTab = sqlite3_mprintf("main.sqlite_schema"); - }else if( strcmp(zDb,"temp")==0 ){ - zSchemaTab = sqlite3_mprintf("%s", "sqlite_temp_schema"); - }else{ - zSchemaTab = sqlite3_mprintf("\"%w\".sqlite_schema", zDb); } - for(i=0; idb, zSql); - sqlite3_free(zSql); - utf8_printf(p->out, "%-20s %d\n", aQuery[i].zName, val); - } - sqlite3_free(zSchemaTab); - sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion); - utf8_printf(p->out, "%-20s %u\n", "data version", iDataVersion); - return 0; -} -/* -** Print the current sqlite3_errmsg() value to stderr and return 1. -*/ -static int shellDatabaseError(sqlite3 *db){ - const char *zErr = sqlite3_errmsg(db); - utf8_printf(stderr, "Error: %s\n", zErr); - return 1; + return SQLITE_OK; } /* -** Compare the pattern in zGlob[] against the text in z[]. Return TRUE -** if they match and FALSE (0) if they do not match. -** -** Globbing rules: -** -** '*' Matches any sequence of zero or more characters. -** -** '?' Matches exactly one character. -** -** [...] Matches one character from the enclosed list of -** characters. -** -** [^...] Matches one character not in the enclosed list. -** -** '#' Matches any sequence of one or more digits with an -** optional + or - sign in front -** -** ' ' Any span of whitespace matches any other span of -** whitespace. +** This function assumes that all arguments within the ArCommand.azArg[] +** array refer to archive members, as for the --extract, --list or --remove +** commands. It checks that each of them are "present". If any specified +** file is not present in the archive, an error is printed to stderr and an +** error code returned. Otherwise, if all specified arguments are present +** in the archive, SQLITE_OK is returned. Here, "present" means either an +** exact equality when pAr->bGlob is false or a "name GLOB pattern" match +** when pAr->bGlob is true. ** -** Extra whitespace at the end of z[] is ignored. +** This function strips any trailing '/' characters from each argument. +** This is consistent with the way the [tar] command seems to work on +** Linux. */ -static int testcase_glob(const char *zGlob, const char *z){ - int c, c2; - int invert; - int seen; +static int arCheckEntries(ArCommand *pAr){ + int rc = SQLITE_OK; + if( pAr->nArg ){ + int i, j; + sqlite3_stmt *pTest = 0; + const char *zSel = (pAr->bGlob) + ? "SELECT name FROM %s WHERE glob($name,name)" + : "SELECT name FROM %s WHERE name=$name"; - while( (c = (*(zGlob++)))!=0 ){ - if( IsSpace(c) ){ - if( !IsSpace(*z) ) return 0; - while( IsSpace(*zGlob) ) zGlob++; - while( IsSpace(*z) ) z++; - }else if( c=='*' ){ - while( (c=(*(zGlob++))) == '*' || c=='?' ){ - if( c=='?' && (*(z++))==0 ) return 0; - } - if( c==0 ){ - return 1; - }else if( c=='[' ){ - while( *z && testcase_glob(zGlob-1,z)==0 ){ - z++; - } - return (*z)!=0; - } - while( (c2 = (*(z++)))!=0 ){ - while( c2!=c ){ - c2 = *(z++); - if( c2==0 ) return 0; - } - if( testcase_glob(zGlob,z) ) return 1; - } - return 0; - }else if( c=='?' ){ - if( (*(z++))==0 ) return 0; - }else if( c=='[' ){ - int prior_c = 0; - seen = 0; - invert = 0; - c = *(z++); - if( c==0 ) return 0; - c2 = *(zGlob++); - if( c2=='^' ){ - invert = 1; - c2 = *(zGlob++); + shellPreparePrintf(pAr->db, &rc, &pTest, zSel, pAr->zSrcTable); + j = sqlite3_bind_parameter_index(pTest, "$name"); + for(i=0; inArg && rc==SQLITE_OK; i++){ + char *z = pAr->azArg[i]; + int n = strlen30(z); + int bOk = 0; + while( n>0 && z[n-1]=='/' ) n--; + z[n] = '\0'; + sqlite3_bind_text(pTest, j, z, -1, SQLITE_STATIC); + if( SQLITE_ROW==sqlite3_step(pTest) ){ + bOk = 1; } - if( c2==']' ){ - if( c==']' ) seen = 1; - c2 = *(zGlob++); + shellReset(&rc, pTest); + if( rc==SQLITE_OK && bOk==0 ){ + utf8_printf(STD_ERR, "not found in archive: %s\n", z); + rc = SQLITE_ERROR; } - while( c2 && c2!=']' ){ - if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){ - c2 = *(zGlob++); - if( c>=prior_c && c<=c2 ) seen = 1; - prior_c = 0; - }else{ - if( c==c2 ){ - seen = 1; - } - prior_c = c2; + } + shellFinalize(&rc, pTest); + } + return rc; +} + +/* +** Format a WHERE clause that can be used against the "sqlar" table to +** identify all archive members that match the command arguments held +** in (*pAr). Leave this WHERE clause in (*pzWhere) before returning. +** The caller is responsible for eventually calling sqlite3_free() on +** any non-NULL (*pzWhere) value. Here, "match" means strict equality +** when pAr->bGlob is false and GLOB match when pAr->bGlob is true. + +*/ +static void arWhereClause( + int *pRc, + ArCommand *pAr, + char **pzWhere /* OUT: New WHERE clause */ +){ + char *zWhere = 0; + const char *zSameOp = (pAr->bGlob)? "GLOB" : "="; + if( *pRc==SQLITE_OK ){ + if( pAr->nArg==0 ){ - zWhere = sqlite3_mprintf("1"); ++ zWhere = smprintf("1"); + }else{ + int i; + const char *zSep = ""; + for(i=0; inArg; i++){ + const char *z = pAr->azArg[i]; - zWhere = sqlite3_mprintf( - "%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'", - zWhere, zSep, zSameOp, z, strlen30(z)+1, zSameOp, z - ); ++ zWhere = smprintf("%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'", ++ zWhere, zSep, zSameOp, z, strlen30(z)+1, zSameOp, z); + if( zWhere==0 ){ + *pRc = SQLITE_NOMEM; + break; } - c2 = *(zGlob++); + zSep = " OR "; } - if( c2==0 || (seen ^ invert)==0 ) return 0; - }else if( c=='#' ){ - if( (z[0]=='-' || z[0]=='+') && IsDigit(z[1]) ) z++; - if( !IsDigit(z[0]) ) return 0; - z++; - while( IsDigit(z[0]) ){ z++; } - }else{ - if( c!=(*(z++)) ) return 0; } } - while( IsSpace(*z) ){ z++; } - return *z==0; + *pzWhere = zWhere; } - /* -** Compare the string as a command-line option with either one or two -** initial "-" characters. +** Implementation of .ar "lisT" command. */ -static int optionMatch(const char *zStr, const char *zOpt){ - if( zStr[0]!='-' ) return 0; - zStr++; - if( zStr[0]=='-' ) zStr++; - return strcmp(zStr, zOpt)==0; +static int arListCommand(ArCommand *pAr){ + const char *zSql = "SELECT %s FROM %s WHERE %s"; + const char *azCols[] = { + "name", + "lsmode(mode), sz, datetime(mtime, 'unixepoch'), name" + }; + + char *zWhere = 0; + sqlite3_stmt *pSql = 0; + int rc; + + rc = arCheckEntries(pAr); + arWhereClause(&rc, pAr, &zWhere); + + shellPreparePrintf(pAr->db, &rc, &pSql, zSql, azCols[pAr->bVerbose], + pAr->zSrcTable, zWhere); + if( pAr->bDryRun ){ + utf8_printf(pAr->out, "%s\n", sqlite3_sql(pSql)); + }else{ + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ + if( pAr->bVerbose ){ + utf8_printf(pAr->out, "%s % 10d %s %s\n", + sqlite3_column_text(pSql, 0), + sqlite3_column_int(pSql, 1), + sqlite3_column_text(pSql, 2), + sqlite3_column_text(pSql, 3) + ); + }else{ + utf8_printf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0)); + } + } + } + shellFinalize(&rc, pSql); + sqlite3_free(zWhere); + return rc; } + /* -** Delete a file. +** Implementation of .ar "Remove" command. */ -int shellDeleteFile(const char *zFilename){ - int rc; -#ifdef _WIN32 - wchar_t *z = sqlite3_win32_utf8_to_unicode(zFilename); - rc = _wunlink(z); - sqlite3_free(z); -#else - rc = unlink(zFilename); -#endif +static int arRemoveCommand(ArCommand *pAr){ + int rc = 0; + char *zSql = 0; + char *zWhere = 0; + + if( pAr->nArg ){ + /* Verify that args actually exist within the archive before proceeding. + ** And formulate a WHERE clause to match them. */ + rc = arCheckEntries(pAr); + arWhereClause(&rc, pAr, &zWhere); + } + if( rc==SQLITE_OK ){ - zSql = sqlite3_mprintf("DELETE FROM %s WHERE %s;", - pAr->zSrcTable, zWhere); ++ zSql = smprintf("DELETE FROM %s WHERE %s;", pAr->zSrcTable, zWhere); + if( pAr->bDryRun ){ + utf8_printf(pAr->out, "%s\n", zSql); + }else{ + char *zErr = 0; + rc = sqlite3_exec(pAr->db, "SAVEPOINT ar;", 0, 0, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr); + if( rc!=SQLITE_OK ){ + sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0); + }else{ + rc = sqlite3_exec(pAr->db, "RELEASE ar;", 0, 0, 0); + } + } + if( zErr ){ + utf8_printf(STD_OUT, "ERROR: %s\n", zErr); + sqlite3_free(zErr); + } + } + } + sqlite3_free(zWhere); + sqlite3_free(zSql); return rc; } /* -** Try to delete the temporary file (if there is one) and free the -** memory used to hold the name of the temp file. +** Implementation of .ar "eXtract" command. */ -static void clearTempFile(ShellState *p){ - if( p->zTempFile==0 ) return; - if( p->doXdgOpen ) return; - if( shellDeleteFile(p->zTempFile) ) return; - sqlite3_free(p->zTempFile); - p->zTempFile = 0; -} +static int arExtractCommand(ArCommand *pAr){ + const char *zSql1 = + "SELECT " + " ($dir || name)," + " writefile(($dir || name), %s, mode, mtime) " + "FROM %s WHERE (%s) AND (data IS NULL OR $dirOnly = 0)" + " AND name NOT GLOB '*..[/\\]*'"; -/* -** Create a new temp file name with the given suffix. -*/ -static void newTempFile(ShellState *p, const char *zSuffix){ - clearTempFile(p); - sqlite3_free(p->zTempFile); - p->zTempFile = 0; - if( p->db ){ - sqlite3_file_control(p->db, 0, SQLITE_FCNTL_TEMPFILENAME, &p->zTempFile); - } - if( p->zTempFile==0 ){ - /* If p->db is an in-memory database then the TEMPFILENAME file-control - ** will not work and we will need to fallback to guessing */ - char *zTemp; - sqlite3_uint64 r; - sqlite3_randomness(sizeof(r), &r); - zTemp = getenv("TEMP"); - if( zTemp==0 ) zTemp = getenv("TMP"); - if( zTemp==0 ){ -#ifdef _WIN32 - zTemp = "\\tmp"; -#else - zTemp = "/tmp"; -#endif - } - p->zTempFile = sqlite3_mprintf("%s/temp%llx.%s", zTemp, r, zSuffix); - }else{ - p->zTempFile = sqlite3_mprintf("%z.%s", p->zTempFile, zSuffix); - } - shell_check_oom(p->zTempFile); -} + const char *azExtraArg[] = { + "sqlar_uncompress(data, sz)", + "data" + }; + sqlite3_stmt *pSql = 0; + int rc = SQLITE_OK; + char *zDir = 0; + char *zWhere = 0; + int i, j; -/* -** The implementation of SQL scalar function fkey_collate_clause(), used -** by the ".lint fkey-indexes" command. This scalar function is always -** called with four arguments - the parent table name, the parent column name, -** the child table name and the child column name. -** -** fkey_collate_clause('parent-tab', 'parent-col', 'child-tab', 'child-col') -** -** If either of the named tables or columns do not exist, this function -** returns an empty string. An empty string is also returned if both tables -** and columns exist but have the same default collation sequence. Or, -** if both exist but the default collation sequences are different, this -** function returns the string " COLLATE ", where -** is the default collation sequence of the parent column. -*/ -static void shellFkeyCollateClause( - sqlite3_context *pCtx, - int nVal, - sqlite3_value **apVal -){ - sqlite3 *db = sqlite3_context_db_handle(pCtx); - const char *zParent; - const char *zParentCol; - const char *zParentSeq; - const char *zChild; - const char *zChildCol; - const char *zChildSeq = 0; /* Initialize to avoid false-positive warning */ - int rc; + /* If arguments are specified, check that they actually exist within + ** the archive before proceeding. And formulate a WHERE clause to + ** match them. */ + rc = arCheckEntries(pAr); + arWhereClause(&rc, pAr, &zWhere); - assert( nVal==4 ); - zParent = (const char*)sqlite3_value_text(apVal[0]); - zParentCol = (const char*)sqlite3_value_text(apVal[1]); - zChild = (const char*)sqlite3_value_text(apVal[2]); - zChildCol = (const char*)sqlite3_value_text(apVal[3]); + if( rc==SQLITE_OK ){ + if( pAr->zDir ){ - zDir = sqlite3_mprintf("%s/", pAr->zDir); ++ zDir = smprintf("%s/", pAr->zDir); + }else{ - zDir = sqlite3_mprintf(""); ++ zDir = smprintf(""); + } + if( zDir==0 ) rc = SQLITE_NOMEM; + } - sqlite3_result_text(pCtx, "", -1, SQLITE_STATIC); - rc = sqlite3_table_column_metadata( - db, "main", zParent, zParentCol, 0, &zParentSeq, 0, 0, 0 + shellPreparePrintf(pAr->db, &rc, &pSql, zSql1, + azExtraArg[pAr->bZip], pAr->zSrcTable, zWhere ); + if( rc==SQLITE_OK ){ - rc = sqlite3_table_column_metadata( - db, "main", zChild, zChildCol, 0, &zChildSeq, 0, 0, 0 - ); + j = sqlite3_bind_parameter_index(pSql, "$dir"); + sqlite3_bind_text(pSql, j, zDir, -1, SQLITE_STATIC); + + /* Run the SELECT statement twice. The first time, writefile() is called + ** for all archive members that should be extracted. The second time, + ** only for the directories. This is because the timestamps for + ** extracted directories must be reset after they are populated (as + ** populating them changes the timestamp). */ + for(i=0; i<2; i++){ + j = sqlite3_bind_parameter_index(pSql, "$dirOnly"); + sqlite3_bind_int(pSql, j, i); + if( pAr->bDryRun ){ + utf8_printf(pAr->out, "%s\n", sqlite3_sql(pSql)); + }else{ + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ + if( i==0 && pAr->bVerbose ){ + utf8_printf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0)); + } + } + } + shellReset(&rc, pSql); + } + shellFinalize(&rc, pSql); } - if( rc==SQLITE_OK && sqlite3_stricmp(zParentSeq, zChildSeq) ){ - char *z = sqlite3_mprintf(" COLLATE %s", zParentSeq); - sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT); - sqlite3_free(z); + sqlite3_free(zDir); + sqlite3_free(zWhere); + return rc; +} + +/* +** Run the SQL statement in zSql. Or if doing a --dryrun, merely print it out. +*/ +static int arExecSql(ArCommand *pAr, const char *zSql){ + int rc; + if( pAr->bDryRun ){ + utf8_printf(pAr->out, "%s\n", zSql); + rc = SQLITE_OK; + }else{ + char *zErr = 0; + rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr); + if( zErr ){ + utf8_printf(STD_OUT, "ERROR: %s\n", zErr); + sqlite3_free(zErr); + } } + return rc; } /* -** The implementation of dot-command ".lint fkey-indexes". +** Implementation of .ar "create", "insert", and "update" commands. +** +** create -> Create a new SQL archive +** insert -> Insert or reinsert all files listed +** update -> Insert files that have changed or that were not +** previously in the archive +** +** Create the "sqlar" table in the database if it does not already exist. +** Then add each file in the azFile[] array to the archive. Directories +** are added recursively. If argument bVerbose is non-zero, a message is +** printed on stdout for each file archived. +** +** The create command is the same as update, except that it drops +** any existing "sqlar" table before beginning. The "insert" command +** always overwrites every file named on the command-line, where as +** "update" only overwrites if the size or mtime or mode has changed. */ -static int lintFkeyIndexes( - ShellState *pState, /* Current shell tool state */ - char **azArg, /* Array of arguments passed to dot command */ - int nArg /* Number of entries in azArg[] */ +static int arCreateOrUpdateCommand( + ArCommand *pAr, /* Command arguments and options */ + int bUpdate, /* true for a --create. */ + int bOnlyIfChanged /* Only update if file has changed */ ){ - sqlite3 *db = pState->db; /* Database handle to query "main" db of */ - FILE *out = pState->out; /* Stream to write non-error output to */ - int bVerbose = 0; /* If -verbose is present */ - int bGroupByParent = 0; /* If -groupbyparent is present */ - int i; /* To iterate through azArg[] */ - const char *zIndent = ""; /* How much to indent CREATE INDEX by */ - int rc; /* Return code */ - sqlite3_stmt *pSql = 0; /* Compiled version of SQL statement below */ - - /* - ** This SELECT statement returns one row for each foreign key constraint - ** in the schema of the main database. The column values are: - ** - ** 0. The text of an SQL statement similar to: - ** - ** "EXPLAIN QUERY PLAN SELECT 1 FROM child_table WHERE child_key=?" - ** - ** This SELECT is similar to the one that the foreign keys implementation - ** needs to run internally on child tables. If there is an index that can - ** be used to optimize this query, then it can also be used by the FK - ** implementation to optimize DELETE or UPDATE statements on the parent - ** table. - ** - ** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by - ** the EXPLAIN QUERY PLAN command matches this pattern, then the schema - ** contains an index that can be used to optimize the query. - ** - ** 2. Human readable text that describes the child table and columns. e.g. - ** - ** "child_table(child_key1, child_key2)" - ** - ** 3. Human readable text that describes the parent table and columns. e.g. - ** - ** "parent_table(parent_key1, parent_key2)" - ** - ** 4. A full CREATE INDEX statement for an index that could be used to - ** optimize DELETE or UPDATE statements on the parent table. e.g. - ** - ** "CREATE INDEX child_table_child_key ON child_table(child_key)" - ** - ** 5. The name of the parent table. - ** - ** These six values are used by the C logic below to generate the report. - */ - const char *zSql = - "SELECT " - " 'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '" - " || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' " - " || fkey_collate_clause(" - " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')" - ", " - " 'SEARCH ' || s.name || ' USING COVERING INDEX*('" - " || group_concat('*=?', ' AND ') || ')'" - ", " - " s.name || '(' || group_concat(f.[from], ', ') || ')'" - ", " - " f.[table] || '(' || group_concat(COALESCE(f.[to], p.[name])) || ')'" - ", " - " 'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))" - " || ' ON ' || quote(s.name) || '('" - " || group_concat(quote(f.[from]) ||" - " fkey_collate_clause(" - " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')" - " || ');'" - ", " - " f.[table] " - "FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f " - "LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) " - "GROUP BY s.name, f.id " - "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)" - ; - const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)"; + const char *zCreate = + "CREATE TABLE IF NOT EXISTS sqlar(\n" + " name TEXT PRIMARY KEY, -- name of the file\n" + " mode INT, -- access permissions\n" + " mtime INT, -- last modification time\n" + " sz INT, -- original file size\n" + " data BLOB -- compressed content\n" + ")"; + const char *zDrop = "DROP TABLE IF EXISTS sqlar"; + const char *zInsertFmt[2] = { + "REPLACE INTO %s(name,mode,mtime,sz,data)\n" + " SELECT\n" + " %s,\n" + " mode,\n" + " mtime,\n" + " CASE substr(lsmode(mode),1,1)\n" + " WHEN '-' THEN length(data)\n" + " WHEN 'd' THEN 0\n" + " ELSE -1 END,\n" + " sqlar_compress(data)\n" + " FROM fsdir(%Q,%Q) AS disk\n" + " WHERE lsmode(mode) NOT LIKE '?%%'%s;" + , + "REPLACE INTO %s(name,mode,mtime,data)\n" + " SELECT\n" + " %s,\n" + " mode,\n" + " mtime,\n" + " data\n" + " FROM fsdir(%Q,%Q) AS disk\n" + " WHERE lsmode(mode) NOT LIKE '?%%'%s;" + }; + int i; /* For iterating through azFile[] */ + int rc; /* Return code */ + const char *zTab = 0; /* SQL table into which to insert */ + char *zSql; + char zTemp[50]; + char *zExists = 0; - for(i=2; i1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){ - bVerbose = 1; + arExecSql(pAr, "PRAGMA page_size=512"); + rc = arExecSql(pAr, "SAVEPOINT ar;"); + if( rc!=SQLITE_OK ) return rc; + zTemp[0] = 0; + if( pAr->bZip ){ + /* Initialize the zipfile virtual table, if necessary */ + if( pAr->zFile ){ + sqlite3_uint64 r; + sqlite3_randomness(sizeof(r),&r); + sqlite3_snprintf(sizeof(zTemp),zTemp,"zip%016llx",r); + zTab = zTemp; - zSql = sqlite3_mprintf( - "CREATE VIRTUAL TABLE temp.%s USING zipfile(%Q)", - zTab, pAr->zFile ++ zSql = smprintf("CREATE VIRTUAL TABLE temp.%s USING zipfile(%Q)", ++ zTab, pAr->zFile + ); + rc = arExecSql(pAr, zSql); + sqlite3_free(zSql); + }else{ + zTab = "zip"; } - else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){ - bGroupByParent = 1; - zIndent = " "; + }else{ + /* Initialize the table for an SQLAR */ + zTab = "sqlar"; + if( bUpdate==0 ){ + rc = arExecSql(pAr, zDrop); + if( rc!=SQLITE_OK ) goto end_ar_transaction; } - else{ - raw_printf(stderr, "Usage: %s %s ?-verbose? ?-groupbyparent?\n", - azArg[0], azArg[1] - ); - return SQLITE_ERROR; + rc = arExecSql(pAr, zCreate); + } + if( bOnlyIfChanged ){ - zExists = sqlite3_mprintf( - " AND NOT EXISTS(" - "SELECT 1 FROM %s AS mem" - " WHERE mem.name=disk.name" - " AND mem.mtime=disk.mtime" - " AND mem.mode=disk.mode)", zTab); ++ zExists = smprintf(" AND NOT EXISTS(SELECT 1 FROM %s AS mem" ++ " WHERE mem.name=disk.name AND mem.mtime=disk.mtime" ++ " AND mem.mode=disk.mode)", zTab); + }else{ - zExists = sqlite3_mprintf(""); ++ zExists = smprintf(""); + } + if( zExists==0 ) rc = SQLITE_NOMEM; + for(i=0; inArg && rc==SQLITE_OK; i++){ - char *zSql2 = sqlite3_mprintf(zInsertFmt[pAr->bZip], zTab, - pAr->bVerbose ? "shell_putsnl(name)" : "name", - pAr->azArg[i], pAr->zDir, zExists); ++ char *zSql2 = smprintf(zInsertFmt[pAr->bZip], zTab, ++ pAr->bVerbose ? "shell_putsnl(name)" : "name", ++ pAr->azArg[i], pAr->zDir, zExists); + rc = arExecSql(pAr, zSql2); + sqlite3_free(zSql2); + } +end_ar_transaction: + if( rc!=SQLITE_OK ){ + sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0); + }else{ + rc = arExecSql(pAr, "RELEASE ar;"); + if( pAr->bZip && pAr->zFile ){ - zSql = sqlite3_mprintf("DROP TABLE %s", zTemp); ++ zSql = smprintf("DROP TABLE %s", zTemp); + arExecSql(pAr, zSql); + sqlite3_free(zSql); } } + sqlite3_free(zExists); + return rc; +} - /* Register the fkey_collate_clause() SQL function */ - rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8, - 0, shellFkeyCollateClause, 0, 0 - ); - +/* +** Implementation of ".ar" dot command. +*/ +static DotCmdRC arDotCommand( + ShellExState *pState, /* Current shell tool state */ + int fromCmdLine, /* True if -A command-line option, not .ar cmd */ + char **azArg, /* Array of arguments passed to dot command */ + int nArg /* Number of entries in azArg[] */ +){ + ArCommand cmd; + DotCmdRC rv; + int rc; + memset(&cmd, 0, sizeof(cmd)); + cmd.fromCmdLine = fromCmdLine; + rv = arParseCommand(azArg, nArg, &cmd); + cmd.out = currentOutputFile(pState); + if( rv==DCR_Ok ){ + int eDbType = SHELL_OPEN_UNSPEC; + cmd.p = pState; + cmd.db = DBX(pState); + if( cmd.zFile ){ + eDbType = deduceDatabaseType(cmd.zFile, 1); + }else{ + eDbType = ISS(pState)->openMode; + } + if( eDbType==SHELL_OPEN_ZIPFILE ){ + if( cmd.eCmd==AR_CMD_EXTRACT || cmd.eCmd==AR_CMD_LIST ){ + if( cmd.zFile==0 ){ - cmd.zSrcTable = sqlite3_mprintf("zip"); ++ cmd.zSrcTable = smprintf("zip"); + }else{ - cmd.zSrcTable = sqlite3_mprintf("zipfile(%Q)", cmd.zFile); ++ cmd.zSrcTable = smprintf("zipfile(%Q)", cmd.zFile); + } + } + cmd.bZip = 1; + }else if( cmd.zFile ){ + int flags; + if( cmd.bAppend ) eDbType = SHELL_OPEN_APPENDVFS; + if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_INSERT + || cmd.eCmd==AR_CMD_REMOVE || cmd.eCmd==AR_CMD_UPDATE ){ + flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE; + }else{ + flags = SQLITE_OPEN_READONLY; + } + cmd.db = 0; + if( cmd.bDryRun ){ + utf8_printf(cmd.out, "-- open database '%s'%s\n", cmd.zFile, + eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : ""); + } + rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, + eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0); + if( rc!=SQLITE_OK ){ + utf8_printf(STD_ERR, "cannot open file: %s (%s)\n", + cmd.zFile, sqlite3_errmsg(cmd.db) + ); + goto end_ar_command; + } + sqlite3_fileio_init(cmd.db, 0, 0); + sqlite3_sqlar_init(cmd.db, 0, 0); + sqlite3_create_function(cmd.db, "shell_putsnl", 1, SQLITE_UTF8, cmd.p, + shellPutsFunc, 0, 0); + } + if( cmd.zSrcTable==0 && cmd.bZip==0 && cmd.eCmd!=AR_CMD_HELP ){ + if( cmd.eCmd!=AR_CMD_CREATE + && sqlite3_table_column_metadata(cmd.db,0,"sqlar","name",0,0,0,0,0) + ){ + utf8_printf(STD_ERR, "database does not contain an 'sqlar' table\n"); + rc = SQLITE_ERROR; + goto end_ar_command; + } - cmd.zSrcTable = sqlite3_mprintf("sqlar"); ++ cmd.zSrcTable = smprintf("sqlar"); + } - if( rc==SQLITE_OK ){ - rc = sqlite3_prepare_v2(db, zSql, -1, &pSql, 0); - } - if( rc==SQLITE_OK ){ - sqlite3_bind_int(pSql, 1, bGroupByParent); - } + switch( cmd.eCmd ){ + case AR_CMD_CREATE: + rc = arCreateOrUpdateCommand(&cmd, 0, 0); + break; - if( rc==SQLITE_OK ){ - int rc2; - char *zPrev = 0; - while( SQLITE_ROW==sqlite3_step(pSql) ){ - int res = -1; - sqlite3_stmt *pExplain = 0; - const char *zEQP = (const char*)sqlite3_column_text(pSql, 0); - const char *zGlob = (const char*)sqlite3_column_text(pSql, 1); - const char *zFrom = (const char*)sqlite3_column_text(pSql, 2); - const char *zTarget = (const char*)sqlite3_column_text(pSql, 3); - const char *zCI = (const char*)sqlite3_column_text(pSql, 4); - const char *zParent = (const char*)sqlite3_column_text(pSql, 5); + case AR_CMD_EXTRACT: + rc = arExtractCommand(&cmd); + break; - if( zEQP==0 ) continue; - if( zGlob==0 ) continue; - rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0); - if( rc!=SQLITE_OK ) break; - if( SQLITE_ROW==sqlite3_step(pExplain) ){ - const char *zPlan = (const char*)sqlite3_column_text(pExplain, 3); - res = zPlan!=0 && ( 0==sqlite3_strglob(zGlob, zPlan) - || 0==sqlite3_strglob(zGlobIPK, zPlan)); - } - rc = sqlite3_finalize(pExplain); - if( rc!=SQLITE_OK ) break; + case AR_CMD_LIST: + rc = arListCommand(&cmd); + break; - if( res<0 ){ - raw_printf(stderr, "Error: internal error"); + case AR_CMD_HELP: + arUsage(cmd.out, &cmd); break; - }else{ - if( bGroupByParent - && (bVerbose || res==0) - && (zPrev==0 || sqlite3_stricmp(zParent, zPrev)) - ){ - raw_printf(out, "-- Parent table %s\n", zParent); - sqlite3_free(zPrev); - zPrev = sqlite3_mprintf("%s", zParent); - } - if( res==0 ){ - raw_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget); - }else if( bVerbose ){ - raw_printf(out, "%s/* no extra indexes required for %s -> %s */\n", - zIndent, zFrom, zTarget - ); - } - } - } - sqlite3_free(zPrev); + case AR_CMD_INSERT: + rc = arCreateOrUpdateCommand(&cmd, 1, 0); + break; - if( rc!=SQLITE_OK ){ - raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); - } + case AR_CMD_REMOVE: + rc = arRemoveCommand(&cmd); + break; - rc2 = sqlite3_finalize(pSql); - if( rc==SQLITE_OK && rc2!=SQLITE_OK ){ - rc = rc2; - raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); + default: + assert( cmd.eCmd==AR_CMD_UPDATE ); + rc = arCreateOrUpdateCommand(&cmd, 1, 1); + break; } - }else{ - raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); } +end_ar_command: + if( cmd.db!=DBX(pState) ){ + close_db(cmd.db); + } + sqlite3_free(cmd.zSrcTable); - return rc; + return (rv!=DCR_Ok)? rv : (DCR_Ok|(rc!=0)); } +/* End of the ".archive" or ".ar" command logic +*******************************************************************************/ +#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) */ +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) /* -** Implementation of ".lint" dot command. +** If (*pRc) is not SQLITE_OK when this function is called, it is a no-op. +** Otherwise, the SQL statement or statements in zSql are executed using +** database connection db and the error code written to *pRc before +** this function returns. */ -static int lintDotCommand( - ShellState *pState, /* Current shell tool state */ - char **azArg, /* Array of arguments passed to dot command */ - int nArg /* Number of entries in azArg[] */ -){ - int n; - n = (nArg>=2 ? strlen30(azArg[1]) : 0); - if( n<1 || sqlite3_strnicmp(azArg[1], "fkey-indexes", n) ) goto usage; - return lintFkeyIndexes(pState, azArg, nArg); - - usage: - raw_printf(stderr, "Usage %s sub-command ?switches...?\n", azArg[0]); - raw_printf(stderr, "Where sub-commands are:\n"); - raw_printf(stderr, " fkey-indexes\n"); - return SQLITE_ERROR; -} - -#if !defined SQLITE_OMIT_VIRTUALTABLE -static void shellPrepare( - sqlite3 *db, - int *pRc, - const char *zSql, - sqlite3_stmt **ppStmt -){ - *ppStmt = 0; - if( *pRc==SQLITE_OK ){ - int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0); +static void shellExec(sqlite3 *db, int *pRc, const char *zSql){ + int rc = *pRc; + if( rc==SQLITE_OK ){ + char *zErr = 0; + rc = sqlite3_exec(db, zSql, 0, 0, &zErr); if( rc!=SQLITE_OK ){ - raw_printf(stderr, "sql error: %s (%d)\n", - sqlite3_errmsg(db), sqlite3_errcode(db) - ); - *pRc = rc; + raw_printf(STD_ERR, "SQL error: %s\n", zErr); } + sqlite3_free(zErr); + *pRc = rc; } -} - -/* -** Create a prepared statement using printf-style arguments for the SQL. -** -** This routine is could be marked "static". But it is not always used, -** depending on compile-time options. By omitting the "static", we avoid -** nuisance compiler warnings about "defined but not used". -*/ -void shellPreparePrintf( - sqlite3 *db, - int *pRc, - sqlite3_stmt **ppStmt, - const char *zFmt, - ... -){ - *ppStmt = 0; +} + +/* +** Like shellExec(), except that zFmt is a printf() style format string. +*/ +static void shellExecPrintf(sqlite3 *db, int *pRc, const char *zFmt, ...){ + char *z = 0; if( *pRc==SQLITE_OK ){ va_list ap; - char *z; va_start(ap, zFmt); z = sqlite3_vmprintf(zFmt, ap); va_end(ap); @@@ -7410,4142 -6651,2837 +7338,4168 @@@ static RecoverTable *recoverFindTable } /* -** This function assumes that all arguments within the ArCommand.azArg[] -** array refer to archive members, as for the --extract, --list or --remove -** commands. It checks that each of them are "present". If any specified -** file is not present in the archive, an error is printed to stderr and an -** error code returned. Otherwise, if all specified arguments are present -** in the archive, SQLITE_OK is returned. Here, "present" means either an -** exact equality when pAr->bGlob is false or a "name GLOB pattern" match -** when pAr->bGlob is true. -** -** This function strips any trailing '/' characters from each argument. -** This is consistent with the way the [tar] command seems to work on -** Linux. +** Return a RecoverTable object representing the orphans table. */ -static int arCheckEntries(ArCommand *pAr){ - int rc = SQLITE_OK; - if( pAr->nArg ){ - int i, j; +static RecoverTable *recoverOrphanTable( + sqlite3 *db, /* DB from which to recover */ + FILE *out, /* Where to put recovery DDL */ + int *pRc, /* IN/OUT: Error code */ + const char *zLostAndFound, /* Base name for orphans table */ + int nCol /* Number of user data columns */ +){ + RecoverTable *pTab = 0; + if( nCol>=0 && *pRc==SQLITE_OK ){ + int i; + + /* This block determines the name of the orphan table. The prefered + ** name is zLostAndFound. But if that clashes with another name + ** in the recovered schema, try zLostAndFound_0, zLostAndFound_1 + ** and so on until a non-clashing name is found. */ + int iTab = 0; + char *zTab = shellMPrintf(pRc, "%s", zLostAndFound); sqlite3_stmt *pTest = 0; - const char *zSel = (pAr->bGlob) - ? "SELECT name FROM %s WHERE glob($name,name)" - : "SELECT name FROM %s WHERE name=$name"; + shellPrepare(db, pRc, + "SELECT 1 FROM recovery.schema WHERE name=?", &pTest + ); + if( pTest ) sqlite3_bind_text(pTest, 1, zTab, -1, SQLITE_TRANSIENT); + while( *pRc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pTest) ){ + shellReset(pRc, pTest); + sqlite3_free(zTab); + zTab = shellMPrintf(pRc, "%s_%d", zLostAndFound, iTab++); + sqlite3_bind_text(pTest, 1, zTab, -1, SQLITE_TRANSIENT); + } + shellFinalize(pRc, pTest); - shellPreparePrintf(pAr->db, &rc, &pTest, zSel, pAr->zSrcTable); - j = sqlite3_bind_parameter_index(pTest, "$name"); - for(i=0; inArg && rc==SQLITE_OK; i++){ - char *z = pAr->azArg[i]; - int n = strlen30(z); - int bOk = 0; - while( n>0 && z[n-1]=='/' ) n--; - z[n] = '\0'; - sqlite3_bind_text(pTest, j, z, -1, SQLITE_STATIC); - if( SQLITE_ROW==sqlite3_step(pTest) ){ - bOk = 1; + pTab = (RecoverTable*)shellMalloc(pRc, sizeof(RecoverTable)); + if( pTab ){ + pTab->zQuoted = shellMPrintf(pRc, "\"%w\"", zTab); + pTab->nCol = nCol; + pTab->iPk = -2; + if( nCol>0 ){ + pTab->azlCol = (char**)shellMalloc(pRc, sizeof(char*) * (nCol+1)); + if( pTab->azlCol ){ + pTab->azlCol[nCol] = shellMPrintf(pRc, ""); + for(i=nCol-1; i>=0; i--){ + pTab->azlCol[i] = shellMPrintf(pRc, "%s, NULL", pTab->azlCol[i+1]); + } + } } - shellReset(&rc, pTest); - if( rc==SQLITE_OK && bOk==0 ){ - utf8_printf(stderr, "not found in archive: %s\n", z); - rc = SQLITE_ERROR; + + if( *pRc!=SQLITE_OK ){ + recoverFreeTable(pTab); + pTab = 0; + }else{ + raw_printf(out, + "CREATE TABLE %s(rootpgno INTEGER, " + "pgno INTEGER, nfield INTEGER, id INTEGER", pTab->zQuoted + ); + for(i=0; ibGlob is false and GLOB match when pAr->bGlob is true. -*/ -static void arWhereClause( - int *pRc, - ArCommand *pAr, - char **pzWhere /* OUT: New WHERE clause */ -){ - char *zWhere = 0; - const char *zSameOp = (pAr->bGlob)? "GLOB" : "="; - if( *pRc==SQLITE_OK ){ - if( pAr->nArg==0 ){ - zWhere = sqlite3_mprintf("1"); - }else{ - int i; - const char *zSep = ""; - for(i=0; inArg; i++){ - const char *z = pAr->azArg[i]; - zWhere = sqlite3_mprintf( - "%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'", - zWhere, zSep, zSameOp, z, strlen30(z)+1, zSameOp, z - ); - if( zWhere==0 ){ - *pRc = SQLITE_NOMEM; - break; - } - zSep = " OR "; +static DotCmdRC +writeDb( char *azArg[], int nArg, ShellExState *psx, char **pzErr ){ + int rc = 0; + const char *zDestFile = 0; + const char *zDb = 0; + sqlite3 *pDest; + sqlite3_backup *pBackup; + int j; + int bAsync = 0; + const char *zVfs = 0; + if( ISS(psx)->bSafeMode ) return DCR_AbortError; + for(j=1; j Maybe init db, add column zCol to it. + * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE, + * close db and set it to 0, and return the columns spec, to later + * be sqlite3_free()'ed by the caller. + * The return is 0 when either: + * (a) The db was not initialized and zCol==0 (There are no columns.) + * (b) zCol!=0 (Column was added, db initialized as needed.) + * The 3rd argument, pRenamed, references an out parameter. If the + * pointer is non-zero, its referent will be set to a summary of renames + * done if renaming was necessary, or set to 0 if none was done. The out + * string (if any) must be sqlite3_free()'ed by the caller. + */ +#ifdef SHELL_DEBUG +#define rc_err_oom_die(rc) \ + if( rc==SQLITE_NOMEM ) shell_check_oom(0); \ + else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \ + fprintf(STD_ERR,"E:%d\n",rc), assert(0) +#else +static void rc_err_oom_die(int rc){ + if( rc==SQLITE_NOMEM ) shell_check_oom(0); + assert(rc==SQLITE_OK||rc==SQLITE_DONE); +} +#endif - char *zWhere = 0; - sqlite3_stmt *pSql = 0; - int rc; +#ifdef SHELL_COLFIX_DB /* If this is set, the DB can be in a file. */ +static char zCOL_DB[] = SHELL_STRINGIFY(SHELL_COLFIX_DB); +#else /* Otherwise, memory is faster/better for the transient DB. */ +static const char *zCOL_DB = ":memory:"; +#endif - rc = arCheckEntries(pAr); - arWhereClause(&rc, pAr, &zWhere); +/* Define character (as C string) to separate generated column ordinal + * from protected part of incoming column names. This defaults to "_" + * so that incoming column identifiers that did not need not be quoted + * remain usable without being quoted. It must be one character. + */ +#ifndef SHELL_AUTOCOLUMN_SEP +# define AUTOCOLUMN_SEP "_" +#else +# define AUTOCOLUMN_SEP SHELL_STRINGIFY(SHELL_AUTOCOLUMN_SEP) +#endif - shellPreparePrintf(pAr->db, &rc, &pSql, zSql, azCols[pAr->bVerbose], - pAr->zSrcTable, zWhere); - if( pAr->bDryRun ){ - utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql)); +static char *zAutoColumn(const char *zColNew, sqlite3 **pDb, char **pzRenamed){ + /* Queries and D{D,M}L used here */ + static const char * const zTabMake = "\ +CREATE TABLE ColNames(\ + cpos INTEGER PRIMARY KEY,\ + name TEXT, nlen INT, chop INT, reps INT, suff TEXT);\ +CREATE VIEW RepeatedNames AS \ +SELECT DISTINCT t.name FROM ColNames t \ +WHERE t.name COLLATE NOCASE IN (\ + SELECT o.name FROM ColNames o WHERE o.cpos<>t.cpos\ +);\ +"; + static const char * const zTabFill = "\ +INSERT INTO ColNames(name,nlen,chop,reps,suff)\ + VALUES(iif(length(?1)>0,?1,'?'),max(length(?1),1),0,0,'')\ +"; + static const char * const zHasDupes = "\ +SELECT count(DISTINCT (substring(name,1,nlen-chop)||suff) COLLATE NOCASE)\ + 1, printf('%c%0*d', '"AUTOCOLUMN_SEP"', $1, cpos), '')" +#else /* ...RENAME_MINIMAL_ONE_PASS */ +"WITH Lzn(nlz) AS (" /* Find minimum extraneous leading 0's for uniqueness */ +" SELECT 0 AS nlz" +" UNION" +" SELECT nlz+1 AS nlz FROM Lzn" +" WHERE EXISTS(" +" SELECT 1" +" FROM ColNames t, ColNames o" +" WHERE" +" iif(t.name IN (SELECT * FROM RepeatedNames)," +" printf('%s"AUTOCOLUMN_SEP"%s'," +" t.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,t.cpos),2))," +" t.name" +" )" +" =" +" iif(o.name IN (SELECT * FROM RepeatedNames)," +" printf('%s"AUTOCOLUMN_SEP"%s'," +" o.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,o.cpos),2))," +" o.name" +" )" +" COLLATE NOCASE" +" AND o.cpos<>t.cpos" +" GROUP BY t.cpos" +" )" +") UPDATE Colnames AS t SET" +" chop = 0," /* No chopping, never touch incoming names. */ +" suff = iif(name IN (SELECT * FROM RepeatedNames)," +" printf('"AUTOCOLUMN_SEP"%s', substring(" +" printf('%.*c%0.*d',(SELECT max(nlz) FROM Lzn)+1,'0',1,t.cpos),2))," +" ''" +" )" +#endif + ; + static const char * const zCollectVar = "\ +SELECT\ + '('||x'0a'\ + || group_concat(\ + cname||' TEXT',\ + ','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\ + ||')' AS ColsSpec \ +FROM (\ + SELECT cpos, printf('\"%w\"',printf('%.*s%s', nlen-chop,name,suff)) AS cname \ + FROM ColNames ORDER BY cpos\ +)"; + static const char * const zRenamesDone = + "SELECT group_concat(" + " printf('\"%w\" to \"%w\"',name,printf('%.*s%s', nlen-chop, name, suff))," + " ','||x'0a')" + "FROM ColNames WHERE suff<>'' OR chop!=0" + ; + int rc; + sqlite3_stmt *pStmt = 0; + assert(pDb!=0); + if( zColNew ){ + /* Add initial or additional column. Init db if necessary. */ + if( *pDb==0 ){ + if( SQLITE_OK!=sqlite3_open(zCOL_DB, pDb) ) return 0; +#ifdef SHELL_COLFIX_DB + if(*zCOL_DB!=':') + sqlite3_exec(*pDb,"drop table if exists ColNames;" + "drop view if exists RepeatedNames;",0,0,0); +#endif + rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0); + rc_err_oom_die(rc); + } + assert(*pDb!=0); + rc = sqlite3_prepare_v2(*pDb, zTabFill, -1, &pStmt, 0); + rc_err_oom_die(rc); + rc = sqlite3_bind_text(pStmt, 1, zColNew, -1, 0); + rc_err_oom_die(rc); + rc = sqlite3_step(pStmt); + rc_err_oom_die(rc); + sqlite3_finalize(pStmt); + return 0; + }else if( *pDb==0 ){ + return 0; }else{ - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ - if( pAr->bVerbose ){ - utf8_printf(pAr->p->out, "%s % 10d %s %s\n", - sqlite3_column_text(pSql, 0), - sqlite3_column_int(pSql, 1), - sqlite3_column_text(pSql, 2), - sqlite3_column_text(pSql, 3) - ); - }else{ - utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0)); + /* Formulate the columns spec, close the DB, zero *pDb. */ + char *zColsSpec = 0; + int hasDupes = db_int(*pDb, zHasDupes); +#ifdef SQLITE_ENABLE_MATH_FUNCTIONS + int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0; +#else +# define nDigits 2 +#endif + if( hasDupes ){ +#ifdef SHELL_COLUMN_RENAME_CLEAN + rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0); + rc_err_oom_die(rc); +#endif + rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0); + rc_err_oom_die(rc); + rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0); + rc_err_oom_die(rc); + sqlite3_bind_int(pStmt, 1, nDigits); + rc = sqlite3_step(pStmt); + sqlite3_finalize(pStmt); + assert(rc==SQLITE_DONE); + } + /* This assert is maybe overly cautious for above de-dup DML, but that can + * be replaced via #define's. So this check is made for debug builds. */ + assert(db_int(*pDb, zHasDupes)==0); + rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0); + rc_err_oom_die(rc); + rc = sqlite3_step(pStmt); + if( rc==SQLITE_ROW ){ - zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); ++ zColsSpec = smprintf("%s", sqlite3_column_text(pStmt, 0)); + }else{ + zColsSpec = 0; + } + if( pzRenamed!=0 ){ + if( !hasDupes ) *pzRenamed = 0; + else{ + sqlite3_finalize(pStmt); + if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0) + && SQLITE_ROW==sqlite3_step(pStmt) ){ - *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); ++ *pzRenamed = smprintf("%s", sqlite3_column_text(pStmt, 0)); + }else + *pzRenamed = 0; } } + sqlite3_finalize(pStmt); + sqlite3_close(*pDb); + *pDb = 0; + return zColsSpec; } - shellFinalize(&rc, pSql); - sqlite3_free(zWhere); - return rc; } - -/* -** Implementation of .ar "Remove" command. -*/ -static int arRemoveCommand(ArCommand *pAr){ - int rc = 0; - char *zSql = 0; - char *zWhere = 0; - - if( pAr->nArg ){ - /* Verify that args actually exist within the archive before proceeding. - ** And formulate a WHERE clause to match them. */ - rc = arCheckEntries(pAr); - arWhereClause(&rc, pAr, &zWhere); +#if SHELL_DYNAMIC_EXTENSION +/* Register a meta-command */ +static int register_meta_command(ShellExState *p, + ExtensionId eid, MetaCommand *pMC){ + ShellInState *psi = ISS(p); + ShExtInfo *psei = 0; + const char *zSql + = "INSERT INTO ShellCommands (name, extIx, cmdIx) VALUES(?, ?, ?)"; + int nle = psi->numExtLoaded; + int ie; + if( pMC==0 ) return SQLITE_ERROR; + assert(psi->pShxLoaded!=0 && nle>0 && p->dbShell!=0); + for( ie=0; iepShxLoaded[ie].extId==eid ){ + psei = &psi->pShxLoaded[ie]; + break; + } } - if( rc==SQLITE_OK ){ - zSql = sqlite3_mprintf("DELETE FROM %s WHERE %s;", - pAr->zSrcTable, zWhere); - if( pAr->bDryRun ){ - utf8_printf(pAr->p->out, "%s\n", zSql); + if( psei==0 ){ + ShExtInfo sei = {eid,0}; + psi->pShxLoaded = sqlite3_realloc(psi->pShxLoaded, + (nle+1)*sizeof(ShExtInfo)); + shell_check_oom(psi->pShxLoaded); + psei = &psi->pShxLoaded[psi->numExtLoaded]; + psi->pShxLoaded[psi->numExtLoaded++] = sei; + ie = nle; + } + { + const char *zName = pMC->pMethods->name(pMC); + sqlite3_stmt *pStmt; + int rc = sqlite3_prepare_v2(p->dbShell, zSql, -1, &pStmt, 0); + int nc = psei->numMetaCommands; + if( rc!=SQLITE_OK ) return SQLITE_ERROR; + psei->ppMetaCommands + = sqlite3_realloc(psei->ppMetaCommands, (nc+1)*sizeof(MetaCommand *)); + shell_check_oom(psei->ppMetaCommands); + sqlite3_bind_text(pStmt, 1, zName, -1, 0); + sqlite3_bind_int(pStmt, 2, ie); + sqlite3_bind_int(pStmt, 3, nc); + rc = sqlite3_step(pStmt); + sqlite3_finalize(pStmt); + if( rc==SQLITE_DONE ){ + psei->ppMetaCommands[nc++] = pMC; + psei->numMetaCommands = nc; + return SQLITE_OK; }else{ - char *zErr = 0; - rc = sqlite3_exec(pAr->db, "SAVEPOINT ar;", 0, 0, 0); - if( rc==SQLITE_OK ){ - rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr); - if( rc!=SQLITE_OK ){ - sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0); - }else{ - rc = sqlite3_exec(pAr->db, "RELEASE ar;", 0, 0, 0); - } - } - if( zErr ){ - utf8_printf(stdout, "ERROR: %s\n", zErr); - sqlite3_free(zErr); - } + psei->ppMetaCommands[nc] = 0; } } - sqlite3_free(zWhere); - sqlite3_free(zSql); - return rc; + return SQLITE_ERROR; } -/* -** Implementation of .ar "eXtract" command. -*/ -static int arExtractCommand(ArCommand *pAr){ - const char *zSql1 = - "SELECT " - " ($dir || name)," - " writefile(($dir || name), %s, mode, mtime) " - "FROM %s WHERE (%s) AND (data IS NULL OR $dirOnly = 0)" - " AND name NOT GLOB '*..[/\\]*'"; +/* Register an output data display (or other disposition) mode */ +static int register_out_mode(ShellExState *p, + ExtensionId eid, OutModeHandler *pOMH){ + return SQLITE_ERROR; +} - const char *azExtraArg[] = { - "sqlar_uncompress(data, sz)", - "data" - }; +/* Register an import variation from (various sources) for .import */ +static int register_importer(ShellExState *p, + ExtensionId eid, ImportHandler *pIH){ + return SQLITE_ERROR; +} - sqlite3_stmt *pSql = 0; - int rc = SQLITE_OK; - char *zDir = 0; - char *zWhere = 0; - int i, j; +static FILE *currentOutputFile(ShellExState *p){ + return ISS(p)->out; +} - /* If arguments are specified, check that they actually exist within - ** the archive before proceeding. And formulate a WHERE clause to - ** match them. */ - rc = arCheckEntries(pAr); - arWhereClause(&rc, pAr, &zWhere); +static struct InSource *currentInputSource(ShellExState *p){ + return ISS(p)->pInSource; +} - if( rc==SQLITE_OK ){ - if( pAr->zDir ){ - zDir = sqlite3_mprintf("%s/", pAr->zDir); - }else{ - zDir = sqlite3_mprintf(""); - } - if( zDir==0 ) rc = SQLITE_NOMEM; +static int nowInteractive(ShellExState *p){ + return INSOURCE_IS_INTERACTIVE(ISS(p)->pInSource); +} + +static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths); ++static MetaCommand * findMetaCommand(const char *, ShellExState *, int *); + +static ExtensionHelpers extHelpers = { - 6, ++ 7, + { + failIfSafeMode, + currentOutputFile, + currentInputSource, + strLineGet, ++ findMetaCommand, + setColumnWidths, + nowInteractive, + 0 } +}; - shellPreparePrintf(pAr->db, &rc, &pSql, zSql1, - azExtraArg[pAr->bZip], pAr->zSrcTable, zWhere - ); +static ShellExtensionAPI shellExtAPI = { + &extHelpers, 3, { + register_meta_command, + register_out_mode, + register_importer, + 0 + } +}; - if( rc==SQLITE_OK ){ - j = sqlite3_bind_parameter_index(pSql, "$dir"); - sqlite3_bind_text(pSql, j, zDir, -1, SQLITE_STATIC); +/* This SQL function provides a way for a just-loaded shell extension to + * obtain a ShellExtensionLink pointer from the shell core while using + * the same sqlite3_load_extension API used for SQLite extensions. + * This serves as an alternative to deriving the same pointer from + * the pzErr argument passed into that API. + */ +static void shell_linkage( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int linkKind = 0; + if( argc>0 ){ + linkKind = sqlite3_value_int(argv[0]); + } + switch (linkKind){ + case 0: + sqlite3_result_pointer(context, sqlite3_user_data(context), + SHELLEXT_API_POINTERS, 0); + break; + default: + sqlite3_result_null(context); + } +} - /* Run the SELECT statement twice. The first time, writefile() is called - ** for all archive members that should be extracted. The second time, - ** only for the directories. This is because the timestamps for - ** extracted directories must be reset after they are populated (as - ** populating them changes the timestamp). */ - for(i=0; i<2; i++){ - j = sqlite3_bind_parameter_index(pSql, "$dirOnly"); - sqlite3_bind_int(pSql, j, i); - if( pAr->bDryRun ){ - utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql)); - }else{ - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ - if( i==0 && pAr->bVerbose ){ - utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0)); - } +#ifndef SHELL_DB_FILE +# define SHELL_DB_STORE ":memory:" +#else +# define SHELL_DB_STORE SHELL_STRINGIFY(SHELL_DB_FILE) +#endif + +/* Do the initialization needed for use of dbShell for command lookup + * and dispatch and for I/O handler lookup and dispatch. + */ +static int begin_db_dispatch(ShellExState *psx){ + ShellInState *psi = ISS(psx); + sqlite3_stmt *pStmt = 0; + int ic, rc, rc1, rc2; + char *zErr = 0; + const char *zSql; + ShExtInfo sei = {0}; + /* Consider: Store these dynamic arrays in the DB as indexed-into blobs. */ + assert(psx->dbShell==0 && psi->numExtLoaded==0 && psi->pShxLoaded==0); + psi->pShxLoaded = (ShExtInfo *)sqlite3_malloc(2*sizeof(ShExtInfo)); + sei.ppMetaCommands + = (MetaCommand **)sqlite3_malloc((numCommands+2)*sizeof(MetaCommand *)); + sei.ppOutModeHandlers + = (OutModeHandler **)sqlite3_malloc(2*sizeof(OutModeHandler *)); + sei.ppImportHandlers + = (ImportHandler **)sqlite3_malloc(2*sizeof(ImportHandler *)); + if( sei.ppMetaCommands==0||sei.ppOutModeHandlers==0||sei.ppImportHandlers==0 + || psi->pShxLoaded==0 ){ + shell_out_of_memory(); + } + sei.numOutModeHandlers = 0; + sei.numImportHandlers = 0; + for( ic=0; icpShxLoaded[psi->numExtLoaded++] = sei; + rc = sqlite3_open(SHELL_DB_STORE, &psx->dbShell); + if( rc!=SQLITE_OK ) return 1; +#ifdef SHELL_DB_FILE + sqlite3_exec(psx->dbShell, "DROP TABLE IF EXISTS ShellCommands", 0,0,0); + sqlite3_exec(psx->dbShell, "DROP VIEW IF EXISTS ActiveCommands", 0,0,0); +#endif + rc1 = sqlite3_exec(psx->dbShell, "CREATE TABLE ShellCommands(" + "name TEXT, extIx INT, cmdIx INT," + "PRIMARY KEY(extIx,cmdIx)) WITHOUT ROWID", + 0, 0, &zErr); + rc2 = sqlite3_exec(psx->dbShell, "CREATE VIEW ActiveCommands AS SELECT " + "s.name AS name, max(s.extIx) AS extIx, s.cmdIx AS cmdIx " + "FROM ShellCommands s GROUP BY name,extIx", + 0, 0, &zErr); + if( rc1!=SQLITE_OK || rc2!=SQLITE_OK || zErr!=0 ){ + utf8_printf(STD_ERR, "Shell DB init failure, %s\n", zErr? zErr : ""); + return 1; + } + zSql = "INSERT INTO ShellCommands (name, extIx, cmdIx) VALUES(?, 0, ?)"; + rc1 = sqlite3_prepare_v2(psx->dbShell, zSql, -1, &pStmt, 0); + rc2 = sqlite3_exec(psx->dbShell, "BEGIN TRANSACTION", 0, 0, &zErr); + if( rc1!=SQLITE_OK || rc2!=SQLITE_OK ) return 1; + assert(sei.numMetaCommands>0); + for( ic=0; icpMethods->name(pmc); + sqlite3_reset(pStmt); + sqlite3_bind_text(pStmt, 1, zName, -1, 0); + sqlite3_bind_int(pStmt, 2, ic); + rc = sqlite3_step(pStmt); + if( rc!=SQLITE_DONE ){ + sqlite3_exec(psx->dbShell, "ABORT", 0, 0, 0); + break; + } + } + sqlite3_finalize(pStmt); + if( rc!=SQLITE_DONE ) return 1; + rc = sqlite3_exec(psx->dbShell, "COMMIT", 0, 0, &zErr); + sqlite3_enable_load_extension(psx->dbShell, 1); + return SQLITE_OK; +} + +/* Call all existent loaded extension destructors, in reverse order + * of their objects' creation, then free the tracking dyna-arrays. + */ +static void free_shext_tracking(ShellInState *psi){ + int i, j; + if( psi->pShxLoaded!=0 ){ + for( i=psi->numExtLoaded; i>0; --i ){ + ShExtInfo *psei = &psi->pShxLoaded[i-1]; + if( psei->ppMetaCommands!=0 ){ + for( j=psei->numMetaCommands; j>0; --j ){ + MetaCommand *pmc = psei->ppMetaCommands[j-1]; + if( pmc->pMethods->destruct!=0 ) pmc->pMethods->destruct(pmc); } + sqlite3_free(psei->ppMetaCommands); + } + if( psei->ppOutModeHandlers!=0 ){ + for( j=psei->numOutModeHandlers; j>0; --j ){ + OutModeHandler *pomh = psei->ppOutModeHandlers[j-1]; + if( pomh->pMethods->destruct!=0 ) pomh->pMethods->destruct(pomh); + } + sqlite3_free(psei->ppOutModeHandlers); + } + if( psei->ppImportHandlers!=0 ){ + for( j=psei->numImportHandlers; j>0; --j ){ + ImportHandler *pih = psei->ppImportHandlers[j-1]; + if( pih->pMethods->destruct!=0 ) pih->pMethods->destruct(pih); + } + sqlite3_free(psei->ppImportHandlers); } - shellReset(&rc, pSql); } - shellFinalize(&rc, pSql); + sqlite3_free(psi->pShxLoaded); + psi->pShxLoaded = 0; + psi->numExtLoaded = 0; } - - sqlite3_free(zDir); - sqlite3_free(zWhere); - return rc; } -/* -** Run the SQL statement in zSql. Or if doing a --dryrun, merely print it out. -*/ -static int arExecSql(ArCommand *pAr, const char *zSql){ - int rc; - if( pAr->bDryRun ){ - utf8_printf(pAr->p->out, "%s\n", zSql); - rc = SQLITE_OK; - }else{ - char *zErr = 0; - rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr); - if( zErr ){ - utf8_printf(stdout, "ERROR: %s\n", zErr); - sqlite3_free(zErr); +static MetaCommand *command_by_index(ShellInState *psi, int extIx, int cmdIx){ + if( extIx>=0 && extIxnumExtLoaded ){ + ShExtInfo *psei = & psi->pShxLoaded[extIx]; + if( cmdIx>=0 && cmdIxnumMetaCommands ){ + return psei->ppMetaCommands[cmdIx]; } } - return rc; + return 0; } - -/* -** Implementation of .ar "create", "insert", and "update" commands. -** -** create -> Create a new SQL archive -** insert -> Insert or reinsert all files listed -** update -> Insert files that have changed or that were not -** previously in the archive -** -** Create the "sqlar" table in the database if it does not already exist. -** Then add each file in the azFile[] array to the archive. Directories -** are added recursively. If argument bVerbose is non-zero, a message is -** printed on stdout for each file archived. -** -** The create command is the same as update, except that it drops -** any existing "sqlar" table before beginning. The "insert" command -** always overwrites every file named on the command-line, where as -** "update" only overwrites if the size or mtime or mode has changed. -*/ -static int arCreateOrUpdateCommand( - ArCommand *pAr, /* Command arguments and options */ - int bUpdate, /* true for a --create. */ - int bOnlyIfChanged /* Only update if file has changed */ -){ - const char *zCreate = - "CREATE TABLE IF NOT EXISTS sqlar(\n" - " name TEXT PRIMARY KEY, -- name of the file\n" - " mode INT, -- access permissions\n" - " mtime INT, -- last modification time\n" - " sz INT, -- original file size\n" - " data BLOB -- compressed content\n" - ")"; - const char *zDrop = "DROP TABLE IF EXISTS sqlar"; - const char *zInsertFmt[2] = { - "REPLACE INTO %s(name,mode,mtime,sz,data)\n" - " SELECT\n" - " %s,\n" - " mode,\n" - " mtime,\n" - " CASE substr(lsmode(mode),1,1)\n" - " WHEN '-' THEN length(data)\n" - " WHEN 'd' THEN 0\n" - " ELSE -1 END,\n" - " sqlar_compress(data)\n" - " FROM fsdir(%Q,%Q) AS disk\n" - " WHERE lsmode(mode) NOT LIKE '?%%'%s;" - , - "REPLACE INTO %s(name,mode,mtime,data)\n" - " SELECT\n" - " %s,\n" - " mode,\n" - " mtime,\n" - " data\n" - " FROM fsdir(%Q,%Q) AS disk\n" - " WHERE lsmode(mode) NOT LIKE '?%%'%s;" +static int load_shell_extension(ShellExState *psx, const char *zFile, + const char *zProc, char **pzErr){ + ShellExtensionLink shxLink = { + sizeof(ShellExtensionLink), + &shellExtAPI, + psx, /* pSXS */ + 0, /* zErrMsg */ + 0, /* ExtensionId */ + 0 /* Extension destructor */ }; - int i; /* For iterating through azFile[] */ - int rc; /* Return code */ - const char *zTab = 0; /* SQL table into which to insert */ - char *zSql; - char zTemp[50]; - char *zExists = 0; + int rc; + if( psx->dbShell==0 ){ + rc = begin_db_dispatch(psx); + if( rc!=SQLITE_OK ) return rc; + assert(ISS(psx)->numExtLoaded==1 && psx->dbShell!=0); + } + sqlite3_create_function(psx->dbShell, "shext_pointer", 1, + SQLITE_DIRECTONLY|SQLITE_UTF8, + &shxLink, shell_linkage, 0, 0); + rc = sqlite3_load_extension(psx->dbShell, zFile, zProc, &shxLink.zErrMsg); + sqlite3_create_function(psx->dbShell, "shext_pointer", 1, + SQLITE_DIRECTONLY|SQLITE_UTF8, + 0, 0, 0, 0); /* deregister */ + if( pzErr!=0 ) *pzErr = shxLink.zErrMsg; + if( rc==SQLITE_OK ){ + /* Keep extension's id and destructor for later disposal. */ + } + return rc!=SQLITE_OK; +} +#endif + +/* Meta-command implementation functions are defined in this section. +COMMENT Define meta-commands and provide for their dispatch and .help text. +COMMENT These should be kept in command name order for coding convenience +COMMENT except where meta-commands share implementation. (The ordering +COMMENT required for dispatch and help text is effected regardless.) The +COMMENT effect of this configuration can be seen in generated output or by +COMMENT executing tool/mkshellc.tcl --parameters (or --details or --help). +COMMENT Generally, this section defines dispatchable functions inline and +COMMENT causes collection of command_table entry initializers, to be later +COMMENT emitted by a mkshellc macro. (See EMIT_METACMD_INIT further on.) +** All dispatchable meta-command execute functions have this signature: +static int someCommand(char *azArg[], int nArg, ShellExState *p, char **pzErr); +*/ +DISPATCH_CONFIG[ + RETURN_TYPE=DotCmdRC + STORAGE_CLASS=static + ARGS_SIGNATURE=char *$arg4\[\], int $arg5, ShellExState *$arg6, char **$arg7 + DISPATCH_ENTRY={ "$cmd", ${cmd}Command, $arg1, $arg2, $arg3 }, + METACMD_INIT=META_CMD_INFO(${cmd}, $arg1,$arg2,$arg3, , ), + CMD_CAPTURE_RE=^\s*{\s*"(\w+)" + DISPATCHEE_NAME=${cmd}Command + DC_ARG1_DEFAULT=[string length $cmd] + DC_ARG2_DEFAULT=0 + DC_ARG3_DEFAULT=0 + DC_ARG4_DEFAULT=azArg + DC_ARG5_DEFAULT=nArg + DC_ARG6_DEFAULT=p + DC_ARG7_DEFAULT=pzErr + DC_ARG_COUNT=8 +]; + +CONDITION_COMMAND(seeargs !defined(SHELL_OMIT_SEEARGS)); +/***************** + * The .seeargs command + */ +COLLECT_HELP_TEXT[ + ",seeargs Echo arguments suffixed with |", +]; +DISPATCHABLE_COMMAND( seeargs ? 0 0 azArg nArg p ){ + int ia = 0; + for (ia=1; iaout, "%s%s", azArg[ia], (ia==nArg-1)? "|\n" : "|"); + return DCR_Ok; +} + +CONDITION_COMMAND(archive !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)); +/***************** + * The .archive command + */ +COLLECT_HELP_TEXT[ + ".archive ... Manage SQL archives", + " Each command must have exactly one of the following options:", + " -c, --create Create a new archive", + " -u, --update Add or update files with changed mtime", + " -i, --insert Like -u but always add even if unchanged", + " -r, --remove Remove files from archive", + " -t, --list List contents of archive", + " -x, --extract Extract files from archive", + " Optional arguments:", + " -v, --verbose Print each filename as it is processed", + " -f FILE, --file FILE Use archive FILE (default is current db)", + " -a FILE, --append FILE Open FILE using the apndvfs VFS", + " -C DIR, --directory DIR Read/extract files from directory DIR", + " -g, --glob Use glob matching for names in archive", + " -n, --dryrun Show the SQL that would have occurred", + " Examples:", + " .ar -cf ARCHIVE foo bar # Create ARCHIVE from files foo and bar", + " .ar -tf ARCHIVE # List members of ARCHIVE", + " .ar -xvf ARCHIVE # Verbosely extract files from ARCHIVE", + " See also:", + " http://sqlite.org/cli.html#sqlite_archive_support", +]; +DISPATCHABLE_COMMAND( archive ? 0 0 azArg nArg p ){ + open_db(p, 0); + if( ISS(p)->bSafeMode ) return DCR_AbortError; + return arDotCommand(p, 0, azArg, nArg); +} - arExecSql(pAr, "PRAGMA page_size=512"); - rc = arExecSql(pAr, "SAVEPOINT ar;"); - if( rc!=SQLITE_OK ) return rc; - zTemp[0] = 0; - if( pAr->bZip ){ - /* Initialize the zipfile virtual table, if necessary */ - if( pAr->zFile ){ - sqlite3_uint64 r; - sqlite3_randomness(sizeof(r),&r); - sqlite3_snprintf(sizeof(zTemp),zTemp,"zip%016llx",r); - zTab = zTemp; - zSql = sqlite3_mprintf( - "CREATE VIRTUAL TABLE temp.%s USING zipfile(%Q)", - zTab, pAr->zFile - ); - rc = arExecSql(pAr, zSql); - sqlite3_free(zSql); - }else{ - zTab = "zip"; - } +/***************** + * The .auth command + */ +CONDITION_COMMAND(auth !defined(SQLITE_OMIT_AUTHORIZATION)); +COLLECT_HELP_TEXT[ + ".auth ON|OFF Show authorizer callbacks", +]; +DISPATCHABLE_COMMAND( auth 3 2 2 azArg nArg p ){ + open_db(p, 0); + if( booleanValue(azArg[1]) ){ + sqlite3_set_authorizer(DBX(p), shellAuth, p); + }else if( ISS(p)->bSafeModeFuture ){ + sqlite3_set_authorizer(DBX(p), safeModeAuth, p); }else{ - /* Initialize the table for an SQLAR */ - zTab = "sqlar"; - if( bUpdate==0 ){ - rc = arExecSql(pAr, zDrop); - if( rc!=SQLITE_OK ) goto end_ar_transaction; - } - rc = arExecSql(pAr, zCreate); + sqlite3_set_authorizer(DBX(p), 0, 0); } - if( bOnlyIfChanged ){ - zExists = sqlite3_mprintf( - " AND NOT EXISTS(" - "SELECT 1 FROM %s AS mem" - " WHERE mem.name=disk.name" - " AND mem.mtime=disk.mtime" - " AND mem.mode=disk.mode)", zTab); + return DCR_Ok; +} + +/***************** + * The .backup and .save commands (aliases for each other) + * These defer to writeDb in the dispatch table, so are not here. + */ +COLLECT_HELP_TEXT[ + ".backup ?DB? FILE Backup DB (default \"main\") to FILE", + " Options:", + " --append Use the appendvfs", + " --async Write the FILE without journal and fsync()", + ".save ?DB? FILE Write DB (default \"main\") to FILE", + " Options:", + " --append Use the appendvfs", + " --async Write the FILE without journal and fsync()", +]; +DISPATCHABLE_COMMAND( backup 4 2 5 ){ + return writeDb( azArg, nArg, p, pzErr); +} +DISPATCHABLE_COMMAND( save 3 2 5 ){ + return writeDb( azArg, nArg, p, pzErr); +} + +/***************** + * The .bail command + */ +COLLECT_HELP_TEXT[ + ".bail on|off Stop after hitting an error. Default OFF", +]; +DISPATCHABLE_COMMAND( bail 3 2 2 ){ + bail_on_error = booleanValue(azArg[1]); + return DCR_Ok; +} + +/***************** + * The .binary and .cd commands + */ +COLLECT_HELP_TEXT[ + ".binary on|off Turn binary output on or off. Default OFF", + ".cd DIRECTORY Change the working directory to DIRECTORY", +]; +DISPATCHABLE_COMMAND( binary 3 2 2 ){ + if( booleanValue(azArg[1]) ){ + setBinaryMode(ISS(p)->out, 1); }else{ - zExists = sqlite3_mprintf(""); + setTextMode(ISS(p)->out, 1); } - if( zExists==0 ) rc = SQLITE_NOMEM; - for(i=0; inArg && rc==SQLITE_OK; i++){ - char *zSql2 = sqlite3_mprintf(zInsertFmt[pAr->bZip], zTab, - pAr->bVerbose ? "shell_putsnl(name)" : "name", - pAr->azArg[i], pAr->zDir, zExists); - rc = arExecSql(pAr, zSql2); - sqlite3_free(zSql2); + return DCR_Ok; +} + +DISPATCHABLE_COMMAND( cd ? 2 2 ){ + int rc=0; + if( ISS(p)->bSafeMode ) return DCR_AbortError; +#if defined(_WIN32) || defined(WIN32) + wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]); + rc = !SetCurrentDirectoryW(z); + sqlite3_free(z); +#else + rc = chdir(azArg[1]); +#endif + if( rc ){ + utf8_printf(STD_ERR, "Cannot change to directory \"%s\"\n", azArg[1]); + rc = 1; } -end_ar_transaction: - if( rc!=SQLITE_OK ){ - sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0); + return DCR_Ok|rc; +} + +/* The undocumented ".breakpoint" command causes a call +** to the no-op routine named test_breakpoint(). +*/ +DISPATCHABLE_COMMAND( breakpoint 3 1 1 ){ + test_breakpoint(); + return DCR_Ok; +} + +/***************** + * The .changes, .check, .clone and .connection commands + */ +COLLECT_HELP_TEXT[ + ".changes on|off Show number of rows changed by SQL", + ",check GLOB Fail if output since .testcase does not match", + ".clone NEWDB Clone data into NEWDB from the existing database", + ".connection [close] [#] Open or close an auxiliary database connection", +]; +DISPATCHABLE_COMMAND( changes 3 2 2 ){ + setOrClearFlag(p, SHFLG_CountChanges, azArg[1]); + return DCR_Ok; +} +DISPATCHABLE_COMMAND( check 3 0 0 ){ + /* Cancel output redirection, if it is currently set (by .testcase) + ** Then read the content of the testcase-out.txt file and compare against + ** azArg[1]. If there are differences, report an error and exit. + */ + char *zRes = 0; + int rc = 0; + DotCmdRC rv = DCR_Ok; + output_reset(ISS(p)); + if( nArg!=2 ){ - return DCR_ArgError; ++ return DCR_ArgWrong; + }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){ + *pzErr = shellMPrintf(&rc, "Error: cannot read 'testcase-out.txt'"); + rv = DCR_Return; + }else if( testcase_glob(azArg[1],zRes)==0 ){ + *pzErr = + shellMPrintf(&rc, + "testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n", + ISS(p)->zTestcase, azArg[1], zRes); + rv = DCR_Error; }else{ - rc = arExecSql(pAr, "RELEASE ar;"); - if( pAr->bZip && pAr->zFile ){ - zSql = sqlite3_mprintf("DROP TABLE %s", zTemp); - arExecSql(pAr, zSql); - sqlite3_free(zSql); + utf8_printf(STD_OUT, "testcase-%s ok\n", ISS(p)->zTestcase); + ISS(p)->nCheck++; + } + sqlite3_free(zRes); + return (rc==SQLITE_NOMEM)? DCR_Abort : rv; +} +DISPATCHABLE_COMMAND( clone ? 2 2 ){ + if( ISS(p)->bSafeMode ) return DCR_AbortError; + tryToClone(p, azArg[1]); + return DCR_Ok; +} +DISPATCHABLE_COMMAND( connection ? 1 4 ){ + ShellInState *psi = ISS(p); + if( nArg==1 ){ + /* List available connections */ + int i; + for(i=0; iaAuxDb); i++){ + const char *zFile = psi->aAuxDb[i].zDbFilename; + if( psi->aAuxDb[i].db==0 && psi->pAuxDb!=&psi->aAuxDb[i] ){ + zFile = "(not open)"; + }else if( zFile==0 ){ + zFile = "(memory)"; + }else if( zFile[0]==0 ){ + zFile = "(temporary-file)"; + } + if( psi->pAuxDb == &psi->aAuxDb[i] ){ + utf8_printf(STD_OUT, "ACTIVE %d: %s\n", i, zFile); + }else if( psi->aAuxDb[i].db!=0 ){ + utf8_printf(STD_OUT, " %d: %s\n", i, zFile); + } + } + }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){ + int i = azArg[1][0] - '0'; + if( psi->pAuxDb != &psi->aAuxDb[i] && i>=0 && iaAuxDb) ){ + psi->pAuxDb->db = DBI(psi); + psi->pAuxDb = &psi->aAuxDb[i]; + globalDb = DBI(psi) = psi->pAuxDb->db; + psi->pAuxDb->db = 0; + } + }else if( nArg==3 && strcmp(azArg[1], "close")==0 + && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){ + int i = azArg[2][0] - '0'; + if( i<0 || i>=ArraySize(psi->aAuxDb) ){ + /* No-op */ + }else if( psi->pAuxDb == &psi->aAuxDb[i] ){ + raw_printf(STD_ERR, "cannot close the active database connection\n"); + return DCR_Error; + }else if( psi->aAuxDb[i].db ){ + session_close_all(psi, i); + close_db(psi->aAuxDb[i].db); + psi->aAuxDb[i].db = 0; } + }else{ - return DCR_ArgError; ++ return DCR_ArgWrong; } - sqlite3_free(zExists); - return rc; + return DCR_Ok; } -/* -** Implementation of ".ar" dot command. -*/ -static int arDotCommand( - ShellState *pState, /* Current shell tool state */ - int fromCmdLine, /* True if -A command-line option, not .ar cmd */ - char **azArg, /* Array of arguments passed to dot command */ - int nArg /* Number of entries in azArg[] */ -){ - ArCommand cmd; +/***************** + * The .databases, .dbconfig and .dbinfo commands + */ +COLLECT_HELP_TEXT[ + ".databases List names and files of attached databases", + ".dbconfig ?op? ?val? List or change sqlite3_db_config() options", + ".dbinfo ?DB? Show status information about the database", +]; +/* Allow garbage arguments on this, to be ignored. */ +DISPATCHABLE_COMMAND( databases 2 1 0 ){ int rc; - memset(&cmd, 0, sizeof(cmd)); - cmd.fromCmdLine = fromCmdLine; - rc = arParseCommand(azArg, nArg, &cmd); - if( rc==SQLITE_OK ){ - int eDbType = SHELL_OPEN_UNSPEC; - cmd.p = pState; - cmd.db = pState->db; - if( cmd.zFile ){ - eDbType = deduceDatabaseType(cmd.zFile, 1); - }else{ - eDbType = pState->openMode; + char **azName = 0; + int nName = 0; + sqlite3_stmt *pStmt; + sqlite3 *db; + int i; + open_db(p, 0); + db = DBX(p); + rc = sqlite3_prepare_v2(db, "PRAGMA database_list", -1, &pStmt, 0); + if( rc ){ - *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(db)); ++ *pzErr = smprintf("%s\n", sqlite3_errmsg(db)); + rc = 1; + }else{ + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + const char *zSchema = (const char *)sqlite3_column_text(pStmt,1); + const char *zFile = (const char*)sqlite3_column_text(pStmt,2); + if( zSchema==0 || zFile==0 ) continue; + azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*)); + shell_check_oom(azName); + azName[nName*2] = strdup(zSchema); + shell_check_oom(azName[nName*2]); + azName[nName*2+1] = strdup(zFile); + shell_check_oom(azName[nName*2+1]); + nName++; } - if( eDbType==SHELL_OPEN_ZIPFILE ){ - if( cmd.eCmd==AR_CMD_EXTRACT || cmd.eCmd==AR_CMD_LIST ){ - if( cmd.zFile==0 ){ - cmd.zSrcTable = sqlite3_mprintf("zip"); + } + sqlite3_finalize(pStmt); + for(i=0; iout, "%s: %s %s%s\n", + azName[i*2], + z && z[0] ? z : "\"\"", + bRdonly ? "r/o" : "r/w", + eTxn==SQLITE_TXN_NONE ? "" : + eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn"); + free(azName[i*2]); + free(azName[i*2+1]); + } + sqlite3_free(azName); + return DCR_Ok|(rc!=0); +} +DISPATCHABLE_COMMAND( dbconfig 3 1 3 ){ + static const struct DbConfigChoices { + const char *zName; + int op; + } aDbConfig[] = { + { "defensive", SQLITE_DBCONFIG_DEFENSIVE }, + { "dqs_ddl", SQLITE_DBCONFIG_DQS_DDL }, + { "dqs_dml", SQLITE_DBCONFIG_DQS_DML }, + { "enable_fkey", SQLITE_DBCONFIG_ENABLE_FKEY }, + { "enable_qpsg", SQLITE_DBCONFIG_ENABLE_QPSG }, + { "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER }, + { "enable_view", SQLITE_DBCONFIG_ENABLE_VIEW }, + { "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER }, + { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE }, + { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT }, + { "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION }, + { "no_ckpt_on_close", SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE }, + { "reset_database", SQLITE_DBCONFIG_RESET_DATABASE }, + { "trigger_eqp", SQLITE_DBCONFIG_TRIGGER_EQP }, + { "trusted_schema", SQLITE_DBCONFIG_TRUSTED_SCHEMA }, + { "writable_schema", SQLITE_DBCONFIG_WRITABLE_SCHEMA }, + }; + int ii, v; + open_db(p, 0); + for(ii=0; ii1 && strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue; + if( nArg>=3 ){ + sqlite3_db_config(DBX(p), aDbConfig[ii].op, booleanValue(azArg[2]), 0); + } + sqlite3_db_config(DBX(p), aDbConfig[ii].op, -1, &v); + utf8_printf(ISS(p)->out, "%19s %s\n", + aDbConfig[ii].zName, v ? "on" : "off"); + if( nArg>1 ) break; + } + if( nArg>1 && ii==ArraySize(aDbConfig) ){ - *pzErr = sqlite3_mprintf - ("Error: unknown dbconfig \"%s\"\n" - "Enter \".dbconfig\" with no arguments for a list\n", - azArg[1]); - return DCR_ArgError; ++ *pzErr = smprintf("Error: unknown dbconfig \"%s\"\n" ++ "Enter \".dbconfig\" with no arguments for a list\n", ++ azArg[1]); ++ return DCR_ArgWrong; + } + return DCR_Ok; +} +DISPATCHABLE_COMMAND( dbinfo 3 1 2 ){ + return shell_dbinfo_command(p, nArg, azArg); +} + +/***************** + * The .dump, .echo and .eqp commands + */ +COLLECT_HELP_TEXT[ + ".dump ?OBJECTS? Render database content as SQL", + " Options:", + " --data-only Output only INSERT statements", + " --newlines Allow unescaped newline characters in output", + " --nosys Omit system tables (ex: \"sqlite_stat1\")", + " --preserve-rowids Include ROWID values in the output", + " --schema SCHEMA Dump table(s) from given SCHEMA", + " OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump", + " Additional LIKE patterns can be given in subsequent arguments", + ".echo on|off Turn command echo on or off", + ".eqp on|off|full|... Enable or disable automatic EXPLAIN QUERY PLAN", + " Other Modes:", +#ifdef SQLITE_DEBUG + " test Show raw EXPLAIN QUERY PLAN output", + " trace Like \"full\" but enable \"PRAGMA vdbe_trace\"", +#endif + " trigger Like \"full\" but also show trigger bytecode", +]; +DISPATCHABLE_COMMAND( dump ? 1 2 ){ + ShellInState *psi = ISS(p); + char *zLike = 0; + char *zSchema = "main"; + char *zSql; + int i; + int savedShowHeader = psi->showHeader; + int savedShellFlags = psi->shellFlgs; + ShellClearFlag(p, + SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo + |SHFLG_DumpDataOnly|SHFLG_DumpNoSys); + for(i=1; iout, "-- open database '%s'%s\n", cmd.zFile, - eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : ""); - } - rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, - eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0); - if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "cannot open file: %s (%s)\n", - cmd.zFile, sqlite3_errmsg(cmd.db) - ); - goto end_ar_command; - } - sqlite3_fileio_init(cmd.db, 0, 0); - sqlite3_sqlar_init(cmd.db, 0, 0); - sqlite3_create_function(cmd.db, "shell_putsnl", 1, SQLITE_UTF8, cmd.p, - shellPutsFunc, 0, 0); - - } - if( cmd.zSrcTable==0 && cmd.bZip==0 && cmd.eCmd!=AR_CMD_HELP ){ - if( cmd.eCmd!=AR_CMD_CREATE - && sqlite3_table_column_metadata(cmd.db,0,"sqlar","name",0,0,0,0,0) - ){ - utf8_printf(stderr, "database does not contain an 'sqlar' table\n"); - rc = SQLITE_ERROR; - goto end_ar_command; + zLike = zExpr; } - cmd.zSrcTable = sqlite3_mprintf("sqlar"); } + } - switch( cmd.eCmd ){ - case AR_CMD_CREATE: - rc = arCreateOrUpdateCommand(&cmd, 0, 0); - break; + open_db(p, 0); - case AR_CMD_EXTRACT: - rc = arExtractCommand(&cmd); - break; + if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){ + /* When playing back a "dump", the content might appear in an order + ** which causes immediate foreign key constraints to be violated. + ** So disable foreign-key constraint enforcement to prevent problems. */ + raw_printf(psi->out, "PRAGMA foreign_keys=OFF;\n"); + raw_printf(psi->out, "BEGIN TRANSACTION;\n"); + } + psi->writableSchema = 0; + psi->showHeader = 0; + /* Set writable_schema=ON since doing so forces SQLite to initialize + ** as much of the schema as it can even if the sqlite_schema table is + ** corrupt. */ + sqlite3_exec(DBX(p), "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0); + psi->nErr = 0; - if( zLike==0 ) zLike = sqlite3_mprintf("true"); - zSql = sqlite3_mprintf( - "SELECT name, type, sql FROM %w.sqlite_schema AS o " - "WHERE (%s) AND type=='table'" - " AND sql NOT NULL" - " ORDER BY tbl_name='sqlite_sequence', rowid", - zSchema, zLike - ); ++ if( zLike==0 ) zLike = smprintf("true"); ++ zSql = smprintf("SELECT name, type, sql FROM %w.sqlite_schema AS o " ++ "WHERE (%s) AND type=='table' AND sql NOT NULL" ++ " ORDER BY tbl_name='sqlite_sequence', rowid", ++ zSchema, zLike); + run_schema_dump_query(psi,zSql); + sqlite3_free(zSql); + if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){ - zSql = sqlite3_mprintf( ++ zSql = smprintf( + "SELECT sql FROM sqlite_schema AS o " + "WHERE (%s) AND sql NOT NULL" + " AND type IN ('index','trigger','view')", + zLike + ); + run_table_dump_query(psi, zSql); + sqlite3_free(zSql); + } + sqlite3_free(zLike); + if( psi->writableSchema ){ + raw_printf(psi->out, "PRAGMA writable_schema=OFF;\n"); + psi->writableSchema = 0; + } + sqlite3_exec(DBX(p), "PRAGMA writable_schema=OFF;", 0, 0, 0); + sqlite3_exec(DBX(p), "RELEASE dump;", 0, 0, 0); + if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){ + raw_printf(psi->out, psi->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n"); + } + psi->showHeader = savedShowHeader; + psi->shellFlgs = savedShellFlags; + + return DCR_Ok; +} +DISPATCHABLE_COMMAND( echo ? 2 2 ){ + setOrClearFlag(p, SHFLG_Echo, azArg[1]); + return DCR_Ok; +} +DISPATCHABLE_COMMAND( eqp ? 0 0 ){ + ShellInState *psi = ISS(p); + if( nArg==2 ){ + psi->autoEQPtest = 0; + if( psi->autoEQPtrace ){ + if( DBX(p) ) sqlite3_exec(DBX(p), "PRAGMA vdbe_trace=OFF;", 0, 0, 0); + psi->autoEQPtrace = 0; + } + if( strcmp(azArg[1],"full")==0 ){ + psi->autoEQP = AUTOEQP_full; + }else if( strcmp(azArg[1],"trigger")==0 ){ + psi->autoEQP = AUTOEQP_trigger; +#ifdef SQLITE_DEBUG + }else if( strcmp(azArg[1],"test")==0 ){ + psi->autoEQP = AUTOEQP_on; + psi->autoEQPtest = 1; + }else if( strcmp(azArg[1],"trace")==0 ){ + psi->autoEQP = AUTOEQP_full; + psi->autoEQPtrace = 1; + open_db(p, 0); + sqlite3_exec(DBX(p), "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0); + sqlite3_exec(DBX(p), "PRAGMA vdbe_trace=ON;", 0, 0, 0); +#endif + }else{ + psi->autoEQP = (u8)booleanValue(azArg[1]); + } + }else{ - return DCR_ArgError; ++ return DCR_ArgWrong; + } + return DCR_Ok; +} - case AR_CMD_LIST: - rc = arListCommand(&cmd); - break; +/***************** + * The .exit and .quit commands + * These are together so that their subtle differences are apparent. + */ +COLLECT_HELP_TEXT[ + ".exit ?CODE? Exit this program with return-code CODE", + ".quit Exit this program or script", +]; +DISPATCHABLE_COMMAND( exit 3 1 0 ){ + int rc; + if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) + p->shellAbruptExit = rc; + return DCR_Return; +} +DISPATCHABLE_COMMAND( quit 1 1 0 ){ + return DCR_Return; +} - case AR_CMD_HELP: - arUsage(pState->out); - break; +/***************** + * The .expert and .explain commands + */ +CONDITION_COMMAND( expert !defined(SQLITE_OMIT_VIRTUALTABLE) ); +COLLECT_HELP_TEXT[ + ".expert Suggest indexes for queries", + ".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto", +]; +DISPATCHABLE_COMMAND( expert ? 1 1 ){ ++ ShellInState *psi = ISS(p); ++ int rv = DCR_Ok; ++ char *zErr = 0; ++ int i; ++ int iSample = 0; + - case AR_CMD_INSERT: - rc = arCreateOrUpdateCommand(&cmd, 1, 0); - break; ++ if( psi->bSafeMode ) return DCR_AbortError; ++ assert( psi->expert.pExpert==0 ); ++ memset(&psi->expert, 0, sizeof(ExpertInfo)); + - case AR_CMD_REMOVE: - rc = arRemoveCommand(&cmd); - break; + open_db(p, 0); - expertDotCommand(ISS(p), azArg, nArg); + - default: - assert( cmd.eCmd==AR_CMD_UPDATE ); - rc = arCreateOrUpdateCommand(&cmd, 1, 1); - break; ++ for(i=1; i=2 && 0==strncmp(z, "-verbose", n) ){ ++ psi->expert.bVerbose = 1; ++ } ++ else if( n>=2 && 0==strncmp(z, "-sample", n) ){ ++ if( i==(nArg-1) ){ ++ return DCR_Unpaired|i; ++ }else{ ++ iSample = (int)integerValue(azArg[++i]); ++ if( iSample<0 || iSample>100 ){ ++ *pzErr = smprintf("value out of range: %s\n", azArg[i]); ++ return DCR_ArgWrong|i; ++ } ++ } ++ } ++ else{ ++ return DCR_Unknown|i; + } + } -end_ar_command: - if( cmd.db!=pState->db ){ - close_db(cmd.db); ++ ++ psi->expert.pExpert = sqlite3_expert_new(DBI(psi), &zErr); ++ if( psi->expert.pExpert==0 ){ ++ *pzErr = smprintf("sqlite3_expert_new: %s\n", ++ zErr ? zErr : "out of memory"); ++ return DCR_Error; ++ }else{ ++ sqlite3_expert_config(psi->expert.pExpert, EXPERT_CONFIG_SAMPLE, iSample); + } - sqlite3_free(cmd.zSrcTable); + - return rc; + return DCR_Ok; } -/* End of the ".archive" or ".ar" command logic -*******************************************************************************/ -#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) */ + -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) -/* -** If (*pRc) is not SQLITE_OK when this function is called, it is a no-op. -** Otherwise, the SQL statement or statements in zSql are executed using -** database connection db and the error code written to *pRc before -** this function returns. -*/ -static void shellExec(sqlite3 *db, int *pRc, const char *zSql){ - int rc = *pRc; - if( rc==SQLITE_OK ){ - char *zErr = 0; - rc = sqlite3_exec(db, zSql, 0, 0, &zErr); - if( rc!=SQLITE_OK ){ - raw_printf(stderr, "SQL error: %s\n", zErr); +DISPATCHABLE_COMMAND( explain ? 1 2 ){ + /* The ".explain" command is automatic now. It is largely + ** pointless, retained purely for backwards compatibility */ + ShellInState *psi = ISS(p); + int val = 1; + if( nArg>1 ){ + if( strcmp(azArg[1],"auto")==0 ){ + val = 99; + }else{ + val = booleanValue(azArg[1]); } - sqlite3_free(zErr); - *pRc = rc; } + if( val==1 && psi->mode!=MODE_Explain ){ + psi->normalMode = psi->mode; + psi->mode = MODE_Explain; + psi->autoExplain = 0; + }else if( val==0 ){ + if( psi->mode==MODE_Explain ) psi->mode = psi->normalMode; + psi->autoExplain = 0; + }else if( val==99 ){ + if( psi->mode==MODE_Explain ) psi->mode = psi->normalMode; + psi->autoExplain = 1; + } + return DCR_Ok; } -/* -** Like shellExec(), except that zFmt is a printf() style format string. -*/ -static void shellExecPrintf(sqlite3 *db, int *pRc, const char *zFmt, ...){ - char *z = 0; - if( *pRc==SQLITE_OK ){ - va_list ap; - va_start(ap, zFmt); - z = sqlite3_vmprintf(zFmt, ap); - va_end(ap); - if( z==0 ){ - *pRc = SQLITE_NOMEM; +/***************** + * The .excel, .once and .output commands + * These share much implementation, so they stick together. + */ +COLLECT_HELP_TEXT[ + ".excel Display the output of next command in spreadsheet", + " --bom Prefix the file with a UTF8 byte-order mark", + ".once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE", + " If FILE begins with '|' then open it as a command to be piped into.", + " Options:", + " --bom Prefix output with a UTF8 byte-order mark", + " -e Send output to the system text editor", + " -x Send output as CSV to a spreadsheet (same as \".excel\")", + ".output ?FILE? Send output to FILE or stdout if FILE is omitted", + " If FILE begins with '|' then open it as a command to be piped into.", + " Options:", + " --bom Prefix output with a UTF8 byte-order mark", + " -e Send output to the system text editor", + " -x Send output as CSV to a spreadsheet (same as \".excel\")", +]; +/* Shared implementation of .excel, .once and .output */ +static DotCmdRC outputRedirs(char *azArg[], int nArg, + ShellInState *psi, char **pzErr, + int bOnce, int eMode){ + /* bOnce => 0: .output, 1: .once, 2: .excel */ + /* eMode => 'x' for excel, else 0 */ + int rc = 0; + char *zFile = 0; + int bTxtMode = 0; + int i; + int bBOM = 0; + if( psi->bSafeMode ) return DCR_AbortError; + for(i=1; ioutCount = 2; + }else{ + psi->outCount = 0; + } + output_reset(psi); +#ifndef SQLITE_NOHAVE_SYSTEM + if( eMode=='e' || eMode=='x' ){ + psi->doXdgOpen = 1; + outputModePush(psi); + if( eMode=='x' ){ + /* spreadsheet mode. Output as CSV. */ + newTempFile(psi, "csv"); + psi->shellFlgs &= ~SHFLG_Echo; + psi->mode = MODE_Csv; + sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator, SEP_Comma); + sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, SEP_CrLf); }else{ - memset(pRet, 0, nByte); + /* text editor mode */ + newTempFile(psi, "txt"); + bTxtMode = 1; } + sqlite3_free(zFile); - zFile = sqlite3_mprintf("%s", psi->zTempFile); ++ zFile = smprintf("%s", psi->zTempFile); } - return pRet; -} - -/* -** If *pRc is not SQLITE_OK when this function is called, it is a no-op. -** Otherwise, zFmt is treated as a printf() style string. The result of -** formatting it along with any trailing arguments is written into a -** buffer obtained from sqlite3_malloc(), and pointer to which is returned. -** It is the responsibility of the caller to eventually free this buffer -** using a call to sqlite3_free(). -** -** If an OOM error occurs, (*pRc) is set to SQLITE_NOMEM and a NULL -** pointer returned. -*/ -static char *shellMPrintf(int *pRc, const char *zFmt, ...){ - char *z = 0; - if( *pRc==SQLITE_OK ){ - va_list ap; - va_start(ap, zFmt); - z = sqlite3_vmprintf(zFmt, ap); - va_end(ap); - if( z==0 ){ - *pRc = SQLITE_NOMEM; +#endif /* SQLITE_NOHAVE_SYSTEM */ + shell_check_oom(zFile); + if( zFile[0]=='|' ){ +#ifdef SQLITE_OMIT_POPEN - *pzErr = shellMPrintf(&rc, "Error: pipes are not supported in this OS\n"); ++ *pzErr = smprintf("pipes are not supported in this OS\n"); + rc = 1; + psi->out = STD_OUT; +#else + psi->out = popen(zFile + 1, "w"); + if( psi->out==0 ){ - *pzErr = shellMPrintf(&rc, "Error: cannot open pipe \"%s\"\n", zFile + 1); ++ *pzErr = smprintf("cannot open pipe \"%s\"\n", zFile + 1); + psi->out = STD_OUT; + rc = 1; + }else{ + if( bBOM ) fprintf(psi->out,"\357\273\277"); + sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile); + } +#endif + }else{ + psi->out = output_file_open(zFile, bTxtMode); + if( psi->out==0 ){ + if( strcmp(zFile,"off")!=0 ){ - *pzErr = shellMPrintf - (&rc, "Error: cannot write to \"%s\"\n", zFile); ++ *pzErr = smprintf("cannot write to \"%s\"\n", zFile); + } + psi->out = STD_OUT; + rc = 1; + } else { + if( bBOM ) fprintf(psi->out,"\357\273\277"); + sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile); } } - return z; + sqlite3_free(zFile); + return DCR_Ok|rc; +} +DISPATCHABLE_COMMAND( excel ? 1 2 ){ + return outputRedirs(azArg, nArg, ISS(p), pzErr, 2, 'x'); +} +DISPATCHABLE_COMMAND( once ? 1 6 ){ + return outputRedirs(azArg, nArg, ISS(p), pzErr, 1, 0); +} +DISPATCHABLE_COMMAND( output ? 1 6 ){ + return outputRedirs(azArg, nArg, ISS(p), pzErr, 0, 0); } -/* -** When running the ".recover" command, each output table, and the special -** orphaned row table if it is required, is represented by an instance -** of the following struct. -*/ -typedef struct RecoverTable RecoverTable; -struct RecoverTable { - char *zQuoted; /* Quoted version of table name */ - int nCol; /* Number of columns in table */ - char **azlCol; /* Array of column lists */ - int iPk; /* Index of IPK column */ -}; +/***************** + * The .filectrl and fullschema commands + */ +COLLECT_HELP_TEXT[ + ".filectrl CMD ... Run various sqlite3_file_control() operations", + " --schema SCHEMA Use SCHEMA instead of \"main\"", + " --help Show CMD details", + ".fullschema ?--indent? Show schema and the content of sqlite_stat tables", +]; +DISPATCHABLE_COMMAND( filectrl ? 2 0 ){ + static const struct { + const char *zCtrlName; /* Name of a test-control option */ + int ctrlCode; /* Integer code for that option */ + const char *zUsage; /* Usage notes */ + } aCtrl[] = { + { "chunk_size", SQLITE_FCNTL_CHUNK_SIZE, "SIZE" }, + { "data_version", SQLITE_FCNTL_DATA_VERSION, "" }, + { "has_moved", SQLITE_FCNTL_HAS_MOVED, "" }, + { "lock_timeout", SQLITE_FCNTL_LOCK_TIMEOUT, "MILLISEC" }, + { "persist_wal", SQLITE_FCNTL_PERSIST_WAL, "[BOOLEAN]" }, + /* { "pragma", SQLITE_FCNTL_PRAGMA, "NAME ARG" },*/ + { "psow", SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]" }, + { "reserve_bytes", SQLITE_FCNTL_RESERVE_BYTES, "[N]" }, + { "size_limit", SQLITE_FCNTL_SIZE_LIMIT, "[LIMIT]" }, + { "tempfilename", SQLITE_FCNTL_TEMPFILENAME, "" }, + /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY, "COUNT DELAY" },*/ + }; + ShellInState *psi = ISS(p); + int filectrl = -1; + int iCtrl = -1; + sqlite3_int64 iRes = 0; /* Integer result to display if rc2==1 */ + int isOk = 0; /* 0: usage 1: %lld 2: no-result */ + int n2, i; + const char *zCmd = 0; + const char *zSchema = 0; -/* -** Free a RecoverTable object allocated by recoverFindTable() or -** recoverOrphanTable(). -*/ -static void recoverFreeTable(RecoverTable *pTab){ - if( pTab ){ - sqlite3_free(pTab->zQuoted); - if( pTab->azlCol ){ - int i; - for(i=0; i<=pTab->nCol; i++){ - sqlite3_free(pTab->azlCol[i]); - } - sqlite3_free(pTab->azlCol); - } - sqlite3_free(pTab); + open_db(p, 0); + zCmd = nArg>=2 ? azArg[1] : "help"; + + if( zCmd[0]=='-' + && (strcmp(zCmd,"--schema")==0 || strcmp(zCmd,"-schema")==0) + && nArg>=4 + ){ + zSchema = azArg[2]; + for(i=3; iout, "Available file-controls:\n"); + for(i=0; iout, " .filectrl %s %s\n", + aCtrl[i].zCtrlName, aCtrl[i].zUsage); } - return DCR_ArgError; - if( rc==SQLITE_OK ){ - rc = sqlite3_exec(dbtmp, zSql, 0, 0, 0); - if( rc==SQLITE_ERROR ){ - rc = SQLITE_OK; - goto finished; ++ return DCR_Error; + } + + /* Convert filectrl text option to value. Allow any + ** unique prefix of the option name, or a numerical value. */ + n2 = strlen30(zCmd); + for(i=0; iiPk to the index - ** of the column, where columns are 0-numbered from left to right. - ** Or, if this is a WITHOUT ROWID table or if there is no IPK column, - ** leave zPk as "_rowid_" and pTab->iPk at -2. */ - pTab->iPk = -2; - if( bIntkey ){ - shellPreparePrintf(dbtmp, &rc, &pPkFinder, - "SELECT cid, name FROM pragma_table_info(%Q) " - " WHERE pk=1 AND type='integer' COLLATE nocase" - " AND NOT EXISTS (SELECT cid FROM pragma_table_info(%Q) WHERE pk=2)" - , zName, zName - ); - if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPkFinder) ){ - pTab->iPk = sqlite3_column_int(pPkFinder, 0); - zPk = (const char*)sqlite3_column_text(pPkFinder, 1); - if( zPk==0 ){ zPk = "_"; /* Defensive. Should never happen */ } - } - } - - pTab->zQuoted = shellMPrintf(&rc, "\"%w\"", zName); - pTab->azlCol = (char**)shellMalloc(&rc, sizeof(char*) * (nSqlCol+1)); - pTab->nCol = nSqlCol; - - if( bIntkey ){ - pTab->azlCol[0] = shellMPrintf(&rc, "\"%w\"", zPk); - }else{ - pTab->azlCol[0] = shellMPrintf(&rc, ""); + case SQLITE_FCNTL_DATA_VERSION: + case SQLITE_FCNTL_HAS_MOVED: { + int x; + if( nArg!=2 ) break; + sqlite3_file_control(DBX(p), zSchema, filectrl, &x); + iRes = x; + isOk = 1; + break; + } + case SQLITE_FCNTL_TEMPFILENAME: { + char *z = 0; + if( nArg!=2 ) break; + sqlite3_file_control(DBX(p), zSchema, filectrl, &z); + if( z ){ + utf8_printf(psi->out, "%s\n", z); + sqlite3_free(z); } - i = 1; - shellPreparePrintf(dbtmp, &rc, &pStmt, - "SELECT %Q || group_concat(shell_idquote(name), ', ') " - " FILTER (WHERE cid!=%d) OVER (ORDER BY %s cid) " - "FROM pragma_table_info(%Q)", - bIntkey ? ", " : "", pTab->iPk, - bIntkey ? "" : "(CASE WHEN pk=0 THEN 1000000 ELSE pk END), ", - zName - ); - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ - const char *zText = (const char*)sqlite3_column_text(pStmt, 0); - pTab->azlCol[i] = shellMPrintf(&rc, "%s%s", pTab->azlCol[0], zText); - i++; + isOk = 2; + break; + } + case SQLITE_FCNTL_RESERVE_BYTES: { + int x; + if( nArg>=3 ){ + x = atoi(azArg[2]); + sqlite3_file_control(DBX(p), zSchema, filectrl, &x); } - shellFinalize(&rc, pStmt); - - shellFinalize(&rc, pPkFinder); + x = -1; + sqlite3_file_control(DBX(p), zSchema, filectrl, &x); + utf8_printf(psi->out,"%d\n", x); + isOk = 2; + break; } + } } + if( isOk==0 && iCtrl>=0 ){ + utf8_printf(psi->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage); - return DCR_ArgError; ++ return DCR_CmdErred; + }else if( isOk==1 ){ + char zBuf[100]; + sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes); + raw_printf(psi->out, "%s\n", zBuf); ++ return DCR_Ok; + } - return DCR_Ok; +} - finished: - sqlite3_close(dbtmp); - *pRc = rc; - if( rc!=SQLITE_OK || (pTab && pTab->zQuoted==0) ){ - recoverFreeTable(pTab); - pTab = 0; +DISPATCHABLE_COMMAND( fullschema ? 1 2 ){ + int rc; + ShellInState data; + ShellExState datax; + int doStats = 0; + /* Consider some refactoring to avoid this wholesale copying. */ + memcpy(&data, ISS(p), sizeof(data)); + memcpy(&datax, p, sizeof(datax)); + data.pSXS = &datax; + datax.pSIS = &data; + data.showHeader = 0; + data.cMode = data.mode = MODE_Semi; + if( nArg==2 && optionMatch(azArg[1], "indent") ){ + data.cMode = data.mode = MODE_Pretty; + nArg = 1; + } + if( nArg!=1 ){ + return DCR_TooMany|1; } - return pTab; + open_db(p, 0); + rc = sqlite3_exec(datax.dbUser, + "SELECT sql FROM" + " (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x" + " FROM sqlite_schema UNION ALL" + " SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) " + "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' " + "ORDER BY x", + callback, &datax, 0 + ); + if( rc==SQLITE_OK ){ + sqlite3_stmt *pStmt; + rc = sqlite3_prepare_v2(datax.dbUser, + "SELECT rowid FROM sqlite_schema" + " WHERE name GLOB 'sqlite_stat[134]'", + -1, &pStmt, 0); + doStats = sqlite3_step(pStmt)==SQLITE_ROW; + sqlite3_finalize(pStmt); + } + if( doStats==0 ){ + raw_printf(data.out, "/* No STAT tables available */\n"); + }else{ + raw_printf(data.out, "ANALYZE sqlite_schema;\n"); + data.cMode = data.mode = MODE_Insert; + datax.zDestTable = "sqlite_stat1"; + shell_exec(&datax, "SELECT * FROM sqlite_stat1", 0); + datax.zDestTable = "sqlite_stat4"; + shell_exec(&datax, "SELECT * FROM sqlite_stat4", 0); + raw_printf(data.out, "ANALYZE sqlite_schema;\n"); + } + return rc > 0; } -/* -** This function is called to search the schema recovered from the -** sqlite_schema table of the (possibly) corrupt database as part -** of a ".recover" command. Specifically, for a table with root page -** iRoot and at least nCol columns. Additionally, if bIntkey is 0, the -** table must be a WITHOUT ROWID table, or if non-zero, not one of -** those. -** -** If a table is found, a (RecoverTable*) object is returned. Or, if -** no such table is found, but bIntkey is false and iRoot is the -** root page of an index in the recovered schema, then (*pbNoop) is -** set to true and NULL returned. Or, if there is no such table or -** index, NULL is returned and (*pbNoop) set to 0, indicating that -** the caller should write data to the orphans table. -*/ -static RecoverTable *recoverFindTable( - ShellState *pState, /* Shell state object */ - int *pRc, /* IN/OUT: Error code */ - int iRoot, /* Root page of table */ - int bIntkey, /* True for an intkey table */ - int nCol, /* Number of columns in table */ - int *pbNoop /* OUT: True if iRoot is root of index */ -){ - sqlite3_stmt *pStmt = 0; - RecoverTable *pRet = 0; - int bNoop = 0; - const char *zSql = 0; - const char *zName = 0; +/***************** + * The .headers command + */ +COLLECT_HELP_TEXT[ + ".headers on|off Turn display of headers on or off", +]; +DISPATCHABLE_COMMAND( headers 6 2 2 ){ + ISS(p)->showHeader = booleanValue(azArg[1]); + ISS(p)->shellFlgs |= SHFLG_HeaderSet; + return DCR_Ok; +} - /* Search the recovered schema for an object with root page iRoot. */ - shellPreparePrintf(pState->db, pRc, &pStmt, - "SELECT type, name, sql FROM recovery.schema WHERE rootpage=%d", iRoot - ); - while( *pRc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ - const char *zType = (const char*)sqlite3_column_text(pStmt, 0); - if( bIntkey==0 && sqlite3_stricmp(zType, "index")==0 ){ - bNoop = 1; - break; - } - if( sqlite3_stricmp(zType, "table")==0 ){ - zName = (const char*)sqlite3_column_text(pStmt, 1); - zSql = (const char*)sqlite3_column_text(pStmt, 2); - if( zName!=0 && zSql!=0 ){ - pRet = recoverNewTable(pRc, zName, zSql, bIntkey, nCol); - break; - } +/***************** + * The .help command + */ + +/* This literal's value AND address are used for help's workings. */ +static const char *zHelpAll = "-all"; + +COLLECT_HELP_TEXT[ + ".help ?PATTERN?|?-all? Show help for PATTERN or everything, or summarize", + " Repeat -all to see undocumented commands", +]; +DISPATCHABLE_COMMAND( help 3 1 3 ){ + const char *zPat = 0; + FILE *out = ISS(p)->out; + if( nArg>1 ){ + char *z = azArg[1]; + if( nArg==3 && strcmp(z, zHelpAll)==0 && strcmp(azArg[2], zHelpAll)==0 ){ + /* Show the undocumented command help */ + zPat = zHelpAll; + }else if( strcmp(z,"-a")==0 || strcmp(z,"-all")==0 || strcmp(z,"--all")==0 ){ + zPat = ""; + }else{ + zPat = z; } } - - shellFinalize(pRc, pStmt); - *pbNoop = bNoop; - return pRet; + if( showHelp(out, zPat, p)==0 && nArg>1 ){ + utf8_printf(out, "Nothing matches '%s'\n", azArg[1]); + } + /* Help pleas never fail! */ + return DCR_Ok; } -/* -** Return a RecoverTable object representing the orphans table. -*/ -static RecoverTable *recoverOrphanTable( - ShellState *pState, /* Shell state object */ - int *pRc, /* IN/OUT: Error code */ - const char *zLostAndFound, /* Base name for orphans table */ - int nCol /* Number of user data columns */ -){ - RecoverTable *pTab = 0; - if( nCol>=0 && *pRc==SQLITE_OK ){ - int i; +/***************** + * The .import command + */ +COLLECT_HELP_TEXT[ + ".import FILE TABLE Import data from FILE into TABLE", + " Options:", + " --ascii Use \\037 and \\036 as column and row separators", + " --csv Use , and \\n as column and row separators", + " --skip N Skip the first N rows of input", + " --schema S Target table to be S.TABLE", + " -v \"Verbose\" - increase auxiliary output", + " Notes:", + " * If TABLE does not exist, it is created. The first row of input", + " determines the column names.", + " * If neither --csv or --ascii are used, the input mode is derived", + " from the \".mode\" output mode", + " * If FILE begins with \"|\" then it is a command that generates the", + " input text.", +]; +DISPATCHABLE_COMMAND( import ? 3 7 ){ + char *zTable = 0; /* Insert data into this table */ + char *zSchema = 0; /* within this schema (may default to "main") */ + char *zFile = 0; /* Name of file to extra content from */ + sqlite3_stmt *pStmt = NULL; /* A statement */ + int nCol; /* Number of columns in the table */ + int nByte; /* Number of bytes in an SQL string */ + int i, j; /* Loop counters */ + int needCommit; /* True to COMMIT or ROLLBACK at end */ + int nSep; /* Number of bytes in psi->colSeparator[] */ + char *zSql; /* An SQL statement */ + char *zFullTabName; /* Table name with schema if applicable */ + ImportCtx sCtx; /* Reader context */ + char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */ + int eVerbose = 0; /* Larger for more console output */ + int nSkip = 0; /* Initial lines to skip */ + int useOutputMode = 1; /* Use output mode to determine separators */ + FILE *out = ISS(p)->out; /* output stream */ + ShellInState *psi = ISS(p); + int rc = 0; - /* This block determines the name of the orphan table. The prefered - ** name is zLostAndFound. But if that clashes with another name - ** in the recovered schema, try zLostAndFound_0, zLostAndFound_1 - ** and so on until a non-clashing name is found. */ - int iTab = 0; - char *zTab = shellMPrintf(pRc, "%s", zLostAndFound); - sqlite3_stmt *pTest = 0; - shellPrepare(pState->db, pRc, - "SELECT 1 FROM recovery.schema WHERE name=?", &pTest - ); - if( pTest ) sqlite3_bind_text(pTest, 1, zTab, -1, SQLITE_TRANSIENT); - while( *pRc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pTest) ){ - shellReset(pRc, pTest); - sqlite3_free(zTab); - zTab = shellMPrintf(pRc, "%s_%d", zLostAndFound, iTab++); - sqlite3_bind_text(pTest, 1, zTab, -1, SQLITE_TRANSIENT); + if(psi->bSafeMode) return DCR_AbortError; + memset(&sCtx, 0, sizeof(sCtx)); + if( 0==(sCtx.z = sqlite3_malloc64(120)) ){ + shell_out_of_memory(); + } + + if( psi->mode==MODE_Ascii ){ + xRead = ascii_read_one_field; + }else{ + xRead = csv_read_one_field; + } + for(i=1; icolSeparator); + if( nSep==0 ){ - zYap = "Error: non-null column separator required for import"; ++ zYap = "non-null column separator required for import"; + } + if( nSep>1 ){ - zYap = "Error: multi-character or multi-byte column separators" ++ zYap = "multi-character or multi-byte column separators" + " not allowed for import"; + } + nSep = strlen30(psi->rowSeparator); + if( nSep==0 ){ - zYap = "Error: non-null row separator required for import"; ++ zYap = "non-null row separator required for import"; + } + if( zYap!=0 ){ - *pzErr = shellMPrintf(0,"%s\n", zYap); ++ *pzErr = smprintf("%s\n", zYap); + return DCR_Error; + } + if( nSep==2 && psi->mode==MODE_Csv + && strcmp(psi->rowSeparator,SEP_CrLf)==0 ){ + /* When importing CSV (only), if the row separator is set to the + ** default output row separator, change it to the default input + ** row separator. This avoids having to maintain different input + ** and output row separators. */ + sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, SEP_Row); + nSep = strlen30(psi->rowSeparator); + } + if( nSep>1 ){ - *pzErr = sqlite3_mprintf - ("Error: multi-character row separators not allowed for import\n"); ++ *pzErr ++ = smprintf("multi-character row separators not allowed for import\n"); + return DCR_Error; + } + sCtx.cColSep = psi->colSeparator[0]; + sCtx.cRowSep = psi->rowSeparator[0]; + } + sCtx.zFile = zFile; + sCtx.nLine = 1; + if( sCtx.zFile[0]=='|' ){ +#ifdef SQLITE_OMIT_POPEN - *pzErr = shellMPrintf(0,"Error: pipes are not supported in this OS\n"); ++ *pzErr = smprintf("pipes are not supported in this OS\n"); + return DCR_Error; +#else + sCtx.in = popen(sCtx.zFile+1, "r"); + sCtx.zFile = ""; + sCtx.xCloser = pclose; +#endif + }else{ + sCtx.in = fopen(sCtx.zFile, "rb"); + sCtx.xCloser = fclose; + } + if( sCtx.in==0 ){ - *pzErr = shellMPrintf(0,"Error: cannot open \"%s\"\n", zFile); ++ *pzErr = smprintf("cannot open \"%s\"\n", zFile); + import_cleanup(&sCtx); + return DCR_Error; + } + /* Here and below, resources must be freed before exit. */ + if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){ + char zSep[2]; + zSep[1] = 0; + zSep[0] = sCtx.cColSep; + utf8_printf(out, "Column separator "); + output_c_string(out, zSep); + utf8_printf(out, ", row separator "); + zSep[0] = sCtx.cRowSep; + output_c_string(out, zSep); + utf8_printf(out, "\n"); + } + while( (nSkip--)>0 ){ + while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){} + } + if( zSchema!=0 ){ - zFullTabName = sqlite3_mprintf("\"%w\".\"%w\"", zSchema, zTable); ++ zFullTabName = smprintf("\"%w\".\"%w\"", zSchema, zTable); + }else{ - zFullTabName = sqlite3_mprintf("\"%w\"", zTable); ++ zFullTabName = smprintf("\"%w\"", zTable); + } - zSql = sqlite3_mprintf("SELECT * FROM %s", zFullTabName); ++ zSql = smprintf("SELECT * FROM %s", zFullTabName); + if( zSql==0 || zFullTabName==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + nByte = strlen30(zSql); + rc = sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0); + import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ + if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(DBX(p)))==0 ){ - char *zCreate = sqlite3_mprintf("CREATE TABLE %s", zFullTabName); ++ char *zCreate = smprintf("CREATE TABLE %s", zFullTabName); + sqlite3 *dbCols = 0; + char *zRenames = 0; + char *zColDefs; + while( xRead(&sCtx) ){ + zAutoColumn(sCtx.z, &dbCols, 0); + if( sCtx.cTerm!=sCtx.cColSep ) break; + } + zColDefs = zAutoColumn(0, &dbCols, &zRenames); + if( zRenames!=0 ){ + FILE *fh = INSOURCE_IS_INTERACTIVE(psi->pInSource)? out : STD_ERR; + utf8_printf(fh, "Columns renamed during .import %s due to duplicates:\n" + "%s\n", sCtx.zFile, zRenames); + sqlite3_free(zRenames); + } + assert(dbCols==0); + if( zColDefs==0 ){ - utf8_printf(stderr,"%s: empty file\n", sCtx.zFile); ++ *pzErr = smprintf("%s: empty file\n", sCtx.zFile); + sqlite3_free(zCreate); + import_fail: /* entry from outer blocks */ + sqlite3_free(zSql); + sqlite3_free(zFullTabName); + import_cleanup(&sCtx); + return DCR_Error; + } - zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs); ++ zCreate = smprintf("%z%z\n", zCreate, zColDefs); + if( eVerbose>=1 ){ + utf8_printf(out, "%s\n", zCreate); + } + rc = sqlite3_exec(DBX(p), zCreate, 0, 0, 0); + sqlite3_free(zCreate); + if( rc ){ - utf8_printf(STD_ERR, "%s failed:\n%s\n", zCreate, sqlite3_errmsg(DBX(p))); ++ *pzErr = smprintf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(DBX(p))); + goto import_fail; + } + rc = sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0); + } + if( rc ){ + if (pStmt) sqlite3_finalize(pStmt); - *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(DBX(p))); ++ *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p))); + goto import_fail; + } + sqlite3_free(zSql); + nCol = sqlite3_column_count(pStmt); + sqlite3_finalize(pStmt); + pStmt = 0; + if( nCol==0 ) return DCR_Ok; /* no columns, no error */ + zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 ); + if( zSql==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName); + j = strlen30(zSql); + for(i=1; i=2 ){ + utf8_printf(psi->out, "Insert using: %s\n", zSql); + } + rc = sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0); + if( rc ){ - *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(DBX(p))); ++ *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p))); + if (pStmt) sqlite3_finalize(pStmt); + goto import_fail; + } + sqlite3_free(zSql); + sqlite3_free(zFullTabName); + needCommit = sqlite3_get_autocommit(DBX(p)); + if( needCommit ) sqlite3_exec(DBX(p), "BEGIN", 0, 0, 0); + do{ + int startLine = sCtx.nLine; + for(i=0; imode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; + sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); + if( i=nCol ){ + sqlite3_step(pStmt); + rc = sqlite3_reset(pStmt); + if( rc!=SQLITE_OK ){ + utf8_printf(STD_ERR, "%s:%d: INSERT failed: %s\n", sCtx.zFile, + startLine, sqlite3_errmsg(DBX(p))); + sCtx.nErr++; + }else{ + sCtx.nRow++; + } + } + }while( sCtx.cTerm!=EOF ); - pTab = (RecoverTable*)shellMalloc(pRc, sizeof(RecoverTable)); - if( pTab ){ - pTab->zQuoted = shellMPrintf(pRc, "\"%w\"", zTab); - pTab->nCol = nCol; - pTab->iPk = -2; - if( nCol>0 ){ - pTab->azlCol = (char**)shellMalloc(pRc, sizeof(char*) * (nCol+1)); - if( pTab->azlCol ){ - pTab->azlCol[nCol] = shellMPrintf(pRc, ""); - for(i=nCol-1; i>=0; i--){ - pTab->azlCol[i] = shellMPrintf(pRc, "%s, NULL", pTab->azlCol[i+1]); + import_cleanup(&sCtx); + sqlite3_finalize(pStmt); + if( needCommit ) sqlite3_exec(DBX(p), "COMMIT", 0, 0, 0); + if( eVerbose>0 ){ + utf8_printf(out, + "Added %d rows with %d errors using %d lines of input\n", + sCtx.nRow, sCtx.nErr, sCtx.nLine-1); + } - return DCR_Ok; ++ return DCR_Ok|(sCtx.nErr>0); +} + +/***************** + * The .keyword command + */ +CONDITION_COMMAND( keyword !defined(NO_KEYWORD_COMMAND) ); +COLLECT_HELP_TEXT[ + ".keyword ?KW? List keywords, or say whether KW is one.", +]; +DISPATCHABLE_COMMAND( keyword ? 1 2 ){ + FILE *out = ISS(p)->out; + if( nArg<2 ){ + int i = 0; + int nk = sqlite3_keyword_count(); + int nCol = 0; + int szKW; + while( i75){ + zSep = "\n"; + nCol = 0; } + memcpy(kwBuf, zKW, szKW); + kwBuf[szKW] = 0; + utf8_printf(out, "%s%s", kwBuf, zSep); } } + } + if( nCol>0 ) utf8_printf(out, "\n"); + }else{ + int szKW = strlen30(azArg[1]); + int isKeyword = sqlite3_keyword_check(azArg[1], szKW); + utf8_printf(out, "%s is%s a keyword\n", + azArg[1], (isKeyword)? "" : " not"); + } + return DCR_Ok; +} - if( *pRc!=SQLITE_OK ){ - recoverFreeTable(pTab); - pTab = 0; +/***************** + * The .imposter, .iotrace, limit, lint, .load and .log commands + */ +CONDITION_COMMAND( imposter !defined(SQLITE_OMIT_TEST_CONTROL) ); +CONDITION_COMMAND( iotrace defined(SQLITE_ENABLE_IOTRACE) ); +CONDITION_COMMAND( load !defined(SQLITE_OMIT_LOAD_EXTENSION) ); +COLLECT_HELP_TEXT[ + ".imposter INDEX TABLE Create imposter table TABLE on index INDEX", + ".iotrace FILE Enable I/O diagnostic logging to FILE", + ".limit ?LIMIT? ?VAL? Display or change the value of an SQLITE_LIMIT", + ".lint OPTIONS Report potential schema issues.", + " Options:", + " fkey-indexes Find missing foreign key indexes", + ".load FILE ?ENTRY? Load an extension library", +#if SHELL_DYNAMIC_EXTENSION + " Option -shellext will load the library as a shell extension.", +#endif + ".log FILE|off Turn logging on or off. FILE can be stderr/stdout", +]; +DISPATCHABLE_COMMAND( imposter ? 3 3 ){ + int rc = 0; + char *zSql; + char *zCollist = 0; + sqlite3_stmt *pStmt; + sqlite3 *db; + int tnum = 0; + int isWO = 0; /* True if making an imposter of a WITHOUT ROWID table */ + int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */ + int i; + if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){ - *pzErr = shellMPrintf(0,"Usage: .imposter INDEX IMPOSTER\n" - " .imposter off\n"); ++ *pzErr = smprintf("Usage: .imposter INDEX IMPOSTER\n" ++ " .imposter off\n"); + /* Also allowed, but not documented: + ** + ** .imposter TABLE IMPOSTER + ** + ** where TABLE is a WITHOUT ROWID table. In that case, the + ** imposter is another WITHOUT ROWID table with the columns in + ** storage order. */ - return DCR_ArgError; ++ return DCR_SayUsage; + } + open_db(p, 0); + db = DBX(p); + if( nArg==2 ){ + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 1); + return DCR_Ok; + } - zSql = sqlite3_mprintf( - "SELECT rootpage, 0 FROM sqlite_schema" - " WHERE name='%q' AND type='index'" - "UNION ALL " - "SELECT rootpage, 1 FROM sqlite_schema" - " WHERE name='%q' AND type='table'" - " AND sql LIKE '%%without%%rowid%%'", - azArg[1], azArg[1] - ); ++ zSql = smprintf("SELECT rootpage, 0 FROM sqlite_schema" ++ " WHERE name='%q' AND type='index'" ++ "UNION ALL " ++ "SELECT rootpage, 1 FROM sqlite_schema" ++ " WHERE name='%q' AND type='table'" ++ " AND sql LIKE '%%without%%rowid%%'", ++ azArg[1], azArg[1]); + sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + tnum = sqlite3_column_int(pStmt, 0); + isWO = sqlite3_column_int(pStmt, 1); + } + sqlite3_finalize(pStmt); - zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]); ++ zSql = smprintf("PRAGMA index_xinfo='%q'", azArg[1]); + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + i = 0; + while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ + char zLabel[20]; + const char *zCol = (const char*)sqlite3_column_text(pStmt,2); + i++; + if( zCol==0 ){ + if( sqlite3_column_int(pStmt,1)==-1 ){ + zCol = "_ROWID_"; }else{ - raw_printf(pState->out, - "CREATE TABLE %s(rootpgno INTEGER, " - "pgno INTEGER, nfield INTEGER, id INTEGER", pTab->zQuoted - ); - for(i=0; iout, ", c%d", i); + sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i); + zCol = zLabel; + } + } + if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){ + lenPK = (int)strlen(zCollist); + } + if( zCollist==0 ){ - zCollist = sqlite3_mprintf("\"%w\"", zCol); ++ zCollist = smprintf("\"%w\"", zCol); + }else{ - zCollist = sqlite3_mprintf("%z,\"%w\"", zCollist, zCol); ++ zCollist = smprintf("%z,\"%w\"", zCollist, zCol); + } + } + sqlite3_finalize(pStmt); + if( i==0 || tnum==0 ){ - *pzErr = shellMPrintf(0,"no such index: \"%s\"\n", azArg[1]); ++ *pzErr = smprintf("no such index: \"%s\"\n", azArg[1]); + sqlite3_free(zCollist); + return DCR_Error; + } + if( lenPK==0 ) lenPK = 100000; - zSql = sqlite3_mprintf("CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))" - "WITHOUT ROWID", azArg[2], zCollist, lenPK, zCollist); ++ zSql = smprintf("CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))" ++ "WITHOUT ROWID", azArg[2], zCollist, lenPK, zCollist); + sqlite3_free(zCollist); + rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 1, tnum); + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(db, zSql, 0, 0, 0); + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 0); + if( rc ){ - *pzErr = shellMPrintf(0,"Error in [%s]: %s\n", - zSql, sqlite3_errmsg(db)); ++ *pzErr = smprintf("Error in [%s]: %s\n", zSql, sqlite3_errmsg(db)); + }else{ + utf8_printf(STD_OUT, "%s;\n", zSql); + raw_printf(STD_OUT, "WARNING: " + "writing to an imposter table will corrupt the \"%s\" %s!\n", + azArg[1], isWO ? "table" : "index" + ); + } + }else{ - *pzErr = shellMPrintf(0,"SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); ++ *pzErr = smprintf("SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); + } + sqlite3_free(zSql); + return rc != 0; +} +DISPATCHABLE_COMMAND( iotrace ? 2 2 ){ + SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...); + if( iotrace && iotrace!=STD_OUT ) fclose(iotrace); + iotrace = 0; + if( nArg<2 ){ + sqlite3IoTrace = 0; + }else if( strcmp(azArg[1], "-")==0 ){ + sqlite3IoTrace = iotracePrintf; + iotrace = STD_OUT; + }else{ + iotrace = fopen(azArg[1], "w"); + if( iotrace==0 ){ - *pzErr = shellMPrintf(0,"Error: cannot open \"%s\"\n", azArg[1]); ++ *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]); + sqlite3IoTrace = 0; + return DCR_Error; + }else{ + sqlite3IoTrace = iotracePrintf; + } + } + return DCR_Ok; +} +DISPATCHABLE_COMMAND( limits 5 1 3 ){ + static const struct { + const char *zLimitName; /* Name of a limit */ + int limitCode; /* Integer code for that limit */ + } aLimit[] = { + { "length", SQLITE_LIMIT_LENGTH }, + { "sql_length", SQLITE_LIMIT_SQL_LENGTH }, + { "column", SQLITE_LIMIT_COLUMN }, + { "expr_depth", SQLITE_LIMIT_EXPR_DEPTH }, + { "compound_select", SQLITE_LIMIT_COMPOUND_SELECT }, + { "vdbe_op", SQLITE_LIMIT_VDBE_OP }, + { "function_arg", SQLITE_LIMIT_FUNCTION_ARG }, + { "attached", SQLITE_LIMIT_ATTACHED }, + { "like_pattern_length", SQLITE_LIMIT_LIKE_PATTERN_LENGTH }, + { "variable_number", SQLITE_LIMIT_VARIABLE_NUMBER }, + { "trigger_depth", SQLITE_LIMIT_TRIGGER_DEPTH }, + { "worker_threads", SQLITE_LIMIT_WORKER_THREADS }, + }; + int i, n2; + open_db(p, 0); + if( nArg==1 ){ + for(i=0; i3 ){ + return DCR_TooMany; + }else{ + int iLimit = -1; + n2 = strlen30(azArg[1]); + for(i=0; iout, ");\n"); } } - sqlite3_free(zTab); + if( iLimit<0 ){ - *pzErr = sqlite3_mprintf - ("unknown limit: \"%s\"\n" - "enter \".limits\" with no arguments for a list.\n", - azArg[1]); - return DCR_ArgError; ++ *pzErr = smprintf("unknown limit: \"%s\"\n" ++ "enter \".limits\" with no arguments for a list.\n", ++ azArg[1]); ++ return DCR_ArgWrong; + } + if( nArg==3 ){ + sqlite3_limit(DBX(p), aLimit[iLimit].limitCode, + (int)integerValue(azArg[2])); + } + fprintf(STD_OUT, "%20s %d\n", aLimit[iLimit].zLimitName, + sqlite3_limit(DBX(p), aLimit[iLimit].limitCode, -1)); } - return pTab; + return DCR_Ok; } -/* -** This function is called to recover data from the database. A script -** to construct a new database containing all recovered data is output -** on stream pState->out. -*/ -static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ - int rc = SQLITE_OK; - sqlite3_stmt *pLoop = 0; /* Loop through all root pages */ - sqlite3_stmt *pPages = 0; /* Loop through all pages in a group */ - sqlite3_stmt *pCells = 0; /* Loop through all cells in a page */ - const char *zRecoveryDb = ""; /* Name of "recovery" database */ - const char *zLostAndFound = "lost_and_found"; - int i; - int nOrphan = -1; - RecoverTable *pOrphan = 0; +DISPATCHABLE_COMMAND( lint 3 1 0 ){ + sqlite3 *db; /* Database handle to query "main" db of */ + FILE *out = ISS(p)->out; /* Stream to write non-error output to */ + int bVerbose = 0; /* If -verbose is present */ + int bGroupByParent = 0; /* If -groupbyparent is present */ + int i; /* To iterate through azArg[] */ + const char *zIndent = ""; /* How much to indent CREATE INDEX by */ + int rc; /* Return code */ + sqlite3_stmt *pSql = 0; /* Compiled version of SQL statement below */ - int bFreelist = 1; /* 0 if --freelist-corrupt is specified */ - int bRowids = 1; /* 0 if --no-rowids */ - for(i=1; i=2 ? strlen30(azArg[1]) : 0); + if( i==0 || 0!=sqlite3_strnicmp(azArg[1], "fkey-indexes", i) ){ - *pzErr = sqlite3_mprintf ++ *pzErr = smprintf + ("Usage %s sub-command ?switches...?\n" + "Where sub-commands are:\n" + " fkey-indexes\n", azArg[0]); - return DCR_ArgError; ++ return DCR_SayUsage; + } + open_db(p, 0); + db = DBX(p); + + /* + ** This SELECT statement returns one row for each foreign key constraint + ** in the schema of the main database. The column values are: + ** + ** 0. The text of an SQL statement similar to: + ** + ** "EXPLAIN QUERY PLAN SELECT 1 FROM child_table WHERE child_key=?" + ** + ** This SELECT is similar to the one that the foreign keys implementation + ** needs to run internally on child tables. If there is an index that can + ** be used to optimize this query, then it can also be used by the FK + ** implementation to optimize DELETE or UPDATE statements on the parent + ** table. + ** + ** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by + ** the EXPLAIN QUERY PLAN command matches this pattern, then the schema + ** contains an index that can be used to optimize the query. + ** + ** 2. Human readable text that describes the child table and columns. e.g. + ** + ** "child_table(child_key1, child_key2)" + ** + ** 3. Human readable text that describes the parent table and columns. e.g. + ** + ** "parent_table(parent_key1, parent_key2)" + ** + ** 4. A full CREATE INDEX statement for an index that could be used to + ** optimize DELETE or UPDATE statements on the parent table. e.g. + ** + ** "CREATE INDEX child_table_child_key ON child_table(child_key)" + ** + ** 5. The name of the parent table. + ** + ** These six values are used by the C logic below to generate the report. + */ + const char *zSql = + "SELECT " + " 'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '" + " || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' " + " || fkey_collate_clause(" + " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')" + ", " + " 'SEARCH ' || s.name || ' USING COVERING INDEX*('" + " || group_concat('*=?', ' AND ') || ')'" + ", " + " s.name || '(' || group_concat(f.[from], ', ') || ')'" + ", " + " f.[table] || '(' || group_concat(COALESCE(f.[to], p.[name])) || ')'" + ", " + " 'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))" + " || ' ON ' || quote(s.name) || '('" + " || group_concat(quote(f.[from]) ||" + " fkey_collate_clause(" + " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')" + " || ');'" + ", " + " f.[table] " + "FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f " + "LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) " + "GROUP BY s.name, f.id " + "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)" + ; + const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)"; + + for(i=2; i1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){ + bVerbose = 1; + } + else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){ + bGroupByParent = 1; + zIndent = " "; } else{ - utf8_printf(stderr, "unexpected option: %s\n", azArg[i]); - showHelp(pState->out, azArg[0]); - return 1; + raw_printf(STD_ERR, "Usage: %s %s ?-verbose? ?-groupbyparent?\n", - azArg[0], azArg[1] ++ azArg[0], azArg[1] + ); - return SQLITE_ERROR; ++ return DCR_Unknown|i; } } - shellExecPrintf(pState->db, &rc, - /* Attach an in-memory database named 'recovery'. Create an indexed - ** cache of the sqlite_dbptr virtual table. */ - "PRAGMA writable_schema = on;" - "ATTACH %Q AS recovery;" - "DROP TABLE IF EXISTS recovery.dbptr;" - "DROP TABLE IF EXISTS recovery.freelist;" - "DROP TABLE IF EXISTS recovery.map;" - "DROP TABLE IF EXISTS recovery.schema;" - "CREATE TABLE recovery.freelist(pgno INTEGER PRIMARY KEY);", zRecoveryDb + /* Register the fkey_collate_clause() SQL function */ + rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8, + 0, shellFkeyCollateClause, 0, 0 ); - if( bFreelist ){ - shellExec(pState->db, &rc, - "WITH trunk(pgno) AS (" - " SELECT shell_int32(" - " (SELECT data FROM sqlite_dbpage WHERE pgno=1), 8) AS x " - " WHERE x>0" - " UNION" - " SELECT shell_int32(" - " (SELECT data FROM sqlite_dbpage WHERE pgno=trunk.pgno), 0) AS x " - " FROM trunk WHERE x>0" - ")," - "freelist(data, n, freepgno) AS (" - " SELECT data, min(16384, shell_int32(data, 1)-1), t.pgno " - " FROM trunk t, sqlite_dbpage s WHERE s.pgno=t.pgno" - " UNION ALL" - " SELECT data, n-1, shell_int32(data, 2+n) " - " FROM freelist WHERE n>=0" - ")" - "REPLACE INTO recovery.freelist SELECT freepgno FROM freelist;" - ); + if( rc==SQLITE_OK ){ + rc = sqlite3_prepare_v2(db, zSql, -1, &pSql, 0); + } + if( rc==SQLITE_OK ){ + sqlite3_bind_int(pSql, 1, bGroupByParent); } - /* If this is an auto-vacuum database, add all pointer-map pages to - ** the freelist table. Do this regardless of whether or not - ** --freelist-corrupt was specified. */ - shellExec(pState->db, &rc, - "WITH ptrmap(pgno) AS (" - " SELECT 2 WHERE shell_int32(" - " (SELECT data FROM sqlite_dbpage WHERE pgno=1), 13" - " )" - " UNION ALL " - " SELECT pgno+1+(SELECT page_size FROM pragma_page_size)/5 AS pp " - " FROM ptrmap WHERE pp<=(SELECT page_count FROM pragma_page_count)" - ")" - "REPLACE INTO recovery.freelist SELECT pgno FROM ptrmap" - ); - - shellExec(pState->db, &rc, - "CREATE TABLE recovery.dbptr(" - " pgno, child, PRIMARY KEY(child, pgno)" - ") WITHOUT ROWID;" - "INSERT OR IGNORE INTO recovery.dbptr(pgno, child) " - " SELECT * FROM sqlite_dbptr" - " WHERE pgno NOT IN freelist AND child NOT IN freelist;" - - /* Delete any pointer to page 1. This ensures that page 1 is considered - ** a root page, regardless of how corrupt the db is. */ - "DELETE FROM recovery.dbptr WHERE child = 1;" - - /* Delete all pointers to any pages that have more than one pointer - ** to them. Such pages will be treated as root pages when recovering - ** data. */ - "DELETE FROM recovery.dbptr WHERE child IN (" - " SELECT child FROM recovery.dbptr GROUP BY child HAVING count(*)>1" - ");" - - /* Create the "map" table that will (eventually) contain instructions - ** for dealing with each page in the db that contains one or more - ** records. */ - "CREATE TABLE recovery.map(" - "pgno INTEGER PRIMARY KEY, maxlen INT, intkey, root INT" - ");" + if( rc==SQLITE_OK ){ + int rc2; + char *zPrev = 0; + while( SQLITE_ROW==sqlite3_step(pSql) ){ + int res = -1; + sqlite3_stmt *pExplain = 0; + const char *zEQP = (const char*)sqlite3_column_text(pSql, 0); + const char *zGlob = (const char*)sqlite3_column_text(pSql, 1); + const char *zFrom = (const char*)sqlite3_column_text(pSql, 2); + const char *zTarget = (const char*)sqlite3_column_text(pSql, 3); + const char *zCI = (const char*)sqlite3_column_text(pSql, 4); + const char *zParent = (const char*)sqlite3_column_text(pSql, 5); - /* Populate table [map]. If there are circular loops of pages in the - ** database, the following adds all pages in such a loop to the map - ** as individual root pages. This could be handled better. */ - "WITH pages(i, maxlen) AS (" - " SELECT page_count, (" - " SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=page_count" - " ) FROM pragma_page_count WHERE page_count>0" - " UNION ALL" - " SELECT i-1, (" - " SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=i-1" - " ) FROM pages WHERE i>=2" - ")" - "INSERT INTO recovery.map(pgno, maxlen, intkey, root) " - " SELECT i, maxlen, NULL, (" - " WITH p(orig, pgno, parent) AS (" - " SELECT 0, i, (SELECT pgno FROM recovery.dbptr WHERE child=i)" - " UNION " - " SELECT i, p.parent, " - " (SELECT pgno FROM recovery.dbptr WHERE child=p.parent) FROM p" - " )" - " SELECT pgno FROM p WHERE (parent IS NULL OR pgno = orig)" - ") " - "FROM pages WHERE maxlen IS NOT NULL AND i NOT IN freelist;" - "UPDATE recovery.map AS o SET intkey = (" - " SELECT substr(data, 1, 1)==X'0D' FROM sqlite_dbpage WHERE pgno=o.pgno" - ");" + if( zEQP==0 || zGlob==0 ) continue; + rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0); + if( rc!=SQLITE_OK ) break; + if( SQLITE_ROW==sqlite3_step(pExplain) ){ + const char *zPlan = (const char*)sqlite3_column_text(pExplain, 3); + res = zPlan!=0 && ( 0==sqlite3_strglob(zGlob, zPlan) + || 0==sqlite3_strglob(zGlobIPK, zPlan)); + } + rc = sqlite3_finalize(pExplain); + if( rc!=SQLITE_OK ) break; - /* Extract data from page 1 and any linked pages into table - ** recovery.schema. With the same schema as an sqlite_schema table. */ - "CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);" - "INSERT INTO recovery.schema SELECT " - " max(CASE WHEN field=0 THEN value ELSE NULL END)," - " max(CASE WHEN field=1 THEN value ELSE NULL END)," - " max(CASE WHEN field=2 THEN value ELSE NULL END)," - " max(CASE WHEN field=3 THEN value ELSE NULL END)," - " max(CASE WHEN field=4 THEN value ELSE NULL END)" - "FROM sqlite_dbdata WHERE pgno IN (" - " SELECT pgno FROM recovery.map WHERE root=1" - ")" - "GROUP BY pgno, cell;" - "CREATE INDEX recovery.schema_rootpage ON schema(rootpage);" - ); + if( res<0 ){ + raw_printf(STD_ERR, "Error: internal error"); + break; + }else{ + if( bGroupByParent + && (bVerbose || res==0) + && (zPrev==0 || sqlite3_stricmp(zParent, zPrev)) + ){ + raw_printf(out, "-- Parent table %s\n", zParent); + sqlite3_free(zPrev); - zPrev = sqlite3_mprintf("%s", zParent); ++ zPrev = smprintf("%s", zParent); + } - /* Open a transaction, then print out all non-virtual, non-"sqlite_%" - ** CREATE TABLE statements that extracted from the existing schema. */ - if( rc==SQLITE_OK ){ - sqlite3_stmt *pStmt = 0; - /* ".recover" might output content in an order which causes immediate - ** foreign key constraints to be violated. So disable foreign-key - ** constraint enforcement to prevent problems when running the output - ** script. */ - raw_printf(pState->out, "PRAGMA foreign_keys=OFF;\n"); - raw_printf(pState->out, "BEGIN;\n"); - raw_printf(pState->out, "PRAGMA writable_schema = on;\n"); - shellPrepare(pState->db, &rc, - "SELECT sql FROM recovery.schema " - "WHERE type='table' AND sql LIKE 'create table%'", &pStmt - ); - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ - const char *zCreateTable = (const char*)sqlite3_column_text(pStmt, 0); - raw_printf(pState->out, "CREATE TABLE IF NOT EXISTS %s;\n", - &zCreateTable[12] - ); + if( res==0 ){ + raw_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget); + }else if( bVerbose ){ + raw_printf(out, "%s/* no extra indexes required for %s -> %s */\n", + zIndent, zFrom, zTarget); + } + } } - shellFinalize(&rc, pStmt); - } + sqlite3_free(zPrev); - /* Figure out if an orphan table will be required. And if so, how many - ** user columns it should contain */ - shellPrepare(pState->db, &rc, - "SELECT coalesce(max(maxlen), -2) FROM recovery.map WHERE root>1" - , &pLoop - ); - if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){ - nOrphan = sqlite3_column_int(pLoop, 0); + if( rc!=SQLITE_OK ){ - raw_printf(STD_ERR, "%s\n", sqlite3_errmsg(db)); ++ *pzErr = smprintf("%s\n", sqlite3_errmsg(db)); + } + + rc2 = sqlite3_finalize(pSql); + if( rc==SQLITE_OK && rc2!=SQLITE_OK ){ + rc = rc2; - raw_printf(STD_ERR, "%s\n", sqlite3_errmsg(db)); ++ *pzErr = smprintf("%s\n", sqlite3_errmsg(db)); + } + }else{ - raw_printf(STD_ERR, "%s\n", sqlite3_errmsg(db)); ++ *pzErr = smprintf("%s\n", sqlite3_errmsg(db)); } - shellFinalize(&rc, pLoop); - pLoop = 0; - return rc; - shellPrepare(pState->db, &rc, - "SELECT pgno FROM recovery.map WHERE root=?", &pPages - ); ++ return DCR_Ok|(rc!=0); +} - shellPrepare(pState->db, &rc, - "SELECT max(field), group_concat(shell_escape_crnl(quote" - "(case when (? AND field<0) then NULL else value end)" - "), ', ')" - ", min(field) " - "FROM sqlite_dbdata WHERE pgno = ? AND field != ?" - "GROUP BY cell", &pCells - ); +DISPATCHABLE_COMMAND( load ? 2 4 ){ + const char *zFile = 0, *zProc = 0; + char *zErrMsg = 0; + int ai = 1, rc; +#if SHELL_DYNAMIC_EXTENSION - u8 bLoadExt = 0; ++ u8 bLoadShellExt = 0; +#endif + if( ISS(p)->bSafeMode ) return DCR_AbortError; + while( aidb, &rc, - "SELECT root, intkey, max(maxlen) FROM recovery.map" - " WHERE root>1 GROUP BY root, intkey ORDER BY root=(" - " SELECT rootpage FROM recovery.schema WHERE name='sqlite_sequence'" - ")", &pLoop - ); - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){ - int iRoot = sqlite3_column_int(pLoop, 0); - int bIntkey = sqlite3_column_int(pLoop, 1); - int nCol = sqlite3_column_int(pLoop, 2); - int bNoop = 0; - RecoverTable *pTab; +DISPATCHABLE_COMMAND( log ? 2 2 ){ + const char *zFile = azArg[1]; + if( ISS(p)->bSafeMode ) return DCR_AbortError; + output_file_close(ISS(p)->pLog); + ISS(p)->pLog = output_file_open(zFile, 0); + return DCR_Ok; +} - assert( bIntkey==0 || bIntkey==1 ); - pTab = recoverFindTable(pState, &rc, iRoot, bIntkey, nCol, &bNoop); - if( bNoop || rc ) continue; - if( pTab==0 ){ - if( pOrphan==0 ){ - pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan); - } - pTab = pOrphan; - if( pTab==0 ) break; +static void effectMode(ShellInState *psi, u8 modeRequest, u8 modeNominal){ + /* Effect the specified mode change. */ + const char *zColSep = 0, *zRowSep = 0; + assert(modeNominal!=MODE_COUNT_OF); + switch( modeRequest ){ + case MODE_Line: + zRowSep = SEP_Row; + break; + case MODE_Column: + if( (psi->shellFlgs & SHFLG_HeaderSet)==0 ){ + psi->showHeader = 1; } + zRowSep = SEP_Row; + break; + case MODE_List: + zColSep = SEP_Column; + zRowSep = SEP_Row; + break; + case MODE_Html: + break; + case MODE_Tcl: + zColSep = SEP_Space; + zRowSep = SEP_Row; + break; + case MODE_Csv: + zColSep = SEP_Comma; + zRowSep = SEP_CrLf; + break; + case MODE_Tab: + zColSep = SEP_Tab; + break; + case MODE_Insert: + break; + case MODE_Quote: + zColSep = SEP_Comma; + zRowSep = SEP_Row; + break; + case MODE_Ascii: + zColSep = SEP_Unit; + zRowSep = SEP_Record; + break; + case MODE_Markdown: + /* fall-thru */ + case MODE_Table: + /* fall-thru */ + case MODE_Box: + break; + case MODE_Count: + /* fall-thru */ + case MODE_Off: + /* fall-thru */ + case MODE_Json: + break; + case MODE_Explain: case MODE_Semi: case MODE_EQP: case MODE_Pretty: default: + /* Modes used internally, not settable by .mode command. */ + return; + } + if( zRowSep!=0 ){ + sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, zRowSep); + } + if( zColSep!=0 ){ + sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator, zColSep); + } + psi->mode = modeNominal; +} - if( 0==sqlite3_stricmp(pTab->zQuoted, "\"sqlite_sequence\"") ){ - raw_printf(pState->out, "DELETE FROM sqlite_sequence;\n"); +/***************** + * The .mode command + */ +COLLECT_HELP_TEXT[ + ".mode MODE ?OPTIONS? Set output mode", + " MODE is one of:", + " ascii Columns/rows delimited by 0x1F and 0x1E", + " box Tables using unicode box-drawing characters", + " csv Comma-separated values", + " column Output in columns. (See .width)", + " html HTML
code", + " insert SQL insert statements for TABLE", + " json Results in a JSON array", + " line One value per line", + " list Values delimited by \"|\"", + " markdown Markdown table format", + " qbox Shorthand for \"box --width 60 --quote\"", + " quote Escape answers as for SQL", + " table ASCII-art table", + " tabs Tab-separated values", + " tcl TCL list elements", + " OPTIONS: (for columnar modes or insert mode):", + " --wrap N Wrap output lines to no longer than N characters", + " --wordwrap B Wrap or not at word boundaries per B (on/off)", + " --ww Shorthand for \"--wordwrap 1\"", + " --quote Quote output text as SQL literals", + " --noquote Do not quote output text", + " TABLE The name of SQL table used for \"insert\" mode", +]; +DISPATCHABLE_COMMAND( mode ? 1 0 ){ + ShellInState *psi = ISS(p); + const char *zTabname = 0; + const char *zArg; - int i; ++ int i, aix; + u8 foundMode = MODE_COUNT_OF, setMode = MODE_COUNT_OF; + ColModeOpts cmOpts = ColModeOpts_default; - for(i=1; iout; + const char *zMode; + int nms; + i = psi->mode; + assert(i>=0 && imode) ){ + raw_printf + (out, "current output mode: %.*s --wrap %d --wordwrap %s --%squote\n", + nms, zMode, psi->cmOpts.iWrap, + psi->cmOpts.bWordWrap ? "on" : "off", + psi->cmOpts.bQuote ? "" : "no"); + }else{ + raw_printf(out, "current output mode: %.*s\n", nms, zMode); } - sqlite3_bind_int(pPages, 1, iRoot); - if( bRowids==0 && pTab->iPk<0 ){ - sqlite3_bind_int(pCells, 1, 1); + }else{ + effectMode(psi, foundMode, setMode); + if( MODE_IS_COLUMNAR(setMode) ) psi->cmOpts = cmOpts; + else if( setMode==MODE_Insert ){ + set_table_name(p, zTabname ? zTabname : "table"); + } + } + psi->cMode = psi->mode; + return DCR_Ok; + flag_unknown: - utf8_printf(STD_ERR, "Error: Unknown .mode option: %s\nValid options:\n%s", - zArg, - " --noquote\n" - " --quote\n" - " --wordwrap on/off\n" - " --wrap N\n" - " --ww\n"); - return DCR_ArgError; ++ *pzErr = smprintf("Unknown .mode option: %s\nValid options:\n%s", ++ zArg, ++ " --noquote\n" ++ " --quote\n" ++ " --wordwrap on/off\n" ++ " --wrap N\n" ++ " --ww\n"); ++ return DCR_Unknown|aix; + mode_unknown: - raw_printf(STD_ERR, "Error: Mode should be one of: " - "ascii box column csv html insert json line list markdown " - "qbox quote table tabs tcl\n"); - return DCR_ArgError; ++ *pzErr = smprintf("Mode should be one of:\n" ++ " ascii box column csv html insert json line\n" ++ " list markdown qbox quote table tabs tcl\n"); ++ return DCR_Unknown|aix; + mode_badarg: - utf8_printf(STD_ERR, "Error: Invalid .mode argument: %s\n", zArg); - return DCR_ArgError; ++ *pzErr = smprintf("Invalid .mode argument: %s\n", zArg); ++ return DCR_ArgWrong|aix; +} + +/***************** + * The .open, .nonce and .nullvalue commands + */ +COLLECT_HELP_TEXT[ + ".open ?OPTIONS? ?FILE? Close existing database and reopen FILE", + " Options:", + " --append Use appendvfs to append database to the end of FILE", +#ifndef SQLITE_OMIT_DESERIALIZE + " --deserialize Load into memory using sqlite3_deserialize()", + " --hexdb Load the output of \"dbtotxt\" as an in-memory db", + " --maxsize N Maximum size for --hexdb or --deserialized database", +#endif + " --new Initialize FILE to an empty database", + " --nofollow Do not follow symbolic links", + " --readonly Open FILE readonly", + " --zip FILE is a ZIP archive", + ".nonce STRING Suspend safe mode for one command if nonce matches", + ".nullvalue STRING Use STRING in place of NULL values", +]; +DISPATCHABLE_COMMAND( open 3 1 0 ){ + ShellInState *psi = ISS(p); + const char *zFN = 0; /* Pointer to constant filename */ + char *zNewFilename = 0; /* Name of the database file to open */ + int iName = 1; /* Index in azArg[] of the filename */ + int newFlag = 0; /* True to delete file before opening */ + u8 openMode = SHELL_OPEN_UNSPEC; + int openFlags = 0; + sqlite3_int64 szMax = 0; + int rc = 0; + /* Check for command-line arguments */ + for(iName=1; iNamepAuxDb->zDbFilename = 0; + sqlite3_free(psi->pAuxDb->zFreeOnClose); + psi->pAuxDb->zFreeOnClose = 0; + psi->openMode = openMode; + psi->openFlags = 0; + psi->szMax = 0; + + /* If a filename is specified, try to open it first */ + if( zFN || psi->openMode==SHELL_OPEN_HEXDB ){ + if( newFlag && zFN && !psi->bSafeMode ) shellDeleteFile(zFN); + if( psi->bSafeMode + && psi->openMode!=SHELL_OPEN_HEXDB + && zFN + && strcmp(zFN,":memory:")!=0 + ){ - *pzErr = shellMPrintf(0,"cannot open database files in safe mode"); ++ *pzErr = smprintf("cannot open database files in safe mode"); + return DCR_AbortError; } - sqlite3_bind_int(pCells, 3, pTab->iPk); + if( zFN ){ - zNewFilename = sqlite3_mprintf("%s", zFN); ++ zNewFilename = smprintf("%s", zFN); + shell_check_oom(zNewFilename); + }else{ + zNewFilename = 0; + } + psi->pAuxDb->zDbFilename = zNewFilename; + psi->openFlags = openFlags; + psi->szMax = szMax; + open_db(p, OPEN_DB_KEEPALIVE); + if( DBX(p)==0 ){ - *pzErr = shellMPrintf(0,"Error: cannot open '%s'\n", zNewFilename); ++ *pzErr = smprintf("cannot open '%s'\n", zNewFilename); + sqlite3_free(zNewFilename); + rc = 1; + }else{ + psi->pAuxDb->zFreeOnClose = zNewFilename; + } + } + if( DBX(p)==0 ){ + /* As a fall-back open a TEMP database */ + psi->pAuxDb->zDbFilename = 0; + open_db(p, 0); + } - return rc; ++ return DCR_Ok|(rc!=0); +} - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPages) ){ - int iPgno = sqlite3_column_int(pPages, 0); - sqlite3_bind_int(pCells, 2, iPgno); - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pCells) ){ - int nField = sqlite3_column_int(pCells, 0); - int iMin = sqlite3_column_int(pCells, 2); - const char *zVal = (const char*)sqlite3_column_text(pCells, 1); +DISPATCHABLE_COMMAND( nonce ? 2 2 ){ + ShellInState *psi = ISS(p); + if( psi->zNonce==0 || strcmp(azArg[1],psi->zNonce)!=0 ){ + raw_printf(STD_ERR, "line %d: incorrect nonce: \"%s\"\n", + psi->pInSource->lineno, azArg[1]); + exit(1); + } + /* Suspend safe mode for 1 meta-command after this. */ + psi->bSafeModeFuture = 2; + return DCR_Ok; +} - RecoverTable *pTab2 = pTab; - if( pTab!=pOrphan && (iMin<0)!=bIntkey ){ - if( pOrphan==0 ){ - pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan); - } - pTab2 = pOrphan; - if( pTab2==0 ) break; - } +DISPATCHABLE_COMMAND( nullvalue ? 2 2 ){ + sqlite3_snprintf(sizeof(ISS(p)->nullValue), ISS(p)->nullValue, "%.*s", + (int)ArraySize(ISS(p)->nullValue)-1, azArg[1]); + return DCR_Ok; +} - nField = nField+1; - if( pTab2==pOrphan ){ - raw_printf(pState->out, - "INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n", - pTab2->zQuoted, iRoot, iPgno, nField, - iMin<0 ? "" : "NULL, ", zVal, pTab2->azlCol[nField] - ); - }else{ - raw_printf(pState->out, "INSERT INTO %s(%s) VALUES( %s );\n", - pTab2->zQuoted, pTab2->azlCol[nField], zVal - ); - } - } - shellReset(&rc, pCells); - } - shellReset(&rc, pPages); - if( pTab!=pOrphan ) recoverFreeTable(pTab); +/* Helper functions for .parameter command + */ + +struct param_row { char * value; int uses; int hits; }; + +static int param_find_callback(void *pData, int nc, char **pV, char **pC){ + assert(nc>=1); + assert(strcmp(pC[0],"value")==0); + struct param_row *pParam = (struct param_row *)pData; + assert(pParam->value==0); /* key values are supposedly unique. */ + if( pParam->value!=0 ) sqlite3_free( pParam->value ); - pParam->value = sqlite3_mprintf("%s", pV[0]); /* source owned by statement */ ++ pParam->value = smprintf("%s", pV[0]); /* source owned by statement */ + if( nc>1 ) pParam->uses = (int)integerValue(pV[1]); + ++pParam->hits; + return 0; +} + +static void append_in_clause(sqlite3_str *pStr, + const char **azBeg, const char **azLim); +static void append_glob_terms(sqlite3_str *pStr, const char *zColName, + const char **azBeg, const char **azLim); +static char *find_home_dir(int clearFlag); + +/* Create a home-relative pathname from ~ prefixed path. + * Return it, or 0 for any error. + * Caller must sqlite3_free() it. + */ +static char *home_based_path( const char *zPath ){ + char *zHome = find_home_dir(0); + char *zErr = 0; + assert( zPath[0]=='~' ); + if( zHome==0 ){ + zErr = "Cannot find home directory."; + }else if( zPath[0]==0 || (zPath[1]!='/' +#if defined(_WIN32) || defined(WIN32) + && zPath[1]!='\\' +#endif + ) ){ + zErr = "Malformed pathname"; + }else{ - return sqlite3_mprintf("%s%s", zHome, zPath+1); ++ return smprintf("%s%s", zHome, zPath+1); } - shellFinalize(&rc, pLoop); - shellFinalize(&rc, pPages); - shellFinalize(&rc, pCells); - recoverFreeTable(pOrphan); + utf8_printf(STD_ERR, "Error: %s\n", zErr); + return 0; +} - /* The rest of the schema */ - if( rc==SQLITE_OK ){ - sqlite3_stmt *pStmt = 0; - shellPrepare(pState->db, &rc, - "SELECT sql, name FROM recovery.schema " - "WHERE sql NOT LIKE 'create table%'", &pStmt - ); - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ - const char *zSql = (const char*)sqlite3_column_text(pStmt, 0); - if( sqlite3_strnicmp(zSql, "create virt", 11)==0 ){ - const char *zName = (const char*)sqlite3_column_text(pStmt, 1); - char *zPrint = shellMPrintf(&rc, - "INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)", - zName, zName, zSql - ); - raw_printf(pState->out, "%s;\n", zPrint); - sqlite3_free(zPrint); - }else{ - raw_printf(pState->out, "%s;\n", zSql); +/* Transfer selected parameters between two parameter tables, for save/load. + * Argument bSaveNotLoad determines transfer direction and other actions. + * If it is true, the store DB will be created if not existent, and its + * table for keeping parameters will be created. Or failure is returned. + * If it is false, the store DB will be opened for read and its presumed + * table for keeping parameters will be read. Or failure is returned. + * + * Arguments azNames and nNames reference the ?NAMES? save/load arguments. + * If it is an empty list, all parameters will be saved or loaded. + * Otherwise, only the named parameters are transferred, if they exist. + * It is not an error to specify a name that cannot be transferred + * because it does not exist in the source table. + * + * Returns are SQLITE_OK for success, or other codes for failure. + */ +static int param_xfr_table(sqlite3 *db, const char *zStoreDbName, + int bSaveNotLoad, const char *azNames[], int nNames){ + int rc = 0; + char *zSql = 0; /* to be sqlite3_free()'ed */ + sqlite3_str *sbCopy = 0; + const char *zHere = PARAM_TABLE_SNAME; + const char *zThere = PARAM_STORE_SNAME; + const char *zTo = (bSaveNotLoad)? zThere : zHere; + const char *zFrom = (bSaveNotLoad)? zHere : zThere; + sqlite3 *dbStore = 0; + int openFlags = (bSaveNotLoad) + ? SQLITE_OPEN_URI|SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE + : SQLITE_OPEN_READONLY; + + /* Ensure store DB can be opened and/or created appropriately. */ + rc = sqlite3_open_v2(zStoreDbName, &dbStore, openFlags, 0); + if( rc!=SQLITE_OK ){ + utf8_printf(STD_ERR, "Error: Cannot %s parameter store DB %s\n", + bSaveNotLoad? "open/create" : "read", zStoreDbName); + return rc; + } + /* Ensure it has the parameter store table, or handle its absence. */ + assert(dbStore!=0); + if( sqlite3_table_column_metadata + (dbStore, "main", PARAM_STORE_NAME, 0, 0, 0, 0, 0, 0)!=SQLITE_OK ){ + if( !bSaveNotLoad ){ + utf8_printf(STD_ERR, "Error: No parameters ever stored in DB %s\n", + zStoreDbName); + rc = 1; + }else{ + /* The saved parameters table is not there yet; create it. */ + const char *zCT = + "CREATE TABLE IF NOT EXISTS "PARAM_STORE_NAME"(\n" + " key TEXT PRIMARY KEY,\n" + " value,\n" + " uses INT\n" + ") WITHOUT ROWID;"; + rc = sqlite3_exec(dbStore, zCT, 0, 0, 0); + if( rc!=SQLITE_OK ){ + utf8_printf(STD_ERR, "Cannot create table %s. Nothing saved.", zThere); } } - shellFinalize(&rc, pStmt); } + sqlite3_close(dbStore); + if( rc!=0 ) return rc; - zSql = sqlite3_mprintf("ATTACH %Q AS %s;", zStoreDbName, PARAM_STORE_SCHEMA); - if( rc==SQLITE_OK ){ - raw_printf(pState->out, "PRAGMA writable_schema = off;\n"); - raw_printf(pState->out, "COMMIT;\n"); - } - sqlite3_exec(pState->db, "DETACH recovery", 0, 0, 0); ++ zSql = smprintf("ATTACH %Q AS %s;", zStoreDbName, PARAM_STORE_SCHEMA); + shell_check_oom(zSql); + rc = sqlite3_exec(db, zSql, 0, 0, 0); + sqlite3_free(zSql); + if( rc!=SQLITE_OK ) return rc; + + sbCopy = sqlite3_str_new(db); + sqlite3_str_appendf + (sbCopy, "INSERT OR REPLACE INTO %s(key,value,uses)" + "SELECT key, value, uses FROM %s WHERE key ", zTo, zFrom); + append_in_clause(sbCopy, azNames, azNames+nNames); + zSql = sqlite3_str_finish(sbCopy); + shell_check_oom(zSql); + rc = sqlite3_exec(db, zSql, 0, 0, 0); + sqlite3_free(zSql); + + sqlite3_exec(db, "DETACH "PARAM_STORE_SCHEMA";", 0, 0, 0); return rc; } -#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */ +/* Default location of parameters store DB for .parameters save/load. */ +static const char *zDefaultParamStore = "~/sqlite_params.sdb"; -/* - * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it. - * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE, - * close db and set it to 0, and return the columns spec, to later - * be sqlite3_free()'ed by the caller. - * The return is 0 when either: - * (a) The db was not initialized and zCol==0 (There are no columns.) - * (b) zCol!=0 (Column was added, db initialized as needed.) - * The 3rd argument, pRenamed, references an out parameter. If the - * pointer is non-zero, its referent will be set to a summary of renames - * done if renaming was necessary, or set to 0 if none was done. The out - * string (if any) must be sqlite3_free()'ed by the caller. +/* Possibly generate a derived path from input spec, with defaulting + * and conversion of leading (or only) tilde as home directory. + * The above-set default is used for zSpec NULL, "" or "~". + * When return is 0, there is an error; what needs doing cannnot be done. + * If the return is exactly the input, it must not be sqlite3_free()'ed. + * If the return differs from the input, it must be sqlite3_free()'ed. */ -#ifdef SHELL_DEBUG -#define rc_err_oom_die(rc) \ - if( rc==SQLITE_NOMEM ) shell_check_oom(0); \ - else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \ - fprintf(stderr,"E:%d\n",rc), assert(0) -#else -static void rc_err_oom_die(int rc){ - if( rc==SQLITE_NOMEM ) shell_check_oom(0); - assert(rc==SQLITE_OK||rc==SQLITE_DONE); +static const char *params_store_path(const char *zSpec){ + if( zSpec==0 || zSpec[0]==0 || strcmp(zSpec,"~")==0 ){ + return home_based_path(zDefaultParamStore); + }else if ( zSpec[0]=='~' ){ + return home_based_path(zSpec); + } + return zSpec; } -#endif -#ifdef SHELL_COLFIX_DB /* If this is set, the DB can be in a file. */ -static char zCOL_DB[] = SHELL_STRINGIFY(SHELL_COLFIX_DB); -#else /* Otherwise, memory is faster/better for the transient DB. */ -static const char *zCOL_DB = ":memory:"; -#endif +/* Load some or all parameters. Arguments are "load FILE ?NAMES?". */ +static int parameters_load(sqlite3 *db, const char *azArg[], int nArg){ + const char *zStore = params_store_path((nArg>1)? azArg[1] : 0); + if( zStore==0 ){ + utf8_printf(STD_ERR, "Cannot form parameter load path. Nothing loaded.\n"); + return DCR_Error; + }else{ + const char **pzFirst = (nArg>2)? azArg+2 : 0; + int nNames = (nArg>2)? nArg-2 : 0; + int rc = param_xfr_table(db, zStore, 0, pzFirst, nNames); + if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore); + return rc; + } +} -/* Define character (as C string) to separate generated column ordinal - * from protected part of incoming column names. This defaults to "_" - * so that incoming column identifiers that did not need not be quoted - * remain usable without being quoted. It must be one character. - */ -#ifndef SHELL_AUTOCOLUMN_SEP -# define AUTOCOLUMN_SEP "_" -#else -# define AUTOCOLUMN_SEP SHELL_STRINGIFY(SHELL_AUTOCOLUMN_SEP) -#endif +/* Save some or all parameters. Arguments are "save FILE ?NAMES?". */ +static int parameters_save(sqlite3 *db, const char *azArg[], int nArg){ + const char *zStore = params_store_path((nArg>1)? azArg[1] : 0); + if( zStore==0 ){ + utf8_printf(STD_ERR, "Cannot form parameter save path. Nothing saved.\n"); + return DCR_Error; + }else{ + const char **pzFirst = (nArg>2)? azArg+2 : 0; + int nNames = (nArg>2)? nArg-2 : 0; + int rc = param_xfr_table(db, zStore, 1, pzFirst, nNames); + if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore); + return rc; + } +} -static char *zAutoColumn(const char *zColNew, sqlite3 **pDb, char **pzRenamed){ - /* Queries and D{D,M}L used here */ - static const char * const zTabMake = "\ -CREATE TABLE ColNames(\ - cpos INTEGER PRIMARY KEY,\ - name TEXT, nlen INT, chop INT, reps INT, suff TEXT);\ -CREATE VIEW RepeatedNames AS \ -SELECT DISTINCT t.name FROM ColNames t \ -WHERE t.name COLLATE NOCASE IN (\ - SELECT o.name FROM ColNames o WHERE o.cpos<>t.cpos\ -);\ -"; - static const char * const zTabFill = "\ -INSERT INTO ColNames(name,nlen,chop,reps,suff)\ - VALUES(iif(length(?1)>0,?1,'?'),max(length(?1),1),0,0,'')\ -"; - static const char * const zHasDupes = "\ -SELECT count(DISTINCT (substring(name,1,nlen-chop)||suff) COLLATE NOCASE)\ - 1, printf('%c%0*d', '"AUTOCOLUMN_SEP"', $1, cpos), '')" -#else /* ...RENAME_MINIMAL_ONE_PASS */ -"WITH Lzn(nlz) AS (" /* Find minimum extraneous leading 0's for uniqueness */ -" SELECT 0 AS nlz" -" UNION" -" SELECT nlz+1 AS nlz FROM Lzn" -" WHERE EXISTS(" -" SELECT 1" -" FROM ColNames t, ColNames o" -" WHERE" -" iif(t.name IN (SELECT * FROM RepeatedNames)," -" printf('%s"AUTOCOLUMN_SEP"%s'," -" t.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,t.cpos),2))," -" t.name" -" )" -" =" -" iif(o.name IN (SELECT * FROM RepeatedNames)," -" printf('%s"AUTOCOLUMN_SEP"%s'," -" o.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,o.cpos),2))," -" o.name" -" )" -" COLLATE NOCASE" -" AND o.cpos<>t.cpos" -" GROUP BY t.cpos" -" )" -") UPDATE Colnames AS t SET" -" chop = 0," /* No chopping, never touch incoming names. */ -" suff = iif(name IN (SELECT * FROM RepeatedNames)," -" printf('"AUTOCOLUMN_SEP"%s', substring(" -" printf('%.*c%0.*d',(SELECT max(nlz) FROM Lzn)+1,'0',1,t.cpos),2))," -" ''" -" )" -#endif - ; - static const char * const zCollectVar = "\ -SELECT\ - '('||x'0a'\ - || group_concat(\ - cname||' TEXT',\ - ','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\ - ||')' AS ColsSpec \ -FROM (\ - SELECT cpos, printf('\"%w\"',printf('%.*s%s', nlen-chop,name,suff)) AS cname \ - FROM ColNames ORDER BY cpos\ -)"; - static const char * const zRenamesDone = - "SELECT group_concat(" - " printf('\"%w\" to \"%w\"',name,printf('%.*s%s', nlen-chop, name, suff))," - " ','||x'0a')" - "FROM ColNames WHERE suff<>'' OR chop!=0" - ; +#ifndef SQLITE_NOHAVE_SYSTEM +/* + * Edit one named parameter in the parameters table. If it does not + * yet exist, create it. If eval is true, the value is treated as a + * bare expression, otherwise it is a text value. The uses argument + * sets the 3rd column in the parameters table, and may also serve + * to partition the key namespace. (This is not done now.) + */ +static int edit_one_param(sqlite3 *db, char *name, int eval, + ParamTableUse uses, const char * zEditor){ + struct param_row paramVU = {0,0,0}; int rc; - sqlite3_stmt *pStmt = 0; - assert(pDb!=0); - if( zColNew ){ - /* Add initial or additional column. Init db if necessary. */ - if( *pDb==0 ){ - if( SQLITE_OK!=sqlite3_open(zCOL_DB, pDb) ) return 0; -#ifdef SHELL_COLFIX_DB - if(*zCOL_DB!=':') - sqlite3_exec(*pDb,"drop table if exists ColNames;" - "drop view if exists RepeatedNames;",0,0,0); -#endif - rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0); - rc_err_oom_die(rc); + char * zVal = 0; - char * zSql = sqlite3_mprintf ++ char * zSql = smprintf + ("SELECT value, uses FROM " PARAM_TABLE_SNAME " WHERE key=%Q", name); + shell_check_oom(zSql); + sqlite3_exec(db, zSql, param_find_callback, ¶mVU, 0); + sqlite3_free(zSql); + assert(paramVU.hits<2); + if( paramVU.hits==1 && paramVU.uses==uses){ + /* Editing an existing value of same kind. */ + sqlite3_free(paramVU.value); + if( eval!=0 ){ - zSql = sqlite3_mprintf ++ zSql = smprintf + ("SELECT edit(value, %Q) FROM " PARAM_TABLE_SNAME + " WHERE key=%Q AND uses=%d", zEditor, name, uses); + zVal = db_text(db, zSql, 1); + sqlite3_free(zSql); - zSql = sqlite3_mprintf ++ zSql = smprintf + ("UPDATE "PARAM_TABLE_SNAME" SET value=(SELECT %s) WHERE" + " key=%Q AND uses=%d", zVal, name, uses); + }else{ - zSql = sqlite3_mprintf ++ zSql = smprintf + ("UPDATE "PARAM_TABLE_SNAME" SET value=edit(value, %Q) WHERE" + " key=%Q AND uses=%d", zEditor, name, uses); } - assert(*pDb!=0); - rc = sqlite3_prepare_v2(*pDb, zTabFill, -1, &pStmt, 0); - rc_err_oom_die(rc); - rc = sqlite3_bind_text(pStmt, 1, zColNew, -1, 0); - rc_err_oom_die(rc); - rc = sqlite3_step(pStmt); - rc_err_oom_die(rc); - sqlite3_finalize(pStmt); - return 0; - }else if( *pDb==0 ){ - return 0; }else{ - /* Formulate the columns spec, close the DB, zero *pDb. */ - char *zColsSpec = 0; - int hasDupes = db_int(*pDb, zHasDupes); -#ifdef SQLITE_ENABLE_MATH_FUNCTIONS - int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0; -#else -# define nDigits 2 -#endif - if( hasDupes ){ -#ifdef SHELL_COLUMN_RENAME_CLEAN - rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0); - rc_err_oom_die(rc); -#endif - rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0); - rc_err_oom_die(rc); - rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0); - rc_err_oom_die(rc); - sqlite3_bind_int(pStmt, 1, nDigits); - rc = sqlite3_step(pStmt); - sqlite3_finalize(pStmt); - assert(rc==SQLITE_DONE); - } - assert(db_int(*pDb, zHasDupes)==0); /* Consider: remove this */ - rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0); - rc_err_oom_die(rc); - rc = sqlite3_step(pStmt); - if( rc==SQLITE_ROW ){ - zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); + /* Editing a new value of same kind. */ + assert(paramVU.value==0 || paramVU.uses!=uses); + if( eval!=0 ){ - zSql = sqlite3_mprintf ++ zSql = smprintf + ("SELECT edit('-- %q%s', %Q)", name, "\n", zEditor); + zVal = db_text(db, zSql, 1); + sqlite3_free(zSql); - zSql = sqlite3_mprintf ++ zSql = smprintf + ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)" + " VALUES (%Q,(SELECT %s LIMIT 1),%d)", + name, zVal, uses); }else{ - zSql = sqlite3_mprintf - zColsSpec = 0; - } - if( pzRenamed!=0 ){ - if( !hasDupes ) *pzRenamed = 0; - else{ - sqlite3_finalize(pStmt); - if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0) - && SQLITE_ROW==sqlite3_step(pStmt) ){ - *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); - }else - *pzRenamed = 0; - } ++ zSql = smprintf + ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)" + " VALUES (%Q,edit('-- %q%s', %Q),%d)", + name, name, "\n", zEditor, uses); } - sqlite3_finalize(pStmt); - sqlite3_close(*pDb); - *pDb = 0; - return zColsSpec; } -} - -/* -** If an input line begins with "." then invoke this routine to -** process that line. -** -** Return 1 on error, 2 to exit, and 0 otherwise. -*/ -static int do_meta_command(char *zLine, ShellState *p){ - int h = 1; - int nArg = 0; - int n, c; - int rc = 0; - char *azArg[52]; - -#ifndef SQLITE_OMIT_VIRTUALTABLE - if( p->expert.pExpert ){ - expertFinish(p, 1, 0); + shell_check_oom(zSql); + if( eval!=0 ){ } + rc = sqlite3_exec(db, zSql, 0, 0, 0); + sqlite3_free(zSql); + sqlite3_free(zVal); + return rc!=SQLITE_OK; +} #endif - /* Parse the input line into tokens. - */ - while( zLine[h] && nArg1)? values_join(valBeg, valLim) : 0; + sqlite3_stmt *pStmtSet = 0; + const char *zCastTo = 0; + char *zValue = (zValGlom==0)? *valBeg : zValGlom; + if( cCast ){ + struct ParamSetOpts *pSO = param_set_opts; + for(; pSO-param_set_opts < ArraySize(param_set_opts); ++pSO ){ + if( cCast==pSO->cCast ){ + zCastTo = pSO->zTypename; + needsEval = pSO->evalKind > 0; + break; + } } - open_db(p, 0); - if( booleanValue(azArg[1]) ){ - sqlite3_set_authorizer(p->db, shellAuth, p); - }else if( p->bSafeModePersist ){ - sqlite3_set_authorizer(p->db, safeModeAuth, p); + } + if( needsEval ){ + if( zCastTo!=0 ){ - zSql = sqlite3_mprintf ++ zSql = smprintf + ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" + " VALUES(%Q,CAST((%s) AS %s),%d);", name, zValue, zCastTo, ptu ); }else{ - zSql = sqlite3_mprintf - sqlite3_set_authorizer(p->db, 0, 0); ++ zSql = smprintf + ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" + "VALUES(%Q,(%s),%d);", name, zValue, ptu ); } - }else -#endif - -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) - if( c=='a' && strncmp(azArg[0], "archive", n)==0 ){ - open_db(p, 0); - failIfSafeMode(p, "cannot run .archive in safe mode"); - rc = arDotCommand(p, 0, azArg, nArg); - }else -#endif + shell_check_oom(zSql); + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0); + sqlite3_free(zSql); + } + if( !needsEval || rc!=SQLITE_OK ){ + /* Reach here when value either requested to be cast to text, or must be. */ + sqlite3_finalize(pStmtSet); + pStmtSet = 0; - zSql = sqlite3_mprintf ++ zSql = smprintf + ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" + "VALUES(%Q,%Q,%d);", name, zValue, ptu ); + shell_check_oom(zSql); + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0); + assert(rc==SQLITE_OK); + sqlite3_free(zSql); + } + sqlite3_step(pStmtSet); + sqlite3_finalize(pStmtSet); + sqlite3_free(zValGlom); + return rc; +} - if( (c=='b' && n>=3 && strncmp(azArg[0], "backup", n)==0) - || (c=='s' && n>=3 && strncmp(azArg[0], "save", n)==0) - ){ - const char *zDestFile = 0; - const char *zDb = 0; - sqlite3 *pDest; - sqlite3_backup *pBackup; - int j; - int bAsync = 0; - const char *zVfs = 0; - failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]); - for(j=1; j40 ) len = 40; + if( len<4 ) len = 4; + } + } + sqlite3_finalize(pStmt); + pStmt = 0; + if( len ){ + FILE *out = ISS(psx)->out; + sqlite3_free(zSql); + if( !bShort ){ + int nBindings = 0, nScripts = 0; - zSql = sqlite3_mprintf("SELECT key, uses, iif(uses, value, quote(value))" - " %z ORDER BY uses, key", zFromWhere); ++ zSql = smprintf("SELECT key, uses, iif(uses, value, quote(value))" ++ " %z ORDER BY uses, key", zFromWhere); + shell_check_oom(zSql); + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + sqlite3_bind_int(pStmt, 1, ptu); + while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ + ParamTableUse ptux = sqlite3_column_int(pStmt,1); + switch( ptux ){ + case PTU_Binding: + if( nBindings++ == 0 ){ + utf8_printf(out, "Binding Values:\n%-*s %s\n", + len, "name", "value"); + } + utf8_printf(out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0), + sqlite3_column_text(pStmt,2)); + break; + case PTU_Script: + if( nScripts++ == 0 ){ + utf8_printf(out, "Scripts\n%-*s %s\n", len, "name", "value"); + } + utf8_printf(out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0), + sqlite3_column_text(pStmt,2)); + break; + default: break; /* Ignore */ } - }else if( zDestFile==0 ){ - zDestFile = azArg[j]; - }else if( zDb==0 ){ - zDb = zDestFile; - zDestFile = azArg[j]; - }else{ - raw_printf(stderr, "Usage: .backup ?DB? ?OPTIONS? FILENAME\n"); - return 1; } - } - if( zDestFile==0 ){ - raw_printf(stderr, "missing FILENAME argument on .backup\n"); - return 1; - } - if( zDb==0 ) zDb = "main"; - rc = sqlite3_open_v2(zDestFile, &pDest, - SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs); - if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", zDestFile); - close_db(pDest); - return 1; - } - if( bAsync ){ - sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;", - 0, 0, 0); - } - open_db(p, 0); - pBackup = sqlite3_backup_init(pDest, "main", p->db, zDb); - if( pBackup==0 ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest)); - close_db(pDest); - return 1; - } - while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){} - sqlite3_backup_finish(pBackup); - if( rc==SQLITE_DONE ){ - rc = 0; }else{ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest)); - rc = 1; + int nc = 0, ncw = 78/(len+2); - zSql = sqlite3_mprintf("SELECT key %z ORDER BY key", zFromWhere); ++ zSql = smprintf("SELECT key %z ORDER BY key", zFromWhere); + shell_check_oom(zSql); + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + sqlite3_bind_int(pStmt, 1, ptu); + while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ + utf8_printf(out, "%s %-*s", ((++nc%ncw==0)? "\n" : ""), + len, sqlite3_column_text(pStmt,0)); + } + if( nc>0 ) utf8_printf(out, "\n"); } - close_db(pDest); - }else + sqlite3_finalize(pStmt); + }else{ + sqlite3_free(zFromWhere); + } + sqlite3_free(zSql); +} - if( c=='b' && n>=3 && strncmp(azArg[0], "bail", n)==0 ){ - if( nArg==2 ){ - bail_on_error = booleanValue(azArg[1]); - }else{ - raw_printf(stderr, "Usage: .bail on|off\n"); - rc = 1; +/* Append an OR'ed series of GLOB terms comparing a given column + * name to a series of patterns. Result is an appended expression. + * For an empty pattern series, expression is true for non-NULL. + */ +static void append_glob_terms(sqlite3_str *pStr, const char *zColName, + const char **azBeg, const char **azLim){ + if( azBeg==azLim ) sqlite3_str_appendf(pStr, "%s NOT NULL", zColName); + else{ + char *zSep = "("; + while( azBeg=3 && strncmp(azArg[0], "binary", n)==0 ){ - if( nArg==2 ){ - if( booleanValue(azArg[1]) ){ - setBinaryMode(p->out, 1); - }else{ - setTextMode(p->out, 1); - } - }else{ - raw_printf(stderr, "Usage: .binary on|off\n"); - rc = 1; - } - }else +/***************** + * The .parameter command + */ +COLLECT_HELP_TEXT[ + ".parameter CMD ... Manage SQL parameter bindings and scripts table", + " clear ?NAMES? Erase all or only given named parameters", +#ifndef SQLITE_NOHAVE_SYSTEM + " edit ?OPT? NAME ... Use edit() to create or alter parameter NAME", + " OPT may be -t to use edited value as text or -e to evaluate it.", +#endif + " init Initialize TEMP table for bindings and scripts", + " list ?PATTERNS? List parameters table binding and script values", + " Alternatively, to list just some or all names: ls ?PATTERNS?", + " load ?FILE? ?NAMES? Load some or all named parameters from FILE", + " If FILE missing, empty or '~', it defaults to ~/sqlite_params.sdb", + " save ?FILE? ?NAMES? Save some or all named parameters into FILE", + " If FILE missing, empty or '~', it defaults to ~/sqlite_params.sdb", + " set ?TOPT? NAME VALUE Give SQL parameter NAME a value of VALUE", + " NAME must begin with one of $,:,@,? for bindings, or with a letter", + " to be executable; value is the space-joined argument list.", + " Option TOPT may be one of {-b -i -n -r -t} to cast effective value", + " to BLOB, INT, NUMERIC, REAL or TEXT respectively.", + " unset ?NAMES? Remove named parameter(s) from parameters table", +]; +DISPATCHABLE_COMMAND( parameter 2 2 0 ){ + int rc = 0; + open_db(p,0); + sqlite3 *db = DBX(p); - /* The undocumented ".breakpoint" command causes a call to the no-op - ** routine named test_breakpoint(). + /* .parameter clear and .parameter unset ?NAMES? + ** Delete some or all parameters from the TEMP table that holds them. + ** Without any arguments, clear deletes them all and unset does nothing. */ - if( c=='b' && n>=3 && strncmp(azArg[0], "breakpoint", n)==0 ){ - test_breakpoint(); + if( strcmp(azArg[1],"clear")==0 || strcmp(azArg[1],"unset")==0 ){ + if( param_table_exists(db) && (nArg>2 || azArg[1][0]=='c') ){ + sqlite3_str *sbZap = sqlite3_str_new(db); + char *zSql; + sqlite3_str_appendf + (sbZap, "DELETE FROM "PARAM_TABLE_SNAME" WHERE key "); + append_in_clause(sbZap, + (const char **)&azArg[2], (const char **)&azArg[nArg]); + zSql = sqlite3_str_finish(sbZap); + shell_check_oom(zSql); + sqlite3_exec(db, zSql, 0, 0, 0); + sqlite3_free(zSql); + } }else - - if( c=='c' && strcmp(azArg[0],"cd")==0 ){ - failIfSafeMode(p, "cannot run .cd in safe mode"); - if( nArg==2 ){ -#if defined(_WIN32) || defined(WIN32) - wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]); - rc = !SetCurrentDirectoryW(z); - sqlite3_free(z); -#else - rc = chdir(azArg[1]); -#endif - if( rc ){ - utf8_printf(stderr, "Cannot change to directory \"%s\"\n", azArg[1]); - rc = 1; +#ifndef SQLITE_NOHAVE_SYSTEM + /* .parameter edit ?NAMES? + ** Edit the named parameters. Any that do not exist are created. + ** New ones get a uses tag auto-selected by their leading char. + */ + if( strcmp(azArg[1],"edit")==0 ){ + ShellInState *psi = ISS(p); + int ia = 2; + int eval = 0; + if( !INSOURCE_IS_INTERACTIVE(psi->pInSource) ){ + utf8_printf(STD_ERR, "Error: " + ".parameter edit can only be used interactively.\n"); + return DCR_Error; + } + param_table_init(db); + if( psi->zEditor==0 ){ + const char *zE = getenv("VISUAL"); - if( zE!=0 ) psi->zEditor = sqlite3_mprintf("%s", zE); ++ if( zE!=0 ) psi->zEditor = smprintf("%s", zE); + } + if( nArg>=3 && azArg[2][0]=='-' ){ + char *zArg = (azArg[2][1]=='-')? azArg[2]+2 : azArg[2]+1; + if( strncmp(zArg,"editor=",7)==0 ){ + sqlite3_free(psi->zEditor); + /* Accept an initial -editor=? option. */ - psi->zEditor = sqlite3_mprintf("%s", zArg+7); ++ psi->zEditor = smprintf("%s", zArg+7); + ++ia; + } + } + if( psi->zEditor==0 ){ + utf8_printf(STD_ERR, + "Either set env-var VISUAL to name an" + " editor and restart, or rerun\n " + ".parameter edit with an initial " + "edit option, --editor=EDITOR_COMMAND .\n"); + return DCR_Error; + } + /* Future: Allow an option whereby new value can be evaluated + * the way that .parameter set ... does. + */ + while( ia < nArg ){ + ParamTableUse ptu; + char cf = (azArg[ia][0]=='-')? azArg[ia][1] : 0; + if( cf!=0 && azArg[ia][2]==0 ){ + ++ia; + switch( cf ){ + case 'e': eval = 1; continue; + case 't': eval = 0; continue; + default: + utf8_printf(STD_ERR, "Error: bad .parameter name: %s\n", + azArg[--ia]); + return DCR_Error; + } } - }else{ - raw_printf(stderr, "Usage: .cd DIRECTORY\n"); - rc = 1; + ptu = classify_param_name(azArg[ia]); + if( ptu==PTU_Nil ){ + utf8_printf(STD_ERR, "Error: %s cannot be a binding or executable" + " parameter name.\n", azArg[ia]); + return DCR_Error; + } + rc = edit_one_param(db, azArg[ia], eval, ptu, psi->zEditor); + ++ia; + if( rc!=0 ) return rc; } }else +#endif - if( c=='c' && n>=3 && strncmp(azArg[0], "changes", n)==0 ){ - if( nArg==2 ){ - setOrClearFlag(p, SHFLG_CountChanges, azArg[1]); - }else{ - raw_printf(stderr, "Usage: .changes on|off\n"); - rc = 1; - } + /* .parameter init + ** Make sure the TEMP table used to hold bind parameters exists. + ** Create it if necessary. + */ + if( nArg==2 && strcmp(azArg[1],"init")==0 ){ + param_table_init(db); }else - /* Cancel output redirection, if it is currently set (by .testcase) - ** Then read the content of the testcase-out.txt file and compare against - ** azArg[1]. If there are differences, report an error and exit. + /* .parameter list|ls + ** List all or selected bind parameters. + ** list displays names, values and uses. + ** ls displays just the names. */ - if( c=='c' && n>=3 && strncmp(azArg[0], "check", n)==0 ){ - char *zRes = 0; - output_reset(p); - if( nArg!=2 ){ - raw_printf(stderr, "Usage: .check GLOB-PATTERN\n"); - rc = 2; - }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){ - raw_printf(stderr, "Error: cannot read 'testcase-out.txt'\n"); - rc = 2; - }else if( testcase_glob(azArg[1],zRes)==0 ){ - utf8_printf(stderr, - "testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n", - p->zTestcase, azArg[1], zRes); - rc = 1; - }else{ - utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase); - p->nCheck++; - } - sqlite3_free(zRes); + if( nArg>=2 && ((strcmp(azArg[1],"list")==0) + || (strcmp(azArg[1],"ls")==0)) ){ + list_params(p, PTU_Nil, azArg[1][1]=='s', azArg+2, nArg-2); }else - if( c=='c' && strncmp(azArg[0], "clone", n)==0 ){ - failIfSafeMode(p, "cannot run .clone in safe mode"); - if( nArg==2 ){ - tryToClone(p, azArg[1]); - }else{ - raw_printf(stderr, "Usage: .clone FILENAME\n"); - rc = 1; - } + /* .parameter load + ** Load all or named parameters from specified or default (DB) file. + */ + if( strcmp(azArg[1],"load")==0 ){ + param_table_init(db); + rc = parameters_load(db, (const char **)azArg+1, nArg-1); }else - if( c=='c' && strncmp(azArg[0], "connection", n)==0 ){ - if( nArg==1 ){ - /* List available connections */ - int i; - for(i=0; iaAuxDb); i++){ - const char *zFile = p->aAuxDb[i].zDbFilename; - if( p->aAuxDb[i].db==0 && p->pAuxDb!=&p->aAuxDb[i] ){ - zFile = "(not open)"; - }else if( zFile==0 ){ - zFile = "(memory)"; - }else if( zFile[0]==0 ){ - zFile = "(temporary-file)"; - } - if( p->pAuxDb == &p->aAuxDb[i] ){ - utf8_printf(stdout, "ACTIVE %d: %s\n", i, zFile); - }else if( p->aAuxDb[i].db!=0 ){ - utf8_printf(stdout, " %d: %s\n", i, zFile); - } - } - }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){ - int i = azArg[1][0] - '0'; - if( p->pAuxDb != &p->aAuxDb[i] && i>=0 && iaAuxDb) ){ - p->pAuxDb->db = p->db; - p->pAuxDb = &p->aAuxDb[i]; - globalDb = p->db = p->pAuxDb->db; - p->pAuxDb->db = 0; - } - }else if( nArg==3 && strcmp(azArg[1], "close")==0 - && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){ - int i = azArg[2][0] - '0'; - if( i<0 || i>=ArraySize(p->aAuxDb) ){ - /* No-op */ - }else if( p->pAuxDb == &p->aAuxDb[i] ){ - raw_printf(stderr, "cannot close the active database connection\n"); - rc = 1; - }else if( p->aAuxDb[i].db ){ - session_close_all(p, i); - close_db(p->aAuxDb[i].db); - p->aAuxDb[i].db = 0; - } - }else{ - raw_printf(stderr, "Usage: .connection [close] [CONNECTION-NUMBER]\n"); - rc = 1; - } + /* .parameter save + ** Save all or named parameters into specified or default (DB) file. + */ + if( strcmp(azArg[1],"save")==0 ){ + rc = parameters_save(db, (const char **)azArg+1, nArg-1); }else - if( c=='d' && n>1 && strncmp(azArg[0], "databases", n)==0 ){ - char **azName = 0; - int nName = 0; - sqlite3_stmt *pStmt; - int i; - open_db(p, 0); - rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); - if( rc ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); + /* .parameter set NAME VALUE + ** Set or reset a bind parameter. NAME should be the full parameter + ** name exactly as it appears in the query. (ex: $abc, @def). The + ** VALUE can be in either SQL literal notation, or if not it will be + ** understood to be a text string. + */ + if( nArg>=4 && strcmp(azArg[1],"set")==0 ){ + char cCast = option_char(azArg[2]); + int inv = 2 + (cCast != 0); + ParamTableUse ptu = classify_param_name(azArg[inv]); + if( ptu==PTU_Nil ){ + utf8_printf(STD_ERR, + "Error: %s is not a usable parameter name.\n", azArg[inv]); rc = 1; }else{ - while( sqlite3_step(pStmt)==SQLITE_ROW ){ - const char *zSchema = (const char *)sqlite3_column_text(pStmt,1); - const char *zFile = (const char*)sqlite3_column_text(pStmt,2); - if( zSchema==0 || zFile==0 ) continue; - azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*)); - shell_check_oom(azName); - azName[nName*2] = strdup(zSchema); - azName[nName*2+1] = strdup(zFile); - nName++; - } - } - sqlite3_finalize(pStmt); - for(i=0; idb, azName[i*2]); - int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]); - const char *z = azName[i*2+1]; - utf8_printf(p->out, "%s: %s %s%s\n", - azName[i*2], - z && z[0] ? z : "\"\"", - bRdonly ? "r/o" : "r/w", - eTxn==SQLITE_TXN_NONE ? "" : - eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn"); - free(azName[i*2]); - free(azName[i*2+1]); - } - sqlite3_free(azName); - }else - - if( c=='d' && n>=3 && strncmp(azArg[0], "dbconfig", n)==0 ){ - static const struct DbConfigChoices { - const char *zName; - int op; - } aDbConfig[] = { - { "defensive", SQLITE_DBCONFIG_DEFENSIVE }, - { "dqs_ddl", SQLITE_DBCONFIG_DQS_DDL }, - { "dqs_dml", SQLITE_DBCONFIG_DQS_DML }, - { "enable_fkey", SQLITE_DBCONFIG_ENABLE_FKEY }, - { "enable_qpsg", SQLITE_DBCONFIG_ENABLE_QPSG }, - { "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER }, - { "enable_view", SQLITE_DBCONFIG_ENABLE_VIEW }, - { "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER }, - { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE }, - { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT }, - { "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION }, - { "no_ckpt_on_close", SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE }, - { "reset_database", SQLITE_DBCONFIG_RESET_DATABASE }, - { "trigger_eqp", SQLITE_DBCONFIG_TRIGGER_EQP }, - { "trusted_schema", SQLITE_DBCONFIG_TRUSTED_SCHEMA }, - { "writable_schema", SQLITE_DBCONFIG_WRITABLE_SCHEMA }, - }; - int ii, v; - open_db(p, 0); - for(ii=0; ii1 && strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue; - if( nArg>=3 ){ - sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0); + param_table_init(db); + rc = param_set(db, cCast, azArg[inv], + &azArg[inv+1], &azArg[nArg], ptu); + if( rc!=SQLITE_OK ){ + utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(db)); + rc = 1; } - sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v); - utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off"); - if( nArg>1 ) break; } - if( nArg>1 && ii==ArraySize(aDbConfig) ){ - utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]); - utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n"); - } }else - if( c=='d' && n>=3 && strncmp(azArg[0], "dbinfo", n)==0 ){ - rc = shell_dbinfo_command(p, nArg, azArg); - }else + { /* If no command name and arg count matches, show a syntax error */ + showHelp(ISS(p)->out, "parameter", p); - return DCR_ArgError; ++ return DCR_CmdErred; + } -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) - if( c=='r' && strncmp(azArg[0], "recover", n)==0 ){ - open_db(p, 0); - rc = recoverDatabaseCmd(p, nArg, azArg); - }else -#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */ + return rc; +} - if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){ - char *zLike = 0; - char *zSql; - int i; - int savedShowHeader = p->showHeader; - int savedShellFlags = p->shellFlgs; - ShellClearFlag(p, - SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo - |SHFLG_DumpDataOnly|SHFLG_DumpNoSys); - for(i=1; iout, "%s%s", azArg[i], (i==nArg-1)? "\n" : " "); + } + return DCR_Ok; +} +DISPATCHABLE_COMMAND( progress 3 2 0 ){ + ShellInState *psi = ISS(p); + int i; + int nn = 0; + psi->flgProgress = 0; + psi->mxProgress = 0; + psi->nProgress = 0; + for(i=1; iflgProgress |= SHELL_PROGRESS_QUIET; + continue; + } + if( strcmp(z,"reset")==0 ){ + psi->flgProgress |= SHELL_PROGRESS_RESET; + continue; + } + if( strcmp(z,"once")==0 ){ + psi->flgProgress |= SHELL_PROGRESS_ONCE; + continue; + } + if( strcmp(z,"limit")==0 ){ + if( i+1>=nArg ){ - *pzErr = shellMPrintf(0,"Error: missing argument on --limit\n"); ++ *pzErr = smprintf("missing argument on --limit\n"); + return DCR_Unpaired|i; }else{ - zLike = zExpr; + psi->mxProgress = (int)integerValue(azArg[++i]); } + continue; } - *pzErr = shellMPrintf(0, "Error: unknown option: \"%s\"\n", azArg[i]); - } - - open_db(p, 0); - - if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ - /* When playing back a "dump", the content might appear in an order - ** which causes immediate foreign key constraints to be violated. - ** So disable foreign-key constraint enforcement to prevent problems. */ - raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n"); - raw_printf(p->out, "BEGIN TRANSACTION;\n"); - } - p->writableSchema = 0; - p->showHeader = 0; - /* Set writable_schema=ON since doing so forces SQLite to initialize - ** as much of the schema as it can even if the sqlite_schema table is - ** corrupt. */ - sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0); - p->nErr = 0; - if( zLike==0 ) zLike = sqlite3_mprintf("true"); - zSql = sqlite3_mprintf( - "SELECT name, type, sql FROM sqlite_schema AS o " - "WHERE (%s) AND type=='table'" - " AND sql NOT NULL" - " ORDER BY tbl_name='sqlite_sequence', rowid", - zLike - ); - run_schema_dump_query(p,zSql); - sqlite3_free(zSql); - if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ - zSql = sqlite3_mprintf( - "SELECT sql FROM sqlite_schema AS o " - "WHERE (%s) AND sql NOT NULL" - " AND type IN ('index','trigger','view')", - zLike - ); - run_table_dump_query(p, zSql); - sqlite3_free(zSql); - } - sqlite3_free(zLike); - if( p->writableSchema ){ - raw_printf(p->out, "PRAGMA writable_schema=OFF;\n"); - p->writableSchema = 0; - } - sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0); - sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0); - if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ - raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n"); - } - p->showHeader = savedShowHeader; - p->shellFlgs = savedShellFlags; - }else - - if( c=='e' && strncmp(azArg[0], "echo", n)==0 ){ - if( nArg==2 ){ - setOrClearFlag(p, SHFLG_Echo, azArg[1]); + return DCR_Unknown|i; }else{ - raw_printf(stderr, "Usage: .echo on|off\n"); - rc = 1; + nn = (int)integerValue(z); } - }else + } + open_db(p, 0); + sqlite3_progress_handler(DBX(p), nn, progress_handler, psi); + return DCR_Ok; +} +/* Allow too few arguments by tradition, (a form of no-op.) */ +DISPATCHABLE_COMMAND( prompt ? 1 3 ){ + if( nArg >= 2) { + strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1); + } + if( nArg >= 3) { + strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1); + } + return DCR_Ok; +} - if( c=='e' && strncmp(azArg[0], "eqp", n)==0 ){ - if( nArg==2 ){ - p->autoEQPtest = 0; - if( p->autoEQPtrace ){ - if( p->db ) sqlite3_exec(p->db, "PRAGMA vdbe_trace=OFF;", 0, 0, 0); - p->autoEQPtrace = 0; - } - if( strcmp(azArg[1],"full")==0 ){ - p->autoEQP = AUTOEQP_full; - }else if( strcmp(azArg[1],"trigger")==0 ){ - p->autoEQP = AUTOEQP_trigger; -#ifdef SQLITE_DEBUG - }else if( strcmp(azArg[1],"test")==0 ){ - p->autoEQP = AUTOEQP_on; - p->autoEQPtest = 1; - }else if( strcmp(azArg[1],"trace")==0 ){ - p->autoEQP = AUTOEQP_full; - p->autoEQPtrace = 1; - open_db(p, 0); - sqlite3_exec(p->db, "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0); - sqlite3_exec(p->db, "PRAGMA vdbe_trace=ON;", 0, 0, 0); -#endif - }else{ - p->autoEQP = (u8)booleanValue(azArg[1]); - } +/***************** + * The .read, .recover and .restore commands + */ +CONDITION_COMMAND( recover !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) ); +COLLECT_HELP_TEXT[ + ".read FILE Read input from FILE", + " If FILE begins with \"|\", it is a command that generates the input.", + ".recover Recover as much data as possible from corrupt db.", + " --freelist-corrupt Assume the freelist is corrupt", + " --recovery-db NAME Store recovery metadata in database file NAME", + " --lost-and-found TABLE Alternative name for the lost-and-found table", + " --no-rowids Do not attempt to recover rowid values", + " that are not also INTEGER PRIMARY KEYs", + ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE", +]; +DISPATCHABLE_COMMAND( read 3 2 2 ){ - int rc = 0; ++ DotCmdRC rc = DCR_Ok; + FILE *inUse = 0; + int (*fCloser)(FILE *) = 0; + if( ISS(p)->bSafeMode ) return DCR_AbortError; + if( azArg[1][0]=='|' ){ +#ifdef SQLITE_OMIT_POPEN - *pzErr = shellMPrintf(0,"Error: pipes are not supported in this OS\n"); - rc = 1; ++ *pzErr = smprintf("pipes are not supported in this OS\n"); ++ rc = DCR_Error; + /* p->out = STD_OUT; This was likely not needed. To be investigated. */ +#else + inUse = popen(azArg[1]+1, "r"); + if( inUse==0 ){ - utf8_printf(STD_ERR, "Error: cannot open \"%s\"\n", azArg[1]); - rc = 1; ++ *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]); ++ rc = DCR_Error; }else{ - raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n"); - rc = 1; + fCloser = pclose; } - }else +#endif + }else if( (inUse = openChrSource(azArg[1]))==0 ){ - *pzErr = shellMPrintf(0,"Error: cannot open \"%s\"\n", azArg[1]); - rc = 1; ++ *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]); ++ rc = DCR_Error; + }else{ + fCloser = fclose; + } + if( inUse!=0 ){ + InSource inSourceRedir + = INSOURCE_FILE_REDIR(inUse, azArg[1], ISS(p)->pInSource); + ISS(p)->pInSource = &inSourceRedir; + rc = process_input(ISS(p)); ++ /* If error(s) occured during process, leave complaining to them. */ ++ if( rc==DCR_Error ) rc = DCR_CmdErred; + assert(fCloser!=0); + fCloser(inUse); + ISS(p)->pInSource = inSourceRedir.pFrom; + } + return rc; +} + +/* +** This command is invoked to recover data from the database. A script +** to construct a new database containing all recovered data is output +** on stream pState->out. +*/ +DISPATCHABLE_COMMAND( recover ? 1 7 ){ + FILE *out = ISS(p)->out; + sqlite3 *db; + int rc = SQLITE_OK; + sqlite3_stmt *pLoop = 0; /* Loop through all root pages */ + sqlite3_stmt *pPages = 0; /* Loop through all pages in a group */ + sqlite3_stmt *pCells = 0; /* Loop through all cells in a page */ + const char *zRecoveryDb = ""; /* Name of "recovery" database */ + const char *zLostAndFound = "lost_and_found"; + int i; + int nOrphan = -1; + RecoverTable *pOrphan = 0; - if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){ - if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc); - rc = 2; - }else + open_db(p, 0); + db = DBX(p); - /* The ".explain" command is automatic now. It is largely pointless. It - ** retained purely for backwards compatibility */ - if( c=='e' && strncmp(azArg[0], "explain", n)==0 ){ - int val = 1; - if( nArg>=2 ){ - if( strcmp(azArg[1],"auto")==0 ){ - val = 99; - }else{ - val = booleanValue(azArg[1]); - } + int bFreelist = 1; /* 0 if --freelist-corrupt is specified */ + int bRowids = 1; /* 0 if --no-rowids */ + for(i=1; imode!=MODE_Explain ){ - p->normalMode = p->mode; - p->mode = MODE_Explain; - p->autoExplain = 0; - }else if( val==0 ){ - if( p->mode==MODE_Explain ) p->mode = p->normalMode; - p->autoExplain = 0; - }else if( val==99 ){ - if( p->mode==MODE_Explain ) p->mode = p->normalMode; - p->autoExplain = 1; + else{ - *pzErr = shellMPrintf(0,"unexpected option: %s\n", azArg[i]); ++ *pzErr = smprintf("unexpected option: %s\n", azArg[i]); + showHelp(out, azArg[0], p); - return DCR_ArgError; ++ return DCR_CmdErred; } - }else + } -#ifndef SQLITE_OMIT_VIRTUALTABLE - if( c=='e' && strncmp(azArg[0], "expert", n)==0 ){ - if( p->bSafeMode ){ - raw_printf(stderr, - "Cannot run experimental commands such as \"%s\" in safe mode\n", - azArg[0]); - rc = 1; - }else{ - open_db(p, 0); - expertDotCommand(p, azArg, nArg); - } - }else -#endif + shellExecPrintf(db, &rc, + /* Attach an in-memory database named 'recovery'. Create an indexed + ** cache of the sqlite_dbptr virtual table. */ + "PRAGMA writable_schema = on;" + "ATTACH %Q AS recovery;" + "DROP TABLE IF EXISTS recovery.dbptr;" + "DROP TABLE IF EXISTS recovery.freelist;" + "DROP TABLE IF EXISTS recovery.map;" + "DROP TABLE IF EXISTS recovery.schema;" + "CREATE TABLE recovery.freelist(pgno INTEGER PRIMARY KEY);", zRecoveryDb + ); - if( c=='f' && strncmp(azArg[0], "filectrl", n)==0 ){ - static const struct { - const char *zCtrlName; /* Name of a test-control option */ - int ctrlCode; /* Integer code for that option */ - const char *zUsage; /* Usage notes */ - } aCtrl[] = { - { "chunk_size", SQLITE_FCNTL_CHUNK_SIZE, "SIZE" }, - { "data_version", SQLITE_FCNTL_DATA_VERSION, "" }, - { "has_moved", SQLITE_FCNTL_HAS_MOVED, "" }, - { "lock_timeout", SQLITE_FCNTL_LOCK_TIMEOUT, "MILLISEC" }, - { "persist_wal", SQLITE_FCNTL_PERSIST_WAL, "[BOOLEAN]" }, - /* { "pragma", SQLITE_FCNTL_PRAGMA, "NAME ARG" },*/ - { "psow", SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]" }, - { "reserve_bytes", SQLITE_FCNTL_RESERVE_BYTES, "[N]" }, - { "size_limit", SQLITE_FCNTL_SIZE_LIMIT, "[LIMIT]" }, - { "tempfilename", SQLITE_FCNTL_TEMPFILENAME, "" }, - /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY, "COUNT DELAY" },*/ - }; - int filectrl = -1; - int iCtrl = -1; - sqlite3_int64 iRes = 0; /* Integer result to display if rc2==1 */ - int isOk = 0; /* 0: usage 1: %lld 2: no-result */ - int n2, i; - const char *zCmd = 0; - const char *zSchema = 0; + if( bFreelist ){ + shellExec(db, &rc, + "WITH trunk(pgno) AS (" + " SELECT shell_int32(" + " (SELECT data FROM sqlite_dbpage WHERE pgno=1), 8) AS x " + " WHERE x>0" + " UNION" + " SELECT shell_int32(" + " (SELECT data FROM sqlite_dbpage WHERE pgno=trunk.pgno), 0) AS x " + " FROM trunk WHERE x>0" + ")," + "freelist(data, n, freepgno) AS (" + " SELECT data, min(16384, shell_int32(data, 1)-1), t.pgno " + " FROM trunk t, sqlite_dbpage s WHERE s.pgno=t.pgno" + " UNION ALL" + " SELECT data, n-1, shell_int32(data, 2+n) " + " FROM freelist WHERE n>=0" + ")" + "REPLACE INTO recovery.freelist SELECT freepgno FROM freelist;" + ); + } - open_db(p, 0); - zCmd = nArg>=2 ? azArg[1] : "help"; + /* If this is an auto-vacuum database, add all pointer-map pages to + ** the freelist table. Do this regardless of whether or not + ** --freelist-corrupt was specified. */ + shellExec(db, &rc, + "WITH ptrmap(pgno) AS (" + " SELECT 2 WHERE shell_int32(" + " (SELECT data FROM sqlite_dbpage WHERE pgno=1), 13" + " )" + " UNION ALL " + " SELECT pgno+1+(SELECT page_size FROM pragma_page_size)/5 AS pp " + " FROM ptrmap WHERE pp<=(SELECT page_count FROM pragma_page_count)" + ")" + "REPLACE INTO recovery.freelist SELECT pgno FROM ptrmap" + ); - if( zCmd[0]=='-' - && (strcmp(zCmd,"--schema")==0 || strcmp(zCmd,"-schema")==0) - && nArg>=4 - ){ - zSchema = azArg[2]; - for(i=3; iout, "Available file-controls:\n"); - for(i=0; iout, " .filectrl %s %s\n", - aCtrl[i].zCtrlName, aCtrl[i].zUsage); - } - rc = 1; - goto meta_command_exit; - } + /* Delete all pointers to any pages that have more than one pointer + ** to them. Such pages will be treated as root pages when recovering + ** data. */ + "DELETE FROM recovery.dbptr WHERE child IN (" + " SELECT child FROM recovery.dbptr GROUP BY child HAVING count(*)>1" + ");" - /* convert filectrl text option to value. allow any unique prefix - ** of the option name, or a numerical value. */ - n2 = strlen30(zCmd); - for(i=0; idb, zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes); - isOk = 1; - break; - } - case SQLITE_FCNTL_LOCK_TIMEOUT: - case SQLITE_FCNTL_CHUNK_SIZE: { - int x; - if( nArg!=3 ) break; - x = (int)integerValue(azArg[2]); - sqlite3_file_control(p->db, zSchema, filectrl, &x); - isOk = 2; - break; - } - case SQLITE_FCNTL_PERSIST_WAL: - case SQLITE_FCNTL_POWERSAFE_OVERWRITE: { - int x; - if( nArg!=2 && nArg!=3 ) break; - x = nArg==3 ? booleanValue(azArg[2]) : -1; - sqlite3_file_control(p->db, zSchema, filectrl, &x); - iRes = x; - isOk = 1; - break; - } - case SQLITE_FCNTL_DATA_VERSION: - case SQLITE_FCNTL_HAS_MOVED: { - int x; - if( nArg!=2 ) break; - sqlite3_file_control(p->db, zSchema, filectrl, &x); - iRes = x; - isOk = 1; - break; - } - case SQLITE_FCNTL_TEMPFILENAME: { - char *z = 0; - if( nArg!=2 ) break; - sqlite3_file_control(p->db, zSchema, filectrl, &z); - if( z ){ - utf8_printf(p->out, "%s\n", z); - sqlite3_free(z); - } - isOk = 2; - break; - } - case SQLITE_FCNTL_RESERVE_BYTES: { - int x; - if( nArg>=3 ){ - x = atoi(azArg[2]); - sqlite3_file_control(p->db, zSchema, filectrl, &x); - } - x = -1; - sqlite3_file_control(p->db, zSchema, filectrl, &x); - utf8_printf(p->out,"%d\n", x); - isOk = 2; - break; - } - } - } - if( isOk==0 && iCtrl>=0 ){ - utf8_printf(p->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage); - rc = 1; - }else if( isOk==1 ){ - char zBuf[100]; - sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes); - raw_printf(p->out, "%s\n", zBuf); - } - }else + /* Create the "map" table that will (eventually) contain instructions + ** for dealing with each page in the db that contains one or more + ** records. */ + "CREATE TABLE recovery.map(" + "pgno INTEGER PRIMARY KEY, maxlen INT, intkey, root INT" + ");" - if( c=='f' && strncmp(azArg[0], "fullschema", n)==0 ){ - ShellState data; - int doStats = 0; - memcpy(&data, p, sizeof(data)); - data.showHeader = 0; - data.cMode = data.mode = MODE_Semi; - if( nArg==2 && optionMatch(azArg[1], "indent") ){ - data.cMode = data.mode = MODE_Pretty; - nArg = 1; - } - if( nArg!=1 ){ - raw_printf(stderr, "Usage: .fullschema ?--indent?\n"); - rc = 1; - goto meta_command_exit; - } - open_db(p, 0); - rc = sqlite3_exec(p->db, - "SELECT sql FROM" - " (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x" - " FROM sqlite_schema UNION ALL" - " SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) " - "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' " - "ORDER BY x", - callback, &data, 0 + /* Populate table [map]. If there are circular loops of pages in the + ** database, the following adds all pages in such a loop to the map + ** as individual root pages. This could be handled better. */ + "WITH pages(i, maxlen) AS (" + " SELECT page_count, (" + " SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=page_count" + " ) FROM pragma_page_count WHERE page_count>0" + " UNION ALL" + " SELECT i-1, (" + " SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=i-1" + " ) FROM pages WHERE i>=2" + ")" + "INSERT INTO recovery.map(pgno, maxlen, intkey, root) " + " SELECT i, maxlen, NULL, (" + " WITH p(orig, pgno, parent) AS (" + " SELECT 0, i, (SELECT pgno FROM recovery.dbptr WHERE child=i)" + " UNION " + " SELECT i, p.parent, " + " (SELECT pgno FROM recovery.dbptr WHERE child=p.parent) FROM p" + " )" + " SELECT pgno FROM p WHERE (parent IS NULL OR pgno = orig)" + ") " + "FROM pages WHERE maxlen IS NOT NULL AND i NOT IN freelist;" + "UPDATE recovery.map AS o SET intkey = (" + " SELECT substr(data, 1, 1)==X'0D' FROM sqlite_dbpage WHERE pgno=o.pgno" + ");" + + /* Extract data from page 1 and any linked pages into table + ** recovery.schema. With the same schema as an sqlite_schema table. */ + "CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);" + "INSERT INTO recovery.schema SELECT " + " max(CASE WHEN field=0 THEN value ELSE NULL END)," + " max(CASE WHEN field=1 THEN value ELSE NULL END)," + " max(CASE WHEN field=2 THEN value ELSE NULL END)," + " max(CASE WHEN field=3 THEN value ELSE NULL END)," + " max(CASE WHEN field=4 THEN value ELSE NULL END)" + "FROM sqlite_dbdata WHERE pgno IN (" + " SELECT pgno FROM recovery.map WHERE root=1" + ")" + "GROUP BY pgno, cell;" + "CREATE INDEX recovery.schema_rootpage ON schema(rootpage);" + ); + + /* Open a transaction, then print out all non-virtual, non-"sqlite_%" + ** CREATE TABLE statements that extracted from the existing schema. */ + if( rc==SQLITE_OK ){ + sqlite3_stmt *pStmt = 0; + /* ".recover" might output content in an order which causes immediate + ** foreign key constraints to be violated. So disable foreign-key + ** constraint enforcement to prevent problems when running the output + ** script. */ + raw_printf(out, "PRAGMA foreign_keys=OFF;\n"); + raw_printf(out, "BEGIN;\n"); + raw_printf(out, "PRAGMA writable_schema = on;\n"); + shellPrepare(db, &rc, + "SELECT sql FROM recovery.schema " + "WHERE type='table' AND sql LIKE 'create table%'", &pStmt ); - if( rc==SQLITE_OK ){ - sqlite3_stmt *pStmt; - rc = sqlite3_prepare_v2(p->db, - "SELECT rowid FROM sqlite_schema" - " WHERE name GLOB 'sqlite_stat[134]'", - -1, &pStmt, 0); - doStats = sqlite3_step(pStmt)==SQLITE_ROW; - sqlite3_finalize(pStmt); - } - if( doStats==0 ){ - raw_printf(p->out, "/* No STAT tables available */\n"); - }else{ - raw_printf(p->out, "ANALYZE sqlite_schema;\n"); - data.cMode = data.mode = MODE_Insert; - data.zDestTable = "sqlite_stat1"; - shell_exec(&data, "SELECT * FROM sqlite_stat1", 0); - data.zDestTable = "sqlite_stat4"; - shell_exec(&data, "SELECT * FROM sqlite_stat4", 0); - raw_printf(p->out, "ANALYZE sqlite_schema;\n"); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + const char *zCreateTable = (const char*)sqlite3_column_text(pStmt, 0); + raw_printf(out, "CREATE TABLE IF NOT EXISTS %s;\n", &zCreateTable[12]); } - }else + shellFinalize(&rc, pStmt); + } - if( c=='h' && strncmp(azArg[0], "headers", n)==0 ){ - if( nArg==2 ){ - p->showHeader = booleanValue(azArg[1]); - p->shellFlgs |= SHFLG_HeaderSet; - }else{ - raw_printf(stderr, "Usage: .headers on|off\n"); - rc = 1; - } - }else + /* Figure out if an orphan table will be required. And if so, how many + ** user columns it should contain */ + shellPrepare(db, &rc, + "SELECT coalesce(max(maxlen), -2) FROM recovery.map WHERE root>1" + , &pLoop + ); + if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){ + nOrphan = sqlite3_column_int(pLoop, 0); + } + shellFinalize(&rc, pLoop); + pLoop = 0; - if( c=='h' && strncmp(azArg[0], "help", n)==0 ){ - if( nArg>=2 ){ - n = showHelp(p->out, azArg[1]); - if( n==0 ){ - utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]); - } - }else{ - showHelp(p->out, 0); - } - }else + shellPrepare(db, &rc, + "SELECT pgno FROM recovery.map WHERE root=?", &pPages + ); - if( c=='i' && strncmp(azArg[0], "import", n)==0 ){ - char *zTable = 0; /* Insert data into this table */ - char *zSchema = 0; /* within this schema (may default to "main") */ - char *zFile = 0; /* Name of file to extra content from */ - sqlite3_stmt *pStmt = NULL; /* A statement */ - int nCol; /* Number of columns in the table */ - int nByte; /* Number of bytes in an SQL string */ - int i, j; /* Loop counters */ - int needCommit; /* True to COMMIT or ROLLBACK at end */ - int nSep; /* Number of bytes in p->colSeparator[] */ - char *zSql; /* An SQL statement */ - char *zFullTabName; /* Table name with schema if applicable */ - ImportCtx sCtx; /* Reader context */ - char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */ - int eVerbose = 0; /* Larger for more console output */ - int nSkip = 0; /* Initial lines to skip */ - int useOutputMode = 1; /* Use output mode to determine separators */ - char *zCreate = 0; /* CREATE TABLE statement text */ - - failIfSafeMode(p, "cannot run .import in safe mode"); - memset(&sCtx, 0, sizeof(sCtx)); - sCtx.z = sqlite3_malloc64(120); - if( sCtx.z==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - if( p->mode==MODE_Ascii ){ - xRead = ascii_read_one_field; - }else{ - xRead = csv_read_one_field; - } - for(i=1; iout, "ERROR: extra argument: \"%s\". Usage:\n", z); - showHelp(p->out, "import"); - rc = 1; - goto meta_command_exit; - } - }else if( strcmp(z,"-v")==0 ){ - eVerbose++; - }else if( strcmp(z,"-schema")==0 && iout, "ERROR: unknown option: \"%s\". Usage:\n", z); - showHelp(p->out, "import"); - rc = 1; - goto meta_command_exit; - } - } - if( zTable==0 ){ - utf8_printf(p->out, "ERROR: missing %s argument. Usage:\n", - zFile==0 ? "FILE" : "TABLE"); - showHelp(p->out, "import"); - rc = 1; - goto meta_command_exit; - } - seenInterrupt = 0; - open_db(p, 0); - if( useOutputMode ){ - /* If neither the --csv or --ascii options are specified, then set - ** the column and row separator characters from the output mode. */ - nSep = strlen30(p->colSeparator); - if( nSep==0 ){ - raw_printf(stderr, - "Error: non-null column separator required for import\n"); - rc = 1; - goto meta_command_exit; - } - if( nSep>1 ){ - raw_printf(stderr, - "Error: multi-character column separators not allowed" - " for import\n"); - rc = 1; - goto meta_command_exit; - } - nSep = strlen30(p->rowSeparator); - if( nSep==0 ){ - raw_printf(stderr, - "Error: non-null row separator required for import\n"); - rc = 1; - goto meta_command_exit; - } - if( nSep==2 && p->mode==MODE_Csv && strcmp(p->rowSeparator,SEP_CrLf)==0 ){ - /* When importing CSV (only), if the row separator is set to the - ** default output row separator, change it to the default input - ** row separator. This avoids having to maintain different input - ** and output row separators. */ - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - nSep = strlen30(p->rowSeparator); - } - if( nSep>1 ){ - raw_printf(stderr, "Error: multi-character row separators not allowed" - " for import\n"); - rc = 1; - goto meta_command_exit; + shellPrepare(db, &rc, + "SELECT max(field), group_concat(shell_escape_crnl(quote" + "(case when (? AND field<0) then NULL else value end)" + "), ', ')" + ", min(field) " + "FROM sqlite_dbdata WHERE pgno = ? AND field != ?" + "GROUP BY cell", &pCells + ); + + /* Loop through each root page. */ + shellPrepare(db, &rc, + "SELECT root, intkey, max(maxlen) FROM recovery.map" + " WHERE root>1 GROUP BY root, intkey ORDER BY root=(" + " SELECT rootpage FROM recovery.schema WHERE name='sqlite_sequence'" + ")", &pLoop + ); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){ + int iRoot = sqlite3_column_int(pLoop, 0); + int bIntkey = sqlite3_column_int(pLoop, 1); + int nCol = sqlite3_column_int(pLoop, 2); + int bNoop = 0; + RecoverTable *pTab; + + assert( bIntkey==0 || bIntkey==1 ); + pTab = recoverFindTable(db, &rc, iRoot, bIntkey, nCol, &bNoop); + if( bNoop || rc ) continue; + if( pTab==0 ){ + if( pOrphan==0 ){ + pOrphan = recoverOrphanTable(db, out, &rc, zLostAndFound, nOrphan); } - sCtx.cColSep = p->colSeparator[0]; - sCtx.cRowSep = p->rowSeparator[0]; + pTab = pOrphan; + if( pTab==0 ) break; } - sCtx.zFile = zFile; - sCtx.nLine = 1; - if( sCtx.zFile[0]=='|' ){ -#ifdef SQLITE_OMIT_POPEN - raw_printf(stderr, "Error: pipes are not supported in this OS\n"); - rc = 1; - goto meta_command_exit; -#else - sCtx.in = popen(sCtx.zFile+1, "r"); - sCtx.zFile = ""; - sCtx.xCloser = pclose; -#endif - }else{ - sCtx.in = fopen(sCtx.zFile, "rb"); - sCtx.xCloser = fclose; + + if( 0==sqlite3_stricmp(pTab->zQuoted, "\"sqlite_sequence\"") ){ + raw_printf(out, "DELETE FROM sqlite_sequence;\n"); } - if( sCtx.in==0 ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile); - rc = 1; - import_cleanup(&sCtx); - goto meta_command_exit; - } - if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){ - char zSep[2]; - zSep[1] = 0; - zSep[0] = sCtx.cColSep; - utf8_printf(p->out, "Column separator "); - output_c_string(p->out, zSep); - utf8_printf(p->out, ", row separator "); - zSep[0] = sCtx.cRowSep; - output_c_string(p->out, zSep); - utf8_printf(p->out, "\n"); - } - /* Below, resources must be freed before exit. */ - while( (nSkip--)>0 ){ - while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){} - } - if( zSchema!=0 ){ - zFullTabName = sqlite3_mprintf("\"%w\".\"%w\"", zSchema, zTable); + sqlite3_bind_int(pPages, 1, iRoot); + if( bRowids==0 && pTab->iPk<0 ){ + sqlite3_bind_int(pCells, 1, 1); }else{ - zFullTabName = sqlite3_mprintf("\"%w\"", zTable); - } - zSql = sqlite3_mprintf("SELECT * FROM %s", zFullTabName); - if( zSql==0 || zFullTabName==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - nByte = strlen30(zSql); - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ - if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){ - sqlite3 *dbCols = 0; - char *zRenames = 0; - char *zColDefs; - zCreate = sqlite3_mprintf("CREATE TABLE %s", zFullTabName); - while( xRead(&sCtx) ){ - zAutoColumn(sCtx.z, &dbCols, 0); - if( sCtx.cTerm!=sCtx.cColSep ) break; - } - zColDefs = zAutoColumn(0, &dbCols, &zRenames); - if( zRenames!=0 ){ - utf8_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr, - "Columns renamed during .import %s due to duplicates:\n" - "%s\n", sCtx.zFile, zRenames); - sqlite3_free(zRenames); - } - assert(dbCols==0); - if( zColDefs==0 ){ - utf8_printf(stderr,"%s: empty file\n", sCtx.zFile); - import_fail: - sqlite3_free(zCreate); - sqlite3_free(zSql); - sqlite3_free(zFullTabName); - import_cleanup(&sCtx); - rc = 1; - goto meta_command_exit; - } - zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs); - if( eVerbose>=1 ){ - utf8_printf(p->out, "%s\n", zCreate); - } - rc = sqlite3_exec(p->db, zCreate, 0, 0, 0); - if( rc ){ - utf8_printf(stderr, "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db)); - goto import_fail; - } - sqlite3_free(zCreate); - zCreate = 0; - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - } - if( rc ){ - if (pStmt) sqlite3_finalize(pStmt); - utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db)); - goto import_fail; - } - sqlite3_free(zSql); - nCol = sqlite3_column_count(pStmt); - sqlite3_finalize(pStmt); - pStmt = 0; - if( nCol==0 ) return 0; /* no columns, no error */ - zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 ); - if( zSql==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName); - j = strlen30(zSql); - for(i=1; i=2 ){ - utf8_printf(p->out, "Insert using: %s\n", zSql); - } - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - if( rc ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); - if (pStmt) sqlite3_finalize(pStmt); - goto import_fail; + sqlite3_bind_int(pCells, 1, 0); } - sqlite3_free(zSql); - sqlite3_free(zFullTabName); - needCommit = sqlite3_get_autocommit(p->db); - if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0); - do{ - int startLine = sCtx.nLine; - for(i=0; imode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; - sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); - if( iiPk); + + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPages) ){ + int iPgno = sqlite3_column_int(pPages, 0); + sqlite3_bind_int(pCells, 2, iPgno); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pCells) ){ + int nField = sqlite3_column_int(pCells, 0); + int iMin = sqlite3_column_int(pCells, 2); + const char *zVal = (const char*)sqlite3_column_text(pCells, 1); + + RecoverTable *pTab2 = pTab; + if( pTab!=pOrphan && (iMin<0)!=bIntkey ){ + if( pOrphan==0 ){ + pOrphan = recoverOrphanTable(db, out, &rc, zLostAndFound, nOrphan); + } + pTab2 = pOrphan; + if( pTab2==0 ) break; } - } - if( sCtx.cTerm==sCtx.cColSep ){ - do{ - xRead(&sCtx); - i++; - }while( sCtx.cTerm==sCtx.cColSep ); - utf8_printf(stderr, "%s:%d: expected %d columns but found %d - " - "extras ignored\n", - sCtx.zFile, startLine, nCol, i); - } - if( i>=nCol ){ - sqlite3_step(pStmt); - rc = sqlite3_reset(pStmt); - if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile, - startLine, sqlite3_errmsg(p->db)); - sCtx.nErr++; + + nField = nField+1; + if( pTab2==pOrphan ){ + raw_printf(out, + "INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n", + pTab2->zQuoted, iRoot, iPgno, nField, + iMin<0 ? "" : "NULL, ", zVal, pTab2->azlCol[nField] + ); }else{ - sCtx.nRow++; + raw_printf(out, "INSERT INTO %s(%s) VALUES( %s );\n", + pTab2->zQuoted, pTab2->azlCol[nField], zVal + ); } } - }while( sCtx.cTerm!=EOF ); - - import_cleanup(&sCtx); - sqlite3_finalize(pStmt); - if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0); - if( eVerbose>0 ){ - utf8_printf(p->out, - "Added %d rows with %d errors using %d lines of input\n", - sCtx.nRow, sCtx.nErr, sCtx.nLine-1); + shellReset(&rc, pCells); } - }else + shellReset(&rc, pPages); + if( pTab!=pOrphan ) recoverFreeTable(pTab); + } + shellFinalize(&rc, pLoop); + shellFinalize(&rc, pPages); + shellFinalize(&rc, pCells); + recoverFreeTable(pOrphan); -#ifndef SQLITE_UNTESTABLE - if( c=='i' && strncmp(azArg[0], "imposter", n)==0 ){ - char *zSql; - char *zCollist = 0; - sqlite3_stmt *pStmt; - int tnum = 0; - int isWO = 0; /* True if making an imposter of a WITHOUT ROWID table */ - int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */ - int i; - if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){ - utf8_printf(stderr, "Usage: .imposter INDEX IMPOSTER\n" - " .imposter off\n"); - /* Also allowed, but not documented: - ** - ** .imposter TABLE IMPOSTER - ** - ** where TABLE is a WITHOUT ROWID table. In that case, the - ** imposter is another WITHOUT ROWID table with the columns in - ** storage order. */ - rc = 1; - goto meta_command_exit; - } - open_db(p, 0); - if( nArg==2 ){ - sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 1); - goto meta_command_exit; - } - zSql = sqlite3_mprintf( - "SELECT rootpage, 0 FROM sqlite_schema" - " WHERE name='%q' AND type='index'" - "UNION ALL " - "SELECT rootpage, 1 FROM sqlite_schema" - " WHERE name='%q' AND type='table'" - " AND sql LIKE '%%without%%rowid%%'", - azArg[1], azArg[1] + /* The rest of the schema */ + if( rc==SQLITE_OK ){ + sqlite3_stmt *pStmt = 0; + shellPrepare(db, &rc, + "SELECT sql, name FROM recovery.schema " + "WHERE sql NOT LIKE 'create table%'", &pStmt ); - sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); - if( sqlite3_step(pStmt)==SQLITE_ROW ){ - tnum = sqlite3_column_int(pStmt, 0); - isWO = sqlite3_column_int(pStmt, 1); - } - sqlite3_finalize(pStmt); - zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]); - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); - i = 0; - while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ - char zLabel[20]; - const char *zCol = (const char*)sqlite3_column_text(pStmt,2); - i++; - if( zCol==0 ){ - if( sqlite3_column_int(pStmt,1)==-1 ){ - zCol = "_ROWID_"; - }else{ - sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i); - zCol = zLabel; - } - } - if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){ - lenPK = (int)strlen(zCollist); - } - if( zCollist==0 ){ - zCollist = sqlite3_mprintf("\"%w\"", zCol); - }else{ - zCollist = sqlite3_mprintf("%z,\"%w\"", zCollist, zCol); - } - } - sqlite3_finalize(pStmt); - if( i==0 || tnum==0 ){ - utf8_printf(stderr, "no such index: \"%s\"\n", azArg[1]); - rc = 1; - sqlite3_free(zCollist); - goto meta_command_exit; - } - if( lenPK==0 ) lenPK = 100000; - zSql = sqlite3_mprintf( - "CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))WITHOUT ROWID", - azArg[2], zCollist, lenPK, zCollist); - sqlite3_free(zCollist); - rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 1, tnum); - if( rc==SQLITE_OK ){ - rc = sqlite3_exec(p->db, zSql, 0, 0, 0); - sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0); - if( rc ){ - utf8_printf(stderr, "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db)); - }else{ - utf8_printf(stdout, "%s;\n", zSql); - raw_printf(stdout, - "WARNING: writing to an imposter table will corrupt the \"%s\" %s!\n", - azArg[1], isWO ? "table" : "index" + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + const char *zSql = (const char*)sqlite3_column_text(pStmt, 0); + if( sqlite3_strnicmp(zSql, "create virt", 11)==0 ){ + const char *zName = (const char*)sqlite3_column_text(pStmt, 1); + char *zPrint = shellMPrintf(&rc, + "INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)", + zName, zName, zSql ); + raw_printf(out, "%s;\n", zPrint); + sqlite3_free(zPrint); + }else{ + raw_printf(out, "%s;\n", zSql); } - }else{ - raw_printf(stderr, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); - rc = 1; } - sqlite3_free(zSql); - }else -#endif /* !defined(SQLITE_OMIT_TEST_CONTROL) */ + shellFinalize(&rc, pStmt); + } -#ifdef SQLITE_ENABLE_IOTRACE - if( c=='i' && strncmp(azArg[0], "iotrace", n)==0 ){ - SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...); - if( iotrace && iotrace!=stdout ) fclose(iotrace); - iotrace = 0; - if( nArg<2 ){ - sqlite3IoTrace = 0; - }else if( strcmp(azArg[1], "-")==0 ){ - sqlite3IoTrace = iotracePrintf; - iotrace = stdout; - }else{ - iotrace = fopen(azArg[1], "w"); - if( iotrace==0 ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]); - sqlite3IoTrace = 0; - rc = 1; - }else{ - sqlite3IoTrace = iotracePrintf; - } + if( rc==SQLITE_OK ){ + raw_printf(out, "PRAGMA writable_schema = off;\n"); + raw_printf(out, "COMMIT;\n"); + } + sqlite3_exec(db, "DETACH recovery", 0, 0, 0); + return rc; +} + +DISPATCHABLE_COMMAND( restore ? 2 3 ){ + int rc; + const char *zSrcFile; + const char *zDb; + sqlite3 *pSrc; + sqlite3_backup *pBackup; + int nTimeout = 0; + + if( ISS(p)->bSafeMode ) return DCR_AbortError; + if( nArg==2 ){ + zSrcFile = azArg[1]; + zDb = "main"; + }else if( nArg==3 ){ + zSrcFile = azArg[2]; + zDb = azArg[1]; + }else{ + return DCR_TooMany; + } + rc = sqlite3_open(zSrcFile, &pSrc); + if( rc!=SQLITE_OK ){ - *pzErr = shellMPrintf(0,"Error: cannot open \"%s\"\n", zSrcFile); ++ *pzErr = smprintf("cannot open \"%s\"\n", zSrcFile); + close_db(pSrc); + return DCR_Error; + } + open_db(p, 0); + pBackup = sqlite3_backup_init(DBX(p), zDb, pSrc, "main"); + if( pBackup==0 ){ - *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(DBX(p))); ++ *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p))); + close_db(pSrc); + return DCR_Error; + } + while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK + || rc==SQLITE_BUSY ){ + if( rc==SQLITE_BUSY ){ + if( nTimeout++ >= 3 ) break; + sqlite3_sleep(100); } - }else + } + sqlite3_backup_finish(pBackup); + if( rc==SQLITE_DONE ){ + rc = 0; + }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){ - *pzErr = shellMPrintf(0,"Error: source database is busy\n"); ++ *pzErr = smprintf("source database is busy\n"); + rc = 1; + }else{ - *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(DBX(p))); ++ *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p))); + rc = 1; + } + close_db(pSrc); - return rc; ++ return DCR_Ok|rc; +} + +/***************** + * The .scanstats and .schema commands + */ +COLLECT_HELP_TEXT[ + ".scanstats on|off Turn sqlite3_stmt_scanstatus() metrics on or off", + ".schema ?PATTERN? Show the CREATE statements matching PATTERN", + " Options:", + " --indent Try to pretty-print the schema", + " --nosys Omit objects whose names start with \"sqlite_\"", +]; +DISPATCHABLE_COMMAND( scanstats ? 2 2 ){ + ISS(p)->scanstatsOn = (u8)booleanValue(azArg[1]); +#ifndef SQLITE_ENABLE_STMT_SCANSTATUS + raw_printf(STD_ERR, "Warning: .scanstats not available in this build.\n"); #endif + return DCR_Ok; +} +DISPATCHABLE_COMMAND( schema ? 1 2 ){ + int rc; + ShellText sSelect; + ShellInState data; + ShellExState datax; + char *zErrMsg = 0; + const char *zDiv = "("; + const char *zName = 0; + int iSchema = 0; + int bDebug = 0; + int bNoSystemTabs = 0; + int ii; - if( c=='l' && n>=5 && strncmp(azArg[0], "limits", n)==0 ){ - static const struct { - const char *zLimitName; /* Name of a limit */ - int limitCode; /* Integer code for that limit */ - } aLimit[] = { - { "length", SQLITE_LIMIT_LENGTH }, - { "sql_length", SQLITE_LIMIT_SQL_LENGTH }, - { "column", SQLITE_LIMIT_COLUMN }, - { "expr_depth", SQLITE_LIMIT_EXPR_DEPTH }, - { "compound_select", SQLITE_LIMIT_COMPOUND_SELECT }, - { "vdbe_op", SQLITE_LIMIT_VDBE_OP }, - { "function_arg", SQLITE_LIMIT_FUNCTION_ARG }, - { "attached", SQLITE_LIMIT_ATTACHED }, - { "like_pattern_length", SQLITE_LIMIT_LIKE_PATTERN_LENGTH }, - { "variable_number", SQLITE_LIMIT_VARIABLE_NUMBER }, - { "trigger_depth", SQLITE_LIMIT_TRIGGER_DEPTH }, - { "worker_threads", SQLITE_LIMIT_WORKER_THREADS }, - }; - int i, n2; - open_db(p, 0); - if( nArg==1 ){ - for(i=0; idb, aLimit[i].limitCode, -1)); - } - }else if( nArg>3 ){ - raw_printf(stderr, "Usage: .limit NAME ?NEW-VALUE?\n"); - rc = 1; - goto meta_command_exit; + open_db(p, 0); + /* Consider some refactoring to avoid duplicative wholesale copying. */ + memcpy(&data, ISS(p), sizeof(data)); + memcpy(&datax, p, sizeof(datax)); + data.pSXS = &datax; + datax.pSIS = &data; + + data.showHeader = 0; + data.cMode = data.mode = MODE_Semi; + initText(&sSelect); + for(ii=1; iidb, aLimit[iLimit].limitCode, - (int)integerValue(azArg[2])); + appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0); + appendText(&sSelect, zQarg, 0); + if( !bGlob ){ + appendText(&sSelect, " ESCAPE '\\' ", 0); } - printf("%20s %d\n", aLimit[iLimit].zLimitName, - sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1)); + appendText(&sSelect, " AND ", 0); + sqlite3_free(zQarg); } - }else - - if( c=='l' && n>2 && strncmp(azArg[0], "lint", n)==0 ){ - open_db(p, 0); - lintDotCommand(p, azArg, nArg); - }else - -#ifndef SQLITE_OMIT_LOAD_EXTENSION - if( c=='l' && strncmp(azArg[0], "load", n)==0 ){ - const char *zFile, *zProc; - char *zErrMsg = 0; - failIfSafeMode(p, "cannot run .load in safe mode"); - if( nArg<2 ){ - raw_printf(stderr, "Usage: .load FILE ?ENTRYPOINT?\n"); - rc = 1; - goto meta_command_exit; + if( bNoSystemTabs ){ + appendText(&sSelect, "name NOT LIKE 'sqlite_%%' AND ", 0); } - zFile = azArg[1]; - zProc = nArg>=3 ? azArg[2] : 0; - open_db(p, 0); - rc = sqlite3_load_extension(p->db, zFile, zProc, &zErrMsg); - if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "Error: %s\n", zErrMsg); - sqlite3_free(zErrMsg); - rc = 1; + appendText(&sSelect, "sql IS NOT NULL" + " ORDER BY snum, rowid", 0); + if( bDebug ){ + utf8_printf(data.out, "SQL: %s;\n", sSelect.z); + }else{ + rc = sqlite3_exec(datax.dbUser, sSelect.z, callback, &datax, &zErrMsg); + } + freeText(&sSelect); + } + if( zErrMsg ){ + *pzErr = zErrMsg; + return DCR_Error; + }else if( rc != SQLITE_OK ){ - *pzErr = shellMPrintf(0,"Error: querying schema information\n"); ++ *pzErr = smprintf("Error: querying schema information\n"); + return DCR_Error; + }else{ + return DCR_Ok; + } +} + +/***************** + * The .selecttrace, .separator, .session and .sha3sum commands + */ +CONDITION_COMMAND( session defined(SQLITE_ENABLE_SESSION) ); +COLLECT_HELP_TEXT[ + ".separator COL ?ROW? Change the column and row separators", + ".session ?NAME? CMD ... Create or control sessions", + " Subcommands:", + " attach TABLE Attach TABLE", + " changeset FILE Write a changeset into FILE", + " close Close one session", + " enable ?BOOLEAN? Set or query the enable bit", + " filter GLOB... Reject tables matching GLOBs", + " indirect ?BOOLEAN? Mark or query the indirect status", + " isempty Query whether the session is empty", + " list List currently open session names", + " open DB NAME Open a new session on DB", + " patchset FILE Write a patchset into FILE", + " If ?NAME? is omitted, the first defined session is used.", + ".sha3sum ... Compute a SHA3 hash of database content", + " Options:", + " --schema Also hash the sqlite_schema table", + " --sha3-224 Use the sha3-224 algorithm", + " --sha3-256 Use the sha3-256 algorithm (default)", + " --sha3-384 Use the sha3-384 algorithm", + " --sha3-512 Use the sha3-512 algorithm", + " Any other argument is a LIKE pattern for tables to hash", +]; +DISPATCHABLE_COMMAND( selecttrace ? 1 0 ){ + unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff; + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x); + return DCR_Ok; +} +DISPATCHABLE_COMMAND( separator ? 2 3 ){ + if( nArg>=2 ){ + sqlite3_snprintf(sizeof(ISS(p)->colSeparator), ISS(p)->colSeparator, + "%.*s", (int)ArraySize(ISS(p)->colSeparator)-1, azArg[1]); + } + if( nArg>=3 ){ + sqlite3_snprintf(sizeof(ISS(p)->rowSeparator), ISS(p)->rowSeparator, + "%.*s", (int)ArraySize(ISS(p)->rowSeparator)-1, azArg[2]); + } + return DCR_Ok; +} +DISPATCHABLE_COMMAND( session 3 2 0 ){ + int rc = 0; + struct AuxDb *pAuxDb = ISS(p)->pAuxDb; + OpenSession *pSession = &pAuxDb->aSession[0]; + FILE *out = ISS(p)->out; + char **azCmd = &azArg[1]; + int iSes = 0; + int nCmd = nArg - 1; + int i; + open_db(p, 0); + if( nArg>=3 ){ + for(iSes=0; iSesnSession; iSes++){ + if( strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break; + } + if( iSesnSession ){ + pSession = &pAuxDb->aSession[iSes]; + azCmd++; + nCmd--; + }else{ + pSession = &pAuxDb->aSession[0]; + iSes = 0; } - }else -#endif + } - if( c=='l' && strncmp(azArg[0], "log", n)==0 ){ - failIfSafeMode(p, "cannot run .log in safe mode"); - if( nArg!=2 ){ - raw_printf(stderr, "Usage: .log FILENAME\n"); - rc = 1; + /* .session attach TABLE + ** Invoke the sqlite3session_attach() interface to attach a particular + ** table so that it is never filtered. + */ + if( strcmp(azCmd[0],"attach")==0 ){ + if( nCmd!=2 ) goto session_syntax_error; + if( pSession->p==0 ){ + session_not_open: + raw_printf(STD_ERR, "ERROR: No sessions are open\n"); }else{ - const char *zFile = azArg[1]; - output_file_close(p->pLog); - p->pLog = output_file_open(zFile, 0); + rc = sqlite3session_attach(pSession->p, azCmd[1]); + if( rc ){ + raw_printf(STD_ERR, "ERROR: sqlite3session_attach() returns %d\n", rc); + rc = 0; + } } }else - if( c=='m' && strncmp(azArg[0], "mode", n)==0 ){ - const char *zMode = 0; - const char *zTabname = 0; - int i, n2; - ColModeOpts cmOpts = ColModeOpts_default; - for(i=1; ip==0 ) goto session_not_open; + cs_out = fopen(azCmd[1], "wb"); + if( cs_out==0 ){ - *pzErr = sqlite3_mprintf - ("ERROR: cannot open \"%s\" for writing\n", azCmd[1]); ++ *pzErr = smprintf("cannot open \"%s\" for writing\n", azCmd[1]); rc = 1; - goto meta_command_exit; }else{ - utf8_printf(stderr, "extra argument: \"%s\"\n", z); - rc = 1; - goto meta_command_exit; + int szChng; + void *pChng; + if( azCmd[0][0]=='c' ){ + rc = sqlite3session_changeset(pSession->p, &szChng, &pChng); + }else{ + rc = sqlite3session_patchset(pSession->p, &szChng, &pChng); + } + if( rc ){ + fprintf(out, "Error: error code %d\n", rc); + rc = 0; + } + if( pChng && fwrite(pChng, szChng, 1, cs_out)!=1 ){ + raw_printf(STD_ERR, "ERROR: Failed to write entire %d-byte output\n", + szChng); + } + sqlite3_free(pChng); + fclose(cs_out); } } - if( zMode==0 ){ - if( p->mode==MODE_Column - || (p->mode>=MODE_Markdown && p->mode<=MODE_Box) - ){ - raw_printf - (p->out, - "current output mode: %s --wrap %d --wordwrap %s --%squote\n", - modeDescr[p->mode], p->cmOpts.iWrap, - p->cmOpts.bWordWrap ? "on" : "off", - p->cmOpts.bQuote ? "" : "no"); - }else{ - raw_printf(p->out, "current output mode: %s\n", modeDescr[p->mode]); - } - zMode = modeDescr[p->mode]; - } - n2 = strlen30(zMode); - if( strncmp(zMode,"lines",n2)==0 ){ - p->mode = MODE_Line; - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - }else if( strncmp(zMode,"columns",n2)==0 ){ - p->mode = MODE_Column; - if( (p->shellFlgs & SHFLG_HeaderSet)==0 ){ - p->showHeader = 1; - } - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - p->cmOpts = cmOpts; - }else if( strncmp(zMode,"list",n2)==0 ){ - p->mode = MODE_List; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Column); - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - }else if( strncmp(zMode,"html",n2)==0 ){ - p->mode = MODE_Html; - }else if( strncmp(zMode,"tcl",n2)==0 ){ - p->mode = MODE_Tcl; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Space); - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - }else if( strncmp(zMode,"csv",n2)==0 ){ - p->mode = MODE_Csv; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf); - }else if( strncmp(zMode,"tabs",n2)==0 ){ - p->mode = MODE_List; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Tab); - }else if( strncmp(zMode,"insert",n2)==0 ){ - p->mode = MODE_Insert; - set_table_name(p, zTabname ? zTabname : "table"); - }else if( strncmp(zMode,"quote",n2)==0 ){ - p->mode = MODE_Quote; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); - }else if( strncmp(zMode,"ascii",n2)==0 ){ - p->mode = MODE_Ascii; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Unit); - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Record); - }else if( strncmp(zMode,"markdown",n2)==0 ){ - p->mode = MODE_Markdown; - p->cmOpts = cmOpts; - }else if( strncmp(zMode,"table",n2)==0 ){ - p->mode = MODE_Table; - p->cmOpts = cmOpts; - }else if( strncmp(zMode,"box",n2)==0 ){ - p->mode = MODE_Box; - p->cmOpts = cmOpts; - }else if( strncmp(zMode,"count",n2)==0 ){ - p->mode = MODE_Count; - }else if( strncmp(zMode,"off",n2)==0 ){ - p->mode = MODE_Off; - }else if( strncmp(zMode,"json",n2)==0 ){ - p->mode = MODE_Json; - }else{ - raw_printf(stderr, "Error: mode should be one of: " - "ascii box column csv html insert json line list markdown " - "qbox quote table tabs tcl\n"); - rc = 1; - } - p->cMode = p->mode; }else - if( c=='n' && strcmp(azArg[0], "nonce")==0 ){ - if( nArg!=2 ){ - raw_printf(stderr, "Usage: .nonce NONCE\n"); - rc = 1; - }else if( p->zNonce==0 || strcmp(azArg[1],p->zNonce)!=0 ){ - raw_printf(stderr, "line %d: incorrect nonce: \"%s\"\n", - p->lineno, azArg[1]); - exit(1); - }else{ - p->bSafeMode = 0; - return 0; /* Return immediately to bypass the safe mode reset - ** at the end of this procedure */ + /* .session close + ** Close the identified session + */ + if( strcmp(azCmd[0], "close")==0 ){ + if( nCmd!=1 ) goto session_syntax_error; + if( pAuxDb->nSession ){ + session_close(pSession); + pAuxDb->aSession[iSes] = pAuxDb->aSession[--pAuxDb->nSession]; } }else @@@ -11563,1792 -9495,1783 +11517,1823 @@@ } }else - if( c=='o' && strncmp(azArg[0], "open", n)==0 && n>=2 ){ - const char *zFN = 0; /* Pointer to constant filename */ - char *zNewFilename = 0; /* Name of the database file to open */ - int iName = 1; /* Index in azArg[] of the filename */ - int newFlag = 0; /* True to delete file before opening */ - int openMode = SHELL_OPEN_UNSPEC; - - /* Check for command-line arguments */ - for(iName=1; iNameopenFlags |= SQLITE_OPEN_NOFOLLOW; -#ifndef SQLITE_OMIT_DESERIALIZE - }else if( optionMatch(z, "deserialize") ){ - openMode = SHELL_OPEN_DESERIALIZE; - }else if( optionMatch(z, "hexdb") ){ - openMode = SHELL_OPEN_HEXDB; - }else if( optionMatch(z, "maxsize") && iName+1szMax = integerValue(azArg[++iName]); -#endif /* SQLITE_OMIT_DESERIALIZE */ - }else if( z[0]=='-' ){ - utf8_printf(stderr, "unknown option: %s\n", z); - rc = 1; - goto meta_command_exit; - }else if( zFN ){ - utf8_printf(stderr, "extra argument: \"%s\"\n", z); - rc = 1; - goto meta_command_exit; - }else{ - zFN = z; - } - } - - /* Close the existing database */ - session_close_all(p, -1); - close_db(p->db); - p->db = 0; - p->pAuxDb->zDbFilename = 0; - sqlite3_free(p->pAuxDb->zFreeOnClose); - p->pAuxDb->zFreeOnClose = 0; - p->openMode = openMode; - p->openFlags = 0; - p->szMax = 0; - - /* If a filename is specified, try to open it first */ - if( zFN || p->openMode==SHELL_OPEN_HEXDB ){ - if( newFlag && zFN && !p->bSafeMode ) shellDeleteFile(zFN); - if( p->bSafeMode - && p->openMode!=SHELL_OPEN_HEXDB - && zFN - && strcmp(zFN,":memory:")!=0 - ){ - failIfSafeMode(p, "cannot open disk-based database files in safe mode"); + /* .session filter GLOB .... + ** Set a list of GLOB patterns of table names to be excluded. + */ + if( strcmp(azCmd[0], "filter")==0 ){ + int ii, nByte; + if( nCmd<2 ) goto session_syntax_error; + if( pAuxDb->nSession ){ + for(ii=0; iinFilter; ii++){ + sqlite3_free(pSession->azFilter[ii]); } - if( zFN ){ - zNewFilename = sqlite3_mprintf("%s", zFN); - shell_check_oom(zNewFilename); - }else{ - zNewFilename = 0; + sqlite3_free(pSession->azFilter); + nByte = sizeof(pSession->azFilter[0])*(nCmd-1); + pSession->azFilter = sqlite3_malloc( nByte ); + if( pSession->azFilter==0 ){ + shell_out_of_memory(); } - p->pAuxDb->zDbFilename = zNewFilename; - open_db(p, OPEN_DB_KEEPALIVE); - if( p->db==0 ){ - utf8_printf(stderr, "Error: cannot open '%s'\n", zNewFilename); - sqlite3_free(zNewFilename); - }else{ - p->pAuxDb->zFreeOnClose = zNewFilename; + for(ii=1; iiazFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]); ++ pSession->azFilter[ii-1] = smprintf("%s", azCmd[ii]); + shell_check_oom(pSession->azFilter[ii-1]); } - } - if( p->db==0 ){ - /* As a fall-back open a TEMP database */ - p->pAuxDb->zDbFilename = 0; - open_db(p, 0); + pSession->nFilter = ii-1; } }else - if( (c=='o' - && (strncmp(azArg[0], "output", n)==0||strncmp(azArg[0], "once", n)==0)) - || (c=='e' && n==5 && strcmp(azArg[0],"excel")==0) - ){ - char *zFile = 0; - int bTxtMode = 0; - int i; - int eMode = 0; - int bBOM = 0; - int bOnce = 0; /* 0: .output, 1: .once, 2: .excel */ - - failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]); - if( c=='e' ){ - eMode = 'x'; - bOnce = 2; - }else if( strncmp(azArg[0],"once",n)==0 ){ - bOnce = 1; - } - for(i=1; iout, "ERROR: unknown option: \"%s\". Usage:\n", - azArg[i]); - showHelp(p->out, azArg[0]); - rc = 1; - goto meta_command_exit; - } - }else if( zFile==0 && eMode!='e' && eMode!='x' ){ - zFile = sqlite3_mprintf("%s", z); - if( zFile && zFile[0]=='|' ){ - while( i+1out,"ERROR: extra parameter: \"%s\". Usage:\n", - azArg[i]); - showHelp(p->out, azArg[0]); - rc = 1; - sqlite3_free(zFile); - goto meta_command_exit; - } + /* .session indirect ?BOOLEAN? + ** Query or set the indirect flag + */ + if( strcmp(azCmd[0], "indirect")==0 ){ + int ii; + if( nCmd>2 ) goto session_syntax_error; + ii = nCmd==1 ? -1 : booleanValue(azCmd[1]); + if( pAuxDb->nSession ){ + ii = sqlite3session_indirect(pSession->p, ii); + utf8_printf(out, "session %s indirect flag = %d\n", + pSession->zName, ii); } - if( zFile==0 ){ - zFile = sqlite3_mprintf("stdout"); + }else + + /* .session isempty + ** Determine if the session is empty + */ + if( strcmp(azCmd[0], "isempty")==0 ){ + int ii; + if( nCmd!=1 ) goto session_syntax_error; + if( pAuxDb->nSession ){ + ii = sqlite3session_isempty(pSession->p); + utf8_printf(out, "session %s isempty flag = %d\n", + pSession->zName, ii); } - if( bOnce ){ - p->outCount = 2; - }else{ - p->outCount = 0; + }else + + /* .session list + ** List all currently open sessions + */ + if( strcmp(azCmd[0],"list")==0 ){ + for(i=0; inSession; i++){ + utf8_printf(out, "%d %s\n", i, pAuxDb->aSession[i].zName); } - output_reset(p); -#ifndef SQLITE_NOHAVE_SYSTEM - if( eMode=='e' || eMode=='x' ){ - p->doXdgOpen = 1; - outputModePush(p); - if( eMode=='x' ){ - /* spreadsheet mode. Output as CSV. */ - newTempFile(p, "csv"); - ShellClearFlag(p, SHFLG_Echo); - p->mode = MODE_Csv; - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf); - }else{ - /* text editor mode */ - newTempFile(p, "txt"); - bTxtMode = 1; - } - sqlite3_free(zFile); - zFile = sqlite3_mprintf("%s", p->zTempFile); + }else + + /* .session open DB NAME + ** Open a new session called NAME on the attached database DB. + ** DB is normally "main". + */ + if( strcmp(azCmd[0],"open")==0 ){ + char *zName; + if( nCmd!=3 ) goto session_syntax_error; + zName = azCmd[2]; + if( zName[0]==0 ) goto session_syntax_error; + for(i=0; inSession; i++){ + if( strcmp(pAuxDb->aSession[i].zName,zName)==0 ){ + utf8_printf(STD_ERR, "Session \"%s\" already exists\n", zName); + return rc; + } + } + if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){ + raw_printf + (STD_ERR, "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession)); + return rc; + } + pSession = &pAuxDb->aSession[pAuxDb->nSession]; + rc = sqlite3session_create(DBX(p), azCmd[1], &pSession->p); + if( rc ){ - *pzErr = sqlite3_mprintf - ("Cannot open session: error code=%d\n", rc); ++ *pzErr = smprintf("Cannot open session: error code=%d\n", rc); + return rc; } -#endif /* SQLITE_NOHAVE_SYSTEM */ - shell_check_oom(zFile); - if( zFile[0]=='|' ){ -#ifdef SQLITE_OMIT_POPEN - raw_printf(stderr, "Error: pipes are not supported in this OS\n"); - rc = 1; - p->out = stdout; -#else - p->out = popen(zFile + 1, "w"); - if( p->out==0 ){ - utf8_printf(stderr,"Error: cannot open pipe \"%s\"\n", zFile + 1); - p->out = stdout; - rc = 1; - }else{ - if( bBOM ) fprintf(p->out,"\357\273\277"); - sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); - } -#endif + pSession->nFilter = 0; + sqlite3session_table_filter(pSession->p, session_filter, pSession); + pAuxDb->nSession++; - shell_newstr_assign(&pSession->zName, sqlite3_mprintf("%s", zName)); ++ shell_newstr_assign(&pSession->zName, smprintf("%s", zName)); + }else{ + + /* If no command name matches, show a syntax error */ + session_syntax_error: + showHelp(out, "session", p); - return DCR_ArgError; ++ return DCR_CmdErred; + } + return rc; +} +DISPATCHABLE_COMMAND( sha3sum 4 1 1 ){ + const char *zLike = 0; /* Which table to checksum. 0 means everything */ + int i; /* Loop counter */ + int bSchema = 0; /* Also hash the schema */ + int bSeparate = 0; /* Hash each table separately */ + int iSize = 224; /* Hash algorithm to use */ + int bDebug = 0; /* Only show the query that would have run */ + sqlite3_stmt *pStmt; /* For querying tables names */ + char *zSql; /* SQL to be run */ + char *zSep; /* Separator */ + ShellText sSql; /* Complete SQL for the query to run the hash */ + ShellText sQuery; /* Set of queries used to read all content */ + open_db(p, 0); + for(i=1; iout = output_file_open(zFile, bTxtMode); - if( p->out==0 ){ - if( strcmp(zFile,"off")!=0 ){ - utf8_printf(stderr,"Error: cannot write to \"%s\"\n", zFile); - } - p->out = stdout; - rc = 1; - } else { - if( bBOM ) fprintf(p->out,"\357\273\277"); - sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); - } + zLike = z; + bSeparate = 1; + if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 1; } - sqlite3_free(zFile); - }else + } + if( bSchema ){ + zSql = "SELECT lower(name) FROM sqlite_schema" + " WHERE type='table' AND coalesce(rootpage,0)>1" + " UNION ALL SELECT 'sqlite_schema'" + " ORDER BY 1 collate nocase"; + }else{ + zSql = "SELECT lower(name) FROM sqlite_schema" + " WHERE type='table' AND coalesce(rootpage,0)>1" + " AND name NOT LIKE 'sqlite_%'" + " ORDER BY 1 collate nocase"; + } + sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0); + initText(&sQuery); + initText(&sSql); + appendText(&sSql, "WITH [sha3sum$query](a,b) AS(",0); + zSep = "VALUES("; + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + const char *zTab = (const char*)sqlite3_column_text(pStmt,0); + if( zTab==0 ) continue; + if( zLike && sqlite3_strlike(zLike, zTab, 0)!=0 ) continue; + if( strncmp(zTab, "sqlite_",7)!=0 ){ + appendText(&sQuery,"SELECT * FROM ", 0); + appendText(&sQuery,zTab,'"'); + appendText(&sQuery," NOT INDEXED;", 0); + }else if( strcmp(zTab, "sqlite_schema")==0 ){ + appendText(&sQuery,"SELECT type,name,tbl_name,sql FROM sqlite_schema" + " ORDER BY name;", 0); + }else if( strcmp(zTab, "sqlite_sequence")==0 ){ + appendText(&sQuery,"SELECT name,seq FROM sqlite_sequence" + " ORDER BY name;", 0); + }else if( strcmp(zTab, "sqlite_stat1")==0 ){ + appendText(&sQuery,"SELECT tbl,idx,stat FROM sqlite_stat1" + " ORDER BY tbl,idx;", 0); + }else if( strcmp(zTab, "sqlite_stat4")==0 ){ + appendText(&sQuery, "SELECT * FROM ", 0); + appendText(&sQuery, zTab, 0); + appendText(&sQuery, " ORDER BY tbl, idx, rowid;\n", 0); + } + appendText(&sSql, zSep, 0); + appendText(&sSql, sQuery.z, '\''); + sQuery.n = 0; + appendText(&sSql, ",", 0); + appendText(&sSql, zTab, '\''); + zSep = "),("; + } + sqlite3_finalize(pStmt); + if( bSeparate ){ - zSql = sqlite3_mprintf( ++ zSql = smprintf( + "%s))" + " SELECT lower(hex(sha3_query(a,%d))) AS hash, b AS label" + " FROM [sha3sum$query]", + sSql.z, iSize); + }else{ - zSql = sqlite3_mprintf( ++ zSql = smprintf( + "%s))" + " SELECT lower(hex(sha3_query(group_concat(a,''),%d))) AS hash" + " FROM [sha3sum$query]", + sSql.z, iSize); + } + shell_check_oom(zSql); + freeText(&sQuery); + freeText(&sSql); + if( bDebug ){ + utf8_printf(ISS(p)->out, "%s\n", zSql); + }else{ + shell_exec(p, zSql, 0); + } + sqlite3_free(zSql); + return DCR_Ok; +} - if( c=='p' && n>=3 && strncmp(azArg[0], "parameter", n)==0 ){ - open_db(p,0); - if( nArg<=1 ) goto parameter_syntax_error; +/***************** + * The .selftest*, .shell, .show, .shxopts, .stats and .system commands + */ +CONDITION_COMMAND( selftest_bool defined(SQLITE_DEBUG) ); +CONDITION_COMMAND( selftest_int defined(SQLITE_DEBUG) ); +CONDITION_COMMAND( shell !defined(SQLITE_NOHAVE_SYSTEM) ); +CONDITION_COMMAND( shxopts (SHELL_EXTENSIONS)!=0 ); +CONDITION_COMMAND( system !defined(SQLITE_NOHAVE_SYSTEM) ); +COLLECT_HELP_TEXT[ + ",selftest ?OPTIONS? Run tests defined in the SELFTEST table", + " Options:", + " --init Create a new SELFTEST table", + " -v Verbose output", + ",selftest_bool ?ARGS? Show boolean values of ARGS as flag tokens", + ",selftest_int ?ARGS? Show integer values of ARGS as integer tokens", + ".shell CMD ARGS... Run CMD ARGS... in a system shell", + ".show Show the current values for various settings", + ".shxopts ?SIGNED_OPTS? Show or alter shell extension options", + " Run without arguments to see their self-descriptive names", + ".stats ?ARG? Show stats or turn stats on or off", + " off Turn off automatic stat display", + " on Turn on automatic stat display", + " stmt Show statement stats", + " vmstep Show the virtual machine step count only", + ".system CMD ARGS... Run CMD ARGS... in a system shell", +]; - /* .parameter clear - ** Clear all bind parameters by dropping the TEMP table that holds them. - */ - if( nArg==2 && strcmp(azArg[1],"clear")==0 ){ - sqlite3_exec(p->db, "DROP TABLE IF EXISTS temp.sqlite_parameters;", - 0, 0, 0); - }else +DISPATCHABLE_COMMAND( selftest_bool 10 0 0 ){ + int i, v; + for(i=1; iout, "%s: %d 0x%x\n", azArg[i], v, v); + } + return DCR_Ok; +} +DISPATCHABLE_COMMAND( selftest_int 10 0 0 ){ + int i; sqlite3_int64 v; + for(i=1; iout, "%s", zBuf); + } + return DCR_Ok; +} - /* .parameter list - ** List all bind parameters. - */ - if( nArg==2 && strcmp(azArg[1],"list")==0 ){ - sqlite3_stmt *pStmt = 0; - int rx; - int len = 0; - rx = sqlite3_prepare_v2(p->db, - "SELECT max(length(key)) " - "FROM temp.sqlite_parameters;", -1, &pStmt, 0); - if( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ - len = sqlite3_column_int(pStmt, 0); - if( len>40 ) len = 40; - } - sqlite3_finalize(pStmt); - pStmt = 0; - if( len ){ - rx = sqlite3_prepare_v2(p->db, - "SELECT key, quote(value) " - "FROM temp.sqlite_parameters;", -1, &pStmt, 0); - while( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ - utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0), - sqlite3_column_text(pStmt,1)); - } - sqlite3_finalize(pStmt); - } - }else +DISPATCHABLE_COMMAND( selftest 4 0 0 ){ + int rc; + ShellInState *psi = ISS(p); + int bIsInit = 0; /* True to initialize the SELFTEST table */ + int bVerbose = 0; /* Verbose output */ + int bSelftestExists; /* True if SELFTEST already exists */ + int i, k; /* Loop counters */ + int nTest = 0; /* Number of tests runs */ + int nErr = 0; /* Number of errors seen */ + ShellText str; /* Answer for a query */ + sqlite3_stmt *pStmt = 0; /* Query against the SELFTEST table */ - /* .parameter init - ** Make sure the TEMP table used to hold bind parameters exists. - ** Create it if necessary. - */ - if( nArg==2 && strcmp(azArg[1],"init")==0 ){ - bind_table_init(p); + for(i=1; idb, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); - if( rx!=SQLITE_OK ){ - sqlite3_finalize(pStmt); - pStmt = 0; - zSql = sqlite3_mprintf( - "REPLACE INTO temp.sqlite_parameters(key,value)" - "VALUES(%Q,%Q);", zKey, zValue); - shell_check_oom(zSql); - rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); - if( rx!=SQLITE_OK ){ - utf8_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db)); - sqlite3_finalize(pStmt); - pStmt = 0; - rc = 1; + if( strcmp(z,"-v")==0 ){ + bVerbose++; + }else + { - *pzErr = sqlite3_mprintf ++ *pzErr = smprintf + ("Unknown option \"%s\" on \"%s\"\n" + "Should be one of: --init -v\n", azArg[i], azArg[0]); - return DCR_ArgError; ++ return DCR_ArgWrong; } - } - sqlite3_step(pStmt); - sqlite3_finalize(pStmt); - }else - - /* .parameter unset NAME - ** Remove the NAME binding from the parameter binding table, if it - ** exists. - */ - if( nArg==3 && strcmp(azArg[1],"unset")==0 ){ - char *zSql = sqlite3_mprintf( - "DELETE FROM temp.sqlite_parameters WHERE key=%Q", azArg[2]); - shell_check_oom(zSql); - sqlite3_exec(p->db, zSql, 0, 0, 0); - sqlite3_free(zSql); - }else - /* If no command name matches, show a syntax error */ - parameter_syntax_error: - showHelp(p->out, "parameter"); - }else - - if( c=='p' && n>=3 && strncmp(azArg[0], "print", n)==0 ){ - int i; - for(i=1; i1 ) raw_printf(p->out, " "); - utf8_printf(p->out, "%s", azArg[i]); + } + open_db(p,0); + if( sqlite3_table_column_metadata(DBX(p),"main","selftest",0,0,0,0,0,0) + != SQLITE_OK ){ + bSelftestExists = 0; + }else{ + bSelftestExists = 1; + } + if( bIsInit ){ + createSelftestTable(ISS(p)); + bSelftestExists = 1; + } + initText(&str); + appendText(&str, "x", 0); + for(k=bSelftestExists; k>=0; k--){ + if( k==1 ){ + rc = sqlite3_prepare_v2(DBX(p), + "SELECT tno,op,cmd,ans FROM selftest ORDER BY tno", + -1, &pStmt, 0); + }else{ + rc = sqlite3_prepare_v2(DBX(p), + "VALUES(0,'memo','Missing SELFTEST table - default checks only','')," + " (1,'run','PRAGMA integrity_check','ok')", + -1, &pStmt, 0); } - raw_printf(p->out, "\n"); - }else - -#ifndef SQLITE_OMIT_PROGRESS_CALLBACK - if( c=='p' && n>=3 && strncmp(azArg[0], "progress", n)==0 ){ - int i; - int nn = 0; - p->flgProgress = 0; - p->mxProgress = 0; - p->nProgress = 0; - for(i=1; iflgProgress |= SHELL_PROGRESS_QUIET; - continue; - } - if( strcmp(z,"reset")==0 ){ - p->flgProgress |= SHELL_PROGRESS_RESET; - continue; - } - if( strcmp(z,"once")==0 ){ - p->flgProgress |= SHELL_PROGRESS_ONCE; - continue; + if( rc ){ - *pzErr = shellMPrintf(0,"Error querying the selftest table\n"); ++ *pzErr = smprintf("Error querying the selftest table\n"); + sqlite3_finalize(pStmt); + return DCR_Error; + } + for(i=1; sqlite3_step(pStmt)==SQLITE_ROW; i++){ + int tno = sqlite3_column_int(pStmt, 0); + const char *zOp = (const char*)sqlite3_column_text(pStmt, 1); + const char *zSql = (const char*)sqlite3_column_text(pStmt, 2); + const char *zAns = (const char*)sqlite3_column_text(pStmt, 3); + + if( zOp==0 || zSql==0 || zAns==0 ) continue; + k = 0; + if( bVerbose>0 ){ + /* This unusually directed output is for test purposes. */ + fprintf(STD_OUT, "%d: %s %s\n", tno, zOp, zSql); + } + if( strcmp(zOp,"memo")==0 ){ + utf8_printf(psi->out, "%s\n", zSql); + }else if( strcmp(zOp,"run")==0 ){ + char *zErrMsg = 0; + str.n = 0; + str.z[0] = 0; + rc = sqlite3_exec(DBX(p), zSql, captureOutputCallback, &str, &zErrMsg); + nTest++; + if( bVerbose ){ + utf8_printf(psi->out, "Result: %s\n", str.z); } - if( strcmp(z,"limit")==0 ){ - if( i+1>=nArg ){ - utf8_printf(stderr, "Error: missing argument on --limit\n"); - rc = 1; - goto meta_command_exit; - }else{ - p->mxProgress = (int)integerValue(azArg[++i]); - } - continue; + if( rc || zErrMsg ){ + nErr++; + rc = 1; + utf8_printf(psi->out, "%d: error-code-%d: %s\n", tno, rc, zErrMsg); + sqlite3_free(zErrMsg); + }else if( strcmp(zAns,str.z)!=0 ){ + nErr++; + rc = 1; + utf8_printf(psi->out, "%d: Expected: [%s]\n", tno, zAns); + utf8_printf(psi->out, "%d: Got: [%s]\n", tno, str.z); } - utf8_printf(stderr, "Error: unknown option: \"%s\"\n", azArg[i]); - rc = 1; - goto meta_command_exit; }else{ - *pzErr = sqlite3_mprintf - nn = (int)integerValue(z); - } - } - open_db(p, 0); - sqlite3_progress_handler(p->db, nn, progress_handler, p); - }else -#endif /* SQLITE_OMIT_PROGRESS_CALLBACK */ - - if( c=='p' && strncmp(azArg[0], "prompt", n)==0 ){ - if( nArg >= 2) { - strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1); - } - if( nArg >= 3) { - strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1); - } - }else - - if( c=='q' && strncmp(azArg[0], "quit", n)==0 ){ - rc = 2; - }else - - if( c=='r' && n>=3 && strncmp(azArg[0], "read", n)==0 ){ - FILE *inSaved = p->in; - int savedLineno = p->lineno; - failIfSafeMode(p, "cannot run .read in safe mode"); - if( nArg!=2 ){ - raw_printf(stderr, "Usage: .read FILE\n"); - rc = 1; - goto meta_command_exit; - } - if( azArg[1][0]=='|' ){ -#ifdef SQLITE_OMIT_POPEN - raw_printf(stderr, "Error: pipes are not supported in this OS\n"); - rc = 1; - p->out = stdout; -#else - p->in = popen(azArg[1]+1, "r"); - if( p->in==0 ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]); ++ *pzErr = smprintf + ("Unknown operation \"%s\" on selftest line %d\n", zOp, tno); rc = 1; - }else{ - rc = process_input(p); - pclose(p->in); + break; } + } /* End loop over rows of content from SELFTEST */ + sqlite3_finalize(pStmt); + } /* End loop over k */ + freeText(&str); + utf8_printf(psi->out, "%d errors out of %d tests\n", nErr, nTest); + return rc > 0; +} + +DISPATCHABLE_COMMAND( shell ? 2 0 ){ + char *zCmd; + int i, x; + if( ISS(p)->bSafeMode ) return DCR_AbortError; - zCmd = sqlite3_mprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]); ++ zCmd = smprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]); + shell_check_oom(zCmd); + for(i=2; iin = openChrSource(azArg[1]))==0 ){ - utf8_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); - rc = 1; +#if SHELL_EXTENDED_PARSING + {"parsing", 1<1 ){ + for( ia=1; iabExtendedDotCmds |= shopts[io].mask; + else psi->bExtendedDotCmds &= ~shopts[io].mask; + break; + } + } + if( io==ArraySize(shopts) ){ + zAbout = azArg[ia]; + zMoan = "is not a recognized option name"; + goto moan_error; + } + } + }else{ + raw_printf(psi->out, + " name value \"-shxopts set\"\n" + " -------- ----- ---------------\n"); + for( io=0; iobExtendedDotCmds & m) == m)? 1 : 0; + raw_printf(psi->out, + " %9s %2d \"-shxopts 0x%02X\"\n", + shopts[io].name, v, m); + } + } + return DCR_Ok; + moan_error: + raw_printf(STD_ERR, "Error: %s %s\n", zAbout, zMoan); - return DCR_ArgError; ++ return DCR_CmdErred; +} +DISPATCHABLE_COMMAND( system ? 2 0 ){ + return shellCommand(azArg, nArg, p, pzErr); +} +DISPATCHABLE_COMMAND( show ? 1 1 ){ + static const char *azBool[] = { "off", "on", "trigger", "full"}; + const char *zOut; + ShellInState *psi = ISS(p); + FILE *out = psi->out; + int i; + utf8_printf(out, "%12.12s: %s\n","echo", + azBool[ShellHasFlag(p, SHFLG_Echo)]); + utf8_printf(out, "%12.12s: %s\n","eqp", azBool[psi->autoEQP&3]); + utf8_printf(out, "%12.12s: %s\n","explain", + psi->mode==MODE_Explain + ? "on" : psi->autoExplain ? "auto" : "off"); + utf8_printf(out,"%12.12s: %s\n","headers", azBool[psi->showHeader!=0]); + zOut = modeDescr[psi->mode].zModeName; + i = strlen30(zOut) - modeDescr[psi->mode].bDepluralize; + if( MODE_IS_COLUMNAR(psi->mode) ){ + utf8_printf + (out, "%12.12s: %.*s --wrap %d --wordwrap %s --%squote\n", "mode", + i, zOut, psi->cmOpts.iWrap, + psi->cmOpts.bWordWrap ? "on" : "off", + psi->cmOpts.bQuote ? "" : "no"); + }else{ + utf8_printf(out, "%12.12s: %.*s\n","mode", i, zOut); + } + utf8_printf(out, "%12.12s: ", "nullvalue"); + output_c_string(out, psi->nullValue); + raw_printf(out, "\n"); + utf8_printf(out,"%12.12s: %s\n","output", + strlen30(psi->outfile) ? psi->outfile : "stdout"); + utf8_printf(out,"%12.12s: ", "colseparator"); + output_c_string(out, psi->colSeparator); + raw_printf(out, "\n"); + utf8_printf(out,"%12.12s: ", "rowseparator"); + output_c_string(out, psi->rowSeparator); + raw_printf(out, "\n"); + switch( psi->statsOn ){ + case 0: zOut = "off"; break; + default: zOut = "on"; break; + case 2: zOut = "stmt"; break; + case 3: zOut = "vmstep"; break; + } + utf8_printf(out, "%12.12s: %s\n","stats", zOut); + utf8_printf(out, "%12.12s: ", "width"); + for (i=0;inumWidths;i++) { + raw_printf(out, "%d ", p->pSpecWidths[i]); + } + raw_printf(out, "\n"); + utf8_printf(out, "%12.12s: %s\n", "filename", + psi->pAuxDb->zDbFilename ? psi->pAuxDb->zDbFilename : ""); + return DCR_Ok; +} +DISPATCHABLE_COMMAND( stats ? 0 0 ){ + ShellInState *psi = ISS(p); + if( nArg==2 ){ + if( strcmp(azArg[1],"stmt")==0 ){ + psi->statsOn = 2; + }else if( strcmp(azArg[1],"vmstep")==0 ){ + psi->statsOn = 3; }else{ - rc = process_input(p); - fclose(p->in); + psi->statsOn = (u8)booleanValue(azArg[1]); } - p->in = inSaved; - p->lineno = savedLineno; - }else + }else if( nArg==1 ){ + display_stats(DBX(p), psi, 0); + }else{ - *pzErr = shellMPrintf(0,"Usage: .stats ?on|off|stmt|vmstep?\n"); - return DCR_ArgError; ++ *pzErr = smprintf("Usage: .stats ?on|off|stmt|vmstep?\n"); ++ return DCR_SayUsage; + } + return DCR_Ok; +} + +/***************** + * The .tables, .views, .indices and .indexes command + * These are together because they share implementation or are aliases. + */ +COLLECT_HELP_TEXT[ + ".indexes ?TABLE? Show names of indexes", + " If TABLE is specified, only show indexes for", + " tables matching TABLE using the LIKE operator.", +]; +static int showTableLike(char *azArg[], int nArg, ShellExState *p, + char **pzErr, char ot){ + int rc; + sqlite3_stmt *pStmt; + char **azResult; + int nRow, nAlloc; + int ii; + ShellText s; + initText(&s); + open_db(p, 0); + rc = sqlite3_prepare_v2(DBX(p), "PRAGMA database_list", -1, &pStmt, 0); + if( rc ){ + sqlite3_finalize(pStmt); + return shellDatabaseError(DBX(p)); + } - if( c=='r' && n>=3 && strncmp(azArg[0], "restore", n)==0 ){ - const char *zSrcFile; - const char *zDb; - sqlite3 *pSrc; - sqlite3_backup *pBackup; - int nTimeout = 0; - - failIfSafeMode(p, "cannot run .restore in safe mode"); - if( nArg==2 ){ - zSrcFile = azArg[1]; - zDb = "main"; - }else if( nArg==3 ){ - zSrcFile = azArg[2]; - zDb = azArg[1]; + if( nArg>2 && ot=='i' ){ + /* It is an historical accident that the .indexes command shows an error + ** when called with the wrong number of arguments whereas the .tables + ** command does not. */ - *pzErr = shellMPrintf(0,"Usage: .indexes ?LIKE-PATTERN?\n"); ++ *pzErr = smprintf("Usage: .indexes ?LIKE-PATTERN?\n"); + sqlite3_finalize(pStmt); - return DCR_ArgError; ++ return DCR_SayUsage; + } + for(ii=0; sqlite3_step(pStmt)==SQLITE_ROW; ii++){ + const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1); + const char *zFilter = ""; + const char *zSystem = " AND name NOT LIKE 'sqlite_%'"; + if( zDbName==0 ) continue; + if( s.z && s.z[0] ) appendText(&s, " UNION ALL ", 0); + if( sqlite3_stricmp(zDbName, "main")==0 ){ + appendText(&s, "SELECT name FROM ", 0); }else{ - raw_printf(stderr, "Usage: .restore ?DB? FILE\n"); - rc = 1; - goto meta_command_exit; - } - rc = sqlite3_open(zSrcFile, &pSrc); - if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", zSrcFile); - close_db(pSrc); - return 1; + appendText(&s, "SELECT ", 0); + appendText(&s, zDbName, '\''); + appendText(&s, "||'.'||name FROM ", 0); + } + appendText(&s, zDbName, '"'); + appendText(&s, ".sqlite_schema ", 0); + switch (ot) { + case 'i': + zFilter = "'index'"; + break; +#ifndef LEGACY_TABLES_LISTING + case 't': + zFilter = "'table'"; + break; + case 'v': + zFilter = "'view'"; + break; +#endif + case 's': + zSystem = " AND name LIKE 'sqlite_%'"; + /* fall thru */ + case 'T': + zFilter = "'table','view'"; + break; + default: + assert(0); } - open_db(p, 0); - pBackup = sqlite3_backup_init(p->db, zDb, pSrc, "main"); - if( pBackup==0 ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); - close_db(pSrc); - return 1; + appendText(&s, " WHERE type IN(", 0); + appendText(&s, zFilter, 0); + appendText(&s, ") AND name LIKE ?1", 0); + appendText(&s, zSystem, 0); + } + rc = sqlite3_finalize(pStmt); + if( rc==SQLITE_OK ){ + appendText(&s, " ORDER BY 1", 0); + rc = sqlite3_prepare_v2(DBX(p), s.z, -1, &pStmt, 0); + } + freeText(&s); + if( rc ) return shellDatabaseError(DBX(p)); + + /* Run the SQL statement prepared by the above block. Store the results + ** as an array of nul-terminated strings in azResult[]. */ + nRow = nAlloc = 0; + azResult = 0; + if( nArg>1 ){ + sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT); + }else{ + sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC); + } + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + if( nRow>=nAlloc ){ + char **azNew; + int n2 = nAlloc*2 + 10; + azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2); + shell_check_oom(azNew); + nAlloc = n2; + azResult = azNew; + } - azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); ++ azResult[nRow] = smprintf("%s", sqlite3_column_text(pStmt, 0)); + shell_check_oom(azResult[nRow]); + nRow++; + } + if( sqlite3_finalize(pStmt)!=SQLITE_OK ){ + rc = shellDatabaseError(DBX(p)); + } + + /* Pretty-print the contents of array azResult[] to the output */ + if( rc==0 && nRow>0 ){ + int len, maxlen = 0; + int i, j; + int nPrintCol, nPrintRow; + for(i=0; imaxlen ) maxlen = len; } - while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK - || rc==SQLITE_BUSY ){ - if( rc==SQLITE_BUSY ){ - if( nTimeout++ >= 3 ) break; - sqlite3_sleep(100); + nPrintCol = 80/(maxlen+2); + if( nPrintCol<1 ) nPrintCol = 1; + nPrintRow = (nRow + nPrintCol - 1)/nPrintCol; + for(i=0; iout, "%s%-*s", zSp, maxlen, + azResult[j] ? azResult[j]:""); } + raw_printf(ISS(p)->out, "\n"); } - sqlite3_backup_finish(pBackup); - if( rc==SQLITE_DONE ){ - rc = 0; - }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){ - raw_printf(stderr, "Error: source database is busy\n"); - rc = 1; - }else{ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); - rc = 1; - } - close_db(pSrc); - }else + } - if( c=='s' && strncmp(azArg[0], "scanstats", n)==0 ){ - if( nArg==2 ){ - p->scanstatsOn = (u8)booleanValue(azArg[1]); -#ifndef SQLITE_ENABLE_STMT_SCANSTATUS - raw_printf(stderr, "Warning: .scanstats not available in this build.\n"); + for(ii=0; ii1 && azArg[1][0]=='-' && azArg[1][1]!=0 && azArg[1][2]==0 ){ + char c = azArg[1][1]; + switch (c){ + case 's': + case 't': + case 'v': + objType = c; + ++azArg; + --nArg; + break; + default: + return DCR_Unknown|1; } - }else + } +#endif + return showTableLike(azArg, nArg, p, pzErr, objType); +} +DISPATCHABLE_COMMAND( indexes 3 1 2 ){ + return showTableLike(azArg, nArg, p, pzErr, 'i'); +} +DISPATCHABLE_COMMAND( indices 3 1 2 ){ + return showTableLike(azArg, nArg, p, pzErr, 'i'); +} - if( c=='s' && strncmp(azArg[0], "schema", n)==0 ){ - ShellText sSelect; - ShellState data; - char *zErrMsg = 0; - const char *zDiv = "("; - const char *zName = 0; - int iSchema = 0; - int bDebug = 0; - int bNoSystemTabs = 0; - int ii; +/***************** + * The .testcase, .testctrl, .timeout, .timer and .trace commands + */ +CONDITION_COMMAND( testctrl !defined(SQLITE_UNTESTABLE) ); +CONDITION_COMMAND( trace !defined(SQLITE_OMIT_TRACE) ); +COLLECT_HELP_TEXT[ + ",testcase NAME Begin redirecting output to 'testcase-out.txt'", + ",testctrl CMD ... Run various sqlite3_test_control() operations", + " Run \".testctrl\" with no arguments for details", + ".timeout MS Try opening locked tables for MS milliseconds", + ".timer on|off Turn SQL timer on or off", + ".trace ?OPTIONS? Output each SQL statement as it is run", + " FILE Send output to FILE", + " stdout Send output to stdout", + " stderr Send output to stderr", + " off Disable tracing", + " --expanded Expand query parameters", +#ifdef SQLITE_ENABLE_NORMALIZE + " --normalized Normal the SQL statements", +#endif + " --plain Show SQL as it is input", + " --stmt Trace statement execution (SQLITE_TRACE_STMT)", + " --profile Profile statements (SQLITE_TRACE_PROFILE)", + " --row Trace each row (SQLITE_TRACE_ROW)", + " --close Trace connection close (SQLITE_TRACE_CLOSE)", +]; + +/* Begin redirecting output to the file "testcase-out.txt" */ +DISPATCHABLE_COMMAND( testcase ? 0 0 ){ + ShellInState *psi = ISS(p); + output_reset(psi); + psi->out = output_file_open("testcase-out.txt", 0); + if( psi->out==0 ){ + raw_printf(STD_ERR, "Error: cannot open 'testcase-out.txt'\n"); + } + if( nArg>=2 ){ + sqlite3_snprintf(sizeof(psi->zTestcase), psi->zTestcase, "%s", azArg[1]); + }else{ + sqlite3_snprintf(sizeof(psi->zTestcase), psi->zTestcase, "?"); + } + return DCR_Ok; +} +DISPATCHABLE_COMMAND( testctrl ? 0 0 ){ + FILE *out = ISS(p)->out; + static const struct { + const char *zCtrlName; /* Name of a test-control option */ + int ctrlCode; /* Integer code for that option */ + int unSafe; /* Not valid for --safe mode */ + const char *zUsage; /* Usage notes */ + } aCtrl[] = { + { "always", SQLITE_TESTCTRL_ALWAYS, 1, "BOOLEAN" }, + { "assert", SQLITE_TESTCTRL_ASSERT, 1, "BOOLEAN" }, + /*{ "benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS,1, "" },*/ + /*{ "bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST, 1, "" },*/ + { "byteorder", SQLITE_TESTCTRL_BYTEORDER, 0, "" }, + { "extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,0,"BOOLEAN" }, + /*{ "fault_install", SQLITE_TESTCTRL_FAULT_INSTALL, 1,"" },*/ + { "imposter", SQLITE_TESTCTRL_IMPOSTER,1,"SCHEMA ON/OFF ROOTPAGE"}, + { "internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS,0,"" }, + { "localtime_fault", SQLITE_TESTCTRL_LOCALTIME_FAULT,0,"BOOLEAN" }, + { "never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT,1, "BOOLEAN" }, + { "optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS,0,"DISABLE-MASK" }, +#ifdef YYCOVERAGE + { "parser_coverage", SQLITE_TESTCTRL_PARSER_COVERAGE,0,"" }, +#endif + { "pending_byte", SQLITE_TESTCTRL_PENDING_BYTE,0, "OFFSET " }, + { "prng_restore", SQLITE_TESTCTRL_PRNG_RESTORE,0, "" }, + { "prng_save", SQLITE_TESTCTRL_PRNG_SAVE, 0, "" }, + { "prng_seed", SQLITE_TESTCTRL_PRNG_SEED, 0, "SEED ?db?" }, + { "seek_count", SQLITE_TESTCTRL_SEEK_COUNT, 0, "" }, + { "sorter_mmap", SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX" }, + { "tune", SQLITE_TESTCTRL_TUNE, 1, "ID VALUE" }, + }; + int testctrl = -1; + int iCtrl = -1; + int rc2 = 0; /* 0: usage. 1: %d 2: %x 3: no-output */ + int isOk = 0; + int i, n2; + const char *zCmd = 0; - open_db(p, 0); - memcpy(&data, p, sizeof(data)); - data.showHeader = 0; - data.cMode = data.mode = MODE_Semi; - initText(&sSelect); - for(ii=1; iidb, "SELECT name FROM pragma_database_list", - -1, &pStmt, 0); - if( rc ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); - sqlite3_finalize(pStmt); - rc = 1; - goto meta_command_exit; - } - appendText(&sSelect, "SELECT sql FROM", 0); - iSchema = 0; - while( sqlite3_step(pStmt)==SQLITE_ROW ){ - const char *zDb = (const char*)sqlite3_column_text(pStmt, 0); - char zScNum[30]; - sqlite3_snprintf(sizeof(zScNum), zScNum, "%d", ++iSchema); - appendText(&sSelect, zDiv, 0); - zDiv = " UNION ALL "; - appendText(&sSelect, "SELECT shell_add_schema(sql,", 0); - if( sqlite3_stricmp(zDb, "main")!=0 ){ - appendText(&sSelect, zDb, '\''); - }else{ - appendText(&sSelect, "NULL", 0); - } - appendText(&sSelect, ",name) AS sql, type, tbl_name, name, rowid,", 0); - appendText(&sSelect, zScNum, 0); - appendText(&sSelect, " AS snum, ", 0); - appendText(&sSelect, zDb, '\''); - appendText(&sSelect, " AS sname FROM ", 0); - appendText(&sSelect, zDb, quoteChar(zDb)); - appendText(&sSelect, ".sqlite_schema", 0); - } - sqlite3_finalize(pStmt); -#ifndef SQLITE_OMIT_INTROSPECTION_PRAGMAS - if( zName ){ - appendText(&sSelect, - " UNION ALL SELECT shell_module_schema(name)," - " 'table', name, name, name, 9e+99, 'main' FROM pragma_module_list", - 0); - } -#endif - appendText(&sSelect, ") WHERE ", 0); - if( zName ){ - char *zQarg = sqlite3_mprintf("%Q", zName); - int bGlob; - shell_check_oom(zQarg); - bGlob = strchr(zName, '*') != 0 || strchr(zName, '?') != 0 || - strchr(zName, '[') != 0; - if( strchr(zName, '.') ){ - appendText(&sSelect, "lower(printf('%s.%s',sname,tbl_name))", 0); - }else{ - appendText(&sSelect, "lower(tbl_name)", 0); - } - appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0); - appendText(&sSelect, zQarg, 0); - if( !bGlob ){ - appendText(&sSelect, " ESCAPE '\\' ", 0); - } - appendText(&sSelect, " AND ", 0); - sqlite3_free(zQarg); - } - if( bNoSystemTabs ){ - appendText(&sSelect, "name NOT LIKE 'sqlite_%%' AND ", 0); - } - appendText(&sSelect, "sql IS NOT NULL" - " ORDER BY snum, rowid", 0); - if( bDebug ){ - utf8_printf(p->out, "SQL: %s;\n", sSelect.z); - }else{ - rc = sqlite3_exec(p->db, sSelect.z, callback, &data, &zErrMsg); - } - freeText(&sSelect); - } - if( zErrMsg ){ - utf8_printf(stderr,"Error: %s\n", zErrMsg); - sqlite3_free(zErrMsg); - rc = 1; - }else if( rc != SQLITE_OK ){ - raw_printf(stderr,"Error: querying schema information\n"); - rc = 1; - }else{ - rc = 0; - } - }else + open_db(p, 0); + zCmd = nArg>=2 ? azArg[1] : "help"; - if( c=='s' && n==11 && strncmp(azArg[0], "selecttrace", n)==0 ){ - unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff; - sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x); - }else + /* The argument can optionally begin with "-" or "--" */ + if( zCmd[0]=='-' && zCmd[1] ){ + zCmd++; + if( zCmd[0]=='-' && zCmd[1] ) zCmd++; + } -#if defined(SQLITE_ENABLE_SESSION) - if( c=='s' && strncmp(azArg[0],"session",n)==0 && n>=3 ){ - struct AuxDb *pAuxDb = p->pAuxDb; - OpenSession *pSession = &pAuxDb->aSession[0]; - char **azCmd = &azArg[1]; - int iSes = 0; - int nCmd = nArg - 1; - int i; - if( nArg<=1 ) goto session_syntax_error; - open_db(p, 0); - if( nArg>=3 ){ - for(iSes=0; iSesnSession; iSes++){ - if( strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break; - } - if( iSesnSession ){ - pSession = &pAuxDb->aSession[iSes]; - azCmd++; - nCmd--; - }else{ - pSession = &pAuxDb->aSession[0]; - iSes = 0; - } + /* --help lists all test-controls */ + if( strcmp(zCmd,"help")==0 ){ + utf8_printf(out, "Available test-controls:\n"); + for(i=0; ip==0 ){ - session_not_open: - raw_printf(stderr, "ERROR: No sessions are open\n"); + /* convert testctrl text option to value. allow any unique prefix + ** of the option name, or a numerical value. */ + n2 = strlen30(zCmd); + for(i=0; ip, azCmd[1]); - if( rc ){ - raw_printf(stderr, "ERROR: sqlite3session_attach() returns %d\n", rc); - rc = 0; - } ++ *pzErr = smprintf + ("Error: ambiguous test-control: \"%s\"\n" + "Use \".testctrl --help\" for help\n", zCmd); - return DCR_ArgError; ++ return DCR_ArgWrong; } - }else + } + } + if( testctrl<0 ){ + utf8_printf(STD_ERR,"Error: unknown test-control: %s\n" + "Use \".testctrl --help\" for help\n", zCmd); + }else if( aCtrl[iCtrl].unSafe && ISS(p)->bSafeMode ){ + utf8_printf(STD_ERR, + "line %d: \".testctrl %s\" may not be used in safe mode\n", + ISS(p)->pInSource->lineno, aCtrl[iCtrl].zCtrlName); + exit(1); + }else{ + switch(testctrl){ - /* .session changeset FILE - ** .session patchset FILE - ** Write a changeset or patchset into a file. The file is overwritten. - */ - if( strcmp(azCmd[0],"changeset")==0 || strcmp(azCmd[0],"patchset")==0 ){ - FILE *out = 0; - failIfSafeMode(p, "cannot run \".session %s\" in safe mode", azCmd[0]); - if( nCmd!=2 ) goto session_syntax_error; - if( pSession->p==0 ) goto session_not_open; - out = fopen(azCmd[1], "wb"); - if( out==0 ){ - utf8_printf(stderr, "ERROR: cannot open \"%s\" for writing\n", - azCmd[1]); - }else{ - int szChng; - void *pChng; - if( azCmd[0][0]=='c' ){ - rc = sqlite3session_changeset(pSession->p, &szChng, &pChng); - }else{ - rc = sqlite3session_patchset(pSession->p, &szChng, &pChng); - } - if( rc ){ - printf("Error: error code %d\n", rc); - rc = 0; - } - if( pChng - && fwrite(pChng, szChng, 1, out)!=1 ){ - raw_printf(stderr, "ERROR: Failed to write entire %d-byte output\n", - szChng); - } - sqlite3_free(pChng); - fclose(out); + /* sqlite3_test_control(int, db, int) */ + case SQLITE_TESTCTRL_OPTIMIZATIONS: + if( nArg==3 ){ + unsigned int opt = (unsigned int)strtol(azArg[2], 0, 0); + rc2 = sqlite3_test_control(testctrl, DBX(p), opt); + isOk = 3; } - }else + break; - /* .session close - ** Close the identified session - */ - if( strcmp(azCmd[0], "close")==0 ){ - if( nCmd!=1 ) goto session_syntax_error; - if( pAuxDb->nSession ){ - session_close(pSession); - pAuxDb->aSession[iSes] = pAuxDb->aSession[--pAuxDb->nSession]; + /* sqlite3_test_control(int) */ + case SQLITE_TESTCTRL_PRNG_SAVE: + case SQLITE_TESTCTRL_PRNG_RESTORE: + case SQLITE_TESTCTRL_BYTEORDER: + if( nArg==2 ){ + rc2 = sqlite3_test_control(testctrl); + isOk = testctrl==SQLITE_TESTCTRL_BYTEORDER ? 1 : 3; } - }else + break; - /* .session enable ?BOOLEAN? - ** Query or set the enable flag - */ - if( strcmp(azCmd[0], "enable")==0 ){ - int ii; - if( nCmd>2 ) goto session_syntax_error; - ii = nCmd==1 ? -1 : booleanValue(azCmd[1]); - if( pAuxDb->nSession ){ - ii = sqlite3session_enable(pSession->p, ii); - utf8_printf(p->out, "session %s enable flag = %d\n", - pSession->zName, ii); + /* sqlite3_test_control(int, uint) */ + case SQLITE_TESTCTRL_PENDING_BYTE: + if( nArg==3 ){ + unsigned int opt = (unsigned int)integerValue(azArg[2]); + rc2 = sqlite3_test_control(testctrl, opt); + isOk = 3; } - }else + break; - /* .session filter GLOB .... - ** Set a list of GLOB patterns of table names to be excluded. - */ - if( strcmp(azCmd[0], "filter")==0 ){ - int ii, nByte; - if( nCmd<2 ) goto session_syntax_error; - if( pAuxDb->nSession ){ - for(ii=0; iinFilter; ii++){ - sqlite3_free(pSession->azFilter[ii]); + /* sqlite3_test_control(int, int, sqlite3*) */ + case SQLITE_TESTCTRL_PRNG_SEED: + if( nArg==3 || nArg==4 ){ + int ii = (int)integerValue(azArg[2]); + sqlite3 *db; + if( ii==0 && strcmp(azArg[2],"random")==0 ){ + sqlite3_randomness(sizeof(ii),&ii); + fprintf(STD_OUT, "-- random seed: %d\n", ii); } - sqlite3_free(pSession->azFilter); - nByte = sizeof(pSession->azFilter[0])*(nCmd-1); - pSession->azFilter = sqlite3_malloc( nByte ); - if( pSession->azFilter==0 ){ - raw_printf(stderr, "Error: out or memory\n"); - exit(1); - } - for(ii=1; iiazFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]); - shell_check_oom(x); + if( nArg==3 ){ + db = 0; + }else{ + db = DBX(p); + /* Make sure the schema has been loaded */ + sqlite3_table_column_metadata(db, 0, "x", 0, 0, 0, 0, 0, 0); } - pSession->nFilter = ii-1; + rc2 = sqlite3_test_control(testctrl, ii, db); + isOk = 3; } - }else + break; - /* .session indirect ?BOOLEAN? - ** Query or set the indirect flag - */ - if( strcmp(azCmd[0], "indirect")==0 ){ - int ii; - if( nCmd>2 ) goto session_syntax_error; - ii = nCmd==1 ? -1 : booleanValue(azCmd[1]); - if( pAuxDb->nSession ){ - ii = sqlite3session_indirect(pSession->p, ii); - utf8_printf(p->out, "session %s indirect flag = %d\n", - pSession->zName, ii); + /* sqlite3_test_control(int, int) */ + case SQLITE_TESTCTRL_ASSERT: + case SQLITE_TESTCTRL_ALWAYS: + if( nArg==3 ){ + int opt = booleanValue(azArg[2]); + rc2 = sqlite3_test_control(testctrl, opt); + isOk = 1; } - }else + break; - /* .session isempty - ** Determine if the session is empty - */ - if( strcmp(azCmd[0], "isempty")==0 ){ - int ii; - if( nCmd!=1 ) goto session_syntax_error; - if( pAuxDb->nSession ){ - ii = sqlite3session_isempty(pSession->p); - utf8_printf(p->out, "session %s isempty flag = %d\n", - pSession->zName, ii); + /* sqlite3_test_control(int, int) */ + case SQLITE_TESTCTRL_LOCALTIME_FAULT: + case SQLITE_TESTCTRL_NEVER_CORRUPT: + if( nArg==3 ){ + int opt = booleanValue(azArg[2]); + rc2 = sqlite3_test_control(testctrl, opt); + isOk = 3; } - }else + break; - /* .session list - ** List all currently open sessions - */ - if( strcmp(azCmd[0],"list")==0 ){ - for(i=0; inSession; i++){ - utf8_printf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName); - } - }else + /* sqlite3_test_control(sqlite3*) */ + case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS: + rc2 = sqlite3_test_control(testctrl, DBX(p)); + isOk = 3; + break; - /* .session open DB NAME - ** Open a new session called NAME on the attached database DB. - ** DB is normally "main". - */ - if( strcmp(azCmd[0],"open")==0 ){ - char *zName; - if( nCmd!=3 ) goto session_syntax_error; - zName = azCmd[2]; - if( zName[0]==0 ) goto session_syntax_error; - for(i=0; inSession; i++){ - if( strcmp(pAuxDb->aSession[i].zName,zName)==0 ){ - utf8_printf(stderr, "Session \"%s\" already exists\n", zName); - goto meta_command_exit; - } + case SQLITE_TESTCTRL_IMPOSTER: + if( nArg==5 ){ + rc2 = sqlite3_test_control(testctrl, DBX(p), + azArg[2], + integerValue(azArg[3]), + integerValue(azArg[4])); + isOk = 3; } - if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){ - raw_printf(stderr, "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession)); - goto meta_command_exit; - } - pSession = &pAuxDb->aSession[pAuxDb->nSession]; - rc = sqlite3session_create(p->db, azCmd[1], &pSession->p); - if( rc ){ - raw_printf(stderr, "Cannot open session: error code=%d\n", rc); - rc = 0; - goto meta_command_exit; + break; + + case SQLITE_TESTCTRL_SEEK_COUNT: { + u64 x = 0; + rc2 = sqlite3_test_control(testctrl, DBX(p), &x); + utf8_printf(out, "%llu\n", x); + isOk = 3; + break; + } + +#ifdef YYCOVERAGE + case SQLITE_TESTCTRL_PARSER_COVERAGE: { + if( nArg==2 ){ + sqlite3_test_control(testctrl, out); + isOk = 3; } - pSession->nFilter = 0; - sqlite3session_table_filter(pSession->p, session_filter, pSession); - pAuxDb->nSession++; - pSession->zName = sqlite3_mprintf("%s", zName); - shell_check_oom(pSession->zName); - }else - /* If no command name matches, show a syntax error */ - session_syntax_error: - showHelp(p->out, "session"); - }else + break; + } #endif - #ifdef SQLITE_DEBUG - /* Undocumented commands for internal testing. Subject to change - ** without notice. */ - if( c=='s' && n>=10 && strncmp(azArg[0], "selftest-", 9)==0 ){ - if( strncmp(azArg[0]+9, "boolean", n-9)==0 ){ - int i, v; - for(i=1; iout, "%s: %d 0x%x\n", azArg[i], v, v); + case SQLITE_TESTCTRL_TUNE: { + if( nArg==4 ){ + int id = (int)integerValue(azArg[2]); + int val = (int)integerValue(azArg[3]); + sqlite3_test_control(testctrl, id, &val); + isOk = 3; + }else if( nArg==3 ){ + int id = (int)integerValue(azArg[2]); + sqlite3_test_control(testctrl, -id, &rc2); + isOk = 1; + }else if( nArg==2 ){ + int id = 1; + while(1){ + int val = 0; + rc2 = sqlite3_test_control(testctrl, -id, &val); + if( rc2!=SQLITE_OK ) break; + if( id>1 ) utf8_printf(out, " "); + utf8_printf(out, "%d: %d", id, val); + id++; + } + if( id>1 ) utf8_printf(out, "\n"); + isOk = 3; } + break; } - if( strncmp(azArg[0]+9, "integer", n-9)==0 ){ - int i; sqlite3_int64 v; - for(i=1; iout, "%s", zBuf); - } +#endif } - }else + } + if( isOk==0 && iCtrl>=0 ){ + utf8_printf(out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage); - return DCR_ArgError; ++ return DCR_CmdErred; + }else if( isOk==1 ){ + raw_printf(out, "%d\n", rc2); + }else if( isOk==2 ){ + raw_printf(out, "0x%08x\n", rc2); + } + return DCR_Ok; +} +DISPATCHABLE_COMMAND( timeout 4 1 2 ){ + open_db(p, 0); + sqlite3_busy_timeout(DBX(p), nArg>=2 ? (int)integerValue(azArg[1]) : 0); + return DCR_Ok; +} +DISPATCHABLE_COMMAND( timer ? 2 2 ){ + enableTimer = booleanValue(azArg[1]); + if( enableTimer && !HAS_TIMER ){ + raw_printf(STD_ERR, "Error: timer not available on this system.\n"); + enableTimer = 0; + } + return DCR_Ok; +} +DISPATCHABLE_COMMAND( trace ? 0 0 ){ + ShellInState *psi = ISS(p); + int mType = 0; + int jj; + open_db(p, 0); + for(jj=1; jjeTraceType = SHELL_TRACE_EXPANDED; + } +#ifdef SQLITE_ENABLE_NORMALIZE + else if( optionMatch(z, "normalized") ){ + psi->eTraceType = SHELL_TRACE_NORMALIZED; + } #endif - - if( c=='s' && n>=4 && strncmp(azArg[0],"selftest",n)==0 ){ - int bIsInit = 0; /* True to initialize the SELFTEST table */ - int bVerbose = 0; /* Verbose output */ - int bSelftestExists; /* True if SELFTEST already exists */ - int i, k; /* Loop counters */ - int nTest = 0; /* Number of tests runs */ - int nErr = 0; /* Number of errors seen */ - ShellText str; /* Answer for a query */ - sqlite3_stmt *pStmt = 0; /* Query against the SELFTEST table */ - - open_db(p,0); - for(i=1; ieTraceType = SHELL_TRACE_PLAIN; } - } - if( sqlite3_table_column_metadata(p->db,"main","selftest",0,0,0,0,0,0) - != SQLITE_OK ){ - bSelftestExists = 0; - }else{ - bSelftestExists = 1; - } - if( bIsInit ){ - createSelftestTable(p); - bSelftestExists = 1; - } - initText(&str); - appendText(&str, "x", 0); - for(k=bSelftestExists; k>=0; k--){ - if( k==1 ){ - rc = sqlite3_prepare_v2(p->db, - "SELECT tno,op,cmd,ans FROM selftest ORDER BY tno", - -1, &pStmt, 0); - }else{ - rc = sqlite3_prepare_v2(p->db, - "VALUES(0,'memo','Missing SELFTEST table - default checks only','')," - " (1,'run','PRAGMA integrity_check','ok')", - -1, &pStmt, 0); + else if( optionMatch(z, "profile") ){ + mType |= SQLITE_TRACE_PROFILE; } - if( rc ){ - raw_printf(stderr, "Error querying the selftest table\n"); - rc = 1; - sqlite3_finalize(pStmt); - goto meta_command_exit; - } - for(i=1; sqlite3_step(pStmt)==SQLITE_ROW; i++){ - int tno = sqlite3_column_int(pStmt, 0); - const char *zOp = (const char*)sqlite3_column_text(pStmt, 1); - const char *zSql = (const char*)sqlite3_column_text(pStmt, 2); - const char *zAns = (const char*)sqlite3_column_text(pStmt, 3); - - if( zOp==0 ) continue; - if( zSql==0 ) continue; - if( zAns==0 ) continue; - k = 0; - if( bVerbose>0 ){ - printf("%d: %s %s\n", tno, zOp, zSql); - } - if( strcmp(zOp,"memo")==0 ){ - utf8_printf(p->out, "%s\n", zSql); - }else - if( strcmp(zOp,"run")==0 ){ - char *zErrMsg = 0; - str.n = 0; - str.z[0] = 0; - rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg); - nTest++; - if( bVerbose ){ - utf8_printf(p->out, "Result: %s\n", str.z); - } - if( rc || zErrMsg ){ - nErr++; - rc = 1; - utf8_printf(p->out, "%d: error-code-%d: %s\n", tno, rc, zErrMsg); - sqlite3_free(zErrMsg); - }else if( strcmp(zAns,str.z)!=0 ){ - nErr++; - rc = 1; - utf8_printf(p->out, "%d: Expected: [%s]\n", tno, zAns); - utf8_printf(p->out, "%d: Got: [%s]\n", tno, str.z); - } - }else - { - utf8_printf(stderr, - "Unknown operation \"%s\" on selftest line %d\n", zOp, tno); - rc = 1; - break; - } - } /* End loop over rows of content from SELFTEST */ - sqlite3_finalize(pStmt); - } /* End loop over k */ - freeText(&str); - utf8_printf(p->out, "%d errors out of %d tests\n", nErr, nTest); - }else - - if( c=='s' && strncmp(azArg[0], "separator", n)==0 ){ - if( nArg<2 || nArg>3 ){ - raw_printf(stderr, "Usage: .separator COL ?ROW?\n"); - rc = 1; - } - if( nArg>=2 ){ - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, - "%.*s", (int)ArraySize(p->colSeparator)-1, azArg[1]); - } - if( nArg>=3 ){ - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, - "%.*s", (int)ArraySize(p->rowSeparator)-1, azArg[2]); - } - }else - - if( c=='s' && n>=4 && strncmp(azArg[0],"sha3sum",n)==0 ){ - const char *zLike = 0; /* Which table to checksum. 0 means everything */ - int i; /* Loop counter */ - int bSchema = 0; /* Also hash the schema */ - int bSeparate = 0; /* Hash each table separately */ - int iSize = 224; /* Hash algorithm to use */ - int bDebug = 0; /* Only show the query that would have run */ - sqlite3_stmt *pStmt; /* For querying tables names */ - char *zSql; /* SQL to be run */ - char *zSep; /* Separator */ - ShellText sSql; /* Complete SQL for the query to run the hash */ - ShellText sQuery; /* Set of queries used to read all content */ - open_db(p, 0); - for(i=1; iout, azArg[0]); - rc = 1; - goto meta_command_exit; - } - }else if( zLike ){ - raw_printf(stderr, "Usage: .sha3sum ?OPTIONS? ?LIKE-PATTERN?\n"); - rc = 1; - goto meta_command_exit; - }else{ - zLike = z; - bSeparate = 1; - if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 1; + else if( optionMatch(z, "row") ){ + mType |= SQLITE_TRACE_ROW; + } + else if( optionMatch(z, "stmt") ){ + mType |= SQLITE_TRACE_STMT; + } + else if( optionMatch(z, "close") ){ + mType |= SQLITE_TRACE_CLOSE; + } + else { - *pzErr = shellMPrintf(0,"Unknown option \"%s\" on \".trace\"\n", z); - return DCR_ArgError; ++ return DCR_Unknown|jj; } - } - if( bSchema ){ - zSql = "SELECT lower(name) FROM sqlite_schema" - " WHERE type='table' AND coalesce(rootpage,0)>1" - " UNION ALL SELECT 'sqlite_schema'" - " ORDER BY 1 collate nocase"; - }else{ - zSql = "SELECT lower(name) FROM sqlite_schema" - " WHERE type='table' AND coalesce(rootpage,0)>1" - " AND name NOT LIKE 'sqlite_%'" - " ORDER BY 1 collate nocase"; - } - sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - initText(&sQuery); - initText(&sSql); - appendText(&sSql, "WITH [sha3sum$query](a,b) AS(",0); - zSep = "VALUES("; - while( SQLITE_ROW==sqlite3_step(pStmt) ){ - const char *zTab = (const char*)sqlite3_column_text(pStmt,0); - if( zTab==0 ) continue; - if( zLike && sqlite3_strlike(zLike, zTab, 0)!=0 ) continue; - if( strncmp(zTab, "sqlite_",7)!=0 ){ - appendText(&sQuery,"SELECT * FROM ", 0); - appendText(&sQuery,zTab,'"'); - appendText(&sQuery," NOT INDEXED;", 0); - }else if( strcmp(zTab, "sqlite_schema")==0 ){ - appendText(&sQuery,"SELECT type,name,tbl_name,sql FROM sqlite_schema" - " ORDER BY name;", 0); - }else if( strcmp(zTab, "sqlite_sequence")==0 ){ - appendText(&sQuery,"SELECT name,seq FROM sqlite_sequence" - " ORDER BY name;", 0); - }else if( strcmp(zTab, "sqlite_stat1")==0 ){ - appendText(&sQuery,"SELECT tbl,idx,stat FROM sqlite_stat1" - " ORDER BY tbl,idx;", 0); - }else if( strcmp(zTab, "sqlite_stat4")==0 ){ - appendText(&sQuery, "SELECT * FROM ", 0); - appendText(&sQuery, zTab, 0); - appendText(&sQuery, " ORDER BY tbl, idx, rowid;\n", 0); - } - appendText(&sSql, zSep, 0); - appendText(&sSql, sQuery.z, '\''); - sQuery.n = 0; - appendText(&sSql, ",", 0); - appendText(&sSql, zTab, '\''); - zSep = "),("; - } - sqlite3_finalize(pStmt); - if( bSeparate ){ - zSql = sqlite3_mprintf( - "%s))" - " SELECT lower(hex(sha3_query(a,%d))) AS hash, b AS label" - " FROM [sha3sum$query]", - sSql.z, iSize); }else{ - zSql = sqlite3_mprintf( - "%s))" - " SELECT lower(hex(sha3_query(group_concat(a,''),%d))) AS hash" - " FROM [sha3sum$query]", - sSql.z, iSize); + output_file_close(psi->traceOut); + psi->traceOut = output_file_open(azArg[1], 0); } - shell_check_oom(zSql); - freeText(&sQuery); - freeText(&sSql); - if( bDebug ){ - utf8_printf(p->out, "%s\n", zSql); - }else{ - shell_exec(p, zSql, 0); + } + if( psi->traceOut==0 ){ + sqlite3_trace_v2(DBX(p), 0, 0, 0); + }else{ + if( mType==0 ) mType = SQLITE_TRACE_STMT; + sqlite3_trace_v2(DBX(p), mType, sql_trace_callback, psi); + } + return DCR_Ok; +} + +/***************** + * The .unmodule command + */ +CONDITION_COMMAND( unmodule defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_VIRTUALTABLE) ); +COLLECT_HELP_TEXT[ + ".unmodule NAME ... Unregister virtual table modules", + " --allexcept Unregister everything except those named", +]; +DISPATCHABLE_COMMAND( unmodule ? 2 0 ){ + int ii; + int lenOpt; + char *zOpt; + open_db(p, 0); + zOpt = azArg[1]; + if( zOpt[0]=='-' && zOpt[1]=='-' && zOpt[2]!=0 ) zOpt++; + lenOpt = (int)strlen(zOpt); + if( lenOpt>=3 && strncmp(zOpt, "-allexcept",lenOpt)==0 ){ + assert( azArg[nArg]==0 ); + sqlite3_drop_modules(DBX(p), nArg>2 ? (const char**)(azArg+2) : 0); + }else{ + for(ii=1; iiout, "%12.12s: %s\n","echo", - azBool[ShellHasFlag(p, SHFLG_Echo)]); - utf8_printf(p->out, "%12.12s: %s\n","eqp", azBool[p->autoEQP&3]); - utf8_printf(p->out, "%12.12s: %s\n","explain", - p->mode==MODE_Explain ? "on" : p->autoExplain ? "auto" : "off"); - utf8_printf(p->out,"%12.12s: %s\n","headers", azBool[p->showHeader!=0]); - if( p->mode==MODE_Column - || (p->mode>=MODE_Markdown && p->mode<=MODE_Box) - ){ - utf8_printf - (p->out, "%12.12s: %s --wrap %d --wordwrap %s --%squote\n", "mode", - modeDescr[p->mode], p->cmOpts.iWrap, - p->cmOpts.bWordWrap ? "on" : "off", - p->cmOpts.bQuote ? "" : "no"); - }else{ - utf8_printf(p->out, "%12.12s: %s\n","mode", modeDescr[p->mode]); - } - utf8_printf(p->out, "%12.12s: ", "nullvalue"); - output_c_string(p->out, p->nullValue); - raw_printf(p->out, "\n"); - utf8_printf(p->out,"%12.12s: %s\n","output", - strlen30(p->outfile) ? p->outfile : "stdout"); - utf8_printf(p->out,"%12.12s: ", "colseparator"); - output_c_string(p->out, p->colSeparator); - raw_printf(p->out, "\n"); - utf8_printf(p->out,"%12.12s: ", "rowseparator"); - output_c_string(p->out, p->rowSeparator); - raw_printf(p->out, "\n"); - switch( p->statsOn ){ - case 0: zOut = "off"; break; - default: zOut = "on"; break; - case 2: zOut = "stmt"; break; - case 3: zOut = "vmstep"; break; - } - utf8_printf(p->out, "%12.12s: %s\n","stats", zOut); - utf8_printf(p->out, "%12.12s: ", "width"); - for (i=0;inWidth;i++) { - raw_printf(p->out, "%d ", p->colWidth[i]); - } - raw_printf(p->out, "\n"); - utf8_printf(p->out, "%12.12s: %s\n", "filename", - p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : ""); - }else - - if( c=='s' && strncmp(azArg[0], "stats", n)==0 ){ - if( nArg==2 ){ - if( strcmp(azArg[1],"stmt")==0 ){ - p->statsOn = 2; - }else if( strcmp(azArg[1],"vmstep")==0 ){ - p->statsOn = 3; - }else{ - p->statsOn = (u8)booleanValue(azArg[1]); - } - }else if( nArg==1 ){ - display_stats(p->db, p, 0); - }else{ - raw_printf(stderr, "Usage: .stats ?on|off|stmt|vmstep?\n"); - rc = 1; + }else if( strcmp(azArg[1],"add")==0 ){ + if( nArg!=5 ){ + goto teach_fail; } - }else - - if( (c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0) - || (c=='i' && (strncmp(azArg[0], "indices", n)==0 - || strncmp(azArg[0], "indexes", n)==0) ) - ){ - sqlite3_stmt *pStmt; - char **azResult; - int nRow, nAlloc; - int ii; - ShellText s; - initText(&s); - open_db(p, 0); - rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); + rc = sqlite3_user_add(DBX(p), azArg[2], azArg[3], strlen30(azArg[3]), + booleanValue(azArg[4])); if( rc ){ - sqlite3_finalize(pStmt); - return shellDatabaseError(p->db); + *pzErr = shellMPrintf(0,"User-Add failed: %d\n", rc); + return DCR_Error; } - - if( nArg>2 && c=='i' ){ - /* It is an historical accident that the .indexes command shows an error - ** when called with the wrong number of arguments whereas the .tables - ** command does not. */ - raw_printf(stderr, "Usage: .indexes ?LIKE-PATTERN?\n"); - rc = 1; - sqlite3_finalize(pStmt); - goto meta_command_exit; - } - for(ii=0; sqlite3_step(pStmt)==SQLITE_ROW; ii++){ - const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1); - if( zDbName==0 ) continue; - if( s.z && s.z[0] ) appendText(&s, " UNION ALL ", 0); - if( sqlite3_stricmp(zDbName, "main")==0 ){ - appendText(&s, "SELECT name FROM ", 0); - }else{ - appendText(&s, "SELECT ", 0); - appendText(&s, zDbName, '\''); - appendText(&s, "||'.'||name FROM ", 0); - } - appendText(&s, zDbName, '"'); - appendText(&s, ".sqlite_schema ", 0); - if( c=='t' ){ - appendText(&s," WHERE type IN ('table','view')" - " AND name NOT LIKE 'sqlite_%'" - " AND name LIKE ?1", 0); - }else{ - appendText(&s," WHERE type='index'" - " AND tbl_name LIKE ?1", 0); - } + }else if( strcmp(azArg[1],"edit")==0 ){ + if( nArg!=5 ){ + goto teach_fail; } - rc = sqlite3_finalize(pStmt); - if( rc==SQLITE_OK ){ - appendText(&s, " ORDER BY 1", 0); - rc = sqlite3_prepare_v2(p->db, s.z, -1, &pStmt, 0); + rc = sqlite3_user_change(DBX(p), azArg[2], azArg[3], strlen30(azArg[3]), + booleanValue(azArg[4])); + if( rc ){ + *pzErr = shellMPrintf(0,"User-Edit failed: %d\n", rc); + return DCR_Error; } - freeText(&s); - if( rc ) return shellDatabaseError(p->db); - - /* Run the SQL statement prepared by the above block. Store the results - ** as an array of nul-terminated strings in azResult[]. */ - nRow = nAlloc = 0; - azResult = 0; - if( nArg>1 ){ - sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT); - }else{ - sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC); + }else if( strcmp(azArg[1],"delete")==0 ){ + if( nArg!=3 ){ + goto teach_fail; } - while( sqlite3_step(pStmt)==SQLITE_ROW ){ - if( nRow>=nAlloc ){ - char **azNew; - int n2 = nAlloc*2 + 10; - azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2); - shell_check_oom(azNew); - nAlloc = n2; - azResult = azNew; - } - azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); - shell_check_oom(azResult[nRow]); - nRow++; - } - if( sqlite3_finalize(pStmt)!=SQLITE_OK ){ - rc = shellDatabaseError(p->db); - } - - /* Pretty-print the contents of array azResult[] to the output */ - if( rc==0 && nRow>0 ){ - int len, maxlen = 0; - int i, j; - int nPrintCol, nPrintRow; - for(i=0; imaxlen ) maxlen = len; - } - nPrintCol = 80/(maxlen+2); - if( nPrintCol<1 ) nPrintCol = 1; - nPrintRow = (nRow + nPrintCol - 1)/nPrintCol; - for(i=0; iout, "%s%-*s", zSp, maxlen, - azResult[j] ? azResult[j]:""); - } - raw_printf(p->out, "\n"); - } + rc = sqlite3_user_delete(DBX(p), azArg[2]); + if( rc ){ + *pzErr = shellMPrintf(0,"User-Delete failed: %d\n", rc); + return DCR_Error; } + }else{ + goto teach_fail; + } + return DCR_Ok; +} - for(ii=0; iiout = output_file_open("testcase-out.txt", 0); - if( p->out==0 ){ - raw_printf(stderr, "Error: cannot open 'testcase-out.txt'\n"); +/***************** + * The .vfsinfo, .vfslist, .vfsname and .version commands + */ +COLLECT_HELP_TEXT[ + ".version Show a variety of version info", + ".vfsinfo ?AUX? Information about the top-level VFS", + ".vfslist List all available VFSes", + ".vfsname ?AUX? Print the name of the VFS stack", +]; +DISPATCHABLE_COMMAND( version ? 1 1 ){ + FILE *out = ISS(p)->out; + utf8_printf(out, "SQLite %s %s\n" /*extra-version-info*/, + sqlite3_libversion(), sqlite3_sourceid()); +#if SQLITE_HAVE_ZLIB + utf8_printf(out, "zlib version %s\n", zlibVersion()); +#endif +#define CTIMEOPT_VAL_(opt) #opt +#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt) +#if defined(__clang__) && defined(__clang_major__) + utf8_printf(out, "clang-" CTIMEOPT_VAL(__clang_major__) "." + CTIMEOPT_VAL(__clang_minor__) "." + CTIMEOPT_VAL(__clang_patchlevel__) "\n"); +#elif defined(_MSC_VER) + utf8_printf(out, "msvc-" CTIMEOPT_VAL(_MSC_VER) "\n"); +#elif defined(__GNUC__) && defined(__VERSION__) + utf8_printf(out, "gcc-" __VERSION__ "\n"); +#endif + return DCR_Ok; +} +DISPATCHABLE_COMMAND( vfsinfo ? 1 2 ){ + const char *zDbName = nArg==2 ? azArg[1] : "main"; + sqlite3_vfs *pVfs = 0; + if( DBX(p) ){ + sqlite3_file_control(DBX(p), zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs); + if( pVfs ){ + FILE *out = ISS(p)->out; + utf8_printf(out, "vfs.zName = \"%s\"\n", pVfs->zName); + raw_printf(out, "vfs.iVersion = %d\n", pVfs->iVersion); + raw_printf(out, "vfs.szOsFile = %d\n", pVfs->szOsFile); + raw_printf(out, "vfs.mxPathname = %d\n", pVfs->mxPathname); } - if( nArg>=2 ){ - sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", azArg[1]); - }else{ - sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?"); + } + return DCR_Ok; +} +DISPATCHABLE_COMMAND( vfslist ? 1 1 ){ + sqlite3_vfs *pVfs; + sqlite3_vfs *pCurrent = 0; + FILE *out = ISS(p)->out; + if( DBX(p) ){ + sqlite3_file_control(DBX(p), "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent); + } + for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){ + utf8_printf(out, "vfs.zName = \"%s\"%s\n", pVfs->zName, + pVfs==pCurrent ? " <--- CURRENT" : ""); + raw_printf(out, "vfs.iVersion = %d\n", pVfs->iVersion); + raw_printf(out, "vfs.szOsFile = %d\n", pVfs->szOsFile); + raw_printf(out, "vfs.mxPathname = %d\n", pVfs->mxPathname); + if( pVfs->pNext ){ + raw_printf(out, "-----------------------------------\n"); } - }else - -#ifndef SQLITE_UNTESTABLE - if( c=='t' && n>=8 && strncmp(azArg[0], "testctrl", n)==0 ){ - static const struct { - const char *zCtrlName; /* Name of a test-control option */ - int ctrlCode; /* Integer code for that option */ - int unSafe; /* Not valid for --safe mode */ - const char *zUsage; /* Usage notes */ - } aCtrl[] = { - { "always", SQLITE_TESTCTRL_ALWAYS, 1, "BOOLEAN" }, - { "assert", SQLITE_TESTCTRL_ASSERT, 1, "BOOLEAN" }, - /*{ "benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS,1, "" },*/ - /*{ "bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST, 1, "" },*/ - { "byteorder", SQLITE_TESTCTRL_BYTEORDER, 0, "" }, - { "extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,0,"BOOLEAN" }, - /*{ "fault_install", SQLITE_TESTCTRL_FAULT_INSTALL, 1,"" },*/ - { "imposter", SQLITE_TESTCTRL_IMPOSTER,1,"SCHEMA ON/OFF ROOTPAGE"}, - { "internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS,0,"" }, - { "localtime_fault", SQLITE_TESTCTRL_LOCALTIME_FAULT,0,"BOOLEAN" }, - { "never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT,1, "BOOLEAN" }, - { "optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS,0,"DISABLE-MASK" }, -#ifdef YYCOVERAGE - { "parser_coverage", SQLITE_TESTCTRL_PARSER_COVERAGE,0,"" }, -#endif - { "pending_byte", SQLITE_TESTCTRL_PENDING_BYTE,0, "OFFSET " }, - { "prng_restore", SQLITE_TESTCTRL_PRNG_RESTORE,0, "" }, - { "prng_save", SQLITE_TESTCTRL_PRNG_SAVE, 0, "" }, - { "prng_seed", SQLITE_TESTCTRL_PRNG_SEED, 0, "SEED ?db?" }, - { "seek_count", SQLITE_TESTCTRL_SEEK_COUNT, 0, "" }, - { "sorter_mmap", SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX" }, - { "tune", SQLITE_TESTCTRL_TUNE, 1, "ID VALUE" }, - }; - int testctrl = -1; - int iCtrl = -1; - int rc2 = 0; /* 0: usage. 1: %d 2: %x 3: no-output */ - int isOk = 0; - int i, n2; - const char *zCmd = 0; - - open_db(p, 0); - zCmd = nArg>=2 ? azArg[1] : "help"; - - /* The argument can optionally begin with "-" or "--" */ - if( zCmd[0]=='-' && zCmd[1] ){ - zCmd++; - if( zCmd[0]=='-' && zCmd[1] ) zCmd++; + } + return DCR_Ok; +} +DISPATCHABLE_COMMAND( vfsname ? 0 0 ){ + const char *zDbName = nArg==2 ? azArg[1] : "main"; + char *zVfsName = 0; + if( DBX(p) ){ + sqlite3_file_control(DBX(p), zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName); + if( zVfsName ){ + utf8_printf(ISS(p)->out, "%s\n", zVfsName); + sqlite3_free(zVfsName); } + } + return DCR_Ok; +} - /* --help lists all test-controls */ - if( strcmp(zCmd,"help")==0 ){ - utf8_printf(p->out, "Available test-controls:\n"); - for(i=0; iout, " .testctrl %s %s\n", - aCtrl[i].zCtrlName, aCtrl[i].zUsage); - } - rc = 1; - goto meta_command_exit; - } +/***************** + * The .width and .wheretrace commands + * The .wheretrace command has no help. + */ +static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths){ + int j; + p->numWidths = nWidths; + p->pSpecWidths = realloc(p->pSpecWidths, (nWidths+1)*sizeof(int)*2); + if( nWidths>0 ){ + shell_check_oom(p->pSpecWidths); + p->pHaveWidths = &p->pSpecWidths[nWidths]; + for(j=0; jpSpecWidths[j] = (int)integerValue(azWidths[j]); + p->pHaveWidths[j] = 0; + } + }else p->pHaveWidths = p->pSpecWidths; +} +COLLECT_HELP_TEXT[ + ".width NUM1 NUM2 ... Set minimum column widths for columnar output", + " Negative values right-justify", +]; +DISPATCHABLE_COMMAND( width ? 1 0 ){ + setColumnWidths(p, azArg+1, nArg-1); + return DCR_Ok; +} +DISPATCHABLE_COMMAND( wheretrace ? 1 2 ){ + unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff; + sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &x); + return DCR_Ok; +} - /* convert testctrl text option to value. allow any unique prefix - ** of the option name, or a numerical value. */ - n2 = strlen30(zCmd); - for(i=0; i0 && IsSpace(zValue[nb-1]) ) --nb; + if( nb>0 ){ + /* The trailing newline (or some other placeholder) is important + * because one (or some other character) will likely be put in + * its place during process_input() line/group handling, along + * with a terminating NUL character. Without it, the NULL could + * land past the end of the allocation made just below. + */ + int nle = zValue[nb-1]=='\n'; - char *zSubmit = sqlite3_mprintf( "%.*s%s", nb, zValue, "\n"+nle ); ++ char *zSubmit = smprintf( "%.*s%s", nb, zValue, "\n"+nle ); + ShellInState *psi = ISS(p); + InSource inRedir + = INSOURCE_STR_REDIR(zSubmit, azArg[ia], psi->pInSource); + sqlite3_reset(pStmt); /* End the parameter read to unlock DB. */ + shell_check_oom(zSubmit); + psi->pInSource = &inRedir; + rv = process_input(psi); + if( rv=DCR_Exit ) break; ++ /* Handle DCR_Return, DCR_Exit or DCR_Abort */ ++ if( rv>DCR_Error ) break; + } + sqlite3_free(zSubmit); + psi->pInSource = inRedir.pFrom; }else{ - utf8_printf(stderr, "Error: ambiguous test-control: \"%s\"\n" - "Use \".testctrl --help\" for help\n", zCmd); - rc = 1; - goto meta_command_exit; + continue; /* All white, ignore. */ } + }else{ + utf8_printf(STD_ERR, + "Skipping parameter '%s' (not set and executable.)\n", + azArg[ia]); + ++nErrors; } - } - if( testctrl<0 ){ - utf8_printf(stderr,"Error: unknown test-control: %s\n" - "Use \".testctrl --help\" for help\n", zCmd); - }else if( aCtrl[iCtrl].unSafe && p->bSafeMode ){ - utf8_printf(stderr, - "line %d: \".testctrl %s\" may not be used in safe mode\n", - p->lineno, aCtrl[iCtrl].zCtrlName); - exit(1); }else{ - switch(testctrl){ - - /* sqlite3_test_control(int, db, int) */ - case SQLITE_TESTCTRL_OPTIMIZATIONS: - if( nArg==3 ){ - unsigned int opt = (unsigned int)strtol(azArg[2], 0, 0); - rc2 = sqlite3_test_control(testctrl, p->db, opt); - isOk = 3; - } - break; - - /* sqlite3_test_control(int) */ - case SQLITE_TESTCTRL_PRNG_SAVE: - case SQLITE_TESTCTRL_PRNG_RESTORE: - case SQLITE_TESTCTRL_BYTEORDER: - if( nArg==2 ){ - rc2 = sqlite3_test_control(testctrl); - isOk = testctrl==SQLITE_TESTCTRL_BYTEORDER ? 1 : 3; - } - break; + utf8_printf(STD_ERR, + "Skipping badly named %s. Run \".help x\"\n", azArg[ia]); + ++nErrors; + } + } + sqlite3_finalize(pStmt); - return (rv==DCR_Return)? DCR_Return : DCR_Ok|(nErrors>0); ++ rv |= (nErrors>0); ++ /* If error to be returned, indicate that complaining about it is done. */ ++ return (rv==DCR_Error)? DCR_CmdErred : rv; +} - /* sqlite3_test_control(int, uint) */ - case SQLITE_TESTCTRL_PENDING_BYTE: - if( nArg==3 ){ - unsigned int opt = (unsigned int)integerValue(azArg[2]); - rc2 = sqlite3_test_control(testctrl, opt); - isOk = 3; - } - break; +/* End of published, standard meta-command implementation functions +COMMENT Build-time overrides of above meta-commands or new meta-commands may be +COMMENT incorporated into shell.c via: -it COMMAND_CUSTOMIZE= +COMMENT where names a file using the above methodology to +COMMENT define new or altered meta-commands and their help text. +*/ +INCLUDE( COMMAND_CUSTOMIZE ); - /* sqlite3_test_control(int, int, sqlite3*) */ - case SQLITE_TESTCTRL_PRNG_SEED: - if( nArg==3 || nArg==4 ){ - int ii = (int)integerValue(azArg[2]); - sqlite3 *db; - if( ii==0 && strcmp(azArg[2],"random")==0 ){ - sqlite3_randomness(sizeof(ii),&ii); - printf("-- random seed: %d\n", ii); - } - if( nArg==3 ){ - db = 0; - }else{ - db = p->db; - /* Make sure the schema has been loaded */ - sqlite3_table_column_metadata(db, 0, "x", 0, 0, 0, 0, 0, 0); - } - rc2 = sqlite3_test_control(testctrl, ii, db); - isOk = 3; - } - break; +COMMENT This help text is set seperately from meta-command definition section +COMMENT for the always-built-in, non-customizable commands with visible help. +COLLECT_HELP_TEXT[ + ".exit ?CODE? Exit this program with return-code CODE or 0", + ".quit Exit this program", +]; + +static void MetaCommand_dtor(MetaCommand *); +static const char * MetaCommand_name(MetaCommand *); +static const char * MetaCommand_help(MetaCommand *, int); +static DotCmdRC + MetaCommand_argsCheck(MetaCommand *, char **, int nArgs, char *azArgs[]); +static DotCmdRC + MetaCommand_execute(MetaCommand *, ShellExState *, char **, int, char *[]); + +static VTABLE_NAME(MetaCommand) meta_cmd_VtabBuiltIn = { + MetaCommand_dtor, + MetaCommand_name, + MetaCommand_help, + MetaCommand_argsCheck, + MetaCommand_execute +}; - /* sqlite3_test_control(int, int) */ - case SQLITE_TESTCTRL_ASSERT: - case SQLITE_TESTCTRL_ALWAYS: - if( nArg==3 ){ - int opt = booleanValue(azArg[2]); - rc2 = sqlite3_test_control(testctrl, opt); - isOk = 1; - } - break; +/* Define and populate command dispatch table. */ +static struct CommandInfo { + VTABLE_NAME(MetaCommand) *mcVtabBuiltIn; + const char * cmdName; + DotCmdRC (*cmdDoer)(char *azArg[], int nArg, + ShellExState *, char **pzErr); + unsigned char minLen, minArgs, maxArgs; + const char *azHelp[2]; /* primary and secondary help text */ + void * pCmdData; + } command_table[] = { + COMMENT Emit the dispatch table entries generated and collected above. +#define META_CMD_INFO(cmd, nlenMin, minArgs, maxArgs, ht0, ht1 ) \ + { &meta_cmd_VtabBuiltIn, #cmd, cmd ## Command, \ + nlenMin, minArgs, maxArgs, { ht0, ht1 }, 0 \ + } + EMIT_METACMD_INIT(2); +#undef META_CMD_INFO + { 0, 0, 0, 0, ~0, ~0, {0,0}, 0 } +}; +static unsigned numCommands + = sizeof(command_table)/sizeof(struct CommandInfo) - 1; - /* sqlite3_test_control(int, int) */ - case SQLITE_TESTCTRL_LOCALTIME_FAULT: - case SQLITE_TESTCTRL_NEVER_CORRUPT: - if( nArg==3 ){ - int opt = booleanValue(azArg[2]); - rc2 = sqlite3_test_control(testctrl, opt); - isOk = 3; - } - break; +static MetaCommand *builtInCommand(int ix){ + if( ix<0 || ix>=numCommands ) return 0; + return (MetaCommand *)&command_table[ix]; +} - /* sqlite3_test_control(sqlite3*) */ - case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS: - rc2 = sqlite3_test_control(testctrl, p->db); - isOk = 3; - break; +static void MetaCommand_dtor(MetaCommand *pMe){ + UNUSED_PARAMETER(pMe); +} - case SQLITE_TESTCTRL_IMPOSTER: - if( nArg==5 ){ - rc2 = sqlite3_test_control(testctrl, p->db, - azArg[2], - integerValue(azArg[3]), - integerValue(azArg[4])); - isOk = 3; - } - break; +static const char * MetaCommand_name(MetaCommand *pMe){ + return ((struct CommandInfo *)pMe)->cmdName; +} - case SQLITE_TESTCTRL_SEEK_COUNT: { - u64 x = 0; - rc2 = sqlite3_test_control(testctrl, p->db, &x); - utf8_printf(p->out, "%llu\n", x); - isOk = 3; - break; - } +static const char * MetaCommand_help(MetaCommand *pMe, int more){ + if( more>=0 && more<2 ) return ((struct CommandInfo *)pMe)->azHelp[more]; + else return 0; +} -#ifdef YYCOVERAGE - case SQLITE_TESTCTRL_PARSER_COVERAGE: { - if( nArg==2 ){ - sqlite3_test_control(testctrl, p->out); - isOk = 3; - } - break; - } -#endif -#ifdef SQLITE_DEBUG - case SQLITE_TESTCTRL_TUNE: { - if( nArg==4 ){ - int id = (int)integerValue(azArg[2]); - int val = (int)integerValue(azArg[3]); - sqlite3_test_control(testctrl, id, &val); - isOk = 3; - }else if( nArg==3 ){ - int id = (int)integerValue(azArg[2]); - sqlite3_test_control(testctrl, -id, &rc2); - isOk = 1; - }else if( nArg==2 ){ - int id = 1; - while(1){ - int val = 0; - rc2 = sqlite3_test_control(testctrl, -id, &val); - if( rc2!=SQLITE_OK ) break; - if( id>1 ) utf8_printf(p->out, " "); - utf8_printf(p->out, "%d: %d", id, val); - id++; - } - if( id>1 ) utf8_printf(p->out, "\n"); - isOk = 3; - } - break; - } -#endif - case SQLITE_TESTCTRL_SORTER_MMAP: - if( nArg==3 ){ - int opt = (unsigned int)integerValue(azArg[2]); - rc2 = sqlite3_test_control(testctrl, p->db, opt); - isOk = 3; - } - break; - } +static DotCmdRC + MetaCommand_argsCheck(MetaCommand *pMe, + char **pzErrMsg, int nArgs, char *azArgs[]){ + struct CommandInfo *pci = (struct CommandInfo *)pMe; + UNUSED_PARAMETER(azArgs); + if( pci->minArgs > nArgs ){ + if( pzErrMsg ){ - *pzErrMsg = sqlite3_mprintf("Too few arguments, need %d\n", pci->minArgs); ++ *pzErrMsg = smprintf("Too few arguments for \".%s\", need %d\n", ++ azArgs[0], pci->minArgs-1); } - if( isOk==0 && iCtrl>=0 ){ - utf8_printf(p->out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage); - rc = 1; - }else if( isOk==1 ){ - raw_printf(p->out, "%d\n", rc2); - }else if( isOk==2 ){ - raw_printf(p->out, "0x%08x\n", rc2); + return DCR_TooFew; + }else if( pci->maxArgs > 0 && pci->maxArgs < nArgs ){ + if( pzErrMsg ){ - *pzErrMsg = sqlite3_mprintf("Too many arguments, over %d\n", - pci->maxArgs); ++ *pzErrMsg = smprintf("Too many arguments for \".%s\", over %d\n", ++ azArgs[0], pci->maxArgs-1); } - }else -#endif /* !defined(SQLITE_UNTESTABLE) */ + return DCR_TooMany; + }else return DCR_Ok; +} - if( c=='t' && n>4 && strncmp(azArg[0], "timeout", n)==0 ){ - open_db(p, 0); - sqlite3_busy_timeout(p->db, nArg>=2 ? (int)integerValue(azArg[1]) : 0); - }else +static DotCmdRC + MetaCommand_execute(MetaCommand *pMe, ShellExState *pssx, + char **pzErrMsg, int nArgs, char *azArgs[]){ + return (((struct CommandInfo *)pMe)->cmdDoer)(azArgs, nArgs, pssx, pzErrMsg); +} - if( c=='t' && n>=5 && strncmp(azArg[0], "timer", n)==0 ){ - if( nArg==2 ){ - enableTimer = booleanValue(azArg[1]); - if( enableTimer && !HAS_TIMER ){ - raw_printf(stderr, "Error: timer not available on this system.\n"); - enableTimer = 0; - } +/***************** +** MetaCommand iteration by name match, used by the .help meta-command +** MetaCommands with matching names are produced in lexical order. Any +** returned MetaMatchIter must eventually be passed to freeMetaMatchIter(). +*/ +typedef struct MetaMatchIter { + ShellExState *psx; + /* 0 indicates prepared statement; non-0 is the glob pattern. */ + const char *zPattern; + union { + MetaCommand *pMC; + sqlite3_stmt *stmt; + }; +} MetaMatchIter; + +/* Prepare an iterator that will produce a sequence of MetaCommand + * pointers whose referents names match the given cmdFragment. */ +static MetaMatchIter findMatchingMetaCmds(const char *cmdFragment, + ShellExState *psx){ + MetaMatchIter rv = { psx, 0, 0 }; + if( psx->dbShell==0 ){ - rv.zPattern = sqlite3_mprintf("%s*", cmdFragment? cmdFragment : ""); ++ rv.zPattern = smprintf("%s*", cmdFragment? cmdFragment : ""); + shell_check_oom((void *)rv.zPattern); + rv.pMC = (MetaCommand *)command_table; + }else{ + /* Prepare rv.stmt to yield results glob-matching cmdFragment. */ + const char *zSql = + "SELECT name, extIx, cmdIx FROM ActiveCommands " + "WHERE name glob (?||'*') ORDER BY name"; + sqlite3_prepare_v2(psx->dbShell, zSql, -1, &rv.stmt, 0); + sqlite3_bind_text(rv.stmt, 1, cmdFragment? cmdFragment : "", -1, 0); + } + return rv; +} +/* Produce the next MetaCommand pointer from the iterator, or 0 if no next. */ +static MetaCommand * nextMatchingMetaCmd(MetaMatchIter *pMMI){ + MetaCommand *rv = 0; + if( pMMI->zPattern!=0 ){ + struct CommandInfo *pCI = (struct CommandInfo *)(pMMI->pMC); + assert(pCI>=command_table && pCI<=command_table+numCommands); + while( pCIzPattern, pCI->cmdName)==0 ) rv = pMMI->pMC; + pMMI->pMC = (MetaCommand *)(++pCI); + if( rv!=0 ) break; + } + }else{ + int rc = sqlite3_step(pMMI->stmt); + if( rc==SQLITE_ROW ){ + int extIx = sqlite3_column_int(pMMI->stmt, 1); + int cmdIx = sqlite3_column_int(pMMI->stmt, 2); + return command_by_index(ISS(pMMI->psx), extIx, cmdIx); }else{ - raw_printf(stderr, "Usage: .timer on|off\n"); - rc = 1; + sqlite3_finalize(pMMI->stmt); + pMMI->stmt = 0; } - }else + } + return rv; +} +/* Release resources held by the iterator and clear it. */ +static void freeMetaMatchIter(MetaMatchIter *pMMI){ + if( pMMI->zPattern!=0 ){ + sqlite3_free((void *)pMMI->zPattern); + pMMI->zPattern = 0; + pMMI->pMC = 0; + }else{ + sqlite3_finalize(pMMI->stmt); + pMMI->stmt = 0; + } +} -#ifndef SQLITE_OMIT_TRACE - if( c=='t' && strncmp(azArg[0], "trace", n)==0 ){ - int mType = 0; - int jj; - open_db(p, 0); - for(jj=1; jjeTraceType = SHELL_TRACE_EXPANDED; - } -#ifdef SQLITE_ENABLE_NORMALIZE - else if( optionMatch(z, "normalized") ){ - p->eTraceType = SHELL_TRACE_NORMALIZED; - } +/***************** +** MetaCommand lookup +** +** For the non-extended or non-extensible shell, this function does +** a binary search of the fixed list of meta-command info structs. +** For an extended shell, it queries the shell's DB. Either way, +** this function returns a MetaCommand pointer if one can be found +** with an adequate match for the given name. Here, "adequate" may +** vary according to whether shell extensions have been loaded. If +** not, the match must be for as many characters as set within the +** above CommandInfo array (set via DISPATCHABLE_COMMAND macro call.) +** If shell extensions are loaded, the match must be long enough to +** result in a unique lookup. +*/ +MetaCommand *findMetaCommand(const char *cmdName, ShellExState *psx, - /* out */ int *nFound){ - *nFound = 0; ++ /* out */ int *pnFound){ ++ if( pnFound ) *pnFound = 0; +#if SHELL_DYNAMIC_EXTENSION + if( psx->dbShell!=0 ){ + int rc; - int extIx = -1, cmdIx = -1, nf; ++ int extIx = -1, cmdIx = -1, nf = 0; + sqlite3_stmt *pStmt = 0; + const char *zSql = "SELECT COUNT(*), extIx, cmdIx" + " FROM ActiveCommands WHERE name glob (?||'*')"; + rc = sqlite3_prepare_v2(psx->dbShell, zSql, -1, &pStmt, 0); + sqlite3_bind_text(pStmt, 1, cmdName, -1, 0); + rc = sqlite3_step(pStmt); + nf = sqlite3_column_int(pStmt, 0); + extIx = sqlite3_column_int(pStmt, 1); + cmdIx = sqlite3_column_int(pStmt, 2); + sqlite3_finalize(pStmt); + if( rc!= SQLITE_ROW ) return 0; - *nFound = nf; ++ if( pnFound ) *pnFound = nf; + if( nf!=1 ) return 0; /* Future: indicate ambiguity if > 1 */ + return command_by_index(ISS(psx), extIx, cmdIx); + }else #endif - else if( optionMatch(z, "plain") ){ - p->eTraceType = SHELL_TRACE_PLAIN; - } - else if( optionMatch(z, "profile") ){ - mType |= SQLITE_TRACE_PROFILE; - } - else if( optionMatch(z, "row") ){ - mType |= SQLITE_TRACE_ROW; - } - else if( optionMatch(z, "stmt") ){ - mType |= SQLITE_TRACE_STMT; - } - else if( optionMatch(z, "close") ){ - mType |= SQLITE_TRACE_CLOSE; - } - else { - raw_printf(stderr, "Unknown option \"%s\" on \".trace\"\n", z); - rc = 1; - goto meta_command_exit; - } + { + int cmdLen = strlen30(cmdName); + struct CommandInfo *pci = 0; + int ixb = 0, ixe = numCommands-1; + while( ixb <= ixe ){ + int ixm = (ixb+ixe)/2; + int md = strncmp(cmdName, command_table[ixm].cmdName, cmdLen); + if( md>0 ){ + ixb = ixm+1; + }else if( md<0 ){ + ixe = ixm-1; }else{ - output_file_close(p->traceOut); - p->traceOut = output_file_open(azArg[1], 0); + if( command_table[ixm].minLen > cmdLen ) return 0; + pci = &command_table[ixm]; + break; } } - *nFound = 1; - if( p->traceOut==0 ){ - sqlite3_trace_v2(p->db, 0, 0, 0); - }else{ - if( mType==0 ) mType = SQLITE_TRACE_STMT; - sqlite3_trace_v2(p->db, mType, sql_trace_callback, p); - } - }else -#endif /* !defined(SQLITE_OMIT_TRACE) */ ++ if( pnFound ) *pnFound = 1; + return (MetaCommand *)pci; + } +} -#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_VIRTUALTABLE) - if( c=='u' && strncmp(azArg[0], "unmodule", n)==0 ){ - int ii; - int lenOpt; - char *zOpt; - if( nArg<2 ){ - raw_printf(stderr, "Usage: .unmodule [--allexcept] NAME ...\n"); - rc = 1; - goto meta_command_exit; - } - open_db(p, 0); - zOpt = azArg[1]; - if( zOpt[0]=='-' && zOpt[1]=='-' && zOpt[2]!=0 ) zOpt++; - lenOpt = (int)strlen(zOpt); - if( lenOpt>=3 && strncmp(zOpt, "-allexcept",lenOpt)==0 ){ - assert( azArg[nArg]==0 ); - sqlite3_drop_modules(p->db, nArg>2 ? (const char**)(azArg+2) : 0); - }else{ - for(ii=1; iidb, azArg[ii], 0, 0); - } - } - }else -#endif +/***************** +** Command dispatcher +** After successful command lookup and (simple) argument checking, +** invoke the found MetaCommand with the input arguments (except +** that azArg[0] is replaced with the properly spelled command name.) +** The return is either a dispatch error or whatever the dispatched +** MetaCommand returns. +*/ +static DotCmdRC dispatchCommand(char *azArg[], int nArg, + ShellExState *psx, char **pzErr){ + const char *cmdName = azArg[0]; + int nFound = 0; + DotCmdRC argsCheck; + MetaCommand *pMC = findMetaCommand(cmdName, psx, &nFound); + if( 0==pMC || nFound>1 ) return (nFound>1)? DCR_Ambiguous: DCR_Unknown; + argsCheck = pMC->pMethods->argsCheck(pMC, pzErr, nArg, azArg); + if( argsCheck!=DCR_Ok ) return argsCheck; + /* Replace any user-shortened command name with its whole name. */ + azArg[0] = (char *)(pMC->pMethods->name(pMC)); + return pMC->pMethods->execute(pMC, psx, pzErr, nArg, azArg); +} -#if SQLITE_USER_AUTHENTICATION - if( c=='u' && strncmp(azArg[0], "user", n)==0 ){ - if( nArg<2 ){ - raw_printf(stderr, "Usage: .user SUBCOMMAND ...\n"); - rc = 1; - goto meta_command_exit; - } - open_db(p, 0); - if( strcmp(azArg[1],"login")==0 ){ - if( nArg!=4 ){ - raw_printf(stderr, "Usage: .user login USER PASSWORD\n"); - rc = 1; - goto meta_command_exit; - } - rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3], - strlen30(azArg[3])); - if( rc ){ - utf8_printf(stderr, "Authentication failed for user %s\n", azArg[2]); - rc = 1; - } - }else if( strcmp(azArg[1],"add")==0 ){ - if( nArg!=5 ){ - raw_printf(stderr, "Usage: .user add USER PASSWORD ISADMIN\n"); - rc = 1; - goto meta_command_exit; - } - rc = sqlite3_user_add(p->db, azArg[2], azArg[3], strlen30(azArg[3]), - booleanValue(azArg[4])); - if( rc ){ - raw_printf(stderr, "User-Add failed: %d\n", rc); - rc = 1; - } - }else if( strcmp(azArg[1],"edit")==0 ){ - if( nArg!=5 ){ - raw_printf(stderr, "Usage: .user edit USER PASSWORD ISADMIN\n"); - rc = 1; - goto meta_command_exit; - } - rc = sqlite3_user_change(p->db, azArg[2], azArg[3], strlen30(azArg[3]), - booleanValue(azArg[4])); - if( rc ){ - raw_printf(stderr, "User-Edit failed: %d\n", rc); - rc = 1; - } - }else if( strcmp(azArg[1],"delete")==0 ){ - if( nArg!=3 ){ - raw_printf(stderr, "Usage: .user delete USER\n"); - rc = 1; - goto meta_command_exit; - } - rc = sqlite3_user_delete(p->db, azArg[2]); - if( rc ){ - raw_printf(stderr, "User-Delete failed: %d\n", rc); - rc = 1; +/* +** Output primary (single-line) help for a known command. +*/ +static void showPrimaryHelp(FILE *out, const char *zCmd, ShellExState *psx){ + MetaMatchIter mmi = findMatchingMetaCmds(zCmd, psx); + MetaCommand *pmc = nextMatchingMetaCmd(&mmi); + if( pmc!=0 ){ + const char *zH = pmc->pMethods->help(pmc, 0); + if( zH!=0 && *zH ) utf8_printf(out, HELP_TEXT_FMTP, zH+1); + } + freeMetaMatchIter(&mmi); +} + +/* +** Output various subsets of help text. These 5 are defined: +** 1. For all commands, primary help text only. +** 2. For all commands, complete help text. +** 3. For multiple commands matching a pattern, primary help text only. +** 4. For a single matched command, complete help text. +** 5. For commands whose help contains a pattern, complete help text. +** 6. For the set of "undocumented" (without normal help) commands. +** These variations are indicated thusly: +** 1. zPattern is NULL +** 2. zPattern is "" +** 3. zPattern is a prefix matching more than one command +** 4. zPattern is a word or prefix matching just one command +** 5. zPattern is neither case 3 or 4 but is found in complete help text +** 6. zPattern is exactly the pointer known locally as zHelpAll. +** +** Return the number of matches. +*/ +static int showHelp(FILE *out, const char *zPattern, ShellExState *psx){ + u8 bNullPattern = zPattern==0; + u8 bShowUndoc = zPattern==zHelpAll; + u8 bEmptyPattern = !bNullPattern && (*zPattern==0 || bShowUndoc); + int npm = 0; /* track how many matches found */ + MetaMatchIter mmi = findMatchingMetaCmds(bShowUndoc? "" : zPattern, psx); + MetaCommand *pmc, *pmcLastShown = 0; + char *zPat = 0; + char cLead = (bShowUndoc)? ',' : '.'; + + if( bShowUndoc ){ + utf8_printf(out, "%s\n%s\n", + "The following commands are used for internal SQLite testing.", + "They are undocumented and subject to change without notice."); + } + while( 0 != (pmc = nextMatchingMetaCmd(&mmi)) ){ + const char *zH = pmc->pMethods->help(pmc, 0); + if( zH!=0 && *zH==cLead){ + ++npm; + pmcLastShown = pmc; + utf8_printf(out, HELP_TEXT_FMTP, zH+1); + if( bEmptyPattern ){ + zH = pmc->pMethods->help(pmc, 1); + if( zH!=0 ){ + utf8_printf(out, HELP_TEXT_FMTS, zH); + } } - }else{ - raw_printf(stderr, "Usage: .user login|add|edit|delete ...\n"); - rc = 1; - goto meta_command_exit; } - }else -#endif /* SQLITE_USER_AUTHENTICATION */ + } + freeMetaMatchIter(&mmi); + if( npm==1 && !bEmptyPattern ){ + /* When zPattern is a prefix of exactly one command, then emit + * the secondary help of that command, even if not requested, + * unless it was already emitted. */ + const char *zH = pmcLastShown->pMethods->help(pmcLastShown, 1); + if( zH!=0 ) utf8_printf(out, HELP_TEXT_FMTS, zH); + return npm; + } + /* If found anything with provided (or NULL or empty) pattern, it's done. */ + if( npm>0 ) return npm; + /* Otherwise, look for the pattern in all of the help text and show the + * complete help for those meta-commands whose help matches. */ + mmi = findMatchingMetaCmds("", psx); - zPat = sqlite3_mprintf("%%%s%%", zPattern); ++ zPat = smprintf("%%%s%%", zPattern); + shell_check_oom(zPat); + while( 0 != (pmc = nextMatchingMetaCmd(&mmi)) ){ + const char *zHp = pmc->pMethods->help(pmc, 0); + const char *zHs = pmc->pMethods->help(pmc, 1); + if( zHp==0 || *zHp!='.' ) continue; + if( sqlite3_strlike(zPat, zHp, 0)==0 + || (zHs!=0 && sqlite3_strlike(zPat, zHs, 0)==0) ){ + utf8_printf(out, HELP_TEXT_FMTP, zHp+1); + if( zHs ) utf8_printf(out, HELP_TEXT_FMTS, zHs); + ++npm; + } + } + sqlite3_free(zPat); + freeMetaMatchIter(&mmi); + return npm; +} - if( c=='v' && strncmp(azArg[0], "version", n)==0 ){ - utf8_printf(p->out, "SQLite %s %s\n" /*extra-version-info*/, - sqlite3_libversion(), sqlite3_sourceid()); -#if SQLITE_HAVE_ZLIB - utf8_printf(p->out, "zlib version %s\n", zlibVersion()); +/* +** If an input line or line group begins with "." then invoke this routine +** to process that line. +** +** Return sanitized DotCmdRC values, with invocation error codes +** translated to DCR_Error, so that only these 8 returns are possible: +** DCR_Ok, DCR_Return, DCR_Exit or DCR_Abort possibly or'ed with DCR_Error. +*/ +static DotCmdRC do_meta_command(char *zLine, ShellExState *psx){ + int h = 1; + int nArg = 0; - int n, c; + char *azArg[52]; + char *zErr = 0; + DotCmdRC dispatchResult; +#if SHELL_VARIABLE_EXPANSION + int ncLineIn = strlen30(zLine); + u8 bExpVars = SHEXT_VAREXP(ISS(psx)); #endif -#define CTIMEOPT_VAL_(opt) #opt -#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt) -#if defined(__clang__) && defined(__clang_major__) - utf8_printf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "." - CTIMEOPT_VAL(__clang_minor__) "." - CTIMEOPT_VAL(__clang_patchlevel__) "\n"); -#elif defined(_MSC_VER) - utf8_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) "\n"); -#elif defined(__GNUC__) && defined(__VERSION__) - utf8_printf(p->out, "gcc-" __VERSION__ "\n"); + +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( ISS(psx)->expert.pExpert ){ + expertFinish(ISS(psx), 1, 0); + } #endif - }else - if( c=='v' && strncmp(azArg[0], "vfsinfo", n)==0 ){ - const char *zDbName = nArg==2 ? azArg[1] : "main"; - sqlite3_vfs *pVfs = 0; - if( p->db ){ - sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs); - if( pVfs ){ - utf8_printf(p->out, "vfs.zName = \"%s\"\n", pVfs->zName); - raw_printf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion); - raw_printf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile); - raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname); + /* Parse the input line into tokens. + */ + while( zLine[h] && nArgdb ){ - sqlite3_file_control(p->db, "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent); + dispatchResult = dispatchCommand(azArg, nArg, psx, &zErr); + if( psx->shellAbruptExit!=0 ){ + dispatchResult = DCR_AbortError; + } - if( dispatchResult > DCR_AbortError ){ ++ if( dispatchResult==DCR_CmdErred ){ ++ /* Error message(s) already emitted. Just translate to execute error. */ ++ dispatchResult = DCR_Error; ++ }else if( dispatchResult==DCR_SayUsage ){ ++ if( zErr ){ ++ utf8_printf(STD_ERR, "%s", zErr); ++ }else{ ++ utf8_printf(STD_ERR, "Usage:\n"); ++ showPrimaryHelp(STD_ERR, azArg[0], psx); ++ } ++ dispatchResult = DCR_Error; ++ }else if( dispatchResult > DCR_AbortError ){ + /* Handle invocation errors. */ + int ia = dispatchResult & DCR_ArgIxMask; ++ const char *pArg1st = (ia>=nArg)? "" : azArg[ia]; ++ const char *pArg2nd = 0; + int ec = dispatchResult & ~DCR_ArgIxMask; + int unknownCmd = 0; + const char *z = 0; + switch( ec ){ + case DCR_Unknown: + unknownCmd = ia==0; - z = unknownCmd - ? "unknown dot command: \"%s\"" - : "unknown option or subcommand: \"%s\""; ++ if( unknownCmd ) z = "unknown dot command: \".%s\""; ++ else{ ++ z = "On .%s, unknown option or subcommand: \"%s\""; ++ pArg2nd = pArg1st; ++ pArg1st = azArg[0]; ++ } + break; + case DCR_Ambiguous: - z = ia>0? "\"%s\" is ambiguous; it matches more than one subcommand" - : "\"%s\" is ambiguous; it matches multiple dot commands"; ++ if( ia>0 ){ ++ z = "For .%s, \"%s\" is ambiguous; it matches more than one subcommand"; ++ pArg2nd = pArg1st; ++ pArg1st = azArg[0]; ++ }else z = "\"%s\" is ambiguous; it matches multiple dot commands"; + break; + case DCR_Unpaired: - z = "option %s must be paired with a value argument"; ++ z = "With .%s, option %s must be paired with a value argument"; ++ pArg2nd = pArg1st; ++ pArg1st = azArg[0]; + break; + case DCR_TooMany: - z = "excess arguments provided; try .help %s"; - ia = 0; ++ z = "Excess arguments provided; try .help %s"; ++ pArg1st = azArg[0]; + break; + case DCR_TooFew: - z = "insufficient arguments; try .help %s"; - ia = 0; ++ z = "Insufficient arguments; try .help %s"; ++ pArg1st = azArg[0]; + break; + case DCR_Missing: - z = "required arguments missing; try .help %s"; - ia = 0; ++ z = "Required arguments missing; try .help %s"; ++ pArg1st = azArg[0]; + break; - case DCR_ArgError: - z = "argument(s) are not right; try .help %s"; - ia = 0; ++ case DCR_ArgWrong: ++ z = "Argument(s) incorrect; try .help %s"; ++ pArg1st = azArg[0]; + break; + default: + assert(0); + z = "?"; + break; } - for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){ - utf8_printf(p->out, "vfs.zName = \"%s\"%s\n", pVfs->zName, - pVfs==pCurrent ? " <--- CURRENT" : ""); - raw_printf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion); - raw_printf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile); - raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname); - if( pVfs->pNext ){ - raw_printf(p->out, "-----------------------------------\n"); + if( zErr==0 ){ - zErr = sqlite3_mprintf(z, azArg[ia]); - zErr = sqlite3_mprintf("Error: %z\n", zErr); ++ if( z!=0 ){ ++ zErr = smprintf("Error: %z\n", smprintf(z, pArg1st, pArg2nd)); ++ utf8_printf(STD_ERR, "%s", zErr); + } ++ }else{ ++ const char *fmt ++ = (sqlite3_strnicmp(zErr, "Error", 5)==0)? "%s" : "Error: %s"; ++ utf8_printf(STD_ERR, fmt, zErr); } - utf8_printf(STD_ERR, "%s", zErr); - }else - - if( c=='v' && strncmp(azArg[0], "vfsname", n)==0 ){ - const char *zDbName = nArg==2 ? azArg[1] : "main"; - char *zVfsName = 0; - if( p->db ){ - sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName); - if( zVfsName ){ - utf8_printf(p->out, "%s\n", zVfsName); - sqlite3_free(zVfsName); + if( INSOURCE_IS_INTERACTIVE(ISS(psx)->pInSource) ){ + if( unknownCmd ){ + utf8_printf(STD_ERR, "Enter \".help\" for a list of commands.\n"); + }else{ + utf8_printf(STD_ERR, "Usage:\n"); + showPrimaryHelp(STD_ERR, azArg[0], psx); } } - }else - - if( c=='w' && strncmp(azArg[0], "wheretrace", n)==0 ){ - unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff; - sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &x); - }else - - if( c=='w' && strncmp(azArg[0], "width", n)==0 ){ - int j; - assert( nArg<=ArraySize(azArg) ); - p->nWidth = nArg-1; - p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(int)*2); - if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory(); - if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth]; - for(j=1; jcolWidth[j-1] = (int)integerValue(azArg[j]); + /* If the shell DB is messed up, at least .quit will be doable. */ + if( unknownCmd && psx->dbShell!=0 - && sqlite3_strnicmp(azArg[0],"quit",n)==0 ){ ++ && sqlite3_strnicmp(azArg[0],"quit",strlen30(azArg[0]))==0 ){ + dispatchResult = DCR_Return; + }else{ + dispatchResult = DCR_Error; + } + }else{ + /* Handle execution errors. */ + if( dispatchResult==DCR_AbortError ){ + if( zErr!=0 ){ + utf8_printf(STD_ERR, "Error: \".%s\" may not %s in --safe mode\n", + azArg[0], zErr); + }else { + utf8_printf(STD_ERR, "Error: \".%s\" forbidden in --safe mode\n", + azArg[0]); + } + psx->shellAbruptExit = 3; + }else{ + int error = dispatchResult & DCR_Error; + int action = dispatchResult & ~DCR_Error; + if( error ){ - if( zErr!=0 ) utf8_printf(STD_ERR, "%s", zErr); - else utf8_printf(STD_ERR, "%s failed", azArg[0]); ++ if( zErr!=0 ){ ++ const char *fmt ++ = (sqlite3_strnicmp(zErr, "Error", 5)==0)? "%s" : "Error: %s"; ++ utf8_printf(STD_ERR, fmt, zErr); ++ } ++ else utf8_printf(STD_ERR, "Error: .%s failed\n", azArg[0]); + } } - }else - - { - utf8_printf(stderr, "Error: unknown command or invalid arguments: " - " \"%s\". Enter \".help\" for help\n", azArg[0]); - rc = 1; } - -meta_command_exit: - if( p->outCount ){ - p->outCount--; - if( p->outCount==0 ) output_reset(p); + if( zErr ) sqlite3_free(zErr); + if( ISS(psx)->outCount ){ + ISS(psx)->outCount--; + if( ISS(psx)->outCount==0 ) output_reset(ISS(psx)); } - p->bSafeMode = p->bSafeModePersist; - return rc; + updateSafeMode(ISS(psx)); +#if SHELL_VARIABLE_EXPANSION + if( bExpVars ){ + /* Free any arguments that are allocated rather than tokenized in place. */ + for( n=1; n0 && iArgOffsetinputNesting==MAX_INPUT_NESTING ){ - /* This will be more informative in a later version. */ - utf8_printf(stderr,"Input nesting limit (%d) reached at line %d." - " Check recursion.\n", MAX_INPUT_NESTING, p->lineno); - return 1; - } - ++p->inputNesting; - p->lineno = 0; - while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){ - fflush(p->out); - zLine = one_input_line(p->in, zLine, nSql>0); - if( zLine==0 ){ - /* End of input */ - if( p->in==0 && stdin_is_interactive ) printf("\n"); - break; - } - if( seenInterrupt ){ - if( p->in!=0 ) break; - seenInterrupt = 0; - } - p->lineno++; - if( QSS_INPLAIN(qss) - && line_is_command_terminator(zLine) - && line_is_complete(zSql, nSql) ){ - memcpy(zLine,";",2); - } - qss = quickscan(zLine, qss); - if( QSS_PLAINWHITE(qss) && nSql==0 ){ - if( ShellHasFlag(p, SHFLG_Echo) ) - printf("%s\n", zLine); - /* Just swallow single-line whitespace */ - qss = QSS_Start; +** Determines if a dot-command is open, having either an unclosed +** quoted argument or an escape sequence opener ('\') at its end. +** +** The FSM design/behavior assumes/requires that a terminating '\' +** is not part of the character sequence being classified -- that +** it represents an escaped newline which is removed as physical +** lines are spliced to accumulate logical lines. +** +** The line or added line-portion is passed as zCmd. +** The pScanState pointer must reference an (opaque) DCmd_ScanState, +** which must be set to DCSS_Start to initialize the scanner state. +** Resumed scanning should always be done with zCmd logically just +** past the last non-0 char of the text previously passed in, with +** any previously scanned, trailing newline escape first trimmed. +** Returns are: 0 => not open (aka complete), 1 => is open (incomplete) +** The following macros may be applied to the scan state: +*/ +#define DCSS_InDarkArg(dcss) (((dcss)&argPosMask)==inDqArg) +#define DCSS_EndEscaped(dcss) (((dcss)&endEscaped)!=0) +#define DCSS_IsOpen(dcss) (((dcss)&isOpenMask)!=0) +typedef enum { + DCSS_Start = 0, + twixtArgs = 0, inSqArg = 1, inDarkArg = 2, inDqArg = 3, /* ordered */ + endEscaped = 4, /* bit used */ + argPosMask = 3, /* bits used */ + isOpenMask = 1|4 /* bit test */ +} DCmd_ScanState; + +static void dot_command_scan(char *zCmd, DCmd_ScanState *pScanState){ + DCmd_ScanState ss = *pScanState & ~endEscaped; + char c = (ss&isOpenMask)? 1 : *zCmd++; + while( c!=0 ){ + switch( ss ){ + case twixtArgs: + while( IsSpace(c) ){ + if( (c=*zCmd++)==0 ) goto atEnd; + } + switch( c ){ + case '\\': + if( *zCmd==0 ){ + ss |= endEscaped; + goto atEnd; + }else goto inDark; + case '\'': ss = inSqArg; goto inSq; + case '"': ss = inDqArg; goto inDq; + default: ss = inDarkArg; goto inDark; + } + inSq: + case inSqArg: + while( (c=*zCmd++)!='\'' ){ + if( c==0 ) goto atEnd; + if( c=='\\' && *zCmd==0 ){ + ss |= endEscaped; + goto atEnd; + } + } + ss = twixtArgs; + c = *zCmd++; continue; - } - if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){ - if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zLine); - if( zLine[0]=='.' ){ - rc = do_meta_command(zLine, p); - if( rc==2 ){ /* exit requested */ - break; - }else if( rc ){ - errCnt++; + inDq: + case inDqArg: + do { + if( (c=*zCmd++)==0 ) goto atEnd; + if( c=='\\' ){ + if( (c=*zCmd++)==0 ){ + ss |= endEscaped; + goto atEnd; + } + if( (c=*zCmd++)==0 ) goto atEnd; + } + } while( c!='"' ); + ss = twixtArgs; + c = *zCmd++; + continue; + inDark: + case inDarkArg: + while( !IsSpace(c) ){ + if( c=='\\' && *zCmd==0 ){ + ss |= endEscaped; + goto atEnd; } + if( (c=*zCmd++)==0 ) goto atEnd; } - qss = QSS_Start; + ss = twixtArgs; + c = *zCmd++; continue; } - /* No single-line dispositions remain; accumulate line(s). */ - nLine = strlen30(zLine); - if( nSql+nLine+2>=nAlloc ){ - /* Grow buffer by half-again increments when big. */ - nAlloc = nSql+(nSql>>1)+nLine+100; - zSql = realloc(zSql, nAlloc); - shell_check_oom(zSql); - } - if( nSql==0 ){ - int i; - for(i=0; zLine[i] && IsSpace(zLine[i]); i++){} - assert( nAlloc>0 && zSql!=0 ); - memcpy(zSql, zLine+i, nLine+1-i); - startline = p->lineno; - nSql = nLine-i; + } + atEnd: + *pScanState = ss; +} +#else +# define dot_command_scan(x,y) +#endif + +/* Utility functions for process_input. */ + +#if SHELL_EXTENDED_PARSING +/* +** Process dot-command line with its scan state to: +** 1. Setup for requested line-splicing; and +** 2. Say whether it is complete. +** The last two out parameters are the line's length, which may be +** adjusted, and the char to be used for joining a subsequent line. +** This is broken out of process_input() mainly for readability. +** The return is TRUE for dot-command ready to run, else false. +*/ +static int line_join_ends(DCmd_ScanState dcss, char *zLine, + int *pnLength, char *pcLE){ + /* It is ready only if has no open argument or escaped newline. */ + int bOpen = DCSS_IsOpen(dcss); + if( !DCSS_EndEscaped(dcss) ){ + *pcLE = '\n'; + return !bOpen; + }else{ + *pcLE = (bOpen || DCSS_InDarkArg(dcss))? 0 : ' '; + /* Swallow the trailing escape character. */ + zLine[--*pnLength] = 0; + return 0; + } +} +#endif + +/* +** Grow the accumulation line buffer to accommodate ncNeed chars. +** In/out parameters pz and pna reference the buffer and its size. +** The buffer must eventually be sqlite3_free()'ed by the caller. +*/ +static void grow_line_buffer(char **pz, int *pna, int ncNeed){ + + if( ncNeed > *pna ){ + *pna += *pna + (*pna>>1) + 100; + *pz = sqlite3_realloc(*pz, *pna); + shell_check_oom(*pz); + } +} + +/* +** Read input from designated source (p->pInSource) and process it. +** If pInSource==0 then input is interactive - the user is typing it. +** Otherwise, input is coming from a file, stream device or string. +** Prompts issue and history is saved only for interactive input. +** An interrupt signal will cause this routine to exit immediately, +** with "exit demanded" code returned, unless input is interactive. +** - ** Returns: - ** 0 => no errors - ** 1 => errors>0 - ** 2 => exit demanded, no errors. - ** 3 => exit demanded, errors>0 ++** Returns are the post-execute values of enum DotCmdRC: ++** DCR_Ok, DCR_Return, DCR_Exit, DCR_Abort ++** each of which may be bit-wise or'ed with DCR_Error. +*/ - static int process_input(ShellInState *psi){ ++static DotCmdRC process_input(ShellInState *psi){ + char *zLineInput = 0; /* a line-at-a-time input buffer or usable result */ + char *zLineAccum = 0; /* accumulation buffer, used for multi-line input */ + /* Above two pointers could be local to the group handling loop, but are + * not so that the number of memory allocations can be reduced. They are + * reused from one incoming group to another, realloc()'ed as needed. */ + int naAccum = 0; /* tracking how big zLineAccum buffer has become */ + /* Some flags for ending the overall group processing loop, always 1 or 0 */ - u8 bErrorBail=0, bInputEnd=0, bExitDemand=0, bInterrupted=0; ++ u8 bInputEnd=0, bInterrupted=0; ++ /* Termination kind: DCR_Ok, DCR_Error, DCR_Return, DCR_Exit, DCR_Abort, ++ * the greatest of whichever is applicable */ ++ u8 termKind = DCR_Ok; + /* Flag to affect prompting and interrupt action */ + u8 bInteractive = INSOURCE_IS_INTERACTIVE(psi->pInSource); + int nErrors = 0; /* count of errors during execution or its prep */ + + /* Block overly-recursive or absurdly nested input redirects. */ + if( psi->inputNesting>=MAX_INPUT_NESTING ){ + InSource *pInSrc = psi->pInSource->pFrom; + const char *zLead = "Input nesting limit (" + SHELL_STRINGIFY(MAX_INPUT_NESTING)") reached,"; + int i = 3; + assert(pInSrc!=0 && MAX_INPUT_NESTING>0); + while( i-->0 && pInSrc!=0 ){ + utf8_printf(STD_ERR, + "%s from line %d of \"%s\"", + zLead, pInSrc->lineno, pInSrc->zSourceSay); + zLead = (i%2==0)? "\n" : ""; + pInSrc=pInSrc->pFrom; + } - utf8_printf(STD_ERR, " ...\nERROR: Check recursion.\n"); - return 1; ++ utf8_printf(STD_ERR, " ...\nError: Check recursion.\n"); ++ return DCR_Error; + } + ++psi->inputNesting; + + /* line-group processing loop (per SQL block, dot-command or comment) */ - while( !bErrorBail && !bInputEnd && !bExitDemand && !bInterrupted ){ ++ while( !bInputEnd && termKind==DCR_Ok && !bInterrupted ){ + int nGroupLines = 0; /* count of lines belonging to this group */ + int ncLineIn = 0; /* how many (non-zero) chars are in zLineInput */ + int ncLineAcc = 0; /* how many (non-zero) chars are in zLineAccum */ + int iLastLine = 0; /* index of last accumulated line start */ + /* Initialize resumable scanner(s). */ + SqlScanState sqScanState = SSS_Start; /* for SQL scan */ +#if SHELL_EXTENDED_PARSING + DCmd_ScanState dcScanState = DCSS_Start; /* for dot-command scan */ - int ndcLeadWhite = 0; /* for skip over initial whitespace to . or # */ ++ int ndcLeadWhite = 0; /* skips over initial whitespace to . or # */ + char cLineEnd = '\n'; /* May be swallowed or replaced with space. */ +#else +# define ndcLeadWhite 0 /* For legacy parsing, no white before . or # . */ +# define cLineEnd '\n' /* For legacy parsing, this always joins lines. */ +#endif + /* An ordered enum to record kind of incoming line group. Its ordering + * means than a value greater than Comment implies something runnable. + */ + enum { Tbd = 0, Eof, Comment, Sql, Cmd /*, Tcl */ } inKind = Tbd; + /* An enum signifying the group disposition state */ + enum { + Incoming, Runnable, Dumpable, Erroneous, Ignore + } disposition = Incoming; + char **pzLineUse = &zLineInput; /* ref line to be processed */ + int *pncLineUse = &ncLineIn; /* ref that line's char count */ + int iStartline = 0; /* starting line number of group */ + + fflush(psi->out); + zLineInput = one_input_line(psi->pInSource, zLineInput, nGroupLines>0); + if( zLineInput==0 ){ + bInputEnd = 1; + inKind = Eof; + disposition = Ignore; + if( bInteractive ) printf("\n"); }else{ - zSql[nSql++] = '\n'; - memcpy(zSql+nSql, zLine, nLine+1); - nSql += nLine; - } - if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){ - errCnt += runOneSqlLine(p, zSql, p->in, startline); - nSql = 0; - if( p->outCount ){ - output_reset(p); - p->outCount = 0; - }else{ - clearTempFile(p); + ++nGroupLines; + iStartline = psi->pInSource->lineno; + ncLineIn = strlen30(zLineInput); + if( seenInterrupt ){ + if( psi->pInSource!=0 ) break; + bInterrupted = 1; /* This will be honored, or not, later. */ + seenInterrupt = 0; + disposition = Dumpable; + } + /* Classify and check for single-line dispositions, prep for more. */ +#if SHELL_EXTENDED_PARSING + ndcLeadWhite = (SHEXT_PARSING(psi)) + ? skipWhite(zLineInput)-zLineInput + : 0; /* Disallow leading whitespace for . or # in legacy mode. */ +#endif + switch( zLineInput[ndcLeadWhite] ){ + case '.': + inKind = Cmd; + dot_command_scan(zLineInput+ndcLeadWhite, &dcScanState); + break; + case '#': + inKind = Comment; + break; + default: + /* Might be SQL, or a swallowable whole SQL comment. */ + sql_prescan(zLineInput, &sqScanState); + if( SSS_PLAINWHITE(sqScanState) ){ + /* It's either all blank or a whole SQL comment. Swallowable. */ + inKind = Comment; + }else{ + /* Something dark, not a # comment or dot-command. Must be SQL. */ + inKind = Sql; + } + break; + } /* end classification switch */ + } /* end read/classify initial group input line */ + + /* Here, if not at end of input, the initial line of group is in, and + * it has been scanned and classified. Next, do the processing needed + * to recognize whether the initial line or accumulated group so far + * is complete such that it may be run, and perform joining of more + * lines into the group while it is not so complete. This loop ends + * with the input group line(s) ready to be run, or if the input ends + * before it is ready, with the group marked as erroneous. + */ + while( disposition==Incoming ){ + /* Check whether more to accumulate, or ready for final disposition. */ + switch( inKind ){ + case Comment: + disposition = Dumpable; + case Cmd: +#if SHELL_EXTENDED_PARSING + if( SHEXT_PARSING(psi) ){ + if( line_join_ends(dcScanState, *pzLineUse, pncLineUse, &cLineEnd) ){ + disposition = Runnable; + } + }else +#endif + disposition = Runnable; /* Legacy, any dot-command line is ready. */ + break; + case Sql: + /* Check to see if it is complete and ready to run. */ + if( SSS_SEMITERM(sqScanState) && sqlite3_complete(*pzLineUse)){ + disposition = Runnable; + }else if( SSS_PLAINWHITE(sqScanState) ){ + /* It is a leading single-line or multi-line comment. */ + disposition = Runnable; + inKind = Comment; + }else{ + char *zT = line_is_command_terminator(zLineInput); + if( zT!=0 ){ + /* Last line is a lone go or / -- prep for running it. */ + if( nGroupLines>1 ){ + disposition = Runnable; + memcpy(*pzLineUse+iLastLine,";\n",3); + *pncLineUse = iLastLine + 2; + }else{ + /* Unless nothing preceded it, then dump it. */ + disposition = Dumpable; + } + } + } + break; + } /* end switch on inKind */ + /* Collect and accumulate more input if group not yet complete. */ + if( disposition==Incoming ){ + if( nGroupLines==1 ){ + grow_line_buffer(&zLineAccum, &naAccum, ncLineIn+2); + /* Copy line just input */ + memcpy(zLineAccum, zLineInput, ncLineIn); + ncLineAcc = ncLineIn; + pzLineUse = &zLineAccum; + pncLineUse = &ncLineAcc; + } + /* Read in next line of group, (if available.) */ + zLineInput = one_input_line(psi->pInSource, zLineInput, nGroupLines>0); + if( zLineInput==0 ){ + bInputEnd = 1; + inKind = Eof; + disposition = Erroneous; + if( bInteractive ) printf("\n"); + continue; + } + ++nGroupLines; + ncLineIn = strlen30(zLineInput); + /* Scan line just input (if needed) and append to accumulation. */ + switch( inKind ){ + case Cmd: + dot_command_scan(zLineInput, &dcScanState); + break; + case Sql: + sql_prescan(zLineInput, &sqScanState); + break; + default: + break; + } + grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+ncLineIn+2); + /* Join lines as setup by exam of previous line(s). */ + if( cLineEnd!=0 ) zLineAccum[ncLineAcc++] = cLineEnd; + cLineEnd = '\n'; /* reset to default after use */ + memcpy(zLineAccum+ncLineAcc, zLineInput, ncLineIn); + iLastLine = ncLineAcc; + ncLineAcc += ncLineIn; + zLineAccum[ncLineAcc] = 0; + } /* end glom another line */ + } /* end group collection loop */ + /* Here, the group is fully collected or known to be incomplete forever. */ + switch( disposition ){ + case Dumpable: + echo_group_input(psi, *pzLineUse); + break; + case Runnable: + switch( inKind ){ + case Sql: + /* runOneSqlLine() does its own echo when requested. */ + nErrors += runOneSqlLine(XSS(psi), *pzLineUse, + INSOURCE_IS_INTERACTIVE(psi->pInSource), + iStartline); - if( bail_on_error && nErrors>0 ) bErrorBail = 1; + break; + case Cmd: { + DotCmdRC dcr; + echo_group_input(psi, *pzLineUse); + dcr = do_meta_command(*pzLineUse+ndcLeadWhite, XSS(psi)); + nErrors += (dcr & DCR_Error); - bExitDemand |= (dcr & ~DCR_Error)!= DCR_Ok; ++ dcr &= ~DCR_Error; ++ if( dcr > termKind ) termKind = dcr; + break; + } + default: + assert(inKind!=Tbd); + break; } - if( XSS(psi)->shellAbruptExit!=0 ) bExitDemand = 1; - p->bSafeMode = p->bSafeModePersist; - qss = QSS_Start; - }else if( nSql && QSS_PLAINWHITE(qss) ){ - if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zSql); - nSql = 0; - qss = QSS_Start; ++ if( XSS(psi)->shellAbruptExit!=0 ){ ++ termKind = XSS(psi)->shellAbruptExit; ++ } + break; + case Erroneous: + utf8_printf(STD_ERR, "Error: Input incomplete at line %d of \"%s\"\n", + psi->pInSource->lineno, psi->pInSource->zSourceSay); + ++nErrors; + break; + case Ignore: + break; + default: assert(0); } - if( nErrors>0 && bail_on_error ) bErrorBail = 1; - } - if( nSql ){ - /* This may be incomplete. Let the SQL parser deal with that. */ - errCnt += runOneSqlLine(p, zSql, p->in, startline); - } - free(zSql); - free(zLine); - --p->inputNesting; - return errCnt>0; ++ if( bail_on_error && nErrors>0 && termKind==DCR_Ok ) termKind = DCR_Error; + } /* end group consume/prep/(run, dump or complain) loop */ + + /* Cleanup and determine return value based on flags and error count. */ + free(zLineInput); /* Allocated via malloc() by readline or equivalents. */ + sqlite3_free(zLineAccum); + - return ((bErrorBail | bExitDemand)<<1) + (nErrors>0); ++ /* Translate DCR_Return because it has been done here, not to propagate. */ ++ if( termKind==DCR_Return ) termKind = DCR_Ok; ++ ++ return termKind|(nErrors>0); } /* @@@ -14054,27 -11662,23 +14045,27 @@@ static void process_sqliterc " cannot read ~/.sqliterc\n"); return; } -- zBuf = sqlite3_mprintf("%s/.sqliterc",home_dir); ++ zBuf = smprintf("%s/.sqliterc",home_dir); shell_check_oom(zBuf); sqliterc = zBuf; } - p->in = fopen(sqliterc,"rb"); - if( p->in ){ + inUse = fopen(sqliterc,"rb"); + if( inUse!=0 ){ + InSource inSourceRedir + = INSOURCE_FILE_REDIR(inUse, sqliterc, psi->pInSource); - int rc; ++ DotCmdRC rc; + psi->pInSource = &inSourceRedir; if( stdin_is_interactive ){ - utf8_printf(stderr,"-- Loading resources from %s\n",sqliterc); + utf8_printf(STD_ERR,"-- Loading resources from %s\n",sqliterc); } - if( process_input(p) && bail_on_error ) exit(1); - fclose(p->in); + rc = process_input(psi); + fclose(inUse); + psi->pInSource = inSourceRedir.pFrom; - if( rc!=0 && bail_on_error ) XSS(psi)->shellAbruptExit = rc; ++ if( rc>DCR_Ok && bail_on_error ) XSS(psi)->shellAbruptExit = rc; }else if( sqliterc_override!=0 ){ - utf8_printf(stderr,"cannot open: \"%s\"\n", sqliterc); - if( bail_on_error ) exit(1); + utf8_printf(STD_ERR,"cannot open: \"%s\"\n", sqliterc); + if( bail_on_error ) exit(1); /* Future: Exit shell rather than process. */ } - p->in = inSaved; - p->lineno = savedLineno; sqlite3_free(zBuf); } @@@ -14246,26 -11836,17 +14237,27 @@@ static char *cmdline_option_value(int a # endif #endif +#ifndef SHELL_MAIN +# if SQLITE_SHELL_IS_UTF8 +# define SHELL_MAIN main +# else +# define SHELL_MAIN wmain +# endif +#endif + #if SQLITE_SHELL_IS_UTF8 -int SQLITE_CDECL main(int argc, char **argv){ +int SQLITE_CDECL SHELL_MAIN(int argc, char **argv){ #else -int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ +int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){ char **argv; #endif - char *zErrMsg = 0; - ShellState data; + ShellInState data; + ShellExState datax; const char *zInitFile = 0; - int i; + int bQuiet = 0; /* for testing, to suppress banner and history actions */ + int i, aec; int rc = 0; ++ DotCmdRC drc = DCR_Ok; int warnInmemoryDb = 0; int readStdin = 1; int nCmd = 0; @@@ -14694,29 -12270,33 +14686,31 @@@ if( i==argc-1 ) break; z = cmdline_option_value(argc,argv,++i); if( z[0]=='.' ){ - rc = do_meta_command(z, &datax); - rc = do_meta_command(z, &data); - if( rc && bail_on_error ) return rc==2 ? 0 : rc; ++ drc = do_meta_command(z, &datax); ++ rc = (drc>2)? 2 : drc; }else{ - open_db(&data, 0); - rc = shell_exec(&data, z, &zErrMsg); - if( zErrMsg!=0 ){ - utf8_printf(stderr,"Error: %s\n", zErrMsg); - if( bail_on_error ) return rc!=0 ? rc : 1; - }else if( rc!=0 ){ - utf8_printf(stderr,"Error: unable to process SQL \"%s\"\n", z); - if( bail_on_error ) return rc; - } + rc = run_single_query(&datax, z); + } + if( rc && bail_on_error ){ + if( rc==2 ) rc = 0; + goto shell_bail; } #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) }else if( strncmp(z, "-A", 2)==0 ){ if( nCmd>0 ){ - utf8_printf(stderr, "Error: cannot mix regular SQL or dot-commands" + utf8_printf(STD_ERR, "Error: cannot mix regular SQL or dot-commands" " with \"%s\"\n", z); - return 1; + rc = 1; + goto shell_bail; } - open_db(&data, OPEN_DB_ZIPFILE); + open_db(&datax, OPEN_DB_ZIPFILE); if( z[2] ){ argv[i] = &z[2]; - rc = arDotCommand(&datax, 1, argv+(i-1), argc-(i-1)); - arDotCommand(&data, 1, argv+(i-1), argc-(i-1)); ++ drc = arDotCommand(&datax, 1, argv+(i-1), argc-(i-1)); }else{ - rc = arDotCommand(&datax, 1, argv+i, argc-i); - arDotCommand(&data, 1, argv+i, argc-i); ++ drc = arDotCommand(&datax, 1, argv+i, argc-i); } ++ rc = (drc>2)? 2 : drc; readStdin = 0; break; #endif @@@ -14741,14 -12315,26 +14735,15 @@@ ** command-line inputs, except for the argToSkip argument which contains ** the database filename. */ - for(i=0; i2)? 2 : drc; }else{ - open_db(&data, 0); - rc = shell_exec(&data, azCmd[i], &zErrMsg); - if( zErrMsg || rc ){ - if( zErrMsg!=0 ){ - utf8_printf(stderr,"Error: %s\n", zErrMsg); - }else{ - utf8_printf(stderr,"Error: unable to process SQL: %s\n", azCmd[i]); - } - sqlite3_free(zErrMsg); - free(azCmd); - return rc!=0 ? rc : 1; - } + rc = run_single_query(&datax, azCmd[i]); + } + if( rc && bail_on_error ){ + goto shell_bail; } } }else{ @@@ -14758,60 -12344,50 +14753,62 @@@ char *zHome; char *zHistory; int nHistory; - printf( - "SQLite version %s %.19s\n" /*extra-version-info*/ - "Enter \".help\" for usage hints.\n", - sqlite3_libversion(), sqlite3_sourceid() - ); - if( warnInmemoryDb ){ - printf("Connected to a "); - printBold("transient in-memory database"); - printf(".\nUse \".open FILENAME\" to reopen on a " - "persistent database.\n"); - } - zHistory = getenv("SQLITE_HISTORY"); - if( zHistory ){ - zHistory = strdup(zHistory); - }else if( (zHome = find_home_dir(0))!=0 ){ - nHistory = strlen30(zHome) + 20; - if( (zHistory = malloc(nHistory))!=0 ){ - sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome); + if( bQuiet ){ + /* bQuiet is almost like normal interactive, but quieter. */ + mainPrompt[0] = 0; + continuePrompt[0] = 0; + }else{ + fprintf(STD_OUT, + "SQLite version %s %.19s\n" /*extra-version-info*/ + "Enter \".help\" for usage hints.\n", + sqlite3_libversion(), sqlite3_sourceid() + ); + if( warnInmemoryDb ){ + fprintf(STD_OUT, "Connected to a "); + printBold("transient in-memory database"); + fprintf(STD_OUT, ".\nUse \".open FILENAME\" to reopen on a " + "persistent database.\n"); } - } - if( zHistory ){ shell_read_history(zHistory); } + zHistory = getenv("SQLITE_HISTORY"); + if( zHistory ){ + zHistory = strdup(zHistory); + }else if( (zHome = find_home_dir(0))!=0 ){ + nHistory = strlen30(zHome) + 20; + if( (zHistory = malloc(nHistory))!=0 ){ + sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome); + } + } + if( zHistory ){ shell_read_history(zHistory); } #if HAVE_READLINE || HAVE_EDITLINE - rl_attempted_completion_function = readline_completion; + rl_attempted_completion_function = readline_completion; #elif HAVE_LINENOISE - linenoiseSetCompletionCallback(linenoise_completion); + linenoiseSetCompletionCallback(linenoise_completion); #endif - data.in = 0; - rc = process_input(&data); - if( zHistory ){ - shell_stifle_history(2000); - shell_write_history(zHistory); - free(zHistory); + } + data.pInSource = &termInSource; /* read from stdin interactively */ - rc = process_input(&data); ++ drc = process_input(&data); ++ rc = (drc>2)? 2 : drc; + if( !bQuiet ){ + if( zHistory ){ + shell_stifle_history(2000); + shell_write_history(zHistory); + free(zHistory); + } } }else{ - data.in = stdin; - rc = process_input(&data); + data.pInSource = &stdInSource; /* read from stdin without prompts */ - rc = process_input(&data); ++ drc = process_input(&data); ++ rc = (drc>2)? 2 : drc; } } - free(azCmd); - set_table_name(&data, 0); - if( data.db ){ + shell_bail: + set_table_name(&datax, 0); + if( datax.dbUser ){ session_close_all(&data, -1); - close_db(data.db); + close_db(datax.dbUser); } + sqlite3_mutex_free(pGlobalDbLock); + pGlobalDbLock = 0; for(i=0; i +#include +#include +#include + +#include "sqlite3ext.h" + +#include "obj_interfaces.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Convey data to, from and/or between I/O handlers and meta-commands. */ +typedef struct ShellExState { + /* A sizeof(*) to permit extensions to guard against too-old hosts */ + int sizeofThis; + + /* A semi-transient holder of arbitrary data used during operations + * not interrupted by meta-command invocations. Any not-null pointer + * left after a meta-command has completed is, by contract, to be + * freeable using sqlite3_free(), unless freeHandlerData is non-zero, + * in which case it is used for the free, then zeroed too. This + * pointer's use is otherwise unconstrained. */ + void *pvHandlerData; + void (*freeHandlerData)(void *); + + /* The user's currently open and primary DB connection + * Extensions may use this DB, but must not modify this pointer. + */ + sqlite3 *dbUser; + /* DB connection for shell dynamical data and extension management + * Extensions may use this DB, but should not alter content created + * by the shell nor depend upon its schema. Names with prefix "Shell" + * or "shext_" are reserved for the shell's use. + */ + sqlite3 *dbShell; + + /* Output stream to which shell's text output to be written (reference) */ + FILE **ppCurrentOutput; + + /* Whether to exit as command completes. + * 0 => no exit + * ~0 => a non-error (0) exit + * other => exit with process exit code other + * For embedded shell, "exit" means "return from REPL function". + */ + int shellAbruptExit; + + /* Number of lines written during a query result output */ + int resultCount; + /* Whether to show column names for certain output modes (reference) */ + unsigned char *pShowHeader; + /* Column separator character for some modes, read-only */ + char *zFieldSeparator; + /* Row separator character for some modes (MODE_Ascii), read-only */ + char *zRecordSeparator; + /* Row set prefix for some modes if not 0 */ + char *zRecordLead; + /* Row set suffix for some modes if not 0 */ + char *zRecordTail; + /* Text to represent a NULL in external data formats, read-only */ + char *zNullValue; + /* Name of table for which inserts are to be written or performed */ + const char *zDestTable; + /* Next 3 members should be set and/or allocated by .width meta-command. + * The values of pSpecWidths[i] and pHaveWidths[i] can be modified or + * used by extensions, but setColumnWidths(...) must resize those lists. + */ + /* Number of column widths presently desired or tracked, read-only */ + int numWidths; /* known allocation count of next 2 members */ + /* The column widths last specified via .width command */ + int *pSpecWidths; + /* The column widths last observed in query results, read-only */ + int *pHaveWidths; + + /* Internal and opaque shell state, not for use by extensions */ + struct ShellInState *pSIS; /* Offset of this member is NOT STABLE. */ +} ShellExState; + +/***************** + * See "Shell Extensions, Programming" for purposes and usage of the following + * interfaces supporting extended meta-commands and import and output modes. + */ + +/* Define status codes returned by a meta-command, either during its argument + * checking or during its execution (to which checking may be deferred.) The + * code has 1 or 2 parts. The low-valued codes, below MCR_ArgIxMask, have an + * action part and an error flag. Higher-valued codes are bitwise-or'ed with + * a small integer and indicate problems with the meta-command itself. + */ +typedef enum DotCmdRC { - /* Post-execute action and success/error status */ ++ /* Post-execute action and success/error status (semi-ordered) */ + DCR_Ok = 0, /* ordinary success and continue */ + DCR_Error = 1, /* or'ed with low-valued codes upon error */ + DCR_Return = 2, /* return from present input source/script */ + DCR_ReturnError = 3, /* return with error */ + DCR_Exit = 4, /* exit shell ( process or pseudo-main() ) */ + DCR_ExitError = 5, /* exit with error */ + DCR_Abort = 6, /* abort for unrecoverable cause (OOM) */ + DCR_AbortError = 7, /* abort with error (blocked unsafe) */ ++ /* Above are in reverse-priority order for process_input() returns. */ ++ + /* Dispatch and argument errors */ + DCR_ArgIxMask = 0xfff, /* mask to retain/exclude argument index */ + /* Below codes may be or'ed with the offending argument index */ + DCR_Unknown = 0x1000, /* unknown command, subcommand or option */ + DCR_Ambiguous = 0x2000, /* ambiguous (sub)command (too abreviated) */ + DCR_Unpaired = 0x3000, /* option value indicated but missing */ + DCR_TooMany = 0x4000, /* excess arguments were provided */ + DCR_TooFew = 0x5000, /* insufficient arguments provided */ + DCR_Missing = 0x6000, /* required argument(s) missing */ - DCR_ArgError = 0x7000 /* non-specific argument error */ ++ DCR_ArgWrong = 0x7000, /* non-specific argument error, nothing emitted */ ++ ++ /* This code indicates error and a usage message to be emitted to stderr. */ ++ DCR_SayUsage = 0x7ffd, /* usage is at *pzErr or is to be generated */ ++ /* This code indicates nothing more need be put to stderr (or stdout.) */ ++ DCR_CmdErred = 0x7fff /* non-specific error for which complaint is done */ +} DotCmdRC; + +/* An object implementing below interface is registered with the + * shell to make new or overriding meta-commands available to it. + */ +INTERFACE_BEGIN( MetaCommand ); +PURE_VMETHOD(const char *, name, MetaCommand, 0,()); +PURE_VMETHOD(const char *, help, MetaCommand, 1,(int more)); +PURE_VMETHOD(DotCmdRC, argsCheck, MetaCommand, + 3, (char **pzErrMsg, int nArgs, char *azArgs[])); +PURE_VMETHOD(DotCmdRC, execute, MetaCommand, + 4,(ShellExState *, char **pzErrMsg, int nArgs, char *azArgs[])); +INTERFACE_END( MetaCommand ); + +/* An object implementing below interface is registered with the + * shell to make new or overriding output modes available to it. + */ +INTERFACE_BEGIN( OutModeHandler ); +PURE_VMETHOD(const char *, name, OutModeHandler, 0,()); +PURE_VMETHOD(const char *, help, OutModeHandler, 1,(int more)); +PURE_VMETHOD(int, openResultsOutStream, OutModeHandler, + 5,( ShellExState *pSES, char **pzErr, + int numArgs, char *azArgs[], const char * zName )); +PURE_VMETHOD(int, prependResultsOut, OutModeHandler, + 3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt )); +PURE_VMETHOD(int, rowResultsOut, OutModeHandler, + 3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt )); +PURE_VMETHOD(int, appendResultsOut, OutModeHandler, + 3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt )); +PURE_VMETHOD(void, closeResultsOutStream, OutModeHandler, + 2,( ShellExState *pSES, char **pzErr )); +INTERFACE_END( OutModeHandlerVtable ); + +/* An object implementing below interface is registered with the + * shell to make new or overriding data importers available to it. + */ +INTERFACE_BEGIN( ImportHandler ); +PURE_VMETHOD(const char *, name, ImportHandler, 0,()); +PURE_VMETHOD(const char *, help, ImportHandler, 1,( int more )); +PURE_VMETHOD(int, openDataInStream, ImportHandler, + 5,( ShellExState *pSES, char **pzErr, + int numArgs, char *azArgs[], const char * zName )); +PURE_VMETHOD(int, prepareDataInput, ImportHandler, + 3,( ShellExState *pSES, char **pzErr, sqlite3_stmt * *ppStmt )); +PURE_VMETHOD(int, rowDataInput, ImportHandler, + 3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt )); +PURE_VMETHOD(int, finishDataInput, ImportHandler, + 3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt )); +PURE_VMETHOD(void, closeDataInStream, ImportHandler, + 2,( ShellExState *pSES, char **pzErr )); +INTERFACE_END( ImportHandlerVtable ); + +/* This function pointer has the same signature as the sqlite3_X_init() + * function that is called as SQLite3 completes loading an extension. + */ +typedef int (*ExtensionId) + (sqlite3 *, char **, const struct sqlite3_api_routines *); + +typedef struct ExtensionHelpers { + int helperCount; /* Helper count, not including sentinel */ + union { + struct ExtHelpers { + int (*failIfSafeMode)(ShellExState *p, const char *zErrMsg, ...); + FILE * (*currentOutputFile)(ShellExState *p); + struct InSource * (*currentInputSource)(ShellExState *p); + char * (*strLineGet)(char *zBuf, int ncMax, struct InSource *pInSrc); ++ MetaCommand * (*findMetaCommand)(const char *cmdName, ShellExState *p, ++ /* out */ int *pnFound); + void (*setColumnWidths)(ShellExState *p, char *azWidths[], int nWidths); + int (*nowInteractive)(ShellExState *p); + void (*sentinel)(void); + } named ; + void (*nameless[5+1])(); /* Same as named but anonymous plus a sentinel. */ + } helpers; +} ExtensionHelpers; + +/* Various shell extension helpers and feature registration functions */ +typedef struct ShellExtensionAPI { + /* Utility functions for use by extensions */ + ExtensionHelpers * pExtHelpers; + + /* Functions for extension to register its implementors with shell */ + const int numRegistrars; /* 3 for this version */ + union { + struct ShExtAPI { + /* Register a meta-command */ + int (*registerMetaCommand)(ShellExState *p, + ExtensionId eid, MetaCommand *pMC); + /* Register query result data display (or other disposition) mode */ + int (*registerOutMode)(ShellExState *p, + ExtensionId eid, OutModeHandler *pOMH); + /* Register an import variation from (various sources) for .import */ + int (*registerImporter)(ShellExState *p, + ExtensionId eid, ImportHandler *pIH); + /* Preset to 0 at extension load, a sentinel for expansion */ + void (*sentinel)(void); + } named; + void (*pFunctions[4])(); /* 0-terminated sequence of function pointers */ + } api; +} ShellExtensionAPI; + +/* Struct passed to extension init function to establish linkage. The + * lifetime of instances spans only the init call itself. Extensions + * should make a copy, if needed, of pShellExtensionAPI for later use. + * Its referant is static, persisting for the process duration. + */ +typedef struct ShellExtensionLink { + int sizeOfThis; /* sizeof(ShellExtensionLink) for expansion */ + ShellExtensionAPI *pShellExtensionAPI; + ShellExState *pSXS; /* For use in extension feature registrations */ + char *zErrMsg; /* Extension error messages land here, if any. */ + + /* An init "out" parameter, used as the loaded extension ID. Unless + * this is set within sqlite3_X_init() prior to register*() calls, + * the extension cannot be unloaded. + */ + ExtensionId eid; + + /* Another init "out" parameter, a destructor for extension overall. + * Set to 0 on input and may be left so if no destructor is needed. + */ + void (*extensionDestruct)(void *); + +} ShellExtensionLink; + +/* String used with SQLite "Pointer Passing Interfaces" as a type marker. + * That API subset is used by the shell to pass its extension API to the + * sqlite3_X_init() function of shell extensions, via the DB parameter. + */ +#define SHELLEXT_API_POINTERS "shellext_api_pointers" + +/* Pre-write a function to retrieve a ShellExtensionLink pointer from the + * shell's DB. This macro defines a function which will return either a + * pointer to a ShellExtensionLink instance during an extension's *init*() + * call (during shell extension load) or 0 (during SQLite extension load.) + */ +#define DEFINE_SHDB_TO_SHEXTLINK(func_name) \ + static ShellExtensionLink * func_name(sqlite3 * db){ \ + ShellExtensionLink *rv = 0; sqlite3_stmt *pStmt = 0; \ + if( SQLITE_OK==sqlite3_prepare_v2(db,"SELECT shext_pointer(0)",-1,&pStmt,0) \ + && SQLITE_ROW == sqlite3_step(pStmt) ) \ + rv = (ShellExtensionLink *)sqlite3_value_pointer \ + (sqlite3_column_value(pStmt, 0), SHELLEXT_API_POINTERS); \ + sqlite3_finalize(pStmt); return rv; \ + } + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* !defined(SQLITE3SHX_H) */ diff --cc src/test_shellext.c index 52fb1d431e,0000000000..1db2b5da28 mode 100644,000000..100644 --- a/src/test_shellext.c +++ b/src/test_shellext.c @@@ -1,127 -1,0 +1,138 @@@ +/* +** 2022 Feb 28 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** Test extension for testing the shell's .load -shellext ... function. +** gcc -shared -fPIC -Wall -I$srcdir -I.. -g test_shellext.c -o test_shellext.so +*/ +#include +#include "shext_linkage.h" + +SQLITE_EXTENSION_INIT1; + +static struct ShExtAPI *pShExtApi = 0; +static struct ExtHelpers *pExtHelpers = 0; + +/* These DERIVED_METHOD(...) macro calls' arguments were copied and + * pasted from the MetaCommand interface declaration in shext_linkage.h , + * but with Interface,Derived substituted for the interface typename. + * The function bodies are not so easily written, of course. */ + +DERIVED_METHOD(void, destruct, MetaCommand,BatBeing, 0, ()){ + fprintf(stderr, "BatBeing unbecoming.\n"); +} + +DERIVED_METHOD(const char *, name, MetaCommand,BatBeing, 0,()){ + return "bat_being"; +} + +DERIVED_METHOD(const char *, help, MetaCommand,BatBeing, 1,(int more)){ + switch( more ){ + case 0: return + ".bat_being ?whatever? Demonstrates vigilantism weekly\n"; + case 1: return " Options summon side-kick and villains.\n"; + default: return 0; + } +} + - DERIVED_METHOD(int, argsCheck, MetaCommand,BatBeing, 3, ++DERIVED_METHOD(DotCmdRC, argsCheck, MetaCommand,BatBeing, 3, + (char **pzErrMsg, int nArgs, char *azArgs[])){ - return 0; ++ return DCR_Ok; +} + +typedef struct BatBeing BatBeing; - static void sayHowMany( struct BatBeing *pbb, FILE *out ); ++static void sayHowMany( struct BatBeing *pbb, FILE *out, ShellExState *psx ); + - DERIVED_METHOD(int, execute, MetaCommand,BatBeing, 4, ++DERIVED_METHOD(DotCmdRC, execute, MetaCommand,BatBeing, 4, + (ShellExState *psx, char **pzErrMsg, int nArgs, char *azArgs[])){ + FILE *out = pExtHelpers->currentOutputFile(psx); + switch( nArgs ){ + default: fprintf(out, "The Penguin, Joker and Riddler have teamed up!\n"); + case 2: fprintf(out, "The Dynamic Duo arrives, and ... "); + case 1: fprintf(out, "@#$ KaPow! $#@\n"); + } - sayHowMany((struct BatBeing *)pThis, out); - return 0; ++ sayHowMany((struct BatBeing *)pThis, out, psx); ++ return DCR_Ok; +} + +/* Note that these CONCRETE_METHOD... macro calls' arguments were copied and + * pasted from the MetaCommand interface declaration in shext_linkage.h . + * In a future version of shext_linkage.h, this will all be a mondo maco. */ +CONCRETE_BEGIN(MetaCommand, BatBeing); +CONCRETE_METHOD(const char *, name, MetaCommand, 0,()); +CONCRETE_METHOD(const char *, help, MetaCommand, 1,(int more)); - CONCRETE_METHOD(int, argsCheck, MetaCommand, 3, ++CONCRETE_METHOD(DotCmdRC, argsCheck, MetaCommand, 3, + (char **pzErrMsg, int nArgs, char *azArgs[])); - CONCRETE_METHOD(int, execute, MetaCommand, 4, ++CONCRETE_METHOD(DotCmdRC, execute, MetaCommand, 4, + (ShellExState *, char **pzErrMsg, int nArgs, char *azArgs[])); +CONCRETE_END(BatBeing) batty_methods = { + DECORATE_METHOD(BatBeing,destruct), + DECORATE_METHOD(BatBeing,name), + DECORATE_METHOD(BatBeing,help), + DECORATE_METHOD(BatBeing,argsCheck), + DECORATE_METHOD(BatBeing,execute) +}; + +INSTANCE_BEGIN(BatBeing); + int numCalls; ++ MetaCommand * pShow; +INSTANCE_END(BatBeing) batty = { + &batty_methods, - 0 ++ 0, 0 +}; + - static void sayHowMany( struct BatBeing *pbb, FILE *out ){ ++static void sayHowMany( struct BatBeing *pbb, FILE *out, ShellExState *psx ){ + fprintf(out, "This execute has been called %d times.\n", ++pbb->numCalls); ++ if( pbb->pShow ){ ++ char *az[] = { "show" }; ++ char *zErr = 0; ++ MetaCommand * pmcShow = (MetaCommand *)pbb->pShow; ++ DotCmdRC rc = pmc->pMethods->execute(pmc, psx, &zErr, 1, az); ++ if( rc!= DCR_Ok ){ ++ fprintf(out, "show() failed: %d\n", rc); ++ } ++ } +} + + +DEFINE_SHDB_TO_SHEXTLINK(shext_link); + +/* +** Extension load function. +*/ +#ifdef _WIN32 +__declspec(dllexport) +#endif +int sqlite3_testshellext_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int nErr = 0; + ShellExtensionLink *pShExtLink; + SQLITE_EXTENSION_INIT2(pApi); + pShExtLink = shext_link(db); + if( pShExtLink && pShExtLink->pShellExtensionAPI->numRegistrars>=1 ){ + ShellExState *psx = pShExtLink->pSXS; + MetaCommand *pmc = (MetaCommand *)&batty; + int rc; + + pShExtApi = & pShExtLink->pShellExtensionAPI->api.named; + pExtHelpers = & pShExtLink->pShellExtensionAPI->pExtHelpers->helpers.named; ++ batty.pShow = pExtHelpers->findMetaCommand("show", psx, &rc); + rc = pShExtApi->registerMetaCommand(psx, sqlite3_testshellext_init,pmc); + if( rc!=0 ) ++nErr; + } + else{ + printf("No ShellExtensionLink pointer or registration API.\n"); + ++nErr; + } + return nErr ? SQLITE_ERROR : SQLITE_OK; +} diff --cc test/shell1.test index 050c9acd33,cbbadc58c9..bf55a72554 --- a/test/shell1.test +++ b/test/shell1.test @@@ -182,23 -182,21 +182,25 @@@ do_test shell1-1.16.1 # check first token handling do_test shell1-2.1.1 { catchcmd "test.db" ".foo" - } {1 {Error: unknown command: "foo"}} -} {1 {Error: unknown command or invalid arguments: "foo". Enter ".help" for help}} ++} {1 {Error: unknown dot command: ".foo"}} do_test shell1-2.1.2 { catchcmd "test.db" ".\"foo OFF\"" - } {1 {Error: unknown command: "foo OFF"}} -} {1 {Error: unknown command or invalid arguments: "foo OFF". Enter ".help" for help}} ++} {1 {Error: unknown dot command: ".foo OFF"}} do_test shell1-2.1.3 { catchcmd "test.db" ".\'foo OFF\'" - } {1 {Error: unknown command: "foo OFF"}} -} {1 {Error: unknown command or invalid arguments: "foo OFF". Enter ".help" for help}} ++} {1 {Error: unknown dot command: ".foo OFF"}} + - set modeShouldBe "Error: Mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl" ++set modeShouldBe {Error: Mode should be one of: ++ ascii box column csv html insert json line ++ list markdown qbox quote table tabs tcl} # unbalanced quotes do_test shell1-2.2.1 { catchcmd "test.db" ".\"foo OFF" - } {1 {Error: unknown command: "foo OFF"}} -} {1 {Error: unknown command or invalid arguments: "foo OFF". Enter ".help" for help}} ++} {1 {Error: unknown dot command: ".foo OFF"}} do_test shell1-2.2.2 { catchcmd "test.db" ".\'foo OFF" - } {1 {Error: unknown command: "foo OFF"}} -} {1 {Error: unknown command or invalid arguments: "foo OFF". Enter ".help" for help}} ++} {1 {Error: unknown dot command: ".foo OFF"}} do_test shell1-2.2.3 { catchcmd "test.db" ".explain \"OFF" } {0 {}} @@@ -254,23 -252,23 +256,23 @@@ do_test shell1-2.4.2 # .backup ?DB? FILE Backup DB (default "main") to FILE do_test shell1-3.1.1 { catchcmd "test.db" ".backup" - } {1 {Error: invalid arguments for ".backup"}} -} {1 {missing FILENAME argument on .backup}} ++} {1 {Error: Too few arguments for ".backup", need 1}} forcedelete FOO do_test shell1-3.1.2 { catchcmd "test.db" ".backup FOO" } {0 {}} do_test shell1-3.1.3 { catchcmd "test.db" ".backup FOO BAR" --} {1 {Error: unknown database FOO}} ++} {1 {Error: backup failed, unknown database FOO}} do_test shell1-3.1.4 { # too many arguments catchcmd "test.db" ".backup FOO BAR BAD" - } {1 {Error: invalid arguments for ".backup"}} -} {1 {Usage: .backup ?DB? ?OPTIONS? FILENAME}} ++} {1 {Error: Excess arguments provided; try .help backup}} # .bail ON|OFF Stop after hitting an error. Default OFF do_test shell1-3.2.1 { catchcmd "test.db" ".bail" - } {1 {Error: invalid arguments for ".bail"}} -} {1 {Usage: .bail on|off}} ++} {1 {Error: Too few arguments for ".bail", need 1}} do_test shell1-3.2.2 { catchcmd "test.db" ".bail ON" } {0 {}} @@@ -280,7 -278,7 +282,7 @@@ do_test shell1-3.2.3 do_test shell1-3.2.4 { # too many arguments catchcmd "test.db" ".bail OFF BAD" - } {1 {Error: invalid arguments for ".bail"}} -} {1 {Usage: .bail on|off}} ++} {1 {Error: Too many arguments for ".bail", over 1}} ifcapable vtab { # .databases List names and files of attached databases @@@ -315,7 -313,7 +317,7 @@@ do_test shell1-3.4.2 # .echo ON|OFF Turn command echo on or off do_test shell1-3.5.1 { catchcmd "test.db" ".echo" - } {1 {Error: invalid arguments for ".echo"}} -} {1 {Usage: .echo on|off}} ++} {1 {Error: Too few arguments for ".echo", need 1}} do_test shell1-3.5.2 { catchcmd "test.db" ".echo ON" } {0 {}} @@@ -325,7 -323,7 +327,7 @@@ do_test shell1-3.5.3 do_test shell1-3.5.4 { # too many arguments catchcmd "test.db" ".echo OFF BAD" - } {1 {Error: invalid arguments for ".echo"}} -} {1 {Usage: .echo on|off}} ++} {1 {Error: Too many arguments for ".echo", over 1}} # .exit Exit this program do_test shell1-3.6.1 { @@@ -344,14 -342,15 +346,14 @@@ do_test shell1-3.7.3 catchcmd "test.db" ".explain OFF" } {0 {}} do_test shell1-3.7.4 { - # extra arguments ignored + # extra arguments no longer ignored catchcmd "test.db" ".explain OFF BAD" - } {1 {Error: invalid arguments for ".explain"}} -} {0 {}} - ++} {1 {Error: Too many arguments for ".explain", over 1}} # .header(s) ON|OFF Turn display of headers on or off do_test shell1-3.9.1 { catchcmd "test.db" ".header" - } {1 {Error: invalid arguments for ".header"}} -} {1 {Usage: .headers on|off}} ++} {1 {Error: Too few arguments for ".header", need 1}} do_test shell1-3.9.2 { catchcmd "test.db" ".header ON" } {0 {}} @@@ -361,21 -360,21 +363,11 @@@ do_test shell1-3.9.3 do_test shell1-3.9.4 { # too many arguments catchcmd "test.db" ".header OFF BAD" - } {1 {Error: invalid arguments for ".header"}} -} {1 {Usage: .headers on|off}} -- ++} {1 {Error: Too many arguments for ".header", over 1}} ++# .headers is accepted alias do_test shell1-3.9.5 { -- catchcmd "test.db" ".headers" - } {1 {Error: invalid arguments for ".headers"}} -} {1 {Usage: .headers on|off}} --do_test shell1-3.9.6 { catchcmd "test.db" ".headers ON" } {0 {}} --do_test shell1-3.9.7 { -- catchcmd "test.db" ".headers OFF" --} {0 {}} --do_test shell1-3.9.8 { -- # too many arguments -- catchcmd "test.db" ".headers OFF BAD" - } {1 {Error: invalid arguments for ".headers"}} -} {1 {Usage: .headers on|off}} # .help Show this message do_test shell1-3.10.1 { @@@ -397,14 -396,14 +389,14 @@@ do_test shell1-3.10.2 # .import FILE TABLE Import data from FILE into TABLE do_test shell1-3.11.1 { catchcmd "test.db" ".import" - } {1 {Error: invalid arguments for ".import"}} -} {/1 .ERROR: missing FILE argument.*/} ++} {1 {Error: Too few arguments for ".import", need 2}} do_test shell1-3.11.2 { catchcmd "test.db" ".import FOO" - } {1 {Error: invalid arguments for ".import"}} -} {/1 .ERROR: missing TABLE argument.*/} ++} {1 {Error: Too few arguments for ".import", need 2}} do_test shell1-3.11.3 { # too many arguments catchcmd "test.db" ".import FOO BAR BAD" - } {1 {Error: invalid arguments for ".import"}} -} {/1 .ERROR: extra argument: "BAD".*./} ++} {1 {Error: Excess arguments provided; try .help import}} # .indexes ?TABLE? Show names of all indexes # If TABLE specified, only show indexes for tables @@@ -421,7 -420,7 +413,7 @@@ do_test shell1-3.12.2-legacy do_test shell1-3.12.3 { # too many arguments catchcmd "test.db" ".indexes FOO BAD" - } {1 {Error: invalid arguments for ".indexes"}} -} {1 {Usage: .indexes ?LIKE-PATTERN?}} ++} {1 {Error: Too many arguments for ".indexes", over 1}} # .mode MODE ?TABLE? Set output mode where MODE is one of: # ascii Columns/rows delimited by 0x1F and 0x1E @@@ -476,14 -470,14 +468,14 @@@ do_test shell1-3.13.12 # .nullvalue STRING Print STRING in place of NULL values do_test shell1-3.14.1 { catchcmd "test.db" ".nullvalue" - } {1 {Error: invalid arguments for ".nullvalue"}} -} {1 {Usage: .nullvalue STRING}} ++} {1 {Error: Too few arguments for ".nullvalue", need 1}} do_test shell1-3.14.2 { catchcmd "test.db" ".nullvalue FOO" } {0 {}} do_test shell1-3.14.3 { # too many arguments catchcmd "test.db" ".nullvalue FOO BAD" - } {1 {Error: invalid arguments for ".nullvalue"}} -} {1 {Usage: .nullvalue STRING}} ++} {1 {Error: Too many arguments for ".nullvalue", over 1}} # .output FILENAME Send output to FILENAME do_test shell1-3.15.1 { @@@ -495,7 -489,14 +487,7 @@@ do_test shell1-3.15.2 do_test shell1-3.15.3 { # too many arguments catchcmd "test.db" ".output FOO BAD" - } {1 {Error: invalid arguments for ".output"}} -} {1 {ERROR: extra parameter: "BAD". Usage: -.output ?FILE? Send output to FILE or stdout if FILE is omitted - If FILE begins with '|' then open it as a pipe. - Options: - --bom Prefix output with a UTF8 byte-order mark - -e Send output to the system text editor - -x Send output as CSV to a spreadsheet -child process exited abnormally}} ++} {1 {Error: Excess arguments provided; try .help output}} # .output stdout Send output to the screen do_test shell1-3.16.1 { @@@ -504,7 -505,14 +496,7 @@@ do_test shell1-3.16.2 { # too many arguments catchcmd "test.db" ".output stdout BAD" - } {1 {Error: invalid arguments for ".output"}} -} {1 {ERROR: extra parameter: "BAD". Usage: -.output ?FILE? Send output to FILE or stdout if FILE is omitted - If FILE begins with '|' then open it as a pipe. - Options: - --bom Prefix output with a UTF8 byte-order mark - -e Send output to the system text editor - -x Send output as CSV to a spreadsheet -child process exited abnormally}} ++} {1 {Error: Excess arguments provided; try .help output}} # .prompt MAIN CONTINUE Replace the standard prompts do_test shell1-3.17.1 { @@@ -519,7 -527,7 +511,7 @@@ do_test shell1-3.17.3 do_test shell1-3.17.4 { # too many arguments catchcmd "test.db" ".prompt FOO BAR BAD" - } {1 {Error: invalid arguments for ".prompt"}} -} {0 {}} ++} {1 {Error: Too many arguments for ".prompt", over 2}} # .quit Exit this program do_test shell1-3.18.1 { @@@ -533,7 -541,7 +525,7 @@@ do_test shell1-3.18.2 # .read FILENAME Execute SQL in FILENAME do_test shell1-3.19.1 { catchcmd "test.db" ".read" - } {1 {Error: invalid arguments for ".read"}} -} {1 {Usage: .read FILE}} ++} {1 {Error: Too few arguments for ".read", need 1}} do_test shell1-3.19.2 { forcedelete FOO catchcmd "test.db" ".read FOO" @@@ -541,12 -549,12 +533,12 @@@ do_test shell1-3.19.3 { # too many arguments catchcmd "test.db" ".read FOO BAD" - } {1 {Error: invalid arguments for ".read"}} -} {1 {Usage: .read FILE}} ++} {1 {Error: Too many arguments for ".read", over 1}} # .restore ?DB? FILE Restore content of DB (default "main") from FILE do_test shell1-3.20.1 { catchcmd "test.db" ".restore" - } {1 {Error: invalid arguments for ".restore"}} -} {1 {Usage: .restore ?DB? FILE}} ++} {1 {Error: Too few arguments for ".restore", need 1}} do_test shell1-3.20.2 { catchcmd "test.db" ".restore FOO" } {0 {}} @@@ -556,7 -564,7 +548,7 @@@ do_test shell1-3.20.3 do_test shell1-3.20.4 { # too many arguments catchcmd "test.db" ".restore FOO BAR BAD" - } {1 {Error: invalid arguments for ".restore"}} -} {1 {Usage: .restore ?DB? FILE}} ++} {1 {Error: Too many arguments for ".restore", over 2}} ifcapable vtab { # .schema ?TABLE? Show the CREATE statements @@@ -571,7 -579,7 +563,7 @@@ do_test shell1-3.21.2 do_test shell1-3.21.3 { # too many arguments catchcmd "test.db" ".schema FOO BAD" - } {1 {Error: invalid arguments for ".schema"}} -} {1 {Usage: .schema ?--indent? ?--nosys? ?LIKE-PATTERN?}} ++} {1 {Error: Too many arguments for ".schema", over 1}} do_test shell1-3.21.4 { catchcmd "test.db" { @@@ -592,7 -600,7 +584,7 @@@ CREATE VIEW v1 AS SELECT y+1 FROM v # .separator STRING Change column separator used by output and .import do_test shell1-3.22.1 { catchcmd "test.db" ".separator" - } {1 {Error: invalid arguments for ".separator"}} -} {1 {Usage: .separator COL ?ROW?}} ++} {1 {Error: Too few arguments for ".separator", need 1}} do_test shell1-3.22.2 { catchcmd "test.db" ".separator FOO" } {0 {}} @@@ -602,7 -610,7 +594,7 @@@ do_test shell1-3.22.3 do_test shell1-3.22.4 { # too many arguments catchcmd "test.db" ".separator FOO BAD BAD2" - } {1 {Error: invalid arguments for ".separator"}} -} {1 {Usage: .separator COL ?ROW?}} ++} {1 {Error: Too many arguments for ".separator", over 2}} # .show Show the current values for various settings do_test shell1-3.23.1 { @@@ -621,7 -629,7 +613,7 @@@ do_test shell1-3.23.2 { # too many arguments catchcmd "test.db" ".show BAD" - } {1 {Error: invalid arguments for ".show"}} -} {1 {Usage: .show}} ++} {1 {Error: Too many arguments for ".show", over 0}} # .stats ON|OFF Turn stats on or off #do_test shell1-3.23b.1 { @@@ -679,7 -687,7 +671,7 @@@ do_test shell1-3.25.3 do_test shell1-3.25.4 { # too many arguments catchcmd "test.db" ".timeout 1 BAD" - } {1 {Error: invalid arguments for ".timeout"}} -} {0 {}} ++} {1 {Error: Too many arguments for ".timeout", over 1}} # .width NUM NUM ... Set column widths for "column" mode do_test shell1-3.26.1 { @@@ -710,7 -718,7 +702,7 @@@ do_test shell1-3.26.6 # .timer ON|OFF Turn the CPU timer measurement on or off do_test shell1-3.27.1 { catchcmd "test.db" ".timer" - } {1 {Error: invalid arguments for ".timer"}} -} {1 {Usage: .timer on|off}} ++} {1 {Error: Too few arguments for ".timer", need 1}} do_test shell1-3.27.2 { catchcmd "test.db" ".timer ON" } {0 {}} @@@ -720,7 -728,7 +712,7 @@@ do_test shell1-3.27.3 do_test shell1-3.27.4 { # too many arguments catchcmd "test.db" ".timer OFF BAD" - } {1 {Error: invalid arguments for ".timer"}} -} {1 {Usage: .timer on|off}} ++} {1 {Error: Too many arguments for ".timer", over 1}} do_test shell1-3-28.1 { catchcmd test.db \ diff --cc test/shell2.test index 6f29df3d82,29712c7f07..1db38c4c5b --- a/test/shell2.test +++ b/test/shell2.test @@@ -186,4 -188,13 +186,11 @@@ 2 }} + # Test for rejection of incomplete input at EOF. + # Reported at https://sqlite.org/forum/forumpost/718f489a43be3197 + do_test shell2-1.4.7 { + catchcmd ":memory:" { + SELECT 'unclosed;} -} {1 {Parse error near line 2: unrecognized token: "'unclosed;" - SELECT 'unclosed; - ^--- error here}} ++} {1 {Error: Input incomplete at line 2 of ""}} + finish_test diff --cc test/shell4.test index a7491148ef,ee7d2b856f..ae4918fa40 --- a/test/shell4.test +++ b/test/shell4.test @@@ -110,7 -110,7 +110,7 @@@ SELECT 1 ifcapable trace { do_test shell4-2.1 { catchcmd ":memory:" "CREATE TABLE t1(x);\n.trace --unknown" --} {1 {Unknown option "--unknown" on ".trace"}} ++} {1 {Error: On .trace, unknown option or subcommand: "--unknown"}} do_test shell4-2.2 { catchcmd ":memory:" "CREATE TABLE t1(x);\n.trace off\n.trace off\n" } {0 {}} @@@ -144,8 -144,6 +144,8 @@@ do_test shell4-4.1 puts $fd ".read t1.txt" close $fd catchcmd ":memory:" ".read t1.txt" -} {1 {Input nesting limit (25) reached at line 1. Check recursion.}} +} {1 {Input nesting limit (25) reached, from line 1 of "t1.txt" + from line 1 of "t1.txt" from line 1 of "t1.txt" ... - ERROR: Check recursion.}} ++Error: Check recursion.}} finish_test diff --cc test/shell5.test index db0ead997f,d63a0a20d3..97835d41ed --- a/test/shell5.test +++ b/test/shell5.test @@@ -32,29 -32,29 +32,22 @@@ forcedelete test.db test.db-journal tes # .import FILE TABLE Import data from FILE into TABLE do_test shell5-1.1.1 { catchcmd "test.db" ".import" - } {1 {Error: invalid arguments for ".import"}} -} {/1 .ERROR: missing FILE argument.*/} ++} {1 {Error: Too few arguments for ".import", need 2}} do_test shell5-1.1.2 { catchcmd "test.db" ".import FOO" - } {1 {Error: invalid arguments for ".import"}} -} {/1 .ERROR: missing TABLE argument.*/} ++} {1 {Error: Too few arguments for ".import", need 2}} do_test shell5-1.1.3 { # too many arguments catchcmd "test.db" ".import FOO BAR BAD" - } {1 {Error: invalid arguments for ".import"}} -} {/1 .ERROR: extra argument.*/} ++} {1 {Error: Excess arguments provided; try .help import}} # .separator STRING Change separator used by output mode and .import do_test shell5-1.2.1 { -- catchcmd "test.db" ".separator" - } {1 {Error: invalid arguments for ".separator"}} -} {1 {Usage: .separator COL ?ROW?}} --do_test shell5-1.2.2 { catchcmd "test.db" ".separator ONE" } {0 {}} --do_test shell5-1.2.3 { ++do_test shell5-1.2.2 { catchcmd "test.db" ".separator ONE TWO" } {0 {}} --do_test shell5-1.2.4 { -- # too many arguments -- catchcmd "test.db" ".separator ONE TWO THREE" - } {1 {Error: invalid arguments for ".separator"}} -} {1 {Usage: .separator COL ?ROW?}} # column separator should default to "|" do_test shell5-1.3.1.1 { diff --cc test/shell9.test index bbbe181407,0000000000..199c25cfb7 mode 100644,000000..100644 --- a/test/shell9.test +++ b/test/shell9.test @@@ -1,261 -1,0 +1,257 @@@ +# 2022 Feb 5 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# The focus of this file is testing the CLI shell tool enhanced parsing, +# new .parameter subcommands and uses, and the new .x meta-command. +# +# + +# Test plan: +# +# shell9-1.*: command line parsing and acting accordingly +# shell9-2.*: Basic "dot" command, cross-line token parsing +# shell9-3.*: .parameter set options and types +# shell9-4.*: .parameter save/load operation +# shell9-5.*: Ensure "dot" commands and SQL intermix ok. +# shell9-6.*: .x command operation and refusal +# +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set CLI [test_find_cli] +db close +forcedelete test.db test.db-journal test.db-wal +forcedelete x.db xn.db + +sqlite3 db test.db + +#---------------------------------------------------------------------------- +# Test cases shell9-1.*: command line parsing and acting accordingly + +do_test shell9-1.1 { + set res [catchcmd ":memory: -cmd .quit" ""] +} {0 {}} + +do_test shell9-1.2 { + set res [catchcmd ":memory: -shxopts 1 -cmd .shxopts -cmd .quit" ""] +} {0 { name value "-shxopts set" + -------- ----- --------------- + parsing 1 "-shxopts 0x01" + all_opts 0 "-shxopts 0x07"}} + +do_test shell9-1.3 { + set res [catchcmd ":memory: -cmd .shxopts -cmd .quit" ""] +} {0 { name value "-shxopts set" + -------- ----- --------------- + parsing 0 "-shxopts 0x01" + all_opts 0 "-shxopts 0x07"}} + +#---------------------------------------------------------------------------- +# Test cases shell9-2.*: Basic "dot" command, cross-line token parsing + +set cmds ".print 'l1\nl2'\n.print 'a\\\nb'" +do_test shell9-2.1 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {l1 +l2 +ab}} + +set cmds " .print \"l1\nl2\"\n .print \"a\\\nb\" \n# c\n ## c" +do_test shell9-2.2 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {l1 +l2 +ab}} + +set cmds ".echo on\n.seeargs 'a'\\\n'b'\n#!" +do_test shell9-2.3 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {.seeargs 'a''b' +a|b|}} + +set cmds ".echo on\n.seeargs a\\\nb\n#!" +do_test shell9-2.4 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {.seeargs ab +ab|}} + +set cmds ".echo 1\n.print \"\\\"\nq\\\"\"" +do_test shell9-2.5 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {.print "\" +q\"" +" +q"}} + +#---------------------------------------------------------------------------- +# Test cases shell9-3.*: .parameter set options and types + +set cmds { +.pa set -b b x'a5a5' +.pa set -i ii 33-11 +.pa set -i ir 3.3-1.1 +.pa set -n ni 3-1 +.pa set -n nr 3.3-1.1 +.pa set -r ri 1 +.pa set -r rr 1.2 +.pa set -t t 123 +.mode list +select typeof(value) from temp.sqlite_parameters order by key; +} +do_test shell9-3.1 { + set res [catchcmd ":memory:" $cmds] +} {0 {blob +integer +integer +integer +real +real +real +text}} + +set cmds { +.pa set expr 1 + 2 * 3 +.pa set text "'1 + 2*3'" +.pa set -t ttext 1 + 2*3 +.pa list +} +do_test shell9-3.2 { + set res [catchcmd ":memory:" $cmds] +} {0 {Scripts +name value +expr 7 +text 1 + 2*3 +ttext 1 + 2*3}} + +set cmds { +.pa set a "'a'" +.pa set b "'b'" +.pa set c "'c'" +.pa unset +.pa list +.pa clear a +.pa list +.pa unset b +.pa list +.pa clear +.pa list +.pa set d "'e'" +.pa set e "'e'" +.pa unset d e +.pa list +} +do_test shell9-3.3 { + set res [catchcmd ":memory:" $cmds] +} {0 {Scripts +name value +a a +b b +c c +Scripts +name value +b b +c c +Scripts +name value +c c}} + +if {$::tcl_platform(platform)=="unix"} { + proc set_ed {sayWhat} { + global env + set env(VISUAL) "echo SELECT $sayWhat ';' >" + return 1 + } +} elseif {$::tcl_platform(platform)=="windows"} { + proc set_ed {sayWhat} { + global env + set env(VISUAL) "echo SELECT $sayWhat ; >" + return 1 + } +} else { return 0 } + +if {[set_ed @name]} { + set cmds { +.pa set @name Fido +.pa edit -t dog +.x dog + } + do_test shell9-3.4 { + set res [catchcmd ":memory: -quiet 1 -shxopts 1 -interactive" $cmds] - } {0 {.pa set @name Fido - .pa edit -t dog - .x dog - Fido - }} ++ } {0 {Fido ++}} +} + +#---------------------------------------------------------------------------- +# Test cases shell9-4.*: .parameter save/load operation + +set cmds { + .pa set -t x '.print Ex' + .pa set -i $n 7 + .pa save xn.db + .pa save x.db x + .pa clear + .pa load xn.db + .pa list + .pa clear + .pa load x.db + .pa list + .pa clear + .pa load xn.db $n + .pa list +} +do_test shell9-4.1 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {Binding Values: +name value +$n 7 +Scripts +name value +x .print Ex +Scripts +name value +x .print Ex +Binding Values: +name value +$n 7}} + +forcedelete x.db xn.db + +#---------------------------------------------------------------------------- +# Test cases shell9-5.*: Ensure "dot" commands and SQL intermix ok. + +set cmds { + .pa set -t mixed " + .print Hi. + select 'Hi.'; + .print 'Good\ + Bye.' + select 'Good'|| + ' Bye.'; + " + .x mixed +} +do_test shell9-5.1 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {Hi. +Hi. +Good Bye. +Good Bye.}} + +#---------------------------------------------------------------------------- +# Test cases shell9-6.*: .x command operation and refusal +set cmds { + .pa set -t $v '.print Ok' + .x $v +} +do_test shell9-6.1 { + set res [catchcmd ":memory: -bail -shxopts 1" $cmds] - } {1 {Skipping badly named $v. Run ".help x" - Abnormal exit (0)}} ++} {1 {Skipping badly named $v. Run ".help x"}} + +finish_test