]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Sync with 3.38
authorlarrybr <larrybr@noemail.net>
Wed, 23 Feb 2022 10:10:59 +0000 (10:10 +0000)
committerlarrybr <larrybr@noemail.net>
Wed, 23 Feb 2022 10:10:59 +0000 (10:10 +0000)
FossilOrigin-Name: b9b27d74b93ada32ece46c9e53652f766de97f5a77fe4c34251996697374e659

1  2 
manifest
manifest.uuid
src/shell.c.in
test/shell1.test
test/shell2.test
test/shell5.test

diff --cc manifest
index 221fd3e5e97eb7ae70206e0f15dada1bdf9ed8fc,1ee93b5f40aa3c2a45a24582f028a4caf3cacb5a..f70c7f90be6883dfb656102b6fbc5986dd624c07
+++ b/manifest
@@@ -1,5 -1,5 +1,5 @@@
- C .ar\sfixed\sand\sconstituent\sline\snumbers\soff\s(until\sthey\sare\sright)
- D 2022-01-17T17:22:53.220
 -C For\sthe\s"PRAGMA\sdatabase_list"\sstatement,\sdo\snot\scheck\sto\ssee\sif\sthe\sschema\nis\sup-to-date\sand\sdo\snot\stry\sto\sreload\sthe\sschema.
 -D 2022-02-22T20:29:31.035
++C Sync\swith\s3.38
++D 2022-02-23T10:10:59.145
  F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
  F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
  F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@@ -546,20 -545,19 +547,20 @@@ F src/parse.y 0f02b27cdaa334441463153ff
  F src/pcache.c 084e638432c610f95aea72b8509f0845d2791293f39d1b82f0c0a7e089c3bb6b
  F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586
  F src/pcache1.c 54881292a9a5db202b2c0ac541c5e3ef9a5e8c4f1c1383adb2601d5499a60e65
- F src/pragma.c c536665ce8431c8b1efbf7e0a5c01852f49f7bf28f1954f8118b2d28e4a3797f
- F src/pragma.h 87330ed2fbfa2a1274de93ca0ab850fba336189228cb256089202c3b52766fad
- F src/prepare.c 45fe7408eb78d80eca8392669070a6e5caf231b09e5c7b1ff65c1ad64a3734c5
- F src/printf.c 975f1f5417f2526365b6e6d7f22332e3e11806dad844701d92846292b654ba9a
+ F src/pragma.c 7c024d690a3dc93f61830f11f900e4af2357f31d081b0c79099ca5e28919cba7
+ F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7
+ F src/prepare.c a187dade741c1f09ae118fcbbf0302511807bfc0355880927d7152eb75b8260d
+ F src/printf.c 05d8dfd2018bc4fc3ddb8b37eb97ccef7abf985643fa1caebdcf2916ca90fa32
  F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
- F src/resolve.c 359bc0e445d427583d2ab6110433a5dc777f64a0ecdf8d24826d8b475233ead9
+ F src/resolve.c ea935b87d6fb36c78b70cdc7b28561dc8f33f2ef37048389549c7b5ef9b0ba5e
  F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
- F src/select.c a4a23a70f0a24a1103ac9698f6be181a6ec7ff6c19e03e8899c43cb6d2af09d6
- F src/shell.c.in cdcb686aad93a51bd888b314f6c0fc00b20151d97a938b0771e666496e798e12
+ F src/select.c 3baa9dd8cf240654773c7974e2bcce398ac9dd24419c36684156963defe43b35
 -F src/shell.c.in 14cdfba32c73cb06169e50cd448632c28359f2bab2a0f803dc4a7f46dfc5b6fa
++F src/shell.c.in 1986c71edd5023c320d5c68a14a464cc375cf7b3d7b7fc730bd6368a89576882
 +F src/shext_linkage.h 5897e8140a06cb733d07a927994b30d41225eb93b5b8cf2ad0e3460cb4e95fd6
- F src/sqlite.h.in a5e0d6bd47e67aabf1475986d36bdcc7bfa9e06566790ebf8e3aa7fa551c9f99
+ F src/sqlite.h.in e30cedf008d9c51511f4027a3739b727a588da553424748b48d2393f85dbde41
  F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
- F src/sqlite3ext.h 01eb85e4f2759a5ee79c183f4b2877889d4ffdc49d27ae74529c9579e3c8c0ef
- F src/sqliteInt.h 21a31abf60222f50c1d654cdc27ad9d4040249f0341129dd8286b8b5b32bcd30
+ F src/sqlite3ext.h a95cb9ed106e3d39e2118e4dcc15a14faec3fa50d0093425083d340d9dfd96e6
+ F src/sqliteInt.h f8814239fb1f95056555e2d7fa475750e64681cac4221fb03610d1fde0b79d53
  F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657
  F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1
  F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
@@@ -1387,11 -1388,11 +1391,11 @@@ 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 4350026b456dc4b82641d459e712834fcb1104b0e44d8d62209b0f80d9439614
- F test/shell2.test 2e63015532de08ed9459657374a68e128a23235f90125f7a99ed383e9bd25886
- F test/shell3.test cb4b835a901742c9719437a89171172ecc4a8823ad97349af8e4e841e6f82566
- F test/shell4.test 3ed6c4b42fd695efcbc25d69ef759dbb15855ca8e52ba6c5ee076f8b435f48be
- F test/shell5.test 84edcdd1754b8ae279329ea2bb999f3cbb9f55b1339fc27995c1cdb2cf0c9918
 -F test/shell1.test b224e0793c5f48aa3749e65d8c64b93a30731bd206f2e41e6c5f1bee1bdb16c6
 -F test/shell2.test 89e4b2db062d52baed75022227b462d085cff495809de1699652779d8e0257d6
++F test/shell1.test 8ff9d7684346a39fdd73e201d30788b5d2207fd527d83ba793f8b1a85d6f34d1
++F test/shell2.test aed8773dd236517149a6abaeccf94f066e5a0c871108e8d21889122132dd51ba
+ F test/shell3.test a50628ab1d78d90889d9d3f32fb2c084ee15674771e96afe954aaa0accd1de3c
+ F test/shell4.test 8f6c0fce4abed19a8a7f7262517149812a04caa905d01bdc8f5e92573504b759
 -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
@@@ -1874,8 -1877,8 +1880,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/mkpragmatab.tcl bd07bd59d45d0f3448e123d6937e9811195f9908a51e09d774609883055bfd3d
 -F tool/mkshellc.tcl df5d249617f9cc94d5c48eb0401673eb3f31f383ecbc54e8a13ca3dd97e89450
 +F tool/mkshellc.tcl 160d16067f71caeb54cbebd09c12c3e545c77f46d9714cc8e7219407b3d3001b
  F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9
  F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
  F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f
@@@ -1941,8 -1944,8 +1947,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 e09a6dd7b9de6c53e1dd59967c3455b38551024bb8d5d1606e7e7324b3b65931
- R 1dd8fb7871fba4bad4bc518faf171ce6
 -P 40fa792d359f84c3b9e9d6623743e1a59826274e221df1bde8f47086968a1bab
 -R 8e774d76a97f244938c2cd31e6e650dd
 -U drh
 -Z 87215546f30ba5e5b58306e47d0efae5
++P ec363f81ce775bb3313d08a959e1573f694f18eb4699a33a9b98ce241056292e 710de6a2137c872812effa43ea888aa77d339362ca0b6c8990d4426c8fb20689
++R 3e137f57c07bfa1d2f3bcf91d0b0c096
 +U larrybr
- Z dff757fa5b4664ef427a6a6f0d132a50
++Z adaf44bd0c510479196b838bd6c2a755
  # Remove this line to create a well-formed Fossil manifest.
diff --cc manifest.uuid
index 8ee50daf9b064870f6f283a655b37163d1307095,30ca50acbdf7377e18d65521ed608f819dde8efe..e8894783483284ea738680c5758a9fd9cd1922db
@@@ -1,1 -1,1 +1,1 @@@
- ec363f81ce775bb3313d08a959e1573f694f18eb4699a33a9b98ce241056292e
 -710de6a2137c872812effa43ea888aa77d339362ca0b6c8990d4426c8fb20689
++b9b27d74b93ada32ece46c9e53652f766de97f5a77fe4c34251996697374e659
diff --cc src/shell.c.in
index e14346054e6cd7edb3dd676db09d7a2a40cb71ae,354c9a849bb324cde8b6fcf1ef0ba41e3cb08059..0f6b684706b03584a9c96c51a5cf71d46b6b7549
@@@ -1135,10 -1091,12 +1144,12 @@@ struct ShellState 
    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. */
+   ColModeOpts cmOpts;    /* Option values affecting columnar mode output */
    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 */
    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
  */
@@@ -2722,11 -2648,18 +2744,19 @@@ static char *save_err_msg
  ){
    char *zErr;
    char *zContext;
-   if( zWhen==0 ) zWhen = "%s (%d)%s";
+   sqlite3_str *pStr = sqlite3_str_new(0);
+   sqlite3_str_appendf(pStr, "%s, %s", zPhase, sqlite3_errmsg(db));
+   if( rc>1 ){
+     sqlite3_str_appendf(pStr, " (%d)", rc);
+   }
    zContext = shell_error_context(zSql, db);
-   zErr = sqlite3_mprintf(zWhen, sqlite3_errmsg(db), rc, zContext);
+   if( zContext ){
+     sqlite3_str_appendall(pStr, zContext);
+     sqlite3_free(zContext);
+   }
+   zErr = sqlite3_str_finish(pStr);
    shell_check_oom(zErr);
-   sqlite3_free(zContext);
++
    return zErr;
  }
  
@@@ -3253,7 -3186,134 +3283,134 @@@ static void print_box_row_separator
    fputs("\n", p->out);
  }
  
+ /*
+ ** z[] is a line of text that is to be displayed the .mode box or table or
+ ** similar tabular formats.  z[] might contain control characters such
+ ** as \n, \t, \f, or \r.
+ **
+ ** Compute characters to display on the first line of z[].  Stop at the
+ ** first \r, \n, or \f.  Expand \t into spaces.  Return a copy (obtained
+ ** from malloc()) of that first line, which caller should free sometime.
+ ** Write anything to display on the next line into *pzTail.  If this is
+ ** the last line, write a NULL into *pzTail. (*pzTail is not allocated.)
+ */
+ static char *translateForDisplayAndDup(
+   const unsigned char *z,            /* Input text to be transformed */
+   const unsigned char **pzTail,      /* OUT: Tail of the input for next line */
+   int mxWidth,                       /* Max width.  0 means no limit */
+   u8 bWordWrap                       /* If true, avoid breaking mid-word */
+ ){
+   int i;                 /* Input bytes consumed */
+   int j;                 /* Output bytes generated */
+   int k;                 /* Input bytes to be displayed */
+   int n;                 /* Output column number */
+   unsigned char *zOut;   /* Output text */
  
 -  return (char*)zOut;  
+   if( z==0 ){
+     *pzTail = 0;
+     return 0;
+   }
+   if( mxWidth<0 ) mxWidth = -mxWidth;
+   if( mxWidth==0 ) mxWidth = 1000000;
+   i = j = n = 0;
+   while( n<mxWidth ){
+     if( z[i]>=' ' ){
+       n++;
+       do{ i++; j++; }while( (z[i]&0xc0)==0x80 );
+       continue;
+     }
+     if( z[i]=='\t' ){
+       do{
+         n++;
+         j++;
+       }while( (n&7)!=0 && n<mxWidth );
+       i++;
+       continue;
+     }
+     break;
+   }
+   if( n>=mxWidth && bWordWrap  ){
+     /* Perhaps try to back up to a better place to break the line */
+     for(k=i; k>i/2; k--){
+       if( isspace(z[k-1]) ) break;
+     }
+     if( k<=i/2 ){
+       for(k=i; k>i/2; k--){
+         if( isalnum(z[k-1])!=isalnum(z[k]) && (z[k]&0xc0)!=0x80 ) break;
+       }
+     }
+     if( k<=i/2 ){
+       k = i;
+     }else{
+       i = k;
+       while( z[i]==' ' ) i++;
+     }
+   }else{
+     k = i;
+   }
+   if( n>=mxWidth && z[i]>=' ' ){
+    *pzTail = &z[i];
+   }else if( z[i]=='\r' && z[i+1]=='\n' ){
+     *pzTail = z[i+2] ? &z[i+2] : 0;
+   }else if( z[i]==0 || z[i+1]==0 ){
+     *pzTail = 0;
+   }else{
+     *pzTail = &z[i+1];
+   }
+   zOut = malloc( j+1 );
+   shell_check_oom(zOut);
+   i = j = n = 0;
+   while( i<k ){
+     if( z[i]>=' ' ){
+       n++;
+       do{ zOut[j++] = z[i++]; }while( (z[i]&0xc0)==0x80 );
+       continue;
+     }
+     if( z[i]=='\t' ){
+       do{
+         n++;
+         zOut[j++] = ' ';
+       }while( (n&7)!=0 && n<mxWidth );
+       i++;
+       continue;
+     }
+     break;
+   }
+   zOut[j] = 0;
++  return (char*)zOut;
+ }
+ /* Extract the value of the i-th current column for pStmt as an SQL literal
+ ** value.  Memory is obtained from sqlite3_malloc64() and must be freed by
+ ** the caller.
+ */
+ static char *quoted_column(sqlite3_stmt *pStmt, int i){
+   switch( sqlite3_column_type(pStmt, i) ){
+     case SQLITE_NULL: {
+       return sqlite3_mprintf("NULL");
+     }
+     case SQLITE_INTEGER:
+     case SQLITE_FLOAT: {
+       return sqlite3_mprintf("%s",sqlite3_column_text(pStmt,i));
+     }
+     case SQLITE_TEXT: {
+       return sqlite3_mprintf("%Q",sqlite3_column_text(pStmt,i));
+     }
+     case SQLITE_BLOB: {
+       int j;
+       sqlite3_str *pStr = sqlite3_str_new(0);
+       const unsigned char *a = sqlite3_column_blob(pStmt,i);
+       int n = sqlite3_column_bytes(pStmt,i);
+       sqlite3_str_append(pStr, "x'", 2);
+       for(j=0; j<n; j++){
+         sqlite3_str_appendf(pStr, "%02x", a[j]);
+       }
+       sqlite3_str_append(pStr, "'", 1);
+       return sqlite3_str_finish(pStr);
+     }
+   }
+   return 0; /* Not reached */
+ }
  
  /*
  ** Run a prepared statement and output the result in one of the
@@@ -7137,1874 -7505,2257 +7356,2159 @@@ 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 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"
 -  );
 -
 -  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);
 +  if( zDb==0 ) zDb = "main";
 +  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;
    }
 -
 -  /* 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);
 +  if( bAsync ){
 +    sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
 +                 0, 0, 0);
    }
 -  shellFinalize(&rc, pLoop);
 -  pLoop = 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;
 +}
  
 -  shellPrepare(pState->db, &rc,
 -      "SELECT pgno FROM recovery.map WHERE root=?", &pPages
 -  );
++/*
++ * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it.
++ * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE,
++ *   close db and set it to 0, and return the columns spec, to later
++ *   be sqlite3_free()'ed by the caller.
++ * The return is 0 when either:
++ *   (a) The db was not initialized and zCol==0 (There are no columns.)
++ *   (b) zCol!=0  (Column was added, db initialized as needed.)
++ * The 3rd argument, pRenamed, references an out parameter. If the
++ * pointer is non-zero, its referent will be set to a summary of renames
++ * done if renaming was necessary, or set to 0 if none was done. The out
++ * string (if any) must be sqlite3_free()'ed by the caller.
++ */
++#ifdef SHELL_DEBUG
++#define rc_err_oom_die(rc) \
++  if( rc==SQLITE_NOMEM ) shell_check_oom(0); \
++  else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \
++    fprintf(stderr,"E:%d\n",rc), assert(0)
++#else
++static void rc_err_oom_die(int rc){
++  if( rc==SQLITE_NOMEM ) shell_check_oom(0);
++  assert(rc==SQLITE_OK||rc==SQLITE_DONE);
++}
++#endif
 -  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) */
 -
 -
 -/* 
 - * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it.
 - * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE,
 - *   close db and set it to 0, and return the columns spec, to later
 - *   be sqlite3_free()'ed by the caller.
 - * The return is 0 when either:
 - *   (a) The db was not initialized and zCol==0 (There are no columns.)
 - *   (b) zCol!=0  (Column was added, db initialized as needed.)
 - * The 3rd argument, pRenamed, references an out parameter. If the
 - * pointer is non-zero, its referent will be set to a summary of renames
 - * done if renaming was necessary, or set to 0 if none was done. The out
 - * string (if any) must be sqlite3_free()'ed by the caller.
 - */
 -#ifdef SHELL_DEBUG
 -#define rc_err_oom_die(rc) \
 -  if( rc==SQLITE_NOMEM ) shell_check_oom(0); \
 -  else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \
 -    fprintf(stderr,"E:%d\n",rc), assert(0)
 -#else
 -static void rc_err_oom_die(int rc){
 -  if( rc==SQLITE_NOMEM ) shell_check_oom(0);
 -  assert(rc==SQLITE_OK||rc==SQLITE_DONE);
 -}
 -#endif
 -
 -#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
++#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
+ /* 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 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);
+     }
+     assert(*pDb!=0);
+     rc = sqlite3_prepare_v2(*pDb, zTabFill, -1, &pStmt, 0);
+     rc_err_oom_die(rc);
+     rc = sqlite3_bind_text(pStmt, 1, zColNew, -1, 0);
+     rc_err_oom_die(rc);
+     rc = sqlite3_step(pStmt);
+     rc_err_oom_die(rc);
+     sqlite3_finalize(pStmt);
+     return 0;
+   }else if( *pDb==0 ){
+     return 0;
+   }else{
+     /* Formulate the columns spec, close the DB, zero *pDb. */
+     char *zColsSpec = 0;
+     int hasDupes = db_int(*pDb, zHasDupes);
+ #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);
+ #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);
+     }
+     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;
+       }
+     }
+     sqlite3_finalize(pStmt);
+     sqlite3_close(*pDb);
+     *pDb = 0;
+     return zColsSpec;
+   }
+ }
 -/*
 -** If an input line begins with "." then invoke this routine to
 -** process that line.
 -**
 -** Return 1 on error, 2 to exit, and 0 otherwise.
 -*/
 -static int do_meta_command(char *zLine, ShellState *p){
 -  int h = 1;
 -  int nArg = 0;
 -  int n, c;
 +#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));
 +/*****************
 + * The .seeargs command
 + */
 +COLLECT_HELP_TEXT[
 +  ".seeargs                 Echo arguments separated by |",
 +  "    A near-dummy command for use as a template (to vanish soon)",
 +];
 +DISPATCHABLE_COMMAND( seeargs ? 0 0 azArg nArg p ){
    int rc = 0;
 -  char *azArg[52];
 +  for (rc=1; rc<nArg; ++rc)
 +    raw_printf(p->out, "%s%s", azArg[rc], (rc==nArg-1)? "\n" : "|");
 +  return rc;
 +}
  
 -#ifndef SQLITE_OMIT_VIRTUALTABLE
 -  if( p->expert.pExpert ){
 -    expertFinish(p, 1, 0);
 -  }
 -#endif
 +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);
 +}
  
 -  /* Parse the input line into tokens.
 -  */
 -  while( zLine[h] && nArg<ArraySize(azArg)-1 ){
 -    while( IsSpace(zLine[h]) ){ h++; }
 -    if( zLine[h]==0 ) break;
 -    if( zLine[h]=='\'' || zLine[h]=='"' ){
 -      int delim = zLine[h++];
 -      azArg[nArg++] = &zLine[h];
 -      while( zLine[h] && zLine[h]!=delim ){
 -        if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++;
 -        h++;
 -      }
 -      if( zLine[h]==delim ){
 -        zLine[h++] = 0;
 -      }
 -      if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
 -    }else{
 -      azArg[nArg++] = &zLine[h];
 -      while( zLine[h] && !IsSpace(zLine[h]) ){ h++; }
 -      if( zLine[h] ) zLine[h++] = 0;
 -      resolve_backslashes(azArg[nArg-1]);
 -    }
 +/*****************
 + * The .auth command
 + */
 +CONDITION_COMMAND(auth !defined(SQLITE_OMIT_AUTHORIZATION));
 +COLLECT_HELP_TEXT[
 +  ".auth ON|OFF             Show authorizer callbacks",
 +];
 +DISPATCHABLE_COMMAND( auth 3 2 2 azArg nArg p ){
 +  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);
    }
 -  azArg[nArg] = 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);
 +/*****************
 + * 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;
 +}
  
 -#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
 +/*****************
 + * 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;
 +}
  
 -#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
 +DISPATCHABLE_COMMAND( cd ? 2 2 ){
 +  int rc=0;
 +  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
 +#if defined(_WIN32) || defined(WIN32)
 +  wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
 +  rc = !SetCurrentDirectoryW(z);
 +  sqlite3_free(z);
 +#else
 +  rc = chdir(azArg[1]);
  #endif
 +  if( rc ){
 +    utf8_printf(STD_ERR, "Cannot change to directory \"%s\"\n", azArg[1]);
 +    rc = 1;
 +  }
 +  return 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
 -
 -  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
 -
 -  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
 -
 -  /* 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
 -
 -  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;
 -      }
 -    }else{
 -      raw_printf(stderr, "Usage: .cd DIRECTORY\n");
 -      rc = 1;
 -    }
 -  }else
 -
 -  if( c=='c' && n>=3 && strncmp(azArg[0], "changes", n)==0 ){
 -    if( nArg==2 ){
 -      setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
 -    }else{
 -      raw_printf(stderr, "Usage: .changes on|off\n");
 -      rc = 1;
 -    }
 -  }else
 +/* 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;
 +}
  
 +/*****************
 + * The .changes, .check, .clone and .connection commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".changes on|off          Show number of rows changed by SQL",
 +  ".check GLOB              Fail if output since .testcase does not match",
 +  ".clone NEWDB             Clone data into NEWDB from the existing database",
 +  ".connection [close] [#]  Open or close an auxiliary database connection",
 +];
 +DISPATCHABLE_COMMAND( changes 3 2 2 ){
 +  setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
 +  return 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.
    */
 -  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
 -
 -  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
 -
 -  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;
 -      }
 -    }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;
 +  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;
 -    open_db(p, 0);
 -    rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
 -    if( rc ){
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 -      rc = 1;
 -    }else{
 -      while( sqlite3_step(pStmt)==SQLITE_ROW ){
 -        const char *zSchema = (const char *)sqlite3_column_text(pStmt,1);
 -        const char *zFile = (const char*)sqlite3_column_text(pStmt,2);
 -        if( zSchema==0 || zFile==0 ) continue;
 -        azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*));
 -        shell_check_oom(azName);
 -        azName[nName*2] = strdup(zSchema);
 -        azName[nName*2+1] = strdup(zFile);
 -        nName++;
 -      }
 +    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;
      }
 -    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
 +  }else{
 +    return SHELL_INVALID_ARGS;
 +  }
 +  return 0;
 +}
  
 -  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;
 +/*****************
 + * 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++;
      }
 -    if( nArg>1 && ii==ArraySize(aDbConfig) ){
 -      utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]);
 -      utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n");
 -    }   
 -  }else
 -
 -  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) */
 +  }
 +  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);
 +}
  
 -  if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){
 -    char *zLike = 0;
 -    char *zSql;
 -    int i;
 -    int savedShowHeader = p->showHeader;
 -    int savedShellFlags = p->shellFlgs;
 -    ShellClearFlag(p, 
 -       SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo
 -       |SHFLG_DumpDataOnly|SHFLG_DumpNoSys);
 -    for(i=1; i<nArg; i++){
 -      if( azArg[i][0]=='-' ){
 -        const char *z = azArg[i]+1;
 -        if( z[0]=='-' ) z++;
 -        if( strcmp(z,"preserve-rowids")==0 ){
 +/*****************
 + * 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",
 +  "   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 *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;
 +        *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);
 +        ShellSetFlag(p, SHFLG_PreserveRowid);
  #endif
 -        }else
 +      }else{
          if( strcmp(z,"newlines")==0 ){
            ShellSetFlag(p, SHFLG_Newlines);
 -        }else
 -        if( strcmp(z,"data-only")==0 ){
 +        }else if( strcmp(z,"data-only")==0 ){
            ShellSetFlag(p, SHFLG_DumpDataOnly);
 -        }else
 -        if( strcmp(z,"nosys")==0 ){
 +        }else if( strcmp(z,"nosys")==0 ){
            ShellSetFlag(p, SHFLG_DumpNoSys);
 -        }else
 -        {
 -          raw_printf(stderr, "Unknown option \"%s\" on \".dump\"\n", azArg[i]);
 -          rc = 1;
 +        }else{
 +          *pzErr = sqlite3_mprintf
 +            ("Unknown option \"%s\" on \".dump\"\n", azArg[i]);
            sqlite3_free(zLike);
 -          goto meta_command_exit;
 +          return 1;
          }
 -      }else{
 -        /* azArg[i] contains a LIKE pattern. This ".dump" request should
 -        ** only dump data for tables for which either the table name matches
 -        ** the LIKE pattern, or the table appears to be a shadow table of
 -        ** a virtual table for which the name matches the LIKE pattern.
 -        */
 -        char *zExpr = sqlite3_mprintf(
 -            "name LIKE %Q ESCAPE '\\' OR EXISTS ("
 -            "  SELECT 1 FROM sqlite_schema WHERE "
 -            "    name LIKE %Q ESCAPE '\\' AND"
 -            "    sql LIKE 'CREATE VIRTUAL TABLE%%' AND"
 -            "    substr(o.name, 1, length(name)+1) == (name||'_')"
 -            ")", azArg[i], azArg[i]
 -        );
 +      }
 +    }else{
 +      /* azArg[i] contains a LIKE pattern. This ".dump" request should
 +      ** only dump data for tables for which either the table name matches
 +      ** the LIKE pattern, or the table appears to be a shadow table of
 +      ** a virtual table for which the name matches the LIKE pattern.
 +      */
 +      char *zExpr = sqlite3_mprintf(
 +                    "name LIKE %Q ESCAPE '\\' OR EXISTS ("
 +                    "  SELECT 1 FROM sqlite_schema WHERE "
 +                    "    name LIKE %Q ESCAPE '\\' AND"
 +                    "    sql LIKE 'CREATE VIRTUAL TABLE%%' AND"
 +                    "    substr(o.name, 1, length(name)+1) == (name||'_')"
 +                    ")", azArg[i], azArg[i]
 +                    );
        
 -        if( zLike ){
 -          zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr);
 -        }else{
 -          zLike = zExpr;
 -        }
 +      if( zLike ){
 +        zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr);
 +      }else{
 +        zLike = zExpr;
        }
      }
 +  }
  
 -    open_db(p, 0);
 +  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");
 +  if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 +    /* When playing back a "dump", the content might appear in an order
 +    ** which causes immediate foreign key constraints to be violated.
 +    ** So disable foreign-key constraint enforcement to prevent problems. */
 +    raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n");
 +    raw_printf(p->out, "BEGIN TRANSACTION;\n");
 +  }
 +  p->writableSchema = 0;
 +  p->showHeader = 0;
 +  /* Set writable_schema=ON since doing so forces SQLite to initialize
 +  ** as much of the schema as it can even if the sqlite_schema table is
 +  ** corrupt. */
 +  sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
 +  p->nErr = 0;
 +  if( zLike==0 ) zLike = sqlite3_mprintf("true");
 +  zSql = sqlite3_mprintf(
 +           "SELECT name, type, sql FROM sqlite_schema AS o "
 +           "WHERE (%s) AND type=='table'"
 +           "  AND sql NOT NULL"
 +           " ORDER BY tbl_name='sqlite_sequence', rowid",
 +           zLike
 +         );
 +  run_schema_dump_query(p,zSql);
 +  sqlite3_free(zSql);
 +  if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
      zSql = sqlite3_mprintf(
 -      "SELECT name, type, sql FROM sqlite_schema AS o "
 -      "WHERE (%s) AND type=='table'"
 -      "  AND sql NOT NULL"
 -      " ORDER BY tbl_name='sqlite_sequence', rowid",
 -      zLike
 -    );
 -    run_schema_dump_query(p,zSql);
 +             "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);
 -    if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 -      zSql = sqlite3_mprintf(
 -        "SELECT sql FROM sqlite_schema AS o "
 -        "WHERE (%s) AND sql NOT NULL"
 -        "  AND type IN ('index','trigger','view')",
 -        zLike
 -      );
 -      run_table_dump_query(p, zSql);
 -      sqlite3_free(zSql);
 -    }
 -    sqlite3_free(zLike);
 -    if( p->writableSchema ){
 -      raw_printf(p->out, "PRAGMA writable_schema=OFF;\n");
 -      p->writableSchema = 0;
 -    }
 -    sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0);
 -    sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0);
 -    if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 -      raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n");
 -    }
 -    p->showHeader = savedShowHeader;
 -    p->shellFlgs = savedShellFlags;
 -  }else
 +  }
 +  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;
  
 -  if( c=='e' && strncmp(azArg[0], "echo", n)==0 ){
 -    if( nArg==2 ){
 -      setOrClearFlag(p, SHFLG_Echo, azArg[1]);
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( echo ? 2 2 ){
 +  setOrClearFlag(p, SHFLG_Echo, azArg[1]);
 +  return 0;
 +}
 +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{
 -      raw_printf(stderr, "Usage: .echo on|off\n");
 -      rc = 1;
 +      p->autoEQP = (u8)booleanValue(azArg[1]);
      }
 -  }else
 +  }else{
 +    return SHELL_INVALID_ARGS;
 +  }
 +}
  
 -  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]);
 -      }
 +/*****************
 + * 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{
 -      raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n");
 -      rc = 1;
 +      val = booleanValue(azArg[1]);
      }
 -  }else
 +  }
 +  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;
 +}
  
 -  if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){
 -    if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc);
 -    rc = 2;
 -  }else
 +/*****************
 + * The .excel, .once and .output commands
 + * These share much implementation, so they stick together.
 + */
 +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);
 +}
  
 -  /* 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;
 +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{
 -        val =  booleanValue(azArg[1]);
 +        *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;
        }
 -    }
 -    if( val==1 && p->mode!=MODE_Explain ){
 -      p->normalMode = p->mode;
 -      p->mode = MODE_Explain;
 -      p->autoExplain = 0;
 -    }else if( val==0 ){
 -      if( p->mode==MODE_Explain ) p->mode = p->normalMode;
 -      p->autoExplain = 0;
 -    }else if( val==99 ){
 -      if( p->mode==MODE_Explain ) p->mode = p->normalMode;
 -      p->autoExplain = 1;
 -    }
 -  }else
 -
 -#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);
 +      *pzErr = shellMPrintf(0,"       excess argument: \"%s\"\n", azArg[i]);
 +      sqlite3_free(zFile);
 +      return SHELL_INVALID_ARGS;
      }
 -  }else
 -#endif
 -
 -  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];
 +  }
 +  if( zFile==0 ){
 +    zFile = sqlite3_mprintf("stdout");
 +    shell_check_oom(zFile);
 +  }
 +  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;
      }
 -
 -    /* The argument can optionally begin with "-" or "--" */
 -    if( zCmd[0]=='-' && zCmd[1] ){
 -      zCmd++;
 -      if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 +    sqlite3_free(zFile);
 +    zFile = sqlite3_mprintf("%s", p->zTempFile);
 +  }
 +#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);
      }
 -
 -    /* --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);
 +#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;
 -      goto meta_command_exit;
 +    } else {
 +      if( bBOM ) fprintf(p->out,"\357\273\277");
 +      sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
      }
 +  }
 +  sqlite3_free(zFile);
 +  return rc;
 +}
 +
 +
 +/*****************
 + * The .filectrl and fullschema commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".filectrl CMD ...        Run various sqlite3_file_control() operations",
 +  "   --schema SCHEMA         Use SCHEMA instead of \"main\"",
 +  "   --help                  Show CMD details",
 +  ".fullschema ?--indent?   Show schema and the content of sqlite_stat tables",
 +];
 +DISPATCHABLE_COMMAND( filectrl ? 2 0 ){
 +  static const struct {
 +    const char *zCtrlName;   /* Name of a test-control option */
 +    int ctrlCode;            /* Integer code for that option */
 +    const char *zUsage;      /* Usage notes */
 +  } aCtrl[] = {
 +    { "chunk_size",     SQLITE_FCNTL_CHUNK_SIZE,      "SIZE"           },
 +    { "data_version",   SQLITE_FCNTL_DATA_VERSION,    ""               },
 +    { "has_moved",      SQLITE_FCNTL_HAS_MOVED,       ""               },  
 +    { "lock_timeout",   SQLITE_FCNTL_LOCK_TIMEOUT,    "MILLISEC"       },
 +    { "persist_wal",    SQLITE_FCNTL_PERSIST_WAL,     "[BOOLEAN]"      },
 + /* { "pragma",         SQLITE_FCNTL_PRAGMA,          "NAME ARG"       },*/
 +    { "psow",       SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]"      },
 +    { "reserve_bytes",  SQLITE_FCNTL_RESERVE_BYTES,   "[N]"            },
 +    { "size_limit",     SQLITE_FCNTL_SIZE_LIMIT,      "[LIMIT]"        },
 +    { "tempfilename",   SQLITE_FCNTL_TEMPFILENAME,    ""               },
 + /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY,  "COUNT DELAY"    },*/
 +  };
 +  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];
 +  }
 +
 +  /* The argument can optionally begin with "-" or "--" */
 +  if( zCmd[0]=='-' && zCmd[1] ){
 +    zCmd++;
 +    if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 +  }
  
 -    /* convert filectrl text option to value. allow any unique prefix
 -    ** of the option name, or a numerical value. */
 -    n2 = strlen30(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++){
 -      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;
 -        }
 -      }
 +      utf8_printf(p->out, "  .filectrl %s %s\n",
 +                  aCtrl[i].zCtrlName, aCtrl[i].zUsage);
      }
 -    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;
 -        }
 +    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( 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);
 +  }
 +  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;
      }
 -  }else
 -
 -  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;
 +    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;
      }
 -    if( nArg!=1 ){
 -      raw_printf(stderr, "Usage: .fullschema ?--indent?\n");
 -      rc = 1;
 -      goto meta_command_exit;
 +    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;
      }
 -    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);
 +    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;
      }
 -    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");
 +    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;
      }
 -  }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;
 +    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;
      }
 -  }else
 +   }
 +  }
 +  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;
 +}
  
 -  if( c=='h' && strncmp(azArg[0], "help", n)==0 ){
 -    if( nArg>=2 ){
 -      n = showHelp(p->out, azArg[1]);
 -      if( n==0 ){
 -        utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]);
 -      }
 +DISPATCHABLE_COMMAND( fullschema ? 1 2 ){
 +  int rc;
 +  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 rc > 0;
 +}
 +
 +/*****************
 + * 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;
 +}
 +
 +/*****************
 + * The .help command
 + */
 +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",
 +];
 +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{
 -      showHelp(p->out, 0);
 +      zPat = z;
      }
 -  }else
 +  }
 +  if( showHelp(p->out, zPat)==0 ){
 +    utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]);
 +  }
 +  /* Help pleas never fail! */
 +  return 0;
 +}
  
 -  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 ){
 +/*****************
 + * 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( 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;
 -    }else{
 +      useOutputMode = 0;
 +    }else if( strcmp(z,"-csv")==0 ){
 +      sCtx.cColSep = ',';
 +      sCtx.cRowSep = '\n';
        xRead = csv_read_one_field;
 +      useOutputMode = 0;
 +    }else{
 +      *pzErr = shellMPrintf(0,"       unknown option: \"%s\"", z);
 +      return SHELL_INVALID_ARGS;
      }
 -    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;
 +  }
 +  if( zTable==0 ){
 +    *pzErr = shellMPrintf(0,"       missing %s argument.\n",
 +                zFile==0 ? "FILE" : "TABLE");
 +    return SHELL_INVALID_ARGS;
 +  }
 +  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;
      }
 -    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;
 -      }
 +    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==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]=='|' ){
 +    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
 -      raw_printf(stderr, "Error: pipes are not supported in this OS\n");
 -      rc = 1;
 -      goto meta_command_exit;
 +    *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;
 +    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 ){
 +  }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);
 +  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);
-     char cSep = '(';
++    sqlite3 *dbCols = 0;
++    char *zRenames = 0;
++    char *zColDefs;
 +    while( xRead(&sCtx) ){
-       zCreate = sqlite3_mprintf("%z%c\n  \"%w\" TEXT", zCreate, cSep, sCtx.z);
-       cSep = ',';
++      zAutoColumn(sCtx.z, &dbCols, 0);
++
 +      if( sCtx.cTerm!=sCtx.cColSep ) break;
 +    }
-     if( cSep=='(' ){
++    zColDefs = zAutoColumn(0, &dbCols, &zRenames);
++    if( zRenames!=0 ){
++      utf8_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr,
++                  "Columns renamed during .import %s due to duplicates:\n"
++                  "%s\n", sCtx.zFile, zRenames);
++      sqlite3_free(zRenames);
++    }
++    assert(dbCols==0);
++    if( zColDefs==0 ){
 +      sqlite3_free(zCreate);
        import_cleanup(&sCtx);
 -      shell_out_of_memory();
 +      *pzErr = shellMPrintf(0,"%s: empty file\n", sCtx.zFile);
 +      return 1;
      }
-     zCreate = sqlite3_mprintf("%z\n)", zCreate);
 -    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((stdin_is_interactive && p->in==stdin)? p->out : stderr,
 -                    "Columns renamed during .import %s due to duplicates:\n"
 -                    "%s\n", sCtx.zFile, zRenames);
 -        sqlite3_free(zRenames);
 -      }
 -      assert(dbCols==0);
 -      if( zColDefs==0 ){
 -        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);
++    zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs);
 +    if( eVerbose>=1 ){
 +      utf8_printf(p->out, "%s\n", zCreate);
      }
 -    sqlite3_free(zSql);
 +    rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
      if( rc ){
 -      if (pStmt) sqlite3_finalize(pStmt);
 -      utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db));
 +      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;
 +      return 1;
      }
 -    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_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( 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++; }
 +      }
      }
 -    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++] = '?';
 +    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);
      }
 -    zSql[j++] = ')';
 -    zSql[j] = 0;
 -    if( eVerbose>=2 ){
 -      utf8_printf(p->out, "Insert using: %s\n", zSql);
 +    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++;
 +      }
      }
 -    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 );
 +  }while( sCtx.cTerm!=EOF );
  
 -    import_cleanup(&sCtx);
 -    sqlite3_finalize(pStmt);
 -    if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0);
 -    if( eVerbose>0 ){
 -      utf8_printf(p->out,
 -          "Added %d rows with %d errors using %d lines of input\n",
 -          sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
 -    }
 -  }else
 +  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;
 +}
  
 -#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;
 +/*****************
 + * 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;
 +          }
 +          memcpy(kwBuf, zKW, szKW);
 +          kwBuf[szKW] = 0;
 +          utf8_printf(p->out, "%s%s", kwBuf, zSep);
          }
        }
 -      if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){
 -        lenPK = (int)strlen(zCollist);
 -      }
 -      if( zCollist==0 ){
 -        zCollist = sqlite3_mprintf("\"%w\"", zCol);
 +    }
 +    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{
 -        zCollist = sqlite3_mprintf("%z,\"%w\"", zCollist, zCol);
 +        sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i);
 +        zCol = zLabel;
        }
      }
 -    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( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){
 +      lenPK = (int)strlen(zCollist);
      }
 -    if( lenPK==0 ) lenPK = 100000;
 -    zSql = sqlite3_mprintf(
 -          "CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))WITHOUT ROWID",
 -          azArg[2], zCollist, lenPK, 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);
 -    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"
 -        );
 -      }
 +    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{
 -      raw_printf(stderr, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
 -      rc = 1;
 +      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"
 +                 );
      }
 -    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 ){
 +  }else{
 +    *pzErr = shellMPrintf(0,"SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
 +  }
 +  sqlite3_free(zSql);
 +  return rc != 0;
 +}
 +DISPATCHABLE_COMMAND( iotrace ? 2 2 ){
 +  SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...);
 +  if( iotrace && iotrace!=STD_OUT ) fclose(iotrace);
 +  iotrace = 0;
 +  if( nArg<2 ){
 +    sqlite3IoTrace = 0;
 +  }else if( 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;
 -    }else if( strcmp(azArg[1], "-")==0 ){
 -      sqlite3IoTrace = iotracePrintf;
 -      iotrace = stdout;
 +      return 1;
      }else{
 -      iotrace = fopen(azArg[1], "w");
 -      if( iotrace==0 ){
 -        utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]);
 -        sqlite3IoTrace = 0;
 -        rc = 1;
 -      }else{
 -        sqlite3IoTrace = iotracePrintf;
 -      }
 +      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));
 -      }
 -    }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;
 -          }
 +  }
 +  return 0;
 +}
 +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));
 +    }
 +  }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 ){
 -        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( nArg==3 ){
 -        sqlite3_limit(p->db, aLimit[iLimit].limitCode,
 -                      (int)integerValue(azArg[2]));
 -      }
 -      printf("%20s %d\n", aLimit[iLimit].zLimitName,
 -             sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1));
      }
 -  }else
 -
 -  if( c=='l' && n>2 && strncmp(azArg[0], "lint", n)==0 ){
 -    open_db(p, 0);
 -    lintDotCommand(p, azArg, nArg);
 -  }else
 -
 -#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;
 +    if( iLimit<0 ){
 +      *pzErr = sqlite3_mprintf
 +        ("unknown limit: \"%s\"\n"
 +         "enter \".limits\" with no arguments for a list.\n",
 +         azArg[1]);
 +      return 1;
      }
 -    zFile = azArg[1];
 -    zProc = nArg>=3 ? azArg[2] : 0;
 -    open_db(p, 0);
 -    rc = sqlite3_load_extension(p->db, zFile, zProc, &zErrMsg);
 -    if( rc!=SQLITE_OK ){
 -      utf8_printf(stderr, "Error: %s\n", zErrMsg);
 -      sqlite3_free(zErrMsg);
 -      rc = 1;
 +    if( nArg==3 ){
 +      sqlite3_limit(p->db, aLimit[iLimit].limitCode,
 +                    (int)integerValue(azArg[2]));
      }
 -  }else
 -#endif
 +    fprintf(STD_OUT, "%20s %d\n", aLimit[iLimit].zLimitName,
 +           sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1));
 +  }
 +  return 0;
 +}
  
 -  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
 +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;
 +}
  
 -  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];
 -    }
 -    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;
 -    }
 -    p->cMode = p->mode;
 -  }else
 +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;
 +}
  
 -  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->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 */
 -    }
 -  }else
 +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;
 +}
  
 -  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]);
 +/*****************
 + * The .mode command
 + */
 +COLLECT_HELP_TEXT[
 +  ".mode MODE ?TABLE?       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)",
-   "     count     Output only result row count",
-   "     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",
-   "     off       Query output suppressed",
-   "     quote     Escape answers as for SQL",
-   "     table     ASCII-art table",
-   "     tabs      Tab-separated values",
-   "     tcl       TCL list elements",
++  "     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 3 ){
-   const char *zMode = nArg>=2 ? azArg[1] : "";
-   int n2 = strlen30(zMode);
-   int c2 = zMode[0];
-   if( c2=='i' && strncmp(azArg[1],"insert",n2)==0 ){
-     p->mode = MODE_Insert;
-     set_table_name(p, nArg>=3 ? azArg[2] : "table");
-   }else if( nArg>2 ){
-     return SHELL_INVALID_ARGS;
-   }else if( c2=='l' && n2>2 && strncmp(azArg[1],"lines",n2)==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");
++      return 1;
+     }else{
 -      raw_printf(stderr, "Usage: .nullvalue STRING\n");
 -      rc = 1;
++      utf8_printf(stderr, "extra argument: \"%s\"\n", z);
++      return 1;
+     }
 -  }else
++  }
++  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];
++  }
++  n2 = strlen30(zMode);
++  if( strncmp(zMode,"lines",n2)==0 ){
 +    p->mode = MODE_Line;
 +    sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
-   }else if( c2=='c' && strncmp(azArg[1],"columns",n2)==0 ){
++  }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);
-   }else if( c2=='l' && n2>2 && strncmp(azArg[1],"list",n2)==0 ){
++    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( c2=='h' && strncmp(azArg[1],"html",n2)==0 ){
++  }else if( strncmp(zMode,"html",n2)==0 ){
 +    p->mode = MODE_Html;
-   }else if( c2=='t' && strncmp(azArg[1],"tcl",n2)==0 ){
++  }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( c2=='c' && strncmp(azArg[1],"csv",n2)==0 ){
++  }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( c2=='t' && strncmp(azArg[1],"tabs",n2)==0 ){
++  }else if( strncmp(zMode,"tabs",n2)==0 ){
 +    p->mode = MODE_List;
 +    sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Tab);
-   }else if( c2=='q' && strncmp(azArg[1],"quote",n2)==0 ){
++  }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( c2=='a' && strncmp(azArg[1],"ascii",n2)==0 ){
++  }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( c2=='m' && strncmp(azArg[1],"markdown",n2)==0 ){
++  }else if( strncmp(zMode,"markdown",n2)==0 ){
 +    p->mode = MODE_Markdown;
-   }else if( c2=='t' && strncmp(azArg[1],"table",n2)==0 ){
++    p->cmOpts = cmOpts;
++  }else if( strncmp(zMode,"table",n2)==0 ){
 +    p->mode = MODE_Table;
-   }else if( c2=='b' && strncmp(azArg[1],"box",n2)==0 ){
++    p->cmOpts = cmOpts;
++  }else if( strncmp(zMode,"box",n2)==0 ){
 +    p->mode = MODE_Box;
-   }else if( c2=='c' && strncmp(azArg[1],"count",n2)==0 ){
++    p->cmOpts = cmOpts;
++  }else if( strncmp(zMode,"count",n2)==0 ){
 +    p->mode = MODE_Count;
-   }else if( c2=='o' && strncmp(azArg[1],"off",n2)==0 ){
++  }else if( strncmp(zMode,"off",n2)==0 ){
 +    p->mode = MODE_Off;
-   }else if( c2=='j' && strncmp(azArg[1],"json",n2)==0 ){
++  }else if( strncmp(zMode,"json",n2)==0 ){
 +    p->mode = MODE_Json;
-   }else if( nArg==1 ){
-     raw_printf(p->out, "current output mode: %s\n", modeDescr[p->mode]);
 +  }else{
-     *pzErr = sqlite3_mprintf
-       ("Error: mode should be one of: ascii box column count csv html\n"
-        " insert json line list markdown off quote table tabs tcl\n");
++    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");
 +    return 1;
 +  }
 +  p->cMode = p->mode;
 +  return 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 */
 -    int 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;
 +/*****************
 + * 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
 +  "        --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            Disable safe mode for one command if nonce matches",
++  ".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;
 +  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;
 +    }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]);
 +    }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;
 -      }
 +    }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;
      }
 -
 -    /* 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
 -
 -  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 */
 -
 -    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;
 -      }
 -    }
 -    if( zFile==0 ){
 -      zFile = sqlite3_mprintf("stdout");
 -    }
 -    if( bOnce ){
 -      p->outCount = 2;
 +    if( zFN ){
 +      zNewFilename = sqlite3_mprintf("%s", zFN);
 +      shell_check_oom(zNewFilename);
      }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);
 +      zNewFilename = 0;
      }
 -#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");
 +    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;
 -      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
      }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);
 -      }
 +      p->pAuxDb->zFreeOnClose = zNewFilename;
      }
 -    sqlite3_free(zFile);
 -  }else
 +  }
 +  if( p->db==0 ){
 +    /* As a fall-back open a TEMP database */
 +    p->pAuxDb->zDbFilename = 0;
 +    open_db(p, 0);
 +  }
 +  return rc;
 +}
  
 -  if( c=='p' && n>=3 && strncmp(azArg[0], "parameter", n)==0 ){
 -    open_db(p,0);
 -    if( nArg<=1 ) goto parameter_syntax_error;
 +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]);
 +    exit(1);
 +  }
 +  /* Suspend safe mode for 1 meta-command after this. */
 +  p->bSafeModeFuture = 2;
 +  return 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
 +DISPATCHABLE_COMMAND( nullvalue ? 2 2 ){
 +  sqlite3_snprintf(sizeof(p->nullValue), p->nullValue,
 +                   "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]);
 +  return 0;
 +}
  
 -    /* .parameter list
 -    ** List all bind parameters.
 -    */
 -    if( nArg==2 && strcmp(azArg[1],"list")==0 ){
 -      sqlite3_stmt *pStmt = 0;
 -      int rx;
 -      int len = 0;
 +/*****************
 + * The .parameter command
 + */
 +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 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;
 +                              "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);
 -      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
 +    }
 +  }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
 +  /* .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
  
 -    /* .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);
 +  /* .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,%s);", zKey, zValue);
 +                 "REPLACE INTO temp.sqlite_parameters(key,value)"
 +                 "VALUES(%Q,%Q);", 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 ){
@@@ -10163,702 -10427,760 +10667,713 @@@ DISPATCHABLE_COMMAND( selftest 4 0 0 )
              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;
 -        }
 -      } /* 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( nArg>=3 ){
 -      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator,
 -                       "%.*s", (int)ArraySize(p->rowSeparator)-1, azArg[2]);
 -    }
 -  }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]);
 -          rc = 1;
 -          goto meta_command_exit;
 -        }
 -      }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;
 -      }
 -    }
 -    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 = "),(";
 -    }
 +          {
 +            *pzErr = sqlite3_mprintf
 +              ("Unknown operation \"%s\" on selftest line %d\n", zOp, tno);
 +            rc = 1;
 +            break;
 +          }
 +    } /* 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( 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]);
 -      }
 -    }else if( nArg==1 ){
 -      display_stats(p->db, p, 0);
 -    }else{
 -      raw_printf(stderr, "Usage: .stats ?on|off|stmt|vmstep?\n");
 -      rc = 1;
 -    }
 -  }else
 -
 -  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);
 +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
 +DISPATCHABLE_COMMAND( shell ? 2 0 ){
 +  return shellOut(azArg, nArg, p, pzErr);
 +}
 +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]);
-   utf8_printf(p->out, "%12.12s: %s\n","mode", modeDescr[p->mode]);
++  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 : "");
 +  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{
 +      p->statsOn = (u8)booleanValue(azArg[1]);
      }
 +  }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( 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);
 +/*****************
 + * 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 && 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;
 +}
  
 -#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"   },
 +/*****************
 + * 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)",
 +];
 +
 +/* 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;
      }
 +  }
 +  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->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->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;
 +    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++;
          }
 -#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;
 +        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;
        }
 -    }else{
 -      raw_printf(stderr, "Usage: .timer on|off\n");
 -      rc = 1;
 -    }
 -  }else
 -
 -#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;
 -        }
 +      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 {
 -          raw_printf(stderr, "Unknown option \"%s\" on \".trace\"\n", z);
 -          rc = 1;
 -          goto meta_command_exit;
 -        }
 -      }else{
 -        output_file_close(p->traceOut);
 -        p->traceOut = output_file_open(azArg[1], 0);
 +      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;
        }
 -    }
 -    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);
 +      output_file_close(p->traceOut);
 +      p->traceOut = output_file_open(azArg[1], 0);
      }
 -  }else
 -#endif /* !defined(SQLITE_OMIT_TRACE) */
 -
 -#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;
 +  }
 +  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;
 +}
 +
 +/*****************
 + * 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;
      }
 -    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);
 -      }
 +    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
 -#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;
 +  }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;
      }
 -    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;
 -      }
 -      rc = sqlite3_user_delete(p->db, azArg[2]);
 -      if( rc ){
 -        raw_printf(stderr, "User-Delete failed: %d\n", rc);
 -        rc = 1;
 -      }
 -    }else{
 -      raw_printf(stderr, "Usage: .user login|add|edit|delete ...\n");
 -      rc = 1;
 -      goto meta_command_exit;
 +    rc = sqlite3_user_delete(p->db, azArg[2]);
 +    if( rc ){
 +      *pzErr = shellMPrintf(0,"User-Delete failed: %d\n", rc);
 +      return 1;
      }
 -  }else
 -#endif /* SQLITE_USER_AUTHENTICATION */
 +  }else{
 +    goto teach_fail;
 +  }
 +  return 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());
 +/*****************
 + * 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());
 +  utf8_printf(p->out, "zlib version %s\n", zlibVersion());
  #endif
  #define CTIMEOPT_VAL_(opt) #opt
  #define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt)
@@@ -11383,10 -11473,15 +11909,17 @@@ static int process_input(ShellState *p)
    int startline = 0;        /* Line number for start of current input */
    QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */
  
+   if( p->inputNesting==MAX_INPUT_NESTING ){
+     /* This will be more informative in a later version. */
+     utf8_printf(stderr,"Input nesting limit (%d) reached at line %d."
+                 " Check recursion.\n", MAX_INPUT_NESTING, p->lineno);
+     return 1;
+   }
+   ++p->inputNesting;
    p->lineno = 0;
 -  while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){
 +  while( rc<2
 +         &&
 +         (errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive)) ){
      fflush(p->out);
      zLine = one_input_line(p->in, zLine, nSql>0);
      if( zLine==0 ){
    }
    free(zSql);
    free(zLine);
 -  return errCnt>0;
+   --p->inputNesting;
 +  return (p->abruptExit)? SHELL_FORBIDDEN_OP : errCnt>0;
  }
  
  /*
index 8726454769c2a88765dd74ad86758099c885a583,cbbadc58c9e1b4d83745e503473be9d81e1b73c3..fee302b5ddcd0fc100ad502f5ba238b34da8053b
@@@ -74,11 -74,11 +74,11 @@@ 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" ""
 -} {0 {}}
 +  catchcmdex "-init FOO test.db .quit BAD" ""
 +} {1 {child process exited abnormally}}
  do_test shell1-1.3.3 {
    catchcmd "-init FOO test.db BAD .quit" ""
- } {/1 .Error: in prepare, near "BAD": syntax error (1)*/}
+ } {/1 .Error: in prepare, near "BAD": syntax error*/}
  
  # -echo                print commands before execution
  do_test shell1-1.4.1 {
@@@ -182,16 -182,13 +182,15 @@@ do_test shell1-1.16.1 
  # check first token handling
  do_test shell1-2.1.1 {
    catchcmd "test.db" ".foo"
 -} {1 {Error: unknown command or invalid arguments:  "foo". Enter ".help" for help}}
 +} {1 {Error: unknown command: "foo"}}
  do_test shell1-2.1.2 {
    catchcmd "test.db" ".\"foo OFF\""
 -} {1 {Error: unknown command or invalid arguments:  "foo OFF". Enter ".help" for help}}
 +} {1 {Error: unknown command: "foo OFF"}}
  do_test shell1-2.1.3 {
    catchcmd "test.db" ".\'foo OFF\'"
 -} {1 {Error: unknown command or invalid arguments:  "foo OFF". Enter ".help" for help}}
 +} {1 {Error: unknown command: "foo OFF"}}
 +
- set modeShouldBe "Error: mode should be one of: ascii box column count csv html
-  insert json line list markdown off quote table tabs tcl"
++set modeShouldBe "Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl"
  
  # unbalanced quotes
  do_test shell1-2.2.1 {
index 6f59507acffd5a426378da84450ffdfd4d4315d8,db8cd96ab87cd7c4376ea656f994794b4ec7137c..6f29df3d827763bc491c45ca64472b1aabe7e819
@@@ -63,8 -63,10 +63,8 @@@ do_test shell2-1.3 
  
      UPDATE OR REPLACE t5 SET a = 4 WHERE a = 1;
    }
- } {1 {Error: near line 9: stepping, too many levels of trigger recursion (1)}}
+ } {1 {Runtime error near line 9: too many levels of trigger recursion}}
  
 -
 -
  # Shell not echoing all commands with echo on.
  # Ticket [eb620916be].
  
Simple merge