From: larrybr Date: Sat, 27 May 2023 00:03:43 +0000 (+0000) Subject: CLI much closer to being callable. (fewer exit() calls) ^C interrupt response improve... X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=9e1e39de6150a8bbf733e29206f639fa1fb8ea29;p=thirdparty%2Fsqlite.git CLI much closer to being callable. (fewer exit() calls) ^C interrupt response improved. Sync w/trunk. Some diagnostic code removed. Some refactoring for sake of FIDDLE build. Streamline main(), by moving code to more dedicated routines and otherwise. Make -quiet more useful. FossilOrigin-Name: bff3a9a834a71871129e516c9a6d05fab92e004934fe0b99bccb9f3f1fd56e33 --- 9e1e39de6150a8bbf733e29206f639fa1fb8ea29 diff --cc manifest index db172f79a4,be1e1a88d0..2ba48ebe56 --- a/manifest +++ b/manifest @@@ -1,13 -1,13 +1,13 @@@ - C First\sserious\sstab\sat\sFIDDLE-build-ready.\sFix\sa\sflub\swith\sabsent\s-A\soption\sfor\ssome\sbuilds. - D 2023-05-21T05:06:30.329 -C Add\sability\sfor\sthe\sJS\sWorker1.exec()\sAPI\sto\sreport\sthe\snumber\sof\schanges\smade\sto\sthe\scaller,\sper\srequest\sin\s[forum:d0b19483642e20dd\s|\sforum\spost\sd0b19483642e20dd]. -D 2023-05-25T16:49:06.244 ++C CLI\smuch\scloser\sto\sbeing\scallable.\s(fewer\sexit()\scalls)\s^C\sinterrupt\sresponse\simproved.\sSync\sw/trunk.\sSome\sdiagnostic\scode\sremoved.\sSome\srefactoring\sfor\ssake\sof\sFIDDLE\sbuild.\sStreamline\smain(),\sby\smoving\scode\sto\smore\sdedicated\sroutines\sand\sotherwise.\sMake\s-quiet\smore\suseful. ++D 2023-05-27T00:03:43.927 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 -F Makefile.in 764f2e3e8fb4ae1c8dfe03e65b2b3b01bd1fc57edf78ec2cab3a1301e90e1905 +F Makefile.in d97bb86f0fbd38e2b71b3bd8089158c89d6b03ef575b72183348c0d7322c90b7 F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241 -F Makefile.msc ce7bea7931e1ef96b0f44c0362074a8bb62e61a0e7cfdb05afebd928adaacc2b +F Makefile.msc ac5dfaf082eef506a9cfc7d5f2d95338282e76dad3e37567110362925f40cf41 F README.md e05bd8fcb45da04ab045c37f79a98654e8aa3b3b8f302cfbba80a0d510df75f7 - F VERSION 17f95ae2fdf21f0e9575eb0b0511ea63f15d71dfff431b21c2b4adbfa70cfbbf + F VERSION c6366dc72582d3144ce87b013cc35fe48d62f6d07d5be0c9716ea33c862144aa F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 @@@ -33,8 -33,8 +33,8 @@@ F autoconf/tea/win/nmakehlp.c b01f822ea F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 - F configure 5e52f3bce3b598d0c4ee6e6c92c180f35f9b29dfcc1775332bf87cf55302f9c3 x -F configure 9dc3300339f4d6b3c3b108de60cc6ae6b3c547e25c7e6df280b4775db4de3a1b x -F configure.ac 4654d32ac0a0d0b48f1e1e79bdc3d777b723cf2f63c33eb1d7c4ed8b435938e8 ++F configure 697e926af786eb6ddb94ade8aace9f42814902d939ddc6a7bc5f11c3eca7dd51 x +F configure.ac 510be9293c7efca69c0cc7f427f223b0597f82dda214af7491887db25fa4e237 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd F doc/json-enhancements.md e356fc834781f1f1aa22ee300027a270b2c960122468499bf347bb123ce1ea4f @@@ -638,14 -634,11 +638,14 @@@ F src/pragma.h e690a356c18e98414d2e870e F src/prepare.c 6350675966bd0e7ac3a464af9dbfe26db6f0d4237f4e1f1acdb17b12ad371e6e F src/printf.c b9320cdbeca0b336c3f139fd36dd121e4167dd62b35fbe9ccaa9bab44c0af38d F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c +F src/resmanage.c 3d8e80124e21723c2ef7027d42558c2f4ebc7b2a04c9ad26136561c17f2b4827 +F src/resmanage.h 626dc03a581fdbd4a52fc15bcfb49da5dec1acc3be73ebd053ef9a34fa153834 F src/resolve.c 3e53e02ce87c9582bd7e7d22f13f4094a271678d9dc72820fa257a2abb5e4032 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 - F src/select.c 738c3a3d6929f8be66c319bad17f6b297bd60a4eb14006075c48a28487dc7786 - F src/shell.c.in 2399be6f9e2e7270126aaffd1e8e47a21342bf590073b8a6a2e16ef8d76dd001 + F src/select.c 899d5313955e7d187bbc01e32c1ba81d145f48e4e24d0f161d63af8c243b1511 -F src/shell.c.in 29357bf9001c4dd977c2ce6cf55dcedd82f539c17654d15cd779fe429ceb169d ++F src/shell.c.in a10797809499499c192d5e95653ca36dbdb89bfbc512ae077d733b91a1053c7d +F src/shext_linkage.h 27dcf7624df05b2a7a6d367834339a6db3636f3035157f641f7db2ec499f8f6d - F src/sqlite.h.in c14a4471fcd897a03631ac7ad3d05505e895e7b6419ec5b96cae9bc4df7a9fc6 + F src/sqlite.h.in 8e0265e42d7695d86a0b8c7de9979e6c29edc2c616fa5eea11b682a6af838c7c F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4 F src/sqliteInt.h a3ced8b9ebc573189c87b69f24bf10d2b9cd3cefefaae52623a2fa79e6fdd408 @@@ -2081,8 -2070,8 +2081,8 @@@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a9 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 - P b64ef7f92e7c369b279dac136983c69ab1e6fedc7f12a7dff65a86506761bce5 - R 2bc4de713d5e36abe65fec53d4e63406 -P 80c7c0360c08bea0733deccb8071920c60558b75df76b6afad093c82adf30ea6 -R e8fcc223cacf06d978c3812fcacaa2db -U stephan -Z 4c26ba95f98d5f81d88f07b72f4945f7 ++P bfc36f0f10c37ae89bae6de96da38197998c8d95ab49397775d43d8a1ae7eeef 6e79505df915612b60696e4eec5c9973175fe6ecf273eb3152b996e63ae54a07 ++R ec7c0e85ca7d865fd239eca8d8703f9f +U larrybr - Z 28193c513ee8b55c220f72b52c0149d2 ++Z 67d99d9d87525146ba5f4b40fdff84fe # Remove this line to create a well-formed Fossil manifest. diff --cc manifest.uuid index fd143b2db7,f2df01c2ff..c88514feb7 --- a/manifest.uuid +++ b/manifest.uuid @@@ -1,1 -1,1 +1,1 @@@ - bfc36f0f10c37ae89bae6de96da38197998c8d95ab49397775d43d8a1ae7eeef -6e79505df915612b60696e4eec5c9973175fe6ecf273eb3152b996e63ae54a07 ++bff3a9a834a71871129e516c9a6d05fab92e004934fe0b99bccb9f3f1fd56e33 diff --cc src/shell.c.in index c434240330,8e9ccce7cc..185d2afdb4 --- a/src/shell.c.in +++ b/src/shell.c.in @@@ -9,15 -9,15 +9,14 @@@ ** May you share freely, never taking more than you give. ** ************************************************************************* --** This file contains code to implement the "sqlite" command line --** utility for accessing SQLite databases. ++** This file contains code to implement the "sqlite" or "sqlitex" ++** command line utilities for accessing SQLite databases. */ ++ #if (defined(_WIN32) || defined(WIN32)) && !defined(_CRT_SECURE_NO_WARNINGS) /* This needs to come before any includes for MSVC compiler */ #define _CRT_SECURE_NO_WARNINGS #endif --typedef unsigned int u32; --typedef unsigned short int u16; /* ** Optionally #include a user-defined header, whereby compilation options @@@ -239,30 -239,6 +239,30 @@@ extern char *sqlite3_win32_utf8_to_mbcs extern LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText); #endif - /* Get the shell extension interfaces and structs. */ ++/* Get the shell extension interfaces and structs. (ShellExState ...) */ +INCLUDE shext_linkage.h + +/* Forward declarations for use by resmanage. */ +typedef struct ShellText ShellText; +static void freeText(ShellText *p); +#define SHELL_MANAGE_TEXT /* Cause text_holder() to be available. */ +/* Get resource management package, supporting OOM and safe mode exits. */ +INCLUDE resmanage.c + +/* For an embedded shell, allow the 3 standard streams to be specified. +** If used, these names will have to refer to something globally reachable +** from the same thread which called the shell's main(). +**/ +#ifndef STD_IN +# define STD_IN stdin +#endif +#ifndef STD_OUT +# define STD_OUT stdout +#endif +#ifndef STD_ERR +# define STD_ERR stderr +#endif + /* On Windows, we normally run with output mode of TEXT so that \n characters ** are automatically translated into \r\n. However, this behavior needs ** to be disabled in some cases (ex: when generating CSV output and when @@@ -506,39 -470,15 +506,33 @@@ static int console_utf8 = 0 */ static int stdout_is_console = 1; +/* +** Disable certain features for FIDDLE build. +*/ +#ifdef SQLITE_SHELL_FIDDLE +# undef SQLITE_OMIT_DYNAPROMPT +# undef SHELL_OMIT_LOAD_EXTENSION +# define SQLITE_OMIT_DYNAPROMPT 1 +# define SHELL_OMIT_LOAD_EXTENSION 1 +#endif + - /* - ** This statically allocated variable is used to strip a resource - ** stack upon abrupt exits (involving OOM or -safe mode violations. - */ - static ResourceMark main_resource_mark = 0; - - /* Avoid redundant atexit() registration. */ - static u8 atexit_registered = 0; - /* ** The following is the open SQLite database. We make a pointer ** to this database a static variable so that it can be accessed ** by the SIGINT handler to interrupt database processing. */ --static sqlite3 *globalDb = 0; ++static volatile sqlite3 *globalDb = 0; ++/* Shorten voltatile cast-away for this. */ ++#define GLOBAL_DB ((sqlite3 *)globalDb) ++ +/* - ** Mutex used to access *globalDb from main thread or ^C handler. ++** Mutex used to access *globalDb from main thread or ^C handler and to ++** guard against referenced DB race where it is closed and interrupted. +*/ +static sqlite3_mutex *pGlobalDbLock = 0; - /* -** True if an interrupt (Control-C) has been received. +** Greater than 0 if an interrupt (Control-C) has been received. */ static volatile int seenInterrupt = 0; @@@ -663,6 -595,6 +657,30 @@@ static void dynamicContinuePrompt(void) } #endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */ ++#ifndef SHELL_NOT_CALLABLE ++/* ++** Provide an atexit() replacement for when shell is called by an application. ++*/ ++typedef void (*terminate_callback_t)(void); ++#define MAX_TERM_CALLBACKS 4 ++static terminate_callback_t terminate_callbacks[MAX_TERM_CALLBACKS]; ++static int terminate_callback_count = 0; ++static void upon_terminate(terminate_callback_t tcb){ ++ assert(terminate_callback_count < MAX_TERM_CALLBACKS); ++ if( terminate_callback_count0 ){ ++ (terminate_callbacks[--terminate_callback_count])(); ++ } ++} ++#else ++# define upon_terminate(x) atexit(x) ++# define terminate_actions() ++#endif /* !defined(SHELL_NOT_CALLABLE) */ ++ #if SHELL_WIN_UTF8_OPT /* Following struct is used for -utf8 operation. */ static struct ConsoleState { @@@ -833,125 -761,17 +851,125 @@@ void utf8_printf(FILE *out, const char # define raw_printf fprintf #endif -/* Indicate out-of-memory and exit. */ -static void shell_out_of_memory(void){ - raw_printf(stderr,"Error: out of memory\n"); +/* +** Provide a way for embedding apps to handle OOM or other shell-fatal +** conditions their way. A plain exit() call will undoubtedly leak +** memory and handles. The default scheme, when SHELL_TERMINATE is +** not defined, uses setjmp()/longjmp() execution stack ripping +** together with a resource stack which can be popped to release +** resources automatically. One replacement could be: +# define SHELL_TERMINATE(why) \ + fprintf(stderr, "Error: Terminating due to %s\n", why);\ exit(1); +*/ + +/* +** Provide an abrupt exit/termination for routines that cannot proceed +** or whose failure means the CLI cannot usefully continue. +*/ +static void shell_terminate(const char *zWhy){ +#ifndef SHELL_TERMINATE + quit_moan(zWhy, 2); +#else + release_holders_mark(exit_mark); + SHELL_TERMINATE(zWhy); +#endif +} + +/* Indicate out-of-memory and terminate. */ +static void shell_out_of_memory(void){ + shell_terminate("out of memory"); } -/* Check a pointer to see if it is NULL. If it is NULL, exit with an -** out-of-memory error. +#ifdef SQLITE_DEBUG +int fake_oom_countdown = 0; + +static void maybe_fake_oom(void){ + if( fake_oom_countdown>0 && --fake_oom_countdown==0 ){ + shell_out_of_memory(); + } +} + +/* The next 2 routines normally check for an OOM error. However, when +** fake_oom_countdown is non-zero, it is decremented and, upon reaching +** zero, a fake OOM condition is emulated. Additionally, (to keep leak - ** detection working), the non-zero memory pointer is freed. ++** detection reporting useful), the non-zero memory pointer is freed. +*/ + +/* Check a malloc()'ed (or equivalent) pointer to see if it is NULL. +** If so, terminate with an out-of-memory error. +*/ +static void shell_check_oomm(const void *p){ + if( p==0 ) shell_out_of_memory(); + if( fake_oom_countdown>0 && --fake_oom_countdown==0 ){ + free((void*)p); + shell_out_of_memory(); + } +} +/* Check a sqlite3_malloc()'ed (or equivalent) pointer to see if it is NULL. +** If so, terminate with an out-of-memory error. */ -static void shell_check_oom(const void *p){ +static void shell_check_ooms(const void *p){ if( p==0 ) shell_out_of_memory(); + if( fake_oom_countdown>0 && --fake_oom_countdown==0 ){ + sqlite3_free((void*)p); + shell_out_of_memory(); + } +} +#else +# define shell_check_ooms(p) do{ if((p)==0) shell_out_of_memory(); }while 0 +# define shell_check_oomm(p) do{ if((p)==0) shell_out_of_memory(); }while 0 +# define maybe_fake_oom() +#endif + +/* Check a SQLite result code for out-of-memory indication. +** If that is so, terminate with an out-of-memory error. +*/ +static int shell_check_nomem(int rc){ + if( SQLITE_NOMEM==rc ) shell_out_of_memory(); + return rc; +} +/* Convenience functions using shell_check_nomem supporting OOM testing: */ +static int s3_exec_noom(sqlite3 *db, const char *sql, + int (*callback)(void*,int,char**,char**), void *pvcb, + char **pzErr){ + int rc; + char *zErrHere = 0; + if( pzErr ) *pzErr = 0; + maybe_fake_oom(); + rc = sqlite3_exec(db, sql, callback, pvcb, &zErrHere); + if( rc==SQLITE_NOMEM ){ + sqlite3_free(zErrHere); + shell_out_of_memory(); + }else{ + if( pzErr ) *pzErr = zErrHere; + } + return rc; +} +static int s3_step_noom(sqlite3_stmt *pstmt){ + maybe_fake_oom(); + return shell_check_nomem(sqlite3_step(pstmt)); +} +static int s3_prepare_v2_noom(sqlite3 *db, const char *zSql, int nByte, + sqlite3_stmt **ppStmt, const char **pzTail){ + maybe_fake_oom(); + return shell_check_nomem(sqlite3_prepare_v2(db,zSql,nByte,ppStmt,pzTail)); +} + +/* Shorten a sqlite3_prepare_v2() usage pattern common in this code: +** Build a query string; OOM-check it; prepare; free and mark the string. +** There is no length or pzTail argument -- (useless in this context.) +** On return (if any), *pzSql will have been set to 0. */ +static int s3_prep_noom_free(sqlite3 *db, char **pzSql, sqlite3_stmt **ppStmt){ + int rc; + sstr_ptr_holder(pzSql); + maybe_fake_oom(); + shell_check_ooms(*pzSql); + rc = sqlite3_prepare_v2(db,*pzSql,-1,ppStmt,0); + shell_check_nomem(rc); + release_holder(); + *pzSql = 0; + return rc; } /* @@@ -1094,128 -914,10 +1112,128 @@@ static FILE * openChrSource(const char } /* -** This routine reads a line of text from FILE in, stores -** the text in memory obtained from malloc() and returns a pointer -** to the text. NULL is returned at end of file, or if malloc() -** fails. +** Arrange for shell input from either a FILE or a string. +** For applicable invariants, see strLineGet(...) which is +** the only modifier of this struct. (4rd and 5th members) +** All other members are simply initialized to select the +** input stream and track input sources, set by whatever +** routines redirect the input. +*/ +typedef struct InSource { + struct InSource *pFrom; /* Aid redirect tracking and auto-unnesting. */ + FILE *inFile; /* Will be 0 when input is to be taken from string. */ + char *zStrIn; /* Will be 0 when no input is available from string. */ + int iReadOffset; /* Offset into zStrIn where next "read" to be done */ + int lineno; /* How many lines have been read from this source */ + const char *zSourceSay; /* For complaints, a name kept for this source */ + union { + int (*stream)(FILE *); + void (*ptext)(char *); + } closer; /* Closer for the file or freer for the text, set by opener */ +} InSource; - #define INSOURCE_STR_REDIR(str, tagTo, isFrom) {isFrom, 0, str, 0, 0, tagTo } - #define INSOURCE_FILE_REDIR(fh, tagTo, isFrom) {isFrom, fh, 0, 0, 0, tagTo } ++#define INSOURCE_STR_REDIR(str, tagTo, isFrom) {isFrom, 0, str, 0, 0, tagTo, 0 } ++#define INSOURCE_FILE_REDIR(fh, tagTo, isFrom) {isFrom, fh, 0, 0, 0, tagTo, 0 } +#define INSOURCE_IS_INVOKEARG(pIS) \ + ((pIS)==&cmdInSource) +#ifndef SQLITE_SHELL_FIDDLE +# define INSOURCE_IS_INTERACTIVE(pIS) \ + ((pIS)==&termInSource && stdin_is_interactive ) +#else +# define INSOURCE_IS_INTERACTIVE(pIS) 0 +#endif + +/* These instances' addresses are taken as part of interactive input test + * or test for other special handling as a command-line argument. */ +static InSource cmdInSource = { 0, 0, 0, 0, 0, "", 0 }; +#ifndef SQLITE_SHELL_FIDDLE +static InSource termInSource = { 0, 0, 0, 0, 0, "", 0 }; +static InSource stdInSource = { 0, 0, 0, 0, 0, "", 0 }; +#else +static InSource fiddleInSource = { 0, 0, 0, 0, 0, "", 0 }; +#endif /* defined(SQLITE_SHELL_FIDDLE) */ + +/* Initializer for just above 3 (non-FIDDLE) InSource objects */ +#ifndef SQLITE_SHELL_FIDDLE +static void init_std_inputs(FILE *pIn ){ + termInSource.inFile = pIn; + stdInSource.inFile = pIn; + cmdInSource.lineno = 0; +} +#else +# define init_std_inputs(x) +#endif +/* Setup cmdInSource to supply given text as input. */ +static void set_invocation_cmd(char *zDo){ + cmdInSource.iReadOffset = 0; + cmdInSource.zStrIn = zDo; + ++cmdInSource.lineno; +} +#ifdef SQLITE_SHELL_FIDDLE +static void set_fiddle_input_text(char *zDo){ + fiddleInSource.iReadOffset = 0; + fiddleInSource.zStrIn = zDo; + if( zDo ) ++fiddleInSource.lineno; +} +#endif /* defined(SQLITE_SHELL_FIDDLE) */ + +/* Close an InSource object and unlink it from redirect nesting. */ +static void finish_InSource( InSource **ppIS ){ + if( ppIS!=0 && *ppIS!=0 ){ + InSource *pIS = *ppIS; + if( pIS->closer.stream != 0 ){ + if( pIS->inFile != 0 ){ + (pIS->closer.stream)(pIS->inFile); + pIS->inFile = 0; + }else if( pIS->zStrIn != 0 ){ + (pIS->closer.ptext)(pIS->zStrIn); + pIS->zStrIn = 0; + } + } + *ppIS = pIS->pFrom; + pIS->pFrom = 0; + } +} + +/* Similar to fgets(buffer, limit, file) but for InSource holding a string. */ +static char *strLineGet(char *zBuf, int ncMax, InSource *pInSrc){ + if( pInSrc->inFile!=0 ){ + char *zRet = fgets(zBuf, ncMax, pInSrc->inFile ); + if( zRet!=0 ){ + int iRead = strlen30(zRet); + if( iRead>0 && zRet[iRead-1]=='\n' ) ++pInSrc->lineno; + /* Consider: record line length to avoid rescan for it. */ + return zRet; + } + } + else if( pInSrc->zStrIn!=0 ){ + char *zBegin = pInSrc->zStrIn + pInSrc->iReadOffset; + if( *zBegin!=0 ){ + int iTake = 0; + char c; + ncMax -= 1; + while( iTakelineno; + break; + } + } + if( ncMax>=0 ) zBuf[iTake] = 0; + pInSrc->iReadOffset += iTake; + /* Consider: record line length to avoid rescan for it. */ + return zBuf; + } + } + return 0; +} + +/* +** This routine reads a line of text from designated stream source, +** stores the text in memory obtained from malloc() and returns a +** pointer to the text. NULL is returned at end of file. +** There will be no return if malloc() fails. +** +** The trailing newline (or other line-end chars) are stripped. ** ** If zLine is not NULL then it is a malloced buffer returned from ** a previous call to this routine that may be reused. @@@ -1275,73 -973,55 +1293,76 @@@ static char *local_getline(char *zLine } /* -** Retrieve a single line of input text. +** Retrieve a single line of input text from designated input source. ** -** If in==0 then read from standard input and prompt before each line. -** If isContinuation is true, then a continuation prompt is appropriate. -** If isContinuation is zero, then the main prompt should be used. +** If the input is interactive, then issue either the continuation prompt +** or the main prompt, per isContinuation, before reading a line from +** standard input. Otherwise, just read a line from the specified source. ** ** If zPrior is not NULL then it is a buffer from a prior call to this --** routine that can be reused. ++** routine that can be reused. If not used, it must be passed to free(). ** ** The result is stored in space obtained from malloc() and must either -** be freed by the caller or else passed back into this routine via the -** zPrior argument for reuse. +** be freed by the caller, using the same allocator[a], or else passed +** back into this routine via the zPrior argument for reuse. +** +** [a. This function is exposed for use by shell extensions which may +** have no access to "the same allocator". This is why the function +** following this one exists, also exposed to shell extensions. ] +** +** If this function is called until it returns NULL, and the prior return +** has been passed in for resuse, then the caller need/must not free it. +** Otherwise, (in case of an early termination of reading from the given +** input), the caller is responsible for freeing a prior, non-NULL return. +** +** The trailing newline (or its ilk), if any, is trimmed. +** The input line number is adjusted (via delegation or directly.) */ -#ifndef SQLITE_SHELL_FIDDLE -static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ - char *zPrompt; - char *zResult; - if( in!=0 ){ - zResult = local_getline(zPrior, in); +static char *one_input_line(InSource *pInSrc, char *zPrior, + int isContinuation, Prompts *pCue){ + if( !INSOURCE_IS_INTERACTIVE(pInSrc) ){ + return local_getline(zPrior, pInSrc); }else{ - zPrompt = isContinuation ? CONTINUATION_PROMPT : mainPrompt; + static Prompts cueDefault = { "$ ","> " }; + const char *zPrompt; + char *zResult; + if( pCue==0 ) pCue = &cueDefault; + zPrompt = isContinuation ? pCue->zContinue : pCue->zMain; #if SHELL_USE_LOCAL_GETLINE printf("%s", zPrompt); fflush(stdout); do{ - zResult = local_getline(zPrior, stdin); + zResult = local_getline(zPrior, pInSrc); zPrior = 0; /* ^C trap creates a false EOF, so let "interrupt" thread catch up. */ -- if( zResult==0 ) sqlite3_sleep(50); ++ if( zResult==0 ){ ++ sqlite3_sleep(150); ++ } }while( zResult==0 && seenInterrupt>0 ); #else free(zPrior); -- zResult = shell_readline(zPrompt); -- while( zResult==0 ){ ++ while( (zResult = shell_readline(zPrompt))==0 ){ /* ^C trap creates a false EOF, so let "interrupt" thread catch up. */ -- sqlite3_sleep(50); ++ sqlite3_sleep(150); if( seenInterrupt==0 ) break; -- zResult = shell_readline(""); ++ zPrompt = "\n"; ++ } ++ if( zResult ){ ++ if( *zResult ) shell_add_history(zResult); ++ ++pInSrc->lineno; } -- if( zResult && *zResult ) shell_add_history(zResult); - ++pInSrc->lineno; #endif + return zResult; } - return zResult; } -#endif /* !SQLITE_SHELL_FIDDLE */ + +/* For use by shell extensions. See footnote [a] to one_input_line(). */ +void free_input_line(char *z){ + free(z); +} /* -** Return the value of a hexadecimal digit. Return -1 if the input -** is not a hex digit. +** Return the value of a hexadecimal digit, or -1 if c is not a hex digit. */ static int hexDigitValue(char c){ if( c>='0' && c<='9' ) return c - '0'; @@@ -2775,35 -2116,17 +2796,45 @@@ static void output_csv(ShellExState *ps } } if( bSep ){ - utf8_printf(p->out, "%s", p->colSeparator); + utf8_printf(out, "%s", zColSep); + } +} + ++/* ++** Interrupt running VDBE (if any). The mutex on globalDb is taken to ensure ++** the active DB is not closed as this interrupt is done. Return SQLITE_OK ++** if able to do so (or no DB is active), else SQLITE_ERROR. ++*/ ++static int active_db_interrupt(void){ ++ int itry = 0; ++ if( !globalDb ) return SQLITE_OK; ++ while( itry < 10 ){ ++ if( sqlite3_mutex_try(pGlobalDbLock)==SQLITE_OK ){ ++ if( globalDb ) sqlite3_interrupt(GLOBAL_DB); ++ sqlite3_mutex_leave(pGlobalDbLock); ++ return SQLITE_OK; ++ }else{ ++ sqlite3_sleep(50); ++ ++itry; ++ } + } ++ return SQLITE_ERROR; + } + /* ** This routine runs when the user presses Ctrl-C - ** TBD: It will need some changes for embedability. ++** This may need some changes for embedability, perhaps a client- ++** defined action when/if DB operation becomes uninterruptable. */ static void interrupt_handler(int NotUsed){ UNUSED_PARAMETER(NotUsed); - if( ++seenInterrupt>1 ){ - exit(1); - } - if( globalDb ){ - int itry = 0; - while( itry < 10 ){ - if( sqlite3_mutex_try(pGlobalDbLock)==SQLITE_OK ){ - sqlite3_interrupt(globalDb); - sqlite3_mutex_leave(pGlobalDbLock); - return; - }else{ - sqlite3_sleep(50); - ++itry; - } - } - if( ++seenInterrupt>1 ) exit(1); - if( globalDb ) sqlite3_interrupt(globalDb); ++ if( ++seenInterrupt>1 || active_db_interrupt()!=SQLITE_OK ){ + /* Apparently, no polite interruption is working. (Something is - ** very busy under mutex protection.) So quit altother. */ ++ ** very busy under mutex protection.) So quit altogether. */ ++ utf8_printf(STD_ERR, "Forced ^C exit.\n"); ++ terminate_actions(); + exit(1); + } } #if (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE) @@@ -5618,2049 -4684,990 +5649,2050 @@@ static int run_schema_dump_query return rc; } +/* Configure help text generation to have coalesced secondary help lines + * with trailing newlines on all help lines. This allow help text to be + * representable as an array of two C-strings per dot-command. + */ +DISPATCH_CONFIG[ + HELP_COALESCE=1 +]; +#define HELP_TEXT_FMTP ".%s" +#define HELP_TEXT_FMTS "%s" +/* Above HELP_COALESCE config and HELP_TEXT_FMT PP vars must track. + * Alternative is 0, ".%s\n" and "%s\n" . + */ + +/* Forward references */ +static int showHelp(FILE *out, const char *zPattern, ShellExState *); +static DotCmdRC process_input(ShellInState *psx); +static DotCommand *builtInCommand(int ix); + /* -** Text of help messages. +** Read the content of file zName into memory obtained from sqlite3_malloc64() +** and return a pointer to the buffer. The caller is responsible for freeing +** the memory. +** +** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes +** read. +** +** For convenience, a nul-terminator byte is always appended to the data read +** from the file before the buffer is returned. This byte is not included in +** the final value of (*pnByte), if applicable. ** -** The help text for each individual command begins with a line that starts -** with ".". Subsequent lines are supplemental information. +** NULL is returned if any error is encountered. The final value of *pnByte +** is undefined in this case. ** -** There must be two or more spaces between the end of the command and the -** start of the description of what that command does. +** This function always returns; no abrubt OOM exits are taken. */ -static const char *(azHelp[]) = { -#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) \ - && !defined(SQLITE_SHELL_FIDDLE) - ".archive ... Manage SQL archives", - " Each command must have exactly one of the following options:", - " -c, --create Create a new archive", - " -u, --update Add or update files with changed mtime", - " -i, --insert Like -u but always add even if unchanged", - " -r, --remove Remove files from archive", - " -t, --list List contents of archive", - " -x, --extract Extract files from archive", - " Optional arguments:", - " -v, --verbose Print each filename as it is processed", - " -f FILE, --file FILE Use archive FILE (default is current db)", - " -a FILE, --append FILE Open FILE using the apndvfs VFS", - " -C DIR, --directory DIR Read/extract files from directory DIR", - " -g, --glob Use glob matching for names in archive", - " -n, --dryrun Show the SQL that would have occurred", - " Examples:", - " .ar -cf ARCHIVE foo bar # Create ARCHIVE from files foo and bar", - " .ar -tf ARCHIVE # List members of ARCHIVE", - " .ar -xvf ARCHIVE # Verbosely extract files from ARCHIVE", - " See also:", - " http://sqlite.org/cli.html#sqlite_archive_support", -#endif -#ifndef SQLITE_OMIT_AUTHORIZATION - ".auth ON|OFF Show authorizer callbacks", -#endif -#ifndef SQLITE_SHELL_FIDDLE - ".backup ?DB? FILE Backup DB (default \"main\") to FILE", - " Options:", - " --append Use the appendvfs", - " --async Write to FILE without journal and fsync()", -#endif - ".bail on|off Stop after hitting an error. Default OFF", - ".binary on|off Turn binary output on or off. Default OFF", -#ifndef SQLITE_SHELL_FIDDLE - ".cd DIRECTORY Change the working directory to DIRECTORY", -#endif - ".changes on|off Show number of rows changed by SQL", -#ifndef SQLITE_SHELL_FIDDLE - ".check GLOB Fail if output since .testcase does not match", - ".clone NEWDB Clone data into NEWDB from the existing database", -#endif - ".connection [close] [#] Open or close an auxiliary database connection", - ".databases List names and files of attached databases", - ".dbconfig ?op? ?val? List or change sqlite3_db_config() options", -#if SQLITE_SHELL_HAVE_RECOVER - ".dbinfo ?DB? Show status information about the database", -#endif - ".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", -#ifndef SQLITE_SHELL_FIDDLE - ".excel Display the output of next command in spreadsheet", - " --bom Put a UTF8 byte-order mark on intermediate file", -#endif -#ifndef SQLITE_SHELL_FIDDLE - ".exit ?CODE? Exit this program with return-code CODE", -#endif - ".expert EXPERIMENTAL. Suggest indexes for queries", - ".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto", - ".filectrl CMD ... Run various sqlite3_file_control() operations", - " --schema SCHEMA Use SCHEMA instead of \"main\"", - " --help Show CMD details", - ".fullschema ?--indent? Show schema and the content of sqlite_stat tables", - ".headers on|off Turn display of headers on or off", - ".help ?-all? ?PATTERN? Show help text for PATTERN", -#ifndef SQLITE_SHELL_FIDDLE - ".import FILE TABLE Import data from FILE into TABLE", - " Options:", - " --ascii Use \\037 and \\036 as column and row separators", - " --csv Use , and \\n as column and row separators", - " --skip N Skip the first N rows of input", - " --schema S Target table to be S.TABLE", - " -v \"Verbose\" - increase auxiliary output", - " Notes:", - " * If TABLE does not exist, it is created. The first row of input", - " determines the column names.", - " * If neither --csv or --ascii are used, the input mode is derived", - " from the \".mode\" output mode", - " * If FILE begins with \"|\" then it is a command that generates the", - " input text.", -#endif -#ifndef SQLITE_OMIT_TEST_CONTROL - ",imposter INDEX TABLE Create imposter table TABLE on index INDEX", -#endif - ".indexes ?TABLE? Show names of indexes", - " If TABLE is specified, only show indexes for", - " tables matching TABLE using the LIKE operator.", -#ifdef SQLITE_ENABLE_IOTRACE - ",iotrace FILE Enable I/O diagnostic logging to FILE", -#endif - ".limit ?LIMIT? ?VAL? Display or change the value of an SQLITE_LIMIT", - ".lint OPTIONS Report potential schema issues.", - " Options:", - " fkey-indexes Find missing foreign key indexes", -#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE) - ".load FILE ?ENTRY? Load an extension library", +static char *readFile(const char *zName, int *pnByte){ + FILE *in = fopen(zName, "rb"); + long nIn; + size_t nRead; + char *pBuf; + int rc; + if( in==0 ) return 0; + rc = fseek(in, 0, SEEK_END); + if( rc!=0 ){ + raw_printf(stderr, "Error: '%s' not seekable\n", zName); + fclose(in); + return 0; + } + nIn = ftell(in); + rewind(in); + pBuf = sqlite3_malloc64( nIn+1 ); + if( pBuf==0 ){ + raw_printf(stderr, "Error: out of memory\n"); + fclose(in); + return 0; + } + nRead = fread(pBuf, nIn, 1, in); + fclose(in); + if( nRead!=1 ){ + sqlite3_free(pBuf); + raw_printf(stderr, "Error: cannot read '%s'\n", zName); + return 0; + } + pBuf[nIn] = 0; + if( pnByte ) *pnByte = nIn; + return pBuf; +} + +#if defined(SQLITE_ENABLE_SESSION) +/* +** Close a single OpenSession object and release all of its associated +** resources. +*/ +static void session_close(OpenSession *pSession){ + int i; + sqlite3session_delete(pSession->p); + sqlite3_free(pSession->zName); + for(i=0; inFilter; i++){ + sqlite3_free(pSession->azFilter[i]); + } + sqlite3_free(pSession->azFilter); + memset(pSession, 0, sizeof(OpenSession)); +} #endif -#if !defined(SQLITE_SHELL_FIDDLE) - ".log FILE|on|off Turn logging on or off. FILE can be stderr/stdout", + +/* +** Close all OpenSession objects and release all associated resources. +*/ +#if defined(SQLITE_ENABLE_SESSION) +static void session_close_all(ShellInState *psi, int i){ + int j; + struct AuxDb *pAuxDb = i<0 ? psi->pAuxDb : &psi->aAuxDb[i]; + for(j=0; jnSession; j++){ + session_close(&pAuxDb->aSession[j]); + } + pAuxDb->nSession = 0; +} #else - ".log on|off Turn logging on or off.", -#endif - ".mode MODE ?OPTIONS? Set output mode", - " MODE is one of:", - " ascii Columns/rows delimited by 0x1F and 0x1E", - " box Tables using unicode box-drawing characters", - " csv Comma-separated values", - " column Output in columns. (See .width)", - " html HTML code", - " insert SQL insert statements for TABLE", - " json Results in a JSON array", - " line One value per line", - " list Values delimited by \"|\"", - " markdown Markdown table format", - " qbox Shorthand for \"box --wrap 60 --quote\"", - " quote Escape answers as for SQL", - " table ASCII-art table", - " tabs Tab-separated values", - " tcl TCL list elements", - " OPTIONS: (for columnar modes or insert mode):", - " --wrap N Wrap output lines to no longer than N characters", - " --wordwrap B Wrap or not at word boundaries per B (on/off)", - " --ww Shorthand for \"--wordwrap 1\"", - " --quote Quote output text as SQL literals", - " --noquote Do not quote output text", - " TABLE The name of SQL table used for \"insert\" mode", -#ifndef SQLITE_SHELL_FIDDLE - ".nonce STRING Suspend safe mode for one command if nonce matches", -#endif - ".nullvalue STRING Use STRING in place of NULL values", -#ifndef SQLITE_SHELL_FIDDLE - ".once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE", - " If FILE begins with '|' then open as a pipe", - " --bom Put a UTF8 byte-order mark at the beginning", - " -e Send output to the system text editor", - " -x Send output as CSV to a spreadsheet (same as \".excel\")", - /* Note that .open is (partially) available in WASM builds but is - ** currently only intended to be used by the fiddle tool, not - ** end users, so is "undocumented." */ - ".open ?OPTIONS? ?FILE? Close existing database and reopen FILE", - " Options:", - " --append Use appendvfs to append database to the end of FILE", -#endif -#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", -#ifndef SQLITE_SHELL_FIDDLE - ".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", -#endif - ".parameter CMD ... Manage SQL parameter bindings", - " clear Erase all bindings", - " init Initialize the TEMP table that holds bindings", - " list List the current parameter bindings", - " set PARAMETER VALUE Given SQL parameter PARAMETER a value of VALUE", - " PARAMETER should start with one of: $ : @ ?", - " unset PARAMETER Remove PARAMETER from the binding table", - ".print STRING... Print literal STRING", -#ifndef SQLITE_OMIT_PROGRESS_CALLBACK - ".progress N Invoke progress handler after every N opcodes", - " --limit N Interrupt after N progress callbacks", - " --once Do no more than one progress interrupt", - " --quiet|-q No output except at interrupts", - " --reset Reset the count for each input and interrupt", -#endif - ".prompt MAIN CONTINUE Replace the standard prompts", -#ifndef SQLITE_SHELL_FIDDLE - ".quit Stop interpreting input stream, exit if primary.", - ".read FILE Read input from FILE or command output", - " If FILE begins with \"|\", it is a command that generates the input.", -#endif -#if SQLITE_SHELL_HAVE_RECOVER - ".recover Recover as much data as possible from corrupt db.", - " --ignore-freelist Ignore pages that appear to be on db freelist", - " --lost-and-found TABLE Alternative name for the lost-and-found table", - " --no-rowids Do not attempt to recover rowid values", - " that are not also INTEGER PRIMARY KEYs", -#endif -#ifndef SQLITE_SHELL_FIDDLE - ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE", - ".save ?OPTIONS? FILE Write database to FILE (an alias for .backup ...)", +# define session_close_all(X,Y) #endif - ".scanstats on|off|est Turn sqlite3_stmt_scanstatus() metrics on or off", - ".schema ?PATTERN? Show the CREATE statements matching PATTERN", - " Options:", - " --indent Try to pretty-print the schema", - " --nosys Omit objects whose names start with \"sqlite_\"", - ",selftest ?OPTIONS? Run tests defined in the SELFTEST table", - " Options:", - " --init Create a new SELFTEST table", - " -v Verbose output", - ".separator COL ?ROW? Change the column and row separators", + +/* +** Implementation of the xFilter function for an open session. Omit +** any tables named by ".session filter" but let all other table through. +*/ #if defined(SQLITE_ENABLE_SESSION) - ".session ?NAME? CMD ... Create or control sessions", - " Subcommands:", - " attach TABLE Attach TABLE", - " changeset FILE Write a changeset into FILE", - " close Close one session", - " enable ?BOOLEAN? Set or query the enable bit", - " filter GLOB... Reject tables matching GLOBs", - " indirect ?BOOLEAN? Mark or query the indirect status", - " isempty Query whether the session is empty", - " list List currently open session names", - " open DB NAME Open a new session on DB", - " patchset FILE Write a patchset into FILE", - " If ?NAME? is omitted, the first defined session is used.", -#endif - ".sha3sum ... Compute a SHA3 hash of database content", - " Options:", - " --schema Also hash the sqlite_schema table", - " --sha3-224 Use the sha3-224 algorithm", - " --sha3-256 Use the sha3-256 algorithm (default)", - " --sha3-384 Use the sha3-384 algorithm", - " --sha3-512 Use the sha3-512 algorithm", - " Any other argument is a LIKE pattern for tables to hash", -#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) - ".shell CMD ARGS... Run CMD ARGS... in a system shell", -#endif - ".show Show the current values for various settings", - ".stats ?ARG? Show stats or turn stats on or off", - " off Turn off automatic stat display", - " on Turn on automatic stat display", - " stmt Show statement stats", - " vmstep Show the virtual machine step count only", -#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) - ".system CMD ARGS... Run CMD ARGS... in a system shell", +static int session_filter(void *pCtx, const char *zTab){ + OpenSession *pSession = (OpenSession*)pCtx; + int i; + for(i=0; inFilter; i++){ + if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0; + } + return 1; +} #endif - ".tables ?TABLE? List names of tables matching LIKE pattern TABLE", -#ifndef SQLITE_SHELL_FIDDLE - ",testcase NAME Begin redirecting output to 'testcase-out.txt'", + +#if SHELL_DYNAMIC_EXTENSION +static int notify_subscribers(ShellInState *psi, NoticeKind nk, void *pvs) { + int six = 0; + int rcFlags = 0; + ShellExState *psx = XSS(psi); + while( six < psi->numSubscriptions ){ + struct EventSubscription *pes = psi->pSubscriptions + six++; + rcFlags |= pes->eventHandler(pes->pvUserData, nk, pvs, psx); + } + return rcFlags; +} #endif - ",testctrl CMD ... Run various sqlite3_test_control() operations", - " Run \".testctrl\" with no arguments for details", - ".timeout MS Try opening locked tables for MS milliseconds", - ".timer on|off Turn SQL timer on or off", -#ifndef SQLITE_OMIT_TRACE - ".trace ?OPTIONS? Output each SQL statement as it is run", - " FILE Send output to FILE", - " stdout Send output to stdout", - " stderr Send output to stderr", - " off Disable tracing", - " --expanded Expand query parameters", + +/* +** Try to deduce the type of file for zName based on its content. Return +** one of the SHELL_OPEN_* constants. +** +** If the file does not exist or is empty but its name looks like a ZIP +** archive and the dfltZip flag is true, then assume it is a ZIP archive. +** Otherwise, assume an ordinary database regardless of the filename if +** the type cannot be determined from content. +*/ +u8 deduceDatabaseType(const char *zName, int dfltZip){ + FILE *f = fopen(zName, "rb"); + size_t n; + u8 rc = SHELL_OPEN_UNSPEC; + char zBuf[100]; + if( f==0 ){ + if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){ + return SHELL_OPEN_ZIPFILE; + }else{ + return SHELL_OPEN_NORMAL; + } + } + n = fread(zBuf, 16, 1, f); + if( n==1 && memcmp(zBuf, "SQLite format 3", 16)==0 ){ + fclose(f); + return SHELL_OPEN_NORMAL; + } + fseek(f, -25, SEEK_END); + n = fread(zBuf, 25, 1, f); + if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){ + rc = SHELL_OPEN_APPENDVFS; + }else{ + fseek(f, -22, SEEK_END); + n = fread(zBuf, 22, 1, f); + if( n==1 && zBuf[0]==0x50 && zBuf[1]==0x4b && zBuf[2]==0x05 + && zBuf[3]==0x06 ){ + rc = SHELL_OPEN_ZIPFILE; + }else if( n==0 && dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){ + rc = SHELL_OPEN_ZIPFILE; + } + } + fclose(f); + return rc; +} + +#ifndef SQLITE_OMIT_DESERIALIZE +/* +** Reconstruct an in-memory database using the output from the "dbtotxt" +** program. Read content from the file in p->aAuxDb[].zDbFilename. +** If p->aAuxDb[].zDbFilename is 0, then read from the present input. +*/ +static unsigned char *readHexDb(ShellInState *psi, int *pnData){ + ResourceMark mark = holder_mark(); + unsigned char *a = 0; + int n = 0; + int pgsz = 0; + int iOffset = 0; + int j, k, nlError; + int rc; + static const char *zEndMarker = "| end "; + const char *zDbFilename = psi->pAuxDb->zDbFilename; + /* Need next two objects only if redirecting input to get the hex. */ + InSource inRedir = INSOURCE_FILE_REDIR(0, zDbFilename, psi->pInSource); + AnyResourceHolder arh = { &psi->pInSource, (GenericFreer)finish_InSource }; + unsigned int x[16]; + char zLine[1000]; + if( zDbFilename ){ + inRedir.inFile = fopen(zDbFilename, "r"); + if( inRedir.inFile==0 ){ + utf8_printf(STD_ERR, "cannot open \"%s\" for reading\n", zDbFilename); + return 0; + } + psi->pInSource = &inRedir; + inRedir.closer.stream = fclose; + any_ref_holder(&arh); + }else{ + /* Will read hex DB lines inline from present input, without redirect. */ + if( INSOURCE_IS_INTERACTIVE(psi->pInSource) ){ + printf("Reading hex DB from \"%s\", until end-marker input like:\n%s\n", + psi->pInSource->zSourceSay, zEndMarker); + fflush(STD_OUT); + } + } + *pnData = 0; + if( strLineGet(zLine,sizeof(zLine), psi->pInSource)==0 ) goto readHexDb_error; + rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz); + if( rc!=2 ) goto readHexDb_error; + if( n<0 ) goto readHexDb_error; + if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ) goto readHexDb_error; + n = (n+pgsz-1)&~(pgsz-1); /* Round n up to the next multiple of pgsz */ + a = sqlite3_malloc( n ? n : 1 ); + shell_check_ooms(a); + smem_holder(a); + memset(a, 0, n); + if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){ + utf8_printf(STD_ERR, "invalid pagesize\n"); + goto readHexDb_error; + } + while( strLineGet(zLine,sizeof(zLine), psi->pInSource)!=0 ){ + rc = sscanf(zLine, "| page %d offset %d", &j, &k); + if( rc==2 ){ + iOffset = k; + continue; + } + if( cli_strncmp(zLine, zEndMarker, 6)==0 ){ + break; + } + rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + &j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7], + &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]); + if( rc==17 ){ + k = iOffset+j; + if( k+16<=n && k>=0 ){ + int ii; + for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff; + } + } + } + *pnData = n; /* Record success and size. */ + drop_holder(); + readHexDb_cleanup: + RESOURCE_FREE(mark); + return a; + + readHexDb_error: + nlError = psi->pInSource->lineno; + if( psi->pInSource!=&inRedir ){ + /* Since taking input inline, consume through its end marker. */ + while( strLineGet(zLine, sizeof(zLine), psi->pInSource)!=0 ){ + if(cli_strncmp(zLine, zEndMarker, 6)==0 ) break; + } + } + a = 0; + utf8_printf(STD_ERR,"Error on line %d within --hexdb input\n", nlError); + goto readHexDb_cleanup; +} +#endif /* SQLITE_OMIT_DESERIALIZE */ + +/* +** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X. +*/ +static void shellUSleepFunc( + sqlite3_context *context, + int argcUnused, + sqlite3_value **argv +){ + int sleep = sqlite3_value_int(argv[0]); + (void)argcUnused; + sqlite3_sleep(sleep/1000); + sqlite3_result_int(context, sleep); +} + ++/* ++** Attempt to close the database connection. Report errors. ++** The close is done under mutex protection for globalDb. ++*/ ++static void close_db(sqlite3 *db){ ++ int rc; ++ if( db==globalDb ){ ++ /* This should only block for the time needed to handle ^C interrupt. */ ++ sqlite3_mutex_enter(pGlobalDbLock); ++ globalDb = 0; ++ rc = sqlite3_close(db); ++ sqlite3_mutex_leave(pGlobalDbLock); ++ }else{ ++ rc = sqlite3_close(db); ++ } ++ if( rc ){ ++ utf8_printf(STD_ERR, "Error: sqlite3_close() returns %d: %s\n", ++ rc, sqlite3_errmsg(db)); ++ } ++} ++ +/* Flags for open_db(). ToDo: Conform comments to code or vice-versa. +** +** The default behavior of open_db() is to exit(1) if the database fails to +** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error +** but still returns without calling exit. +** +** The OPEN_DB_ZIPFILE flag causes open_db() to prefer to open files as a +** ZIP archive if the file does not exist or is empty and its name matches +** the *.zip pattern. +*/ +#define OPEN_DB_KEEPALIVE 0x001 /* Return after error if true */ +#define OPEN_DB_ZIPFILE 0x002 /* Open as ZIP if name matches *.zip */ + +/* +** Make sure the database is open. If it is not, then open it. If +** the database fails to open, print an error message and exit. +*/ +static void open_db(ShellExState *psx, int openFlags){ + ShellInState *psi = ISS(psx); + if( DBX(psx)==0 ){ + sqlite3 **pDb = &DBX(psx); + const char *zDbFilename = psi->pAuxDb->zDbFilename; + if( psi->openMode==SHELL_OPEN_UNSPEC ){ + if( zDbFilename==0 || zDbFilename[0]==0 ){ + psi->openMode = SHELL_OPEN_NORMAL; + }else{ + psi->openMode = deduceDatabaseType(zDbFilename, + (openFlags & OPEN_DB_ZIPFILE)!=0); + } + } + switch( psi->openMode ){ + case SHELL_OPEN_APPENDVFS: { + sqlite3_open_v2 + (zDbFilename, pDb, + SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|psi->openFlags, + "apndvfs"); + break; + } + case SHELL_OPEN_HEXDB: + case SHELL_OPEN_DESERIALIZE: { + sqlite3_open(0, pDb); + break; + } + case SHELL_OPEN_ZIPFILE: { + sqlite3_open(":memory:", pDb); + break; + } + case SHELL_OPEN_READONLY: { + sqlite3_open_v2(zDbFilename, pDb, + SQLITE_OPEN_READONLY|psi->openFlags, 0); + break; + } + case SHELL_OPEN_UNSPEC: + case SHELL_OPEN_NORMAL: { + sqlite3_open_v2(zDbFilename, pDb, + SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|psi->openFlags, 0); + break; + } + } + globalDb = DBX(psx); + if( DBX(psx)==0 || SQLITE_OK!=sqlite3_errcode(DBX(psx)) ){ + const char *zWhy = (DBX(psx)==0)? "(?)" : sqlite3_errmsg(DBX(psx)); + utf8_printf(STD_ERR,"Error: unable to open database \"%s\": %s\n", + zDbFilename, zWhy); - sqlite3_close(DBX(psx)); ++ close_db(DBX(psx)); + if( bail_on_error && !stdin_is_interactive ){ + shell_terminate("-bail used in batch mode."); + } + if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){ + shell_terminate("with OPEN_DB_KEEPALIVE."); + } + sqlite3_open(":memory:", &DBX(psx)); + if( DBX(psx)==0 || SQLITE_OK!=sqlite3_errcode(DBX(psx)) ){ + shell_terminate("Also: unable to open substitute in-memory database."); + }else{ + utf8_printf(stderr, + "Notice: using substitute in-memory database instead of \"%s\"\n", + zDbFilename); + } + } - sqlite3_db_config(globalDb, SQLITE_DBCONFIG_STMT_SCANSTATUS,(int)0,(int*)0); ++ sqlite3_db_config(GLOBAL_DB,SQLITE_DBCONFIG_STMT_SCANSTATUS,(int)0,(int*)0); + + /* Reflect the use or absence of --unsafe-testing invocation. */ + { + int testmode_on = ShellHasFlag(psx,SHFLG_TestingMode); - sqlite3_db_config(globalDb, SQLITE_DBCONFIG_TRUSTED_SCHEMA,testmode_on,0); - sqlite3_db_config(globalDb, SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0); ++ sqlite3_db_config(GLOBAL_DB,SQLITE_DBCONFIG_TRUSTED_SCHEMA,testmode_on,0); ++ sqlite3_db_config(GLOBAL_DB,SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0); + } +#ifndef SQLITE_OMIT_LOAD_EXTENSION - sqlite3_enable_load_extension(globalDb, 1); ++ sqlite3_enable_load_extension(GLOBAL_DB, 1); +#endif - sqlite3_shathree_init(globalDb, 0, 0); - sqlite3_uint_init(globalDb, 0, 0); - sqlite3_decimal_init(globalDb, 0, 0); - sqlite3_base64_init(globalDb, 0, 0); - sqlite3_base85_init(globalDb, 0, 0); - sqlite3_regexp_init(globalDb, 0, 0); - sqlite3_ieee_init(globalDb, 0, 0); - sqlite3_series_init(globalDb, 0, 0); ++ sqlite3_shathree_init(GLOBAL_DB, 0, 0); ++ sqlite3_uint_init(GLOBAL_DB, 0, 0); ++ sqlite3_decimal_init(GLOBAL_DB, 0, 0); ++ sqlite3_base64_init(GLOBAL_DB, 0, 0); ++ sqlite3_base85_init(GLOBAL_DB, 0, 0); ++ sqlite3_regexp_init(GLOBAL_DB, 0, 0); ++ sqlite3_ieee_init(GLOBAL_DB, 0, 0); ++ sqlite3_series_init(GLOBAL_DB, 0, 0); +#ifndef SQLITE_SHELL_FIDDLE - sqlite3_fileio_init(globalDb, 0, 0); - sqlite3_completion_init(globalDb, 0, 0); ++ sqlite3_fileio_init(GLOBAL_DB, 0, 0); ++ sqlite3_completion_init(GLOBAL_DB, 0, 0); +#endif +#ifdef SQLITE_HAVE_ZLIB + if( !psi->bSafeModeFuture ){ - sqlite3_zipfile_init(globalDb, 0, 0); - sqlite3_sqlar_init(globalDb, 0, 0); ++ sqlite3_zipfile_init(GLOBAL_DB, 0, 0); ++ sqlite3_sqlar_init(GLOBAL_DB, 0, 0); + } +#endif + +#ifdef SQLITE_SHELL_EXTFUNCS + /* Create a preprocessing mechanism for extensions to make + * their own provisions for being built into the shell. + * This is a short-span macro. See further below for usage. + */ +#define SHELL_SUB_MACRO(base, variant) base ## _ ## variant +#define SHELL_SUBMACRO(base, variant) SHELL_SUB_MACRO(base, variant) + /* Let custom-included extensions get their ..._init() called. + * The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause + * the extension's sqlite3_*_init( db, pzErrorMsg, pApi ) + * inititialization routine to be called. + */ + { + int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db); + /* Let custom-included extensions expose their functionality. + * The WHATEVER_EXPOSE( db, pzErrorMsg ) macro should cause + * the SQL functions, virtual tables, collating sequences or + * VFS's implemented by the extension to be registered. + */ + if( irc==SQLITE_OK + || irc==SQLITE_OK_LOAD_PERMANENTLY ){ + SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, EXPOSE)(p->db, 0); + } +#undef SHELL_SUB_MACRO +#undef SHELL_SUBMACRO + } +#endif + - sqlite3_create_function(globalDb, "shell_add_schema", 3, SQLITE_UTF8, 0, ++ sqlite3_create_function(GLOBAL_DB, "shell_add_schema", 3,SQLITE_UTF8,0, + shellAddSchemaName, 0, 0); - sqlite3_create_function(globalDb, "shell_module_schema", 1, SQLITE_UTF8, 0, ++ sqlite3_create_function(GLOBAL_DB, "shell_module_schema", 1,SQLITE_UTF8,0, + shellModuleSchema, 0, 0); - sqlite3_create_function(globalDb, "shell_putsnl", 1, SQLITE_UTF8, psx, ++ sqlite3_create_function(GLOBAL_DB, "shell_putsnl", 1,SQLITE_UTF8,psx, + shellPutsFunc, 0, 0); - sqlite3_create_function(globalDb, "usleep",1,SQLITE_UTF8, 0, ++ sqlite3_create_function(GLOBAL_DB, "usleep", 1,SQLITE_UTF8,0, + shellUSleepFunc, 0, 0); +#ifndef SQLITE_NOHAVE_SYSTEM - sqlite3_create_function(globalDb, "edit", 1, SQLITE_UTF8, 0, ++ sqlite3_create_function(GLOBAL_DB, "edit", 1, SQLITE_UTF8, 0, + editFunc, 0, 0); - sqlite3_create_function(globalDb, "edit", 2, SQLITE_UTF8, 0, ++ sqlite3_create_function(GLOBAL_DB, "edit", 2, SQLITE_UTF8, 0, + editFunc, 0, 0); +#endif + if( psi->openMode==SHELL_OPEN_ZIPFILE ){ + char *zSql = smprintf("CREATE VIRTUAL TABLE zip USING zipfile(%Q);", + zDbFilename); + shell_check_ooms(zSql); + sstr_holder(zSql); + s3_exec_noom(DBX(psx), zSql, 0, 0, 0); + release_holder(); + } +#ifndef SQLITE_OMIT_DESERIALIZE + else + if( psi->openMode==SHELL_OPEN_DESERIALIZE + || psi->openMode==SHELL_OPEN_HEXDB ){ + int rc; + int nData = 0; + unsigned char *aData; + if( psi->openMode==SHELL_OPEN_DESERIALIZE ){ + aData = (unsigned char*)readFile(zDbFilename, &nData); + }else{ + aData = readHexDb(psi, &nData); + if( aData==0 ){ + return; + } + } + rc = sqlite3_deserialize(DBX(psx), "main", aData, nData, nData, + SQLITE_DESERIALIZE_RESIZEABLE | + SQLITE_DESERIALIZE_FREEONCLOSE); + if( rc ){ + utf8_printf(STD_ERR, "Error: sqlite3_deserialize() returns %d\n", rc); + } + if( psi->szMax>0 ){ + sqlite3_file_control(DBX(psx), "main", SQLITE_FCNTL_SIZE_LIMIT, + &psi->szMax); + } + } +#endif + if( DBX(psx)!=0 ){ + if( psi->bSafeModeFuture ){ + sqlite3_set_authorizer(DBX(psx), safeModeAuth, psx); + } + sqlite3_db_config( + DBX(psx), SQLITE_DBCONFIG_STMT_SCANSTATUS, psi->scanstatsOn,(int*)0); + } +#if SHELL_DYNAMIC_EXTENSION + notify_subscribers(psi, NK_DbUserAppeared, DBX(psx)); +#endif + } +} + - /* - ** Attempt to close the database connection. Report errors. - */ - void close_db(sqlite3 *db){ - int rc; - if( db==globalDb ){ - /* This should only block for the time needed to handle ^C interrupt. */ - sqlite3_mutex_enter(pGlobalDbLock); - globalDb = 0; - rc = sqlite3_close(db); - sqlite3_mutex_leave(pGlobalDbLock); - }else{ - rc = sqlite3_close(db); - } - if( rc ){ - utf8_printf(STD_ERR, "Error: sqlite3_close() returns %d: %s\n", - rc, sqlite3_errmsg(db)); - } - } - +#if HAVE_READLINE || HAVE_EDITLINE +/* +** Readline completion callbacks +*/ +static char *readline_completion_generator(const char *text, int state){ + static sqlite3_stmt *pStmt = 0; + char *zRet; + if( state==0 ){ + char *zSql; + sqlite3_finalize(pStmt); + pStmt = 0; + zSql = smprintf("SELECT DISTINCT candidate COLLATE nocase" + " FROM completion(%Q) ORDER BY 1", text); - s3_prep_noom_free(globalDb, &zSql, &pStmt); ++ s3_prep_noom_free(GLOBAL_DB, &zSql, &pStmt); + } + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + const char *z = (const char*)sqlite3_column_text(pStmt,0); + if( z!=0 ){ + zRet = strdup(z); + shell_check_oomm(zRet); + } + }else{ + sqlite3_finalize(pStmt); + pStmt = 0; + zRet = 0; + } + return zRet; +} +static char **readline_completion(const char *zText, int iStart, int iEnd){ + (void)iStart; + (void)iEnd; + rl_attempted_completion_over = 1; + return rl_completion_matches(zText, readline_completion_generator); +} + +#elif HAVE_LINENOISE +/* +** Linenoise completion callback +*/ +static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){ + i64 nLine = strlen(zLine); + i64 i, iStart; + sqlite3_stmt *pStmt = 0; + char *zSql; + char zBuf[1000]; + + if( nLine>(i64)sizeof(zBuf)-30 ) return; + if( zLine[0]=='.' || zLine[0]=='#') return; + for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){} + if( i==nLine-1 ) return; + iStart = i+1; + memcpy(zBuf, zLine, iStart); + zSql = smprintf("SELECT DISTINCT candidate COLLATE nocase" + " FROM completion(%Q,%Q) ORDER BY 1", + &zLine[iStart], zLine); - s3_prep_noom_free(globalDb, &zSql, &pStmt); - sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */ ++ s3_prep_noom_free(GLOBAL_DB, &zSql, &pStmt); ++ sqlite3_exec(GLOBAL_DB, "PRAGMA page_count", 0, 0, 0); /* Load the schema */ + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0); + int nCompletion = sqlite3_column_bytes(pStmt, 0); + if( iStart+nCompletion < (i64)sizeof(zBuf)-1 && zCompletion ){ + memcpy(zBuf+iStart, zCompletion, nCompletion+1); + linenoiseAddCompletion(lc, zBuf); + } + } + sqlite3_finalize(pStmt); +} +#endif + +/* +** Do C-language style escape sequence translation. +** +** \a -> alarm +** \b -> backspace +** \t -> tab +** \n -> newline +** \v -> vertical tab +** \f -> form feed +** \r -> carriage return +** \s -> space +** \" -> " +** \' -> ' +** \\ -> backslash +** \NNN -> ascii character NNN in octal +** \xHH -> ascii character HH in hexadecimal +*/ +static void resolve_backslashes(char *z){ + int i, j; + char c; + while( *z && *z!='\\' ) z++; + for(i=j=0; (c = z[i])!=0; i++, j++){ + if( c=='\\' && z[i+1]!=0 ){ + c = z[++i]; + if( c=='a' ){ + c = '\a'; + }else if( c=='b' ){ + c = '\b'; + }else if( c=='t' ){ + c = '\t'; + }else if( c=='n' ){ + c = '\n'; + }else if( c=='v' ){ + c = '\v'; + }else if( c=='f' ){ + c = '\f'; + }else if( c=='r' ){ + c = '\r'; + }else if( c=='"' ){ + c = '"'; + }else if( c=='\'' ){ + c = '\''; + }else if( c=='\\' ){ + c = '\\'; + }else if( c=='x' ){ + int nhd = 0, hdv; + u8 hv = 0; + while( nhd<2 && (c=z[i+1+nhd])!=0 && (hdv=hexDigitValue(c))>=0 ){ + hv = (u8)((hv<<4)|hdv); + ++nhd; + } + i += nhd; + c = hv; + }else if( c>='0' && c<='7' ){ + c -= '0'; + if( z[i+1]>='0' && z[i+1]<='7' ){ + i++; + c = (c<<3) + z[i] - '0'; + if( z[i+1]>='0' && z[i+1]<='7' ){ + i++; + c = (c<<3) + z[i] - '0'; + } + } + } + } + z[j] = c; + } + if( j=0; i++){} + }else{ + for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){} + } + if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff); + for( i=0; zBoolNames[i]!=0; ++i ){ + if( sqlite3_stricmp(zArg, zBoolNames[i])==0 ) return i&1; + } + utf8_printf(STD_ERR, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n", + zArg); + return 0; +} + +/* +** Set or clear a shell flag according to a boolean value. +*/ +static void setOrClearFlag(ShellExState *psx, unsigned mFlag, const char *zArg){ + if( booleanValue(zArg) ){ + ShellSetFlag(psx, mFlag); + }else{ + ShellClearFlag(psx, mFlag); + } +} + +/* +** Close an output file, provided it is not stderr or stdout +*/ +static void output_file_close(FILE *f){ + if( f && f!=STD_OUT && f!=STD_ERR ) fclose(f); +} + +/* +** Try to open an output file. The names "stdout" and "stderr" are +** recognized and do the right thing. NULL is returned if the output +** filename is "off". +*/ +static FILE *output_file_open(const char *zFile, int bTextMode){ + FILE *f; + if( cli_strcmp(zFile,"stdout")==0 ){ + f = STD_OUT; + }else if( cli_strcmp(zFile, "stderr")==0 ){ + f = STD_ERR; + }else if( cli_strcmp(zFile, "off")==0 ){ + f = 0; + }else{ + f = fopen(zFile, bTextMode ? "w" : "wb"); + if( f==0 ){ + utf8_printf(STD_ERR, "Error: cannot open \"%s\"\n", zFile); + } + } + return f; +} + +#ifndef SQLITE_OMIT_TRACE +/* +** A routine for handling output from sqlite3_trace(). +*/ +static int sql_trace_callback( + unsigned mType, /* The trace type */ + void *pArg, /* The shell state pointer */ + void *pP, /* Usually a pointer to sqlite_stmt */ + void *pX /* Auxiliary output */ +){ + ShellInState *psi = (ShellInState*)pArg; + sqlite3_stmt *pStmt; + const char *zSql; + i64 nSql; + if( psi->traceOut==0 ) return 0; + if( mType==SQLITE_TRACE_CLOSE ){ + utf8_printf(psi->traceOut, "-- closing database connection\n"); + return 0; + } + if( mType!=SQLITE_TRACE_ROW && pX!=0 && ((const char*)pX)[0]=='-' ){ + zSql = (const char*)pX; + }else{ + pStmt = (sqlite3_stmt*)pP; + switch( psi->eTraceType ){ + case SHELL_TRACE_EXPANDED: { + zSql = sqlite3_expanded_sql(pStmt); + break; + } #ifdef SQLITE_ENABLE_NORMALIZE - " --normalized Normal the SQL statements", + case SHELL_TRACE_NORMALIZED: { + zSql = sqlite3_normalized_sql(pStmt); + break; + } #endif - " --plain Show SQL as it is input", - " --stmt Trace statement execution (SQLITE_TRACE_STMT)", - " --profile Profile statements (SQLITE_TRACE_PROFILE)", - " --row Trace each row (SQLITE_TRACE_ROW)", - " --close Trace connection close (SQLITE_TRACE_CLOSE)", -#endif /* SQLITE_OMIT_TRACE */ -#ifdef SQLITE_DEBUG - ".unmodule NAME ... Unregister virtual table modules", - " --allexcept Unregister everything except those named", + default: { + zSql = sqlite3_sql(pStmt); + break; + } + } + } + if( zSql==0 ) return 0; + nSql = strlen(zSql); + if( nSql>1000000000 ) nSql = 1000000000; /* clamp to 1 billion */ + while( nSql>0 && zSql[nSql-1]==';' ){ nSql--; } + switch( mType ){ + case SQLITE_TRACE_ROW: + case SQLITE_TRACE_STMT: { + utf8_printf(psi->traceOut, "%.*s;\n", (int)nSql, zSql); + break; + } + case SQLITE_TRACE_PROFILE: { + sqlite3_int64 nNanosec = pX ? *(sqlite3_int64*)pX : 0; + utf8_printf(psi->traceOut,"%.*s; -- %lld ns\n", (int)nSql,zSql,nNanosec); + break; + } + } + return 0; +} #endif - ".version Show source, library and compiler versions", - ".vfsinfo ?AUX? Information about the top-level VFS", - ".vfslist List all available VFSes", - ".vfsname ?AUX? Print the name of the VFS stack", - ".width NUM1 NUM2 ... Set minimum column widths for columnar output", - " Negative values right-justify", + +/* +** A no-op routine that runs with the ".breakpoint" dot-command. +** This is a useful spot to set a debugger breakpoint. +** +** This routine does not do anything practical. The code are there simply +** to prevent the compiler from optimizing this routine out. +*/ +static void test_breakpoint(void){ + static int nCall = 0; + if( (nCall++)==0xffffffff ) printf("Many .breakpoints have run\n"); +} + +/* +** An object used to read a CSV and other files for import. +*/ +typedef struct ImportCtx ImportCtx; +struct ImportCtx { + const char *zFile; /* Name of the input file */ + FILE *in; /* Read the CSV text from this input stream */ + int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close in */ + char *z; /* Accumulated text for a field */ + int n; /* Number of bytes in z */ + int nAlloc; /* Space allocated for z[] */ + int nLine; /* Current line number */ + int nRow; /* Number of rows imported */ + int nErr; /* Number of errors encountered */ + int bNotFirst; /* True if one or more bytes already read */ + int cTerm; /* Character that terminated the most recent field */ + int cColSep; /* The column separator character. (Usually ",") */ + int cRowSep; /* The row separator character. (Usually "\n") */ }; +/* Clean up resourced used by an ImportCtx */ +static void import_cleanup(ImportCtx *p){ + if( p->in!=0 && p->xCloser!=0 ){ + p->xCloser(p->in); + p->in = 0; + } + sqlite3_free(p->z); + p->z = 0; +} + +/* Append a single byte to z[] */ +static void import_append_char(ImportCtx *p, int c){ + if( p->n+1>=p->nAlloc ){ + p->nAlloc += p->nAlloc + 100; + p->z = sqlite3_realloc64(p->z, p->nAlloc); + shell_check_ooms(p->z); + } + p->z[p->n++] = (char)c; +} + +/* Read a single field of CSV text. Compatible with rfc4180 and extended +** with the option of having a separator other than ",". +** +** + Input comes from p->in. +** + Store results in p->z of length p->n. Space to hold p->z comes +** from sqlite3_malloc64(). +** + Use p->cSep as the column separator. The default is ",". +** + Use p->rSep as the row separator. The default is "\n". +** + Keep track of the line number in p->nLine. +** + Store the character that terminates the field in p->cTerm. Store +** EOF on end-of-file. +** + Report syntax errors on stderr +*/ +static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){ + int c; + int cSep = (u8)p->cColSep; + int rSep = (u8)p->cRowSep; + p->n = 0; + c = fgetc(p->in); + if( c==EOF || seenInterrupt ){ + p->cTerm = EOF; + return 0; + } + if( c=='"' ){ + int pc, ppc; + int startLine = p->nLine; + int cQuote = c; + pc = ppc = 0; + while( 1 ){ + c = fgetc(p->in); + if( c==rSep ) p->nLine++; + if( c==cQuote ){ + if( pc==cQuote ){ + pc = 0; + continue; + } + } + if( (c==cSep && pc==cQuote) + || (c==rSep && pc==cQuote) + || (c==rSep && pc=='\r' && ppc==cQuote) + || (c==EOF && pc==cQuote) + ){ + do{ p->n--; }while( p->z[p->n]!=cQuote ); + p->cTerm = c; + break; + } + if( pc==cQuote && c!='\r' ){ + utf8_printf(STD_ERR, "%s:%d: unescaped %c character\n", + p->zFile, p->nLine, cQuote); + } + if( c==EOF ){ + utf8_printf(STD_ERR, "%s:%d: unterminated %c-quoted field\n", + p->zFile, startLine, cQuote); + p->cTerm = c; + break; + } + import_append_char(p, c); + ppc = pc; + pc = c; + } + }else{ + /* If this is the first field being parsed and it begins with the + ** UTF-8 BOM (0xEF BB BF) then skip the BOM */ + if( (c&0xff)==0xef && p->bNotFirst==0 ){ + import_append_char(p, c); + c = fgetc(p->in); + if( (c&0xff)==0xbb ){ + import_append_char(p, c); + c = fgetc(p->in); + if( (c&0xff)==0xbf ){ + p->bNotFirst = 1; + p->n = 0; + return csv_read_one_field(p); + } + } + } + while( c!=EOF && c!=cSep && c!=rSep ){ + import_append_char(p, c); + c = fgetc(p->in); + } + if( c==rSep ){ + p->nLine++; + if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--; + } + p->cTerm = c; + } + if( p->z ) p->z[p->n] = 0; + p->bNotFirst = 1; + return p->z; +} + +/* Read a single field of ASCII delimited text. +** +** + Input comes from p->in. +** + Store results in p->z of length p->n. Space to hold p->z comes +** from sqlite3_malloc64(). +** + Use p->cSep as the column separator. The default is "\x1F". +** + Use p->rSep as the row separator. The default is "\x1E". +** + Keep track of the row number in p->nLine. +** + Store the character that terminates the field in p->cTerm. Store +** EOF on end-of-file. +** + Report syntax errors on stderr +*/ +static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p){ + int c; + int cSep = (u8)p->cColSep; + int rSep = (u8)p->cRowSep; + p->n = 0; + c = fgetc(p->in); + if( c==EOF || seenInterrupt ){ + p->cTerm = EOF; + return 0; + } + while( c!=EOF && c!=cSep && c!=rSep ){ + import_append_char(p, c); + c = fgetc(p->in); + } + if( c==rSep ){ + p->nLine++; + } + p->cTerm = c; + if( p->z ) p->z[p->n] = 0; + return p->z; +} + +/* +** Try to transfer data for table zTable. If an error is seen while +** moving forward, try to go backwards. The backwards movement won't +** work for WITHOUT ROWID tables. +*/ +static void tryToCloneData( + ShellExState *psx, + sqlite3 *newDb, + const char *zTable +){ + sqlite3_stmt *pQuery = 0; + sqlite3_stmt *pInsert = 0; + char *zQuery = 0; + char *zInsert = 0; + int rc; + int i, j, n; + int nTable = strlen30(zTable); + int k = 0; + int cnt = 0; + const int spinRate = 10000; + + shell_check_ooms(zQuery = smprintf("SELECT * FROM \"%w\"", zTable)); + rc = s3_prepare_v2_noom(DBX(psx), zQuery, -1, &pQuery, 0); + if( rc ){ + utf8_printf(STD_ERR, "Error %d: %s on [%s]\n", + sqlite3_extended_errcode(DBX(psx)), sqlite3_errmsg(DBX(psx)), + zQuery); + goto end_data_xfer; + } + n = sqlite3_column_count(pQuery); + zInsert = sqlite3_malloc64(200 + nTable + n*3); + shell_check_ooms(zInsert); + sqlite3_snprintf(200+nTable,zInsert, + "INSERT OR IGNORE INTO \"%s\" VALUES(?", zTable); + i = strlen30(zInsert); + for(j=1; j summary of documented commands only - ** *zPattern=='0' => whole help for undocumented commands - ** Otherwise => whole help for documented commands - */ - enum HelpWanted hw = HW_SummaryOnly; - enum HelpHave hh = HH_More; - if( zPattern!=0 ){ - hw = (*zPattern=='0')? HW_NoCull|HW_Undoc : HW_NoCull; - } - for(i=0; idoXdgOpen flag is set, that means the output was being +** redirected to a temporary file named by psi->zTempFile. In that case, +** launch start/open/xdg-open on that temporary file. +*/ +static void output_reset(ShellInState *psi){ + if( psi->outfile[0]=='|' ){ +#ifndef SQLITE_OMIT_POPEN + pclose(psi->out); +#endif + }else{ + output_file_close(psi->out); +#ifndef SQLITE_NOHAVE_SYSTEM + if( psi->doXdgOpen ){ + const char *zXdgOpenCmd = +#if defined(_WIN32) + "start"; +#elif defined(__APPLE__) + "open"; +#else + "xdg-open"; +#endif + char *zCmd; + zCmd = smprintf("%s %s", zXdgOpenCmd, psi->zTempFile); + if( system(zCmd) ){ + utf8_printf(STD_ERR, "Failed: [%s]\n", zCmd); + }else{ + /* Give the start/open/xdg-open command some time to get + ** going before we continue, and potential delete the + ** psi->zTempFile data file out from under it */ + sqlite3_sleep(2000); + } + sqlite3_free(zCmd); + outputModePop(psi); + psi->doXdgOpen = 0; + } +#endif /* !defined(SQLITE_NOHAVE_SYSTEM) */ + } + psi->outfile[0] = 0; + psi->out = STD_OUT; +} + +/* +** Run an SQL command and return the single integer result. +** No parameter binding is done. +*/ +static int db_int(sqlite3 *db, const char *zSql){ + sqlite3_stmt *pStmt; + int res = 0; + s3_prepare_v2_noom(db, zSql, -1, &pStmt, 0); + stmt_holder(pStmt); + if( pStmt && s3_step_noom(pStmt)==SQLITE_ROW ){ + res = sqlite3_column_int(pStmt,0); + } + release_holder(); + return res; +} + +/* +** Run an SQL command and return the single text result, +** Parameter binding is done iff bBind is true. +** The return must be freed by caller using sqlite3_free(). +*/ +static char *db_text(sqlite3 *db, const char *zSql, int bBind){ + sqlite3_stmt *pStmt; + char *zRes = 0; + if( s3_prepare_v2_noom(db, zSql, -1, &pStmt, 0)==SQLITE_OK && pStmt!=0 ){ + stmt_holder(pStmt); + if( bBind ) bind_prepared_stmt(db, pStmt); + if( s3_step_noom(pStmt)==SQLITE_ROW ){ + shell_check_ooms(zRes = smprintf("%s", sqlite3_column_text(pStmt,0))); + } + release_holder(); + } + return zRes; +} + +#if SQLITE_SHELL_HAVE_RECOVER +/* +** Convert a 2-byte or 4-byte big-endian integer into a native integer +*/ +static unsigned int get2byteInt(unsigned char *a){ + return (a[0]<<8) + a[1]; +} +static unsigned int get4byteInt(unsigned char *a){ + return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3]; +} + +/* +** Implementation of the ".dbinfo" command. ** -** NULL is returned if any error is encountered. The final value of *pnByte -** is undefined in this case. +** Return 1 on error, 2 to exit, and 0 otherwise. */ -static char *readFile(const char *zName, int *pnByte){ - FILE *in = fopen(zName, "rb"); - long nIn; - size_t nRead; - char *pBuf; - int rc; - if( in==0 ) return 0; - rc = fseek(in, 0, SEEK_END); - if( rc!=0 ){ - raw_printf(stderr, "Error: '%s' not seekable\n", zName); - fclose(in); - return 0; +static int shell_dbinfo_command(ShellExState *psx, int nArg, char **azArg){ + static const struct { const char *zName; int ofst; } aField[] = { + { "file change counter:", 24 }, + { "database page count:", 28 }, + { "freelist page count:", 36 }, + { "schema cookie:", 40 }, + { "schema format:", 44 }, + { "default cache size:", 48 }, + { "autovacuum top root:", 52 }, + { "incremental vacuum:", 64 }, + { "text encoding:", 56 }, + { "user version:", 60 }, + { "application id:", 68 }, + { "software version:", 96 }, + }; + static const struct { const char *zName; const char *zSql; } aQuery[] = { + { "number of tables:", + "SELECT count(*) FROM %s WHERE type='table'" }, + { "number of indexes:", + "SELECT count(*) FROM %s WHERE type='index'" }, + { "number of triggers:", + "SELECT count(*) FROM %s WHERE type='trigger'" }, + { "number of views:", + "SELECT count(*) FROM %s WHERE type='view'" }, + { "schema size:", + "SELECT total(length(sql)) FROM %s" }, + }; + int i, rc; + unsigned iDataVersion; + char *zSchemaTab; + char *zDb = nArg>=2 ? azArg[1] : "main"; + sqlite3_stmt *pStmt = 0; + unsigned char aHdr[100]; + FILE *out = ISS(psx)->out; + + open_db(psx, 0); + if( DBX(psx)==0 ) return 1; + rc = s3_prepare_v2_noom(DBX(psx), + "SELECT data FROM sqlite_dbpage(?1) WHERE pgno=1", + -1, &pStmt, 0); + if( rc ){ + utf8_printf(STD_ERR, "error: %s\n", sqlite3_errmsg(DBX(psx))); + sqlite3_finalize(pStmt); + return 1; } - nIn = ftell(in); - rewind(in); - pBuf = sqlite3_malloc64( nIn+1 ); - if( pBuf==0 ){ - raw_printf(stderr, "Error: out of memory\n"); - fclose(in); - return 0; + sqlite3_bind_text(pStmt, 1, zDb, -1, SQLITE_STATIC); + if( sqlite3_step(pStmt)==SQLITE_ROW + && sqlite3_column_bytes(pStmt,0)>100 + ){ + const u8 *pb = sqlite3_column_blob(pStmt,0); + shell_check_ooms(pb); + memcpy(aHdr, pb, 100); + sqlite3_finalize(pStmt); + }else{ + raw_printf(STD_ERR, "unable to read database header\n"); + sqlite3_finalize(pStmt); + return 1; } - nRead = fread(pBuf, nIn, 1, in); - fclose(in); - if( nRead!=1 ){ - sqlite3_free(pBuf); - raw_printf(stderr, "Error: cannot read '%s'\n", zName); - return 0; + i = get2byteInt(aHdr+16); + if( i==1 ) i = 65536; + utf8_printf(out, "%-20s %d\n", "database page size:", i); + utf8_printf(out, "%-20s %d\n", "write format:", aHdr[18]); + utf8_printf(out, "%-20s %d\n", "read format:", aHdr[19]); + utf8_printf(out, "%-20s %d\n", "reserved bytes:", aHdr[20]); + for(i=0; ip); - sqlite3_free(pSession->zName); - for(i=0; inFilter; i++){ - sqlite3_free(pSession->azFilter[i]); + if( zDb==0 ){ + zSchemaTab = smprintf("main.sqlite_schema"); + }else if( cli_strcmp(zDb,"temp")==0 ){ + zSchemaTab = smprintf("%s", "sqlite_temp_schema"); + }else{ + zSchemaTab = smprintf("\"%w\".sqlite_schema", zDb); } - sqlite3_free(pSession->azFilter); - memset(pSession, 0, sizeof(OpenSession)); -} -#endif - -/* -** Close all OpenSession objects and release all associated resources. -*/ -#if defined(SQLITE_ENABLE_SESSION) -static void session_close_all(ShellState *p, int i){ - int j; - struct AuxDb *pAuxDb = i<0 ? p->pAuxDb : &p->aAuxDb[i]; - for(j=0; jnSession; j++){ - session_close(&pAuxDb->aSession[j]); + for(i=0; inSession = 0; + sqlite3_free(zSchemaTab); + sqlite3_file_control(DBX(psx), zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion); + utf8_printf(out, "%-20s %u\n", "data version", iDataVersion); + return 0; } -#else -# define session_close_all(X,Y) -#endif +#endif /* SQLITE_SHELL_HAVE_RECOVER */ /* -** Implementation of the xFilter function for an open session. Omit -** any tables named by ".session filter" but let all other table through. +** Print the current sqlite3_errmsg() value to stderr and return 1. */ -#if defined(SQLITE_ENABLE_SESSION) -static int session_filter(void *pCtx, const char *zTab){ - OpenSession *pSession = (OpenSession*)pCtx; - int i; - for(i=0; inFilter; i++){ - if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0; - } +static int shellDatabaseError(sqlite3 *db){ + const char *zErr = sqlite3_errmsg(db); + utf8_printf(STD_ERR, "Error: %s\n", zErr); return 1; } -#endif /* -** Try to deduce the type of file for zName based on its content. Return -** one of the SHELL_OPEN_* constants. +** Compare the pattern in zGlob[] against the text in z[]. Return TRUE +** if they match and FALSE (0) if they do not match. ** -** If the file does not exist or is empty but its name looks like a ZIP -** archive and the dfltZip flag is true, then assume it is a ZIP archive. -** Otherwise, assume an ordinary database regardless of the filename if -** the type cannot be determined from content. +** Globbing rules: +** +** '*' Matches any sequence of zero or more characters. +** +** '?' Matches exactly one character. +** +** [...] Matches one character from the enclosed list of +** characters. +** +** [^...] Matches one character not in the enclosed list. +** +** '#' Matches any sequence of one or more digits with an +** optional + or - sign in front +** +** ' ' Any span of whitespace matches any other span of +** whitespace. +** +** Extra whitespace at the end of z[] is ignored. */ -int deduceDatabaseType(const char *zName, int dfltZip){ - FILE *f = fopen(zName, "rb"); - size_t n; - int rc = SHELL_OPEN_UNSPEC; - char zBuf[100]; - if( f==0 ){ - if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){ - return SHELL_OPEN_ZIPFILE; +static int testcase_glob(const char *zGlob, const char *z){ + int c, c2; + int invert; + int seen; + + while( (c = (*(zGlob++)))!=0 ){ + if( IsSpace(c) ){ + if( !IsSpace(*z) ) return 0; + zGlob = skipWhite(zGlob); + z = skipWhite(z); + }else if( c=='*' ){ + while( (c=(*(zGlob++))) == '*' || c=='?' ){ + if( c=='?' && (*(z++))==0 ) return 0; + } + if( c==0 ){ + return 1; + }else if( c=='[' ){ + while( *z && testcase_glob(zGlob-1,z)==0 ){ + z++; + } + return (*z)!=0; + } + while( (c2 = (*(z++)))!=0 ){ + while( c2!=c ){ + c2 = *(z++); + if( c2==0 ) return 0; + } + if( testcase_glob(zGlob,z) ) return 1; + } + return 0; + }else if( c=='?' ){ + if( (*(z++))==0 ) return 0; + }else if( c=='[' ){ + int prior_c = 0; + seen = 0; + invert = 0; + c = *(z++); + if( c==0 ) return 0; + c2 = *(zGlob++); + if( c2=='^' ){ + invert = 1; + c2 = *(zGlob++); + } + if( c2==']' ){ + if( c==']' ) seen = 1; + c2 = *(zGlob++); + } + while( c2 && c2!=']' ){ + if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){ + c2 = *(zGlob++); + if( c>=prior_c && c<=c2 ) seen = 1; + prior_c = 0; + }else{ + if( c==c2 ){ + seen = 1; + } + prior_c = c2; + } + c2 = *(zGlob++); + } + if( c2==0 || (seen ^ invert)==0 ) return 0; + }else if( c=='#' ){ + if( (z[0]=='-' || z[0]=='+') && IsDigit(z[1]) ) z++; + if( !IsDigit(z[0]) ) return 0; + z++; + while( IsDigit(z[0]) ){ z++; } }else{ - return SHELL_OPEN_NORMAL; - } - } - n = fread(zBuf, 16, 1, f); - if( n==1 && memcmp(zBuf, "SQLite format 3", 16)==0 ){ - fclose(f); - return SHELL_OPEN_NORMAL; - } - fseek(f, -25, SEEK_END); - n = fread(zBuf, 25, 1, f); - if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){ - rc = SHELL_OPEN_APPENDVFS; - }else{ - fseek(f, -22, SEEK_END); - n = fread(zBuf, 22, 1, f); - if( n==1 && zBuf[0]==0x50 && zBuf[1]==0x4b && zBuf[2]==0x05 - && zBuf[3]==0x06 ){ - rc = SHELL_OPEN_ZIPFILE; - }else if( n==0 && dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){ - rc = SHELL_OPEN_ZIPFILE; + if( c!=(*(z++)) ) return 0; } } - fclose(f); - return rc; + return *skipWhite(z)==0; } -#ifndef SQLITE_OMIT_DESERIALIZE /* -** Reconstruct an in-memory database using the output from the "dbtotxt" -** program. Read content from the file in p->aAuxDb[].zDbFilename. -** If p->aAuxDb[].zDbFilename is 0, then read from standard input. +** Compare the string as a command-line option with either one or two +** initial "-" characters. */ -static unsigned char *readHexDb(ShellState *p, int *pnData){ - unsigned char *a = 0; - int nLine; - int n = 0; - int pgsz = 0; - int iOffset = 0; - int j, k; - int rc; - FILE *in; - const char *zDbFilename = p->pAuxDb->zDbFilename; - unsigned int x[16]; - char zLine[1000]; - if( zDbFilename ){ - in = fopen(zDbFilename, "r"); - if( in==0 ){ - utf8_printf(stderr, "cannot open \"%s\" for reading\n", zDbFilename); - return 0; - } - nLine = 0; - }else{ - in = p->in; - nLine = p->lineno; - if( in==0 ) in = stdin; - } - *pnData = 0; - nLine++; - if( fgets(zLine, sizeof(zLine), in)==0 ) goto readHexDb_error; - rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz); - if( rc!=2 ) goto readHexDb_error; - if( n<0 ) goto readHexDb_error; - if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ) goto readHexDb_error; - n = (n+pgsz-1)&~(pgsz-1); /* Round n up to the next multiple of pgsz */ - a = sqlite3_malloc( n ? n : 1 ); - shell_check_oom(a); - memset(a, 0, n); - if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){ - utf8_printf(stderr, "invalid pagesize\n"); - goto readHexDb_error; - } - for(nLine++; fgets(zLine, sizeof(zLine), in)!=0; nLine++){ - rc = sscanf(zLine, "| page %d offset %d", &j, &k); - if( rc==2 ){ - iOffset = k; - continue; - } - if( cli_strncmp(zLine, "| end ", 6)==0 ){ - break; - } - rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", - &j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7], - &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]); - if( rc==17 ){ - k = iOffset+j; - if( k+16<=n && k>=0 ){ - int ii; - for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff; - } - } - } - *pnData = n; - if( in!=p->in ){ - fclose(in); - }else{ - p->lineno = nLine; - } - return a; +static int optionMatch(const char *zStr, const char *zOpt){ + if( zStr[0]!='-' ) return 0; + zStr++; + if( zStr[0]=='-' ) zStr++; + return cli_strcmp(zStr, zOpt)==0; +} -readHexDb_error: - if( in!=p->in ){ - fclose(in); - }else{ - while( fgets(zLine, sizeof(zLine), p->in)!=0 ){ - nLine++; - if(cli_strncmp(zLine, "| end ", 6)==0 ) break; - } - p->lineno = nLine; - } - sqlite3_free(a); - utf8_printf(stderr,"Error on line %d of --hexdb input\n", nLine); - return 0; +/* +** Delete a file. +*/ +int shellDeleteFile(const char *zFilename){ + int rc; +#ifdef _WIN32 + wchar_t *z = sqlite3_win32_utf8_to_unicode(zFilename); + rc = _wunlink(z); + sqlite3_free(z); +#else + rc = unlink(zFilename); +#endif + return rc; } -#endif /* SQLITE_OMIT_DESERIALIZE */ /* -** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X. +** Try to delete the temporary file (if there is one) and free the +** memory used to hold the name of the temp file. */ -static void shellUSleepFunc( - sqlite3_context *context, - int argcUnused, - sqlite3_value **argv -){ - int sleep = sqlite3_value_int(argv[0]); - (void)argcUnused; - sqlite3_sleep(sleep/1000); - sqlite3_result_int(context, sleep); +static void clearTempFile(ShellInState *psi){ + if( psi->zTempFile==0 ) return; + if( psi->doXdgOpen ) return; + if( shellDeleteFile(psi->zTempFile) ) return; + sqlite3_free(psi->zTempFile); + psi->zTempFile = 0; } -/* Flags for open_db(). -** -** The default behavior of open_db() is to exit(1) if the database fails to -** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error -** but still returns without calling exit. -** -** The OPEN_DB_ZIPFILE flag causes open_db() to prefer to open files as a -** ZIP archive if the file does not exist or is empty and its name matches -** the *.zip pattern. +/* +** Create a new temp file name with the given suffix. */ -#define OPEN_DB_KEEPALIVE 0x001 /* Return after error if true */ -#define OPEN_DB_ZIPFILE 0x002 /* Open as ZIP if name matches *.zip */ +static void newTempFile(ShellInState *psi, const char *zSuffix){ + clearTempFile(psi); + sqlite3_free(psi->zTempFile); + psi->zTempFile = 0; + if( DBI(psi) ){ + sqlite3_file_control(DBI(psi), 0, SQLITE_FCNTL_TEMPFILENAME, + &psi->zTempFile); + } + if( psi->zTempFile==0 ){ + /* If DB is an in-memory database then the TEMPFILENAME file-control + ** will not work and we will need to fallback to guessing */ + char *zTemp; + sqlite3_uint64 r; + sqlite3_randomness(sizeof(r), &r); + zTemp = getenv("TEMP"); + if( zTemp==0 ) zTemp = getenv("TMP"); + if( zTemp==0 ){ +#ifdef _WIN32 + zTemp = "\\tmp"; +#else + zTemp = "/tmp"; +#endif + } + psi->zTempFile = smprintf("%s/temp%llx.%s", zTemp, r, zSuffix); + }else{ + psi->zTempFile = smprintf("%z.%s", psi->zTempFile, zSuffix); + } + shell_check_ooms(psi->zTempFile); +} /* -** Make sure the database is open. If it is not, then open it. If -** the database fails to open, print an error message and exit. +** The implementation of SQL scalar function fkey_collate_clause(), used +** by the ".lint fkey-indexes" command. This scalar function is always +** called with four arguments - the parent table name, the parent column name, +** the child table name and the child column name. +** +** fkey_collate_clause('parent-tab', 'parent-col', 'child-tab', 'child-col') +** +** If either of the named tables or columns do not exist, this function +** returns an empty string. An empty string is also returned if both tables +** and columns exist but have the same default collation sequence. Or, +** if both exist but the default collation sequences are different, this +** function returns the string " COLLATE ", where +** is the default collation sequence of the parent column. */ -static void open_db(ShellState *p, int openFlags){ - if( p->db==0 ){ - const char *zDbFilename = p->pAuxDb->zDbFilename; - if( p->openMode==SHELL_OPEN_UNSPEC ){ - if( zDbFilename==0 || zDbFilename[0]==0 ){ - p->openMode = SHELL_OPEN_NORMAL; - }else{ - p->openMode = (u8)deduceDatabaseType(zDbFilename, - (openFlags & OPEN_DB_ZIPFILE)!=0); - } - } - switch( p->openMode ){ - case SHELL_OPEN_APPENDVFS: { - sqlite3_open_v2(zDbFilename, &p->db, - SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, "apndvfs"); - break; - } - case SHELL_OPEN_HEXDB: - case SHELL_OPEN_DESERIALIZE: { - sqlite3_open(0, &p->db); - break; - } - case SHELL_OPEN_ZIPFILE: { - sqlite3_open(":memory:", &p->db); - break; - } - case SHELL_OPEN_READONLY: { - sqlite3_open_v2(zDbFilename, &p->db, - SQLITE_OPEN_READONLY|p->openFlags, 0); - break; - } - case SHELL_OPEN_UNSPEC: - case SHELL_OPEN_NORMAL: { - sqlite3_open_v2(zDbFilename, &p->db, - SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, 0); - break; - } - } - globalDb = p->db; - if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ - utf8_printf(stderr,"Error: unable to open database \"%s\": %s\n", - zDbFilename, sqlite3_errmsg(p->db)); - if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){ - exit(1); - } - sqlite3_close(p->db); - sqlite3_open(":memory:", &p->db); - if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ - utf8_printf(stderr, - "Also: unable to open substitute in-memory database.\n" - ); - exit(1); - }else{ - utf8_printf(stderr, - "Notice: using substitute in-memory database instead of \"%s\"\n", - zDbFilename); - } - } - sqlite3_db_config(p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, (int)0, (int*)0); +static void shellFkeyCollateClause( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + sqlite3 *db = sqlite3_context_db_handle(pCtx); + const char *zParent; + const char *zParentCol; + const char *zParentSeq; + const char *zChild; + const char *zChildCol; + const char *zChildSeq = 0; /* Initialize to avoid false-positive warning */ + int rc; - /* Reflect the use or absence of --unsafe-testing invocation. */ - { - int testmode_on = ShellHasFlag(p,SHFLG_TestingMode); - sqlite3_db_config(p->db, SQLITE_DBCONFIG_TRUSTED_SCHEMA, testmode_on,0); - sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0); - } + assert( nVal==4 ); + zParent = (const char*)sqlite3_value_text(apVal[0]); + zParentCol = (const char*)sqlite3_value_text(apVal[1]); + zChild = (const char*)sqlite3_value_text(apVal[2]); + zChildCol = (const char*)sqlite3_value_text(apVal[3]); -#ifndef SQLITE_OMIT_LOAD_EXTENSION - sqlite3_enable_load_extension(p->db, 1); -#endif - sqlite3_shathree_init(p->db, 0, 0); - sqlite3_uint_init(p->db, 0, 0); - sqlite3_decimal_init(p->db, 0, 0); - sqlite3_base64_init(p->db, 0, 0); - sqlite3_base85_init(p->db, 0, 0); - sqlite3_regexp_init(p->db, 0, 0); - sqlite3_ieee_init(p->db, 0, 0); - sqlite3_series_init(p->db, 0, 0); -#ifndef SQLITE_SHELL_FIDDLE - sqlite3_fileio_init(p->db, 0, 0); - sqlite3_completion_init(p->db, 0, 0); -#endif -#ifdef SQLITE_HAVE_ZLIB - if( !p->bSafeModePersist ){ - sqlite3_zipfile_init(p->db, 0, 0); - sqlite3_sqlar_init(p->db, 0, 0); - } -#endif -#ifdef SQLITE_SHELL_EXTFUNCS - /* Create a preprocessing mechanism for extensions to make - * their own provisions for being built into the shell. - * This is a short-span macro. See further below for usage. - */ -#define SHELL_SUB_MACRO(base, variant) base ## _ ## variant -#define SHELL_SUBMACRO(base, variant) SHELL_SUB_MACRO(base, variant) - /* Let custom-included extensions get their ..._init() called. - * The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause - * the extension's sqlite3_*_init( db, pzErrorMsg, pApi ) - * inititialization routine to be called. - */ - { - int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db); - /* Let custom-included extensions expose their functionality. - * The WHATEVER_EXPOSE( db, pzErrorMsg ) macro should cause - * the SQL functions, virtual tables, collating sequences or - * VFS's implemented by the extension to be registered. - */ - if( irc==SQLITE_OK - || irc==SQLITE_OK_LOAD_PERMANENTLY ){ - SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, EXPOSE)(p->db, 0); - } -#undef SHELL_SUB_MACRO -#undef SHELL_SUBMACRO - } -#endif + sqlite3_result_text(pCtx, "", -1, SQLITE_STATIC); + rc = sqlite3_table_column_metadata( + db, "main", zParent, zParentCol, 0, &zParentSeq, 0, 0, 0 + ); + if( rc==SQLITE_OK ){ + rc = sqlite3_table_column_metadata( + db, "main", zChild, zChildCol, 0, &zChildSeq, 0, 0, 0 + ); + } - sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0, - shellAddSchemaName, 0, 0); - sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0, - shellModuleSchema, 0, 0); - sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p, - shellPutsFunc, 0, 0); - sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0, - shellUSleepFunc, 0, 0); -#ifndef SQLITE_NOHAVE_SYSTEM - sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0, - editFunc, 0, 0); - sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0, - editFunc, 0, 0); -#endif + if( rc==SQLITE_OK && sqlite3_stricmp(zParentSeq, zChildSeq) ){ + char *z = smprintf(" COLLATE %s", zParentSeq); + sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT); + sqlite3_free(z); + } +} - if( p->openMode==SHELL_OPEN_ZIPFILE ){ - char *zSql = sqlite3_mprintf( - "CREATE VIRTUAL TABLE zip USING zipfile(%Q);", zDbFilename); - shell_check_oom(zSql); - sqlite3_exec(p->db, zSql, 0, 0, 0); - sqlite3_free(zSql); +#if !defined SQLITE_OMIT_VIRTUALTABLE +static void shellPrepare( + sqlite3 *db, + int *pRc, + const char *zSql, + sqlite3_stmt **ppStmt +){ + *ppStmt = 0; + if( *pRc==SQLITE_OK ){ + int rc = s3_prepare_v2_noom(db, zSql, -1, ppStmt, 0); + if( rc!=SQLITE_OK ){ + raw_printf(STD_ERR, "sql error: %s (%d)\n", + sqlite3_errmsg(db), sqlite3_errcode(db) + ); + *pRc = rc; } -#ifndef SQLITE_OMIT_DESERIALIZE - else - if( p->openMode==SHELL_OPEN_DESERIALIZE || p->openMode==SHELL_OPEN_HEXDB ){ - int rc; - int nData = 0; - unsigned char *aData; - if( p->openMode==SHELL_OPEN_DESERIALIZE ){ - aData = (unsigned char*)readFile(zDbFilename, &nData); - }else{ - aData = readHexDb(p, &nData); - } - if( aData==0 ){ - return; - } - rc = sqlite3_deserialize(p->db, "main", aData, nData, nData, - SQLITE_DESERIALIZE_RESIZEABLE | - SQLITE_DESERIALIZE_FREEONCLOSE); - if( rc ){ - utf8_printf(stderr, "Error: sqlite3_deserialize() returns %d\n", rc); - } - if( p->szMax>0 ){ - sqlite3_file_control(p->db, "main", SQLITE_FCNTL_SIZE_LIMIT, &p->szMax); + } +} + +/* +** Create a prepared statement using printf-style arguments for the SQL. +** +** This routine is could be marked "static". But it is not always used, +** depending on compile-time options. By omitting the "static", we avoid +** nuisance compiler warnings about "defined but not used". +*/ +void shellPreparePrintf( + sqlite3 *db, + int *pRc, + sqlite3_stmt **ppStmt, + const char *zFmt, + ... +){ + *ppStmt = 0; + if( *pRc==SQLITE_OK ){ + va_list ap; + char *z; + va_start(ap, zFmt); + z = sqlite3_vmprintf(zFmt, ap); + va_end(ap); + if( z==0 ){ + *pRc = SQLITE_NOMEM; + }else{ + shellPrepare(db, pRc, z, ppStmt); + sqlite3_free(z); + } + } +} + +/* Finalize the prepared statement created using shellPreparePrintf(). +** +** This routine is could be marked "static". But it is not always used, +** depending on compile-time options. By omitting the "static", we avoid +** nuisance compiler warnings about "defined but not used". +*/ +void shellFinalize( + int *pRc, + sqlite3_stmt *pStmt +){ + if( pStmt ){ + sqlite3 *db = sqlite3_db_handle(pStmt); + int rc = sqlite3_finalize(pStmt); + if( *pRc==SQLITE_OK ){ + if( rc!=SQLITE_OK ){ + raw_printf(STD_ERR, "SQL error: %s\n", sqlite3_errmsg(db)); } + *pRc = rc; } -#endif } - if( p->db!=0 ){ - if( p->bSafeModePersist ){ - sqlite3_set_authorizer(p->db, safeModeAuth, p); +} + +/* Reset the prepared statement created using shellPreparePrintf(). +** +** This routine is could be marked "static". But it is not always used, +** depending on compile-time options. By omitting the "static", we avoid +** nuisance compiler warnings about "defined but not used". +*/ +void shellReset( + int *pRc, + sqlite3_stmt *pStmt +){ + int rc = sqlite3_reset(pStmt); + if( *pRc==SQLITE_OK ){ + if( rc!=SQLITE_OK ){ + sqlite3 *db = sqlite3_db_handle(pStmt); + raw_printf(STD_ERR, "SQL error: %s\n", sqlite3_errmsg(db)); } - sqlite3_db_config( - p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0 - ); + *pRc = rc; } } +#endif /* !defined SQLITE_OMIT_VIRTUALTABLE */ + +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) +# define ARCHIVE_ENABLE 1 +#else +# define ARCHIVE_ENABLE 0 +#endif +#if ARCHIVE_ENABLE && !defined(SQLITE_SHELL_FIDDLE) +/****************************************************************************** +** The ".archive" or ".ar" command. +*/ /* -** Attempt to close the databaes connection. Report errors. +** Structure representing a single ".ar" command. */ -void close_db(sqlite3 *db){ - int rc = sqlite3_close(db); - if( rc ){ - utf8_printf(stderr, "Error: sqlite3_close() returns %d: %s\n", - rc, sqlite3_errmsg(db)); +typedef struct ArCommand ArCommand; +struct ArCommand { + u8 eCmd; /* An AR_CMD_* value */ + u8 bVerbose; /* True if --verbose */ + u8 bZip; /* True if the archive is a ZIP */ + u8 bDryRun; /* True if --dry-run */ + u8 bAppend; /* True if --append */ + u8 bGlob; /* True if --glob */ + u8 fromCmdLine; /* Run from -A instead of .archive */ + int nArg; /* Number of command arguments */ + char *zSrcTable; /* "sqlar", "zipfile($file)" or "zip" */ + const char *zFile; /* --file argument, or NULL */ + const char *zDir; /* --directory argument, or NULL */ + char **azArg; /* Array of command arguments */ + ShellExState *p; /* Shell state */ + FILE *out; /* Where to put normal messages or info */ + sqlite3 *db; /* Database containing the archive */ +}; + +/* +** Print a usage message for the .ar command to stderr and return SQLITE_ERROR. +*/ +static int arUsage(FILE *f, ArCommand *pAr){ + showHelp(f,"archive", pAr->p); + return SQLITE_ERROR; +} + +/* +** Print an error message for the .ar command to stderr and return +** SQLITE_ERROR. +*/ +static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){ + va_list ap; + char *z; + va_start(ap, zFmt); + z = sqlite3_vmprintf(zFmt, ap); + va_end(ap); + utf8_printf(STD_ERR, "Error: %s\n", z); + if( pAr->fromCmdLine ){ + utf8_printf(STD_ERR, "Use \"-A\" for more help\n"); + }else{ + utf8_printf(STD_ERR, "Use \".archive --help\" for more help\n"); } + sqlite3_free(z); + return SQLITE_ERROR; } -#if HAVE_READLINE || HAVE_EDITLINE /* -** Readline completion callbacks +** Values for ArCommand.eCmd. */ -static char *readline_completion_generator(const char *text, int state){ - static sqlite3_stmt *pStmt = 0; - char *zRet; - if( state==0 ){ - char *zSql; - sqlite3_finalize(pStmt); - zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase" - " FROM completion(%Q) ORDER BY 1", text); - shell_check_oom(zSql); - sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); +#define AR_CMD_CREATE 1 +#define AR_CMD_UPDATE 2 +#define AR_CMD_INSERT 3 +#define AR_CMD_EXTRACT 4 +#define AR_CMD_LIST 5 +#define AR_CMD_HELP 6 +#define AR_CMD_REMOVE 7 + +/* +** Other (non-command) switches. +*/ +#define AR_SWITCH_VERBOSE 8 +#define AR_SWITCH_FILE 9 +#define AR_SWITCH_DIRECTORY 10 +#define AR_SWITCH_APPEND 11 +#define AR_SWITCH_DRYRUN 12 +#define AR_SWITCH_GLOB 13 + +static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){ + switch( eSwitch ){ + case AR_CMD_CREATE: + case AR_CMD_EXTRACT: + case AR_CMD_LIST: + case AR_CMD_REMOVE: + case AR_CMD_UPDATE: + case AR_CMD_INSERT: + case AR_CMD_HELP: + if( pAr->eCmd ){ + return arErrorMsg(pAr, "multiple command options"); + } + pAr->eCmd = eSwitch; + break; + + case AR_SWITCH_DRYRUN: + pAr->bDryRun = 1; + break; + case AR_SWITCH_GLOB: + pAr->bGlob = 1; + break; + case AR_SWITCH_VERBOSE: + pAr->bVerbose = 1; + break; + case AR_SWITCH_APPEND: + pAr->bAppend = 1; + deliberate_fall_through; + case AR_SWITCH_FILE: + pAr->zFile = zArg; + break; + case AR_SWITCH_DIRECTORY: + pAr->zDir = zArg; + break; } - if( sqlite3_step(pStmt)==SQLITE_ROW ){ - const char *z = (const char*)sqlite3_column_text(pStmt,0); - zRet = z ? strdup(z) : 0; + + return SQLITE_OK; +} + +/* +** Parse the command line for an ".ar" command. The results are written into +** structure (*pAr). DCR_Ok is returned if the command line is parsed +** successfully, otherwise an error message is written to stderr and an +** appropriate DCR_* argument error is returned. +*/ +static DotCmdRC arParseCommand( + char **azArg, /* Array of arguments passed to dot command */ + int nArg, /* Number of entries in azArg[] */ + ArCommand *pAr /* Populate this object */ +){ + struct ArSwitch { + const char *zLong; + char cShort; + u8 eSwitch; + u8 bArg; + } aSwitch[] = { + { "create", 'c', AR_CMD_CREATE, 0 }, + { "extract", 'x', AR_CMD_EXTRACT, 0 }, + { "insert", 'i', AR_CMD_INSERT, 0 }, + { "list", 't', AR_CMD_LIST, 0 }, + { "remove", 'r', AR_CMD_REMOVE, 0 }, + { "update", 'u', AR_CMD_UPDATE, 0 }, + { "help", 'h', AR_CMD_HELP, 0 }, + { "verbose", 'v', AR_SWITCH_VERBOSE, 0 }, + { "file", 'f', AR_SWITCH_FILE, 1 }, + { "append", 'a', AR_SWITCH_APPEND, 1 }, + { "directory", 'C', AR_SWITCH_DIRECTORY, 1 }, + { "dryrun", 'n', AR_SWITCH_DRYRUN, 0 }, + { "glob", 'g', AR_SWITCH_GLOB, 0 }, + }; + int nSwitch = sizeof(aSwitch) / sizeof(struct ArSwitch); + struct ArSwitch *pEnd = &aSwitch[nSwitch]; + DotCmdRC rv = DCR_Ok; + + if( nArg<=1 ){ + utf8_printf(STD_ERR, "Error: Wrong number of arguments to \"%s\".\n", + azArg[0]); + if( stdin_is_interactive ){ + utf8_printf(STD_ERR, "Usage:\n"); + arUsage(STD_ERR, pAr); + return DCR_CmdErred; + } }else{ - sqlite3_finalize(pStmt); - pStmt = 0; - zRet = 0; + char *z = azArg[1]; + if( z[0]!='-' ){ + /* Traditional style [tar] invocation */ + int i; + int iArg = 2; + for(i=0; z[i]; i++){ + const char *zArg = 0; + struct ArSwitch *pOpt; + for(pOpt=&aSwitch[0]; pOptcShort ) break; + } + if( pOpt==pEnd ){ + arErrorMsg(pAr, "unrecognized option: %c", z[i]); + return DCR_Unknown|i; + } + if( pOpt->bArg ){ + if( iArg>=nArg ){ + arErrorMsg(pAr, "option requires an argument: %c",z[i]); + return DCR_Unpaired|i; + } + zArg = azArg[iArg++]; + } + rv = arProcessSwitch(pAr, pOpt->eSwitch, zArg); + if( rv!=DCR_Ok ) return rv; + } + pAr->nArg = nArg-iArg; + if( pAr->nArg>0 ){ + pAr->azArg = &azArg[iArg]; + } + }else{ + /* Non-traditional invocation */ + int iArg; + for(iArg=1; iArgazArg = &azArg[iArg]; + pAr->nArg = nArg-iArg; + break; + } + n = strlen30(z); + + if( z[1]!='-' ){ + int i; + /* One or more short options */ + for(i=1; icShort ) break; + } + if( pOpt==pEnd ){ + arErrorMsg(pAr, "unrecognized option: %c", z[i]); + return DCR_Unknown|iArg; + } + if( pOpt->bArg ){ + if( i<(n-1) ){ + zArg = &z[i+1]; + i = n; + }else{ + if( iArg>=(nArg-1) ){ + arErrorMsg(pAr, "option requires an argument: %c", z[i]); + return DCR_Unpaired|iArg; + } + zArg = azArg[++iArg]; + } + } + rv = arProcessSwitch(pAr, pOpt->eSwitch, zArg); + if( rv!=DCR_Ok ) return rv; + } + }else if( z[2]=='\0' ){ + /* A -- option, indicating that all remaining command line words + ** are command arguments. */ + pAr->azArg = &azArg[iArg+1]; + pAr->nArg = nArg-iArg-1; + break; + }else{ + /* A long option */ + const char *zArg = 0; /* Argument for option, if any */ + struct ArSwitch *pMatch = 0; /* Matching option */ + struct ArSwitch *pOpt; /* Iterator */ + for(pOpt=&aSwitch[0]; pOptzLong; + if( (n-2)<=strlen30(zLong) && 0==memcmp(&z[2], zLong, n-2) ){ + if( pMatch ){ + arErrorMsg(pAr, "ambiguous option: %s",z); + return DCR_Ambiguous|iArg; + }else{ + pMatch = pOpt; + } + } + } + + if( pMatch==0 ){ + arErrorMsg(pAr, "unrecognized option: %s", z); + return DCR_Unknown|iArg; + } + if( pMatch->bArg ){ + if( iArg>=(nArg-1) ){ + arErrorMsg(pAr, "option requires an argument: %s", z); + return DCR_Unpaired|iArg; + } + zArg = azArg[++iArg]; + } + if( arProcessSwitch(pAr, pMatch->eSwitch, zArg) ) return SQLITE_ERROR; + } + } + } } - return zRet; -} -static char **readline_completion(const char *zText, int iStart, int iEnd){ - (void)iStart; - (void)iEnd; - rl_attempted_completion_over = 1; - return rl_completion_matches(zText, readline_completion_generator); + if( pAr->eCmd==0 ){ + utf8_printf(stderr, "Required argument missing. Usage:\n"); + return arUsage(stderr,pAr); + } + return SQLITE_OK; } -#elif HAVE_LINENOISE /* -** Linenoise completion callback +** This function assumes that all arguments within the ArCommand.azArg[] +** array refer to archive members, as for the --extract, --list or --remove +** commands. It checks that each of them are "present". If any specified +** file is not present in the archive, an error is printed to stderr and an +** error code returned. Otherwise, if all specified arguments are present +** in the archive, SQLITE_OK is returned. Here, "present" means either an +** exact equality when pAr->bGlob is false or a "name GLOB pattern" match +** when pAr->bGlob is true. +** +** This function strips any trailing '/' characters from each argument. +** This is consistent with the way the [tar] command seems to work on +** Linux. */ -static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){ - i64 nLine = strlen(zLine); - i64 i, iStart; - sqlite3_stmt *pStmt = 0; - char *zSql; - char zBuf[1000]; +static int arCheckEntries(ArCommand *pAr){ + int rc = SQLITE_OK; + if( pAr->nArg ){ + int i, j; + sqlite3_stmt *pTest = 0; + const char *zSel = (pAr->bGlob) + ? "SELECT name FROM %s WHERE glob($name,name)" + : "SELECT name FROM %s WHERE name=$name"; - if( nLine>(i64)sizeof(zBuf)-30 ) return; - if( zLine[0]=='.' || zLine[0]=='#') return; - for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){} - if( i==nLine-1 ) return; - iStart = i+1; - memcpy(zBuf, zLine, iStart); - zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase" - " FROM completion(%Q,%Q) ORDER BY 1", - &zLine[iStart], zLine); - shell_check_oom(zSql); - sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); - sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */ - while( sqlite3_step(pStmt)==SQLITE_ROW ){ - const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0); - int nCompletion = sqlite3_column_bytes(pStmt, 0); - if( iStart+nCompletion < (i64)sizeof(zBuf)-1 && zCompletion ){ - memcpy(zBuf+iStart, zCompletion, nCompletion+1); - linenoiseAddCompletion(lc, zBuf); + shellPreparePrintf(pAr->db, &rc, &pTest, zSel, pAr->zSrcTable); + j = sqlite3_bind_parameter_index(pTest, "$name"); + for(i=0; inArg && rc==SQLITE_OK; i++){ + char *z = pAr->azArg[i]; + int n = strlen30(z); + int bOk = 0; + while( n>0 && z[n-1]=='/' ) n--; + z[n] = '\0'; + sqlite3_bind_text(pTest, j, z, -1, SQLITE_STATIC); + if( SQLITE_ROW==sqlite3_step(pTest) ){ + bOk = 1; + } + shellReset(&rc, pTest); + if( rc==SQLITE_OK && bOk==0 ){ + utf8_printf(STD_ERR, "not found in archive: %s\n", z); + rc = SQLITE_ERROR; + } } + shellFinalize(&rc, pTest); } - sqlite3_finalize(pStmt); + return rc; } -#endif /* -** Do C-language style dequoting. -** -** \a -> alarm -** \b -> backspace -** \t -> tab -** \n -> newline -** \v -> vertical tab -** \f -> form feed -** \r -> carriage return -** \s -> space -** \" -> " -** \' -> ' -** \\ -> backslash -** \NNN -> ascii character NNN in octal -** \xHH -> ascii character HH in hexadecimal +** Format a WHERE clause that can be used against the "sqlar" table to +** identify all archive members that match the command arguments held +** in (*pAr). Leave this WHERE clause in (*pzWhere) before returning. +** The caller is responsible for eventually calling sqlite3_free() on +** any non-NULL (*pzWhere) value. Here, "match" means strict equality +** when pAr->bGlob is false and GLOB match when pAr->bGlob is true. + */ -static void resolve_backslashes(char *z){ - int i, j; - char c; - while( *z && *z!='\\' ) z++; - for(i=j=0; (c = z[i])!=0; i++, j++){ - if( c=='\\' && z[i+1]!=0 ){ - c = z[++i]; - if( c=='a' ){ - c = '\a'; - }else if( c=='b' ){ - c = '\b'; - }else if( c=='t' ){ - c = '\t'; - }else if( c=='n' ){ - c = '\n'; - }else if( c=='v' ){ - c = '\v'; - }else if( c=='f' ){ - c = '\f'; - }else if( c=='r' ){ - c = '\r'; - }else if( c=='"' ){ - c = '"'; - }else if( c=='\'' ){ - c = '\''; - }else if( c=='\\' ){ - c = '\\'; - }else if( c=='x' ){ - int nhd = 0, hdv; - u8 hv = 0; - while( nhd<2 && (c=z[i+1+nhd])!=0 && (hdv=hexDigitValue(c))>=0 ){ - hv = (u8)((hv<<4)|hdv); - ++nhd; - } - i += nhd; - c = (u8)hv; - }else if( c>='0' && c<='7' ){ - c -= '0'; - if( z[i+1]>='0' && z[i+1]<='7' ){ - i++; - c = (c<<3) + z[i] - '0'; - if( z[i+1]>='0' && z[i+1]<='7' ){ - i++; - c = (c<<3) + z[i] - '0'; - } +static void arWhereClause( + int *pRc, + ArCommand *pAr, + char **pzWhere /* OUT: New WHERE clause */ +){ + char *zWhere = 0; + const char *zSameOp = (pAr->bGlob)? "GLOB" : "="; + if( *pRc==SQLITE_OK ){ + if( pAr->nArg==0 ){ + zWhere = smprintf("1"); + }else{ + int i; + const char *zSep = ""; + for(i=0; inArg; i++){ + const char *z = pAr->azArg[i]; + zWhere = smprintf("%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'", + zWhere, zSep, zSameOp, z, strlen30(z)+1, zSameOp, z); + if( zWhere==0 ){ + *pRc = SQLITE_NOMEM; + break; } + zSep = " OR "; } } - z[j] = c; } - if( jdbShell==0 || (psi->numExtLoaded==0 && psi->pShxLoaded==0)); + rc = ensure_shell_db(psx); + if( rc!=SQLITE_OK ){ + utf8_printf(STD_ERR, "Error: Shell DB uncreatable. Best to .quit soon.\n"); + return SQLITE_ERROR; + } + if( ensure_dispatch_table(psx)!=SQLITE_OK ) return 1; + + psi->pShxLoaded = (ShExtInfo *)sqlite3_malloc(2*sizeof(ShExtInfo)); + shell_check_ooms(psi->pShxLoaded); + /* The ShellInState object now owns above allocation, so initialize it. */ + memset(psi->pShxLoaded, 0, 2*sizeof(ShExtInfo)); + any_ref_holder(&arh_sei); /* protect against early aborts */ + sei.ppDotCommands + = (DotCommand **)sqlite3_malloc((numCommands+2)*sizeof(DotCommand *)); + sei.ppExportHandlers + = (ExportHandler **)sqlite3_malloc(2*sizeof(ExportHandler *)); + sei.ppImportHandlers + = (ImportHandler **)sqlite3_malloc(2*sizeof(ImportHandler *)); + if( sei.ppDotCommands==0||sei.ppExportHandlers==0||sei.ppImportHandlers==0 + || psi->pShxLoaded==0 ){ + shell_out_of_memory(); + } + sei.numExportHandlers = 0; + sei.numImportHandlers = 0; + for( ic=0; ic<(int)numCommands; ++ic ){ + sei.ppDotCommands[ic] = builtInCommand(ic); + } + sei.numDotCommands = ic; + zSql = "INSERT INTO "SHELL_DISP_TAB"(name, extIx, cmdIx) VALUES(?, 0, ?)"; + rc1 = s3_prepare_v2_noom(psx->dbShell, zSql, -1, &pStmt, 0); + stmt_holder(pStmt); + rc2 = s3_exec_noom(psx->dbShell, "BEGIN TRANSACTION", 0, 0, &zErr); + if( rc1!=SQLITE_OK || rc2!=SQLITE_OK ){ + rc = SQLITE_ERROR; + }else{ + assert(sei.numDotCommands>0); + for( ic=0; icpMethods->name(pmc); + sqlite3_reset(pStmt); + shell_check_nomem(sqlite3_bind_text(pStmt, 1, zName, -1, 0)); + sqlite3_bind_int(pStmt, 2, ic); + rc = s3_step_noom(pStmt); + if( rc!=SQLITE_DONE ){ + sqlite3_exec(psx->dbShell, "ABORT", 0, 0, 0); + break; } - *pRc = rc; + } + if( rc!=SQLITE_DONE ){ + rc = SQLITE_ERROR; + zSql = "ABORT"; + }else{ + rc = SQLITE_OK; + zSql = "COMMIT"; + } + rc2 = s3_exec_noom(psx->dbShell, zSql, 0, 0, &zErr); + if( SQLITE_OK==rc ){ + /* Transfer just-built ShExtInfo to ShellInState use and ownership. */ + psi->pShxLoaded[psi->numExtLoaded++] = sei; + arh_sei.pAny = 0; + sqlite3_enable_load_extension(psx->dbShell, 1); + psi->bDbDispatch = 1; } } + RESOURCE_FREE(mark); + + return rc; } -/* Reset the prepared statement created using shellPreparePrintf(). -** -** This routine is could be marked "static". But it is not always used, -** depending on compile-time options. By omitting the "static", we avoid -** nuisance compiler warnings about "defined but not used". -*/ -void shellReset( - int *pRc, - sqlite3_stmt *pStmt -){ - int rc = sqlite3_reset(pStmt); - if( *pRc==SQLITE_OK ){ - if( rc!=SQLITE_OK ){ - sqlite3 *db = sqlite3_db_handle(pStmt); - raw_printf(stderr, "SQL error: %s\n", sqlite3_errmsg(db)); +/* Call one loaded extension's destructors, in reverse order of their + * objects' creation. + */ +static void run_one_shext_dtors(ShExtInfo *psei){ + int j; + if( psei->ppDotCommands!=0 ){ + for( j=psei->numDotCommands; j>0; --j ){ + DotCommand *pmc = psei->ppDotCommands[j-1]; + if( pmc->pMethods->destruct!=0 ) pmc->pMethods->destruct(pmc); } - *pRc = rc; + } + if( psei->ppExportHandlers!=0 ){ + for( j=psei->numExportHandlers; j>0; --j ){ + ExportHandler *peh = psei->ppExportHandlers[j-1]; + if( peh->pMethods->destruct!=0 ) peh->pMethods->destruct(peh); + } + } + if( psei->ppImportHandlers!=0 ){ + for( j=psei->numImportHandlers; j>0; --j ){ + ImportHandler *pih = psei->ppImportHandlers[j-1]; + if( pih->pMethods->destruct!=0 ) pih->pMethods->destruct(pih); + } + } + if( psei->extDtor!=0 ){ + psei->extDtor(psei->pvExtObj); } } -#endif /* !defined SQLITE_OMIT_VIRTUALTABLE */ -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) -/****************************************************************************** -** The ".archive" or ".ar" command. -*/ -/* -** Structure representing a single ".ar" command. -*/ -typedef struct ArCommand ArCommand; -struct ArCommand { - u8 eCmd; /* An AR_CMD_* value */ - u8 bVerbose; /* True if --verbose */ - u8 bZip; /* True if the archive is a ZIP */ - u8 bDryRun; /* True if --dry-run */ - u8 bAppend; /* True if --append */ - u8 bGlob; /* True if --glob */ - u8 fromCmdLine; /* Run from -A instead of .archive */ - int nArg; /* Number of command arguments */ - char *zSrcTable; /* "sqlar", "zipfile($file)" or "zip" */ - const char *zFile; /* --file argument, or NULL */ - const char *zDir; /* --directory argument, or NULL */ - char **azArg; /* Array of command arguments */ - ShellState *p; /* Shell state */ - sqlite3 *db; /* Database containing the archive */ -}; +/* Call all existent loaded extension destructors, in reverse order of their + * objects' creation, except for scripting support which is done last, + * then free the tracking dyna-arrays. + */ +static void free_all_shext_tracking(ShellInState *psi){ + if( psi->pShxLoaded!=0 ){ + int i = psi->numExtLoaded; + while( i>1 ){ + ShExtInfo *psei = &psi->pShxLoaded[--i]; + run_one_shext_dtors(psei); + free_ShExtInfo(psei); + if( psi->scriptXid!=0 && psi->scriptXid==psei->extId ){ + assert(psi->script!=0); + if (psi->script->pMethods->destruct){ + psi->script->pMethods->destruct(psi->script); + } + psi->script = 0; + psi->scriptXid = 0; + } + } + free_ShExtInfo(psi->pShxLoaded); + sqlite3_free(psi->pShxLoaded); + psi->pShxLoaded = 0; + psi->numExtLoaded = 0; + } +} -/* -** Print a usage message for the .ar command to stderr and return SQLITE_ERROR. -*/ -static int arUsage(FILE *f){ - showHelp(f,"archive"); - return SQLITE_ERROR; +static DotCommand *command_by_index(ShellInState *psi, int extIx, int cmdIx){ + assert(extIx>=0); + if( extIx>=0 && extIxnumExtLoaded ){ + ShExtInfo *psei = & psi->pShxLoaded[extIx]; + if( cmdIx>=0 && cmdIxnumDotCommands ){ + return psei->ppDotCommands[cmdIx]; + } + } + return 0; } -/* -** Print an error message for the .ar command to stderr and return -** SQLITE_ERROR. -*/ -static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){ - va_list ap; - char *z; - va_start(ap, zFmt); - z = sqlite3_vmprintf(zFmt, ap); - va_end(ap); - utf8_printf(stderr, "Error: %s\n", z); - if( pAr->fromCmdLine ){ - utf8_printf(stderr, "Use \"-A\" for more help\n"); +static int load_shell_extension(ShellExState *psx, const char *zFile, + const char *zProc, char **pzErr, + int nLoadArgs, char **azLoadArgs){ + ShellExtensionLink shxLink = { + sizeof(ShellExtensionLink), + &shellExtAPI, + psx, /* pSXS */ + 0, /* zErrMsg */ + 0, /* ExtensionId */ + 0, /* Extension destructor */ + 0, /* Extension data ref */ + nLoadArgs, azLoadArgs /* like-named members */ + }; //extDtor(pvExtObj) + ShellInState *psi = ISS(psx); + /* save script support state for possible fallback if load fails */ + ScriptSupport *pssSave = psi->script; + ExtensionId ssiSave = psi->scriptXid; + int rc; + + if( pzErr ) *pzErr = 0; + if( psx->dbShell==0 || ISS(psx)->numExtLoaded==0 ){ + rc = begin_db_dispatch(psx); + if( rc!=SQLITE_OK ) return rc; + assert(ISS(psx)->numExtLoaded==1 && psx->dbShell!=0); + } + psi->ixExtPending = psi->numExtLoaded; + sqlite3_create_function(psx->dbShell, "shext_pointer", 1, + SQLITE_DIRECTONLY|SQLITE_UTF8, + &shxLink, shell_linkage, 0, 0); + rc = sqlite3_load_extension(psx->dbShell, zFile, zProc, &shxLink.zErrMsg); + sqlite3_create_function(psx->dbShell, "shext_pointer", 1, + SQLITE_DIRECTONLY|SQLITE_UTF8, + 0, 0, 0, 0); /* deregister */ + if( pzErr!=0 ) *pzErr = shxLink.zErrMsg; + if( rc==SQLITE_OK ){ + /* Keep extension's id and destructor for later disposal. */ + ShExtInfo *psei = pending_ext_info(psi); + if( psei->extId!=0 && psei->extId!=shxLink.eid ) rc = SQLITE_MISUSE; + psei->extId = shxLink.eid; + psei->extDtor = shxLink.extensionDestruct; + psei->pvExtObj = shxLink.pvExtensionObject; }else{ - utf8_printf(stderr, "Use \".archive --help\" for more help\n"); + /* Release all resources extension might have registered before failing. */ + if( psi->ixExtPending < psi->numExtLoaded ){ + run_one_shext_dtors(psi->pShxLoaded+psi->ixExtPending); + free_ShExtInfo(psi->pShxLoaded+psi->ixExtPending); + --psi->numExtLoaded; + } + /* And make it unwind any scripting linkage it might have setup. */ + if( psi->script!=0 ) psi->script->pMethods->destruct(psi->script); + psi->script = pssSave; + psi->scriptXid = ssiSave; + } + psi->ixExtPending = 0; + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_MISUSE && pzErr!=0 ){ + *pzErr = smprintf("extension id mismatch %z\n", *pzErr); + } + rc = SQLITE_ERROR; } - sqlite3_free(z); - return SQLITE_ERROR; + return rc; } +#endif -/* -** Values for ArCommand.eCmd. +/* Dot-command implementation functions are defined in this section. +COMMENT Define dot-commands and provide for their dispatch and .help text. +COMMENT These should be kept in command name order for coding convenience +COMMENT except where dot-commands share implementation. (The ordering +COMMENT required for dispatch and help text is effected regardless.) The +COMMENT effect of this configuration can be seen in generated output or by +COMMENT executing tool/mkshellc.tcl --parameters (or --details or --help). +COMMENT Generally, this section defines dispatchable functions inline and +COMMENT causes collection of command_table entry initializers, to be later +COMMENT emitted by a mkshellc macro. (See EMIT_DOTCMD_INIT further on.) +** All dispatchable dot-command execute functions have this signature: +static int someCommand(char *azArg[], int nArg, ShellExState *p, char **pzErr); */ -#define AR_CMD_CREATE 1 -#define AR_CMD_UPDATE 2 -#define AR_CMD_INSERT 3 -#define AR_CMD_EXTRACT 4 -#define AR_CMD_LIST 5 -#define AR_CMD_HELP 6 -#define AR_CMD_REMOVE 7 +DISPATCH_CONFIG[ + RETURN_TYPE=DotCmdRC + STORAGE_CLASS=static + ARGS_SIGNATURE=char *$arg4\[\], int $arg5, ShellExState *$arg6, char **$arg7 + DISPATCH_ENTRY={ "$cmd", ${cmd}Command, $arg1, $arg2, $arg3 }, + DOTCMD_INIT={ DOT_CMD_INFO(${cmd}, $arg1,$arg2,$arg3), {, }, 0 }, + CMD_CAPTURE_RE=^\s*{\s*"(\w+)" + DISPATCHEE_NAME=${cmd}Command + DC_ARG1_DEFAULT=[string length $cmd] + DC_ARG2_DEFAULT=0 + DC_ARG3_DEFAULT=0 + DC_ARG4_DEFAULT=azArg + DC_ARG5_DEFAULT=nArg + DC_ARG6_DEFAULT=p + DC_ARG7_DEFAULT=pzErr + DC_ARG_COUNT=8 +]; + +CONDITION_COMMAND(seeargs !defined(SHELL_OMIT_SEEARGS)); +/***************** + * The .seeargs command + */ +COLLECT_HELP_TEXT[ + ",seeargs Echo arguments suffixed with |", +]; +DISPATCHABLE_COMMAND( seeargs ? 0 0 azArg nArg p ){ + int ia = 0; + for (ia=1; iaout, "%s%s", azArg[ia], (ia==nArg-1)? "|\n" : "|"); + return DCR_Ok; +} -/* -** Other (non-command) switches. -*/ -#define AR_SWITCH_VERBOSE 8 -#define AR_SWITCH_FILE 9 -#define AR_SWITCH_DIRECTORY 10 -#define AR_SWITCH_APPEND 11 -#define AR_SWITCH_DRYRUN 12 -#define AR_SWITCH_GLOB 13 +CONDITION_COMMAND(archive ARCHIVE_ENABLE && !defined(SQLITE_SHELL_FIDDLE)); +/***************** + * The .archive command + */ +COLLECT_HELP_TEXT[ + ".archive ... Manage SQL archives", + " Each command must have exactly one of the following options:", + " -c, --create Create a new archive", + " -u, --update Add or update files with changed mtime", + " -i, --insert Like -u but always add even if unchanged", + " -r, --remove Remove files from archive", + " -t, --list List contents of archive", + " -x, --extract Extract files from archive", + " Optional arguments:", + " -v, --verbose Print each filename as it is processed", + " -f FILE, --file FILE Use archive FILE (default is current db)", + " -a FILE, --append FILE Open FILE using the apndvfs VFS", + " -C DIR, --directory DIR Read/extract files from directory DIR", + " -g, --glob Use glob matching for names in archive", + " -n, --dryrun Show the SQL that would have occurred", + " Examples:", + " .ar -cf ARCHIVE foo bar # Create ARCHIVE from files foo and bar", + " .ar -tf ARCHIVE # List members of ARCHIVE", + " .ar -xvf ARCHIVE # Verbosely extract files from ARCHIVE", + " See also:", + " http://sqlite.org/cli.html#sqlite_archive_support", +]; +DISPATCHABLE_COMMAND( archive ? 0 0 azArg nArg p ){ + open_db(p, 0); + if( ISS(p)->bSafeMode ) return DCR_AbortError; + return arDotCommand(p, 0, azArg, nArg); +} -static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){ - switch( eSwitch ){ - case AR_CMD_CREATE: - case AR_CMD_EXTRACT: - case AR_CMD_LIST: - case AR_CMD_REMOVE: - case AR_CMD_UPDATE: - case AR_CMD_INSERT: - case AR_CMD_HELP: - if( pAr->eCmd ){ - return arErrorMsg(pAr, "multiple command options"); - } - pAr->eCmd = eSwitch; - break; +/***************** + * The .auth command + */ +CONDITION_COMMAND(auth !defined(SQLITE_OMIT_AUTHORIZATION)); +COLLECT_HELP_TEXT[ + ".auth ON|OFF Show authorizer callbacks", +]; +DISPATCHABLE_COMMAND( auth 3 2 2 azArg nArg p ){ + open_db(p, 0); + if( booleanValue(azArg[1]) ){ + sqlite3_set_authorizer(DBX(p), shellAuth, p); + }else if( ISS(p)->bSafeModeFuture ){ + sqlite3_set_authorizer(DBX(p), safeModeAuth, p); + }else{ + sqlite3_set_authorizer(DBX(p), 0, 0); + } + return DCR_Ok; +} - case AR_SWITCH_DRYRUN: - pAr->bDryRun = 1; - break; - case AR_SWITCH_GLOB: - pAr->bGlob = 1; - break; - case AR_SWITCH_VERBOSE: - pAr->bVerbose = 1; - break; - case AR_SWITCH_APPEND: - pAr->bAppend = 1; - deliberate_fall_through; - case AR_SWITCH_FILE: - pAr->zFile = zArg; - break; - case AR_SWITCH_DIRECTORY: - pAr->zDir = zArg; - break; +/***************** + * The .backup and .save commands (aliases for each other) + * These defer to writeDb in the dispatch table, so are not here. + */ +CONDITION_COMMAND(backup !defined(SQLITE_SHELL_FIDDLE)); +CONDITION_COMMAND(save !defined(SQLITE_SHELL_FIDDLE) ); +COLLECT_HELP_TEXT[ + ".backup ?DB? FILE Backup DB (default \"main\") to FILE", + " Options:", + " --append Use the appendvfs", + " --async Write the FILE without journal and fsync()", + ".save ?DB? FILE Write DB (default \"main\") to FILE", + " Options:", + " --append Use the appendvfs", + " --async Write the FILE without journal and fsync()", +]; +DISPATCHABLE_COMMAND( backup 4 2 5 ){ + return writeDb( azArg, nArg, p, pzErr); +} +DISPATCHABLE_COMMAND( save 3 2 5 ){ + return writeDb( azArg, nArg, p, pzErr); +} + +/***************** + * The .bail command + */ +COLLECT_HELP_TEXT[ + ".bail on|off Stop after hitting an error. Default OFF", +]; +DISPATCHABLE_COMMAND( bail 3 2 2 ){ + bail_on_error = booleanValue(azArg[1]); + return DCR_Ok; +} + +CONDITION_COMMAND(cd !defined(SQLITE_SHELL_FIDDLE)); +/***************** + * The .binary and .cd commands + */ +COLLECT_HELP_TEXT[ + ".binary on|off Turn binary output on or off. Default OFF", + ".cd DIRECTORY Change the working directory to DIRECTORY", +]; +DISPATCHABLE_COMMAND( binary 3 2 2 ){ + if( booleanValue(azArg[1]) ){ + setBinaryMode(ISS(p)->out, 1); + }else{ + setTextMode(ISS(p)->out, 1); } + return DCR_Ok; +} - return SQLITE_OK; +DISPATCHABLE_COMMAND( cd ? 2 2 ){ + int rc=0; + if( ISS(p)->bSafeMode ) return DCR_AbortError; + else{ +#if defined(_WIN32) || defined(WIN32) + wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]); + shell_check_ooms(z); + rc = (z)? !SetCurrentDirectoryW(z) : 1; + 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; + } + return DCR_Ok|rc; } -/* -** Parse the command line for an ".ar" command. The results are written into -** structure (*pAr). SQLITE_OK is returned if the command line is parsed -** successfully, otherwise an error message is written to stderr and -** SQLITE_ERROR returned. +/* The ".breakpoint" command causes a call to the no-op routine named + * test_breakpoint(). It is undocumented. */ -static int arParseCommand( - char **azArg, /* Array of arguments passed to dot command */ - int nArg, /* Number of entries in azArg[] */ - ArCommand *pAr /* Populate this object */ -){ - struct ArSwitch { - const char *zLong; - char cShort; - u8 eSwitch; - u8 bArg; - } aSwitch[] = { - { "create", 'c', AR_CMD_CREATE, 0 }, - { "extract", 'x', AR_CMD_EXTRACT, 0 }, - { "insert", 'i', AR_CMD_INSERT, 0 }, - { "list", 't', AR_CMD_LIST, 0 }, - { "remove", 'r', AR_CMD_REMOVE, 0 }, - { "update", 'u', AR_CMD_UPDATE, 0 }, - { "help", 'h', AR_CMD_HELP, 0 }, - { "verbose", 'v', AR_SWITCH_VERBOSE, 0 }, - { "file", 'f', AR_SWITCH_FILE, 1 }, - { "append", 'a', AR_SWITCH_APPEND, 1 }, - { "directory", 'C', AR_SWITCH_DIRECTORY, 1 }, - { "dryrun", 'n', AR_SWITCH_DRYRUN, 0 }, - { "glob", 'g', AR_SWITCH_GLOB, 0 }, - }; - int nSwitch = sizeof(aSwitch) / sizeof(struct ArSwitch); - struct ArSwitch *pEnd = &aSwitch[nSwitch]; +COLLECT_HELP_TEXT[ + ",breakpoint calls test_breakpoint(). (a debugging aid)", +]; +DISPATCHABLE_COMMAND( breakpoint 3 1 1 ){ + test_breakpoint(); + return DCR_Ok; +} - if( nArg<=1 ){ - utf8_printf(stderr, "Wrong number of arguments. Usage:\n"); - return arUsage(stderr); +CONDITION_COMMAND(check !defined(SQLITE_SHELL_FIDDLE)); +CONDITION_COMMAND(clone !defined(SQLITE_SHELL_FIDDLE)); +/***************** + * The .changes, .check, .clone and .connection commands + */ +COLLECT_HELP_TEXT[ + ".changes on|off Show number of rows changed by SQL", + ",check GLOB Fail if output since .testcase does not match", + ".clone NEWDB Clone data into NEWDB from the existing database", + ".connection [close] [#] Open or close an auxiliary database connection", +]; +DISPATCHABLE_COMMAND( changes 3 2 2 ){ + setOrClearFlag(p, SHFLG_CountChanges, azArg[1]); + return DCR_Ok; +} +DISPATCHABLE_COMMAND( check 3 0 0 ){ + /* Cancel output redirection, if it is currently set (by .testcase) + ** Then read the content of the testcase-out.txt file and compare against + ** azArg[1]. If there are differences, report an error and exit. + */ + char *zRes = 0; + DotCmdRC rv = DCR_Ok; + output_reset(ISS(p)); + if( nArg!=2 ){ + return DCR_ArgWrong; + }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){ + *pzErr = smprintf("Error: cannot read 'testcase-out.txt'\n"); + rv = DCR_Return; + }else if( testcase_glob(azArg[1],zRes)==0 ){ + *pzErr = + smprintf("testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n", + ISS(p)->zTestcase, azArg[1], zRes); + rv = DCR_Error; }else{ - char *z = azArg[1]; - if( z[0]!='-' ){ - /* Traditional style [tar] invocation */ - int i; - int iArg = 2; - for(i=0; z[i]; i++){ - const char *zArg = 0; - struct ArSwitch *pOpt; - for(pOpt=&aSwitch[0]; pOptcShort ) break; - } - if( pOpt==pEnd ){ - return arErrorMsg(pAr, "unrecognized option: %c", z[i]); - } - if( pOpt->bArg ){ - if( iArg>=nArg ){ - return arErrorMsg(pAr, "option requires an argument: %c",z[i]); - } - zArg = azArg[iArg++]; - } - if( arProcessSwitch(pAr, pOpt->eSwitch, zArg) ) return SQLITE_ERROR; - } - pAr->nArg = nArg-iArg; - if( pAr->nArg>0 ){ - pAr->azArg = &azArg[iArg]; - } - }else{ - /* Non-traditional invocation */ - int iArg; - for(iArg=1; iArgazArg = &azArg[iArg]; - pAr->nArg = nArg-iArg; - break; - } - n = strlen30(z); - - if( z[1]!='-' ){ - int i; - /* One or more short options */ - for(i=1; icShort ) break; - } - if( pOpt==pEnd ){ - return arErrorMsg(pAr, "unrecognized option: %c", z[i]); - } - if( pOpt->bArg ){ - if( i<(n-1) ){ - zArg = &z[i+1]; - i = n; - }else{ - if( iArg>=(nArg-1) ){ - return arErrorMsg(pAr, "option requires an argument: %c", - z[i]); - } - zArg = azArg[++iArg]; - } - } - if( arProcessSwitch(pAr, pOpt->eSwitch, zArg) ) return SQLITE_ERROR; - } - }else if( z[2]=='\0' ){ - /* A -- option, indicating that all remaining command line words - ** are command arguments. */ - pAr->azArg = &azArg[iArg+1]; - pAr->nArg = nArg-iArg-1; - break; - }else{ - /* A long option */ - const char *zArg = 0; /* Argument for option, if any */ - struct ArSwitch *pMatch = 0; /* Matching option */ - struct ArSwitch *pOpt; /* Iterator */ - for(pOpt=&aSwitch[0]; pOptzLong; - if( (n-2)<=strlen30(zLong) && 0==memcmp(&z[2], zLong, n-2) ){ - if( pMatch ){ - return arErrorMsg(pAr, "ambiguous option: %s",z); - }else{ - pMatch = pOpt; - } - } - } + utf8_printf(STD_OUT, "testcase-%s ok\n", ISS(p)->zTestcase); + ISS(p)->nCheck++; + } + sqlite3_free(zRes); + return (zRes==0)? DCR_Abort : rv; +} +DISPATCHABLE_COMMAND( clone ? 2 2 ){ + if( ISS(p)->bSafeMode ) return DCR_AbortError; + tryToClone(p, azArg[1]); + return DCR_Ok; +} +DISPATCHABLE_COMMAND( connection ? 1 4 ){ + ShellInState *psi = ISS(p); + if( nArg==1 ){ + /* List available connections */ + int i; + for(i=0; iaAuxDb); i++){ + const char *zFile = psi->aAuxDb[i].zDbFilename; + if( psi->aAuxDb[i].db==0 && psi->pAuxDb!=&psi->aAuxDb[i] ){ + zFile = "(not open)"; + }else if( zFile==0 ){ + zFile = "(memory)"; + }else if( zFile[0]==0 ){ + zFile = "(temporary-file)"; + } + if( psi->pAuxDb == &psi->aAuxDb[i] ){ + utf8_printf(STD_OUT, "ACTIVE %d: %s\n", i, zFile); + }else if( psi->aAuxDb[i].db!=0 ){ + utf8_printf(STD_OUT, " %d: %s\n", i, zFile); + } + } + }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){ + int i = azArg[1][0] - '0'; + if( psi->pAuxDb != &psi->aAuxDb[i] && i>=0 && iaAuxDb) ){ + psi->pAuxDb->db = DBX(p); + psi->pAuxDb = &psi->aAuxDb[i]; +#if SHELL_DYNAMIC_EXTENSION + if( DBX(p)!=0 ) notify_subscribers(psi, NK_DbUserVanishing, DBX(p)); +#endif + globalDb = DBX(p) = psi->pAuxDb->db; +#if SHELL_DYNAMIC_EXTENSION + if( DBX(p)!=0 ) notify_subscribers(psi, NK_DbUserAppeared, DBX(p)); +#endif + psi->pAuxDb->db = 0; + } + }else if( nArg==3 && cli_strcmp(azArg[1], "close")==0 + && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){ + int i = azArg[2][0] - '0'; + if( i<0 || i>=ArraySize(psi->aAuxDb) ){ + /* No-op */ + }else if( psi->pAuxDb == &psi->aAuxDb[i] ){ + raw_printf(STD_ERR, "cannot close the active database connection\n"); + return DCR_Error; + }else if( psi->aAuxDb[i].db ){ + session_close_all(psi, i); + close_db(psi->aAuxDb[i].db); + psi->aAuxDb[i].db = 0; + } + }else{ + return DCR_ArgWrong; + } + return DCR_Ok; +} - if( pMatch==0 ){ - return arErrorMsg(pAr, "unrecognized option: %s", z); - } - if( pMatch->bArg ){ - if( iArg>=(nArg-1) ){ - return arErrorMsg(pAr, "option requires an argument: %s", z); - } - zArg = azArg[++iArg]; - } - if( arProcessSwitch(pAr, pMatch->eSwitch, zArg) ) return SQLITE_ERROR; - } - } +CONDITION_COMMAND(dbinfo SQLITE_SHELL_HAVE_RECOVER); +/***************** + * 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 = 0; + sqlite3 *db; + int i; + open_db(p, 0); + db = DBX(p); + rc = s3_prepare_v2_noom(db, "PRAGMA database_list", -1, &pStmt, 0); + stmt_holder(pStmt); + if( rc || pStmt==0 ){ + *pzErr = smprintf("%s\n", sqlite3_errmsg(db)); + rc = 1; + }else{ + while( s3_step_noom(pStmt)==SQLITE_ROW ){ + int eTxn, bRdonly; + const char *zSchema = (const char *)sqlite3_column_text(pStmt,1); + const char *zFile = (const char*)sqlite3_column_text(pStmt,2); + if( zSchema==0 || zFile==0 ) continue; + eTxn = sqlite3_txn_state(db, zSchema); + bRdonly = sqlite3_db_readonly(db, zSchema); + utf8_printf(ISS(p)->out, "%s: %s %s%s\n", + zSchema, + zFile[0] ? zFile : "\"\"", + bRdonly ? "r/o" : "r/w", + eTxn==SQLITE_TXN_NONE ? "" : + eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn"); + } + } + release_holder(); + return DCR_Ok|(rc!=0); +} +DISPATCHABLE_COMMAND( dbconfig 3 1 3 ){ + static const struct DbConfigChoices { + const char *zName; + int op; + } aDbConfig[] = { + { "defensive", SQLITE_DBCONFIG_DEFENSIVE }, + { "dqs_ddl", SQLITE_DBCONFIG_DQS_DDL }, + { "dqs_dml", SQLITE_DBCONFIG_DQS_DML }, + { "enable_fkey", SQLITE_DBCONFIG_ENABLE_FKEY }, + { "enable_qpsg", SQLITE_DBCONFIG_ENABLE_QPSG }, + { "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER }, + { "enable_view", SQLITE_DBCONFIG_ENABLE_VIEW }, + { "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER }, + { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE }, + { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT }, + { "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION }, + { "no_ckpt_on_close", SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE }, + { "reset_database", SQLITE_DBCONFIG_RESET_DATABASE }, + { "reverse_scanorder", SQLITE_DBCONFIG_REVERSE_SCANORDER }, + { "stmt_scanstatus", SQLITE_DBCONFIG_STMT_SCANSTATUS }, + { "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 && cli_strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue; + if( nArg>=3 ){ + sqlite3_db_config(DBX(p), aDbConfig[ii].op, booleanValue(azArg[2]), 0); } + sqlite3_db_config(DBX(p), aDbConfig[ii].op, -1, &v); + utf8_printf(ISS(p)->out, "%19s %s\n", + aDbConfig[ii].zName, v ? "on" : "off"); + if( nArg>1 ) break; } - if( pAr->eCmd==0 ){ - utf8_printf(stderr, "Required argument missing. Usage:\n"); - return arUsage(stderr); + if( nArg>1 && ii==ArraySize(aDbConfig) ){ + *pzErr = smprintf("Error: unknown dbconfig \"%s\"\n" + "Enter \".dbconfig\" with no arguments for a list\n", + azArg[1]); + return DCR_ArgWrong; } - return SQLITE_OK; + return DCR_Ok; +} +DISPATCHABLE_COMMAND( dbinfo 3 1 2 ){ + return shell_dbinfo_command(p, nArg, azArg); } -/* -** This function assumes that all arguments within the ArCommand.azArg[] -** array refer to archive members, as for the --extract, --list or --remove -** commands. It checks that each of them are "present". If any specified -** file is not present in the archive, an error is printed to stderr and an -** error code returned. Otherwise, if all specified arguments are present -** in the archive, SQLITE_OK is returned. Here, "present" means either an -** exact equality when pAr->bGlob is false or a "name GLOB pattern" match -** when pAr->bGlob is true. -** -** This function strips any trailing '/' characters from each argument. -** This is consistent with the way the [tar] command seems to work on -** Linux. -*/ -static int arCheckEntries(ArCommand *pAr){ - int rc = SQLITE_OK; - if( pAr->nArg ){ - int i, j; - sqlite3_stmt *pTest = 0; - const char *zSel = (pAr->bGlob) - ? "SELECT name FROM %s WHERE glob($name,name)" - : "SELECT name FROM %s WHERE name=$name"; - - shellPreparePrintf(pAr->db, &rc, &pTest, zSel, pAr->zSrcTable); - j = sqlite3_bind_parameter_index(pTest, "$name"); - for(i=0; inArg && rc==SQLITE_OK; i++){ - char *z = pAr->azArg[i]; - int n = strlen30(z); - int bOk = 0; - while( n>0 && z[n-1]=='/' ) n--; - z[n] = '\0'; - sqlite3_bind_text(pTest, j, z, -1, SQLITE_STATIC); - if( SQLITE_ROW==sqlite3_step(pTest) ){ - bOk = 1; +/***************** + * The .dump, .echo and .eqp commands + */ +COLLECT_HELP_TEXT[ + ".dump ?OBJECTS? Render database content as SQL", + " Options:", + " --data-only Output only INSERT statements", + " --newlines Allow unescaped newline characters in output", + " --nosys Omit system tables (ex: \"sqlite_stat1\")", + " --preserve-rowids Include ROWID values in the output", + " --schema SCHEMA Dump table(s) from given SCHEMA", + " OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump", + " Additional LIKE patterns can be given in subsequent arguments", + ".echo on|off Turn command echo on or off", + ".eqp on|off|full|... Enable or disable automatic EXPLAIN QUERY PLAN", + " Other Modes:", +#ifdef SQLITE_DEBUG + " test Show raw EXPLAIN QUERY PLAN output", + " trace Like \"full\" but enable \"PRAGMA vdbe_trace\"", +#endif + " trigger Like \"full\" but also show trigger bytecode", +]; +DISPATCHABLE_COMMAND( dump ? 1 2 ){ + ShellInState *psi = ISS(p); + char *zLike = 0; + char *zSchema = "main"; + char *zSql; + int i; + int savedShowHeader = psi->showHeader; + int savedShellFlags = psi->shellFlgs; + sstr_ptr_holder(&zLike); + ShellClearFlag(p, + SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo + |SHFLG_DumpDataOnly|SHFLG_DumpNoSys); + for(i=1; ibGlob is false and GLOB match when pAr->bGlob is true. -*/ -static void arWhereClause( - int *pRc, - ArCommand *pAr, - char **pzWhere /* OUT: New WHERE clause */ -){ - char *zWhere = 0; - const char *zSameOp = (pAr->bGlob)? "GLOB" : "="; - if( *pRc==SQLITE_OK ){ - if( pAr->nArg==0 ){ - zWhere = sqlite3_mprintf("1"); + open_db(p, 0); + + if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){ + /* When playing back a "dump", the content might appear in an order + ** which causes immediate foreign key constraints to be violated. + ** So disable foreign-key constraint enforcement to prevent problems. */ + raw_printf(psi->out, "PRAGMA foreign_keys=OFF;\n"); + raw_printf(psi->out, "BEGIN TRANSACTION;\n"); + } + psi->writableSchema = 0; + psi->showHeader = 0; + /* Set writable_schema=ON since doing so forces SQLite to initialize + ** as much of the schema as it can even if the sqlite_schema table is + ** corrupt. */ + sqlite3_exec(DBX(p), "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0); + psi->nErr = 0; + if( zLike==0 ) zLike = smprintf("true"); + zSql = smprintf("SELECT name, type, sql FROM %w.sqlite_schema AS o " + "WHERE (%s) AND type=='table' AND sql NOT NULL" + " ORDER BY tbl_name='sqlite_sequence', rowid", + zSchema, zLike); + shell_check_ooms(zSql); + sstr_ptr_holder(&zSql); + run_schema_dump_query(psi,zSql); + if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){ + sqlite3_free(zSql); + zSql = smprintf( + "SELECT sql FROM sqlite_schema AS o " + "WHERE (%s) AND sql NOT NULL" + " AND type IN ('index','trigger','view')", + zLike + ); + run_table_dump_query(psi, zSql); + } + release_holder(); /* zSql */ + if( psi->writableSchema ){ + raw_printf(psi->out, "PRAGMA writable_schema=OFF;\n"); + psi->writableSchema = 0; + } + sqlite3_exec(DBX(p), "PRAGMA writable_schema=OFF;", 0, 0, 0); + sqlite3_exec(DBX(p), "RELEASE dump;", 0, 0, 0); + if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){ + raw_printf(psi->out, psi->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n"); + } + psi->showHeader = savedShowHeader; + psi->shellFlgs = savedShellFlags; + release_holder(); /* zLike */ + + return DCR_Ok; +} +DISPATCHABLE_COMMAND( echo ? 2 2 ){ + setOrClearFlag(p, SHFLG_Echo, azArg[1]); + return DCR_Ok; +} +DISPATCHABLE_COMMAND( eqp ? 0 0 ){ + ShellInState *psi = ISS(p); + if( nArg==2 ){ + psi->autoEQPtest = 0; + if( psi->autoEQPtrace ){ + if( DBX(p) ) sqlite3_exec(DBX(p), "PRAGMA vdbe_trace=OFF;", 0, 0, 0); + psi->autoEQPtrace = 0; + } + if( cli_strcmp(azArg[1],"full")==0 ){ + psi->autoEQP = AUTOEQP_full; + }else if( cli_strcmp(azArg[1],"trigger")==0 ){ + psi->autoEQP = AUTOEQP_trigger; +#ifdef SQLITE_DEBUG + }else if( cli_strcmp(azArg[1],"test")==0 ){ + psi->autoEQP = AUTOEQP_on; + psi->autoEQPtest = 1; + }else if( cli_strcmp(azArg[1],"trace")==0 ){ + psi->autoEQP = AUTOEQP_full; + psi->autoEQPtrace = 1; + open_db(p, 0); + sqlite3_exec(DBX(p), "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0); + sqlite3_exec(DBX(p), "PRAGMA vdbe_trace=ON;", 0, 0, 0); +#endif }else{ - int i; - const char *zSep = ""; - for(i=0; inArg; i++){ - const char *z = pAr->azArg[i]; - zWhere = sqlite3_mprintf( - "%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'", - zWhere, zSep, zSameOp, z, strlen30(z)+1, zSameOp, z - ); - if( zWhere==0 ){ - *pRc = SQLITE_NOMEM; - break; - } - zSep = " OR "; - } + psi->autoEQP = (u8)booleanValue(azArg[1]); } + }else{ + return DCR_ArgWrong; } - *pzWhere = zWhere; + return DCR_Ok; } -/* -** Implementation of .ar "lisT" command. -*/ -static int arListCommand(ArCommand *pAr){ - const char *zSql = "SELECT %s FROM %s WHERE %s"; - const char *azCols[] = { - "name", - "lsmode(mode), sz, datetime(mtime, 'unixepoch'), name" - }; - - char *zWhere = 0; - sqlite3_stmt *pSql = 0; +CONDITION_COMMAND(cease !defined(SQLITE_SHELL_FIDDLE)); +CONDITION_COMMAND(exit !defined(SQLITE_SHELL_FIDDLE)); +CONDITION_COMMAND(quit !defined(SQLITE_SHELL_FIDDLE)); +/***************** + * The .cease, .exit and .quit commands + * These are together so that their differing effects are apparent. + */ +CONDITION_COMMAND(cease defined(SHELL_CEASE)); +COLLECT_HELP_TEXT[ + ".cease ?CODE? Cease shell operation, with optional return code", + " Return code defaults to 0, otherwise is limited to non-signal values", + ".exit ?CODE? Exit shell program, maybe with return-code CODE", + " Exit immediately if CODE != 0, else functions as \"quit this input\"", + ".quit Stop interpreting input stream, done if primary.", +]; +DISPATCHABLE_COMMAND( cease 4 1 2 ){ + /* .cease effects an exit, always. Only the exit code is variable. */ + int rc = 0; + if( nArg>1 ){ + rc = (int)integerValue(azArg[1]); + if( rc>0x7f ) rc = 0x7f; + } + p->shellAbruptExit = 0x100|rc; + return DCR_Exit; +} +DISPATCHABLE_COMMAND( exit 3 1 0 ){ + /* .exit acts like .quit with no argument or a zero argument, + * only returning. With a non-zero argument, it effects an exit. */ int rc; + if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ){ + rc &= 0xff; /* Mimic effect of legacy call to exit(). */ +#ifdef SHELL_EXIT_EXITS_PROCESS ++ terminate_actions(); + exit(rc); +#else + p->shellAbruptExit = 0x100|rc; +#endif + } + return DCR_Return; +} +DISPATCHABLE_COMMAND( quit 1 1 0 ){ + /* .quit would be more aptly named .return, as it does nothing more. */ + return DCR_Return; +} - rc = arCheckEntries(pAr); - arWhereClause(&rc, pAr, &zWhere); +/***************** + * The .expert and .explain commands + */ +CONDITION_COMMAND( expert !defined(SQLITE_OMIT_VIRTUALTABLE) ); +COLLECT_HELP_TEXT[ + ".expert Suggest indexes for queries", + ".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto", +]; +DISPATCHABLE_COMMAND( expert ? 1 1 ){ + ShellInState *psi = ISS(p); + int rv = DCR_Ok; + char *zErr = 0; + int i; + int iSample = 0; - shellPreparePrintf(pAr->db, &rc, &pSql, zSql, azCols[pAr->bVerbose], - pAr->zSrcTable, zWhere); - if( pAr->bDryRun ){ - utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql)); - }else{ - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ - if( pAr->bVerbose ){ - utf8_printf(pAr->p->out, "%s % 10d %s %s\n", - sqlite3_column_text(pSql, 0), - sqlite3_column_int(pSql, 1), - sqlite3_column_text(pSql, 2), - sqlite3_column_text(pSql, 3) - ); + if( psi->bSafeMode ) return DCR_AbortError; + assert( psi->expert.pExpert==0 ); + memset(&psi->expert, 0, sizeof(ExpertInfo)); + + open_db(p, 0); + + for(i=1; i=2 && 0==cli_strncmp(z, "-verbose", n) ){ + psi->expert.bVerbose = 1; + } + else if( n>=2 && 0==cli_strncmp(z, "-sample", n) ){ + if( i==(nArg-1) ){ + return DCR_Unpaired|i; }else{ - utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0)); + iSample = (int)integerValue(azArg[++i]); + if( iSample<0 || iSample>100 ){ + *pzErr = smprintf("value out of range: %s\n", azArg[i]); + return DCR_ArgWrong|i; + } } } + else{ + return DCR_Unknown|i; + } } - shellFinalize(&rc, pSql); - sqlite3_free(zWhere); - return rc; + + psi->expert.pExpert = sqlite3_expert_new(DBI(psi), &zErr); + if( psi->expert.pExpert==0 ){ + *pzErr = smprintf("sqlite3_expert_new: %s\n", + zErr ? zErr : "out of memory"); + return DCR_Error; + }else{ + sqlite3_expert_config(psi->expert.pExpert, EXPERT_CONFIG_SAMPLE, iSample); + } + + return DCR_Ok; +} + +DISPATCHABLE_COMMAND( explain ? 1 2 ){ + /* The ".explain" command is automatic now. It is largely + ** pointless, retained purely for backwards compatibility */ + ShellInState *psi = ISS(p); + int val = 1; + if( nArg>1 ){ + if( cli_strcmp(azArg[1],"auto")==0 ){ + val = 99; + }else{ + val = booleanValue(azArg[1]); + } + } + if( val==1 && psi->mode!=MODE_Explain ){ + psi->normalMode = psi->mode; + psi->mode = MODE_Explain; + psi->autoExplain = 0; + }else if( val==0 ){ + if( psi->mode==MODE_Explain ) psi->mode = psi->normalMode; + psi->autoExplain = 0; + }else if( val==99 ){ + if( psi->mode==MODE_Explain ) psi->mode = psi->normalMode; + psi->autoExplain = 1; + } + return DCR_Ok; } +/***************** + * The .excel, .once and .output commands + * These share much implementation, so they stick together. + */ +CONDITION_COMMAND(excel !defined(SQLITE_SHELL_FIDDLE)); +CONDITION_COMMAND(once !defined(SQLITE_SHELL_FIDDLE)); +CONDITION_COMMAND(output !defined(SQLITE_SHELL_FIDDLE)); -/* -** Implementation of .ar "Remove" command. -*/ -static int arRemoveCommand(ArCommand *pAr){ +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\")", +]; +#ifndef SQLITE_SHELL_FIDDLE +/* Shared implementation of .excel, .once and .output */ +static DotCmdRC outputRedirs(char *azArg[], int nArg, + ShellInState *psi, char **pzErr, + int bOnce, int eMode){ + /* bOnce => 0: .output, 1: .once, 2: .excel */ + /* eMode => 'x' for excel, else 0 */ int rc = 0; - char *zSql = 0; - char *zWhere = 0; + char *zFile = 0; + u8 bTxtMode = 0; + u8 bPutBOM = 0; + int i; + static unsigned const char zBOM[4] = {0xef,0xbb,0xbf,0}; - if( pAr->nArg ){ - /* Verify that args actually exist within the archive before proceeding. - ** And formulate a WHERE clause to match them. */ - rc = arCheckEntries(pAr); - arWhereClause(&rc, pAr, &zWhere); - } - if( rc==SQLITE_OK ){ - zSql = sqlite3_mprintf("DELETE FROM %s WHERE %s;", - pAr->zSrcTable, zWhere); - if( pAr->bDryRun ){ - utf8_printf(pAr->p->out, "%s\n", zSql); - }else{ - char *zErr = 0; - rc = sqlite3_exec(pAr->db, "SAVEPOINT ar;", 0, 0, 0); - if( rc==SQLITE_OK ){ - rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr); - if( rc!=SQLITE_OK ){ - sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0); - }else{ - rc = sqlite3_exec(pAr->db, "RELEASE ar;", 0, 0, 0); + sstr_ptr_holder(&zFile); + if( psi->bSafeMode ) return DCR_AbortError; + for(i=1; ioutCount = 2; + }else{ + psi->outCount = 0; + } + output_reset(psi); +#ifndef SQLITE_NOHAVE_SYSTEM + if( eMode=='e' || eMode=='x' ){ + psi->doXdgOpen = 1; + outputModePush(psi); + if( eMode=='x' ){ + /* spreadsheet mode. Output as CSV. */ + newTempFile(psi, "csv"); + psi->shellFlgs &= ~SHFLG_Echo; + psi->mode = MODE_Csv; + sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator, SEP_Comma); + sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, SEP_CrLf); + }else{ + /* text editor mode */ + newTempFile(psi, "txt"); + bTxtMode = 1; + } + sqlite3_free(zFile); + zFile = smprintf("%s", psi->zTempFile); + } +#endif /* SQLITE_NOHAVE_SYSTEM */ + shell_check_ooms(zFile); + if( zFile[0]=='|' ){ +#ifdef SQLITE_OMIT_POPEN + *pzErr = smprintf("pipes are not supported in this OS\n"); + rc = 1; + psi->out = STD_OUT; +#else + psi->out = popen(zFile + 1, "w"); + if( psi->out==0 ){ + *pzErr = smprintf("cannot open pipe \"%s\"\n", zFile + 1); + psi->out = STD_OUT; + rc = 1; + }else{ + if( bPutBOM ) fwrite(zBOM, 1, 3, psi->out); + sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile); + } +#endif + }else{ + psi->out = output_file_open(zFile, bTxtMode); + if( psi->out==0 ){ + if( cli_strcmp(zFile,"off")!=0 ){ + *pzErr = smprintf("cannot write to \"%s\"\n", zFile); } + psi->out = STD_OUT; + rc = 1; + } else { + if( bPutBOM ) fwrite(zBOM, 1, 3, psi->out); + sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile); } } - sqlite3_free(zWhere); - sqlite3_free(zSql); - return rc; + release_holder(); + return DCR_Ok|rc; } +#endif /* !defined(SQLITE_SHELL_FIDDLE)*/ -/* -** Implementation of .ar "eXtract" command. -*/ -static int arExtractCommand(ArCommand *pAr){ - const char *zSql1 = - "SELECT " - " ($dir || name)," - " writefile(($dir || name), %s, mode, mtime) " - "FROM %s WHERE (%s) AND (data IS NULL OR $dirOnly = 0)" - " AND name NOT GLOB '*..[/\\]*'"; +DISPATCHABLE_COMMAND( excel ? 1 2 ){ + return outputRedirs(azArg, nArg, ISS(p), pzErr, 2, 'x'); +} +DISPATCHABLE_COMMAND( once ? 1 6 ){ + return outputRedirs(azArg, nArg, ISS(p), pzErr, 1, 0); +} +DISPATCHABLE_COMMAND( output ? 1 6 ){ + return outputRedirs(azArg, nArg, ISS(p), pzErr, 0, 0); +} - const char *azExtraArg[] = { - "sqlar_uncompress(data, sz)", - "data" - }; - sqlite3_stmt *pSql = 0; - int rc = SQLITE_OK; - char *zDir = 0; - char *zWhere = 0; - int i, j; +/***************** + * The .filectrl and fullschema commands + */ +COLLECT_HELP_TEXT[ + ".filectrl CMD ... Run various sqlite3_file_control() operations", + " --schema SCHEMA Use SCHEMA instead of \"main\"", + " --help Show CMD details", + ".fullschema ?--indent? Show schema and the content of sqlite_stat tables", +]; +DISPATCHABLE_COMMAND( filectrl ? 2 0 ){ + static const struct { + const char *zCtrlName; /* Name of a test-control option */ + int ctrlCode; /* Integer code for that option */ + const char *zUsage; /* Usage notes */ + } aCtrl[] = { + { "chunk_size", SQLITE_FCNTL_CHUNK_SIZE, "SIZE" }, + { "data_version", SQLITE_FCNTL_DATA_VERSION, "" }, + { "has_moved", SQLITE_FCNTL_HAS_MOVED, "" }, + { "lock_timeout", SQLITE_FCNTL_LOCK_TIMEOUT, "MILLISEC" }, + { "persist_wal", SQLITE_FCNTL_PERSIST_WAL, "[BOOLEAN]" }, + /* { "pragma", SQLITE_FCNTL_PRAGMA, "NAME ARG" },*/ + { "psow", SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]" }, + { "reserve_bytes", SQLITE_FCNTL_RESERVE_BYTES, "[N]" }, + { "size_limit", SQLITE_FCNTL_SIZE_LIMIT, "[LIMIT]" }, + { "tempfilename", SQLITE_FCNTL_TEMPFILENAME, "" }, + /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY, "COUNT DELAY" },*/ + }; + ShellInState *psi = ISS(p); + int filectrl = -1; + int iCtrl = -1; + sqlite3_int64 iRes = 0; /* Integer result to display if rc2==1 */ + int isOk = 0; /* 0: usage 1: %lld 2: no-result */ + int n2, i; + const char *zCmd = 0; + const char *zSchema = 0; - /* If arguments are specified, check that they actually exist within - ** the archive before proceeding. And formulate a WHERE clause to - ** match them. */ - rc = arCheckEntries(pAr); - arWhereClause(&rc, pAr, &zWhere); + open_db(p, 0); + zCmd = nArg>=2 ? azArg[1] : "help"; - if( rc==SQLITE_OK ){ - if( pAr->zDir ){ - zDir = sqlite3_mprintf("%s/", pAr->zDir); - }else{ - zDir = sqlite3_mprintf(""); - } - if( zDir==0 ) rc = SQLITE_NOMEM; + if( zCmd[0]=='-' + && (cli_strcmp(zCmd,"--schema")==0 || cli_strcmp(zCmd,"-schema")==0) + && nArg>=4 + ){ + zSchema = azArg[2]; + for(i=3; idb, &rc, &pSql, zSql1, - azExtraArg[pAr->bZip], pAr->zSrcTable, zWhere - ); + /* The argument can optionally begin with "-" or "--" */ + if( zCmd[0]=='-' && zCmd[1] ){ + zCmd++; + if( zCmd[0]=='-' && zCmd[1] ) zCmd++; + } - if( rc==SQLITE_OK ){ - j = sqlite3_bind_parameter_index(pSql, "$dir"); - sqlite3_bind_text(pSql, j, zDir, -1, SQLITE_STATIC); + /* --help lists all file-controls */ + if( cli_strcmp(zCmd,"help")==0 ){ + utf8_printf(psi->out, "Available file-controls:\n"); + for(i=0; iout, " .filectrl %s %s\n", + aCtrl[i].zCtrlName, aCtrl[i].zUsage); + } + return DCR_Error; + } - /* Run the SELECT statement twice. The first time, writefile() is called - ** for all archive members that should be extracted. The second time, - ** only for the directories. This is because the timestamps for - ** extracted directories must be reset after they are populated (as - ** populating them changes the timestamp). */ - for(i=0; i<2; i++){ - j = sqlite3_bind_parameter_index(pSql, "$dirOnly"); - sqlite3_bind_int(pSql, j, i); - if( pAr->bDryRun ){ - utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql)); + /* Convert filectrl text option to value. Allow any + ** unique prefix of the option name, or a numerical value. */ + n2 = strlen30(zCmd); + for(i=0; ibVerbose ){ - utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0)); - } - } + *pzErr = smprintf("ambiguous file-control: \"%s\"\n" + "Use \".filectrl --help\" for help\n", zCmd); + return DCR_ArgWrong; } - shellReset(&rc, pSql); } - shellFinalize(&rc, pSql); } + if( filectrl<0 ){ + *pzErr = smprintf("unknown file-control: %s\n" + "Use \".filectrl --help\" for help\n", zCmd); + return DCR_ArgWrong; + }else{ + switch(filectrl){ + case SQLITE_FCNTL_SIZE_LIMIT: { + if( nArg!=2 && nArg!=3 ) break; + iRes = nArg==3 ? integerValue(azArg[2]) : -1; + sqlite3_file_control(DBX(p), 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(DBX(p), 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(DBX(p), 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(DBX(p), zSchema, filectrl, &x); + iRes = x; + isOk = 1; + break; + } + case SQLITE_FCNTL_TEMPFILENAME: { + char *z = 0; + if( nArg!=2 ) break; + sqlite3_file_control(DBX(p), zSchema, filectrl, &z); + if( z ){ + utf8_printf(psi->out, "%s\n", z); + sqlite3_free(z); + } + isOk = 2; + break; + } + case SQLITE_FCNTL_RESERVE_BYTES: { + int x; + if( nArg>=3 ){ + x = atoi(azArg[2]); + sqlite3_file_control(DBX(p), zSchema, filectrl, &x); + } + x = -1; + sqlite3_file_control(DBX(p), zSchema, filectrl, &x); + utf8_printf(psi->out,"%d\n", x); + isOk = 2; + break; + } + } + } + if( isOk==0 && iCtrl>=0 ){ + *pzErr = smprintf("Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage); + return DCR_CmdErred; + }else if( isOk==1 ){ + char zBuf[21]; + sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes); + raw_printf(psi->out, "%s\n", zBuf); + } + return DCR_Ok; +} - sqlite3_free(zDir); - sqlite3_free(zWhere); - return rc; +static void modePopper(ShellInState *psi){ + outputModePop(psi); } -/* -** Run the SQL statement in zSql. Or if doing a --dryrun, merely print it out. -*/ -static int arExecSql(ArCommand *pAr, const char *zSql){ +DISPATCHABLE_COMMAND( fullschema ? 1 2 ){ int rc; - if( pAr->bDryRun ){ - utf8_printf(pAr->p->out, "%s\n", zSql); - rc = SQLITE_OK; + int doStats = 0; + ShellInState *psi = ISS(p); + u8 useMode = MODE_Semi; + AnyResourceHolder arh = {psi, (GenericFreer)modePopper}; + + if( nArg==2 && optionMatch(azArg[1], "indent") ){ + useMode = MODE_Pretty; + nArg = 1; + } + if( nArg!=1 ){ + return DCR_TooMany|1; + } + outputModePush(psi); /* Can fail to return due to OOM. */ + any_ref_holder(&arh); + psi->showHeader = 0; + psi->cMode = psi->mode = useMode; + open_db(p, 0); + rc = s3_exec_noom(DBX(p), + "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, p, 0 + ); + if( rc==SQLITE_OK ){ + sqlite3_stmt *pStmt; + rc = s3_prepare_v2_noom(p->dbUser, + "SELECT rowid FROM sqlite_schema" + " WHERE name GLOB 'sqlite_stat[134]'", + -1, &pStmt, 0); + stmt_holder(pStmt); + doStats = s3_step_noom(pStmt)==SQLITE_ROW; + release_holder(); + } + if( doStats==0 ){ + raw_printf(psi->out, "/* No STAT tables available */\n"); }else{ - char *zErr = 0; - rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr); - if( zErr ){ - utf8_printf(stdout, "ERROR: %s\n", zErr); - sqlite3_free(zErr); + const char *zOldDestTable = p->zDestTable; + raw_printf(psi->out, "ANALYZE sqlite_schema;\n"); + psi->cMode = psi->mode = MODE_Insert; + p->zDestTable = "sqlite_stat1"; + shell_exec(p, "SELECT * FROM sqlite_stat1", 0); + p->zDestTable = "sqlite_stat4"; + shell_exec(p, "SELECT * FROM sqlite_stat4", 0); + raw_printf(psi->out, "ANALYZE sqlite_schema;\n"); + p->zDestTable = zOldDestTable; + } + release_holder(); /* Restore shell state */ + return rc > 0; +} + +/***************** + * The .headers command + */ +COLLECT_HELP_TEXT[ + ".headers on|off Turn display of headers on or off", +]; +DISPATCHABLE_COMMAND( headers 6 2 2 ){ + ISS(p)->showHeader = booleanValue(azArg[1]); + ISS(p)->shellFlgs |= SHFLG_HeaderSet; + return DCR_Ok; +} + +/***************** + * The .help command + */ + +/* This literal's value AND address are used for help's workings. */ +static const char *zHelpAll = "-all"; + +COLLECT_HELP_TEXT[ + ".help ?PATTERN?|?-all? Show help for PATTERN or everything, or summarize", + " Repeat -all to see undocumented commands", +]; +DISPATCHABLE_COMMAND( help 3 1 3 ){ + const char *zPat = 0; + FILE *out = ISS(p)->out; + if( nArg>1 ){ + char *z = azArg[1]; + if( (nArg==2 && azArg[1][0]=='0' && azArg[1][1]==0) + || (nArg==3 && cli_strcmp(z, zHelpAll)==0 + && cli_strcmp(azArg[2], zHelpAll)==0) ){ + /* Show the undocumented command help */ + zPat = zHelpAll; + }else if( cli_strcmp(z,"-a")==0 || optionMatch(z, "all") ){ + zPat = ""; + }else{ + zPat = z; } } - return rc; + if( showHelp(out, zPat, p)==0 && nArg>1 ){ + utf8_printf(out, "Nothing matches '%s'\n", azArg[1]); + } + /* Help pleas never fail! */ + return DCR_Ok; } +CONDITION_COMMAND(import !defined(SQLITE_SHELL_FIDDLE)); +/***************** + * The .import command + */ +COLLECT_HELP_TEXT[ + ".import FILE TABLE Import data from FILE into TABLE", + " Options:", + " --ascii Use \\037 and \\036 as column and row separators", + " --csv Use , and \\n as column and row separators", + " --skip N Skip the first N rows of input", + " --schema S Target table to be S.TABLE", + " -v \"Verbose\" - increase auxiliary output", + " Notes:", + " * If TABLE does not exist, it is created. The first row of input", + " determines the column names.", + " * If neither --csv or --ascii are used, the input mode is derived", + " from the \".mode\" output mode", + " * If FILE begins with \"|\" then it is a command that generates the", + " input text.", +]; +DISPATCHABLE_COMMAND( import ? 3 7 ){ + char *zTable = 0; /* Insert data into this table */ + char *zSchema = 0; /* within this schema (may default to "main") */ + char *zFile = 0; /* Name of file to extra content from */ + sqlite3_stmt *pStmt = NULL; /* A statement */ + int nCol; /* Number of columns in the table */ + int nByte; /* Number of bytes in an SQL string */ + int i, j; /* Loop counters */ + int needCommit; /* True to COMMIT or ROLLBACK at end */ + int nSep; /* Number of bytes in psi->colSeparator[] */ + char *zSql = 0; /* An SQL statement */ + char *zFullTabName = 0; /* Table name with schema if applicable */ + ImportCtx sCtx = {0}; /* Reader context */ + AnyResourceHolder arh = { &sCtx, (GenericFreer)import_cleanup }; + char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */ + int eVerbose = 0; /* Larger for more console output */ + int nSkip = 0; /* Initial lines to skip */ + int useOutputMode = 1; /* Use output mode to determine separators */ + FILE *out = ISS(p)->out; /* output stream */ + char *zCreate = 0; /* CREATE TABLE statement text */ + ShellInState *psi = ISS(p); + ResourceMark mark = holder_mark(); + int rc = 0; -/* -** Implementation of .ar "create", "insert", and "update" commands. -** -** create -> Create a new SQL archive -** insert -> Insert or reinsert all files listed -** update -> Insert files that have changed or that were not -** previously in the archive -** -** Create the "sqlar" table in the database if it does not already exist. -** Then add each file in the azFile[] array to the archive. Directories -** are added recursively. If argument bVerbose is non-zero, a message is -** printed on stdout for each file archived. -** -** The create command is the same as update, except that it drops -** any existing "sqlar" table before beginning. The "insert" command -** always overwrites every file named on the command-line, where as -** "update" only overwrites if the size or mtime or mode has changed. -*/ -static int arCreateOrUpdateCommand( - ArCommand *pAr, /* Command arguments and options */ - int bUpdate, /* true for a --create. */ - int bOnlyIfChanged /* Only update if file has changed */ -){ - const char *zCreate = - "CREATE TABLE IF NOT EXISTS sqlar(\n" - " name TEXT PRIMARY KEY, -- name of the file\n" - " mode INT, -- access permissions\n" - " mtime INT, -- last modification time\n" - " sz INT, -- original file size\n" - " data BLOB -- compressed content\n" - ")"; - const char *zDrop = "DROP TABLE IF EXISTS sqlar"; - const char *zInsertFmt[2] = { - "REPLACE INTO %s(name,mode,mtime,sz,data)\n" - " SELECT\n" - " %s,\n" - " mode,\n" - " mtime,\n" - " CASE substr(lsmode(mode),1,1)\n" - " WHEN '-' THEN length(data)\n" - " WHEN 'd' THEN 0\n" - " ELSE -1 END,\n" - " sqlar_compress(data)\n" - " FROM fsdir(%Q,%Q) AS disk\n" - " WHERE lsmode(mode) NOT LIKE '?%%'%s;" - , - "REPLACE INTO %s(name,mode,mtime,data)\n" - " SELECT\n" - " %s,\n" - " mode,\n" - " mtime,\n" - " data\n" - " FROM fsdir(%Q,%Q) AS disk\n" - " WHERE lsmode(mode) NOT LIKE '?%%'%s;" - }; - int i; /* For iterating through azFile[] */ - int rc; /* Return code */ - const char *zTab = 0; /* SQL table into which to insert */ - char *zSql; - char zTemp[50]; - char *zExists = 0; - - arExecSql(pAr, "PRAGMA page_size=512"); - rc = arExecSql(pAr, "SAVEPOINT ar;"); - if( rc!=SQLITE_OK ) return rc; - zTemp[0] = 0; - if( pAr->bZip ){ - /* Initialize the zipfile virtual table, if necessary */ - if( pAr->zFile ){ - sqlite3_uint64 r; - sqlite3_randomness(sizeof(r),&r); - sqlite3_snprintf(sizeof(zTemp),zTemp,"zip%016llx",r); - zTab = zTemp; - zSql = sqlite3_mprintf( - "CREATE VIRTUAL TABLE temp.%s USING zipfile(%Q)", - zTab, pAr->zFile - ); - rc = arExecSql(pAr, zSql); - sqlite3_free(zSql); + if(psi->bSafeMode) return DCR_AbortError; + memset(&sCtx, 0, sizeof(sCtx)); + if( psi->mode==MODE_Ascii ){ + xRead = ascii_read_one_field; + }else{ + xRead = csv_read_one_field; + } + for(i=1; icolSeparator); + if( nSep==0 ){ + zYap = "non-null column separator required for import"; + } + if( nSep>1 ){ + zYap = "multi-character or multi-byte column separators" + " not allowed for import"; + } + nSep = strlen30(psi->rowSeparator); + if( nSep==0 ){ + zYap = "non-null row separator required for import"; + } + if( zYap!=0 ){ + *pzErr = smprintf("%s\n", zYap); + return DCR_Error; + } + if( nSep==2 && psi->mode==MODE_Csv + && cli_strcmp(psi->rowSeparator,SEP_CrLf)==0 ){ + /* When importing CSV (only), if the row separator is set to the + ** default output row separator, change it to the default input + ** row separator. This avoids having to maintain different input + ** and output row separators. */ + sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, SEP_Row); + nSep = strlen30(psi->rowSeparator); + } + if( nSep>1 ){ + *pzErr + = smprintf("multi-character row separators not allowed for import\n"); + return DCR_Error; + } + sCtx.cColSep = (u8)psi->colSeparator[0]; + sCtx.cRowSep = (u8)psi->rowSeparator[0]; + } + sCtx.zFile = zFile; + sCtx.nLine = 1; + if( sCtx.zFile[0]=='|' ){ +#ifdef SQLITE_OMIT_POPEN + *pzErr = smprintf("pipes are not supported in this OS\n"); + return DCR_Error; +#else + sCtx.in = popen(sCtx.zFile+1, "r"); + sCtx.zFile = ""; + sCtx.xCloser = pclose; +#endif + }else{ + sCtx.in = fopen(sCtx.zFile, "rb"); + sCtx.xCloser = fclose; + } + if( sCtx.in==0 ){ + *pzErr = smprintf("cannot open \"%s\"\n", zFile); + return DCR_Error; + } + /* Here and below, resources must be freed before exit. */ + any_ref_holder(&arh); + sCtx.z = sqlite3_malloc64(120); + shell_check_ooms(sCtx.z); + if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){ + char zSep[2]; + zSep[1] = 0; + zSep[0] = sCtx.cColSep; + utf8_printf(out, "Column separator "); + output_c_string(out, zSep); + utf8_printf(out, ", row separator "); + zSep[0] = sCtx.cRowSep; + output_c_string(out, zSep); + utf8_printf(out, "\n"); + } + while( (nSkip--)>0 ){ + while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){} + } + if( zSchema!=0 ){ + zFullTabName = smprintf("\"%w\".\"%w\"", zSchema, zTable); + }else{ + zFullTabName = smprintf("\"%w\"", zTable); + } + shell_check_ooms(zFullTabName); + sstr_ptr_holder(&zFullTabName); + zSql = smprintf("SELECT * FROM %s", zFullTabName); + shell_check_ooms(zSql); + sstr_ptr_holder(&zSql); + nByte = strlen30(zSql); + rc = s3_prepare_v2_noom(DBX(p), zSql, -1, &pStmt, 0); + stmt_ptr_holder(&pStmt); + import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ + if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(DBX(p)))==0 ){ + zCreate = smprintf("CREATE TABLE %s", zFullTabName); + sqlite3 *dbCols = 0; + char *zRenames = 0; + char *zColDefs; + shell_check_ooms(zCreate); + sstr_ptr_holder(&zCreate); /* +1 */ + sstr_ptr_holder(&zRenames); /* +2 */ + sstr_ptr_holder(&zColDefs); /* +3 */ + conn_ptr_holder(&dbCols); + while( xRead(&sCtx) ){ + zAutoColumn(sCtx.z, &dbCols, 0); + if( sCtx.cTerm!=sCtx.cColSep ) break; + } + zColDefs = zAutoColumn(0, &dbCols, &zRenames); + if( zRenames!=0 ){ + FILE *fh = INSOURCE_IS_INTERACTIVE(psi->pInSource)? out : STD_ERR; + utf8_printf(fh, "Columns renamed during .import %s due to duplicates:\n" + "%s\n", sCtx.zFile, zRenames); + } + assert(dbCols==0); + drop_holder(); /* dbCols */ + if( zColDefs==0 ){ + *pzErr = smprintf("%s: empty file\n", sCtx.zFile); + import_fail: /* entry from outer blocks */ + RESOURCE_FREE(mark); + return DCR_Error; + } + zCreate = smprintf("%z%z\n", zCreate, zColDefs); + zColDefs = 0; + shell_check_ooms(zCreate); + if( eVerbose>=1 ){ + utf8_printf(out, "%s\n", zCreate); + } + rc = s3_exec_noom(DBX(p), zCreate, 0, 0, 0); + if( rc ){ + *pzErr = smprintf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(DBX(p))); + goto import_fail; + } + rc = s3_prepare_v2_noom(DBX(p), zSql, -1, &pStmt, 0); + } + if( rc ){ + *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p))); + goto import_fail; + } + nCol = sqlite3_column_count(pStmt); + sqlite3_finalize(pStmt); + pStmt = 0; + if( nCol==0 ) return DCR_Ok; /* no columns, no error */ + sqlite3_free(zSql); + zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 ); + shell_check_ooms(zSql); + sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName); + j = strlen30(zSql); + for(i=1; i=2 ){ + utf8_printf(psi->out, "Insert using: %s\n", zSql); + } + rc = s3_prepare_v2_noom(DBX(p), zSql, -1, &pStmt, 0); + if( rc ){ + *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p))); + goto import_fail; + } + needCommit = sqlite3_get_autocommit(DBX(p)); + if( needCommit ) sqlite3_exec(DBX(p), "BEGIN", 0, 0, 0); + do{ + int startLine = sCtx.nLine; + for(i=0; imode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; + sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); + if( i=nCol ){ + sqlite3_step(pStmt); + rc = sqlite3_reset(pStmt); + if( rc!=SQLITE_OK ){ + utf8_printf(STD_ERR, "%s:%d: INSERT failed: %s\n", sCtx.zFile, + startLine, sqlite3_errmsg(DBX(p))); + sCtx.nErr++; + }else{ + sCtx.nRow++; + } } - rc = arExecSql(pAr, zCreate); + }while( sCtx.cTerm!=EOF ); + + if( needCommit ) sqlite3_exec(DBX(p), "COMMIT", 0, 0, 0); + if( eVerbose>0 ){ + utf8_printf(out, + "Added %d rows with %d errors using %d lines of input\n", + sCtx.nRow, sCtx.nErr, sCtx.nLine-1); } - if( bOnlyIfChanged ){ - zExists = sqlite3_mprintf( - " AND NOT EXISTS(" - "SELECT 1 FROM %s AS mem" - " WHERE mem.name=disk.name" - " AND mem.mtime=disk.mtime" - " AND mem.mode=disk.mode)", zTab); + RESOURCE_FREE(mark); + return DCR_Ok|(sCtx.nErr>0); +} + +/***************** + * The .keyword command + */ +CONDITION_COMMAND( keyword !defined(NO_KEYWORD_COMMAND) ); +COLLECT_HELP_TEXT[ + ".keyword ?KW? List keywords, or say whether KW is one.", +]; +DISPATCHABLE_COMMAND( keyword ? 1 2 ){ + FILE *out = ISS(p)->out; + if( nArg<2 ){ + int i = 0; + int nk = sqlite3_keyword_count(); + int nCol = 0; + int szKW; + while( i75){ + zSep = "\n"; + nCol = 0; + } + memcpy(kwBuf, zKW, szKW); + kwBuf[szKW] = 0; + utf8_printf(out, "%s%s", kwBuf, zSep); + } + } + } + if( nCol>0 ) utf8_printf(out, "\n"); }else{ - zExists = sqlite3_mprintf(""); + int szKW = strlen30(azArg[1]); + int isKeyword = sqlite3_keyword_check(azArg[1], szKW); + utf8_printf(out, "%s is%s a keyword\n", + azArg[1], (isKeyword)? "" : " not"); } - if( zExists==0 ) rc = SQLITE_NOMEM; - for(i=0; inArg && rc==SQLITE_OK; i++){ - char *zSql2 = sqlite3_mprintf(zInsertFmt[pAr->bZip], zTab, - pAr->bVerbose ? "shell_putsnl(name)" : "name", - pAr->azArg[i], pAr->zDir, zExists); - rc = arExecSql(pAr, zSql2); - sqlite3_free(zSql2); + return DCR_Ok; +} + +/***************** + * The .imposter, .iotrace, .limit, .lint and .log commands + */ +#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE) +# define LOAD_ENABLE 1 +#else +# define LOAD_ENABLE 0 +#endif +CONDITION_COMMAND( imposter !defined(SQLITE_OMIT_TEST_CONTROL) ); +CONDITION_COMMAND( iotrace defined(SQLITE_ENABLE_IOTRACE) ); +CONDITION_COMMAND( load LOAD_ENABLE ); +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", +]; +COLLECT_HELP_TEXT[ +#if !defined(SQLITE_SHELL_FIDDLE) + ".log FILE|on|off Turn logging on or off. FILE can be stderr/stdout", +#else + ".log on|off Turn logging on or off.", +#endif +]; +DISPATCHABLE_COMMAND( imposter ? 3 3 ){ + int rc = 0; + char *zSql = 0; + char *zCollist = 0; + sqlite3_stmt *pStmt = 0; + sqlite3 *db; + int tnum = 0; + int isWO = 0; /* True if making an imposter of a WITHOUT ROWID table */ + int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */ + int i; + ResourceMark mark = holder_mark(); + + if( !ShellHasFlag(p,SHFLG_TestingMode) ){ + utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n", + "imposter"); + return DCR_Error; + } + if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){ + *pzErr = smprintf("Usage: .imposter INDEX IMPOSTER\n" + " .imposter off\n"); + /* Also allowed, but not documented: + ** + ** .imposter TABLE IMPOSTER + ** + ** where TABLE is a WITHOUT ROWID table. In that case, the + ** imposter is another WITHOUT ROWID table with the columns in + ** storage order. */ + return DCR_SayUsage; } -end_ar_transaction: + open_db(p, 0); + db = DBX(p); + if( nArg==2 ){ + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 1); + return DCR_Ok; + } + sstr_ptr_holder(&zSql); + zSql = smprintf("SELECT rootpage, 0 FROM sqlite_schema" + " WHERE name='%q' AND type='index'" + "UNION ALL " + "SELECT rootpage, 1 FROM sqlite_schema" + " WHERE name='%q' AND type='table'" + " AND sql LIKE '%%without%%rowid%%'", + azArg[1], azArg[1]); + rc = s3_prep_noom_free(db, &zSql, &pStmt); if( rc!=SQLITE_OK ){ - sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0); + release_holder(); + return DCR_Error; + } + stmt_ptr_holder(&pStmt); + if( s3_step_noom(pStmt)==SQLITE_ROW ){ + tnum = sqlite3_column_int(pStmt, 0); + isWO = sqlite3_column_int(pStmt, 1); + } + zSql = smprintf("PRAGMA index_xinfo='%q'", azArg[1]); + sqlite3_finalize(pStmt); + pStmt = 0; + rc = s3_prep_noom_free(db, &zSql, &pStmt); + i = 0; + sstr_ptr_holder(&zCollist); + while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ + char zLabel[20]; + const char *zCol = (const char*)sqlite3_column_text(pStmt,2); + i++; + if( zCol==0 ){ + if( sqlite3_column_int(pStmt,1)==-1 ){ + zCol = "_ROWID_"; + }else{ + sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i); + zCol = zLabel; + } + } + if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){ + lenPK = (int)strlen(zCollist); + } + if( zCollist==0 ){ + zCollist = smprintf("\"%w\"", zCol); + }else{ + zCollist = smprintf("%z,\"%w\"", zCollist, zCol); + } + } + if( i==0 || tnum==0 ){ + *pzErr = smprintf("no such index: \"%s\"\n", azArg[1]); + RESOURCE_FREE(mark); + return DCR_Error; + } + if( lenPK==0 ) lenPK = 100000; + zSql = smprintf("CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))" + "WITHOUT ROWID", azArg[2], zCollist, lenPK, zCollist); + shell_check_ooms(zSql); + rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 1, tnum); + if( rc==SQLITE_OK ){ + rc = s3_exec_noom(db, zSql, 0, 0, 0); + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 0); + if( rc ){ + *pzErr = smprintf("Error in [%s]: %s\n", zSql, sqlite3_errmsg(db)); + }else{ + utf8_printf(STD_OUT, "%s;\n", zSql); + raw_printf(STD_OUT, "WARNING: " + "writing to an imposter table will corrupt the \"%s\" %s!\n", + azArg[1], isWO ? "table" : "index" + ); + } }else{ - rc = arExecSql(pAr, "RELEASE ar;"); - if( pAr->bZip && pAr->zFile ){ - zSql = sqlite3_mprintf("DROP TABLE %s", zTemp); - arExecSql(pAr, zSql); - sqlite3_free(zSql); + *pzErr = smprintf("SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); + } + RESOURCE_FREE(mark); + return DCR_Ok|(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( cli_strcmp(azArg[1], "-")==0 ){ + sqlite3IoTrace = iotracePrintf; + iotrace = STD_OUT; + }else{ + iotrace = fopen(azArg[1], "w"); + if( iotrace==0 ){ + *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]); + sqlite3IoTrace = 0; + return DCR_Error; + }else{ + sqlite3IoTrace = iotracePrintf; } } - sqlite3_free(zExists); - return rc; + return DCR_Ok; } -/* -** Implementation of ".ar" dot command. -*/ -static int arDotCommand( - ShellState *pState, /* Current shell tool state */ - int fromCmdLine, /* True if -A command-line option, not .ar cmd */ - char **azArg, /* Array of arguments passed to dot command */ - int nArg /* Number of entries in azArg[] */ -){ - ArCommand cmd; - int rc; - memset(&cmd, 0, sizeof(cmd)); - cmd.fromCmdLine = fromCmdLine; - rc = arParseCommand(azArg, nArg, &cmd); - if( rc==SQLITE_OK ){ - int eDbType = SHELL_OPEN_UNSPEC; - cmd.p = pState; - cmd.db = pState->db; - if( cmd.zFile ){ - eDbType = deduceDatabaseType(cmd.zFile, 1); - }else{ - eDbType = pState->openMode; +/***************** + * The .limits and .load commands + */ +COLLECT_HELP_TEXT[ + ",limits ?LIMIT_NAME? Display limit selected by its name, or all limits", + ".load FILE ?ENTRY? Load a SQLite extension library", + " If ENTRY is provided, the entry point \"sqlite_ENTRY_init\" is called.", + " Otherwise, the entry point name is derived from the FILE's name.", +]; + +DISPATCHABLE_COMMAND( limits 5 1 3 ){ + static const struct { + const char *zLimitName; /* Name of a limit */ + int limitCode; /* Integer code for that limit */ + } aLimit[] = { + { "length", SQLITE_LIMIT_LENGTH }, + { "sql_length", SQLITE_LIMIT_SQL_LENGTH }, + { "column", SQLITE_LIMIT_COLUMN }, + { "expr_depth", SQLITE_LIMIT_EXPR_DEPTH }, + { "compound_select", SQLITE_LIMIT_COMPOUND_SELECT }, + { "vdbe_op", SQLITE_LIMIT_VDBE_OP }, + { "function_arg", SQLITE_LIMIT_FUNCTION_ARG }, + { "attached", SQLITE_LIMIT_ATTACHED }, + { "like_pattern_length", SQLITE_LIMIT_LIKE_PATTERN_LENGTH }, + { "variable_number", SQLITE_LIMIT_VARIABLE_NUMBER }, + { "trigger_depth", SQLITE_LIMIT_TRIGGER_DEPTH }, + { "worker_threads", SQLITE_LIMIT_WORKER_THREADS }, + }; + int i, n2; + open_db(p, 0); + if( nArg==1 ){ + for(i=0; i3 ){ + return DCR_TooMany; + }else{ + int iLimit = -1; + n2 = strlen30(azArg[1]); + for(i=0; iout, "-- open database '%s'%s\n", cmd.zFile, - eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : ""); - } - rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, - eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0); - if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "cannot open file: %s (%s)\n", - cmd.zFile, sqlite3_errmsg(cmd.db) - ); - goto end_ar_command; - } - sqlite3_fileio_init(cmd.db, 0, 0); - sqlite3_sqlar_init(cmd.db, 0, 0); - sqlite3_create_function(cmd.db, "shell_putsnl", 1, SQLITE_UTF8, cmd.p, - shellPutsFunc, 0, 0); - } - if( cmd.zSrcTable==0 && cmd.bZip==0 && cmd.eCmd!=AR_CMD_HELP ){ - if( cmd.eCmd!=AR_CMD_CREATE - && sqlite3_table_column_metadata(cmd.db,0,"sqlar","name",0,0,0,0,0) - ){ - utf8_printf(stderr, "database does not contain an 'sqlar' table\n"); - rc = SQLITE_ERROR; - goto end_ar_command; - } - cmd.zSrcTable = sqlite3_mprintf("sqlar"); + if( iLimit<0 ){ + *pzErr = smprintf("unknown limit: \"%s\"\n" + "enter \".limits\" with no arguments for a list.\n", + azArg[1]); + return DCR_ArgWrong; } - - switch( cmd.eCmd ){ - case AR_CMD_CREATE: - rc = arCreateOrUpdateCommand(&cmd, 0, 0); - break; - - case AR_CMD_EXTRACT: - rc = arExtractCommand(&cmd); - break; - - case AR_CMD_LIST: - rc = arListCommand(&cmd); - break; - - case AR_CMD_HELP: - arUsage(pState->out); - break; - - case AR_CMD_INSERT: - rc = arCreateOrUpdateCommand(&cmd, 1, 0); - break; - - case AR_CMD_REMOVE: - rc = arRemoveCommand(&cmd); - break; - - default: - assert( cmd.eCmd==AR_CMD_UPDATE ); - rc = arCreateOrUpdateCommand(&cmd, 1, 1); - break; + if( nArg==3 ){ + sqlite3_limit(DBX(p), aLimit[iLimit].limitCode, + (int)integerValue(azArg[2])); } + fprintf(STD_OUT, "%20s %d\n", aLimit[iLimit].zLimitName, + sqlite3_limit(DBX(p), aLimit[iLimit].limitCode, -1)); } -end_ar_command: - if( cmd.db!=pState->db ){ - close_db(cmd.db); - } - sqlite3_free(cmd.zSrcTable); - - return rc; + return DCR_Ok; } -/* End of the ".archive" or ".ar" command logic -*******************************************************************************/ -#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) */ -#if SQLITE_SHELL_HAVE_RECOVER +DISPATCHABLE_COMMAND( lint 3 1 0 ){ + sqlite3 *db; /* Database handle to query "main" db of */ + FILE *out = ISS(p)->out; /* Stream to write non-error output to */ + int bVerbose = 0; /* If -verbose is present */ + int bGroupByParent = 0; /* If -groupbyparent is present */ + int i; /* To iterate through azArg[] */ + const char *zIndent = ""; /* How much to indent CREATE INDEX by */ + int rc; /* Return code */ + sqlite3_stmt *pSql = 0; /* Compiled version of SQL statement below */ + ResourceMark mark = holder_mark(); -/* -** This function is used as a callback by the recover extension. Simply -** print the supplied SQL statement to stdout. -*/ -static int recoverSqlCb(void *pCtx, const char *zSql){ - ShellState *pState = (ShellState*)pCtx; - utf8_printf(pState->out, "%s;\n", zSql); - return SQLITE_OK; -} + i = (nArg>=2 ? strlen30(azArg[1]) : 0); + if( i==0 || 0!=sqlite3_strnicmp(azArg[1], "fkey-indexes", i) ){ + *pzErr = smprintf + ("Usage %s sub-command ?switches...?\n" + "Where sub-commands are:\n" + " fkey-indexes\n", azArg[0]); + return DCR_SayUsage; + } + open_db(p, 0); + db = DBX(p); -/* -** This function is called to recover data from the database. A script -** to construct a new database containing all recovered data is output -** on stream pState->out. -*/ -static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ - int rc = SQLITE_OK; - const char *zRecoveryDb = ""; /* Name of "recovery" database. Debug only */ - const char *zLAF = "lost_and_found"; - int bFreelist = 1; /* 0 if --ignore-freelist is specified */ - int bRowids = 1; /* 0 if --no-rowids */ - sqlite3_recover *p = 0; - int i = 0; + /* + ** This SELECT statement returns one row for each foreign key constraint + ** in the schema of the main database. The column values are: + ** + ** 0. The text of an SQL statement similar to: + ** + ** "EXPLAIN QUERY PLAN SELECT 1 FROM child_table WHERE child_key=?" + ** + ** This SELECT is similar to the one that the foreign keys implementation + ** needs to run internally on child tables. If there is an index that can + ** be used to optimize this query, then it can also be used by the FK + ** implementation to optimize DELETE or UPDATE statements on the parent + ** table. + ** + ** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by + ** the EXPLAIN QUERY PLAN command matches this pattern, then the schema + ** contains an index that can be used to optimize the query. + ** + ** 2. Human readable text that describes the child table and columns. e.g. + ** + ** "child_table(child_key1, child_key2)" + ** + ** 3. Human readable text that describes the parent table and columns. e.g. + ** + ** "parent_table(parent_key1, parent_key2)" + ** + ** 4. A full CREATE INDEX statement for an index that could be used to + ** optimize DELETE or UPDATE statements on the parent table. e.g. + ** + ** "CREATE INDEX child_table_child_key ON child_table(child_key)" + ** + ** 5. The name of the parent table. + ** + ** These six values are used by the C logic below to generate the report. + */ + const char *zSql = + "SELECT " + " 'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '" + " || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' " + " || fkey_collate_clause(" + " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')" + ", " + " 'SEARCH ' || s.name || ' USING COVERING INDEX*('" + " || group_concat('*=?', ' AND ') || ')'" + ", " + " s.name || '(' || group_concat(f.[from], ', ') || ')'" + ", " + " f.[table] || '(' || group_concat(COALESCE(f.[to], p.[name])) || ')'" + ", " + " 'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))" + " || ' ON ' || quote(s.name) || '('" + " || group_concat(quote(f.[from]) ||" + " fkey_collate_clause(" + " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')" + " || ');'" + ", " + " f.[table] " + "FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f " + "LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) " + "GROUP BY s.name, f.id " + "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)" + ; + const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)"; - for(i=1; i1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){ + bVerbose = 1; + } + else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){ + bGroupByParent = 1; + zIndent = " "; } else{ - utf8_printf(stderr, "unexpected option: %s\n", azArg[i]); - showHelp(pState->out, azArg[0]); - return 1; + raw_printf(STD_ERR, "Usage: %s %s ?-verbose? ?-groupbyparent?\n", + azArg[0], azArg[1] + ); + return DCR_Unknown|i; } } - p = sqlite3_recover_init_sql( - pState->db, "main", recoverSqlCb, (void*)pState + /* Register the fkey_collate_clause() SQL function */ + rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8, + 0, shellFkeyCollateClause, 0, 0 ); - sqlite3_recover_config(p, 789, (void*)zRecoveryDb); /* Debug use only */ - sqlite3_recover_config(p, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF); - sqlite3_recover_config(p, SQLITE_RECOVER_ROWIDS, (void*)&bRowids); - sqlite3_recover_config(p, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist); - - sqlite3_recover_run(p); - if( sqlite3_recover_errcode(p)!=SQLITE_OK ){ - const char *zErr = sqlite3_recover_errmsg(p); - int errCode = sqlite3_recover_errcode(p); - raw_printf(stderr, "sql error: %s (%d)\n", zErr, errCode); + if( rc==SQLITE_OK ){ + rc = s3_prepare_v2_noom(db, zSql, -1, &pSql, 0); + } + /* Track resources after here. */ + stmt_ptr_holder(&pSql); + if( rc==SQLITE_OK ){ + sqlite3_bind_int(pSql, 1, bGroupByParent); } - rc = sqlite3_recover_finish(p); - return rc; -} -#endif /* SQLITE_SHELL_HAVE_RECOVER */ - -/* - * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it. - * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE, - * close db and set it to 0, and return the columns spec, to later - * be sqlite3_free()'ed by the caller. - * The return is 0 when either: - * (a) The db was not initialized and zCol==0 (There are no columns.) - * (b) zCol!=0 (Column was added, db initialized as needed.) - * The 3rd argument, pRenamed, references an out parameter. If the - * pointer is non-zero, its referent will be set to a summary of renames - * done if renaming was necessary, or set to 0 if none was done. The out - * string (if any) must be sqlite3_free()'ed by the caller. - */ -#ifdef SHELL_DEBUG -#define rc_err_oom_die(rc) \ - if( rc==SQLITE_NOMEM ) shell_check_oom(0); \ - else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \ - fprintf(stderr,"E:%d\n",rc), assert(0) -#else -static void rc_err_oom_die(int rc){ - if( rc==SQLITE_NOMEM ) shell_check_oom(0); - assert(rc==SQLITE_OK||rc==SQLITE_DONE); -} -#endif + if( rc==SQLITE_OK ){ - int rc2; + char *zPrev = 0; + sqlite3_stmt *pExplain = 0; + sstr_ptr_holder(&zPrev); + stmt_ptr_holder(&pExplain); + while( SQLITE_ROW==s3_step_noom(pSql) ){ + int res = -1; + const char *zEQP = (const char*)sqlite3_column_text(pSql, 0); + const char *zGlob = (const char*)sqlite3_column_text(pSql, 1); + const char *zFrom = (const char*)sqlite3_column_text(pSql, 2); + const char *zTarget = (const char*)sqlite3_column_text(pSql, 3); + const char *zCI = (const char*)sqlite3_column_text(pSql, 4); + const char *zParent = (const char*)sqlite3_column_text(pSql, 5); -#ifdef SHELL_COLFIX_DB /* If this is set, the DB can be in a file. */ -static char zCOL_DB[] = SHELL_STRINGIFY(SHELL_COLFIX_DB); -#else /* Otherwise, memory is faster/better for the transient DB. */ -static const char *zCOL_DB = ":memory:"; -#endif + if( zEQP==0 || zGlob==0 ) continue; + rc = s3_prepare_v2_noom(db, zEQP, -1, &pExplain, 0); + if( rc!=SQLITE_OK ) break; + if( SQLITE_ROW==s3_step_noom(pExplain) ){ + const char *zPlan = (const char*)sqlite3_column_text(pExplain, 3); - shell_check_ooms(zPlan); + res = zPlan!=0 && ( 0==sqlite3_strglob(zGlob, zPlan) + || 0==sqlite3_strglob(zGlobIPK, zPlan)); + } + rc = sqlite3_finalize(pExplain); + pExplain = 0; + if( rc!=SQLITE_OK ) break; -/* Define character (as C string) to separate generated column ordinal - * from protected part of incoming column names. This defaults to "_" - * so that incoming column identifiers that did not need not be quoted - * remain usable without being quoted. It must be one character. - */ -#ifndef SHELL_AUTOCOLUMN_SEP -# define AUTOCOLUMN_SEP "_" -#else -# define AUTOCOLUMN_SEP SHELL_STRINGIFY(SHELL_AUTOCOLUMN_SEP) -#endif + if( res<0 ){ + raw_printf(STD_ERR, "Error: internal error"); + break; + }else{ + if( bGroupByParent + && (bVerbose || res==0) + && (zPrev==0 || sqlite3_stricmp(zParent, zPrev)) + ){ + raw_printf(out, "-- Parent table %s\n", zParent); + sqlite3_free(zPrev); + zPrev = smprintf("%s", zParent); + shell_check_ooms(zPrev); + } -static char *zAutoColumn(const char *zColNew, sqlite3 **pDb, char **pzRenamed){ - /* Queries and D{D,M}L used here */ - static const char * const zTabMake = "\ -CREATE TABLE ColNames(\ - cpos INTEGER PRIMARY KEY,\ - name TEXT, nlen INT, chop INT, reps INT, suff TEXT);\ -CREATE VIEW RepeatedNames AS \ -SELECT DISTINCT t.name FROM ColNames t \ -WHERE t.name COLLATE NOCASE IN (\ - SELECT o.name FROM ColNames o WHERE o.cpos<>t.cpos\ -);\ -"; - static const char * const zTabFill = "\ -INSERT INTO ColNames(name,nlen,chop,reps,suff)\ - VALUES(iif(length(?1)>0,?1,'?'),max(length(?1),1),0,0,'')\ -"; - static const char * const zHasDupes = "\ -SELECT count(DISTINCT (substring(name,1,nlen-chop)||suff) COLLATE NOCASE)\ - 1, printf('%c%0*d', '"AUTOCOLUMN_SEP"', $1, cpos), '')" -#else /* ...RENAME_MINIMAL_ONE_PASS */ -"WITH Lzn(nlz) AS (" /* Find minimum extraneous leading 0's for uniqueness */ -" SELECT 0 AS nlz" -" UNION" -" SELECT nlz+1 AS nlz FROM Lzn" -" WHERE EXISTS(" -" SELECT 1" -" FROM ColNames t, ColNames o" -" WHERE" -" iif(t.name IN (SELECT * FROM RepeatedNames)," -" printf('%s"AUTOCOLUMN_SEP"%s'," -" t.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,t.cpos),2))," -" t.name" -" )" -" =" -" iif(o.name IN (SELECT * FROM RepeatedNames)," -" printf('%s"AUTOCOLUMN_SEP"%s'," -" o.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,o.cpos),2))," -" o.name" -" )" -" COLLATE NOCASE" -" AND o.cpos<>t.cpos" -" GROUP BY t.cpos" -" )" -") UPDATE Colnames AS t SET" -" chop = 0," /* No chopping, never touch incoming names. */ -" suff = iif(name IN (SELECT * FROM RepeatedNames)," -" printf('"AUTOCOLUMN_SEP"%s', substring(" -" printf('%.*c%0.*d',(SELECT max(nlz) FROM Lzn)+1,'0',1,t.cpos),2))," -" ''" -" )" -#endif - ; - static const char * const zCollectVar = "\ -SELECT\ - '('||x'0a'\ - || group_concat(\ - cname||' TEXT',\ - ','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\ - ||')' AS ColsSpec \ -FROM (\ - SELECT cpos, printf('\"%w\"',printf('%!.*s%s', nlen-chop,name,suff)) AS cname \ - FROM ColNames ORDER BY cpos\ -)"; - static const char * const zRenamesDone = - "SELECT group_concat(" - " printf('\"%w\" to \"%w\"',name,printf('%!.*s%s', nlen-chop, name, suff))," - " ','||x'0a')" - "FROM ColNames WHERE suff<>'' OR chop!=0" - ; - int rc; - sqlite3_stmt *pStmt = 0; - assert(pDb!=0); - if( zColNew ){ - /* Add initial or additional column. Init db if necessary. */ - if( *pDb==0 ){ - if( SQLITE_OK!=sqlite3_open(zCOL_DB, pDb) ) return 0; -#ifdef SHELL_COLFIX_DB - if(*zCOL_DB!=':') - sqlite3_exec(*pDb,"drop table if exists ColNames;" - "drop view if exists RepeatedNames;",0,0,0); -#endif - rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0); - rc_err_oom_die(rc); - } - assert(*pDb!=0); - rc = sqlite3_prepare_v2(*pDb, zTabFill, -1, &pStmt, 0); - rc_err_oom_die(rc); - rc = sqlite3_bind_text(pStmt, 1, zColNew, -1, 0); - rc_err_oom_die(rc); - rc = sqlite3_step(pStmt); - rc_err_oom_die(rc); - sqlite3_finalize(pStmt); - return 0; - }else if( *pDb==0 ){ - return 0; - }else{ - /* Formulate the columns spec, close the DB, zero *pDb. */ - char *zColsSpec = 0; - int hasDupes = db_int(*pDb, zHasDupes); - int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0; - if( hasDupes ){ -#ifdef SHELL_COLUMN_RENAME_CLEAN - rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0); - rc_err_oom_die(rc); -#endif - rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0); - rc_err_oom_die(rc); - rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0); - rc_err_oom_die(rc); - sqlite3_bind_int(pStmt, 1, nDigits); - rc = sqlite3_step(pStmt); - sqlite3_finalize(pStmt); - if( rc!=SQLITE_DONE ) rc_err_oom_die(SQLITE_NOMEM); - } - assert(db_int(*pDb, zHasDupes)==0); /* Consider: remove this */ - rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0); - rc_err_oom_die(rc); - rc = sqlite3_step(pStmt); - if( rc==SQLITE_ROW ){ - zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); - }else{ - zColsSpec = 0; - } - if( pzRenamed!=0 ){ - if( !hasDupes ) *pzRenamed = 0; - else{ - sqlite3_finalize(pStmt); - if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0) - && SQLITE_ROW==sqlite3_step(pStmt) ){ - *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); - }else - *pzRenamed = 0; + if( res==0 ){ + raw_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget); + }else if( bVerbose ){ + raw_printf(out, "%s/* no extra indexes required for %s -> %s */\n", + zIndent, zFrom, zTarget); + } } } - sqlite3_finalize(pStmt); - sqlite3_close(*pDb); - *pDb = 0; - return zColsSpec; + + if( rc!=SQLITE_OK ){ + *pzErr = smprintf("%s\n", sqlite3_errmsg(db)); - } - - if( rc==SQLITE_OK && rc2!=SQLITE_OK ){ - rc = rc2; - *pzErr = smprintf("%s\n", sqlite3_errmsg(db)); ++ }else{ ++ rc = sqlite3_finalize(pSql); ++ pSql = 0; ++ if( rc!=SQLITE_OK ) *pzErr = smprintf("%s\n", sqlite3_errmsg(db)); + } + }else{ + *pzErr = smprintf("%s\n", sqlite3_errmsg(db)); } -} + RESOURCE_FREE(mark); -/* -** 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]; + return DCR_Ok|(rc!=0); +} -#ifndef SQLITE_OMIT_VIRTUALTABLE - if( p->expert.pExpert ){ - expertFinish(p, 1, 0); +DISPATCHABLE_COMMAND( load ? 2 3 ){ + const char *zFile = 0, *zProc = 0; + int ai = 1, rc; + if( ISS(p)->bSafeMode ) return DCR_AbortError; + if( nArg<2 || azArg[1][0]==0 ){ + /* Must have a non-empty FILE. (Will not load self.) */ + return DCR_SayUsage; } + while( aibSafeMode && !bOn && !bOff ) return DCR_AbortError; #endif + output_file_close(ISS(p)->pLog); + if( bOff ){ + ISS(p)->pLog = 0; + return DCR_Ok; + } + if( bOn ) zFile = "stdout"; + ISS(p)->pLog = output_file_open(zFile, 0); + return DCR_Ok|(ISS(p)->pLog==0); +} - /* Parse the input line into tokens. - */ - while( zLine[h] && nArgshellFlgs & SHFLG_HeaderSet)==0 ){ + psi->showHeader = 1; } + zRowSep = SEP_Row; + break; + case MODE_List: + zColSep = SEP_Column; + zRowSep = SEP_Row; + break; + case MODE_Html: + break; + case MODE_Tcl: + zColSep = SEP_Space; + zRowSep = SEP_Row; + break; + case MODE_Csv: + zColSep = SEP_Comma; + zRowSep = SEP_CrLf; + break; + case MODE_Tab: + zColSep = SEP_Tab; + break; + case MODE_Insert: + break; + case MODE_Quote: + zColSep = SEP_Comma; + zRowSep = SEP_Row; + break; + case MODE_Ascii: + zColSep = SEP_Unit; + zRowSep = SEP_Record; + break; + case MODE_Markdown: + /* fall-thru */ + case MODE_Table: + /* fall-thru */ + case MODE_Box: + break; + case MODE_Count: + /* fall-thru */ + case MODE_Off: + /* fall-thru */ + case MODE_Json: + break; + case MODE_Explain: case MODE_Semi: case MODE_EQP: case MODE_Pretty: default: + /* Modes used internally, not settable by .mode command. */ + return; } - 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); + if( zRowSep!=0 ){ + sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, zRowSep); + } + if( zColSep!=0 ){ + sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator, zColSep); + } + psi->mode = modeNominal; +} -#ifndef SQLITE_OMIT_AUTHORIZATION - if( c=='a' && cli_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 .mode command + */ +COLLECT_HELP_TEXT[ + ".mode MODE ?OPTIONS? Set output mode", + " MODE is one of:", + " ascii Columns/rows delimited by 0x1F and 0x1E", + " box Tables using unicode box-drawing characters", + " csv Comma-separated values", + " column Output in columns. (See .width)", + " html HTML
code", + " insert SQL insert statements for TABLE", + " json Results in a JSON array", + " line One value per line", + " list Values delimited by \"|\"", + " markdown Markdown table format", + " qbox Shorthand for \"box --wrap 60 --quote\"", + " quote Escape answers as for SQL", + " table ASCII-art table", + " tabs Tab-separated values", + " tcl TCL list elements", + " OPTIONS: (for columnar modes or insert mode):", + " --wrap N Wrap output lines to no longer than N characters", + " --wordwrap B Wrap or not at word boundaries per B (on/off)", + " --ww Shorthand for \"--wordwrap 1\"", + " --quote Quote output text as SQL literals", + " --noquote Do not quote output text", + " TABLE The name of SQL table used for \"insert\" mode", +]; +DISPATCHABLE_COMMAND( mode ? 1 0 ){ + ShellInState *psi = ISS(p); + const char *zTabname = 0; + const char *zArg; + int i, aix; + u8 foundMode = MODE_COUNT_OF, setMode = MODE_COUNT_OF; + ColModeOpts cmOpts = ColModeOpts_default; + for(aix=1; aixout; + const char *zMode; + int nms; + i = psi->mode; + assert(i>=0 && imode) ){ + raw_printf + (out, "current output mode: %.*s --wrap %d --wordwrap %s --%squote\n", + nms, zMode, psi->cmOpts.iWrap, + psi->cmOpts.bWordWrap ? "on" : "off", + psi->cmOpts.bQuote ? "" : "no"); }else{ - sqlite3_set_authorizer(p->db, 0, 0); + raw_printf(out, "current output mode: %.*s\n", nms, zMode); } - }else + }else{ + effectMode(psi, foundMode, setMode); + if( MODE_IS_COLUMNAR(setMode) ){ + psi->cmOpts = cmOpts; +#if SHELL_DATAIO_EXT + psi->pActiveExporter = psi->pColumnarExporter; #endif - -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) \ - && !defined(SQLITE_SHELL_FIDDLE) - if( c=='a' && cli_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 + }else{ +#if SHELL_DATAIO_EXT + psi->pActiveExporter = psi->pFreeformExporter; #endif + if( setMode==MODE_Insert ){ + set_table_name(p, zTabname ? zTabname : "table"); + } + } + } + psi->cMode = psi->mode; + return DCR_Ok; + flag_unknown: + *pzErr = smprintf("Unknown .mode option: %s\nValid options:\n%s", + zArg, + " --noquote\n" + " --quote\n" + " --wordwrap on/off\n" + " --wrap N\n" + " --ww\n"); + return DCR_Unknown|aix; + mode_unknown: + *pzErr = smprintf("Mode should be one of:\n" + " ascii box column csv html insert json line\n" + " list markdown qbox quote table tabs tcl\n"); + return DCR_Unknown|aix; + mode_badarg: + *pzErr = smprintf("Invalid .mode argument: %s\n", zArg); + return DCR_ArgWrong|aix; +} + +/***************** + * The .oomfake command + */ +CONDITION_COMMAND(oomfake defined(SQLITE_DEBUG)); +COLLECT_HELP_TEXT[ + ",oomfake [how_soon] Set how soon or whether to simulate OOM condition", +]; +DISPATCHABLE_COMMAND( oomfake ? 1 2 azArg nArg p ){ + if( nArg>1 ){ + int oomf = (int)integerValue(azArg[1]); + fake_oom_countdown = oomf; + } + else raw_printf(ISS(p)->out, "OOM sim in %d allocations\n", + fake_oom_countdown); + return DCR_Ok; +} +/* Note that .open is (partially) available in WASM builds but is +** currently only intended to be used by the fiddle tool, not +** end users, so is "undocumented." */ +#ifdef SQLITE_SHELL_FIDDLE +# define HOPEN ",open" +#else +# define HOPEN ".open" +#endif +/***************** + * The .nonce, .nullvalue and .open commands + */ +CONDITION_COMMAND(nonce !defined(SQLITE_SHELL_FIDDLE)); +COLLECT_HELP_TEXT[ + ".open ?OPTIONS? ?FILE? Close existing database and reopen FILE", + " Options:", + " --append Use appendvfs to append database to the end of FILE", +#ifndef SQLITE_OMIT_DESERIALIZE + " --deserialize Load into memory using sqlite3_deserialize()", + " --hexdb Load the output of \"dbtotxt\" as an in-memory db", + " --maxsize N Maximum size for --hexdb or --deserialized database", +#endif + " --new Initialize FILE to an empty database", + " --nofollow Do not follow symbolic links", + " --readonly Open FILE readonly", + " --zip FILE is a ZIP archive", + ".nonce STRING Suspend safe mode for one command if nonce matches", + ".nullvalue STRING Use STRING in place of NULL values", +]; +DISPATCHABLE_COMMAND( open 3 1 0 ){ + ShellInState *psi = ISS(p); + const char *zFN = 0; /* Pointer to constant filename */ + char *zNewFilename = 0; /* Name of the database file to open */ + int iName = 1; /* Index in azArg[] of the filename */ + int newFlag = 0; /* True to delete file before opening */ + u8 openMode = SHELL_OPEN_UNSPEC; + int openFlags = 0; + sqlite3_int64 szMax = 0; + int rc = 0; + /* Check for command-line arguments */ + for(iName=1; iName=3 && cli_strncmp(azArg[0], "backup", n)==0) - || (c=='s' && n>=3 && cli_strncmp(azArg[0], "save", n)==0) - ){ - const char *zDestFile = 0; - const char *zDb = 0; - sqlite3 *pDest; - sqlite3_backup *pBackup; - int j; - int bAsync = 0; - const char *zVfs = 0; - failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]); - for(j=1; jdb, zDb); - if( pBackup==0 ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest)); - close_db(pDest); - return 1; + } + + /* Close the existing database */ + session_close_all(psi, -1); +#if SHELL_DYNAMIC_EXTENSION + if( DBX(p)!=0 ) notify_subscribers(psi, NK_DbAboutToClose, DBX(p)); +#endif + close_db(DBX(p)); + DBX(p) = 0; + psi->pAuxDb->zDbFilename = 0; + sqlite3_free(psi->pAuxDb->zFreeOnClose); + psi->pAuxDb->zFreeOnClose = 0; + psi->openMode = openMode; + psi->openFlags = 0; + psi->szMax = 0; + + /* If a filename is specified, try to open it first */ + if( zFN || psi->openMode==SHELL_OPEN_HEXDB ){ + if( newFlag && zFN && !psi->bSafeMode ) shellDeleteFile(zFN); +#ifndef SQLITE_SHELL_FIDDLE + if( psi->bSafeMode + && psi->openMode!=SHELL_OPEN_HEXDB + && zFN + && cli_strcmp(zFN,":memory:")!=0 + ){ + *pzErr = smprintf("cannot open database files in safe mode"); + return DCR_AbortError; } - while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){} - sqlite3_backup_finish(pBackup); - if( rc==SQLITE_DONE ){ - rc = 0; +#else + /* WASM mode has its own sandboxed pseudo-filesystem. */ +#endif + if( zFN ){ + zNewFilename = smprintf("%s", zFN); + shell_check_ooms(zNewFilename); }else{ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest)); + zNewFilename = 0; + } + psi->pAuxDb->zDbFilename = zNewFilename; + psi->openFlags = openFlags; + psi->szMax = szMax; + open_db(p, OPEN_DB_KEEPALIVE); + if( DBX(p)==0 ){ + *pzErr = smprintf("cannot open '%z'\n", zNewFilename); rc = 1; + }else{ + psi->pAuxDb->zFreeOnClose = zNewFilename; } - close_db(pDest); - }else -#endif /* !defined(SQLITE_SHELL_FIDDLE) */ + } + if( DBX(p)==0 ){ + /* As a fall-back open a TEMP database */ + psi->pAuxDb->zDbFilename = 0; + open_db(p, 0); + } + return DCR_Ok|(rc!=0); +} - if( c=='b' && n>=3 && cli_strncmp(azArg[0], "bail", n)==0 ){ - if( nArg==2 ){ - bail_on_error = booleanValue(azArg[1]); - }else{ - raw_printf(stderr, "Usage: .bail on|off\n"); +DISPATCHABLE_COMMAND( nonce ? 2 2 ){ + ShellInState *psi = ISS(p); + if( psi->zNonce==0 || cli_strcmp(azArg[1],psi->zNonce)!=0 ){ + raw_printf(STD_ERR, "line %d: incorrect nonce: \"%s\"\n", + psi->pInSource->lineno, azArg[1]); + p->shellAbruptExit = 0x102; + return DCR_Abort; + } + /* Suspend safe mode for 1 dot-command after this. */ + psi->bSafeModeFuture = 2; + return DCR_Ok; +} + +DISPATCHABLE_COMMAND( nullvalue ? 2 2 ){ + sqlite3_snprintf(sizeof(ISS(p)->nullValue), ISS(p)->nullValue, "%.*s", + (int)ArraySize(ISS(p)->nullValue)-1, azArg[1]); + return DCR_Ok; +} + +/* Helper functions for .parameter and .vars commands + */ + +struct keyval_row { char * value; int uses; int hits; }; + +static int kv_find_callback(void *pData, int nc, char **pV, char **pC){ + assert(nc>=1); + assert(cli_strcmp(pC[0],"value")==0); + struct keyval_row *pParam = (struct keyval_row *)pData; + assert(pParam->value==0); /* key values are supposedly unique. */ + if( pParam->value!=0 ) sqlite3_free( pParam->value ); + pParam->value = smprintf("%s", pV[0]); /* source owned by statement */ + if( nc>1 ) pParam->uses = (int)integerValue(pV[1]); + ++pParam->hits; + return 0; +} + +static void append_in_clause(sqlite3_str *pStr, + const char **azBeg, const char **azLim); +static void append_glob_terms(sqlite3_str *pStr, const char *zColName, + const char **azBeg, const char **azLim); +static char *find_home_dir(int clearFlag); + +/* Create a home-relative pathname from ~ prefixed path. + * Return it, or 0 for any error. + * Caller must sqlite3_free() it. + */ +static char *home_based_path( const char *zPath ){ + char *zHome = find_home_dir(0); + char *zErr = 0; + assert( zPath[0]=='~' ); + if( zHome==0 ){ + zErr = "Cannot find home directory."; + }else if( zPath[0]==0 || (zPath[1]!='/' +#if defined(_WIN32) || defined(WIN32) + && zPath[1]!='\\' +#endif + ) ){ + zErr = "Malformed pathname"; + }else{ + return smprintf("%s%s", zHome, zPath+1); + } + utf8_printf(STD_ERR, "Error: %s\n", zErr); + return 0; +} + +/* Transfer selected parameters between two parameter tables, for save/load. + * Argument bSaveNotLoad determines transfer direction and other actions. + * If it is true, the store DB will be created if not existent, and its + * table for keeping parameters will be created. Or failure is returned. + * If it is false, the store DB will be opened for read and its presumed + * table for keeping parameters will be read. Or failure is returned. + * + * Arguments azNames and nNames reference the ?NAMES? save/load arguments. + * If it is an empty list, all parameters will be saved or loaded. + * Otherwise, only the named parameters are transferred, if they exist. + * It is not an error to specify a name that cannot be transferred + * because it does not exist in the source table. + * + * Returns are SQLITE_OK for success, or other codes for failure. + */ +static int kv_xfr_table(sqlite3 *db, const char *zStoreDbName, + int bSaveNotLoad, ParamTableUse ptu, + const char *azNames[], int nNames){ + char *zSql = 0; /* to be sqlite3_free()'ed */ + sqlite3_str *sbCopy = 0; + sqlite3 *dbStore = 0; + const char *zHere = 0; + const char *zThere = SH_KV_STORE_SNAME; + const char *zTo; + const char *zFrom; + int rc = 0; + int openFlags = (bSaveNotLoad) + ? SQLITE_OPEN_URI|SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE + : SQLITE_OPEN_READONLY; + + switch( ptu ){ + case PTU_Binding: zHere = PARAM_TABLE_SNAME; break; + case PTU_Script: zHere = SHVAR_TABLE_SNAME; break; + default: assert(0); return 1; + } + zTo = (bSaveNotLoad)? zThere : zHere; + zFrom = (bSaveNotLoad)? zHere : zThere; + /* Ensure store DB can be opened and/or created appropriately. */ + rc = shell_check_nomem(sqlite3_open_v2(zStoreDbName,&dbStore,openFlags,0)); + if( rc!=SQLITE_OK ){ + utf8_printf(STD_ERR, "Error: Cannot %s key/value store DB %s\n", + bSaveNotLoad? "open/create" : "read", zStoreDbName); + return rc; + } + /* Ensure it has the kv store table, or handle its absence. */ + assert(dbStore!=0); + conn_ptr_holder(&dbStore); + if( sqlite3_table_column_metadata + (dbStore, "main", SH_KV_STORE_NAME, 0, 0, 0, 0, 0, 0)!=SQLITE_OK ){ + if( !bSaveNotLoad ){ + utf8_printf(STD_ERR, "Error: No key/value pairs ever stored in DB %s\n", + zStoreDbName); rc = 1; + }else{ + /* The saved parameters table is not there yet; create it. */ + const char *zCT = + "CREATE TABLE IF NOT EXISTS "SH_KV_STORE_NAME"(\n" + " key TEXT PRIMARY KEY,\n" + " value,\n" + " uses INT\n" + ") WITHOUT ROWID;"; + rc = s3_exec_noom(dbStore, zCT, 0, 0, 0); + if( rc!=SQLITE_OK ){ + utf8_printf(STD_ERR, "Cannot create table %s. Nothing saved.", zThere); + } } - }else + } + release_holder(); + assert(dbStore==0); + if( rc!=0 ) return rc; - if( c=='b' && n>=3 && cli_strncmp(azArg[0], "binary", n)==0 ){ - if( nArg==2 ){ - if( booleanValue(azArg[1]) ){ - setBinaryMode(p->out, 1); - }else{ - setTextMode(p->out, 1); - } + zSql = smprintf("ATTACH %Q AS %s;", zStoreDbName, SH_KV_STORE_SCHEMA); + shell_check_ooms(zSql); + sstr_ptr_holder(&zSql); + rc = sqlite3_exec(db, zSql, 0, 0, 0); + release_holder(); + if( rc!=SQLITE_OK ) return rc; + + sbCopy = sqlite3_str_new(db); + sqst_ptr_holder(&sbCopy); + sqlite3_str_appendf + (sbCopy, "INSERT OR REPLACE INTO %s(key,value,uses)" + "SELECT key, value, uses FROM %s WHERE key ", zTo, zFrom); + append_in_clause(sbCopy, azNames, azNames+nNames); + zSql = sqlite3_str_finish(sbCopy); + drop_holder(); + shell_check_ooms(zSql); + sstr_ptr_holder(&zSql); + rc = sqlite3_exec(db, zSql, 0, 0, 0); + release_holder(); + + s3_exec_noom(db, "DETACH "SH_KV_STORE_SCHEMA";", 0, 0, 0); + return rc; +} + +/* Default locations of kv store DBs for .parameters and .vars save/load. */ +static const char *zDefaultParamStore = "~/sqlite_params.sdb"; +static const char *zDefaultVarStore = "~/sqlite_vars.sdb"; + +/* Possibly generate a derived path from input spec, with defaulting + * and conversion of leading (or only) tilde as home directory. + * The above-set default is used for zSpec NULL, "" or "~". + * When return is 0, there is an error; what needs doing cannnot be done. + * The return must eventually be sqlite3_free()'ed. + */ +static char *kv_store_path(const char *zSpec, ParamTableUse ptu){ + if( zSpec==0 || zSpec[0]==0 || cli_strcmp(zSpec,"~")==0 ){ + const char *zDef; + switch( ptu ){ + case PTU_Binding: zDef = zDefaultParamStore; break; + case PTU_Script: zDef = zDefaultVarStore; break; + default: return 0; + } + return home_based_path(zDef); + }else if ( zSpec[0]=='~' ){ + return home_based_path(zSpec); + } + return smprintf("%s", zSpec); +} + +/* Load some or all kv pairs. Arguments are "load FILE ?NAMES?". */ +static int kv_pairs_load(sqlite3 *db, ParamTableUse ptu, + const char *azArg[], int nArg){ + char *zStore = kv_store_path((nArg>1)? azArg[1] : 0, ptu); + if( zStore==0 ){ + utf8_printf(STD_ERR, "Cannot form parameter load path. Nothing loaded.\n"); + return DCR_Error; + }else{ + const char **pzFirst = (nArg>2)? azArg+2 : 0; + int nNames = (nArg>2)? nArg-2 : 0; + int rc; + sstr_holder(zStore); + rc = kv_xfr_table(db, zStore, 0, ptu, pzFirst, nNames); + release_holder(); + return rc; + } +} + +/* Save some or all parameters. Arguments are "save FILE ?NAMES?". */ +static int kv_pairs_save(sqlite3 *db, ParamTableUse ptu, + const char *azArg[], int nArg){ + char *zStore = kv_store_path((nArg>1)? azArg[1] : 0, ptu); + if( zStore==0 ){ + utf8_printf(STD_ERR, "Cannot form parameter save path. Nothing saved.\n"); + return DCR_Error; + }else{ + const char **pzFirst = (nArg>2)? azArg+2 : 0; + int nNames = (nArg>2)? nArg-2 : 0; + int rc; + sstr_holder(zStore); + rc = kv_xfr_table(db, zStore, 1, ptu, pzFirst, nNames); + release_holder(); + return rc; + } +} + +#ifndef SQLITE_NOHAVE_SYSTEM +/* + * Edit one named value in the parameters or shell variables table. + * If it does not yet exist, create it. If eval is true, the value + * is treated as a bare expression, otherwise it is a text value. + * The "uses" argument sets the 3rd column in the selected table, + * and serves to select which of the two tables is modified. + */ +static int edit_one_kvalue(sqlite3 *db, char *name, int eval, + ParamTableUse uses, const char * zEditor){ + struct keyval_row kvRow = {0,0,0}; + int rc; + char * zVal = 0; + const char *zTab = 0; + char * zSql = 0; + + switch( uses ){ + case PTU_Script: zTab = SHVAR_TABLE_SNAME; break; + case PTU_Binding: zTab = PARAM_TABLE_SNAME; break; + default: assert(0); + } + zSql = smprintf("SELECT value, uses FROM %s " + "WHERE key=%Q AND uses=%d", zTab, name, uses); + shell_check_ooms(zSql); + sstr_ptr_holder(&kvRow.value); + sstr_holder(zSql); + s3_exec_noom(db, zSql, kv_find_callback, &kvRow, 0); + release_holder(); + assert(kvRow.hits<2); + if( kvRow.hits==1 && kvRow.uses==uses){ + /* Editing an existing value of same kind. */ + release_holder(); /* kvRow.value */ + if( eval!=0 ){ + zSql = smprintf("SELECT edit(value, %Q) FROM %s " + "WHERE key=%Q AND uses=%d", zEditor, zTab, name, uses); + shell_check_ooms(zSql); + sstr_holder(zSql); + zVal = db_text(db, zSql, 1); + release_holder(); + sqlite3_free(zSql); + zSql = smprintf("UPDATE %s SET value=(SELECT %s) " + "WHERE key=%Q AND uses=%d", zTab, zVal, name, uses); }else{ - raw_printf(stderr, "Usage: .binary on|off\n"); - rc = 1; + zSql = smprintf("UPDATE %s SET value=edit(value, %Q) WHERE" + " key=%Q AND uses=%d", zTab, zEditor, name, uses); } - }else + }else{ + /* Editing a new value of same kind. */ + assert(kvRow.value==0 || kvRow.uses!=uses); + drop_holder(); /* kvRow.value */ + if( eval!=0 ){ + zSql = smprintf("SELECT edit('-- %q%s', %Q)", name, "\n", zEditor); + sstr_holder(zSql); + zVal = db_text(db, zSql, 1); + release_holder(); + zSql = smprintf("INSERT INTO %s(key,value,uses)" + " VALUES (%Q,(SELECT %s LIMIT 1),%d)", + zTab, name, zVal, uses); + }else{ + zSql = smprintf("INSERT INTO %s(key,value,uses)" + " VALUES (%Q,edit('-- %q;%s', %Q),%d)", + zTab, name, name, "\n", zEditor, uses); + } + } + shell_check_ooms(zSql); + sstr_holder(zSql); + sstr_holder(zVal); + rc = sqlite3_exec(db, zSql, 0, 0, 0); + release_holders(2); + return rc!=SQLITE_OK; +} +#endif - /* The undocumented ".breakpoint" command causes a call to the no-op - ** routine named test_breakpoint(). - */ - if( c=='b' && n>=3 && cli_strncmp(azArg[0], "breakpoint", n)==0 ){ - test_breakpoint(); - }else +/* Space-join values in an argument list. *valLim is not included. */ +char *values_join( char **valBeg, char **valLim ){ + char *z = 0; + const char *zSep = 0; + while( valBeg < valLim ){ + z = smprintf("%z%s%s", z, zSep, *valBeg); + zSep = " "; + ++valBeg; + } + return z; +} -#ifndef SQLITE_SHELL_FIDDLE - if( c=='c' && cli_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; +static struct ParamSetOpts { + const char cCast; + const char *zTypename; + int evalKind; +} param_set_opts[] = { + /* { 'q', 0, 2 }, */ + /* { 'x', 0, 1 }, */ + { 'i', "INT", 1 }, + { 'r', "REAL", 1 }, + { 'b', "BLOB", 1 }, + { 't', "TEXT", 0 }, + { 'n', "NUMERIC", 1 } +}; + +/* Return an option character if it is single and prefixed by - or --, + * else return 0. + */ +static char option_char(char *zArg){ + if( zArg[0]=='-' ){ + ++zArg; + if( zArg[0]=='-' ) ++zArg; + if( zArg[0]!=0 && zArg[1]==0 ) return zArg[0]; + } + return 0; +} + +/* Most of .vars set + * Return SQLITE_OK on success, else SQLITE_ERROR. + */ +static int shvar_set(sqlite3 *db, char *name, char **valBeg, char **valLim){ + int rc = SQLITE_OK; + char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0; + sqlite3_stmt *pStmtSet = 0; + char *zValue = (zValGlom==0)? *valBeg : zValGlom; + char *zSql + = smprintf("REPLACE INTO "SHVAR_TABLE_SNAME"(key,value,uses)" + "VALUES(%Q,%Q,"SPTU_Script");", name, zValue); + sstr_holder(zValGlom); + rc = s3_prep_noom_free(db, &zSql, &pStmtSet); + assert(rc==SQLITE_OK); + stmt_holder(pStmtSet); + rc = s3_step_noom(pStmtSet); + rc = (SQLITE_DONE==rc)? SQLITE_OK : SQLITE_ERROR; + release_holders(2); + return rc; +} + + +/* Most of the .parameter set subcommand (per help text) + * Return SQLITE_OK on success, else SQLITE_ERROR. + */ +static int param_set(sqlite3 *db, char cCast, + char *name, char **valBeg, char **valLim){ + char *zSql = 0; + char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0; + sqlite3_stmt *pStmtSet = 0; + /* Above objects are managed. */ + const char *zCastTo = 0; + int rc = SQLITE_OK, retries = 0, needsEval = 1; + char *zValue = (zValGlom==0)? *valBeg : zValGlom; + + sstr_holder(zValGlom); /* +1 */ + if( cCast ){ + struct ParamSetOpts *pSO = param_set_opts; + for(; pSO-param_set_opts < ArraySize(param_set_opts); ++pSO ){ + if( cCast==pSO->cCast ){ + zCastTo = pSO->zTypename; + needsEval = pSO->evalKind > 0; + break; } - }else{ - raw_printf(stderr, "Usage: .cd DIRECTORY\n"); - rc = 1; } - }else -#endif /* !defined(SQLITE_SHELL_FIDDLE) */ - - if( c=='c' && n>=3 && cli_strncmp(azArg[0], "changes", n)==0 ){ - if( nArg==2 ){ - setOrClearFlag(p, SHFLG_CountChanges, azArg[1]); + } + stmt_ptr_holder(&pStmtSet); /* +2 */ + if( needsEval ){ + if( zCastTo!=0 ){ + zSql = smprintf + ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" + " VALUES(%Q,CAST((%s) AS %s),"SPTU_Binding");", + name, zValue, zCastTo ); }else{ - raw_printf(stderr, "Usage: .changes on|off\n"); - rc = 1; - } - }else + zSql = smprintf + ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" + "VALUES(%Q,(%s),"SPTU_Binding");", name, zValue ); + } + rc = s3_prep_noom_free(db, &zSql, &pStmtSet); + } + if( !needsEval || rc!=SQLITE_OK ){ + /* Reach here when value either requested to be cast to text, or must be. */ + sqlite3_finalize(pStmtSet); + pStmtSet = 0; + zSql = smprintf + ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" + "VALUES(%Q,%Q,"SPTU_Binding");", name, zValue ); + rc = s3_prep_noom_free(db, &zSql, &pStmtSet); + assert(rc==SQLITE_OK); + } + sqlite3_step(pStmtSet); + release_holders(2); + return rc; +} -#ifndef SQLITE_SHELL_FIDDLE - /* 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 && cli_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 ){ - rc = 2; - }else if( testcase_glob(azArg[1],zRes)==0 ){ - utf8_printf(stderr, - "testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n", - p->zTestcase, azArg[1], zRes); - rc = 1; - }else{ - utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase); - p->nCheck++; - } - sqlite3_free(zRes); - }else -#endif /* !defined(SQLITE_SHELL_FIDDLE) */ +/* list or ls subcommand for .parameter and .vars dot-commands */ +static void list_pov_entries(ShellExState *psx, ParamTableUse ptu, u8 bShort, + char **pzArgs, int nArg){ + sqlite3_stmt *pStmt = 0; + sqlite3_str *sbList = 0; + char *zFromWhere = 0; + char *zSql = 0; + /* Above objects are managed. */ + RESOURCE_MARK(mark); + sqlite3 *db; + int len = 0, rc; + const char *zTab; -#ifndef SQLITE_SHELL_FIDDLE - if( c=='c' && cli_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; + switch( ptu ){ + case PTU_Binding: + db = DBX(psx); + zTab = PARAM_TABLE_SNAME; + break; + case PTU_Script: + db = psx->dbShell; + zTab = SHVAR_TABLE_NAME; + break; + default: assert(0); return; + } + sbList = sqlite3_str_new(db); + sqst_holder(sbList); + sqlite3_str_appendf(sbList, "FROM "); + sqlite3_str_appendf(sbList, zTab); + sqlite3_str_appendf(sbList, " WHERE (uses=?1) AND "); + append_glob_terms(sbList, "key", + (const char **)pzArgs, (const char **)pzArgs+nArg); + shell_check_nomem(sqlite3_str_errcode(sbList)); + zFromWhere = sqlite3_str_finish(sbList); + drop_holder(); + shell_check_ooms(zFromWhere); + sstr_holder(zFromWhere); /* +1 */ + zSql = smprintf("SELECT max(length(key)) %s", zFromWhere); + sstr_ptr_holder(&zSql); + rc = s3_prep_noom_free(db, &zSql, &pStmt); + stmt_ptr_holder(&pStmt); + if( rc==SQLITE_OK ){ + sqlite3_bind_int(pStmt, 1, ptu); + if( s3_step_noom(pStmt)==SQLITE_ROW ){ + len = sqlite3_column_int(pStmt, 0); + if( len>40 ) len = 40; + if( len<4 ) len = 4; } - }else -#endif /* !defined(SQLITE_SHELL_FIDDLE) */ - - if( c=='c' && cli_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); + sqlite3_finalize(pStmt); + pStmt = 0; + } + if( len ){ + FILE *out = ISS(psx)->out; + sqlite3_free(zSql); + zSql = 0; + if( !bShort ){ + int nBindings = 0, nScripts = 0; + zSql = smprintf("SELECT key, uses," + " iif(typeof(value)='text', quote(value), value) as v" + " %s ORDER BY uses, key", zFromWhere); + rc = s3_prep_noom_free(db, &zSql, &pStmt); + sqlite3_bind_int(pStmt, 1, ptu); + while( rc==SQLITE_OK && s3_step_noom(pStmt)==SQLITE_ROW ){ + ParamTableUse ptux = sqlite3_column_int(pStmt,1); + const char *zName = sqlite3_column_text(pStmt,0); + const char *zValue = sqlite3_column_text(pStmt,2); + if( !zName ) zName = "?"; + if( !zValue ) zValue = "?"; + switch( ptux ){ + case PTU_Binding: + if( nBindings++ == 0 ){ + utf8_printf(out, "Bindings:\n%-*s %s\n", len, "name", "value"); + } + utf8_printf(out, "%-*s %s\n", len, zName, zValue); + break; + case PTU_Script: + if( nScripts++ == 0 ){ + utf8_printf(out, "Scripts:\n%-*s %s\n", len, "name", "value"); + } + utf8_printf(out, "%-*s %s\n", len, zName, zValue); + break; + default: break; /* Ignore */ } } - }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 && cli_strcmp(azArg[1], "close")==0 - && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){ - int i = azArg[2][0] - '0'; - if( i<0 || i>=ArraySize(p->aAuxDb) ){ - /* No-op */ - }else if( p->pAuxDb == &p->aAuxDb[i] ){ - raw_printf(stderr, "cannot close the active database connection\n"); - rc = 1; - }else if( p->aAuxDb[i].db ){ - session_close_all(p, i); - close_db(p->aAuxDb[i].db); - p->aAuxDb[i].db = 0; - } - }else{ - raw_printf(stderr, "Usage: .connection [close] [CONNECTION-NUMBER]\n"); - rc = 1; - } - }else - - if( c=='d' && n>1 && cli_strncmp(azArg[0], "databases", n)==0 ){ - char **azName = 0; - int nName = 0; - sqlite3_stmt *pStmt; - int i; - open_db(p, 0); - rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); - if( rc ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); - rc = 1; }else{ - while( sqlite3_step(pStmt)==SQLITE_ROW ){ - const char *zSchema = (const char *)sqlite3_column_text(pStmt,1); - const char *zFile = (const char*)sqlite3_column_text(pStmt,2); - if( zSchema==0 || zFile==0 ) continue; - azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*)); - shell_check_oom(azName); - azName[nName*2] = strdup(zSchema); - azName[nName*2+1] = strdup(zFile); - nName++; - } - } - sqlite3_finalize(pStmt); - for(i=0; idb, azName[i*2]); - int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]); - const char *z = azName[i*2+1]; - utf8_printf(p->out, "%s: %s %s%s\n", - azName[i*2], - z && z[0] ? z : "\"\"", - bRdonly ? "r/o" : "r/w", - eTxn==SQLITE_TXN_NONE ? "" : - eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn"); - free(azName[i*2]); - free(azName[i*2+1]); - } - sqlite3_free(azName); - }else - - if( c=='d' && n>=3 && cli_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 }, - { "reverse_scanorder", SQLITE_DBCONFIG_REVERSE_SCANORDER }, - { "stmt_scanstatus", SQLITE_DBCONFIG_STMT_SCANSTATUS }, - { "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 && cli_strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue; - if( nArg>=3 ){ - sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0); + int nc = 0, ncw = 78/(len+2); + zSql = smprintf("SELECT key %s ORDER BY key", zFromWhere); + rc = s3_prep_noom_free(db, &zSql, &pStmt); + sqlite3_bind_int(pStmt, 1, ptu); + while( rc==SQLITE_OK && s3_step_noom(pStmt)==SQLITE_ROW ){ + utf8_printf(out, "%s %-*s", ((++nc%ncw==0)? "\n" : ""), + len, sqlite3_column_text(pStmt,0)); } - sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v); - utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off"); - if( nArg>1 ) break; + if( nc>0 ) utf8_printf(out, "\n"); } - 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 + } + RESOURCE_FREE(mark); +} -#if SQLITE_SHELL_HAVE_RECOVER - if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbinfo", n)==0 ){ - rc = shell_dbinfo_command(p, nArg, azArg); - }else +/* Append an OR'ed series of GLOB terms comparing a given column + * name to a series of patterns. Result is an appended expression. + * For an empty pattern series, expression is true for non-NULL. + */ +static void append_glob_terms(sqlite3_str *pStr, const char *zColName, + const char **azBeg, const char **azLim){ + if( azBeg==azLim ) sqlite3_str_appendf(pStr, "%s NOT NULL", zColName); + else{ + char *zSep = "("; + while( azBegshowHeader; - int savedShellFlags = p->shellFlgs; - ShellClearFlag(p, - SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo - |SHFLG_DumpDataOnly|SHFLG_DumpNoSys); - for(i=1; i2 || azArg[1][0]=='c') ){ + sqlite3_str *sbZap = sqlite3_str_new(db); + char *zSql = 0; + sqlite3_str_appendf + (sbZap, "DELETE FROM "PARAM_TABLE_SNAME" WHERE key "); + append_in_clause(sbZap, + (const char **)&azArg[2], (const char **)&azArg[nArg]); + zSql = sqlite3_str_finish(sbZap); + shell_check_ooms(zSql); + sstr_holder(zSql); + sqlite3_exec(db, zSql, 0, 0, 0); + release_holder(); + } + }else +#ifndef SQLITE_NOHAVE_SYSTEM + /* .parameter edit ?NAMES? + ** Edit the named parameters. Any that do not exist are created. + */ + if( cli_strcmp(azArg[1],"edit")==0 ){ + ShellInState *psi = ISS(p); + int ia = 2; + int eval = 0; + int edSet; + + if( !INSOURCE_IS_INTERACTIVE(psi->pInSource) ){ + utf8_printf(STD_ERR, "Error: " + ".parameter edit can only be used interactively.\n"); + return DCR_Error; + } + param_table_init(db); + edSet = attempt_editor_set(psi, azArg[0], (nArg>2)? azArg[2] : 0 ); + if( edSet < 0 ) return DCR_Error; + else ia += edSet; + /* Future: Allow an option whereby new value can be evaluated + * the way that .parameter set ... does. + */ + while( ia < nArg ){ + ParamTableUse ptu; + char *zA = azArg[ia]; + char cf = (zA[0]=='-')? zA[1] : 0; + if( cf!=0 && zA[2]==0 ){ + ++ia; + switch( cf ){ + case 'e': eval = 1; continue; + case 't': eval = 0; continue; + default: + utf8_printf(STD_ERR, "Error: bad .parameter name: %s\n", zA); + return DCR_Error; } } + ptu = classify_param_name(zA); + if( ptu!=PTU_Binding ){ + utf8_printf(STD_ERR, "Error: " + "%s cannot be a binding parameter name.\n", zA); + return DCR_Error; + } + rc = edit_one_kvalue(db, zA, eval, ptu, psi->zEditor); + ++ia; + if( rc!=0 ) return DCR_Error; } - - open_db(p, 0); - - if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ - /* When playing back a "dump", the content might appear in an order - ** which causes immediate foreign key constraints to be violated. - ** So disable foreign-key constraint enforcement to prevent problems. */ - raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n"); - raw_printf(p->out, "BEGIN TRANSACTION;\n"); - } - p->writableSchema = 0; - p->showHeader = 0; - /* Set writable_schema=ON since doing so forces SQLite to initialize - ** as much of the schema as it can even if the sqlite_schema table is - ** corrupt. */ - sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0); - p->nErr = 0; - if( zLike==0 ) zLike = sqlite3_mprintf("true"); - zSql = sqlite3_mprintf( - "SELECT name, type, sql FROM sqlite_schema AS o " - "WHERE (%s) AND type=='table'" - " AND sql NOT NULL" - " ORDER BY tbl_name='sqlite_sequence', rowid", - zLike - ); - run_schema_dump_query(p,zSql); - sqlite3_free(zSql); - if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ - zSql = sqlite3_mprintf( - "SELECT sql FROM sqlite_schema AS o " - "WHERE (%s) AND sql NOT NULL" - " AND type IN ('index','trigger','view')", - zLike - ); - run_table_dump_query(p, zSql); - sqlite3_free(zSql); - } - sqlite3_free(zLike); - if( p->writableSchema ){ - raw_printf(p->out, "PRAGMA writable_schema=OFF;\n"); - p->writableSchema = 0; - } - sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0); - sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0); - if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ - raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n"); - } - p->showHeader = savedShowHeader; - p->shellFlgs = savedShellFlags; }else +#endif - if( c=='e' && cli_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; - } + /* .parameter init + ** Make sure the TEMP table used to hold bind parameters exists. + ** Create it if necessary. + */ + if( nArg==2 && cli_strcmp(azArg[1],"init")==0 ){ + param_table_init(db); }else - if( c=='e' && cli_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( cli_strcmp(azArg[1],"full")==0 ){ - p->autoEQP = AUTOEQP_full; - }else if( cli_strcmp(azArg[1],"trigger")==0 ){ - p->autoEQP = AUTOEQP_trigger; -#ifdef SQLITE_DEBUG - }else if( cli_strcmp(azArg[1],"test")==0 ){ - p->autoEQP = AUTOEQP_on; - p->autoEQPtest = 1; - }else if( cli_strcmp(azArg[1],"trace")==0 ){ - p->autoEQP = AUTOEQP_full; - p->autoEQPtrace = 1; - open_db(p, 0); - sqlite3_exec(p->db, "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0); - sqlite3_exec(p->db, "PRAGMA vdbe_trace=ON;", 0, 0, 0); -#endif - }else{ - p->autoEQP = (u8)booleanValue(azArg[1]); - } - }else{ - raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n"); - rc = 1; - } + /* .parameter list|ls + ** List all or selected bind parameters. + ** list displays names, values and uses. + ** ls displays just the names. + */ + if( nArg>=2 && ((cli_strcmp(azArg[1],"list")==0) + || (cli_strcmp(azArg[1],"ls")==0)) ){ + list_pov_entries(p, PTU_Binding, azArg[1][1]=='s', azArg+2, nArg-2); }else -#ifndef SQLITE_SHELL_FIDDLE - if( c=='e' && cli_strncmp(azArg[0], "exit", n)==0 ){ - if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc); - rc = 2; + /* .parameter load + ** Load all or named parameters from specified or default (DB) file. + */ + if( cli_strcmp(azArg[1],"load")==0 ){ + param_table_init(db); + rc = kv_pairs_load(db, PTU_Binding, (const char **)azArg+1, nArg-1); }else -#endif - /* The ".explain" command is automatic now. It is largely pointless. It - ** retained purely for backwards compatibility */ - if( c=='e' && cli_strncmp(azArg[0], "explain", n)==0 ){ - int val = 1; - if( nArg>=2 ){ - if( cli_strcmp(azArg[1],"auto")==0 ){ - val = 99; - }else{ - val = booleanValue(azArg[1]); - } - } - if( val==1 && p->mode!=MODE_Explain ){ - p->normalMode = p->mode; - p->mode = MODE_Explain; - p->autoExplain = 0; - }else if( val==0 ){ - if( p->mode==MODE_Explain ) p->mode = p->normalMode; - p->autoExplain = 0; - }else if( val==99 ){ - if( p->mode==MODE_Explain ) p->mode = p->normalMode; - p->autoExplain = 1; - } + /* .parameter save + ** Save all or named parameters into specified or default (DB) file. + */ + if( cli_strcmp(azArg[1],"save")==0 ){ + rc = kv_pairs_save(db, PTU_Binding, (const char **)azArg+1, nArg-1); }else -#ifndef SQLITE_OMIT_VIRTUALTABLE - if( c=='e' && cli_strncmp(azArg[0], "expert", n)==0 ){ - if( p->bSafeMode ){ - raw_printf(stderr, - "Cannot run experimental commands such as \"%s\" in safe mode\n", - azArg[0]); + /* .parameter set NAME VALUE + ** Set or reset a bind parameter. NAME should be the full parameter + ** name exactly as it appears in the query. (ex: $abc, @def). The + ** VALUE can be in either SQL literal notation, or if not it will be + ** understood to be a text string. + */ + if( nArg>=4 && cli_strcmp(azArg[1],"set")==0 ){ + char cCast = option_char(azArg[2]); + int inv = 2 + (cCast != 0); + ParamTableUse ptu = classify_param_name(azArg[inv]); + if( ptu!=PTU_Binding ){ + utf8_printf(STD_ERR, + "Error: %s is not a usable parameter name.\n", azArg[inv]); rc = 1; }else{ - open_db(p, 0); - expertDotCommand(p, azArg, nArg); + param_table_init(db); + rc = param_set(db, cCast, azArg[inv], &azArg[inv+1], &azArg[nArg]); + if( rc!=SQLITE_OK ){ + utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(db)); + rc = 1; + } } }else -#endif - if( c=='f' && cli_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]=='-' - && (cli_strcmp(zCmd,"--schema")==0 || cli_strcmp(zCmd,"-schema")==0) - && nArg>=4 - ){ - zSchema = azArg[2]; - for(i=3; iout, "parameter", p); + return DCR_CmdErred; + } - /* The argument can optionally begin with "-" or "--" */ - if( zCmd[0]=='-' && zCmd[1] ){ - zCmd++; - if( zCmd[0]=='-' && zCmd[1] ) zCmd++; - } + return DCR_Ok | (rc!=0); +} - /* --help lists all file-controls */ - if( cli_strcmp(zCmd,"help")==0 ){ - utf8_printf(p->out, "Available file-controls:\n"); - for(i=0; iout, " .filectrl %s %s\n", - aCtrl[i].zCtrlName, aCtrl[i].zUsage); +/***************** + * The .print, .progress and .prompt commands + */ +CONDITION_COMMAND( progress !defined(SQLITE_OMIT_PROGRESS_CALLBACK) ); +COLLECT_HELP_TEXT[ + ".print STRING... Print literal STRING", + ".progress N Invoke progress handler after every N opcodes", + " --limit N Interrupt after N progress callbacks", + " --once Do no more than one progress interrupt", + " --quiet|-q No output except at interrupts", + " --reset Reset the count for each input and interrupt", + ".prompt MAIN CONTINUE Replace the standard prompts", +]; +DISPATCHABLE_COMMAND( print 3 1 0 ){ + int i; + for(i=1; iout, "%s%s", azArg[i], (i==nArg-1)? "\n" : " "); + } + return DCR_Ok; +} +DISPATCHABLE_COMMAND( progress 3 2 0 ){ + ShellInState *psi = ISS(p); + int i; + int nn = 0; + psi->flgProgress = 0; + psi->mxProgress = 0; + psi->nProgress = 0; + for(i=1; iflgProgress |= SHELL_PROGRESS_QUIET; + continue; } - rc = 1; - goto meta_command_exit; - } - - /* convert filectrl text option to value. allow any unique prefix - ** of the option name, or a numerical value. */ - n2 = strlen30(zCmd); - for(i=0; iflgProgress |= SHELL_PROGRESS_RESET; + continue; + } + if( cli_strcmp(z,"once")==0 ){ + psi->flgProgress |= SHELL_PROGRESS_ONCE; + continue; + } + if( cli_strcmp(z,"limit")==0 ){ + if( i+1>=nArg ){ + *pzErr = smprintf("missing argument on --limit\n"); + return DCR_Unpaired|i; }else{ - utf8_printf(stderr, "Error: ambiguous file-control: \"%s\"\n" - "Use \".filectrl --help\" for help\n", zCmd); - rc = 1; - goto meta_command_exit; + psi->mxProgress = (int)integerValue(azArg[++i]); } + continue; } - } - if( filectrl<0 ){ - utf8_printf(stderr,"Error: unknown file-control: %s\n" - "Use \".filectrl --help\" for help\n", zCmd); + return DCR_Unknown|i; }else{ - switch(filectrl){ - case SQLITE_FCNTL_SIZE_LIMIT: { - if( nArg!=2 && nArg!=3 ) break; - iRes = nArg==3 ? integerValue(azArg[2]) : -1; - sqlite3_file_control(p->db, zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes); - isOk = 1; - break; - } - case SQLITE_FCNTL_LOCK_TIMEOUT: - case SQLITE_FCNTL_CHUNK_SIZE: { - int x; - if( nArg!=3 ) break; - x = (int)integerValue(azArg[2]); - sqlite3_file_control(p->db, zSchema, filectrl, &x); - isOk = 2; - break; - } - case SQLITE_FCNTL_PERSIST_WAL: - case SQLITE_FCNTL_POWERSAFE_OVERWRITE: { - int x; - if( nArg!=2 && nArg!=3 ) break; - x = nArg==3 ? booleanValue(azArg[2]) : -1; - sqlite3_file_control(p->db, zSchema, filectrl, &x); - iRes = x; - isOk = 1; - break; - } - case SQLITE_FCNTL_DATA_VERSION: - case SQLITE_FCNTL_HAS_MOVED: { - int x; - if( nArg!=2 ) break; - sqlite3_file_control(p->db, zSchema, filectrl, &x); - iRes = x; - isOk = 1; - break; - } - case SQLITE_FCNTL_TEMPFILENAME: { - char *z = 0; - if( nArg!=2 ) break; - sqlite3_file_control(p->db, zSchema, filectrl, &z); - if( z ){ - utf8_printf(p->out, "%s\n", z); - sqlite3_free(z); - } - isOk = 2; - break; - } - case SQLITE_FCNTL_RESERVE_BYTES: { - int x; - if( nArg>=3 ){ - x = atoi(azArg[2]); - sqlite3_file_control(p->db, zSchema, filectrl, &x); - } - x = -1; - sqlite3_file_control(p->db, zSchema, filectrl, &x); - utf8_printf(p->out,"%d\n", x); - isOk = 2; - break; - } - } + nn = (int)integerValue(z); + } + } + open_db(p, 0); + sqlite3_progress_handler(DBX(p), nn, progress_handler, psi); + return DCR_Ok; +} + +/* Allow too few arguments by tradition, (a form of no-op.) */ +DISPATCHABLE_COMMAND( prompt ? 1 3 ){ + if( nArg >= 2) { + SET_MAIN_PROMPT(azArg[1]); + } + if( nArg >= 3) { + SET_MORE_PROMPT(azArg[2]); + } + return DCR_Ok; +} + +/***************** + * The .recover and .restore commands + */ +CONDITION_COMMAND( recover SQLITE_SHELL_HAVE_RECOVER ); +CONDITION_COMMAND( restore !defined(SQLITE_SHELL_FIDDLE) ); +COLLECT_HELP_TEXT[ + ".recover Recover as much data as possible from corrupt db.", + " --ignore-freelist Ignore pages that appear to be on db freelist", + " --lost-and-found TABLE Alternative name for the lost-and-found table", + " --no-rowids Do not attempt to recover rowid values", + " that are not also INTEGER PRIMARY KEYs", + ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE", +]; + +/* +** This command is invoked to recover data from the database. A script +** to construct a new database containing all recovered data is output +** on stream pState->out. +*/ +DISPATCHABLE_COMMAND( recover ? 1 7 ){ + int rc = SQLITE_OK; + const char *zRecoveryDb = ""; /* Name of "recovery" database. Debug only */ + const char *zLAF = "lost_and_found"; + int bFreelist = 1; /* 0 if --ignore-freelist is specified */ + int bRowids = 1; /* 0 if --no-rowids */ + sqlite3_recover *pr = 0; + int i = 0; + FILE *out = ISS(p)->out; + sqlite3 *db; + + open_db(p, 0); + db = DBX(p); + + for(i=1; i=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{ + utf8_printf(stderr, "unexpected option: %s\n", azArg[i]); + showHelp(stderr, azArg[0], p); + return DCR_Error; } - }else + } - if( c=='f' && cli_strncmp(azArg[0], "fullschema", n)==0 ){ - ShellState data; - int doStats = 0; - memcpy(&data, p, sizeof(data)); - data.showHeader = 0; - data.cMode = data.mode = MODE_Semi; - if( nArg==2 && optionMatch(azArg[1], "indent") ){ - data.cMode = data.mode = MODE_Pretty; - nArg = 1; - } - if( nArg!=1 ){ - raw_printf(stderr, "Usage: .fullschema ?--indent?\n"); - rc = 1; - goto meta_command_exit; - } - open_db(p, 0); - rc = sqlite3_exec(p->db, - "SELECT sql FROM" - " (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x" - " FROM sqlite_schema UNION ALL" - " SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) " - "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' " - "ORDER BY x", - callback, &data, 0 - ); - if( rc==SQLITE_OK ){ - sqlite3_stmt *pStmt; - rc = sqlite3_prepare_v2(p->db, - "SELECT rowid FROM sqlite_schema" - " WHERE name GLOB 'sqlite_stat[134]'", - -1, &pStmt, 0); - doStats = sqlite3_step(pStmt)==SQLITE_ROW; - sqlite3_finalize(pStmt); - } - if( doStats==0 ){ - raw_printf(p->out, "/* No STAT tables available */\n"); - }else{ - raw_printf(p->out, "ANALYZE sqlite_schema;\n"); - data.cMode = data.mode = MODE_Insert; - data.zDestTable = "sqlite_stat1"; - shell_exec(&data, "SELECT * FROM sqlite_stat1", 0); - data.zDestTable = "sqlite_stat4"; - shell_exec(&data, "SELECT * FROM sqlite_stat4", 0); - raw_printf(p->out, "ANALYZE sqlite_schema;\n"); - } - }else + pr = sqlite3_recover_init_sql(db, "main", recoverSqlCb, (void*)p); - if( c=='h' && cli_strncmp(azArg[0], "headers", n)==0 ){ - if( nArg==2 ){ - p->showHeader = booleanValue(azArg[1]); - p->shellFlgs |= SHFLG_HeaderSet; - }else{ - raw_printf(stderr, "Usage: .headers on|off\n"); - rc = 1; - } - }else + sqlite3_recover_config(pr, 789, (void*)zRecoveryDb); /* Debug use only */ + sqlite3_recover_config(pr, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF); + sqlite3_recover_config(pr, SQLITE_RECOVER_ROWIDS, (void*)&bRowids); + sqlite3_recover_config(pr, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist); - if( c=='h' && cli_strncmp(azArg[0], "help", n)==0 ){ - if( nArg>=2 ){ - n = showHelp(p->out, azArg[1]); - if( n==0 ){ - utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]); - } - }else{ - showHelp(p->out, 0); - } - }else + sqlite3_recover_run(pr); + if( sqlite3_recover_errcode(pr)!=SQLITE_OK ){ + const char *zErr = sqlite3_recover_errmsg(pr); + int errCode = sqlite3_recover_errcode(pr); + raw_printf(stderr, "sql error: %s (%d)\n", zErr, errCode); + } + rc = sqlite3_recover_finish(pr); + return (rc!=SQLITE_OK)? DCR_Error : DCR_Ok; +} -#ifndef SQLITE_SHELL_FIDDLE - if( c=='i' && cli_strncmp(azArg[0], "import", n)==0 ){ - char *zTable = 0; /* Insert data into this table */ - char *zSchema = 0; /* within this schema (may default to "main") */ - char *zFile = 0; /* Name of file to extra content from */ - sqlite3_stmt *pStmt = NULL; /* A statement */ - int nCol; /* Number of columns in the table */ - int nByte; /* Number of bytes in an SQL string */ - int i, j; /* Loop counters */ - int needCommit; /* True to COMMIT or ROLLBACK at end */ - int nSep; /* Number of bytes in p->colSeparator[] */ - char *zSql; /* An SQL statement */ - char *zFullTabName; /* Table name with schema if applicable */ - ImportCtx sCtx; /* Reader context */ - char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */ - int eVerbose = 0; /* Larger for more console output */ - int nSkip = 0; /* Initial lines to skip */ - int useOutputMode = 1; /* Use output mode to determine separators */ - char *zCreate = 0; /* CREATE TABLE statement text */ - - failIfSafeMode(p, "cannot run .import in safe mode"); - memset(&sCtx, 0, sizeof(sCtx)); - if( p->mode==MODE_Ascii ){ - xRead = ascii_read_one_field; - }else{ - xRead = csv_read_one_field; - } +DISPATCHABLE_COMMAND( restore ? 2 3 ){ + int rc; + const char *zSrcFile; + const char *zDb; + sqlite3 *pSrc = 0; + sqlite3_backup *pBackup; + AnyResourceHolder arh = { 0, (GenericFreer)sqlite3_backup_finish }; + int nTimeout = 0; + + if( ISS(p)->bSafeMode ) return DCR_AbortError; + if( nArg==2 ){ + zSrcFile = azArg[1]; + zDb = "main"; + }else if( nArg==3 ){ + zSrcFile = azArg[2]; + zDb = azArg[1]; + }else{ + return DCR_TooMany; + } + rc = shell_check_nomem(sqlite3_open(zSrcFile, &pSrc)); + conn_holder(pSrc); + if( rc!=SQLITE_OK ){ + *pzErr = smprintf("cannot open \"%s\"\n", zSrcFile); + release_holder(); + return DCR_Error; + } + open_db(p, 0); + pBackup = sqlite3_backup_init(DBX(p), zDb, pSrc, "main"); + if( pBackup==0 ){ + *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p))); + release_holder(); + return DCR_Error; + } + arh.pAny = pBackup; + any_ref_holder(&arh); + while( (rc = shell_check_nomem(sqlite3_backup_step(pBackup,100)))==SQLITE_OK + || rc==SQLITE_BUSY ){ + if( rc==SQLITE_BUSY ){ + if( nTimeout++ >= 3 ) break; + sqlite3_sleep(100); + } + } + if( rc==SQLITE_DONE ){ + rc = 0; + }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){ + *pzErr = smprintf("source database is busy\n"); rc = 1; - for(i=1; iout, "ERROR: extra argument: \"%s\". Usage:\n", z); - showHelp(p->out, "import"); - goto meta_command_exit; - } - }else if( cli_strcmp(z,"-v")==0 ){ - eVerbose++; - }else if( cli_strcmp(z,"-schema")==0 && iout, "ERROR: unknown option: \"%s\". Usage:\n", z); - showHelp(p->out, "import"); - 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"); - goto meta_command_exit; - } - seenInterrupt = 0; - open_db(p, 0); - if( useOutputMode ){ - /* If neither the --csv or --ascii options are specified, then set - ** the column and row separator characters from the output mode. */ - nSep = strlen30(p->colSeparator); - if( nSep==0 ){ - raw_printf(stderr, - "Error: non-null column separator required for import\n"); - goto meta_command_exit; - } - if( nSep>1 ){ - raw_printf(stderr, - "Error: multi-character column separators not allowed" - " for import\n"); - goto meta_command_exit; - } - nSep = strlen30(p->rowSeparator); - if( nSep==0 ){ - raw_printf(stderr, - "Error: non-null row separator required for import\n"); - goto meta_command_exit; - } - if( nSep==2 && p->mode==MODE_Csv - && cli_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"); - goto meta_command_exit; - } - sCtx.cColSep = (u8)p->colSeparator[0]; - sCtx.cRowSep = (u8)p->rowSeparator[0]; - } - sCtx.zFile = zFile; - sCtx.nLine = 1; - if( sCtx.zFile[0]=='|' ){ -#ifdef SQLITE_OMIT_POPEN - raw_printf(stderr, "Error: pipes are not supported in this OS\n"); - goto meta_command_exit; -#else - sCtx.in = popen(sCtx.zFile+1, "r"); - sCtx.zFile = ""; - sCtx.xCloser = pclose; + }else{ + *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p))); + rc = 1; + } + release_holders(2); + return DCR_Ok|rc; +} + +/***************** + * The .scanstats and .schema commands + */ +COLLECT_HELP_TEXT[ + ".scanstats on|off|est Turn sqlite3_stmt_scanstatus() metrics on or off", + ".schema ?PATTERN? Show the CREATE statements matching PATTERN", + " Options:", + " --indent Try to pretty-print the schema", + " --nosys Omit objects whose names start with \"sqlite_\"", +]; +DISPATCHABLE_COMMAND( scanstats ? 2 2 ){ + if( cli_strcmp(azArg[1], "est")==0 ){ + ISS(p)->scanstatsOn = 2; + }else{ + ISS(p)->scanstatsOn = (u8)booleanValue(azArg[1]); + } + open_db(p, 0); + sqlite3_db_config(DBX(p), SQLITE_DBCONFIG_STMT_SCANSTATUS, + ISS(p)->scanstatsOn, (int*)0); +#ifndef SQLITE_ENABLE_STMT_SCANSTATUS + raw_printf(STD_ERR, "Warning: .scanstats not available in this build.\n"); #endif + return DCR_Ok; +} +DISPATCHABLE_COMMAND( schema ? 1 2 ){ + int rc = 0; + ShellText sSelect = {0}; + ShellInState *psi = ISS(p); + u8 useMode = MODE_Semi; + AnyResourceHolder arh = {psi, (GenericFreer)modePopper}; + RESOURCE_MARK(mark); + char *zErrMsg = 0; + const char *zDiv = "("; + const char *zName = 0; + int iSchema = 0; + int bDebug = 0; + int bNoSystemTabs = 0; + int ii; + + open_db(p, 0); + + initText(&sSelect); + for(ii=1; ii=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"); - } - sCtx.z = sqlite3_malloc64(120); - if( sCtx.z==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - /* Below, resources must be freed before exit. */ - while( (nSkip--)>0 ){ - while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){} - } - if( zSchema!=0 ){ - zFullTabName = sqlite3_mprintf("\"%w\".\"%w\"", zSchema, zTable); - }else{ - zFullTabName = sqlite3_mprintf("\"%w\"", zTable); - } - zSql = sqlite3_mprintf("SELECT * FROM %s", zFullTabName); - if( zSql==0 || zFullTabName==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - nByte = strlen30(zSql); - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ - if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){ - sqlite3 *dbCols = 0; - char *zRenames = 0; - char *zColDefs; - zCreate = sqlite3_mprintf("CREATE TABLE %s", zFullTabName); - while( xRead(&sCtx) ){ - zAutoColumn(sCtx.z, &dbCols, 0); - if( sCtx.cTerm!=sCtx.cColSep ) break; - } - zColDefs = zAutoColumn(0, &dbCols, &zRenames); - if( zRenames!=0 ){ - utf8_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr, - "Columns renamed during .import %s due to duplicates:\n" - "%s\n", sCtx.zFile, zRenames); - sqlite3_free(zRenames); - } - assert(dbCols==0); - if( zColDefs==0 ){ - utf8_printf(stderr,"%s: empty file\n", sCtx.zFile); - import_fail: - sqlite3_free(zCreate); - sqlite3_free(zSql); - sqlite3_free(zFullTabName); - import_cleanup(&sCtx); - rc = 1; - goto meta_command_exit; - } - zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs); - if( eVerbose>=1 ){ - utf8_printf(p->out, "%s\n", zCreate); - } - rc = sqlite3_exec(p->db, zCreate, 0, 0, 0); - if( rc ){ - utf8_printf(stderr, "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db)); - goto import_fail; - } - sqlite3_free(zCreate); - zCreate = 0; - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - } + return DCR_TooMany; + } + } + outputModePush(psi); /* May OOM fail. */ + any_ref_holder(&arh); + psi->showHeader = 0; + psi->cMode = psi->mode = useMode; + if( zName!=0 ){ + int isSchema = sqlite3_strlike(zName, "sqlite_master", '\\')==0 + || sqlite3_strlike(zName, "sqlite_schema", '\\')==0 + || sqlite3_strlike(zName,"sqlite_temp_master", '\\')==0 + || sqlite3_strlike(zName,"sqlite_temp_schema", '\\')==0; + if( isSchema ){ + char *new_argv[2], *new_colv[2]; + new_argv[0] = smprintf( + "CREATE TABLE %s (\n" + " type text,\n" + " name text,\n" + " tbl_name text,\n" + " rootpage integer,\n" + " sql text\n" + ")", zName); + shell_check_ooms(new_argv[0]); + sstr_holder(new_argv[0]); + new_argv[1] = 0; + new_colv[0] = "sql"; + new_colv[1] = 0; + callback(p, 1, new_argv, new_colv); + release_holder(); + } + } + if( zDiv ){ + sqlite3_stmt *pStmt = 0; + rc = s3_prepare_v2_noom(p->dbUser, + "SELECT name FROM pragma_database_list", + -1, &pStmt, 0); + stmt_ptr_holder(&pStmt); if( rc ){ - if (pStmt) sqlite3_finalize(pStmt); - utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db)); - goto import_fail; + *pzErr = smprintf("%s\n", sqlite3_errmsg(p->dbUser)); + RESOURCE_FREE(mark); + return DCR_Error; + } + text_ref_holder(&sSelect); + appendText(&sSelect, "SELECT sql FROM", 0); + iSchema = 0; + while( s3_step_noom(pStmt)==SQLITE_ROW ){ + const char *zDb = (const char*)sqlite3_column_text(pStmt, 0); + char zScNum[30]; + sqlite3_snprintf(sizeof(zScNum), zScNum, "%d", ++iSchema); + appendText(&sSelect, zDiv, 0); + zDiv = " UNION ALL "; + appendText(&sSelect, "SELECT shell_add_schema(sql,", 0); + if( sqlite3_stricmp(zDb, "main")!=0 ){ + appendText(&sSelect, zDb, '\''); + }else{ + appendText(&sSelect, "NULL", 0); + } + appendText(&sSelect, ",name) AS sql, type, tbl_name, name, rowid,", 0); + appendText(&sSelect, zScNum, 0); + appendText(&sSelect, " AS snum, ", 0); + appendText(&sSelect, zDb, '\''); + appendText(&sSelect, " AS sname FROM ", 0); + appendText(&sSelect, zDb, quoteChar(zDb)); + appendText(&sSelect, ".sqlite_schema", 0); } - sqlite3_free(zSql); - nCol = sqlite3_column_count(pStmt); sqlite3_finalize(pStmt); pStmt = 0; - if( nCol==0 ) return 0; /* no columns, no error */ - zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 ); - if( zSql==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName); - j = strlen30(zSql); - for(i=1; i=2 ){ - utf8_printf(p->out, "Insert using: %s\n", zSql); - } - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - if( rc ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); - if (pStmt) sqlite3_finalize(pStmt); - goto import_fail; +#ifndef SQLITE_OMIT_INTROSPECTION_PRAGMAS + if( zName ){ + appendText(&sSelect, + " UNION ALL SELECT shell_module_schema(name)," + " 'table', name, name, name, 9e+99, 'main'" + " FROM pragma_module_list", + 0); } - sqlite3_free(zSql); - sqlite3_free(zFullTabName); - needCommit = sqlite3_get_autocommit(p->db); - if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0); - do{ - int startLine = sCtx.nLine; - for(i=0; imode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; - sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); - if( 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++; - } + appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0); + appendText(&sSelect, zQarg, 0); + if( !bGlob ){ + appendText(&sSelect, " ESCAPE '\\' ", 0); } - }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); - } - }else -#endif /* !defined(SQLITE_SHELL_FIDDLE) */ - -#ifndef SQLITE_UNTESTABLE - if( c=='i' && cli_strncmp(azArg[0], "imposter", n)==0 ){ - char *zSql; - char *zCollist = 0; - sqlite3_stmt *pStmt; - int tnum = 0; - int isWO = 0; /* True if making an imposter of a WITHOUT ROWID table */ - int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */ - int i; - if( !ShellHasFlag(p,SHFLG_TestingMode) ){ - utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n", - "imposter"); - rc = 1; - goto meta_command_exit; - } - if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){ - utf8_printf(stderr, "Usage: .imposter INDEX IMPOSTER\n" - " .imposter off\n"); - /* Also allowed, but not documented: - ** - ** .imposter TABLE IMPOSTER - ** - ** where TABLE is a WITHOUT ROWID table. In that case, the - ** imposter is another WITHOUT ROWID table with the columns in - ** storage order. */ - rc = 1; - goto meta_command_exit; + appendText(&sSelect, " AND ", 0); + release_holder(); } - open_db(p, 0); - if( nArg==2 ){ - sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 1); - goto meta_command_exit; - } - zSql = sqlite3_mprintf( - "SELECT rootpage, 0 FROM sqlite_schema" - " WHERE name='%q' AND type='index'" - "UNION ALL " - "SELECT rootpage, 1 FROM sqlite_schema" - " WHERE name='%q' AND type='table'" - " AND sql LIKE '%%without%%rowid%%'", - azArg[1], azArg[1] - ); - sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); - if( sqlite3_step(pStmt)==SQLITE_ROW ){ - tnum = sqlite3_column_int(pStmt, 0); - isWO = sqlite3_column_int(pStmt, 1); + if( bNoSystemTabs ){ + appendText(&sSelect, "name NOT LIKE 'sqlite_%%' AND ", 0); } - sqlite3_finalize(pStmt); - zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]); - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); - i = 0; - while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ - char zLabel[20]; - const char *zCol = (const char*)sqlite3_column_text(pStmt,2); - i++; - if( zCol==0 ){ - if( sqlite3_column_int(pStmt,1)==-1 ){ - zCol = "_ROWID_"; - }else{ - sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i); - zCol = zLabel; - } - } - if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){ - lenPK = (int)strlen(zCollist); - } - if( zCollist==0 ){ - zCollist = sqlite3_mprintf("\"%w\"", zCol); - }else{ - zCollist = sqlite3_mprintf("%z,\"%w\"", zCollist, zCol); - } + appendText(&sSelect, "sql IS NOT NULL" + " ORDER BY snum, rowid", 0); + if( bDebug ){ + utf8_printf(psi->out, "SQL: %s;\n", sSelect.z); + }else{ + rc = sqlite3_exec(p->dbUser, sSelect.z, callback, p, &zErrMsg); } - sqlite3_finalize(pStmt); - if( i==0 || tnum==0 ){ - utf8_printf(stderr, "no such index: \"%s\"\n", azArg[1]); - rc = 1; - sqlite3_free(zCollist); - goto meta_command_exit; - } - if( lenPK==0 ) lenPK = 100000; - zSql = sqlite3_mprintf( - "CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))WITHOUT ROWID", - azArg[2], zCollist, lenPK, zCollist); - sqlite3_free(zCollist); - rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 1, tnum); - if( rc==SQLITE_OK ){ - rc = sqlite3_exec(p->db, zSql, 0, 0, 0); - sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0); - if( rc ){ - utf8_printf(stderr, "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db)); - }else{ - utf8_printf(stdout, "%s;\n", zSql); - raw_printf(stdout, - "WARNING: writing to an imposter table will corrupt the \"%s\" %s!\n", - azArg[1], isWO ? "table" : "index" - ); - } + } + RESOURCE_FREE(mark); + if( zErrMsg ){ + *pzErr = zErrMsg; + return DCR_Error; + }else if( rc != SQLITE_OK ){ + *pzErr = smprintf("Error: querying schema information\n"); + return DCR_Error; + }else{ + return DCR_Ok; + } +} + +/***************** + * The .selecttrace, .separator, .session and .sha3sum commands + */ +CONDITION_COMMAND( session defined(SQLITE_ENABLE_SESSION) ); +COLLECT_HELP_TEXT[ + ".separator COL ?ROW? Change the column and row separators", + ".session ?NAME? CMD ... Create or control sessions", + " Subcommands:", + " attach TABLE Attach TABLE", + " changeset FILE Write a changeset into FILE", + " close Close one session", + " enable ?BOOLEAN? Set or query the enable bit", + " filter GLOB... Reject tables matching GLOBs", + " indirect ?BOOLEAN? Mark or query the indirect status", + " isempty Query whether the session is empty", + " list List currently open session names", + " open DB NAME Open a new session on DB", + " patchset FILE Write a patchset into FILE", + " If ?NAME? is omitted, the first defined session is used.", + ".sha3sum ... Compute a SHA3 hash of database content", + " Options:", + " --schema Also hash the sqlite_schema table", + " --sha3-224 Use the sha3-224 algorithm", + " --sha3-256 Use the sha3-256 algorithm (default)", + " --sha3-384 Use the sha3-384 algorithm", + " --sha3-512 Use the sha3-512 algorithm", + " Any other argument is a LIKE pattern for tables to hash", +]; +DISPATCHABLE_COMMAND( separator ? 2 3 ){ + if( nArg>=2 ){ + sqlite3_snprintf(sizeof(ISS(p)->colSeparator), ISS(p)->colSeparator, + "%.*s", (int)ArraySize(ISS(p)->colSeparator)-1, azArg[1]); + } + if( nArg>=3 ){ + sqlite3_snprintf(sizeof(ISS(p)->rowSeparator), ISS(p)->rowSeparator, + "%.*s", (int)ArraySize(ISS(p)->rowSeparator)-1, azArg[2]); + } + return DCR_Ok; +} +DISPATCHABLE_COMMAND( session 3 2 0 ){ + int rc = 0; + struct AuxDb *pAuxDb = ISS(p)->pAuxDb; + OpenSession *pSession = &pAuxDb->aSession[0]; + FILE *out = ISS(p)->out; + char **azCmd = &azArg[1]; + int iSes = 0; + int nCmd = nArg - 1; + int i; + open_db(p, 0); + if( nArg>=3 ){ + for(iSes=0; iSesnSession; iSes++){ + if( cli_strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break; + } + if( iSesnSession ){ + pSession = &pAuxDb->aSession[iSes]; + azCmd++; + nCmd--; }else{ - raw_printf(stderr, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); - rc = 1; + pSession = &pAuxDb->aSession[0]; + iSes = 0; } - sqlite3_free(zSql); - }else -#endif /* !defined(SQLITE_OMIT_TEST_CONTROL) */ + } -#ifdef SQLITE_ENABLE_IOTRACE - if( c=='i' && cli_strncmp(azArg[0], "iotrace", n)==0 ){ - SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...); - if( iotrace && iotrace!=stdout ) fclose(iotrace); - iotrace = 0; - if( nArg<2 ){ - sqlite3IoTrace = 0; - }else if( cli_strcmp(azArg[1], "-")==0 ){ - sqlite3IoTrace = iotracePrintf; - iotrace = stdout; + /* .session attach TABLE + ** Invoke the sqlite3session_attach() interface to attach a particular + ** table so that it is never filtered. + */ + if( cli_strcmp(azCmd[0],"attach")==0 ){ + if( nCmd!=2 ) goto session_syntax_error; + if( pSession->p==0 ){ + session_not_open: + raw_printf(STD_ERR, "ERROR: No sessions are open\n"); }else{ - iotrace = fopen(azArg[1], "w"); - if( iotrace==0 ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]); - sqlite3IoTrace = 0; - rc = 1; - }else{ - sqlite3IoTrace = iotracePrintf; + rc = sqlite3session_attach(pSession->p, azCmd[1]); + if( rc ){ + raw_printf(STD_ERR, "ERROR: sqlite3session_attach() returns %d\n", rc); + rc = 0; } } }else @@@ -15471,549 -11431,209 +15500,548 @@@ static int line_is_complete(char *zSql } /* -** Run a single line of SQL. Return the number of errors. +** Run a single line of SQL. Return the number of errors. +** bAltIn indicates that input has been redirected in some way. +*/ +static int runOneSqlLine(ShellExState *psx, char *zSql, + int bAltIn, int startline){ + int rc; + char *zErrMsg = 0; + RipStackDest sqlRipDest = RIP_STACK_DEST_INIT; + + open_db(psx, 0); + sstr_ptr_holder(&zErrMsg); + if( ShellHasFlag(psx,SHFLG_Backslash) ) resolve_backslashes(zSql); + if( ISS(psx)->flgProgress & SHELL_PROGRESS_RESET ) ISS(psx)->nProgress = 0; + BEGIN_TIMER; + register_exit_ripper(&sqlRipDest); + if( 0==RIP_TO_HERE(sqlRipDest) ){ + rc = shell_exec(psx, zSql, &zErrMsg); + forget_exit_ripper(&sqlRipDest); + }else{ + psx->shellAbruptExit = 0x102; + rc = 1; + } + END_TIMER; + if( rc || zErrMsg ){ + char zPrefix[100]; + const char *zErrorTail; + const char *zErrorType; + if( psx->shellAbruptExit==0 ){ + if( zErrMsg==0 ){ + zErrorType = "Error"; + zErrorTail = sqlite3_errmsg(DBX(psx)); + }else if( cli_strncmp(zErrMsg, "in prepare, ",12)==0 ){ + zErrorType = "Parse error"; + zErrorTail = &zErrMsg[12]; + }else if( cli_strncmp(zErrMsg, "stepping, ", 10)==0 ){ + zErrorType = "Runtime error"; + zErrorTail = &zErrMsg[10]; + }else{ + zErrorType = "Error"; + zErrorTail = zErrMsg; + } + if( bAltIn || !stdin_is_interactive ){ + sqlite3_snprintf(sizeof(zPrefix), zPrefix, + "%s near line %d:", zErrorType, startline); + }else{ + sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType); + } + utf8_printf(STD_ERR, "%s %s\n", zPrefix, zErrorTail); + } + rc = 1; + }else if( ShellHasFlag(psx, SHFLG_CountChanges) ){ + char zLineBuf[36+2*20]; + sqlite3_snprintf(sizeof(zLineBuf), zLineBuf, + "changes: %lld total_changes: %lld", + sqlite3_changes64(DBX(psx)), sqlite3_total_changes64(DBX(psx))); + raw_printf(ISS(psx)->out, "%s\n", zLineBuf); + } + release_holder(); + return rc; +} + +#if SHELL_EXTENDED_PARSING +/* Resumable line classsifier for dot-commands +** +** Determines if a dot-command is open, having either an unclosed +** quoted argument or an escape sequence opener ('\') at its end. +** +** The FSM design/behavior assumes/requires that a terminating '\' +** is not part of the character sequence being classified -- that +** it represents an escaped newline which is removed as physical +** lines are spliced to accumulate logical lines. +** +** The line or added line-portion is passed as zCmd. +** The pScanState pointer must reference an (opaque) DCmd_ScanState, +** which must be set to DCSS_Start to initialize the scanner state. +** Resumed scanning should always be done with zCmd logically just +** past the last non-0 char of the text previously passed in, with +** any previously scanned, trailing newline escape first trimmed. +** Returns are: 0 => not open (aka complete), 1 => is open (incomplete) +** The following macros may be applied to the scan state: +*/ +#define DCSS_InDarkArg(dcss) (((dcss)&argPosMask)==inDqArg) +#define DCSS_EndEscaped(dcss) (((dcss)&endEscaped)!=0) +#define DCSS_IsOpen(dcss) (((dcss)&isOpenMask)!=0) +typedef enum { + DCSS_Start = 0, + twixtArgs = 0, inSqArg = 1, inDarkArg = 2, inDqArg = 3, /* ordered */ + endEscaped = 4, /* bit used */ + argPosMask = 3, /* bits used */ + isOpenMask = 1|4 /* bit test */ +} DCmd_ScanState; + +static void dot_command_scan(char *zCmd, DCmd_ScanState *pScanState, + SCAN_TRACKER_REFTYPE pst){ + DCmd_ScanState ss = *pScanState & ~endEscaped; + char c = (ss&isOpenMask)? 1 : *zCmd++; + while( c!=0 ){ + switch( ss ){ + case twixtArgs: + CONTINUE_PROMPT_AWAITC(pst, 0); + while( IsSpace(c) ){ + if( (c=*zCmd++)==0 ) goto atEnd; + } + switch( c ){ + case '\\': + if( *zCmd==0 ){ + ss |= endEscaped; + goto atEnd; + }else goto inDark; + case '\'': ss = inSqArg; goto inSq; + case '"': ss = inDqArg; goto inDq; + default: ss = inDarkArg; goto inDark; + } + inSq: + case inSqArg: + CONTINUE_PROMPT_AWAITC(pst, '\''); + while( (c=*zCmd++)!='\'' ){ + if( c==0 ) goto atEnd; + if( c=='\\' && *zCmd==0 ){ + ss |= endEscaped; + goto atEnd; + } + } + ss = twixtArgs; + c = *zCmd++; + continue; + inDq: + case inDqArg: + CONTINUE_PROMPT_AWAITC(pst, '"'); + do { + if( (c=*zCmd++)==0 ) goto atEnd; + if( c=='\\' ){ + if( (c=*zCmd++)==0 ){ + ss |= endEscaped; + goto atEnd; + } + if( (c=*zCmd++)==0 ) goto atEnd; + } + } while( c!='"' ); + ss = twixtArgs; + c = *zCmd++; + continue; + inDark: + case inDarkArg: + CONTINUE_PROMPT_AWAITC(pst, 0); + while( !IsSpace(c) ){ + if( c=='\\' && *zCmd==0 ){ + ss |= endEscaped; + goto atEnd; + } + if( (c=*zCmd++)==0 ) goto atEnd; + } + ss = twixtArgs; + c = *zCmd++; + continue; + case endEscaped: case isOpenMask: default: + ; /* Not reachable, but quiet compilers unable to see this. */ + } + } + atEnd: + *pScanState = ss; +} +#else +# define dot_command_scan(x,y,z) +#endif + +/* Utility functions for process_input. */ + +#if SHELL_EXTENDED_PARSING +/* +** Process dot-command line with its scan state to: +** 1. Setup for requested line-splicing; and +** 2. Say whether it is complete. +** The last two out parameters are the line's length, which may be +** adjusted, and the char to be used for joining a subsequent line. +** This is broken out of process_input() mainly for readability. +** The return is TRUE for dot-command ready to run, else false. */ -static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ - int rc; - char *zErrMsg = 0; - - open_db(p, 0); - if( ShellHasFlag(p,SHFLG_Backslash) ) resolve_backslashes(zSql); - if( p->flgProgress & SHELL_PROGRESS_RESET ) p->nProgress = 0; - BEGIN_TIMER; - rc = shell_exec(p, zSql, &zErrMsg); - END_TIMER; - if( rc || zErrMsg ){ - char zPrefix[100]; - const char *zErrorTail; - const char *zErrorType; - if( zErrMsg==0 ){ - zErrorType = "Error"; - zErrorTail = sqlite3_errmsg(p->db); - }else if( cli_strncmp(zErrMsg, "in prepare, ",12)==0 ){ - zErrorType = "Parse error"; - zErrorTail = &zErrMsg[12]; - }else if( cli_strncmp(zErrMsg, "stepping, ", 10)==0 ){ - zErrorType = "Runtime error"; - zErrorTail = &zErrMsg[10]; - }else{ - zErrorType = "Error"; - zErrorTail = zErrMsg; - } - if( in!=0 || !stdin_is_interactive ){ - sqlite3_snprintf(sizeof(zPrefix), zPrefix, - "%s near line %d:", zErrorType, startline); - }else{ - sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType); - } - utf8_printf(stderr, "%s %s\n", zPrefix, zErrorTail); - sqlite3_free(zErrMsg); - zErrMsg = 0; - return 1; - }else if( ShellHasFlag(p, SHFLG_CountChanges) ){ - char zLineBuf[2000]; - sqlite3_snprintf(sizeof(zLineBuf), zLineBuf, - "changes: %lld total_changes: %lld", - sqlite3_changes64(p->db), sqlite3_total_changes64(p->db)); - raw_printf(p->out, "%s\n", zLineBuf); +static int line_join_done(DCmd_ScanState dcss, char *zLine, + i64 *pnLength, char *pcLE){ + /* It is ready only if has no open argument or escaped newline. */ + int bOpen = DCSS_IsOpen(dcss); + if( !DCSS_EndEscaped(dcss) ){ + *pcLE = '\n'; + return !bOpen; + }else{ + *pcLE = (bOpen || DCSS_InDarkArg(dcss))? 0 : ' '; + /* Swallow the trailing escape character. */ + zLine[--*pnLength] = 0; + return 0; } - return 0; -} - -static void echo_group_input(ShellState *p, const char *zDo){ - if( ShellHasFlag(p, SHFLG_Echo) ) utf8_printf(p->out, "%s\n", zDo); } +#endif -#ifdef SQLITE_SHELL_FIDDLE /* -** Alternate one_input_line() impl for wasm mode. This is not in the primary -** impl because we need the global shellState and cannot access it from that -** function without moving lots of code around (creating a larger/messier diff). +** Grow the accumulation line buffer to accommodate ncNeed chars. +** In/out parameters pz and pna reference the buffer and its size. +** The buffer must eventually be sqlite3_free()'ed by the caller. */ -static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ - /* Parse the next line from shellState.wasm.zInput. */ - const char *zBegin = shellState.wasm.zPos; - const char *z = zBegin; - char *zLine = 0; - i64 nZ = 0; +static void grow_line_buffer(char **pz, i64 *pna, int ncNeed){ - UNUSED_PARAMETER(in); - UNUSED_PARAMETER(isContinuation); - if(!z || !*z){ - return 0; + if( ncNeed > *pna ){ + *pna += *pna + (*pna>>1) + 100; + *pz = sqlite3_realloc(*pz, *pna); + shell_check_ooms(*pz); } - while(*z && isspace(*z)) ++z; - zBegin = z; - for(; *z && '\n'!=*z; ++nZ, ++z){} - if(nZ>0 && '\r'==zBegin[nZ-1]){ - --nZ; - } - shellState.wasm.zPos = z; - zLine = realloc(zPrior, nZ+1); - shell_check_oom(zLine); - memcpy(zLine, zBegin, nZ); - zLine[nZ] = 0; - return zLine; } -#endif /* SQLITE_SHELL_FIDDLE */ /* -** Read input from *in and process it. If *in==0 then input -** is interactive - the user is typing it it. Otherwise, input -** is coming from a file or device. A prompt is issued and history -** is saved only if input is interactive. An interrupt signal will -** cause this routine to exit immediately, unless input is interactive. +** Read input from designated source (p->pInSource) and process it. +** If pInSource==0 then input is interactive - the user is typing it. +** Otherwise, input is coming from a file, stream device or string. +** Prompts issue and history is saved only for interactive input. +** An interrupt signal will cause this routine to exit immediately, +** with "exit demanded" code returned, unless input is interactive. ** -** Return the number of errors. -*/ -static int process_input(ShellState *p){ - char *zLine = 0; /* A single input line */ - char *zSql = 0; /* Accumulated SQL text */ - i64 nLine; /* Length of current line */ - i64 nSql = 0; /* Bytes of zSql[] used */ - i64 nAlloc = 0; /* Allocated zSql[] space */ - int rc; /* Error code */ - int errCnt = 0; /* Number of errors seen */ - i64 startline = 0; /* Line number for start of current input */ - QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */ - - if( p->inputNesting==MAX_INPUT_NESTING ){ - /* This will be more informative in a later version. */ - utf8_printf(stderr,"Input nesting limit (%d) reached at line %d." - " Check recursion.\n", MAX_INPUT_NESTING, p->lineno); - return 1; - } - ++p->inputNesting; - p->lineno = 0; - CONTINUE_PROMPT_RESET; - while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){ - fflush(p->out); - zLine = one_input_line(p->in, zLine, nSql>0); - if( zLine==0 ){ - /* End of input */ - if( p->in==0 && stdin_is_interactive ) printf("\n"); - break; - } - if( seenInterrupt ){ - if( p->in!=0 ) break; - seenInterrupt = 0; - } - p->lineno++; - if( QSS_INPLAIN(qss) - && line_is_command_terminator(zLine) - && line_is_complete(zSql, nSql) ){ - memcpy(zLine,";",2); - } - qss = quickscan(zLine, qss, CONTINUE_PROMPT_PSTATE); - if( QSS_PLAINWHITE(qss) && nSql==0 ){ - /* Just swallow single-line whitespace */ - echo_group_input(p, zLine); - qss = QSS_Start; - continue; - } - if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){ - CONTINUE_PROMPT_RESET; - echo_group_input(p, zLine); - if( zLine[0]=='.' ){ - rc = do_meta_command(zLine, p); - if( rc==2 ){ /* exit requested */ +** Returns are the post-execute values of enum DotCmdRC: +** DCR_Ok, DCR_Return, DCR_Exit, DCR_Abort +** each of which may be bit-wise or'ed with DCR_Error. +*/ +static DotCmdRC process_input(ShellInState *psi){ + char *zLineInput = 0; /* a line-at-a-time input buffer or usable result */ + char *zLineAccum = 0; /* accumulation buffer, used for multi-line input */ + /* Above two pointers could be local to the group handling loop, but are + * not so that the number of memory allocations can be reduced. They are + * reused from one incoming group to another, realloc()'ed as needed. */ + i64 naAccum = 0; /* tracking how big zLineAccum buffer has become */ - /* Some flags for ending the overall group processing loop, always 1 or 0 */ - u8 bInputEnd=0, bInterrupted=0; ++ /* Flag for ending the overall group processing loop, always 1 or 0 */ ++ u8 bInputEnd = 0; + /* Termination kind: DCR_Ok, DCR_Error, DCR_Return, DCR_Exit, DCR_Abort, + * the greatest of whichever is applicable */ + u8 termKind = (XSS(psi)->shellAbruptExit==0)? DCR_Ok : DCR_Exit; + /* Flag to indicate input from shell invocation argument. */ + u8 bInvokeArg = INSOURCE_IS_INVOKEARG(psi->pInSource); + /* Flag to affect prompting and interrupt action */ + u8 bInteractive = INSOURCE_IS_INTERACTIVE(psi->pInSource); + int nErrors = 0; /* count of errors during execution or its prep */ + + /* Block overly-recursive or absurdly nested input redirects. */ + if( psi->inputNesting>=MAX_INPUT_NESTING ){ + InSource *pInSrc = psi->pInSource->pFrom; + const char *zLead = "Input nesting limit (" + SHELL_STRINGIFY(MAX_INPUT_NESTING)") reached,"; + int i = 3; + assert(pInSrc!=0 && MAX_INPUT_NESTING>0); + while( i-->0 && pInSrc!=0 ){ + utf8_printf(STD_ERR, + "%s from line %d of \"%s\"", + zLead, pInSrc->lineno, pInSrc->zSourceSay); + zLead = (i%2==0)? "\n" : ""; + pInSrc=pInSrc->pFrom; + } + utf8_printf(STD_ERR, " ...\nError: Check recursion.\n"); + return DCR_Error; + } + ++psi->inputNesting; + + /* line-group processing loop (per SQL block, dot-command or comment) */ - while( !bInputEnd && termKind==DCR_Ok && !bInterrupted ){ ++ while( !bInputEnd && termKind==DCR_Ok ){ +#if SHELL_DYNAMIC_EXTENSION + ScriptSupport *pSS = psi->script; +#endif + int nGroupLines = 0; /* count of lines belonging to this group */ + i64 ncLineIn = 0; /* how many (non-zero) chars are in zLineInput */ + i64 ncLineAcc = 0; /* how many (non-zero) chars are in zLineAccum */ + i64 iLastLine = 0; /* index of last accumulated line start */ + /* Initialize resumable scanner(s). */ + SqlScanState sqScanState = SSS_Start; /* for SQL scan */ +#if SHELL_EXTENDED_PARSING + DCmd_ScanState dcScanState = DCSS_Start; /* for dot-command scan */ + int nLeadWhite = 0; /* skips over initial whitespace to . or # */ + char cLineEnd = '\n'; /* May be swallowed or replaced with space. */ +#else +# define nLeadWhite 0 /* For legacy parsing, no white before . or # . */ +# define cLineEnd '\n' /* For legacy parsing, this always joins lines. */ +#endif + /* An ordered enum to record kind of incoming line group. Its ordering + * means than a value greater than Comment implies something runnable. + */ + enum { Tbd = 0, Eof, Comment, Sql, Cmd +#if SHELL_DYNAMIC_EXTENSION + , Script +#endif + } inKind = Tbd; + /* An enum signifying the group disposition state */ + enum { + Incoming, Runnable, Dumpable, Erroneous, Ignore + } disposition = Incoming; + char **pzLineUse = &zLineInput; /* ref line to be processed */ + i64 *pncLineUse = &ncLineIn; /* ref that line's char count */ + int iStartline = 0; /* starting line number of group */ + + seenInterrupt = 0; + fflush(psi->out); + CONTINUE_PROMPT_RESET; + zLineInput = one_input_line(psi->pInSource, zLineInput, + nGroupLines>0, &shellPrompts); + if( zLineInput==0 ){ + bInputEnd = 1; + inKind = Eof; + disposition = Ignore; + if( bInteractive ) printf("\n"); + }else{ + ++nGroupLines; + iStartline = psi->pInSource->lineno; + ncLineIn = strlen30(zLineInput); + if( seenInterrupt ){ - if( psi->pInSource!=0 ) break; - bInterrupted = 1; /* This will be honored, or not, later. */ + seenInterrupt = 0; + disposition = Dumpable; - } - /* Classify and check for single-line dispositions, prep for more. */ ++ }else{ ++ /* Classify and check for single-line dispositions, prep for more. */ +#if SHELL_EXTENDED_PARSING - nLeadWhite = (SHEXT_PARSING(psi)) - ? skipWhite(zLineInput)-zLineInput - : 0; /* Disallow leading whitespace for . or # in legacy mode. */ ++ nLeadWhite = (SHEXT_PARSING(psi)) ++ ? skipWhite(zLineInput)-zLineInput ++ : 0; /* Disallow leading whitespace for . or # in legacy mode. */ +#endif +#if SHELL_DYNAMIC_EXTENSION - if( pSS && pSS->pMethods->isScriptLeader(pSS, zLineInput+nLeadWhite) ){ - inKind = Script; - }else ++ if( pSS && pSS->pMethods->isScriptLeader(pSS, zLineInput+nLeadWhite) ){ ++ inKind = Script; ++ }else +#endif - { - switch( zLineInput[nLeadWhite] ){ - case '.': - inKind = Cmd; - dot_command_scan(zLineInput+nLeadWhite, &dcScanState, - CONTINUE_PROMPT_PSTATE); - break; - case '#': - inKind = Comment; - break; - default: - /* Might be SQL, or a swallowable whole SQL comment. */ - sql_prescan(zLineInput, &sqScanState, CONTINUE_PROMPT_PSTATE); - if( SSS_PLAINWHITE(sqScanState) ){ - /* It's either all blank or a whole SQL comment. Swallowable. */ ++ { ++ switch( zLineInput[nLeadWhite] ){ ++ case '.': ++ inKind = Cmd; ++ dot_command_scan(zLineInput+nLeadWhite, &dcScanState, ++ CONTINUE_PROMPT_PSTATE); ++ break; ++ case '#': + inKind = Comment; - }else{ - /* Something dark, not a # comment or dot-command. Must be SQL. */ - inKind = Sql; ++ break; ++ default: ++ /* Might be SQL, or a swallowable whole SQL comment. */ ++ sql_prescan(zLineInput, &sqScanState, CONTINUE_PROMPT_PSTATE); ++ if( SSS_PLAINWHITE(sqScanState) ){ ++ /* It's either all blank or a whole SQL comment. Swallowable. */ ++ inKind = Comment; ++ }else{ ++ /* Something dark, not a # comment or dot-command. Must be SQL. */ ++ inKind = Sql; ++ } ++ break; + } - break; + } + } + } /* end read/classify initial group input line */ + + /* Here, if not at end of input, the initial line of group is in, and + * it has been scanned and classified. Next, do the processing needed + * to recognize whether the initial line or accumulated group so far + * is complete such that it may be run, and perform joining of more + * lines into the group while it is not so complete. This loop ends + * with the input group line(s) ready to be run, or if the input ends + * before it is ready, with the group marked as erroneous. + */ + while( disposition==Incoming ){ + PROMPTS_UPDATE(inKind == Sql || inKind == Cmd); + /* Check whether more to accumulate, or ready for final disposition. */ + switch( inKind ){ + case Comment: + disposition = Dumpable; + case Cmd: +#if SHELL_EXTENDED_PARSING + if( SHEXT_PARSING(psi) ){ + if( line_join_done(dcScanState, *pzLineUse, pncLineUse, &cLineEnd) ){ + disposition = Runnable; + } + }else +#endif + disposition = Runnable; /* Legacy, any dot-command line is ready. */ + break; +#if SHELL_DYNAMIC_EXTENSION + case Script: + if( pSS==0 + || pSS->pMethods->scriptIsComplete(pSS, *pzLineUse+nLeadWhite, 0) ){ + disposition = Runnable; + } + break; +#endif + case Sql: + /* Check to see if it is complete and ready to run. */ + if( SSS_SEMITERM(sqScanState) && 1==sqlite3_complete(*pzLineUse)){ + disposition = Runnable; + }else if( SSS_PLAINWHITE(sqScanState) ){ + /* It is a leading single-line or multi-line comment. */ + disposition = Runnable; + inKind = Comment; + }else{ + char *zT = line_is_command_terminator(zLineInput); + if( zT!=0 ){ + /* Last line is a lone go or / -- prep for running it. */ + if( nGroupLines>1 ){ + disposition = Runnable; + memcpy(*pzLineUse+iLastLine,";\n",3); + *pncLineUse = iLastLine + 2; + }else{ + /* Unless nothing preceded it, then dump it. */ + disposition = Dumpable; + } + } + } + break; + case Tbd: case Eof: default: assert(0); /* Not reachable */ + } /* end switch on inKind */ + /* Collect and accumulate more input if group not yet complete. */ + if( disposition==Incoming ){ + if( nGroupLines==1 ){ + grow_line_buffer(&zLineAccum, &naAccum, ncLineIn+2); + /* Copy line just input */ + memcpy(zLineAccum, zLineInput, ncLineIn); + zLineAccum[ncLineIn] = 0; + ncLineAcc = ncLineIn; + pzLineUse = &zLineAccum; + pncLineUse = &ncLineAcc; + } + /* Read in next line of group, (if available.) */ + zLineInput = one_input_line(psi->pInSource, zLineInput, + nGroupLines>0, &shellPrompts); + if( zLineInput==0 ){ + bInputEnd = 1; + if( inKind==Sql && bInvokeArg ){ + /* As a special dispensation, SQL arguments on the command line + ** do not need to end with ';' (or a lone go.) */ + if( nGroupLines>1 ){ + grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+2); + } + strcpy( zLineAccum+ncLineAcc, ";" ); + if( 1==sqlite3_complete(*pzLineUse) ){ + zLineAccum[ncLineAcc] = 0; + disposition = Runnable; + continue; + } + } + disposition = Erroneous; + inKind = Eof; + if( bInteractive ) printf("\n"); + continue; + } + ++nGroupLines; + ncLineIn = strlen30(zLineInput); + /* Scan line just input (if needed) and append to accumulation. */ + switch( inKind ){ + case Cmd: + dot_command_scan(zLineInput, &dcScanState, CONTINUE_PROMPT_PSTATE); + break; + case Sql: + sql_prescan(zLineInput, &sqScanState, CONTINUE_PROMPT_PSTATE); + break; + default: break; - }else if( rc ){ - errCnt++; } + grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+ncLineIn+2); + /* Join lines as setup by exam of previous line(s). */ + if( cLineEnd!=0 ) zLineAccum[ncLineAcc++] = cLineEnd; +#if SHELL_EXTENDED_PARSING + cLineEnd = '\n'; /* reset to default after use */ +#endif + memcpy(zLineAccum+ncLineAcc, zLineInput, ncLineIn); + iLastLine = ncLineAcc; + ncLineAcc += ncLineIn; + zLineAccum[ncLineAcc] = 0; + } /* end glom another line */ + } /* end group collection loop */ + /* Here, the group is fully collected or known to be incomplete forever. */ + CONTINUE_PROMPT_RESET; + switch( disposition ){ + case Dumpable: + echo_group_input(psi, *pzLineUse); +#if SHELL_DYNAMIC_EXTENSION + if( inKind==Script && pSS!=0 ) pSS->pMethods->resetCompletionScan(pSS); +#endif + break; + case Runnable: + switch( inKind ){ + case Sql: + echo_group_input(psi, *pzLineUse); + nErrors += runOneSqlLine(XSS(psi), *pzLineUse, + INSOURCE_IS_INTERACTIVE(psi->pInSource), + iStartline); + break; + case Cmd: { + DotCmdRC dcr; + echo_group_input(psi, *pzLineUse); + dcr = do_dot_command(*pzLineUse+nLeadWhite, XSS(psi)); + nErrors += (dcr & DCR_Error); + dcr &= ~DCR_Error; + if( dcr > termKind ) termKind = dcr; + break; } - qss = QSS_Start; - continue; - } - /* No single-line dispositions remain; accumulate line(s). */ - nLine = strlen(zLine); - if( nSql+nLine+2>=nAlloc ){ - /* Grow buffer by half-again increments when big. */ - nAlloc = nSql+(nSql>>1)+nLine+100; - zSql = realloc(zSql, nAlloc); - shell_check_oom(zSql); - } - if( nSql==0 ){ - i64 i; - for(i=0; zLine[i] && IsSpace(zLine[i]); i++){} - assert( nAlloc>0 && zSql!=0 ); - memcpy(zSql, zLine+i, nLine+1-i); - startline = p->lineno; - nSql = nLine-i; - }else{ - zSql[nSql++] = '\n'; - memcpy(zSql+nSql, zLine, nLine+1); - nSql += nLine; - } - if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){ - echo_group_input(p, zSql); - errCnt += runOneSqlLine(p, zSql, p->in, startline); - CONTINUE_PROMPT_RESET; - nSql = 0; - if( p->outCount ){ - output_reset(p); - p->outCount = 0; - }else{ - clearTempFile(p); +#if SHELL_DYNAMIC_EXTENSION + case Script: { + char *zErr = 0; + DotCmdRC dcr; + assert(pSS!=0); + /* Consider: Should echo flag be honored here? */ + pSS->pMethods->resetCompletionScan(pSS); + dcr = pSS->pMethods->runScript(pSS, *pzLineUse+nLeadWhite, + XSS(psi), &zErr); + if( dcr!=DCR_Ok || zErr!=0 ){ + /* Future: Handle errors more informatively and like dot commands. */ + nErrors += (dcr!=DCR_Ok); + if( zErr!=0 ){ + utf8_printf(STD_ERR, "Error: %s\n", zErr); + sqlite3_free(zErr); + } + } + break; } - p->bSafeMode = p->bSafeModePersist; - qss = QSS_Start; - }else if( nSql && QSS_PLAINWHITE(qss) ){ - echo_group_input(p, zSql); - nSql = 0; - qss = QSS_Start; +#endif + default: + assert(inKind!=Tbd); + break; + } + if( XSS(psi)->shellAbruptExit!=0 ){ + termKind = DCR_Exit; + } + break; + case Erroneous: + utf8_printf(STD_ERR, "Error: Input incomplete at line %d of \"%s\"\n", + psi->pInSource->lineno, psi->pInSource->zSourceSay); +#if SHELL_DYNAMIC_EXTENSION + if( inKind==Script && pSS!=0 ) pSS->pMethods->resetCompletionScan(pSS); +#endif + ++nErrors; + break; + case Ignore: + break; + default: assert(0); } + if( bail_on_error && nErrors>0 && termKind==DCR_Ok ) termKind = DCR_Error; + } /* end group consume/prep/(run, dump or complain) loop */ + + /* Cleanup and determine return value based on flags and error count. */ + free(zLineInput); /* Allocated via malloc() by readline or equivalents. */ + sqlite3_free(zLineAccum); + + /* Translate DCR_Return because it has been done here, not to propagate + * unless input is from shell invocation argument. */ + if( termKind==DCR_Return && psi->pInSource!=&cmdInSource ){ + termKind = DCR_Ok; } - if( nSql ){ - /* This may be incomplete. Let the SQL parser deal with that. */ - echo_group_input(p, zSql); - errCnt += runOneSqlLine(p, zSql, p->in, startline); - CONTINUE_PROMPT_RESET; - } - free(zSql); - free(zLine); - --p->inputNesting; - return errCnt>0; + return termKind|(nErrors>0); } /* @@@ -16246,11 -11860,11 +16274,10 @@@ static void usage(int showDetail) "FILENAME is the name of an SQLite database. A new database is created\n" "if the file does not previously exist. Defaults to :memory:.\n", Argv0); if( showDetail ){ - utf8_printf(stderr, "OPTIONS include:\n%s", zOptions); + utf8_printf(STD_ERR, "OPTIONS include:\n%s", zOptions); }else{ - raw_printf(stderr, "Use the -help option for additional information\n"); + raw_printf(STD_ERR, "Use the -help option for additional information\n"); } -- exit(1); } /* @@@ -16264,97 -11878,28 +16291,222 @@@ static void verify_uninitialized(void) } } ++static void sayInterrupted(void){ ++ if( seenInterrupt ){ ++ fprintf(stderr, "SQLite CLI interrupted.\n"); ++ } ++} ++ /* -** Initialize the state information in data +** Initialize the state information in datai and datax. +** Does no heap allocation, so will do no OOM abort. */ -static void main_init(ShellState *data) { - memset(data, 0, sizeof(*data)); - data->normalMode = data->cMode = data->mode = MODE_List; - data->autoExplain = 1; - data->pAuxDb = &data->aAuxDb[0]; - memcpy(data->colSeparator,SEP_Column, 2); - memcpy(data->rowSeparator,SEP_Row, 2); - data->showHeader = 0; - data->shellFlgs = SHFLG_Lookaside; - sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data); -#if !defined(SQLITE_SHELL_FIDDLE) +static void main_init(ShellInState *pDatai, ShellExState *pDatax) { + memset(pDatai, 0, sizeof(*pDatai)); + memset(pDatax, 0, sizeof(*pDatax)); + pDatax->sizeofThis = sizeof(*pDatax); + pDatax->pSIS = pDatai; + pDatai->pSXS = pDatax; + pDatax->pShowHeader = &pDatai->showHeader; + pDatax->zFieldSeparator = &pDatai->colSeparator[0]; + pDatax->zRecordSeparator = &pDatai->rowSeparator[0]; + pDatax->zNullValue = &pDatai->nullValue[0]; + pDatai->out = STD_OUT; + pDatai->normalMode = pDatai->cMode = pDatai->mode = MODE_List; + pDatai->autoExplain = 1; + pDatai->pAuxDb = &pDatai->aAuxDb[0]; + memcpy(pDatai->colSeparator,SEP_Column, 2); + memcpy(pDatai->rowSeparator,SEP_Row, 2); + pDatai->showHeader = 0; + pDatai->shellFlgs = SHFLG_Lookaside; + sqlite3_config(SQLITE_CONFIG_LOG, shellLog, pDatai); +#ifndef SQLITE_SHELL_FIDDLE verify_uninitialized(); +#else + datai.zDefaultDbName = "/fiddle.sqlite3"; #endif sqlite3_config(SQLITE_CONFIG_URI, 1); + sqlite3_config(SQLITE_CONFIG_LOG, shellLog, pDatai); sqlite3_config(SQLITE_CONFIG_MULTITHREAD); sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> "); sqlite3_snprintf(sizeof(continuePrompt), continuePrompt," ...> "); + /* Source at EOF (for now), saying it is command line. */ + pDatai->pInSource = &cmdInSource; +} + +/* +** Takedown and deallocate data structures held in datai and datax. +** Does no heap allocation, so will do no OOM abort. It leaves those +** structs zero-initialized to aid valgrind memory leak reporting. +*/ +static void main_cleanup(ShellInState *psi, ShellExState *psx) { + int i; + set_table_name(psx, 0); + if( psx->dbUser ){ + session_close_all(psi, -1); +#if SHELL_DYNAMIC_EXTENSION + notify_subscribers(psi, NK_DbAboutToClose, psx->dbUser); +#endif + close_db(psx->dbUser); + } + for(i=0; iaAuxDb); i++){ + sqlite3_free(psi->aAuxDb[i].zFreeOnClose); + if( psi->aAuxDb[i].db ){ + session_close_all(psi, i); + close_db(psi->aAuxDb[i].db); + } + } + find_home_dir(1); + output_reset(psi); + psi->doXdgOpen = 0; + clearTempFile(psi); + sqlite3_free(psi->zEditor); + explain_data_delete(psi); +#if SHELL_DYNAMIC_EXTENSION + notify_subscribers(psi, NK_DbAboutToClose, psx->dbShell); + /* It is necessary that the shell DB be closed after the user DBs. + * This is because loaded extensions are held by the shell DB and + * are therefor (automatically) unloaded when it is closed. */ + notify_subscribers(psi, NK_ExtensionUnload, psx->dbShell); + /* Forcefully unsubscribe any extension which ignored above or did + * not unsubscribe upon getting above event. */ + unsubscribe_extensions(psi); + /* This must be done before any extensions unload. */ + free_all_shext_tracking(psi); + /* Unload extensions and free the DB used for dealing with them. */ + sqlite3_close(psx->dbShell); + /* This notification can only reach statically linked extensions. */ + notify_subscribers(psi, NK_ShutdownImminent, 0); + /* Forcefull unsubscribe static extension event listeners. */ + subscribe_events(psx, 0, psx, NK_Unsubscribe, 0); +#endif /* SHELL_DYNAMIC_EXTENSION */ + free(psx->pSpecWidths); + free(psi->zNonce); + for(i=0; inSavedModes; ++i) sqlite3_free(psi->pModeStack[i]); + /* Clear shell state objects so that valgrind detects real memory leaks. */ + memset(psi, 0, sizeof(*psi)); + memset(psx, 0, sizeof(*psx)); +} + ++/* Setup or restore ^C interrupt handling. */ ++static void interruption_alter(int setup_nrestore){ ++#if defined(SIGINT) ++# if !defined(SHELL_LEGACY_SIGINT) ++ static struct sigaction cli_sigaction = { 0 }; ++ static struct sigaction cli_sigrestore = { 0 }; ++ if( setup_nrestore ){ ++ /* Leave .sa_mask and .sa_flags zero-initialized. */ ++ cli_sigaction.sa_handler = interrupt_handler; ++ sigaction(SIGINT,&cli_sigaction,&cli_sigrestore); ++ }else{ ++ sigaction(SIGINT,&cli_sigrestore,&cli_sigaction); ++ } ++# else ++ static __sighandler_t cli_sigrestore = SIG_DFL; ++ if( setup_nrestore ){ ++ cli_sigrestore = signal(SIGINT, interrupt_handler); ++ }else{ ++ signal(SIGINT, cli_sigrestore); ++ } ++# endif ++#elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE) ++ BOOL add = setup_nrestore!=0; ++ BOOL success = SetConsoleCtrlHandler(ConsoleCtrlHandler, add); ++ if( add && !success ) fprintf(stderr, "No ^C handler.\n"); ++#else ++ (void)setup_nrestore; ++#endif ++} ++ ++/* ++** Prepare execution environment for running shell. ++** Return is SQLITE_OK on success, else an error code. ++*/ ++static int execution_prepare(ShellInState *psi, ShellExState *psx){ ++ /* Register the control-C (SIGINT) handler. ++ ** Make sure we have a valid signal handler early, before anything ++ ** is done that might take long. */ ++ interruption_alter(1); ++ /* Ensure stderr is unbuffered. */ ++ setvbuf(STD_ERR, 0, _IONBF, 0); ++#ifdef SQLITE_SHELL_FIDDLE ++ stdin_is_interactive = 0; ++ stdout_is_console = 1; ++#else ++ stdin_is_interactive = isatty(0); ++ stdout_is_console = isatty(1); ++#endif ++#if SHELL_WIN_UTF8_OPT ++ upon_terminate(console_restore); ++#endif ++ upon_terminate(sayInterrupted); ++ ++#if !defined(_WIN32_WCE) ++ if( getenv("SQLITE_DEBUG_BREAK") ){ ++ if( isatty(0) && isatty(2) ){ ++ fprintf(STD_ERR, ++ "attach debugger to process %d and press any key to continue.\n", ++ GETPID()); ++ fgetc(STD_IN); ++ }else{ ++#if defined(_WIN32) || defined(WIN32) ++#if SQLITE_OS_WINRT ++ __debugbreak(); ++#else ++ DebugBreak(); ++#endif ++#elif defined(SIGTRAP) ++ raise(SIGTRAP); ++#endif ++ } ++ } ++#endif ++ ++ /**** Data initialization. ****/ ++ main_init(psi,psx); ++ init_std_inputs(stdin); ++#if SHELL_DATAIO_EXT ++ { ++ BuiltInFFExporter *pFFX ++ = (BuiltInFFExporter *)sqlite3_malloc(sizeof(BuiltInFFExporter)); ++ BuiltInCMExporter *pCMX ++ = (BuiltInCMExporter *)sqlite3_malloc(sizeof(BuiltInCMExporter)); ++ if( pFFX!=0 && pCMX!=0 ){ ++ BuiltInFFExporter ffExporter = BI_FF_EXPORTER_INIT( psi ); ++ BuiltInCMExporter cmExporter = BI_CM_EXPORTER_INIT( psi ); ++ memcpy(pFFX, &ffExporter, sizeof(BuiltInFFExporter)); ++ memcpy(pCMX, &cmExporter, sizeof(BuiltInCMExporter)); ++ psi->pFreeformExporter = (ExportHandler *)pFFX; ++ psi->pColumnarExporter = (ExportHandler *)pCMX; ++ psi->pActiveExporter = psi->pFreeformExporter; ++ }else{ ++ sqlite3_free(pFFX); ++ sqlite3_free(pCMX); ++ return SQLITE_NOMEM; ++ } ++ } ++#endif ++ return SQLITE_OK; ++} ++ ++#ifndef SQLITE_SHELL_FIDDLE ++/* ++** Undo execution environment preparation for shell. ++*/ ++static void execution_restore(ShellInState *psi, ShellExState *psx){ ++#if SHELL_DATAIO_EXT ++ ExportHandler *pEHCM = psi->pColumnarExporter; ++ ExportHandler *pEHFF = psi->pFreeformExporter; ++ pEHCM->pMethods->destruct(pEHCM); ++ pEHFF->pMethods->destruct(pEHFF); ++ sqlite3_free(pEHCM); ++ sqlite3_free(pEHFF); ++#endif ++ main_cleanup(psi, psx); ++ interruption_alter(0); + } ++#endif + /* ** Output text to the console in a font that attracts extra attention. */ @@@ -16368,416 -11913,32 +16520,420 @@@ static void printBold(const char *zText FOREGROUND_RED|FOREGROUND_INTENSITY ); #endif - printf("%s", zText); -#if !SQLITE_OS_WINRT - SetConsoleTextAttribute(out, defaultScreenInfo.wAttributes); + fprintf(STD_OUT, "%s", zText); +#if !SQLITE_OS_WINRT + SetConsoleTextAttribute(out, defaultScreenInfo.wAttributes); +#endif +} +#else +static void printBold(const char *zText){ + fprintf(STD_OUT, "\033[1m%s\033[0m", zText); +} +#endif + +/* +** Get the argument to an --option. Throw an error and die if no argument +** is available. +*/ +static char *cmdline_option_value(int argc, char **argv, int i){ + if( i==argc ){ + utf8_printf(STD_ERR, "%s: Error: missing argument to %s\n", + argv[0], argv[argc-1]); + quit_moan("invocation error", 1); + } + return argv[i]; +} + +static void zapGlobalDbLock(void){ + if( pGlobalDbLock ){ + sqlite3_mutex_free(pGlobalDbLock); + pGlobalDbLock = 0; + } +} - static void sayAbnormalExit(void){ - if( seenInterrupt ) fprintf(stderr, "Program interrupted.\n"); - } + +/* A vector of command strings collected from the command line. */ +typedef struct CmdArgs { + char **azCmd; /* the strings */ + int nCmd; /* how many collected */ + u8 bArgsHeld; /* whether "the strings" are owned by this object */ +} CmdArgs; +/* Freer for above. */ +static void freeCmdArgs(CmdArgs *pca){ + int i; + if( !pca ) return; + if( pca->bArgsHeld ){ + for( i=0; inCmd; ++i ){ + free(pca->azCmd[i]); + } + } + free(pca->azCmd); + pca->azCmd = 0; + pca->nCmd = 0; +} +/* Capacity grower for above. May terminate for OOM. */ +static void growCmdArgs(CmdArgs *pca, int nRoom){ + void *vaz = realloc(pca->azCmd, sizeof(pca->azCmd[0])*nRoom); + shell_check_oomm(vaz); + pca->azCmd = (char**)vaz; +} + +/* Data collected during args scanning. */ +typedef struct ArgsData { + int readStdin; /* whether stdin will be read */ + int nOptsEnd; /* where -- seen, else argc */ + const char *zInitFile; /* specified init file */ + const char *zVfs; /* -vfs command-line option */ + short bQuiet; /* -quiet option */ +#if ARCHIVE_ENABLE + short bArCmd; /* -A option given */ +#endif /* ARCHIVE_ENABLE */ +} ArgsData; + +/* +** Perform CLI invocation argument processing. +** This code is collected here for convenience, to declutter main() +** and to make this processing a little simpler to understand. +** Parameters are: +** argc, argv : command-line arguments +** pass : the pass number, 1 or 2 +** *pcaCmd (out) : arguments preceded by -cmd (which are run first) +** *pcaBare (out) : non-option or -A arguments (never the DB name) +** *pad (out, in/out) : option data not held in Shell??State +** ++** The 1st pass must be done with SQLite uninitialized. ++** The 2nd pass is indifferent to SQLite initialized or not. ++** ++** Returns are: 0 => normal, 1 => error, 2 => quit ++** +** This function may terminate abrubtly under OOM conditions. +*/ +static int scanInvokeArgs(int argc, char **argv, int pass, ShellInState *psi, + CmdArgs *pcaCmd, CmdArgs *pcaBare, ArgsData *pad){ + int rc = 0; + DotCmdRC drc; + int i; + if( pass==1 ){ + for(i=1; ipad->nOptsEnd ){ + if( psi->aAuxDb->zDbFilename==0 ){ + psi->aAuxDb->zDbFilename = z; + }else{ + growCmdArgs(pcaBare, pcaBare->nCmd+1); + pcaBare->azCmd[pcaBare->nCmd++] = z; + /* Excesss, non-option-like arguments are interpreted as SQL (or + ** dot-commands) and mean that nothing is to be read from stdin. */ + pad->readStdin = 0; + } + continue; + } + if( z[1]=='-' ) z++; + if( cli_strcmp(z, "-")==0 ){ + pad->nOptsEnd = i; + continue; + }else if( cli_strcmp(z,"-separator")==0 + || cli_strcmp(z,"-nullvalue")==0 + || cli_strcmp(z,"-newline")==0 + || cli_strcmp(z,"-cmd")==0 + ){ + (void)cmdline_option_value(argc, argv, ++i); + /* Will pickup value on next pass. */ + }else if( cli_strcmp(z,"-init")==0 ){ + pad->zInitFile = cmdline_option_value(argc, argv, ++i); + }else if( cli_strcmp(z,"-batch")==0 ){ + /* Need to check for batch mode here to so we can avoid printing + ** informational messages (like from process_sqliterc) before + ** we do the actual processing of arguments later in a second pass. + */ + stdin_is_interactive = 0; + }else if( cli_strcmp(z,"-heap")==0 ){ +#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) + const char *zSize; + sqlite3_int64 szHeap; + + zSize = cmdline_option_value(argc, argv, ++i); + szHeap = integerValue(zSize); + if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000; + verify_uninitialized(); + sqlite3_config(SQLITE_CONFIG_HEAP,malloc((int)szHeap),(int)szHeap, 64); +#else + (void)cmdline_option_value(argc, argv, ++i); +#endif + }else if( cli_strcmp(z,"-pagecache")==0 ){ + sqlite3_int64 n, sz; + void *pvCache = 0; + sz = integerValue(cmdline_option_value(argc,argv,++i)); + if( sz>70000 ) sz = 70000; + if( sz<0 ) sz = 0; + n = integerValue(cmdline_option_value(argc,argv,++i)); + if( sz>0 && n>0 && 0xffffffffffffLL/sz0 && sz>0 ) pvCache = malloc(n*sz); + shell_check_oomm(pvCache); + sqlite3_config(SQLITE_CONFIG_PAGECACHE, pvCache, sz, n); + psi->shellFlgs |= SHFLG_Pagecache; + }else if( cli_strcmp(z,"-lookaside")==0 ){ + int n, sz; + sz = (int)integerValue(cmdline_option_value(argc,argv,++i)); + if( sz<0 ) sz = 0; + n = (int)integerValue(cmdline_option_value(argc,argv,++i)); + if( n<0 ) n = 0; + verify_uninitialized(); + sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n); + if( sz*n==0 ) psi->shellFlgs &= ~SHFLG_Lookaside; + }else if( cli_strcmp(z,"-threadsafe")==0 ){ + int n; + n = (int)integerValue(cmdline_option_value(argc,argv,++i)); + verify_uninitialized(); + switch( n ){ + case 0: sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); break; + case 2: sqlite3_config(SQLITE_CONFIG_MULTITHREAD); break; + default: sqlite3_config(SQLITE_CONFIG_SERIALIZED); break; + } +#ifdef SQLITE_ENABLE_VFSTRACE + }else if( cli_strcmp(z,"-vfstrace")==0 ){ + extern int vfstrace_register( + const char *zTraceName, + const char *zOldVfsName, + int (*xOut)(const char*,void*), + void *pOutArg, + int makeDefault + ); + vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,STD_ERR,1); +#endif +#ifdef SQLITE_ENABLE_MULTIPLEX + }else if( cli_strcmp(z,"-multiplex")==0 ){ + extern int sqlite3_multiplex_initialize(const char*,int); + sqlite3_multiplex_initialize(0, 1); +#endif + }else if( cli_strcmp(z,"-mmap")==0 ){ + sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i)); + verify_uninitialized(); + sqlite3_config(SQLITE_CONFIG_MMAP_SIZE, sz, sz); +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + }else if( cli_strcmp(z,"-sorterref")==0 ){ + sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i)); + verify_uninitialized(); + sqlite3_config(SQLITE_CONFIG_SORTERREF_SIZE, (int)sz); +#endif + }else if( cli_strcmp(z,"-vfs")==0 ){ + pad->zVfs = cmdline_option_value(argc, argv, ++i); +#ifdef SQLITE_HAVE_ZLIB + }else if( cli_strcmp(z,"-zip")==0 ){ + psi->openMode = SHELL_OPEN_ZIPFILE; +#endif + }else if( cli_strcmp(z,"-append")==0 ){ + psi->openMode = SHELL_OPEN_APPENDVFS; +#ifndef SQLITE_OMIT_DESERIALIZE + }else if( cli_strcmp(z,"-deserialize")==0 ){ + psi->openMode = SHELL_OPEN_DESERIALIZE; + }else if( cli_strcmp(z,"-maxsize")==0 && i+1szMax = integerValue(argv[++i]); +#endif + }else if( cli_strcmp(z,"-readonly")==0 ){ + psi->openMode = SHELL_OPEN_READONLY; + }else if( cli_strcmp(z,"-nofollow")==0 ){ + psi->openFlags = SQLITE_OPEN_NOFOLLOW; +#if ARCHIVE_ENABLE + }else if( cli_strncmp(z, "-A",2)==0 ){ + /* All remaining command-line arguments are passed to the ".archive" + ** command, so ignore them */ + break; #endif -} -#else -static void printBold(const char *zText){ - printf("\033[1m%s\033[0m", zText); -} + }else if( cli_strcmp(z, "-memtrace")==0 ){ + sqlite3MemTraceActivate(STD_ERR); + }else if( cli_strcmp(z,"-bail")==0 ){ + bail_on_error = 1; +#if SHELL_EXTENSIONS + }else if( cli_strcmp(z,"-shxopts")==0 ){ + psi->bExtendedDotCmds = (u8)integerValue(argv[++i]); #endif - -/* -** Get the argument to an --option. Throw an error and die if no argument -** is available. -*/ -static char *cmdline_option_value(int argc, char **argv, int i){ - if( i==argc ){ - utf8_printf(stderr, "%s: Error: missing argument to %s\n", - argv[0], argv[argc-1]); - exit(1); + }else if( cli_strcmp(z,"-nonce")==0 ){ + free(psi->zNonce); + shell_check_oomm(psi->zNonce = strdup(argv[++i])); + }else if( cli_strcmp(z,"-quiet")==0 ){ + pad->bQuiet = (int)integerValue(cmdline_option_value(argc,argv,++i)); + }else if( cli_strcmp(z,"-unsafe-testing")==0 ){ + psi->shellFlgs |= SHFLG_TestingMode; + }else if( cli_strcmp(z,"-safe")==0 ){ + /* catch this on the second pass (Unsafe is fine on invocation.) */ + } + } + }else if( pass==2 ){ + for(i=1; i=pad->nOptsEnd ) continue; + if( z[1]=='-' ){ z++; } + if( cli_strcmp(z,"-init")==0 ){ + i++; + }else if( cli_strcmp(z,"-html")==0 ){ + zModeSet = z; + }else if( cli_strcmp(z,"-list")==0 ){ + zModeSet = z; + }else if( cli_strcmp(z,"-quote")==0 ){ + zModeSet = z; + }else if( cli_strcmp(z,"-line")==0 ){ + zModeSet = z; + }else if( cli_strcmp(z,"-column")==0 ){ + zModeSet = z; + }else if( cli_strcmp(z,"-json")==0 ){ + zModeSet = z; + }else if( cli_strcmp(z,"-markdown")==0 ){ + zModeSet = z; + }else if( cli_strcmp(z,"-table")==0 ){ + zModeSet = z; + }else if( cli_strcmp(z,"-box")==0 ){ + zModeSet = z; + }else if( cli_strcmp(z,"-csv")==0 ){ + zModeSet = z; + }else if( cli_strcmp(z,"-ascii")==0 ){ + zModeSet = z; + }else if( cli_strcmp(z,"-tabs")==0 ){ + zModeSet = z; +#ifdef SQLITE_HAVE_ZLIB + }else if( cli_strcmp(z,"-zip")==0 ){ + psi->openMode = SHELL_OPEN_ZIPFILE; +#endif + }else if( cli_strcmp(z,"-append")==0 ){ + psi->openMode = SHELL_OPEN_APPENDVFS; +#ifndef SQLITE_OMIT_DESERIALIZE + }else if( cli_strcmp(z,"-deserialize")==0 ){ + psi->openMode = SHELL_OPEN_DESERIALIZE; + }else if( cli_strcmp(z,"-maxsize")==0 && i+1szMax = integerValue(argv[++i]); +#endif + }else if( cli_strcmp(z,"-readonly")==0 ){ + psi->openMode = SHELL_OPEN_READONLY; + }else if( cli_strcmp(z,"-nofollow")==0 ){ + psi->openFlags |= SQLITE_OPEN_NOFOLLOW; + }else if( cli_strcmp(z,"-separator")==0 ){ + sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator, + "%s",cmdline_option_value(argc,argv,++i)); + }else if( cli_strcmp(z,"-newline")==0 ){ + sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, + "%s",cmdline_option_value(argc,argv,++i)); + }else if( cli_strcmp(z,"-nullvalue")==0 ){ + sqlite3_snprintf(sizeof(psi->nullValue), psi->nullValue, + "%s",cmdline_option_value(argc,argv,++i)); + }else if( cli_strcmp(z,"-header")==0 ){ + psi->showHeader = 1; + ShellSetFlagI(psi, SHFLG_HeaderSet); + }else if( cli_strcmp(z,"-noheader")==0 ){ + psi->showHeader = 0; + ShellSetFlagI(psi, SHFLG_HeaderSet); + }else if( cli_strcmp(z,"-echo")==0 ){ + ShellSetFlagI(psi, SHFLG_Echo); + }else if( cli_strcmp(z,"-eqp")==0 ){ + psi->autoEQP = AUTOEQP_on; + }else if( cli_strcmp(z,"-eqpfull")==0 ){ + psi->autoEQP = AUTOEQP_full; + }else if( cli_strcmp(z,"-stats")==0 ){ + psi->statsOn = 1; + }else if( cli_strcmp(z,"-scanstats")==0 ){ + psi->scanstatsOn = 1; + }else if( cli_strcmp(z,"-backslash")==0 ){ + /* Undocumented command-line option: -backslash + ** Causes C-style backslash escapes to be evaluated in SQL statements + ** prior to sending the SQL into SQLite. Useful for injecting crazy + ** bytes in the middle of SQL statements for testing and debugging. + */ + ShellSetFlagI(psi, SHFLG_Backslash); + }else if( cli_strcmp(z,"-bail")==0 ){ + /* No-op. The bail_on_error flag should already be set. */ +#if SHELL_EXTENSIONS + }else if( cli_strcmp(z,"-shxopts")==0 ){ + i++; /* Handled on first pass. */ +#endif + }else if( cli_strcmp(z,"-version")==0 ){ + fprintf(STD_OUT, "%s %s\n", sqlite3_libversion(), sqlite3_sourceid()); + rc = 2; + }else if( cli_strcmp(z,"-interactive")==0 ){ + stdin_is_interactive = 1; + }else if( cli_strcmp(z,"-batch")==0 ){ + stdin_is_interactive = 0; + }else if( cli_strcmp(z,"-utf8")==0 ){ +#if SHELL_WIN_UTF8_OPT + console_utf8 = 1; +#endif /* SHELL_WIN_UTF8_OPT */ + }else if( cli_strcmp(z,"-heap")==0 ){ + i++; + }else if( cli_strcmp(z,"-pagecache")==0 ){ + i+=2; + }else if( cli_strcmp(z,"-lookaside")==0 ){ + i+=2; + }else if( cli_strcmp(z,"-threadsafe")==0 ){ + i+=2; + }else if( cli_strcmp(z,"-nonce")==0 ){ + i+=2; + }else if( cli_strcmp(z,"-mmap")==0 ){ + i++; + }else if( cli_strcmp(z,"-memtrace")==0 ){ + i++; +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + }else if( cli_strcmp(z,"-sorterref")==0 ){ + i++; +#endif + }else if( cli_strcmp(z,"-vfs")==0 ){ + i++; +#ifdef SQLITE_ENABLE_VFSTRACE + }else if( cli_strcmp(z,"-vfstrace")==0 ){ + i++; +#endif +#ifdef SQLITE_ENABLE_MULTIPLEX + }else if( cli_strcmp(z,"-multiplex")==0 ){ + i++; +#endif + }else if( cli_strcmp(z,"-help")==0 ){ + usage(1); ++ rc = 2; ++ break; + }else if( cli_strcmp(z,"-cmd")==0 ){ + /* Run commands that follow -cmd first and separately from commands + ** that simply appear on the command-line. This is likely surprising. + ** Better would be to run all commands in the order that they appear. + ** But we retain this goofy behavior for historical compatibility. */ + if( i==argc-1 ) break; /* Pretend (un)specified command is empty. */ + growCmdArgs(pcaCmd, pcaCmd->nCmd+1); + pcaCmd->azCmd[pcaCmd->nCmd++] = cmdline_option_value(argc,argv,++i); +#if ARCHIVE_ENABLE + }else if( cli_strncmp(z, "-A", 2)==0 ){ + if( pcaBare->nCmd>0 ){ + utf8_printf(STD_ERR, "Error: cannot mix regular SQL or dot-commands" + " with \"%s\"\n", z); + rc = 1; + break; + } + growCmdArgs(pcaBare, argc-i+1); + if( z[2] ) pcaBare->azCmd[pcaBare->nCmd++] = &z[2]; + while( iazCmd[pcaBare->nCmd++] = argv[i++]; + } + pad->readStdin = 0; + pad->bArCmd = 1; + break; +#endif /* ARCHIVE_ENABLE */ + }else if( cli_strcmp(z,"-safe")==0 ){ + psi->bSafeMode = psi->bSafeModeFuture = 1; + }else if( cli_strcmp(z,"-unsafe-testing")==0 ){ + /* Acted upon in first pass. */ + }else if( cli_strcmp(z,"-quiet")==0 ){ + ++i; + }else{ + utf8_printf(STD_ERR,"%s: Error: unknown option: %s\n", Argv0, z); + raw_printf(STD_ERR,"Use -help for a list of options.\n"); + rc = 2; + } + if( zModeSet!=0 ){ + char *azModeCmd[] = { ".mode", zModeSet+1 }; + modeCommand(azModeCmd, 2, XSS(psi), 0); + psi->cMode = psi->mode; + } + } } - return argv[i]; -} - -static void sayAbnormalExit(void){ - if( seenInterrupt ) fprintf(stderr, "Program interrupted.\n"); + return rc; } #ifndef SQLITE_SHELL_IS_UTF8 @@@ -16789,16 -11950,8 +16945,14 @@@ # endif #endif --#ifdef SQLITE_SHELL_FIDDLE - # define SHELL_MAIN fiddle_main - #endif - -# define main fiddle_main +#ifndef SHELL_MAIN - # if SQLITE_SHELL_IS_UTF8 ++# if defined(SQLITE_SHELL_FIDDLE) ++# define SHELL_MAIN fiddle_main ++# elif SQLITE_SHELL_IS_UTF8 +# define SHELL_MAIN main +# else +# define SHELL_MAIN wmain +# endif #endif #if SQLITE_SHELL_IS_UTF8 @@@ -16810,414 -11963,630 +16964,351 @@@ int SQLITE_CDECL SHELL_MAIN(int argc, w #ifdef SQLITE_DEBUG sqlite3_int64 mem_main_enter = 0; #endif - char *zErrMsg = 0; #ifdef SQLITE_SHELL_FIDDLE -# define data shellState +# define datai shellStateI +# define datax shellStateX #else - ShellState data; + ShellInState datai; + ShellExState datax; - #endif - #if SHELL_DATAIO_EXT - BuiltInFFExporter ffExporter = BI_FF_EXPORTER_INIT( &datai ); - BuiltInCMExporter cmExporter = BI_CM_EXPORTER_INIT( &datai ); #endif - const char *zInitFile = 0; - int i; - int rc = 0; + RipStackDest mainRipDest = RIP_STACK_DEST_INIT; + int i; /* General purpose */ + int iAbruptExitCode; + int rc = 0; /* main() exit code */ + DotCmdRC drc = DCR_Ok; int warnInmemoryDb = 0; - int readStdin = 1; - int nCmd = 0; - int nOptsEnd = argc; - char **azCmd = 0; - const char *zVfs = 0; /* Value of -vfs command-line option */ + /* azCmd, nCmd, bArgsHeld */ + CmdArgs cmdArgsCmd = {0,0,0}; /* for -cmd invocation arguments */ + AnyResourceHolder cacRH = {&cmdArgsCmd, (GenericFreer)freeCmdArgs}; + CmdArgs cmdArgsBare = {0,0,0}; /* for bare or -A invocation arguments */ + AnyResourceHolder cabRH = {&cmdArgsBare, (GenericFreer)freeCmdArgs}; + /* readStdin, nOptsEnd, zInitFile, zVfs, bQuiet */ + ArgsData argsData = { 1, argc, 0,0,0 }; #if !SQLITE_SHELL_IS_UTF8 - char **argvToFree = 0; - int argcToFree = 0; -#endif - setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */ - -#ifdef SQLITE_SHELL_FIDDLE - stdin_is_interactive = 0; - stdout_is_console = 1; - data.wasm.zDefaultDbName = "/fiddle.sqlite3"; -#else - stdin_is_interactive = isatty(0); - stdout_is_console = isatty(1); -#endif -#if SHELL_WIN_UTF8_OPT - atexit(console_restore); /* Needs revision for CLI as library call */ -#endif - atexit(sayAbnormalExit); -#ifdef SQLITE_DEBUG - mem_main_enter = sqlite3_memory_used(); -#endif -#if !defined(_WIN32_WCE) - if( getenv("SQLITE_DEBUG_BREAK") ){ - if( isatty(0) && isatty(2) ){ - fprintf(stderr, - "attach debugger to process %d and press any key to continue.\n", - GETPID()); - fgetc(stdin); - }else{ -#if defined(_WIN32) || defined(WIN32) -#if SQLITE_OS_WINRT - __debugbreak(); -#else - DebugBreak(); -#endif -#elif defined(SIGTRAP) - raise(SIGTRAP); -#endif - } - } -#endif - /* Register a valid signal handler early, before much else is done. */ -#ifdef SIGINT - signal(SIGINT, interrupt_handler); -#elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE) - if( !SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE) ){ - fprintf(stderr, "No ^C handler.\n"); - } + CmdArgs argsUtf8 = {0,0,1}; + AnyResourceHolder caRH = {&argsUtf8, (GenericFreer)freeCmdArgs}; #endif - ResourceCount mainResCount = 0; - - /**** Execution environment setup. ****/ - setvbuf(STD_ERR, 0, _IONBF, 0); /* Make sure stderr is unbuffered */ - #ifdef SQLITE_SHELL_FIDDLE - stdin_is_interactive = 0; - stdout_is_console = 1; - #else - stdin_is_interactive = isatty(0); - stdout_is_console = isatty(1); - #endif - if( atexit_registered==0 ){ - #if SHELL_WIN_UTF8_OPT - atexit(console_restore); /* Needs revision for CLI as library call */ - #endif - atexit(sayAbnormalExit); - } - #ifdef SQLITE_DEBUG - mem_main_enter = sqlite3_memory_used(); - #endif - /* Register the control-C (SIGINT) handler. - ** Make sure we have a valid signal handler early, before anything - ** is done that might take long. */ - #ifdef SIGINT - signal(SIGINT, interrupt_handler); - #elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE) - SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE); - #endif - - #if !defined(_WIN32_WCE) - if( getenv("SQLITE_DEBUG_BREAK") ){ - if( isatty(0) && isatty(2) ){ - fprintf(STD_ERR, - "attach debugger to process %d and press any key to continue.\n", - GETPID()); - fgetc(STD_IN); - }else{ - #if defined(_WIN32) || defined(WIN32) - #if SQLITE_OS_WINRT - __debugbreak(); - #else - DebugBreak(); - #endif - #elif defined(SIGTRAP) - raise(SIGTRAP); - #endif - } - } - #endif - /* Register a valid signal handler early, before much else is done. */ - #ifdef SIGINT - signal(SIGINT, interrupt_handler); - #elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE) - if( !SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE) ){ - fprintf(stderr, "No ^C handler.\n"); - } - #endif ++ RESOURCE_MARK(mark); ++ /* Just return error if using mismatched dynamic SQLite library version. */ #if USE_SYSTEM_SQLITE+0!=1 if( cli_strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,60)!=0 ){ - utf8_printf(stderr, "SQLite header and source version mismatch\n%s\n%s\n", + utf8_printf(STD_ERR, "SQLite header and source version mismatch\n%s\n%s\n", sqlite3_sourceid(), SQLITE_SOURCE_ID); -- exit(1); ++ return 1; } #endif - main_init(&data); - /**** Data initialization. ****/ - main_init(&datai,&datax); - init_std_inputs(stdin); - #if SHELL_DATAIO_EXT - datai.pFreeformExporter = (ExportHandler*)&ffExporter; - datai.pColumnarExporter = (ExportHandler*)&cmExporter; - datai.pActiveExporter = (ExportHandler*)&ffExporter; - #endif - /* On Windows, we must translate command-line arguments into UTF-8. - ** The SQLite memory allocator subsystem has to be enabled in order to - ** do this. But we want to run an sqlite3_shutdown() afterwards so that - ** subsequent sqlite3_config() calls will work. So copy all results into - ** memory that does not come from the SQLite memory allocator. - */ -#if !SQLITE_SHELL_IS_UTF8 + sqlite3_initialize(); - argvToFree = malloc(sizeof(argv[0])*argc*2); - shell_check_oom(argvToFree); - argcToFree = argc; - argv = argvToFree + argc; - for(i=0; i=1 && argv && argv[0] ); - Argv0 = argv[0]; ++#ifdef SQLITE_DEBUG ++ mem_main_enter = sqlite3_memory_used(); ++#endif -#ifdef SQLITE_SHELL_DBNAME_PROC - { - /* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name - ** of a C-function that will provide the name of the database file. Use - ** this compile-time option to embed this shell program in larger - ** applications. */ - extern void SQLITE_SHELL_DBNAME_PROC(const char**); - SQLITE_SHELL_DBNAME_PROC(&data.pAuxDb->zDbFilename); - warnInmemoryDb = 0; - } + /* On Windows, we must translate command-line arguments into UTF-8. + ** The SQLite memory allocator subsystem has to be enabled in order to + ** do this. But we want to run an sqlite3_shutdown() afterwards so that + ** subsequent sqlite3_config() calls will work. So copy all results into + ** memory that does not come from the SQLite memory allocator. + */ +#if !SQLITE_SHELL_IS_UTF8 - sqlite3_initialize(); - argsUtf8.azCmd = malloc(sizeof(argv[0])*argc); - shell_check_oomm(argsUtf8.azCmd); ++ growCmdArgs(&argsUtf8, argc); + argsUtf8.nCmd = 0; + any_ref_holder(&caRH); /* This will normally activate as shell exits. */ - ++mainResCount; + for(i=0; inOptsEnd ){ - if( data.aAuxDb->zDbFilename==0 ){ - data.aAuxDb->zDbFilename = z; - }else{ - /* Excesss arguments are interpreted as SQL (or dot-commands) and - ** mean that nothing is read from stdin */ - readStdin = 0; - nCmd++; - azCmd = realloc(azCmd, sizeof(azCmd[0])*nCmd); - shell_check_oom(azCmd); - azCmd[nCmd-1] = z; - } - continue; ++ register_exit_ripper(&mainRipDest); ++ if( 0==RIP_TO_HERE(mainRipDest) ){ ++ /**** Input processing. ****/ ++ + assert( argc>=1 && argv && argv[0] ); + Argv0 = argv[0]; +#if SHELL_DYNAMIC_EXTENSION + initStartupDir(); + if( isExtendedBasename(Argv0) ){ + datai.bExtendedDotCmds = SHELL_ALL_EXTENSIONS; } - if( z[1]=='-' ) z++; - if( cli_strcmp(z, "-")==0 ){ - nOptsEnd = i; - continue; - }else if( cli_strcmp(z,"-separator")==0 - || cli_strcmp(z,"-nullvalue")==0 - || cli_strcmp(z,"-newline")==0 - || cli_strcmp(z,"-cmd")==0 - ){ - (void)cmdline_option_value(argc, argv, ++i); - }else if( cli_strcmp(z,"-init")==0 ){ - zInitFile = cmdline_option_value(argc, argv, ++i); - }else if( cli_strcmp(z,"-batch")==0 ){ - /* Need to check for batch mode here to so we can avoid printing - ** informational messages (like from process_sqliterc) before - ** we do the actual processing of arguments later in a second pass. - */ - stdin_is_interactive = 0; - }else if( cli_strcmp(z,"-heap")==0 ){ -#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) - const char *zSize; - sqlite3_int64 szHeap; - - zSize = cmdline_option_value(argc, argv, ++i); - szHeap = integerValue(zSize); - if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000; - verify_uninitialized(); - sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64); -#else - (void)cmdline_option_value(argc, argv, ++i); -#endif - }else if( cli_strcmp(z,"-pagecache")==0 ){ - sqlite3_int64 n, sz; - sz = integerValue(cmdline_option_value(argc,argv,++i)); - if( sz>70000 ) sz = 70000; - if( sz<0 ) sz = 0; - n = integerValue(cmdline_option_value(argc,argv,++i)); - if( sz>0 && n>0 && 0xffffffffffffLL/sz0 && sz>0) ? malloc(n*sz) : 0, sz, n); - data.shellFlgs |= SHFLG_Pagecache; - }else if( cli_strcmp(z,"-lookaside")==0 ){ - int n, sz; - sz = (int)integerValue(cmdline_option_value(argc,argv,++i)); - if( sz<0 ) sz = 0; - n = (int)integerValue(cmdline_option_value(argc,argv,++i)); - if( n<0 ) n = 0; - verify_uninitialized(); - sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n); - if( sz*n==0 ) data.shellFlgs &= ~SHFLG_Lookaside; - }else if( cli_strcmp(z,"-threadsafe")==0 ){ - int n; - n = (int)integerValue(cmdline_option_value(argc,argv,++i)); - verify_uninitialized(); - switch( n ){ - case 0: sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); break; - case 2: sqlite3_config(SQLITE_CONFIG_MULTITHREAD); break; - default: sqlite3_config(SQLITE_CONFIG_SERIALIZED); break; - } -#ifdef SQLITE_ENABLE_VFSTRACE - }else if( cli_strcmp(z,"-vfstrace")==0 ){ - extern int vfstrace_register( - const char *zTraceName, - const char *zOldVfsName, - int (*xOut)(const char*,void*), - void *pOutArg, - int makeDefault - ); - vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,stderr,1); #endif -#ifdef SQLITE_ENABLE_MULTIPLEX - }else if( cli_strcmp(z,"-multiplex")==0 ){ - extern int sqlite3_multiplex_initialize(const char*,int); - sqlite3_multiplex_initialize(0, 1); -#endif - }else if( cli_strcmp(z,"-mmap")==0 ){ - sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i)); - verify_uninitialized(); - sqlite3_config(SQLITE_CONFIG_MMAP_SIZE, sz, sz); -#if defined(SQLITE_ENABLE_SORTER_REFERENCES) - }else if( cli_strcmp(z,"-sorterref")==0 ){ - sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i)); - verify_uninitialized(); - sqlite3_config(SQLITE_CONFIG_SORTERREF_SIZE, (int)sz); -#endif - }else if( cli_strcmp(z,"-vfs")==0 ){ - zVfs = cmdline_option_value(argc, argv, ++i); -#ifdef SQLITE_HAVE_ZLIB - }else if( cli_strcmp(z,"-zip")==0 ){ - data.openMode = SHELL_OPEN_ZIPFILE; + + any_ref_holder(&cacRH); + any_ref_holder(&cabRH); - mainResCount += 2; + +#ifdef SQLITE_SHELL_DBNAME_PROC + { + /* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name + ** of a C-function that will provide the name of the database file. Use + ** this compile-time option to embed this shell program in larger + ** applications. */ + extern void SQLITE_SHELL_DBNAME_PROC(const char**); + SQLITE_SHELL_DBNAME_PROC(&datai.pAuxDb->zDbFilename); + warnInmemoryDb = 0; + } #endif - }else if( cli_strcmp(z,"-append")==0 ){ - data.openMode = SHELL_OPEN_APPENDVFS; -#ifndef SQLITE_OMIT_DESERIALIZE - }else if( cli_strcmp(z,"-deserialize")==0 ){ - data.openMode = SHELL_OPEN_DESERIALIZE; - }else if( cli_strcmp(z,"-maxsize")==0 && i+1zDbFilename==0 ){ + if( datai.pAuxDb->zDbFilename==0 ){ #ifndef SQLITE_OMIT_MEMORYDB - data.pAuxDb->zDbFilename = ":memory:"; - warnInmemoryDb = argc==1; + datai.pAuxDb->zDbFilename = ":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); + rc = 1; + goto shell_bail; #endif - } - data.out = stdout; + } #ifndef SQLITE_SHELL_FIDDLE - sqlite3_appendvfs_init(0,0,0); + sqlite3_appendvfs_init(0,0,0); #endif - /* Go ahead and open the database file if it already exists. If the - ** file does not exist, delay opening it. This prevents empty database - ** files from being created if a user mistypes the database name argument - ** to the sqlite command-line tool. - */ - if( access(data.pAuxDb->zDbFilename, 0)==0 ){ - open_db(&data, 0); - } + /* Go ahead and open the database file if it already exists. If the + ** file does not exist, delay opening it. This prevents empty database + ** files from being created if a user mistypes the database name argument + ** to the sqlite command-line tool. + */ + if( access(datai.pAuxDb->zDbFilename, 0)==0 ){ + open_db(&datax, 0); + } - /* Process the initialization file if there is one. If no -init option - ** is given on the command line, look for a file named ~/.sqliterc and - ** try to process it. - */ - process_sqliterc(&data,zInitFile); + /* Process the initialization file if there is one. If no -init option + ** is given on the command line, look for a file named ~/.sqliterc and + ** try to process it, without any quitting or bail-on-error. + */ + process_sqliterc(&datai,argsData.zInitFile); + + /* 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. + */ + rc = scanInvokeArgs(argc, argv, /*pass*/ 2, &datai, + &cmdArgsCmd, &cmdArgsBare, &argsData); + if( rc>0 ){ + 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; i=nOptsEnd ) continue; - if( z[1]=='-' ){ z++; } - if( cli_strcmp(z,"-init")==0 ){ - i++; - }else if( cli_strcmp(z,"-html")==0 ){ - data.mode = MODE_Html; - }else if( cli_strcmp(z,"-list")==0 ){ - data.mode = MODE_List; - }else if( cli_strcmp(z,"-quote")==0 ){ - data.mode = MODE_Quote; - sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Comma); - sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, SEP_Row); - }else if( cli_strcmp(z,"-line")==0 ){ - data.mode = MODE_Line; - }else if( cli_strcmp(z,"-column")==0 ){ - data.mode = MODE_Column; - }else if( cli_strcmp(z,"-json")==0 ){ - data.mode = MODE_Json; - }else if( cli_strcmp(z,"-markdown")==0 ){ - data.mode = MODE_Markdown; - }else if( cli_strcmp(z,"-table")==0 ){ - data.mode = MODE_Table; - }else if( cli_strcmp(z,"-box")==0 ){ - data.mode = MODE_Box; - }else if( cli_strcmp(z,"-csv")==0 ){ - data.mode = MODE_Csv; - memcpy(data.colSeparator,",",2); -#ifdef SQLITE_HAVE_ZLIB - }else if( cli_strcmp(z,"-zip")==0 ){ - data.openMode = SHELL_OPEN_ZIPFILE; -#endif - }else if( cli_strcmp(z,"-append")==0 ){ - data.openMode = SHELL_OPEN_APPENDVFS; -#ifndef SQLITE_OMIT_DESERIALIZE - }else if( cli_strcmp(z,"-deserialize")==0 ){ - data.openMode = SHELL_OPEN_DESERIALIZE; - }else if( cli_strcmp(z,"-maxsize")==0 && i+1 0 ){ + /* cmdArgsCmd not empty; some -cmd commands are to be run first. */ + for( i=0; i2)? 2 : drc; + if( rc>0 ) goto shell_bail; + } + } + + if( !argsData.readStdin && cmdArgsBare.nCmd>0 ){ + /* cmdArgsBare holds either "bare command" arguments or -A arguments. + ** (Former are arguments not naming the DB or beginning with '-'.) + ** Run whichever kind there are. */ +#if ARCHIVE_ENABLE + if( argsData.bArCmd ){ + open_db(&datax, OPEN_DB_ZIPFILE); + drc = arDotCommand(&datax, 1, cmdArgsBare.azCmd, cmdArgsBare.nCmd); + }else +#endif /* ARCHIVE_ENABLE */ + { + /* Run bare command arguments like separate command-line inputs. */ + for(i=0; i2)? 2 : drc; + if( drc > DCR_Ok ) break; } } -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) - }else if( cli_strncmp(z, "-A", 2)==0 ){ - if( nCmd>0 ){ - utf8_printf(stderr, "Error: cannot mix regular SQL or dot-commands" - " with \"%s\"\n", z); - return 1; - } - open_db(&data, OPEN_DB_ZIPFILE); - if( z[2] ){ - argv[i] = &z[2]; - arDotCommand(&data, 1, argv+(i-1), argc-(i-1)); - }else{ - arDotCommand(&data, 1, argv+i, argc-i); - } - readStdin = 0; - break; -#endif - }else if( cli_strcmp(z,"-safe")==0 ){ - data.bSafeMode = data.bSafeModePersist = 1; - }else if( cli_strcmp(z,"-unsafe-testing")==0 ){ - /* Acted upon in first pass. */ + rc = (drc>2)? 2 : drc; + if( rc>0 ) goto shell_bail; }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; - } - data.cMode = data.mode; - } -#if SHELL_WIN_UTF8_OPT - if( console_utf8 && stdin_is_interactive ){ - console_prepare(); - }else{ - setBinaryMode(stdin, 0); - console_utf8 = 0; - } -#endif - - if( !readStdin ){ - /* Run all arguments that do not begin with '-' as if they were separate - ** command-line inputs, except for the argToSkip argument which contains - ** the database filename. - */ - for(i=0; i1 ){ - /* bQuiet is almost like normal interactive, but quieter - ** and avoids history keeping and line editor completions. */ ++ /* bQuiet>1 is almost like normal interactive, but quieter, without ++ ** prompts and avoids history keeping and line editor completions. ++ ** It exists for testing or bug repro purposes. */ + mainPrompt[0] = 0; + continuePrompt[0] = 0; + }else{ - fprintf(STD_OUT, - "SQLite version %s %.19s\n" /*extra-version-info*/ - "Enter \".help\" for usage hints.\n", - sqlite3_libversion(), sqlite3_sourceid() - ); - if( warnInmemoryDb ){ - fprintf(STD_OUT, "Connected to a "); - printBold("transient in-memory database"); - fprintf(STD_OUT, ".\nUse \".open FILENAME\" to reopen on a " - "persistent database.\n"); ++ char *zHome; ++ if( argsData.bQuiet<1 ){ ++ fprintf(STD_OUT, ++ "SQLite version %s %.19s\n" /*extra-version-info*/ ++ "Enter \".help\" for usage hints.\n", ++ sqlite3_libversion(), sqlite3_sourceid() ++ ); ++ if( warnInmemoryDb ){ ++ fprintf(STD_OUT, "Connected to a "); ++ printBold("transient in-memory database"); ++ fprintf(STD_OUT, ".\nUse \".open FILENAME\" to reopen on a " ++ "persistent database.\n"); ++ } + } + zHistory = getenv("SQLITE_HISTORY"); + if( zHistory ){ + zHistory = strdup(zHistory); + }else if( (zHome = find_home_dir(0))!=0 ){ - int nHistory = strlen30(zHome) + 20; ++ static const char zBasename[] = ".sqlite_history"; ++ int nHistory = strlen30(zHome) + 2 + sizeof(zBasename); + if( (zHistory = malloc(nHistory))!=0 ){ - sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome); ++ sqlite3_snprintf(nHistory, zHistory,"%s/%s", zHome, zBasename); + } + } - if( zHistory ){ shell_read_history(zHistory); } - #if HAVE_READLINE || HAVE_EDITLINE ++ if( zHistory ){ ++ mstr_holder(zHistory); ++ shell_read_history(zHistory); ++ } ++# if HAVE_READLINE || HAVE_EDITLINE + rl_attempted_completion_function = readline_completion; - #elif HAVE_LINENOISE ++# elif HAVE_LINENOISE + linenoiseSetCompletionCallback(linenoise_completion); - #endif ++# endif } - }else{ - open_db(&data, 0); - echo_group_input(&data, azCmd[i]); - rc = shell_exec(&data, azCmd[i], &zErrMsg); - if( zErrMsg || rc ){ - if( zErrMsg!=0 ){ - utf8_printf(stderr,"Error: %s\n", zErrMsg); - }else{ - utf8_printf(stderr,"Error: unable to process SQL: %s\n", azCmd[i]); + datai.pInSource = &termInSource; /* read from stdin interactively */ + drc = process_input(&datai); + if( !argsData.bQuiet ){ + if( zHistory ){ + shell_stifle_history(2000); + shell_write_history(zHistory); - free(zHistory); ++ release_holder(); } - sqlite3_free(zErrMsg); - free(azCmd); - return rc!=0 ? rc : 1; } - } - } + }else{ + datai.pInSource = &stdInSource; /* read from stdin without prompts */ + drc = process_input(&datai); + } +#else /* !defined(SQLITE_SHELL_FIDDLE) */ + datai.pInSource = &fiddleInSource; /* read per set_fiddle_input_text() */ + drc = process_input(&datai); +#endif /* defined(SQLITE_SHELL_FIDDLE) */ + rc = (drc>2)? 2 : drc; + } ++ shell_bail: + forget_exit_ripper(&mainRipDest); ++ /* Next is a no-op except for "goto shell_bail" early exits. */ ++ RESOURCE_FREE(mainRipDest.resDest); }else{ - /* Any abrupt, stack-ripping exit arrives here. */ - utf8_printf(STD_ERR, "Exiting with error (2) after orderly cleanup.\n"); - /* Run commands received from standard input - */ - if( stdin_is_interactive ){ - char *zHome; - char *zHistory; - int nHistory; - printf( - "SQLite version %s %.19s\n" /*extra-version-info*/ - "Enter \".help\" for usage hints.\n", - sqlite3_libversion(), sqlite3_sourceid() - ); - if( warnInmemoryDb ){ - printf("Connected to a "); - printBold("transient in-memory database"); - printf(".\nUse \".open FILENAME\" to reopen on a " - "persistent database.\n"); - } - zHistory = getenv("SQLITE_HISTORY"); - if( zHistory ){ - zHistory = strdup(zHistory); - }else if( (zHome = find_home_dir(0))!=0 ){ - nHistory = strlen30(zHome) + 20; - if( (zHistory = malloc(nHistory))!=0 ){ - sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome); - } - } - if( zHistory ){ shell_read_history(zHistory); } -#if HAVE_READLINE || HAVE_EDITLINE - rl_attempted_completion_function = readline_completion; -#elif HAVE_LINENOISE - linenoiseSetCompletionCallback(linenoise_completion); -#endif - data.in = 0; - rc = process_input(&data); - if( zHistory ){ - shell_stifle_history(2000); - shell_write_history(zHistory); - free(zHistory); - } - }else{ - data.in = stdin; - rc = process_input(&data); - } ++ /* Abrupt, stack-ripping exit arrives here (with mainRipDest forgotten.) */ ++ utf8_printf(STD_ERR,"Terminating with error (2) after orderly cleanup.\n"); + datax.shellAbruptExit = 0x102; } - shell_bail: - /* All users of resource managment should have left its stack as it - ** was near the beginning of shell REPL execution. Verify this. */ - assert(main_resource_mark+mainResCount==holder_mark()); - RESOURCE_FREE(main_resource_mark); + - /**** Termination cleanup. ****/ ++ /* Extract this code to preserve its value before datax disposal. */ + iAbruptExitCode = datax.shellAbruptExit; ++ ++ /**** Termination cleanup. ****/ #ifndef SQLITE_SHELL_FIDDLE /* In WASM mode we have to leave the db state in place so that - ** client code can "push" SQL into it after this call returns. */ - free(azCmd); - set_table_name(&data, 0); - if( data.db ){ - session_close_all(&data, -1); - close_db(data.db); - } - for(i=0; idestruct((ExportHandler*)&cmExporter); - ffExporter.pMethods->destruct((ExportHandler*)&ffExporter); - # endif ++ execution_restore(&datai, &datax); ++ RESOURCE_FREE(mark); +# ifdef SQLITE_DEBUG if( sqlite3_memory_used()>mem_main_enter ){ utf8_printf(stderr, "Memory leaked: %u bytes\n", (unsigned int)(sqlite3_memory_used()-mem_main_enter)); } -#endif -#endif /* !SQLITE_SHELL_FIDDLE */ +# endif +#endif /* !defined(SQLITE_SHELL_FIDDLE) */ + /* Process exit codes to yield single shell exit code. + * rc == 2 is a quit signal, resulting in no error by itself. + * datax.shellAbruptExit conveyed either a normal (success or error) + * exit code or an abnormal exit code. Its abnormal values take priority. + */ + /* Check for an abnormal exit, and issue error if so. */ + if( iAbruptExitCode!=0 ){ + rc = iAbruptExitCode & 0xff; + if( iAbruptExitCode>0x1ff ) raw_printf(STD_ERR,"Abnormal exit (%d)\n",rc); + }else{ + /* rc is one of 0,1,2, mapping to 0,1,0 shellexit codes. */ + rc &= ~2; + } ++ terminate_actions(); return rc; } @@@ -17231,7 -12601,7 +17322,7 @@@ int fiddle_experiment(int a,int b) ** Returns a pointer to the current DB handle. */ sqlite3 * fiddle_db_handle(){ -- return globalDb; ++ return GLOBAL_DB; } /* @@@ -17242,7 -12612,7 +17333,7 @@@ sqlite3_vfs * fiddle_db_vfs(const char *zDbName){ sqlite3_vfs * pVfs = 0; if(globalDb){ -- sqlite3_file_control(globalDb, zDbName ? zDbName : "main", ++ sqlite3_file_control(GLOBAL_DB, zDbName ? zDbName : "main", SQLITE_FCNTL_VFS_POINTER, &pVfs); } return pVfs; @@@ -17261,7 -12631,7 +17352,7 @@@ sqlite3 * fiddle_db_arg(sqlite3 *arg) ** portable enough to make real use of. */ void fiddle_interrupt(void){ -- if( globalDb ) sqlite3_interrupt(globalDb); ++ active_db_interrupt(); } /* @@@ -17270,7 -12640,7 +17361,7 @@@ */ const char * fiddle_db_filename(const char * zDbName){ return globalDb -- ? sqlite3_db_filename(globalDb, zDbName ? zDbName : "main") ++ ? sqlite3_db_filename(GLOBAL_DB, zDbName ? zDbName : "main") : NULL; } @@@ -17280,9 -12650,9 +17371,9 @@@ */ void fiddle_reset_db(void){ if( globalDb ){ -- int rc = sqlite3_db_config(globalDb, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); -- if( 0==rc ) rc = sqlite3_exec(globalDb, "VACUUM", 0, 0, 0); -- sqlite3_db_config(globalDb, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); ++ int rc = sqlite3_db_config(GLOBAL_DB, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); ++ if( 0==rc ) rc = sqlite3_exec(GLOBAL_DB, "VACUUM", 0, 0, 0); ++ sqlite3_db_config(GLOBAL_DB, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); } }