]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Merge shell-tweaks enhancements (with 3.38)
authorlarrybr <larrybr@noemail.net>
Fri, 25 Feb 2022 00:09:07 +0000 (00:09 +0000)
committerlarrybr <larrybr@noemail.net>
Fri, 25 Feb 2022 00:09:07 +0000 (00:09 +0000)
FossilOrigin-Name: 4c7d94d3f339d5de98ab5488e75b5dfea2c11fadb2cd44a9f6bc4e0769dbd34a

1  2 
manifest
manifest.uuid
src/shell.c.in
test/shell1.test
test/shell9.test
tool/mkshellc.tcl

diff --cc manifest
index da4d88a4cb135b8ffd73e5551cf8aad8fbce10a3,e5f4607332841c8ee02efb5946afaa23d67bff43..19d125b5505df2cc9e2823595b066e873447ddec
+++ b/manifest
@@@ -1,5 -1,5 +1,5 @@@
- C Regularize\sCLI\s.mode\sprocessing
- D 2022-02-24T04:29:09.586
 -C Sync\sw/3.38,\sadd\s.parameter\sls\sto\sCLI
 -D 2022-02-22T22:28:37.952
++C Merge\sshell-tweaks\senhancements\s(with\s3.38)
++D 2022-02-25T00:09:07.697
  F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
  F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
  F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@@ -555,8 -553,7 +555,8 @@@ F src/random.c 097dc8b31b8fba5a9aca1697
  F src/resolve.c ea935b87d6fb36c78b70cdc7b28561dc8f33f2ef37048389549c7b5ef9b0ba5e
  F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
  F src/select.c 3baa9dd8cf240654773c7974e2bcce398ac9dd24419c36684156963defe43b35
- F src/shell.c.in 0b1269056aca0e71680a65dac98bd731597f97334ab7a06509eba49f4d6e5093
 -F src/shell.c.in 5a8a5350b7284f185aed21c140e6c81a33b54873f89a3568600cf6a37dd4acac
++F src/shell.c.in 0da11ed800103e60c0cea85538efb025bebdba25c1e8516aa20ee67237e16be6
 +F src/shext_linkage.h 5897e8140a06cb733d07a927994b30d41225eb93b5b8cf2ad0e3460cb4e95fd6
  F src/sqlite.h.in e30cedf008d9c51511f4027a3739b727a588da553424748b48d2393f85dbde41
  F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
  F src/sqlite3ext.h a95cb9ed106e3d39e2118e4dcc15a14faec3fa50d0093425083d340d9dfd96e6
@@@ -1391,14 -1388,15 +1391,15 @@@ F test/sharedA.test 49d87ec54ab640fbbc3
  F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e
  F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939
  F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
- F test/shell1.test e05b84687a531419165dd79aefcfd149e4da24532480ec93439ddfa13efdf65b
 -F test/shell1.test b224e0793c5f48aa3749e65d8c64b93a30731bd206f2e41e6c5f1bee1bdb16c6
 -F test/shell2.test 89e4b2db062d52baed75022227b462d085cff495809de1699652779d8e0257d6
++F test/shell1.test 2a8d73538f672c5b5b238ba2fa15e4fbaea47454f372dd5b683be51727af33cc
 +F test/shell2.test aed8773dd236517149a6abaeccf94f066e5a0c871108e8d21889122132dd51ba
- F test/shell3.test a50628ab1d78d90889d9d3f32fb2c084ee15674771e96afe954aaa0accd1de3c
- F test/shell4.test 8f6c0fce4abed19a8a7f7262517149812a04caa905d01bdc8f5e92573504b759
+ F test/shell3.test 4ddea2bd182e7e03249911b23ae249e7cb8a91cdc86e695198725affabe8ecd3
+ F test/shell4.test 867e0675d7b096d6b93de534541e07c7f5ffafa3e6612695ecf55180802e1115
 -F test/shell5.test 2b521446f55146c9aafccd0946bdb44ae288b0d25bd48f722e041974fdeeb04a
 +F test/shell5.test 839a4489bc97d6f2028ffd036d500d2b8cab1657ff477bbcd635184f8e8b3887
  F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3
  F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f
  F test/shell8.test 388471d16e4de767333107e30653983f186232c0e863f4490bb230419e830aae
 -F test/shell9.test 03e28206c016b27b3c79f43c3b09c843468dd8961f980e60d37117ff1ce32908
++F test/shell9.test 9c6512384f4dcdaddd7c0efd0b1bd54cdac3c46dd82d2b30f877d90e666d7a2c
  F test/shmlock.test 3dbf017d34ab0c60abe6a44e447d3552154bd0c87b41eaf5ceacd408dd13fda5
  F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3
  F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5
@@@ -1880,8 -1878,8 +1881,8 @@@ F tool/mkmsvcmin.tcl 6ecab9fe22c2c8de4d
  F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61a07ef
  F tool/mkopcodeh.tcl 130b88697da6ec5b89b41844d955d08fb62c2552e889dec8c7bcecb28d8f50bd
  F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa
 -F tool/mkpragmatab.tcl de206c64b6e9ac8cd5e3cbd0ffe456f07d5710605ef8385d677e60ce3335ea12
 -F tool/mkshellc.tcl 5a9e27449694b179d2facde3fa10ff5a2d689be25822526addc20a1f1d9a12ec
 +F tool/mkpragmatab.tcl bd07bd59d45d0f3448e123d6937e9811195f9908a51e09d774609883055bfd3d
- F tool/mkshellc.tcl 160d16067f71caeb54cbebd09c12c3e545c77f46d9714cc8e7219407b3d3001b
++F tool/mkshellc.tcl 754c73032cd7d279ce42ddeaaa49d1ece71adf96293e10e3b0670c0d6b0f8972
  F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9
  F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
  F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f
@@@ -1947,8 -1945,8 +1948,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 b9b27d74b93ada32ece46c9e53652f766de97f5a77fe4c34251996697374e659
- R 3b1535aa20e62ad155a398c2745a0c17
 -P 2b4a295c58a908288840157c3e08289af8c1d287f72e5f14b7adfd98065bf241 40fa792d359f84c3b9e9d6623743e1a59826274e221df1bde8f47086968a1bab
 -R 5520524d725423f68aaec6a9faa61a16
++P 6d0557244f69e78474f9222aae95c4c47925a466aa15e5666d0ada237538e262 8c9a5fb26ba045edef1269c5f5e8c8d87fa890b88ddb1121be72514a389a845d
++R 791b63806b337836957b5b12020a8987
  U larrybr
- Z 1bf768e26b209b9a9f7fc853e590446e
 -Z 1b4999d38bc60cdea13bcadad5fd8a8e
++Z 6cdde194291306233e0d54419a5b14ca
  # Remove this line to create a well-formed Fossil manifest.
diff --cc manifest.uuid
index d3eefb1647139034b21f6818dbbde9cb486d2c3d,f2c7191aa183683c366ff69d7331409f3cbaa32d..632da48fb79120fd66c7f6bd12a0b12e8b38a042
@@@ -1,1 -1,1 +1,1 @@@
- 6d0557244f69e78474f9222aae95c4c47925a466aa15e5666d0ada237538e262
 -8c9a5fb26ba045edef1269c5f5e8c8d87fa890b88ddb1121be72514a389a845d
++4c7d94d3f339d5de98ab5488e75b5dfea2c11fadb2cd44a9f6bc4e0769dbd34a
diff --cc src/shell.c.in
index 92a773bd5349a5050a5dea41708487352bb75f4e,543e76149f8fb89faf2df3edb939734077a5bb8f..d227a9bd495be3b19fa4775bcef6ab886f5593a1
@@@ -661,26 -629,81 +662,80 @@@ static FILE * openChrSource(const char 
  }
  
  /*
- ** This routine reads a line of text from FILE in, stores it in
- ** memory obtained from malloc() and returns a pointer to it.
- ** NULL is returned at end of file, or if malloc() fails.
+ ** Arrange for shell input from either a FILE or a string.
+ ** For applicable invariants, see str_line_get(...) which is
+ ** the only modifier of this struct. (3rd and 4th members)
+ ** All other members are simply initialized to select the
+ ** input stream and track input sources, set by whatever
+ ** routines redirect the input.
+ */
+ typedef struct InSource {
+   FILE *inFile;     /* Will be 0 when input is to be taken from string.  */
+   char *zStrIn;     /* Will be 0 when no input is available from string. */
+   int iReadOffset;  /* Offset into zStrIn where next "read" to be done.  */
+   int lineno;       /* How many lines have been read from this source    */
+   const char *zSourceSay; /* For complaints, keep a name for this source */
+   struct InSource *pFrom; /* and redirect tracking to aid unraveling.    */
+ } InSource;
+ #define INSOURCE_STR_REDIR(str, tagTo, isFrom) {0, str, 0, 0, tagTo, isFrom}
+ #define INSOURCE_FILE_REDIR(fh, tagTo, isFrom) {fh, 0, 0, 0, tagTo, isFrom}
+ #define INSOURCE_IS_INTERACTIVE(pIS) \
+   ((pIS)==&termInSource && stdin_is_interactive )
+ /* This instance's address is taken as part of interactive input test. */
+ static InSource termInSource = { 0 /*stdin*/, 0, 0, 0, "<terminal>", 0};
+ static InSource stdInSource = { 0 /*stdin*/, 0, 0, 0, "<stdin>", 0};
+ static void init_std_inputs( FILE *pIn ){
+   termInSource.inFile = pIn;
+   stdInSource.inFile = pIn;
+ }
+ static char *str_line_get( char *zBuf, int ncMax, InSource *pInSrc){
+   if( pInSrc->inFile!=0 ){
+     char *zRet = fgets(zBuf, ncMax, pInSrc->inFile );
+     if( zRet!=0 ){
+       int iRead = strlen30(zRet);
+       if( iRead>0 && zRet[iRead-1]=='\n' ) ++pInSrc->lineno;
+       /* Consider: record line length to avoid rescan for it. */
+       return zRet;
+     }
+   }
+   else if( pInSrc->zStrIn!=0 ){
+     char *zBegin = pInSrc->zStrIn + pInSrc->iReadOffset;
+     if( *zBegin!=0 ){
+       int iTake = 0;
+       char c;
+       ncMax -= 1;
+       while( iTake<ncMax && (c=zBegin[iTake])!=0 ){
+         zBuf[iTake++] = c;
+         if( c=='\n' ){
+           ++pInSrc->lineno;
+           break;
+         }
+       }
+       if( ncMax>=0 ) zBuf[iTake] = 0;
+       pInSrc->iReadOffset += iTake;
+       /* Consider: record line length to avoid rescan for it. */
+       return zBuf;
+     }
+   }
+   return 0;
+ }
+ /*
+ ** This routine reads a line of text from designated stream source,
+ ** stores the text in memory obtained from malloc() and returns a
+ ** pointer to the text.  NULL is returned at end of file.
+ ** There will be no return if malloc() fails.
  **
- ** If zLine is not NULL then it is (and must be) a malloced buffer
- ** returned from a previous call to this routine that may be reused.
+ ** The trailing newline (or other line-end chars) are stripped.
+ **
+ ** If zLine is not NULL then it is a malloced buffer returned from
+ ** a previous call to this routine that may be reused.
  */
- static char *local_getline(char *zLine, FILE *in){
- #ifdef SQLITE_DEBUG
-   static char *zLinePrior = 0;
-   /* If zLine is passed in, not for the first time, it *must* be prior return,
-   ** otherwise the assumption made here as to its allocated size is shaky. */
-   assert(zLine==0 || zLinePrior==0 || zLine==zLinePrior);
- # define ZLINE_PRIOR(z) zLinePrior = z
- #else
- # define ZLINE_PRIOR(z)
- #endif
-   int nLine = zLine==0 ? 0 : 100; /* Length now (or to be)allocated */
-   int n = 0; /* Count of chars accumulated now */
+ static char *local_getline(char *zLine, InSource *pInSrc){
+   int nLine = zLine==0 ? 0 : 100;
+   int n = 0;
 -
    while( 1 ){
      if( n+100>nLine ){
        nLine = nLine*2 + 100;
    return zLine;
  }
  
 -
  /*
- ** Retrieve a single line of input text.
+ ** Retrieve a single line of input text from designated input source.
  **
- ** If in==0 then read from standard input and prompt before each line.
- ** If isContinuation is true, then a continuation prompt is appropriate.
- ** If isContinuation is zero, then the main prompt should be used.
+ ** If the input is interactive, then issue either the continuation prompt
+ ** or the main prompt, per isContinuation, before reading a line from
+ ** standard input. Otherwise, just read a line from the specified source.
  **
  ** If zPrior is not NULL then it is a buffer from a prior call to this
  ** routine that can be reused.
  ** The result is stored in space obtained from malloc() and must either
  ** be freed by the caller or else passed back into this routine via the
  ** zPrior argument for reuse.
+ **
+ ** If this function is called until it returns NULL, and the prior return
+ ** has been passed in for resuse, then the caller need/must not free it.
+ ** Otherwise, (in case of an early termination of reading from the given
+ ** input), the caller is responsible for freeing a prior, non-NULL return.
+ **
+ ** The trailing newline (or its ilk), if any, is trimmed.
+ ** The input line number is adjusted (via delegation or directly.)
  */
- static char *one_input_line(FILE *in, char *zPrior, int isContinuation){
-   char *zPrompt;
-   char *zResult;
-   if( in!=0 ){
-     zResult = local_getline(zPrior, in);
+ static char *one_input_line(InSource *pInSrc, char *zPrior, int isContinuation){
+   if( !INSOURCE_IS_INTERACTIVE(pInSrc) ){
+     return local_getline(zPrior, pInSrc);
    }else{
-     zPrompt = isContinuation ? continuePrompt : mainPrompt;
+     char *zPrompt = isContinuation ? continuePrompt : mainPrompt;
  #if SHELL_USE_LOCAL_GETLINE
-     fprintf(STD_OUT, "%s", zPrompt);
-     fflush(STD_OUT);
-     zResult = local_getline(zPrior, STD_IN);
+     printf("%s", zPrompt);
+     fflush(stdout);
+     return local_getline(zPrior, pInSrc);
  #else
+     char *zResult;
      free(zPrior);
      zResult = shell_readline(zPrompt);
+     ++pInSrc->lineno;
      if( zResult && *zResult ) shell_add_history(zResult);
+     return zResult;
  #endif
    }
-   return zResult;
  }
  
 -
  /*
  ** Return the value of a hexadecimal digit.  Return -1 if the input
  ** is not a hex digit.
@@@ -1118,6 -1143,51 +1172,51 @@@ struct EQPGraph 
    char zPrefix[100];    /* Graph prefix */
  };
  
 -# define SHELL_EXTENSIONS (~SHELL_OMIT_EXTENSIONS) & SHELL_ALL_EXTENSIONS
+ /* By default, omit the extension options that are not done yet. */
+ #ifndef SHELL_OMIT_EXTENSIONS
+ # define SHELL_OMIT_EXTENSIONS 6
+ #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 dynamic dot-command extension */
+ #define SHEXT_DYNCMDS_BIT 1
+ #define SHELL_DYNAMIC_COMMANDS \
+   NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_DYNCMDS_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_DYNCMDS_BIT)+(1<<SHEXT_VAREXP_BIT)
+ #if !defined(SHELL_OMIT_EXTENSIONS)
+ # define SHELL_EXTENSIONS SHELL_ALL_EXTENSIONS
+ #else
++# define SHELL_EXTENSIONS ((~SHELL_OMIT_EXTENSIONS) & SHELL_ALL_EXTENSIONS)
+ #endif
+ /* Runtime test for shell extended parsing, given ShellState pointer */
+ #if SHELL_EXTENDED_PARSING
+ # define SHEXT_PARSING(pSS) ((pSS->bExtendedDotCmds&(1<<SHEXT_PARSING_BIT))!=0)
+ #else
+ # define SHEXT_PARSING(pSS) 0
+ #endif
+ /* Runtime test for shell variable expansion, given ShellState pointer */
+ #if SHELL_EXTENDED_PARSING
+ # define SHEXT_VAREXP(pSS) ((pSS->bExtendedDotCmds&(1<<SHEXT_VAREXP_BIT))!=0)
+ #else
+ # define SHEXT_VAREXP(pSS) 0
+ #endif
  /* Parameters affecting columnar mode result display (defaulting together) */
  typedef struct ColModeOpts {
    int iWrap;            /* In columnar modes, wrap lines reaching this limit */
@@@ -1134,7 -1220,8 +1249,8 @@@ typedef struct OutputModeSave
  typedef struct ShellState ShellState;
  struct ShellState {
    sqlite3 *db;           /* The database */
-   int abruptExit;        /* Flag for immediate shell exit, exit code */
 -  int openFlags;         /* Additional flags to open. (SQLITE_OPEN_NOFOLLOW) */
++  int openFlags;         /* Additional flags to open.  (SQLITE_OPEN_NOFOLLOW) */
+   u8 openMode;           /* SHELL_OPEN_NORMAL, _APPENDVFS, or _ZIPFILE */
    u8 autoExplain;        /* Automatically turn on .explain mode */
    u8 autoEQP;            /* Run EXPLAIN QUERY PLAN prior to seach SQL stmt */
    u8 autoEQPtest;        /* autoEQP is in test mode */
    u8 doXdgOpen;          /* Invoke start/open/xdg-open in output_reset() */
    u8 nEqpLevel;          /* Depth of the EQP output graph */
    u8 eTraceType;         /* SHELL_TRACE_* value for type of trace */
 -  u8 bSafeMode;          /* True to prohibit unsafe operations */
 -  u8 bSafeModePersist;   /* The long-term value of bSafeMode */
 +  u8 bSafeMode;          /* True when unsafe operations are prohibited */
 +  u8 bSafeModeFuture;    /* See updateSafeMode regarding use of this. */
+   u8 bAllowSysDump;      /* Allow .dump use for sqlite_* tables. */
+   u8 bExtendedDotCmds;   /* Bits set to enable various shell extensions */
+   /* Output mode state subject to systematic save/restore: (See OM_STATE.) */
+   u8 showHeader;         /* True to show column names in List or Column mode */
+   u16 shellFlgs;         /* Various flags */
+   u8 mode;               /* An output mode setting */
    ColModeOpts cmOpts;    /* Option values affecting columnar mode output */
 -
+   char colSeparator[20]; /* Column separator character for several modes */
+   char rowSeparator[20]; /* Row separator character for MODE_Ascii */
+   /* Output mode state-keep for systematic save/restore: (See OM_STATE.) */
+   u8 nSavedModes;        /* number of valid items in next array */
+   OutputModeSave *pModeStack[MODE_STACK_MAX]; /* saved mode data buffers */
+   /* Output mode state-keep for certain ad-hoc save/restore ops: */
+   u8 cMode;              /* temporary output mode for the current query */
+   u8 normalMode;         /* Output mode before ".explain on" */
+   int *colWidth;         /* Requested width of each column in columnar modes */
+   int *actualWidth;      /* Actual width of each column */
+   int nWidth;            /* Number of slots in colWidth[] and actualWidth[] */
+   char nullValue[20];    /* Text to print for NULL retrieved from database */
    unsigned statsOn;      /* True to display memory stats before each finalize */
    unsigned mEqpLines;    /* Mask of veritical lines in the EQP output graph */
--  int inputNesting;      /* Track nesting level of .read and other redirects */
    int outCount;          /* Revert to stdout when reaching zero */
    int cnt;               /* Number of records displayed so far */
-   int lineno;            /* Line number of last line read from in */
-   int openFlags;         /* Additional flags to open.  (SQLITE_OPEN_NOFOLLOW) */
-   FILE *in;              /* Read commands from this stream */
++
++  int inputNesting;      /* Track nesting level of .read and other redirects */
+   InSource *pInSource;   /* Read commands and SQL from this stream source */
++
    FILE *out;             /* Write results here */
    FILE *traceOut;        /* Output for sqlite3_trace() */
++  int abruptExit;        /* Flag for immediate shell exit, exit code */
    int nErr;              /* Number of errors seen */
-   int mode;              /* An output mode setting */
-   int modePrior;         /* Saved mode */
-   int cMode;             /* temporary output mode for the current query */
-   int normalMode;        /* Output mode before ".explain on" */
    int writableSchema;    /* True if PRAGMA writable_schema=ON */
-   int showHeader;        /* True to show column names in List or Column mode */
    int nCheck;            /* Number of ".check" commands run */
    unsigned nProgress;    /* Number of progress callbacks encountered */
    unsigned mxProgress;   /* Maximum progress callbacks before failing */
    unsigned flgProgress;  /* Flags for the progress callback */
-   unsigned shellFlgs;    /* Various flags */
-   unsigned priorShFlgs;  /* Saved copy of flags */
++
    sqlite3_int64 szMax;   /* --maxsize argument to .open */
    char *zDestTable;      /* Name of destination table when MODE_Insert */
    char *zTempFile;       /* Temporary file that might need deleting */
    ExpertInfo expert;     /* Valid if previous command was ".expert OPT..." */
  };
  
- /* 
 +/*
 +** Limit input nesting via .read or any other input redirect.
 +** It's not too expensive, so a generous allowance can be made.
 +*/
 +#define MAX_INPUT_NESTING 25
 +
++/*
 +** This procedure updates the bSafeMode flag after completion of any
 +** operation (meta-command or SQL submission) that counts as one for
 +** which safe mode might be suspended.
 +** bSafeModeFuture has 3 states salient here:
 +** equal 0 => Safe mode is and will remain inactive.
 +** equal 1 => Safe mode is and will remain active.
 +** N > 1 => Safe mode is suspended for N-1 operations.
 +*/
 +static void updateSafeMode(ShellState *pSS){
 +  switch( pSS->bSafeModeFuture ){
 +  case 2:
 +  default:
 +    --pSS->bSafeModeFuture;
 +    /* Fall thru, suspension is in effect. */
 +  case 0:
 +    pSS->bSafeMode = 0;
 +    break;
 +  case 1:
 +    pSS->bSafeMode = 1;
 +  }
 +}
  
  /* Allowed values for ShellState.autoEQP
  */
  #define MODE_Line     0  /* One column per line.  Blank line between records */
  #define MODE_Column   1  /* One record per line in neat columns */
  #define MODE_List     2  /* One record per line with a separator */
 -#define MODE_Semi     3  /* Same as MODE_List but append ";" to each line */
 -#define MODE_Html     4  /* Generate an XHTML table */
 -#define MODE_Insert   5  /* Generate SQL "insert" statements */
 -#define MODE_Quote    6  /* Quote values as for SQL */
 -#define MODE_Tcl      7  /* Generate ANSI-C or TCL quoted elements */
 -#define MODE_Csv      8  /* Quote strings, numbers are plain */
 -#define MODE_Explain  9  /* Like MODE_Column, but do not truncate data */
 -#define MODE_Ascii   10  /* Use ASCII unit and record separators (0x1F/0x1E) */
 -#define MODE_Pretty  11  /* Pretty-print schemas */
 -#define MODE_EQP     12  /* Converts EXPLAIN QUERY PLAN output into a graph */
 -#define MODE_Json    13  /* Output JSON */
 -#define MODE_Markdown 14 /* Markdown formatting */
 -#define MODE_Table   15  /* MySQL-style table formatting */
 -#define MODE_Box     16  /* Unicode box-drawing characters */
 -#define MODE_Count   17  /* Output only a count of the rows of output */
 -#define MODE_Off     18  /* No query output shown */
 -
 -static const char *modeDescr[] = {
 -  "line",
 -  "column",
 -  "list",
 -  "semi",
 -  "html",
 -  "insert",
 -  "quote",
 -  "tcl",
 -  "csv",
 -  "explain",
 -  "ascii",
 -  "prettyprint",
 -  "eqp",
 -  "json",
 -  "markdown",
 -  "table",
 -  "box",
 -  "count",
 -  "off"
 +#define MODE_Html     3  /* Generate an XHTML table */
 +#define MODE_Tcl      4  /* Generate ANSI-C or TCL quoted elements */
 +#define MODE_Csv      5  /* Quote strings, numbers are plain */
- #define MODE_Tab      6  /* Transitory */
++#define MODE_Tab      6  /* Transitory, an alias for MODE_List with tabs */
 +#define MODE_Insert   7  /* Generate SQL "insert" statements */
 +#define MODE_Quote    8  /* Quote values as for SQL */
 +#define MODE_Ascii    9  /* Use ASCII unit and record separators (0x1F/0x1E) */
 +#define MODE_Markdown 10 /* Markdown formatting */
 +#define MODE_Table    11 /* MySQL-style table formatting */
 +#define MODE_Box      12 /* Unicode box-drawing characters */
 +#define MODE_Count    13 /* Output only a count of the rows of output */
 +#define MODE_Off      14 /* No query output shown */
 +#define MODE_Json     15 /* Output JSON */
 +#define MODE_EQP      16 /* Converts EXPLAIN QUERY PLAN output into a graph */
 +#define MODE_Explain  17 /* Like MODE_Column, but do not truncate data */
 +#define MODE_Pretty   18 /* Pretty-print schemas */
 +#define MODE_Semi     19 /* Same as MODE_List but append ";" to each line */
- #define MODE_COUNT_OF 20
++#define MODE_COUNT_OF 20 /* also an invalid MODE_x value */
++
++/* Note that some of above ordering is assumed for this mode classification. */
++#define MODE_IS_COLUMNAR(m) \
++  ( (m)==MODE_Column||((m)>=MODE_Markdown&&(m)<=MODE_Box) )
++
 +static struct {
 +  const char *zModeName;
 +  u8 bDepluralize;
 +  u8 bUserBlocked;
 +  u8 iAliasFor;
 +} modeDescr[] = {
 +  { "lines", 1, 0, MODE_Line },
 +  { "columns", 1, 0, MODE_Column },
 +  { "list", 0, 0, MODE_List },
 +  { "html", 0, 0, MODE_Html },
 +  { "tcl", 0, 0, MODE_Tcl },
 +  { "csv", 0, 0, MODE_Csv },
 +  { "tabs", 1, 0, MODE_List },
 +  { "insert", 0, 0, MODE_Insert },
 +  { "quote", 0, 0, MODE_Quote },
 +  { "ascii", 0, 0, MODE_Ascii },
 +  { "markdown", 0, 0, MODE_Markdown },
 +  { "table", 0, 0, MODE_Table },
 +  { "box", 0, 0, MODE_Box },
 +  { "count", 0, 0, MODE_Count },
 +  { "off", 0, 0, MODE_Off },
 +  { "json", 0, 0, MODE_Json },
 +  { "eqp", 0, 1, MODE_EQP },
 +  { "explain", 0, 1, MODE_Explain },
 +  { "prettyprint", 0, 1, MODE_Pretty },
 +  { "semi", 0, 1, MODE_Semi }
  };
  
  /*
@@@ -1399,12 -1451,10 +1525,12 @@@ static int failIfSafeMode
      va_start(ap, zErrMsg);
      zMsg = sqlite3_vmprintf(zErrMsg, ap);
      va_end(ap);
-     raw_printf(STD_ERR, "line %d: ", p->lineno);
 -    raw_printf(stderr, "line %d: ", p->pInSource->lineno);
 -    utf8_printf(stderr, "%s\n", zMsg);
 -    exit(1);
++    raw_printf(STD_ERR, "line %d: ", p->pInSource->lineno);
 +    utf8_printf(STD_ERR, "%s\n", zMsg);
 +    p->abruptExit = 3;
 +    return 1;
    }
 +  return 0;
  }
  
  /*
@@@ -3190,24 -3367,22 +3453,20 @@@ static int param_table_exists( sqlite3 
  **
  ** No bindings occur if this table does not exist.  The name of the table
  ** begins with "sqlite_" so that it will not collide with ordinary application
- ** tables.  The table must be in the TEMP schema.
+ ** tables.  The table must be in the TEMP schema. Only rows with
+ ** uses=PTU_Binding are eligible for parameter binding.
  */
- static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){
-   int nVar;
+ static void bind_prepared_stmt(sqlite3 *db, sqlite3_stmt *pStmt){
 -  int nVar;
++  int nVar = sqlite3_bind_parameter_count(pStmt);
    int i;
    int rc;
    sqlite3_stmt *pQ = 0;
  
--  nVar = sqlite3_bind_parameter_count(pStmt);
-   if( nVar==0 ) return;  /* Nothing to do */
-   if( sqlite3_table_column_metadata(pArg->db, "TEMP", "sqlite_parameters",
-                                     "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){
-     return; /* Parameter table does not exist */
-   }
-   rc = sqlite3_prepare_v2(pArg->db,
+   if( nVar==0 || !param_table_exists(db) ) return;  /* Nothing to do */
+   rc = sqlite3_prepare_v2(db,
 -          "SELECT value FROM "PARAM_TABLE_SNAME
 -          " WHERE key=?1 AND uses=0", -1, &pQ, 0);
 +          "SELECT value FROM temp.sqlite_parameters"
 +          " WHERE key=?1", -1, &pQ, 0);
    if( rc || pQ==0 ) return;
 -  assert( PTU_Binding==0 ); /* instead of working symbol value into query */
    for(i=1; i<=nVar; i++){
      char zNum[30];
      const char *zVar = sqlite3_bind_parameter_name(pStmt, i);
@@@ -3421,8 -3596,8 +3680,8 @@@ static char *quoted_column(sqlite3_stm
  
  /*
  ** Run a prepared statement and output the result in one of the
--** table-oriented formats: MODE_Column, MODE_Markdown, MODE_Table,
--** or MODE_Box.
++** table-oriented formats, for which MODE_IS_COLUMNAR(m) is true:
++** MODE_Column, MODE_Markdown, MODE_Table, or MODE_Box.
  **
  ** This is different from ordinary exec_prepared_stmt() in that
  ** it has to run the entire query and gather the results into memory
@@@ -3654,11 -3829,11 +3913,7 @@@ static void exec_prepared_stmt
    int rc;
    sqlite3_uint64 nRow = 0;
  
--  if( pArg->cMode==MODE_Column
--   || pArg->cMode==MODE_Table
--   || pArg->cMode==MODE_Box
--   || pArg->cMode==MODE_Markdown
--  ){
++  if( MODE_IS_COLUMNAR(pArg->cMode) ){
      exec_prepared_stmt_columnar(pArg, pStmt);
      return;
    }
@@@ -3807,12 -3987,6 +4062,12 @@@ static int expertDotCommand
    int i;
    int iSample = 0;
  
-     raw_printf(stderr, 
 +  if( pState->bSafeMode ){
++    raw_printf(STD_ERR,
 +               "Cannot run experimental commands such as \"%s\" in safe mode\n",
 +               azArg[0]);
 +    return 1;
 +  }
    assert( pState->expert.pExpert==0 );
    memset(&pState->expert, 0, sizeof(ExpertInfo));
  
    if( rc==SQLITE_OK ){
      pState->expert.pExpert = sqlite3_expert_new(pState->db, &zErr);
      if( pState->expert.pExpert==0 ){
-       raw_printf(stderr, "sqlite3_expert_new: %s\n",
 -      raw_printf(stderr, "sqlite3_expert_new: %s\n", zErr ? zErr : "out of memory");
++      raw_printf(STD_ERR, "sqlite3_expert_new: %s\n",
 +                 zErr ? zErr : "out of memory");
        rc = SQLITE_ERROR;
      }else{
        sqlite3_expert_config(
@@@ -4313,238 -4492,535 +4573,238 @@@ static int run_schema_dump_query
    return rc;
  }
  
 +/* Configure help text generation to have coalesced secondary
 + * help lines with trailing newlines on all help lines.
 + */
 +DISPATCH_CONFIG[
 +  HELP_COALESCE=1
 +];
 +#define HELP_TEXT_FMT "%s"
 +/* Above HELP_COALESCE config and HELP_TEXT_FMT PP vars must track.
 + * Alternative is 0 and "%s\n" .
 + */
 +
 +/*
 +** Output primary (single-line) help for a known command.
 +*/
 +static void showPrimaryHelp(FILE *out, const char *zCmd){
 +  const char **pzH;
 +  int nc = strlen30(zCmd);
 +  for(pzH = azHelp; *pzH != 0; ++pzH){
 +    if( **pzH=='.' && strncmp(zCmd, (*pzH)+1, nc)==0 ){
 +      utf8_printf(out, HELP_TEXT_FMT, *pzH);
 +      break;
 +    }
 +  }
 +}
 +
  /*
 -** Text of help messages.
 +** Output various subsets of help text. These 5 are defined:
 +** 1. For all commands, primary help text only.
 +** 2. For all commands, complete help text.
 +** 3. For multiple commands matching a pattern, primary help text only.
 +** 4. For a single matched command, complete help text.
 +** 5. For commands whose help contains a pattern, complete help text.
 +** These variations are indicated thusly:
 +** 1. zPattern is NULL
 +** 2. zPattern is ""
 +** 3. zPattern is a prefix matching more than one command
 +** 4. zPattern is a word or prefix matching just one command
 +** 5. zPattern is neither case 3 or 4 but is found in complete help text
  **
 -** The help text for each individual command begins with a line that starts
 -** with ".".  Subsequent lines are supplimental information.
 +** Return the number of matches.
 +*/
 +static int showHelp(FILE *out, const char *zPattern){
 +  int npm = 0;
 +  char *zPat = sqlite3_mprintf(".%s*", zPattern? zPattern : "");
 +  const char **pzHxtra;
 +  const char **pzH;
 +  int nma = 0;
 +  shell_check_oom(zPat);
 +  for(pzH = azHelp; *pzH != 0; ++pzH){
 +    /* Look for all commands or those for which zPattern is an exact prefix */
 +    if( *pzH[0]=='.' ){
 +      if ( sqlite3_strglob(zPat, *pzH)==0 ){
 +        utf8_printf(out, HELP_TEXT_FMT, *pzH);
 +        pzHxtra = pzH + 1;
 +        ++npm;
 +      }
 +    }else if( zPattern && *zPattern==0 ){
 +      utf8_printf(out, HELP_TEXT_FMT, *pzH);
 +    }
 +  }
 +  sqlite3_free(zPat);
 +  if( npm==1 ){
 +    /* When zPattern is a prefix of exactly one command, then include
 +    ** the secondary help of that command, (beginning at *pzHxtra.) */
 +    while( *pzHxtra !=0 && *pzHxtra[0]!='.' ){
 +      utf8_printf(out, HELP_TEXT_FMT, *pzHxtra++);
 +    }
 +  }
 +  if( npm>0 )
 +    return npm;
 +
 +  /* Having failed to match a command, look for commands whose help contains
 +   * zPattern anywhere. Show the complete text of all such commands.
 +   */
 +  zPat = sqlite3_mprintf("%%%s%%", zPattern);
 +  if( zPat==0 ) shell_out_of_memory();
 +  for(pzH = azHelp; *pzH != 0;){
 +    if( *pzH[0]=='.' ){
 +      pzHxtra = pzH;
 +      nma = 0;
 +    }
 +    if( sqlite3_strlike(zPat, *pzH, 0)==0 )
 +      ++nma;
 +    ++pzH;
 +    if( nma>0 && (*pzH==0 || *pzH[0]=='.') ){
 +      ++npm;
 +      while( pzHxtra < pzH )
 +        utf8_printf(out, HELP_TEXT_FMT, *pzHxtra++);
 +    }
 +  }
 +  sqlite3_free(zPat);
 +
 +  return npm;
 +}
 +
 +/* Forward reference */
 +static int process_input(ShellState *p);
 +
 +/*
 +** Read the content of file zName into memory obtained from sqlite3_malloc64()
 +** and return a pointer to the buffer. The caller is responsible for freeing
 +** the memory.
  **
 -** There must be two or more spaces between the end of the command and the
 -** start of the description of what that command does.
 +** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes
 +** read.
 +**
 +** For convenience, a nul-terminator byte is always appended to the data read
 +** from the file before the buffer is returned. This byte is not included in
 +** the final value of (*pnByte), if applicable.
 +**
 +** NULL is returned if any error is encountered. The final value of *pnByte
 +** is undefined in this case.
  */
 -static const char *(azHelp[]) = {
 -#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE)
 -  ".archive ...             Manage SQL archives",
 -  "   Each command must have exactly one of the following options:",
 -  "     -c, --create               Create a new archive",
 -  "     -u, --update               Add or update files with changed mtime",
 -  "     -i, --insert               Like -u but always add even if unchanged",
 -  "     -r, --remove               Remove files from archive",
 -  "     -t, --list                 List contents of archive",
 -  "     -x, --extract              Extract files from archive",
 -  "   Optional arguments:",
 -  "     -v, --verbose              Print each filename as it is processed",
 -  "     -f FILE, --file FILE       Use archive FILE (default is current db)",
 -  "     -a FILE, --append FILE     Open FILE using the apndvfs VFS",
 -  "     -C DIR, --directory DIR    Read/extract files from directory DIR",
 -  "     -g, --glob                 Use glob matching for names in archive",
 -  "     -n, --dryrun               Show the SQL that would have occurred",
 -  "   Examples:",
 -  "     .ar -cf ARCHIVE foo bar  # Create ARCHIVE from files foo and bar",
 -  "     .ar -tf ARCHIVE          # List members of ARCHIVE",
 -  "     .ar -xvf ARCHIVE         # Verbosely extract files from ARCHIVE",
 -  "   See also:",
 -  "      http://sqlite.org/cli.html#sqlite_archive_support",
 -#endif
 -#ifndef SQLITE_OMIT_AUTHORIZATION
 -  ".auth ON|OFF             Show authorizer callbacks",
 -#endif
 -  ".backup ?DB? FILE        Backup DB (default \"main\") to FILE",
 -  "   Options:",
 -  "       --append            Use the appendvfs",
 -  "       --async             Write to FILE without journal and fsync()",
 -  ".bail on|off             Stop after hitting an error.  Default OFF",
 -  ".binary on|off           Turn binary output on or off.  Default OFF",
 -  ".cd DIRECTORY            Change the working directory to DIRECTORY",
 -  ".changes on|off          Show number of rows changed by SQL",
 -  ".clone NEWDB             Clone data into NEWDB from the existing database",
 -  ".connection [close] [#]  Open or close an auxiliary database connection",
 -  ".databases               List names and files of attached databases",
 -  ".dbconfig ?op? ?val?     List or change sqlite3_db_config() options",
 -  ".dbinfo ?DB?             Show status information about the database",
 -  ".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\"",
 +static char *readFile(const char *zName, int *pnByte){
 +  FILE *in = fopen(zName, "rb");
 +  long nIn;
 +  size_t nRead;
 +  char *pBuf;
 +  if( in==0 ) return 0;
 +  fseek(in, 0, SEEK_END);
 +  nIn = ftell(in);
 +  rewind(in);
 +  pBuf = sqlite3_malloc64( nIn+1 );
 +  if( pBuf==0 ){ fclose(in); return 0; }
 +  nRead = fread(pBuf, nIn, 1, in);
 +  fclose(in);
 +  if( nRead!=1 ){
 +    sqlite3_free(pBuf);
 +    return 0;
 +  }
 +  pBuf[nIn] = 0;
 +  if( pnByte ) *pnByte = nIn;
 +  return pBuf;
 +}
 +
 +#if defined(SQLITE_ENABLE_SESSION)
 +/*
 +** Close a single OpenSession object and release all of its associated
 +** resources.
 +*/
 +static void session_close(OpenSession *pSession){
 +  int i;
 +  sqlite3session_delete(pSession->p);
 +  sqlite3_free(pSession->zName);
 +  for(i=0; i<pSession->nFilter; i++){
 +    sqlite3_free(pSession->azFilter[i]);
 +  }
 +  sqlite3_free(pSession->azFilter);
 +  memset(pSession, 0, sizeof(OpenSession));
 +}
  #endif
 -  "      trigger               Like \"full\" but also show trigger bytecode",
 -  ".excel                   Display the output of next command in spreadsheet",
 -  "   --bom                   Put a UTF8 byte-order mark on intermediate file",
 -  ".exit ?CODE?             Exit this program with return-code CODE",
 -  ".expert                  EXPERIMENTAL. Suggest indexes for queries",
 -  ".explain ?on|off|auto?   Change the EXPLAIN formatting mode.  Default: auto",
 -  ".filectrl CMD ...        Run various sqlite3_file_control() operations",
 -  "   --schema SCHEMA         Use SCHEMA instead of \"main\"",
 -  "   --help                  Show CMD details",
 -  ".fullschema ?--indent?   Show schema and the content of sqlite_stat tables",
 -  ".headers on|off          Turn display of headers on or off",
 -  ".help ?PATTERN?|?-all?   Show help for PATTERN or everything, or summarize",
 -  "                           Repeat -all to see undocumented commands",
 -  ".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.",
 -#ifndef SQLITE_OMIT_TEST_CONTROL
 -  ".imposter INDEX TABLE    Create imposter table TABLE on index INDEX",
 +
 +/*
 +** Close all OpenSession objects and release all associated resources.
 +*/
 +#if defined(SQLITE_ENABLE_SESSION)
 +static void session_close_all(ShellState *p, int i){
 +  int j;
 +  struct AuxDb *pAuxDb = i<0 ? p->pAuxDb : &p->aAuxDb[i];
 +  for(j=0; j<pAuxDb->nSession; j++){
 +    session_close(&pAuxDb->aSession[j]);
 +  }
 +  pAuxDb->nSession = 0;
 +}
 +#else
 +# define session_close_all(X,Y)
  #endif
 -  ".indexes ?TABLE?         Show names of indexes",
 -  "                           If TABLE is specified, only show indexes for",
 -  "                           tables matching TABLE using the LIKE operator.",
 -#ifdef SQLITE_ENABLE_IOTRACE
 -  ".iotrace FILE            Enable I/O diagnostic logging to FILE",
 +
 +/*
 +** Implementation of the xFilter function for an open session.  Omit
 +** any tables named by ".session filter" but let all other table through.
 +*/
 +#if defined(SQLITE_ENABLE_SESSION)
 +static int session_filter(void *pCtx, const char *zTab){
 +  OpenSession *pSession = (OpenSession*)pCtx;
 +  int i;
 +  for(i=0; i<pSession->nFilter; i++){
 +    if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0;
 +  }
 +  return 1;
 +}
  #endif
 -  ".limit ?LIMIT? ?VAL?     Display or change the value of an SQLITE_LIMIT",
 -  ".lint OPTIONS            Report potential schema issues.",
 -  "     Options:",
 -  "        fkey-indexes     Find missing foreign key indexes",
 -#ifndef SQLITE_OMIT_LOAD_EXTENSION
 -  ".load FILE ?ENTRY?       Load an extension library",
 -#endif
 -  ".log FILE|off            Turn logging on or off.  FILE can be stderr/stdout",
 -  ".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 --width 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",
 -  ".nonce STRING            Suspend safe mode for one command if nonce matches",
 -  ".nullvalue STRING        Use STRING in place of NULL values",
 -  ".once ?OPTIONS? ?FILE?   Output for the next SQL command only to FILE",
 -  "     If FILE begins with '|' then open as a pipe",
 -  "       --bom  Put a UTF8 byte-order mark at the beginning",
 -  "       -e     Send output to the system text editor",
 -  "       -x     Send output as CSV to a spreadsheet (same as \".excel\")",
 -  ".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",
 -  ".output ?FILE?           Send output to FILE or stdout if FILE is omitted",
 -  "   If FILE begins with '|' then open it as a pipe.",
 -  "   Options:",
 -  "     --bom                 Prefix output with a UTF8 byte-order mark",
 -  "     -e                    Send output to the system text editor",
 -  "     -x                    Send output as CSV to a spreadsheet",
 -  ".parameter CMD ...       Manage SQL parameter bindings and scripts 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 or -e to use edited value as text or evaluate it first.",
 -#endif
 -  "   init                    Initialize TEMP table for bindings and scripts",
 -  "   list ?PATTERNS?         List parameters table binding and script 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, or with a letter",
 -  "      to be executable; the value is following argument list, space-joined.",
 -  "      Option TOPT may be one of {-b -i -n -r -t} to cast effective value",
 -  "      to BLOB, INT, NUMERIC, REAL or TEXT respectively.",
 -  "   unset ?NAMES?           Remove named parameter(s) from parameters table",
 -  ".print STRING...         Print literal STRING, then a newline",
 -#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
 -  ".progress N              Invoke progress handler after every N opcodes",
 -  "   --limit N                 Interrupt after N progress callbacks",
 -  "   --once                    Do no more than one progress interrupt",
 -  "   --quiet|-q                No output except at interrupts",
 -  "   --reset                   Reset the count for each input and interrupt",
 -#endif
 -  ".prompt MAIN CONTINUE    Replace the standard prompts",
 -  ".quit                    Exit this program",
 -  ".read FILE               Read input from FILE or command output",
 -  "    If FILE begins with \"|\", it is a command that generates the input.",
 -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
 -  ".recover                 Recover as much data as possible from corrupt db.",
 -  "   --freelist-corrupt       Assume the freelist is corrupt",
 -  "   --recovery-db NAME       Store recovery metadata in database file NAME",
 -  "   --lost-and-found TABLE   Alternative name for the lost-and-found table",
 -  "   --no-rowids              Do not attempt to recover rowid values",
 -  "                            that are not also INTEGER PRIMARY KEYs",
 -#endif
 -  ".restore ?DB? FILE       Restore content of DB (default \"main\") from FILE",
 -  ".save ?OPTIONS? FILE     Write database to FILE (an alias for .backup ...)",
 -  ".scanstats on|off        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_\"",
 -#if 0 /* .script is soon to vanish, or be implemented. */
 -  ".script CMD ...            Manage and use SQL statement store (some TBD)",
 -  "   clear ?NAMES?           Erase some or all stored SQL statements",
 -  "   edit NAME               Use edit() to create or alter statement NAME",
 -  "   execute ?NAMES?         Execute the named SQL statements in order",
 -  "   init                    Initialize the TEMP table that holds statements",
 -  "   list                    List the currently stored statements",
 -  "   load FILE ?NAMES?       Load some or all SQL statements from FILE",
 -  "   save FILE ?NAMES?       Save some or all SQL statements into FILE",
 -  "   set NAME VALUE          Give SQL statement NAME a value of VALUE",
 -  "                           NAME must start with a letter, and effective",
 -  "                           value is space-joined, following argument list",
 -  "   unset ?NAMES?           Remove NAMES from statement store",
 -#endif
 -  ".selftest ?OPTIONS?      Run tests defined in the SELFTEST table",
 -  "    Options:",
 -  "       --init               Create a new SELFTEST table",
 -  "       -v                   Verbose output",
 -  ".separator COL ?ROW?     Change the column and row separators",
 -#if defined(SQLITE_ENABLE_SESSION)
 -  ".session ?NAME? CMD ...  Create or control sessions",
 -  "   Subcommands:",
 -  "     attach TABLE             Attach TABLE",
 -  "     changeset FILE           Write a changeset into FILE",
 -  "     close                    Close one session",
 -  "     enable ?BOOLEAN?         Set or query the enable bit",
 -  "     filter GLOB...           Reject tables matching GLOBs",
 -  "     indirect ?BOOLEAN?       Mark or query the indirect status",
 -  "     isempty                  Query whether the session is empty",
 -  "     list                     List currently open session names",
 -  "     open DB NAME             Open a new session on DB",
 -  "     patchset FILE            Write a patchset into FILE",
 -  "   If ?NAME? is omitted, the first defined session is used.",
 -#endif
 -  ".sha3sum ...             Compute a SHA3 hash of database content",
 -  "    Options:",
 -  "      --schema              Also hash the sqlite_schema table",
 -  "      --sha3-224            Use the sha3-224 algorithm",
 -  "      --sha3-256            Use the sha3-256 algorithm (default)",
 -  "      --sha3-384            Use the sha3-384 algorithm",
 -  "      --sha3-512            Use the sha3-512 algorithm",
 -  "    Any other argument is a LIKE pattern for tables to hash",
 -#ifndef SQLITE_NOHAVE_SYSTEM
 -  ".shell CMD ARGS...       Run CMD ARGS... in a system shell",
 -#endif
 -#ifndef SHELL_OMIT_EXTENSIONS
 -  ".shxopts ?SIGNED_OPTS?   Show or alter shell extension options",
 -  "   Run without arguments to see their self-descriptive names",
 -#endif
 -  ".show                    Show the current values for various settings",
 -  ".stats ?ARG?             Show stats or turn stats on or off",
 -  "   off                      Turn off automatic stat display",
 -  "   on                       Turn on automatic stat display",
 -  "   stmt                     Show statement stats",
 -  "   vmstep                   Show the virtual machine step count only",
 -#ifndef SQLITE_NOHAVE_SYSTEM
 -  ".system CMD ARGS...      Run CMD ARGS... in a system shell",
 -#endif
 -  ".tables ?TABLE?          List names of tables matching LIKE pattern TABLE",
 -  ".timeout MS              Try opening locked tables for MS milliseconds",
 -  ".timer on|off            Turn SQL timer on or off",
 -#ifndef SQLITE_OMIT_TRACE
 -  ".trace ?OPTIONS?         Output each SQL statement as it is run",
 -  "    FILE                    Send output to FILE",
 -  "    stdout                  Send output to stdout",
 -  "    stderr                  Send output to stderr",
 -  "    off                     Disable tracing",
 -  "    --expanded              Expand query parameters",
 -#ifdef SQLITE_ENABLE_NORMALIZE
 -  "    --normalized            Normal the SQL statements",
 -#endif
 -  "    --plain                 Show SQL as it is input",
 -  "    --stmt                  Trace statement execution (SQLITE_TRACE_STMT)",
 -  "    --profile               Profile statements (SQLITE_TRACE_PROFILE)",
 -  "    --row                   Trace each row (SQLITE_TRACE_ROW)",
 -  "    --close                 Trace connection close (SQLITE_TRACE_CLOSE)",
 -#endif /* SQLITE_OMIT_TRACE */
 -#ifdef SQLITE_DEBUG
 -  ".unmodule NAME ...       Unregister virtual table modules",
 -  "    --allexcept             Unregister everything except those named",
 -#endif
 -  ".vfsinfo ?AUX?           Information about the top-level VFS",
 -  ".vfslist                 List all available VFSes",
 -  ".vfsname ?AUX?           Print the name of the VFS stack",
 -  ".width NUM1 NUM2 ...     Set minimum column widths for columnar output",
 -  "   Negative values right-justify",
 -  ".x NAMES ...             Excecute content of some .param set variable(s)",
 -  "   Only variables whose name begins with a letter are eligible for this."
 -};
 -
 -/* Like azHelp[], but for undocumented commands. */
 -static const char *(azHelpUndoc[]) = {
 -  "These undocumented commands are for internal testing.\n"
 -  "They are subject to change without notice.\n"
 -  ".check GLOB        Fail if output since .testcase does not match",
 -  ".seeargs           Echo arguments separated or terminated by |",
 -  ".selftest-boolean  Check boolean translation",
 -  ".selftest-integer  Check integer conversion",
 -  ".testcase NAME     Begin output redirect to 'testcase-out.txt'",
 -  ".testctrl CMD ...  Run various sqlite3_test_control() operations",
 -  "   Run \".testctrl\" with no arguments for details",
 -};
 -
 -/* This literal's value AND address are used for help's workings. */
 -static const char *zHelpAll = "-all";
  
  /*
 -** Output help text.
 -**
 -** zPattern describes the set of commands for which help text is provided.
 -** If zPattern is NULL, then show all commands, but only give a one-line
 -** description of each.
 +** Try to deduce the type of file for zName based on its content.  Return
 +** one of the SHELL_OPEN_* constants.
  **
 -** Return the number of matches.
 +** If the file does not exist or is empty but its name looks like a ZIP
 +** archive and the dfltZip flag is true, then assume it is a ZIP archive.
 +** Otherwise, assume an ordinary database regardless of the filename if
 +** the type cannot be determined from content.
  */
- int deduceDatabaseType(const char *zName, int dfltZip){
 -static int showHelp(FILE *out, const char *zPattern){
 -  int i = 0;
 -  int j = 0;
 -  int n = 0;
 -  int nHelp = ArraySize(azHelp);
 -  const char **azHelpText = azHelp;
 -  char *zPat;
 -  if( zPattern==zHelpAll ){
 -    /* It's zHelpAll, show help for the undocumented commands. */
 -    nHelp = ArraySize(azHelpUndoc);
 -    azHelpText = azHelpUndoc;
 -  }
 -  if( zPattern==0
 -   || zPattern[0]=='0'
 -   || strcmp(zPattern,"-a")==0
 -   || strcmp(zPattern,"-all")==0
 -   || strcmp(zPattern,"--all")==0
 -  ){
 -    /* Show all commands.
 -     * If no pattern, only one line per command.
 -     * If any --all-like pattern, show all help.
 -     */
 -    if( zPattern==0 ) zPattern = "";
 -    for(i=0; i<nHelp; i++){
 -      if( azHelpText[i][0]=='.' || zPattern[0] ){
 -        utf8_printf(out, "%s\n", azHelpText[i]);
 -        n++;
 -      }
++u8 deduceDatabaseType(const char *zName, int dfltZip){
 +  FILE *f = fopen(zName, "rb");
 +  size_t n;
-   int rc = SHELL_OPEN_UNSPEC;
++  u8 rc = SHELL_OPEN_UNSPEC;
 +  char zBuf[100];
 +  if( f==0 ){
 +    if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
 +       return SHELL_OPEN_ZIPFILE;
 +    }else{
 +       return SHELL_OPEN_NORMAL;
      }
 +  }
 +  n = fread(zBuf, 16, 1, f);
 +  if( n==1 && memcmp(zBuf, "SQLite format 3", 16)==0 ){
 +    fclose(f);
 +    return SHELL_OPEN_NORMAL;
 +  }
 +  fseek(f, -25, SEEK_END);
 +  n = fread(zBuf, 25, 1, f);
 +  if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){
 +    rc = SHELL_OPEN_APPENDVFS;
    }else{
 -    /* Look for commands that for which zPattern is an exact prefix */
 -    zPat = sqlite3_mprintf(".%s*", zPattern);
 -    shell_check_oom(zPat);
 -    for(i=0; i<nHelp; i++){
 -      if( sqlite3_strglob(zPat, azHelpText[i])==0 ){
 -        utf8_printf(out, "%s\n", azHelpText[i]);
 -        j = i+1;
 -        n++;
 -      }
 -    }
 -    sqlite3_free(zPat);
 -    if( n ){
 -      if( n==1 ){
 -        /* when zPattern is a prefix of exactly one command, then include the
 -        ** details of that command, which should begin at offset j */
 -        while( j<nHelp-1 && azHelpText[j][0]!='.' ){
 -          utf8_printf(out, "%s\n", azHelpText[j]);
 -          j++;
 -        }
 -      }
 -      return n;
 -    }
 -    /* Look for commands that contain zPattern anywhere.  Show the complete
 -    ** text of all commands that match. */
 -    zPat = sqlite3_mprintf("%%%s%%", zPattern);
 -    shell_check_oom(zPat);
 -    for(i=0; i<nHelp; i++){
 -      if( azHelpText[i][0]=='.' ) j = i;
 -      if( sqlite3_strlike(zPat, azHelpText[i], 0)==0 ){
 -        utf8_printf(out, "%s\n", azHelpText[j]);
 -        while( j<nHelp-1 && azHelpText[j+1][0]!='.' ){
 -          j++;
 -          utf8_printf(out, "%s\n", azHelpText[j]);
 -        }
 -        i = j;
 -        n++;
 -      }
 +    fseek(f, -22, SEEK_END);
 +    n = fread(zBuf, 22, 1, f);
 +    if( n==1 && zBuf[0]==0x50 && zBuf[1]==0x4b && zBuf[2]==0x05
 +       && zBuf[3]==0x06 ){
 +      rc = SHELL_OPEN_ZIPFILE;
 +    }else if( n==0 && dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
 +      rc = SHELL_OPEN_ZIPFILE;
      }
 -    sqlite3_free(zPat);
    }
 -  return n;
 +  fclose(f);
-   return rc;  
++  return rc;
  }
  
 -/* Forward reference */
 -static int process_input(ShellState *p);
 -
 +#ifndef SQLITE_OMIT_DESERIALIZE
  /*
 -** Read the content of file zName into memory obtained from sqlite3_malloc64()
 -** and return a pointer to the buffer. The caller is responsible for freeing
 -** the memory.
 -**
 -** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes
 -** read.
 -**
 -** For convenience, a nul-terminator byte is always appended to the data read
 -** from the file before the buffer is returned. This byte is not included in
 -** the final value of (*pnByte), if applicable.
 -**
 -** NULL is returned if any error is encountered. The final value of *pnByte
 -** is undefined in this case.
 -*/
 -static char *readFile(const char *zName, int *pnByte){
 -  FILE *in = fopen(zName, "rb");
 -  long nIn;
 -  size_t nRead;
 -  char *pBuf;
 -  if( in==0 ) return 0;
 -  fseek(in, 0, SEEK_END);
 -  nIn = ftell(in);
 -  rewind(in);
 -  pBuf = sqlite3_malloc64( nIn+1 );
 -  if( pBuf==0 ){ fclose(in); return 0; }
 -  nRead = fread(pBuf, nIn, 1, in);
 -  fclose(in);
 -  if( nRead!=1 ){
 -    sqlite3_free(pBuf);
 -    return 0;
 -  }
 -  pBuf[nIn] = 0;
 -  if( pnByte ) *pnByte = nIn;
 -  return pBuf;
 -}
 -
 -#if defined(SQLITE_ENABLE_SESSION)
 -/*
 -** Close a single OpenSession object and release all of its associated
 -** resources.
 -*/
 -static void session_close(OpenSession *pSession){
 -  int i;
 -  sqlite3session_delete(pSession->p);
 -  sqlite3_free(pSession->zName);
 -  for(i=0; i<pSession->nFilter; i++){
 -    sqlite3_free(pSession->azFilter[i]);
 -  }
 -  sqlite3_free(pSession->azFilter);
 -  memset(pSession, 0, sizeof(OpenSession));
 -}
 -#endif
 -
 -/*
 -** Close all OpenSession objects and release all associated resources.
 -*/
 -#if defined(SQLITE_ENABLE_SESSION)
 -static void session_close_all(ShellState *p, int i){
 -  int j;
 -  struct AuxDb *pAuxDb = i<0 ? p->pAuxDb : &p->aAuxDb[i];
 -  for(j=0; j<pAuxDb->nSession; j++){
 -    session_close(&pAuxDb->aSession[j]);
 -  }
 -  pAuxDb->nSession = 0;
 -}
 -#else
 -# define session_close_all(X,Y)
 -#endif
 -
 -/*
 -** Implementation of the xFilter function for an open session.  Omit
 -** any tables named by ".session filter" but let all other table through.
 -*/
 -#if defined(SQLITE_ENABLE_SESSION)
 -static int session_filter(void *pCtx, const char *zTab){
 -  OpenSession *pSession = (OpenSession*)pCtx;
 -  int i;
 -  for(i=0; i<pSession->nFilter; i++){
 -    if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0;
 -  }
 -  return 1;
 -}
 -#endif
 -
 -/*
 -** Try to deduce the type of file for zName based on its content.  Return
 -** one of the SHELL_OPEN_* constants.
 -**
 -** If the file does not exist or is empty but its name looks like a ZIP
 -** archive and the dfltZip flag is true, then assume it is a ZIP archive.
 -** Otherwise, assume an ordinary database regardless of the filename if
 -** the type cannot be determined from content.
 -*/
 -u8 deduceDatabaseType(const char *zName, int dfltZip){
 -  FILE *f = fopen(zName, "rb");
 -  size_t n;
 -  u8 rc = SHELL_OPEN_UNSPEC;
 -  char zBuf[100];
 -  if( f==0 ){
 -    if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
 -       return SHELL_OPEN_ZIPFILE;
 -    }else{
 -       return SHELL_OPEN_NORMAL;
 -    }
 -  }
 -  n = fread(zBuf, 16, 1, f);
 -  if( n==1 && memcmp(zBuf, "SQLite format 3", 16)==0 ){
 -    fclose(f);
 -    return SHELL_OPEN_NORMAL;
 -  }
 -  fseek(f, -25, SEEK_END);
 -  n = fread(zBuf, 25, 1, f);
 -  if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){
 -    rc = SHELL_OPEN_APPENDVFS;
 -  }else{
 -    fseek(f, -22, SEEK_END);
 -    n = fread(zBuf, 22, 1, f);
 -    if( n==1 && zBuf[0]==0x50 && zBuf[1]==0x4b && zBuf[2]==0x05
 -       && zBuf[3]==0x06 ){
 -      rc = SHELL_OPEN_ZIPFILE;
 -    }else if( n==0 && dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
 -      rc = SHELL_OPEN_ZIPFILE;
 -    }
 -  }
 -  fclose(f);
 -  return rc;
 -}
 -
 -#ifndef SQLITE_OMIT_DESERIALIZE
 -/*
 -** Reconstruct an in-memory database using the output from the "dbtotxt"
 -** program.  Read content from the file in p->aAuxDb[].zDbFilename.
 -** If p->aAuxDb[].zDbFilename is 0, then read from the present input.
 +** Reconstruct an in-memory database using the output from the "dbtotxt"
 +** program.  Read content from the file in p->aAuxDb[].zDbFilename.
- ** If p->aAuxDb[].zDbFilename is 0, then read from standard input.
++** If p->aAuxDb[].zDbFilename is 0, then read from the present input.
  */
  static unsigned char *readHexDb(ShellState *p, int *pnData){
    unsigned char *a = 0;
    unsigned int x[16];
    char zLine[1000];
    if( zDbFilename ){
-     in = fopen(zDbFilename, "r");
-     if( in==0 ){
+     inRedir.inFile = fopen(zDbFilename, "r");
+     if( inRedir.inFile==0 ){
 -      utf8_printf(stderr, "cannot open \"%s\" for reading\n", zDbFilename);
 +      utf8_printf(STD_ERR, "cannot open \"%s\" for reading\n", zDbFilename);
        return 0;
      }
-     nLine = 0;
+     p->pInSource = &inRedir;
    }else{
-     in = p->in;
-     nLine = p->lineno;
-     if( in==0 ) in = STD_IN;
+     /* Will read hex DB lines inline from present input, without redirect. */
+     if( INSOURCE_IS_INTERACTIVE(p->pInSource) ){
+       printf("Reading hex DB from \"%s\", until end-marker input like:\n%s\n",
+              p->pInSource->zSourceSay, zEndMarker);
 -      fflush(stdout);
++      fflush(STD_OUT);
+     }
    }
    *pnData = 0;
-   nLine++;
-   if( fgets(zLine, sizeof(zLine), in)==0 ) goto readHexDb_error;
+   if( str_line_get(zLine,sizeof(zLine), p->pInSource)==0 ) goto readHexDb_error;
    rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz);
    if( rc!=2 ) goto readHexDb_error;
    if( n<0 ) goto readHexDb_error;
    shell_check_oom(a);
    memset(a, 0, n);
    if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){
 -    utf8_printf(stderr, "invalid pagesize\n");
 +    utf8_printf(STD_ERR, "invalid pagesize\n");
      goto readHexDb_error;
    }
-   for(nLine++; fgets(zLine, sizeof(zLine), in)!=0; nLine++){
+   while( str_line_get(zLine,sizeof(zLine), p->pInSource)!=0 ){
      rc = sscanf(zLine, "| page %d offset %d", &j, &k);
      if( rc==2 ){
        iOffset = k;
    }
    return a;
  
- readHexDb_error:
-   if( in!=p->in ){
-     fclose(in);
-   }else{
-     while( fgets(zLine, sizeof(zLine), p->in)!=0 ){
-       nLine++;
-       if(strncmp(zLine, "| end ", 6)==0 ) break;
+  readHexDb_error:
+   nlError = p->pInSource->lineno;
+   if( p->pInSource!=&inRedir ){
+     /* Since taking input inline, consume through its end marker. */
+     while( str_line_get(zLine, sizeof(zLine), p->pInSource)!=0 ){
++
+       if(strncmp(zLine, zEndMarker, 6)==0 ) break;
      }
-     p->lineno = nLine;
++
    }
    sqlite3_free(a);
-   utf8_printf(STD_ERR,"Error on line %d of --hexdb input\n", nLine);
-   return 0;
+   a = 0;
 -  utf8_printf(stderr,"Error on line %d within --hexdb input\n", nlError);
++  utf8_printf(STD_ERR,"Error on line %d within --hexdb input\n", nlError);
+   goto readHexDb_cleanup;
  }
  #endif /* SQLITE_OMIT_DESERIALIZE */
  
@@@ -4805,8 -5283,8 +5069,8 @@@ static void open_db(ShellState *p, int 
        if( zDbFilename==0 || zDbFilename[0]==0 ){
          p->openMode = SHELL_OPEN_NORMAL;
        }else{
-         p->openMode = (u8)deduceDatabaseType(p->pAuxDb->zDbFilename, 
-                              (openFlags & OPEN_DB_ZIPFILE)!=0);
 -        p->openMode = deduceDatabaseType(zDbFilename,
++        p->openMode = deduceDatabaseType(p->pAuxDb->zDbFilename,
+                                          (openFlags & OPEN_DB_ZIPFILE)!=0);
        }
      }
      switch( p->openMode ){
  ** Attempt to close the databaes connection.  Report errors.
  */
  void close_db(sqlite3 *db){
 -  int rc = sqlite3_close(db);
 +  int rc;
 +  if( db==globalDb ){
 +    sqlite3_mutex_enter(pGlobalDbLock);
 +    globalDb = 0;
 +    rc = sqlite3_close(db);
 +    sqlite3_mutex_leave(pGlobalDbLock);
 +  }else{
 +    rc = sqlite3_close(db);
 +  }
    if( rc ){
 -    utf8_printf(stderr, "Error: sqlite3_close() returns %d: %s\n",
 +    utf8_printf(STD_ERR, "Error: sqlite3_close() returns %d: %s\n",
          rc, sqlite3_errmsg(db));
-   } 
+   }
  }
  
  #if HAVE_READLINE || HAVE_EDITLINE
@@@ -5083,14 -5557,11 +5354,11 @@@ static int booleanValue(const char *zAr
      for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){}
    }
    if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff);
-   if( sqlite3_stricmp(zArg, "on")==0 || sqlite3_stricmp(zArg,"yes")==0 ){
-     return 1;
-   }
-   if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){
-     return 0;
+   for( i=0; zBoolNames[i]!=0; ++i ){
+     if( sqlite3_stricmp(zArg, zBoolNames[i])==0 ) return i&1;
    }
 -  utf8_printf(stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n",
 +  utf8_printf(STD_ERR, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n",
-           zArg);
+               zArg);
    return 0;
  }
  
@@@ -5643,6 -6115,26 +5912,26 @@@ static int db_int(sqlite3 *db, const ch
    return res;
  }
  
 -** The return must be freed by caller.
+ /*
+ ** Run an SQL command and return the single text result,
+ ** Parameter binding is done iff bBind is true.
++** The return must be freed by caller using sqlite3_free().
+ */
+ static char *db_text(sqlite3 *db, const char *zSql, int bBind){
+   sqlite3_stmt *pStmt;
+   char *zRes = 0;
+   sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+   if( pStmt ){
+     if( bBind ) bind_prepared_stmt(db, pStmt);
+     if( sqlite3_step(pStmt)==SQLITE_ROW ){
+       zRes = sqlite3_mprintf("%s", sqlite3_column_text(pStmt,0));
+       shell_check_oom(zRes);
+     }
+   }
+   sqlite3_finalize(pStmt);
+   return zRes;
+ }
  /*
  ** Convert a 2-byte or 4-byte big-endian integer into a native integer
  */
@@@ -5852,11 -6344,10 +6141,9 @@@ static int testcase_glob(const char *zG
        if( c!=(*(z++)) ) return 0;
      }
    }
-   while( IsSpace(*z) ){ z++; }
-   return *z==0;
+   return *skipWhite(z)==0;
  }
  
--
  /*
  ** Compare the string as a command-line option with either one or two
  ** initial "-" characters.
@@@ -6167,7 -6679,7 +6454,7 @@@ static void shellPrepare
    if( *pRc==SQLITE_OK ){
      int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0);
      if( rc!=SQLITE_OK ){
-       raw_printf(STD_ERR, "sql error: %s (%d)\n", 
 -      raw_printf(stderr, "sql error: %s (%d)\n",
++      raw_printf(STD_ERR, "sql error: %s (%d)\n",
            sqlite3_errmsg(db), sqlite3_errcode(db)
        );
        *pRc = rc;
@@@ -6561,10 -7069,9 +6848,10 @@@ static int arCheckEntries(ArCommand *pA
  ** The caller is responsible for eventually calling sqlite3_free() on
  ** any non-NULL (*pzWhere) value. Here, "match" means strict equality
  ** when pAr->bGlob is false and GLOB match when pAr->bGlob is true.
 +
  */
  static void arWhereClause(
-   int *pRc, 
+   int *pRc,
    ArCommand *pAr,
    char **pzWhere                  /* OUT: New WHERE clause */
  ){
@@@ -6937,10 -7444,10 +7224,10 @@@ static int arDotCommand
          utf8_printf(pState->out, "-- open database '%s'%s\n", cmd.zFile,
               eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : "");
        }
-       rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, 
+       rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags,
               eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0);
        if( rc!=SQLITE_OK ){
-         utf8_printf(STD_ERR, "cannot open file: %s (%s)\n", 
 -        utf8_printf(stderr, "cannot open file: %s (%s)\n",
++        utf8_printf(STD_ERR, "cannot open file: %s (%s)\n",
              cmd.zFile, sqlite3_errmsg(cmd.db)
          );
          goto end_ar_command;
@@@ -7064,15 -7571,15 +7351,15 @@@ static void *shellMalloc(int *pRc, sqli
  }
  
  /*
 -** If *pRc is not SQLITE_OK when this function is called, it is a no-op.
 -** Otherwise, zFmt is treated as a printf() style string. The result of
 -** formatting it along with any trailing arguments is written into a
 +** If pRc!=0 and *pRc is not SQLITE_OK when this function is called, it is a
 +** no-op. Otherwise, zFmt is treated as a printf() style string. The result
- ** of formatting it along with any trailing arguments is written into a 
++** of formatting it along with any trailing arguments is written into a
  ** buffer obtained from sqlite3_malloc(), and pointer to which is returned.
  ** It is the responsibility of the caller to eventually free this buffer
  ** using a call to sqlite3_free().
- ** 
+ **
 -** If an OOM error occurs, (*pRc) is set to SQLITE_NOMEM and a NULL
 -** pointer returned.
 +** If an OOM error occurs, (*pRc) is set to SQLITE_NOMEM (if pRc!=0)
 +** and a NULL pointer returned.
  */
  static char *shellMPrintf(int *pRc, const char *zFmt, ...){
    char *z = 0;
@@@ -7363,4370 -7871,4447 +7650,5041 @@@ static RecoverTable *recoverOrphanTable
    }
    return pTab;
  }
 +#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
  
 -/*
 -** This function is called to recover data from the database. A script
 -** to construct a new database containing all recovered data is output
 -** on stream pState->out.
 -*/
 -static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){
 -  int rc = SQLITE_OK;
 -  sqlite3_stmt *pLoop = 0;        /* Loop through all root pages */
 -  sqlite3_stmt *pPages = 0;       /* Loop through all pages in a group */
 -  sqlite3_stmt *pCells = 0;       /* Loop through all cells in a page */
 -  const char *zRecoveryDb = "";   /* Name of "recovery" database */
 -  const char *zLostAndFound = "lost_and_found";
 -  int i;
 -  int nOrphan = -1;
 -  RecoverTable *pOrphan = 0;
 -
 -  int bFreelist = 1;              /* 0 if --freelist-corrupt is specified */
 -  int bRowids = 1;                /* 0 if --no-rowids */
 -  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("-freelist-corrupt", z, n)==0 ){
 -      bFreelist = 0;
 -    }else
 -    if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
 -      i++;
 -      zRecoveryDb = azArg[i];
 -    }else
 -    if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
 -      i++;
 -      zLostAndFound = azArg[i];
 -    }else
 -    if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
 -      bRowids = 0;
 -    }
 -    else{
 -      utf8_printf(stderr, "unexpected option: %s\n", azArg[i]);
 -      showHelp(pState->out, azArg[0]);
 -      return 1;
 +static int writeDb( char *azArg[], int nArg, ShellState *p, char **pzErr ){
 +  int rc = 0;
 +  const char *zDestFile = 0;
 +  const char *zDb = 0;
 +  sqlite3 *pDest;
 +  sqlite3_backup *pBackup;
 +  int j;
 +  int bAsync = 0;
 +  const char *zVfs = 0;
 +  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
 +  for(j=1; j<nArg; j++){
 +    const char *z = azArg[j];
 +    if( z[0]=='-' ){
 +      if( z[1]=='-' ) z++;
 +      if( strcmp(z, "-append")==0 ){
 +        zVfs = "apndvfs";
 +      }else
 +        if( strcmp(z, "-async")==0 ){
 +          bAsync = 1;
 +        }else
 +          {
 +            utf8_printf(STD_ERR, "unknown option: %s\n", azArg[j]);
 +            return SHELL_INVALID_ARGS;
 +          }
 +    }else if( zDestFile==0 ){
 +      zDestFile = azArg[j];
 +    }else if( zDb==0 ){
 +      zDb = zDestFile;
 +      zDestFile = azArg[j];
 +    }else{
 +      return SHELL_INVALID_ARGS;
      }
    }
 -
 -  shellExecPrintf(pState->db, &rc,
 -    /* Attach an in-memory database named 'recovery'. Create an indexed
 -    ** cache of the sqlite_dbptr virtual table. */
 -    "PRAGMA writable_schema = on;"
 -    "ATTACH %Q AS recovery;"
 -    "DROP TABLE IF EXISTS recovery.dbptr;"
 -    "DROP TABLE IF EXISTS recovery.freelist;"
 -    "DROP TABLE IF EXISTS recovery.map;"
 -    "DROP TABLE IF EXISTS recovery.schema;"
 -    "CREATE TABLE recovery.freelist(pgno INTEGER PRIMARY KEY);", zRecoveryDb
 -  );
 -
 -  if( bFreelist ){
 -    shellExec(pState->db, &rc,
 -      "WITH trunk(pgno) AS ("
 -      "  SELECT shell_int32("
 -      "      (SELECT data FROM sqlite_dbpage WHERE pgno=1), 8) AS x "
 -      "      WHERE x>0"
 -      "    UNION"
 -      "  SELECT shell_int32("
 -      "      (SELECT data FROM sqlite_dbpage WHERE pgno=trunk.pgno), 0) AS x "
 -      "      FROM trunk WHERE x>0"
 -      "),"
 -      "freelist(data, n, freepgno) AS ("
 -      "  SELECT data, min(16384, shell_int32(data, 1)-1), t.pgno "
 -      "      FROM trunk t, sqlite_dbpage s WHERE s.pgno=t.pgno"
 -      "    UNION ALL"
 -      "  SELECT data, n-1, shell_int32(data, 2+n) "
 -      "      FROM freelist WHERE n>=0"
 -      ")"
 -      "REPLACE INTO recovery.freelist SELECT freepgno FROM freelist;"
 -    );
 +  if( zDestFile==0 ){
 +    return SHELL_INVALID_ARGS;
 +  }
 +  if( zDb==0 ) zDb = "main";
-   rc = sqlite3_open_v2(zDestFile, &pDest, 
++  rc = sqlite3_open_v2(zDestFile, &pDest,
 +                       SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs);
 +  if( rc!=SQLITE_OK ){
 +    utf8_printf(STD_ERR, "Error: cannot open \"%s\"\n", zDestFile);
 +    close_db(pDest);
 +    return 1;
 +  }
 +  if( bAsync ){
 +    sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
 +                 0, 0, 0);
 +  }
 +  open_db(p, 0);
 +  pBackup = sqlite3_backup_init(pDest, "main", p->db, zDb);
 +  if( pBackup==0 ){
 +    utf8_printf(STD_ERR, "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(STD_ERR, "Error: %s\n", sqlite3_errmsg(pDest));
 +    rc = 1;
    }
 +  close_db(pDest);
 +  return rc;
 +}
  
 -  /* If this is an auto-vacuum database, add all pointer-map pages to
 -  ** the freelist table. Do this regardless of whether or not
 -  ** --freelist-corrupt was specified.  */
 -  shellExec(pState->db, &rc,
 -    "WITH ptrmap(pgno) AS ("
 -    "  SELECT 2 WHERE shell_int32("
 -    "    (SELECT data FROM sqlite_dbpage WHERE pgno=1), 13"
 -    "  )"
 -    "    UNION ALL "
 -    "  SELECT pgno+1+(SELECT page_size FROM pragma_page_size)/5 AS pp "
 -    "  FROM ptrmap WHERE pp<=(SELECT page_count FROM pragma_page_count)"
 -    ")"
 -    "REPLACE INTO recovery.freelist SELECT pgno FROM ptrmap"
 -  );
 +/*
 + * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it.
 + * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE,
 + *   close db and set it to 0, and return the columns spec, to later
 + *   be sqlite3_free()'ed by the caller.
 + * The return is 0 when either:
 + *   (a) The db was not initialized and zCol==0 (There are no columns.)
 + *   (b) zCol!=0  (Column was added, db initialized as needed.)
 + * The 3rd argument, pRenamed, references an out parameter. If the
 + * pointer is non-zero, its referent will be set to a summary of renames
 + * done if renaming was necessary, or set to 0 if none was done. The out
 + * string (if any) must be sqlite3_free()'ed by the caller.
 + */
 +#ifdef SHELL_DEBUG
 +#define rc_err_oom_die(rc) \
 +  if( rc==SQLITE_NOMEM ) shell_check_oom(0); \
 +  else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \
-     fprintf(stderr,"E:%d\n",rc), assert(0)
++    fprintf(STD_ERR,"E:%d\n",rc), assert(0)
 +#else
 +static void rc_err_oom_die(int rc){
 +  if( rc==SQLITE_NOMEM ) shell_check_oom(0);
 +  assert(rc==SQLITE_OK||rc==SQLITE_DONE);
 +}
 +#endif
  
 -  shellExec(pState->db, &rc,
 -    "CREATE TABLE recovery.dbptr("
 -    "      pgno, child, PRIMARY KEY(child, pgno)"
 -    ") WITHOUT ROWID;"
 -    "INSERT OR IGNORE INTO recovery.dbptr(pgno, child) "
 -    "    SELECT * FROM sqlite_dbptr"
 -    "      WHERE pgno NOT IN freelist AND child NOT IN freelist;"
 -
 -    /* Delete any pointer to page 1. This ensures that page 1 is considered
 -    ** a root page, regardless of how corrupt the db is. */
 -    "DELETE FROM recovery.dbptr WHERE child = 1;"
 -
 -    /* Delete all pointers to any pages that have more than one pointer
 -    ** to them. Such pages will be treated as root pages when recovering
 -    ** data.  */
 -    "DELETE FROM recovery.dbptr WHERE child IN ("
 -    "  SELECT child FROM recovery.dbptr GROUP BY child HAVING count(*)>1"
 -    ");"
 -
 -    /* Create the "map" table that will (eventually) contain instructions
 -    ** for dealing with each page in the db that contains one or more
 -    ** records. */
 -    "CREATE TABLE recovery.map("
 -      "pgno INTEGER PRIMARY KEY, maxlen INT, intkey, root INT"
 -    ");"
 -
 -    /* Populate table [map]. If there are circular loops of pages in the
 -    ** database, the following adds all pages in such a loop to the map
 -    ** as individual root pages. This could be handled better.  */
 -    "WITH pages(i, maxlen) AS ("
 -    "  SELECT page_count, ("
 -    "    SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=page_count"
 -    "  ) FROM pragma_page_count WHERE page_count>0"
 -    "    UNION ALL"
 -    "  SELECT i-1, ("
 -    "    SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=i-1"
 -    "  ) FROM pages WHERE i>=2"
 -    ")"
 -    "INSERT INTO recovery.map(pgno, maxlen, intkey, root) "
 -    "  SELECT i, maxlen, NULL, ("
 -    "    WITH p(orig, pgno, parent) AS ("
 -    "      SELECT 0, i, (SELECT pgno FROM recovery.dbptr WHERE child=i)"
 -    "        UNION "
 -    "      SELECT i, p.parent, "
 -    "        (SELECT pgno FROM recovery.dbptr WHERE child=p.parent) FROM p"
 -    "    )"
 -    "    SELECT pgno FROM p WHERE (parent IS NULL OR pgno = orig)"
 -    ") "
 -    "FROM pages WHERE maxlen IS NOT NULL AND i NOT IN freelist;"
 -    "UPDATE recovery.map AS o SET intkey = ("
 -    "  SELECT substr(data, 1, 1)==X'0D' FROM sqlite_dbpage WHERE pgno=o.pgno"
 -    ");"
 -
 -    /* Extract data from page 1 and any linked pages into table
 -    ** recovery.schema. With the same schema as an sqlite_schema table.  */
 -    "CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);"
 -    "INSERT INTO recovery.schema SELECT "
 -    "  max(CASE WHEN field=0 THEN value ELSE NULL END),"
 -    "  max(CASE WHEN field=1 THEN value ELSE NULL END),"
 -    "  max(CASE WHEN field=2 THEN value ELSE NULL END),"
 -    "  max(CASE WHEN field=3 THEN value ELSE NULL END),"
 -    "  max(CASE WHEN field=4 THEN value ELSE NULL END)"
 -    "FROM sqlite_dbdata WHERE pgno IN ("
 -    "  SELECT pgno FROM recovery.map WHERE root=1"
 -    ")"
 -    "GROUP BY pgno, cell;"
 -    "CREATE INDEX recovery.schema_rootpage ON schema(rootpage);"
 -  );
 -
 -  /* Open a transaction, then print out all non-virtual, non-"sqlite_%"
 -  ** CREATE TABLE statements that extracted from the existing schema.  */
 -  if( rc==SQLITE_OK ){
 -    sqlite3_stmt *pStmt = 0;
 -    /* ".recover" might output content in an order which causes immediate
 -    ** foreign key constraints to be violated. So disable foreign-key
 -    ** constraint enforcement to prevent problems when running the output
 -    ** script. */
 -    raw_printf(pState->out, "PRAGMA foreign_keys=OFF;\n");
 -    raw_printf(pState->out, "BEGIN;\n");
 -    raw_printf(pState->out, "PRAGMA writable_schema = on;\n");
 -    shellPrepare(pState->db, &rc,
 -        "SELECT sql FROM recovery.schema "
 -        "WHERE type='table' AND sql LIKE 'create table%'", &pStmt
 -    );
 -    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
 -      const char *zCreateTable = (const char*)sqlite3_column_text(pStmt, 0);
 -      raw_printf(pState->out, "CREATE TABLE IF NOT EXISTS %s;\n",
 -          &zCreateTable[12]
 -      );
 -    }
 -    shellFinalize(&rc, pStmt);
 -  }
 -
 -  /* Figure out if an orphan table will be required. And if so, how many
 -  ** user columns it should contain */
 -  shellPrepare(pState->db, &rc,
 -      "SELECT coalesce(max(maxlen), -2) FROM recovery.map WHERE root>1"
 -      , &pLoop
 -  );
 -  if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
 -    nOrphan = sqlite3_column_int(pLoop, 0);
 -  }
 -  shellFinalize(&rc, pLoop);
 -  pLoop = 0;
 -
 -  shellPrepare(pState->db, &rc,
 -      "SELECT pgno FROM recovery.map WHERE root=?", &pPages
 -  );
 -
 -  shellPrepare(pState->db, &rc,
 -      "SELECT max(field), group_concat(shell_escape_crnl(quote"
 -      "(case when (? AND field<0) then NULL else value end)"
 -      "), ', ')"
 -      ", min(field) "
 -      "FROM sqlite_dbdata WHERE pgno = ? AND field != ?"
 -      "GROUP BY cell", &pCells
 -  );
 -
 -  /* Loop through each root page. */
 -  shellPrepare(pState->db, &rc,
 -      "SELECT root, intkey, max(maxlen) FROM recovery.map"
 -      " WHERE root>1 GROUP BY root, intkey ORDER BY root=("
 -      "  SELECT rootpage FROM recovery.schema WHERE name='sqlite_sequence'"
 -      ")", &pLoop
 -  );
 -  while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
 -    int iRoot = sqlite3_column_int(pLoop, 0);
 -    int bIntkey = sqlite3_column_int(pLoop, 1);
 -    int nCol = sqlite3_column_int(pLoop, 2);
 -    int bNoop = 0;
 -    RecoverTable *pTab;
 -
 -    assert( bIntkey==0 || bIntkey==1 );
 -    pTab = recoverFindTable(pState, &rc, iRoot, bIntkey, nCol, &bNoop);
 -    if( bNoop || rc ) continue;
 -    if( pTab==0 ){
 -      if( pOrphan==0 ){
 -        pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan);
 -      }
 -      pTab = pOrphan;
 -      if( pTab==0 ) break;
 -    }
 -
 -    if( 0==sqlite3_stricmp(pTab->zQuoted, "\"sqlite_sequence\"") ){
 -      raw_printf(pState->out, "DELETE FROM sqlite_sequence;\n");
 -    }
 -    sqlite3_bind_int(pPages, 1, iRoot);
 -    if( bRowids==0 && pTab->iPk<0 ){
 -      sqlite3_bind_int(pCells, 1, 1);
 -    }else{
 -      sqlite3_bind_int(pCells, 1, 0);
 -    }
 -    sqlite3_bind_int(pCells, 3, pTab->iPk);
 -
 -    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPages) ){
 -      int iPgno = sqlite3_column_int(pPages, 0);
 -      sqlite3_bind_int(pCells, 2, iPgno);
 -      while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pCells) ){
 -        int nField = sqlite3_column_int(pCells, 0);
 -        int iMin = sqlite3_column_int(pCells, 2);
 -        const char *zVal = (const char*)sqlite3_column_text(pCells, 1);
 -
 -        RecoverTable *pTab2 = pTab;
 -        if( pTab!=pOrphan && (iMin<0)!=bIntkey ){
 -          if( pOrphan==0 ){
 -            pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan);
 -          }
 -          pTab2 = pOrphan;
 -          if( pTab2==0 ) break;
 -        }
 -
 -        nField = nField+1;
 -        if( pTab2==pOrphan ){
 -          raw_printf(pState->out,
 -              "INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n",
 -              pTab2->zQuoted, iRoot, iPgno, nField,
 -              iMin<0 ? "" : "NULL, ", zVal, pTab2->azlCol[nField]
 -          );
 -        }else{
 -          raw_printf(pState->out, "INSERT INTO %s(%s) VALUES( %s );\n",
 -              pTab2->zQuoted, pTab2->azlCol[nField], zVal
 -          );
 -        }
 -      }
 -      shellReset(&rc, pCells);
 -    }
 -    shellReset(&rc, pPages);
 -    if( pTab!=pOrphan ) recoverFreeTable(pTab);
 -  }
 -  shellFinalize(&rc, pLoop);
 -  shellFinalize(&rc, pPages);
 -  shellFinalize(&rc, pCells);
 -  recoverFreeTable(pOrphan);
 -
 -  /* The rest of the schema */
 -  if( rc==SQLITE_OK ){
 -    sqlite3_stmt *pStmt = 0;
 -    shellPrepare(pState->db, &rc,
 -        "SELECT sql, name FROM recovery.schema "
 -        "WHERE sql NOT LIKE 'create table%'", &pStmt
 -    );
 -    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
 -      const char *zSql = (const char*)sqlite3_column_text(pStmt, 0);
 -      if( sqlite3_strnicmp(zSql, "create virt", 11)==0 ){
 -        const char *zName = (const char*)sqlite3_column_text(pStmt, 1);
 -        char *zPrint = shellMPrintf(&rc,
 -          "INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)",
 -          zName, zName, zSql
 -        );
 -        raw_printf(pState->out, "%s;\n", zPrint);
 -        sqlite3_free(zPrint);
 -      }else{
 -        raw_printf(pState->out, "%s;\n", zSql);
 -      }
 -    }
 -    shellFinalize(&rc, pStmt);
 -  }
 -
 -  if( rc==SQLITE_OK ){
 -    raw_printf(pState->out, "PRAGMA writable_schema = off;\n");
 -    raw_printf(pState->out, "COMMIT;\n");
 -  }
 -  sqlite3_exec(pState->db, "DETACH recovery", 0, 0, 0);
 -  return rc;
 -}
 -#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
 +#ifdef SHELL_COLFIX_DB /* If this is set, the DB can be in a file. */
 +static char zCOL_DB[] = SHELL_STRINGIFY(SHELL_COLFIX_DB);
 +#else  /* Otherwise, memory is faster/better for the transient DB. */
 +static const char *zCOL_DB = ":memory:";
 +#endif
  
 -/*
 -** The .shxopts command, for setting or listing shell extension options.
 +/* Define character (as C string) to separate generated column ordinal
 + * from protected part of incoming column names. This defaults to "_"
 + * so that incoming column identifiers that did not need not be quoted
 + * remain usable without being quoted. It must be one character.
   */
 -#if SHELL_EXTENSIONS
 -static int shxoptsCommand(char *azArg[], int nArg, ShellState *p, char **pzE){
 -  static struct { const char *name; u8 mask; } shopts[] = {
 -#if SHELL_DYNAMIC_COMMANDS
 -    {"dyn_cmds", 1<<SHEXT_DYNCMDS_BIT},
 +#ifndef SHELL_AUTOCOLUMN_SEP
 +# define AUTOCOLUMN_SEP "_"
 +#else
 +# define AUTOCOLUMN_SEP SHELL_STRINGIFY(SHELL_AUTOCOLUMN_SEP)
  #endif
 -#if SHELL_EXTENDED_PARSING
 -    {"parsing", 1<<SHEXT_PARSING_BIT},
 +
 +static char *zAutoColumn(const char *zColNew, sqlite3 **pDb, char **pzRenamed){
 +  /* Queries and D{D,M}L used here */
 +  static const char * const zTabMake = "\
 +CREATE TABLE ColNames(\
 + cpos INTEGER PRIMARY KEY,\
 + name TEXT, nlen INT, chop INT, reps INT, suff TEXT);\
 +CREATE VIEW RepeatedNames AS \
 +SELECT DISTINCT t.name FROM ColNames t \
 +WHERE t.name COLLATE NOCASE IN (\
 + SELECT o.name FROM ColNames o WHERE o.cpos<>t.cpos\
 +);\
 +";
 +  static const char * const zTabFill = "\
 +INSERT INTO ColNames(name,nlen,chop,reps,suff)\
 + VALUES(iif(length(?1)>0,?1,'?'),max(length(?1),1),0,0,'')\
 +";
 +  static const char * const zHasDupes = "\
 +SELECT count(DISTINCT (substring(name,1,nlen-chop)||suff) COLLATE NOCASE)\
 + <count(name) FROM ColNames\
 +";
 +#ifdef SHELL_COLUMN_RENAME_CLEAN
 +  static const char * const zDedoctor = "\
 +UPDATE ColNames SET chop=iif(\
 +  (substring(name,nlen,1) BETWEEN '0' AND '9')\
 +  AND (rtrim(name,'0123456790') glob '*"AUTOCOLUMN_SEP"'),\
 + nlen-length(rtrim(name, '"AUTOCOLUMN_SEP"0123456789')),\
 + 0\
 +)\
 +";
  #endif
 -#if SHELL_VARIABLE_EXPANSION
 -    {"dot_vars", 1<<SHEXT_VAREXP_BIT},
 +  static const char * const zSetReps = "\
 +UPDATE ColNames AS t SET reps=\
 +(SELECT count(*) FROM ColNames d \
 + WHERE substring(t.name,1,t.nlen-t.chop)=substring(d.name,1,d.nlen-d.chop)\
 + COLLATE NOCASE\
 +)\
 +";
 +#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
 +  static const char * const zColDigits = "\
 +SELECT CAST(ceil(log(count(*)+0.5)) AS INT) FROM ColNames \
 +";
  #endif
 -    {"all_opts", SHELL_ALL_EXTENSIONS}
 -  };
 -  const char *zMoan = 0, *zAbout = 0;
 -  int ia, io;
 -  if( nArg>1 ){
 -    for( ia=1; ia<nArg; ++ia ){
 -      char cs = azArg[ia][0];
 -      if( cs!='+' && cs!='-' ){
 -        zMoan = "arguments must have a sign prefix.";
 -        zAbout = azArg[0];
 -        goto moan_error;
 -      }
 -      for( io=0; io<ArraySize(shopts); ++io ){
 -        if( strcmp(azArg[ia]+1, shopts[io].name)==0 ){
 -          if( cs=='+' ) p->bExtendedDotCmds |= shopts[io].mask;
 -          else p->bExtendedDotCmds &= ~shopts[io].mask;
 -          break;
 -        }
 -      }
 -      if( io==ArraySize(shopts) ){
 -        zAbout = azArg[ia];
 -        zMoan = "is not a recognized option name";
 -        goto moan_error;
 -      }
 +  static const char * const zRenameRank =
 +#ifdef SHELL_COLUMN_RENAME_CLEAN
 +    "UPDATE ColNames AS t SET suff="
 +    "iif(reps>1, printf('%c%0*d', '"AUTOCOLUMN_SEP"', $1, cpos), '')"
 +#else /* ...RENAME_MINIMAL_ONE_PASS */
 +"WITH Lzn(nlz) AS (" /* Find minimum extraneous leading 0's for uniqueness */
 +"  SELECT 0 AS nlz"
 +"  UNION"
 +"  SELECT nlz+1 AS nlz FROM Lzn"
 +"  WHERE EXISTS("
 +"   SELECT 1"
 +"   FROM ColNames t, ColNames o"
 +"   WHERE"
 +"    iif(t.name IN (SELECT * FROM RepeatedNames),"
 +"     printf('%s"AUTOCOLUMN_SEP"%s',"
 +"      t.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,t.cpos),2)),"
 +"     t.name"
 +"    )"
 +"    ="
 +"    iif(o.name IN (SELECT * FROM RepeatedNames),"
 +"     printf('%s"AUTOCOLUMN_SEP"%s',"
 +"      o.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,o.cpos),2)),"
 +"     o.name"
 +"    )"
 +"    COLLATE NOCASE"
 +"    AND o.cpos<>t.cpos"
 +"   GROUP BY t.cpos"
 +"  )"
 +") UPDATE Colnames AS t SET"
 +" chop = 0," /* No chopping, never touch incoming names. */
 +" suff = iif(name IN (SELECT * FROM RepeatedNames),"
 +"  printf('"AUTOCOLUMN_SEP"%s', substring("
 +"   printf('%.*c%0.*d',(SELECT max(nlz) FROM Lzn)+1,'0',1,t.cpos),2)),"
 +"  ''"
 +" )"
 +#endif
 +    ;
 +  static const char * const zCollectVar = "\
 +SELECT\
 + '('||x'0a'\
 + || group_concat(\
 +  cname||' TEXT',\
 +  ','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\
 + ||')' AS ColsSpec \
 +FROM (\
 + SELECT cpos, printf('\"%w\"',printf('%.*s%s', nlen-chop,name,suff)) AS cname \
 + FROM ColNames ORDER BY cpos\
 +)";
 +  static const char * const zRenamesDone =
 +    "SELECT group_concat("
 +    " printf('\"%w\" to \"%w\"',name,printf('%.*s%s', nlen-chop, name, suff)),"
 +    " ','||x'0a')"
 +    "FROM ColNames WHERE suff<>'' OR chop!=0"
 +    ;
 +  int rc;
 +  sqlite3_stmt *pStmt = 0;
 +  assert(pDb!=0);
 +  if( zColNew ){
 +    /* Add initial or additional column. Init db if necessary. */
 +    if( *pDb==0 ){
 +      if( SQLITE_OK!=sqlite3_open(zCOL_DB, pDb) ) return 0;
 +#ifdef SHELL_COLFIX_DB
 +      if(*zCOL_DB!=':')
 +        sqlite3_exec(*pDb,"drop table if exists ColNames;"
 +                     "drop view if exists RepeatedNames;",0,0,0);
 +#endif
 +      rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0);
 +      rc_err_oom_die(rc);
      }
 +    assert(*pDb!=0);
 +    rc = sqlite3_prepare_v2(*pDb, zTabFill, -1, &pStmt, 0);
 +    rc_err_oom_die(rc);
 +    rc = sqlite3_bind_text(pStmt, 1, zColNew, -1, 0);
 +    rc_err_oom_die(rc);
 +    rc = sqlite3_step(pStmt);
 +    rc_err_oom_die(rc);
 +    sqlite3_finalize(pStmt);
 +    return 0;
 +  }else if( *pDb==0 ){
 +    return 0;
    }else{
 -    raw_printf(p->out,
 -               "     name    value  \"-shxopts set\"\n"
 -               "   --------  -----  ---------------\n");
 -    for( io=0; io<ArraySize(shopts); ++io ){
 -      unsigned m = shopts[io].mask;
 -      unsigned v = ((p->bExtendedDotCmds & m) == m)? 1 : 0;
 -      raw_printf(p->out,
 -                 "  %9s   %2d    \"-shxopts 0x%02X\"\n",
 -                 shopts[io].name,  v, m);
 -    }
 -  }
 -  return 0;
 - moan_error:
 -  raw_printf(stderr, "Error: %s %s\n", zAbout, zMoan);
 -  return 1;
 -}
 +    /* Formulate the columns spec, close the DB, zero *pDb. */
 +    char *zColsSpec = 0;
 +    int hasDupes = db_int(*pDb, zHasDupes);
 +#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
 +    int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0;
 +#else
 +# define nDigits 2
  #endif
 -
 -static int execute_variables(char *azArg[], int nArg, ShellState *p){
 -  int ia, rc, nErrors = 0;
 -  sqlite3_stmt *pStmt = 0;
 -  open_db(p, 0);
 -  if( p->db==0 ){
 -    utf8_printf(stderr, ".x can only be done with a database open.\n");
 -    return 1;
 -  }
 -  if( sqlite3_table_column_metadata(p->db, PARAM_TABLE_SCHEMA,
 -                                    PARAM_TABLE_NAME,
 -                                    "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){
 -    utf8_printf(stderr, "No "PARAM_TABLE_SNAME" table exists.\n");
 -    return 1;
 -  }
 -  rc = sqlite3_prepare_v2
 -    (p->db, "SELECT value FROM "PARAM_TABLE_SNAME
 -     " WHERE key=$1 AND uses=1",
 -     -1, &pStmt, 0);
 -  if( rc!=SQLITE_OK ){
 -    utf8_printf(stderr, PARAM_TABLE_SNAME" is wrongly created.\n");
 -    return 1;
 -  }
 -  for( ia=1; ia < nArg; ++ia ){
 -    if( isalpha(azArg[ia][0]) ){
 -      rc = sqlite3_reset(pStmt);
 -      rc = sqlite3_bind_text(pStmt, 1, azArg[ia], -1, 0);
 +    if( hasDupes ){
 +#ifdef SHELL_COLUMN_RENAME_CLEAN
 +      rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0);
 +      rc_err_oom_die(rc);
 +#endif
 +      rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0);
 +      rc_err_oom_die(rc);
 +      rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0);
 +      rc_err_oom_die(rc);
 +      sqlite3_bind_int(pStmt, 1, nDigits);
        rc = sqlite3_step(pStmt);
 -      if( rc==SQLITE_ROW ){
 -        const unsigned char *zValue = sqlite3_column_text(pStmt, 0);
 -        int nb = sqlite3_column_bytes(pStmt, 0);
 -        while( nb>0 && IsSpace(zValue[nb-1]) ) --nb;
 -        if( nb>0 ){
 -          /* The trailing newline (or some other placeholder) is important
 -           * because one (or some other character) will likely be put in
 -           * its place during process_input() line/group handling, along
 -           * with a terminating NUL character. Without it, the NULL could
 -           * land past the end of the allocation made just below.
 -           */
 -          int nle = zValue[nb-1]=='\n';
 -          char *zSubmit = sqlite3_mprintf( "%.*s%s", nb, zValue, "\n"+nle );
 -          InSource inRedir
 -            = INSOURCE_STR_REDIR(zSubmit, azArg[ia], p->pInSource);
 -          shell_check_oom(zSubmit);
 -          p->pInSource = &inRedir;
 -          rc = process_input(p);
 -          sqlite3_free(zSubmit);
 -          p->pInSource = inRedir.pFrom;
 -        }else{
 -          continue; /* All white, ignore. */
 -        }
 -      }else{
 -        utf8_printf(stderr,
 -                    "Skipping parameter '%s' (not set and executable.)\n",
 -                    azArg[ia]);
 -        ++nErrors;
 +      sqlite3_finalize(pStmt);
 +      assert(rc==SQLITE_DONE);
 +    }
 +    assert(db_int(*pDb, zHasDupes)==0); /* Consider: remove this */
 +    rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0);
 +    rc_err_oom_die(rc);
 +    rc = sqlite3_step(pStmt);
 +    if( rc==SQLITE_ROW ){
 +      zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 +    }else{
 +      zColsSpec = 0;
 +    }
 +    if( pzRenamed!=0 ){
 +      if( !hasDupes ) *pzRenamed = 0;
 +      else{
 +        sqlite3_finalize(pStmt);
 +        if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0)
 +            && SQLITE_ROW==sqlite3_step(pStmt) ){
 +          *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 +        }else
 +          *pzRenamed = 0;
        }
 -    }else{
 -      utf8_printf(stderr,
 -                  "Skipping badly named %s. Run \".help x\"\n", azArg[ia]);
 -      ++nErrors;
      }
 +    sqlite3_finalize(pStmt);
 +    sqlite3_close(*pDb);
 +    *pDb = 0;
 +    return zColsSpec;
    }
 -  sqlite3_finalize(pStmt);
 -  return (rc==2)? 2 : nErrors>0;
  }
  
 -struct param_row { char * value; int uses; int hits; };
 -
 -static int param_find_callback(void *pData, int nc, char **pV, char **pC){
 -  assert(nc>=1);
 -  assert(strcmp(pC[0],"value")==0);
 -  struct param_row *pParam = (struct param_row *)pData;
 -  assert(pParam->value==0); /* key values are supposedly unique. */
 -  if( pParam->value!=0 ) sqlite3_free( pParam->value );
 -  pParam->value = sqlite3_mprintf("%s", pV[0]); /* source owned by statement */
 -  if( nc>1 ) pParam->uses = (int)integerValue(pV[1]);
 -  ++pParam->hits;
 +#ifndef OBJECTIFY_COMMANDS
 +# define OBJECTIFY_COMMANDS 1
 +#endif
 +
 +/* Meta-command implementation functions are defined in this section.
 +COMMENT  Define meta-commands and provide for their dispatch and .help text.
 +COMMENT  These should be kept in command name order for coding convenience
 +COMMENT  except where meta-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 dispatch and help table entries, to be later
 +COMMENT  emitted by certain macros. (See EMIT_* further on.)
 +** All dispatchable meta-command execute functions have this signature:
 +static int someCommand(char *azArg[], int nArg, ShellState *p, char **pzErr);
 +*/
 +DISPATCH_CONFIG[
 +  RETURN_TYPE=int
 +  STORAGE_CLASS=static
 +  ARGS_SIGNATURE=char *$arg4\[\], int $arg5, ShellState *$arg6, char **$arg7
 +  DISPATCH_ENTRY={ "$cmd", ${cmd}Command, $arg1, $arg2, $arg3 },
 +  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(SQLITE_GIMME_SEEARGS));
++CONDITION_COMMAND(seeargs !defined(SHELL_OMIT_SEEARGS));
 +/*****************
 + * The .seeargs command
 + */
 +COLLECT_HELP_TEXT[
-   ".seeargs                 Echo arguments separated by |",
-   "    A near-dummy command for use as a template (to vanish soon)",
++  ".seeargs                 Echo arguments suffixed with |",
 +];
 +DISPATCHABLE_COMMAND( seeargs ? 0 0 azArg nArg p ){
 +  int rc = 0;
 +  for (rc=1; rc<nArg; ++rc)
-     raw_printf(p->out, "%s%s", azArg[rc], (rc==nArg-1)? "\n" : "|");
-   return rc;
++    raw_printf(p->out, "%s%s", azArg[rc], (rc==nArg-1)? "|\n" : "|");
+   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);
 +CONDITION_COMMAND(archive !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB));
 +/*****************
 + * 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( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
 +  return arDotCommand(p, 0, azArg, nArg);
 +}
  
 -/* Create a home-relative pathname from ~ prefixed path.
 - * Return it, or 0 for any error.
 - * Caller must sqlite3_free() it.
 +/*****************
 + * The .auth command
   */
 -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]!='/'
 +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 ){
 +  int rc = 0;
 +  open_db(p, 0);
 +  if( booleanValue(azArg[1]) ){
 +    sqlite3_set_authorizer(p->db, shellAuth, p);
 +  }else if( p->bSafeModeFuture ){
 +    sqlite3_set_authorizer(p->db, safeModeAuth, p);
 +  }else{
 +    sqlite3_set_authorizer(p->db, 0, 0);
 +  }
 +  return rc;
 +}
 +
 +/*****************
 + * The .backup and .save commands (aliases for each other)
 + * These defer to writeDb in the dispatch table, so are not here.
 + */
 +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()",
 +];
 +COLLECT_DISPATCH( * )[
 +  { "backup", writeDb, 4, 2, 5 },
 +  { "save", writeDb, 3, 2, 5 },
 +];
 +
 +/*****************
 + * 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 0;
 +}
 +
 +/*****************
 + * 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(p->out, 1);
 +  }else{
 +    setTextMode(p->out, 1);
 +  }
 +  return 0;
 +}
 +
 +DISPATCHABLE_COMMAND( cd ? 2 2 ){
 +  int rc=0;
 +  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
  #if defined(_WIN32) || defined(WIN32)
 -                            && zPath[1]!='\\'
 +  wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
 +  rc = !SetCurrentDirectoryW(z);
 +  sqlite3_free(z);
 +#else
 +  rc = chdir(azArg[1]);
  #endif
 -                            ) ){
 -    zErr = "Malformed pathname";
 -  }else{
 -    return sqlite3_mprintf("%s%s", zHome, zPath+1);
 +  if( rc ){
 +    utf8_printf(STD_ERR, "Cannot change to directory \"%s\"\n", azArg[1]);
 +    rc = 1;
    }
 -  utf8_printf(stderr, "Error: %s\n", zErr);
 +  return rc;
 +}
 +
 +/* The undocumented ".breakpoint" command causes a call
 +** to the no-op routine named test_breakpoint().
 +*/
 +DISPATCHABLE_COMMAND( breakpoint 3 1 1 ){
 +  test_breakpoint();
    return 0;
  }
  
 -/* Transfer selected parameters between two parameter tables, for save/load.
 - * Argument bSaveNotLoad determines transfer direction and other actions.
 - * If it is true, the store DB will be created if not existent, and its
 - * table for keeping parameters will be created. Or failure is returned.
 - * If it is false, the store DB will be opened for read and its presumed
 - * table for keeping parameters will be read. Or failure is returned.
 - *
 - * Arguments azNames and nNames reference the ?NAMES? save/load arguments.
 - * If it is an empty list, all parameters will be saved or loaded.
 - * Otherwise, only the named parameters are transferred, if they exist.
 - * It is not an error to specify a name that cannot be transferred
 - * because it does not exist in the source table.
 - *
 - * Returns are SQLITE_OK for success, or other codes for failure.
 +/*****************
 + * The .changes, .check, .clone and .connection commands
   */
 -static int param_xfr_table(sqlite3 *db, const char *zStoreDbName,
 -                           int bSaveNotLoad, const char *azNames[], int nNames){
 -  int rc = 0;
 -  char *zSql = 0; /* to be sqlite3_free()'ed */
 -  sqlite3_str *sbCopy = 0;
 -  const char *zHere = PARAM_TABLE_SNAME;
 -  const char *zThere = PARAM_STORE_SNAME;
 -  const char *zTo = (bSaveNotLoad)? zThere : zHere;
 -  const char *zFrom = (bSaveNotLoad)? zHere : zThere;
 -  sqlite3 *dbStore = 0;
 -  int openFlags = (bSaveNotLoad)
 -    ? SQLITE_OPEN_URI|SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE
 -    : SQLITE_OPEN_READONLY;
 +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 0;
 +}
 +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;
 +  int rc=0;
 +  output_reset(p);
 +  if( nArg!=2 ){
 +    return SHELL_INVALID_ARGS;
 +  }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){
 +    *pzErr = shellMPrintf(&rc, "Error: cannot read 'testcase-out.txt'");
 +    rc = 2;
 +  }else if( testcase_glob(azArg[1],zRes)==0 ){
 +    *pzErr =
 +      shellMPrintf(&rc,
 +                   "testcase-%s FAILED\n Expected: [%s]\n      Got: [%s]\n",
 +                   p->zTestcase, azArg[1], zRes);
 +    rc = 1;
 +  }else{
 +    utf8_printf(STD_OUT, "testcase-%s ok\n", p->zTestcase);
 +    p->nCheck++;
 +  }
 +  sqlite3_free(zRes);
 +  return rc;
 +}
 +DISPATCHABLE_COMMAND( clone ? 2 2 ){
 +  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
 +  tryToClone(p, azArg[1]);
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( connection ? 1 4 ){
 +  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(STD_OUT, "ACTIVE %d: %s\n", i, zFile);
 +      }else if( p->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( 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 && 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(STD_ERR, "cannot close the active database connection\n");
 +      return 1;
 +    }else if( p->aAuxDb[i].db ){
 +      session_close_all(p, i);
 +      close_db(p->aAuxDb[i].db);
 +      p->aAuxDb[i].db = 0;
 +    }
 +  }else{
 +    return SHELL_INVALID_ARGS;
 +  }
 +  return 0;
 +}
  
 -  /* Ensure store DB can be opened and/or created appropriately. */
 -  rc = sqlite3_open_v2(zStoreDbName, &dbStore, openFlags, 0);
 -  if( rc!=SQLITE_OK ){
 -    utf8_printf(stderr, "Error: Cannot %s parameter store DB %s\n",
 -                bSaveNotLoad? "open/create" : "read", zStoreDbName);
 -    return rc;
 +/*****************
 + * 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;
 +  int i;
 +  open_db(p, 0);
 +  rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
 +  if( rc ){
 +    *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
 +    rc = 1;
 +  }else{
 +    while( sqlite3_step(pStmt)==SQLITE_ROW ){
 +      const char *zSchema = (const char *)sqlite3_column_text(pStmt,1);
 +      const char *zFile = (const char*)sqlite3_column_text(pStmt,2);
 +      if( zSchema==0 || zFile==0 ) continue;
 +      azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*));
 +      shell_check_oom(azName);
 +      azName[nName*2] = strdup(zSchema);
 +      shell_check_oom(azName[nName*2]);
 +      azName[nName*2+1] = strdup(zFile);
 +      shell_check_oom(azName[nName*2+1]);
 +      nName++;
 +    }
    }
 -  /* Ensure it has the parameter store table, or handle its absence. */
 -  assert(dbStore!=0);
 -  if( sqlite3_table_column_metadata
 -      (dbStore, "main", PARAM_STORE_NAME, 0, 0, 0, 0, 0, 0)!=SQLITE_OK ){
 -    if( !bSaveNotLoad ){
 -      utf8_printf(stderr, "Error: No parameters ever stored in DB %s\n",
 -                  zStoreDbName);
 -      rc = 1;
 +  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);
 +  return rc;
 +}
 +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        },
 +    { "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 && 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) ){
 +    *pzErr = sqlite3_mprintf
 +      ("Error: unknown dbconfig \"%s\"\n"
 +       "Enter \".dbconfig\" with no arguments for a list\n",
 +       azArg[1]);
 +    return 1;
-   }   
++  }
 +  return 0;
 +}
 +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 ){
 +  char *zLike = 0;
++  char *zSchema = "main";
 +  char *zSql;
 +  int i;
 +  int savedShowHeader = p->showHeader;
 +  int savedShellFlags = p->shellFlgs;
-   ShellClearFlag(p, 
++  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( strcmp(z,"preserve-rowids")==0 ){
 +#ifdef SQLITE_OMIT_VIRTUALTABLE
 +        *pzErr = sqlite3_mprintf
 +          ("The --preserve-rowids option is not compatible"
 +                   " with SQLITE_OMIT_VIRTUALTABLE\n");
 +        sqlite3_free(zLike);
 +        return 1;
 +#else
 +        ShellSetFlag(p, SHFLG_PreserveRowid);
 +#endif
 +      }else{
 +        if( strcmp(z,"newlines")==0 ){
 +          ShellSetFlag(p, SHFLG_Newlines);
 +        }else if( strcmp(z,"data-only")==0 ){
 +          ShellSetFlag(p, SHFLG_DumpDataOnly);
 +        }else if( strcmp(z,"nosys")==0 ){
 +          ShellSetFlag(p, SHFLG_DumpNoSys);
++        }else if( strcmp(z,"schema")==0 && ++i<nArg ){
++          zSchema = azArg[i];
 +        }else{
 +          *pzErr = sqlite3_mprintf
 +            ("Unknown option \"%s\" on \".dump\"\n", azArg[i]);
 +          sqlite3_free(zLike);
 +          return 1;
 +        }
 +      }
      }else{
 -      /* The saved parameters table is not there yet; create it. */
 -      const char *zCT =
 -        "CREATE TABLE IF NOT EXISTS "PARAM_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(stderr, "Cannot create table %s. Nothing saved.", zThere);
 +      /* 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 "
++                    "  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], azArg[i]
++                    ")", azArg[i], zSchema, azArg[i]
 +                    );
-       
++
 +      if( zLike ){
 +        zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr);
 +      }else{
 +        zLike = zExpr;
        }
      }
    }
 -  sqlite3_close(dbStore);
 -  if( rc!=0 ) return rc;
  
 -  zSql = sqlite3_mprintf("ATTACH %Q AS %s;", zStoreDbName, PARAM_STORE_SCHEMA);
 -  shell_check_oom(zSql);
 -  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 -  sqlite3_free(zSql);
 -  if( rc!=SQLITE_OK ) return rc;
 +  open_db(p, 0);
  
 -  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);
 +  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 "
++           "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",
-            zLike
++           zSchema, 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;
  
 -  sqlite3_exec(db, "DETACH "PARAM_STORE_SCHEMA";", 0, 0, 0);
 -  return rc;
 +  return 0;
  }
 -
 -/* Default location of parameters store DB for .parameters save/load. */
 -static const char *zDefaultParamStore = "~/sqlite_params.sdb";
 -
 -/* Possibly generate a derived path from input spec, with defaulting
 - * and conversion of leading (or only) tilde as home directory.
 - * The above-set default is used for zSpec NULL, "" or "~".
 - * When return is 0, there is an error; what needs doing cannnot be done.
 - * 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 *params_store_path(const char *zSpec){
 -  if( zSpec==0 || zSpec[0]==0 || strcmp(zSpec,"~")==0 ){
 -    return home_based_path(zDefaultParamStore);
 -  }else if ( zSpec[0]=='~' ){
 -    return home_based_path(zSpec);
 -  }
 -  return zSpec;
 +DISPATCHABLE_COMMAND( echo ? 2 2 ){
 +  setOrClearFlag(p, SHFLG_Echo, azArg[1]);
 +  return 0;
  }
 -
 -/* Load some or all parameters. Arguments are "load FILE ?NAMES?". */
 -static int parameters_load(sqlite3 *db, const char *azArg[], int nArg){
 -  const char *zStore = params_store_path((nArg>1)? azArg[1] : 0);
 -  if( zStore==0 ){
 -    utf8_printf(stderr, "Cannot form parameter load path. Nothing loaded.\n");
 -    return 1;
 +DISPATCHABLE_COMMAND( eqp ? 0 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( strcmp(azArg[1],"full")==0 ){
 +      p->autoEQP = AUTOEQP_full;
 +    }else if( strcmp(azArg[1],"trigger")==0 ){
 +      p->autoEQP = AUTOEQP_trigger;
 +#ifdef SQLITE_DEBUG
 +    }else if( strcmp(azArg[1],"test")==0 ){
 +      p->autoEQP = AUTOEQP_on;
 +      p->autoEQPtest = 1;
 +    }else if( 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{
 -    const char **pzFirst = (nArg>2)? azArg+2 : 0;
 -    int nNames = (nArg>2)? nArg-2 : 0;
 -    int rc = param_xfr_table(db, zStore, 0, pzFirst, nNames);
 -    if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore);
 -    return rc;
 +    return SHELL_INVALID_ARGS;
    }
  }
  
 -/* Save some or all parameters. Arguments are "save FILE ?NAMES?". */
 -static int parameters_save(sqlite3 *db, const char *azArg[], int nArg){
 -  const char *zStore = params_store_path((nArg>1)? azArg[1] : 0);
 -  if( zStore==0 ){
 -    utf8_printf(stderr, "Cannot form parameter save path. Nothing saved.\n");
 -    return 1;
 -  }else{
 -    const char **pzFirst = (nArg>2)? azArg+2 : 0;
 -    int nNames = (nArg>2)? nArg-2 : 0;
 -    int rc = param_xfr_table(db, zStore, 1, pzFirst, nNames);
 -    if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore);
 -    return rc;
 +/*****************
 + * 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 ){
 +  open_db(p, 0);
 +  expertDotCommand(p, azArg, nArg);
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( explain ? 1 2 ){
 +  /* The ".explain" command is automatic now.  It is largely
 +  ** pointless, retained purely for backwards compatibility */
 +  int val = 1;
 +  if( nArg>1 ){
 +    if( 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;
 +  }
 +  return 0;
  }
  
 -#ifndef SQLITE_NOHAVE_SYSTEM
 -/*
 - * Edit one named parameter in the parameters 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 parameters table, and may also serve
 - * to partition the key namespace. (This is not done now.)
 +/*****************
 + * The .excel, .once and .output commands
 + * These share much implementation, so they stick together.
   */
 -static int edit_one_param(sqlite3 *db, char *name, int eval,
 -                          ParamTableUse uses, const char * zEditor){
 -  struct param_row paramVU = {0,0,0};
 -  int rc;
 -  char * zVal = 0;
 -  char * zSql = sqlite3_mprintf
 -    ("SELECT value, uses FROM " PARAM_TABLE_SNAME " WHERE key=%Q", name);
 -  shell_check_oom(zSql);
 -  sqlite3_exec(db, zSql, param_find_callback, &paramVU, 0);
 -  sqlite3_free(zSql);
 -  assert(paramVU.hits<2);
 -  if( paramVU.hits==1 && paramVU.uses==uses){
 -    /* Editing an existing value of same kind. */
 -    sqlite3_free(paramVU.value);
 -    if( eval!=0 ){
 -      zSql = sqlite3_mprintf
 -        ("SELECT edit(value, %Q) FROM " PARAM_TABLE_SNAME
 -         " WHERE key=%Q AND uses=%d", zEditor, name, uses);
 -      zVal = db_text(db, zSql, 1);
 -      sqlite3_free(zSql);
 -      zSql = sqlite3_mprintf
 -        ("UPDATE "PARAM_TABLE_SNAME" SET value=(SELECT %s) WHERE"
 -         " key=%Q AND uses=%d", zVal, name, uses);
 +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\")",
 +];
 +static int outputRedirs(char *[], int, ShellState *,
 +                        char **pzErr, int bOnce, int eMode);
 +DISPATCHABLE_COMMAND( excel ? 1 2 ){
 +  return outputRedirs(azArg, nArg, p, pzErr, 2, 'x');
 +}
 +DISPATCHABLE_COMMAND( once ? 1 6 ){
 +  return outputRedirs(azArg, nArg, p, pzErr, 1, 0);
 +}
 +DISPATCHABLE_COMMAND( output ? 1 6 ){
 +  return outputRedirs(azArg, nArg, p, pzErr, 0, 0);
 +}
 +
 +static int outputRedirs(char *azArg[], int nArg, ShellState *p,
 +                        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;
 +  int bTxtMode = 0;
 +  int i;
 +  int bBOM = 0;
 +  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
 +  for(i=1; i<nArg; i++){
 +    char *z = azArg[i];
 +    if( z[0]=='-' ){
 +      if( z[1]=='-' ) z++;
 +      if( strcmp(z,"-bom")==0 ){
 +        bBOM = 1;
 +      }else if( bOnce!=2 && strcmp(z,"-x")==0 ){
 +        eMode = 'x';  /* spreadsheet */
 +      }else if( bOnce!=2 && strcmp(z,"-e")==0 ){
 +        eMode = 'e';  /* text editor */
 +      }else{
 +        *pzErr = shellMPrintf(0,"       unknown option: \"%s\"\n",azArg[i]);
 +        return SHELL_INVALID_ARGS;
 +      }
 +    }else if( zFile==0 && eMode!='e' && eMode!='x' ){
 +      zFile = sqlite3_mprintf("%s", z);
 +      shell_check_oom(zFile);
 +      if( zFile[0]=='|' ){
 +        while( i+1<nArg ){
 +          zFile = sqlite3_mprintf("%z %s", zFile, azArg[++i]);
 +          shell_check_oom(zFile);
 +        }
 +        break;
 +      }
      }else{
 -      zSql = sqlite3_mprintf
 -        ("UPDATE "PARAM_TABLE_SNAME" SET value=edit(value, %Q) WHERE"
 -         " key=%Q AND uses=%d", zEditor, name, uses);
 +      *pzErr = shellMPrintf(0,"       excess argument: \"%s\"\n", azArg[i]);
 +      sqlite3_free(zFile);
 +      return SHELL_INVALID_ARGS;
      }
 +  }
 +  if( zFile==0 ){
 +    zFile = sqlite3_mprintf("stdout");
 +    shell_check_oom(zFile);
 +  }
 +  if( bOnce ){
 +    p->outCount = 2;
    }else{
 -    /* Editing a new value of same kind. */
 -    assert(paramVU.value==0 || paramVU.uses!=uses);
 -    if( eval!=0 ){
 -      zSql = sqlite3_mprintf
 -        ("SELECT edit('-- %q%s', %Q)", name, "\n", zEditor);
 -      zVal = db_text(db, zSql, 1);
 -      sqlite3_free(zSql);
 -      zSql = sqlite3_mprintf
 -        ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 -         " VALUES (%Q,(SELECT %s LIMIT 1),%d)",
 -         name, zVal, uses);
 +    p->outCount = 0;
 +  }
 +  output_reset(p);
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +  if( eMode=='e' || eMode=='x' ){
 +    p->doXdgOpen = 1;
 +    outputModePush(p);
 +    if( eMode=='x' ){
 +      /* spreadsheet mode.  Output as CSV. */
 +      newTempFile(p, "csv");
 +      ShellClearFlag(p, SHFLG_Echo);
 +      p->mode = MODE_Csv;
 +      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
 +      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf);
      }else{
 -      zSql = sqlite3_mprintf
 -        ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 -         " VALUES (%Q,edit('-- %q%s', %Q),%d)",
 -         name, name, "\n", zEditor, uses);
 +      /* text editor mode */
 +      newTempFile(p, "txt");
 +      bTxtMode = 1;
      }
 +    sqlite3_free(zFile);
 +    zFile = sqlite3_mprintf("%s", p->zTempFile);
    }
 -  shell_check_oom(zSql);
 -  if( eval!=0 ){
 +#endif /* SQLITE_NOHAVE_SYSTEM */
 +  shell_check_oom(zFile);
 +  if( zFile[0]=='|' ){
 +#ifdef SQLITE_OMIT_POPEN
 +    *pzErr = shellMPrintf(&rc, "Error: pipes are not supported in this OS\n");
 +    rc = 1;
 +    p->out = STD_OUT;
 +#else
 +    p->out = popen(zFile + 1, "w");
 +    if( p->out==0 ){
 +      *pzErr = shellMPrintf(&rc, "Error: cannot open pipe \"%s\"\n", zFile + 1);
 +      p->out = STD_OUT;
 +      rc = 1;
 +    }else{
 +      if( bBOM ) fprintf(p->out,"\357\273\277");
 +      sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
 +    }
 +#endif
 +  }else{
 +    p->out = output_file_open(zFile, bTxtMode);
 +    if( p->out==0 ){
 +      if( strcmp(zFile,"off")!=0 ){
 +        *pzErr = shellMPrintf
 +          (&rc, "Error: cannot write to \"%s\"\n", zFile);
 +      }
 +      p->out = STD_OUT;
 +      rc = 1;
 +    } else {
 +      if( bBOM ) fprintf(p->out,"\357\273\277");
 +      sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
 +    }
    }
 -  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 -  sqlite3_free(zSql);
 -  sqlite3_free(zVal);
 -  return rc!=SQLITE_OK;
 +  sqlite3_free(zFile);
 +  return rc;
  }
 -#endif
  
 -/* 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 = sqlite3_mprintf("%z%s%s", z, zSep, *valBeg);
 -    zSep = " ";
 -    ++valBeg;
 +
 +/*****************
 + * 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,       ""               },  
++    { "has_moved",      SQLITE_FCNTL_HAS_MOVED,       ""               },
 +    { "lock_timeout",   SQLITE_FCNTL_LOCK_TIMEOUT,    "MILLISEC"       },
 +    { "persist_wal",    SQLITE_FCNTL_PERSIST_WAL,     "[BOOLEAN]"      },
 + /* { "pragma",         SQLITE_FCNTL_PRAGMA,          "NAME ARG"       },*/
 +    { "psow",       SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]"      },
 +    { "reserve_bytes",  SQLITE_FCNTL_RESERVE_BYTES,   "[N]"            },
 +    { "size_limit",     SQLITE_FCNTL_SIZE_LIMIT,      "[LIMIT]"        },
 +    { "tempfilename",   SQLITE_FCNTL_TEMPFILENAME,    ""               },
 + /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY,  "COUNT DELAY"    },*/
 +  };
 +  int filectrl = -1;
 +  int iCtrl = -1;
 +  sqlite3_int64 iRes = 0;  /* Integer result to display if rc2==1 */
 +  int isOk = 0;            /* 0: usage  1: %lld  2: no-result */
 +  int n2, i;
 +  const char *zCmd = 0;
 +  const char *zSchema = 0;
 +
 +  open_db(p, 0);
 +  zCmd = nArg>=2 ? azArg[1] : "help";
 +
-   if( zCmd[0]=='-' 
++  if( zCmd[0]=='-'
 +      && (strcmp(zCmd,"--schema")==0 || 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 z;
 +
 +  /* 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( 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);
 +    }
 +    return 1;
 +  }
 +  
 +  /* 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( strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
 +      if( filectrl<0 ){
 +        filectrl = aCtrl[i].ctrlCode;
 +        iCtrl = i;
 +      }else{
 +        utf8_printf(STD_ERR, "Error: ambiguous file-control: \"%s\"\n"
 +                    "Use \".filectrl --help\" for help\n", zCmd);
 +        return 1;
 +      }
 +    }
 +  }
 +  if( filectrl<0 ){
 +    utf8_printf(STD_ERR,"Error: unknown file-control: %s\n"
 +                "Use \".filectrl --help\" for help\n", zCmd);
 +  }else{
 +   switch(filectrl){
 +    case SQLITE_FCNTL_SIZE_LIMIT: {
 +      if( nArg!=2 && nArg!=3 ) break;
 +      iRes = nArg==3 ? integerValue(azArg[2]) : -1;
 +      sqlite3_file_control(p->db, zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes);
 +      isOk = 1;
 +      break;
 +    }
 +    case SQLITE_FCNTL_LOCK_TIMEOUT:
 +    case SQLITE_FCNTL_CHUNK_SIZE: {
 +      int x;
 +      if( nArg!=3 ) break;
 +      x = (int)integerValue(azArg[2]);
 +      sqlite3_file_control(p->db, zSchema, filectrl, &x);
 +      isOk = 2;
 +      break;
 +    }
 +    case SQLITE_FCNTL_PERSIST_WAL:
 +    case SQLITE_FCNTL_POWERSAFE_OVERWRITE: {
 +      int x;
 +      if( nArg!=2 && nArg!=3 ) break;
 +      x = nArg==3 ? booleanValue(azArg[2]) : -1;
 +      sqlite3_file_control(p->db, zSchema, filectrl, &x);
 +      iRes = x;
 +      isOk = 1;
 +      break;
 +    }
 +    case SQLITE_FCNTL_DATA_VERSION:
 +    case SQLITE_FCNTL_HAS_MOVED: {
 +      int x;
 +      if( nArg!=2 ) break;
 +      sqlite3_file_control(p->db, zSchema, filectrl, &x);
 +      iRes = x;
 +      isOk = 1;
 +      break;
 +    }
 +    case SQLITE_FCNTL_TEMPFILENAME: {
 +      char *z = 0;
 +      if( nArg!=2 ) break;
 +      sqlite3_file_control(p->db, zSchema, filectrl, &z);
 +      if( z ){
 +        utf8_printf(p->out, "%s\n", z);
 +        sqlite3_free(z);
 +      }
 +      isOk = 2;
 +      break;
 +    }
 +    case SQLITE_FCNTL_RESERVE_BYTES: {
 +      int x;
 +      if( nArg>=3 ){
 +        x = atoi(azArg[2]);
 +        sqlite3_file_control(p->db, zSchema, filectrl, &x);
 +      }
 +      x = -1;
 +      sqlite3_file_control(p->db, zSchema, filectrl, &x);
 +      utf8_printf(p->out,"%d\n", x);
 +      isOk = 2;
 +      break;
 +    }
 +   }
 +  }
 +  if( isOk==0 && iCtrl>=0 ){
 +    utf8_printf(p->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
 +    return 1;
 +  }else if( isOk==1 ){
 +    char zBuf[100];
 +    sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes);
 +    raw_printf(p->out, "%s\n", zBuf);
 +  }
 +  return 0;
  }
  
 -/* Get a named parameter value in form of stepped prepared statement,
 - * ready to have its value taken from the 0th column. If the name
 - * cannot be found for the given ParamTableUse, 0 is returned.
 - * The caller is responsible for calling sqlite3_finalize(pStmt),
 - * where pStmt is the return from this function.
 - */
 -static sqlite3_stmt *get_param_value(sqlite3 *db, char *name,
 -                                     ParamTableUse ptu){
 -  sqlite3_stmt *rv = 0;
 +DISPATCHABLE_COMMAND( fullschema ? 1 2 ){
    int rc;
 -  char *zSql = sqlite3_mprintf
 -    ( "SELECT value FROM "PARAM_TABLE_SNAME
 -      " WHERE key=%Q AND uses=%d", name, ptu );
 -  shell_check_oom(zSql);
 -  rc = sqlite3_prepare_v2(db, zSql, -1, &rv, 0);
 -  sqlite3_free(zSql);
 -  if( SQLITE_OK==rc ){
 -    if( SQLITE_ROW==sqlite3_step(rv) ) return rv;
 -    sqlite3_finalize(rv);
 +  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 ){
 +    return SHELL_INVALID_ARGS;
 +  }
 +  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");
    }
 -  return 0;
 +  return rc > 0;
  }
  
 -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 }
 -};
 +/*****************
 + * The .headers command
 + */
 +COLLECT_HELP_TEXT[
 +  ".headers on|off          Turn display of headers on or off",
 +];
 +DISPATCHABLE_COMMAND( headers 6 2 2 ){
 +  p->showHeader = booleanValue(azArg[1]);
 +  p->shellFlgs |= SHFLG_HeaderSet;
 +  return 0;
 +}
  
 -/* Return an option character if it is single and prefixed by - or --,
 - * else return 0.
 +/*****************
 + * The .help command
   */
 -static char option_char(char *zArg){
 -  if( zArg[0]=='-' ){
 -    ++zArg;
 -    if( zArg[0]=='-' ) ++zArg;
 -    if( zArg[0]!=0 && zArg[1]==0 ) return zArg[0];
 +COLLECT_HELP_TEXT[
-   ".help ?(PATTERN|-all)? Show help text for some or all command(s)",
-   "   PATTERN                Show help for matching command(s)",
-   "   -all                   Show all help for all commands",
++  ".help ?PATTERN?|?-all?   Show help for PATTERN or everything, or summarize",
++  "                           Repeat -all to see undocumented commands",
 +];
 +DISPATCHABLE_COMMAND( help 3 1 2 ){
 +  const char *zPat = 0;
 +  if( nArg>1 ){
 +    char *z = azArg[1];
 +    if( strcmp(z,"-a")==0
 +        || strcmp(z,"-all")==0
 +        || strcmp(z,"--all")==0 ){
 +      zPat = "";
 +    }else{
 +      zPat = z;
 +    }
 +  }
 +  if( showHelp(p->out, zPat)==0 ){
 +    utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]);
    }
 +  /* Help pleas never fail! */
    return 0;
  }
  
 -static int param_set(sqlite3 *db, char cCast,
 -                     char *name, char **valBeg, char **valLim,
 -                     ParamTableUse ptu){
 -  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;
 -      }
 -    }
 +/*****************
 + * 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 = "main";     /* within this schema */
 +  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 */
 +  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 */
 +  int rc = 0;
 +
 +  if(p->bSafeMode) return SHELL_FORBIDDEN_OP;
 +  memset(&sCtx, 0, sizeof(sCtx));
 +  if( 0==(sCtx.z = sqlite3_malloc64(120)) ){
 +    shell_out_of_memory();
    }
 -  if( needsEval ){
 -    if( zCastTo!=0 ){
 -      zSql = sqlite3_mprintf
 -        ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 -          " VALUES(%Q,CAST((%s) AS %s),%d);", name, zValue, zCastTo, ptu );
 +
 +  if( p->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{
 +        *pzErr = shellMPrintf(0,"       surplus argument: \"%s\"\n", z);
 +        return SHELL_INVALID_ARGS;
 +      }
 +    }else if( strcmp(z,"-v")==0 ){
 +      eVerbose++;
 +    }else if( strcmp(z,"-schema")==0 && i<nArg-1 ){
 +      zSchema = azArg[++i];
 +    }else if( strcmp(z,"-skip")==0 && i<nArg-1 ){
 +      nSkip = integerValue(azArg[++i]);
 +    }else if( strcmp(z,"-ascii")==0 ){
 +      sCtx.cColSep = SEP_Unit[0];
 +      sCtx.cRowSep = SEP_Record[0];
 +      xRead = ascii_read_one_field;
 +      useOutputMode = 0;
 +    }else if( strcmp(z,"-csv")==0 ){
 +      sCtx.cColSep = ',';
 +      sCtx.cRowSep = '\n';
 +      xRead = csv_read_one_field;
 +      useOutputMode = 0;
      }else{
 -      zSql = sqlite3_mprintf
 -        ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 -          "VALUES(%Q,(%s),%d);", name, zValue, ptu );
 +      *pzErr = shellMPrintf(0,"       unknown option: \"%s\"", z);
 +      return SHELL_INVALID_ARGS;
      }
 -    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 = sqlite3_mprintf
 -      ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 -        "VALUES(%Q,%Q,%d);", name, zValue, ptu );
 -    shell_check_oom(zSql);
 -    rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0);
 -    assert(rc==SQLITE_OK);
 -    sqlite3_free(zSql);
 +  if( zTable==0 ){
 +    *pzErr = shellMPrintf(0,"       missing %s argument.\n",
 +                zFile==0 ? "FILE" : "TABLE");
 +    return SHELL_INVALID_ARGS;
    }
 -  sqlite3_step(pStmtSet);
 -  sqlite3_finalize(pStmtSet);
 -  sqlite3_free(zValGlom);
 -  return rc;
 -}
 -
 -/* list or ls subcommand for .parameter dot-command */
 -static void list_params(ShellState *p, ParamTableUse ptu, u8 bShort,
 -                        char **pzArgs, int nArg){
 -  sqlite3_stmt *pStmt = 0;
 -  sqlite3_str *sbList = sqlite3_str_new(p->db);
 -  int len = 0, rc;
 -  char *zFromWhere = 0;
 -  char *zSql = 0;
 -  sqlite3_str_appendf(sbList, "FROM "PARAM_TABLE_SNAME
 -                      " WHERE (?1=3 OR 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 = sqlite3_mprintf("SELECT max(length(key)) %s", zFromWhere);
 -  shell_check_oom(zSql);
 +  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(p->colSeparator);
 +    if( nSep==0 ){
 +      zYap = "Error: non-null column separator required for import";
 +    }
 +    if( nSep>1 ){
 +      zYap = "Error: multi-character or multi-byte column separators"
 +        " not allowed for import";
 +    }
 +    nSep = strlen30(p->rowSeparator);
 +    if( nSep==0 ){
 +      zYap = "Error: non-null row separator required for import";
 +    }
 +    if( zYap!=0 ){
 +      *pzErr = shellMPrintf(0,"%s\n", zYap);
 +      return 1;
 +    }
 +    if( nSep==2 && p->mode==MODE_Csv && 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 ){
 +      *pzErr = sqlite3_mprintf
 +        ("Error: multi-character row separators not allowed for import\n");
 +      return 1;
 +    }
 +    sCtx.cColSep = p->colSeparator[0];
 +    sCtx.cRowSep = p->rowSeparator[0];
 +  }
 +  sCtx.zFile = zFile;
 +  sCtx.nLine = 1;
 +  if( sCtx.zFile[0]=='|' ){
 +#ifdef SQLITE_OMIT_POPEN
 +    *pzErr = shellMPrintf(0,"Error: pipes are not supported in this OS\n");
 +    return 1;
 +#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 = shellMPrintf(0,"Error: cannot open \"%s\"\n", zFile);
 +    import_cleanup(&sCtx);
 +    return 1;
 +  }
 +  /* 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(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");
 +  }
 +  while( (nSkip--)>0 ){
 +    while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
 +  }
 +  zSql = sqlite3_mprintf("SELECT * FROM \"%w\".\"%w\"", zSchema, zTable);
 +  if( zSql==0 ){
 +    import_cleanup(&sCtx);
 +    shell_out_of_memory();
 +  }
 +  nByte = strlen30(zSql);
    rc = sqlite3_prepare_v2(p->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;
 +  import_append_char(&sCtx, 0);    /* To ensure sCtx.z is allocated */
 +  if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){
 +    char *zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"",
 +                                    zSchema, zTable);
 +    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 ){
-       utf8_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr,
-                   "Columns renamed during .import %s due to duplicates:\n"
++      FILE *fh = INSOURCE_IS_INTERACTIVE(p->pInSource)?  p->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 ){
 +      sqlite3_free(zCreate);
 +      import_cleanup(&sCtx);
 +      *pzErr = shellMPrintf(0,"%s: empty file\n", sCtx.zFile);
 +      return 1;
      }
-       utf8_printf(stderr, "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
 +    zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs);
 +    if( eVerbose>=1 ){
 +      utf8_printf(p->out, "%s\n", zCreate);
 +    }
 +    rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
 +    if( rc ){
++      utf8_printf(STD_ERR, "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
 +      sqlite3_free(zCreate);
 +      import_cleanup(&sCtx);
 +      return 1;
 +    }
 +    sqlite3_free(zCreate);
 +    rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 +  }
 +  sqlite3_free(zSql);
 +  if( rc ){
 +    if (pStmt) sqlite3_finalize(pStmt);
 +    *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
 +    import_cleanup(&sCtx);
 +    return 1;
    }
 +  nCol = sqlite3_column_count(pStmt);
    sqlite3_finalize(pStmt);
    pStmt = 0;
 -  if( len ){
 -    sqlite3_free(zSql);
 -    if( !bShort ){
 -      int nBindings = 0, nScripts = 0;
 -      zSql = sqlite3_mprintf("SELECT key, uses, iif(uses, value, quote(value))"
 -                             " %z ORDER BY uses, key", zFromWhere);
 -      shell_check_oom(zSql);
 -      rc = sqlite3_prepare_v2(p->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(p->out, "Binding Values:\n%-*s %s\n",
 -                        len, "name", "value");
 -          }
 -          utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
 -                      sqlite3_column_text(pStmt,2));
 -          break;
 -        case PTU_Script:
 -          if( nScripts++ == 0 ){
 -            utf8_printf(p->out, "Scripts\n%-*s %s\n", len, "name", "value");
 +  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 \"%w\".\"%w\" VALUES(?",
 +                   zSchema, zTable);
 +  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);
 +  sqlite3_free(zSql);
 +  if( rc ){
 +    *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
 +    if (pStmt) sqlite3_finalize(pStmt);
 +    import_cleanup(&sCtx);
 +    return 1;
 +  }
 +  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(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(p->db));
 +        sCtx.nErr++;
 +      }else{
 +        sCtx.nRow++;
 +      }
 +    }
 +  }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 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 ){
 +  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;
            }
 -          utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
 -                      sqlite3_column_text(pStmt,2));
 -          break;
 -        default: break; /* Ignore */
 +          memcpy(kwBuf, zKW, szKW);
 +          kwBuf[szKW] = 0;
 +          utf8_printf(p->out, "%s%s", kwBuf, zSep);
          }
        }
 -    }else{
 -      int nc = 0, ncw = 78/(len+2);
 -      zSql = sqlite3_mprintf("SELECT key %z ORDER BY key", zFromWhere);
 -      shell_check_oom(zSql);
 -      rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -      sqlite3_bind_int(pStmt, 1, ptu);
 -      while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 -        utf8_printf(p->out, "%s  %-*s", ((++nc%ncw==0)? "\n" : ""),
 -                    len, sqlite3_column_text(pStmt,0));
 +    }
 +    if( nCol>0 ) utf8_printf(p->out, "\n");
 +  }else{
 +    int szKW = strlen30(azArg[1]);
 +    int isKeyword = sqlite3_keyword_check(azArg[1], szKW);
 +    utf8_printf(p->out, "%s is%s a keyword\n",
 +                azArg[1], (isKeyword)? "" : " not");
 +  }
 +  return 0;
 +}
 +
 +/*****************
 + * The .imposter, .iotrace, limit, lint, .load and .log commands
 + */
 +CONDITION_COMMAND( imposter !defined(SQLITE_OMIT_TEST_CONTROL) );
 +CONDITION_COMMAND( iotrace defined(SQLITE_ENABLE_IOTRACE) );
 +CONDITION_COMMAND( load !defined(SQLITE_OMIT_LOAD_EXTENSION) );
 +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",
 +  ".load FILE ?ENTRY?       Load an extension library",
 +  ".log FILE|off            Turn logging on or off.  FILE can be stderr/stdout",
 +];
 +DISPATCHABLE_COMMAND( imposter ? 3 3 ){
 +  int rc = 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( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){
 +    *pzErr = shellMPrintf(0,"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 1;
 +  }
 +  open_db(p, 0);
 +  if( nArg==2 ){
 +    sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 1);
 +    return 0;
 +  }
 +  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);
 +    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( nc>0 ) utf8_printf(p->out, "\n");
      }
 -    sqlite3_finalize(pStmt);
 +    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);
 +    }
 +  }
 +  sqlite3_finalize(pStmt);
 +  if( i==0 || tnum==0 ){
 +    *pzErr = shellMPrintf(0,"no such index: \"%s\"\n", azArg[1]);
 +    sqlite3_free(zCollist);
 +    return 1;
 +  }
 +  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 ){
 +      *pzErr = shellMPrintf(0,"Error in [%s]: %s\n",
 +                               zSql, sqlite3_errmsg(p->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{
 -    sqlite3_free(zFromWhere);
 +    *pzErr = shellMPrintf(0,"SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
    }
    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;
 +  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( strcmp(azArg[1], "-")==0 ){
 +    sqlite3IoTrace = iotracePrintf;
 +    iotrace = STD_OUT;
 +  }else{
 +    iotrace = fopen(azArg[1], "w");
 +    if( iotrace==0 ){
 +      *pzErr = shellMPrintf(0,"Error: cannot open \"%s\"\n", azArg[1]);
 +      sqlite3IoTrace = 0;
 +      return 1;
 +    }else{
 +      sqlite3IoTrace = iotracePrintf;
      }
 -    sqlite3_str_appendf(pStr, ")");
    }
 +  return 0;
  }
 -
 -/* 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;
 +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(p->db, aLimit[i].limitCode, -1));
      }
 -    sqlite3_str_appendf(pStr, ")");
 +  }else if( nArg>3 ){
 +    return SHELL_INVALID_ARGS;
 +  }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 = shellMPrintf(0,"ambiguous limit: \"%s\"\n", azArg[1]);
 +          return 1;
 +        }
 +      }
 +    }
 +    if( iLimit<0 ){
 +      *pzErr = sqlite3_mprintf
 +        ("unknown limit: \"%s\"\n"
 +         "enter \".limits\" with no arguments for a list.\n",
 +         azArg[1]);
 +      return 1;
 +    }
 +    if( nArg==3 ){
 +      sqlite3_limit(p->db, aLimit[iLimit].limitCode,
 +                    (int)integerValue(azArg[2]));
 +    }
 +    fprintf(STD_OUT, "%20s %d\n", aLimit[iLimit].zLimitName,
 +           sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1));
    }
 +  return 0;
  }
  
 +DISPATCHABLE_COMMAND( lint 3 1 0 ){
 +  open_db(p, 0);
 +  int n = (nArg>=2 ? strlen30(azArg[1]) : 0);
 +  if( n>0 && !sqlite3_strnicmp(azArg[1], "fkey-indexes", n) ){
 +    return lintFkeyIndexes(p, azArg, nArg);
 +  }
 +  *pzErr = sqlite3_mprintf
 +    ("Usage %s sub-command ?switches...?\n"
 +     "Where sub-commands are:\n"
 +     "    fkey-indexes\n", azArg[0]);
 +  return 1;
 +}
  
 -/*
 - * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it.
 - * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE,
 - *   close db and set it to 0, and return the columns spec, to later
 - *   be sqlite3_free()'ed by the caller.
 - * The return is 0 when either:
 - *   (a) The db was not initialized and zCol==0 (There are no columns.)
 - *   (b) zCol!=0  (Column was added, db initialized as needed.)
 - * The 3rd argument, pRenamed, references an out parameter. If the
 - * pointer is non-zero, its referent will be set to a summary of renames
 - * done if renaming was necessary, or set to 0 if none was done. The out
 - * string (if any) must be sqlite3_free()'ed by the caller.
 - */
 -#ifdef SHELL_DEBUG
 -#define rc_err_oom_die(rc) \
 -  if( rc==SQLITE_NOMEM ) shell_check_oom(0); \
 -  else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \
 -    fprintf(stderr,"E:%d\n",rc), assert(0)
 -#else
 -static void rc_err_oom_die(int rc){
 -  if( rc==SQLITE_NOMEM ) shell_check_oom(0);
 -  assert(rc==SQLITE_OK||rc==SQLITE_DONE);
 +DISPATCHABLE_COMMAND( load ? 2 3 ){
 +  const char *zFile, *zProc;
 +  char *zErrMsg = 0;
 +  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
 +  zFile = azArg[1];
 +  zProc = nArg>=3 ? azArg[2] : 0;
 +  open_db(p, 0);
 +  if( SQLITE_OK!=sqlite3_load_extension(p->db, zFile, zProc, pzErr) ){
 +    return 1;
 +  }
 +  return 0;
  }
 -#endif
  
 -#ifdef SHELL_COLFIX_DB /* If this is set, the DB can be in a file. */
 -static char zCOL_DB[] = SHELL_STRINGIFY(SHELL_COLFIX_DB);
 -#else  /* Otherwise, memory is faster/better for the transient DB. */
 -static const char *zCOL_DB = ":memory:";
 -#endif
 +DISPATCHABLE_COMMAND( log ? 2 2 ){
 +  const char *zFile = azArg[1];
 +  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
 +  output_file_close(p->pLog);
 +  p->pLog = output_file_open(zFile, 0);
 +  return 0;
 +}
  
 -/* Define character (as C string) to separate generated column ordinal
 - * from protected part of incoming column names. This defaults to "_"
 - * so that incoming column identifiers that did not need not be quoted
 - * remain usable without being quoted. It must be one character.
 - */
 -#ifndef SHELL_AUTOCOLUMN_SEP
 -# define AUTOCOLUMN_SEP "_"
 -#else
 -# define AUTOCOLUMN_SEP SHELL_STRINGIFY(SHELL_AUTOCOLUMN_SEP)
 -#endif
++static void effectMode(ShellState *p, 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( (p->shellFlgs & SHFLG_HeaderSet)==0 ){
++      p->showHeader = 1;
++    }
++    zRowSep = SEP_Row;
++    break;
++  case MODE_List:
++    zColSep = SEP_Column;
++    zRowSep = SEP_Row;
++    break;
++  case MODE_Html:
++    break;
++  case MODE_Tcl:
++    zColSep = SEP_Space;
++    zRowSep = SEP_Row;
++    break;
++  case MODE_Csv:
++    zColSep = SEP_Comma;
++    zRowSep = SEP_CrLf;
++    break;
++  case MODE_Tab:
++    zColSep = SEP_Tab;
++    break;
++  case MODE_Insert:
++    break;
++  case MODE_Quote:
++    zColSep = SEP_Comma;
++    zRowSep = SEP_Row;
++    break;
++  case MODE_Ascii:
++    zColSep = SEP_Unit;
++    zRowSep = SEP_Record;
++    break;
++  case MODE_Markdown:
++    /* fall-thru */
++  case MODE_Table:
++    /* fall-thru */
++  case MODE_Box:
++    break;
++  case MODE_Count:
++    /* fall-thru */
++  case MODE_Off:
++    /* fall-thru */
++  case MODE_Json:
++    break;
++  case MODE_Explain: case MODE_Semi: case MODE_EQP: case MODE_Pretty: default:
++    /* Modes used internally, not settable by .mode command. */
++    return;
++  }
++  if( zRowSep!=0 ){
++    sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, zRowSep);
++  }
++  if( zColSep!=0 ){
++    sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, zColSep);
++  }
++  p->mode = modeNominal;
++}
 -static char *zAutoColumn(const char *zColNew, sqlite3 **pDb, char **pzRenamed){
 -  /* Queries and D{D,M}L used here */
 -  static const char * const zTabMake = "\
 -CREATE TABLE ColNames(\
 - cpos INTEGER PRIMARY KEY,\
 - name TEXT, nlen INT, chop INT, reps INT, suff TEXT);\
 -CREATE VIEW RepeatedNames AS \
 -SELECT DISTINCT t.name FROM ColNames t \
 -WHERE t.name COLLATE NOCASE IN (\
 - SELECT o.name FROM ColNames o WHERE o.cpos<>t.cpos\
 -);\
 -";
 -  static const char * const zTabFill = "\
 -INSERT INTO ColNames(name,nlen,chop,reps,suff)\
 - VALUES(iif(length(?1)>0,?1,'?'),max(length(?1),1),0,0,'')\
 -";
 -  static const char * const zHasDupes = "\
 -SELECT count(DISTINCT (substring(name,1,nlen-chop)||suff) COLLATE NOCASE)\
 - <count(name) FROM ColNames\
 -";
 -#ifdef SHELL_COLUMN_RENAME_CLEAN
 -  static const char * const zDedoctor = "\
 -UPDATE ColNames SET chop=iif(\
 -  (substring(name,nlen,1) BETWEEN '0' AND '9')\
 -  AND (rtrim(name,'0123456790') glob '*"AUTOCOLUMN_SEP"'),\
 - nlen-length(rtrim(name, '"AUTOCOLUMN_SEP"0123456789')),\
 - 0\
 -)\
 -";
 -#endif
 -  static const char * const zSetReps = "\
 -UPDATE ColNames AS t SET reps=\
 -(SELECT count(*) FROM ColNames d \
 - WHERE substring(t.name,1,t.nlen-t.chop)=substring(d.name,1,d.nlen-d.chop)\
 - COLLATE NOCASE\
 -)\
 -";
 -#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
 -  static const char * const zColDigits = "\
 -SELECT CAST(ceil(log(count(*)+0.5)) AS INT) FROM ColNames \
 -";
 -#endif
 -  static const char * const zRenameRank =
 -#ifdef SHELL_COLUMN_RENAME_CLEAN
 -    "UPDATE ColNames AS t SET suff="
 -    "iif(reps>1, printf('%c%0*d', '"AUTOCOLUMN_SEP"', $1, cpos), '')"
 -#else /* ...RENAME_MINIMAL_ONE_PASS */
 -"WITH Lzn(nlz) AS (" /* Find minimum extraneous leading 0's for uniqueness */
 -"  SELECT 0 AS nlz"
 -"  UNION"
 -"  SELECT nlz+1 AS nlz FROM Lzn"
 -"  WHERE EXISTS("
 -"   SELECT 1"
 -"   FROM ColNames t, ColNames o"
 -"   WHERE"
 -"    iif(t.name IN (SELECT * FROM RepeatedNames),"
 -"     printf('%s"AUTOCOLUMN_SEP"%s',"
 -"      t.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,t.cpos),2)),"
 -"     t.name"
 -"    )"
 -"    ="
 -"    iif(o.name IN (SELECT * FROM RepeatedNames),"
 -"     printf('%s"AUTOCOLUMN_SEP"%s',"
 -"      o.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,o.cpos),2)),"
 -"     o.name"
 -"    )"
 -"    COLLATE NOCASE"
 -"    AND o.cpos<>t.cpos"
 -"   GROUP BY t.cpos"
 -"  )"
 -") UPDATE Colnames AS t SET"
 -" chop = 0," /* No chopping, never touch incoming names. */
 -" suff = iif(name IN (SELECT * FROM RepeatedNames),"
 -"  printf('"AUTOCOLUMN_SEP"%s', substring("
 -"   printf('%.*c%0.*d',(SELECT max(nlz) FROM Lzn)+1,'0',1,t.cpos),2)),"
 -"  ''"
 -" )"
 -#endif
 -    ;
 -  static const char * const zCollectVar = "\
 -SELECT\
 - '('||x'0a'\
 - || group_concat(\
 -  cname||' TEXT',\
 -  ','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\
 - ||')' AS ColsSpec \
 -FROM (\
 - SELECT cpos, printf('\"%w\"',printf('%.*s%s', nlen-chop,name,suff)) AS cname \
 - FROM ColNames ORDER BY cpos\
 -)";
 -  static const char * const zRenamesDone =
 -    "SELECT group_concat("
 -    " printf('\"%w\" to \"%w\"',name,printf('%.*s%s', nlen-chop, name, suff)),"
 -    " ','||x'0a')"
 -    "FROM ColNames WHERE suff<>'' OR chop!=0"
 -    ;
 -  int rc;
 -  sqlite3_stmt *pStmt = 0;
 -  assert(pDb!=0);
 -  if( zColNew ){
 -    /* Add initial or additional column. Init db if necessary. */
 -    if( *pDb==0 ){
 -      if( SQLITE_OK!=sqlite3_open(zCOL_DB, pDb) ) return 0;
 -#ifdef SHELL_COLFIX_DB
 -      if(*zCOL_DB!=':')
 -        sqlite3_exec(*pDb,"drop table if exists ColNames;"
 -                     "drop view if exists RepeatedNames;",0,0,0);
 -#endif
 -      rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0);
 -      rc_err_oom_die(rc);
 +/*****************
 + * The .mode command
 + */
 +COLLECT_HELP_TEXT[
-   ".mode MODE ?TABLE?       Set output mode",
++  ".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 --width 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 ){
 +  const char *zTabname = 0;
 +  const char *zArg;
 +  int i;
 +  u8 foundMode = MODE_COUNT_OF, setMode = MODE_COUNT_OF;
 +  ColModeOpts cmOpts = ColModeOpts_default;
 +  for(i=1; i<nArg; i++){
 +    zArg = azArg[i];
 +    if( optionMatch(zArg,"wrap") && i+1<nArg ){
 +      cmOpts.iWrap = integerValue(azArg[++i]);
 +    }else if( optionMatch(zArg,"ww") ){
 +      cmOpts.bWordWrap = 1;
 +    }else if( optionMatch(zArg,"wordwrap") && i+1<nArg ){
 +      cmOpts.bWordWrap = (u8)booleanValue(azArg[++i]);
 +    }else if( optionMatch(zArg,"quote") ){
 +      cmOpts.bQuote = 1;
 +    }else if( optionMatch(zArg,"noquote") ){
 +      cmOpts.bQuote = 0;
 +    }else{
 +      /* Not a known option. Check for known mode, or possibly a table name. */
 +      if( foundMode==MODE_Insert && zTabname==0 ){
 +        zTabname = zArg;
 +      }else if( *zArg=='-' ){
 +        goto flag_unknown;
 +      }else{
 +        int im, nza = strlen30(zArg);
 +        if( foundMode!=MODE_COUNT_OF ) goto mode_badarg;
 +        for( im=0; im<MODE_COUNT_OF; ++im ){
 +          if( modeDescr[i].bUserBlocked ) continue;
 +          if( strncmp(zArg,modeDescr[im].zModeName,nza)==0 ){
 +            foundMode = (u8)im;
 +            setMode = modeDescr[im].iAliasFor;
 +            break;
 +          }
 +        }
 +        if( 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 ){
 +    const char *zMode;
 +    int nms;
 +    i = p->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( p->mode==MODE_Column || (p->mode>=MODE_Markdown && p->mode<=MODE_Box) ){
++    if( MODE_IS_COLUMNAR(p->mode) ){
 +      raw_printf
 +        (p->out, "current output mode: %.*s --wrap %d --wordwrap %s --%squote\n",
 +         nms, zMode, p->cmOpts.iWrap,
 +         p->cmOpts.bWordWrap ? "on" : "off",
 +         p->cmOpts.bQuote ? "" : "no");
 +    }else{
 +      raw_printf(p->out, "current output mode: %.*s\n", nms, zMode);
      }
 -    assert(*pDb!=0);
 -    rc = sqlite3_prepare_v2(*pDb, zTabFill, -1, &pStmt, 0);
 -    rc_err_oom_die(rc);
 -    rc = sqlite3_bind_text(pStmt, 1, zColNew, -1, 0);
 -    rc_err_oom_die(rc);
 -    rc = sqlite3_step(pStmt);
 -    rc_err_oom_die(rc);
 -    sqlite3_finalize(pStmt);
 -    return 0;
 -  }else if( *pDb==0 ){
 -    return 0;
    }else{
-     /* Effect the specified mode change. */
-     const char *zColSep = 0, *zRowSep = 0;
-     assert(setMode!=MODE_COUNT_OF);
-     switch( foundMode ){
-     case MODE_Line:
-       zRowSep = SEP_Row;
-       break;
-     case MODE_Column:
-       if( (p->shellFlgs & SHFLG_HeaderSet)==0 ){
-         p->showHeader = 1;
-       }
-       zRowSep = SEP_Row;
-       p->cmOpts = cmOpts;
-       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:
 -    /* Formulate the columns spec, close the DB, zero *pDb. */
 -    char *zColsSpec = 0;
 -    int hasDupes = db_int(*pDb, zHasDupes);
 -#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
 -    int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0;
 -#else
 -# define nDigits 2
 -#endif
 -    if( hasDupes ){
 -#ifdef SHELL_COLUMN_RENAME_CLEAN
 -      rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0);
 -      rc_err_oom_die(rc);
++    effectMode(p, foundMode, setMode);
++    if( MODE_IS_COLUMNAR(setMode) ) p->cmOpts = cmOpts;
++    else if( setMode==MODE_Insert ){
 +      set_table_name(p, zTabname ? zTabname : "table");
-       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:
-       p->cmOpts = cmOpts;
-       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 0;
-     }
-     if( zRowSep!=0 ){
-       sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, zRowSep);
-     }
-     if( zColSep!=0 ){
-       sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, zColSep);
 +    }
-     p->mode = setMode;
 +  }
 +  p->cMode = p->mode;
 +  return 0;
 + flag_unknown:
-   utf8_printf(stderr, "Error: Unknown .mode option: %s\nValid options:\n%s",
++  utf8_printf(STD_ERR, "Error: Unknown .mode option: %s\nValid options:\n%s",
 +              zArg,
 +              "  --noquote\n"
 +              "  --quote\n"
 +              "  --wordwrap on/off\n"
 +              "  --wrap N\n"
 +              "  --ww\n");
 +  return 1;
 + mode_unknown:
-   raw_printf(stderr, "Error: Mode should be one of: "
++  raw_printf(STD_ERR, "Error: Mode should be one of: "
 +             "ascii box column csv html insert json line list markdown "
 +             "qbox quote table tabs tcl\n");
 +  return 1;
 + mode_badarg:
-   utf8_printf(stderr, "Error: Invalid .mode argument: %s\n", zArg);
++  utf8_printf(STD_ERR, "Error: Invalid .mode argument: %s\n", zArg);
 +  return 1;
 +}
 +
 +/*****************
 + * The .open, .nonce and .nullvalue commands
 + */
 +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
 -      rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0);
 -      rc_err_oom_die(rc);
 -      rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0);
 -      rc_err_oom_die(rc);
 -      sqlite3_bind_int(pStmt, 1, nDigits);
 -      rc = sqlite3_step(pStmt);
 -      sqlite3_finalize(pStmt);
 -      assert(rc==SQLITE_DONE);
 +  "        --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 ){
 +  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 */
-   int openMode = SHELL_OPEN_UNSPEC;
++  u8 openMode = SHELL_OPEN_UNSPEC;
 +  int rc = 0;
 +  /* Check for command-line arguments */
 +  for(iName=1; iName<nArg; iName++){
 +    const char *z = azArg[iName];
 +    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") ){
 +      p->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 ){
 +      p->szMax = integerValue(azArg[++iName]);
 +#endif /* SQLITE_OMIT_DESERIALIZE */
 +    }else if( z[0]=='-' ){
 +      *pzErr = shellMPrintf(0,"unknown option: %s\n", z);
 +      return SHELL_INVALID_ARGS;
 +    }else if( zFN ){
 +      *pzErr = shellMPrintf(0,"extra argument: \"%s\"\n", z);
 +      return SHELL_INVALID_ARGS;
 +    }else{
 +      zFN = z;
 +    }
 +  }
 +
 +  /* Close the existing database */
 +  session_close_all(p, -1);
 +  close_db(p->db);
 +  p->db = 0;
 +  p->pAuxDb->zDbFilename = 0;
 +  sqlite3_free(p->pAuxDb->zFreeOnClose);
 +  p->pAuxDb->zFreeOnClose = 0;
 +  p->openMode = openMode;
 +  p->openFlags = 0;
 +  p->szMax = 0;
 +
 +  /* If a filename is specified, try to open it first */
 +  if( zFN || p->openMode==SHELL_OPEN_HEXDB ){
 +    if( newFlag && zFN && !p->bSafeMode ) shellDeleteFile(zFN);
 +    if( p->bSafeMode
 +        && p->openMode!=SHELL_OPEN_HEXDB
 +        && zFN
 +        && strcmp(zFN,":memory:")!=0
 +        ){
 +      *pzErr = shellMPrintf(0,"cannot open database files in safe mode");
 +      return SHELL_FORBIDDEN_OP;
      }
 -    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));
 +    if( zFN ){
 +      zNewFilename = sqlite3_mprintf("%s", zFN);
 +      shell_check_oom(zNewFilename);
      }else{
 -      zColsSpec = 0;
 +      zNewFilename = 0;
      }
 -    if( pzRenamed!=0 ){
 -      if( !hasDupes ) *pzRenamed = 0;
 -      else{
 -        sqlite3_finalize(pStmt);
 -        if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0)
 -            && SQLITE_ROW==sqlite3_step(pStmt) ){
 -          *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 -        }else
 -          *pzRenamed = 0;
 -      }
 +    p->pAuxDb->zDbFilename = zNewFilename;
 +    open_db(p, OPEN_DB_KEEPALIVE);
 +    if( p->db==0 ){
 +      *pzErr = shellMPrintf(0,"Error: cannot open '%s'\n", zNewFilename);
 +      sqlite3_free(zNewFilename);
 +      rc = 1;
 +    }else{
 +      p->pAuxDb->zFreeOnClose = zNewFilename;
      }
 -    sqlite3_finalize(pStmt);
 -    sqlite3_close(*pDb);
 -    *pDb = 0;
 -    return zColsSpec;
    }
 +  if( p->db==0 ){
 +    /* As a fall-back open a TEMP database */
 +    p->pAuxDb->zDbFilename = 0;
 +    open_db(p, 0);
 +  }
 +  return rc;
  }
  
 -/*
 -** 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];
 -#if SHELL_VARIABLE_EXPANSION
 -  int ncLineIn = strlen30(zLine);
 -  u8 bExpVars = SHEXT_VAREXP(p);
 -#endif
 -
 -#ifndef SQLITE_OMIT_VIRTUALTABLE
 -  if( p->expert.pExpert ){
 -    expertFinish(p, 1, 0);
 +DISPATCHABLE_COMMAND( nonce ? 2 2 ){
 +  if( p->zNonce==0 || strcmp(azArg[1],p->zNonce)!=0 ){
 +    raw_printf(STD_ERR, "line %d: incorrect nonce: \"%s\"\n",
-                p->lineno, azArg[1]);
++               p->pInSource->lineno, azArg[1]);
 +    exit(1);
    }
- /*****************
-  * The .parameter command
 +  /* Suspend safe mode for 1 meta-command after this. */
 +  p->bSafeModeFuture = 2;
 +  return 0;
 +}
 +
 +DISPATCHABLE_COMMAND( nullvalue ? 2 2 ){
 +  sqlite3_snprintf(sizeof(p->nullValue), p->nullValue,
 +                   "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]);
 +  return 0;
 +}
 +
- COLLECT_HELP_TEXT[
-   ".parameter CMD ...       Manage SQL parameter bindings",
-   "   clear                   Erase all bindings",
-   "   init                    Initialize the TEMP table that holds bindings",
-   "   list                    List the current parameter bindings",
-   "   set PARAMETER VALUE     Given SQL parameter PARAMETER a value of VALUE",
-   "                           PARAMETER should start with one of: $ : @ ?",
-   "   unset PARAMETER         Remove PARAMETER from the binding table",
- ];
- DISPATCHABLE_COMMAND( parameter 4 2 4 ){
-   int rc = 0;
-   open_db(p,0);
-   /* .parameter clear
-   ** Clear all bind parameters by dropping the TEMP table that holds them.
-   */
-   if( nArg==2 && strcmp(azArg[1],"clear")==0 ){
-     sqlite3_exec(p->db, "DROP TABLE IF EXISTS temp.sqlite_parameters;",
-                  0, 0, 0);
-   }else
-   /* .parameter list
-   ** List all bind parameters.
-   */
-   if( nArg==2 && strcmp(azArg[1],"list")==0 ){
-     sqlite3_stmt *pStmt = 0;
-     int rx;
-     int len = 0;
-     rx = sqlite3_prepare_v2(p->db,
-                             "SELECT max(length(key)) "
-                             "FROM temp.sqlite_parameters;", -1, &pStmt, 0);
-     if( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
-       len = sqlite3_column_int(pStmt, 0);
-       if( len>40 ) len = 40;
-     }
-     sqlite3_finalize(pStmt);
-     pStmt = 0;
-     if( len ){
-       rx = sqlite3_prepare_v2(p->db,
-                               "SELECT key, quote(value) "
-                               "FROM temp.sqlite_parameters;", -1, &pStmt, 0);
-       while( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
-         utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
-                     sqlite3_column_text(pStmt,1));
-       }
-       sqlite3_finalize(pStmt);
-     }
-   }else
-   /* .parameter init
-   ** Make sure the TEMP table used to hold bind parameters exists.
-   ** Create it if necessary.
-   */
-   if( nArg==2 && strcmp(azArg[1],"init")==0 ){
-     bind_table_init(p);
-   }else
++/* Helper functions for .parameter command
 + */
-   /* .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 && strcmp(azArg[1],"set")==0 ){
-     int rx;
-     char *zSql;
-     sqlite3_stmt *pStmt;
-     const char *zKey = azArg[2];
-     const char *zValue = azArg[3];
-     bind_table_init(p);
-     zSql = sqlite3_mprintf(
-                 "REPLACE INTO temp.sqlite_parameters(key,value)"
-                 "VALUES(%Q,%s);", zKey, zValue);
-     shell_check_oom(zSql);
-     pStmt = 0;
-     rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
-     sqlite3_free(zSql);
-     if( rx!=SQLITE_OK ){
-       sqlite3_finalize(pStmt);
-       pStmt = 0;
-       zSql = sqlite3_mprintf(
-                  "REPLACE INTO temp.sqlite_parameters(key,value)"
-                  "VALUES(%Q,%Q);", zKey, zValue);
-       shell_check_oom(zSql);
-       rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
-       sqlite3_free(zSql);
-       if( rx!=SQLITE_OK ){
-         utf8_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db));
-         sqlite3_finalize(pStmt);
-         pStmt = 0;
-         rc = 1;
-       }
-     }
-     sqlite3_step(pStmt);
-     sqlite3_finalize(pStmt);
-   }else
 +
-   /* .parameter unset NAME
-   ** Remove the NAME binding from the parameter binding table, if it
-   ** exists.
-   */
-   if( nArg==3 && strcmp(azArg[1],"unset")==0 ){
-     char *zSql = sqlite3_mprintf(
-         "DELETE FROM temp.sqlite_parameters WHERE key=%Q", azArg[2]);
-     if( zSql==0 ) shell_out_of_memory();
-     sqlite3_exec(p->db, zSql, 0, 0, 0);
-     sqlite3_free(zSql);
-   }else
++struct param_row { char * value; int uses; int hits; };
++
++static int param_find_callback(void *pData, int nc, char **pV, char **pC){
++  assert(nc>=1);
++  assert(strcmp(pC[0],"value")==0);
++  struct param_row *pParam = (struct param_row *)pData;
++  assert(pParam->value==0); /* key values are supposedly unique. */
++  if( pParam->value!=0 ) sqlite3_free( pParam->value );
++  pParam->value = sqlite3_mprintf("%s", pV[0]); /* source owned by statement */
++  if( nc>1 ) pParam->uses = (int)integerValue(pV[1]);
++  ++pParam->hits;
++  return 0;
++}
 +
-   {  /* If no command name and arg count matches, show a syntax error */
-     showHelp(p->out, "parameter");
-     return 1;
++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);
 +
-   return rc;
++/* Create a home-relative pathname from ~ prefixed path.
++ * Return it, or 0 for any error.
++ * Caller must sqlite3_free() it.
++ */
++static char *home_based_path( const char *zPath ){
++  char *zHome = find_home_dir(0);
++  char *zErr = 0;
++  assert( zPath[0]=='~' );
++  if( zHome==0 ){
++    zErr = "Cannot find home directory.";
++  }else if( zPath[0]==0 || (zPath[1]!='/'
++#if defined(_WIN32) || defined(WIN32)
++                            && zPath[1]!='\\'
+ #endif
++                            ) ){
++    zErr = "Malformed pathname";
++  }else{
++    return sqlite3_mprintf("%s%s", zHome, zPath+1);
 +  }
++  utf8_printf(STD_ERR, "Error: %s\n", zErr);
++  return 0;
 +}
  
- /*****************
-  * The .print, .progress and .prompt commands
 -  /* 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;
++/* 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.
 + */
- CONDITION_COMMAND( progress !defined(SQLITE_OMIT_PROGRESS_CALLBACK) );
++static int param_xfr_table(sqlite3 *db, const char *zStoreDbName,
++                           int bSaveNotLoad, const char *azNames[], int nNames){
++  int rc = 0;
++  char *zSql = 0; /* to be sqlite3_free()'ed */
++  sqlite3_str *sbCopy = 0;
++  const char *zHere = PARAM_TABLE_SNAME;
++  const char *zThere = PARAM_STORE_SNAME;
++  const char *zTo = (bSaveNotLoad)? zThere : zHere;
++  const char *zFrom = (bSaveNotLoad)? zHere : zThere;
++  sqlite3 *dbStore = 0;
++  int openFlags = (bSaveNotLoad)
++    ? SQLITE_OPEN_URI|SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE
++    : SQLITE_OPEN_READONLY;
++
++  /* 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 parameter store DB %s\n",
++                bSaveNotLoad? "open/create" : "read", zStoreDbName);
++    return rc;
++  }
++  /* Ensure it has the parameter store table, or handle its absence. */
++  assert(dbStore!=0);
++  if( sqlite3_table_column_metadata
++      (dbStore, "main", PARAM_STORE_NAME, 0, 0, 0, 0, 0, 0)!=SQLITE_OK ){
++    if( !bSaveNotLoad ){
++      utf8_printf(STD_ERR, "Error: No parameters 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 "PARAM_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);
+       }
 -      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]);
+     }
+   }
 -  azArg[nArg] = 0;
++  sqlite3_close(dbStore);
++  if( rc!=0 ) return rc;
 -  /* Process the input line.
 -  */
 -  if( nArg==0 ) return 0; /* no tokens, no error */
 -  n = strlen30(azArg[0]);
 -  c = azArg[0][0];
 -  clearTempFile(p);
++  zSql = sqlite3_mprintf("ATTACH %Q AS %s;", zStoreDbName, PARAM_STORE_SCHEMA);
++  shell_check_oom(zSql);
++  rc = sqlite3_exec(db, zSql, 0, 0, 0);
++  sqlite3_free(zSql);
++  if( rc!=SQLITE_OK ) return rc;
 -#ifndef SQLITE_OMIT_AUTHORIZATION
 -  if( c=='a' && 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
++  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 !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
 -  if( c=='a' && 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
++  sqlite3_exec(db, "DETACH "PARAM_STORE_SCHEMA";", 0, 0, 0);
++  return rc;
++}
 -  if( (c=='b' && n>=3 && strncmp(azArg[0], "backup", n)==0)
 -   || (c=='s' && n>=3 && 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( strcmp(z, "-append")==0 ){
 -          zVfs = "apndvfs";
 -        }else
 -        if( strcmp(z, "-async")==0 ){
 -          bAsync = 1;
 -        }else
 -        {
 -          utf8_printf(stderr, "unknown option: %s\n", azArg[j]);
 -          return 1;
 -        }
 -      }else if( zDestFile==0 ){
 -        zDestFile = azArg[j];
 -      }else if( zDb==0 ){
 -        zDb = zDestFile;
 -        zDestFile = azArg[j];
 -      }else{
 -        raw_printf(stderr, "Usage: .backup ?DB? ?OPTIONS? FILENAME\n");
 -        return 1;
 -      }
 -    }
 -    if( zDestFile==0 ){
 -      raw_printf(stderr, "missing FILENAME argument on .backup\n");
 -      return 1;
 -    }
 -    if( zDb==0 ) zDb = "main";
 -    rc = sqlite3_open_v2(zDestFile, &pDest,
 -                  SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs);
 -    if( rc!=SQLITE_OK ){
 -      utf8_printf(stderr, "Error: cannot open \"%s\"\n", zDestFile);
 -      close_db(pDest);
 -      return 1;
 -    }
 -    if( bAsync ){
 -      sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
 -                   0, 0, 0);
 -    }
 -    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
++/* Default location of parameters store DB for .parameters save/load. */
++static const char *zDefaultParamStore = "~/sqlite_params.sdb";
 -  if( c=='b' && n>=3 && 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;
 -    }
 -  }else
++/* 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 *params_store_path(const char *zSpec){
++  if( zSpec==0 || zSpec[0]==0 || strcmp(zSpec,"~")==0 ){
++    return home_based_path(zDefaultParamStore);
++  }else if ( zSpec[0]=='~' ){
++    return home_based_path(zSpec);
++  }
++  return zSpec;
++}
 -  if( c=='b' && n>=3 && 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
++/* Load some or all parameters. Arguments are "load FILE ?NAMES?". */
++static int parameters_load(sqlite3 *db, const char *azArg[], int nArg){
++  const char *zStore = params_store_path((nArg>1)? azArg[1] : 0);
++  if( zStore==0 ){
++    utf8_printf(STD_ERR, "Cannot form parameter load path. Nothing loaded.\n");
++    return 1;
++  }else{
++    const char **pzFirst = (nArg>2)? azArg+2 : 0;
++    int nNames = (nArg>2)? nArg-2 : 0;
++    int rc = param_xfr_table(db, zStore, 0, pzFirst, nNames);
++    if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore);
++    return rc;
++  }
++}
 -  /* The undocumented ".breakpoint" command causes a call to the no-op
 -  ** routine named test_breakpoint().
 -  */
 -  if( c=='b' && n>=3 && strncmp(azArg[0], "breakpoint", n)==0 ){
 -    test_breakpoint();
 -  }else
++/* Save some or all parameters. Arguments are "save FILE ?NAMES?". */
++static int parameters_save(sqlite3 *db, const char *azArg[], int nArg){
++  const char *zStore = params_store_path((nArg>1)? azArg[1] : 0);
++  if( zStore==0 ){
++    utf8_printf(STD_ERR, "Cannot form parameter save path. Nothing saved.\n");
++    return 1;
++  }else{
++    const char **pzFirst = (nArg>2)? azArg+2 : 0;
++    int nNames = (nArg>2)? nArg-2 : 0;
++    int rc = param_xfr_table(db, zStore, 1, pzFirst, nNames);
++    if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore);
++    return rc;
++  }
++}
 -  if( c=='c' && strcmp(azArg[0],"cd")==0 ){
 -    failIfSafeMode(p, "cannot run .cd in safe mode");
 -    if( nArg==2 ){
 -#if defined(_WIN32) || defined(WIN32)
 -      wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
 -      rc = !SetCurrentDirectoryW(z);
 -      sqlite3_free(z);
 -#else
 -      rc = chdir(azArg[1]);
 -#endif
 -      if( rc ){
 -        utf8_printf(stderr, "Cannot change to directory \"%s\"\n", azArg[1]);
 -        rc = 1;
 -      }
++#ifndef SQLITE_NOHAVE_SYSTEM
++/*
++ * Edit one named parameter in the parameters 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 parameters table, and may also serve
++ * to partition the key namespace. (This is not done now.)
++ */
++static int edit_one_param(sqlite3 *db, char *name, int eval,
++                          ParamTableUse uses, const char * zEditor){
++  struct param_row paramVU = {0,0,0};
++  int rc;
++  char * zVal = 0;
++  char * zSql = sqlite3_mprintf
++    ("SELECT value, uses FROM " PARAM_TABLE_SNAME " WHERE key=%Q", name);
++  shell_check_oom(zSql);
++  sqlite3_exec(db, zSql, param_find_callback, &paramVU, 0);
++  sqlite3_free(zSql);
++  assert(paramVU.hits<2);
++  if( paramVU.hits==1 && paramVU.uses==uses){
++    /* Editing an existing value of same kind. */
++    sqlite3_free(paramVU.value);
++    if( eval!=0 ){
++      zSql = sqlite3_mprintf
++        ("SELECT edit(value, %Q) FROM " PARAM_TABLE_SNAME
++         " WHERE key=%Q AND uses=%d", zEditor, name, uses);
++      zVal = db_text(db, zSql, 1);
++      sqlite3_free(zSql);
++      zSql = sqlite3_mprintf
++        ("UPDATE "PARAM_TABLE_SNAME" SET value=(SELECT %s) WHERE"
++         " key=%Q AND uses=%d", zVal, name, uses);
+     }else{
 -      raw_printf(stderr, "Usage: .cd DIRECTORY\n");
 -      rc = 1;
++      zSql = sqlite3_mprintf
++        ("UPDATE "PARAM_TABLE_SNAME" SET value=edit(value, %Q) WHERE"
++         " key=%Q AND uses=%d", zEditor, name, uses);
+     }
 -  }else
 -
 -  if( c=='c' && n>=3 && strncmp(azArg[0], "changes", n)==0 ){
 -    if( nArg==2 ){
 -      setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
++  }else{
++    /* Editing a new value of same kind. */
++    assert(paramVU.value==0 || paramVU.uses!=uses);
++    if( eval!=0 ){
++      zSql = sqlite3_mprintf
++        ("SELECT edit('-- %q%s', %Q)", name, "\n", zEditor);
++      zVal = db_text(db, zSql, 1);
++      sqlite3_free(zSql);
++      zSql = sqlite3_mprintf
++        ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)"
++         " VALUES (%Q,(SELECT %s LIMIT 1),%d)",
++         name, zVal, uses);
+     }else{
 -      raw_printf(stderr, "Usage: .changes on|off\n");
 -      rc = 1;
++      zSql = sqlite3_mprintf
++        ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)"
++         " VALUES (%Q,edit('-- %q%s', %Q),%d)",
++         name, name, "\n", zEditor, uses);
+     }
 -  }else
++  }
++  shell_check_oom(zSql);
++  if( eval!=0 ){
++  }
++  rc = sqlite3_exec(db, zSql, 0, 0, 0);
++  sqlite3_free(zSql);
++  sqlite3_free(zVal);
++  return rc!=SQLITE_OK;
++}
++#endif
 -  /* 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 && 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 ){
 -      raw_printf(stderr, "Error: cannot read 'testcase-out.txt'\n");
 -      rc = 2;
 -    }else if( testcase_glob(azArg[1],zRes)==0 ){
 -      utf8_printf(stderr,
 -                 "testcase-%s FAILED\n Expected: [%s]\n      Got: [%s]\n",
 -                 p->zTestcase, azArg[1], zRes);
 -      rc = 1;
 -    }else{
 -      utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase);
 -      p->nCheck++;
 -    }
 -    sqlite3_free(zRes);
 -  }else
++/* 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 = sqlite3_mprintf("%z%s%s", z, zSep, *valBeg);
++    zSep = " ";
++    ++valBeg;
++  }
++  return z;
++}
++
++/* Get a named parameter value in form of stepped prepared statement,
++ * ready to have its value taken from the 0th column. If the name
++ * cannot be found for the given ParamTableUse, 0 is returned.
++ * The caller is responsible for calling sqlite3_finalize(pStmt),
++ * where pStmt is the return from this function.
++ */
++static sqlite3_stmt *get_param_value(sqlite3 *db, char *name,
++                                     ParamTableUse ptu){
++  sqlite3_stmt *rv = 0;
++  int rc;
++  char *zSql = sqlite3_mprintf
++    ( "SELECT value FROM "PARAM_TABLE_SNAME
++      " WHERE key=%Q AND uses=%d", name, ptu );
++  shell_check_oom(zSql);
++  rc = sqlite3_prepare_v2(db, zSql, -1, &rv, 0);
++  sqlite3_free(zSql);
++  if( SQLITE_OK==rc ){
++    if( SQLITE_ROW==sqlite3_step(rv) ) return rv;
++    sqlite3_finalize(rv);
++  }
++  return 0;
++}
++
++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( c=='c' && 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;
 -    }
 -  }else
++/* 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;
++}
 -  if( c=='c' && 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 && 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;
++/* The set subcommand (per help text)
++ */
++static int param_set(sqlite3 *db, char cCast,
++                     char *name, char **valBeg, char **valLim,
++                     ParamTableUse ptu){
++  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;
+       }
 -    }else{
 -      raw_printf(stderr, "Usage: .connection [close] [CONNECTION-NUMBER]\n");
 -      rc = 1;
+     }
 -  }else
 -
 -  if( c=='d' && n>1 && strncmp(azArg[0], "databases", n)==0 ){
 -    char **azName = 0;
 -    int nName = 0;
 -    sqlite3_stmt *pStmt;
 -    int i;
 -    open_db(p, 0);
 -    rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
 -    if( rc ){
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 -      rc = 1;
++  }
++  if( needsEval ){
++    if( zCastTo!=0 ){
++      zSql = sqlite3_mprintf
++        ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
++          " VALUES(%Q,CAST((%s) AS %s),%d);", name, zValue, zCastTo, ptu );
+     }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++;
 -      }
++      zSql = sqlite3_mprintf
++        ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
++          "VALUES(%Q,(%s),%d);", name, zValue, ptu );
+     }
 -    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
++    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 = sqlite3_mprintf
++      ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
++        "VALUES(%Q,%Q,%d);", name, zValue, ptu );
++    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;
++}
 -  if( c=='d' && n>=3 && 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        },
 -        { "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 && 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");
++/* list or ls subcommand for .parameter dot-command */
++static void list_params(ShellState *p, ParamTableUse ptu, u8 bShort,
++                        char **pzArgs, int nArg){
++  sqlite3_stmt *pStmt = 0;
++  sqlite3_str *sbList = sqlite3_str_new(p->db);
++  int len = 0, rc;
++  char *zFromWhere = 0;
++  char *zSql = 0;
++  sqlite3_str_appendf(sbList, "FROM "PARAM_TABLE_SNAME
++                      " WHERE (?1=3 OR 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 = sqlite3_mprintf("SELECT max(length(key)) %s", zFromWhere);
++  shell_check_oom(zSql);
++  rc = sqlite3_prepare_v2(p->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;
+     }
 -  }else
 -
 -  if( c=='d' && n>=3 && strncmp(azArg[0], "dbinfo", n)==0 ){
 -    rc = shell_dbinfo_command(p, nArg, azArg);
 -  }else
 -
 -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
 -  if( c=='r' && strncmp(azArg[0], "recover", n)==0 ){
 -    open_db(p, 0);
 -    rc = recoverDatabaseCmd(p, nArg, azArg);
 -  }else
 -#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
 -
 -  if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){
 -    char *zLike = 0;
 -    char *zSchema = "main";
 -    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( 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);
 -#endif
 -        }else
 -        if( strcmp(z,"newlines")==0 ){
 -          ShellSetFlag(p, SHFLG_Newlines);
 -        }else
 -        if( strcmp(z,"data-only")==0 ){
 -          ShellSetFlag(p, SHFLG_DumpDataOnly);
 -        }else
 -        if( strcmp(z,"nosys")==0 ){
 -          ShellSetFlag(p, SHFLG_DumpNoSys);
 -        }else if( strcmp(z,"schema")==0 && ++i<nArg ){
 -          zSchema = azArg[i];
 -        }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 %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 = sqlite3_mprintf("%z OR %z", zLike, zExpr);
 -        }else{
 -          zLike = zExpr;
++  }
++  sqlite3_finalize(pStmt);
++  pStmt = 0;
++  if( len ){
++    sqlite3_free(zSql);
++    if( !bShort ){
++      int nBindings = 0, nScripts = 0;
++      zSql = sqlite3_mprintf("SELECT key, uses, iif(uses, value, quote(value))"
++                             " %z ORDER BY uses, key", zFromWhere);
++      shell_check_oom(zSql);
++      rc = sqlite3_prepare_v2(p->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(p->out, "Binding Values:\n%-*s %s\n",
++                        len, "name", "value");
++          }
++          utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
++                      sqlite3_column_text(pStmt,2));
++          break;
++        case PTU_Script:
++          if( nScripts++ == 0 ){
++            utf8_printf(p->out, "Scripts\n%-*s %s\n", len, "name", "value");
++          }
++          utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
++                      sqlite3_column_text(pStmt,2));
++          break;
++        default: break; /* Ignore */
+         }
+       }
 -    }
 -
 -    open_db(p, 0);
 -
 -    if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 -      /* When playing back a "dump", the content might appear in an order
 -      ** which causes immediate foreign key constraints to be violated.
 -      ** So disable foreign-key constraint enforcement to prevent problems. */
 -      raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n");
 -      raw_printf(p->out, "BEGIN TRANSACTION;\n");
 -    }
 -    p->writableSchema = 0;
 -    p->showHeader = 0;
 -    /* Set writable_schema=ON since doing so forces SQLite to initialize
 -    ** as much of the schema as it can even if the sqlite_schema table is
 -    ** corrupt. */
 -    sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
 -    p->nErr = 0;
 -    if( zLike==0 ) zLike = sqlite3_mprintf("true");
 -    zSql = sqlite3_mprintf(
 -      "SELECT name, type, sql FROM %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(p,zSql);
 -    sqlite3_free(zSql);
 -    if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 -      zSql = sqlite3_mprintf(
 -        "SELECT sql FROM %w.sqlite_schema AS o "
 -        "WHERE (%s) AND sql NOT NULL"
 -        "  AND type IN ('index','trigger','view')",
 -        zSchema, 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
 -
 -  if( c=='e' && 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
 -
 -  if( c=='e' && 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( strcmp(azArg[1],"full")==0 ){
 -        p->autoEQP = AUTOEQP_full;
 -      }else if( strcmp(azArg[1],"trigger")==0 ){
 -        p->autoEQP = AUTOEQP_trigger;
 -#ifdef SQLITE_DEBUG
 -      }else if( strcmp(azArg[1],"test")==0 ){
 -        p->autoEQP = AUTOEQP_on;
 -        p->autoEQPtest = 1;
 -      }else if( 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]);
++      int nc = 0, ncw = 78/(len+2);
++      zSql = sqlite3_mprintf("SELECT key %z ORDER BY key", zFromWhere);
++      shell_check_oom(zSql);
++      rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
++      sqlite3_bind_int(pStmt, 1, ptu);
++      while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
++        utf8_printf(p->out, "%s  %-*s", ((++nc%ncw==0)? "\n" : ""),
++                    len, sqlite3_column_text(pStmt,0));
+       }
 -    }else{
 -      raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n");
 -      rc = 1;
++      if( nc>0 ) utf8_printf(p->out, "\n");
+     }
 -  }else
 -
 -  if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){
 -    if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc);
 -    rc = 2;
 -  }else
++    sqlite3_finalize(pStmt);
++  }else{
++    sqlite3_free(zFromWhere);
++  }
++  sqlite3_free(zSql);
++}
 -  /* The ".explain" command is automatic now.  It is largely pointless.  It
 -  ** retained purely for backwards compatibility */
 -  if( c=='e' && strncmp(azArg[0], "explain", n)==0 ){
 -    int val = 1;
 -    if( nArg>=2 ){
 -      if( strcmp(azArg[1],"auto")==0 ){
 -        val = 99;
 -      }else{
 -        val =  booleanValue(azArg[1]);
 -      }
++/* 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;
+     }
 -    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;
++    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;
+     }
 -  }else
++    sqlite3_str_appendf(pStr, ")");
++  }
++}
 -#ifndef SQLITE_OMIT_VIRTUALTABLE
 -  if( c=='e' && 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;
 -    }else{
 -      open_db(p, 0);
 -      expertDotCommand(p, azArg, nArg);
 -    }
 -  }else
++/*****************
++ * The .parameter command
++ */
++COLLECT_HELP_TEXT[
++  ".parameter CMD ...       Manage SQL parameter bindings and scripts 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 or -e to use edited value as text or evaluate it first.",
+ #endif
++  "   init                    Initialize TEMP table for bindings and scripts",
++  "   list ?PATTERNS?         List parameters table binding and script 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, or with a letter",
++  "      to be executable; the value is following argument list, space-joined.",
++  "      Option TOPT may be one of {-b -i -n -r -t} to cast 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);
 -  if( c=='f' && strncmp(azArg[0], "filectrl", n)==0 ){
 -    static const struct {
 -       const char *zCtrlName;   /* Name of a test-control option */
 -       int ctrlCode;            /* Integer code for that option */
 -       const char *zUsage;      /* Usage notes */
 -    } aCtrl[] = {
 -      { "chunk_size",     SQLITE_FCNTL_CHUNK_SIZE,      "SIZE"           },
 -      { "data_version",   SQLITE_FCNTL_DATA_VERSION,    ""               },
 -      { "has_moved",      SQLITE_FCNTL_HAS_MOVED,       ""               },
 -      { "lock_timeout",   SQLITE_FCNTL_LOCK_TIMEOUT,    "MILLISEC"       },
 -      { "persist_wal",    SQLITE_FCNTL_PERSIST_WAL,     "[BOOLEAN]"      },
 -   /* { "pragma",         SQLITE_FCNTL_PRAGMA,          "NAME ARG"       },*/
 -      { "psow",       SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]"      },
 -      { "reserve_bytes",  SQLITE_FCNTL_RESERVE_BYTES,   "[N]"            },
 -      { "size_limit",     SQLITE_FCNTL_SIZE_LIMIT,      "[LIMIT]"        },
 -      { "tempfilename",   SQLITE_FCNTL_TEMPFILENAME,    ""               },
 -   /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY,  "COUNT DELAY"    },*/
 -    };
 -    int filectrl = -1;
 -    int iCtrl = -1;
 -    sqlite3_int64 iRes = 0;  /* Integer result to display if rc2==1 */
 -    int isOk = 0;            /* 0: usage  1: %lld  2: no-result */
 -    int n2, i;
 -    const char *zCmd = 0;
 -    const char *zSchema = 0;
 -
 -    open_db(p, 0);
 -    zCmd = nArg>=2 ? azArg[1] : "help";
 -
 -    if( zCmd[0]=='-'
 -     && (strcmp(zCmd,"--schema")==0 || 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];
++  /* .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( strcmp(azArg[1],"clear")==0 || strcmp(azArg[1],"unset")==0 ){
++    if( param_table_exists(p->db) && (nArg>2 || azArg[1][0]=='c') ){
++      sqlite3_str *sbZap = sqlite3_str_new(p->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(p->db, zSql, 0, 0, 0);
++      sqlite3_free(zSql);
+     }
 -
 -    /* The argument can optionally begin with "-" or "--" */
 -    if( zCmd[0]=='-' && zCmd[1] ){
 -      zCmd++;
 -      if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
++  }else
++#ifndef SQLITE_NOHAVE_SYSTEM
++  /* .parameter edit ?NAMES?
++  ** Edit the named parameters. Any that do not exist are created.
++  ** New ones get a uses tag auto-selected by their leading char.
++  */
++  if( strcmp(azArg[1],"edit")==0 ){
++    int ia = 2;
++    int eval = 0;
++    if( !INSOURCE_IS_INTERACTIVE(p->pInSource) ){
++      utf8_printf(STD_ERR, "Error: "
++                  ".parameter edit can only be used interactively.\n");
++      return 1;
+     }
 -
 -    /* --help lists all file-controls */
 -    if( 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);
++    param_table_init(p);
++    if( p->zEditor==0 ){
++      const char *zE = getenv("VISUAL");
++      if( zE!=0 ) p->zEditor = sqlite3_mprintf("%s", zE);
++    }
++    if( nArg>=3 && azArg[2][0]=='-' ){
++      char *zArg = (azArg[2][1]=='-')? azArg[2]+2 : azArg[2]+1;
++      if( strncmp(zArg,"editor=",7)==0 ){
++        sqlite3_free(p->zEditor);
++        /* Accept an initial -editor=? option. */
++        p->zEditor = sqlite3_mprintf("%s", zArg+7);
++        ++ia;
+       }
 -      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( 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;
 -        }
 -      }
++    if( p->zEditor==0 ){
++      utf8_printf(STD_ERR,
++                  "Either set env-var VISUAL to name an"
++                  " editor and restart, or rerun\n "
++                  ".parameter edit with an initial "
++                  "edit option, --editor=EDITOR_COMMAND .\n");
++      return 1;
+     }
 -    if( filectrl<0 ){
 -      utf8_printf(stderr,"Error: unknown file-control: %s\n"
 -                         "Use \".filectrl --help\" for help\n", zCmd);
 -    }else{
 -      switch(filectrl){
 -        case SQLITE_FCNTL_SIZE_LIMIT: {
 -          if( nArg!=2 && nArg!=3 ) break;
 -          iRes = nArg==3 ? integerValue(azArg[2]) : -1;
 -          sqlite3_file_control(p->db, zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes);
 -          isOk = 1;
 -          break;
 -        }
 -        case SQLITE_FCNTL_LOCK_TIMEOUT:
 -        case SQLITE_FCNTL_CHUNK_SIZE: {
 -          int x;
 -          if( nArg!=3 ) break;
 -          x = (int)integerValue(azArg[2]);
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          isOk = 2;
 -          break;
 -        }
 -        case SQLITE_FCNTL_PERSIST_WAL:
 -        case SQLITE_FCNTL_POWERSAFE_OVERWRITE: {
 -          int x;
 -          if( nArg!=2 && nArg!=3 ) break;
 -          x = nArg==3 ? booleanValue(azArg[2]) : -1;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          iRes = x;
 -          isOk = 1;
 -          break;
 -        }
 -        case SQLITE_FCNTL_DATA_VERSION:
 -        case SQLITE_FCNTL_HAS_MOVED: {
 -          int x;
 -          if( nArg!=2 ) break;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          iRes = x;
 -          isOk = 1;
 -          break;
 -        }
 -        case SQLITE_FCNTL_TEMPFILENAME: {
 -          char *z = 0;
 -          if( nArg!=2 ) break;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &z);
 -          if( z ){
 -            utf8_printf(p->out, "%s\n", z);
 -            sqlite3_free(z);
 -          }
 -          isOk = 2;
 -          break;
 -        }
 -        case SQLITE_FCNTL_RESERVE_BYTES: {
 -          int x;
 -          if( nArg>=3 ){
 -            x = atoi(azArg[2]);
 -            sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          }
 -          x = -1;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          utf8_printf(p->out,"%d\n", x);
 -          isOk = 2;
 -          break;
++    /* Future: Allow an option whereby new value can be evaluated
++     * the way that .parameter set ... does.
++     */
++    while( ia < nArg ){
++      ParamTableUse ptu;
++      char cf = (azArg[ia][0]=='-')? azArg[ia][1] : 0;
++      if( cf!=0 && azArg[ia][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",
++                      azArg[--ia]);
++          return 1;
+         }
+       }
 -    }
 -    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(azArg[ia]);
++      if( ptu==PTU_Nil ){
++        utf8_printf(STD_ERR, "Error: %s cannot be a binding or executable"
++                    " parameter name.\n", azArg[ia]);
++        return 1;
++      }
++      rc = edit_one_param(p->db, azArg[ia], eval, ptu, p->zEditor);
++      ++ia;
++      if( rc!=0 ) return rc;
+     }
+   }else
++#endif
 -  if( c=='f' && 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 && strcmp(azArg[1],"init")==0 ){
++    param_table_init(p);
+   }else
 -  if( c=='h' && 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 && ((strcmp(azArg[1],"list")==0)
++                  || (strcmp(azArg[1],"ls")==0)) ){
++    list_params(p, PTU_Nil, azArg[1][1]=='s', azArg+2, nArg-2);
+   }else
 -  if( c=='h' && strncmp(azArg[0], "help", n)==0 ){
 -    if( nArg>=2 ){
 -      if( nArg==3
 -          && strcmp(azArg[1], zHelpAll)==0 && strcmp(azArg[2], zHelpAll)==0 ){
 -        /* Show the undocumented command help */
 -        n = showHelp(p->out, zHelpAll);
 -      }else{
 -        /* Show such help as the pattern selects */
 -        n = showHelp(p->out, azArg[1]);
 -      }
 -      if( n==0 ){
 -        utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]);
 -      }
 -    }else{
 -      /* Show one-line summaries */
 -      showHelp(p->out, 0);
 -    }
++  /* .parameter load
++  ** Load all or named parameters from specified or default (DB) file.
++  */
++  if( strcmp(azArg[1],"load")==0 ){
++    param_table_init(p);
++    rc = parameters_load(p->db, (const char **)azArg+1, nArg-1);
+   }else
 -  if( c=='i' && strncmp(azArg[0], "import", n)==0 ){
 -    char *zTable = 0;           /* Insert data into this table */
 -    char *zSchema = "main";     /* within this schema */
 -    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 */
 -    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 */
 -
 -    failIfSafeMode(p, "cannot run .import in safe mode");
 -    memset(&sCtx, 0, sizeof(sCtx));
 -    sCtx.z = sqlite3_malloc64(120);
 -    if( sCtx.z==0 ){
 -      import_cleanup(&sCtx);
 -      shell_out_of_memory();
 -    }
 -    if( p->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{
 -          utf8_printf(p->out, "ERROR: extra argument: \"%s\".  Usage:\n", z);
 -          showHelp(p->out, "import");
 -          rc = 1;
 -          goto meta_command_exit;
 -        }
 -      }else if( strcmp(z,"-v")==0 ){
 -        eVerbose++;
 -      }else if( strcmp(z,"-schema")==0 && i<nArg-1 ){
 -        zSchema = azArg[++i];
 -      }else if( strcmp(z,"-skip")==0 && i<nArg-1 ){
 -        nSkip = integerValue(azArg[++i]);
 -      }else if( strcmp(z,"-ascii")==0 ){
 -        sCtx.cColSep = SEP_Unit[0];
 -        sCtx.cRowSep = SEP_Record[0];
 -        xRead = ascii_read_one_field;
 -        useOutputMode = 0;
 -      }else if( 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");
 -        rc = 1;
 -        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");
 -      rc = 1;
 -      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");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      if( nSep>1 ){
 -        raw_printf(stderr,
 -              "Error: multi-character column separators not allowed"
 -              " for import\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      nSep = strlen30(p->rowSeparator);
 -      if( nSep==0 ){
 -        raw_printf(stderr,
 -            "Error: non-null row separator required for import\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      if( nSep==2 && p->mode==MODE_Csv && 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");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      sCtx.cColSep = p->colSeparator[0];
 -      sCtx.cRowSep = 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");
 -      rc = 1;
 -      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);
 -      rc = 1;
 -      import_cleanup(&sCtx);
 -      goto meta_command_exit;
 -    }
 -    /* 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(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");
 -    }
 -    while( (nSkip--)>0 ){
 -      while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
 -    }
 -    zSql = sqlite3_mprintf("SELECT * FROM \"%w\".\"%w\"", zSchema, zTable);
 -    if( zSql==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 ){
 -      char *zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"",
 -                                      zSchema, zTable);
 -      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 ){
 -        utf8_printf(INSOURCE_IS_INTERACTIVE(p->pInSource)? 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 ){
 -        sqlite3_free(zCreate);
 -        import_cleanup(&sCtx);
 -        utf8_printf(stderr,"%s: empty file\n", sCtx.zFile);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs);
 -      if( eVerbose>=1 ){
 -        utf8_printf(p->out, "%s\n", zCreate);
 -      }
 -      rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
 -      if( rc ){
 -        utf8_printf(stderr, "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
 -        sqlite3_free(zCreate);
 -        import_cleanup(&sCtx);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      sqlite3_free(zCreate);
 -      rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    }
 -    sqlite3_free(zSql);
 -    if( rc ){
 -      if (pStmt) sqlite3_finalize(pStmt);
 -      utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db));
 -      import_cleanup(&sCtx);
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    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 \"%w\".\"%w\" VALUES(?",
 -                     zSchema, zTable);
 -    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);
 -    sqlite3_free(zSql);
 -    if( rc ){
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 -      if (pStmt) sqlite3_finalize(pStmt);
 -      import_cleanup(&sCtx);
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    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( sCtx.cTerm==sCtx.cColSep ){
 -        do{
 -          xRead(&sCtx);
 -          i++;
 -        }while( sCtx.cTerm==sCtx.cColSep );
 -        utf8_printf(stderr, "%s:%d: expected %d columns but found %d - "
 -                        "extras ignored\n",
 -                        sCtx.zFile, startLine, nCol, i);
 -      }
 -      if( i>=nCol ){
 -        sqlite3_step(pStmt);
 -        rc = sqlite3_reset(pStmt);
 -        if( rc!=SQLITE_OK ){
 -          utf8_printf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
 -                      startLine, sqlite3_errmsg(p->db));
 -          sCtx.nErr++;
 -        }else{
 -          sCtx.nRow++;
 -        }
 -      }
 -    }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);
 -    }
++  /* .parameter save
++  ** Save all or named parameters into specified or default (DB) file.
++  */
++  if( strcmp(azArg[1],"save")==0 ){
++    rc = parameters_save(p->db, (const char **)azArg+1, nArg-1);
+   }else
 -#ifndef SQLITE_UNTESTABLE
 -  if( c=='i' && 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( !(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);
 -      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);
 -      }
 -    }
 -    sqlite3_finalize(pStmt);
 -    if( i==0 || tnum==0 ){
 -      utf8_printf(stderr, "no such index: \"%s\"\n", azArg[1]);
 -      rc = 1;
 -      sqlite3_free(zCollist);
 -      goto meta_command_exit;
 -    }
 -    if( lenPK==0 ) lenPK = 100000;
 -    zSql = sqlite3_mprintf(
 -          "CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))WITHOUT ROWID",
 -          azArg[2], zCollist, lenPK, zCollist);
 -    sqlite3_free(zCollist);
 -    rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 1, tnum);
 -    if( rc==SQLITE_OK ){
 -      rc = sqlite3_exec(p->db, zSql, 0, 0, 0);
 -      sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0);
 -      if( rc ){
 -        utf8_printf(stderr, "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db));
 -      }else{
 -        utf8_printf(stdout, "%s;\n", zSql);
 -        raw_printf(stdout,
 -          "WARNING: writing to an imposter table will corrupt the \"%s\" %s!\n",
 -          azArg[1], isWO ? "table" : "index"
 -        );
 -      }
 -    }else{
 -      raw_printf(stderr, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
++  /* .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 && 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_Nil ){
++      utf8_printf(STD_ERR,
++                  "Error: %s is not a usable parameter name.\n", azArg[inv]);
+       rc = 1;
 -    }
 -    sqlite3_free(zSql);
 -  }else
 -#endif /* !defined(SQLITE_OMIT_TEST_CONTROL) */
 -
 -#ifdef SQLITE_ENABLE_IOTRACE
 -  if( c=='i' && 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( strcmp(azArg[1], "-")==0 ){
 -      sqlite3IoTrace = iotracePrintf;
 -      iotrace = stdout;
+     }else{
 -      iotrace = fopen(azArg[1], "w");
 -      if( iotrace==0 ){
 -        utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]);
 -        sqlite3IoTrace = 0;
++      param_table_init(p);
++      rc = param_set(p->db, cCast, azArg[inv],
++                     &azArg[inv+1], &azArg[nArg], ptu);
++      if( rc!=SQLITE_OK ){
++        utf8_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db));
+         rc = 1;
 -      }else{
 -        sqlite3IoTrace = iotracePrintf;
+       }
+     }
+   }else
 -#endif
 -  if( c=='l' && n>=5 && 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));
++  {  /* If no command name and arg count matches, show a syntax error */
++    showHelp(p->out, "parameter");
++    return 1;
++  }
++
++  return rc;
++}
++
++/*****************
++ * 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++){
 +    if( i>1 ) raw_printf(p->out, " ");
 +    utf8_printf(p->out, "%s", azArg[i]);
 +  }
 +  raw_printf(p->out, "\n");
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( progress 3 2 0 ){
 +  int i;
 +  int nn = 0;
 +  p->flgProgress = 0;
 +  p->mxProgress = 0;
 +  p->nProgress = 0;
 +  for(i=1; i<nArg; i++){
 +    const char *z = azArg[i];
 +    if( z[0]=='-' ){
 +      z++;
 +      if( z[0]=='-' ) z++;
 +      if( strcmp(z,"quiet")==0 || strcmp(z,"q")==0 ){
 +        p->flgProgress |= SHELL_PROGRESS_QUIET;
 +        continue;
        }
 -    }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( strcmp(z,"reset")==0 ){
 +        p->flgProgress |= SHELL_PROGRESS_RESET;
 +        continue;
        }
 -      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;
 +      if( strcmp(z,"once")==0 ){
 +        p->flgProgress |= SHELL_PROGRESS_ONCE;
 +        continue;
        }
 -      if( nArg==3 ){
 -        sqlite3_limit(p->db, aLimit[iLimit].limitCode,
 -                      (int)integerValue(azArg[2]));
 +      if( strcmp(z,"limit")==0 ){
 +        if( i+1>=nArg ){
 +          *pzErr = shellMPrintf(0,"Error: missing argument on --limit\n");
 +          return SHELL_INVALID_ARGS;
 +        }else{
 +          p->mxProgress = (int)integerValue(azArg[++i]);
 +        }
 +        continue;
        }
 -      printf("%20s %d\n", aLimit[iLimit].zLimitName,
 -             sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1));
 +      *pzErr = shellMPrintf(0,"Error: unknown option: \"%s\"\n", azArg[i]);
 +      return SHELL_INVALID_ARGS;
 +    }else{
 +      nn = (int)integerValue(z);
      }
 -  }else
 -
 -  if( c=='l' && n>2 && strncmp(azArg[0], "lint", n)==0 ){
 -    open_db(p, 0);
 -    lintDotCommand(p, azArg, nArg);
 -  }else
 +  }
 +  open_db(p, 0);
 +  sqlite3_progress_handler(p->db, nn, progress_handler, p);
 +  return 0;
 +}
 +/* Allow too few arguments by tradition, (a form of no-op.) */
 +DISPATCHABLE_COMMAND( prompt ? 1 3 ){
 +  if( nArg >= 2) {
 +    strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1);
 +  }
 +  if( nArg >= 3) {
 +    strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1);
 +  }
 +  return 0;
 +}
  
 -#ifndef SQLITE_OMIT_LOAD_EXTENSION
 -  if( c=='l' && strncmp(azArg[0], "load", n)==0 ){
 -    const char *zFile, *zProc;
 -    char *zErrMsg = 0;
 -    failIfSafeMode(p, "cannot run .load in safe mode");
 -    if( nArg<2 ){
 -      raw_printf(stderr, "Usage: .load FILE ?ENTRYPOINT?\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    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);
 +/*****************
 + * The .read, .recover and .restore commands
 + */
 +CONDITION_COMMAND( recover !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) );
 +COLLECT_HELP_TEXT[
 +  ".read FILE               Read input from FILE",
 +  "   If FILE begins with \"|\", it is a command that generates the input.",
 +  ".recover                 Recover as much data as possible from corrupt db.",
 +  "   --freelist-corrupt       Assume the freelist is corrupt",
 +  "   --recovery-db NAME       Store recovery metadata in database file NAME",
 +  "   --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",
 +];
 +DISPATCHABLE_COMMAND( read 3 2 2 ){
 +  int rc = 0;
-   FILE *inSaved = p->in;
-   int savedLineno = p->lineno;
++  FILE *inUse = 0;
++  int (*fCloser)(FILE *) = 0;
 +  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
 +  if( azArg[1][0]=='|' ){
 +#ifdef SQLITE_OMIT_POPEN
 +    *pzErr = shellMPrintf(0,"Error: pipes are not supported in this OS\n");
 +    rc = 1;
-     p->out = STD_OUT;
++    p->out = STD_OUT; /* This is likely not needed. To be investigated. */
 +#else
-     p->in = popen(azArg[1]+1, "r");
-     if( p->in==0 ){
-       *pzErr = shellMPrintf(0,"Error: cannot open \"%s\"\n", azArg[1]);
++    inUse = popen(azArg[1]+1, "r");
++    if( inUse==0 ){
++      utf8_printf(STD_ERR, "Error: cannot open \"%s\"\n", azArg[1]);
        rc = 1;
-       rc = process_input(p);
-       pclose(p->in);
 +    }else{
++      fCloser = pclose;
      }
 -  }else
  #endif
-   }else if( (p->in = openChrSource(azArg[1]))==0 ){
++  }else if( (inUse = openChrSource(azArg[1]))==0 ){
 +    *pzErr = shellMPrintf(0,"Error: cannot open \"%s\"\n", azArg[1]);
 +    rc = 1;
 +  }else{
++    fCloser = fclose;
++  }
++  if( inUse!=0 ){
++    InSource inSourceRedir
++      = INSOURCE_FILE_REDIR(inUse, azArg[1], p->pInSource);
++    p->pInSource = &inSourceRedir;
 +    rc = process_input(p);
-     fclose(p->in);
++    assert(fCloser!=0);
++    fCloser(inUse);
++    p->pInSource = p->pInSource->pFrom;
 +  }
-   p->in = inSaved;
-   p->lineno = savedLineno;
 +  return rc;
 +}
  
 -  if( c=='l' && strncmp(azArg[0], "log", n)==0 ){
 -    failIfSafeMode(p, "cannot run .log in safe mode");
 -    if( nArg!=2 ){
 -      raw_printf(stderr, "Usage: .log FILENAME\n");
 -      rc = 1;
 -    }else{
 -      const char *zFile = azArg[1];
 -      output_file_close(p->pLog);
 -      p->pLog = output_file_open(zFile, 0);
 -    }
 -  }else
 +/*
 +** 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 ){
 +  open_db(p, 0);
 +  int rc = SQLITE_OK;
 +  sqlite3_stmt *pLoop = 0;        /* Loop through all root pages */
 +  sqlite3_stmt *pPages = 0;       /* Loop through all pages in a group */
 +  sqlite3_stmt *pCells = 0;       /* Loop through all cells in a page */
 +  const char *zRecoveryDb = "";   /* Name of "recovery" database */
 +  const char *zLostAndFound = "lost_and_found";
 +  int i;
 +  int nOrphan = -1;
 +  RecoverTable *pOrphan = 0;
  
 -  if( c=='m' && strncmp(azArg[0], "mode", n)==0 ){
 -    const char *zMode = 0;
 -    const char *zTabname = 0;
 -    int i, n2;
 -    ColModeOpts cmOpts = ColModeOpts_default;
 -    for(i=1; i<nArg; i++){
 -      const char *z = azArg[i];
 -      if( optionMatch(z,"wrap") && i+1<nArg ){
 -        cmOpts.iWrap = integerValue(azArg[++i]);
 -      }else if( optionMatch(z,"ww") ){
 -        cmOpts.bWordWrap = 1;
 -      }else if( optionMatch(z,"wordwrap") && i+1<nArg ){
 -        cmOpts.bWordWrap = (u8)booleanValue(azArg[++i]);
 -      }else if( optionMatch(z,"quote") ){
 -        cmOpts.bQuote = 1;
 -      }else if( optionMatch(z,"noquote") ){
 -        cmOpts.bQuote = 0;
 -      }else if( zMode==0 ){
 -        zMode = z;
 -        /* Apply defaults for qbox pseudo-mods. If that
 -         * overwrites already-set values, user was informed of this.
 -         */
 -        if( strcmp(z, "qbox")==0 ){
 -          ColModeOpts cmo = ColModeOpts_default_qbox;
 -          zMode = "box";
 -          cmOpts = cmo;
 -        }
 -      }else if( zTabname==0 ){
 -        zTabname = z;
 -      }else if( z[0]=='-' ){
 -        utf8_printf(stderr, "unknown option: %s\n", z);
 -        utf8_printf(stderr, "options:\n"
 -                            "  --noquote\n"
 -                            "  --quote\n"
 -                            "  --wordwrap on/off\n"
 -                            "  --wrap N\n"
 -                            "  --ww\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }else{
 -        utf8_printf(stderr, "extra argument: \"%s\"\n", z);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -    }
 -    if( zMode==0 ){
 -      if( p->mode==MODE_Column
 -       || (p->mode>=MODE_Markdown && p->mode<=MODE_Box)
 -      ){
 -        raw_printf
 -          (p->out,
 -           "current output mode: %s --wrap %d --wordwrap %s --%squote\n",
 -           modeDescr[p->mode], p->cmOpts.iWrap,
 -           p->cmOpts.bWordWrap ? "on" : "off",
 -           p->cmOpts.bQuote ? "" : "no");
 -      }else{
 -        raw_printf(p->out, "current output mode: %s\n", modeDescr[p->mode]);
 -      }
 -      zMode = modeDescr[p->mode];
 +  int bFreelist = 1;              /* 0 if --freelist-corrupt is specified */
 +  int bRowids = 1;                /* 0 if --no-rowids */
 +  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("-freelist-corrupt", z, n)==0 ){
 +      bFreelist = 0;
 +    }else
 +    if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
 +      i++;
 +      zRecoveryDb = azArg[i];
 +    }else
 +    if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
 +      i++;
 +      zLostAndFound = azArg[i];
 +    }else
 +    if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
 +      bRowids = 0;
      }
 -    n2 = strlen30(zMode);
 -    if( strncmp(zMode,"lines",n2)==0 ){
 -      p->mode = MODE_Line;
 -      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 -    }else if( strncmp(zMode,"columns",n2)==0 ){
 -      p->mode = MODE_Column;
 -      if( (p->shellFlgs & SHFLG_HeaderSet)==0 ){
 -        p->showHeader = 1;
 -      }
 -      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 -      p->cmOpts = cmOpts;
 -    }else if( strncmp(zMode,"list",n2)==0 ){
 -      p->mode = MODE_List;
 -      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Column);
 -      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 -    }else if( strncmp(zMode,"html",n2)==0 ){
 -      p->mode = MODE_Html;
 -    }else if( strncmp(zMode,"tcl",n2)==0 ){
 -      p->mode = MODE_Tcl;
 -      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Space);
 -      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 -    }else if( strncmp(zMode,"csv",n2)==0 ){
 -      p->mode = MODE_Csv;
 -      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
 -      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf);
 -    }else if( strncmp(zMode,"tabs",n2)==0 ){
 -      p->mode = MODE_List;
 -      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Tab);
 -    }else if( strncmp(zMode,"insert",n2)==0 ){
 -      p->mode = MODE_Insert;
 -      set_table_name(p, zTabname ? zTabname : "table");
 -    }else if( strncmp(zMode,"quote",n2)==0 ){
 -      p->mode = MODE_Quote;
 -      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
 -      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 -    }else if( strncmp(zMode,"ascii",n2)==0 ){
 -      p->mode = MODE_Ascii;
 -      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Unit);
 -      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Record);
 -    }else if( strncmp(zMode,"markdown",n2)==0 ){
 -      p->mode = MODE_Markdown;
 -      p->cmOpts = cmOpts;
 -    }else if( strncmp(zMode,"table",n2)==0 ){
 -      p->mode = MODE_Table;
 -      p->cmOpts = cmOpts;
 -    }else if( strncmp(zMode,"box",n2)==0 ){
 -      p->mode = MODE_Box;
 -      p->cmOpts = cmOpts;
 -    }else if( strncmp(zMode,"count",n2)==0 ){
 -      p->mode = MODE_Count;
 -    }else if( strncmp(zMode,"off",n2)==0 ){
 -      p->mode = MODE_Off;
 -    }else if( strncmp(zMode,"json",n2)==0 ){
 -      p->mode = MODE_Json;
 -    }else{
 -      raw_printf(stderr, "Error: mode should be one of: "
 -         "ascii box column csv html insert json line list markdown "
 -         "qbox quote table tabs tcl\n");
 -      rc = 1;
 +    else{
-       *pzErr = shellMPrintf(0,"unexpected option: %s\n", azArg[i]); 
++      *pzErr = shellMPrintf(0,"unexpected option: %s\n", azArg[i]);
 +      showHelp(p->out, azArg[0]);
 +      return 1;
      }
 -    p->cMode = p->mode;
 -  }else
 +  }
  
 -  if( c=='n' && strcmp(azArg[0], "nonce")==0 ){
 -    if( nArg!=2 ){
 -      raw_printf(stderr, "Usage: .nonce NONCE\n");
 -      rc = 1;
 -    }else if( p->zNonce==0 || strcmp(azArg[1],p->zNonce)!=0 ){
 -      raw_printf(stderr, "line %d: incorrect nonce: \"%s\"\n",
 -                 p->pInSource->lineno, azArg[1]);
 -      exit(1);
 -    }else{
 -      p->bSafeMode = 0;
 -      return 0;  /* Return immediately to bypass the safe mode reset
 -                 ** at the end of this procedure */
 +  shellExecPrintf(p->db, &rc,
-     /* Attach an in-memory database named 'recovery'. Create an indexed 
++    /* Attach an in-memory database named 'recovery'. Create an indexed
 +    ** cache of the sqlite_dbptr virtual table. */
 +    "PRAGMA writable_schema = on;"
 +    "ATTACH %Q AS recovery;"
 +    "DROP TABLE IF EXISTS recovery.dbptr;"
 +    "DROP TABLE IF EXISTS recovery.freelist;"
 +    "DROP TABLE IF EXISTS recovery.map;"
 +    "DROP TABLE IF EXISTS recovery.schema;"
 +    "CREATE TABLE recovery.freelist(pgno INTEGER PRIMARY KEY);", zRecoveryDb
 +  );
 +
 +  if( bFreelist ){
 +    shellExec(p->db, &rc,
 +      "WITH trunk(pgno) AS ("
 +      "  SELECT shell_int32("
 +      "      (SELECT data FROM sqlite_dbpage WHERE pgno=1), 8) AS x "
 +      "      WHERE x>0"
 +      "    UNION"
 +      "  SELECT shell_int32("
 +      "      (SELECT data FROM sqlite_dbpage WHERE pgno=trunk.pgno), 0) AS x "
 +      "      FROM trunk WHERE x>0"
 +      "),"
 +      "freelist(data, n, freepgno) AS ("
 +      "  SELECT data, min(16384, shell_int32(data, 1)-1), t.pgno "
 +      "      FROM trunk t, sqlite_dbpage s WHERE s.pgno=t.pgno"
 +      "    UNION ALL"
 +      "  SELECT data, n-1, shell_int32(data, 2+n) "
 +      "      FROM freelist WHERE n>=0"
 +      ")"
 +      "REPLACE INTO recovery.freelist SELECT freepgno FROM freelist;"
 +    );
 +  }
 +
 +  /* If this is an auto-vacuum database, add all pointer-map pages to
-   ** the freelist table. Do this regardless of whether or not 
++  ** the freelist table. Do this regardless of whether or not
 +  ** --freelist-corrupt was specified.  */
-   shellExec(p->db, &rc, 
++  shellExec(p->db, &rc,
 +    "WITH ptrmap(pgno) AS ("
 +    "  SELECT 2 WHERE shell_int32("
 +    "    (SELECT data FROM sqlite_dbpage WHERE pgno=1), 13"
 +    "  )"
 +    "    UNION ALL "
 +    "  SELECT pgno+1+(SELECT page_size FROM pragma_page_size)/5 AS pp "
 +    "  FROM ptrmap WHERE pp<=(SELECT page_count FROM pragma_page_count)"
 +    ")"
 +    "REPLACE INTO recovery.freelist SELECT pgno FROM ptrmap"
 +  );
 +
-   shellExec(p->db, &rc, 
++  shellExec(p->db, &rc,
 +    "CREATE TABLE recovery.dbptr("
 +    "      pgno, child, PRIMARY KEY(child, pgno)"
 +    ") WITHOUT ROWID;"
 +    "INSERT OR IGNORE INTO recovery.dbptr(pgno, child) "
 +    "    SELECT * FROM sqlite_dbptr"
 +    "      WHERE pgno NOT IN freelist AND child NOT IN freelist;"
 +
 +    /* Delete any pointer to page 1. This ensures that page 1 is considered
 +    ** a root page, regardless of how corrupt the db is. */
 +    "DELETE FROM recovery.dbptr WHERE child = 1;"
 +
 +    /* Delete all pointers to any pages that have more than one pointer
 +    ** to them. Such pages will be treated as root pages when recovering
 +    ** data.  */
 +    "DELETE FROM recovery.dbptr WHERE child IN ("
 +    "  SELECT child FROM recovery.dbptr GROUP BY child HAVING count(*)>1"
 +    ");"
 +
 +    /* Create the "map" table that will (eventually) contain instructions
-     ** for dealing with each page in the db that contains one or more 
++    ** for dealing with each page in the db that contains one or more
 +    ** records. */
 +    "CREATE TABLE recovery.map("
 +      "pgno INTEGER PRIMARY KEY, maxlen INT, intkey, root INT"
 +    ");"
 +
 +    /* Populate table [map]. If there are circular loops of pages in the
 +    ** database, the following adds all pages in such a loop to the map
 +    ** as individual root pages. This could be handled better.  */
 +    "WITH pages(i, maxlen) AS ("
 +    "  SELECT page_count, ("
 +    "    SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=page_count"
 +    "  ) FROM pragma_page_count WHERE page_count>0"
 +    "    UNION ALL"
 +    "  SELECT i-1, ("
 +    "    SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=i-1"
 +    "  ) FROM pages WHERE i>=2"
 +    ")"
 +    "INSERT INTO recovery.map(pgno, maxlen, intkey, root) "
 +    "  SELECT i, maxlen, NULL, ("
 +    "    WITH p(orig, pgno, parent) AS ("
 +    "      SELECT 0, i, (SELECT pgno FROM recovery.dbptr WHERE child=i)"
 +    "        UNION "
 +    "      SELECT i, p.parent, "
 +    "        (SELECT pgno FROM recovery.dbptr WHERE child=p.parent) FROM p"
 +    "    )"
 +    "    SELECT pgno FROM p WHERE (parent IS NULL OR pgno = orig)"
 +    ") "
 +    "FROM pages WHERE maxlen IS NOT NULL AND i NOT IN freelist;"
 +    "UPDATE recovery.map AS o SET intkey = ("
 +    "  SELECT substr(data, 1, 1)==X'0D' FROM sqlite_dbpage WHERE pgno=o.pgno"
 +    ");"
 +
 +    /* Extract data from page 1 and any linked pages into table
 +    ** recovery.schema. With the same schema as an sqlite_schema table.  */
 +    "CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);"
 +    "INSERT INTO recovery.schema SELECT "
 +    "  max(CASE WHEN field=0 THEN value ELSE NULL END),"
 +    "  max(CASE WHEN field=1 THEN value ELSE NULL END),"
 +    "  max(CASE WHEN field=2 THEN value ELSE NULL END),"
 +    "  max(CASE WHEN field=3 THEN value ELSE NULL END),"
 +    "  max(CASE WHEN field=4 THEN value ELSE NULL END)"
 +    "FROM sqlite_dbdata WHERE pgno IN ("
 +    "  SELECT pgno FROM recovery.map WHERE root=1"
 +    ")"
 +    "GROUP BY pgno, cell;"
 +    "CREATE INDEX recovery.schema_rootpage ON schema(rootpage);"
 +  );
 +
-   /* Open a transaction, then print out all non-virtual, non-"sqlite_%" 
++  /* Open a transaction, then print out all non-virtual, non-"sqlite_%"
 +  ** CREATE TABLE statements that extracted from the existing schema.  */
 +  if( rc==SQLITE_OK ){
 +    sqlite3_stmt *pStmt = 0;
 +    /* ".recover" might output content in an order which causes immediate
 +    ** foreign key constraints to be violated. So disable foreign-key
 +    ** constraint enforcement to prevent problems when running the output
 +    ** script. */
 +    raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n");
 +    raw_printf(p->out, "BEGIN;\n");
 +    raw_printf(p->out, "PRAGMA writable_schema = on;\n");
 +    shellPrepare(p->db, &rc,
 +        "SELECT sql FROM recovery.schema "
 +        "WHERE type='table' AND sql LIKE 'create table%'", &pStmt
 +    );
 +    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
 +      const char *zCreateTable = (const char*)sqlite3_column_text(pStmt, 0);
-       raw_printf(p->out, "CREATE TABLE IF NOT EXISTS %s;\n", 
++      raw_printf(p->out, "CREATE TABLE IF NOT EXISTS %s;\n",
 +          &zCreateTable[12]
 +      );
      }
 -  }else
 +    shellFinalize(&rc, pStmt);
 +  }
  
 -  if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 ){
 -    if( nArg==2 ){
 -      sqlite3_snprintf(sizeof(p->nullValue), p->nullValue,
 -                       "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]);
 -    }else{
 -      raw_printf(stderr, "Usage: .nullvalue STRING\n");
 -      rc = 1;
 -    }
 -  }else
 +  /* Figure out if an orphan table will be required. And if so, how many
 +  ** user columns it should contain */
-   shellPrepare(p->db, &rc, 
++  shellPrepare(p->db, &rc,
 +      "SELECT coalesce(max(maxlen), -2) FROM recovery.map WHERE root>1"
 +      , &pLoop
 +  );
 +  if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
 +    nOrphan = sqlite3_column_int(pLoop, 0);
 +  }
 +  shellFinalize(&rc, pLoop);
 +  pLoop = 0;
  
 -  if( c=='o' && strncmp(azArg[0], "open", n)==0 && n>=2 ){
 -    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;
 -
 -    /* Check for command-line arguments */
 -    for(iName=1; iName<nArg; iName++){
 -      const char *z = azArg[iName];
 -      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") ){
 -        p->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 ){
 -        p->szMax = integerValue(azArg[++iName]);
 -#endif /* SQLITE_OMIT_DESERIALIZE */
 -      }else if( z[0]=='-' ){
 -        utf8_printf(stderr, "unknown option: %s\n", z);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }else if( zFN ){
 -        utf8_printf(stderr, "extra argument: \"%s\"\n", z);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }else{
 -        zFN = z;
 -      }
 -    }
 +  shellPrepare(p->db, &rc,
 +      "SELECT pgno FROM recovery.map WHERE root=?", &pPages
 +  );
  
 -    /* Close the existing database */
 -    session_close_all(p, -1);
 -    close_db(p->db);
 -    p->db = 0;
 -    p->pAuxDb->zDbFilename = 0;
 -    sqlite3_free(p->pAuxDb->zFreeOnClose);
 -    p->pAuxDb->zFreeOnClose = 0;
 -    p->openMode = openMode;
 -    p->openFlags = 0;
 -    p->szMax = 0;
 -
 -    /* If a filename is specified, try to open it first */
 -    if( zFN || p->openMode==SHELL_OPEN_HEXDB ){
 -      if( newFlag && zFN && !p->bSafeMode ) shellDeleteFile(zFN);
 -      if( p->bSafeMode
 -       && p->openMode!=SHELL_OPEN_HEXDB
 -       && zFN
 -       && strcmp(zFN,":memory:")!=0
 -      ){
 -        failIfSafeMode(p, "cannot open disk-based database files in safe mode");
 -      }
 -      if( zFN ){
 -        zNewFilename = sqlite3_mprintf("%s", zFN);
 -        shell_check_oom(zNewFilename);
 -      }else{
 -        zNewFilename = 0;
 -      }
 -      p->pAuxDb->zDbFilename = zNewFilename;
 -      open_db(p, OPEN_DB_KEEPALIVE);
 -      if( p->db==0 ){
 -        utf8_printf(stderr, "Error: cannot open '%s'\n", zNewFilename);
 -        sqlite3_free(zNewFilename);
 -      }else{
 -        p->pAuxDb->zFreeOnClose = zNewFilename;
 -      }
 -    }
 -    if( p->db==0 ){
 -      /* As a fall-back open a TEMP database */
 -      p->pAuxDb->zDbFilename = 0;
 -      open_db(p, 0);
 -    }
 -  }else
 +  shellPrepare(p->db, &rc,
 +      "SELECT max(field), group_concat(shell_escape_crnl(quote"
 +      "(case when (? AND field<0) then NULL else value end)"
 +      "), ', ')"
 +      ", min(field) "
 +      "FROM sqlite_dbdata WHERE pgno = ? AND field != ?"
 +      "GROUP BY cell", &pCells
 +  );
  
 -  if( (c=='o'
 -       && (strncmp(azArg[0], "output", n)==0||strncmp(azArg[0], "once", n)==0))
 -      || (c=='e' && n==5 && strcmp(azArg[0],"excel")==0)
 -  ){
 -    char *zFile = 0;
 -    int bTxtMode = 0;
 -    int i;
 -    int eMode = 0;
 -    int bBOM = 0;
 -    int bOnce = 0;  /* 0: .output, 1: .once, 2: .excel */
 +  /* Loop through each root page. */
-   shellPrepare(p->db, &rc, 
-       "SELECT root, intkey, max(maxlen) FROM recovery.map" 
++  shellPrepare(p->db, &rc,
++      "SELECT root, intkey, max(maxlen) FROM recovery.map"
 +      " WHERE root>1 GROUP BY root, intkey ORDER BY root=("
 +      "  SELECT rootpage FROM recovery.schema WHERE name='sqlite_sequence'"
 +      ")", &pLoop
 +  );
 +  while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
 +    int iRoot = sqlite3_column_int(pLoop, 0);
 +    int bIntkey = sqlite3_column_int(pLoop, 1);
 +    int nCol = sqlite3_column_int(pLoop, 2);
 +    int bNoop = 0;
 +    RecoverTable *pTab;
  
 -    failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
 -    if( c=='e' ){
 -      eMode = 'x';
 -      bOnce = 2;
 -    }else if( strncmp(azArg[0],"once",n)==0 ){
 -      bOnce = 1;
 -    }
 -    for(i=1; i<nArg; i++){
 -      char *z = azArg[i];
 -      if( z[0]=='-' ){
 -        if( z[1]=='-' ) z++;
 -        if( strcmp(z,"-bom")==0 ){
 -          bBOM = 1;
 -        }else if( c!='e' && strcmp(z,"-x")==0 ){
 -          eMode = 'x';  /* spreadsheet */
 -        }else if( c!='e' && strcmp(z,"-e")==0 ){
 -          eMode = 'e';  /* text editor */
 -        }else{
 -          utf8_printf(p->out, "ERROR: unknown option: \"%s\".  Usage:\n",
 -                      azArg[i]);
 -          showHelp(p->out, azArg[0]);
 -          rc = 1;
 -          goto meta_command_exit;
 -        }
 -      }else if( zFile==0 && eMode!='e' && eMode!='x' ){
 -        zFile = sqlite3_mprintf("%s", z);
 -        if( zFile && zFile[0]=='|' ){
 -          while( i+1<nArg ) zFile = sqlite3_mprintf("%z %s", zFile, azArg[++i]);
 -          break;
 -        }
 -      }else{
 -        utf8_printf(p->out,"ERROR: extra parameter: \"%s\".  Usage:\n",
 -                    azArg[i]);
 -        showHelp(p->out, azArg[0]);
 -        rc = 1;
 -        sqlite3_free(zFile);
 -        goto meta_command_exit;
 +    assert( bIntkey==0 || bIntkey==1 );
 +    pTab = recoverFindTable(p, &rc, iRoot, bIntkey, nCol, &bNoop);
 +    if( bNoop || rc ) continue;
 +    if( pTab==0 ){
 +      if( pOrphan==0 ){
 +        pOrphan = recoverOrphanTable(p, &rc, zLostAndFound, nOrphan);
        }
 +      pTab = pOrphan;
 +      if( pTab==0 ) break;
      }
 -    if( zFile==0 ){
 -      zFile = sqlite3_mprintf("stdout");
 -    }
 -    if( bOnce ){
 -      p->outCount = 2;
 -    }else{
 -      p->outCount = 0;
 -    }
 -    output_reset(p);
 -#ifndef SQLITE_NOHAVE_SYSTEM
 -    if( eMode=='e' || eMode=='x' ){
 -      p->doXdgOpen = 1;
 -      outputModePush(p);
 -      if( eMode=='x' ){
 -        /* spreadsheet mode.  Output as CSV. */
 -        newTempFile(p, "csv");
 -        ShellClearFlag(p, SHFLG_Echo);
 -        p->mode = MODE_Csv;
 -        sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
 -        sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf);
 -      }else{
 -        /* text editor mode */
 -        newTempFile(p, "txt");
 -        bTxtMode = 1;
 -      }
 -      sqlite3_free(zFile);
 -      zFile = sqlite3_mprintf("%s", p->zTempFile);
 +
 +    if( 0==sqlite3_stricmp(pTab->zQuoted, "\"sqlite_sequence\"") ){
 +      raw_printf(p->out, "DELETE FROM sqlite_sequence;\n");
      }
 -#endif /* SQLITE_NOHAVE_SYSTEM */
 -    shell_check_oom(zFile);
 -    if( zFile[0]=='|' ){
 -#ifdef SQLITE_OMIT_POPEN
 -      raw_printf(stderr, "Error: pipes are not supported in this OS\n");
 -      rc = 1;
 -      p->out = stdout;
 -#else
 -      p->out = popen(zFile + 1, "w");
 -      if( p->out==0 ){
 -        utf8_printf(stderr,"Error: cannot open pipe \"%s\"\n", zFile + 1);
 -        p->out = stdout;
 -        rc = 1;
 -      }else{
 -        if( bBOM ) fprintf(p->out,"\357\273\277");
 -        sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
 -      }
 -#endif
 +    sqlite3_bind_int(pPages, 1, iRoot);
 +    if( bRowids==0 && pTab->iPk<0 ){
 +      sqlite3_bind_int(pCells, 1, 1);
      }else{
 -      p->out = output_file_open(zFile, bTxtMode);
 -      if( p->out==0 ){
 -        if( strcmp(zFile,"off")!=0 ){
 -          utf8_printf(stderr,"Error: cannot write to \"%s\"\n", zFile);
 -        }
 -        p->out = stdout;
 -        rc = 1;
 -      } else {
 -        if( bBOM ) fprintf(p->out,"\357\273\277");
 -        sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
 -      }
 +      sqlite3_bind_int(pCells, 1, 0);
      }
 -    sqlite3_free(zFile);
 -  }else
 -
 -  if( c=='p' && n>=2 && strncmp(azArg[0], "parameter", n)==0 ){
 -    open_db(p,0);
 -    if( nArg<=1 ) goto parameter_syntax_error;
 +    sqlite3_bind_int(pCells, 3, pTab->iPk);
  
 -    /* .parameter clear  and  .parameter unset ?NAMES?
 -    **  Delete some or all bind parameters from the TEMP table that holds them.
 -    **  Without any arguments, clear deletes them all and unset does nothing.
 -    */
 -    if( strcmp(azArg[1],"clear")==0 || strcmp(azArg[1],"unset")==0 ){
 -      if( param_table_exists(p->db) && (nArg>2 || azArg[1][0]=='c') ){
 -        sqlite3_str *sbZap = sqlite3_str_new(p->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(p->db, zSql, 0, 0, 0);
 -        sqlite3_free(zSql);
 -      }
 -    }else
 +    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPages) ){
 +      int iPgno = sqlite3_column_int(pPages, 0);
 +      sqlite3_bind_int(pCells, 2, iPgno);
 +      while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pCells) ){
 +        int nField = sqlite3_column_int(pCells, 0);
 +        int iMin = sqlite3_column_int(pCells, 2);
 +        const char *zVal = (const char*)sqlite3_column_text(pCells, 1);
  
 -#ifndef SQLITE_NOHAVE_SYSTEM
 -    /* .parameter edit ?NAMES?
 -    ** Edit the named parameters. Any that do not exist are created.
 -    ** New ones get a uses tag auto-selected by their leading char.
 -    */
 -    if( strcmp(azArg[1],"edit")==0 ){
 -      int ia = 2;
 -      int eval = 0;
 -      if( !INSOURCE_IS_INTERACTIVE(p->pInSource) ){
 -        utf8_printf(stderr, "Error: "
 -                    ".parameter edit can only be used interactively.\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      param_table_init(p);
 -      if( p->zEditor==0 ){
 -        const char *zE = getenv("VISUAL");
 -        if( zE!=0 ) p->zEditor = sqlite3_mprintf("%s", zE);
 -      }
 -      if( nArg>=3 && azArg[2][0]=='-' ){
 -        char *zArg = (azArg[2][1]=='-')? azArg[2]+2 : azArg[2]+1;
 -        if( strncmp(zArg,"editor=",7)==0 ){
 -          sqlite3_free(p->zEditor);
 -          /* Accept an initial -editor=? option. */
 -          p->zEditor = sqlite3_mprintf("%s", zArg+7);
 -          ++ia;
 -        }
 -      }
 -      if( p->zEditor==0 ){
 -        /* This is klutzy, but edit is for interactive use. So this
 -         * problem, due to not heeding the .parameter doc, can be
 -         * fixed with a modest inconvenience to the casual user.
 -         */
 -        utf8_printf(stderr,
 -                    "Either set env-var VISUAL to name an"
 -                    " editor and restart, or rerun\n "
 -                    ".parameter edit with an initial "
 -                    "edit option, --editor=EDITOR_COMMAND .\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      /* Future: Allow an option whereby new value can be evaluated
 -       * the way that .parameter set ... does.
 -       */
 -      while( ia < nArg ){
 -        ParamTableUse ptu;
 -        char cf = (azArg[ia][0]=='-')? azArg[ia][1] : 0;
 -        if( cf!=0 && azArg[ia][2]==0 ){
 -          ++ia;
 -          switch( cf ){
 -          case 'e': eval = 1; continue;
 -          case 't': eval = 0; continue;
 -          default:
 -            utf8_printf(stderr, "Error: bad .parameter name: %s\n",
 -                        azArg[--ia]);
 -            rc = 1;
 -            goto meta_command_exit;
 +        RecoverTable *pTab2 = pTab;
 +        if( pTab!=pOrphan && (iMin<0)!=bIntkey ){
 +          if( pOrphan==0 ){
 +            pOrphan = recoverOrphanTable(p, &rc, zLostAndFound, nOrphan);
            }
 +          pTab2 = pOrphan;
 +          if( pTab2==0 ) break;
          }
 -        ptu = classify_param_name(azArg[ia]);
 -        if( ptu==PTU_Nil ){
 -          utf8_printf(stderr, "Error: %s cannot be a binding or executable"
 -                      " parameter name.\n", azArg[ia]);
 -          rc = 1;
 -          goto meta_command_exit;
 -        }
 -        rc = edit_one_param(p->db, azArg[ia], eval, ptu, p->zEditor);
 -        ++ia;
 -        if( rc!=0 ) goto meta_command_exit;
 -      }
 -    }else
 -#endif
 -    /* .parameter init
 -    ** Make sure the TEMP table used to hold bind parameters exists.
 -    ** Create it if necessary.
 -    */
 -    if( nArg==2 && strcmp(azArg[1],"init")==0 ){
 -      param_table_init(p);
 -    }else
 -
 -    /* .parameter list|ls
 -    ** List all or selected bind parameters.
 -    ** list displays names, values and uses.
 -    ** ls displays just the names.
 -    */
 -    if( nArg>=2 && ((strcmp(azArg[1],"list")==0)
 -                    || (strcmp(azArg[1],"ls")==0)) ){
 -      list_params(p, PTU_Nil, azArg[1][1]=='s', azArg+2, nArg-2);
 -    }else
 -
 -    /* .parameter load
 -    ** Load all or named parameters from specified or default (DB) file.
 -    */
 -    if( strcmp(azArg[1],"load")==0 ){
 -      param_table_init(p);
 -      rc = parameters_load(p->db, (const char **)azArg+1, nArg-1);
 -    }else
 -
 -    /* .parameter save
 -    ** Save all or named parameters into specified or default (DB) file.
 -    */
 -    if( strcmp(azArg[1],"save")==0 ){
 -      rc = parameters_save(p->db, (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 && 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_Nil ){
 -        utf8_printf(stderr,
 -                    "Error: %s is not a usable parameter name.\n", azArg[inv]);
 -        rc = 1;
 -      }else{
 -        param_table_init(p);
 -        rc = param_set(p->db, cCast, azArg[inv],
 -                       &azArg[inv+1], &azArg[nArg], ptu);
 -        if( rc!=SQLITE_OK ){
 -          utf8_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db));
 -          rc = 1;
 -        }
 -      }
 -    }else
 -
 -    /* If no command name matches, show a syntax error */
 -    parameter_syntax_error:
 -    showHelp(p->out, "parameter");
 -  }else
 -
 -  if( c=='p' && n>=3 && strncmp(azArg[0], "print", n)==0 ){
 -    int i;
 -    for(i=1; i<nArg; i++){
 -      if( i>1 ) raw_printf(p->out, " ");
 -      utf8_printf(p->out, "%s", azArg[i]);
 -    }
 -    raw_printf(p->out, "\n");
 -  }else
  
 -#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
 -  if( c=='p' && n>=3 && strncmp(azArg[0], "progress", n)==0 ){
 -    int i;
 -    int nn = 0;
 -    p->flgProgress = 0;
 -    p->mxProgress = 0;
 -    p->nProgress = 0;
 -    for(i=1; i<nArg; i++){
 -      const char *z = azArg[i];
 -      if( z[0]=='-' ){
 -        z++;
 -        if( z[0]=='-' ) z++;
 -        if( strcmp(z,"quiet")==0 || strcmp(z,"q")==0 ){
 -          p->flgProgress |= SHELL_PROGRESS_QUIET;
 -          continue;
 -        }
 -        if( strcmp(z,"reset")==0 ){
 -          p->flgProgress |= SHELL_PROGRESS_RESET;
 -          continue;
 -        }
 -        if( strcmp(z,"once")==0 ){
 -          p->flgProgress |= SHELL_PROGRESS_ONCE;
 -          continue;
 -        }
 -        if( strcmp(z,"limit")==0 ){
 -          if( i+1>=nArg ){
 -            utf8_printf(stderr, "Error: missing argument on --limit\n");
 -            rc = 1;
 -            goto meta_command_exit;
 -          }else{
 -            p->mxProgress = (int)integerValue(azArg[++i]);
 -          }
 -          continue;
 +        nField = nField+1;
 +        if( pTab2==pOrphan ){
-           raw_printf(p->out, 
++          raw_printf(p->out,
 +              "INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n",
 +              pTab2->zQuoted, iRoot, iPgno, nField,
 +              iMin<0 ? "" : "NULL, ", zVal, pTab2->azlCol[nField]
 +          );
 +        }else{
-           raw_printf(p->out, "INSERT INTO %s(%s) VALUES( %s );\n", 
++          raw_printf(p->out, "INSERT INTO %s(%s) VALUES( %s );\n",
 +              pTab2->zQuoted, pTab2->azlCol[nField], zVal
 +          );
          }
 -        utf8_printf(stderr, "Error: unknown option: \"%s\"\n", azArg[i]);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }else{
 -        nn = (int)integerValue(z);
        }
 +      shellReset(&rc, pCells);
      }
 -    open_db(p, 0);
 -    sqlite3_progress_handler(p->db, nn, progress_handler, p);
 -  }else
 -#endif /* SQLITE_OMIT_PROGRESS_CALLBACK */
 -
 -  if( c=='p' && strncmp(azArg[0], "prompt", n)==0 ){
 -    if( nArg >= 2) {
 -      strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1);
 -    }
 -    if( nArg >= 3) {
 -      strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1);
 -    }
 -  }else
 -
 -  if( c=='q' && strncmp(azArg[0], "quit", n)==0 ){
 -    rc = 2;
 -  }else
 +    shellReset(&rc, pPages);
 +    if( pTab!=pOrphan ) recoverFreeTable(pTab);
 +  }
 +  shellFinalize(&rc, pLoop);
 +  shellFinalize(&rc, pPages);
 +  shellFinalize(&rc, pCells);
 +  recoverFreeTable(pOrphan);
  
 -  if( c=='r' && n>=3 && strncmp(azArg[0], "read", n)==0 ){
 -    FILE *inUse = 0;
 -    int (*fCloser)(FILE *) = 0;
 -    failIfSafeMode(p, "cannot run .read in safe mode");
 -    if( nArg!=2 ){
 -      raw_printf(stderr, "Usage: .read FILE\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    if( azArg[1][0]=='|' ){
 -#ifdef SQLITE_OMIT_POPEN
 -      raw_printf(stderr, "Error: pipes are not supported in this OS\n");
 -      rc = 1;
 -      p->out = stdout; /* This is likely not needed. To be investigated. */
 -#else
 -      inUse = popen(azArg[1]+1, "r");
 -      if( inUse==0 ){
 -        utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]);
 -        rc = 1;
 +  /* The rest of the schema */
 +  if( rc==SQLITE_OK ){
 +    sqlite3_stmt *pStmt = 0;
-     shellPrepare(p->db, &rc, 
++    shellPrepare(p->db, &rc,
 +        "SELECT sql, name FROM recovery.schema "
 +        "WHERE sql NOT LIKE 'create table%'", &pStmt
 +    );
 +    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
 +      const char *zSql = (const char*)sqlite3_column_text(pStmt, 0);
 +      if( sqlite3_strnicmp(zSql, "create virt", 11)==0 ){
 +        const char *zName = (const char*)sqlite3_column_text(pStmt, 1);
-         char *zPrint = shellMPrintf(&rc, 
++        char *zPrint = shellMPrintf(&rc,
 +          "INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)",
 +          zName, zName, zSql
 +        );
 +        raw_printf(p->out, "%s;\n", zPrint);
 +        sqlite3_free(zPrint);
        }else{
 -        fCloser = pclose;
 +        raw_printf(p->out, "%s;\n", zSql);
        }
 -#endif
 -    }else if( (inUse = openChrSource(azArg[1]))==0 ){
 -      utf8_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]);
 -      rc = 1;
 -    }else{
 -      fCloser = fclose;
      }
 -    if( inUse!=0 ){
 -      InSource inSourceRedir
 -        = INSOURCE_FILE_REDIR(inUse, azArg[1], p->pInSource);
 -      p->pInSource = &inSourceRedir;
 -      rc = process_input(p);
 -      assert(fCloser!=0);
 -      fCloser(inUse);
 -      p->pInSource = p->pInSource->pFrom;
 -    }
 -  }else
 +    shellFinalize(&rc, pStmt);
 +  }
  
 -  if( c=='r' && n>=3 && strncmp(azArg[0], "restore", n)==0 ){
 -    const char *zSrcFile;
 -    const char *zDb;
 -    sqlite3 *pSrc;
 -    sqlite3_backup *pBackup;
 -    int nTimeout = 0;
 -
 -    failIfSafeMode(p, "cannot run .restore in safe mode");
 -    if( nArg==2 ){
 -      zSrcFile = azArg[1];
 -      zDb = "main";
 -    }else if( nArg==3 ){
 -      zSrcFile = azArg[2];
 -      zDb = azArg[1];
 -    }else{
 -      raw_printf(stderr, "Usage: .restore ?DB? FILE\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    rc = sqlite3_open(zSrcFile, &pSrc);
 -    if( rc!=SQLITE_OK ){
 -      utf8_printf(stderr, "Error: cannot open \"%s\"\n", zSrcFile);
 -      close_db(pSrc);
 -      return 1;
 -    }
 -    open_db(p, 0);
 -    pBackup = sqlite3_backup_init(p->db, zDb, pSrc, "main");
 -    if( pBackup==0 ){
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 -      close_db(pSrc);
 -      return 1;
 -    }
 -    while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK
 -          || rc==SQLITE_BUSY  ){
 -      if( rc==SQLITE_BUSY ){
 -        if( nTimeout++ >= 3 ) break;
 -        sqlite3_sleep(100);
 -      }
 -    }
 -    sqlite3_backup_finish(pBackup);
 -    if( rc==SQLITE_DONE ){
 -      rc = 0;
 -    }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){
 -      raw_printf(stderr, "Error: source database is busy\n");
 -      rc = 1;
 -    }else{
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 -      rc = 1;
 -    }
 +  if( rc==SQLITE_OK ){
 +    raw_printf(p->out, "PRAGMA writable_schema = off;\n");
 +    raw_printf(p->out, "COMMIT;\n");
 +  }
 +  sqlite3_exec(p->db, "DETACH recovery", 0, 0, 0);
 +  return rc;
 +}
 +
 +DISPATCHABLE_COMMAND( restore ? 2 3 ){
 +  int rc;
 +  const char *zSrcFile;
 +  const char *zDb;
 +  sqlite3 *pSrc;
 +  sqlite3_backup *pBackup;
 +  int nTimeout = 0;
 +
 +  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
 +  if( nArg==2 ){
 +    zSrcFile = azArg[1];
 +    zDb = "main";
 +  }else if( nArg==3 ){
 +    zSrcFile = azArg[2];
 +    zDb = azArg[1];
 +  }else{
 +    return SHELL_INVALID_ARGS;
 +  }
 +  rc = sqlite3_open(zSrcFile, &pSrc);
 +  if( rc!=SQLITE_OK ){
 +    *pzErr = shellMPrintf(0,"Error: cannot open \"%s\"\n", zSrcFile);
      close_db(pSrc);
 -  }else
 +    return 1;
 +  }
 +  open_db(p, 0);
 +  pBackup = sqlite3_backup_init(p->db, zDb, pSrc, "main");
 +  if( pBackup==0 ){
 +    *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
 +    close_db(pSrc);
 +    return 1;
 +  }
 +  while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK
 +         || rc==SQLITE_BUSY  ){
 +    if( rc==SQLITE_BUSY ){
 +      if( nTimeout++ >= 3 ) break;
 +      sqlite3_sleep(100);
 +    }
 +  }
 +  sqlite3_backup_finish(pBackup);
 +  if( rc==SQLITE_DONE ){
 +    rc = 0;
 +  }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){
 +    *pzErr = shellMPrintf(0,"Error: source database is busy\n");
 +    rc = 1;
 +  }else{
 +    *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
 +    rc = 1;
 +  }
 +  close_db(pSrc);
 +  return rc;
 +}
  
 -  if( c=='s' && strncmp(azArg[0], "scanstats", n)==0 ){
 -    if( nArg==2 ){
 -      p->scanstatsOn = (u8)booleanValue(azArg[1]);
 +/*****************
 + * The .scanstats and .schema commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".scanstats on|off        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 ){
 +    p->scanstatsOn = (u8)booleanValue(azArg[1]);
  #ifndef SQLITE_ENABLE_STMT_SCANSTATUS
 -      raw_printf(stderr, "Warning: .scanstats not available in this build.\n");
 +    raw_printf(STD_ERR, "Warning: .scanstats not available in this build.\n");
  #endif
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( schema ? 1 2 ){
 +  int rc;
 +  ShellText sSelect;
 +  ShellState data;
 +  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);
 +  memcpy(&data, p, sizeof(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]=='-' ){
 +      *pzErr = shellMPrintf(0,"Unknown option: \"%s\"\n", azArg[ii]);
 +      return SHELL_INVALID_ARGS;
 +    }else if( zName==0 ){
 +      zName = azArg[ii];
      }else{
 -      raw_printf(stderr, "Usage: .scanstats on|off\n");
 -      rc = 1;
 +      return SHELL_INVALID_ARGS;
 +    }
 +  }
 +  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] = sqlite3_mprintf(
 +                                    "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(&data, 1, new_argv, new_colv);
 +      sqlite3_free(new_argv[0]);
 +    }
 +  }
 +  if( zDiv ){
 +    sqlite3_stmt *pStmt = 0;
 +    rc = sqlite3_prepare_v2(p->db, "SELECT name FROM pragma_database_list",
 +                            -1, &pStmt, 0);
 +    if( rc ){
 +      *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
 +      sqlite3_finalize(pStmt);
 +      return 1;
      }
 -  }else
 -
 -  if( c=='s' && strncmp(azArg[0], "schema", n)==0 ){
 -    ShellText sSelect;
 -    ShellState data;
 -    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);
 -    memcpy(&data, p, sizeof(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]=='-' ){
 -        utf8_printf(stderr, "Unknown option: \"%s\"\n", azArg[ii]);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }else if( zName==0 ){
 -        zName = azArg[ii];
 -      }else{
 -        raw_printf(stderr,
 -                   "Usage: .schema ?--indent? ?--nosys? ?LIKE-PATTERN?\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -    }
 -    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] = sqlite3_mprintf(
 -                      "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(&data, 1, new_argv, new_colv);
 -        sqlite3_free(new_argv[0]);
 -      }
 -    }
 -    if( zDiv ){
 -      sqlite3_stmt *pStmt = 0;
 -      rc = sqlite3_prepare_v2(p->db, "SELECT name FROM pragma_database_list",
 -                              -1, &pStmt, 0);
 -      if( rc ){
 -        utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 -        sqlite3_finalize(pStmt);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      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{
 -          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, "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, '\'');
 -        appendText(&sSelect, " AS sname FROM ", 0);
 -        appendText(&sSelect, zDb, quoteChar(zDb));
 -        appendText(&sSelect, ".sqlite_schema", 0);
 +      }else{
 +        appendText(&sSelect, "NULL", 0);
        }
 -      sqlite3_finalize(pStmt);
 +      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);
 -      }
 -#endif
 -      appendText(&sSelect, ") WHERE ", 0);
 -      if( zName ){
 -        char *zQarg = sqlite3_mprintf("%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);
 -        }
 -        appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0);
 -        appendText(&sSelect, zQarg, 0);
 -        if( !bGlob ){
 -          appendText(&sSelect, " ESCAPE '\\' ", 0);
 -        }
 -        appendText(&sSelect, " AND ", 0);
 -        sqlite3_free(zQarg);
 -      }
 -      if( bNoSystemTabs ){
 -        appendText(&sSelect, "name NOT LIKE 'sqlite_%%' AND ", 0);
 -      }
 -      appendText(&sSelect, "sql IS NOT NULL"
 -                           " ORDER BY snum, rowid", 0);
 -      if( bDebug ){
 -        utf8_printf(p->out, "SQL: %s;\n", sSelect.z);
 +    if( zName ){
 +      appendText(&sSelect,
 +                 " UNION ALL SELECT shell_module_schema(name),"
-                  " 'table', name, name, name, 9e+99, 'main' FROM pragma_module_list",
++                 " 'table', name, name, name, 9e+99, 'main'"
++                 " FROM pragma_module_list",
 +                 0);
 +    }
 +#endif
 +    appendText(&sSelect, ") WHERE ", 0);
 +    if( zName ){
 +      char *zQarg = sqlite3_mprintf("%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{
 -        rc = sqlite3_exec(p->db, sSelect.z, callback, &data, &zErrMsg);
 +        appendText(&sSelect, "lower(tbl_name)", 0);
 +      }
 +      appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0);
 +      appendText(&sSelect, zQarg, 0);
 +      if( !bGlob ){
 +        appendText(&sSelect, " ESCAPE '\\' ", 0);
        }
 -      freeText(&sSelect);
 +      appendText(&sSelect, " AND ", 0);
 +      sqlite3_free(zQarg);
      }
 -    if( zErrMsg ){
 -      utf8_printf(stderr,"Error: %s\n", zErrMsg);
 -      sqlite3_free(zErrMsg);
 -      rc = 1;
 -    }else if( rc != SQLITE_OK ){
 -      raw_printf(stderr,"Error: querying schema information\n");
 -      rc = 1;
 +    if( bNoSystemTabs ){
 +      appendText(&sSelect, "name NOT LIKE 'sqlite_%%' AND ", 0);
 +    }
 +    appendText(&sSelect, "sql IS NOT NULL"
 +               " ORDER BY snum, rowid", 0);
 +    if( bDebug ){
 +      utf8_printf(p->out, "SQL: %s;\n", sSelect.z);
      }else{
 -      rc = 0;
 +      rc = sqlite3_exec(p->db, sSelect.z, callback, &data, &zErrMsg);
      }
 -  }else
 +    freeText(&sSelect);
 +  }
 +  if( zErrMsg ){
 +    *pzErr = zErrMsg;
 +    rc = 1;
 +  }else if( rc != SQLITE_OK ){
 +    *pzErr = shellMPrintf(0,"Error: querying schema information\n");
 +    rc = 1;
 +  }else{
 +    rc = 0;
 +  }
 +  return rc;
 +}
  
 -  if( c=='s' && n==7 && strncmp(azArg[0], "seeargs", n)==0 ){
 -    for( n=1; n<nArg; ++n ){
 -      raw_printf(p->out, "%s%s", azArg[n], (n==nArg-1)? "|\n" : "|");
 +/*****************
 + * 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( selecttrace ? 1 0 ){
 +  unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
 +  sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x);
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( separator ? 2 3 ){
 +  if( nArg>=2 ){
 +    sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator,
 +                     "%.*s", (int)ArraySize(p->colSeparator)-1, azArg[1]);
 +  }
 +  if( nArg>=3 ){
 +    sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator,
 +                     "%.*s", (int)ArraySize(p->rowSeparator)-1, azArg[2]);
 +  }
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( session 3 2 0 ){
 +  int rc = 0;
 +  struct AuxDb *pAuxDb = p->pAuxDb;
 +  OpenSession *pSession = &pAuxDb->aSession[0];
 +  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( strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break;
 +    }
 +    if( iSes<pAuxDb->nSession ){
 +      pSession = &pAuxDb->aSession[iSes];
 +      azCmd++;
 +      nCmd--;
 +    }else{
 +      pSession = &pAuxDb->aSession[0];
 +      iSes = 0;
      }
 -  }else
 -
 -  if( c=='s' && n==11 && strncmp(azArg[0], "selecttrace", n)==0 ){
 -    unsigned int x
 -      = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
 -    sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x);
 -  }else
 +  }
  
 -#if defined(SQLITE_ENABLE_SESSION)
 -  if( c=='s' && strncmp(azArg[0],"session",n)==0 && n>=3 ){
 -    struct AuxDb *pAuxDb = p->pAuxDb;
 -    OpenSession *pSession = &pAuxDb->aSession[0];
 -    char **azCmd = &azArg[1];
 -    int iSes = 0;
 -    int nCmd = nArg - 1;
 -    int i;
 -    if( nArg<=1 ) goto session_syntax_error;
 -    open_db(p, 0);
 -    if( nArg>=3 ){
 -      for(iSes=0; iSes<pAuxDb->nSession; iSes++){
 -        if( strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break;
 -      }
 -      if( iSes<pAuxDb->nSession ){
 -        pSession = &pAuxDb->aSession[iSes];
 -        azCmd++;
 -        nCmd--;
 -      }else{
 -        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( 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;
        }
      }
 +  }else
  
 -    /* .session attach TABLE
 -    ** Invoke the sqlite3session_attach() interface to attach a particular
 -    ** table so that it is never filtered.
 -    */
 -    if( strcmp(azCmd[0],"attach")==0 ){
 -      if( nCmd!=2 ) goto session_syntax_error;
 -      if( pSession->p==0 ){
 -        session_not_open:
 -        raw_printf(stderr, "ERROR: No sessions are open\n");
 -      }else{
 -        rc = sqlite3session_attach(pSession->p, azCmd[1]);
 -        if( rc ){
 -          raw_printf(stderr, "ERROR: sqlite3session_attach() returns %d\n", rc);
 -          rc = 0;
 -        }
 -      }
 -    }else
 -
--    /* .session changeset FILE
--    ** .session patchset FILE
--    ** Write a changeset or patchset into a file.  The file is overwritten.
--    */
--    if( strcmp(azCmd[0],"changeset")==0 || strcmp(azCmd[0],"patchset")==0 ){
--      FILE *out = 0;
-       if( failIfSafeMode
-           (p, "cannot run \".session %s\" in safe mode", azCmd[0]) ){
-         rc = SHELL_FORBIDDEN_OP;
 -      failIfSafeMode(p, "cannot run \".session %s\" in safe mode", azCmd[0]);
++  /* .session changeset FILE
++  ** .session patchset FILE
++  ** Write a changeset or patchset into a file.  The file is overwritten.
++  */
++  if( strcmp(azCmd[0],"changeset")==0 || strcmp(azCmd[0],"patchset")==0 ){
++    FILE *out = 0;
++    if( failIfSafeMode
++        (p, "cannot run \".session %s\" in safe mode", azCmd[0]) ){
++      rc = SHELL_FORBIDDEN_OP;
++    }else{
+       if( nCmd!=2 ) goto session_syntax_error;
+       if( pSession->p==0 ) goto session_not_open;
+       out = fopen(azCmd[1], "wb");
+       if( out==0 ){
 -        utf8_printf(stderr, "ERROR: cannot open \"%s\" for writing\n",
 -                    azCmd[1]);
++        *pzErr = sqlite3_mprintf
++          ("ERROR: cannot open \"%s\" for writing\n", azCmd[1]);
++        rc = 1;
        }else{
-         if( nCmd!=2 ) goto session_syntax_error;
-         if( pSession->p==0 ) goto session_not_open;
-         out = fopen(azCmd[1], "wb");
-         if( out==0 ){
-           *pzErr = sqlite3_mprintf
-             ("ERROR: cannot open \"%s\" for writing\n", azCmd[1]);
-           rc = 1;
+         int szChng;
+         void *pChng;
+         if( azCmd[0][0]=='c' ){
+           rc = sqlite3session_changeset(pSession->p, &szChng, &pChng);
          }else{
-           int szChng;
-           void *pChng;
-           if( azCmd[0][0]=='c' ){
-             rc = sqlite3session_changeset(pSession->p, &szChng, &pChng);
-           }else{
-             rc = sqlite3session_patchset(pSession->p, &szChng, &pChng);
-           }
-           if( rc ){
-             fprintf(STD_OUT, "Error: error code %d\n", rc);
-             rc = 0;
-           }
-           if( pChng && fwrite(pChng, szChng, 1, out)!=1 ){
-             raw_printf(STD_ERR, "ERROR: Failed to write entire %d-byte output\n",
-                        szChng);
-           }
-           sqlite3_free(pChng);
-           fclose(out);
+           rc = sqlite3session_patchset(pSession->p, &szChng, &pChng);
+         }
+         if( rc ){
 -          printf("Error: error code %d\n", rc);
++          fprintf(STD_OUT, "Error: error code %d\n", rc);
+           rc = 0;
          }
 -        if( pChng
 -          && fwrite(pChng, szChng, 1, out)!=1 ){
 -          raw_printf(stderr, "ERROR: Failed to write entire %d-byte output\n",
 -                  szChng);
++        if( pChng && fwrite(pChng, szChng, 1, out)!=1 ){
++          raw_printf(STD_ERR, "ERROR: Failed to write entire %d-byte output\n",
++                     szChng);
+         }
+         sqlite3_free(pChng);
+         fclose(out);
        }
 -    }else
 -
 -    /* .session close
 -    ** Close the identified session
 -    */
 -    if( strcmp(azCmd[0], "close")==0 ){
 -      if( nCmd!=1 ) goto session_syntax_error;
 -      if( pAuxDb->nSession ){
 -        session_close(pSession);
 -        pAuxDb->aSession[iSes] = pAuxDb->aSession[--pAuxDb->nSession];
 -      }
--    }else
++    }
++  }else
  
-       /* .session close
-       ** Close the identified session
-       */
-       if( strcmp(azCmd[0], "close")==0 ){
-         if( nCmd!=1 ) goto session_syntax_error;
-         if( pAuxDb->nSession ){
-           session_close(pSession);
-           pAuxDb->aSession[iSes] = pAuxDb->aSession[--pAuxDb->nSession];
-         }
-       }else
 -    /* .session enable ?BOOLEAN?
 -    ** Query or set the enable flag
 -    */
 -    if( strcmp(azCmd[0], "enable")==0 ){
 -      int ii;
 -      if( nCmd>2 ) goto session_syntax_error;
 -      ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
 -      if( pAuxDb->nSession ){
 -        ii = sqlite3session_enable(pSession->p, ii);
 -        utf8_printf(p->out, "session %s enable flag = %d\n",
 -                    pSession->zName, ii);
 -      }
 -    }else
++  /* .session close
++  ** Close the identified session
++  */
++  if( strcmp(azCmd[0], "close")==0 ){
++    if( nCmd!=1 ) goto session_syntax_error;
++    if( pAuxDb->nSession ){
++      session_close(pSession);
++      pAuxDb->aSession[iSes] = pAuxDb->aSession[--pAuxDb->nSession];
++    }
++  }else
  
-         /* .session enable ?BOOLEAN?
-         ** Query or set the enable flag
-         */
-         if( strcmp(azCmd[0], "enable")==0 ){
-           int ii;
-           if( nCmd>2 ) goto session_syntax_error;
-           ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
-           if( pAuxDb->nSession ){
-             ii = sqlite3session_enable(pSession->p, ii);
-             utf8_printf(p->out, "session %s enable flag = %d\n",
-                         pSession->zName, ii);
-           }
-         }else
 -    /* .session filter GLOB ....
 -    ** Set a list of GLOB patterns of table names to be excluded.
 -    */
 -    if( strcmp(azCmd[0], "filter")==0 ){
 -      int ii, nByte;
 -      if( nCmd<2 ) goto session_syntax_error;
 -      if( pAuxDb->nSession ){
 -        for(ii=0; ii<pSession->nFilter; ii++){
 -          sqlite3_free(pSession->azFilter[ii]);
 -        }
 -        sqlite3_free(pSession->azFilter);
 -        nByte = sizeof(pSession->azFilter[0])*(nCmd-1);
 -        pSession->azFilter = sqlite3_malloc( nByte );
 -        if( pSession->azFilter==0 ){
 -          raw_printf(stderr, "Error: out or memory\n");
 -          exit(1);
 -        }
 -        for(ii=1; ii<nCmd; ii++){
 -          char *x = pSession->azFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]);
 -          shell_check_oom(x);
 -        }
 -        pSession->nFilter = ii-1;
 -      }
 -    }else
++  /* .session enable ?BOOLEAN?
++  ** Query or set the enable flag
++  */
++  if( strcmp(azCmd[0], "enable")==0 ){
++    int ii;
++    if( nCmd>2 ) goto session_syntax_error;
++    ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
++    if( pAuxDb->nSession ){
++      ii = sqlite3session_enable(pSession->p, ii);
++      utf8_printf(p->out, "session %s enable flag = %d\n",
++                  pSession->zName, ii);
++    }
++  }else
  
-           /* .session filter GLOB ....
-           ** Set a list of GLOB patterns of table names to be excluded.
-           */
-           if( strcmp(azCmd[0], "filter")==0 ){
-             int ii, nByte;
-             if( nCmd<2 ) goto session_syntax_error;
-             if( pAuxDb->nSession ){
-               for(ii=0; ii<pSession->nFilter; ii++){
-                 sqlite3_free(pSession->azFilter[ii]);
-               }
-               sqlite3_free(pSession->azFilter);
-               nByte = sizeof(pSession->azFilter[0])*(nCmd-1);
-               pSession->azFilter = sqlite3_malloc( nByte );
-               if( pSession->azFilter==0 ){
-                 shell_out_of_memory();
-               }
-               for(ii=1; ii<nCmd; ii++){
-                 pSession->azFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]);
-                 shell_check_oom(pSession->azFilter[ii-1]);
-               }
-               pSession->nFilter = ii-1;
-             }
-           }else
 -    /* .session indirect ?BOOLEAN?
 -    ** Query or set the indirect flag
 -    */
 -    if( strcmp(azCmd[0], "indirect")==0 ){
 -      int ii;
 -      if( nCmd>2 ) goto session_syntax_error;
 -      ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
 -      if( pAuxDb->nSession ){
 -        ii = sqlite3session_indirect(pSession->p, ii);
 -        utf8_printf(p->out, "session %s indirect flag = %d\n",
 -                    pSession->zName, ii);
++  /* .session filter GLOB ....
++  ** Set a list of GLOB patterns of table names to be excluded.
++  */
++  if( strcmp(azCmd[0], "filter")==0 ){
++    int ii, nByte;
++    if( nCmd<2 ) goto session_syntax_error;
++    if( pAuxDb->nSession ){
++      for(ii=0; ii<pSession->nFilter; ii++){
++        sqlite3_free(pSession->azFilter[ii]);
+       }
 -    }else
 -
 -    /* .session isempty
 -    ** Determine if the session is empty
 -    */
 -    if( strcmp(azCmd[0], "isempty")==0 ){
 -      int ii;
 -      if( nCmd!=1 ) goto session_syntax_error;
 -      if( pAuxDb->nSession ){
 -        ii = sqlite3session_isempty(pSession->p);
 -        utf8_printf(p->out, "session %s isempty flag = %d\n",
 -                    pSession->zName, ii);
++      sqlite3_free(pSession->azFilter);
++      nByte = sizeof(pSession->azFilter[0])*(nCmd-1);
++      pSession->azFilter = sqlite3_malloc( nByte );
++      if( pSession->azFilter==0 ){
++        shell_out_of_memory();
+       }
 -    }else
 -
 -    /* .session list
 -    ** List all currently open sessions
 -    */
 -    if( strcmp(azCmd[0],"list")==0 ){
 -      for(i=0; i<pAuxDb->nSession; i++){
 -        utf8_printf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName);
++      for(ii=1; ii<nCmd; ii++){
++        pSession->azFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]);
++        shell_check_oom(pSession->azFilter[ii-1]);
+       }
 -    }else
++      pSession->nFilter = ii-1;
++    }
++  }else
  
-             /* .session indirect ?BOOLEAN?
-             ** Query or set the indirect flag
-             */
-             if( strcmp(azCmd[0], "indirect")==0 ){
-               int ii;
-               if( nCmd>2 ) goto session_syntax_error;
-               ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
-               if( pAuxDb->nSession ){
-                 ii = sqlite3session_indirect(pSession->p, ii);
-                 utf8_printf(p->out, "session %s indirect flag = %d\n",
-                             pSession->zName, ii);
-               }
-             }else
-               /* .session isempty
-               ** Determine if the session is empty
-               */
-               if( strcmp(azCmd[0], "isempty")==0 ){
-                 int ii;
-                 if( nCmd!=1 ) goto session_syntax_error;
-                 if( pAuxDb->nSession ){
-                   ii = sqlite3session_isempty(pSession->p);
-                   utf8_printf(p->out, "session %s isempty flag = %d\n",
-                               pSession->zName, ii);
-                 }
-               }else
-                 /* .session list
-                 ** List all currently open sessions
-                 */
-                 if( strcmp(azCmd[0],"list")==0 ){
-                   for(i=0; i<pAuxDb->nSession; i++){
-                     utf8_printf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName);
-                   }
-                 }else
-                   /* .session open DB NAME
-                   ** Open a new session called NAME on the attached database DB.
-                   ** DB is normally "main".
-                   */
-                   if( strcmp(azCmd[0],"open")==0 ){
-                     char *zName;
-                     if( nCmd!=3 ) goto session_syntax_error;
-                     zName = azCmd[2];
-                     if( zName[0]==0 ) goto session_syntax_error;
-                     for(i=0; i<pAuxDb->nSession; i++){
-                       if( strcmp(pAuxDb->aSession[i].zName,zName)==0 ){
-                         utf8_printf(STD_ERR, "Session \"%s\" already exists\n", zName);
-                         return rc;
-                       }
-                     }
-                     if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){
-                       raw_printf
-                         (STD_ERR, "Maximum of %d sessions\n",
-                          ArraySize(pAuxDb->aSession));
-                       return rc;
-                     }
-                     pSession = &pAuxDb->aSession[pAuxDb->nSession];
-                     rc = sqlite3session_create(p->db, azCmd[1], &pSession->p);
-                     if( rc ){
-                       *pzErr = sqlite3_mprintf
-                         ("Cannot open session: error code=%d\n", rc);
-                       return rc;
-                     }
-                     pSession->nFilter = 0;
-                     sqlite3session_table_filter(pSession->p, session_filter,
-                                                 pSession);
-                     pAuxDb->nSession++;
-                     shell_newstr_assign(&pSession->zName,
-                                         sqlite3_mprintf("%s", zName));
-                   }else{
-                     /* If no command name matches, show a syntax error */
-                   session_syntax_error:
-                     showHelp(p->out, "session");
-                     return 1;
-                   }
 -    /* .session open DB NAME
 -    ** Open a new session called NAME on the attached database DB.
 -    ** DB is normally "main".
 -    */
 -    if( strcmp(azCmd[0],"open")==0 ){
 -      char *zName;
 -      if( nCmd!=3 ) goto session_syntax_error;
 -      zName = azCmd[2];
 -      if( zName[0]==0 ) goto session_syntax_error;
 -      for(i=0; i<pAuxDb->nSession; i++){
 -        if( strcmp(pAuxDb->aSession[i].zName,zName)==0 ){
 -          utf8_printf(stderr, "Session \"%s\" already exists\n", zName);
 -          goto meta_command_exit;
 -        }
 -      }
 -      if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){
 -        raw_printf(stderr, "Maximum of %d sessions\n",
 -                   ArraySize(pAuxDb->aSession));
 -        goto meta_command_exit;
 -      }
 -      pSession = &pAuxDb->aSession[pAuxDb->nSession];
 -      rc = sqlite3session_create(p->db, azCmd[1], &pSession->p);
 -      if( rc ){
 -        raw_printf(stderr, "Cannot open session: error code=%d\n", rc);
 -        rc = 0;
 -        goto meta_command_exit;
 -      }
 -      pSession->nFilter = 0;
 -      sqlite3session_table_filter(pSession->p, session_filter, pSession);
 -      pAuxDb->nSession++;
 -      pSession->zName = sqlite3_mprintf("%s", zName);
 -      shell_check_oom(pSession->zName);
 -    }else
 -    /* If no command name matches, show a syntax error */
 -    session_syntax_error:
 -    showHelp(p->out, "session");
++  /* .session indirect ?BOOLEAN?
++  ** Query or set the indirect flag
++  */
++  if( strcmp(azCmd[0], "indirect")==0 ){
++    int ii;
++    if( nCmd>2 ) goto session_syntax_error;
++    ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
++    if( pAuxDb->nSession ){
++      ii = sqlite3session_indirect(pSession->p, ii);
++      utf8_printf(p->out, "session %s indirect flag = %d\n",
++                  pSession->zName, ii);
++    }
+   }else
 -#endif
 -#ifdef SQLITE_DEBUG
 -  /* Undocumented commands for internal testing.  Subject to change
 -  ** without notice. */
 -  if( c=='s' && n>=10 && strncmp(azArg[0], "selftest-", 9)==0 ){
 -    if( strncmp(azArg[0]+9, "boolean", n-9)==0 ){
 -      int i, v;
 -      for(i=1; i<nArg; i++){
 -        v = booleanValue(azArg[i]);
 -        utf8_printf(p->out, "%s: %d 0x%x\n", azArg[i], v, v);
 -      }
++  /* .session isempty
++  ** Determine if the session is empty
++  */
++  if( strcmp(azCmd[0], "isempty")==0 ){
++    int ii;
++    if( nCmd!=1 ) goto session_syntax_error;
++    if( pAuxDb->nSession ){
++      ii = sqlite3session_isempty(pSession->p);
++      utf8_printf(p->out, "session %s isempty flag = %d\n",
++                  pSession->zName, ii);
+     }
 -    if( strncmp(azArg[0]+9, "integer", n-9)==0 ){
 -      int i; sqlite3_int64 v;
 -      for(i=1; i<nArg; i++){
 -        char zBuf[200];
 -        v = integerValue(azArg[i]);
 -        sqlite3_snprintf(sizeof(zBuf),zBuf,"%s: %lld 0x%llx\n", azArg[i],v,v);
 -        utf8_printf(p->out, "%s", zBuf);
 -      }
++  }else
++
++  /* .session list
++  ** List all currently open sessions
++  */
++  if( strcmp(azCmd[0],"list")==0 ){
++    for(i=0; i<pAuxDb->nSession; i++){
++      utf8_printf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName);
+     }
+   }else
 -#endif
 -  if( c=='s' && n>=4 && strncmp(azArg[0],"selftest",n)==0 ){
 -    int bIsInit = 0;         /* True to initialize the SELFTEST table */
 -    int bVerbose = 0;        /* Verbose output */
 -    int bSelftestExists;     /* True if SELFTEST already exists */
 -    int i, k;                /* Loop counters */
 -    int nTest = 0;           /* Number of tests runs */
 -    int nErr = 0;            /* Number of errors seen */
 -    ShellText str;           /* Answer for a query */
 -    sqlite3_stmt *pStmt = 0; /* Query against the SELFTEST table */
++  /* .session open DB NAME
++  ** Open a new session called NAME on the attached database DB.
++  ** DB is normally "main".
++  */
++  if( strcmp(azCmd[0],"open")==0 ){
++    char *zName;
++    if( nCmd!=3 ) goto session_syntax_error;
++    zName = azCmd[2];
++    if( zName[0]==0 ) goto session_syntax_error;
++    for(i=0; i<pAuxDb->nSession; i++){
++      if( strcmp(pAuxDb->aSession[i].zName,zName)==0 ){
++        utf8_printf(STD_ERR, "Session \"%s\" already exists\n", zName);
++        return rc;
++      }
++    }
++    if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){
++      raw_printf
++        (STD_ERR, "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession));
++      return rc;
++    }
++    pSession = &pAuxDb->aSession[pAuxDb->nSession];
++    rc = sqlite3session_create(p->db, azCmd[1], &pSession->p);
++    if( rc ){
++      *pzErr = sqlite3_mprintf
++        ("Cannot open session: error code=%d\n", rc);
++      return rc;
++    }
++    pSession->nFilter = 0;
++    sqlite3session_table_filter(pSession->p, session_filter, pSession);
++    pAuxDb->nSession++;
++    shell_newstr_assign(&pSession->zName, sqlite3_mprintf("%s", zName));
++  }else{
 -    open_db(p,0);
 -    for(i=1; i<nArg; i++){
 -      const char *z = azArg[i];
 -      if( z[0]=='-' && z[1]=='-' ) z++;
 -      if( strcmp(z,"-init")==0 ){
 -        bIsInit = 1;
++  /* If no command name matches, show a syntax error */
++  session_syntax_error:
++    showHelp(p->out, "session");
++    return 1;
++  }
 +  return rc;
 +}
 +DISPATCHABLE_COMMAND( sha3sum 4 1 1 ){
 +  const char *zLike = 0;   /* Which table to checksum. 0 means everything */
 +  int i;                   /* Loop counter */
 +  int bSchema = 0;         /* Also hash the schema */
 +  int bSeparate = 0;       /* Hash each table separately */
 +  int iSize = 224;         /* Hash algorithm to use */
 +  int bDebug = 0;          /* Only show the query that would have run */
 +  sqlite3_stmt *pStmt;     /* For querying tables names */
 +  char *zSql;              /* SQL to be run */
 +  char *zSep;              /* Separator */
 +  ShellText sSql;          /* Complete SQL for the query to run the hash */
 +  ShellText sQuery;        /* Set of queries used to read all content */
 +  open_db(p, 0);
 +  for(i=1; i<nArg; i++){
 +    const char *z = azArg[i];
 +    if( z[0]=='-' ){
 +      z++;
 +      if( z[0]=='-' ) z++;
 +      if( strcmp(z,"schema")==0 ){
 +        bSchema = 1;
        }else
-  * The .selftest, .shell, .show, .stats and .system commands
 +        if( strcmp(z,"sha3-224")==0 || strcmp(z,"sha3-256")==0
 +            || strcmp(z,"sha3-384")==0 || strcmp(z,"sha3-512")==0
 +            ){
 +          iSize = atoi(&z[5]);
 +        }else
 +          if( strcmp(z,"debug")==0 ){
 +            bDebug = 1;
 +          }else
 +            {
 +              *pzErr = sqlite3_mprintf
 +                ("Unknown option \"%s\" on \"%s\"\n", azArg[i], azArg[0]);
 +              return SHELL_INVALID_ARGS;
 +            }
 +    }else if( zLike ){
 +      return SHELL_INVALID_ARGS;
 +    }else{
 +      zLike = z;
 +      bSeparate = 1;
 +      if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 1;
 +    }
 +  }
 +  if( bSchema ){
 +    zSql = "SELECT lower(name) FROM sqlite_schema"
 +      " WHERE type='table' AND coalesce(rootpage,0)>1"
 +      " UNION ALL SELECT 'sqlite_schema'"
 +      " ORDER BY 1 collate nocase";
 +  }else{
 +    zSql = "SELECT lower(name) FROM sqlite_schema"
 +      " WHERE type='table' AND coalesce(rootpage,0)>1"
 +      " AND name NOT LIKE 'sqlite_%'"
 +      " ORDER BY 1 collate nocase";
 +  }
 +  sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 +  initText(&sQuery);
 +  initText(&sSql);
 +  appendText(&sSql, "WITH [sha3sum$query](a,b) AS(",0);
 +  zSep = "VALUES(";
 +  while( SQLITE_ROW==sqlite3_step(pStmt) ){
 +    const char *zTab = (const char*)sqlite3_column_text(pStmt,0);
 +    if( zTab==0 ) continue;
 +    if( zLike && sqlite3_strlike(zLike, zTab, 0)!=0 ) continue;
 +    if( strncmp(zTab, "sqlite_",7)!=0 ){
 +      appendText(&sQuery,"SELECT * FROM ", 0);
 +      appendText(&sQuery,zTab,'"');
 +      appendText(&sQuery," NOT INDEXED;", 0);
 +    }else if( strcmp(zTab, "sqlite_schema")==0 ){
 +      appendText(&sQuery,"SELECT type,name,tbl_name,sql FROM sqlite_schema"
 +                 " ORDER BY name;", 0);
 +    }else if( strcmp(zTab, "sqlite_sequence")==0 ){
 +      appendText(&sQuery,"SELECT name,seq FROM sqlite_sequence"
 +                 " ORDER BY name;", 0);
 +    }else if( strcmp(zTab, "sqlite_stat1")==0 ){
 +      appendText(&sQuery,"SELECT tbl,idx,stat FROM sqlite_stat1"
 +                 " ORDER BY tbl,idx;", 0);
 +    }else if( strcmp(zTab, "sqlite_stat4")==0 ){
 +      appendText(&sQuery, "SELECT * FROM ", 0);
 +      appendText(&sQuery, zTab, 0);
 +      appendText(&sQuery, " ORDER BY tbl, idx, rowid;\n", 0);
 +    }
 +    appendText(&sSql, zSep, 0);
 +    appendText(&sSql, sQuery.z, '\'');
 +    sQuery.n = 0;
 +    appendText(&sSql, ",", 0);
 +    appendText(&sSql, zTab, '\'');
 +    zSep = "),(";
 +  }
 +  sqlite3_finalize(pStmt);
 +  if( bSeparate ){
 +    zSql = sqlite3_mprintf(
 +           "%s))"
 +           " SELECT lower(hex(sha3_query(a,%d))) AS hash, b AS label"
 +           "   FROM [sha3sum$query]",
 +           sSql.z, iSize);
 +  }else{
 +    zSql = sqlite3_mprintf(
 +           "%s))"
 +           " SELECT lower(hex(sha3_query(group_concat(a,''),%d))) AS hash"
 +           "   FROM [sha3sum$query]",
 +           sSql.z, iSize);
 +  }
 +  shell_check_oom(zSql);
 +  freeText(&sQuery);
 +  freeText(&sSql);
 +  if( bDebug ){
 +    utf8_printf(p->out, "%s\n", zSql);
 +  }else{
 +    shell_exec(p, zSql, 0);
 +  }
 +  sqlite3_free(zSql);
 +  return 0;
 +}
 +
 +/*****************
++ * The .selftest, .shell, .show, .shxopts, .stats and .system commands
 + */
 +CONDITION_COMMAND( shell !defined(SQLITE_NOHAVE_SYSTEM) );
++CONDITION_COMMAND( shxopts (SHELL_EXTENSIONS)!=0 );
 +CONDITION_COMMAND( system !defined(SQLITE_NOHAVE_SYSTEM) );
 +COLLECT_HELP_TEXT[
 +  ".selftest ?OPTIONS?      Run tests defined in the SELFTEST table",
 +  "    Options:",
 +  "       --init               Create a new SELFTEST table",
 +  "       -v                   Verbose output",
 +  ".shell CMD ARGS...       Run CMD ARGS... in a system shell",
 +  ".show                    Show the current values for various settings",
++  ".shxopts ?SIGNED_OPTS?   Show or alter shell extension options",
++  "   Run without arguments to see their self-descriptive names",
 +  ".stats ?ARG?             Show stats or turn stats on or off",
 +  "   off                      Turn off automatic stat display",
 +  "   on                       Turn on automatic stat display",
 +  "   stmt                     Show statement stats",
 +  "   vmstep                   Show the virtual machine step count only",
 +  ".system CMD ARGS...      Run CMD ARGS... in a system shell",
 +];
 +DISPATCHABLE_COMMAND( selftest 4 0 0 ){
 +  int rc;
 +  int bIsInit = 0;         /* True to initialize the SELFTEST table */
 +  int bVerbose = 0;        /* Verbose output */
 +  int bSelftestExists;     /* True if SELFTEST already exists */
 +  int i, k;                /* Loop counters */
 +  int nTest = 0;           /* Number of tests runs */
 +  int nErr = 0;            /* Number of errors seen */
 +  ShellText str;           /* Answer for a query */
 +  sqlite3_stmt *pStmt = 0; /* Query against the SELFTEST table */
 +
 +  open_db(p,0);
 +  for(i=1; i<nArg; i++){
 +    const char *z = azArg[i];
 +    if( z[0]=='-' && z[1]=='-' ) z++;
 +    if( strcmp(z,"-init")==0 ){
 +      bIsInit = 1;
 +    }else
        if( strcmp(z,"-v")==0 ){
          bVerbose++;
        }else
 -      {
 -        utf8_printf(stderr, "Unknown option \"%s\" on \"%s\"\n",
 -                    azArg[i], azArg[0]);
 -        raw_printf(stderr, "Should be one of: --init -v\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -    }
 -    if( sqlite3_table_column_metadata(p->db,"main","selftest",0,0,0,0,0,0)
 -           != SQLITE_OK ){
 -      bSelftestExists = 0;
 -    }else{
 -      bSelftestExists = 1;
 -    }
 -    if( bIsInit ){
 -      createSelftestTable(p);
 -      bSelftestExists = 1;
 -    }
 -    initText(&str);
 -    appendText(&str, "x", 0);
 -    for(k=bSelftestExists; k>=0; k--){
 -      if( k==1 ){
 -        rc = sqlite3_prepare_v2(p->db,
 -            "SELECT tno,op,cmd,ans FROM selftest ORDER BY tno",
 -            -1, &pStmt, 0);
 -      }else{
 -        rc = sqlite3_prepare_v2(p->db,
 -          "VALUES(0,'memo','Missing SELFTEST table - default checks only',''),"
 -          "      (1,'run','PRAGMA integrity_check','ok')",
 -          -1, &pStmt, 0);
 -      }
 -      if( rc ){
 -        raw_printf(stderr, "Error querying the selftest table\n");
 -        rc = 1;
 -        sqlite3_finalize(pStmt);
 -        goto meta_command_exit;
 -      }
 -      for(i=1; sqlite3_step(pStmt)==SQLITE_ROW; i++){
 -        int tno = sqlite3_column_int(pStmt, 0);
 -        const char *zOp = (const char*)sqlite3_column_text(pStmt, 1);
 -        const char *zSql = (const char*)sqlite3_column_text(pStmt, 2);
 -        const char *zAns = (const char*)sqlite3_column_text(pStmt, 3);
 -
 -        if( zOp==0 ) continue;
 -        if( zSql==0 ) continue;
 -        if( zAns==0 ) continue;
 -        k = 0;
 -        if( bVerbose>0 ){
 -          printf("%d: %s %s\n", tno, zOp, zSql);
 -        }
 -        if( strcmp(zOp,"memo")==0 ){
 -          utf8_printf(p->out, "%s\n", zSql);
 -        }else
 -        if( strcmp(zOp,"run")==0 ){
 -          char *zErrMsg = 0;
 -          str.n = 0;
 -          str.z[0] = 0;
 -          rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg);
 -          nTest++;
 -          if( bVerbose ){
 -            utf8_printf(p->out, "Result: %s\n", str.z);
 -          }
 -          if( rc || zErrMsg ){
 -            nErr++;
 -            rc = 1;
 -            utf8_printf(p->out, "%d: error-code-%d: %s\n", tno, rc, zErrMsg);
 -            sqlite3_free(zErrMsg);
 -          }else if( strcmp(zAns,str.z)!=0 ){
 -            nErr++;
 -            rc = 1;
 -            utf8_printf(p->out, "%d: Expected: [%s]\n", tno, zAns);
 -            utf8_printf(p->out, "%d:      Got: [%s]\n", tno, str.z);
 -          }
 -        }else
          {
 -          utf8_printf(stderr,
 -            "Unknown operation \"%s\" on selftest line %d\n", zOp, tno);
 -          rc = 1;
 -          break;
 +          *pzErr = sqlite3_mprintf
 +            ("Unknown option \"%s\" on \"%s\"\n"
 +             "Should be one of: --init -v\n", azArg[i], azArg[0]);
 +          return 1;
          }
 -      } /* End loop over rows of content from SELFTEST */
 -      sqlite3_finalize(pStmt);
 -    } /* End loop over k */
 -    freeText(&str);
 -    utf8_printf(p->out, "%d errors out of %d tests\n", nErr, nTest);
 -  }else
 -
 -  if( c=='s' && strncmp(azArg[0], "separator", n)==0 ){
 -    if( nArg<2 || nArg>3 ){
 -      raw_printf(stderr, "Usage: .separator COL ?ROW?\n");
 -      rc = 1;
 -    }
 -    if( nArg>=2 ){
 -      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator,
 -                       "%.*s", (int)ArraySize(p->colSeparator)-1, azArg[1]);
 +  }
 +  if( sqlite3_table_column_metadata(p->db,"main","selftest",0,0,0,0,0,0)
 +      != SQLITE_OK ){
 +    bSelftestExists = 0;
 +  }else{
 +    bSelftestExists = 1;
 +  }
 +  if( bIsInit ){
 +    createSelftestTable(p);
 +    bSelftestExists = 1;
 +  }
 +  initText(&str);
 +  appendText(&str, "x", 0);
 +  for(k=bSelftestExists; k>=0; k--){
 +    if( k==1 ){
 +      rc = sqlite3_prepare_v2(p->db,
 +              "SELECT tno,op,cmd,ans FROM selftest ORDER BY tno",
 +              -1, &pStmt, 0);
 +    }else{
 +      rc = sqlite3_prepare_v2(p->db,
 +              "VALUES(0,'memo','Missing SELFTEST table - default checks only',''),"
 +              "      (1,'run','PRAGMA integrity_check','ok')",
 +              -1, &pStmt, 0);
      }
 -    if( nArg>=3 ){
 -      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator,
 -                       "%.*s", (int)ArraySize(p->rowSeparator)-1, azArg[2]);
 +    if( rc ){
 +      *pzErr = shellMPrintf(0,"Error querying the selftest table\n");
 +      sqlite3_finalize(pStmt);
 +      return 1;
      }
 -  }else
 -
 -  if( c=='s' && n>=4 && strncmp(azArg[0],"sha3sum",n)==0 ){
 -    const char *zLike = 0;   /* Which table to checksum. 0 means everything */
 -    int i;                   /* Loop counter */
 -    int bSchema = 0;         /* Also hash the schema */
 -    int bSeparate = 0;       /* Hash each table separately */
 -    int iSize = 224;         /* Hash algorithm to use */
 -    int bDebug = 0;          /* Only show the query that would have run */
 -    sqlite3_stmt *pStmt;     /* For querying tables names */
 -    char *zSql;              /* SQL to be run */
 -    char *zSep;              /* Separator */
 -    ShellText sSql;          /* Complete SQL for the query to run the hash */
 -    ShellText sQuery;        /* Set of queries used to read all content */
 -    open_db(p, 0);
 -    for(i=1; i<nArg; i++){
 -      const char *z = azArg[i];
 -      if( z[0]=='-' ){
 -        z++;
 -        if( z[0]=='-' ) z++;
 -        if( strcmp(z,"schema")==0 ){
 -          bSchema = 1;
 -        }else
 -        if( strcmp(z,"sha3-224")==0 || strcmp(z,"sha3-256")==0
 -         || strcmp(z,"sha3-384")==0 || strcmp(z,"sha3-512")==0
 -        ){
 -          iSize = atoi(&z[5]);
 -        }else
 -        if( strcmp(z,"debug")==0 ){
 -          bDebug = 1;
 -        }else
 -        {
 -          utf8_printf(stderr, "Unknown option \"%s\" on \"%s\"\n",
 -                      azArg[i], azArg[0]);
 -          showHelp(p->out, azArg[0]);
 +    for(i=1; sqlite3_step(pStmt)==SQLITE_ROW; i++){
 +      int tno = sqlite3_column_int(pStmt, 0);
 +      const char *zOp = (const char*)sqlite3_column_text(pStmt, 1);
 +      const char *zSql = (const char*)sqlite3_column_text(pStmt, 2);
 +      const char *zAns = (const char*)sqlite3_column_text(pStmt, 3);
 +
 +      if( zOp==0 || zSql==0 || zAns==0 ) continue;
 +      k = 0;
 +      if( bVerbose>0 ){
 +        fprintf(STD_OUT, "%d: %s %s\n", tno, zOp, zSql);
 +      }
 +      if( strcmp(zOp,"memo")==0 ){
 +        utf8_printf(p->out, "%s\n", zSql);
-       }else
-         if( strcmp(zOp,"run")==0 ){
-           char *zErrMsg = 0;
-           str.n = 0;
-           str.z[0] = 0;
-           rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg);
-           nTest++;
-           if( bVerbose ){
-             utf8_printf(p->out, "Result: %s\n", str.z);
-           }
-           if( rc || zErrMsg ){
-             nErr++;
-             rc = 1;
-             utf8_printf(p->out, "%d: error-code-%d: %s\n", tno, rc, zErrMsg);
-             sqlite3_free(zErrMsg);
-           }else if( strcmp(zAns,str.z)!=0 ){
-             nErr++;
-             rc = 1;
-             utf8_printf(p->out, "%d: Expected: [%s]\n", tno, zAns);
-             utf8_printf(p->out, "%d:      Got: [%s]\n", tno, str.z);
-           }
-         }else
-           {
-             *pzErr = sqlite3_mprintf
-               ("Unknown operation \"%s\" on selftest line %d\n", zOp, tno);
-             rc = 1;
-             break;
-           }
++      }else if( strcmp(zOp,"run")==0 ){
++        char *zErrMsg = 0;
++        str.n = 0;
++        str.z[0] = 0;
++        rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg);
++        nTest++;
++        if( bVerbose ){
++          utf8_printf(p->out, "Result: %s\n", str.z);
++        }
++        if( rc || zErrMsg ){
++          nErr++;
++          rc = 1;
++          utf8_printf(p->out, "%d: error-code-%d: %s\n", tno, rc, zErrMsg);
++          sqlite3_free(zErrMsg);
++        }else if( strcmp(zAns,str.z)!=0 ){
++          nErr++;
+           rc = 1;
 -          goto meta_command_exit;
++          utf8_printf(p->out, "%d: Expected: [%s]\n", tno, zAns);
++          utf8_printf(p->out, "%d:      Got: [%s]\n", tno, str.z);
+         }
 -      }else if( zLike ){
 -        raw_printf(stderr, "Usage: .sha3sum ?OPTIONS? ?LIKE-PATTERN?\n");
 -        rc = 1;
 -        goto meta_command_exit;
+       }else{
 -        zLike = z;
 -        bSeparate = 1;
 -        if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 1;
++        *pzErr = sqlite3_mprintf
++          ("Unknown operation \"%s\" on selftest line %d\n", zOp, tno);
++        rc = 1;
++        break;
+       }
 -    }
 -    if( bSchema ){
 -      zSql = "SELECT lower(name) FROM sqlite_schema"
 -             " WHERE type='table' AND coalesce(rootpage,0)>1"
 -             " UNION ALL SELECT 'sqlite_schema'"
 -             " ORDER BY 1 collate nocase";
 -    }else{
 -      zSql = "SELECT lower(name) FROM sqlite_schema"
 -             " WHERE type='table' AND coalesce(rootpage,0)>1"
 -             " AND name NOT LIKE 'sqlite_%'"
 -             " ORDER BY 1 collate nocase";
 -    }
 -    sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    initText(&sQuery);
 -    initText(&sSql);
 -    appendText(&sSql, "WITH [sha3sum$query](a,b) AS(",0);
 -    zSep = "VALUES(";
 -    while( SQLITE_ROW==sqlite3_step(pStmt) ){
 -      const char *zTab = (const char*)sqlite3_column_text(pStmt,0);
 -      if( zTab==0 ) continue;
 -      if( zLike && sqlite3_strlike(zLike, zTab, 0)!=0 ) continue;
 -      if( strncmp(zTab, "sqlite_",7)!=0 ){
 -        appendText(&sQuery,"SELECT * FROM ", 0);
 -        appendText(&sQuery,zTab,'"');
 -        appendText(&sQuery," NOT INDEXED;", 0);
 -      }else if( strcmp(zTab, "sqlite_schema")==0 ){
 -        appendText(&sQuery,"SELECT type,name,tbl_name,sql FROM sqlite_schema"
 -                           " ORDER BY name;", 0);
 -      }else if( strcmp(zTab, "sqlite_sequence")==0 ){
 -        appendText(&sQuery,"SELECT name,seq FROM sqlite_sequence"
 -                           " ORDER BY name;", 0);
 -      }else if( strcmp(zTab, "sqlite_stat1")==0 ){
 -        appendText(&sQuery,"SELECT tbl,idx,stat FROM sqlite_stat1"
 -                           " ORDER BY tbl,idx;", 0);
 -      }else if( strcmp(zTab, "sqlite_stat4")==0 ){
 -        appendText(&sQuery, "SELECT * FROM ", 0);
 -        appendText(&sQuery, zTab, 0);
 -        appendText(&sQuery, " ORDER BY tbl, idx, rowid;\n", 0);
 -      }
 -      appendText(&sSql, zSep, 0);
 -      appendText(&sSql, sQuery.z, '\'');
 -      sQuery.n = 0;
 -      appendText(&sSql, ",", 0);
 -      appendText(&sSql, zTab, '\'');
 -      zSep = "),(";
 -    }
 +    } /* End loop over rows of content from SELFTEST */
      sqlite3_finalize(pStmt);
 -    if( bSeparate ){
 -      zSql = sqlite3_mprintf(
 -          "%s))"
 -          " SELECT lower(hex(sha3_query(a,%d))) AS hash, b AS label"
 -          "   FROM [sha3sum$query]",
 -          sSql.z, iSize);
 -    }else{
 -      zSql = sqlite3_mprintf(
 -          "%s))"
 -          " SELECT lower(hex(sha3_query(group_concat(a,''),%d))) AS hash"
 -          "   FROM [sha3sum$query]",
 -          sSql.z, iSize);
 -    }
 -    shell_check_oom(zSql);
 -    freeText(&sQuery);
 -    freeText(&sSql);
 -    if( bDebug ){
 -      utf8_printf(p->out, "%s\n", zSql);
 -    }else{
 -      shell_exec(p, zSql, 0);
 -    }
 -    sqlite3_free(zSql);
 -  }else
 +  } /* End loop over k */
 +  freeText(&str);
 +  utf8_printf(p->out, "%d errors out of %d tests\n", nErr, nTest);
 +  return rc > 0;
 +}
  
  #ifndef SQLITE_NOHAVE_SYSTEM
 -  if( c=='s'
 -   && (strncmp(azArg[0], "shell", n)==0 || strncmp(azArg[0],"system",n)==0)
 -  ){
 -    char *zCmd;
 -    int i, x;
 -    failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
 -    if( nArg<2 ){
 -      raw_printf(stderr, "Usage: .system COMMAND\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    zCmd = sqlite3_mprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]);
 -    for(i=2; i<nArg && zCmd!=0; i++){
 -      zCmd = sqlite3_mprintf(strchr(azArg[i],' ')==0?"%z %s":"%z \"%s\"",
 -                             zCmd, azArg[i]);
 -    }
 -    x = zCmd!=0 ? system(zCmd) : 1;
 -    sqlite3_free(zCmd);
 -    if( x ) raw_printf(stderr, "System command returns %d\n", x);
 -  }else
 -#endif /* !defined(SQLITE_NOHAVE_SYSTEM) */
 -
 -  if( c=='s' && strncmp(azArg[0], "show", n)==0 ){
 -    static const char *azBool[] = { "off", "on", "trigger", "full"};
 -    const char *zOut;
 -    int i;
 -    if( nArg!=1 ){
 -      raw_printf(stderr, "Usage: .show\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    utf8_printf(p->out, "%12.12s: %s\n","echo",
 -                                  azBool[ShellHasFlag(p, SHFLG_Echo)]);
 -    utf8_printf(p->out, "%12.12s: %s\n","eqp", azBool[p->autoEQP&3]);
 -    utf8_printf(p->out, "%12.12s: %s\n","explain",
 -         p->mode==MODE_Explain ? "on" : p->autoExplain ? "auto" : "off");
 -    utf8_printf(p->out,"%12.12s: %s\n","headers", azBool[p->showHeader!=0]);
 -    if( p->mode==MODE_Column
 -     || (p->mode>=MODE_Markdown && p->mode<=MODE_Box)
 -    ){
 -      utf8_printf
 -        (p->out, "%12.12s: %s --wrap %d --wordwrap %s --%squote\n", "mode",
 -         modeDescr[p->mode], p->cmOpts.iWrap,
 -         p->cmOpts.bWordWrap ? "on" : "off",
 -         p->cmOpts.bQuote ? "" : "no");
 -    }else{
 -      utf8_printf(p->out, "%12.12s: %s\n","mode", modeDescr[p->mode]);
 -    }
 -    utf8_printf(p->out, "%12.12s: ", "nullvalue");
 -      output_c_string(p->out, p->nullValue);
 -      raw_printf(p->out, "\n");
 -    utf8_printf(p->out,"%12.12s: %s\n","output",
 -            strlen30(p->outfile) ? p->outfile : "stdout");
 -    utf8_printf(p->out,"%12.12s: ", "colseparator");
 -      output_c_string(p->out, p->colSeparator);
 -      raw_printf(p->out, "\n");
 -    utf8_printf(p->out,"%12.12s: ", "rowseparator");
 -      output_c_string(p->out, p->rowSeparator);
 -      raw_printf(p->out, "\n");
 -    switch( p->statsOn ){
 -      case 0:  zOut = "off";     break;
 -      default: zOut = "on";      break;
 -      case 2:  zOut = "stmt";    break;
 -      case 3:  zOut = "vmstep";  break;
 -    }
 -    utf8_printf(p->out, "%12.12s: %s\n","stats", zOut);
 -    utf8_printf(p->out, "%12.12s: ", "width");
 -    for (i=0;i<p->nWidth;i++) {
 -      raw_printf(p->out, "%d ", p->colWidth[i]);
 -    }
 -    raw_printf(p->out, "\n");
 -    utf8_printf(p->out, "%12.12s: %s\n", "filename",
 -                p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : "");
 -  }else
 -#if SHELL_EXTENSIONS
 -  if( c=='s' && strncmp(azArg[0], "shxopts", n)==0 ){
 -    rc = shxoptsCommand(azArg, nArg, p, 0);
 -  }else
 +static int shellOut(char *azArg[], int nArg, ShellState *p, char **pzErr){
 +  char *zCmd;
 +  int i, x;
 +  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
 +  zCmd = sqlite3_mprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]);
 +  shell_check_oom(zCmd);
 +  for(i=2; i<nArg; i++){
 +    zCmd = sqlite3_mprintf(strchr(azArg[i],' ')==0?"%z %s":"%z \"%s\"",
 +                           zCmd, azArg[i]);
 +    shell_check_oom(zCmd);
 +  }
 +  x = system(zCmd);
 +  sqlite3_free(zCmd);
 +  if( x ) raw_printf(STD_ERR, "%s command returns %d\n", azArg[0], x);
 +  return 0;
 +}
  #endif
 -  if( c=='s' && strncmp(azArg[0], "stats", n)==0 ){
 -    if( nArg==2 ){
 -      if( strcmp(azArg[1],"stmt")==0 ){
 -        p->statsOn = 2;
 -      }else if( strcmp(azArg[1],"vmstep")==0 ){
 -        p->statsOn = 3;
 -      }else{
 -        p->statsOn = (u8)booleanValue(azArg[1]);
 +DISPATCHABLE_COMMAND( shell ? 2 0 ){
 +  return shellOut(azArg, nArg, p, pzErr);
 +}
++DISPATCHABLE_COMMAND( shxopts 3 0 0 ){
++  static struct { const char *name; u8 mask; } shopts[] = {
++#if SHELL_DYNAMIC_COMMANDS
++    {"dyn_cmds", 1<<SHEXT_DYNCMDS_BIT},
++#endif
++#if SHELL_EXTENDED_PARSING
++    {"parsing", 1<<SHEXT_PARSING_BIT},
++#endif
++#if SHELL_VARIABLE_EXPANSION
++    {"dot_vars", 1<<SHEXT_VAREXP_BIT},
++#endif
++    {"all_opts", SHELL_ALL_EXTENSIONS}
++  };
++  const char *zMoan = 0, *zAbout = 0;
++  int ia, io;
++  if( nArg>1 ){
++    for( ia=1; ia<nArg; ++ia ){
++      char cs = azArg[ia][0];
++      if( cs!='+' && cs!='-' ){
++        zMoan = "arguments must have a sign prefix.";
++        zAbout = azArg[0];
++        goto moan_error;
+       }
 -    }else if( nArg==1 ){
 -      display_stats(p->db, p, 0);
++      for( io=0; io<ArraySize(shopts); ++io ){
++        if( strcmp(azArg[ia]+1, shopts[io].name)==0 ){
++          if( cs=='+' ) p->bExtendedDotCmds |= shopts[io].mask;
++          else p->bExtendedDotCmds &= ~shopts[io].mask;
++          break;
++        }
++      }
++      if( io==ArraySize(shopts) ){
++        zAbout = azArg[ia];
++        zMoan = "is not a recognized option name";
++        goto moan_error;
++      }
++    }
++  }else{
++    raw_printf(p->out,
++               "     name    value  \"-shxopts set\"\n"
++               "   --------  -----  ---------------\n");
++    for( io=0; io<ArraySize(shopts); ++io ){
++      unsigned m = shopts[io].mask;
++      unsigned v = ((p->bExtendedDotCmds & m) == m)? 1 : 0;
++      raw_printf(p->out,
++                 "  %9s   %2d    \"-shxopts 0x%02X\"\n",
++                 shopts[io].name,  v, m);
++    }
++  }
++  return 0;
++ moan_error:
++  raw_printf(STD_ERR, "Error: %s %s\n", zAbout, zMoan);
++  return 1;
++}
 +DISPATCHABLE_COMMAND( system ? 2 0 ){
 +  return shellOut(azArg, nArg, p, pzErr);
 +}
 +DISPATCHABLE_COMMAND( show ? 1 1 ){
 +  static const char *azBool[] = { "off", "on", "trigger", "full"};
 +  const char *zOut;
 +  int i;
 +  utf8_printf(p->out, "%12.12s: %s\n","echo",
 +              azBool[ShellHasFlag(p, SHFLG_Echo)]);
 +  utf8_printf(p->out, "%12.12s: %s\n","eqp", azBool[p->autoEQP&3]);
 +  utf8_printf(p->out, "%12.12s: %s\n","explain",
 +              p->mode==MODE_Explain ? "on" : p->autoExplain ? "auto" : "off");
 +  utf8_printf(p->out,"%12.12s: %s\n","headers", azBool[p->showHeader!=0]);
 +  zOut = modeDescr[p->mode].zModeName;
 +  i = strlen30(zOut) - modeDescr[p->mode].bDepluralize;
-   if( p->mode==MODE_Column
-       || (p->mode>=MODE_Markdown && p->mode<=MODE_Box)
-       ){
++  if( MODE_IS_COLUMNAR(p->mode) ){
 +    utf8_printf
 +      (p->out, "%12.12s: %.*s --wrap %d --wordwrap %s --%squote\n", "mode",
 +       i, zOut, p->cmOpts.iWrap,
 +       p->cmOpts.bWordWrap ? "on" : "off",
 +       p->cmOpts.bQuote ? "" : "no");
 +  }else{
 +    utf8_printf(p->out, "%12.12s: %.*s\n","mode", i, zOut);
 +  }
 +  utf8_printf(p->out, "%12.12s: ", "nullvalue");
 +  output_c_string(p->out, p->nullValue);
 +  raw_printf(p->out, "\n");
 +  utf8_printf(p->out,"%12.12s: %s\n","output",
 +              strlen30(p->outfile) ? p->outfile : "stdout");
 +  utf8_printf(p->out,"%12.12s: ", "colseparator");
 +  output_c_string(p->out, p->colSeparator);
 +  raw_printf(p->out, "\n");
 +  utf8_printf(p->out,"%12.12s: ", "rowseparator");
 +  output_c_string(p->out, p->rowSeparator);
 +  raw_printf(p->out, "\n");
 +  switch( p->statsOn ){
 +  case 0:  zOut = "off";     break;
 +  default: zOut = "on";      break;
 +  case 2:  zOut = "stmt";    break;
 +  case 3:  zOut = "vmstep";  break;
 +  }
 +  utf8_printf(p->out, "%12.12s: %s\n","stats", zOut);
 +  utf8_printf(p->out, "%12.12s: ", "width");
 +  for (i=0;i<p->nWidth;i++) {
 +    raw_printf(p->out, "%d ", p->colWidth[i]);
 +  }
 +  raw_printf(p->out, "\n");
 +  utf8_printf(p->out, "%12.12s: %s\n", "filename",
 +              p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : "");
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( stats ? 0 0 ){
 +  if( nArg==2 ){
 +    if( strcmp(azArg[1],"stmt")==0 ){
 +      p->statsOn = 2;
 +    }else if( strcmp(azArg[1],"vmstep")==0 ){
 +      p->statsOn = 3;
      }else{
 -      raw_printf(stderr, "Usage: .stats ?on|off|stmt|vmstep?\n");
 -      rc = 1;
 +      p->statsOn = (u8)booleanValue(azArg[1]);
      }
 -  }else
 +  }else if( nArg==1 ){
 +    display_stats(p->db, p, 0);
 +  }else{
 +    *pzErr = shellMPrintf(0,"Usage: .stats ?on|off|stmt|vmstep?\n");
 +    return 1;
 +  }
 +  return 0;
 +}
  
 -  if( (c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0)
 -   || (c=='i' && (strncmp(azArg[0], "indices", n)==0
 -                 || strncmp(azArg[0], "indexes", n)==0) )
 -  ){
 -    sqlite3_stmt *pStmt;
 -    char **azResult;
 -    int nRow, nAlloc;
 -    int ii;
 -    ShellText s;
 -    initText(&s);
 -    open_db(p, 0);
 -    rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
 -    if( rc ){
 -      sqlite3_finalize(pStmt);
 -      return shellDatabaseError(p->db);
 -    }
 +/*****************
 + * The .tables, .views, .indices and .indexes command
 + * These are together because they share implementation or are aliases.
 + */
 +COLLECT_HELP_TEXT[
 +  ".indexes ?TABLE?         Show names of indexes",
 +  "                           If TABLE is specified, only show indexes for",
 +  "                           tables matching TABLE using the LIKE operator.",
 +];
 +static int showTableLike(char *azArg[], int nArg, ShellState *p,
 +                         char **pzErr, char ot){
 +  int rc;
 +  sqlite3_stmt *pStmt;
 +  char **azResult;
 +  int nRow, nAlloc;
 +  int ii;
 +  ShellText s;
 +  initText(&s);
 +  open_db(p, 0);
 +  rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
 +  if( rc ){
 +    sqlite3_finalize(pStmt);
 +    return shellDatabaseError(p->db);
 +  }
  
 -    if( nArg>2 && c=='i' ){
 -      /* It is an historical accident that the .indexes command shows an error
 -      ** when called with the wrong number of arguments whereas the .tables
 -      ** command does not. */
 -      raw_printf(stderr, "Usage: .indexes ?LIKE-PATTERN?\n");
 -      rc = 1;
 -      sqlite3_finalize(pStmt);
 -      goto meta_command_exit;
 -    }
 -    for(ii=0; sqlite3_step(pStmt)==SQLITE_ROW; ii++){
 -      const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1);
 -      if( zDbName==0 ) continue;
 -      if( s.z && s.z[0] ) appendText(&s, " UNION ALL ", 0);
 -      if( sqlite3_stricmp(zDbName, "main")==0 ){
 -        appendText(&s, "SELECT name FROM ", 0);
 -      }else{
 -        appendText(&s, "SELECT ", 0);
 -        appendText(&s, zDbName, '\'');
 -        appendText(&s, "||'.'||name FROM ", 0);
 -      }
 -      appendText(&s, zDbName, '"');
 -      appendText(&s, ".sqlite_schema ", 0);
 -      if( c=='t' ){
 -        appendText(&s," WHERE type IN ('table','view')"
 -                      "   AND name NOT LIKE 'sqlite_%'"
 -                      "   AND name LIKE ?1", 0);
 -      }else{
 -        appendText(&s," WHERE type='index'"
 -                      "   AND tbl_name LIKE ?1", 0);
 -      }
 -    }
 -    rc = sqlite3_finalize(pStmt);
 -    if( rc==SQLITE_OK ){
 -      appendText(&s, " ORDER BY 1", 0);
 -      rc = sqlite3_prepare_v2(p->db, s.z, -1, &pStmt, 0);
 -    }
 -    freeText(&s);
 -    if( rc ) return shellDatabaseError(p->db);
 -
 -    /* Run the SQL statement prepared by the above block. Store the results
 -    ** as an array of nul-terminated strings in azResult[].  */
 -    nRow = nAlloc = 0;
 -    azResult = 0;
 -    if( nArg>1 ){
 -      sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT);
 +  if( nArg>2 && ot=='i' ){
 +    /* It is an historical accident that the .indexes command shows an error
 +    ** when called with the wrong number of arguments whereas the .tables
 +    ** command does not. */
 +    *pzErr = shellMPrintf(0,"Usage: .indexes ?LIKE-PATTERN?\n");
 +    sqlite3_finalize(pStmt);
 +    return 1;
 +  }
 +  for(ii=0; sqlite3_step(pStmt)==SQLITE_ROW; ii++){
 +    const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1);
 +    const char *zFilter = "";
 +    const char *zSystem = " AND name NOT LIKE 'sqlite_%'";
 +    if( zDbName==0 ) continue;
 +    if( s.z && s.z[0] ) appendText(&s, " UNION ALL ", 0);
 +    if( sqlite3_stricmp(zDbName, "main")==0 ){
 +      appendText(&s, "SELECT name FROM ", 0);
      }else{
 -      sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC);
 +      appendText(&s, "SELECT ", 0);
 +      appendText(&s, zDbName, '\'');
 +      appendText(&s, "||'.'||name FROM ", 0);
 +    }
 +    appendText(&s, zDbName, '"');
 +    appendText(&s, ".sqlite_schema ", 0);
 +    switch (ot) {
 +    case 'i':
 +      zFilter = "'index'";
 +      break;
 +#ifndef LEGACY_TABLES_LISTING
 +    case 't':
 +      zFilter = "'table'";
 +      break;
 +    case 'v':
 +      zFilter = "'view'";
 +      break;
 +#endif
 +    case 's':
 +      zSystem = " AND name LIKE 'sqlite_%'";
 +      /* fall thru */
 +    case 'T':
 +      zFilter = "'table','view'";
 +      break;
 +    default:
 +      assert(0);
      }
 -    while( sqlite3_step(pStmt)==SQLITE_ROW ){
 -      if( nRow>=nAlloc ){
 -        char **azNew;
 -        int n2 = nAlloc*2 + 10;
 -        azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2);
 -        shell_check_oom(azNew);
 -        nAlloc = n2;
 -        azResult = azNew;
 -      }
 -      azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 -      shell_check_oom(azResult[nRow]);
 -      nRow++;
 -    }
 -    if( sqlite3_finalize(pStmt)!=SQLITE_OK ){
 -      rc = shellDatabaseError(p->db);
 -    }
 -
 -    /* Pretty-print the contents of array azResult[] to the output */
 -    if( rc==0 && nRow>0 ){
 -      int len, maxlen = 0;
 -      int i, j;
 -      int nPrintCol, nPrintRow;
 -      for(i=0; i<nRow; i++){
 -        len = strlen30(azResult[i]);
 -        if( len>maxlen ) maxlen = len;
 -      }
 -      nPrintCol = 80/(maxlen+2);
 -      if( nPrintCol<1 ) nPrintCol = 1;
 -      nPrintRow = (nRow + nPrintCol - 1)/nPrintCol;
 -      for(i=0; i<nPrintRow; i++){
 -        for(j=i; j<nRow; j+=nPrintRow){
 -          char *zSp = j<nPrintRow ? "" : "  ";
 -          utf8_printf(p->out, "%s%-*s", zSp, maxlen,
 -                      azResult[j] ? azResult[j]:"");
 -        }
 -        raw_printf(p->out, "\n");
 +    appendText(&s, " WHERE type IN(", 0);
 +    appendText(&s, zFilter, 0);
 +    appendText(&s, ") AND name LIKE ?1", 0);
 +    appendText(&s, zSystem, 0);
 +  }
 +  rc = sqlite3_finalize(pStmt);
 +  if( rc==SQLITE_OK ){
 +    appendText(&s, " ORDER BY 1", 0);
 +    rc = sqlite3_prepare_v2(p->db, s.z, -1, &pStmt, 0);
 +  }
 +  freeText(&s);
 +  if( rc ) return shellDatabaseError(p->db);
 +
 +  /* Run the SQL statement prepared by the above block. Store the results
 +  ** as an array of nul-terminated strings in azResult[].  */
 +  nRow = nAlloc = 0;
 +  azResult = 0;
 +  if( nArg>1 ){
 +    sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT);
 +  }else{
 +    sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC);
 +  }
 +  while( sqlite3_step(pStmt)==SQLITE_ROW ){
 +    if( nRow>=nAlloc ){
 +      char **azNew;
 +      int n2 = nAlloc*2 + 10;
 +      azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2);
 +      shell_check_oom(azNew);
 +      nAlloc = n2;
 +      azResult = azNew;
 +    }
 +    azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 +    shell_check_oom(azResult[nRow]);
 +    nRow++;
 +  }
 +  if( sqlite3_finalize(pStmt)!=SQLITE_OK ){
 +    rc = shellDatabaseError(p->db);
 +  }
 +
 +  /* Pretty-print the contents of array azResult[] to the output */
 +  if( rc==0 && nRow>0 ){
 +    int len, maxlen = 0;
 +    int i, j;
 +    int nPrintCol, nPrintRow;
 +    for(i=0; i<nRow; i++){
 +      len = strlen30(azResult[i]);
 +      if( len>maxlen ) maxlen = len;
 +    }
 +    nPrintCol = 80/(maxlen+2);
 +    if( nPrintCol<1 ) nPrintCol = 1;
 +    nPrintRow = (nRow + nPrintCol - 1)/nPrintCol;
 +    for(i=0; i<nPrintRow; i++){
 +      for(j=i; j<nRow; j+=nPrintRow){
 +        char *zSp = j<nPrintRow ? "" : "  ";
 +        utf8_printf(p->out, "%s%-*s", zSp, maxlen,
 +                    azResult[j] ? azResult[j]:"");
        }
 +      raw_printf(p->out, "\n");
      }
 +  }
  
 -    for(ii=0; ii<nRow; ii++) sqlite3_free(azResult[ii]);
 -    sqlite3_free(azResult);
 -  }else
 +  for(ii=0; ii<nRow; ii++) sqlite3_free(azResult[ii]);
 +  sqlite3_free(azResult);
 +  return 0;
 +}
  
 -  /* Begin redirecting output to the file "testcase-out.txt" */
 -  if( c=='t' && strcmp(azArg[0],"testcase")==0 ){
 -    output_reset(p);
 -    p->out = output_file_open("testcase-out.txt", 0);
 -    if( p->out==0 ){
 -      raw_printf(stderr, "Error: cannot open 'testcase-out.txt'\n");
 +COLLECT_HELP_TEXT[
 +#ifndef LEGACY_TABLES_LISTING
 +  ".tables ?FLAG? ?TVLIKE?  List names of tables and/or views",
 +  "   FLAG may be -t, -v or -s to list only tables, views or system tables",
 +  "   TVLIKE may restrict the listing to names matching given LIKE pattern",
 +#else
 +  ".tables ?TABLE?          List names of tables matching LIKE pattern TABLE",
 +#endif
 +];
 +DISPATCHABLE_COMMAND( tables 2 1 3 ){
 +  char objType = 'T';
 +#ifndef LEGACY_TABLES_LISTING
 +  if( nArg>1 && azArg[1][0]=='-' && azArg[1][1]!=0 && azArg[1][2]==0 ){
 +    char c = azArg[1][1];
 +    switch (c){
 +    case 's':
 +    case 't':
 +    case 'v':
 +      objType = c;
 +      ++azArg;
 +      --nArg;
 +      break;
 +    default:
 +      return SHELL_INVALID_ARGS;
      }
 -    if( nArg>=2 ){
 -      sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", azArg[1]);
 -    }else{
 -      sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?");
 +  }
 +#endif
 +  return showTableLike(azArg, nArg, p, pzErr, objType);
 +}
 +DISPATCHABLE_COMMAND( indexes 3 1 2 ){
 +  return showTableLike(azArg, nArg, p, pzErr, 'i');
 +}
 +DISPATCHABLE_COMMAND( indices 3 1 2 ){
 +  return showTableLike(azArg, nArg, p, pzErr, 'i');
 +}
 +
 +/*****************
 + * The .unmodule command
 + */
 +CONDITION_COMMAND( unmodule defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_VIRTUALTABLE) );
 +COLLECT_HELP_TEXT[
 +  ".unmodule NAME ...       Unregister virtual table modules",
 +  "    --allexcept             Unregister everything except those named",
 +];
 +DISPATCHABLE_COMMAND( unmodule ? 2 0 ){
 +  int ii;
 +  int lenOpt;
 +  char *zOpt;
 +  open_db(p, 0);
 +  zOpt = azArg[1];
 +  if( zOpt[0]=='-' && zOpt[1]=='-' && zOpt[2]!=0 ) zOpt++;
 +  lenOpt = (int)strlen(zOpt);
 +  if( lenOpt>=3 && strncmp(zOpt, "-allexcept",lenOpt)==0 ){
 +    assert( azArg[nArg]==0 );
 +    sqlite3_drop_modules(p->db, nArg>2 ? (const char**)(azArg+2) : 0);
 +  }else{
 +    for(ii=1; ii<nArg; ii++){
 +      sqlite3_create_module(p->db, azArg[ii], 0, 0);
      }
 -  }else
 +  }
 +  return 0;
 +}
 +
 +/*****************
 + * The .testcase, .testctrl, .timeout, .timer and .trace commands
 + */
 +CONDITION_COMMAND( testctrl !defined(SQLITE_UNTESTABLE) );
 +CONDITION_COMMAND( trace !defined(SQLITE_OMIT_TRACE) );
 +COLLECT_HELP_TEXT[
 +  ".testcase NAME           Begin redirecting output to 'testcase-out.txt'",
 +  ".testctrl CMD ...        Run various sqlite3_test_control() operations",
 +  "                            Run \".testctrl\" with no arguments for details",
 +  ".timeout MS              Try opening locked tables for MS milliseconds",
 +  ".timer on|off            Turn SQL timer on or off",
 +  ".trace ?OPTIONS?         Output each SQL statement as it is run",
 +  "    FILE                    Send output to FILE",
 +  "    stdout                  Send output to stdout",
 +  "    stderr                  Send output to stderr",
 +  "    off                     Disable tracing",
 +  "    --expanded              Expand query parameters",
 +#ifdef SQLITE_ENABLE_NORMALIZE
 +  "    --normalized            Normal the SQL statements",
 +#endif
 +  "    --plain                 Show SQL as it is input",
 +  "    --stmt                  Trace statement execution (SQLITE_TRACE_STMT)",
 +  "    --profile               Profile statements (SQLITE_TRACE_PROFILE)",
 +  "    --row                   Trace each row (SQLITE_TRACE_ROW)",
 +  "    --close                 Trace connection close (SQLITE_TRACE_CLOSE)",
 +];
  
 -#ifndef SQLITE_UNTESTABLE
 -  if( c=='t' && n>=8 && strncmp(azArg[0], "testctrl", n)==0 ){
 -    static const struct {
 -       const char *zCtrlName;   /* Name of a test-control option */
 -       int ctrlCode;            /* Integer code for that option */
 -       int unSafe;              /* Not valid for --safe mode */
 -       const char *zUsage;      /* Usage notes */
 -    } aCtrl[] = {
 -      { "always",             SQLITE_TESTCTRL_ALWAYS, 1,     "BOOLEAN"         },
 -      { "assert",             SQLITE_TESTCTRL_ASSERT, 1,     "BOOLEAN"         },
 -    /*{ "benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS,1, ""        },*/
 -    /*{ "bitvec_test",        SQLITE_TESTCTRL_BITVEC_TEST, 1,  ""              },*/
 -      { "byteorder",          SQLITE_TESTCTRL_BYTEORDER, 0,  ""                },
 -      { "extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,0,"BOOLEAN"  },
 -    /*{ "fault_install",      SQLITE_TESTCTRL_FAULT_INSTALL, 1,""              },*/
 -      { "imposter",         SQLITE_TESTCTRL_IMPOSTER,1,"SCHEMA ON/OFF ROOTPAGE"},
 -      { "internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS,0,""          },
 -      { "localtime_fault",    SQLITE_TESTCTRL_LOCALTIME_FAULT,0,"BOOLEAN"      },
 -      { "never_corrupt",      SQLITE_TESTCTRL_NEVER_CORRUPT,1, "BOOLEAN"       },
 -      { "optimizations",      SQLITE_TESTCTRL_OPTIMIZATIONS,0,"DISABLE-MASK"   },
 +/* Begin redirecting output to the file "testcase-out.txt" */
 +DISPATCHABLE_COMMAND( testcase ? 0 0 ){
 +  output_reset(p);
 +  p->out = output_file_open("testcase-out.txt", 0);
 +  if( p->out==0 ){
 +    raw_printf(STD_ERR, "Error: cannot open 'testcase-out.txt'\n");
 +  }
 +  if( nArg>=2 ){
 +    sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", azArg[1]);
 +  }else{
 +    sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?");
 +  }
 +}
 +DISPATCHABLE_COMMAND( testctrl ? 0 0 ){
 +  static const struct {
 +    const char *zCtrlName;   /* Name of a test-control option */
 +    int ctrlCode;            /* Integer code for that option */
 +    int unSafe;              /* Not valid for --safe mode */
 +    const char *zUsage;      /* Usage notes */
 +  } aCtrl[] = {
 +    { "always",             SQLITE_TESTCTRL_ALWAYS, 1,     "BOOLEAN"         },
 +    { "assert",             SQLITE_TESTCTRL_ASSERT, 1,     "BOOLEAN"         },
 +  /*{ "benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS,1, ""        },*/
 +  /*{ "bitvec_test",        SQLITE_TESTCTRL_BITVEC_TEST, 1,  ""              },*/
 +    { "byteorder",          SQLITE_TESTCTRL_BYTEORDER, 0,  ""                },
 +    { "extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,0,"BOOLEAN"  },
 +  /*{ "fault_install",      SQLITE_TESTCTRL_FAULT_INSTALL, 1,""              },*/
 +    { "imposter",         SQLITE_TESTCTRL_IMPOSTER,1,"SCHEMA ON/OFF ROOTPAGE"},
 +    { "internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS,0,""          },
 +    { "localtime_fault",    SQLITE_TESTCTRL_LOCALTIME_FAULT,0,"BOOLEAN"      },
 +    { "never_corrupt",      SQLITE_TESTCTRL_NEVER_CORRUPT,1, "BOOLEAN"       },
 +    { "optimizations",      SQLITE_TESTCTRL_OPTIMIZATIONS,0,"DISABLE-MASK"   },
  #ifdef YYCOVERAGE
 -      { "parser_coverage",    SQLITE_TESTCTRL_PARSER_COVERAGE,0,""             },
 -#endif
 -      { "pending_byte",       SQLITE_TESTCTRL_PENDING_BYTE,0, "OFFSET  "       },
 -      { "prng_restore",       SQLITE_TESTCTRL_PRNG_RESTORE,0, ""               },
 -      { "prng_save",          SQLITE_TESTCTRL_PRNG_SAVE,   0, ""               },
 -      { "prng_seed",          SQLITE_TESTCTRL_PRNG_SEED,   0, "SEED ?db?"      },
 -      { "seek_count",         SQLITE_TESTCTRL_SEEK_COUNT,  0, ""               },
 -      { "sorter_mmap",        SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX"           },
 -      { "tune",               SQLITE_TESTCTRL_TUNE,        1, "ID VALUE"       },
 -    };
 -    int testctrl = -1;
 -    int iCtrl = -1;
 -    int rc2 = 0;    /* 0: usage.  1: %d  2: %x  3: no-output */
 -    int isOk = 0;
 -    int i, n2;
 -    const char *zCmd = 0;
 +    { "parser_coverage",    SQLITE_TESTCTRL_PARSER_COVERAGE,0,""             },
 +#endif
 +    { "pending_byte",       SQLITE_TESTCTRL_PENDING_BYTE,0, "OFFSET  "       },
 +    { "prng_restore",       SQLITE_TESTCTRL_PRNG_RESTORE,0, ""               },
 +    { "prng_save",          SQLITE_TESTCTRL_PRNG_SAVE,   0, ""               },
 +    { "prng_seed",          SQLITE_TESTCTRL_PRNG_SEED,   0, "SEED ?db?"      },
 +    { "seek_count",         SQLITE_TESTCTRL_SEEK_COUNT,  0, ""               },
 +    { "sorter_mmap",        SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX"           },
 +    { "tune",               SQLITE_TESTCTRL_TUNE,        1, "ID VALUE"       },
 +  };
 +  int testctrl = -1;
 +  int iCtrl = -1;
 +  int rc2 = 0;    /* 0: usage.  1: %d  2: %x  3: no-output */
 +  int isOk = 0;
 +  int i, n2;
 +  const char *zCmd = 0;
  
 -    open_db(p, 0);
 -    zCmd = nArg>=2 ? azArg[1] : "help";
 +  open_db(p, 0);
 +  zCmd = nArg>=2 ? azArg[1] : "help";
 +
 +  /* The argument can optionally begin with "-" or "--" */
 +  if( zCmd[0]=='-' && zCmd[1] ){
 +    zCmd++;
 +    if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 +  }
  
 -    /* The argument can optionally begin with "-" or "--" */
 -    if( zCmd[0]=='-' && zCmd[1] ){
 -      zCmd++;
 -      if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 +  /* --help lists all test-controls */
 +  if( strcmp(zCmd,"help")==0 ){
 +    utf8_printf(p->out, "Available test-controls:\n");
 +    for(i=0; i<ArraySize(aCtrl); i++){
 +      utf8_printf(p->out, "  .testctrl %s %s\n",
 +                  aCtrl[i].zCtrlName, aCtrl[i].zUsage);
      }
 +    return 1;
 +  }
  
 -    /* --help lists all test-controls */
 -    if( strcmp(zCmd,"help")==0 ){
 -      utf8_printf(p->out, "Available test-controls:\n");
 -      for(i=0; i<ArraySize(aCtrl); i++){
 -        utf8_printf(p->out, "  .testctrl %s %s\n",
 -                    aCtrl[i].zCtrlName, aCtrl[i].zUsage);
 +  /* convert testctrl 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( strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
 +      if( testctrl<0 ){
 +        testctrl = aCtrl[i].ctrlCode;
 +        iCtrl = i;
 +      }else{
 +        *pzErr = sqlite3_mprintf
 +          ("Error: ambiguous test-control: \"%s\"\n"
 +           "Use \".testctrl --help\" for help\n", zCmd);
 +        return 1;
        }
 -      rc = 1;
 -      goto meta_command_exit;
      }
-        p->lineno, aCtrl[iCtrl].zCtrlName);
 +  }
 +  if( testctrl<0 ){
 +    utf8_printf(STD_ERR,"Error: unknown test-control: %s\n"
 +                "Use \".testctrl --help\" for help\n", zCmd);
 +  }else if( aCtrl[iCtrl].unSafe && p->bSafeMode ){
 +    utf8_printf(STD_ERR,
 +       "line %d: \".testctrl %s\" may not be used in safe mode\n",
++       p->pInSource->lineno, aCtrl[iCtrl].zCtrlName);
 +    exit(1);
 +  }else{
 +    switch(testctrl){
  
 -    /* convert testctrl 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( strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
 -        if( testctrl<0 ){
 -          testctrl = aCtrl[i].ctrlCode;
 -          iCtrl = i;
 -        }else{
 -          utf8_printf(stderr, "Error: ambiguous test-control: \"%s\"\n"
 -                              "Use \".testctrl --help\" for help\n", zCmd);
 -          rc = 1;
 -          goto meta_command_exit;
 -        }
 +      /* sqlite3_test_control(int, db, int) */
 +    case SQLITE_TESTCTRL_OPTIMIZATIONS:
 +      if( nArg==3 ){
 +        unsigned int opt = (unsigned int)strtol(azArg[2], 0, 0);
 +        rc2 = sqlite3_test_control(testctrl, p->db, opt);
 +        isOk = 3;
        }
 -    }
 -    if( testctrl<0 ){
 -      utf8_printf(stderr,"Error: unknown test-control: %s\n"
 -                         "Use \".testctrl --help\" for help\n", zCmd);
 -    }else if( aCtrl[iCtrl].unSafe && p->bSafeMode ){
 -      utf8_printf(stderr,
 -         "line %d: \".testctrl %s\" may not be used in safe mode\n",
 -         p->pInSource->lineno, aCtrl[iCtrl].zCtrlName);
 -      exit(1);
 -    }else{
 -      switch(testctrl){
 -
 -        /* sqlite3_test_control(int, db, int) */
 -        case SQLITE_TESTCTRL_OPTIMIZATIONS:
 -          if( nArg==3 ){
 -            unsigned int opt = (unsigned int)strtol(azArg[2], 0, 0);
 -            rc2 = sqlite3_test_control(testctrl, p->db, opt);
 -            isOk = 3;
 -          }
 -          break;
 +      break;
  
 -        /* sqlite3_test_control(int) */
 -        case SQLITE_TESTCTRL_PRNG_SAVE:
 -        case SQLITE_TESTCTRL_PRNG_RESTORE:
 -        case SQLITE_TESTCTRL_BYTEORDER:
 -          if( nArg==2 ){
 -            rc2 = sqlite3_test_control(testctrl);
 -            isOk = testctrl==SQLITE_TESTCTRL_BYTEORDER ? 1 : 3;
 -          }
 -          break;
 +      /* sqlite3_test_control(int) */
 +    case SQLITE_TESTCTRL_PRNG_SAVE:
 +    case SQLITE_TESTCTRL_PRNG_RESTORE:
 +    case SQLITE_TESTCTRL_BYTEORDER:
 +      if( nArg==2 ){
 +        rc2 = sqlite3_test_control(testctrl);
 +        isOk = testctrl==SQLITE_TESTCTRL_BYTEORDER ? 1 : 3;
 +      }
 +      break;
  
 -        /* sqlite3_test_control(int, uint) */
 -        case SQLITE_TESTCTRL_PENDING_BYTE:
 -          if( nArg==3 ){
 -            unsigned int opt = (unsigned int)integerValue(azArg[2]);
 -            rc2 = sqlite3_test_control(testctrl, opt);
 -            isOk = 3;
 -          }
 -          break;
 +      /* sqlite3_test_control(int, uint) */
 +    case SQLITE_TESTCTRL_PENDING_BYTE:
 +      if( nArg==3 ){
 +        unsigned int opt = (unsigned int)integerValue(azArg[2]);
 +        rc2 = sqlite3_test_control(testctrl, opt);
 +        isOk = 3;
 +      }
 +      break;
  
 -        /* sqlite3_test_control(int, int, sqlite3*) */
 -        case SQLITE_TESTCTRL_PRNG_SEED:
 -          if( nArg==3 || nArg==4 ){
 -            int ii = (int)integerValue(azArg[2]);
 -            sqlite3 *db;
 -            if( ii==0 && strcmp(azArg[2],"random")==0 ){
 -              sqlite3_randomness(sizeof(ii),&ii);
 -              printf("-- random seed: %d\n", ii);
 -            }
 -            if( nArg==3 ){
 -              db = 0;
 -            }else{
 -              db = p->db;
 -              /* Make sure the schema has been loaded */
 -              sqlite3_table_column_metadata(db, 0, "x", 0, 0, 0, 0, 0, 0);
 -            }
 -            rc2 = sqlite3_test_control(testctrl, ii, db);
 -            isOk = 3;
 -          }
 -          break;
 +      /* sqlite3_test_control(int, int, sqlite3*) */
 +    case SQLITE_TESTCTRL_PRNG_SEED:
 +      if( nArg==3 || nArg==4 ){
 +        int ii = (int)integerValue(azArg[2]);
 +        sqlite3 *db;
 +        if( ii==0 && strcmp(azArg[2],"random")==0 ){
 +          sqlite3_randomness(sizeof(ii),&ii);
 +          fprintf(STD_OUT, "-- random seed: %d\n", ii);
 +        }
 +        if( nArg==3 ){
 +          db = 0;
 +        }else{
 +          db = p->db;
 +          /* Make sure the schema has been loaded */
 +          sqlite3_table_column_metadata(db, 0, "x", 0, 0, 0, 0, 0, 0);
 +        }
 +        rc2 = sqlite3_test_control(testctrl, ii, db);
 +        isOk = 3;
 +      }
 +      break;
  
 -        /* sqlite3_test_control(int, int) */
 -        case SQLITE_TESTCTRL_ASSERT:
 -        case SQLITE_TESTCTRL_ALWAYS:
 -          if( nArg==3 ){
 -            int opt = booleanValue(azArg[2]);
 -            rc2 = sqlite3_test_control(testctrl, opt);
 -            isOk = 1;
 -          }
 -          break;
 +      /* sqlite3_test_control(int, int) */
 +    case SQLITE_TESTCTRL_ASSERT:
 +    case SQLITE_TESTCTRL_ALWAYS:
 +      if( nArg==3 ){
 +        int opt = booleanValue(azArg[2]);
 +        rc2 = sqlite3_test_control(testctrl, opt);
 +        isOk = 1;
 +      }
 +      break;
  
 -        /* sqlite3_test_control(int, int) */
 -        case SQLITE_TESTCTRL_LOCALTIME_FAULT:
 -        case SQLITE_TESTCTRL_NEVER_CORRUPT:
 -          if( nArg==3 ){
 -            int opt = booleanValue(azArg[2]);
 -            rc2 = sqlite3_test_control(testctrl, opt);
 -            isOk = 3;
 -          }
 -          break;
 +      /* sqlite3_test_control(int, int) */
 +    case SQLITE_TESTCTRL_LOCALTIME_FAULT:
 +    case SQLITE_TESTCTRL_NEVER_CORRUPT:
 +      if( nArg==3 ){
 +        int opt = booleanValue(azArg[2]);
 +        rc2 = sqlite3_test_control(testctrl, opt);
 +        isOk = 3;
 +      }
 +      break;
  
 -        /* sqlite3_test_control(sqlite3*) */
 -        case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS:
 -          rc2 = sqlite3_test_control(testctrl, p->db);
 -          isOk = 3;
 -          break;
 +      /* sqlite3_test_control(sqlite3*) */
 +    case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS:
 +      rc2 = sqlite3_test_control(testctrl, p->db);
 +      isOk = 3;
 +      break;
  
 -        case SQLITE_TESTCTRL_IMPOSTER:
 -          if( nArg==5 ){
 -            rc2 = sqlite3_test_control(testctrl, p->db,
 -                          azArg[2],
 -                          integerValue(azArg[3]),
 -                          integerValue(azArg[4]));
 -            isOk = 3;
 -          }
 -          break;
 +    case SQLITE_TESTCTRL_IMPOSTER:
 +      if( nArg==5 ){
 +        rc2 = sqlite3_test_control(testctrl, p->db,
 +                                   azArg[2],
 +                                   integerValue(azArg[3]),
 +                                   integerValue(azArg[4]));
 +        isOk = 3;
 +      }
 +      break;
  
 -        case SQLITE_TESTCTRL_SEEK_COUNT: {
 -          u64 x = 0;
 -          rc2 = sqlite3_test_control(testctrl, p->db, &x);
 -          utf8_printf(p->out, "%llu\n", x);
 -          isOk = 3;
 -          break;
 -        }
 +    case SQLITE_TESTCTRL_SEEK_COUNT: {
 +      u64 x = 0;
 +      rc2 = sqlite3_test_control(testctrl, p->db, &x);
 +      utf8_printf(p->out, "%llu\n", x);
 +      isOk = 3;
 +      break;
 +    }
  
  #ifdef YYCOVERAGE
 -        case SQLITE_TESTCTRL_PARSER_COVERAGE: {
 -          if( nArg==2 ){
 -            sqlite3_test_control(testctrl, p->out);
 -            isOk = 3;
 -          }
 -          break;
 -        }
 +    case SQLITE_TESTCTRL_PARSER_COVERAGE: {
 +      if( nArg==2 ){
 +        sqlite3_test_control(testctrl, p->out);
 +        isOk = 3;
 +      }
 +      break;
 +    }
  #endif
  #ifdef SQLITE_DEBUG
 -        case SQLITE_TESTCTRL_TUNE: {
 -          if( nArg==4 ){
 -            int id = (int)integerValue(azArg[2]);
 -            int val = (int)integerValue(azArg[3]);
 -            sqlite3_test_control(testctrl, id, &val);
 -            isOk = 3;
 -          }else if( nArg==3 ){
 -            int id = (int)integerValue(azArg[2]);
 -            sqlite3_test_control(testctrl, -id, &rc2);
 -            isOk = 1;
 -          }else if( nArg==2 ){
 -            int id = 1;
 -            while(1){
 -              int val = 0;
 -              rc2 = sqlite3_test_control(testctrl, -id, &val);
 -              if( rc2!=SQLITE_OK ) break;
 -              if( id>1 ) utf8_printf(p->out, "  ");
 -              utf8_printf(p->out, "%d: %d", id, val);
 -              id++;
 -            }
 -            if( id>1 ) utf8_printf(p->out, "\n");
 -            isOk = 3;
 -          }
 -          break;
 -        }
 -#endif
 -        case SQLITE_TESTCTRL_SORTER_MMAP:
 -          if( nArg==3 ){
 -            int opt = (unsigned int)integerValue(azArg[2]);
 -            rc2 = sqlite3_test_control(testctrl, p->db, opt);
 -            isOk = 3;
 -          }
 -          break;
 +    case SQLITE_TESTCTRL_TUNE: {
 +      if( nArg==4 ){
 +        int id = (int)integerValue(azArg[2]);
 +        int val = (int)integerValue(azArg[3]);
 +        sqlite3_test_control(testctrl, id, &val);
 +        isOk = 3;
 +      }else if( nArg==3 ){
 +        int id = (int)integerValue(azArg[2]);
 +        sqlite3_test_control(testctrl, -id, &rc2);
 +        isOk = 1;
 +      }else if( nArg==2 ){
 +        int id = 1;
 +        while(1){
 +          int val = 0;
 +          rc2 = sqlite3_test_control(testctrl, -id, &val);
 +          if( rc2!=SQLITE_OK ) break;
 +          if( id>1 ) utf8_printf(p->out, "  ");
 +          utf8_printf(p->out, "%d: %d", id, val);
 +          id++;
 +        }
 +        if( id>1 ) utf8_printf(p->out, "\n");
 +        isOk = 3;
        }
 +      break;
      }
 -    if( isOk==0 && iCtrl>=0 ){
 -      utf8_printf(p->out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
 -      rc = 1;
 -    }else if( isOk==1 ){
 -      raw_printf(p->out, "%d\n", rc2);
 -    }else if( isOk==2 ){
 -      raw_printf(p->out, "0x%08x\n", rc2);
 +#endif
      }
 -  }else
 -#endif /* !defined(SQLITE_UNTESTABLE) */
 -
 -  if( c=='t' && n>4 && strncmp(azArg[0], "timeout", n)==0 ){
 -    open_db(p, 0);
 -    sqlite3_busy_timeout(p->db, nArg>=2 ? (int)integerValue(azArg[1]) : 0);
 -  }else
 -
 -  if( c=='t' && n>=5 && strncmp(azArg[0], "timer", n)==0 ){
 -    if( nArg==2 ){
 -      enableTimer = booleanValue(azArg[1]);
 -      if( enableTimer && !HAS_TIMER ){
 -        raw_printf(stderr, "Error: timer not available on this system.\n");
 -        enableTimer = 0;
 +  }
 +  if( isOk==0 && iCtrl>=0 ){
 +    utf8_printf(p->out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
 +    return 1;
 +  }else if( isOk==1 ){
 +    raw_printf(p->out, "%d\n", rc2);
 +  }else if( isOk==2 ){
 +    raw_printf(p->out, "0x%08x\n", rc2);
 +  }
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( timeout 4 1 2 ){
 +  open_db(p, 0);
 +  sqlite3_busy_timeout(p->db, nArg>=2 ? (int)integerValue(azArg[1]) : 0);
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( timer ? 2 2 ){
 +  enableTimer = booleanValue(azArg[1]);
 +  if( enableTimer && !HAS_TIMER ){
 +    raw_printf(STD_ERR, "Error: timer not available on this system.\n");
 +    enableTimer = 0;
 +  }
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( trace ? 0 0 ){
 +  int mType = 0;
 +  int jj;
 +  open_db(p, 0);
 +  for(jj=1; jj<nArg; jj++){
 +    const char *z = azArg[jj];
 +    if( z[0]=='-' ){
 +      if( optionMatch(z, "expanded") ){
 +        p->eTraceType = SHELL_TRACE_EXPANDED;
 +      }
 +#ifdef SQLITE_ENABLE_NORMALIZE
 +      else if( optionMatch(z, "normalized") ){
 +        p->eTraceType = SHELL_TRACE_NORMALIZED;
 +      }
 +#endif
 +      else if( optionMatch(z, "plain") ){
 +        p->eTraceType = SHELL_TRACE_PLAIN;
 +      }
 +      else if( optionMatch(z, "profile") ){
 +        mType |= SQLITE_TRACE_PROFILE;
 +      }
 +      else if( optionMatch(z, "row") ){
 +        mType |= SQLITE_TRACE_ROW;
 +      }
 +      else if( optionMatch(z, "stmt") ){
 +        mType |= SQLITE_TRACE_STMT;
 +      }
 +      else if( optionMatch(z, "close") ){
 +        mType |= SQLITE_TRACE_CLOSE;
 +      }
 +      else {
 +        *pzErr = shellMPrintf(0,"Unknown option \"%s\" on \".trace\"\n", z);
 +        return 1;
        }
      }else{
 -      raw_printf(stderr, "Usage: .timer on|off\n");
 -      rc = 1;
 +      output_file_close(p->traceOut);
 +      p->traceOut = output_file_open(azArg[1], 0);
      }
 -  }else
 +  }
 +  if( p->traceOut==0 ){
 +    sqlite3_trace_v2(p->db, 0, 0, 0);
 +  }else{
 +    if( mType==0 ) mType = SQLITE_TRACE_STMT;
 +    sqlite3_trace_v2(p->db, mType, sql_trace_callback, p);
 +  }
 +  return 0;
 +}
  
 -#ifndef SQLITE_OMIT_TRACE
 -  if( c=='t' && strncmp(azArg[0], "trace", n)==0 ){
 -    int mType = 0;
 -    int jj;
 -    open_db(p, 0);
 -    for(jj=1; jj<nArg; jj++){
 -      const char *z = azArg[jj];
 -      if( z[0]=='-' ){
 -        if( optionMatch(z, "expanded") ){
 -          p->eTraceType = SHELL_TRACE_EXPANDED;
 -        }
 -#ifdef SQLITE_ENABLE_NORMALIZE
 -        else if( optionMatch(z, "normalized") ){
 -          p->eTraceType = SHELL_TRACE_NORMALIZED;
 -        }
 +/*****************
 + * The .user command
 + * Because there is no help text for .user, it does its own argument validation.
 + */
 +CONDITION_COMMAND( user SQLITE_USER_AUTHENTICATION );
 +DISPATCHABLE_COMMAND( user ? 0 0 ){
 +  int rc;
 +  const char *usage
 +    = "Usage: .user SUBCOMMAND ...\n"
 +      "Subcommands are:\n"
 +      "   login USER PASSWORD\n"
 +      "   delete USER\n"
 +      "   add USER PASSWORD ISADMIN\n"
 +      "   edit USER PASSWORD ISADMIN\n"
 +    ;
 +  if( nArg<2 ){
 +  teach_fail:
 +    *pzErr = shellMPrintf(0,usage);
 +    return 1;
 +  }
 +  open_db(p, 0);
 +  if( strcmp(azArg[1],"login")==0 ){
 +    if( nArg!=4 ){
 +      goto teach_fail;
 +    }
 +    rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3],
 +                                   strlen30(azArg[3]));
 +    if( rc ){
 +      *pzErr = shellMPrintf(0,"Authentication failed for user %s\n", azArg[2]);
 +      return 1;
 +    }
 +  }else if( strcmp(azArg[1],"add")==0 ){
 +    if( nArg!=5 ){
 +      goto teach_fail;
 +    }
 +    rc = sqlite3_user_add(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
 +                          booleanValue(azArg[4]));
 +    if( rc ){
 +      *pzErr = shellMPrintf(0,"User-Add failed: %d\n", rc);
 +      return 1;
 +    }
 +  }else if( strcmp(azArg[1],"edit")==0 ){
 +    if( nArg!=5 ){
 +      goto teach_fail;
 +    }
 +    rc = sqlite3_user_change(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
 +                             booleanValue(azArg[4]));
 +    if( rc ){
 +      *pzErr = shellMPrintf(0,"User-Edit failed: %d\n", rc);
 +      return 1;
 +    }
 +  }else if( strcmp(azArg[1],"delete")==0 ){
 +    if( nArg!=3 ){
 +      goto teach_fail;
 +    }
 +    rc = sqlite3_user_delete(p->db, azArg[2]);
 +    if( rc ){
 +      *pzErr = shellMPrintf(0,"User-Delete failed: %d\n", rc);
 +      return 1;
 +    }
 +  }else{
 +    goto teach_fail;
 +  }
 +  return 0;
 +}
 +
 +/*****************
 + * The .vfsinfo, .vfslist, .vfsname and .version commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".version                 Show a variety of version info",
 +  ".vfsinfo ?AUX?           Information about the top-level VFS",
 +  ".vfslist                 List all available VFSes",
 +  ".vfsname ?AUX?           Print the name of the VFS stack",
 +];
 +DISPATCHABLE_COMMAND( version ? 1 1 ){
 +  utf8_printf(p->out, "SQLite %s %s\n" /*extra-version-info*/,
 +              sqlite3_libversion(), sqlite3_sourceid());
 +#if SQLITE_HAVE_ZLIB
 +  utf8_printf(p->out, "zlib version %s\n", zlibVersion());
  #endif
 -        else if( optionMatch(z, "plain") ){
 -          p->eTraceType = SHELL_TRACE_PLAIN;
 -        }
 -        else if( optionMatch(z, "profile") ){
 -          mType |= SQLITE_TRACE_PROFILE;
 -        }
 -        else if( optionMatch(z, "row") ){
 -          mType |= SQLITE_TRACE_ROW;
 -        }
 -        else if( optionMatch(z, "stmt") ){
 -          mType |= SQLITE_TRACE_STMT;
 -        }
 -        else if( optionMatch(z, "close") ){
 -          mType |= SQLITE_TRACE_CLOSE;
 -        }
 -        else {
 -          raw_printf(stderr, "Unknown option \"%s\" on \".trace\"\n", z);
 -          rc = 1;
 -          goto meta_command_exit;
 +#define CTIMEOPT_VAL_(opt) #opt
 +#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt)
 +#if defined(__clang__) && defined(__clang_major__)
 +  utf8_printf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "."
 +              CTIMEOPT_VAL(__clang_minor__) "."
 +              CTIMEOPT_VAL(__clang_patchlevel__) "\n");
 +#elif defined(_MSC_VER)
 +  utf8_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) "\n");
 +#elif defined(__GNUC__) && defined(__VERSION__)
 +  utf8_printf(p->out, "gcc-" __VERSION__ "\n");
 +#endif
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( vfsinfo ? 1 2 ){
 +  const char *zDbName = nArg==2 ? azArg[1] : "main";
 +  sqlite3_vfs *pVfs = 0;
 +  if( p->db ){
 +    sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs);
 +    if( pVfs ){
 +      utf8_printf(p->out, "vfs.zName      = \"%s\"\n", pVfs->zName);
 +      raw_printf(p->out, "vfs.iVersion   = %d\n", pVfs->iVersion);
 +      raw_printf(p->out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
 +      raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
 +    }
 +  }
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( vfslist ? 1 1 ){
 +  sqlite3_vfs *pVfs;
 +  sqlite3_vfs *pCurrent = 0;
 +  if( p->db ){
 +    sqlite3_file_control(p->db, "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent);
 +  }
 +  for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){
 +    utf8_printf(p->out, "vfs.zName      = \"%s\"%s\n", pVfs->zName,
 +                pVfs==pCurrent ? "  <--- CURRENT" : "");
 +    raw_printf(p->out, "vfs.iVersion   = %d\n", pVfs->iVersion);
 +    raw_printf(p->out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
 +    raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
 +    if( pVfs->pNext ){
 +      raw_printf(p->out, "-----------------------------------\n");
 +    }
 +  }
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( vfsname ? 0 0 ){
 +  const char *zDbName = nArg==2 ? azArg[1] : "main";
 +  char *zVfsName = 0;
 +  if( p->db ){
 +    sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName);
 +    if( zVfsName ){
 +      utf8_printf(p->out, "%s\n", zVfsName);
 +      sqlite3_free(zVfsName);
 +    }
 +  }
 +  return 0;
 +}
 +
 +/*****************
 + * The .width and .wheretrace commands
 + * The .wheretrace command has no help.
 + */
 +COLLECT_HELP_TEXT[
 +  ".width NUM1 NUM2 ...     Set minimum column widths for columnar output",
-   "     Negative values right-justify",
++  "   Negative values right-justify",
 +];
 +DISPATCHABLE_COMMAND( width ? 1 0 ){
 +  int j;
 +  p->nWidth = nArg-1;
 +  p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(int)*2);
 +  if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory();
 +  if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth];
 +  for(j=1; j<nArg; j++){
 +    p->colWidth[j-1] = (int)integerValue(azArg[j]);
 +  }
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( wheretrace ? 1 2 ){
 +  unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
 +  sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &x);
 +  return 0;
 +}
 +
++/*****************
++ * The .x command
++ */
++COLLECT_HELP_TEXT[
++  ".x NAMES ...             Excecute content of some .parameter set variable(s)",
++  "   Only variables whose name begins with a letter are eligible for this."
++];
++DISPATCHABLE_COMMAND( x ? 1 0 ){
++  int ia, rc, nErrors = 0;
++  sqlite3_stmt *pStmt = 0;
++  open_db(p, 0);
++  if( p->db==0 ){
++    utf8_printf(STD_ERR, ".x can only be done with a database open.\n");
++    return 1;
++  }
++  if( sqlite3_table_column_metadata(p->db, PARAM_TABLE_SCHEMA,
++                                    PARAM_TABLE_NAME,
++                                    "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){
++    utf8_printf(STD_ERR, "No "PARAM_TABLE_SNAME" table exists.\n");
++    return 1;
++  }
++  rc = sqlite3_prepare_v2
++    (p->db, "SELECT value FROM "PARAM_TABLE_SNAME
++     " WHERE key=$1 AND uses=1",
++     -1, &pStmt, 0);
++  if( rc!=SQLITE_OK ){
++    utf8_printf(STD_ERR, PARAM_TABLE_SNAME" is wrongly created.\n");
++    return 1;
++  }
++  for( ia=1; ia < nArg; ++ia ){
++    if( isalpha(azArg[ia][0]) ){
++      rc = sqlite3_reset(pStmt);
++      rc = sqlite3_bind_text(pStmt, 1, azArg[ia], -1, 0);
++      rc = sqlite3_step(pStmt);
++      if( rc==SQLITE_ROW ){
++        const unsigned char *zValue = sqlite3_column_text(pStmt, 0);
++        int nb = sqlite3_column_bytes(pStmt, 0);
++        while( nb>0 && IsSpace(zValue[nb-1]) ) --nb;
++        if( nb>0 ){
++          /* The trailing newline (or some other placeholder) is important
++           * because one (or some other character) will likely be put in
++           * its place during process_input() line/group handling, along
++           * with a terminating NUL character. Without it, the NULL could
++           * land past the end of the allocation made just below.
++           */
++          int nle = zValue[nb-1]=='\n';
++          char *zSubmit = sqlite3_mprintf( "%.*s%s", nb, zValue, "\n"+nle );
++          InSource inRedir
++            = INSOURCE_STR_REDIR(zSubmit, azArg[ia], p->pInSource);
++          shell_check_oom(zSubmit);
++          p->pInSource = &inRedir;
++          rc = process_input(p);
++          sqlite3_free(zSubmit);
++          p->pInSource = inRedir.pFrom;
++        }else{
++          continue; /* All white, ignore. */
+         }
+       }else{
 -        output_file_close(p->traceOut);
 -        p->traceOut = output_file_open(azArg[1], 0);
++        utf8_printf(STD_ERR,
++                    "Skipping parameter '%s' (not set and executable.)\n",
++                    azArg[ia]);
++        ++nErrors;
+       }
 -    }
 -    if( p->traceOut==0 ){
 -      sqlite3_trace_v2(p->db, 0, 0, 0);
+     }else{
 -      if( mType==0 ) mType = SQLITE_TRACE_STMT;
 -      sqlite3_trace_v2(p->db, mType, sql_trace_callback, p);
++      utf8_printf(STD_ERR,
++                  "Skipping badly named %s. Run \".help x\"\n", azArg[ia]);
++      ++nErrors;
+     }
 -  }else
 -#endif /* !defined(SQLITE_OMIT_TRACE) */
++  }
++  sqlite3_finalize(pStmt);
++  return (rc==2)? 2 : nErrors>0;
++}
 -#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_VIRTUALTABLE)
 -  if( c=='u' && strncmp(azArg[0], "unmodule", n)==0 ){
 -    int ii;
 -    int lenOpt;
 -    char *zOpt;
 -    if( nArg<2 ){
 -      raw_printf(stderr, "Usage: .unmodule [--allexcept] NAME ...\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    open_db(p, 0);
 -    zOpt = azArg[1];
 -    if( zOpt[0]=='-' && zOpt[1]=='-' && zOpt[2]!=0 ) zOpt++;
 -    lenOpt = (int)strlen(zOpt);
 -    if( lenOpt>=3 && strncmp(zOpt, "-allexcept",lenOpt)==0 ){
 -      assert( azArg[nArg]==0 );
 -      sqlite3_drop_modules(p->db, nArg>2 ? (const char**)(azArg+2) : 0);
 +/* End of published, standard meta-command implementation functions
 +COMMENT  Build-time overrides of above meta-commands or new meta-commands may be
 +COMMENT  incorporated into shell.c via: -it COMMAND_CUSTOMIZE=<customize source>
 +COMMENT  where <customize source> names a file using the methodology of the above
 +COMMENT  section to define new or altered meta-commands and their help text.
 +*/
 +INCLUDE( COMMAND_CUSTOMIZE );
 +
 +typedef struct MetaCommand MetaCommand;
 +
 +/* Define and populate command dispatch table. */
 +static struct CommandInfo {
 +  const char * cmdName;
 +  int (*cmdDoer)(char *azArg[], int nArg, ShellState *, char **pzErr);
 +  unsigned char minLen, minArgs, maxArgs;
 +#if OBJECTIFY_COMMANDS
 +  const char *azHelp[2]; /* primary and secondary help text */
 +  void * pCmdData;
 +#endif
 +} command_table[] = {
 +  COMMENT Emit the dispatch table entries generated and collected above.
 +  EMIT_DISPATCH(2);
 +  { 0, 0, 0, -1, -1 }
 +};
 +static unsigned numCommands
 +  = sizeof(command_table)/sizeof(struct CommandInfo) - 1;
 +
 +COMMENT  This help text is set seperately from meta-command definition section
 +COMMENT  for the always-built-in, non-customizable commands with visible help.
 +COLLECT_HELP_TEXT[
 +  ".exit ?CODE?             Exit this program with return-code CODE or 0",
 +  ".quit                    Exit this program",
 +];
 +
 +/*
 +** Text of help messages.
 +**
 +** The help text for each individual command begins with a line that starts
 +** with ".".  Subsequent lines are supplimental information.
 +**
 +** There must be two or more spaces between the end of the command and the
 +** start of the description of what that command does.
 +*/
 +static const char *(azHelp[]) = {
 +/* Template for help text indents and length:
 +  ".whatever ?arg? ...      Summary of effects (limited to this line's length)",
 +  "   ^ ^                   ^  ^                                              ",
 +*/
 +  COMMENT  Emit the help text fragments collected above via COLLECT_HELP_TEXT.
 +  EMIT_HELP_TEXT(2);
 +  0 /* Sentinel */
 +};
 +
 +
 +#define NO_SUCH_COMMAND SQLITE_NOTFOUND
 +/* SHELL_INVALID_ARGS defined as SQLITE_MISUSE in shext_linkage.h */
 +
 +/*****************
 +** Command dispatcher
 +** For the non-extended or non-extensible shell, this function does
 +** a binary search of the fixed list of meta-command info structs.
 +** For an extended shell, it may (TBD) query the shell's DB. Either
 +** way, this function retains its interface.
 +** After successful command lookup and (simple) argument checking,
 +** it calls the found meta-command with the input arguments (except
 +** that azArg[0] is replaced with the properly spelled command name.)
 +** The return is either a dispatch error or whatever the dispatched
 +** meta-command returns.
 +*/
 +int dispatchCommand(char *azArg[], int nArg, ShellState *pSS, char **pzErr){
 +  const char *cmdName = azArg[0];
 +  int cmdLen = strlen30(cmdName);
 +  struct CommandInfo *pci = 0;
 +  int ixb = 0, ixe = numCommands-1;
 +  while( ixb <= ixe ){
 +    int ixm = (ixb+ixe)/2;
 +    int md = strncmp(cmdName, command_table[ixm].cmdName, cmdLen);
 +    if( md>0 ){
 +      ixb = ixm+1;
 +    }else if( md<0 ){
 +      ixe = ixm-1;
      }else{
 -      for(ii=1; ii<nArg; ii++){
 -        sqlite3_create_module(p->db, azArg[ii], 0, 0);
 +      if( command_table[ixm].minLen > cmdLen ){
 +        return NO_SUCH_COMMAND;
        }
 +      pci = &command_table[ixm];
 +      break;
      }
 -  }else
 +  }
 +  if( 0==pci ){
 +    return NO_SUCH_COMMAND;
 +  }
 +  if( pci->minArgs > nArg||(pci->maxArgs > 0 && pci->maxArgs < nArg) ){
 +    return SHELL_INVALID_ARGS;
 +  }
 +  /* Replace any user-shortened command name with its whole name. */
 +  azArg[0] = (char *)pci->cmdName;
 +  return (pci->cmdDoer)(azArg, nArg, pSS, pzErr);
 +}
 +
 +/*
 +** 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];
++#if SHELL_VARIABLE_EXPANSION
++  int ncLineIn = strlen30(zLine);
++  u8 bExpVars = SHEXT_VAREXP(p);
+ #endif
  
 -#if SQLITE_USER_AUTHENTICATION
 -  if( c=='u' && strncmp(azArg[0], "user", n)==0 ){
 -    if( nArg<2 ){
 -      raw_printf(stderr, "Usage: .user SUBCOMMAND ...\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    open_db(p, 0);
 -    if( strcmp(azArg[1],"login")==0 ){
 -      if( nArg!=4 ){
 -        raw_printf(stderr, "Usage: .user login USER PASSWORD\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3],
 -                                     strlen30(azArg[3]));
 -      if( rc ){
 -        utf8_printf(stderr, "Authentication failed for user %s\n", azArg[2]);
 -        rc = 1;
 -      }
 -    }else if( strcmp(azArg[1],"add")==0 ){
 -      if( nArg!=5 ){
 -        raw_printf(stderr, "Usage: .user add USER PASSWORD ISADMIN\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      rc = sqlite3_user_add(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
 -                            booleanValue(azArg[4]));
 -      if( rc ){
 -        raw_printf(stderr, "User-Add failed: %d\n", rc);
 -        rc = 1;
 -      }
 -    }else if( strcmp(azArg[1],"edit")==0 ){
 -      if( nArg!=5 ){
 -        raw_printf(stderr, "Usage: .user edit USER PASSWORD ISADMIN\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      rc = sqlite3_user_change(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
 -                              booleanValue(azArg[4]));
 -      if( rc ){
 -        raw_printf(stderr, "User-Edit failed: %d\n", rc);
 -        rc = 1;
 -      }
 -    }else if( strcmp(azArg[1],"delete")==0 ){
 -      if( nArg!=3 ){
 -        raw_printf(stderr, "Usage: .user delete USER\n");
 -        rc = 1;
 -        goto meta_command_exit;
 +#ifndef SQLITE_OMIT_VIRTUALTABLE
 +  if( p->expert.pExpert ){
 +    expertFinish(p, 1, 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++;
        }
 -      rc = sqlite3_user_delete(p->db, azArg[2]);
 -      if( rc ){
 -        raw_printf(stderr, "User-Delete failed: %d\n", rc);
 -        rc = 1;
 +      if( zLine[h]==delim ){
 +        zLine[h++] = 0;
        }
 +      if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
      }else{
 -      raw_printf(stderr, "Usage: .user login|add|edit|delete ...\n");
 -      rc = 1;
 -      goto meta_command_exit;
 +      azArg[nArg++] = &zLine[h];
 +      while( zLine[h] && !IsSpace(zLine[h]) ){ h++; }
 +      if( zLine[h] ) zLine[h++] = 0;
 +      resolve_backslashes(azArg[nArg-1]);
      }
 -  }else
 -#endif /* SQLITE_USER_AUTHENTICATION */
 +  }
 +  azArg[nArg] = 0;
  
 -  if( c=='v' && strncmp(azArg[0], "version", n)==0 ){
 -    utf8_printf(p->out, "SQLite %s %s\n" /*extra-version-info*/,
 -        sqlite3_libversion(), sqlite3_sourceid());
 -#if SQLITE_HAVE_ZLIB
 -    utf8_printf(p->out, "zlib version %s\n", zlibVersion());
 -#endif
 -#define CTIMEOPT_VAL_(opt) #opt
 -#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt)
 -#if defined(__clang__) && defined(__clang_major__)
 -    utf8_printf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "."
 -                    CTIMEOPT_VAL(__clang_minor__) "."
 -                    CTIMEOPT_VAL(__clang_patchlevel__) "\n");
 -#elif defined(_MSC_VER)
 -    utf8_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) "\n");
 -#elif defined(__GNUC__) && defined(__VERSION__)
 -    utf8_printf(p->out, "gcc-" __VERSION__ "\n");
 -#endif
 -  }else
 +  /* Process the input line.
 +  */
 +  if( nArg==0 ) return 0; /* no tokens, no error */
 +  n = strlen30(azArg[0]);
 +  c = azArg[0][0];
 +  clearTempFile(p);
  
 -  if( c=='v' && strncmp(azArg[0], "vfsinfo", n)==0 ){
 -    const char *zDbName = nArg==2 ? azArg[1] : "main";
 -    sqlite3_vfs *pVfs = 0;
 -    if( p->db ){
 -      sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs);
 -      if( pVfs ){
 -        utf8_printf(p->out, "vfs.zName      = \"%s\"\n", pVfs->zName);
 -        raw_printf(p->out, "vfs.iVersion   = %d\n", pVfs->iVersion);
 -        raw_printf(p->out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
 -        raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
 -      }
 -    }
 +  /* Check for the special, non-dispatched meta-commands.
 +  */
 +
 +  if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){
 +    if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 )
 +      p->abruptExit = rc;
 +    rc = 2;
    }else
  
 -  if( c=='v' && strncmp(azArg[0], "vfslist", n)==0 ){
 -    sqlite3_vfs *pVfs;
 -    sqlite3_vfs *pCurrent = 0;
 -    if( p->db ){
 -      sqlite3_file_control(p->db, "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent);
 -    }
 -    for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){
 -      utf8_printf(p->out, "vfs.zName      = \"%s\"%s\n", pVfs->zName,
 -           pVfs==pCurrent ? "  <--- CURRENT" : "");
 -      raw_printf(p->out, "vfs.iVersion   = %d\n", pVfs->iVersion);
 -      raw_printf(p->out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
 -      raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
 -      if( pVfs->pNext ){
 -        raw_printf(p->out, "-----------------------------------\n");
 -      }
 -    }
 +  if( c=='q' && strncmp(azArg[0], "quit", n)==0 ){
 +    rc = 2;
    }else
  
 -  if( c=='v' && strncmp(azArg[0], "vfsname", n)==0 ){
 -    const char *zDbName = nArg==2 ? azArg[1] : "main";
 -    char *zVfsName = 0;
 -    if( p->db ){
 -      sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName);
 -      if( zVfsName ){
 -        utf8_printf(p->out, "%s\n", zVfsName);
 -        sqlite3_free(zVfsName);
 +#ifdef SQLITE_DEBUG
 +  /* Undocumented commands for internal testing.  
 +   * Subject to change without notice.
 +   * These are not dispatched via lookup because the command word varies.
 +   */
 +  if( c=='s' && n>=10 && strncmp(azArg[0], "selftest-", 9)==0 ){
 +    if( strncmp(azArg[0]+9, "boolean", n-9)==0 ){
 +      int i, v;
 +      for(i=1; i<nArg; i++){
 +        v = booleanValue(azArg[i]);
 +        utf8_printf(p->out, "%s: %d 0x%x\n", azArg[i], v, v);
        }
      }
 -  }else
 -
 -  if( c=='w' && strncmp(azArg[0], "wheretrace", n)==0 ){
 -    unsigned int x
 -      = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
 -    sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &x);
 -  }else
 -
 -  if( c=='w' && strncmp(azArg[0], "width", n)==0 ){
 -    int j;
 -    assert( nArg<=ArraySize(azArg) );
 -    p->nWidth = nArg-1;
 -    p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(int)*2);
 -    if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory();
 -    if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth];
 -    for(j=1; j<nArg; j++){
 -      p->colWidth[j-1] = (int)integerValue(azArg[j]);
 +    if( strncmp(azArg[0]+9, "integer", n-9)==0 ){
 +      int i; sqlite3_int64 v;
 +      for(i=1; i<nArg; i++){
 +        char zBuf[200];
 +        v = integerValue(azArg[i]);
 +        sqlite3_snprintf(sizeof(zBuf),zBuf,"%s: %lld 0x%llx\n", azArg[i],v,v);
 +        utf8_printf(p->out, "%s", zBuf);
 +      }
      }
    }else
 -
 -  if( c=='x' && n==1 ){ /* "x" */
 -    rc = execute_variables(azArg, nArg, p);
 -  }else
 -
 +#endif
 +    /* The meta-command is not among the specially handled ones. Dispatch it. */
    {
 -    utf8_printf(stderr, "Error: unknown command or invalid arguments: "
 -      " \"%s\". Enter \".help\" for help\n", azArg[0]);
 -    rc = 1;
 +    char *zErr = 0;
 +    int dispatchResult = dispatchCommand(azArg, nArg, p, &zErr);
 +    if( p->abruptExit!=0 ){
 +      dispatchResult = SHELL_FORBIDDEN_OP;
 +    }
 +    switch( dispatchResult ){
 +    case NO_SUCH_COMMAND:
 +      utf8_printf(STD_ERR, "Error: unknown command: \"%s\"\n", azArg[0]);
 +      if( stdin_is_interactive )
 +        utf8_printf(STD_ERR, "  Enter \".help\" for a list of commands.\n");
 +      rc = 1;
 +      break;
 +    case SHELL_INVALID_ARGS:
 +      utf8_printf(STD_ERR, "Error: invalid arguments for \".%s\"\n", azArg[0]);
 +      if( stdin_is_interactive ){
 +        if( zErr!=0 ){
 +          utf8_printf(STD_ERR, "  %s\n", zErr);
 +        }else{
 +          utf8_printf(STD_ERR, "Usage: ");
 +          showPrimaryHelp(STD_ERR, azArg[0]);
 +        }
 +      }
 +      rc = 1;
 +      break;
 +    case SHELL_FORBIDDEN_OP:
 +      if( zErr!=0 ){
 +        utf8_printf
 +          (STD_ERR,
 +           "Error: \".%s\" may not %s in --safe mode\n", azArg[0], zErr);
 +        sqlite3_free(zErr);
 +      }else {
 +        utf8_printf(STD_ERR,
 +                    "Error: \".%s\" forbidden in --safe mode\n", azArg[0]);
 +      }
 +      p->abruptExit = 3;
 +      rc = 2;
 +    default:
 +      if( 0!=dispatchResult ) rc = 1;
 +      if( zErr!=0 ){
 +        utf8_printf(STD_ERR, "%s", zErr);
 +        sqlite3_free(zErr);
 +      }
 +    }
    }
  
  meta_command_exit:
      p->outCount--;
      if( p->outCount==0 ) output_reset(p);
    }
 -  p->bSafeMode = p->bSafeModePersist;
 +  updateSafeMode(p);
+ #if SHELL_VARIABLE_EXPANSION
+   if( bExpVars ){
+     /* Free any arguments that are allocated rather than tokenized in place. */
+     for( n=1; n<nArg; ++n ){
+       int iArgOffset = azArg[n]-zLine;
+       u8 bInPlace = iArgOffset>0 && iArgOffset<ncLineIn;
+       if( !bInPlace ) sqlite3_free(azArg[n]);
+     }
+   }
+ #endif
    return rc;
  }
  
  #ifndef CHAR_BIT
  # define CHAR_BIT 8
  #endif
 +
  typedef enum {
-   QSS_HasDark = 1<<CHAR_BIT, QSS_EndingSemi = 2<<CHAR_BIT,
-   QSS_CharMask = (1<<CHAR_BIT)-1, QSS_ScanMask = 3<<CHAR_BIT,
-   QSS_Start = 0
- } QuickScanState;
- #define QSS_SETV(qss, newst) ((newst) | ((qss) & QSS_ScanMask))
- #define QSS_INPLAIN(qss) (((qss)&QSS_CharMask)==QSS_Start)
- #define QSS_PLAINWHITE(qss) (((qss)&~QSS_EndingSemi)==QSS_Start)
- #define QSS_PLAINDARK(qss) (((qss)&~QSS_EndingSemi)==QSS_HasDark)
- #define QSS_SEMITERM(qss) (((qss)&~QSS_HasDark)==QSS_EndingSemi)
+   SSS_HasDark = 1<<CHAR_BIT, SSS_EndingSemi = 2<<CHAR_BIT,
+   SSS_CharMask = (1<<CHAR_BIT)-1, SSS_ScanMask = 3<<CHAR_BIT,
+   SSS_Start = 0
+ } SqlScanState;
+ #define SSS_SETV(qss, newst) ((newst) | ((qss) & SSS_ScanMask))
+ #define SSS_INPLAIN(qss) (((qss)&SSS_CharMask)==SSS_Start)
+ #define SSS_PLAINWHITE(qss) (((qss)&~SSS_EndingSemi)==SSS_Start)
+ #define SSS_PLAINDARK(qss) (((qss)&~SSS_EndingSemi)==SSS_HasDark)
+ #define SSS_SEMITERM(qss) (((qss)&~SSS_HasDark)==SSS_EndingSemi)
  
  /*
  ** Scan line for classification to guide shell's handling.
@@@ -11812,8 -12405,7 +12781,8 @@@ static void sql_prescan(char *zLine, Sq
              continue;
            ++zLine;
            cWait = 0;
-           qss = QSS_SETV(qss, 0);
 +
+           sss = SSS_SETV(sss, 0);
            goto PlainScan;
          case '`': case '\'': case '"':
            if(*zLine==cWait){
  }
  
  /*
- ** Return TRUE if the line typed in is an SQL command terminator other
- ** than a semi-colon.  The SQL Server style "go" command is understood
- ** as is the Oracle "/".
+ ** If the line typed in is an SQL command terminator other than ';',
+ ** return a pointer to the terminator. Otherwise return 0.
+ ** The SQL Server style "go" command and Oracle "/" are understood.
  */
- static int line_is_command_terminator(char *zLine){
+ static char *line_is_command_terminator(char *zLine){
+   int iSkip = 0;
    while( IsSpace(zLine[0]) ){ zLine++; };
    if( zLine[0]=='/' )
 -    iSkip = 1; /* Oracle */
 -  else if ( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o' )
 -    iSkip = 2; /* SQL Server */
 +    zLine += 1; /* Oracle */
-   else if ( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o' )
++  else if( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o' )
 +    zLine += 2; /* SQL Server */
-   else
-     return 0;
-   return quickscan(zLine,QSS_Start)==QSS_Start;
+   else if( iSkip>0 ){
+     SqlScanState sss = SSS_Start;
+     sql_prescan(zLine+iSkip,&sss);
+     if( sss==SSS_Start ) return zLine;
+   }
+   return 0;
  }
  
  /*
@@@ -11909,9 -12507,8 +12884,8 @@@ static int runOneSqlLine(ShellState *p
      }else{
        sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType);
      }
--    utf8_printf(stderr, "%s %s\n", zPrefix, zErrorTail);
++    utf8_printf(STD_ERR, "%s %s\n", zPrefix, zErrorTail);
      sqlite3_free(zErrMsg);
-     zErrMsg = 0;
      return 1;
    }else if( ShellHasFlag(p, SHFLG_CountChanges) ){
      char zLineBuf[2000];
    return 0;
  }
  
+ #if SHELL_EXTENDED_PARSING
+ /* Resumable line classsifier for dot-commands
+ **
+ ** Determines if a dot-command is open, having either an unclosed
+ ** quoted argument or an escape sequence opener ('\') at its end.
+ **
+ ** The FSM design/behavior assumes/requires that a terminating '\'
+ ** is not part of the character sequence being classified -- that
+ ** it represents an escaped newline which is removed as physical
+ ** lines are spliced to accumulate logical lines.
+ **
+ ** The line or added line-portion is passed as zCmd.
+ ** The pScanState pointer must reference an (opaque) DCmd_ScanState,
+ ** which must be set to DCSS_Start to initialize the scanner state.
+ ** Resumed scanning should always be done with zCmd logically just
+ ** past the last non-0 char of the text previously passed in, with
+ ** any previously scanned, trailing newline escape first trimmed.
+ ** Returns are: 0 => not open (aka complete), 1 => is open (incomplete)
+ ** The following macros may be applied to the scan state:
+ */
+ #define DCSS_InDarkArg(dcss) (((dcss)&argPosMask)==inDqArg)
+ #define DCSS_EndEscaped(dcss) (((dcss)&endEscaped)!=0)
+ #define DCSS_IsOpen(dcss) (((dcss)&isOpenMask)!=0)
+ typedef enum {
+   DCSS_Start = 0,
+   twixtArgs = 0, inSqArg = 1, inDarkArg = 2, inDqArg = 3, /* ordered */
+   endEscaped = 4, /* bit used */
+   argPosMask = 3, /* bits used */
+   isOpenMask = 1|4 /* bit test */
+ } DCmd_ScanState;
+ static void dot_command_scan(char *zCmd, DCmd_ScanState *pScanState){
+   DCmd_ScanState ss = *pScanState & ~endEscaped;
+   char c = (ss&isOpenMask)? 1 : *zCmd++;
+   while( c!=0 ){
+     switch( ss ){
+     case twixtArgs:
+       while( IsSpace(c) ){
+         if( (c=*zCmd++)==0 ) goto atEnd;
+       }
+       switch( c ){
+       case '\\':
+         if( *zCmd==0 ){
+           ss |= endEscaped;
+           goto atEnd;
+         }else goto inDark;
+       case '\'': ss = inSqArg; goto inSq;
+       case '"': ss = inDqArg; goto inDq;
+       default: ss = inDarkArg; goto inDark;
+       }
+     inSq:
+     case inSqArg:
+       while( (c=*zCmd++)!='\'' ){
+         if( c==0 ) goto atEnd;
+         if( c=='\\' && *zCmd==0 ){
+           ss |= endEscaped;
+           goto atEnd;
+         }
+       }
+       ss = twixtArgs;
+       c = *zCmd++;
+       continue;
+     inDq:
+     case inDqArg:
+       do {
+         if( (c=*zCmd++)==0 ) goto atEnd;
+         if( c=='\\' ){
+           if( (c=*zCmd++)==0 ){
+             ss |= endEscaped;
+             goto atEnd;
+           }
+           if( (c=*zCmd++)==0 ) goto atEnd;
+         }
+       } while( c!='"' );
+       ss = twixtArgs;
+       c = *zCmd++;
+       continue;
+     inDark:
+     case inDarkArg:
+       while( !IsSpace(c) ){
+         if( c=='\\' && *zCmd==0 ){
+           ss |= endEscaped;
+           goto atEnd;
+         }
+         if( (c=*zCmd++)==0 ) goto atEnd;
+       }
+       ss = twixtArgs;
+       c = *zCmd++;
+       continue;
+     }
+   }
+  atEnd:
+   *pScanState = ss;
+ }
+ #else
+ # define dot_command_scan(x,y)
+ #endif
+ /* Utility functions for process_input. */
+ #if SHELL_EXTENDED_PARSING
+ /*
+ ** Process dot-command line with its scan state to:
+ ** 1. Setup for requested line-splicing; and
+ ** 2. Say whether it is complete.
+ ** The last two out parameters are the line's length, which may be
+ ** adjusted, and the char to be used for joining a subsequent line.
+ ** This is broken out of process_input() mainly for readability.
+ ** The return is TRUE for dot-command ready to run, else false.
+ */
+ static int line_join_ends(DCmd_ScanState dcss, char *zLine,
+                           int *pnLength, char *pcLE){
+   /* It is ready only if has no open argument or escaped newline. */
+   int bOpen = DCSS_IsOpen(dcss);
+   if( !DCSS_EndEscaped(dcss) ){
+     *pcLE = '\n';
+     return !bOpen;
+   }else{
+     *pcLE = (bOpen || DCSS_InDarkArg(dcss))? 0 : ' ';
+     /* Swallow the trailing escape character. */
+     zLine[--*pnLength] = 0;
+     return 0;
+   }
+ }
+ #endif
  
  /*
- ** Read input from *in and process it.  If *in==0 then input
- ** is interactive - the user is typing it it.  Otherwise, input
- ** is coming from a file or device.  A prompt is issued and history
- ** is saved only if input is interactive.  An interrupt signal will
- ** cause this routine to exit immediately, unless input is interactive.
+ ** Grow the accumulation line buffer to accommodate ncNeed chars.
+ ** In/out parameters pz and pna reference the buffer and its size.
+ ** The buffer must eventually be sqlite3_free()'ed by the caller.
+ */
+ static void grow_line_buffer(char **pz, int *pna, int ncNeed){
++
+   if( ncNeed > *pna ){
+     *pna += *pna + (*pna>>1) + 100;
+     *pz = sqlite3_realloc(*pz, *pna);
+     shell_check_oom(*pz);
+   }
+ }
+ /*
+ ** Read input from designated source (p->pInSource) and process it.
+ ** If pInSource==0 then input is interactive - the user is typing it.
+ ** Otherwise, input is coming from a file, stream device or string.
+ ** Prompts issue and history is saved only for interactive input.
+ ** An interrupt signal will cause this routine to exit immediately,
+ ** with "exit demanded" code returned, unless input is interactive.
  **
- ** Normally return (number_of_errors > 0),
- ** but return SHELL_FORBIDDEN_OP for immediate shell exit.
+ ** Returns:
+ **   0 => no errors
+ **   1 => errors>0
+ **   2 => exit demanded, no errors.
+ **   3 => exit demanded, errors>0
  */
  static int process_input(ShellState *p){
-   char *zLine = 0;          /* A single input line */
-   char *zSql = 0;           /* Accumulated SQL text */
-   int nLine;                /* Length of current line */
-   int nSql = 0;             /* Bytes of zSql[] used */
-   int nAlloc = 0;           /* Allocated zSql[] space */
-   int rc = 0;               /* Error or exit code */
-   int errCnt = 0;           /* Number of errors seen */
-   int startline = 0;        /* Line number for start of current input */
-   QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */
+   char *zLineInput = 0;  /* a line-at-a-time input buffer or usable result */
+   char *zLineAccum = 0;  /* accumulation buffer, used for multi-line input */
+   /* Above two pointers could be local to the group handling loop, but are
+    * not so that the number of memory allocations can be reduced. They are
+    * reused from one incoming group to another, realloc()'ed as needed. */
+   int naAccum = 0;       /* tracking how big zLineAccum buffer has become */
+   /* Some flags for ending the overall group processing loop, always 1 or 0 */
+   u8 bErrorBail=0, bInputEnd=0, bExitDemand=0, bInterrupted=0;
+   /* Flag to affect prompting and interrupt action */
+   u8 bInteractive = (p->pInSource==&stdInSource && stdin_is_interactive);
+   int nErrors = 0;       /* count of errors during execution or its prep */
+   /* Block overly-recursive or absurdly nested input redirects. */
    if( p->inputNesting==MAX_INPUT_NESTING ){
-     /* This will be more informative in a later version. */
-     utf8_printf(stderr,"Input nesting limit (%d) reached at line %d."
-                 " Check recursion.\n", MAX_INPUT_NESTING, p->lineno);
+     InSource *pInSrc = p->pInSource->pFrom;
+     const char *zLead = "Input nesting limit ("
+       SHELL_STRINGIFY(MAX_INPUT_NESTING)") reached,";
+     int i = 3;
+     assert(pInSrc!=0 && MAX_INPUT_NESTING>0);
+     while( i-->0 && pInSrc!=0 ){
 -      utf8_printf(stderr,
++      utf8_printf(STD_ERR,
+                   "%s from line %d of \"%s\"",
+                   zLead, pInSrc->lineno, pInSrc->zSourceSay);
+       zLead = (i%2==0)? "\n" : "";
+       pInSrc=pInSrc->pFrom;
+     }
 -    utf8_printf(stderr, " ...\nERROR: Check recursion.\n");
++    utf8_printf(STD_ERR, " ...\nERROR: Check recursion.\n");
      return 1;
    }
    ++p->inputNesting;
-   p->lineno = 0;
-   while( rc<2
-          &&
-          (errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive)) ){
+   /* line-group processing loop (per SQL block, dot-command or comment) */
+   while( !bErrorBail && !bInputEnd && !bExitDemand && !bInterrupted ){
+     int nGroupLines = 0;  /* count of lines belonging to this group */
+     int ncLineIn = 0;     /* how many (non-zero) chars are in zLineInput  */
+     int ncLineAcc = 0;    /* how many (non-zero) chars are in zLineAccum  */
+     int iLastLine = 0;    /* index of last accumulated line start */
+     /* Initialize resumable scanner(s). */
+     SqlScanState sqScanState = SSS_Start; /* for SQL scan */
+ #if SHELL_EXTENDED_PARSING
+     DCmd_ScanState dcScanState = DCSS_Start;  /* for dot-command scan */
+     int ndcLeadWhite = 0; /* for skip over initial whitespace to . or # */
+     char cLineEnd = '\n'; /* May be swallowed or replaced with space. */
+ #else
+ # define ndcLeadWhite 0   /* For legacy parsing, no white before . or # . */
+ # define cLineEnd '\n'    /* For legacy parsing, this always joins lines. */
+ #endif
+     /* An ordered enum to record kind of incoming line group. Its ordering
+      * means than a value greater than Comment implies something runnable.
+      */
+     enum { Tbd = 0, Eof, Comment, Sql, Cmd /*, Tcl */ } inKind = Tbd;
+     /* An enum signifying the group disposition state */
+     enum {
+       Incoming, Runnable, Dumpable, Erroneous, Ignore
+     } disposition = Incoming;
+     char **pzLineUse = &zLineInput;   /* ref line to be processed */
+     int *pncLineUse = &ncLineIn;      /* ref that line's char count */
+     int iStartline = 0;               /* starting line number of group */
      fflush(p->out);
-     zLine = one_input_line(p->in, zLine, nSql>0);
-     if( zLine==0 ){
-       /* End of input */
-       if( p->in==0 && stdin_is_interactive ) fprintf(STD_OUT, "\n");
-       break;
-     }
-     if( seenInterrupt ){
-       if( p->in!=0 ) break;
-       seenInterrupt = 0;
-     }
-     p->lineno++;
-     if( QSS_INPLAIN(qss)
-         && line_is_command_terminator(zLine)
-         && line_is_complete(zSql, nSql) ){
-       memcpy(zLine,";",2);
-     }
-     qss = quickscan(zLine, qss);
-     if( QSS_PLAINWHITE(qss) && nSql==0 ){
-       if( ShellHasFlag(p, SHFLG_Echo) )
-         fprintf(STD_OUT, "%s\n", zLine);
-       /* Just swallow single-line whitespace */
-       qss = QSS_Start;
-       continue;
-     }
-     if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){
-       if( ShellHasFlag(p, SHFLG_Echo) ) fprintf(STD_OUT, "%s\n", zLine);
-       if( zLine[0]=='.' ){
-         rc = do_meta_command(zLine, p);
-         if( rc==2 || p->abruptExit!=0 ){ /* exit requested */
+     zLineInput = one_input_line(p->pInSource, zLineInput, nGroupLines>0);
+     if( zLineInput==0 ){
+       bInputEnd = 1;
+       inKind = Eof;
+       disposition = Ignore;
+       if( bInteractive ) printf("\n");
+     }else{
+       ++nGroupLines;
+       iStartline = p->pInSource->lineno;
+       ncLineIn = strlen30(zLineInput);
+       if( seenInterrupt ){
+         if( p->pInSource!=0 ) break;
+         bInterrupted = 1; /* This will be honored, or not, later. */
+         seenInterrupt = 0;
+         disposition = Dumpable;
+       }
+       /* Classify and check for single-line dispositions, prep for more. */
+ #if SHELL_EXTENDED_PARSING
+       ndcLeadWhite = (SHEXT_PARSING(p))
+         ? skipWhite(zLineInput)-zLineInput
+         : 0; /* Disallow leading whitespace for . or # in legacy mode. */
+ #endif
+       switch( zLineInput[ndcLeadWhite] ){
+       case '.':
+         inKind = Cmd;
+         dot_command_scan(zLineInput+ndcLeadWhite, &dcScanState);
+         break;
+       case '#':
+         inKind = Comment;
+         break;
+       default:
+         /* Might be SQL, or a swallowable whole SQL comment. */
+         sql_prescan(zLineInput, &sqScanState);
+         if( SSS_PLAINWHITE(sqScanState) ){
+           /* It's either all blank or a whole SQL comment. Swallowable. */
+           inKind = Comment;
+         }else{
+           /* Something dark, not a # comment or dot-command. Must be SQL. */
+           inKind = Sql;
+         }
+         break;
+       } /* end classification switch */
+     } /* end read/classify initial group input line */
+     /* Here, if not at end of input, the initial line of group is in, and
+      * it has been scanned and classified. Next, do the processing needed
+      * to recognize whether the initial line or accumulated group so far
+      * is complete such that it may be run, and perform joining of more
+      * lines into the group while it is not so complete. This loop ends
+      * with the input group line(s) ready to be run, or if the input ends
+      * before it is ready, with the group marked as erroneous.
+      */
+     while( disposition==Incoming ){
+       /* Check whether more to accumulate, or ready for final disposition. */
+       switch( inKind ){
+       case Comment:
+         disposition = Dumpable;
+       case Cmd:
+ #if SHELL_EXTENDED_PARSING
+         if( SHEXT_PARSING(p) ){
+           if( line_join_ends(dcScanState, *pzLineUse, pncLineUse, &cLineEnd) ){
+             disposition = Runnable;
+           }
+         }else
+ #endif
+           disposition = Runnable; /* Legacy, any dot-command line is ready. */
+         break;
+       case Sql:
+         /* Check to see if it is complete and ready to run. */
+         if( SSS_SEMITERM(sqScanState) && sqlite3_complete(*pzLineUse)){
+           disposition = Runnable;
+         }else if( SSS_PLAINWHITE(sqScanState) ){
+           /* It is a leading single-line or multi-line comment. */
+           disposition = Runnable;
+           inKind = Comment;
+         }else{
+           char *zT = line_is_command_terminator(zLineInput);
+           if( zT!=0  ){
+             /* Last line is a lone go or / -- prep for running it. */
+             if( nGroupLines>1 ){
+               disposition = Runnable;
+               memcpy(*pzLineUse+iLastLine,";\n",3);
+               *pncLineUse = iLastLine + 2;
+             }else{
+               /* Unless nothing preceded it, then dump it. */
+               disposition = Dumpable;
+             }
+           }
+         }
+         break;
+       } /* end switch on inKind */
+       /* Collect and accumulate more input if group not yet complete. */
+       if( disposition==Incoming ){
+         if( nGroupLines==1 ){
+           grow_line_buffer(&zLineAccum, &naAccum, ncLineIn+2);
+           /* Copy line just input */
+           memcpy(zLineAccum, zLineInput, ncLineIn);
+           ncLineAcc = ncLineIn;
+           pzLineUse = &zLineAccum;
+           pncLineUse = &ncLineAcc;
+         }
+         /* Read in next line of group, (if available.) */
+         zLineInput = one_input_line(p->pInSource, zLineInput, nGroupLines>0);
+         if( zLineInput==0 ){
+           bInputEnd = 1;
+           inKind = Eof;
+           disposition = Erroneous;
+           if( bInteractive ) printf("\n");
+           continue;
+         }
+         ++nGroupLines;
+         ncLineIn = strlen30(zLineInput);
+         /* Scan line just input (if needed) and append to accumulation. */
+         switch( inKind ){
+         case Cmd:
+           dot_command_scan(zLineInput, &dcScanState);
+           break;
+         case Sql:
+           sql_prescan(zLineInput, &sqScanState);
+           break;
+         default:
            break;
-         }else if( rc ){
-           errCnt++;
          }
+         grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+ncLineIn+2);
+         /* Join lines as setup by exam of previous line(s). */
+         if( cLineEnd!=0 ) zLineAccum[ncLineAcc++] = cLineEnd;
+         cLineEnd = '\n'; /* reset to default after use */
+         memcpy(zLineAccum+ncLineAcc, zLineInput, ncLineIn);
+         iLastLine = ncLineAcc;
+         ncLineAcc += ncLineIn;
+         zLineAccum[ncLineAcc] = 0;
+       } /* end glom another line */
+     } /* end group collection loop */
+     /* Here, the group is fully collected or known to be incomplete forever. */
+     switch( disposition ){
+     case Dumpable:
+       echo_group_input(p, *pzLineUse);
+       break;
+     case Runnable:
+       switch( inKind ){
+       case Sql:
+         /* runOneSqlLine() does its own echo when requested. */
+         nErrors += runOneSqlLine(p, *pzLineUse,
+                                  INSOURCE_IS_INTERACTIVE(p->pInSource),
+                                  iStartline);
+         if( bail_on_error && nErrors>0 ) bErrorBail = 1;
+         break;
+       case Cmd:
+         echo_group_input(p, *pzLineUse);
+         switch( do_meta_command(*pzLineUse+ndcLeadWhite, p) ){
+         default: ++nErrors; /* fall thru */
+         case 0: break;
+         case 2: bExitDemand = 1; break;
+         }
+         break;
+       default:
+         assert(inKind!=Tbd);
+         break;
        }
-       qss = QSS_Start;
-       continue;
-     }
-     /* No single-line dispositions remain; accumulate line(s). */
-     nLine = strlen30(zLine);
-     if( nSql+nLine+2>=nAlloc ){
-       /* Grow buffer by half-again increments when big. */
-       nAlloc = nSql+(nSql>>1)+nLine+100;
-       zSql = realloc(zSql, nAlloc);
-       shell_check_oom(zSql);
-     }
-     if( nSql==0 ){
-       int i;
-       for(i=0; zLine[i] && IsSpace(zLine[i]); i++){}
-       assert( nAlloc>0 && zSql!=0 );
-       memcpy(zSql, zLine+i, nLine+1-i);
-       startline = p->lineno;
-       nSql = nLine-i;
-     }else{
-       zSql[nSql++] = '\n';
-       memcpy(zSql+nSql, zLine, nLine+1);
-       nSql += nLine;
-     }
-     if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){
-       errCnt += runOneSqlLine(p, zSql, p->in, startline);
-       nSql = 0;
-       if( p->outCount ){
-         output_reset(p);
-         p->outCount = 0;
-       }else{
-         clearTempFile(p);
-       }
-       updateSafeMode(p);
-       qss = QSS_Start;
-     }else if( nSql && QSS_PLAINWHITE(qss) ){
-       if( ShellHasFlag(p, SHFLG_Echo) ) fprintf(STD_OUT, "%s\n", zSql);
-       nSql = 0;
-       qss = QSS_Start;
++      if( p->abruptExit!=0 ) bExitDemand = 1;
+       break;
+     case Erroneous:
 -      utf8_printf(stderr, "Error: Input incomplete at line %d of \"%s\"\n",
++      utf8_printf(STD_ERR, "Error: Input incomplete at line %d of \"%s\"\n",
+                   p->pInSource->lineno, p->pInSource->zSourceSay);
+       ++nErrors;
+       break;
+     case Ignore:
+       break;
+     default: assert(0);
      }
-   }
-   if( nSql && QSS_PLAINDARK(qss) ){
-     errCnt += runOneSqlLine(p, zSql, p->in, startline);
-     updateSafeMode(p);
-   }
-   free(zSql);
-   free(zLine);
-   --p->inputNesting;
-   return (p->abruptExit)? SHELL_FORBIDDEN_OP : errCnt>0;
+     if( nErrors>0 && bail_on_error ) bErrorBail = 1;
+   } /* end group consume/prep/(run, dump or complain) loop */
+   /* Cleanup and determine return value based on flags and error count. */
+   free(zLineInput); /* Allocated via malloc() by readline or equivalents. */
+   sqlite3_free(zLineAccum);
+   return ((bErrorBail | bExitDemand)<<1) + (nErrors>0);
  }
  
  /*
@@@ -12138,9 -12985,10 +13389,9 @@@ static int run_single_query(ShellState 
  /*
  ** Read input from the file given by sqliterc_override.  Or if that
  ** parameter is NULL, take input from ~/.sqliterc
 -**
 -** Returns the number of errors.
 +** The return is similar to process_input() (0 success, 1 error, x abort)
  */
- static int process_sqliterc(
+ static void process_sqliterc(
    ShellState *p,                  /* Configuration data */
    const char *sqliterc_override   /* Name of config file. NULL to use default */
  ){
    if (sqliterc == NULL) {
      home_dir = find_home_dir(0);
      if( home_dir==0 ){
 -      raw_printf(stderr, "-- warning: cannot find home directory;"
 +      raw_printf(STD_ERR, "-- warning: cannot find home directory;"
                        " cannot read ~/.sqliterc\n");
-       return 1;
+       return;
      }
      zBuf = sqlite3_mprintf("%s/.sqliterc",home_dir);
      shell_check_oom(zBuf);
      sqliterc = zBuf;
    }
-   p->in = fopen(sqliterc,"rb");
-   if( p->in ){
+   inUse = fopen(sqliterc,"rb");
+   if( inUse!=0 ){
+     InSource inSourceRedir
+       = INSOURCE_FILE_REDIR(inUse, sqliterc, p->pInSource);
+     int rc;
+     p->pInSource = &inSourceRedir;
      if( stdin_is_interactive ){
 -      utf8_printf(stderr,"-- Loading resources from %s\n",sqliterc);
 +      utf8_printf(STD_ERR,"-- Loading resources from %s\n",sqliterc);
      }
      rc = process_input(p);
-     fclose(p->in);
+     fclose(inUse);
 -    p->pInSource = inSourceRedir.pFrom; /* 0 when called by main() */
 -    if( rc!=0 && bail_on_error ) exit(1);
++    p->pInSource = inSourceRedir.pFrom;
++    if( rc!=0 && bail_on_error ) p->abruptExit = rc;
    }else if( sqliterc_override!=0 ){
 -    utf8_printf(stderr,"cannot open: \"%s\"\n", sqliterc);
 +    utf8_printf(STD_ERR,"cannot open: \"%s\"\n", sqliterc);
-     rc = 1;
-   }else{
-     rc = 0;
+     if( bail_on_error ) exit(1);
    }
-   p->in = inSaved;
-   p->lineno = savedLineno;
    sqlite3_free(zBuf);
-   return rc;
  }
  
  /*
@@@ -12230,6 -13077,9 +13480,9 @@@ static const char *zOptions 
    "   -readonly            open the database read-only\n"
    "   -safe                enable safe-mode\n"
    "   -separator SEP       set output column separator. Default: '|'\n"
 -  "   -shxopts BMASK       enable shell extensions and options\n"
+ #if SHELL_EXTENSIONS
++  "   -shxopts BMASK       enable shell extensions and options (7 for all)\n"
+ #endif
  #ifdef SQLITE_ENABLE_SORTER_REFERENCES
    "   -sorterref SIZE      sorter references threshold size\n"
  #endif
@@@ -12286,8 -13135,9 +13539,9 @@@ static void main_init(ShellState *data
    sqlite3_config(SQLITE_CONFIG_URI, 1);
    sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data);
    sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
 -  sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,        "sqlite> ");
 +  sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> ");
    sqlite3_snprintf(sizeof(continuePrompt), continuePrompt,"   ...> ");
+   init_std_inputs(stdin);
  }
  
  /*
@@@ -12336,22 -13186,16 +13590,23 @@@ static char *cmdline_option_value(int a
  #  endif
  #endif
  
 +#ifndef SHELL_MAIN
 +# if SQLITE_SHELL_IS_UTF8
 +#  define SHELL_MAIN main
 +# else
 +#  define SHELL_MAIN wmain
 +# endif
 +#endif
 +
  #if SQLITE_SHELL_IS_UTF8
 -int SQLITE_CDECL main(int argc, char **argv){
 +int SQLITE_CDECL SHELL_MAIN(int argc, char **argv){
  #else
 -int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
 +int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
    char **argv;
  #endif
 -  char *zErrMsg = 0;
    ShellState data;
    const char *zInitFile = 0;
+   int bQuiet = 0; /* for testing, to suppress banner and history actions */
    int i;
    int rc = 0;
    int warnInmemoryDb = 0;
        break;
  #endif
      }else if( strcmp(z, "-memtrace")==0 ){
 -      sqlite3MemTraceActivate(stderr);
 +      sqlite3MemTraceActivate(STD_ERR);
      }else if( strcmp(z,"-bail")==0 ){
        bail_on_error = 1;
+ #if SHELL_EXTENSIONS
+     }else if( strcmp(z,"-shxopts")==0 ){
+       data.bExtendedDotCmds = (u8)integerValue(argv[++i]);
+ #endif
      }else if( strcmp(z,"-nonce")==0 ){
        free(data.zNonce);
        data.zNonce = strdup(argv[++i]);
+     }else if( strcmp(z,"-quiet")==0 ){
+       bQuiet = (int)integerValue(cmdline_option_value(argc,argv,++i));
      }else if( strcmp(z,"-safe")==0 ){
 -      /* no-op - catch this on the second pass */
 +      /* catch this on the second pass (Unsafe is fine on invocation.) */
      }
    }
    verify_uninitialized();
    ** file is processed so that the command-line arguments will override
    ** settings in the initialization file.
    */
 -  for(i=1; i<argc; i++){
 +  for(i=1; i<argc && rc<2; i++){
      char *z = argv[i];
++    char *zModeSet = 0;
      if( z[0]!='-' ) continue;
      if( z[1]=='-' ){ z++; }
      if( strcmp(z,"-init")==0 ){
        i++;
      }else if( strcmp(z,"-html")==0 ){
--      data.mode = MODE_Html;
++      zModeSet = z;
      }else if( strcmp(z,"-list")==0 ){
--      data.mode = MODE_List;
++      zModeSet = z;
      }else if( strcmp(z,"-quote")==0 ){
--      data.mode = MODE_Quote;
--      sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Comma);
--      sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, SEP_Row);
++      zModeSet = z;
      }else if( strcmp(z,"-line")==0 ){
--      data.mode = MODE_Line;
++      zModeSet = z;
      }else if( strcmp(z,"-column")==0 ){
--      data.mode = MODE_Column;
++      zModeSet = z;
      }else if( strcmp(z,"-json")==0 ){
--      data.mode = MODE_Json;
++      zModeSet = z;
      }else if( strcmp(z,"-markdown")==0 ){
--      data.mode = MODE_Markdown;
++      zModeSet = z;
      }else if( strcmp(z,"-table")==0 ){
--      data.mode = MODE_Table;
++      zModeSet = z;
      }else if( strcmp(z,"-box")==0 ){
--      data.mode = MODE_Box;
++      zModeSet = z;
      }else if( strcmp(z,"-csv")==0 ){
--      data.mode = MODE_Csv;
--      memcpy(data.colSeparator,",",2);
++      zModeSet = z;
++    }else if( strcmp(z,"-ascii")==0 ){
++      zModeSet = z;
++    }else if( strcmp(z,"-tabs")==0 ){
++      zModeSet = z;
  #ifdef SQLITE_HAVE_ZLIB
      }else if( strcmp(z,"-zip")==0 ){
        data.openMode = SHELL_OPEN_ZIPFILE;
        data.openMode = SHELL_OPEN_READONLY;
      }else if( strcmp(z,"-nofollow")==0 ){
        data.openFlags |= SQLITE_OPEN_NOFOLLOW;
--    }else if( strcmp(z,"-ascii")==0 ){
--      data.mode = MODE_Ascii;
--      sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Unit);
-       sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, SEP_Record);
 -      sqlite3_snprintf(sizeof(data.rowSeparator),data.rowSeparator, SEP_Record);
--    }else if( strcmp(z,"-tabs")==0 ){
--      data.mode = MODE_List;
--      sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Tab);
--      sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, SEP_Row);
      }else if( strcmp(z,"-separator")==0 ){
        sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,
                         "%s",cmdline_option_value(argc,argv,++i));
        ShellSetFlag(&data, SHFLG_Backslash);
      }else if( strcmp(z,"-bail")==0 ){
        /* No-op.  The bail_on_error flag should already be set. */
+ #if SHELL_EXTENSIONS
+     }else if( strcmp(z,"-shxopts")==0 ){
+       i++; /* Handled on first pass. */
+ #endif
      }else if( strcmp(z,"-version")==0 ){
 -      printf("%s %s\n", sqlite3_libversion(), sqlite3_sourceid());
 -      return 0;
 +      fprintf(STD_OUT, "%s %s\n", sqlite3_libversion(), sqlite3_sourceid());
 +      rc = 2;
      }else if( strcmp(z,"-interactive")==0 ){
        stdin_is_interactive = 1;
      }else if( strcmp(z,"-batch")==0 ){
        z = cmdline_option_value(argc,argv,++i);
        if( z[0]=='.' ){
          rc = do_meta_command(z, &data);
-         if( rc && bail_on_error ){
-           data.abruptExit = rc;
-           rc = 2;
-           goto shell_bail;
 -        switch( rc ){
 -        case 2: return 0;
 -        case 3: return 1;
 -        default: return 2;
 -        case 0: break;
 -        case 1: if( bail_on_error ) return 1;
 -          break;
--        }
        }else{
 -        open_db(&data, 0);
 -        rc = shell_exec(&data, z, &zErrMsg);
 -        if( zErrMsg!=0 ){
 -          utf8_printf(stderr,"Error: %s\n", zErrMsg);
 -          if( bail_on_error ) return rc!=0 ? rc : 1;
 -        }else if( rc!=0 ){
 -          utf8_printf(stderr,"Error: unable to process SQL \"%s\"\n", z);
 -          if( bail_on_error ) return rc;
 -        }
 +        rc = run_single_query(&data, z);
 +      }
++      if( rc && bail_on_error ){
++        if( rc==2 ) rc = 0;
++        goto shell_bail;
+       }
  #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
      }else if( strncmp(z, "-A", 2)==0 ){
        if( nCmd>0 ){
        break;
  #endif
      }else if( strcmp(z,"-safe")==0 ){
 -      data.bSafeMode = data.bSafeModePersist = 1;
 +      data.bSafeMode = data.bSafeModeFuture = 1;
+     }else if( strcmp(z,"-quiet")==0 ){
+       ++i;
      }else{
 -      utf8_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z);
 -      raw_printf(stderr,"Use -help for a list of options.\n");
 -      return 1;
 +      utf8_printf(STD_ERR,"%s: Error: unknown option: %s\n", Argv0, z);
 +      raw_printf(STD_ERR,"Use -help for a list of options.\n");
 +      rc = 2;
 +    }
-     data.cMode = data.mode;
++    if( zModeSet!=0 ){
++      char *azModeCmd[] = { ".mode", zModeSet+1 };
++      modeCommand(azModeCmd, 2, &data, 0);
++      data.cMode = data.mode;
+     }
 -    data.cMode = data.mode;
    }
  
    if( !readStdin ){
      ** command-line inputs, except for the argToSkip argument which contains
      ** the database filename.
      */
 -    for(i=0; i<nCmd; i++){
 +    for(i=0; i<nCmd && rc<2; i++){
        if( azCmd[i][0]=='.' ){
          rc = do_meta_command(azCmd[i], &data);
-         if( rc && bail_on_error ){
-           goto shell_bail;
 -        if( rc ){
 -          free(azCmd);
 -          return rc==2 ? 0 : rc;
--        }
        }else{
 -        open_db(&data, 0);
 -        rc = shell_exec(&data, azCmd[i], &zErrMsg);
 -        if( zErrMsg || rc ){
 -          if( zErrMsg!=0 ){
 -            utf8_printf(stderr,"Error: %s\n", zErrMsg);
 -          }else{
 -            utf8_printf(stderr,"Error: unable to process SQL: %s\n", azCmd[i]);
 -          }
 -          sqlite3_free(zErrMsg);
 -          free(azCmd);
 -          return rc!=0 ? rc : 1;
 -        }
 +        rc = run_single_query(&data, azCmd[i]);
 +      }
++      if( rc && bail_on_error ){
++        goto shell_bail;
+       }
      }
    }else{
      /* Run commands received from standard input
      */
      if( stdin_is_interactive ){
        char *zHome;
 -      char *zHistory = 0;
 +      char *zHistory;
        int nHistory;
-       fprintf(STD_OUT,
-         "SQLite version %s %.19s\n" /*extra-version-info*/
-         "Enter \".help\" for usage hints.\n",
-         sqlite3_libversion(), sqlite3_sourceid()
-       );
-       if( warnInmemoryDb ){
-         fprintf(STD_OUT, "Connected to a ");
-         printBold("transient in-memory database");
-         fprintf(STD_OUT, ".\nUse \".open FILENAME\" to reopen on a "
-                "persistent database.\n");
-       }
-       zHistory = getenv("SQLITE_HISTORY");
-       if( zHistory ){
-         zHistory = strdup(zHistory);
-       }else if( (zHome = find_home_dir(0))!=0 ){
-         nHistory = strlen30(zHome) + 20;
-         if( (zHistory = malloc(nHistory))!=0 ){
-           sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome);
+       if( bQuiet ){
+         /* bQuiet is almost like normal interactive, but quieter. */
+         mainPrompt[0] = 0;
+         continuePrompt[0] = 0;
+       }else{
 -        printf(
++        fprintf(STD_OUT,
+           "SQLite version %s %.19s\n" /*extra-version-info*/
+           "Enter \".help\" for usage hints.\n",
+           sqlite3_libversion(), sqlite3_sourceid()
+         );
+         if( warnInmemoryDb ){
 -          printf("Connected to a ");
++          fprintf(STD_OUT, "Connected to a ");
+           printBold("transient in-memory database");
 -          printf(".\nUse \".open FILENAME\" to reopen on a "
++          fprintf(STD_OUT, ".\nUse \".open FILENAME\" to reopen on a "
+                  "persistent database.\n");
          }
-       }
-       if( zHistory ){ shell_read_history(zHistory); }
+         zHistory = getenv("SQLITE_HISTORY");
+         if( zHistory ){
+           zHistory = strdup(zHistory);
+         }else if( (zHome = find_home_dir(0))!=0 ){
+           nHistory = strlen30(zHome) + 20;
+           if( (zHistory = malloc(nHistory))!=0 ){
+             sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome);
+           }
+         }
+         if( zHistory ){ shell_read_history(zHistory); }
  #if HAVE_READLINE || HAVE_EDITLINE
-       rl_attempted_completion_function = readline_completion;
+         rl_attempted_completion_function = readline_completion;
  #elif HAVE_LINENOISE
-       linenoiseSetCompletionCallback(linenoise_completion);
+         linenoiseSetCompletionCallback(linenoise_completion);
  #endif
-       data.in = 0;
+       }
+       data.pInSource = &termInSource; /* read from stdin interactively */
        rc = process_input(&data);
-       if( zHistory ){
-         shell_stifle_history(2000);
-         shell_write_history(zHistory);
-         free(zHistory);
+       if( !bQuiet ){
+         if( zHistory ){
+           shell_stifle_history(2000);
+           shell_write_history(zHistory);
+           free(zHistory);
+         }
        }
      }else{
-       data.in = STD_IN;
+       data.pInSource = &stdInSource; /* read from stdin without prompts */
        rc = process_input(&data);
      }
    }
  #endif
    free(data.colWidth);
    free(data.zNonce);
-   /* Freed ShellState objects so that valgrind detects real memory leaks. */
+   for(i=0; i<data.nSavedModes; ++i) sqlite3_free(data.pModeStack[i]);
 -  /* Clear the global data structure so that valgrind will detect memory
 -  ** leaks */
 +  free(azCmd);
++  /* Clear ShellState objects so that valgrind detects real memory leaks. */
+   memset(&data, 0, sizeof(data));
 -  return rc & ~2; /* Clear the "quit" bit. */
 +  /* Process exit codes to yield single shell exit code. 
 +   * rc == 2 is a quit signal, resulting in no error by itself.
 +   * data.bAbruptExit conveys either an normal (success or error) exit
 +   * code or an abnormal exit code. Its abnormal values take priority.
 +   */
 +  /* Check for an abnormal exit, and issue error if so. */
 +  if( rc>2 || data.abruptExit>1 ){
 +    rc = (rc<data.abruptExit)? data.abruptExit : rc;
 +    raw_printf(STD_ERR, "Abnormal exit (%d)\n", data.abruptExit);
 +  }else{
 +    /* rc is one of 0,1,2, mapping to 0,1,0 shellexit codes. */
-     rc &= ~1;
++    rc &= ~2;
 +  }
-   memset(&data, 0, sizeof(data));
 +  return rc;
  }
index 2aca271e371e012584a72fbeb84c4fe94cace4c7,cbbadc58c9e1b4d83745e503473be9d81e1b73c3..050c9acd334517fea687e76e16694aadf12169d4
@@@ -74,8 -74,8 +74,8 @@@ do_test shell1-1.3.1 
    catchcmd "-init FOO test.db" ""
  } {0 {}}
  do_test shell1-1.3.2 {
 -  catchcmd "-init FOO test.db .quit BAD" ""
 +  catchcmdex "-init FOO test.db .quit BAD" ""
- } {1 {child process exited abnormally}}
+ } {0 {}}
  do_test shell1-1.3.3 {
    catchcmd "-init FOO test.db BAD .quit" ""
  } {/1 .Error: in prepare, near "BAD": syntax error*/}
index 0000000000000000000000000000000000000000,6113ce748a9691973284ef5e4ea4564305fcacc4..bbbe181407705e74ef02ae89cb64dec9b54649c1
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,260 +1,261 @@@
 -} {1 {Skipping badly named $v. Run ".help x"}}
+ # 2022 Feb 5
+ #
+ # 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.
+ #
+ #***********************************************************************
+ #
+ # The focus of this file is testing the CLI shell tool enhanced parsing,
+ # new .parameter subcommands and uses, and the new .x meta-command.
+ #
+ #
+ # Test plan:
+ #
+ #   shell9-1.*: command line parsing and acting accordingly
+ #   shell9-2.*: Basic "dot" command, cross-line token parsing
+ #   shell9-3.*: .parameter set options and types
+ #   shell9-4.*: .parameter save/load operation
+ #   shell9-5.*: Ensure "dot" commands and SQL intermix ok.
+ #   shell9-6.*: .x command operation and refusal
+ #
+ set testdir [file dirname $argv0]
+ source $testdir/tester.tcl
+ set CLI [test_find_cli]
+ db close
+ forcedelete test.db test.db-journal test.db-wal
+ forcedelete x.db xn.db
+ sqlite3 db test.db
+ #----------------------------------------------------------------------------
+ # Test cases shell9-1.*: command line parsing and acting accordingly
+ do_test shell9-1.1 {
+   set res [catchcmd ":memory: -cmd .quit" ""]
+ } {0 {}}
+ do_test shell9-1.2 {
+   set res [catchcmd ":memory: -shxopts 1 -cmd .shxopts -cmd .quit" ""]
+ } {0 {     name    value  "-shxopts set"
+    --------  -----  ---------------
+     parsing    1    "-shxopts 0x01"
+    all_opts    0    "-shxopts 0x07"}}
+ do_test shell9-1.3 {
+   set res [catchcmd ":memory: -cmd .shxopts -cmd .quit" ""]
+ } {0 {     name    value  "-shxopts set"
+    --------  -----  ---------------
+     parsing    0    "-shxopts 0x01"
+    all_opts    0    "-shxopts 0x07"}}
+ #----------------------------------------------------------------------------
+ # Test cases shell9-2.*: Basic "dot" command, cross-line token parsing
+ set cmds ".print 'l1\nl2'\n.print 'a\\\nb'"
+ do_test shell9-2.1 {
+   set res [catchcmd ":memory: -shxopts 1" $cmds]
+ } {0 {l1
+ l2
+ ab}}
+ set cmds " .print \"l1\nl2\"\n .print \"a\\\nb\" \n# c\n  ## c"
+ do_test shell9-2.2 {
+   set res [catchcmd ":memory: -shxopts 1" $cmds]
+ } {0 {l1
+ l2
+ ab}}
+ set cmds ".echo on\n.seeargs 'a'\\\n'b'\n#!"
+ do_test shell9-2.3 {
+   set res [catchcmd ":memory: -shxopts 1" $cmds]
+ } {0 {.seeargs 'a''b'
+ a|b|}}
+ set cmds ".echo on\n.seeargs a\\\nb\n#!"
+ do_test shell9-2.4 {
+   set res [catchcmd ":memory: -shxopts 1" $cmds]
+ } {0 {.seeargs ab
+ ab|}}
+ set cmds ".echo 1\n.print \"\\\"\nq\\\"\""
+ do_test shell9-2.5 {
+   set res [catchcmd ":memory: -shxopts 1" $cmds]
+ } {0 {.print "\"
+ q\""
+ "
+ q"}}
+ #----------------------------------------------------------------------------
+ # Test cases shell9-3.*: .parameter set options and types
+ set cmds {
+ .pa set -b b x'a5a5'
+ .pa set -i ii 33-11
+ .pa set -i ir 3.3-1.1
+ .pa set -n ni 3-1
+ .pa set -n nr 3.3-1.1
+ .pa set -r ri 1
+ .pa set -r rr 1.2
+ .pa set -t t 123
+ .mode list
+ select typeof(value) from temp.sqlite_parameters order by key;
+ }
+ do_test shell9-3.1 {
+   set res [catchcmd ":memory:" $cmds]
+ } {0 {blob
+ integer
+ integer
+ integer
+ real
+ real
+ real
+ text}}
+ set cmds {
+ .pa set expr 1 + 2 * 3
+ .pa set text "'1 + 2*3'"
+ .pa set -t ttext 1 + 2*3
+ .pa list
+ }
+ do_test shell9-3.2 {
+   set res [catchcmd ":memory:" $cmds]
+ } {0 {Scripts
+ name  value
+ expr  7
+ text  1 + 2*3
+ ttext 1 + 2*3}}
+ set cmds {
+ .pa set a "'a'"
+ .pa set b "'b'"
+ .pa set c "'c'"
+ .pa unset
+ .pa list
+ .pa clear a
+ .pa list
+ .pa unset b
+ .pa list
+ .pa clear
+ .pa list
+ .pa set d "'e'"
+ .pa set e "'e'"
+ .pa unset d e
+ .pa list
+ }
+ do_test shell9-3.3 {
+   set res [catchcmd ":memory:" $cmds]
+ } {0 {Scripts
+ name value
+ a    a
+ b    b
+ c    c
+ Scripts
+ name value
+ b    b
+ c    c
+ Scripts
+ name value
+ c    c}}
+ if {$::tcl_platform(platform)=="unix"} {
+   proc set_ed {sayWhat} {
+     global env
+     set env(VISUAL) "echo SELECT $sayWhat ';' >"
+     return 1
+   }
+ } elseif {$::tcl_platform(platform)=="windows"} {
+   proc set_ed {sayWhat} {
+     global env
+     set env(VISUAL) "echo SELECT $sayWhat ; >"
+     return 1
+   }
+ } else { return 0 }
+ if {[set_ed @name]} {
+   set cmds {
+ .pa set @name Fido
+ .pa edit -t dog
+ .x dog
+   }
+   do_test shell9-3.4 {
+     set res [catchcmd ":memory: -quiet 1 -shxopts 1 -interactive" $cmds]
+   } {0 {.pa set @name Fido
+ .pa edit -t dog
+ .x dog
+ Fido
+   }}
+ }
+ #----------------------------------------------------------------------------
+ # Test cases shell9-4.*: .parameter save/load operation
+ set cmds {
+   .pa set -t x '.print Ex'
+   .pa set -i $n 7
+   .pa save xn.db
+   .pa save x.db x
+   .pa clear
+   .pa load xn.db
+   .pa list
+   .pa clear
+   .pa load x.db
+   .pa list
+   .pa clear
+   .pa load xn.db $n
+   .pa list
+ }
+ do_test shell9-4.1 {
+   set res [catchcmd ":memory: -shxopts 1" $cmds]
+ } {0 {Binding Values:
+ name value
+ $n   7
+ Scripts
+ name value
+ x    .print Ex
+ Scripts
+ name value
+ x    .print Ex
+ Binding Values:
+ name value
+ $n   7}}
+ forcedelete x.db xn.db
+ #----------------------------------------------------------------------------
+ # Test cases shell9-5.*: Ensure "dot" commands and SQL intermix ok.
+ set cmds {
+  .pa set -t mixed "
+  .print Hi.
+  select 'Hi.';
+  .print 'Good\
+  Bye.'
+  select 'Good'||
+  ' Bye.';
+  "
+  .x mixed
+ }
+ do_test shell9-5.1 {
+   set res [catchcmd ":memory: -shxopts 1" $cmds]
+ } {0 {Hi.
+ Hi.
+ Good Bye.
+ Good Bye.}}
+ #----------------------------------------------------------------------------
+ # Test cases shell9-6.*: .x command operation and refusal
+ set cmds {
+  .pa set -t $v '.print Ok'
+  .x $v
+ }
+ do_test shell9-6.1 {
+   set res [catchcmd ":memory: -bail -shxopts 1" $cmds]
++} {1 {Skipping badly named $v. Run ".help x"
++Abnormal exit (0)}}
+ finish_test
index 9328991cddb0724e77215254eec9f8116844d6d0,f1e2bd2eb6de12486d3a2e49831f3e8e9a26e440..cdb7d894a7318f9f60e840e8d82cc36d9b6e31c1
@@@ -58,712 -21,62 +58,720 @@@ set ::headComment {/* DO NOT EDIT
  ** Most of the code found below comes from the "src/shell.c.in" file in
  ** the canonical SQLite source tree.  That main file contains "INCLUDE"
  ** lines that specify other files in the canonical source tree that are
 -** inserted to getnerate this complete program source file.
 +** inserted and transformed, (via macro invocations explained by running
 +** "tool/mkshellc.tcl --help"), to generate this complete program source.
  **
 -** The code from multiple files is combined into this single "shell.c"
 -** source file to help make the command-line program easier to compile.
 +** By means of this generation process, creating this single "shell.c"
 +** file, building the command-line program is made simpler and easier.
  **
  ** To modify this program, get a copy of the canonical SQLite source tree,
 -** edit the src/shell.c.in" and/or some of the other files that are included
 -** by "src/shell.c.in", then rerun the tool/mkshellc.tcl script.
 +** edit file src/shell.c.in and/or some of the other files included by it,
 +** then rerun the tool/mkshellc.tcl script.
  */}
 -set in [open $topdir/src/shell.c.in]
 -fconfigure $in -translation binary
 -proc omit_redundant_typedefs {line} {
 -  global typedef_seen
 +
 +set ::headCommentLines [expr 1+[regexp -all "\n" $::headComment]]
 +
 +set ::topDir [file dir [file dir [file normal $argv0]]]
 +set runMode normal
 +
 +set ::lineTags 0 ; # 0 => none, 1 => source change, 2 => line syncs, 3 => more
 +
 +set ::tclGenerate 0
 +set ::verbosity 0
 +set ::inFiles {}
 +array set ::incTypes [list "*" "$::topDir/src/shell.c.in"]
 +array set ::ignoringCommands [list]
 +
 +while  {[llength $argv] > 0} {
 +  foreach {opt} $arv { set argv [lreplace $argv 1 end] ; break }
 +  if {[regexp {^-{1,2}((help)|(details)|(parameters))$} $opt ma ho]} {
 +    set runMode $ho
 +  } elseif {[regexp {^-it$} $opt]} {
 +    foreach {nextOpt} $arv { set argv [lreplace $argv 1 end] ; break }
 +    if {![regexp {^(\w+)=(.+)$} $nextOpt ma k v]} {
 +      puts stderr "Get help with --help."
 +      exit 1 
 +    }
 +    set ::incTypes($k) $v
 +  } elseif {$opt eq "-top-dir"} {
 +    foreach {::topDir} $arv { set argv [lreplace $argv 1 end] ; break }
 +    if {::topDir eq ""} { set ::topDir . }
 +  } elseif {$opt eq "-source-tags"} {
 +    foreach {nextOpt} $arv { set argv [lreplace $argv 1 end] ; break }
 +    if {![regexp {^\d$} $nextOpt ::lineTags]} {
 +      puts stderr "Argument following -source-tags must be a digit."
 +    }
 +  } elseif {$opt eq "-tcl"} {
 +    puts stderr "Warning: Tcl extension not wholly implemented."
 +    set ::tclGenerate 1
 +  } elseif {$opt eq "-v"} {
 +    incr ::verbosity
 +  } elseif {[regexp {^[^-]} $opt]} {
 +    lappend ::inFiles $opt
 +  } else {
 +    puts stderr "Skipping unknown option: $opt"
 +  }
 +}
 +if {$runMode eq "normal"} {
 +  if {[llength $::inFiles] == 0} {
 +    lappend ::inFiles $::incTypes(*)
 +  }
 +  fconfigure stdout -translation {auto lf}
 +  set ::outStrm stdout
 +}
 +
 +# Given a path relative to <project>/src, return its full pathname.
 +proc project_path {relPath} {
 +  return "$::topDir/src/$relPath"
 +}
 +
 +if {$::lineTags >= 3} {
 +  # These k/v stores hold {lineNum filename} lists keyed by meta-command,
 +  # which are used to get #line directives on all dispatch and help table
 +  # entries, and any conditionals affecting their compilation.
 +  array set ::cmd_help_tags {}
 +  array set ::cmd_dispatch_tags {}
 +  array set ::cmd_conditional_tags {}
 +}
 +
 +# Set one of above k/v stores, (help, dispatch, conditional) for given
 +# cmd from members of inSrc triple {filename istrm lineNumber}.
 +proc set_src_tags {which cmd inSrc} {
 +  if {$::lineTags >= 3} {
 +    foreach {filename _ lineNumber} $inSrc break
 +    set [subst ::cmd_${which}_tags]($cmd) [list $lineNumber $filename]
 +  }
 +}
 +# Return pair {lineNumber fileName} from one of above k/v stores,
 +# (help, dispatch, conditional) for given cmd, or get empty list.
 +# The empty list indicates either not keeping such k/v, or there
 +# is not one for the given cmd
 +proc get_src_tags {which cmd} {
 +  if {$::lineTags >= 3 && [info exists [subst ::cmd_${which}_tags]($cmd)]} {
 +    return [subst "\$[subst ::cmd_${which}_tags]($cmd)"]
 +  }
 +  return {}
 +}
 +
 +# To faciliate non-excessive line tagging, track these values before emits:
 +# These 2 variables are set/used only by procs line_tag and emit_sync .
 +set ::apparentSrcFile ""
 +set ::apparentSrcPrecLines $::headCommentLines
 +
 +# Maybe put a #line directive if ::lineTags not 0. Directive style depends
 +# on its value and whether srcFile input is provided as follows:
 +# 1 => just file changes, 2 => line syncs too if srcFile not empty.
 +# A #line directive is only emitted if its kind is enabled
 +# All #line emits pass through this proc.
 +proc line_tag { ostrm srcPrecLines {srcFile ""} } {
 +  if {$::lineTags == 0} return
 +  set sayLine [expr {$srcPrecLines + 1}]
 +  if {$srcFile ne ""} {
 +    set ::apparentSrcFile $srcFile
 +    puts $ostrm "#line $sayLine \"$::apparentSrcFile\""
 +  } elseif {$::lineTags > 1} {
 +    puts $ostrm "#line $sayLine"
 +  }
 +  set ::apparentSrcPrecLines $srcPrecLines
 +}
 +
 +# Put a #line directive only if needed to resynchronize compiler's
 +# notion of source line location with actual source line location.
 +# And do this only if about to emit some line(s). Then emit them.
 +# This proc is used for all output emits (to make this work.)
 +# The precLines input is the number of source lines preceding the
 +# one to be represented (via #line ...) as producing next output.
 +proc emit_sync { lines ostrm precLines {fromFile ""} } {
 +  if {$::lineTags > 0} {
 +    if {$fromFile ne "" && $fromFile ne $::apparentSrcFile} {
 +      line_tag $ostrm $precLines $fromFile
 +    } elseif {$::lineTags > 1
 +              && $precLines != $::apparentSrcPrecLines
 +              && $lines ne {}} {
 +      line_tag $ostrm $precLines
 +    }
 +  }
 +  foreach line $lines {
 +    puts $ostrm $line
 +    incr ::apparentSrcPrecLines
 +  }
 +}
 +
 +array set ::cmd_help {}
 +array set ::cmd_dispatch {}
 +array set ::cmd_condition {}
 +array set ::inc_type_files {}
 +set ::iShuffleErrors 0
 +# Ease use of { and } in literals. Instead, $::lb and $::rb can be used.
 +regexp {(\{)(\})} "{}" ma ::lb ::rb
 +
 +# Setup dispatching function signature and table entry struct .
 +# The effect of these key/value pairs is as this --parameters output says:
 +set ::parametersHelp {
 +  The following parameters given to DISPATCH_CONFIG have these effects:
 +   RETURN_TYPE sets the generated dispatchable function signature return type.
 +   STORAGE_CLASS sets the dispatchable function linkage, (typically "static".)
 +   ARGS_SIGNATURE sets the formal argument list for the dispatchable functions.
 +   DISPATCH_ENTRY sets the text of each entry line in emitted dispatch table.
 +   DISPATCHEE_NAME sets the name to be generated for dispatchable functions.
 +   CMD_CAPTURE_RE sets a regular expression to be used for capturing the name
 +     to be used for meta-commands within a line passed into COLLECT_DISPATCH,
 +     (which is needed to permit them to be emitted in lexical order by name.)
 +   DC_ARG_COUNT sets the effective argument count for DISPATCHABLE_COMMAND().
 +   DC_ARG#_DEFAULT sets a default value, DISPATCHABLE_COMMAND() #'th argument.
 +   HELP_COALESCE sets whether to coalesce secondary help text and add newlines.
 +  Within values set for ARGS_SIGNATURE, DISPATCHEE_NAME, and DISPATCH_ENTRY
 +  parameters, the variables $cmd and $arg# (where # is an integer) may appear,
 +  to be replaced by the meta-command name or the #'th effective argument to
 +  DISPATCHABLE_COMMAND(). The "effective" argument is either what is provided,
 +  or a default value when the actual argument is missing (at the right end of
 +  the provided argument list) or the argument has the value ? . The expansion
 +  of $cmd and $arg# variables is done by Tcl evaluation (via subst), allowing
 +  a wide range of logic to be employed in the derivation of effective values.
 +}
 +array set ::dispCfg [list \
 +  RETURN_TYPE int \
 +  STORAGE_CLASS static \
 +  ARGS_SIGNATURE "char *\$arg4\\\[\\\], int \$arg5, ShellState *\$arg6" \
 +  DISPATCH_ENTRY \
 +   "{ \"\$cmd\", \${cmd}Command, \$arg1,\$arg2,\$arg3 }," \
 +  DISPATCHEE_NAME {${cmd}Command} \
 +  CMD_CAPTURE_RE "^\\s*$::lb\\s*\"(\\w+)\"" \
 +  HELP_COALESCE 0 \
 +]
 +# Other config keys:
 +#  DC_ARG_COUNT=<number of arguments to DISPATCHABLE_COMMAND()>
 +#  DC_ARG#_DEFAULT=<default value for the #th argument>
 +# Variables $cmd and $arg# (where # = 0 .. DC_ARG_COUNT-1) have values
 +# when ARGS_SIGNATURE, DISPATCH_ENTRY, and DISPATCHEE_NAME are evaluated.
 +
 +proc emit_conditionally {cmd lines inSrc ostrm {indent ""} {cmdTagStore {}}} {
 +  foreach {fname _ lnum} $inSrc break
 +  set wrapped [info exists ::cmd_condition($cmd)]
 +  if {$wrapped} {
 +    emit_sync [list $::cmd_condition($cmd)] $ostrm $lnum $fname
 +    incr lnum
 +  }
 +  if {[regexp {^\s*(\d+)\s*$} $indent ma inum]} {
 +    set lead [string repeat " " $inum]
 +    set ilines [list]
 +    foreach line $lines { lappend ilines "$lead[string trimleft $line]" }
 +    set lines $ilines
 +  }
 +  emit_sync $lines $ostrm $lnum $fname
 +  incr lnum [llength $lines]
 +  if {$wrapped} {
 +    emit_sync [list "#endif"] $ostrm $lnum $fname
 +    incr lnum
 +  }
 +}
 +
 +# Coalesce secondary help text lines using C's string literal concatenation
 +# and arrange that each command's help has one primary (leading '.') help
 +# text line and one secondary help text line-set even if it is empty.
 +proc coalesce_help {htin} {
 +  set htrv {}
 +  foreach hl $htin {
 +    if {[regexp {^\s*"\.\w+} $hl]} { ;# "
 +      lappend htrv [regsub {"\s*,\s*$} $hl {\n",}]
 +    } elseif {[regexp {^\s*#\s*\w+} $hl]} {
 +      lappend htrv $hl
 +    } else {
 +      lappend htrv [regsub {"\s*,\s*$} $hl {\n"}]
 +    }
 +  }
 +  lappend htrv {"",}
 +}
 +
 +# Convert list of help text lines into a key-value list.
 +# Keys are the command names. Values are the help for the
 +# commands as a list of lines, with .* logically first.
 +# Any #if... #endif structures are maintained and do not
 +# interact with "logically first" .* lines, except that
 +# only one such line is seen within such a conditional.
 +# (The effect of this is to defeat sorting by command if
 +# help for multiple commands' is within one conditional.)
 +proc chunkify_help {htin} {
 +  array set rv [list]
 +  set if_depth 0
 +  set cmd_seen ""
 +  set chunk {}
 +  foreach htx $htin {
 +    if {[regexp {^\s*\"\.\w} $htx] && $cmd_seen ne "" && $if_depth == 0} {
 +      # Flush accumulated chunk.
 +      set rv($cmd_seen) $chunk
 +      set cmd_seen ""
 +      set chunk {}
 +    }
 +    lappend chunk $htx
 +    if {[regexp {^\s*#if} $htx]} {
 +      incr if_depth
 +    } elseif {[regexp {^\s*#endif} $htx]} {
 +      incr if_depth -1
 +    } else {
 +      if {[regexp {^\s*\"\.(\w+)} $htx all cmd] && $cmd_seen eq ""} {
 +        set cmd_seen $cmd
 +      }
 +    }
 +  }
 +  if {$if_depth != 0} {
 +    puts stderr "Help chunk bad #conditional:"
 +    puts stderr [join $htin "\n"]
 +    puts stderr "Swallowed [join $chunk \n]"
 +    incr ::iShuffleErrors
 +  } else {
 +    if {$cmd_seen ne "" && [llength $chunk] > 0} {
 +      # Flush accumulated chunk.
 +      set rv($cmd_seen) $chunk
 +    } elseif {$cmd_seen ne "" || [llength $chunk] > 0} {
 +      puts stderr "Orphaned help: '$cmd_seen' [join $chunk \n]"
 +      incr ::iShuffleErrors
 +    }
 +  }
 +  if {$::dispCfg(HELP_COALESCE)} {
 +    foreach cmd_seen [array names rv] {
 +      set rv($cmd_seen) [coalesce_help $rv($cmd_seen)]
 +    }
 +  }
 +  return [array get rv]
 +}
 +
 +array set ::macroTailREs [list \
 +  COLLECT_DISPATCH {^\(\s*([\w\*]+)\s*\)\[} \
 +  COLLECT_HELP_TEXT {^\[} \
 +  COMMENT {\s+(.*)$} \
 +  CONDITION_COMMAND {^\(\s*(\w+)\s+([^;]+)\);} \
 +  DISPATCH_CONFIG {^\[} \
 +  DISPATCHABLE_COMMAND {^\(([\w\? ]+)\)(\S)\s*$} \
 +  EMIT_DISPATCH {^\((\d*)\)} \
 +  EMIT_HELP_TEXT {^\((\d*)\)} \
 +  INCLUDE {^(?:\(\s*(\w+)\s*\))|(?:\s+([\w./\\]+)\M)} \
 +  IGNORE_COMMANDS {^\(\s*([-+\w ]*)\)\s*;\s*} \
 +]
 +# Names of the subcaptures as formal parameter to macro procs.
 +# COMMENT tailCapture_Commentary
 +# CONDITION_COMMAND tailCapture_Cmd_Condition
 +# CONFIGURE_DISPATCH tailCapture_Empty
 +# COLLECT_DISPATCH tailCapture_Cmd
 +# COLLECT_HELP_TEXT tailCapture_Empty
 +# DISPATCHABLE_COMMAND tailCapture_ArgsGlom_TrailChar
 +# EMIT_DISPATCH tailCapture_Indent
 +# EMIT_HELP_TEXT tailCapture_Indent
 +# IGNORED_COMMANDS tailCapture_SignedCmdGlom
 +# INCLUDE tailCapture_IncType_Filename
 +
 +array set ::macroUsages [list \
 +  COLLECT_DISPATCH "\[\n   <dispatch table entry lines>\n  \];" \
 +  COLLECT_HELP_TEXT "\[\n   <help text lines>\n  \];" \
 +  COMMENT " <arbitrary characters to end of line>" \
 +  CONDITION_COMMAND "( name pp_expr );" \
 +  DISPATCH_CONFIG "\[\n   <NAME=value lines>\n  \];" \
 +  DISPATCHABLE_COMMAND \
 +      "( name args... ){\n   <implementation code lines>\n  }" \
 +  EMIT_DISPATCH "( indent );" \
 +  EMIT_HELP_TEXT "( indent );" \
 +  INCLUDE {( <inc_type> )} \
 +  SKIP_COMMANDS "( <signed_names> );" \
 +]
 +# RE for early discard of non-macro lines, matching all above keywords
 +set ::macroKeywordTailRE \
 + {^\s{0,8}((?:(?:CO)|(?:DI)|(?:EM)|(?:IN)|(?:SK))[A-Z_]+)\M(.+)$}
 +
 +########
 +# Macro procs, general signature and usage:
 +# inSrc is a triple, { input_filename open_input_stream input_lines_consumed }.
 +# Arg 2 is the macro tail as RE-captured by one of ::macroTailREs .
 +# ostrm is the open output stream for all regular output.
 +# The number of input lines consumed, including macro invocation, is returned.
 +#
 +# These procs may consume additional input, leave side-effects, or emit
 +# output to ostrm (via emit_sync), as individually documented.
 +# Their names always exactly match the invocation identifier.
 +
 +proc IGNORED_COMMANDS {inSrc tcSignedCmdGlom ostrm} {
 +  # Cause the listed commands to be ignored or allowed to generate, as set
 +  # by a preceeding + or - respectively in the list. This may be useful
 +  # when statically extending the shell to avoid duplicate implementation.
 +  # Commands never mentioned within this macro are allowed to generate.
 +  # TBD WIP
 +  set sign ""
 +  foreach {. o} [regexp -inline -all {\s*([\-\+]|[\w]+)\s*} $tcSignedCmdGlom] {
 +    if {![regexp {[\+\-\?]} $o . sign]} {
 +      if {$sign eq "+"} {
 +      } else {
 +      }
 +    }
 +  }
 +  return 1
 +}
 +
 +proc COLLECT_DISPATCH {inSrc tailCaptureCmdOrStar ostrm} {
 +  # Collect dispatch table entries, along with cmd(s) as ordering info.
 +  foreach {infile istrm inLineNum} $inSrc {}
 +  foreach {cmd} $tailCaptureCmdOrStar {}
 +  set iAte 2
 +  set lx [gets $istrm]
 +  set disp_frag {}
 +  while {![eof $istrm] && ![regexp {^\s*\];} $lx]} {
 +    lappend disp_frag $lx
 +    set grabCmd $::dispCfg(CMD_CAPTURE_RE)
 +    if {![regexp $grabCmd $lx ma dcmd]} {
 +      puts stderr "malformed dispatch element:\n $lx"
 +      incr ::iShuffleErrors
 +    } elseif {$cmd ne "*" && $dcmd ne $cmd} {
 +      puts stderr "misdeclared dispatch element:\n $lx"
 +      incr ::iShuffleErrors
 +    } else {
 +      set ::cmd_dispatch($dcmd) [list $lx]
 +      set_src_tags dispatch $dcmd $inSrc
 +    }
 +    set lx [gets $istrm]
 +    incr iAte
 +  }
 +  return $iAte
 +}
 +
 +proc COMMENT {inSrc tailCaptureIgnore ostrm} {
 +  # Allow comments in an input file which have no effect on output.
 +  return 1
 +}
 +
 +proc INCLUDE {inSrc tailCaptureIncType ostrm} {
 +  # If invoked with a bare filename, include the named file. If invoked
 +  # with the parenthesized word form, include a file named by means of
 +  # the '-it <inc_type>=filename' command line option, provided that the
 +  # word matches a specified <inc_type>. Otherwise, do nothing.
 +  foreach {it rfpath} $tailCaptureIncType break
 +  foreach { srcFile istrm srcPrecLines } $inSrc break
 +  set saySkip ""
 +  if {$it ne ""} {
 +    if {[info exists ::incTypes($it)]} {
 +      set rfpath $::incTypes($it)
 +      if {![file exists [project_path $rfpath]]} {
 +        set saySkip "/* INCLUDE($it), of missing \"$rfpath\" skipped. */"
 +      }
 +    } else {
 +      set saySkip "/* INCLUDE($it), undefined and skipped. */"
 +    }
 +  }
 +  if {$saySkip ne ""} {
 +    emit_sync [list $saySkip] $ostrm $srcPrecLines $srcFile
 +  } else {
 +    process_file [project_path $rfpath] $ostrm
 +    incr srcPrecLines
 +    emit_sync {} $ostrm $srcPrecLines $srcFile
 +  }
 +  return 1
 +}
 +
 +proc COLLECT_HELP_TEXT {inSrc tailCaptureEmpty ostrm} {
 +  # Collect help text table values, along with ordering info.
 +  foreach { srcFile istrm srcPrecLines } $inSrc break
 +  set iAte 2
 +  set help_frag {}
 +  set lx [gets $istrm]
 +  while {![eof $istrm] && ![regexp {^\s*\];} $lx]} {
 +    lappend help_frag $lx
 +    set lx [gets $istrm]
 +    incr iAte
 +  }
 +  set chunked_help [chunkify_help $help_frag]
 +  array set ::cmd_help $chunked_help
 +  foreach {cmd _} $chunked_help { set_src_tags help $cmd $inSrc }
 +  return $iAte
 +}
 +
 +proc CONDITION_COMMAND {inSrc tailCap ostrm} {
 +  # Name a command to be conditionally available, with the condition.
 +  foreach {cmd pp_expr} $tailCap { set pp_expr [string trim $pp_expr] ; break }
 +  if {[regexp {^(!)?defined\(\s*(\w+)\s*\)} $pp_expr ma bang pp_var]} {
 +    if {$bang eq "!"} {
 +      set pp_expr "#ifndef $pp_var"
 +    } else {
 +      set pp_expr "#ifdef $pp_var"
 +    }
 +  } else {
 +    set pp_expr "#if [string trim $pp_expr]"
 +  }
 +  set ::cmd_condition($cmd) $pp_expr
 +  set_src_tags conditional $cmd $inSrc
 +  return 1
 +}
 +
 +proc DISPATCH_CONFIG {inSrc tailCaptureEmpty ostrm} {
 +  foreach { srcFile istrm srcPrecLines } $inSrc break
 +  # Set parameters affecting generated dispatchable command function
 +  # signatures and generated dispatch table entries.
 +  set iAte 2
 +  set def_disp {}
 +  set lx [gets $istrm]
 +  while {![eof $istrm] && ![regexp {^\s*\];} $lx]} {
 +    lappend def_disp $lx
 +    set lx [gets $istrm]
 +    incr iAte
 +  }
 +  foreach line $def_disp {
 +    if {[regexp {^\s*(\w+)=(.+)$} $line ma k v]} {
 +      set ::dispCfg($k) $v
 +    }
 +  }
 +  return $iAte
 +}
 +
 +proc DISPATCHABLE_COMMAND {inSrc tailCapture ostrm} {
 +  # Generate and emit a function definition, maybe wrapped as set by
 +  # CONDITION_COMMAND(), and generate/collect its dispatch table entry,
 +  # as determined by its actual arguments and DISPATCH_CONFIG parameters.
 +  foreach { srcFile istrm srcPrecLines } $inSrc break
 +  set args [lindex $tailCapture 0]
 +  set tc [lindex $tailCapture 1]
 +  if {$tc ne $::lb} {
 +    yap_usage "DISPATCHABLE_COMMAND($args)$tc" DISPATCHABLE_COMMAND
 +    incr $::iShuffleErrors
 +    return 0
 +  }
 +  set iAte 1
 +  set args [split [regsub {\s+} [string trim $args] " "]]
 +  set na [llength $args]
 +  set cmd [lindex $args 0]
 +  set naPass $::dispCfg(DC_ARG_COUNT)
 +  if {$na > $naPass} {
 +    puts stderr "Bad args: $lx"
 +  } else {
 +    while {$na < $naPass} {
 +      set nad "DC_ARG${na}_DEFAULT"
 +      if {![info exists ::dispCfg($nad)]} {
 +        puts stderr "Too few args: $lx (need $naPass)"
 +        incr ::iShuffleErrors
 +        break
 +      } else {
 +        lappend args [subst $::dispCfg($nad)]
 +      }
 +      incr na
 +    }
 +    set body {}
 +    while {![eof $istrm]} {
 +      set bl [gets $istrm]
 +      incr iAte
 +      lappend body $bl
 +      if {[regexp "^$::rb\\s*\$" $bl]} { break }
 +    }
 +    for {set aix 1} {$aix < $na} {incr aix} {
 +      set av [lindex $args $aix]
 +      if {$av eq "?"} {
 +        set ai [expr {$aix + 1}]
 +        set aid "DC_ARG${ai}_DEFAULT"
 +        set av [subst $::dispCfg($aid)]
 +      }
 +      set "arg$aix" $av
 +    }
 +    if {$cmd ne "?"} {
 +      set rsct $::dispCfg(STORAGE_CLASS)
 +      set rsct "$rsct $::dispCfg(RETURN_TYPE)"
 +      set argexp [subst $::dispCfg(ARGS_SIGNATURE)]
 +      set fname [subst $::dispCfg(DISPATCHEE_NAME)]
 +      set funcOpen "$rsct $fname\($argexp\)$::lb"
 +      set dispEntry [subst $::dispCfg(DISPATCH_ENTRY)]
 +      emit_conditionally $cmd [linsert $body 0 $funcOpen] $inSrc $ostrm
 +      set ::cmd_dispatch($cmd) [list $dispEntry]
 +      set_src_tags dispatch $cmd $inSrc
 +    }
 +  }
 +  return $iAte
 +}
 +
 +proc EMIT_DISPATCH {inSrc tailCap ostrm} {
 +  # Emit the collected dispatch table entries, in command order, maybe
 +  # wrapped with a conditional construct as set by CONDITION_COMMAND().
 +  foreach cmd [lsort [array names ::cmd_dispatch]] {
 +    emit_conditionally $cmd $::cmd_dispatch($cmd) $inSrc $ostrm $tailCap
 +  }
 +  return 1
 +}
 +
 +proc EMIT_HELP_TEXT {inSrc tailCap ostrm} {
 +  # Emit the collected help text table entries, in command order, maybe
 +  # wrapped with a conditional construct as set by CONDITION_COMMAND().
 +  foreach htc [lsort [array names ::cmd_help]] {
 +    emit_conditionally $htc $::cmd_help($htc) $inSrc $ostrm $tailCap
 +  }
 +  return 1
 +}
 +
 +proc say_usage {macros {extra {}}} {
 +  puts stderr "Usage:$extra"
 +  foreach m $macros {puts stderr "  $m$::macroUsages($m)"}
 +}
 +proc yap_usage {got macro} {
 +  puts stderr "Bad macro use: $got"
 +  say_usage $macro
 +}
 +
 +# Perform any input collection or deferred output emits specified by a macro.
 +# Return number of input lines consumed, or 0 if not a recognized macro.
 +# This function may consume additional lines via triple inSrc.
 +proc do_macro {inSrc lx ostrm} {
 +  if {![regexp $::macroKeywordTailRE $lx ma macro tail] \
 +          || ![info exists ::macroTailREs($macro)]} {
 +    return 0
 +  }
 +  # It's an attempted macro invocation line. Process or fail and yap.
 +  set tailCap [regexp -inline $::macroTailREs($macro) $tail]
 +  # Call like-named proc with any args captured by the corresponding RE.
 +  return [$macro $inSrc [lrange $tailCap 1 end] $ostrm]
 +}
 +
 +array set ::typedefsSeen {}
 +array set ::includesDone {}
 +
 +# Filter redundant typedefs and certain includes and qualifiers, in place.
 +# Return 1 if line can be emitted as-is, 0 if to be processed further.
 +# In either case, the line named by $lineVar may have been changed.
 +proc transform_line {lineVar nesting} {
 +  upvar $lineVar line
    if {[regexp {^typedef .*;} $line]} {
 -    if {[info exists typedef_seen($line)]} {
 -      return "/* $line */"
 +    if {[info exists ::typedefsSeen($line)]} {
 +      set line "/* $line */"
 +      return 1
      }
-     set ::typedefsSeen($line) 1
+     if {[regexp {\s(\w+)\s*;} $line _ tdname]} {
 -      if {[info exists typedef_seen($tdname)]} {
 -        return "/* [regsub {;} $line {; **/}]"
++      if {[info exists ::typedefsSeen($tdname)]} {
++        set line "/* [regsub {;} $line {; **/}]"
++        return 1
+       }
 -      set typedef_seen($tdname) 1
++      set ::typedefsSeen($tdname) 1
+     } else {
 -      set typedef_seen($line) 1
 -    }
 -  }
 -  return $line
 -}
 -set iLine 0
 -while {1} {
 -  set lx [omit_redundant_typedefs [gets $in]]
 -  if {[eof $in]} break;
 -  incr iLine
 -  if {[regexp {^INCLUDE } $lx]} {
 -    set cfile [lindex $lx 1]
 -    puts $out "/************************* Begin $cfile ******************/"
 -#   puts $out "#line 1 \"$cfile\""
 -    set in2 [open $topdir/src/$cfile]
 -    fconfigure $in2 -translation binary
 -    while {![eof $in2]} {
 -      set lx [omit_redundant_typedefs [gets $in2]]
 -      if {[regexp {^#include "sqlite} $lx]} {
 -        set lx "/* $lx */"
++      set ::typedefsSeen($line) 1
++    }
 +    return 0
 +  } elseif {$nesting == 0} {
 +    return 0
 +  }
 +  if {[regexp {^#include "sqlite.*"} $line]
 +    || [regexp {^# *include "test_windirent.h"} $line]} {
 +    set line "/* $line */"
 +    return 1
 +  }
 +  if {$nesting > 0 && [regexp {^#include "([\w\.]+)"} $line _ incRelPath]} {
 +    set fromPath [lindex $::incFileStack end]
 +    set incPath [file join [file dirname $fromPath] $incRelPath]
 +    set inTree [file exists $incPath]
 +    if {$inTree} {
 +      if {[info exists ::includesDone($incPath)]} {
 +        set line "/* $line */"
 +        return 1
 +      } else {
 +        set line "INCLUDE $incRelPath"
 +        set ::includesDone($incPath) 1
 +        return 0
 +      }
 +    }
 +  }
 +  if {[string first "__declspec(dllexport)" $line] >= 0} {
 +    set line [string map [list __declspec(dllexport) {}] $line]
 +    return 1
 +  }
 +  return 0
 +}
 +
 +
 +set ::incFileStack {}
 +
 +# Read a named file and process its content to given output stream.
 +# Global ::incStack is maintained to support diagnostics.
 +# There is no (meaningful) return.
 +#
 +proc process_file { inFilepath ostrm } {
 +  set linesRead 0
 +  if { [catch {set istrm [open $inFilepath r]}] } {
 +    return -code error "Cannot read $inFilepath"
 +  } else {
 +    fconfigure $istrm -translation auto
 +    set nesting [llength $::incFileStack]
 +    lappend ::incFileStack $inFilepath
 +    set inFns [list $inFilepath $istrm]
 +    if {$nesting > 0} {
 +      set sayPath [string map [list \
 +                               "$::topDir/src/.." <projectDir> \
 +                               "$::topDir/src" <projectDir>/src \
 +                              ] $inFilepath]
 +      set splats [string repeat * [expr {33 - [string length $sayPath]/2 }]]
 +      set sayFile [list "/*$splats Begin $sayPath $splats*/"]
 +    } else { set sayFile {} }
 +    emit_sync $sayFile $ostrm $linesRead $inFilepath
 +    while {1} {
 +      set lin [gets $istrm]
 +      if {[eof $istrm]} break
 +      if {![transform_line lin $nesting]} {
 +        set ni [do_macro [concat $inFns $linesRead] $lin $ostrm]
 +        if {$ni > 0} {
 +          incr linesRead $ni
 +          continue
 +        }
        }
 -      if {[regexp {^# *include "test_windirent.h"} $lx]} {
 -        set lx "/* $lx */"
 +      emit_sync [list $lin] $ostrm $linesRead
 +      incr linesRead
 +    }
 +    if {$nesting > 0} {
 +      set sayFile [list "/**$splats End $sayPath $splats**/"]
 +      emit_sync $sayFile $ostrm $linesRead $inFilepath
 +    }
 +    set ::incFileStack [lrange $::incFileStack 0 end-1]
 +    close $istrm
 +  }
 +}
 +
 +if {$runMode == "help"} {
 +  # Show options and usage
 +  say_usage [lsort [array names ::macroUsages]] {
 + mkshellc.tcl <options>
 +  <options> may be either --help, --details, --parameters or any sequence of:
 +    <input_filename>
 +    -it <inc_type>=<include_filename>
 +    -tcl
 +    -no-line-directives
 + If no input files are specified, <PROJECT_ROOT>/src/shell.c.in is read.
 + Input files are read and processed in order, producing output to sdout.
 + The -it option associates a filename with an <inc_type> word which may
 + be encountered during execution of INCLUDE(...) directives in the input.
 + Input files may include macro lines or line sequences matching any of:
 +  INCUDE <file_name> }
 +  puts stderr {
 + Use --details option for detailed effects of these macros.
 + Use --parameters option for DISPATCH_CONFIG parameter names and effects.
 +  }
 +  exit 0
 +} elseif {$runMode == "details"} {
 +  set sfd [open $argv0 r]
 +  array set macdos [list]
 +  while {![eof $sfd]} {
 +    if {[regexp {^proc ([A-Z_]+\M)} [gets $sfd] ma macro]} {
 +      if {[info exists ::macroTailREs($macro)]} {
 +        set effects {}
 +        while {[regexp {^\s+#\s*(.+)$} [gets $sfd] ma effect]} {
 +          lappend effects " $effect"
 +        }
 +        set macdos($macro) [join $effects "\n"]
        }
 -      set lx [string map [list __declspec(dllexport) {}] $lx]
 -      puts $out $lx
      }
 -    close $in2
 -    puts $out "/************************* End $cfile ********************/"
 -#   puts $out "#line [expr $iLine+1] \"shell.c.in\""
 -    continue
    }
 -  puts $out $lx
 +  close $sfd
 +  foreach m [lsort [array names macdos]] {
 +    puts stderr "\nThe $m macro will:\n $macdos($m)"
 +  }
 +  exit 0
 +} elseif {$runMode == "parameters"} {
 +  puts stderr $::parametersHelp
 +  exit 0
 +}
 +
 +if {$runMode == "normal"} {
 +  fconfigure $outStrm -translation {auto lf}
 +  emit_sync [list $::headComment] $outStrm $::headCommentLines
 +  foreach {f} $::inFiles {
 +    process_file $f $outStrm
 +  }
 +  close $outStrm
  }
 -close $in
 -close $out
 +
 +exit $::iShuffleErrors