]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Fix CLI memory leak upon loading any shell extension.
authorlarrybr <larrybr@noemail.net>
Mon, 15 May 2023 21:33:26 +0000 (21:33 +0000)
committerlarrybr <larrybr@noemail.net>
Mon, 15 May 2023 21:33:26 +0000 (21:33 +0000)
FossilOrigin-Name: b91cec479d1b43598863d7b15927054cd089f51a385e86a4e511ffef64f6cfad

1  2 
Makefile.msc
manifest
manifest.uuid
src/resmanage.c
src/resmanage.h
src/shell.c.in

diff --cc Makefile.msc
Simple merge
diff --cc manifest
index 0cf46cf6477239e5dee525a462de53c1297d10d5,6d074cb6f3757dfd08714eb18e5e39f51da084a7..1a4e359d3acb480e22fa9a3edfc49d0cefd64238
+++ b/manifest
@@@ -1,11 -1,11 +1,11 @@@
- C Extend\sCLI\sresource\smanager\susage.\sImprove\sinterface\sto\sit\sfor\sclarity\sand\ssimplicity.
- D 2023-05-13T16:54:12.397
 -C Fix\sa\sC++-style\svariable\sdeclaration\sin\sthe\sgenerate_series()\sextension.
 -D 2023-05-15T19:17:31.451
++C Fix\sCLI\smemory\sleak\supon\sloading\sany\sshell\sextension.
++D 2023-05-15T21:33:26.829
  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 ecdd1e9fd753ecc02cd52f0d256a1bd86323be6eab6a3b2a196005d54edea3d2
 -F Makefile.msc ce7bea7931e1ef96b0f44c0362074a8bb62e61a0e7cfdb05afebd928adaacc2b
++F Makefile.msc ac5dfaf082eef506a9cfc7d5f2d95338282e76dad3e37567110362925f40cf41
  F README.md e05bd8fcb45da04ab045c37f79a98654e8aa3b3b8f302cfbba80a0d510df75f7
  F VERSION 17f95ae2fdf21f0e9575eb0b0511ea63f15d71dfff431b21c2b4adbfa70cfbbf
  F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
@@@ -637,13 -634,10 +638,13 @@@ F src/pragma.h e690a356c18e98414d2e870e
  F src/prepare.c 6350675966bd0e7ac3a464af9dbfe26db6f0d4237f4e1f1acdb17b12ad371e6e
  F src/printf.c b9320cdbeca0b336c3f139fd36dd121e4167dd62b35fbe9ccaa9bab44c0af38d
  F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
- F src/resmanage.c 3b3a76d32e45471356e83dc59b67f5ea537b3381df09f550c1a9810648769799
- F src/resmanage.h 161405806c22fe80b6aa786a0f8b0962f21cd25c1966c8aaafaf497e563c84d4
++F src/resmanage.c da3a5c4597c26f6592f20fb98d7988a9be70fcc4c97875c7dc924ac8fae3188a
++F src/resmanage.h 7caffb310388a25d147018752a25a5b1a85a2c21478ec723eddc5def64a9148a
  F src/resolve.c 3e53e02ce87c9582bd7e7d22f13f4094a271678d9dc72820fa257a2abb5e4032
  F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
- F src/select.c 12aa3168be4ff175702fe0ebeaf544312be22d275d378a28e7b2fad32d552d36
- F src/shell.c.in 498b7653702e8a4391fe6c69fcd535069e7eb8033c07a2481cf817eee79882ca
+ F src/select.c 738c3a3d6929f8be66c319bad17f6b297bd60a4eb14006075c48a28487dc7786
 -F src/shell.c.in 52836b4002a2cad8095b451f0c39a6542c23a231eb0ed5e39387bc8b1f7aaa9e
++F src/shell.c.in 70257d31c7567a968aebce74374e9f8dae3b90b9fb908e9233ee6f670e60f06a
 +F src/shext_linkage.h 27dcf7624df05b2a7a6d367834339a6db3636f3035157f641f7db2ec499f8f6d
  F src/sqlite.h.in c14a4471fcd897a03631ac7ad3d05505e895e7b6419ec5b96cae9bc4df7a9fc6
  F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
  F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4
@@@ -2080,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 8751f93fa505a514d8ab7eae4f9093310ee60b90046f4632e80858001781cb31 603d9ad5012ca8996783996d7b8cd6a1aabf12b21604a2ccc137f4c2d99427b9
- R 90a259a6a8128da5d9e2c168718fd7cd
 -P 01219e69b430c8f5fea5ab6ce511ba8c9b4c9b32b6d2d36623dde99c3d3812c9
 -R ccc4d09eb3957210b78ade7e66d7ff9f
 -U drh
 -Z 1e80afa7ba4f18f8081f7f615db3b634
++P fd379f22926d55d52176b34b20e6dda2cd1218adaaed446e4945c38a5efe0fb1 1d3e008905461ebbd3ea0a862672f740fa72914d4d59fcf800e1ce56f1edfc9d
++R 8b881796fad1e627eb1d7b67d9259c6b
 +U larrybr
- Z bcc2e382aa2ea964989bed4339357d06
++Z f67a8be8d6109389bcabd9f6ab5f1a80
  # Remove this line to create a well-formed Fossil manifest.
diff --cc manifest.uuid
index 17264b230f44b6d89ef27954c1ea919ff71e4a3f,4b41cdd7ad1b38460f8345c68a7fca9d4aa87ef5..2bcd73ef09fc72f2eca8af77e6deccade0ffbaf7
@@@ -1,1 -1,1 +1,1 @@@
- fd379f22926d55d52176b34b20e6dda2cd1218adaaed446e4945c38a5efe0fb1
 -1d3e008905461ebbd3ea0a862672f740fa72914d4d59fcf800e1ce56f1edfc9d
++b91cec479d1b43598863d7b15927054cd089f51a385e86a4e511ffef64f6cfad
diff --cc src/resmanage.c
index 7dd9883de6ae58a77b936c9d6acadbef249d4798,0000000000000000000000000000000000000000..c23cd10f4def8568411f6c1930a24b0870b901d3
mode 100644,000000..100644
--- /dev/null
@@@ -1,341 -1,0 +1,347 @@@
 +/*
 +** 2023 May 8
 +**
 +** The author disclaims copyright to this source code.  In place of
 +** a legal notice, here is a blessing:
 +**
 +**    May you do good and not evil.
 +**    May you find forgiveness for yourself and forgive others.
 +**    May you share freely, never taking more than you give.
 +**
 +*************************************************************************
 +** This file implements a simple resource management package, interface
 +** and function of which is described in resmanage.h .
 +*/
 +
 +#include <stdlib.h>
 +#include <stdio.h>
 +
 +#include <assert.h>
 +#include "resmanage.h"
 +
 +/* Track how to free various held resources. */
 +typedef enum FreeableResourceKind {
 +  FRK_Indirect = 1<<15,
 +  FRK_Malloc = 0, FRK_DbConn = 1, FRK_DbStmt = 2,
 +  FRK_DbMem = 3, FRK_File = 4,
 +#if (!defined(_WIN32) && !defined(WIN32)) || !SQLITE_OS_WINRT
 +  FRK_Pipe,
 +#endif
 +#ifdef SHELL_MANAGE_TEXT
 +  FRK_Text,
 +#endif
 +  FRK_AnyRef,
 +  FRK_CustomBase /* series of values for custom freers */
 +} FreeableResourceKind;
 +
 +#if defined(_WIN32) || defined(WIN32)
 +# if !SQLITE_OS_WINRT
 +#  include <io.h>
 +#  include <fcntl.h>
 +#  undef pclose
 +#  define pclose _pclose
 +# endif
 +#else
 +extern int pclose(FILE*);
 +#endif
 +
 +typedef struct ResourceHeld {
 +  union {
 +    void *p_any;
 +    void *p_malloced;
 +    sqlite3 *p_conn;
 +    sqlite3_stmt *p_stmt;
 +    void *p_s3mem;
 +    FILE *p_stream;
 +#ifdef SHELL_MANAGE_TEXT
 +    ShellText *p_text;
 +#endif
 +    AnyResourceHolder *p_any_rh;
 +  } held;
 +  FreeableResourceKind frk;
 +} ResourceHeld;
 +
 +/* The held-resource stack. This is for single-threaded use only. */
 +static ResourceHeld *pResHold = 0;
 +static ResourceCount numResHold = 0;
 +static ResourceCount numResAlloc = 0;
 +
 +/* A small set of custom freers. It is linearly searched, used for
 +** layered heap-allocated (and other-allocated) data structures, so
 +** tends to have use limited to where slow things are happening.
 +*/
 +static ResourceCount numCustom = 0; /* number of the set */
 +static ResourceCount numCustomAlloc = 0; /* allocated space */
 +typedef void (*FreerFunction)(void *);
 +static FreerFunction *aCustomFreers = 0; /* content of set */
 +
 +const char *resmanage_oom_message = "out of memory, aborting";
 +
 +/* Info recorded in support of quit_moan(...) and stack-ripping */
 +static RipStackDest *pRipStack = 0;
 +
 +/* Current position of the held-resource stack */
 +ResourceMark holder_mark(){
 +  return numResHold;
 +}
 +
 +/* Strip resource stack then strip call stack (or exit.) */
 +void quit_moan(const char *zMoan, int errCode){
 +  RipStackDest *pRSD = pRipStack;
 +  int nFreed;
 +  if( zMoan ){
 +    fprintf(stderr, "Error: Terminating due to %s.\n", zMoan);
 +  }
 +  fprintf(stderr, "Auto-freed %d resources.\n",
 +          holder_free( (pRSD)? pRSD->resDest : 0 ));
 +  pRipStack = (pRSD)? pRSD->pPrev : 0;
 +#ifndef SHELL_OMIT_LONGJMP
 +  if( pRSD!=0 ){
 +    longjmp(pRSD->exeDest, errCode);
 +  } else
 +#endif
 +    exit(errCode);
 +}
 +
 +/* Free a single resource item. (ignorant of stack) */
 +static int free_rk( ResourceHeld *pRH ){
 +  int rv = 0;
 +  if( pRH->held.p_any == 0 ) return rv;
 +  if( pRH->frk & FRK_Indirect ){
 +    pRH->held.p_any = *(void**)pRH->held.p_any;
 +    if( pRH->held.p_any == 0 ) return rv;
 +    pRH->frk &= ~FRK_Indirect;
 +  }
 +  ++rv;
 +  switch( pRH->frk ){
 +  case FRK_Malloc:
 +    free(pRH->held.p_malloced);
 +    break;
 +  case FRK_DbConn:
 +    sqlite3_close_v2(pRH->held.p_conn);
 +    break;
 +  case FRK_DbStmt:
 +    sqlite3_clear_bindings(pRH->held.p_stmt);
 +    sqlite3_finalize(pRH->held.p_stmt);
 +    break;
 +  case FRK_DbMem:
 +    sqlite3_free(pRH->held.p_s3mem);
 +    break;
 +  case FRK_File:
 +    fclose(pRH->held.p_stream);
 +    break;
 +#if (!defined(_WIN32) && !defined(WIN32)) || !SQLITE_OS_WINRT
 +  case FRK_Pipe:
 +    pclose(pRH->held.p_stream);
 +    break;
 +#endif
 +#ifdef SHELL_MANAGE_TEXT
 +  case FRK_Text:
 +    freeText(pRH->held.p_text);
 +    break;
 +#endif
 +  case FRK_AnyRef:
 +    (pRH->held.p_any_rh->its_freer)(pRH->held.p_any_rh->pAny);
 +    break;
 +  default:
 +    {
 +      int ck = pRH->frk - FRK_CustomBase;
 +      assert(ck>=0);
 +      if( ck < numCustom ){
 +        aCustomFreers[ck]( pRH->held.p_any );
 +      }else --rv;
 +    }
 +  }
 +  pRH->held.p_any = 0;
 +  return rv;
 +}
 +
 +/* Take back a held resource pointer, leaving held as NULL. (no-op) */
 +void* take_held(ResourceMark mark, ResourceCount offset){
 +  return swap_held(mark,offset,0);
 +}
 +
 +/* Swap a held resource pointer for a new one. */
 +void* swap_held(ResourceMark mark, ResourceCount offset, void *pNew){
 +  ResourceCount rix = mark + offset;
 +  assert(rix < numResHold && numResHold > 0);
 +  if( rix < numResHold && numResHold > 0 ){
 +    void *rv = pResHold[rix].held.p_any;
 +    pResHold[rix].held.p_any = pNew;
 +    return rv;
 +  }else return 0;
 +}
 +
 +/* Reserve room for at least count additional holders, with no chance
 +** of an OOM abrupt exit. This is used internally only by this package.
 +** The return is either 1 for success, or 0 indicating an OOM failure.
 + */
 +static int more_holders_try(ResourceCount count){
 +  ResourceHeld *prh;
 +  ResourceCount newAlloc = numResHold + count;
 +  if( newAlloc < numResHold ) return 0; /* Overflow, sorry. */
 +  if( newAlloc <= numResAlloc ) return 1;
 +  prh = (ResourceHeld*)realloc(pResHold, newAlloc*sizeof(ResourceHeld));
 +  if( prh == 0 ) return 0;
 +  pResHold = prh;
 +  numResAlloc = newAlloc;
 +  return 1;
 +}
 +
 +/* Lose one or more holders. */
 +void* pop_holder(void){
 +  assert(numResHold>0);
 +  if( numResHold>0 ) return pResHold[--numResHold].held.p_any;
 +  return 0;
 +}
 +void pop_holders(ResourceCount num){
 +  assert(numResHold>=num);
 +  if( numResHold>=num ) numResHold -= num;
 +  else numResHold = 0;
 +}
 +
++/* Drop a holder while freeing its holdee. */
++void release_holder(void){
++  assert(numResHold>0);
++  free_rk(&pResHold[--numResHold]);
++}
++
 +/* Shared resource-stack pushing code */
 +static void res_hold(void *pv, FreeableResourceKind frk){
 +  ResourceHeld rh = { pv, frk };
 +  if( numResHold == numResAlloc ){
 +    ResourceCount nrn = (ResourceCount)((numResAlloc>>2) + 5);
 +    if( !more_holders_try(nrn) ){
 +      free_rk(&rh);
 +      quit_moan(resmanage_oom_message,1);
 +    }
 +  }
 +  pResHold[numResHold++] = rh;
 +}
 +
 +/* Hold a NULL (Assure swap_held() cannot fail.) */
 +void more_holders(ResourceCount more){
 +  if( !more_holders_try(more) ) quit_moan(resmanage_oom_message,1);
 +}
 +
 +/* Hold anything in the malloc() heap */
 +void* mmem_holder(void *pm){
 +  res_hold(pm, FRK_Malloc);
 +  return pm;
 +}
 +/* Hold a C string in the malloc() heap */
 +char* mstr_holder(char *z){
 +  res_hold(z, FRK_Malloc);
 +  return z;
 +}
 +/* Hold a C string in the SQLite heap */
 +char* sstr_holder(char *z){
 +  res_hold(z, FRK_DbMem);
 +  return z;
 +}
 +/* Hold a C string in the SQLite heap, reference to */
 +void sstr_ptr_holder(char **pz){
 +  assert(pz!=0);
 +  res_hold(pz, FRK_DbMem|FRK_Indirect);
 +}
 +/* Hold an open C runtime FILE */
 +void file_holder(FILE *pf){
 +  res_hold(pf, FRK_File);
 +}
 +#if (!defined(_WIN32) && !defined(WIN32)) || !SQLITE_OS_WINRT
 +/* Hold an open C runtime pipe */
 +void pipe_holder(FILE *pp){
 +  res_hold(pp, FRK_Pipe);
 +}
 +#endif
 +#ifdef SHELL_MANAGE_TEXT
 +/* a ShellText object, reference to (storage for which not managed) */
 +static void text_ref_holder(ShellText *pt){
 +  res_hold(pt, FRK_Text);
 +}
 +#endif
 +/* Hold anything together with arbitrary freeing function */
 +void* any_holder(void *pm, void (*its_freer)(void*)){
 +  int i = 0;
 +  while( i < numCustom ){
 +    if( its_freer == aCustomFreers[i] ) break;
 +    ++i;
 +  }
 +  if( i == numCustom ){
 +    size_t ncf = numCustom + 2;
 +    FreerFunction *pcf;
 +    pcf = (FreerFunction *)realloc(aCustomFreers, ncf*sizeof(FreerFunction));
 +    if( pcf!=0 ){
 +      assert(ncf < (1<<16));
 +      numCustomAlloc = (ResourceCount)ncf;
 +      aCustomFreers = pcf;
 +      aCustomFreers[numCustom++] = its_freer;
 +    }else{
 +      quit_moan(resmanage_oom_message,1);
 +    }
 +  }
 +  res_hold(pm, i + FRK_CustomBase);
 +  return pm;
 +}
 +/* Hold some SQLite-allocated memory */
 +void* smem_holder(void *pm){
 +  res_hold(pm, FRK_DbMem);
 +  return pm;
 +}
 +/* Hold a SQLite database "connection" */
 +void conn_holder(sqlite3 *pdb){
 +  res_hold(pdb, FRK_DbConn);
 +}
 +/* Hold a SQLite prepared statement */
 +void stmt_holder(sqlite3_stmt *pstmt){
 +  res_hold(pstmt, FRK_DbStmt);
 +}
 +/* Hold a SQLite prepared statement, reference to */
 +void stmt_ptr_holder(sqlite3_stmt **ppstmt){
 +  assert(ppstmt!=0);
 +  res_hold(ppstmt, FRK_DbStmt|FRK_Indirect);
 +}
 +/* Hold a reference to an AnyResourceHolder (in stack frame) */
 +void any_ref_holder(AnyResourceHolder *parh){
 +  assert(parh!=0 && parh->its_freer!=0);
 +  res_hold(parh, FRK_AnyRef);
 +}
 +
 +/* Free all held resources in excess of given resource stack mark,
 +** then return how many needed freeing. */
 +int holder_free(ResourceMark mark){
 +  int rv = 0;
 +  while( numResHold > mark ){
 +    rv += free_rk(&pResHold[--numResHold]);
 +  }
 +  if( mark==0 ){
 +    if( numResAlloc>0 ){
 +      free(pResHold);
 +      pResHold = 0;
 +      numResAlloc = 0;
 +    }
 +    if( numCustomAlloc>0 ){
 +      free(aCustomFreers);
 +      aCustomFreers = 0;
 +      numCustom = 0;
 +      numCustomAlloc = 0;
 +    }
 +  }
 +  return rv;
 +}
 +
 +/* Record a resource stack and call stack rip-to position */
 +void register_exit_ripper(RipStackDest *pRSD){
 +  assert(pRSD!=0);
 +  pRSD->pPrev = pRipStack;
 +  pRSD->resDest = holder_mark();
 +  pRipStack = pRSD;
 +}
 +/* Undo register_exit_ripper effect, back to previous state. */
 +void forget_exit_ripper(RipStackDest *pRSD){
 +  if( pRSD==0 ) pRipStack = 0;
 +  else{
 +    pRipStack = pRSD->pPrev;
 +    pRSD->pPrev = 0;
 +  }
 +}
diff --cc src/resmanage.h
index 4401a553511cc1b5b852540bf017349d8c170b82,0000000000000000000000000000000000000000..f1cd1558235d8ceccdeae8d76b0631fc24e4e860
mode 100644,000000..100644
--- /dev/null
@@@ -1,171 -1,0 +1,174 @@@
 +/*
 +** 2023 May 8
 +**
 +** The author disclaims copyright to this source code.  In place of
 +** a legal notice, here is a blessing:
 +**
 +**    May you do good and not evil.
 +**    May you find forgiveness for yourself and forgive others.
 +**    May you share freely, never taking more than you give.
 +**
 +*************************************************************************
 +** This file declares the interface of a simple resource management package
 +** which supports freeing of resources upon an abrupt, program-initiated
 +** termination of a function from somewhere in a call tree. The package is
 +** designed to be used with setjmp()/longjmp() to effect a pattern similar
 +** to the try/throw/catch available in some programming languages. (But see
 +** below regarding usage when the setmp()/longjmp() pair is unavailable.)
 +**
 +** The general scheme is that routines whose pre-return code might be
 +** bypassed, thereby leaking resources, do not rely on pre-return code
 +** to release locally held resources. Instead, they give ownership of
 +** such resources to this package, via xxxx_holder(...) calls, and use
 +** its holder_mark() and holder_free(...) functions to release locally
 +** acquired resources.
 +**
 +** For environments where setmp()/longjmp() are unavailable, (indicated by
 +** SHELL_OMIT_LONGJMP defined), the package substitutes a process exit for
 +** resumption of execution at a chosen code location. The resources in the
 +** package's held resource stack are still released. And the ability to
 +** free locally acquired resources as functions return is retained. This
 +** can simplify early function exit logic, but a body of code relying on
 +** process exit to ease error handling is unsuitable for use as a called
 +** routine within a larger application. That use is most of the reason for
 +** this package's existence.
 +*/
 +
 +#ifndef RES_MANAGE_H
 +# define RES_MANAGE_H
 +
 +#ifndef SHELL_OMIT_LONGJMP
 +# include <setjmp.h>
 +#endif
 +#include <stdio.h>
 +#include <assert.h>
 +
 +#ifdef __cplusplus
 +extern "C" {
 +#endif
 +
 +#include "sqlite3.h"
 +
 +/* Type used for marking/conveying positions within a held-resource stack */
 +typedef unsigned short ResourceMark;
 +typedef unsigned short ResourceCount;
 +
 +/* Type used to record a possible succession of recovery destinations */
 +typedef struct RipStackDest {
 +  struct RipStackDest *pPrev;
 +  ResourceMark resDest;
 +#ifndef SHELL_OMIT_LONGJMP
 +  jmp_buf exeDest;
 +#endif
 +} RipStackDest;
 +#define RIP_STACK_DEST_INIT {0}
 +
 +/* This macro effects stack mark and potential rip-back, keeping needed
 +** details in a RipStackDest object (independent of SHELL_OMIT_LONGJMP.) */
 +#ifndef SHELL_OMIT_LONGJMP
 +# define RIP_TO_HERE(RSD) (RSD.resDest=holder_mark(), setjmp(RSD.exeDest))
 +#else
 +# define RIP_TO_HERE(RSD) (RSD.resDest=holder_mark(), 0)
 +#endif
 +
 +/* Type used for generic free functions (to fib about their signature.) */
 +typedef void (*GenericFreer)(void*);
 +
 +/* Current position of the held-resource stack */
 +extern ResourceMark holder_mark();
 +
 +/* Assure no allocation failure for some more xxx_holder() calls.
 +** Note that this call may fail with an OOM abrupt exit. */
 +extern void more_holders(ResourceCount more);
 +
 +/* Lose one or more holders, without freeing anything. */
 +extern void* pop_holder(void);
 +extern void pop_holders(ResourceCount num);
 +
++/* Drop a holder while freeing its holdee. */
++extern void release_holder(void);
++
 +/*
 +** Routines for holding resources on held-resource stack together
 +** with enough information for them to be freed by this package.
 +*/
 +
 +/* xxxx_holder(...)
 +** The referenced objects are directly freed; they are stored in
 +** the heap with lifetime not bound to the caller's activation.
 +*/
 +/* anything together with arbitrary freeing function */
 +extern void* any_holder(void *pm, void (*its_freer)(void*));
 +/* anything in the malloc() heap */
 +extern void* mmem_holder(void *pm);
 +/* a C string in the malloc() heap */
 +extern char* mstr_holder(char *z);
 +/* some SQLite-allocated memory */
 +extern void* smem_holder(void *pm);
 +/* a C string in the SQLite heap */
 +extern char* sstr_holder(char *z);
 +/* a SQLite database "connection" */
 +extern void conn_holder(sqlite3 *pdb);
 +/* a SQLite prepared statement */
 +extern void stmt_holder(sqlite3_stmt *pstmt);
 +/* an open C runtime FILE */
 +extern void file_holder(FILE *);
 +#if (!defined(_WIN32) && !defined(WIN32)) || !SQLITE_OS_WINRT
 +/* an open C runtime pipe */
 +extern void pipe_holder(FILE *);
 +#endif
 +
 +/* xxxx_ptr_holder(...) or xxxx_ref_holder(...)
 +** The referenced objects are indirectly freed; they are stored
 +** in the caller's stack frame, with lifetime limited to the
 +** the caller's activation. It is a grave error to use these
 +** then fail to call holder_free() before returning. For abrupt
 +** exits, this condition is met because holder_free() is called
 +** by the abrupt exiter before the execution stack is stripped.
 +*/
 +typedef struct AnyResourceHolder {
 +  void *pAny;
 +  GenericFreer its_freer;
 +} AnyResourceHolder;
 +
 +/* a reference to an AnyResourceHolder (whose storage is not managed) */
 +extern void any_ref_holder(AnyResourceHolder *parh);
 +/* a C string in the SQLite heap, reference to */
 +extern void sstr_ptr_holder(char **pz);
 +/* a SQLite prepared statement, reference to */
 +extern void stmt_ptr_holder(sqlite3_stmt **ppstmt);
 +#ifdef SHELL_MANAGE_TEXT
 +/* a ShellText object, reference to (storage for which not managed) */
 +static void text_ref_holder(ShellText *);
 +#endif
 +
 +/* Take back a held resource pointer, leaving held as NULL. (no-op) */
 +extern void* take_held(ResourceMark mark, ResourceCount offset);
 +
 +/* Swap a held resource pointer for a new one. */
 +extern void* swap_held(ResourceMark mark, ResourceCount offset, void *pNew);
 +
 +/* Free all held resources in excess of given resource stack mark.
 +** Return count of number actually freed (rather than being 0.) */
 +extern int holder_free(ResourceMark mark);
 +
 +/* Remember execution and resource stack postion/state. This determines
 +** how far these stacks may be stripped should quit_moan(...) be called.
 +*/
 +extern void register_exit_ripper(RipStackDest *);
 +/* Forget whatever register_exit_ripper() has been recorded. */
 +extern void forget_exit_ripper(RipStackDest *);
 +
 +/* Strip resource stack and execute previously registered longjmp() as
 +** previously prepared by register_exit_ripper() call. Or, if no such
 +** prep done (or possible), strip the whole stack and exit the process.
 +*/
 +extern void quit_moan(const char *zMoan, int errCode);
 +
 +/* What the complaint will be for OOM failures and abrupt exits. */
 +extern const char *resmanage_oom_message;
 +
 +#ifdef __cplusplus
 +}  /* End of the 'extern "C"' block */
 +#endif
 +#endif /* RES_MANAGE_H */
diff --cc src/shell.c.in
index 6cc019333da7055a8554e011e9380488f6612e60,72e449840231c7623b881bb3ef1d66aa527d0f7d..8e5fff243e83835fffa14685d202f48778a3f990
@@@ -859,14 -772,6 +859,20 @@@ static void shell_out_of_memory(void)
  static void shell_check_oom(const void *p){
    if( p==0 ) shell_out_of_memory();
  }
++/* Check a SQLite result code for out-of-memory indication.
++** If that is so, terminate with an out-of-memory error.
++*/
++static void shell_check_nomem(int rc){
++  if( SQLITE_NOMEM==rc ) shell_out_of_memory();
++}
 +/* This pattern is ubiquitous and subject to change, so encapsulate it. */
 +#define SHELL_ASSIGN_OOM_CHECK(lv, pv) \
 +  do{ lv = pv; shell_check_oom(lv); }while(0)
 +
 +static void shell_newstr_assign(char **pLV, char *z){
 +  if( !z ) shell_out_of_memory();
 +  *pLV = z;
 +}
  
  /*
  ** Write I/O traces to the following stream.
@@@ -1592,8 -1349,10 +1598,10 @@@ INCLUDE ../ext/expert/sqlite3expert.
  #endif
  #if SQLITE_SHELL_HAVE_RECOVER
  INCLUDE ../ext/recover/sqlite3recover.h
+ # ifndef SQLITE_HAVE_SQLITE3R
  INCLUDE ../ext/recover/dbdata.c
  INCLUDE ../ext/recover/sqlite3recover.c
 -# endif /* SQLITE_HAVE_SQLITE3R */
++# endif /* !defined(SQLITE_HAVE_SQLITE3R) */
  #endif
  #ifdef SQLITE_SHELL_EXTSRC
  # include SHELL_STRINGIFY(SQLITE_SHELL_EXTSRC)
@@@ -1635,113 -1394,6 +1643,114 @@@ struct EQPGraph 
    char zPrefix[100];    /* Graph prefix */
  };
  
-   DotCommand *pUnknown;  /* .unknown registered for this extension */
 +/* By default, omit the extension options that are not done yet.
 + * "SHELL_EXTENSIONS" is short for "Some shell extensions are built in." */
 +#ifndef SHELL_OMIT_EXTENSIONS
 +# define SHELL_OMIT_EXTENSIONS 4
 +# define SHELL_EXTENSIONS 1
 +#else
 +# define SHELL_EXTENSIONS \
 +  (0!=((~SHELL_OMIT_EXTENSIONS) & SHELL_ALL_EXTENSIONS))
 +#endif
 +
 +#ifdef SQLITE_OMIT_LOAD_EXTENSION
 +# define SHELL_OMIT_LOAD_EXTENSION 1
 +#else
 +# define SHELL_OMIT_LOAD_EXTENSION 0
 +#endif
 +
 +/* Selectively omit features with one PP variable. Value is true iff
 +** either x is not defined or defined with 0 in bitnum bit position.
 +*/
 +#define NOT_IFDEF_BIT(x,bitnum) (x? (!(x & (1<<bitnum))) : !(x+0))
 +
 +/* Whether build will include extended input parsing option */
 +#define SHEXT_PARSING_BIT 0
 +#define SHELL_EXTENDED_PARSING \
 +  NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_PARSING_BIT)
 +/* Whether build will include runtime extension via .shxload */
 +#define SHEXT_DYNEXT_BIT 1
 +#define SHELL_DYNAMIC_EXTENSION ( !SHELL_OMIT_LOAD_EXTENSION \
 +  && NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_DYNEXT_BIT) )
 +/* Whether build will include expansion of variables in dot-commands */
 +#define SHEXT_VAREXP_BIT 2
 +#define SHELL_VARIABLE_EXPANSION \
 +  NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_VAREXP_BIT)
 +
 +#define SHELL_ALL_EXTENSIONS \
 +  (1<<SHEXT_PARSING_BIT)+(1<<SHEXT_DYNEXT_BIT)+(1<<SHEXT_VAREXP_BIT)
 +
 +/* Runtime test for shell extended parsing, given ShellInState pointer */
 +#if SHELL_EXTENDED_PARSING
 +# define SHEXT_PARSING(psi) ((psi->bExtendedDotCmds&(1<<SHEXT_PARSING_BIT))!=0)
 +#else
 +# define SHEXT_PARSING(psi) 0
 +#endif
 +
 +/* Runtime test for shell variable expansion, given ShellInState pointer */
 +#if SHELL_EXTENDED_PARSING
 +# define SHEXT_VAREXP(psi) ((psi->bExtendedDotCmds&(1<<SHEXT_VAREXP_BIT))!=0)
 +#else
 +# define SHEXT_VAREXP(psi) 0
 +#endif
 +
 +/* Enable use of ExportHandler and ImportHandler interfaces for built-in I/O */
 +#define SHELL_DATAIO_EXT 1
 +
 +#if SHELL_DYNAMIC_EXTENSION
 +
 +/* This is only used to support extensions that need this information.
 + * For example, they might need to locate and load related files. */
 +# if defined(_WIN32) || defined(WIN32)
 +static char startupDir[MAX_PATH+1] = {0};
 +# define initStartupDir() (_getcwd(startupDir,MAX_PATH)!=0)
 +#  define IS_PATH_SEP(c) ((c)=='/'||(c)=='\\')
 +# else
 +static char startupDir[PATH_MAX+1] = {0};
 +# define initStartupDir() (getcwd(startupDir, sizeof(startupDir))!=0)
 +  /* Above useless expression avoids an "unused result" warning. */
 +#  define IS_PATH_SEP(c) ((c)=='/')
 +# endif
 +
 +# ifndef SHELL_OMIT_EXTBYNAME
 +/* Is a program invocation name one used for a shell to start as extensible? */
 +static int isExtendedBasename(const char *zPgm){
 +  int ixe = (zPgm)? (int)strlen(zPgm)-1 : 0;
 +  if( ixe==0 ) return 0;
 +  while( ixe>=0 && !IS_PATH_SEP(zPgm[ixe]) ) --ixe;
 +  /* ixe is just before the basename with extension(s) */
 +  return sqlite3_strnicmp(&zPgm[ixe+1], "sqlite3x", 8)==0;
 +}
 +# else
 +#  define isExtendedBasename(pathname) 0
 +# endif
 +
 +/* Tracking and use info for loaded shell extensions
 + * An instance is kept for each shell extension that is currently loaded.
 + * They are kept in a simple list (aka dynamic array), index into which
 + * is used internally to get the extension's object. These indices are
 + * kept in the dbShell and updated there as the list content changes.
 + */
 +typedef struct ShExtInfo {
 +  ExtensionId extId;        /* The xInit function pointer */
 +  void (*extDtor)(void *);  /* Extension shutdown on exit or unload */
 +  void *pvExtObj;           /* Passed to extDtor(...) at shutdown */
 +  /* Each shell extension library registers 0 or more of its extension
 +   * implementations, interfaces to which are kept in below dynamic.
 +   * arrays. The dbShell DB keeps indices into these arrays and into
 +   * an array of this struct's instances to facilitate lookup by name
 +   * of pointers to the implementations. */
 +  int numDotCommands;
 +  DotCommand **ppDotCommands;
 +  int numExportHandlers;
 +  ExportHandler **ppExportHandlers;
 +  int numImportHandlers;
 +  ImportHandler **ppImportHandlers;
++  DotCommand *pUnknown;  /* .unknown registered for this extension (unowned) */
 +} ShExtInfo;
++#define SHEXT_INFO_INIT {0,0,NULL, 0,NULL, 0,NULL, 0,NULL, NULL}
 +#endif
 +
  /* Parameters affecting columnar mode result display (defaulting together) */
  typedef struct ColModeOpts {
    int iWrap;            /* In columnar modes, wrap lines reaching this limit */
@@@ -5878,35 -5407,32 +5887,32 @@@ static void open_db(ShellExState *psx, 
  
      /* 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);
 +      int testmode_on = ShellHasFlag(psx,SHFLG_TestingMode);
 +      sqlite3_db_config(globalDb, SQLITE_DBCONFIG_TRUSTED_SCHEMA,testmode_on,0);
 +      sqlite3_db_config(globalDb, SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0);
      }
 -
  #ifndef SQLITE_OMIT_LOAD_EXTENSION
 -    sqlite3_enable_load_extension(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);
 +    sqlite3_enable_load_extension(globalDb, 1);
 +#endif
 +    sqlite3_shathree_init(globalDb, 0, 0);
 +    sqlite3_uint_init(globalDb, 0, 0);
 +    sqlite3_decimal_init(globalDb, 0, 0);
 +    sqlite3_base64_init(globalDb, 0, 0);
 +    sqlite3_base85_init(globalDb, 0, 0);
 +    sqlite3_regexp_init(globalDb, 0, 0);
 +    sqlite3_ieee_init(globalDb, 0, 0);
 +    sqlite3_series_init(globalDb, 0, 0);
  #ifndef SQLITE_SHELL_FIDDLE
 -    sqlite3_fileio_init(p->db, 0, 0);
 -    sqlite3_completion_init(p->db, 0, 0);
 +    sqlite3_fileio_init(globalDb, 0, 0);
 +    sqlite3_completion_init(globalDb, 0, 0);
  #endif
- #if SQLITE_SHELL_HAVE_RECOVER
-     sqlite3_dbdata_init(globalDb, 0, 0);
- #endif
  #ifdef SQLITE_HAVE_ZLIB
 -    if( !p->bSafeModePersist ){
 -      sqlite3_zipfile_init(p->db, 0, 0);
 -      sqlite3_sqlar_init(p->db, 0, 0);
 +    if( !psi->bSafeModeFuture ){
 +      sqlite3_zipfile_init(globalDb, 0, 0);
 +      sqlite3_sqlar_init(globalDb, 0, 0);
      }
  #endif
 +
  #ifdef SQLITE_SHELL_EXTFUNCS
      /* Create a preprocessing mechanism for extensions to make
       * their own provisions for being built into the shell.
        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;
 +    /* This assert is maybe overly cautious for above de-dup DML, but that can
 +     * be replaced via #define's. So this check is made for debug builds. */
 +    assert(db_int(*pDb, zHasDupes)==0);
 +    rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0);
 +    rc_err_oom_die(rc);
 +    rc = sqlite3_step(pStmt);
 +    if( rc==SQLITE_ROW ){
 +      zColsSpec = smprintf("%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 = smprintf("%s", sqlite3_column_text(pStmt, 0));
 +        }else
 +          *pzRenamed = 0;
 +      }
 +    }
 +    sqlite3_finalize(pStmt);
 +    sqlite3_close(*pDb);
 +    *pDb = 0;
 +    return zColsSpec;
 +  }
 +}
 +
 +#if SHELL_DATAIO_EXT
 +
 +/*
 +** Standard ExportHandlers
 +** These implement the built-in renderers of query results.
 +** Two are provided, one for freeform results, the other for columnar results.
 +*/
 +
 +static void EH_FF_destruct(ExportHandler *pMe);
 +static void EH_CM_destruct(ExportHandler *pMe);
 +static const char *EH_FF_name(ExportHandler *pMe);
 +static const char *EH_CM_name(ExportHandler *pMe);
 +static const char *EH_help(ExportHandler *pMe, const char *zWhat);
 +static int EH_openResultsOutStream(ExportHandler *pMe,
 +                                   ShellExState *pSES, char **pzErr,
 +                                   int numArgs, char *azArgs[],
 +                                   const char * zName);
 +static int EH_FF_prependResultsOut(ExportHandler *pMe,
 +                                   ShellExState *pSES, char **pzErr,
 +                                   sqlite3_stmt *pStmt);
 +static int EH_CM_prependResultsOut(ExportHandler *pMe,
 +                                   ShellExState *pSES, char **pzErr,
 +                                   sqlite3_stmt *pStmt);
 +static int EH_FF_rowResultsOut(ExportHandler *pMe,
 +                               ShellExState *pSES, char **pzErr,
 +                               sqlite3_stmt *pStmt);
 +static int EH_CM_rowResultsOut(ExportHandler *pMe,
 +                               ShellExState *pSES, char **pzErr,
 +                               sqlite3_stmt *pStmt);
 +static int EH_FF_appendResultsOut(ExportHandler *pMe,
 +                                  ShellExState *pSES, char **pzErr,
 +                                  sqlite3_stmt *pStmt);
 +static int EH_CM_appendResultsOut(ExportHandler *pMe,
 +                                  ShellExState *pSES, char **pzErr,
 +                                  sqlite3_stmt *pStmt);
 +static void EH_closeResultsOutStream(ExportHandler *pMe,
 +                                     ShellExState *pSES,
 +                                     char **pzErr);
 +
 +static VTABLE_NAME(ExportHandler) exporter_Vtab_FF = {
 +  EH_FF_destruct,
 +  EH_FF_name,
 +  EH_help,
 +  EH_openResultsOutStream,
 +  EH_FF_prependResultsOut,
 +  EH_FF_rowResultsOut,
 +  EH_FF_appendResultsOut,
 +  EH_closeResultsOutStream
 +};
 +
 +static VTABLE_NAME(ExportHandler) exporter_Vtab_CM = {
 +  EH_CM_destruct,
 +  EH_CM_name,
 +  EH_help,
 +  EH_openResultsOutStream,
 +  EH_CM_prependResultsOut,
 +  EH_CM_rowResultsOut,
 +  EH_CM_appendResultsOut,
 +  EH_closeResultsOutStream
 +};
 +
 +typedef struct {
 +  char **azCols; /* Names of result columns */
 +  char **azVals; /* Results */
 +  int *aiTypes;  /* Result types */
 +} ColumnsInfo;
 +
 +typedef struct {
 +  VTABLE_NAME(ExportHandler) *pMethods;
 +  ShellInState *psi;
 +  int nCol;
 +  sqlite3_uint64 nRow;
 +  void *pData;
 +  void *pRowInfo;
 +  ColumnsInfo colInfo;
 +} BuiltInFFExporter;
 +#define BI_FF_EXPORTER_INIT(psi) { & exporter_Vtab_FF, psi, 0, 0, 0, 0 }
 +
 +typedef struct {
 +  VTABLE_NAME(ExportHandler) *pMethods;
 +  ShellInState *psi;
 +  int nCol;
 +  sqlite3_uint64 nRow;
 +  void *pData;
 +  void *pRowInfo;
 +  const char *colSep;
 +  const char *rowSep;
 +} BuiltInCMExporter;
 +#define BI_CM_EXPORTER_INIT(psi) { & exporter_Vtab_CM, psi, 0, 0, 0, 0 }
 +
 +static void EH_FF_destruct(ExportHandler *pMe){
 +  /* This serves two purposes: idempotent reinitialize, and final takedown */
 +  BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
 +  if( pbie->nCol!=0 ){
 +    sqlite3_free(pbie->pData);
 +    pbie->pData = 0;
 +  }
 +  pbie->nRow = 0;
 +  pbie->nCol = 0;
 +}
 +
 +static const char *zEmpty = "";
 +
 +static void EH_CM_destruct(ExportHandler *pMe){
 +  /* This serves two purposes: idempotent reinitialize, and final takedown */
 +  BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
 +  if( pbie->nCol!=0 ){
 +    sqlite3_uint64 nData = pbie->nCol * (pbie->nRow + 1), i;
 +    const char *zNull = pbie->psi->nullValue;
 +    char **azData = (char**)pbie->pData;
 +    for(i=0; i<nData; i++){
 +      char *z = azData[i];
 +      if( z!=zEmpty && z!=zNull ) sqlite3_free(z);
 +    }
 +    sqlite3_free(pbie->pData);
 +    sqlite3_free(pbie->pRowInfo);
 +    pbie->pData = 0;
 +    pbie->pRowInfo = 0;
 +  }
 +  pbie->nCol = 0;
 +  pbie->nRow = 0;
 +  pbie->colSep = 0;
 +  pbie->rowSep = 0;
 +}
 +
 +static const char *zModeName(ShellInState *psi){
 +  int mi = psi->mode;
 +  return (mi>=0 && mi<MODE_COUNT_OF)? modeDescr[mi].zModeName : 0;
 +}
 +static const char *EH_FF_name(ExportHandler *pMe){
 +  return zModeName(((BuiltInFFExporter*)pMe)->psi);
 +}
 +static const char *EH_CM_name(ExportHandler *pMe){
 +  return zModeName(((BuiltInCMExporter*)pMe)->psi);
 +}
 +
 +static const char *EH_help(ExportHandler *pMe, const char *zWhat){
 +  (void)(pMe);
 +  (void)(zWhat);
 +  return 0;
 +}
 +
 +static int EH_openResultsOutStream(ExportHandler *pMe,
 +                                   ShellExState *pSES, char **pzErr,
 +                                   int numArgs, char *azArgs[],
 +                                   const char * zName){
 +  /* The built-in exporters have a predetermined destination, and their
 +   * action is set by the shell state .mode member, so this method has
 +   * nothing to do. For similar reasons, the shell never calls it. That
 +   * could change if .mode command functionality is moved to here.
 +   */
 +  (void)(pMe);
 +  (void)(pSES);
 +  (void)(pzErr);
 +  (void)(numArgs);
 +  (void)(azArgs);
 +  (void)(zName);
 +  return 0;
 +}
 +
 +static int EH_CM_prependResultsOut(ExportHandler *pMe,
 +                                   ShellExState *psx, char **pzErr,
 +                                   sqlite3_stmt *pStmt){
 +  BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
 +  ShellInState *psi = ISS(psx);
 +  sqlite3_int64 nRow = 0;
 +  char **azData = 0;
 +  sqlite3_int64 nAlloc = 0;
 +  char *abRowDiv = 0;
 +  const unsigned char *uz;
 +  const char *z;
 +  char **azQuoted = 0;
 +  int rc;
 +  sqlite3_int64 i, nData;
 +  int j, w, n;
 +  const unsigned char **azNextLine = 0;
 +  int bNextLine = 0;
 +  int bMultiLineRowExists = 0;
 +  int bw = psi->cmOpts.bWordWrap;
 +  int nColumn = sqlite3_column_count(pStmt);
 +
 +  if( nColumn==0 ){
 +    rc = sqlite3_step(pStmt);
 +    assert(rc!=SQLITE_ROW);
 +    return rc;
 +  }
 +  EH_CM_destruct(pMe);
 +
 +  nAlloc = nColumn*4;
 +  if( nAlloc<=0 ) nAlloc = 1;
 +  azData = sqlite3_malloc64( nAlloc*sizeof(char*) );
 +  shell_check_oom(azData);
 +  azNextLine = sqlite3_malloc64( nColumn*sizeof(char*) );
 +  shell_check_oom((void*)azNextLine);
 +  memset((void*)azNextLine, 0, nColumn*sizeof(char*) );
 +  if( psi->cmOpts.bQuote ){
 +    azQuoted = sqlite3_malloc64( nColumn*sizeof(char*) );
 +    shell_check_oom(azQuoted);
 +    memset(azQuoted, 0, nColumn*sizeof(char*) );
 +  }
 +  abRowDiv = sqlite3_malloc64( nAlloc/nColumn );
 +  shell_check_oom(abRowDiv);
 +  if( nColumn>psx->numWidths ){
 +    psx->pSpecWidths = realloc(psx->pSpecWidths, (nColumn+1)*2*sizeof(int));
 +    shell_check_oom(psx->pSpecWidths);
 +    for(i=psx->numWidths; i<nColumn; i++) psx->pSpecWidths[i] = 0;
 +    psx->numWidths = nColumn;
 +    psx->pHaveWidths = &psx->pSpecWidths[nColumn];
 +  }
 +  memset(psx->pHaveWidths, 0, nColumn*sizeof(int));
 +  for(i=0; i<nColumn; i++){
 +    w = psx->pSpecWidths[i];
 +    if( w<0 ) w = -w;
 +    psx->pHaveWidths[i] = w;
 +  }
 +  for(i=0; i<nColumn; i++){
 +    const unsigned char *zNotUsed;
 +    int wx = psx->pSpecWidths[i];
 +    if( wx==0 ){
 +      wx = psi->cmOpts.iWrap;
 +    }
 +    if( wx<0 ) wx = -wx;
 +    uz = (const unsigned char*)sqlite3_column_name(pStmt,i);
 +    azData[i] = translateForDisplayAndDup(uz, &zNotUsed, wx, bw);
 +  }
 +  while( bNextLine || SQLITE_ROW==sqlite3_step(pStmt) ){
 +    int useNextLine = bNextLine;
 +    bNextLine = 0;
 +    if( (nRow+2)*nColumn >= nAlloc ){
 +      nAlloc *= 2;
 +      azData = sqlite3_realloc64(azData, nAlloc*sizeof(char*));
 +      shell_check_oom(azData);
 +      abRowDiv = sqlite3_realloc64(abRowDiv, nAlloc/nColumn);
 +      shell_check_oom(abRowDiv);
 +    }
 +    abRowDiv[nRow] = 1;
 +    nRow++;
 +    for(i=0; i<nColumn; i++){
 +      int wx = psx->pSpecWidths[i];
 +      if( wx==0 ){
 +        wx = psi->cmOpts.iWrap;
 +      }
 +      if( wx<0 ) wx = -wx;
 +      if( useNextLine ){
 +        uz = azNextLine[i];
 +        if( uz==0 ) uz = (u8*)zEmpty;
 +      }else if( psi->cmOpts.bQuote ){
 +        sqlite3_free(azQuoted[i]);
 +        azQuoted[i] = quoted_column(pStmt,i);
 +        uz = (const unsigned char*)azQuoted[i];
 +      }else{
 +        uz = (const unsigned char*)sqlite3_column_text(pStmt,i);
 +        if( uz==0 ) uz = (u8*)psi->nullValue;
 +      }
 +      azData[nRow*nColumn + i]
 +        = translateForDisplayAndDup(uz, &azNextLine[i], wx, bw);
 +      if( azNextLine[i] ){
 +        bNextLine = 1;
 +        abRowDiv[nRow-1] = 0;
 +        bMultiLineRowExists = 1;
 +      }
 +    }
 +  }
 +  sqlite3_free((void*)azNextLine);
 +  if( azQuoted ){
 +    for(i=0; i<nColumn; i++) sqlite3_free(azQuoted[i]);
 +    sqlite3_free(azQuoted);
 +  }
 +  if( nRow==0 ){
 +    EH_CM_destruct(pMe);
 +    return SQLITE_DONE;
 +  }
 +
 +  nData = nColumn*(nRow+1);
 +
 +  for(i=0; i<nData; i++){
 +    z = azData[i];
 +    if( z==0 ) z = (char*)zEmpty;
 +    n = strlenChar(z);
 +    j = i%nColumn;
 +    if( n>psx->pHaveWidths[j] ) psx->pHaveWidths[j] = n;
 +  }
 +  if( seenInterrupt ) goto done;
 +  switch( psi->cMode ){
 +    case MODE_Column: {
 +      pbie->colSep = "  ";
 +      pbie->rowSep = "\n";
 +      if( psi->showHeader ){
 +        for(i=0; i<nColumn; i++){
 +          w = psx->pHaveWidths[i];
 +          if( psx->pSpecWidths[i]<0 ) w = -w;
 +          utf8_width_print(psi->out, w, azData[i]);
 +          fputs(i==nColumn-1?"\n":"  ", psi->out);
 +        }
 +        for(i=0; i<nColumn; i++){
 +          print_dashes(psi->out, psx->pHaveWidths[i]);
 +          fputs(i==nColumn-1?"\n":"  ", psi->out);
 +        }
 +      }
 +      break;
 +    }
 +    case MODE_Table: {
 +      pbie->colSep = " | ";
 +      pbie->rowSep = " |\n";
 +      print_row_separator(psx, nColumn, "+");
 +      fputs("| ", psi->out);
 +      for(i=0; i<nColumn; i++){
 +        w = psx->pHaveWidths[i];
 +        n = strlenChar(azData[i]);
 +        utf8_printf(psi->out, "%*s%s%*s",
 +                    (w-n)/2, "", azData[i], (w-n+1)/2, "");
 +        fputs(i==nColumn-1?" |\n":" | ", psi->out);
 +      }
 +      print_row_separator(psx, nColumn, "+");
 +      break;
 +    }
 +    case MODE_Markdown: {
 +      pbie->colSep = " | ";
 +      pbie->rowSep = " |\n";
 +      fputs("| ", psi->out);
 +      for(i=0; i<nColumn; i++){
 +        w = psx->pHaveWidths[i];
 +        n = strlenChar(azData[i]);
 +        utf8_printf(psi->out, "%*s%s%*s",
 +                    (w-n)/2, "", azData[i], (w-n+1)/2, "");
 +        fputs(i==nColumn-1?" |\n":" | ", psi->out);
 +      }
 +      print_row_separator(psx, nColumn, "|");
 +      break;
 +    }
 +    case MODE_Box: {
 +      pbie->colSep = " " BOX_13 " ";
 +      pbie->rowSep = " " BOX_13 "\n";
 +      print_box_row_separator(psx, nColumn, BOX_23, BOX_234, BOX_34);
 +      utf8_printf(psi->out, BOX_13 " ");
 +      for(i=0; i<nColumn; i++){
 +        w = psx->pHaveWidths[i];
 +        n = strlenChar(azData[i]);
 +        utf8_printf(psi->out, "%*s%s%*s%s",
 +            (w-n)/2, "", azData[i], (w-n+1)/2, "",
 +            i==nColumn-1?" "BOX_13"\n":" "BOX_13" ");
 +      }
 +      print_box_row_separator(psx, nColumn, BOX_123, BOX_1234, BOX_134);
 +      break;
 +    }
 +  }
 + done:
 +  pbie->nCol = nColumn;
 +  pbie->pData = azData;
 +  pbie->nRow = nRow;
 +  if( bMultiLineRowExists ){
 +    pbie->pRowInfo = abRowDiv;
 +  }else{
 +    pbie->pRowInfo = 0;
 +    sqlite3_free(abRowDiv);
 +  }
 +  if( seenInterrupt ){
 +    EH_CM_destruct(pMe);
 +    return SQLITE_INTERRUPT;
 +  }
 +  return SQLITE_OK;
 +}
 +
 +static int EH_CM_rowResultsOut(ExportHandler *pMe,
 +                               ShellExState *psx, char **pzErr,
 +                               sqlite3_stmt *pStmt){
 +  BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
 +  ShellInState *psi = pbie->psi;
 +  sqlite3_int64 nRow = pbie->nRow;
 +  int nColumn = pbie->nCol, j, w;
 +  char **azData = (char**)(pbie->pData);
 +  sqlite3_int64 nData = (nRow+1)*nColumn, i;
 +  char *abRowDiv = pbie->pRowInfo;
 +  const char *z;
 +
 +  (void)(pzErr);
 +  (void)(pStmt);
 +  if( nRow==0 || nColumn==0 ) return SQLITE_INTERRUPT;
 +  for(i=nColumn, j=0; i<nData; i++, j++){
 +    if( j==0 && psi->cMode!=MODE_Column ){
 +      utf8_printf(psi->out, "%s", psi->cMode==MODE_Box?BOX_13" ":"| ");
 +    }
 +    z = azData[i];
 +    if( z==0 ) z = zEmpty;
 +    w = psx->pHaveWidths[j];
 +    if( psx->pSpecWidths[j]<0 ) w = -w;
 +    utf8_width_print(psi->out, w, z);
 +    if( j==nColumn-1 ){
 +      utf8_printf(psi->out, "%s", pbie->rowSep);
 +      if( abRowDiv!=0 && abRowDiv[i/nColumn-1] && i+1<nData ){
 +        if( psi->cMode==MODE_Table ){
 +          print_row_separator(psx, nColumn, "+");
 +        }else if( psi->cMode==MODE_Box ){
 +          print_box_row_separator(psx, nColumn, BOX_123, BOX_1234, BOX_134);
 +        }else if( psi->cMode==MODE_Column ){
 +          raw_printf(psi->out, "\n");
 +        }
 +      }
 +      j = -1;
 +      if( seenInterrupt ){
 +        EH_CM_destruct(pMe);
 +        return SQLITE_INTERRUPT;
 +      }
 +    }else{
 +      utf8_printf(psi->out, "%s", pbie->colSep);
 +    }
 +  }
 +  return SQLITE_DONE;
 +}
 +
 +static int EH_CM_appendResultsOut(ExportHandler *pMe,
 +                                  ShellExState *psx, char **pzErr,
 +                                  sqlite3_stmt *pStmt){
 +  BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
 +  ShellInState *psi = ISS(psx);
 +  sqlite3_int64 nRow = pbie->nRow;
 +  int nColumn = pbie->nCol;
 +  char **azData = (char**)(pbie->pData);
 +  sqlite3_int64 nData = (nRow+1)*nColumn;
 +
 +  if( nRow==0 || nColumn==0 ) return SQLITE_INTERRUPT;
 +
 +  if( psi->cMode==MODE_Table ){
 +    print_row_separator(psx, nColumn, "+");
 +  }else if( psi->cMode==MODE_Box ){
 +    print_box_row_separator(psx, nColumn, BOX_12, BOX_124, BOX_14);
 +  }
 +  EH_CM_destruct(pMe);
 +  return SQLITE_OK;
 +}
 +
 +static int EH_FF_prependResultsOut(ExportHandler *pMe,
 +                                   ShellExState *pSES, char **pzErr,
 +                                   sqlite3_stmt *pStmt){
 +  BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
 +  int nc = sqlite3_column_count(pStmt);
 +  int rc;
 +
 +  pbie->pMethods->destruct(pMe);
 +  if( nc>0 ){
 +    /* allocate space for col name ptr, value ptr, and type */
 +    pbie->pData = sqlite3_malloc64(3*nc*sizeof(const char*) + 1);
 +    if( !pbie->pData ){
 +      shell_out_of_memory();
 +    }else{
 +      ColumnsInfo ci
 +        = { (char **)pbie->pData, &ci.azCols[nc], (int *)&ci.azVals[nc] };
 +      int i;
 +      assert(sizeof(int) <= sizeof(char *));
 +      pbie->nCol = nc;
 +      pbie->colInfo = ci;
 +      /* save off ptrs to column names */
 +      for(i=0; i<nc; i++){
 +        pbie->colInfo.azCols[i] = (char *)sqlite3_column_name(pStmt, i);
 +      }
 +    }
 +    return SQLITE_OK;
 +  }
 +  rc = sqlite3_step(pStmt);
 +  assert(rc!=SQLITE_ROW);
 +  return rc;
 +}
 +
 +static int EH_FF_rowResultsOut(ExportHandler *pMe,
 +                               ShellExState *pSES, char **pzErr,
 +                               sqlite3_stmt *pStmt){
 +  BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
 +  ShellInState *psi = ISS(pSES);
 +  int rc = sqlite3_step(pStmt);
 +  int i, x, nc = pbie->nCol;
 +  if( rc==SQLITE_ROW ){
 +    ColumnsInfo *pc = &pbie->colInfo;
 +    sqlite3_uint64 nr = ++(pbie->nRow);
 +    for( i=0; i<nc; ++i ){
 +      pc->aiTypes[i] = x = sqlite3_column_type(pStmt, i);
 +      if( x==SQLITE_BLOB
 +          && (psi->cMode==MODE_Insert || psi->cMode==MODE_Quote) ){
 +        pc->azVals[i] = "";
 +      }else{
 +        pc->azVals[i] = (char*)sqlite3_column_text(pStmt, i);
 +      }
 +      if( !pc->azVals[i] && (x!=SQLITE_NULL) ){
 +        rc = SQLITE_NOMEM;
 +        break; /* from for */
 +      }
 +    }
 +    /* if data and types extracted successfully... */
 +    if( SQLITE_ROW==rc ){
 +      /* call the supplied callback with the result row data */
 +      if( shell_callback(pSES, nc, pc->azVals, pc->azCols, pc->aiTypes) ){
 +        rc = SQLITE_ABORT;
 +      }
 +    }
 +  }
 +  return rc;
 +}
 +
 +static int EH_FF_appendResultsOut(ExportHandler *pMe,
 +                                  ShellExState *pSES, char **pzErr,
 +                                  sqlite3_stmt *pStmt){
 +  BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
 +  ShellInState *psi = ISS(pSES);
 +  if( psi->cMode==MODE_Json ){
 +    fputs("]\n", psi->out);
 +  }else if( psi->cMode==MODE_Count ){
 +    utf8_printf(psi->out, "%llu row%s\n", pbie->nRow, pbie->nRow!=1 ? "s" : "");
 +  }
 +  EH_FF_destruct(pMe);
 +  return SQLITE_OK;
 +}
 +
 +static void EH_closeResultsOutStream(ExportHandler *pMe,
 +                                     ShellExState *pSES,
 +                                     char **pzErr){
 +  /* The built-in exporters have a predetermined destination which is
 +   * never "closed", so this method has nothing to do. For similar
 +   * reasons, it is not called by the shell.
 +   */
 +  (void)(pMe);
 +  (void)(pSES);
 +  (void)(pzErr);
 +}
 +#endif /* SHELL_DATAIO_EXT */
 +
 +#if SHELL_DYNAMIC_EXTENSION
 +
 +/* Ensure there is room in loaded extension info list for one being loaded.
 + * On exit, psi->ixExtPending can be used to index into psi->pShxLoaded.
 + */
 +static ShExtInfo *pending_ext_info(ShellInState *psi){
 +  int ixpe = psi->ixExtPending;
 +  assert(ixpe!=0);
 +  if( ixpe >= psi->numExtLoaded ){
 +    psi->pShxLoaded = sqlite3_realloc(psi->pShxLoaded,
 +                                      (ixpe+1)*sizeof(ShExtInfo));
 +    shell_check_oom(psi->pShxLoaded);
 +    ++psi->numExtLoaded;
 +    memset(psi->pShxLoaded+ixpe, 0, sizeof(ShExtInfo));
 +  }
 +  return &psi->pShxLoaded[ixpe];
 +}
 +
 +/* Register a dot-command, to be called during extension load/init. */
 +static int register_dot_command(ShellExState *p,
 +                                ExtensionId eid, DotCommand *pMC){
 +  ShellInState *psi = ISS(p);
 +  ShExtInfo *psei = pending_ext_info(psi);
 +  const char *zSql
 +    = "INSERT INTO "SHELL_DISP_TAB"(name, extIx, cmdIx) VALUES(?, ?, ?)";
 +  int ie = psi->ixExtPending;
 +  assert(psi->pShxLoaded!=0 && p->dbShell!=0);
 +  if( pMC==0 ) return SQLITE_ERROR;
 +  else{
 +    const char *zName = pMC->pMethods->name(pMC);
 +    sqlite3_stmt *pStmt;
 +    int nc = psei->numDotCommands;
 +    int rc;
 +    if( psei->extId!=0 && psei->extId!=eid ) return SQLITE_MISUSE;
 +    psei->extId = eid;
 +    rc = sqlite3_prepare_v2(p->dbShell, zSql, -1, &pStmt, 0);
 +    if( rc!=SQLITE_OK ) return rc;
 +    psei->ppDotCommands
 +      = sqlite3_realloc(psei->ppDotCommands, (nc+1)*sizeof(DotCommand *));
 +    shell_check_oom(psei->ppDotCommands);
 +    sqlite3_bind_text(pStmt, 1, zName, -1, 0);
 +    sqlite3_bind_int(pStmt, 2, ie);
 +    sqlite3_bind_int(pStmt, 3, nc);
 +    rc = sqlite3_step(pStmt);
 +    sqlite3_finalize(pStmt);
 +    if( rc==SQLITE_DONE ){
 +      psei->ppDotCommands[nc++] = pMC;
 +      psei->numDotCommands = nc;
 +      notify_subscribers(psi, NK_NewDotCommand, pMC);
 +      if( cli_strcmp("unknown", zName)==0 ){
 +        psi->pUnknown = pMC;
 +        psei->pUnknown = pMC;
 +      }
 +      return SQLITE_OK;
 +    }else{
 +      psei->ppDotCommands[nc] = 0;
 +    }
 +  }
 +  return SQLITE_ERROR;
 +}
 +
 +/* Register an output data display (or other disposition) mode */
 +static int register_exporter(ShellExState *p,
 +                             ExtensionId eid, ExportHandler *pEH){
 +  return SQLITE_ERROR;
 +}
 +
 +/* Register an import variation from (various sources) for .import */
 +static int register_importer(ShellExState *p,
 +                             ExtensionId eid, ImportHandler *pIH){
 +  return SQLITE_ERROR;
 +}
 +
 +/* See registerScripting API in shext_linkage.h */
 +static int register_scripting(ShellExState *p, ExtensionId eid,
 +                              ScriptSupport *pSS){
 +  ShellInState *psi = ISS(p);
 +  if( psi->scriptXid!=0 || psi->script!=0 ){
 +    /* Scripting support already provided. Only one provider is allowed. */
 +    return SQLITE_BUSY;
 +  }
 +  if( eid==0 || pSS==0 || psi->ixExtPending==0 ){
 +    /* Scripting addition allowed only when sqlite3_*_init() runs. */
 +    return SQLITE_MISUSE;
 +  }
 +  psi->script = pSS;
 +  psi->scriptXid = eid;
 +  return SQLITE_OK;
 +}
 +
 +/* See registerAdHocCommand API in shext_linkage.h re detailed behavior.
 + * Depending on zHelp==0, either register or unregister ad-hoc treatment
 + * of zName for this extension (identified by eid.)
 + */
 +static int register_adhoc_command(ShellExState *p, ExtensionId eid,
 +                                  const char *zName, const char *zHelp){
 +  ShellInState *psi = ISS(p);
 +  u8 bRegNotRemove = zHelp!=0;
 +  const char *zSql = bRegNotRemove
 +    ? "INSERT OR REPLACE INTO "SHELL_AHELP_TAB
 +    "(name, extIx, helpText) VALUES(?, ?, ?||?||?)"
 +    : "DELETE FROM "SHELL_AHELP_TAB" WHERE name=? AND extIx=?";
 +  sqlite3_stmt *pStmt;
 +  int rc, ie;
 +
 +  assert(psi->pShxLoaded!=0 && p->dbShell!=0);
 +  for( ie=psi->numExtLoaded-1; ie>0; --ie ){
 +    if( psi->pShxLoaded[ie].extId==eid ) break;
 +  }
 +  if( !zName || ie==0 || psi->pShxLoaded[ie].pUnknown==0 ) return SQLITE_MISUSE;
 +  rc = sqlite3_prepare_v2(p->dbShell, zSql, -1, &pStmt, 0);
 +  if( rc!=SQLITE_OK ) return rc;
 +  sqlite3_bind_text(pStmt, 1, zName, -1, 0);
 +  sqlite3_bind_int(pStmt, 2, ie);
 +  if( bRegNotRemove ){
 +    int nc = strlen30(zHelp);
 +    char cLead = *zHelp;
 +    /* Add leading '.' if no help classifier present. */
 +    const char *zCL = (cLead!='.' && cLead!=',')? "." : "";
 +    /* Add trailing newline if not already there. */
 +    const char *zLE = (nc>0 && zHelp[nc-1]!='\n')? "\n" : "";
 +    sqlite3_bind_text(pStmt, 3, zCL, -1, 0);
 +    sqlite3_bind_text(pStmt, 4, zHelp, -1, 0);
 +    sqlite3_bind_text(pStmt, 5, zLE, -1, 0);
 +  }
 +  rc = sqlite3_step(pStmt);
 +  sqlite3_finalize(pStmt);
 +  return (rc==SQLITE_DONE)? SQLITE_OK : SQLITE_ERROR;
 +}
 +
 +/*
 + * Subscribe to (or unsubscribe from) messages about various changes.
 + * Unsubscribe, effected when nkMin==NK_Unsubscribe, always succeeds.
 + * Return SQLITE_OK on success, or one of these error codes:
 + * SQLITE_ERROR when the nkMin value is unsupported by this host;
 + * SQLITE_NOMEM when a required allocation failed; or
 + * SQLITE_MISUSE when the provided eid or eventHandler is invalid.
 + */
 +static int subscribe_events(ShellExState *p, ExtensionId eid, void *pvUserData,
 +                            NoticeKind nkMin, ShellEventNotify eventHandler){
 +  ShellInState *psi = ISS(p);
 +  struct EventSubscription *pes = psi->pSubscriptions;
 +  struct EventSubscription *pesLim = pes + psi->numSubscriptions;
 +  if( nkMin==NK_Unsubscribe ){
 +    /* unsubscribe (if now subscribed) */
 +    while( pes < pesLim ){
 +      if( (eventHandler==0 || eventHandler==pes->eventHandler)
 +          && (pes->eid==0 || pes->eid==eid)
 +          && (eid!=0 || eventHandler!=0 ||/*for shell use*/ pvUserData==p ) ){
 +        int nLeft = pesLim - pes;
 +        assert(pes->eventHandler!=0);
 +        pes->eventHandler(pes->pvUserData, NK_Unsubscribe, pes->eid, p);
 +        if( nLeft>1 ) memmove(pes, pes+1, (nLeft-1)*sizeof(*pes));
 +        --pesLim;
 +        --psi->numSubscriptions;
 +      }else{
 +        ++pes;
 +      }
 +    }
 +    if( psi->numSubscriptions==0 ){
 +      sqlite3_free(psi->pSubscriptions);
 +      psi->pSubscriptions = 0;
 +    }
 +    return SQLITE_OK;
 +  }else{
 +    /* subscribe only if minimum NoticeKind supported by this host */
 +    if( nkMin > NK_CountOf ) return SQLITE_ERROR;
 +    if( eventHandler==0 || eid==0 ) return SQLITE_MISUSE;
 +    while( pes < pesLim ){
 +      /* Never add duplicate handlers, but may renew their user data. */
 +      if( pes->eid==eid && pes->eventHandler==eventHandler ){
 +        pes->pvUserData = pvUserData;
 +        return SQLITE_OK;
 +      }
 +      ++pes;
 +    }
 +    assert(pes==pesLim);
 +    pes = sqlite3_realloc(psi->pSubscriptions,
 +                          (psi->numSubscriptions+1)*sizeof(*pes));
 +    if( pes==0 ) return SQLITE_NOMEM;
 +    psi->pSubscriptions = pes;
 +    pes += (psi->numSubscriptions++);
 +    pes->eid = eid;
 +    pes->pvUserData = pvUserData;
 +    pes->eventHandler = eventHandler;
 +    return SQLITE_OK;
 +  }
 +}
 +
 +/*
 + * Unsubscribe all event listeners having an ExtensionId > 0. This is
 + * done just prior closing the shell DB (when dynamic extensions will
 + * be unloaded and accessing them in any way is good for a crash.)
 + */
 +static void unsubscribe_extensions(ShellInState *psi){
 +  ShellExState *psx = XSS(psi);
 +  int esix = 0;
 +
 +  if( psi->numExtLoaded<=1 ) return; /* Ignore shell pseudo-extension. */
 +  while( esix<psi->numSubscriptions ){
 +    struct EventSubscription *pes = psi->pSubscriptions+esix;
 +    if( pes->eid > 0 ){
 +      int nsin = psi->numSubscriptions;
 +      subscribe_events(psx, pes->eid, psx, NK_Unsubscribe, 0);
 +      esix = esix + 1 + (psi->numSubscriptions - nsin);
 +    }else ++esix;
 +  }
 +}
 +
 +static struct InSource *currentInputSource(ShellExState *p){
 +  return ISS(p)->pInSource;
 +}
 +
 +static int nowInteractive(ShellExState *p){
 +  return INSOURCE_IS_INTERACTIVE(ISS(p)->pInSource);
 +}
 +
 +static const char *shellInvokedAs(void){
 +  return Argv0;
 +}
 +
 +static const char *shellStartupDir(void){
 +  return startupDir;
 +}
 +
 +static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths);
 +static DotCommand * findDotCommand(const char *, ShellExState *, int *);
 +static DotCmdRC runDotCommand(DotCommand*, char *[], int na, ShellExState*);
 +
 +static ExtensionHelpers extHelpers = {
 +  13,
 +  {
 +    failIfSafeMode,
 +    utf8_out_printf,
 +    currentInputSource,
 +    strLineGet,
 +    findDotCommand,
 +    runDotCommand,
 +    setColumnWidths,
 +    nowInteractive,
 +    shellInvokedAs,
 +    shellStartupDir,
 +    one_input_line,
 +    free_input_line,
 +    sqlite3_enable_load_extension,
 +    0
 +  }
 +};
 +
 +static ShellExtensionAPI shellExtAPI = {
 +  &extHelpers, 6, {
 +    register_dot_command,
 +    register_exporter,
 +    register_importer,
 +    register_scripting,
 +    subscribe_events,
 +    register_adhoc_command,
 +    0
 +  }
 +};
 +
 +/* This SQL function provides a way for a just-loaded shell extension to
 + * obtain a ShellExtensionLink pointer from the shell core while using
 + * the same sqlite3_load_extension API used for SQLite extensions.
 + *
 + * (It is also useful for debugging a shell extension, as a breakpoint
 + * on it will be hit soon after loading and before real work is done.)
 + */
 +static void shell_linkage(
 +  sqlite3_context *context,
 +  int argc,
 +  sqlite3_value **argv
 +){
 +  int linkKind = 0;
 +  void *pv;
 +  if( argc>0 ){
 +    linkKind = sqlite3_value_int(argv[0]);
 +  }
 +  switch (linkKind){
 +  case 0:
 +    pv = sqlite3_user_data(context);
 +    break;
 +  case 1:
 +    pv = &extHelpers;
 +    break;
 +  case 2:
 +    pv = &shellExtAPI;
 +    break;
 +  default:
 +    pv = 0;
 +  }
 +  if( pv==0 ) sqlite3_result_null(context);
 +  else sqlite3_result_pointer(context, pv, SHELLEXT_API_POINTERS, 0);
 +}
 +
++/* Free the memory held by a ShExtInfo object but not the object itself.
++ * No notifications associated with takedown and termination are done. */
++static void free_ShExtInfo( ShExtInfo *psei ){
++  if( psei ){
++    if( psei->ppDotCommands ) sqlite3_free(psei->ppDotCommands);
++    if( psei->ppExportHandlers ) sqlite3_free(psei->ppExportHandlers);
++    if( psei->ppImportHandlers ) sqlite3_free(psei->ppImportHandlers);
++    memset(psei, 0, sizeof(ShExtInfo));
++  }
++}
++
 +/* 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 = {0};
++  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_oom(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;
-   psi->pShxLoaded[psi->numExtLoaded++] = sei;
 +  zSql = "INSERT INTO "SHELL_DISP_TAB"(name, extIx, cmdIx) VALUES(?, 0, ?)";
-   rc1 = sqlite3_prepare_v2(psx->dbShell, zSql, -1, &pStmt, 0);
++  shell_check_nomem(rc1=sqlite3_prepare_v2(psx->dbShell, zSql, -1, &pStmt, 0));
++  stmt_holder(pStmt);
 +  rc2 = sqlite3_exec(psx->dbShell, "BEGIN TRANSACTION", 0, 0, &zErr);
-   if( rc1!=SQLITE_OK || rc2!=SQLITE_OK ) return 1;
-   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);
-     sqlite3_bind_text(pStmt, 1, zName, -1, 0);
-     sqlite3_bind_int(pStmt, 2, ic);
-     rc = sqlite3_step(pStmt);
++  shell_check_nomem(rc2);
++  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);
++      shell_check_nomem(rc = sqlite3_step(pStmt));
++      if( rc!=SQLITE_DONE ){
++        sqlite3_exec(psx->dbShell, "ABORT", 0, 0, 0);
++        break;
++      }
++    }
 +    if( rc!=SQLITE_DONE ){
-       sqlite3_exec(psx->dbShell, "ABORT", 0, 0, 0);
-       break;
++      rc = SQLITE_ERROR;
++      zSql = "ABORT";
++    }else{
++      rc = SQLITE_OK;
++      zSql = "COMMIT";
++    }
++    shell_check_nomem(rc2 = sqlite3_exec(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;
 +    }
 +  }
-   sqlite3_finalize(pStmt);
-   if( rc!=SQLITE_DONE ) return 1;
-   rc = sqlite3_exec(psx->dbShell, "COMMIT", 0, 0, &zErr);
-   sqlite3_enable_load_extension(psx->dbShell, 1);
-   sqlite3_free(zErr);
-   psi->bDbDispatch = 1;
++  holder_free(mark);
 +
-   return SQLITE_OK;
++  return rc;
 +}
 +
- /* Call one loaded extension's destructors, in reverse order
-  * of their objects' creation, then free the tracking dyna-arrays.
++/* Call one loaded extension's destructors, in reverse order of their
++ * objects' creation.
 + */
- static void free_one_shext_tracking(ShExtInfo *psei){
++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);
 +    }
-     sqlite3_free(psei->ppDotCommands);
 +  }
 +  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);
 +    }
-     sqlite3_free(psei->ppExportHandlers);
 +  }
 +  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);
 +    }
-     sqlite3_free(psei->ppImportHandlers);
 +  }
 +  if( psei->extDtor!=0 ){
 +    psei->extDtor(psei->pvExtObj);
 +  }
 +}
 +
 +/* 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];
-       free_one_shext_tracking(psei);
++      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;
 +  }
 +}
 +
 +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;
 +}
 +
 +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{
 +    /* Release all resources extension might have registered before failing. */
 +    if( psi->ixExtPending < psi->numExtLoaded ){
-       free_one_shext_tracking(psi->pShxLoaded+psi->ixExtPending);
++      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 = sqlite3_mprintf("extension id mismatch %z\n", *pzErr);
 +    }
 +    rc = SQLITE_ERROR;
 +  }
 +  return rc;
 +}
 +#endif
 +
 +/* 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);
 +*/
 +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;
 +}
 +
 +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
 +# define ARCHIVE_ENABLE 1
 +#else
 +# define ARCHIVE_ENABLE 0
 +#endif
 +
 +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);
 +}
 +
 +/*****************
 + * 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;
 +}
 +
 +/*****************
 + * 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;
 +}
 +
 +DISPATCHABLE_COMMAND( cd ? 2 2 ){
 +  int rc=0;
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +#if defined(_WIN32) || defined(WIN32)
 +  wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
 +  rc = !SetCurrentDirectoryW(z);
 +  sqlite3_free(z);
 +#else
 +  rc = chdir(azArg[1]);
 +#endif
 +  if( rc ){
 +    utf8_printf(STD_ERR, "Cannot change to directory \"%s\"\n", azArg[1]);
 +    rc = 1;
 +  }
 +  return DCR_Ok|rc;
 +}
 +
 +/* The ".breakpoint" command causes a call to the no-op routine named
 + * test_breakpoint(). It is undocumented.
 +*/
 +COLLECT_HELP_TEXT[
 +  ",breakpoint              calls test_breakpoint(). (a debugging aid)",
 +];
 +DISPATCHABLE_COMMAND( breakpoint 3 1 1 ){
 +  test_breakpoint();
 +  return DCR_Ok;
 +}
 +
 +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{
 +    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 = DBI(psi);
 +      psi->pAuxDb = &psi->aAuxDb[i];
 +#if SHELL_DYNAMIC_EXTENSION
 +      if( DBI(psi)!=0 ) notify_subscribers(psi, NK_DbUserVanishing, DBI(psi));
 +#endif
 +      globalDb = DBI(psi) = psi->pAuxDb->db;
 +#if SHELL_DYNAMIC_EXTENSION
 +      if( DBI(psi)!=0 ) notify_subscribers(psi, NK_DbUserAppeared, DBI(psi));
 +#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;
 +}
 +
 +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;
 +  sqlite3 *db;
 +  int i;
 +  open_db(p, 0);
 +  db = DBX(p);
 +  rc = sqlite3_prepare_v2(db, "PRAGMA database_list", -1, &pStmt, 0);
 +  if( rc ){
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(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);
 +      shell_check_oom(azName[nName*2]);
 +      azName[nName*2+1] = strdup(zFile);
 +      shell_check_oom(azName[nName*2+1]);
 +      nName++;
 +    }
 +  }
 +  sqlite3_finalize(pStmt);
 +  for(i=0; i<nName; i++){
 +    int eTxn = sqlite3_txn_state(db, azName[i*2]);
 +    int bRdonly = sqlite3_db_readonly(db, azName[i*2]);
 +    const char *z = azName[i*2+1];
 +    utf8_printf(ISS(p)->out, "%s: %s %s%s\n",
 +                azName[i*2],
 +                z && z[0] ? z : "\"\"",
 +                bRdonly ? "r/o" : "r/w",
 +                eTxn==SQLITE_TXN_NONE ? "" :
 +                eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
 +    free(azName[i*2]);
 +    free(azName[i*2+1]);
 +  }
 +  sqlite3_free(azName);
 +  return 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( 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 DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( dbinfo 3 1 2 ){
 +  return shell_dbinfo_command(p, nArg, azArg);
 +}
 +
 +/*****************
 + * 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;
 +  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");
 +        sqlite3_free(zLike);
 +        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]);
 +          sqlite3_free(zLike);
 +          return DCR_ArgWrong;
 +        }
 +      }
 +    }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]
 +                    );
 +
 +      if( zLike ){
 +        zLike = smprintf("%z OR %z", zLike, zExpr);
 +      }else{
 +        zLike = zExpr;
 +      }
 +    }
 +  }
 +
 +  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);
 +  run_schema_dump_query(psi,zSql);
 +  sqlite3_free(zSql);
 +  if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 +    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);
 +    sqlite3_free(zSql);
 +  }
 +  sqlite3_free(zLike);
 +  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;
 +
 +  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{
 +      psi->autoEQP = (u8)booleanValue(azArg[1]);
 +    }
 +  }else{
 +    return DCR_ArgWrong;
 +  }
 +  return DCR_Ok;
 +}
 +
 +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, exit 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(). */
 +    p->shellAbruptExit = 0x100|rc;
 +  }
 +  return DCR_Return;
 +}
 +DISPATCHABLE_COMMAND( quit 1 1 0 ){
 +  /* .quit would be more aptly named .return, as it does nothing more. */
 +  return DCR_Return;
 +}
 +
 +/*****************
 + * 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;
 +
 +  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{
 +        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;
 +    }
 +  }
 +
 +  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));
 +
 +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 *zFile = 0;
 +  u8 bTxtMode = 0;
 +  u8 bPutBOM = 0;
 +  int i;
 +  static unsigned const char zBOM[4] = {0xef,0xbb,0xbf,0};
 +  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_oom(zFile);
 +      if( zFile[0]=='|' ){
 +        while( i+1<nArg ){
 +          zFile = smprintf("%z %s", zFile, azArg[++i]);
 +          shell_check_oom(zFile);
 +        }
 +        break;
 +      }
 +    }else{
 +      sqlite3_free(zFile);
 +      return DCR_TooMany|i;
 +    }
 +  }
 +  if( zFile==0 ){
 +    zFile = smprintf("stdout");
 +    shell_check_oom(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_oom(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(zFile);
 +  return DCR_Ok|rc;
 +}
 +#endif /* !defined(SQLITE_SHELL_FIDDLE)*/
 +
 +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);
 +}
 +
 +
 +/*****************
 + * 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;
 +
 +  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];
 +  }
 +
 +  /* The argument can optionally begin with "-" or "--" */
 +  if( zCmd[0]=='-' && zCmd[1] ){
 +    zCmd++;
 +    if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 +  }
 +
 +  /* --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;
 +  }
 +
 +  /* 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{
 +        *pzErr = smprintf("ambiguous file-control: \"%s\"\n"
 +                          "Use \".filectrl --help\" for help\n", zCmd);
 +        return DCR_ArgWrong;
 +      }
 +    }
 +  }
 +  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 ){
 +    utf8_printf(psi->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
 +    return DCR_CmdErred;
 +  }else if( isOk==1 ){
 +    char zBuf[100];
 +    sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes);
 +    raw_printf(psi->out, "%s\n", zBuf);
 +  }
 +  return DCR_Ok;
 +}
 +
 +DISPATCHABLE_COMMAND( fullschema ? 1 2 ){
 +  int rc;
 +  ShellInState data;
 +  ShellExState datax;
 +  int doStats = 0;
 +  /* Consider some refactoring to avoid this wholesale copying. */
 +  memcpy(&data, ISS(p), sizeof(data));
 +  memcpy(&datax, p, sizeof(datax));
 +  data.pSXS = &datax;
 +  datax.pSIS = &data;
 +  data.showHeader = 0;
 +  data.cMode = data.mode = MODE_Semi;
 +  if( nArg==2 && optionMatch(azArg[1], "indent") ){
 +    data.cMode = data.mode = MODE_Pretty;
 +    nArg = 1;
 +  }
 +  if( nArg!=1 ){
 +    return DCR_TooMany|1;
 +  }
 +  open_db(p, 0);
 +  rc = sqlite3_exec(datax.dbUser,
 +    "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, &datax, 0
 +  );
 +  if( rc==SQLITE_OK ){
 +    sqlite3_stmt *pStmt;
 +    rc = sqlite3_prepare_v2(datax.dbUser,
 +                            "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(data.out, "/* No STAT tables available */\n");
 +  }else{
 +    raw_printf(data.out, "ANALYZE sqlite_schema;\n");
 +    data.cMode = data.mode = MODE_Insert;
 +    datax.zDestTable = "sqlite_stat1";
 +    shell_exec(&datax, "SELECT * FROM sqlite_stat1", 0);
 +    datax.zDestTable = "sqlite_stat4";
 +    shell_exec(&datax, "SELECT * FROM sqlite_stat4", 0);
 +    raw_printf(data.out, "ANALYZE sqlite_schema;\n");
 +  }
 +  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==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;
 +    }
 +  }
 +  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;                 /* 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 */
 +  FILE *out = ISS(p)->out;    /* output stream */
 +  char *zCreate = 0;          /* CREATE TABLE statement text */
 +  ShellInState *psi = ISS(p);
 +  int rc = 0;
 +
 +  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{
 +      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;
 +  }
 +  sCtx.z = sqlite3_malloc64(120);
 +  if( sCtx.z==0 ){
 +    import_cleanup(&sCtx);
 +    shell_out_of_memory();
 +  }
 +  /* Here and below, resources must be freed before exit. */
 +  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);
 +  }
 +  zSql = smprintf("SELECT * FROM %s", zFullTabName);
 +  if( zSql==0 || zFullTabName==0 ){
 +    import_cleanup(&sCtx);
 +    shell_out_of_memory();
 +  }
 +  nByte = strlen30(zSql);
 +  rc = sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0);
 +  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;
 +    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);
 +      sqlite3_free(zRenames);
 +    }
 +    assert(dbCols==0);
 +    if( zColDefs==0 ){
 +      *pzErr = smprintf("%s: empty file\n", sCtx.zFile);
 +      sqlite3_free(zCreate);
 +    import_fail: /* entry from outer blocks */
 +      sqlite3_free(zSql);
 +      sqlite3_free(zFullTabName);
 +      import_cleanup(&sCtx);
 +      return DCR_Error;
 +    }
 +    zCreate = smprintf("%z%z\n", zCreate, zColDefs);
 +    if( eVerbose>=1 ){
 +      utf8_printf(out, "%s\n", zCreate);
 +    }
 +    rc = sqlite3_exec(DBX(p), zCreate, 0, 0, 0);
 +    sqlite3_free(zCreate);
 +    if( rc ){
 +      *pzErr = smprintf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(DBX(p)));
 +      goto import_fail;
 +    }
 +    rc = sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0);
 +  }
 +  if( rc ){
 +    if (pStmt) sqlite3_finalize(pStmt);
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
 +    goto import_fail;
 +  }
 +  sqlite3_free(zSql);
 +  nCol = sqlite3_column_count(pStmt);
 +  sqlite3_finalize(pStmt);
 +  pStmt = 0;
 +  if( nCol==0 ) return DCR_Ok; /* 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(psi->out, "Insert using: %s\n", zSql);
 +  }
 +  rc = sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0);
 +  if( rc ){
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
 +    if (pStmt) sqlite3_finalize(pStmt);
 +    goto import_fail;
 +  }
 +  sqlite3_free(zSql);
 +  sqlite3_free(zFullTabName);
 +  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++; }
 +      }
 +    }
 +    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++;
 +      }
 +    }
 +  }while( sCtx.cTerm!=EOF );
 +
 +  import_cleanup(&sCtx);
 +  sqlite3_finalize(pStmt);
 +  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);
 +  }
 +  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{
 +    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");
 +  }
 +  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;
 +  char *zCollist = 0;
 +  sqlite3_stmt *pStmt;
 +  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;
 +  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;
 +  }
 +  open_db(p, 0);
 +  db = DBX(p);
 +  if( nArg==2 ){
 +    sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 1);
 +    return DCR_Ok;
 +  }
 +  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]);
 +  sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
 +  sqlite3_free(zSql);
 +  if( sqlite3_step(pStmt)==SQLITE_ROW ){
 +    tnum = sqlite3_column_int(pStmt, 0);
 +    isWO = sqlite3_column_int(pStmt, 1);
 +  }
 +  sqlite3_finalize(pStmt);
 +  zSql = smprintf("PRAGMA index_xinfo='%q'", azArg[1]);
 +  rc = sqlite3_prepare_v2(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 = smprintf("\"%w\"", zCol);
 +    }else{
 +      zCollist = smprintf("%z,\"%w\"", zCollist, zCol);
 +    }
 +  }
 +  sqlite3_finalize(pStmt);
 +  if( i==0 || tnum==0 ){
 +    *pzErr = smprintf("no such index: \"%s\"\n", azArg[1]);
 +    sqlite3_free(zCollist);
 +    return DCR_Error;
 +  }
 +  if( lenPK==0 ) lenPK = 100000;
 +  zSql = smprintf("CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))"
 +                  "WITHOUT ROWID", azArg[2], zCollist, lenPK, zCollist);
 +  sqlite3_free(zCollist);
 +  rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 1, tnum);
 +  if( rc==SQLITE_OK ){
 +    rc = sqlite3_exec(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{
 +    *pzErr = smprintf("SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
 +  }
 +  sqlite3_free(zSql);
 +  return rc != 0;
 +}
 +DISPATCHABLE_COMMAND( iotrace ? 2 2 ){
 +  SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...);
 +  if( iotrace && iotrace!=STD_OUT ) fclose(iotrace);
 +  iotrace = 0;
 +  if( nArg<2 ){
 +    sqlite3IoTrace = 0;
 +  }else if( 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;
 +    }
 +  }
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * 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));
 +    }
 +  }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{
 +          *pzErr = smprintf("ambiguous limit: \"%s\"\n", azArg[1]);
 +          return DCR_Error;
 +        }
 +      }
 +    }
 +    if( iLimit<0 ){
 +      *pzErr = smprintf("unknown limit: \"%s\"\n"
 +                        "enter \".limits\" with no arguments for a list.\n",
 +                        azArg[1]);
 +      return DCR_ArgWrong;
 +    }
 +    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));
 +  }
 +  return DCR_Ok;
 +}
 +
 +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 */
 +
 +  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 SELECT statement returns one row for each foreign key constraint
 +  ** in the schema of the main database. The column values are:
 +  **
 +  ** 0. The text of an SQL statement similar to:
 +  **
 +  **      "EXPLAIN QUERY PLAN SELECT 1 FROM child_table WHERE child_key=?"
 +  **
 +  **    This SELECT is similar to the one that the foreign keys implementation
 +  **    needs to run internally on child tables. If there is an index that can
 +  **    be used to optimize this query, then it can also be used by the FK
 +  **    implementation to optimize DELETE or UPDATE statements on the parent
 +  **    table.
 +  **
 +  ** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by
 +  **    the EXPLAIN QUERY PLAN command matches this pattern, then the schema
 +  **    contains an index that can be used to optimize the query.
 +  **
 +  ** 2. Human readable text that describes the child table and columns. e.g.
 +  **
 +  **       "child_table(child_key1, child_key2)"
 +  **
 +  ** 3. Human readable text that describes the parent table and columns. e.g.
 +  **
 +  **       "parent_table(parent_key1, parent_key2)"
 +  **
 +  ** 4. A full CREATE INDEX statement for an index that could be used to
 +  **    optimize DELETE or UPDATE statements on the parent table. e.g.
 +  **
 +  **       "CREATE INDEX child_table_child_key ON child_table(child_key)"
 +  **
 +  ** 5. The name of the parent table.
 +  **
 +  ** These six values are used by the C logic below to generate the report.
 +  */
 +  const char *zSql =
 +  "SELECT "
 +    "     'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '"
 +    "  || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' "
 +    "  || fkey_collate_clause("
 +    "       f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')"
 +    ", "
 +    "     'SEARCH ' || s.name || ' USING COVERING INDEX*('"
 +    "  || group_concat('*=?', ' AND ') || ')'"
 +    ", "
 +    "     s.name  || '(' || group_concat(f.[from],  ', ') || ')'"
 +    ", "
 +    "     f.[table] || '(' || group_concat(COALESCE(f.[to], p.[name])) || ')'"
 +    ", "
 +    "     'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))"
 +    "  || ' ON ' || quote(s.name) || '('"
 +    "  || group_concat(quote(f.[from]) ||"
 +    "        fkey_collate_clause("
 +    "          f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')"
 +    "  || ');'"
 +    ", "
 +    "     f.[table] "
 +    "FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f "
 +    "LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) "
 +    "GROUP BY s.name, f.id "
 +    "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)"
 +  ;
 +  const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)";
 +
 +  for(i=2; 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 = "    ";
      }
 -    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;
 -      }
 +    else{
 +      raw_printf(STD_ERR, "Usage: %s %s ?-verbose? ?-groupbyparent?\n",
 +                 azArg[0], azArg[1]
 +      );
 +      return DCR_Unknown|i;
      }
 -    sqlite3_finalize(pStmt);
 -    sqlite3_close(*pDb);
 -    *pDb = 0;
 -    return zColsSpec;
    }
 -}
  
 -/*
 -** 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];
 +  /* Register the fkey_collate_clause() SQL function */
 +  rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8,
 +      0, shellFkeyCollateClause, 0, 0
 +  );
  
 -#ifndef SQLITE_OMIT_VIRTUALTABLE
 -  if( p->expert.pExpert ){
 -    expertFinish(p, 1, 0);
 +  if( rc==SQLITE_OK ){
 +    rc = sqlite3_prepare_v2(db, zSql, -1, &pSql, 0);
    }
 -#endif
 -
 -  /* 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]);
 -    }
 +  if( rc==SQLITE_OK ){
 +    sqlite3_bind_int(pSql, 1, bGroupByParent);
    }
 -  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( rc==SQLITE_OK ){
 +    int rc2;
 +    char *zPrev = 0;
 +    while( SQLITE_ROW==sqlite3_step(pSql) ){
 +      int res = -1;
 +      sqlite3_stmt *pExplain = 0;
 +      const char *zEQP = (const char*)sqlite3_column_text(pSql, 0);
 +      const char *zGlob = (const char*)sqlite3_column_text(pSql, 1);
 +      const char *zFrom = (const char*)sqlite3_column_text(pSql, 2);
 +      const char *zTarget = (const char*)sqlite3_column_text(pSql, 3);
 +      const char *zCI = (const char*)sqlite3_column_text(pSql, 4);
 +      const char *zParent = (const char*)sqlite3_column_text(pSql, 5);
  
 -#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);
 -    }else{
 -      sqlite3_set_authorizer(p->db, 0, 0);
 -    }
 -  }else
 -#endif
 +      if( zEQP==0 || zGlob==0 ) continue;
 +      rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
 +      if( rc!=SQLITE_OK ) break;
 +      if( SQLITE_ROW==sqlite3_step(pExplain) ){
 +        const char *zPlan = (const char*)sqlite3_column_text(pExplain, 3);
 +        res = zPlan!=0 && (  0==sqlite3_strglob(zGlob, zPlan)
 +                             || 0==sqlite3_strglob(zGlobIPK, zPlan));
 +      }
 +      rc = sqlite3_finalize(pExplain);
 +      if( rc!=SQLITE_OK ) break;
  
 -#if !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
 -#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);
 +        }
  
 -#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;
 +        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);
          }
 -      }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);
 +    sqlite3_free(zPrev);
 +
      if( rc!=SQLITE_OK ){
 -      utf8_printf(stderr, "Error: cannot open \"%s\"\n", zDestFile);
 -      close_db(pDest);
 -      return 1;
 +      *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
      }
 -    if( bAsync ){
 -      sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
 -                   0, 0, 0);
 -    }
 -    open_db(p, 0);
 -    pBackup = sqlite3_backup_init(pDest, "main", p->db, zDb);
 -    if( pBackup==0 ){
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
 -      close_db(pDest);
 -      return 1;
 -    }
 -    while(  (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
 -    sqlite3_backup_finish(pBackup);
 -    if( rc==SQLITE_DONE ){
 -      rc = 0;
 -    }else{
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
 -      rc = 1;
 -    }
 -    close_db(pDest);
 -  }else
 -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
  
 -  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");
 -      rc = 1;
 +    rc2 = sqlite3_finalize(pSql);
 +    if( rc==SQLITE_OK && rc2!=SQLITE_OK ){
 +      rc = rc2;
 +      *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
      }
 -  }else
 +  }else{
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
 +  }
  
 -  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);
 -      }
 -    }else{
 -      raw_printf(stderr, "Usage: .binary on|off\n");
 -      rc = 1;
 -    }
 -  }else
 +  return DCR_Ok|(rc!=0);
 +}
  
 -  /* 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
 +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);
 +}
  
 -#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);
 +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( ISS(p)->bSafeMode && !bOn && !bOff ){
 +    return DCR_AbortError;
 +  }
 +  output_file_close(ISS(p)->pLog);
 +  if( bOff ){
 +    ISS(p)->pLog = 0;
 +    return DCR_Ok;
 +  }
 +#if defined(SQLITE_SHELL_FIDDLE)
 +  if( !bOn ) return DCR_SayUsage;
  #else
 -      rc = chdir(azArg[1]);
 +  if( bOn ) zFile = "stdout";
  #endif
 -      if( rc ){
 -        utf8_printf(stderr, "Cannot change to directory \"%s\"\n", azArg[1]);
 -        rc = 1;
 -      }
 -    }else{
 -      raw_printf(stderr, "Usage: .cd DIRECTORY\n");
 -      rc = 1;
 +  ISS(p)->pLog = output_file_open(zFile, 0);
 +  return DCR_Ok;
 +}
 +
 +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;
      }
 -  }else
 -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 +    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;
 +  }
 +  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;
 +}
  
 -  if( c=='c' && n>=3 && cli_strncmp(azArg[0], "changes", n)==0 ){
 -    if( nArg==2 ){
 -      setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
 +/*****************
 + * 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{
 -      raw_printf(stderr, "Usage: .changes on|off\n");
 -      rc = 1;
 +      /* 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{
 +      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
 +    }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;
 +}
  
 +/* 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
 +/* ToDo: Get defined help text collection into macro processor. */
 +/*****************
 + * 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
 -  /* 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;
 +    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{
 -      utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase);
 -      p->nCheck++;
 +      zFN = z;
      }
 -    sqlite3_free(zRes);
 -  }else
 -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 +  }
  
 +  /* 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( 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;
 +    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;
      }
 -  }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);
 -        }
 -      }
 -    }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
 +      /* WASM mode has its own sandboxed pseudo-filesystem. */
 +#endif
 +    if( zFN ){
 +      zNewFilename = smprintf("%s", zFN);
 +      shell_check_oom(zNewFilename);
      }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));
 +      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 '%s'\n", zNewFilename);
 +      sqlite3_free(zNewFilename);
        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++;
 -      }
 +      psi->pAuxDb->zFreeOnClose = zNewFilename;
      }
 -    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;
 +  }
 +  if( DBX(p)==0 ){
 +    /* As a fall-back open a TEMP database */
 +    psi->pAuxDb->zDbFilename = 0;
      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);
 -      }
 -      sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v);
 -      utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off");
 -      if( nArg>1 ) break;
 -    }
 -    if( nArg>1 && ii==ArraySize(aDbConfig) ){
 -      utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]);
 -      utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n");
 -    }
 -  }else
 +  }
 +  return DCR_Ok|(rc!=0);
 +}
  
 -#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
 +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]);
 +    exit(1);
 +  }
 +  /* Suspend safe mode for 1 dot-command after this. */
 +  psi->bSafeModeFuture = 2;
 +  return DCR_Ok;
 +}
  
 -  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 */
 +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;
 +}
  
 -  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);
 +/* 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
 -        }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]
 -        );
 +                            ) ){
 +    zErr = "Malformed pathname";
 +  }else{
 +    return smprintf("%s%s", zHome, zPath+1);
 +  }
 +  utf8_printf(STD_ERR, "Error: %s\n", zErr);
 +  return 0;
 +}
  
 -        if( zLike ){
 -          zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr);
 -        }else{
 -          zLike = zExpr;
 -        }
 +/* 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){
 +  int rc = 0;
 +  char *zSql = 0; /* to be sqlite3_free()'ed */
 +  sqlite3_str *sbCopy = 0;
 +  const char *zHere = 0;
 +  const char *zThere = SH_KV_STORE_SNAME;
 +  const char *zTo;
 +  const char *zFrom;
 +  sqlite3 *dbStore = 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 = 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);
 +  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 = sqlite3_exec(dbStore, zCT, 0, 0, 0);
 +      if( rc!=SQLITE_OK ){
 +        utf8_printf(STD_ERR, "Cannot create table %s. Nothing saved.", zThere);
        }
      }
 +  }
 +  sqlite3_close(dbStore);
 +  if( rc!=0 ) return rc;
  
 -    open_db(p, 0);
 +  zSql = smprintf("ATTACH %Q AS %s;", zStoreDbName, SH_KV_STORE_SCHEMA);
 +  shell_check_oom(zSql);
 +  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 +  sqlite3_free(zSql);
 +  if( rc!=SQLITE_OK ) return rc;
  
 -    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
 +  sbCopy = sqlite3_str_new(db);
 +  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);
 +  shell_check_oom(zSql);
 +  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 +  sqlite3_free(zSql);
  
 -  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;
 -    }
 -  }else
 +  sqlite3_exec(db, "DETACH "SH_KV_STORE_SCHEMA";", 0, 0, 0);
 +  return rc;
 +}
  
 -  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;
 -    }
 -  }else
 +/* 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";
  
 -#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;
 -  }else
 -#endif
 +/* 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.
 + * If the return is exactly the input, it must not be sqlite3_free()'ed.
 + * If the return differs from the input, it must be sqlite3_free()'ed.
 + */
 +  static const 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 zSpec;
 +}
  
 -  /* 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;
 -    }
 -  }else
 +/* 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){
 +  const 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 = kv_xfr_table(db, zStore, 0, ptu, pzFirst, nNames);
 +    if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore);
 +    return rc;
 +  }
 +}
  
 -#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]);
 -      rc = 1;
 +/* Save some or all parameters. Arguments are "save FILE ?NAMES?". */
 +static int kv_pairs_save(sqlite3 *db, ParamTableUse ptu,
 +                         const char *azArg[], int nArg){
 +  const 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 = kv_xfr_table(db, zStore, 1, ptu, pzFirst, nNames);
 +    if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore);
 +    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_oom(zSql);
 +  sqlite3_exec(db, zSql, kv_find_callback, &kvRow, 0);
 +  sqlite3_free(zSql);
 +  assert(kvRow.hits<2);
 +  if( kvRow.hits==1 && kvRow.uses==uses){
 +    /* Editing an existing value of same kind. */
 +    sqlite3_free(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_oom(zSql);
 +      zVal = db_text(db, zSql, 1);
 +      sqlite3_free(zSql);
 +      zSql = smprintf("UPDATE %s SET value=(SELECT %s) "
 +                      "WHERE key=%Q AND uses=%d", zTab, zVal, name, uses);
      }else{
 -      open_db(p, 0);
 -      expertDotCommand(p, azArg, nArg);
 +      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);
 +    if( eval!=0 ){
 +      zSql = smprintf("SELECT edit('-- %q%s', %Q)", name, "\n", zEditor);
 +      zVal = db_text(db, zSql, 1);
 +      sqlite3_free(zSql);
 +      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_oom(zSql);
 +  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 +  sqlite3_free(zSql);
 +  sqlite3_free(zVal);
 +  return rc!=SQLITE_OK;
 +}
  #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;
 +/* 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;
 +}
  
 -    open_db(p, 0);
 -    zCmd = nArg>=2 ? azArg[1] : "help";
 +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 }
 +};
  
 -    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];
 -    }
 +/* 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;
 +}
  
 -    /* The argument can optionally begin with "-" or "--" */
 -    if( zCmd[0]=='-' && zCmd[1] ){
 -      zCmd++;
 -      if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 -    }
 +/* 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);
 +  shell_check_oom(zSql);
 +  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0);
 +  assert(rc==SQLITE_OK);
 +  sqlite3_free(zSql);
 +  rc = (SQLITE_DONE==sqlite3_step(pStmtSet))? SQLITE_OK : SQLITE_ERROR;
 +  sqlite3_finalize(pStmtSet);
 +  sqlite3_free(zValGlom);
 +  return rc;
 +}
  
 -    /* --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);
 -      }
 -      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;
 -        }else{
 -          utf8_printf(stderr, "Error: ambiguous file-control: \"%s\"\n"
 -                              "Use \".filectrl --help\" for help\n", zCmd);
 -          rc = 1;
 -          goto meta_command_exit;
 -        }
 +/* Most of the .parameter set subcommand (per help text)
 + */
 +static int param_set(sqlite3 *db, char cCast,
 +                     char *name, char **valBeg, char **valLim){
 +  char *zSql = 0;
 +  int rc = SQLITE_OK, retries = 0, needsEval = 1;
 +  char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0;
 +  sqlite3_stmt *pStmtSet = 0;
 +  const char *zCastTo = 0;
 +  char *zValue = (zValGlom==0)? *valBeg : zValGlom;
 +  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;
        }
      }
 -    if( filectrl<0 ){
 -      utf8_printf(stderr,"Error: unknown file-control: %s\n"
 -                         "Use \".filectrl --help\" for help\n", zCmd);
 +  }
 +  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{
 -      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);
 +      zSql = smprintf
 +        ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 +          "VALUES(%Q,(%s),"SPTU_Binding");", name, zValue );
 +    }
 +    shell_check_oom(zSql);
 +    rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0);
 +    sqlite3_free(zSql);
 +  }
 +  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 );
 +    shell_check_oom(zSql);
 +    rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0);
 +    assert(rc==SQLITE_OK);
 +    sqlite3_free(zSql);
 +  }
 +  sqlite3_step(pStmtSet);
 +  sqlite3_finalize(pStmtSet);
 +  sqlite3_free(zValGlom);
 +  return rc;
 +}
 +
 +/* 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;
 +  int len = 0, rc;
 +  char *zFromWhere = 0;
 +  char *zSql = 0;
 +  sqlite3 *db;
 +  const char *zTab;
 +
 +  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);
 +  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);
 +  zFromWhere = sqlite3_str_finish(sbList);
 +  shell_check_oom(zFromWhere);
 +  zSql = smprintf("SELECT max(length(key)) %s", zFromWhere);
 +  shell_check_oom(zSql);
 +  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
 +  if( rc==SQLITE_OK ){
 +    sqlite3_bind_int(pStmt, 1, ptu);
 +    if( sqlite3_step(pStmt)==SQLITE_ROW ){
 +      len = sqlite3_column_int(pStmt, 0);
 +      if( len>40 ) len = 40;
 +      if( len<4 ) len = 4;
 +    }
 +  }
 +  sqlite3_finalize(pStmt);
 +  pStmt = 0;
 +  if( len ){
 +    FILE *out = ISS(psx)->out;
 +    sqlite3_free(zSql);
 +    if( !bShort ){
 +      int nBindings = 0, nScripts = 0;
 +      zSql = smprintf("SELECT key, uses,"
 +                      " iif(typeof(value)='text', quote(value), value) as v"
 +                      " %z ORDER BY uses, key", zFromWhere);
 +      shell_check_oom(zSql);
 +      rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
 +      sqlite3_bind_int(pStmt, 1, ptu);
 +      while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 +        ParamTableUse ptux = sqlite3_column_int(pStmt,1);
 +        switch( ptux ){
 +        case PTU_Binding:
 +          if( nBindings++ == 0 ){
 +            utf8_printf(out, "Bindings:\n%-*s %s\n",
 +                        len, "name", "value");
            }
 -          isOk = 2;
 +          utf8_printf(out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
 +                      sqlite3_column_text(pStmt,2));
            break;
 -        }
 -        case SQLITE_FCNTL_RESERVE_BYTES: {
 -          int x;
 -          if( nArg>=3 ){
 -            x = atoi(azArg[2]);
 -            sqlite3_file_control(p->db, zSchema, filectrl, &x);
 +        case PTU_Script:
 +          if( nScripts++ == 0 ){
 +            utf8_printf(out, "Scripts:\n%-*s %s\n", len, "name", "value");
            }
 -          x = -1;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          utf8_printf(p->out,"%d\n", x);
 -          isOk = 2;
 +          utf8_printf(out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
 +                      sqlite3_column_text(pStmt,2));
            break;
 +        default: break; /* Ignore */
 +        }
 +      }
 +    }else{
 +      int nc = 0, ncw = 78/(len+2);
 +      zSql = smprintf("SELECT key %z ORDER BY key", zFromWhere);
 +      shell_check_oom(zSql);
 +      rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
 +      sqlite3_bind_int(pStmt, 1, ptu);
 +      while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 +        utf8_printf(out, "%s  %-*s", ((++nc%ncw==0)? "\n" : ""),
 +                    len, sqlite3_column_text(pStmt,0));
 +      }
 +      if( nc>0 ) utf8_printf(out, "\n");
 +    }
 +    sqlite3_finalize(pStmt);
 +  }else{
 +    sqlite3_free(zFromWhere);
 +  }
 +  sqlite3_free(zSql);
 +}
 +
 +/* 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, ")");
 +  }
 +}
 +
 +/* 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, ")");
 +  }
 +}
 +
 +/*****************
 + * 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
 +  "   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);
 +
 +  /* .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;
 +      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_oom(zSql);
 +      sqlite3_exec(db, zSql, 0, 0, 0);
 +      sqlite3_free(zSql);
 +    }
 +  }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;
          }
        }
 -    }
 -    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);
 +      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;
      }
    }else
 +#endif
  
 -  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");
 -    }
 +  /* .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=='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;
 -    }
 +  /* .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
  
 -  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);
 -    }
 +  /* .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
  
 -#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;
 -    }
 -    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;
 -#endif
 -    }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);
 +  /* .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
 +
 +  /* .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{
 -      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);
 +      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;
 -        goto meta_command_exit;
        }
 -      zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs);
 -      if( eVerbose>=1 ){
 -        utf8_printf(p->out, "%s\n", zCreate);
 +    }
 +  }else
 +
 +  {  /* If no command name and arg count matches, show a syntax error */
 +    showHelp(ISS(p)->out, "parameter", p);
 +    return DCR_CmdErred;
 +  }
 +
 +  return DCR_Ok | (rc!=0);
 +}
 +
 +/*****************
 + * 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 = 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;
 +      if( cli_strcmp(z,"reset")==0 ){
 +        psi->flgProgress |= SHELL_PROGRESS_RESET;
 +        continue;
        }
 -      sqlite3_free(zCreate);
 -      zCreate = 0;
 -      rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    }
 -    if( rc ){
 -      if (pStmt) sqlite3_finalize(pStmt);
 -      utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db));
 -      goto import_fail;
 -    }
 -    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;
 -    }
 -    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++; }
 -        }
 +      if( cli_strcmp(z,"once")==0 ){
 +        psi->flgProgress |= SHELL_PROGRESS_ONCE;
 +        continue;
        }
 -      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++;
 +      if( cli_strcmp(z,"limit")==0 ){
 +        if( i+1>=nArg ){
 +          *pzErr = smprintf("missing argument on --limit\n");
 +          return DCR_Unpaired|i;
          }else{
 -          sCtx.nRow++;
 +          psi->mxProgress = (int)integerValue(azArg[++i]);
          }
 +        continue;
        }
 -    }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);
 +      return DCR_Unknown|i;
 +    }else{
 +      nn = (int)integerValue(z);
      }
 -  }else
 -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 +  }
 +  open_db(p, 0);
 +  sqlite3_progress_handler(DBX(p), nn, progress_handler, psi);
 +  return DCR_Ok;
 +}
  
 -#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;
 -    }
 -    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);
 -    }
 -    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);
 +/* 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++;
 -      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);
 -      }
 +      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;
      }
 -    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;
 +    else{
 +      utf8_printf(stderr, "unexpected option: %s\n", azArg[i]);
 +      showHelp(stderr, azArg[0], p);
 +      return DCR_Error;
      }
 -    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"
 -        );
 -      }
 -    }else{
 -      raw_printf(stderr, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
 -      rc = 1;
 +  }
 +
 +  pr = sqlite3_recover_init_sql(db, "main", recoverSqlCb, (void*)p);
 +
 +  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);
 +
 +  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? DCR_Error : DCR_Ok;
 +}
 +
 +DISPATCHABLE_COMMAND( restore ? 2 3 ){
 +  int rc;
 +  const char *zSrcFile;
 +  const char *zDb;
 +  sqlite3 *pSrc;
 +  sqlite3_backup *pBackup;
 +  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 = sqlite3_open(zSrcFile, &pSrc);
 +  if( rc!=SQLITE_OK ){
 +    *pzErr = smprintf("cannot open \"%s\"\n", zSrcFile);
 +    close_db(pSrc);
 +    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)));
 +    close_db(pSrc);
 +    return DCR_Error;
 +  }
 +  while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK
 +         || rc==SQLITE_BUSY  ){
 +    if( rc==SQLITE_BUSY ){
 +      if( nTimeout++ >= 3 ) break;
 +      sqlite3_sleep(100);
      }
 -    sqlite3_free(zSql);
 -  }else
 -#endif /* !defined(SQLITE_OMIT_TEST_CONTROL) */
 +  }
 +  sqlite3_backup_finish(pBackup);
 +  if( rc==SQLITE_DONE ){
 +    rc = 0;
 +  }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){
 +    *pzErr = smprintf("source database is busy\n");
 +    rc = 1;
 +  }else{
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
 +    rc = 1;
 +  }
 +  close_db(pSrc);
 +  return DCR_Ok|rc;
 +}
  
 -#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;
 +/*****************
 + * 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;
 +  ShellInState data;
 +  ShellExState datax;
 +  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);
 +  /* Consider some refactoring to avoid duplicative wholesale copying. */
 +  memcpy(&data, ISS(p), sizeof(data));
 +  memcpy(&datax, p, sizeof(datax));
 +  data.pSXS = &datax;
 +  datax.pSIS = &data;
 +
 +  data.showHeader = 0;
 +  data.cMode = data.mode = MODE_Semi;
 +  initText(&sSelect);
 +  for(ii=1; ii<nArg; ii++){
 +    if( optionMatch(azArg[ii],"indent") ){
 +      data.cMode = data.mode = 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{
 -      iotrace = fopen(azArg[1], "w");
 -      if( iotrace==0 ){
 -        utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]);
 -        sqlite3IoTrace = 0;
 -        rc = 1;
 +      return DCR_TooMany;
 +    }
 +  }
 +  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_oom(new_argv[0]);
 +      new_argv[1] = 0;
 +      new_colv[0] = "sql";
 +      new_colv[1] = 0;
 +      callback(&datax, 1, new_argv, new_colv);
 +      sqlite3_free(new_argv[0]);
 +    }
 +  }
 +  if( zDiv ){
 +    sqlite3_stmt *pStmt = 0;
 +    rc = sqlite3_prepare_v2(datax.dbUser,
 +                            "SELECT name FROM pragma_database_list",
 +                            -1, &pStmt, 0);
 +    if( rc ){
 +      *pzErr = smprintf("%s\n", sqlite3_errmsg(datax.dbUser));
 +      sqlite3_finalize(pStmt);
 +      return DCR_Error;
 +    }
 +    appendText(&sSelect, "SELECT sql FROM", 0);
 +    iSchema = 0;
 +    while( sqlite3_step(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{
 -        sqlite3IoTrace = iotracePrintf;
 +        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_finalize(pStmt);
 +#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);
      }
 -  }else
  #endif
 -
 -  if( c=='l' && n>=5 && cli_strncmp(azArg[0], "limits", n)==0 ){
 -    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++){
 -        printf("%20s %d\n", aLimit[i].zLimitName,
 -               sqlite3_limit(p->db, aLimit[i].limitCode, -1));
 -      }
 -    }else if( nArg>3 ){
 -      raw_printf(stderr, "Usage: .limit NAME ?NEW-VALUE?\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }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{
 -            utf8_printf(stderr, "ambiguous limit: \"%s\"\n", azArg[1]);
 -            rc = 1;
 -            goto meta_command_exit;
 -          }
 -        }
 -      }
 -      if( iLimit<0 ){
 -        utf8_printf(stderr, "unknown limit: \"%s\"\n"
 -                        "enter \".limits\" with no arguments for a list.\n",
 -                         azArg[1]);
 -        rc = 1;
 -        goto meta_command_exit;
 +    appendText(&sSelect, ") WHERE ", 0);
 +    if( zName ){
 +      char *zQarg = smprintf("%Q", zName);
 +      int bGlob;
 +      shell_check_oom(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( nArg==3 ){
 -        sqlite3_limit(p->db, aLimit[iLimit].limitCode,
 -                      (int)integerValue(azArg[2]));
 +      appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0);
 +      appendText(&sSelect, zQarg, 0);
 +      if( !bGlob ){
 +        appendText(&sSelect, " ESCAPE '\\' ", 0);
        }
 -      printf("%20s %d\n", aLimit[iLimit].zLimitName,
 -             sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1));
 +      appendText(&sSelect, " AND ", 0);
 +      sqlite3_free(zQarg);
      }
 -  }else
 -
 -  if( c=='l' && n>2 && cli_strncmp(azArg[0], "lint", n)==0 ){
 -    open_db(p, 0);
 -    lintDotCommand(p, azArg, nArg);
 -  }else
 -
 -#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE)
 -  if( c=='l' && cli_strncmp(azArg[0], "load", n)==0 ){
 -    const char *zFile, *zProc;
 -    char *zErrMsg = 0;
 -    failIfSafeMode(p, "cannot run .load in safe mode");
 -    if( nArg<2 || azArg[1][0]==0 ){
 -      /* Must have a non-empty FILE. (Will not load self.) */
 -      raw_printf(stderr, "Usage: .load FILE ?ENTRYPOINT?\n");
 -      rc = 1;
 -      goto meta_command_exit;
 +    if( bNoSystemTabs ){
 +      appendText(&sSelect, "name NOT LIKE 'sqlite_%%' AND ", 0);
      }
 -    zFile = azArg[1];
 -    zProc = nArg>=3 ? azArg[2] : 0;
 -    open_db(p, 0);
 -    rc = sqlite3_load_extension(p->db, zFile, zProc, &zErrMsg);
 -    if( rc!=SQLITE_OK ){
 -      utf8_printf(stderr, "Error: %s\n", zErrMsg);
 -      sqlite3_free(zErrMsg);
 -      rc = 1;
 +    appendText(&sSelect, "sql IS NOT NULL"
 +               " ORDER BY snum, rowid", 0);
 +    if( bDebug ){
 +      utf8_printf(data.out, "SQL: %s;\n", sSelect.z);
 +    }else{
 +      rc = sqlite3_exec(datax.dbUser, sSelect.z, callback, &datax, &zErrMsg);
      }
 -  }else
 -#endif
 +    freeText(&sSelect);
 +  }
 +  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;
 +  }
 +}
  
 -  if( c=='l' && cli_strncmp(azArg[0], "log", n)==0 ){
 -    if( nArg!=2 ){
 -      raw_printf(stderr, "Usage: .log FILENAME\n");
 -      rc = 1;
 +/*****************
 + * 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{
 -      const char *zFile = azArg[1];
 -      if( p->bSafeMode
 -       && cli_strcmp(zFile,"on")!=0
 -       && cli_strcmp(zFile,"off")!=0
 -      ){
 -        raw_printf(stdout, "cannot set .log to anything other "
 -                   "than \"on\" or \"off\"\n");
 -        zFile = "off";
 +      pSession = &pAuxDb->aSession[0];
 +      iSes = 0;
 +    }
 +  }
 +
 +  /* .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{
 +      rc = sqlite3session_attach(pSession->p, azCmd[1]);
 +      if( rc ){
 +        raw_printf(STD_ERR, "ERROR: sqlite3session_attach() returns %d\n", rc);
 +        rc = 0;
        }
 -      output_file_close(p->pLog);
 -      if( cli_strcmp(zFile,"on")==0 ) zFile = "stdout";
 -      p->pLog = output_file_open(zFile, 0);
      }
    }else