]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
More commands dispathed. Dispatch translation parameterized.
authorlarrybr <larrybr@noemail.net>
Tue, 6 Jul 2021 02:07:21 +0000 (02:07 +0000)
committerlarrybr <larrybr@noemail.net>
Tue, 6 Jul 2021 02:07:21 +0000 (02:07 +0000)
FossilOrigin-Name: 372e3241c9e87144ffb19f7e880e9c75ec80260b0db5aed24474bc2a8d7381e4

manifest
manifest.uuid
src/shell.c.in
tool/mkshellc.tcl

index 37b39890780747bcc0d16b3d93ec301faa83b21b..836c2d1f8ad3cfc8aa889849c7734e049102534b 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Streamline\smost\scommon\scommand\sdefinition
-D 2021-07-04T22:38:20.110
+C More\scommands\sdispathed.\sDispatch\stranslation\sparameterized.
+D 2021-07-06T02:07:21.072
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -545,7 +545,7 @@ F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
 F src/resolve.c b379c5ffe3b692e9c64fa37817cc0efa204b7c9468a818309dde85fd132d9d81
 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
 F src/select.c 4fa607bab6bcc580f12dbaf9c800b2250a1e408f10321a1d3bcb1dd30c447e62
-F src/shell.c.in 0a1460f9daacc06ce7618981d2d3d3b206a64ca30258a5968ffd421eccaabaeb
+F src/shell.c.in ce74a51f548a268436cba07b0082c9b36161ac7f8bdc4c476f88f2d506ba248c
 F src/sqlite.h.in ecf5aa981da30c33da3e9f353bf3ebf055d3c380c80d6a4f954e58d18ccd6df1
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h e97f4e9b509408fea4c4e9bef5a41608dfac343b4d3c7a990dedde1e19af9510
@@ -1853,7 +1853,7 @@ F tool/mkopcodec.tcl d1b6362bd3aa80d5520d4d6f3765badf01f6c43c
 F tool/mkopcodeh.tcl 130b88697da6ec5b89b41844d955d08fb62c2552e889dec8c7bcecb28d8f50bd
 F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa
 F tool/mkpragmatab.tcl ae5585ae76ca26e4d6ccd5ea9cdebaf5efefb318bf989497a0e846cd711d9ab1
-F tool/mkshellc.tcl 270ddd503b3c6c2ea1fa6559a086cd2517ff84088b54909960fffeda4d0b816e
+F tool/mkshellc.tcl e671cba0dfdab17a652530c59ad7c44170f9aa31cda04222ec77079adbf73c9b
 F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9
 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
 F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f
@@ -1919,7 +1919,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 5e7e0d4ef8665e924f499238b1469a5fc06d24f6cf96864b502e62734d92e7ee
-R 1911848c8c9c967a495f2da60a0478ec
+P dd76b41a72aca94450fb6f45ff56af95d2adb9275eebe9ca67ebd04a52a63c33
+R 8168cef1e5221cce40797275ada85323
 U larrybr
-Z 9f01a27b7f49ee2ea4a875a6945797b7
+Z b0378e746b391ae098a6bfe801e6d972
index 7ee1d1065db39c7f18c334505cf3c42e55806f40..60446aa03ffff47c0db59099108bf5812be88843 100644 (file)
@@ -1 +1 @@
-dd76b41a72aca94450fb6f45ff56af95d2adb9275eebe9ca67ebd04a52a63c33
\ No newline at end of file
+372e3241c9e87144ffb19f7e880e9c75ec80260b0db5aed24474bc2a8d7381e4
\ No newline at end of file
index fe5db11388e0922a83d777718035f9ffd19f34a8..f4312d3508bb012534cd2f883b708f5f47d3b058 100644 (file)
@@ -3345,6 +3345,9 @@ static int expertFinish(
   return rc;
 }
 
+#define NO_SUCH_COMMAND -0x7fff
+#define INVALID_ARGS -0x7ffe
+
 /*
 ** Implementation of ".expert" dot command.
 */
@@ -7131,6 +7134,98 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){
 }
 #endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
 
+static int writeDb( char *azArg[], int nArg, ShellState *p ){
+  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;
+  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 INVALID_ARGS;
+          }
+    }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 INVALID_ARGS;
+    }
+  }
+  if( zDestFile==0 ){
+    raw_printf(stderr, "missing FILENAME argument on .backup\n");
+    return INVALID_ARGS;
+  }
+  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);
+  return rc;
+}
+
+/*
+ * Define meta-commands and provide for their dispatch and .help text.
+ * These should be kept in command name order for coding convenience
+ * except where meta-commands share implementation. (The ordering
+ * required for dispatch and help text is effected regardless.)
+ */
+
+DISPATCH_CONFIG[
+  RETURN_TYPE=int
+  STORAGE_CLASS=static
+  ARGS_SIGNATURE=char *$arg4\[\], int $arg5, ShellState *$arg6
+  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_ARG_COUNT=7
+];
+
+
 CONDITION_COMMAND(seeargs defined(SQLITE_GIMME_SEEARGS));
 /*****************
  * The .seeargs command
@@ -7146,6 +7241,36 @@ DISPATCHABLE_COMMAND( seeargs ? 0 0 azArg nArg p ){
   return rc;
 }
 
+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",
+  "     -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",
+  "     -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 ? 3 0 azArg nArg p ){
+  open_db(p, 0);
+  return arDotCommand(p, 0, azArg, nArg);
+}
+
 /*****************
  * The .auth command
  */
@@ -7164,17 +7289,397 @@ DISPATCHABLE_COMMAND( auth 3 2 2 azArg nArg p ){
   return rc;
 }
 
+/*****************
+ * The .backup and .save commands (aliases for each other)
+ * These defer to writeDb in the dispatch table, so are not here.
+ */
+COLLECT_HELP_TEXT[
+  ".backup ?DB? FILE        Backup DB (default \"main\") to FILE",
+  "   Options:",
+  "     --append               Use the appendvfs",
+  "     --async                Write the FILE without journal and fsync()",
+  ".save ?DB? FILE          Write DB (default \"main\") to FILE",
+  "   Options:",
+  "     --append               Use the appendvfs",
+  "     --async                Write the FILE without journal and fsync()",
+];
+COLLECT_DISPATCH( * )[
+  { "backup", writeDb, 4, 2, 5 },
+  { "save", writeDb, 3, 2, 5 },
+];
+
+/*****************
+ * The .bail command
+ */
+COLLECT_HELP_TEXT[
+  ".bail on|off             Stop after hitting an error.  Default OFF",
+];
+DISPATCHABLE_COMMAND( bail 3 2 2 ){
+  if( nArg==2 ){
+    bail_on_error = booleanValue(azArg[1]);
+    return 0;
+  }else{
+    raw_printf(stderr, "Usage: .bail on|off\n");
+    return 1;
+  }
+}
+
+/*****************
+ * The .binary and .cd commands
+ */
+COLLECT_HELP_TEXT[
+  ".binary on|off           Turn binary output on or off.  Default OFF",
+  ".cd DIRECTORY            Change the working directory to DIRECTORY",
+];
+DISPATCHABLE_COMMAND( binary 3 2 2 ){
+  if( booleanValue(azArg[1]) ){
+    setBinaryMode(p->out, 1);
+  }else{
+    setTextMode(p->out, 1);
+  }
+  return 0;
+}
+
+DISPATCHABLE_COMMAND( cd ? 2 2 ){
+  int rc=0;
+#if 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;
+  }
+  return rc;
+}
+
+/* The undocumented ".breakpoint" command causes a call
+** to the no-op routine named test_breakpoint().
+*/
+DISPATCHABLE_COMMAND( breakpoint 3 1 1 ){
+  test_breakpoint();
+  return 0;
+}
+
+/*****************
+ * The .changes, .check and .clone 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",
+];
+DISPATCHABLE_COMMAND( changes 3 2 2 ){
+  setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
+  return 0;
+}
+DISPATCHABLE_COMMAND( check 3 0 0 ){
+  /* Cancel output redirection, if it is currently set (by .testcase)
+  ** Then read the content of the testcase-out.txt file and compare against
+  ** azArg[1].  If there are differences, report an error and exit.
+  */
+  char *zRes = 0;
+  int rc=0;
+  output_reset(p);
+  if( nArg!=2 ){
+    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);
+  return rc;
+}
+DISPATCHABLE_COMMAND( clone ? 2 2 ){
+  tryToClone(p, azArg[1]);
+  return 0;
+}
+
+
+/*****************
+ * 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",
+];
+DISPATCHABLE_COMMAND( databases 2 1 1 ){
+  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 ){
+    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);
+      azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*));
+      if( azName==0 ){ shell_out_of_memory();  /* Does not return */ }
+      azName[nName*2] = strdup(zSchema);
+      azName[nName*2+1] = strdup(zFile);
+      nName++;
+    }
+  }
+  sqlite3_finalize(pStmt);
+  for(i=0; i<nName; i++){
+    int eTxn = sqlite3_txn_state(p->db, azName[i*2]);
+    int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]);
+    const char *z = azName[i*2+1];
+    utf8_printf(p->out, "%s: %s %s%s\n",
+                azName[i*2],
+                z && z[0] ? z : "\"\"",
+                bRdonly ? "r/o" : "r/w",
+                eTxn==SQLITE_TXN_NONE ? "" :
+                eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
+    free(azName[i*2]);
+    free(azName[i*2+1]);
+  }
+  sqlite3_free(azName);
+  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) ){
+    utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]);
+    utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n");
+    return 1;
+  }   
+  return 0;
+}
+DISPATCHABLE_COMMAND( dbinfo 3 1 2 ){
+  return shell_dbinfo_command(p, nArg, azArg);
+}
+
+/*****************
+ * The .dump, .echo and .eqp commands
+ */
+COLLECT_HELP_TEXT[
+  ".dump ?OBJECTS?          Render database content as SQL",
+  "   Options:",
+  "     --data-only            Output only INSERT statements",
+  "     --newlines             Allow unescaped newline characters in output",
+  "     --nosys                Omit system tables (ex: \"sqlite_stat1\")",
+  "     --preserve-rowids      Include ROWID values in the output",
+  "   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");
+        sqlite3_free(zLike);
+        return 1;
+#else
+        ShellSetFlag(p, SHFLG_PreserveRowid);
+#endif
+      }else{
+        if( strcmp(z,"newlines")==0 ){
+          ShellSetFlag(p, SHFLG_Newlines);
+        }else if( strcmp(z,"data-only")==0 ){
+          ShellSetFlag(p, SHFLG_DumpDataOnly);
+        }else if( strcmp(z,"nosys")==0 ){
+          ShellSetFlag(p, SHFLG_DumpNoSys);
+        }else{
+          raw_printf(stderr, "Unknown option \"%s\" on \".dump\"\n", azArg[i]);
+          sqlite3_free(zLike);
+          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]
+                    );
+      
+      if( zLike ){
+        zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr);
+      }else{
+        zLike = zExpr;
+      }
+    }
+  }
+
+  open_db(p, 0);
+
+  if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
+    /* When playing back a "dump", the content might appear in an order
+    ** which causes immediate foreign key constraints to be violated.
+    ** So disable foreign-key constraint enforcement to prevent problems. */
+    raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n");
+    raw_printf(p->out, "BEGIN TRANSACTION;\n");
+  }
+  p->writableSchema = 0;
+  p->showHeader = 0;
+  /* Set writable_schema=ON since doing so forces SQLite to initialize
+  ** as much of the schema as it can even if the sqlite_schema table is
+  ** corrupt. */
+  sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
+  p->nErr = 0;
+  if( zLike==0 ) zLike = sqlite3_mprintf("true");
+  zSql = sqlite3_mprintf(
+           "SELECT name, type, sql FROM sqlite_schema AS o "
+           "WHERE (%s) AND type=='table'"
+           "  AND sql NOT NULL"
+           " ORDER BY tbl_name='sqlite_sequence', rowid",
+           zLike
+         );
+  run_schema_dump_query(p,zSql);
+  sqlite3_free(zSql);
+  if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
+    zSql = sqlite3_mprintf(
+             "SELECT sql FROM sqlite_schema AS o "
+             "WHERE (%s) AND sql NOT NULL"
+             "  AND type IN ('index','trigger','view')",
+             zLike
+           );
+    run_table_dump_query(p, zSql);
+    sqlite3_free(zSql);
+  }
+  sqlite3_free(zLike);
+  if( p->writableSchema ){
+    raw_printf(p->out, "PRAGMA writable_schema=OFF;\n");
+    p->writableSchema = 0;
+  }
+  sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0);
+  sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0);
+  if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
+    raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n");
+  }
+  p->showHeader = savedShowHeader;
+  p->shellFlgs = savedShellFlags;
+
+  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{
+      p->autoEQP = (u8)booleanValue(azArg[1]);
+    }
+  }else{
+    raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n");
+    return 1;
+  }
+}
+
+/*****************
+ * The . command
+ */
+COLLECT_HELP_TEXT[
+];
+DISPATCHABLE_COMMAND( ? ? 1 1 ){
+}
+
+
 /*****************
  * The .help command
  */
 COLLECT_HELP_TEXT[
   ".help ?-all? ?PATTERN?   Show help text for PATTERN",
 ];
-COLLECT_DISPATCH( help )[
-  { "help", commandHelp, 3, 1, 0 },
-];
-static int commandHelp(char *azArg[], int nArg, ShellState *p)
-{
+DISPATCHABLE_COMMAND( help 3 0 0 ){
   if( nArg>=2 ){
     if( showHelp(p->out, azArg[1])==0 ){
       utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]);
@@ -7192,7 +7697,7 @@ static struct DispatchEntry {
   int (*cmdDoer)(char *azArg[], int nArg, ShellState *);
   unsigned char minLen, minArgs, maxArgs;
 } command_table[] = {
-  EMIT_DISPATCH();
+  EMIT_DISPATCH(2);
   { 0, 0, 0, -1, -1 }
 };
 static unsigned numCommands = sizeof(command_table)/sizeof(struct DispatchEntry) - 1;
@@ -7200,22 +7705,24 @@ static unsigned numCommands = sizeof(command_table)/sizeof(struct DispatchEntry)
 /*****************
  * Command dispatcher
  */
-#define NO_SUCH_COMMAND -0x7fff
-#define INVALID_ARGS -0x7ffe
 
 int dispatchCommand(char *azArg[], int nArg, ShellState *pSS)
 {
   const char *cmdName = azArg[0];
+  int cmdLen = strlen30(cmdName);
   struct DispatchEntry *pde = 0;
   int ixb = 0, ixe = numCommands-1;
   while( ixb <= ixe ){
     int ixm = (ixb+ixe)/2;
-    int md = strncmp(cmdName, command_table[ixm].cmdName, command_table[ixm].minLen);
+    int md = strncmp(cmdName, command_table[ixm].cmdName, cmdLen);
     if( md>0 ){
       ixb = ixm+1;
     }else if( md<0 ){
       ixe = ixm-1;
     }else{
+      if( command_table[ixm].minLen > cmdLen ){
+        return NO_SUCH_COMMAND;
+      }
       pde = &command_table[ixm];
       break;
     }
@@ -7281,412 +7788,9 @@ static int do_meta_command(char *zLine, ShellState *p){
   c = azArg[0][0];
   clearTempFile(p);
 
-
-#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
-  if( c=='a' && strncmp(azArg[0], "archive", n)==0 ){
-    open_db(p, 0);
-    rc = arDotCommand(p, 0, azArg, nArg);
-  }else
-#endif
-
-  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;
-    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
-
-  if( c=='c' && strcmp(azArg[0],"cd")==0 ){
-    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
-
-  /* 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' && 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
-
-  /* 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.
+  /* Check for the special, non-dispatched meta-commands.
   */
-  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 ){
-    if( nArg==2 ){
-      tryToClone(p, azArg[1]);
-    }else{
-      raw_printf(stderr, "Usage: .clone FILENAME\n");
-      rc = 1;
-    }
-  }else
-
-  if( c=='d' && n>1 && strncmp(azArg[0], "databases", n)==0 ){
-    char **azName = 0;
-    int nName = 0;
-    sqlite3_stmt *pStmt;
-    int i;
-    open_db(p, 0);
-    rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
-    if( rc ){
-      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
-      rc = 1;
-    }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);
-        azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*));
-        if( azName==0 ){ shell_out_of_memory();  /* Does not return */ }
-        azName[nName*2] = strdup(zSchema);
-        azName[nName*2+1] = strdup(zFile);
-        nName++;
-      }
-    }
-    sqlite3_finalize(pStmt);
-    for(i=0; i<nName; i++){
-      int eTxn = sqlite3_txn_state(p->db, azName[i*2]);
-      int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]);
-      const char *z = azName[i*2+1];
-      utf8_printf(p->out, "%s: %s %s%s\n",
-         azName[i*2],
-         z && z[0] ? z : "\"\"",
-         bRdonly ? "r/o" : "r/w",
-         eTxn==SQLITE_TXN_NONE ? "" :
-            eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
-      free(azName[i*2]);
-      free(azName[i*2+1]);
-    }
-    sqlite3_free(azName);
-  }else
-
-  if( c=='d' && n>=3 && strncmp(azArg[0], "dbconfig", n)==0 ){
-    static const struct DbConfigChoices {
-      const char *zName;
-      int op;
-    } aDbConfig[] = {
-        { "defensive",          SQLITE_DBCONFIG_DEFENSIVE             },
-        { "dqs_ddl",            SQLITE_DBCONFIG_DQS_DDL               },
-        { "dqs_dml",            SQLITE_DBCONFIG_DQS_DML               },
-        { "enable_fkey",        SQLITE_DBCONFIG_ENABLE_FKEY           },
-        { "enable_qpsg",        SQLITE_DBCONFIG_ENABLE_QPSG           },
-        { "enable_trigger",     SQLITE_DBCONFIG_ENABLE_TRIGGER        },
-        { "enable_view",        SQLITE_DBCONFIG_ENABLE_VIEW           },
-        { "fts3_tokenizer",     SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER },
-        { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE    },
-        { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT    },
-        { "load_extension",     SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION },
-        { "no_ckpt_on_close",   SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE      },
-        { "reset_database",     SQLITE_DBCONFIG_RESET_DATABASE        },
-        { "trigger_eqp",        SQLITE_DBCONFIG_TRIGGER_EQP           },
-        { "trusted_schema",     SQLITE_DBCONFIG_TRUSTED_SCHEMA        },
-        { "writable_schema",    SQLITE_DBCONFIG_WRITABLE_SCHEMA       },
-    };
-    int ii, v;
-    open_db(p, 0);
-    for(ii=0; ii<ArraySize(aDbConfig); ii++){
-      if( nArg>1 && strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue;
-      if( nArg>=3 ){
-        sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0);
-      }
-      sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v);
-      utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off");
-      if( nArg>1 ) break;
-    }
-    if( nArg>1 && ii==ArraySize(aDbConfig) ){
-      utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]);
-      utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n");
-    }   
-  }else
-
-  if( c=='d' && n>=3 && strncmp(azArg[0], "dbinfo", n)==0 ){
-    rc = shell_dbinfo_command(p, nArg, azArg);
-  }else
-
-#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
-  if( c=='r' && strncmp(azArg[0], "recover", n)==0 ){
-    open_db(p, 0);
-    rc = recoverDatabaseCmd(p, nArg, azArg);
-  }else
-#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
 
-  if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){
-    char *zLike = 0;
-    char *zSql;
-    int i;
-    int savedShowHeader = p->showHeader;
-    int savedShellFlags = p->shellFlgs;
-    ShellClearFlag(p, 
-       SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo
-       |SHFLG_DumpDataOnly|SHFLG_DumpNoSys);
-    for(i=1; i<nArg; i++){
-      if( azArg[i][0]=='-' ){
-        const char *z = azArg[i]+1;
-        if( z[0]=='-' ) z++;
-        if( strcmp(z,"preserve-rowids")==0 ){
-#ifdef SQLITE_OMIT_VIRTUALTABLE
-          raw_printf(stderr, "The --preserve-rowids option is not compatible"
-                             " with SQLITE_OMIT_VIRTUALTABLE\n");
-          rc = 1;
-          sqlite3_free(zLike);
-          goto meta_command_exit;
-#else
-          ShellSetFlag(p, SHFLG_PreserveRowid);
-#endif
-        }else
-        if( strcmp(z,"newlines")==0 ){
-          ShellSetFlag(p, SHFLG_Newlines);
-        }else
-        if( strcmp(z,"data-only")==0 ){
-          ShellSetFlag(p, SHFLG_DumpDataOnly);
-        }else
-        if( strcmp(z,"nosys")==0 ){
-          ShellSetFlag(p, SHFLG_DumpNoSys);
-        }else
-        {
-          raw_printf(stderr, "Unknown option \"%s\" on \".dump\"\n", azArg[i]);
-          rc = 1;
-          sqlite3_free(zLike);
-          goto meta_command_exit;
-        }
-      }else{
-        /* azArg[i] contains a LIKE pattern. This ".dump" request should
-        ** only dump data for tables for which either the table name matches
-        ** the LIKE pattern, or the table appears to be a shadow table of
-        ** a virtual table for which the name matches the LIKE pattern.
-        */
-        char *zExpr = sqlite3_mprintf(
-            "name LIKE %Q ESCAPE '\\' OR EXISTS ("
-            "  SELECT 1 FROM sqlite_schema WHERE "
-            "    name LIKE %Q ESCAPE '\\' AND"
-            "    sql LIKE 'CREATE VIRTUAL TABLE%%' AND"
-            "    substr(o.name, 1, length(name)+1) == (name||'_')"
-            ")", azArg[i], azArg[i]
-        );
-      
-        if( zLike ){
-          zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr);
-        }else{
-          zLike = zExpr;
-        }
-      }
-    }
-
-    open_db(p, 0);
-
-    if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
-      /* When playing back a "dump", the content might appear in an order
-      ** which causes immediate foreign key constraints to be violated.
-      ** So disable foreign-key constraint enforcement to prevent problems. */
-      raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n");
-      raw_printf(p->out, "BEGIN TRANSACTION;\n");
-    }
-    p->writableSchema = 0;
-    p->showHeader = 0;
-    /* Set writable_schema=ON since doing so forces SQLite to initialize
-    ** as much of the schema as it can even if the sqlite_schema table is
-    ** corrupt. */
-    sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
-    p->nErr = 0;
-    if( zLike==0 ) zLike = sqlite3_mprintf("true");
-    zSql = sqlite3_mprintf(
-      "SELECT name, type, sql FROM sqlite_schema AS o "
-      "WHERE (%s) AND type=='table'"
-      "  AND sql NOT NULL"
-      " ORDER BY tbl_name='sqlite_sequence', rowid",
-      zLike
-    );
-    run_schema_dump_query(p,zSql);
-    sqlite3_free(zSql);
-    if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
-      zSql = sqlite3_mprintf(
-        "SELECT sql FROM sqlite_schema AS o "
-        "WHERE (%s) AND sql NOT NULL"
-        "  AND type IN ('index','trigger','view')",
-        zLike
-      );
-      run_table_dump_query(p, zSql);
-      sqlite3_free(zSql);
-    }
-    sqlite3_free(zLike);
-    if( p->writableSchema ){
-      raw_printf(p->out, "PRAGMA writable_schema=OFF;\n");
-      p->writableSchema = 0;
-    }
-    sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0);
-    sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0);
-    if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
-      raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n");
-    }
-    p->showHeader = savedShowHeader;
-    p->shellFlgs = savedShellFlags;
-  }else
-
-  if( c=='e' && strncmp(azArg[0], "echo", n)==0 ){
-    if( nArg==2 ){
-      setOrClearFlag(p, SHFLG_Echo, azArg[1]);
-    }else{
-      raw_printf(stderr, "Usage: .echo on|off\n");
-      rc = 1;
-    }
-  }else
-
-  if( c=='e' && strncmp(azArg[0], "eqp", n)==0 ){
-    if( nArg==2 ){
-      p->autoEQPtest = 0;
-      if( p->autoEQPtrace ){
-        if( p->db ) sqlite3_exec(p->db, "PRAGMA vdbe_trace=OFF;", 0, 0, 0);
-        p->autoEQPtrace = 0;
-      }
-      if( strcmp(azArg[1],"full")==0 ){
-        p->autoEQP = AUTOEQP_full;
-      }else if( strcmp(azArg[1],"trigger")==0 ){
-        p->autoEQP = AUTOEQP_trigger;
-#ifdef SQLITE_DEBUG
-      }else if( strcmp(azArg[1],"test")==0 ){
-        p->autoEQP = AUTOEQP_on;
-        p->autoEQPtest = 1;
-      }else if( strcmp(azArg[1],"trace")==0 ){
-        p->autoEQP = AUTOEQP_full;
-        p->autoEQPtrace = 1;
-        open_db(p, 0);
-        sqlite3_exec(p->db, "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0);
-        sqlite3_exec(p->db, "PRAGMA vdbe_trace=ON;", 0, 0, 0);
-#endif
-      }else{
-        p->autoEQP = (u8)booleanValue(azArg[1]);
-      }
-    }else{
-      raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n");
-      rc = 1;
-    }
-  }else
 
   if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){
     if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc);
@@ -8905,6 +9009,13 @@ static int do_meta_command(char *zLine, ShellState *p){
     p->lineno = savedLineno;
   }else
 
+#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
+  if( c=='r' && strncmp(azArg[0], "recover", n)==0 ){
+    open_db(p, 0);
+    rc = recoverDatabaseCmd(p, nArg, azArg);
+  }else
+#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
+
   if( c=='r' && n>=3 && strncmp(azArg[0], "restore", n)==0 ){
     const char *zSrcFile;
     const char *zDb;
@@ -10567,57 +10678,6 @@ static void process_sqliterc(
 }
 
 COLLECT_HELP_TEXT[
-#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE)
-  ".archive ...             Manage SQL archives",
-  "   Each command must have exactly one of the following options:",
-  "     -c, --create               Create a new archive",
-  "     -u, --update               Add or update files with changed mtime",
-  "     -i, --insert               Like -u but always add even if unchanged",
-  "     -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",
-  "     -n, --dryrun               Show the SQL that would have occurred",
-  "   Examples:",
-  "     .ar -cf ARCHIVE foo bar  # Create ARCHIVE from files foo and bar",
-  "     .ar -tf ARCHIVE          # List members of ARCHIVE",
-  "     .ar -xvf ARCHIVE         # Verbosely extract files from ARCHIVE",
-  "   See also:",
-  "      http://sqlite.org/cli.html#sqlite_archive_support",
-#endif
-];
-COLLECT_HELP_TEXT[
-  ".backup ?DB? FILE        Backup DB (default \"main\") to FILE",
-  "       --append            Use the appendvfs",
-  "       --async             Write to FILE without journal and fsync()",
-  ".bail on|off             Stop after hitting an error.  Default OFF",
-  ".binary on|off           Turn binary output on or off.  Default OFF",
-  ".cd DIRECTORY            Change the working directory to DIRECTORY",
-  ".changes on|off          Show number of rows changed by SQL",
-  ".check GLOB              Fail if output since .testcase does not match",
-  ".clone NEWDB             Clone data into NEWDB from the existing database",
-  ".databases               List names and files of attached databases",
-  ".dbconfig ?op? ?val?     List or change sqlite3_db_config() options",
-  ".dbinfo ?DB?             Show status information about the database",
-  ".dump ?OBJECTS?          Render database content as SQL",
-  "   Options:",
-  "     --data-only            Output only INSERT statements",
-  "     --newlines             Allow unescaped newline characters in output",
-  "     --nosys                Omit system tables (ex: \"sqlite_stat1\")",
-  "     --preserve-rowids      Include ROWID values in the output",
-  "   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",
   ".excel                   Display the output of next command in spreadsheet",
   "   --bom                   Put a UTF8 byte-order mark on intermediate file",
   ".exit ?CODE?             Exit this program with return-code CODE",
@@ -10752,7 +10812,6 @@ COLLECT_HELP_TEXT[
 ];
 COLLECT_HELP_TEXT[
   ".restore ?DB? FILE       Restore content of DB (default \"main\") from FILE",
-  ".save FILE               Write in-memory database into FILE",
   ".scanstats on|off        Turn sqlite3_stmt_scanstatus() metrics on or off",
   ".schema ?PATTERN?        Show the CREATE statements matching PATTERN",
   "   Options:",
@@ -10863,7 +10922,7 @@ static const char *(azHelp[]) = {
   ".whatever ?arg? ...      Summary of effects (limited to this line's length)",
   "   ^ ^                   ^  ^                                              ",
 */
-  EMIT_HELP_TEXT();
+  EMIT_HELP_TEXT(2);
   0 /* Sentinel */
 };
 
index 8ca4259ded162ba558c6b4d0846d76946a66f5e7..b47e4eb92e1e60da5dff821d8bb016abe62ecf94 100644 (file)
@@ -37,7 +37,28 @@ set ::cmd_help [dict create]
 set ::cmd_dispatch [dict create]
 set ::cmd_condition [dict create]
 set ::iShuffleErrors 0
-set ::commandFuncSuffix "Command"
+
+# Setup dispatching function signature and table entry struct .
+set ::dispCfg [dict create \
+  RETURN_TYPE int \
+  STORAGE_CLASS static \
+  ARGS_SIGNATURE "char *\$arg4\\\[\\\], int \$arg5, ShellState *\$arg6" \
+  DISPATCH_ENTRY \
+   "\x7B \"\$cmd\", \$\x7Bcmd\x7DCommand, \$arg1,\$arg2,\$arg3 \x7D," \
+  DISPATCHEE_NAME {${cmd}Command} \
+  CMD_CAPTURE_RE "^\\s*\x7B\\s*\"(\\w+)\"" \
+]
+# Other config keys:
+#  DC_ARG_COUNT=<number of arguments to DISPATCHABLE_COMMAND()>
+#  DC_ARG#_DEFAULT=<default value for the #th argument>
+# Variables $cmd and $arg# (where # = 0 .. DC_ARG_COUNT-1) have values
+# when ARGS_SIGNATURE, DISPATCH_ENTRY, and DISPATCHEE_NAME are evaluated.
+
+# proc dump_cfg {} {
+#   foreach k [dict keys $::dispCfg] {
+#     puts stderr "$k=[dict get $::dispCfg $k]"
+#   }
+# }
 
 proc condition_command {cmd pp_expr} {
   if {[regexp {^(!)?defined\(\s*(\w+)\s*\)} $pp_expr ma bang pp_var]} {
@@ -52,12 +73,19 @@ proc condition_command {cmd pp_expr} {
   dict set ::cmd_condition $cmd $pp_expr
 }
 
-proc emit_conditionally {cmd lines ostrm} {
+proc emit_conditionally {cmd lines ostrm {indent ""}} {
   set wrapped [dict exists $::cmd_condition $cmd]
   if {$wrapped} {
     puts $ostrm [dict get $::cmd_condition $cmd]
   }
-  puts $ostrm [join $lines "\n"]
+  if {[regexp {^\s*(\d+)\s*$} $indent ma inum]} {
+    set lead [string repeat " " $inum]
+    foreach line $lines {
+      puts $ostrm "$lead[string trimleft $line]"
+    }
+  } else {
+    puts $ostrm [join $lines "\n"]
+  }
   if {$wrapped} {
     puts $ostrm "#endif"
   }
@@ -118,7 +146,9 @@ proc chunkify_help {htin} {
 # in which case it is emitted as-is.
 proc do_shuffle {hFile lx ostrm} {
   set iAte 0
-  if {[regexp {^COLLECT_HELP_TEXT\[} $lx]} {
+  if {![regexp {^\s{0,4}[A-Z]+} $lx]} {
+    puts $ostrm $lx
+  } elseif {[regexp {^COLLECT_HELP_TEXT\[} $lx]} {
     incr iAte
     set help_frag {}
     set lx [gets $hFile]
@@ -133,9 +163,22 @@ proc do_shuffle {hFile lx ostrm} {
       && $tc eq "\x7B"} {
     set args [split [regsub {\s+} [string trim $args] " "]]
     incr iAte
-    if {[llength $args] != 7} {
+    set na [llength $args]
+    set cmd [lindex $args 0]
+    set naPass [dict get $::dispCfg DC_ARG_COUNT]
+    if {$na > $naPass} {
       puts stderr "Bad args: $lx"
     } else {
+      while {$na < $naPass} {
+        if {![dict exists $::dispCfg "DC_ARG${na}_DEFAULT"]} {
+          puts stderr "Too few args: $lx (need $naPass)"
+          incr ::iShuffleErrors
+          break
+        } else {
+          lappend args [subst [dict get $::dispCfg "DC_ARG${na}_DEFAULT"]]
+        }
+        incr na
+      }
       set body {}
       while {![eof $hFile]} {
         set lb [gets $hFile]
@@ -143,41 +186,72 @@ proc do_shuffle {hFile lx ostrm} {
         lappend body $lb
         if {[regexp "^\x7D\\s*\$" $lb]} { break }
       }
-      foreach {cmd cmdLen naMin naMax azA nA pSS} $args {
-        if {$cmdLen eq "?"} {
-          set cmdLen [string length $cmd]
+      for {set aix 1} {$aix < $na} {incr aix} {
+        set av [lindex $args $aix]
+        if {$av eq "?"} {
+          set ai [expr {$aix + 1}]
+          set av [subst [dict get $::dispCfg "DC_ARG${ai}_DEFAULT"]]
         }
-        set func "$cmd$::commandFuncSuffix"
-        set dispEntry "  \x7B \"$cmd\", $func, $cmdLen, $naMin, $naMax \x7D,"
-        set funcOpen "static int ${func}(char *$azA\[\], int $nA, ShellState *$pSS)\x7B"
+        set "arg$aix" $av
+      }
+      if {$cmd ne "?"} {
+        set rsct [dict get $::dispCfg STORAGE_CLASS]
+        set rsct "$rsct [dict get $::dispCfg RETURN_TYPE]"
+        set argexp [subst [dict get $::dispCfg ARGS_SIGNATURE]]
+        set fname [subst [dict get $::dispCfg DISPATCHEE_NAME]]
+        set funcOpen "$rsct $fname\($argexp\)\x7B"
+        set dispEntry [subst [dict get $::dispCfg DISPATCH_ENTRY]]
         emit_conditionally $cmd [linsert $body 0 $funcOpen] $ostrm
         dict set ::cmd_dispatch $cmd [list $dispEntry]
       }
     }
-  } elseif {[regexp {^\s*EMIT_HELP_TEXT\(\)} $lx]} {
+  } elseif {[regexp {^\s*EMIT_HELP_TEXT\((\d*)\)} $lx ma indent]} {
     incr iAte
     foreach htc [lsort [dict keys $::cmd_help]] {
       emit_conditionally $htc [dict get $::cmd_help $htc] $ostrm
     }
-  } elseif {[regexp {^COLLECT_DISPATCH\(\s*(\w+)\s*\)\[} $lx ma cmd]} {
+  } elseif {[regexp {^COLLECT_DISPATCH\(\s*([\w\*]+)\s*\)\[} $lx ma cmd]} {
     incr iAte
-    set disp_frag {}
     set lx [gets $hFile]
     while {![eof $hFile] && ![regexp {^\s*\];} $lx]} {
       lappend disp_frag $lx
+      set grabCmd [dict get $::dispCfg CMD_CAPTURE_RE]
+      if {![regexp $grabCmd $lx ma dcmd]} {
+        puts stderr "malformed dispatch element:\n $lx"
+        incr ::iShuffleErrors
+      } elseif {$cmd ne "*" && $dcmd ne $cmd} {
+        puts stderr "misdeclared dispatch element:\n $lx"
+        incr ::iShuffleErrors
+      } else {
+        dict set ::cmd_dispatch $dcmd [list $lx]
+      }
       set lx [gets $hFile]
       incr iAte
     }
     incr iAte
-    dict set ::cmd_dispatch $cmd $disp_frag
-  } elseif {[regexp {^\s*EMIT_DISPATCH\(\)} $lx]} {
+  } elseif {[regexp {^\s*EMIT_DISPATCH\((\d*)\)} $lx ma indent]} {
     incr iAte
     foreach cmd [lsort [dict keys $::cmd_dispatch]] {
-      emit_conditionally $cmd [dict get $::cmd_dispatch $cmd] $ostrm
+      emit_conditionally $cmd [dict get $::cmd_dispatch $cmd] $ostrm $indent
     }
   } elseif {[regexp {^CONDITION_COMMAND\(\s*(\w+)\s+([^;]+)\);} $lx ma cmd pp_expr]} {
     incr iAte
     condition_command $cmd [string trim $pp_expr]
+  } elseif {[regexp {^DISPATCH_CONFIG\[} $lx]} {
+    incr iAte
+    set def_disp {}
+    set lx [gets $hFile]
+    while {![eof $hFile] && ![regexp {^\s*\];} $lx]} {
+      lappend def_disp $lx
+      set lx [gets $hFile]
+      incr iAte
+    }
+    incr iAte
+    foreach line $def_disp {
+      if {[regexp {^\s*(\w+)=(.+)$} $line ma k v]} {
+        dict set ::dispCfg $k $v
+      }
+    }
   } else {
     puts $ostrm $lx
   }