From: larrybr Date: Fri, 25 Feb 2022 00:09:07 +0000 (+0000) Subject: Merge shell-tweaks enhancements (with 3.38) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=19d72321fbaedab3a922e8c21ab6d540c602943f;p=thirdparty%2Fsqlite.git Merge shell-tweaks enhancements (with 3.38) FossilOrigin-Name: 4c7d94d3f339d5de98ab5488e75b5dfea2c11fadb2cd44a9f6bc4e0769dbd34a --- 19d72321fbaedab3a922e8c21ab6d540c602943f diff --cc manifest index da4d88a4cb,e5f4607332..19d125b550 --- a/manifest +++ b/manifest @@@ -1,5 -1,5 +1,5 @@@ - C Regularize\sCLI\s.mode\sprocessing - D 2022-02-24T04:29:09.586 -C Sync\sw/3.38,\sadd\s.parameter\sls\sto\sCLI -D 2022-02-22T22:28:37.952 ++C Merge\sshell-tweaks\senhancements\s(with\s3.38) ++D 2022-02-25T00:09:07.697 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@@ -555,8 -553,7 +555,8 @@@ F src/random.c 097dc8b31b8fba5a9aca1697 F src/resolve.c ea935b87d6fb36c78b70cdc7b28561dc8f33f2ef37048389549c7b5ef9b0ba5e F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/select.c 3baa9dd8cf240654773c7974e2bcce398ac9dd24419c36684156963defe43b35 - F src/shell.c.in 0b1269056aca0e71680a65dac98bd731597f97334ab7a06509eba49f4d6e5093 -F src/shell.c.in 5a8a5350b7284f185aed21c140e6c81a33b54873f89a3568600cf6a37dd4acac ++F src/shell.c.in 0da11ed800103e60c0cea85538efb025bebdba25c1e8516aa20ee67237e16be6 +F src/shext_linkage.h 5897e8140a06cb733d07a927994b30d41225eb93b5b8cf2ad0e3460cb4e95fd6 F src/sqlite.h.in e30cedf008d9c51511f4027a3739b727a588da553424748b48d2393f85dbde41 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h a95cb9ed106e3d39e2118e4dcc15a14faec3fa50d0093425083d340d9dfd96e6 @@@ -1391,14 -1388,15 +1391,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 e05b84687a531419165dd79aefcfd149e4da24532480ec93439ddfa13efdf65b -F test/shell1.test b224e0793c5f48aa3749e65d8c64b93a30731bd206f2e41e6c5f1bee1bdb16c6 -F test/shell2.test 89e4b2db062d52baed75022227b462d085cff495809de1699652779d8e0257d6 ++F test/shell1.test 2a8d73538f672c5b5b238ba2fa15e4fbaea47454f372dd5b683be51727af33cc +F test/shell2.test aed8773dd236517149a6abaeccf94f066e5a0c871108e8d21889122132dd51ba - F test/shell3.test a50628ab1d78d90889d9d3f32fb2c084ee15674771e96afe954aaa0accd1de3c - F test/shell4.test 8f6c0fce4abed19a8a7f7262517149812a04caa905d01bdc8f5e92573504b759 + F test/shell3.test 4ddea2bd182e7e03249911b23ae249e7cb8a91cdc86e695198725affabe8ecd3 + F test/shell4.test 867e0675d7b096d6b93de534541e07c7f5ffafa3e6612695ecf55180802e1115 -F test/shell5.test 2b521446f55146c9aafccd0946bdb44ae288b0d25bd48f722e041974fdeeb04a +F test/shell5.test 839a4489bc97d6f2028ffd036d500d2b8cab1657ff477bbcd635184f8e8b3887 F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3 F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f F test/shell8.test 388471d16e4de767333107e30653983f186232c0e863f4490bb230419e830aae -F test/shell9.test 03e28206c016b27b3c79f43c3b09c843468dd8961f980e60d37117ff1ce32908 ++F test/shell9.test 9c6512384f4dcdaddd7c0efd0b1bd54cdac3c46dd82d2b30f877d90e666d7a2c F test/shmlock.test 3dbf017d34ab0c60abe6a44e447d3552154bd0c87b41eaf5ceacd408dd13fda5 F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5 @@@ -1880,8 -1878,8 +1881,8 @@@ F tool/mkmsvcmin.tcl 6ecab9fe22c2c8de4d F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61a07ef F tool/mkopcodeh.tcl 130b88697da6ec5b89b41844d955d08fb62c2552e889dec8c7bcecb28d8f50bd F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa -F tool/mkpragmatab.tcl de206c64b6e9ac8cd5e3cbd0ffe456f07d5710605ef8385d677e60ce3335ea12 -F tool/mkshellc.tcl 5a9e27449694b179d2facde3fa10ff5a2d689be25822526addc20a1f1d9a12ec +F tool/mkpragmatab.tcl bd07bd59d45d0f3448e123d6937e9811195f9908a51e09d774609883055bfd3d - F tool/mkshellc.tcl 160d16067f71caeb54cbebd09c12c3e545c77f46d9714cc8e7219407b3d3001b ++F tool/mkshellc.tcl 754c73032cd7d279ce42ddeaaa49d1ece71adf96293e10e3b0670c0d6b0f8972 F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f @@@ -1947,8 -1945,8 +1948,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 b9b27d74b93ada32ece46c9e53652f766de97f5a77fe4c34251996697374e659 - R 3b1535aa20e62ad155a398c2745a0c17 -P 2b4a295c58a908288840157c3e08289af8c1d287f72e5f14b7adfd98065bf241 40fa792d359f84c3b9e9d6623743e1a59826274e221df1bde8f47086968a1bab -R 5520524d725423f68aaec6a9faa61a16 ++P 6d0557244f69e78474f9222aae95c4c47925a466aa15e5666d0ada237538e262 8c9a5fb26ba045edef1269c5f5e8c8d87fa890b88ddb1121be72514a389a845d ++R 791b63806b337836957b5b12020a8987 U larrybr - Z 1bf768e26b209b9a9f7fc853e590446e -Z 1b4999d38bc60cdea13bcadad5fd8a8e ++Z 6cdde194291306233e0d54419a5b14ca # Remove this line to create a well-formed Fossil manifest. diff --cc manifest.uuid index d3eefb1647,f2c7191aa1..632da48fb7 --- a/manifest.uuid +++ b/manifest.uuid @@@ -1,1 -1,1 +1,1 @@@ - 6d0557244f69e78474f9222aae95c4c47925a466aa15e5666d0ada237538e262 -8c9a5fb26ba045edef1269c5f5e8c8d87fa890b88ddb1121be72514a389a845d ++4c7d94d3f339d5de98ab5488e75b5dfea2c11fadb2cd44a9f6bc4e0769dbd34a diff --cc src/shell.c.in index 92a773bd53,543e76149f..d227a9bd49 --- a/src/shell.c.in +++ b/src/shell.c.in @@@ -661,26 -629,81 +662,80 @@@ static FILE * openChrSource(const char } /* - ** This routine reads a line of text from FILE in, stores it in - ** memory obtained from malloc() and returns a pointer to it. - ** NULL is returned at end of file, or if malloc() fails. + ** Arrange for shell input from either a FILE or a string. + ** For applicable invariants, see str_line_get(...) which is + ** the only modifier of this struct. (3rd and 4th members) + ** All other members are simply initialized to select the + ** input stream and track input sources, set by whatever + ** routines redirect the input. + */ + typedef struct InSource { + FILE *inFile; /* Will be 0 when input is to be taken from string. */ + char *zStrIn; /* Will be 0 when no input is available from string. */ + int iReadOffset; /* Offset into zStrIn where next "read" to be done. */ + int lineno; /* How many lines have been read from this source */ + const char *zSourceSay; /* For complaints, keep a name for this source */ + struct InSource *pFrom; /* and redirect tracking to aid unraveling. */ + } InSource; + #define INSOURCE_STR_REDIR(str, tagTo, isFrom) {0, str, 0, 0, tagTo, isFrom} + #define INSOURCE_FILE_REDIR(fh, tagTo, isFrom) {fh, 0, 0, 0, tagTo, isFrom} + #define INSOURCE_IS_INTERACTIVE(pIS) \ + ((pIS)==&termInSource && stdin_is_interactive ) + + /* This instance's address is taken as part of interactive input test. */ + static InSource termInSource = { 0 /*stdin*/, 0, 0, 0, "", 0}; + static InSource stdInSource = { 0 /*stdin*/, 0, 0, 0, "", 0}; + static void init_std_inputs( FILE *pIn ){ + termInSource.inFile = pIn; + stdInSource.inFile = pIn; + } + + static char *str_line_get( char *zBuf, int ncMax, InSource *pInSrc){ + if( pInSrc->inFile!=0 ){ + char *zRet = fgets(zBuf, ncMax, pInSrc->inFile ); + if( zRet!=0 ){ + int iRead = strlen30(zRet); + if( iRead>0 && zRet[iRead-1]=='\n' ) ++pInSrc->lineno; + /* Consider: record line length to avoid rescan for it. */ + return zRet; + } + } + else if( pInSrc->zStrIn!=0 ){ + char *zBegin = pInSrc->zStrIn + pInSrc->iReadOffset; + if( *zBegin!=0 ){ + int iTake = 0; + char c; + ncMax -= 1; + while( iTakelineno; + break; + } + } + if( ncMax>=0 ) zBuf[iTake] = 0; + pInSrc->iReadOffset += iTake; + /* Consider: record line length to avoid rescan for it. */ + return zBuf; + } + } + return 0; + } + + /* + ** This routine reads a line of text from designated stream source, + ** stores the text in memory obtained from malloc() and returns a + ** pointer to the text. NULL is returned at end of file. + ** There will be no return if malloc() fails. ** - ** If zLine is not NULL then it is (and must be) a malloced buffer - ** returned from a previous call to this routine that may be reused. + ** The trailing newline (or other line-end chars) are stripped. + ** + ** If zLine is not NULL then it is a malloced buffer returned from + ** a previous call to this routine that may be reused. */ - static char *local_getline(char *zLine, FILE *in){ - #ifdef SQLITE_DEBUG - static char *zLinePrior = 0; - /* If zLine is passed in, not for the first time, it *must* be prior return, - ** otherwise the assumption made here as to its allocated size is shaky. */ - assert(zLine==0 || zLinePrior==0 || zLine==zLinePrior); - # define ZLINE_PRIOR(z) zLinePrior = z - #else - # define ZLINE_PRIOR(z) - #endif - int nLine = zLine==0 ? 0 : 100; /* Length now (or to be)allocated */ - int n = 0; /* Count of chars accumulated now */ - + static char *local_getline(char *zLine, InSource *pInSrc){ + int nLine = zLine==0 ? 0 : 100; + int n = 0; - while( 1 ){ if( n+100>nLine ){ nLine = nLine*2 + 100; @@@ -730,13 -745,13 +777,12 @@@ return zLine; } - /* - ** Retrieve a single line of input text. + ** Retrieve a single line of input text from designated input source. ** - ** If in==0 then read from standard input and prompt before each line. - ** If isContinuation is true, then a continuation prompt is appropriate. - ** If isContinuation is zero, then the main prompt should be used. + ** If the input is interactive, then issue either the continuation prompt + ** or the main prompt, per isContinuation, before reading a line from + ** standard input. Otherwise, just read a line from the specified source. ** ** If zPrior is not NULL then it is a buffer from a prior call to this ** routine that can be reused. @@@ -744,27 -759,36 +790,35 @@@ ** The result is stored in space obtained from malloc() and must either ** be freed by the caller or else passed back into this routine via the ** zPrior argument for reuse. + ** + ** If this function is called until it returns NULL, and the prior return + ** has been passed in for resuse, then the caller need/must not free it. + ** Otherwise, (in case of an early termination of reading from the given + ** input), the caller is responsible for freeing a prior, non-NULL return. + ** + ** The trailing newline (or its ilk), if any, is trimmed. + ** The input line number is adjusted (via delegation or directly.) */ - static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ - char *zPrompt; - char *zResult; - if( in!=0 ){ - zResult = local_getline(zPrior, in); + static char *one_input_line(InSource *pInSrc, char *zPrior, int isContinuation){ + if( !INSOURCE_IS_INTERACTIVE(pInSrc) ){ + return local_getline(zPrior, pInSrc); }else{ - zPrompt = isContinuation ? continuePrompt : mainPrompt; + char *zPrompt = isContinuation ? continuePrompt : mainPrompt; #if SHELL_USE_LOCAL_GETLINE - fprintf(STD_OUT, "%s", zPrompt); - fflush(STD_OUT); - zResult = local_getline(zPrior, STD_IN); + printf("%s", zPrompt); + fflush(stdout); + return local_getline(zPrior, pInSrc); #else + char *zResult; free(zPrior); zResult = shell_readline(zPrompt); + ++pInSrc->lineno; if( zResult && *zResult ) shell_add_history(zResult); + return zResult; #endif } - return zResult; } - /* ** Return the value of a hexadecimal digit. Return -1 if the input ** is not a hex digit. @@@ -1118,6 -1143,51 +1172,51 @@@ struct EQPGraph char zPrefix[100]; /* Graph prefix */ }; + /* By default, omit the extension options that are not done yet. */ + #ifndef SHELL_OMIT_EXTENSIONS + # define SHELL_OMIT_EXTENSIONS 6 + #endif + + /* Selectively omit features with one PP variable. Value is true iff + ** either x is not defined or defined with 0 in bitnum bit position. + */ + #define NOT_IFDEF_BIT(x,bitnum) (x? (!(x & (1<bExtendedDotCmds&(1<bExtendedDotCmds&(1< Safe mode is and will remain inactive. +** equal 1 => Safe mode is and will remain active. +** N > 1 => Safe mode is suspended for N-1 operations. +*/ +static void updateSafeMode(ShellState *pSS){ + switch( pSS->bSafeModeFuture ){ + case 2: + default: + --pSS->bSafeModeFuture; + /* Fall thru, suspension is in effect. */ + case 0: + pSS->bSafeMode = 0; + break; + case 1: + pSS->bSafeMode = 1; + } +} /* Allowed values for ShellState.autoEQP */ @@@ -1290,50 -1351,43 +1411,55 @@@ #define MODE_Line 0 /* One column per line. Blank line between records */ #define MODE_Column 1 /* One record per line in neat columns */ #define MODE_List 2 /* One record per line with a separator */ -#define MODE_Semi 3 /* Same as MODE_List but append ";" to each line */ -#define MODE_Html 4 /* Generate an XHTML table */ -#define MODE_Insert 5 /* Generate SQL "insert" statements */ -#define MODE_Quote 6 /* Quote values as for SQL */ -#define MODE_Tcl 7 /* Generate ANSI-C or TCL quoted elements */ -#define MODE_Csv 8 /* Quote strings, numbers are plain */ -#define MODE_Explain 9 /* Like MODE_Column, but do not truncate data */ -#define MODE_Ascii 10 /* Use ASCII unit and record separators (0x1F/0x1E) */ -#define MODE_Pretty 11 /* Pretty-print schemas */ -#define MODE_EQP 12 /* Converts EXPLAIN QUERY PLAN output into a graph */ -#define MODE_Json 13 /* Output JSON */ -#define MODE_Markdown 14 /* Markdown formatting */ -#define MODE_Table 15 /* MySQL-style table formatting */ -#define MODE_Box 16 /* Unicode box-drawing characters */ -#define MODE_Count 17 /* Output only a count of the rows of output */ -#define MODE_Off 18 /* No query output shown */ - -static const char *modeDescr[] = { - "line", - "column", - "list", - "semi", - "html", - "insert", - "quote", - "tcl", - "csv", - "explain", - "ascii", - "prettyprint", - "eqp", - "json", - "markdown", - "table", - "box", - "count", - "off" +#define MODE_Html 3 /* Generate an XHTML table */ +#define MODE_Tcl 4 /* Generate ANSI-C or TCL quoted elements */ +#define MODE_Csv 5 /* Quote strings, numbers are plain */ - #define MODE_Tab 6 /* Transitory */ ++#define MODE_Tab 6 /* Transitory, an alias for MODE_List with tabs */ +#define MODE_Insert 7 /* Generate SQL "insert" statements */ +#define MODE_Quote 8 /* Quote values as for SQL */ +#define MODE_Ascii 9 /* Use ASCII unit and record separators (0x1F/0x1E) */ +#define MODE_Markdown 10 /* Markdown formatting */ +#define MODE_Table 11 /* MySQL-style table formatting */ +#define MODE_Box 12 /* Unicode box-drawing characters */ +#define MODE_Count 13 /* Output only a count of the rows of output */ +#define MODE_Off 14 /* No query output shown */ +#define MODE_Json 15 /* Output JSON */ +#define MODE_EQP 16 /* Converts EXPLAIN QUERY PLAN output into a graph */ +#define MODE_Explain 17 /* Like MODE_Column, but do not truncate data */ +#define MODE_Pretty 18 /* Pretty-print schemas */ +#define MODE_Semi 19 /* Same as MODE_List but append ";" to each line */ - #define MODE_COUNT_OF 20 ++#define MODE_COUNT_OF 20 /* also an invalid MODE_x value */ ++ ++/* Note that some of above ordering is assumed for this mode classification. */ ++#define MODE_IS_COLUMNAR(m) \ ++ ( (m)==MODE_Column||((m)>=MODE_Markdown&&(m)<=MODE_Box) ) ++ +static struct { + const char *zModeName; + u8 bDepluralize; + u8 bUserBlocked; + u8 iAliasFor; +} modeDescr[] = { + { "lines", 1, 0, MODE_Line }, + { "columns", 1, 0, MODE_Column }, + { "list", 0, 0, MODE_List }, + { "html", 0, 0, MODE_Html }, + { "tcl", 0, 0, MODE_Tcl }, + { "csv", 0, 0, MODE_Csv }, + { "tabs", 1, 0, MODE_List }, + { "insert", 0, 0, MODE_Insert }, + { "quote", 0, 0, MODE_Quote }, + { "ascii", 0, 0, MODE_Ascii }, + { "markdown", 0, 0, MODE_Markdown }, + { "table", 0, 0, MODE_Table }, + { "box", 0, 0, MODE_Box }, + { "count", 0, 0, MODE_Count }, + { "off", 0, 0, MODE_Off }, + { "json", 0, 0, MODE_Json }, + { "eqp", 0, 1, MODE_EQP }, + { "explain", 0, 1, MODE_Explain }, + { "prettyprint", 0, 1, MODE_Pretty }, + { "semi", 0, 1, MODE_Semi } }; /* @@@ -1399,12 -1451,10 +1525,12 @@@ static int failIfSafeMode va_start(ap, zErrMsg); zMsg = sqlite3_vmprintf(zErrMsg, ap); va_end(ap); - raw_printf(STD_ERR, "line %d: ", p->lineno); - raw_printf(stderr, "line %d: ", p->pInSource->lineno); - utf8_printf(stderr, "%s\n", zMsg); - exit(1); ++ raw_printf(STD_ERR, "line %d: ", p->pInSource->lineno); + utf8_printf(STD_ERR, "%s\n", zMsg); + p->abruptExit = 3; + return 1; } + return 0; } /* @@@ -3190,24 -3367,22 +3453,20 @@@ static int param_table_exists( sqlite3 ** ** No bindings occur if this table does not exist. The name of the table ** begins with "sqlite_" so that it will not collide with ordinary application - ** tables. The table must be in the TEMP schema. + ** tables. The table must be in the TEMP schema. Only rows with + ** uses=PTU_Binding are eligible for parameter binding. */ - static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){ - int nVar; + static void bind_prepared_stmt(sqlite3 *db, sqlite3_stmt *pStmt){ - int nVar; ++ int nVar = sqlite3_bind_parameter_count(pStmt); int i; int rc; sqlite3_stmt *pQ = 0; -- nVar = sqlite3_bind_parameter_count(pStmt); - if( nVar==0 ) return; /* Nothing to do */ - if( sqlite3_table_column_metadata(pArg->db, "TEMP", "sqlite_parameters", - "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){ - return; /* Parameter table does not exist */ - } - rc = sqlite3_prepare_v2(pArg->db, + if( nVar==0 || !param_table_exists(db) ) return; /* Nothing to do */ + rc = sqlite3_prepare_v2(db, - "SELECT value FROM "PARAM_TABLE_SNAME - " WHERE key=?1 AND uses=0", -1, &pQ, 0); + "SELECT value FROM temp.sqlite_parameters" + " WHERE key=?1", -1, &pQ, 0); if( rc || pQ==0 ) return; - assert( PTU_Binding==0 ); /* instead of working symbol value into query */ for(i=1; i<=nVar; i++){ char zNum[30]; const char *zVar = sqlite3_bind_parameter_name(pStmt, i); @@@ -3421,8 -3596,8 +3680,8 @@@ static char *quoted_column(sqlite3_stm /* ** Run a prepared statement and output the result in one of the --** table-oriented formats: MODE_Column, MODE_Markdown, MODE_Table, --** or MODE_Box. ++** table-oriented formats, for which MODE_IS_COLUMNAR(m) is true: ++** MODE_Column, MODE_Markdown, MODE_Table, or MODE_Box. ** ** This is different from ordinary exec_prepared_stmt() in that ** it has to run the entire query and gather the results into memory @@@ -3654,11 -3829,11 +3913,7 @@@ static void exec_prepared_stmt int rc; sqlite3_uint64 nRow = 0; -- if( pArg->cMode==MODE_Column -- || pArg->cMode==MODE_Table -- || pArg->cMode==MODE_Box -- || pArg->cMode==MODE_Markdown -- ){ ++ if( MODE_IS_COLUMNAR(pArg->cMode) ){ exec_prepared_stmt_columnar(pArg, pStmt); return; } @@@ -3807,12 -3987,6 +4062,12 @@@ static int expertDotCommand int i; int iSample = 0; + if( pState->bSafeMode ){ - raw_printf(stderr, ++ raw_printf(STD_ERR, + "Cannot run experimental commands such as \"%s\" in safe mode\n", + azArg[0]); + return 1; + } assert( pState->expert.pExpert==0 ); memset(&pState->expert, 0, sizeof(ExpertInfo)); @@@ -3845,8 -4019,7 +4100,8 @@@ if( rc==SQLITE_OK ){ pState->expert.pExpert = sqlite3_expert_new(pState->db, &zErr); if( pState->expert.pExpert==0 ){ - raw_printf(stderr, "sqlite3_expert_new: %s\n", - raw_printf(stderr, "sqlite3_expert_new: %s\n", zErr ? zErr : "out of memory"); ++ raw_printf(STD_ERR, "sqlite3_expert_new: %s\n", + zErr ? zErr : "out of memory"); rc = SQLITE_ERROR; }else{ sqlite3_expert_config( @@@ -4313,238 -4492,535 +4573,238 @@@ 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. + */ +DISPATCH_CONFIG[ + HELP_COALESCE=1 +]; +#define HELP_TEXT_FMT "%s" +/* Above HELP_COALESCE config and HELP_TEXT_FMT PP vars must track. + * Alternative is 0 and "%s\n" . + */ + +/* +** Output primary (single-line) help for a known command. +*/ +static void showPrimaryHelp(FILE *out, const char *zCmd){ + const char **pzH; + int nc = strlen30(zCmd); + for(pzH = azHelp; *pzH != 0; ++pzH){ + if( **pzH=='.' && strncmp(zCmd, (*pzH)+1, nc)==0 ){ + utf8_printf(out, HELP_TEXT_FMT, *pzH); + break; + } + } +} + /* -** Text of help messages. +** 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. +** 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 ** -** The help text for each individual command begins with a line that starts -** with ".". Subsequent lines are supplimental information. +** Return the number of matches. +*/ +static int showHelp(FILE *out, const char *zPattern){ + int npm = 0; + char *zPat = sqlite3_mprintf(".%s*", zPattern? zPattern : ""); + const char **pzHxtra; + const char **pzH; + int nma = 0; + shell_check_oom(zPat); + for(pzH = azHelp; *pzH != 0; ++pzH){ + /* Look for all commands or those for which zPattern is an exact prefix */ + if( *pzH[0]=='.' ){ + if ( sqlite3_strglob(zPat, *pzH)==0 ){ + utf8_printf(out, HELP_TEXT_FMT, *pzH); + pzHxtra = pzH + 1; + ++npm; + } + }else if( zPattern && *zPattern==0 ){ + utf8_printf(out, HELP_TEXT_FMT, *pzH); + } + } + sqlite3_free(zPat); + if( npm==1 ){ + /* When zPattern is a prefix of exactly one command, then include + ** the secondary help of that command, (beginning at *pzHxtra.) */ + while( *pzHxtra !=0 && *pzHxtra[0]!='.' ){ + utf8_printf(out, HELP_TEXT_FMT, *pzHxtra++); + } + } + if( npm>0 ) + return npm; + + /* Having failed to match a command, look for commands whose help contains + * zPattern anywhere. Show the complete text of all such commands. + */ + zPat = sqlite3_mprintf("%%%s%%", zPattern); + if( zPat==0 ) shell_out_of_memory(); + for(pzH = azHelp; *pzH != 0;){ + if( *pzH[0]=='.' ){ + pzHxtra = pzH; + nma = 0; + } + if( sqlite3_strlike(zPat, *pzH, 0)==0 ) + ++nma; + ++pzH; + if( nma>0 && (*pzH==0 || *pzH[0]=='.') ){ + ++npm; + while( pzHxtra < pzH ) + utf8_printf(out, HELP_TEXT_FMT, *pzHxtra++); + } + } + sqlite3_free(zPat); + + return npm; +} + +/* 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. ** -** There must be two or more spaces between the end of the command and the -** start of the description of what that command does. +** 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 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", -#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", - ".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", - " --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\"", +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 - " 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 ?PATTERN?|?-all? Show help for PATTERN or everything, or summarize", - " Repeat -all to see undocumented commands", - ".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", + +/* +** Close all OpenSession objects and release all associated resources. +*/ +#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]); + } + pAuxDb->nSession = 0; +} +#else +# define session_close_all(X,Y) #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", + +/* +** 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 - ".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 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 or -e to use edited value as text or evaluate it first.", -#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; the value is following argument list, space-joined.", - " 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", - ".print STRING... Print literal STRING, then a newline", -#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_\"", -#if 0 /* .script is soon to vanish, or be implemented. */ - ".script CMD ... Manage and use SQL statement store (some TBD)", - " clear ?NAMES? Erase some or all stored SQL statements", - " edit NAME Use edit() to create or alter statement NAME", - " execute ?NAMES? Execute the named SQL statements in order", - " init Initialize the TEMP table that holds statements", - " list List the currently stored statements", - " load FILE ?NAMES? Load some or all SQL statements from FILE", - " save FILE ?NAMES? Save some or all SQL statements into FILE", - " set NAME VALUE Give SQL statement NAME a value of VALUE", - " NAME must start with a letter, and effective", - " value is space-joined, following argument list", - " unset ?NAMES? Remove NAMES from statement store", -#endif - ".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", -#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 -#ifndef SHELL_OMIT_EXTENSIONS - ".shxopts ?SIGNED_OPTS? Show or alter shell extension options", - " Run without arguments to see their self-descriptive names", -#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", - ".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", -#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", -#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", - ".x NAMES ... Excecute content of some .param set variable(s)", - " Only variables whose name begins with a letter are eligible for this." -}; - -/* Like azHelp[], but for undocumented commands. */ -static const char *(azHelpUndoc[]) = { - "These undocumented commands are for internal testing.\n" - "They are subject to change without notice.\n" - ".check GLOB Fail if output since .testcase does not match", - ".seeargs Echo arguments separated or terminated by |", - ".selftest-boolean Check boolean translation", - ".selftest-integer Check integer conversion", - ".testcase NAME Begin output redirect to 'testcase-out.txt'", - ".testctrl CMD ... Run various sqlite3_test_control() operations", - " Run \".testctrl\" with no arguments for details", -}; - -/* This literal's value AND address are used for help's workings. */ -static const char *zHelpAll = "-all"; /* -** Output help text. -** -** 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. +** Try to deduce the type of file for zName based on its content. Return +** one of the SHELL_OPEN_* constants. ** -** Return the number of matches. +** 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. */ - int deduceDatabaseType(const char *zName, int dfltZip){ -static int showHelp(FILE *out, const char *zPattern){ - int i = 0; - int j = 0; - int n = 0; - int nHelp = ArraySize(azHelp); - const char **azHelpText = azHelp; - char *zPat; - if( zPattern==zHelpAll ){ - /* It's zHelpAll, show help for the undocumented commands. */ - nHelp = ArraySize(azHelpUndoc); - azHelpText = azHelpUndoc; - } - if( zPattern==0 - || zPattern[0]=='0' - || strcmp(zPattern,"-a")==0 - || strcmp(zPattern,"-all")==0 - || strcmp(zPattern,"--all")==0 - ){ - /* Show all commands. - * If no pattern, only one line per command. - * If any --all-like pattern, show all help. - */ - if( zPattern==0 ) zPattern = ""; - for(i=0; ip); - sqlite3_free(pSession->zName); - for(i=0; inFilter; i++){ - sqlite3_free(pSession->azFilter[i]); - } - sqlite3_free(pSession->azFilter); - memset(pSession, 0, sizeof(OpenSession)); -} -#endif - -/* -** Close all OpenSession objects and release all associated resources. -*/ -#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]); - } - pAuxDb->nSession = 0; -} -#else -# define session_close_all(X,Y) -#endif - -/* -** 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 - -/* -** 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. +** 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 standard input. ++** If p->aAuxDb[].zDbFilename is 0, then read from the present input. */ static unsigned char *readHexDb(ShellState *p, int *pnData){ unsigned char *a = 0; @@@ -4559,20 -5036,22 +4820,22 @@@ unsigned int x[16]; char zLine[1000]; if( zDbFilename ){ - in = fopen(zDbFilename, "r"); - if( in==0 ){ + inRedir.inFile = fopen(zDbFilename, "r"); + if( inRedir.inFile==0 ){ - utf8_printf(stderr, "cannot open \"%s\" for reading\n", zDbFilename); + utf8_printf(STD_ERR, "cannot open \"%s\" for reading\n", zDbFilename); return 0; } - nLine = 0; + p->pInSource = &inRedir; }else{ - in = p->in; - nLine = p->lineno; - if( in==0 ) in = STD_IN; + /* Will read hex DB lines inline from present input, without redirect. */ + if( INSOURCE_IS_INTERACTIVE(p->pInSource) ){ + printf("Reading hex DB from \"%s\", until end-marker input like:\n%s\n", + p->pInSource->zSourceSay, zEndMarker); - fflush(stdout); ++ fflush(STD_OUT); + } } *pnData = 0; - nLine++; - if( fgets(zLine, sizeof(zLine), in)==0 ) goto readHexDb_error; + if( str_line_get(zLine,sizeof(zLine), p->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; @@@ -4582,10 -5061,10 +4845,10 @@@ shell_check_oom(a); memset(a, 0, n); if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){ - utf8_printf(stderr, "invalid pagesize\n"); + utf8_printf(STD_ERR, "invalid pagesize\n"); goto readHexDb_error; } - for(nLine++; fgets(zLine, sizeof(zLine), in)!=0; nLine++){ + while( str_line_get(zLine,sizeof(zLine), p->pInSource)!=0 ){ rc = sscanf(zLine, "| page %d offset %d", &j, &k); if( rc==2 ){ iOffset = k; @@@ -4613,19 -5092,18 +4876,20 @@@ } return a; - readHexDb_error: - if( in!=p->in ){ - fclose(in); - }else{ - while( fgets(zLine, sizeof(zLine), p->in)!=0 ){ - nLine++; - if(strncmp(zLine, "| end ", 6)==0 ) break; + readHexDb_error: + nlError = p->pInSource->lineno; + if( p->pInSource!=&inRedir ){ + /* Since taking input inline, consume through its end marker. */ + while( str_line_get(zLine, sizeof(zLine), p->pInSource)!=0 ){ ++ + if(strncmp(zLine, zEndMarker, 6)==0 ) break; } - p->lineno = nLine; ++ } sqlite3_free(a); - utf8_printf(STD_ERR,"Error on line %d of --hexdb input\n", nLine); - return 0; + a = 0; - utf8_printf(stderr,"Error on line %d within --hexdb input\n", nlError); ++ utf8_printf(STD_ERR,"Error on line %d within --hexdb input\n", nlError); + goto readHexDb_cleanup; } #endif /* SQLITE_OMIT_DESERIALIZE */ @@@ -4805,8 -5283,8 +5069,8 @@@ static void open_db(ShellState *p, int if( zDbFilename==0 || zDbFilename[0]==0 ){ p->openMode = SHELL_OPEN_NORMAL; }else{ - p->openMode = (u8)deduceDatabaseType(p->pAuxDb->zDbFilename, - (openFlags & OPEN_DB_ZIPFILE)!=0); - p->openMode = deduceDatabaseType(zDbFilename, ++ p->openMode = deduceDatabaseType(p->pAuxDb->zDbFilename, + (openFlags & OPEN_DB_ZIPFILE)!=0); } } switch( p->openMode ){ @@@ -4926,19 -5404,11 +5190,19 @@@ ** Attempt to close the databaes connection. Report errors. */ void close_db(sqlite3 *db){ - int rc = sqlite3_close(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(stderr, "Error: sqlite3_close() returns %d: %s\n", + utf8_printf(STD_ERR, "Error: sqlite3_close() returns %d: %s\n", rc, sqlite3_errmsg(db)); - } + } } #if HAVE_READLINE || HAVE_EDITLINE @@@ -5083,14 -5557,11 +5354,11 @@@ static int booleanValue(const char *zAr for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){} } if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff); - if( sqlite3_stricmp(zArg, "on")==0 || sqlite3_stricmp(zArg,"yes")==0 ){ - return 1; - } - if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){ - return 0; + for( i=0; zBoolNames[i]!=0; ++i ){ + if( sqlite3_stricmp(zArg, zBoolNames[i])==0 ) return i&1; } - utf8_printf(stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n", + utf8_printf(STD_ERR, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n", - zArg); + zArg); return 0; } @@@ -5643,6 -6115,26 +5912,26 @@@ static int db_int(sqlite3 *db, const ch return res; } + /* + ** 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. ++** The return must be freed by caller using sqlite3_free(). + */ + 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)); + shell_check_oom(zRes); + } + } + sqlite3_finalize(pStmt); + return zRes; + } + /* ** Convert a 2-byte or 4-byte big-endian integer into a native integer */ @@@ -5852,11 -6344,10 +6141,9 @@@ static int testcase_glob(const char *zG if( c!=(*(z++)) ) return 0; } } - while( IsSpace(*z) ){ z++; } - return *z==0; + return *skipWhite(z)==0; } -- /* ** Compare the string as a command-line option with either one or two ** initial "-" characters. @@@ -6167,7 -6679,7 +6454,7 @@@ static void shellPrepare 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", - raw_printf(stderr, "sql error: %s (%d)\n", ++ raw_printf(STD_ERR, "sql error: %s (%d)\n", sqlite3_errmsg(db), sqlite3_errcode(db) ); *pRc = rc; @@@ -6561,10 -7069,9 +6848,10 @@@ static int arCheckEntries(ArCommand *pA ** 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, + int *pRc, ArCommand *pAr, char **pzWhere /* OUT: New WHERE clause */ ){ @@@ -6937,10 -7444,10 +7224,10 @@@ static int arDotCommand utf8_printf(pState->out, "-- open database '%s'%s\n", cmd.zFile, eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : ""); } - rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, + 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", - utf8_printf(stderr, "cannot open file: %s (%s)\n", ++ utf8_printf(STD_ERR, "cannot open file: %s (%s)\n", cmd.zFile, sqlite3_errmsg(cmd.db) ); goto end_ar_command; @@@ -7064,15 -7571,15 +7351,15 @@@ static void *shellMalloc(int *pRc, sqli } /* -** 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 +** If pRc!=0 and *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 ++** 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. +** If an OOM error occurs, (*pRc) is set to SQLITE_NOMEM (if pRc!=0) +** and a NULL pointer returned. */ static char *shellMPrintf(int *pRc, const char *zFmt, ...){ char *z = 0; @@@ -7363,4370 -7871,4447 +7650,5041 @@@ static RecoverTable *recoverOrphanTable } return pTab; } +#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */ -/* -** 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; - - int bFreelist = 1; /* 0 if --freelist-corrupt is specified */ - int bRowids = 1; /* 0 if --no-rowids */ - for(i=1; iout, azArg[0]); - return 1; +static int writeDb( char *azArg[], int nArg, ShellState *p, 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( p->bSafeMode ) return SHELL_FORBIDDEN_OP; + for(j=1; jdb, &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( 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( zDestFile==0 ){ + return SHELL_INVALID_ARGS; + } + if( zDb==0 ) zDb = "main"; - rc = sqlite3_open_v2(zDestFile, &pDest, ++ rc = sqlite3_open_v2(zDestFile, &pDest, + SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs); + if( rc!=SQLITE_OK ){ + utf8_printf(STD_ERR, "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(STD_ERR, "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(STD_ERR, "Error: %s\n", sqlite3_errmsg(pDest)); + rc = 1; } + close_db(pDest); + return rc; +} - /* 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" - ); +/* + * 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. + */ +#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) ++ 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 - 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" - ");" - - /* 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(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] - ); - } - shellFinalize(&rc, pStmt); - } - - /* 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); - } - shellFinalize(&rc, pLoop); - pLoop = 0; - - shellPrepare(pState->db, &rc, - "SELECT pgno FROM recovery.map WHERE root=?", &pPages - ); - - 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 - ); - - /* Loop through each root page. */ - shellPrepare(pState->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(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; - } - - if( 0==sqlite3_stricmp(pTab->zQuoted, "\"sqlite_sequence\"") ){ - raw_printf(pState->out, "DELETE FROM sqlite_sequence;\n"); - } - sqlite3_bind_int(pPages, 1, iRoot); - if( bRowids==0 && pTab->iPk<0 ){ - sqlite3_bind_int(pCells, 1, 1); - }else{ - sqlite3_bind_int(pCells, 1, 0); - } - sqlite3_bind_int(pCells, 3, pTab->iPk); - - 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(pState, &rc, zLostAndFound, nOrphan); - } - pTab2 = pOrphan; - if( pTab2==0 ) break; - } - - 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); - } - shellFinalize(&rc, pLoop); - shellFinalize(&rc, pPages); - shellFinalize(&rc, pCells); - recoverFreeTable(pOrphan); - - /* 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); - } - } - shellFinalize(&rc, pStmt); - } - - 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); - return rc; -} -#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */ +#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 -/* -** The .shxopts command, for setting or listing shell extension options. +/* 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. */ -#if SHELL_EXTENSIONS -static int shxoptsCommand(char *azArg[], int nArg, ShellState *p, char **pzE){ - static struct { const char *name; u8 mask; } shopts[] = { -#if SHELL_DYNAMIC_COMMANDS - {"dyn_cmds", 1<1 ){ - for( ia=1; iabExtendedDotCmds |= shopts[io].mask; - else p->bExtendedDotCmds &= ~shopts[io].mask; - break; - } - } - if( io==ArraySize(shopts) ){ - zAbout = azArg[ia]; - zMoan = "is not a recognized option name"; - goto moan_error; - } + static const char * const zRenameRank = +#ifdef SHELL_COLUMN_RENAME_CLEAN + "UPDATE ColNames AS t SET suff=" + "iif(reps>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{ - raw_printf(p->out, - " name value \"-shxopts set\"\n" - " -------- ----- ---------------\n"); - for( io=0; iobExtendedDotCmds & m) == m)? 1 : 0; - raw_printf(p->out, - " %9s %2d \"-shxopts 0x%02X\"\n", - shopts[io].name, v, m); - } - } - return 0; - moan_error: - raw_printf(stderr, "Error: %s %s\n", zAbout, zMoan); - return 1; -} + /* 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 - -static int execute_variables(char *azArg[], int nArg, ShellState *p){ - int ia, rc, nErrors = 0; - sqlite3_stmt *pStmt = 0; - open_db(p, 0); - if( p->db==0 ){ - utf8_printf(stderr, ".x can only be done with a database open.\n"); - return 1; - } - if( sqlite3_table_column_metadata(p->db, PARAM_TABLE_SCHEMA, - PARAM_TABLE_NAME, - "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){ - utf8_printf(stderr, "No "PARAM_TABLE_SNAME" table exists.\n"); - return 1; - } - rc = sqlite3_prepare_v2 - (p->db, "SELECT value FROM "PARAM_TABLE_SNAME - " WHERE key=$1 AND uses=1", - -1, &pStmt, 0); - if( rc!=SQLITE_OK ){ - utf8_printf(stderr, PARAM_TABLE_SNAME" is wrongly created.\n"); - return 1; - } - for( ia=1; ia < nArg; ++ia ){ - if( isalpha(azArg[ia][0]) ){ - rc = sqlite3_reset(pStmt); - rc = sqlite3_bind_text(pStmt, 1, azArg[ia], -1, 0); + 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); - if( rc==SQLITE_ROW ){ - const unsigned char *zValue = sqlite3_column_text(pStmt, 0); - int nb = sqlite3_column_bytes(pStmt, 0); - while( nb>0 && 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 ); - InSource inRedir - = INSOURCE_STR_REDIR(zSubmit, azArg[ia], p->pInSource); - shell_check_oom(zSubmit); - p->pInSource = &inRedir; - rc = process_input(p); - sqlite3_free(zSubmit); - p->pInSource = inRedir.pFrom; - }else{ - continue; /* All white, ignore. */ - } - }else{ - utf8_printf(stderr, - "Skipping parameter '%s' (not set and executable.)\n", - azArg[ia]); - ++nErrors; + 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)); + }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)); + }else + *pzRenamed = 0; } - }else{ - utf8_printf(stderr, - "Skipping badly named %s. Run \".help x\"\n", azArg[ia]); - ++nErrors; } + sqlite3_finalize(pStmt); + sqlite3_close(*pDb); + *pDb = 0; + return zColsSpec; } - sqlite3_finalize(pStmt); - return (rc==2)? 2 : nErrors>0; } -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 */ - if( nc>1 ) pParam->uses = (int)integerValue(pV[1]); - ++pParam->hits; +#ifndef OBJECTIFY_COMMANDS +# define OBJECTIFY_COMMANDS 1 +#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 dispatch and help table entries, to be later +COMMENT emitted by certain macros. (See EMIT_* further on.) +** All dispatchable meta-command execute functions have this signature: +static int someCommand(char *azArg[], int nArg, ShellState *p, char **pzErr); +*/ +DISPATCH_CONFIG[ + RETURN_TYPE=int + STORAGE_CLASS=static + ARGS_SIGNATURE=char *$arg4\[\], int $arg5, ShellState *$arg6, char **$arg7 + DISPATCH_ENTRY={ "$cmd", ${cmd}Command, $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(SQLITE_GIMME_SEEARGS)); ++CONDITION_COMMAND(seeargs !defined(SHELL_OMIT_SEEARGS)); +/***************** + * The .seeargs command + */ +COLLECT_HELP_TEXT[ - ".seeargs Echo arguments separated by |", - " A near-dummy command for use as a template (to vanish soon)", ++ ".seeargs Echo arguments suffixed with |", +]; +DISPATCHABLE_COMMAND( seeargs ? 0 0 azArg nArg p ){ + int rc = 0; + for (rc=1; rcout, "%s%s", azArg[rc], (rc==nArg-1)? "\n" : "|"); - return rc; ++ raw_printf(p->out, "%s%s", azArg[rc], (rc==nArg-1)? "|\n" : "|"); + 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); +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( p->bSafeMode ) return SHELL_FORBIDDEN_OP; + return arDotCommand(p, 0, azArg, nArg); +} -/* Create a home-relative pathname from ~ prefixed path. - * Return it, or 0 for any error. - * Caller must sqlite3_free() it. +/***************** + * The .auth command */ -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]!='/' +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 ){ + int rc = 0; + open_db(p, 0); + if( booleanValue(azArg[1]) ){ + sqlite3_set_authorizer(p->db, shellAuth, p); + }else if( p->bSafeModeFuture ){ + sqlite3_set_authorizer(p->db, safeModeAuth, p); + }else{ + sqlite3_set_authorizer(p->db, 0, 0); + } + return rc; +} + +/***************** + * 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()", +]; +COLLECT_DISPATCH( * )[ + { "backup", writeDb, 4, 2, 5 }, + { "save", writeDb, 3, 2, 5 }, +]; + +/***************** + * 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 0; +} + +/***************** + * 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(p->out, 1); + }else{ + setTextMode(p->out, 1); + } + return 0; +} + +DISPATCHABLE_COMMAND( cd ? 2 2 ){ + int rc=0; + if( p->bSafeMode ) return SHELL_FORBIDDEN_OP; #if defined(_WIN32) || defined(WIN32) - && zPath[1]!='\\' + wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]); + rc = !SetCurrentDirectoryW(z); + sqlite3_free(z); +#else + rc = chdir(azArg[1]); #endif - ) ){ - zErr = "Malformed pathname"; - }else{ - return sqlite3_mprintf("%s%s", zHome, zPath+1); + if( rc ){ + utf8_printf(STD_ERR, "Cannot change to directory \"%s\"\n", azArg[1]); + rc = 1; } - utf8_printf(stderr, "Error: %s\n", zErr); + return 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 0; } -/* 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. +/***************** + * The .changes, .check, .clone and .connection commands */ -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; +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 0; +} +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; + output_reset(p); + if( nArg!=2 ){ + return SHELL_INVALID_ARGS; + }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){ + *pzErr = shellMPrintf(&rc, "Error: cannot read 'testcase-out.txt'"); + rc = 2; + }else if( testcase_glob(azArg[1],zRes)==0 ){ + *pzErr = + shellMPrintf(&rc, + "testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n", + p->zTestcase, azArg[1], zRes); + rc = 1; + }else{ + utf8_printf(STD_OUT, "testcase-%s ok\n", p->zTestcase); + p->nCheck++; + } + sqlite3_free(zRes); + return rc; +} +DISPATCHABLE_COMMAND( clone ? 2 2 ){ + if( p->bSafeMode ) return SHELL_FORBIDDEN_OP; + tryToClone(p, azArg[1]); + return 0; +} +DISPATCHABLE_COMMAND( connection ? 1 4 ){ + 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(STD_OUT, "ACTIVE %d: %s\n", i, zFile); + }else if( p->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( 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(STD_ERR, "cannot close the active database connection\n"); + return 1; + }else if( p->aAuxDb[i].db ){ + session_close_all(p, i); + close_db(p->aAuxDb[i].db); + p->aAuxDb[i].db = 0; + } + }else{ + return SHELL_INVALID_ARGS; + } + return 0; +} - /* Ensure store DB can be opened and/or created appropriately. */ - rc = sqlite3_open_v2(zStoreDbName, &dbStore, openFlags, 0); - if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "Error: Cannot %s parameter store DB %s\n", - bSaveNotLoad? "open/create" : "read", zStoreDbName); - return rc; +/***************** + * 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; + 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 ){ + *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->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++; + } } - /* 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(stderr, "Error: No parameters ever stored in DB %s\n", - zStoreDbName); - rc = 1; + 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); + return rc; +} +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(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0); + } + 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) ){ + *pzErr = sqlite3_mprintf + ("Error: unknown dbconfig \"%s\"\n" + "Enter \".dbconfig\" with no arguments for a list\n", + azArg[1]); + return 1; - } ++ } + return 0; +} +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 ){ + char *zLike = 0; ++ char *zSchema = "main"; + char *zSql; + int i; + int savedShowHeader = p->showHeader; + int savedShellFlags = p->shellFlgs; - ShellClearFlag(p, ++ ShellClearFlag(p, + SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo + |SHFLG_DumpDataOnly|SHFLG_DumpNoSys); + for(i=1; ishellFlgs & 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 " ++ "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", - zLike ++ zSchema, 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; - sqlite3_exec(db, "DETACH "PARAM_STORE_SCHEMA";", 0, 0, 0); - return rc; + return 0; } - -/* Default location of parameters store DB for .parameters save/load. */ -static const char *zDefaultParamStore = "~/sqlite_params.sdb"; - -/* 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. - */ -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; +DISPATCHABLE_COMMAND( echo ? 2 2 ){ + setOrClearFlag(p, SHFLG_Echo, azArg[1]); + return 0; } - -/* 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(stderr, "Cannot form parameter load path. Nothing loaded.\n"); - return 1; +DISPATCHABLE_COMMAND( eqp ? 0 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]); + } }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; + return SHELL_INVALID_ARGS; } } -/* 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(stderr, "Cannot form parameter save path. Nothing saved.\n"); - return 1; - }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; +/***************** + * 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 ){ + open_db(p, 0); + expertDotCommand(p, azArg, nArg); + return 0; +} +DISPATCHABLE_COMMAND( explain ? 1 2 ){ + /* The ".explain" command is automatic now. It is largely + ** pointless, retained purely for backwards compatibility */ + int val = 1; + if( nArg>1 ){ + if( strcmp(azArg[1],"auto")==0 ){ + val = 99; + }else{ + val = booleanValue(azArg[1]); + } } + if( val==1 && p->mode!=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; + } + return 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.) +/***************** + * The .excel, .once and .output commands + * These share much implementation, so they stick together. */ -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; - char * zVal = 0; - char * zSql = sqlite3_mprintf - ("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 - ("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 - ("UPDATE "PARAM_TABLE_SNAME" SET value=(SELECT %s) WHERE" - " key=%Q AND uses=%d", zVal, name, uses); +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\")", +]; +static int outputRedirs(char *[], int, ShellState *, + char **pzErr, int bOnce, int eMode); +DISPATCHABLE_COMMAND( excel ? 1 2 ){ + return outputRedirs(azArg, nArg, p, pzErr, 2, 'x'); +} +DISPATCHABLE_COMMAND( once ? 1 6 ){ + return outputRedirs(azArg, nArg, p, pzErr, 1, 0); +} +DISPATCHABLE_COMMAND( output ? 1 6 ){ + return outputRedirs(azArg, nArg, p, pzErr, 0, 0); +} + +static int outputRedirs(char *azArg[], int nArg, ShellState *p, + 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( p->bSafeMode ) return SHELL_FORBIDDEN_OP; + for(i=1; ioutCount = 2; }else{ - /* Editing a new value of same kind. */ - assert(paramVU.value==0 || paramVU.uses!=uses); - if( eval!=0 ){ - zSql = sqlite3_mprintf - ("SELECT edit('-- %q%s', %Q)", name, "\n", zEditor); - zVal = db_text(db, zSql, 1); - sqlite3_free(zSql); - zSql = sqlite3_mprintf - ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)" - " VALUES (%Q,(SELECT %s LIMIT 1),%d)", - name, zVal, uses); + p->outCount = 0; + } + 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{ - zSql = sqlite3_mprintf - ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)" - " VALUES (%Q,edit('-- %q%s', %Q),%d)", - name, name, "\n", zEditor, uses); + /* text editor mode */ + newTempFile(p, "txt"); + bTxtMode = 1; } + sqlite3_free(zFile); + zFile = sqlite3_mprintf("%s", p->zTempFile); } - shell_check_oom(zSql); - if( eval!=0 ){ +#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"); + rc = 1; + p->out = STD_OUT; +#else + p->out = popen(zFile + 1, "w"); + if( p->out==0 ){ + *pzErr = shellMPrintf(&rc, "Error: cannot open pipe \"%s\"\n", zFile + 1); + p->out = STD_OUT; + rc = 1; + }else{ + if( bBOM ) fprintf(p->out,"\357\273\277"); + sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); + } +#endif + }else{ + p->out = output_file_open(zFile, bTxtMode); + if( p->out==0 ){ + if( strcmp(zFile,"off")!=0 ){ + *pzErr = shellMPrintf + (&rc, "Error: cannot write to \"%s\"\n", zFile); + } + p->out = STD_OUT; + rc = 1; + } else { + if( bBOM ) fprintf(p->out,"\357\273\277"); + sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); + } } - rc = sqlite3_exec(db, zSql, 0, 0, 0); - sqlite3_free(zSql); - sqlite3_free(zVal); - return rc!=SQLITE_OK; + sqlite3_free(zFile); + return rc; } -#endif -/* Space-join values in an argument list. *valLim is not included. */ -char *values_join( char **valBeg, char **valLim ){ - char *z = 0; - const char *zSep = 0; - while( valBeg < valLim ){ - z = sqlite3_mprintf("%z%s%s", z, zSep, *valBeg); - zSep = " "; - ++valBeg; + +/***************** + * 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, "" }, ++ { "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; + + open_db(p, 0); + zCmd = nArg>=2 ? azArg[1] : "help"; + - if( zCmd[0]=='-' ++ 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 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); + return 1; + }else if( isOk==1 ){ + char zBuf[100]; + sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes); + raw_printf(p->out, "%s\n", zBuf); + } + return 0; } -/* Get a named parameter value in form of stepped prepared statement, - * ready to have its value taken from the 0th column. If the name - * cannot be found for the given ParamTableUse, 0 is returned. - * The caller is responsible for calling sqlite3_finalize(pStmt), - * where pStmt is the return from this function. - */ -static sqlite3_stmt *get_param_value(sqlite3 *db, char *name, - ParamTableUse ptu){ - sqlite3_stmt *rv = 0; +DISPATCHABLE_COMMAND( fullschema ? 1 2 ){ int rc; - char *zSql = sqlite3_mprintf - ( "SELECT value FROM "PARAM_TABLE_SNAME - " WHERE key=%Q AND uses=%d", name, ptu ); - shell_check_oom(zSql); - rc = sqlite3_prepare_v2(db, zSql, -1, &rv, 0); - sqlite3_free(zSql); - if( SQLITE_OK==rc ){ - if( SQLITE_ROW==sqlite3_step(rv) ) return rv; - sqlite3_finalize(rv); + 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 ){ + return SHELL_INVALID_ARGS; + } + 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 + ); + 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"); } - return 0; + return rc > 0; } -static struct ParamSetOpts { - const char cCast; - const char *zTypename; - int evalKind; -} param_set_opts[] = { - /* { 'q', 0, 2 }, */ - /* { 'x', 0, 1 }, */ - { 'i', "INT", 1 }, - { 'r', "REAL", 1 }, - { 'b', "BLOB", 1 }, - { 't', "TEXT", 0 }, - { 'n', "NUMERIC", 1 } -}; +/***************** + * The .headers command + */ +COLLECT_HELP_TEXT[ + ".headers on|off Turn display of headers on or off", +]; +DISPATCHABLE_COMMAND( headers 6 2 2 ){ + p->showHeader = booleanValue(azArg[1]); + p->shellFlgs |= SHFLG_HeaderSet; + return 0; +} -/* Return an option character if it is single and prefixed by - or --, - * else return 0. +/***************** + * The .help command */ -static char option_char(char *zArg){ - if( zArg[0]=='-' ){ - ++zArg; - if( zArg[0]=='-' ) ++zArg; - if( zArg[0]!=0 && zArg[1]==0 ) return zArg[0]; +COLLECT_HELP_TEXT[ - ".help ?(PATTERN|-all)? Show help text for some or all command(s)", - " PATTERN Show help for matching command(s)", - " -all Show all help for all commands", ++ ".help ?PATTERN?|?-all? Show help for PATTERN or everything, or summarize", ++ " Repeat -all to see undocumented commands", +]; +DISPATCHABLE_COMMAND( help 3 1 2 ){ + const char *zPat = 0; + if( nArg>1 ){ + char *z = azArg[1]; + if( strcmp(z,"-a")==0 + || strcmp(z,"-all")==0 + || strcmp(z,"--all")==0 ){ + zPat = ""; + }else{ + zPat = z; + } + } + if( showHelp(p->out, zPat)==0 ){ + utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]); } + /* Help pleas never fail! */ return 0; } -static int param_set(sqlite3 *db, char cCast, - char *name, char **valBeg, char **valLim, - ParamTableUse ptu){ - char *zSql = 0; - int rc = SQLITE_OK, retries = 0, needsEval = 1; - char *zValGlom = (valLim-valBeg>1)? 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; - } - } +/***************** + * 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 = "main"; /* within this schema */ + 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 */ + 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 */ + int rc = 0; + + if(p->bSafeMode) return SHELL_FORBIDDEN_OP; + memset(&sCtx, 0, sizeof(sCtx)); + if( 0==(sCtx.z = sqlite3_malloc64(120)) ){ + shell_out_of_memory(); } - if( needsEval ){ - if( zCastTo!=0 ){ - zSql = sqlite3_mprintf - ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" - " VALUES(%Q,CAST((%s) AS %s),%d);", name, zValue, zCastTo, ptu ); + + if( p->mode==MODE_Ascii ){ + xRead = ascii_read_one_field; + }else{ + xRead = csv_read_one_field; + } + for(i=1; idb); - int len = 0, rc; - char *zFromWhere = 0; - char *zSql = 0; - sqlite3_str_appendf(sbList, "FROM "PARAM_TABLE_SNAME - " WHERE (?1=3 OR uses=?1) AND "); - append_glob_terms(sbList, "key", - (const char **)pzArgs, (const char **)pzArgs+nArg); - zFromWhere = sqlite3_str_finish(sbList); - shell_check_oom(zFromWhere); - zSql = sqlite3_mprintf("SELECT max(length(key)) %s", zFromWhere); - shell_check_oom(zSql); + seenInterrupt = 0; + open_db(p, 0); + if( useOutputMode ){ + const char *zYap = 0; + /* 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 ){ + zYap = "Error: non-null column separator required for import"; + } + if( nSep>1 ){ + zYap = "Error: multi-character or multi-byte column separators" + " not allowed for import"; + } + nSep = strlen30(p->rowSeparator); + if( nSep==0 ){ + zYap = "Error: non-null row separator required for import"; + } + if( zYap!=0 ){ + *pzErr = shellMPrintf(0,"%s\n", zYap); + return 1; + } + 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 ){ + *pzErr = sqlite3_mprintf + ("Error: multi-character row separators not allowed for import\n"); + return 1; + } + sCtx.cColSep = p->colSeparator[0]; + sCtx.cRowSep = p->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"); + return 1; +#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); + import_cleanup(&sCtx); + return 1; + } + /* 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(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"); + } + while( (nSkip--)>0 ){ + while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){} + } + zSql = sqlite3_mprintf("SELECT * FROM \"%w\".\"%w\"", zSchema, zTable); + if( zSql==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + nByte = strlen30(zSql); rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - if( rc==SQLITE_OK ){ - sqlite3_bind_int(pStmt, 1, ptu); - if( sqlite3_step(pStmt)==SQLITE_ROW ){ - len = sqlite3_column_int(pStmt, 0); - if( len>40 ) len = 40; - if( len<4 ) len = 4; + import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ + if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){ + char *zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"", + zSchema, zTable); + 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 ){ - utf8_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr, - "Columns renamed during .import %s due to duplicates:\n" ++ FILE *fh = INSOURCE_IS_INTERACTIVE(p->pInSource)? p->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 ){ + sqlite3_free(zCreate); + import_cleanup(&sCtx); + *pzErr = shellMPrintf(0,"%s: empty file\n", sCtx.zFile); + return 1; } + 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)); ++ utf8_printf(STD_ERR, "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db)); + sqlite3_free(zCreate); + import_cleanup(&sCtx); + return 1; + } + sqlite3_free(zCreate); + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + } + sqlite3_free(zSql); + if( rc ){ + if (pStmt) sqlite3_finalize(pStmt); + *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db)); + import_cleanup(&sCtx); + return 1; } + nCol = sqlite3_column_count(pStmt); sqlite3_finalize(pStmt); pStmt = 0; - if( len ){ - 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); - shell_check_oom(zSql); - rc = sqlite3_prepare_v2(p->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(p->out, "Binding Values:\n%-*s %s\n", - len, "name", "value"); - } - utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0), - sqlite3_column_text(pStmt,2)); - break; - case PTU_Script: - if( nScripts++ == 0 ){ - utf8_printf(p->out, "Scripts\n%-*s %s\n", len, "name", "value"); + 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 \"%w\".\"%w\" VALUES(?", + zSchema, zTable); + 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); + sqlite3_free(zSql); + if( rc ){ + *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db)); + if (pStmt) sqlite3_finalize(pStmt); + import_cleanup(&sCtx); + return 1; + } + 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( 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(p->db)); + sCtx.nErr++; + }else{ + sCtx.nRow++; + } + } + }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); + } + return 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 ){ + if( nArg<2 ){ + int i = 0; + int nk = sqlite3_keyword_count(); + int nCol = 0; + int szKW; + while( i75){ + zSep = "\n"; + nCol = 0; } - utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0), - sqlite3_column_text(pStmt,2)); - break; - default: break; /* Ignore */ + memcpy(kwBuf, zKW, szKW); + kwBuf[szKW] = 0; + utf8_printf(p->out, "%s%s", kwBuf, zSep); } } - }else{ - int nc = 0, ncw = 78/(len+2); - zSql = sqlite3_mprintf("SELECT key %z ORDER BY key", zFromWhere); - shell_check_oom(zSql); - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - sqlite3_bind_int(pStmt, 1, ptu); - while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ - utf8_printf(p->out, "%s %-*s", ((++nc%ncw==0)? "\n" : ""), - len, sqlite3_column_text(pStmt,0)); + } + if( nCol>0 ) utf8_printf(p->out, "\n"); + }else{ + int szKW = strlen30(azArg[1]); + int isKeyword = sqlite3_keyword_check(azArg[1], szKW); + utf8_printf(p->out, "%s is%s a keyword\n", + azArg[1], (isKeyword)? "" : " not"); + } + return 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", + ".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; + 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"); + /* 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 1; + } + open_db(p, 0); + if( nArg==2 ){ + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 1); + return 0; + } + 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] + ); + 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( nc>0 ) utf8_printf(p->out, "\n"); } - sqlite3_finalize(pStmt); + 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 ){ + *pzErr = shellMPrintf(0,"no such index: \"%s\"\n", azArg[1]); + sqlite3_free(zCollist); + return 1; + } + 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 ){ + *pzErr = shellMPrintf(0,"Error in [%s]: %s\n", + zSql, sqlite3_errmsg(p->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{ - sqlite3_free(zFromWhere); + *pzErr = shellMPrintf(0,"SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); } sqlite3_free(zSql); -} - -/* 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( azBegdb, aLimit[i].limitCode, -1)); } - sqlite3_str_appendf(pStr, ")"); + }else if( nArg>3 ){ + return SHELL_INVALID_ARGS; + }else{ + int iLimit = -1; + n2 = strlen30(azArg[1]); + for(i=0; idb, aLimit[iLimit].limitCode, + (int)integerValue(azArg[2])); + } + fprintf(STD_OUT, "%20s %d\n", aLimit[iLimit].zLimitName, + sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1)); } + return 0; } +DISPATCHABLE_COMMAND( lint 3 1 0 ){ + open_db(p, 0); + int n = (nArg>=2 ? strlen30(azArg[1]) : 0); + if( n>0 && !sqlite3_strnicmp(azArg[1], "fkey-indexes", n) ){ + return lintFkeyIndexes(p, azArg, nArg); + } + *pzErr = sqlite3_mprintf + ("Usage %s sub-command ?switches...?\n" + "Where sub-commands are:\n" + " fkey-indexes\n", azArg[0]); + return 1; +} -/* - * 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. - */ -#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); +DISPATCHABLE_COMMAND( load ? 2 3 ){ + const char *zFile, *zProc; + char *zErrMsg = 0; + if( p->bSafeMode ) return SHELL_FORBIDDEN_OP; + zFile = azArg[1]; + zProc = nArg>=3 ? azArg[2] : 0; + open_db(p, 0); + if( SQLITE_OK!=sqlite3_load_extension(p->db, zFile, zProc, pzErr) ){ + return 1; + } + return 0; } -#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 +DISPATCHABLE_COMMAND( log ? 2 2 ){ + const char *zFile = azArg[1]; + if( p->bSafeMode ) return SHELL_FORBIDDEN_OP; + output_file_close(p->pLog); + p->pLog = output_file_open(zFile, 0); + return 0; +} -/* 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 ++static void effectMode(ShellState *p, 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( (p->shellFlgs & SHFLG_HeaderSet)==0 ){ ++ p->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(p->rowSeparator), p->rowSeparator, zRowSep); ++ } ++ if( zColSep!=0 ){ ++ sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, zColSep); ++ } ++ p->mode = modeNominal; ++} + -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); +/***************** + * The .mode command + */ +COLLECT_HELP_TEXT[ - ".mode MODE ?TABLE? Set output mode", ++ ".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 ){ + const char *zTabname = 0; + const char *zArg; + int i; + u8 foundMode = MODE_COUNT_OF, setMode = MODE_COUNT_OF; + ColModeOpts cmOpts = ColModeOpts_default; + for(i=1; imode; + assert(i>=0 && imode==MODE_Column || (p->mode>=MODE_Markdown && p->mode<=MODE_Box) ){ ++ if( MODE_IS_COLUMNAR(p->mode) ){ + raw_printf + (p->out, "current output mode: %.*s --wrap %d --wordwrap %s --%squote\n", + nms, zMode, p->cmOpts.iWrap, + p->cmOpts.bWordWrap ? "on" : "off", + p->cmOpts.bQuote ? "" : "no"); + }else{ + raw_printf(p->out, "current output mode: %.*s\n", nms, zMode); } - 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{ - /* Effect the specified mode change. */ - const char *zColSep = 0, *zRowSep = 0; - assert(setMode!=MODE_COUNT_OF); - switch( foundMode ){ - case MODE_Line: - zRowSep = SEP_Row; - break; - case MODE_Column: - if( (p->shellFlgs & SHFLG_HeaderSet)==0 ){ - p->showHeader = 1; - } - zRowSep = SEP_Row; - p->cmOpts = cmOpts; - 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: - /* 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); ++ effectMode(p, foundMode, setMode); ++ if( MODE_IS_COLUMNAR(setMode) ) p->cmOpts = cmOpts; ++ else if( setMode==MODE_Insert ){ + set_table_name(p, zTabname ? zTabname : "table"); - 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: - p->cmOpts = cmOpts; - 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 0; - } - if( zRowSep!=0 ){ - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, zRowSep); - } - if( zColSep!=0 ){ - sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, zColSep); + } - p->mode = setMode; + } + p->cMode = p->mode; + return 0; + flag_unknown: - utf8_printf(stderr, "Error: Unknown .mode option: %s\nValid options:\n%s", ++ 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 1; + mode_unknown: - raw_printf(stderr, "Error: Mode should be one of: " ++ 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 1; + mode_badarg: - utf8_printf(stderr, "Error: Invalid .mode argument: %s\n", zArg); ++ utf8_printf(STD_ERR, "Error: Invalid .mode argument: %s\n", zArg); + return 1; +} + +/***************** + * 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 - 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); + " --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 ){ + 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; ++ u8 openMode = SHELL_OPEN_UNSPEC; + int rc = 0; + /* 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]=='-' ){ + *pzErr = shellMPrintf(0,"unknown option: %s\n", z); + return SHELL_INVALID_ARGS; + }else if( zFN ){ + *pzErr = shellMPrintf(0,"extra argument: \"%s\"\n", z); + return SHELL_INVALID_ARGS; + }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 + ){ + *pzErr = shellMPrintf(0,"cannot open database files in safe mode"); + return SHELL_FORBIDDEN_OP; } - 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)); + if( zFN ){ + zNewFilename = sqlite3_mprintf("%s", zFN); + shell_check_oom(zNewFilename); }else{ - zColsSpec = 0; + zNewFilename = 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; - } + p->pAuxDb->zDbFilename = zNewFilename; + open_db(p, OPEN_DB_KEEPALIVE); + if( p->db==0 ){ + *pzErr = shellMPrintf(0,"Error: cannot open '%s'\n", zNewFilename); + sqlite3_free(zNewFilename); + rc = 1; + }else{ + p->pAuxDb->zFreeOnClose = zNewFilename; } - sqlite3_finalize(pStmt); - sqlite3_close(*pDb); - *pDb = 0; - return zColsSpec; } + if( p->db==0 ){ + /* As a fall-back open a TEMP database */ + p->pAuxDb->zDbFilename = 0; + open_db(p, 0); + } + return rc; } -/* -** 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]; -#if SHELL_VARIABLE_EXPANSION - int ncLineIn = strlen30(zLine); - u8 bExpVars = SHEXT_VAREXP(p); -#endif - -#ifndef SQLITE_OMIT_VIRTUALTABLE - if( p->expert.pExpert ){ - expertFinish(p, 1, 0); +DISPATCHABLE_COMMAND( nonce ? 2 2 ){ + if( p->zNonce==0 || strcmp(azArg[1],p->zNonce)!=0 ){ + raw_printf(STD_ERR, "line %d: incorrect nonce: \"%s\"\n", - p->lineno, azArg[1]); ++ p->pInSource->lineno, azArg[1]); + exit(1); } + /* Suspend safe mode for 1 meta-command after this. */ + p->bSafeModeFuture = 2; + return 0; +} + +DISPATCHABLE_COMMAND( nullvalue ? 2 2 ){ + sqlite3_snprintf(sizeof(p->nullValue), p->nullValue, + "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]); + return 0; +} + - /***************** - * The .parameter command ++/* Helper functions for .parameter command + */ - COLLECT_HELP_TEXT[ - ".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", - ]; - DISPATCHABLE_COMMAND( parameter 4 2 4 ){ - int rc = 0; - open_db(p,0); - - /* .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 - - /* .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 - - /* .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); - }else + - /* .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 ){ - int rx; - char *zSql; - sqlite3_stmt *pStmt; - const char *zKey = azArg[2]; - const char *zValue = azArg[3]; - bind_table_init(p); - zSql = sqlite3_mprintf( - "REPLACE INTO temp.sqlite_parameters(key,value)" - "VALUES(%Q,%s);", zKey, zValue); - shell_check_oom(zSql); - pStmt = 0; - rx = sqlite3_prepare_v2(p->db, 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; - } - } - sqlite3_step(pStmt); - sqlite3_finalize(pStmt); - }else ++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 */ ++ if( nc>1 ) pParam->uses = (int)integerValue(pV[1]); ++ ++pParam->hits; ++ return 0; ++} + - /* .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]); - if( zSql==0 ) shell_out_of_memory(); - sqlite3_exec(p->db, zSql, 0, 0, 0); - sqlite3_free(zSql); - }else ++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); + - { /* If no command name and arg count matches, show a syntax error */ - showHelp(p->out, "parameter"); - return 1; ++/* 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 rc; ++ utf8_printf(STD_ERR, "Error: %s\n", zErr); ++ return 0; +} - /***************** - * The .print, .progress and .prompt commands - /* Parse the input line into tokens. - */ - while( zLine[h] && nArgdb, shellAuth, p); - }else if( p->bSafeModePersist ){ - sqlite3_set_authorizer(p->db, safeModeAuth, p); - }else{ - sqlite3_set_authorizer(p->db, 0, 0); - } - }else -#endif ++ 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); + -#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 ++ sqlite3_exec(db, "DETACH "PARAM_STORE_SCHEMA";", 0, 0, 0); ++ 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; jdb, 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; - } - close_db(pDest); - }else ++/* Default location of parameters store DB for .parameters save/load. */ ++static const char *zDefaultParamStore = "~/sqlite_params.sdb"; + - 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; - } - }else ++/* 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. ++ */ ++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; ++} + - if( c=='b' && n>=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 ++/* 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 1; ++ }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; ++ } ++} + - /* The undocumented ".breakpoint" command causes a call to the no-op - ** routine named test_breakpoint(). - */ - if( c=='b' && n>=3 && strncmp(azArg[0], "breakpoint", n)==0 ){ - test_breakpoint(); - }else ++/* 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 1; ++ }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; ++ } ++} + - 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 ++/* ++ * 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; ++ char * zVal = 0; ++ char * zSql = sqlite3_mprintf ++ ("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 ++ ("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 ++ ("UPDATE "PARAM_TABLE_SNAME" SET value=(SELECT %s) WHERE" ++ " key=%Q AND uses=%d", zVal, name, uses); + }else{ - raw_printf(stderr, "Usage: .cd DIRECTORY\n"); - rc = 1; ++ zSql = sqlite3_mprintf ++ ("UPDATE "PARAM_TABLE_SNAME" SET value=edit(value, %Q) WHERE" ++ " key=%Q AND uses=%d", zEditor, name, uses); + } - }else - - if( c=='c' && n>=3 && strncmp(azArg[0], "changes", n)==0 ){ - if( nArg==2 ){ - setOrClearFlag(p, SHFLG_CountChanges, azArg[1]); ++ }else{ ++ /* Editing a new value of same kind. */ ++ assert(paramVU.value==0 || paramVU.uses!=uses); ++ if( eval!=0 ){ ++ zSql = sqlite3_mprintf ++ ("SELECT edit('-- %q%s', %Q)", name, "\n", zEditor); ++ zVal = db_text(db, zSql, 1); ++ sqlite3_free(zSql); ++ zSql = sqlite3_mprintf ++ ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)" ++ " VALUES (%Q,(SELECT %s LIMIT 1),%d)", ++ name, zVal, uses); + }else{ - raw_printf(stderr, "Usage: .changes on|off\n"); - rc = 1; ++ zSql = sqlite3_mprintf ++ ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)" ++ " VALUES (%Q,edit('-- %q%s', %Q),%d)", ++ name, name, "\n", zEditor, uses); + } - }else ++ } ++ 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 + - /* 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. - */ - 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); - }else ++/* Space-join values in an argument list. *valLim is not included. */ ++char *values_join( char **valBeg, char **valLim ){ ++ char *z = 0; ++ const char *zSep = 0; ++ while( valBeg < valLim ){ ++ z = sqlite3_mprintf("%z%s%s", z, zSep, *valBeg); ++ zSep = " "; ++ ++valBeg; ++ } ++ return z; ++} ++ ++/* Get a named parameter value in form of stepped prepared statement, ++ * ready to have its value taken from the 0th column. If the name ++ * cannot be found for the given ParamTableUse, 0 is returned. ++ * The caller is responsible for calling sqlite3_finalize(pStmt), ++ * where pStmt is the return from this function. ++ */ ++static sqlite3_stmt *get_param_value(sqlite3 *db, char *name, ++ ParamTableUse ptu){ ++ sqlite3_stmt *rv = 0; ++ int rc; ++ char *zSql = sqlite3_mprintf ++ ( "SELECT value FROM "PARAM_TABLE_SNAME ++ " WHERE key=%Q AND uses=%d", name, ptu ); ++ shell_check_oom(zSql); ++ rc = sqlite3_prepare_v2(db, zSql, -1, &rv, 0); ++ sqlite3_free(zSql); ++ if( SQLITE_OK==rc ){ ++ if( SQLITE_ROW==sqlite3_step(rv) ) return rv; ++ sqlite3_finalize(rv); ++ } ++ return 0; ++} ++ ++static struct ParamSetOpts { ++ const char cCast; ++ const char *zTypename; ++ int evalKind; ++} param_set_opts[] = { ++ /* { 'q', 0, 2 }, */ ++ /* { 'x', 0, 1 }, */ ++ { 'i', "INT", 1 }, ++ { 'r', "REAL", 1 }, ++ { 'b', "BLOB", 1 }, ++ { 't', "TEXT", 0 }, ++ { 'n', "NUMERIC", 1 } ++}; + - 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; - } - }else ++/* Return an option character if it is single and prefixed by - or --, ++ * else return 0. ++ */ ++static char option_char(char *zArg){ ++ if( zArg[0]=='-' ){ ++ ++zArg; ++ if( zArg[0]=='-' ) ++zArg; ++ if( zArg[0]!=0 && zArg[1]==0 ) return zArg[0]; ++ } ++ return 0; ++} + - 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; ++/* The set subcommand (per help text) ++ */ ++static int param_set(sqlite3 *db, char cCast, ++ char *name, char **valBeg, char **valLim, ++ ParamTableUse ptu){ ++ char *zSql = 0; ++ int rc = SQLITE_OK, retries = 0, needsEval = 1; ++ char *zValGlom = (valLim-valBeg>1)? 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; + } - }else{ - raw_printf(stderr, "Usage: .connection [close] [CONNECTION-NUMBER]\n"); - rc = 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)); - rc = 1; ++ } ++ if( needsEval ){ ++ if( zCastTo!=0 ){ ++ zSql = sqlite3_mprintf ++ ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" ++ " VALUES(%Q,CAST((%s) AS %s),%d);", name, zValue, zCastTo, ptu ); + }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++; - } ++ zSql = sqlite3_mprintf ++ ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" ++ "VALUES(%Q,(%s),%d);", name, zValue, ptu ); + } - 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 ++ 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 ++ ( "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=='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); - } - 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"); ++/* list or ls subcommand for .parameter dot-command */ ++static void list_params(ShellState *p, ParamTableUse ptu, u8 bShort, ++ char **pzArgs, int nArg){ ++ sqlite3_stmt *pStmt = 0; ++ sqlite3_str *sbList = sqlite3_str_new(p->db); ++ int len = 0, rc; ++ char *zFromWhere = 0; ++ char *zSql = 0; ++ sqlite3_str_appendf(sbList, "FROM "PARAM_TABLE_SNAME ++ " WHERE (?1=3 OR uses=?1) AND "); ++ append_glob_terms(sbList, "key", ++ (const char **)pzArgs, (const char **)pzArgs+nArg); ++ zFromWhere = sqlite3_str_finish(sbList); ++ shell_check_oom(zFromWhere); ++ zSql = sqlite3_mprintf("SELECT max(length(key)) %s", zFromWhere); ++ shell_check_oom(zSql); ++ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); ++ if( rc==SQLITE_OK ){ ++ sqlite3_bind_int(pStmt, 1, ptu); ++ if( sqlite3_step(pStmt)==SQLITE_ROW ){ ++ len = sqlite3_column_int(pStmt, 0); ++ if( len>40 ) len = 40; ++ if( len<4 ) len = 4; + } - }else - - if( c=='d' && n>=3 && strncmp(azArg[0], "dbinfo", n)==0 ){ - rc = shell_dbinfo_command(p, nArg, azArg); - }else - -#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) */ - - if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){ - char *zLike = 0; - char *zSchema = "main"; - 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; idb, 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(p->out, "Binding Values:\n%-*s %s\n", ++ len, "name", "value"); ++ } ++ utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0), ++ sqlite3_column_text(pStmt,2)); ++ break; ++ case PTU_Script: ++ if( nScripts++ == 0 ){ ++ utf8_printf(p->out, "Scripts\n%-*s %s\n", len, "name", "value"); ++ } ++ utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0), ++ sqlite3_column_text(pStmt,2)); ++ break; ++ default: break; /* Ignore */ + } + } - } - - 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 %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(p,zSql); - sqlite3_free(zSql); - if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ - zSql = sqlite3_mprintf( - "SELECT sql FROM %w.sqlite_schema AS o " - "WHERE (%s) AND sql NOT NULL" - " AND type IN ('index','trigger','view')", - zSchema, 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]); + }else{ - raw_printf(stderr, "Usage: .echo on|off\n"); - rc = 1; - } - }else - - 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]); ++ int nc = 0, ncw = 78/(len+2); ++ zSql = sqlite3_mprintf("SELECT key %z ORDER BY key", zFromWhere); ++ shell_check_oom(zSql); ++ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); ++ sqlite3_bind_int(pStmt, 1, ptu); ++ while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ ++ utf8_printf(p->out, "%s %-*s", ((++nc%ncw==0)? "\n" : ""), ++ len, sqlite3_column_text(pStmt,0)); + } - }else{ - raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n"); - rc = 1; ++ if( nc>0 ) utf8_printf(p->out, "\n"); + } - }else - - if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){ - if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc); - rc = 2; - }else ++ sqlite3_finalize(pStmt); ++ }else{ ++ sqlite3_free(zFromWhere); ++ } ++ sqlite3_free(zSql); ++} + - /* 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]); - } ++/* 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( azBegmode!=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; ++ sqlite3_str_appendf(pStr, ")"); ++ } ++} ++ ++/* Append either an IN clause or an always true test to some SQL. ++ * ++ * An empty IN list is the same as always true (for non-NULL LHS) ++ * for this clause, which assumes a trailing LHS operand and space. ++ * If that is not the right result, guard the call against it. ++ * This is used for ".parameter dostuff ?NAMES?" options, ++ * where a missing list means all the qualifying entries. ++ * ++ * The empty list may be signified by azBeg and azLim both 0. ++ */ ++static void append_in_clause(sqlite3_str *pStr, ++ const char **azBeg, const char **azLim){ ++ if( azBeg==azLim ) sqlite3_str_appendf(pStr, "NOT NULL"); ++ else{ ++ char cSep = '('; ++ sqlite3_str_appendf(pStr, "IN"); ++ while( azBegbSafeMode ){ - 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 ++/***************** ++ * 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 or -e to use edited value as text or evaluate it first.", + #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; the value is following argument list, space-joined.", ++ " 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); + - 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; - - 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; idb) && (nArg>2 || azArg[1][0]=='c') ){ ++ sqlite3_str *sbZap = sqlite3_str_new(p->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(p->db, zSql, 0, 0, 0); ++ sqlite3_free(zSql); + } - - /* The argument can optionally begin with "-" or "--" */ - if( zCmd[0]=='-' && zCmd[1] ){ - zCmd++; - if( zCmd[0]=='-' && zCmd[1] ) zCmd++; ++ }else ++#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 ){ ++ int ia = 2; ++ int eval = 0; ++ if( !INSOURCE_IS_INTERACTIVE(p->pInSource) ){ ++ utf8_printf(STD_ERR, "Error: " ++ ".parameter edit can only be used interactively.\n"); ++ return 1; + } - - /* --help lists all file-controls */ - if( strcmp(zCmd,"help")==0 ){ - utf8_printf(p->out, "Available file-controls:\n"); - for(i=0; iout, " .filectrl %s %s\n", - aCtrl[i].zCtrlName, aCtrl[i].zUsage); ++ param_table_init(p); ++ if( p->zEditor==0 ){ ++ const char *zE = getenv("VISUAL"); ++ if( zE!=0 ) p->zEditor = sqlite3_mprintf("%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(p->zEditor); ++ /* Accept an initial -editor=? option. */ ++ p->zEditor = sqlite3_mprintf("%s", zArg+7); ++ ++ia; + } - rc = 1; - goto meta_command_exit; + } - - /* convert filectrl text option to value. allow any unique prefix - ** of the option name, or a numerical value. */ - n2 = strlen30(zCmd); - for(i=0; izEditor==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 1; + } - if( filectrl<0 ){ - utf8_printf(stderr,"Error: unknown file-control: %s\n" - "Use \".filectrl --help\" for help\n", zCmd); - }else{ - switch(filectrl){ - case SQLITE_FCNTL_SIZE_LIMIT: { - if( nArg!=2 && nArg!=3 ) break; - iRes = nArg==3 ? integerValue(azArg[2]) : -1; - sqlite3_file_control(p->db, 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; ++ /* 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 1; + } + } - } - 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); ++ 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 1; ++ } ++ rc = edit_one_param(p->db, azArg[ia], eval, ptu, p->zEditor); ++ ++ia; ++ if( rc!=0 ) return rc; + } + }else ++#endif + - 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 - ); - 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"); - } ++ /* .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(p); + }else + - 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; - } ++ /* .parameter list|ls ++ ** List all or selected bind parameters. ++ ** list displays names, values and uses. ++ ** ls displays just the names. ++ */ ++ 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=='h' && strncmp(azArg[0], "help", n)==0 ){ - if( nArg>=2 ){ - if( nArg==3 - && strcmp(azArg[1], zHelpAll)==0 && strcmp(azArg[2], zHelpAll)==0 ){ - /* Show the undocumented command help */ - n = showHelp(p->out, zHelpAll); - }else{ - /* Show such help as the pattern selects */ - n = showHelp(p->out, azArg[1]); - } - if( n==0 ){ - utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]); - } - }else{ - /* Show one-line summaries */ - showHelp(p->out, 0); - } ++ /* .parameter load ++ ** Load all or named parameters from specified or default (DB) file. ++ */ ++ if( strcmp(azArg[1],"load")==0 ){ ++ param_table_init(p); ++ rc = parameters_load(p->db, (const char **)azArg+1, nArg-1); + }else + - if( c=='i' && strncmp(azArg[0], "import", n)==0 ){ - char *zTable = 0; /* Insert data into this table */ - char *zSchema = "main"; /* within this schema */ - 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 */ - 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 */ - - 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; - } - sCtx.cColSep = p->colSeparator[0]; - sCtx.cRowSep = p->rowSeparator[0]; - } - 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( sCtx.in==0 ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile); - rc = 1; - import_cleanup(&sCtx); - goto meta_command_exit; - } - /* 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(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"); - } - while( (nSkip--)>0 ){ - while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){} - } - zSql = sqlite3_mprintf("SELECT * FROM \"%w\".\"%w\"", zSchema, zTable); - if( zSql==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 ){ - char *zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"", - zSchema, zTable); - 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 ){ - utf8_printf(INSOURCE_IS_INTERACTIVE(p->pInSource)? 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 ){ - sqlite3_free(zCreate); - import_cleanup(&sCtx); - utf8_printf(stderr,"%s: empty file\n", sCtx.zFile); - 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)); - sqlite3_free(zCreate); - import_cleanup(&sCtx); - rc = 1; - goto meta_command_exit; - } - sqlite3_free(zCreate); - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - } - sqlite3_free(zSql); - if( rc ){ - if (pStmt) sqlite3_finalize(pStmt); - utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db)); - import_cleanup(&sCtx); - rc = 1; - goto meta_command_exit; - } - 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 \"%w\".\"%w\" VALUES(?", - zSchema, zTable); - 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); - sqlite3_free(zSql); - if( rc ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); - if (pStmt) sqlite3_finalize(pStmt); - import_cleanup(&sCtx); - rc = 1; - goto meta_command_exit; - } - 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( 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++; - }else{ - sCtx.nRow++; - } - } - }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); - } ++ /* .parameter save ++ ** Save all or named parameters into specified or default (DB) file. ++ */ ++ if( strcmp(azArg[1],"save")==0 ){ ++ rc = parameters_save(p->db, (const char **)azArg+1, nArg-1); + }else + -#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] - ); - 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" - ); - } - }else{ - raw_printf(stderr, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); ++ /* .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; - } - sqlite3_free(zSql); - }else -#endif /* !defined(SQLITE_OMIT_TEST_CONTROL) */ - -#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; ++ param_table_init(p); ++ rc = param_set(p->db, cCast, azArg[inv], ++ &azArg[inv+1], &azArg[nArg], ptu); ++ if( rc!=SQLITE_OK ){ ++ utf8_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db)); + rc = 1; - }else{ - sqlite3IoTrace = iotracePrintf; + } + } + }else -#endif + - 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)); ++ { /* If no command name and arg count matches, show a syntax error */ ++ showHelp(p->out, "parameter"); ++ return 1; ++ } ++ ++ return rc; ++} ++ ++/***************** ++ * The .print, .progress and .prompt commands ++ */ ++CONDITION_COMMAND( progress !defined(SQLITE_OMIT_PROGRESS_CALLBACK) ); +COLLECT_HELP_TEXT[ + ".print STRING... Print literal STRING", + ".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", + ".prompt MAIN CONTINUE Replace the standard prompts", +]; +DISPATCHABLE_COMMAND( print 3 1 0 ){ + int i; + for(i=1; i1 ) raw_printf(p->out, " "); + utf8_printf(p->out, "%s", azArg[i]); + } + raw_printf(p->out, "\n"); + return 0; +} +DISPATCHABLE_COMMAND( progress 3 2 0 ){ + int i; + int nn = 0; + p->flgProgress = 0; + p->mxProgress = 0; + p->nProgress = 0; + for(i=1; iflgProgress |= SHELL_PROGRESS_QUIET; + continue; } - }else if( nArg>3 ){ - raw_printf(stderr, "Usage: .limit NAME ?NEW-VALUE?\n"); - rc = 1; - goto meta_command_exit; - }else{ - int iLimit = -1; - n2 = strlen30(azArg[1]); - for(i=0; iflgProgress |= SHELL_PROGRESS_RESET; + continue; } - if( iLimit<0 ){ - utf8_printf(stderr, "unknown limit: \"%s\"\n" - "enter \".limits\" with no arguments for a list.\n", - azArg[1]); - rc = 1; - goto meta_command_exit; + if( strcmp(z,"once")==0 ){ + p->flgProgress |= SHELL_PROGRESS_ONCE; + continue; } - if( nArg==3 ){ - sqlite3_limit(p->db, aLimit[iLimit].limitCode, - (int)integerValue(azArg[2])); + if( strcmp(z,"limit")==0 ){ + if( i+1>=nArg ){ + *pzErr = shellMPrintf(0,"Error: missing argument on --limit\n"); + return SHELL_INVALID_ARGS; + }else{ + p->mxProgress = (int)integerValue(azArg[++i]); + } + continue; } - printf("%20s %d\n", aLimit[iLimit].zLimitName, - sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1)); + *pzErr = shellMPrintf(0,"Error: unknown option: \"%s\"\n", azArg[i]); + return SHELL_INVALID_ARGS; + }else{ + nn = (int)integerValue(z); } - }else - - if( c=='l' && n>2 && strncmp(azArg[0], "lint", n)==0 ){ - open_db(p, 0); - lintDotCommand(p, azArg, nArg); - }else + } + open_db(p, 0); + sqlite3_progress_handler(p->db, nn, progress_handler, p); + return 0; +} +/* 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 0; +} -#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; - } - 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); +/***************** + * 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; - FILE *inSaved = p->in; - int savedLineno = p->lineno; ++ FILE *inUse = 0; ++ int (*fCloser)(FILE *) = 0; + if( p->bSafeMode ) return SHELL_FORBIDDEN_OP; + if( azArg[1][0]=='|' ){ +#ifdef SQLITE_OMIT_POPEN + *pzErr = shellMPrintf(0,"Error: pipes are not supported in this OS\n"); + rc = 1; - p->out = STD_OUT; ++ p->out = STD_OUT; /* This is likely not needed. To be investigated. */ +#else - p->in = popen(azArg[1]+1, "r"); - if( p->in==0 ){ - *pzErr = shellMPrintf(0,"Error: cannot open \"%s\"\n", azArg[1]); ++ inUse = popen(azArg[1]+1, "r"); ++ if( inUse==0 ){ ++ utf8_printf(STD_ERR, "Error: cannot open \"%s\"\n", azArg[1]); rc = 1; + }else{ - rc = process_input(p); - pclose(p->in); ++ fCloser = pclose; } - }else #endif - }else if( (p->in = openChrSource(azArg[1]))==0 ){ ++ }else if( (inUse = openChrSource(azArg[1]))==0 ){ + *pzErr = shellMPrintf(0,"Error: cannot open \"%s\"\n", azArg[1]); + rc = 1; + }else{ ++ fCloser = fclose; ++ } ++ if( inUse!=0 ){ ++ InSource inSourceRedir ++ = INSOURCE_FILE_REDIR(inUse, azArg[1], p->pInSource); ++ p->pInSource = &inSourceRedir; + rc = process_input(p); - fclose(p->in); ++ assert(fCloser!=0); ++ fCloser(inUse); ++ p->pInSource = p->pInSource->pFrom; + } - p->in = inSaved; - p->lineno = savedLineno; + return rc; +} - 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; - }else{ - const char *zFile = azArg[1]; - output_file_close(p->pLog); - p->pLog = output_file_open(zFile, 0); - } - }else +/* +** 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 ){ + open_db(p, 0); + 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=='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; imode==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]; + int bFreelist = 1; /* 0 if --freelist-corrupt is specified */ + int bRowids = 1; /* 0 if --no-rowids */ + for(i=1; imode = 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; + else{ - *pzErr = shellMPrintf(0,"unexpected option: %s\n", azArg[i]); ++ *pzErr = shellMPrintf(0,"unexpected option: %s\n", azArg[i]); + showHelp(p->out, azArg[0]); + return 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->pInSource->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 */ + shellExecPrintf(p->db, &rc, - /* Attach an in-memory database named 'recovery'. Create an indexed ++ /* 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( bFreelist ){ + shellExec(p->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 this is an auto-vacuum database, add all pointer-map pages to - ** the freelist table. Do this regardless of whether or not ++ ** the freelist table. Do this regardless of whether or not + ** --freelist-corrupt was specified. */ - shellExec(p->db, &rc, ++ shellExec(p->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(p->db, &rc, ++ shellExec(p->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 ++ ** 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" + ");" + + /* 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_%" ++ /* 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(p->out, "PRAGMA foreign_keys=OFF;\n"); + raw_printf(p->out, "BEGIN;\n"); + raw_printf(p->out, "PRAGMA writable_schema = on;\n"); + shellPrepare(p->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(p->out, "CREATE TABLE IF NOT EXISTS %s;\n", ++ raw_printf(p->out, "CREATE TABLE IF NOT EXISTS %s;\n", + &zCreateTable[12] + ); } - }else + shellFinalize(&rc, pStmt); + } - if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 ){ - if( nArg==2 ){ - sqlite3_snprintf(sizeof(p->nullValue), p->nullValue, - "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]); - }else{ - raw_printf(stderr, "Usage: .nullvalue STRING\n"); - rc = 1; - } - }else + /* Figure out if an orphan table will be required. And if so, how many + ** user columns it should contain */ - shellPrepare(p->db, &rc, ++ shellPrepare(p->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=='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 */ - u8 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; - } - } + shellPrepare(p->db, &rc, + "SELECT pgno FROM recovery.map WHERE root=?", &pPages + ); - /* 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"); - } - if( zFN ){ - zNewFilename = sqlite3_mprintf("%s", zFN); - shell_check_oom(zNewFilename); - }else{ - zNewFilename = 0; - } - 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; - } - } - if( p->db==0 ){ - /* As a fall-back open a TEMP database */ - p->pAuxDb->zDbFilename = 0; - open_db(p, 0); - } - }else + shellPrepare(p->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 + ); - 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 */ + /* Loop through each root page. */ - shellPrepare(p->db, &rc, - "SELECT root, intkey, max(maxlen) FROM recovery.map" ++ shellPrepare(p->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; - 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; + assert( bIntkey==0 || bIntkey==1 ); + pTab = recoverFindTable(p, &rc, iRoot, bIntkey, nCol, &bNoop); + if( bNoop || rc ) continue; + if( pTab==0 ){ + if( pOrphan==0 ){ + pOrphan = recoverOrphanTable(p, &rc, zLostAndFound, nOrphan); } + pTab = pOrphan; + if( pTab==0 ) break; } - if( zFile==0 ){ - zFile = sqlite3_mprintf("stdout"); - } - if( bOnce ){ - p->outCount = 2; - }else{ - p->outCount = 0; - } - 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); + + if( 0==sqlite3_stricmp(pTab->zQuoted, "\"sqlite_sequence\"") ){ + raw_printf(p->out, "DELETE FROM sqlite_sequence;\n"); } -#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 + sqlite3_bind_int(pPages, 1, iRoot); + if( bRowids==0 && pTab->iPk<0 ){ + sqlite3_bind_int(pCells, 1, 1); }else{ - p->out = 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); - } + sqlite3_bind_int(pCells, 1, 0); } - sqlite3_free(zFile); - }else - - if( c=='p' && n>=2 && strncmp(azArg[0], "parameter", n)==0 ){ - open_db(p,0); - if( nArg<=1 ) goto parameter_syntax_error; + sqlite3_bind_int(pCells, 3, pTab->iPk); - /* .parameter clear and .parameter unset ?NAMES? - ** Delete some or all bind parameters from the TEMP table that holds them. - ** Without any arguments, clear deletes them all and unset does nothing. - */ - if( strcmp(azArg[1],"clear")==0 || strcmp(azArg[1],"unset")==0 ){ - if( param_table_exists(p->db) && (nArg>2 || azArg[1][0]=='c') ){ - sqlite3_str *sbZap = sqlite3_str_new(p->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(p->db, zSql, 0, 0, 0); - sqlite3_free(zSql); - } - }else + 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); -#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 ){ - int ia = 2; - int eval = 0; - if( !INSOURCE_IS_INTERACTIVE(p->pInSource) ){ - utf8_printf(stderr, "Error: " - ".parameter edit can only be used interactively.\n"); - rc = 1; - goto meta_command_exit; - } - param_table_init(p); - if( p->zEditor==0 ){ - const char *zE = getenv("VISUAL"); - if( zE!=0 ) p->zEditor = sqlite3_mprintf("%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(p->zEditor); - /* Accept an initial -editor=? option. */ - p->zEditor = sqlite3_mprintf("%s", zArg+7); - ++ia; - } - } - if( p->zEditor==0 ){ - /* This is klutzy, but edit is for interactive use. So this - * problem, due to not heeding the .parameter doc, can be - * fixed with a modest inconvenience to the casual user. - */ - utf8_printf(stderr, - "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"); - rc = 1; - goto meta_command_exit; - } - /* 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(stderr, "Error: bad .parameter name: %s\n", - azArg[--ia]); - rc = 1; - goto meta_command_exit; + RecoverTable *pTab2 = pTab; + if( pTab!=pOrphan && (iMin<0)!=bIntkey ){ + if( pOrphan==0 ){ + pOrphan = recoverOrphanTable(p, &rc, zLostAndFound, nOrphan); } + pTab2 = pOrphan; + if( pTab2==0 ) break; } - ptu = classify_param_name(azArg[ia]); - if( ptu==PTU_Nil ){ - utf8_printf(stderr, "Error: %s cannot be a binding or executable" - " parameter name.\n", azArg[ia]); - rc = 1; - goto meta_command_exit; - } - rc = edit_one_param(p->db, azArg[ia], eval, ptu, p->zEditor); - ++ia; - if( rc!=0 ) goto meta_command_exit; - } - }else -#endif - /* .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(p); - }else - - /* .parameter list|ls - ** List all or selected bind parameters. - ** list displays names, values and uses. - ** ls displays just the names. - */ - 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 - - /* .parameter load - ** Load all or named parameters from specified or default (DB) file. - */ - if( strcmp(azArg[1],"load")==0 ){ - param_table_init(p); - rc = parameters_load(p->db, (const char **)azArg+1, nArg-1); - }else - - /* .parameter save - ** Save all or named parameters into specified or default (DB) file. - */ - if( strcmp(azArg[1],"save")==0 ){ - rc = parameters_save(p->db, (const char **)azArg+1, nArg-1); - }else - - /* .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(stderr, - "Error: %s is not a usable parameter name.\n", azArg[inv]); - rc = 1; - }else{ - param_table_init(p); - rc = param_set(p->db, cCast, azArg[inv], - &azArg[inv+1], &azArg[nArg], ptu); - if( rc!=SQLITE_OK ){ - utf8_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db)); - rc = 1; - } - } - }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]); - } - 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( 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; + nField = nField+1; + if( pTab2==pOrphan ){ - raw_printf(p->out, ++ raw_printf(p->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(p->out, "INSERT INTO %s(%s) VALUES( %s );\n", ++ raw_printf(p->out, "INSERT INTO %s(%s) VALUES( %s );\n", + pTab2->zQuoted, pTab2->azlCol[nField], zVal + ); } - utf8_printf(stderr, "Error: unknown option: \"%s\"\n", azArg[i]); - rc = 1; - goto meta_command_exit; - }else{ - nn = (int)integerValue(z); } + shellReset(&rc, pCells); } - 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 + shellReset(&rc, pPages); + if( pTab!=pOrphan ) recoverFreeTable(pTab); + } + shellFinalize(&rc, pLoop); + shellFinalize(&rc, pPages); + shellFinalize(&rc, pCells); + recoverFreeTable(pOrphan); - if( c=='r' && n>=3 && strncmp(azArg[0], "read", n)==0 ){ - FILE *inUse = 0; - int (*fCloser)(FILE *) = 0; - 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; /* This is likely not needed. To be investigated. */ -#else - inUse = popen(azArg[1]+1, "r"); - if( inUse==0 ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]); - rc = 1; + /* The rest of the schema */ + if( rc==SQLITE_OK ){ + sqlite3_stmt *pStmt = 0; - shellPrepare(p->db, &rc, ++ shellPrepare(p->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, ++ char *zPrint = shellMPrintf(&rc, + "INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)", + zName, zName, zSql + ); + raw_printf(p->out, "%s;\n", zPrint); + sqlite3_free(zPrint); }else{ - fCloser = pclose; + raw_printf(p->out, "%s;\n", zSql); } -#endif - }else if( (inUse = openChrSource(azArg[1]))==0 ){ - utf8_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); - rc = 1; - }else{ - fCloser = fclose; } - if( inUse!=0 ){ - InSource inSourceRedir - = INSOURCE_FILE_REDIR(inUse, azArg[1], p->pInSource); - p->pInSource = &inSourceRedir; - rc = process_input(p); - assert(fCloser!=0); - fCloser(inUse); - p->pInSource = p->pInSource->pFrom; - } - }else + shellFinalize(&rc, pStmt); + } - 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]; - }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; - } - 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; - } - while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK - || rc==SQLITE_BUSY ){ - if( rc==SQLITE_BUSY ){ - if( nTimeout++ >= 3 ) break; - sqlite3_sleep(100); - } - } - 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; - } + if( rc==SQLITE_OK ){ + raw_printf(p->out, "PRAGMA writable_schema = off;\n"); + raw_printf(p->out, "COMMIT;\n"); + } + sqlite3_exec(p->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( p->bSafeMode ) return SHELL_FORBIDDEN_OP; + if( nArg==2 ){ + zSrcFile = azArg[1]; + zDb = "main"; + }else if( nArg==3 ){ + zSrcFile = azArg[2]; + zDb = azArg[1]; + }else{ + return SHELL_INVALID_ARGS; + } + rc = sqlite3_open(zSrcFile, &pSrc); + if( rc!=SQLITE_OK ){ + *pzErr = shellMPrintf(0,"Error: cannot open \"%s\"\n", zSrcFile); close_db(pSrc); - }else + return 1; + } + open_db(p, 0); + pBackup = sqlite3_backup_init(p->db, zDb, pSrc, "main"); + if( pBackup==0 ){ + *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db)); + close_db(pSrc); + return 1; + } + while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK + || rc==SQLITE_BUSY ){ + if( rc==SQLITE_BUSY ){ + if( nTimeout++ >= 3 ) break; + sqlite3_sleep(100); + } + } + 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"); + rc = 1; + }else{ + *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db)); + rc = 1; + } + close_db(pSrc); + return rc; +} - if( c=='s' && strncmp(azArg[0], "scanstats", n)==0 ){ - if( nArg==2 ){ - p->scanstatsOn = (u8)booleanValue(azArg[1]); +/***************** + * 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 ){ + p->scanstatsOn = (u8)booleanValue(azArg[1]); #ifndef SQLITE_ENABLE_STMT_SCANSTATUS - raw_printf(stderr, "Warning: .scanstats not available in this build.\n"); + raw_printf(STD_ERR, "Warning: .scanstats not available in this build.\n"); #endif + return 0; +} +DISPATCHABLE_COMMAND( schema ? 1 2 ){ + int rc; + 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; + + 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 ){ + *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db)); + sqlite3_finalize(pStmt); + return 1; } - }else - - 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; - - 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, "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, '\''); - appendText(&sSelect, " AS sname FROM ", 0); - appendText(&sSelect, zDb, quoteChar(zDb)); - appendText(&sSelect, ".sqlite_schema", 0); + }else{ + appendText(&sSelect, "NULL", 0); } - sqlite3_finalize(pStmt); + 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); + if( zName ){ + appendText(&sSelect, + " UNION ALL SELECT shell_module_schema(name)," - " 'table', name, name, name, 9e+99, 'main' FROM pragma_module_list", ++ " '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{ - rc = sqlite3_exec(p->db, sSelect.z, callback, &data, &zErrMsg); + appendText(&sSelect, "lower(tbl_name)", 0); + } + appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0); + appendText(&sSelect, zQarg, 0); + if( !bGlob ){ + appendText(&sSelect, " ESCAPE '\\' ", 0); } - freeText(&sSelect); + appendText(&sSelect, " AND ", 0); + sqlite3_free(zQarg); } - 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; + 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 = 0; + rc = sqlite3_exec(p->db, sSelect.z, callback, &data, &zErrMsg); } - }else + freeText(&sSelect); + } + if( zErrMsg ){ + *pzErr = zErrMsg; + rc = 1; + }else if( rc != SQLITE_OK ){ + *pzErr = shellMPrintf(0,"Error: querying schema information\n"); + rc = 1; + }else{ + rc = 0; + } + return rc; +} - if( c=='s' && n==7 && strncmp(azArg[0], "seeargs", n)==0 ){ - for( n=1; nout, "%s%s", azArg[n], (n==nArg-1)? "|\n" : "|"); +/***************** + * 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 0; +} +DISPATCHABLE_COMMAND( separator ? 2 3 ){ + 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]); + } + return 0; +} +DISPATCHABLE_COMMAND( session 3 2 0 ){ + int rc = 0; + struct AuxDb *pAuxDb = p->pAuxDb; + OpenSession *pSession = &pAuxDb->aSession[0]; + 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 - - 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 + } -#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; + /* .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{ + rc = sqlite3session_attach(pSession->p, azCmd[1]); + if( rc ){ + raw_printf(STD_ERR, "ERROR: sqlite3session_attach() returns %d\n", rc); + rc = 0; } } + }else - /* .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(stderr, "ERROR: No sessions are open\n"); - }else{ - rc = sqlite3session_attach(pSession->p, azCmd[1]); - if( rc ){ - raw_printf(stderr, "ERROR: sqlite3session_attach() returns %d\n", rc); - rc = 0; - } - } - }else - -- /* .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; - if( failIfSafeMode - (p, "cannot run \".session %s\" in safe mode", azCmd[0]) ){ - rc = SHELL_FORBIDDEN_OP; - failIfSafeMode(p, "cannot run \".session %s\" in safe mode", azCmd[0]); ++ /* .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; ++ if( failIfSafeMode ++ (p, "cannot run \".session %s\" in safe mode", azCmd[0]) ){ ++ rc = SHELL_FORBIDDEN_OP; ++ }else{ + 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]); ++ *pzErr = sqlite3_mprintf ++ ("ERROR: cannot open \"%s\" for writing\n", azCmd[1]); ++ rc = 1; }else{ - if( nCmd!=2 ) goto session_syntax_error; - if( pSession->p==0 ) goto session_not_open; - out = fopen(azCmd[1], "wb"); - if( out==0 ){ - *pzErr = sqlite3_mprintf - ("ERROR: cannot open \"%s\" for writing\n", azCmd[1]); - rc = 1; + int szChng; + void *pChng; + if( azCmd[0][0]=='c' ){ + rc = sqlite3session_changeset(pSession->p, &szChng, &pChng); }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 ){ - fprintf(STD_OUT, "Error: error code %d\n", rc); - rc = 0; - } - if( pChng && fwrite(pChng, szChng, 1, out)!=1 ){ - raw_printf(STD_ERR, "ERROR: Failed to write entire %d-byte output\n", - szChng); - } - sqlite3_free(pChng); - fclose(out); + rc = sqlite3session_patchset(pSession->p, &szChng, &pChng); + } + if( rc ){ - printf("Error: error code %d\n", rc); ++ fprintf(STD_OUT, "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); ++ if( pChng && fwrite(pChng, szChng, 1, out)!=1 ){ ++ raw_printf(STD_ERR, "ERROR: Failed to write entire %d-byte output\n", ++ szChng); + } + sqlite3_free(pChng); + fclose(out); } - }else - - /* .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 ++ } ++ }else - /* .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 - /* .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); - } - }else ++ /* .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 - /* .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); - } - }else - /* .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_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); - } - pSession->nFilter = ii-1; - } - }else ++ /* .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); ++ } ++ }else - /* .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_free(pSession->azFilter); - nByte = sizeof(pSession->azFilter[0])*(nCmd-1); - pSession->azFilter = sqlite3_malloc( nByte ); - if( pSession->azFilter==0 ){ - shell_out_of_memory(); - } - for(ii=1; iiazFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]); - shell_check_oom(pSession->azFilter[ii-1]); - } - pSession->nFilter = ii-1; - } - }else - /* .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); ++ /* .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]); + } - }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(p->out, "session %s isempty flag = %d\n", - pSession->zName, ii); ++ sqlite3_free(pSession->azFilter); ++ nByte = sizeof(pSession->azFilter[0])*(nCmd-1); ++ pSession->azFilter = sqlite3_malloc( nByte ); ++ if( pSession->azFilter==0 ){ ++ shell_out_of_memory(); + } - }else - - /* .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); ++ for(ii=1; iiazFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]); ++ shell_check_oom(pSession->azFilter[ii-1]); + } - }else ++ pSession->nFilter = ii-1; ++ } ++ }else - /* .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); - } - }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(p->out, "session %s isempty flag = %d\n", - pSession->zName, ii); - } - }else - - /* .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 - - /* .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(p->db, azCmd[1], &pSession->p); - if( rc ){ - *pzErr = sqlite3_mprintf - ("Cannot open session: error code=%d\n", rc); - return rc; - } - pSession->nFilter = 0; - sqlite3session_table_filter(pSession->p, session_filter, - pSession); - pAuxDb->nSession++; - shell_newstr_assign(&pSession->zName, - sqlite3_mprintf("%s", zName)); - }else{ - /* If no command name matches, show a syntax error */ - session_syntax_error: - showHelp(p->out, "session"); - return 1; - } - /* .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; - } - } - 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; - } - 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"); ++ /* .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); ++ } + }else -#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); - } ++ /* .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); + } - if( strncmp(azArg[0]+9, "integer", n-9)==0 ){ - int i; sqlite3_int64 v; - for(i=1; iout, "%s", zBuf); - } ++ }else ++ ++ /* .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 -#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 */ ++ /* .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(p->db, azCmd[1], &pSession->p); ++ if( rc ){ ++ *pzErr = sqlite3_mprintf ++ ("Cannot open session: error code=%d\n", rc); ++ return rc; ++ } ++ pSession->nFilter = 0; ++ sqlite3session_table_filter(pSession->p, session_filter, pSession); ++ pAuxDb->nSession++; ++ shell_newstr_assign(&pSession->zName, sqlite3_mprintf("%s", zName)); ++ }else{ + - open_db(p,0); - for(i=1; iout, "session"); ++ return 1; ++ } + 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; i1" + " 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); + } + shell_check_oom(zSql); + freeText(&sQuery); + freeText(&sSql); + if( bDebug ){ + utf8_printf(p->out, "%s\n", zSql); + }else{ + shell_exec(p, zSql, 0); + } + sqlite3_free(zSql); + return 0; +} + +/***************** - * The .selftest, .shell, .show, .stats and .system commands ++ * The .selftest, .shell, .show, .shxopts, .stats and .system commands + */ +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", + ".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", +]; +DISPATCHABLE_COMMAND( selftest 4 0 0 ){ + int rc; + 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; idb,"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); - } - 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; + *pzErr = sqlite3_mprintf + ("Unknown option \"%s\" on \"%s\"\n" + "Should be one of: --init -v\n", azArg[i], azArg[0]); + return 1; } - } /* 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( 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); } - if( nArg>=3 ){ - sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, - "%.*s", (int)ArraySize(p->rowSeparator)-1, azArg[2]); + if( rc ){ + *pzErr = shellMPrintf(0,"Error querying the selftest table\n"); + sqlite3_finalize(pStmt); + return 1; } - }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]); + 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 ){ + fprintf(STD_OUT, "%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 - { - *pzErr = sqlite3_mprintf - ("Unknown operation \"%s\" on selftest line %d\n", zOp, tno); - rc = 1; - break; - } ++ }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; - goto meta_command_exit; ++ utf8_printf(p->out, "%d: Expected: [%s]\n", tno, zAns); ++ utf8_printf(p->out, "%d: Got: [%s]\n", tno, str.z); + } - }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; ++ *pzErr = sqlite3_mprintf ++ ("Unknown operation \"%s\" on selftest line %d\n", zOp, tno); ++ rc = 1; ++ break; + } - } - 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 = "),("; - } + } /* End loop over rows of content from SELFTEST */ 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); - } - shell_check_oom(zSql); - freeText(&sQuery); - freeText(&sSql); - if( bDebug ){ - utf8_printf(p->out, "%s\n", zSql); - }else{ - shell_exec(p, zSql, 0); - } - sqlite3_free(zSql); - }else + } /* End loop over k */ + freeText(&str); + utf8_printf(p->out, "%d errors out of %d tests\n", nErr, nTest); + return rc > 0; +} #ifndef SQLITE_NOHAVE_SYSTEM - if( c=='s' - && (strncmp(azArg[0], "shell", n)==0 || strncmp(azArg[0],"system",n)==0) - ){ - char *zCmd; - int i, x; - failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]); - if( nArg<2 ){ - raw_printf(stderr, "Usage: .system COMMAND\n"); - rc = 1; - goto meta_command_exit; - } - zCmd = sqlite3_mprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]); - for(i=2; iout, "%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 SHELL_EXTENSIONS - if( c=='s' && strncmp(azArg[0], "shxopts", n)==0 ){ - rc = shxoptsCommand(azArg, nArg, p, 0); - }else +static int shellOut(char *azArg[], int nArg, ShellState *p, char **pzErr){ + char *zCmd; + int i, x; + if( p->bSafeMode ) return SHELL_FORBIDDEN_OP; + zCmd = sqlite3_mprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]); + shell_check_oom(zCmd); + for(i=2; istatsOn = 2; - }else if( strcmp(azArg[1],"vmstep")==0 ){ - p->statsOn = 3; - }else{ - p->statsOn = (u8)booleanValue(azArg[1]); +DISPATCHABLE_COMMAND( shell ? 2 0 ){ + return shellOut(azArg, nArg, p, pzErr); +} ++DISPATCHABLE_COMMAND( shxopts 3 0 0 ){ ++ static struct { const char *name; u8 mask; } shopts[] = { ++#if SHELL_DYNAMIC_COMMANDS ++ {"dyn_cmds", 1<1 ){ ++ for( ia=1; iadb, p, 0); ++ for( io=0; iobExtendedDotCmds |= shopts[io].mask; ++ else p->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(p->out, ++ " name value \"-shxopts set\"\n" ++ " -------- ----- ---------------\n"); ++ for( io=0; iobExtendedDotCmds & m) == m)? 1 : 0; ++ raw_printf(p->out, ++ " %9s %2d \"-shxopts 0x%02X\"\n", ++ shopts[io].name, v, m); ++ } ++ } ++ return 0; ++ moan_error: ++ raw_printf(STD_ERR, "Error: %s %s\n", zAbout, zMoan); ++ return 1; ++} +DISPATCHABLE_COMMAND( system ? 2 0 ){ + return shellOut(azArg, nArg, p, pzErr); +} +DISPATCHABLE_COMMAND( show ? 1 1 ){ + static const char *azBool[] = { "off", "on", "trigger", "full"}; + const char *zOut; + int i; + utf8_printf(p->out, "%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]); + zOut = modeDescr[p->mode].zModeName; + i = strlen30(zOut) - modeDescr[p->mode].bDepluralize; - if( p->mode==MODE_Column - || (p->mode>=MODE_Markdown && p->mode<=MODE_Box) - ){ ++ if( MODE_IS_COLUMNAR(p->mode) ){ + utf8_printf + (p->out, "%12.12s: %.*s --wrap %d --wordwrap %s --%squote\n", "mode", + i, zOut, p->cmOpts.iWrap, + p->cmOpts.bWordWrap ? "on" : "off", + p->cmOpts.bQuote ? "" : "no"); + }else{ + utf8_printf(p->out, "%12.12s: %.*s\n","mode", i, zOut); + } + 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 : ""); + return 0; +} +DISPATCHABLE_COMMAND( stats ? 0 0 ){ + if( nArg==2 ){ + if( strcmp(azArg[1],"stmt")==0 ){ + p->statsOn = 2; + }else if( strcmp(azArg[1],"vmstep")==0 ){ + p->statsOn = 3; }else{ - raw_printf(stderr, "Usage: .stats ?on|off|stmt|vmstep?\n"); - rc = 1; + p->statsOn = (u8)booleanValue(azArg[1]); } - }else + }else if( nArg==1 ){ + display_stats(p->db, p, 0); + }else{ + *pzErr = shellMPrintf(0,"Usage: .stats ?on|off|stmt|vmstep?\n"); + return 1; + } + return 0; +} - 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); - if( rc ){ - sqlite3_finalize(pStmt); - return shellDatabaseError(p->db); - } +/***************** + * 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, ShellState *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(p->db, "PRAGMA database_list", -1, &pStmt, 0); + if( rc ){ + sqlite3_finalize(pStmt); + return shellDatabaseError(p->db); + } - 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); - } - } - 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); - } - 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); + 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"); + sqlite3_finalize(pStmt); + return 1; + } + 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{ - sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC); + 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); } - 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"); + 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(p->db, s.z, -1, &pStmt, 0); + } + 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); + } + 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"); } + } - 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"); +COLLECT_HELP_TEXT[ +#ifndef LEGACY_TABLES_LISTING + ".tables ?FLAG? ?TVLIKE? List names of tables and/or views", + " FLAG may be -t, -v or -s to list only tables, views or system tables", + " TVLIKE may restrict the listing to names matching given LIKE pattern", +#else + ".tables ?TABLE? List names of tables matching LIKE pattern TABLE", +#endif +]; +DISPATCHABLE_COMMAND( tables 2 1 3 ){ + char objType = 'T'; +#ifndef LEGACY_TABLES_LISTING + if( nArg>1 && 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 SHELL_INVALID_ARGS; } - if( nArg>=2 ){ - sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", azArg[1]); - }else{ - sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?"); + } +#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'); +} + +/***************** + * 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(p->db, nArg>2 ? (const char**)(azArg+2) : 0); + }else{ + for(ii=1; iidb, azArg[ii], 0, 0); } - }else + } + return 0; +} + +/***************** + * 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)", +]; -#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" }, +/* Begin redirecting output to the file "testcase-out.txt" */ +DISPATCHABLE_COMMAND( testcase ? 0 0 ){ + output_reset(p); + p->out = output_file_open("testcase-out.txt", 0); + if( p->out==0 ){ + raw_printf(STD_ERR, "Error: cannot open 'testcase-out.txt'\n"); + } + if( nArg>=2 ){ + sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", azArg[1]); + }else{ + sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?"); + } +} +DISPATCHABLE_COMMAND( testctrl ? 0 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; + { "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"; + 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++; + } - /* The argument can optionally begin with "-" or "--" */ - if( zCmd[0]=='-' && zCmd[1] ){ - zCmd++; - if( zCmd[0]=='-' && zCmd[1] ) zCmd++; + /* --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); } + return 1; + } - /* --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); + /* convert testctrl text option to value. allow any unique prefix + ** of the option name, or a numerical value. */ + n2 = strlen30(zCmd); + for(i=0; ibSafeMode ){ + utf8_printf(STD_ERR, + "line %d: \".testctrl %s\" may not be used in safe mode\n", - p->lineno, aCtrl[iCtrl].zCtrlName); ++ p->pInSource->lineno, aCtrl[iCtrl].zCtrlName); + exit(1); + }else{ + switch(testctrl){ - /* convert testctrl text option to value. allow any unique prefix - ** of the option name, or a numerical value. */ - n2 = strlen30(zCmd); - for(i=0; idb, opt); + isOk = 3; } - } - 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->pInSource->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; + 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; + /* 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; - /* 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; + /* 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; - /* 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; + /* 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); + } + 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; - /* 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; + /* 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; - /* 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; + /* 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; - /* sqlite3_test_control(sqlite3*) */ - case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS: - rc2 = sqlite3_test_control(testctrl, p->db); - isOk = 3; - break; + /* sqlite3_test_control(sqlite3*) */ + case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS: + rc2 = sqlite3_test_control(testctrl, p->db); + isOk = 3; + break; - 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; + 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; - 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; - } + 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; + } #ifdef YYCOVERAGE - case SQLITE_TESTCTRL_PARSER_COVERAGE: { - if( nArg==2 ){ - sqlite3_test_control(testctrl, p->out); - isOk = 3; - } - break; - } + 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; + 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; } - 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); +#endif } - }else -#endif /* !defined(SQLITE_UNTESTABLE) */ - - 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 - - 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; + } + if( isOk==0 && iCtrl>=0 ){ + utf8_printf(p->out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage); + return 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 0; +} +DISPATCHABLE_COMMAND( timeout 4 1 2 ){ + open_db(p, 0); + sqlite3_busy_timeout(p->db, nArg>=2 ? (int)integerValue(azArg[1]) : 0); + return 0; +} +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 0; +} +DISPATCHABLE_COMMAND( trace ? 0 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; + } +#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 { + *pzErr = shellMPrintf(0,"Unknown option \"%s\" on \".trace\"\n", z); + return 1; } }else{ - raw_printf(stderr, "Usage: .timer on|off\n"); - rc = 1; + output_file_close(p->traceOut); + p->traceOut = output_file_open(azArg[1], 0); } - }else + } + 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); + } + return 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; - } +/***************** + * The .user command + * Because there is no help text for .user, it does its own argument validation. + */ +CONDITION_COMMAND( user SQLITE_USER_AUTHENTICATION ); +DISPATCHABLE_COMMAND( user ? 0 0 ){ + int rc; + const char *usage + = "Usage: .user SUBCOMMAND ...\n" + "Subcommands are:\n" + " login USER PASSWORD\n" + " delete USER\n" + " add USER PASSWORD ISADMIN\n" + " edit USER PASSWORD ISADMIN\n" + ; + if( nArg<2 ){ + teach_fail: + *pzErr = shellMPrintf(0,usage); + return 1; + } + open_db(p, 0); + if( strcmp(azArg[1],"login")==0 ){ + if( nArg!=4 ){ + goto teach_fail; + } + rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3], + strlen30(azArg[3])); + if( rc ){ + *pzErr = shellMPrintf(0,"Authentication failed for user %s\n", azArg[2]); + return 1; + } + }else if( strcmp(azArg[1],"add")==0 ){ + if( nArg!=5 ){ + goto teach_fail; + } + rc = sqlite3_user_add(p->db, azArg[2], azArg[3], strlen30(azArg[3]), + booleanValue(azArg[4])); + if( rc ){ + *pzErr = shellMPrintf(0,"User-Add failed: %d\n", rc); + return 1; + } + }else if( strcmp(azArg[1],"edit")==0 ){ + if( nArg!=5 ){ + goto teach_fail; + } + rc = sqlite3_user_change(p->db, azArg[2], azArg[3], strlen30(azArg[3]), + booleanValue(azArg[4])); + if( rc ){ + *pzErr = shellMPrintf(0,"User-Edit failed: %d\n", rc); + return 1; + } + }else if( strcmp(azArg[1],"delete")==0 ){ + if( nArg!=3 ){ + goto teach_fail; + } + rc = sqlite3_user_delete(p->db, azArg[2]); + if( rc ){ + *pzErr = shellMPrintf(0,"User-Delete failed: %d\n", rc); + return 1; + } + }else{ + goto teach_fail; + } + return 0; +} + +/***************** + * 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 ){ + 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()); #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; +#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"); +#endif + return 0; +} +DISPATCHABLE_COMMAND( vfsinfo ? 1 2 ){ + 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); + } + } + return 0; +} +DISPATCHABLE_COMMAND( vfslist ? 1 1 ){ + sqlite3_vfs *pVfs; + sqlite3_vfs *pCurrent = 0; + if( p->db ){ + sqlite3_file_control(p->db, "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent); + } + 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"); + } + } + return 0; +} +DISPATCHABLE_COMMAND( vfsname ? 0 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); + } + } + return 0; +} + +/***************** + * The .width and .wheretrace commands + * The .wheretrace command has no help. + */ +COLLECT_HELP_TEXT[ + ".width NUM1 NUM2 ... Set minimum column widths for columnar output", - " Negative values right-justify", ++ " Negative values right-justify", +]; +DISPATCHABLE_COMMAND( width ? 1 0 ){ + int j; + 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]); + } + return 0; +} +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 0; +} + ++/***************** ++ * The .x command ++ */ ++COLLECT_HELP_TEXT[ ++ ".x NAMES ... Excecute content of some .parameter set variable(s)", ++ " Only variables whose name begins with a letter are eligible for this." ++]; ++DISPATCHABLE_COMMAND( x ? 1 0 ){ ++ int ia, rc, nErrors = 0; ++ sqlite3_stmt *pStmt = 0; ++ open_db(p, 0); ++ if( p->db==0 ){ ++ utf8_printf(STD_ERR, ".x can only be done with a database open.\n"); ++ return 1; ++ } ++ if( sqlite3_table_column_metadata(p->db, PARAM_TABLE_SCHEMA, ++ PARAM_TABLE_NAME, ++ "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){ ++ utf8_printf(STD_ERR, "No "PARAM_TABLE_SNAME" table exists.\n"); ++ return 1; ++ } ++ rc = sqlite3_prepare_v2 ++ (p->db, "SELECT value FROM "PARAM_TABLE_SNAME ++ " WHERE key=$1 AND uses=1", ++ -1, &pStmt, 0); ++ if( rc!=SQLITE_OK ){ ++ utf8_printf(STD_ERR, PARAM_TABLE_SNAME" is wrongly created.\n"); ++ return 1; ++ } ++ for( ia=1; ia < nArg; ++ia ){ ++ if( isalpha(azArg[ia][0]) ){ ++ rc = sqlite3_reset(pStmt); ++ rc = sqlite3_bind_text(pStmt, 1, azArg[ia], -1, 0); ++ rc = sqlite3_step(pStmt); ++ if( rc==SQLITE_ROW ){ ++ const unsigned char *zValue = sqlite3_column_text(pStmt, 0); ++ int nb = sqlite3_column_bytes(pStmt, 0); ++ while( nb>0 && 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 ); ++ InSource inRedir ++ = INSOURCE_STR_REDIR(zSubmit, azArg[ia], p->pInSource); ++ shell_check_oom(zSubmit); ++ p->pInSource = &inRedir; ++ rc = process_input(p); ++ sqlite3_free(zSubmit); ++ p->pInSource = inRedir.pFrom; ++ }else{ ++ continue; /* All white, ignore. */ + } + }else{ - output_file_close(p->traceOut); - p->traceOut = output_file_open(azArg[1], 0); ++ utf8_printf(STD_ERR, ++ "Skipping parameter '%s' (not set and executable.)\n", ++ azArg[ia]); ++ ++nErrors; + } - } - 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); ++ utf8_printf(STD_ERR, ++ "Skipping badly named %s. Run \".help x\"\n", azArg[ia]); ++ ++nErrors; + } - }else -#endif /* !defined(SQLITE_OMIT_TRACE) */ ++ } ++ sqlite3_finalize(pStmt); ++ return (rc==2)? 2 : nErrors>0; ++} + -#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); +/* 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 methodology of the above +COMMENT section to define new or altered meta-commands and their help text. +*/ +INCLUDE( COMMAND_CUSTOMIZE ); + +typedef struct MetaCommand MetaCommand; + +/* Define and populate command dispatch table. */ +static struct CommandInfo { + const char * cmdName; + int (*cmdDoer)(char *azArg[], int nArg, ShellState *, char **pzErr); + unsigned char minLen, minArgs, maxArgs; +#if OBJECTIFY_COMMANDS + const char *azHelp[2]; /* primary and secondary help text */ + void * pCmdData; +#endif +} command_table[] = { + COMMENT Emit the dispatch table entries generated and collected above. + EMIT_DISPATCH(2); + { 0, 0, 0, -1, -1 } +}; +static unsigned numCommands + = sizeof(command_table)/sizeof(struct CommandInfo) - 1; + +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", +]; + +/* +** Text of help messages. +** +** The help text for each individual command begins with a line that starts +** with ".". Subsequent lines are supplimental information. +** +** There must be two or more spaces between the end of the command and the +** start of the description of what that command does. +*/ +static const char *(azHelp[]) = { +/* Template for help text indents and length: + ".whatever ?arg? ... Summary of effects (limited to this line's length)", + " ^ ^ ^ ^ ", +*/ + COMMENT Emit the help text fragments collected above via COLLECT_HELP_TEXT. + EMIT_HELP_TEXT(2); + 0 /* Sentinel */ +}; + + +#define NO_SUCH_COMMAND SQLITE_NOTFOUND +/* SHELL_INVALID_ARGS defined as SQLITE_MISUSE in shext_linkage.h */ + +/***************** +** Command dispatcher +** 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 may (TBD) query the shell's DB. Either +** way, this function retains its interface. +** After successful command lookup and (simple) argument checking, +** it calls the found meta-command 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 +** meta-command returns. +*/ +int dispatchCommand(char *azArg[], int nArg, ShellState *pSS, char **pzErr){ + const char *cmdName = azArg[0]; + 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{ - for(ii=1; iidb, azArg[ii], 0, 0); + if( command_table[ixm].minLen > cmdLen ){ + return NO_SUCH_COMMAND; } + pci = &command_table[ixm]; + break; } - }else + } + if( 0==pci ){ + return NO_SUCH_COMMAND; + } + if( pci->minArgs > nArg||(pci->maxArgs > 0 && pci->maxArgs < nArg) ){ + return SHELL_INVALID_ARGS; + } + /* Replace any user-shortened command name with its whole name. */ + azArg[0] = (char *)pci->cmdName; + return (pci->cmdDoer)(azArg, nArg, pSS, pzErr); +} + +/* +** 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]; ++#if SHELL_VARIABLE_EXPANSION ++ int ncLineIn = strlen30(zLine); ++ u8 bExpVars = SHEXT_VAREXP(p); + #endif -#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; +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( p->expert.pExpert ){ + expertFinish(p, 1, 0); + } +#endif + + /* Parse the input line into tokens. + */ + while( zLine[h] && nArgdb, azArg[2]); - if( rc ){ - raw_printf(stderr, "User-Delete failed: %d\n", rc); - rc = 1; + if( zLine[h]==delim ){ + zLine[h++] = 0; } + if( delim=='"' ) resolve_backslashes(azArg[nArg-1]); }else{ - raw_printf(stderr, "Usage: .user login|add|edit|delete ...\n"); - rc = 1; - goto meta_command_exit; + azArg[nArg++] = &zLine[h]; + while( zLine[h] && !IsSpace(zLine[h]) ){ h++; } + if( zLine[h] ) zLine[h++] = 0; + resolve_backslashes(azArg[nArg-1]); } - }else -#endif /* SQLITE_USER_AUTHENTICATION */ + } + azArg[nArg] = 0; - 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()); -#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"); -#endif - }else + /* Process the input line. + */ + if( nArg==0 ) return 0; /* no tokens, no error */ + n = strlen30(azArg[0]); + c = azArg[0][0]; + clearTempFile(p); - 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); - } - } + /* Check for the special, non-dispatched meta-commands. + */ + + if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){ + if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) + p->abruptExit = rc; + rc = 2; }else - if( c=='v' && strncmp(azArg[0], "vfslist", n)==0 ){ - sqlite3_vfs *pVfs; - sqlite3_vfs *pCurrent = 0; - if( p->db ){ - sqlite3_file_control(p->db, "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent); - } - 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( c=='q' && strncmp(azArg[0], "quit", n)==0 ){ + rc = 2; }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); +#ifdef SQLITE_DEBUG + /* Undocumented commands for internal testing. + * Subject to change without notice. + * These are not dispatched via lookup because the command word varies. + */ + 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); } } - }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( strncmp(azArg[0]+9, "integer", n-9)==0 ){ + int i; sqlite3_int64 v; + for(i=1; iout, "%s", zBuf); + } } }else - - if( c=='x' && n==1 ){ /* "x" */ - rc = execute_variables(azArg, nArg, p); - }else - +#endif + /* The meta-command is not among the specially handled ones. Dispatch it. */ { - utf8_printf(stderr, "Error: unknown command or invalid arguments: " - " \"%s\". Enter \".help\" for help\n", azArg[0]); - rc = 1; + char *zErr = 0; + int dispatchResult = dispatchCommand(azArg, nArg, p, &zErr); + if( p->abruptExit!=0 ){ + dispatchResult = SHELL_FORBIDDEN_OP; + } + switch( dispatchResult ){ + case NO_SUCH_COMMAND: + utf8_printf(STD_ERR, "Error: unknown command: \"%s\"\n", azArg[0]); + if( stdin_is_interactive ) + utf8_printf(STD_ERR, " Enter \".help\" for a list of commands.\n"); + rc = 1; + break; + case SHELL_INVALID_ARGS: + utf8_printf(STD_ERR, "Error: invalid arguments for \".%s\"\n", azArg[0]); + if( stdin_is_interactive ){ + if( zErr!=0 ){ + utf8_printf(STD_ERR, " %s\n", zErr); + }else{ + utf8_printf(STD_ERR, "Usage: "); + showPrimaryHelp(STD_ERR, azArg[0]); + } + } + rc = 1; + break; + case SHELL_FORBIDDEN_OP: + if( zErr!=0 ){ + utf8_printf + (STD_ERR, + "Error: \".%s\" may not %s in --safe mode\n", azArg[0], zErr); + sqlite3_free(zErr); + }else { + utf8_printf(STD_ERR, + "Error: \".%s\" forbidden in --safe mode\n", azArg[0]); + } + p->abruptExit = 3; + rc = 2; + default: + if( 0!=dispatchResult ) rc = 1; + if( zErr!=0 ){ + utf8_printf(STD_ERR, "%s", zErr); + sqlite3_free(zErr); + } + } } meta_command_exit: @@@ -11734,7 -12319,17 +12692,17 @@@ p->outCount--; if( p->outCount==0 ) output_reset(p); } - p->bSafeMode = p->bSafeModePersist; + updateSafeMode(p); + #if SHELL_VARIABLE_EXPANSION + if( bExpVars ){ + /* Free any arguments that are allocated rather than tokenized in place. */ + for( n=1; n0 && iArgOffset0 ){ + SqlScanState sss = SSS_Start; + sql_prescan(zLine+iSkip,&sss); + if( sss==SSS_Start ) return zLine; + } + return 0; } /* @@@ -11909,9 -12507,8 +12884,8 @@@ static int runOneSqlLine(ShellState *p }else{ sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType); } -- utf8_printf(stderr, "%s %s\n", zPrefix, zErrorTail); ++ utf8_printf(STD_ERR, "%s %s\n", zPrefix, zErrorTail); sqlite3_free(zErrMsg); - zErrMsg = 0; return 1; }else if( ShellHasFlag(p, SHFLG_CountChanges) ){ char zLineBuf[2000]; @@@ -11923,122 -12520,397 +12897,399 @@@ return 0; } + #if SHELL_EXTENDED_PARSING + /* Resumable line classsifier for dot-commands + ** + ** 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; + 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; + } + ss = twixtArgs; + c = *zCmd++; + continue; + } + } + 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 /* - ** Read input from *in and process it. If *in==0 then input - ** is interactive - the user is typing it it. Otherwise, input - ** is coming from a file or device. A prompt is issued and history - ** is saved only if input is interactive. An interrupt signal will - ** cause this routine to exit immediately, unless input is interactive. + ** 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. ** - ** Normally return (number_of_errors > 0), - ** but return SHELL_FORBIDDEN_OP for immediate shell exit. + ** Returns: + ** 0 => no errors + ** 1 => errors>0 + ** 2 => exit demanded, no errors. + ** 3 => exit demanded, errors>0 */ static int process_input(ShellState *p){ - char *zLine = 0; /* A single input line */ - char *zSql = 0; /* Accumulated SQL text */ - int nLine; /* Length of current line */ - int nSql = 0; /* Bytes of zSql[] used */ - int nAlloc = 0; /* Allocated zSql[] space */ - int rc = 0; /* Error or exit code */ - int errCnt = 0; /* Number of errors seen */ - int startline = 0; /* Line number for start of current input */ - QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */ - + 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; + /* Flag to affect prompting and interrupt action */ + u8 bInteractive = (p->pInSource==&stdInSource && stdin_is_interactive); + int nErrors = 0; /* count of errors during execution or its prep */ + + /* Block overly-recursive or absurdly nested input redirects. */ if( p->inputNesting==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); + InSource *pInSrc = p->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(stderr, ++ 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(stderr, " ...\nERROR: Check recursion.\n"); ++ utf8_printf(STD_ERR, " ...\nERROR: Check recursion.\n"); return 1; } ++p->inputNesting; - p->lineno = 0; - while( rc<2 - && - (errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive)) ){ + + /* line-group processing loop (per SQL block, dot-command or comment) */ + while( !bErrorBail && !bInputEnd && !bExitDemand && !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 # */ + 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(p->out); - zLine = one_input_line(p->in, zLine, nSql>0); - if( zLine==0 ){ - /* End of input */ - if( p->in==0 && stdin_is_interactive ) fprintf(STD_OUT, "\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) ) - fprintf(STD_OUT, "%s\n", zLine); - /* Just swallow single-line whitespace */ - qss = QSS_Start; - continue; - } - if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){ - if( ShellHasFlag(p, SHFLG_Echo) ) fprintf(STD_OUT, "%s\n", zLine); - if( zLine[0]=='.' ){ - rc = do_meta_command(zLine, p); - if( rc==2 || p->abruptExit!=0 ){ /* exit requested */ + zLineInput = one_input_line(p->pInSource, zLineInput, nGroupLines>0); + if( zLineInput==0 ){ + bInputEnd = 1; + inKind = Eof; + disposition = Ignore; + if( bInteractive ) printf("\n"); + }else{ + ++nGroupLines; + iStartline = p->pInSource->lineno; + ncLineIn = strlen30(zLineInput); + if( seenInterrupt ){ + if( p->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(p)) + ? 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(p) ){ + 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(p->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; - }else if( rc ){ - errCnt++; } + 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(p, *pzLineUse); + break; + case Runnable: + switch( inKind ){ + case Sql: + /* runOneSqlLine() does its own echo when requested. */ + nErrors += runOneSqlLine(p, *pzLineUse, + INSOURCE_IS_INTERACTIVE(p->pInSource), + iStartline); + if( bail_on_error && nErrors>0 ) bErrorBail = 1; + break; + case Cmd: + echo_group_input(p, *pzLineUse); + switch( do_meta_command(*pzLineUse+ndcLeadWhite, p) ){ + default: ++nErrors; /* fall thru */ + case 0: break; + case 2: bExitDemand = 1; break; + } + break; + default: + assert(inKind!=Tbd); + break; } - qss = QSS_Start; - 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; - }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); - } - updateSafeMode(p); - qss = QSS_Start; - }else if( nSql && QSS_PLAINWHITE(qss) ){ - if( ShellHasFlag(p, SHFLG_Echo) ) fprintf(STD_OUT, "%s\n", zSql); - nSql = 0; - qss = QSS_Start; ++ if( p->abruptExit!=0 ) bExitDemand = 1; + break; + case Erroneous: - utf8_printf(stderr, "Error: Input incomplete at line %d of \"%s\"\n", ++ utf8_printf(STD_ERR, "Error: Input incomplete at line %d of \"%s\"\n", + p->pInSource->lineno, p->pInSource->zSourceSay); + ++nErrors; + break; + case Ignore: + break; + default: assert(0); } - } - if( nSql && QSS_PLAINDARK(qss) ){ - errCnt += runOneSqlLine(p, zSql, p->in, startline); - updateSafeMode(p); - } - free(zSql); - free(zLine); - --p->inputNesting; - return (p->abruptExit)? SHELL_FORBIDDEN_OP : errCnt>0; + if( nErrors>0 && bail_on_error ) bErrorBail = 1; + } /* 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); } /* @@@ -12138,9 -12985,10 +13389,9 @@@ static int run_single_query(ShellState /* ** Read input from the file given by sqliterc_override. Or if that ** parameter is NULL, take input from ~/.sqliterc -** -** Returns the number of errors. +** The return is similar to process_input() (0 success, 1 error, x abort) */ - static int process_sqliterc( + static void process_sqliterc( ShellState *p, /* Configuration data */ const char *sqliterc_override /* Name of config file. NULL to use default */ ){ @@@ -12154,31 -13000,32 +13403,32 @@@ if (sqliterc == NULL) { home_dir = find_home_dir(0); if( home_dir==0 ){ - raw_printf(stderr, "-- warning: cannot find home directory;" + raw_printf(STD_ERR, "-- warning: cannot find home directory;" " cannot read ~/.sqliterc\n"); - return 1; + return; } zBuf = sqlite3_mprintf("%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, p->pInSource); + int rc; + p->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); } rc = process_input(p); - fclose(p->in); + fclose(inUse); - p->pInSource = inSourceRedir.pFrom; /* 0 when called by main() */ - if( rc!=0 && bail_on_error ) exit(1); ++ p->pInSource = inSourceRedir.pFrom; ++ if( rc!=0 && bail_on_error ) p->abruptExit = rc; }else if( sqliterc_override!=0 ){ - utf8_printf(stderr,"cannot open: \"%s\"\n", sqliterc); + utf8_printf(STD_ERR,"cannot open: \"%s\"\n", sqliterc); - rc = 1; - }else{ - rc = 0; + if( bail_on_error ) exit(1); } - p->in = inSaved; - p->lineno = savedLineno; sqlite3_free(zBuf); - return rc; } /* @@@ -12230,6 -13077,9 +13480,9 @@@ static const char *zOptions " -readonly open the database read-only\n" " -safe enable safe-mode\n" " -separator SEP set output column separator. Default: '|'\n" + #if SHELL_EXTENSIONS - " -shxopts BMASK enable shell extensions and options\n" ++ " -shxopts BMASK enable shell extensions and options (7 for all)\n" + #endif #ifdef SQLITE_ENABLE_SORTER_REFERENCES " -sorterref SIZE sorter references threshold size\n" #endif @@@ -12286,8 -13135,9 +13539,9 @@@ static void main_init(ShellState *data sqlite3_config(SQLITE_CONFIG_URI, 1); sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data); sqlite3_config(SQLITE_CONFIG_MULTITHREAD); - sqlite3_snprintf(sizeof(mainPrompt), mainPrompt, "sqlite> "); + sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> "); sqlite3_snprintf(sizeof(continuePrompt), continuePrompt," ...> "); + init_std_inputs(stdin); } /* @@@ -12336,22 -13186,16 +13590,23 @@@ 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; const char *zInitFile = 0; + int bQuiet = 0; /* for testing, to suppress banner and history actions */ int i; int rc = 0; int warnInmemoryDb = 0; @@@ -12565,14 -13420,20 +13820,20 @@@ break; #endif }else if( strcmp(z, "-memtrace")==0 ){ - sqlite3MemTraceActivate(stderr); + sqlite3MemTraceActivate(STD_ERR); }else if( strcmp(z,"-bail")==0 ){ bail_on_error = 1; + #if SHELL_EXTENSIONS + }else if( strcmp(z,"-shxopts")==0 ){ + data.bExtendedDotCmds = (u8)integerValue(argv[++i]); + #endif }else if( strcmp(z,"-nonce")==0 ){ free(data.zNonce); data.zNonce = strdup(argv[++i]); + }else if( strcmp(z,"-quiet")==0 ){ + bQuiet = (int)integerValue(cmdline_option_value(argc,argv,++i)); }else if( strcmp(z,"-safe")==0 ){ - /* no-op - catch this on the second pass */ + /* catch this on the second pass (Unsafe is fine on invocation.) */ } } verify_uninitialized(); @@@ -12649,35 -13496,35 +13907,37 @@@ ** file is processed so that the command-line arguments will override ** settings in the initialization file. */ - for(i=1; i0 ){ @@@ -12809,13 -13670,15 +14064,19 @@@ break; #endif }else if( strcmp(z,"-safe")==0 ){ - data.bSafeMode = data.bSafeModePersist = 1; + data.bSafeMode = data.bSafeModeFuture = 1; + }else if( strcmp(z,"-quiet")==0 ){ + ++i; }else{ - utf8_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z); - raw_printf(stderr,"Use -help for a list of options.\n"); - return 1; + utf8_printf(STD_ERR,"%s: Error: unknown option: %s\n", Argv0, z); + raw_printf(STD_ERR,"Use -help for a list of options.\n"); + rc = 2; + } - data.cMode = data.mode; ++ if( zModeSet!=0 ){ ++ char *azModeCmd[] = { ".mode", zModeSet+1 }; ++ modeCommand(azModeCmd, 2, &data, 0); ++ data.cMode = data.mode; + } - data.cMode = data.mode; } if( !readStdin ){ @@@ -12823,58 -13686,78 +14084,66 @@@ ** command-line inputs, except for the argToSkip argument which contains ** the database filename. */ - for(i=0; i2 || data.abruptExit>1 ){ + rc = (rc" + 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 + }} + } + + #---------------------------------------------------------------------------- + # 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"}} ++} {1 {Skipping badly named $v. Run ".help x" ++Abnormal exit (0)}} + + finish_test diff --cc tool/mkshellc.tcl index 9328991cdd,f1e2bd2eb6..cdb7d894a7 --- a/tool/mkshellc.tcl +++ b/tool/mkshellc.tcl @@@ -58,712 -21,62 +58,720 @@@ set ::headComment {/* DO NOT EDIT ** Most of the code found below comes from the "src/shell.c.in" file in ** the canonical SQLite source tree. That main file contains "INCLUDE" ** lines that specify other files in the canonical source tree that are -** inserted to getnerate this complete program source file. +** inserted and transformed, (via macro invocations explained by running +** "tool/mkshellc.tcl --help"), to generate this complete program source. ** -** The code from multiple files is combined into this single "shell.c" -** source file to help make the command-line program easier to compile. +** By means of this generation process, creating this single "shell.c" +** file, building the command-line program is made simpler and easier. ** ** To modify this program, get a copy of the canonical SQLite source tree, -** edit the src/shell.c.in" and/or some of the other files that are included -** by "src/shell.c.in", then rerun the tool/mkshellc.tcl script. +** edit file src/shell.c.in and/or some of the other files included by it, +** then rerun the tool/mkshellc.tcl script. */} -set in [open $topdir/src/shell.c.in] -fconfigure $in -translation binary -proc omit_redundant_typedefs {line} { - global typedef_seen + +set ::headCommentLines [expr 1+[regexp -all "\n" $::headComment]] + +set ::topDir [file dir [file dir [file normal $argv0]]] +set runMode normal + +set ::lineTags 0 ; # 0 => none, 1 => source change, 2 => line syncs, 3 => more + +set ::tclGenerate 0 +set ::verbosity 0 +set ::inFiles {} +array set ::incTypes [list "*" "$::topDir/src/shell.c.in"] +array set ::ignoringCommands [list] + +while {[llength $argv] > 0} { + foreach {opt} $arv { set argv [lreplace $argv 1 end] ; break } + if {[regexp {^-{1,2}((help)|(details)|(parameters))$} $opt ma ho]} { + set runMode $ho + } elseif {[regexp {^-it$} $opt]} { + foreach {nextOpt} $arv { set argv [lreplace $argv 1 end] ; break } + if {![regexp {^(\w+)=(.+)$} $nextOpt ma k v]} { + puts stderr "Get help with --help." + exit 1 + } + set ::incTypes($k) $v + } elseif {$opt eq "-top-dir"} { + foreach {::topDir} $arv { set argv [lreplace $argv 1 end] ; break } + if {::topDir eq ""} { set ::topDir . } + } elseif {$opt eq "-source-tags"} { + foreach {nextOpt} $arv { set argv [lreplace $argv 1 end] ; break } + if {![regexp {^\d$} $nextOpt ::lineTags]} { + puts stderr "Argument following -source-tags must be a digit." + } + } elseif {$opt eq "-tcl"} { + puts stderr "Warning: Tcl extension not wholly implemented." + set ::tclGenerate 1 + } elseif {$opt eq "-v"} { + incr ::verbosity + } elseif {[regexp {^[^-]} $opt]} { + lappend ::inFiles $opt + } else { + puts stderr "Skipping unknown option: $opt" + } +} +if {$runMode eq "normal"} { + if {[llength $::inFiles] == 0} { + lappend ::inFiles $::incTypes(*) + } + fconfigure stdout -translation {auto lf} + set ::outStrm stdout +} + +# Given a path relative to /src, return its full pathname. +proc project_path {relPath} { + return "$::topDir/src/$relPath" +} + +if {$::lineTags >= 3} { + # These k/v stores hold {lineNum filename} lists keyed by meta-command, + # which are used to get #line directives on all dispatch and help table + # entries, and any conditionals affecting their compilation. + array set ::cmd_help_tags {} + array set ::cmd_dispatch_tags {} + array set ::cmd_conditional_tags {} +} + +# Set one of above k/v stores, (help, dispatch, conditional) for given +# cmd from members of inSrc triple {filename istrm lineNumber}. +proc set_src_tags {which cmd inSrc} { + if {$::lineTags >= 3} { + foreach {filename _ lineNumber} $inSrc break + set [subst ::cmd_${which}_tags]($cmd) [list $lineNumber $filename] + } +} +# Return pair {lineNumber fileName} from one of above k/v stores, +# (help, dispatch, conditional) for given cmd, or get empty list. +# The empty list indicates either not keeping such k/v, or there +# is not one for the given cmd +proc get_src_tags {which cmd} { + if {$::lineTags >= 3 && [info exists [subst ::cmd_${which}_tags]($cmd)]} { + return [subst "\$[subst ::cmd_${which}_tags]($cmd)"] + } + return {} +} + +# To faciliate non-excessive line tagging, track these values before emits: +# These 2 variables are set/used only by procs line_tag and emit_sync . +set ::apparentSrcFile "" +set ::apparentSrcPrecLines $::headCommentLines + +# Maybe put a #line directive if ::lineTags not 0. Directive style depends +# on its value and whether srcFile input is provided as follows: +# 1 => just file changes, 2 => line syncs too if srcFile not empty. +# A #line directive is only emitted if its kind is enabled +# All #line emits pass through this proc. +proc line_tag { ostrm srcPrecLines {srcFile ""} } { + if {$::lineTags == 0} return + set sayLine [expr {$srcPrecLines + 1}] + if {$srcFile ne ""} { + set ::apparentSrcFile $srcFile + puts $ostrm "#line $sayLine \"$::apparentSrcFile\"" + } elseif {$::lineTags > 1} { + puts $ostrm "#line $sayLine" + } + set ::apparentSrcPrecLines $srcPrecLines +} + +# Put a #line directive only if needed to resynchronize compiler's +# notion of source line location with actual source line location. +# And do this only if about to emit some line(s). Then emit them. +# This proc is used for all output emits (to make this work.) +# The precLines input is the number of source lines preceding the +# one to be represented (via #line ...) as producing next output. +proc emit_sync { lines ostrm precLines {fromFile ""} } { + if {$::lineTags > 0} { + if {$fromFile ne "" && $fromFile ne $::apparentSrcFile} { + line_tag $ostrm $precLines $fromFile + } elseif {$::lineTags > 1 + && $precLines != $::apparentSrcPrecLines + && $lines ne {}} { + line_tag $ostrm $precLines + } + } + foreach line $lines { + puts $ostrm $line + incr ::apparentSrcPrecLines + } +} + +array set ::cmd_help {} +array set ::cmd_dispatch {} +array set ::cmd_condition {} +array set ::inc_type_files {} +set ::iShuffleErrors 0 +# Ease use of { and } in literals. Instead, $::lb and $::rb can be used. +regexp {(\{)(\})} "{}" ma ::lb ::rb + +# Setup dispatching function signature and table entry struct . +# The effect of these key/value pairs is as this --parameters output says: +set ::parametersHelp { + The following parameters given to DISPATCH_CONFIG have these effects: + RETURN_TYPE sets the generated dispatchable function signature return type. + STORAGE_CLASS sets the dispatchable function linkage, (typically "static".) + ARGS_SIGNATURE sets the formal argument list for the dispatchable functions. + DISPATCH_ENTRY sets the text of each entry line in emitted dispatch table. + DISPATCHEE_NAME sets the name to be generated for dispatchable functions. + CMD_CAPTURE_RE sets a regular expression to be used for capturing the name + to be used for meta-commands within a line passed into COLLECT_DISPATCH, + (which is needed to permit them to be emitted in lexical order by name.) + DC_ARG_COUNT sets the effective argument count for DISPATCHABLE_COMMAND(). + DC_ARG#_DEFAULT sets a default value, DISPATCHABLE_COMMAND() #'th argument. + HELP_COALESCE sets whether to coalesce secondary help text and add newlines. + Within values set for ARGS_SIGNATURE, DISPATCHEE_NAME, and DISPATCH_ENTRY + parameters, the variables $cmd and $arg# (where # is an integer) may appear, + to be replaced by the meta-command name or the #'th effective argument to + DISPATCHABLE_COMMAND(). The "effective" argument is either what is provided, + or a default value when the actual argument is missing (at the right end of + the provided argument list) or the argument has the value ? . The expansion + of $cmd and $arg# variables is done by Tcl evaluation (via subst), allowing + a wide range of logic to be employed in the derivation of effective values. +} +array set ::dispCfg [list \ + RETURN_TYPE int \ + STORAGE_CLASS static \ + ARGS_SIGNATURE "char *\$arg4\\\[\\\], int \$arg5, ShellState *\$arg6" \ + DISPATCH_ENTRY \ + "{ \"\$cmd\", \${cmd}Command, \$arg1,\$arg2,\$arg3 }," \ + DISPATCHEE_NAME {${cmd}Command} \ + CMD_CAPTURE_RE "^\\s*$::lb\\s*\"(\\w+)\"" \ + HELP_COALESCE 0 \ +] +# Other config keys: +# DC_ARG_COUNT= +# DC_ARG#_DEFAULT= +# Variables $cmd and $arg# (where # = 0 .. DC_ARG_COUNT-1) have values +# when ARGS_SIGNATURE, DISPATCH_ENTRY, and DISPATCHEE_NAME are evaluated. + +proc emit_conditionally {cmd lines inSrc ostrm {indent ""} {cmdTagStore {}}} { + foreach {fname _ lnum} $inSrc break + set wrapped [info exists ::cmd_condition($cmd)] + if {$wrapped} { + emit_sync [list $::cmd_condition($cmd)] $ostrm $lnum $fname + incr lnum + } + if {[regexp {^\s*(\d+)\s*$} $indent ma inum]} { + set lead [string repeat " " $inum] + set ilines [list] + foreach line $lines { lappend ilines "$lead[string trimleft $line]" } + set lines $ilines + } + emit_sync $lines $ostrm $lnum $fname + incr lnum [llength $lines] + if {$wrapped} { + emit_sync [list "#endif"] $ostrm $lnum $fname + incr lnum + } +} + +# Coalesce secondary help text lines using C's string literal concatenation +# and arrange that each command's help has one primary (leading '.') help +# text line and one secondary help text line-set even if it is empty. +proc coalesce_help {htin} { + set htrv {} + foreach hl $htin { + if {[regexp {^\s*"\.\w+} $hl]} { ;# " + lappend htrv [regsub {"\s*,\s*$} $hl {\n",}] + } elseif {[regexp {^\s*#\s*\w+} $hl]} { + lappend htrv $hl + } else { + lappend htrv [regsub {"\s*,\s*$} $hl {\n"}] + } + } + lappend htrv {"",} +} + +# Convert list of help text lines into a key-value list. +# Keys are the command names. Values are the help for the +# commands as a list of lines, with .* logically first. +# Any #if... #endif structures are maintained and do not +# interact with "logically first" .* lines, except that +# only one such line is seen within such a conditional. +# (The effect of this is to defeat sorting by command if +# help for multiple commands' is within one conditional.) +proc chunkify_help {htin} { + array set rv [list] + set if_depth 0 + set cmd_seen "" + set chunk {} + foreach htx $htin { + if {[regexp {^\s*\"\.\w} $htx] && $cmd_seen ne "" && $if_depth == 0} { + # Flush accumulated chunk. + set rv($cmd_seen) $chunk + set cmd_seen "" + set chunk {} + } + lappend chunk $htx + if {[regexp {^\s*#if} $htx]} { + incr if_depth + } elseif {[regexp {^\s*#endif} $htx]} { + incr if_depth -1 + } else { + if {[regexp {^\s*\"\.(\w+)} $htx all cmd] && $cmd_seen eq ""} { + set cmd_seen $cmd + } + } + } + if {$if_depth != 0} { + puts stderr "Help chunk bad #conditional:" + puts stderr [join $htin "\n"] + puts stderr "Swallowed [join $chunk \n]" + incr ::iShuffleErrors + } else { + if {$cmd_seen ne "" && [llength $chunk] > 0} { + # Flush accumulated chunk. + set rv($cmd_seen) $chunk + } elseif {$cmd_seen ne "" || [llength $chunk] > 0} { + puts stderr "Orphaned help: '$cmd_seen' [join $chunk \n]" + incr ::iShuffleErrors + } + } + if {$::dispCfg(HELP_COALESCE)} { + foreach cmd_seen [array names rv] { + set rv($cmd_seen) [coalesce_help $rv($cmd_seen)] + } + } + return [array get rv] +} + +array set ::macroTailREs [list \ + COLLECT_DISPATCH {^\(\s*([\w\*]+)\s*\)\[} \ + COLLECT_HELP_TEXT {^\[} \ + COMMENT {\s+(.*)$} \ + CONDITION_COMMAND {^\(\s*(\w+)\s+([^;]+)\);} \ + DISPATCH_CONFIG {^\[} \ + DISPATCHABLE_COMMAND {^\(([\w\? ]+)\)(\S)\s*$} \ + EMIT_DISPATCH {^\((\d*)\)} \ + EMIT_HELP_TEXT {^\((\d*)\)} \ + INCLUDE {^(?:\(\s*(\w+)\s*\))|(?:\s+([\w./\\]+)\M)} \ + IGNORE_COMMANDS {^\(\s*([-+\w ]*)\)\s*;\s*} \ +] +# Names of the subcaptures as formal parameter to macro procs. +# COMMENT tailCapture_Commentary +# CONDITION_COMMAND tailCapture_Cmd_Condition +# CONFIGURE_DISPATCH tailCapture_Empty +# COLLECT_DISPATCH tailCapture_Cmd +# COLLECT_HELP_TEXT tailCapture_Empty +# DISPATCHABLE_COMMAND tailCapture_ArgsGlom_TrailChar +# EMIT_DISPATCH tailCapture_Indent +# EMIT_HELP_TEXT tailCapture_Indent +# IGNORED_COMMANDS tailCapture_SignedCmdGlom +# INCLUDE tailCapture_IncType_Filename + +array set ::macroUsages [list \ + COLLECT_DISPATCH "\[\n \n \];" \ + COLLECT_HELP_TEXT "\[\n \n \];" \ + COMMENT " " \ + CONDITION_COMMAND "( name pp_expr );" \ + DISPATCH_CONFIG "\[\n \n \];" \ + DISPATCHABLE_COMMAND \ + "( name args... ){\n \n }" \ + EMIT_DISPATCH "( indent );" \ + EMIT_HELP_TEXT "( indent );" \ + INCLUDE {( )} \ + SKIP_COMMANDS "( );" \ +] +# RE for early discard of non-macro lines, matching all above keywords +set ::macroKeywordTailRE \ + {^\s{0,8}((?:(?:CO)|(?:DI)|(?:EM)|(?:IN)|(?:SK))[A-Z_]+)\M(.+)$} + +######## +# Macro procs, general signature and usage: +# inSrc is a triple, { input_filename open_input_stream input_lines_consumed }. +# Arg 2 is the macro tail as RE-captured by one of ::macroTailREs . +# ostrm is the open output stream for all regular output. +# The number of input lines consumed, including macro invocation, is returned. +# +# These procs may consume additional input, leave side-effects, or emit +# output to ostrm (via emit_sync), as individually documented. +# Their names always exactly match the invocation identifier. + +proc IGNORED_COMMANDS {inSrc tcSignedCmdGlom ostrm} { + # Cause the listed commands to be ignored or allowed to generate, as set + # by a preceeding + or - respectively in the list. This may be useful + # when statically extending the shell to avoid duplicate implementation. + # Commands never mentioned within this macro are allowed to generate. + # TBD WIP + set sign "" + foreach {. o} [regexp -inline -all {\s*([\-\+]|[\w]+)\s*} $tcSignedCmdGlom] { + if {![regexp {[\+\-\?]} $o . sign]} { + if {$sign eq "+"} { + } else { + } + } + } + return 1 +} + +proc COLLECT_DISPATCH {inSrc tailCaptureCmdOrStar ostrm} { + # Collect dispatch table entries, along with cmd(s) as ordering info. + foreach {infile istrm inLineNum} $inSrc {} + foreach {cmd} $tailCaptureCmdOrStar {} + set iAte 2 + set lx [gets $istrm] + set disp_frag {} + while {![eof $istrm] && ![regexp {^\s*\];} $lx]} { + lappend disp_frag $lx + set grabCmd $::dispCfg(CMD_CAPTURE_RE) + if {![regexp $grabCmd $lx ma dcmd]} { + puts stderr "malformed dispatch element:\n $lx" + incr ::iShuffleErrors + } elseif {$cmd ne "*" && $dcmd ne $cmd} { + puts stderr "misdeclared dispatch element:\n $lx" + incr ::iShuffleErrors + } else { + set ::cmd_dispatch($dcmd) [list $lx] + set_src_tags dispatch $dcmd $inSrc + } + set lx [gets $istrm] + incr iAte + } + return $iAte +} + +proc COMMENT {inSrc tailCaptureIgnore ostrm} { + # Allow comments in an input file which have no effect on output. + return 1 +} + +proc INCLUDE {inSrc tailCaptureIncType ostrm} { + # If invoked with a bare filename, include the named file. If invoked + # with the parenthesized word form, include a file named by means of + # the '-it =filename' command line option, provided that the + # word matches a specified . Otherwise, do nothing. + foreach {it rfpath} $tailCaptureIncType break + foreach { srcFile istrm srcPrecLines } $inSrc break + set saySkip "" + if {$it ne ""} { + if {[info exists ::incTypes($it)]} { + set rfpath $::incTypes($it) + if {![file exists [project_path $rfpath]]} { + set saySkip "/* INCLUDE($it), of missing \"$rfpath\" skipped. */" + } + } else { + set saySkip "/* INCLUDE($it), undefined and skipped. */" + } + } + if {$saySkip ne ""} { + emit_sync [list $saySkip] $ostrm $srcPrecLines $srcFile + } else { + process_file [project_path $rfpath] $ostrm + incr srcPrecLines + emit_sync {} $ostrm $srcPrecLines $srcFile + } + return 1 +} + +proc COLLECT_HELP_TEXT {inSrc tailCaptureEmpty ostrm} { + # Collect help text table values, along with ordering info. + foreach { srcFile istrm srcPrecLines } $inSrc break + set iAte 2 + set help_frag {} + set lx [gets $istrm] + while {![eof $istrm] && ![regexp {^\s*\];} $lx]} { + lappend help_frag $lx + set lx [gets $istrm] + incr iAte + } + set chunked_help [chunkify_help $help_frag] + array set ::cmd_help $chunked_help + foreach {cmd _} $chunked_help { set_src_tags help $cmd $inSrc } + return $iAte +} + +proc CONDITION_COMMAND {inSrc tailCap ostrm} { + # Name a command to be conditionally available, with the condition. + foreach {cmd pp_expr} $tailCap { set pp_expr [string trim $pp_expr] ; break } + if {[regexp {^(!)?defined\(\s*(\w+)\s*\)} $pp_expr ma bang pp_var]} { + if {$bang eq "!"} { + set pp_expr "#ifndef $pp_var" + } else { + set pp_expr "#ifdef $pp_var" + } + } else { + set pp_expr "#if [string trim $pp_expr]" + } + set ::cmd_condition($cmd) $pp_expr + set_src_tags conditional $cmd $inSrc + return 1 +} + +proc DISPATCH_CONFIG {inSrc tailCaptureEmpty ostrm} { + foreach { srcFile istrm srcPrecLines } $inSrc break + # Set parameters affecting generated dispatchable command function + # signatures and generated dispatch table entries. + set iAte 2 + set def_disp {} + set lx [gets $istrm] + while {![eof $istrm] && ![regexp {^\s*\];} $lx]} { + lappend def_disp $lx + set lx [gets $istrm] + incr iAte + } + foreach line $def_disp { + if {[regexp {^\s*(\w+)=(.+)$} $line ma k v]} { + set ::dispCfg($k) $v + } + } + return $iAte +} + +proc DISPATCHABLE_COMMAND {inSrc tailCapture ostrm} { + # Generate and emit a function definition, maybe wrapped as set by + # CONDITION_COMMAND(), and generate/collect its dispatch table entry, + # as determined by its actual arguments and DISPATCH_CONFIG parameters. + foreach { srcFile istrm srcPrecLines } $inSrc break + set args [lindex $tailCapture 0] + set tc [lindex $tailCapture 1] + if {$tc ne $::lb} { + yap_usage "DISPATCHABLE_COMMAND($args)$tc" DISPATCHABLE_COMMAND + incr $::iShuffleErrors + return 0 + } + set iAte 1 + set args [split [regsub {\s+} [string trim $args] " "]] + set na [llength $args] + set cmd [lindex $args 0] + set naPass $::dispCfg(DC_ARG_COUNT) + if {$na > $naPass} { + puts stderr "Bad args: $lx" + } else { + while {$na < $naPass} { + set nad "DC_ARG${na}_DEFAULT" + if {![info exists ::dispCfg($nad)]} { + puts stderr "Too few args: $lx (need $naPass)" + incr ::iShuffleErrors + break + } else { + lappend args [subst $::dispCfg($nad)] + } + incr na + } + set body {} + while {![eof $istrm]} { + set bl [gets $istrm] + incr iAte + lappend body $bl + if {[regexp "^$::rb\\s*\$" $bl]} { break } + } + for {set aix 1} {$aix < $na} {incr aix} { + set av [lindex $args $aix] + if {$av eq "?"} { + set ai [expr {$aix + 1}] + set aid "DC_ARG${ai}_DEFAULT" + set av [subst $::dispCfg($aid)] + } + set "arg$aix" $av + } + if {$cmd ne "?"} { + set rsct $::dispCfg(STORAGE_CLASS) + set rsct "$rsct $::dispCfg(RETURN_TYPE)" + set argexp [subst $::dispCfg(ARGS_SIGNATURE)] + set fname [subst $::dispCfg(DISPATCHEE_NAME)] + set funcOpen "$rsct $fname\($argexp\)$::lb" + set dispEntry [subst $::dispCfg(DISPATCH_ENTRY)] + emit_conditionally $cmd [linsert $body 0 $funcOpen] $inSrc $ostrm + set ::cmd_dispatch($cmd) [list $dispEntry] + set_src_tags dispatch $cmd $inSrc + } + } + return $iAte +} + +proc EMIT_DISPATCH {inSrc tailCap ostrm} { + # Emit the collected dispatch table entries, in command order, maybe + # wrapped with a conditional construct as set by CONDITION_COMMAND(). + foreach cmd [lsort [array names ::cmd_dispatch]] { + emit_conditionally $cmd $::cmd_dispatch($cmd) $inSrc $ostrm $tailCap + } + return 1 +} + +proc EMIT_HELP_TEXT {inSrc tailCap ostrm} { + # Emit the collected help text table entries, in command order, maybe + # wrapped with a conditional construct as set by CONDITION_COMMAND(). + foreach htc [lsort [array names ::cmd_help]] { + emit_conditionally $htc $::cmd_help($htc) $inSrc $ostrm $tailCap + } + return 1 +} + +proc say_usage {macros {extra {}}} { + puts stderr "Usage:$extra" + foreach m $macros {puts stderr " $m$::macroUsages($m)"} +} +proc yap_usage {got macro} { + puts stderr "Bad macro use: $got" + say_usage $macro +} + +# Perform any input collection or deferred output emits specified by a macro. +# Return number of input lines consumed, or 0 if not a recognized macro. +# This function may consume additional lines via triple inSrc. +proc do_macro {inSrc lx ostrm} { + if {![regexp $::macroKeywordTailRE $lx ma macro tail] \ + || ![info exists ::macroTailREs($macro)]} { + return 0 + } + # It's an attempted macro invocation line. Process or fail and yap. + set tailCap [regexp -inline $::macroTailREs($macro) $tail] + # Call like-named proc with any args captured by the corresponding RE. + return [$macro $inSrc [lrange $tailCap 1 end] $ostrm] +} + +array set ::typedefsSeen {} +array set ::includesDone {} + +# Filter redundant typedefs and certain includes and qualifiers, in place. +# Return 1 if line can be emitted as-is, 0 if to be processed further. +# In either case, the line named by $lineVar may have been changed. +proc transform_line {lineVar nesting} { + upvar $lineVar line if {[regexp {^typedef .*;} $line]} { - if {[info exists typedef_seen($line)]} { - return "/* $line */" + if {[info exists ::typedefsSeen($line)]} { + set line "/* $line */" + return 1 } - set ::typedefsSeen($line) 1 + if {[regexp {\s(\w+)\s*;} $line _ tdname]} { - if {[info exists typedef_seen($tdname)]} { - return "/* [regsub {;} $line {; **/}]" ++ if {[info exists ::typedefsSeen($tdname)]} { ++ set line "/* [regsub {;} $line {; **/}]" ++ return 1 + } - set typedef_seen($tdname) 1 ++ set ::typedefsSeen($tdname) 1 + } else { - set typedef_seen($line) 1 - } - } - return $line -} -set iLine 0 -while {1} { - set lx [omit_redundant_typedefs [gets $in]] - if {[eof $in]} break; - incr iLine - if {[regexp {^INCLUDE } $lx]} { - set cfile [lindex $lx 1] - puts $out "/************************* Begin $cfile ******************/" -# puts $out "#line 1 \"$cfile\"" - set in2 [open $topdir/src/$cfile] - fconfigure $in2 -translation binary - while {![eof $in2]} { - set lx [omit_redundant_typedefs [gets $in2]] - if {[regexp {^#include "sqlite} $lx]} { - set lx "/* $lx */" ++ set ::typedefsSeen($line) 1 ++ } + return 0 + } elseif {$nesting == 0} { + return 0 + } + if {[regexp {^#include "sqlite.*"} $line] + || [regexp {^# *include "test_windirent.h"} $line]} { + set line "/* $line */" + return 1 + } + if {$nesting > 0 && [regexp {^#include "([\w\.]+)"} $line _ incRelPath]} { + set fromPath [lindex $::incFileStack end] + set incPath [file join [file dirname $fromPath] $incRelPath] + set inTree [file exists $incPath] + if {$inTree} { + if {[info exists ::includesDone($incPath)]} { + set line "/* $line */" + return 1 + } else { + set line "INCLUDE $incRelPath" + set ::includesDone($incPath) 1 + return 0 + } + } + } + if {[string first "__declspec(dllexport)" $line] >= 0} { + set line [string map [list __declspec(dllexport) {}] $line] + return 1 + } + return 0 +} + + +set ::incFileStack {} + +# Read a named file and process its content to given output stream. +# Global ::incStack is maintained to support diagnostics. +# There is no (meaningful) return. +# +proc process_file { inFilepath ostrm } { + set linesRead 0 + if { [catch {set istrm [open $inFilepath r]}] } { + return -code error "Cannot read $inFilepath" + } else { + fconfigure $istrm -translation auto + set nesting [llength $::incFileStack] + lappend ::incFileStack $inFilepath + set inFns [list $inFilepath $istrm] + if {$nesting > 0} { + set sayPath [string map [list \ + "$::topDir/src/.." \ + "$::topDir/src" /src \ + ] $inFilepath] + set splats [string repeat * [expr {33 - [string length $sayPath]/2 }]] + set sayFile [list "/*$splats Begin $sayPath $splats*/"] + } else { set sayFile {} } + emit_sync $sayFile $ostrm $linesRead $inFilepath + while {1} { + set lin [gets $istrm] + if {[eof $istrm]} break + if {![transform_line lin $nesting]} { + set ni [do_macro [concat $inFns $linesRead] $lin $ostrm] + if {$ni > 0} { + incr linesRead $ni + continue + } } - if {[regexp {^# *include "test_windirent.h"} $lx]} { - set lx "/* $lx */" + emit_sync [list $lin] $ostrm $linesRead + incr linesRead + } + if {$nesting > 0} { + set sayFile [list "/**$splats End $sayPath $splats**/"] + emit_sync $sayFile $ostrm $linesRead $inFilepath + } + set ::incFileStack [lrange $::incFileStack 0 end-1] + close $istrm + } +} + +if {$runMode == "help"} { + # Show options and usage + say_usage [lsort [array names ::macroUsages]] { + mkshellc.tcl + may be either --help, --details, --parameters or any sequence of: + + -it = + -tcl + -no-line-directives + If no input files are specified, /src/shell.c.in is read. + Input files are read and processed in order, producing output to sdout. + The -it option associates a filename with an word which may + be encountered during execution of INCLUDE(...) directives in the input. + Input files may include macro lines or line sequences matching any of: + INCUDE } + puts stderr { + Use --details option for detailed effects of these macros. + Use --parameters option for DISPATCH_CONFIG parameter names and effects. + } + exit 0 +} elseif {$runMode == "details"} { + set sfd [open $argv0 r] + array set macdos [list] + while {![eof $sfd]} { + if {[regexp {^proc ([A-Z_]+\M)} [gets $sfd] ma macro]} { + if {[info exists ::macroTailREs($macro)]} { + set effects {} + while {[regexp {^\s+#\s*(.+)$} [gets $sfd] ma effect]} { + lappend effects " $effect" + } + set macdos($macro) [join $effects "\n"] } - set lx [string map [list __declspec(dllexport) {}] $lx] - puts $out $lx } - close $in2 - puts $out "/************************* End $cfile ********************/" -# puts $out "#line [expr $iLine+1] \"shell.c.in\"" - continue } - puts $out $lx + close $sfd + foreach m [lsort [array names macdos]] { + puts stderr "\nThe $m macro will:\n $macdos($m)" + } + exit 0 +} elseif {$runMode == "parameters"} { + puts stderr $::parametersHelp + exit 0 +} + +if {$runMode == "normal"} { + fconfigure $outStrm -translation {auto lf} + emit_sync [list $::headComment] $outStrm $::headCommentLines + foreach {f} $::inFiles { + process_file $f $outStrm + } + close $outStrm } -close $in -close $out + +exit $::iShuffleErrors