From: larrybr Date: Mon, 31 Jan 2022 19:23:32 +0000 (+0000) Subject: Cleanup dot-command handling, make multi-line work, honor exit requests from more... X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=d7596a2a0027458aee0c97b118b35eb458c57444;p=thirdparty%2Fsqlite.git Cleanup dot-command handling, make multi-line work, honor exit requests from more contexts FossilOrigin-Name: 5cf66e89071b619d116a4707b6ff1cd7917edffe5ab504514f37da433a77b61d --- diff --git a/manifest b/manifest index 959350d87a..1fdb6dec8b 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Sync\swith\strunk -D 2022-01-30T01:35:58.827 +C Cleanup\sdot-command\shandling,\smake\smulti-line\swork,\shonor\sexit\srequests\sfrom\smore\scontexts +D 2022-01-31T19:23:32.887 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -553,7 +553,7 @@ F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c F src/resolve.c 24032ae57aec10df2f3fa2e20be0aae7d256bc704124b76c52d763440c7c0fe9 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/select.c a6d2d4bed279d7fe4fcedaf297eaf6441e8e17c6e3947a32d24d23be52ac02f2 -F src/shell.c.in 7763f8af2cf54a9fef0f6893dfeb64551d4bcaa3ca4e0753f86954d4795c5219 +F src/shell.c.in 4424f30c3b6fa8075da6edf8657895851d95133535af489249d9f6fd31b3ac1a F src/sqlite.h.in eaade58049152dac850d57415bcced885ca27ae9582f8aea2cfb7f1db78a521b F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 5d54cf13d3406d8eb65d921a0d3c349de6126b732e695e79ecd4830ce86b4f8a @@ -1942,8 +1942,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 5ed528e27b84466f165c0af52028242d95cc54dc53d6bb4d7afcbb081e6e11de 312642d982f7861fd4db416e5eb24d863535b3ade40539a32f2dfe3f6fc45d46 -R f64c637c8e44a3750cfffb14c4feb5a6 +P 59693d3e732bbfd4855d97c55e91ad3fbc9d22ebd2c073c5debe958e8100c93e +R 183362d79d916a1a03ae235575ff10ea U larrybr -Z 070f9b90917eac9f0476cb6dbd10e91c +Z add4c9190edfa3f82cfa473c5298219b # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index fd185128ad..b3e64aea77 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -59693d3e732bbfd4855d97c55e91ad3fbc9d22ebd2c073c5debe958e8100c93e \ No newline at end of file +5cf66e89071b619d116a4707b6ff1cd7917edffe5ab504514f37da433a77b61d \ No newline at end of file diff --git a/src/shell.c.in b/src/shell.c.in index 49fdd517af..60deebf09a 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -1161,6 +1161,46 @@ struct EQPGraph { /* 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<bExtendedDotCmds & SHEXT_PARSING_MASK !=0) -#else -# define SHEXT_PARSING(pSS) 0 -#endif + u8 bExtendedDotCmds; /* Bits set to enable various shell extensions */ unsigned statsOn; /* True to display memory stats before each finalize */ unsigned mEqpLines; /* Mask of veritical lines in the EQP output graph */ int inputNesting; /* Track nesting level of .read and other redirects */ @@ -2116,6 +2150,14 @@ static int progress_handler(void *pClientData) { } #endif /* SQLITE_OMIT_PROGRESS_CALLBACK */ +/* +** Skip over whitespace, returning remainder. +*/ +static const char *skipWhite( const char *z ){ + while( IsSpace(*z) ) ++z; + return z; +} + /* ** Print N dashes */ @@ -3661,12 +3703,11 @@ static int expertDotCommand( #endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ /* This saves little code and source volume, but provides a nice breakpoint. -** It is called when input is ready to be run, (or would be run if it was -** not about to dumped as a no-op. For shell # comments, "processable" is a -** slight misnomer.) Someday, a tracing facility may enhance this function's -** output to show where and at what line input has originated. +** It is called when input is ready to be run, or would be run if it was +** not about to dumped as a no-op. Someday, a tracing facility may enhance +** this function's output to show where the input group has originated. */ -static void echo_processable_input(ShellState *p, const char *zDo){ +static void echo_group_input(ShellState *p, const char *zDo){ if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zDo); } @@ -3711,13 +3752,12 @@ static int shell_exec( }else{ if( !pStmt ){ /* this happens for a comment or white-space */ - zSql = zLeftover; - while( IsSpace(zSql[0]) ) zSql++; + zSql = skipWhite(zLeftover); continue; } zStmtSql = sqlite3_sql(pStmt); if( zStmtSql==0 ) zStmtSql = ""; - while( IsSpace(zStmtSql[0]) ) zStmtSql++; + else zStmtSql = skipWhite(zStmtSql); /* save off the prepared statment handle and reset row count */ if( pArg ){ @@ -3726,7 +3766,7 @@ static int shell_exec( } /* echo the sql statement if echo on */ - if( pArg ) echo_processable_input( pArg, zStmtSql ? zStmtSql : zSql); + if( pArg ) echo_group_input( pArg, zStmtSql ? zStmtSql : zSql); /* Show the EXPLAIN QUERY PLAN if .eqp is on */ if( pArg && pArg->autoEQP && sqlite3_stmt_isexplain(pStmt)==0 ){ @@ -3817,8 +3857,7 @@ static int shell_exec( rc2 = sqlite3_finalize(pStmt); if( rc!=SQLITE_NOMEM ) rc = rc2; if( rc==SQLITE_OK ){ - zSql = zLeftover; - while( IsSpace(zSql[0]) ) zSql++; + zSql = skipWhite(zLeftover); }else if( pzErrMsg ){ *pzErrMsg = save_err_msg(db, "stepping, %s (%d)", rc, 0); } @@ -5852,8 +5891,8 @@ static int testcase_glob(const char *zGlob, const char *z){ while( (c = (*(zGlob++)))!=0 ){ if( IsSpace(c) ){ if( !IsSpace(*z) ) return 0; - while( IsSpace(*zGlob) ) zGlob++; - while( IsSpace(*z) ) z++; + zGlob = skipWhite(zGlob); + z = skipWhite(z); }else if( c=='*' ){ while( (c=(*(zGlob++))) == '*' || c=='?' ){ if( c=='?' && (*(z++))==0 ) return 0; @@ -5914,8 +5953,7 @@ static int testcase_glob(const char *zGlob, const char *z){ if( c!=(*(z++)) ) return 0; } } - while( IsSpace(*z) ){ z++; } - return *z==0; + return *skipWhite(z)==0; } @@ -7762,10 +7800,19 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ } #endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */ -#ifndef SHELL_OMIT_EXTENSIONS +#if SHELL_EXTENSIONS static int shxoptsCommand(char *azArg[], int nArg, ShellState *p, char **pzE){ static struct { const char *name; u8 mask; } shopts[] = { - {"parsing", SHEXT_PARSING_MASK} +#if SHELL_DYNAMIC_COMMANDS + {"dyn_cmds", 1<out, + " name value \"-shxopts set\"\n" + " -------- ----- ---------------\n"); for( io=0; iobExtendedDotCmds&shopts[io].mask; - raw_printf(p->out, " %8s: %2X (-shxopts %02X)\n", shopts[io].name, m, m); + unsigned m = shopts[io].mask; + unsigned v = ((p->bExtendedDotCmds & m) == m)? 1 : 0; + raw_printf(p->out, + " %9s %2d \"-shxopts 0x%02X\"\n", + shopts[io].name, v, m); } } return 0; @@ -7839,7 +7892,8 @@ static int execute_variables(char *azArg[], int nArg, ShellState *p){ * with a terminating NUL character. Without it, the NULL could * land past the end of the allocation made at this next line. */ - char *zSubmit = sqlite3_mprintf( "%.*s\n", nb, zValue ); + 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; @@ -7880,6 +7934,10 @@ static int do_meta_command(char *zLine, ShellState *p){ 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 ){ @@ -10417,7 +10475,7 @@ static int do_meta_command(char *zLine, ShellState *p){ utf8_printf(p->out, "%12.12s: %s\n", "filename", p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : ""); }else -#ifndef SHELL_OMIT_EXTENSIONS +#if SHELL_EXTENSIONS if( c=='s' && strncmp(azArg[0], "shxopts", n)==0 ){ rc = shxoptsCommand(azArg, nArg, p, 0); }else @@ -11047,6 +11105,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. */ + for( n=1; n0 && iArgOffset0 && quickscan(zLine+iSkip,QSS_Start)==QSS_Start ) return zLine; - else return 0; + else if( iSkip>0 ){ + SqlScanState sss = SSS_Start; + sql_prescan(zLine+iSkip,&sss); + if( sss==SSS_Start ) return zLine; + } + return 0; } /* @@ -11221,7 +11295,7 @@ static int runOneSqlLine(ShellState *p, char *zSql, int bAltIn, int startline){ return 0; } -#ifndef SHELL_OMIT_EXTENDED_PARSING +#if SHELL_EXTENDED_PARSING /* Resumable line classsifier for dot-commands ** ** Determines if a dot-command is open, having either an unclosed @@ -11252,7 +11326,7 @@ typedef enum { isOpenMask = 1|4 /* bit test */ } DCmd_ScanState; -static int dot_command_open(char *zCmd, DCmd_ScanState *pScanState){ +static void dot_command_scan(char *zCmd, DCmd_ScanState *pScanState){ DCmd_ScanState ss = *pScanState & ~endEscaped; char c = (ss&isOpenMask)? 1 : *zCmd++; while( c!=0 ){ @@ -11315,23 +11389,48 @@ static int dot_command_open(char *zCmd, DCmd_ScanState *pScanState){ } atEnd: *pScanState = ss; - return DCSS_IsOpen(ss); } #else -# define dot_command_open(x) -#endif /* !defined(SHELL_OMIT_EXTENDED_PARSING) */ +# define dot_command_scan(x,y) +#endif /* Utility functions for process_input. */ -static char *skipWhite( char *z ){ - while( IsSpace(*z) ) ++z; - return z; +#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 = realloc(*pz, *pna); + *pz = sqlite3_realloc(*pz, *pna); shell_check_oom(*pz); } } @@ -11344,7 +11443,11 @@ static void grow_line_buffer(char **pz, int *pna, int ncNeed){ ** 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. +** 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 */ @@ -11353,7 +11456,7 @@ static int process_input(ShellState *p){ * 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 */ + /* 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); @@ -11381,8 +11484,8 @@ static int process_input(ShellState *p){ int ncLineAcc = 0; /* how many (non-zero) chars are in zLineAccum */ int iLastLine = 0; /* index of last accumulated line start */ /* Initialize resumable scanner(s). */ - QuickScanState qss = QSS_Start; /* for SQL scan */ -#ifndef SHELL_OMIT_EXTENDED_PARSING + 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. */ @@ -11398,7 +11501,8 @@ static int process_input(ShellState *p){ enum { Incoming, Runnable, Dumpable, Erroneous, Ignore } disposition = Incoming; - char **pzLineUse = &zLineInput; /* line to be processed */ + 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); @@ -11420,7 +11524,7 @@ static int process_input(ShellState *p){ disposition = Dumpable; } /* Classify and check for single-line dispositions, prep for more. */ -#ifndef SHELL_OMIT_EXTENDED_PARSING +#if SHELL_EXTENDED_PARSING ndcLeadWhite = (SHEXT_PARSING(p)) ? skipWhite(zLineInput)-zLineInput : 0; /* Disallow leading whitespace for . or # in legacy mode. */ @@ -11428,24 +11532,20 @@ static int process_input(ShellState *p){ switch( zLineInput[ndcLeadWhite] ){ case '.': inKind = Cmd; - dot_command_open(zLineInput+ndcLeadWhite, &dcScanState); + dot_command_scan(zLineInput+ndcLeadWhite, &dcScanState); break; case '#': inKind = Comment; - disposition = Dumpable; break; default: - { - /* Might be SQL, or a swallowable whole SQL comment. */ - qss = quickscan(zLineInput, qss); - if( QSS_PLAINWHITE(qss) ){ - /* It's either all blank or a whole SQL comment. Swallow it. */ - inKind = Comment; - disposition = Dumpable; - }else{ - /* Something dark, not a # comment or dot-command. Must be SQL. */ - inKind = Sql; - } + /* 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 */ @@ -11455,104 +11555,58 @@ static int process_input(ShellState *p){ * 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 if it is not so complete. This loop finishes + * 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, issues an error instead of marking it as ready. + * 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: - /* This is almost redundant, but for open SQL comments being closed. */ disposition = Dumpable; - continue; case Cmd: - { -#ifndef SHELL_OMIT_EXTENDED_PARSING - if( SHEXT_PARSING(p) ){ - /* It's ready only if has no open argument or escaped newline. */ - int bOpen = DCSS_IsOpen(dcScanState); - int bEscNewline = DCSS_EndEscaped(dcScanState); - switch( bEscNewline<<1 | bOpen ){ - case 0: /* neither */ - /* It's ready to run as-is. */ - disposition = Runnable; - cLineEnd = '\n'; - break; - case 1: /* only an open argument */ - /* Open argument, without escaped newline. - * Newline becomes part of the quoted argument. */ - cLineEnd = '\n'; - break; - case 2: /* only escaped newline */ - /* Escaped newline but otherwise ready. - * Handle these two cases: - * a. The linebreak terminates an unquoted argument - * b. The linebreak follows some whitespace. */ - if( DCSS_InDarkArg(dcScanState) ){ - /* case a, swallow the newline, splicing lines */ - cLineEnd = 0; - }else{ - /* case b, replace the newline with a space. */ - cLineEnd = ' '; - } - break; - case 3: /* both */ - /* Escaped newline within a quoted argument. - * Newline is to be incorporated into the argument. */ - cLineEnd = '\n'; - break; - } - if( bEscNewline ){ - /* Swallow the trailing escape character. */ - (*pzLineUse)[--ncLineIn] = 0; - } - }else -#endif - { - /* In legacy parsing, any dot-command line is deemed ready. */ - assert(cLineEnd=='\n'); +#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( QSS_SEMITERM(qss) && sqlite3_complete(*pzLineUse)){ - disposition = Runnable; - }else if( QSS_PLAINWHITE(qss) ){ - /* It's a 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); - ncLineAcc = iLastLine + 2; - }else{ - /* Unless nothing preceded it, then dump it. */ - disposition = Dumpable; - } + /* 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 not yet a complete group. */ + /* Collect and accumulate more input if group not yet complete. */ if( disposition==Incoming ){ - grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+ncLineIn+2); if( nGroupLines==1 ){ + grow_line_buffer(&zLineAccum, &naAccum, ncLineIn+2); /* Copy line just input */ - iLastLine = ncLineAcc; memcpy(zLineAccum, zLineInput, ncLineIn); ncLineAcc = ncLineIn; - if( cLineEnd!=0 ) zLineAccum[ncLineAcc++] = cLineEnd; - zLineAccum[ncLineAcc] = 0; pzLineUse = &zLineAccum; + pncLineUse = &ncLineAcc; } /* Read in next line of group, (if available.) */ zLineInput = one_input_line(p->pInSource, zLineInput, nGroupLines>0); @@ -11569,42 +11623,42 @@ static int process_input(ShellState *p){ /* Scan line just input (if needed) and append to accumulation. */ switch( inKind ){ case Cmd: - dot_command_open(zLineInput, &dcScanState); + dot_command_scan(zLineInput, &dcScanState); break; case Sql: - qss = quickscan(zLineInput, qss); + sql_prescan(zLineInput, &sqScanState); + break; + default: break; } grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+ncLineIn+2); - iLastLine = ncLineAcc; + /* 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; - if( cLineEnd!=0 ) zLineAccum[ncLineAcc++] = cLineEnd; 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_processable_input(p, *pzLineUse); + 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); if( bail_on_error && nErrors>0 ) bErrorBail = 1; break; case Cmd: - { - int rc; - echo_processable_input(p, *pzLineUse); - rc = do_meta_command(*pzLineUse, p); - if( rc==2 ){ /* exit requested */ - bExitDemand = 1; - }else if( rc!=0 ){ - if( bail_on_error ) bErrorBail = 1; - ++nErrors; - } + 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: @@ -11613,31 +11667,28 @@ static int process_input(ShellState *p){ } break; case Erroneous: - { - const char *zSrc = (p->pInSource!=0) - ? p->pInSource->zSourceSay : ""; - utf8_printf(stderr, "Error: Input incomplete at line %d of \"%s\"\n", - p->lineno, zSrc); - if( bail_on_error ) bErrorBail = 1; - ++nErrors; - } + utf8_printf(stderr, "Error: Input incomplete at line %d of \"%s\"\n", + p->lineno, + (p->pInSource!=0)? p->pInSource->zSourceSay : ""); + ++nErrors; break; case Ignore: break; default: assert(0); } - } /* end group consume/prep/(run or dump) loop */ + 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); - free(zLineAccum); + free(zLineInput); /* Allocated via malloc() by readline or equivalents. */ + sqlite3_free(zLineAccum); - return bErrorBail? 2 : (nErrors>0)? 1 : 0; + return ((bErrorBail | bExitDemand)<<1) + (nErrors>0); } /* -** Return a pathname which is the user's home directory. A -** 0 return indicates an error of some kind. +** Return a pathname which is the user's home directory. +** A 0 return indicates an error of some kind. */ static char *find_home_dir(int clearFlag){ static char *home_dir = NULL; @@ -11801,7 +11852,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" +#endif #ifdef SQLITE_ENABLE_SORTER_REFERENCES " -sorterref SIZE sorter references threshold size\n" #endif @@ -12143,7 +12196,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ sqlite3MemTraceActivate(stderr); }else if( strcmp(z,"-bail")==0 ){ bail_on_error = 1; -#ifndef SHELL_OMIT_EXTENSIONS +#if SHELL_EXTENSIONS }else if( strcmp(z,"-shxopts")==0 ){ data.bExtendedDotCmds = (u8)integerValue(argv[++i]); #endif @@ -12301,7 +12354,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ ShellSetFlag(&data, SHFLG_Backslash); }else if( strcmp(z,"-bail")==0 ){ /* No-op. The bail_on_error flag should already be set. */ -#ifndef SHELL_OMIT_EXTENSIONS +#if SHELL_EXTENSIONS }else if( strcmp(z,"-shxopts")==0 ){ i++; /* Handled on first pass. */ #endif @@ -12351,7 +12404,14 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ z = cmdline_option_value(argc,argv,++i); if( z[0]=='.' ){ rc = do_meta_command(z, &data); - if( rc && bail_on_error ) return rc==2 ? 0 : rc; + switch( rc ){ + case 2: return 0; + case 3: return 1; + default: return 2; + case 0: break; + case 1: if( bail_on_error ) return 1; + break; + } }else{ open_db(&data, 0); rc = shell_exec(&data, z, &zErrMsg); @@ -12488,5 +12548,5 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ /* Clear the global data structure so that valgrind will detect memory ** leaks */ memset(&data, 0, sizeof(data)); - return rc; + return rc & ~2; /* Clear the "quit" bit. */ }