From: larrybr Date: Thu, 3 Feb 2022 20:57:47 +0000 (+0000) Subject: Scripting and .parameter enhancements most in; all in working X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2527e8b2f8164531c4508d01f989fa952944f399;p=thirdparty%2Fsqlite.git Scripting and .parameter enhancements most in; all in working FossilOrigin-Name: a1581118b07b111edbad8dc930a959ec0b98461b2737619d9976ef4a498b6571 --- 2527e8b2f8164531c4508d01f989fa952944f399 diff --cc manifest index 0f7e2c9f82,c4baec7ba3..e7750ac755 --- a/manifest +++ b/manifest @@@ -1,5 -1,5 +1,5 @@@ - C Take\sCLI's\swordwrap\sfrom\strunk - D 2022-02-01T15:08:10.395 -C A\sWIP\scheckin,\sprogress\stoward\swhat\s.help\spromises -D 2022-01-20T05:20:27.018 ++C Scripting\sand\s.parameter\senhancements\smost\sin;\sall\sin\sworking ++D 2022-02-03T20:57:47.860 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@@ -545,19 -544,19 +545,19 @@@ F src/parse.y 04f61db1cdd7036c6d74baad1 F src/pcache.c 084e638432c610f95aea72b8509f0845d2791293f39d1b82f0c0a7e089c3bb6b F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586 F src/pcache1.c 54881292a9a5db202b2c0ac541c5e3ef9a5e8c4f1c1383adb2601d5499a60e65 -F src/pragma.c c536665ce8431c8b1efbf7e0a5c01852f49f7bf28f1954f8118b2d28e4a3797f +F src/pragma.c 7c024d690a3dc93f61830f11f900e4af2357f31d081b0c79099ca5e28919cba7 F src/pragma.h 87330ed2fbfa2a1274de93ca0ab850fba336189228cb256089202c3b52766fad -F src/prepare.c 45fe7408eb78d80eca8392669070a6e5caf231b09e5c7b1ff65c1ad64a3734c5 +F src/prepare.c a187dade741c1f09ae118fcbbf0302511807bfc0355880927d7152eb75b8260d F src/printf.c 975f1f5417f2526365b6e6d7f22332e3e11806dad844701d92846292b654ba9a F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c -F src/resolve.c 359bc0e445d427583d2ab6110433a5dc777f64a0ecdf8d24826d8b475233ead9 +F src/resolve.c 24032ae57aec10df2f3fa2e20be0aae7d256bc704124b76c52d763440c7c0fe9 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 -F src/select.c a4a23a70f0a24a1103ac9698f6be181a6ec7ff6c19e03e8899c43cb6d2af09d6 -F src/shell.c.in 732a2ee32b11825b797754bacd77cb4b09ff813c2f77786fc5d9f3a803fa4997 -F src/sqlite.h.in a5e0d6bd47e67aabf1475986d36bdcc7bfa9e06566790ebf8e3aa7fa551c9f99 +F src/select.c a6d2d4bed279d7fe4fcedaf297eaf6441e8e17c6e3947a32d24d23be52ac02f2 - F src/shell.c.in 285e00069c9c1d0c55d70489cb13e7a75c08d44761e1015f7839f945a6a81cbd ++F src/shell.c.in af7f6c1140ad80505cdc7c5ba75399fae75d2512ee0202f1b5666805eb955929 +F src/sqlite.h.in eaade58049152dac850d57415bcced885ca27ae9582f8aea2cfb7f1db78a521b F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 -F src/sqlite3ext.h 01eb85e4f2759a5ee79c183f4b2877889d4ffdc49d27ae74529c9579e3c8c0ef -F src/sqliteInt.h 21a31abf60222f50c1d654cdc27ad9d4040249f0341129dd8286b8b5b32bcd30 +F src/sqlite3ext.h 5d54cf13d3406d8eb65d921a0d3c349de6126b732e695e79ecd4830ce86b4f8a +F src/sqliteInt.h 8ef2996e02476f73e41ba977f819bda0cc68b7ce238cf404b9b8930df57bc1d0 F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@@ -1386,10 -1384,10 +1386,10 @@@ 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 70f46b5d07776a107335c3c2c9cbd0431d44637bfeae1f6b9ded5e33b4c7c0bf +F test/shell1.test ce2f370886645f38fabdde44976c14a004400f166edea8fdd9741079b645fef6 F test/shell2.test f00a0501c00583cbc46f7510e1d713366326b2b3e63d06d15937284171a8787c - F test/shell3.test 3fed756f9e254d638b012fc018f5125e2f15bf79a824a4f474405b3aad9f0fb8 - F test/shell4.test 823b84d39d4dc7a78b7570342b7e43dc32805fa8ee92e5b40a89775c3170dac1 -F test/shell3.test cb4b835a901742c9719437a89171172ecc4a8823ad97349af8e4e841e6f82566 -F test/shell4.test 3ed6c4b42fd695efcbc25d69ef759dbb15855ca8e52ba6c5ee076f8b435f48be ++F test/shell3.test c073c2adda6d1aabe4450024ade4d45607f34d2fb76c2551cc89e70871191a3c ++F test/shell4.test 867e0675d7b096d6b93de534541e07c7f5ffafa3e6612695ecf55180802e1115 F test/shell5.test b85069bfcf3159b225228629ab2c3e69aa923d098fea8ea074b5dcd743522e2c F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3 F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f @@@ -1942,8 -1938,8 +1942,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 f51a17b6271a8dd7c48725e4ec2df1fde0460866c81c7225dc27216ab389591e 00b1b7020a564976da3237532434e47ccf17eb5d620e6ac45f3e70b5d5739200 - R de0a9f4bae0a768836bea9c7e678066d -P a94ab403eb836d3fcb9710d22da5129f58db05d3be145bc77ce1c017761e7894 -R df6ba47c6584db70fd023a69e282f313 ++P 768c70a926bd3d5338c4d7c07689e61f03973b4bc7d9d2c62c6f83a6c2b85829 4688e6dff88527dbff436b811512206d31a9695095776c4e1fdff95da0b0c4d4 ++R b8ba5718d1d75fb62d099309ab94cde4 U larrybr - Z 216ac63c3bf7c9a422cebb513acf55e0 -Z 2d0ff93e3043918f6083e9631f60e14c ++Z 6bf0561e73a5d6ab55c67a719c76d473 # Remove this line to create a well-formed Fossil manifest. diff --cc manifest.uuid index 741bc1c157,a2de2b2869..1ff7058c70 --- a/manifest.uuid +++ b/manifest.uuid @@@ -1,1 -1,1 +1,1 @@@ - 768c70a926bd3d5338c4d7c07689e61f03973b4bc7d9d2c62c6f83a6c2b85829 -4688e6dff88527dbff436b811512206d31a9695095776c4e1fdff95da0b0c4d4 ++a1581118b07b111edbad8dc930a959ec0b98461b2737619d9976ef4a498b6571 diff --cc src/shell.c.in index 0e403b433a,712ffe4734..17685a1ccd --- a/src/shell.c.in +++ b/src/shell.c.in @@@ -17,6 -17,6 +17,10 @@@ #define _CRT_SECURE_NO_WARNINGS #endif ++/* Define stringification of a bare word (or anything without commas.) */ ++# define SHELL_STRINGIFY_(f) #f ++# define SHELL_STRINGIFY(f) SHELL_STRINGIFY_(f) ++ /* ** Optionally #include a user-defined header, whereby compilation options ** may be set prior to where they take effect, but after platform setup. @@@ -24,9 -24,9 +28,7 @@@ ** file. Note that this macro has a like effect on sqlite3.c compilation. */ #ifdef SQLITE_CUSTOM_INCLUDE --# define INC_STRINGIFY_(f) #f --# define INC_STRINGIFY(f) INC_STRINGIFY_(f) --# include INC_STRINGIFY(SQLITE_CUSTOM_INCLUDE) ++# include SHELL_STRINGIFY(SQLITE_CUSTOM_INCLUDE) #endif /* @@@ -403,7 -403,7 +405,7 @@@ static void endTimer(void) static int bail_on_error = 0; /* --** Threat stdin as an interactive input if the following variable ++** Treat stdin as an interactive input if the following variable ** is true. Otherwise, assume stdin is connected to a file or pipe. */ static int stdin_is_interactive = 1; @@@ -624,17 -624,15 +626,78 @@@ static FILE * openChrSource(const char } /* --** This routine reads a line of text from FILE in, stores --** the text in memory obtained from malloc() and returns a pointer --** to the text. 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. +** +** 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){ ++static char *local_getline(char *zLine, InSource *pInSrc){ int nLine = zLine==0 ? 0 : 100; int n = 0; @@@ -644,7 -642,7 +707,7 @@@ zLine = realloc(zLine, nLine); shell_check_oom(zLine); } -- if( fgets(&zLine[n], nLine - n, in)==0 ){ ++ if( str_line_get(&zLine[n], nLine - n, pInSrc)==0 ){ if( n==0 ){ free(zLine); return 0; @@@ -663,7 -661,7 +726,7 @@@ #if defined(_WIN32) || defined(WIN32) /* For interactive input on Windows systems, translate the ** multi-byte characterset characters into UTF-8. */ -- if( stdin_is_interactive && in==stdin ){ ++ if( stdin_is_interactive && pInSrc==&stdInSource) ){ char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0); if( zTrans ){ int nTrans = strlen30(zTrans)+1; @@@ -679,95 -677,12 +742,13 @@@ return zLine; } - /* - ** Arrange for shell input from either a FILE or a string. - ** See getline_from(...) for applicable invariants. It is - ** the only modifier of data within this struct, except for - ** initialization which is either {0, zS} or {pF, 0}, (with - ** the iReadOffset always 0-initialized), left to whatever - ** routine is switching input sources. - */ - 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. */ - const char *zSourceSay; /* For complaints, keep a name for this source */ - struct InSource *pFrom; /* and redirect tracking to aid unraveling. */ - } InSource; + /* - ** Read single lines of input text from source selected by pInSource. - ** This function presents an interface similar to local_getline(...), - ** to which it defers for FILE input. (This function may be considered - ** to be an adapter except for the source parameter. With a rewrite, - ** it and local_getline can merge, saving most of 26 LOC. ) - */ - static char *getline_from( char *zLine, InSource *pSource ){ - if( pSource->inFile!=0 ){ - return local_getline(zLine, pSource->inFile); - }else if( pSource->zStrIn!=0 - && pSource->zStrIn[pSource->iReadOffset]!=0 ){ - /* This code mostly copied from local_getline(); It begs for refactor. */ - int nLine = zLine==0 ? 0 : 100; - int n = 0; - - while( 1 ){ - if( n+100>nLine ){ - nLine = nLine*2 + 100; - zLine = realloc(zLine, nLine); - shell_check_oom(zLine); - } - { - /* This block is where fgets() became "read string". */ - char *zBegin = pSource->zStrIn + pSource->iReadOffset; - int iTake = 0; - char c = zBegin[iTake]; - if( c==0 ){ - if( n==0 ){ - free(zLine); - return 0; - } - zLine[n] = 0; - break; - }else{ - while( iTakeiReadOffset += iTake; - } - } - /* Above block replaced this commented code: */ - /* if( fgets(&zLine[n], nLine - n, in)==0 ){ */ - /* if( n==0 ){ */ - /* free(zLine); */ - /* return 0; */ - /* } */ - /* zLine[n] = 0; */ - /* break; */ - /* } */ - while( zLine[n] ) n++; - if( n>0 && zLine[n-1]=='\n' ){ - n--; - if( n>0 && zLine[n-1]=='\r' ) n--; - zLine[n] = 0; - break; - } - } - return zLine; - }else{ - return 0; - } - } - - /* --** 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. @@@ -775,32 -690,25 +756,33 @@@ ** 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 not free it. ++** 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){ - char *zPrompt; - char *zResult; - if( pInSrc!=0 ){ - zResult = getline_from(zPrior, pInSrc); ++ 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 printf("%s", zPrompt); fflush(stdout); -- zResult = local_getline(zPrior, stdin); ++ 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; } @@@ -1006,7 -914,7 +988,7 @@@ static void shellModuleSchema char *zFake; UNUSED_PARAMETER(nVal); zName = (const char*)sqlite3_value_text(apVal[0]); -- zFake = zName ? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0; ++ zFake = zName? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0; if( zFake ){ sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake), -1, sqlite3_free); @@@ -1158,58 -1066,6 +1140,55 @@@ struct EQPGraph char zPrefix[100]; /* Graph prefix */ }; - /* Input source switching is done through one of these (defined below) */ - typedef struct InSource InSource; - +/* 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<lineno); ++ raw_printf(stderr, "line %d: ", p->pInSource->lineno); utf8_printf(stderr, "%s\n", zMsg); exit(1); } @@@ -2716,9 -2554,9 +2695,11 @@@ static char *shell_error_context(const zCode = sqlite3_mprintf("%.*s", len, zSql); for(i=0; zCode[i]; i++){ if( IsSpace(zSql[i]) ) zCode[i] = ' '; } if( iOffset<25 ){ -- zMsg = sqlite3_mprintf("\n %z\n %*s^--- error here", zCode, iOffset, ""); ++ zMsg = sqlite3_mprintf("\n %z\n %*s^--- error here", ++ zCode, iOffset, ""); }else{ -- zMsg = sqlite3_mprintf("\n %z\n %*serror here ---^", zCode, iOffset-14, ""); ++ zMsg = sqlite3_mprintf("\n %z\n %*serror here ---^", ++ zCode, iOffset-14, ""); } return zMsg; } @@@ -3192,38 -3030,29 +3173,45 @@@ static void restore_debug_trace_modes(v sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &savedWhereTrace); } +/* DB schema protection for use by next two utility functions */ +typedef struct DbProtectState { + int wrSchema; + int defensiveMode; +} DbProtectState; + +/* Allow system (sqlite_*) schema changes, return prior protection state. */ +static DbProtectState allow_sys_schema_change(sqlite3 *db){ + DbProtectState rv; + sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, -1, &rv.defensiveMode); + sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0); + sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, -1, &rv.wrSchema); + sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, 0); + return rv; +} + +/* Restore protection state using allow_sys_schema_change() return. */ +static void restore_sys_schema_protection( sqlite3 *db, DbProtectState *pPS ){ + sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, pPS->wrSchema, 0); + sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, pPS->defensiveMode, 0); +} + - /* Create the TEMP table used to store parameter bindings */ - static void bind_table_init(ShellState *p){ + /* Partition the temp.sqlite_parameters key namespace according to use. */ -enum ParamTableUses { PTU_Binding = 0, PTU_Script = 1 }; ++typedef enum { PTU_Binding = 0, PTU_Script, PTU_Source } ParamTableUse; + #define PARAM_TABLE_NAME "sqlite_parameters" + #define PARAM_TABLE_SCHEMA "temp" + #define PARAM_TABLE_SNAME PARAM_TABLE_SCHEMA"."PARAM_TABLE_NAME + + /* Create the TEMP table used to store parameter bindings and SQL statements */ + static void param_table_init(ShellState *p){ - int wrSchema = 0; - int defensiveMode = 0; - sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, -1, &defensiveMode); - sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0); - sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, -1, &wrSchema); - sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, 0); + DbProtectState dps = allow_sys_schema_change(p->db); sqlite3_exec(p->db, -- "CREATE TABLE IF NOT EXISTS temp.sqlite_parameters(\n" ++ "CREATE TABLE IF NOT EXISTS "PARAM_TABLE_SNAME"(\n" " key TEXT PRIMARY KEY,\n" - " value\n" + " value,\n" + " uses INT DEFAULT (0)" /* aka PTU_Binding */ ") WITHOUT ROWID;", 0, 0, 0); - sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, wrSchema, 0); - sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, defensiveMode, 0); + restore_sys_schema_protection( p->db, &dps ); } /* @@@ -3246,14 -3077,15 +3236,16 @@@ static void bind_prepared_stmt(ShellSta nVar = sqlite3_bind_parameter_count(pStmt); if( nVar==0 ) return; /* Nothing to do */ -- if( sqlite3_table_column_metadata(pArg->db, "TEMP", "sqlite_parameters", ++ if( sqlite3_table_column_metadata(pArg->db, PARAM_TABLE_SCHEMA, ++ PARAM_TABLE_NAME, "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){ return; /* Parameter table does not exist */ } rc = sqlite3_prepare_v2(pArg->db, -- "SELECT value FROM temp.sqlite_parameters" - " WHERE key=?1", -1, &pQ, 0); ++ "SELECT value FROM "PARAM_TABLE_SNAME + " WHERE key=?1 AND uses=0", -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); @@@ -3281,7 -3113,7 +3273,7 @@@ ** | ** 3 ** --** Each box characters has between 2 and 4 of the lines leading from ++** Each box character has between 2 and 4 of the lines leading from ** the center. The characters are here identified by the numbers of ** their corresponding lines. */ @@@ -4553,6 -4186,18 +4549,18 @@@ static const char *(azHelp[]) = " Options:", " --indent Try to pretty-print the schema", " --nosys Omit objects whose names start with \"sqlite_\"", - ".script CMD ... Manage SQL statement store", ++ ".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", ".selftest ?OPTIONS? Run tests defined in the SELFTEST table", " Options:", " --init Create a new SELFTEST table", @@@ -4624,27 -4268,11 +4632,29 @@@ ".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", + " Negative values right-justify", + ".x NAME ... 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", + ".x ?NAMES? Execute the named stored SQL statements in order", + " This is merely a shorter alias for .script execute ?NAMES?" }; - /* This literal's value and address is used for help's workings. */ ++/* This literal's value AND address are used for help's workings. */ +static const char *zHelpAll = "-all"; + /* ** Output help text. ** @@@ -4863,36 -4481,35 +4873,38 @@@ int deduceDatabaseType(const char *zNam /* ** 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 either the present - ** input file, or standard input if there is no file open for input. -** 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; -- int nLine; int n = 0; int pgsz = 0; int iOffset = 0; -- int j, k; ++ int j, k, nlError; int rc; - FILE *in = 0; - FILE *in; ++ static const char *zEndMarker = "| end "; const char *zDbFilename = p->pAuxDb->zDbFilename; ++ /* Need next two objects only if redirecting input to get the hex. */ ++ InSource inRedir = INSOURCE_FILE_REDIR(0, zDbFilename, p->pInSource); 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); return 0; } -- nLine = 0; ++ p->pInSource = &inRedir; }else{ - if( p->pInSource!=0 ) in = p->pInSource->inFile; - in = p->in; -- nLine = p->lineno; -- if( in==0 ) in = stdin; ++ /* 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); ++ } } *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; @@@ -4905,13 -4522,13 +4917,13 @@@ utf8_printf(stderr, "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; continue; } -- if( strncmp(zLine, "| end ", 6)==0 ){ ++ if( strncmp(zLine, zEndMarker, 6)==0 ){ break; } rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", @@@ -4925,25 -4542,27 +4937,26 @@@ } } } -- *pnData = n; - if( zDbFilename ) fclose(in); - else p->lineno = nLine; - if( in!=p->in ){ - fclose(in); - }else{ - p->lineno = nLine; ++ *pnData = n; /* Record success and size. */ ++ readHexDb_cleanup: ++ if( p->pInSource==&inRedir ){ ++ fclose( inRedir.inFile ); ++ p->pInSource = inRedir.pFrom; + } return a; --readHexDb_error: - if( zDbFilename ){ - if( in!=p->in ){ -- fclose(in); -- }else{ - int nLinePass = nLine; - while( fgets(zLine, sizeof(zLine), in)!=0 ){ - nLinePass++; - 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 = nLinePass; - p->lineno = nLine; } sqlite3_free(a); -- utf8_printf(stderr,"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); ++ goto readHexDb_cleanup; } #endif /* SQLITE_OMIT_DESERIALIZE */ @@@ -5397,11 -5009,14 +5410,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", -- zArg); ++ zArg); return 0; } @@@ -6509,7 -6125,7 +6522,7 @@@ static void shellPrepare /* ** Create a prepared statement using printf-style arguments for the SQL. ** --** This routine is could be marked "static". But it is not always used, ++** This routine could be marked "static". But it is not always used, ** depending on compile-time options. By omitting the "static", we avoid ** nuisance compiler warnings about "defined but not used". */ @@@ -8010,128 -7626,79 +8023,204 @@@ static int recoverDatabaseCmd(ShellStat } #endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */ ++/* ++** The .shxopts command, for setting or listing shell extension options. ++ */ +#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; + } + } + }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; +} +#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, "TEMP", "sqlite_parameters", ++ 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 temp.sqlite_parameters table exists.\n"); ++ utf8_printf(stderr, "No "PARAM_TABLE_SNAME" table exists.\n"); + return 1; + } + rc = sqlite3_prepare_v2 - (p->db, "SELECT value FROM temp.sqlite_parameters WHERE key=$1", ++ (p->db, "SELECT value FROM "PARAM_TABLE_SNAME ++ " WHERE key=$1 AND uses=1", + -1, &pStmt, 0); + if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "temp.sqlite_parameters is wrongly created.\n"); ++ 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); + 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 it or some other character is likely to be put in its - * place during process_input() line or group handling, along ++ * 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 at this next line. ++ * 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 inSourceDivert = - {0, zSubmit, 0, azArg[ia], p->pInSource }; - InSource *pInSrcSave = p->pInSource; - int savedLineno = p->lineno; ++ InSource inRedir ++ = INSOURCE_STR_REDIR(zSubmit, azArg[ia], p->pInSource); + shell_check_oom(zSubmit); - p->pInSource = &inSourceDivert; ++ p->pInSource = &inRedir; + rc = process_input(p); + sqlite3_free(zSubmit); - p->pInSource = pInSrcSave; - p->lineno = savedLineno; ++ p->pInSource = inRedir.pFrom; + }else{ + continue; /* All white, ignore. */ + } + }else{ + utf8_printf(stderr, + "Skipping parameter '%s' (not set.)\n", azArg[ia]); + ++nErrors; + } + }else{ + utf8_printf(stderr, + "Skipping badly named %s. Run \".help x\"\n", azArg[ia]); + ++nErrors; + } + } + 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; + return 0; + } -/* Edit one named parameter in the parameters table. If it does not ++ ++/* ++ * 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, + int uses, const char * zEditor){ + struct param_row paramVU = {0,0,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( eval!=0 ){ + /* ToDo: Run an eval-like update, leaving as-is if it's a bad expr. */ + // int rv = edit_one_param(db, name, 0, uses); + } + if( paramVU.hits==1 && paramVU.uses==uses){ + /* Editing an existing value of same kind. */ + sqlite3_free(paramVU.value); + zSql = sqlite3_mprintf + ("UPDATE "PARAM_TABLE_SNAME" SET value=edit(value, %Q) WHERE" + " key=%Q AND uses=%d", zEditor, name, uses); + }else{ + /* Editing a new value of same kind. */ + assert(paramVU.value==0); + zSql = sqlite3_mprintf + ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)" + " VALUES (%Q,edit('-- %q%s', %Q),%d)", + name, name, "\n", zEditor, uses); + } + shell_check_oom(zSql); + sqlite3_exec(db, zSql, 0, 0, 0); + sqlite3_free(zSql); + return 0; /* ToDo: add some error checks */ + } + + static void append_in_clause(sqlite3_str *pStr, char **azBeg, char **azLim){ + /* 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 and .script ?NAMES? options, + * where a missing list means all the qualifying entries. + */ + if( azBeg==azLim ) sqlite3_str_appendf(pStr, "NOT NULL"); + else{ + char cSep = '('; + sqlite3_str_appendf(pStr, "IN"); + while( azBegzNonce==0 || strcmp(azArg[1],p->zNonce)!=0 ){ raw_printf(stderr, "line %d: incorrect nonce: \"%s\"\n", -- p->lineno, azArg[1]); ++ p->pInSource->lineno, azArg[1]); exit(1); }else{ p->bSafeMode = 0; @@@ -9673,8 -9169,8 +9762,8 @@@ }else if( (c=='o' -- && (strncmp(azArg[0], "output", n)==0||strncmp(azArg[0], "once", n)==0)) -- || (c=='e' && n==5 && strcmp(azArg[0],"excel")==0) ++ && (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; @@@ -9805,7 -9368,7 +9961,7 @@@ int len = 0; rx = sqlite3_prepare_v2(p->db, "SELECT max(length(key)) " -- "FROM temp.sqlite_parameters;", -1, &pStmt, 0); ++ "FROM "PARAM_TABLE_SNAME, -1, &pStmt, 0); if( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ len = sqlite3_column_int(pStmt, 0); if( len>40 ) len = 40; @@@ -9814,11 -9377,11 +9970,13 @@@ pStmt = 0; if( len ){ rx = sqlite3_prepare_v2(p->db, -- "SELECT key, quote(value) " -- "FROM temp.sqlite_parameters;", -1, &pStmt, 0); ++ "SELECT key, quote(value), uses " ++ "FROM "PARAM_TABLE_SNAME, -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)); ++ utf8_printf(p->out, "%-*s %s %d\n", len, ++ sqlite3_column_text(pStmt,0), ++ sqlite3_column_text(pStmt,1), ++ sqlite3_column_int(pStmt,2)); } sqlite3_finalize(pStmt); } @@@ -9844,10 -9407,10 +10002,11 @@@ sqlite3_stmt *pStmt; const char *zKey = azArg[2]; const char *zValue = azArg[3]; - bind_table_init(p); ++ u8 ptu = isalpha(*zKey)? PTU_Script : PTU_Binding; + param_table_init(p); zSql = sqlite3_mprintf( -- "REPLACE INTO temp.sqlite_parameters(key,value)" -- "VALUES(%Q,%s);", zKey, zValue); ++ "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" ++ "VALUES(%Q,%s,%d);", zKey, zValue, ptu); shell_check_oom(zSql); pStmt = 0; rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); @@@ -9856,8 -9419,8 +10015,8 @@@ sqlite3_finalize(pStmt); pStmt = 0; zSql = sqlite3_mprintf( -- "REPLACE INTO temp.sqlite_parameters(key,value)" -- "VALUES(%Q,%Q);", zKey, zValue); ++ "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" ++ "VALUES(%Q,%Q,%d);", zKey, zValue, ptu); shell_check_oom(zSql); rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); @@@ -9878,7 -9441,7 +10037,7 @@@ */ if( nArg==3 && strcmp(azArg[1],"unset")==0 ){ char *zSql = sqlite3_mprintf( -- "DELETE FROM temp.sqlite_parameters WHERE key=%Q", azArg[2]); ++ "DELETE FROM "PARAM_TABLE_SNAME" WHERE key=%Q", azArg[2]); shell_check_oom(zSql); sqlite3_exec(p->db, zSql, 0, 0, 0); sqlite3_free(zSql); @@@ -9983,20 -9547,11 +10142,17 @@@ utf8_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); rc = 1; }else{ + fCloser = fclose; + } + if( inUse!=0 ){ - InSource *pInSrcSave = p->pInSource; - int savedLineno = p->lineno; - InSource inSourceDivert = - {inUse, 0, 0, azArg[1], p->pInSource}; - p->pInSource = &inSourceDivert; ++ 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 = pInSrcSave; - p->lineno = savedLineno; ++ p->pInSource = p->pInSource->pFrom; } - p->in = inSaved; - p->lineno = savedLineno; }else if( c=='r' && n>=3 && strncmp(azArg[0], "restore", n)==0 ){ @@@ -10093,7 -9648,7 +10249,8 @@@ }else if( zName==0 ){ zName = azArg[ii]; }else{ -- raw_printf(stderr, "Usage: .schema ?--indent? ?--nosys? ?LIKE-PATTERN?\n"); ++ raw_printf(stderr, ++ "Usage: .schema ?--indent? ?--nosys? ?LIKE-PATTERN?\n"); rc = 1; goto meta_command_exit; } @@@ -10157,9 -9712,9 +10314,9 @@@ #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); ++ " UNION ALL SELECT shell_module_schema(name)," ++ " 'table', name, name, name, 9e+99, 'main'" ++ " FROM pragma_module_list", 0); } #endif appendText(&sSelect, ") WHERE ", 0); @@@ -10206,14 -9761,8 +10363,15 @@@ } }else + if( c=='s' && n==7 && strncmp(azArg[0], "seeargs", n)==0 ){ + for( n=1; nout, "%s%s", azArg[n], (n==nArg-1)? "|\n" : "|"); + } + }else + if( c=='s' && n==11 && strncmp(azArg[0], "selecttrace", n)==0 ){ -- unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff; ++ unsigned int x ++ = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff; sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x); }else @@@ -10396,7 -9945,7 +10554,8 @@@ } } if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){ -- raw_printf(stderr, "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession)); ++ raw_printf(stderr, "Maximum of %d sessions\n", ++ ArraySize(pAuxDb->aSession)); goto meta_command_exit; } pSession = &pAuxDb->aSession[pAuxDb->nSession]; @@@ -10978,7 -10513,7 +11137,7 @@@ }else if( aCtrl[iCtrl].unSafe && p->bSafeMode ){ utf8_printf(stderr, "line %d: \".testctrl %s\" may not be used in safe mode\n", -- p->lineno, aCtrl[iCtrl].zCtrlName); ++ p->pInSource->lineno, aCtrl[iCtrl].zCtrlName); exit(1); }else{ switch(testctrl){ @@@ -11352,7 -10887,7 +11511,8 @@@ }else if( c=='w' && strncmp(azArg[0], "wheretrace", n)==0 ){ -- unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff; ++ unsigned int x ++ = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff; sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &x); }else @@@ -11384,16 -10915,6 +11544,16 @@@ meta_command_exit if( p->outCount==0 ) output_reset(p); } p->bSafeMode = p->bSafeModePersist; +#if SHELL_VARIABLE_EXPANSION + if( bExpVars ){ - /* Free any arguments that had to be allocated rather than tokenized in place. */ ++ /* Free any arguments that are allocated rather than tokenized in place. */ + for( n=1; n0 && iArgOffsetlineno = 0; - while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){ - fflush(p->out); - zLine = one_input_line(p->in, zLine, nSql>0); - if( zLine==0 ){ - /* End of input */ - if( p->in==0 && stdin_is_interactive ) printf("\n"); - break; - } - if( seenInterrupt ){ - if( p->in!=0 ) break; - seenInterrupt = 0; - } - p->lineno++; - if( QSS_INPLAIN(qss) - && line_is_command_terminator(zLine) - && line_is_complete(zSql, nSql) ){ - memcpy(zLine,";",2); - } - qss = quickscan(zLine, qss); - if( QSS_PLAINWHITE(qss) && nSql==0 ){ - if( ShellHasFlag(p, SHFLG_Echo) ) - printf("%s\n", zLine); - /* Just swallow single-line whitespace */ - qss = QSS_Start; +** Determines if a dot-command is open, having either an unclosed +** quoted argument or an escape sequence opener ('\') at its end. +** +** The FSM design/behavior assumes/requires that a terminating '\' +** is not part of the character sequence being classified -- that +** it represents an escaped newline which is removed as physical +** lines are spliced to accumulate logical lines. +** +** The line or added line-portion is passed as zCmd. +** The pScanState pointer must reference an (opaque) DCmd_ScanState, +** which must be set to DCSS_Start to initialize the scanner state. +** Resumed scanning should always be done with zCmd logically just +** past the last non-0 char of the text previously passed in, with +** any previously scanned, trailing newline escape first trimmed. +** Returns are: 0 => not open (aka complete), 1 => is open (incomplete) +** The following macros may be applied to the scan state: +*/ +#define DCSS_InDarkArg(dcss) ((dcss)&argPosMask==inDqArg) +#define DCSS_EndEscaped(dcss) (((dcss)&endEscaped)!=0) +#define DCSS_IsOpen(dcss) (((dcss) & isOpenMask)!=0) +typedef enum { + DCSS_Start = 0, + twixtArgs = 0, inSqArg = 1, inDarkArg = 2, inDqArg = 3, /* ordered */ + endEscaped = 4, /* bit used */ + argPosMask = 3, /* bits used */ + isOpenMask = 1|4 /* bit test */ +} DCmd_ScanState; + +static void dot_command_scan(char *zCmd, DCmd_ScanState *pScanState){ + DCmd_ScanState ss = *pScanState & ~endEscaped; + char c = (ss&isOpenMask)? 1 : *zCmd++; + while( c!=0 ){ + switch( ss ){ + twixt: + case twixtArgs: + while( IsSpace(c) ){ + if( (c=*zCmd++)==0 ) goto atEnd; + } + switch( c ){ + case '\\': + if( *zCmd==0 ){ + ss |= endEscaped; + goto atEnd; + }else goto inDark; + case '\'': ss = inSqArg; goto inSq; + case '"': ss = inDqArg; goto inDq; + default: ss = inDarkArg; goto inDark; + } + inSq: + case inSqArg: + while( (c=*zCmd++)!='\'' ){ + if( c==0 ) goto atEnd; + if( c=='\\' && *zCmd==0 ){ + ss |= endEscaped; + goto atEnd; + } + } + ss = twixtArgs; + c = *zCmd++; continue; - } - if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){ - if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zLine); - if( zLine[0]=='.' ){ - rc = do_meta_command(zLine, p); - if( rc==2 ){ /* exit requested */ - break; - }else if( rc ){ - errCnt++; + inDq: + case inDqArg: + do { + if( (c=*zCmd++)==0 ) goto atEnd; + if( c=='\\' ){ + if( (c=*zCmd++)==0 ){ + ss |= endEscaped; + goto atEnd; + } + if( (c=*zCmd++)==0 ) goto atEnd; } + } while( c!='"' ); + ss = twixtArgs; + c = *zCmd++; + continue; + inDark: + case inDarkArg: + while( !IsSpace(c) ){ + if( c=='\\' && *zCmd==0 ){ + ss |= endEscaped; + goto atEnd; + } + if( (c=*zCmd++)==0 ) goto atEnd; } - qss = QSS_Start; + ss = twixtArgs; + c = *zCmd++; continue; } - /* No single-line dispositions remain; accumulate line(s). */ - nLine = strlen30(zLine); - if( nSql+nLine+2>=nAlloc ){ - /* Grow buffer by half-again increments when big. */ - nAlloc = nSql+(nSql>>1)+nLine+100; - zSql = realloc(zSql, nAlloc); - shell_check_oom(zSql); + } + atEnd: + *pScanState = ss; +} +#else +# define dot_command_scan(x,y) +#endif + +/* Utility functions for process_input. */ + +#if SHELL_EXTENDED_PARSING +/* +** Process dot-command line with its scan state to: +** 1. Setup for requested line-splicing; and +** 2. Say whether it is complete. +** The last two out parameters are the line's length, which may be +** adjusted, and the char to be used for joining a subsequent line. +** This is broken out of process_input() mainly for readability. +** The return is TRUE for dot-command ready to run, else false. +*/ +static int line_join_ends(DCmd_ScanState dcss, char *zLine, + int *pnLength, char *pcLE){ + /* It is ready only if has no open argument or escaped newline. */ + int bOpen = DCSS_IsOpen(dcss); + if( !DCSS_EndEscaped(dcss) ){ + *pcLE = '\n'; + return !bOpen; + }else{ + *pcLE = (bOpen || DCSS_InDarkArg(dcss))? 0 : ' '; + /* Swallow the trailing escape character. */ + zLine[--*pnLength] = 0; + return 0; + } +} +#endif + +/* +** Grow the accumulation line buffer to accommodate ncNeed chars. +** In/out parameters pz and pna reference the buffer and its size. +** The buffer must eventually be sqlite3_free()'ed by the caller. +*/ +static void grow_line_buffer(char **pz, int *pna, int ncNeed){ + if( ncNeed > *pna ){ + *pna += *pna + (*pna>>1) + 100; + *pz = sqlite3_realloc(*pz, *pna); + shell_check_oom(*pz); + } +} + +/* +** Read input from designated source (p->pInSource) and process it. +** If pInSource==0 then input is interactive - the user is typing it. +** Otherwise, input is coming from a file, stream device or string. +** Prompts issue and history is saved only for interactive input. +** An interrupt signal will cause this routine to exit immediately, +** with "exit demanded" code returned, unless input is interactive. +** +** Returns: +** 0 => no errors +** 1 => errors>0 +** 2 => exit demanded, no errors. +** 3 => exit demanded, errors>0 +*/ +static int process_input(ShellState *p){ + 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==0 && stdin_is_interactive); ++ 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 ){ - InSource *pInSrc = p->pInSource; - int i; - utf8_printf - (stderr, "Input nesting limit (%d) reached, from line %d of \"%s\",\n", - MAX_INPUT_NESTING, p->lineno, pInSrc->zSourceSay); - for( i=0; i<3 && (pInSrc=pInSrc->pFrom)!=0; ++i ) - utf8_printf(stderr, " from \"%s\"", pInSrc->zSourceSay); ++ 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, ++ "%s from line %d of \"%s\"", ++ zLead, pInSrc->lineno, pInSrc->zSourceSay); ++ zLead = (i%2==0)? "\n" : ""; ++ pInSrc=pInSrc->pFrom; + } - 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; + utf8_printf(stderr, " ...\nERROR: Check recursion.\n"); + return 1; + } + ++p->inputNesting; - p->lineno = 0; + + /* 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); + zLineInput = one_input_line(p->pInSource, zLineInput, nGroupLines>0); + if( zLineInput==0 ){ + bInputEnd = 1; + inKind = Eof; + disposition = Ignore; + if( bInteractive ) printf("\n"); }else{ - zSql[nSql++] = '\n'; - memcpy(zSql+nSql, zLine, nLine+1); - nSql += nLine; - } - if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){ - errCnt += runOneSqlLine(p, zSql, p->in, startline); - nSql = 0; - if( p->outCount ){ - output_reset(p); - p->outCount = 0; - }else{ - clearTempFile(p); + ++nGroupLines; - p->lineno++; - iStartline = p->lineno; ++ 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; - p->lineno++; + ncLineIn = strlen30(zLineInput); + /* Scan line just input (if needed) and append to accumulation. */ + switch( inKind ){ + case Cmd: + dot_command_scan(zLineInput, &dcScanState); + break; + case Sql: + sql_prescan(zLineInput, &sqScanState); + break; + default: + break; + } + grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+ncLineIn+2); + /* Join lines as setup by exam of previous line(s). */ + if( cLineEnd!=0 ) zLineAccum[ncLineAcc++] = cLineEnd; + cLineEnd = '\n'; /* reset to default after use */ + memcpy(zLineAccum+ncLineAcc, zLineInput, ncLineIn); + iLastLine = ncLineAcc; + ncLineAcc += ncLineIn; + zLineAccum[ncLineAcc] = 0; + } /* end glom another line */ + } /* end group collection loop */ + /* Here, the group is fully collected or known to be incomplete forever. */ + switch( disposition ){ + case Dumpable: + echo_group_input(p, *pzLineUse); + break; + case Runnable: + switch( inKind ){ + case Sql: + /* runOneSqlLine() does its own echo when requested. */ - nErrors += runOneSqlLine(p, *pzLineUse, p->pInSource!=0, iStartline); ++ 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, p) ){ + default: ++nErrors; /* fall thru */ + case 0: break; + case 2: bExitDemand = 1; break; + } + break; + default: + assert(inKind!=Tbd); + break; } - p->bSafeMode = p->bSafeModePersist; - qss = QSS_Start; - }else if( nSql && QSS_PLAINWHITE(qss) ){ - if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zSql); - nSql = 0; - qss = QSS_Start; + break; + case Erroneous: + utf8_printf(stderr, "Error: Input incomplete at line %d of \"%s\"\n", - p->lineno, - (p->pInSource!=0)? p->pInSource->zSourceSay : ""); ++ 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); - } - free(zSql); - free(zLine); - return 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); } /* @@@ -12060,21 -11290,13 +12223,19 @@@ static void process_sqliterc shell_check_oom(zBuf); sqliterc = zBuf; } - p->in = fopen(sqliterc,"rb"); - if( p->in ){ + inUse = fopen(sqliterc,"rb"); + if( inUse!=0 ){ - InSource *pInSrcSave = p->pInSource; - int savedLineno = p->lineno; - InSource inSourceDivert = {inUse, 0, 0, sqliterc, p->pInSource}; ++ InSource inSourceRedir ++ = INSOURCE_FILE_REDIR(inUse, sqliterc, p->pInSource); + int rc; - p->pInSource = &inSourceDivert; ++ p->pInSource = &inSourceRedir; if( stdin_is_interactive ){ utf8_printf(stderr,"-- Loading resources from %s\n",sqliterc); } - if( process_input(p) && bail_on_error ) exit(1); - fclose(p->in); + rc = process_input(p); + fclose(inUse); - p->pInSource = pInSrcSave; - p->lineno = savedLineno; ++ p->pInSource = inSourceRedir.pFrom; /* 0 when called by main() */ + if( rc!=0 && bail_on_error ) exit(1); }else if( sqliterc_override!=0 ){ utf8_printf(stderr,"cannot open: \"%s\"\n", sqliterc); if( bail_on_error ) exit(1); @@@ -12189,8 -11410,8 +12350,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); } /* @@@ -12537,7 -11753,7 +12698,7 @@@ int SQLITE_CDECL wmain(int argc, wchar_ /* Process the initialization file if there is one. If no -init option ** is given on the command line, look for a file named ~/.sqliterc and -- ** try to process it. ++ ** try to process it, without any quitting or bail-on-error. */ process_sqliterc(&data,zInitFile); @@@ -12594,7 -11810,7 +12755,7 @@@ }else if( strcmp(z,"-ascii")==0 ){ data.mode = MODE_Ascii; sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Unit); -- sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, SEP_Record); ++ sqlite3_snprintf(sizeof(data.rowSeparator),data.rowSeparator, SEP_Record); }else if( strcmp(z,"-tabs")==0 ){ data.mode = MODE_List; sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Tab); @@@ -12789,7 -11994,7 +12950,7 @@@ #elif HAVE_LINENOISE linenoiseSetCompletionCallback(linenoise_completion); #endif - data.pInSource = 0; /* read from stdin interactively */ - data.in = 0; ++ data.pInSource = &termInSource; /* read from stdin interactively */ rc = process_input(&data); if( zHistory ){ shell_stifle_history(2000); @@@ -12797,7 -12002,7 +12958,7 @@@ free(zHistory); } }else{ - data.pInSource = &inputSource; /* read from stdin without prompts */ - data.in = stdin; ++ data.pInSource = &stdInSource; /* read from stdin without prompts */ rc = process_input(&data); } } diff --cc test/shell3.test index c9367f7fc1,243da976fa..b42f2d534d --- a/test/shell3.test +++ b/test/shell3.test @@@ -98,7 -98,7 +98,7 @@@ do_test shell3-2.6 } {0 {}} do_test shell3-2.7 { catchcmd "foo.db" "CREATE TABLE" - } {1 {Error: Input incomplete at line 1 of "stdin"}} -} {1 {Error: near line 1: in prepare, incomplete input (1)}} ++} {1 {Error: Input incomplete at line 1 of ""}} #---------------------------------------------------------------------------- diff --cc test/shell4.test index 17ee3b8e30,9e3c58fbec..a7491148ef --- a/test/shell4.test +++ b/test/shell4.test @@@ -139,13 -138,4 +139,13 @@@ do_test shell4-3.2 exec $::CLI :memory: --interactive ".read t1.txt" } {pound: £} +do_test shell4-4.1 { + set fd [open t1.txt wb] + puts $fd ".read t1.txt" + close $fd + catchcmd ":memory:" ".read t1.txt" - } {1 {Input nesting limit (25) reached, from line 1 of "t1.txt", - from "t1.txt" from "t1.txt" from "t1.txt" ... ++} {1 {Input nesting limit (25) reached, from line 1 of "t1.txt" ++ from line 1 of "t1.txt" from line 1 of "t1.txt" ... +ERROR: Check recursion.}} + finish_test