From: larrybr Date: Fri, 10 Dec 2021 18:11:29 +0000 (+0000) Subject: All shell tests except shell8.test pass. (a WIP) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8acc81f100c34a3233b580e9e957cc7687d8f691;p=thirdparty%2Fsqlite.git All shell tests except shell8.test pass. (a WIP) FossilOrigin-Name: 653db501b46a4035ea1cb2f39862d781fa40fa0442a6abc84d32a9ac29e3af20 --- 8acc81f100c34a3233b580e9e957cc7687d8f691 diff --cc manifest index d290f4e1a6,765acd7045..955c297c47 --- a/manifest +++ b/manifest @@@ -1,5 -1,5 +1,5 @@@ - C Pickup\strunk\s.mode\supgrade\s+\scosmetic\sshell\schanges - D 2021-12-09T16:45:03.203 -C Add\ssupport\sfor\sBloom-filters\sas\sa\sperformance\soptimization\sfor\sjoins. -D 2021-12-09T20:06:18.928 ++C All\sshell\stests\sexcept\sshell8.test\spass.\s(a\sWIP) ++D 2021-12-10T18:11:29.142 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@@ -532,7 -531,6 +532,7 @@@ F src/mutex_noop.c 9d4309c075ba9cc7249e F src/mutex_unix.c dd2b3f1cc1863079bc1349ac0fec395a500090c4fe4e11ab775310a49f2f956d F src/mutex_w32.c caa50e1c0258ac4443f52e00fe8aaea73b6d0728bd8856bedfff822cae418541 F src/notify.c 89a97dc854c3aa62ad5f384ef50c5a4a11d70fcc69f86de3e991573421130ed6 - F src/obj_interfaces.h ed6864b7970f5fcc1dc20654c778f6d73dd724e28396a270ec0f3632f0e47e25 ++F src/obj_interfaces.h 194b36e15805ee8a1d8056986ccc3cb5762b4ca55ae2deb13ceb5adde965f8e6 F src/os.c b1c4f2d485961e9a5b6b648c36687d25047c252222e9660b7cc25a6e1ea436ab F src/os.h 26890f540b475598cd9881dcc68931377b8d429d3ea3e2eeb64470cde64199f8 F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85 @@@ -554,12 -552,11 +554,12 @@@ F src/random.c 097dc8b31b8fba5a9aca1697 F src/resolve.c 4a1db4aadd802683db40ca2dbbb268187bd195f10cbdb7206dbd8ac988795571 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/select.c a7a3d9f54eb24821ec5f67f2e5589b68a5d42d46fc5849d7376886777d93a85a - F src/shell.c.in 3bdbdf9b8051fbab9f5f654dd3c406f0fd6d743ae270825ea1ee3ec8930d25c0 - F src/shext_linkage.h 1508132ce49c023d8ac445399fa275a01f27667f8686797051aefd1d6e8ae03a - F src/sqlite.h.in 5cd209ac7dc4180f0e19292846f40440b8488015849ca0110c70b906b57d68f0 -F src/shell.c.in 239bee1085d94964f02582b0714dc3fc85cfc16e27e95813e4dbc24bb215a7e0 ++F src/shell.c.in c7873707062a7d90d3d3e0993386d890ac4312ff735f2fe3d2381a447a23d84d ++F src/shext_linkage.h 5897e8140a06cb733d07a927994b30d41225eb93b5b8cf2ad0e3460cb4e95fd6 + F src/sqlite.h.in 5999d6db0e65afbd686b76cddc385b310aa3815624edba43987913067f50e209 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 8ff2fd2c166150b2e48639f5e506fb44e29f1a3f65031710b9e89d1c126ac839 - F src/sqliteInt.h 375bb03b5c2de4c3e2f746e6205f01b8a51abd5835a1a72ab9a1c027f5c9849e + F src/sqliteInt.h b4391c3c2ae0a8020ce0f543fc2b529f9bcdf72ab7ba3c31d170e3228169162f F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@@ -1383,11 -1380,11 +1383,11 @@@ 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 f7cf041033277c0759aef303fcb42615e569163d23cc8e005deb37a9e29c419a -F test/shell1.test c354008b27c904f0166c2138abd7382013ea070b41114114ecbdfb32c726a807 -F test/shell2.test f00a0501c00583cbc46f7510e1d713366326b2b3e63d06d15937284171a8787c ++F test/shell1.test 022db2884daceef52c40c4ed9f3c684e5b9930f1e0097d66857b420890a1f23c +F test/shell2.test de123dd6be4b774b5ebdc81b29b3515c29c4a6a81bd5d2e1c38605d2f775a25a F test/shell3.test cb4b835a901742c9719437a89171172ecc4a8823ad97349af8e4e841e6f82566 F test/shell4.test 3ed6c4b42fd695efcbc25d69ef759dbb15855ca8e52ba6c5ee076f8b435f48be -F test/shell5.test 6e4aa0e531dcb8dcf74b7920a2a7442c6712d4dff8422bbc81f768f9dee8a0e3 +F test/shell5.test 596342db4ada597c6e021081a63f27be87eb5b1d8cf71028f3ec7dc17a8dd42e F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3 F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f F test/shell8.test 388471d16e4de767333107e30653983f186232c0e863f4490bb230419e830aae @@@ -1937,7 -1934,8 +1937,7 @@@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a9 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 - P 6956e989083462b6745276c45edcb03ac7ab9c31518e1f505bfae9e8f1cd2b2f 1eefd957ff35e961685db565f7ef116c566a04574c5bedad7298b3cc69dd72d2 - R 2caf0db7e891499e6512ba7304c5d674 -P 1eefd957ff35e961685db565f7ef116c566a04574c5bedad7298b3cc69dd72d2 ce42039f5647b1f276acf5d9911528ecb47df1544a587def72c8cd6b2f664289 -R c6e1a17bdb20d0994b97369ef3d14311 -T +closed ce42039f5647b1f276acf5d9911528ecb47df1544a587def72c8cd6b2f664289 -U drh -Z 2960f76524291316688b632931fc780c ++P 8dc69c81b9ad1c45f881f7833efdec736c16d6f542490d14d7f3707d5e0ee1ef 633bfeeea2bccdd44126acf3f61ecca163c9d933bdc787a2c18a697dc9406882 ++R 468297f5d055ff0dcc1bb6af44d0c133 +U larrybr - Z 2a9012fb2ed359d6d4488b48bf07c732 ++Z 38665f90add4e3626669babde7fc6734 diff --cc manifest.uuid index 7c94d5d54a,bbb228f8b8..dcc81baffd --- a/manifest.uuid +++ b/manifest.uuid @@@ -1,1 -1,1 +1,1 @@@ - 8dc69c81b9ad1c45f881f7833efdec736c16d6f542490d14d7f3707d5e0ee1ef -633bfeeea2bccdd44126acf3f61ecca163c9d933bdc787a2c18a697dc9406882 ++653db501b46a4035ea1cb2f39862d781fa40fa0442a6abc84d32a9ac29e3af20 diff --cc src/obj_interfaces.h index 8ec18f6cad,0000000000..1e5eec15a4 mode 100644,000000..100644 --- a/src/obj_interfaces.h +++ b/src/obj_interfaces.h @@@ -1,95 -1,0 +1,98 @@@ +#ifndef OBJIFACE_H +#define OBJIFACE_H + +/* This header defines macros to aid declaration of object interfaces - * used for shell extension. These macros are suitable for either C - * or C++ implementations. For C implementations, an extra struct is ++ * used for shell extension, and to support convenient implementation ++ * of those interfaces.[a] These macros are suitable for either C or ++ * C++ implementations. For C implementations, an extra struct is + * defined, whose typename is _Vtable, which will need + * to be instantiated and populated with function pointers having the + * same order and signatures as those declared for the interface. For + * C++ implementations, a purely abstract base class (with all public + * methods) is declared from which a concrete class will need to be + * derived and instantiated. (No *_Vtable is necessary for C++.) + * + * The macros defined for external use are: + * (for C or C++ implementations) + * INTERFACE_BEGIN( InterfaceName ) + * PURE_VMETHOD( returnType, methodName, InterfaceName, argCount, args ) + * INTERFACE_END( InterfaceName ) - * IMPLEMENTING( returnType, methodName, ClassName, argCount, args ) [a] ++ * IMPLEMENTING( returnType, methodName, ClassName, argCount, args ) [b] + * (for C implementations only) - * VTABLE_NAME( ClassName ) [b] - * (for C++ implementations only [c]) ++ * VTABLE_NAME( ClassName ) [c] ++ * (for C++ implementations only [d]) + * CONCRETE_BEGIN( InterfaceName, DerivedName ) + * CONCRETE_METHOD( returnType, methodName, ClassName, argCount, args ) + * CONCRETE_END( DerivedName ) + * Notes on these macros: + * 1. These macros should be used in the order shown. Many should be + * terminated with either ';' or a curly-braced construct (which + * helps auto-indentation tools to operate sanely.) + * 2. The "args" parameter is a parenthesized list of the additional + * arguments, those beyond an explicit "InterfaceName *pThis" for C + * or the implicit "this" for C++. + * 3. The argCount parameter must number the additional arguments. + * 4. A leading method, named "destruct" without additional arguments + * and returning void, is declared for all interfaces. This is not + * the C++ destructor. (It might delegate to a destructor.) - * [a. This macro may be useful for function/method definitions which ++ * [a. The convenience is that the signatures from the interface may ++ * be reused for method implementations with a copy and paste. ] ++ * [b. This macro may be useful for function/method definitions which + * implement methods in an INTERFACE_{BEGIN,...,END} sequence. ] - * [b. This macro is useful for populating a C dispatch table whose ++ * [c. This macro is useful for populating a C dispatch table whose + * layout is declared in the INTERFACE_{BEGIN,...,END} sequence. ] - * [c. These macros are useful for declaring instantiatable classes ++ * [d. These macros are useful for declaring instantiatable classes + * derived from an abstract base class via INTERFACE_{BEGIN,END}. ] + */ + +#ifdef __cplusplus + +# define VMETHOD_BEGIN(rType, mName) virtual rType mName( +# define PURE_VMETHOD_END )=0 +#define ARG_FIRST_0(t) +#define ARG_FIRST_1(t) +#else +# define VMETHOD_BEGIN(rType, mName) rType (*mName)( +# define PURE_VMETHOD_END ) +#define ARG_FIRST_0(t) t *pThis +#define ARG_FIRST_1(t) t *pThis, +#endif +#define ARG_FIRST_2 ARG_FIRST_1 +#define ARG_FIRST_3 ARG_FIRST_1 +#define ARG_FIRST_4 ARG_FIRST_1 +#define ARG_FIRST_5 ARG_FIRST_1 +#define ARGS_EXPAND(na) ARGS_EXPAND_ ## na +#define ARGS_EXPAND_0() +#define ARGS_EXPAND_1(a1) a1 +#define ARGS_EXPAND_2(a1,a2) a1,a2 +#define ARGS_EXPAND_3(a1,a2,a3) a1,a2,a3 +#define ARGS_EXPAND_4(a1,a2,a3,a4) a1,a2,a3,a4 +#define ARGS_EXPAND_5(a1,a2,a3,a4,a5) a1,a2,a3,a4,a5 + +#define PURE_VMETHOD(rt, mn, ot, na, args) VMETHOD_BEGIN(rt, mn) \ + ARG_FIRST_ ## na(ot) ARGS_EXPAND(na)args PURE_VMETHOD_END +#define CONCRETE_METHOD(rt, mn, ot, na, args) rt mn( \ + ARG_FIRST_ ## na(ot) ARGS_EXPAND(na)args ) + +#ifdef __cplusplus +# define INTERFACE_BEGIN(iname) struct iname { \ + PURE_VMETHOD(void, destruct, iname, 0, ()) +# define INTERFACE_END(iname) } +# define CONCRETE_BEGIN(iname, derived) class derived : public iname { \ + CONCRETE_METHOD(void, destruct, derived, 0, ()) +# define CONCRETE_END(derived) } +# define IMPLEMENTING(rt, mn, derived, na, args) rt derived::mn( \ + ARG_FIRST_ ## na(derived) ARGS_EXPAND(na)args ) +#else +# define VTABLE_NAME(name) name ## _Vtable +# define INTERFACE_BEGIN(iname) typedef struct iname { \ + struct VTABLE_NAME(iname) * pMethods; \ + } iname; typedef struct VTABLE_NAME(iname) { \ + PURE_VMETHOD(void, destruct, iname, 0, ()) +# define INTERFACE_END(iname) } VTABLE_NAME(iname) +# define DECORATE_METHOD(ot, mn) ot ## _ ## mn +# define IMPLEMENTING(rt, mn, ot, na, args) rt DECORATE_METHOD(ot, mn)( \ + ARG_FIRST_ ## na(ot) ARGS_EXPAND(na)args ) +#endif + +#endif /* !defined(OBJIFACE_H) */ diff --cc src/shell.c.in index 0a20ba9784,3262f98c12..baf568471c --- a/src/shell.c.in +++ b/src/shell.c.in @@@ -439,6 -421,6 +439,7 @@@ static int stdout_is_console = 1 ** by the SIGINT handler to interrupt database processing. */ static sqlite3 *globalDb = 0; ++static sqlite3_mutex *pGlobalDbLock = 0; /* ** True if an interrupt (Control-C) has been received. @@@ -499,10 -481,10 +500,21 @@@ void utf8_printf(FILE *out, const char # define raw_printf fprintf #endif ++/* ++** Provide a way for embedding apps to handle OOP condition their way. ++** This is crude, and using it will undoubtedly leak memory and handles. ++** The alternative is extensive recoding of the shell, (not for today.) ++** This could become a longjump(...) (paired with app's setjump(...).) ++*/ ++#ifndef SHELL_OOM_EXIT ++# define SHELL_OOM_EXIT exit(1) ++#endif /* Indicate out-of-memory and exit. */ static void shell_out_of_memory(void){ - raw_printf(stderr,"Error: out of memory\n"); - exit(1); + raw_printf(STD_ERR,"Error: out of memory\n"); - exit(1); ++ sqlite3_mutex_free(pGlobalDbLock); ++ pGlobalDbLock = 0; ++ SHELL_OOM_EXIT; } #ifdef SQLITE_DEBUG @@@ -687,17 -669,17 +699,25 @@@ 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. ++** 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. ** --** If zLine is not NULL then it is a malloced buffer returned from --** a previous call to this routine that may be reused. ++** 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. */ static char *local_getline(char *zLine, FILE *in){ -- int nLine = zLine==0 ? 0 : 100; -- int n = 0; ++#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 */ while( 1 ){ if( n+100>nLine ){ @@@ -707,16 -689,16 +727,24 @@@ } if( fgets(&zLine[n], nLine - n, in)==0 ){ if( n==0 ){ ++ /* Empty (EOF) input and zip accumulated. Return so. */ free(zLine); ++ ZLINE_PRIOR(0); return 0; } ++ /* NUL-terminate presently obtained input (and done.) */ zLine[n] = 0; break; } ++ /* Note non-NUL chars added in this loop. (Tough if input had NULs.) */ while( zLine[n] ) n++; -- if( n>0 && zLine[n-1]=='\n' ){ ++ /* Replace final line-end with NUL. */ ++ while( n>0 && (zLine[n-1]=='\n' || zLine[n-1]=='\r') ){ n--; -- if( n>0 && zLine[n-1]=='\r' ) n--; ++ } ++ if( zLine[n]==0 ){ ++ continue; ++ }else{ zLine[n] = 0; break; } @@@ -730,6 -712,6 +758,7 @@@ int nTrans = strlen30(zTrans)+1; if( nTrans>nLine ){ zLine = realloc(zLine, nTrans); ++ ZLINE_PRIOR(zLine); if( zLine==0 ) shell_out_of_memory(); } memcpy(zLine, zTrans, nTrans); @@@ -737,6 -719,6 +766,7 @@@ } } #endif /* defined(_WIN32) || defined(WIN32) */ ++ ZLINE_PRIOR(zLine); return zLine; } @@@ -774,7 -756,7 +804,6 @@@ static char *one_input_line(FILE *in, c return zResult; } -- /* ** Return the value of a hexadecimal digit. Return -1 if the input ** is not a hex digit. @@@ -1132,6 -1114,6 +1161,7 @@@ struct EQPGraph typedef struct ShellState ShellState; struct ShellState { sqlite3 *db; /* The database */ ++ int abruptExit; /* Flag for immediate shell exit, exit code */ u8 autoExplain; /* Automatically turn on .explain mode */ u8 autoEQP; /* Run EXPLAIN QUERY PLAN prior to seach SQL stmt */ u8 autoEQPtest; /* autoEQP is in test mode */ @@@ -1141,8 -1123,8 +1171,8 @@@ u8 doXdgOpen; /* Invoke start/open/xdg-open in output_reset() */ u8 nEqpLevel; /* Depth of the EQP output graph */ u8 eTraceType; /* SHELL_TRACE_* value for type of trace */ - u8 bSafeMode; /* True to prohibit unsafe operations */ - u8 bSafeModePersist; /* The long-term value of bSafeMode */ + u8 bSafeMode; /* True when unsafe operations are prohibited */ - u8 bSafeModeFuture; /* Next bSafeMode - 0, 1 or suspending downto 1 */ ++ u8 bSafeModeFuture; /* See updateSafeMode regarding use of this. */ unsigned statsOn; /* True to display memory stats before each finalize */ unsigned mEqpLines; /* Mask of veritical lines in the EQP output graph */ int outCount; /* Revert to stdout when reaching zero */ @@@ -1194,11 -1176,11 +1224,33 @@@ int *aiIndent; /* Array of indents used in MODE_Explain */ int nIndent; /* Size of array aiIndent[] */ int iIndent; /* Index of current op in aiIndent[] */ -- char *zNonce; /* Nonce for temporary safe-mode excapes */ ++ char *zNonce; /* Nonce for temporary safe-mode suspension */ EQPGraph sGraph; /* Information for the graphical EXPLAIN QUERY PLAN */ ExpertInfo expert; /* Valid if previous command was ".expert OPT..." */ }; ++/* ++** This procedure updates the bSafeMode flag after completion of any ++** operation (meta-command or SQL submission) that counts as one for ++** which safe mode might be suspended. ++** bSafeModeFuture has 3 states salient here: ++** equal 0 => 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 */ @@@ -1338,15 -1320,9 +1390,17 @@@ static void shellPutsFunc /* ** If in safe mode, print an error message described by the arguments -** and exit immediately. +** and "exit" without returning to the caller. This "exit" will occur +** immediately, as a process exit, for normal shell builds. When the +** shell is built with options to use it embedded, control returns to +** the caller of the shell's main, "do shell things" entry point. - ** (TBD: Embedded exit arrangement) ++** +** It is an error, (perhaps with only minor effect such as memory leak), +** for a meta-command to call this function while it holds resources. ++** ++** The return is true if failing, 0 otherwise. */ --static void failIfSafeMode( ++static int failIfSafeMode( ShellState *p, const char *zErrMsg, ... @@@ -1357,10 -1333,10 +1411,12 @@@ va_start(ap, zErrMsg); zMsg = sqlite3_vmprintf(zErrMsg, ap); va_end(ap); - raw_printf(stderr, "line %d: ", p->lineno); - utf8_printf(stderr, "%s\n", zMsg); - exit(1); + raw_printf(STD_ERR, "line %d: ", p->lineno); + utf8_printf(STD_ERR, "%s\n", zMsg); - exit(1); ++ p->abruptExit = 3; ++ return 1; } ++ return 0; } /* @@@ -1810,12 -1788,12 +1866,20 @@@ static void output_csv(ShellState *p, c /* ** This routine runs when the user presses Ctrl-C ++** TBD: It will need some changes for embedability. */ static void interrupt_handler(int NotUsed){ UNUSED_PARAMETER(NotUsed); seenInterrupt++; -- if( seenInterrupt>2 ) exit(1); -- if( globalDb ) sqlite3_interrupt(globalDb); ++ if( seenInterrupt>2 ){ ++ sqlite3_mutex_free(pGlobalDbLock); ++ exit(1); ++ } ++ if( globalDb ){ ++ sqlite3_mutex_enter(pGlobalDbLock); ++ sqlite3_interrupt(globalDb); ++ sqlite3_mutex_leave(pGlobalDbLock); ++ } } #if (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE) @@@ -1860,15 -1838,15 +1924,17 @@@ static int safeModeAuth UNUSED_PARAMETER(zA4); switch( op ){ case SQLITE_ATTACH: { -- failIfSafeMode(p, "cannot run ATTACH in safe mode"); ++ if ( failIfSafeMode(p, "cannot run ATTACH in safe mode") ) ++ return SQLITE_ERROR; break; } case SQLITE_FUNCTION: { int i; for(i=0; iout. -*/ -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, + 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" - ); - - 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" - ");" +#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)); +/***************** + * The .seeargs command + */ +COLLECT_HELP_TEXT[ + ".seeargs Echo arguments separated by |", + " A near-dummy command for use as a template (to vanish soon)", +]; +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; +} - /* 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" - ");" +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 ? 3 0 azArg nArg p ){ + open_db(p, 0); + if( p->bSafeMode ) return SHELL_FORBIDDEN_OP; + return arDotCommand(p, 0, azArg, nArg); +} - /* 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" - ");" +/***************** + * The .auth command + */ +CONDITION_COMMAND(auth !defined(SQLITE_OMIT_AUTHORIZATION)); +COLLECT_HELP_TEXT[ + ".auth ON|OFF Show authorizer callbacks", +]; +DISPATCHABLE_COMMAND( auth 3 2 2 azArg nArg p ){ + 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; +} - /* 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);" - ); +/***************** + * 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; +} - /* 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); +/***************** + * 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; +} - /* 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); +DISPATCHABLE_COMMAND( cd ? 2 2 ){ + int rc=0; + if( p->bSafeMode ) return SHELL_FORBIDDEN_OP; +#if defined(_WIN32) || defined(WIN32) + wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]); + rc = !SetCurrentDirectoryW(z); + sqlite3_free(z); +#else + rc = chdir(azArg[1]); +#endif + if( rc ){ + utf8_printf(STD_ERR, "Cannot change to directory \"%s\"\n", azArg[1]); + rc = 1; } - 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; + return rc; +} - 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; +/* 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; +} + +/***************** + * The .changes, .check, .clone and .connection commands + */ +COLLECT_HELP_TEXT[ + ".changes on|off Show number of rows changed by SQL", + ".check GLOB Fail if output since .testcase does not match", + ".clone NEWDB Clone data into NEWDB from the existing database", + ".connection [close] [#] Open or close an auxiliary database connection", +]; +DISPATCHABLE_COMMAND( changes 3 2 2 ){ + setOrClearFlag(p, SHFLG_CountChanges, azArg[1]); + return 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; +} - if( 0==sqlite3_stricmp(pTab->zQuoted, "\"sqlite_sequence\"") ){ - raw_printf(pState->out, "DELETE FROM sqlite_sequence;\n"); +/***************** + * 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); + azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*)); + if( azName==0 ){ shell_out_of_memory(); /* Does not return */ } + azName[nName*2] = strdup(zSchema); + azName[nName*2+1] = strdup(zFile); + nName++; } - 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_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_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; - } + 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); +} - 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] - ); +/***************** + * 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", + " 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 *zSql; + int i; + int savedShowHeader = p->showHeader; + int savedShellFlags = p->shellFlgs; + ShellClearFlag(p, + SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo + |SHFLG_DumpDataOnly|SHFLG_DumpNoSys); + for(i=1; iout, "INSERT INTO %s(%s) VALUES( %s );\n", - pTab2->zQuoted, pTab2->azlCol[nField], zVal - ); + *pzErr = sqlite3_mprintf + ("Unknown option \"%s\" on \".dump\"\n", azArg[i]); + sqlite3_free(zLike); + return 1; } } - 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{ + /* azArg[i] contains a LIKE pattern. This ".dump" request should + ** only dump data for tables for which either the table name matches + ** the LIKE pattern, or the table appears to be a shadow table of + ** a virtual table for which the name matches the LIKE pattern. + */ + char *zExpr = sqlite3_mprintf( + "name LIKE %Q ESCAPE '\\' OR EXISTS (" + " SELECT 1 FROM sqlite_schema WHERE " + " name LIKE %Q ESCAPE '\\' AND" + " sql LIKE 'CREATE VIRTUAL TABLE%%' AND" + " substr(o.name, 1, length(name)+1) == (name||'_')" + ")", azArg[i], azArg[i] + ); + + if( zLike ){ + zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr); }else{ - raw_printf(pState->out, "%s;\n", zSql); + zLike = zExpr; } } - 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) */ - -/* -** 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]; + open_db(p, 0); -#ifndef SQLITE_OMIT_VIRTUALTABLE - if( p->expert.pExpert ){ - expertFinish(p, 1, 0); + if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ + /* When playing back a "dump", the content might appear in an order + ** which causes immediate foreign key constraints to be violated. + ** So disable foreign-key constraint enforcement to prevent problems. */ + raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n"); + raw_printf(p->out, "BEGIN TRANSACTION;\n"); + } + p->writableSchema = 0; + p->showHeader = 0; + /* Set writable_schema=ON since doing so forces SQLite to initialize + ** as much of the schema as it can even if the sqlite_schema table is + ** corrupt. */ + sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0); + p->nErr = 0; + if( zLike==0 ) zLike = sqlite3_mprintf("true"); + zSql = sqlite3_mprintf( + "SELECT name, type, sql FROM sqlite_schema AS o " + "WHERE (%s) AND type=='table'" + " AND sql NOT NULL" + " ORDER BY tbl_name='sqlite_sequence', rowid", + zLike + ); + run_schema_dump_query(p,zSql); + sqlite3_free(zSql); + if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ + zSql = sqlite3_mprintf( + "SELECT sql FROM sqlite_schema AS o " + "WHERE (%s) AND sql NOT NULL" + " AND type IN ('index','trigger','view')", + zLike + ); + run_table_dump_query(p, zSql); + sqlite3_free(zSql); } -#endif + 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; - /* Parse the input line into tokens. - */ - while( zLine[h] && nArgautoEQPtest = 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{ - azArg[nArg++] = &zLine[h]; - while( zLine[h] && !IsSpace(zLine[h]) ){ h++; } - if( zLine[h] ) zLine[h++] = 0; - resolve_backslashes(azArg[nArg-1]); + p->autoEQP = (u8)booleanValue(azArg[1]); } + }else{ + return SHELL_INVALID_ARGS; } - azArg[nArg] = 0; - - /* Process the input line. - */ - if( nArg==0 ) return 0; /* no tokens, no error */ - n = strlen30(azArg[0]); - c = azArg[0][0]; - clearTempFile(p); +} -#ifndef SQLITE_OMIT_AUTHORIZATION - if( c=='a' && strncmp(azArg[0], "auth", n)==0 ){ - if( nArg!=2 ){ - raw_printf(stderr, "Usage: .auth ON|OFF\n"); - rc = 1; - goto meta_command_exit; - } - open_db(p, 0); - if( booleanValue(azArg[1]) ){ - sqlite3_set_authorizer(p->db, shellAuth, p); - }else if( p->bSafeModePersist ){ - sqlite3_set_authorizer(p->db, safeModeAuth, p); +/***************** + * 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{ - sqlite3_set_authorizer(p->db, 0, 0); + val = booleanValue(azArg[1]); } - }else -#endif + } + 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; +} -#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 +/***************** + * The .excel, .once and .output commands + * These share much implementation, so they stick together. + */ +COLLECT_HELP_TEXT[ + ".excel Display the output of next command in spreadsheet", + " --bom Prefix the file with a UTF8 byte-order mark", + ".once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE", + " If FILE begins with '|' then open it as a command to be piped into.", + " Options:", + " --bom Prefix output with a UTF8 byte-order mark", + " -e Send output to the system text editor", + " -x Send output as CSV to a spreadsheet (same as \".excel\")", + ".output ?FILE? Send output to FILE or stdout if FILE is omitted", + " If FILE begins with '|' then open it as a command to be piped into.", + " Options:", + " --bom Prefix output with a UTF8 byte-order mark", + " -e Send output to the system text editor", + " -x Send output as CSV to a spreadsheet (same as \".excel\")", +]; +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); +} - 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; j 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; idb, 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; + *pzErr = shellMPrintf(0," excess argument: \"%s\"\n", azArg[i]); + sqlite3_free(zFile); + return SHELL_INVALID_ARGS; } - close_db(pDest); - }else - - if( c=='b' && n>=3 && strncmp(azArg[0], "bail", n)==0 ){ - if( nArg==2 ){ - bail_on_error = booleanValue(azArg[1]); + } + 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{ - raw_printf(stderr, "Usage: .bail on|off\n"); + /* text editor mode */ + newTempFile(p, "txt"); + bTxtMode = 1; + } + sqlite3_free(zFile); + zFile = sqlite3_mprintf("%s", p->zTempFile); + } +#endif /* SQLITE_NOHAVE_SYSTEM */ + 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); } - }else - - 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); +#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); } - }else{ - raw_printf(stderr, "Usage: .binary on|off\n"); + p->out = STD_OUT; rc = 1; + } else { + if( bBOM ) fprintf(p->out,"\357\273\277"); + sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); } - }else + } + sqlite3_free(zFile); + 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 - 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; +/***************** + * The .filectrl and fullschema commands + */ +COLLECT_HELP_TEXT[ + ".filectrl CMD ... Run various sqlite3_file_control() operations", + " --schema SCHEMA Use SCHEMA instead of \"main\"", + " --help Show CMD details", + ".fullschema ?--indent? Show schema and the content of sqlite_stat tables", +]; +DISPATCHABLE_COMMAND( filectrl ? 2 0 ){ + static const struct { + const char *zCtrlName; /* Name of a test-control option */ + int ctrlCode; /* Integer code for that option */ + const char *zUsage; /* Usage notes */ + } aCtrl[] = { + { "chunk_size", SQLITE_FCNTL_CHUNK_SIZE, "SIZE" }, + { "data_version", SQLITE_FCNTL_DATA_VERSION, "" }, + { "has_moved", SQLITE_FCNTL_HAS_MOVED, "" }, + { "lock_timeout", SQLITE_FCNTL_LOCK_TIMEOUT, "MILLISEC" }, + { "persist_wal", SQLITE_FCNTL_PERSIST_WAL, "[BOOLEAN]" }, + /* { "pragma", SQLITE_FCNTL_PRAGMA, "NAME ARG" },*/ + { "psow", SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]" }, + { "reserve_bytes", SQLITE_FCNTL_RESERVE_BYTES, "[N]" }, + { "size_limit", SQLITE_FCNTL_SIZE_LIMIT, "[LIMIT]" }, + { "tempfilename", SQLITE_FCNTL_TEMPFILENAME, "" }, + /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY, "COUNT DELAY" },*/ + }; + 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; 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; i=3 && strncmp(azArg[0], "changes", n)==0 ){ - if( nArg==2 ){ - setOrClearFlag(p, SHFLG_CountChanges, azArg[1]); - }else{ - raw_printf(stderr, "Usage: .changes on|off\n"); - rc = 1; + } + if( filectrl<0 ){ + utf8_printf(STD_ERR,"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; } - }else - - /* Cancel output redirection, if it is currently set (by .testcase) - ** Then read the content of the testcase-out.txt file and compare against - ** azArg[1]. If there are differences, report an error and exit. - */ - 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++; + 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; } - sqlite3_free(zRes); - }else - - if( c=='c' && strncmp(azArg[0], "clone", n)==0 ){ - failIfSafeMode(p, "cannot run .clone in safe mode"); - if( nArg==2 ){ - tryToClone(p, azArg[1]); - }else{ - raw_printf(stderr, "Usage: .clone FILENAME\n"); - rc = 1; + 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; } - }else - - if( c=='c' && strncmp(azArg[0], "connection", n)==0 ){ - if( nArg==1 ){ - /* List available connections */ - int i; - for(i=0; iaAuxDb); i++){ - const char *zFile = p->aAuxDb[i].zDbFilename; - if( p->aAuxDb[i].db==0 && p->pAuxDb!=&p->aAuxDb[i] ){ - zFile = "(not open)"; - }else if( zFile==0 ){ - zFile = "(memory)"; - }else if( zFile[0]==0 ){ - zFile = "(temporary-file)"; - } - if( p->pAuxDb == &p->aAuxDb[i] ){ - utf8_printf(stdout, "ACTIVE %d: %s\n", i, zFile); - }else if( p->aAuxDb[i].db!=0 ){ - utf8_printf(stdout, " %d: %s\n", i, zFile); - } + 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); } - }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; + 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); } - }else{ - raw_printf(stderr, "Usage: .connection [close] [CONNECTION-NUMBER]\n"); - rc = 1; + x = -1; + sqlite3_file_control(p->db, zSchema, filectrl, &x); + utf8_printf(p->out,"%d\n", x); + isOk = 2; + break; } - }else + } + } + 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; +} - if( c=='d' && n>1 && strncmp(azArg[0], "databases", n)==0 ){ - char **azName = 0; - int nName = 0; +DISPATCHABLE_COMMAND( fullschema ? 1 2 ){ + int rc; + 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; - 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; - }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); - azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*)); - if( azName==0 ){ shell_out_of_memory(); /* Does not return */ } - azName[nName*2] = strdup(zSchema); - azName[nName*2+1] = strdup(zFile); - nName++; - } - } + 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); - for(i=0; idb, azName[i*2]); - int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]); - const char *z = azName[i*2+1]; - utf8_printf(p->out, "%s: %s %s%s\n", - azName[i*2], - z && z[0] ? z : "\"\"", - bRdonly ? "r/o" : "r/w", - eTxn==SQLITE_TXN_NONE ? "" : - eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn"); - free(azName[i*2]); - free(azName[i*2+1]); - } - sqlite3_free(azName); - }else + } + if( 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 rc > 0; +} - 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; +/***************** + * 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; +} + +/***************** + * The .help command + */ +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", +]; +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( nArg>1 && ii==ArraySize(aDbConfig) ){ - utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]); - utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n"); - } - }else + } + if( showHelp(p->out, zPat)==0 ){ + utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]); + } + /* Help pleas never fail! */ + return 0; +} - if( c=='d' && n>=3 && strncmp(azArg[0], "dbinfo", n)==0 ){ - rc = shell_dbinfo_command(p, nArg, azArg); - }else +/***************** + * 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", + " -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 *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 !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(p->bSafeMode) return SHELL_FORBIDDEN_OP; + memset(&sCtx, 0, sizeof(sCtx)); + if( 0==(sCtx.z = sqlite3_malloc64(120)) ){ + shell_out_of_memory(); + } - if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){ - char *zLike = 0; - char *zSql; - int i; - int savedShowHeader = p->showHeader; - int savedShellFlags = p->shellFlgs; - ShellClearFlag(p, - SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo - |SHFLG_DumpDataOnly|SHFLG_DumpNoSys); - for(i=1; imode==MODE_Ascii ){ + xRead = ascii_read_one_field; + }else{ + xRead = csv_read_one_field; + } + 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 " - "WHERE (%s) AND type=='table'" - " AND sql NOT NULL" - " ORDER BY tbl_name='sqlite_sequence', rowid", - zLike - ); - run_schema_dump_query(p,zSql); - sqlite3_free(zSql); - if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ - zSql = sqlite3_mprintf( - "SELECT sql FROM sqlite_schema AS o " - "WHERE (%s) AND sql NOT NULL" - " AND type IN ('index','trigger','view')", - zLike - ); - run_table_dump_query(p, zSql); - sqlite3_free(zSql); - } - sqlite3_free(zLike); - if( p->writableSchema ){ - raw_printf(p->out, "PRAGMA writable_schema=OFF;\n"); - p->writableSchema = 0; + } + if( zTable==0 ){ + *pzErr = shellMPrintf(0," missing %s argument.\n", + zFile==0 ? "FILE" : "TABLE"); + return SHELL_INVALID_ARGS; + } + 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; } - 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"); + 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); } - 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; + if( nSep>1 ){ + *pzErr = sqlite3_mprintf + ("Error: multi-character row separators not allowed for import\n"); + return 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); + 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{ - p->autoEQP = (u8)booleanValue(azArg[1]); - } - }else{ - raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n"); - rc = 1; + }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; + } + 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\"", 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\"", zTable); + char cSep = '('; + while( xRead(&sCtx) ){ + zCreate = sqlite3_mprintf("%z%c\n \"%w\" TEXT", zCreate, cSep, sCtx.z); + cSep = ','; + if( sCtx.cTerm!=sCtx.cColSep ) break; + } + if( cSep=='(' ){ + sqlite3_free(zCreate); + import_cleanup(&sCtx); + *pzErr = shellMPrintf(0,"%s: empty file\n", sCtx.zFile); + return 1; } - }else - - if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){ - if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc); - rc = 2; - }else - - /* 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]); - } + zCreate = sqlite3_mprintf("%z\n)", zCreate); + if( eVerbose>=1 ){ + utf8_printf(p->out, "%s\n", zCreate); } - 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; + rc = sqlite3_exec(p->db, zCreate, 0, 0, 0); + sqlite3_free(zCreate); + if( rc ){ + *pzErr = shellMPrintf(0,"CREATE TABLE \"%s\"(...) failed: %s\n", + zTable, sqlite3_errmsg(p->db)); + import_cleanup(&sCtx); + return 1; } - }else - -#ifndef SQLITE_OMIT_VIRTUALTABLE - if( c=='e' && strncmp(azArg[0], "expert", n)==0 ){ - open_db(p, 0); - expertDotCommand(p, azArg, nArg); - }else -#endif - - 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, 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( 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\" VALUES(?", 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( iout, "Available file-controls:\n"); - for(i=0; iout, " .filectrl %s %s\n", - aCtrl[i].zCtrlName, aCtrl[i].zUsage); + 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++; } - rc = 1; - goto meta_command_exit; } + }while( sCtx.cTerm!=EOF ); - /* 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); + 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; + } - x = -1; - sqlite3_file_control(p->db, zSchema, filectrl, &x); - utf8_printf(p->out,"%d\n", x); - isOk = 2; - break; ++ memcpy(kwBuf, zKW, szKW); ++ kwBuf[szKW] = 0; ++ utf8_printf(p->out, "%s%s", kwBuf, zSep); + } + } + } - if( isOk==0 && iCtrl>=0 ){ - utf8_printf(p->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage); - rc = 1; - }else if( isOk==1 ){ - char zBuf[100]; - sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes); - raw_printf(p->out, "%s\n", zBuf); - } - }else ++ 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; ++} - 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; +/***************** + * 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; + } } - 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( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){ + lenPK = (int)strlen(zCollist); } - if( doStats==0 ){ - raw_printf(p->out, "/* No STAT tables available */\n"); + if( zCollist==0 ){ + zCollist = sqlite3_mprintf("\"%w\"", zCol); }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"); + zCollist = sqlite3_mprintf("%z,\"%w\"", zCollist, zCol); } - }else - - if( c=='h' && strncmp(azArg[0], "headers", n)==0 ){ - if( nArg==2 ){ - p->showHeader = booleanValue(azArg[1]); - p->shellFlgs |= SHFLG_HeaderSet; + } + 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{ - raw_printf(stderr, "Usage: .headers on|off\n"); - rc = 1; + 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 - - if( c=='h' && strncmp(azArg[0], "help", n)==0 ){ - if( nArg>=2 ){ - n = showHelp(p->out, azArg[1]); - if( n==0 ){ - utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]); - } + }else{ + *pzErr = shellMPrintf(0,"SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); + } + sqlite3_free(zSql); + return rc != 0; +} +DISPATCHABLE_COMMAND( iotrace ? 2 2 ){ + SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...); + if( iotrace && iotrace!=STD_OUT ) fclose(iotrace); + iotrace = 0; + if( nArg<2 ){ + sqlite3IoTrace = 0; + }else if( strcmp(azArg[1], "-")==0 ){ + sqlite3IoTrace = iotracePrintf; + iotrace = STD_OUT; + }else{ + iotrace = fopen(azArg[1], "w"); + if( iotrace==0 ){ + *pzErr = shellMPrintf(0,"Error: cannot open \"%s\"\n", azArg[1]); + sqlite3IoTrace = 0; + return 1; }else{ - showHelp(p->out, 0); - } - }else - - if( c=='i' && strncmp(azArg[0], "import", n)==0 ){ - char *zTable = 0; /* Insert data into this table */ - 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(); + sqlite3IoTrace = iotracePrintf; } - if( p->mode==MODE_Ascii ){ - xRead = ascii_read_one_field; - }else{ - xRead = csv_read_one_field; + } + return 0; +} +DISPATCHABLE_COMMAND( limits 5 1 3 ){ + static const struct { + const char *zLimitName; /* Name of a limit */ + int limitCode; /* Integer code for that limit */ + } aLimit[] = { + { "length", SQLITE_LIMIT_LENGTH }, + { "sql_length", SQLITE_LIMIT_SQL_LENGTH }, + { "column", SQLITE_LIMIT_COLUMN }, + { "expr_depth", SQLITE_LIMIT_EXPR_DEPTH }, + { "compound_select", SQLITE_LIMIT_COMPOUND_SELECT }, + { "vdbe_op", SQLITE_LIMIT_VDBE_OP }, + { "function_arg", SQLITE_LIMIT_FUNCTION_ARG }, + { "attached", SQLITE_LIMIT_ATTACHED }, + { "like_pattern_length", SQLITE_LIMIT_LIKE_PATTERN_LENGTH }, + { "variable_number", SQLITE_LIMIT_VARIABLE_NUMBER }, + { "trigger_depth", SQLITE_LIMIT_TRIGGER_DEPTH }, + { "worker_threads", SQLITE_LIMIT_WORKER_THREADS }, + }; + int i, n2; + open_db(p, 0); + if( nArg==1 ){ + for(i=0; idb, aLimit[i].limitCode, -1)); } - for(i=1; i3 ){ + return SHELL_INVALID_ARGS; + }else{ + int iLimit = -1; + n2 = strlen30(azArg[1]); + for(i=0; iout, "ERROR: extra argument: \"%s\". Usage:\n", z); - showHelp(p->out, "import"); - rc = 1; - goto meta_command_exit; + *pzErr = shellMPrintf(0,"ambiguous limit: \"%s\"\n", azArg[1]); + return 1; } - }else if( strcmp(z,"-v")==0 ){ - eVerbose++; - }else if( strcmp(z,"-skip")==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; + if( iLimit<0 ){ + *pzErr = sqlite3_mprintf + ("unknown limit: \"%s\"\n" + "enter \".limits\" with no arguments for a list.\n", + azArg[1]); + return 1; } - 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]; + if( nArg==3 ){ + sqlite3_limit(p->db, aLimit[iLimit].limitCode, + (int)integerValue(azArg[2])); } - 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 + 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; +} + +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; +} + +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; +} + +/***************** + * The .mode command + */ +COLLECT_HELP_TEXT[ + ".mode MODE ?TABLE? 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)", ++ " count Output only result row count", + " 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", ++ " off Query output suppressed", + " quote Escape answers as for SQL", + " table ASCII-art table", + " tabs Tab-separated values", + " tcl TCL list elements", +]; +DISPATCHABLE_COMMAND( mode ? 1 3 ){ + const char *zMode = nArg>=2 ? azArg[1] : ""; + int n2 = strlen30(zMode); + int c2 = zMode[0]; + if( c2=='i' && strncmp(azArg[1],"insert",n2)==0 ){ + p->mode = MODE_Insert; + set_table_name(p, nArg>=3 ? azArg[2] : "table"); + }else if( nArg>2 ){ + return SHELL_INVALID_ARGS; + }else if( c2=='l' && n2>2 && strncmp(azArg[1],"lines",n2)==0 ){ + p->mode = MODE_Line; + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); + }else if( c2=='c' && strncmp(azArg[1],"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); + }else if( c2=='l' && n2>2 && strncmp(azArg[1],"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( c2=='h' && strncmp(azArg[1],"html",n2)==0 ){ + p->mode = MODE_Html; + }else if( c2=='t' && strncmp(azArg[1],"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( c2=='c' && strncmp(azArg[1],"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( c2=='t' && strncmp(azArg[1],"tabs",n2)==0 ){ + p->mode = MODE_List; + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Tab); + }else if( c2=='q' && strncmp(azArg[1],"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( c2=='a' && strncmp(azArg[1],"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( c2=='m' && strncmp(azArg[1],"markdown",n2)==0 ){ + p->mode = MODE_Markdown; + }else if( c2=='t' && strncmp(azArg[1],"table",n2)==0 ){ + p->mode = MODE_Table; + }else if( c2=='b' && strncmp(azArg[1],"box",n2)==0 ){ + p->mode = MODE_Box; + }else if( c2=='c' && strncmp(azArg[1],"count",n2)==0 ){ + p->mode = MODE_Count; + }else if( c2=='o' && strncmp(azArg[1],"off",n2)==0 ){ + p->mode = MODE_Off; + }else if( c2=='j' && strncmp(azArg[1],"json",n2)==0 ){ + p->mode = MODE_Json; + }else if( nArg==1 ){ + raw_printf(p->out, "current output mode: %s\n", modeDescr[p->mode]); + }else{ + *pzErr = sqlite3_mprintf + ("Error: mode should be one of: ascii box column count csv html\n" + " insert json line list markdown off quote table tabs tcl\n"); + return 1; + } + p->cMode = p->mode; + return 0; +} + +/***************** + * The .oom command + */ +CONDITION_COMMAND( oom defined(SQLITE_DEBUG) ); +COLLECT_HELP_TEXT[ + ".oom ?--repeat M? ?N? Simulate an OOM error on the N-th allocation", +]; +DISPATCHABLE_COMMAND( oom ? 1 4 ){ + int i; + for(i=1; iout, "missing argument on \"%s\"\n", azArg[i]); + return 1; + }else{ + oomRepeat = (int)integerValue(azArg[++i]); + } + }else if( IsDigit(z[0]) ){ + oomCounter = (int)integerValue(azArg[i]); }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; - } - 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\"", zTable); - if( zSql==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); + raw_printf(p->out, "unknown argument: \"%s\"\n", azArg[i]); + raw_printf(p->out, "Usage: .oom [--repeat N] [M]\n"); + return 1; } - 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\"", zTable); - char cSep = '('; - while( xRead(&sCtx) ){ - zCreate = sqlite3_mprintf("%z%c\n \"%w\" TEXT", zCreate, cSep, sCtx.z); - cSep = ','; - if( sCtx.cTerm!=sCtx.cColSep ) break; - } - if( cSep=='(' ){ - 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\n)", zCreate); - if( eVerbose>=1 ){ - utf8_printf(p->out, "%s\n", zCreate); - } - rc = sqlite3_exec(p->db, zCreate, 0, 0, 0); - sqlite3_free(zCreate); - if( rc ){ - utf8_printf(stderr, "CREATE TABLE \"%s\"(...) failed: %s\n", zTable, - sqlite3_errmsg(p->db)); - import_cleanup(&sCtx); - rc = 1; - goto meta_command_exit; - } - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + } + raw_printf(p->out, "oomCounter = %d\n", oomCounter); + raw_printf(p->out, "oomRepeat = %d\n", oomRepeat); + return 0; +} + +/***************** + * The .open, .nonce and .nullvalue commands + */ +COLLECT_HELP_TEXT[ + ".open ?OPTIONS? ?FILE? Close existing database and reopen FILE", + " Options:", + " --append Use appendvfs to append database to the end of FILE", +#ifndef SQLITE_OMIT_DESERIALIZE + " --deserialize Load into memory using sqlite3_deserialize()", + " --hexdb Load the output of \"dbtotxt\" as an in-memory db", + " --maxsize N Maximum size for --hexdb or --deserialized database", +#endif + " --new Initialize FILE to an empty database", + " --nofollow Do not follow symbolic links", + " --readonly Open FILE readonly", + " --zip FILE is a ZIP archive", + ".nonce STRING Disable safe mode for one command if nonce matches", + ".nullvalue STRING Use STRING in place of NULL values", +]; +DISPATCHABLE_COMMAND( open 3 1 0 ){ + 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; + 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( zNewFilename ){ + *pzErr = shellMPrintf(0,"extra argument: \"%s\"\n", z); + return SHELL_INVALID_ARGS; + }else{ + zNewFilename = sqlite3_mprintf("%s", 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( zNewFilename || p->openMode==SHELL_OPEN_HEXDB ){ + if( newFlag && !p->bSafeMode ) shellDeleteFile(zNewFilename); + if( p->bSafeMode + && p->openMode!=SHELL_OPEN_HEXDB + && zNewFilename + && strcmp(zNewFilename,":memory:")!=0 + ){ + *pzErr = shellMPrintf(0,"open disk-based databases"); + return SHELL_FORBIDDEN_OP; } - sqlite3_free(zSql); - if( rc ){ - if (pStmt) sqlite3_finalize(pStmt); - utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db)); - import_cleanup(&sCtx); + 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; - goto meta_command_exit; + }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); + } + return rc; +} + +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]); + 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 + */ +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; } - 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\" VALUES(?", 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++; - } + 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)); } - }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); + sqlite3_finalize(pStmt); } }else @@@ -9618,32 -9612,57 +9739,36 @@@ DISPATCHABLE_COMMAND( session 3 2 0 ) */ if( strcmp(azCmd[0],"changeset")==0 || strcmp(azCmd[0],"patchset")==0 ){ FILE *out = 0; -- failIfSafeMode(p, "cannot run \".session %s\" in safe mode", azCmd[0]); -- if( nCmd!=2 ) goto session_syntax_error; -- if( pSession->p==0 ) goto session_not_open; -- out = fopen(azCmd[1], "wb"); -- if( out==0 ){ - *pzErr = sqlite3_mprintf - ("ERROR: cannot open \"%s\" for writing\n", azCmd[1]); - rc = 1; - utf8_printf(stderr, "ERROR: cannot open \"%s\" for writing\n", - azCmd[1]); ++ if( failIfSafeMode ++ (p, "cannot run \".session %s\" in safe mode", azCmd[0]) ){ ++ rc = SHELL_FORBIDDEN_OP; }else{ -- int szChng; -- void *pChng; -- if( azCmd[0][0]=='c' ){ -- rc = sqlite3session_changeset(pSession->p, &szChng, &pChng); ++ 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; }else{ -- rc = sqlite3session_patchset(pSession->p, &szChng, &pChng); -- } -- if( rc ){ - fprintf(STD_OUT, "Error: error code %d\n", rc); - printf("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); - if( pChng - && fwrite(pChng, szChng, 1, out)!=1 ){ - raw_printf(stderr, "ERROR: Failed to write entire %d-byte output\n", - szChng); ++ 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); } -- 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 - - /* .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 @@@ -10711,296 -10661,45 +10836,300 @@@ DISPATCHABLE_COMMAND( vfsinfo ? 1 2 ) 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( 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", +]; +DISPATCHABLE_COMMAND( width ? 1 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]); + } + 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; +} + +/* 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{ + 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( 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); +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( p->expert.pExpert ){ + expertFinish(p, 1, 0); + } +#endif + + /* Parse the input line into tokens. + */ + while( zLine[h] && nArg1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc); ++ if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) ++ p->abruptExit = rc; + rc = 2; }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); + if( c=='q' && strncmp(azArg[0], "quit", n)==0 ){ + rc = 2; }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]); +#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); + } + } + if( strncmp(azArg[0]+9, "integer", n-9)==0 ){ + int i; sqlite3_int64 v; + for(i=1; iout, "%s", zBuf); + } } }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]); + } - exit(1); ++ 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: @@@ -11008,16 -10707,7 +11137,7 @@@ p->outCount--; if( p->outCount==0 ) output_reset(p); } - switch( p->bSafeModeFuture ){ - default: - --p->bSafeModeFuture; - /* fall thru */ - case 0: - p->bSafeMode = 0; - break; - case 1: - p->bSafeMode = 1; - } - p->bSafeMode = p->bSafeModePersist; ++ updateSafeMode(p); return rc; } @@@ -11203,7 -10889,7 +11323,8 @@@ static int runOneSqlLine(ShellState *p ** is saved only if input is interactive. An interrupt signal will ** cause this routine to exit immediately, unless input is interactive. ** --** Return the number of errors. ++** Normally return (number_of_errors > 0), ++** but return SHELL_FORBIDDEN_OP for immediate shell exit. */ static int process_input(ShellState *p){ char *zLine = 0; /* A single input line */ @@@ -11211,13 -10897,13 +11332,15 @@@ int nLine; /* Length of current line */ int nSql = 0; /* Bytes of zSql[] used */ int nAlloc = 0; /* Allocated zSql[] space */ -- int rc; /* Error code */ ++ 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) */ p->lineno = 0; -- while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){ ++ while( rc<2 ++ && ++ (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 ){ @@@ -11244,10 -10930,10 +11367,10 @@@ continue; } if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){ - if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zLine); + if( ShellHasFlag(p, SHFLG_Echo) ) fprintf(STD_OUT, "%s\n", zLine); if( zLine[0]=='.' ){ rc = do_meta_command(zLine, p); -- if( rc==2 ){ /* exit requested */ ++ if( rc==2 || p->abruptExit!=0 ){ /* exit requested */ break; }else if( rc ){ errCnt++; @@@ -11285,20 -10971,20 +11408,21 @@@ }else{ clearTempFile(p); } - p->bSafeMode = p->bSafeModeFuture!=1; - p->bSafeMode = p->bSafeModePersist; ++ updateSafeMode(p); qss = QSS_Start; }else if( nSql && QSS_PLAINWHITE(qss) ){ - if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zSql); + if( ShellHasFlag(p, SHFLG_Echo) ) fprintf(STD_OUT, "%s\n", zSql); nSql = 0; qss = QSS_Start; } } if( nSql && QSS_PLAINDARK(qss) ){ errCnt += runOneSqlLine(p, zSql, p->in, startline); ++ updateSafeMode(p); } free(zSql); free(zLine); -- return errCnt>0; ++ return (p->abruptExit)? SHELL_FORBIDDEN_OP : errCnt>0; } /* @@@ -11370,13 -11056,13 +11494,37 @@@ static char *find_home_dir(int clearFla return home_dir; } ++/* ++** Run a single query. ++** This is a convenience function, used in several places, all of ++** demand a shell exit upon failure, which leads to return 2. ++ */ ++static int run_single_query(ShellState *p, char *zSql){ ++ char *zErrMsg = 0; ++ int rc; ++ open_db(p, 0); ++ rc = shell_exec(p, zSql, &zErrMsg); ++ if( zErrMsg!=0 || rc>0 ){ ++ /* Some kind of error, to result in exit before REPL. */ ++ if( zErrMsg!=0 ){ ++ utf8_printf(STD_ERR,"Error: %s\n", zErrMsg); ++ free(zErrMsg); ++ }else{ ++ utf8_printf(STD_ERR,"Error: unable to process SQL \"%s\"\n", zSql); ++ } ++ p->abruptExit = (rc==0)? 1 : rc; ++ rc = 2; ++ } ++ return rc; ++} ++ ++ /* ** 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 void process_sqliterc( ++static int process_sqliterc( ShellState *p, /* Configuration data */ const char *sqliterc_override /* Name of config file. NULL to use default */ ){ @@@ -11385,13 -11071,13 +11533,14 @@@ char *zBuf = 0; FILE *inSaved = p->in; int savedLineno = p->lineno; ++ int rc; 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; ++ return 1; } zBuf = sqlite3_mprintf("%s/.sqliterc",home_dir); sqliterc = zBuf; @@@ -11399,17 -11085,17 +11548,20 @@@ p->in = fopen(sqliterc,"rb"); if( p->in ){ if( stdin_is_interactive ){ - utf8_printf(stderr,"-- Loading resources from %s\n",sqliterc); + utf8_printf(STD_ERR,"-- Loading resources from %s\n",sqliterc); } -- if( process_input(p) && bail_on_error ) exit(1); ++ rc = process_input(p); fclose(p->in); }else if( sqliterc_override!=0 ){ - utf8_printf(stderr,"cannot open: \"%s\"\n", sqliterc); - if( bail_on_error ) exit(1); + utf8_printf(STD_ERR,"cannot open: \"%s\"\n", sqliterc); - if( bail_on_error ) exit(1); ++ rc = 1; ++ }else{ ++ rc = 0; } p->in = inSaved; p->lineno = savedLineno; sqlite3_free(zBuf); ++ return rc; } /* @@@ -11567,21 -11252,13 +11719,20 @@@ 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 i; @@@ -11595,9 -11272,9 +11746,8 @@@ char **argvToFree = 0; int argcToFree = 0; #endif -- - setBinaryMode(stdin, 0); - setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */ + setBinaryMode(STD_IN, 0); + setvbuf(STD_ERR, 0, _IONBF, 0); /* Make sure stderr is unbuffered */ stdin_is_interactive = isatty(0); stdout_is_console = isatty(1); @@@ -11664,15 -11341,15 +11814,6 @@@ assert( argc>=1 && argv && argv[0] ); Argv0 = argv[0]; -- /* Make sure we have a valid signal handler early, before anything -- ** else is done. -- */ --#ifdef SIGINT -- signal(SIGINT, interrupt_handler); --#elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE) -- SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE); --#endif -- #ifdef SQLITE_SHELL_DBNAME_PROC { /* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name @@@ -11691,7 -11368,7 +11832,7 @@@ ** and the first command to execute. */ verify_uninitialized(); -- for(i=1; izDbFilename = ":memory:"; warnInmemoryDb = argc==1; #else - utf8_printf(stderr,"%s: Error: no database filename specified\n", Argv0); - return 1; + utf8_printf(STD_ERR,"%s: Error: no database filename specified\n", Argv0); - return 1; ++ rc = 1; ++ goto shell_bail; #endif } - data.out = stdout; + data.out = STD_OUT; sqlite3_appendvfs_init(0,0,0); /* Go ahead and open the database file if it already exists. If the @@@ -11874,14 -11551,14 +12026,17 @@@ ** is given on the command line, look for a file named ~/.sqliterc and ** try to process it. */ -- process_sqliterc(&data,zInitFile); ++ rc = process_sqliterc(&data,zInitFile); ++ if( rc>1 || (rc!=0 && bail_on_error) ){ ++ goto shell_bail; ++ } /* Make a second pass through the command-line argument and set ** options. This second pass is delayed until after the initialization ** file is processed so that the command-line arguments will override ** settings in the initialization file. */ -- for(i=1; i0 ){ - utf8_printf(stderr, "Error: cannot mix regular SQL or dot-commands" + utf8_printf(STD_ERR, "Error: cannot mix regular SQL or dot-commands" " with \"%s\"\n", z); -- return 1; ++ rc = 1; ++ goto shell_bail; } open_db(&data, OPEN_DB_ZIPFILE); if( z[2] ){ argv[i] = &z[2]; -- arDotCommand(&data, 1, argv+(i-1), argc-(i-1)); ++ rc = arDotCommand(&data, 1, argv+(i-1), argc-(i-1)); }else{ -- arDotCommand(&data, 1, argv+i, argc-i); ++ rc = arDotCommand(&data, 1, argv+i, argc-i); } readStdin = 0; break; #endif }else if( strcmp(z,"-safe")==0 ){ - data.bSafeMode = data.bSafeModePersist = 1; + data.bSafeMode = data.bSafeModeFuture = 1; }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"); - return 1; ++ rc = 2; } data.cMode = data.mode; } @@@ -12058,26 -11735,26 +12210,14 @@@ ** command-line inputs, except for the argToSkip argument which contains ** the database filename. */ -- for(i=0; i2 || data.abruptExit>1 ){ ++ rc = (rc no exit + * ~0 => a non-error (0) exit + * other => exit with process exit code other + * For embedded shell, "exit" means "return from REPL". + */ + int shellExit; ++ ++ /* Number of lines written during a query result output */ ++ int resultCount; + /* Whether to show column names for certain output modes */ + int showHeader; + /* Column separator character for some modes */ + char *zFieldSeparator; + /* Row separator character for some modes (MODE_Ascii) */ + char *zRecordSeparator; + /* Row set prefix for some modes */ + char *zRecordLead; + /* Row set suffix for some modes */ + char *zRecordTrail; + /* Text to represent a NULL in external data formats */ + char *zNullValue; + /* Number of column widths presently desired or tracked */ + int numWidths; /* known allocation count of next 2 members */ + /* The column widths last specified via .width command */ + int *pWantWidths; + /* The column widths last observed in query results */ + int *pHaveWidths; +} ShellExState; + +/* The shell's state, shared among meta-command implementations. + * The ShellStateX object includes a private partition whose content + * and usage are opaque to shell extensions compiled separately + * from the shell.c core. (As defined here, it is wholly opaque.) + */ +typedef struct ShellStateX { + ShellExState sxs; /* sizeof(ShellExState) will never shrink. */ + struct ShellState *pSS; /* The offset of this member is NOT STABLE. */ +} ShellStateX; + +/* This function pointer has the same signature as the sqlite3_X_init() + * function that is called as SQLite3 completes loading an extension. + */ +typedef int (*ExtensionId) + (sqlite3 *, char **, const struct sqlite3_api_routines *); + +/***************** + * See "Shell Extensions, Programming" for purposes and usage of the following + * interfaces supporting extended meta-commands and import and output modes. + */ + +/* An object implementing below interface is registered with the + * shell to make new or overriding meta-commands available to it. + */ +INTERFACE_BEGIN( MetaCommand ); +PURE_VMETHOD(const char *, name, MetaCommand, 0,()); +PURE_VMETHOD(const char *, help, MetaCommand, 1,(int more)); +PURE_VMETHOD(struct {unsigned minArgs; unsigned maxArgs;}, + argsRange, MetaCommand, 0,()); +PURE_VMETHOD(int, execute, MetaCommand, + 4,(ShellStateX *, char **pzErrMsg, int nArgs, char *azArgs[])); +INTERFACE_END( MetaCommand ); + +/* Define error codes to be returned either by a meta-command during + * its own checking or by the dispatcher for bad argument counts. + */ +#define SHELL_INVALID_ARGS SQLITE_MISUSE +#define SHELL_FORBIDDEN_OP 0x7ffe /* Action disallowed under --safe.*/ + +/* An object implementing below interface is registered with the + * shell to make new or overriding output modes available to it. + */ +INTERFACE_BEGIN( OutModeHandler ); +PURE_VMETHOD(const char *, name, OutModeHandler, 0,()); +PURE_VMETHOD(const char *, help, OutModeHandler, 1,(int more)); +PURE_VMETHOD(int, openResultsOutStream, OutModeHandler, + 5,( ShellExState *pSES, char **pzErr, + int numArgs, char *azArgs[], const char * zName )); +PURE_VMETHOD(int, prependResultsOut, OutModeHandler, + 3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt )); +PURE_VMETHOD(int, rowResultsOut, OutModeHandler, + 3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt )); +PURE_VMETHOD(int, appendResultsOut, OutModeHandler, + 3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt )); +PURE_VMETHOD(void, closeResultsOutStream, OutModeHandler, + 2,( ShellExState *pSES, char **pzErr )); +INTERFACE_END( OutModeHandlerVtable ); + +/* An object implementing below interface is registered with the + * shell to make new or overriding data importers available to it. + */ +INTERFACE_BEGIN( ImportHandler ); +PURE_VMETHOD(const char *, name, ImportHandler, 0,()); +PURE_VMETHOD(const char *, help, ImportHandler, 1,( int more )); +PURE_VMETHOD(int, openDataInStream, ImportHandler, + 5,( ShellExState *pSES, char **pzErr, + int numArgs, char *azArgs[], const char * zName )); +PURE_VMETHOD(int, prepareDataInput, ImportHandler, + 3,( ShellExState *pSES, char **pzErr, sqlite3_stmt * *ppStmt )); +PURE_VMETHOD(int, rowDataInput, ImportHandler, + 3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt )); +PURE_VMETHOD(int, finishDataInput, ImportHandler, + 3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt )); +PURE_VMETHOD(void, closeDataInStream, ImportHandler, + 2,( ShellExState *pSES, char **pzErr )); +INTERFACE_END( ImportHandlerVtable ); + +typedef struct { + int helperCount; + union ExtHelp { + struct { + void (*failIfSafeMode)(ShellStateX *p, const char *zErrMsg, ...); + } named ; + void (*nameless[2])(); /* Same as named but anonymous plus a sentinel. */ + } helpers; +} ExtensionHelpers; + +#define SHELLEXT_VALIDITY_MARK "ExtensibleShell" + +typedef struct ShellExtensionLink { + char validityMark[16]; /* Preset to contain "ExtensibleShell\x00" */ + char *zErrMsg; /* Extension puts error message here if any. */ + int sizeOfThis; /* sizeof(struct ShellExtensionLink) */ + const char *shellVersion; /* Preset to "3.??.??\x00" or similar */ + + /* An init "out" parameter, used as the loaded extension ID. Unless + * this is set within sqlite3_X_init() prior to register*() calls, + * the extension cannot be unloaded. + */ + ExtensionId eid; + + /* Another init "out" parameter, a destructor for extension overall. + * Set to 0 on input and may be left so if no destructor is needed. + */ + void (*extensionDestruct)(void *); + + /* Various shell extension helpers and feature registration functions + */ + ExtensionHelpers * pExtHelp; + + union ShellExtensionAPI { + struct ShExtAPI { + /* Register a meta-command */ + int (*registerMetaCommand)(ExtensionId eid, MetaCommand *pMC); + /* Register an output data display (or other disposition) mode */ + int (*registerOutMode)(ExtensionId eid, OutModeHandler *pOMH); + /* Register an import variation from (various sources) for .import */ + int (*registerImporter)(ExtensionId eid, ImportHandler *pIH); + /* Preset to 0 at extension load, a sentinel for expansion */ + void (*pExtra)(void); + } named; + void (*pFunctions[4])(); /* 0-terminated sequence of function pointers */ + } api; +} ShellExtensionLink; + +/* Test whether a char ** references a ShellExtensionLink instance's + * validityMark, and if so return the instance's address, else return 0. + * This macro may be used by a shell extension's sqlite3_X_init() function + * to obtain a pointer to the ShellExtensionLink struct, derived from the + * error message pointer (pzErrMsg) passed as the 2nd argument. This enables + * the extension to incorporate its features into a running shell process. + */ +#define EXTENSION_LINKAGE_PTR(pzem) ( \ + pzem != 0 && *pzem != 0 && strcmp(*pzem, SHELLEXT_VALIDITY_MARK) == 0 \ + && *pzem == (char *)pzem \ + + offsetof(ShellExtensionLink, validityMark) \ + - offsetof(ShellExtensionLink, zErrMsg) ) \ + ? (ShellExtensionLink *) \ + ((char *)pzem-offsetof(ShellExtensionLink,zErrMsg)) \ + : 0 + +/* String used with SQLite "Pointer Passing Interfaces" as a type marker. + * That API subset is used by the shell to pass its extension API to the + * sqlite3_X_init() function of extensions, via the DB parameter. + */ +#define SHELLEXT_API_POINTERS "shellext_api_pointers" + +/* Pre-write a function to retrieve a ShellExtensionLink pointer from the + * shell's DB. This is an alternative to use of the EXTENSION_LINKAGE_PTR + * macro above. It takes some more code, replicated across extensions. + */ +#define DEFINE_SHDB_TO_SHEXT_API(func_name) \ + static ShellExtensionLink * func_name(sqlite3 * db){ \ + ShellExtensionLink *rv = 0; sqlite3_stmt *pStmt = 0; \ + if( SQLITE_OK==sqlite3_prepare(db,"SELECT shext_pointer(0)",-1,&pStmt,0) \ + && SQLITE_ROW == sqlite3_step(pStmt) ) \ + rv = (ShellExtensionLink *)sqlite3_value_pointer \ + (sqlite3_column_value(pStmt, 0), SHELLEXT_API_POINTERS); \ + sqlite3_finalize(pStmt); return rv; \ + } + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* !defined(SQLITE3SHX_H) */ diff --cc test/shell1.test index aaf03ce43e,c4e2ceb88b..4fbde2976a --- a/test/shell1.test +++ b/test/shell1.test @@@ -74,7 -74,7 +74,7 @@@ do_test shell1-1.3.1 catchcmd "-init FOO test.db" "" } {0 {}} do_test shell1-1.3.2 { -- catchcmd "-init FOO test.db .quit BAD" "" ++ catchcmdex "-init FOO test.db .quit BAD" "" } {0 {}} do_test shell1-1.3.3 { catchcmd "-init FOO test.db BAD .quit" "" @@@ -182,16 -182,13 +182,16 @@@ do_test shell1-1.16.1 # check first token handling do_test shell1-2.1.1 { catchcmd "test.db" ".foo" -} {1 {Error: unknown command or invalid arguments: "foo". Enter ".help" for help}} +} {1 {Error: unknown command: "foo"}} do_test shell1-2.1.2 { catchcmd "test.db" ".\"foo OFF\"" -} {1 {Error: unknown command or invalid arguments: "foo OFF". Enter ".help" for help}} +} {1 {Error: unknown command: "foo OFF"}} do_test shell1-2.1.3 { catchcmd "test.db" ".\'foo OFF\'" -} {1 {Error: unknown command or invalid arguments: "foo OFF". Enter ".help" for help}} +} {1 {Error: unknown command: "foo OFF"}} + - set modeShouldBe "Error: mode should be one of: ascii box column csv html - insert json line list markdown quote table tabs tcl" ++set modeShouldBe "Error: mode should be one of: ascii box column count csv html ++ insert json line list markdown off quote table tabs tcl" # unbalanced quotes do_test shell1-2.2.1 { @@@ -405,8 -403,7 +405,7 @@@ do_test shell1-3.11.2 do_test shell1-3.11.3 { # too many arguments catchcmd "test.db" ".import FOO BAR BAD" - } {1 {Error: invalid arguments for ".import" - surplus argument: "BAD"}} -} {/1 .ERROR: extra argument: "BAD".*./} ++} {1 {Error: invalid arguments for ".import"}} # .indexes ?TABLE? Show names of all indexes # If TABLE specified, only show indexes for tables @@@ -503,8 -500,14 +502,7 @@@ do_test shell1-3.15.2 do_test shell1-3.15.3 { # too many arguments catchcmd "test.db" ".output FOO BAD" - } {1 {Error: invalid arguments for ".output" - excess argument: "BAD"}} -} {1 {ERROR: extra parameter: "BAD". Usage: -.output ?FILE? Send output to FILE or stdout if FILE is omitted - If FILE begins with '|' then open it as a pipe. - Options: - --bom Prefix output with a UTF8 byte-order mark - -e Send output to the system text editor - -x Send output as CSV to a spreadsheet -child process exited abnormally}} ++} {1 {Error: invalid arguments for ".output"}} # .output stdout Send output to the screen do_test shell1-3.16.1 { @@@ -513,8 -516,14 +511,7 @@@ do_test shell1-3.16.2 { # too many arguments catchcmd "test.db" ".output stdout BAD" - } {1 {Error: invalid arguments for ".output" - excess argument: "BAD"}} -} {1 {ERROR: extra parameter: "BAD". Usage: -.output ?FILE? Send output to FILE or stdout if FILE is omitted - If FILE begins with '|' then open it as a pipe. - Options: - --bom Prefix output with a UTF8 byte-order mark - -e Send output to the system text editor - -x Send output as CSV to a spreadsheet -child process exited abnormally}} ++} {1 {Error: invalid arguments for ".output"}} # .prompt MAIN CONTINUE Replace the standard prompts do_test shell1-3.17.1 {