From: larrybr Date: Fri, 12 May 2023 21:21:37 +0000 (+0000) Subject: CLI resource management applied to about 30% of shell source. A few minor flaws fixed... X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=077ac435511cb7775c5d2bd30ca00fd5f0bd298d;p=thirdparty%2Fsqlite.git CLI resource management applied to about 30% of shell source. A few minor flaws fixed. Comments begin to describe OOM responses. FossilOrigin-Name: ce25a07950e10e5f0c33f179f9b7d307a73b23ad859b5a97e5c6d7bc9e68b254 --- 077ac435511cb7775c5d2bd30ca00fd5f0bd298d diff --cc manifest index abc3589786,b708b2f95c,be8887dde6..9b21651f10 --- a/manifest +++ b/manifest @@@@ -1,5 -1,5 -1,5 +1,5 @@@@ - C More\sCLI\sresource\smanager\suse.\sCatch\smore\sOOMs.\sEliminate\ssome\sopportunities\sto\slose\sresources\sunder\sOOM\sconditions.\sConform\sto\scoding\sguidelines. - D 2023-05-11T21:47:01.654 - C Make\sthe\sregexp\sextension\smore\srobust\sagainst\sOOM\serrors\sduring\scompilation\nof\sthe\srecognizer\sengine.\n[forum:/forumpost/f50aecd5e8|Forum\spost\sf50aecd5e8]. - D 2023-05-12T15:45:34.949 -C Ensure\sthe\s_O_U16TEXT\sis\sdefined\sfor\sWindows\sbuilds\sof\sthe\sCLI.\n[forum:/forumpost/c80aa942dc6bf2|Forum\spost\sc80aa942dc6bf2]. -D 2023-05-12T13:20:57.273 +++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 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@@@ -38,10 -38,8 -38,8 +38,10 @@@@ F configure.ac 510be9293c7efca69c0cc7f4 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd F doc/json-enhancements.md e356fc834781f1f1aa22ee300027a270b2c960122468499bf347bb123ce1ea4f - F doc/lemon.html efc0cd2345d66905505d98f862e1c571512def0ceb5b016cb658fd4918eb76a3 + F doc/lemon.html d2862dbef72496e87f7996f37e814b146848190a742c12161d13fd15346051b0 F doc/pager-invariants.txt 27fed9a70ddad2088750c4a2b493b63853da2710 ++F doc/shell_extend.html c2ce1584bd976613c984a0b7ea4680ef2d2c0ab0642540d730e0a899648e2294 ++F doc/tcl_extension_intro.md 216e29662703881e25c857875d6b2b3cba0dfd93c1f718ec27a3bb79b0049066 F doc/trusted-schema.md 33625008620e879c7bcfbbfa079587612c434fa094d338b08242288d358c3e8a F doc/vdbesort-memory.md 4da2639c14cd24a31e0af694b1a8dd37eaf277aff3867e9a8cc14046bc49df56 F doc/vfs-shm.txt e101f27ea02a8387ce46a05be2b1a902a021d37a @@@@ -637,13 -633,10 -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 eecf8c4f7584d994e64d24a359b404710b198b34b6a00392f1c8b55f7cffa23f - F src/resmanage.h b5bf691153bb083386c526a3f834905cf9d9e0dc182d1c48e5c5e06527b40ab1 +++F src/resmanage.c e013c6a7703cfa11db74fd6269e9cecd5eb5ae371c3702bff0ddd1205e5a555a +++F src/resmanage.h 4f4c0359cee4b3cdfff404851c5a877dfdd075ffdc8206c289fc05ea39a9c685 F src/resolve.c 3e53e02ce87c9582bd7e7d22f13f4094a271678d9dc72820fa257a2abb5e4032 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/select.c 12aa3168be4ff175702fe0ebeaf544312be22d275d378a28e7b2fad32d552d36 - F src/shell.c.in 33598a4967aefba67ce3309fba6a612846e1ef5c3237b24d7a5ddc47c1888e48 - F src/shell.c.in 1e18312f58d365042036fc9d19dcef416074f783702b168f07814332c2268ee0 -F src/shell.c.in f2878df634b9d13ef7944f0d2d516d0b1abaf69302c26b4a8f1878b8be9f8411 +++F src/shell.c.in 924a3467c33c4cc6211c1f35d534f57f88d0e79005370982aeb2428d0951d6f0 ++F src/shext_linkage.h 27dcf7624df05b2a7a6d367834339a6db3636f3035157f641f7db2ec499f8f6d F src/sqlite.h.in 27ca1d4b2eda8feee468af5735182390e8fe4696522751eec0136d17323201ad F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4 @@@@ -2079,8 -2068,8 -2068,8 +2079,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 0f55868e2c51775eaa717564f220acf6ddb2094d358d2011e6736f033e77d8dc 607cfb5bc5c0fb8b789944c2326cfdebf0629e45fbf0a61dd5f667ed685a1bbe - R 79d40353dac451fd08d6986e8e615333 - P ab3797e844c97fed344b36f30cfb788aca9e0d68c574fb833712219eb712db44 - R 273fa6b9f32e4223b5b45d8fc6394dc6 -P 48139fb904de4b7b383e7f8c29bed83ad878eb22b1dd773c366a0aee74613d23 -R 48448dc19b809c692b0d7c49b41acf2c --U drh - Z f78e0028467b93abd16e9ed3e77ee0f5 -Z 99702f4eff9217b484a47a30f0a1c45c +++P efdcf1093b4a327da36b5854cff32a8244244302a5f979859c1398e8d191fc6b 0772ddf56713d013cd1bd44f9c75977ca14f852e3a8f038b0a6b9814f6519d79 504effa89b48c0dc6cd12b3eaf6017be3f32017c601af17759a3cc185346d868 +++R 54bc1318f5a495f2da0644115fe872c7 ++U larrybr - Z 44139e30849c6cbe0b94cb231c0378c9 +++Z d6d73699c601b55c7b4dec78c2a1df9f # Remove this line to create a well-formed Fossil manifest. diff --cc manifest.uuid index b6d0f98ef1,0bfa56a968,6425bb986f..6f5fcbf80b --- a/manifest.uuid +++ b/manifest.uuid @@@@ -1,1 -1,1 -1,1 +1,1 @@@@ - efdcf1093b4a327da36b5854cff32a8244244302a5f979859c1398e8d191fc6b - 0772ddf56713d013cd1bd44f9c75977ca14f852e3a8f038b0a6b9814f6519d79 -504effa89b48c0dc6cd12b3eaf6017be3f32017c601af17759a3cc185346d868 +++ce25a07950e10e5f0c33f179f9b7d307a73b23ad859b5a97e5c6d7bc9e68b254 diff --cc src/resmanage.c index 92d1b43fd8,0000000000,0000000000..b4ae2aecf0 mode 100644,000000,000000..100644 --- a/src/resmanage.c +++ b/src/resmanage.c @@@@ -1,294 -1,0 -1,0 +1,302 @@@@ ++/* ++** 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_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_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 ++ } 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){ ++ if( zMoan ){ ++ fprintf(stderr, "Quitting due to %s, freeing %d resources.\n", ++ zMoan, numResHold); ++ } ++ 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; ++ 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 ++ default: ++ { ++ int ck = pRH->frk - FRK_CustomBase; ++ assert(ck>=0); ++ if( ck < numCustom ){ ++ aCustomFreers[ck]( pRH->held.p_any ); ++ } ++ } ++ } ++ pRH->held.p_any = 0; ++} ++ ++/* 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; ++} ++ ++/* 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){ - if( z!=0 ) res_hold(z, FRK_Malloc); +++ res_hold(z, FRK_Malloc); ++ return z; ++} ++/* Hold a C string in the SQLite heap */ ++char* sstr_holder(char *z){ - if( z!=0 ) res_hold(z, FRK_DbMem); +++ res_hold(z, FRK_DbMem); ++ return z; ++} ++/* Hold an open C runtime FILE */ ++void file_holder(FILE *pf){ - if( pf!=0 ) res_hold(pf, FRK_File); +++ res_hold(pf, FRK_File); ++} ++#if (!defined(_WIN32) && !defined(WIN32)) || !SQLITE_OS_WINRT ++/* Hold an open C runtime pipe */ ++void pipe_holder(FILE *pp){ - if( pp!=0 ) res_hold(pp, FRK_Pipe); +++ 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){ ++ 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 ){ - numCustomAlloc = ncf; +++ 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); ++} ++ - /* Free all held resources in excess of given resource stack mark. */ - void holder_free(ResourceMark mark){ +++/* 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 ){ - free_rk(&pResHold[--numResHold]); +++ --numResHold; +++ if( pResHold[numResHold].held.p_any!=0 ){ +++ free_rk(&pResHold[numResHold]); +++ ++rv; +++ } ++ } ++ 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 5ee75178e9,0000000000,0000000000..bb276de611 mode 100644,000000,000000..100644 --- a/src/resmanage.h +++ b/src/resmanage.h @@@@ -1,131 -1,0 -1,0 +1,132 @@@@ ++/* ++** 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 */ ++typedef unsigned short ResourceMark; ++typedef unsigned short ResourceCount; ++ ++/* 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 ++** with enough information for them to be freed by this package. ++*/ ++/* 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 ++#ifdef SHELL_MANAGE_TEXT ++/* a reference to a ShellText object, (storage for which not managed) */ ++static void text_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. */ - extern void holder_free(ResourceMark mark); +++/* 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 9cca5e31d4,ccaed896d9,b29e402913..72496ae665 --- a/src/shell.c.in +++ b/src/shell.c.in @@@@ -462,6 -431,6 -431,6 +462,12 @@@@ static void endTimer(void) */ #define UNUSED_PARAMETER(x) (void)(x) +++#ifdef SQLITE_DEBUG +++# define CHECK_RETURN_EQUAL(val, func) assert((val)==(func)) +++#else +++# define CHECK_RETURN_EQUAL(val, func) func +++#endif +++ /* ** Number of elements in an array */ @@@@ -506,10 -475,9 -475,9 +512,13 @@@@ static int stdout_is_console = 1 ** 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; @@@@ -2033,16 -1619,16 -1619,16 +2046,18 @@@@ static void shellLog(void *pArg, int iE ** SQL function: shell_putsnl(X) ** ** Write the text X to the screen (or whatever output is being directed) ---** adding a newline at the end, and then return X. +++** adding a newline at the end, and then return X. If X is the NULL +++** equivalent (due to OOM or otherwise), it acts as an empty string. */ static void shellPutsFunc( sqlite3_context *pCtx, int nVal, sqlite3_value **apVal ){ -- ShellState *p = (ShellState*)sqlite3_user_data(pCtx); ++ ShellExState *psx = (ShellExState*)sqlite3_user_data(pCtx); +++ const unsigned char *s = sqlite3_value_text(apVal[0]); (void)nVal; - utf8_printf(ISS(psx)->out, "%s\n", sqlite3_value_text(apVal[0])); -- utf8_printf(p->out, "%s\n", sqlite3_value_text(apVal[0])); +++ utf8_printf(ISS(psx)->out, "%s\n", (s!=0)?(char*)s:""); sqlite3_result_value(pCtx, apVal[0]); } @@@@ -2068,32 -1645,12 -1645,12 +2083,32 @@@@ static int failIfSafeMode va_list ap; char *zMsg; va_start(ap, zErrMsg); --- zMsg = sqlite3_vmprintf(zErrMsg, ap); +++ shell_check_oom(zMsg = sqlite3_vmprintf(zErrMsg, ap)); va_end(ap); -- raw_printf(stderr, "line %d: ", p->lineno); -- utf8_printf(stderr, "%s\n", zMsg); -- exit(1); ++ raw_printf(STD_ERR, "line %d: ", ISS(psx)->pInSource->lineno); ++ utf8_printf(STD_ERR, "%s\n", zMsg); ++ sqlite3_free(zMsg); ++ psx->shellAbruptExit = 0x202; ++ return 1; } ++ return 0; ++} ++ ++/* ++** Emit formatted output to shell's current output, possibly translated ++** for the legacy console on the Windows platform. This is exposed as ++** a helper for extensions so that they may share a common buffering ++** for FILE* output or share output capture when/if that is implemented. ++*/ ++static void utf8_out_printf(ShellExState *p, const char *zFormat, ...){ ++ va_list ap; ++ va_start(ap, zFormat); ++#if defined(_WIN32) || defined(WIN32) ++ vf_utf8_printf(ISS(p)->out, zFormat, ap); ++#else ++ vfprintf(ISS(p)->out, zFormat, ap); ++#endif ++ va_end(ap); } /* @@@@ -2132,8 -1689,8 -1689,8 +2147,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() +++ ** 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); @@@@ -2144,7 -1701,7 -1701,7 +2164,6 @@@@ return; } db = sqlite3_context_db_handle(context); --- zTempFile = 0; sqlite3_file_control(db, 0, SQLITE_FCNTL_TEMPFILENAME, &zTempFile); if( zTempFile==0 ){ sqlite3_uint64 r = 0; @@@@ -2214,9 -1771,12 -1771,11 +2233,10 @@@@ sqlite3_result_blob64(context, p, sz, sqlite3_free); }else{ sqlite3_int64 i, j; -- if( hasCRNL ){ -- /* If the original contains \r\n then do no conversions back to \n */ -- }else{ ++ if( !hasCRNL ){ /* If the file did not originally contain \r\n then convert any new ** \r\n back into \n */ + + p[sz] = 0; for(i=j=0; imember)) ++#define MEMBER_SIZEOF(stype, member) (sizeof(((stype*)0)->member)) ++static struct { ++ size_t offset; ++ size_t size; ++} outputModeCopy[] = { ++#define SS_MEMBER_COPY(mn) \ ++ { MEMBER_OFFSET(ShellInState,mn), MEMBER_SIZEOF(ShellInState,mn) } ++ SS_MEMBER_COPY(showHeader), SS_MEMBER_COPY(shellFlgs), ++ SS_MEMBER_COPY(mode), SS_MEMBER_COPY(cmOpts), ++ SS_MEMBER_COPY(colSeparator), SS_MEMBER_COPY(rowSeparator) ++#undef SS_MEMBER_COPY ++}; ++ - /* Allocate a buffer, copy requested output mode data to it, and return it. */ +++/* Allocate a buffer, copy requested output mode data to it, and return it. +++ * This never fails under OOM conditions. Instead, it returns 0. +++ */ ++static OutputModeSave *outputModeSave(ShellInState *psi, SaveWhatMode w){ ++ u16 what = (u16)w; ++ int i, nAlloc = sizeof(what)+1, mask = 1; ++ char *pSaved = 0, *pFill; ++ for( i=0; isizeof(what))? *((u16 *)pSaved) : 0; ++ int i, nAlloc = sizeof(what)+1, mask = 1; ++ char *pTake = (char *)pSaved + sizeof(what); ++ for( i=0; imodePrior = p->mode; -- p->priorShFlgs = p->shellFlgs; -- memcpy(p->colSepPrior, p->colSeparator, sizeof(p->colSeparator)); -- memcpy(p->rowSepPrior, p->rowSeparator, sizeof(p->rowSeparator)); ++static void outputModePushSome(ShellInState *psi, SaveWhatMode w){ ++ OutputModeSave *pOMS; ++ assert(psi->nSavedModesnSavedModes>=MODE_STACK_MAX ) return; ++ pOMS = outputModeSave(psi, w); ++ shell_check_oom(pOMS); ++ psi->pModeStack[psi->nSavedModes++] = pOMS; +} - static void outputModePop(ShellState *p){ - p->mode = p->modePrior; - p->shellFlgs = p->priorShFlgs; - memcpy(p->colSeparator, p->colSepPrior, sizeof(p->colSeparator)); - memcpy(p->rowSeparator, p->rowSepPrior, sizeof(p->rowSeparator)); ++static void outputModePush(ShellInState *psi){ ++ outputModePushSome(psi, SWM_everything); + } -static void outputModePop(ShellState *p){ - p->mode = p->modePrior; - p->shellFlgs = p->priorShFlgs; - memcpy(p->colSeparator, p->colSepPrior, sizeof(p->colSeparator)); - memcpy(p->rowSeparator, p->rowSepPrior, sizeof(p->rowSeparator)); ++static void outputModePop(ShellInState *p){ ++ OutputModeSave *pOMS; ++ assert(p->nSavedModes>0); /* Should not be here unless something pushed. */ ++ if( p->nSavedModes==0 ) return; ++ pOMS = p->pModeStack[--p->nSavedModes]; ++ assert(pOMS!=0); ++ p->pModeStack[p->nSavedModes] = 0; ++ outputModeRestore(p, pOMS); } /* @@@@ -2359,7 -1843,7 -1842,7 +2382,9 @@@@ static void output_hex_blob(FILE *out, ** ** Try to use zA and zB first. If both of those are already found in z[] ** then make up some string and store it in the buffer zBuf. +++** The length of this buffer must be no less than SHELL_QESC_GENLEN . */ +++#define SHELL_QESC_GENLEN 20 static const char *unused_string( const char *z, /* Result must not appear anywhere in z */ const char *zA, const char *zB, /* Try these first */ @@@@ -2369,7 -1853,7 -1852,7 +2394,7 @@@@ if( strstr(z, zA)==0 ) return zA; if( strstr(z, zB)==0 ) return zB; do{ --- sqlite3_snprintf(20,zBuf,"(%s%u)", zA, i++); +++ sqlite3_snprintf(SHELL_QESC_GENLEN, zBuf,"(%s%u)", zA, i++); }while( strstr(z,zBuf)!=0 ); return zBuf; } @@@@ -2431,7 -1915,7 -1914,7 +2456,7 @@@@ static void output_quoted_escaped_strin const char *zCR = 0; int nNL = 0; int nCR = 0; --- char zBuf1[20], zBuf2[20]; +++ char zBuf1[SHELL_QESC_GENLEN], zBuf2[SHELL_QESC_GENLEN]; for(i=0; z[i]; i++){ if( z[i]=='\n' ) nNL++; if( z[i]=='\r' ) nCR++; @@@@ -2606,12 -2090,11 -2089,11 +2631,13 @@@@ static const char needCsvQuote[] = ** the separator, which may or may not be a comma. p->nullValue is ** the null value. Strings are quoted if necessary. The separator ** is only issued if bSep is true. +++** This routine may abort under OOM conditions. */ --static void output_csv(ShellState *p, const char *z, int bSep){ -- FILE *out = p->out; ++static void output_csv(ShellExState *psx, const char *z, int bSep){ ++ FILE *out = ISS(psx)->out; ++ char *zColSep = psx->zFieldSeparator; if( z==0 ){ -- utf8_printf(out,"%s",p->nullValue); ++ utf8_printf(out,"%s",psx->zNullValue); }else{ unsigned i; for(i=0; z[i]; i++){ @@@@ -2640,15 -2122,8 -2121,8 +2666,25 @@@@ */ static void interrupt_handler(int NotUsed){ UNUSED_PARAMETER(NotUsed); -- if( ++seenInterrupt>1 ) exit(1); -- if( globalDb ) sqlite3_interrupt(globalDb); ++ if( ++seenInterrupt>1 ){ - sqlite3_mutex_free(pGlobalDbLock); ++ exit(1); ++ } ++ if( globalDb ){ - sqlite3_mutex_enter(pGlobalDbLock); - sqlite3_interrupt(globalDb); - sqlite3_mutex_leave(pGlobalDbLock); +++ int itry = 0; +++ while( itry < 10 ){ +++ if( sqlite3_mutex_try(pGlobalDbLock)==SQLITE_OK ){ +++ sqlite3_interrupt(globalDb); +++ sqlite3_mutex_leave(pGlobalDbLock); +++ return; +++ }else{ +++ sqlite3_sleep(50); +++ ++itry; +++ } +++ } +++ /* Apparently, no polite interruption is working. (Something is +++ ** very busy under mutex protection.) So quit altother. */ +++ exit(1); ++ } } #if (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE) @@@@ -2780,11 -2253,11 -2252,11 +2816,11 @@@@ static void printSchemaLine(FILE *out, if( zTail[0]==';' && (strstr(z, "/*")!=0 || strstr(z,"--")!=0) ){ const char *zOrig = z; static const char *azTerm[] = { "", "*/", "\n" }; --- int i; +++ int i, rc; for(i=0; idbShell!=0 ) return SQLITE_OK; ++ else{ ++ int rc = sqlite3_open(SHELL_DB_STORE, &psx->dbShell); ++ if( rc!=SQLITE_OK ){ ++ utf8_printf(STD_ERR, "Shell DB open failure: %s\n", sqlite3_errstr(rc)); ++ return SQLITE_ERROR; ++ } ++#ifndef SQLITE_NOHAVE_SYSTEM ++ sqlite3_create_function(psx->dbShell, "edit", 1, ++ SQLITE_UTF8, 0, editFunc, 0, 0); ++ sqlite3_create_function(psx->dbShell, "edit", 2, ++ SQLITE_UTF8, 0, editFunc, 0, 0); ++#endif ++ return rc; ++ } ++} ++ ++/* Tell whether the above-created table exists, return true iff exists. */ ++static int dispatch_table_exists(sqlite3 *dbs){ ++ return sqlite3_table_column_metadata ++ (dbs, SHELL_DISP_SCHEMA, SHELL_DISP_TAB, 0, 0, 0, 0, 0, 0)==SQLITE_OK; ++} ++ ++static int ensure_dispatch_table(ShellExState *psx){ ++ int rc = ensure_shell_db(psx); ++ if( rc==SQLITE_OK ){ ++ char *zErr = 0; ++ int rc1, rc2; ++ if( dispatch_table_exists(psx->dbShell) ) return rc; ++ /* Create the dispatch table and view on it. */ ++#ifdef SHELL_DB_FILE ++ sqlite3_exec(psx->dbShell, "DROP TABLE IF EXISTS "SHELL_DISP_TAB, 0,0,0); ++ sqlite3_exec(psx->dbShell, "DROP VIEW IF EXISTS "SHELL_DISP_VIEW, 0,0,0); ++ sqlite3_exec(psx->dbShell, "DROP TABLE IF EXISTS "SHELL_AHELP_TAB, 0,0,0); ++ sqlite3_exec(psx->dbShell, "DROP VIEW IF EXISTS "SHELL_HELP_VIEW, 0,0,0); ++#endif ++ rc1 = sqlite3_exec(psx->dbShell, "CREATE TABLE "SHELL_AHELP_TAB"(" ++ "name TEXT, extIx INT, helpText TEXT," ++ "PRIMARY KEY(name,extIx)) WITHOUT ROWID", 0, 0, &zErr); ++ rc1 = sqlite3_exec(psx->dbShell, "CREATE TABLE "SHELL_DISP_TAB"(" ++ "name TEXT, extIx INT, cmdIx INT," ++ "PRIMARY KEY(extIx,cmdIx)) WITHOUT ROWID", 0, 0, &zErr); ++ rc2 = sqlite3_exec(psx->dbShell, ++ /* name, extIx, cmdIx */ ++ "CREATE VIEW "SHELL_DISP_VIEW" AS" ++ " SELECT s.name AS name," ++ " max(s.extIx) AS extIx, s.cmdIx AS cmdIx" ++ " FROM "SHELL_DISP_TAB" s GROUP BY name" ++ " ORDER BY name", ++ 0, 0, &zErr); ++ rc2 = sqlite3_exec(psx->dbShell, ++ /* name, extIx, cmdIx, help */ ++ "CREATE VIEW "SHELL_HELP_VIEW" AS" ++ " SELECT s.name AS name, max(s.extIx) AS extIx," ++ " s.cmdIx AS cmdIx, NULL as help" ++ " FROM "SHELL_DISP_TAB" s GROUP BY name" ++ " UNION" ++ " SELECT s.name AS name, max(s.extIx) AS extIx," ++ " -1 AS cmdIx, s.helpText AS help" ++ " FROM "SHELL_AHELP_TAB" s GROUP BY name" ++ " ORDER BY name", ++ 0, 0, &zErr); ++ if( rc1!=SQLITE_OK || rc2!=SQLITE_OK || zErr!=0 ){ ++ utf8_printf(STD_ERR, "Shell DB init failure, %s\n", zErr? zErr : "?"); ++ rc = SQLITE_ERROR; ++ }else rc = SQLITE_OK; ++ sqlite3_free(zErr); ++ } ++ return rc; ++} ++ ++/* ++** Skip over whitespace, returning remainder. ++*/ ++static const char *skipWhite( const char *z ){ ++ while( IsSpace(*z) ) ++z; ++ return z; ++} ++ +/* +** Print N dashes +*/ static void print_dashes(FILE *out, int N){ const char zDash[] = "--------------------------------------------------"; const int nDash = sizeof(zDash) - 1; @@@@ -3656,6 -3025,6 -3024,6 +3693,7 @@@@ static int run_table_dump_query /* ** Allocate space and save off string indicating current error. +++** The return must be passed to sqlite3_free() sometime. */ static char *save_err_msg( sqlite3 *db, /* Database to query */ @@@@ -4145,151 -3516,22 -3515,22 +4183,151 @@@@ static void restore_debug_trace_modes(v sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &savedWhereTrace); } ++/* DB schema protection for use by next two utility functions */ ++typedef struct DbProtectState { ++ int wrSchema; ++ int defensiveMode; ++} DbProtectState; ++ ++/* Allow system (sqlite_*) schema changes, return prior protection state. */ ++static DbProtectState allow_sys_schema_change(sqlite3 *db){ ++ DbProtectState rv; ++ sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, -1, &rv.defensiveMode); ++ sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0); ++ sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, -1, &rv.wrSchema); ++ sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, 0); ++ return rv; ++} ++ ++/* Restore protection state using allow_sys_schema_change() return. */ ++static void restore_sys_schema_protection( sqlite3 *db, DbProtectState *pPS ){ ++ sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, pPS->wrSchema, 0); ++ sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, pPS->defensiveMode, 0); ++} ++ ++/* Partition the temp.sqlite_parameters and main.sqlite_variables ++ * key namespace according to use. ++ */ ++typedef enum { ++ PTU_Binding = 0, /* Binding parameter, value to be bound */ ++#define SPTU_Binding SHELL_STRINGIFY(0) ++ PTU_Script = 1, /* A value containing shell SQL and dot-commands */ ++#define SPTU_Script SHELL_STRINGIFY(1) ++ PTU_Entry = 2, /* Value as entered for non-text binding parameters */ ++#define SPTU_Entry SHELL_STRINGIFY(2) ++ PTU_Nil = 3 /* Unspecified */ ++} ParamTableUse; ++ ++static ParamTableUse classify_param_name( const char *zName ){ ++ char c = *zName; ++ switch( c ){ ++ case '$': case ':': case '@': case '?': return PTU_Binding; ++ default: return isalpha(c)? PTU_Script : PTU_Nil; ++ } ++} ++ ++#ifndef SQLITE_NOHAVE_SYSTEM ++/* Possibly using a -editor=X argument and env-var VISUAL, attempt ++ * to get the zEditor shell state member set iff not already set. ++ * If there is no such argument, the env-var is retrieved if set. ++ * If the argument is -editor=X or --editor=X, use that and leave ++ * the zEditor member set accordingly. Returns are: ++ * 0 => editor set, zEd was not the -editor option ++ * 1 => editor set, zEd consumed as -editor option ++ * -1 => editor not set, and error/advice message issued. ++ * ++ * This implements an undocumented fall-back for the .vars and ++ * .parameters edit subcommands, so that users need not restart ++ * a shell session to get an editor specified upon need for it. */ ++int attempt_editor_set(ShellInState *psi, char *zDot, const char *zEd){ ++ if( psi->zEditor==0 ){ ++ const char *zE = getenv("VISUAL"); ++ if( zE!=0 ) psi->zEditor = smprintf("%s", zE); ++ } ++ if( zEd && zEd[0]=='-' ){ ++ zEd += 1 + (zEd[1]=='-'); ++ if( cli_strncmp(zEd,"editor=",7)==0 ){ ++ sqlite3_free(psi->zEditor); ++ /* Accept an initial -editor=? option. */ ++ psi->zEditor = smprintf("%s", zEd+7); ++ return 1; ++ } ++ } ++ if( psi->zEditor==0 ){ ++ utf8_printf(STD_ERR, ++ "Either set env-var VISUAL to name an" ++ " editor and restart, or rerun\n " ++ ".%s edit with an initial edit option," ++ " --editor=EDITOR_COMMAND .\n", zDot); ++ return -1; ++ } ++ return 0; ++} ++#endif ++ ++/* The table kept for user DBs if .parameter command is used usefully. */ ++#define PARAM_TABLE_NAME "sqlite_parameters" ++#define PARAM_TABLE_SCHEMA "temp" ++#define PARAM_TABLE_SNAME PARAM_TABLE_SCHEMA"."PARAM_TABLE_NAME ++ ++/* The table kept for the shell DB if .vars command is used usefully. */ ++#define SHVAR_TABLE_NAME "sqlite_variables" ++#define SHVAR_TABLE_SCHEMA "main" ++#define SHVAR_TABLE_SNAME SHVAR_TABLE_SCHEMA"."SHVAR_TABLE_NAME ++ ++#ifndef SH_KV_STORE_NAME ++/* Name for table keeping user's saved parameters */ ++# define SH_KV_STORE_NAME "SQLiteShell_KeyValuePairs" ++#endif ++#ifndef SH_KV_STORE_SCHEMA ++/* Schema name used to attach saved parameters DB during load/save */ ++# define SH_KV_STORE_SCHEMA "SQLiteShell" ++#endif ++#define SH_KV_STORE_SNAME SH_KV_STORE_SCHEMA"."SH_KV_STORE_NAME ++ /* Create the TEMP table used to store parameter bindings */ --static void bind_table_init(ShellState *p){ -- int wrSchema = 0; -- int defensiveMode = 0; -- sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, -1, &defensiveMode); -- sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0); -- sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, -1, &wrSchema); -- sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, 0); -- sqlite3_exec(p->db, -- "CREATE TABLE IF NOT EXISTS temp.sqlite_parameters(\n" ++static void param_table_init(sqlite3 *db){ ++ DbProtectState dps = allow_sys_schema_change(db); ++ sqlite3_exec(db, ++ "CREATE TABLE IF NOT EXISTS "PARAM_TABLE_SNAME"(\n" + " key TEXT PRIMARY KEY,\n" - " value\n" ++ " value,\n" ++ " uses INT DEFAULT ("SPTU_Binding")" + ") WITHOUT ROWID;", + 0, 0, 0); - sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, wrSchema, 0); - sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, defensiveMode, 0); ++ restore_sys_schema_protection( db, &dps ); ++} ++ ++/* Tell whether the above-created table exists, return true iff exists. */ ++static int param_table_exists( sqlite3 *db ){ ++ return sqlite3_table_column_metadata ++ (db, PARAM_TABLE_SCHEMA, PARAM_TABLE_NAME, 0, 0, 0, 0, 0, 0)==SQLITE_OK; ++} ++ ++/* Create the shell DB table used to store shell variables or scripts */ ++static int shvars_table_init(sqlite3 *db){ ++ DbProtectState dps = allow_sys_schema_change(db); ++ int rc = sqlite3_exec(db, ++ "CREATE TABLE IF NOT EXISTS "SHVAR_TABLE_SNAME"(\n" + " key TEXT PRIMARY KEY,\n" - " value\n" ++ " value,\n" ++ " uses INT DEFAULT ("SPTU_Script")" + ") WITHOUT ROWID;", + 0, 0, 0); - sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, wrSchema, 0); - sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, defensiveMode, 0); ++ restore_sys_schema_protection( db, &dps ); ++ return rc!=SQLITE_OK; ++} ++ ++/* Tell whether the above-created table exists, return true iff exists. */ ++static int shvars_table_exists( sqlite3 *db ){ ++ return sqlite3_table_column_metadata ++ (db, SHVAR_TABLE_SCHEMA, SHVAR_TABLE_NAME, 0, 0, 0, 0, 0, 0)==SQLITE_OK; ++} ++ ++/* Make shell vars table exist. */ ++static int ensure_shvars_table(sqlite3 *dbs){ ++ if( shvars_table_exists(dbs) ) return SQLITE_OK; ++ else return shvars_table_init(dbs); } /* @@@@ -4975,102 -4244,93 -4243,93 +5013,116 @@@@ static int shell_exec const char *zSql, /* SQL to be evaluated */ char **pzErrMsg /* Error msg written here */ ){ ++ ShellInState *psi = ISS(psx); sqlite3_stmt *pStmt = NULL; /* Statement to execute. */ +++ sqlite3_stmt *pExplain = NULL; /* For explain generate */ +++ char *zEQP = NULL; /* For explain report */ int rc = SQLITE_OK; /* Return Code */ int rc2; const char *zLeftover; /* Tail of unprocessed SQL */ -- sqlite3 *db = pArg->db; -- ++ sqlite3 *db = DBX(psx); - +++ ResourceMark mark = holder_mark(); if( pzErrMsg ){ *pzErrMsg = NULL; } #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 */ 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); } }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 */ -- if( pArg && pArg->autoEQP && sqlite3_stmt_isexplain(pStmt)==0 ){ -- sqlite3_stmt *pExplain; -- char *zEQP; ++ if( psx && psi->autoEQP && sqlite3_stmt_isexplain(pStmt)==0 ){ - sqlite3_stmt *pExplain; - char *zEQP; 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); sqlite3_free(zEQP); -- if( pArg->autoEQP>=AUTOEQP_full ){ +++ swap_held(mark, 2, 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 ){ -- pArg->cMode = MODE_Explain; -- explain_data_prepare(pArg, pExplain); -- exec_prepared_stmt(pArg, pExplain); -- explain_data_delete(pArg); +++ swap_held(mark, 1, pExplain); ++ 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); sqlite3_free(zEQP); +++ swap_held(mark, 2, 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); sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); -- if( pArg ) pArg->pStmt = pStmt; +++ swap_held(mark, 0, pStmt); ++ if( psx ) psi->pStmt = pStmt; } restore_debug_trace_modes(); } @@@@ -5112,9 -4372,10 -4371,10 +5164,10 @@@@ ** 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); 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); } @@@@ -5125,6 -4386,6 -4385,6 +5178,7 @@@@ } } } /* end while */ +++ CHECK_RETURN_EQUAL(0, holder_free(mark)); return rc; } @@@@ -5419,958 -4681,1304 -4680,1304 +5473,959 @@@@ static int run_schema_dump_query return rc; } ++/* Configure help text generation to have coalesced secondary help lines ++ * with trailing newlines on all help lines. This allow help text to be ++ * representable as an array of two C-strings per dot-command. ++ */ ++DISPATCH_CONFIG[ ++ HELP_COALESCE=1 ++]; ++#define HELP_TEXT_FMTP ".%s" ++#define HELP_TEXT_FMTS "%s" ++/* Above HELP_COALESCE config and HELP_TEXT_FMT PP vars must track. ++ * Alternative is 0, ".%s\n" and "%s\n" . ++ */ ++ ++/* Forward references */ ++static int showHelp(FILE *out, const char *zPattern, ShellExState *); ++static DotCmdRC process_input(ShellInState *psx); ++static DotCommand *builtInCommand(int ix); ++ /* --** Text of help messages. ++** Read the content of file zName into memory obtained from sqlite3_malloc64() ++** and return a pointer to the buffer. The caller is responsible for freeing ++** the memory. ++** ++** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes ++** read. ** --** 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. ** --** There must be two or more spaces between the end of the command and the --** start of the description of what that command does. ++** NULL is returned if any error is encountered. The final value of *pnByte ++** is undefined in this case. */ --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", ++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 --#ifndef SQLITE_OMIT_AUTHORIZATION -- ".auth ON|OFF Show authorizer callbacks", ++ ++/* ++** 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 ++# define session_close_all(X,Y) #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()", ++ ++/* ++** 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 -- ".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", ++ ++#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 -- ".changes on|off Show number of rows changed by SQL", ++ ++/* ++** Try to deduce the type of file for zName based on its content. Return ++** one of the SHELL_OPEN_* constants. ++** ++** If the file does not exist or is empty but its name looks like a ZIP ++** archive and the dfltZip flag is true, then assume it is a ZIP archive. ++** Otherwise, assume an ordinary database regardless of the filename if ++** the type cannot be determined from content. ++*/ ++u8 deduceDatabaseType(const char *zName, int dfltZip){ ++ FILE *f = fopen(zName, "rb"); ++ size_t n; ++ u8 rc = SHELL_OPEN_UNSPEC; ++ char zBuf[100]; ++ if( f==0 ){ ++ if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){ ++ return SHELL_OPEN_ZIPFILE; ++ }else{ ++ return SHELL_OPEN_NORMAL; ++ } ++ } ++ n = fread(zBuf, 16, 1, f); ++ if( n==1 && memcmp(zBuf, "SQLite format 3", 16)==0 ){ ++ fclose(f); ++ return SHELL_OPEN_NORMAL; ++ } ++ fseek(f, -25, SEEK_END); ++ n = fread(zBuf, 25, 1, f); ++ if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){ ++ rc = SHELL_OPEN_APPENDVFS; ++ }else{ ++ fseek(f, -22, SEEK_END); ++ n = fread(zBuf, 22, 1, f); ++ if( n==1 && zBuf[0]==0x50 && zBuf[1]==0x4b && zBuf[2]==0x05 ++ && zBuf[3]==0x06 ){ ++ rc = SHELL_OPEN_ZIPFILE; ++ }else if( n==0 && dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){ ++ rc = SHELL_OPEN_ZIPFILE; ++ } ++ } ++ fclose(f); ++ return rc; ++} ++ ++#ifndef SQLITE_OMIT_DESERIALIZE ++/* ++** Reconstruct an in-memory database using the output from the "dbtotxt" ++** program. Read content from the file in p->aAuxDb[].zDbFilename. ++** If p->aAuxDb[].zDbFilename is 0, then read from the present input. ++*/ ++static unsigned char *readHexDb(ShellInState *psi, int *pnData){ ++ unsigned char *a = 0; ++ int n = 0; ++ int pgsz = 0; ++ int iOffset = 0; ++ int j, k, nlError; ++ int rc; ++ static const char *zEndMarker = "| end "; ++ const char *zDbFilename = psi->pAuxDb->zDbFilename; ++ /* Need next two objects only if redirecting input to get the hex. */ ++ InSource inRedir = INSOURCE_FILE_REDIR(0, zDbFilename, psi->pInSource); ++ unsigned int x[16]; ++ char zLine[1000]; ++ if( zDbFilename ){ ++ inRedir.inFile = fopen(zDbFilename, "r"); ++ if( inRedir.inFile==0 ){ ++ utf8_printf(STD_ERR, "cannot open \"%s\" for reading\n", zDbFilename); ++ return 0; ++ } ++ psi->pInSource = &inRedir; ++ }else{ ++ /* Will read hex DB lines inline from present input, without redirect. */ ++ if( INSOURCE_IS_INTERACTIVE(psi->pInSource) ){ ++ printf("Reading hex DB from \"%s\", until end-marker input like:\n%s\n", ++ psi->pInSource->zSourceSay, zEndMarker); ++ fflush(STD_OUT); ++ } ++ } ++ *pnData = 0; ++ if( strLineGet(zLine,sizeof(zLine), psi->pInSource)==0 ) goto readHexDb_error; ++ rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz); ++ if( rc!=2 ) goto readHexDb_error; ++ if( n<0 ) goto readHexDb_error; ++ if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ) goto readHexDb_error; ++ n = (n+pgsz-1)&~(pgsz-1); /* Round n up to the next multiple of pgsz */ ++ a = sqlite3_malloc( n ? n : 1 ); ++ shell_check_oom(a); ++ memset(a, 0, n); ++ if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){ ++ utf8_printf(STD_ERR, "invalid pagesize\n"); ++ goto readHexDb_error; ++ } ++ while( strLineGet(zLine,sizeof(zLine), psi->pInSource)!=0 ){ ++ rc = sscanf(zLine, "| page %d offset %d", &j, &k); ++ if( rc==2 ){ ++ iOffset = k; ++ continue; ++ } ++ if( cli_strncmp(zLine, zEndMarker, 6)==0 ){ ++ break; ++ } ++ rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", ++ &j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7], ++ &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]); ++ if( rc==17 ){ ++ k = iOffset+j; ++ if( k+16<=n && k>=0 ){ ++ int ii; ++ for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff; ++ } ++ } ++ } ++ *pnData = n; /* Record success and size. */ ++ readHexDb_cleanup: ++ if( psi->pInSource==&inRedir ){ ++ fclose( inRedir.inFile ); ++ psi->pInSource = inRedir.pFrom; ++ } ++ return a; ++ ++ readHexDb_error: ++ nlError = psi->pInSource->lineno; ++ if( psi->pInSource!=&inRedir ){ ++ /* Since taking input inline, consume through its end marker. */ ++ while( strLineGet(zLine, sizeof(zLine), psi->pInSource)!=0 ){ ++ if(cli_strncmp(zLine, zEndMarker, 6)==0 ) break; ++ } ++ } ++ sqlite3_free(a); ++ a = 0; ++ utf8_printf(STD_ERR,"Error on line %d within --hexdb input\n", nlError); ++ goto readHexDb_cleanup; ++} ++#endif /* SQLITE_OMIT_DESERIALIZE */ ++ ++/* ++** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X. ++*/ ++static void shellUSleepFunc( ++ sqlite3_context *context, ++ int argcUnused, ++ sqlite3_value **argv ++){ ++ int sleep = sqlite3_value_int(argv[0]); ++ (void)argcUnused; ++ sqlite3_sleep(sleep/1000); ++ sqlite3_result_int(context, sleep); ++} ++ ++/* Flags for open_db(). ++** ++** The default behavior of open_db() is to exit(1) if the database fails to ++** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error ++** but still returns without calling exit. ++** ++** The OPEN_DB_ZIPFILE flag causes open_db() to prefer to open files as a ++** ZIP archive if the file does not exist or is empty and its name matches ++** the *.zip pattern. ++*/ ++#define OPEN_DB_KEEPALIVE 0x001 /* Return after error if true */ ++#define OPEN_DB_ZIPFILE 0x002 /* Open as ZIP if name matches *.zip */ ++ ++/* ++** Make sure the database is open. If it is not, then open it. If ++** the database fails to open, print an error message and exit. ++*/ ++static void open_db(ShellExState *psx, int openFlags){ ++ ShellInState *psi = ISS(psx); ++ if( DBX(psx)==0 ){ ++ sqlite3 **pDb = &DBX(psx); ++ const char *zDbFilename = psi->pAuxDb->zDbFilename; ++ if( psi->openMode==SHELL_OPEN_UNSPEC ){ ++ if( zDbFilename==0 || zDbFilename[0]==0 ){ ++ psi->openMode = SHELL_OPEN_NORMAL; ++ }else{ ++ psi->openMode = deduceDatabaseType(zDbFilename, ++ (openFlags & OPEN_DB_ZIPFILE)!=0); ++ } ++ } ++ switch( psi->openMode ){ ++ case SHELL_OPEN_APPENDVFS: { ++ sqlite3_open_v2 ++ (zDbFilename, pDb, ++ SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|psi->openFlags, ++ "apndvfs"); ++ break; ++ } ++ case SHELL_OPEN_HEXDB: ++ case SHELL_OPEN_DESERIALIZE: { ++ sqlite3_open(0, pDb); ++ break; ++ } ++ case SHELL_OPEN_ZIPFILE: { ++ sqlite3_open(":memory:", pDb); ++ break; ++ } ++ case SHELL_OPEN_READONLY: { ++ sqlite3_open_v2(zDbFilename, pDb, ++ SQLITE_OPEN_READONLY|psi->openFlags, 0); ++ break; ++ } ++ case SHELL_OPEN_UNSPEC: ++ case SHELL_OPEN_NORMAL: { ++ sqlite3_open_v2(zDbFilename, pDb, ++ SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|psi->openFlags, 0); ++ break; ++ } ++ } ++ globalDb = DBX(psx); ++ if( DBX(psx)==0 || SQLITE_OK!=sqlite3_errcode(DBX(psx)) ){ ++ const char *zWhy = (DBX(psx)==0)? "(?)" : sqlite3_errmsg(DBX(psx)); ++ utf8_printf(STD_ERR,"Error: unable to open database \"%s\": %s\n", ++ zDbFilename, zWhy); ++ if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){ ++ exit(1); ++ } ++ sqlite3_close(DBX(psx)); ++ 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); ++ }else{ ++ utf8_printf(stderr, ++ "Notice: using substitute in-memory database instead of \"%s\"\n", ++ zDbFilename); ++ } ++ } ++ sqlite3_db_config(globalDb, SQLITE_DBCONFIG_STMT_SCANSTATUS,(int)0,(int*)0); ++ ++ /* Reflect the use or absence of --unsafe-testing invocation. */ ++ { ++ int testmode_on = ShellHasFlag(psx,SHFLG_TestingMode); ++ sqlite3_db_config(globalDb, SQLITE_DBCONFIG_TRUSTED_SCHEMA,testmode_on,0); ++ sqlite3_db_config(globalDb, SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0); ++ } ++#ifndef SQLITE_OMIT_LOAD_EXTENSION ++ sqlite3_enable_load_extension(globalDb, 1); ++#endif ++ sqlite3_shathree_init(globalDb, 0, 0); ++ sqlite3_uint_init(globalDb, 0, 0); ++ sqlite3_decimal_init(globalDb, 0, 0); ++ sqlite3_base64_init(globalDb, 0, 0); ++ sqlite3_base85_init(globalDb, 0, 0); ++ sqlite3_regexp_init(globalDb, 0, 0); ++ sqlite3_ieee_init(globalDb, 0, 0); ++ sqlite3_series_init(globalDb, 0, 0); #ifndef SQLITE_SHELL_FIDDLE -- ".check GLOB Fail if output since .testcase does not match", -- ".clone NEWDB Clone data into NEWDB from the existing database", ++ sqlite3_fileio_init(globalDb, 0, 0); ++ sqlite3_completion_init(globalDb, 0, 0); #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\"", ++ sqlite3_dbdata_init(globalDb, 0, 0); #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", - ".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\"", ++#ifdef SQLITE_HAVE_ZLIB ++ if( !psi->bSafeModeFuture ){ ++ sqlite3_zipfile_init(globalDb, 0, 0); ++ sqlite3_sqlar_init(globalDb, 0, 0); ++ } #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", - " trigger Like \"full\" but also show trigger bytecode", --#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.", - ".excel Display the output of next command in spreadsheet", - " --bom Put a UTF8 byte-order mark on intermediate file", ++ ++#ifdef SQLITE_SHELL_EXTFUNCS ++ /* Create a preprocessing mechanism for extensions to make ++ * their own provisions for being built into the shell. ++ * This is a short-span macro. See further below for usage. ++ */ ++#define SHELL_SUB_MACRO(base, variant) base ## _ ## variant ++#define SHELL_SUBMACRO(base, variant) SHELL_SUB_MACRO(base, variant) ++ /* Let custom-included extensions get their ..._init() called. ++ * The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause ++ * the extension's sqlite3_*_init( db, pzErrorMsg, pApi ) ++ * inititialization routine to be called. ++ */ ++ { ++ int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db); ++ /* Let custom-included extensions expose their functionality. ++ * The WHATEVER_EXPOSE( db, pzErrorMsg ) macro should cause ++ * the SQL functions, virtual tables, collating sequences or ++ * VFS's implemented by the extension to be registered. ++ */ ++ if( irc==SQLITE_OK ++ || irc==SQLITE_OK_LOAD_PERMANENTLY ){ ++ SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, EXPOSE)(p->db, 0); ++ } ++#undef SHELL_SUB_MACRO ++#undef SHELL_SUBMACRO ++ } #endif -#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", --#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", ++ ++ sqlite3_create_function(globalDb, "shell_add_schema", 3, SQLITE_UTF8, 0, ++ shellAddSchemaName, 0, 0); ++ sqlite3_create_function(globalDb, "shell_module_schema", 1, SQLITE_UTF8, 0, ++ shellModuleSchema, 0, 0); ++ sqlite3_create_function(globalDb, "shell_putsnl", 1, SQLITE_UTF8, psx, ++ shellPutsFunc, 0, 0); ++ sqlite3_create_function(globalDb, "usleep",1,SQLITE_UTF8, 0, ++ shellUSleepFunc, 0, 0); ++#ifndef SQLITE_NOHAVE_SYSTEM ++ sqlite3_create_function(globalDb, "edit", 1, SQLITE_UTF8, 0, ++ editFunc, 0, 0); ++ sqlite3_create_function(globalDb, "edit", 2, SQLITE_UTF8, 0, ++ editFunc, 0, 0); #endif ++ if( psi->openMode==SHELL_OPEN_ZIPFILE ){ ++ char *zSql = smprintf("CREATE VIRTUAL TABLE zip USING zipfile(%Q);", ++ zDbFilename); ++ shell_check_oom(zSql); ++ sqlite3_exec(DBX(psx), zSql, 0, 0, 0); ++ sqlite3_free(zSql); ++ } #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 ...)", --#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", --#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", --#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", --#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. --** --** Return the number of matches. --*/ --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; iopenMode==SHELL_OPEN_DESERIALIZE ++ || psi->openMode==SHELL_OPEN_HEXDB ){ ++ int rc; ++ int nData = 0; ++ unsigned char *aData; ++ if( psi->openMode==SHELL_OPEN_DESERIALIZE ){ ++ aData = (unsigned char*)readFile(zDbFilename, &nData); ++ }else{ ++ aData = readHexDb(psi, &nData); ++ if( aData==0 ){ ++ return; } } -- } -- }else{ -- /* Seek documented commands for which zPattern is an exact prefix */ -- zPat = sqlite3_mprintf(".%s*", zPattern); -- shell_check_oom(zPat); -- for(i=0; iszMax>0 ){ ++ sqlite3_file_control(DBX(psx), "main", SQLITE_FCNTL_SIZE_LIMIT, ++ &psi->szMax); } -- return n; } -- /* Look for documented commands that contain zPattern anywhere. -- ** Show complete text of all documented commands that match. */ -- zPat = sqlite3_mprintf("%%%s%%", zPattern); -- shell_check_oom(zPat); -- for(i=0; ibSafeModeFuture ){ ++ sqlite3_set_authorizer(DBX(psx), safeModeAuth, psx); } ++ sqlite3_db_config( ++ DBX(psx), SQLITE_DBCONFIG_STMT_SCANSTATUS, psi->scanstatsOn,(int*)0); } -- sqlite3_free(zPat); ++#if SHELL_DYNAMIC_EXTENSION ++ notify_subscribers(psi, NK_DbUserAppeared, DBX(psx)); ++#endif } -- return n; } --/* Forward reference */ --static int process_input(ShellState *p); -- /* --** Read the content of file zName into memory obtained from sqlite3_malloc64() --** and return a pointer to the buffer. The caller is responsible for freeing --** the memory. --** --** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes --** read. --** --** For convenience, a nul-terminator byte is always appended to the data read --** from the file before the buffer is returned. This byte is not included in --** the final value of (*pnByte), if applicable. --** --** NULL is returned if any error is encountered. The final value of *pnByte --** is undefined in this case. ++** Attempt to close the database connection. Report errors. */ --static char *readFile(const char *zName, int *pnByte){ -- FILE *in = fopen(zName, "rb"); -- long nIn; -- size_t nRead; -- char *pBuf; ++void close_db(sqlite3 *db){ 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; ++ if( db==globalDb ){ +++ /* This should only block for the time needed to handle ^C interrupt. */ ++ sqlite3_mutex_enter(pGlobalDbLock); ++ globalDb = 0; ++ rc = sqlite3_close(db); ++ sqlite3_mutex_leave(pGlobalDbLock); ++ }else{ ++ rc = sqlite3_close(db); } -- 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; ++ if( rc ){ ++ utf8_printf(STD_ERR, "Error: sqlite3_close() returns %d: %s\n", ++ rc, sqlite3_errmsg(db)); } -- pBuf[nIn] = 0; -- if( pnByte ) *pnByte = nIn; -- return pBuf; } --#if defined(SQLITE_ENABLE_SESSION) ++#if HAVE_READLINE || HAVE_EDITLINE /* --** Close a single OpenSession object and release all of its associated --** resources. ++** Readline completion callbacks */ --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]); ++static char *readline_completion_generator(const char *text, int state){ ++ static sqlite3_stmt *pStmt = 0; ++ char *zRet; ++ if( state==0 ){ ++ char *zSql; ++ sqlite3_finalize(pStmt); ++ zSql = smprintf("SELECT DISTINCT candidate COLLATE nocase" ++ " FROM completion(%Q) ORDER BY 1", text); ++ shell_check_oom(zSql); ++ sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0); ++ sqlite3_free(zSql); } -- 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]); ++ if( sqlite3_step(pStmt)==SQLITE_ROW ){ ++ const char *z = (const char*)sqlite3_column_text(pStmt,0); ++ if( z!=0 ){ ++ zRet = strdup(z); ++ shell_check_oom(zRet); ++ } ++ }else{ ++ sqlite3_finalize(pStmt); ++ pStmt = 0; ++ zRet = 0; } -- pAuxDb->nSession = 0; ++ return zRet; ++} ++static char **readline_completion(const char *zText, int iStart, int iEnd){ ++ (void)iStart; ++ (void)iEnd; ++ rl_attempted_completion_over = 1; ++ return rl_completion_matches(zText, readline_completion_generator); } --#else --# define session_close_all(X,Y) --#endif ++#elif HAVE_LINENOISE /* --** Implementation of the xFilter function for an open session. Omit --** any tables named by ".session filter" but let all other table through. ++** Linenoise completion callback */ --#if defined(SQLITE_ENABLE_SESSION) --static int session_filter(void *pCtx, const char *zTab){ -- OpenSession *pSession = (OpenSession*)pCtx; -- int i; -- for(i=0; inFilter; i++){ -- if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0; ++static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){ ++ i64 nLine = strlen(zLine); ++ i64 i, iStart; ++ sqlite3_stmt *pStmt = 0; ++ char *zSql; ++ char zBuf[1000]; ++ ++ if( nLine>(i64)sizeof(zBuf)-30 ) return; ++ if( zLine[0]=='.' || zLine[0]=='#') return; ++ for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){} ++ if( i==nLine-1 ) return; ++ iStart = i+1; ++ memcpy(zBuf, zLine, iStart); ++ zSql = smprintf("SELECT DISTINCT candidate COLLATE nocase" ++ " FROM completion(%Q,%Q) ORDER BY 1", ++ &zLine[iStart], zLine); ++ shell_check_oom(zSql); ++ sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0); ++ sqlite3_free(zSql); ++ sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */ ++ while( sqlite3_step(pStmt)==SQLITE_ROW ){ ++ const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0); ++ int nCompletion = sqlite3_column_bytes(pStmt, 0); ++ if( iStart+nCompletion < (i64)sizeof(zBuf)-1 && zCompletion ){ ++ memcpy(zBuf+iStart, zCompletion, nCompletion+1); ++ linenoiseAddCompletion(lc, zBuf); ++ } } -- return 1; ++ sqlite3_finalize(pStmt); } #endif /* --** Try to deduce the type of file for zName based on its content. Return --** one of the SHELL_OPEN_* constants. ++** Do C-language style escape sequence translation. ** --** 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. ++** \a -> alarm ++** \b -> backspace ++** \t -> tab ++** \n -> newline ++** \v -> vertical tab ++** \f -> form feed ++** \r -> carriage return ++** \s -> space ++** \" -> " ++** \' -> ' ++** \\ -> backslash ++** \NNN -> ascii character NNN in octal ++** \xHH -> ascii character HH in hexadecimal */ --int deduceDatabaseType(const char *zName, int dfltZip){ -- FILE *f = fopen(zName, "rb"); -- size_t n; -- int rc = SHELL_OPEN_UNSPEC; -- char zBuf[100]; -- if( f==0 ){ -- if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){ -- return SHELL_OPEN_ZIPFILE; -- }else{ -- return SHELL_OPEN_NORMAL; -- } -- } -- n = fread(zBuf, 16, 1, f); -- if( n==1 && memcmp(zBuf, "SQLite format 3", 16)==0 ){ -- fclose(f); -- return SHELL_OPEN_NORMAL; -- } -- fseek(f, -25, SEEK_END); -- n = fread(zBuf, 25, 1, f); -- if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){ -- rc = SHELL_OPEN_APPENDVFS; -- }else{ -- fseek(f, -22, SEEK_END); -- n = fread(zBuf, 22, 1, f); -- if( n==1 && zBuf[0]==0x50 && zBuf[1]==0x4b && zBuf[2]==0x05 -- && zBuf[3]==0x06 ){ -- rc = SHELL_OPEN_ZIPFILE; -- }else if( n==0 && dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){ -- rc = SHELL_OPEN_ZIPFILE; ++static void resolve_backslashes(char *z){ ++ int i, j; ++ char c; ++ while( *z && *z!='\\' ) z++; ++ for(i=j=0; (c = z[i])!=0; i++, j++){ ++ if( c=='\\' && z[i+1]!=0 ){ ++ c = z[++i]; ++ if( c=='a' ){ ++ c = '\a'; ++ }else if( c=='b' ){ ++ c = '\b'; ++ }else if( c=='t' ){ ++ c = '\t'; ++ }else if( c=='n' ){ ++ c = '\n'; ++ }else if( c=='v' ){ ++ c = '\v'; ++ }else if( c=='f' ){ ++ c = '\f'; ++ }else if( c=='r' ){ ++ c = '\r'; ++ }else if( c=='"' ){ ++ c = '"'; ++ }else if( c=='\'' ){ ++ c = '\''; ++ }else if( c=='\\' ){ ++ c = '\\'; ++ }else if( c=='x' ){ ++ int nhd = 0, hdv; ++ u8 hv = 0; ++ while( nhd<2 && (c=z[i+1+nhd])!=0 && (hdv=hexDigitValue(c))>=0 ){ ++ hv = (u8)((hv<<4)|hdv); ++ ++nhd; ++ } ++ i += nhd; ++ c = hv; ++ }else if( c>='0' && c<='7' ){ ++ c -= '0'; ++ if( z[i+1]>='0' && z[i+1]<='7' ){ ++ i++; ++ c = (c<<3) + z[i] - '0'; ++ if( z[i+1]>='0' && z[i+1]<='7' ){ ++ i++; ++ c = (c<<3) + z[i] - '0'; ++ } ++ } ++ } } ++ z[j] = c; } -- fclose(f); -- return rc; ++ if( jaAuxDb[].zDbFilename. --** If p->aAuxDb[].zDbFilename is 0, then read from standard input. ++** Interpret zArg as either an integer or a boolean value. Return 1 or 0 ++** for TRUE and FALSE. Return the integer value if appropriate. */ --static unsigned char *readHexDb(ShellState *p, int *pnData){ -- unsigned char *a = 0; -- int nLine; -- int n = 0; -- int pgsz = 0; -- int iOffset = 0; -- int j, k; -- int rc; -- FILE *in; -- const char *zDbFilename = p->pAuxDb->zDbFilename; -- unsigned int x[16]; -- char zLine[1000]; -- if( zDbFilename ){ -- in = fopen(zDbFilename, "r"); -- if( in==0 ){ -- utf8_printf(stderr, "cannot open \"%s\" for reading\n", zDbFilename); -- return 0; -- } -- nLine = 0; ++static int booleanValue(const char *zArg){ ++ static const char *zBoolNames[] = { ++ "no","yes", "off","on", ++#ifdef BOOLNAMES_ARE_BOOLEAN ++ "false","true", ++#endif ++ 0 ++ }; ++ int i; ++ if( zArg[0]=='0' && zArg[1]=='x' ){ ++ for(i=2; hexDigitValue(zArg[i])>=0; i++){} }else{ -- in = p->in; -- nLine = p->lineno; -- if( in==0 ) in = stdin; -- } -- *pnData = 0; -- nLine++; -- if( fgets(zLine, sizeof(zLine), in)==0 ) goto readHexDb_error; -- rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz); -- if( rc!=2 ) goto readHexDb_error; -- if( n<0 ) goto readHexDb_error; -- if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ) goto readHexDb_error; -- n = (n+pgsz-1)&~(pgsz-1); /* Round n up to the next multiple of pgsz */ -- a = sqlite3_malloc( n ? n : 1 ); -- shell_check_oom(a); -- memset(a, 0, n); -- if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){ -- utf8_printf(stderr, "invalid pagesize\n"); -- goto readHexDb_error; -- } -- for(nLine++; fgets(zLine, sizeof(zLine), in)!=0; nLine++){ -- rc = sscanf(zLine, "| page %d offset %d", &j, &k); -- if( rc==2 ){ -- iOffset = k; -- continue; -- } -- if( cli_strncmp(zLine, "| end ", 6)==0 ){ -- break; -- } -- rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", -- &j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7], -- &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]); -- if( rc==17 ){ -- k = iOffset+j; -- if( k+16<=n && k>=0 ){ -- int ii; -- for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff; -- } -- } ++ for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){} } -- *pnData = n; -- if( in!=p->in ){ -- fclose(in); -- }else{ -- p->lineno = nLine; ++ if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff); ++ for( i=0; zBoolNames[i]!=0; ++i ){ ++ if( sqlite3_stricmp(zArg, zBoolNames[i])==0 ) return i&1; } -- return a; ++ utf8_printf(STD_ERR, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n", ++ zArg); ++ return 0; ++} --readHexDb_error: -- if( in!=p->in ){ -- fclose(in); ++/* ++** Set or clear a shell flag according to a boolean value. ++*/ ++static void setOrClearFlag(ShellExState *psx, unsigned mFlag, const char *zArg){ ++ if( booleanValue(zArg) ){ ++ ShellSetFlag(psx, mFlag); }else{ -- while( fgets(zLine, sizeof(zLine), p->in)!=0 ){ -- nLine++; -- if(cli_strncmp(zLine, "| end ", 6)==0 ) break; -- } -- p->lineno = nLine; ++ ShellClearFlag(psx, mFlag); } -- sqlite3_free(a); -- utf8_printf(stderr,"Error on line %d of --hexdb input\n", nLine); -- return 0; } --#endif /* SQLITE_OMIT_DESERIALIZE */ /* --** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X. ++** Close an output file, provided it is not stderr or stdout */ --static void shellUSleepFunc( -- sqlite3_context *context, -- int argcUnused, -- sqlite3_value **argv --){ -- int sleep = sqlite3_value_int(argv[0]); -- (void)argcUnused; -- sqlite3_sleep(sleep/1000); -- sqlite3_result_int(context, sleep); ++static void output_file_close(FILE *f){ ++ if( f && f!=STD_OUT && f!=STD_ERR ) fclose(f); } --/* Flags for open_db(). --** --** The default behavior of open_db() is to exit(1) if the database fails to --** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error --** but still returns without calling exit. --** --** The OPEN_DB_ZIPFILE flag causes open_db() to prefer to open files as a --** ZIP archive if the file does not exist or is empty and its name matches --** the *.zip pattern. ++/* ++** Try to open an output file. The names "stdout" and "stderr" are ++** recognized and do the right thing. NULL is returned if the output ++** filename is "off". */ --#define OPEN_DB_KEEPALIVE 0x001 /* Return after error if true */ --#define OPEN_DB_ZIPFILE 0x002 /* Open as ZIP if name matches *.zip */ ++static FILE *output_file_open(const char *zFile, int bTextMode){ ++ FILE *f; ++ if( cli_strcmp(zFile,"stdout")==0 ){ ++ f = STD_OUT; ++ }else if( cli_strcmp(zFile, "stderr")==0 ){ ++ f = STD_ERR; ++ }else if( cli_strcmp(zFile, "off")==0 ){ ++ f = 0; ++ }else{ ++ f = fopen(zFile, bTextMode ? "w" : "wb"); ++ if( f==0 ){ ++ utf8_printf(STD_ERR, "Error: cannot open \"%s\"\n", zFile); ++ } ++ } ++ return f; ++} ++#ifndef SQLITE_OMIT_TRACE /* --** Make sure the database is open. If it is not, then open it. If --** the database fails to open, print an error message and exit. ++** A routine for handling output from sqlite3_trace(). */ --static void open_db(ShellState *p, int openFlags){ -- if( p->db==0 ){ -- const char *zDbFilename = p->pAuxDb->zDbFilename; -- if( p->openMode==SHELL_OPEN_UNSPEC ){ -- if( zDbFilename==0 || zDbFilename[0]==0 ){ -- p->openMode = SHELL_OPEN_NORMAL; -- }else{ -- p->openMode = (u8)deduceDatabaseType(zDbFilename, -- (openFlags & OPEN_DB_ZIPFILE)!=0); -- } -- } -- switch( p->openMode ){ -- case SHELL_OPEN_APPENDVFS: { -- sqlite3_open_v2(zDbFilename, &p->db, -- SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, "apndvfs"); -- break; -- } -- case SHELL_OPEN_HEXDB: -- case SHELL_OPEN_DESERIALIZE: { -- sqlite3_open(0, &p->db); -- break; -- } -- case SHELL_OPEN_ZIPFILE: { -- sqlite3_open(":memory:", &p->db); -- break; -- } -- case SHELL_OPEN_READONLY: { -- sqlite3_open_v2(zDbFilename, &p->db, -- SQLITE_OPEN_READONLY|p->openFlags, 0); ++static int sql_trace_callback( ++ unsigned mType, /* The trace type */ ++ void *pArg, /* The shell state pointer */ ++ void *pP, /* Usually a pointer to sqlite_stmt */ ++ void *pX /* Auxiliary output */ ++){ ++ ShellInState *psi = (ShellInState*)pArg; ++ sqlite3_stmt *pStmt; ++ const char *zSql; ++ i64 nSql; ++ if( psi->traceOut==0 ) return 0; ++ if( mType==SQLITE_TRACE_CLOSE ){ ++ utf8_printf(psi->traceOut, "-- closing database connection\n"); ++ return 0; ++ } ++ if( mType!=SQLITE_TRACE_ROW && pX!=0 && ((const char*)pX)[0]=='-' ){ ++ zSql = (const char*)pX; ++ }else{ ++ pStmt = (sqlite3_stmt*)pP; ++ switch( psi->eTraceType ){ ++ case SHELL_TRACE_EXPANDED: { ++ zSql = sqlite3_expanded_sql(pStmt); break; } -- case SHELL_OPEN_UNSPEC: -- case SHELL_OPEN_NORMAL: { -- sqlite3_open_v2(zDbFilename, &p->db, -- SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, 0); ++#ifdef SQLITE_ENABLE_NORMALIZE ++ case SHELL_TRACE_NORMALIZED: { ++ zSql = sqlite3_normalized_sql(pStmt); break; } -- } -- globalDb = p->db; -- if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ -- utf8_printf(stderr,"Error: unable to open database \"%s\": %s\n", -- zDbFilename, sqlite3_errmsg(p->db)); -- if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){ -- exit(1); -- } -- sqlite3_close(p->db); -- sqlite3_open(":memory:", &p->db); -- if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ -- utf8_printf(stderr, -- "Also: unable to open substitute in-memory database.\n" -- ); -- exit(1); -- }else{ -- utf8_printf(stderr, -- "Notice: using substitute in-memory database instead of \"%s\"\n", -- zDbFilename); -- } -- } -- sqlite3_db_config(p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, (int)0, (int*)0); -- -- /* Reflect the use or absence of --unsafe-testing invocation. */ -- { -- int testmode_on = ShellHasFlag(p,SHFLG_TestingMode); -- sqlite3_db_config(p->db, SQLITE_DBCONFIG_TRUSTED_SCHEMA, testmode_on,0); -- sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0); -- } -- --#ifndef SQLITE_OMIT_LOAD_EXTENSION -- sqlite3_enable_load_extension(p->db, 1); --#endif -- sqlite3_shathree_init(p->db, 0, 0); -- sqlite3_uint_init(p->db, 0, 0); -- sqlite3_decimal_init(p->db, 0, 0); -- sqlite3_base64_init(p->db, 0, 0); -- sqlite3_base85_init(p->db, 0, 0); -- sqlite3_regexp_init(p->db, 0, 0); -- sqlite3_ieee_init(p->db, 0, 0); -- sqlite3_series_init(p->db, 0, 0); --#if SQLITE_SHELL_HAVE_RECOVER -- sqlite3_dbdata_init(p->db, 0, 0); --#endif --#ifndef SQLITE_SHELL_FIDDLE -- sqlite3_fileio_init(p->db, 0, 0); -- sqlite3_completion_init(p->db, 0, 0); --#endif --#ifdef SQLITE_HAVE_ZLIB -- if( !p->bSafeModePersist ){ -- sqlite3_zipfile_init(p->db, 0, 0); -- sqlite3_sqlar_init(p->db, 0, 0); -- } --#endif --#ifdef SQLITE_SHELL_EXTFUNCS -- /* Create a preprocessing mechanism for extensions to make -- * their own provisions for being built into the shell. -- * This is a short-span macro. See further below for usage. -- */ --#define SHELL_SUB_MACRO(base, variant) base ## _ ## variant --#define SHELL_SUBMACRO(base, variant) SHELL_SUB_MACRO(base, variant) -- /* Let custom-included extensions get their ..._init() called. -- * The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause -- * the extension's sqlite3_*_init( db, pzErrorMsg, pApi ) -- * inititialization routine to be called. -- */ -- { -- int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db); -- /* Let custom-included extensions expose their functionality. -- * The WHATEVER_EXPOSE( db, pzErrorMsg ) macro should cause -- * the SQL functions, virtual tables, collating sequences or -- * VFS's implemented by the extension to be registered. -- */ -- if( irc==SQLITE_OK -- || irc==SQLITE_OK_LOAD_PERMANENTLY ){ -- SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, EXPOSE)(p->db, 0); -- } --#undef SHELL_SUB_MACRO --#undef SHELL_SUBMACRO -- } --#endif -- -- sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0, -- shellAddSchemaName, 0, 0); -- sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0, -- shellModuleSchema, 0, 0); -- sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p, -- shellPutsFunc, 0, 0); -- sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0, -- shellUSleepFunc, 0, 0); --#ifndef SQLITE_NOHAVE_SYSTEM -- sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0, -- editFunc, 0, 0); -- sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0, -- editFunc, 0, 0); #endif -- -- if( p->openMode==SHELL_OPEN_ZIPFILE ){ -- char *zSql = sqlite3_mprintf( -- "CREATE VIRTUAL TABLE zip USING zipfile(%Q);", zDbFilename); -- shell_check_oom(zSql); -- sqlite3_exec(p->db, zSql, 0, 0, 0); -- sqlite3_free(zSql); -- } --#ifndef SQLITE_OMIT_DESERIALIZE -- else -- if( p->openMode==SHELL_OPEN_DESERIALIZE || p->openMode==SHELL_OPEN_HEXDB ){ -- int rc; -- int nData = 0; -- unsigned char *aData; -- if( p->openMode==SHELL_OPEN_DESERIALIZE ){ -- aData = (unsigned char*)readFile(zDbFilename, &nData); -- }else{ -- aData = readHexDb(p, &nData); -- } -- if( aData==0 ){ -- return; -- } -- rc = sqlite3_deserialize(p->db, "main", aData, nData, nData, -- SQLITE_DESERIALIZE_RESIZEABLE | -- SQLITE_DESERIALIZE_FREEONCLOSE); -- if( rc ){ -- utf8_printf(stderr, "Error: sqlite3_deserialize() returns %d\n", rc); -- } -- if( p->szMax>0 ){ -- sqlite3_file_control(p->db, "main", SQLITE_FCNTL_SIZE_LIMIT, &p->szMax); ++ default: { ++ zSql = sqlite3_sql(pStmt); ++ break; } } --#endif } -- if( p->db!=0 ){ -- if( p->bSafeModePersist ){ -- sqlite3_set_authorizer(p->db, safeModeAuth, p); ++ if( zSql==0 ) return 0; ++ nSql = strlen(zSql); ++ if( nSql>1000000000 ) nSql = 1000000000; /* clamp to 1 billion */ ++ while( nSql>0 && zSql[nSql-1]==';' ){ nSql--; } ++ switch( mType ){ ++ case SQLITE_TRACE_ROW: ++ case SQLITE_TRACE_STMT: { ++ utf8_printf(psi->traceOut, "%.*s;\n", (int)nSql, zSql); ++ break; ++ } ++ case SQLITE_TRACE_PROFILE: { ++ sqlite3_int64 nNanosec = pX ? *(sqlite3_int64*)pX : 0; ++ utf8_printf(psi->traceOut,"%.*s; -- %lld ns\n", (int)nSql,zSql,nNanosec); ++ break; } -- sqlite3_db_config( -- p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0 -- ); } ++ return 0; } ++#endif /* --** Attempt to close the databaes connection. Report errors. ++** A no-op routine that runs with the ".breakpoint" dot-command. ++** This is a useful spot to set a debugger breakpoint. ++** ++** This routine does not do anything practical. The code are there simply ++** to prevent the compiler from optimizing this routine out. */ --void close_db(sqlite3 *db){ -- int rc = sqlite3_close(db); -- if( rc ){ -- utf8_printf(stderr, "Error: sqlite3_close() returns %d: %s\n", -- rc, sqlite3_errmsg(db)); -- } ++static void test_breakpoint(void){ ++ static int nCall = 0; ++ if( (nCall++)==0xffffffff ) printf("Many .breakpoints have run\n"); } --#if HAVE_READLINE || HAVE_EDITLINE /* --** Readline completion callbacks ++** An object used to read a CSV and other files for import. */ --static char *readline_completion_generator(const char *text, int state){ -- static sqlite3_stmt *pStmt = 0; -- char *zRet; -- if( state==0 ){ -- char *zSql; -- sqlite3_finalize(pStmt); -- zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase" -- " FROM completion(%Q) ORDER BY 1", text); -- shell_check_oom(zSql); -- sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0); -- sqlite3_free(zSql); -- } -- if( sqlite3_step(pStmt)==SQLITE_ROW ){ -- const char *z = (const char*)sqlite3_column_text(pStmt,0); -- zRet = z ? strdup(z) : 0; -- }else{ -- sqlite3_finalize(pStmt); -- pStmt = 0; -- zRet = 0; ++typedef struct ImportCtx ImportCtx; ++struct ImportCtx { ++ const char *zFile; /* Name of the input file */ ++ FILE *in; /* Read the CSV text from this input stream */ ++ int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close in */ ++ char *z; /* Accumulated text for a field */ ++ int n; /* Number of bytes in z */ ++ int nAlloc; /* Space allocated for z[] */ ++ int nLine; /* Current line number */ ++ int nRow; /* Number of rows imported */ ++ int nErr; /* Number of errors encountered */ ++ int bNotFirst; /* True if one or more bytes already read */ ++ int cTerm; /* Character that terminated the most recent field */ ++ int cColSep; /* The column separator character. (Usually ",") */ ++ int cRowSep; /* The row separator character. (Usually "\n") */ ++}; ++ ++/* Clean up resourced used by an ImportCtx */ ++static void import_cleanup(ImportCtx *p){ ++ if( p->in!=0 && p->xCloser!=0 ){ ++ p->xCloser(p->in); ++ p->in = 0; } -- return zRet; ++ sqlite3_free(p->z); ++ p->z = 0; } --static char **readline_completion(const char *zText, int iStart, int iEnd){ -- (void)iStart; -- (void)iEnd; -- rl_attempted_completion_over = 1; -- return rl_completion_matches(zText, readline_completion_generator); ++ ++/* Append a single byte to z[] */ ++static void import_append_char(ImportCtx *p, int c){ ++ if( p->n+1>=p->nAlloc ){ ++ p->nAlloc += p->nAlloc + 100; ++ p->z = sqlite3_realloc64(p->z, p->nAlloc); ++ shell_check_oom(p->z); ++ } ++ p->z[p->n++] = (char)c; } --#elif HAVE_LINENOISE --/* --** Linenoise completion callback - */ - static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){ - i64 nLine = strlen(zLine); - i64 i, iStart; - sqlite3_stmt *pStmt = 0; - char *zSql; - char zBuf[1000]; - - if( nLine>(i64)sizeof(zBuf)-30 ) return; - if( zLine[0]=='.' || zLine[0]=='#') return; - for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){} - if( i==nLine-1 ) return; - iStart = i+1; - memcpy(zBuf, zLine, iStart); - zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase" - " FROM completion(%Q,%Q) ORDER BY 1", - &zLine[iStart], zLine); - shell_check_oom(zSql); - sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); - sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */ - while( sqlite3_step(pStmt)==SQLITE_ROW ){ - const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0); - int nCompletion = sqlite3_column_bytes(pStmt, 0); - if( iStart+nCompletion < (i64)sizeof(zBuf)-1 && zCompletion ){ - memcpy(zBuf+iStart, zCompletion, nCompletion+1); - linenoiseAddCompletion(lc, zBuf); - } - } - sqlite3_finalize(pStmt); - } - #endif - - /* - ** Do C-language style dequoting. - ** - ** \a -> alarm - ** \b -> backspace - ** \t -> tab - ** \n -> newline - ** \v -> vertical tab - ** \f -> form feed - ** \r -> carriage return - ** \s -> space - ** \" -> " - ** \' -> ' - ** \\ -> backslash - ** \NNN -> ascii character NNN in octal - ** \xHH -> ascii character HH in hexadecimal - */ - static void resolve_backslashes(char *z){ - int i, j; - char c; - while( *z && *z!='\\' ) z++; - for(i=j=0; (c = z[i])!=0; i++, j++){ - if( c=='\\' && z[i+1]!=0 ){ - c = z[++i]; - if( c=='a' ){ - c = '\a'; - }else if( c=='b' ){ - c = '\b'; - }else if( c=='t' ){ - c = '\t'; - }else if( c=='n' ){ - c = '\n'; - }else if( c=='v' ){ - c = '\v'; - }else if( c=='f' ){ - c = '\f'; - }else if( c=='r' ){ - c = '\r'; - }else if( c=='"' ){ - c = '"'; - }else if( c=='\'' ){ - c = '\''; - }else if( c=='\\' ){ - c = '\\'; - }else if( c=='x' ){ - int nhd = 0, hdv; - u8 hv = 0; - while( nhd<2 && (c=z[i+1+nhd])!=0 && (hdv=hexDigitValue(c))>=0 ){ - hv = (u8)((hv<<4)|hdv); - ++nhd; - } - i += nhd; - c = (u8)hv; - }else if( c>='0' && c<='7' ){ - c -= '0'; - if( z[i+1]>='0' && z[i+1]<='7' ){ - i++; - c = (c<<3) + z[i] - '0'; - if( z[i+1]>='0' && z[i+1]<='7' ){ - i++; - c = (c<<3) + z[i] - '0'; - } - } - } - } - z[j] = c; - } - if( j=0; i++){} - }else{ - for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){} - } - if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff); - if( sqlite3_stricmp(zArg, "on")==0 || sqlite3_stricmp(zArg,"yes")==0 ){ - return 1; - } - if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){ - return 0; - } - utf8_printf(stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n", - zArg); - return 0; - } - - /* - ** Set or clear a shell flag according to a boolean value. - */ - static void setOrClearFlag(ShellState *p, unsigned mFlag, const char *zArg){ - if( booleanValue(zArg) ){ - ShellSetFlag(p, mFlag); - }else{ - ShellClearFlag(p, mFlag); - } - } - - /* - ** Close an output file, assuming it is not stderr or stdout - */ - static void output_file_close(FILE *f){ - if( f && f!=stdout && f!=stderr ) fclose(f); - } - - /* - ** Try to open an output file. The names "stdout" and "stderr" are - ** recognized and do the right thing. NULL is returned if the output - ** filename is "off". - */ - static FILE *output_file_open(const char *zFile, int bTextMode){ - FILE *f; - if( cli_strcmp(zFile,"stdout")==0 ){ - f = stdout; - }else if( cli_strcmp(zFile, "stderr")==0 ){ - f = stderr; - }else if( cli_strcmp(zFile, "off")==0 ){ - f = 0; - }else{ - f = fopen(zFile, bTextMode ? "w" : "wb"); - if( f==0 ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile); - } - } - return f; - } - - #ifndef SQLITE_OMIT_TRACE - /* - ** A routine for handling output from sqlite3_trace(). - */ - static int sql_trace_callback( - unsigned mType, /* The trace type */ - void *pArg, /* The ShellState pointer */ - void *pP, /* Usually a pointer to sqlite_stmt */ - void *pX /* Auxiliary output */ - ){ - ShellState *p = (ShellState*)pArg; - sqlite3_stmt *pStmt; - const char *zSql; - i64 nSql; - if( p->traceOut==0 ) return 0; - if( mType==SQLITE_TRACE_CLOSE ){ - utf8_printf(p->traceOut, "-- closing database connection\n"); - return 0; - } - if( mType!=SQLITE_TRACE_ROW && pX!=0 && ((const char*)pX)[0]=='-' ){ - zSql = (const char*)pX; - }else{ - pStmt = (sqlite3_stmt*)pP; - switch( p->eTraceType ){ - case SHELL_TRACE_EXPANDED: { - zSql = sqlite3_expanded_sql(pStmt); - break; - } - #ifdef SQLITE_ENABLE_NORMALIZE - case SHELL_TRACE_NORMALIZED: { - zSql = sqlite3_normalized_sql(pStmt); - break; - } - #endif - default: { - zSql = sqlite3_sql(pStmt); - break; - } - } - } - if( zSql==0 ) return 0; - nSql = strlen(zSql); - if( nSql>1000000000 ) nSql = 1000000000; - while( nSql>0 && zSql[nSql-1]==';' ){ nSql--; } - switch( mType ){ - case SQLITE_TRACE_ROW: - case SQLITE_TRACE_STMT: { - utf8_printf(p->traceOut, "%.*s;\n", (int)nSql, zSql); - break; - } - case SQLITE_TRACE_PROFILE: { - sqlite3_int64 nNanosec = pX ? *(sqlite3_int64*)pX : 0; - utf8_printf(p->traceOut, "%.*s; -- %lld ns\n", (int)nSql, zSql, nNanosec); - break; - } - } - return 0; - } - #endif - - /* - ** A no-op routine that runs with the ".breakpoint" doc-command. This is - ** a useful spot to set a debugger breakpoint. - ** - ** This routine does not do anything practical. The code are there simply - ** to prevent the compiler from optimizing this routine out. - */ - static void test_breakpoint(void){ - static unsigned int nCall = 0; - if( (nCall++)==0xffffffff ) printf("Many .breakpoints have run\n"); - } - - /* - ** An object used to read a CSV and other files for import. - */ - typedef struct ImportCtx ImportCtx; - struct ImportCtx { - const char *zFile; /* Name of the input file */ - FILE *in; /* Read the CSV text from this input stream */ - int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close in */ - char *z; /* Accumulated text for a field */ - int n; /* Number of bytes in z */ - int nAlloc; /* Space allocated for z[] */ - int nLine; /* Current line number */ - int nRow; /* Number of rows imported */ - int nErr; /* Number of errors encountered */ - int bNotFirst; /* True if one or more bytes already read */ - int cTerm; /* Character that terminated the most recent field */ - int cColSep; /* The column separator character. (Usually ",") */ - int cRowSep; /* The row separator character. (Usually "\n") */ - }; - - /* Clean up resourced used by an ImportCtx */ - static void import_cleanup(ImportCtx *p){ - if( p->in!=0 && p->xCloser!=0 ){ - p->xCloser(p->in); - p->in = 0; - } - sqlite3_free(p->z); - p->z = 0; - } - - /* Append a single byte to z[] */ - static void import_append_char(ImportCtx *p, int c){ - if( p->n+1>=p->nAlloc ){ - p->nAlloc += p->nAlloc + 100; - p->z = sqlite3_realloc64(p->z, p->nAlloc); - shell_check_oom(p->z); - } - p->z[p->n++] = (char)c; - } - - /* Read a single field of CSV text. Compatible with rfc4180 and extended - ** with the option of having a separator other than ",". - ** - ** + Input comes from p->in. - ** + Store results in p->z of length p->n. Space to hold p->z comes - ** from sqlite3_malloc64(). - ** + Use p->cSep as the column separator. The default is ",". - ** + Use p->rSep as the row separator. The default is "\n". - ** + Keep track of the line number in p->nLine. - ** + Store the character that terminates the field in p->cTerm. Store - ** EOF on end-of-file. - ** + Report syntax errors on stderr ++/* Read a single field of CSV text. Compatible with rfc4180 and extended ++** with the option of having a separator other than ",". ++** ++** + Input comes from p->in. ++** + Store results in p->z of length p->n. Space to hold p->z comes ++** from sqlite3_malloc64(). ++** + Use p->cSep as the column separator. The default is ",". ++** + Use p->rSep as the row separator. The default is "\n". ++** + Keep track of the line number in p->nLine. ++** + Store the character that terminates the field in p->cTerm. Store ++** EOF on end-of-file. ++** + Report syntax errors on stderr */ -static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){ - i64 nLine = strlen(zLine); - i64 i, iStart; - sqlite3_stmt *pStmt = 0; - char *zSql; - char zBuf[1000]; - - if( nLine>(i64)sizeof(zBuf)-30 ) return; - if( zLine[0]=='.' || zLine[0]=='#') return; - for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){} - if( i==nLine-1 ) return; - iStart = i+1; - memcpy(zBuf, zLine, iStart); - zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase" - " FROM completion(%Q,%Q) ORDER BY 1", - &zLine[iStart], zLine); - shell_check_oom(zSql); - sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); - sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */ - while( sqlite3_step(pStmt)==SQLITE_ROW ){ - const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0); - int nCompletion = sqlite3_column_bytes(pStmt, 0); - if( iStart+nCompletion < (i64)sizeof(zBuf)-1 && zCompletion ){ - memcpy(zBuf+iStart, zCompletion, nCompletion+1); - linenoiseAddCompletion(lc, zBuf); - } +static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){ + int c; + int cSep = (u8)p->cColSep; + int rSep = (u8)p->cRowSep; + p->n = 0; + c = fgetc(p->in); + if( c==EOF || seenInterrupt ){ + p->cTerm = EOF; + return 0; } - sqlite3_finalize(pStmt); -} -#endif - -/* -** Do C-language style dequoting. -** -** \a -> alarm -** \b -> backspace -** \t -> tab -** \n -> newline -** \v -> vertical tab -** \f -> form feed -** \r -> carriage return -** \s -> space -** \" -> " -** \' -> ' -** \\ -> backslash -** \NNN -> ascii character NNN in octal -** \xHH -> ascii character HH in hexadecimal -*/ -static void resolve_backslashes(char *z){ - int i, j; - char c; - while( *z && *z!='\\' ) z++; - for(i=j=0; (c = z[i])!=0; i++, j++){ - if( c=='\\' && z[i+1]!=0 ){ - c = z[++i]; - if( c=='a' ){ - c = '\a'; - }else if( c=='b' ){ - c = '\b'; - }else if( c=='t' ){ - c = '\t'; - }else if( c=='n' ){ - c = '\n'; - }else if( c=='v' ){ - c = '\v'; - }else if( c=='f' ){ - c = '\f'; - }else if( c=='r' ){ - c = '\r'; - }else if( c=='"' ){ - c = '"'; - }else if( c=='\'' ){ - c = '\''; - }else if( c=='\\' ){ - c = '\\'; - }else if( c=='x' ){ - int nhd = 0, hdv; - u8 hv = 0; - while( nhd<2 && (c=z[i+1+nhd])!=0 && (hdv=hexDigitValue(c))>=0 ){ - hv = (u8)((hv<<4)|hdv); - ++nhd; + if( c=='"' ){ + int pc, ppc; + int startLine = p->nLine; + int cQuote = c; + pc = ppc = 0; + while( 1 ){ + c = fgetc(p->in); + if( c==rSep ) p->nLine++; + if( c==cQuote ){ + if( pc==cQuote ){ + pc = 0; + continue; } - i += nhd; - c = (u8)hv; - }else if( c>='0' && c<='7' ){ - c -= '0'; - if( z[i+1]>='0' && z[i+1]<='7' ){ - i++; - c = (c<<3) + z[i] - '0'; - if( z[i+1]>='0' && z[i+1]<='7' ){ - i++; - c = (c<<3) + z[i] - '0'; - } + } + if( (c==cSep && pc==cQuote) + || (c==rSep && pc==cQuote) + || (c==rSep && pc=='\r' && ppc==cQuote) + || (c==EOF && pc==cQuote) + ){ + do{ p->n--; }while( p->z[p->n]!=cQuote ); + p->cTerm = c; + break; + } + if( pc==cQuote && c!='\r' ){ - utf8_printf(stderr, "%s:%d: unescaped %c character\n", ++ utf8_printf(STD_ERR, "%s:%d: unescaped %c character\n", + p->zFile, p->nLine, cQuote); + } + if( c==EOF ){ - utf8_printf(stderr, "%s:%d: unterminated %c-quoted field\n", ++ utf8_printf(STD_ERR, "%s:%d: unterminated %c-quoted field\n", + p->zFile, startLine, cQuote); + p->cTerm = c; + break; + } + import_append_char(p, c); + ppc = pc; + pc = c; + } + }else{ + /* If this is the first field being parsed and it begins with the + ** UTF-8 BOM (0xEF BB BF) then skip the BOM */ + if( (c&0xff)==0xef && p->bNotFirst==0 ){ + import_append_char(p, c); + c = fgetc(p->in); + if( (c&0xff)==0xbb ){ + import_append_char(p, c); + c = fgetc(p->in); + if( (c&0xff)==0xbf ){ + p->bNotFirst = 1; + p->n = 0; + return csv_read_one_field(p); } } } - z[j] = c; + while( c!=EOF && c!=cSep && c!=rSep ){ + import_append_char(p, c); + c = fgetc(p->in); + } + if( c==rSep ){ + p->nLine++; + if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--; + } + p->cTerm = c; } - if( jz ) p->z[p->n] = 0; + p->bNotFirst = 1; + return p->z; } -/* -** Interpret zArg as either an integer or a boolean value. Return 1 or 0 -** for TRUE and FALSE. Return the integer value if appropriate. +/* Read a single field of ASCII delimited text. +** +** + Input comes from p->in. +** + Store results in p->z of length p->n. Space to hold p->z comes +** from sqlite3_malloc64(). +** + Use p->cSep as the column separator. The default is "\x1F". +** + Use p->rSep as the row separator. The default is "\x1E". +** + Keep track of the row number in p->nLine. +** + Store the character that terminates the field in p->cTerm. Store +** EOF on end-of-file. +** + Report syntax errors on stderr */ -static int booleanValue(const char *zArg){ - int i; - if( zArg[0]=='0' && zArg[1]=='x' ){ - for(i=2; hexDigitValue(zArg[i])>=0; i++){} - }else{ - for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){} - } - if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff); - if( sqlite3_stricmp(zArg, "on")==0 || sqlite3_stricmp(zArg,"yes")==0 ){ - return 1; - } - if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){ +static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p){ + int c; + int cSep = (u8)p->cColSep; + int rSep = (u8)p->cRowSep; + p->n = 0; + c = fgetc(p->in); + if( c==EOF || seenInterrupt ){ + p->cTerm = EOF; return 0; } - utf8_printf(stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n", - zArg); - return 0; -} - -/* -** Set or clear a shell flag according to a boolean value. -*/ -static void setOrClearFlag(ShellState *p, unsigned mFlag, const char *zArg){ - if( booleanValue(zArg) ){ - ShellSetFlag(p, mFlag); - }else{ - ShellClearFlag(p, mFlag); + while( c!=EOF && c!=cSep && c!=rSep ){ + import_append_char(p, c); + c = fgetc(p->in); } -} - -/* -** Close an output file, assuming it is not stderr or stdout -*/ -static void output_file_close(FILE *f){ - if( f && f!=stdout && f!=stderr ) fclose(f); -} - -/* -** Try to open an output file. The names "stdout" and "stderr" are -** recognized and do the right thing. NULL is returned if the output -** filename is "off". -*/ -static FILE *output_file_open(const char *zFile, int bTextMode){ - FILE *f; - if( cli_strcmp(zFile,"stdout")==0 ){ - f = stdout; - }else if( cli_strcmp(zFile, "stderr")==0 ){ - f = stderr; - }else if( cli_strcmp(zFile, "off")==0 ){ - f = 0; - }else{ - f = fopen(zFile, bTextMode ? "w" : "wb"); - if( f==0 ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile); - } + if( c==rSep ){ + p->nLine++; } - return f; + p->cTerm = c; + if( p->z ) p->z[p->n] = 0; + return p->z; } -#ifndef SQLITE_OMIT_TRACE /* -** A routine for handling output from sqlite3_trace(). +** Try to transfer data for table zTable. If an error is seen while +** moving forward, try to go backwards. The backwards movement won't +** work for WITHOUT ROWID tables. */ -static int sql_trace_callback( - unsigned mType, /* The trace type */ - void *pArg, /* The ShellState pointer */ - void *pP, /* Usually a pointer to sqlite_stmt */ - void *pX /* Auxiliary output */ -){ - ShellState *p = (ShellState*)pArg; - sqlite3_stmt *pStmt; - const char *zSql; - i64 nSql; - if( p->traceOut==0 ) return 0; - if( mType==SQLITE_TRACE_CLOSE ){ - utf8_printf(p->traceOut, "-- closing database connection\n"); - return 0; - } - if( mType!=SQLITE_TRACE_ROW && pX!=0 && ((const char*)pX)[0]=='-' ){ - zSql = (const char*)pX; - }else{ - pStmt = (sqlite3_stmt*)pP; - switch( p->eTraceType ){ - case SHELL_TRACE_EXPANDED: { - zSql = sqlite3_expanded_sql(pStmt); - break; - } -#ifdef SQLITE_ENABLE_NORMALIZE - case SHELL_TRACE_NORMALIZED: { - zSql = sqlite3_normalized_sql(pStmt); - break; - } -#endif - default: { - zSql = sqlite3_sql(pStmt); - break; - } - } - } - if( zSql==0 ) return 0; - nSql = strlen(zSql); - if( nSql>1000000000 ) nSql = 1000000000; - while( nSql>0 && zSql[nSql-1]==';' ){ nSql--; } - switch( mType ){ - case SQLITE_TRACE_ROW: - case SQLITE_TRACE_STMT: { - utf8_printf(p->traceOut, "%.*s;\n", (int)nSql, zSql); - break; - } - case SQLITE_TRACE_PROFILE: { - sqlite3_int64 nNanosec = pX ? *(sqlite3_int64*)pX : 0; - utf8_printf(p->traceOut, "%.*s; -- %lld ns\n", (int)nSql, zSql, nNanosec); - break; - } - } - return 0; -} -#endif - -/* -** A no-op routine that runs with the ".breakpoint" doc-command. This is -** a useful spot to set a debugger breakpoint. -** -** This routine does not do anything practical. The code are there simply -** to prevent the compiler from optimizing this routine out. -*/ -static void test_breakpoint(void){ - static unsigned int nCall = 0; - if( (nCall++)==0xffffffff ) printf("Many .breakpoints have run\n"); -} - -/* -** An object used to read a CSV and other files for import. -*/ -typedef struct ImportCtx ImportCtx; -struct ImportCtx { - const char *zFile; /* Name of the input file */ - FILE *in; /* Read the CSV text from this input stream */ - int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close in */ - char *z; /* Accumulated text for a field */ - int n; /* Number of bytes in z */ - int nAlloc; /* Space allocated for z[] */ - int nLine; /* Current line number */ - int nRow; /* Number of rows imported */ - int nErr; /* Number of errors encountered */ - int bNotFirst; /* True if one or more bytes already read */ - int cTerm; /* Character that terminated the most recent field */ - int cColSep; /* The column separator character. (Usually ",") */ - int cRowSep; /* The row separator character. (Usually "\n") */ -}; - -/* Clean up resourced used by an ImportCtx */ -static void import_cleanup(ImportCtx *p){ - if( p->in!=0 && p->xCloser!=0 ){ - p->xCloser(p->in); - p->in = 0; - } - sqlite3_free(p->z); - p->z = 0; -} - -/* Append a single byte to z[] */ -static void import_append_char(ImportCtx *p, int c){ - if( p->n+1>=p->nAlloc ){ - p->nAlloc += p->nAlloc + 100; - p->z = sqlite3_realloc64(p->z, p->nAlloc); - shell_check_oom(p->z); - } - p->z[p->n++] = (char)c; -} - -/* Read a single field of CSV text. Compatible with rfc4180 and extended -** with the option of having a separator other than ",". -** -** + Input comes from p->in. -** + Store results in p->z of length p->n. Space to hold p->z comes -** from sqlite3_malloc64(). -** + Use p->cSep as the column separator. The default is ",". -** + Use p->rSep as the row separator. The default is "\n". -** + Keep track of the line number in p->nLine. -** + Store the character that terminates the field in p->cTerm. Store -** EOF on end-of-file. -** + Report syntax errors on stderr -*/ -static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){ - int c; - int cSep = (u8)p->cColSep; - int rSep = (u8)p->cRowSep; - p->n = 0; - c = fgetc(p->in); - if( c==EOF || seenInterrupt ){ - p->cTerm = EOF; - return 0; - } - if( c=='"' ){ - int pc, ppc; - int startLine = p->nLine; - int cQuote = c; - pc = ppc = 0; - while( 1 ){ - c = fgetc(p->in); - if( c==rSep ) p->nLine++; - if( c==cQuote ){ - if( pc==cQuote ){ - pc = 0; - continue; - } - } - if( (c==cSep && pc==cQuote) - || (c==rSep && pc==cQuote) - || (c==rSep && pc=='\r' && ppc==cQuote) - || (c==EOF && pc==cQuote) - ){ - do{ p->n--; }while( p->z[p->n]!=cQuote ); - p->cTerm = c; - break; - } - if( pc==cQuote && c!='\r' ){ - utf8_printf(stderr, "%s:%d: unescaped %c character\n", - p->zFile, p->nLine, cQuote); - } - if( c==EOF ){ - utf8_printf(stderr, "%s:%d: unterminated %c-quoted field\n", - p->zFile, startLine, cQuote); - p->cTerm = c; - break; - } - import_append_char(p, c); - ppc = pc; - pc = c; - } - }else{ - /* If this is the first field being parsed and it begins with the - ** UTF-8 BOM (0xEF BB BF) then skip the BOM */ - if( (c&0xff)==0xef && p->bNotFirst==0 ){ - import_append_char(p, c); - c = fgetc(p->in); - if( (c&0xff)==0xbb ){ - import_append_char(p, c); - c = fgetc(p->in); - if( (c&0xff)==0xbf ){ - p->bNotFirst = 1; - p->n = 0; - return csv_read_one_field(p); - } - } - } - while( c!=EOF && c!=cSep && c!=rSep ){ - import_append_char(p, c); - c = fgetc(p->in); - } - if( c==rSep ){ - p->nLine++; - if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--; - } - p->cTerm = c; - } - if( p->z ) p->z[p->n] = 0; - p->bNotFirst = 1; - return p->z; -} - -/* Read a single field of ASCII delimited text. -** -** + Input comes from p->in. -** + Store results in p->z of length p->n. Space to hold p->z comes -** from sqlite3_malloc64(). -** + Use p->cSep as the column separator. The default is "\x1F". -** + Use p->rSep as the row separator. The default is "\x1E". -** + Keep track of the row number in p->nLine. -** + Store the character that terminates the field in p->cTerm. Store -** EOF on end-of-file. -** + Report syntax errors on stderr -*/ -static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p){ - int c; - int cSep = (u8)p->cColSep; - int rSep = (u8)p->cRowSep; - p->n = 0; - c = fgetc(p->in); - if( c==EOF || seenInterrupt ){ - p->cTerm = EOF; - return 0; - } - while( c!=EOF && c!=cSep && c!=rSep ){ - import_append_char(p, c); - c = fgetc(p->in); - } - if( c==rSep ){ - p->nLine++; - } - p->cTerm = c; - if( p->z ) p->z[p->n] = 0; - return p->z; -} - -/* -** Try to transfer data for table zTable. If an error is seen while -** moving forward, try to go backwards. The backwards movement won't -** work for WITHOUT ROWID tables. -*/ -static void tryToCloneData( - ShellState *p, - sqlite3 *newDb, - const char *zTable +static void tryToCloneData( - ShellState *p, ++ ShellExState *psx, + sqlite3 *newDb, + const char *zTable ){ sqlite3_stmt *pQuery = 0; sqlite3_stmt *pInsert = 0; @@@@ -7008,38 -6590,235 -6589,235 +7063,38 @@@@ static void shellFkeyCollateClause } } - - /* - ** The implementation of dot-command ".lint fkey-indexes". - */ - static int lintFkeyIndexes( - ShellState *pState, /* Current shell tool state */ - char **azArg, /* Array of arguments passed to dot command */ - int nArg /* Number of entries in azArg[] */ - ){ - sqlite3 *db = pState->db; /* Database handle to query "main" db of */ - FILE *out = pState->out; /* Stream to write non-error output to */ - int bVerbose = 0; /* If -verbose is present */ - int bGroupByParent = 0; /* If -groupbyparent is present */ - int i; /* To iterate through azArg[] */ - const char *zIndent = ""; /* How much to indent CREATE INDEX by */ - int rc; /* Return code */ - sqlite3_stmt *pSql = 0; /* Compiled version of SQL statement below */ - - /* - ** This SELECT statement returns one row for each foreign key constraint - ** in the schema of the main database. The column values are: - ** - ** 0. The text of an SQL statement similar to: - ** - ** "EXPLAIN QUERY PLAN SELECT 1 FROM child_table WHERE child_key=?" - ** - ** This SELECT is similar to the one that the foreign keys implementation - ** needs to run internally on child tables. If there is an index that can - ** be used to optimize this query, then it can also be used by the FK - ** implementation to optimize DELETE or UPDATE statements on the parent - ** table. - ** - ** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by - ** the EXPLAIN QUERY PLAN command matches this pattern, then the schema - ** contains an index that can be used to optimize the query. - ** - ** 2. Human readable text that describes the child table and columns. e.g. - ** - ** "child_table(child_key1, child_key2)" - ** - ** 3. Human readable text that describes the parent table and columns. e.g. - ** - ** "parent_table(parent_key1, parent_key2)" - ** - ** 4. A full CREATE INDEX statement for an index that could be used to - ** optimize DELETE or UPDATE statements on the parent table. e.g. - ** - ** "CREATE INDEX child_table_child_key ON child_table(child_key)" - ** - ** 5. The name of the parent table. - ** - ** These six values are used by the C logic below to generate the report. - */ - const char *zSql = - "SELECT " - " 'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '" - " || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' " - " || fkey_collate_clause(" - " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')" - ", " - " 'SEARCH ' || s.name || ' USING COVERING INDEX*('" - " || group_concat('*=?', ' AND ') || ')'" - ", " - " s.name || '(' || group_concat(f.[from], ', ') || ')'" - ", " - " f.[table] || '(' || group_concat(COALESCE(f.[to], p.[name])) || ')'" - ", " - " 'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))" - " || ' ON ' || quote(s.name) || '('" - " || group_concat(quote(f.[from]) ||" - " fkey_collate_clause(" - " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')" - " || ');'" - ", " - " f.[table] " - "FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f " - "LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) " - "GROUP BY s.name, f.id " - "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)" - ; - const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)"; - - for(i=2; i1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){ - bVerbose = 1; - } - else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){ - bGroupByParent = 1; - zIndent = " "; - } - else{ - raw_printf(stderr, "Usage: %s %s ?-verbose? ?-groupbyparent?\n", - azArg[0], azArg[1] - ); - return SQLITE_ERROR; - } - } - - /* Register the fkey_collate_clause() SQL function */ - rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8, - 0, shellFkeyCollateClause, 0, 0 - ); - - - if( rc==SQLITE_OK ){ - rc = sqlite3_prepare_v2(db, zSql, -1, &pSql, 0); - } - if( rc==SQLITE_OK ){ - sqlite3_bind_int(pSql, 1, bGroupByParent); - } - - if( rc==SQLITE_OK ){ - int rc2; - char *zPrev = 0; - while( SQLITE_ROW==sqlite3_step(pSql) ){ - int res = -1; - sqlite3_stmt *pExplain = 0; - const char *zEQP = (const char*)sqlite3_column_text(pSql, 0); - const char *zGlob = (const char*)sqlite3_column_text(pSql, 1); - const char *zFrom = (const char*)sqlite3_column_text(pSql, 2); - const char *zTarget = (const char*)sqlite3_column_text(pSql, 3); - const char *zCI = (const char*)sqlite3_column_text(pSql, 4); - const char *zParent = (const char*)sqlite3_column_text(pSql, 5); - - if( zEQP==0 ) continue; - if( zGlob==0 ) continue; - rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0); - if( rc!=SQLITE_OK ) break; - if( SQLITE_ROW==sqlite3_step(pExplain) ){ - const char *zPlan = (const char*)sqlite3_column_text(pExplain, 3); - res = zPlan!=0 && ( 0==sqlite3_strglob(zGlob, zPlan) - || 0==sqlite3_strglob(zGlobIPK, zPlan)); - } - rc = sqlite3_finalize(pExplain); - if( rc!=SQLITE_OK ) break; - - if( res<0 ){ - raw_printf(stderr, "Error: internal error"); - break; - }else{ - if( bGroupByParent - && (bVerbose || res==0) - && (zPrev==0 || sqlite3_stricmp(zParent, zPrev)) - ){ - raw_printf(out, "-- Parent table %s\n", zParent); - sqlite3_free(zPrev); - zPrev = sqlite3_mprintf("%s", zParent); - } - - if( res==0 ){ - raw_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget); - }else if( bVerbose ){ - raw_printf(out, "%s/* no extra indexes required for %s -> %s */\n", - zIndent, zFrom, zTarget - ); - } - } - } - sqlite3_free(zPrev); - - if( rc!=SQLITE_OK ){ - raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); - } - - rc2 = sqlite3_finalize(pSql); - if( rc==SQLITE_OK && rc2!=SQLITE_OK ){ - rc = rc2; - raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); - } - }else{ - raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); - } - - return rc; - } - - /* - ** Implementation of ".lint" dot command. - */ - static int lintDotCommand( - ShellState *pState, /* Current shell tool state */ - char **azArg, /* Array of arguments passed to dot command */ - int nArg /* Number of entries in azArg[] */ - ){ - int n; - n = (nArg>=2 ? strlen30(azArg[1]) : 0); - if( n<1 || sqlite3_strnicmp(azArg[1], "fkey-indexes", n) ) goto usage; - return lintFkeyIndexes(pState, azArg, nArg); - - usage: - raw_printf(stderr, "Usage %s sub-command ?switches...?\n", azArg[0]); - raw_printf(stderr, "Where sub-commands are:\n"); - raw_printf(stderr, " fkey-indexes\n"); - return SQLITE_ERROR; - } - +#if !defined SQLITE_OMIT_VIRTUALTABLE +static void shellPrepare( + sqlite3 *db, + int *pRc, + const char *zSql, + sqlite3_stmt **ppStmt +){ + *ppStmt = 0; + if( *pRc==SQLITE_OK ){ + int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0); + if( rc!=SQLITE_OK ){ - raw_printf(stderr, "sql error: %s (%d)\n", ++ raw_printf(STD_ERR, "sql error: %s (%d)\n", + sqlite3_errmsg(db), sqlite3_errcode(db) + ); + *pRc = rc; + } + } +} /* -** The implementation of dot-command ".lint fkey-indexes". +** Create a prepared statement using printf-style arguments for the SQL. +** +** This routine is could be marked "static". But it is not always used, +** depending on compile-time options. By omitting the "static", we avoid +** nuisance compiler warnings about "defined but not used". */ -static int lintFkeyIndexes( - ShellState *pState, /* Current shell tool state */ - char **azArg, /* Array of arguments passed to dot command */ - int nArg /* Number of entries in azArg[] */ -){ - sqlite3 *db = pState->db; /* Database handle to query "main" db of */ - FILE *out = pState->out; /* Stream to write non-error output to */ - int bVerbose = 0; /* If -verbose is present */ - int bGroupByParent = 0; /* If -groupbyparent is present */ - int i; /* To iterate through azArg[] */ - const char *zIndent = ""; /* How much to indent CREATE INDEX by */ - int rc; /* Return code */ - sqlite3_stmt *pSql = 0; /* Compiled version of SQL statement below */ - - /* - ** This SELECT statement returns one row for each foreign key constraint - ** in the schema of the main database. The column values are: - ** - ** 0. The text of an SQL statement similar to: - ** - ** "EXPLAIN QUERY PLAN SELECT 1 FROM child_table WHERE child_key=?" - ** - ** This SELECT is similar to the one that the foreign keys implementation - ** needs to run internally on child tables. If there is an index that can - ** be used to optimize this query, then it can also be used by the FK - ** implementation to optimize DELETE or UPDATE statements on the parent - ** table. - ** - ** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by - ** the EXPLAIN QUERY PLAN command matches this pattern, then the schema - ** contains an index that can be used to optimize the query. - ** - ** 2. Human readable text that describes the child table and columns. e.g. - ** - ** "child_table(child_key1, child_key2)" - ** - ** 3. Human readable text that describes the parent table and columns. e.g. - ** - ** "parent_table(parent_key1, parent_key2)" - ** - ** 4. A full CREATE INDEX statement for an index that could be used to - ** optimize DELETE or UPDATE statements on the parent table. e.g. - ** - ** "CREATE INDEX child_table_child_key ON child_table(child_key)" - ** - ** 5. The name of the parent table. - ** - ** These six values are used by the C logic below to generate the report. - */ - const char *zSql = - "SELECT " - " 'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '" - " || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' " - " || fkey_collate_clause(" - " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')" - ", " - " 'SEARCH ' || s.name || ' USING COVERING INDEX*('" - " || group_concat('*=?', ' AND ') || ')'" - ", " - " s.name || '(' || group_concat(f.[from], ', ') || ')'" - ", " - " f.[table] || '(' || group_concat(COALESCE(f.[to], p.[name])) || ')'" - ", " - " 'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))" - " || ' ON ' || quote(s.name) || '('" - " || group_concat(quote(f.[from]) ||" - " fkey_collate_clause(" - " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')" - " || ');'" - ", " - " f.[table] " - "FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f " - "LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) " - "GROUP BY s.name, f.id " - "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)" - ; - const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)"; - - for(i=2; i1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){ - bVerbose = 1; - } - else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){ - bGroupByParent = 1; - zIndent = " "; - } - else{ - raw_printf(stderr, "Usage: %s %s ?-verbose? ?-groupbyparent?\n", - azArg[0], azArg[1] - ); - return SQLITE_ERROR; - } - } - - /* Register the fkey_collate_clause() SQL function */ - rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8, - 0, shellFkeyCollateClause, 0, 0 - ); - - - if( rc==SQLITE_OK ){ - rc = sqlite3_prepare_v2(db, zSql, -1, &pSql, 0); - } - if( rc==SQLITE_OK ){ - sqlite3_bind_int(pSql, 1, bGroupByParent); - } - - if( rc==SQLITE_OK ){ - int rc2; - char *zPrev = 0; - while( SQLITE_ROW==sqlite3_step(pSql) ){ - int res = -1; - sqlite3_stmt *pExplain = 0; - const char *zEQP = (const char*)sqlite3_column_text(pSql, 0); - const char *zGlob = (const char*)sqlite3_column_text(pSql, 1); - const char *zFrom = (const char*)sqlite3_column_text(pSql, 2); - const char *zTarget = (const char*)sqlite3_column_text(pSql, 3); - const char *zCI = (const char*)sqlite3_column_text(pSql, 4); - const char *zParent = (const char*)sqlite3_column_text(pSql, 5); - - if( zEQP==0 ) continue; - if( zGlob==0 ) continue; - rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0); - if( rc!=SQLITE_OK ) break; - if( SQLITE_ROW==sqlite3_step(pExplain) ){ - const char *zPlan = (const char*)sqlite3_column_text(pExplain, 3); - res = zPlan!=0 && ( 0==sqlite3_strglob(zGlob, zPlan) - || 0==sqlite3_strglob(zGlobIPK, zPlan)); - } - rc = sqlite3_finalize(pExplain); - if( rc!=SQLITE_OK ) break; - - if( res<0 ){ - raw_printf(stderr, "Error: internal error"); - break; - }else{ - if( bGroupByParent - && (bVerbose || res==0) - && (zPrev==0 || sqlite3_stricmp(zParent, zPrev)) - ){ - raw_printf(out, "-- Parent table %s\n", zParent); - sqlite3_free(zPrev); - zPrev = sqlite3_mprintf("%s", zParent); - } - - if( res==0 ){ - raw_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget); - }else if( bVerbose ){ - raw_printf(out, "%s/* no extra indexes required for %s -> %s */\n", - zIndent, zFrom, zTarget - ); - } - } - } - sqlite3_free(zPrev); - - if( rc!=SQLITE_OK ){ - raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); - } - - rc2 = sqlite3_finalize(pSql); - if( rc==SQLITE_OK && rc2!=SQLITE_OK ){ - rc = rc2; - raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); - } - }else{ - raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); - } - - return rc; -} - -/* -** Implementation of ".lint" dot command. -*/ -static int lintDotCommand( - ShellState *pState, /* Current shell tool state */ - char **azArg, /* Array of arguments passed to dot command */ - int nArg /* Number of entries in azArg[] */ -){ - int n; - n = (nArg>=2 ? strlen30(azArg[1]) : 0); - if( n<1 || sqlite3_strnicmp(azArg[1], "fkey-indexes", n) ) goto usage; - return lintFkeyIndexes(pState, azArg, nArg); - - usage: - raw_printf(stderr, "Usage %s sub-command ?switches...?\n", azArg[0]); - raw_printf(stderr, "Where sub-commands are:\n"); - raw_printf(stderr, " fkey-indexes\n"); - return SQLITE_ERROR; -} - -#if !defined SQLITE_OMIT_VIRTUALTABLE -static void shellPrepare( - sqlite3 *db, - int *pRc, - const char *zSql, - sqlite3_stmt **ppStmt -){ - *ppStmt = 0; - if( *pRc==SQLITE_OK ){ - int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0); - if( rc!=SQLITE_OK ){ - raw_printf(stderr, "sql error: %s (%d)\n", - sqlite3_errmsg(db), sqlite3_errcode(db) - ); - *pRc = rc; - } - } -} - -/* -** Create a prepared statement using printf-style arguments for the SQL. -** -** This routine is could be marked "static". But it is not always used, -** depending on compile-time options. By omitting the "static", we avoid -** nuisance compiler warnings about "defined but not used". -*/ -void shellPreparePrintf( - sqlite3 *db, - int *pRc, - sqlite3_stmt **ppStmt, - const char *zFmt, - ... +void shellPreparePrintf( + sqlite3 *db, + int *pRc, + sqlite3_stmt **ppStmt, + const char *zFmt, + ... ){ *ppStmt = 0; if( *pRc==SQLITE_OK ){ @@@@ -7877,74 -7646,71 -7645,71 +7932,74 @@@@ static int recoverSqlCb(void *pCtx, con return SQLITE_OK; } --/* --** This function is called to recover data from the database. A script --** to construct a new database containing all recovered data is output --** on stream pState->out. --*/ --static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ -- int rc = SQLITE_OK; -- const char *zRecoveryDb = ""; /* Name of "recovery" database. Debug only */ -- const char *zLAF = "lost_and_found"; -- int bFreelist = 1; /* 0 if --ignore-freelist is specified */ -- int bRowids = 1; /* 0 if --no-rowids */ -- sqlite3_recover *p = 0; -- int i = 0; ++#endif /* SQLITE_SHELL_HAVE_RECOVER */ -- for(i=1; iout, azArg[0]); -- return 1; ++#ifndef SQLITE_SHELL_FIDDLE ++static DotCmdRC ++writeDb( char *azArg[], int nArg, ShellExState *psx, char **pzErr ){ ++ int rc = 0; ++ const char *zDestFile = 0; ++ const char *zDb = 0; ++ sqlite3 *pDest; ++ sqlite3_backup *pBackup; ++ int j; ++ int bAsync = 0; ++ const char *zVfs = 0; ++ if( ISS(psx)->bSafeMode ) return DCR_AbortError; ++ for(j=1; jdb, "main", recoverSqlCb, (void*)pState -- ); -- -- sqlite3_recover_config(p, 789, (void*)zRecoveryDb); /* Debug use only */ -- sqlite3_recover_config(p, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF); -- sqlite3_recover_config(p, SQLITE_RECOVER_ROWIDS, (void*)&bRowids); -- sqlite3_recover_config(p, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist); -- -- sqlite3_recover_run(p); -- if( sqlite3_recover_errcode(p)!=SQLITE_OK ){ -- const char *zErr = sqlite3_recover_errmsg(p); -- int errCode = sqlite3_recover_errcode(p); -- raw_printf(stderr, "sql error: %s (%d)\n", zErr, errCode); ++ if( zDestFile==0 ){ ++ return DCR_Missing; } -- rc = sqlite3_recover_finish(p); -- return rc; -} -#endif /* SQLITE_SHELL_HAVE_RECOVER */ - ++ if( zDb==0 ) zDb = "main"; ++ rc = sqlite3_open_v2(zDestFile, &pDest, ++ SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs); ++ if( rc!=SQLITE_OK ){ ++ *pzErr = smprintf("cannot open \"%s\"\n", zDestFile); ++ close_db(pDest); ++ return DCR_Error; ++ } ++ if( bAsync ){ ++ sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;", ++ 0, 0, 0); ++ } ++ open_db(psx, 0); ++ pBackup = sqlite3_backup_init(pDest, "main", DBX(psx), zDb); ++ if( pBackup==0 ){ ++ *pzErr = smprintf("%s failed, %s\n", azArg[0], sqlite3_errmsg(pDest)); ++ close_db(pDest); ++ return DCR_Error; ++ } ++ while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){} ++ sqlite3_backup_finish(pBackup); ++ if( rc==SQLITE_DONE ){ ++ rc = 0; ++ }else{ ++ *pzErr = smprintf("%s\n", sqlite3_errmsg(pDest)); ++ rc = 1; ++ } ++ close_db(pDest); ++ return DCR_Ok|rc; +} - #endif /* SQLITE_SHELL_HAVE_RECOVER */ - ++#endif /* !defined(SQLITE_SHELL_FIDDLE) */ /* * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it. @@@@ -15155,6 -11426,6 -11425,6 +15210,7 @@@@ static int line_is_complete(char *zSql zSql[nSql] = ';'; zSql[nSql+1] = 0; rc = sqlite3_complete(zSql); +++ if( rc==SQLITE_NOMEM ) shell_out_of_memory(); zSql[nSql] = 0; return rc; } @@@@ -15178,519 -11447,193 -11446,193 +15234,520 @@@@ static int runOneSqlLine(ShellExState * char zPrefix[100]; const char *zErrorTail; const char *zErrorType; -- if( zErrMsg==0 ){ -- zErrorType = "Error"; -- zErrorTail = sqlite3_errmsg(p->db); -- }else if( cli_strncmp(zErrMsg, "in prepare, ",12)==0 ){ -- zErrorType = "Parse error"; -- zErrorTail = &zErrMsg[12]; -- }else if( cli_strncmp(zErrMsg, "stepping, ", 10)==0 ){ -- zErrorType = "Runtime error"; -- zErrorTail = &zErrMsg[10]; -- }else{ -- zErrorType = "Error"; -- zErrorTail = zErrMsg; -- } -- if( in!=0 || !stdin_is_interactive ){ -- sqlite3_snprintf(sizeof(zPrefix), zPrefix, -- "%s near line %d:", zErrorType, startline); -- }else{ -- sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType); ++ if( psx->shellAbruptExit==0 ){ ++ if( zErrMsg==0 ){ ++ zErrorType = "Error"; ++ zErrorTail = sqlite3_errmsg(DBX(psx)); ++ }else if( cli_strncmp(zErrMsg, "in prepare, ",12)==0 ){ ++ zErrorType = "Parse error"; ++ zErrorTail = &zErrMsg[12]; ++ }else if( cli_strncmp(zErrMsg, "stepping, ", 10)==0 ){ ++ zErrorType = "Runtime error"; ++ zErrorTail = &zErrMsg[10]; ++ }else{ ++ zErrorType = "Error"; ++ zErrorTail = zErrMsg; ++ } ++ if( bAltIn || !stdin_is_interactive ){ ++ sqlite3_snprintf(sizeof(zPrefix), zPrefix, ++ "%s near line %d:", zErrorType, startline); ++ }else{ ++ sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType); ++ } ++ utf8_printf(STD_ERR, "%s %s\n", zPrefix, zErrorTail); } -- utf8_printf(stderr, "%s %s\n", zPrefix, zErrorTail); sqlite3_free(zErrMsg); -- zErrMsg = 0; return 1; -- }else if( ShellHasFlag(p, SHFLG_CountChanges) ){ -- char zLineBuf[2000]; ++ }else if( ShellHasFlag(psx, SHFLG_CountChanges) ){ - char zLineBuf[2000]; +++ char zLineBuf[36+2*20]; sqlite3_snprintf(sizeof(zLineBuf), zLineBuf, "changes: %lld total_changes: %lld", -- sqlite3_changes64(p->db), sqlite3_total_changes64(p->db)); -- raw_printf(p->out, "%s\n", zLineBuf); ++ sqlite3_changes64(DBX(psx)), sqlite3_total_changes64(DBX(psx))); ++ raw_printf(ISS(psx)->out, "%s\n", zLineBuf); } 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; ++ /* 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) && sqlite3_complete(*pzLineUse)){ +++ 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 ){ ++ /* 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); } /* @@@@ -16039,6 -11937,6 -11936,6 +16096,12 @@@@ static char *cmdline_option_value(int a return argv[i]; } +++static void zapGlobalDbLock(void){ +++ if( pGlobalDbLock ){ +++ sqlite3_mutex_free(pGlobalDbLock); +++ pGlobalDbLock = 0; +++ } +++} static void sayAbnormalExit(void){ if( seenInterrupt ) fprintf(stderr, "Program interrupted.\n"); } @@@@ -16518,258 -12035,536 -12034,536 +16581,263 @@@@ int SQLITE_CDECL SHELL_MAIN(int argc, w 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); ++ 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( 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); #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); } -- for(i=0; i