]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Merge .testctrl safe mode blocks
authorlarrybr <larrybr@noemail.net>
Mon, 6 Dec 2021 16:22:17 +0000 (16:22 +0000)
committerlarrybr <larrybr@noemail.net>
Mon, 6 Dec 2021 16:22:17 +0000 (16:22 +0000)
FossilOrigin-Name: a42fc4ac0804673fe05a8e73aa8ea8bbdf468b6857884a716518a8e5d8db1b17

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

diff --cc manifest
index 4771a2bff73861016a217d8b13102f3c434b2b79,bb27ffe6b36584ed5853390a5cd645e50184fad5..6f44febe2378b323caad2aff7daeef3097227e1d
+++ b/manifest
@@@ -1,5 -1,5 +1,5 @@@
- C Add\sdesign\sdoc\sfor\sshell\sextensibility
- D 2021-12-04T18:40:51.887
 -C Do\snot\sallow\sSQLITE_LIMIT_LENGTH\sto\sbe\sset\slower\sthan\s1\sas\san\s\nSQLITE_LIMIT_LENGTH\sof\s0\scauses\slots\sof\sunnecessary\sproblems\sfor\nusers\sof\sthe\ssqlite3_str\sobject.
 -D 2021-12-06T15:40:24.187
++C Merge\s.testctrl\ssafe\smode\sblocks
++D 2021-12-06T16:22:17.875
  F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
  F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
  F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@@ -552,8 -551,7 +552,8 @@@ F src/random.c 097dc8b31b8fba5a9aca1697
  F src/resolve.c 4a1db4aadd802683db40ca2dbbb268187bd195f10cbdb7206dbd8ac988795571
  F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
  F src/select.c a7a3d9f54eb24821ec5f67f2e5589b68a5d42d46fc5849d7376886777d93a85a
- F src/shell.c.in d44df00f8353ade83160cf1f4229e7c9f5dbc0270d6221b8a8435fe3ba2d0d97
 -F src/shell.c.in 1458b700144c8326fda2514aaddeda49d6f01f1d1ccf7b9b696c53a3535a119c
++F src/shell.c.in 1bf703900efc4a7c1bf5bbf1876209d501344189bfd84e7c192c273cc4c150a7
 +F src/shext_linkage.h 1f95d182a0c2526be1b3566567ccfe7847cf13eedf039355a329be08282b2a8b
  F src/sqlite.h.in 5cd209ac7dc4180f0e19292846f40440b8488015849ca0110c70b906b57d68f0
  F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
  F src/sqlite3ext.h 8ff2fd2c166150b2e48639f5e506fb44e29f1a3f65031710b9e89d1c126ac839
@@@ -1935,7 -1933,7 +1935,7 @@@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a9
  F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
  F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
  F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
- P ce2a91438a3403f55cddc6c5e26db292bf6dd10e805f55416063a63986d28740
- R e58755c0a075efc772ebe9b493832561
 -P 080e72d119b836c49916201bf135445aa7d53cefe40a47437a93448c4d0f41cd
 -R c4a19d6efe230f8d42eba90fbf4c1b2a
 -U drh
 -Z 860c42735bbc5c02ee56a87604c8521a
++P 96b8ffb05497b6c44f491fbb56d5ff6580b4fea112274f9f19faf24f79727460 8fd5b8ec4ab9b5554d27f25a4638d56e347eab78b60900f24b15a815d3731330
++R 6de1a1d00e7b5c80f4dfabb42e0c657c
 +U larrybr
- Z 7adbcc5b389a42b1ea01dd812dbf6613
++Z dd702223014e0e8684ac7bbd5dffacd2
diff --cc manifest.uuid
index 1bb52128621bd2accdc52111a39436fa6eaf08d7,23a65b5f9dd3737b883a241d64fa0dd6ba5d87b8..3bd7bab73114e32ace9b5a02484abe7cdba3380a
@@@ -1,1 -1,1 +1,1 @@@
- 96b8ffb05497b6c44f491fbb56d5ff6580b4fea112274f9f19faf24f79727460
 -8fd5b8ec4ab9b5554d27f25a4638d56e347eab78b60900f24b15a815d3731330
++a42fc4ac0804673fe05a8e73aa8ea8bbdf468b6857884a716518a8e5d8db1b17
diff --cc src/shell.c.in
index ca9a581a2d969006d9dc0831c48b36a713bef718,543141c9e4f5f070695f56b34efbe11e402da67a..b28e5b5f13af9afc0dccc5724fc12a9a3060e75d
@@@ -9876,760 -9916,688 +9876,767 @@@ DISPATCHABLE_COMMAND( selftest 4 0 0 )
          {
            utf8_printf(stderr, "Unknown option \"%s\" on \"%s\"\n",
                        azArg[i], azArg[0]);
 -          showHelp(p->out, azArg[0]);
 -          rc = 1;
 -          goto meta_command_exit;
 +          raw_printf(stderr, "Should be one of: --init -v\n");
 +          return 1;
          }
 -      }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( zLike && sqlite3_strlike(zLike, zTab, 0)!=0 ) continue;
 -      if( strncmp(zTab, "sqlite_",7)!=0 ){
 -        appendText(&sQuery,"SELECT * FROM ", 0);
 -        appendText(&sQuery,zTab,'"');
 -        appendText(&sQuery," NOT INDEXED;", 0);
 -      }else if( strcmp(zTab, "sqlite_schema")==0 ){
 -        appendText(&sQuery,"SELECT type,name,tbl_name,sql FROM sqlite_schema"
 -                           " ORDER BY name;", 0);
 -      }else if( strcmp(zTab, "sqlite_sequence")==0 ){
 -        appendText(&sQuery,"SELECT name,seq FROM sqlite_sequence"
 -                           " ORDER BY name;", 0);
 -      }else if( strcmp(zTab, "sqlite_stat1")==0 ){
 -        appendText(&sQuery,"SELECT tbl,idx,stat FROM sqlite_stat1"
 -                           " ORDER BY tbl,idx;", 0);
 -      }else if( strcmp(zTab, "sqlite_stat4")==0 ){
 -        appendText(&sQuery, "SELECT * FROM ", 0);
 -        appendText(&sQuery, zTab, 0);
 -        appendText(&sQuery, " ORDER BY tbl, idx, rowid;\n", 0);
 -      }
 -      appendText(&sSql, zSep, 0);
 -      appendText(&sSql, sQuery.z, '\'');
 -      sQuery.n = 0;
 -      appendText(&sSql, ",", 0);
 -      appendText(&sSql, zTab, '\'');
 -      zSep = "),(";
 -    }
 -    sqlite3_finalize(pStmt);
 -    if( bSeparate ){
 -      zSql = sqlite3_mprintf(
 -          "%s))"
 -          " SELECT lower(hex(sha3_query(a,%d))) AS hash, b AS label"
 -          "   FROM [sha3sum$query]",
 -          sSql.z, iSize);
 -    }else{
 -      zSql = sqlite3_mprintf(
 -          "%s))"
 -          " SELECT lower(hex(sha3_query(group_concat(a,''),%d))) AS hash"
 -          "   FROM [sha3sum$query]",
 -          sSql.z, iSize);
 -    }
 -    freeText(&sQuery);
 -    freeText(&sSql);
 -    if( bDebug ){
 -      utf8_printf(p->out, "%s\n", zSql);
 +  }
 +  if( sqlite3_table_column_metadata(p->db,"main","selftest",0,0,0,0,0,0)
 +      != SQLITE_OK ){
 +    bSelftestExists = 0;
 +  }else{
 +    bSelftestExists = 1;
 +  }
 +  if( bIsInit ){
 +    createSelftestTable(p);
 +    bSelftestExists = 1;
 +  }
 +  initText(&str);
 +  appendText(&str, "x", 0);
 +  for(k=bSelftestExists; k>=0; k--){
 +    if( k==1 ){
 +      rc = sqlite3_prepare_v2(p->db,
 +              "SELECT tno,op,cmd,ans FROM selftest ORDER BY tno",
 +              -1, &pStmt, 0);
      }else{
 -      shell_exec(p, zSql, 0);
 -    }
 -    sqlite3_free(zSql);
 -  }else
 -
 -#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; i++){
 -      zCmd = sqlite3_mprintf(strchr(azArg[i],' ')==0?"%z %s":"%z \"%s\"",
 -                             zCmd, azArg[i]);
 -    }
 -    x = system(zCmd);
 -    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]);
 -    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;
 +      rc = sqlite3_prepare_v2(p->db,
 +              "VALUES(0,'memo','Missing SELFTEST table - default checks only',''),"
 +              "      (1,'run','PRAGMA integrity_check','ok')",
 +              -1, &pStmt, 0);
      }
 -    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]);
 +    if( rc ){
 +      raw_printf(stderr, "Error querying the selftest table\n");
 +      sqlite3_finalize(pStmt);
 +      return 1;
      }
 -    raw_printf(p->out, "\n");
 -    utf8_printf(p->out, "%12.12s: %s\n", "filename",
 -                p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : "");
 -  }else
 +    for(i=1; sqlite3_step(pStmt)==SQLITE_ROW; i++){
 +      int tno = sqlite3_column_int(pStmt, 0);
 +      const char *zOp = (const char*)sqlite3_column_text(pStmt, 1);
 +      const char *zSql = (const char*)sqlite3_column_text(pStmt, 2);
 +      const char *zAns = (const char*)sqlite3_column_text(pStmt, 3);
  
 -  if( 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]);
 +      k = 0;
 +      if( bVerbose>0 ){
 +        char *zQuote = sqlite3_mprintf("%q", zSql);
 +        printf("%d: %s %s\n", tno, zOp, zSql);
 +        sqlite3_free(zQuote);
        }
 -    }else if( nArg==1 ){
 -      display_stats(p->db, p, 0);
 +      if( strcmp(zOp,"memo")==0 ){
 +        utf8_printf(p->out, "%s\n", zSql);
 +      }else
 +        if( strcmp(zOp,"run")==0 ){
 +          char *zErrMsg = 0;
 +          str.n = 0;
 +          str.z[0] = 0;
 +          rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg);
 +          nTest++;
 +          if( bVerbose ){
 +            utf8_printf(p->out, "Result: %s\n", str.z);
 +          }
 +          if( rc || zErrMsg ){
 +            nErr++;
 +            rc = 1;
 +            utf8_printf(p->out, "%d: error-code-%d: %s\n", tno, rc, zErrMsg);
 +            sqlite3_free(zErrMsg);
 +          }else if( strcmp(zAns,str.z)!=0 ){
 +            nErr++;
 +            rc = 1;
 +            utf8_printf(p->out, "%d: Expected: [%s]\n", tno, zAns);
 +            utf8_printf(p->out, "%d:      Got: [%s]\n", tno, str.z);
 +          }
 +        }else
 +          {
 +            utf8_printf(stderr,
 +                        "Unknown operation \"%s\" on selftest line %d\n", zOp, tno);
 +            rc = 1;
 +            break;
 +          }
 +    } /* 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);
 +  return rc > 0;
 +}
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +static int shellOut(char *azArg[], int nArg, ShellState *p){
 +  char *zCmd;
 +  int i, x;
 +  failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
 +  zCmd = sqlite3_mprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]);
 +  for(i=2; i<nArg; i++){
 +    zCmd = sqlite3_mprintf(strchr(azArg[i],' ')==0?"%z %s":"%z \"%s\"",
 +                           zCmd, azArg[i]);
 +  }
 +  x = system(zCmd);
 +  sqlite3_free(zCmd);
 +  if( x ) raw_printf(stderr, "%s command returns %d\n", azArg[0], x);
 +  return 0;
 +}
 +#endif
 +DISPATCHABLE_COMMAND( shell ? 2 0 ){
 +  return shellOut(azArg, nArg, p);  
 +}
 +DISPATCHABLE_COMMAND( system ? 2 0 ){
 +  return shellOut(azArg, nArg, p);  
 +}
 +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]);
 +  utf8_printf(p->out, "%12.12s: ", "nullvalue");
 +  output_c_string(p->out, p->nullValue);
 +  raw_printf(p->out, "\n");
 +  utf8_printf(p->out,"%12.12s: %s\n","output",
 +              strlen30(p->outfile) ? p->outfile : "stdout");
 +  utf8_printf(p->out,"%12.12s: ", "colseparator");
 +  output_c_string(p->out, p->colSeparator);
 +  raw_printf(p->out, "\n");
 +  utf8_printf(p->out,"%12.12s: ", "rowseparator");
 +  output_c_string(p->out, p->rowSeparator);
 +  raw_printf(p->out, "\n");
 +  switch( p->statsOn ){
 +  case 0:  zOut = "off";     break;
 +  default: zOut = "on";      break;
 +  case 2:  zOut = "stmt";    break;
 +  case 3:  zOut = "vmstep";  break;
 +  }
 +  utf8_printf(p->out, "%12.12s: %s\n","stats", zOut);
 +  utf8_printf(p->out, "%12.12s: ", "width");
 +  for (i=0;i<p->nWidth;i++) {
 +    raw_printf(p->out, "%d ", p->colWidth[i]);
 +  }
 +  raw_printf(p->out, "\n");
 +  utf8_printf(p->out, "%12.12s: %s\n", "filename",
 +              p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : "");
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( stats ? 0 0 ){
 +  if( nArg==2 ){
 +    if( strcmp(azArg[1],"stmt")==0 ){
 +      p->statsOn = 2;
 +    }else if( strcmp(azArg[1],"vmstep")==0 ){
 +      p->statsOn = 3;
      }else{
 -      raw_printf(stderr, "Usage: .stats ?on|off|stmt|vmstep?\n");
 -      rc = 1;
 +      p->statsOn = (u8)booleanValue(azArg[1]);
      }
 -  }else
 +  }else if( nArg==1 ){
 +    display_stats(p->db, p, 0);
 +  }else{
 +    raw_printf(stderr, "Usage: .stats ?on|off|stmt|vmstep?\n");
 +    return 1;
 +  }
 +  return 0;
 +}
  
 -  if( (c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0)
 -   || (c=='i' && (strncmp(azArg[0], "indices", n)==0
 -                 || strncmp(azArg[0], "indexes", n)==0) )
 -  ){
 -    sqlite3_stmt *pStmt;
 -    char **azResult;
 -    int nRow, nAlloc;
 -    int ii;
 -    ShellText s;
 -    initText(&s);
 -    open_db(p, 0);
 -    rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
 -    if( rc ){
 -      sqlite3_finalize(pStmt);
 -      return shellDatabaseError(p->db);
 -    }
 +/*****************
 + * The .tables, .views, .indices and .indexes command
 + * These are together because they share implementation or are aliases.
 + */
 +COLLECT_HELP_TEXT[
 +  ".indexes ?TABLE?         Show names of indexes",
 +  "                           If TABLE is specified, only show indexes for",
 +  "                           tables matching TABLE using the LIKE operator.",
 +];
 +static int showTableLike(char *azArg[], int nArg, ShellState *p, char ot){
 +  int rc;
 +  sqlite3_stmt *pStmt;
 +  char **azResult;
 +  int nRow, nAlloc;
 +  int ii;
 +  ShellText s;
 +  initText(&s);
 +  open_db(p, 0);
 +  rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
 +  if( rc ){
 +    sqlite3_finalize(pStmt);
 +    return shellDatabaseError(p->db);
 +  }
  
 -    if( nArg>2 && c=='i' ){
 -      /* It is an historical accident that the .indexes command shows an error
 -      ** when called with the wrong number of arguments whereas the .tables
 -      ** command does not. */
 -      raw_printf(stderr, "Usage: .indexes ?LIKE-PATTERN?\n");
 -      rc = 1;
 -      sqlite3_finalize(pStmt);
 -      goto meta_command_exit;
 -    }
 -    for(ii=0; sqlite3_step(pStmt)==SQLITE_ROW; ii++){
 -      const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1);
 -      if( zDbName==0 ) continue;
 -      if( s.z && s.z[0] ) appendText(&s, " UNION ALL ", 0);
 -      if( sqlite3_stricmp(zDbName, "main")==0 ){
 -        appendText(&s, "SELECT name FROM ", 0);
 -      }else{
 -        appendText(&s, "SELECT ", 0);
 -        appendText(&s, zDbName, '\'');
 -        appendText(&s, "||'.'||name FROM ", 0);
 -      }
 -      appendText(&s, zDbName, '"');
 -      appendText(&s, ".sqlite_schema ", 0);
 -      if( c=='t' ){
 -        appendText(&s," WHERE type IN ('table','view')"
 -                      "   AND name NOT LIKE 'sqlite_%'"
 -                      "   AND name LIKE ?1", 0);
 -      }else{
 -        appendText(&s," WHERE type='index'"
 -                      "   AND tbl_name LIKE ?1", 0);
 -      }
 -    }
 -    rc = sqlite3_finalize(pStmt);
 -    if( rc==SQLITE_OK ){
 -      appendText(&s, " ORDER BY 1", 0);
 -      rc = sqlite3_prepare_v2(p->db, s.z, -1, &pStmt, 0);
 -    }
 -    freeText(&s);
 -    if( rc ) return shellDatabaseError(p->db);
 -
 -    /* Run the SQL statement prepared by the above block. Store the results
 -    ** as an array of nul-terminated strings in azResult[].  */
 -    nRow = nAlloc = 0;
 -    azResult = 0;
 -    if( nArg>1 ){
 -      sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT);
 +  if( nArg>2 && ot=='i' ){
 +    /* It is an historical accident that the .indexes command shows an error
 +    ** when called with the wrong number of arguments whereas the .tables
 +    ** command does not. */
 +    raw_printf(stderr, "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);
 -        if( azNew==0 ) shell_out_of_memory();
 -        nAlloc = n2;
 -        azResult = azNew;
 -      }
 -      azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 -      if( 0==azResult[nRow] ) shell_out_of_memory();
 -      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);
 +      if( azNew==0 ) shell_out_of_memory();
 +      nAlloc = n2;
 +      azResult = azNew;
 +    }
 +    azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 +    if( 0==azResult[nRow] ) shell_out_of_memory();
 +    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 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, objType);
 +}
 +DISPATCHABLE_COMMAND( indexes 3 1 2 ){
 +  return showTableLike(azArg, nArg, p, 'i');
 +}
 +DISPATCHABLE_COMMAND( indices 3 1 2 ){
 +  return showTableLike(azArg, nArg, p, '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"   },
 -#ifdef YYCOVERAGE
 -      { "parser_coverage",    SQLITE_TESTCTRL_PARSER_COVERAGE,0,""             },
 +/*****************
 + * The .testcase, .testctrl, .timeout, .timer and .trace commands
 + */
 +CONDITION_COMMAND( testctrl !defined(SQLITE_UNTESTABLE) );
 +CONDITION_COMMAND( trace !defined(SQLITE_OMIT_TRACE) );
 +COLLECT_HELP_TEXT[
 +  ".testcase NAME           Begin redirecting output to 'testcase-out.txt'",
 +  ".testctrl CMD ...        Run various sqlite3_test_control() operations",
 +  "                            Run \".testctrl\" with no arguments for details",
 +  ".timeout MS              Try opening locked tables for MS milliseconds",
 +  ".timer on|off            Turn SQL timer on or off",
 +  ".trace ?OPTIONS?         Output each SQL statement as it is run",
 +  "    FILE                    Send output to FILE",
 +  "    stdout                  Send output to stdout",
 +  "    stderr                  Send output to stderr",
 +  "    off                     Disable tracing",
 +  "    --expanded              Expand query parameters",
 +#ifdef SQLITE_ENABLE_NORMALIZE
 +  "    --normalized            Normal the SQL statements",
  #endif
 -      { "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;
 +  "    --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)",
 +];
  
 -    open_db(p, 0);
 -    zCmd = nArg>=2 ? azArg[1] : "help";
 +/* 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(stderr, "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,        "BOOLEAN"        },
-     { "assert",             SQLITE_TESTCTRL_ASSERT,        "BOOLEAN"        },
-     /*{ "benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS, ""       },*/
-     /*{ "bitvec_test",        SQLITE_TESTCTRL_BITVEC_TEST,   ""             },*/
-     { "byteorder",          SQLITE_TESTCTRL_BYTEORDER,     ""               },
-     { "extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,"BOOLEAN"   },
-     /*{ "fault_install",      SQLITE_TESTCTRL_FAULT_INSTALL, ""             },*/
-     { "imposter",         SQLITE_TESTCTRL_IMPOSTER, "SCHEMA ON/OFF ROOTPAGE"},
-     { "internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS, "" },
-     { "localtime_fault",    SQLITE_TESTCTRL_LOCALTIME_FAULT,"BOOLEAN"       },
-     { "never_corrupt",      SQLITE_TESTCTRL_NEVER_CORRUPT, "BOOLEAN"        },
-     { "optimizations",      SQLITE_TESTCTRL_OPTIMIZATIONS, "DISABLE-MASK"   },
++    { "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, ""             },
++    { "parser_coverage",    SQLITE_TESTCTRL_PARSER_COVERAGE,0,""             },
 +#endif
-     { "pending_byte",       SQLITE_TESTCTRL_PENDING_BYTE,  "OFFSET  "       },
-     { "prng_restore",       SQLITE_TESTCTRL_PRNG_RESTORE,  ""               },
-     { "prng_save",          SQLITE_TESTCTRL_PRNG_SAVE,     ""               },
-     { "prng_seed",          SQLITE_TESTCTRL_PRNG_SEED,     "SEED ?db?"      },
-     { "seek_count",         SQLITE_TESTCTRL_SEEK_COUNT,    ""               },
-     { "tune",               SQLITE_TESTCTRL_TUNE,          "ID VALUE"       },
++    { "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;
  
 -    /* The argument can optionally begin with "-" or "--" */
 -    if( zCmd[0]=='-' && zCmd[1] ){
 -      zCmd++;
 -      if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 -    }
 +  open_db(p, 0);
 +  zCmd = nArg>=2 ? azArg[1] : "help";
  
 -    /* --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);
 -      }
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 +  /* The argument can optionally begin with "-" or "--" */
 +  if( zCmd[0]=='-' && zCmd[1] ){
 +    zCmd++;
 +    if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 +  }
  
 -    /* convert testctrl text option to value. allow any unique prefix
 -    ** of the option name, or a numerical value. */
 -    n2 = strlen30(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++){
 -      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;
 -        }
 -      }
 +      utf8_printf(p->out, "  .testctrl %s %s\n",
 +                  aCtrl[i].zCtrlName, aCtrl[i].zUsage);
      }
 -    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;
 +    return 1;
 +  }
  
 -        /* 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;
 +  /* 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);
 +        return 1;
 +      }
 +    }
 +  }
 +  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, 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, 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;
  
 -        /* 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) */
 +    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, 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, 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) */
 -        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, 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(sqlite3*) */
 -        case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS:
 -          rc2 = sqlite3_test_control(testctrl, p->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;
  
 -        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;
 +      /* 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;
  
 -        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;
 -        }
 +      /* sqlite3_test_control(sqlite3*) */
 +    case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS:
 +      rc2 = sqlite3_test_control(testctrl, p->db);
 +      isOk = 3;
 +      break;
  
 -#ifdef YYCOVERAGE
 -        case SQLITE_TESTCTRL_PARSER_COVERAGE: {
 -          if( nArg==2 ){
 -            sqlite3_test_control(testctrl, p->out);
 -            isOk = 3;
 -          }
 -          break;
 -        }
 -#endif
 -#ifdef SQLITE_DEBUG
 -        case SQLITE_TESTCTRL_TUNE: {
 -          if( nArg==4 ){
 -            int id = (int)integerValue(azArg[2]);
 -            int val = (int)integerValue(azArg[3]);
 -            sqlite3_test_control(testctrl, id, &val);
 -            isOk = 3;
 -          }else if( nArg==3 ){
 -            int id = (int)integerValue(azArg[2]);
 -            sqlite3_test_control(testctrl, -id, &rc2);
 -            isOk = 1;
 -          }else if( nArg==2 ){
 -            int id = 1;
 -            while(1){
 -              int val = 0;
 -              rc2 = sqlite3_test_control(testctrl, -id, &val);
 -              if( rc2!=SQLITE_OK ) break;
 -              if( id>1 ) utf8_printf(p->out, "  ");
 -              utf8_printf(p->out, "%d: %d", id, val);
 -              id++;
 -            }
 -            if( id>1 ) utf8_printf(p->out, "\n");
 -            isOk = 3;
 -          }
 -          break;
 -        }
 -#endif
 -        case SQLITE_TESTCTRL_SORTER_MMAP:
 -          if( nArg==3 ){
 -            int opt = (unsigned int)integerValue(azArg[2]);
 -            rc2 = sqlite3_test_control(testctrl, p->db, opt);
 -            isOk = 3;
 -          }
 -          break;
 +    case SQLITE_TESTCTRL_IMPOSTER:
 +      if( nArg==5 ){
 +        rc2 = sqlite3_test_control(testctrl, p->db,
 +                                   azArg[2],
 +                                   integerValue(azArg[3]),
 +                                   integerValue(azArg[4]));
 +        isOk = 3;
        }
 -    }
 -    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);
 -    }
 -  }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
 +      break;
  
 -  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;
 -      }
 -    }else{
 -      raw_printf(stderr, "Usage: .timer on|off\n");
 -      rc = 1;
 +    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;
      }
 -  }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;
 -        }
 -#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);
 +#ifdef YYCOVERAGE
 +    case SQLITE_TESTCTRL_PARSER_COVERAGE: {
 +      if( nArg==2 ){
 +        sqlite3_test_control(testctrl, p->out);
 +        isOk = 3;
        }
 +      break;
      }
 -    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);
 -    }
 -  }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;
 -    }
 -    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);
 +#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;
      }
 -  }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;
      }
 -    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;
 +  }
 +  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 2 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(stderr, "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;
        }
 -      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;
 +#ifdef SQLITE_ENABLE_NORMALIZE
 +      else if( optionMatch(z, "normalized") ){
 +        p->eTraceType = SHELL_TRACE_NORMALIZED;
        }
 -    }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;
 +#endif
 +      else if( optionMatch(z, "plain") ){
 +        p->eTraceType = SHELL_TRACE_PLAIN;
        }
 -      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( optionMatch(z, "profile") ){
 +        mType |= SQLITE_TRACE_PROFILE;
        }
 -    }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;
 +      else if( optionMatch(z, "row") ){
 +        mType |= SQLITE_TRACE_ROW;
        }
 -      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( optionMatch(z, "stmt") ){
 +        mType |= SQLITE_TRACE_STMT;
        }
 -    }else if( strcmp(azArg[1],"delete")==0 ){
 -      if( nArg!=3 ){
 -        raw_printf(stderr, "Usage: .user delete USER\n");
 -        rc = 1;
 -        goto meta_command_exit;
 +      else if( optionMatch(z, "close") ){
 +        mType |= SQLITE_TRACE_CLOSE;
        }
 -      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, "Unknown option \"%s\" on \".trace\"\n", z);
 +        return 1;
        }
      }else{
 -      raw_printf(stderr, "Usage: .user login|add|edit|delete ...\n");
 -      rc = 1;
 -      goto meta_command_exit;
 +      output_file_close(p->traceOut);
 +      p->traceOut = output_file_open(azArg[1], 0);
      }
 -  }else
 -#endif /* SQLITE_USER_AUTHENTICATION */
 +  }
 +  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:
 +    raw_printf(stderr, usage);
 +    return 1;
 +  }
 +  open_db(p, 0);
 +  if( strcmp(azArg[1],"login")==0 ){
 +    if( nArg!=4 ){
 +      raw_printf(stderr, usage);
 +      return 1;
 +    }
 +    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]);
 +      return 1;
 +    }
 +  }else if( strcmp(azArg[1],"add")==0 ){
 +    if( nArg!=5 ){
 +      goto teach_fail;
 +    }
 +    rc = sqlite3_user_add(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
 +                          booleanValue(azArg[4]));
 +    if( rc ){
 +      raw_printf(stderr, "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 ){
 +      raw_printf(stderr, "User-Edit failed: %d\n", rc);
 +      return 1;
 +    }
 +  }else if( strcmp(azArg[1],"delete")==0 ){
 +    if( nArg!=3 ){
 +      goto teach_fail;
 +    }
 +    rc = sqlite3_user_delete(p->db, azArg[2]);
 +    if( rc ){
 +      raw_printf(stderr, "User-Delete failed: %d\n", rc);
 +      return 1;
 +    }
 +  }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)