]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
CLI much closer to being callable. (fewer exit() calls) ^C interrupt response improve...
authorlarrybr <larrybr@noemail.net>
Sat, 27 May 2023 00:03:43 +0000 (00:03 +0000)
committerlarrybr <larrybr@noemail.net>
Sat, 27 May 2023 00:03:43 +0000 (00:03 +0000)
FossilOrigin-Name: bff3a9a834a71871129e516c9a6d05fab92e004934fe0b99bccb9f3f1fd56e33

1  2 
configure
manifest
manifest.uuid
src/shell.c.in

diff --cc configure
Simple merge
diff --cc manifest
index db172f79a4e27e8b59abb25bc06a5274fc4004ac,be1e1a88d04b30cfb6f223455f451da32384843a..2ba48ebe5671ffac95963c2a2198226388c5cc8a
+++ b/manifest
@@@ -1,13 -1,13 +1,13 @@@
- C First\sserious\sstab\sat\sFIDDLE-build-ready.\sFix\sa\sflub\swith\sabsent\s-A\soption\sfor\ssome\sbuilds.
- D 2023-05-21T05:06:30.329
 -C Add\sability\sfor\sthe\sJS\sWorker1.exec()\sAPI\sto\sreport\sthe\snumber\sof\schanges\smade\sto\sthe\scaller,\sper\srequest\sin\s[forum:d0b19483642e20dd\s|\sforum\spost\sd0b19483642e20dd].
 -D 2023-05-25T16:49:06.244
++C CLI\smuch\scloser\sto\sbeing\scallable.\s(fewer\sexit()\scalls)\s^C\sinterrupt\sresponse\simproved.\sSync\sw/trunk.\sSome\sdiagnostic\scode\sremoved.\sSome\srefactoring\sfor\ssake\sof\sFIDDLE\sbuild.\sStreamline\smain(),\sby\smoving\scode\sto\smore\sdedicated\sroutines\sand\sotherwise.\sMake\s-quiet\smore\suseful.
++D 2023-05-27T00:03:43.927
  F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
  F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
  F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
 -F Makefile.in 764f2e3e8fb4ae1c8dfe03e65b2b3b01bd1fc57edf78ec2cab3a1301e90e1905
 +F Makefile.in d97bb86f0fbd38e2b71b3bd8089158c89d6b03ef575b72183348c0d7322c90b7
  F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
 -F Makefile.msc ce7bea7931e1ef96b0f44c0362074a8bb62e61a0e7cfdb05afebd928adaacc2b
 +F Makefile.msc ac5dfaf082eef506a9cfc7d5f2d95338282e76dad3e37567110362925f40cf41
  F README.md e05bd8fcb45da04ab045c37f79a98654e8aa3b3b8f302cfbba80a0d510df75f7
- F VERSION 17f95ae2fdf21f0e9575eb0b0511ea63f15d71dfff431b21c2b4adbfa70cfbbf
+ F VERSION c6366dc72582d3144ce87b013cc35fe48d62f6d07d5be0c9716ea33c862144aa
  F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
  F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2
  F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90
@@@ -33,8 -33,8 +33,8 @@@ F autoconf/tea/win/nmakehlp.c b01f822ea
  F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63
  F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6
  F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559
- F configure 5e52f3bce3b598d0c4ee6e6c92c180f35f9b29dfcc1775332bf87cf55302f9c3 x
 -F configure 9dc3300339f4d6b3c3b108de60cc6ae6b3c547e25c7e6df280b4775db4de3a1b x
 -F configure.ac 4654d32ac0a0d0b48f1e1e79bdc3d777b723cf2f63c33eb1d7c4ed8b435938e8
++F configure 697e926af786eb6ddb94ade8aace9f42814902d939ddc6a7bc5f11c3eca7dd51 x
 +F configure.ac 510be9293c7efca69c0cc7f427f223b0597f82dda214af7491887db25fa4e237
  F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad
  F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd
  F doc/json-enhancements.md e356fc834781f1f1aa22ee300027a270b2c960122468499bf347bb123ce1ea4f
@@@ -638,14 -634,11 +638,14 @@@ F src/pragma.h e690a356c18e98414d2e870e
  F src/prepare.c 6350675966bd0e7ac3a464af9dbfe26db6f0d4237f4e1f1acdb17b12ad371e6e
  F src/printf.c b9320cdbeca0b336c3f139fd36dd121e4167dd62b35fbe9ccaa9bab44c0af38d
  F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
 +F src/resmanage.c 3d8e80124e21723c2ef7027d42558c2f4ebc7b2a04c9ad26136561c17f2b4827
 +F src/resmanage.h 626dc03a581fdbd4a52fc15bcfb49da5dec1acc3be73ebd053ef9a34fa153834
  F src/resolve.c 3e53e02ce87c9582bd7e7d22f13f4094a271678d9dc72820fa257a2abb5e4032
  F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
- F src/select.c 738c3a3d6929f8be66c319bad17f6b297bd60a4eb14006075c48a28487dc7786
- F src/shell.c.in 2399be6f9e2e7270126aaffd1e8e47a21342bf590073b8a6a2e16ef8d76dd001
+ F src/select.c 899d5313955e7d187bbc01e32c1ba81d145f48e4e24d0f161d63af8c243b1511
 -F src/shell.c.in 29357bf9001c4dd977c2ce6cf55dcedd82f539c17654d15cd779fe429ceb169d
++F src/shell.c.in a10797809499499c192d5e95653ca36dbdb89bfbc512ae077d733b91a1053c7d
 +F src/shext_linkage.h 27dcf7624df05b2a7a6d367834339a6db3636f3035157f641f7db2ec499f8f6d
- F src/sqlite.h.in c14a4471fcd897a03631ac7ad3d05505e895e7b6419ec5b96cae9bc4df7a9fc6
+ F src/sqlite.h.in 8e0265e42d7695d86a0b8c7de9979e6c29edc2c616fa5eea11b682a6af838c7c
  F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
  F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4
  F src/sqliteInt.h a3ced8b9ebc573189c87b69f24bf10d2b9cd3cefefaae52623a2fa79e6fdd408
@@@ -2081,8 -2070,8 +2081,8 @@@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a9
  F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
  F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
  F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
- P b64ef7f92e7c369b279dac136983c69ab1e6fedc7f12a7dff65a86506761bce5
- R 2bc4de713d5e36abe65fec53d4e63406
 -P 80c7c0360c08bea0733deccb8071920c60558b75df76b6afad093c82adf30ea6
 -R e8fcc223cacf06d978c3812fcacaa2db
 -U stephan
 -Z 4c26ba95f98d5f81d88f07b72f4945f7
++P bfc36f0f10c37ae89bae6de96da38197998c8d95ab49397775d43d8a1ae7eeef 6e79505df915612b60696e4eec5c9973175fe6ecf273eb3152b996e63ae54a07
++R ec7c0e85ca7d865fd239eca8d8703f9f
 +U larrybr
- Z 28193c513ee8b55c220f72b52c0149d2
++Z 67d99d9d87525146ba5f4b40fdff84fe
  # Remove this line to create a well-formed Fossil manifest.
diff --cc manifest.uuid
index fd143b2db7abb29595d4a27418ca9c554531390a,f2df01c2ff50567aa9d114565b46eeaaba9e8e05..c88514feb730ed3d1202cd404aacb19ecc9eb0ec
@@@ -1,1 -1,1 +1,1 @@@
- bfc36f0f10c37ae89bae6de96da38197998c8d95ab49397775d43d8a1ae7eeef
 -6e79505df915612b60696e4eec5c9973175fe6ecf273eb3152b996e63ae54a07
++bff3a9a834a71871129e516c9a6d05fab92e004934fe0b99bccb9f3f1fd56e33
diff --cc src/shell.c.in
index c434240330c199a31ff7c0f0e1d82c9f1268dab8,8e9ccce7cc926bd67876fc9d728ea6b5b0b55702..185d2afdb40751e2088ee040dc4d3b3e29b66bc3
@@@ -9,15 -9,15 +9,14 @@@
  **    May you share freely, never taking more than you give.
  **
  *************************************************************************
--** This file contains code to implement the "sqlite" command line
--** utility for accessing SQLite databases.
++** This file contains code to implement the "sqlite" or "sqlitex"
++** command line utilities for accessing SQLite databases.
  */
++
  #if (defined(_WIN32) || defined(WIN32)) && !defined(_CRT_SECURE_NO_WARNINGS)
  /* This needs to come before any includes for MSVC compiler */
  #define _CRT_SECURE_NO_WARNINGS
  #endif
--typedef unsigned int u32;
--typedef unsigned short int u16;
  
  /*
  ** Optionally #include a user-defined header, whereby compilation options
@@@ -239,30 -239,6 +239,30 @@@ extern char *sqlite3_win32_utf8_to_mbcs
  extern LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText);
  #endif
  
- /* Get the shell extension interfaces and structs. */
++/* Get the shell extension interfaces and structs. (ShellExState ...) */
 +INCLUDE shext_linkage.h
 +
 +/* Forward declarations for use by resmanage. */
 +typedef struct ShellText ShellText;
 +static void freeText(ShellText *p);
 +#define SHELL_MANAGE_TEXT /* Cause text_holder() to be available. */
 +/* Get resource management package, supporting OOM and safe mode exits. */
 +INCLUDE resmanage.c
 +
 +/* For an embedded shell, allow the 3 standard streams to be specified.
 +** If used, these names will have to refer to something globally reachable
 +** from the same thread which called the shell's main().
 +**/
 +#ifndef STD_IN
 +# define STD_IN stdin
 +#endif
 +#ifndef STD_OUT
 +# define STD_OUT stdout
 +#endif
 +#ifndef STD_ERR
 +# define STD_ERR stderr
 +#endif
 +
  /* On Windows, we normally run with output mode of TEXT so that \n characters
  ** are automatically translated into \r\n.  However, this behavior needs
  ** to be disabled in some cases (ex: when generating CSV output and when
@@@ -506,39 -470,15 +506,33 @@@ static int console_utf8 = 0
  */
  static int stdout_is_console = 1;
  
- /*
- ** This statically allocated variable is used to strip a resource
- ** stack upon abrupt exits (involving OOM or -safe mode violations.
- */
- static ResourceMark main_resource_mark = 0;
- /* Avoid redundant atexit() registration. */
- static u8 atexit_registered = 0;
 +/*
 +** Disable certain features for FIDDLE build.
 +*/
 +#ifdef SQLITE_SHELL_FIDDLE
 +# undef SQLITE_OMIT_DYNAPROMPT
 +# undef SHELL_OMIT_LOAD_EXTENSION
 +# define SQLITE_OMIT_DYNAPROMPT 1
 +# define SHELL_OMIT_LOAD_EXTENSION 1
 +#endif
 +
  /*
  ** The following is the open SQLite database.  We make a pointer
  ** to this database a static variable so that it can be accessed
  ** by the SIGINT handler to interrupt database processing.
  */
--static sqlite3 *globalDb = 0;
++static volatile sqlite3 *globalDb = 0;
++/* Shorten voltatile cast-away for this. */
++#define GLOBAL_DB ((sqlite3 *)globalDb)
++
 +/*
- ** Mutex used to access *globalDb from main thread or ^C handler.
++** Mutex used to access *globalDb from main thread or ^C handler and to
++** guard against referenced DB race where it is closed and interrupted.
 +*/
 +static sqlite3_mutex *pGlobalDbLock = 0;
  
  /*
 -** True if an interrupt (Control-C) has been received.
 +** Greater than 0 if an interrupt (Control-C) has been received.
  */
  static volatile int seenInterrupt = 0;
  
@@@ -663,6 -595,6 +657,30 @@@ static void dynamicContinuePrompt(void)
  }
  #endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */
  
++#ifndef SHELL_NOT_CALLABLE
++/*
++** Provide an atexit() replacement for when shell is called by an application.
++*/
++typedef void (*terminate_callback_t)(void);
++#define MAX_TERM_CALLBACKS 4
++static terminate_callback_t terminate_callbacks[MAX_TERM_CALLBACKS];
++static int terminate_callback_count = 0;
++static void upon_terminate(terminate_callback_t tcb){
++  assert(terminate_callback_count < MAX_TERM_CALLBACKS);
++  if( terminate_callback_count<MAX_TERM_CALLBACKS ){
++    terminate_callbacks[terminate_callback_count++] = tcb;
++  }
++}
++static void terminate_actions(void){
++  while( terminate_callback_count>0 ){
++    (terminate_callbacks[--terminate_callback_count])();
++  }
++}
++#else
++# define upon_terminate(x) atexit(x)
++# define terminate_actions()
++#endif /* !defined(SHELL_NOT_CALLABLE) */
++
  #if SHELL_WIN_UTF8_OPT
  /* Following struct is used for -utf8 operation. */
  static struct ConsoleState {
@@@ -833,125 -761,17 +851,125 @@@ void utf8_printf(FILE *out, const char 
  # define raw_printf fprintf
  #endif
  
 -/* Indicate out-of-memory and exit. */
 -static void shell_out_of_memory(void){
 -  raw_printf(stderr,"Error: out of memory\n");
 +/*
 +** Provide a way for embedding apps to handle OOM or other shell-fatal
 +** conditions their way. A plain exit() call will undoubtedly leak
 +** memory and handles. The default scheme, when SHELL_TERMINATE is
 +** not defined, uses setjmp()/longjmp() execution stack ripping
 +** together with a resource stack which can be popped to release
 +** resources automatically. One replacement could be:
 +# define SHELL_TERMINATE(why) \
 +  fprintf(stderr, "Error: Terminating due to %s\n", why);\
    exit(1);
 +*/
 +
 +/*
 +** Provide an abrupt exit/termination for routines that cannot proceed
 +** or whose failure means the CLI cannot usefully continue.
 +*/
 +static void shell_terminate(const char *zWhy){
 +#ifndef SHELL_TERMINATE
 +  quit_moan(zWhy, 2);
 +#else
 +  release_holders_mark(exit_mark);
 +  SHELL_TERMINATE(zWhy);
 +#endif
 +}
 +
 +/* Indicate out-of-memory and terminate. */
 +static void shell_out_of_memory(void){
 +  shell_terminate("out of memory");
  }
  
 -/* Check a pointer to see if it is NULL.  If it is NULL, exit with an
 -** out-of-memory error.
 +#ifdef SQLITE_DEBUG
 +int fake_oom_countdown = 0;
 +
 +static void maybe_fake_oom(void){
 +  if( fake_oom_countdown>0 && --fake_oom_countdown==0 ){
 +    shell_out_of_memory();
 +  }
 +}
 +
 +/* The next 2 routines normally check for an OOM error. However, when
 +** fake_oom_countdown is non-zero, it is decremented and, upon reaching
 +** zero, a fake OOM condition is emulated. Additionally, (to keep leak
- ** detection working), the non-zero memory pointer is freed.
++** detection reporting useful), the non-zero memory pointer is freed.
 +*/
 +
 +/* Check a malloc()'ed (or equivalent) pointer to see if it is NULL.
 +** If so, terminate with an out-of-memory error.
 +*/
 +static void shell_check_oomm(const void *p){
 +  if( p==0 ) shell_out_of_memory();
 +  if( fake_oom_countdown>0 && --fake_oom_countdown==0 ){
 +    free((void*)p);
 +    shell_out_of_memory();
 +  }
 +}
 +/* Check a sqlite3_malloc()'ed (or equivalent) pointer to see if it is NULL.
 +** If so, terminate with an out-of-memory error.
  */
 -static void shell_check_oom(const void *p){
 +static void shell_check_ooms(const void *p){
    if( p==0 ) shell_out_of_memory();
 +  if( fake_oom_countdown>0 && --fake_oom_countdown==0 ){
 +    sqlite3_free((void*)p);
 +    shell_out_of_memory();
 +  }
 +}
 +#else
 +# define shell_check_ooms(p) do{ if((p)==0) shell_out_of_memory(); }while 0
 +# define shell_check_oomm(p) do{ if((p)==0) shell_out_of_memory(); }while 0
 +# define maybe_fake_oom()
 +#endif
 +
 +/* Check a SQLite result code for out-of-memory indication.
 +** If that is so, terminate with an out-of-memory error.
 +*/
 +static int shell_check_nomem(int rc){
 +  if( SQLITE_NOMEM==rc ) shell_out_of_memory();
 +  return rc;
 +}
 +/* Convenience functions using shell_check_nomem supporting OOM testing: */
 +static int s3_exec_noom(sqlite3 *db, const char *sql,
 +                        int (*callback)(void*,int,char**,char**), void *pvcb,
 +                        char **pzErr){
 +  int rc;
 +  char *zErrHere = 0;
 +  if( pzErr ) *pzErr = 0;
 +  maybe_fake_oom();
 +  rc = sqlite3_exec(db, sql, callback, pvcb, &zErrHere);
 +  if( rc==SQLITE_NOMEM ){
 +    sqlite3_free(zErrHere);
 +    shell_out_of_memory();
 +  }else{
 +    if( pzErr ) *pzErr = zErrHere;
 +  }
 +  return rc;
 +}
 +static int s3_step_noom(sqlite3_stmt *pstmt){
 +  maybe_fake_oom();
 +  return shell_check_nomem(sqlite3_step(pstmt));
 +}
 +static int s3_prepare_v2_noom(sqlite3 *db, const char *zSql, int nByte,
 +                              sqlite3_stmt **ppStmt, const char **pzTail){
 +  maybe_fake_oom();
 +  return shell_check_nomem(sqlite3_prepare_v2(db,zSql,nByte,ppStmt,pzTail));
 +}
 +
 +/* Shorten a sqlite3_prepare_v2() usage pattern common in this code:
 +** Build a query string; OOM-check it; prepare; free and mark the string.
 +** There is no length or pzTail argument -- (useless in this context.)
 +** On return (if any), *pzSql will have been set to 0. */
 +static int s3_prep_noom_free(sqlite3 *db, char **pzSql, sqlite3_stmt **ppStmt){
 +  int rc;
 +  sstr_ptr_holder(pzSql);
 +  maybe_fake_oom();
 +  shell_check_ooms(*pzSql);
 +  rc = sqlite3_prepare_v2(db,*pzSql,-1,ppStmt,0);
 +  shell_check_nomem(rc);
 +  release_holder();
 +  *pzSql = 0;
 +  return rc;
  }
  
  /*
@@@ -1094,128 -914,10 +1112,128 @@@ static FILE * openChrSource(const char 
  }
  
  /*
 -** This routine reads a line of text from FILE in, stores
 -** the text in memory obtained from malloc() and returns a pointer
 -** to the text.  NULL is returned at end of file, or if malloc()
 -** fails.
 +** Arrange for shell input from either a FILE or a string.
 +** For applicable invariants, see strLineGet(...) which is
 +** the only modifier of this struct. (4rd and 5th members)
 +** All other members are simply initialized to select the
 +** input stream and track input sources, set by whatever
 +** routines redirect the input.
 +*/
 +typedef struct InSource {
 +  struct InSource *pFrom; /* Aid redirect tracking and auto-unnesting.   */
 +  FILE *inFile;     /* Will be 0 when input is to be taken from string.  */
 +  char *zStrIn;     /* Will be 0 when no input is available from string. */
 +  int iReadOffset;  /* Offset into zStrIn where next "read" to be done   */
 +  int lineno;       /* How many lines have been read from this source    */
 +  const char *zSourceSay; /* For complaints, a name kept for this source */
 +  union {
 +    int (*stream)(FILE *);
 +    void (*ptext)(char *);
 +  } closer;  /* Closer for the file or freer for the text, set by opener */
 +} InSource;
- #define INSOURCE_STR_REDIR(str, tagTo, isFrom) {isFrom, 0, str, 0, 0, tagTo }
- #define INSOURCE_FILE_REDIR(fh, tagTo, isFrom) {isFrom, fh,  0, 0, 0, tagTo }
++#define INSOURCE_STR_REDIR(str, tagTo, isFrom) {isFrom, 0, str, 0, 0, tagTo, 0 }
++#define INSOURCE_FILE_REDIR(fh, tagTo, isFrom) {isFrom, fh,  0, 0, 0, tagTo, 0 }
 +#define INSOURCE_IS_INVOKEARG(pIS) \
 +  ((pIS)==&cmdInSource)
 +#ifndef SQLITE_SHELL_FIDDLE
 +# define INSOURCE_IS_INTERACTIVE(pIS) \
 +   ((pIS)==&termInSource && stdin_is_interactive )
 +#else
 +# define INSOURCE_IS_INTERACTIVE(pIS) 0
 +#endif
 +
 +/* These instances' addresses are taken as part of interactive input test
 + * or test for other special handling as a command-line argument. */
 +static InSource cmdInSource = { 0, 0, 0, 0, 0, "<cmdLine>", 0 };
 +#ifndef SQLITE_SHELL_FIDDLE
 +static InSource termInSource = { 0, 0, 0, 0, 0, "<terminal>", 0 };
 +static InSource stdInSource = { 0, 0, 0, 0, 0, "<stdin>", 0 };
 +#else
 +static InSource fiddleInSource = { 0, 0, 0, 0, 0, "<fiddle>", 0 };
 +#endif /* defined(SQLITE_SHELL_FIDDLE) */
 +
 +/* Initializer for just above 3 (non-FIDDLE) InSource objects */
 +#ifndef SQLITE_SHELL_FIDDLE
 +static void init_std_inputs(FILE *pIn ){
 +  termInSource.inFile = pIn;
 +  stdInSource.inFile = pIn;
 +  cmdInSource.lineno = 0;
 +}
 +#else
 +# define init_std_inputs(x)
 +#endif
 +/* Setup cmdInSource to supply given text as input. */
 +static void set_invocation_cmd(char *zDo){
 +  cmdInSource.iReadOffset = 0;
 +  cmdInSource.zStrIn = zDo;
 +  ++cmdInSource.lineno;
 +}
 +#ifdef SQLITE_SHELL_FIDDLE
 +static void set_fiddle_input_text(char *zDo){
 +  fiddleInSource.iReadOffset = 0;
 +  fiddleInSource.zStrIn = zDo;
 +  if( zDo ) ++fiddleInSource.lineno;
 +}
 +#endif /* defined(SQLITE_SHELL_FIDDLE) */
 +
 +/* Close an InSource object and unlink it from redirect nesting. */
 +static void finish_InSource( InSource **ppIS ){
 +  if( ppIS!=0 && *ppIS!=0 ){
 +    InSource *pIS = *ppIS;
 +    if( pIS->closer.stream != 0 ){
 +      if( pIS->inFile != 0 ){
 +        (pIS->closer.stream)(pIS->inFile);
 +        pIS->inFile = 0;
 +      }else if( pIS->zStrIn != 0 ){
 +        (pIS->closer.ptext)(pIS->zStrIn);
 +        pIS->zStrIn = 0;
 +      }
 +    }
 +    *ppIS = pIS->pFrom;
 +    pIS->pFrom = 0;
 +  }
 +}
 +
 +/* Similar to fgets(buffer, limit, file) but for InSource holding a string. */
 +static char *strLineGet(char *zBuf, int ncMax, InSource *pInSrc){
 +  if( pInSrc->inFile!=0 ){
 +    char *zRet = fgets(zBuf, ncMax, pInSrc->inFile );
 +    if( zRet!=0 ){
 +      int iRead = strlen30(zRet);
 +      if( iRead>0 && zRet[iRead-1]=='\n' ) ++pInSrc->lineno;
 +      /* Consider: record line length to avoid rescan for it. */
 +      return zRet;
 +    }
 +  }
 +  else if( pInSrc->zStrIn!=0 ){
 +    char *zBegin = pInSrc->zStrIn + pInSrc->iReadOffset;
 +    if( *zBegin!=0 ){
 +      int iTake = 0;
 +      char c;
 +      ncMax -= 1;
 +      while( iTake<ncMax && (c=zBegin[iTake])!=0 ){
 +        zBuf[iTake++] = c;
 +        if( c=='\n' ){
 +          ++pInSrc->lineno;
 +          break;
 +        }
 +      }
 +      if( ncMax>=0 ) zBuf[iTake] = 0;
 +      pInSrc->iReadOffset += iTake;
 +      /* Consider: record line length to avoid rescan for it. */
 +      return zBuf;
 +    }
 +  }
 +  return 0;
 +}
 +
 +/*
 +** This routine reads a line of text from designated stream source,
 +** stores the text in memory obtained from malloc() and returns a
 +** pointer to the text.  NULL is returned at end of file.
 +** There will be no return if malloc() fails.
 +**
 +** The trailing newline (or other line-end chars) are stripped.
  **
  ** If zLine is not NULL then it is a malloced buffer returned from
  ** a previous call to this routine that may be reused.
@@@ -1275,73 -973,55 +1293,76 @@@ static char *local_getline(char *zLine
  }
  
  /*
 -** Retrieve a single line of input text.
 +** Retrieve a single line of input text from designated input source.
  **
 -** If in==0 then read from standard input and prompt before each line.
 -** If isContinuation is true, then a continuation prompt is appropriate.
 -** If isContinuation is zero, then the main prompt should be used.
 +** If the input is interactive, then issue either the continuation prompt
 +** or the main prompt, per isContinuation, before reading a line from
 +** standard input. Otherwise, just read a line from the specified source.
  **
  ** If zPrior is not NULL then it is a buffer from a prior call to this
--** routine that can be reused.
++** routine that can be reused. If not used, it must be passed to free().
  **
  ** The result is stored in space obtained from malloc() and must either
 -** be freed by the caller or else passed back into this routine via the
 -** zPrior argument for reuse.
 +** be freed by the caller, using the same allocator[a], or else passed
 +** back into this routine via the zPrior argument for reuse.
 +**
 +** [a. This function is exposed for use by shell extensions which may
 +**   have no access to "the same allocator". This is why the function
 +**   following this one exists, also exposed to shell extensions. ]
 +**
 +** If this function is called until it returns NULL, and the prior return
 +** has been passed in for resuse, then the caller need/must not free it.
 +** Otherwise, (in case of an early termination of reading from the given
 +** input), the caller is responsible for freeing a prior, non-NULL return.
 +**
 +** The trailing newline (or its ilk), if any, is trimmed.
 +** The input line number is adjusted (via delegation or directly.)
  */
 -#ifndef SQLITE_SHELL_FIDDLE
 -static char *one_input_line(FILE *in, char *zPrior, int isContinuation){
 -  char *zPrompt;
 -  char *zResult;
 -  if( in!=0 ){
 -    zResult = local_getline(zPrior, in);
 +static char *one_input_line(InSource *pInSrc, char *zPrior,
 +                            int isContinuation, Prompts *pCue){
 +  if( !INSOURCE_IS_INTERACTIVE(pInSrc) ){
 +    return local_getline(zPrior, pInSrc);
    }else{
 -    zPrompt = isContinuation ? CONTINUATION_PROMPT : mainPrompt;
 +    static Prompts cueDefault = { "$ ","> " };
 +    const char *zPrompt;
 +    char *zResult;
 +    if( pCue==0 ) pCue = &cueDefault;
 +    zPrompt = isContinuation ? pCue->zContinue : pCue->zMain;
  #if SHELL_USE_LOCAL_GETLINE
      printf("%s", zPrompt);
      fflush(stdout);
      do{
 -      zResult = local_getline(zPrior, stdin);
 +      zResult = local_getline(zPrior, pInSrc);
        zPrior = 0;
        /* ^C trap creates a false EOF, so let "interrupt" thread catch up. */
--      if( zResult==0 ) sqlite3_sleep(50);
++      if( zResult==0 ){
++        sqlite3_sleep(150);
++      }
      }while( zResult==0 && seenInterrupt>0 );
  #else
      free(zPrior);
--    zResult = shell_readline(zPrompt);
--    while( zResult==0 ){
++    while( (zResult = shell_readline(zPrompt))==0 ){
        /* ^C trap creates a false EOF, so let "interrupt" thread catch up. */
--      sqlite3_sleep(50);
++      sqlite3_sleep(150);
        if( seenInterrupt==0 ) break;
--      zResult = shell_readline("");
++      zPrompt = "\n";
++    }
++    if( zResult ){
++      if( *zResult ) shell_add_history(zResult);
++      ++pInSrc->lineno;
      }
--    if( zResult && *zResult ) shell_add_history(zResult);
-     ++pInSrc->lineno;
  #endif
 +    return zResult;
    }
 -  return zResult;
  }
 -#endif /* !SQLITE_SHELL_FIDDLE */
 +
 +/* For use by shell extensions. See footnote [a] to one_input_line(). */
 +void free_input_line(char *z){
 +  free(z);
 +}
  
  /*
 -** Return the value of a hexadecimal digit.  Return -1 if the input
 -** is not a hex digit.
 +** Return the value of a hexadecimal digit, or -1 if c is not a hex digit.
  */
  static int hexDigitValue(char c){
    if( c>='0' && c<='9' ) return c - '0';
@@@ -2775,35 -2116,17 +2796,45 @@@ static void output_csv(ShellExState *ps
      }
    }
    if( bSep ){
 -    utf8_printf(p->out, "%s", p->colSeparator);
 +    utf8_printf(out, "%s", zColSep);
 +  }
 +}
 +
++/*
++** Interrupt running VDBE (if any). The mutex on globalDb is taken to ensure
++** the active DB is not closed as this interrupt is done. Return SQLITE_OK
++** if able to do so (or no DB is active), else SQLITE_ERROR.
++*/
++static int active_db_interrupt(void){
++  int itry = 0;
++  if( !globalDb ) return SQLITE_OK;
++  while( itry < 10 ){
++    if( sqlite3_mutex_try(pGlobalDbLock)==SQLITE_OK ){
++      if( globalDb ) sqlite3_interrupt(GLOBAL_DB);
++      sqlite3_mutex_leave(pGlobalDbLock);
++      return SQLITE_OK;
++    }else{
++      sqlite3_sleep(50);
++      ++itry;
++    }
+   }
++  return SQLITE_ERROR;
+ }
  /*
  ** This routine runs when the user presses Ctrl-C
- ** TBD: It will need some changes for embedability.
++** This may need some changes for embedability, perhaps a client-
++** defined action when/if DB operation becomes uninterruptable.
  */
  static void interrupt_handler(int NotUsed){
    UNUSED_PARAMETER(NotUsed);
-   if( ++seenInterrupt>1 ){
-     exit(1);
-   }
-   if( globalDb ){
-     int itry = 0;
-     while( itry < 10 ){
-       if( sqlite3_mutex_try(pGlobalDbLock)==SQLITE_OK ){
-         sqlite3_interrupt(globalDb);
-         sqlite3_mutex_leave(pGlobalDbLock);
-         return;
-       }else{
-         sqlite3_sleep(50);
-         ++itry;
-       }
-     }
 -  if( ++seenInterrupt>1 ) exit(1);
 -  if( globalDb ) sqlite3_interrupt(globalDb);
++  if( ++seenInterrupt>1 || active_db_interrupt()!=SQLITE_OK ){
 +    /* Apparently, no polite interruption is working. (Something is
-     ** very busy under mutex protection.) So quit altother. */
++    ** very busy under mutex protection.) So quit altogether. */
++    utf8_printf(STD_ERR, "Forced ^C exit.\n");
++    terminate_actions();
 +    exit(1);
 +  }
  }
  
  #if (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE)
@@@ -5618,2049 -4684,990 +5649,2050 @@@ static int run_schema_dump_query
    return rc;
  }
  
 +/* Configure help text generation to have coalesced secondary help lines
 + * with trailing newlines on all help lines. This allow help text to be
 + * representable as an array of two C-strings per dot-command.
 + */
 +DISPATCH_CONFIG[
 +  HELP_COALESCE=1
 +];
 +#define HELP_TEXT_FMTP ".%s"
 +#define HELP_TEXT_FMTS "%s"
 +/* Above HELP_COALESCE config and HELP_TEXT_FMT PP vars must track.
 + * Alternative is 0, ".%s\n" and "%s\n" .
 + */
 +
 +/* Forward references */
 +static int showHelp(FILE *out, const char *zPattern, ShellExState *);
 +static DotCmdRC process_input(ShellInState *psx);
 +static DotCommand *builtInCommand(int ix);
 +
  /*
 -** Text of help messages.
 +** Read the content of file zName into memory obtained from sqlite3_malloc64()
 +** and return a pointer to the buffer. The caller is responsible for freeing
 +** the memory.
 +**
 +** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes
 +** read.
 +**
 +** For convenience, a nul-terminator byte is always appended to the data read
 +** from the file before the buffer is returned. This byte is not included in
 +** the final value of (*pnByte), if applicable.
  **
 -** The help text for each individual command begins with a line that starts
 -** with ".".  Subsequent lines are supplemental information.
 +** NULL is returned if any error is encountered. The final value of *pnByte
 +** is undefined in this case.
  **
 -** There must be two or more spaces between the end of the command and the
 -** start of the description of what that command does.
 +** This function always returns; no abrubt OOM exits are taken.
  */
 -static const char *(azHelp[]) = {
 -#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) \
 -  && !defined(SQLITE_SHELL_FIDDLE)
 -  ".archive ...             Manage SQL archives",
 -  "   Each command must have exactly one of the following options:",
 -  "     -c, --create               Create a new archive",
 -  "     -u, --update               Add or update files with changed mtime",
 -  "     -i, --insert               Like -u but always add even if unchanged",
 -  "     -r, --remove               Remove files from archive",
 -  "     -t, --list                 List contents of archive",
 -  "     -x, --extract              Extract files from archive",
 -  "   Optional arguments:",
 -  "     -v, --verbose              Print each filename as it is processed",
 -  "     -f FILE, --file FILE       Use archive FILE (default is current db)",
 -  "     -a FILE, --append FILE     Open FILE using the apndvfs VFS",
 -  "     -C DIR, --directory DIR    Read/extract files from directory DIR",
 -  "     -g, --glob                 Use glob matching for names in archive",
 -  "     -n, --dryrun               Show the SQL that would have occurred",
 -  "   Examples:",
 -  "     .ar -cf ARCHIVE foo bar  # Create ARCHIVE from files foo and bar",
 -  "     .ar -tf ARCHIVE          # List members of ARCHIVE",
 -  "     .ar -xvf ARCHIVE         # Verbosely extract files from ARCHIVE",
 -  "   See also:",
 -  "      http://sqlite.org/cli.html#sqlite_archive_support",
 -#endif
 -#ifndef SQLITE_OMIT_AUTHORIZATION
 -  ".auth ON|OFF             Show authorizer callbacks",
 -#endif
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".backup ?DB? FILE        Backup DB (default \"main\") to FILE",
 -  "   Options:",
 -  "       --append            Use the appendvfs",
 -  "       --async             Write to FILE without journal and fsync()",
 -#endif
 -  ".bail on|off             Stop after hitting an error.  Default OFF",
 -  ".binary on|off           Turn binary output on or off.  Default OFF",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".cd DIRECTORY            Change the working directory to DIRECTORY",
 -#endif
 -  ".changes on|off          Show number of rows changed by SQL",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".check GLOB              Fail if output since .testcase does not match",
 -  ".clone NEWDB             Clone data into NEWDB from the existing database",
 -#endif
 -  ".connection [close] [#]  Open or close an auxiliary database connection",
 -  ".databases               List names and files of attached databases",
 -  ".dbconfig ?op? ?val?     List or change sqlite3_db_config() options",
 -#if SQLITE_SHELL_HAVE_RECOVER
 -  ".dbinfo ?DB?             Show status information about the database",
 -#endif
 -  ".dump ?OBJECTS?          Render database content as SQL",
 -  "   Options:",
 -  "     --data-only            Output only INSERT statements",
 -  "     --newlines             Allow unescaped newline characters in output",
 -  "     --nosys                Omit system tables (ex: \"sqlite_stat1\")",
 -  "     --preserve-rowids      Include ROWID values in the output",
 -  "   OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump",
 -  "   Additional LIKE patterns can be given in subsequent arguments",
 -  ".echo on|off             Turn command echo on or off",
 -  ".eqp on|off|full|...     Enable or disable automatic EXPLAIN QUERY PLAN",
 -  "   Other Modes:",
 -#ifdef SQLITE_DEBUG
 -  "      test                  Show raw EXPLAIN QUERY PLAN output",
 -  "      trace                 Like \"full\" but enable \"PRAGMA vdbe_trace\"",
 -#endif
 -  "      trigger               Like \"full\" but also show trigger bytecode",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".excel                   Display the output of next command in spreadsheet",
 -  "   --bom                   Put a UTF8 byte-order mark on intermediate file",
 -#endif
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".exit ?CODE?             Exit this program with return-code CODE",
 -#endif
 -  ".expert                  EXPERIMENTAL. Suggest indexes for queries",
 -  ".explain ?on|off|auto?   Change the EXPLAIN formatting mode.  Default: auto",
 -  ".filectrl CMD ...        Run various sqlite3_file_control() operations",
 -  "   --schema SCHEMA         Use SCHEMA instead of \"main\"",
 -  "   --help                  Show CMD details",
 -  ".fullschema ?--indent?   Show schema and the content of sqlite_stat tables",
 -  ".headers on|off          Turn display of headers on or off",
 -  ".help ?-all? ?PATTERN?   Show help text for PATTERN",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".import FILE TABLE       Import data from FILE into TABLE",
 -  "   Options:",
 -  "     --ascii               Use \\037 and \\036 as column and row separators",
 -  "     --csv                 Use , and \\n as column and row separators",
 -  "     --skip N              Skip the first N rows of input",
 -  "     --schema S            Target table to be S.TABLE",
 -  "     -v                    \"Verbose\" - increase auxiliary output",
 -  "   Notes:",
 -  "     *  If TABLE does not exist, it is created.  The first row of input",
 -  "        determines the column names.",
 -  "     *  If neither --csv or --ascii are used, the input mode is derived",
 -  "        from the \".mode\" output mode",
 -  "     *  If FILE begins with \"|\" then it is a command that generates the",
 -  "        input text.",
 -#endif
 -#ifndef SQLITE_OMIT_TEST_CONTROL
 -  ",imposter INDEX TABLE    Create imposter table TABLE on index INDEX",
 -#endif
 -  ".indexes ?TABLE?         Show names of indexes",
 -  "                           If TABLE is specified, only show indexes for",
 -  "                           tables matching TABLE using the LIKE operator.",
 -#ifdef SQLITE_ENABLE_IOTRACE
 -  ",iotrace FILE            Enable I/O diagnostic logging to FILE",
 -#endif
 -  ".limit ?LIMIT? ?VAL?     Display or change the value of an SQLITE_LIMIT",
 -  ".lint OPTIONS            Report potential schema issues.",
 -  "     Options:",
 -  "        fkey-indexes     Find missing foreign key indexes",
 -#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE)
 -  ".load FILE ?ENTRY?       Load an extension library",
 +static char *readFile(const char *zName, int *pnByte){
 +  FILE *in = fopen(zName, "rb");
 +  long nIn;
 +  size_t nRead;
 +  char *pBuf;
 +  int rc;
 +  if( in==0 ) return 0;
 +  rc = fseek(in, 0, SEEK_END);
 +  if( rc!=0 ){
 +    raw_printf(stderr, "Error: '%s' not seekable\n", zName);
 +    fclose(in);
 +    return 0;
 +  }
 +  nIn = ftell(in);
 +  rewind(in);
 +  pBuf = sqlite3_malloc64( nIn+1 );
 +  if( pBuf==0 ){
 +    raw_printf(stderr, "Error: out of memory\n");
 +    fclose(in);
 +    return 0;
 +  }
 +  nRead = fread(pBuf, nIn, 1, in);
 +  fclose(in);
 +  if( nRead!=1 ){
 +    sqlite3_free(pBuf);
 +    raw_printf(stderr, "Error: cannot read '%s'\n", zName);
 +    return 0;
 +  }
 +  pBuf[nIn] = 0;
 +  if( pnByte ) *pnByte = nIn;
 +  return pBuf;
 +}
 +
 +#if defined(SQLITE_ENABLE_SESSION)
 +/*
 +** Close a single OpenSession object and release all of its associated
 +** resources.
 +*/
 +static void session_close(OpenSession *pSession){
 +  int i;
 +  sqlite3session_delete(pSession->p);
 +  sqlite3_free(pSession->zName);
 +  for(i=0; i<pSession->nFilter; i++){
 +    sqlite3_free(pSession->azFilter[i]);
 +  }
 +  sqlite3_free(pSession->azFilter);
 +  memset(pSession, 0, sizeof(OpenSession));
 +}
  #endif
 -#if !defined(SQLITE_SHELL_FIDDLE)
 -  ".log FILE|on|off         Turn logging on or off.  FILE can be stderr/stdout",
 +
 +/*
 +** Close all OpenSession objects and release all associated resources.
 +*/
 +#if defined(SQLITE_ENABLE_SESSION)
 +static void session_close_all(ShellInState *psi, int i){
 +  int j;
 +  struct AuxDb *pAuxDb = i<0 ? psi->pAuxDb : &psi->aAuxDb[i];
 +  for(j=0; j<pAuxDb->nSession; j++){
 +    session_close(&pAuxDb->aSession[j]);
 +  }
 +  pAuxDb->nSession = 0;
 +}
  #else
 -  ".log on|off              Turn logging on or off.",
 -#endif
 -  ".mode MODE ?OPTIONS?     Set output mode",
 -  "   MODE is one of:",
 -  "     ascii       Columns/rows delimited by 0x1F and 0x1E",
 -  "     box         Tables using unicode box-drawing characters",
 -  "     csv         Comma-separated values",
 -  "     column      Output in columns.  (See .width)",
 -  "     html        HTML <table> code",
 -  "     insert      SQL insert statements for TABLE",
 -  "     json        Results in a JSON array",
 -  "     line        One value per line",
 -  "     list        Values delimited by \"|\"",
 -  "     markdown    Markdown table format",
 -  "     qbox        Shorthand for \"box --wrap 60 --quote\"",
 -  "     quote       Escape answers as for SQL",
 -  "     table       ASCII-art table",
 -  "     tabs        Tab-separated values",
 -  "     tcl         TCL list elements",
 -  "   OPTIONS: (for columnar modes or insert mode):",
 -  "     --wrap N       Wrap output lines to no longer than N characters",
 -  "     --wordwrap B   Wrap or not at word boundaries per B (on/off)",
 -  "     --ww           Shorthand for \"--wordwrap 1\"",
 -  "     --quote        Quote output text as SQL literals",
 -  "     --noquote      Do not quote output text",
 -  "     TABLE          The name of SQL table used for \"insert\" mode",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".nonce STRING            Suspend safe mode for one command if nonce matches",
 -#endif
 -  ".nullvalue STRING        Use STRING in place of NULL values",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".once ?OPTIONS? ?FILE?   Output for the next SQL command only to FILE",
 -  "     If FILE begins with '|' then open as a pipe",
 -  "       --bom  Put a UTF8 byte-order mark at the beginning",
 -  "       -e     Send output to the system text editor",
 -  "       -x     Send output as CSV to a spreadsheet (same as \".excel\")",
 -  /* Note that .open is (partially) available in WASM builds but is
 -  ** currently only intended to be used by the fiddle tool, not
 -  ** end users, so is "undocumented." */
 -  ".open ?OPTIONS? ?FILE?   Close existing database and reopen FILE",
 -  "     Options:",
 -  "        --append        Use appendvfs to append database to the end of FILE",
 -#endif
 -#ifndef SQLITE_OMIT_DESERIALIZE
 -  "        --deserialize   Load into memory using sqlite3_deserialize()",
 -  "        --hexdb         Load the output of \"dbtotxt\" as an in-memory db",
 -  "        --maxsize N     Maximum size for --hexdb or --deserialized database",
 -#endif
 -  "        --new           Initialize FILE to an empty database",
 -  "        --nofollow      Do not follow symbolic links",
 -  "        --readonly      Open FILE readonly",
 -  "        --zip           FILE is a ZIP archive",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".output ?FILE?           Send output to FILE or stdout if FILE is omitted",
 -  "   If FILE begins with '|' then open it as a pipe.",
 -  "   Options:",
 -  "     --bom                 Prefix output with a UTF8 byte-order mark",
 -  "     -e                    Send output to the system text editor",
 -  "     -x                    Send output as CSV to a spreadsheet",
 -#endif
 -  ".parameter CMD ...       Manage SQL parameter bindings",
 -  "   clear                   Erase all bindings",
 -  "   init                    Initialize the TEMP table that holds bindings",
 -  "   list                    List the current parameter bindings",
 -  "   set PARAMETER VALUE     Given SQL parameter PARAMETER a value of VALUE",
 -  "                           PARAMETER should start with one of: $ : @ ?",
 -  "   unset PARAMETER         Remove PARAMETER from the binding table",
 -  ".print STRING...         Print literal STRING",
 -#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
 -  ".progress N              Invoke progress handler after every N opcodes",
 -  "   --limit N                 Interrupt after N progress callbacks",
 -  "   --once                    Do no more than one progress interrupt",
 -  "   --quiet|-q                No output except at interrupts",
 -  "   --reset                   Reset the count for each input and interrupt",
 -#endif
 -  ".prompt MAIN CONTINUE    Replace the standard prompts",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".quit                    Stop interpreting input stream, exit if primary.",
 -  ".read FILE               Read input from FILE or command output",
 -  "    If FILE begins with \"|\", it is a command that generates the input.",
 -#endif
 -#if SQLITE_SHELL_HAVE_RECOVER
 -  ".recover                 Recover as much data as possible from corrupt db.",
 -  "   --ignore-freelist        Ignore pages that appear to be on db freelist",
 -  "   --lost-and-found TABLE   Alternative name for the lost-and-found table",
 -  "   --no-rowids              Do not attempt to recover rowid values",
 -  "                            that are not also INTEGER PRIMARY KEYs",
 -#endif
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".restore ?DB? FILE       Restore content of DB (default \"main\") from FILE",
 -  ".save ?OPTIONS? FILE     Write database to FILE (an alias for .backup ...)",
 +# define session_close_all(X,Y)
  #endif
 -  ".scanstats on|off|est    Turn sqlite3_stmt_scanstatus() metrics on or off",
 -  ".schema ?PATTERN?        Show the CREATE statements matching PATTERN",
 -  "   Options:",
 -  "      --indent             Try to pretty-print the schema",
 -  "      --nosys              Omit objects whose names start with \"sqlite_\"",
 -  ",selftest ?OPTIONS?      Run tests defined in the SELFTEST table",
 -  "    Options:",
 -  "       --init               Create a new SELFTEST table",
 -  "       -v                   Verbose output",
 -  ".separator COL ?ROW?     Change the column and row separators",
 +
 +/*
 +** Implementation of the xFilter function for an open session.  Omit
 +** any tables named by ".session filter" but let all other table through.
 +*/
  #if defined(SQLITE_ENABLE_SESSION)
 -  ".session ?NAME? CMD ...  Create or control sessions",
 -  "   Subcommands:",
 -  "     attach TABLE             Attach TABLE",
 -  "     changeset FILE           Write a changeset into FILE",
 -  "     close                    Close one session",
 -  "     enable ?BOOLEAN?         Set or query the enable bit",
 -  "     filter GLOB...           Reject tables matching GLOBs",
 -  "     indirect ?BOOLEAN?       Mark or query the indirect status",
 -  "     isempty                  Query whether the session is empty",
 -  "     list                     List currently open session names",
 -  "     open DB NAME             Open a new session on DB",
 -  "     patchset FILE            Write a patchset into FILE",
 -  "   If ?NAME? is omitted, the first defined session is used.",
 -#endif
 -  ".sha3sum ...             Compute a SHA3 hash of database content",
 -  "    Options:",
 -  "      --schema              Also hash the sqlite_schema table",
 -  "      --sha3-224            Use the sha3-224 algorithm",
 -  "      --sha3-256            Use the sha3-256 algorithm (default)",
 -  "      --sha3-384            Use the sha3-384 algorithm",
 -  "      --sha3-512            Use the sha3-512 algorithm",
 -  "    Any other argument is a LIKE pattern for tables to hash",
 -#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
 -  ".shell CMD ARGS...       Run CMD ARGS... in a system shell",
 -#endif
 -  ".show                    Show the current values for various settings",
 -  ".stats ?ARG?             Show stats or turn stats on or off",
 -  "   off                      Turn off automatic stat display",
 -  "   on                       Turn on automatic stat display",
 -  "   stmt                     Show statement stats",
 -  "   vmstep                   Show the virtual machine step count only",
 -#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
 -  ".system CMD ARGS...      Run CMD ARGS... in a system shell",
 +static int session_filter(void *pCtx, const char *zTab){
 +  OpenSession *pSession = (OpenSession*)pCtx;
 +  int i;
 +  for(i=0; i<pSession->nFilter; i++){
 +    if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0;
 +  }
 +  return 1;
 +}
  #endif
 -  ".tables ?TABLE?          List names of tables matching LIKE pattern TABLE",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ",testcase NAME           Begin redirecting output to 'testcase-out.txt'",
 +
 +#if SHELL_DYNAMIC_EXTENSION
 +static int notify_subscribers(ShellInState *psi, NoticeKind nk, void *pvs) {
 +  int six = 0;
 +  int rcFlags = 0;
 +  ShellExState *psx = XSS(psi);
 +  while( six < psi->numSubscriptions ){
 +    struct EventSubscription *pes = psi->pSubscriptions + six++;
 +    rcFlags |= pes->eventHandler(pes->pvUserData, nk, pvs, psx);
 +  }
 +  return rcFlags;
 +}
  #endif
 -  ",testctrl CMD ...        Run various sqlite3_test_control() operations",
 -  "                           Run \".testctrl\" with no arguments for details",
 -  ".timeout MS              Try opening locked tables for MS milliseconds",
 -  ".timer on|off            Turn SQL timer on or off",
 -#ifndef SQLITE_OMIT_TRACE
 -  ".trace ?OPTIONS?         Output each SQL statement as it is run",
 -  "    FILE                    Send output to FILE",
 -  "    stdout                  Send output to stdout",
 -  "    stderr                  Send output to stderr",
 -  "    off                     Disable tracing",
 -  "    --expanded              Expand query parameters",
 +
 +/*
 +** Try to deduce the type of file for zName based on its content.  Return
 +** one of the SHELL_OPEN_* constants.
 +**
 +** If the file does not exist or is empty but its name looks like a ZIP
 +** archive and the dfltZip flag is true, then assume it is a ZIP archive.
 +** Otherwise, assume an ordinary database regardless of the filename if
 +** the type cannot be determined from content.
 +*/
 +u8 deduceDatabaseType(const char *zName, int dfltZip){
 +  FILE *f = fopen(zName, "rb");
 +  size_t n;
 +  u8 rc = SHELL_OPEN_UNSPEC;
 +  char zBuf[100];
 +  if( f==0 ){
 +    if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
 +       return SHELL_OPEN_ZIPFILE;
 +    }else{
 +       return SHELL_OPEN_NORMAL;
 +    }
 +  }
 +  n = fread(zBuf, 16, 1, f);
 +  if( n==1 && memcmp(zBuf, "SQLite format 3", 16)==0 ){
 +    fclose(f);
 +    return SHELL_OPEN_NORMAL;
 +  }
 +  fseek(f, -25, SEEK_END);
 +  n = fread(zBuf, 25, 1, f);
 +  if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){
 +    rc = SHELL_OPEN_APPENDVFS;
 +  }else{
 +    fseek(f, -22, SEEK_END);
 +    n = fread(zBuf, 22, 1, f);
 +    if( n==1 && zBuf[0]==0x50 && zBuf[1]==0x4b && zBuf[2]==0x05
 +       && zBuf[3]==0x06 ){
 +      rc = SHELL_OPEN_ZIPFILE;
 +    }else if( n==0 && dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
 +      rc = SHELL_OPEN_ZIPFILE;
 +    }
 +  }
 +  fclose(f);
 +  return rc;
 +}
 +
 +#ifndef SQLITE_OMIT_DESERIALIZE
 +/*
 +** Reconstruct an in-memory database using the output from the "dbtotxt"
 +** program.  Read content from the file in p->aAuxDb[].zDbFilename.
 +** If p->aAuxDb[].zDbFilename is 0, then read from the present input.
 +*/
 +static unsigned char *readHexDb(ShellInState *psi, int *pnData){
 +  ResourceMark mark = holder_mark();
 +  unsigned char *a = 0;
 +  int n = 0;
 +  int pgsz = 0;
 +  int iOffset = 0;
 +  int j, k, nlError;
 +  int rc;
 +  static const char *zEndMarker = "| end ";
 +  const char *zDbFilename = psi->pAuxDb->zDbFilename;
 +  /* Need next two objects only if redirecting input to get the hex. */
 +  InSource inRedir = INSOURCE_FILE_REDIR(0, zDbFilename, psi->pInSource);
 +  AnyResourceHolder arh = { &psi->pInSource, (GenericFreer)finish_InSource };
 +  unsigned int x[16];
 +  char zLine[1000];
 +  if( zDbFilename ){
 +    inRedir.inFile = fopen(zDbFilename, "r");
 +    if( inRedir.inFile==0 ){
 +      utf8_printf(STD_ERR, "cannot open \"%s\" for reading\n", zDbFilename);
 +      return 0;
 +    }
 +    psi->pInSource = &inRedir;
 +    inRedir.closer.stream = fclose;
 +    any_ref_holder(&arh);
 +  }else{
 +    /* Will read hex DB lines inline from present input, without redirect. */
 +    if( INSOURCE_IS_INTERACTIVE(psi->pInSource) ){
 +      printf("Reading hex DB from \"%s\", until end-marker input like:\n%s\n",
 +             psi->pInSource->zSourceSay, zEndMarker);
 +      fflush(STD_OUT);
 +    }
 +  }
 +  *pnData = 0;
 +  if( strLineGet(zLine,sizeof(zLine), psi->pInSource)==0 ) goto readHexDb_error;
 +  rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz);
 +  if( rc!=2 ) goto readHexDb_error;
 +  if( n<0 ) goto readHexDb_error;
 +  if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ) goto readHexDb_error;
 +  n = (n+pgsz-1)&~(pgsz-1);  /* Round n up to the next multiple of pgsz */
 +  a = sqlite3_malloc( n ? n : 1 );
 +  shell_check_ooms(a);
 +  smem_holder(a);
 +  memset(a, 0, n);
 +  if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){
 +    utf8_printf(STD_ERR, "invalid pagesize\n");
 +    goto readHexDb_error;
 +  }
 +  while( strLineGet(zLine,sizeof(zLine), psi->pInSource)!=0 ){
 +    rc = sscanf(zLine, "| page %d offset %d", &j, &k);
 +    if( rc==2 ){
 +      iOffset = k;
 +      continue;
 +    }
 +    if( cli_strncmp(zLine, zEndMarker, 6)==0 ){
 +      break;
 +    }
 +    rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
 +                &j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7],
 +                &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]);
 +    if( rc==17 ){
 +      k = iOffset+j;
 +      if( k+16<=n && k>=0 ){
 +        int ii;
 +        for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff;
 +      }
 +    }
 +  }
 +  *pnData = n; /* Record success and size. */
 +  drop_holder();
 + readHexDb_cleanup:
 +  RESOURCE_FREE(mark);
 +  return a;
 +
 + readHexDb_error:
 +  nlError = psi->pInSource->lineno;
 +  if( psi->pInSource!=&inRedir ){
 +    /* Since taking input inline, consume through its end marker. */
 +    while( strLineGet(zLine, sizeof(zLine), psi->pInSource)!=0 ){
 +      if(cli_strncmp(zLine, zEndMarker, 6)==0 ) break;
 +    }
 +  }
 +  a = 0;
 +  utf8_printf(STD_ERR,"Error on line %d within --hexdb input\n", nlError);
 +  goto readHexDb_cleanup;
 +}
 +#endif /* SQLITE_OMIT_DESERIALIZE */
 +
 +/*
 +** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X.
 +*/
 +static void shellUSleepFunc(
 +  sqlite3_context *context,
 +  int argcUnused,
 +  sqlite3_value **argv
 +){
 +  int sleep = sqlite3_value_int(argv[0]);
 +  (void)argcUnused;
 +  sqlite3_sleep(sleep/1000);
 +  sqlite3_result_int(context, sleep);
 +}
 +
++/*
++** Attempt to close the database connection. Report errors.
++** The close is done under mutex protection for globalDb.
++*/
++static void close_db(sqlite3 *db){
++  int rc;
++  if( db==globalDb ){
++    /* This should only block for the time needed to handle ^C interrupt. */
++    sqlite3_mutex_enter(pGlobalDbLock);
++    globalDb = 0;
++    rc = sqlite3_close(db);
++    sqlite3_mutex_leave(pGlobalDbLock);
++  }else{
++    rc = sqlite3_close(db);
++  }
++  if( rc ){
++    utf8_printf(STD_ERR, "Error: sqlite3_close() returns %d: %s\n",
++        rc, sqlite3_errmsg(db));
++  }
++}
++
 +/* Flags for open_db(). ToDo: Conform comments to code or vice-versa.
 +**
 +** The default behavior of open_db() is to exit(1) if the database fails to
 +** open.  The OPEN_DB_KEEPALIVE flag changes that so that it prints an error
 +** but still returns without calling exit.
 +**
 +** The OPEN_DB_ZIPFILE flag causes open_db() to prefer to open files as a
 +** ZIP archive if the file does not exist or is empty and its name matches
 +** the *.zip pattern.
 +*/
 +#define OPEN_DB_KEEPALIVE   0x001   /* Return after error if true */
 +#define OPEN_DB_ZIPFILE     0x002   /* Open as ZIP if name matches *.zip */
 +
 +/*
 +** Make sure the database is open.  If it is not, then open it.  If
 +** the database fails to open, print an error message and exit.
 +*/
 +static void open_db(ShellExState *psx, int openFlags){
 +  ShellInState *psi = ISS(psx);
 +  if( DBX(psx)==0 ){
 +    sqlite3 **pDb = &DBX(psx);
 +    const char *zDbFilename = psi->pAuxDb->zDbFilename;
 +    if( psi->openMode==SHELL_OPEN_UNSPEC ){
 +      if( zDbFilename==0 || zDbFilename[0]==0 ){
 +        psi->openMode = SHELL_OPEN_NORMAL;
 +      }else{
 +        psi->openMode = deduceDatabaseType(zDbFilename,
 +                                           (openFlags & OPEN_DB_ZIPFILE)!=0);
 +      }
 +    }
 +    switch( psi->openMode ){
 +      case SHELL_OPEN_APPENDVFS: {
 +        sqlite3_open_v2
 +          (zDbFilename, pDb,
 +           SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|psi->openFlags,
 +           "apndvfs");
 +        break;
 +      }
 +      case SHELL_OPEN_HEXDB:
 +      case SHELL_OPEN_DESERIALIZE: {
 +        sqlite3_open(0, pDb);
 +        break;
 +      }
 +      case SHELL_OPEN_ZIPFILE: {
 +        sqlite3_open(":memory:", pDb);
 +        break;
 +      }
 +      case SHELL_OPEN_READONLY: {
 +        sqlite3_open_v2(zDbFilename, pDb,
 +            SQLITE_OPEN_READONLY|psi->openFlags, 0);
 +        break;
 +      }
 +      case SHELL_OPEN_UNSPEC:
 +      case SHELL_OPEN_NORMAL: {
 +        sqlite3_open_v2(zDbFilename, pDb,
 +           SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|psi->openFlags, 0);
 +        break;
 +      }
 +    }
 +    globalDb = DBX(psx);
 +    if( DBX(psx)==0 || SQLITE_OK!=sqlite3_errcode(DBX(psx)) ){
 +      const char *zWhy = (DBX(psx)==0)? "(?)" : sqlite3_errmsg(DBX(psx));
 +      utf8_printf(STD_ERR,"Error: unable to open database \"%s\": %s\n",
 +          zDbFilename, zWhy);
-       sqlite3_close(DBX(psx));
++      close_db(DBX(psx));
 +      if( bail_on_error && !stdin_is_interactive ){
 +        shell_terminate("-bail used in batch mode.");
 +      }
 +      if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){
 +        shell_terminate("with OPEN_DB_KEEPALIVE.");
 +      }
 +      sqlite3_open(":memory:", &DBX(psx));
 +      if( DBX(psx)==0 || SQLITE_OK!=sqlite3_errcode(DBX(psx)) ){
 +        shell_terminate("Also: unable to open substitute in-memory database.");
 +      }else{
 +        utf8_printf(stderr,
 +          "Notice: using substitute in-memory database instead of \"%s\"\n",
 +          zDbFilename);
 +      }
 +    }
-     sqlite3_db_config(globalDb, SQLITE_DBCONFIG_STMT_SCANSTATUS,(int)0,(int*)0);
++    sqlite3_db_config(GLOBAL_DB,SQLITE_DBCONFIG_STMT_SCANSTATUS,(int)0,(int*)0);
 +
 +    /* Reflect the use or absence of --unsafe-testing invocation. */
 +    {
 +      int testmode_on = ShellHasFlag(psx,SHFLG_TestingMode);
-       sqlite3_db_config(globalDb, SQLITE_DBCONFIG_TRUSTED_SCHEMA,testmode_on,0);
-       sqlite3_db_config(globalDb, SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0);
++      sqlite3_db_config(GLOBAL_DB,SQLITE_DBCONFIG_TRUSTED_SCHEMA,testmode_on,0);
++      sqlite3_db_config(GLOBAL_DB,SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0);
 +    }
 +#ifndef SQLITE_OMIT_LOAD_EXTENSION
-     sqlite3_enable_load_extension(globalDb, 1);
++    sqlite3_enable_load_extension(GLOBAL_DB, 1);
 +#endif
-     sqlite3_shathree_init(globalDb, 0, 0);
-     sqlite3_uint_init(globalDb, 0, 0);
-     sqlite3_decimal_init(globalDb, 0, 0);
-     sqlite3_base64_init(globalDb, 0, 0);
-     sqlite3_base85_init(globalDb, 0, 0);
-     sqlite3_regexp_init(globalDb, 0, 0);
-     sqlite3_ieee_init(globalDb, 0, 0);
-     sqlite3_series_init(globalDb, 0, 0);
++    sqlite3_shathree_init(GLOBAL_DB, 0, 0);
++    sqlite3_uint_init(GLOBAL_DB, 0, 0);
++    sqlite3_decimal_init(GLOBAL_DB, 0, 0);
++    sqlite3_base64_init(GLOBAL_DB, 0, 0);
++    sqlite3_base85_init(GLOBAL_DB, 0, 0);
++    sqlite3_regexp_init(GLOBAL_DB, 0, 0);
++    sqlite3_ieee_init(GLOBAL_DB, 0, 0);
++    sqlite3_series_init(GLOBAL_DB, 0, 0);
 +#ifndef SQLITE_SHELL_FIDDLE
-     sqlite3_fileio_init(globalDb, 0, 0);
-     sqlite3_completion_init(globalDb, 0, 0);
++    sqlite3_fileio_init(GLOBAL_DB, 0, 0);
++    sqlite3_completion_init(GLOBAL_DB, 0, 0);
 +#endif
 +#ifdef SQLITE_HAVE_ZLIB
 +    if( !psi->bSafeModeFuture ){
-       sqlite3_zipfile_init(globalDb, 0, 0);
-       sqlite3_sqlar_init(globalDb, 0, 0);
++      sqlite3_zipfile_init(GLOBAL_DB, 0, 0);
++      sqlite3_sqlar_init(GLOBAL_DB, 0, 0);
 +    }
 +#endif
 +
 +#ifdef SQLITE_SHELL_EXTFUNCS
 +    /* Create a preprocessing mechanism for extensions to make
 +     * their own provisions for being built into the shell.
 +     * This is a short-span macro. See further below for usage.
 +     */
 +#define SHELL_SUB_MACRO(base, variant) base ## _ ## variant
 +#define SHELL_SUBMACRO(base, variant) SHELL_SUB_MACRO(base, variant)
 +    /* Let custom-included extensions get their ..._init() called.
 +     * The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause
 +     * the extension's sqlite3_*_init( db, pzErrorMsg, pApi )
 +     * inititialization routine to be called.
 +     */
 +    {
 +      int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db);
 +    /* Let custom-included extensions expose their functionality.
 +     * The WHATEVER_EXPOSE( db, pzErrorMsg ) macro should cause
 +     * the SQL functions, virtual tables, collating sequences or
 +     * VFS's implemented by the extension to be registered.
 +     */
 +      if( irc==SQLITE_OK
 +          || irc==SQLITE_OK_LOAD_PERMANENTLY ){
 +        SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, EXPOSE)(p->db, 0);
 +      }
 +#undef SHELL_SUB_MACRO
 +#undef SHELL_SUBMACRO
 +    }
 +#endif
 +
-     sqlite3_create_function(globalDb, "shell_add_schema", 3, SQLITE_UTF8, 0,
++    sqlite3_create_function(GLOBAL_DB, "shell_add_schema", 3,SQLITE_UTF8,0,
 +                            shellAddSchemaName, 0, 0);
-     sqlite3_create_function(globalDb, "shell_module_schema", 1, SQLITE_UTF8, 0,
++    sqlite3_create_function(GLOBAL_DB, "shell_module_schema", 1,SQLITE_UTF8,0,
 +                            shellModuleSchema, 0, 0);
-     sqlite3_create_function(globalDb, "shell_putsnl", 1, SQLITE_UTF8, psx,
++    sqlite3_create_function(GLOBAL_DB, "shell_putsnl", 1,SQLITE_UTF8,psx,
 +                            shellPutsFunc, 0, 0);
-     sqlite3_create_function(globalDb, "usleep",1,SQLITE_UTF8, 0,
++    sqlite3_create_function(GLOBAL_DB, "usleep", 1,SQLITE_UTF8,0,
 +                            shellUSleepFunc, 0, 0);
 +#ifndef SQLITE_NOHAVE_SYSTEM
-     sqlite3_create_function(globalDb, "edit", 1, SQLITE_UTF8, 0,
++    sqlite3_create_function(GLOBAL_DB, "edit", 1, SQLITE_UTF8, 0,
 +                            editFunc, 0, 0);
-     sqlite3_create_function(globalDb, "edit", 2, SQLITE_UTF8, 0,
++    sqlite3_create_function(GLOBAL_DB, "edit", 2, SQLITE_UTF8, 0,
 +                            editFunc, 0, 0);
 +#endif
 +    if( psi->openMode==SHELL_OPEN_ZIPFILE ){
 +      char *zSql = smprintf("CREATE VIRTUAL TABLE zip USING zipfile(%Q);",
 +                            zDbFilename);
 +      shell_check_ooms(zSql);
 +      sstr_holder(zSql);
 +      s3_exec_noom(DBX(psx), zSql, 0, 0, 0);
 +      release_holder();
 +    }
 +#ifndef SQLITE_OMIT_DESERIALIZE
 +    else
 +    if( psi->openMode==SHELL_OPEN_DESERIALIZE
 +        || psi->openMode==SHELL_OPEN_HEXDB ){
 +      int rc;
 +      int nData = 0;
 +      unsigned char *aData;
 +      if( psi->openMode==SHELL_OPEN_DESERIALIZE ){
 +        aData = (unsigned char*)readFile(zDbFilename, &nData);
 +      }else{
 +        aData = readHexDb(psi, &nData);
 +        if( aData==0 ){
 +          return;
 +        }
 +      }
 +      rc = sqlite3_deserialize(DBX(psx), "main", aData, nData, nData,
 +                   SQLITE_DESERIALIZE_RESIZEABLE |
 +                   SQLITE_DESERIALIZE_FREEONCLOSE);
 +      if( rc ){
 +        utf8_printf(STD_ERR, "Error: sqlite3_deserialize() returns %d\n", rc);
 +      }
 +      if( psi->szMax>0 ){
 +        sqlite3_file_control(DBX(psx), "main", SQLITE_FCNTL_SIZE_LIMIT,
 +                             &psi->szMax);
 +      }
 +    }
 +#endif
 +    if( DBX(psx)!=0 ){
 +      if( psi->bSafeModeFuture ){
 +        sqlite3_set_authorizer(DBX(psx), safeModeAuth, psx);
 +      }
 +      sqlite3_db_config(
 +           DBX(psx), SQLITE_DBCONFIG_STMT_SCANSTATUS, psi->scanstatsOn,(int*)0);
 +    }
 +#if SHELL_DYNAMIC_EXTENSION
 +    notify_subscribers(psi, NK_DbUserAppeared, DBX(psx));
 +#endif
 +  }
 +}
 +
- /*
- ** Attempt to close the database connection. Report errors.
- */
- void close_db(sqlite3 *db){
-   int rc;
-   if( db==globalDb ){
-     /* This should only block for the time needed to handle ^C interrupt. */
-     sqlite3_mutex_enter(pGlobalDbLock);
-     globalDb = 0;
-     rc = sqlite3_close(db);
-     sqlite3_mutex_leave(pGlobalDbLock);
-   }else{
-     rc = sqlite3_close(db);
-   }
-   if( rc ){
-     utf8_printf(STD_ERR, "Error: sqlite3_close() returns %d: %s\n",
-         rc, sqlite3_errmsg(db));
-   }
- }
 +#if HAVE_READLINE || HAVE_EDITLINE
 +/*
 +** Readline completion callbacks
 +*/
 +static char *readline_completion_generator(const char *text, int state){
 +  static sqlite3_stmt *pStmt = 0;
 +  char *zRet;
 +  if( state==0 ){
 +    char *zSql;
 +    sqlite3_finalize(pStmt);
 +    pStmt = 0;
 +    zSql = smprintf("SELECT DISTINCT candidate COLLATE nocase"
 +                    " FROM completion(%Q) ORDER BY 1", text);
-     s3_prep_noom_free(globalDb, &zSql, &pStmt);
++    s3_prep_noom_free(GLOBAL_DB, &zSql, &pStmt);
 +  }
 +  if( sqlite3_step(pStmt)==SQLITE_ROW ){
 +    const char *z = (const char*)sqlite3_column_text(pStmt,0);
 +    if( z!=0 ){
 +      zRet = strdup(z);
 +      shell_check_oomm(zRet);
 +    }
 +  }else{
 +    sqlite3_finalize(pStmt);
 +    pStmt = 0;
 +    zRet = 0;
 +  }
 +  return zRet;
 +}
 +static char **readline_completion(const char *zText, int iStart, int iEnd){
 +  (void)iStart;
 +  (void)iEnd;
 +  rl_attempted_completion_over = 1;
 +  return rl_completion_matches(zText, readline_completion_generator);
 +}
 +
 +#elif HAVE_LINENOISE
 +/*
 +** Linenoise completion callback
 +*/
 +static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){
 +  i64 nLine = strlen(zLine);
 +  i64 i, iStart;
 +  sqlite3_stmt *pStmt = 0;
 +  char *zSql;
 +  char zBuf[1000];
 +
 +  if( nLine>(i64)sizeof(zBuf)-30 ) return;
 +  if( zLine[0]=='.' || zLine[0]=='#') return;
 +  for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){}
 +  if( i==nLine-1 ) return;
 +  iStart = i+1;
 +  memcpy(zBuf, zLine, iStart);
 +  zSql = smprintf("SELECT DISTINCT candidate COLLATE nocase"
 +                  " FROM completion(%Q,%Q) ORDER BY 1",
 +                  &zLine[iStart], zLine);
-   s3_prep_noom_free(globalDb, &zSql, &pStmt);
-   sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */
++  s3_prep_noom_free(GLOBAL_DB, &zSql, &pStmt);
++  sqlite3_exec(GLOBAL_DB, "PRAGMA page_count", 0, 0, 0); /* Load the schema */
 +  while( sqlite3_step(pStmt)==SQLITE_ROW ){
 +    const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0);
 +    int nCompletion = sqlite3_column_bytes(pStmt, 0);
 +    if( iStart+nCompletion < (i64)sizeof(zBuf)-1 && zCompletion ){
 +      memcpy(zBuf+iStart, zCompletion, nCompletion+1);
 +      linenoiseAddCompletion(lc, zBuf);
 +    }
 +  }
 +  sqlite3_finalize(pStmt);
 +}
 +#endif
 +
 +/*
 +** Do C-language style escape sequence translation.
 +**
 +**    \a    -> alarm
 +**    \b    -> backspace
 +**    \t    -> tab
 +**    \n    -> newline
 +**    \v    -> vertical tab
 +**    \f    -> form feed
 +**    \r    -> carriage return
 +**    \s    -> space
 +**    \"    -> "
 +**    \'    -> '
 +**    \\    -> backslash
 +**    \NNN  -> ascii character NNN in octal
 +**    \xHH  -> ascii character HH in hexadecimal
 +*/
 +static void resolve_backslashes(char *z){
 +  int i, j;
 +  char c;
 +  while( *z && *z!='\\' ) z++;
 +  for(i=j=0; (c = z[i])!=0; i++, j++){
 +    if( c=='\\' && z[i+1]!=0 ){
 +      c = z[++i];
 +      if( c=='a' ){
 +        c = '\a';
 +      }else if( c=='b' ){
 +        c = '\b';
 +      }else if( c=='t' ){
 +        c = '\t';
 +      }else if( c=='n' ){
 +        c = '\n';
 +      }else if( c=='v' ){
 +        c = '\v';
 +      }else if( c=='f' ){
 +        c = '\f';
 +      }else if( c=='r' ){
 +        c = '\r';
 +      }else if( c=='"' ){
 +        c = '"';
 +      }else if( c=='\'' ){
 +        c = '\'';
 +      }else if( c=='\\' ){
 +        c = '\\';
 +      }else if( c=='x' ){
 +        int nhd = 0, hdv;
 +        u8 hv = 0;
 +        while( nhd<2 && (c=z[i+1+nhd])!=0 && (hdv=hexDigitValue(c))>=0 ){
 +          hv = (u8)((hv<<4)|hdv);
 +          ++nhd;
 +        }
 +        i += nhd;
 +        c = hv;
 +      }else if( c>='0' && c<='7' ){
 +        c -= '0';
 +        if( z[i+1]>='0' && z[i+1]<='7' ){
 +          i++;
 +          c = (c<<3) + z[i] - '0';
 +          if( z[i+1]>='0' && z[i+1]<='7' ){
 +            i++;
 +            c = (c<<3) + z[i] - '0';
 +          }
 +        }
 +      }
 +    }
 +    z[j] = c;
 +  }
 +  if( j<i ) z[j] = 0;
 +}
 +
 +/*
 +** 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 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{
 +    for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){}
 +  }
 +  if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff);
 +  for( i=0; zBoolNames[i]!=0; ++i ){
 +    if( sqlite3_stricmp(zArg, zBoolNames[i])==0 ) return i&1;
 +  }
 +  utf8_printf(STD_ERR, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n",
 +              zArg);
 +  return 0;
 +}
 +
 +/*
 +** Set or clear a shell flag according to a boolean value.
 +*/
 +static void setOrClearFlag(ShellExState *psx, unsigned mFlag, const char *zArg){
 +  if( booleanValue(zArg) ){
 +    ShellSetFlag(psx, mFlag);
 +  }else{
 +    ShellClearFlag(psx, mFlag);
 +  }
 +}
 +
 +/*
 +** Close an output file, provided it is not stderr or stdout
 +*/
 +static void output_file_close(FILE *f){
 +  if( f && f!=STD_OUT && f!=STD_ERR ) fclose(f);
 +}
 +
 +/*
 +** Try to open an output file.   The names "stdout" and "stderr" are
 +** recognized and do the right thing.  NULL is returned if the output
 +** filename is "off".
 +*/
 +static FILE *output_file_open(const char *zFile, int bTextMode){
 +  FILE *f;
 +  if( cli_strcmp(zFile,"stdout")==0 ){
 +    f = STD_OUT;
 +  }else if( cli_strcmp(zFile, "stderr")==0 ){
 +    f = STD_ERR;
 +  }else if( cli_strcmp(zFile, "off")==0 ){
 +    f = 0;
 +  }else{
 +    f = fopen(zFile, bTextMode ? "w" : "wb");
 +    if( f==0 ){
 +      utf8_printf(STD_ERR, "Error: cannot open \"%s\"\n", zFile);
 +    }
 +  }
 +  return f;
 +}
 +
 +#ifndef SQLITE_OMIT_TRACE
 +/*
 +** A routine for handling output from sqlite3_trace().
 +*/
 +static int sql_trace_callback(
 +  unsigned mType,         /* The trace type */
 +  void *pArg,             /* The shell state pointer */
 +  void *pP,               /* Usually a pointer to sqlite_stmt */
 +  void *pX                /* Auxiliary output */
 +){
 +  ShellInState *psi = (ShellInState*)pArg;
 +  sqlite3_stmt *pStmt;
 +  const char *zSql;
 +  i64 nSql;
 +  if( psi->traceOut==0 ) return 0;
 +  if( mType==SQLITE_TRACE_CLOSE ){
 +    utf8_printf(psi->traceOut, "-- closing database connection\n");
 +    return 0;
 +  }
 +  if( mType!=SQLITE_TRACE_ROW && pX!=0 && ((const char*)pX)[0]=='-' ){
 +    zSql = (const char*)pX;
 +  }else{
 +    pStmt = (sqlite3_stmt*)pP;
 +    switch( psi->eTraceType ){
 +      case SHELL_TRACE_EXPANDED: {
 +        zSql = sqlite3_expanded_sql(pStmt);
 +        break;
 +      }
  #ifdef SQLITE_ENABLE_NORMALIZE
 -  "    --normalized            Normal the SQL statements",
 +      case SHELL_TRACE_NORMALIZED: {
 +        zSql = sqlite3_normalized_sql(pStmt);
 +        break;
 +      }
  #endif
 -  "    --plain                 Show SQL as it is input",
 -  "    --stmt                  Trace statement execution (SQLITE_TRACE_STMT)",
 -  "    --profile               Profile statements (SQLITE_TRACE_PROFILE)",
 -  "    --row                   Trace each row (SQLITE_TRACE_ROW)",
 -  "    --close                 Trace connection close (SQLITE_TRACE_CLOSE)",
 -#endif /* SQLITE_OMIT_TRACE */
 -#ifdef SQLITE_DEBUG
 -  ".unmodule NAME ...       Unregister virtual table modules",
 -  "    --allexcept             Unregister everything except those named",
 +      default: {
 +        zSql = sqlite3_sql(pStmt);
 +        break;
 +      }
 +    }
 +  }
 +  if( zSql==0 ) return 0;
 +  nSql = strlen(zSql);
 +  if( nSql>1000000000 ) nSql = 1000000000; /* clamp to 1 billion */
 +  while( nSql>0 && zSql[nSql-1]==';' ){ nSql--; }
 +  switch( mType ){
 +    case SQLITE_TRACE_ROW:
 +    case SQLITE_TRACE_STMT: {
 +      utf8_printf(psi->traceOut, "%.*s;\n", (int)nSql, zSql);
 +      break;
 +    }
 +    case SQLITE_TRACE_PROFILE: {
 +      sqlite3_int64 nNanosec = pX ? *(sqlite3_int64*)pX : 0;
 +      utf8_printf(psi->traceOut,"%.*s; -- %lld ns\n", (int)nSql,zSql,nNanosec);
 +      break;
 +    }
 +  }
 +  return 0;
 +}
  #endif
 -  ".version                 Show source, library and compiler versions",
 -  ".vfsinfo ?AUX?           Information about the top-level VFS",
 -  ".vfslist                 List all available VFSes",
 -  ".vfsname ?AUX?           Print the name of the VFS stack",
 -  ".width NUM1 NUM2 ...     Set minimum column widths for columnar output",
 -  "     Negative values right-justify",
 +
 +/*
 +** A no-op routine that runs with the ".breakpoint" dot-command.
 +** This is a useful spot to set a debugger breakpoint.
 +**
 +** This routine does not do anything practical.  The code are there simply
 +** to prevent the compiler from optimizing this routine out.
 +*/
 +static void test_breakpoint(void){
 +  static int nCall = 0;
 +  if( (nCall++)==0xffffffff ) printf("Many .breakpoints have run\n");
 +}
 +
 +/*
 +** An object used to read a CSV and other files for import.
 +*/
 +typedef struct ImportCtx ImportCtx;
 +struct ImportCtx {
 +  const char *zFile;  /* Name of the input file */
 +  FILE *in;           /* Read the CSV text from this input stream */
 +  int (SQLITE_CDECL *xCloser)(FILE*);      /* Func to close in */
 +  char *z;            /* Accumulated text for a field */
 +  int n;              /* Number of bytes in z */
 +  int nAlloc;         /* Space allocated for z[] */
 +  int nLine;          /* Current line number */
 +  int nRow;           /* Number of rows imported */
 +  int nErr;           /* Number of errors encountered */
 +  int bNotFirst;      /* True if one or more bytes already read */
 +  int cTerm;          /* Character that terminated the most recent field */
 +  int cColSep;        /* The column separator character.  (Usually ",") */
 +  int cRowSep;        /* The row separator character.  (Usually "\n") */
  };
  
 +/* Clean up resourced used by an ImportCtx */
 +static void import_cleanup(ImportCtx *p){
 +  if( p->in!=0 && p->xCloser!=0 ){
 +    p->xCloser(p->in);
 +    p->in = 0;
 +  }
 +  sqlite3_free(p->z);
 +  p->z = 0;
 +}
 +
 +/* Append a single byte to z[] */
 +static void import_append_char(ImportCtx *p, int c){
 +  if( p->n+1>=p->nAlloc ){
 +    p->nAlloc += p->nAlloc + 100;
 +    p->z = sqlite3_realloc64(p->z, p->nAlloc);
 +    shell_check_ooms(p->z);
 +  }
 +  p->z[p->n++] = (char)c;
 +}
 +
 +/* Read a single field of CSV text.  Compatible with rfc4180 and extended
 +** with the option of having a separator other than ",".
 +**
 +**   +  Input comes from p->in.
 +**   +  Store results in p->z of length p->n.  Space to hold p->z comes
 +**      from sqlite3_malloc64().
 +**   +  Use p->cSep as the column separator.  The default is ",".
 +**   +  Use p->rSep as the row separator.  The default is "\n".
 +**   +  Keep track of the line number in p->nLine.
 +**   +  Store the character that terminates the field in p->cTerm.  Store
 +**      EOF on end-of-file.
 +**   +  Report syntax errors on stderr
 +*/
 +static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){
 +  int c;
 +  int cSep = (u8)p->cColSep;
 +  int rSep = (u8)p->cRowSep;
 +  p->n = 0;
 +  c = fgetc(p->in);
 +  if( c==EOF || seenInterrupt ){
 +    p->cTerm = EOF;
 +    return 0;
 +  }
 +  if( c=='"' ){
 +    int pc, ppc;
 +    int startLine = p->nLine;
 +    int cQuote = c;
 +    pc = ppc = 0;
 +    while( 1 ){
 +      c = fgetc(p->in);
 +      if( c==rSep ) p->nLine++;
 +      if( c==cQuote ){
 +        if( pc==cQuote ){
 +          pc = 0;
 +          continue;
 +        }
 +      }
 +      if( (c==cSep && pc==cQuote)
 +       || (c==rSep && pc==cQuote)
 +       || (c==rSep && pc=='\r' && ppc==cQuote)
 +       || (c==EOF && pc==cQuote)
 +      ){
 +        do{ p->n--; }while( p->z[p->n]!=cQuote );
 +        p->cTerm = c;
 +        break;
 +      }
 +      if( pc==cQuote && c!='\r' ){
 +        utf8_printf(STD_ERR, "%s:%d: unescaped %c character\n",
 +                p->zFile, p->nLine, cQuote);
 +      }
 +      if( c==EOF ){
 +        utf8_printf(STD_ERR, "%s:%d: unterminated %c-quoted field\n",
 +                p->zFile, startLine, cQuote);
 +        p->cTerm = c;
 +        break;
 +      }
 +      import_append_char(p, c);
 +      ppc = pc;
 +      pc = c;
 +    }
 +  }else{
 +    /* If this is the first field being parsed and it begins with the
 +    ** UTF-8 BOM  (0xEF BB BF) then skip the BOM */
 +    if( (c&0xff)==0xef && p->bNotFirst==0 ){
 +      import_append_char(p, c);
 +      c = fgetc(p->in);
 +      if( (c&0xff)==0xbb ){
 +        import_append_char(p, c);
 +        c = fgetc(p->in);
 +        if( (c&0xff)==0xbf ){
 +          p->bNotFirst = 1;
 +          p->n = 0;
 +          return csv_read_one_field(p);
 +        }
 +      }
 +    }
 +    while( c!=EOF && c!=cSep && c!=rSep ){
 +      import_append_char(p, c);
 +      c = fgetc(p->in);
 +    }
 +    if( c==rSep ){
 +      p->nLine++;
 +      if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--;
 +    }
 +    p->cTerm = c;
 +  }
 +  if( p->z ) p->z[p->n] = 0;
 +  p->bNotFirst = 1;
 +  return p->z;
 +}
 +
 +/* Read a single field of ASCII delimited text.
 +**
 +**   +  Input comes from p->in.
 +**   +  Store results in p->z of length p->n.  Space to hold p->z comes
 +**      from sqlite3_malloc64().
 +**   +  Use p->cSep as the column separator.  The default is "\x1F".
 +**   +  Use p->rSep as the row separator.  The default is "\x1E".
 +**   +  Keep track of the row number in p->nLine.
 +**   +  Store the character that terminates the field in p->cTerm.  Store
 +**      EOF on end-of-file.
 +**   +  Report syntax errors on stderr
 +*/
 +static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p){
 +  int c;
 +  int cSep = (u8)p->cColSep;
 +  int rSep = (u8)p->cRowSep;
 +  p->n = 0;
 +  c = fgetc(p->in);
 +  if( c==EOF || seenInterrupt ){
 +    p->cTerm = EOF;
 +    return 0;
 +  }
 +  while( c!=EOF && c!=cSep && c!=rSep ){
 +    import_append_char(p, c);
 +    c = fgetc(p->in);
 +  }
 +  if( c==rSep ){
 +    p->nLine++;
 +  }
 +  p->cTerm = c;
 +  if( p->z ) p->z[p->n] = 0;
 +  return p->z;
 +}
 +
 +/*
 +** Try to transfer data for table zTable.  If an error is seen while
 +** moving forward, try to go backwards.  The backwards movement won't
 +** work for WITHOUT ROWID tables.
 +*/
 +static void tryToCloneData(
 +  ShellExState *psx,
 +  sqlite3 *newDb,
 +  const char *zTable
 +){
 +  sqlite3_stmt *pQuery = 0;
 +  sqlite3_stmt *pInsert = 0;
 +  char *zQuery = 0;
 +  char *zInsert = 0;
 +  int rc;
 +  int i, j, n;
 +  int nTable = strlen30(zTable);
 +  int k = 0;
 +  int cnt = 0;
 +  const int spinRate = 10000;
 +
 +  shell_check_ooms(zQuery = smprintf("SELECT * FROM \"%w\"", zTable));
 +  rc = s3_prepare_v2_noom(DBX(psx), zQuery, -1, &pQuery, 0);
 +  if( rc ){
 +    utf8_printf(STD_ERR, "Error %d: %s on [%s]\n",
 +            sqlite3_extended_errcode(DBX(psx)), sqlite3_errmsg(DBX(psx)),
 +            zQuery);
 +    goto end_data_xfer;
 +  }
 +  n = sqlite3_column_count(pQuery);
 +  zInsert = sqlite3_malloc64(200 + nTable + n*3);
 +  shell_check_ooms(zInsert);
 +  sqlite3_snprintf(200+nTable,zInsert,
 +                   "INSERT OR IGNORE INTO \"%s\" VALUES(?", zTable);
 +  i = strlen30(zInsert);
 +  for(j=1; j<n; j++){
 +    memcpy(zInsert+i, ",?", 2);
 +    i += 2;
 +  }
 +  memcpy(zInsert+i, ");", 3);
 +  rc = s3_prepare_v2_noom(newDb, zInsert, -1, &pInsert, 0);
 +  if( rc ){
 +    utf8_printf(STD_ERR, "Error %d: %s on [%s]\n",
 +            sqlite3_extended_errcode(newDb), sqlite3_errmsg(newDb),
 +            zInsert);
 +    goto end_data_xfer;
 +  }
 +  for(k=0; k<2; k++){
 +    while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){
 +      for(i=0; i<n; i++){
 +        switch( sqlite3_column_type(pQuery, i) ){
 +          case SQLITE_NULL: {
 +            sqlite3_bind_null(pInsert, i+1);
 +            break;
 +          }
 +          case SQLITE_INTEGER: {
 +            sqlite3_bind_int64(pInsert, i+1, sqlite3_column_int64(pQuery,i));
 +            break;
 +          }
 +          case SQLITE_FLOAT: {
 +            sqlite3_bind_double(pInsert, i+1, sqlite3_column_double(pQuery,i));
 +            break;
 +          }
 +          case SQLITE_TEXT: {
 +            sqlite3_bind_text(pInsert, i+1,
 +                             (const char*)sqlite3_column_text(pQuery,i),
 +                             -1, SQLITE_STATIC);
 +            break;
 +          }
 +          case SQLITE_BLOB: {
 +            sqlite3_bind_blob(pInsert, i+1, sqlite3_column_blob(pQuery,i),
 +                                            sqlite3_column_bytes(pQuery,i),
 +                                            SQLITE_STATIC);
 +            break;
 +          }
 +        }
 +      } /* End for */
 +      rc = sqlite3_step(pInsert);
 +      if( rc!=SQLITE_OK && rc!=SQLITE_ROW && rc!=SQLITE_DONE ){
 +        utf8_printf(STD_ERR, "Error %d: %s\n", sqlite3_extended_errcode(newDb),
 +                        sqlite3_errmsg(newDb));
 +      }
 +      sqlite3_reset(pInsert);
 +      cnt++;
 +      if( (cnt%spinRate)==0 ){
 +        fprintf(STD_OUT, "%c\b", "|/-\\"[(cnt/spinRate)%4]);
 +        fflush(STD_OUT);
 +      }
 +    } /* End while */
 +    if( rc==SQLITE_DONE ) break;
 +    sqlite3_finalize(pQuery);
 +    sqlite3_free(zQuery);
 +    zQuery = smprintf("SELECT * FROM \"%w\" ORDER BY rowid DESC;", zTable);
 +    rc = s3_prep_noom_free(DBX(psx), &zQuery, &pQuery);
 +    if( rc ){
 +      utf8_printf(STD_ERR, "Warning: cannot step \"%s\" backwards", zTable);
 +      break;
 +    }
 +  } /* End for(k=0...) */
 +
 +end_data_xfer:
 +  sqlite3_finalize(pQuery);
 +  sqlite3_finalize(pInsert);
 +  sqlite3_free(zQuery);
 +  sqlite3_free(zInsert);
 +}
 +
 +
  /*
 -** 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.
 +** Try to transfer all rows of the schema that match zWhere.  For
 +** each row, invoke xForEach() on the object defined by that row.
 +** If an error is encountered while moving forward through the
 +** sqlite_schema table, try again moving backwards.
  */
 -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; i<ArraySize(azHelp); i++){
 -      switch( azHelp[i][0] ){
 -      case ',':
 -        hh = HH_Summary|HH_Undoc;
 -        break;
 -      case '.':
 -        hh = HH_Summary;
 -        break;
 -      default:
 -        hh &= ~HH_Summary;
 -        break;
 -      }
 -      if( ((hw^hh)&HH_Undoc)==0 ){
 -        if( (hh&HH_Summary)!=0 ){
 -          utf8_printf(out, ".%s\n", azHelp[i]+1);
 -          ++n;
 -        }else if( (hw&HW_SummaryOnly)==0 ){
 -          utf8_printf(out, "%s\n", azHelp[i]);
 -        }
 +static void tryToCloneSchema(
 +  ShellExState *psx,
 +  sqlite3 *newDb,
 +  const char *zWhere,
 +  void (*xForEach)(ShellExState*,sqlite3*,const char*)
 +){
 +  sqlite3_stmt *pQuery = 0;
 +  char *zQuery = 0;
 +  int rc;
 +  const unsigned char *zName;
 +  const unsigned char *zSql;
 +  char *zErrMsg = 0;
 +  RESOURCE_MARK(mark);
 +
 +  zQuery = smprintf("SELECT name, sql FROM sqlite_schema"
 +                    " WHERE %s ORDER BY rowid ASC", zWhere);
 +  shell_check_ooms(zQuery);
 +  sstr_ptr_holder(&zQuery);
 +  rc = s3_prepare_v2_noom(DBX(psx), zQuery, -1, &pQuery, 0);
 +  if( rc ){
 +    utf8_printf(STD_ERR, "Error: (%d) %s on [%s]\n",
 +                sqlite3_extended_errcode(DBX(psx)),
 +                sqlite3_errmsg(DBX(psx)), zQuery);
 +    goto end_schema_xfer;
 +  }
 +  stmt_ptr_holder(&pQuery);
 +  while( (rc = s3_step_noom(pQuery))==SQLITE_ROW ){
 +    zName = sqlite3_column_text(pQuery, 0);
 +    zSql = sqlite3_column_text(pQuery, 1);
 +    if( zName==0 || zSql==0 ) continue;
 +    if( sqlite3_stricmp((char*)zName, "sqlite_sequence")!=0 ){
 +      /* Consider directing this output to current output. */
 +      fprintf(STD_OUT, "%s... ", zName); fflush(STD_OUT);
 +      s3_exec_noom(newDb, (const char*)zSql, 0, 0, &zErrMsg);
 +      if( zErrMsg ){
 +        utf8_printf(STD_ERR, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql);
 +        sqlite3_free(zErrMsg);
 +        zErrMsg = 0;
        }
      }
 -  }else{
 -    /* Seek documented commands for which zPattern is an exact prefix */
 -    zPat = sqlite3_mprintf(".%s*", zPattern);
 -    shell_check_oom(zPat);
 -    for(i=0; i<ArraySize(azHelp); i++){
 -      if( sqlite3_strglob(zPat, azHelp[i])==0 ){
 -        utf8_printf(out, "%s\n", azHelp[i]);
 -        j = i+1;
 -        n++;
 -      }
 +    if( xForEach ){
 +      xForEach(psx, newDb, (const char*)zName);
      }
 -    sqlite3_free(zPat);
 -    if( n ){
 -      if( n==1 ){
 -        /* when zPattern is a prefix of exactly one command, then include
 -        ** the details of that command, which should begin at offset j */
 -        while( j<ArraySize(azHelp)-1 && azHelp[j][0]==' ' ){
 -          utf8_printf(out, "%s\n", azHelp[j]);
 -          j++;
 -        }
 -      }
 -      return n;
 +    /* Consider directing this output to current output. */
 +    fprintf(STD_OUT, "done\n");
 +  }
 +  if( rc!=SQLITE_DONE ){
 +    sqlite3_finalize(pQuery);
 +    pQuery = 0;
 +    sqlite3_free(zQuery);
 +    zQuery = smprintf("SELECT name, sql FROM sqlite_schema"
 +                      " WHERE %s ORDER BY rowid DESC", zWhere);
 +    shell_check_ooms(zQuery);
 +    rc = s3_prepare_v2_noom(DBX(psx), zQuery, -1, &pQuery, 0);
 +    if( rc ){
 +      utf8_printf(STD_ERR, "Error: (%d) %s on [%s]\n",
 +                  sqlite3_extended_errcode(DBX(psx)),
 +                  sqlite3_errmsg(DBX(psx)), zQuery);
 +      goto end_schema_xfer;
      }
 -    /* 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; i<ArraySize(azHelp); i++){
 -      if( azHelp[i][0]==',' ){
 -        while( i<ArraySize(azHelp)-1 && azHelp[i+1][0]==' ' ) ++i;
 -        continue;
 +    while( (rc = s3_step_noom(pQuery))==SQLITE_ROW ){
 +      zName = sqlite3_column_text(pQuery, 0);
 +      zSql = sqlite3_column_text(pQuery, 1);
 +      if( zName==0 || zSql==0 ) continue;
 +      if( sqlite3_stricmp((char*)zName, "sqlite_sequence")==0 ) continue;
 +      /* Consider directing ... */
 +      fprintf(STD_OUT, "%s... ", zName); fflush(STD_OUT);
 +      s3_exec_noom(newDb, (const char*)zSql, 0, 0, &zErrMsg);
 +      if( zErrMsg ){
 +        utf8_printf(STD_ERR, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql);
 +        sqlite3_free(zErrMsg);
 +        zErrMsg = 0;
        }
 -      if( azHelp[i][0]=='.' ) j = i;
 -      if( sqlite3_strlike(zPat, azHelp[i], 0)==0 ){
 -        utf8_printf(out, "%s\n", azHelp[j]);
 -        while( j<ArraySize(azHelp)-1 && azHelp[j+1][0]==' ' ){
 -          j++;
 -          utf8_printf(out, "%s\n", azHelp[j]);
 -        }
 -        i = j;
 -        n++;
 +      if( xForEach ){
 +        xForEach(psx, newDb, (const char*)zName);
        }
 +      /* Consider directing ... */
 +      fprintf(STD_OUT, "done\n");
      }
 -    sqlite3_free(zPat);
    }
 -  return n;
 +end_schema_xfer:
 +  RESOURCE_FREE(mark);
  }
  
 -/* Forward reference */
 -static int process_input(ShellState *p);
 +/*
 +** Open a new database file named "zNewDb".  Try to recover as much information
 +** as possible out of the main database (which might be corrupt) and write it
 +** into zNewDb.
 +*/
 +static void tryToClone(ShellExState *psx, const char *zNewDb){
 +  int rc;
 +  sqlite3 *newDb = 0;
 +  if( access(zNewDb,0)==0 ){
 +    utf8_printf(STD_ERR, "File \"%s\" already exists.\n", zNewDb);
 +    return;
 +  }
 +  rc = sqlite3_open(zNewDb, &newDb);
 +  if( rc ){
 +    utf8_printf(STD_ERR, "Cannot create output database: %s\n",
 +            sqlite3_errmsg(newDb));
 +  }else{
 +    // sqlite3_exec(DBX(psx), "PRAGMA writable_schema=ON;", 0, 0, 0);
 +    sqlite3_db_config(newDb, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, 0);
 +    sqlite3_db_config(newDb, SQLITE_DBCONFIG_DEFENSIVE, 0, 0);
 +    sqlite3_exec(newDb, "BEGIN EXCLUSIVE;", 0, 0, 0);
 +    tryToCloneSchema(psx, newDb, "type='table'", tryToCloneData);
 +    tryToCloneSchema(psx, newDb, "type!='table'", 0);
 +    sqlite3_exec(newDb, "COMMIT;", 0, 0, 0);
 +    // sqlite3_exec(DBX(psx), "PRAGMA writable_schema=OFF;", 0, 0, 0);
 +  }
 +  close_db(newDb);
 +}
  
  /*
 -** 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.
 +** Change the output file back to stdout.
  **
 -** 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.
 +** If the psi->doXdgOpen flag is set, that means the output was being
 +** redirected to a temporary file named by psi->zTempFile.  In that case,
 +** launch start/open/xdg-open on that temporary file.
 +*/
 +static void output_reset(ShellInState *psi){
 +  if( psi->outfile[0]=='|' ){
 +#ifndef SQLITE_OMIT_POPEN
 +    pclose(psi->out);
 +#endif
 +  }else{
 +    output_file_close(psi->out);
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +    if( psi->doXdgOpen ){
 +      const char *zXdgOpenCmd =
 +#if defined(_WIN32)
 +      "start";
 +#elif defined(__APPLE__)
 +      "open";
 +#else
 +      "xdg-open";
 +#endif
 +      char *zCmd;
 +      zCmd = smprintf("%s %s", zXdgOpenCmd, psi->zTempFile);
 +      if( system(zCmd) ){
 +        utf8_printf(STD_ERR, "Failed: [%s]\n", zCmd);
 +      }else{
 +        /* Give the start/open/xdg-open command some time to get
 +        ** going before we continue, and potential delete the
 +        ** psi->zTempFile data file out from under it */
 +        sqlite3_sleep(2000);
 +      }
 +      sqlite3_free(zCmd);
 +      outputModePop(psi);
 +      psi->doXdgOpen = 0;
 +    }
 +#endif /* !defined(SQLITE_NOHAVE_SYSTEM) */
 +  }
 +  psi->outfile[0] = 0;
 +  psi->out = STD_OUT;
 +}
 +
 +/*
 +** Run an SQL command and return the single integer result.
 +** No parameter binding is done.
 +*/
 +static int db_int(sqlite3 *db, const char *zSql){
 +  sqlite3_stmt *pStmt;
 +  int res = 0;
 +  s3_prepare_v2_noom(db, zSql, -1, &pStmt, 0);
 +  stmt_holder(pStmt);
 +  if( pStmt && s3_step_noom(pStmt)==SQLITE_ROW ){
 +    res = sqlite3_column_int(pStmt,0);
 +  }
 +  release_holder();
 +  return res;
 +}
 +
 +/*
 +** Run an SQL command and return the single text result,
 +** Parameter binding is done iff bBind is true.
 +** The return must be freed by caller using sqlite3_free().
 +*/
 +static char *db_text(sqlite3 *db, const char *zSql, int bBind){
 +  sqlite3_stmt *pStmt;
 +  char *zRes = 0;
 +  if( s3_prepare_v2_noom(db, zSql, -1, &pStmt, 0)==SQLITE_OK && pStmt!=0 ){
 +    stmt_holder(pStmt);
 +    if( bBind ) bind_prepared_stmt(db, pStmt);
 +    if( s3_step_noom(pStmt)==SQLITE_ROW ){
 +      shell_check_ooms(zRes = smprintf("%s", sqlite3_column_text(pStmt,0)));
 +    }
 +    release_holder();
 +  }
 +  return zRes;
 +}
 +
 +#if SQLITE_SHELL_HAVE_RECOVER
 +/*
 +** Convert a 2-byte or 4-byte big-endian integer into a native integer
 +*/
 +static unsigned int get2byteInt(unsigned char *a){
 +  return (a[0]<<8) + a[1];
 +}
 +static unsigned int get4byteInt(unsigned char *a){
 +  return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3];
 +}
 +
 +/*
 +** Implementation of the ".dbinfo" command.
  **
 -** NULL is returned if any error is encountered. The final value of *pnByte
 -** is undefined in this case.
 +** Return 1 on error, 2 to exit, and 0 otherwise.
  */
 -static char *readFile(const char *zName, int *pnByte){
 -  FILE *in = fopen(zName, "rb");
 -  long nIn;
 -  size_t nRead;
 -  char *pBuf;
 -  int rc;
 -  if( in==0 ) return 0;
 -  rc = fseek(in, 0, SEEK_END);
 -  if( rc!=0 ){
 -    raw_printf(stderr, "Error: '%s' not seekable\n", zName);
 -    fclose(in);
 -    return 0;
 +static int shell_dbinfo_command(ShellExState *psx, int nArg, char **azArg){
 +  static const struct { const char *zName; int ofst; } aField[] = {
 +     { "file change counter:",  24  },
 +     { "database page count:",  28  },
 +     { "freelist page count:",  36  },
 +     { "schema cookie:",        40  },
 +     { "schema format:",        44  },
 +     { "default cache size:",   48  },
 +     { "autovacuum top root:",  52  },
 +     { "incremental vacuum:",   64  },
 +     { "text encoding:",        56  },
 +     { "user version:",         60  },
 +     { "application id:",       68  },
 +     { "software version:",     96  },
 +  };
 +  static const struct { const char *zName; const char *zSql; } aQuery[] = {
 +     { "number of tables:",
 +       "SELECT count(*) FROM %s WHERE type='table'" },
 +     { "number of indexes:",
 +       "SELECT count(*) FROM %s WHERE type='index'" },
 +     { "number of triggers:",
 +       "SELECT count(*) FROM %s WHERE type='trigger'" },
 +     { "number of views:",
 +       "SELECT count(*) FROM %s WHERE type='view'" },
 +     { "schema size:",
 +       "SELECT total(length(sql)) FROM %s" },
 +  };
 +  int i, rc;
 +  unsigned iDataVersion;
 +  char *zSchemaTab;
 +  char *zDb = nArg>=2 ? azArg[1] : "main";
 +  sqlite3_stmt *pStmt = 0;
 +  unsigned char aHdr[100];
 +  FILE *out = ISS(psx)->out;
 +
 +  open_db(psx, 0);
 +  if( DBX(psx)==0 ) return 1;
 +  rc = s3_prepare_v2_noom(DBX(psx),
 +             "SELECT data FROM sqlite_dbpage(?1) WHERE pgno=1",
 +             -1, &pStmt, 0);
 +  if( rc ){
 +    utf8_printf(STD_ERR, "error: %s\n", sqlite3_errmsg(DBX(psx)));
 +    sqlite3_finalize(pStmt);
 +    return 1;
    }
 -  nIn = ftell(in);
 -  rewind(in);
 -  pBuf = sqlite3_malloc64( nIn+1 );
 -  if( pBuf==0 ){
 -    raw_printf(stderr, "Error: out of memory\n");
 -    fclose(in);
 -    return 0;
 +  sqlite3_bind_text(pStmt, 1, zDb, -1, SQLITE_STATIC);
 +  if( sqlite3_step(pStmt)==SQLITE_ROW
 +   && sqlite3_column_bytes(pStmt,0)>100
 +  ){
 +    const u8 *pb = sqlite3_column_blob(pStmt,0);
 +    shell_check_ooms(pb);
 +    memcpy(aHdr, pb, 100);
 +    sqlite3_finalize(pStmt);
 +  }else{
 +    raw_printf(STD_ERR, "unable to read database header\n");
 +    sqlite3_finalize(pStmt);
 +    return 1;
    }
 -  nRead = fread(pBuf, nIn, 1, in);
 -  fclose(in);
 -  if( nRead!=1 ){
 -    sqlite3_free(pBuf);
 -    raw_printf(stderr, "Error: cannot read '%s'\n", zName);
 -    return 0;
 +  i = get2byteInt(aHdr+16);
 +  if( i==1 ) i = 65536;
 +  utf8_printf(out, "%-20s %d\n", "database page size:", i);
 +  utf8_printf(out, "%-20s %d\n", "write format:", aHdr[18]);
 +  utf8_printf(out, "%-20s %d\n", "read format:", aHdr[19]);
 +  utf8_printf(out, "%-20s %d\n", "reserved bytes:", aHdr[20]);
 +  for(i=0; i<ArraySize(aField); i++){
 +    int ofst = aField[i].ofst;
 +    unsigned int val = get4byteInt(aHdr + ofst);
 +    utf8_printf(out, "%-20s %u", aField[i].zName, val);
 +    switch( ofst ){
 +      case 56: {
 +        if( val==1 ) raw_printf(out, " (utf8)");
 +        if( val==2 ) raw_printf(out, " (utf16le)");
 +        if( val==3 ) raw_printf(out, " (utf16be)");
 +      }
 +    }
 +    raw_printf(out, "\n");
    }
 -  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; i<pSession->nFilter; i++){
 -    sqlite3_free(pSession->azFilter[i]);
 +  if( zDb==0 ){
 +    zSchemaTab = smprintf("main.sqlite_schema");
 +  }else if( cli_strcmp(zDb,"temp")==0 ){
 +    zSchemaTab = smprintf("%s", "sqlite_temp_schema");
 +  }else{
 +    zSchemaTab = smprintf("\"%w\".sqlite_schema", zDb);
    }
 -  sqlite3_free(pSession->azFilter);
 -  memset(pSession, 0, sizeof(OpenSession));
 -}
 -#endif
 -
 -/*
 -** Close all OpenSession objects and release all associated resources.
 -*/
 -#if defined(SQLITE_ENABLE_SESSION)
 -static void session_close_all(ShellState *p, int i){
 -  int j;
 -  struct AuxDb *pAuxDb = i<0 ? p->pAuxDb : &p->aAuxDb[i];
 -  for(j=0; j<pAuxDb->nSession; j++){
 -    session_close(&pAuxDb->aSession[j]);
 +  for(i=0; i<ArraySize(aQuery); i++){
 +    char *zSql = smprintf(aQuery[i].zSql, zSchemaTab);
 +    int val = db_int(DBX(psx), zSql);
 +    sqlite3_free(zSql);
 +    utf8_printf(out, "%-20s %d\n", aQuery[i].zName, val);
    }
 -  pAuxDb->nSession = 0;
 +  sqlite3_free(zSchemaTab);
 +  sqlite3_file_control(DBX(psx), zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion);
 +  utf8_printf(out, "%-20s %u\n", "data version", iDataVersion);
 +  return 0;
  }
 -#else
 -# define session_close_all(X,Y)
 -#endif
 +#endif /* SQLITE_SHELL_HAVE_RECOVER */
  
  /*
 -** Implementation of the xFilter function for an open session.  Omit
 -** any tables named by ".session filter" but let all other table through.
 +** Print the current sqlite3_errmsg() value to stderr and return 1.
  */
 -#if defined(SQLITE_ENABLE_SESSION)
 -static int session_filter(void *pCtx, const char *zTab){
 -  OpenSession *pSession = (OpenSession*)pCtx;
 -  int i;
 -  for(i=0; i<pSession->nFilter; i++){
 -    if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0;
 -  }
 +static int shellDatabaseError(sqlite3 *db){
 +  const char *zErr = sqlite3_errmsg(db);
 +  utf8_printf(STD_ERR, "Error: %s\n", zErr);
    return 1;
  }
 -#endif
  
  /*
 -** Try to deduce the type of file for zName based on its content.  Return
 -** one of the SHELL_OPEN_* constants.
 +** Compare the pattern in zGlob[] against the text in z[].  Return TRUE
 +** if they match and FALSE (0) if they do not match.
  **
 -** If the file does not exist or is empty but its name looks like a ZIP
 -** archive and the dfltZip flag is true, then assume it is a ZIP archive.
 -** Otherwise, assume an ordinary database regardless of the filename if
 -** the type cannot be determined from content.
 +** Globbing rules:
 +**
 +**      '*'       Matches any sequence of zero or more characters.
 +**
 +**      '?'       Matches exactly one character.
 +**
 +**     [...]      Matches one character from the enclosed list of
 +**                characters.
 +**
 +**     [^...]     Matches one character not in the enclosed list.
 +**
 +**      '#'       Matches any sequence of one or more digits with an
 +**                optional + or - sign in front
 +**
 +**      ' '       Any span of whitespace matches any other span of
 +**                whitespace.
 +**
 +** Extra whitespace at the end of z[] is ignored.
  */
 -int deduceDatabaseType(const char *zName, int dfltZip){
 -  FILE *f = fopen(zName, "rb");
 -  size_t n;
 -  int rc = SHELL_OPEN_UNSPEC;
 -  char zBuf[100];
 -  if( f==0 ){
 -    if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
 -       return SHELL_OPEN_ZIPFILE;
 +static int testcase_glob(const char *zGlob, const char *z){
 +  int c, c2;
 +  int invert;
 +  int seen;
 +
 +  while( (c = (*(zGlob++)))!=0 ){
 +    if( IsSpace(c) ){
 +      if( !IsSpace(*z) ) return 0;
 +      zGlob = skipWhite(zGlob);
 +      z = skipWhite(z);
 +    }else if( c=='*' ){
 +      while( (c=(*(zGlob++))) == '*' || c=='?' ){
 +        if( c=='?' && (*(z++))==0 ) return 0;
 +      }
 +      if( c==0 ){
 +        return 1;
 +      }else if( c=='[' ){
 +        while( *z && testcase_glob(zGlob-1,z)==0 ){
 +          z++;
 +        }
 +        return (*z)!=0;
 +      }
 +      while( (c2 = (*(z++)))!=0 ){
 +        while( c2!=c ){
 +          c2 = *(z++);
 +          if( c2==0 ) return 0;
 +        }
 +        if( testcase_glob(zGlob,z) ) return 1;
 +      }
 +      return 0;
 +    }else if( c=='?' ){
 +      if( (*(z++))==0 ) return 0;
 +    }else if( c=='[' ){
 +      int prior_c = 0;
 +      seen = 0;
 +      invert = 0;
 +      c = *(z++);
 +      if( c==0 ) return 0;
 +      c2 = *(zGlob++);
 +      if( c2=='^' ){
 +        invert = 1;
 +        c2 = *(zGlob++);
 +      }
 +      if( c2==']' ){
 +        if( c==']' ) seen = 1;
 +        c2 = *(zGlob++);
 +      }
 +      while( c2 && c2!=']' ){
 +        if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
 +          c2 = *(zGlob++);
 +          if( c>=prior_c && c<=c2 ) seen = 1;
 +          prior_c = 0;
 +        }else{
 +          if( c==c2 ){
 +            seen = 1;
 +          }
 +          prior_c = c2;
 +        }
 +        c2 = *(zGlob++);
 +      }
 +      if( c2==0 || (seen ^ invert)==0 ) return 0;
 +    }else if( c=='#' ){
 +      if( (z[0]=='-' || z[0]=='+') && IsDigit(z[1]) ) z++;
 +      if( !IsDigit(z[0]) ) return 0;
 +      z++;
 +      while( IsDigit(z[0]) ){ z++; }
      }else{
 -       return SHELL_OPEN_NORMAL;
 -    }
 -  }
 -  n = fread(zBuf, 16, 1, f);
 -  if( n==1 && memcmp(zBuf, "SQLite format 3", 16)==0 ){
 -    fclose(f);
 -    return SHELL_OPEN_NORMAL;
 -  }
 -  fseek(f, -25, SEEK_END);
 -  n = fread(zBuf, 25, 1, f);
 -  if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){
 -    rc = SHELL_OPEN_APPENDVFS;
 -  }else{
 -    fseek(f, -22, SEEK_END);
 -    n = fread(zBuf, 22, 1, f);
 -    if( n==1 && zBuf[0]==0x50 && zBuf[1]==0x4b && zBuf[2]==0x05
 -       && zBuf[3]==0x06 ){
 -      rc = SHELL_OPEN_ZIPFILE;
 -    }else if( n==0 && dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
 -      rc = SHELL_OPEN_ZIPFILE;
 +      if( c!=(*(z++)) ) return 0;
      }
    }
 -  fclose(f);
 -  return rc;
 +  return *skipWhite(z)==0;
  }
  
 -#ifndef SQLITE_OMIT_DESERIALIZE
  /*
 -** Reconstruct an in-memory database using the output from the "dbtotxt"
 -** program.  Read content from the file in p->aAuxDb[].zDbFilename.
 -** If p->aAuxDb[].zDbFilename is 0, then read from standard input.
 +** Compare the string as a command-line option with either one or two
 +** initial "-" characters.
  */
 -static unsigned char *readHexDb(ShellState *p, int *pnData){
 -  unsigned char *a = 0;
 -  int nLine;
 -  int n = 0;
 -  int pgsz = 0;
 -  int iOffset = 0;
 -  int j, k;
 -  int rc;
 -  FILE *in;
 -  const char *zDbFilename = p->pAuxDb->zDbFilename;
 -  unsigned int x[16];
 -  char zLine[1000];
 -  if( zDbFilename ){
 -    in = fopen(zDbFilename, "r");
 -    if( in==0 ){
 -      utf8_printf(stderr, "cannot open \"%s\" for reading\n", zDbFilename);
 -      return 0;
 -    }
 -    nLine = 0;
 -  }else{
 -    in = p->in;
 -    nLine = p->lineno;
 -    if( in==0 ) in = stdin;
 -  }
 -  *pnData = 0;
 -  nLine++;
 -  if( fgets(zLine, sizeof(zLine), in)==0 ) goto readHexDb_error;
 -  rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz);
 -  if( rc!=2 ) goto readHexDb_error;
 -  if( n<0 ) goto readHexDb_error;
 -  if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ) goto readHexDb_error;
 -  n = (n+pgsz-1)&~(pgsz-1);  /* Round n up to the next multiple of pgsz */
 -  a = sqlite3_malloc( n ? n : 1 );
 -  shell_check_oom(a);
 -  memset(a, 0, n);
 -  if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){
 -    utf8_printf(stderr, "invalid pagesize\n");
 -    goto readHexDb_error;
 -  }
 -  for(nLine++; fgets(zLine, sizeof(zLine), in)!=0; nLine++){
 -    rc = sscanf(zLine, "| page %d offset %d", &j, &k);
 -    if( rc==2 ){
 -      iOffset = k;
 -      continue;
 -    }
 -    if( cli_strncmp(zLine, "| end ", 6)==0 ){
 -      break;
 -    }
 -    rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
 -                &j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7],
 -                &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]);
 -    if( rc==17 ){
 -      k = iOffset+j;
 -      if( k+16<=n && k>=0 ){
 -        int ii;
 -        for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff;
 -      }
 -    }
 -  }
 -  *pnData = n;
 -  if( in!=p->in ){
 -    fclose(in);
 -  }else{
 -    p->lineno = nLine;
 -  }
 -  return a;
 +static int optionMatch(const char *zStr, const char *zOpt){
 +  if( zStr[0]!='-' ) return 0;
 +  zStr++;
 +  if( zStr[0]=='-' ) zStr++;
 +  return cli_strcmp(zStr, zOpt)==0;
 +}
  
 -readHexDb_error:
 -  if( in!=p->in ){
 -    fclose(in);
 -  }else{
 -    while( fgets(zLine, sizeof(zLine), p->in)!=0 ){
 -      nLine++;
 -      if(cli_strncmp(zLine, "| end ", 6)==0 ) break;
 -    }
 -    p->lineno = nLine;
 -  }
 -  sqlite3_free(a);
 -  utf8_printf(stderr,"Error on line %d of --hexdb input\n", nLine);
 -  return 0;
 +/*
 +** Delete a file.
 +*/
 +int shellDeleteFile(const char *zFilename){
 +  int rc;
 +#ifdef _WIN32
 +  wchar_t *z = sqlite3_win32_utf8_to_unicode(zFilename);
 +  rc = _wunlink(z);
 +  sqlite3_free(z);
 +#else
 +  rc = unlink(zFilename);
 +#endif
 +  return rc;
  }
 -#endif /* SQLITE_OMIT_DESERIALIZE */
  
  /*
 -** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X.
 +** Try to delete the temporary file (if there is one) and free the
 +** memory used to hold the name of the temp file.
  */
 -static void shellUSleepFunc(
 -  sqlite3_context *context,
 -  int argcUnused,
 -  sqlite3_value **argv
 -){
 -  int sleep = sqlite3_value_int(argv[0]);
 -  (void)argcUnused;
 -  sqlite3_sleep(sleep/1000);
 -  sqlite3_result_int(context, sleep);
 +static void clearTempFile(ShellInState *psi){
 +  if( psi->zTempFile==0 ) return;
 +  if( psi->doXdgOpen ) return;
 +  if( shellDeleteFile(psi->zTempFile) ) return;
 +  sqlite3_free(psi->zTempFile);
 +  psi->zTempFile = 0;
  }
  
 -/* Flags for open_db().
 -**
 -** The default behavior of open_db() is to exit(1) if the database fails to
 -** open.  The OPEN_DB_KEEPALIVE flag changes that so that it prints an error
 -** but still returns without calling exit.
 -**
 -** The OPEN_DB_ZIPFILE flag causes open_db() to prefer to open files as a
 -** ZIP archive if the file does not exist or is empty and its name matches
 -** the *.zip pattern.
 +/*
 +** Create a new temp file name with the given suffix.
  */
 -#define OPEN_DB_KEEPALIVE   0x001   /* Return after error if true */
 -#define OPEN_DB_ZIPFILE     0x002   /* Open as ZIP if name matches *.zip */
 +static void newTempFile(ShellInState *psi, const char *zSuffix){
 +  clearTempFile(psi);
 +  sqlite3_free(psi->zTempFile);
 +  psi->zTempFile = 0;
 +  if( DBI(psi) ){
 +    sqlite3_file_control(DBI(psi), 0, SQLITE_FCNTL_TEMPFILENAME,
 +                         &psi->zTempFile);
 +  }
 +  if( psi->zTempFile==0 ){
 +    /* If DB is an in-memory database then the TEMPFILENAME file-control
 +    ** will not work and we will need to fallback to guessing */
 +    char *zTemp;
 +    sqlite3_uint64 r;
 +    sqlite3_randomness(sizeof(r), &r);
 +    zTemp = getenv("TEMP");
 +    if( zTemp==0 ) zTemp = getenv("TMP");
 +    if( zTemp==0 ){
 +#ifdef _WIN32
 +      zTemp = "\\tmp";
 +#else
 +      zTemp = "/tmp";
 +#endif
 +    }
 +    psi->zTempFile = smprintf("%s/temp%llx.%s", zTemp, r, zSuffix);
 +  }else{
 +    psi->zTempFile = smprintf("%z.%s", psi->zTempFile, zSuffix);
 +  }
 +  shell_check_ooms(psi->zTempFile);
 +}
  
  /*
 -** Make sure the database is open.  If it is not, then open it.  If
 -** the database fails to open, print an error message and exit.
 +** The implementation of SQL scalar function fkey_collate_clause(), used
 +** by the ".lint fkey-indexes" command. This scalar function is always
 +** called with four arguments - the parent table name, the parent column name,
 +** the child table name and the child column name.
 +**
 +**   fkey_collate_clause('parent-tab', 'parent-col', 'child-tab', 'child-col')
 +**
 +** If either of the named tables or columns do not exist, this function
 +** returns an empty string. An empty string is also returned if both tables
 +** and columns exist but have the same default collation sequence. Or,
 +** if both exist but the default collation sequences are different, this
 +** function returns the string " COLLATE <parent-collation>", where
 +** <parent-collation> is the default collation sequence of the parent column.
  */
 -static void open_db(ShellState *p, int openFlags){
 -  if( p->db==0 ){
 -    const char *zDbFilename = p->pAuxDb->zDbFilename;
 -    if( p->openMode==SHELL_OPEN_UNSPEC ){
 -      if( zDbFilename==0 || zDbFilename[0]==0 ){
 -        p->openMode = SHELL_OPEN_NORMAL;
 -      }else{
 -        p->openMode = (u8)deduceDatabaseType(zDbFilename,
 -                             (openFlags & OPEN_DB_ZIPFILE)!=0);
 -      }
 -    }
 -    switch( p->openMode ){
 -      case SHELL_OPEN_APPENDVFS: {
 -        sqlite3_open_v2(zDbFilename, &p->db,
 -           SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, "apndvfs");
 -        break;
 -      }
 -      case SHELL_OPEN_HEXDB:
 -      case SHELL_OPEN_DESERIALIZE: {
 -        sqlite3_open(0, &p->db);
 -        break;
 -      }
 -      case SHELL_OPEN_ZIPFILE: {
 -        sqlite3_open(":memory:", &p->db);
 -        break;
 -      }
 -      case SHELL_OPEN_READONLY: {
 -        sqlite3_open_v2(zDbFilename, &p->db,
 -            SQLITE_OPEN_READONLY|p->openFlags, 0);
 -        break;
 -      }
 -      case SHELL_OPEN_UNSPEC:
 -      case SHELL_OPEN_NORMAL: {
 -        sqlite3_open_v2(zDbFilename, &p->db,
 -           SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, 0);
 -        break;
 -      }
 -    }
 -    globalDb = p->db;
 -    if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){
 -      utf8_printf(stderr,"Error: unable to open database \"%s\": %s\n",
 -          zDbFilename, sqlite3_errmsg(p->db));
 -      if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){
 -        exit(1);
 -      }
 -      sqlite3_close(p->db);
 -      sqlite3_open(":memory:", &p->db);
 -      if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){
 -        utf8_printf(stderr,
 -          "Also: unable to open substitute in-memory database.\n"
 -        );
 -        exit(1);
 -      }else{
 -        utf8_printf(stderr,
 -          "Notice: using substitute in-memory database instead of \"%s\"\n",
 -          zDbFilename);
 -      }
 -    }
 -    sqlite3_db_config(p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, (int)0, (int*)0);
 +static void shellFkeyCollateClause(
 +  sqlite3_context *pCtx,
 +  int nVal,
 +  sqlite3_value **apVal
 +){
 +  sqlite3 *db = sqlite3_context_db_handle(pCtx);
 +  const char *zParent;
 +  const char *zParentCol;
 +  const char *zParentSeq;
 +  const char *zChild;
 +  const char *zChildCol;
 +  const char *zChildSeq = 0;  /* Initialize to avoid false-positive warning */
 +  int rc;
  
 -    /* Reflect the use or absence of --unsafe-testing invocation. */
 -    {
 -      int testmode_on = ShellHasFlag(p,SHFLG_TestingMode);
 -      sqlite3_db_config(p->db, SQLITE_DBCONFIG_TRUSTED_SCHEMA, testmode_on,0);
 -      sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0);
 -    }
 +  assert( nVal==4 );
 +  zParent = (const char*)sqlite3_value_text(apVal[0]);
 +  zParentCol = (const char*)sqlite3_value_text(apVal[1]);
 +  zChild = (const char*)sqlite3_value_text(apVal[2]);
 +  zChildCol = (const char*)sqlite3_value_text(apVal[3]);
  
 -#ifndef SQLITE_OMIT_LOAD_EXTENSION
 -    sqlite3_enable_load_extension(p->db, 1);
 -#endif
 -    sqlite3_shathree_init(p->db, 0, 0);
 -    sqlite3_uint_init(p->db, 0, 0);
 -    sqlite3_decimal_init(p->db, 0, 0);
 -    sqlite3_base64_init(p->db, 0, 0);
 -    sqlite3_base85_init(p->db, 0, 0);
 -    sqlite3_regexp_init(p->db, 0, 0);
 -    sqlite3_ieee_init(p->db, 0, 0);
 -    sqlite3_series_init(p->db, 0, 0);
 -#ifndef SQLITE_SHELL_FIDDLE
 -    sqlite3_fileio_init(p->db, 0, 0);
 -    sqlite3_completion_init(p->db, 0, 0);
 -#endif
 -#ifdef SQLITE_HAVE_ZLIB
 -    if( !p->bSafeModePersist ){
 -      sqlite3_zipfile_init(p->db, 0, 0);
 -      sqlite3_sqlar_init(p->db, 0, 0);
 -    }
 -#endif
 -#ifdef SQLITE_SHELL_EXTFUNCS
 -    /* Create a preprocessing mechanism for extensions to make
 -     * their own provisions for being built into the shell.
 -     * This is a short-span macro. See further below for usage.
 -     */
 -#define SHELL_SUB_MACRO(base, variant) base ## _ ## variant
 -#define SHELL_SUBMACRO(base, variant) SHELL_SUB_MACRO(base, variant)
 -    /* Let custom-included extensions get their ..._init() called.
 -     * The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause
 -     * the extension's sqlite3_*_init( db, pzErrorMsg, pApi )
 -     * inititialization routine to be called.
 -     */
 -    {
 -      int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db);
 -    /* Let custom-included extensions expose their functionality.
 -     * The WHATEVER_EXPOSE( db, pzErrorMsg ) macro should cause
 -     * the SQL functions, virtual tables, collating sequences or
 -     * VFS's implemented by the extension to be registered.
 -     */
 -      if( irc==SQLITE_OK
 -          || irc==SQLITE_OK_LOAD_PERMANENTLY ){
 -        SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, EXPOSE)(p->db, 0);
 -      }
 -#undef SHELL_SUB_MACRO
 -#undef SHELL_SUBMACRO
 -    }
 -#endif
 +  sqlite3_result_text(pCtx, "", -1, SQLITE_STATIC);
 +  rc = sqlite3_table_column_metadata(
 +      db, "main", zParent, zParentCol, 0, &zParentSeq, 0, 0, 0
 +  );
 +  if( rc==SQLITE_OK ){
 +    rc = sqlite3_table_column_metadata(
 +        db, "main", zChild, zChildCol, 0, &zChildSeq, 0, 0, 0
 +    );
 +  }
  
 -    sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0,
 -                            shellAddSchemaName, 0, 0);
 -    sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0,
 -                            shellModuleSchema, 0, 0);
 -    sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p,
 -                            shellPutsFunc, 0, 0);
 -    sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0,
 -                            shellUSleepFunc, 0, 0);
 -#ifndef SQLITE_NOHAVE_SYSTEM
 -    sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0,
 -                            editFunc, 0, 0);
 -    sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0,
 -                            editFunc, 0, 0);
 -#endif
 +  if( rc==SQLITE_OK && sqlite3_stricmp(zParentSeq, zChildSeq) ){
 +    char *z = smprintf(" COLLATE %s", zParentSeq);
 +    sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT);
 +    sqlite3_free(z);
 +  }
 +}
  
 -    if( p->openMode==SHELL_OPEN_ZIPFILE ){
 -      char *zSql = sqlite3_mprintf(
 -         "CREATE VIRTUAL TABLE zip USING zipfile(%Q);", zDbFilename);
 -      shell_check_oom(zSql);
 -      sqlite3_exec(p->db, zSql, 0, 0, 0);
 -      sqlite3_free(zSql);
 +#if !defined SQLITE_OMIT_VIRTUALTABLE
 +static void shellPrepare(
 +  sqlite3 *db,
 +  int *pRc,
 +  const char *zSql,
 +  sqlite3_stmt **ppStmt
 +){
 +  *ppStmt = 0;
 +  if( *pRc==SQLITE_OK ){
 +    int rc = s3_prepare_v2_noom(db, zSql, -1, ppStmt, 0);
 +    if( rc!=SQLITE_OK ){
 +      raw_printf(STD_ERR, "sql error: %s (%d)\n",
 +          sqlite3_errmsg(db), sqlite3_errcode(db)
 +      );
 +      *pRc = rc;
      }
 -#ifndef SQLITE_OMIT_DESERIALIZE
 -    else
 -    if( p->openMode==SHELL_OPEN_DESERIALIZE || p->openMode==SHELL_OPEN_HEXDB ){
 -      int rc;
 -      int nData = 0;
 -      unsigned char *aData;
 -      if( p->openMode==SHELL_OPEN_DESERIALIZE ){
 -        aData = (unsigned char*)readFile(zDbFilename, &nData);
 -      }else{
 -        aData = readHexDb(p, &nData);
 -      }
 -      if( aData==0 ){
 -        return;
 -      }
 -      rc = sqlite3_deserialize(p->db, "main", aData, nData, nData,
 -                   SQLITE_DESERIALIZE_RESIZEABLE |
 -                   SQLITE_DESERIALIZE_FREEONCLOSE);
 -      if( rc ){
 -        utf8_printf(stderr, "Error: sqlite3_deserialize() returns %d\n", rc);
 -      }
 -      if( p->szMax>0 ){
 -        sqlite3_file_control(p->db, "main", SQLITE_FCNTL_SIZE_LIMIT, &p->szMax);
 +  }
 +}
 +
 +/*
 +** Create a prepared statement using printf-style arguments for the SQL.
 +**
 +** This routine is could be marked "static".  But it is not always used,
 +** depending on compile-time options.  By omitting the "static", we avoid
 +** nuisance compiler warnings about "defined but not used".
 +*/
 +void shellPreparePrintf(
 +  sqlite3 *db,
 +  int *pRc,
 +  sqlite3_stmt **ppStmt,
 +  const char *zFmt,
 +  ...
 +){
 +  *ppStmt = 0;
 +  if( *pRc==SQLITE_OK ){
 +    va_list ap;
 +    char *z;
 +    va_start(ap, zFmt);
 +    z = sqlite3_vmprintf(zFmt, ap);
 +    va_end(ap);
 +    if( z==0 ){
 +      *pRc = SQLITE_NOMEM;
 +    }else{
 +      shellPrepare(db, pRc, z, ppStmt);
 +      sqlite3_free(z);
 +    }
 +  }
 +}
 +
 +/* Finalize the prepared statement created using shellPreparePrintf().
 +**
 +** This routine is could be marked "static".  But it is not always used,
 +** depending on compile-time options.  By omitting the "static", we avoid
 +** nuisance compiler warnings about "defined but not used".
 +*/
 +void shellFinalize(
 +  int *pRc,
 +  sqlite3_stmt *pStmt
 +){
 +  if( pStmt ){
 +    sqlite3 *db = sqlite3_db_handle(pStmt);
 +    int rc = sqlite3_finalize(pStmt);
 +    if( *pRc==SQLITE_OK ){
 +      if( rc!=SQLITE_OK ){
 +        raw_printf(STD_ERR, "SQL error: %s\n", sqlite3_errmsg(db));
        }
 +      *pRc = rc;
      }
 -#endif
    }
 -  if( p->db!=0 ){
 -    if( p->bSafeModePersist ){
 -      sqlite3_set_authorizer(p->db, safeModeAuth, p);
 +}
 +
 +/* Reset the prepared statement created using shellPreparePrintf().
 +**
 +** This routine is could be marked "static".  But it is not always used,
 +** depending on compile-time options.  By omitting the "static", we avoid
 +** nuisance compiler warnings about "defined but not used".
 +*/
 +void shellReset(
 +  int *pRc,
 +  sqlite3_stmt *pStmt
 +){
 +  int rc = sqlite3_reset(pStmt);
 +  if( *pRc==SQLITE_OK ){
 +    if( rc!=SQLITE_OK ){
 +      sqlite3 *db = sqlite3_db_handle(pStmt);
 +      raw_printf(STD_ERR, "SQL error: %s\n", sqlite3_errmsg(db));
      }
 -    sqlite3_db_config(
 -        p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0
 -    );
 +    *pRc = rc;
    }
  }
 +#endif /* !defined SQLITE_OMIT_VIRTUALTABLE */
 +
 +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
 +# define ARCHIVE_ENABLE 1
 +#else
 +# define ARCHIVE_ENABLE 0
 +#endif
  
 +#if ARCHIVE_ENABLE && !defined(SQLITE_SHELL_FIDDLE)
 +/******************************************************************************
 +** The ".archive" or ".ar" command.
 +*/
  /*
 -** Attempt to close the databaes connection.  Report errors.
 +** Structure representing a single ".ar" command.
  */
 -void close_db(sqlite3 *db){
 -  int rc = sqlite3_close(db);
 -  if( rc ){
 -    utf8_printf(stderr, "Error: sqlite3_close() returns %d: %s\n",
 -        rc, sqlite3_errmsg(db));
 +typedef struct ArCommand ArCommand;
 +struct ArCommand {
 +  u8 eCmd;                        /* An AR_CMD_* value */
 +  u8 bVerbose;                    /* True if --verbose */
 +  u8 bZip;                        /* True if the archive is a ZIP */
 +  u8 bDryRun;                     /* True if --dry-run */
 +  u8 bAppend;                     /* True if --append */
 +  u8 bGlob;                       /* True if --glob */
 +  u8 fromCmdLine;                 /* Run from -A instead of .archive */
 +  int nArg;                       /* Number of command arguments */
 +  char *zSrcTable;                /* "sqlar", "zipfile($file)" or "zip" */
 +  const char *zFile;              /* --file argument, or NULL */
 +  const char *zDir;               /* --directory argument, or NULL */
 +  char **azArg;                   /* Array of command arguments */
 +  ShellExState *p;                /* Shell state */
 +  FILE *out;                      /* Where to put normal messages or info */
 +  sqlite3 *db;                    /* Database containing the archive */
 +};
 +
 +/*
 +** Print a usage message for the .ar command to stderr and return SQLITE_ERROR.
 +*/
 +static int arUsage(FILE *f, ArCommand *pAr){
 +  showHelp(f,"archive", pAr->p);
 +  return SQLITE_ERROR;
 +}
 +
 +/*
 +** Print an error message for the .ar command to stderr and return
 +** SQLITE_ERROR.
 +*/
 +static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){
 +  va_list ap;
 +  char *z;
 +  va_start(ap, zFmt);
 +  z = sqlite3_vmprintf(zFmt, ap);
 +  va_end(ap);
 +  utf8_printf(STD_ERR, "Error: %s\n", z);
 +  if( pAr->fromCmdLine ){
 +    utf8_printf(STD_ERR, "Use \"-A\" for more help\n");
 +  }else{
 +    utf8_printf(STD_ERR, "Use \".archive --help\" for more help\n");
    }
 +  sqlite3_free(z);
 +  return SQLITE_ERROR;
  }
  
 -#if HAVE_READLINE || HAVE_EDITLINE
  /*
 -** Readline completion callbacks
 +** Values for ArCommand.eCmd.
  */
 -static char *readline_completion_generator(const char *text, int state){
 -  static sqlite3_stmt *pStmt = 0;
 -  char *zRet;
 -  if( state==0 ){
 -    char *zSql;
 -    sqlite3_finalize(pStmt);
 -    zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase"
 -                           "  FROM completion(%Q) ORDER BY 1", text);
 -    shell_check_oom(zSql);
 -    sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0);
 -    sqlite3_free(zSql);
 +#define AR_CMD_CREATE       1
 +#define AR_CMD_UPDATE       2
 +#define AR_CMD_INSERT       3
 +#define AR_CMD_EXTRACT      4
 +#define AR_CMD_LIST         5
 +#define AR_CMD_HELP         6
 +#define AR_CMD_REMOVE       7
 +
 +/*
 +** Other (non-command) switches.
 +*/
 +#define AR_SWITCH_VERBOSE     8
 +#define AR_SWITCH_FILE        9
 +#define AR_SWITCH_DIRECTORY  10
 +#define AR_SWITCH_APPEND     11
 +#define AR_SWITCH_DRYRUN     12
 +#define AR_SWITCH_GLOB       13
 +
 +static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){
 +  switch( eSwitch ){
 +    case AR_CMD_CREATE:
 +    case AR_CMD_EXTRACT:
 +    case AR_CMD_LIST:
 +    case AR_CMD_REMOVE:
 +    case AR_CMD_UPDATE:
 +    case AR_CMD_INSERT:
 +    case AR_CMD_HELP:
 +      if( pAr->eCmd ){
 +        return arErrorMsg(pAr, "multiple command options");
 +      }
 +      pAr->eCmd = eSwitch;
 +      break;
 +
 +    case AR_SWITCH_DRYRUN:
 +      pAr->bDryRun = 1;
 +      break;
 +    case AR_SWITCH_GLOB:
 +      pAr->bGlob = 1;
 +      break;
 +    case AR_SWITCH_VERBOSE:
 +      pAr->bVerbose = 1;
 +      break;
 +    case AR_SWITCH_APPEND:
 +      pAr->bAppend = 1;
 +      deliberate_fall_through;
 +    case AR_SWITCH_FILE:
 +      pAr->zFile = zArg;
 +      break;
 +    case AR_SWITCH_DIRECTORY:
 +      pAr->zDir = zArg;
 +      break;
    }
 -  if( sqlite3_step(pStmt)==SQLITE_ROW ){
 -    const char *z = (const char*)sqlite3_column_text(pStmt,0);
 -    zRet = z ? strdup(z) : 0;
 +
 +  return SQLITE_OK;
 +}
 +
 +/*
 +** Parse the command line for an ".ar" command. The results are written into
 +** structure (*pAr). DCR_Ok is returned if the command line is parsed
 +** successfully, otherwise an error message is written to stderr and an
 +** appropriate DCR_* argument error is returned.
 +*/
 +static DotCmdRC arParseCommand(
 +  char **azArg,                   /* Array of arguments passed to dot command */
 +  int nArg,                       /* Number of entries in azArg[] */
 +  ArCommand *pAr                  /* Populate this object */
 +){
 +  struct ArSwitch {
 +    const char *zLong;
 +    char cShort;
 +    u8 eSwitch;
 +    u8 bArg;
 +  } aSwitch[] = {
 +    { "create",    'c', AR_CMD_CREATE,       0 },
 +    { "extract",   'x', AR_CMD_EXTRACT,      0 },
 +    { "insert",    'i', AR_CMD_INSERT,       0 },
 +    { "list",      't', AR_CMD_LIST,         0 },
 +    { "remove",    'r', AR_CMD_REMOVE,       0 },
 +    { "update",    'u', AR_CMD_UPDATE,       0 },
 +    { "help",      'h', AR_CMD_HELP,         0 },
 +    { "verbose",   'v', AR_SWITCH_VERBOSE,   0 },
 +    { "file",      'f', AR_SWITCH_FILE,      1 },
 +    { "append",    'a', AR_SWITCH_APPEND,    1 },
 +    { "directory", 'C', AR_SWITCH_DIRECTORY, 1 },
 +    { "dryrun",    'n', AR_SWITCH_DRYRUN,    0 },
 +    { "glob",      'g', AR_SWITCH_GLOB,      0 },
 +  };
 +  int nSwitch = sizeof(aSwitch) / sizeof(struct ArSwitch);
 +  struct ArSwitch *pEnd = &aSwitch[nSwitch];
 +  DotCmdRC rv = DCR_Ok;
 +
 +  if( nArg<=1 ){
 +    utf8_printf(STD_ERR, "Error: Wrong number of arguments to \"%s\".\n",
 +                azArg[0]);
 +    if( stdin_is_interactive ){
 +      utf8_printf(STD_ERR, "Usage:\n");
 +      arUsage(STD_ERR, pAr);
 +      return DCR_CmdErred;
 +    }
    }else{
 -    sqlite3_finalize(pStmt);
 -    pStmt = 0;
 -    zRet = 0;
 +    char *z = azArg[1];
 +    if( z[0]!='-' ){
 +      /* Traditional style [tar] invocation */
 +      int i;
 +      int iArg = 2;
 +      for(i=0; z[i]; i++){
 +        const char *zArg = 0;
 +        struct ArSwitch *pOpt;
 +        for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
 +          if( z[i]==pOpt->cShort ) break;
 +        }
 +        if( pOpt==pEnd ){
 +          arErrorMsg(pAr, "unrecognized option: %c", z[i]);
 +          return DCR_Unknown|i;
 +        }
 +        if( pOpt->bArg ){
 +          if( iArg>=nArg ){
 +            arErrorMsg(pAr, "option requires an argument: %c",z[i]);
 +            return DCR_Unpaired|i;
 +          }
 +          zArg = azArg[iArg++];
 +        }
 +        rv = arProcessSwitch(pAr, pOpt->eSwitch, zArg);
 +        if( rv!=DCR_Ok ) return rv;
 +      }
 +      pAr->nArg = nArg-iArg;
 +      if( pAr->nArg>0 ){
 +        pAr->azArg = &azArg[iArg];
 +      }
 +    }else{
 +      /* Non-traditional invocation */
 +      int iArg;
 +      for(iArg=1; iArg<nArg; iArg++){
 +        int n;
 +        z = azArg[iArg];
 +        if( z[0]!='-' ){
 +          /* All remaining command line words are command arguments. */
 +          pAr->azArg = &azArg[iArg];
 +          pAr->nArg = nArg-iArg;
 +          break;
 +        }
 +        n = strlen30(z);
 +
 +        if( z[1]!='-' ){
 +          int i;
 +          /* One or more short options */
 +          for(i=1; i<n; i++){
 +            const char *zArg = 0;
 +            struct ArSwitch *pOpt;
 +            for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
 +              if( z[i]==pOpt->cShort ) break;
 +            }
 +            if( pOpt==pEnd ){
 +              arErrorMsg(pAr, "unrecognized option: %c", z[i]);
 +              return DCR_Unknown|iArg;
 +            }
 +            if( pOpt->bArg ){
 +              if( i<(n-1) ){
 +                zArg = &z[i+1];
 +                i = n;
 +              }else{
 +                if( iArg>=(nArg-1) ){
 +                  arErrorMsg(pAr, "option requires an argument: %c", z[i]);
 +                  return DCR_Unpaired|iArg;
 +                }
 +                zArg = azArg[++iArg];
 +              }
 +            }
 +            rv = arProcessSwitch(pAr, pOpt->eSwitch, zArg);
 +            if( rv!=DCR_Ok ) return rv;
 +          }
 +        }else if( z[2]=='\0' ){
 +          /* A -- option, indicating that all remaining command line words
 +          ** are command arguments.  */
 +          pAr->azArg = &azArg[iArg+1];
 +          pAr->nArg = nArg-iArg-1;
 +          break;
 +        }else{
 +          /* A long option */
 +          const char *zArg = 0;             /* Argument for option, if any */
 +          struct ArSwitch *pMatch = 0;      /* Matching option */
 +          struct ArSwitch *pOpt;            /* Iterator */
 +          for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
 +            const char *zLong = pOpt->zLong;
 +            if( (n-2)<=strlen30(zLong) && 0==memcmp(&z[2], zLong, n-2) ){
 +              if( pMatch ){
 +                arErrorMsg(pAr, "ambiguous option: %s",z);
 +                return DCR_Ambiguous|iArg;
 +              }else{
 +                pMatch = pOpt;
 +              }
 +            }
 +          }
 +
 +          if( pMatch==0 ){
 +            arErrorMsg(pAr, "unrecognized option: %s", z);
 +            return DCR_Unknown|iArg;
 +          }
 +          if( pMatch->bArg ){
 +            if( iArg>=(nArg-1) ){
 +              arErrorMsg(pAr, "option requires an argument: %s", z);
 +              return DCR_Unpaired|iArg;
 +            }
 +            zArg = azArg[++iArg];
 +          }
 +          if( arProcessSwitch(pAr, pMatch->eSwitch, zArg) ) return SQLITE_ERROR;
 +        }
 +      }
 +    }
    }
 -  return zRet;
 -}
 -static char **readline_completion(const char *zText, int iStart, int iEnd){
 -  (void)iStart;
 -  (void)iEnd;
 -  rl_attempted_completion_over = 1;
 -  return rl_completion_matches(zText, readline_completion_generator);
 +  if( pAr->eCmd==0 ){
 +    utf8_printf(stderr, "Required argument missing.  Usage:\n");
 +    return arUsage(stderr,pAr);
 +  }
 +  return SQLITE_OK;
  }
  
 -#elif HAVE_LINENOISE
  /*
 -** Linenoise completion callback
 +** This function assumes that all arguments within the ArCommand.azArg[]
 +** array refer to archive members, as for the --extract, --list or --remove
 +** commands. It checks that each of them are "present". If any specified
 +** file is not present in the archive, an error is printed to stderr and an
 +** error code returned. Otherwise, if all specified arguments are present
 +** in the archive, SQLITE_OK is returned. Here, "present" means either an
 +** exact equality when pAr->bGlob is false or a "name GLOB pattern" match
 +** when pAr->bGlob is true.
 +**
 +** This function strips any trailing '/' characters from each argument.
 +** This is consistent with the way the [tar] command seems to work on
 +** Linux.
  */
 -static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){
 -  i64 nLine = strlen(zLine);
 -  i64 i, iStart;
 -  sqlite3_stmt *pStmt = 0;
 -  char *zSql;
 -  char zBuf[1000];
 +static int arCheckEntries(ArCommand *pAr){
 +  int rc = SQLITE_OK;
 +  if( pAr->nArg ){
 +    int i, j;
 +    sqlite3_stmt *pTest = 0;
 +    const char *zSel = (pAr->bGlob)
 +      ? "SELECT name FROM %s WHERE glob($name,name)"
 +      : "SELECT name FROM %s WHERE name=$name";
  
 -  if( nLine>(i64)sizeof(zBuf)-30 ) return;
 -  if( zLine[0]=='.' || zLine[0]=='#') return;
 -  for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){}
 -  if( i==nLine-1 ) return;
 -  iStart = i+1;
 -  memcpy(zBuf, zLine, iStart);
 -  zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase"
 -                         "  FROM completion(%Q,%Q) ORDER BY 1",
 -                         &zLine[iStart], zLine);
 -  shell_check_oom(zSql);
 -  sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0);
 -  sqlite3_free(zSql);
 -  sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */
 -  while( sqlite3_step(pStmt)==SQLITE_ROW ){
 -    const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0);
 -    int nCompletion = sqlite3_column_bytes(pStmt, 0);
 -    if( iStart+nCompletion < (i64)sizeof(zBuf)-1 && zCompletion ){
 -      memcpy(zBuf+iStart, zCompletion, nCompletion+1);
 -      linenoiseAddCompletion(lc, zBuf);
 +    shellPreparePrintf(pAr->db, &rc, &pTest, zSel, pAr->zSrcTable);
 +    j = sqlite3_bind_parameter_index(pTest, "$name");
 +    for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){
 +      char *z = pAr->azArg[i];
 +      int n = strlen30(z);
 +      int bOk = 0;
 +      while( n>0 && z[n-1]=='/' ) n--;
 +      z[n] = '\0';
 +      sqlite3_bind_text(pTest, j, z, -1, SQLITE_STATIC);
 +      if( SQLITE_ROW==sqlite3_step(pTest) ){
 +        bOk = 1;
 +      }
 +      shellReset(&rc, pTest);
 +      if( rc==SQLITE_OK && bOk==0 ){
 +        utf8_printf(STD_ERR, "not found in archive: %s\n", z);
 +        rc = SQLITE_ERROR;
 +      }
      }
 +    shellFinalize(&rc, pTest);
    }
 -  sqlite3_finalize(pStmt);
 +  return rc;
  }
 -#endif
  
  /*
 -** Do C-language style dequoting.
 -**
 -**    \a    -> alarm
 -**    \b    -> backspace
 -**    \t    -> tab
 -**    \n    -> newline
 -**    \v    -> vertical tab
 -**    \f    -> form feed
 -**    \r    -> carriage return
 -**    \s    -> space
 -**    \"    -> "
 -**    \'    -> '
 -**    \\    -> backslash
 -**    \NNN  -> ascii character NNN in octal
 -**    \xHH  -> ascii character HH in hexadecimal
 +** Format a WHERE clause that can be used against the "sqlar" table to
 +** identify all archive members that match the command arguments held
 +** in (*pAr). Leave this WHERE clause in (*pzWhere) before returning.
 +** The caller is responsible for eventually calling sqlite3_free() on
 +** any non-NULL (*pzWhere) value. Here, "match" means strict equality
 +** when pAr->bGlob is false and GLOB match when pAr->bGlob is true.
 +
  */
 -static void resolve_backslashes(char *z){
 -  int i, j;
 -  char c;
 -  while( *z && *z!='\\' ) z++;
 -  for(i=j=0; (c = z[i])!=0; i++, j++){
 -    if( c=='\\' && z[i+1]!=0 ){
 -      c = z[++i];
 -      if( c=='a' ){
 -        c = '\a';
 -      }else if( c=='b' ){
 -        c = '\b';
 -      }else if( c=='t' ){
 -        c = '\t';
 -      }else if( c=='n' ){
 -        c = '\n';
 -      }else if( c=='v' ){
 -        c = '\v';
 -      }else if( c=='f' ){
 -        c = '\f';
 -      }else if( c=='r' ){
 -        c = '\r';
 -      }else if( c=='"' ){
 -        c = '"';
 -      }else if( c=='\'' ){
 -        c = '\'';
 -      }else if( c=='\\' ){
 -        c = '\\';
 -      }else if( c=='x' ){
 -        int nhd = 0, hdv;
 -        u8 hv = 0;
 -        while( nhd<2 && (c=z[i+1+nhd])!=0 && (hdv=hexDigitValue(c))>=0 ){
 -          hv = (u8)((hv<<4)|hdv);
 -          ++nhd;
 -        }
 -        i += nhd;
 -        c = (u8)hv;
 -      }else if( c>='0' && c<='7' ){
 -        c -= '0';
 -        if( z[i+1]>='0' && z[i+1]<='7' ){
 -          i++;
 -          c = (c<<3) + z[i] - '0';
 -          if( z[i+1]>='0' && z[i+1]<='7' ){
 -            i++;
 -            c = (c<<3) + z[i] - '0';
 -          }
 +static void arWhereClause(
 +  int *pRc,
 +  ArCommand *pAr,
 +  char **pzWhere                  /* OUT: New WHERE clause */
 +){
 +  char *zWhere = 0;
 +  const char *zSameOp = (pAr->bGlob)? "GLOB" : "=";
 +  if( *pRc==SQLITE_OK ){
 +    if( pAr->nArg==0 ){
 +      zWhere = smprintf("1");
 +    }else{
 +      int i;
 +      const char *zSep = "";
 +      for(i=0; i<pAr->nArg; i++){
 +        const char *z = pAr->azArg[i];
 +        zWhere = smprintf("%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'",
 +                          zWhere, zSep, zSameOp, z, strlen30(z)+1, zSameOp, z);
 +        if( zWhere==0 ){
 +          *pRc = SQLITE_NOMEM;
 +          break;
          }
 +        zSep = " OR ";
        }
      }
 -    z[j] = c;
    }
 -  if( j<i ) z[j] = 0;
 +  *pzWhere = zWhere;
  }
  
  /*
@@@ -9183,3590 -6836,2315 +9215,3587 @@@ static void free_ShExtInfo( ShExtInfo *
    }
  }
  
 -/* Finalize the prepared statement created using shellPreparePrintf().
 -**
 -** This routine is could be marked "static".  But it is not always used,
 -** depending on compile-time options.  By omitting the "static", we avoid
 -** nuisance compiler warnings about "defined but not used".
 -*/
 -void shellFinalize(
 -  int *pRc,
 -  sqlite3_stmt *pStmt
 -){
 -  if( pStmt ){
 -    sqlite3 *db = sqlite3_db_handle(pStmt);
 -    int rc = sqlite3_finalize(pStmt);
 -    if( *pRc==SQLITE_OK ){
 -      if( rc!=SQLITE_OK ){
 -        raw_printf(stderr, "SQL error: %s\n", sqlite3_errmsg(db));
 +/* Do the initialization needed for use of dbShell for command lookup
 + * and dispatch and for I/O handler lookup and dispatch.
 + */
 +static int begin_db_dispatch(ShellExState *psx){
 +  ShellInState *psi = ISS(psx);
 +  sqlite3_stmt *pStmt = 0;
 +  int ic, rc1, rc2;
 +  int rc = 0;
 +  char *zErr = 0;
 +  const char *zSql;
 +  ShExtInfo sei = SHEXT_INFO_INIT;
 +  AnyResourceHolder arh_sei = {&sei, (GenericFreer)free_ShExtInfo};
 +  ResourceMark mark = holder_mark();
 +
 +  sstr_ptr_holder(&zErr);
 +  /* Consider: Store these dynamic arrays in the DB as indexed-into blobs. */
 +  assert(psx->dbShell==0 || (psi->numExtLoaded==0 && psi->pShxLoaded==0));
 +  rc = ensure_shell_db(psx);
 +  if( rc!=SQLITE_OK ){
 +    utf8_printf(STD_ERR, "Error: Shell DB uncreatable. Best to .quit soon.\n");
 +    return SQLITE_ERROR;
 +  }
 +  if( ensure_dispatch_table(psx)!=SQLITE_OK ) return 1;
 +
 +  psi->pShxLoaded = (ShExtInfo *)sqlite3_malloc(2*sizeof(ShExtInfo));
 +  shell_check_ooms(psi->pShxLoaded);
 +  /* The ShellInState object now owns above allocation, so initialize it. */
 +  memset(psi->pShxLoaded, 0, 2*sizeof(ShExtInfo));
 +  any_ref_holder(&arh_sei); /* protect against early aborts */
 +  sei.ppDotCommands
 +    = (DotCommand **)sqlite3_malloc((numCommands+2)*sizeof(DotCommand *));
 +  sei.ppExportHandlers
 +    = (ExportHandler **)sqlite3_malloc(2*sizeof(ExportHandler *));
 +  sei.ppImportHandlers
 +    = (ImportHandler **)sqlite3_malloc(2*sizeof(ImportHandler *));
 +  if( sei.ppDotCommands==0||sei.ppExportHandlers==0||sei.ppImportHandlers==0
 +      || psi->pShxLoaded==0 ){
 +    shell_out_of_memory();
 +  }
 +  sei.numExportHandlers = 0;
 +  sei.numImportHandlers = 0;
 +  for( ic=0; ic<(int)numCommands; ++ic ){
 +    sei.ppDotCommands[ic] = builtInCommand(ic);
 +  }
 +  sei.numDotCommands = ic;
 +  zSql = "INSERT INTO "SHELL_DISP_TAB"(name, extIx, cmdIx) VALUES(?, 0, ?)";
 +  rc1 = s3_prepare_v2_noom(psx->dbShell, zSql, -1, &pStmt, 0);
 +  stmt_holder(pStmt);
 +  rc2 = s3_exec_noom(psx->dbShell, "BEGIN TRANSACTION", 0, 0, &zErr);
 +  if( rc1!=SQLITE_OK || rc2!=SQLITE_OK ){
 +    rc = SQLITE_ERROR;
 +  }else{
 +    assert(sei.numDotCommands>0);
 +    for( ic=0; ic<sei.numDotCommands; ++ic ){
 +      DotCommand *pmc = sei.ppDotCommands[ic];
 +      const char *zName = pmc->pMethods->name(pmc);
 +      sqlite3_reset(pStmt);
 +      shell_check_nomem(sqlite3_bind_text(pStmt, 1, zName, -1, 0));
 +      sqlite3_bind_int(pStmt, 2, ic);
 +      rc = s3_step_noom(pStmt);
 +      if( rc!=SQLITE_DONE ){
 +        sqlite3_exec(psx->dbShell, "ABORT", 0, 0, 0);
 +        break;
        }
 -      *pRc = rc;
 +    }
 +    if( rc!=SQLITE_DONE ){
 +      rc = SQLITE_ERROR;
 +      zSql = "ABORT";
 +    }else{
 +      rc = SQLITE_OK;
 +      zSql = "COMMIT";
 +    }
 +    rc2 = s3_exec_noom(psx->dbShell, zSql, 0, 0, &zErr);
 +    if( SQLITE_OK==rc ){
 +      /* Transfer just-built ShExtInfo to ShellInState use and ownership. */
 +      psi->pShxLoaded[psi->numExtLoaded++] = sei;
 +      arh_sei.pAny = 0;
 +      sqlite3_enable_load_extension(psx->dbShell, 1);
 +      psi->bDbDispatch = 1;
      }
    }
 +  RESOURCE_FREE(mark);
 +
 +  return rc;
  }
  
 -/* Reset the prepared statement created using shellPreparePrintf().
 -**
 -** This routine is could be marked "static".  But it is not always used,
 -** depending on compile-time options.  By omitting the "static", we avoid
 -** nuisance compiler warnings about "defined but not used".
 -*/
 -void shellReset(
 -  int *pRc,
 -  sqlite3_stmt *pStmt
 -){
 -  int rc = sqlite3_reset(pStmt);
 -  if( *pRc==SQLITE_OK ){
 -    if( rc!=SQLITE_OK ){
 -      sqlite3 *db = sqlite3_db_handle(pStmt);
 -      raw_printf(stderr, "SQL error: %s\n", sqlite3_errmsg(db));
 +/* Call one loaded extension's destructors, in reverse order of their
 + * objects' creation.
 + */
 +static void run_one_shext_dtors(ShExtInfo *psei){
 +  int j;
 +  if( psei->ppDotCommands!=0 ){
 +    for( j=psei->numDotCommands; j>0; --j ){
 +      DotCommand *pmc = psei->ppDotCommands[j-1];
 +      if( pmc->pMethods->destruct!=0 ) pmc->pMethods->destruct(pmc);
      }
 -    *pRc = rc;
 +  }
 +  if( psei->ppExportHandlers!=0 ){
 +    for( j=psei->numExportHandlers; j>0; --j ){
 +      ExportHandler *peh = psei->ppExportHandlers[j-1];
 +      if( peh->pMethods->destruct!=0 ) peh->pMethods->destruct(peh);
 +    }
 +  }
 +  if( psei->ppImportHandlers!=0 ){
 +    for( j=psei->numImportHandlers; j>0; --j ){
 +      ImportHandler *pih = psei->ppImportHandlers[j-1];
 +      if( pih->pMethods->destruct!=0 ) pih->pMethods->destruct(pih);
 +    }
 +  }
 +  if( psei->extDtor!=0 ){
 +    psei->extDtor(psei->pvExtObj);
    }
  }
 -#endif /* !defined SQLITE_OMIT_VIRTUALTABLE */
  
 -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
 -/******************************************************************************
 -** The ".archive" or ".ar" command.
 -*/
 -/*
 -** Structure representing a single ".ar" command.
 -*/
 -typedef struct ArCommand ArCommand;
 -struct ArCommand {
 -  u8 eCmd;                        /* An AR_CMD_* value */
 -  u8 bVerbose;                    /* True if --verbose */
 -  u8 bZip;                        /* True if the archive is a ZIP */
 -  u8 bDryRun;                     /* True if --dry-run */
 -  u8 bAppend;                     /* True if --append */
 -  u8 bGlob;                       /* True if --glob */
 -  u8 fromCmdLine;                 /* Run from -A instead of .archive */
 -  int nArg;                       /* Number of command arguments */
 -  char *zSrcTable;                /* "sqlar", "zipfile($file)" or "zip" */
 -  const char *zFile;              /* --file argument, or NULL */
 -  const char *zDir;               /* --directory argument, or NULL */
 -  char **azArg;                   /* Array of command arguments */
 -  ShellState *p;                  /* Shell state */
 -  sqlite3 *db;                    /* Database containing the archive */
 -};
 +/* Call all existent loaded extension destructors, in reverse order of their
 + * objects' creation, except for scripting support which is done last,
 + * then free the tracking dyna-arrays.
 + */
 +static void free_all_shext_tracking(ShellInState *psi){
 +  if( psi->pShxLoaded!=0 ){
 +    int i = psi->numExtLoaded;
 +    while( i>1 ){
 +      ShExtInfo *psei = &psi->pShxLoaded[--i];
 +      run_one_shext_dtors(psei);
 +      free_ShExtInfo(psei);
 +      if( psi->scriptXid!=0 && psi->scriptXid==psei->extId ){
 +        assert(psi->script!=0);
 +        if (psi->script->pMethods->destruct){
 +          psi->script->pMethods->destruct(psi->script);
 +        }
 +        psi->script = 0;
 +        psi->scriptXid = 0;
 +      }
 +    }
 +    free_ShExtInfo(psi->pShxLoaded);
 +    sqlite3_free(psi->pShxLoaded);
 +    psi->pShxLoaded = 0;
 +    psi->numExtLoaded = 0;
 +  }
 +}
  
 -/*
 -** Print a usage message for the .ar command to stderr and return SQLITE_ERROR.
 -*/
 -static int arUsage(FILE *f){
 -  showHelp(f,"archive");
 -  return SQLITE_ERROR;
 +static DotCommand *command_by_index(ShellInState *psi, int extIx, int cmdIx){
 +  assert(extIx>=0);
 +  if( extIx>=0 && extIx<psi->numExtLoaded ){
 +    ShExtInfo *psei = & psi->pShxLoaded[extIx];
 +    if( cmdIx>=0 && cmdIx<psei->numDotCommands ){
 +      return psei->ppDotCommands[cmdIx];
 +    }
 +  }
 +  return 0;
  }
  
 -/*
 -** Print an error message for the .ar command to stderr and return
 -** SQLITE_ERROR.
 -*/
 -static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){
 -  va_list ap;
 -  char *z;
 -  va_start(ap, zFmt);
 -  z = sqlite3_vmprintf(zFmt, ap);
 -  va_end(ap);
 -  utf8_printf(stderr, "Error: %s\n", z);
 -  if( pAr->fromCmdLine ){
 -    utf8_printf(stderr, "Use \"-A\" for more help\n");
 +static int load_shell_extension(ShellExState *psx, const char *zFile,
 +                                const char *zProc, char **pzErr,
 +                                int nLoadArgs, char **azLoadArgs){
 +  ShellExtensionLink shxLink = {
 +    sizeof(ShellExtensionLink),
 +    &shellExtAPI,
 +    psx, /* pSXS */
 +    0,   /* zErrMsg */
 +    0,   /* ExtensionId */
 +    0,   /* Extension destructor */
 +    0,   /* Extension data ref */
 +    nLoadArgs, azLoadArgs /* like-named members */
 +  }; //extDtor(pvExtObj)
 +  ShellInState *psi = ISS(psx);
 +  /* save script support state for possible fallback if load fails */
 +  ScriptSupport *pssSave = psi->script;
 +  ExtensionId ssiSave = psi->scriptXid;
 +  int rc;
 +
 +  if( pzErr ) *pzErr = 0;
 +  if( psx->dbShell==0 || ISS(psx)->numExtLoaded==0 ){
 +    rc = begin_db_dispatch(psx);
 +    if( rc!=SQLITE_OK ) return rc;
 +    assert(ISS(psx)->numExtLoaded==1 && psx->dbShell!=0);
 +  }
 +  psi->ixExtPending = psi->numExtLoaded;
 +  sqlite3_create_function(psx->dbShell, "shext_pointer", 1,
 +                          SQLITE_DIRECTONLY|SQLITE_UTF8,
 +                          &shxLink, shell_linkage, 0, 0);
 +  rc = sqlite3_load_extension(psx->dbShell, zFile, zProc, &shxLink.zErrMsg);
 +  sqlite3_create_function(psx->dbShell, "shext_pointer", 1,
 +                          SQLITE_DIRECTONLY|SQLITE_UTF8,
 +                          0, 0, 0, 0); /* deregister */
 +  if( pzErr!=0 ) *pzErr = shxLink.zErrMsg;
 +  if( rc==SQLITE_OK ){
 +    /* Keep extension's id and destructor for later disposal. */
 +    ShExtInfo *psei = pending_ext_info(psi);
 +    if( psei->extId!=0 && psei->extId!=shxLink.eid ) rc = SQLITE_MISUSE;
 +    psei->extId = shxLink.eid;
 +    psei->extDtor = shxLink.extensionDestruct;
 +    psei->pvExtObj = shxLink.pvExtensionObject;
    }else{
 -    utf8_printf(stderr, "Use \".archive --help\" for more help\n");
 +    /* Release all resources extension might have registered before failing. */
 +    if( psi->ixExtPending < psi->numExtLoaded ){
 +      run_one_shext_dtors(psi->pShxLoaded+psi->ixExtPending);
 +      free_ShExtInfo(psi->pShxLoaded+psi->ixExtPending);
 +      --psi->numExtLoaded;
 +    }
 +    /* And make it unwind any scripting linkage it might have setup. */
 +    if( psi->script!=0 ) psi->script->pMethods->destruct(psi->script);
 +    psi->script = pssSave;
 +    psi->scriptXid = ssiSave;
 +  }
 +  psi->ixExtPending = 0;
 +  if( rc!=SQLITE_OK ){
 +    if( rc==SQLITE_MISUSE && pzErr!=0 ){
 +      *pzErr = smprintf("extension id mismatch %z\n", *pzErr);
 +    }
 +    rc = SQLITE_ERROR;
    }
 -  sqlite3_free(z);
 -  return SQLITE_ERROR;
 +  return rc;
  }
 +#endif
  
 -/*
 -** Values for ArCommand.eCmd.
 +/* Dot-command implementation functions are defined in this section.
 +COMMENT  Define dot-commands and provide for their dispatch and .help text.
 +COMMENT  These should be kept in command name order for coding convenience
 +COMMENT  except where dot-commands share implementation. (The ordering
 +COMMENT  required for dispatch and help text is effected regardless.) The
 +COMMENT  effect of this configuration can be seen in generated output or by
 +COMMENT  executing tool/mkshellc.tcl --parameters (or --details or --help).
 +COMMENT  Generally, this section defines dispatchable functions inline and
 +COMMENT  causes collection of command_table entry initializers, to be later
 +COMMENT  emitted by a mkshellc macro. (See EMIT_DOTCMD_INIT further on.)
 +** All dispatchable dot-command execute functions have this signature:
 +static int someCommand(char *azArg[], int nArg, ShellExState *p, char **pzErr);
  */
 -#define AR_CMD_CREATE       1
 -#define AR_CMD_UPDATE       2
 -#define AR_CMD_INSERT       3
 -#define AR_CMD_EXTRACT      4
 -#define AR_CMD_LIST         5
 -#define AR_CMD_HELP         6
 -#define AR_CMD_REMOVE       7
 +DISPATCH_CONFIG[
 +  RETURN_TYPE=DotCmdRC
 +  STORAGE_CLASS=static
 +  ARGS_SIGNATURE=char *$arg4\[\], int $arg5, ShellExState *$arg6, char **$arg7
 +  DISPATCH_ENTRY={ "$cmd", ${cmd}Command, $arg1, $arg2, $arg3 },
 +  DOTCMD_INIT={ DOT_CMD_INFO(${cmd}, $arg1,$arg2,$arg3), {<HT0>, <HT1>}, 0 },
 +  CMD_CAPTURE_RE=^\s*{\s*"(\w+)"
 +  DISPATCHEE_NAME=${cmd}Command
 +  DC_ARG1_DEFAULT=[string length $cmd]
 +  DC_ARG2_DEFAULT=0
 +  DC_ARG3_DEFAULT=0
 +  DC_ARG4_DEFAULT=azArg
 +  DC_ARG5_DEFAULT=nArg
 +  DC_ARG6_DEFAULT=p
 +  DC_ARG7_DEFAULT=pzErr
 +  DC_ARG_COUNT=8
 +];
 +
 +CONDITION_COMMAND(seeargs !defined(SHELL_OMIT_SEEARGS));
 +/*****************
 + * The .seeargs command
 + */
 +COLLECT_HELP_TEXT[
 +  ",seeargs                 Echo arguments suffixed with |",
 +];
 +DISPATCHABLE_COMMAND( seeargs ? 0 0 azArg nArg p ){
 +  int ia = 0;
 +  for (ia=1; ia<nArg; ++ia)
 +    raw_printf(ISS(p)->out, "%s%s", azArg[ia], (ia==nArg-1)? "|\n" : "|");
 +  return DCR_Ok;
 +}
  
 -/*
 -** Other (non-command) switches.
 -*/
 -#define AR_SWITCH_VERBOSE     8
 -#define AR_SWITCH_FILE        9
 -#define AR_SWITCH_DIRECTORY  10
 -#define AR_SWITCH_APPEND     11
 -#define AR_SWITCH_DRYRUN     12
 -#define AR_SWITCH_GLOB       13
 +CONDITION_COMMAND(archive ARCHIVE_ENABLE && !defined(SQLITE_SHELL_FIDDLE));
 +/*****************
 + * The .archive command
 + */
 +COLLECT_HELP_TEXT[
 +  ".archive ...             Manage SQL archives",
 +  "   Each command must have exactly one of the following options:",
 +  "     -c, --create               Create a new archive",
 +  "     -u, --update               Add or update files with changed mtime",
 +  "     -i, --insert               Like -u but always add even if unchanged",
 +  "     -r, --remove               Remove files from archive",
 +  "     -t, --list                 List contents of archive",
 +  "     -x, --extract              Extract files from archive",
 +  "   Optional arguments:",
 +  "     -v, --verbose              Print each filename as it is processed",
 +  "     -f FILE, --file FILE       Use archive FILE (default is current db)",
 +  "     -a FILE, --append FILE     Open FILE using the apndvfs VFS",
 +  "     -C DIR, --directory DIR    Read/extract files from directory DIR",
 +  "     -g, --glob                 Use glob matching for names in archive",
 +  "     -n, --dryrun               Show the SQL that would have occurred",
 +  "   Examples:",
 +  "     .ar -cf ARCHIVE foo bar  # Create ARCHIVE from files foo and bar",
 +  "     .ar -tf ARCHIVE          # List members of ARCHIVE",
 +  "     .ar -xvf ARCHIVE         # Verbosely extract files from ARCHIVE",
 +  "   See also:",
 +  "      http://sqlite.org/cli.html#sqlite_archive_support",
 +];
 +DISPATCHABLE_COMMAND( archive ? 0 0 azArg nArg p ){
 +  open_db(p, 0);
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +  return arDotCommand(p, 0, azArg, nArg);
 +}
  
 -static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){
 -  switch( eSwitch ){
 -    case AR_CMD_CREATE:
 -    case AR_CMD_EXTRACT:
 -    case AR_CMD_LIST:
 -    case AR_CMD_REMOVE:
 -    case AR_CMD_UPDATE:
 -    case AR_CMD_INSERT:
 -    case AR_CMD_HELP:
 -      if( pAr->eCmd ){
 -        return arErrorMsg(pAr, "multiple command options");
 -      }
 -      pAr->eCmd = eSwitch;
 -      break;
 +/*****************
 + * The .auth command
 + */
 +CONDITION_COMMAND(auth !defined(SQLITE_OMIT_AUTHORIZATION));
 +COLLECT_HELP_TEXT[
 +  ".auth ON|OFF             Show authorizer callbacks",
 +];
 +DISPATCHABLE_COMMAND( auth 3 2 2 azArg nArg p ){
 +  open_db(p, 0);
 +  if( booleanValue(azArg[1]) ){
 +    sqlite3_set_authorizer(DBX(p), shellAuth, p);
 +  }else if( ISS(p)->bSafeModeFuture ){
 +    sqlite3_set_authorizer(DBX(p), safeModeAuth, p);
 +  }else{
 +    sqlite3_set_authorizer(DBX(p), 0, 0);
 +  }
 +  return DCR_Ok;
 +}
  
 -    case AR_SWITCH_DRYRUN:
 -      pAr->bDryRun = 1;
 -      break;
 -    case AR_SWITCH_GLOB:
 -      pAr->bGlob = 1;
 -      break;
 -    case AR_SWITCH_VERBOSE:
 -      pAr->bVerbose = 1;
 -      break;
 -    case AR_SWITCH_APPEND:
 -      pAr->bAppend = 1;
 -      deliberate_fall_through;
 -    case AR_SWITCH_FILE:
 -      pAr->zFile = zArg;
 -      break;
 -    case AR_SWITCH_DIRECTORY:
 -      pAr->zDir = zArg;
 -      break;
 +/*****************
 + * The .backup and .save commands (aliases for each other)
 + * These defer to writeDb in the dispatch table, so are not here.
 + */
 +CONDITION_COMMAND(backup !defined(SQLITE_SHELL_FIDDLE));
 +CONDITION_COMMAND(save !defined(SQLITE_SHELL_FIDDLE) );
 +COLLECT_HELP_TEXT[
 +  ".backup ?DB? FILE        Backup DB (default \"main\") to FILE",
 +  "   Options:",
 +  "     --append               Use the appendvfs",
 +  "     --async                Write the FILE without journal and fsync()",
 +  ".save ?DB? FILE          Write DB (default \"main\") to FILE",
 +  "   Options:",
 +  "     --append               Use the appendvfs",
 +  "     --async                Write the FILE without journal and fsync()",
 +];
 +DISPATCHABLE_COMMAND( backup 4 2 5 ){
 +  return writeDb( azArg, nArg, p, pzErr);
 +}
 +DISPATCHABLE_COMMAND( save 3 2 5 ){
 +  return writeDb( azArg, nArg, p, pzErr);
 +}
 +
 +/*****************
 + * The .bail command
 + */
 +COLLECT_HELP_TEXT[
 +  ".bail on|off             Stop after hitting an error.  Default OFF",
 +];
 +DISPATCHABLE_COMMAND( bail 3 2 2 ){
 +  bail_on_error = booleanValue(azArg[1]);
 +  return DCR_Ok;
 +}
 +
 +CONDITION_COMMAND(cd !defined(SQLITE_SHELL_FIDDLE));
 +/*****************
 + * The .binary and .cd commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".binary on|off           Turn binary output on or off.  Default OFF",
 +  ".cd DIRECTORY            Change the working directory to DIRECTORY",
 +];
 +DISPATCHABLE_COMMAND( binary 3 2 2 ){
 +  if( booleanValue(azArg[1]) ){
 +    setBinaryMode(ISS(p)->out, 1);
 +  }else{
 +    setTextMode(ISS(p)->out, 1);
    }
 +  return DCR_Ok;
 +}
  
 -  return SQLITE_OK;
 +DISPATCHABLE_COMMAND( cd ? 2 2 ){
 +  int rc=0;
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +  else{
 +#if defined(_WIN32) || defined(WIN32)
 +    wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
 +    shell_check_ooms(z);
 +    rc = (z)? !SetCurrentDirectoryW(z) : 1;
 +    sqlite3_free(z);
 +#else
 +    rc = chdir(azArg[1]);
 +#endif
 +  }
 +  if( rc ){
 +    utf8_printf(STD_ERR, "Cannot change to directory \"%s\"\n", azArg[1]);
 +    rc = 1;
 +  }
 +  return DCR_Ok|rc;
  }
  
 -/*
 -** Parse the command line for an ".ar" command. The results are written into
 -** structure (*pAr). SQLITE_OK is returned if the command line is parsed
 -** successfully, otherwise an error message is written to stderr and
 -** SQLITE_ERROR returned.
 +/* The ".breakpoint" command causes a call to the no-op routine named
 + * test_breakpoint(). It is undocumented.
  */
 -static int arParseCommand(
 -  char **azArg,                   /* Array of arguments passed to dot command */
 -  int nArg,                       /* Number of entries in azArg[] */
 -  ArCommand *pAr                  /* Populate this object */
 -){
 -  struct ArSwitch {
 -    const char *zLong;
 -    char cShort;
 -    u8 eSwitch;
 -    u8 bArg;
 -  } aSwitch[] = {
 -    { "create",    'c', AR_CMD_CREATE,       0 },
 -    { "extract",   'x', AR_CMD_EXTRACT,      0 },
 -    { "insert",    'i', AR_CMD_INSERT,       0 },
 -    { "list",      't', AR_CMD_LIST,         0 },
 -    { "remove",    'r', AR_CMD_REMOVE,       0 },
 -    { "update",    'u', AR_CMD_UPDATE,       0 },
 -    { "help",      'h', AR_CMD_HELP,         0 },
 -    { "verbose",   'v', AR_SWITCH_VERBOSE,   0 },
 -    { "file",      'f', AR_SWITCH_FILE,      1 },
 -    { "append",    'a', AR_SWITCH_APPEND,    1 },
 -    { "directory", 'C', AR_SWITCH_DIRECTORY, 1 },
 -    { "dryrun",    'n', AR_SWITCH_DRYRUN,    0 },
 -    { "glob",      'g', AR_SWITCH_GLOB,      0 },
 -  };
 -  int nSwitch = sizeof(aSwitch) / sizeof(struct ArSwitch);
 -  struct ArSwitch *pEnd = &aSwitch[nSwitch];
 +COLLECT_HELP_TEXT[
 +  ",breakpoint              calls test_breakpoint(). (a debugging aid)",
 +];
 +DISPATCHABLE_COMMAND( breakpoint 3 1 1 ){
 +  test_breakpoint();
 +  return DCR_Ok;
 +}
  
 -  if( nArg<=1 ){
 -    utf8_printf(stderr, "Wrong number of arguments.  Usage:\n");
 -    return arUsage(stderr);
 +CONDITION_COMMAND(check !defined(SQLITE_SHELL_FIDDLE));
 +CONDITION_COMMAND(clone !defined(SQLITE_SHELL_FIDDLE));
 +/*****************
 + * The .changes, .check, .clone and .connection commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".changes on|off          Show number of rows changed by SQL",
 +  ",check GLOB              Fail if output since .testcase does not match",
 +  ".clone NEWDB             Clone data into NEWDB from the existing database",
 +  ".connection [close] [#]  Open or close an auxiliary database connection",
 +];
 +DISPATCHABLE_COMMAND( changes 3 2 2 ){
 +  setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( check 3 0 0 ){
 +  /* Cancel output redirection, if it is currently set (by .testcase)
 +  ** Then read the content of the testcase-out.txt file and compare against
 +  ** azArg[1].  If there are differences, report an error and exit.
 +  */
 +  char *zRes = 0;
 +  DotCmdRC rv = DCR_Ok;
 +  output_reset(ISS(p));
 +  if( nArg!=2 ){
 +    return DCR_ArgWrong;
 +  }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){
 +    *pzErr = smprintf("Error: cannot read 'testcase-out.txt'\n");
 +    rv = DCR_Return;
 +  }else if( testcase_glob(azArg[1],zRes)==0 ){
 +    *pzErr =
 +      smprintf("testcase-%s FAILED\n Expected: [%s]\n      Got: [%s]\n",
 +               ISS(p)->zTestcase, azArg[1], zRes);
 +    rv = DCR_Error;
    }else{
 -    char *z = azArg[1];
 -    if( z[0]!='-' ){
 -      /* Traditional style [tar] invocation */
 -      int i;
 -      int iArg = 2;
 -      for(i=0; z[i]; i++){
 -        const char *zArg = 0;
 -        struct ArSwitch *pOpt;
 -        for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
 -          if( z[i]==pOpt->cShort ) break;
 -        }
 -        if( pOpt==pEnd ){
 -          return arErrorMsg(pAr, "unrecognized option: %c", z[i]);
 -        }
 -        if( pOpt->bArg ){
 -          if( iArg>=nArg ){
 -            return arErrorMsg(pAr, "option requires an argument: %c",z[i]);
 -          }
 -          zArg = azArg[iArg++];
 -        }
 -        if( arProcessSwitch(pAr, pOpt->eSwitch, zArg) ) return SQLITE_ERROR;
 -      }
 -      pAr->nArg = nArg-iArg;
 -      if( pAr->nArg>0 ){
 -        pAr->azArg = &azArg[iArg];
 -      }
 -    }else{
 -      /* Non-traditional invocation */
 -      int iArg;
 -      for(iArg=1; iArg<nArg; iArg++){
 -        int n;
 -        z = azArg[iArg];
 -        if( z[0]!='-' ){
 -          /* All remaining command line words are command arguments. */
 -          pAr->azArg = &azArg[iArg];
 -          pAr->nArg = nArg-iArg;
 -          break;
 -        }
 -        n = strlen30(z);
 -
 -        if( z[1]!='-' ){
 -          int i;
 -          /* One or more short options */
 -          for(i=1; i<n; i++){
 -            const char *zArg = 0;
 -            struct ArSwitch *pOpt;
 -            for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
 -              if( z[i]==pOpt->cShort ) break;
 -            }
 -            if( pOpt==pEnd ){
 -              return arErrorMsg(pAr, "unrecognized option: %c", z[i]);
 -            }
 -            if( pOpt->bArg ){
 -              if( i<(n-1) ){
 -                zArg = &z[i+1];
 -                i = n;
 -              }else{
 -                if( iArg>=(nArg-1) ){
 -                  return arErrorMsg(pAr, "option requires an argument: %c",
 -                                    z[i]);
 -                }
 -                zArg = azArg[++iArg];
 -              }
 -            }
 -            if( arProcessSwitch(pAr, pOpt->eSwitch, zArg) ) return SQLITE_ERROR;
 -          }
 -        }else if( z[2]=='\0' ){
 -          /* A -- option, indicating that all remaining command line words
 -          ** are command arguments.  */
 -          pAr->azArg = &azArg[iArg+1];
 -          pAr->nArg = nArg-iArg-1;
 -          break;
 -        }else{
 -          /* A long option */
 -          const char *zArg = 0;             /* Argument for option, if any */
 -          struct ArSwitch *pMatch = 0;      /* Matching option */
 -          struct ArSwitch *pOpt;            /* Iterator */
 -          for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
 -            const char *zLong = pOpt->zLong;
 -            if( (n-2)<=strlen30(zLong) && 0==memcmp(&z[2], zLong, n-2) ){
 -              if( pMatch ){
 -                return arErrorMsg(pAr, "ambiguous option: %s",z);
 -              }else{
 -                pMatch = pOpt;
 -              }
 -            }
 -          }
 +    utf8_printf(STD_OUT, "testcase-%s ok\n", ISS(p)->zTestcase);
 +    ISS(p)->nCheck++;
 +  }
 +  sqlite3_free(zRes);
 +  return (zRes==0)? DCR_Abort : rv;
 +}
 +DISPATCHABLE_COMMAND( clone ? 2 2 ){
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +  tryToClone(p, azArg[1]);
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( connection ? 1 4 ){
 +  ShellInState *psi = ISS(p);
 +  if( nArg==1 ){
 +    /* List available connections */
 +    int i;
 +    for(i=0; i<ArraySize(psi->aAuxDb); i++){
 +      const char *zFile = psi->aAuxDb[i].zDbFilename;
 +      if( psi->aAuxDb[i].db==0 && psi->pAuxDb!=&psi->aAuxDb[i] ){
 +        zFile = "(not open)";
 +      }else if( zFile==0 ){
 +        zFile = "(memory)";
 +      }else if( zFile[0]==0 ){
 +        zFile = "(temporary-file)";
 +      }
 +      if( psi->pAuxDb == &psi->aAuxDb[i] ){
 +        utf8_printf(STD_OUT, "ACTIVE %d: %s\n", i, zFile);
 +      }else if( psi->aAuxDb[i].db!=0 ){
 +        utf8_printf(STD_OUT, "       %d: %s\n", i, zFile);
 +      }
 +    }
 +  }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){
 +    int i = azArg[1][0] - '0';
 +    if( psi->pAuxDb != &psi->aAuxDb[i] && i>=0 && i<ArraySize(psi->aAuxDb) ){
 +      psi->pAuxDb->db = DBX(p);
 +      psi->pAuxDb = &psi->aAuxDb[i];
 +#if SHELL_DYNAMIC_EXTENSION
 +      if( DBX(p)!=0 ) notify_subscribers(psi, NK_DbUserVanishing, DBX(p));
 +#endif
 +      globalDb = DBX(p) = psi->pAuxDb->db;
 +#if SHELL_DYNAMIC_EXTENSION
 +      if( DBX(p)!=0 ) notify_subscribers(psi, NK_DbUserAppeared, DBX(p));
 +#endif
 +      psi->pAuxDb->db = 0;
 +    }
 +  }else if( nArg==3 && cli_strcmp(azArg[1], "close")==0
 +            && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){
 +    int i = azArg[2][0] - '0';
 +    if( i<0 || i>=ArraySize(psi->aAuxDb) ){
 +      /* No-op */
 +    }else if( psi->pAuxDb == &psi->aAuxDb[i] ){
 +      raw_printf(STD_ERR, "cannot close the active database connection\n");
 +      return DCR_Error;
 +    }else if( psi->aAuxDb[i].db ){
 +      session_close_all(psi, i);
 +      close_db(psi->aAuxDb[i].db);
 +      psi->aAuxDb[i].db = 0;
 +    }
 +  }else{
 +    return DCR_ArgWrong;
 +  }
 +  return DCR_Ok;
 +}
  
 -          if( pMatch==0 ){
 -            return arErrorMsg(pAr, "unrecognized option: %s", z);
 -          }
 -          if( pMatch->bArg ){
 -            if( iArg>=(nArg-1) ){
 -              return arErrorMsg(pAr, "option requires an argument: %s", z);
 -            }
 -            zArg = azArg[++iArg];
 -          }
 -          if( arProcessSwitch(pAr, pMatch->eSwitch, zArg) ) return SQLITE_ERROR;
 -        }
 -      }
 +CONDITION_COMMAND(dbinfo SQLITE_SHELL_HAVE_RECOVER);
 +/*****************
 + * The .databases, .dbconfig and .dbinfo commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".databases               List names and files of attached databases",
 +  ".dbconfig ?op? ?val?     List or change sqlite3_db_config() options",
 +  ".dbinfo ?DB?             Show status information about the database",
 +];
 +/* Allow garbage arguments on this, to be ignored. */
 +DISPATCHABLE_COMMAND( databases 2 1 0 ){
 +  int rc;
 +  char **azName = 0;
 +  int nName = 0;
 +  sqlite3_stmt *pStmt = 0;
 +  sqlite3 *db;
 +  int i;
 +  open_db(p, 0);
 +  db = DBX(p);
 +  rc = s3_prepare_v2_noom(db, "PRAGMA database_list", -1, &pStmt, 0);
 +  stmt_holder(pStmt);
 +  if( rc || pStmt==0 ){
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
 +    rc = 1;
 +  }else{
 +    while( s3_step_noom(pStmt)==SQLITE_ROW ){
 +      int eTxn, bRdonly;
 +      const char *zSchema = (const char *)sqlite3_column_text(pStmt,1);
 +      const char *zFile = (const char*)sqlite3_column_text(pStmt,2);
 +      if( zSchema==0 || zFile==0 ) continue;
 +      eTxn = sqlite3_txn_state(db, zSchema);
 +      bRdonly = sqlite3_db_readonly(db, zSchema);
 +      utf8_printf(ISS(p)->out, "%s: %s %s%s\n",
 +                  zSchema,
 +                  zFile[0] ? zFile : "\"\"",
 +                  bRdonly ? "r/o" : "r/w",
 +                  eTxn==SQLITE_TXN_NONE ? "" :
 +                  eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
 +    }
 +  }
 +  release_holder();
 +  return DCR_Ok|(rc!=0);
 +}
 +DISPATCHABLE_COMMAND( dbconfig 3 1 3 ){
 +  static const struct DbConfigChoices {
 +    const char *zName;
 +    int op;
 +  } aDbConfig[] = {
 +    { "defensive",          SQLITE_DBCONFIG_DEFENSIVE             },
 +    { "dqs_ddl",            SQLITE_DBCONFIG_DQS_DDL               },
 +    { "dqs_dml",            SQLITE_DBCONFIG_DQS_DML               },
 +    { "enable_fkey",        SQLITE_DBCONFIG_ENABLE_FKEY           },
 +    { "enable_qpsg",        SQLITE_DBCONFIG_ENABLE_QPSG           },
 +    { "enable_trigger",     SQLITE_DBCONFIG_ENABLE_TRIGGER        },
 +    { "enable_view",        SQLITE_DBCONFIG_ENABLE_VIEW           },
 +    { "fts3_tokenizer",     SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER },
 +    { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE    },
 +    { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT    },
 +    { "load_extension",     SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION },
 +    { "no_ckpt_on_close",   SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE      },
 +    { "reset_database",     SQLITE_DBCONFIG_RESET_DATABASE        },
 +    { "reverse_scanorder",  SQLITE_DBCONFIG_REVERSE_SCANORDER     },
 +    { "stmt_scanstatus",    SQLITE_DBCONFIG_STMT_SCANSTATUS       },
 +    { "trigger_eqp",        SQLITE_DBCONFIG_TRIGGER_EQP           },
 +    { "trusted_schema",     SQLITE_DBCONFIG_TRUSTED_SCHEMA        },
 +    { "writable_schema",    SQLITE_DBCONFIG_WRITABLE_SCHEMA       },
 +  };
 +  int ii, v;
 +  open_db(p, 0);
 +  for(ii=0; ii<ArraySize(aDbConfig); ii++){
 +    if( nArg>1 && cli_strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue;
 +    if( nArg>=3 ){
 +      sqlite3_db_config(DBX(p), aDbConfig[ii].op, booleanValue(azArg[2]), 0);
      }
 +    sqlite3_db_config(DBX(p), aDbConfig[ii].op, -1, &v);
 +    utf8_printf(ISS(p)->out, "%19s %s\n",
 +                aDbConfig[ii].zName, v ? "on" : "off");
 +    if( nArg>1 ) break;
    }
 -  if( pAr->eCmd==0 ){
 -    utf8_printf(stderr, "Required argument missing.  Usage:\n");
 -    return arUsage(stderr);
 +  if( nArg>1 && ii==ArraySize(aDbConfig) ){
 +    *pzErr = smprintf("Error: unknown dbconfig \"%s\"\n"
 +                      "Enter \".dbconfig\" with no arguments for a list\n",
 +                      azArg[1]);
 +    return DCR_ArgWrong;
    }
 -  return SQLITE_OK;
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( dbinfo 3 1 2 ){
 +  return shell_dbinfo_command(p, nArg, azArg);
  }
  
 -/*
 -** This function assumes that all arguments within the ArCommand.azArg[]
 -** array refer to archive members, as for the --extract, --list or --remove
 -** commands. It checks that each of them are "present". If any specified
 -** file is not present in the archive, an error is printed to stderr and an
 -** error code returned. Otherwise, if all specified arguments are present
 -** in the archive, SQLITE_OK is returned. Here, "present" means either an
 -** exact equality when pAr->bGlob is false or a "name GLOB pattern" match
 -** when pAr->bGlob is true.
 -**
 -** This function strips any trailing '/' characters from each argument.
 -** This is consistent with the way the [tar] command seems to work on
 -** Linux.
 -*/
 -static int arCheckEntries(ArCommand *pAr){
 -  int rc = SQLITE_OK;
 -  if( pAr->nArg ){
 -    int i, j;
 -    sqlite3_stmt *pTest = 0;
 -    const char *zSel = (pAr->bGlob)
 -      ? "SELECT name FROM %s WHERE glob($name,name)"
 -      : "SELECT name FROM %s WHERE name=$name";
 -
 -    shellPreparePrintf(pAr->db, &rc, &pTest, zSel, pAr->zSrcTable);
 -    j = sqlite3_bind_parameter_index(pTest, "$name");
 -    for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){
 -      char *z = pAr->azArg[i];
 -      int n = strlen30(z);
 -      int bOk = 0;
 -      while( n>0 && z[n-1]=='/' ) n--;
 -      z[n] = '\0';
 -      sqlite3_bind_text(pTest, j, z, -1, SQLITE_STATIC);
 -      if( SQLITE_ROW==sqlite3_step(pTest) ){
 -        bOk = 1;
 +/*****************
 + * The .dump, .echo and .eqp commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".dump ?OBJECTS?          Render database content as SQL",
 +  "   Options:",
 +  "     --data-only            Output only INSERT statements",
 +  "     --newlines             Allow unescaped newline characters in output",
 +  "     --nosys                Omit system tables (ex: \"sqlite_stat1\")",
 +  "     --preserve-rowids      Include ROWID values in the output",
 +  "     --schema SCHEMA        Dump table(s) from given SCHEMA",
 +  "   OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump",
 +  "   Additional LIKE patterns can be given in subsequent arguments",
 +  ".echo on|off             Turn command echo on or off",
 +  ".eqp on|off|full|...     Enable or disable automatic EXPLAIN QUERY PLAN",
 +  "   Other Modes:",
 +#ifdef SQLITE_DEBUG
 +  "      test                  Show raw EXPLAIN QUERY PLAN output",
 +  "      trace                 Like \"full\" but enable \"PRAGMA vdbe_trace\"",
 +#endif
 +  "      trigger               Like \"full\" but also show trigger bytecode",
 +];
 +DISPATCHABLE_COMMAND( dump ? 1 2 ){
 +  ShellInState *psi = ISS(p);
 +  char *zLike = 0;
 +  char *zSchema = "main";
 +  char *zSql;
 +  int i;
 +  int savedShowHeader = psi->showHeader;
 +  int savedShellFlags = psi->shellFlgs;
 +  sstr_ptr_holder(&zLike);
 +  ShellClearFlag(p,
 +     SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo
 +     |SHFLG_DumpDataOnly|SHFLG_DumpNoSys);
 +  for(i=1; i<nArg; i++){
 +    if( azArg[i][0]=='-' ){
 +      const char *z = azArg[i]+1;
 +      if( z[0]=='-' ) z++;
 +      if( cli_strcmp(z,"preserve-rowids")==0 ){
 +#ifdef SQLITE_OMIT_VIRTUALTABLE
 +        *pzErr = smprintf("The --preserve-rowids option is not compatible"
 +                          " with SQLITE_OMIT_VIRTUALTABLE\n");
 +        release_holder();
 +        return DCR_ArgWrong;
 +#else
 +        ShellSetFlag(p, SHFLG_PreserveRowid);
 +#endif
 +      }else{
 +        if( cli_strcmp(z,"newlines")==0 ){
 +          ShellSetFlag(p, SHFLG_Newlines);
 +        }else if( cli_strcmp(z,"data-only")==0 ){
 +          ShellSetFlag(p, SHFLG_DumpDataOnly);
 +        }else if( cli_strcmp(z,"nosys")==0 ){
 +          ShellSetFlag(p, SHFLG_DumpNoSys);
 +        }else if( cli_strcmp(z,"schema")==0 && ++i<nArg ){
 +          zSchema = azArg[i];
 +        }else{
 +          *pzErr = smprintf("Unknown option \"%s\" on \".dump\"\n", azArg[i]);
 +          release_holder();
 +          return DCR_ArgWrong;
 +        }
        }
 -      shellReset(&rc, pTest);
 -      if( rc==SQLITE_OK && bOk==0 ){
 -        utf8_printf(stderr, "not found in archive: %s\n", z);
 -        rc = SQLITE_ERROR;
 +    }else{
 +      /* azArg[i] contains a LIKE pattern. This ".dump" request should
 +      ** only dump data for tables for which either the table name matches
 +      ** the LIKE pattern, or the table appears to be a shadow table of
 +      ** a virtual table for which the name matches the LIKE pattern.
 +      */
 +      char *zExpr = smprintf(
 +                    "name LIKE %Q ESCAPE '\\' OR EXISTS ("
 +                    "  SELECT 1 FROM %w.sqlite_schema WHERE "
 +                    "    name LIKE %Q ESCAPE '\\' AND"
 +                    "    sql LIKE 'CREATE VIRTUAL TABLE%%' AND"
 +                    "    substr(o.name, 1, length(name)+1) == (name||'_')"
 +                    ")", azArg[i], zSchema, azArg[i]
 +                    );
 +
 +      shell_check_ooms(zExpr);
 +      if( zLike ){
 +        zLike = smprintf("%z OR %z", zLike, zExpr);
 +      }else{
 +        zLike = zExpr;
        }
      }
 -    shellFinalize(&rc, pTest);
    }
 -  return rc;
 -}
  
 -/*
 -** Format a WHERE clause that can be used against the "sqlar" table to
 -** identify all archive members that match the command arguments held
 -** in (*pAr). Leave this WHERE clause in (*pzWhere) before returning.
 -** The caller is responsible for eventually calling sqlite3_free() on
 -** any non-NULL (*pzWhere) value. Here, "match" means strict equality
 -** when pAr->bGlob is false and GLOB match when pAr->bGlob is true.
 -*/
 -static void arWhereClause(
 -  int *pRc,
 -  ArCommand *pAr,
 -  char **pzWhere                  /* OUT: New WHERE clause */
 -){
 -  char *zWhere = 0;
 -  const char *zSameOp = (pAr->bGlob)? "GLOB" : "=";
 -  if( *pRc==SQLITE_OK ){
 -    if( pAr->nArg==0 ){
 -      zWhere = sqlite3_mprintf("1");
 +  open_db(p, 0);
 +
 +  if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 +    /* When playing back a "dump", the content might appear in an order
 +    ** which causes immediate foreign key constraints to be violated.
 +    ** So disable foreign-key constraint enforcement to prevent problems. */
 +    raw_printf(psi->out, "PRAGMA foreign_keys=OFF;\n");
 +    raw_printf(psi->out, "BEGIN TRANSACTION;\n");
 +  }
 +  psi->writableSchema = 0;
 +  psi->showHeader = 0;
 +  /* Set writable_schema=ON since doing so forces SQLite to initialize
 +  ** as much of the schema as it can even if the sqlite_schema table is
 +  ** corrupt. */
 +  sqlite3_exec(DBX(p), "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
 +  psi->nErr = 0;
 +  if( zLike==0 ) zLike = smprintf("true");
 +  zSql = smprintf("SELECT name, type, sql FROM %w.sqlite_schema AS o "
 +                  "WHERE (%s) AND type=='table' AND sql NOT NULL"
 +                  " ORDER BY tbl_name='sqlite_sequence', rowid",
 +                  zSchema, zLike);
 +  shell_check_ooms(zSql);
 +  sstr_ptr_holder(&zSql);
 +  run_schema_dump_query(psi,zSql);
 +  if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 +    sqlite3_free(zSql);
 +    zSql = smprintf(
 +             "SELECT sql FROM sqlite_schema AS o "
 +             "WHERE (%s) AND sql NOT NULL"
 +             "  AND type IN ('index','trigger','view')",
 +             zLike
 +           );
 +    run_table_dump_query(psi, zSql);
 +  }
 +  release_holder(); /* zSql */
 +  if( psi->writableSchema ){
 +    raw_printf(psi->out, "PRAGMA writable_schema=OFF;\n");
 +    psi->writableSchema = 0;
 +  }
 +  sqlite3_exec(DBX(p), "PRAGMA writable_schema=OFF;", 0, 0, 0);
 +  sqlite3_exec(DBX(p), "RELEASE dump;", 0, 0, 0);
 +  if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 +    raw_printf(psi->out, psi->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n");
 +  }
 +  psi->showHeader = savedShowHeader;
 +  psi->shellFlgs = savedShellFlags;
 +  release_holder(); /* zLike */
 +
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( echo ? 2 2 ){
 +  setOrClearFlag(p, SHFLG_Echo, azArg[1]);
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( eqp ? 0 0 ){
 +  ShellInState *psi = ISS(p);
 +  if( nArg==2 ){
 +    psi->autoEQPtest = 0;
 +    if( psi->autoEQPtrace ){
 +      if( DBX(p) ) sqlite3_exec(DBX(p), "PRAGMA vdbe_trace=OFF;", 0, 0, 0);
 +      psi->autoEQPtrace = 0;
 +    }
 +    if( cli_strcmp(azArg[1],"full")==0 ){
 +      psi->autoEQP = AUTOEQP_full;
 +    }else if( cli_strcmp(azArg[1],"trigger")==0 ){
 +      psi->autoEQP = AUTOEQP_trigger;
 +#ifdef SQLITE_DEBUG
 +    }else if( cli_strcmp(azArg[1],"test")==0 ){
 +      psi->autoEQP = AUTOEQP_on;
 +      psi->autoEQPtest = 1;
 +    }else if( cli_strcmp(azArg[1],"trace")==0 ){
 +      psi->autoEQP = AUTOEQP_full;
 +      psi->autoEQPtrace = 1;
 +      open_db(p, 0);
 +      sqlite3_exec(DBX(p), "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0);
 +      sqlite3_exec(DBX(p), "PRAGMA vdbe_trace=ON;", 0, 0, 0);
 +#endif
      }else{
 -      int i;
 -      const char *zSep = "";
 -      for(i=0; i<pAr->nArg; i++){
 -        const char *z = pAr->azArg[i];
 -        zWhere = sqlite3_mprintf(
 -          "%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'",
 -          zWhere, zSep, zSameOp, z, strlen30(z)+1, zSameOp, z
 -        );
 -        if( zWhere==0 ){
 -          *pRc = SQLITE_NOMEM;
 -          break;
 -        }
 -        zSep = " OR ";
 -      }
 +      psi->autoEQP = (u8)booleanValue(azArg[1]);
      }
 +  }else{
 +    return DCR_ArgWrong;
    }
 -  *pzWhere = zWhere;
 +  return DCR_Ok;
  }
  
 -/*
 -** Implementation of .ar "lisT" command.
 -*/
 -static int arListCommand(ArCommand *pAr){
 -  const char *zSql = "SELECT %s FROM %s WHERE %s";
 -  const char *azCols[] = {
 -    "name",
 -    "lsmode(mode), sz, datetime(mtime, 'unixepoch'), name"
 -  };
 -
 -  char *zWhere = 0;
 -  sqlite3_stmt *pSql = 0;
 +CONDITION_COMMAND(cease !defined(SQLITE_SHELL_FIDDLE));
 +CONDITION_COMMAND(exit !defined(SQLITE_SHELL_FIDDLE));
 +CONDITION_COMMAND(quit !defined(SQLITE_SHELL_FIDDLE));
 +/*****************
 + * The .cease, .exit and .quit commands
 + * These are together so that their differing effects are apparent.
 + */
 +CONDITION_COMMAND(cease defined(SHELL_CEASE));
 +COLLECT_HELP_TEXT[
 +  ".cease ?CODE?            Cease shell operation, with optional return code",
 +  "   Return code defaults to 0, otherwise is limited to non-signal values",
 +  ".exit ?CODE?             Exit shell program, maybe with return-code CODE",
 +  "   Exit immediately if CODE != 0, else functions as \"quit this input\"",
 +  ".quit                    Stop interpreting input stream, done if primary.",
 +];
 +DISPATCHABLE_COMMAND( cease 4 1 2 ){
 +  /* .cease effects an exit, always. Only the exit code is variable. */
 +  int rc = 0;
 +  if( nArg>1 ){
 +    rc = (int)integerValue(azArg[1]);
 +    if( rc>0x7f ) rc = 0x7f;
 +  }
 +  p->shellAbruptExit = 0x100|rc;
 +  return DCR_Exit;
 +}
 +DISPATCHABLE_COMMAND( exit 3 1 0 ){
 +  /* .exit acts like .quit with no argument or a zero argument,
 +   * only returning. With a non-zero argument, it effects an exit. */
    int rc;
 +  if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ){
 +    rc &= 0xff;    /* Mimic effect of legacy call to exit(). */
 +#ifdef SHELL_EXIT_EXITS_PROCESS
++    terminate_actions();
 +    exit(rc);
 +#else
 +    p->shellAbruptExit = 0x100|rc;
 +#endif
 +  }
 +  return DCR_Return;
 +}
 +DISPATCHABLE_COMMAND( quit 1 1 0 ){
 +  /* .quit would be more aptly named .return, as it does nothing more. */
 +  return DCR_Return;
 +}
  
 -  rc = arCheckEntries(pAr);
 -  arWhereClause(&rc, pAr, &zWhere);
 +/*****************
 + * The .expert and .explain commands
 + */
 +CONDITION_COMMAND( expert !defined(SQLITE_OMIT_VIRTUALTABLE) );
 +COLLECT_HELP_TEXT[
 +  ".expert                  Suggest indexes for queries",
 +  ".explain ?on|off|auto?   Change the EXPLAIN formatting mode. Default: auto",
 +];
 +DISPATCHABLE_COMMAND( expert ? 1 1 ){
 +  ShellInState *psi = ISS(p);
 +  int rv = DCR_Ok;
 +  char *zErr = 0;
 +  int i;
 +  int iSample = 0;
  
 -  shellPreparePrintf(pAr->db, &rc, &pSql, zSql, azCols[pAr->bVerbose],
 -                     pAr->zSrcTable, zWhere);
 -  if( pAr->bDryRun ){
 -    utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql));
 -  }else{
 -    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
 -      if( pAr->bVerbose ){
 -        utf8_printf(pAr->p->out, "%s % 10d  %s  %s\n",
 -            sqlite3_column_text(pSql, 0),
 -            sqlite3_column_int(pSql, 1),
 -            sqlite3_column_text(pSql, 2),
 -            sqlite3_column_text(pSql, 3)
 -        );
 +  if( psi->bSafeMode ) return DCR_AbortError;
 +  assert( psi->expert.pExpert==0 );
 +  memset(&psi->expert, 0, sizeof(ExpertInfo));
 +
 +  open_db(p, 0);
 +
 +  for(i=1; i<nArg; i++){
 +    char *z = azArg[i];
 +    int n;
 +    if( z[0]=='-' && z[1]=='-' ) z++;
 +    n = strlen30(z);
 +    if( n>=2 && 0==cli_strncmp(z, "-verbose", n) ){
 +      psi->expert.bVerbose = 1;
 +    }
 +    else if( n>=2 && 0==cli_strncmp(z, "-sample", n) ){
 +      if( i==(nArg-1) ){
 +        return DCR_Unpaired|i;
        }else{
 -        utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0));
 +        iSample = (int)integerValue(azArg[++i]);
 +        if( iSample<0 || iSample>100 ){
 +          *pzErr = smprintf("value out of range: %s\n", azArg[i]);
 +          return DCR_ArgWrong|i;
 +        }
        }
      }
 +    else{
 +      return DCR_Unknown|i;
 +    }
    }
 -  shellFinalize(&rc, pSql);
 -  sqlite3_free(zWhere);
 -  return rc;
 +
 +  psi->expert.pExpert = sqlite3_expert_new(DBI(psi), &zErr);
 +  if( psi->expert.pExpert==0 ){
 +    *pzErr = smprintf("sqlite3_expert_new: %s\n",
 +                      zErr ? zErr : "out of memory");
 +    return DCR_Error;
 +  }else{
 +    sqlite3_expert_config(psi->expert.pExpert, EXPERT_CONFIG_SAMPLE, iSample);
 +  }
 +
 +  return DCR_Ok;
 +}
 +
 +DISPATCHABLE_COMMAND( explain ? 1 2 ){
 +  /* The ".explain" command is automatic now.  It is largely
 +  ** pointless, retained purely for backwards compatibility */
 +  ShellInState *psi = ISS(p);
 +  int val = 1;
 +  if( nArg>1 ){
 +    if( cli_strcmp(azArg[1],"auto")==0 ){
 +      val = 99;
 +    }else{
 +      val = booleanValue(azArg[1]);
 +    }
 +  }
 +  if( val==1 && psi->mode!=MODE_Explain ){
 +    psi->normalMode = psi->mode;
 +    psi->mode = MODE_Explain;
 +    psi->autoExplain = 0;
 +  }else if( val==0 ){
 +    if( psi->mode==MODE_Explain ) psi->mode = psi->normalMode;
 +    psi->autoExplain = 0;
 +  }else if( val==99 ){
 +    if( psi->mode==MODE_Explain ) psi->mode = psi->normalMode;
 +    psi->autoExplain = 1;
 +  }
 +  return DCR_Ok;
  }
  
 +/*****************
 + * The .excel, .once and .output commands
 + * These share much implementation, so they stick together.
 + */
 +CONDITION_COMMAND(excel !defined(SQLITE_SHELL_FIDDLE));
 +CONDITION_COMMAND(once !defined(SQLITE_SHELL_FIDDLE));
 +CONDITION_COMMAND(output !defined(SQLITE_SHELL_FIDDLE));
  
 -/*
 -** Implementation of .ar "Remove" command.
 -*/
 -static int arRemoveCommand(ArCommand *pAr){
 +COLLECT_HELP_TEXT[
 +  ".excel                   Display the output of next command in spreadsheet",
 +  "   --bom                   Prefix the file with a UTF8 byte-order mark",
 +  ".once ?OPTIONS? ?FILE?   Output for the next SQL command only to FILE",
 +  "   If FILE begins with '|' then open it as a command to be piped into.",
 +  "   Options:",
 +  "     --bom                 Prefix output with a UTF8 byte-order mark",
 +  "     -e                    Send output to the system text editor",
 +  "     -x       Send output as CSV to a spreadsheet (same as \".excel\")",
 +  ".output ?FILE?           Send output to FILE or stdout if FILE is omitted",
 +  "   If FILE begins with '|' then open it as a command to be piped into.",
 +  "   Options:",
 +  "     --bom                 Prefix output with a UTF8 byte-order mark",
 +  "     -e                    Send output to the system text editor",
 +  "     -x       Send output as CSV to a spreadsheet (same as \".excel\")",
 +];
 +#ifndef SQLITE_SHELL_FIDDLE
 +/* Shared implementation of .excel, .once and .output */
 +static DotCmdRC outputRedirs(char *azArg[], int nArg,
 +                                      ShellInState *psi, char **pzErr,
 +                                      int bOnce, int eMode){
 +  /* bOnce => 0: .output, 1: .once, 2: .excel */
 +  /* eMode => 'x' for excel, else 0 */
    int rc = 0;
 -  char *zSql = 0;
 -  char *zWhere = 0;
 +  char *zFile = 0;
 +  u8 bTxtMode = 0;
 +  u8 bPutBOM = 0;
 +  int i;
 +  static unsigned const char zBOM[4] = {0xef,0xbb,0xbf,0};
  
 -  if( pAr->nArg ){
 -    /* Verify that args actually exist within the archive before proceeding.
 -    ** And formulate a WHERE clause to match them.  */
 -    rc = arCheckEntries(pAr);
 -    arWhereClause(&rc, pAr, &zWhere);
 -  }
 -  if( rc==SQLITE_OK ){
 -    zSql = sqlite3_mprintf("DELETE FROM %s WHERE %s;",
 -                           pAr->zSrcTable, zWhere);
 -    if( pAr->bDryRun ){
 -      utf8_printf(pAr->p->out, "%s\n", zSql);
 -    }else{
 -      char *zErr = 0;
 -      rc = sqlite3_exec(pAr->db, "SAVEPOINT ar;", 0, 0, 0);
 -      if( rc==SQLITE_OK ){
 -        rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr);
 -        if( rc!=SQLITE_OK ){
 -          sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0);
 -        }else{
 -          rc = sqlite3_exec(pAr->db, "RELEASE ar;", 0, 0, 0);
 +  sstr_ptr_holder(&zFile);
 +  if( psi->bSafeMode ) return DCR_AbortError;
 +  for(i=1; i<nArg; i++){
 +    char *z = azArg[i];
 +    if( z[0]=='-' ){
 +      if( z[1]=='-' ) z++;
 +      if( cli_strcmp(z,"-bom")==0 ){
 +        bPutBOM = 1;
 +      }else if( bOnce!=2 && cli_strcmp(z,"-x")==0 ){
 +        eMode = 'x';  /* spreadsheet */
 +      }else if( bOnce!=2 && cli_strcmp(z,"-e")==0 ){
 +        eMode = 'e';  /* text editor */
 +      }else{
 +        return DCR_Unknown|i;
 +      }
 +    }else if( zFile==0 && eMode!='e' && eMode!='x' ){
 +      zFile = smprintf("%s", z);
 +      shell_check_ooms(zFile);
 +      if( zFile[0]=='|' ){
 +        while( i+1<nArg ){
 +          zFile = smprintf("%z %s", zFile, azArg[++i]);
 +          shell_check_ooms(zFile);
          }
 +        break;
        }
 -      if( zErr ){
 -        utf8_printf(stdout, "ERROR: %s\n", zErr);
 -        sqlite3_free(zErr);
 +    }else{
 +      release_holder();
 +      return DCR_TooMany|i;
 +    }
 +  }
 +  if( zFile==0 ){
 +    zFile = smprintf("stdout");
 +    shell_check_ooms(zFile);
 +  }
 +  if( bOnce ){
 +    psi->outCount = 2;
 +  }else{
 +    psi->outCount = 0;
 +  }
 +  output_reset(psi);
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +  if( eMode=='e' || eMode=='x' ){
 +    psi->doXdgOpen = 1;
 +    outputModePush(psi);
 +    if( eMode=='x' ){
 +      /* spreadsheet mode.  Output as CSV. */
 +      newTempFile(psi, "csv");
 +      psi->shellFlgs &= ~SHFLG_Echo;
 +      psi->mode = MODE_Csv;
 +      sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator, SEP_Comma);
 +      sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, SEP_CrLf);
 +    }else{
 +      /* text editor mode */
 +      newTempFile(psi, "txt");
 +      bTxtMode = 1;
 +    }
 +    sqlite3_free(zFile);
 +    zFile = smprintf("%s", psi->zTempFile);
 +  }
 +#endif /* SQLITE_NOHAVE_SYSTEM */
 +  shell_check_ooms(zFile);
 +  if( zFile[0]=='|' ){
 +#ifdef SQLITE_OMIT_POPEN
 +    *pzErr = smprintf("pipes are not supported in this OS\n");
 +    rc = 1;
 +    psi->out = STD_OUT;
 +#else
 +    psi->out = popen(zFile + 1, "w");
 +    if( psi->out==0 ){
 +      *pzErr = smprintf("cannot open pipe \"%s\"\n", zFile + 1);
 +      psi->out = STD_OUT;
 +      rc = 1;
 +    }else{
 +      if( bPutBOM ) fwrite(zBOM, 1, 3, psi->out);
 +      sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile);
 +    }
 +#endif
 +  }else{
 +    psi->out = output_file_open(zFile, bTxtMode);
 +    if( psi->out==0 ){
 +      if( cli_strcmp(zFile,"off")!=0 ){
 +        *pzErr = smprintf("cannot write to \"%s\"\n", zFile);
        }
 +      psi->out = STD_OUT;
 +      rc = 1;
 +    } else {
 +      if( bPutBOM ) fwrite(zBOM, 1, 3, psi->out);
 +      sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile);
      }
    }
 -  sqlite3_free(zWhere);
 -  sqlite3_free(zSql);
 -  return rc;
 +  release_holder();
 +  return DCR_Ok|rc;
  }
 +#endif /* !defined(SQLITE_SHELL_FIDDLE)*/
  
 -/*
 -** Implementation of .ar "eXtract" command.
 -*/
 -static int arExtractCommand(ArCommand *pAr){
 -  const char *zSql1 =
 -    "SELECT "
 -    " ($dir || name),"
 -    " writefile(($dir || name), %s, mode, mtime) "
 -    "FROM %s WHERE (%s) AND (data IS NULL OR $dirOnly = 0)"
 -    " AND name NOT GLOB '*..[/\\]*'";
 +DISPATCHABLE_COMMAND( excel ? 1 2 ){
 +  return outputRedirs(azArg, nArg, ISS(p), pzErr, 2, 'x');
 +}
 +DISPATCHABLE_COMMAND( once ? 1 6 ){
 +  return outputRedirs(azArg, nArg, ISS(p), pzErr, 1, 0);
 +}
 +DISPATCHABLE_COMMAND( output ? 1 6 ){
 +  return outputRedirs(azArg, nArg, ISS(p), pzErr, 0, 0);
 +}
  
 -  const char *azExtraArg[] = {
 -    "sqlar_uncompress(data, sz)",
 -    "data"
 -  };
  
 -  sqlite3_stmt *pSql = 0;
 -  int rc = SQLITE_OK;
 -  char *zDir = 0;
 -  char *zWhere = 0;
 -  int i, j;
 +/*****************
 + * The .filectrl and fullschema commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".filectrl CMD ...        Run various sqlite3_file_control() operations",
 +  "   --schema SCHEMA         Use SCHEMA instead of \"main\"",
 +  "   --help                  Show CMD details",
 +  ".fullschema ?--indent?   Show schema and the content of sqlite_stat tables",
 +];
 +DISPATCHABLE_COMMAND( filectrl ? 2 0 ){
 +  static const struct {
 +    const char *zCtrlName;   /* Name of a test-control option */
 +    int ctrlCode;            /* Integer code for that option */
 +    const char *zUsage;      /* Usage notes */
 +  } aCtrl[] = {
 +    { "chunk_size",     SQLITE_FCNTL_CHUNK_SIZE,      "SIZE"           },
 +    { "data_version",   SQLITE_FCNTL_DATA_VERSION,    ""               },
 +    { "has_moved",      SQLITE_FCNTL_HAS_MOVED,       ""               },
 +    { "lock_timeout",   SQLITE_FCNTL_LOCK_TIMEOUT,    "MILLISEC"       },
 +    { "persist_wal",    SQLITE_FCNTL_PERSIST_WAL,     "[BOOLEAN]"      },
 + /* { "pragma",         SQLITE_FCNTL_PRAGMA,          "NAME ARG"       },*/
 +    { "psow",       SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]"      },
 +    { "reserve_bytes",  SQLITE_FCNTL_RESERVE_BYTES,   "[N]"            },
 +    { "size_limit",     SQLITE_FCNTL_SIZE_LIMIT,      "[LIMIT]"        },
 +    { "tempfilename",   SQLITE_FCNTL_TEMPFILENAME,    ""               },
 + /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY,  "COUNT DELAY"    },*/
 +  };
 +  ShellInState *psi = ISS(p);
 +  int filectrl = -1;
 +  int iCtrl = -1;
 +  sqlite3_int64 iRes = 0;  /* Integer result to display if rc2==1 */
 +  int isOk = 0;            /* 0: usage  1: %lld  2: no-result */
 +  int n2, i;
 +  const char *zCmd = 0;
 +  const char *zSchema = 0;
  
 -  /* If arguments are specified, check that they actually exist within
 -  ** the archive before proceeding. And formulate a WHERE clause to
 -  ** match them.  */
 -  rc = arCheckEntries(pAr);
 -  arWhereClause(&rc, pAr, &zWhere);
 +  open_db(p, 0);
 +  zCmd = nArg>=2 ? azArg[1] : "help";
  
 -  if( rc==SQLITE_OK ){
 -    if( pAr->zDir ){
 -      zDir = sqlite3_mprintf("%s/", pAr->zDir);
 -    }else{
 -      zDir = sqlite3_mprintf("");
 -    }
 -    if( zDir==0 ) rc = SQLITE_NOMEM;
 +  if( zCmd[0]=='-'
 +      && (cli_strcmp(zCmd,"--schema")==0 || cli_strcmp(zCmd,"-schema")==0)
 +      && nArg>=4
 +      ){
 +    zSchema = azArg[2];
 +    for(i=3; i<nArg; i++) azArg[i-2] = azArg[i];
 +    nArg -= 2;
 +    zCmd = azArg[1];
    }
  
 -  shellPreparePrintf(pAr->db, &rc, &pSql, zSql1,
 -      azExtraArg[pAr->bZip], pAr->zSrcTable, zWhere
 -  );
 +  /* The argument can optionally begin with "-" or "--" */
 +  if( zCmd[0]=='-' && zCmd[1] ){
 +    zCmd++;
 +    if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 +  }
  
 -  if( rc==SQLITE_OK ){
 -    j = sqlite3_bind_parameter_index(pSql, "$dir");
 -    sqlite3_bind_text(pSql, j, zDir, -1, SQLITE_STATIC);
 +  /* --help lists all file-controls */
 +  if( cli_strcmp(zCmd,"help")==0 ){
 +    utf8_printf(psi->out, "Available file-controls:\n");
 +    for(i=0; i<ArraySize(aCtrl); i++){
 +      utf8_printf(psi->out, "  .filectrl %s %s\n",
 +                  aCtrl[i].zCtrlName, aCtrl[i].zUsage);
 +    }
 +    return DCR_Error;
 +  }
  
 -    /* Run the SELECT statement twice. The first time, writefile() is called
 -    ** for all archive members that should be extracted. The second time,
 -    ** only for the directories. This is because the timestamps for
 -    ** extracted directories must be reset after they are populated (as
 -    ** populating them changes the timestamp).  */
 -    for(i=0; i<2; i++){
 -      j = sqlite3_bind_parameter_index(pSql, "$dirOnly");
 -      sqlite3_bind_int(pSql, j, i);
 -      if( pAr->bDryRun ){
 -        utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql));
 +  /* Convert filectrl text option to value. Allow any
 +  ** unique prefix of the option name, or a numerical value. */
 +  n2 = strlen30(zCmd);
 +  for(i=0; i<ArraySize(aCtrl); i++){
 +    if( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
 +      if( filectrl<0 ){
 +        filectrl = aCtrl[i].ctrlCode;
 +        iCtrl = i;
        }else{
 -        while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
 -          if( i==0 && pAr->bVerbose ){
 -            utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0));
 -          }
 -        }
 +        *pzErr = smprintf("ambiguous file-control: \"%s\"\n"
 +                          "Use \".filectrl --help\" for help\n", zCmd);
 +        return DCR_ArgWrong;
        }
 -      shellReset(&rc, pSql);
      }
 -    shellFinalize(&rc, pSql);
    }
 +  if( filectrl<0 ){
 +    *pzErr = smprintf("unknown file-control: %s\n"
 +                      "Use \".filectrl --help\" for help\n", zCmd);
 +    return DCR_ArgWrong;
 +  }else{
 +   switch(filectrl){
 +    case SQLITE_FCNTL_SIZE_LIMIT: {
 +      if( nArg!=2 && nArg!=3 ) break;
 +      iRes = nArg==3 ? integerValue(azArg[2]) : -1;
 +      sqlite3_file_control(DBX(p), zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes);
 +      isOk = 1;
 +      break;
 +    }
 +    case SQLITE_FCNTL_LOCK_TIMEOUT:
 +    case SQLITE_FCNTL_CHUNK_SIZE: {
 +      int x;
 +      if( nArg!=3 ) break;
 +      x = (int)integerValue(azArg[2]);
 +      sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
 +      isOk = 2;
 +      break;
 +    }
 +    case SQLITE_FCNTL_PERSIST_WAL:
 +    case SQLITE_FCNTL_POWERSAFE_OVERWRITE: {
 +      int x;
 +      if( nArg!=2 && nArg!=3 ) break;
 +      x = nArg==3 ? booleanValue(azArg[2]) : -1;
 +      sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
 +      iRes = x;
 +      isOk = 1;
 +      break;
 +    }
 +    case SQLITE_FCNTL_DATA_VERSION:
 +    case SQLITE_FCNTL_HAS_MOVED: {
 +      int x;
 +      if( nArg!=2 ) break;
 +      sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
 +      iRes = x;
 +      isOk = 1;
 +      break;
 +    }
 +    case SQLITE_FCNTL_TEMPFILENAME: {
 +      char *z = 0;
 +      if( nArg!=2 ) break;
 +      sqlite3_file_control(DBX(p), zSchema, filectrl, &z);
 +      if( z ){
 +        utf8_printf(psi->out, "%s\n", z);
 +        sqlite3_free(z);
 +      }
 +      isOk = 2;
 +      break;
 +    }
 +    case SQLITE_FCNTL_RESERVE_BYTES: {
 +      int x;
 +      if( nArg>=3 ){
 +        x = atoi(azArg[2]);
 +        sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
 +      }
 +      x = -1;
 +      sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
 +      utf8_printf(psi->out,"%d\n", x);
 +      isOk = 2;
 +      break;
 +    }
 +   }
 +  }
 +  if( isOk==0 && iCtrl>=0 ){
 +    *pzErr = smprintf("Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
 +    return DCR_CmdErred;
 +  }else if( isOk==1 ){
 +    char zBuf[21];
 +    sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes);
 +    raw_printf(psi->out, "%s\n", zBuf);
 +  }
 +  return DCR_Ok;
 +}
  
 -  sqlite3_free(zDir);
 -  sqlite3_free(zWhere);
 -  return rc;
 +static void modePopper(ShellInState *psi){
 +  outputModePop(psi);
  }
  
 -/*
 -** Run the SQL statement in zSql.  Or if doing a --dryrun, merely print it out.
 -*/
 -static int arExecSql(ArCommand *pAr, const char *zSql){
 +DISPATCHABLE_COMMAND( fullschema ? 1 2 ){
    int rc;
 -  if( pAr->bDryRun ){
 -    utf8_printf(pAr->p->out, "%s\n", zSql);
 -    rc = SQLITE_OK;
 +  int doStats = 0;
 +  ShellInState *psi = ISS(p);
 +  u8 useMode = MODE_Semi;
 +  AnyResourceHolder arh = {psi, (GenericFreer)modePopper};
 +
 +  if( nArg==2 && optionMatch(azArg[1], "indent") ){
 +    useMode = MODE_Pretty;
 +    nArg = 1;
 +  }
 +  if( nArg!=1 ){
 +    return DCR_TooMany|1;
 +  }
 +  outputModePush(psi); /* Can fail to return due to OOM. */
 +  any_ref_holder(&arh);
 +  psi->showHeader = 0;
 +  psi->cMode = psi->mode = useMode;
 +  open_db(p, 0);
 +  rc = s3_exec_noom(DBX(p),
 +    "SELECT sql FROM"
 +    "  (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x"
 +    "     FROM sqlite_schema UNION ALL"
 +    "   SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) "
 +    "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' "
 +    "ORDER BY x",
 +    callback, p, 0
 +  );
 +  if( rc==SQLITE_OK ){
 +    sqlite3_stmt *pStmt;
 +    rc = s3_prepare_v2_noom(p->dbUser,
 +                            "SELECT rowid FROM sqlite_schema"
 +                            " WHERE name GLOB 'sqlite_stat[134]'",
 +                            -1, &pStmt, 0);
 +    stmt_holder(pStmt);
 +    doStats = s3_step_noom(pStmt)==SQLITE_ROW;
 +    release_holder();
 +  }
 +  if( doStats==0 ){
 +    raw_printf(psi->out, "/* No STAT tables available */\n");
    }else{
 -    char *zErr = 0;
 -    rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr);
 -    if( zErr ){
 -      utf8_printf(stdout, "ERROR: %s\n", zErr);
 -      sqlite3_free(zErr);
 +    const char *zOldDestTable = p->zDestTable;
 +    raw_printf(psi->out, "ANALYZE sqlite_schema;\n");
 +    psi->cMode = psi->mode = MODE_Insert;
 +    p->zDestTable = "sqlite_stat1";
 +    shell_exec(p, "SELECT * FROM sqlite_stat1", 0);
 +    p->zDestTable = "sqlite_stat4";
 +    shell_exec(p, "SELECT * FROM sqlite_stat4", 0);
 +    raw_printf(psi->out, "ANALYZE sqlite_schema;\n");
 +    p->zDestTable = zOldDestTable;
 +  }
 +  release_holder(); /* Restore shell state */
 +  return rc > 0;
 +}
 +
 +/*****************
 + * The .headers command
 + */
 +COLLECT_HELP_TEXT[
 +  ".headers on|off          Turn display of headers on or off",
 +];
 +DISPATCHABLE_COMMAND( headers 6 2 2 ){
 +  ISS(p)->showHeader = booleanValue(azArg[1]);
 +  ISS(p)->shellFlgs |= SHFLG_HeaderSet;
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * The .help command
 + */
 +
 +/* This literal's value AND address are used for help's workings. */
 +static const char *zHelpAll = "-all";
 +
 +COLLECT_HELP_TEXT[
 +  ".help ?PATTERN?|?-all?   Show help for PATTERN or everything, or summarize",
 +  "                           Repeat -all to see undocumented commands",
 +];
 +DISPATCHABLE_COMMAND( help 3 1 3 ){
 +  const char *zPat = 0;
 +  FILE *out = ISS(p)->out;
 +  if( nArg>1 ){
 +    char *z = azArg[1];
 +    if( (nArg==2 && azArg[1][0]=='0' && azArg[1][1]==0)
 +       || (nArg==3 && cli_strcmp(z, zHelpAll)==0
 +           && cli_strcmp(azArg[2], zHelpAll)==0) ){
 +      /* Show the undocumented command help */
 +      zPat = zHelpAll;
 +    }else if( cli_strcmp(z,"-a")==0 || optionMatch(z, "all") ){
 +      zPat = "";
 +    }else{
 +      zPat = z;
      }
    }
 -  return rc;
 +  if( showHelp(out, zPat, p)==0 && nArg>1 ){
 +    utf8_printf(out, "Nothing matches '%s'\n", azArg[1]);
 +  }
 +  /* Help pleas never fail! */
 +  return DCR_Ok;
  }
  
 +CONDITION_COMMAND(import !defined(SQLITE_SHELL_FIDDLE));
 +/*****************
 + * The .import command
 + */
 +COLLECT_HELP_TEXT[
 +  ".import FILE TABLE       Import data from FILE into TABLE",
 +  "   Options:",
 +  "     --ascii               Use \\037 and \\036 as column and row separators",
 +  "     --csv                 Use , and \\n as column and row separators",
 +  "     --skip N              Skip the first N rows of input",
 +  "     --schema S            Target table to be S.TABLE",
 +  "     -v                    \"Verbose\" - increase auxiliary output",
 +  "   Notes:",
 +  "     *  If TABLE does not exist, it is created.  The first row of input",
 +  "        determines the column names.",
 +  "     *  If neither --csv or --ascii are used, the input mode is derived",
 +  "        from the \".mode\" output mode",
 +  "     *  If FILE begins with \"|\" then it is a command that generates the",
 +  "        input text.",
 +];
 +DISPATCHABLE_COMMAND( import ? 3 7 ){
 +  char *zTable = 0;           /* Insert data into this table */
 +  char *zSchema = 0;          /* within this schema (may default to "main") */
 +  char *zFile = 0;            /* Name of file to extra content from */
 +  sqlite3_stmt *pStmt = NULL; /* A statement */
 +  int nCol;                   /* Number of columns in the table */
 +  int nByte;                  /* Number of bytes in an SQL string */
 +  int i, j;                   /* Loop counters */
 +  int needCommit;             /* True to COMMIT or ROLLBACK at end */
 +  int nSep;                   /* Number of bytes in psi->colSeparator[] */
 +  char *zSql = 0;             /* An SQL statement */
 +  char *zFullTabName = 0;     /* Table name with schema if applicable */
 +  ImportCtx sCtx = {0};       /* Reader context */
 +  AnyResourceHolder arh = { &sCtx, (GenericFreer)import_cleanup };
 +  char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
 +  int eVerbose = 0;           /* Larger for more console output */
 +  int nSkip = 0;              /* Initial lines to skip */
 +  int useOutputMode = 1;      /* Use output mode to determine separators */
 +  FILE *out = ISS(p)->out;    /* output stream */
 +  char *zCreate = 0;          /* CREATE TABLE statement text */
 +  ShellInState *psi = ISS(p);
 +  ResourceMark mark = holder_mark();
 +  int rc = 0;
  
 -/*
 -** Implementation of .ar "create", "insert", and "update" commands.
 -**
 -**     create    ->     Create a new SQL archive
 -**     insert    ->     Insert or reinsert all files listed
 -**     update    ->     Insert files that have changed or that were not
 -**                      previously in the archive
 -**
 -** Create the "sqlar" table in the database if it does not already exist.
 -** Then add each file in the azFile[] array to the archive. Directories
 -** are added recursively. If argument bVerbose is non-zero, a message is
 -** printed on stdout for each file archived.
 -**
 -** The create command is the same as update, except that it drops
 -** any existing "sqlar" table before beginning.  The "insert" command
 -** always overwrites every file named on the command-line, where as
 -** "update" only overwrites if the size or mtime or mode has changed.
 -*/
 -static int arCreateOrUpdateCommand(
 -  ArCommand *pAr,                 /* Command arguments and options */
 -  int bUpdate,                    /* true for a --create. */
 -  int bOnlyIfChanged              /* Only update if file has changed */
 -){
 -  const char *zCreate =
 -      "CREATE TABLE IF NOT EXISTS sqlar(\n"
 -      "  name TEXT PRIMARY KEY,  -- name of the file\n"
 -      "  mode INT,               -- access permissions\n"
 -      "  mtime INT,              -- last modification time\n"
 -      "  sz INT,                 -- original file size\n"
 -      "  data BLOB               -- compressed content\n"
 -      ")";
 -  const char *zDrop = "DROP TABLE IF EXISTS sqlar";
 -  const char *zInsertFmt[2] = {
 -     "REPLACE INTO %s(name,mode,mtime,sz,data)\n"
 -     "  SELECT\n"
 -     "    %s,\n"
 -     "    mode,\n"
 -     "    mtime,\n"
 -     "    CASE substr(lsmode(mode),1,1)\n"
 -     "      WHEN '-' THEN length(data)\n"
 -     "      WHEN 'd' THEN 0\n"
 -     "      ELSE -1 END,\n"
 -     "    sqlar_compress(data)\n"
 -     "  FROM fsdir(%Q,%Q) AS disk\n"
 -     "  WHERE lsmode(mode) NOT LIKE '?%%'%s;"
 -     ,
 -     "REPLACE INTO %s(name,mode,mtime,data)\n"
 -     "  SELECT\n"
 -     "    %s,\n"
 -     "    mode,\n"
 -     "    mtime,\n"
 -     "    data\n"
 -     "  FROM fsdir(%Q,%Q) AS disk\n"
 -     "  WHERE lsmode(mode) NOT LIKE '?%%'%s;"
 -  };
 -  int i;                          /* For iterating through azFile[] */
 -  int rc;                         /* Return code */
 -  const char *zTab = 0;           /* SQL table into which to insert */
 -  char *zSql;
 -  char zTemp[50];
 -  char *zExists = 0;
 -
 -  arExecSql(pAr, "PRAGMA page_size=512");
 -  rc = arExecSql(pAr, "SAVEPOINT ar;");
 -  if( rc!=SQLITE_OK ) return rc;
 -  zTemp[0] = 0;
 -  if( pAr->bZip ){
 -    /* Initialize the zipfile virtual table, if necessary */
 -    if( pAr->zFile ){
 -      sqlite3_uint64 r;
 -      sqlite3_randomness(sizeof(r),&r);
 -      sqlite3_snprintf(sizeof(zTemp),zTemp,"zip%016llx",r);
 -      zTab = zTemp;
 -      zSql = sqlite3_mprintf(
 -         "CREATE VIRTUAL TABLE temp.%s USING zipfile(%Q)",
 -         zTab, pAr->zFile
 -      );
 -      rc = arExecSql(pAr, zSql);
 -      sqlite3_free(zSql);
 +  if(psi->bSafeMode) return DCR_AbortError;
 +  memset(&sCtx, 0, sizeof(sCtx));
 +  if( psi->mode==MODE_Ascii ){
 +    xRead = ascii_read_one_field;
 +  }else{
 +    xRead = csv_read_one_field;
 +  }
 +  for(i=1; i<nArg; i++){
 +    char *z = azArg[i];
 +    if( z[0]=='-' && z[1]=='-' ) z++;
 +    if( z[0]!='-' ){
 +      if( zFile==0 ){
 +        zFile = z;
 +      }else if( zTable==0 ){
 +        zTable = z;
 +      }else{
 +        return DCR_TooMany|i;
 +      }
 +    }else if( cli_strcmp(z,"-v")==0 ){
 +      eVerbose++;
 +    }else if( cli_strcmp(z,"-schema")==0 && i<nArg-1 ){
 +      zSchema = azArg[++i];
 +    }else if( cli_strcmp(z,"-skip")==0 && i<nArg-1 ){
 +      nSkip = integerValue(azArg[++i]);
 +    }else if( cli_strcmp(z,"-ascii")==0 ){
 +      sCtx.cColSep = SEP_Unit[0];
 +      sCtx.cRowSep = SEP_Record[0];
 +      xRead = ascii_read_one_field;
 +      useOutputMode = 0;
 +    }else if( cli_strcmp(z,"-csv")==0 ){
 +      sCtx.cColSep = ',';
 +      sCtx.cRowSep = '\n';
 +      xRead = csv_read_one_field;
 +      useOutputMode = 0;
      }else{
 -      zTab = "zip";
 +      return DCR_Unknown|i;
 +    }
 +  }
 +  if( zTable==0 ){
 +    *pzErr = smprintf("missing %s argument.\n", zFile==0 ? "FILE" : "TABLE");
 +    return DCR_Missing;
 +  }
-   seenInterrupt = 0;
 +  open_db(p, 0);
 +  if( useOutputMode ){
 +    const char *zYap = 0;
 +    /* If neither the --csv or --ascii options are specified, then set
 +    ** the column and row separator characters from the output mode. */
 +    nSep = strlen30(psi->colSeparator);
 +    if( nSep==0 ){
 +      zYap = "non-null column separator required for import";
 +    }
 +    if( nSep>1 ){
 +      zYap = "multi-character or multi-byte column separators"
 +        " not allowed for import";
 +    }
 +    nSep = strlen30(psi->rowSeparator);
 +    if( nSep==0 ){
 +      zYap = "non-null row separator required for import";
 +    }
 +    if( zYap!=0 ){
 +      *pzErr = smprintf("%s\n", zYap);
 +      return DCR_Error;
 +    }
 +    if( nSep==2 && psi->mode==MODE_Csv
 +        && cli_strcmp(psi->rowSeparator,SEP_CrLf)==0 ){
 +      /* When importing CSV (only), if the row separator is set to the
 +      ** default output row separator, change it to the default input
 +      ** row separator.  This avoids having to maintain different input
 +      ** and output row separators. */
 +      sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, SEP_Row);
 +      nSep = strlen30(psi->rowSeparator);
 +    }
 +    if( nSep>1 ){
 +      *pzErr
 +        = smprintf("multi-character row separators not allowed for import\n");
 +      return DCR_Error;
 +    }
 +    sCtx.cColSep = (u8)psi->colSeparator[0];
 +    sCtx.cRowSep = (u8)psi->rowSeparator[0];
 +  }
 +  sCtx.zFile = zFile;
 +  sCtx.nLine = 1;
 +  if( sCtx.zFile[0]=='|' ){
 +#ifdef SQLITE_OMIT_POPEN
 +    *pzErr = smprintf("pipes are not supported in this OS\n");
 +    return DCR_Error;
 +#else
 +    sCtx.in = popen(sCtx.zFile+1, "r");
 +    sCtx.zFile = "<pipe>";
 +    sCtx.xCloser = pclose;
 +#endif
 +  }else{
 +    sCtx.in = fopen(sCtx.zFile, "rb");
 +    sCtx.xCloser = fclose;
 +  }
 +  if( sCtx.in==0 ){
 +    *pzErr = smprintf("cannot open \"%s\"\n", zFile);
 +    return DCR_Error;
 +  }
 +  /* Here and below, resources must be freed before exit. */
 +  any_ref_holder(&arh);
 +  sCtx.z = sqlite3_malloc64(120);
 +  shell_check_ooms(sCtx.z);
 +  if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
 +    char zSep[2];
 +    zSep[1] = 0;
 +    zSep[0] = sCtx.cColSep;
 +    utf8_printf(out, "Column separator ");
 +    output_c_string(out, zSep);
 +    utf8_printf(out, ", row separator ");
 +    zSep[0] = sCtx.cRowSep;
 +    output_c_string(out, zSep);
 +    utf8_printf(out, "\n");
 +  }
 +  while( (nSkip--)>0 ){
 +    while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
 +  }
 +  if( zSchema!=0 ){
 +    zFullTabName = smprintf("\"%w\".\"%w\"", zSchema, zTable);
 +  }else{
 +    zFullTabName = smprintf("\"%w\"", zTable);
 +  }
 +  shell_check_ooms(zFullTabName);
 +  sstr_ptr_holder(&zFullTabName);
 +  zSql = smprintf("SELECT * FROM %s", zFullTabName);
 +  shell_check_ooms(zSql);
 +  sstr_ptr_holder(&zSql);
 +  nByte = strlen30(zSql);
 +  rc = s3_prepare_v2_noom(DBX(p), zSql, -1, &pStmt, 0);
 +  stmt_ptr_holder(&pStmt);
 +  import_append_char(&sCtx, 0);    /* To ensure sCtx.z is allocated */
 +  if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(DBX(p)))==0 ){
 +    zCreate = smprintf("CREATE TABLE %s", zFullTabName);
 +    sqlite3 *dbCols = 0;
 +    char *zRenames = 0;
 +    char *zColDefs;
 +    shell_check_ooms(zCreate);
 +    sstr_ptr_holder(&zCreate); /* +1 */
 +    sstr_ptr_holder(&zRenames); /* +2 */
 +    sstr_ptr_holder(&zColDefs); /* +3 */
 +    conn_ptr_holder(&dbCols);
 +    while( xRead(&sCtx) ){
 +      zAutoColumn(sCtx.z, &dbCols, 0);
 +      if( sCtx.cTerm!=sCtx.cColSep ) break;
 +    }
 +    zColDefs = zAutoColumn(0, &dbCols, &zRenames);
 +    if( zRenames!=0 ){
 +      FILE *fh = INSOURCE_IS_INTERACTIVE(psi->pInSource)?  out : STD_ERR;
 +      utf8_printf(fh, "Columns renamed during .import %s due to duplicates:\n"
 +                  "%s\n", sCtx.zFile, zRenames);
 +    }
 +    assert(dbCols==0);
 +    drop_holder(); /* dbCols */
 +    if( zColDefs==0 ){
 +      *pzErr = smprintf("%s: empty file\n", sCtx.zFile);
 +    import_fail: /* entry from outer blocks */
 +      RESOURCE_FREE(mark);
 +      return DCR_Error;
 +    }
 +    zCreate = smprintf("%z%z\n", zCreate, zColDefs);
 +    zColDefs = 0;
 +    shell_check_ooms(zCreate);
 +    if( eVerbose>=1 ){
 +      utf8_printf(out, "%s\n", zCreate);
 +    }
 +    rc = s3_exec_noom(DBX(p), zCreate, 0, 0, 0);
 +    if( rc ){
 +      *pzErr = smprintf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(DBX(p)));
 +      goto import_fail;
 +    }
 +    rc = s3_prepare_v2_noom(DBX(p), zSql, -1, &pStmt, 0);
 +  }
 +  if( rc ){
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
 +    goto import_fail;
 +  }
 +  nCol = sqlite3_column_count(pStmt);
 +  sqlite3_finalize(pStmt);
 +  pStmt = 0;
 +  if( nCol==0 ) return DCR_Ok; /* no columns, no error */
 +  sqlite3_free(zSql);
 +  zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 );
 +  shell_check_ooms(zSql);
 +  sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName);
 +  j = strlen30(zSql);
 +  for(i=1; i<nCol; i++){
 +    zSql[j++] = ',';
 +    zSql[j++] = '?';
 +  }
 +  zSql[j++] = ')';
 +  zSql[j] = 0;
 +  if( eVerbose>=2 ){
 +    utf8_printf(psi->out, "Insert using: %s\n", zSql);
 +  }
 +  rc = s3_prepare_v2_noom(DBX(p), zSql, -1, &pStmt, 0);
 +  if( rc ){
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
 +    goto import_fail;
 +  }
 +  needCommit = sqlite3_get_autocommit(DBX(p));
 +  if( needCommit ) sqlite3_exec(DBX(p), "BEGIN", 0, 0, 0);
 +  do{
 +    int startLine = sCtx.nLine;
 +    for(i=0; i<nCol; i++){
 +      char *z = xRead(&sCtx);
 +      /*
 +      ** Did we reach end-of-file before finding any columns?
 +      ** If so, stop instead of NULL filling the remaining columns.
 +      */
 +      if( z==0 && i==0 ) break;
 +      /*
 +      ** Did we reach end-of-file OR end-of-line before finding any
 +      ** columns in ASCII mode?  If so, stop instead of NULL filling
 +      ** the remaining columns.
 +      */
 +      if( psi->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
 +      sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
 +      if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
 +        utf8_printf(STD_ERR, "%s:%d: expected %d columns but found %d - "
 +                    "filling the rest with NULL\n",
 +                    sCtx.zFile, startLine, nCol, i+1);
 +        i += 2;
 +        while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; }
 +      }
      }
 -  }else{
 -    /* Initialize the table for an SQLAR */
 -    zTab = "sqlar";
 -    if( bUpdate==0 ){
 -      rc = arExecSql(pAr, zDrop);
 -      if( rc!=SQLITE_OK ) goto end_ar_transaction;
 +    if( sCtx.cTerm==sCtx.cColSep ){
 +      do{
 +        xRead(&sCtx);
 +        i++;
 +      }while( sCtx.cTerm==sCtx.cColSep );
 +      utf8_printf(STD_ERR, "%s:%d: expected %d columns but found %d - "
 +                  "extras ignored\n",
 +                  sCtx.zFile, startLine, nCol, i);
 +    }
 +    if( i>=nCol ){
 +      sqlite3_step(pStmt);
 +      rc = sqlite3_reset(pStmt);
 +      if( rc!=SQLITE_OK ){
 +        utf8_printf(STD_ERR, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
 +                    startLine, sqlite3_errmsg(DBX(p)));
 +        sCtx.nErr++;
 +      }else{
 +        sCtx.nRow++;
 +      }
      }
 -    rc = arExecSql(pAr, zCreate);
 +  }while( sCtx.cTerm!=EOF );
 +
 +  if( needCommit ) sqlite3_exec(DBX(p), "COMMIT", 0, 0, 0);
 +  if( eVerbose>0 ){
 +    utf8_printf(out,
 +      "Added %d rows with %d errors using %d lines of input\n",
 +      sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
    }
 -  if( bOnlyIfChanged ){
 -    zExists = sqlite3_mprintf(
 -      " AND NOT EXISTS("
 -          "SELECT 1 FROM %s AS mem"
 -          " WHERE mem.name=disk.name"
 -          " AND mem.mtime=disk.mtime"
 -          " AND mem.mode=disk.mode)", zTab);
 +  RESOURCE_FREE(mark);
 +  return DCR_Ok|(sCtx.nErr>0);
 +}
 +
 +/*****************
 + * The .keyword command
 + */
 +CONDITION_COMMAND( keyword !defined(NO_KEYWORD_COMMAND) );
 +COLLECT_HELP_TEXT[
 +  ".keyword ?KW?            List keywords, or say whether KW is one.",
 +];
 +DISPATCHABLE_COMMAND( keyword ? 1 2 ){
 +  FILE *out = ISS(p)->out;
 +  if( nArg<2 ){
 +    int i = 0;
 +    int nk = sqlite3_keyword_count();
 +    int nCol = 0;
 +    int szKW;
 +    while( i<nk ){
 +      const char *zKW = 0;
 +      if( SQLITE_OK==sqlite3_keyword_name(i++, &zKW, &szKW) ){
 +        char kwBuf[50];
 +        if( szKW < sizeof(kwBuf) ){
 +          const char *zSep = " ";
 +          if( (nCol += (1+szKW))>75){
 +            zSep = "\n";
 +            nCol = 0;
 +          }
 +          memcpy(kwBuf, zKW, szKW);
 +          kwBuf[szKW] = 0;
 +          utf8_printf(out, "%s%s", kwBuf, zSep);
 +        }
 +      }
 +    }
 +    if( nCol>0 ) utf8_printf(out, "\n");
    }else{
 -    zExists = sqlite3_mprintf("");
 +    int szKW = strlen30(azArg[1]);
 +    int isKeyword = sqlite3_keyword_check(azArg[1], szKW);
 +    utf8_printf(out, "%s is%s a keyword\n",
 +                azArg[1], (isKeyword)? "" : " not");
    }
 -  if( zExists==0 ) rc = SQLITE_NOMEM;
 -  for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){
 -    char *zSql2 = sqlite3_mprintf(zInsertFmt[pAr->bZip], zTab,
 -        pAr->bVerbose ? "shell_putsnl(name)" : "name",
 -        pAr->azArg[i], pAr->zDir, zExists);
 -    rc = arExecSql(pAr, zSql2);
 -    sqlite3_free(zSql2);
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * The .imposter, .iotrace, .limit, .lint and .log commands
 + */
 +#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE)
 +# define LOAD_ENABLE 1
 +#else
 +# define LOAD_ENABLE 0
 +#endif
 +CONDITION_COMMAND( imposter !defined(SQLITE_OMIT_TEST_CONTROL) );
 +CONDITION_COMMAND( iotrace defined(SQLITE_ENABLE_IOTRACE) );
 +CONDITION_COMMAND( load LOAD_ENABLE );
 +COLLECT_HELP_TEXT[
 +  ",imposter INDEX TABLE    Create imposter table TABLE on index INDEX",
 +  ",iotrace FILE            Enable I/O diagnostic logging to FILE",
 +  ".limit ?LIMIT? ?VAL?     Display or change the value of an SQLITE_LIMIT",
 +  ".lint OPTIONS            Report potential schema issues.",
 +  "     Options:",
 +  "        fkey-indexes     Find missing foreign key indexes",
 +];
 +COLLECT_HELP_TEXT[
 +#if !defined(SQLITE_SHELL_FIDDLE)
 +  ".log FILE|on|off         Turn logging on or off.  FILE can be stderr/stdout",
 +#else
 +  ".log on|off              Turn logging on or off.",
 +#endif
 +];
 +DISPATCHABLE_COMMAND( imposter ? 3 3 ){
 +  int rc = 0;
 +  char *zSql = 0;
 +  char *zCollist = 0;
 +  sqlite3_stmt *pStmt = 0;
 +  sqlite3 *db;
 +  int tnum = 0;
 +  int isWO = 0;  /* True if making an imposter of a WITHOUT ROWID table */
 +  int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */
 +  int i;
 +  ResourceMark mark = holder_mark();
 +
 +  if( !ShellHasFlag(p,SHFLG_TestingMode) ){
 +    utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n",
 +                "imposter");
 +    return DCR_Error;
 +  }
 +  if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){
 +    *pzErr = smprintf("Usage: .imposter INDEX IMPOSTER\n"
 +                      "       .imposter off\n");
 +    /* Also allowed, but not documented:
 +    **
 +    **    .imposter TABLE IMPOSTER
 +    **
 +    ** where TABLE is a WITHOUT ROWID table.  In that case, the
 +    ** imposter is another WITHOUT ROWID table with the columns in
 +    ** storage order. */
 +    return DCR_SayUsage;
    }
 -end_ar_transaction:
 +  open_db(p, 0);
 +  db = DBX(p);
 +  if( nArg==2 ){
 +    sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 1);
 +    return DCR_Ok;
 +  }
 +  sstr_ptr_holder(&zSql);
 +  zSql = smprintf("SELECT rootpage, 0 FROM sqlite_schema"
 +                  " WHERE name='%q' AND type='index'"
 +                  "UNION ALL "
 +                  "SELECT rootpage, 1 FROM sqlite_schema"
 +                  " WHERE name='%q' AND type='table'"
 +                  "  AND sql LIKE '%%without%%rowid%%'",
 +                  azArg[1], azArg[1]);
 +  rc = s3_prep_noom_free(db, &zSql, &pStmt);
    if( rc!=SQLITE_OK ){
 -    sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0);
 +    release_holder();
 +    return DCR_Error;
 +  }
 +  stmt_ptr_holder(&pStmt);
 +  if( s3_step_noom(pStmt)==SQLITE_ROW ){
 +    tnum = sqlite3_column_int(pStmt, 0);
 +    isWO = sqlite3_column_int(pStmt, 1);
 +  }
 +  zSql = smprintf("PRAGMA index_xinfo='%q'", azArg[1]);
 +  sqlite3_finalize(pStmt);
 +  pStmt = 0;
 +  rc = s3_prep_noom_free(db, &zSql, &pStmt);
 +  i = 0;
 +  sstr_ptr_holder(&zCollist);
 +  while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 +    char zLabel[20];
 +    const char *zCol = (const char*)sqlite3_column_text(pStmt,2);
 +    i++;
 +    if( zCol==0 ){
 +      if( sqlite3_column_int(pStmt,1)==-1 ){
 +        zCol = "_ROWID_";
 +      }else{
 +        sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i);
 +        zCol = zLabel;
 +      }
 +    }
 +    if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){
 +      lenPK = (int)strlen(zCollist);
 +    }
 +    if( zCollist==0 ){
 +      zCollist = smprintf("\"%w\"", zCol);
 +    }else{
 +      zCollist = smprintf("%z,\"%w\"", zCollist, zCol);
 +    }
 +  }
 +  if( i==0 || tnum==0 ){
 +    *pzErr = smprintf("no such index: \"%s\"\n", azArg[1]);
 +    RESOURCE_FREE(mark);
 +    return DCR_Error;
 +  }
 +  if( lenPK==0 ) lenPK = 100000;
 +  zSql = smprintf("CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))"
 +                  "WITHOUT ROWID", azArg[2], zCollist, lenPK, zCollist);
 +  shell_check_ooms(zSql);
 +  rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 1, tnum);
 +  if( rc==SQLITE_OK ){
 +    rc = s3_exec_noom(db, zSql, 0, 0, 0);
 +    sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 0);
 +    if( rc ){
 +      *pzErr = smprintf("Error in [%s]: %s\n", zSql, sqlite3_errmsg(db));
 +    }else{
 +      utf8_printf(STD_OUT, "%s;\n", zSql);
 +      raw_printf(STD_OUT, "WARNING: "
 +                 "writing to an imposter table will corrupt the \"%s\" %s!\n",
 +                 azArg[1], isWO ? "table" : "index"
 +                 );
 +    }
    }else{
 -    rc = arExecSql(pAr, "RELEASE ar;");
 -    if( pAr->bZip && pAr->zFile ){
 -      zSql = sqlite3_mprintf("DROP TABLE %s", zTemp);
 -      arExecSql(pAr, zSql);
 -      sqlite3_free(zSql);
 +    *pzErr = smprintf("SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
 +  }
 +  RESOURCE_FREE(mark);
 +  return DCR_Ok|(rc != 0);
 +}
 +DISPATCHABLE_COMMAND( iotrace ? 2 2 ){
 +  SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...);
 +  if( iotrace && iotrace!=STD_OUT ) fclose(iotrace);
 +  iotrace = 0;
 +  if( nArg<2 ){
 +    sqlite3IoTrace = 0;
 +  }else if( cli_strcmp(azArg[1], "-")==0 ){
 +    sqlite3IoTrace = iotracePrintf;
 +    iotrace = STD_OUT;
 +  }else{
 +    iotrace = fopen(azArg[1], "w");
 +    if( iotrace==0 ){
 +      *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
 +      sqlite3IoTrace = 0;
 +      return DCR_Error;
 +    }else{
 +      sqlite3IoTrace = iotracePrintf;
      }
    }
 -  sqlite3_free(zExists);
 -  return rc;
 +  return DCR_Ok;
  }
  
 -/*
 -** Implementation of ".ar" dot command.
 -*/
 -static int arDotCommand(
 -  ShellState *pState,          /* Current shell tool state */
 -  int fromCmdLine,             /* True if -A command-line option, not .ar cmd */
 -  char **azArg,                /* Array of arguments passed to dot command */
 -  int nArg                     /* Number of entries in azArg[] */
 -){
 -  ArCommand cmd;
 -  int rc;
 -  memset(&cmd, 0, sizeof(cmd));
 -  cmd.fromCmdLine = fromCmdLine;
 -  rc = arParseCommand(azArg, nArg, &cmd);
 -  if( rc==SQLITE_OK ){
 -    int eDbType = SHELL_OPEN_UNSPEC;
 -    cmd.p = pState;
 -    cmd.db = pState->db;
 -    if( cmd.zFile ){
 -      eDbType = deduceDatabaseType(cmd.zFile, 1);
 -    }else{
 -      eDbType = pState->openMode;
 +/*****************
 + * The .limits and .load commands
 + */
 +COLLECT_HELP_TEXT[
 +  ",limits ?LIMIT_NAME?     Display limit selected by its name, or all limits",
 +  ".load FILE ?ENTRY?       Load a SQLite extension library",
 +  "   If ENTRY is provided, the entry point \"sqlite_ENTRY_init\" is called.",
 +  "   Otherwise, the entry point name is derived from the FILE's name.",
 +];
 +
 +DISPATCHABLE_COMMAND( limits 5 1 3 ){
 +  static const struct {
 +    const char *zLimitName;   /* Name of a limit */
 +    int limitCode;            /* Integer code for that limit */
 +  } aLimit[] = {
 +    { "length",                SQLITE_LIMIT_LENGTH                    },
 +    { "sql_length",            SQLITE_LIMIT_SQL_LENGTH                },
 +    { "column",                SQLITE_LIMIT_COLUMN                    },
 +    { "expr_depth",            SQLITE_LIMIT_EXPR_DEPTH                },
 +    { "compound_select",       SQLITE_LIMIT_COMPOUND_SELECT           },
 +    { "vdbe_op",               SQLITE_LIMIT_VDBE_OP                   },
 +    { "function_arg",          SQLITE_LIMIT_FUNCTION_ARG              },
 +    { "attached",              SQLITE_LIMIT_ATTACHED                  },
 +    { "like_pattern_length",   SQLITE_LIMIT_LIKE_PATTERN_LENGTH       },
 +    { "variable_number",       SQLITE_LIMIT_VARIABLE_NUMBER           },
 +    { "trigger_depth",         SQLITE_LIMIT_TRIGGER_DEPTH             },
 +    { "worker_threads",        SQLITE_LIMIT_WORKER_THREADS            },
 +  };
 +  int i, n2;
 +  open_db(p, 0);
 +  if( nArg==1 ){
 +    for(i=0; i<ArraySize(aLimit); i++){
 +      fprintf(STD_OUT, "%20s %d\n", aLimit[i].zLimitName,
 +             sqlite3_limit(DBX(p), aLimit[i].limitCode, -1));
      }
 -    if( eDbType==SHELL_OPEN_ZIPFILE ){
 -      if( cmd.eCmd==AR_CMD_EXTRACT || cmd.eCmd==AR_CMD_LIST ){
 -        if( cmd.zFile==0 ){
 -          cmd.zSrcTable = sqlite3_mprintf("zip");
 +  }else if( nArg>3 ){
 +    return DCR_TooMany;
 +  }else{
 +    int iLimit = -1;
 +    n2 = strlen30(azArg[1]);
 +    for(i=0; i<ArraySize(aLimit); i++){
 +      if( sqlite3_strnicmp(aLimit[i].zLimitName, azArg[1], n2)==0 ){
 +        if( iLimit<0 ){
 +          iLimit = i;
          }else{
 -          cmd.zSrcTable = sqlite3_mprintf("zipfile(%Q)", cmd.zFile);
 +          *pzErr = smprintf("ambiguous limit: \"%s\"\n", azArg[1]);
 +          return DCR_Error;
          }
        }
 -      cmd.bZip = 1;
 -    }else if( cmd.zFile ){
 -      int flags;
 -      if( cmd.bAppend ) eDbType = SHELL_OPEN_APPENDVFS;
 -      if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_INSERT
 -           || cmd.eCmd==AR_CMD_REMOVE || cmd.eCmd==AR_CMD_UPDATE ){
 -        flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE;
 -      }else{
 -        flags = SQLITE_OPEN_READONLY;
 -      }
 -      cmd.db = 0;
 -      if( cmd.bDryRun ){
 -        utf8_printf(pState->out, "-- open database '%s'%s\n", cmd.zFile,
 -             eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : "");
 -      }
 -      rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags,
 -             eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0);
 -      if( rc!=SQLITE_OK ){
 -        utf8_printf(stderr, "cannot open file: %s (%s)\n",
 -            cmd.zFile, sqlite3_errmsg(cmd.db)
 -        );
 -        goto end_ar_command;
 -      }
 -      sqlite3_fileio_init(cmd.db, 0, 0);
 -      sqlite3_sqlar_init(cmd.db, 0, 0);
 -      sqlite3_create_function(cmd.db, "shell_putsnl", 1, SQLITE_UTF8, cmd.p,
 -                              shellPutsFunc, 0, 0);
 -
      }
 -    if( cmd.zSrcTable==0 && cmd.bZip==0 && cmd.eCmd!=AR_CMD_HELP ){
 -      if( cmd.eCmd!=AR_CMD_CREATE
 -       && sqlite3_table_column_metadata(cmd.db,0,"sqlar","name",0,0,0,0,0)
 -      ){
 -        utf8_printf(stderr, "database does not contain an 'sqlar' table\n");
 -        rc = SQLITE_ERROR;
 -        goto end_ar_command;
 -      }
 -      cmd.zSrcTable = sqlite3_mprintf("sqlar");
 +    if( iLimit<0 ){
 +      *pzErr = smprintf("unknown limit: \"%s\"\n"
 +                        "enter \".limits\" with no arguments for a list.\n",
 +                        azArg[1]);
 +      return DCR_ArgWrong;
      }
 -
 -    switch( cmd.eCmd ){
 -      case AR_CMD_CREATE:
 -        rc = arCreateOrUpdateCommand(&cmd, 0, 0);
 -        break;
 -
 -      case AR_CMD_EXTRACT:
 -        rc = arExtractCommand(&cmd);
 -        break;
 -
 -      case AR_CMD_LIST:
 -        rc = arListCommand(&cmd);
 -        break;
 -
 -      case AR_CMD_HELP:
 -        arUsage(pState->out);
 -        break;
 -
 -      case AR_CMD_INSERT:
 -        rc = arCreateOrUpdateCommand(&cmd, 1, 0);
 -        break;
 -
 -      case AR_CMD_REMOVE:
 -        rc = arRemoveCommand(&cmd);
 -        break;
 -
 -      default:
 -        assert( cmd.eCmd==AR_CMD_UPDATE );
 -        rc = arCreateOrUpdateCommand(&cmd, 1, 1);
 -        break;
 +    if( nArg==3 ){
 +      sqlite3_limit(DBX(p), aLimit[iLimit].limitCode,
 +                    (int)integerValue(azArg[2]));
      }
 +    fprintf(STD_OUT, "%20s %d\n", aLimit[iLimit].zLimitName,
 +           sqlite3_limit(DBX(p), aLimit[iLimit].limitCode, -1));
    }
 -end_ar_command:
 -  if( cmd.db!=pState->db ){
 -    close_db(cmd.db);
 -  }
 -  sqlite3_free(cmd.zSrcTable);
 -
 -  return rc;
 +  return DCR_Ok;
  }
 -/* End of the ".archive" or ".ar" command logic
 -*******************************************************************************/
 -#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) */
  
 -#if SQLITE_SHELL_HAVE_RECOVER
 +DISPATCHABLE_COMMAND( lint 3 1 0 ){
 +  sqlite3 *db;                    /* Database handle to query "main" db of */
 +  FILE *out = ISS(p)->out;        /* Stream to write non-error output to */
 +  int bVerbose = 0;               /* If -verbose is present */
 +  int bGroupByParent = 0;         /* If -groupbyparent is present */
 +  int i;                          /* To iterate through azArg[] */
 +  const char *zIndent = "";       /* How much to indent CREATE INDEX by */
 +  int rc;                         /* Return code */
 +  sqlite3_stmt *pSql = 0;         /* Compiled version of SQL statement below */
 +  ResourceMark mark = holder_mark();
  
 -/*
 -** This function is used as a callback by the recover extension. Simply
 -** print the supplied SQL statement to stdout.
 -*/
 -static int recoverSqlCb(void *pCtx, const char *zSql){
 -  ShellState *pState = (ShellState*)pCtx;
 -  utf8_printf(pState->out, "%s;\n", zSql);
 -  return SQLITE_OK;
 -}
 +  i = (nArg>=2 ? strlen30(azArg[1]) : 0);
 +  if( i==0 || 0!=sqlite3_strnicmp(azArg[1], "fkey-indexes", i) ){
 +    *pzErr = smprintf
 +      ("Usage %s sub-command ?switches...?\n"
 +       "Where sub-commands are:\n"
 +       "    fkey-indexes\n", azArg[0]);
 +    return DCR_SayUsage;
 +  }
 +  open_db(p, 0);
 +  db = DBX(p);
  
 -/*
 -** This function is called to recover data from the database. A script
 -** to construct a new database containing all recovered data is output
 -** on stream pState->out.
 -*/
 -static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){
 -  int rc = SQLITE_OK;
 -  const char *zRecoveryDb = "";   /* Name of "recovery" database.  Debug only */
 -  const char *zLAF = "lost_and_found";
 -  int bFreelist = 1;              /* 0 if --ignore-freelist is specified */
 -  int bRowids = 1;                /* 0 if --no-rowids */
 -  sqlite3_recover *p = 0;
 -  int i = 0;
 +  /*
 +  ** This SELECT statement returns one row for each foreign key constraint
 +  ** in the schema of the main database. The column values are:
 +  **
 +  ** 0. The text of an SQL statement similar to:
 +  **
 +  **      "EXPLAIN QUERY PLAN SELECT 1 FROM child_table WHERE child_key=?"
 +  **
 +  **    This SELECT is similar to the one that the foreign keys implementation
 +  **    needs to run internally on child tables. If there is an index that can
 +  **    be used to optimize this query, then it can also be used by the FK
 +  **    implementation to optimize DELETE or UPDATE statements on the parent
 +  **    table.
 +  **
 +  ** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by
 +  **    the EXPLAIN QUERY PLAN command matches this pattern, then the schema
 +  **    contains an index that can be used to optimize the query.
 +  **
 +  ** 2. Human readable text that describes the child table and columns. e.g.
 +  **
 +  **       "child_table(child_key1, child_key2)"
 +  **
 +  ** 3. Human readable text that describes the parent table and columns. e.g.
 +  **
 +  **       "parent_table(parent_key1, parent_key2)"
 +  **
 +  ** 4. A full CREATE INDEX statement for an index that could be used to
 +  **    optimize DELETE or UPDATE statements on the parent table. e.g.
 +  **
 +  **       "CREATE INDEX child_table_child_key ON child_table(child_key)"
 +  **
 +  ** 5. The name of the parent table.
 +  **
 +  ** These six values are used by the C logic below to generate the report.
 +  */
 +  const char *zSql =
 +  "SELECT "
 +    "     'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '"
 +    "  || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' "
 +    "  || fkey_collate_clause("
 +    "       f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')"
 +    ", "
 +    "     'SEARCH ' || s.name || ' USING COVERING INDEX*('"
 +    "  || group_concat('*=?', ' AND ') || ')'"
 +    ", "
 +    "     s.name  || '(' || group_concat(f.[from],  ', ') || ')'"
 +    ", "
 +    "     f.[table] || '(' || group_concat(COALESCE(f.[to], p.[name])) || ')'"
 +    ", "
 +    "     'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))"
 +    "  || ' ON ' || quote(s.name) || '('"
 +    "  || group_concat(quote(f.[from]) ||"
 +    "        fkey_collate_clause("
 +    "          f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')"
 +    "  || ');'"
 +    ", "
 +    "     f.[table] "
 +    "FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f "
 +    "LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) "
 +    "GROUP BY s.name, f.id "
 +    "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)"
 +  ;
 +  const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)";
  
 -  for(i=1; i<nArg; i++){
 -    char *z = azArg[i];
 -    int n;
 -    if( z[0]=='-' && z[1]=='-' ) z++;
 -    n = strlen30(z);
 -    if( n<=17 && memcmp("-ignore-freelist", z, n)==0 ){
 -      bFreelist = 0;
 -    }else
 -    if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
 -      /* This option determines the name of the ATTACH-ed database used
 -      ** internally by the recovery extension.  The default is "" which
 -      ** means to use a temporary database that is automatically deleted
 -      ** when closed.  This option is undocumented and might disappear at
 -      ** any moment. */
 -      i++;
 -      zRecoveryDb = azArg[i];
 -    }else
 -    if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
 -      i++;
 -      zLAF = azArg[i];
 -    }else
 -    if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
 -      bRowids = 0;
 +  for(i=2; i<nArg; i++){
 +    int n = strlen30(azArg[i]);
 +    if( n>1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){
 +      bVerbose = 1;
 +    }
 +    else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){
 +      bGroupByParent = 1;
 +      zIndent = "    ";
      }
      else{
 -      utf8_printf(stderr, "unexpected option: %s\n", azArg[i]);
 -      showHelp(pState->out, azArg[0]);
 -      return 1;
 +      raw_printf(STD_ERR, "Usage: %s %s ?-verbose? ?-groupbyparent?\n",
 +                 azArg[0], azArg[1]
 +      );
 +      return DCR_Unknown|i;
      }
    }
  
 -  p = sqlite3_recover_init_sql(
 -      pState->db, "main", recoverSqlCb, (void*)pState
 +  /* Register the fkey_collate_clause() SQL function */
 +  rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8,
 +      0, shellFkeyCollateClause, 0, 0
    );
  
 -  sqlite3_recover_config(p, 789, (void*)zRecoveryDb);  /* Debug use only */
 -  sqlite3_recover_config(p, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF);
 -  sqlite3_recover_config(p, SQLITE_RECOVER_ROWIDS, (void*)&bRowids);
 -  sqlite3_recover_config(p, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist);
 -
 -  sqlite3_recover_run(p);
 -  if( sqlite3_recover_errcode(p)!=SQLITE_OK ){
 -    const char *zErr = sqlite3_recover_errmsg(p);
 -    int errCode = sqlite3_recover_errcode(p);
 -    raw_printf(stderr, "sql error: %s (%d)\n", zErr, errCode);
 +  if( rc==SQLITE_OK ){
 +    rc = s3_prepare_v2_noom(db, zSql, -1, &pSql, 0);
 +  }
 +  /* Track resources after here. */
 +  stmt_ptr_holder(&pSql);
 +  if( rc==SQLITE_OK ){
 +    sqlite3_bind_int(pSql, 1, bGroupByParent);
    }
 -  rc = sqlite3_recover_finish(p);
 -  return rc;
 -}
 -#endif /* SQLITE_SHELL_HAVE_RECOVER */
 -
  
 -/*
 - * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it.
 - * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE,
 - *   close db and set it to 0, and return the columns spec, to later
 - *   be sqlite3_free()'ed by the caller.
 - * The return is 0 when either:
 - *   (a) The db was not initialized and zCol==0 (There are no columns.)
 - *   (b) zCol!=0  (Column was added, db initialized as needed.)
 - * The 3rd argument, pRenamed, references an out parameter. If the
 - * pointer is non-zero, its referent will be set to a summary of renames
 - * done if renaming was necessary, or set to 0 if none was done. The out
 - * string (if any) must be sqlite3_free()'ed by the caller.
 - */
 -#ifdef SHELL_DEBUG
 -#define rc_err_oom_die(rc) \
 -  if( rc==SQLITE_NOMEM ) shell_check_oom(0); \
 -  else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \
 -    fprintf(stderr,"E:%d\n",rc), assert(0)
 -#else
 -static void rc_err_oom_die(int rc){
 -  if( rc==SQLITE_NOMEM ) shell_check_oom(0);
 -  assert(rc==SQLITE_OK||rc==SQLITE_DONE);
 -}
 -#endif
 +  if( rc==SQLITE_OK ){
-     int rc2;
 +    char *zPrev = 0;
 +    sqlite3_stmt *pExplain = 0;
 +    sstr_ptr_holder(&zPrev);
 +    stmt_ptr_holder(&pExplain);
 +    while( SQLITE_ROW==s3_step_noom(pSql) ){
 +      int res = -1;
 +      const char *zEQP = (const char*)sqlite3_column_text(pSql, 0);
 +      const char *zGlob = (const char*)sqlite3_column_text(pSql, 1);
 +      const char *zFrom = (const char*)sqlite3_column_text(pSql, 2);
 +      const char *zTarget = (const char*)sqlite3_column_text(pSql, 3);
 +      const char *zCI = (const char*)sqlite3_column_text(pSql, 4);
 +      const char *zParent = (const char*)sqlite3_column_text(pSql, 5);
  
 -#ifdef SHELL_COLFIX_DB /* If this is set, the DB can be in a file. */
 -static char zCOL_DB[] = SHELL_STRINGIFY(SHELL_COLFIX_DB);
 -#else  /* Otherwise, memory is faster/better for the transient DB. */
 -static const char *zCOL_DB = ":memory:";
 -#endif
 +      if( zEQP==0 || zGlob==0 ) continue;
 +      rc = s3_prepare_v2_noom(db, zEQP, -1, &pExplain, 0);
 +      if( rc!=SQLITE_OK ) break;
 +      if( SQLITE_ROW==s3_step_noom(pExplain) ){
 +        const char *zPlan = (const char*)sqlite3_column_text(pExplain, 3);
-         shell_check_ooms(zPlan);
 +        res = zPlan!=0 && (  0==sqlite3_strglob(zGlob, zPlan)
 +                             || 0==sqlite3_strglob(zGlobIPK, zPlan));
 +      }
 +      rc = sqlite3_finalize(pExplain);
 +      pExplain = 0;
 +      if( rc!=SQLITE_OK ) break;
  
 -/* Define character (as C string) to separate generated column ordinal
 - * from protected part of incoming column names. This defaults to "_"
 - * so that incoming column identifiers that did not need not be quoted
 - * remain usable without being quoted. It must be one character.
 - */
 -#ifndef SHELL_AUTOCOLUMN_SEP
 -# define AUTOCOLUMN_SEP "_"
 -#else
 -# define AUTOCOLUMN_SEP SHELL_STRINGIFY(SHELL_AUTOCOLUMN_SEP)
 -#endif
 +      if( res<0 ){
 +        raw_printf(STD_ERR, "Error: internal error");
 +        break;
 +      }else{
 +        if( bGroupByParent
 +        && (bVerbose || res==0)
 +        && (zPrev==0 || sqlite3_stricmp(zParent, zPrev))
 +        ){
 +          raw_printf(out, "-- Parent table %s\n", zParent);
 +          sqlite3_free(zPrev);
 +          zPrev = smprintf("%s", zParent);
 +          shell_check_ooms(zPrev);
 +        }
  
 -static char *zAutoColumn(const char *zColNew, sqlite3 **pDb, char **pzRenamed){
 -  /* Queries and D{D,M}L used here */
 -  static const char * const zTabMake = "\
 -CREATE TABLE ColNames(\
 - cpos INTEGER PRIMARY KEY,\
 - name TEXT, nlen INT, chop INT, reps INT, suff TEXT);\
 -CREATE VIEW RepeatedNames AS \
 -SELECT DISTINCT t.name FROM ColNames t \
 -WHERE t.name COLLATE NOCASE IN (\
 - SELECT o.name FROM ColNames o WHERE o.cpos<>t.cpos\
 -);\
 -";
 -  static const char * const zTabFill = "\
 -INSERT INTO ColNames(name,nlen,chop,reps,suff)\
 - VALUES(iif(length(?1)>0,?1,'?'),max(length(?1),1),0,0,'')\
 -";
 -  static const char * const zHasDupes = "\
 -SELECT count(DISTINCT (substring(name,1,nlen-chop)||suff) COLLATE NOCASE)\
 - <count(name) FROM ColNames\
 -";
 -#ifdef SHELL_COLUMN_RENAME_CLEAN
 -  static const char * const zDedoctor = "\
 -UPDATE ColNames SET chop=iif(\
 -  (substring(name,nlen,1) BETWEEN '0' AND '9')\
 -  AND (rtrim(name,'0123456790') glob '*"AUTOCOLUMN_SEP"'),\
 - nlen-length(rtrim(name, '"AUTOCOLUMN_SEP"0123456789')),\
 - 0\
 -)\
 -";
 -#endif
 -  static const char * const zSetReps = "\
 -UPDATE ColNames AS t SET reps=\
 -(SELECT count(*) FROM ColNames d \
 - WHERE substring(t.name,1,t.nlen-t.chop)=substring(d.name,1,d.nlen-d.chop)\
 - COLLATE NOCASE\
 -)\
 -";
 -#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
 -  static const char * const zColDigits = "\
 -SELECT CAST(ceil(log(count(*)+0.5)) AS INT) FROM ColNames \
 -";
 -#else
 -  /* Counting on SQLITE_MAX_COLUMN < 100,000 here. (32767 is the hard limit.) */
 -  static const char * const zColDigits = "\
 -SELECT CASE WHEN (nc < 10) THEN 1 WHEN (nc < 100) THEN 2 \
 - WHEN (nc < 1000) THEN 3 WHEN (nc < 10000) THEN 4 \
 - ELSE 5 FROM (SELECT count(*) AS nc FROM ColNames) \
 -";
 -#endif
 -  static const char * const zRenameRank =
 -#ifdef SHELL_COLUMN_RENAME_CLEAN
 -    "UPDATE ColNames AS t SET suff="
 -    "iif(reps>1, printf('%c%0*d', '"AUTOCOLUMN_SEP"', $1, cpos), '')"
 -#else /* ...RENAME_MINIMAL_ONE_PASS */
 -"WITH Lzn(nlz) AS (" /* Find minimum extraneous leading 0's for uniqueness */
 -"  SELECT 0 AS nlz"
 -"  UNION"
 -"  SELECT nlz+1 AS nlz FROM Lzn"
 -"  WHERE EXISTS("
 -"   SELECT 1"
 -"   FROM ColNames t, ColNames o"
 -"   WHERE"
 -"    iif(t.name IN (SELECT * FROM RepeatedNames),"
 -"     printf('%s"AUTOCOLUMN_SEP"%s',"
 -"      t.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,t.cpos),2)),"
 -"     t.name"
 -"    )"
 -"    ="
 -"    iif(o.name IN (SELECT * FROM RepeatedNames),"
 -"     printf('%s"AUTOCOLUMN_SEP"%s',"
 -"      o.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,o.cpos),2)),"
 -"     o.name"
 -"    )"
 -"    COLLATE NOCASE"
 -"    AND o.cpos<>t.cpos"
 -"   GROUP BY t.cpos"
 -"  )"
 -") UPDATE Colnames AS t SET"
 -" chop = 0," /* No chopping, never touch incoming names. */
 -" suff = iif(name IN (SELECT * FROM RepeatedNames),"
 -"  printf('"AUTOCOLUMN_SEP"%s', substring("
 -"   printf('%.*c%0.*d',(SELECT max(nlz) FROM Lzn)+1,'0',1,t.cpos),2)),"
 -"  ''"
 -" )"
 -#endif
 -    ;
 -  static const char * const zCollectVar = "\
 -SELECT\
 - '('||x'0a'\
 - || group_concat(\
 -  cname||' TEXT',\
 -  ','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\
 - ||')' AS ColsSpec \
 -FROM (\
 - SELECT cpos, printf('\"%w\"',printf('%!.*s%s', nlen-chop,name,suff)) AS cname \
 - FROM ColNames ORDER BY cpos\
 -)";
 -  static const char * const zRenamesDone =
 -    "SELECT group_concat("
 -    " printf('\"%w\" to \"%w\"',name,printf('%!.*s%s', nlen-chop, name, suff)),"
 -    " ','||x'0a')"
 -    "FROM ColNames WHERE suff<>'' OR chop!=0"
 -    ;
 -  int rc;
 -  sqlite3_stmt *pStmt = 0;
 -  assert(pDb!=0);
 -  if( zColNew ){
 -    /* Add initial or additional column. Init db if necessary. */
 -    if( *pDb==0 ){
 -      if( SQLITE_OK!=sqlite3_open(zCOL_DB, pDb) ) return 0;
 -#ifdef SHELL_COLFIX_DB
 -      if(*zCOL_DB!=':')
 -        sqlite3_exec(*pDb,"drop table if exists ColNames;"
 -                     "drop view if exists RepeatedNames;",0,0,0);
 -#endif
 -      rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0);
 -      rc_err_oom_die(rc);
 -    }
 -    assert(*pDb!=0);
 -    rc = sqlite3_prepare_v2(*pDb, zTabFill, -1, &pStmt, 0);
 -    rc_err_oom_die(rc);
 -    rc = sqlite3_bind_text(pStmt, 1, zColNew, -1, 0);
 -    rc_err_oom_die(rc);
 -    rc = sqlite3_step(pStmt);
 -    rc_err_oom_die(rc);
 -    sqlite3_finalize(pStmt);
 -    return 0;
 -  }else if( *pDb==0 ){
 -    return 0;
 -  }else{
 -    /* Formulate the columns spec, close the DB, zero *pDb. */
 -    char *zColsSpec = 0;
 -    int hasDupes = db_int(*pDb, zHasDupes);
 -    int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0;
 -    if( hasDupes ){
 -#ifdef SHELL_COLUMN_RENAME_CLEAN
 -      rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0);
 -      rc_err_oom_die(rc);
 -#endif
 -      rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0);
 -      rc_err_oom_die(rc);
 -      rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0);
 -      rc_err_oom_die(rc);
 -      sqlite3_bind_int(pStmt, 1, nDigits);
 -      rc = sqlite3_step(pStmt);
 -      sqlite3_finalize(pStmt);
 -      if( rc!=SQLITE_DONE ) rc_err_oom_die(SQLITE_NOMEM);
 -    }
 -    assert(db_int(*pDb, zHasDupes)==0); /* Consider: remove this */
 -    rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0);
 -    rc_err_oom_die(rc);
 -    rc = sqlite3_step(pStmt);
 -    if( rc==SQLITE_ROW ){
 -      zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 -    }else{
 -      zColsSpec = 0;
 -    }
 -    if( pzRenamed!=0 ){
 -      if( !hasDupes ) *pzRenamed = 0;
 -      else{
 -        sqlite3_finalize(pStmt);
 -        if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0)
 -            && SQLITE_ROW==sqlite3_step(pStmt) ){
 -          *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 -        }else
 -          *pzRenamed = 0;
 +        if( res==0 ){
 +          raw_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget);
 +        }else if( bVerbose ){
 +          raw_printf(out, "%s/* no extra indexes required for %s -> %s */\n",
 +                     zIndent, zFrom, zTarget);
 +        }
        }
      }
 -    sqlite3_finalize(pStmt);
 -    sqlite3_close(*pDb);
 -    *pDb = 0;
 -    return zColsSpec;
 +
 +    if( rc!=SQLITE_OK ){
 +      *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
-     }
-     if( rc==SQLITE_OK && rc2!=SQLITE_OK ){
-       rc = rc2;
-       *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
++    }else{
++      rc = sqlite3_finalize(pSql);
++      pSql = 0;
++      if( rc!=SQLITE_OK ) *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
 +    }
 +  }else{
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
    }
 -}
 +  RESOURCE_FREE(mark);
  
 -/*
 -** If an input line begins with "." then invoke this routine to
 -** process that line.
 -**
 -** Return 1 on error, 2 to exit, and 0 otherwise.
 -*/
 -static int do_meta_command(char *zLine, ShellState *p){
 -  int h = 1;
 -  int nArg = 0;
 -  int n, c;
 -  int rc = 0;
 -  char *azArg[52];
 +  return DCR_Ok|(rc!=0);
 +}
  
 -#ifndef SQLITE_OMIT_VIRTUALTABLE
 -  if( p->expert.pExpert ){
 -    expertFinish(p, 1, 0);
 +DISPATCHABLE_COMMAND( load ? 2 3 ){
 +  const char *zFile = 0, *zProc = 0;
 +  int ai = 1, rc;
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +  if( nArg<2 || azArg[1][0]==0 ){
 +    /* Must have a non-empty FILE. (Will not load self.) */
 +    return DCR_SayUsage;
    }
 +  while( ai<nArg ){
 +    const char *zA = azArg[ai++];
 +    if( zFile==0 ) zFile = zA;
 +    else if( zProc==0 ) zProc = zA;
 +    else return DCR_TooMany|ai;
 +  }
 +  open_db(p, 0);
 +  rc = sqlite3_load_extension(DBX(p), zFile, zProc, pzErr);
 +  return DCR_Ok|(rc!=SQLITE_OK);
 +}
 +
 +DISPATCHABLE_COMMAND( log ? 2 2 ){
 +  const char *zFile = azArg[1];
 +  int bOn = cli_strcmp(zFile,"on")==0;
 +  int bOff = cli_strcmp(zFile,"off")==0;
 +#if defined(SQLITE_SHELL_FIDDLE)
 +  if( !bOn && !bOff ) return DCR_SayUsage;
 +#else
 +  if( ISS(p)->bSafeMode && !bOn && !bOff ) return DCR_AbortError;
  #endif
 +  output_file_close(ISS(p)->pLog);
 +  if( bOff ){
 +    ISS(p)->pLog = 0;
 +    return DCR_Ok;
 +  }
 +  if( bOn ) zFile = "stdout";
 +  ISS(p)->pLog = output_file_open(zFile, 0);
 +  return DCR_Ok|(ISS(p)->pLog==0);
 +}
  
 -  /* Parse the input line into tokens.
 -  */
 -  while( zLine[h] && nArg<ArraySize(azArg)-1 ){
 -    while( IsSpace(zLine[h]) ){ h++; }
 -    if( zLine[h]==0 ) break;
 -    if( zLine[h]=='\'' || zLine[h]=='"' ){
 -      int delim = zLine[h++];
 -      azArg[nArg++] = &zLine[h];
 -      while( zLine[h] && zLine[h]!=delim ){
 -        if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++;
 -        h++;
 -      }
 -      if( zLine[h]==delim ){
 -        zLine[h++] = 0;
 -      }
 -      if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
 -    }else{
 -      azArg[nArg++] = &zLine[h];
 -      while( zLine[h] && !IsSpace(zLine[h]) ){ h++; }
 -      if( zLine[h] ) zLine[h++] = 0;
 -      resolve_backslashes(azArg[nArg-1]);
 +static void effectMode(ShellInState *psi, u8 modeRequest, u8 modeNominal){
 +  /* Effect the specified mode change. */
 +  const char *zColSep = 0, *zRowSep = 0;
 +  assert(modeNominal!=MODE_COUNT_OF);
 +  switch( modeRequest ){
 +  case MODE_Line:
 +    zRowSep = SEP_Row;
 +    break;
 +  case MODE_Column:
 +    if( (psi->shellFlgs & SHFLG_HeaderSet)==0 ){
 +      psi->showHeader = 1;
      }
 +    zRowSep = SEP_Row;
 +    break;
 +  case MODE_List:
 +    zColSep = SEP_Column;
 +    zRowSep = SEP_Row;
 +    break;
 +  case MODE_Html:
 +    break;
 +  case MODE_Tcl:
 +    zColSep = SEP_Space;
 +    zRowSep = SEP_Row;
 +    break;
 +  case MODE_Csv:
 +    zColSep = SEP_Comma;
 +    zRowSep = SEP_CrLf;
 +    break;
 +  case MODE_Tab:
 +    zColSep = SEP_Tab;
 +    break;
 +  case MODE_Insert:
 +    break;
 +  case MODE_Quote:
 +    zColSep = SEP_Comma;
 +    zRowSep = SEP_Row;
 +    break;
 +  case MODE_Ascii:
 +    zColSep = SEP_Unit;
 +    zRowSep = SEP_Record;
 +    break;
 +  case MODE_Markdown:
 +    /* fall-thru */
 +  case MODE_Table:
 +    /* fall-thru */
 +  case MODE_Box:
 +    break;
 +  case MODE_Count:
 +    /* fall-thru */
 +  case MODE_Off:
 +    /* fall-thru */
 +  case MODE_Json:
 +    break;
 +  case MODE_Explain: case MODE_Semi: case MODE_EQP: case MODE_Pretty: default:
 +    /* Modes used internally, not settable by .mode command. */
 +    return;
    }
 -  azArg[nArg] = 0;
 -
 -  /* Process the input line.
 -  */
 -  if( nArg==0 ) return 0; /* no tokens, no error */
 -  n = strlen30(azArg[0]);
 -  c = azArg[0][0];
 -  clearTempFile(p);
 +  if( zRowSep!=0 ){
 +    sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, zRowSep);
 +  }
 +  if( zColSep!=0 ){
 +    sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator, zColSep);
 +  }
 +  psi->mode = modeNominal;
 +}
  
 -#ifndef SQLITE_OMIT_AUTHORIZATION
 -  if( c=='a' && cli_strncmp(azArg[0], "auth", n)==0 ){
 -    if( nArg!=2 ){
 -      raw_printf(stderr, "Usage: .auth ON|OFF\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    open_db(p, 0);
 -    if( booleanValue(azArg[1]) ){
 -      sqlite3_set_authorizer(p->db, shellAuth, p);
 -    }else if( p->bSafeModePersist ){
 -      sqlite3_set_authorizer(p->db, safeModeAuth, p);
 +/*****************
 + * The .mode command
 + */
 +COLLECT_HELP_TEXT[
 +  ".mode MODE ?OPTIONS?     Set output mode",
 +  "   MODE is one of:",
 +  "     ascii       Columns/rows delimited by 0x1F and 0x1E",
 +  "     box         Tables using unicode box-drawing characters",
 +  "     csv         Comma-separated values",
 +  "     column      Output in columns.  (See .width)",
 +  "     html        HTML <table> code",
 +  "     insert      SQL insert statements for TABLE",
 +  "     json        Results in a JSON array",
 +  "     line        One value per line",
 +  "     list        Values delimited by \"|\"",
 +  "     markdown    Markdown table format",
 +  "     qbox        Shorthand for \"box --wrap 60 --quote\"",
 +  "     quote       Escape answers as for SQL",
 +  "     table       ASCII-art table",
 +  "     tabs        Tab-separated values",
 +  "     tcl         TCL list elements",
 +  "   OPTIONS: (for columnar modes or insert mode):",
 +  "     --wrap N       Wrap output lines to no longer than N characters",
 +  "     --wordwrap B   Wrap or not at word boundaries per B (on/off)",
 +  "     --ww           Shorthand for \"--wordwrap 1\"",
 +  "     --quote        Quote output text as SQL literals",
 +  "     --noquote      Do not quote output text",
 +  "     TABLE          The name of SQL table used for \"insert\" mode",
 +];
 +DISPATCHABLE_COMMAND( mode ? 1 0 ){
 +  ShellInState *psi = ISS(p);
 +  const char *zTabname = 0;
 +  const char *zArg;
 +  int i, aix;
 +  u8 foundMode = MODE_COUNT_OF, setMode = MODE_COUNT_OF;
 +  ColModeOpts cmOpts = ColModeOpts_default;
 +  for(aix=1; aix<nArg; aix++){
 +    zArg = azArg[aix];
 +    if( optionMatch(zArg,"wrap") && aix+1<nArg ){
 +      cmOpts.iWrap = integerValue(azArg[++aix]);
 +    }else if( optionMatch(zArg,"ww") ){
 +      cmOpts.bWordWrap = 1;
 +    }else if( optionMatch(zArg,"wordwrap") && aix+1<nArg ){
 +      cmOpts.bWordWrap = (u8)booleanValue(azArg[++aix]);
 +    }else if( optionMatch(zArg,"quote") ){
 +      cmOpts.bQuote = 1;
 +    }else if( optionMatch(zArg,"noquote") ){
 +      cmOpts.bQuote = 0;
 +    }else{
 +      /* Not a known option. Check for known mode, or possibly a table name. */
 +      if( foundMode==MODE_Insert && zTabname==0 ){
 +        zTabname = zArg;
 +      }else if( *zArg=='-' ){
 +        goto flag_unknown;
 +      }else{
 +        int im, nza = strlen30(zArg);
 +        if( foundMode!=MODE_COUNT_OF ) goto mode_badarg;
 +        for( im=0; im<MODE_COUNT_OF; ++im ){
 +          if( modeDescr[im].bUserBlocked ) continue;
 +          if( cli_strncmp(zArg,modeDescr[im].zModeName,nza)==0 ){
 +            foundMode = (u8)im;
 +            setMode = modeDescr[im].iAliasFor;
 +            break;
 +          }
 +        }
 +        if( cli_strcmp(zArg, "qbox")==0 ){
 +          ColModeOpts cmo = ColModeOpts_default_qbox;
 +          foundMode = setMode = MODE_Box;
 +          cmOpts = cmo;
 +        }else if( im==MODE_COUNT_OF ) goto mode_unknown;
 +      }
 +    }
 +  } /* Arg loop */
 +  if( foundMode==MODE_COUNT_OF ){
 +    FILE *out = psi->out;
 +    const char *zMode;
 +    int nms;
 +    i = psi->mode;
 +    assert(i>=0 && i<MODE_COUNT_OF);
 +    zMode = modeDescr[i].zModeName;
 +    nms = strlen30(zMode)-modeDescr[i].bDepluralize;
 +    /* Mode not specified. Show present mode (and toss any options set.) */
 +    if( MODE_IS_COLUMNAR(psi->mode) ){
 +      raw_printf
 +        (out, "current output mode: %.*s --wrap %d --wordwrap %s --%squote\n",
 +         nms, zMode, psi->cmOpts.iWrap,
 +         psi->cmOpts.bWordWrap ? "on" : "off",
 +         psi->cmOpts.bQuote ? "" : "no");
      }else{
 -      sqlite3_set_authorizer(p->db, 0, 0);
 +      raw_printf(out, "current output mode: %.*s\n", nms, zMode);
      }
 -  }else
 +  }else{
 +    effectMode(psi, foundMode, setMode);
 +    if( MODE_IS_COLUMNAR(setMode) ){
 +      psi->cmOpts = cmOpts;
 +#if SHELL_DATAIO_EXT
 +      psi->pActiveExporter = psi->pColumnarExporter;
  #endif
 -
 -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) \
 -  && !defined(SQLITE_SHELL_FIDDLE)
 -  if( c=='a' && cli_strncmp(azArg[0], "archive", n)==0 ){
 -    open_db(p, 0);
 -    failIfSafeMode(p, "cannot run .archive in safe mode");
 -    rc = arDotCommand(p, 0, azArg, nArg);
 -  }else
 +    }else{
 +#if SHELL_DATAIO_EXT
 +      psi->pActiveExporter = psi->pFreeformExporter;
  #endif
 +      if( setMode==MODE_Insert ){
 +        set_table_name(p, zTabname ? zTabname : "table");
 +      }
 +    }
 +  }
 +  psi->cMode = psi->mode;
 +  return DCR_Ok;
 + flag_unknown:
 +  *pzErr = smprintf("Unknown .mode option: %s\nValid options:\n%s",
 +                    zArg,
 +                    "  --noquote\n"
 +                    "  --quote\n"
 +                    "  --wordwrap on/off\n"
 +                    "  --wrap N\n"
 +                    "  --ww\n");
 +  return DCR_Unknown|aix;
 + mode_unknown:
 +  *pzErr = smprintf("Mode should be one of:\n"
 +                    "  ascii box column csv html insert json line\n"
 +                    "  list markdown qbox quote table tabs tcl\n");
 +  return DCR_Unknown|aix;
 + mode_badarg:
 +  *pzErr = smprintf("Invalid .mode argument: %s\n", zArg);
 +  return DCR_ArgWrong|aix;
 +}
 +
 +/*****************
 + * The .oomfake command
 + */
 +CONDITION_COMMAND(oomfake defined(SQLITE_DEBUG));
 +COLLECT_HELP_TEXT[
 +  ",oomfake [how_soon]      Set how soon or whether to simulate OOM condition",
 +];
 +DISPATCHABLE_COMMAND( oomfake ? 1 2 azArg nArg p ){
 +  if( nArg>1 ){
 +    int oomf = (int)integerValue(azArg[1]);
 +    fake_oom_countdown = oomf;
 +  }
 +  else raw_printf(ISS(p)->out, "OOM sim in %d allocations\n",
 +                  fake_oom_countdown);
 +  return DCR_Ok;
 +}
  
 +/* Note that .open is (partially) available in WASM builds but is
 +** currently only intended to be used by the fiddle tool, not
 +** end users, so is "undocumented." */
 +#ifdef SQLITE_SHELL_FIDDLE
 +# define HOPEN ",open"
 +#else
 +# define HOPEN ".open"
 +#endif
 +/*****************
 + * The .nonce, .nullvalue and .open commands
 + */
 +CONDITION_COMMAND(nonce !defined(SQLITE_SHELL_FIDDLE));
 +COLLECT_HELP_TEXT[
 +  ".open ?OPTIONS? ?FILE?   Close existing database and reopen FILE",
 +  "     Options:",
 +  "        --append        Use appendvfs to append database to the end of FILE",
 +#ifndef SQLITE_OMIT_DESERIALIZE
 +  "        --deserialize   Load into memory using sqlite3_deserialize()",
 +  "        --hexdb         Load the output of \"dbtotxt\" as an in-memory db",
 +  "        --maxsize N     Maximum size for --hexdb or --deserialized database",
 +#endif
 +  "        --new           Initialize FILE to an empty database",
 +  "        --nofollow      Do not follow symbolic links",
 +  "        --readonly      Open FILE readonly",
 +  "        --zip           FILE is a ZIP archive",
 +  ".nonce STRING            Suspend safe mode for one command if nonce matches",
 +  ".nullvalue STRING        Use STRING in place of NULL values",
 +];
 +DISPATCHABLE_COMMAND( open 3 1 0 ){
 +  ShellInState *psi = ISS(p);
 +  const char *zFN = 0;     /* Pointer to constant filename */
 +  char *zNewFilename = 0;  /* Name of the database file to open */
 +  int iName = 1;           /* Index in azArg[] of the filename */
 +  int newFlag = 0;         /* True to delete file before opening */
 +  u8 openMode = SHELL_OPEN_UNSPEC;
 +  int openFlags = 0;
 +  sqlite3_int64 szMax = 0;
 +  int rc = 0;
 +  /* Check for command-line arguments */
 +  for(iName=1; iName<nArg; iName++){
 +    const char *z = azArg[iName];
  #ifndef SQLITE_SHELL_FIDDLE
 -  if( (c=='b' && n>=3 && cli_strncmp(azArg[0], "backup", n)==0)
 -   || (c=='s' && n>=3 && cli_strncmp(azArg[0], "save", n)==0)
 -  ){
 -    const char *zDestFile = 0;
 -    const char *zDb = 0;
 -    sqlite3 *pDest;
 -    sqlite3_backup *pBackup;
 -    int j;
 -    int bAsync = 0;
 -    const char *zVfs = 0;
 -    failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
 -    for(j=1; j<nArg; j++){
 -      const char *z = azArg[j];
 -      if( z[0]=='-' ){
 -        if( z[1]=='-' ) z++;
 -        if( cli_strcmp(z, "-append")==0 ){
 -          zVfs = "apndvfs";
 -        }else
 -        if( cli_strcmp(z, "-async")==0 ){
 -          bAsync = 1;
 -        }else
 -        {
 -          utf8_printf(stderr, "unknown option: %s\n", azArg[j]);
 -          return 1;
 -        }
 -      }else if( zDestFile==0 ){
 -        zDestFile = azArg[j];
 -      }else if( zDb==0 ){
 -        zDb = zDestFile;
 -        zDestFile = azArg[j];
 -      }else{
 -        raw_printf(stderr, "Usage: .backup ?DB? ?OPTIONS? FILENAME\n");
 -        return 1;
 -      }
 -    }
 -    if( zDestFile==0 ){
 -      raw_printf(stderr, "missing FILENAME argument on .backup\n");
 -      return 1;
 -    }
 -    if( zDb==0 ) zDb = "main";
 -    rc = sqlite3_open_v2(zDestFile, &pDest,
 -                  SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs);
 -    if( rc!=SQLITE_OK ){
 -      utf8_printf(stderr, "Error: cannot open \"%s\"\n", zDestFile);
 -      close_db(pDest);
 -      return 1;
 -    }
 -    if( bAsync ){
 -      sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
 -                   0, 0, 0);
 +    if( optionMatch(z,"new") ){
 +      newFlag = 1;
 +# ifdef SQLITE_HAVE_ZLIB
 +    }else if( optionMatch(z, "zip") ){
 +      openMode = SHELL_OPEN_ZIPFILE;
 +# endif
 +    }else if( optionMatch(z, "append") ){
 +      openMode = SHELL_OPEN_APPENDVFS;
 +    }else if( optionMatch(z, "readonly") ){
 +      openMode = SHELL_OPEN_READONLY;
 +    }else if( optionMatch(z, "nofollow") ){
 +      openFlags |= SQLITE_OPEN_NOFOLLOW;
 +# ifndef SQLITE_OMIT_DESERIALIZE
 +    }else if( optionMatch(z, "deserialize") ){
 +      openMode = SHELL_OPEN_DESERIALIZE;
 +    }else if( optionMatch(z, "hexdb") ){
 +      openMode = SHELL_OPEN_HEXDB;
 +    }else if( optionMatch(z, "maxsize") && iName+1<nArg ){
 +      szMax = integerValue(azArg[++iName]);
 +# endif /* SQLITE_OMIT_DESERIALIZE */
 +    }else
 +#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 +    if( z[0]=='-' ){
 +      return DCR_Unknown|iName;
 +    }else if( zFN ){
 +      *pzErr = smprintf("extra argument: \"%s\"\n", z);
 +      return DCR_TooMany;
 +    }else{
 +      zFN = z;
      }
 -    open_db(p, 0);
 -    pBackup = sqlite3_backup_init(pDest, "main", p->db, zDb);
 -    if( pBackup==0 ){
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
 -      close_db(pDest);
 -      return 1;
 +  }
 +
 +  /* Close the existing database */
 +  session_close_all(psi, -1);
 +#if SHELL_DYNAMIC_EXTENSION
 +  if( DBX(p)!=0 ) notify_subscribers(psi, NK_DbAboutToClose, DBX(p));
 +#endif
 +  close_db(DBX(p));
 +  DBX(p) = 0;
 +  psi->pAuxDb->zDbFilename = 0;
 +  sqlite3_free(psi->pAuxDb->zFreeOnClose);
 +  psi->pAuxDb->zFreeOnClose = 0;
 +  psi->openMode = openMode;
 +  psi->openFlags = 0;
 +  psi->szMax = 0;
 +
 +  /* If a filename is specified, try to open it first */
 +  if( zFN || psi->openMode==SHELL_OPEN_HEXDB ){
 +    if( newFlag && zFN && !psi->bSafeMode ) shellDeleteFile(zFN);
 +#ifndef SQLITE_SHELL_FIDDLE
 +    if( psi->bSafeMode
 +        && psi->openMode!=SHELL_OPEN_HEXDB
 +        && zFN
 +        && cli_strcmp(zFN,":memory:")!=0
 +        ){
 +      *pzErr = smprintf("cannot open database files in safe mode");
 +      return DCR_AbortError;
      }
 -    while(  (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
 -    sqlite3_backup_finish(pBackup);
 -    if( rc==SQLITE_DONE ){
 -      rc = 0;
 +#else
 +      /* WASM mode has its own sandboxed pseudo-filesystem. */
 +#endif
 +    if( zFN ){
 +      zNewFilename = smprintf("%s", zFN);
 +      shell_check_ooms(zNewFilename);
      }else{
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
 +      zNewFilename = 0;
 +    }
 +    psi->pAuxDb->zDbFilename = zNewFilename;
 +    psi->openFlags = openFlags;
 +    psi->szMax = szMax;
 +    open_db(p, OPEN_DB_KEEPALIVE);
 +    if( DBX(p)==0 ){
 +      *pzErr = smprintf("cannot open '%z'\n", zNewFilename);
        rc = 1;
 +    }else{
 +      psi->pAuxDb->zFreeOnClose = zNewFilename;
      }
 -    close_db(pDest);
 -  }else
 -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 +  }
 +  if( DBX(p)==0 ){
 +    /* As a fall-back open a TEMP database */
 +    psi->pAuxDb->zDbFilename = 0;
 +    open_db(p, 0);
 +  }
 +  return DCR_Ok|(rc!=0);
 +}
  
 -  if( c=='b' && n>=3 && cli_strncmp(azArg[0], "bail", n)==0 ){
 -    if( nArg==2 ){
 -      bail_on_error = booleanValue(azArg[1]);
 -    }else{
 -      raw_printf(stderr, "Usage: .bail on|off\n");
 +DISPATCHABLE_COMMAND( nonce ? 2 2 ){
 +  ShellInState *psi = ISS(p);
 +  if( psi->zNonce==0 || cli_strcmp(azArg[1],psi->zNonce)!=0 ){
 +    raw_printf(STD_ERR, "line %d: incorrect nonce: \"%s\"\n",
 +               psi->pInSource->lineno, azArg[1]);
 +    p->shellAbruptExit = 0x102;
 +    return DCR_Abort;
 +  }
 +  /* Suspend safe mode for 1 dot-command after this. */
 +  psi->bSafeModeFuture = 2;
 +  return DCR_Ok;
 +}
 +
 +DISPATCHABLE_COMMAND( nullvalue ? 2 2 ){
 +  sqlite3_snprintf(sizeof(ISS(p)->nullValue), ISS(p)->nullValue, "%.*s",
 +                   (int)ArraySize(ISS(p)->nullValue)-1, azArg[1]);
 +  return DCR_Ok;
 +}
 +
 +/* Helper functions for .parameter and .vars commands
 + */
 +
 +struct keyval_row { char * value; int uses; int hits; };
 +
 +static int kv_find_callback(void *pData, int nc, char **pV, char **pC){
 +  assert(nc>=1);
 +  assert(cli_strcmp(pC[0],"value")==0);
 +  struct keyval_row *pParam = (struct keyval_row *)pData;
 +  assert(pParam->value==0); /* key values are supposedly unique. */
 +  if( pParam->value!=0 ) sqlite3_free( pParam->value );
 +  pParam->value = smprintf("%s", pV[0]); /* source owned by statement */
 +  if( nc>1 ) pParam->uses = (int)integerValue(pV[1]);
 +  ++pParam->hits;
 +  return 0;
 +}
 +
 +static void append_in_clause(sqlite3_str *pStr,
 +                             const char **azBeg, const char **azLim);
 +static void append_glob_terms(sqlite3_str *pStr, const char *zColName,
 +                              const char **azBeg, const char **azLim);
 +static char *find_home_dir(int clearFlag);
 +
 +/* Create a home-relative pathname from ~ prefixed path.
 + * Return it, or 0 for any error.
 + * Caller must sqlite3_free() it.
 + */
 +static char *home_based_path( const char *zPath ){
 +  char *zHome = find_home_dir(0);
 +  char *zErr = 0;
 +  assert( zPath[0]=='~' );
 +  if( zHome==0 ){
 +    zErr = "Cannot find home directory.";
 +  }else if( zPath[0]==0 || (zPath[1]!='/'
 +#if defined(_WIN32) || defined(WIN32)
 +                            && zPath[1]!='\\'
 +#endif
 +                            ) ){
 +    zErr = "Malformed pathname";
 +  }else{
 +    return smprintf("%s%s", zHome, zPath+1);
 +  }
 +  utf8_printf(STD_ERR, "Error: %s\n", zErr);
 +  return 0;
 +}
 +
 +/* Transfer selected parameters between two parameter tables, for save/load.
 + * Argument bSaveNotLoad determines transfer direction and other actions.
 + * If it is true, the store DB will be created if not existent, and its
 + * table for keeping parameters will be created. Or failure is returned.
 + * If it is false, the store DB will be opened for read and its presumed
 + * table for keeping parameters will be read. Or failure is returned.
 + *
 + * Arguments azNames and nNames reference the ?NAMES? save/load arguments.
 + * If it is an empty list, all parameters will be saved or loaded.
 + * Otherwise, only the named parameters are transferred, if they exist.
 + * It is not an error to specify a name that cannot be transferred
 + * because it does not exist in the source table.
 + *
 + * Returns are SQLITE_OK for success, or other codes for failure.
 + */
 +static int kv_xfr_table(sqlite3 *db, const char *zStoreDbName,
 +                        int bSaveNotLoad, ParamTableUse ptu,
 +                        const char *azNames[], int nNames){
 +  char *zSql = 0; /* to be sqlite3_free()'ed */
 +  sqlite3_str *sbCopy = 0;
 +  sqlite3 *dbStore = 0;
 +  const char *zHere = 0;
 +  const char *zThere = SH_KV_STORE_SNAME;
 +  const char *zTo;
 +  const char *zFrom;
 +  int rc = 0;
 +  int openFlags = (bSaveNotLoad)
 +    ? SQLITE_OPEN_URI|SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE
 +    : SQLITE_OPEN_READONLY;
 +
 +  switch( ptu ){
 +  case PTU_Binding: zHere = PARAM_TABLE_SNAME; break;
 +  case PTU_Script:  zHere = SHVAR_TABLE_SNAME; break;
 +  default: assert(0); return 1;
 +  }
 +  zTo = (bSaveNotLoad)? zThere : zHere;
 +  zFrom = (bSaveNotLoad)? zHere : zThere;
 +  /* Ensure store DB can be opened and/or created appropriately. */
 +  rc = shell_check_nomem(sqlite3_open_v2(zStoreDbName,&dbStore,openFlags,0));
 +  if( rc!=SQLITE_OK ){
 +    utf8_printf(STD_ERR, "Error: Cannot %s key/value store DB %s\n",
 +                bSaveNotLoad? "open/create" : "read", zStoreDbName);
 +    return rc;
 +  }
 +  /* Ensure it has the kv store table, or handle its absence. */
 +  assert(dbStore!=0);
 +  conn_ptr_holder(&dbStore);
 +  if( sqlite3_table_column_metadata
 +      (dbStore, "main", SH_KV_STORE_NAME, 0, 0, 0, 0, 0, 0)!=SQLITE_OK ){
 +    if( !bSaveNotLoad ){
 +      utf8_printf(STD_ERR, "Error: No key/value pairs ever stored in DB %s\n",
 +                  zStoreDbName);
        rc = 1;
 +    }else{
 +      /* The saved parameters table is not there yet; create it. */
 +      const char *zCT =
 +        "CREATE TABLE IF NOT EXISTS "SH_KV_STORE_NAME"(\n"
 +        "  key TEXT PRIMARY KEY,\n"
 +        "  value,\n"
 +        "  uses INT\n"
 +        ") WITHOUT ROWID;";
 +      rc = s3_exec_noom(dbStore, zCT, 0, 0, 0);
 +      if( rc!=SQLITE_OK ){
 +        utf8_printf(STD_ERR, "Cannot create table %s. Nothing saved.", zThere);
 +      }
      }
 -  }else
 +  }
 +  release_holder();
 +  assert(dbStore==0);
 +  if( rc!=0 ) return rc;
  
 -  if( c=='b' && n>=3 && cli_strncmp(azArg[0], "binary", n)==0 ){
 -    if( nArg==2 ){
 -      if( booleanValue(azArg[1]) ){
 -        setBinaryMode(p->out, 1);
 -      }else{
 -        setTextMode(p->out, 1);
 -      }
 +  zSql = smprintf("ATTACH %Q AS %s;", zStoreDbName, SH_KV_STORE_SCHEMA);
 +  shell_check_ooms(zSql);
 +  sstr_ptr_holder(&zSql);
 +  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 +  release_holder();
 +  if( rc!=SQLITE_OK ) return rc;
 +
 +  sbCopy = sqlite3_str_new(db);
 +  sqst_ptr_holder(&sbCopy);
 +  sqlite3_str_appendf
 +      (sbCopy, "INSERT OR REPLACE INTO %s(key,value,uses)"
 +       "SELECT key, value, uses FROM %s WHERE key ", zTo, zFrom);
 +  append_in_clause(sbCopy, azNames, azNames+nNames);
 +  zSql = sqlite3_str_finish(sbCopy);
 +  drop_holder();
 +  shell_check_ooms(zSql);
 +  sstr_ptr_holder(&zSql);
 +  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 +  release_holder();
 +
 +  s3_exec_noom(db, "DETACH "SH_KV_STORE_SCHEMA";", 0, 0, 0);
 +  return rc;
 +}
 +
 +/* Default locations of kv store DBs for .parameters and .vars save/load. */
 +static const char *zDefaultParamStore = "~/sqlite_params.sdb";
 +static const char *zDefaultVarStore = "~/sqlite_vars.sdb";
 +
 +/* Possibly generate a derived path from input spec, with defaulting
 + * and conversion of leading (or only) tilde as home directory.
 + * The above-set default is used for zSpec NULL, "" or "~".
 + * When return is 0, there is an error; what needs doing cannnot be done.
 + * The return must eventually be sqlite3_free()'ed.
 + */
 +static char *kv_store_path(const char *zSpec, ParamTableUse ptu){
 +  if( zSpec==0 || zSpec[0]==0 || cli_strcmp(zSpec,"~")==0 ){
 +    const char *zDef;
 +    switch( ptu ){
 +    case PTU_Binding: zDef = zDefaultParamStore; break;
 +    case PTU_Script:  zDef = zDefaultVarStore; break;
 +    default: return 0;
 +    }
 +    return home_based_path(zDef);
 +  }else if ( zSpec[0]=='~' ){
 +    return home_based_path(zSpec);
 +  }
 +  return smprintf("%s", zSpec);
 +}
 +
 +/* Load some or all kv pairs. Arguments are "load FILE ?NAMES?". */
 +static int kv_pairs_load(sqlite3 *db, ParamTableUse ptu,
 +                         const char *azArg[], int nArg){
 +  char *zStore = kv_store_path((nArg>1)? azArg[1] : 0, ptu);
 +  if( zStore==0 ){
 +    utf8_printf(STD_ERR, "Cannot form parameter load path. Nothing loaded.\n");
 +    return DCR_Error;
 +  }else{
 +    const char **pzFirst = (nArg>2)? azArg+2 : 0;
 +    int nNames = (nArg>2)? nArg-2 : 0;
 +    int rc;
 +    sstr_holder(zStore);
 +    rc = kv_xfr_table(db, zStore, 0, ptu, pzFirst, nNames);
 +    release_holder();
 +    return rc;
 +  }
 +}
 +
 +/* Save some or all parameters. Arguments are "save FILE ?NAMES?". */
 +static int kv_pairs_save(sqlite3 *db, ParamTableUse ptu,
 +                         const char *azArg[], int nArg){
 +  char *zStore = kv_store_path((nArg>1)? azArg[1] : 0, ptu);
 +  if( zStore==0 ){
 +    utf8_printf(STD_ERR, "Cannot form parameter save path. Nothing saved.\n");
 +    return DCR_Error;
 +  }else{
 +    const char **pzFirst = (nArg>2)? azArg+2 : 0;
 +    int nNames = (nArg>2)? nArg-2 : 0;
 +    int rc;
 +    sstr_holder(zStore);
 +    rc = kv_xfr_table(db, zStore, 1, ptu, pzFirst, nNames);
 +    release_holder();
 +    return rc;
 +  }
 +}
 +
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +/*
 + * Edit one named value in the parameters or shell variables table.
 + * If it does not yet exist, create it. If eval is true, the value
 + * is treated as a bare expression, otherwise it is a text value.
 + * The "uses" argument sets the 3rd column in the selected table,
 + * and serves to select which of the two tables is modified.
 + */
 +static int edit_one_kvalue(sqlite3 *db, char *name, int eval,
 +                           ParamTableUse uses, const char * zEditor){
 +  struct keyval_row kvRow = {0,0,0};
 +  int rc;
 +  char * zVal = 0;
 +  const char *zTab = 0;
 +  char * zSql = 0;
 +
 +  switch( uses ){
 +  case PTU_Script:  zTab = SHVAR_TABLE_SNAME; break;
 +  case PTU_Binding: zTab = PARAM_TABLE_SNAME; break;
 +  default: assert(0);
 +  }
 +  zSql = smprintf("SELECT value, uses FROM %s "
 +                  "WHERE key=%Q AND uses=%d", zTab, name, uses);
 +  shell_check_ooms(zSql);
 +  sstr_ptr_holder(&kvRow.value);
 +  sstr_holder(zSql);
 +  s3_exec_noom(db, zSql, kv_find_callback, &kvRow, 0);
 +  release_holder();
 +  assert(kvRow.hits<2);
 +  if( kvRow.hits==1 && kvRow.uses==uses){
 +    /* Editing an existing value of same kind. */
 +    release_holder(); /* kvRow.value */
 +    if( eval!=0 ){
 +      zSql = smprintf("SELECT edit(value, %Q) FROM %s "
 +                      "WHERE key=%Q AND uses=%d", zEditor, zTab, name, uses);
 +      shell_check_ooms(zSql);
 +      sstr_holder(zSql);
 +      zVal = db_text(db, zSql, 1);
 +      release_holder();
 +      sqlite3_free(zSql);
 +      zSql = smprintf("UPDATE %s SET value=(SELECT %s) "
 +                      "WHERE key=%Q AND uses=%d", zTab, zVal, name, uses);
      }else{
 -      raw_printf(stderr, "Usage: .binary on|off\n");
 -      rc = 1;
 +      zSql = smprintf("UPDATE %s SET value=edit(value, %Q) WHERE"
 +                      " key=%Q AND uses=%d", zTab, zEditor, name, uses);
      }
 -  }else
 +  }else{
 +    /* Editing a new value of same kind. */
 +    assert(kvRow.value==0 || kvRow.uses!=uses);
 +    drop_holder(); /* kvRow.value */
 +    if( eval!=0 ){
 +      zSql = smprintf("SELECT edit('-- %q%s', %Q)", name, "\n", zEditor);
 +      sstr_holder(zSql);
 +      zVal = db_text(db, zSql, 1);
 +      release_holder();
 +      zSql = smprintf("INSERT INTO %s(key,value,uses)"
 +         " VALUES (%Q,(SELECT %s LIMIT 1),%d)",
 +         zTab, name, zVal, uses);
 +    }else{
 +      zSql = smprintf("INSERT INTO %s(key,value,uses)"
 +                      " VALUES (%Q,edit('-- %q;%s', %Q),%d)",
 +                      zTab, name, name, "\n", zEditor, uses);
 +    }
 +  }
 +  shell_check_ooms(zSql);
 +  sstr_holder(zSql);
 +  sstr_holder(zVal);
 +  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 +  release_holders(2);
 +  return rc!=SQLITE_OK;
 +}
 +#endif
  
 -  /* The undocumented ".breakpoint" command causes a call to the no-op
 -  ** routine named test_breakpoint().
 -  */
 -  if( c=='b' && n>=3 && cli_strncmp(azArg[0], "breakpoint", n)==0 ){
 -    test_breakpoint();
 -  }else
 +/* Space-join values in an argument list. *valLim is not included. */
 +char *values_join( char **valBeg, char **valLim ){
 +  char *z = 0;
 +  const char *zSep = 0;
 +  while( valBeg < valLim ){
 +    z = smprintf("%z%s%s", z, zSep, *valBeg);
 +    zSep = " ";
 +    ++valBeg;
 +  }
 +  return z;
 +}
  
 -#ifndef SQLITE_SHELL_FIDDLE
 -  if( c=='c' && cli_strcmp(azArg[0],"cd")==0 ){
 -    failIfSafeMode(p, "cannot run .cd in safe mode");
 -    if( nArg==2 ){
 -#if defined(_WIN32) || defined(WIN32)
 -      wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
 -      rc = !SetCurrentDirectoryW(z);
 -      sqlite3_free(z);
 -#else
 -      rc = chdir(azArg[1]);
 -#endif
 -      if( rc ){
 -        utf8_printf(stderr, "Cannot change to directory \"%s\"\n", azArg[1]);
 -        rc = 1;
 +static struct ParamSetOpts {
 +  const char cCast;
 +  const char *zTypename;
 +  int evalKind;
 +} param_set_opts[] = {
 +  /* { 'q', 0, 2 }, */
 +  /* { 'x', 0, 1 }, */
 +  { 'i', "INT", 1 },
 +  { 'r', "REAL", 1 },
 +  { 'b', "BLOB", 1 },
 +  { 't', "TEXT", 0 },
 +  { 'n', "NUMERIC", 1 }
 +};
 +
 +/* Return an option character if it is single and prefixed by - or --,
 + * else return 0.
 + */
 +static char option_char(char *zArg){
 +  if( zArg[0]=='-' ){
 +    ++zArg;
 +    if( zArg[0]=='-' ) ++zArg;
 +    if( zArg[0]!=0 && zArg[1]==0 ) return zArg[0];
 +  }
 +  return 0;
 +}
 +
 +/* Most of .vars set
 + * Return SQLITE_OK on success, else SQLITE_ERROR.
 + */
 +static int shvar_set(sqlite3 *db, char *name, char **valBeg, char **valLim){
 +  int rc = SQLITE_OK;
 +  char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0;
 +  sqlite3_stmt *pStmtSet = 0;
 +  char *zValue = (zValGlom==0)? *valBeg : zValGlom;
 +  char *zSql
 +    = smprintf("REPLACE INTO "SHVAR_TABLE_SNAME"(key,value,uses)"
 +               "VALUES(%Q,%Q,"SPTU_Script");", name, zValue);
 +  sstr_holder(zValGlom);
 +  rc = s3_prep_noom_free(db, &zSql, &pStmtSet);
 +  assert(rc==SQLITE_OK);
 +  stmt_holder(pStmtSet);
 +  rc = s3_step_noom(pStmtSet);
 +  rc = (SQLITE_DONE==rc)? SQLITE_OK : SQLITE_ERROR;
 +  release_holders(2);
 +  return rc;
 +}
 +
 +
 +/* Most of the .parameter set subcommand (per help text)
 + * Return SQLITE_OK on success, else SQLITE_ERROR.
 + */
 +static int param_set(sqlite3 *db, char cCast,
 +                     char *name, char **valBeg, char **valLim){
 +  char *zSql = 0;
 +  char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0;
 +  sqlite3_stmt *pStmtSet = 0;
 +  /* Above objects are managed. */
 +  const char *zCastTo = 0;
 +  int rc = SQLITE_OK, retries = 0, needsEval = 1;
 +  char *zValue = (zValGlom==0)? *valBeg : zValGlom;
 +
 +  sstr_holder(zValGlom); /* +1 */
 +  if( cCast ){
 +    struct ParamSetOpts *pSO = param_set_opts;
 +    for(; pSO-param_set_opts < ArraySize(param_set_opts); ++pSO ){
 +      if( cCast==pSO->cCast ){
 +        zCastTo = pSO->zTypename;
 +        needsEval = pSO->evalKind > 0;
 +        break;
        }
 -    }else{
 -      raw_printf(stderr, "Usage: .cd DIRECTORY\n");
 -      rc = 1;
      }
 -  }else
 -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 -
 -  if( c=='c' && n>=3 && cli_strncmp(azArg[0], "changes", n)==0 ){
 -    if( nArg==2 ){
 -      setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
 +  }
 +  stmt_ptr_holder(&pStmtSet); /* +2 */
 +  if( needsEval ){
 +    if( zCastTo!=0 ){
 +      zSql = smprintf
 +        ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 +          " VALUES(%Q,CAST((%s) AS %s),"SPTU_Binding");",
 +          name, zValue, zCastTo );
      }else{
 -      raw_printf(stderr, "Usage: .changes on|off\n");
 -      rc = 1;
 -    }
 -  }else
 +      zSql = smprintf
 +        ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 +          "VALUES(%Q,(%s),"SPTU_Binding");", name, zValue );
 +    }
 +    rc = s3_prep_noom_free(db, &zSql, &pStmtSet);
 +  }
 +  if( !needsEval || rc!=SQLITE_OK ){
 +    /* Reach here when value either requested to be cast to text, or must be. */
 +    sqlite3_finalize(pStmtSet);
 +    pStmtSet = 0;
 +    zSql = smprintf
 +      ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 +        "VALUES(%Q,%Q,"SPTU_Binding");", name, zValue );
 +    rc = s3_prep_noom_free(db, &zSql, &pStmtSet);
 +    assert(rc==SQLITE_OK);
 +  }
 +  sqlite3_step(pStmtSet);
 +  release_holders(2);
 +  return rc;
 +}
  
 -#ifndef SQLITE_SHELL_FIDDLE
 -  /* Cancel output redirection, if it is currently set (by .testcase)
 -  ** Then read the content of the testcase-out.txt file and compare against
 -  ** azArg[1].  If there are differences, report an error and exit.
 -  */
 -  if( c=='c' && n>=3 && cli_strncmp(azArg[0], "check", n)==0 ){
 -    char *zRes = 0;
 -    output_reset(p);
 -    if( nArg!=2 ){
 -      raw_printf(stderr, "Usage: .check GLOB-PATTERN\n");
 -      rc = 2;
 -    }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){
 -      rc = 2;
 -    }else if( testcase_glob(azArg[1],zRes)==0 ){
 -      utf8_printf(stderr,
 -                 "testcase-%s FAILED\n Expected: [%s]\n      Got: [%s]\n",
 -                 p->zTestcase, azArg[1], zRes);
 -      rc = 1;
 -    }else{
 -      utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase);
 -      p->nCheck++;
 -    }
 -    sqlite3_free(zRes);
 -  }else
 -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 +/* list or ls subcommand for .parameter and .vars dot-commands */
 +static void list_pov_entries(ShellExState *psx, ParamTableUse ptu, u8 bShort,
 +                             char **pzArgs, int nArg){
 +  sqlite3_stmt *pStmt = 0;
 +  sqlite3_str *sbList = 0;
 +  char *zFromWhere = 0;
 +  char *zSql = 0;
 +  /* Above objects are managed. */
 +  RESOURCE_MARK(mark);
 +  sqlite3 *db;
 +  int len = 0, rc;
 +  const char *zTab;
  
 -#ifndef SQLITE_SHELL_FIDDLE
 -  if( c=='c' && cli_strncmp(azArg[0], "clone", n)==0 ){
 -    failIfSafeMode(p, "cannot run .clone in safe mode");
 -    if( nArg==2 ){
 -      tryToClone(p, azArg[1]);
 -    }else{
 -      raw_printf(stderr, "Usage: .clone FILENAME\n");
 -      rc = 1;
 +  switch( ptu ){
 +  case PTU_Binding:
 +    db = DBX(psx);
 +    zTab = PARAM_TABLE_SNAME;
 +    break;
 +  case PTU_Script:
 +    db = psx->dbShell;
 +    zTab = SHVAR_TABLE_NAME;
 +    break;
 +  default: assert(0); return;
 +  }
 +  sbList = sqlite3_str_new(db);
 +  sqst_holder(sbList);
 +  sqlite3_str_appendf(sbList, "FROM ");
 +  sqlite3_str_appendf(sbList, zTab);
 +  sqlite3_str_appendf(sbList, " WHERE (uses=?1) AND ");
 +  append_glob_terms(sbList, "key",
 +                    (const char **)pzArgs, (const char **)pzArgs+nArg);
 +  shell_check_nomem(sqlite3_str_errcode(sbList));
 +  zFromWhere = sqlite3_str_finish(sbList);
 +  drop_holder();
 +  shell_check_ooms(zFromWhere);
 +  sstr_holder(zFromWhere); /* +1 */
 +  zSql = smprintf("SELECT max(length(key)) %s", zFromWhere);
 +  sstr_ptr_holder(&zSql);
 +  rc = s3_prep_noom_free(db, &zSql, &pStmt);
 +  stmt_ptr_holder(&pStmt);
 +  if( rc==SQLITE_OK ){
 +    sqlite3_bind_int(pStmt, 1, ptu);
 +    if( s3_step_noom(pStmt)==SQLITE_ROW ){
 +      len = sqlite3_column_int(pStmt, 0);
 +      if( len>40 ) len = 40;
 +      if( len<4 ) len = 4;
      }
 -  }else
 -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 -
 -  if( c=='c' && cli_strncmp(azArg[0], "connection", n)==0 ){
 -    if( nArg==1 ){
 -      /* List available connections */
 -      int i;
 -      for(i=0; i<ArraySize(p->aAuxDb); i++){
 -        const char *zFile = p->aAuxDb[i].zDbFilename;
 -        if( p->aAuxDb[i].db==0 && p->pAuxDb!=&p->aAuxDb[i] ){
 -          zFile = "(not open)";
 -        }else if( zFile==0 ){
 -          zFile = "(memory)";
 -        }else if( zFile[0]==0 ){
 -          zFile = "(temporary-file)";
 -        }
 -        if( p->pAuxDb == &p->aAuxDb[i] ){
 -          utf8_printf(stdout, "ACTIVE %d: %s\n", i, zFile);
 -        }else if( p->aAuxDb[i].db!=0 ){
 -          utf8_printf(stdout, "       %d: %s\n", i, zFile);
 +    sqlite3_finalize(pStmt);
 +    pStmt = 0;
 +  }
 +  if( len ){
 +    FILE *out = ISS(psx)->out;
 +    sqlite3_free(zSql);
 +    zSql = 0;
 +    if( !bShort ){
 +      int nBindings = 0, nScripts = 0;
 +      zSql = smprintf("SELECT key, uses,"
 +                      " iif(typeof(value)='text', quote(value), value) as v"
 +                      " %s ORDER BY uses, key", zFromWhere);
 +      rc = s3_prep_noom_free(db, &zSql, &pStmt);
 +      sqlite3_bind_int(pStmt, 1, ptu);
 +      while( rc==SQLITE_OK && s3_step_noom(pStmt)==SQLITE_ROW ){
 +        ParamTableUse ptux = sqlite3_column_int(pStmt,1);
 +        const char *zName = sqlite3_column_text(pStmt,0);
 +        const char *zValue = sqlite3_column_text(pStmt,2);
 +        if( !zName ) zName = "?";
 +        if( !zValue ) zValue = "?";
 +        switch( ptux ){
 +        case PTU_Binding:
 +          if( nBindings++ == 0 ){
 +            utf8_printf(out, "Bindings:\n%-*s %s\n", len, "name", "value");
 +          }
 +          utf8_printf(out, "%-*s %s\n", len, zName, zValue);
 +          break;
 +        case PTU_Script:
 +          if( nScripts++ == 0 ){
 +            utf8_printf(out, "Scripts:\n%-*s %s\n", len, "name", "value");
 +          }
 +          utf8_printf(out, "%-*s %s\n", len, zName, zValue);
 +          break;
 +        default: break; /* Ignore */
          }
        }
 -    }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){
 -      int i = azArg[1][0] - '0';
 -      if( p->pAuxDb != &p->aAuxDb[i] && i>=0 && i<ArraySize(p->aAuxDb) ){
 -        p->pAuxDb->db = p->db;
 -        p->pAuxDb = &p->aAuxDb[i];
 -        globalDb = p->db = p->pAuxDb->db;
 -        p->pAuxDb->db = 0;
 -      }
 -    }else if( nArg==3 && cli_strcmp(azArg[1], "close")==0
 -           && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){
 -      int i = azArg[2][0] - '0';
 -      if( i<0 || i>=ArraySize(p->aAuxDb) ){
 -        /* No-op */
 -      }else if( p->pAuxDb == &p->aAuxDb[i] ){
 -        raw_printf(stderr, "cannot close the active database connection\n");
 -        rc = 1;
 -      }else if( p->aAuxDb[i].db ){
 -        session_close_all(p, i);
 -        close_db(p->aAuxDb[i].db);
 -        p->aAuxDb[i].db = 0;
 -      }
 -    }else{
 -      raw_printf(stderr, "Usage: .connection [close] [CONNECTION-NUMBER]\n");
 -      rc = 1;
 -    }
 -  }else
 -
 -  if( c=='d' && n>1 && cli_strncmp(azArg[0], "databases", n)==0 ){
 -    char **azName = 0;
 -    int nName = 0;
 -    sqlite3_stmt *pStmt;
 -    int i;
 -    open_db(p, 0);
 -    rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
 -    if( rc ){
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 -      rc = 1;
      }else{
 -      while( sqlite3_step(pStmt)==SQLITE_ROW ){
 -        const char *zSchema = (const char *)sqlite3_column_text(pStmt,1);
 -        const char *zFile = (const char*)sqlite3_column_text(pStmt,2);
 -        if( zSchema==0 || zFile==0 ) continue;
 -        azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*));
 -        shell_check_oom(azName);
 -        azName[nName*2] = strdup(zSchema);
 -        azName[nName*2+1] = strdup(zFile);
 -        nName++;
 -      }
 -    }
 -    sqlite3_finalize(pStmt);
 -    for(i=0; i<nName; i++){
 -      int eTxn = sqlite3_txn_state(p->db, azName[i*2]);
 -      int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]);
 -      const char *z = azName[i*2+1];
 -      utf8_printf(p->out, "%s: %s %s%s\n",
 -         azName[i*2],
 -         z && z[0] ? z : "\"\"",
 -         bRdonly ? "r/o" : "r/w",
 -         eTxn==SQLITE_TXN_NONE ? "" :
 -            eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
 -      free(azName[i*2]);
 -      free(azName[i*2+1]);
 -    }
 -    sqlite3_free(azName);
 -  }else
 -
 -  if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbconfig", n)==0 ){
 -    static const struct DbConfigChoices {
 -      const char *zName;
 -      int op;
 -    } aDbConfig[] = {
 -        { "defensive",          SQLITE_DBCONFIG_DEFENSIVE             },
 -        { "dqs_ddl",            SQLITE_DBCONFIG_DQS_DDL               },
 -        { "dqs_dml",            SQLITE_DBCONFIG_DQS_DML               },
 -        { "enable_fkey",        SQLITE_DBCONFIG_ENABLE_FKEY           },
 -        { "enable_qpsg",        SQLITE_DBCONFIG_ENABLE_QPSG           },
 -        { "enable_trigger",     SQLITE_DBCONFIG_ENABLE_TRIGGER        },
 -        { "enable_view",        SQLITE_DBCONFIG_ENABLE_VIEW           },
 -        { "fts3_tokenizer",     SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER },
 -        { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE    },
 -        { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT    },
 -        { "load_extension",     SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION },
 -        { "no_ckpt_on_close",   SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE      },
 -        { "reset_database",     SQLITE_DBCONFIG_RESET_DATABASE        },
 -        { "reverse_scanorder",  SQLITE_DBCONFIG_REVERSE_SCANORDER     },
 -        { "stmt_scanstatus",    SQLITE_DBCONFIG_STMT_SCANSTATUS       },
 -        { "trigger_eqp",        SQLITE_DBCONFIG_TRIGGER_EQP           },
 -        { "trusted_schema",     SQLITE_DBCONFIG_TRUSTED_SCHEMA        },
 -        { "writable_schema",    SQLITE_DBCONFIG_WRITABLE_SCHEMA       },
 -    };
 -    int ii, v;
 -    open_db(p, 0);
 -    for(ii=0; ii<ArraySize(aDbConfig); ii++){
 -      if( nArg>1 && cli_strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue;
 -      if( nArg>=3 ){
 -        sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0);
 +      int nc = 0, ncw = 78/(len+2);
 +      zSql = smprintf("SELECT key %s ORDER BY key", zFromWhere);
 +      rc = s3_prep_noom_free(db, &zSql, &pStmt);
 +      sqlite3_bind_int(pStmt, 1, ptu);
 +      while( rc==SQLITE_OK && s3_step_noom(pStmt)==SQLITE_ROW ){
 +        utf8_printf(out, "%s  %-*s", ((++nc%ncw==0)? "\n" : ""),
 +                    len, sqlite3_column_text(pStmt,0));
        }
 -      sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v);
 -      utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off");
 -      if( nArg>1 ) break;
 +      if( nc>0 ) utf8_printf(out, "\n");
      }
 -    if( nArg>1 && ii==ArraySize(aDbConfig) ){
 -      utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]);
 -      utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n");
 -    }
 -  }else
 +  }
 +  RESOURCE_FREE(mark);
 +}
  
 -#if SQLITE_SHELL_HAVE_RECOVER
 -  if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbinfo", n)==0 ){
 -    rc = shell_dbinfo_command(p, nArg, azArg);
 -  }else
 +/* Append an OR'ed series of GLOB terms comparing a given column
 + * name to a series of patterns. Result is an appended expression.
 + * For an empty pattern series, expression is true for non-NULL.
 + */
 +static void append_glob_terms(sqlite3_str *pStr, const char *zColName,
 +                              const char **azBeg, const char **azLim){
 +  if( azBeg==azLim ) sqlite3_str_appendf(pStr, "%s NOT NULL", zColName);
 +  else{
 +    char *zSep = "(";
 +    while( azBeg<azLim ){
 +      sqlite3_str_appendf(pStr, "%sglob(%Q,%s)", zSep, *azBeg, zColName);
 +      zSep = " OR ";
 +      ++azBeg;
 +    }
 +    sqlite3_str_appendf(pStr, ")");
 +  }
 +}
  
 -  if( c=='r' && cli_strncmp(azArg[0], "recover", n)==0 ){
 -    open_db(p, 0);
 -    rc = recoverDatabaseCmd(p, nArg, azArg);
 -  }else
 -#endif /* SQLITE_SHELL_HAVE_RECOVER */
 +/* Append either an IN clause or an always true test to some SQL.
 + *
 + * An empty IN list is the same as always true (for non-NULL LHS)
 + * for this clause, which assumes a trailing LHS operand and space.
 + * If that is not the right result, guard the call against it.
 + * This is used for ".parameter dostuff ?NAMES?" options,
 + * where a missing list means all the qualifying entries.
 + *
 + * The empty list may be signified by azBeg and azLim both 0.
 + */
 +static void append_in_clause(sqlite3_str *pStr,
 +                             const char **azBeg, const char **azLim){
 +  if( azBeg==azLim ) sqlite3_str_appendf(pStr, "NOT NULL");
 +  else{
 +    char cSep = '(';
 +    sqlite3_str_appendf(pStr, "IN");
 +    while( azBeg<azLim ){
 +      sqlite3_str_appendf(pStr, "%c%Q", cSep, *azBeg);
 +      cSep = ',';
 +      ++azBeg;
 +    }
 +    sqlite3_str_appendf(pStr, ")");
 +  }
 +}
  
 -  if( c=='d' && cli_strncmp(azArg[0], "dump", n)==0 ){
 -    char *zLike = 0;
 -    char *zSql;
 -    int i;
 -    int savedShowHeader = p->showHeader;
 -    int savedShellFlags = p->shellFlgs;
 -    ShellClearFlag(p,
 -       SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo
 -       |SHFLG_DumpDataOnly|SHFLG_DumpNoSys);
 -    for(i=1; i<nArg; i++){
 -      if( azArg[i][0]=='-' ){
 -        const char *z = azArg[i]+1;
 -        if( z[0]=='-' ) z++;
 -        if( cli_strcmp(z,"preserve-rowids")==0 ){
 -#ifdef SQLITE_OMIT_VIRTUALTABLE
 -          raw_printf(stderr, "The --preserve-rowids option is not compatible"
 -                             " with SQLITE_OMIT_VIRTUALTABLE\n");
 -          rc = 1;
 -          sqlite3_free(zLike);
 -          goto meta_command_exit;
 -#else
 -          ShellSetFlag(p, SHFLG_PreserveRowid);
 +/*****************
 + * The .parameter command
 + */
 +COLLECT_HELP_TEXT[
 +  ".parameter CMD ...       Manage per-DB SQL parameter bindings table",
 +  "   clear ?NAMES?           Erase all or only given named parameters",
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +  "   edit ?OPT? NAME ...     Use edit() to create or alter parameter NAME",
 +  "      OPT may be -t to use edited value as text or -e to evaluate it.",
  #endif
 -        }else
 -        if( cli_strcmp(z,"newlines")==0 ){
 -          ShellSetFlag(p, SHFLG_Newlines);
 -        }else
 -        if( cli_strcmp(z,"data-only")==0 ){
 -          ShellSetFlag(p, SHFLG_DumpDataOnly);
 -        }else
 -        if( cli_strcmp(z,"nosys")==0 ){
 -          ShellSetFlag(p, SHFLG_DumpNoSys);
 -        }else
 -        {
 -          raw_printf(stderr, "Unknown option \"%s\" on \".dump\"\n", azArg[i]);
 -          rc = 1;
 -          sqlite3_free(zLike);
 -          goto meta_command_exit;
 -        }
 -      }else{
 -        /* azArg[i] contains a LIKE pattern. This ".dump" request should
 -        ** only dump data for tables for which either the table name matches
 -        ** the LIKE pattern, or the table appears to be a shadow table of
 -        ** a virtual table for which the name matches the LIKE pattern.
 -        */
 -        char *zExpr = sqlite3_mprintf(
 -            "name LIKE %Q ESCAPE '\\' OR EXISTS ("
 -            "  SELECT 1 FROM sqlite_schema WHERE "
 -            "    name LIKE %Q ESCAPE '\\' AND"
 -            "    sql LIKE 'CREATE VIRTUAL TABLE%%' AND"
 -            "    substr(o.name, 1, length(name)+1) == (name||'_')"
 -            ")", azArg[i], azArg[i]
 -        );
 +  "   init                    Initialize TEMP table for bindings and scripts",
 +  "   list ?PATTERNS?         List current DB parameters table binding values",
 +  "      Alternatively, to list just some or all names: ls ?PATTERNS?",
 +  "   load ?FILE? ?NAMES?     Load some or all named parameters from FILE",
 +  "      If FILE missing, empty or '~', it defaults to ~/sqlite_params.sdb",
 +  "   save ?FILE? ?NAMES?     Save some or all named parameters into FILE",
 +  "      If FILE missing, empty or '~', it defaults to ~/sqlite_params.sdb",
 +  "   set ?TOPT? NAME VALUE   Give SQL parameter NAME a value of VALUE",
 +  "      NAME must begin with one of $,:,@,? for bindings, VALUE is the space-",
 +  "      joined argument list. TOPT may be one of {-b -i -n -r -t} to cast the",
 +  "      effective value to BLOB, INT, NUMERIC, REAL or TEXT respectively.",
 +  "   unset ?NAMES?           Remove named parameter(s) from parameters table",
 +];
 +DISPATCHABLE_COMMAND( parameter 2 2 0 ){
 +  int rc = 0;
 +  open_db(p,0);
 +  sqlite3 *db = DBX(p);
  
 -        if( zLike ){
 -          zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr);
 -        }else{
 -          zLike = zExpr;
 +  /* .parameter clear  and  .parameter unset ?NAMES?
 +  **  Delete some or all parameters from the TEMP table that holds them.
 +  **  Without any arguments, clear deletes them all and unset does nothing.
 +  */
 +  if( cli_strcmp(azArg[1],"clear")==0 || cli_strcmp(azArg[1],"unset")==0 ){
 +    if( param_table_exists(db) && (nArg>2 || azArg[1][0]=='c') ){
 +      sqlite3_str *sbZap = sqlite3_str_new(db);
 +      char *zSql = 0;
 +      sqlite3_str_appendf
 +        (sbZap, "DELETE FROM "PARAM_TABLE_SNAME" WHERE key ");
 +      append_in_clause(sbZap,
 +                       (const char **)&azArg[2], (const char **)&azArg[nArg]);
 +      zSql = sqlite3_str_finish(sbZap);
 +      shell_check_ooms(zSql);
 +      sstr_holder(zSql);
 +      sqlite3_exec(db, zSql, 0, 0, 0);
 +      release_holder();
 +    }
 +  }else
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +  /* .parameter edit ?NAMES?
 +  ** Edit the named parameters. Any that do not exist are created.
 +  */
 +  if( cli_strcmp(azArg[1],"edit")==0 ){
 +    ShellInState *psi = ISS(p);
 +    int ia = 2;
 +    int eval = 0;
 +    int edSet;
 +
 +    if( !INSOURCE_IS_INTERACTIVE(psi->pInSource) ){
 +      utf8_printf(STD_ERR, "Error: "
 +                  ".parameter edit can only be used interactively.\n");
 +      return DCR_Error;
 +    }
 +    param_table_init(db);
 +    edSet = attempt_editor_set(psi, azArg[0], (nArg>2)? azArg[2] : 0 );
 +    if( edSet < 0 ) return DCR_Error;
 +    else ia += edSet;
 +    /* Future: Allow an option whereby new value can be evaluated
 +     * the way that .parameter set ... does.
 +     */
 +    while( ia < nArg ){
 +      ParamTableUse ptu;
 +      char *zA = azArg[ia];
 +      char cf = (zA[0]=='-')? zA[1] : 0;
 +      if( cf!=0 && zA[2]==0 ){
 +        ++ia;
 +        switch( cf ){
 +        case 'e': eval = 1; continue;
 +        case 't': eval = 0; continue;
 +        default:
 +          utf8_printf(STD_ERR, "Error: bad .parameter name: %s\n", zA);
 +          return DCR_Error;
          }
        }
 +      ptu = classify_param_name(zA);
 +      if( ptu!=PTU_Binding ){
 +        utf8_printf(STD_ERR, "Error: "
 +                    "%s cannot be a binding parameter name.\n", zA);
 +        return DCR_Error;
 +      }
 +      rc = edit_one_kvalue(db, zA, eval, ptu, psi->zEditor);
 +      ++ia;
 +      if( rc!=0 ) return DCR_Error;
      }
 -
 -    open_db(p, 0);
 -
 -    if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 -      /* When playing back a "dump", the content might appear in an order
 -      ** which causes immediate foreign key constraints to be violated.
 -      ** So disable foreign-key constraint enforcement to prevent problems. */
 -      raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n");
 -      raw_printf(p->out, "BEGIN TRANSACTION;\n");
 -    }
 -    p->writableSchema = 0;
 -    p->showHeader = 0;
 -    /* Set writable_schema=ON since doing so forces SQLite to initialize
 -    ** as much of the schema as it can even if the sqlite_schema table is
 -    ** corrupt. */
 -    sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
 -    p->nErr = 0;
 -    if( zLike==0 ) zLike = sqlite3_mprintf("true");
 -    zSql = sqlite3_mprintf(
 -      "SELECT name, type, sql FROM sqlite_schema AS o "
 -      "WHERE (%s) AND type=='table'"
 -      "  AND sql NOT NULL"
 -      " ORDER BY tbl_name='sqlite_sequence', rowid",
 -      zLike
 -    );
 -    run_schema_dump_query(p,zSql);
 -    sqlite3_free(zSql);
 -    if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 -      zSql = sqlite3_mprintf(
 -        "SELECT sql FROM sqlite_schema AS o "
 -        "WHERE (%s) AND sql NOT NULL"
 -        "  AND type IN ('index','trigger','view')",
 -        zLike
 -      );
 -      run_table_dump_query(p, zSql);
 -      sqlite3_free(zSql);
 -    }
 -    sqlite3_free(zLike);
 -    if( p->writableSchema ){
 -      raw_printf(p->out, "PRAGMA writable_schema=OFF;\n");
 -      p->writableSchema = 0;
 -    }
 -    sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0);
 -    sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0);
 -    if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 -      raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n");
 -    }
 -    p->showHeader = savedShowHeader;
 -    p->shellFlgs = savedShellFlags;
    }else
 +#endif
  
 -  if( c=='e' && cli_strncmp(azArg[0], "echo", n)==0 ){
 -    if( nArg==2 ){
 -      setOrClearFlag(p, SHFLG_Echo, azArg[1]);
 -    }else{
 -      raw_printf(stderr, "Usage: .echo on|off\n");
 -      rc = 1;
 -    }
 +  /* .parameter init
 +  ** Make sure the TEMP table used to hold bind parameters exists.
 +  ** Create it if necessary.
 +  */
 +  if( nArg==2 && cli_strcmp(azArg[1],"init")==0 ){
 +    param_table_init(db);
    }else
  
 -  if( c=='e' && cli_strncmp(azArg[0], "eqp", n)==0 ){
 -    if( nArg==2 ){
 -      p->autoEQPtest = 0;
 -      if( p->autoEQPtrace ){
 -        if( p->db ) sqlite3_exec(p->db, "PRAGMA vdbe_trace=OFF;", 0, 0, 0);
 -        p->autoEQPtrace = 0;
 -      }
 -      if( cli_strcmp(azArg[1],"full")==0 ){
 -        p->autoEQP = AUTOEQP_full;
 -      }else if( cli_strcmp(azArg[1],"trigger")==0 ){
 -        p->autoEQP = AUTOEQP_trigger;
 -#ifdef SQLITE_DEBUG
 -      }else if( cli_strcmp(azArg[1],"test")==0 ){
 -        p->autoEQP = AUTOEQP_on;
 -        p->autoEQPtest = 1;
 -      }else if( cli_strcmp(azArg[1],"trace")==0 ){
 -        p->autoEQP = AUTOEQP_full;
 -        p->autoEQPtrace = 1;
 -        open_db(p, 0);
 -        sqlite3_exec(p->db, "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0);
 -        sqlite3_exec(p->db, "PRAGMA vdbe_trace=ON;", 0, 0, 0);
 -#endif
 -      }else{
 -        p->autoEQP = (u8)booleanValue(azArg[1]);
 -      }
 -    }else{
 -      raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n");
 -      rc = 1;
 -    }
 +  /* .parameter list|ls
 +  ** List all or selected bind parameters.
 +  ** list displays names, values and uses.
 +  ** ls displays just the names.
 +  */
 +  if( nArg>=2 && ((cli_strcmp(azArg[1],"list")==0)
 +                  || (cli_strcmp(azArg[1],"ls")==0)) ){
 +    list_pov_entries(p, PTU_Binding, azArg[1][1]=='s', azArg+2, nArg-2);
    }else
  
 -#ifndef SQLITE_SHELL_FIDDLE
 -  if( c=='e' && cli_strncmp(azArg[0], "exit", n)==0 ){
 -    if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc);
 -    rc = 2;
 +  /* .parameter load
 +  ** Load all or named parameters from specified or default (DB) file.
 +  */
 +  if( cli_strcmp(azArg[1],"load")==0 ){
 +    param_table_init(db);
 +    rc = kv_pairs_load(db, PTU_Binding, (const char **)azArg+1, nArg-1);
    }else
 -#endif
  
 -  /* The ".explain" command is automatic now.  It is largely pointless.  It
 -  ** retained purely for backwards compatibility */
 -  if( c=='e' && cli_strncmp(azArg[0], "explain", n)==0 ){
 -    int val = 1;
 -    if( nArg>=2 ){
 -      if( cli_strcmp(azArg[1],"auto")==0 ){
 -        val = 99;
 -      }else{
 -        val =  booleanValue(azArg[1]);
 -      }
 -    }
 -    if( val==1 && p->mode!=MODE_Explain ){
 -      p->normalMode = p->mode;
 -      p->mode = MODE_Explain;
 -      p->autoExplain = 0;
 -    }else if( val==0 ){
 -      if( p->mode==MODE_Explain ) p->mode = p->normalMode;
 -      p->autoExplain = 0;
 -    }else if( val==99 ){
 -      if( p->mode==MODE_Explain ) p->mode = p->normalMode;
 -      p->autoExplain = 1;
 -    }
 +  /* .parameter save
 +  ** Save all or named parameters into specified or default (DB) file.
 +  */
 +  if( cli_strcmp(azArg[1],"save")==0 ){
 +    rc = kv_pairs_save(db, PTU_Binding, (const char **)azArg+1, nArg-1);
    }else
  
 -#ifndef SQLITE_OMIT_VIRTUALTABLE
 -  if( c=='e' && cli_strncmp(azArg[0], "expert", n)==0 ){
 -    if( p->bSafeMode ){
 -      raw_printf(stderr,
 -        "Cannot run experimental commands such as \"%s\" in safe mode\n",
 -        azArg[0]);
 +  /* .parameter set NAME VALUE
 +  ** Set or reset a bind parameter.  NAME should be the full parameter
 +  ** name exactly as it appears in the query.  (ex: $abc, @def).  The
 +  ** VALUE can be in either SQL literal notation, or if not it will be
 +  ** understood to be a text string.
 +  */
 +  if( nArg>=4 && cli_strcmp(azArg[1],"set")==0 ){
 +    char cCast = option_char(azArg[2]);
 +    int inv = 2 + (cCast != 0);
 +    ParamTableUse ptu = classify_param_name(azArg[inv]);
 +    if( ptu!=PTU_Binding ){
 +      utf8_printf(STD_ERR,
 +                  "Error: %s is not a usable parameter name.\n", azArg[inv]);
        rc = 1;
      }else{
 -      open_db(p, 0);
 -      expertDotCommand(p, azArg, nArg);
 +      param_table_init(db);
 +      rc = param_set(db, cCast, azArg[inv], &azArg[inv+1], &azArg[nArg]);
 +      if( rc!=SQLITE_OK ){
 +        utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(db));
 +        rc = 1;
 +      }
      }
    }else
 -#endif
  
 -  if( c=='f' && cli_strncmp(azArg[0], "filectrl", n)==0 ){
 -    static const struct {
 -       const char *zCtrlName;   /* Name of a test-control option */
 -       int ctrlCode;            /* Integer code for that option */
 -       const char *zUsage;      /* Usage notes */
 -    } aCtrl[] = {
 -      { "chunk_size",     SQLITE_FCNTL_CHUNK_SIZE,      "SIZE"           },
 -      { "data_version",   SQLITE_FCNTL_DATA_VERSION,    ""               },
 -      { "has_moved",      SQLITE_FCNTL_HAS_MOVED,       ""               },
 -      { "lock_timeout",   SQLITE_FCNTL_LOCK_TIMEOUT,    "MILLISEC"       },
 -      { "persist_wal",    SQLITE_FCNTL_PERSIST_WAL,     "[BOOLEAN]"      },
 -   /* { "pragma",         SQLITE_FCNTL_PRAGMA,          "NAME ARG"       },*/
 -      { "psow",       SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]"      },
 -      { "reserve_bytes",  SQLITE_FCNTL_RESERVE_BYTES,   "[N]"            },
 -      { "size_limit",     SQLITE_FCNTL_SIZE_LIMIT,      "[LIMIT]"        },
 -      { "tempfilename",   SQLITE_FCNTL_TEMPFILENAME,    ""               },
 -   /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY,  "COUNT DELAY"    },*/
 -    };
 -    int filectrl = -1;
 -    int iCtrl = -1;
 -    sqlite3_int64 iRes = 0;  /* Integer result to display if rc2==1 */
 -    int isOk = 0;            /* 0: usage  1: %lld  2: no-result */
 -    int n2, i;
 -    const char *zCmd = 0;
 -    const char *zSchema = 0;
 -
 -    open_db(p, 0);
 -    zCmd = nArg>=2 ? azArg[1] : "help";
 -
 -    if( zCmd[0]=='-'
 -     && (cli_strcmp(zCmd,"--schema")==0 || cli_strcmp(zCmd,"-schema")==0)
 -     && nArg>=4
 -    ){
 -      zSchema = azArg[2];
 -      for(i=3; i<nArg; i++) azArg[i-2] = azArg[i];
 -      nArg -= 2;
 -      zCmd = azArg[1];
 -    }
 +  {  /* If no command name and arg count matches, show a syntax error */
 +    showHelp(ISS(p)->out, "parameter", p);
 +    return DCR_CmdErred;
 +  }
  
 -    /* The argument can optionally begin with "-" or "--" */
 -    if( zCmd[0]=='-' && zCmd[1] ){
 -      zCmd++;
 -      if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 -    }
 +  return DCR_Ok | (rc!=0);
 +}
  
 -    /* --help lists all file-controls */
 -    if( cli_strcmp(zCmd,"help")==0 ){
 -      utf8_printf(p->out, "Available file-controls:\n");
 -      for(i=0; i<ArraySize(aCtrl); i++){
 -        utf8_printf(p->out, "  .filectrl %s %s\n",
 -                    aCtrl[i].zCtrlName, aCtrl[i].zUsage);
 +/*****************
 + * The .print, .progress and .prompt commands
 + */
 +CONDITION_COMMAND( progress !defined(SQLITE_OMIT_PROGRESS_CALLBACK) );
 +COLLECT_HELP_TEXT[
 +  ".print STRING...         Print literal STRING",
 +  ".progress N              Invoke progress handler after every N opcodes",
 +  "   --limit N                 Interrupt after N progress callbacks",
 +  "   --once                    Do no more than one progress interrupt",
 +  "   --quiet|-q                No output except at interrupts",
 +  "   --reset                   Reset the count for each input and interrupt",
 +  ".prompt MAIN CONTINUE    Replace the standard prompts",
 +];
 +DISPATCHABLE_COMMAND( print 3 1 0 ){
 +  int i;
 +  for(i=1; i<nArg; i++){
 +    utf8_printf(ISS(p)->out, "%s%s", azArg[i], (i==nArg-1)? "\n" : " ");
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( progress 3 2 0 ){
 +  ShellInState *psi = ISS(p);
 +  int i;
 +  int nn = 0;
 +  psi->flgProgress = 0;
 +  psi->mxProgress = 0;
 +  psi->nProgress = 0;
 +  for(i=1; i<nArg; i++){
 +    const char *z = azArg[i];
 +    if( z[0]=='-' ){
 +      z++;
 +      if( z[0]=='-' ) z++;
 +      if( cli_strcmp(z,"quiet")==0 || cli_strcmp(z,"q")==0 ){
 +        psi->flgProgress |= SHELL_PROGRESS_QUIET;
 +        continue;
        }
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -
 -    /* convert filectrl text option to value. allow any unique prefix
 -    ** of the option name, or a numerical value. */
 -    n2 = strlen30(zCmd);
 -    for(i=0; i<ArraySize(aCtrl); i++){
 -      if( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
 -        if( filectrl<0 ){
 -          filectrl = aCtrl[i].ctrlCode;
 -          iCtrl = i;
 +      if( cli_strcmp(z,"reset")==0 ){
 +        psi->flgProgress |= SHELL_PROGRESS_RESET;
 +        continue;
 +      }
 +      if( cli_strcmp(z,"once")==0 ){
 +        psi->flgProgress |= SHELL_PROGRESS_ONCE;
 +        continue;
 +      }
 +      if( cli_strcmp(z,"limit")==0 ){
 +        if( i+1>=nArg ){
 +          *pzErr = smprintf("missing argument on --limit\n");
 +          return DCR_Unpaired|i;
          }else{
 -          utf8_printf(stderr, "Error: ambiguous file-control: \"%s\"\n"
 -                              "Use \".filectrl --help\" for help\n", zCmd);
 -          rc = 1;
 -          goto meta_command_exit;
 +          psi->mxProgress = (int)integerValue(azArg[++i]);
          }
 +        continue;
        }
 -    }
 -    if( filectrl<0 ){
 -      utf8_printf(stderr,"Error: unknown file-control: %s\n"
 -                         "Use \".filectrl --help\" for help\n", zCmd);
 +      return DCR_Unknown|i;
      }else{
 -      switch(filectrl){
 -        case SQLITE_FCNTL_SIZE_LIMIT: {
 -          if( nArg!=2 && nArg!=3 ) break;
 -          iRes = nArg==3 ? integerValue(azArg[2]) : -1;
 -          sqlite3_file_control(p->db, zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes);
 -          isOk = 1;
 -          break;
 -        }
 -        case SQLITE_FCNTL_LOCK_TIMEOUT:
 -        case SQLITE_FCNTL_CHUNK_SIZE: {
 -          int x;
 -          if( nArg!=3 ) break;
 -          x = (int)integerValue(azArg[2]);
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          isOk = 2;
 -          break;
 -        }
 -        case SQLITE_FCNTL_PERSIST_WAL:
 -        case SQLITE_FCNTL_POWERSAFE_OVERWRITE: {
 -          int x;
 -          if( nArg!=2 && nArg!=3 ) break;
 -          x = nArg==3 ? booleanValue(azArg[2]) : -1;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          iRes = x;
 -          isOk = 1;
 -          break;
 -        }
 -        case SQLITE_FCNTL_DATA_VERSION:
 -        case SQLITE_FCNTL_HAS_MOVED: {
 -          int x;
 -          if( nArg!=2 ) break;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          iRes = x;
 -          isOk = 1;
 -          break;
 -        }
 -        case SQLITE_FCNTL_TEMPFILENAME: {
 -          char *z = 0;
 -          if( nArg!=2 ) break;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &z);
 -          if( z ){
 -            utf8_printf(p->out, "%s\n", z);
 -            sqlite3_free(z);
 -          }
 -          isOk = 2;
 -          break;
 -        }
 -        case SQLITE_FCNTL_RESERVE_BYTES: {
 -          int x;
 -          if( nArg>=3 ){
 -            x = atoi(azArg[2]);
 -            sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          }
 -          x = -1;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          utf8_printf(p->out,"%d\n", x);
 -          isOk = 2;
 -          break;
 -        }
 -      }
 +      nn = (int)integerValue(z);
 +    }
 +  }
 +  open_db(p, 0);
 +  sqlite3_progress_handler(DBX(p), nn, progress_handler, psi);
 +  return DCR_Ok;
 +}
 +
 +/* Allow too few arguments by tradition, (a form of no-op.) */
 +DISPATCHABLE_COMMAND( prompt ? 1 3 ){
 +  if( nArg >= 2) {
 +    SET_MAIN_PROMPT(azArg[1]);
 +  }
 +  if( nArg >= 3) {
 +    SET_MORE_PROMPT(azArg[2]);
 +  }
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * The .recover and .restore commands
 + */
 +CONDITION_COMMAND( recover SQLITE_SHELL_HAVE_RECOVER );
 +CONDITION_COMMAND( restore !defined(SQLITE_SHELL_FIDDLE) );
 +COLLECT_HELP_TEXT[
 +  ".recover                 Recover as much data as possible from corrupt db.",
 +  "   --ignore-freelist        Ignore pages that appear to be on db freelist",
 +  "   --lost-and-found TABLE   Alternative name for the lost-and-found table",
 +  "   --no-rowids              Do not attempt to recover rowid values",
 +  "                            that are not also INTEGER PRIMARY KEYs",
 +  ".restore ?DB? FILE       Restore content of DB (default \"main\") from FILE",
 +];
 +
 +/*
 +** This command is invoked to recover data from the database. A script
 +** to construct a new database containing all recovered data is output
 +** on stream pState->out.
 +*/
 +DISPATCHABLE_COMMAND( recover ? 1 7 ){
 +  int rc = SQLITE_OK;
 +  const char *zRecoveryDb = "";   /* Name of "recovery" database. Debug only */
 +  const char *zLAF = "lost_and_found";
 +  int bFreelist = 1;              /* 0 if --ignore-freelist is specified */
 +  int bRowids = 1;                /* 0 if --no-rowids */
 +  sqlite3_recover *pr = 0;
 +  int i = 0;
 +  FILE *out = ISS(p)->out;
 +  sqlite3 *db;
 +
 +  open_db(p, 0);
 +  db = DBX(p);
 +
 +  for(i=1; i<nArg; i++){
 +    char *z = azArg[i];
 +    int n;
 +    if( z[0]=='-' && z[1]=='-' ) z++;
 +    n = strlen30(z);
 +    if( n<=17 && memcmp("-ignore-freelist", z, n)==0 ){
 +      bFreelist = 0;
 +    }else
 +    if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
 +      /* This option determines the name of the ATTACH-ed database used
 +      ** internally by the recovery extension.  The default is "" which
 +      ** means to use a temporary database that is automatically deleted
 +      ** when closed.  This option is undocumented and might disappear at
 +      ** any moment. */
 +      i++;
 +      zRecoveryDb = azArg[i];
 +    }else
 +    if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
 +      i++;
 +      zLAF = azArg[i];
 +    }else
 +    if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
 +      bRowids = 0;
      }
 -    if( isOk==0 && iCtrl>=0 ){
 -      utf8_printf(p->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
 -      rc = 1;
 -    }else if( isOk==1 ){
 -      char zBuf[100];
 -      sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes);
 -      raw_printf(p->out, "%s\n", zBuf);
 +    else{
 +      utf8_printf(stderr, "unexpected option: %s\n", azArg[i]);
 +      showHelp(stderr, azArg[0], p);
 +      return DCR_Error;
      }
 -  }else
 +  }
  
 -  if( c=='f' && cli_strncmp(azArg[0], "fullschema", n)==0 ){
 -    ShellState data;
 -    int doStats = 0;
 -    memcpy(&data, p, sizeof(data));
 -    data.showHeader = 0;
 -    data.cMode = data.mode = MODE_Semi;
 -    if( nArg==2 && optionMatch(azArg[1], "indent") ){
 -      data.cMode = data.mode = MODE_Pretty;
 -      nArg = 1;
 -    }
 -    if( nArg!=1 ){
 -      raw_printf(stderr, "Usage: .fullschema ?--indent?\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    open_db(p, 0);
 -    rc = sqlite3_exec(p->db,
 -       "SELECT sql FROM"
 -       "  (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x"
 -       "     FROM sqlite_schema UNION ALL"
 -       "   SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) "
 -       "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' "
 -       "ORDER BY x",
 -       callback, &data, 0
 -    );
 -    if( rc==SQLITE_OK ){
 -      sqlite3_stmt *pStmt;
 -      rc = sqlite3_prepare_v2(p->db,
 -               "SELECT rowid FROM sqlite_schema"
 -               " WHERE name GLOB 'sqlite_stat[134]'",
 -               -1, &pStmt, 0);
 -      doStats = sqlite3_step(pStmt)==SQLITE_ROW;
 -      sqlite3_finalize(pStmt);
 -    }
 -    if( doStats==0 ){
 -      raw_printf(p->out, "/* No STAT tables available */\n");
 -    }else{
 -      raw_printf(p->out, "ANALYZE sqlite_schema;\n");
 -      data.cMode = data.mode = MODE_Insert;
 -      data.zDestTable = "sqlite_stat1";
 -      shell_exec(&data, "SELECT * FROM sqlite_stat1", 0);
 -      data.zDestTable = "sqlite_stat4";
 -      shell_exec(&data, "SELECT * FROM sqlite_stat4", 0);
 -      raw_printf(p->out, "ANALYZE sqlite_schema;\n");
 -    }
 -  }else
 +  pr = sqlite3_recover_init_sql(db, "main", recoverSqlCb, (void*)p);
  
 -  if( c=='h' && cli_strncmp(azArg[0], "headers", n)==0 ){
 -    if( nArg==2 ){
 -      p->showHeader = booleanValue(azArg[1]);
 -      p->shellFlgs |= SHFLG_HeaderSet;
 -    }else{
 -      raw_printf(stderr, "Usage: .headers on|off\n");
 -      rc = 1;
 -    }
 -  }else
 +  sqlite3_recover_config(pr, 789, (void*)zRecoveryDb);  /* Debug use only */
 +  sqlite3_recover_config(pr, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF);
 +  sqlite3_recover_config(pr, SQLITE_RECOVER_ROWIDS, (void*)&bRowids);
 +  sqlite3_recover_config(pr, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist);
  
 -  if( c=='h' && cli_strncmp(azArg[0], "help", n)==0 ){
 -    if( nArg>=2 ){
 -      n = showHelp(p->out, azArg[1]);
 -      if( n==0 ){
 -        utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]);
 -      }
 -    }else{
 -      showHelp(p->out, 0);
 -    }
 -  }else
 +  sqlite3_recover_run(pr);
 +  if( sqlite3_recover_errcode(pr)!=SQLITE_OK ){
 +    const char *zErr = sqlite3_recover_errmsg(pr);
 +    int errCode = sqlite3_recover_errcode(pr);
 +    raw_printf(stderr, "sql error: %s (%d)\n", zErr, errCode);
 +  }
 +  rc = sqlite3_recover_finish(pr);
 +  return (rc!=SQLITE_OK)? DCR_Error : DCR_Ok;
 +}
  
 -#ifndef SQLITE_SHELL_FIDDLE
 -  if( c=='i' && cli_strncmp(azArg[0], "import", n)==0 ){
 -    char *zTable = 0;           /* Insert data into this table */
 -    char *zSchema = 0;          /* within this schema (may default to "main") */
 -    char *zFile = 0;            /* Name of file to extra content from */
 -    sqlite3_stmt *pStmt = NULL; /* A statement */
 -    int nCol;                   /* Number of columns in the table */
 -    int nByte;                  /* Number of bytes in an SQL string */
 -    int i, j;                   /* Loop counters */
 -    int needCommit;             /* True to COMMIT or ROLLBACK at end */
 -    int nSep;                   /* Number of bytes in p->colSeparator[] */
 -    char *zSql;                 /* An SQL statement */
 -    char *zFullTabName;         /* Table name with schema if applicable */
 -    ImportCtx sCtx;             /* Reader context */
 -    char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
 -    int eVerbose = 0;           /* Larger for more console output */
 -    int nSkip = 0;              /* Initial lines to skip */
 -    int useOutputMode = 1;      /* Use output mode to determine separators */
 -    char *zCreate = 0;          /* CREATE TABLE statement text */
 -
 -    failIfSafeMode(p, "cannot run .import in safe mode");
 -    memset(&sCtx, 0, sizeof(sCtx));
 -    if( p->mode==MODE_Ascii ){
 -      xRead = ascii_read_one_field;
 -    }else{
 -      xRead = csv_read_one_field;
 -    }
 +DISPATCHABLE_COMMAND( restore ? 2 3 ){
 +  int rc;
 +  const char *zSrcFile;
 +  const char *zDb;
 +  sqlite3 *pSrc = 0;
 +  sqlite3_backup *pBackup;
 +  AnyResourceHolder arh = { 0, (GenericFreer)sqlite3_backup_finish };
 +  int nTimeout = 0;
 +
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +  if( nArg==2 ){
 +    zSrcFile = azArg[1];
 +    zDb = "main";
 +  }else if( nArg==3 ){
 +    zSrcFile = azArg[2];
 +    zDb = azArg[1];
 +  }else{
 +    return DCR_TooMany;
 +  }
 +  rc = shell_check_nomem(sqlite3_open(zSrcFile, &pSrc));
 +  conn_holder(pSrc);
 +  if( rc!=SQLITE_OK ){
 +    *pzErr = smprintf("cannot open \"%s\"\n", zSrcFile);
 +    release_holder();
 +    return DCR_Error;
 +  }
 +  open_db(p, 0);
 +  pBackup = sqlite3_backup_init(DBX(p), zDb, pSrc, "main");
 +  if( pBackup==0 ){
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
 +    release_holder();
 +    return DCR_Error;
 +  }
 +  arh.pAny = pBackup;
 +  any_ref_holder(&arh);
 +  while( (rc = shell_check_nomem(sqlite3_backup_step(pBackup,100)))==SQLITE_OK
 +         || rc==SQLITE_BUSY  ){
 +    if( rc==SQLITE_BUSY ){
 +      if( nTimeout++ >= 3 ) break;
 +      sqlite3_sleep(100);
 +    }
 +  }
 +  if( rc==SQLITE_DONE ){
 +    rc = 0;
 +  }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){
 +    *pzErr = smprintf("source database is busy\n");
      rc = 1;
 -    for(i=1; i<nArg; i++){
 -      char *z = azArg[i];
 -      if( z[0]=='-' && z[1]=='-' ) z++;
 -      if( z[0]!='-' ){
 -        if( zFile==0 ){
 -          zFile = z;
 -        }else if( zTable==0 ){
 -          zTable = z;
 -        }else{
 -          utf8_printf(p->out, "ERROR: extra argument: \"%s\".  Usage:\n", z);
 -          showHelp(p->out, "import");
 -          goto meta_command_exit;
 -        }
 -      }else if( cli_strcmp(z,"-v")==0 ){
 -        eVerbose++;
 -      }else if( cli_strcmp(z,"-schema")==0 && i<nArg-1 ){
 -        zSchema = azArg[++i];
 -      }else if( cli_strcmp(z,"-skip")==0 && i<nArg-1 ){
 -        nSkip = integerValue(azArg[++i]);
 -      }else if( cli_strcmp(z,"-ascii")==0 ){
 -        sCtx.cColSep = SEP_Unit[0];
 -        sCtx.cRowSep = SEP_Record[0];
 -        xRead = ascii_read_one_field;
 -        useOutputMode = 0;
 -      }else if( cli_strcmp(z,"-csv")==0 ){
 -        sCtx.cColSep = ',';
 -        sCtx.cRowSep = '\n';
 -        xRead = csv_read_one_field;
 -        useOutputMode = 0;
 -      }else{
 -        utf8_printf(p->out, "ERROR: unknown option: \"%s\".  Usage:\n", z);
 -        showHelp(p->out, "import");
 -        goto meta_command_exit;
 -      }
 -    }
 -    if( zTable==0 ){
 -      utf8_printf(p->out, "ERROR: missing %s argument. Usage:\n",
 -                  zFile==0 ? "FILE" : "TABLE");
 -      showHelp(p->out, "import");
 -      goto meta_command_exit;
 -    }
 -    seenInterrupt = 0;
 -    open_db(p, 0);
 -    if( useOutputMode ){
 -      /* If neither the --csv or --ascii options are specified, then set
 -      ** the column and row separator characters from the output mode. */
 -      nSep = strlen30(p->colSeparator);
 -      if( nSep==0 ){
 -        raw_printf(stderr,
 -                   "Error: non-null column separator required for import\n");
 -        goto meta_command_exit;
 -      }
 -      if( nSep>1 ){
 -        raw_printf(stderr,
 -              "Error: multi-character column separators not allowed"
 -              " for import\n");
 -        goto meta_command_exit;
 -      }
 -      nSep = strlen30(p->rowSeparator);
 -      if( nSep==0 ){
 -        raw_printf(stderr,
 -            "Error: non-null row separator required for import\n");
 -        goto meta_command_exit;
 -      }
 -      if( nSep==2 && p->mode==MODE_Csv
 -       && cli_strcmp(p->rowSeparator,SEP_CrLf)==0
 -      ){
 -        /* When importing CSV (only), if the row separator is set to the
 -        ** default output row separator, change it to the default input
 -        ** row separator.  This avoids having to maintain different input
 -        ** and output row separators. */
 -        sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 -        nSep = strlen30(p->rowSeparator);
 -      }
 -      if( nSep>1 ){
 -        raw_printf(stderr, "Error: multi-character row separators not allowed"
 -                           " for import\n");
 -        goto meta_command_exit;
 -      }
 -      sCtx.cColSep = (u8)p->colSeparator[0];
 -      sCtx.cRowSep = (u8)p->rowSeparator[0];
 -    }
 -    sCtx.zFile = zFile;
 -    sCtx.nLine = 1;
 -    if( sCtx.zFile[0]=='|' ){
 -#ifdef SQLITE_OMIT_POPEN
 -      raw_printf(stderr, "Error: pipes are not supported in this OS\n");
 -      goto meta_command_exit;
 -#else
 -      sCtx.in = popen(sCtx.zFile+1, "r");
 -      sCtx.zFile = "<pipe>";
 -      sCtx.xCloser = pclose;
 +  }else{
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
 +    rc = 1;
 +  }
 +  release_holders(2);
 +  return DCR_Ok|rc;
 +}
 +
 +/*****************
 + * The .scanstats and .schema commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".scanstats on|off|est    Turn sqlite3_stmt_scanstatus() metrics on or off",
 +  ".schema ?PATTERN?        Show the CREATE statements matching PATTERN",
 +  "   Options:",
 +  "      --indent             Try to pretty-print the schema",
 +  "      --nosys              Omit objects whose names start with \"sqlite_\"",
 +];
 +DISPATCHABLE_COMMAND( scanstats ? 2 2 ){
 +  if( cli_strcmp(azArg[1], "est")==0 ){
 +    ISS(p)->scanstatsOn = 2;
 +  }else{
 +    ISS(p)->scanstatsOn = (u8)booleanValue(azArg[1]);
 +  }
 +  open_db(p, 0);
 +  sqlite3_db_config(DBX(p), SQLITE_DBCONFIG_STMT_SCANSTATUS,
 +                    ISS(p)->scanstatsOn, (int*)0);
 +#ifndef SQLITE_ENABLE_STMT_SCANSTATUS
 +  raw_printf(STD_ERR, "Warning: .scanstats not available in this build.\n");
  #endif
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( schema ? 1 2 ){
 +  int rc = 0;
 +  ShellText sSelect = {0};
 +  ShellInState *psi = ISS(p);
 +  u8 useMode = MODE_Semi;
 +  AnyResourceHolder arh = {psi, (GenericFreer)modePopper};
 +  RESOURCE_MARK(mark);
 +  char *zErrMsg = 0;
 +  const char *zDiv = "(";
 +  const char *zName = 0;
 +  int iSchema = 0;
 +  int bDebug = 0;
 +  int bNoSystemTabs = 0;
 +  int ii;
 +
 +  open_db(p, 0);
 +
 +  initText(&sSelect);
 +  for(ii=1; ii<nArg; ii++){
 +    if( optionMatch(azArg[ii],"indent") ){
 +      useMode = MODE_Pretty;
 +    }else if( optionMatch(azArg[ii],"debug") ){
 +      bDebug = 1;
 +    }else if( optionMatch(azArg[ii],"nosys") ){
 +      bNoSystemTabs = 1;
 +    }else if( azArg[ii][0]=='-' ){
 +      return DCR_Unknown|ii;
 +    }else if( zName==0 ){
 +      zName = azArg[ii];
      }else{
 -      sCtx.in = fopen(sCtx.zFile, "rb");
 -      sCtx.xCloser = fclose;
 -    }
 -    if( sCtx.in==0 ){
 -      utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile);
 -      goto meta_command_exit;
 -    }
 -    if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
 -      char zSep[2];
 -      zSep[1] = 0;
 -      zSep[0] = sCtx.cColSep;
 -      utf8_printf(p->out, "Column separator ");
 -      output_c_string(p->out, zSep);
 -      utf8_printf(p->out, ", row separator ");
 -      zSep[0] = sCtx.cRowSep;
 -      output_c_string(p->out, zSep);
 -      utf8_printf(p->out, "\n");
 -    }
 -    sCtx.z = sqlite3_malloc64(120);
 -    if( sCtx.z==0 ){
 -      import_cleanup(&sCtx);
 -      shell_out_of_memory();
 -    }
 -    /* Below, resources must be freed before exit. */
 -    while( (nSkip--)>0 ){
 -      while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
 -    }
 -    if( zSchema!=0 ){
 -      zFullTabName = sqlite3_mprintf("\"%w\".\"%w\"", zSchema, zTable);
 -    }else{
 -      zFullTabName = sqlite3_mprintf("\"%w\"", zTable);
 -    }
 -    zSql = sqlite3_mprintf("SELECT * FROM %s", zFullTabName);
 -    if( zSql==0 || zFullTabName==0 ){
 -      import_cleanup(&sCtx);
 -      shell_out_of_memory();
 -    }
 -    nByte = strlen30(zSql);
 -    rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    import_append_char(&sCtx, 0);    /* To ensure sCtx.z is allocated */
 -    if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){
 -      sqlite3 *dbCols = 0;
 -      char *zRenames = 0;
 -      char *zColDefs;
 -      zCreate = sqlite3_mprintf("CREATE TABLE %s", zFullTabName);
 -      while( xRead(&sCtx) ){
 -        zAutoColumn(sCtx.z, &dbCols, 0);
 -        if( sCtx.cTerm!=sCtx.cColSep ) break;
 -      }
 -      zColDefs = zAutoColumn(0, &dbCols, &zRenames);
 -      if( zRenames!=0 ){
 -        utf8_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr,
 -                    "Columns renamed during .import %s due to duplicates:\n"
 -                    "%s\n", sCtx.zFile, zRenames);
 -        sqlite3_free(zRenames);
 -      }
 -      assert(dbCols==0);
 -      if( zColDefs==0 ){
 -        utf8_printf(stderr,"%s: empty file\n", sCtx.zFile);
 -      import_fail:
 -        sqlite3_free(zCreate);
 -        sqlite3_free(zSql);
 -        sqlite3_free(zFullTabName);
 -        import_cleanup(&sCtx);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs);
 -      if( eVerbose>=1 ){
 -        utf8_printf(p->out, "%s\n", zCreate);
 -      }
 -      rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
 -      if( rc ){
 -        utf8_printf(stderr, "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
 -        goto import_fail;
 -      }
 -      sqlite3_free(zCreate);
 -      zCreate = 0;
 -      rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    }
 +      return DCR_TooMany;
 +    }
 +  }
 +  outputModePush(psi); /* May OOM fail. */
 +  any_ref_holder(&arh);
 +  psi->showHeader = 0;
 +  psi->cMode = psi->mode = useMode;
 +  if( zName!=0 ){
 +    int isSchema = sqlite3_strlike(zName, "sqlite_master", '\\')==0
 +      || sqlite3_strlike(zName, "sqlite_schema", '\\')==0
 +      || sqlite3_strlike(zName,"sqlite_temp_master", '\\')==0
 +      || sqlite3_strlike(zName,"sqlite_temp_schema", '\\')==0;
 +    if( isSchema ){
 +      char *new_argv[2], *new_colv[2];
 +      new_argv[0] = smprintf(
 +                             "CREATE TABLE %s (\n"
 +                             "  type text,\n"
 +                             "  name text,\n"
 +                             "  tbl_name text,\n"
 +                             "  rootpage integer,\n"
 +                             "  sql text\n"
 +                             ")", zName);
 +      shell_check_ooms(new_argv[0]);
 +      sstr_holder(new_argv[0]);
 +      new_argv[1] = 0;
 +      new_colv[0] = "sql";
 +      new_colv[1] = 0;
 +      callback(p, 1, new_argv, new_colv);
 +      release_holder();
 +    }
 +  }
 +  if( zDiv ){
 +    sqlite3_stmt *pStmt = 0;
 +    rc = s3_prepare_v2_noom(p->dbUser,
 +                            "SELECT name FROM pragma_database_list",
 +                            -1, &pStmt, 0);
 +    stmt_ptr_holder(&pStmt);
      if( rc ){
 -      if (pStmt) sqlite3_finalize(pStmt);
 -      utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db));
 -      goto import_fail;
 +      *pzErr = smprintf("%s\n", sqlite3_errmsg(p->dbUser));
 +      RESOURCE_FREE(mark);
 +      return DCR_Error;
 +    }
 +    text_ref_holder(&sSelect);
 +    appendText(&sSelect, "SELECT sql FROM", 0);
 +    iSchema = 0;
 +    while( s3_step_noom(pStmt)==SQLITE_ROW ){
 +      const char *zDb = (const char*)sqlite3_column_text(pStmt, 0);
 +      char zScNum[30];
 +      sqlite3_snprintf(sizeof(zScNum), zScNum, "%d", ++iSchema);
 +      appendText(&sSelect, zDiv, 0);
 +      zDiv = " UNION ALL ";
 +      appendText(&sSelect, "SELECT shell_add_schema(sql,", 0);
 +      if( sqlite3_stricmp(zDb, "main")!=0 ){
 +        appendText(&sSelect, zDb, '\'');
 +      }else{
 +        appendText(&sSelect, "NULL", 0);
 +      }
 +      appendText(&sSelect, ",name) AS sql, type, tbl_name, name, rowid,", 0);
 +      appendText(&sSelect, zScNum, 0);
 +      appendText(&sSelect, " AS snum, ", 0);
 +      appendText(&sSelect, zDb, '\'');
 +      appendText(&sSelect, " AS sname FROM ", 0);
 +      appendText(&sSelect, zDb, quoteChar(zDb));
 +      appendText(&sSelect, ".sqlite_schema", 0);
      }
 -    sqlite3_free(zSql);
 -    nCol = sqlite3_column_count(pStmt);
      sqlite3_finalize(pStmt);
      pStmt = 0;
 -    if( nCol==0 ) return 0; /* no columns, no error */
 -    zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 );
 -    if( zSql==0 ){
 -      import_cleanup(&sCtx);
 -      shell_out_of_memory();
 -    }
 -    sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName);
 -    j = strlen30(zSql);
 -    for(i=1; i<nCol; i++){
 -      zSql[j++] = ',';
 -      zSql[j++] = '?';
 -    }
 -    zSql[j++] = ')';
 -    zSql[j] = 0;
 -    if( eVerbose>=2 ){
 -      utf8_printf(p->out, "Insert using: %s\n", zSql);
 -    }
 -    rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    if( rc ){
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 -      if (pStmt) sqlite3_finalize(pStmt);
 -      goto import_fail;
 +#ifndef SQLITE_OMIT_INTROSPECTION_PRAGMAS
 +    if( zName ){
 +      appendText(&sSelect,
 +                 " UNION ALL SELECT shell_module_schema(name),"
 +                 " 'table', name, name, name, 9e+99, 'main'"
 +                 " FROM pragma_module_list",
 +                 0);
      }
 -    sqlite3_free(zSql);
 -    sqlite3_free(zFullTabName);
 -    needCommit = sqlite3_get_autocommit(p->db);
 -    if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
 -    do{
 -      int startLine = sCtx.nLine;
 -      for(i=0; i<nCol; i++){
 -        char *z = xRead(&sCtx);
 -        /*
 -        ** Did we reach end-of-file before finding any columns?
 -        ** If so, stop instead of NULL filling the remaining columns.
 -        */
 -        if( z==0 && i==0 ) break;
 -        /*
 -        ** Did we reach end-of-file OR end-of-line before finding any
 -        ** columns in ASCII mode?  If so, stop instead of NULL filling
 -        ** the remaining columns.
 -        */
 -        if( p->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
 -        sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
 -        if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
 -          utf8_printf(stderr, "%s:%d: expected %d columns but found %d - "
 -                          "filling the rest with NULL\n",
 -                          sCtx.zFile, startLine, nCol, i+1);
 -          i += 2;
 -          while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; }
 -        }
 +#endif
 +    appendText(&sSelect, ") WHERE ", 0);
 +    if( zName ){
 +      char *zQarg = smprintf("%Q", zName);
 +      int bGlob;
 +      shell_check_ooms(zQarg);
 +      sstr_holder(zQarg);
 +      bGlob = strchr(zName, '*') != 0 || strchr(zName, '?') != 0
 +        || strchr(zName, '[') != 0;
 +      if( strchr(zName, '.') ){
 +        appendText(&sSelect, "lower(printf('%s.%s',sname,tbl_name))", 0);
 +      }else{
 +        appendText(&sSelect, "lower(tbl_name)", 0);
        }
 -      if( sCtx.cTerm==sCtx.cColSep ){
 -        do{
 -          xRead(&sCtx);
 -          i++;
 -        }while( sCtx.cTerm==sCtx.cColSep );
 -        utf8_printf(stderr, "%s:%d: expected %d columns but found %d - "
 -                        "extras ignored\n",
 -                        sCtx.zFile, startLine, nCol, i);
 -      }
 -      if( i>=nCol ){
 -        sqlite3_step(pStmt);
 -        rc = sqlite3_reset(pStmt);
 -        if( rc!=SQLITE_OK ){
 -          utf8_printf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
 -                      startLine, sqlite3_errmsg(p->db));
 -          sCtx.nErr++;
 -        }else{
 -          sCtx.nRow++;
 -        }
 +      appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0);
 +      appendText(&sSelect, zQarg, 0);
 +      if( !bGlob ){
 +        appendText(&sSelect, " ESCAPE '\\' ", 0);
        }
 -    }while( sCtx.cTerm!=EOF );
 -
 -    import_cleanup(&sCtx);
 -    sqlite3_finalize(pStmt);
 -    if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0);
 -    if( eVerbose>0 ){
 -      utf8_printf(p->out,
 -          "Added %d rows with %d errors using %d lines of input\n",
 -          sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
 -    }
 -  }else
 -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 -
 -#ifndef SQLITE_UNTESTABLE
 -  if( c=='i' && cli_strncmp(azArg[0], "imposter", n)==0 ){
 -    char *zSql;
 -    char *zCollist = 0;
 -    sqlite3_stmt *pStmt;
 -    int tnum = 0;
 -    int isWO = 0;  /* True if making an imposter of a WITHOUT ROWID table */
 -    int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */
 -    int i;
 -    if( !ShellHasFlag(p,SHFLG_TestingMode) ){
 -      utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n",
 -                  "imposter");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){
 -      utf8_printf(stderr, "Usage: .imposter INDEX IMPOSTER\n"
 -                          "       .imposter off\n");
 -      /* Also allowed, but not documented:
 -      **
 -      **    .imposter TABLE IMPOSTER
 -      **
 -      ** where TABLE is a WITHOUT ROWID table.  In that case, the
 -      ** imposter is another WITHOUT ROWID table with the columns in
 -      ** storage order. */
 -      rc = 1;
 -      goto meta_command_exit;
 +      appendText(&sSelect, " AND ", 0);
 +      release_holder();
      }
 -    open_db(p, 0);
 -    if( nArg==2 ){
 -      sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 1);
 -      goto meta_command_exit;
 -    }
 -    zSql = sqlite3_mprintf(
 -      "SELECT rootpage, 0 FROM sqlite_schema"
 -      " WHERE name='%q' AND type='index'"
 -      "UNION ALL "
 -      "SELECT rootpage, 1 FROM sqlite_schema"
 -      " WHERE name='%q' AND type='table'"
 -      "   AND sql LIKE '%%without%%rowid%%'",
 -      azArg[1], azArg[1]
 -    );
 -    sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    sqlite3_free(zSql);
 -    if( sqlite3_step(pStmt)==SQLITE_ROW ){
 -      tnum = sqlite3_column_int(pStmt, 0);
 -      isWO = sqlite3_column_int(pStmt, 1);
 +    if( bNoSystemTabs ){
 +      appendText(&sSelect, "name NOT LIKE 'sqlite_%%' AND ", 0);
      }
 -    sqlite3_finalize(pStmt);
 -    zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]);
 -    rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    sqlite3_free(zSql);
 -    i = 0;
 -    while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 -      char zLabel[20];
 -      const char *zCol = (const char*)sqlite3_column_text(pStmt,2);
 -      i++;
 -      if( zCol==0 ){
 -        if( sqlite3_column_int(pStmt,1)==-1 ){
 -          zCol = "_ROWID_";
 -        }else{
 -          sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i);
 -          zCol = zLabel;
 -        }
 -      }
 -      if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){
 -        lenPK = (int)strlen(zCollist);
 -      }
 -      if( zCollist==0 ){
 -        zCollist = sqlite3_mprintf("\"%w\"", zCol);
 -      }else{
 -        zCollist = sqlite3_mprintf("%z,\"%w\"", zCollist, zCol);
 -      }
 +    appendText(&sSelect, "sql IS NOT NULL"
 +               " ORDER BY snum, rowid", 0);
 +    if( bDebug ){
 +      utf8_printf(psi->out, "SQL: %s;\n", sSelect.z);
 +    }else{
 +      rc = sqlite3_exec(p->dbUser, sSelect.z, callback, p, &zErrMsg);
      }
 -    sqlite3_finalize(pStmt);
 -    if( i==0 || tnum==0 ){
 -      utf8_printf(stderr, "no such index: \"%s\"\n", azArg[1]);
 -      rc = 1;
 -      sqlite3_free(zCollist);
 -      goto meta_command_exit;
 -    }
 -    if( lenPK==0 ) lenPK = 100000;
 -    zSql = sqlite3_mprintf(
 -          "CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))WITHOUT ROWID",
 -          azArg[2], zCollist, lenPK, zCollist);
 -    sqlite3_free(zCollist);
 -    rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 1, tnum);
 -    if( rc==SQLITE_OK ){
 -      rc = sqlite3_exec(p->db, zSql, 0, 0, 0);
 -      sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0);
 -      if( rc ){
 -        utf8_printf(stderr, "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db));
 -      }else{
 -        utf8_printf(stdout, "%s;\n", zSql);
 -        raw_printf(stdout,
 -          "WARNING: writing to an imposter table will corrupt the \"%s\" %s!\n",
 -          azArg[1], isWO ? "table" : "index"
 -        );
 -      }
 +  }
 +  RESOURCE_FREE(mark);
 +  if( zErrMsg ){
 +    *pzErr = zErrMsg;
 +    return DCR_Error;
 +  }else if( rc != SQLITE_OK ){
 +    *pzErr = smprintf("Error: querying schema information\n");
 +    return DCR_Error;
 +  }else{
 +    return DCR_Ok;
 +  }
 +}
 +
 +/*****************
 + * The .selecttrace, .separator, .session and .sha3sum commands
 + */
 +CONDITION_COMMAND( session defined(SQLITE_ENABLE_SESSION) );
 +COLLECT_HELP_TEXT[
 +  ".separator COL ?ROW?     Change the column and row separators",
 +  ".session ?NAME? CMD ...  Create or control sessions",
 +  "   Subcommands:",
 +  "     attach TABLE             Attach TABLE",
 +  "     changeset FILE           Write a changeset into FILE",
 +  "     close                    Close one session",
 +  "     enable ?BOOLEAN?         Set or query the enable bit",
 +  "     filter GLOB...           Reject tables matching GLOBs",
 +  "     indirect ?BOOLEAN?       Mark or query the indirect status",
 +  "     isempty                  Query whether the session is empty",
 +  "     list                     List currently open session names",
 +  "     open DB NAME             Open a new session on DB",
 +  "     patchset FILE            Write a patchset into FILE",
 +  "   If ?NAME? is omitted, the first defined session is used.",
 +  ".sha3sum ...             Compute a SHA3 hash of database content",
 +  "    Options:",
 +  "      --schema              Also hash the sqlite_schema table",
 +  "      --sha3-224            Use the sha3-224 algorithm",
 +  "      --sha3-256            Use the sha3-256 algorithm (default)",
 +  "      --sha3-384            Use the sha3-384 algorithm",
 +  "      --sha3-512            Use the sha3-512 algorithm",
 +  "    Any other argument is a LIKE pattern for tables to hash",
 +];
 +DISPATCHABLE_COMMAND( separator ? 2 3 ){
 +  if( nArg>=2 ){
 +    sqlite3_snprintf(sizeof(ISS(p)->colSeparator), ISS(p)->colSeparator,
 +                     "%.*s", (int)ArraySize(ISS(p)->colSeparator)-1, azArg[1]);
 +  }
 +  if( nArg>=3 ){
 +    sqlite3_snprintf(sizeof(ISS(p)->rowSeparator), ISS(p)->rowSeparator,
 +                     "%.*s", (int)ArraySize(ISS(p)->rowSeparator)-1, azArg[2]);
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( session 3 2 0 ){
 +  int rc = 0;
 +  struct AuxDb *pAuxDb = ISS(p)->pAuxDb;
 +  OpenSession *pSession = &pAuxDb->aSession[0];
 +  FILE *out = ISS(p)->out;
 +  char **azCmd = &azArg[1];
 +  int iSes = 0;
 +  int nCmd = nArg - 1;
 +  int i;
 +  open_db(p, 0);
 +  if( nArg>=3 ){
 +    for(iSes=0; iSes<pAuxDb->nSession; iSes++){
 +      if( cli_strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break;
 +    }
 +    if( iSes<pAuxDb->nSession ){
 +      pSession = &pAuxDb->aSession[iSes];
 +      azCmd++;
 +      nCmd--;
      }else{
 -      raw_printf(stderr, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
 -      rc = 1;
 +      pSession = &pAuxDb->aSession[0];
 +      iSes = 0;
      }
 -    sqlite3_free(zSql);
 -  }else
 -#endif /* !defined(SQLITE_OMIT_TEST_CONTROL) */
 +  }
  
 -#ifdef SQLITE_ENABLE_IOTRACE
 -  if( c=='i' && cli_strncmp(azArg[0], "iotrace", n)==0 ){
 -    SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...);
 -    if( iotrace && iotrace!=stdout ) fclose(iotrace);
 -    iotrace = 0;
 -    if( nArg<2 ){
 -      sqlite3IoTrace = 0;
 -    }else if( cli_strcmp(azArg[1], "-")==0 ){
 -      sqlite3IoTrace = iotracePrintf;
 -      iotrace = stdout;
 +  /* .session attach TABLE
 +  ** Invoke the sqlite3session_attach() interface to attach a particular
 +  ** table so that it is never filtered.
 +  */
 +  if( cli_strcmp(azCmd[0],"attach")==0 ){
 +    if( nCmd!=2 ) goto session_syntax_error;
 +    if( pSession->p==0 ){
 +    session_not_open:
 +      raw_printf(STD_ERR, "ERROR: No sessions are open\n");
      }else{
 -      iotrace = fopen(azArg[1], "w");
 -      if( iotrace==0 ){
 -        utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]);
 -        sqlite3IoTrace = 0;
 -        rc = 1;
 -      }else{
 -        sqlite3IoTrace = iotracePrintf;
 +      rc = sqlite3session_attach(pSession->p, azCmd[1]);
 +      if( rc ){
 +        raw_printf(STD_ERR, "ERROR: sqlite3session_attach() returns %d\n", rc);
 +        rc = 0;
        }
      }
    }else
@@@ -15471,549 -11431,209 +15500,548 @@@ static int line_is_complete(char *zSql
  }
  
  /*
 -** Run a single line of SQL.  Return the number of errors.
 +** Run a single line of SQL.  Return the number of errors.
 +** bAltIn indicates that input has been redirected in some way.
 +*/
 +static int runOneSqlLine(ShellExState *psx, char *zSql,
 +                         int bAltIn, int startline){
 +  int rc;
 +  char *zErrMsg = 0;
 +  RipStackDest sqlRipDest = RIP_STACK_DEST_INIT;
 +
 +  open_db(psx, 0);
 +  sstr_ptr_holder(&zErrMsg);
 +  if( ShellHasFlag(psx,SHFLG_Backslash) ) resolve_backslashes(zSql);
 +  if( ISS(psx)->flgProgress & SHELL_PROGRESS_RESET ) ISS(psx)->nProgress = 0;
 +  BEGIN_TIMER;
 +  register_exit_ripper(&sqlRipDest);
 +  if( 0==RIP_TO_HERE(sqlRipDest) ){
 +    rc = shell_exec(psx, zSql, &zErrMsg);
 +    forget_exit_ripper(&sqlRipDest);
 +  }else{
 +    psx->shellAbruptExit = 0x102;
 +    rc = 1;
 +  }
 +  END_TIMER;
 +  if( rc || zErrMsg ){
 +    char zPrefix[100];
 +    const char *zErrorTail;
 +    const char *zErrorType;
 +    if( psx->shellAbruptExit==0 ){
 +      if( zErrMsg==0 ){
 +        zErrorType = "Error";
 +        zErrorTail = sqlite3_errmsg(DBX(psx));
 +      }else if( cli_strncmp(zErrMsg, "in prepare, ",12)==0 ){
 +        zErrorType = "Parse error";
 +        zErrorTail = &zErrMsg[12];
 +      }else if( cli_strncmp(zErrMsg, "stepping, ", 10)==0 ){
 +        zErrorType = "Runtime error";
 +        zErrorTail = &zErrMsg[10];
 +      }else{
 +        zErrorType = "Error";
 +        zErrorTail = zErrMsg;
 +      }
 +      if( bAltIn || !stdin_is_interactive ){
 +        sqlite3_snprintf(sizeof(zPrefix), zPrefix,
 +                         "%s near line %d:", zErrorType, startline);
 +      }else{
 +        sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType);
 +      }
 +      utf8_printf(STD_ERR, "%s %s\n", zPrefix, zErrorTail);
 +    }
 +    rc = 1;
 +  }else if( ShellHasFlag(psx, SHFLG_CountChanges) ){
 +    char zLineBuf[36+2*20];
 +    sqlite3_snprintf(sizeof(zLineBuf), zLineBuf,
 +            "changes: %lld   total_changes: %lld",
 +            sqlite3_changes64(DBX(psx)), sqlite3_total_changes64(DBX(psx)));
 +    raw_printf(ISS(psx)->out, "%s\n", zLineBuf);
 +  }
 +  release_holder();
 +  return rc;
 +}
 +
 +#if SHELL_EXTENDED_PARSING
 +/* Resumable line classsifier for dot-commands
 +**
 +** Determines if a dot-command is open, having either an unclosed
 +** quoted argument or an escape sequence opener ('\') at its end.
 +**
 +** The FSM design/behavior assumes/requires that a terminating '\'
 +** is not part of the character sequence being classified -- that
 +** it represents an escaped newline which is removed as physical
 +** lines are spliced to accumulate logical lines.
 +**
 +** The line or added line-portion is passed as zCmd.
 +** The pScanState pointer must reference an (opaque) DCmd_ScanState,
 +** which must be set to DCSS_Start to initialize the scanner state.
 +** Resumed scanning should always be done with zCmd logically just
 +** past the last non-0 char of the text previously passed in, with
 +** any previously scanned, trailing newline escape first trimmed.
 +** Returns are: 0 => not open (aka complete), 1 => is open (incomplete)
 +** The following macros may be applied to the scan state:
 +*/
 +#define DCSS_InDarkArg(dcss) (((dcss)&argPosMask)==inDqArg)
 +#define DCSS_EndEscaped(dcss) (((dcss)&endEscaped)!=0)
 +#define DCSS_IsOpen(dcss) (((dcss)&isOpenMask)!=0)
 +typedef enum {
 +  DCSS_Start = 0,
 +  twixtArgs = 0, inSqArg = 1, inDarkArg = 2, inDqArg = 3, /* ordered */
 +  endEscaped = 4, /* bit used */
 +  argPosMask = 3, /* bits used */
 +  isOpenMask = 1|4 /* bit test */
 +} DCmd_ScanState;
 +
 +static void dot_command_scan(char *zCmd, DCmd_ScanState *pScanState,
 +                             SCAN_TRACKER_REFTYPE pst){
 +  DCmd_ScanState ss = *pScanState & ~endEscaped;
 +  char c = (ss&isOpenMask)? 1 : *zCmd++;
 +  while( c!=0 ){
 +    switch( ss ){
 +    case twixtArgs:
 +      CONTINUE_PROMPT_AWAITC(pst, 0);
 +      while( IsSpace(c) ){
 +        if( (c=*zCmd++)==0 ) goto atEnd;
 +      }
 +      switch( c ){
 +      case '\\':
 +        if( *zCmd==0 ){
 +          ss |= endEscaped;
 +          goto atEnd;
 +        }else goto inDark;
 +      case '\'': ss = inSqArg; goto inSq;
 +      case '"': ss = inDqArg; goto inDq;
 +      default: ss = inDarkArg; goto inDark;
 +      }
 +    inSq:
 +    case inSqArg:
 +      CONTINUE_PROMPT_AWAITC(pst, '\'');
 +      while( (c=*zCmd++)!='\'' ){
 +        if( c==0 ) goto atEnd;
 +        if( c=='\\' && *zCmd==0 ){
 +          ss |= endEscaped;
 +          goto atEnd;
 +        }
 +      }
 +      ss = twixtArgs;
 +      c = *zCmd++;
 +      continue;
 +    inDq:
 +    case inDqArg:
 +      CONTINUE_PROMPT_AWAITC(pst, '"');
 +      do {
 +        if( (c=*zCmd++)==0 ) goto atEnd;
 +        if( c=='\\' ){
 +          if( (c=*zCmd++)==0 ){
 +            ss |= endEscaped;
 +            goto atEnd;
 +          }
 +          if( (c=*zCmd++)==0 ) goto atEnd;
 +        }
 +      } while( c!='"' );
 +      ss = twixtArgs;
 +      c = *zCmd++;
 +      continue;
 +    inDark:
 +    case inDarkArg:
 +      CONTINUE_PROMPT_AWAITC(pst, 0);
 +      while( !IsSpace(c) ){
 +        if( c=='\\' && *zCmd==0 ){
 +          ss |= endEscaped;
 +          goto atEnd;
 +        }
 +        if( (c=*zCmd++)==0 ) goto atEnd;
 +      }
 +      ss = twixtArgs;
 +      c = *zCmd++;
 +      continue;
 +    case endEscaped: case isOpenMask: default:
 +      ; /* Not reachable, but quiet compilers unable to see this. */
 +    }
 +  }
 + atEnd:
 +  *pScanState = ss;
 +}
 +#else
 +# define dot_command_scan(x,y,z)
 +#endif
 +
 +/* Utility functions for process_input. */
 +
 +#if SHELL_EXTENDED_PARSING
 +/*
 +** Process dot-command line with its scan state to:
 +** 1. Setup for requested line-splicing; and
 +** 2. Say whether it is complete.
 +** The last two out parameters are the line's length, which may be
 +** adjusted, and the char to be used for joining a subsequent line.
 +** This is broken out of process_input() mainly for readability.
 +** The return is TRUE for dot-command ready to run, else false.
  */
 -static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){
 -  int rc;
 -  char *zErrMsg = 0;
 -
 -  open_db(p, 0);
 -  if( ShellHasFlag(p,SHFLG_Backslash) ) resolve_backslashes(zSql);
 -  if( p->flgProgress & SHELL_PROGRESS_RESET ) p->nProgress = 0;
 -  BEGIN_TIMER;
 -  rc = shell_exec(p, zSql, &zErrMsg);
 -  END_TIMER;
 -  if( rc || zErrMsg ){
 -    char zPrefix[100];
 -    const char *zErrorTail;
 -    const char *zErrorType;
 -    if( zErrMsg==0 ){
 -      zErrorType = "Error";
 -      zErrorTail = sqlite3_errmsg(p->db);
 -    }else if( cli_strncmp(zErrMsg, "in prepare, ",12)==0 ){
 -      zErrorType = "Parse error";
 -      zErrorTail = &zErrMsg[12];
 -    }else if( cli_strncmp(zErrMsg, "stepping, ", 10)==0 ){
 -      zErrorType = "Runtime error";
 -      zErrorTail = &zErrMsg[10];
 -    }else{
 -      zErrorType = "Error";
 -      zErrorTail = zErrMsg;
 -    }
 -    if( in!=0 || !stdin_is_interactive ){
 -      sqlite3_snprintf(sizeof(zPrefix), zPrefix,
 -                       "%s near line %d:", zErrorType, startline);
 -    }else{
 -      sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType);
 -    }
 -    utf8_printf(stderr, "%s %s\n", zPrefix, zErrorTail);
 -    sqlite3_free(zErrMsg);
 -    zErrMsg = 0;
 -    return 1;
 -  }else if( ShellHasFlag(p, SHFLG_CountChanges) ){
 -    char zLineBuf[2000];
 -    sqlite3_snprintf(sizeof(zLineBuf), zLineBuf,
 -            "changes: %lld   total_changes: %lld",
 -            sqlite3_changes64(p->db), sqlite3_total_changes64(p->db));
 -    raw_printf(p->out, "%s\n", zLineBuf);
 +static int line_join_done(DCmd_ScanState dcss, char *zLine,
 +                          i64 *pnLength, char *pcLE){
 +  /* It is ready only if has no open argument or escaped newline. */
 +  int bOpen = DCSS_IsOpen(dcss);
 +  if( !DCSS_EndEscaped(dcss) ){
 +    *pcLE = '\n';
 +    return !bOpen;
 +  }else{
 +    *pcLE = (bOpen || DCSS_InDarkArg(dcss))? 0 : ' ';
 +    /* Swallow the trailing escape character. */
 +    zLine[--*pnLength] = 0;
 +    return 0;
    }
 -  return 0;
 -}
 -
 -static void echo_group_input(ShellState *p, const char *zDo){
 -  if( ShellHasFlag(p, SHFLG_Echo) ) utf8_printf(p->out, "%s\n", zDo);
  }
 +#endif
  
 -#ifdef SQLITE_SHELL_FIDDLE
  /*
 -** Alternate one_input_line() impl for wasm mode. This is not in the primary
 -** impl because we need the global shellState and cannot access it from that
 -** function without moving lots of code around (creating a larger/messier diff).
 +** Grow the accumulation line buffer to accommodate ncNeed chars.
 +** In/out parameters pz and pna reference the buffer and its size.
 +** The buffer must eventually be sqlite3_free()'ed by the caller.
  */
 -static char *one_input_line(FILE *in, char *zPrior, int isContinuation){
 -  /* Parse the next line from shellState.wasm.zInput. */
 -  const char *zBegin = shellState.wasm.zPos;
 -  const char *z = zBegin;
 -  char *zLine = 0;
 -  i64 nZ = 0;
 +static void grow_line_buffer(char **pz, i64 *pna, int ncNeed){
  
 -  UNUSED_PARAMETER(in);
 -  UNUSED_PARAMETER(isContinuation);
 -  if(!z || !*z){
 -    return 0;
 +  if( ncNeed > *pna ){
 +    *pna += *pna + (*pna>>1) + 100;
 +    *pz = sqlite3_realloc(*pz, *pna);
 +    shell_check_ooms(*pz);
    }
 -  while(*z && isspace(*z)) ++z;
 -  zBegin = z;
 -  for(; *z && '\n'!=*z; ++nZ, ++z){}
 -  if(nZ>0 && '\r'==zBegin[nZ-1]){
 -    --nZ;
 -  }
 -  shellState.wasm.zPos = z;
 -  zLine = realloc(zPrior, nZ+1);
 -  shell_check_oom(zLine);
 -  memcpy(zLine, zBegin, nZ);
 -  zLine[nZ] = 0;
 -  return zLine;
  }
 -#endif /* SQLITE_SHELL_FIDDLE */
  
  /*
 -** Read input from *in and process it.  If *in==0 then input
 -** is interactive - the user is typing it it.  Otherwise, input
 -** is coming from a file or device.  A prompt is issued and history
 -** is saved only if input is interactive.  An interrupt signal will
 -** cause this routine to exit immediately, unless input is interactive.
 +** Read input from designated source (p->pInSource) and process it.
 +** If pInSource==0 then input is interactive - the user is typing it.
 +** Otherwise, input is coming from a file, stream device or string.
 +** Prompts issue and history is saved only for interactive input.
 +** An interrupt signal will cause this routine to exit immediately,
 +** with "exit demanded" code returned, unless input is interactive.
  **
 -** Return the number of errors.
 -*/
 -static int process_input(ShellState *p){
 -  char *zLine = 0;          /* A single input line */
 -  char *zSql = 0;           /* Accumulated SQL text */
 -  i64 nLine;                /* Length of current line */
 -  i64 nSql = 0;             /* Bytes of zSql[] used */
 -  i64 nAlloc = 0;           /* Allocated zSql[] space */
 -  int rc;                   /* Error code */
 -  int errCnt = 0;           /* Number of errors seen */
 -  i64 startline = 0;        /* Line number for start of current input */
 -  QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */
 -
 -  if( p->inputNesting==MAX_INPUT_NESTING ){
 -    /* This will be more informative in a later version. */
 -    utf8_printf(stderr,"Input nesting limit (%d) reached at line %d."
 -                " Check recursion.\n", MAX_INPUT_NESTING, p->lineno);
 -    return 1;
 -  }
 -  ++p->inputNesting;
 -  p->lineno = 0;
 -  CONTINUE_PROMPT_RESET;
 -  while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){
 -    fflush(p->out);
 -    zLine = one_input_line(p->in, zLine, nSql>0);
 -    if( zLine==0 ){
 -      /* End of input */
 -      if( p->in==0 && stdin_is_interactive ) printf("\n");
 -      break;
 -    }
 -    if( seenInterrupt ){
 -      if( p->in!=0 ) break;
 -      seenInterrupt = 0;
 -    }
 -    p->lineno++;
 -    if( QSS_INPLAIN(qss)
 -        && line_is_command_terminator(zLine)
 -        && line_is_complete(zSql, nSql) ){
 -      memcpy(zLine,";",2);
 -    }
 -    qss = quickscan(zLine, qss, CONTINUE_PROMPT_PSTATE);
 -    if( QSS_PLAINWHITE(qss) && nSql==0 ){
 -      /* Just swallow single-line whitespace */
 -      echo_group_input(p, zLine);
 -      qss = QSS_Start;
 -      continue;
 -    }
 -    if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){
 -      CONTINUE_PROMPT_RESET;
 -      echo_group_input(p, zLine);
 -      if( zLine[0]=='.' ){
 -        rc = do_meta_command(zLine, p);
 -        if( rc==2 ){ /* exit requested */
 +** Returns are the post-execute values of enum DotCmdRC:
 +**   DCR_Ok, DCR_Return, DCR_Exit, DCR_Abort
 +** each of which may be bit-wise or'ed with DCR_Error.
 +*/
 +static DotCmdRC process_input(ShellInState *psi){
 +  char *zLineInput = 0;  /* a line-at-a-time input buffer or usable result */
 +  char *zLineAccum = 0;  /* accumulation buffer, used for multi-line input */
 +  /* Above two pointers could be local to the group handling loop, but are
 +   * not so that the number of memory allocations can be reduced. They are
 +   * reused from one incoming group to another, realloc()'ed as needed. */
 +  i64 naAccum = 0;       /* tracking how big zLineAccum buffer has become */
-   /* Some flags for ending the overall group processing loop, always 1 or 0 */
-   u8 bInputEnd=0, bInterrupted=0;
++  /* Flag for ending the overall group processing loop, always 1 or 0 */
++  u8 bInputEnd = 0;
 +  /* Termination kind: DCR_Ok, DCR_Error, DCR_Return, DCR_Exit, DCR_Abort,
 +   * the greatest of whichever is applicable */
 +  u8 termKind = (XSS(psi)->shellAbruptExit==0)? DCR_Ok : DCR_Exit;
 +  /* Flag to indicate input from shell invocation argument. */
 +  u8 bInvokeArg = INSOURCE_IS_INVOKEARG(psi->pInSource);
 +  /* Flag to affect prompting and interrupt action */
 +  u8 bInteractive = INSOURCE_IS_INTERACTIVE(psi->pInSource);
 +  int nErrors = 0;       /* count of errors during execution or its prep */
 +
 +  /* Block overly-recursive or absurdly nested input redirects. */
 +  if( psi->inputNesting>=MAX_INPUT_NESTING ){
 +    InSource *pInSrc = psi->pInSource->pFrom;
 +    const char *zLead = "Input nesting limit ("
 +      SHELL_STRINGIFY(MAX_INPUT_NESTING)") reached,";
 +    int i = 3;
 +    assert(pInSrc!=0 && MAX_INPUT_NESTING>0);
 +    while( i-->0 && pInSrc!=0 ){
 +      utf8_printf(STD_ERR,
 +                  "%s from line %d of \"%s\"",
 +                  zLead, pInSrc->lineno, pInSrc->zSourceSay);
 +      zLead = (i%2==0)? "\n" : "";
 +      pInSrc=pInSrc->pFrom;
 +    }
 +    utf8_printf(STD_ERR, " ...\nError: Check recursion.\n");
 +    return DCR_Error;
 +  }
 +  ++psi->inputNesting;
 +
 +  /* line-group processing loop (per SQL block, dot-command or comment) */
-   while( !bInputEnd && termKind==DCR_Ok && !bInterrupted ){
++  while( !bInputEnd && termKind==DCR_Ok ){
 +#if SHELL_DYNAMIC_EXTENSION
 +    ScriptSupport *pSS = psi->script;
 +#endif
 +    int nGroupLines = 0;  /* count of lines belonging to this group */
 +    i64 ncLineIn = 0;     /* how many (non-zero) chars are in zLineInput  */
 +    i64 ncLineAcc = 0;    /* how many (non-zero) chars are in zLineAccum  */
 +    i64 iLastLine = 0;    /* index of last accumulated line start */
 +    /* Initialize resumable scanner(s). */
 +    SqlScanState sqScanState = SSS_Start; /* for SQL scan */
 +#if SHELL_EXTENDED_PARSING
 +    DCmd_ScanState dcScanState = DCSS_Start;  /* for dot-command scan */
 +    int nLeadWhite = 0; /* skips over initial whitespace to . or # */
 +    char cLineEnd = '\n'; /* May be swallowed or replaced with space. */
 +#else
 +# define nLeadWhite 0     /* For legacy parsing, no white before . or # . */
 +# define cLineEnd '\n'    /* For legacy parsing, this always joins lines. */
 +#endif
 +    /* An ordered enum to record kind of incoming line group. Its ordering
 +     * means than a value greater than Comment implies something runnable.
 +     */
 +    enum { Tbd = 0, Eof, Comment, Sql, Cmd
 +#if SHELL_DYNAMIC_EXTENSION
 +      , Script
 +#endif
 +    } inKind = Tbd;
 +    /* An enum signifying the group disposition state */
 +    enum {
 +      Incoming, Runnable, Dumpable, Erroneous, Ignore
 +    } disposition = Incoming;
 +    char **pzLineUse = &zLineInput;   /* ref line to be processed */
 +    i64 *pncLineUse = &ncLineIn;      /* ref that line's char count */
 +    int iStartline = 0;               /* starting line number of group */
 +
 +    seenInterrupt = 0;
 +    fflush(psi->out);
 +    CONTINUE_PROMPT_RESET;
 +    zLineInput = one_input_line(psi->pInSource, zLineInput,
 +                                nGroupLines>0, &shellPrompts);
 +    if( zLineInput==0 ){
 +      bInputEnd = 1;
 +      inKind = Eof;
 +      disposition = Ignore;
 +      if( bInteractive ) printf("\n");
 +    }else{
 +      ++nGroupLines;
 +      iStartline = psi->pInSource->lineno;
 +      ncLineIn = strlen30(zLineInput);
 +      if( seenInterrupt ){
-         if( psi->pInSource!=0 ) break;
-         bInterrupted = 1; /* This will be honored, or not, later. */
 +        seenInterrupt = 0;
 +        disposition = Dumpable;
-       }
-       /* Classify and check for single-line dispositions, prep for more. */
++      }else{
++        /* Classify and check for single-line dispositions, prep for more. */
 +#if SHELL_EXTENDED_PARSING
-       nLeadWhite = (SHEXT_PARSING(psi))
-         ? skipWhite(zLineInput)-zLineInput
-         : 0; /* Disallow leading whitespace for . or # in legacy mode. */
++        nLeadWhite = (SHEXT_PARSING(psi))
++          ? skipWhite(zLineInput)-zLineInput
++          : 0; /* Disallow leading whitespace for . or # in legacy mode. */
 +#endif
 +#if SHELL_DYNAMIC_EXTENSION
-       if( pSS && pSS->pMethods->isScriptLeader(pSS, zLineInput+nLeadWhite) ){
-         inKind = Script;
-       }else
++        if( pSS && pSS->pMethods->isScriptLeader(pSS, zLineInput+nLeadWhite) ){
++          inKind = Script;
++        }else
 +#endif
-       {
-         switch( zLineInput[nLeadWhite] ){
-         case '.':
-           inKind = Cmd;
-           dot_command_scan(zLineInput+nLeadWhite, &dcScanState,
-                            CONTINUE_PROMPT_PSTATE);
-           break;
-         case '#':
-           inKind = Comment;
-           break;
-         default:
-           /* Might be SQL, or a swallowable whole SQL comment. */
-           sql_prescan(zLineInput, &sqScanState, CONTINUE_PROMPT_PSTATE);
-           if( SSS_PLAINWHITE(sqScanState) ){
-             /* It's either all blank or a whole SQL comment. Swallowable. */
++        {
++          switch( zLineInput[nLeadWhite] ){
++          case '.':
++            inKind = Cmd;
++            dot_command_scan(zLineInput+nLeadWhite, &dcScanState,
++                             CONTINUE_PROMPT_PSTATE);
++            break;
++          case '#':
 +            inKind = Comment;
-           }else{
-             /* Something dark, not a # comment or dot-command. Must be SQL. */
-             inKind = Sql;
++            break;
++          default:
++            /* Might be SQL, or a swallowable whole SQL comment. */
++            sql_prescan(zLineInput, &sqScanState, CONTINUE_PROMPT_PSTATE);
++            if( SSS_PLAINWHITE(sqScanState) ){
++              /* It's either all blank or a whole SQL comment. Swallowable. */
++              inKind = Comment;
++            }else{
++              /* Something dark, not a # comment or dot-command. Must be SQL. */
++              inKind = Sql;
++            }
++            break;
 +          }
-           break;
 +        }
 +      }
 +    } /* end read/classify initial group input line */
 +
 +    /* Here, if not at end of input, the initial line of group is in, and
 +     * it has been scanned and classified. Next, do the processing needed
 +     * to recognize whether the initial line or accumulated group so far
 +     * is complete such that it may be run, and perform joining of more
 +     * lines into the group while it is not so complete. This loop ends
 +     * with the input group line(s) ready to be run, or if the input ends
 +     * before it is ready, with the group marked as erroneous.
 +     */
 +    while( disposition==Incoming ){
 +      PROMPTS_UPDATE(inKind == Sql || inKind == Cmd);
 +      /* Check whether more to accumulate, or ready for final disposition. */
 +      switch( inKind ){
 +      case Comment:
 +        disposition = Dumpable;
 +      case Cmd:
 +#if SHELL_EXTENDED_PARSING
 +        if( SHEXT_PARSING(psi) ){
 +          if( line_join_done(dcScanState, *pzLineUse, pncLineUse, &cLineEnd) ){
 +            disposition = Runnable;
 +          }
 +        }else
 +#endif
 +          disposition = Runnable; /* Legacy, any dot-command line is ready. */
 +        break;
 +#if SHELL_DYNAMIC_EXTENSION
 +      case Script:
 +        if( pSS==0
 +            || pSS->pMethods->scriptIsComplete(pSS, *pzLineUse+nLeadWhite, 0) ){
 +          disposition = Runnable;
 +        }
 +        break;
 +#endif
 +      case Sql:
 +        /* Check to see if it is complete and ready to run. */
 +        if( SSS_SEMITERM(sqScanState) && 1==sqlite3_complete(*pzLineUse)){
 +          disposition = Runnable;
 +        }else if( SSS_PLAINWHITE(sqScanState) ){
 +          /* It is a leading single-line or multi-line comment. */
 +          disposition = Runnable;
 +          inKind = Comment;
 +        }else{
 +          char *zT = line_is_command_terminator(zLineInput);
 +          if( zT!=0  ){
 +            /* Last line is a lone go or / -- prep for running it. */
 +            if( nGroupLines>1 ){
 +              disposition = Runnable;
 +              memcpy(*pzLineUse+iLastLine,";\n",3);
 +              *pncLineUse = iLastLine + 2;
 +            }else{
 +              /* Unless nothing preceded it, then dump it. */
 +              disposition = Dumpable;
 +            }
 +          }
 +        }
 +        break;
 +      case Tbd: case Eof: default: assert(0); /* Not reachable */
 +      } /* end switch on inKind */
 +      /* Collect and accumulate more input if group not yet complete. */
 +      if( disposition==Incoming ){
 +        if( nGroupLines==1 ){
 +          grow_line_buffer(&zLineAccum, &naAccum, ncLineIn+2);
 +          /* Copy line just input */
 +          memcpy(zLineAccum, zLineInput, ncLineIn);
 +          zLineAccum[ncLineIn] = 0;
 +          ncLineAcc = ncLineIn;
 +          pzLineUse = &zLineAccum;
 +          pncLineUse = &ncLineAcc;
 +        }
 +        /* Read in next line of group, (if available.) */
 +        zLineInput = one_input_line(psi->pInSource, zLineInput,
 +                                    nGroupLines>0, &shellPrompts);
 +        if( zLineInput==0 ){
 +          bInputEnd = 1;
 +          if( inKind==Sql && bInvokeArg ){
 +            /* As a special dispensation, SQL arguments on the command line
 +            ** do not need to end with ';' (or a lone go.) */
 +            if( nGroupLines>1 ){
 +              grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+2);
 +            }
 +            strcpy( zLineAccum+ncLineAcc, ";" );
 +            if( 1==sqlite3_complete(*pzLineUse) ){
 +              zLineAccum[ncLineAcc] = 0;
 +              disposition = Runnable;
 +              continue;
 +            }
 +          }
 +          disposition = Erroneous;
 +          inKind = Eof;
 +          if( bInteractive ) printf("\n");
 +          continue;
 +        }
 +        ++nGroupLines;
 +        ncLineIn = strlen30(zLineInput);
 +        /* Scan line just input (if needed) and append to accumulation. */
 +        switch( inKind ){
 +        case Cmd:
 +          dot_command_scan(zLineInput, &dcScanState, CONTINUE_PROMPT_PSTATE);
 +          break;
 +        case Sql:
 +          sql_prescan(zLineInput, &sqScanState, CONTINUE_PROMPT_PSTATE);
 +          break;
 +        default:
            break;
 -        }else if( rc ){
 -          errCnt++;
          }
 +        grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+ncLineIn+2);
 +        /* Join lines as setup by exam of previous line(s). */
 +        if( cLineEnd!=0 ) zLineAccum[ncLineAcc++] = cLineEnd;
 +#if SHELL_EXTENDED_PARSING
 +        cLineEnd = '\n'; /* reset to default after use */
 +#endif
 +        memcpy(zLineAccum+ncLineAcc, zLineInput, ncLineIn);
 +        iLastLine = ncLineAcc;
 +        ncLineAcc += ncLineIn;
 +        zLineAccum[ncLineAcc] = 0;
 +      } /* end glom another line */
 +    } /* end group collection loop */
 +    /* Here, the group is fully collected or known to be incomplete forever. */
 +    CONTINUE_PROMPT_RESET;
 +    switch( disposition ){
 +    case Dumpable:
 +      echo_group_input(psi, *pzLineUse);
 +#if SHELL_DYNAMIC_EXTENSION
 +      if( inKind==Script && pSS!=0 ) pSS->pMethods->resetCompletionScan(pSS);
 +#endif
 +      break;
 +    case Runnable:
 +      switch( inKind ){
 +      case Sql:
 +        echo_group_input(psi, *pzLineUse);
 +        nErrors += runOneSqlLine(XSS(psi), *pzLineUse,
 +                                 INSOURCE_IS_INTERACTIVE(psi->pInSource),
 +                                 iStartline);
 +        break;
 +      case Cmd: {
 +        DotCmdRC dcr;
 +        echo_group_input(psi, *pzLineUse);
 +        dcr = do_dot_command(*pzLineUse+nLeadWhite, XSS(psi));
 +        nErrors += (dcr & DCR_Error);
 +        dcr &= ~DCR_Error;
 +        if( dcr > termKind ) termKind = dcr;
 +        break;
        }
 -      qss = QSS_Start;
 -      continue;
 -    }
 -    /* No single-line dispositions remain; accumulate line(s). */
 -    nLine = strlen(zLine);
 -    if( nSql+nLine+2>=nAlloc ){
 -      /* Grow buffer by half-again increments when big. */
 -      nAlloc = nSql+(nSql>>1)+nLine+100;
 -      zSql = realloc(zSql, nAlloc);
 -      shell_check_oom(zSql);
 -    }
 -    if( nSql==0 ){
 -      i64 i;
 -      for(i=0; zLine[i] && IsSpace(zLine[i]); i++){}
 -      assert( nAlloc>0 && zSql!=0 );
 -      memcpy(zSql, zLine+i, nLine+1-i);
 -      startline = p->lineno;
 -      nSql = nLine-i;
 -    }else{
 -      zSql[nSql++] = '\n';
 -      memcpy(zSql+nSql, zLine, nLine+1);
 -      nSql += nLine;
 -    }
 -    if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){
 -      echo_group_input(p, zSql);
 -      errCnt += runOneSqlLine(p, zSql, p->in, startline);
 -      CONTINUE_PROMPT_RESET;
 -      nSql = 0;
 -      if( p->outCount ){
 -        output_reset(p);
 -        p->outCount = 0;
 -      }else{
 -        clearTempFile(p);
 +#if SHELL_DYNAMIC_EXTENSION
 +      case Script: {
 +        char *zErr = 0;
 +        DotCmdRC dcr;
 +        assert(pSS!=0);
 +        /* Consider: Should echo flag be honored here? */
 +        pSS->pMethods->resetCompletionScan(pSS);
 +        dcr = pSS->pMethods->runScript(pSS, *pzLineUse+nLeadWhite,
 +                                       XSS(psi), &zErr);
 +        if( dcr!=DCR_Ok || zErr!=0 ){
 +          /* Future: Handle errors more informatively and like dot commands. */
 +          nErrors += (dcr!=DCR_Ok);
 +          if( zErr!=0 ){
 +            utf8_printf(STD_ERR, "Error: %s\n", zErr);
 +            sqlite3_free(zErr);
 +          }
 +        }
 +        break;
        }
 -      p->bSafeMode = p->bSafeModePersist;
 -      qss = QSS_Start;
 -    }else if( nSql && QSS_PLAINWHITE(qss) ){
 -      echo_group_input(p, zSql);
 -      nSql = 0;
 -      qss = QSS_Start;
 +#endif
 +      default:
 +        assert(inKind!=Tbd);
 +        break;
 +      }
 +      if( XSS(psi)->shellAbruptExit!=0 ){
 +        termKind = DCR_Exit;
 +      }
 +      break;
 +    case Erroneous:
 +      utf8_printf(STD_ERR, "Error: Input incomplete at line %d of \"%s\"\n",
 +                  psi->pInSource->lineno, psi->pInSource->zSourceSay);
 +#if SHELL_DYNAMIC_EXTENSION
 +      if( inKind==Script && pSS!=0 ) pSS->pMethods->resetCompletionScan(pSS);
 +#endif
 +      ++nErrors;
 +      break;
 +    case Ignore:
 +      break;
 +    default: assert(0);
      }
 +    if( bail_on_error && nErrors>0 && termKind==DCR_Ok ) termKind = DCR_Error;
 +  } /* end group consume/prep/(run, dump or complain) loop */
 +
 +  /* Cleanup and determine return value based on flags and error count. */
 +  free(zLineInput); /* Allocated via malloc() by readline or equivalents. */
 +  sqlite3_free(zLineAccum);
 +
 +  /* Translate DCR_Return because it has been done here, not to propagate
 +   * unless input is from shell invocation argument. */
 +  if( termKind==DCR_Return && psi->pInSource!=&cmdInSource ){
 +    termKind = DCR_Ok;
    }
 -  if( nSql ){
 -    /* This may be incomplete. Let the SQL parser deal with that. */
 -    echo_group_input(p, zSql);
 -    errCnt += runOneSqlLine(p, zSql, p->in, startline);
 -    CONTINUE_PROMPT_RESET;
 -  }
 -  free(zSql);
 -  free(zLine);
 -  --p->inputNesting;
 -  return errCnt>0;
 +  return termKind|(nErrors>0);
  }
  
  /*
@@@ -16246,11 -11860,11 +16274,10 @@@ static void usage(int showDetail)
        "FILENAME is the name of an SQLite database. A new database is created\n"
        "if the file does not previously exist. Defaults to :memory:.\n", Argv0);
    if( showDetail ){
 -    utf8_printf(stderr, "OPTIONS include:\n%s", zOptions);
 +    utf8_printf(STD_ERR, "OPTIONS include:\n%s", zOptions);
    }else{
 -    raw_printf(stderr, "Use the -help option for additional information\n");
 +    raw_printf(STD_ERR, "Use the -help option for additional information\n");
    }
--  exit(1);
  }
  
  /*
@@@ -16264,97 -11878,28 +16291,222 @@@ static void verify_uninitialized(void)
    }
  }
  
++static void sayInterrupted(void){
++  if( seenInterrupt ){
++    fprintf(stderr, "SQLite CLI interrupted.\n");
++  }
++}
++
  /*
 -** Initialize the state information in data
 +** Initialize the state information in datai and datax.
 +** Does no heap allocation, so will do no OOM abort.
  */
 -static void main_init(ShellState *data) {
 -  memset(data, 0, sizeof(*data));
 -  data->normalMode = data->cMode = data->mode = MODE_List;
 -  data->autoExplain = 1;
 -  data->pAuxDb = &data->aAuxDb[0];
 -  memcpy(data->colSeparator,SEP_Column, 2);
 -  memcpy(data->rowSeparator,SEP_Row, 2);
 -  data->showHeader = 0;
 -  data->shellFlgs = SHFLG_Lookaside;
 -  sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data);
 -#if !defined(SQLITE_SHELL_FIDDLE)
 +static void main_init(ShellInState *pDatai, ShellExState *pDatax) {
 +  memset(pDatai, 0, sizeof(*pDatai));
 +  memset(pDatax, 0, sizeof(*pDatax));
 +  pDatax->sizeofThis = sizeof(*pDatax);
 +  pDatax->pSIS = pDatai;
 +  pDatai->pSXS = pDatax;
 +  pDatax->pShowHeader = &pDatai->showHeader;
 +  pDatax->zFieldSeparator = &pDatai->colSeparator[0];
 +  pDatax->zRecordSeparator = &pDatai->rowSeparator[0];
 +  pDatax->zNullValue = &pDatai->nullValue[0];
 +  pDatai->out = STD_OUT;
 +  pDatai->normalMode = pDatai->cMode = pDatai->mode = MODE_List;
 +  pDatai->autoExplain = 1;
 +  pDatai->pAuxDb = &pDatai->aAuxDb[0];
 +  memcpy(pDatai->colSeparator,SEP_Column, 2);
 +  memcpy(pDatai->rowSeparator,SEP_Row, 2);
 +  pDatai->showHeader = 0;
 +  pDatai->shellFlgs = SHFLG_Lookaside;
 +  sqlite3_config(SQLITE_CONFIG_LOG, shellLog, pDatai);
 +#ifndef SQLITE_SHELL_FIDDLE
    verify_uninitialized();
 +#else
 +  datai.zDefaultDbName = "/fiddle.sqlite3";
  #endif
    sqlite3_config(SQLITE_CONFIG_URI, 1);
 +  sqlite3_config(SQLITE_CONFIG_LOG, shellLog, pDatai);
    sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
    sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> ");
    sqlite3_snprintf(sizeof(continuePrompt), continuePrompt,"   ...> ");
 +  /* Source at EOF (for now), saying it is command line. */
 +  pDatai->pInSource = &cmdInSource;
 +}
 +
 +/*
 +** Takedown and deallocate data structures held in datai and datax.
 +** Does no heap allocation, so will do no OOM abort. It leaves those
 +** structs zero-initialized to aid valgrind memory leak reporting.
 +*/
 +static void main_cleanup(ShellInState *psi, ShellExState *psx) {
 +  int i;
 +  set_table_name(psx, 0);
 +  if( psx->dbUser ){
 +    session_close_all(psi, -1);
 +#if SHELL_DYNAMIC_EXTENSION
 +    notify_subscribers(psi, NK_DbAboutToClose, psx->dbUser);
 +#endif
 +    close_db(psx->dbUser);
 +  }
 +  for(i=0; i<ArraySize(psi->aAuxDb); i++){
 +    sqlite3_free(psi->aAuxDb[i].zFreeOnClose);
 +    if( psi->aAuxDb[i].db ){
 +      session_close_all(psi, i);
 +      close_db(psi->aAuxDb[i].db);
 +    }
 +  }
 +  find_home_dir(1);
 +  output_reset(psi);
 +  psi->doXdgOpen = 0;
 +  clearTempFile(psi);
 +  sqlite3_free(psi->zEditor);
 +  explain_data_delete(psi);
 +#if SHELL_DYNAMIC_EXTENSION
 +  notify_subscribers(psi, NK_DbAboutToClose, psx->dbShell);
 +  /* It is necessary that the shell DB be closed after the user DBs.
 +   * This is because loaded extensions are held by the shell DB and
 +   * are therefor (automatically) unloaded when it is closed. */
 +  notify_subscribers(psi, NK_ExtensionUnload, psx->dbShell);
 +  /* Forcefully unsubscribe any extension which ignored above or did
 +   * not unsubscribe upon getting above event. */
 +  unsubscribe_extensions(psi);
 +  /* This must be done before any extensions unload. */
 +  free_all_shext_tracking(psi);
 +  /* Unload extensions and free the DB used for dealing with them. */
 +  sqlite3_close(psx->dbShell);
 +  /* This notification can only reach statically linked extensions. */
 +  notify_subscribers(psi, NK_ShutdownImminent, 0);
 +  /* Forcefull unsubscribe static extension event listeners. */
 +  subscribe_events(psx, 0, psx, NK_Unsubscribe, 0);
 +#endif /* SHELL_DYNAMIC_EXTENSION */
 +  free(psx->pSpecWidths);
 +  free(psi->zNonce);
 +  for(i=0; i<psi->nSavedModes; ++i) sqlite3_free(psi->pModeStack[i]);
 +  /* Clear shell state objects so that valgrind detects real memory leaks. */
 +  memset(psi, 0, sizeof(*psi));
 +  memset(psx, 0, sizeof(*psx));
 +}
 +
++/* Setup or restore ^C interrupt handling. */
++static void interruption_alter(int setup_nrestore){
++#if defined(SIGINT)
++# if !defined(SHELL_LEGACY_SIGINT)
++  static struct sigaction cli_sigaction = { 0 };
++  static struct sigaction cli_sigrestore = { 0 };
++  if( setup_nrestore ){
++    /* Leave .sa_mask and .sa_flags zero-initialized. */
++    cli_sigaction.sa_handler = interrupt_handler;
++    sigaction(SIGINT,&cli_sigaction,&cli_sigrestore);
++  }else{
++    sigaction(SIGINT,&cli_sigrestore,&cli_sigaction);
++  }
++# else
++  static __sighandler_t cli_sigrestore = SIG_DFL;
++  if( setup_nrestore ){
++    cli_sigrestore = signal(SIGINT, interrupt_handler);
++  }else{
++    signal(SIGINT, cli_sigrestore);
++  }
++# endif
++#elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE)
++  BOOL add = setup_nrestore!=0;
++  BOOL success = SetConsoleCtrlHandler(ConsoleCtrlHandler, add);
++  if( add && !success ) fprintf(stderr, "No ^C handler.\n");
++#else
++  (void)setup_nrestore;
++#endif
++}
++
++/*
++** Prepare execution environment for running shell.
++** Return is SQLITE_OK on success, else an error code.
++*/
++static int execution_prepare(ShellInState *psi, ShellExState *psx){
++  /* Register the control-C (SIGINT) handler.
++  ** Make sure we have a valid signal handler early, before anything
++  ** is done that might take long. */
++  interruption_alter(1);
++  /* Ensure stderr is unbuffered. */
++  setvbuf(STD_ERR, 0, _IONBF, 0);
++#ifdef SQLITE_SHELL_FIDDLE
++  stdin_is_interactive = 0;
++  stdout_is_console = 1;
++#else
++  stdin_is_interactive = isatty(0);
++  stdout_is_console = isatty(1);
++#endif
++#if SHELL_WIN_UTF8_OPT
++  upon_terminate(console_restore);
++#endif
++  upon_terminate(sayInterrupted);
++
++#if !defined(_WIN32_WCE)
++  if( getenv("SQLITE_DEBUG_BREAK") ){
++    if( isatty(0) && isatty(2) ){
++      fprintf(STD_ERR,
++          "attach debugger to process %d and press any key to continue.\n",
++          GETPID());
++      fgetc(STD_IN);
++    }else{
++#if defined(_WIN32) || defined(WIN32)
++#if SQLITE_OS_WINRT
++      __debugbreak();
++#else
++      DebugBreak();
++#endif
++#elif defined(SIGTRAP)
++      raise(SIGTRAP);
++#endif
++    }
++  }
++#endif
++
++  /**** Data initialization. ****/
++  main_init(psi,psx);
++  init_std_inputs(stdin);
++#if SHELL_DATAIO_EXT
++  {
++    BuiltInFFExporter *pFFX
++      = (BuiltInFFExporter *)sqlite3_malloc(sizeof(BuiltInFFExporter));
++    BuiltInCMExporter *pCMX
++      = (BuiltInCMExporter *)sqlite3_malloc(sizeof(BuiltInCMExporter));
++    if( pFFX!=0 && pCMX!=0 ){
++      BuiltInFFExporter ffExporter = BI_FF_EXPORTER_INIT( psi );
++      BuiltInCMExporter cmExporter = BI_CM_EXPORTER_INIT( psi );
++      memcpy(pFFX, &ffExporter, sizeof(BuiltInFFExporter));
++      memcpy(pCMX, &cmExporter, sizeof(BuiltInCMExporter));
++      psi->pFreeformExporter = (ExportHandler *)pFFX;
++      psi->pColumnarExporter = (ExportHandler *)pCMX;
++      psi->pActiveExporter = psi->pFreeformExporter;
++    }else{
++      sqlite3_free(pFFX);
++      sqlite3_free(pCMX);
++      return SQLITE_NOMEM;
++    }
++  }
++#endif
++  return SQLITE_OK;
++}
++
++#ifndef SQLITE_SHELL_FIDDLE
++/*
++** Undo execution environment preparation for shell.
++*/
++static void execution_restore(ShellInState *psi, ShellExState *psx){
++#if SHELL_DATAIO_EXT
++  ExportHandler *pEHCM = psi->pColumnarExporter;
++  ExportHandler *pEHFF = psi->pFreeformExporter;
++  pEHCM->pMethods->destruct(pEHCM);
++  pEHFF->pMethods->destruct(pEHFF);
++  sqlite3_free(pEHCM);
++  sqlite3_free(pEHFF);
++#endif
++  main_cleanup(psi, psx);
++  interruption_alter(0);
+ }
++#endif
  /*
  ** Output text to the console in a font that attracts extra attention.
  */
@@@ -16368,416 -11913,32 +16520,420 @@@ static void printBold(const char *zText
           FOREGROUND_RED|FOREGROUND_INTENSITY
    );
  #endif
 -  printf("%s", zText);
 -#if !SQLITE_OS_WINRT
 -  SetConsoleTextAttribute(out, defaultScreenInfo.wAttributes);
 +  fprintf(STD_OUT, "%s", zText);
 +#if !SQLITE_OS_WINRT
 +  SetConsoleTextAttribute(out, defaultScreenInfo.wAttributes);
 +#endif
 +}
 +#else
 +static void printBold(const char *zText){
 +  fprintf(STD_OUT, "\033[1m%s\033[0m", zText);
 +}
 +#endif
 +
 +/*
 +** Get the argument to an --option.  Throw an error and die if no argument
 +** is available.
 +*/
 +static char *cmdline_option_value(int argc, char **argv, int i){
 +  if( i==argc ){
 +    utf8_printf(STD_ERR, "%s: Error: missing argument to %s\n",
 +            argv[0], argv[argc-1]);
 +    quit_moan("invocation error", 1);
 +  }
 +  return argv[i];
 +}
 +
 +static void zapGlobalDbLock(void){
 +  if( pGlobalDbLock ){
 +    sqlite3_mutex_free(pGlobalDbLock);
 +    pGlobalDbLock = 0;
 +  }
 +}
- static void sayAbnormalExit(void){
-   if( seenInterrupt ) fprintf(stderr, "Program interrupted.\n");
- }
 +
 +/* A vector of command strings collected from the command line. */
 +typedef struct CmdArgs {
 +  char **azCmd;  /* the strings */
 +  int nCmd;      /* how many collected */
 +  u8 bArgsHeld;  /* whether "the strings" are owned by this object */
 +} CmdArgs;
 +/* Freer for above. */
 +static void freeCmdArgs(CmdArgs *pca){
 +  int i;
 +  if( !pca ) return;
 +  if( pca->bArgsHeld ){
 +    for( i=0; i<pca->nCmd; ++i ){
 +      free(pca->azCmd[i]);
 +    }
 +  }
 +  free(pca->azCmd);
 +  pca->azCmd = 0;
 +  pca->nCmd = 0;
 +}
 +/* Capacity grower for above. May terminate for OOM. */
 +static void growCmdArgs(CmdArgs *pca, int nRoom){
 +  void *vaz = realloc(pca->azCmd, sizeof(pca->azCmd[0])*nRoom);
 +  shell_check_oomm(vaz);
 +  pca->azCmd = (char**)vaz;
 +}
 +
 +/* Data collected during args scanning. */
 +typedef struct ArgsData {
 +  int readStdin;         /* whether stdin will be read */
 +  int nOptsEnd;          /* where -- seen, else argc */
 +  const char *zInitFile; /* specified init file */
 +  const char *zVfs;      /* -vfs command-line option */
 +  short bQuiet;          /* -quiet option */
 +#if ARCHIVE_ENABLE
 +  short bArCmd;          /* -A option given */
 +#endif /* ARCHIVE_ENABLE */
 +} ArgsData;
 +
 +/*
 +** Perform CLI invocation argument processing.
 +** This code is collected here for convenience, to declutter main()
 +** and to make this processing a little simpler to understand.
 +** Parameters are:
 +** argc, argv : command-line arguments
 +** pass : the pass number, 1 or 2
 +** *pcaCmd (out) : arguments preceded by -cmd (which are run first)
 +** *pcaBare (out) : non-option or -A arguments (never the DB name)
 +** *pad (out, in/out) : option data not held in Shell??State
 +**
++** The 1st pass must be done with SQLite uninitialized.
++** The 2nd pass is indifferent to SQLite initialized or not.
++**
++** Returns are: 0 => normal, 1 => error, 2 => quit
++**
 +** This function may terminate abrubtly under OOM conditions.
 +*/
 +static int scanInvokeArgs(int argc, char **argv, int pass, ShellInState *psi,
 +                          CmdArgs *pcaCmd, CmdArgs *pcaBare, ArgsData *pad){
 +  int rc = 0;
 +  DotCmdRC drc;
 +  int i;
 +  if( pass==1 ){
 +    for(i=1; i<argc && rc<2; i++){
 +      char *z = argv[i];
 +      if( z[0]!='-' || i>pad->nOptsEnd ){
 +        if( psi->aAuxDb->zDbFilename==0 ){
 +          psi->aAuxDb->zDbFilename = z;
 +        }else{
 +          growCmdArgs(pcaBare, pcaBare->nCmd+1);
 +          pcaBare->azCmd[pcaBare->nCmd++] = z;
 +          /* Excesss, non-option-like arguments are interpreted as SQL (or
 +          ** dot-commands) and mean that nothing is to be read from stdin. */
 +          pad->readStdin = 0;
 +        }
 +        continue;
 +      }
 +      if( z[1]=='-' ) z++;
 +      if( cli_strcmp(z, "-")==0 ){
 +        pad->nOptsEnd = i;
 +        continue;
 +      }else if( cli_strcmp(z,"-separator")==0
 +       || cli_strcmp(z,"-nullvalue")==0
 +       || cli_strcmp(z,"-newline")==0
 +       || cli_strcmp(z,"-cmd")==0
 +      ){
 +        (void)cmdline_option_value(argc, argv, ++i);
 +        /* Will pickup value on next pass. */
 +      }else if( cli_strcmp(z,"-init")==0 ){
 +        pad->zInitFile = cmdline_option_value(argc, argv, ++i);
 +      }else if( cli_strcmp(z,"-batch")==0 ){
 +        /* Need to check for batch mode here to so we can avoid printing
 +        ** informational messages (like from process_sqliterc) before
 +        ** we do the actual processing of arguments later in a second pass.
 +        */
 +        stdin_is_interactive = 0;
 +      }else if( cli_strcmp(z,"-heap")==0 ){
 +#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
 +        const char *zSize;
 +        sqlite3_int64 szHeap;
 +
 +        zSize = cmdline_option_value(argc, argv, ++i);
 +        szHeap = integerValue(zSize);
 +        if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000;
 +        verify_uninitialized();
 +        sqlite3_config(SQLITE_CONFIG_HEAP,malloc((int)szHeap),(int)szHeap, 64);
 +#else
 +        (void)cmdline_option_value(argc, argv, ++i);
 +#endif
 +      }else if( cli_strcmp(z,"-pagecache")==0 ){
 +        sqlite3_int64 n, sz;
 +        void *pvCache = 0;
 +        sz = integerValue(cmdline_option_value(argc,argv,++i));
 +        if( sz>70000 ) sz = 70000;
 +        if( sz<0 ) sz = 0;
 +        n = integerValue(cmdline_option_value(argc,argv,++i));
 +        if( sz>0 && n>0 && 0xffffffffffffLL/sz<n ){
 +          n = 0xffffffffffffLL/sz;
 +        }
 +        verify_uninitialized();
 +        if( n>0 && sz>0 ) pvCache = malloc(n*sz);
 +        shell_check_oomm(pvCache);
 +        sqlite3_config(SQLITE_CONFIG_PAGECACHE, pvCache, sz, n);
 +        psi->shellFlgs |= SHFLG_Pagecache;
 +      }else if( cli_strcmp(z,"-lookaside")==0 ){
 +        int n, sz;
 +        sz = (int)integerValue(cmdline_option_value(argc,argv,++i));
 +        if( sz<0 ) sz = 0;
 +        n = (int)integerValue(cmdline_option_value(argc,argv,++i));
 +        if( n<0 ) n = 0;
 +        verify_uninitialized();
 +        sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n);
 +        if( sz*n==0 ) psi->shellFlgs &= ~SHFLG_Lookaside;
 +      }else if( cli_strcmp(z,"-threadsafe")==0 ){
 +        int n;
 +        n = (int)integerValue(cmdline_option_value(argc,argv,++i));
 +        verify_uninitialized();
 +        switch( n ){
 +           case 0:  sqlite3_config(SQLITE_CONFIG_SINGLETHREAD);  break;
 +           case 2:  sqlite3_config(SQLITE_CONFIG_MULTITHREAD);   break;
 +           default: sqlite3_config(SQLITE_CONFIG_SERIALIZED);    break;
 +        }
 +#ifdef SQLITE_ENABLE_VFSTRACE
 +      }else if( cli_strcmp(z,"-vfstrace")==0 ){
 +        extern int vfstrace_register(
 +           const char *zTraceName,
 +           const char *zOldVfsName,
 +           int (*xOut)(const char*,void*),
 +           void *pOutArg,
 +           int makeDefault
 +        );
 +        vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,STD_ERR,1);
 +#endif
 +#ifdef SQLITE_ENABLE_MULTIPLEX
 +      }else if( cli_strcmp(z,"-multiplex")==0 ){
 +        extern int sqlite3_multiplex_initialize(const char*,int);
 +        sqlite3_multiplex_initialize(0, 1);
 +#endif
 +      }else if( cli_strcmp(z,"-mmap")==0 ){
 +        sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
 +        verify_uninitialized();
 +        sqlite3_config(SQLITE_CONFIG_MMAP_SIZE, sz, sz);
 +#ifdef SQLITE_ENABLE_SORTER_REFERENCES
 +      }else if( cli_strcmp(z,"-sorterref")==0 ){
 +        sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
 +        verify_uninitialized();
 +        sqlite3_config(SQLITE_CONFIG_SORTERREF_SIZE, (int)sz);
 +#endif
 +      }else if( cli_strcmp(z,"-vfs")==0 ){
 +        pad->zVfs = cmdline_option_value(argc, argv, ++i);
 +#ifdef SQLITE_HAVE_ZLIB
 +      }else if( cli_strcmp(z,"-zip")==0 ){
 +        psi->openMode = SHELL_OPEN_ZIPFILE;
 +#endif
 +      }else if( cli_strcmp(z,"-append")==0 ){
 +        psi->openMode = SHELL_OPEN_APPENDVFS;
 +#ifndef SQLITE_OMIT_DESERIALIZE
 +      }else if( cli_strcmp(z,"-deserialize")==0 ){
 +        psi->openMode = SHELL_OPEN_DESERIALIZE;
 +      }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
 +        psi->szMax = integerValue(argv[++i]);
 +#endif
 +      }else if( cli_strcmp(z,"-readonly")==0 ){
 +        psi->openMode = SHELL_OPEN_READONLY;
 +      }else if( cli_strcmp(z,"-nofollow")==0 ){
 +        psi->openFlags = SQLITE_OPEN_NOFOLLOW;
 +#if ARCHIVE_ENABLE
 +      }else if( cli_strncmp(z, "-A",2)==0 ){
 +        /* All remaining command-line arguments are passed to the ".archive"
 +        ** command, so ignore them */
 +        break;
  #endif
 -}
 -#else
 -static void printBold(const char *zText){
 -  printf("\033[1m%s\033[0m", zText);
 -}
 +      }else if( cli_strcmp(z, "-memtrace")==0 ){
 +        sqlite3MemTraceActivate(STD_ERR);
 +      }else if( cli_strcmp(z,"-bail")==0 ){
 +        bail_on_error = 1;
 +#if SHELL_EXTENSIONS
 +      }else if( cli_strcmp(z,"-shxopts")==0 ){
 +        psi->bExtendedDotCmds = (u8)integerValue(argv[++i]);
  #endif
 -
 -/*
 -** Get the argument to an --option.  Throw an error and die if no argument
 -** is available.
 -*/
 -static char *cmdline_option_value(int argc, char **argv, int i){
 -  if( i==argc ){
 -    utf8_printf(stderr, "%s: Error: missing argument to %s\n",
 -            argv[0], argv[argc-1]);
 -    exit(1);
 +      }else if( cli_strcmp(z,"-nonce")==0 ){
 +        free(psi->zNonce);
 +        shell_check_oomm(psi->zNonce = strdup(argv[++i]));
 +      }else if( cli_strcmp(z,"-quiet")==0 ){
 +        pad->bQuiet = (int)integerValue(cmdline_option_value(argc,argv,++i));
 +      }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
 +        psi->shellFlgs |= SHFLG_TestingMode;
 +      }else if( cli_strcmp(z,"-safe")==0 ){
 +        /* catch this on the second pass (Unsafe is fine on invocation.) */
 +      }
 +    }
 +  }else if( pass==2 ){
 +    for(i=1; i<argc && rc<2; i++){
 +      char *z = argv[i];
 +      char *zModeSet = 0;
 +      if( z[0]!='-' || i>=pad->nOptsEnd ) continue;
 +      if( z[1]=='-' ){ z++; }
 +      if( cli_strcmp(z,"-init")==0 ){
 +        i++;
 +      }else if( cli_strcmp(z,"-html")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-list")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-quote")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-line")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-column")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-json")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-markdown")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-table")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-box")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-csv")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-ascii")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-tabs")==0 ){
 +        zModeSet = z;
 +#ifdef SQLITE_HAVE_ZLIB
 +      }else if( cli_strcmp(z,"-zip")==0 ){
 +        psi->openMode = SHELL_OPEN_ZIPFILE;
 +#endif
 +      }else if( cli_strcmp(z,"-append")==0 ){
 +        psi->openMode = SHELL_OPEN_APPENDVFS;
 +#ifndef SQLITE_OMIT_DESERIALIZE
 +      }else if( cli_strcmp(z,"-deserialize")==0 ){
 +        psi->openMode = SHELL_OPEN_DESERIALIZE;
 +      }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
 +        psi->szMax = integerValue(argv[++i]);
 +#endif
 +      }else if( cli_strcmp(z,"-readonly")==0 ){
 +        psi->openMode = SHELL_OPEN_READONLY;
 +      }else if( cli_strcmp(z,"-nofollow")==0 ){
 +        psi->openFlags |= SQLITE_OPEN_NOFOLLOW;
 +      }else if( cli_strcmp(z,"-separator")==0 ){
 +        sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator,
 +                         "%s",cmdline_option_value(argc,argv,++i));
 +      }else if( cli_strcmp(z,"-newline")==0 ){
 +        sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator,
 +                         "%s",cmdline_option_value(argc,argv,++i));
 +      }else if( cli_strcmp(z,"-nullvalue")==0 ){
 +        sqlite3_snprintf(sizeof(psi->nullValue), psi->nullValue,
 +                         "%s",cmdline_option_value(argc,argv,++i));
 +      }else if( cli_strcmp(z,"-header")==0 ){
 +        psi->showHeader = 1;
 +        ShellSetFlagI(psi, SHFLG_HeaderSet);
 +      }else if( cli_strcmp(z,"-noheader")==0 ){
 +        psi->showHeader = 0;
 +        ShellSetFlagI(psi, SHFLG_HeaderSet);
 +      }else if( cli_strcmp(z,"-echo")==0 ){
 +        ShellSetFlagI(psi, SHFLG_Echo);
 +      }else if( cli_strcmp(z,"-eqp")==0 ){
 +        psi->autoEQP = AUTOEQP_on;
 +      }else if( cli_strcmp(z,"-eqpfull")==0 ){
 +        psi->autoEQP = AUTOEQP_full;
 +      }else if( cli_strcmp(z,"-stats")==0 ){
 +        psi->statsOn = 1;
 +      }else if( cli_strcmp(z,"-scanstats")==0 ){
 +        psi->scanstatsOn = 1;
 +      }else if( cli_strcmp(z,"-backslash")==0 ){
 +        /* Undocumented command-line option: -backslash
 +        ** Causes C-style backslash escapes to be evaluated in SQL statements
 +        ** prior to sending the SQL into SQLite.  Useful for injecting crazy
 +        ** bytes in the middle of SQL statements for testing and debugging.
 +        */
 +        ShellSetFlagI(psi, SHFLG_Backslash);
 +      }else if( cli_strcmp(z,"-bail")==0 ){
 +        /* No-op.  The bail_on_error flag should already be set. */
 +#if SHELL_EXTENSIONS
 +      }else if( cli_strcmp(z,"-shxopts")==0 ){
 +        i++; /* Handled on first pass. */
 +#endif
 +      }else if( cli_strcmp(z,"-version")==0 ){
 +        fprintf(STD_OUT, "%s %s\n", sqlite3_libversion(), sqlite3_sourceid());
 +        rc = 2;
 +      }else if( cli_strcmp(z,"-interactive")==0 ){
 +        stdin_is_interactive = 1;
 +      }else if( cli_strcmp(z,"-batch")==0 ){
 +        stdin_is_interactive = 0;
 +      }else if( cli_strcmp(z,"-utf8")==0 ){
 +#if SHELL_WIN_UTF8_OPT
 +        console_utf8 = 1;
 +#endif /* SHELL_WIN_UTF8_OPT */
 +      }else if( cli_strcmp(z,"-heap")==0 ){
 +        i++;
 +      }else if( cli_strcmp(z,"-pagecache")==0 ){
 +        i+=2;
 +      }else if( cli_strcmp(z,"-lookaside")==0 ){
 +        i+=2;
 +      }else if( cli_strcmp(z,"-threadsafe")==0 ){
 +        i+=2;
 +      }else if( cli_strcmp(z,"-nonce")==0 ){
 +        i+=2;
 +      }else if( cli_strcmp(z,"-mmap")==0 ){
 +        i++;
 +      }else if( cli_strcmp(z,"-memtrace")==0 ){
 +        i++;
 +#ifdef SQLITE_ENABLE_SORTER_REFERENCES
 +      }else if( cli_strcmp(z,"-sorterref")==0 ){
 +        i++;
 +#endif
 +      }else if( cli_strcmp(z,"-vfs")==0 ){
 +        i++;
 +#ifdef SQLITE_ENABLE_VFSTRACE
 +      }else if( cli_strcmp(z,"-vfstrace")==0 ){
 +        i++;
 +#endif
 +#ifdef SQLITE_ENABLE_MULTIPLEX
 +      }else if( cli_strcmp(z,"-multiplex")==0 ){
 +        i++;
 +#endif
 +      }else if( cli_strcmp(z,"-help")==0 ){
 +        usage(1);
++        rc = 2;
++        break;
 +      }else if( cli_strcmp(z,"-cmd")==0 ){
 +        /* Run commands that follow -cmd first and separately from commands
 +        ** that simply appear on the command-line. This is likely surprising.
 +        ** Better would be to run all commands in the order that they appear.
 +        ** But we retain this goofy behavior for historical compatibility. */
 +        if( i==argc-1 ) break; /* Pretend (un)specified command is empty. */
 +        growCmdArgs(pcaCmd, pcaCmd->nCmd+1);
 +        pcaCmd->azCmd[pcaCmd->nCmd++] = cmdline_option_value(argc,argv,++i);
 +#if ARCHIVE_ENABLE
 +      }else if( cli_strncmp(z, "-A", 2)==0 ){
 +        if( pcaBare->nCmd>0 ){
 +          utf8_printf(STD_ERR, "Error: cannot mix regular SQL or dot-commands"
 +                              " with \"%s\"\n", z);
 +          rc = 1;
 +          break;
 +        }
 +        growCmdArgs(pcaBare, argc-i+1);
 +        if( z[2] ) pcaBare->azCmd[pcaBare->nCmd++] = &z[2];
 +        while( i<argc ){
 +          pcaBare->azCmd[pcaBare->nCmd++] = argv[i++];
 +        }
 +        pad->readStdin = 0;
 +        pad->bArCmd = 1;
 +        break;
 +#endif /* ARCHIVE_ENABLE */
 +      }else if( cli_strcmp(z,"-safe")==0 ){
 +        psi->bSafeMode = psi->bSafeModeFuture = 1;
 +      }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
 +        /* Acted upon in first pass. */
 +      }else if( cli_strcmp(z,"-quiet")==0 ){
 +        ++i;
 +      }else{
 +        utf8_printf(STD_ERR,"%s: Error: unknown option: %s\n", Argv0, z);
 +        raw_printf(STD_ERR,"Use -help for a list of options.\n");
 +        rc = 2;
 +      }
 +      if( zModeSet!=0 ){
 +        char *azModeCmd[] = { ".mode", zModeSet+1 };
 +        modeCommand(azModeCmd, 2, XSS(psi), 0);
 +        psi->cMode = psi->mode;
 +      }
 +    }
    }
 -  return argv[i];
 -}
 -
 -static void sayAbnormalExit(void){
 -  if( seenInterrupt ) fprintf(stderr, "Program interrupted.\n");
 +  return rc;
  }
  
  #ifndef SQLITE_SHELL_IS_UTF8
  #  endif
  #endif
  
--#ifdef SQLITE_SHELL_FIDDLE
- #  define SHELL_MAIN fiddle_main
- #endif
 -#  define main fiddle_main
 +#ifndef SHELL_MAIN
- # if SQLITE_SHELL_IS_UTF8
++# if defined(SQLITE_SHELL_FIDDLE)
++#  define SHELL_MAIN fiddle_main
++# elif SQLITE_SHELL_IS_UTF8
 +#  define SHELL_MAIN main
 +# else
 +#  define SHELL_MAIN wmain
 +# endif
  #endif
  
  #if SQLITE_SHELL_IS_UTF8
@@@ -16810,414 -11963,630 +16964,351 @@@ int SQLITE_CDECL SHELL_MAIN(int argc, w
  #ifdef SQLITE_DEBUG
    sqlite3_int64 mem_main_enter = 0;
  #endif
 -  char *zErrMsg = 0;
  #ifdef SQLITE_SHELL_FIDDLE
 -#  define data shellState
 +#  define datai shellStateI
 +#  define datax shellStateX
  #else
 -  ShellState data;
 +  ShellInState datai;
 +  ShellExState datax;
- #endif
- #if SHELL_DATAIO_EXT
-   BuiltInFFExporter ffExporter = BI_FF_EXPORTER_INIT( &datai );
-   BuiltInCMExporter cmExporter = BI_CM_EXPORTER_INIT( &datai );
  #endif
 -  const char *zInitFile = 0;
 -  int i;
 -  int rc = 0;
 +  RipStackDest mainRipDest = RIP_STACK_DEST_INIT;
 +  int i;      /* General purpose */
 +  int iAbruptExitCode;
 +  int rc = 0; /* main() exit code */
 +  DotCmdRC drc = DCR_Ok;
    int warnInmemoryDb = 0;
 -  int readStdin = 1;
 -  int nCmd = 0;
 -  int nOptsEnd = argc;
 -  char **azCmd = 0;
 -  const char *zVfs = 0;           /* Value of -vfs command-line option */
 +  /* azCmd, nCmd, bArgsHeld */
 +  CmdArgs cmdArgsCmd = {0,0,0};  /* for -cmd <do_x> invocation arguments */
 +  AnyResourceHolder cacRH = {&cmdArgsCmd, (GenericFreer)freeCmdArgs};
 +  CmdArgs cmdArgsBare = {0,0,0}; /* for bare or -A invocation arguments  */
 +  AnyResourceHolder cabRH = {&cmdArgsBare, (GenericFreer)freeCmdArgs};
 +  /* readStdin, nOptsEnd, zInitFile, zVfs, bQuiet */
 +  ArgsData argsData = { 1, argc, 0,0,0 };
  #if !SQLITE_SHELL_IS_UTF8
 -  char **argvToFree = 0;
 -  int argcToFree = 0;
 -#endif
 -  setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
 -
 -#ifdef SQLITE_SHELL_FIDDLE
 -  stdin_is_interactive = 0;
 -  stdout_is_console = 1;
 -  data.wasm.zDefaultDbName = "/fiddle.sqlite3";
 -#else
 -  stdin_is_interactive = isatty(0);
 -  stdout_is_console = isatty(1);
 -#endif
 -#if SHELL_WIN_UTF8_OPT
 -  atexit(console_restore); /* Needs revision for CLI as library call */
 -#endif
 -  atexit(sayAbnormalExit);
 -#ifdef SQLITE_DEBUG
 -  mem_main_enter = sqlite3_memory_used();
 -#endif
 -#if !defined(_WIN32_WCE)
 -  if( getenv("SQLITE_DEBUG_BREAK") ){
 -    if( isatty(0) && isatty(2) ){
 -      fprintf(stderr,
 -          "attach debugger to process %d and press any key to continue.\n",
 -          GETPID());
 -      fgetc(stdin);
 -    }else{
 -#if defined(_WIN32) || defined(WIN32)
 -#if SQLITE_OS_WINRT
 -      __debugbreak();
 -#else
 -      DebugBreak();
 -#endif
 -#elif defined(SIGTRAP)
 -      raise(SIGTRAP);
 -#endif
 -    }
 -  }
 -#endif
 -  /* Register a valid signal handler early, before much else is done. */
 -#ifdef SIGINT
 -  signal(SIGINT, interrupt_handler);
 -#elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE)
 -  if( !SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE) ){
 -    fprintf(stderr, "No ^C handler.\n");
 -  }
 +  CmdArgs argsUtf8 = {0,0,1};
 +  AnyResourceHolder caRH = {&argsUtf8, (GenericFreer)freeCmdArgs};
  #endif
-   ResourceCount mainResCount = 0;
-   /**** Execution environment setup. ****/
-   setvbuf(STD_ERR, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
- #ifdef SQLITE_SHELL_FIDDLE
-   stdin_is_interactive = 0;
-   stdout_is_console = 1;
- #else
-   stdin_is_interactive = isatty(0);
-   stdout_is_console = isatty(1);
- #endif
-   if( atexit_registered==0 ){
- #if SHELL_WIN_UTF8_OPT
-     atexit(console_restore); /* Needs revision for CLI as library call */
- #endif
-     atexit(sayAbnormalExit);
-   }
- #ifdef SQLITE_DEBUG
-   mem_main_enter = sqlite3_memory_used();
- #endif
-   /* Register the control-C (SIGINT) handler.
-   ** Make sure we have a valid signal handler early, before anything
-   ** is done that might take long. */
- #ifdef SIGINT
-   signal(SIGINT, interrupt_handler);
- #elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE)
-   SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
- #endif
- #if !defined(_WIN32_WCE)
-   if( getenv("SQLITE_DEBUG_BREAK") ){
-     if( isatty(0) && isatty(2) ){
-       fprintf(STD_ERR,
-           "attach debugger to process %d and press any key to continue.\n",
-           GETPID());
-       fgetc(STD_IN);
-     }else{
- #if defined(_WIN32) || defined(WIN32)
- #if SQLITE_OS_WINRT
-       __debugbreak();
- #else
-       DebugBreak();
- #endif
- #elif defined(SIGTRAP)
-       raise(SIGTRAP);
- #endif
-     }
-   }
- #endif
-   /* Register a valid signal handler early, before much else is done. */
- #ifdef SIGINT
-   signal(SIGINT, interrupt_handler);
- #elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE)
-   if( !SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE) ){
-     fprintf(stderr, "No ^C handler.\n");
-   }
- #endif
++  RESOURCE_MARK(mark);
  
++  /* Just return error if using mismatched dynamic SQLite library version. */
  #if USE_SYSTEM_SQLITE+0!=1
    if( cli_strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,60)!=0 ){
 -    utf8_printf(stderr, "SQLite header and source version mismatch\n%s\n%s\n",
 +    utf8_printf(STD_ERR, "SQLite header and source version mismatch\n%s\n%s\n",
              sqlite3_sourceid(), SQLITE_SOURCE_ID);
--    exit(1);
++    return 1;
    }
  #endif
 -  main_init(&data);
  
-   /**** Data initialization. ****/
-   main_init(&datai,&datax);
-   init_std_inputs(stdin);
- #if SHELL_DATAIO_EXT
-   datai.pFreeformExporter = (ExportHandler*)&ffExporter;
-   datai.pColumnarExporter = (ExportHandler*)&cmExporter;
-   datai.pActiveExporter = (ExportHandler*)&ffExporter;
- #endif
 -  /* On Windows, we must translate command-line arguments into UTF-8.
 -  ** The SQLite memory allocator subsystem has to be enabled in order to
 -  ** do this.  But we want to run an sqlite3_shutdown() afterwards so that
 -  ** subsequent sqlite3_config() calls will work.  So copy all results into
 -  ** memory that does not come from the SQLite memory allocator.
 -  */
 -#if !SQLITE_SHELL_IS_UTF8
+   sqlite3_initialize();
 -  argvToFree = malloc(sizeof(argv[0])*argc*2);
 -  shell_check_oom(argvToFree);
 -  argcToFree = argc;
 -  argv = argvToFree + argc;
 -  for(i=0; i<argc; i++){
 -    char *z = sqlite3_win32_unicode_to_utf8(wargv[i]);
 -    i64 n;
 -    shell_check_oom(z);
 -    n = strlen(z);
 -    argv[i] = malloc( n+1 );
 -    shell_check_oom(argv[i]);
 -    memcpy(argv[i], z, n+1);
 -    argvToFree[i] = argv[i];
 -    sqlite3_free(z);
 +
-   /* 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.
-   ** This kind of exit is considered unrecoverable, so it is taken for
-   ** all processing, whether of invocation arguments, ~/.sqliterc, or
-   ** input from stdin (and input redirects instigated there.)
-   */
-   register_exit_ripper(&mainRipDest);
-   if( 0==RIP_TO_HERE(mainRipDest) ){
-     /**** Input processing. ****/
-     main_resource_mark = mainRipDest.resDest;
++  /* Create a mutex for thread-safe query execution interruption. */
++  if( pGlobalDbLock==0 ){
++    pGlobalDbLock = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+   }
 -  sqlite3_shutdown();
 -#endif
++  upon_terminate(zapGlobalDbLock);
 -  assert( argc>=1 && argv && argv[0] );
 -  Argv0 = argv[0];
++#ifdef SQLITE_DEBUG
++  mem_main_enter = sqlite3_memory_used();
++#endif
  
 -#ifdef SQLITE_SHELL_DBNAME_PROC
 -  {
 -    /* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name
 -    ** of a C-function that will provide the name of the database file.  Use
 -    ** this compile-time option to embed this shell program in larger
 -    ** applications. */
 -    extern void SQLITE_SHELL_DBNAME_PROC(const char**);
 -    SQLITE_SHELL_DBNAME_PROC(&data.pAuxDb->zDbFilename);
 -    warnInmemoryDb = 0;
 -  }
 +    /* On Windows, we must translate command-line arguments into UTF-8.
 +    ** The SQLite memory allocator subsystem has to be enabled in order to
 +    ** do this.  But we want to run an sqlite3_shutdown() afterwards so that
 +    ** subsequent sqlite3_config() calls will work.  So copy all results into
 +    ** memory that does not come from the SQLite memory allocator.
 +    */
 +#if !SQLITE_SHELL_IS_UTF8
-     sqlite3_initialize();
-     argsUtf8.azCmd = malloc(sizeof(argv[0])*argc);
-     shell_check_oomm(argsUtf8.azCmd);
++    growCmdArgs(&argsUtf8, argc);
 +    argsUtf8.nCmd = 0;
 +    any_ref_holder(&caRH); /* This will normally activate as shell exits. */
-     ++mainResCount;
 +    for(i=0; i<argc; i++){
 +      char *z = sqlite3_win32_unicode_to_utf8(wargv[i]);
-       i64 n;
 +      shell_check_ooms(z);
-       ++mainResCount;
 +      sstr_holder(z);
-       n = strlen(z);
-       argsUtf8.azCmd[i] = malloc( n+1 );
++      argsUtf8.azCmd[i] = strdup(z);
 +      shell_check_oomm(argsUtf8.azCmd[i]);
-       ++argsUtf8.nCmd;
-       memcpy(argsUtf8.azCmd[i], z, n+1);
 +      release_holder();
-       --mainResCount;
++      ++argsUtf8.nCmd;
 +    }
-     sqlite3_shutdown();
 +    argv = argsUtf8.azCmd;
  #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.
++  /**** Execution environment setup. ****/
++  sqlite3_shutdown();
++  if( (rc = execution_prepare(&datai,&datax))!=0 ){
++    utf8_printf(STD_ERR, "Cannot initialize. Quitting.\n");
++    return 1;
++  }
++  sqlite3_initialize();
++  /* 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.
++  ** This kind of exit is considered unrecoverable, so it is taken for
++  ** all processing, whether of invocation arguments, ~/.sqliterc, or
++  ** input from stdin (and input redirects instigated there.)
+   */
 -#ifndef SQLITE_SHELL_FIDDLE
 -  verify_uninitialized();
 -#endif
 -  for(i=1; i<argc; i++){
 -    char *z;
 -    z = argv[i];
 -    if( z[0]!='-' || i>nOptsEnd ){
 -      if( data.aAuxDb->zDbFilename==0 ){
 -        data.aAuxDb->zDbFilename = z;
 -      }else{
 -        /* Excesss arguments are interpreted as SQL (or dot-commands) and
 -        ** mean that nothing is read from stdin */
 -        readStdin = 0;
 -        nCmd++;
 -        azCmd = realloc(azCmd, sizeof(azCmd[0])*nCmd);
 -        shell_check_oom(azCmd);
 -        azCmd[nCmd-1] = z;
 -      }
 -      continue;
++  register_exit_ripper(&mainRipDest);
++  if( 0==RIP_TO_HERE(mainRipDest) ){
++    /**** Input processing. ****/
++
 +    assert( argc>=1 && argv && argv[0] );
 +    Argv0 = argv[0];
 +#if SHELL_DYNAMIC_EXTENSION
 +    initStartupDir();
 +    if( isExtendedBasename(Argv0) ){
 +      datai.bExtendedDotCmds = SHELL_ALL_EXTENSIONS;
      }
 -    if( z[1]=='-' ) z++;
 -    if( cli_strcmp(z, "-")==0 ){
 -      nOptsEnd = i;
 -      continue;
 -    }else if( cli_strcmp(z,"-separator")==0
 -     || cli_strcmp(z,"-nullvalue")==0
 -     || cli_strcmp(z,"-newline")==0
 -     || cli_strcmp(z,"-cmd")==0
 -    ){
 -      (void)cmdline_option_value(argc, argv, ++i);
 -    }else if( cli_strcmp(z,"-init")==0 ){
 -      zInitFile = cmdline_option_value(argc, argv, ++i);
 -    }else if( cli_strcmp(z,"-batch")==0 ){
 -      /* Need to check for batch mode here to so we can avoid printing
 -      ** informational messages (like from process_sqliterc) before
 -      ** we do the actual processing of arguments later in a second pass.
 -      */
 -      stdin_is_interactive = 0;
 -    }else if( cli_strcmp(z,"-heap")==0 ){
 -#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
 -      const char *zSize;
 -      sqlite3_int64 szHeap;
 -
 -      zSize = cmdline_option_value(argc, argv, ++i);
 -      szHeap = integerValue(zSize);
 -      if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000;
 -      verify_uninitialized();
 -      sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64);
 -#else
 -      (void)cmdline_option_value(argc, argv, ++i);
 -#endif
 -    }else if( cli_strcmp(z,"-pagecache")==0 ){
 -      sqlite3_int64 n, sz;
 -      sz = integerValue(cmdline_option_value(argc,argv,++i));
 -      if( sz>70000 ) sz = 70000;
 -      if( sz<0 ) sz = 0;
 -      n = integerValue(cmdline_option_value(argc,argv,++i));
 -      if( sz>0 && n>0 && 0xffffffffffffLL/sz<n ){
 -        n = 0xffffffffffffLL/sz;
 -      }
 -      verify_uninitialized();
 -      sqlite3_config(SQLITE_CONFIG_PAGECACHE,
 -                    (n>0 && sz>0) ? malloc(n*sz) : 0, sz, n);
 -      data.shellFlgs |= SHFLG_Pagecache;
 -    }else if( cli_strcmp(z,"-lookaside")==0 ){
 -      int n, sz;
 -      sz = (int)integerValue(cmdline_option_value(argc,argv,++i));
 -      if( sz<0 ) sz = 0;
 -      n = (int)integerValue(cmdline_option_value(argc,argv,++i));
 -      if( n<0 ) n = 0;
 -      verify_uninitialized();
 -      sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n);
 -      if( sz*n==0 ) data.shellFlgs &= ~SHFLG_Lookaside;
 -    }else if( cli_strcmp(z,"-threadsafe")==0 ){
 -      int n;
 -      n = (int)integerValue(cmdline_option_value(argc,argv,++i));
 -      verify_uninitialized();
 -      switch( n ){
 -         case 0:  sqlite3_config(SQLITE_CONFIG_SINGLETHREAD);  break;
 -         case 2:  sqlite3_config(SQLITE_CONFIG_MULTITHREAD);   break;
 -         default: sqlite3_config(SQLITE_CONFIG_SERIALIZED);    break;
 -      }
 -#ifdef SQLITE_ENABLE_VFSTRACE
 -    }else if( cli_strcmp(z,"-vfstrace")==0 ){
 -      extern int vfstrace_register(
 -         const char *zTraceName,
 -         const char *zOldVfsName,
 -         int (*xOut)(const char*,void*),
 -         void *pOutArg,
 -         int makeDefault
 -      );
 -      vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,stderr,1);
  #endif
 -#ifdef SQLITE_ENABLE_MULTIPLEX
 -    }else if( cli_strcmp(z,"-multiplex")==0 ){
 -      extern int sqlite3_multiplex_initialize(const char*,int);
 -      sqlite3_multiplex_initialize(0, 1);
 -#endif
 -    }else if( cli_strcmp(z,"-mmap")==0 ){
 -      sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
 -      verify_uninitialized();
 -      sqlite3_config(SQLITE_CONFIG_MMAP_SIZE, sz, sz);
 -#if defined(SQLITE_ENABLE_SORTER_REFERENCES)
 -    }else if( cli_strcmp(z,"-sorterref")==0 ){
 -      sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
 -      verify_uninitialized();
 -      sqlite3_config(SQLITE_CONFIG_SORTERREF_SIZE, (int)sz);
 -#endif
 -    }else if( cli_strcmp(z,"-vfs")==0 ){
 -      zVfs = cmdline_option_value(argc, argv, ++i);
 -#ifdef SQLITE_HAVE_ZLIB
 -    }else if( cli_strcmp(z,"-zip")==0 ){
 -      data.openMode = SHELL_OPEN_ZIPFILE;
 +
 +    any_ref_holder(&cacRH);
 +    any_ref_holder(&cabRH);
-     mainResCount += 2;
 +
 +#ifdef SQLITE_SHELL_DBNAME_PROC
 +    {
 +      /* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name
 +      ** of a C-function that will provide the name of the database file.  Use
 +      ** this compile-time option to embed this shell program in larger
 +      ** applications. */
 +      extern void SQLITE_SHELL_DBNAME_PROC(const char**);
 +      SQLITE_SHELL_DBNAME_PROC(&datai.pAuxDb->zDbFilename);
 +      warnInmemoryDb = 0;
 +    }
  #endif
 -    }else if( cli_strcmp(z,"-append")==0 ){
 -      data.openMode = SHELL_OPEN_APPENDVFS;
 -#ifndef SQLITE_OMIT_DESERIALIZE
 -    }else if( cli_strcmp(z,"-deserialize")==0 ){
 -      data.openMode = SHELL_OPEN_DESERIALIZE;
 -    }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
 -      data.szMax = integerValue(argv[++i]);
 -#endif
 -    }else if( cli_strcmp(z,"-readonly")==0 ){
 -      data.openMode = SHELL_OPEN_READONLY;
 -    }else if( cli_strcmp(z,"-nofollow")==0 ){
 -      data.openFlags = SQLITE_OPEN_NOFOLLOW;
 -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
 -    }else if( cli_strncmp(z, "-A",2)==0 ){
 -      /* All remaining command-line arguments are passed to the ".archive"
 -      ** command, so ignore them */
 -      break;
 +
 +    /* 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.
 +    */
++    sqlite3_shutdown();
 +#ifndef SQLITE_SHELL_FIDDLE
 +    verify_uninitialized();
  #endif
 -    }else if( cli_strcmp(z, "-memtrace")==0 ){
 -      sqlite3MemTraceActivate(stderr);
 -    }else if( cli_strcmp(z,"-bail")==0 ){
 -      bail_on_error = 1;
 -    }else if( cli_strcmp(z,"-nonce")==0 ){
 -      free(data.zNonce);
 -      data.zNonce = strdup(argv[++i]);
 -    }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
 -      ShellSetFlag(&data,SHFLG_TestingMode);
 -    }else if( cli_strcmp(z,"-safe")==0 ){
 -      /* no-op - catch this on the second pass */
 -    }
 -  }
 +    i = scanInvokeArgs(argc, argv, /*pass*/ 1, &datai,
 +                       &cmdArgsCmd, &cmdArgsBare, &argsData);
  #ifndef SQLITE_SHELL_FIDDLE
 -  verify_uninitialized();
 +    verify_uninitialized();
  #endif
  
 -
  #ifdef SQLITE_SHELL_INIT_PROC
 -  {
 -    /* If the SQLITE_SHELL_INIT_PROC macro is defined, then it is the name
 -    ** of a C-function that will perform initialization actions on SQLite that
 -    ** occur just before or after sqlite3_initialize(). Use this compile-time
 -    ** option to embed this shell program in larger applications. */
 -    extern void SQLITE_SHELL_INIT_PROC(void);
 -    SQLITE_SHELL_INIT_PROC();
 -  }
 +    {
 +      /* If the SQLITE_SHELL_INIT_PROC macro is defined, then it is the name
 +      ** of a C-function that will perform initialization actions on SQLite that
 +      ** occur just before or after sqlite3_initialize(). Use this compile-time
-       ** option to embed this shell program in larger applications. */
++      ** option to embed this shell program in larger applications.
++      **
++      ** The provided C-function must call sqlite3_initialize() sometime
++      ** before returning (leaving SQLite in the initialized state.)
++      */
 +      extern void SQLITE_SHELL_INIT_PROC(void);
 +      SQLITE_SHELL_INIT_PROC();
 +    }
  #else
 -  /* All the sqlite3_config() calls have now been made. So it is safe
 -  ** to call sqlite3_initialize() and process any command line -vfs option. */
 -  sqlite3_initialize();
 +    /* All the sqlite3_config() calls have now been made. So it is safe
 +    ** to call sqlite3_initialize() and process any command line -vfs option. */
 +    sqlite3_initialize();
  #endif
  
-     /* Create a mutex for thread-safe query execution interruption. */
-     if( pGlobalDbLock==0 ){
-       pGlobalDbLock = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
-     }
-     if( atexit_registered==0 ){
-       atexit(zapGlobalDbLock);
-       ++atexit_registered;
-     }
 -  if( zVfs ){
 -    sqlite3_vfs *pVfs = sqlite3_vfs_find(zVfs);
 -    if( pVfs ){
 -      sqlite3_vfs_register(pVfs, 1);
 -    }else{
 -      utf8_printf(stderr, "no such VFS: \"%s\"\n", zVfs);
 -      exit(1);
 +    if( argsData.zVfs ){
 +      sqlite3_vfs *pVfs = sqlite3_vfs_find(argsData.zVfs);
 +      if( pVfs ){
 +        sqlite3_vfs_register(pVfs, 1);
 +      }else{
 +        utf8_printf(STD_ERR, "no such VFS: \"%s\"\n", argsData.zVfs);
 +        rc = 1;
 +        goto shell_bail;
 +      }
      }
 -  }
  
 -  if( data.pAuxDb->zDbFilename==0 ){
 +    if( datai.pAuxDb->zDbFilename==0 ){
  #ifndef SQLITE_OMIT_MEMORYDB
 -    data.pAuxDb->zDbFilename = ":memory:";
 -    warnInmemoryDb = argc==1;
 +      datai.pAuxDb->zDbFilename = ":memory:";
 +      warnInmemoryDb = argc==1;
  #else
 -    utf8_printf(stderr,"%s: Error: no database filename specified\n", Argv0);
 -    return 1;
 +      utf8_printf(STD_ERR,"%s: Error: no database filename specified\n", Argv0);
 +      rc = 1;
 +      goto shell_bail;
  #endif
 -  }
 -  data.out = stdout;
 +    }
  #ifndef SQLITE_SHELL_FIDDLE
 -  sqlite3_appendvfs_init(0,0,0);
 +    sqlite3_appendvfs_init(0,0,0);
  #endif
  
 -  /* Go ahead and open the database file if it already exists.  If the
 -  ** file does not exist, delay opening it.  This prevents empty database
 -  ** files from being created if a user mistypes the database name argument
 -  ** to the sqlite command-line tool.
 -  */
 -  if( access(data.pAuxDb->zDbFilename, 0)==0 ){
 -    open_db(&data, 0);
 -  }
 +    /* Go ahead and open the database file if it already exists.  If the
 +    ** file does not exist, delay opening it.  This prevents empty database
 +    ** files from being created if a user mistypes the database name argument
 +    ** to the sqlite command-line tool.
 +    */
 +    if( access(datai.pAuxDb->zDbFilename, 0)==0 ){
 +      open_db(&datax, 0);
 +    }
  
 -  /* Process the initialization file if there is one.  If no -init option
 -  ** is given on the command line, look for a file named ~/.sqliterc and
 -  ** try to process it.
 -  */
 -  process_sqliterc(&data,zInitFile);
 +    /* Process the initialization file if there is one.  If no -init option
 +    ** is given on the command line, look for a file named ~/.sqliterc and
 +    ** try to process it, without any quitting or bail-on-error.
 +    */
 +    process_sqliterc(&datai,argsData.zInitFile);
 +
 +    /* Make a second pass through the command-line argument and set
 +    ** options.  This second pass is delayed until after the initialization
 +    ** file is processed so that the command-line arguments will override
 +    ** settings in the initialization file.
 +    */
 +    rc = scanInvokeArgs(argc, argv, /*pass*/ 2, &datai,
 +                        &cmdArgsCmd, &cmdArgsBare, &argsData);
 +    if( rc>0 ){
 +      goto shell_bail;
 +    }
  
 -  /* Make a second pass through the command-line argument and set
 -  ** options.  This second pass is delayed until after the initialization
 -  ** file is processed so that the command-line arguments will override
 -  ** settings in the initialization file.
 -  */
 -  for(i=1; i<argc; i++){
 -    char *z = argv[i];
 -    if( z[0]!='-' || i>=nOptsEnd ) continue;
 -    if( z[1]=='-' ){ z++; }
 -    if( cli_strcmp(z,"-init")==0 ){
 -      i++;
 -    }else if( cli_strcmp(z,"-html")==0 ){
 -      data.mode = MODE_Html;
 -    }else if( cli_strcmp(z,"-list")==0 ){
 -      data.mode = MODE_List;
 -    }else if( cli_strcmp(z,"-quote")==0 ){
 -      data.mode = MODE_Quote;
 -      sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Comma);
 -      sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, SEP_Row);
 -    }else if( cli_strcmp(z,"-line")==0 ){
 -      data.mode = MODE_Line;
 -    }else if( cli_strcmp(z,"-column")==0 ){
 -      data.mode = MODE_Column;
 -    }else if( cli_strcmp(z,"-json")==0 ){
 -      data.mode = MODE_Json;
 -    }else if( cli_strcmp(z,"-markdown")==0 ){
 -      data.mode = MODE_Markdown;
 -    }else if( cli_strcmp(z,"-table")==0 ){
 -      data.mode = MODE_Table;
 -    }else if( cli_strcmp(z,"-box")==0 ){
 -      data.mode = MODE_Box;
 -    }else if( cli_strcmp(z,"-csv")==0 ){
 -      data.mode = MODE_Csv;
 -      memcpy(data.colSeparator,",",2);
 -#ifdef SQLITE_HAVE_ZLIB
 -    }else if( cli_strcmp(z,"-zip")==0 ){
 -      data.openMode = SHELL_OPEN_ZIPFILE;
 -#endif
 -    }else if( cli_strcmp(z,"-append")==0 ){
 -      data.openMode = SHELL_OPEN_APPENDVFS;
 -#ifndef SQLITE_OMIT_DESERIALIZE
 -    }else if( cli_strcmp(z,"-deserialize")==0 ){
 -      data.openMode = SHELL_OPEN_DESERIALIZE;
 -    }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
 -      data.szMax = integerValue(argv[++i]);
 -#endif
 -    }else if( cli_strcmp(z,"-readonly")==0 ){
 -      data.openMode = SHELL_OPEN_READONLY;
 -    }else if( cli_strcmp(z,"-nofollow")==0 ){
 -      data.openFlags |= SQLITE_OPEN_NOFOLLOW;
 -    }else if( cli_strcmp(z,"-ascii")==0 ){
 -      data.mode = MODE_Ascii;
 -      sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Unit);
 -      sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Record);
 -    }else if( cli_strcmp(z,"-tabs")==0 ){
 -      data.mode = MODE_List;
 -      sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Tab);
 -      sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Row);
 -    }else if( cli_strcmp(z,"-separator")==0 ){
 -      sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,
 -                       "%s",cmdline_option_value(argc,argv,++i));
 -    }else if( cli_strcmp(z,"-newline")==0 ){
 -      sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,
 -                       "%s",cmdline_option_value(argc,argv,++i));
 -    }else if( cli_strcmp(z,"-nullvalue")==0 ){
 -      sqlite3_snprintf(sizeof(data.nullValue), data.nullValue,
 -                       "%s",cmdline_option_value(argc,argv,++i));
 -    }else if( cli_strcmp(z,"-header")==0 ){
 -      data.showHeader = 1;
 -      ShellSetFlag(&data, SHFLG_HeaderSet);
 -     }else if( cli_strcmp(z,"-noheader")==0 ){
 -      data.showHeader = 0;
 -      ShellSetFlag(&data, SHFLG_HeaderSet);
 -    }else if( cli_strcmp(z,"-echo")==0 ){
 -      ShellSetFlag(&data, SHFLG_Echo);
 -    }else if( cli_strcmp(z,"-eqp")==0 ){
 -      data.autoEQP = AUTOEQP_on;
 -    }else if( cli_strcmp(z,"-eqpfull")==0 ){
 -      data.autoEQP = AUTOEQP_full;
 -    }else if( cli_strcmp(z,"-stats")==0 ){
 -      data.statsOn = 1;
 -    }else if( cli_strcmp(z,"-scanstats")==0 ){
 -      data.scanstatsOn = 1;
 -    }else if( cli_strcmp(z,"-backslash")==0 ){
 -      /* Undocumented command-line option: -backslash
 -      ** Causes C-style backslash escapes to be evaluated in SQL statements
 -      ** prior to sending the SQL into SQLite.  Useful for injecting
 -      ** crazy bytes in the middle of SQL statements for testing and debugging.
 -      */
 -      ShellSetFlag(&data, SHFLG_Backslash);
 -    }else if( cli_strcmp(z,"-bail")==0 ){
 -      /* No-op.  The bail_on_error flag should already be set. */
 -    }else if( cli_strcmp(z,"-version")==0 ){
 -      printf("%s %s\n", sqlite3_libversion(), sqlite3_sourceid());
 -      return 0;
 -    }else if( cli_strcmp(z,"-interactive")==0 ){
 -      stdin_is_interactive = 1;
 -    }else if( cli_strcmp(z,"-batch")==0 ){
 -      stdin_is_interactive = 0;
 -    }else if( cli_strcmp(z,"-utf8")==0 ){
  #if SHELL_WIN_UTF8_OPT
 -      console_utf8 = 1;
 -#endif /* SHELL_WIN_UTF8_OPT */
 -    }else if( cli_strcmp(z,"-heap")==0 ){
 -      i++;
 -    }else if( cli_strcmp(z,"-pagecache")==0 ){
 -      i+=2;
 -    }else if( cli_strcmp(z,"-lookaside")==0 ){
 -      i+=2;
 -    }else if( cli_strcmp(z,"-threadsafe")==0 ){
 -      i+=2;
 -    }else if( cli_strcmp(z,"-nonce")==0 ){
 -      i += 2;
 -    }else if( cli_strcmp(z,"-mmap")==0 ){
 -      i++;
 -    }else if( cli_strcmp(z,"-memtrace")==0 ){
 -      i++;
 -#ifdef SQLITE_ENABLE_SORTER_REFERENCES
 -    }else if( cli_strcmp(z,"-sorterref")==0 ){
 -      i++;
 -#endif
 -    }else if( cli_strcmp(z,"-vfs")==0 ){
 -      i++;
 -#ifdef SQLITE_ENABLE_VFSTRACE
 -    }else if( cli_strcmp(z,"-vfstrace")==0 ){
 -      i++;
 -#endif
 -#ifdef SQLITE_ENABLE_MULTIPLEX
 -    }else if( cli_strcmp(z,"-multiplex")==0 ){
 -      i++;
 +    if( console_utf8 && stdin_is_interactive ){
 +      console_prepare();
 +    }else{
 +      setBinaryMode(stdin, 0);
 +      console_utf8 = 0;
 +    }
  #endif
 -    }else if( cli_strcmp(z,"-help")==0 ){
 -      usage(1);
 -    }else if( cli_strcmp(z,"-cmd")==0 ){
 -      /* Run commands that follow -cmd first and separately from commands
 -      ** that simply appear on the command-line.  This seems goofy.  It would
 -      ** be better if all commands ran in the order that they appear.  But
 -      ** we retain the goofy behavior for historical compatibility. */
 -      if( i==argc-1 ) break;
 -      z = cmdline_option_value(argc,argv,++i);
 -      if( z[0]=='.' ){
 -        rc = do_meta_command(z, &data);
 -        if( rc && bail_on_error ) return rc==2 ? 0 : rc;
 -      }else{
 -        open_db(&data, 0);
 -        rc = shell_exec(&data, z, &zErrMsg);
 -        if( zErrMsg!=0 ){
 -          utf8_printf(stderr,"Error: %s\n", zErrMsg);
 -          if( bail_on_error ) return rc!=0 ? rc : 1;
 -        }else if( rc!=0 ){
 -          utf8_printf(stderr,"Error: unable to process SQL \"%s\"\n", z);
 -          if( bail_on_error ) return rc;
 +    if( cmdArgsCmd.nCmd > 0 ){
 +      /* cmdArgsCmd not empty; some -cmd commands are to be run first. */
 +      for( i=0; i<cmdArgsCmd.nCmd; ++i ){
 +        set_invocation_cmd(cmdArgsCmd.azCmd[i]);
 +        drc = process_input(&datai);
 +        rc = (drc>2)? 2 : drc;
 +        if( rc>0 ) goto shell_bail;
 +      }
 +    }
 +
 +    if( !argsData.readStdin && cmdArgsBare.nCmd>0 ){
 +      /* cmdArgsBare holds either "bare command" arguments or -A arguments.
 +      ** (Former are arguments not naming the DB or beginning with '-'.)
 +      ** Run whichever kind there are. */
 +#if ARCHIVE_ENABLE
 +      if( argsData.bArCmd ){
 +        open_db(&datax, OPEN_DB_ZIPFILE);
 +        drc = arDotCommand(&datax, 1, cmdArgsBare.azCmd, cmdArgsBare.nCmd);
 +      }else
 +#endif /* ARCHIVE_ENABLE */
 +      {
 +        /* Run bare command arguments like separate command-line inputs. */
 +        for(i=0; i<cmdArgsBare.nCmd && rc<2; i++){
 +          set_invocation_cmd(cmdArgsBare.azCmd[i]);
 +          drc = process_input(&datai);
 +          rc = (drc>2)? 2 : drc;
 +          if( drc > DCR_Ok ) break;
          }
        }
 -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
 -    }else if( cli_strncmp(z, "-A", 2)==0 ){
 -      if( nCmd>0 ){
 -        utf8_printf(stderr, "Error: cannot mix regular SQL or dot-commands"
 -                            " with \"%s\"\n", z);
 -        return 1;
 -      }
 -      open_db(&data, OPEN_DB_ZIPFILE);
 -      if( z[2] ){
 -        argv[i] = &z[2];
 -        arDotCommand(&data, 1, argv+(i-1), argc-(i-1));
 -      }else{
 -        arDotCommand(&data, 1, argv+i, argc-i);
 -      }
 -      readStdin = 0;
 -      break;
 -#endif
 -    }else if( cli_strcmp(z,"-safe")==0 ){
 -      data.bSafeMode = data.bSafeModePersist = 1;
 -    }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
 -      /* Acted upon in first pass. */
 +      rc = (drc>2)? 2 : drc;
 +      if( rc>0 ) goto shell_bail;
      }else{
 -      utf8_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z);
 -      raw_printf(stderr,"Use -help for a list of options.\n");
 -      return 1;
 -    }
 -    data.cMode = data.mode;
 -  }
 -#if SHELL_WIN_UTF8_OPT
 -  if( console_utf8 && stdin_is_interactive ){
 -    console_prepare();
 -  }else{
 -    setBinaryMode(stdin, 0);
 -    console_utf8 = 0;
 -  }
 -#endif
 -
 -  if( !readStdin ){
 -    /* Run all arguments that do not begin with '-' as if they were separate
 -    ** command-line inputs, except for the argToSkip argument which contains
 -    ** the database filename.
 -    */
 -    for(i=0; i<nCmd; i++){
 -      if( azCmd[i][0]=='.' ){
 -        rc = do_meta_command(azCmd[i], &data);
 -        if( rc ){
 -          free(azCmd);
 -          return rc==2 ? 0 : rc;
 +      /* Run commands received from standard input (however defined.)
 +      */
 +#ifndef SQLITE_SHELL_FIDDLE
 +      if( stdin_is_interactive ){
-         char *zHome;
 +        char *zHistory = 0;
 +        if( argsData.bQuiet>1 ){
-           /* bQuiet is almost like normal interactive, but quieter
-           ** and avoids history keeping and line editor completions. */
++          /* bQuiet>1 is almost like normal interactive, but quieter, without
++          ** prompts and avoids history keeping and line editor completions.
++          ** It exists for testing or bug repro purposes. */
 +          mainPrompt[0] = 0;
 +          continuePrompt[0] = 0;
 +        }else{
-           fprintf(STD_OUT,
-             "SQLite version %s %.19s\n" /*extra-version-info*/
-             "Enter \".help\" for usage hints.\n",
-             sqlite3_libversion(), sqlite3_sourceid()
-           );
-           if( warnInmemoryDb ){
-             fprintf(STD_OUT, "Connected to a ");
-             printBold("transient in-memory database");
-             fprintf(STD_OUT, ".\nUse \".open FILENAME\" to reopen on a "
-                    "persistent database.\n");
++          char *zHome;
++          if( argsData.bQuiet<1 ){
++            fprintf(STD_OUT,
++              "SQLite version %s %.19s\n" /*extra-version-info*/
++              "Enter \".help\" for usage hints.\n",
++              sqlite3_libversion(), sqlite3_sourceid()
++            );
++            if( warnInmemoryDb ){
++              fprintf(STD_OUT, "Connected to a ");
++              printBold("transient in-memory database");
++              fprintf(STD_OUT, ".\nUse \".open FILENAME\" to reopen on a "
++                     "persistent database.\n");
++            }
 +          }
 +          zHistory = getenv("SQLITE_HISTORY");
 +          if( zHistory ){
 +            zHistory = strdup(zHistory);
 +          }else if( (zHome = find_home_dir(0))!=0 ){
-             int nHistory = strlen30(zHome) + 20;
++            static const char zBasename[] = ".sqlite_history";
++            int nHistory = strlen30(zHome) + 2 + sizeof(zBasename);
 +            if( (zHistory = malloc(nHistory))!=0 ){
-               sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome);
++              sqlite3_snprintf(nHistory, zHistory,"%s/%s", zHome, zBasename);
 +            }
 +          }
-           if( zHistory ){ shell_read_history(zHistory); }
- #if HAVE_READLINE || HAVE_EDITLINE
++          if( zHistory ){
++            mstr_holder(zHistory);
++            shell_read_history(zHistory);
++          }
++# if HAVE_READLINE || HAVE_EDITLINE
 +          rl_attempted_completion_function = readline_completion;
- #elif HAVE_LINENOISE
++# elif HAVE_LINENOISE
 +          linenoiseSetCompletionCallback(linenoise_completion);
- #endif
++# endif
          }
 -      }else{
 -        open_db(&data, 0);
 -        echo_group_input(&data, azCmd[i]);
 -        rc = shell_exec(&data, azCmd[i], &zErrMsg);
 -        if( zErrMsg || rc ){
 -          if( zErrMsg!=0 ){
 -            utf8_printf(stderr,"Error: %s\n", zErrMsg);
 -          }else{
 -            utf8_printf(stderr,"Error: unable to process SQL: %s\n", azCmd[i]);
 +        datai.pInSource = &termInSource; /* read from stdin interactively */
 +        drc = process_input(&datai);
 +        if( !argsData.bQuiet ){
 +          if( zHistory ){
 +            shell_stifle_history(2000);
 +            shell_write_history(zHistory);
-             free(zHistory);
++            release_holder();
            }
 -          sqlite3_free(zErrMsg);
 -          free(azCmd);
 -          return rc!=0 ? rc : 1;
          }
 -      }
 -    }
 +      }else{
 +        datai.pInSource = &stdInSource; /* read from stdin without prompts */
 +        drc = process_input(&datai);
 +      }
 +#else /* !defined(SQLITE_SHELL_FIDDLE) */
 +      datai.pInSource = &fiddleInSource; /* read per set_fiddle_input_text() */
 +      drc = process_input(&datai);
 +#endif /* defined(SQLITE_SHELL_FIDDLE) */
 +      rc = (drc>2)? 2 : drc;
 +    }
++  shell_bail:
 +    forget_exit_ripper(&mainRipDest);
++    /* Next is a no-op except for "goto shell_bail" early exits. */
++    RESOURCE_FREE(mainRipDest.resDest);
    }else{
-     /* Any abrupt, stack-ripping exit arrives here. */
-     utf8_printf(STD_ERR, "Exiting with error (2) after orderly cleanup.\n");
 -    /* Run commands received from standard input
 -    */
 -    if( stdin_is_interactive ){
 -      char *zHome;
 -      char *zHistory;
 -      int nHistory;
 -      printf(
 -        "SQLite version %s %.19s\n" /*extra-version-info*/
 -        "Enter \".help\" for usage hints.\n",
 -        sqlite3_libversion(), sqlite3_sourceid()
 -      );
 -      if( warnInmemoryDb ){
 -        printf("Connected to a ");
 -        printBold("transient in-memory database");
 -        printf(".\nUse \".open FILENAME\" to reopen on a "
 -               "persistent database.\n");
 -      }
 -      zHistory = getenv("SQLITE_HISTORY");
 -      if( zHistory ){
 -        zHistory = strdup(zHistory);
 -      }else if( (zHome = find_home_dir(0))!=0 ){
 -        nHistory = strlen30(zHome) + 20;
 -        if( (zHistory = malloc(nHistory))!=0 ){
 -          sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome);
 -        }
 -      }
 -      if( zHistory ){ shell_read_history(zHistory); }
 -#if HAVE_READLINE || HAVE_EDITLINE
 -      rl_attempted_completion_function = readline_completion;
 -#elif HAVE_LINENOISE
 -      linenoiseSetCompletionCallback(linenoise_completion);
 -#endif
 -      data.in = 0;
 -      rc = process_input(&data);
 -      if( zHistory ){
 -        shell_stifle_history(2000);
 -        shell_write_history(zHistory);
 -        free(zHistory);
 -      }
 -    }else{
 -      data.in = stdin;
 -      rc = process_input(&data);
 -    }
++    /* Abrupt, stack-ripping exit arrives here (with mainRipDest forgotten.) */
++    utf8_printf(STD_ERR,"Terminating with error (2) after orderly cleanup.\n");
 +    datax.shellAbruptExit = 0x102;
    }
-  shell_bail:
-   /* All users of resource managment should have left its stack as it
-   ** was near the beginning of shell REPL execution. Verify this. */
-   assert(main_resource_mark+mainResCount==holder_mark());
-   RESOURCE_FREE(main_resource_mark);
 +
-   /**** Termination cleanup. ****/
++  /* Extract this code to preserve its value before datax disposal. */
 +  iAbruptExitCode = datax.shellAbruptExit;
++
++  /**** Termination cleanup. ****/
  #ifndef SQLITE_SHELL_FIDDLE
    /* In WASM mode we have to leave the db state in place so that
 -  ** client code can "push" SQL into it after this call returns. */
 -  free(azCmd);
 -  set_table_name(&data, 0);
 -  if( data.db ){
 -    session_close_all(&data, -1);
 -    close_db(data.db);
 -  }
 -  for(i=0; i<ArraySize(data.aAuxDb); i++){
 -    sqlite3_free(data.aAuxDb[i].zFreeOnClose);
 -    if( data.aAuxDb[i].db ){
 -      session_close_all(&data, i);
 -      close_db(data.aAuxDb[i].db);
 -    }
 -  }
 -  find_home_dir(1);
 -  output_reset(&data);
 -  data.doXdgOpen = 0;
 -  clearTempFile(&data);
 -#if !SQLITE_SHELL_IS_UTF8
 -  for(i=0; i<argcToFree; i++) free(argvToFree[i]);
 -  free(argvToFree);
 -#endif
 -  free(data.colWidth);
 -  free(data.zNonce);
 -  /* Clear the global data structure so that valgrind will detect memory
 -  ** leaks */
 -  memset(&data, 0, sizeof(data));
 -#ifdef SQLITE_DEBUG
 +  ** client code can "push" SQL into it after this call returns.
 +  ** For that build, just bypass freeing all acquired resources.
 +  */
-   /* Do this redundantly with atexit() to aid memory leak reporting,
-   ** or if CLI is embedded, to get it done before return. */
-   sqlite3_mutex_free(pGlobalDbLock);
-   pGlobalDbLock = 0;
-   main_cleanup(&datai, &datax);
- # if SHELL_DATAIO_EXT
-   cmExporter.pMethods->destruct((ExportHandler*)&cmExporter);
-   ffExporter.pMethods->destruct((ExportHandler*)&ffExporter);
- # endif
++  execution_restore(&datai, &datax);
++  RESOURCE_FREE(mark);
 +# ifdef SQLITE_DEBUG
    if( sqlite3_memory_used()>mem_main_enter ){
      utf8_printf(stderr, "Memory leaked: %u bytes\n",
                  (unsigned int)(sqlite3_memory_used()-mem_main_enter));
    }
 -#endif
 -#endif /* !SQLITE_SHELL_FIDDLE */
 +# endif
 +#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 +  /* Process exit codes to yield single shell exit code.
 +   * rc == 2 is a quit signal, resulting in no error by itself.
 +   * datax.shellAbruptExit conveyed either a normal (success or error)
 +   * exit code or an abnormal exit code. Its abnormal values take priority.
 +   */
 +  /* Check for an abnormal exit, and issue error if so. */
 +  if( iAbruptExitCode!=0 ){
 +    rc = iAbruptExitCode & 0xff;
 +    if( iAbruptExitCode>0x1ff ) raw_printf(STD_ERR,"Abnormal exit (%d)\n",rc);
 +  }else{
 +    /* rc is one of 0,1,2, mapping to 0,1,0 shellexit codes. */
 +    rc &= ~2;
 +  }
++  terminate_actions();
    return rc;
  }
  
@@@ -17231,7 -12601,7 +17322,7 @@@ int fiddle_experiment(int a,int b)
  ** Returns a pointer to the current DB handle.
  */
  sqlite3 * fiddle_db_handle(){
--  return globalDb;
++  return GLOBAL_DB;
  }
  
  /*
  sqlite3_vfs * fiddle_db_vfs(const char *zDbName){
    sqlite3_vfs * pVfs = 0;
    if(globalDb){
--    sqlite3_file_control(globalDb, zDbName ? zDbName : "main",
++    sqlite3_file_control(GLOBAL_DB, zDbName ? zDbName : "main",
                           SQLITE_FCNTL_VFS_POINTER, &pVfs);
    }
    return pVfs;
@@@ -17261,7 -12631,7 +17352,7 @@@ sqlite3 * fiddle_db_arg(sqlite3 *arg)
  ** portable enough to make real use of.
  */
  void fiddle_interrupt(void){
--  if( globalDb ) sqlite3_interrupt(globalDb);
++  active_db_interrupt();
  }
  
  /*
  */
  const char * fiddle_db_filename(const char * zDbName){
      return globalDb
--      ? sqlite3_db_filename(globalDb, zDbName ? zDbName : "main")
++      ? sqlite3_db_filename(GLOBAL_DB, zDbName ? zDbName : "main")
        : NULL;
  }
  
  */
  void fiddle_reset_db(void){
    if( globalDb ){
--    int rc = sqlite3_db_config(globalDb, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0);
--    if( 0==rc ) rc = sqlite3_exec(globalDb, "VACUUM", 0, 0, 0);
--    sqlite3_db_config(globalDb, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0);
++    int rc = sqlite3_db_config(GLOBAL_DB, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0);
++    if( 0==rc ) rc = sqlite3_exec(GLOBAL_DB, "VACUUM", 0, 0, 0);
++    sqlite3_db_config(GLOBAL_DB, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0);
    }
  }