From: larrybr Date: Sat, 13 May 2023 07:23:14 +0000 (+0000) Subject: CLI closer to doing full cleanup on error exits. Needs testing with OOM simulation. X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=0cb00bbe9702903ee592a9810a96884c00e618c3;p=thirdparty%2Fsqlite.git CLI closer to doing full cleanup on error exits. Needs testing with OOM simulation. FossilOrigin-Name: 8751f93fa505a514d8ab7eae4f9093310ee60b90046f4632e80858001781cb31 --- 0cb00bbe9702903ee592a9810a96884c00e618c3 diff --cc manifest index 9b21651f10,20ce1b4b84..f6ac686c3c --- a/manifest +++ b/manifest @@@ -1,5 -1,5 +1,5 @@@ - C CLI\sresource\smanagement\sapplied\sto\sabout\s30%\sof\sshell\ssource.\sA\sfew\sminor\sflaws\sfixed.\sComments\sbegin\sto\sdescribe\sOOM\sresponses. - D 2023-05-12T21:21:37.123 -C Fix\sharmless\scompiler\swarning\scaused\sby\s[0772ddf56713d013]. -D 2023-05-12T19:06:00.854 ++C CLI\scloser\sto\sdoing\sfull\scleanup\son\serror\sexits.\sNeeds\stesting\swith\sOOM\ssimulation. ++D 2023-05-13T07:23:14.350 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@@ -637,13 -633,10 +637,13 @@@ F src/pragma.h e690a356c18e98414d2e870e F src/prepare.c 6350675966bd0e7ac3a464af9dbfe26db6f0d4237f4e1f1acdb17b12ad371e6e F src/printf.c b9320cdbeca0b336c3f139fd36dd121e4167dd62b35fbe9ccaa9bab44c0af38d F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c - F src/resmanage.c e013c6a7703cfa11db74fd6269e9cecd5eb5ae371c3702bff0ddd1205e5a555a - F src/resmanage.h 4f4c0359cee4b3cdfff404851c5a877dfdd075ffdc8206c289fc05ea39a9c685 ++F src/resmanage.c 3495deb8d4e7499eec30c1de4c4ce85a610a611f42fd74a24584416f0d1f9aba ++F src/resmanage.h 9f937b7d2334b15f5ed2c3f38d707fec5062b414ecd2483d788a4325855eb3fd F src/resolve.c 3e53e02ce87c9582bd7e7d22f13f4094a271678d9dc72820fa257a2abb5e4032 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/select.c 12aa3168be4ff175702fe0ebeaf544312be22d275d378a28e7b2fad32d552d36 - F src/shell.c.in 924a3467c33c4cc6211c1f35d534f57f88d0e79005370982aeb2428d0951d6f0 -F src/shell.c.in 1e18312f58d365042036fc9d19dcef416074f783702b168f07814332c2268ee0 ++F src/shell.c.in d5761d88d2f47ba75c778f0383573f7def7eb6ba4248c301eda09f40606e9c42 +F src/shext_linkage.h 27dcf7624df05b2a7a6d367834339a6db3636f3035157f641f7db2ec499f8f6d F src/sqlite.h.in 27ca1d4b2eda8feee468af5735182390e8fe4696522751eec0136d17323201ad F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4 @@@ -2079,8 -2069,8 +2080,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 efdcf1093b4a327da36b5854cff32a8244244302a5f979859c1398e8d191fc6b 0772ddf56713d013cd1bd44f9c75977ca14f852e3a8f038b0a6b9814f6519d79 504effa89b48c0dc6cd12b3eaf6017be3f32017c601af17759a3cc185346d868 - R 54bc1318f5a495f2da0644115fe872c7 -P 559cb1c6de384fd14160db005acaf1858b8b018d3d40d7f786a56a49d3cdbd59 -R 4b606a426eae6c5f8f90a511d40b128f -U drh -Z 4e1ae7be32b97c6dbec7b8961e4a74ee ++P ce25a07950e10e5f0c33f179f9b7d307a73b23ad859b5a97e5c6d7bc9e68b254 f06c16a8b0e7a15ce4f7d99af3376a1bf1bfbfc0fdc048b079418ae74c619d6b ++R 383a924fa91f5b4145d9f04d112422b7 +U larrybr - Z d6d73699c601b55c7b4dec78c2a1df9f ++Z 69b83efe69b378ac6c729ccaf8b045c5 # Remove this line to create a well-formed Fossil manifest. diff --cc manifest.uuid index 6f5fcbf80b,4291b08431..889ced5443 --- a/manifest.uuid +++ b/manifest.uuid @@@ -1,1 -1,1 +1,1 @@@ - ce25a07950e10e5f0c33f179f9b7d307a73b23ad859b5a97e5c6d7bc9e68b254 -f06c16a8b0e7a15ce4f7d99af3376a1bf1bfbfc0fdc048b079418ae74c619d6b ++8751f93fa505a514d8ab7eae4f9093310ee60b90046f4632e80858001781cb31 diff --cc src/resmanage.c index b4ae2aecf0,0000000000..f0f79bf39c mode 100644,000000..100644 --- a/src/resmanage.c +++ b/src/resmanage.c @@@ -1,302 -1,0 +1,339 @@@ +/* +** 2023 May 8 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file implements a simple resource management package, interface +** and function of which is described in resmanage.h . +*/ + +#include +#include + +#include +#include "resmanage.h" + +/* Track how to free various held resources. */ +typedef enum FreeableResourceKind { ++ FRK_Indirect = 1<<15, + FRK_Malloc = 0, FRK_DbConn = 1, FRK_DbStmt = 2, + FRK_DbMem = 3, FRK_File = 4, +#if (!defined(_WIN32) && !defined(WIN32)) || !SQLITE_OS_WINRT + FRK_Pipe, +#endif +#ifdef SHELL_MANAGE_TEXT + FRK_Text, +#endif ++ FRK_AnyRef, + FRK_CustomBase /* series of values for custom freers */ +} FreeableResourceKind; + +#if defined(_WIN32) || defined(WIN32) +# if !SQLITE_OS_WINRT +# include +# include +# undef pclose +# define pclose _pclose +# endif +#else +extern int pclose(FILE*); +#endif + +typedef struct ResourceHeld { + union { + void *p_any; + void *p_malloced; + sqlite3 *p_conn; + sqlite3_stmt *p_stmt; + void *p_s3mem; + FILE *p_stream; +#ifdef SHELL_MANAGE_TEXT + ShellText *p_text; +#endif ++ AnyResourceHolder *p_any_rh; + } held; + FreeableResourceKind frk; +} ResourceHeld; + +/* The held-resource stack. This is for single-threaded use only. */ +static ResourceHeld *pResHold = 0; +static ResourceCount numResHold = 0; +static ResourceCount numResAlloc = 0; + +/* A small set of custom freers. It is linearly searched, used for +** layered heap-allocated (and other-allocated) data structures, so +** tends to have use limited to where slow things are happening. +*/ +static ResourceCount numCustom = 0; /* number of the set */ +static ResourceCount numCustomAlloc = 0; /* allocated space */ +typedef void (*FreerFunction)(void *); +static FreerFunction *aCustomFreers = 0; /* content of set */ + +const char *resmanage_oom_message = "out of memory, aborting"; + +/* Info recorded in support of quit_moan(...) and stack-ripping */ +static ResourceMark exit_mark = 0; +#ifndef SHELL_OMIT_LONGJMP +static jmp_buf *p_exit_ripper = 0; +#endif + +/* Current position of the held-resource stack */ +ResourceMark holder_mark(){ + return numResHold; +} + +/* Strip resource stack then strip call stack (or exit.) */ +void quit_moan(const char *zMoan, int errCode){ ++ int nFreed; + if( zMoan ){ - fprintf(stderr, "Quitting due to %s, freeing %d resources.\n", - zMoan, numResHold); ++ fprintf(stderr, "Error: Terminating due to %s.\n", zMoan); + } - holder_free(exit_mark); ++ fprintf(stderr, "Auto-freed %d resources.\n", holder_free(exit_mark)); +#ifndef SHELL_OMIT_LONGJMP + if( p_exit_ripper!=0 ){ + longjmp(*p_exit_ripper, errCode); + } else +#endif + exit(errCode); +} + +/* Free a single resource item. (ignorant of stack) */ - static void free_rk( ResourceHeld *pRH ){ - if( pRH->held.p_any == 0 ) return; ++static int free_rk( ResourceHeld *pRH ){ ++ int rv = 0; ++ if( pRH->held.p_any == 0 ) return rv; ++ if( pRH->frk & FRK_Indirect ){ ++ pRH->held.p_any = *(void**)pRH->held.p_any; ++ if( pRH->held.p_any == 0 ) return rv; ++ pRH->frk &= ~FRK_Indirect; ++ } ++ ++rv; + switch( pRH->frk ){ + case FRK_Malloc: + free(pRH->held.p_malloced); + break; + case FRK_DbConn: + sqlite3_close_v2(pRH->held.p_conn); + break; + case FRK_DbStmt: + sqlite3_clear_bindings(pRH->held.p_stmt); + sqlite3_finalize(pRH->held.p_stmt); + break; + case FRK_DbMem: + sqlite3_free(pRH->held.p_s3mem); + break; + case FRK_File: + fclose(pRH->held.p_stream); + break; +#if (!defined(_WIN32) && !defined(WIN32)) || !SQLITE_OS_WINRT + case FRK_Pipe: + pclose(pRH->held.p_stream); + break; +#endif +#ifdef SHELL_MANAGE_TEXT + case FRK_Text: + freeText(pRH->held.p_text); + break; +#endif ++ case FRK_AnyRef: ++ (pRH->held.p_any_rh->its_freer)(pRH->held.p_any_rh->pAny); ++ break; + default: + { + int ck = pRH->frk - FRK_CustomBase; + assert(ck>=0); + if( ck < numCustom ){ + aCustomFreers[ck]( pRH->held.p_any ); - } ++ }else --rv; + } + } + pRH->held.p_any = 0; ++ return rv; +} + +/* Take back a held resource pointer, leaving held as NULL. (no-op) */ +void* take_held(ResourceMark mark, ResourceCount offset){ + return swap_held(mark,offset,0); +} + +/* Swap a held resource pointer for a new one. */ +void* swap_held(ResourceMark mark, ResourceCount offset, void *pNew){ + ResourceCount rix = mark + offset; + assert(rix < numResHold && numResHold > 0); + if( rix < numResHold && numResHold > 0 ){ + void *rv = pResHold[rix].held.p_any; + pResHold[rix].held.p_any = pNew; + return rv; + }else return 0; +} + +/* Reserve room for at least count additional holders, with no chance +** of an OOM abrupt exit. This is used internally only by this package. +** The return is either 1 for success, or 0 indicating an OOM failure. + */ +static int more_holders_try(ResourceCount count){ + ResourceHeld *prh; + ResourceCount newAlloc = numResHold + count; + if( newAlloc < numResHold ) return 0; /* Overflow, sorry. */ + if( newAlloc <= numResAlloc ) return 1; + prh = (ResourceHeld*)realloc(pResHold, newAlloc*sizeof(ResourceHeld)); + if( prh == 0 ) return 0; + pResHold = prh; + numResAlloc = newAlloc; + return 1; +} + ++/* Lose one or more holders. */ ++void* pop_holder(void){ ++ assert(numResHold>0); ++ if( numResHold>0 ) return pResHold[--numResHold].held.p_any; ++ return 0; ++} ++void pop_holders(ResourceCount num){ ++ assert(numResHold>=num); ++ if( numResHold>=num ) numResHold -= num; ++ else numResHold = 0; ++} ++ +/* Shared resource-stack pushing code */ +static void res_hold(void *pv, FreeableResourceKind frk){ + ResourceHeld rh = { pv, frk }; + if( numResHold == numResAlloc ){ + ResourceCount nrn = (ResourceCount)((numResAlloc>>2) + 5); + if( !more_holders_try(nrn) ){ + free_rk(&rh); + quit_moan(resmanage_oom_message,1); + } + } + pResHold[numResHold++] = rh; +} + +/* Hold a NULL (Assure swap_held() cannot fail.) */ +void more_holders(ResourceCount more){ + if( !more_holders_try(more) ) quit_moan(resmanage_oom_message,1); +} + +/* Hold anything in the malloc() heap */ +void* mmem_holder(void *pm){ + res_hold(pm, FRK_Malloc); + return pm; +} +/* Hold a C string in the malloc() heap */ +char* mstr_holder(char *z){ + res_hold(z, FRK_Malloc); + return z; +} +/* Hold a C string in the SQLite heap */ +char* sstr_holder(char *z){ + res_hold(z, FRK_DbMem); + return z; +} ++/* Hold a C string in the SQLite heap, reference to */ ++void sstr_ptr_holder(char **pz){ ++ assert(pz!=0); ++ res_hold(pz, FRK_DbMem|FRK_Indirect); ++} +/* Hold an open C runtime FILE */ +void file_holder(FILE *pf){ + res_hold(pf, FRK_File); +} +#if (!defined(_WIN32) && !defined(WIN32)) || !SQLITE_OS_WINRT +/* Hold an open C runtime pipe */ +void pipe_holder(FILE *pp){ + res_hold(pp, FRK_Pipe); +} +#endif +#ifdef SHELL_MANAGE_TEXT - /* a reference to a ShellText object, (storage for which not managed) */ - static void text_holder(ShellText *pt){ ++/* a ShellText object, reference to (storage for which not managed) */ ++static void text_ref_holder(ShellText *pt){ + res_hold(pt, FRK_Text); +} +#endif +/* Hold anything together with arbitrary freeing function */ +void* any_holder(void *pm, void (*its_freer)(void*)){ + int i = 0; + while( i < numCustom ){ + if( its_freer == aCustomFreers[i] ) break; + ++i; + } + if( i == numCustom ){ + size_t ncf = numCustom + 2; + FreerFunction *pcf; + pcf = (FreerFunction *)realloc(aCustomFreers, ncf*sizeof(FreerFunction)); + if( pcf!=0 ){ + assert(ncf < (1<<16)); + numCustomAlloc = (ResourceCount)ncf; + aCustomFreers = pcf; + aCustomFreers[numCustom++] = its_freer; + }else{ + quit_moan(resmanage_oom_message,1); + } + } + res_hold(pm, i + FRK_CustomBase); + return pm; +} +/* Hold some SQLite-allocated memory */ +void* smem_holder(void *pm){ + res_hold(pm, FRK_DbMem); + return pm; +} +/* Hold a SQLite database "connection" */ +void conn_holder(sqlite3 *pdb){ + res_hold(pdb, FRK_DbConn); +} +/* Hold a SQLite prepared statement */ +void stmt_holder(sqlite3_stmt *pstmt){ + res_hold(pstmt, FRK_DbStmt); +} ++/* Hold a SQLite prepared statement, reference to */ ++void stmt_ptr_holder(sqlite3_stmt **ppstmt){ ++ assert(ppstmt!=0); ++ res_hold(ppstmt, FRK_DbStmt|FRK_Indirect); ++} ++/* Hold a reference to an AnyResourceHolder (in stack frame) */ ++void any_ref_holder(AnyResourceHolder *parh){ ++ assert(parh!=0 && parh->its_freer!=0); ++ res_hold(parh, FRK_AnyRef); ++} + +/* Free all held resources in excess of given resource stack mark, +** then return how many needed freeing. */ +int holder_free(ResourceMark mark){ + int rv = 0; + while( numResHold > mark ){ - --numResHold; - if( pResHold[numResHold].held.p_any!=0 ){ - free_rk(&pResHold[numResHold]); - ++rv; - } ++ rv += free_rk(&pResHold[--numResHold]); + } + if( mark==0 ){ + if( numResAlloc>0 ){ + free(pResHold); + pResHold = 0; + numResAlloc = 0; + } + if( numCustomAlloc>0 ){ + free(aCustomFreers); + aCustomFreers = 0; + numCustom = 0; + numCustomAlloc = 0; + } + } + return rv; +} + +#ifndef SHELL_OMIT_LONGJMP +/* Record a resource stack and call stack rip-to position */ +void register_exit_ripper(jmp_buf *pjb, ResourceMark rip_mark){ + exit_mark = rip_mark; + p_exit_ripper = pjb; +} +/* Undo register_exit_ripper effect, back to default state. */ +void forget_exit_ripper(jmp_buf *pjb){ + exit_mark = 0; + assert(p_exit_ripper == pjb); + p_exit_ripper = 0; +} +#endif diff --cc src/resmanage.h index bb276de611,0000000000..b083d1724e mode 100644,000000..100644 --- a/src/resmanage.h +++ b/src/resmanage.h @@@ -1,132 -1,0 +1,165 @@@ +/* +** 2023 May 8 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file declares the interface of a simple resource management package +** which supports freeing of resources upon an abrupt, program-initiated +** termination of a function from somewhere in a call tree. The package is +** designed to be used with setjmp()/longjmp() to effect a pattern similar +** to the try/throw/catch available in some programming languages. (But see +** below regarding usage when the setmp()/longjmp() pair is unavailable.) +** +** The general scheme is that routines whose pre-return code might be +** bypassed, thereby leaking resources, do not rely on pre-return code +** to release locally held resources. Instead, they give ownership of +** such resources to this package, via xxxx_holder(...) calls, and use +** its holder_mark() and holder_free(...) functions to release locally +** acquired resources. +** +** For environments where setmp()/longjmp() are unavailable, (indicated by +** SHELL_OMIT_LONGJMP defined), the package substitutes a process exit for +** resumption of execution at a chosen code location. The resources in the +** package's held resource stack are still released. And the ability to +** free locally acquired resources as functions return is retained. This +** can simplify early function exit logic, but a body of code relying on +** process exit to ease error handling is unsuitable for use as a called +** routine within a larger application. That use is most of the reason for +** this package's existence. +*/ + +#ifndef RES_MANAGE_H +# define RES_MANAGE_H + +#ifndef SHELL_OMIT_LONGJMP +# include +# define RIP_STATE(jb) jmp_buf jb +# define RIP_TO_HERE(jb) setjmp(jb) +#else +# define RIP_STATE(jb) +# define RIP_TO_HERE(jb) 0 +#endif +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include "sqlite3.h" + - /* Type used for marking positions within a held-resource stack */ ++/* Type used for marking/conveying positions within a held-resource stack */ +typedef unsigned short ResourceMark; +typedef unsigned short ResourceCount; + ++/* Type used for generic free functions (to fib about their signature.) */ ++typedef void (*GenericFreer)(void*); ++ +/* Current position of the held-resource stack */ +extern ResourceMark holder_mark(); + +/* Assure no allocation failure for some more xxx_holder() calls. +** Note that this call may fail with an OOM abrupt exit. */ +extern void more_holders(ResourceCount more); + - /* Routines for holding resources on held-resource stack together ++/* Lose one or more holders, without freeing anything. */ ++extern void* pop_holder(void); ++extern void pop_holders(ResourceCount num); ++ ++/* ++** Routines for holding resources on held-resource stack together +** with enough information for them to be freed by this package. +*/ ++ ++/* xxxx_holder(...) ++** The referenced objects are directly freed; they are stored in ++** the heap with lifetime not bound to the caller's activation. ++*/ +/* anything together with arbitrary freeing function */ +extern void* any_holder(void *pm, void (*its_freer)(void*)); +/* anything in the malloc() heap */ +extern void* mmem_holder(void *pm); +/* a C string in the malloc() heap */ +extern char* mstr_holder(char *z); +/* some SQLite-allocated memory */ +extern void* smem_holder(void *pm); +/* a C string in the SQLite heap */ +extern char* sstr_holder(char *z); +/* a SQLite database "connection" */ +extern void conn_holder(sqlite3 *pdb); +/* a SQLite prepared statement */ +extern void stmt_holder(sqlite3_stmt *pstmt); +/* an open C runtime FILE */ +extern void file_holder(FILE *); +#if (!defined(_WIN32) && !defined(WIN32)) || !SQLITE_OS_WINRT +/* an open C runtime pipe */ +extern void pipe_holder(FILE *); +#endif ++ ++/* xxxx_ptr_holder(...) or xxxx_ref_holder(...) ++** The referenced objects are indirectly freed; they are stored ++** in the caller's stack frame, with lifetime limited to the ++** the caller's activation. It is a grave error to use these ++** then fail to call holder_free() before returning. For abrupt ++** exits, this condition is met because holder_free() is called ++** by the abrupt exiter before the execution stack is stripped. ++*/ ++typedef struct AnyResourceHolder { ++ void *pAny; ++ GenericFreer its_freer; ++} AnyResourceHolder; ++ ++/* a reference to an AnyResourceHolder (whose storage is not managed) */ ++extern void any_ref_holder(AnyResourceHolder *parh); ++/* a C string in the SQLite heap, reference to */ ++extern void sstr_ptr_holder(char **pz); ++/* a SQLite prepared statement, reference to */ ++extern void stmt_ptr_holder(sqlite3_stmt **ppstmt); +#ifdef SHELL_MANAGE_TEXT - /* a reference to a ShellText object, (storage for which not managed) */ - static void text_holder(ShellText *); ++/* a ShellText object, reference to (storage for which not managed) */ ++static void text_ref_holder(ShellText *); +#endif + +/* Take back a held resource pointer, leaving held as NULL. (no-op) */ +extern void* take_held(ResourceMark mark, ResourceCount offset); + +/* Swap a held resource pointer for a new one. */ +extern void* swap_held(ResourceMark mark, ResourceCount offset, void *pNew); + +/* Free all held resources in excess of given resource stack mark. +** Return count of number actually freed (rather than being 0.) */ +extern int holder_free(ResourceMark mark); + +#ifndef SHELL_OMIT_LONGJMP +/* Remember a longjmp() destination together with a resource stack +** mark which determines how far the resource stack may be stripped. +** This info determines how far the execution and resource stacks +** will be stripped back should quit_moan(...) be called. +*/ +extern void register_exit_ripper(jmp_buf *pjb, ResourceMark rip_mark); +/* Forget whatever register_exit_ripper() has been recorded. */ +extern void forget_exit_ripper(jmp_buf *pjb); +#else +#define register_exit_ripper(jb, rm) +#define forget_exit_ripper() +#endif + +/* Strip resource stack and execute previously registered longjmp() as +** previously prepared by register_exit_ripper() call. Or, if no such +** prep done (or possible), strip the whole stack and exit the process. +*/ +extern void quit_moan(const char *zMoan, int errCode); + +/* What the complaint will be for OOM failures and abrupt exits. */ +extern const char *resmanage_oom_message; + +#ifdef __cplusplus +} /* End of the 'extern "C"' block */ +#endif +#endif /* RES_MANAGE_H */ diff --cc src/shell.c.in index 72496ae665,ccaed896d9..fef2bb43a9 --- a/src/shell.c.in +++ b/src/shell.c.in @@@ -506,19 -469,15 +506,29 @@@ static int console_utf8 = 0 */ static int stdout_is_console = 1; ++/* ++** 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; +/* +** Mutex used to access *globalDb from main thread or ^C handler. +*/ +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; @@@ -813,24 -760,13 +823,37 @@@ 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 condition their way. - ** This is crude, and using it will undoubtedly leak memory and handles. - ** The alternative is extensive recoding of the shell, (not for today.) - ** This could become a longjump(...) (paired with app's setjump(...).) ++** 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); +*/ - #ifndef SHELL_OOM_EXIT - # define SHELL_OOM_EXIT 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 ++ holder_free(exit_mark); ++ SHELL_TERMINATE(zWhy); +#endif - /* Indicate out-of-memory and exit. */ ++} ++ ++/* Indicate out-of-memory and terminate. */ +static void shell_out_of_memory(void){ - raw_printf(STD_ERR,"Error: out of memory\n"); - sqlite3_mutex_free(pGlobalDbLock); - pGlobalDbLock = 0; - SHELL_OOM_EXIT; ++ shell_terminate("out of memory"); } --/* Check a pointer to see if it is NULL. If it is NULL, exit with an ++/* Check a pointer to see if it is NULL. If so, terminate with an ** out-of-memory error. */ static void shell_check_oom(const void *p){ @@@ -985,80 -913,10 +1008,81 @@@ 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. (3rd and 4th members) +** All other members are simply initialized to select the +** input stream and track input sources, set by whatever +** routines redirect the input. +*/ +typedef struct InSource { + FILE *inFile; /* Will be 0 when input is to be taken from string. */ + char *zStrIn; /* Will be 0 when no input is available from string. */ + int iReadOffset; /* Offset into zStrIn where next "read" to be done. */ + int lineno; /* How many lines have been read from this source */ + const char *zSourceSay; /* For complaints, keep a name for this source */ + struct InSource *pFrom; /* and redirect tracking to aid unraveling. */ +} InSource; +#define INSOURCE_STR_REDIR(str, tagTo, isFrom) {0, str, 0, 0, tagTo, isFrom} +#define INSOURCE_FILE_REDIR(fh, tagTo, isFrom) {fh, 0, 0, 0, tagTo, isFrom} +#define INSOURCE_IS_INTERACTIVE(pIS) \ + ((pIS)==&termInSource && stdin_is_interactive ) - ++#define INSOURCE_IS_INVOKEARG(pIS) \ ++ ((pIS)==&cmdInSource) +/* This instance's address is taken as part of interactive input test. */ +static InSource termInSource = { 0, 0, 0, 0, "", 0}; +static InSource stdInSource = { 0, 0, 0, 0, "", 0}; +static InSource cmdInSource = { 0, 0, 0, 0, "", 0}; +static void init_std_inputs(FILE *pIn ){ + termInSource.inFile = pIn; + stdInSource.inFile = pIn; + cmdInSource.lineno = 0; +} +static void set_invocation_cmd(char *zDo){ + cmdInSource.iReadOffset = 0; + cmdInSource.zStrIn = zDo; + ++cmdInSource.lineno; +} + +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. @@@ -1308,6 -1103,6 +1332,8 @@@ static char* takeText(ShellText *p) ** ** If the third argument, quote, is not '\0', then it is used as a ** quote character for zAppend. ++** ++** This routine can abruptly exit under OOM conditions. */ static void appendText(ShellText *p, const char *zAppend, char quote){ i64 len; @@@ -1381,18 -1173,13 +1407,18 @@@ static char *shellFakeSchema char cQuote; char *zDiv = "("; int nRow = 0; - - zSql = sqlite3_mprintf("PRAGMA \"%w\".table_info=%Q;", - zSchema ? zSchema : "main", zName); + int rc; + char *rv = 0; + ResourceMark rm_mark = holder_mark(); + char *zSql = smprintf("PRAGMA \"%w\".table_info=%Q;", + zSchema ? zSchema : "main", zName); shell_check_oom(zSql); - sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); + if( rc==SQLITE_NOMEM ) shell_out_of_memory(); + stmt_holder(pStmt); initText(&s); - text_holder(&s); ++ text_ref_holder(&s); if( zSchema ){ cQuote = quoteChar(zSchema); if( cQuote && sqlite3_stricmp(zSchema,"temp")==0 ) cQuote = 0; @@@ -2063,19 -1634,10 +2089,16 @@@ static void shellPutsFunc /* ** If in safe mode, print an error message described by the arguments - ** and "exit" without returning to the caller. This "exit" will occur - ** immediately, as a process exit, for normal shell builds. When the - ** shell is built with options to use it embedded, control returns to - ** the caller of the shell's main, "do shell things" entry point. -** and exit immediately. ++** and arrange for a semi-abrupt exit. Unsafe actions are blocked ++** by their doers calling this and not acting if 1 is returned. ++** The process_input() routine detects the semi-abrupt exit, (which ++** is indicated by the external shell state member, shellAbruptExit ++** being set non-zero), and aborts any further input processing. +** - ** It is an error, (perhaps with only minor effect such as memory leak), - ** for a dot-command to call this function while it holds resources in - ** need of freeing. Instead, it should be called before acquiring them. - ** - ** The return is true if failing, 0 otherwise. ++** The return is 1 if failing for a forbidded unsafe act, 0 otherwise. */ -static void failIfSafeMode( - ShellState *p, +static int failIfSafeMode( + ShellExState *psx, const char *zErrMsg, ... ){ @@@ -2147,13 -1689,8 +2170,13 @@@ static void editFunc if( argc==2 ){ zEditor = (const char*)sqlite3_value_text(argv[1]); + /* If that failed for OOM, just pretend it is not there. */ }else{ zEditor = getenv("VISUAL"); - /* Note that this code NOT threadsafe due to use of the getenv() ++ /* Note that this code is NOT threadsafe due to use of the getenv() + ** result. Because the shell is single-threaded, this is fine. + ** But if the CLI is called as a subroutine in a multi-threaded + ** program, adjustments may have to be made. */ } if( zEditor==0 ){ sqlite3_result_error(context, "no editor for edit()", -1); @@@ -5027,38 -4255,34 +5050,36 @@@ static int shell_exec } #ifndef SQLITE_OMIT_VIRTUALTABLE - if( pArg->expert.pExpert ){ - rc = expertHandleSQL(pArg, zSql, pzErrMsg); - return expertFinish(pArg, (rc!=SQLITE_OK), pzErrMsg); + if( psi->expert.pExpert ){ + rc = expertHandleSQL(psi, zSql, pzErrMsg); + return expertFinish(psi, (rc!=SQLITE_OK), pzErrMsg); } #endif - stmt_holder(pStmt); /* offset 0 */ - stmt_holder(pExplain); /* offset 1 */ - sstr_holder(zEQP); /* offset 2 */ - ++ stmt_ptr_holder(&pStmt); /* offset 0 */ ++ stmt_ptr_holder(&pExplain); /* offset 1 */ ++ sstr_ptr_holder(&zEQP); /* offset 2 */ while( zSql[0] && (SQLITE_OK == rc) ){ static const char *zStmtSql; rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover); if( SQLITE_OK != rc ){ + if( rc==SQLITE_NOMEM ) shell_out_of_memory(); if( pzErrMsg ){ -- *pzErrMsg = save_err_msg(db, "in prepare", rc, zSql); - shell_check_oom(*pzErrMsg); ++ shell_check_oom(*pzErrMsg = save_err_msg(db, "in prepare", rc, zSql)); } }else{ if( !pStmt ){ /* this happens for a comment or white-space */ - zSql = zLeftover; - while( IsSpace(zSql[0]) ) zSql++; + zSql = skipWhite(zLeftover); continue; } - swap_held(mark, 0, pStmt); zStmtSql = sqlite3_sql(pStmt); if( zStmtSql==0 ) zStmtSql = ""; - while( IsSpace(zStmtSql[0]) ) zStmtSql++; + else zStmtSql = skipWhite(zStmtSql); /* save off the prepared statment handle and reset row count */ - if( pArg ){ - pArg->pStmt = pStmt; - pArg->cnt = 0; + if( psx ){ + psi->pStmt = pStmt; + psx->resultCount = 0; } /* Show the EXPLAIN QUERY PLAN if .eqp is on */ @@@ -5066,63 -4292,45 +5087,59 @@@ int triggerEQP = 0; disable_debug_trace_modes(); sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, -1, &triggerEQP); - if( pArg->autoEQP>=AUTOEQP_trigger ){ + if( psi->autoEQP>=AUTOEQP_trigger ){ sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 1, 0); } - zEQP = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", zStmtSql); + zEQP = smprintf("EXPLAIN QUERY PLAN %s", zStmtSql); shell_check_oom(zEQP); - swap_held(mark, 2, zEQP); rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0); if( rc==SQLITE_OK ){ - swap_held(mark, 1, pExplain); while( sqlite3_step(pExplain)==SQLITE_ROW ){ const char *zEQPLine = (const char*)sqlite3_column_text(pExplain,3); int iEqpId = sqlite3_column_int(pExplain, 0); int iParentId = sqlite3_column_int(pExplain, 1); if( zEQPLine==0 ) zEQPLine = ""; - if( zEQPLine[0]=='-' ) eqp_render(pArg, 0); - eqp_append(pArg, iEqpId, iParentId, zEQPLine); + if( zEQPLine[0]=='-' ) eqp_render(psi, 0); + eqp_append(psi, iEqpId, iParentId, zEQPLine); } - eqp_render(pArg, 0); + eqp_render(psi, 0); } sqlite3_finalize(pExplain); - swap_held(mark, 1, 0); ++ pExplain = 0; sqlite3_free(zEQP); - swap_held(mark, 2, 0); - if( pArg->autoEQP>=AUTOEQP_full ){ ++ zEQP = 0; + if( psi->autoEQP>=AUTOEQP_full ){ /* Also do an EXPLAIN for ".eqp full" mode */ - zEQP = sqlite3_mprintf("EXPLAIN %s", zStmtSql); + zEQP = smprintf("EXPLAIN %s", zStmtSql); shell_check_oom(zEQP); - swap_held(mark, 2, zEQP); rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0); if( rc==SQLITE_OK ){ - swap_held(mark, 1, pExplain); - pArg->cMode = MODE_Explain; - explain_data_prepare(pArg, pExplain); - exec_prepared_stmt(pArg, pExplain); - explain_data_delete(pArg); + explain_data_prepare(psi, pExplain); + psi->cMode = MODE_Explain; +#if SHELL_DATAIO_EXT + { + ExportHandler *pexSave = psi->pActiveExporter; + psi->pActiveExporter = psi->pFreeformExporter; + exec_prepared_stmt(psx, pExplain); + psi->pActiveExporter = pexSave; + } +#else + exec_prepared_stmt(psx, pExplain); +#endif + explain_data_delete(psi); } sqlite3_finalize(pExplain); - swap_held(mark, 1, 0); ++ pExplain = 0; sqlite3_free(zEQP); - swap_held(mark, 2, 0); ++ zEQP = 0; } - if( pArg->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){ + if( psi->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){ sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 0, 0); /* Reprepare pStmt before reactiving trace modes */ sqlite3_finalize(pStmt); ++ pStmt = 0; sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); - swap_held(mark, 0, pStmt); - if( pArg ) pArg->pStmt = pStmt; + if( psx ) psi->pStmt = pStmt; } restore_debug_trace_modes(); } @@@ -5164,12 -4372,12 +5181,12 @@@ ** copy of the error message. Otherwise, set zSql to point to the ** next statement to execute. */ rc2 = sqlite3_finalize(pStmt); - swap_held(mark, 0, 0); ++ pStmt = 0; if( rc!=SQLITE_NOMEM ) rc = rc2; if( rc==SQLITE_OK ){ - zSql = zLeftover; - while( IsSpace(zSql[0]) ) zSql++; + zSql = skipWhite(zLeftover); }else if( pzErrMsg ){ -- *pzErrMsg = save_err_msg(db, "stepping", rc, 0); ++ shell_check_oom(*pzErrMsg = save_err_msg(db, "stepping", rc, 0)); } /* clear saved stmt handle */ @@@ -5188,11 -4395,11 +5205,13 @@@ */ static void freeColumnList(char **azCol){ int i; -- for(i=1; azCol[i]; i++){ -- sqlite3_free(azCol[i]); ++ if( azCol!=0 ){ ++ for(i=1; azCol[i]; i++){ ++ sqlite3_free(azCol[i]); ++ } ++ /* azCol[0] is a static string */ ++ sqlite3_free(azCol); } -- /* azCol[0] is a static string */ -- sqlite3_free(azCol); } /* @@@ -5206,9 -4413,9 +5225,12 @@@ ** name of the rowid column. ** ** The first regular column in the table is azCol[1]. The list is terminated --** by an entry with azCol[i]==0. ++** by an entry with azCol[i]==0 for i>0. This is an invariant, maintained ++** as the list is grown so that freeColumnList() can always deal with it. ++** ++** This function can exit abruptly under OOM conditions. */ -static char **tableColumnList(ShellState *p, const char *zTab){ +static char **tableColumnList(sqlite3 *db, const char *zTab, int preserveRowid){ char **azCol = 0; sqlite3_stmt *pStmt; char *zSql; @@@ -5216,20 -4423,21 +5238,27 @@@ int nAlloc = 0; int nPK = 0; /* Number of PRIMARY KEY columns seen */ int isIPK = 0; /* True if one PRIMARY KEY column of type INTEGER */ - int preserveRowid = ShellHasFlag(p, SHFLG_PreserveRowid); ++ ResourceMark mark = holder_mark(); ++ AnyResourceHolder arh = { 0, (GenericFreer)freeColumnList }; int rc; - zSql = sqlite3_mprintf("PRAGMA table_info=%Q", zTab); + zSql = smprintf("PRAGMA table_info=%Q", zTab); shell_check_oom(zSql); - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); if( rc ) return 0; ++ stmt_holder(pStmt); /* offset 0 */ ++ any_ref_holder(&arh); /* offset 1 */ while( sqlite3_step(pStmt)==SQLITE_ROW ){ if( nCol>=nAlloc-2 ){ ++ int nAllocPrev = nAlloc; nAlloc = nAlloc*2 + nCol + 10; azCol = sqlite3_realloc(azCol, nAlloc*sizeof(azCol[0])); shell_check_oom(azCol); ++ memset(azCol+nAllocPrev, 0, (nAlloc-nAllocPrev)*sizeof(azCol[0])); ++ arh.pAny = azCol; } - azCol[++nCol] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 1)); + azCol[++nCol] = smprintf("%s", sqlite3_column_text(pStmt, 1)); shell_check_oom(azCol[nCol]); if( sqlite3_column_int(pStmt, 5) ){ nPK++; @@@ -5243,10 -4451,10 +5272,11 @@@ } } } -- sqlite3_finalize(pStmt); ++ swap_held(mark, 1, 0); /* Now that it's built, save it from takedown. */ ++ holder_free(mark); if( azCol==0 ) return 0; -- azCol[0] = 0; -- azCol[nCol+1] = 0; ++ any_ref_holder(&arh); /* offset 0 */ ++ /* azCol[0] = 0; azCol[nCol+1] = 0; -- Done by memset() above. */ /* The decision of whether or not a rowid really needs to be preserved ** is tricky. We never need to preserve a rowid for a WITHOUT ROWID table @@@ -5262,17 -4470,17 +5292,17 @@@ ** there is a "pk" entry in "PRAGMA index_list". There will be ** no "pk" index if the PRIMARY KEY really is an alias for the ROWID. */ - zSql = sqlite3_mprintf("SELECT 1 FROM pragma_index_list(%Q)" - " WHERE origin='pk'", zTab); + zSql = smprintf("SELECT 1 FROM pragma_index_list(%Q)" + " WHERE origin='pk'", zTab); shell_check_oom(zSql); - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); if( rc ){ -- freeColumnList(azCol); ++ holder_free(mark); return 0; } ++ stmt_holder(pStmt); rc = sqlite3_step(pStmt); -- sqlite3_finalize(pStmt); preserveRowid = rc==SQLITE_ROW; } if( preserveRowid ){ @@@ -5295,6 -4503,6 +5325,8 @@@ } } } ++ swap_held(mark, 0, 0); /* Save built list from takedown (again.) */ ++ holder_free(mark); return azCol; } @@@ -5326,10 -4534,9 +5358,11 @@@ static int dump_callback(void *pArg, in const char *zTable; const char *zType; const char *zSql; - ShellState *p = (ShellState *)pArg; + ShellInState *psi = (ShellInState *)pArg; + ShellExState *psx = XSS(psi); int dataOnly; int noSys; ++ ResourceMark mark = holder_mark(); UNUSED_PARAMETER(azNotUsed); if( nArg!=3 || azArg==0 ) return 0; @@@ -5366,23 -4575,22 +5399,28 @@@ } if( cli_strcmp(zType, "table")==0 ){ ++ AnyResourceHolder arh = { 0, (GenericFreer)freeColumnList }; ShellText sSelect; ShellText sTable; char **azCol; int i; - char *savedDestTable; + const char *savedDestTable; int savedMode; + int preserveRowid = (psi->shellFlgs & SHFLG_PreserveRowid)!=0; - azCol = tableColumnList(p, zTable); + azCol = tableColumnList(DBI(psi), zTable, preserveRowid); if( azCol==0 ){ - p->nErr++; + psi->nErr++; return 0; } ++ /* Hereafter, resource management is used (for a column list.) */ ++ arh.pAny = azCol; ++ any_ref_holder(&arh); /* Always quote the table name, even if it appears to be pure ascii, ** in case it is a keyword. Ex: INSERT INTO "table" ... */ initText(&sTable); ++ text_ref_holder(&sTable); appendText(&sTable, zTable, quoteChar(zTable)); /* If preserving the rowid, add a column list after the table name. ** In other words: "INSERT INTO tab(rowid,a,b,c,...) VALUES(...)" @@@ -5400,6 -4608,6 +5438,7 @@@ /* Build an appropriate SELECT statement */ initText(&sSelect); ++ text_ref_holder(&sSelect); appendText(&sSelect, "SELECT ", 0); if( azCol[0] ){ appendText(&sSelect, azCol[0], 0); @@@ -5411,26 -4619,26 +5450,24 @@@ appendText(&sSelect, ",", 0); } } -- freeColumnList(azCol); appendText(&sSelect, " FROM ", 0); appendText(&sSelect, zTable, quoteChar(zTable)); - savedDestTable = p->zDestTable; - savedMode = p->mode; - p->zDestTable = sTable.z; - p->mode = p->cMode = MODE_Insert; - rc = shell_exec(p, sSelect.z, 0); + savedDestTable = psx->zDestTable; + savedMode = psi->mode; + psx->zDestTable = sTable.z; + psi->mode = psi->cMode = MODE_Insert; + rc = shell_exec(psx, sSelect.z, 0); if( (rc&0xff)==SQLITE_CORRUPT ){ - raw_printf(p->out, "/****** CORRUPTION ERROR *******/\n"); - toggleSelectOrder(p->db); - shell_exec(p, sSelect.z, 0); - toggleSelectOrder(p->db); - } - p->zDestTable = savedDestTable; - p->mode = savedMode; - freeText(&sTable); - freeText(&sSelect); - if( rc ) p->nErr++; + raw_printf(psi->out, "/****** CORRUPTION ERROR *******/\n"); + toggleSelectOrder(DBX(psx)); + shell_exec(psx, sSelect.z, 0); + toggleSelectOrder(DBX(psx)); + } + psx->zDestTable = savedDestTable; + psi->mode = savedMode; - freeText(&sTable); - freeText(&sSelect); + if( rc ) psi->nErr++; ++ holder_free(mark); } return 0; } @@@ -5447,8 -4655,8 +5484,11 @@@ static int run_schema_dump_query const char *zQuery ){ int rc; ++ ResourceMark mark = holder_mark(); char *zErr = 0; - rc = sqlite3_exec(p->db, zQuery, dump_callback, p, &zErr); ++ ++ sstr_ptr_holder(&zErr); + rc = sqlite3_exec(DBI(psi), zQuery, dump_callback, psi, &zErr); if( rc==SQLITE_CORRUPT ){ char *zQ2; int len = strlen30(zQuery); @@@ -5458,161 -4666,532 +5498,163 @@@ sqlite3_free(zErr); zErr = 0; } -- zQ2 = malloc( len+100 ); -- if( zQ2==0 ) return rc; ++ shell_check_oom(zQ2 = malloc( len+100 )); ++ mmem_holder(zQ2); sqlite3_snprintf(len+100, zQ2, "%s ORDER BY rowid DESC", zQuery); - rc = sqlite3_exec(p->db, zQ2, dump_callback, p, &zErr); + rc = sqlite3_exec(DBI(psi), zQ2, dump_callback, psi, &zErr); if( rc ){ - utf8_printf(p->out, "/****** ERROR: %s ******/\n", zErr); + utf8_printf(psi->out, "/****** ERROR: %s ******/\n", zErr); }else{ rc = SQLITE_CORRUPT; } -- sqlite3_free(zErr); -- free(zQ2); } ++ bail: ++ holder_free(mark); 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. ** -** The help text for each individual command begins with a line that starts -** with ".". Subsequent lines are supplemental information. +** For convenience, a nul-terminator byte is always appended to the data read +** from the file before the buffer is returned. This byte is not included in +** the final value of (*pnByte), if applicable. +** +** NULL is returned if any error is encountered. The final value of *pnByte +** is undefined in this case. + ** -** 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", +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 - ".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", -#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", -#endif - ".tables ?TABLE? List names of tables matching LIKE pattern TABLE", -#ifndef SQLITE_SHELL_FIDDLE - ",testcase NAME Begin redirecting output to 'testcase-out.txt'", -#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", -#ifdef SQLITE_ENABLE_NORMALIZE - " --normalized Normal the SQL statements", +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 - " --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", + +#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 - ".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", -}; /* -** Output help text. -** -** zPattern describes the set of commands for which help text is provided. -** If zPattern is NULL, then show all commands, but only give a one-line -** description of each. +** Try to deduce the type of file for zName based on its content. Return +** one of the SHELL_OPEN_* constants. ** -** Return the number of matches. +** If the file does not exist or is empty but its name looks like a ZIP +** archive and the dfltZip flag is true, then assume it is a ZIP archive. +** Otherwise, assume an ordinary database regardless of the filename if +** the type cannot be determined from content. */ -static int showHelp(FILE *out, const char *zPattern){ - int i = 0; - int j = 0; - int n = 0; - char *zPat; - if( zPattern==0 - || zPattern[0]=='0' - || cli_strcmp(zPattern,"-a")==0 - || cli_strcmp(zPattern,"-all")==0 - || cli_strcmp(zPattern,"--all")==0 - ){ - enum HelpWanted { HW_NoCull = 0, HW_SummaryOnly = 1, HW_Undoc = 2 }; - enum HelpHave { HH_Undoc = 2, HH_Summary = 1, HH_More = 0 }; - /* Show all or most commands - ** *zPattern==0 => 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; ip); - sqlite3_free(pSession->zName); - for(i=0; inFilter; i++){ - sqlite3_free(pSession->azFilter[i]); - } - sqlite3_free(pSession->azFilter); - memset(pSession, 0, sizeof(OpenSession)); -} -#endif - -/* -** Close all OpenSession objects and release all associated resources. -*/ -#if defined(SQLITE_ENABLE_SESSION) -static void session_close_all(ShellState *p, int i){ - int j; - struct AuxDb *pAuxDb = i<0 ? p->pAuxDb : &p->aAuxDb[i]; - for(j=0; jnSession; j++){ - session_close(&pAuxDb->aSession[j]); - } - pAuxDb->nSession = 0; -} -#else -# define session_close_all(X,Y) -#endif - -/* -** Implementation of the xFilter function for an open session. Omit -** any tables named by ".session filter" but let all other table through. -*/ -#if defined(SQLITE_ENABLE_SESSION) -static int session_filter(void *pCtx, const char *zTab){ - OpenSession *pSession = (OpenSession*)pCtx; - int i; - for(i=0; inFilter; i++){ - if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0; - } - return 1; -} -#endif - -/* -** Try to deduce the type of file for zName based on its content. Return -** one of the SHELL_OPEN_* constants. -** -** If the file does not exist or is empty but its name looks like a ZIP -** archive and the dfltZip flag is true, then assume it is a ZIP archive. -** Otherwise, assume an ordinary database regardless of the filename if -** the type cannot be determined from content. -*/ -int deduceDatabaseType(const char *zName, int dfltZip){ +u8 deduceDatabaseType(const char *zName, int dfltZip){ FILE *f = fopen(zName, "rb"); size_t n; - int rc = SHELL_OPEN_UNSPEC; + u8 rc = SHELL_OPEN_UNSPEC; char zBuf[100]; if( f==0 ){ if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){ @@@ -5648,10 -5227,11 +5690,11 @@@ /* ** Reconstruct an in-memory database using the output from the "dbtotxt" ** program. Read content from the file in p->aAuxDb[].zDbFilename. -** If p->aAuxDb[].zDbFilename is 0, then read from standard input. +** If p->aAuxDb[].zDbFilename is 0, then read from the present input. */ -static unsigned char *readHexDb(ShellState *p, int *pnData){ +static unsigned char *readHexDb(ShellInState *psi, int *pnData){ ++ ResourceMark mark = holder_mark(); unsigned char *a = 0; - int nLine; int n = 0; int pgsz = 0; int iOffset = 0; @@@ -5687,12 -5263,12 +5730,13 @@@ 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); ++ smem_holder(a); /* offset 0 */ memset(a, 0, n); if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){ - utf8_printf(stderr, "invalid pagesize\n"); + utf8_printf(STD_ERR, "invalid pagesize\n"); goto readHexDb_error; } - for(nLine++; fgets(zLine, sizeof(zLine), in)!=0; nLine++){ + while( strLineGet(zLine,sizeof(zLine), psi->pInSource)!=0 ){ rc = sscanf(zLine, "| page %d offset %d", &j, &k); if( rc==2 ){ iOffset = k; @@@ -5712,26 -5288,27 +5756,27 @@@ } } } - *pnData = n; - if( in!=p->in ){ - fclose(in); - }else{ - p->lineno = nLine; + *pnData = n; /* Record success and size. */ ++ swap_held(mark, 0, 0); + readHexDb_cleanup: + if( psi->pInSource==&inRedir ){ + fclose( inRedir.inFile ); + psi->pInSource = inRedir.pFrom; } ++ holder_free(mark); return a; -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; + 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; } - p->lineno = nLine; } -- sqlite3_free(a); - utf8_printf(stderr,"Error on line %d of --hexdb input\n", nLine); - return 0; + a = 0; + utf8_printf(STD_ERR,"Error on line %d within --hexdb input\n", nlError); + goto readHexDb_cleanup; } #endif /* SQLITE_OMIT_DESERIALIZE */ @@@ -5749,7 -5326,7 +5794,7 @@@ static void shellUSleepFunc sqlite3_result_int(context, sleep); } --/* Flags for open_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 @@@ -5808,21 -5381,20 +5853,21 @@@ static void open_db(ShellExState *psx, 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)); + 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)); ++ if( bail_on_error && !stdin_is_interactive ){ ++ shell_terminate("-bail used in batch mode."); ++ } if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){ -- exit(1); ++ shell_terminate("with OPEN_DB_KEEPALIVE."); } - sqlite3_close(DBX(psx)); - 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); + sqlite3_open(":memory:", &DBX(psx)); + if( DBX(psx)==0 || SQLITE_OK!=sqlite3_errcode(DBX(psx)) ){ - utf8_printf(stderr, - "Also: unable to open substitute in-memory database.\n" - ); - exit(1); ++ shell_terminate("Also: unable to open substitute in-memory database."); }else{ utf8_printf(stderr, "Notice: using substitute in-memory database instead of \"%s\"\n", @@@ -15268,486 -11480,160 +15313,488 @@@ static int runOneSqlLine(ShellExState * return 0; } -static void echo_group_input(ShellState *p, const char *zDo){ - if( ShellHasFlag(p, SHFLG_Echo) ) utf8_printf(p->out, "%s\n", zDo); +#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 -#ifdef SQLITE_SHELL_FIDDLE +/* Utility functions for process_input. */ + +#if SHELL_EXTENDED_PARSING /* -** 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). +** 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 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; - - UNUSED_PARAMETER(in); - UNUSED_PARAMETER(isContinuation); - if(!z || !*z){ +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; } - while(*z && isspace(*z)) ++z; - zBegin = z; - for(; *z && '\n'!=*z; ++nZ, ++z){} - if(nZ>0 && '\r'==zBegin[nZ-1]){ - --nZ; +} +#endif + +/* +** Grow the accumulation line buffer to accommodate ncNeed chars. +** In/out parameters pz and pna reference the buffer and its size. +** The buffer must eventually be sqlite3_free()'ed by the caller. +*/ +static void grow_line_buffer(char **pz, i64 *pna, int ncNeed){ + + if( ncNeed > *pna ){ + *pna += *pna + (*pna>>1) + 100; + *pz = sqlite3_realloc(*pz, *pna); + shell_check_oom(*pz); } - 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; + /* Termination kind: DCR_Ok, DCR_Error, DCR_Return, DCR_Exit, DCR_Abort, + * the greatest of whichever is applicable */ - u8 termKind = DCR_Ok; ++ 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 ){ +#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. */ +#if SHELL_EXTENDED_PARSING + 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 +#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. */ + inKind = Comment; + }else{ + /* Something dark, not a # comment or dot-command. Must be SQL. */ + inKind = Sql; + } break; - }else if( rc ){ - errCnt++; } } - 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); + } /* 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 && psi->pInSource==&cmdInSource ){ ++ 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; + } + 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; + } +#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; + } +#endif + default: + assert(inKind!=Tbd); + break; + } + if( XSS(psi)->shellAbruptExit!=0 ){ + termKind = DCR_Exit; } - p->bSafeMode = p->bSafeModePersist; - qss = QSS_Start; - }else if( nSql && QSS_PLAINWHITE(qss) ){ - echo_group_input(p, zSql); - nSql = 0; - qss = QSS_Start; + 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); } /* @@@ -16106,369 -11941,6 +16153,361 @@@ static void sayAbnormalExit(void) if( seenInterrupt ) fprintf(stderr, "Program interrupted.\n"); } +/* A vector of command strings collected from . */ +typedef struct CmdArgs { + /* Array is malloc'ed, but not its elements except by wmain()'s futzing. */ + char **azCmd; /* the strings */ + int nCmd; /* how many collected */ - ResourceMark buffMark; /* where to grab held array pointer */ +} CmdArgs; +/* 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 */ +} 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. +*/ +static int scanInvokeArgs(int argc, char **argv, int pass, ShellInState *psi, + CmdArgs *pca, 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{ - void *vaz; - if( pca->nCmd == 0 ){ - pca->buffMark = holder_mark(); - more_holders(1); - } - vaz = realloc(pca->azCmd, sizeof(pca->azCmd[0])*(pca->nCmd+1)); ++ void *vaz = realloc(pca->azCmd, sizeof(pca->azCmd[0])*(pca->nCmd+1)); + shell_check_oom(vaz); - if( pca->nCmd == 0 ) mmem_holder(vaz); - else swap_held(pca->buffMark, 0, vaz); + pca->azCmd = (char**)vaz; + pca->azCmd[pca->nCmd++] = z; + /* Excesss 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_oom(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_multiple_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 !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) + }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 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 + }else if( cli_strcmp(z,"-nonce")==0 ){ + free(psi->zNonce); + psi->zNonce = strdup(argv[++i]); + shell_check_oom(psi->zNonce); + }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); + }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 seems goofy. It would + ** be better if all commands ran in the order that they appear. But + ** we retain the goofy behavior for historical compatibility. */ + if( i==argc-1 ) break; /* Pretend specified command is empty. */ + set_invocation_cmd(cmdline_option_value(argc,argv,++i)); + drc = process_input(psi); + rc = (drc>2)? 2 : drc; + if( rc>0 ){ + break; + } +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) + }else if( cli_strncmp(z, "-A", 2)==0 ){ + if( pca->nCmd>0 ){ + utf8_printf(STD_ERR, "Error: cannot mix regular SQL or dot-commands" + " with \"%s\"\n", z); + rc = 1; + break; + } + open_db(XSS(psi), OPEN_DB_ZIPFILE); + if( z[2] ){ + argv[i] = &z[2]; + drc = arDotCommand(XSS(psi), 1, argv+(i-1), argc-(i-1)); + }else{ + drc = arDotCommand(XSS(psi), 1, argv+i, argc-i); + } + rc = (drc>2)? 2 : drc; + pad->readStdin = 0; + break; +#endif + }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 rc; +} + #ifndef SQLITE_SHELL_IS_UTF8 # if (defined(_WIN32) || defined(WIN32)) \ && (defined(_MSC_VER) || (defined(UNICODE) && defined(__GNUC__))) @@@ -16499,36 -11963,27 +16538,36 @@@ 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 + RIP_STATE(exit_jb); const char *zInitFile = 0; - int i; + int bQuiet = 0; /* for testing, to suppress banner and history actions */ + int i, aec; int rc = 0; + DotCmdRC drc = DCR_Ok; int warnInmemoryDb = 0; - /* azCmd, nCmd, buffMark */ - CmdArgs cmdArgs = {0,0,0}; - int readStdin = 1; - int nCmd = 0; ++ /* azCmd, nCmd */ ++ CmdArgs cmdArgs = {0,0}; + /* readStdin, nOptsEnd, zInitFile, zVfs, bQuiet */ + ArgsData argsData = { 1, argc, 0,0,0 }; int nOptsEnd = argc; - char **azCmd = 0; - const char *zVfs = 0; /* Value of -vfs command-line option */ #if !SQLITE_SHELL_IS_UTF8 char **argvToFree = 0; int argcToFree = 0; #endif - ResourceMark entry_mark = holder_mark(); - setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */ ++ main_resource_mark = holder_mark(); + setvbuf(STD_ERR, 0, _IONBF, 0); /* Make sure stderr is unbuffered */ #ifdef SQLITE_SHELL_FIDDLE stdin_is_interactive = 0; stdout_is_console = 1; @@@ -16537,10 -11992,10 +16576,12 @@@ 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 */ ++ atexit(console_restore); /* Needs revision for CLI as library call */ #endif -- atexit(sayAbnormalExit); ++ atexit(sayAbnormalExit); ++ } #ifdef SQLITE_DEBUG mem_main_enter = sqlite3_memory_used(); #endif @@@ -16581,263 -12035,536 +16622,273 @@@ exit(1); } #endif - main_init(&data); + main_init(&datai,&datax); +#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. + /* From here on, within the true clause of this next test, various + ** heap allocations are made which may fail, resulting in an abrupt + ** shell exit. Such an exit happens in 1 of 2 ways: A held resource + ** stack and the call stack are ripped back to this point; or just + ** the held resource stack is ripped back and a process exit occurs. */ - register_exit_ripper(&exit_jb, entry_mark); ++ register_exit_ripper(&exit_jb, main_resource_mark); + if( 0==RIP_TO_HERE(exit_jb) ){ + + /* 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]; + assert( argc>=1 && argv && argv[0] ); + Argv0 = argv[0]; +#if SHELL_DYNAMIC_EXTENSION + initStartupDir(); + if( isExtendedBasename(Argv0) ){ + datai.bExtendedDotCmds = SHELL_ALL_EXTENSIONS; + } +#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; - } + { + /* 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 - /* Do an initial pass through the command-line argument to locate - ** the name of the database file, the name of the initialization file, - ** the size of the alternative malloc heap, - ** and the first command to execute. - */ + /* Do an initial pass through the command-line argument to locate + ** the name of the database file, the name of the initialization file, + ** the size of the alternative malloc heap, + ** and the first command to execute. + */ #ifndef SQLITE_SHELL_FIDDLE - verify_uninitialized(); -#endif - for(i=1; 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; - } - 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_multiple_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; -#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( argsData.zVfs ){ + sqlite3_vfs *pVfs = sqlite3_vfs_find(argsData.zVfs); + if( pVfs ){ + sqlite3_vfs_register(pVfs, 1); + }else{ + utf8_printf(STD_ERR, "no such VFS: \"%s\"\n", argsData.zVfs); + rc = 1; + goto shell_bail; + } + } + + 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; + } + datai.out = STD_OUT; #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, 2, &datai, &cmdArgs, &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+10 ){ - 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. */ + if( console_utf8 && stdin_is_interactive ){ + console_prepare(); }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; + setBinaryMode(stdin, 0); + console_utf8 = 0; } - 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; i2)? 2 : drc; + if( rc>0 ){ + goto shell_bail; } } - if( zHistory ){ shell_read_history(zHistory); } + }else{ + /* Run commands received from standard input + */ + if( stdin_is_interactive ){ + char *zHome; + char *zHistory = 0; + if( argsData.bQuiet ){ + /* bQuiet is almost like normal interactive, but quieter + ** and avoids history keeping and line editor completions. */ + 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"); + } + zHistory = getenv("SQLITE_HISTORY"); + if( zHistory ){ + zHistory = strdup(zHistory); + }else if( (zHome = find_home_dir(0))!=0 ){ + int 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; + rl_attempted_completion_function = readline_completion; #elif HAVE_LINENOISE - linenoiseSetCompletionCallback(linenoise_completion); + linenoiseSetCompletionCallback(linenoise_completion); #endif - data.in = 0; - rc = process_input(&data); - if( zHistory ){ - shell_stifle_history(2000); - shell_write_history(zHistory); - free(zHistory); + } + datai.pInSource = &termInSource; /* read from stdin interactively */ + drc = process_input(&datai); + rc = (drc>2)? 2 : drc; + if( !bQuiet ){ + if( zHistory ){ + shell_stifle_history(2000); + shell_write_history(zHistory); + free(zHistory); + } + } + }else{ + datai.pInSource = &stdInSource; /* read from stdin without prompts */ + drc = process_input(&datai); + rc = (drc>2)? 2 : drc; } - }else{ - data.in = stdin; - rc = process_input(&data); } + }else{ + /* An abrupt, stack-ripping exit arrives here. */ + } + shell_bail: - holder_free(entry_mark); ++ /* All users of resource managment should have left its stack as ++ ** it was near the beginning of shell execution. Verify this. */ ++ assert(main_resource_mark==holder_mark()); ++ if( cmdArgs.azCmd!=0 ){ ++ free(cmdArgs.azCmd); ++ cmdArgs.azCmd = 0; + } #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); + ** client code can "push" SQL into it after this call returns. + ** For that build, just bypass freeing all acquired resources. + */ + set_table_name(&datax, 0); + if( datax.dbUser ){ + session_close_all(&datai, -1); +# if SHELL_DYNAMIC_EXTENSION + notify_subscribers(&datai, NK_DbAboutToClose, datax.dbUser); +# endif + close_db(datax.dbUser); } - # ifdef SQLITE_DEBUG - /* Do this redundantly with atexit() to aid memory leak reporting. */ - for(i=0; i