]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Get MSVC build going for sqlite3x.exe and extensions.
authorlarrybr <larrybr@noemail.net>
Fri, 29 Apr 2022 18:36:17 +0000 (18:36 +0000)
committerlarrybr <larrybr@noemail.net>
Fri, 29 Apr 2022 18:36:17 +0000 (18:36 +0000)
FossilOrigin-Name: cd5e57e0fe4ef2a59c1202b66f4e774f6642fa826cfe3840a786104841dde2f6

1  2 
Makefile.msc
ext/misc/tclshext.c.in
manifest
manifest.uuid
src/shell.c.in
src/test_shellext_c.c
src/test_shellext_cpp.cpp

diff --cc Makefile.msc
index 8215677a218ec3f0482a4f7fce4c32197fa95a38,d67b7f0b864c0f9a6ad50cc2ad815c1d849cf883..32c758ae2776594e89d9a48222c3b69b181cc9c1
@@@ -1671,6 -1670,6 +1671,11 @@@ TESTPROGS = 
    dbhash.exe \
    sqltclsh.exe
  
++TESTPROGS_SHX = \
++  testfixture.exe \
++  $(SQLITE3XEXE) \
++  shell_extensions
++
  # Databases containing fuzzer test cases
  #
  FUZZDATA = \
@@@ -2385,6 -2377,6 +2390,36 @@@ fts5.dll:      fts5_ext.l
  sqlite3rbu.lo:        $(TOP)\ext\rbu\sqlite3rbu.c $(HDR) $(EXTHDR)
        $(LTCOMPILE) -DSQLITE_CORE -c $(TOP)\ext\rbu\sqlite3rbu.c
  
++# Rule to build shx_link.h (for use by shell extensions)
++
++SHX_LINK_SRC = \
++ $(TOP)/src/shext_linkage.h \
++ $(TOP)/src/obj_interfaces.h \
++ sqlite3ext.h \
++ sqlite3.h
++
++shx_link.h:   $(SHX_LINK_SRC)
++      $(TCLSH_CMD) $(TOP)\tool\mkshellc.tcl -short-head -header-gen \
++     $(TOP)/src/shext_linkage.h > $@
++
++# Rules to build shell extensions used for testing extensible shell
++
++shell_extensions:     tcl_shell_extension \
++  test_shellext_cpp.dll test_shellext_c.dll
++
++test_shellext_cpp.dll:        $(TOP)/src/test_shellext_cpp.cpp $(TOP)/shx_link.h
++      $(TCC) $(TOP)/src/test_shellext_cpp.cpp -LD -Fe$@
++
++test_shellext_c.dll:  $(TOP)/src/test_shellext_c.c $(TOP)/shx_link.h
++      $(TCC) $(TOP)/src/test_shellext_c.c -LD -Fe$@
++
++tclshext.c:   $(TOP)/ext/misc/tclshext.c.in $(TOP)/src/tclsqlite.c
++      $(TCLSH_CMD) $(TOP)\tool\mkshellc.tcl $(TOP)/ext/misc/tclshext.c.in > $@
++
++tclshext.dll: tclshext.c shx_link.h
++      $(TCC) -I$(TCLINCDIR) tclshext.c -Fe$@ -link -DLL \
++  -LIBPATH:$(TCLLIBDIR) $(TCLLIBS)
++
  # Rules to build the 'testfixture' application.
  #
  # If using the amalgamation, use sqlite3.c directly to build the test
index 227ed247e10b42b1b916a75204ebcd132239b2f3,0000000000000000000000000000000000000000..86293d6ad1ae8841e370f4a3bcfe8bd933763a9e
mode 100644,000000..100644
--- /dev/null
@@@ -1,1305 -1,0 +1,1305 @@@
- # define getDir(cArray) _getwd(cArray)
 +/*
 +** 2022 March 20
 +**
 +** The author disclaims copyright to this source code.  In place of
 +** a legal notice, here is a blessing:
 +**
 +**    May you do good and not evil.
 +**    May you find forgiveness for yourself and forgive others.
 +**    May you share freely, never taking more than you give.
 +**
 +*************************************************************************
 +** This file contains code to implement the "tclshext" shell extension
 +** for use with the extensible "sqlite3" CLI shell. On *Nix, build thusly:
 +     tool/mkshellc.tcl ext/misc/tclshext.c.in > tclshext.c
 +     gcc -shared -fPIC -O2 -I. -Isrc -I/usr/include/tcl8.6 \
 +       tclshext.c -o tclshext.so -ltcl8.6
 +** Or, (after ./configure ...), use the provided Makefile thusly:
 +     make tcl_shell_extension
 +** If the Tk library is available, it can be linked and used thusly:
 +     gcc -shared -fPIC -O2 -I. -Isrc -I/usr/include/tcl8.6 \
 +       -DSHELL_ENABLE_TK tclshext.c -o tclshext.so -ltcl8.6 -ltk8.6
 +** Or, make the same target with Tk thusly:
 +     make tcl_shell_extension WITH_TK=1
 +** Later TCL versions can be used if desired (and installed.)
 +**
 +** TCL scripting support is added with a registerScripting() call (in the
 +** ShellExtensionAPI), meeting ScriptingSupport interface requirements.
 +*/
 +static const char * const zTclHelp =
 +  "This extension adds these features to the host shell:\n"
 +  " 1. TCL scripting support is added.\n"
 +  " 2. TCL commands are added: udb shdb now_interactive get_tcl_group ..\n"
 +  " 3. The .tcl and .unknown dot commands are added.\n"
 +  " 4. If built with Tk capability, the gui TCL command will be added if this\n"
 +  "  extension was loaded using shell command: .load tclshext -shext -tk .\n"
 +  "  Any other arguments beyond -shext are copied into TCL's argv variable.\n"
 +  "Operation:\n"
 +  " Shell input groups beginning with \"..\" are treated as TCL input, in\n"
 +  " these ways: (1) When a bare \"..\" is entered, a TCL REPL loop is run\n"
 +  " until the end of input is seen; (2) When \"..D ...\" is entered, (where\n"
 +  " \"D\" is a dot-command name), the D dot command will be run in its normal\n"
 +  " fashion, but its arguments will be collected according to TCL parsing\n"
 +  " rules then expanded as usual for TCL commands; and (3) when \".. T ...\"\n"
 +  " is entered, (where \"T\" is a TCL command name), that TCL command and its\n"
 +  " arguments will be collected and expanded according to TCL parsing rules,\n"
 +  " then run in the TCL execution environment (in its global namespace), but\n"
 +  " the shell REPL and execution environment remains in effect afterward.\n"
 +  "\n"
 +  " Note that cases 2 and 3 differ in having space after the leading \"..\".\n"
 +  "\n"
 +  " The phrase \"end of input\" means either: end-of-file is seen on a file,\n"
 +  " pipe or string stream input, or a lone \".\" on the first and only line\n"
 +  " of an input line group is seen. This convention is useful in scripting\n"
 +  " when it is expedient to switch execution environments from within the\n"
 +  " same input stream. This could be input piped in from another process.\n"
 +  "\n"
 +  ;
 +/*
 +** For example:
 +     # From shell, enter TCL REPL:
 +     ..
 +     # Initialize some variables and insert into the DB:
 +     set var1 [compute_however ...]
 +     set var2 [derive_somehow ...]
 +     udb eval { INSERT INTO SomeTable(col1, col2) VALUES($var1, var2) }
 +     # Leave REPL
 +     .
 +     # Generate and keep pretty output:
 +     .mode box -ww
 +     .header on
 +     .once prettified.txt
 +     SELECT * FROM SomeTable;
 +     # Alternatively, the query can be run from the TCL environment:
 +     ..
 +     set tstamp [clock format [clock seconds] -format %Y-%m-%d]
 +     .once "backup of prettified.txt made $tstamp"
 +     .eval {SELECT col1, col2 FROM SomeTable}
 +     # Return to shell environment:
 +     .
 +**
 +** For any of these ways of providing TCL input, the same TCL interpreter
 +** is used, with its state maintained from one input to the next. In this
 +** way, .sqliterc or other preparatory shell scripts (or typing) can be
 +** made to provide useful, user-defined shell enhancements or specialized
 +** procedures (aka "TCL commands") for oft-repeated tasks.
 +**
 +** The added TCL commands are:
 +**   udb shdb ; # exposes the user DB and shell DB for access via TCL
 +**   now_interactive ; # indicates whether current input is interactive
 +**   get_tcl_group ; # gets one TCL input line group from current input
 +**   register_adhoc_command ; # aids creation of dot commands with help
 +**   .. ; # does nothing, silently and without error
 +**
 +** The .. command exists so that a lone ".." on an input line suffices
 +** to ensure the TCL REPL is running. This is symmetric with a lone "."
 +** input to the TCL REPL because it either terminates the loop or, if
 +** entered in the shell environment, quietly does nothing without error.
 +**
 +** The added .tcl dot command may be used to enter a TCL REPL, or with
 +** arguments, it will read files as TCL. (This is somewhat extraneous,
 +** as the same can be done with TCL commands, but it is more easily done
 +** from the shell invocation, and the .tcl command's integration into
 +** the .help facility provides a way for users to get help for "..".)
 +**
 +** The added .unknown dot command overrides the shell's .unknown so
 +** that new dot commands can be implemented in TCL and then be run
 +** from the shell in the dot command execution context.
 +*/
 +
 +#include "shx_link.h"
 +
 +/* Extension boiler-plate to dynamically link into host's SQLite library */
 +SQLITE_EXTENSION_INIT1;
 +
 +/* Extension boiler-plate for a function to get ShellExtensionLink pointer
 + * from db passed to extension init() and define a pair of static API refs.
 + */
 +SHELL_EXTENSION_INIT1(pShExtApi, pExtHelpers, shextLinkFetcher);
 +#define SHX_API(entry) pShExtApi->entry
 +#define SHX_HELPER(entry) pExtHelpers->entry
 +
 +/* This is not found in the API pointer table published for extensions: */
 +#define sqlite3_enable_load_extension SHX_HELPER(enable_load_extension)
 +
 +/* Forward reference for use as ExtensionId */
 +#ifdef _WIN32
 +__declspec(dllexport)
 +#endif
 +int sqlite3_tclshext_init(sqlite3*, char**, const sqlite3_api_routines*);
 +
 +/* Control how tclsqlite.c compiles. (REPL is effected here, not there.) */
 +#define STATIC_BUILD /* Not publishing TCL API */
 +#undef SQLITE_AMALGAMATION
 +#undef TCLSH
 +#include "tclOO.h"
 +#ifdef SHELL_ENABLE_TK
 +#include "tk.h" /* Only used if option -tk passed during load. */
 +#endif
 +INCLUDE tclsqlite.c
 +
 +#if defined(_WIN32) || defined(WIN32)
-     int rc, nc = strlen30(zScript);
++# include <direct.h>
++# define getDir(cArray) _getcwd(cArray, sizeof(cArray))
 +# define chdir(s) _chdir(s)
 +#else
 +# define getDir(cArray) getcwd(cArray, sizeof(cArray))
 +#endif
 +
 +typedef struct TclCmd TclCmd;
 +typedef struct UnkCmd UnkCmd;
 +
 +static struct InterpManage {
 +  Tcl_Interp *pInterp;
 +  int nRefs;
 +} interpKeep = { 0, 0 };
 +
 +static Tcl_Interp *getInterp(){
 +  assert(interpKeep.nRefs>0 && interpKeep.pInterp!=0);
 +  return interpKeep.pInterp;
 +}
 +
 +static void Tcl_TakeDown(void *pv){
 +  assert(pv==&interpKeep);
 +  if( --interpKeep.nRefs==0 ){
 +    if( interpKeep.pInterp ){
 +      Tcl_DeleteInterp(interpKeep.pInterp);
 +      Tcl_Release(interpKeep.pInterp);
 +      interpKeep.pInterp = 0;
 +      Tcl_Finalize();
 +    }
 +  }
 +}
 +
 +static int Tcl_BringUp(
 +#ifdef SHELL_ENABLE_TK
 +                       int *pWithTk,
 +#endif
 +                       char **pzErrMsg){
 +  if( ++interpKeep.nRefs==1 ){
 +    const char *zShellName = SHX_HELPER(shellInvokedAs)();
 +    const char *zShellDir = SHX_HELPER(shellStartupDir)();
 +    if( zShellDir!=0 ){
 +      char cwd[FILENAME_MAX+1];
 +      if( getDir(cwd) && 0==chdir(zShellDir) ){
 +        int rc;
 +        Tcl_FindExecutable(zShellName);
 +        rc = chdir(cwd); /* result ignored, kept only to silence gcc */
 +      }
 +    }
 +    interpKeep.pInterp = Tcl_CreateInterp();
 +    Tcl_SetSystemEncoding(interpKeep.pInterp, "utf-8");
 +    Sqlite3_Init(interpKeep.pInterp);
 +    Tcl_Preserve(interpKeep.pInterp);
 +    if( 0==Tcl_OOInitStubs(interpKeep.pInterp) ){
 +      *pzErrMsg = sqlite3_mprintf("Tcl v8.6 or higher required.\n");
 +      Tcl_TakeDown(&interpKeep);
 +      return SQLITE_ERROR;
 +    }
 +    if( Tcl_Init(interpKeep.pInterp)!=TCL_OK ){
 +      *pzErrMsg = sqlite3_mprintf("Tcl interpreter startup failed.\n");
 +      Tcl_TakeDown(&interpKeep);
 +      return SQLITE_ERROR;
 +    }
 +#ifdef SHELL_ENABLE_TK
 +    else if( *pWithTk ){
 +      if( TCL_OK!=Tk_Init(interpKeep.pInterp) ){
 +        fprintf(stderr, "Could not load/initialize Tk."
 +                " (Non-fatal, extension is loaded.)\n");
 +        *pWithTk = 0;
 +      }
 +    }
 +#endif
 +  }
 +  return (interpKeep.pInterp!=0)? SQLITE_OK : SQLITE_ERROR;
 +}
 +
 +static void copy_complaint(char **pzErr, Tcl_Interp *pi);
 +static DotCmdRC runTclREPL(Tcl_Interp *interp, char **pzErrMsg);
 +
 +/* Following DERIVED_METHOD(...) macro calls' arguments were copied and
 + * pasted from the respective interface declarations in shext_linkage.h
 + */
 +
 +/* This is in the interface for anouncing what was just provided. */
 +DERIVED_METHOD(const char *, name, ScriptSupport,TclSS, 0,()){
 +  (void)(pThis);
 +  return "TclTk";
 +}
 +
 +/* Provide help for users of this scripting implementation. */
 +DERIVED_METHOD(const char *, help, ScriptSupport,TclSS, 1,(const char *zHK)){
 +  (void)(pThis);
 +  if( zHK==0 ){
 +    return "Provides TCL scripting support for SQLite extensible shell.\n";
 +  }else if( *zHK==0 ) return zTclHelp;
 +  return 0;
 +}
 +
 +/* Not doing this yet. */
 +DERIVED_METHOD(int,  configure, ScriptSupport,TclSS,
 +               4,( ShellExState *pSES, char **pzErr,
 +                   int numArgs, char *azArgs[] )){
 +  (void)(pThis);
 +  return 0;
 +}
 +
 +/* Say line is script lead-in iff its first dark is "..".
 + * In combination with dot commands also being TCL commands and the
 + * special handling in the next three functions, this effects what is
 + * promised in this file's header text and by .tcl's help text.
 + */
 +DERIVED_METHOD(int, isScriptLeader, ScriptSupport,TclSS,
 +               1,( const char *zScriptLeadingLine )){
 +  char c;
 +  (void)(pThis);
 +  while( (c=*zScriptLeadingLine++) && (c==' '||c=='\t') ) {}
 +  return (c=='.' && *zScriptLeadingLine=='.');
 +}
 +
 +/* Say line group is complete if it passes muster as ready-to-go TCL. */
 +DERIVED_METHOD(int, scriptIsComplete, ScriptSupport,TclSS,
 +               2,( const char *zScript, char **pzWhyNot )){
 +  (void)(pThis);
 +  (void)(pzWhyNot);
 +  return Tcl_CommandComplete(zScript);
 +}
 +
 +/* As we rely on Tcl_CommandComplete(), no resumable scanning is done. */
 +DERIVED_METHOD(void, resetCompletionScan, ScriptSupport,TclSS, 0,()){
 +  (void)(pThis);
 +}
 +
 +/* Run as TCL after some jiggering with the leading dots. */
 +DERIVED_METHOD(DotCmdRC, runScript, ScriptSupport,TclSS,
 +               3,( const char *zScript, ShellExState *psx, char **pzErrMsg )){
 +  char c;
 +  Tcl_Interp *interp = getInterp();
 +  (void)(pThis);
 +  (void)(psx);
 +
 +  if( interp==0 ) return DCR_Error;
 +  while( (c=*zScript++) && (c==' '||c=='\t') ) {}
 +  if( c=='.' &&  *zScript++=='.' ){
-       Tcl_Obj *const objv[] = {
++    int rc = 0;
++    int nc = strlen30(zScript);
 +    /* At this point, *zScript should fall into one of these cases: */
 +    switch( *zScript ){
 +    case '.':
 +      /* Three dots, assume user meant to run a dot command. */
 +    one_shot_tcl:
 +      rc = Tcl_EvalEx(interp, zScript, /* needs no adjustment */
 +                      nc, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL);
 +      if( rc!=TCL_OK ) copy_complaint(pzErrMsg, getInterp());
 +      break;
 +    case ' ': case '\t':
 +      /* Two dots then whitespace, it's a TCL one-shot command. */
 +      while( (c = *zScript)!=0 && c==' ' || c=='\t' ) ++zScript, --nc;
 +      if ( c!=0 ) goto one_shot_tcl;
 +      /* It looks like "..", so run it that way via fall-thru. */
 +    case 0:
 +      /* Two lone dots, user wants to run TCL REPL. */
 +      return runTclREPL(interp, pzErrMsg);
 +    default:
 +      /* Two dots then dark not dot, may be a dot command. */
 +      if( *zScript>='a' && *zScript<='z' ){
 +        --zScript, ++nc;
 +        goto one_shot_tcl;
 +      }
 +      /* It cannot be a dot command; a user tip is apparently needed. */
 +      if( pzErrMsg ){
 +        *pzErrMsg = sqlite3_mprintf("Nothing valid begins with ..%c\n"
 +                                    "Run .help tcl to see what is valid.\n",
 +                                    *zScript);
 +        return DCR_SayUsage;
 +      }
 +    }
 +    return DCR_Ok|(rc!=TCL_OK);
 +  }
 +  return DCR_Error; /* Silent error because it should not happen. */
 +}
 +
 +DERIVED_METHOD(void, destruct, DotCommand,TclCmd, 0, ()){
 +  /* Nothing to do, instance data is static. */
 +  (void)(pThis);
 +}
 +static DERIVED_METHOD(void, destruct, DotCommand,UnkCmd, 0, ());
 +
 +DERIVED_METHOD(void, destruct, ScriptSupport,TclSS, 0, ()){
 +  /* Nothing to do, instance data is static. */
 +  (void)(pThis);
 +}
 +
 +DERIVED_METHOD(const char *, name, DotCommand,TclCmd, 0,()){
 +  return "tcl";
 +}
 +DERIVED_METHOD(const char *, name, DotCommand,UnkCmd, 0,()){
 +  return "unknown";
 +}
 +
 +DERIVED_METHOD(const char *, help, DotCommand,TclCmd, 1,(const char *zHK)){
 +  (void)(pThis);
 +  if( zHK==0 )
 +    return
 +    ".tcl ?FILES?             Run a TCL REPL or interpret files as TCL.\n";
 +  if( *zHK==0 )
 +    return
 +    "   If FILES are provided, they name files to be read in as TCL.\n"
 +    "   Otherwise, a read/evaluate/print loop is run until a lone \".\" is\n"
 +    "   entered as complete TCL input or input end-of-stream is encountered.\n"
 +    "\n"
 +    "   The same REPL can be run with a lone \"..\". Or the \"..\" prefix\n"
 +    "   may be used thusly, \"..dotcmd ...\" or \".. tclcmd ...\", to run a\n"
 +    "   single dot command or TCL command, respectively, whereupon it will\n"
 +    "   be run in its respective execution environment after its arguments\n"
 +    "   are collected using TCL parsing rules and expanded as for TCL in\n"
 +    "   the TCL base namespace. In this way, arguments may be \"computed\".\n"
 +    ;
 +  return 0;
 +}
 +
 +DERIVED_METHOD(const char *, help, DotCommand,UnkCmd, 1,(const char *zHK));
 +
 +DERIVED_METHOD(DotCmdRC, argsCheck, DotCommand,TclCmd, 3,
 +             (char **pzErrMsg, int nArgs, char *azArgs[])){
 +  return DCR_Ok;
 +}
 +DERIVED_METHOD(DotCmdRC, argsCheck, DotCommand,UnkCmd, 3,
 +             (char **pzErrMsg, int nArgs, char *azArgs[])){
 +  return DCR_Ok;
 +}
 +
 +static void copy_complaint(char **pzErr, Tcl_Interp *pi){
 +  if( pzErr ){
 +    Tcl_Obj *po = Tcl_GetObjResult(pi);
 +    *pzErr = sqlite3_mprintf("%s\n", Tcl_GetStringFromObj(po,0));
 +  }
 +}
 +
 +/* The .tcl/.. REPL script is one of the 3 following string literals,
 + * selected at build time for these different purposes:
 + *  1st: a simple input collection, reading only stdin, which may
 + *    be (handily) used as a fallback for debugging purposes.
 + *  2nd: input collection which honors the shell's input switching
 + *    and otherwise has low dependency upon shell features, which
 + *    means that it has no input line editing or history recall.
 + *  3rd: an input collection which fully leverages the shell's
 + *    input collection. It has higher shell dependency, and for
 + *    that it gains the shell's line editing and history recall,
 + *    in addition to working with the shell's input switching.
 + *    It also supports recursive REPLs when return is caught.
 + */
 +#ifdef TCL_REPL_STDIN_ONLY
 +# define TCL_REPL 1
 +#elif defined(TCL_REPL_LOW_DEPENDENCY)
 +# define TCL_REPL 2
 +#else
 +# define TCL_REPL 3
 +#endif
 +
 +
 +#if TCL_REPL==1 /* a fallback for debug */
 +TCL_CSTR_LITERAL(static const char * const zREPL = ){
 +  set line {}
 +  while {![eof stdin]} {
 +    if {$line!=""} {
 +      puts -nonewline "> "
 +    } else {
 +      puts -nonewline "% "
 +    }
 +    flush stdout
 +    append line [gets stdin]
 +    if {$line eq "."} break
 +    if {[info complete $line]} {
 +      if {[catch {uplevel #0 $line} result]} {
 +        puts stderr "Error: $result"
 +      } elseif {$result!=""} {
 +        puts $result
 +      }
 +      set line {}
 +    } else {
 +      append line \n
 +    }
 +  }
 +  if {$line ne "."} {puts {}}
 +  read stdin 0
 +};
 +#elif TCL_REPL==2 /* minimal use of shell's read */
 +TCL_CSTR_LITERAL(static const char * const zREPL = ){
 +  namespace eval ::REPL {
 +    variable line {}
 +    variable at_end 0
 +    variable prompting [now_interactive]
 +  }
 +  while {!$::REPL::at_end} {
 +    if {$::REPL::prompting} {
 +      if {$::REPL::line!=""} {
 +        puts -nonewline "...> "
 +      } else {
 +        puts -nonewline "tcl% "
 +      }
 +    }
 +    flush stdout
 +    set ::REPL::li [get_input_line]
 +    if {$::REPL::li eq ""} {
 +      set ::REPL::at_end 1
 +    } elseif {[string trimright $::REPL::li] eq "."} {
 +      if {$::REPL::line ne ""} {
 +        throw {NONE} {incomplete input at EOF}
 +      }
 +      set ::REPL::at_end 1
 +    } else {
 +      append ::REPL::line $::REPL::li
 +      if {[string trim $::REPL::line] eq ""} {
 +        set ::REPL::line ""
 +        continue
 +      }
 +      if {[info complete $::REPL::line]} {
 +        set ::REPL::rc [catch {uplevel #0 $::REPL::line} ::REPL::result]
 +        if {$::REPL::rc == 0} {
 +          if {$::REPL::result!="" && $::REPL::prompting} {
 +            puts $::REPL::result
 +          }
 +        } elseif {$::REPL::rc == 1} {
 +          puts stderr "Error: $::REPL::result"
 +        } elseif {$::REPL::rc == 2} {
 +          set ::REPL::at_end 1
 +        }
 +        set ::REPL::line {}
 +      }
 +    }
 +  }
 +  if {$::REPL::prompting && $::REPL::li ne ".\n"} {puts {}}
 +  namespace delete ::REPL
 +  read stdin 0
 +};
 +#elif TCL_REPL==3
 +/* using shell's input collection with line editing (if configured) */
 +static const char * const zREPL = "uplevel #0 sqlite_shell_REPL";
 +
 +TCL_CSTR_LITERAL(static const char * const zDefineREPL = ){
 +  proc sqlite_shell_REPL {} {
 +    if {[info exists ::tcl_interactive]} {
 +      set save_interactive $::tcl_interactive
 +    }
 +    set ::tcl_interactive [now_interactive]
 +    while {1} {
 +      foreach {group ready} [get_tcl_group] {}
 +      set trimmed [string trim $group]
 +      if {$group eq "" && !$ready} break
 +      if {$trimmed eq ""} continue
 +      if {!$ready && $trimmed ne ""} {
 +        throw {NONE} {incomplete input at EOF}
 +      }
 +      if {$trimmed eq "."} break
 +      set rc [catch {uplevel #0 $group} result]
 +      if {$rc == 0} {
 +        if {$result != "" && $::tcl_interactive} {
 +          puts $result
 +        }
 +      } elseif {$rc == 1} {
 +        puts stderr "Error: $result"
 +      } elseif {$rc == 2} {
 +        return -code 2
 +      }
 +    }
 +    if {$::tcl_interactive && $trimmed ne "."} {puts {}}
 +    read stdin 0
 +    if {[info exists save_interactive]} {
 +      set ::tcl_interactive $save_interactive
 +    } else { unset ::tcl_interactive }
 +  }
 +};
 +#else
 +  "throw {NONE} {not built for REPL}\n"
 +#endif
 +
 +/* Enter the preferred REPL */
 +static DotCmdRC runTclREPL(Tcl_Interp *interp, char **pzErrMsg){
 +  int rc = Tcl_Eval(interp, zREPL);
 +  clearerr(stdin); /* Cure issue where stdin gets stuck after keyboard EOF. */
 +  if( rc!=TCL_OK ){
 +    copy_complaint(pzErrMsg, interp);
 +    return DCR_Error;
 +  }
 +  return DCR_Ok;
 +}
 +
 +DERIVED_METHOD(DotCmdRC, execute, DotCommand,TclCmd, 4,
 +             (ShellExState *psx, char **pzErrMsg, int nArgs, char *azArgs[])){
 +  FILE *out = SHX_HELPER(currentOutputFile)(psx);
 +  TclCmd *ptc = (TclCmd *)pThis;
 +  if( nArgs>1 ){
 +    /* Read named files into the interpreter. */
 +    int rc = TCL_OK;
 +    int aix;
 +    for( aix=0; aix<(nArgs-1) && rc==TCL_OK; ++aix ){
 +      rc = Tcl_EvalFile(getInterp(), azArgs[aix+1]);
 +    }
 +    if( rc!=TCL_OK ){
 +      copy_complaint(pzErrMsg, getInterp());
 +      return DCR_Error;
 +    }
 +    return DCR_Ok;
 +  }else{
 +    /* Enter a REPL */
 +    return runTclREPL(getInterp(), pzErrMsg);
 +  }
 +}
 +
 +DERIVED_METHOD(DotCmdRC, execute, DotCommand,UnkCmd, 4,
 +             (ShellExState *psx, char **pzErrMsg, int nArgs, char *azArgs[])){
 +  Tcl_Interp *interp = getInterp();
 +  Tcl_Obj **ppo;
 +  char zName[50];
 +  int ia, rc;
 +
 +  if( interp==0 || nArgs==0 ) return DCR_Unknown;
 +
 +  sqlite3_snprintf(sizeof(zName), zName, ".%s", azArgs[0]);
 +  if( !Tcl_FindCommand(interp, zName, 0, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) ){
 +    if( !SHX_HELPER(nowInteractive)(psx) ){
 +      *pzErrMsg = sqlite3_mprintf("Command %s not found.\n", zName);
 +      return DCR_Unknown;
 +    }else{
 +      FILE *out = SHX_HELPER(currentOutputFile)(psx);
 +      fprintf(stderr, "The %s command does not yet exist.\n", zName);
 +      fprintf(out, "Run .help to see existent dot commands,"
 +              " or create %s as a TCL proc.\n", zName);
 +      return DCR_CmdErred;
 +    }
 +  }
 +  ppo = sqlite3_malloc((nArgs+1)*sizeof(Tcl_Obj*));
 +  if( ppo==0 ) return TCL_ERROR;
 +  for( ia=0; ia<nArgs; ++ia ){
 +    ppo[ia] = Tcl_NewStringObj((ia)? azArgs[ia] : zName, -1);
 +    Tcl_IncrRefCount(ppo[ia]);
 +  }
 +  rc = Tcl_EvalObjv(interp, nArgs, ppo, TCL_EVAL_GLOBAL);
 +  for( ia=0; ia<nArgs; ++ia ) Tcl_DecrRefCount(ppo[ia]);
 +  sqlite3_free(ppo);
 +  /* Translate TCL return to a dot command return. */
 +  switch( rc ){
 +  case TCL_OK:
 +    return DCR_Ok;
 +  case TCL_ERROR:
 +    *pzErrMsg = sqlite3_mprintf("%s\n", Tcl_GetStringResult(interp));
 +    return DCR_Error;
 +  case TCL_RETURN: case TCL_BREAK: case TCL_CONTINUE:
 +    return DCR_Return;
 +  default:
 +    return DCR_Exit;
 +  }
 +}
 +
 +/* Define DotCommand v-tables initialized to reference most above methods. */
 +DotCommand_IMPLEMENT_VTABLE(TclCmd, tclcmd_methods);
 +DotCommand_IMPLEMENT_VTABLE(UnkCmd, unkcmd_methods);
 +/* Define ScriptSupport v-table initialized to reference the others. */
 +ScriptSupport_IMPLEMENT_VTABLE(TclSS, tclss_methods);
 +
 +/* Static instances are used because that suffices. */
 +INSTANCE_BEGIN(TclCmd);
 +  /* no instance data */
 +INSTANCE_END(TclCmd) tclcmd = {
 +  &tclcmd_methods
 +};
 +INSTANCE_BEGIN(TclSS);
 +  /* no instance data */
 +INSTANCE_END(TclSS) tclss = {
 +  &tclss_methods
 +};
 +
 +INSTANCE_BEGIN(UnkCmd);
 +  /* no instance data */
 +INSTANCE_END(UnkCmd) unkcmd = {
 +  &unkcmd_methods
 +};
 +
 +static DERIVED_METHOD(void, destruct, DotCommand,UnkCmd, 0, ()){
 +  (void)(pThis);
 +}
 +
 +DERIVED_METHOD(const char *, help, DotCommand,UnkCmd, 1,(const char *zHK)){
 +  (void)(pThis);
 +  if( !zHK )
 +    return
 +  ",unknown ?ARGS?          Retry unknown dot command if it is a TCL command\n";
 +  if( !*zHK )
 +    return
 +  "   There is little use for this dot command without the TCL extension, as\n"
 +  "   the shell's version merely does some error reporting. However, with it\n"
 +  "   overridden, (as it is now), it provides a retry mechanism whereby, if\n"
 +  "   the command can be found defined in the TCL environment, that command\n"
 +  "   can be run with whatever arguments it was provided.\n"
 +  "\n"
 +  "   If the TCL command, register_adhoc_command is run, this command's help\n"
 +  "   method can be made to provide help text for the registered TCL command.\n"
 +  ;
 +  return 0;
 +}
 +
 +#if TCL_REPL==1 || TCL_REPL==2
 +#define GETLINE_MAXLEN 1000
 +
 +/* C implementation of TCL proc, get_input_line */
 +static int getInputLine(void *pvSS, Tcl_Interp *interp,
 +                        int nArgs, const char *azArgs[]){
 +  if( nArgs==1 ){
 +    char buffer[GETLINE_MAXLEN+1];
 +    ShellExState *psx = (ShellExState *)pvSS;
 +    struct InSource *pis = SHX_HELPER(currentInputSource)(psx);
 +    if( SHX_HELPER(strLineGet)(buffer, GETLINE_MAXLEN, pis) ){
 +      Tcl_SetResult(interp, buffer, TCL_VOLATILE);
 +    }else{
 +      Tcl_SetResult(interp, 0, 0);
 +    }
 +    return TCL_OK;
 +  }else{
 +    Tcl_SetResult(interp, "too many arguments", TCL_STATIC);
 +    return TCL_ERROR;
 +  }
 +}
 +#endif
 +
 +#if TCL_REPL==3
 +/* C implementation of TCL proc, get_tcl_group 
 + * This routine returns a 2 element list consisting of:
 + *   the collected input lines, joined with "\n", as a string
 + * and
 + *   the line group status, as an integer.
 + * The status is either 0, meaning input EOF was encountered,
 + * or 1, meaning the input is a complete TCL line group.
 + * There are only these return combinations:
 + *   { Empty 0 } => no input obtained and no more to be had
 + *   { Other 0 } => input collected, but is invalid TCL
 + *   { Other 1 } => input collected, may be valid TCL
 + * By design, this combination is never returned:
 + *   { Empty 1 } => no input collected but valid TCL
 + */
 +static int getTclGroup(void *pvSS, Tcl_Interp *interp,
 +                       int objc, Tcl_Obj *const objv[]){
 +  if( objc==1 ){
 +    static Prompts cueTcl = { "tcl% ", "   > " };
 +    ShellExState *psx = (ShellExState *)pvSS;
 +    struct InSource *pis = SHX_HELPER(currentInputSource)(psx);
 +    int isComplete = 0;
 +    char *zIn = 0;
 +    int isContinuation = 0;
 +    do {
 +      zIn = SHX_HELPER(oneInputLine)(pis, zIn, isContinuation, &cueTcl);
 +      if( isContinuation ){
 +        if( zIn ){
 +          Tcl_AppendResult(interp, "\n", zIn, (char*)0);
 +          isComplete = Tcl_CommandComplete(Tcl_GetStringResult(interp));
 +        }
 +      }else if( zIn ){
 +        isComplete = Tcl_CommandComplete(zIn);
 +        Tcl_SetResult(interp, zIn, TCL_VOLATILE);
 +      }
 +      isContinuation = 1;
 +    } while( zIn && !isComplete );
 +    if( zIn ) SHX_HELPER(freeInputLine)(zIn);
 +    {
-       Tcl_SetObjResult(interp, Tcl_NewListObj(2, objv));
++      Tcl_Obj *const objvv[] = {
 +        Tcl_NewStringObj(Tcl_GetStringResult(interp) , -1),
 +        Tcl_NewIntObj(isComplete)
 +      }; /* These unowned objects go directly into result, becoming owned. */
 +      Tcl_ResetResult(interp);
-   int nshift;
++      Tcl_SetObjResult(interp, Tcl_NewListObj(2, objvv));
 +    }
 +    return TCL_OK;
 +  }else{
 +    Tcl_SetResult(interp, "too many arguments", TCL_STATIC);
 +    return TCL_ERROR;
 +  }
 +}
 +#endif
 +
 +/* C implementation of TCL proc, now_interactive */
 +static int nowInteractive(void *pvSS, Tcl_Interp *interp,
 +                          int nArgs, const char *azArgs[]){
 +  if( nArgs==1 ){
 +    ShellExState *psx = (ShellExState *)pvSS;
 +    struct InSource *pis = SHX_HELPER(currentInputSource)(psx);
 +    static const char * zAns[2] = { "0","1" };
 +    int iiix = (SHX_HELPER(nowInteractive)(psx) != 0);
 +    Tcl_SetResult(interp, (char *)zAns[iiix], TCL_STATIC);
 +    return TCL_OK;
 +  }else{
 +    Tcl_SetResult(interp, "too many arguments", TCL_STATIC);
 +    return TCL_ERROR;
 +  }
 +}
 +
 +#ifdef SHELL_ENABLE_TK
 +static int numEventLoops = 0;
 +static int inOuterLoop = 0;
 +
 +static int exitThisTkGUI(void *pvSS, Tcl_Interp *interp,
 +                         int nArgs, const char *azArgs[]){
 +  if( numEventLoops==0 && !inOuterLoop ){
 +    int ec = 0;
 +    if( nArgs>=2 ){
 +      if( azArgs[1] && sscanf(azArgs[1], "%d", &ec)!=1 ){
 +        ec = 1;
 +        fprintf(stderr, "Exit: %d\n", ec);
 +      }else{
 +        const char *zA = (azArgs[1])? azArgs[1] : "null";
 +        fprintf(stderr, "Exit: \"%s\"\n", azArgs[1]);
 +      }
 +    }else{
 +      fprintf(stderr, "Exit without argument\n");
 +    }
 +    fprintf(stderr, "Exit: \"%s\"\n", azArgs[1]);
 +    // exit(ec);
 +  }
 +  --numEventLoops;
 +  return TCL_BREAK;
 +}
 +
 +static void runTclEventLoop(void){
 +  int inOuter = inOuterLoop;
 +  int nmw = Tk_GetNumMainWindows();
 +  /* This runs without looking at stdin. So it cannot be a REPL, yet.
 +   * Unless user has created something for it to do, it does nothing. */
 +  /* Tk_MapWindow(Tk_MainWindow(interpKeep.pInterp)); */
 +  ++numEventLoops;
 +  inOuterLoop = 1;
 +  while( nmw  > 0 ) {
 +    Tcl_DoOneEvent(0);
 +    nmw = Tk_GetNumMainWindows();
 +    /* if( nmw==1 ){ */
 +    /*   Tk_UnmapWindow(Tk_MainWindow(interpKeep.pInterp)); */
 +    /*   nmw = Tk_GetNumMainWindows(); */
 +    /*   break; */
 +    /* } */
 +  }
 +  if( nmw==0 ){
 +    fprintf(stderr,
 +            "Tk application and its root window destroyed. Restarting Tk.\n");
 +    Tk_Init(interpKeep.pInterp);
 +  }
 +  --numEventLoops;
 +  inOuterLoop = inOuter;
 +}
 +
 +static int runTkGUI(void *pvSS, Tcl_Interp *interp,
 +                    int nArgs, const char *azArgs[]){
 +  (void)(pvSS); /* ShellExState *psx = (ShellExState *)pvSS; */
 +  Tcl_SetMainLoop(runTclEventLoop);
 +  runTclEventLoop();
 +  return TCL_OK;
 +}
 +#endif /* defined(SHELL_ENABLE_TK) */
 +
 +#define UNKNOWN_RENAME "_original_unknown"
 +
 +/* C implementation of TCL ::register_adhoc_command name ?help? */
 +static int registerAdHocCommand(/* ShellExState */ void *pv,
 +                                Tcl_Interp *interp,
 +                                int nArgs, const char *azArgs[]){
 +  ShellExState *psx = (ShellExState*)pv;
 +  if( nArgs>3 ){
 +    Tcl_SetResult(interp, "too many arguments", TCL_STATIC);
 +  }else if( nArgs<2 ){
 +    Tcl_SetResult(interp, "too few arguments", TCL_STATIC);
 +  }else{
 +    const char *zHT = (nArgs==3)? azArgs[2] : 0;
 +    Tcl_ResetResult(interp);
 +    SHX_API(registerAdHocCommand)(psx, sqlite3_tclshext_init, azArgs[1], zHT);
 +    return TCL_OK;
 +  }
 +  return TCL_ERROR;
 +}
 +
 +/* C implementation of TCL unknown to (maybe) delegate to dot commands */
 +static int unknownDotDelegate(void *pvSS, Tcl_Interp *interp,
 +                              int nArgs, const char *azArgs[]){
 +  const char *name = (nArgs>1 && *azArgs[1]=='.')? azArgs[1]+1 : 0;
 +  ShellExState *psx = (ShellExState *)pvSS;
 +  DotCommand *pdc = 0;
 +  int nFound = 0;
 +  int ia, rc;
 +
 +  if( name ) pdc = SHX_HELPER(findDotCommand)(name, psx, &nFound);
 +  if( pdc==(DotCommand*)&tclcmd && nArgs==2 ){
 +    /* Will not do a nested REPL, just silently semi-fake it. */
 +    return TCL_OK;
 +  }
 +  if( pdc && nFound==1 ){
 +    /* Run the dot command and interpret its returns. */
 +    DotCmdRC drc = SHX_HELPER(runDotCommand)(pdc, (char **)azArgs+1,
 +                                              nArgs-1, psx);
 +    if( drc==DCR_Ok ) return TCL_OK;
 +    else if( drc==DCR_Return ){
 +      return TCL_RETURN;
 +    }else{
 +      Tcl_AppendResult(interp, "Execution of .", name, " failed.", 0);
 +      return TCL_ERROR;
 +    }
 +  }else{
 +    /* Defer to the TCL-default unknown command, or fail here. */
 +    if( 0!=Tcl_FindCommand(interp, UNKNOWN_RENAME, 0, TCL_GLOBAL_ONLY) ){
 +      Tcl_Obj **ppo = sqlite3_malloc((nArgs+1)*sizeof(Tcl_Obj*));
 +      if( ppo==0 ) return TCL_ERROR;
 +      ppo[0] = Tcl_NewStringObj(UNKNOWN_RENAME, -1);
 +      Tcl_IncrRefCount(ppo[0]);
 +      for( ia=1; ia<nArgs; ++ia ){
 +        ppo[ia] = Tcl_NewStringObj(azArgs[ia], -1);
 +        Tcl_IncrRefCount(ppo[ia]);
 +      }
 +      ppo[ia] = 0;
 +      rc = Tcl_EvalObjv(interp, nArgs, ppo, TCL_EVAL_GLOBAL);
 +      for( ia=0; ia<nArgs; ++ia ) Tcl_DecrRefCount(ppo[ia]);
 +      sqlite3_free(ppo);
 +      return rc;
 +    }else{
 +      /* Fail now (instead of recursing back into this handler.) */
 +      Tcl_AppendResult(interp,
 +                       "Command ", azArgs[1], " does not exist.", (char *)0);
 +      return TCL_ERROR;
 +    }
 +  }
 +}
 +
 +/* TCL dbu command: Acts like a (TCL) sqlite3 command created object except
 + * that it defers to shell's DB and treats the close subcommand as an error.
 + * The below struct and functions through userDbInit() support this feature.
 + */
 +typedef struct UserDb {
 +  SqliteDb **ppSdb;   /* Some tclsqlite.c "sqlite3" DB objects, held here. */
 +  int numSdb;         /* How many "sqlite3" objects are now being held */
 +  int ixuSdb;         /* Which held "sqlite3" object is the .dbUser, if any */
 +  int nRef;           /* TCL object sharing counter */
 +  Tcl_Interp *interp; /* For creation of newly visible .dbUser DBs */
 +  ShellExState *psx;  /* For shell state access when .eval is run */
 +} UserDb;
 +
 +/* Add a DB to the list. Return its index. */
 +static int udbAdd(UserDb *pudb, sqlite3 *udb){
 +  SqliteDb *p;
-   pudb->numSdb==0;
 +  pudb->ppSdb
 +    = (SqliteDb**)Tcl_Realloc((char*)pudb->ppSdb, (pudb->numSdb+1)*sizeof(p));
 +  memset(pudb->ppSdb + pudb->numSdb, 0, sizeof(SqliteDb*));
 +  p = (SqliteDb*)Tcl_Alloc(sizeof(SqliteDb));
 +  memset(p, 0, sizeof(SqliteDb));
 +  pudb->ppSdb[pudb->numSdb] = p;
 +  p->db = udb;
 +  p->interp = pudb->interp;
 +  p->maxStmt = NUM_PREPARED_STMTS;
 +  p->openFlags = SQLITE_OPEN_URI;
 +  p->nRef = 1;
 +  return pudb->numSdb++;
 +}
 +
 +/* Remove a DB from the list */
 +static void udbRemove(UserDb *pudb, int ix){
 +  SqliteDb *pdb;
 +  assert(ix>=0 && ix<pudb->numSdb);
 +  /* The code below is highly dependent upon implementation details of
 +   * tclsqlite.c , and may become incorrect if that code changes. This
 +   * is an accepted downside of reusing vast portions of that code.
 +   * The minutiae in these comments is to explain the dependencies so
 +   * that adjustments might be easily made when proven necessary. */
 +  pdb = pudb->ppSdb[ix];
 +#ifndef SQLITE_OMIT_INCRBLOB
 +  /* This is a preemptive action, which is normally done by the
 +   * delDatabaseRef() routine, which needs a non-zero db pointer
 +   * to reach Tcl_UnregisterChannel()'s implementation. We do it
 +   * now because, to avoid closing that db, that pointer will be
 +   * set to 0 when delDatabaseRef() runs. */
 +  closeIncrblobChannels(pdb);
 +  /* Prevent closeIncrblobChannels() from trying to free anything. */
 +  pdb->pIncrblob = 0;
 +#endif
 +  /* This appears to not be necessary; it is defensive in case the
 +   * flushStmtCache() or dbFreeStmt() code begins to use pdb->db .
 +   * We rely on its behavior whereby, once flushed, the cache is
 +   * made to appear empty in the SqliteDb struct. */
 +  flushStmtCache(pdb);
 +  /* This next intervention prevents delDatabaseRef() from closing
 +   * the .db ; this relies on sqlite3_close(0) being a NOP. If the
 +   * SqliteDb takedown code changes, this may lead to an address
 +   * fault. For that reason, the *.in which produces this source
 +   * should be tested by excercising the TCL udb command well. */
 +  pdb->db = 0;
 +  assert(pdb->nRef==1);
 +  /* Use the "stock" delete for sqlite3-generated objects. */
 +  delDatabaseRef(pdb);
 +  /* At this point, pdb has been Tcl_Free()'ed. Forget it. */
 +  --pudb->numSdb;
 +  {
 +    int nshift = pudb->numSdb-ix;
 +    if( nshift>0 ){
 +      memmove(pudb->ppSdb+ix, pudb->ppSdb+ix+1, nshift*sizeof(pdb));
 +    }
 +  }
 +  /* Adjust index to currently visible DB. */
 +  if( ix==pudb->ixuSdb ) pudb->ixuSdb = -1;
 +  else if( ix<pudb->ixuSdb ) --pudb->ixuSdb;
 +}
 +
 +static struct UserDb *udbCreate(Tcl_Interp *interp, ShellExState *psx);
 +
 +/* Cleanup the UserDb singleton. Should only be done at shutdown. 
 + * This routine is idempotent, and may be called redundantly.
 + */
 +static void udbCleanup(UserDb *pudb){
 +  /* If this is called too early, when *pudb is still associated with
 +   * active (not yet closed) SqliteDb objects, those will simply be
 +   * orphaned and leaked. But this assert may make the error evident. */
 +  if( pudb==0 ) pudb = udbCreate(0, 0);
 +  assert(pudb->numSdb==0);
 +  if( pudb->ppSdb ) Tcl_Free((char*)pudb->ppSdb);
 +  memset(pudb, 0, sizeof(UserDb));
 +  pudb->ixuSdb = -1;
 +}
 +
 +/* Hunt for given db in UserDb's list. Return its index if found, else -1. */
 +static int udbIndexOfDb(UserDb *pudb, sqlite3 *psdb){
 +  int ix = 0;
 +  while( ix < pudb->numSdb ){
 +    if( psdb==pudb->ppSdb[ix]->db ) return ix;
 +    else ++ix;
 +  }
 +  return -1;
 +}
 +
 +/* The event handler used to keep udb command's wrapped DB in sync with
 + * changes to the ShellExState .dbUser member. This task is complicated
 + * by effects of these dot commands: .open ; .connection ; and .quit,
 + * .exit or various other shell exit causes. The intention is to always
 + * have an orderly and leak-free shutdown (excepting kill/OOM aborts.)
 + */
 +static int udbEventHandle(void *pv, NoticeKind nk, void *pvSubject,
 +                          ShellExState *psx){
 +  UserDb *pudb = (UserDb*)pv;
 +  if( nk==NK_ShutdownImminent ){
 +    udbCleanup(pudb);
 +  }else if( nk==NK_Unsubscribe ){
 +    assert(pudb==0 || pudb->numSdb==0);
 +  }else if( nk==NK_DbUserAppeared || nk==NK_DbUserVanishing
 +            || nk==NK_DbAboutToClose || nk==NK_ExtensionUnload ){
 +    sqlite3 *dbSubject = (sqlite3*)pvSubject;
 +    int ix = udbIndexOfDb(pudb, dbSubject);
 +    switch( nk ){
 +    case NK_DbUserAppeared:
 +      if( ix>=0 ) pudb->ixuSdb = ix;
 +      else pudb->ixuSdb = udbAdd(pudb, dbSubject);
 +      break;
 +    case NK_DbUserVanishing:
 +      if( ix>=0 ) pudb->ixuSdb = -1;
 +      break;
 +    case NK_ExtensionUnload:
 +      SHX_API(subscribeEvents)(psx, sqlite3_tclshext_init, 0,
 +                               NK_Unsubscribe, udbEventHandle);
 +      /* fall thru */
 +    case NK_DbAboutToClose:
 +      if( ix>=0 ) udbRemove(pudb, ix);
 +      break;
 +    }
 +  }
 +  return 0;
 +}
 +
 +/* Create the UserDb object supporting TCL "udb" command operations. 
 + * It's not wholly created because it is a singleton. Any subsequent
 + * creation is ignored; instead, the singleton is returned. This
 + * object is made to release resources only upon shutdown. If a TCL
 + * user removes the udb command, this avoids problems arising from
 + * this object holding references to databases that may still be in
 + * use, either as the active .dbUser or as a blob streaming store. */
 +static struct UserDb *udbCreate(Tcl_Interp *interp, ShellExState *psx){
 +  static UserDb *rv = 0;
 +  static UserDb udb = { 0 };
 +  if( interp==0 || psx==0 ) return &udb;
 +  if( rv==0 ){
 +    sqlite3 *sdbS = psx->dbShell;
 +    sqlite3 *sdbU = psx->dbUser;
 +    rv = &udb;
 +    rv->interp = interp;
 +    rv->psx = psx;
 +    rv->ppSdb = (SqliteDb**)Tcl_Alloc(6*sizeof(SqliteDb*));
 +    memset(rv->ppSdb, 0, 6*sizeof(SqliteDb*));
 +    assert(sdbS!=0);
 +    udbAdd(rv, sdbS);
 +    if( sdbU!=0 ){
 +      rv->ixuSdb = udbAdd(rv, sdbU);
 +    } else rv->ixuSdb = -1;
 +    rv->nRef = 1;
 +    /* Arrange that this object tracks lifetimes and visibility of the
 +     * ShellExState .dbUser member values which udb purports to wrap,
 +     * and that shdb ceases wrapping the .dbShell member at shutdown.
 +     * This subscription eventually leads to a udbCleanup() call. */
 +    SHX_API(subscribeEvents)(psx, sqlite3_tclshext_init,
 +                             rv, NK_CountOf, udbEventHandle);
 +  }
 +  return rv;
 +}
 +
 +static const char *azDbNames[] = { "shdb", "udb", 0 };
 +static const int numDbNames = 2;
 +
 +/* C implementation behind added TCL udb command */
 +static int UserDbObjCmd(void *cd, Tcl_Interp *interp,
 +                        int objc, Tcl_Obj * const * objv){
 +  UserDb *pudb = (UserDb*)cd;
 +  static const char *azDoHere[] = { "close", 0 };
 +  enum DbDoWhat { DDW_Close };
 +  int doWhat;
 +  int whichDb = -1;
 +  const char *zMoan;
 +
 +  if( Tcl_GetIndexFromObj(interp, objv[0], azDbNames,
 +                          "shell DB command", 0, &whichDb)){
 +    zMoan = " is not a wrapped DB.\n";
 +    goto complain_fail;
 +  }
 +  if( whichDb>0 ) whichDb = pudb->ixuSdb;
 +  /* Delegate all subcommands except above to the now-visible SqliteDb. */
 +  if( objc>=2 && !Tcl_GetIndexFromObj(interp, objv[1], azDoHere,
 +                                      "subcommand", 0, &doWhat)){
 +    switch( doWhat ){
 +    case DDW_Close:
 +      zMoan = " close is disallowd. It is a wrapped DB.\n";
 +      goto complain_fail;
 +    }
 +  }
 +  if( pudb->numSdb==0 || whichDb<0 ){
 +    zMoan = " references no DB yet.\n";
 +    goto complain_fail;
 +  }
 +  return DbObjCmd(pudb->ppSdb[whichDb], interp, objc, objv);
 +
 + complain_fail:
 +  Tcl_AppendResult(interp,
 +                   Tcl_GetStringFromObj(objv[0], (int*)0), zMoan, (char*)0);
 +  return TCL_ERROR;
 +}
 +
 +/* Get the udb command subsystem initialized and create "udb" TCL command. */
 +static int userDbInit(Tcl_Interp *interp, ShellExState *psx){
 +  UserDb *pudb = udbCreate(interp, psx);
 +  int nCreate = 0;
 +  int ic;
 +  for( ic=0; ic<numDbNames; ++ic ){
 +    nCreate +=
 +      0 != Tcl_CreateObjCommand(interp, azDbNames[ic],
 +                                (Tcl_ObjCmdProc*)UserDbObjCmd,
 +                                (char *)pudb, 0);
 +  }
 +  if( nCreate==ic ){
 +    ++pudb->nRef;
 +    return TCL_OK;
 +  }
 +  return TCL_ERROR;
 +}
 +
 +/*
 +** Extension load function.
 +*/
 +#ifdef _WIN32
 +__declspec(dllexport)
 +#endif
 +int sqlite3_tclshext_init(
 +  sqlite3 *db,
 +  char **pzErrMsg,
 +  const sqlite3_api_routines *pApi
 +){
 +  static const char * const azLoadFailures[] = {
 +    "Extension load failed unexpectedly.",
 +    "No ShellExtensionLink.\n Use '.load tclshext -shext' to load.",
 +    "Outdated shell host extension API.\n Update the shell.",
 +    "Outdated shell host helper API.\n Use a newer shell.",
 +  };
 +  int iLoadStatus;
 +  SQLITE_EXTENSION_INIT2(pApi);
 +  SHELL_EXTENSION_INIT2(pShExtLink, shextLinkFetcher, db);
 +
 +  SHELL_EXTENSION_INIT3(pShExtApi, pExtHelpers, pShExtLink);
 +  iLoadStatus = SHELL_EXTENSION_LOADFAIL_WHY(pShExtLink, 6, 10);
 +  if( iLoadStatus!=EXLD_Ok ){
 +    if( iLoadStatus>=sizeof(azLoadFailures)/sizeof(azLoadFailures[0]) ){
 +      iLoadStatus = 0;
 +    }
 +    *pzErrMsg = sqlite3_mprintf("%s\n", azLoadFailures[iLoadStatus]);
 +    return SQLITE_ERROR;
 +  }else{
 +    ShellExState *psx = pShExtLink->pSXS;
 +    Tcl_Obj *targv = Tcl_NewListObj(0, NULL);
 +    const char *zAppName = "tclshext";
 +    int tnarg = 0;
 +#ifdef SHELL_ENABLE_TK
 +    int ldTk = 0;
 +#endif
 +    int rc = 0;
 +
 +    if( pShExtLink->nLoadArgs>0 ){
 +      int ila;
 +      for( ila=0; ila<pShExtLink->nLoadArgs; ++ila ){
 +        const char *zA = pShExtLink->azLoadArgs[ila];
 +        if( strcmp(zA,"-tk")==0 ){
 +#ifdef SHELL_ENABLE_TK
 +          ldTk = 1;
 +#else
 +          *pzErrMsg = sqlite3_mprintf("Option -tk not supported by this "
 +                                      "tclshext extension as built.\n");
 +          return SQLITE_ERROR;
 +#endif
 +        }else{
 +          /* Collect args not affecting init into the argv list. */
 +          Tcl_ListObjAppendElement(NULL, targv, Tcl_NewStringObj(zA, -1));
 +          ++tnarg;
 +        }
 +      }
 +    }
 +    rc = SHX_API(registerDotCommand)(psx, sqlite3_tclshext_init,
 +                                     (DotCommand *)&unkcmd);
 +    rc = SHX_API(registerDotCommand)(psx, sqlite3_tclshext_init,
 +                                     (DotCommand *)&tclcmd);
 +    if( rc==SQLITE_OK &&
 +        (rc = Tcl_BringUp(
 +#ifdef SHELL_ENABLE_TK
 +                          &ldTk,
 +#endif
 +                          pzErrMsg))==SQLITE_OK
 +        ){
 +      Tcl_Interp *interp = getInterp();
 +      if( TCL_OK==userDbInit(interp, psx) ){
 +        UserDb *pudb = udbCreate(interp, psx);
 +        pShExtLink->extensionDestruct = (void (*)(void*))udbCleanup;
 +        pShExtLink->pvExtensionObject = pudb;
 +      }
 +      SHX_API(registerScripting)(psx, sqlite3_tclshext_init,
 +                                 (ScriptSupport *)&tclss);
 +#if TCL_REPL==1 || TCL_REPL==2
 +      Tcl_CreateCommand(interp, "get_input_line", getInputLine, psx, 0);
 +#endif
 +#if TCL_REPL==3
 +      Tcl_CreateObjCommand(interp, "get_tcl_group", getTclGroup, psx, 0);
 +      Tcl_Eval(interp, zDefineREPL);
 +#endif
 +      Tcl_CreateCommand(interp, "now_interactive", nowInteractive, psx, 0);
 +      /* Rename unknown so that calls to it can be intercepted. */
 +      Tcl_Eval(interp, "rename unknown "UNKNOWN_RENAME);
 +      Tcl_CreateCommand(interp, "unknown", unknownDotDelegate, psx, 0);
 +      /* Add a command to facilitate ad-hoc TCL dot commands showing up in
 +       * the .help output with help text as specified by calling this: */
 +      Tcl_CreateCommand(interp, "register_adhoc_command",
 +                        registerAdHocCommand, (void*)psx, 0);
 +
 +      /* Define this proc so that ".." either gets to the TCL REPL loop
 +       * or does nothing (if already in it), as a user convenience. */
 +      Tcl_Eval(interp, "proc .. {} {}");
 +#ifdef SHELL_ENABLE_TK
 +      if( ldTk ){
 +        /* Create a proc to launch GUI programs, in faint mimicry of wish. 
 +         *
 +         * Its first argument, pgmName is the name to be given to the GUI
 +         * program that may be launched, used for error reporting and to
 +         * become the value of the ::argv0 that it sees.
 +         *
 +         * Its second argument, pgmSetup, will be executed as a list (of
 +         * a command and its arguments) to setup the GUI program. It may
 +         * do anything necessary to prepare for the GUI program to be
 +         * run by running a Tk event loop. It may be an empty list, in
 +         * which case pgmName must name a list serving the same purpose.
 +         *
 +         * Subsequent arguments to this proc will be passed to the GUI
 +         * program in the ::argv/::argc variable pair it sees.
 +         *
 +         * If only two empty arguments are provided to this proc, (whether
 +         * as defaulted or explictly passed), the GUI event loop will be
 +         * run with whatever conditions have been setup prior to the call.
 +         * (This is perfectly legitimate; this "gui" proc provides a way
 +         * to package GUI preparation and separate it from GUI run.)
 +         *
 +         * It is the responsibility of whatever setup code is run, if any,
 +         * to leave Tk objects and variables set so that when a GUI event
 +         * loop is run, some useful GUI program runs and can terminate.
 +         *
 +         * Before running the setup code, a variable, ::isHost, is set
 +         * true to possibly inform the setup code that it should avoid
 +         * exit and exec calls. Setup code which is designed for either
 +         * hosted or standalone use, when run with $::isHost!=0, may opt
 +         * to leave variables ::exitCode and ::resultValue set which are
 +         * taken to indicate pseudo-exit status and a string result to
 +         * be used for error reporting or possibly other purposes.
 +         *
 +         * If the above responsibilities cannot be met, setup code should
 +         * fail in some way so that its execution produces a TCL error or
 +         * follows the ::exitCode and ::resultValue convention. Otherwise,
 +         * annoying sqlite3 shell hangs or abrupt exits may result.
 +         */
 +        TCL_CSTR_LITERAL(const char * const zGui =){
 +          proc gui {{pgmName ""} {pgmSetup {}} args} {
 +            unset -nocomplain ::exitCode
 +            set ::tcl_interactive [now_interactive]
 +            set saveArgs [list $::argv0 $::argc $::argv]
 +            if {"$pgmName" ne ""} {
 +              set ::argv0 $pgmName
 +            } else {set ::argv0 "?"}
 +            set ::argv $args
 +            set ::argc [llength $args]
 +            if {[llength $pgmSetup] == 0 && $pgmName ne ""} {
 +              if { [catch {set ::programSetup [subst "\$$pgmName"]}] } {
 +                foreach {::argv0 ::argc ::argv} $saveArgs {}
 +                return -code 1 "Error: pgmSetup empty, and pgmName does not\
 +                   name a list that might be\n executed in\
 +                   its place. Consult tclshext doc on using the gui command."
 +              }
 +            } elseif {[llength $pgmSetup] == 0 && $pgmName eq ""} {
 +              unset -nocomplain ::programSetup
 +            } else {
 +              set ::programSetup $pgmSetup
 +            }
 +            if {[info exists ::programSetup] && [llength $::programSetup] > 0} {
 +              set rc [catch {uplevel #0 {
 +                {*}$::programSetup
 +              }} result options]
 +              if {$rc==1} {
 +                puts stderr "gui setup failed: $result"
 +                puts stderr [dict get $options -errorinfo]
 +              } elseif {[info exists ::exitCode] && $::exitCode!=0} {
 +                puts stderr "gui setup failed: $::resultValue"
 +              } else { run_gui_event_loop }
 +            } else {
 +              run_gui_event_loop
 +            }
 +            foreach {::argv0 ::argc ::argv} $saveArgs {}
 +          }
 +        };
 +        /* Create a command which nearly emuluates Tk_MainLoop(). It runs a
 +         * GUI event loop, so does not return until either: all Tk top level
 +         * windows are destroyed, which causes and error return, or the Tk
 +         * app has called the replacement exit routine described next. */
 +        Tcl_CreateCommand(interp, "run_gui_event_loop", runTkGUI, psx, 0);
 +        Tcl_Eval(interp, "rename exit process_exit");
 +        Tcl_CreateCommand(interp, "exit", exitThisTkGUI, psx, 0);
 +        Tcl_Eval(interp, zGui);
 +        Tcl_SetMainLoop(runTclEventLoop);
 +        zAppName = "tclshext_tk";
 +      }
 +#endif /* ..TK */
 +      Tcl_SetVar2Ex(interp, "::argv0", NULL,
 +                    Tcl_NewStringObj(zAppName,-1), TCL_GLOBAL_ONLY);
 +      Tcl_SetVar2Ex(interp, "::argc", NULL,
 +                    Tcl_NewIntObj(tnarg), TCL_GLOBAL_ONLY);
 +      Tcl_SetVar2Ex(interp, "::argv", NULL, targv, TCL_GLOBAL_ONLY);
 +      Tcl_SetVar2Ex(interp, "::tcl_interactive", NULL,
 +                    Tcl_NewIntObj(SHX_HELPER(nowInteractive)(psx)),
 +                    TCL_GLOBAL_ONLY);
 +      Tcl_SetVar2Ex(interp, "::isHosted", NULL,
 +                    Tcl_NewIntObj(1), TCL_GLOBAL_ONLY);
 +      pShExtLink->eid = sqlite3_tclshext_init;
 +    }
 +    if( rc==SQLITE_OK ){
 +      pShExtLink->extensionDestruct = Tcl_TakeDown;
 +      pShExtLink->pvExtensionObject = &interpKeep;
 +    }else{
 +      Tcl_TakeDown(&interpKeep);
 +    }
 +    return rc;
 +  }
 +}
diff --cc manifest
index 4488c50091f4917ec0a722a07791fde5124ab9a6,4b561cb6fca2fe3a4d3868a4eea834d77ab4cca8..4b129556485c4ee6bbf2eb43c4d12a4181e14089
+++ b/manifest
@@@ -1,11 -1,11 +1,11 @@@
- C Get\sCLI\sconformed\sto\srevised\sdoc\sfor\sit,\sand\stake\srecent\sfixes.
- D 2022-04-24T20:00:15.974
 -C Fix\sa\sharmless\stypo\sin\sa\scomment.
 -D 2022-04-27T18:38:46.859
++C Get\sMSVC\sbuild\sgoing\sfor\ssqlite3x.exe\sand\sextensions.
++D 2022-04-29T18:36:17.701
  F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
  F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
  F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
 -F Makefile.in b210ad2733317f1a4353085dfb9d385ceec30b0e6a61d20a5accabecac6b1949
 +F Makefile.in 3a2d4e3a13c497b24b92fa36e93f80dc49844f1ca62e5a7848b899afe7105a37
  F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
- F Makefile.msc 689726a2785027e0eb34ea9ce8e67ac94bc4aebbaa6def20ddb6fa9f7b0c43b5
 -F Makefile.msc b28a8a7a977e7312f6859f560348e1eb110c21bd6cf9fab0d16537c0a514eef3
++F Makefile.msc 2c59950b86b58b5a4d78151883ee52a014f006d08ea18783c87509fddc3cde27
  F README.md 2dd87a5c1d108b224921f3dd47dea567973f706e1f6959386282a626f459a70c
  F VERSION fa8e7d2d1cc962f9e14c6d410387cf75860ee139462763fda887c1be4261f824
  F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
@@@ -330,7 -328,6 +330,7 @@@ F ext/misc/showauth.c 732578f0fe4ce42d5
  F ext/misc/spellfix.c 94df9bbfa514a563c1484f684a2df3d128a2f7209a84ca3ca100c68a0163e29f
  F ext/misc/sqlar.c 0ace5d3c10fe736dc584bf1159a36b8e2e60fab309d310cd8a0eecd9036621b6
  F ext/misc/stmt.c 35063044a388ead95557e4b84b89c1b93accc2f1c6ddea3f9710e8486a7af94a
- F ext/misc/tclshext.c.in e0e4387b95410172385488feaea2f622ad1d398f1ac6a4275874791fbfd31d4e
++F ext/misc/tclshext.c.in 8c939f7048630f73608133e6873e2c963eb8bb4bdfdf65144a3c6f2ca3d62510
  F ext/misc/templatevtab.c 8a16a91a5ceaccfcbd6aaaa56d46828806e460dd194965b3f77bf38f14b942c4
  F ext/misc/totype.c fa4aedeb07f66169005dffa8de3b0a2b621779fd44f85c103228a42afa71853b
  F ext/misc/uint.c 053fed3bce2e89583afcd4bf804d75d659879bbcedac74d0fa9ed548839a030b
@@@ -552,13 -549,12 +553,13 @@@ F src/pcache1.c 54881292a9a5db202b2c0ac
  F src/pragma.c d1aead03e8418ff586c7cfca344c50a914b8eb06abd841e8e91a982d823671da
  F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7
  F src/prepare.c fd940149c691684e7c1073c3787a7170e44852b02d1275d2e30a5b58e89cfcaf
- F src/printf.c 05d8dfd2018bc4fc3ddb8b37eb97ccef7abf985643fa1caebdcf2916ca90fa32
+ F src/printf.c 512574910a45341c8ad244bd3d4939968ebdfde215645b676fff01cc46e90757
  F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
- F src/resolve.c 18d99e7146852d6064559561769fcca0743eb32b14a97da6dbed373a30ee0e76
+ F src/resolve.c f72bb13359dd5a74d440df25f320dc2c1baff5cde4fc9f0d1bc3feba90b8932a
  F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
- F src/select.c 7c106b3f36d483242b0a9c696614cd53d6f29e1ac81da6a3f0e9ea92f4211cc3
- F src/shell.c.in 6029fb2305e92ebbca8c3e531031239f310dcfad6801ddc4a57dfebd6efc87d3
+ F src/select.c cc1a7581403fc074eee85283ba8d81de50a831ae175cb65a5751be00f621c0d5
 -F src/shell.c.in ae0a6fae983caac6f8c824733f0599dfdf7b3a7e8efdef3cb5e3ab2e457ffc35
++F src/shell.c.in e0064544eee8542d2f3dc9803c1ae08592a0d9d68cfa7cfb3054ad73f53d1d67
 +F src/shext_linkage.h 0fe0dd831eb1e77b7ae8bc37e80811546531f82b17ef998e08960646cbaf5191
  F src/sqlite.h.in 2a35f62185eb5e7ecc64a2f68442b538ce9be74f80f28a00abc24837edcf1c17
  F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
  F src/sqlite3ext.h f49e28c25bd941e79794db5415fdf7b202deb3bc072ed6f1ed273d578703684e
@@@ -607,8 -603,6 +608,8 @@@ F src/test_quota.h 2a8ad1952d1d2ca9af0c
  F src/test_rtree.c 671f3fae50ff116ef2e32a3bf1fe21b5615b4b7b
  F src/test_schema.c f5d6067dfc2f2845c4dd56df63e66ee826fb23877855c785f75cc2ca83fd0c1b
  F src/test_server.c a2615049954cbb9cfb4a62e18e2f0616e4dc38fe
- F src/test_shellext_c.c b611e468bb99ba27df9211f8b6f1179cbd77f29390a0c7b846eb0d00e14dd1dc
- F src/test_shellext_cpp.cpp d33cc8538ebe76f10114c8c9e5d65caa4e183fb461a4311145739f0affb15566
++F src/test_shellext_c.c 966a97842ab65ffd64a62a9862073139652a4f772fd20f982583892e3c35b8c1
++F src/test_shellext_cpp.cpp 21410915d2985b5e82f63c5b3cb417bc17430c6336c92a0b86c25f3775776130
  F src/test_sqllog.c 540feaea7280cd5f926168aee9deb1065ae136d0bbbe7361e2ef3541783e187a
  F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e
  F src/test_syscall.c 1073306ba2e9bfc886771871a13d3de281ed3939
@@@ -1777,8 -1774,8 +1783,8 @@@ F test/walslow.test c05c68d4dc2700a982f
  F test/walthread.test 14b20fcfa6ae152f5d8e12f5dc8a8a724b7ef189f5d8ef1e2ceab79f2af51747
  F test/walvfs.test bccb3e0d235ef85e276f491d34db32c9ada1ea67be8d9f10aabe7b30319ec656
  F test/wapp.tcl b440cd8cf57953d3a49e7ee81e6a18f18efdaf113b69f7d8482b0710a64566ec
 -F test/wapptest.tcl 899594e25684861d5b0c0880fb012364def50ef8097041b8ddf74be5ba7fa270 x
 +F test/wapptest.tcl c55a4669d02e982921f1dc37ababa21eb14123ec73a396692165e927c16ff061 x
- F test/where.test f114842c1851d257a26770f2ad55119b084001c0e1b8c214f886f45152d37cd8
+ F test/where.test 8c6bbd0cae8feae142a7946e3484a802fa566bacf38452b1c3e48cb77321f9a4
  F test/where2.test 03c21a11e7b90e2845fc3c8b4002fc44cc2797fa74c86ee47d70bd7ea4f29ed6
  F test/where3.test 5b4ffc0ac2ea0fe92f02b1244b7531522fe4d7bccf6fa8741d54e82c10e67753
  F test/where4.test 4a371bfcc607f41d233701bdec33ac2972908ba8
@@@ -1954,10 -1951,8 +1960,8 @@@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a9
  F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
  F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
  F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
- P e21964480f68152d1c1607f75895354241ec3ea7247f1ee3c2a20c454beec919
- Q +77aed89192bdbad819ac17bf5d08728278a9b8cbbbef1d805df230caff79b417
- Q +dfd2100bc4f316825fd199b347849d1a2b941837f9eedcf36f3c3d280692b991
- R f2ef8541ab9fdfb6f43958528715f12d
 -P eb59c46a5aed69bc6fd096997bf24c082e533c1085439f6ec1fbe5ff78e8b374
 -R f499c3fddca86db7a0183d3ecdb0953b
 -U drh
 -Z 7426364b9684e9a6fe822d40fffa4b25
++P 60e85c7e7130724a426fab11552ed2aa8e9280f53a219c7e15e0ae0efcaded57 e1f4a115df34e45cf1bcf98961c699b582f564a58a979e95853b219bda06212c
++R 66992ed57c5d75ba400df4ac1d7afb51
 +U larrybr
- Z 8c1136d387cc01bce4a929f190c32a8e
++Z 08242a9a87ce842bca989bccc1923f6d
  # Remove this line to create a well-formed Fossil manifest.
diff --cc manifest.uuid
index 5e05ac9e5255ed6eb986870e67582b9855e22308,1daa831936fb6c7199910e2ec2d8d551256ff748..d0097b95bf06fbb56302c35ec542c2d1cf6d096f
@@@ -1,1 -1,1 +1,1 @@@
- 60e85c7e7130724a426fab11552ed2aa8e9280f53a219c7e15e0ae0efcaded57
 -e1f4a115df34e45cf1bcf98961c699b582f564a58a979e95853b219bda06212c
++cd5e57e0fe4ef2a59c1202b66f4e774f6642fa826cfe3840a786104841dde2f6
diff --cc src/shell.c.in
index a7d59fa94aa97a785e195ed4e6ec0d9ef6f245dd,95a32f524717d1346a101ffd7bc3eb34aa2928bd..6b333cbd75b7a973bb7b6903f1dadbc12a225322
@@@ -1188,109 -1066,6 +1188,110 @@@ struct EQPGraph 
    char zPrefix[100];    /* Graph prefix */
  };
  
- static char startupDir[PATH_MAX+1] = {0};
 +/* By default, omit the extension options that are not done yet.
 + * "SHELL_EXTENSIONS" is short for "Some shell extensions are built in." */
 +#ifndef SHELL_OMIT_EXTENSIONS
 +# define SHELL_OMIT_EXTENSIONS 4
 +# define SHELL_EXTENSIONS 1
 +#else
 +# define SHELL_EXTENSIONS \
 +  (0!=((~SHELL_OMIT_EXTENSIONS) & SHELL_ALL_EXTENSIONS))
 +#endif
 +
 +#ifdef SQLITE_OMIT_LOAD_EXTENSION
 +# define SHELL_OMIT_LOAD_EXTENSION 1
 +#else
 +# define SHELL_OMIT_LOAD_EXTENSION 0
 +#endif
 +
 +/* Selectively omit features with one PP variable. Value is true iff
 +** either x is not defined or defined with 0 in bitnum bit position.
 +*/
 +#define NOT_IFDEF_BIT(x,bitnum) (x? (!(x & (1<<bitnum))) : !(x+0))
 +
 +/* Whether build will include extended input parsing option */
 +#define SHEXT_PARSING_BIT 0
 +#define SHELL_EXTENDED_PARSING \
 +  NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_PARSING_BIT)
 +/* Whether build will include runtime extension via .shxload */
 +#define SHEXT_DYNEXT_BIT 1
 +#define SHELL_DYNAMIC_EXTENSION ( !SHELL_OMIT_LOAD_EXTENSION \
 +  && NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_DYNEXT_BIT) )
 +/* Whether build will include expansion of variables in dot-commands */
 +#define SHEXT_VAREXP_BIT 2
 +#define SHELL_VARIABLE_EXPANSION \
 +  NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_VAREXP_BIT)
 +
 +#define SHELL_ALL_EXTENSIONS \
 +  (1<<SHEXT_PARSING_BIT)+(1<<SHEXT_DYNEXT_BIT)+(1<<SHEXT_VAREXP_BIT)
 +
 +/* Runtime test for shell extended parsing, given ShellInState pointer */
 +#if SHELL_EXTENDED_PARSING
 +# define SHEXT_PARSING(psi) ((psi->bExtendedDotCmds&(1<<SHEXT_PARSING_BIT))!=0)
 +#else
 +# define SHEXT_PARSING(psi) 0
 +#endif
 +
 +/* Runtime test for shell variable expansion, given ShellInState pointer */
 +#if SHELL_EXTENDED_PARSING
 +# define SHEXT_VAREXP(psi) ((psi->bExtendedDotCmds&(1<<SHEXT_VAREXP_BIT))!=0)
 +#else
 +# define SHEXT_VAREXP(psi) 0
 +#endif
 +
 +#if SHELL_DYNAMIC_EXTENSION
 +
 +/* This is only used to support extensions that need this information.
 + * For example, they might need to locate and load related files. */
- # define initStartupDir() (_getwd(startupDir)!=0)
 +# if defined(_WIN32) || defined(WIN32)
-   int ixe = (zPgm)? strlen(zPgm)-1 : 0;
++static char startupDir[MAX_PATH+1] = {0};
++# define initStartupDir() (_getcwd(startupDir,MAX_PATH)!=0)
 +#  define IS_PATH_SEP(c) ((c)=='/'||(c)=='\\')
 +# else
++static char startupDir[PATH_MAX+1] = {0};
 +# define initStartupDir() (getcwd(startupDir, sizeof(startupDir))!=0)
 +  /* Above useless expression avoids an "unused result" warning. */
 +#  define IS_PATH_SEP(c) ((c)=='/')
 +# endif
 +
 +# ifndef SHELL_OMIT_EXTBYNAME
 +/* Is a program invocation name one used for a shell to start as extensible? */
 +static int isExtendedBasename(const char *zPgm){
++  int ixe = (zPgm)? (int)strlen(zPgm)-1 : 0;
 +  if( ixe==0 ) return 0;
 +  while( ixe>=0 && !IS_PATH_SEP(zPgm[ixe]) ) --ixe;
 +  /* ixe is just before the basename with extension(s) */
 +  return sqlite3_strnicmp(&zPgm[ixe+1], "sqlite3x", 8)==0;
 +}
 +# else
 +#  define isExtendedBasename(pathname) 0
 +# endif
 +
 +/* Tracking and use info for loaded shell extensions
 + * An instance is kept for each shell extension that is currently loaded.
 + * They are kept in a simple list (aka dynamic array), index into which
 + * is used internally to get the extension's object. These indices are
 + * kept in the dbShell and updated there as the list content changes.
 + */
 +typedef struct ShExtInfo {
 +  ExtensionId extId;        /* The xInit function pointer */
 +  void (*extDtor)(void *);  /* Extension shutdown on exit or unload */
 +  void *pvExtObj;           /* Passed to extDtor(...) at shutdown */
 +  /* Each shell extension library registers 0 or more of its extension
 +   * implementations, interfaces to which are kept in below dynamic.
 +   * arrays. The dbShell DB keeps indices into these arrays and into
 +   * an array of this struct's instances to facilitate lookup by name
 +   * of pointers to the implementations. */
 +  int numDotCommands;
 +  DotCommand **ppDotCommands;
 +  int numExportHandlers;
 +  ExportHandler **ppExportHandlers;
 +  int numImportHandlers;
 +  ImportHandler **ppImportHandlers;
 +  DotCommand *pUnknown;  /* .unknown registered for this extension */
 +} ShExtInfo;
 +#endif
 +
  /* Parameters affecting columnar mode result display (defaulting together) */
  typedef struct ColModeOpts {
    int iWrap;            /* In columnar modes, wrap lines reaching this limit */
@@@ -7362,30 -7217,31 +7363,7 @@@ static void *shellMalloc(int *pRc, sqli
    return pRet;
  }
  
--/*
- ** If pRc!=0 and *pRc is not SQLITE_OK when this function is called, it is a
- ** no-op. Otherwise, zFmt is treated as a printf() style string. The result
- ** of formatting it along with any trailing arguments is written into a
 -** If *pRc is not SQLITE_OK when this function is called, it is a no-op.
 -** Otherwise, zFmt is treated as a printf() style string. The result of
 -** formatting it along with any trailing arguments is written into a 
--** buffer obtained from sqlite3_malloc(), and pointer to which is returned.
--** It is the responsibility of the caller to eventually free this buffer
--** using a call to sqlite3_free().
- **
- ** If an OOM error occurs, (*pRc) is set to SQLITE_NOMEM (if pRc!=0)
- ** and a NULL pointer returned.
 -** 
 -** If an OOM error occurs, (*pRc) is set to SQLITE_NOMEM and a NULL 
 -** pointer returned.
--*/
--static char *shellMPrintf(int *pRc, const char *zFmt, ...){
--  char *z = 0;
-   if( pRc==0 || *pRc==SQLITE_OK ){
 -  if( *pRc==SQLITE_OK ){
--    va_list ap;
--    va_start(ap, zFmt);
--    z = sqlite3_vmprintf(zFmt, ap);
--    va_end(ap);
-     if( z==0 && pRc!=0 ){
 -    if( z==0 ){
--      *pRc = SQLITE_NOMEM;
--    }
--  }
--  return z;
--}
 -
++static char *shellMPrintf(int *pRc, const char *zFmt, ...);
  
  /*
  ** When running the ".recover" command, each output table, and the special
@@@ -7663,6700 -7518,3782 +7641,6724 @@@ static RecoverTable *recoverOrphanTable
    }
    return pTab;
  }
 -
 +#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
+ /*
 -** This function is called to recover data from the database. A script
 -** to construct a new database containing all recovered data is output
 -** on stream pState->out.
++** If pRc!=0 and *pRc is not SQLITE_OK when this function is called, it is a
++** no-op. Otherwise, zFmt is treated as a printf() style string. The result
++** of formatting it along with any trailing arguments is written into a
++** buffer obtained from sqlite3_malloc(), and pointer to which is returned.
++** It is the responsibility of the caller to eventually free this buffer
++** using a call to sqlite3_free().
++**
++** If an OOM error occurs, (*pRc) is set to SQLITE_NOMEM (if pRc!=0)
++** and a NULL pointer returned.
+ */
 -static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){
 -  int rc = SQLITE_OK;
 -  sqlite3_stmt *pLoop = 0;        /* Loop through all root pages */
 -  sqlite3_stmt *pPages = 0;       /* Loop through all pages in a group */
 -  sqlite3_stmt *pCells = 0;       /* Loop through all cells in a page */
 -  const char *zRecoveryDb = "";   /* Name of "recovery" database */
 -  const char *zLostAndFound = "lost_and_found";
++static char *shellMPrintf(int *pRc, const char *zFmt, ...){
++  char *z = 0;
++  if( pRc==0 || *pRc==SQLITE_OK ){
++    va_list ap;
++    va_start(ap, zFmt);
++    z = sqlite3_vmprintf(zFmt, ap);
++    va_end(ap);
++    if( z==0 && pRc!=0 ){
++      *pRc = SQLITE_NOMEM;
++    }
++  }
++  return z;
++}
 +
 +static DotCmdRC
 +writeDb( char *azArg[], int nArg, ShellExState *psx, char **pzErr ){
 +  int rc = 0;
 +  const char *zDestFile = 0;
 +  const char *zDb = 0;
 +  sqlite3 *pDest;
 +  sqlite3_backup *pBackup;
 +  int j;
 +  int bAsync = 0;
 +  const char *zVfs = 0;
 +  if( ISS(psx)->bSafeMode ) return DCR_AbortError;
 +  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{
 +        return DCR_Unknown|j;
 +      }
 +    }else if( zDestFile==0 ){
 +      zDestFile = azArg[j];
 +    }else if( zDb==0 ){
 +      zDb = zDestFile;
 +      zDestFile = azArg[j];
 +    }else{
 +      return DCR_TooMany|j;
 +    }
 +  }
 +  if( zDestFile==0 ){
 +    return DCR_Missing;
 +  }
 +  if( zDb==0 ) zDb = "main";
 +  rc = sqlite3_open_v2(zDestFile, &pDest,
 +                       SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs);
 +  if( rc!=SQLITE_OK ){
 +    *pzErr = smprintf("cannot open \"%s\"\n", zDestFile);
 +    close_db(pDest);
 +    return DCR_Error;
 +  }
 +  if( bAsync ){
 +    sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
 +                 0, 0, 0);
 +  }
 +  open_db(psx, 0);
 +  pBackup = sqlite3_backup_init(pDest, "main", DBX(psx), zDb);
 +  if( pBackup==0 ){
 +    *pzErr = smprintf("%s failed, %s\n", azArg[0], sqlite3_errmsg(pDest));
 +    close_db(pDest);
 +    return DCR_Error;
 +  }
 +  while(  (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
 +  sqlite3_backup_finish(pBackup);
 +  if( rc==SQLITE_DONE ){
 +    rc = 0;
 +  }else{
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(pDest));
 +    rc = 1;
 +  }
 +  close_db(pDest);
 +  return DCR_Ok|rc;
 +}
 +
 +/*
 + * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it.
 + * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE,
 + *   close db and set it to 0, and return the columns spec, to later
 + *   be sqlite3_free()'ed by the caller.
 + * The return is 0 when either:
 + *   (a) The db was not initialized and zCol==0 (There are no columns.)
 + *   (b) zCol!=0  (Column was added, db initialized as needed.)
 + * The 3rd argument, pRenamed, references an out parameter. If the
 + * pointer is non-zero, its referent will be set to a summary of renames
 + * done if renaming was necessary, or set to 0 if none was done. The out
 + * string (if any) must be sqlite3_free()'ed by the caller.
 + */
 +#ifdef SHELL_DEBUG
 +#define rc_err_oom_die(rc) \
 +  if( rc==SQLITE_NOMEM ) shell_check_oom(0); \
 +  else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \
 +    fprintf(STD_ERR,"E:%d\n",rc), assert(0)
 +#else
 +static void rc_err_oom_die(int rc){
 +  if( rc==SQLITE_NOMEM ) shell_check_oom(0);
 +  assert(rc==SQLITE_OK||rc==SQLITE_DONE);
 +}
 +#endif
 +
 +#ifdef SHELL_COLFIX_DB /* If this is set, the DB can be in a file. */
 +static char zCOL_DB[] = SHELL_STRINGIFY(SHELL_COLFIX_DB);
 +#else  /* Otherwise, memory is faster/better for the transient DB. */
 +static const char *zCOL_DB = ":memory:";
 +#endif
 +
 +/* Define character (as C string) to separate generated column ordinal
 + * from protected part of incoming column names. This defaults to "_"
 + * so that incoming column identifiers that did not need not be quoted
 + * remain usable without being quoted. It must be one character.
 + */
 +#ifndef SHELL_AUTOCOLUMN_SEP
 +# define AUTOCOLUMN_SEP "_"
 +#else
 +# define AUTOCOLUMN_SEP SHELL_STRINGIFY(SHELL_AUTOCOLUMN_SEP)
 +#endif
 +
 +static char *zAutoColumn(const char *zColNew, sqlite3 **pDb, char **pzRenamed){
 +  /* Queries and D{D,M}L used here */
 +  static const char * const zTabMake = "\
 +CREATE TABLE ColNames(\
 + cpos INTEGER PRIMARY KEY,\
 + name TEXT, nlen INT, chop INT, reps INT, suff TEXT);\
 +CREATE VIEW RepeatedNames AS \
 +SELECT DISTINCT t.name FROM ColNames t \
 +WHERE t.name COLLATE NOCASE IN (\
 + SELECT o.name FROM ColNames o WHERE o.cpos<>t.cpos\
 +);\
 +";
 +  static const char * const zTabFill = "\
 +INSERT INTO ColNames(name,nlen,chop,reps,suff)\
 + VALUES(iif(length(?1)>0,?1,'?'),max(length(?1),1),0,0,'')\
 +";
 +  static const char * const zHasDupes = "\
 +SELECT count(DISTINCT (substring(name,1,nlen-chop)||suff) COLLATE NOCASE)\
 + <count(name) FROM ColNames\
 +";
 +#ifdef SHELL_COLUMN_RENAME_CLEAN
 +  static const char * const zDedoctor = "\
 +UPDATE ColNames SET chop=iif(\
 +  (substring(name,nlen,1) BETWEEN '0' AND '9')\
 +  AND (rtrim(name,'0123456790') glob '*"AUTOCOLUMN_SEP"'),\
 + nlen-length(rtrim(name, '"AUTOCOLUMN_SEP"0123456789')),\
 + 0\
 +)\
 +";
 +#endif
 +  static const char * const zSetReps = "\
 +UPDATE ColNames AS t SET reps=\
 +(SELECT count(*) FROM ColNames d \
 + WHERE substring(t.name,1,t.nlen-t.chop)=substring(d.name,1,d.nlen-d.chop)\
 + COLLATE NOCASE\
 +)\
 +";
 +#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
 +  static const char * const zColDigits = "\
 +SELECT CAST(ceil(log(count(*)+0.5)) AS INT) FROM ColNames \
 +";
 +#else
 +  /* Counting on SQLITE_MAX_COLUMN < 100,000 here. (32767 is the hard limit.) */
 +  static const char * const zColDigits = "\
 +SELECT CASE WHEN (nc < 10) THEN 1 WHEN (nc < 100) THEN 2 \
 + WHEN (nc < 1000) THEN 3 WHEN (nc < 10000) THEN 4 \
 + ELSE 5 FROM (SELECT count(*) AS nc FROM ColNames) \
 +";
 +#endif
 +  static const char * const zRenameRank =
 +#ifdef SHELL_COLUMN_RENAME_CLEAN
 +    "UPDATE ColNames AS t SET suff="
 +    "iif(reps>1, printf('%c%0*d', '"AUTOCOLUMN_SEP"', $1, cpos), '')"
 +#else /* ...RENAME_MINIMAL_ONE_PASS */
 +"WITH Lzn(nlz) AS (" /* Find minimum extraneous leading 0's for uniqueness */
 +"  SELECT 0 AS nlz"
 +"  UNION"
 +"  SELECT nlz+1 AS nlz FROM Lzn"
 +"  WHERE EXISTS("
 +"   SELECT 1"
 +"   FROM ColNames t, ColNames o"
 +"   WHERE"
 +"    iif(t.name IN (SELECT * FROM RepeatedNames),"
 +"     printf('%s"AUTOCOLUMN_SEP"%s',"
 +"      t.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,t.cpos),2)),"
 +"     t.name"
 +"    )"
 +"    ="
 +"    iif(o.name IN (SELECT * FROM RepeatedNames),"
 +"     printf('%s"AUTOCOLUMN_SEP"%s',"
 +"      o.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,o.cpos),2)),"
 +"     o.name"
 +"    )"
 +"    COLLATE NOCASE"
 +"    AND o.cpos<>t.cpos"
 +"   GROUP BY t.cpos"
 +"  )"
 +") UPDATE Colnames AS t SET"
 +" chop = 0," /* No chopping, never touch incoming names. */
 +" suff = iif(name IN (SELECT * FROM RepeatedNames),"
 +"  printf('"AUTOCOLUMN_SEP"%s', substring("
 +"   printf('%.*c%0.*d',(SELECT max(nlz) FROM Lzn)+1,'0',1,t.cpos),2)),"
 +"  ''"
 +" )"
 +#endif
 +    ;
 +  static const char * const zCollectVar = "\
 +SELECT\
 + '('||x'0a'\
 + || group_concat(\
 +  cname||' TEXT',\
 +  ','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\
 + ||')' AS ColsSpec \
 +FROM (\
 + SELECT cpos, printf('\"%w\"',printf('%!.*s%s', nlen-chop,name,suff)) AS cname \
 + FROM ColNames ORDER BY cpos\
 +)";
 +  static const char * const zRenamesDone =
 +    "SELECT group_concat("
 +    " printf('\"%w\" to \"%w\"',name,printf('%!.*s%s', nlen-chop, name, suff)),"
 +    " ','||x'0a')"
 +    "FROM ColNames WHERE suff<>'' OR chop!=0"
 +    ;
 +  int rc;
 +  sqlite3_stmt *pStmt = 0;
 +  assert(pDb!=0);
 +  if( zColNew ){
 +    /* Add initial or additional column. Init db if necessary. */
 +    if( *pDb==0 ){
 +      if( SQLITE_OK!=sqlite3_open(zCOL_DB, pDb) ) return 0;
 +#ifdef SHELL_COLFIX_DB
 +      if(*zCOL_DB!=':')
 +        sqlite3_exec(*pDb,"drop table if exists ColNames;"
 +                     "drop view if exists RepeatedNames;",0,0,0);
 +#endif
 +      rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0);
 +      rc_err_oom_die(rc);
 +    }
 +    assert(*pDb!=0);
 +    rc = sqlite3_prepare_v2(*pDb, zTabFill, -1, &pStmt, 0);
 +    rc_err_oom_die(rc);
 +    rc = sqlite3_bind_text(pStmt, 1, zColNew, -1, 0);
 +    rc_err_oom_die(rc);
 +    rc = sqlite3_step(pStmt);
 +    rc_err_oom_die(rc);
 +    sqlite3_finalize(pStmt);
 +    return 0;
 +  }else if( *pDb==0 ){
 +    return 0;
 +  }else{
 +    /* Formulate the columns spec, close the DB, zero *pDb. */
 +    char *zColsSpec = 0;
 +    int hasDupes = db_int(*pDb, zHasDupes);
 +    int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0;
 +    if( hasDupes ){
 +#ifdef SHELL_COLUMN_RENAME_CLEAN
 +      rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0);
 +      rc_err_oom_die(rc);
 +#endif
 +      rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0);
 +      rc_err_oom_die(rc);
 +      rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0);
 +      rc_err_oom_die(rc);
 +      sqlite3_bind_int(pStmt, 1, nDigits);
 +      rc = sqlite3_step(pStmt);
 +      sqlite3_finalize(pStmt);
 +      assert(rc==SQLITE_DONE);
 +    }
 +    /* This assert is maybe overly cautious for above de-dup DML, but that can
 +     * be replaced via #define's. So this check is made for debug builds. */
 +    assert(db_int(*pDb, zHasDupes)==0);
 +    rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0);
 +    rc_err_oom_die(rc);
 +    rc = sqlite3_step(pStmt);
 +    if( rc==SQLITE_ROW ){
 +      zColsSpec = smprintf("%s", sqlite3_column_text(pStmt, 0));
 +    }else{
 +      zColsSpec = 0;
 +    }
 +    if( pzRenamed!=0 ){
 +      if( !hasDupes ) *pzRenamed = 0;
 +      else{
 +        sqlite3_finalize(pStmt);
 +        if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0)
 +            && SQLITE_ROW==sqlite3_step(pStmt) ){
 +          *pzRenamed = smprintf("%s", sqlite3_column_text(pStmt, 0));
 +        }else
 +          *pzRenamed = 0;
 +      }
 +    }
 +    sqlite3_finalize(pStmt);
 +    sqlite3_close(*pDb);
 +    *pDb = 0;
 +    return zColsSpec;
 +  }
 +}
 +
 +static FILE *currentOutputFile(ShellExState *p){
 +  return ISS(p)->out;
 +}
 +
 +#if SHELL_DYNAMIC_EXTENSION
 +/* Ensure there is room in loaded extension info list for one being loaded.
 + * On exit, psi->ixExtPending can be used to index into psi->pShxLoaded.
 + */
 +static ShExtInfo *pending_ext_info(ShellInState *psi){
 +  int ixpe = psi->ixExtPending;
 +  assert(ixpe!=0);
 +  if( ixpe >= psi->numExtLoaded ){
 +    psi->pShxLoaded = sqlite3_realloc(psi->pShxLoaded,
 +                                      (ixpe+1)*sizeof(ShExtInfo));
 +    shell_check_oom(psi->pShxLoaded);
 +    ++psi->numExtLoaded;
 +    memset(psi->pShxLoaded+ixpe, 0, sizeof(ShExtInfo));
 +  }
 +  return &psi->pShxLoaded[ixpe];
 +}
 +
 +/* Register a dot-command, to be called during extension load/init. */
 +static int register_dot_command(ShellExState *p,
 +                                ExtensionId eid, DotCommand *pMC){
 +  ShellInState *psi = ISS(p);
 +  ShExtInfo *psei = pending_ext_info(psi);
 +  const char *zSql
 +    = "INSERT INTO "SHELL_DISP_TAB"(name, extIx, cmdIx) VALUES(?, ?, ?)";
 +  int ie = psi->ixExtPending;
 +  assert(psi->pShxLoaded!=0 && p->dbShell!=0);
 +  if( pMC==0 ) return SQLITE_ERROR;
 +  else{
 +    const char *zName = pMC->pMethods->name(pMC);
 +    sqlite3_stmt *pStmt;
 +    int nc = psei->numDotCommands;
 +    int rc;
 +    if( psei->extId!=0 && psei->extId!=eid ) return SQLITE_MISUSE;
 +    psei->extId = eid;
 +    rc = sqlite3_prepare_v2(p->dbShell, zSql, -1, &pStmt, 0);
 +    if( rc!=SQLITE_OK ) return rc;
 +    psei->ppDotCommands
 +      = sqlite3_realloc(psei->ppDotCommands, (nc+1)*sizeof(DotCommand *));
 +    shell_check_oom(psei->ppDotCommands);
 +    sqlite3_bind_text(pStmt, 1, zName, -1, 0);
 +    sqlite3_bind_int(pStmt, 2, ie);
 +    sqlite3_bind_int(pStmt, 3, nc);
 +    rc = sqlite3_step(pStmt);
 +    sqlite3_finalize(pStmt);
 +    if( rc==SQLITE_DONE ){
 +      psei->ppDotCommands[nc++] = pMC;
 +      psei->numDotCommands = nc;
 +      notify_subscribers(psi, NK_NewDotCommand, pMC);
 +      if( strcmp("unknown", zName)==0 ){
 +        psi->pUnknown = pMC;
 +        psei->pUnknown = pMC;
 +      }
 +      return SQLITE_OK;
 +    }else{
 +      psei->ppDotCommands[nc] = 0;
 +    }
 +  }
 +  return SQLITE_ERROR;
 +}
 +
 +/* Register an output data display (or other disposition) mode */
 +static int register_exporter(ShellExState *p,
 +                             ExtensionId eid, ExportHandler *pEH){
 +  return SQLITE_ERROR;
 +}
 +
 +/* Register an import variation from (various sources) for .import */
 +static int register_importer(ShellExState *p,
 +                             ExtensionId eid, ImportHandler *pIH){
 +  return SQLITE_ERROR;
 +}
 +
 +/* See registerScripting API in shext_linkage.h */
 +static int register_scripting(ShellExState *p, ExtensionId eid,
 +                              ScriptSupport *pSS){
 +  ShellInState *psi = ISS(p);
 +  if( psi->scriptXid!=0 || psi->script!=0 ){
 +    /* Scripting support already provided. Only one provider is allowed. */
 +    return SQLITE_BUSY;
 +  }
 +  if( eid==0 || pSS==0 || psi->ixExtPending==0 ){
 +    /* Scripting addition allowed only when sqlite3_*_init() runs. */
 +    return SQLITE_MISUSE;
 +  }
 +  psi->script = pSS;
 +  psi->scriptXid = eid;
 +  return SQLITE_OK;
 +}
 +
 +/* See registerAdHocCommand API in shext_linkage.h re detailed behavior.
 + * Depending on zHelp==0, either register or unregister ad-hoc treatment
 + * of zName for this extension (identified by eid.)
 + */
 +static int register_adhoc_command(ShellExState *p, ExtensionId eid,
 +                                  const char *zName, const char *zHelp){
 +  ShellInState *psi = ISS(p);
 +  u8 bRegNotRemove = zHelp!=0;
 +  const char *zSql = bRegNotRemove
 +    ? "INSERT OR REPLACE INTO "SHELL_AHELP_TAB
 +    "(name, extIx, helpText) VALUES(?, ?, ?||?||?)"
 +    : "DELETE FROM "SHELL_AHELP_TAB" WHERE name=? AND extIx=?";
 +  sqlite3_stmt *pStmt;
 +  int rc, ie;
 +
 +  assert(psi->pShxLoaded!=0 && p->dbShell!=0);
 +  for( ie=psi->numExtLoaded-1; ie>0; --ie ){
 +    if( psi->pShxLoaded[ie].extId==eid ) break;
 +  }
 +  if( !zName || ie==0 || psi->pShxLoaded[ie].pUnknown==0 ) return SQLITE_MISUSE;
 +  rc = sqlite3_prepare_v2(p->dbShell, zSql, -1, &pStmt, 0);
 +  if( rc!=SQLITE_OK ) return rc;
 +  sqlite3_bind_text(pStmt, 1, zName, -1, 0);
 +  sqlite3_bind_int(pStmt, 2, ie);
 +  if( bRegNotRemove ){
 +    int nc = strlen30(zHelp);
 +    char cLead = *zHelp;
 +    /* Add leading '.' if no help classifier present. */
 +    const char *zCL = (cLead!='.' && cLead!=',')? "." : "";
 +    /* Add trailing newline if not already there. */
 +    const char *zLE = (nc>0 && zHelp[nc-1]!='\n')? "\n" : "";
 +    sqlite3_bind_text(pStmt, 3, zCL, -1, 0);
 +    sqlite3_bind_text(pStmt, 4, zHelp, -1, 0);
 +    sqlite3_bind_text(pStmt, 5, zLE, -1, 0);
 +  }
 +  rc = sqlite3_step(pStmt);
 +  sqlite3_finalize(pStmt);
 +  return (rc==SQLITE_DONE)? SQLITE_OK : SQLITE_ERROR;
 +}
 +
 +/*
 + * Subscribe to (or unsubscribe from) messages about various changes. 
 + * Unsubscribe, effected when nkMin==NK_Unsubscribe, always succeeds.
 + * Return SQLITE_OK on success, or one of these error codes:
 + * SQLITE_ERROR when the nkMin value is unsupported by this host;
 + * SQLITE_NOMEM when a required allocation failed; or
 + * SQLITE_MISUSE when the provided eid or eventHandler is invalid.
 + */
 +static int subscribe_events(ShellExState *p, ExtensionId eid, void *pvUserData,
 +                            NoticeKind nkMin, ShellEventNotify eventHandler){
 +  ShellInState *psi = ISS(p);
 +  struct EventSubscription *pes = psi->pSubscriptions;
 +  struct EventSubscription *pesLim = pes + psi->numSubscriptions;
 +  if( nkMin==NK_Unsubscribe ){
 +    /* unsubscribe (if now subscribed) */
 +    while( pes < pesLim ){
 +      if( (eventHandler==0 || eventHandler==pes->eventHandler)
 +          && (pes->eid==0 || pes->eid==eid)
 +          && (eid!=0 || eventHandler!=0 ||/*for shell use*/ pvUserData==p ) ){
 +        int nLeft = pesLim - pes;
 +        assert(pes->eventHandler!=0);
 +        pes->eventHandler(pes->pvUserData, NK_Unsubscribe, pes->eid, p);
 +        if( nLeft>1 ) memmove(pes, pes+1, (nLeft-1)*sizeof(*pes));
 +        --pesLim;
 +        --psi->numSubscriptions;
 +      }else{
 +        ++pes;
 +      }
 +    }
 +    if( psi->numSubscriptions==0 ){
 +      sqlite3_free(psi->pSubscriptions);
 +      psi->pSubscriptions = 0;
 +    }
 +    return SQLITE_OK;
 +  }else{
 +    /* subscribe only if minimum NoticeKind supported by this host */
 +    if( nkMin > NK_CountOf ) return SQLITE_ERROR;
 +    if( eventHandler==0 || eid==0 ) return SQLITE_MISUSE;
 +    while( pes < pesLim ){
 +      /* Never add duplicate handlers, but may renew their user data. */
 +      if( pes->eid==eid && pes->eventHandler==eventHandler ){
 +        pes->pvUserData = pvUserData;
 +        return SQLITE_OK;
 +      }
 +      ++pes;
 +    }
 +    assert(pes==pesLim);
 +    pes = sqlite3_realloc(psi->pSubscriptions,
 +                          (psi->numSubscriptions+1)*sizeof(*pes));
 +    if( pes==0 ) return SQLITE_NOMEM;
 +    psi->pSubscriptions = pes;
 +    pes += (psi->numSubscriptions++);
 +    pes->eid = eid;
 +    pes->pvUserData = pvUserData;
 +    pes->eventHandler = eventHandler;
 +    return SQLITE_OK;
 +  }
 +}
 +
 +/* 
 + * Unsubscribe all event listeners having an ExtensionId > 0. This is
 + * done just prior closing the shell DB (when dynamic extensions will
 + * be unloaded and accessing them in any way is good for a crash.)
 + */
 +static void unsubscribe_extensions(ShellInState *psi){
 +  ShellExState *psx = XSS(psi);
 +  int esix = 0;
 +
 +  if( psi->numExtLoaded<=1 ) return; /* Ignore shell pseudo-extension. */
 +  while( esix<psi->numSubscriptions ){
 +    struct EventSubscription *pes = psi->pSubscriptions+esix;
 +    if( pes->eid > 0 ){
 +      int nsin = psi->numSubscriptions;
 +      subscribe_events(psx, pes->eid, psx, NK_Unsubscribe, 0);
 +      esix = esix + 1 + (psi->numSubscriptions - nsin);
 +    }else ++esix;
 +  }
 +}
 +
 +static struct InSource *currentInputSource(ShellExState *p){
 +  return ISS(p)->pInSource;
 +}
 +
 +static int nowInteractive(ShellExState *p){
 +  return INSOURCE_IS_INTERACTIVE(ISS(p)->pInSource);
 +}
 +
 +static const char *shellInvokedAs(void){
 +  return Argv0;
 +}
 +
 +static const char *shellStartupDir(void){
 +  return startupDir;
 +}
 +
 +static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths);
 +static DotCommand * findDotCommand(const char *, ShellExState *, int *);
 +static DotCmdRC runDotCommand(DotCommand*, char *[], int na, ShellExState*);
 +
 +static ExtensionHelpers extHelpers = {
 +  13,
 +  {
 +    failIfSafeMode,
 +    currentOutputFile,
 +    currentInputSource,
 +    strLineGet,
 +    findDotCommand,
 +    runDotCommand,
 +    setColumnWidths,
 +    nowInteractive,
 +    shellInvokedAs,
 +    shellStartupDir,
 +    one_input_line,
 +    free_input_line,
 +    sqlite3_enable_load_extension,
 +    0
 +  }
 +};
 +
 +static ShellExtensionAPI shellExtAPI = {
 +  &extHelpers, 6, {
 +    register_dot_command,
 +    register_exporter,
 +    register_importer,
 +    register_scripting,
 +    subscribe_events,
 +    register_adhoc_command,
 +    0
 +  }
 +};
 +
 +/* This SQL function provides a way for a just-loaded shell extension to
 + * obtain a ShellExtensionLink pointer from the shell core while using
 + * the same sqlite3_load_extension API used for SQLite extensions.
 + *
 + * (It is also useful for debugging a shell extension, as a breakpoint
 + * on it will be hit soon after loading and before real work is done.)
 + */
 +static void shell_linkage(
 +  sqlite3_context *context,
 +  int argc,
 +  sqlite3_value **argv
 +){
 +  int linkKind = 0;
 +  void *pv;
 +  if( argc>0 ){
 +    linkKind = sqlite3_value_int(argv[0]);
 +  }
 +  switch (linkKind){
 +  case 0:
 +    pv = sqlite3_user_data(context);
 +    break;
 +  case 1:
 +    pv = &extHelpers;
 +    break;
 +  case 2:
 +    pv = &shellExtAPI;
 +    break;
 +  default:
 +    pv = 0;
 +  }
 +  if( pv==0 ) sqlite3_result_null(context);
 +  else sqlite3_result_pointer(context, pv, SHELLEXT_API_POINTERS, 0);
 +}
 +
 +/* Do the initialization needed for use of dbShell for command lookup
 + * and dispatch and for I/O handler lookup and dispatch.
 + */
 +static int begin_db_dispatch(ShellExState *psx){
 +  ShellInState *psi = ISS(psx);
 +  sqlite3_stmt *pStmt = 0;
 +  int ic, rc1, rc2;
 +  int rc = 0;
 +  char *zErr = 0;
 +  const char *zSql;
 +  ShExtInfo sei = {0};
 +  /* Consider: Store these dynamic arrays in the DB as indexed-into blobs. */
 +  assert(psx->dbShell==0 || (psi->numExtLoaded==0 && psi->pShxLoaded==0));
 +  rc = ensure_shell_db(psx);
 +  if( rc!=SQLITE_OK ){
 +    utf8_printf(STD_ERR, "Error: Shell DB uncreatable. Best to .quit soon.\n");
 +    return SQLITE_ERROR;
 +  }
 +  if( ensure_dispatch_table(psx)!=SQLITE_OK ) return 1;
 +
 +  psi->pShxLoaded = (ShExtInfo *)sqlite3_malloc(2*sizeof(ShExtInfo));
 +  sei.ppDotCommands
 +    = (DotCommand **)sqlite3_malloc((numCommands+2)*sizeof(DotCommand *));
 +  sei.ppExportHandlers
 +    = (ExportHandler **)sqlite3_malloc(2*sizeof(ExportHandler *));
 +  sei.ppImportHandlers
 +    = (ImportHandler **)sqlite3_malloc(2*sizeof(ImportHandler *));
 +  if( sei.ppDotCommands==0||sei.ppExportHandlers==0||sei.ppImportHandlers==0
 +      || psi->pShxLoaded==0 ){
 +    shell_out_of_memory();
 +  }
 +  sei.numExportHandlers = 0;
 +  sei.numImportHandlers = 0;
-   for( ic=0; ic<numCommands; ++ic ){
++  for( ic=0; ic<(int)numCommands; ++ic ){
 +    sei.ppDotCommands[ic] = builtInCommand(ic);
 +  }
 +  sei.numDotCommands = ic;
 +  psi->pShxLoaded[psi->numExtLoaded++] = sei;
 +  zSql = "INSERT INTO "SHELL_DISP_TAB"(name, extIx, cmdIx) VALUES(?, 0, ?)";
 +  rc1 = sqlite3_prepare_v2(psx->dbShell, zSql, -1, &pStmt, 0);
 +  rc2 = sqlite3_exec(psx->dbShell, "BEGIN TRANSACTION", 0, 0, &zErr);
 +  if( rc1!=SQLITE_OK || rc2!=SQLITE_OK ) return 1;
 +  assert(sei.numDotCommands>0);
 +  for( ic=0; ic<sei.numDotCommands; ++ic ){
 +    DotCommand *pmc = sei.ppDotCommands[ic];
 +    const char *zName = pmc->pMethods->name(pmc);
 +    sqlite3_reset(pStmt);
 +    sqlite3_bind_text(pStmt, 1, zName, -1, 0);
 +    sqlite3_bind_int(pStmt, 2, ic);
 +    rc = sqlite3_step(pStmt);
 +    if( rc!=SQLITE_DONE ){
 +      sqlite3_exec(psx->dbShell, "ABORT", 0, 0, 0);
 +      break;
 +    }
 +  }
 +  sqlite3_finalize(pStmt);
 +  if( rc!=SQLITE_DONE ) return 1;
 +  rc = sqlite3_exec(psx->dbShell, "COMMIT", 0, 0, &zErr);
 +  sqlite3_enable_load_extension(psx->dbShell, 1);
 +  sqlite3_free(zErr);
 +  psi->bDbDispatch = 1;
 +
 +  return SQLITE_OK;
 +}
 +
 +/* Call one loaded extension's destructors, in reverse order
 + * of their objects' creation, then free the tracking dyna-arrays.
 + */
 +static void free_one_shext_tracking(ShExtInfo *psei){
 +  int j;
 +  if( psei->ppDotCommands!=0 ){
 +    for( j=psei->numDotCommands; j>0; --j ){
 +      DotCommand *pmc = psei->ppDotCommands[j-1];
 +      if( pmc->pMethods->destruct!=0 ) pmc->pMethods->destruct(pmc);
 +    }
 +    sqlite3_free(psei->ppDotCommands);
 +  }
 +  if( psei->ppExportHandlers!=0 ){
 +    for( j=psei->numExportHandlers; j>0; --j ){
 +      ExportHandler *peh = psei->ppExportHandlers[j-1];
 +      if( peh->pMethods->destruct!=0 ) peh->pMethods->destruct(peh);
 +    }
 +    sqlite3_free(psei->ppExportHandlers);
 +  }
 +  if( psei->ppImportHandlers!=0 ){
 +    for( j=psei->numImportHandlers; j>0; --j ){
 +      ImportHandler *pih = psei->ppImportHandlers[j-1];
 +      if( pih->pMethods->destruct!=0 ) pih->pMethods->destruct(pih);
 +    }
 +    sqlite3_free(psei->ppImportHandlers);
 +  }
 +  if( psei->extDtor!=0 ){
 +    psei->extDtor(psei->pvExtObj);
 +  }
 +}
 +
 +/* Call all existent loaded extension destructors, in reverse order of their
 + * objects' creation, except for scripting support which is done last,
 + * then free the tracking dyna-arrays.
 + */
 +static void free_all_shext_tracking(ShellInState *psi){
 +  if( psi->pShxLoaded!=0 ){
 +    int i = psi->numExtLoaded;
 +    while( i>1 ){
 +      ShExtInfo *psei = &psi->pShxLoaded[--i];
 +      free_one_shext_tracking(psei);
 +      if( psi->scriptXid!=0 && psi->scriptXid==psei->extId ){
 +        assert(psi->script!=0);
 +        if (psi->script->pMethods->destruct){
 +          psi->script->pMethods->destruct(psi->script);
 +        }
 +        psi->script = 0;
 +        psi->scriptXid = 0;
 +      }
 +    }
 +    sqlite3_free(psi->pShxLoaded);
 +    psi->pShxLoaded = 0;
 +    psi->numExtLoaded = 0;
 +  }
 +}
 +
 +static DotCommand *command_by_index(ShellInState *psi, int extIx, int cmdIx){
 +  assert(extIx>=0);
 +  if( extIx>=0 && extIx<psi->numExtLoaded ){
 +    ShExtInfo *psei = & psi->pShxLoaded[extIx];
 +    if( cmdIx>=0 && cmdIx<psei->numDotCommands ){
 +      return psei->ppDotCommands[cmdIx];
 +    }
 +  }
 +  return 0;
 +}
 +
 +static int load_shell_extension(ShellExState *psx, const char *zFile,
 +                                const char *zProc, char **pzErr,
 +                                int nLoadArgs, char **azLoadArgs){
 +  ShellExtensionLink shxLink = {
 +    sizeof(ShellExtensionLink),
 +    &shellExtAPI,
 +    psx, /* pSXS */
 +    0,   /* zErrMsg */
 +    0,   /* ExtensionId */
 +    0,   /* Extension destructor */
 +    0,   /* Extension data ref */
 +    nLoadArgs, azLoadArgs /* like-named members */
 +  }; //extDtor(pvExtObj)
 +  ShellInState *psi = ISS(psx);
 +  /* save script support state for possible fallback if load fails */
 +  ScriptSupport *pssSave = psi->script;
 +  ExtensionId ssiSave = psi->scriptXid;
 +  int rc;
 +
 +  if( pzErr ) *pzErr = 0;
 +  if( psx->dbShell==0 || ISS(psx)->numExtLoaded==0 ){
 +    rc = begin_db_dispatch(psx);
 +    if( rc!=SQLITE_OK ) return rc;
 +    assert(ISS(psx)->numExtLoaded==1 && psx->dbShell!=0);
 +  }
 +  psi->ixExtPending = psi->numExtLoaded;
 +  sqlite3_create_function(psx->dbShell, "shext_pointer", 1,
 +                          SQLITE_DIRECTONLY|SQLITE_UTF8,
 +                          &shxLink, shell_linkage, 0, 0);
 +  rc = sqlite3_load_extension(psx->dbShell, zFile, zProc, &shxLink.zErrMsg);
 +  sqlite3_create_function(psx->dbShell, "shext_pointer", 1,
 +                          SQLITE_DIRECTONLY|SQLITE_UTF8,
 +                          0, 0, 0, 0); /* deregister */
 +  if( pzErr!=0 ) *pzErr = shxLink.zErrMsg;
 +  if( rc==SQLITE_OK ){
 +    /* Keep extension's id and destructor for later disposal. */
 +    ShExtInfo *psei = pending_ext_info(psi);
 +    if( psei->extId!=0 && psei->extId!=shxLink.eid ) rc = SQLITE_MISUSE;
 +    psei->extId = shxLink.eid;
 +    psei->extDtor = shxLink.extensionDestruct;
 +    psei->pvExtObj = shxLink.pvExtensionObject;
 +  }else{
 +    /* Release all resources extension might have registered before failing. */
 +    if( psi->ixExtPending < psi->numExtLoaded ){
 +      free_one_shext_tracking(psi->pShxLoaded+psi->ixExtPending);
 +      --psi->numExtLoaded;
 +    }
 +    /* And make it unwind any scripting linkage it might have setup. */
 +    if( psi->script!=0 ) psi->script->pMethods->destruct(psi->script);
 +    psi->script = pssSave;
 +    psi->scriptXid = ssiSave;
 +  }
 +  psi->ixExtPending = 0;
 +  if( rc!=SQLITE_OK ){
 +    if( rc==SQLITE_MISUSE && pzErr!=0 ){
 +      *pzErr = sqlite3_mprintf("extension id mismatch %z\n", *pzErr);
 +    }
 +    rc = SQLITE_ERROR;
 +  }
 +  return rc;
 +}
 +#endif
 +
 +/* Dot-command implementation functions are defined in this section.
 +COMMENT  Define dot-commands and provide for their dispatch and .help text.
 +COMMENT  These should be kept in command name order for coding convenience
 +COMMENT  except where dot-commands share implementation. (The ordering
 +COMMENT  required for dispatch and help text is effected regardless.) The
 +COMMENT  effect of this configuration can be seen in generated output or by
 +COMMENT  executing tool/mkshellc.tcl --parameters (or --details or --help).
 +COMMENT  Generally, this section defines dispatchable functions inline and
 +COMMENT  causes collection of command_table entry initializers, to be later
 +COMMENT  emitted by a mkshellc macro. (See EMIT_DOTCMD_INIT further on.)
 +** All dispatchable dot-command execute functions have this signature:
 +static int someCommand(char *azArg[], int nArg, ShellExState *p, char **pzErr);
 +*/
 +DISPATCH_CONFIG[
 +  RETURN_TYPE=DotCmdRC
 +  STORAGE_CLASS=static
 +  ARGS_SIGNATURE=char *$arg4\[\], int $arg5, ShellExState *$arg6, char **$arg7
 +  DISPATCH_ENTRY={ "$cmd", ${cmd}Command, $arg1, $arg2, $arg3 },
-   DOTCMD_INIT=DOT_CMD_INFO(${cmd}, $arg1,$arg2,$arg3, <HT0>, <HT1>),
++  DOTCMD_INIT={ DOT_CMD_INFO(${cmd}, $arg1,$arg2,$arg3), {<HT0>, <HT1>}, 0 },
 +  CMD_CAPTURE_RE=^\s*{\s*"(\w+)"
 +  DISPATCHEE_NAME=${cmd}Command
 +  DC_ARG1_DEFAULT=[string length $cmd]
 +  DC_ARG2_DEFAULT=0
 +  DC_ARG3_DEFAULT=0
 +  DC_ARG4_DEFAULT=azArg
 +  DC_ARG5_DEFAULT=nArg
 +  DC_ARG6_DEFAULT=p
 +  DC_ARG7_DEFAULT=pzErr
 +  DC_ARG_COUNT=8
 +];
 +
 +CONDITION_COMMAND(seeargs !defined(SHELL_OMIT_SEEARGS));
 +/*****************
 + * The .seeargs command
 + */
 +COLLECT_HELP_TEXT[
 +  ",seeargs                 Echo arguments suffixed with |",
 +];
 +DISPATCHABLE_COMMAND( seeargs ? 0 0 azArg nArg p ){
 +  int ia = 0;
 +  for (ia=1; ia<nArg; ++ia)
 +    raw_printf(ISS(p)->out, "%s%s", azArg[ia], (ia==nArg-1)? "|\n" : "|");
 +  return DCR_Ok;
 +}
 +
 +CONDITION_COMMAND(archive !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB));
 +/*****************
 + * The .archive command
 + */
 +COLLECT_HELP_TEXT[
 +  ".archive ...             Manage SQL archives",
 +  "   Each command must have exactly one of the following options:",
 +  "     -c, --create               Create a new archive",
 +  "     -u, --update               Add or update files with changed mtime",
 +  "     -i, --insert               Like -u but always add even if unchanged",
 +  "     -r, --remove               Remove files from archive",
 +  "     -t, --list                 List contents of archive",
 +  "     -x, --extract              Extract files from archive",
 +  "   Optional arguments:",
 +  "     -v, --verbose              Print each filename as it is processed",
 +  "     -f FILE, --file FILE       Use archive FILE (default is current db)",
 +  "     -a FILE, --append FILE     Open FILE using the apndvfs VFS",
 +  "     -C DIR, --directory DIR    Read/extract files from directory DIR",
 +  "     -g, --glob                 Use glob matching for names in archive",
 +  "     -n, --dryrun               Show the SQL that would have occurred",
 +  "   Examples:",
 +  "     .ar -cf ARCHIVE foo bar  # Create ARCHIVE from files foo and bar",
 +  "     .ar -tf ARCHIVE          # List members of ARCHIVE",
 +  "     .ar -xvf ARCHIVE         # Verbosely extract files from ARCHIVE",
 +  "   See also:",
 +  "      http://sqlite.org/cli.html#sqlite_archive_support",
 +];
 +DISPATCHABLE_COMMAND( archive ? 0 0 azArg nArg p ){
 +  open_db(p, 0);
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +  return arDotCommand(p, 0, azArg, nArg);
 +}
 +
 +/*****************
 + * The .auth command
 + */
 +CONDITION_COMMAND(auth !defined(SQLITE_OMIT_AUTHORIZATION));
 +COLLECT_HELP_TEXT[
 +  ".auth ON|OFF             Show authorizer callbacks",
 +];
 +DISPATCHABLE_COMMAND( auth 3 2 2 azArg nArg p ){
 +  open_db(p, 0);
 +  if( booleanValue(azArg[1]) ){
 +    sqlite3_set_authorizer(DBX(p), shellAuth, p);
 +  }else if( ISS(p)->bSafeModeFuture ){
 +    sqlite3_set_authorizer(DBX(p), safeModeAuth, p);
 +  }else{
 +    sqlite3_set_authorizer(DBX(p), 0, 0);
 +  }
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * The .backup and .save commands (aliases for each other)
 + * These defer to writeDb in the dispatch table, so are not here.
 + */
 +COLLECT_HELP_TEXT[
 +  ".backup ?DB? FILE        Backup DB (default \"main\") to FILE",
 +  "   Options:",
 +  "     --append               Use the appendvfs",
 +  "     --async                Write the FILE without journal and fsync()",
 +  ".save ?DB? FILE          Write DB (default \"main\") to FILE",
 +  "   Options:",
 +  "     --append               Use the appendvfs",
 +  "     --async                Write the FILE without journal and fsync()",
 +];
 +DISPATCHABLE_COMMAND( backup 4 2 5 ){
 +  return writeDb( azArg, nArg, p, pzErr);
 +}
 +DISPATCHABLE_COMMAND( save 3 2 5 ){
 +  return writeDb( azArg, nArg, p, pzErr);
 +}
 +
 +/*****************
 + * The .bail command
 + */
 +COLLECT_HELP_TEXT[
 +  ".bail on|off             Stop after hitting an error.  Default OFF",
 +];
 +DISPATCHABLE_COMMAND( bail 3 2 2 ){
 +  bail_on_error = booleanValue(azArg[1]);
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * The .binary and .cd commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".binary on|off           Turn binary output on or off.  Default OFF",
 +  ".cd DIRECTORY            Change the working directory to DIRECTORY",
 +];
 +DISPATCHABLE_COMMAND( binary 3 2 2 ){
 +  if( booleanValue(azArg[1]) ){
 +    setBinaryMode(ISS(p)->out, 1);
 +  }else{
 +    setTextMode(ISS(p)->out, 1);
 +  }
 +  return DCR_Ok;
 +}
 +
 +DISPATCHABLE_COMMAND( cd ? 2 2 ){
 +  int rc=0;
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +#if defined(_WIN32) || defined(WIN32)
 +  wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
 +  rc = !SetCurrentDirectoryW(z);
 +  sqlite3_free(z);
 +#else
 +  rc = chdir(azArg[1]);
 +#endif
 +  if( rc ){
 +    utf8_printf(STD_ERR, "Cannot change to directory \"%s\"\n", azArg[1]);
 +    rc = 1;
 +  }
 +  return DCR_Ok|rc;
 +}
 +
 +/* The ".breakpoint" command causes a call to the no-op routine named
 + * test_breakpoint(). It is undocumented.
 +*/
 +COLLECT_HELP_TEXT[
 +  ",breakpoint              calls test_breakpoint(). (a debugging aid)",
 +];
 +DISPATCHABLE_COMMAND( breakpoint 3 1 1 ){
 +  test_breakpoint();
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * The .changes, .check, .clone and .connection commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".changes on|off          Show number of rows changed by SQL",
 +  ",check GLOB              Fail if output since .testcase does not match",
 +  ".clone NEWDB             Clone data into NEWDB from the existing database",
 +  ".connection [close] [#]  Open or close an auxiliary database connection",
 +];
 +DISPATCHABLE_COMMAND( changes 3 2 2 ){
 +  setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( check 3 0 0 ){
 +  /* Cancel output redirection, if it is currently set (by .testcase)
 +  ** Then read the content of the testcase-out.txt file and compare against
 +  ** azArg[1].  If there are differences, report an error and exit.
 +  */
 +  char *zRes = 0;
 +  int rc = 0;
 +  DotCmdRC rv = DCR_Ok;
 +  output_reset(ISS(p));
 +  if( nArg!=2 ){
 +    return DCR_ArgWrong;
 +  }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){
 +    *pzErr = shellMPrintf(&rc, "Error: cannot read 'testcase-out.txt'");
 +    rv = DCR_Return;
 +  }else if( testcase_glob(azArg[1],zRes)==0 ){
 +    *pzErr =
 +      shellMPrintf(&rc,
 +                   "testcase-%s FAILED\n Expected: [%s]\n      Got: [%s]\n",
 +                   ISS(p)->zTestcase, azArg[1], zRes);
 +    rv = DCR_Error;
 +  }else{
 +    utf8_printf(STD_OUT, "testcase-%s ok\n", ISS(p)->zTestcase);
 +    ISS(p)->nCheck++;
 +  }
 +  sqlite3_free(zRes);
 +  return (rc==SQLITE_NOMEM)? DCR_Abort : rv;
 +}
 +DISPATCHABLE_COMMAND( clone ? 2 2 ){
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +  tryToClone(p, azArg[1]);
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( connection ? 1 4 ){
 +  ShellInState *psi = ISS(p);
 +  if( nArg==1 ){
 +    /* List available connections */
 +    int i;
 +    for(i=0; i<ArraySize(psi->aAuxDb); i++){
 +      const char *zFile = psi->aAuxDb[i].zDbFilename;
 +      if( psi->aAuxDb[i].db==0 && psi->pAuxDb!=&psi->aAuxDb[i] ){
 +        zFile = "(not open)";
 +      }else if( zFile==0 ){
 +        zFile = "(memory)";
 +      }else if( zFile[0]==0 ){
 +        zFile = "(temporary-file)";
 +      }
 +      if( psi->pAuxDb == &psi->aAuxDb[i] ){
 +        utf8_printf(STD_OUT, "ACTIVE %d: %s\n", i, zFile);
 +      }else if( psi->aAuxDb[i].db!=0 ){
 +        utf8_printf(STD_OUT, "       %d: %s\n", i, zFile);
 +      }
 +    }
 +  }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){
 +    int i = azArg[1][0] - '0';
 +    if( psi->pAuxDb != &psi->aAuxDb[i] && i>=0 && i<ArraySize(psi->aAuxDb) ){
 +      psi->pAuxDb->db = DBI(psi);
 +      psi->pAuxDb = &psi->aAuxDb[i];
 +#if SHELL_DYNAMIC_EXTENSION
 +      if( DBI(psi)!=0 ) notify_subscribers(psi, NK_DbUserVanishing, DBI(psi));
 +#endif
 +      globalDb = DBI(psi) = psi->pAuxDb->db;
 +#if SHELL_DYNAMIC_EXTENSION
 +      if( DBI(psi)!=0 ) notify_subscribers(psi, NK_DbUserAppeared, DBI(psi));
 +#endif
 +      psi->pAuxDb->db = 0;
 +    }
 +  }else if( nArg==3 && strcmp(azArg[1], "close")==0
 +            && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){
 +    int i = azArg[2][0] - '0';
 +    if( i<0 || i>=ArraySize(psi->aAuxDb) ){
 +      /* No-op */
 +    }else if( psi->pAuxDb == &psi->aAuxDb[i] ){
 +      raw_printf(STD_ERR, "cannot close the active database connection\n");
 +      return DCR_Error;
 +    }else if( psi->aAuxDb[i].db ){
 +      session_close_all(psi, i);
 +      close_db(psi->aAuxDb[i].db);
 +      psi->aAuxDb[i].db = 0;
 +    }
 +  }else{
 +    return DCR_ArgWrong;
 +  }
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * The .databases, .dbconfig and .dbinfo commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".databases               List names and files of attached databases",
 +  ".dbconfig ?op? ?val?     List or change sqlite3_db_config() options",
 +  ".dbinfo ?DB?             Show status information about the database",
 +];
 +/* Allow garbage arguments on this, to be ignored. */
 +DISPATCHABLE_COMMAND( databases 2 1 0 ){
 +  int rc;
 +  char **azName = 0;
 +  int nName = 0;
 +  sqlite3_stmt *pStmt;
 +  sqlite3 *db;
    int i;
 -  int nOrphan = -1;
 -  RecoverTable *pOrphan = 0;
 +  open_db(p, 0);
 +  db = DBX(p);
 +  rc = sqlite3_prepare_v2(db, "PRAGMA database_list", -1, &pStmt, 0);
 +  if( rc ){
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
 +    rc = 1;
 +  }else{
 +    while( sqlite3_step(pStmt)==SQLITE_ROW ){
 +      const char *zSchema = (const char *)sqlite3_column_text(pStmt,1);
 +      const char *zFile = (const char*)sqlite3_column_text(pStmt,2);
 +      if( zSchema==0 || zFile==0 ) continue;
 +      azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*));
 +      shell_check_oom(azName);
 +      azName[nName*2] = strdup(zSchema);
 +      shell_check_oom(azName[nName*2]);
 +      azName[nName*2+1] = strdup(zFile);
 +      shell_check_oom(azName[nName*2+1]);
 +      nName++;
 +    }
 +  }
 +  sqlite3_finalize(pStmt);
 +  for(i=0; i<nName; i++){
 +    int eTxn = sqlite3_txn_state(db, azName[i*2]);
 +    int bRdonly = sqlite3_db_readonly(db, azName[i*2]);
 +    const char *z = azName[i*2+1];
 +    utf8_printf(ISS(p)->out, "%s: %s %s%s\n",
 +                azName[i*2],
 +                z && z[0] ? z : "\"\"",
 +                bRdonly ? "r/o" : "r/w",
 +                eTxn==SQLITE_TXN_NONE ? "" :
 +                eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
 +    free(azName[i*2]);
 +    free(azName[i*2+1]);
 +  }
 +  sqlite3_free(azName);
 +  return DCR_Ok|(rc!=0);
 +}
 +DISPATCHABLE_COMMAND( dbconfig 3 1 3 ){
 +  static const struct DbConfigChoices {
 +    const char *zName;
 +    int op;
 +  } aDbConfig[] = {
 +    { "defensive",          SQLITE_DBCONFIG_DEFENSIVE             },
 +    { "dqs_ddl",            SQLITE_DBCONFIG_DQS_DDL               },
 +    { "dqs_dml",            SQLITE_DBCONFIG_DQS_DML               },
 +    { "enable_fkey",        SQLITE_DBCONFIG_ENABLE_FKEY           },
 +    { "enable_qpsg",        SQLITE_DBCONFIG_ENABLE_QPSG           },
 +    { "enable_trigger",     SQLITE_DBCONFIG_ENABLE_TRIGGER        },
 +    { "enable_view",        SQLITE_DBCONFIG_ENABLE_VIEW           },
 +    { "fts3_tokenizer",     SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER },
 +    { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE    },
 +    { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT    },
 +    { "load_extension",     SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION },
 +    { "no_ckpt_on_close",   SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE      },
 +    { "reset_database",     SQLITE_DBCONFIG_RESET_DATABASE        },
 +    { "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(DBX(p), aDbConfig[ii].op, booleanValue(azArg[2]), 0);
 +    }
 +    sqlite3_db_config(DBX(p), aDbConfig[ii].op, -1, &v);
 +    utf8_printf(ISS(p)->out, "%19s %s\n",
 +                aDbConfig[ii].zName, v ? "on" : "off");
 +    if( nArg>1 ) break;
 +  }
 +  if( nArg>1 && ii==ArraySize(aDbConfig) ){
 +    *pzErr = smprintf("Error: unknown dbconfig \"%s\"\n"
 +                      "Enter \".dbconfig\" with no arguments for a list\n",
 +                      azArg[1]);
 +    return DCR_ArgWrong;
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( dbinfo 3 1 2 ){
 +  return shell_dbinfo_command(p, nArg, azArg);
 +}
 +
 +/*****************
 + * The .dump, .echo and .eqp commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".dump ?OBJECTS?          Render database content as SQL",
 +  "   Options:",
 +  "     --data-only            Output only INSERT statements",
 +  "     --newlines             Allow unescaped newline characters in output",
 +  "     --nosys                Omit system tables (ex: \"sqlite_stat1\")",
 +  "     --preserve-rowids      Include ROWID values in the output",
 +  "     --schema SCHEMA        Dump table(s) from given SCHEMA",
 +  "   OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump",
 +  "   Additional LIKE patterns can be given in subsequent arguments",
 +  ".echo on|off             Turn command echo on or off",
 +  ".eqp on|off|full|...     Enable or disable automatic EXPLAIN QUERY PLAN",
 +  "   Other Modes:",
 +#ifdef SQLITE_DEBUG
 +  "      test                  Show raw EXPLAIN QUERY PLAN output",
 +  "      trace                 Like \"full\" but enable \"PRAGMA vdbe_trace\"",
 +#endif
 +  "      trigger               Like \"full\" but also show trigger bytecode",
 +];
 +DISPATCHABLE_COMMAND( dump ? 1 2 ){
 +  ShellInState *psi = ISS(p);
 +  char *zLike = 0;
 +  char *zSchema = "main";
 +  char *zSql;
 +  int i;
 +  int savedShowHeader = psi->showHeader;
 +  int savedShellFlags = psi->shellFlgs;
 +  ShellClearFlag(p,
 +     SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo
 +     |SHFLG_DumpDataOnly|SHFLG_DumpNoSys);
 +  for(i=1; i<nArg; i++){
 +    if( azArg[i][0]=='-' ){
 +      const char *z = azArg[i]+1;
 +      if( z[0]=='-' ) z++;
 +      if( strcmp(z,"preserve-rowids")==0 ){
 +#ifdef SQLITE_OMIT_VIRTUALTABLE
 +        *pzErr = smprintf("The --preserve-rowids option is not compatible"
 +                          " with SQLITE_OMIT_VIRTUALTABLE\n");
 +        sqlite3_free(zLike);
 +        return DCR_ArgWrong;
 +#else
 +        ShellSetFlag(p, SHFLG_PreserveRowid);
 +#endif
 +      }else{
 +        if( strcmp(z,"newlines")==0 ){
 +          ShellSetFlag(p, SHFLG_Newlines);
 +        }else if( strcmp(z,"data-only")==0 ){
 +          ShellSetFlag(p, SHFLG_DumpDataOnly);
 +        }else if( strcmp(z,"nosys")==0 ){
 +          ShellSetFlag(p, SHFLG_DumpNoSys);
 +        }else if( strcmp(z,"schema")==0 && ++i<nArg ){
 +          zSchema = azArg[i];
 +        }else{
 +          *pzErr = smprintf("Unknown option \"%s\" on \".dump\"\n", azArg[i]);
 +          sqlite3_free(zLike);
 +          return DCR_ArgWrong;
 +        }
 +      }
 +    }else{
 +      /* azArg[i] contains a LIKE pattern. This ".dump" request should
 +      ** only dump data for tables for which either the table name matches
 +      ** the LIKE pattern, or the table appears to be a shadow table of
 +      ** a virtual table for which the name matches the LIKE pattern.
 +      */
 +      char *zExpr = smprintf(
 +                    "name LIKE %Q ESCAPE '\\' OR EXISTS ("
 +                    "  SELECT 1 FROM %w.sqlite_schema WHERE "
 +                    "    name LIKE %Q ESCAPE '\\' AND"
 +                    "    sql LIKE 'CREATE VIRTUAL TABLE%%' AND"
 +                    "    substr(o.name, 1, length(name)+1) == (name||'_')"
 +                    ")", azArg[i], zSchema, azArg[i]
 +                    );
 +
 +      if( zLike ){
 +        zLike = smprintf("%z OR %z", zLike, zExpr);
 +      }else{
 +        zLike = zExpr;
 +      }
 +    }
 +  }
 +
 +  open_db(p, 0);
 +
 +  if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 +    /* When playing back a "dump", the content might appear in an order
 +    ** which causes immediate foreign key constraints to be violated.
 +    ** So disable foreign-key constraint enforcement to prevent problems. */
 +    raw_printf(psi->out, "PRAGMA foreign_keys=OFF;\n");
 +    raw_printf(psi->out, "BEGIN TRANSACTION;\n");
 +  }
 +  psi->writableSchema = 0;
 +  psi->showHeader = 0;
 +  /* Set writable_schema=ON since doing so forces SQLite to initialize
 +  ** as much of the schema as it can even if the sqlite_schema table is
 +  ** corrupt. */
 +  sqlite3_exec(DBX(p), "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
 +  psi->nErr = 0;
 +  if( zLike==0 ) zLike = smprintf("true");
 +  zSql = smprintf("SELECT name, type, sql FROM %w.sqlite_schema AS o "
 +                  "WHERE (%s) AND type=='table' AND sql NOT NULL"
 +                  " ORDER BY tbl_name='sqlite_sequence', rowid",
 +                  zSchema, zLike);
 +  run_schema_dump_query(psi,zSql);
 +  sqlite3_free(zSql);
 +  if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 +    zSql = smprintf(
 +             "SELECT sql FROM sqlite_schema AS o "
 +             "WHERE (%s) AND sql NOT NULL"
 +             "  AND type IN ('index','trigger','view')",
 +             zLike
 +           );
 +    run_table_dump_query(psi, zSql);
 +    sqlite3_free(zSql);
 +  }
 +  sqlite3_free(zLike);
 +  if( psi->writableSchema ){
 +    raw_printf(psi->out, "PRAGMA writable_schema=OFF;\n");
 +    psi->writableSchema = 0;
 +  }
 +  sqlite3_exec(DBX(p), "PRAGMA writable_schema=OFF;", 0, 0, 0);
 +  sqlite3_exec(DBX(p), "RELEASE dump;", 0, 0, 0);
 +  if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 +    raw_printf(psi->out, psi->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n");
 +  }
 +  psi->showHeader = savedShowHeader;
 +  psi->shellFlgs = savedShellFlags;
 +
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( echo ? 2 2 ){
 +  setOrClearFlag(p, SHFLG_Echo, azArg[1]);
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( eqp ? 0 0 ){
 +  ShellInState *psi = ISS(p);
 +  if( nArg==2 ){
 +    psi->autoEQPtest = 0;
 +    if( psi->autoEQPtrace ){
 +      if( DBX(p) ) sqlite3_exec(DBX(p), "PRAGMA vdbe_trace=OFF;", 0, 0, 0);
 +      psi->autoEQPtrace = 0;
 +    }
 +    if( strcmp(azArg[1],"full")==0 ){
 +      psi->autoEQP = AUTOEQP_full;
 +    }else if( strcmp(azArg[1],"trigger")==0 ){
 +      psi->autoEQP = AUTOEQP_trigger;
 +#ifdef SQLITE_DEBUG
 +    }else if( strcmp(azArg[1],"test")==0 ){
 +      psi->autoEQP = AUTOEQP_on;
 +      psi->autoEQPtest = 1;
 +    }else if( strcmp(azArg[1],"trace")==0 ){
 +      psi->autoEQP = AUTOEQP_full;
 +      psi->autoEQPtrace = 1;
 +      open_db(p, 0);
 +      sqlite3_exec(DBX(p), "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0);
 +      sqlite3_exec(DBX(p), "PRAGMA vdbe_trace=ON;", 0, 0, 0);
 +#endif
 +    }else{
 +      psi->autoEQP = (u8)booleanValue(azArg[1]);
 +    }
 +  }else{
 +    return DCR_ArgWrong;
 +  }
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * The .cease, .exit and .quit commands
 + * These are together so that their differing effects are apparent.
 + */
 +CONDITION_COMMAND(cease defined(SHELL_CEASE));
 +COLLECT_HELP_TEXT[
 +  ".cease ?CODE?            Cease shell operation, with optional return code",
 +  "   Return code defaults to 0, otherwise is limited to non-signal values",
 +  ".exit ?CODE?             Exit shell program, maybe with return-code CODE",
 +  "   Exit immediately if CODE != 0, else functions as \"quit this input\"",
 +  ".quit                    Quit processing this input or script",
 +];
 +DISPATCHABLE_COMMAND( cease 4 1 2 ){
 +  /* .cease effects an exit, always. Only the exit code is variable. */
 +  int rc = 0;
 +  if( nArg>1 ){
 +    rc = (int)integerValue(azArg[1]);
 +    if( rc>0x7f ) rc = 0x7f;
 +  }
 +  p->shellAbruptExit = 0x100|rc;
 +  return DCR_Exit;
 +}
 +DISPATCHABLE_COMMAND( exit 3 1 0 ){
 +  /* .exit acts like .quit with no argument or a zero argument,
 +   * only returning. With a non-zero argument, it effects an exit. */
 +  int rc;
 +  if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ){
 +    rc &= 0xff;    /* Mimic effect of legacy call to exit(). */
 +    p->shellAbruptExit = 0x100|rc;
 +  }
 +  return DCR_Return;
 +}
 +DISPATCHABLE_COMMAND( quit 1 1 0 ){
 +  /* .quit would be more aptly named .return, as it does nothing more. */
 +  return DCR_Return;
 +}
 +
 +/*****************
 + * The .expert and .explain commands
 + */
 +CONDITION_COMMAND( expert !defined(SQLITE_OMIT_VIRTUALTABLE) );
 +COLLECT_HELP_TEXT[
 +  ".expert                  Suggest indexes for queries",
 +  ".explain ?on|off|auto?   Change the EXPLAIN formatting mode. Default: auto",
 +];
 +DISPATCHABLE_COMMAND( expert ? 1 1 ){
 +  ShellInState *psi = ISS(p);
 +  int rv = DCR_Ok;
 +  char *zErr = 0;
 +  int i;
 +  int iSample = 0;
 +
 +  if( psi->bSafeMode ) return DCR_AbortError;
 +  assert( psi->expert.pExpert==0 );
 +  memset(&psi->expert, 0, sizeof(ExpertInfo));
 +
 +  open_db(p, 0);
  
 -  int bFreelist = 1;              /* 0 if --freelist-corrupt is specified */
 -  int bRowids = 1;                /* 0 if --no-rowids */
    for(i=1; i<nArg; i++){
      char *z = azArg[i];
      int n;
      if( z[0]=='-' && z[1]=='-' ) z++;
      n = strlen30(z);
 -    if( n<=17 && memcmp("-freelist-corrupt", z, n)==0 ){
 -      bFreelist = 0;
 -    }else
 -    if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
 -      i++;
 -      zRecoveryDb = azArg[i];
 -    }else
 -    if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
 -      i++;
 -      zLostAndFound = azArg[i];
 -    }else
 -    if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
 -      bRowids = 0;
 +    if( n>=2 && 0==strncmp(z, "-verbose", n) ){
 +      psi->expert.bVerbose = 1;
 +    }
 +    else if( n>=2 && 0==strncmp(z, "-sample", n) ){
 +      if( i==(nArg-1) ){
 +        return DCR_Unpaired|i;
 +      }else{
 +        iSample = (int)integerValue(azArg[++i]);
 +        if( iSample<0 || iSample>100 ){
 +          *pzErr = smprintf("value out of range: %s\n", azArg[i]);
 +          return DCR_ArgWrong|i;
 +        }
 +      }
 +    }
 +    else{
 +      return DCR_Unknown|i;
 +    }
 +  }
 +
 +  psi->expert.pExpert = sqlite3_expert_new(DBI(psi), &zErr);
 +  if( psi->expert.pExpert==0 ){
 +    *pzErr = smprintf("sqlite3_expert_new: %s\n",
 +                      zErr ? zErr : "out of memory");
 +    return DCR_Error;
 +  }else{
 +    sqlite3_expert_config(psi->expert.pExpert, EXPERT_CONFIG_SAMPLE, iSample);
 +  }
 +
 +  return DCR_Ok;
 +}
 +
 +DISPATCHABLE_COMMAND( explain ? 1 2 ){
 +  /* The ".explain" command is automatic now.  It is largely
 +  ** pointless, retained purely for backwards compatibility */
 +  ShellInState *psi = ISS(p);
 +  int val = 1;
 +  if( nArg>1 ){
 +    if( strcmp(azArg[1],"auto")==0 ){
 +      val = 99;
 +    }else{
 +      val = booleanValue(azArg[1]);
 +    }
 +  }
 +  if( val==1 && psi->mode!=MODE_Explain ){
 +    psi->normalMode = psi->mode;
 +    psi->mode = MODE_Explain;
 +    psi->autoExplain = 0;
 +  }else if( val==0 ){
 +    if( psi->mode==MODE_Explain ) psi->mode = psi->normalMode;
 +    psi->autoExplain = 0;
 +  }else if( val==99 ){
 +    if( psi->mode==MODE_Explain ) psi->mode = psi->normalMode;
 +    psi->autoExplain = 1;
 +  }
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * The .excel, .once and .output commands
 + * These share much implementation, so they stick together.
 + */
 +COLLECT_HELP_TEXT[
 +  ".excel                   Display the output of next command in spreadsheet",
 +  "   --bom                   Prefix the file with a UTF8 byte-order mark",
 +  ".once ?OPTIONS? ?FILE?   Output for the next SQL command only to FILE",
 +  "   If FILE begins with '|' then open it as a command to be piped into.",
 +  "   Options:",
 +  "     --bom                 Prefix output with a UTF8 byte-order mark",
 +  "     -e                    Send output to the system text editor",
 +  "     -x       Send output as CSV to a spreadsheet (same as \".excel\")",
 +  ".output ?FILE?           Send output to FILE or stdout if FILE is omitted",
 +  "   If FILE begins with '|' then open it as a command to be piped into.",
 +  "   Options:",
 +  "     --bom                 Prefix output with a UTF8 byte-order mark",
 +  "     -e                    Send output to the system text editor",
 +  "     -x       Send output as CSV to a spreadsheet (same as \".excel\")",
 +];
 +/* Shared implementation of .excel, .once and .output */
 +static DotCmdRC outputRedirs(char *azArg[], int nArg,
 +                                      ShellInState *psi, char **pzErr,
 +                                      int bOnce, int eMode){
 +  /* bOnce => 0: .output, 1: .once, 2: .excel */
 +  /* eMode => 'x' for excel, else 0 */
 +  int rc = 0;
 +  char *zFile = 0;
 +  u8 bTxtMode = 0;
 +  u8 bPutBOM = 0;
 +  int i;
 +  static unsigned const char zBOM[4] = {0xef,0xbb,0xbf,0};
 +  if( psi->bSafeMode ) return DCR_AbortError;
 +  for(i=1; i<nArg; i++){
 +    char *z = azArg[i];
 +    if( z[0]=='-' ){
 +      if( z[1]=='-' ) z++;
 +      if( strcmp(z,"-bom")==0 ){
 +        bPutBOM = 1;
 +      }else if( bOnce!=2 && strcmp(z,"-x")==0 ){
 +        eMode = 'x';  /* spreadsheet */
 +      }else if( bOnce!=2 && strcmp(z,"-e")==0 ){
 +        eMode = 'e';  /* text editor */
 +      }else{
 +        return DCR_Unknown|i;
 +      }
 +    }else if( zFile==0 && eMode!='e' && eMode!='x' ){
 +      zFile = smprintf("%s", z);
 +      shell_check_oom(zFile);
 +      if( zFile[0]=='|' ){
 +        while( i+1<nArg ){
 +          zFile = smprintf("%z %s", zFile, azArg[++i]);
 +          shell_check_oom(zFile);
 +        }
 +        break;
 +      }
 +    }else{
 +      sqlite3_free(zFile);
 +      return DCR_TooMany|i;
 +    }
 +  }
 +  if( zFile==0 ){
 +    zFile = smprintf("stdout");
 +    shell_check_oom(zFile);
 +  }
 +  if( bOnce ){
 +    psi->outCount = 2;
 +  }else{
 +    psi->outCount = 0;
 +  }
 +  output_reset(psi);
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +  if( eMode=='e' || eMode=='x' ){
 +    psi->doXdgOpen = 1;
 +    outputModePush(psi);
 +    if( eMode=='x' ){
 +      /* spreadsheet mode.  Output as CSV. */
 +      newTempFile(psi, "csv");
 +      psi->shellFlgs &= ~SHFLG_Echo;
 +      psi->mode = MODE_Csv;
 +      sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator, SEP_Comma);
 +      sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, SEP_CrLf);
 +    }else{
 +      /* text editor mode */
 +      newTempFile(psi, "txt");
 +      bTxtMode = 1;
 +    }
 +    sqlite3_free(zFile);
 +    zFile = smprintf("%s", psi->zTempFile);
 +  }
 +#endif /* SQLITE_NOHAVE_SYSTEM */
 +  shell_check_oom(zFile);
 +  if( zFile[0]=='|' ){
 +#ifdef SQLITE_OMIT_POPEN
 +    *pzErr = smprintf("pipes are not supported in this OS\n");
 +    rc = 1;
 +    psi->out = STD_OUT;
 +#else
 +    psi->out = popen(zFile + 1, "w");
 +    if( psi->out==0 ){
 +      *pzErr = smprintf("cannot open pipe \"%s\"\n", zFile + 1);
 +      psi->out = STD_OUT;
 +      rc = 1;
 +    }else{
 +      if( bPutBOM ) fwrite(zBOM, 1, 3, psi->out);
 +      sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile);
 +    }
 +#endif
 +  }else{
 +    psi->out = output_file_open(zFile, bTxtMode);
 +    if( psi->out==0 ){
 +      if( strcmp(zFile,"off")!=0 ){
 +        *pzErr = smprintf("cannot write to \"%s\"\n", zFile);
 +      }
 +      psi->out = STD_OUT;
 +      rc = 1;
 +    } else {
 +      if( bPutBOM ) fwrite(zBOM, 1, 3, psi->out);
 +      sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile);
 +    }
 +  }
 +  sqlite3_free(zFile);
 +  return DCR_Ok|rc;
 +}
 +DISPATCHABLE_COMMAND( excel ? 1 2 ){
 +  return outputRedirs(azArg, nArg, ISS(p), pzErr, 2, 'x');
 +}
 +DISPATCHABLE_COMMAND( once ? 1 6 ){
 +  return outputRedirs(azArg, nArg, ISS(p), pzErr, 1, 0);
 +}
 +DISPATCHABLE_COMMAND( output ? 1 6 ){
 +  return outputRedirs(azArg, nArg, ISS(p), pzErr, 0, 0);
 +}
 +
 +
 +/*****************
 + * The .filectrl and fullschema commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".filectrl CMD ...        Run various sqlite3_file_control() operations",
 +  "   --schema SCHEMA         Use SCHEMA instead of \"main\"",
 +  "   --help                  Show CMD details",
 +  ".fullschema ?--indent?   Show schema and the content of sqlite_stat tables",
 +];
 +DISPATCHABLE_COMMAND( filectrl ? 2 0 ){
 +  static const struct {
 +    const char *zCtrlName;   /* Name of a test-control option */
 +    int ctrlCode;            /* Integer code for that option */
 +    const char *zUsage;      /* Usage notes */
 +  } aCtrl[] = {
 +    { "chunk_size",     SQLITE_FCNTL_CHUNK_SIZE,      "SIZE"           },
 +    { "data_version",   SQLITE_FCNTL_DATA_VERSION,    ""               },
 +    { "has_moved",      SQLITE_FCNTL_HAS_MOVED,       ""               },
 +    { "lock_timeout",   SQLITE_FCNTL_LOCK_TIMEOUT,    "MILLISEC"       },
 +    { "persist_wal",    SQLITE_FCNTL_PERSIST_WAL,     "[BOOLEAN]"      },
 + /* { "pragma",         SQLITE_FCNTL_PRAGMA,          "NAME ARG"       },*/
 +    { "psow",       SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]"      },
 +    { "reserve_bytes",  SQLITE_FCNTL_RESERVE_BYTES,   "[N]"            },
 +    { "size_limit",     SQLITE_FCNTL_SIZE_LIMIT,      "[LIMIT]"        },
 +    { "tempfilename",   SQLITE_FCNTL_TEMPFILENAME,    ""               },
 + /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY,  "COUNT DELAY"    },*/
 +  };
 +  ShellInState *psi = ISS(p);
 +  int filectrl = -1;
 +  int iCtrl = -1;
 +  sqlite3_int64 iRes = 0;  /* Integer result to display if rc2==1 */
 +  int isOk = 0;            /* 0: usage  1: %lld  2: no-result */
 +  int n2, i;
 +  const char *zCmd = 0;
 +  const char *zSchema = 0;
 +
 +  open_db(p, 0);
 +  zCmd = nArg>=2 ? azArg[1] : "help";
 +
 +  if( zCmd[0]=='-'
 +      && (strcmp(zCmd,"--schema")==0 || strcmp(zCmd,"-schema")==0)
 +      && nArg>=4
 +      ){
 +    zSchema = azArg[2];
 +    for(i=3; i<nArg; i++) azArg[i-2] = azArg[i];
 +    nArg -= 2;
 +    zCmd = azArg[1];
 +  }
 +
 +  /* The argument can optionally begin with "-" or "--" */
 +  if( zCmd[0]=='-' && zCmd[1] ){
 +    zCmd++;
 +    if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 +  }
 +
 +  /* --help lists all file-controls */
 +  if( strcmp(zCmd,"help")==0 ){
 +    utf8_printf(psi->out, "Available file-controls:\n");
 +    for(i=0; i<ArraySize(aCtrl); i++){
 +      utf8_printf(psi->out, "  .filectrl %s %s\n",
 +                  aCtrl[i].zCtrlName, aCtrl[i].zUsage);
 +    }
 +    return DCR_Error;
 +  }
 +
 +  /* Convert filectrl text option to value. Allow any
 +  ** unique prefix of the option name, or a numerical value. */
 +  n2 = strlen30(zCmd);
 +  for(i=0; i<ArraySize(aCtrl); i++){
 +    if( strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
 +      if( filectrl<0 ){
 +        filectrl = aCtrl[i].ctrlCode;
 +        iCtrl = i;
 +      }else{
 +        *pzErr = smprintf("ambiguous file-control: \"%s\"\n"
 +                          "Use \".filectrl --help\" for help\n", zCmd);
 +        return DCR_ArgWrong;
 +      }
 +    }
 +  }
 +  if( filectrl<0 ){
 +    *pzErr = smprintf("unknown file-control: %s\n"
 +                      "Use \".filectrl --help\" for help\n", zCmd);
 +    return DCR_ArgWrong;
 +  }else{
 +   switch(filectrl){
 +    case SQLITE_FCNTL_SIZE_LIMIT: {
 +      if( nArg!=2 && nArg!=3 ) break;
 +      iRes = nArg==3 ? integerValue(azArg[2]) : -1;
 +      sqlite3_file_control(DBX(p), zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes);
 +      isOk = 1;
 +      break;
      }
 -    else{
 -      utf8_printf(stderr, "unexpected option: %s\n", azArg[i]); 
 -      showHelp(pState->out, azArg[0]);
 -      return 1;
 +    case SQLITE_FCNTL_LOCK_TIMEOUT:
 +    case SQLITE_FCNTL_CHUNK_SIZE: {
 +      int x;
 +      if( nArg!=3 ) break;
 +      x = (int)integerValue(azArg[2]);
 +      sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
 +      isOk = 2;
 +      break;
 +    }
 +    case SQLITE_FCNTL_PERSIST_WAL:
 +    case SQLITE_FCNTL_POWERSAFE_OVERWRITE: {
 +      int x;
 +      if( nArg!=2 && nArg!=3 ) break;
 +      x = nArg==3 ? booleanValue(azArg[2]) : -1;
 +      sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
 +      iRes = x;
 +      isOk = 1;
 +      break;
 +    }
 +    case SQLITE_FCNTL_DATA_VERSION:
 +    case SQLITE_FCNTL_HAS_MOVED: {
 +      int x;
 +      if( nArg!=2 ) break;
 +      sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
 +      iRes = x;
 +      isOk = 1;
 +      break;
      }
 +    case SQLITE_FCNTL_TEMPFILENAME: {
 +      char *z = 0;
 +      if( nArg!=2 ) break;
 +      sqlite3_file_control(DBX(p), zSchema, filectrl, &z);
 +      if( z ){
 +        utf8_printf(psi->out, "%s\n", z);
 +        sqlite3_free(z);
 +      }
 +      isOk = 2;
 +      break;
 +    }
 +    case SQLITE_FCNTL_RESERVE_BYTES: {
 +      int x;
 +      if( nArg>=3 ){
 +        x = atoi(azArg[2]);
 +        sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
 +      }
 +      x = -1;
 +      sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
 +      utf8_printf(psi->out,"%d\n", x);
 +      isOk = 2;
 +      break;
 +    }
 +   }
 +  }
 +  if( isOk==0 && iCtrl>=0 ){
 +    utf8_printf(psi->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
 +    return DCR_CmdErred;
 +  }else if( isOk==1 ){
 +    char zBuf[100];
 +    sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes);
 +    raw_printf(psi->out, "%s\n", zBuf);
    }
 +  return DCR_Ok;
 +}
  
 -  shellExecPrintf(pState->db, &rc,
 -    /* Attach an in-memory database named 'recovery'. Create an indexed 
 -    ** cache of the sqlite_dbptr virtual table. */
 -    "PRAGMA writable_schema = on;"
 -    "ATTACH %Q AS recovery;"
 -    "DROP TABLE IF EXISTS recovery.dbptr;"
 -    "DROP TABLE IF EXISTS recovery.freelist;"
 -    "DROP TABLE IF EXISTS recovery.map;"
 -    "DROP TABLE IF EXISTS recovery.schema;"
 -    "CREATE TABLE recovery.freelist(pgno INTEGER PRIMARY KEY);", zRecoveryDb
 +DISPATCHABLE_COMMAND( fullschema ? 1 2 ){
 +  int rc;
 +  ShellInState data;
 +  ShellExState datax;
 +  int doStats = 0;
 +  /* Consider some refactoring to avoid this wholesale copying. */
 +  memcpy(&data, ISS(p), sizeof(data));
 +  memcpy(&datax, p, sizeof(datax));
 +  data.pSXS = &datax;
 +  datax.pSIS = &data;
 +  data.showHeader = 0;
 +  data.cMode = data.mode = MODE_Semi;
 +  if( nArg==2 && optionMatch(azArg[1], "indent") ){
 +    data.cMode = data.mode = MODE_Pretty;
 +    nArg = 1;
 +  }
 +  if( nArg!=1 ){
 +    return DCR_TooMany|1;
 +  }
 +  open_db(p, 0);
 +  rc = sqlite3_exec(datax.dbUser,
 +    "SELECT sql FROM"
 +    "  (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x"
 +    "     FROM sqlite_schema UNION ALL"
 +    "   SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) "
 +    "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' "
 +    "ORDER BY x",
 +    callback, &datax, 0
    );
 -
 -  if( bFreelist ){
 -    shellExec(pState->db, &rc,
 -      "WITH trunk(pgno) AS ("
 -      "  SELECT shell_int32("
 -      "      (SELECT data FROM sqlite_dbpage WHERE pgno=1), 8) AS x "
 -      "      WHERE x>0"
 -      "    UNION"
 -      "  SELECT shell_int32("
 -      "      (SELECT data FROM sqlite_dbpage WHERE pgno=trunk.pgno), 0) AS x "
 -      "      FROM trunk WHERE x>0"
 -      "),"
 -      "freelist(data, n, freepgno) AS ("
 -      "  SELECT data, min(16384, shell_int32(data, 1)-1), t.pgno "
 -      "      FROM trunk t, sqlite_dbpage s WHERE s.pgno=t.pgno"
 -      "    UNION ALL"
 -      "  SELECT data, n-1, shell_int32(data, 2+n) "
 -      "      FROM freelist WHERE n>=0"
 -      ")"
 -      "REPLACE INTO recovery.freelist SELECT freepgno FROM freelist;"
 -    );
 +  if( rc==SQLITE_OK ){
 +    sqlite3_stmt *pStmt;
 +    rc = sqlite3_prepare_v2(datax.dbUser,
 +                            "SELECT rowid FROM sqlite_schema"
 +                            " WHERE name GLOB 'sqlite_stat[134]'",
 +                            -1, &pStmt, 0);
 +    doStats = sqlite3_step(pStmt)==SQLITE_ROW;
 +    sqlite3_finalize(pStmt);
 +  }
 +  if( doStats==0 ){
 +    raw_printf(data.out, "/* No STAT tables available */\n");
 +  }else{
 +    raw_printf(data.out, "ANALYZE sqlite_schema;\n");
 +    data.cMode = data.mode = MODE_Insert;
 +    datax.zDestTable = "sqlite_stat1";
 +    shell_exec(&datax, "SELECT * FROM sqlite_stat1", 0);
 +    datax.zDestTable = "sqlite_stat4";
 +    shell_exec(&datax, "SELECT * FROM sqlite_stat4", 0);
 +    raw_printf(data.out, "ANALYZE sqlite_schema;\n");
    }
 +  return rc > 0;
 +}
  
 -  /* If this is an auto-vacuum database, add all pointer-map pages to
 -  ** the freelist table. Do this regardless of whether or not 
 -  ** --freelist-corrupt was specified.  */
 -  shellExec(pState->db, &rc, 
 -    "WITH ptrmap(pgno) AS ("
 -    "  SELECT 2 WHERE shell_int32("
 -    "    (SELECT data FROM sqlite_dbpage WHERE pgno=1), 13"
 -    "  )"
 -    "    UNION ALL "
 -    "  SELECT pgno+1+(SELECT page_size FROM pragma_page_size)/5 AS pp "
 -    "  FROM ptrmap WHERE pp<=(SELECT page_count FROM pragma_page_count)"
 -    ")"
 -    "REPLACE INTO recovery.freelist SELECT pgno FROM ptrmap"
 -  );
 +/*****************
 + * The .headers command
 + */
 +COLLECT_HELP_TEXT[
 +  ".headers on|off          Turn display of headers on or off",
 +];
 +DISPATCHABLE_COMMAND( headers 6 2 2 ){
 +  ISS(p)->showHeader = booleanValue(azArg[1]);
 +  ISS(p)->shellFlgs |= SHFLG_HeaderSet;
 +  return DCR_Ok;
 +}
  
 -  shellExec(pState->db, &rc, 
 -    "CREATE TABLE recovery.dbptr("
 -    "      pgno, child, PRIMARY KEY(child, pgno)"
 -    ") WITHOUT ROWID;"
 -    "INSERT OR IGNORE INTO recovery.dbptr(pgno, child) "
 -    "    SELECT * FROM sqlite_dbptr"
 -    "      WHERE pgno NOT IN freelist AND child NOT IN freelist;"
 +/*****************
 + * The .help command
 + */
  
 -    /* Delete any pointer to page 1. This ensures that page 1 is considered
 -    ** a root page, regardless of how corrupt the db is. */
 -    "DELETE FROM recovery.dbptr WHERE child = 1;"
 +/* This literal's value AND address are used for help's workings. */
 +static const char *zHelpAll = "-all";
 +
 +COLLECT_HELP_TEXT[
 +  ".help ?PATTERN?|?-all?   Show help for PATTERN or everything, or summarize",
 +  "                           Repeat -all to see undocumented commands",
 +];
 +DISPATCHABLE_COMMAND( help 3 1 3 ){
 +  const char *zPat = 0;
 +  FILE *out = ISS(p)->out;
 +  if( nArg>1 ){
 +    char *z = azArg[1];
 +    if( nArg==3 && strcmp(z, zHelpAll)==0 && strcmp(azArg[2], zHelpAll)==0 ){
 +      /* Show the undocumented command help */
 +      zPat = zHelpAll;
 +    }else if( strcmp(z,"-a")==0 || optionMatch(z, "all") ){
 +      zPat = "";
 +    }else{
 +      zPat = z;
 +    }
 +  }
 +  if( showHelp(out, zPat, p)==0 && nArg>1 ){
 +    utf8_printf(out, "Nothing matches '%s'\n", azArg[1]);
 +  }
 +  /* Help pleas never fail! */
 +  return DCR_Ok;
 +}
  
 -    /* Delete all pointers to any pages that have more than one pointer
 -    ** to them. Such pages will be treated as root pages when recovering
 -    ** data.  */
 -    "DELETE FROM recovery.dbptr WHERE child IN ("
 -    "  SELECT child FROM recovery.dbptr GROUP BY child HAVING count(*)>1"
 -    ");"
 +/*****************
 + * The .import command
 + */
 +COLLECT_HELP_TEXT[
 +  ".import FILE TABLE       Import data from FILE into TABLE",
 +  "   Options:",
 +  "     --ascii               Use \\037 and \\036 as column and row separators",
 +  "     --csv                 Use , and \\n as column and row separators",
 +  "     --skip N              Skip the first N rows of input",
 +  "     --schema S            Target table to be S.TABLE",
 +  "     -v                    \"Verbose\" - increase auxiliary output",
 +  "   Notes:",
 +  "     *  If TABLE does not exist, it is created.  The first row of input",
 +  "        determines the column names.",
 +  "     *  If neither --csv or --ascii are used, the input mode is derived",
 +  "        from the \".mode\" output mode",
 +  "     *  If FILE begins with \"|\" then it is a command that generates the",
 +  "        input text.",
 +];
 +DISPATCHABLE_COMMAND( import ? 3 7 ){
 +  char *zTable = 0;           /* Insert data into this table */
 +  char *zSchema = 0;          /* within this schema (may default to "main") */
 +  char *zFile = 0;            /* Name of file to extra content from */
 +  sqlite3_stmt *pStmt = NULL; /* A statement */
 +  int nCol;                   /* Number of columns in the table */
 +  int nByte;                  /* Number of bytes in an SQL string */
 +  int i, j;                   /* Loop counters */
 +  int needCommit;             /* True to COMMIT or ROLLBACK at end */
 +  int nSep;                   /* Number of bytes in psi->colSeparator[] */
 +  char *zSql;                 /* An SQL statement */
 +  char *zFullTabName;         /* Table name with schema if applicable */
 +  ImportCtx sCtx;             /* Reader context */
 +  char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
 +  int eVerbose = 0;           /* Larger for more console output */
 +  int nSkip = 0;              /* Initial lines to skip */
 +  int useOutputMode = 1;      /* Use output mode to determine separators */
 +  FILE *out = ISS(p)->out;    /* output stream */
 +  char *zCreate = 0;          /* CREATE TABLE statement text */
 +  ShellInState *psi = ISS(p);
 +  int rc = 0;
  
 -    /* Create the "map" table that will (eventually) contain instructions
 -    ** for dealing with each page in the db that contains one or more 
 -    ** records. */
 -    "CREATE TABLE recovery.map("
 -      "pgno INTEGER PRIMARY KEY, maxlen INT, intkey, root INT"
 -    ");"
 +  if(psi->bSafeMode) return DCR_AbortError;
 +  memset(&sCtx, 0, sizeof(sCtx));
 +  if( 0==(sCtx.z = sqlite3_malloc64(120)) ){
 +    shell_out_of_memory();
 +  }
  
 -    /* Populate table [map]. If there are circular loops of pages in the
 -    ** database, the following adds all pages in such a loop to the map
 -    ** as individual root pages. This could be handled better.  */
 -    "WITH pages(i, maxlen) AS ("
 -    "  SELECT page_count, ("
 -    "    SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=page_count"
 -    "  ) FROM pragma_page_count WHERE page_count>0"
 -    "    UNION ALL"
 -    "  SELECT i-1, ("
 -    "    SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=i-1"
 -    "  ) FROM pages WHERE i>=2"
 -    ")"
 -    "INSERT INTO recovery.map(pgno, maxlen, intkey, root) "
 -    "  SELECT i, maxlen, NULL, ("
 -    "    WITH p(orig, pgno, parent) AS ("
 -    "      SELECT 0, i, (SELECT pgno FROM recovery.dbptr WHERE child=i)"
 -    "        UNION "
 -    "      SELECT i, p.parent, "
 -    "        (SELECT pgno FROM recovery.dbptr WHERE child=p.parent) FROM p"
 -    "    )"
 -    "    SELECT pgno FROM p WHERE (parent IS NULL OR pgno = orig)"
 -    ") "
 -    "FROM pages WHERE maxlen IS NOT NULL AND i NOT IN freelist;"
 -    "UPDATE recovery.map AS o SET intkey = ("
 -    "  SELECT substr(data, 1, 1)==X'0D' FROM sqlite_dbpage WHERE pgno=o.pgno"
 -    ");"
 +  if( psi->mode==MODE_Ascii ){
 +    xRead = ascii_read_one_field;
 +  }else{
 +    xRead = csv_read_one_field;
 +  }
 +  for(i=1; i<nArg; i++){
 +    char *z = azArg[i];
 +    if( z[0]=='-' && z[1]=='-' ) z++;
 +    if( z[0]!='-' ){
 +      if( zFile==0 ){
 +        zFile = z;
 +      }else if( zTable==0 ){
 +        zTable = z;
 +      }else{
 +        return DCR_TooMany|i;
 +      }
 +    }else if( strcmp(z,"-v")==0 ){
 +      eVerbose++;
 +    }else if( strcmp(z,"-schema")==0 && i<nArg-1 ){
 +      zSchema = azArg[++i];
 +    }else if( strcmp(z,"-skip")==0 && i<nArg-1 ){
 +      nSkip = integerValue(azArg[++i]);
 +    }else if( strcmp(z,"-ascii")==0 ){
 +      sCtx.cColSep = SEP_Unit[0];
 +      sCtx.cRowSep = SEP_Record[0];
 +      xRead = ascii_read_one_field;
 +      useOutputMode = 0;
 +    }else if( strcmp(z,"-csv")==0 ){
 +      sCtx.cColSep = ',';
 +      sCtx.cRowSep = '\n';
 +      xRead = csv_read_one_field;
 +      useOutputMode = 0;
 +    }else{
 +      return DCR_Unknown|i;
 +    }
 +  }
 +  if( zTable==0 ){
 +    *pzErr = smprintf("missing %s argument.\n", zFile==0 ? "FILE" : "TABLE");
 +    return DCR_Missing;
 +  }
 +  seenInterrupt = 0;
 +  open_db(p, 0);
 +  if( useOutputMode ){
 +    const char *zYap = 0;
 +    /* If neither the --csv or --ascii options are specified, then set
 +    ** the column and row separator characters from the output mode. */
 +    nSep = strlen30(psi->colSeparator);
 +    if( nSep==0 ){
 +      zYap = "non-null column separator required for import";
 +    }
 +    if( nSep>1 ){
 +      zYap = "multi-character or multi-byte column separators"
 +        " not allowed for import";
 +    }
 +    nSep = strlen30(psi->rowSeparator);
 +    if( nSep==0 ){
 +      zYap = "non-null row separator required for import";
 +    }
 +    if( zYap!=0 ){
 +      *pzErr = smprintf("%s\n", zYap);
 +      return DCR_Error;
 +    }
 +    if( nSep==2 && psi->mode==MODE_Csv
 +        && strcmp(psi->rowSeparator,SEP_CrLf)==0 ){
 +      /* When importing CSV (only), if the row separator is set to the
 +      ** default output row separator, change it to the default input
 +      ** row separator.  This avoids having to maintain different input
 +      ** and output row separators. */
 +      sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, SEP_Row);
 +      nSep = strlen30(psi->rowSeparator);
 +    }
 +    if( nSep>1 ){
 +      *pzErr
 +        = smprintf("multi-character row separators not allowed for import\n");
 +      return DCR_Error;
 +    }
 +    sCtx.cColSep = psi->colSeparator[0];
 +    sCtx.cRowSep = psi->rowSeparator[0];
 +  }
 +  sCtx.zFile = zFile;
 +  sCtx.nLine = 1;
 +  if( sCtx.zFile[0]=='|' ){
 +#ifdef SQLITE_OMIT_POPEN
 +    *pzErr = smprintf("pipes are not supported in this OS\n");
 +    return DCR_Error;
 +#else
 +    sCtx.in = popen(sCtx.zFile+1, "r");
 +    sCtx.zFile = "<pipe>";
 +    sCtx.xCloser = pclose;
 +#endif
 +  }else{
 +    sCtx.in = fopen(sCtx.zFile, "rb");
 +    sCtx.xCloser = fclose;
 +  }
 +  if( sCtx.in==0 ){
 +        *pzErr = smprintf("cannot open \"%s\"\n", zFile);
 +    import_cleanup(&sCtx);
 +    return DCR_Error;
 +  }
 +  /* Here and below, resources must be freed before exit. */
 +  if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
 +    char zSep[2];
 +    zSep[1] = 0;
 +    zSep[0] = sCtx.cColSep;
 +    utf8_printf(out, "Column separator ");
 +    output_c_string(out, zSep);
 +    utf8_printf(out, ", row separator ");
 +    zSep[0] = sCtx.cRowSep;
 +    output_c_string(out, zSep);
 +    utf8_printf(out, "\n");
 +  }
 +  while( (nSkip--)>0 ){
 +    while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
 +  }
 +  if( zSchema!=0 ){
 +    zFullTabName = smprintf("\"%w\".\"%w\"", zSchema, zTable);
 +  }else{
 +    zFullTabName = smprintf("\"%w\"", zTable);
 +  }
 +  zSql = smprintf("SELECT * FROM %s", zFullTabName);
 +  if( zSql==0 || zFullTabName==0 ){
 +    import_cleanup(&sCtx);
 +    shell_out_of_memory();
 +  }
 +  nByte = strlen30(zSql);
 +  rc = sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0);
 +  import_append_char(&sCtx, 0);    /* To ensure sCtx.z is allocated */
 +  if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(DBX(p)))==0 ){
 +    zCreate = smprintf("CREATE TABLE %s", zFullTabName);
 +    sqlite3 *dbCols = 0;
 +    char *zRenames = 0;
 +    char *zColDefs;
 +    while( xRead(&sCtx) ){
 +      zAutoColumn(sCtx.z, &dbCols, 0);
 +      if( sCtx.cTerm!=sCtx.cColSep ) break;
 +    }
 +    zColDefs = zAutoColumn(0, &dbCols, &zRenames);
 +    if( zRenames!=0 ){
 +      FILE *fh = INSOURCE_IS_INTERACTIVE(psi->pInSource)?  out : STD_ERR;
 +      utf8_printf(fh, "Columns renamed during .import %s due to duplicates:\n"
 +                  "%s\n", sCtx.zFile, zRenames);
 +      sqlite3_free(zRenames);
 +    }
 +    assert(dbCols==0);
 +    if( zColDefs==0 ){
 +      *pzErr = smprintf("%s: empty file\n", sCtx.zFile);
 +      sqlite3_free(zCreate);
 +    import_fail: /* entry from outer blocks */
 +      sqlite3_free(zSql);
 +      sqlite3_free(zFullTabName);
 +      import_cleanup(&sCtx);
 +      return DCR_Error;
 +    }
 +    zCreate = smprintf("%z%z\n", zCreate, zColDefs);
 +    if( eVerbose>=1 ){
 +      utf8_printf(out, "%s\n", zCreate);
 +    }
 +    rc = sqlite3_exec(DBX(p), zCreate, 0, 0, 0);
 +    sqlite3_free(zCreate);
 +    if( rc ){
 +      *pzErr = smprintf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(DBX(p)));
 +      goto import_fail;
 +    }
 +    rc = sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0);
 +  }
 +  if( rc ){
 +    if (pStmt) sqlite3_finalize(pStmt);
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
 +    goto import_fail;
 +  }
 +  sqlite3_free(zSql);
 +  nCol = sqlite3_column_count(pStmt);
 +  sqlite3_finalize(pStmt);
 +  pStmt = 0;
 +  if( nCol==0 ) return DCR_Ok; /* no columns, no error */
 +  zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 );
 +  if( zSql==0 ){
 +    import_cleanup(&sCtx);
 +    shell_out_of_memory();
 +  }
 +  sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName);
 +  j = strlen30(zSql);
 +  for(i=1; i<nCol; i++){
 +    zSql[j++] = ',';
 +    zSql[j++] = '?';
 +  }
 +  zSql[j++] = ')';
 +  zSql[j] = 0;
 +  if( eVerbose>=2 ){
 +    utf8_printf(psi->out, "Insert using: %s\n", zSql);
 +  }
 +  rc = sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0);
 +  if( rc ){
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
 +    if (pStmt) sqlite3_finalize(pStmt);
 +    goto import_fail;
 +  }
 +  sqlite3_free(zSql);
 +  sqlite3_free(zFullTabName);
 +  needCommit = sqlite3_get_autocommit(DBX(p));
 +  if( needCommit ) sqlite3_exec(DBX(p), "BEGIN", 0, 0, 0);
 +  do{
 +    int startLine = sCtx.nLine;
 +    for(i=0; i<nCol; i++){
 +      char *z = xRead(&sCtx);
 +      /*
 +      ** Did we reach end-of-file before finding any columns?
 +      ** If so, stop instead of NULL filling the remaining columns.
 +      */
 +      if( z==0 && i==0 ) break;
 +      /*
 +      ** Did we reach end-of-file OR end-of-line before finding any
 +      ** columns in ASCII mode?  If so, stop instead of NULL filling
 +      ** the remaining columns.
 +      */
 +      if( psi->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
 +      sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
 +      if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
 +        utf8_printf(STD_ERR, "%s:%d: expected %d columns but found %d - "
 +                    "filling the rest with NULL\n",
 +                    sCtx.zFile, startLine, nCol, i+1);
 +        i += 2;
 +        while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; }
 +      }
 +    }
 +    if( sCtx.cTerm==sCtx.cColSep ){
 +      do{
 +        xRead(&sCtx);
 +        i++;
 +      }while( sCtx.cTerm==sCtx.cColSep );
 +      utf8_printf(STD_ERR, "%s:%d: expected %d columns but found %d - "
 +                  "extras ignored\n",
 +                  sCtx.zFile, startLine, nCol, i);
 +    }
 +    if( i>=nCol ){
 +      sqlite3_step(pStmt);
 +      rc = sqlite3_reset(pStmt);
 +      if( rc!=SQLITE_OK ){
 +        utf8_printf(STD_ERR, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
 +                    startLine, sqlite3_errmsg(DBX(p)));
 +        sCtx.nErr++;
 +      }else{
 +        sCtx.nRow++;
 +      }
 +    }
 +  }while( sCtx.cTerm!=EOF );
  
 -    /* Extract data from page 1 and any linked pages into table
 -    ** recovery.schema. With the same schema as an sqlite_schema table.  */
 -    "CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);"
 -    "INSERT INTO recovery.schema SELECT "
 -    "  max(CASE WHEN field=0 THEN value ELSE NULL END),"
 -    "  max(CASE WHEN field=1 THEN value ELSE NULL END),"
 -    "  max(CASE WHEN field=2 THEN value ELSE NULL END),"
 -    "  max(CASE WHEN field=3 THEN value ELSE NULL END),"
 -    "  max(CASE WHEN field=4 THEN value ELSE NULL END)"
 -    "FROM sqlite_dbdata WHERE pgno IN ("
 -    "  SELECT pgno FROM recovery.map WHERE root=1"
 -    ")"
 -    "GROUP BY pgno, cell;"
 -    "CREATE INDEX recovery.schema_rootpage ON schema(rootpage);"
 -  );
 +  import_cleanup(&sCtx);
 +  sqlite3_finalize(pStmt);
 +  if( needCommit ) sqlite3_exec(DBX(p), "COMMIT", 0, 0, 0);
 +  if( eVerbose>0 ){
 +    utf8_printf(out,
 +      "Added %d rows with %d errors using %d lines of input\n",
 +      sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
 +  }
 +  return DCR_Ok|(sCtx.nErr>0);
 +}
  
 -  /* Open a transaction, then print out all non-virtual, non-"sqlite_%" 
 -  ** CREATE TABLE statements that extracted from the existing schema.  */
 +/*****************
 + * The .keyword command
 + */
 +CONDITION_COMMAND( keyword !defined(NO_KEYWORD_COMMAND) );
 +COLLECT_HELP_TEXT[
 +  ".keyword ?KW?            List keywords, or say whether KW is one.",
 +];
 +DISPATCHABLE_COMMAND( keyword ? 1 2 ){
 +  FILE *out = ISS(p)->out;
 +  if( nArg<2 ){
 +    int i = 0;
 +    int nk = sqlite3_keyword_count();
 +    int nCol = 0;
 +    int szKW;
 +    while( i<nk ){
 +      const char *zKW = 0;
 +      if( SQLITE_OK==sqlite3_keyword_name(i++, &zKW, &szKW) ){
 +        char kwBuf[50];
 +        if( szKW < sizeof(kwBuf) ){
 +          const char *zSep = " ";
 +          if( (nCol += (1+szKW))>75){
 +            zSep = "\n";
 +            nCol = 0;
 +          }
 +          memcpy(kwBuf, zKW, szKW);
 +          kwBuf[szKW] = 0;
 +          utf8_printf(out, "%s%s", kwBuf, zSep);
 +        }
 +      }
 +    }
 +    if( nCol>0 ) utf8_printf(out, "\n");
 +  }else{
 +    int szKW = strlen30(azArg[1]);
 +    int isKeyword = sqlite3_keyword_check(azArg[1], szKW);
 +    utf8_printf(out, "%s is%s a keyword\n",
 +                azArg[1], (isKeyword)? "" : " not");
 +  }
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * The .imposter, .iotrace, limit, lint and .log commands
 + */
 +CONDITION_COMMAND( imposter !defined(SQLITE_OMIT_TEST_CONTROL) );
 +CONDITION_COMMAND( iotrace defined(SQLITE_ENABLE_IOTRACE) );
 +CONDITION_COMMAND( load !defined(SQLITE_OMIT_LOAD_EXTENSION) );
 +COLLECT_HELP_TEXT[
 +  ".imposter INDEX TABLE    Create imposter table TABLE on index INDEX",
 +  ".iotrace FILE            Enable I/O diagnostic logging to FILE",
 +  ".limit ?LIMIT? ?VAL?     Display or change the value of an SQLITE_LIMIT",
 +  ".lint OPTIONS            Report potential schema issues.",
 +  "     Options:",
 +  "        fkey-indexes     Find missing foreign key indexes",
 +  ".log FILE|off            Turn logging on or off.  FILE can be stderr/stdout",
 +];
 +DISPATCHABLE_COMMAND( imposter ? 3 3 ){
 +  int rc = 0;
 +  char *zSql;
 +  char *zCollist = 0;
 +  sqlite3_stmt *pStmt;
 +  sqlite3 *db;
 +  int tnum = 0;
 +  int isWO = 0;  /* True if making an imposter of a WITHOUT ROWID table */
 +  int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */
 +  int i;
 +  if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){
 +    *pzErr = smprintf("Usage: .imposter INDEX IMPOSTER\n"
 +                      "       .imposter off\n");
 +    /* Also allowed, but not documented:
 +    **
 +    **    .imposter TABLE IMPOSTER
 +    **
 +    ** where TABLE is a WITHOUT ROWID table.  In that case, the
 +    ** imposter is another WITHOUT ROWID table with the columns in
 +    ** storage order. */
 +    return DCR_SayUsage;
 +  }
 +  open_db(p, 0);
 +  db = DBX(p);
 +  if( nArg==2 ){
 +    sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 1);
 +    return DCR_Ok;
 +  }
 +  zSql = smprintf("SELECT rootpage, 0 FROM sqlite_schema"
 +                  " WHERE name='%q' AND type='index'"
 +                  "UNION ALL "
 +                  "SELECT rootpage, 1 FROM sqlite_schema"
 +                  " WHERE name='%q' AND type='table'"
 +                  "  AND sql LIKE '%%without%%rowid%%'",
 +                  azArg[1], azArg[1]);
 +  sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
 +  sqlite3_free(zSql);
 +  if( sqlite3_step(pStmt)==SQLITE_ROW ){
 +    tnum = sqlite3_column_int(pStmt, 0);
 +    isWO = sqlite3_column_int(pStmt, 1);
 +  }
 +  sqlite3_finalize(pStmt);
 +  zSql = smprintf("PRAGMA index_xinfo='%q'", azArg[1]);
 +  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
 +  sqlite3_free(zSql);
 +  i = 0;
 +  while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 +    char zLabel[20];
 +    const char *zCol = (const char*)sqlite3_column_text(pStmt,2);
 +    i++;
 +    if( zCol==0 ){
 +      if( sqlite3_column_int(pStmt,1)==-1 ){
 +        zCol = "_ROWID_";
 +      }else{
 +        sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i);
 +        zCol = zLabel;
 +      }
 +    }
 +    if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){
 +      lenPK = (int)strlen(zCollist);
 +    }
 +    if( zCollist==0 ){
 +      zCollist = smprintf("\"%w\"", zCol);
 +    }else{
 +      zCollist = smprintf("%z,\"%w\"", zCollist, zCol);
 +    }
 +  }
 +  sqlite3_finalize(pStmt);
 +  if( i==0 || tnum==0 ){
 +    *pzErr = smprintf("no such index: \"%s\"\n", azArg[1]);
 +    sqlite3_free(zCollist);
 +    return DCR_Error;
 +  }
 +  if( lenPK==0 ) lenPK = 100000;
 +  zSql = smprintf("CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))"
 +                  "WITHOUT ROWID", azArg[2], zCollist, lenPK, zCollist);
 +  sqlite3_free(zCollist);
 +  rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 1, tnum);
    if( rc==SQLITE_OK ){
 -    sqlite3_stmt *pStmt = 0;
 -    /* ".recover" might output content in an order which causes immediate
 -    ** foreign key constraints to be violated. So disable foreign-key
 -    ** constraint enforcement to prevent problems when running the output
 -    ** script. */
 -    raw_printf(pState->out, "PRAGMA foreign_keys=OFF;\n");
 -    raw_printf(pState->out, "BEGIN;\n");
 -    raw_printf(pState->out, "PRAGMA writable_schema = on;\n");
 -    shellPrepare(pState->db, &rc,
 -        "SELECT sql FROM recovery.schema "
 -        "WHERE type='table' AND sql LIKE 'create table%'", &pStmt
 -    );
 -    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
 -      const char *zCreateTable = (const char*)sqlite3_column_text(pStmt, 0);
 -      raw_printf(pState->out, "CREATE TABLE IF NOT EXISTS %s;\n", 
 -          &zCreateTable[12]
 -      );
 +    rc = sqlite3_exec(db, zSql, 0, 0, 0);
 +    sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 0);
 +    if( rc ){
 +      *pzErr = smprintf("Error in [%s]: %s\n", zSql, sqlite3_errmsg(db));
 +    }else{
 +      utf8_printf(STD_OUT, "%s;\n", zSql);
 +      raw_printf(STD_OUT, "WARNING: "
 +                 "writing to an imposter table will corrupt the \"%s\" %s!\n",
 +                 azArg[1], isWO ? "table" : "index"
 +                 );
      }
 -    shellFinalize(&rc, pStmt);
 +  }else{
 +    *pzErr = smprintf("SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
    }
 +  sqlite3_free(zSql);
 +  return rc != 0;
 +}
 +DISPATCHABLE_COMMAND( iotrace ? 2 2 ){
 +  SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...);
 +  if( iotrace && iotrace!=STD_OUT ) fclose(iotrace);
 +  iotrace = 0;
 +  if( nArg<2 ){
 +    sqlite3IoTrace = 0;
 +  }else if( strcmp(azArg[1], "-")==0 ){
 +    sqlite3IoTrace = iotracePrintf;
 +    iotrace = STD_OUT;
 +  }else{
 +    iotrace = fopen(azArg[1], "w");
 +    if( iotrace==0 ){
 +      *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
 +      sqlite3IoTrace = 0;
 +      return DCR_Error;
 +    }else{
 +      sqlite3IoTrace = iotracePrintf;
 +    }
 +  }
 +  return DCR_Ok;
 +}
  
 -  /* Figure out if an orphan table will be required. And if so, how many
 -  ** user columns it should contain */
 -  shellPrepare(pState->db, &rc, 
 -      "SELECT coalesce(max(maxlen), -2) FROM recovery.map WHERE root>1"
 -      , &pLoop
 -  );
 -  if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
 -    nOrphan = sqlite3_column_int(pLoop, 0);
 +/*****************
 + * The .limits and .load commands
 + */
 +COLLECT_HELP_TEXT[
 +  ",limits ?LIMIT_NAME?     Display limit selected by its name, or all limits",
 +  ".load FILE ?ENTRY?       Load a SQLite extension library",
 +  "   If ENTRY is provided, the entry point \"sqlite_ENTRY_init\" is called.",
 +  "   Otherwise, the entry point name is derived from the FILE's name.",
 +];
 +
 +DISPATCHABLE_COMMAND( limits 5 1 3 ){
 +  static const struct {
 +    const char *zLimitName;   /* Name of a limit */
 +    int limitCode;            /* Integer code for that limit */
 +  } aLimit[] = {
 +    { "length",                SQLITE_LIMIT_LENGTH                    },
 +    { "sql_length",            SQLITE_LIMIT_SQL_LENGTH                },
 +    { "column",                SQLITE_LIMIT_COLUMN                    },
 +    { "expr_depth",            SQLITE_LIMIT_EXPR_DEPTH                },
 +    { "compound_select",       SQLITE_LIMIT_COMPOUND_SELECT           },
 +    { "vdbe_op",               SQLITE_LIMIT_VDBE_OP                   },
 +    { "function_arg",          SQLITE_LIMIT_FUNCTION_ARG              },
 +    { "attached",              SQLITE_LIMIT_ATTACHED                  },
 +    { "like_pattern_length",   SQLITE_LIMIT_LIKE_PATTERN_LENGTH       },
 +    { "variable_number",       SQLITE_LIMIT_VARIABLE_NUMBER           },
 +    { "trigger_depth",         SQLITE_LIMIT_TRIGGER_DEPTH             },
 +    { "worker_threads",        SQLITE_LIMIT_WORKER_THREADS            },
 +  };
 +  int i, n2;
 +  open_db(p, 0);
 +  if( nArg==1 ){
 +    for(i=0; i<ArraySize(aLimit); i++){
 +      fprintf(STD_OUT, "%20s %d\n", aLimit[i].zLimitName,
 +             sqlite3_limit(DBX(p), aLimit[i].limitCode, -1));
 +    }
 +  }else if( nArg>3 ){
 +    return DCR_TooMany;
 +  }else{
 +    int iLimit = -1;
 +    n2 = strlen30(azArg[1]);
 +    for(i=0; i<ArraySize(aLimit); i++){
 +      if( sqlite3_strnicmp(aLimit[i].zLimitName, azArg[1], n2)==0 ){
 +        if( iLimit<0 ){
 +          iLimit = i;
 +        }else{
 +          *pzErr = smprintf("ambiguous limit: \"%s\"\n", azArg[1]);
 +          return DCR_Error;
 +        }
 +      }
 +    }
 +    if( iLimit<0 ){
 +      *pzErr = smprintf("unknown limit: \"%s\"\n"
 +                        "enter \".limits\" with no arguments for a list.\n",
 +                        azArg[1]);
 +      return DCR_ArgWrong;
 +    }
 +    if( nArg==3 ){
 +      sqlite3_limit(DBX(p), aLimit[iLimit].limitCode,
 +                    (int)integerValue(azArg[2]));
 +    }
 +    fprintf(STD_OUT, "%20s %d\n", aLimit[iLimit].zLimitName,
 +           sqlite3_limit(DBX(p), aLimit[iLimit].limitCode, -1));
    }
 -  shellFinalize(&rc, pLoop);
 -  pLoop = 0;
 +  return DCR_Ok;
 +}
  
 -  shellPrepare(pState->db, &rc,
 -      "SELECT pgno FROM recovery.map WHERE root=?", &pPages
 -  );
 +DISPATCHABLE_COMMAND( lint 3 1 0 ){
 +  sqlite3 *db;                    /* Database handle to query "main" db of */
 +  FILE *out = ISS(p)->out;        /* Stream to write non-error output to */
 +  int bVerbose = 0;               /* If -verbose is present */
 +  int bGroupByParent = 0;         /* If -groupbyparent is present */
 +  int i;                          /* To iterate through azArg[] */
 +  const char *zIndent = "";       /* How much to indent CREATE INDEX by */
 +  int rc;                         /* Return code */
 +  sqlite3_stmt *pSql = 0;         /* Compiled version of SQL statement below */
  
 -  shellPrepare(pState->db, &rc,
 -      "SELECT max(field), group_concat(shell_escape_crnl(quote"
 -      "(case when (? AND field<0) then NULL else value end)"
 -      "), ', ')"
 -      ", min(field) "
 -      "FROM sqlite_dbdata WHERE pgno = ? AND field != ?"
 -      "GROUP BY cell", &pCells
 -  );
 +  i = (nArg>=2 ? strlen30(azArg[1]) : 0);
 +  if( i==0 || 0!=sqlite3_strnicmp(azArg[1], "fkey-indexes", i) ){
 +    *pzErr = smprintf
 +      ("Usage %s sub-command ?switches...?\n"
 +       "Where sub-commands are:\n"
 +       "    fkey-indexes\n", azArg[0]);
 +    return DCR_SayUsage;
 +  }
 +  open_db(p, 0);
 +  db = DBX(p);
  
 -  /* Loop through each root page. */
 -  shellPrepare(pState->db, &rc, 
 -      "SELECT root, intkey, max(maxlen) FROM recovery.map" 
 -      " WHERE root>1 GROUP BY root, intkey ORDER BY root=("
 -      "  SELECT rootpage FROM recovery.schema WHERE name='sqlite_sequence'"
 -      ")", &pLoop
 -  );
 -  while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
 -    int iRoot = sqlite3_column_int(pLoop, 0);
 -    int bIntkey = sqlite3_column_int(pLoop, 1);
 -    int nCol = sqlite3_column_int(pLoop, 2);
 -    int bNoop = 0;
 -    RecoverTable *pTab;
 +  /*
 +  ** This SELECT statement returns one row for each foreign key constraint
 +  ** in the schema of the main database. The column values are:
 +  **
 +  ** 0. The text of an SQL statement similar to:
 +  **
 +  **      "EXPLAIN QUERY PLAN SELECT 1 FROM child_table WHERE child_key=?"
 +  **
 +  **    This SELECT is similar to the one that the foreign keys implementation
 +  **    needs to run internally on child tables. If there is an index that can
 +  **    be used to optimize this query, then it can also be used by the FK
 +  **    implementation to optimize DELETE or UPDATE statements on the parent
 +  **    table.
 +  **
 +  ** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by
 +  **    the EXPLAIN QUERY PLAN command matches this pattern, then the schema
 +  **    contains an index that can be used to optimize the query.
 +  **
 +  ** 2. Human readable text that describes the child table and columns. e.g.
 +  **
 +  **       "child_table(child_key1, child_key2)"
 +  **
 +  ** 3. Human readable text that describes the parent table and columns. e.g.
 +  **
 +  **       "parent_table(parent_key1, parent_key2)"
 +  **
 +  ** 4. A full CREATE INDEX statement for an index that could be used to
 +  **    optimize DELETE or UPDATE statements on the parent table. e.g.
 +  **
 +  **       "CREATE INDEX child_table_child_key ON child_table(child_key)"
 +  **
 +  ** 5. The name of the parent table.
 +  **
 +  ** These six values are used by the C logic below to generate the report.
 +  */
 +  const char *zSql =
 +  "SELECT "
 +    "     'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '"
 +    "  || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' "
 +    "  || fkey_collate_clause("
 +    "       f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')"
 +    ", "
 +    "     'SEARCH ' || s.name || ' USING COVERING INDEX*('"
 +    "  || group_concat('*=?', ' AND ') || ')'"
 +    ", "
 +    "     s.name  || '(' || group_concat(f.[from],  ', ') || ')'"
 +    ", "
 +    "     f.[table] || '(' || group_concat(COALESCE(f.[to], p.[name])) || ')'"
 +    ", "
 +    "     'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))"
 +    "  || ' ON ' || quote(s.name) || '('"
 +    "  || group_concat(quote(f.[from]) ||"
 +    "        fkey_collate_clause("
 +    "          f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')"
 +    "  || ');'"
 +    ", "
 +    "     f.[table] "
 +    "FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f "
 +    "LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) "
 +    "GROUP BY s.name, f.id "
 +    "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)"
 +  ;
 +  const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)";
  
 -    assert( bIntkey==0 || bIntkey==1 );
 -    pTab = recoverFindTable(pState, &rc, iRoot, bIntkey, nCol, &bNoop);
 -    if( bNoop || rc ) continue;
 -    if( pTab==0 ){
 -      if( pOrphan==0 ){
 -        pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan);
 -      }
 -      pTab = pOrphan;
 -      if( pTab==0 ) break;
 +  for(i=2; i<nArg; i++){
 +    int n = strlen30(azArg[i]);
 +    if( n>1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){
 +      bVerbose = 1;
      }
 -
 -    if( 0==sqlite3_stricmp(pTab->zQuoted, "\"sqlite_sequence\"") ){
 -      raw_printf(pState->out, "DELETE FROM sqlite_sequence;\n");
 +    else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){
 +      bGroupByParent = 1;
 +      zIndent = "    ";
      }
 -    sqlite3_bind_int(pPages, 1, iRoot);
 -    if( bRowids==0 && pTab->iPk<0 ){
 -      sqlite3_bind_int(pCells, 1, 1);
 -    }else{
 -      sqlite3_bind_int(pCells, 1, 0);
 +    else{
 +      raw_printf(STD_ERR, "Usage: %s %s ?-verbose? ?-groupbyparent?\n",
 +                 azArg[0], azArg[1]
 +      );
 +      return DCR_Unknown|i;
      }
 -    sqlite3_bind_int(pCells, 3, pTab->iPk);
 -
 -    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPages) ){
 -      int iPgno = sqlite3_column_int(pPages, 0);
 -      sqlite3_bind_int(pCells, 2, iPgno);
 -      while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pCells) ){
 -        int nField = sqlite3_column_int(pCells, 0);
 -        int iMin = sqlite3_column_int(pCells, 2);
 -        const char *zVal = (const char*)sqlite3_column_text(pCells, 1);
 +  }
  
 -        RecoverTable *pTab2 = pTab;
 -        if( pTab!=pOrphan && (iMin<0)!=bIntkey ){
 -          if( pOrphan==0 ){
 -            pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan);
 -          }
 -          pTab2 = pOrphan;
 -          if( pTab2==0 ) break;
 -        }
 +  /* Register the fkey_collate_clause() SQL function */
 +  rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8,
 +      0, shellFkeyCollateClause, 0, 0
 +  );
  
 -        nField = nField+1;
 -        if( pTab2==pOrphan ){
 -          raw_printf(pState->out, 
 -              "INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n",
 -              pTab2->zQuoted, iRoot, iPgno, nField,
 -              iMin<0 ? "" : "NULL, ", zVal, pTab2->azlCol[nField]
 -          );
 -        }else{
 -          raw_printf(pState->out, "INSERT INTO %s(%s) VALUES( %s );\n", 
 -              pTab2->zQuoted, pTab2->azlCol[nField], zVal
 -          );
 -        }
 -      }
 -      shellReset(&rc, pCells);
 -    }
 -    shellReset(&rc, pPages);
 -    if( pTab!=pOrphan ) recoverFreeTable(pTab);
 +  if( rc==SQLITE_OK ){
 +    rc = sqlite3_prepare_v2(db, zSql, -1, &pSql, 0);
 +  }
 +  if( rc==SQLITE_OK ){
 +    sqlite3_bind_int(pSql, 1, bGroupByParent);
    }
 -  shellFinalize(&rc, pLoop);
 -  shellFinalize(&rc, pPages);
 -  shellFinalize(&rc, pCells);
 -  recoverFreeTable(pOrphan);
  
 -  /* The rest of the schema */
    if( rc==SQLITE_OK ){
 -    sqlite3_stmt *pStmt = 0;
 -    shellPrepare(pState->db, &rc, 
 -        "SELECT sql, name FROM recovery.schema "
 -        "WHERE sql NOT LIKE 'create table%'", &pStmt
 -    );
 -    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
 -      const char *zSql = (const char*)sqlite3_column_text(pStmt, 0);
 -      if( sqlite3_strnicmp(zSql, "create virt", 11)==0 ){
 -        const char *zName = (const char*)sqlite3_column_text(pStmt, 1);
 -        char *zPrint = shellMPrintf(&rc, 
 -          "INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)",
 -          zName, zName, zSql
 -        );
 -        raw_printf(pState->out, "%s;\n", zPrint);
 -        sqlite3_free(zPrint);
 +    int rc2;
 +    char *zPrev = 0;
 +    while( SQLITE_ROW==sqlite3_step(pSql) ){
 +      int res = -1;
 +      sqlite3_stmt *pExplain = 0;
 +      const char *zEQP = (const char*)sqlite3_column_text(pSql, 0);
 +      const char *zGlob = (const char*)sqlite3_column_text(pSql, 1);
 +      const char *zFrom = (const char*)sqlite3_column_text(pSql, 2);
 +      const char *zTarget = (const char*)sqlite3_column_text(pSql, 3);
 +      const char *zCI = (const char*)sqlite3_column_text(pSql, 4);
 +      const char *zParent = (const char*)sqlite3_column_text(pSql, 5);
 +
 +      if( zEQP==0 || zGlob==0 ) continue;
 +      rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
 +      if( rc!=SQLITE_OK ) break;
 +      if( SQLITE_ROW==sqlite3_step(pExplain) ){
 +        const char *zPlan = (const char*)sqlite3_column_text(pExplain, 3);
 +        res = zPlan!=0 && (  0==sqlite3_strglob(zGlob, zPlan)
 +                             || 0==sqlite3_strglob(zGlobIPK, zPlan));
 +      }
 +      rc = sqlite3_finalize(pExplain);
 +      if( rc!=SQLITE_OK ) break;
 +
 +      if( res<0 ){
 +        raw_printf(STD_ERR, "Error: internal error");
 +        break;
        }else{
 -        raw_printf(pState->out, "%s;\n", zSql);
 +        if( bGroupByParent
 +        && (bVerbose || res==0)
 +        && (zPrev==0 || sqlite3_stricmp(zParent, zPrev))
 +        ){
 +          raw_printf(out, "-- Parent table %s\n", zParent);
 +          sqlite3_free(zPrev);
 +          zPrev = smprintf("%s", zParent);
 +        }
 +
 +        if( res==0 ){
 +          raw_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget);
 +        }else if( bVerbose ){
 +          raw_printf(out, "%s/* no extra indexes required for %s -> %s */\n",
 +                     zIndent, zFrom, zTarget);
 +        }
        }
      }
 -    shellFinalize(&rc, pStmt);
 -  }
 +    sqlite3_free(zPrev);
 +
 +    if( rc!=SQLITE_OK ){
 +      *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
 +    }
  
 -  if( rc==SQLITE_OK ){
 -    raw_printf(pState->out, "PRAGMA writable_schema = off;\n");
 -    raw_printf(pState->out, "COMMIT;\n");
 +    rc2 = sqlite3_finalize(pSql);
 +    if( rc==SQLITE_OK && rc2!=SQLITE_OK ){
 +      rc = rc2;
 +      *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
 +    }
 +  }else{
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
    }
 -  sqlite3_exec(pState->db, "DETACH recovery", 0, 0, 0);
 -  return rc;
 +
 +  return DCR_Ok|(rc!=0);
  }
 -#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
  
 +DISPATCHABLE_COMMAND( load ? 2 3 ){
 +  const char *zFile = 0, *zProc = 0;
 +  int ai = 1, rc;
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +  while( ai<nArg ){
 +    const char *zA = azArg[ai++];
 +    if( zFile==0 ) zFile = zA;
 +    else if( zProc==0 ) zProc = zA;
 +    else return DCR_TooMany|ai;
 +  }
 +  open_db(p, 0);
 +  rc = sqlite3_load_extension(DBX(p), zFile, zProc, pzErr);
 +  return DCR_Ok|(rc!=SQLITE_OK);
 +}
  
 -/*
 - * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it.
 - * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE,
 - *   close db and set it to 0, and return the columns spec, to later
 - *   be sqlite3_free()'ed by the caller.
 - * The return is 0 when either:
 - *   (a) The db was not initialized and zCol==0 (There are no columns.)
 - *   (b) zCol!=0  (Column was added, db initialized as needed.)
 - * The 3rd argument, pRenamed, references an out parameter. If the
 - * pointer is non-zero, its referent will be set to a summary of renames
 - * done if renaming was necessary, or set to 0 if none was done. The out
 - * string (if any) must be sqlite3_free()'ed by the caller.
 - */
 -#ifdef SHELL_DEBUG
 -#define rc_err_oom_die(rc) \
 -  if( rc==SQLITE_NOMEM ) shell_check_oom(0); \
 -  else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \
 -    fprintf(stderr,"E:%d\n",rc), assert(0)
 -#else
 -static void rc_err_oom_die(int rc){
 -  if( rc==SQLITE_NOMEM ) shell_check_oom(0);
 -  assert(rc==SQLITE_OK||rc==SQLITE_DONE);
 +DISPATCHABLE_COMMAND( log ? 2 2 ){
 +  const char *zFile = azArg[1];
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +  output_file_close(ISS(p)->pLog);
 +  ISS(p)->pLog = output_file_open(zFile, 0);
 +  return DCR_Ok;
  }
 -#endif
  
 -#ifdef SHELL_COLFIX_DB /* If this is set, the DB can be in a file. */
 -static char zCOL_DB[] = SHELL_STRINGIFY(SHELL_COLFIX_DB);
 -#else  /* Otherwise, memory is faster/better for the transient DB. */
 -static const char *zCOL_DB = ":memory:";
 -#endif
 +static void effectMode(ShellInState *psi, u8 modeRequest, u8 modeNominal){
 +  /* Effect the specified mode change. */
 +  const char *zColSep = 0, *zRowSep = 0;
 +  assert(modeNominal!=MODE_COUNT_OF);
 +  switch( modeRequest ){
 +  case MODE_Line:
 +    zRowSep = SEP_Row;
 +    break;
 +  case MODE_Column:
 +    if( (psi->shellFlgs & SHFLG_HeaderSet)==0 ){
 +      psi->showHeader = 1;
 +    }
 +    zRowSep = SEP_Row;
 +    break;
 +  case MODE_List:
 +    zColSep = SEP_Column;
 +    zRowSep = SEP_Row;
 +    break;
 +  case MODE_Html:
 +    break;
 +  case MODE_Tcl:
 +    zColSep = SEP_Space;
 +    zRowSep = SEP_Row;
 +    break;
 +  case MODE_Csv:
 +    zColSep = SEP_Comma;
 +    zRowSep = SEP_CrLf;
 +    break;
 +  case MODE_Tab:
 +    zColSep = SEP_Tab;
 +    break;
 +  case MODE_Insert:
 +    break;
 +  case MODE_Quote:
 +    zColSep = SEP_Comma;
 +    zRowSep = SEP_Row;
 +    break;
 +  case MODE_Ascii:
 +    zColSep = SEP_Unit;
 +    zRowSep = SEP_Record;
 +    break;
 +  case MODE_Markdown:
 +    /* fall-thru */
 +  case MODE_Table:
 +    /* fall-thru */
 +  case MODE_Box:
 +    break;
 +  case MODE_Count:
 +    /* fall-thru */
 +  case MODE_Off:
 +    /* fall-thru */
 +  case MODE_Json:
 +    break;
 +  case MODE_Explain: case MODE_Semi: case MODE_EQP: case MODE_Pretty: default:
 +    /* Modes used internally, not settable by .mode command. */
 +    return;
 +  }
 +  if( zRowSep!=0 ){
 +    sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, zRowSep);
 +  }
 +  if( zColSep!=0 ){
 +    sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator, zColSep);
 +  }
 +  psi->mode = modeNominal;
 +}
  
 -/* Define character (as C string) to separate generated column ordinal
 - * from protected part of incoming column names. This defaults to "_"
 - * so that incoming column identifiers that did not need not be quoted
 - * remain usable without being quoted. It must be one character.
 +/*****************
 + * The .mode command
   */
 -#ifndef SHELL_AUTOCOLUMN_SEP
 -# define AUTOCOLUMN_SEP "_"
 -#else
 -# define AUTOCOLUMN_SEP SHELL_STRINGIFY(SHELL_AUTOCOLUMN_SEP)
 -#endif
 -
 -static char *zAutoColumn(const char *zColNew, sqlite3 **pDb, char **pzRenamed){
 -  /* Queries and D{D,M}L used here */
 -  static const char * const zTabMake = "\
 -CREATE TABLE ColNames(\
 - cpos INTEGER PRIMARY KEY,\
 - name TEXT, nlen INT, chop INT, reps INT, suff TEXT);\
 -CREATE VIEW RepeatedNames AS \
 -SELECT DISTINCT t.name FROM ColNames t \
 -WHERE t.name COLLATE NOCASE IN (\
 - SELECT o.name FROM ColNames o WHERE o.cpos<>t.cpos\
 -);\
 -";
 -  static const char * const zTabFill = "\
 -INSERT INTO ColNames(name,nlen,chop,reps,suff)\
 - VALUES(iif(length(?1)>0,?1,'?'),max(length(?1),1),0,0,'')\
 -";
 -  static const char * const zHasDupes = "\
 -SELECT count(DISTINCT (substring(name,1,nlen-chop)||suff) COLLATE NOCASE)\
 - <count(name) FROM ColNames\
 -";
 -#ifdef SHELL_COLUMN_RENAME_CLEAN
 -  static const char * const zDedoctor = "\
 -UPDATE ColNames SET chop=iif(\
 -  (substring(name,nlen,1) BETWEEN '0' AND '9')\
 -  AND (rtrim(name,'0123456790') glob '*"AUTOCOLUMN_SEP"'),\
 - nlen-length(rtrim(name, '"AUTOCOLUMN_SEP"0123456789')),\
 - 0\
 -)\
 -";
 -#endif
 -  static const char * const zSetReps = "\
 -UPDATE ColNames AS t SET reps=\
 -(SELECT count(*) FROM ColNames d \
 - WHERE substring(t.name,1,t.nlen-t.chop)=substring(d.name,1,d.nlen-d.chop)\
 - COLLATE NOCASE\
 -)\
 -";
 -#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
 -  static const char * const zColDigits = "\
 -SELECT CAST(ceil(log(count(*)+0.5)) AS INT) FROM ColNames \
 -";
 -#else
 -  /* Counting on SQLITE_MAX_COLUMN < 100,000 here. (32767 is the hard limit.) */
 -  static const char * const zColDigits = "\
 -SELECT CASE WHEN (nc < 10) THEN 1 WHEN (nc < 100) THEN 2 \
 - WHEN (nc < 1000) THEN 3 WHEN (nc < 10000) THEN 4 \
 - ELSE 5 FROM (SELECT count(*) AS nc FROM ColNames) \
 -";
 -#endif
 -  static const char * const zRenameRank =
 -#ifdef SHELL_COLUMN_RENAME_CLEAN
 -    "UPDATE ColNames AS t SET suff="
 -    "iif(reps>1, printf('%c%0*d', '"AUTOCOLUMN_SEP"', $1, cpos), '')"
 -#else /* ...RENAME_MINIMAL_ONE_PASS */
 -"WITH Lzn(nlz) AS (" /* Find minimum extraneous leading 0's for uniqueness */
 -"  SELECT 0 AS nlz"
 -"  UNION"
 -"  SELECT nlz+1 AS nlz FROM Lzn"
 -"  WHERE EXISTS("
 -"   SELECT 1"
 -"   FROM ColNames t, ColNames o"
 -"   WHERE"
 -"    iif(t.name IN (SELECT * FROM RepeatedNames),"
 -"     printf('%s"AUTOCOLUMN_SEP"%s',"
 -"      t.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,t.cpos),2)),"
 -"     t.name"
 -"    )"
 -"    ="
 -"    iif(o.name IN (SELECT * FROM RepeatedNames),"
 -"     printf('%s"AUTOCOLUMN_SEP"%s',"
 -"      o.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,o.cpos),2)),"
 -"     o.name"
 -"    )"
 -"    COLLATE NOCASE"
 -"    AND o.cpos<>t.cpos"
 -"   GROUP BY t.cpos"
 -"  )"
 -") UPDATE Colnames AS t SET"
 -" chop = 0," /* No chopping, never touch incoming names. */
 -" suff = iif(name IN (SELECT * FROM RepeatedNames),"
 -"  printf('"AUTOCOLUMN_SEP"%s', substring("
 -"   printf('%.*c%0.*d',(SELECT max(nlz) FROM Lzn)+1,'0',1,t.cpos),2)),"
 -"  ''"
 -" )"
 -#endif
 -    ;
 -  static const char * const zCollectVar = "\
 -SELECT\
 - '('||x'0a'\
 - || group_concat(\
 -  cname||' TEXT',\
 -  ','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\
 - ||')' AS ColsSpec \
 -FROM (\
 - SELECT cpos, printf('\"%w\"',printf('%!.*s%s', nlen-chop,name,suff)) AS cname \
 - FROM ColNames ORDER BY cpos\
 -)";
 -  static const char * const zRenamesDone =
 -    "SELECT group_concat("
 -    " printf('\"%w\" to \"%w\"',name,printf('%!.*s%s', nlen-chop, name, suff)),"
 -    " ','||x'0a')"
 -    "FROM ColNames WHERE suff<>'' OR chop!=0"
 -    ;
 -  int rc;
 -  sqlite3_stmt *pStmt = 0;
 -  assert(pDb!=0);
 -  if( zColNew ){
 -    /* Add initial or additional column. Init db if necessary. */
 -    if( *pDb==0 ){
 -      if( SQLITE_OK!=sqlite3_open(zCOL_DB, pDb) ) return 0;
 -#ifdef SHELL_COLFIX_DB
 -      if(*zCOL_DB!=':')
 -        sqlite3_exec(*pDb,"drop table if exists ColNames;"
 -                     "drop view if exists RepeatedNames;",0,0,0);
 -#endif
 -      rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0);
 -      rc_err_oom_die(rc);
 +COLLECT_HELP_TEXT[
 +  ".mode MODE ?OPTIONS?     Set output mode",
 +  "   MODE is one of:",
 +  "     ascii       Columns/rows delimited by 0x1F and 0x1E",
 +  "     box         Tables using unicode box-drawing characters",
 +  "     csv         Comma-separated values",
 +  "     column      Output in columns.  (See .width)",
 +  "     html        HTML <table> code",
 +  "     insert      SQL insert statements for TABLE",
 +  "     json        Results in a JSON array",
 +  "     line        One value per line",
 +  "     list        Values delimited by \"|\"",
 +  "     markdown    Markdown table format",
 +  "     qbox        Shorthand for \"box --width 60 --quote\"",
 +  "     quote       Escape answers as for SQL",
 +  "     table       ASCII-art table",
 +  "     tabs        Tab-separated values",
 +  "     tcl         TCL list elements",
 +  "   OPTIONS: (for columnar modes or insert mode):",
 +  "     --wrap N       Wrap output lines to no longer than N characters",
 +  "     --wordwrap B   Wrap or not at word boundaries per B (on/off)",
 +  "     --ww           Shorthand for \"--wordwrap 1\"",
 +  "     --quote        Quote output text as SQL literals",
 +  "     --noquote      Do not quote output text",
 +  "     TABLE          The name of SQL table used for \"insert\" mode",
 +];
 +DISPATCHABLE_COMMAND( mode ? 1 0 ){
 +  ShellInState *psi = ISS(p);
 +  const char *zTabname = 0;
 +  const char *zArg;
 +  int i, aix;
 +  u8 foundMode = MODE_COUNT_OF, setMode = MODE_COUNT_OF;
 +  ColModeOpts cmOpts = ColModeOpts_default;
 +  for(aix=1; aix<nArg; aix++){
 +    zArg = azArg[aix];
 +    if( optionMatch(zArg,"wrap") && aix+1<nArg ){
 +      cmOpts.iWrap = integerValue(azArg[++aix]);
 +    }else if( optionMatch(zArg,"ww") ){
 +      cmOpts.bWordWrap = 1;
 +    }else if( optionMatch(zArg,"wordwrap") && aix+1<nArg ){
 +      cmOpts.bWordWrap = (u8)booleanValue(azArg[++aix]);
 +    }else if( optionMatch(zArg,"quote") ){
 +      cmOpts.bQuote = 1;
 +    }else if( optionMatch(zArg,"noquote") ){
 +      cmOpts.bQuote = 0;
 +    }else{
 +      /* Not a known option. Check for known mode, or possibly a table name. */
 +      if( foundMode==MODE_Insert && zTabname==0 ){
 +        zTabname = zArg;
 +      }else if( *zArg=='-' ){
 +        goto flag_unknown;
 +      }else{
 +        int im, nza = strlen30(zArg);
 +        if( foundMode!=MODE_COUNT_OF ) goto mode_badarg;
 +        for( im=0; im<MODE_COUNT_OF; ++im ){
 +          if( modeDescr[im].bUserBlocked ) continue;
 +          if( strncmp(zArg,modeDescr[im].zModeName,nza)==0 ){
 +            foundMode = (u8)im;
 +            setMode = modeDescr[im].iAliasFor;
 +            break;
 +          }
 +        }
 +        if( strcmp(zArg, "qbox")==0 ){
 +          ColModeOpts cmo = ColModeOpts_default_qbox;
 +          foundMode = setMode = MODE_Box;
 +          cmOpts = cmo;
 +        }else if( im==MODE_COUNT_OF ) goto mode_unknown;
 +      }
 +    }
 +  } /* Arg loop */
 +  if( foundMode==MODE_COUNT_OF ){
 +    FILE *out = psi->out;
 +    const char *zMode;
 +    int nms;
 +    i = psi->mode;
 +    assert(i>=0 && i<MODE_COUNT_OF);
 +    zMode = modeDescr[i].zModeName;
 +    nms = strlen30(zMode)-modeDescr[i].bDepluralize;
 +    /* Mode not specified. Show present mode (and toss any options set.) */
 +    if( MODE_IS_COLUMNAR(psi->mode) ){
 +      raw_printf
 +        (out, "current output mode: %.*s --wrap %d --wordwrap %s --%squote\n",
 +         nms, zMode, psi->cmOpts.iWrap,
 +         psi->cmOpts.bWordWrap ? "on" : "off",
 +         psi->cmOpts.bQuote ? "" : "no");
 +    }else{
 +      raw_printf(out, "current output mode: %.*s\n", nms, zMode);
      }
 -    assert(*pDb!=0);
 -    rc = sqlite3_prepare_v2(*pDb, zTabFill, -1, &pStmt, 0);
 -    rc_err_oom_die(rc);
 -    rc = sqlite3_bind_text(pStmt, 1, zColNew, -1, 0);
 -    rc_err_oom_die(rc);
 -    rc = sqlite3_step(pStmt);
 -    rc_err_oom_die(rc);
 -    sqlite3_finalize(pStmt);
 -    return 0;
 -  }else if( *pDb==0 ){
 -    return 0;
    }else{
 -    /* Formulate the columns spec, close the DB, zero *pDb. */
 -    char *zColsSpec = 0;
 -    int hasDupes = db_int(*pDb, zHasDupes);
 -    int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0;
 -    if( hasDupes ){
 -#ifdef SHELL_COLUMN_RENAME_CLEAN
 -      rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0);
 -      rc_err_oom_die(rc);
 -#endif
 -      rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0);
 -      rc_err_oom_die(rc);
 -      rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0);
 -      rc_err_oom_die(rc);
 -      sqlite3_bind_int(pStmt, 1, nDigits);
 -      rc = sqlite3_step(pStmt);
 -      sqlite3_finalize(pStmt);
 -      assert(rc==SQLITE_DONE);
 +    effectMode(psi, foundMode, setMode);
 +    if( MODE_IS_COLUMNAR(setMode) ) psi->cmOpts = cmOpts;
 +    else if( setMode==MODE_Insert ){
 +      set_table_name(p, zTabname ? zTabname : "table");
      }
 -    assert(db_int(*pDb, zHasDupes)==0); /* Consider: remove this */
 -    rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0);
 -    rc_err_oom_die(rc);
 -    rc = sqlite3_step(pStmt);
 -    if( rc==SQLITE_ROW ){
 -      zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 +  }
 +  psi->cMode = psi->mode;
 +  return DCR_Ok;
 + flag_unknown:
 +  *pzErr = smprintf("Unknown .mode option: %s\nValid options:\n%s",
 +                    zArg,
 +                    "  --noquote\n"
 +                    "  --quote\n"
 +                    "  --wordwrap on/off\n"
 +                    "  --wrap N\n"
 +                    "  --ww\n");
 +  return DCR_Unknown|aix;
 + mode_unknown:
 +  *pzErr = smprintf("Mode should be one of:\n"
 +                    "  ascii box column csv html insert json line\n"
 +                    "  list markdown qbox quote table tabs tcl\n");
 +  return DCR_Unknown|aix;
 + mode_badarg:
 +  *pzErr = smprintf("Invalid .mode argument: %s\n", zArg);
 +  return DCR_ArgWrong|aix;
 +}
 +
 +/*****************
 + * The .open, .nonce and .nullvalue commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".open ?OPTIONS? ?FILE?   Close existing database and reopen FILE",
 +  "     Options:",
 +  "        --append        Use appendvfs to append database to the end of FILE",
 +#ifndef SQLITE_OMIT_DESERIALIZE
 +  "        --deserialize   Load into memory using sqlite3_deserialize()",
 +  "        --hexdb         Load the output of \"dbtotxt\" as an in-memory db",
 +  "        --maxsize N     Maximum size for --hexdb or --deserialized database",
 +#endif
 +  "        --new           Initialize FILE to an empty database",
 +  "        --nofollow      Do not follow symbolic links",
 +  "        --readonly      Open FILE readonly",
 +  "        --zip           FILE is a ZIP archive",
 +  ".nonce STRING            Suspend safe mode for one command if nonce matches",
 +  ".nullvalue STRING        Use STRING in place of NULL values",
 +];
 +DISPATCHABLE_COMMAND( open 3 1 0 ){
 +  ShellInState *psi = ISS(p);
 +  const char *zFN = 0;     /* Pointer to constant filename */
 +  char *zNewFilename = 0;  /* Name of the database file to open */
 +  int iName = 1;           /* Index in azArg[] of the filename */
 +  int newFlag = 0;         /* True to delete file before opening */
 +  u8 openMode = SHELL_OPEN_UNSPEC;
 +  int openFlags = 0;
 +  sqlite3_int64 szMax = 0;
 +  int rc = 0;
 +  /* Check for command-line arguments */
 +  for(iName=1; iName<nArg; iName++){
 +    const char *z = azArg[iName];
 +    if( optionMatch(z,"new") ){
 +      newFlag = 1;
 +#ifdef SQLITE_HAVE_ZLIB
 +    }else if( optionMatch(z, "zip") ){
 +      openMode = SHELL_OPEN_ZIPFILE;
 +#endif
 +    }else if( optionMatch(z, "append") ){
 +      openMode = SHELL_OPEN_APPENDVFS;
 +    }else if( optionMatch(z, "readonly") ){
 +      openMode = SHELL_OPEN_READONLY;
 +    }else if( optionMatch(z, "nofollow") ){
 +      openFlags |= SQLITE_OPEN_NOFOLLOW;
 +#ifndef SQLITE_OMIT_DESERIALIZE
 +    }else if( optionMatch(z, "deserialize") ){
 +      openMode = SHELL_OPEN_DESERIALIZE;
 +    }else if( optionMatch(z, "hexdb") ){
 +      openMode = SHELL_OPEN_HEXDB;
 +    }else if( optionMatch(z, "maxsize") && iName+1<nArg ){
 +      szMax = integerValue(azArg[++iName]);
 +#endif /* SQLITE_OMIT_DESERIALIZE */
 +    }else if( z[0]=='-' ){
 +      return DCR_Unknown|iName;
 +    }else if( zFN ){
 +      *pzErr = smprintf("extra argument: \"%s\"\n", z);
 +      return DCR_TooMany;
      }else{
 -      zColsSpec = 0;
 +      zFN = z;
 +    }
 +  }
 +
 +  /* Close the existing database */
 +  session_close_all(psi, -1);
 +#if SHELL_DYNAMIC_EXTENSION
 +  if( DBX(p)!=0 ) notify_subscribers(psi, NK_DbAboutToClose, DBX(p));
 +#endif
 +  close_db(DBX(p));
 +  DBX(p) = 0;
 +  psi->pAuxDb->zDbFilename = 0;
 +  sqlite3_free(psi->pAuxDb->zFreeOnClose);
 +  psi->pAuxDb->zFreeOnClose = 0;
 +  psi->openMode = openMode;
 +  psi->openFlags = 0;
 +  psi->szMax = 0;
 +
 +  /* If a filename is specified, try to open it first */
 +  if( zFN || psi->openMode==SHELL_OPEN_HEXDB ){
 +    if( newFlag && zFN && !psi->bSafeMode ) shellDeleteFile(zFN);
 +    if( psi->bSafeMode
 +        && psi->openMode!=SHELL_OPEN_HEXDB
 +        && zFN
 +        && strcmp(zFN,":memory:")!=0
 +        ){
 +      *pzErr = smprintf("cannot open database files in safe mode");
 +      return DCR_AbortError;
      }
 -    if( pzRenamed!=0 ){
 -      if( !hasDupes ) *pzRenamed = 0;
 -      else{
 -        sqlite3_finalize(pStmt);
 -        if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0)
 -            && SQLITE_ROW==sqlite3_step(pStmt) ){
 -          *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 -        }else
 -          *pzRenamed = 0;
 -      }
 +    if( zFN ){
 +      zNewFilename = smprintf("%s", zFN);
 +      shell_check_oom(zNewFilename);
 +    }else{
 +      zNewFilename = 0;
 +    }
 +    psi->pAuxDb->zDbFilename = zNewFilename;
 +    psi->openFlags = openFlags;
 +    psi->szMax = szMax;
 +    open_db(p, OPEN_DB_KEEPALIVE);
 +    if( DBX(p)==0 ){
 +      *pzErr = smprintf("cannot open '%s'\n", zNewFilename);
 +      sqlite3_free(zNewFilename);
 +      rc = 1;
 +    }else{
 +      psi->pAuxDb->zFreeOnClose = zNewFilename;
      }
 -    sqlite3_finalize(pStmt);
 -    sqlite3_close(*pDb);
 -    *pDb = 0;
 -    return zColsSpec;
    }
 +  if( DBX(p)==0 ){
 +    /* As a fall-back open a TEMP database */
 +    psi->pAuxDb->zDbFilename = 0;
 +    open_db(p, 0);
 +  }
 +  return DCR_Ok|(rc!=0);
  }
  
 -/*
 -** If an input line begins with "." then invoke this routine to
 -** process that line.
 -**
 -** Return 1 on error, 2 to exit, and 0 otherwise.
 -*/
 -static int do_meta_command(char *zLine, ShellState *p){
 -  int h = 1;
 -  int nArg = 0;
 -  int n, c;
 -  int rc = 0;
 -  char *azArg[52];
 -
 -#ifndef SQLITE_OMIT_VIRTUALTABLE
 -  if( p->expert.pExpert ){
 -    expertFinish(p, 1, 0);
 +DISPATCHABLE_COMMAND( nonce ? 2 2 ){
 +  ShellInState *psi = ISS(p);
 +  if( psi->zNonce==0 || strcmp(azArg[1],psi->zNonce)!=0 ){
 +    raw_printf(STD_ERR, "line %d: incorrect nonce: \"%s\"\n",
 +               psi->pInSource->lineno, azArg[1]);
 +    exit(1);
    }
 +  /* Suspend safe mode for 1 dot-command after this. */
 +  psi->bSafeModeFuture = 2;
 +  return DCR_Ok;
 +}
 +
 +DISPATCHABLE_COMMAND( nullvalue ? 2 2 ){
 +  sqlite3_snprintf(sizeof(ISS(p)->nullValue), ISS(p)->nullValue, "%.*s",
 +                   (int)ArraySize(ISS(p)->nullValue)-1, azArg[1]);
 +  return DCR_Ok;
 +}
 +
 +/* Helper functions for .parameter and .vars commands
 + */
 +
 +struct keyval_row { char * value; int uses; int hits; };
 +
 +static int kv_find_callback(void *pData, int nc, char **pV, char **pC){
 +  assert(nc>=1);
 +  assert(strcmp(pC[0],"value")==0);
 +  struct keyval_row *pParam = (struct keyval_row *)pData;
 +  assert(pParam->value==0); /* key values are supposedly unique. */
 +  if( pParam->value!=0 ) sqlite3_free( pParam->value );
 +  pParam->value = smprintf("%s", pV[0]); /* source owned by statement */
 +  if( nc>1 ) pParam->uses = (int)integerValue(pV[1]);
 +  ++pParam->hits;
 +  return 0;
 +}
 +
 +static void append_in_clause(sqlite3_str *pStr,
 +                             const char **azBeg, const char **azLim);
 +static void append_glob_terms(sqlite3_str *pStr, const char *zColName,
 +                              const char **azBeg, const char **azLim);
 +static char *find_home_dir(int clearFlag);
 +
 +/* Create a home-relative pathname from ~ prefixed path.
 + * Return it, or 0 for any error.
 + * Caller must sqlite3_free() it.
 + */
 +static char *home_based_path( const char *zPath ){
 +  char *zHome = find_home_dir(0);
 +  char *zErr = 0;
 +  assert( zPath[0]=='~' );
 +  if( zHome==0 ){
 +    zErr = "Cannot find home directory.";
 +  }else if( zPath[0]==0 || (zPath[1]!='/'
 +#if defined(_WIN32) || defined(WIN32)
 +                            && zPath[1]!='\\'
  #endif
 +                            ) ){
 +    zErr = "Malformed pathname";
 +  }else{
 +    return smprintf("%s%s", zHome, zPath+1);
 +  }
 +  utf8_printf(STD_ERR, "Error: %s\n", zErr);
 +  return 0;
 +}
  
 -  /* Parse the input line into tokens.
 -  */
 -  while( zLine[h] && nArg<ArraySize(azArg)-1 ){
 -    while( IsSpace(zLine[h]) ){ h++; }
 -    if( zLine[h]==0 ) break;
 -    if( zLine[h]=='\'' || zLine[h]=='"' ){
 -      int delim = zLine[h++];
 -      azArg[nArg++] = &zLine[h];
 -      while( zLine[h] && zLine[h]!=delim ){
 -        if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++;
 -        h++;
 -      }
 -      if( zLine[h]==delim ){
 -        zLine[h++] = 0;
 -      }
 -      if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
 +/* Transfer selected parameters between two parameter tables, for save/load.
 + * Argument bSaveNotLoad determines transfer direction and other actions.
 + * If it is true, the store DB will be created if not existent, and its
 + * table for keeping parameters will be created. Or failure is returned.
 + * If it is false, the store DB will be opened for read and its presumed
 + * table for keeping parameters will be read. Or failure is returned.
 + *
 + * Arguments azNames and nNames reference the ?NAMES? save/load arguments.
 + * If it is an empty list, all parameters will be saved or loaded.
 + * Otherwise, only the named parameters are transferred, if they exist.
 + * It is not an error to specify a name that cannot be transferred
 + * because it does not exist in the source table.
 + *
 + * Returns are SQLITE_OK for success, or other codes for failure.
 + */
 +static int kv_xfr_table(sqlite3 *db, const char *zStoreDbName,
 +                        int bSaveNotLoad, ParamTableUse ptu,
 +                        const char *azNames[], int nNames){
 +  int rc = 0;
 +  char *zSql = 0; /* to be sqlite3_free()'ed */
 +  sqlite3_str *sbCopy = 0;
 +  const char *zHere = 0;
 +  const char *zThere = SH_KV_STORE_SNAME;
 +  const char *zTo;
 +  const char *zFrom;
 +  sqlite3 *dbStore = 0;
 +  int openFlags = (bSaveNotLoad)
 +    ? SQLITE_OPEN_URI|SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE
 +    : SQLITE_OPEN_READONLY;
 +
 +  switch( ptu ){
 +  case PTU_Binding: zHere = PARAM_TABLE_SNAME; break;
 +  case PTU_Script:  zHere = SHVAR_TABLE_SNAME; break;
 +  default: assert(0); return 1;
 +  }
 +  zTo = (bSaveNotLoad)? zThere : zHere;
 +  zFrom = (bSaveNotLoad)? zHere : zThere;
 +  /* Ensure store DB can be opened and/or created appropriately. */
 +  rc = sqlite3_open_v2(zStoreDbName, &dbStore, openFlags, 0);
 +  if( rc!=SQLITE_OK ){
 +    utf8_printf(STD_ERR, "Error: Cannot %s key/value store DB %s\n",
 +                bSaveNotLoad? "open/create" : "read", zStoreDbName);
 +    return rc;
 +  }
 +  /* Ensure it has the kv store table, or handle its absence. */
 +  assert(dbStore!=0);
 +  if( sqlite3_table_column_metadata
 +      (dbStore, "main", SH_KV_STORE_NAME, 0, 0, 0, 0, 0, 0)!=SQLITE_OK ){
 +    if( !bSaveNotLoad ){
 +      utf8_printf(STD_ERR, "Error: No key/value pairs ever stored in DB %s\n",
 +                  zStoreDbName);
 +      rc = 1;
      }else{
 -      azArg[nArg++] = &zLine[h];
 -      while( zLine[h] && !IsSpace(zLine[h]) ){ h++; }
 -      if( zLine[h] ) zLine[h++] = 0;
 -      resolve_backslashes(azArg[nArg-1]);
 +      /* The saved parameters table is not there yet; create it. */
 +      const char *zCT =
 +        "CREATE TABLE IF NOT EXISTS "SH_KV_STORE_NAME"(\n"
 +        "  key TEXT PRIMARY KEY,\n"
 +        "  value,\n"
 +        "  uses INT\n"
 +        ") WITHOUT ROWID;";
 +      rc = sqlite3_exec(dbStore, zCT, 0, 0, 0);
 +      if( rc!=SQLITE_OK ){
 +        utf8_printf(STD_ERR, "Cannot create table %s. Nothing saved.", zThere);
 +      }
      }
    }
 -  azArg[nArg] = 0;
 +  sqlite3_close(dbStore);
 +  if( rc!=0 ) return rc;
  
 -  /* Process the input line.
 -  */
 -  if( nArg==0 ) return 0; /* no tokens, no error */
 -  n = strlen30(azArg[0]);
 -  c = azArg[0][0];
 -  clearTempFile(p);
 +  zSql = smprintf("ATTACH %Q AS %s;", zStoreDbName, SH_KV_STORE_SCHEMA);
 +  shell_check_oom(zSql);
 +  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 +  sqlite3_free(zSql);
 +  if( rc!=SQLITE_OK ) return rc;
  
 -#ifndef SQLITE_OMIT_AUTHORIZATION
 -  if( c=='a' && strncmp(azArg[0], "auth", n)==0 ){
 -    if( nArg!=2 ){
 -      raw_printf(stderr, "Usage: .auth ON|OFF\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    open_db(p, 0);
 -    if( booleanValue(azArg[1]) ){
 -      sqlite3_set_authorizer(p->db, shellAuth, p);
 -    }else if( p->bSafeModePersist ){
 -      sqlite3_set_authorizer(p->db, safeModeAuth, p);
 -    }else{
 -      sqlite3_set_authorizer(p->db, 0, 0);
 -    }
 -  }else
 -#endif
 +  sbCopy = sqlite3_str_new(db);
 +  sqlite3_str_appendf
 +      (sbCopy, "INSERT OR REPLACE INTO %s(key,value,uses)"
 +       "SELECT key, value, uses FROM %s WHERE key ", zTo, zFrom);
 +  append_in_clause(sbCopy, azNames, azNames+nNames);
 +  zSql = sqlite3_str_finish(sbCopy);
 +  shell_check_oom(zSql);
 +  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 +  sqlite3_free(zSql);
  
 -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
 -  if( c=='a' && strncmp(azArg[0], "archive", n)==0 ){
 -    open_db(p, 0);
 -    failIfSafeMode(p, "cannot run .archive in safe mode");
 -    rc = arDotCommand(p, 0, azArg, nArg);
 -  }else
 -#endif
 +  sqlite3_exec(db, "DETACH "SH_KV_STORE_SCHEMA";", 0, 0, 0);
 +  return rc;
 +}
  
 -  if( (c=='b' && n>=3 && strncmp(azArg[0], "backup", n)==0)
 -   || (c=='s' && n>=3 && strncmp(azArg[0], "save", n)==0)
 -  ){
 -    const char *zDestFile = 0;
 -    const char *zDb = 0;
 -    sqlite3 *pDest;
 -    sqlite3_backup *pBackup;
 -    int j;
 -    int bAsync = 0;
 -    const char *zVfs = 0;
 -    failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
 -    for(j=1; j<nArg; j++){
 -      const char *z = azArg[j];
 -      if( z[0]=='-' ){
 -        if( z[1]=='-' ) z++;
 -        if( strcmp(z, "-append")==0 ){
 -          zVfs = "apndvfs";
 -        }else
 -        if( strcmp(z, "-async")==0 ){
 -          bAsync = 1;
 -        }else
 -        {
 -          utf8_printf(stderr, "unknown option: %s\n", azArg[j]);
 -          return 1;
 -        }
 -      }else if( zDestFile==0 ){
 -        zDestFile = azArg[j];
 -      }else if( zDb==0 ){
 -        zDb = zDestFile;
 -        zDestFile = azArg[j];
 -      }else{
 -        raw_printf(stderr, "Usage: .backup ?DB? ?OPTIONS? FILENAME\n");
 -        return 1;
 -      }
 -    }
 -    if( zDestFile==0 ){
 -      raw_printf(stderr, "missing FILENAME argument on .backup\n");
 -      return 1;
 -    }
 -    if( zDb==0 ) zDb = "main";
 -    rc = sqlite3_open_v2(zDestFile, &pDest, 
 -                  SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs);
 -    if( rc!=SQLITE_OK ){
 -      utf8_printf(stderr, "Error: cannot open \"%s\"\n", zDestFile);
 -      close_db(pDest);
 -      return 1;
 -    }
 -    if( bAsync ){
 -      sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
 -                   0, 0, 0);
 -    }
 -    open_db(p, 0);
 -    pBackup = sqlite3_backup_init(pDest, "main", p->db, zDb);
 -    if( pBackup==0 ){
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
 -      close_db(pDest);
 -      return 1;
 -    }
 -    while(  (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
 -    sqlite3_backup_finish(pBackup);
 -    if( rc==SQLITE_DONE ){
 -      rc = 0;
 -    }else{
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
 -      rc = 1;
 -    }
 -    close_db(pDest);
 -  }else
 +/* Default locations of kv store DBs for .parameters and .vars save/load. */
 +static const char *zDefaultParamStore = "~/sqlite_params.sdb";
 +static const char *zDefaultVarStore = "~/sqlite_vars.sdb";
  
 -  if( c=='b' && n>=3 && strncmp(azArg[0], "bail", n)==0 ){
 -    if( nArg==2 ){
 -      bail_on_error = booleanValue(azArg[1]);
 -    }else{
 -      raw_printf(stderr, "Usage: .bail on|off\n");
 -      rc = 1;
 -    }
 -  }else
 +/* Possibly generate a derived path from input spec, with defaulting
 + * and conversion of leading (or only) tilde as home directory.
 + * The above-set default is used for zSpec NULL, "" or "~".
 + * When return is 0, there is an error; what needs doing cannnot be done.
 + * If the return is exactly the input, it must not be sqlite3_free()'ed.
 + * If the return differs from the input, it must be sqlite3_free()'ed.
 + */
 +  static const char *kv_store_path(const char *zSpec, ParamTableUse ptu){
 +  if( zSpec==0 || zSpec[0]==0 || strcmp(zSpec,"~")==0 ){
 +    const char *zDef;
 +    switch( ptu ){
 +    case PTU_Binding: zDef = zDefaultParamStore; break;
 +    case PTU_Script:  zDef = zDefaultVarStore; break;
 +    default: return 0;
 +    }
 +    return home_based_path(zDef);
 +  }else if ( zSpec[0]=='~' ){
 +    return home_based_path(zSpec);
 +  }
 +  return zSpec;
 +}
 +
 +/* Load some or all kv pairs. Arguments are "load FILE ?NAMES?". */
 +static int kv_pairs_load(sqlite3 *db, ParamTableUse ptu,
 +                         const char *azArg[], int nArg){
 +  const char *zStore = kv_store_path((nArg>1)? azArg[1] : 0, ptu);
 +  if( zStore==0 ){
 +    utf8_printf(STD_ERR, "Cannot form parameter load path. Nothing loaded.\n");
 +    return DCR_Error;
 +  }else{
 +    const char **pzFirst = (nArg>2)? azArg+2 : 0;
 +    int nNames = (nArg>2)? nArg-2 : 0;
 +    int rc = kv_xfr_table(db, zStore, 0, ptu, pzFirst, nNames);
 +    if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore);
 +    return rc;
 +  }
 +}
  
 -  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
 +/* Save some or all parameters. Arguments are "save FILE ?NAMES?". */
 +static int kv_pairs_save(sqlite3 *db, ParamTableUse ptu,
 +                         const char *azArg[], int nArg){
 +  const char *zStore = kv_store_path((nArg>1)? azArg[1] : 0, ptu);
 +  if( zStore==0 ){
 +    utf8_printf(STD_ERR, "Cannot form parameter save path. Nothing saved.\n");
 +    return DCR_Error;
 +  }else{
 +    const char **pzFirst = (nArg>2)? azArg+2 : 0;
 +    int nNames = (nArg>2)? nArg-2 : 0;
 +    int rc = kv_xfr_table(db, zStore, 1, ptu, pzFirst, nNames);
 +    if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore);
 +    return rc;
 +  }
 +}
  
 -  /* 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
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +/*
 + * Edit one named value in the parameters or shell variables table.
 + * If it does not yet exist, create it. If eval is true, the value
 + * is treated as a bare expression, otherwise it is a text value.
 + * The "uses" argument sets the 3rd column in the selected table,
 + * and serves to select which of the two tables is modified.
 + */
 +static int edit_one_kvalue(sqlite3 *db, char *name, int eval,
 +                           ParamTableUse uses, const char * zEditor){
 +  struct keyval_row kvRow = {0,0,0};
 +  int rc;
 +  char * zVal = 0;
 +  const char *zTab = 0;
 +  char * zSql = 0;
  
 -  if( c=='c' && strcmp(azArg[0],"cd")==0 ){
 -    failIfSafeMode(p, "cannot run .cd in safe mode");
 -    if( nArg==2 ){
 -#if defined(_WIN32) || defined(WIN32)
 -      wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
 -      rc = !SetCurrentDirectoryW(z);
 -      sqlite3_free(z);
 -#else
 -      rc = chdir(azArg[1]);
 -#endif
 -      if( rc ){
 -        utf8_printf(stderr, "Cannot change to directory \"%s\"\n", azArg[1]);
 -        rc = 1;
 -      }
 +  switch( uses ){
 +  case PTU_Script:  zTab = SHVAR_TABLE_SNAME; break;
 +  case PTU_Binding: zTab = PARAM_TABLE_SNAME; break;
 +  default: assert(0);
 +  }
 +  zSql = smprintf("SELECT value, uses FROM %s "
 +                  "WHERE key=%Q AND uses=%d", zTab, name, uses);
 +  shell_check_oom(zSql);
 +  sqlite3_exec(db, zSql, kv_find_callback, &kvRow, 0);
 +  sqlite3_free(zSql);
 +  assert(kvRow.hits<2);
 +  if( kvRow.hits==1 && kvRow.uses==uses){
 +    /* Editing an existing value of same kind. */
 +    sqlite3_free(kvRow.value);
 +    if( eval!=0 ){
 +      zSql = smprintf("SELECT edit(value, %Q) FROM %s "
 +                      "WHERE key=%Q AND uses=%d", zEditor, zTab, name, uses);
 +      shell_check_oom(zSql);
 +      zVal = db_text(db, zSql, 1);
 +      sqlite3_free(zSql);
 +      zSql = smprintf("UPDATE %s SET value=(SELECT %s) "
 +                      "WHERE key=%Q AND uses=%d", zTab, zVal, name, uses);
      }else{
 -      raw_printf(stderr, "Usage: .cd DIRECTORY\n");
 -      rc = 1;
 +      zSql = smprintf("UPDATE %s SET value=edit(value, %Q) WHERE"
 +                      " key=%Q AND uses=%d", zTab, zEditor, name, uses);
      }
 -  }else
 -
 -  if( c=='c' && n>=3 && strncmp(azArg[0], "changes", n)==0 ){
 -    if( nArg==2 ){
 -      setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
 +  }else{
 +    /* Editing a new value of same kind. */
 +    assert(kvRow.value==0 || kvRow.uses!=uses);
 +    if( eval!=0 ){
 +      zSql = smprintf("SELECT edit('-- %q%s', %Q)", name, "\n", zEditor);
 +      zVal = db_text(db, zSql, 1);
 +      sqlite3_free(zSql);
 +      zSql = smprintf("INSERT INTO %s(key,value,uses)"
 +         " VALUES (%Q,(SELECT %s LIMIT 1),%d)",
 +         zTab, name, zVal, uses);
      }else{
 -      raw_printf(stderr, "Usage: .changes on|off\n");
 -      rc = 1;
 +      zSql = smprintf("INSERT INTO %s(key,value,uses)"
 +                      " VALUES (%Q,edit('-- %q;%s', %Q),%d)",
 +                      zTab, name, name, "\n", zEditor, uses);
      }
 -  }else
 +  }
 +  shell_check_oom(zSql);
 +  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 +  sqlite3_free(zSql);
 +  sqlite3_free(zVal);
 +  return rc!=SQLITE_OK;
 +}
 +#endif
  
 -  /* Cancel output redirection, if it is currently set (by .testcase)
 -  ** Then read the content of the testcase-out.txt file and compare against
 -  ** azArg[1].  If there are differences, report an error and exit.
 -  */
 -  if( c=='c' && n>=3 && strncmp(azArg[0], "check", n)==0 ){
 -    char *zRes = 0;
 -    output_reset(p);
 -    if( nArg!=2 ){
 -      raw_printf(stderr, "Usage: .check GLOB-PATTERN\n");
 -      rc = 2;
 -    }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){
 -      raw_printf(stderr, "Error: cannot read 'testcase-out.txt'\n");
 -      rc = 2;
 -    }else if( testcase_glob(azArg[1],zRes)==0 ){
 -      utf8_printf(stderr,
 -                 "testcase-%s FAILED\n Expected: [%s]\n      Got: [%s]\n",
 -                 p->zTestcase, azArg[1], zRes);
 -      rc = 1;
 -    }else{
 -      utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase);
 -      p->nCheck++;
 -    }
 -    sqlite3_free(zRes);
 -  }else
 +/* Space-join values in an argument list. *valLim is not included. */
 +char *values_join( char **valBeg, char **valLim ){
 +  char *z = 0;
 +  const char *zSep = 0;
 +  while( valBeg < valLim ){
 +    z = smprintf("%z%s%s", z, zSep, *valBeg);
 +    zSep = " ";
 +    ++valBeg;
 +  }
 +  return z;
 +}
  
 -  if( c=='c' && strncmp(azArg[0], "clone", n)==0 ){
 -    failIfSafeMode(p, "cannot run .clone in safe mode");
 -    if( nArg==2 ){
 -      tryToClone(p, azArg[1]);
 -    }else{
 -      raw_printf(stderr, "Usage: .clone FILENAME\n");
 -      rc = 1;
 -    }
 -  }else
 +static struct ParamSetOpts {
 +  const char cCast;
 +  const char *zTypename;
 +  int evalKind;
 +} param_set_opts[] = {
 +  /* { 'q', 0, 2 }, */
 +  /* { 'x', 0, 1 }, */
 +  { 'i', "INT", 1 },
 +  { 'r', "REAL", 1 },
 +  { 'b', "BLOB", 1 },
 +  { 't', "TEXT", 0 },
 +  { 'n', "NUMERIC", 1 }
 +};
  
 -  if( c=='c' && strncmp(azArg[0], "connection", n)==0 ){
 -    if( nArg==1 ){
 -      /* List available connections */
 -      int i;
 -      for(i=0; i<ArraySize(p->aAuxDb); i++){
 -        const char *zFile = p->aAuxDb[i].zDbFilename;
 -        if( p->aAuxDb[i].db==0 && p->pAuxDb!=&p->aAuxDb[i] ){
 -          zFile = "(not open)";
 -        }else if( zFile==0 ){
 -          zFile = "(memory)";
 -        }else if( zFile[0]==0 ){
 -          zFile = "(temporary-file)";
 -        }
 -        if( p->pAuxDb == &p->aAuxDb[i] ){
 -          utf8_printf(stdout, "ACTIVE %d: %s\n", i, zFile);
 -        }else if( p->aAuxDb[i].db!=0 ){
 -          utf8_printf(stdout, "       %d: %s\n", i, zFile);
 -        }
 -      }
 -    }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){
 -      int i = azArg[1][0] - '0';
 -      if( p->pAuxDb != &p->aAuxDb[i] && i>=0 && i<ArraySize(p->aAuxDb) ){
 -        p->pAuxDb->db = p->db;
 -        p->pAuxDb = &p->aAuxDb[i];
 -        globalDb = p->db = p->pAuxDb->db;
 -        p->pAuxDb->db = 0;
 -      }
 -    }else if( nArg==3 && strcmp(azArg[1], "close")==0
 -           && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){
 -      int i = azArg[2][0] - '0';
 -      if( i<0 || i>=ArraySize(p->aAuxDb) ){
 -        /* No-op */
 -      }else if( p->pAuxDb == &p->aAuxDb[i] ){
 -        raw_printf(stderr, "cannot close the active database connection\n");
 -        rc = 1;
 -      }else if( p->aAuxDb[i].db ){
 -        session_close_all(p, i);
 -        close_db(p->aAuxDb[i].db);
 -        p->aAuxDb[i].db = 0;
 +/* Return an option character if it is single and prefixed by - or --,
 + * else return 0.
 + */
 +static char option_char(char *zArg){
 +  if( zArg[0]=='-' ){
 +    ++zArg;
 +    if( zArg[0]=='-' ) ++zArg;
 +    if( zArg[0]!=0 && zArg[1]==0 ) return zArg[0];
 +  }
 +  return 0;
 +}
 +
 +/* Most of .vars set
 + * Return SQLITE_OK on success, else SQLITE_ERROR.
 + */
 +static int shvar_set(sqlite3 *db, char *name, char **valBeg, char **valLim){
 +  int rc = SQLITE_OK;
 +  char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0;
 +  sqlite3_stmt *pStmtSet = 0;
 +  char *zValue = (zValGlom==0)? *valBeg : zValGlom;
 +  char *zSql
 +    = smprintf("REPLACE INTO "SHVAR_TABLE_SNAME"(key,value,uses)"
 +               "VALUES(%Q,%Q,"SPTU_Script");", name, zValue);
 +  shell_check_oom(zSql);
 +  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0);
 +  assert(rc==SQLITE_OK);
 +  sqlite3_free(zSql);
 +  rc = (SQLITE_DONE==sqlite3_step(pStmtSet))? SQLITE_OK : SQLITE_ERROR;
 +  sqlite3_finalize(pStmtSet);
 +  sqlite3_free(zValGlom);
 +  return rc;
 +}
 +
 +
 +/* Most of the .parameter set subcommand (per help text)
 + */
 +static int param_set(sqlite3 *db, char cCast,
 +                     char *name, char **valBeg, char **valLim){
 +  char *zSql = 0;
 +  int rc = SQLITE_OK, retries = 0, needsEval = 1;
 +  char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0;
 +  sqlite3_stmt *pStmtSet = 0;
 +  const char *zCastTo = 0;
 +  char *zValue = (zValGlom==0)? *valBeg : zValGlom;
 +  if( cCast ){
 +    struct ParamSetOpts *pSO = param_set_opts;
 +    for(; pSO-param_set_opts < ArraySize(param_set_opts); ++pSO ){
 +      if( cCast==pSO->cCast ){
 +        zCastTo = pSO->zTypename;
 +        needsEval = pSO->evalKind > 0;
 +        break;
        }
 +    }
 +  }
 +  if( needsEval ){
 +    if( zCastTo!=0 ){
 +      zSql = smprintf
 +        ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 +          " VALUES(%Q,CAST((%s) AS %s),"SPTU_Binding");",
 +          name, zValue, zCastTo );
      }else{
 -      raw_printf(stderr, "Usage: .connection [close] [CONNECTION-NUMBER]\n");
 -      rc = 1;
 +      zSql = smprintf
 +        ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 +          "VALUES(%Q,(%s),"SPTU_Binding");", name, zValue );
      }
 -  }else
 +    shell_check_oom(zSql);
 +    rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0);
 +    sqlite3_free(zSql);
 +  }
 +  if( !needsEval || rc!=SQLITE_OK ){
 +    /* Reach here when value either requested to be cast to text, or must be. */
 +    sqlite3_finalize(pStmtSet);
 +    pStmtSet = 0;
 +    zSql = smprintf
 +      ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 +        "VALUES(%Q,%Q,"SPTU_Binding");", name, zValue );
 +    shell_check_oom(zSql);
 +    rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0);
 +    assert(rc==SQLITE_OK);
 +    sqlite3_free(zSql);
 +  }
 +  sqlite3_step(pStmtSet);
 +  sqlite3_finalize(pStmtSet);
 +  sqlite3_free(zValGlom);
 +  return rc;
 +}
  
 -  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;
 +/* list or ls subcommand for .parameter and .vars dot-commands */
 +static void list_pov_entries(ShellExState *psx, ParamTableUse ptu, u8 bShort,
 +                             char **pzArgs, int nArg){
 +  sqlite3_stmt *pStmt = 0;
 +  sqlite3_str *sbList;
 +  int len = 0, rc;
 +  char *zFromWhere = 0;
 +  char *zSql = 0;
 +  sqlite3 *db;
 +  const char *zTab;
 +
 +  switch( ptu ){
 +  case PTU_Binding:
 +    db = DBX(psx);
 +    zTab = PARAM_TABLE_SNAME;
 +    break;
 +  case PTU_Script:
 +    db = psx->dbShell;
 +    zTab = SHVAR_TABLE_NAME;
 +    break;
 +  default: assert(0); return;
 +  }
 +  sbList = sqlite3_str_new(db);
 +  sqlite3_str_appendf(sbList, "FROM ");
 +  sqlite3_str_appendf(sbList, zTab);
 +  sqlite3_str_appendf(sbList, " WHERE (uses=?1) AND ");
 +  append_glob_terms(sbList, "key",
 +                    (const char **)pzArgs, (const char **)pzArgs+nArg);
 +  zFromWhere = sqlite3_str_finish(sbList);
 +  shell_check_oom(zFromWhere);
 +  zSql = smprintf("SELECT max(length(key)) %s", zFromWhere);
 +  shell_check_oom(zSql);
 +  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
 +  if( rc==SQLITE_OK ){
 +    sqlite3_bind_int(pStmt, 1, ptu);
 +    if( sqlite3_step(pStmt)==SQLITE_ROW ){
 +      len = sqlite3_column_int(pStmt, 0);
 +      if( len>40 ) len = 40;
 +      if( len<4 ) len = 4;
 +    }
 +  }
 +  sqlite3_finalize(pStmt);
 +  pStmt = 0;
 +  if( len ){
 +    FILE *out = ISS(psx)->out;
 +    sqlite3_free(zSql);
 +    if( !bShort ){
 +      int nBindings = 0, nScripts = 0;
 +      zSql = smprintf("SELECT key, uses,"
 +                      " iif(typeof(value)='text', quote(value), value) as v"
 +                      " %z ORDER BY uses, key", zFromWhere);
 +      shell_check_oom(zSql);
 +      rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
 +      sqlite3_bind_int(pStmt, 1, ptu);
 +      while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 +        ParamTableUse ptux = sqlite3_column_int(pStmt,1);
 +        switch( ptux ){
 +        case PTU_Binding:
 +          if( nBindings++ == 0 ){
 +            utf8_printf(out, "Bindings:\n%-*s %s\n",
 +                        len, "name", "value");
 +          }
 +          utf8_printf(out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
 +                      sqlite3_column_text(pStmt,2));
 +          break;
 +        case PTU_Script:
 +          if( nScripts++ == 0 ){
 +            utf8_printf(out, "Scripts:\n%-*s %s\n", len, "name", "value");
 +          }
 +          utf8_printf(out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
 +                      sqlite3_column_text(pStmt,2));
 +          break;
 +        default: break; /* Ignore */
 +        }
 +      }
      }else{
 -      while( sqlite3_step(pStmt)==SQLITE_ROW ){
 -        const char *zSchema = (const char *)sqlite3_column_text(pStmt,1);
 -        const char *zFile = (const char*)sqlite3_column_text(pStmt,2);
 -        if( zSchema==0 || zFile==0 ) continue;
 -        azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*));
 -        shell_check_oom(azName);
 -        azName[nName*2] = strdup(zSchema);
 -        azName[nName*2+1] = strdup(zFile);
 -        nName++;
 +      int nc = 0, ncw = 78/(len+2);
 +      zSql = smprintf("SELECT key %z ORDER BY key", zFromWhere);
 +      shell_check_oom(zSql);
 +      rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
 +      sqlite3_bind_int(pStmt, 1, ptu);
 +      while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 +        utf8_printf(out, "%s  %-*s", ((++nc%ncw==0)? "\n" : ""),
 +                    len, sqlite3_column_text(pStmt,0));
        }
 +      if( nc>0 ) utf8_printf(out, "\n");
      }
      sqlite3_finalize(pStmt);
 -    for(i=0; i<nName; i++){
 -      int eTxn = sqlite3_txn_state(p->db, azName[i*2]);
 -      int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]);
 -      const char *z = azName[i*2+1];
 -      utf8_printf(p->out, "%s: %s %s%s\n",
 -         azName[i*2],
 -         z && z[0] ? z : "\"\"",
 -         bRdonly ? "r/o" : "r/w",
 -         eTxn==SQLITE_TXN_NONE ? "" :
 -            eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
 -      free(azName[i*2]);
 -      free(azName[i*2+1]);
 -    }
 -    sqlite3_free(azName);
 -  }else
 +  }else{
 +    sqlite3_free(zFromWhere);
 +  }
 +  sqlite3_free(zSql);
 +}
  
 -  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;
 +/* Append an OR'ed series of GLOB terms comparing a given column
 + * name to a series of patterns. Result is an appended expression.
 + * For an empty pattern series, expression is true for non-NULL.
 + */
 +static void append_glob_terms(sqlite3_str *pStr, const char *zColName,
 +                              const char **azBeg, const char **azLim){
 +  if( azBeg==azLim ) sqlite3_str_appendf(pStr, "%s NOT NULL", zColName);
 +  else{
 +    char *zSep = "(";
 +    while( azBeg<azLim ){
 +      sqlite3_str_appendf(pStr, "%sglob(%Q,%s)", zSep, *azBeg, zColName);
 +      zSep = " OR ";
 +      ++azBeg;
 +    }
 +    sqlite3_str_appendf(pStr, ")");
 +  }
 +}
 +
 +/* Append either an IN clause or an always true test to some SQL.
 + *
 + * An empty IN list is the same as always true (for non-NULL LHS)
 + * for this clause, which assumes a trailing LHS operand and space.
 + * If that is not the right result, guard the call against it.
 + * This is used for ".parameter dostuff ?NAMES?" options,
 + * where a missing list means all the qualifying entries.
 + *
 + * The empty list may be signified by azBeg and azLim both 0.
 + */
 +static void append_in_clause(sqlite3_str *pStr,
 +                             const char **azBeg, const char **azLim){
 +  if( azBeg==azLim ) sqlite3_str_appendf(pStr, "NOT NULL");
 +  else{
 +    char cSep = '(';
 +    sqlite3_str_appendf(pStr, "IN");
 +    while( azBeg<azLim ){
 +      sqlite3_str_appendf(pStr, "%c%Q", cSep, *azBeg);
 +      cSep = ',';
 +      ++azBeg;
      }
 -    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
 +    sqlite3_str_appendf(pStr, ")");
 +  }
 +}
  
 -  if( c=='d' && n>=3 && strncmp(azArg[0], "dbinfo", n)==0 ){
 -    rc = shell_dbinfo_command(p, nArg, azArg);
 -  }else
 +/*****************
 + * The .parameter command
 + */
 +COLLECT_HELP_TEXT[
 +  ".parameter CMD ...       Manage per-DB SQL parameter bindings table",
 +  "   clear ?NAMES?           Erase all or only given named parameters",
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +  "   edit ?OPT? NAME ...     Use edit() to create or alter parameter NAME",
 +  "      OPT may be -t to use edited value as text or -e to evaluate it.",
 +#endif
 +  "   init                    Initialize TEMP table for bindings and scripts",
 +  "   list ?PATTERNS?         List current DB parameters table binding values",
 +  "      Alternatively, to list just some or all names: ls ?PATTERNS?",
 +  "   load ?FILE? ?NAMES?     Load some or all named parameters from FILE",
 +  "      If FILE missing, empty or '~', it defaults to ~/sqlite_params.sdb",
 +  "   save ?FILE? ?NAMES?     Save some or all named parameters into FILE",
 +  "      If FILE missing, empty or '~', it defaults to ~/sqlite_params.sdb",
 +  "   set ?TOPT? NAME VALUE   Give SQL parameter NAME a value of VALUE",
 +  "      NAME must begin with one of $,:,@,? for bindings, VALUE is the space-",
 +  "      joined argument list. TOPT may be one of {-b -i -n -r -t} to cast the",
 +  "      effective value to BLOB, INT, NUMERIC, REAL or TEXT respectively.",
 +  "   unset ?NAMES?           Remove named parameter(s) from parameters table",
 +];
 +DISPATCHABLE_COMMAND( parameter 2 2 0 ){
 +  int rc = 0;
 +  open_db(p,0);
 +  sqlite3 *db = DBX(p);
  
 -#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);
 +  /* .parameter clear  and  .parameter unset ?NAMES?
 +  **  Delete some or all parameters from the TEMP table that holds them.
 +  **  Without any arguments, clear deletes them all and unset does nothing.
 +  */
 +  if( strcmp(azArg[1],"clear")==0 || strcmp(azArg[1],"unset")==0 ){
 +    if( param_table_exists(db) && (nArg>2 || azArg[1][0]=='c') ){
 +      sqlite3_str *sbZap = sqlite3_str_new(db);
 +      char *zSql;
 +      sqlite3_str_appendf
 +        (sbZap, "DELETE FROM "PARAM_TABLE_SNAME" WHERE key ");
 +      append_in_clause(sbZap,
 +                       (const char **)&azArg[2], (const char **)&azArg[nArg]);
 +      zSql = sqlite3_str_finish(sbZap);
 +      shell_check_oom(zSql);
 +      sqlite3_exec(db, zSql, 0, 0, 0);
 +      sqlite3_free(zSql);
 +    }
    }else
 -#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;
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +  /* .parameter edit ?NAMES?
 +  ** Edit the named parameters. Any that do not exist are created.
 +  */
 +  if( strcmp(azArg[1],"edit")==0 ){
 +    ShellInState *psi = ISS(p);
 +    int ia = 2;
 +    int eval = 0;
 +    int edSet;
 +
 +    if( !INSOURCE_IS_INTERACTIVE(psi->pInSource) ){
 +      utf8_printf(STD_ERR, "Error: "
 +                  ".parameter edit can only be used interactively.\n");
 +      return DCR_Error;
 +    }
 +    param_table_init(db);
 +    edSet = attempt_editor_set(psi, azArg[0], (nArg>2)? azArg[2] : 0 );
 +    if( edSet < 0 ) return DCR_Error;
 +    else ia += edSet;
 +    /* Future: Allow an option whereby new value can be evaluated
 +     * the way that .parameter set ... does.
 +     */
 +    while( ia < nArg ){
 +      ParamTableUse ptu;
 +      char *zA = azArg[ia];
 +      char cf = (zA[0]=='-')? zA[1] : 0;
 +      if( cf!=0 && zA[2]==0 ){
 +        ++ia;
 +        switch( cf ){
 +        case 'e': eval = 1; continue;
 +        case 't': eval = 0; continue;
 +        default:
 +          utf8_printf(STD_ERR, "Error: bad .parameter name: %s\n", zA);
 +          return DCR_Error;
          }
        }
 +      ptu = classify_param_name(zA);
 +      if( ptu!=PTU_Binding ){
 +        utf8_printf(STD_ERR, "Error: "
 +                    "%s cannot be a binding parameter name.\n", zA);
 +        return DCR_Error;
 +      }
 +      rc = edit_one_kvalue(db, zA, eval, ptu, psi->zEditor);
 +      ++ia;
 +      if( rc!=0 ) return DCR_Error;
      }
 -
 -    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
 +#endif
  
 -  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;
 -    }
 +  /* .parameter init
 +  ** Make sure the TEMP table used to hold bind parameters exists.
 +  ** Create it if necessary.
 +  */
 +  if( nArg==2 && strcmp(azArg[1],"init")==0 ){
 +    param_table_init(db);
    }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;
 -    }
 +  /* .parameter list|ls
 +  ** List all or selected bind parameters.
 +  ** list displays names, values and uses.
 +  ** ls displays just the names.
 +  */
 +  if( nArg>=2 && ((strcmp(azArg[1],"list")==0)
 +                  || (strcmp(azArg[1],"ls")==0)) ){
 +    list_pov_entries(p, PTU_Binding, azArg[1][1]=='s', azArg+2, nArg-2);
    }else
  
 -  if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){
 -    if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc);
 -    rc = 2;
 +  /* .parameter load
 +  ** Load all or named parameters from specified or default (DB) file.
 +  */
 +  if( strcmp(azArg[1],"load")==0 ){
 +    param_table_init(db);
 +    rc = kv_pairs_load(db, PTU_Binding, (const char **)azArg+1, nArg-1);
    }else
  
 -  /* The ".explain" command is automatic now.  It is largely pointless.  It
 -  ** retained purely for backwards compatibility */
 -  if( c=='e' && strncmp(azArg[0], "explain", n)==0 ){
 -    int val = 1;
 -    if( nArg>=2 ){
 -      if( strcmp(azArg[1],"auto")==0 ){
 -        val = 99;
 -      }else{
 -        val =  booleanValue(azArg[1]);
 -      }
 -    }
 -    if( val==1 && p->mode!=MODE_Explain ){
 -      p->normalMode = p->mode;
 -      p->mode = MODE_Explain;
 -      p->autoExplain = 0;
 -    }else if( val==0 ){
 -      if( p->mode==MODE_Explain ) p->mode = p->normalMode;
 -      p->autoExplain = 0;
 -    }else if( val==99 ){
 -      if( p->mode==MODE_Explain ) p->mode = p->normalMode;
 -      p->autoExplain = 1;
 -    }
 +  /* .parameter save
 +  ** Save all or named parameters into specified or default (DB) file.
 +  */
 +  if( strcmp(azArg[1],"save")==0 ){
 +    rc = kv_pairs_save(db, PTU_Binding, (const char **)azArg+1, nArg-1);
    }else
  
 -#ifndef SQLITE_OMIT_VIRTUALTABLE
 -  if( c=='e' && strncmp(azArg[0], "expert", n)==0 ){
 -    if( p->bSafeMode ){
 -      raw_printf(stderr, 
 -        "Cannot run experimental commands such as \"%s\" in safe mode\n",
 -        azArg[0]);
 +  /* .parameter set NAME VALUE
 +  ** Set or reset a bind parameter.  NAME should be the full parameter
 +  ** name exactly as it appears in the query.  (ex: $abc, @def).  The
 +  ** VALUE can be in either SQL literal notation, or if not it will be
 +  ** understood to be a text string.
 +  */
 +  if( nArg>=4 && strcmp(azArg[1],"set")==0 ){
 +    char cCast = option_char(azArg[2]);
 +    int inv = 2 + (cCast != 0);
 +    ParamTableUse ptu = classify_param_name(azArg[inv]);
 +    if( ptu!=PTU_Binding ){
 +      utf8_printf(STD_ERR,
 +                  "Error: %s is not a usable parameter name.\n", azArg[inv]);
        rc = 1;
      }else{
 -      open_db(p, 0);
 -      expertDotCommand(p, azArg, nArg);
 +      param_table_init(db);
 +      rc = param_set(db, cCast, azArg[inv], &azArg[inv+1], &azArg[nArg]);
 +      if( rc!=SQLITE_OK ){
 +        utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(db));
 +        rc = 1;
 +      }
      }
    }else
 -#endif
 -
 -  if( c=='f' && strncmp(azArg[0], "filectrl", n)==0 ){
 -    static const struct {
 -       const char *zCtrlName;   /* Name of a test-control option */
 -       int ctrlCode;            /* Integer code for that option */
 -       const char *zUsage;      /* Usage notes */
 -    } aCtrl[] = {
 -      { "chunk_size",     SQLITE_FCNTL_CHUNK_SIZE,      "SIZE"           },
 -      { "data_version",   SQLITE_FCNTL_DATA_VERSION,    ""               },
 -      { "has_moved",      SQLITE_FCNTL_HAS_MOVED,       ""               },  
 -      { "lock_timeout",   SQLITE_FCNTL_LOCK_TIMEOUT,    "MILLISEC"       },
 -      { "persist_wal",    SQLITE_FCNTL_PERSIST_WAL,     "[BOOLEAN]"      },
 -   /* { "pragma",         SQLITE_FCNTL_PRAGMA,          "NAME ARG"       },*/
 -      { "psow",       SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]"      },
 -      { "reserve_bytes",  SQLITE_FCNTL_RESERVE_BYTES,   "[N]"            },
 -      { "size_limit",     SQLITE_FCNTL_SIZE_LIMIT,      "[LIMIT]"        },
 -      { "tempfilename",   SQLITE_FCNTL_TEMPFILENAME,    ""               },
 -   /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY,  "COUNT DELAY"    },*/
 -    };
 -    int filectrl = -1;
 -    int iCtrl = -1;
 -    sqlite3_int64 iRes = 0;  /* Integer result to display if rc2==1 */
 -    int isOk = 0;            /* 0: usage  1: %lld  2: no-result */
 -    int n2, i;
 -    const char *zCmd = 0;
 -    const char *zSchema = 0;
 -
 -    open_db(p, 0);
 -    zCmd = nArg>=2 ? azArg[1] : "help";
  
 -    if( zCmd[0]=='-' 
 -     && (strcmp(zCmd,"--schema")==0 || strcmp(zCmd,"-schema")==0)
 -     && nArg>=4
 -    ){
 -      zSchema = azArg[2];
 -      for(i=3; i<nArg; i++) azArg[i-2] = azArg[i];
 -      nArg -= 2;
 -      zCmd = azArg[1];
 -    }
 +  {  /* If no command name and arg count matches, show a syntax error */
 +    showHelp(ISS(p)->out, "parameter", p);
 +    return DCR_CmdErred;
 +  }
  
 -    /* The argument can optionally begin with "-" or "--" */
 -    if( zCmd[0]=='-' && zCmd[1] ){
 -      zCmd++;
 -      if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 -    }
 +  return DCR_Ok | (rc!=0);
 +}
  
 -    /* --help lists all file-controls */
 -    if( strcmp(zCmd,"help")==0 ){
 -      utf8_printf(p->out, "Available file-controls:\n");
 -      for(i=0; i<ArraySize(aCtrl); i++){
 -        utf8_printf(p->out, "  .filectrl %s %s\n",
 -                    aCtrl[i].zCtrlName, aCtrl[i].zUsage);
 +/*****************
 + * The .print, .progress and .prompt commands
 + */
 +CONDITION_COMMAND( progress !defined(SQLITE_OMIT_PROGRESS_CALLBACK) );
 +COLLECT_HELP_TEXT[
 +  ".print STRING...         Print literal STRING",
 +  ".progress N              Invoke progress handler after every N opcodes",
 +  "   --limit N                 Interrupt after N progress callbacks",
 +  "   --once                    Do no more than one progress interrupt",
 +  "   --quiet|-q                No output except at interrupts",
 +  "   --reset                   Reset the count for each input and interrupt",
 +  ".prompt MAIN CONTINUE    Replace the standard prompts",
 +];
 +DISPATCHABLE_COMMAND( print 3 1 0 ){
 +  int i;
 +  for(i=1; i<nArg; i++){
 +    utf8_printf(ISS(p)->out, "%s%s", azArg[i], (i==nArg-1)? "\n" : " ");
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( progress 3 2 0 ){
 +  ShellInState *psi = ISS(p);
 +  int i;
 +  int nn = 0;
 +  psi->flgProgress = 0;
 +  psi->mxProgress = 0;
 +  psi->nProgress = 0;
 +  for(i=1; i<nArg; i++){
 +    const char *z = azArg[i];
 +    if( z[0]=='-' ){
 +      z++;
 +      if( z[0]=='-' ) z++;
 +      if( strcmp(z,"quiet")==0 || strcmp(z,"q")==0 ){
 +        psi->flgProgress |= SHELL_PROGRESS_QUIET;
 +        continue;
        }
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -
 -    /* convert filectrl text option to value. allow any unique prefix
 -    ** of the option name, or a numerical value. */
 -    n2 = strlen30(zCmd);
 -    for(i=0; i<ArraySize(aCtrl); i++){
 -      if( strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
 -        if( filectrl<0 ){
 -          filectrl = aCtrl[i].ctrlCode;
 -          iCtrl = i;
 +      if( strcmp(z,"reset")==0 ){
 +        psi->flgProgress |= SHELL_PROGRESS_RESET;
 +        continue;
 +      }
 +      if( strcmp(z,"once")==0 ){
 +        psi->flgProgress |= SHELL_PROGRESS_ONCE;
 +        continue;
 +      }
 +      if( strcmp(z,"limit")==0 ){
 +        if( i+1>=nArg ){
 +          *pzErr = smprintf("missing argument on --limit\n");
 +          return DCR_Unpaired|i;
          }else{
 -          utf8_printf(stderr, "Error: ambiguous file-control: \"%s\"\n"
 -                              "Use \".filectrl --help\" for help\n", zCmd);
 -          rc = 1;
 -          goto meta_command_exit;
 +          psi->mxProgress = (int)integerValue(azArg[++i]);
          }
 +        continue;
        }
 -    }
 -    if( filectrl<0 ){
 -      utf8_printf(stderr,"Error: unknown file-control: %s\n"
 -                         "Use \".filectrl --help\" for help\n", zCmd);
 +      return DCR_Unknown|i;
      }else{
 -      switch(filectrl){
 -        case SQLITE_FCNTL_SIZE_LIMIT: {
 -          if( nArg!=2 && nArg!=3 ) break;
 -          iRes = nArg==3 ? integerValue(azArg[2]) : -1;
 -          sqlite3_file_control(p->db, zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes);
 -          isOk = 1;
 -          break;
 -        }
 -        case SQLITE_FCNTL_LOCK_TIMEOUT:
 -        case SQLITE_FCNTL_CHUNK_SIZE: {
 -          int x;
 -          if( nArg!=3 ) break;
 -          x = (int)integerValue(azArg[2]);
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          isOk = 2;
 -          break;
 -        }
 -        case SQLITE_FCNTL_PERSIST_WAL:
 -        case SQLITE_FCNTL_POWERSAFE_OVERWRITE: {
 -          int x;
 -          if( nArg!=2 && nArg!=3 ) break;
 -          x = nArg==3 ? booleanValue(azArg[2]) : -1;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          iRes = x;
 -          isOk = 1;
 -          break;
 -        }
 -        case SQLITE_FCNTL_DATA_VERSION:
 -        case SQLITE_FCNTL_HAS_MOVED: {
 -          int x;
 -          if( nArg!=2 ) break;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          iRes = x;
 -          isOk = 1;
 -          break;
 -        }
 -        case SQLITE_FCNTL_TEMPFILENAME: {
 -          char *z = 0;
 -          if( nArg!=2 ) break;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &z);
 -          if( z ){
 -            utf8_printf(p->out, "%s\n", z);
 -            sqlite3_free(z);
 -          }
 -          isOk = 2;
 -          break;
 -        }
 -        case SQLITE_FCNTL_RESERVE_BYTES: {
 -          int x;
 -          if( nArg>=3 ){
 -            x = atoi(azArg[2]);
 -            sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          }
 -          x = -1;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          utf8_printf(p->out,"%d\n", x);
 -          isOk = 2;
 -          break;
 -        }
 -      }
 +      nn = (int)integerValue(z);
      }
 -    if( isOk==0 && iCtrl>=0 ){
 -      utf8_printf(p->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
 -      rc = 1;
 -    }else if( isOk==1 ){
 -      char zBuf[100];
 -      sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes);
 -      raw_printf(p->out, "%s\n", zBuf);
 +  }
 +  open_db(p, 0);
 +  sqlite3_progress_handler(DBX(p), nn, progress_handler, psi);
 +  return DCR_Ok;
 +}
 +/* Allow too few arguments by tradition, (a form of no-op.) */
 +DISPATCHABLE_COMMAND( prompt ? 1 3 ){
 +  if( nArg >= 2) {
 +    strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1);
 +  }
 +  if( nArg >= 3) {
 +    strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1);
 +  }
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * The .recover and .restore commands
 + */
 +CONDITION_COMMAND( recover !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) );
 +COLLECT_HELP_TEXT[
 +  ".recover                 Recover as much data as possible from corrupt db.",
 +  "   --freelist-corrupt       Assume the freelist is corrupt",
 +  "   --recovery-db NAME       Store recovery metadata in database file NAME",
 +  "   --lost-and-found TABLE   Alternative name for the lost-and-found table",
 +  "   --no-rowids              Do not attempt to recover rowid values",
 +  "                            that are not also INTEGER PRIMARY KEYs",
 +  ".restore ?DB? FILE       Restore content of DB (default \"main\") from FILE",
 +];
 +
 +/*
 +** This command is invoked to recover data from the database. A script
 +** to construct a new database containing all recovered data is output
 +** on stream pState->out.
 +*/
 +DISPATCHABLE_COMMAND( recover ? 1 7 ){
 +  FILE *out = ISS(p)->out;
 +  sqlite3 *db;
 +  int rc = SQLITE_OK;
 +  sqlite3_stmt *pLoop = 0;        /* Loop through all root pages */
 +  sqlite3_stmt *pPages = 0;       /* Loop through all pages in a group */
 +  sqlite3_stmt *pCells = 0;       /* Loop through all cells in a page */
 +  const char *zRecoveryDb = "";   /* Name of "recovery" database */
 +  const char *zLostAndFound = "lost_and_found";
 +  int i;
 +  int nOrphan = -1;
 +  RecoverTable *pOrphan = 0;
 +
 +  open_db(p, 0);
 +  db = DBX(p);
 +
 +  int bFreelist = 1;              /* 0 if --freelist-corrupt is specified */
 +  int bRowids = 1;                /* 0 if --no-rowids */
 +  for(i=1; i<nArg; i++){
 +    char *z = azArg[i];
 +    int n;
 +    if( z[0]=='-' && z[1]=='-' ) z++;
 +    n = strlen30(z);
 +    if( n<=17 && memcmp("-freelist-corrupt", z, n)==0 ){
 +      bFreelist = 0;
 +    }else
 +    if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
 +      i++;
 +      zRecoveryDb = azArg[i];
 +    }else
 +    if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
 +      i++;
 +      zLostAndFound = azArg[i];
 +    }else
 +    if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
 +      bRowids = 0;
      }
 -  }else
 +    else{
 +      *pzErr = smprintf("unexpected option: %s\n", azArg[i]);
 +      showHelp(out, azArg[0], p);
 +      return DCR_CmdErred;
 +    }
 +  }
 +
 +  shellExecPrintf(db, &rc,
 +    /* Attach an in-memory database named 'recovery'. Create an indexed
 +    ** cache of the sqlite_dbptr virtual table. */
 +    "PRAGMA writable_schema = on;"
 +    "ATTACH %Q AS recovery;"
 +    "DROP TABLE IF EXISTS recovery.dbptr;"
 +    "DROP TABLE IF EXISTS recovery.freelist;"
 +    "DROP TABLE IF EXISTS recovery.map;"
 +    "DROP TABLE IF EXISTS recovery.schema;"
 +    "CREATE TABLE recovery.freelist(pgno INTEGER PRIMARY KEY);", zRecoveryDb
 +  );
 +
 +  if( bFreelist ){
 +    shellExec(db, &rc,
 +      "WITH trunk(pgno) AS ("
 +      "  SELECT shell_int32("
 +      "      (SELECT data FROM sqlite_dbpage WHERE pgno=1), 8) AS x "
 +      "      WHERE x>0"
 +      "    UNION"
 +      "  SELECT shell_int32("
 +      "      (SELECT data FROM sqlite_dbpage WHERE pgno=trunk.pgno), 0) AS x "
 +      "      FROM trunk WHERE x>0"
 +      "),"
 +      "freelist(data, n, freepgno) AS ("
 +      "  SELECT data, min(16384, shell_int32(data, 1)-1), t.pgno "
 +      "      FROM trunk t, sqlite_dbpage s WHERE s.pgno=t.pgno"
 +      "    UNION ALL"
 +      "  SELECT data, n-1, shell_int32(data, 2+n) "
 +      "      FROM freelist WHERE n>=0"
 +      ")"
 +      "REPLACE INTO recovery.freelist SELECT freepgno FROM freelist;"
 +    );
 +  }
 +
 +  /* If this is an auto-vacuum database, add all pointer-map pages to
 +  ** the freelist table. Do this regardless of whether or not
 +  ** --freelist-corrupt was specified.  */
 +  shellExec(db, &rc,
 +    "WITH ptrmap(pgno) AS ("
 +    "  SELECT 2 WHERE shell_int32("
 +    "    (SELECT data FROM sqlite_dbpage WHERE pgno=1), 13"
 +    "  )"
 +    "    UNION ALL "
 +    "  SELECT pgno+1+(SELECT page_size FROM pragma_page_size)/5 AS pp "
 +    "  FROM ptrmap WHERE pp<=(SELECT page_count FROM pragma_page_count)"
 +    ")"
 +    "REPLACE INTO recovery.freelist SELECT pgno FROM ptrmap"
 +  );
 +
 +  shellExec(db, &rc,
 +    "CREATE TABLE recovery.dbptr("
 +    "      pgno, child, PRIMARY KEY(child, pgno)"
 +    ") WITHOUT ROWID;"
 +    "INSERT OR IGNORE INTO recovery.dbptr(pgno, child) "
 +    "    SELECT * FROM sqlite_dbptr"
 +    "      WHERE pgno NOT IN freelist AND child NOT IN freelist;"
  
 -  if( c=='f' && strncmp(azArg[0], "fullschema", n)==0 ){
 -    ShellState data;
 -    int doStats = 0;
 -    memcpy(&data, p, sizeof(data));
 -    data.showHeader = 0;
 -    data.cMode = data.mode = MODE_Semi;
 -    if( nArg==2 && optionMatch(azArg[1], "indent") ){
 -      data.cMode = data.mode = MODE_Pretty;
 -      nArg = 1;
 -    }
 -    if( nArg!=1 ){
 -      raw_printf(stderr, "Usage: .fullschema ?--indent?\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    open_db(p, 0);
 -    rc = sqlite3_exec(p->db,
 -       "SELECT sql FROM"
 -       "  (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x"
 -       "     FROM sqlite_schema UNION ALL"
 -       "   SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) "
 -       "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' "
 -       "ORDER BY x",
 -       callback, &data, 0
 -    );
 -    if( rc==SQLITE_OK ){
 -      sqlite3_stmt *pStmt;
 -      rc = sqlite3_prepare_v2(p->db,
 -               "SELECT rowid FROM sqlite_schema"
 -               " WHERE name GLOB 'sqlite_stat[134]'",
 -               -1, &pStmt, 0);
 -      doStats = sqlite3_step(pStmt)==SQLITE_ROW;
 -      sqlite3_finalize(pStmt);
 -    }
 -    if( doStats==0 ){
 -      raw_printf(p->out, "/* No STAT tables available */\n");
 -    }else{
 -      raw_printf(p->out, "ANALYZE sqlite_schema;\n");
 -      data.cMode = data.mode = MODE_Insert;
 -      data.zDestTable = "sqlite_stat1";
 -      shell_exec(&data, "SELECT * FROM sqlite_stat1", 0);
 -      data.zDestTable = "sqlite_stat4";
 -      shell_exec(&data, "SELECT * FROM sqlite_stat4", 0);
 -      raw_printf(p->out, "ANALYZE sqlite_schema;\n");
 -    }
 -  }else
 +    /* Delete any pointer to page 1. This ensures that page 1 is considered
 +    ** a root page, regardless of how corrupt the db is. */
 +    "DELETE FROM recovery.dbptr WHERE child = 1;"
  
 -  if( c=='h' && strncmp(azArg[0], "headers", n)==0 ){
 -    if( nArg==2 ){
 -      p->showHeader = booleanValue(azArg[1]);
 -      p->shellFlgs |= SHFLG_HeaderSet;
 -    }else{
 -      raw_printf(stderr, "Usage: .headers on|off\n");
 -      rc = 1;
 -    }
 -  }else
 +    /* Delete all pointers to any pages that have more than one pointer
 +    ** to them. Such pages will be treated as root pages when recovering
 +    ** data.  */
 +    "DELETE FROM recovery.dbptr WHERE child IN ("
 +    "  SELECT child FROM recovery.dbptr GROUP BY child HAVING count(*)>1"
 +    ");"
  
 -  if( c=='h' && strncmp(azArg[0], "help", n)==0 ){
 -    if( nArg>=2 ){
 -      n = showHelp(p->out, azArg[1]);
 -      if( n==0 ){
 -        utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]);
 -      }
 -    }else{
 -      showHelp(p->out, 0);
 -    }
 -  }else
 +    /* Create the "map" table that will (eventually) contain instructions
 +    ** for dealing with each page in the db that contains one or more
 +    ** records. */
 +    "CREATE TABLE recovery.map("
 +      "pgno INTEGER PRIMARY KEY, maxlen INT, intkey, root INT"
 +    ");"
  
 -  if( c=='i' && strncmp(azArg[0], "import", n)==0 ){
 -    char *zTable = 0;           /* Insert data into this table */
 -    char *zSchema = 0;          /* within this schema (may default to "main") */
 -    char *zFile = 0;            /* Name of file to extra content from */
 -    sqlite3_stmt *pStmt = NULL; /* A statement */
 -    int nCol;                   /* Number of columns in the table */
 -    int nByte;                  /* Number of bytes in an SQL string */
 -    int i, j;                   /* Loop counters */
 -    int needCommit;             /* True to COMMIT or ROLLBACK at end */
 -    int nSep;                   /* Number of bytes in p->colSeparator[] */
 -    char *zSql;                 /* An SQL statement */
 -    char *zFullTabName;         /* Table name with schema if applicable */
 -    ImportCtx sCtx;             /* Reader context */
 -    char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
 -    int eVerbose = 0;           /* Larger for more console output */
 -    int nSkip = 0;              /* Initial lines to skip */
 -    int useOutputMode = 1;      /* Use output mode to determine separators */
 -    char *zCreate = 0;          /* CREATE TABLE statement text */
 -
 -    failIfSafeMode(p, "cannot run .import in safe mode");
 -    memset(&sCtx, 0, sizeof(sCtx));
 -    sCtx.z = sqlite3_malloc64(120);
 -    if( sCtx.z==0 ){
 -      import_cleanup(&sCtx);
 -      shell_out_of_memory();
 -    }
 -    if( p->mode==MODE_Ascii ){
 -      xRead = ascii_read_one_field;
 -    }else{
 -      xRead = csv_read_one_field;
 -    }
 -    for(i=1; i<nArg; i++){
 -      char *z = azArg[i];
 -      if( z[0]=='-' && z[1]=='-' ) z++;
 -      if( z[0]!='-' ){
 -        if( zFile==0 ){
 -          zFile = z;
 -        }else if( zTable==0 ){
 -          zTable = z;
 -        }else{
 -          utf8_printf(p->out, "ERROR: extra argument: \"%s\".  Usage:\n", z);
 -          showHelp(p->out, "import");
 -          rc = 1;
 -          goto meta_command_exit;
 -        }
 -      }else if( strcmp(z,"-v")==0 ){
 -        eVerbose++;
 -      }else if( strcmp(z,"-schema")==0 && i<nArg-1 ){
 -        zSchema = azArg[++i];
 -      }else if( strcmp(z,"-skip")==0 && i<nArg-1 ){
 -        nSkip = integerValue(azArg[++i]);
 -      }else if( strcmp(z,"-ascii")==0 ){
 -        sCtx.cColSep = SEP_Unit[0];
 -        sCtx.cRowSep = SEP_Record[0];
 -        xRead = ascii_read_one_field;
 -        useOutputMode = 0;
 -      }else if( strcmp(z,"-csv")==0 ){
 -        sCtx.cColSep = ',';
 -        sCtx.cRowSep = '\n';
 -        xRead = csv_read_one_field;
 -        useOutputMode = 0;
 -      }else{
 -        utf8_printf(p->out, "ERROR: unknown option: \"%s\".  Usage:\n", z);
 -        showHelp(p->out, "import");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -    }
 -    if( zTable==0 ){
 -      utf8_printf(p->out, "ERROR: missing %s argument. Usage:\n",
 -                  zFile==0 ? "FILE" : "TABLE");
 -      showHelp(p->out, "import");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    seenInterrupt = 0;
 -    open_db(p, 0);
 -    if( useOutputMode ){
 -      /* If neither the --csv or --ascii options are specified, then set
 -      ** the column and row separator characters from the output mode. */
 -      nSep = strlen30(p->colSeparator);
 -      if( nSep==0 ){
 -        raw_printf(stderr,
 -                   "Error: non-null column separator required for import\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      if( nSep>1 ){
 -        raw_printf(stderr, 
 -              "Error: multi-character column separators not allowed"
 -              " for import\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      nSep = strlen30(p->rowSeparator);
 -      if( nSep==0 ){
 -        raw_printf(stderr,
 -            "Error: non-null row separator required for import\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      if( nSep==2 && p->mode==MODE_Csv && strcmp(p->rowSeparator,SEP_CrLf)==0 ){
 -        /* When importing CSV (only), if the row separator is set to the
 -        ** default output row separator, change it to the default input
 -        ** row separator.  This avoids having to maintain different input
 -        ** and output row separators. */
 -        sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 -        nSep = strlen30(p->rowSeparator);
 -      }
 -      if( nSep>1 ){
 -        raw_printf(stderr, "Error: multi-character row separators not allowed"
 -                           " for import\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      sCtx.cColSep = p->colSeparator[0];
 -      sCtx.cRowSep = p->rowSeparator[0];
 -    }
 -    sCtx.zFile = zFile;
 -    sCtx.nLine = 1;
 -    if( sCtx.zFile[0]=='|' ){
 -#ifdef SQLITE_OMIT_POPEN
 -      raw_printf(stderr, "Error: pipes are not supported in this OS\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -#else
 -      sCtx.in = popen(sCtx.zFile+1, "r");
 -      sCtx.zFile = "<pipe>";
 -      sCtx.xCloser = pclose;
 -#endif
 -    }else{
 -      sCtx.in = fopen(sCtx.zFile, "rb");
 -      sCtx.xCloser = fclose;
 -    }
 -    if( sCtx.in==0 ){
 -      utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile);
 -      rc = 1;
 -      import_cleanup(&sCtx);
 -      goto meta_command_exit;
 -    }
 -    if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
 -      char zSep[2];
 -      zSep[1] = 0;
 -      zSep[0] = sCtx.cColSep;
 -      utf8_printf(p->out, "Column separator ");
 -      output_c_string(p->out, zSep);
 -      utf8_printf(p->out, ", row separator ");
 -      zSep[0] = sCtx.cRowSep;
 -      output_c_string(p->out, zSep);
 -      utf8_printf(p->out, "\n");
 -    }
 -    /* Below, resources must be freed before exit. */
 -    while( (nSkip--)>0 ){
 -      while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
 -    }
 -    if( zSchema!=0 ){
 -      zFullTabName = sqlite3_mprintf("\"%w\".\"%w\"", zSchema, zTable);
 -    }else{
 -      zFullTabName = sqlite3_mprintf("\"%w\"", zTable);
 -    }
 -    zSql = sqlite3_mprintf("SELECT * FROM %s", zFullTabName);
 -    if( zSql==0 || zFullTabName==0 ){
 -      import_cleanup(&sCtx);
 -      shell_out_of_memory();
 -    }
 -    nByte = strlen30(zSql);
 -    rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    import_append_char(&sCtx, 0);    /* To ensure sCtx.z is allocated */
 -    if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){
 -      sqlite3 *dbCols = 0;
 -      char *zRenames = 0;
 -      char *zColDefs;
 -      zCreate = sqlite3_mprintf("CREATE TABLE %s", zFullTabName);
 -      while( xRead(&sCtx) ){
 -        zAutoColumn(sCtx.z, &dbCols, 0);
 -        if( sCtx.cTerm!=sCtx.cColSep ) break;
 -      }
 -      zColDefs = zAutoColumn(0, &dbCols, &zRenames);
 -      if( zRenames!=0 ){
 -        utf8_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr,
 -                    "Columns renamed during .import %s due to duplicates:\n"
 -                    "%s\n", sCtx.zFile, zRenames);
 -        sqlite3_free(zRenames);
 -      }
 -      assert(dbCols==0);
 -      if( zColDefs==0 ){
 -        utf8_printf(stderr,"%s: empty file\n", sCtx.zFile);
 -      import_fail:
 -        sqlite3_free(zCreate);
 -        sqlite3_free(zSql);
 -        sqlite3_free(zFullTabName);
 -        import_cleanup(&sCtx);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs);
 -      if( eVerbose>=1 ){
 -        utf8_printf(p->out, "%s\n", zCreate);
 -      }
 -      rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
 -      if( rc ){
 -        utf8_printf(stderr, "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
 -        goto import_fail;
 -      }
 -      sqlite3_free(zCreate);
 -      zCreate = 0;
 -      rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    }
 -    if( rc ){
 -      if (pStmt) sqlite3_finalize(pStmt);
 -      utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db));
 -      goto import_fail;
 -    }
 -    sqlite3_free(zSql);
 -    nCol = sqlite3_column_count(pStmt);
 -    sqlite3_finalize(pStmt);
 -    pStmt = 0;
 -    if( nCol==0 ) return 0; /* no columns, no error */
 -    zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 );
 -    if( zSql==0 ){
 -      import_cleanup(&sCtx);
 -      shell_out_of_memory();
 -    }
 -    sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName);
 -    j = strlen30(zSql);
 -    for(i=1; i<nCol; i++){
 -      zSql[j++] = ',';
 -      zSql[j++] = '?';
 -    }
 -    zSql[j++] = ')';
 -    zSql[j] = 0;
 -    if( eVerbose>=2 ){
 -      utf8_printf(p->out, "Insert using: %s\n", zSql);
 -    }
 -    rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    if( rc ){
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 -      if (pStmt) sqlite3_finalize(pStmt);
 -      goto import_fail;
 -    }
 -    sqlite3_free(zSql);
 -    sqlite3_free(zFullTabName);
 -    needCommit = sqlite3_get_autocommit(p->db);
 -    if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
 -    do{
 -      int startLine = sCtx.nLine;
 -      for(i=0; i<nCol; i++){
 -        char *z = xRead(&sCtx);
 -        /*
 -        ** Did we reach end-of-file before finding any columns?
 -        ** If so, stop instead of NULL filling the remaining columns.
 -        */
 -        if( z==0 && i==0 ) break;
 -        /*
 -        ** Did we reach end-of-file OR end-of-line before finding any
 -        ** columns in ASCII mode?  If so, stop instead of NULL filling
 -        ** the remaining columns.
 -        */
 -        if( p->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
 -        sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
 -        if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
 -          utf8_printf(stderr, "%s:%d: expected %d columns but found %d - "
 -                          "filling the rest with NULL\n",
 -                          sCtx.zFile, startLine, nCol, i+1);
 -          i += 2;
 -          while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; }
 -        }
 -      }
 -      if( sCtx.cTerm==sCtx.cColSep ){
 -        do{
 -          xRead(&sCtx);
 -          i++;
 -        }while( sCtx.cTerm==sCtx.cColSep );
 -        utf8_printf(stderr, "%s:%d: expected %d columns but found %d - "
 -                        "extras ignored\n",
 -                        sCtx.zFile, startLine, nCol, i);
 -      }
 -      if( i>=nCol ){
 -        sqlite3_step(pStmt);
 -        rc = sqlite3_reset(pStmt);
 -        if( rc!=SQLITE_OK ){
 -          utf8_printf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
 -                      startLine, sqlite3_errmsg(p->db));
 -          sCtx.nErr++;
 -        }else{
 -          sCtx.nRow++;
 -        }
 -      }
 -    }while( sCtx.cTerm!=EOF );
 +    /* Populate table [map]. If there are circular loops of pages in the
 +    ** database, the following adds all pages in such a loop to the map
 +    ** as individual root pages. This could be handled better.  */
 +    "WITH pages(i, maxlen) AS ("
 +    "  SELECT page_count, ("
 +    "    SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=page_count"
 +    "  ) FROM pragma_page_count WHERE page_count>0"
 +    "    UNION ALL"
 +    "  SELECT i-1, ("
 +    "    SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=i-1"
 +    "  ) FROM pages WHERE i>=2"
 +    ")"
 +    "INSERT INTO recovery.map(pgno, maxlen, intkey, root) "
 +    "  SELECT i, maxlen, NULL, ("
 +    "    WITH p(orig, pgno, parent) AS ("
 +    "      SELECT 0, i, (SELECT pgno FROM recovery.dbptr WHERE child=i)"
 +    "        UNION "
 +    "      SELECT i, p.parent, "
 +    "        (SELECT pgno FROM recovery.dbptr WHERE child=p.parent) FROM p"
 +    "    )"
 +    "    SELECT pgno FROM p WHERE (parent IS NULL OR pgno = orig)"
 +    ") "
 +    "FROM pages WHERE maxlen IS NOT NULL AND i NOT IN freelist;"
 +    "UPDATE recovery.map AS o SET intkey = ("
 +    "  SELECT substr(data, 1, 1)==X'0D' FROM sqlite_dbpage WHERE pgno=o.pgno"
 +    ");"
  
 -    import_cleanup(&sCtx);
 -    sqlite3_finalize(pStmt);
 -    if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0);
 -    if( eVerbose>0 ){
 -      utf8_printf(p->out,
 -          "Added %d rows with %d errors using %d lines of input\n",
 -          sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
 -    }
 -  }else
 +    /* Extract data from page 1 and any linked pages into table
 +    ** recovery.schema. With the same schema as an sqlite_schema table.  */
 +    "CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);"
 +    "INSERT INTO recovery.schema SELECT "
 +    "  max(CASE WHEN field=0 THEN value ELSE NULL END),"
 +    "  max(CASE WHEN field=1 THEN value ELSE NULL END),"
 +    "  max(CASE WHEN field=2 THEN value ELSE NULL END),"
 +    "  max(CASE WHEN field=3 THEN value ELSE NULL END),"
 +    "  max(CASE WHEN field=4 THEN value ELSE NULL END)"
 +    "FROM sqlite_dbdata WHERE pgno IN ("
 +    "  SELECT pgno FROM recovery.map WHERE root=1"
 +    ")"
 +    "GROUP BY pgno, cell;"
 +    "CREATE INDEX recovery.schema_rootpage ON schema(rootpage);"
 +  );
  
 -#ifndef SQLITE_UNTESTABLE
 -  if( c=='i' && strncmp(azArg[0], "imposter", n)==0 ){
 -    char *zSql;
 -    char *zCollist = 0;
 -    sqlite3_stmt *pStmt;
 -    int tnum = 0;
 -    int isWO = 0;  /* True if making an imposter of a WITHOUT ROWID table */
 -    int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */
 -    int i;
 -    if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){
 -      utf8_printf(stderr, "Usage: .imposter INDEX IMPOSTER\n"
 -                          "       .imposter off\n");
 -      /* Also allowed, but not documented:
 -      **
 -      **    .imposter TABLE IMPOSTER
 -      **
 -      ** where TABLE is a WITHOUT ROWID table.  In that case, the
 -      ** imposter is another WITHOUT ROWID table with the columns in
 -      ** storage order. */
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    open_db(p, 0);
 -    if( nArg==2 ){
 -      sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 1);
 -      goto meta_command_exit;
 -    }
 -    zSql = sqlite3_mprintf(
 -      "SELECT rootpage, 0 FROM sqlite_schema"
 -      " WHERE name='%q' AND type='index'"
 -      "UNION ALL "
 -      "SELECT rootpage, 1 FROM sqlite_schema"
 -      " WHERE name='%q' AND type='table'"
 -      "   AND sql LIKE '%%without%%rowid%%'",
 -      azArg[1], azArg[1]
 +  /* Open a transaction, then print out all non-virtual, non-"sqlite_%"
 +  ** CREATE TABLE statements that extracted from the existing schema.  */
 +  if( rc==SQLITE_OK ){
 +    sqlite3_stmt *pStmt = 0;
 +    /* ".recover" might output content in an order which causes immediate
 +    ** foreign key constraints to be violated. So disable foreign-key
 +    ** constraint enforcement to prevent problems when running the output
 +    ** script. */
 +    raw_printf(out, "PRAGMA foreign_keys=OFF;\n");
 +    raw_printf(out, "BEGIN;\n");
 +    raw_printf(out, "PRAGMA writable_schema = on;\n");
 +    shellPrepare(db, &rc,
 +        "SELECT sql FROM recovery.schema "
 +        "WHERE type='table' AND sql LIKE 'create table%'", &pStmt
      );
 -    sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    sqlite3_free(zSql);
 -    if( sqlite3_step(pStmt)==SQLITE_ROW ){
 -      tnum = sqlite3_column_int(pStmt, 0);
 -      isWO = sqlite3_column_int(pStmt, 1);
 -    }
 -    sqlite3_finalize(pStmt);
 -    zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]);
 -    rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    sqlite3_free(zSql);
 -    i = 0;
 -    while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 -      char zLabel[20];
 -      const char *zCol = (const char*)sqlite3_column_text(pStmt,2);
 -      i++;
 -      if( zCol==0 ){
 -        if( sqlite3_column_int(pStmt,1)==-1 ){
 -          zCol = "_ROWID_";
 -        }else{
 -          sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i);
 -          zCol = zLabel;
 -        }
 -      }
 -      if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){
 -        lenPK = (int)strlen(zCollist);
 -      }
 -      if( zCollist==0 ){
 -        zCollist = sqlite3_mprintf("\"%w\"", zCol);
 -      }else{
 -        zCollist = sqlite3_mprintf("%z,\"%w\"", zCollist, zCol);
 -      }
 -    }
 -    sqlite3_finalize(pStmt);
 -    if( i==0 || tnum==0 ){
 -      utf8_printf(stderr, "no such index: \"%s\"\n", azArg[1]);
 -      rc = 1;
 -      sqlite3_free(zCollist);
 -      goto meta_command_exit;
 -    }
 -    if( lenPK==0 ) lenPK = 100000;
 -    zSql = sqlite3_mprintf(
 -          "CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))WITHOUT ROWID",
 -          azArg[2], zCollist, lenPK, zCollist);
 -    sqlite3_free(zCollist);
 -    rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 1, tnum);
 -    if( rc==SQLITE_OK ){
 -      rc = sqlite3_exec(p->db, zSql, 0, 0, 0);
 -      sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0);
 -      if( rc ){
 -        utf8_printf(stderr, "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db));
 -      }else{
 -        utf8_printf(stdout, "%s;\n", zSql);
 -        raw_printf(stdout,
 -          "WARNING: writing to an imposter table will corrupt the \"%s\" %s!\n",
 -          azArg[1], isWO ? "table" : "index"
 -        );
 -      }
 -    }else{
 -      raw_printf(stderr, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
 -      rc = 1;
 +    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
 +      const char *zCreateTable = (const char*)sqlite3_column_text(pStmt, 0);
 +      raw_printf(out, "CREATE TABLE IF NOT EXISTS %s;\n", &zCreateTable[12]);
      }
 -    sqlite3_free(zSql);
 -  }else
 -#endif /* !defined(SQLITE_OMIT_TEST_CONTROL) */
 +    shellFinalize(&rc, pStmt);
 +  }
  
 -#ifdef SQLITE_ENABLE_IOTRACE
 -  if( c=='i' && strncmp(azArg[0], "iotrace", n)==0 ){
 -    SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...);
 -    if( iotrace && iotrace!=stdout ) fclose(iotrace);
 -    iotrace = 0;
 -    if( nArg<2 ){
 -      sqlite3IoTrace = 0;
 -    }else if( strcmp(azArg[1], "-")==0 ){
 -      sqlite3IoTrace = iotracePrintf;
 -      iotrace = stdout;
 -    }else{
 -      iotrace = fopen(azArg[1], "w");
 -      if( iotrace==0 ){
 -        utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]);
 -        sqlite3IoTrace = 0;
 -        rc = 1;
 -      }else{
 -        sqlite3IoTrace = iotracePrintf;
 -      }
 -    }
 -  }else
 -#endif
 +  /* Figure out if an orphan table will be required. And if so, how many
 +  ** user columns it should contain */
 +  shellPrepare(db, &rc,
 +      "SELECT coalesce(max(maxlen), -2) FROM recovery.map WHERE root>1"
 +      , &pLoop
 +  );
 +  if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
 +    nOrphan = sqlite3_column_int(pLoop, 0);
 +  }
 +  shellFinalize(&rc, pLoop);
 +  pLoop = 0;
  
 -  if( c=='l' && n>=5 && strncmp(azArg[0], "limits", n)==0 ){
 -    static const struct {
 -       const char *zLimitName;   /* Name of a limit */
 -       int limitCode;            /* Integer code for that limit */
 -    } aLimit[] = {
 -      { "length",                SQLITE_LIMIT_LENGTH                    },
 -      { "sql_length",            SQLITE_LIMIT_SQL_LENGTH                },
 -      { "column",                SQLITE_LIMIT_COLUMN                    },
 -      { "expr_depth",            SQLITE_LIMIT_EXPR_DEPTH                },
 -      { "compound_select",       SQLITE_LIMIT_COMPOUND_SELECT           },
 -      { "vdbe_op",               SQLITE_LIMIT_VDBE_OP                   },
 -      { "function_arg",          SQLITE_LIMIT_FUNCTION_ARG              },
 -      { "attached",              SQLITE_LIMIT_ATTACHED                  },
 -      { "like_pattern_length",   SQLITE_LIMIT_LIKE_PATTERN_LENGTH       },
 -      { "variable_number",       SQLITE_LIMIT_VARIABLE_NUMBER           },
 -      { "trigger_depth",         SQLITE_LIMIT_TRIGGER_DEPTH             },
 -      { "worker_threads",        SQLITE_LIMIT_WORKER_THREADS            },
 -    };
 -    int i, n2;
 -    open_db(p, 0);
 -    if( nArg==1 ){
 -      for(i=0; i<ArraySize(aLimit); i++){
 -        printf("%20s %d\n", aLimit[i].zLimitName,
 -               sqlite3_limit(p->db, aLimit[i].limitCode, -1));
 -      }
 -    }else if( nArg>3 ){
 -      raw_printf(stderr, "Usage: .limit NAME ?NEW-VALUE?\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }else{
 -      int iLimit = -1;
 -      n2 = strlen30(azArg[1]);
 -      for(i=0; i<ArraySize(aLimit); i++){
 -        if( sqlite3_strnicmp(aLimit[i].zLimitName, azArg[1], n2)==0 ){
 -          if( iLimit<0 ){
 -            iLimit = i;
 -          }else{
 -            utf8_printf(stderr, "ambiguous limit: \"%s\"\n", azArg[1]);
 -            rc = 1;
 -            goto meta_command_exit;
 -          }
 -        }
 -      }
 -      if( iLimit<0 ){
 -        utf8_printf(stderr, "unknown limit: \"%s\"\n"
 -                        "enter \".limits\" with no arguments for a list.\n",
 -                         azArg[1]);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      if( nArg==3 ){
 -        sqlite3_limit(p->db, aLimit[iLimit].limitCode,
 -                      (int)integerValue(azArg[2]));
 +  shellPrepare(db, &rc,
 +      "SELECT pgno FROM recovery.map WHERE root=?", &pPages
 +  );
 +
 +  shellPrepare(db, &rc,
 +      "SELECT max(field), group_concat(shell_escape_crnl(quote"
 +      "(case when (? AND field<0) then NULL else value end)"
 +      "), ', ')"
 +      ", min(field) "
 +      "FROM sqlite_dbdata WHERE pgno = ? AND field != ?"
 +      "GROUP BY cell", &pCells
 +  );
 +
 +  /* Loop through each root page. */
 +  shellPrepare(db, &rc,
 +      "SELECT root, intkey, max(maxlen) FROM recovery.map"
 +      " WHERE root>1 GROUP BY root, intkey ORDER BY root=("
 +      "  SELECT rootpage FROM recovery.schema WHERE name='sqlite_sequence'"
 +      ")", &pLoop
 +  );
 +  while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
 +    int iRoot = sqlite3_column_int(pLoop, 0);
 +    int bIntkey = sqlite3_column_int(pLoop, 1);
 +    int nCol = sqlite3_column_int(pLoop, 2);
 +    int bNoop = 0;
 +    RecoverTable *pTab;
 +
 +    assert( bIntkey==0 || bIntkey==1 );
 +    pTab = recoverFindTable(db, &rc, iRoot, bIntkey, nCol, &bNoop);
 +    if( bNoop || rc ) continue;
 +    if( pTab==0 ){
 +      if( pOrphan==0 ){
 +        pOrphan = recoverOrphanTable(db, out, &rc, zLostAndFound, nOrphan);
        }
 -      printf("%20s %d\n", aLimit[iLimit].zLimitName,
 -             sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1));
 +      pTab = pOrphan;
 +      if( pTab==0 ) break;
      }
 -  }else
 -
 -  if( c=='l' && n>2 && strncmp(azArg[0], "lint", n)==0 ){
 -    open_db(p, 0);
 -    lintDotCommand(p, azArg, nArg);
 -  }else
  
 -#ifndef SQLITE_OMIT_LOAD_EXTENSION
 -  if( c=='l' && strncmp(azArg[0], "load", n)==0 ){
 -    const char *zFile, *zProc;
 -    char *zErrMsg = 0;
 -    failIfSafeMode(p, "cannot run .load in safe mode");
 -    if( nArg<2 ){
 -      raw_printf(stderr, "Usage: .load FILE ?ENTRYPOINT?\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    zFile = azArg[1];
 -    zProc = nArg>=3 ? azArg[2] : 0;
 -    open_db(p, 0);
 -    rc = sqlite3_load_extension(p->db, zFile, zProc, &zErrMsg);
 -    if( rc!=SQLITE_OK ){
 -      utf8_printf(stderr, "Error: %s\n", zErrMsg);
 -      sqlite3_free(zErrMsg);
 -      rc = 1;
 +    if( 0==sqlite3_stricmp(pTab->zQuoted, "\"sqlite_sequence\"") ){
 +      raw_printf(out, "DELETE FROM sqlite_sequence;\n");
      }
 -  }else
 -#endif
 -
 -  if( c=='l' && strncmp(azArg[0], "log", n)==0 ){
 -    failIfSafeMode(p, "cannot run .log in safe mode");
 -    if( nArg!=2 ){
 -      raw_printf(stderr, "Usage: .log FILENAME\n");
 -      rc = 1;
 +    sqlite3_bind_int(pPages, 1, iRoot);
 +    if( bRowids==0 && pTab->iPk<0 ){
 +      sqlite3_bind_int(pCells, 1, 1);
      }else{
 -      const char *zFile = azArg[1];
 -      output_file_close(p->pLog);
 -      p->pLog = output_file_open(zFile, 0);
 +      sqlite3_bind_int(pCells, 1, 0);
      }
 -  }else
 +    sqlite3_bind_int(pCells, 3, pTab->iPk);
  
 -  if( c=='m' && strncmp(azArg[0], "mode", n)==0 ){
 -    const char *zMode = 0;
 -    const char *zTabname = 0;
 -    int i, n2;
 -    ColModeOpts cmOpts = ColModeOpts_default;
 -    for(i=1; i<nArg; i++){
 -      const char *z = azArg[i];
 -      if( optionMatch(z,"wrap") && i+1<nArg ){
 -        cmOpts.iWrap = integerValue(azArg[++i]);
 -      }else if( optionMatch(z,"ww") ){
 -        cmOpts.bWordWrap = 1;
 -      }else if( optionMatch(z,"wordwrap") && i+1<nArg ){
 -        cmOpts.bWordWrap = (u8)booleanValue(azArg[++i]);
 -      }else if( optionMatch(z,"quote") ){
 -        cmOpts.bQuote = 1;
 -      }else if( optionMatch(z,"noquote") ){
 -        cmOpts.bQuote = 0;
 -      }else if( zMode==0 ){
 -        zMode = z;
 -        /* Apply defaults for qbox pseudo-mods. If that
 -         * overwrites already-set values, user was informed of this.
 -         */
 -        if( strcmp(z, "qbox")==0 ){
 -          ColModeOpts cmo = ColModeOpts_default_qbox;
 -          zMode = "box";
 -          cmOpts = cmo;
 +    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPages) ){
 +      int iPgno = sqlite3_column_int(pPages, 0);
 +      sqlite3_bind_int(pCells, 2, iPgno);
 +      while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pCells) ){
 +        int nField = sqlite3_column_int(pCells, 0);
 +        int iMin = sqlite3_column_int(pCells, 2);
 +        const char *zVal = (const char*)sqlite3_column_text(pCells, 1);
 +
 +        RecoverTable *pTab2 = pTab;
 +        if( pTab!=pOrphan && (iMin<0)!=bIntkey ){
 +          if( pOrphan==0 ){
 +            pOrphan = recoverOrphanTable(db, out, &rc, zLostAndFound, nOrphan);
 +          }
 +          pTab2 = pOrphan;
 +          if( pTab2==0 ) break;
 +        }
 +
 +        nField = nField+1;
 +        if( pTab2==pOrphan ){
 +          raw_printf(out,
 +              "INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n",
 +              pTab2->zQuoted, iRoot, iPgno, nField,
 +              iMin<0 ? "" : "NULL, ", zVal, pTab2->azlCol[nField]
 +          );
 +        }else{
 +          raw_printf(out, "INSERT INTO %s(%s) VALUES( %s );\n",
 +              pTab2->zQuoted, pTab2->azlCol[nField], zVal
 +          );
          }
 -      }else if( zTabname==0 ){
 -        zTabname = z;
 -      }else if( z[0]=='-' ){
 -        utf8_printf(stderr, "unknown option: %s\n", z);
 -        utf8_printf(stderr, "options:\n"
 -                            "  --noquote\n"
 -                            "  --quote\n"
 -                            "  --wordwrap on/off\n"
 -                            "  --wrap N\n"
 -                            "  --ww\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }else{
 -        utf8_printf(stderr, "extra argument: \"%s\"\n", z);
 -        rc = 1;
 -        goto meta_command_exit;
        }
 +      shellReset(&rc, pCells);
      }
 -    if( zMode==0 ){
 -      if( p->mode==MODE_Column
 -       || (p->mode>=MODE_Markdown && p->mode<=MODE_Box)
 -      ){
 -        raw_printf
 -          (p->out,
 -           "current output mode: %s --wrap %d --wordwrap %s --%squote\n",
 -           modeDescr[p->mode], p->cmOpts.iWrap,
 -           p->cmOpts.bWordWrap ? "on" : "off",
 -           p->cmOpts.bQuote ? "" : "no");
 +    shellReset(&rc, pPages);
 +    if( pTab!=pOrphan ) recoverFreeTable(pTab);
 +  }
 +  shellFinalize(&rc, pLoop);
 +  shellFinalize(&rc, pPages);
 +  shellFinalize(&rc, pCells);
 +  recoverFreeTable(pOrphan);
 +
 +  /* The rest of the schema */
 +  if( rc==SQLITE_OK ){
 +    sqlite3_stmt *pStmt = 0;
 +    shellPrepare(db, &rc,
 +        "SELECT sql, name FROM recovery.schema "
 +        "WHERE sql NOT LIKE 'create table%'", &pStmt
 +    );
 +    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
 +      const char *zSql = (const char*)sqlite3_column_text(pStmt, 0);
 +      if( sqlite3_strnicmp(zSql, "create virt", 11)==0 ){
 +        const char *zName = (const char*)sqlite3_column_text(pStmt, 1);
 +        char *zPrint = shellMPrintf(&rc,
 +          "INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)",
 +          zName, zName, zSql
 +        );
 +        raw_printf(out, "%s;\n", zPrint);
 +        sqlite3_free(zPrint);
        }else{
 -        raw_printf(p->out, "current output mode: %s\n", modeDescr[p->mode]);
 -      }
 -      zMode = modeDescr[p->mode];
 -    }
 -    n2 = strlen30(zMode);
 -    if( strncmp(zMode,"lines",n2)==0 ){
 -      p->mode = MODE_Line;
 -      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 -    }else if( strncmp(zMode,"columns",n2)==0 ){
 -      p->mode = MODE_Column;
 -      if( (p->shellFlgs & SHFLG_HeaderSet)==0 ){
 -        p->showHeader = 1;
 -      }
 -      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 -      p->cmOpts = cmOpts;
 -    }else if( strncmp(zMode,"list",n2)==0 ){
 -      p->mode = MODE_List;
 -      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Column);
 -      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 -    }else if( strncmp(zMode,"html",n2)==0 ){
 -      p->mode = MODE_Html;
 -    }else if( strncmp(zMode,"tcl",n2)==0 ){
 -      p->mode = MODE_Tcl;
 -      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Space);
 -      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 -    }else if( strncmp(zMode,"csv",n2)==0 ){
 -      p->mode = MODE_Csv;
 -      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
 -      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf);
 -    }else if( strncmp(zMode,"tabs",n2)==0 ){
 -      p->mode = MODE_List;
 -      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Tab);
 -    }else if( strncmp(zMode,"insert",n2)==0 ){
 -      p->mode = MODE_Insert;
 -      set_table_name(p, zTabname ? zTabname : "table");
 -    }else if( strncmp(zMode,"quote",n2)==0 ){
 -      p->mode = MODE_Quote;
 -      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
 -      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 -    }else if( strncmp(zMode,"ascii",n2)==0 ){
 -      p->mode = MODE_Ascii;
 -      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Unit);
 -      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Record);
 -    }else if( strncmp(zMode,"markdown",n2)==0 ){
 -      p->mode = MODE_Markdown;
 -      p->cmOpts = cmOpts;
 -    }else if( strncmp(zMode,"table",n2)==0 ){
 -      p->mode = MODE_Table;
 -      p->cmOpts = cmOpts;
 -    }else if( strncmp(zMode,"box",n2)==0 ){
 -      p->mode = MODE_Box;
 -      p->cmOpts = cmOpts;
 -    }else if( strncmp(zMode,"count",n2)==0 ){
 -      p->mode = MODE_Count;
 -    }else if( strncmp(zMode,"off",n2)==0 ){
 -      p->mode = MODE_Off;
 -    }else if( strncmp(zMode,"json",n2)==0 ){
 -      p->mode = MODE_Json;
 -    }else{
 -      raw_printf(stderr, "Error: mode should be one of: "
 -         "ascii box column csv html insert json line list markdown "
 -         "qbox quote table tabs tcl\n");
 -      rc = 1;
 +        raw_printf(out, "%s;\n", zSql);
 +      }
      }
 -    p->cMode = p->mode;
 -  }else
 +    shellFinalize(&rc, pStmt);
 +  }
  
 -  if( c=='n' && strcmp(azArg[0], "nonce")==0 ){
 -    if( nArg!=2 ){
 -      raw_printf(stderr, "Usage: .nonce NONCE\n");
 -      rc = 1;
 -    }else if( p->zNonce==0 || strcmp(azArg[1],p->zNonce)!=0 ){
 -      raw_printf(stderr, "line %d: incorrect nonce: \"%s\"\n",
 -                 p->lineno, azArg[1]);
 -      exit(1);
 -    }else{
 -      p->bSafeMode = 0;
 -      return 0;  /* Return immediately to bypass the safe mode reset
 -                 ** at the end of this procedure */
 -    }
 -  }else
 +  if( rc==SQLITE_OK ){
 +    raw_printf(out, "PRAGMA writable_schema = off;\n");
 +    raw_printf(out, "COMMIT;\n");
 +  }
 +  sqlite3_exec(db, "DETACH recovery", 0, 0, 0);
 +  return rc;
 +}
  
 -  if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 ){
 -    if( nArg==2 ){
 -      sqlite3_snprintf(sizeof(p->nullValue), p->nullValue,
 -                       "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]);
 -    }else{
 -      raw_printf(stderr, "Usage: .nullvalue STRING\n");
 -      rc = 1;
 +DISPATCHABLE_COMMAND( restore ? 2 3 ){
 +  int rc;
 +  const char *zSrcFile;
 +  const char *zDb;
 +  sqlite3 *pSrc;
 +  sqlite3_backup *pBackup;
 +  int nTimeout = 0;
 +
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +  if( nArg==2 ){
 +    zSrcFile = azArg[1];
 +    zDb = "main";
 +  }else if( nArg==3 ){
 +    zSrcFile = azArg[2];
 +    zDb = azArg[1];
 +  }else{
 +    return DCR_TooMany;
 +  }
 +  rc = sqlite3_open(zSrcFile, &pSrc);
 +  if( rc!=SQLITE_OK ){
 +    *pzErr = smprintf("cannot open \"%s\"\n", zSrcFile);
 +    close_db(pSrc);
 +    return DCR_Error;
 +  }
 +  open_db(p, 0);
 +  pBackup = sqlite3_backup_init(DBX(p), zDb, pSrc, "main");
 +  if( pBackup==0 ){
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
 +    close_db(pSrc);
 +    return DCR_Error;
 +  }
 +  while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK
 +         || rc==SQLITE_BUSY  ){
 +    if( rc==SQLITE_BUSY ){
 +      if( nTimeout++ >= 3 ) break;
 +      sqlite3_sleep(100);
      }
 -  }else
 +  }
 +  sqlite3_backup_finish(pBackup);
 +  if( rc==SQLITE_DONE ){
 +    rc = 0;
 +  }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){
 +    *pzErr = smprintf("source database is busy\n");
 +    rc = 1;
 +  }else{
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
 +    rc = 1;
 +  }
 +  close_db(pSrc);
 +  return DCR_Ok|rc;
 +}
  
 -  if( c=='o' && strncmp(azArg[0], "open", n)==0 && n>=2 ){
 -    const char *zFN = 0;     /* Pointer to constant filename */
 -    char *zNewFilename = 0;  /* Name of the database file to open */
 -    int iName = 1;           /* Index in azArg[] of the filename */
 -    int newFlag = 0;         /* True to delete file before opening */
 -    int openMode = SHELL_OPEN_UNSPEC;
 -
 -    /* Check for command-line arguments */
 -    for(iName=1; iName<nArg; iName++){
 -      const char *z = azArg[iName];
 -      if( optionMatch(z,"new") ){
 -        newFlag = 1;
 -#ifdef SQLITE_HAVE_ZLIB
 -      }else if( optionMatch(z, "zip") ){
 -        openMode = SHELL_OPEN_ZIPFILE;
 -#endif
 -      }else if( optionMatch(z, "append") ){
 -        openMode = SHELL_OPEN_APPENDVFS;
 -      }else if( optionMatch(z, "readonly") ){
 -        openMode = SHELL_OPEN_READONLY;
 -      }else if( optionMatch(z, "nofollow") ){
 -        p->openFlags |= SQLITE_OPEN_NOFOLLOW;
 -#ifndef SQLITE_OMIT_DESERIALIZE
 -      }else if( optionMatch(z, "deserialize") ){
 -        openMode = SHELL_OPEN_DESERIALIZE;
 -      }else if( optionMatch(z, "hexdb") ){
 -        openMode = SHELL_OPEN_HEXDB;
 -      }else if( optionMatch(z, "maxsize") && iName+1<nArg ){
 -        p->szMax = integerValue(azArg[++iName]);
 -#endif /* SQLITE_OMIT_DESERIALIZE */
 -      }else if( z[0]=='-' ){
 -        utf8_printf(stderr, "unknown option: %s\n", z);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }else if( zFN ){
 -        utf8_printf(stderr, "extra argument: \"%s\"\n", z);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }else{
 -        zFN = z;
 -      }
 -    }
 -
 -    /* Close the existing database */
 -    session_close_all(p, -1);
 -    close_db(p->db);
 -    p->db = 0;
 -    p->pAuxDb->zDbFilename = 0;
 -    sqlite3_free(p->pAuxDb->zFreeOnClose);
 -    p->pAuxDb->zFreeOnClose = 0;
 -    p->openMode = openMode;
 -    p->openFlags = 0;
 -    p->szMax = 0;
 -
 -    /* If a filename is specified, try to open it first */
 -    if( zFN || p->openMode==SHELL_OPEN_HEXDB ){
 -      if( newFlag && zFN && !p->bSafeMode ) shellDeleteFile(zFN);
 -      if( p->bSafeMode
 -       && p->openMode!=SHELL_OPEN_HEXDB
 -       && zFN
 -       && strcmp(zFN,":memory:")!=0
 -      ){
 -        failIfSafeMode(p, "cannot open disk-based database files in safe mode");
 -      }
 -      if( zFN ){
 -        zNewFilename = sqlite3_mprintf("%s", zFN);
 -        shell_check_oom(zNewFilename);
 -      }else{
 -        zNewFilename = 0;
 -      }
 -      p->pAuxDb->zDbFilename = zNewFilename;
 -      open_db(p, OPEN_DB_KEEPALIVE);
 -      if( p->db==0 ){
 -        utf8_printf(stderr, "Error: cannot open '%s'\n", zNewFilename);
 -        sqlite3_free(zNewFilename);
 -      }else{
 -        p->pAuxDb->zFreeOnClose = zNewFilename;
 -      }
 -    }
 -    if( p->db==0 ){
 -      /* As a fall-back open a TEMP database */
 -      p->pAuxDb->zDbFilename = 0;
 -      open_db(p, 0);
 -    }
 -  }else
 +/*****************
 + * The .scanstats and .schema commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".scanstats on|off        Turn sqlite3_stmt_scanstatus() metrics on or off",
 +  ".schema ?PATTERN?        Show the CREATE statements matching PATTERN",
 +  "   Options:",
 +  "      --indent             Try to pretty-print the schema",
 +  "      --nosys              Omit objects whose names start with \"sqlite_\"",
 +];
 +DISPATCHABLE_COMMAND( scanstats ? 2 2 ){
 +  ISS(p)->scanstatsOn = (u8)booleanValue(azArg[1]);
 +#ifndef SQLITE_ENABLE_STMT_SCANSTATUS
 +  raw_printf(STD_ERR, "Warning: .scanstats not available in this build.\n");
 +#endif
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( schema ? 1 2 ){
-   int rc;
++  int rc = 0;
 +  ShellText sSelect;
 +  ShellInState data;
 +  ShellExState datax;
 +  char *zErrMsg = 0;
 +  const char *zDiv = "(";
 +  const char *zName = 0;
 +  int iSchema = 0;
 +  int bDebug = 0;
 +  int bNoSystemTabs = 0;
 +  int ii;
  
 -  if( (c=='o'
 -        && (strncmp(azArg[0], "output", n)==0||strncmp(azArg[0], "once", n)==0))
 -   || (c=='e' && n==5 && strcmp(azArg[0],"excel")==0)
 -  ){
 -    char *zFile = 0;
 -    int bTxtMode = 0;
 -    int i;
 -    int eMode = 0;
 -    int bOnce = 0;            /* 0: .output, 1: .once, 2: .excel */
 -    unsigned char zBOM[4];    /* Byte-order mark to using if --bom is present */
 -
 -    zBOM[0] = 0;
 -    failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
 -    if( c=='e' ){
 -      eMode = 'x';
 -      bOnce = 2;
 -    }else if( strncmp(azArg[0],"once",n)==0 ){
 -      bOnce = 1;
 -    }
 -    for(i=1; i<nArg; i++){
 -      char *z = azArg[i];
 -      if( z[0]=='-' ){
 -        if( z[1]=='-' ) z++;
 -        if( strcmp(z,"-bom")==0 ){
 -          zBOM[0] = 0xef;
 -          zBOM[1] = 0xbb;
 -          zBOM[2] = 0xbf;
 -          zBOM[3] = 0;
 -        }else if( c!='e' && strcmp(z,"-x")==0 ){
 -          eMode = 'x';  /* spreadsheet */
 -        }else if( c!='e' && strcmp(z,"-e")==0 ){
 -          eMode = 'e';  /* text editor */
 -        }else{
 -          utf8_printf(p->out, "ERROR: unknown option: \"%s\".  Usage:\n",
 -                      azArg[i]);
 -          showHelp(p->out, azArg[0]);
 -          rc = 1;
 -          goto meta_command_exit;
 -        }
 -      }else if( zFile==0 && eMode!='e' && eMode!='x' ){
 -        zFile = sqlite3_mprintf("%s", z);
 -        if( zFile && zFile[0]=='|' ){
 -          while( i+1<nArg ) zFile = sqlite3_mprintf("%z %s", zFile, azArg[++i]);
 -          break;
 -        }
 -      }else{
 -        utf8_printf(p->out,"ERROR: extra parameter: \"%s\".  Usage:\n",
 -                    azArg[i]);
 -        showHelp(p->out, azArg[0]);
 -        rc = 1;
 -        sqlite3_free(zFile);
 -        goto meta_command_exit;
 -      }
 -    }
 -    if( zFile==0 ){
 -      zFile = sqlite3_mprintf("stdout");
 -    }
 -    if( bOnce ){
 -      p->outCount = 2;
 +  open_db(p, 0);
 +  /* Consider some refactoring to avoid duplicative wholesale copying. */
 +  memcpy(&data, ISS(p), sizeof(data));
 +  memcpy(&datax, p, sizeof(datax));
 +  data.pSXS = &datax;
 +  datax.pSIS = &data;
 +
 +  data.showHeader = 0;
 +  data.cMode = data.mode = MODE_Semi;
 +  initText(&sSelect);
 +  for(ii=1; ii<nArg; ii++){
 +    if( optionMatch(azArg[ii],"indent") ){
 +      data.cMode = data.mode = MODE_Pretty;
 +    }else if( optionMatch(azArg[ii],"debug") ){
 +      bDebug = 1;
 +    }else if( optionMatch(azArg[ii],"nosys") ){
 +      bNoSystemTabs = 1;
 +    }else if( azArg[ii][0]=='-' ){
 +      return DCR_Unknown|ii;
 +    }else if( zName==0 ){
 +      zName = azArg[ii];
      }else{
 -      p->outCount = 0;
 +      return DCR_TooMany;
 +    }
 +  }
 +  if( zName!=0 ){
 +    int isSchema = sqlite3_strlike(zName, "sqlite_master", '\\')==0
 +      || sqlite3_strlike(zName, "sqlite_schema", '\\')==0
 +      || sqlite3_strlike(zName,"sqlite_temp_master", '\\')==0
 +      || sqlite3_strlike(zName,"sqlite_temp_schema", '\\')==0;
 +    if( isSchema ){
 +      char *new_argv[2], *new_colv[2];
 +      new_argv[0] = smprintf(
 +                             "CREATE TABLE %s (\n"
 +                             "  type text,\n"
 +                             "  name text,\n"
 +                             "  tbl_name text,\n"
 +                             "  rootpage integer,\n"
 +                             "  sql text\n"
 +                             ")", zName);
 +      shell_check_oom(new_argv[0]);
 +      new_argv[1] = 0;
 +      new_colv[0] = "sql";
 +      new_colv[1] = 0;
 +      callback(&datax, 1, new_argv, new_colv);
 +      sqlite3_free(new_argv[0]);
 +    }
 +  }
 +  if( zDiv ){
 +    sqlite3_stmt *pStmt = 0;
 +    rc = sqlite3_prepare_v2(datax.dbUser,
 +                            "SELECT name FROM pragma_database_list",
 +                            -1, &pStmt, 0);
 +    if( rc ){
 +      *pzErr = smprintf("%s\n", sqlite3_errmsg(datax.dbUser));
 +      sqlite3_finalize(pStmt);
 +      return DCR_Error;
      }
 -    output_reset(p);
 -#ifndef SQLITE_NOHAVE_SYSTEM
 -    if( eMode=='e' || eMode=='x' ){
 -      p->doXdgOpen = 1;
 -      outputModePush(p);
 -      if( eMode=='x' ){
 -        /* spreadsheet mode.  Output as CSV. */
 -        newTempFile(p, "csv");
 -        ShellClearFlag(p, SHFLG_Echo);
 -        p->mode = MODE_Csv;
 -        sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
 -        sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf);
 +    appendText(&sSelect, "SELECT sql FROM", 0);
 +    iSchema = 0;
 +    while( sqlite3_step(pStmt)==SQLITE_ROW ){
 +      const char *zDb = (const char*)sqlite3_column_text(pStmt, 0);
 +      char zScNum[30];
 +      sqlite3_snprintf(sizeof(zScNum), zScNum, "%d", ++iSchema);
 +      appendText(&sSelect, zDiv, 0);
 +      zDiv = " UNION ALL ";
 +      appendText(&sSelect, "SELECT shell_add_schema(sql,", 0);
 +      if( sqlite3_stricmp(zDb, "main")!=0 ){
 +        appendText(&sSelect, zDb, '\'');
        }else{
 -        /* text editor mode */
 -        newTempFile(p, "txt");
 -        bTxtMode = 1;
 +        appendText(&sSelect, "NULL", 0);
        }
 -      sqlite3_free(zFile);
 -      zFile = sqlite3_mprintf("%s", p->zTempFile);
 +      appendText(&sSelect, ",name) AS sql, type, tbl_name, name, rowid,", 0);
 +      appendText(&sSelect, zScNum, 0);
 +      appendText(&sSelect, " AS snum, ", 0);
 +      appendText(&sSelect, zDb, '\'');
 +      appendText(&sSelect, " AS sname FROM ", 0);
 +      appendText(&sSelect, zDb, quoteChar(zDb));
 +      appendText(&sSelect, ".sqlite_schema", 0);
      }
 -#endif /* SQLITE_NOHAVE_SYSTEM */
 -    shell_check_oom(zFile);
 -    if( zFile[0]=='|' ){
 -#ifdef SQLITE_OMIT_POPEN
 -      raw_printf(stderr, "Error: pipes are not supported in this OS\n");
 -      rc = 1;
 -      p->out = stdout;
 -#else
 -      p->out = popen(zFile + 1, "w");
 -      if( p->out==0 ){
 -        utf8_printf(stderr,"Error: cannot open pipe \"%s\"\n", zFile + 1);
 -        p->out = stdout;
 -        rc = 1;
 +    sqlite3_finalize(pStmt);
 +#ifndef SQLITE_OMIT_INTROSPECTION_PRAGMAS
 +    if( zName ){
 +      appendText(&sSelect,
 +                 " UNION ALL SELECT shell_module_schema(name),"
 +                 " 'table', name, name, name, 9e+99, 'main'"
 +                 " FROM pragma_module_list",
 +                 0);
 +    }
 +#endif
 +    appendText(&sSelect, ") WHERE ", 0);
 +    if( zName ){
 +      char *zQarg = smprintf("%Q", zName);
 +      int bGlob;
 +      shell_check_oom(zQarg);
 +      bGlob = strchr(zName, '*') != 0 || strchr(zName, '?') != 0
 +        || strchr(zName, '[') != 0;
 +      if( strchr(zName, '.') ){
 +        appendText(&sSelect, "lower(printf('%s.%s',sname,tbl_name))", 0);
        }else{
 -        if( zBOM[0] ) fwrite(zBOM, 1, 3, p->out);
 -        sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
 +        appendText(&sSelect, "lower(tbl_name)", 0);
        }
 -#endif
 -    }else{
 -      p->out = output_file_open(zFile, bTxtMode);
 -      if( p->out==0 ){
 -        if( strcmp(zFile,"off")!=0 ){
 -          utf8_printf(stderr,"Error: cannot write to \"%s\"\n", zFile);
 -        }
 -        p->out = stdout;
 -        rc = 1;
 -      } else {
 -        if( zBOM[0] ) fwrite(zBOM, 1, 3, p->out);
 -        sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
 +      appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0);
 +      appendText(&sSelect, zQarg, 0);
 +      if( !bGlob ){
 +        appendText(&sSelect, " ESCAPE '\\' ", 0);
        }
 +      appendText(&sSelect, " AND ", 0);
 +      sqlite3_free(zQarg);
      }
 -    sqlite3_free(zFile);
 -  }else
 -
 -  if( c=='p' && n>=3 && strncmp(azArg[0], "parameter", n)==0 ){
 -    open_db(p,0);
 -    if( nArg<=1 ) goto parameter_syntax_error;
 -
 -    /* .parameter clear
 -    ** Clear all bind parameters by dropping the TEMP table that holds them.
 -    */
 -    if( nArg==2 && strcmp(azArg[1],"clear")==0 ){
 -      sqlite3_exec(p->db, "DROP TABLE IF EXISTS temp.sqlite_parameters;",
 -                   0, 0, 0);
 -    }else
 -
 -    /* .parameter list
 -    ** List all bind parameters.
 -    */
 -    if( nArg==2 && strcmp(azArg[1],"list")==0 ){
 -      sqlite3_stmt *pStmt = 0;
 -      int rx;
 -      int len = 0;
 -      rx = sqlite3_prepare_v2(p->db,
 -             "SELECT max(length(key)) "
 -             "FROM temp.sqlite_parameters;", -1, &pStmt, 0);
 -      if( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 -        len = sqlite3_column_int(pStmt, 0);
 -        if( len>40 ) len = 40;
 -      }
 -      sqlite3_finalize(pStmt);
 -      pStmt = 0;
 -      if( len ){
 -        rx = sqlite3_prepare_v2(p->db,
 -             "SELECT key, quote(value) "
 -             "FROM temp.sqlite_parameters;", -1, &pStmt, 0);
 -        while( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 -          utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
 -                      sqlite3_column_text(pStmt,1));
 -        }
 -        sqlite3_finalize(pStmt);
 -      }
 -    }else
 -
 -    /* .parameter init
 -    ** Make sure the TEMP table used to hold bind parameters exists.
 -    ** Create it if necessary.
 -    */
 -    if( nArg==2 && strcmp(azArg[1],"init")==0 ){
 -      bind_table_init(p);
 -    }else
 -
 -    /* .parameter set NAME VALUE
 -    ** Set or reset a bind parameter.  NAME should be the full parameter
 -    ** name exactly as it appears in the query.  (ex: $abc, @def).  The
 -    ** VALUE can be in either SQL literal notation, or if not it will be
 -    ** understood to be a text string.
 -    */
 -    if( nArg==4 && strcmp(azArg[1],"set")==0 ){
 -      int rx;
 -      char *zSql;
 -      sqlite3_stmt *pStmt;
 -      const char *zKey = azArg[2];
 -      const char *zValue = azArg[3];
 -      bind_table_init(p);
 -      zSql = sqlite3_mprintf(
 -                  "REPLACE INTO temp.sqlite_parameters(key,value)"
 -                  "VALUES(%Q,%s);", zKey, zValue);
 -      shell_check_oom(zSql);
 -      pStmt = 0;
 -      rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -      sqlite3_free(zSql);
 -      if( rx!=SQLITE_OK ){
 -        sqlite3_finalize(pStmt);
 -        pStmt = 0;
 -        zSql = sqlite3_mprintf(
 -                   "REPLACE INTO temp.sqlite_parameters(key,value)"
 -                   "VALUES(%Q,%Q);", zKey, zValue);
 -        shell_check_oom(zSql);
 -        rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -        sqlite3_free(zSql);
 -        if( rx!=SQLITE_OK ){
 -          utf8_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db));
 -          sqlite3_finalize(pStmt);
 -          pStmt = 0;
 -          rc = 1;
 -        }
 -      }
 -      sqlite3_step(pStmt);
 -      sqlite3_finalize(pStmt);
 -    }else
 +    if( bNoSystemTabs ){
 +      appendText(&sSelect, "name NOT LIKE 'sqlite_%%' AND ", 0);
 +    }
 +    appendText(&sSelect, "sql IS NOT NULL"
 +               " ORDER BY snum, rowid", 0);
 +    if( bDebug ){
 +      utf8_printf(data.out, "SQL: %s;\n", sSelect.z);
 +    }else{
 +      rc = sqlite3_exec(datax.dbUser, sSelect.z, callback, &datax, &zErrMsg);
 +    }
 +    freeText(&sSelect);
 +  }
 +  if( zErrMsg ){
 +    *pzErr = zErrMsg;
 +    return DCR_Error;
 +  }else if( rc != SQLITE_OK ){
 +    *pzErr = smprintf("Error: querying schema information\n");
 +    return DCR_Error;
 +  }else{
 +    return DCR_Ok;
 +  }
 +}
  
 -    /* .parameter unset NAME
 -    ** Remove the NAME binding from the parameter binding table, if it
 -    ** exists.
 -    */
 -    if( nArg==3 && strcmp(azArg[1],"unset")==0 ){
 -      char *zSql = sqlite3_mprintf(
 -          "DELETE FROM temp.sqlite_parameters WHERE key=%Q", azArg[2]);
 -      shell_check_oom(zSql);
 -      sqlite3_exec(p->db, zSql, 0, 0, 0);
 -      sqlite3_free(zSql);
 -    }else
 -    /* If no command name matches, show a syntax error */
 -    parameter_syntax_error:
 -    showHelp(p->out, "parameter");
 -  }else
 +/*****************
 + * The .selecttrace, .separator, .session and .sha3sum commands
 + */
 +CONDITION_COMMAND( session defined(SQLITE_ENABLE_SESSION) );
 +COLLECT_HELP_TEXT[
 +  ".separator COL ?ROW?     Change the column and row separators",
 +  ".session ?NAME? CMD ...  Create or control sessions",
 +  "   Subcommands:",
 +  "     attach TABLE             Attach TABLE",
 +  "     changeset FILE           Write a changeset into FILE",
 +  "     close                    Close one session",
 +  "     enable ?BOOLEAN?         Set or query the enable bit",
 +  "     filter GLOB...           Reject tables matching GLOBs",
 +  "     indirect ?BOOLEAN?       Mark or query the indirect status",
 +  "     isempty                  Query whether the session is empty",
 +  "     list                     List currently open session names",
 +  "     open DB NAME             Open a new session on DB",
 +  "     patchset FILE            Write a patchset into FILE",
 +  "   If ?NAME? is omitted, the first defined session is used.",
 +  ".sha3sum ...             Compute a SHA3 hash of database content",
 +  "    Options:",
 +  "      --schema              Also hash the sqlite_schema table",
 +  "      --sha3-224            Use the sha3-224 algorithm",
 +  "      --sha3-256            Use the sha3-256 algorithm (default)",
 +  "      --sha3-384            Use the sha3-384 algorithm",
 +  "      --sha3-512            Use the sha3-512 algorithm",
 +  "    Any other argument is a LIKE pattern for tables to hash",
 +];
 +DISPATCHABLE_COMMAND( selecttrace ? 1 0 ){
 +  unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
 +  sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x);
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( separator ? 2 3 ){
 +  if( nArg>=2 ){
 +    sqlite3_snprintf(sizeof(ISS(p)->colSeparator), ISS(p)->colSeparator,
 +                     "%.*s", (int)ArraySize(ISS(p)->colSeparator)-1, azArg[1]);
 +  }
 +  if( nArg>=3 ){
 +    sqlite3_snprintf(sizeof(ISS(p)->rowSeparator), ISS(p)->rowSeparator,
 +                     "%.*s", (int)ArraySize(ISS(p)->rowSeparator)-1, azArg[2]);
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( session 3 2 0 ){
 +  int rc = 0;
 +  struct AuxDb *pAuxDb = ISS(p)->pAuxDb;
 +  OpenSession *pSession = &pAuxDb->aSession[0];
 +  FILE *out = ISS(p)->out;
 +  char **azCmd = &azArg[1];
 +  int iSes = 0;
 +  int nCmd = nArg - 1;
 +  int i;
 +  open_db(p, 0);
 +  if( nArg>=3 ){
 +    for(iSes=0; iSes<pAuxDb->nSession; iSes++){
 +      if( strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break;
 +    }
 +    if( iSes<pAuxDb->nSession ){
 +      pSession = &pAuxDb->aSession[iSes];
 +      azCmd++;
 +      nCmd--;
 +    }else{
 +      pSession = &pAuxDb->aSession[0];
 +      iSes = 0;
 +    }
 +  }
  
 -  if( c=='p' && n>=3 && strncmp(azArg[0], "print", n)==0 ){
 -    int i;
 -    for(i=1; i<nArg; i++){
 -      if( i>1 ) raw_printf(p->out, " ");
 -      utf8_printf(p->out, "%s", azArg[i]);
 +  /* .session attach TABLE
 +  ** Invoke the sqlite3session_attach() interface to attach a particular
 +  ** table so that it is never filtered.
 +  */
 +  if( strcmp(azCmd[0],"attach")==0 ){
 +    if( nCmd!=2 ) goto session_syntax_error;
 +    if( pSession->p==0 ){
 +    session_not_open:
 +      raw_printf(STD_ERR, "ERROR: No sessions are open\n");
 +    }else{
 +      rc = sqlite3session_attach(pSession->p, azCmd[1]);
 +      if( rc ){
 +        raw_printf(STD_ERR, "ERROR: sqlite3session_attach() returns %d\n", rc);
 +        rc = 0;
 +      }
      }
 -    raw_printf(p->out, "\n");
    }else
  
 -#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
 -  if( c=='p' && n>=3 && strncmp(azArg[0], "progress", n)==0 ){
 -    int i;
 -    int nn = 0;
 -    p->flgProgress = 0;
 -    p->mxProgress = 0;
 -    p->nProgress = 0;
 -    for(i=1; i<nArg; i++){
 -      const char *z = azArg[i];
 -      if( z[0]=='-' ){
 -        z++;
 -        if( z[0]=='-' ) z++;
 -        if( strcmp(z,"quiet")==0 || strcmp(z,"q")==0 ){
 -          p->flgProgress |= SHELL_PROGRESS_QUIET;
 -          continue;
 -        }
 -        if( strcmp(z,"reset")==0 ){
 -          p->flgProgress |= SHELL_PROGRESS_RESET;
 -          continue;
 +  /* .session changeset FILE
 +  ** .session patchset FILE
 +  ** Write a changeset or patchset into a file.  The file is overwritten.
 +  */
 +  if( strcmp(azCmd[0],"changeset")==0 || strcmp(azCmd[0],"patchset")==0 ){
 +    FILE *cs_out = 0;
 +    if( failIfSafeMode
 +        (p, "cannot run \".session %s\" in safe mode", azCmd[0]) ){
 +      rc = DCR_AbortError;
 +    }else{
 +      if( nCmd!=2 ) goto session_syntax_error;
 +      if( pSession->p==0 ) goto session_not_open;
 +      cs_out = fopen(azCmd[1], "wb");
 +      if( cs_out==0 ){
 +        *pzErr = smprintf("cannot open \"%s\" for writing\n", azCmd[1]);
 +        rc = 1;
 +      }else{
 +        int szChng;
 +        void *pChng;
 +        if( azCmd[0][0]=='c' ){
 +          rc = sqlite3session_changeset(pSession->p, &szChng, &pChng);
 +        }else{
 +          rc = sqlite3session_patchset(pSession->p, &szChng, &pChng);
          }
 -        if( strcmp(z,"once")==0 ){
 -          p->flgProgress |= SHELL_PROGRESS_ONCE;
 -          continue;
 +        if( rc ){
 +          fprintf(out, "Error: error code %d\n", rc);
 +          rc = 0;
          }
 -        if( strcmp(z,"limit")==0 ){
 -          if( i+1>=nArg ){
 -            utf8_printf(stderr, "Error: missing argument on --limit\n");
 -            rc = 1;
 -            goto meta_command_exit;
 -          }else{
 -            p->mxProgress = (int)integerValue(azArg[++i]);
 -          }
 -          continue;
 +        if( pChng && fwrite(pChng, szChng, 1, cs_out)!=1 ){
 +          raw_printf(STD_ERR, "ERROR: Failed to write entire %d-byte output\n",
 +                     szChng);
          }
 -        utf8_printf(stderr, "Error: unknown option: \"%s\"\n", azArg[i]);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }else{
 -        nn = (int)integerValue(z);
 +        sqlite3_free(pChng);
 +        fclose(cs_out);
        }
      }
 -    open_db(p, 0);
 -    sqlite3_progress_handler(p->db, nn, progress_handler, p);
    }else
 -#endif /* SQLITE_OMIT_PROGRESS_CALLBACK */
  
 -  if( c=='p' && strncmp(azArg[0], "prompt", n)==0 ){
 -    if( nArg >= 2) {
 -      strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1);
 -    }
 -    if( nArg >= 3) {
 -      strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1);
 +  /* .session close
 +  ** Close the identified session
 +  */
 +  if( strcmp(azCmd[0], "close")==0 ){
 +    if( nCmd!=1 ) goto session_syntax_error;
 +    if( pAuxDb->nSession ){
 +      session_close(pSession);
 +      pAuxDb->aSession[iSes] = pAuxDb->aSession[--pAuxDb->nSession];
      }
    }else
  
 -  if( c=='q' && strncmp(azArg[0], "quit", n)==0 ){
 -    rc = 2;
 +  /* .session enable ?BOOLEAN?
 +  ** Query or set the enable flag
 +  */
 +  if( strcmp(azCmd[0], "enable")==0 ){
 +    int ii;
 +    if( nCmd>2 ) goto session_syntax_error;
 +    ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
 +    if( pAuxDb->nSession ){
 +      ii = sqlite3session_enable(pSession->p, ii);
 +      utf8_printf(out, "session %s enable flag = %d\n",
 +                  pSession->zName, ii);
 +    }
    }else
  
 -  if( c=='r' && n>=3 && strncmp(azArg[0], "read", n)==0 ){
 -    FILE *inSaved = p->in;
 -    int savedLineno = p->lineno;
 -    failIfSafeMode(p, "cannot run .read in safe mode");
 -    if( nArg!=2 ){
 -      raw_printf(stderr, "Usage: .read FILE\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    if( azArg[1][0]=='|' ){
 -#ifdef SQLITE_OMIT_POPEN
 -      raw_printf(stderr, "Error: pipes are not supported in this OS\n");
 -      rc = 1;
 -      p->out = stdout;
 -#else
 -      p->in = popen(azArg[1]+1, "r");
 -      if( p->in==0 ){
 -        utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]);
 -        rc = 1;
 -      }else{
 -        rc = process_input(p);
 -        pclose(p->in);
 +  /* .session filter GLOB ....
 +  ** Set a list of GLOB patterns of table names to be excluded.
 +  */
 +  if( strcmp(azCmd[0], "filter")==0 ){
 +    int ii, nByte;
 +    if( nCmd<2 ) goto session_syntax_error;
 +    if( pAuxDb->nSession ){
 +      for(ii=0; ii<pSession->nFilter; ii++){
 +        sqlite3_free(pSession->azFilter[ii]);
        }
 -#endif
 -    }else if( (p->in = openChrSource(azArg[1]))==0 ){
 -      utf8_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]);
 -      rc = 1;
 -    }else{
 -      rc = process_input(p);
 -      fclose(p->in);
 +      sqlite3_free(pSession->azFilter);
 +      nByte = sizeof(pSession->azFilter[0])*(nCmd-1);
 +      pSession->azFilter = sqlite3_malloc( nByte );
 +      if( pSession->azFilter==0 ){
 +        shell_out_of_memory();
 +      }
 +      for(ii=1; ii<nCmd; ii++){
 +        pSession->azFilter[ii-1] = smprintf("%s", azCmd[ii]);
 +        shell_check_oom(pSession->azFilter[ii-1]);
 +      }
 +      pSession->nFilter = ii-1;
      }
 -    p->in = inSaved;
 -    p->lineno = savedLineno;
    }else
  
 -  if( c=='r' && n>=3 && strncmp(azArg[0], "restore", n)==0 ){
 -    const char *zSrcFile;
 -    const char *zDb;
 -    sqlite3 *pSrc;
 -    sqlite3_backup *pBackup;
 -    int nTimeout = 0;
 -
 -    failIfSafeMode(p, "cannot run .restore in safe mode");
 -    if( nArg==2 ){
 -      zSrcFile = azArg[1];
 -      zDb = "main";
 -    }else if( nArg==3 ){
 -      zSrcFile = azArg[2];
 -      zDb = azArg[1];
 -    }else{
 -      raw_printf(stderr, "Usage: .restore ?DB? FILE\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    rc = sqlite3_open(zSrcFile, &pSrc);
 -    if( rc!=SQLITE_OK ){
 -      utf8_printf(stderr, "Error: cannot open \"%s\"\n", zSrcFile);
 -      close_db(pSrc);
 -      return 1;
 -    }
 -    open_db(p, 0);
 -    pBackup = sqlite3_backup_init(p->db, zDb, pSrc, "main");
 -    if( pBackup==0 ){
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 -      close_db(pSrc);
 -      return 1;
 +  /* .session indirect ?BOOLEAN?
 +  ** Query or set the indirect flag
 +  */
 +  if( strcmp(azCmd[0], "indirect")==0 ){
 +    int ii;
 +    if( nCmd>2 ) goto session_syntax_error;
 +    ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
 +    if( pAuxDb->nSession ){
 +      ii = sqlite3session_indirect(pSession->p, ii);
 +      utf8_printf(out, "session %s indirect flag = %d\n",
 +                  pSession->zName, ii);
      }
 -    while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK
 -          || rc==SQLITE_BUSY  ){
 -      if( rc==SQLITE_BUSY ){
 -        if( nTimeout++ >= 3 ) break;
 -        sqlite3_sleep(100);
 -      }
 +  }else
 +
 +  /* .session isempty
 +  ** Determine if the session is empty
 +  */
 +  if( strcmp(azCmd[0], "isempty")==0 ){
 +    int ii;
 +    if( nCmd!=1 ) goto session_syntax_error;
 +    if( pAuxDb->nSession ){
 +      ii = sqlite3session_isempty(pSession->p);
 +      utf8_printf(out, "session %s isempty flag = %d\n",
 +                  pSession->zName, ii);
      }
 -    sqlite3_backup_finish(pBackup);
 -    if( rc==SQLITE_DONE ){
 -      rc = 0;
 -    }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){
 -      raw_printf(stderr, "Error: source database is busy\n");
 -      rc = 1;
 -    }else{
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 -      rc = 1;
 +  }else
 +
 +  /* .session list
 +  ** List all currently open sessions
 +  */
 +  if( strcmp(azCmd[0],"list")==0 ){
 +    for(i=0; i<pAuxDb->nSession; i++){
 +      utf8_printf(out, "%d %s\n", i, pAuxDb->aSession[i].zName);
      }
 -    close_db(pSrc);
    }else
  
 -  if( c=='s' && strncmp(azArg[0], "scanstats", n)==0 ){
 -    if( nArg==2 ){
 -      p->scanstatsOn = (u8)booleanValue(azArg[1]);
 -#ifndef SQLITE_ENABLE_STMT_SCANSTATUS
 -      raw_printf(stderr, "Warning: .scanstats not available in this build.\n");
 -#endif
 +  /* .session open DB NAME
 +  ** Open a new session called NAME on the attached database DB.
 +  ** DB is normally "main".
 +  */
 +  if( strcmp(azCmd[0],"open")==0 ){
 +    char *zName;
 +    if( nCmd!=3 ) goto session_syntax_error;
 +    zName = azCmd[2];
 +    if( zName[0]==0 ) goto session_syntax_error;
 +    for(i=0; i<pAuxDb->nSession; i++){
 +      if( strcmp(pAuxDb->aSession[i].zName,zName)==0 ){
 +        utf8_printf(STD_ERR, "Session \"%s\" already exists\n", zName);
 +        return rc;
 +      }
 +    }
 +    if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){
 +      raw_printf
 +        (STD_ERR, "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession));
 +      return rc;
 +    }
 +    pSession = &pAuxDb->aSession[pAuxDb->nSession];
 +    rc = sqlite3session_create(DBX(p), azCmd[1], &pSession->p);
 +    if( rc ){
 +      *pzErr = smprintf("Cannot open session: error code=%d\n", rc);
 +      return rc;
 +    }
 +    pSession->nFilter = 0;
 +    sqlite3session_table_filter(pSession->p, session_filter, pSession);
 +    pAuxDb->nSession++;
 +    shell_newstr_assign(&pSession->zName, smprintf("%s", zName));
 +  }else{
 +
 +  /* If no command name matches, show a syntax error */
 +  session_syntax_error:
 +    showHelp(out, "session", p);
 +    return DCR_CmdErred;
 +  }
 +  return rc;
 +}
 +DISPATCHABLE_COMMAND( sha3sum 4 1 1 ){
 +  const char *zLike = 0;   /* Which table to checksum. 0 means everything */
 +  int i;                   /* Loop counter */
 +  int bSchema = 0;         /* Also hash the schema */
 +  int bSeparate = 0;       /* Hash each table separately */
 +  int iSize = 224;         /* Hash algorithm to use */
 +  int bDebug = 0;          /* Only show the query that would have run */
 +  sqlite3_stmt *pStmt;     /* For querying tables names */
 +  char *zSql;              /* SQL to be run */
 +  char *zSep;              /* Separator */
 +  ShellText sSql;          /* Complete SQL for the query to run the hash */
 +  ShellText sQuery;        /* Set of queries used to read all content */
 +  open_db(p, 0);
 +  for(i=1; i<nArg; i++){
 +    const char *z = azArg[i];
 +    if( z[0]=='-' ){
 +      z++;
 +      if( z[0]=='-' ) z++;
 +      if( strcmp(z,"schema")==0 ){
 +        bSchema = 1;
 +      }else
 +        if( strcmp(z,"sha3-224")==0 || strcmp(z,"sha3-256")==0
 +            || strcmp(z,"sha3-384")==0 || strcmp(z,"sha3-512")==0
 +            ){
 +          iSize = atoi(&z[5]);
 +        }else
 +          if( strcmp(z,"debug")==0 ){
 +            bDebug = 1;
 +          }else
 +            {
 +              *pzErr = smprintf("Unknown option \"%s\" on \"%s\"\n",
 +                                azArg[i], azArg[0]);
 +              return DCR_Unknown|i;
 +            }
 +    }else if( zLike ){
 +      return DCR_TooMany;
      }else{
 -      raw_printf(stderr, "Usage: .scanstats on|off\n");
 -      rc = 1;
 +      zLike = z;
 +      bSeparate = 1;
 +      if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 1;
      }
 -  }else
 +  }
 +  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(DBX(p), zSql, -1, &pStmt, 0);
 +  initText(&sQuery);
 +  initText(&sSql);
 +  appendText(&sSql, "WITH [sha3sum$query](a,b) AS(",0);
 +  zSep = "VALUES(";
 +  while( SQLITE_ROW==sqlite3_step(pStmt) ){
 +    const char *zTab = (const char*)sqlite3_column_text(pStmt,0);
 +    if( zTab==0 ) continue;
 +    if( zLike && sqlite3_strlike(zLike, zTab, 0)!=0 ) continue;
 +    if( strncmp(zTab, "sqlite_",7)!=0 ){
 +      appendText(&sQuery,"SELECT * FROM ", 0);
 +      appendText(&sQuery,zTab,'"');
 +      appendText(&sQuery," NOT INDEXED;", 0);
 +    }else if( strcmp(zTab, "sqlite_schema")==0 ){
 +      appendText(&sQuery,"SELECT type,name,tbl_name,sql FROM sqlite_schema"
 +                 " ORDER BY name;", 0);
 +    }else if( strcmp(zTab, "sqlite_sequence")==0 ){
 +      appendText(&sQuery,"SELECT name,seq FROM sqlite_sequence"
 +                 " ORDER BY name;", 0);
 +    }else if( strcmp(zTab, "sqlite_stat1")==0 ){
 +      appendText(&sQuery,"SELECT tbl,idx,stat FROM sqlite_stat1"
 +                 " ORDER BY tbl,idx;", 0);
 +    }else if( strcmp(zTab, "sqlite_stat4")==0 ){
 +      appendText(&sQuery, "SELECT * FROM ", 0);
 +      appendText(&sQuery, zTab, 0);
 +      appendText(&sQuery, " ORDER BY tbl, idx, rowid;\n", 0);
 +    }
 +    appendText(&sSql, zSep, 0);
 +    appendText(&sSql, sQuery.z, '\'');
 +    sQuery.n = 0;
 +    appendText(&sSql, ",", 0);
 +    appendText(&sSql, zTab, '\'');
 +    zSep = "),(";
 +  }
 +  sqlite3_finalize(pStmt);
 +  if( bSeparate ){
 +    zSql = smprintf(
 +           "%s))"
 +           " SELECT lower(hex(sha3_query(a,%d))) AS hash, b AS label"
 +           "   FROM [sha3sum$query]",
 +           sSql.z, iSize);
 +  }else{
 +    zSql = smprintf(
 +           "%s))"
 +           " SELECT lower(hex(sha3_query(group_concat(a,''),%d))) AS hash"
 +           "   FROM [sha3sum$query]",
 +           sSql.z, iSize);
 +  }
 +  shell_check_oom(zSql);
 +  freeText(&sQuery);
 +  freeText(&sSql);
 +  if( bDebug ){
 +    utf8_printf(ISS(p)->out, "%s\n", zSql);
 +  }else{
 +    shell_exec(p, zSql, 0);
 +  }
 +  sqlite3_free(zSql);
 +  return DCR_Ok;
 +}
  
 -  if( c=='s' && strncmp(azArg[0], "schema", n)==0 ){
 -    ShellText sSelect;
 -    ShellState data;
 -    char *zErrMsg = 0;
 -    const char *zDiv = "(";
 -    const char *zName = 0;
 -    int iSchema = 0;
 -    int bDebug = 0;
 -    int bNoSystemTabs = 0;
 -    int ii;
 +/*****************
 + * The .selftest*, .shell, and .show commands
 + */
 +CONDITION_COMMAND( selftest_bool defined(SQLITE_DEBUG) );
 +CONDITION_COMMAND( selftest_int defined(SQLITE_DEBUG) );
 +COLLECT_HELP_TEXT[
 +  ",selftest ?OPTIONS?      Run tests defined in the SELFTEST table",
 +  "    Options:",
 +  "       --init               Create a new SELFTEST table",
 +  "       -v                   Verbose output",
 +  ",selftest_bool ?ARGS?    Show boolean values of ARGS as flag tokens",
 +  ",selftest_int ?ARGS?     Show integer values of ARGS as integer tokens",
 +  ".show                    Show the current values for various settings",
 +];
  
 -    open_db(p, 0);
 -    memcpy(&data, p, sizeof(data));
 -    data.showHeader = 0;
 -    data.cMode = data.mode = MODE_Semi;
 -    initText(&sSelect);
 -    for(ii=1; ii<nArg; ii++){
 -      if( optionMatch(azArg[ii],"indent") ){
 -        data.cMode = data.mode = MODE_Pretty;
 -      }else if( optionMatch(azArg[ii],"debug") ){
 -        bDebug = 1;
 -      }else if( optionMatch(azArg[ii],"nosys") ){
 -        bNoSystemTabs = 1;
 -      }else if( azArg[ii][0]=='-' ){
 -        utf8_printf(stderr, "Unknown option: \"%s\"\n", azArg[ii]);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }else if( zName==0 ){
 -        zName = azArg[ii];
 -      }else{
 -        raw_printf(stderr, "Usage: .schema ?--indent? ?--nosys? ?LIKE-PATTERN?\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -    }
 -    if( zName!=0 ){
 -      int isSchema = sqlite3_strlike(zName, "sqlite_master", '\\')==0
 -                  || sqlite3_strlike(zName, "sqlite_schema", '\\')==0
 -                  || sqlite3_strlike(zName,"sqlite_temp_master", '\\')==0
 -                  || sqlite3_strlike(zName,"sqlite_temp_schema", '\\')==0;
 -      if( isSchema ){
 -        char *new_argv[2], *new_colv[2];
 -        new_argv[0] = sqlite3_mprintf(
 -                      "CREATE TABLE %s (\n"
 -                      "  type text,\n"
 -                      "  name text,\n"
 -                      "  tbl_name text,\n"
 -                      "  rootpage integer,\n"
 -                      "  sql text\n"
 -                      ")", zName);
 -        shell_check_oom(new_argv[0]);
 -        new_argv[1] = 0;
 -        new_colv[0] = "sql";
 -        new_colv[1] = 0;
 -        callback(&data, 1, new_argv, new_colv);
 -        sqlite3_free(new_argv[0]);
 -      }
 -    }
 -    if( zDiv ){
 -      sqlite3_stmt *pStmt = 0;
 -      rc = sqlite3_prepare_v2(p->db, "SELECT name FROM pragma_database_list",
 -                              -1, &pStmt, 0);
 -      if( rc ){
 -        utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 -        sqlite3_finalize(pStmt);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      appendText(&sSelect, "SELECT sql FROM", 0);
 -      iSchema = 0;
 -      while( sqlite3_step(pStmt)==SQLITE_ROW ){
 -        const char *zDb = (const char*)sqlite3_column_text(pStmt, 0);
 -        char zScNum[30];
 -        sqlite3_snprintf(sizeof(zScNum), zScNum, "%d", ++iSchema);
 -        appendText(&sSelect, zDiv, 0);
 -        zDiv = " UNION ALL ";
 -        appendText(&sSelect, "SELECT shell_add_schema(sql,", 0);
 -        if( sqlite3_stricmp(zDb, "main")!=0 ){
 -          appendText(&sSelect, zDb, '\'');
 -        }else{
 -          appendText(&sSelect, "NULL", 0);
 +DISPATCHABLE_COMMAND( selftest_bool 10 0 0 ){
 +  int i, v;
 +  for(i=1; i<nArg; i++){
 +    v = booleanValue(azArg[i]);
 +    utf8_printf(ISS(p)->out, "%s: %d 0x%x\n", azArg[i], v, v);
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( selftest_int 10 0 0 ){
 +  int i; sqlite3_int64 v;
 +  for(i=1; i<nArg; i++){
 +    char zBuf[200];
 +    v = integerValue(azArg[i]);
 +    sqlite3_snprintf(sizeof(zBuf),zBuf,"%s: %lld 0x%llx\n", azArg[i],v,v);
 +    utf8_printf(ISS(p)->out, "%s", zBuf);
 +  }
 +  return DCR_Ok;
 +}
 +
 +DISPATCHABLE_COMMAND( selftest 4 0 0 ){
-   int rc;
++  int rc = 0;
 +  ShellInState *psi = ISS(p);
 +  int bIsInit = 0;         /* True to initialize the SELFTEST table */
 +  int bVerbose = 0;        /* Verbose output */
 +  int bSelftestExists;     /* True if SELFTEST already exists */
 +  int i, k;                /* Loop counters */
 +  int nTest = 0;           /* Number of tests runs */
 +  int nErr = 0;            /* Number of errors seen */
 +  ShellText str;           /* Answer for a query */
 +  sqlite3_stmt *pStmt = 0; /* Query against the SELFTEST table */
 +
 +  for(i=1; i<nArg; i++){
 +    const char *z = azArg[i];
 +    if( z[0]=='-' && z[1]=='-' ) z++;
 +    if( strcmp(z,"-init")==0 ){
 +      bIsInit = 1;
 +    }else
 +      if( strcmp(z,"-v")==0 ){
 +        bVerbose++;
 +      }else
 +        {
 +          *pzErr = smprintf
 +            ("Unknown option \"%s\" on \"%s\"\n"
 +             "Should be one of: --init -v\n", azArg[i], azArg[0]);
 +          return DCR_ArgWrong;
          }
 -        appendText(&sSelect, ",name) AS sql, type, tbl_name, name, rowid,", 0);
 -        appendText(&sSelect, zScNum, 0);
 -        appendText(&sSelect, " AS snum, ", 0);
 -        appendText(&sSelect, zDb, '\'');
 -        appendText(&sSelect, " AS sname FROM ", 0);
 -        appendText(&sSelect, zDb, quoteChar(zDb));
 -        appendText(&sSelect, ".sqlite_schema", 0);
 -      }
 +  }
 +  open_db(p,0);
 +  if( sqlite3_table_column_metadata(DBX(p),"main","selftest",0,0,0,0,0,0)
 +      != SQLITE_OK ){
 +    bSelftestExists = 0;
 +  }else{
 +    bSelftestExists = 1;
 +  }
 +  if( bIsInit ){
 +    createSelftestTable(ISS(p));
 +    bSelftestExists = 1;
 +  }
 +  initText(&str);
 +  appendText(&str, "x", 0);
 +  for(k=bSelftestExists; k>=0; k--){
 +    if( k==1 ){
 +      rc = sqlite3_prepare_v2(DBX(p),
 +              "SELECT tno,op,cmd,ans FROM selftest ORDER BY tno",
 +              -1, &pStmt, 0);
 +    }else{
 +      rc = sqlite3_prepare_v2(DBX(p),
 +          "VALUES(0,'memo','Missing SELFTEST table - default checks only',''),"
 +          "      (1,'run','PRAGMA integrity_check','ok')",
 +          -1, &pStmt, 0);
 +    }
 +    if( rc ){
 +      *pzErr = smprintf("Error querying the selftest table\n");
        sqlite3_finalize(pStmt);
 -#ifndef SQLITE_OMIT_INTROSPECTION_PRAGMAS
 -      if( zName ){
 -        appendText(&sSelect,
 -           " UNION ALL SELECT shell_module_schema(name),"
 -           " 'table', name, name, name, 9e+99, 'main' FROM pragma_module_list",
 -        0);
 -      }
 -#endif
 -      appendText(&sSelect, ") WHERE ", 0);
 -      if( zName ){
 -        char *zQarg = sqlite3_mprintf("%Q", zName);
 -        int bGlob;
 -        shell_check_oom(zQarg);
 -        bGlob = strchr(zName, '*') != 0 || strchr(zName, '?') != 0 ||
 -                strchr(zName, '[') != 0;
 -        if( strchr(zName, '.') ){
 -          appendText(&sSelect, "lower(printf('%s.%s',sname,tbl_name))", 0);
 -        }else{
 -          appendText(&sSelect, "lower(tbl_name)", 0);
 +      return DCR_Error;
 +    }
 +    for(i=1; sqlite3_step(pStmt)==SQLITE_ROW; i++){
 +      int tno = sqlite3_column_int(pStmt, 0);
 +      const char *zOp = (const char*)sqlite3_column_text(pStmt, 1);
 +      const char *zSql = (const char*)sqlite3_column_text(pStmt, 2);
 +      const char *zAns = (const char*)sqlite3_column_text(pStmt, 3);
 +
 +      if( zOp==0 || zSql==0 || zAns==0 ) continue;
 +      k = 0;
 +      if( bVerbose>0 ){
 +        /* This unusually directed output is for test purposes. */
 +        fprintf(STD_OUT, "%d: %s %s\n", tno, zOp, zSql);
 +      }
 +      if( strcmp(zOp,"memo")==0 ){
 +        utf8_printf(psi->out, "%s\n", zSql);
 +      }else if( strcmp(zOp,"run")==0 ){
 +        char *zErrMsg = 0;
 +        str.n = 0;
 +        str.z[0] = 0;
 +        rc = sqlite3_exec(DBX(p), zSql, captureOutputCallback, &str, &zErrMsg);
 +        nTest++;
 +        if( bVerbose ){
 +          utf8_printf(psi->out, "Result: %s\n", str.z);
          }
 -        appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0);
 -        appendText(&sSelect, zQarg, 0);
 -        if( !bGlob ){
 -          appendText(&sSelect, " ESCAPE '\\' ", 0);
 +        if( rc || zErrMsg ){
 +          nErr++;
 +          rc = 1;
 +          utf8_printf(psi->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(psi->out, "%d: Expected: [%s]\n", tno, zAns);
 +          utf8_printf(psi->out, "%d:      Got: [%s]\n", tno, str.z);
          }
 -        appendText(&sSelect, " AND ", 0);
 -        sqlite3_free(zQarg);
 +      }else{
 +        *pzErr = smprintf
 +          ("Unknown operation \"%s\" on selftest line %d\n", zOp, tno);
 +        rc = 1;
 +        break;
        }
 -      if( bNoSystemTabs ){
 -        appendText(&sSelect, "name NOT LIKE 'sqlite_%%' AND ", 0);
 +    } /* End loop over rows of content from SELFTEST */
 +    sqlite3_finalize(pStmt);
 +  } /* End loop over k */
 +  freeText(&str);
 +  utf8_printf(psi->out, "%d errors out of %d tests\n", nErr, nTest);
 +  return rc > 0;
 +}
 +
 +/*****************
 + * The .shell and .system commands
 + */
 +CONDITION_COMMAND( shell !defined(SQLITE_NOHAVE_SYSTEM) );
 +CONDITION_COMMAND( system !defined(SQLITE_NOHAVE_SYSTEM) );
 +COLLECT_HELP_TEXT[
 +  ".shell CMD ARGS...       Run CMD ARGS... in a system shell",
 +  ".system CMD ARGS...      Run CMD ARGS... in a system shell",
 +];
 +
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +static DotCmdRC shellOut(char *azArg[], int nArg,
 +                         ShellExState *psx, char **pzErr){
 +  char *zCmd;
 +  int i, x;
 +  if( ISS(psx)->bSafeMode ) return DCR_AbortError;
 +  zCmd = smprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]);
 +  for(i=2; i<nArg; i++){
 +    zCmd = smprintf(strchr(azArg[i],' ')==0?"%z %s":"%z \"%s\"",
 +                    zCmd, azArg[i]);
 +  }
 +  shell_check_oom(zCmd);
 +  x = system(zCmd);
 +  sqlite3_free(zCmd);
 +  if( x ) raw_printf(STD_ERR, "%s command returns %d\n", azArg[0], x);
 +  return DCR_Ok;
 +}
 +#endif
 +DISPATCHABLE_COMMAND( shell ? 2 0 ){
 +  return shellOut(azArg, nArg, p, pzErr);
 +}
 +
 +DISPATCHABLE_COMMAND( system ? 2 0 ){
 +  return shellOut(azArg, nArg, p, pzErr);
 +}
 +
 +/*****************
 + * The .shxload and .shxopts commands
 + */
 +CONDITION_COMMAND( shxload (SHELL_DYNAMIC_EXTENSION)!=0 );
 +CONDITION_COMMAND( shxopts (SHELL_EXTENSIONS)!=0 );
 +COLLECT_HELP_TEXT[
 +  ".shxload FILE ?OPTIONS?  Load a CLI shell extension library",
 +  "   The first option may name the init function to be called upon load.",
 +  "   Otherwise, its name is derived from FILE. Either way, the entry point"
 +  "   \"sqlite_NAME_init\" is called. All options after \"--\" are passed to",
 +  "   the extension's init function in the ShellExtensionLink struct.",
 +  ".shxopts ?SIGNED_OPTS?   Show or alter shell extension options",
 +  "   Run without arguments to see their self-descriptive names",
 +];
 +
 +DISPATCHABLE_COMMAND( shxload 4 2 0 ){
 +  const char *zFile = 0, *zProc = 0;
 +  int ai = 1, rc;
 +  char **pzExtArgs = 0;
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +  while( ai<nArg ){
 +    const char *zA = azArg[ai++];
 +    if( strcmp(zA, "--")==0 ){
 +      pzExtArgs = azArg + ai;
 +      break;
 +    }
 +    if( zFile==0 ) zFile = zA;
 +    else if( zProc==0 ) zProc = zA;
 +  }
 +  if( zFile==0 ) return DCR_Missing;
 +  if( pzExtArgs==0 ) pzExtArgs = azArg + ai;
 +  rc = load_shell_extension(p, zFile, zProc, pzErr, nArg-ai, pzExtArgs);
 +  return DCR_Ok|(rc!=SQLITE_OK);
 +}
 +
 +DISPATCHABLE_COMMAND( shxopts 3 0 0 ){
 +  static struct { const char *name; u8 mask; } shopts[] = {
 +#if SHELL_DYNAMIC_COMMANDS
 +    {"dyn_cmds", 1<<SHEXT_DYNCMDS_BIT},
 +#endif
 +#if SHELL_EXTENDED_PARSING
 +    {"parsing", 1<<SHEXT_PARSING_BIT},
 +#endif
 +#if SHELL_VARIABLE_EXPANSION
 +    {"dot_vars", 1<<SHEXT_VAREXP_BIT},
 +#endif
 +    {"all_opts", SHELL_ALL_EXTENSIONS}
 +  };
 +  const char *zMoan = 0, *zAbout = 0;
 +  ShellInState *psi = ISS(p);
 +  int ia, io;
 +  if( nArg>1 ){
 +    for( ia=1; ia<nArg; ++ia ){
 +      char cs = azArg[ia][0];
 +      if( cs!='+' && cs!='-' ){
 +        zMoan = "arguments must have a sign prefix.";
 +        zAbout = azArg[0];
 +        goto moan_error;
 +      }
 +      for( io=0; io<ArraySize(shopts); ++io ){
 +        if( strcmp(azArg[ia]+1, shopts[io].name)==0 ){
 +          if( cs=='+' ) psi->bExtendedDotCmds |= shopts[io].mask;
 +          else psi->bExtendedDotCmds &= ~shopts[io].mask;
 +          break;
 +        }
        }
 -      appendText(&sSelect, "sql IS NOT NULL"
 -                           " ORDER BY snum, rowid", 0);
 -      if( bDebug ){
 -        utf8_printf(p->out, "SQL: %s;\n", sSelect.z);
 -      }else{
 -        rc = sqlite3_exec(p->db, sSelect.z, callback, &data, &zErrMsg);
 +      if( io==ArraySize(shopts) ){
 +        zAbout = azArg[ia];
 +        zMoan = "is not a recognized option name";
 +        goto moan_error;
        }
 -      freeText(&sSelect);
      }
 -    if( zErrMsg ){
 -      utf8_printf(stderr,"Error: %s\n", zErrMsg);
 -      sqlite3_free(zErrMsg);
 -      rc = 1;
 -    }else if( rc != SQLITE_OK ){
 -      raw_printf(stderr,"Error: querying schema information\n");
 -      rc = 1;
 +  }else{
 +    raw_printf(psi->out,
 +               "     name    value  \"-shxopts set\"\n"
 +               "   --------  -----  ---------------\n");
 +    for( io=0; io<ArraySize(shopts); ++io ){
 +      unsigned m = shopts[io].mask;
 +      unsigned v = ((psi->bExtendedDotCmds & m) == m)? 1 : 0;
 +      raw_printf(psi->out,
 +                 "  %9s   %2d    \"-shxopts 0x%02X\"\n",
 +                 shopts[io].name,  v, m);
 +    }
 +  }
 +  return DCR_Ok;
 + moan_error:
 +  raw_printf(STD_ERR, "Error: %s %s\n", zAbout, zMoan);
 +  return DCR_CmdErred;
 +}
 +DISPATCHABLE_COMMAND( show ? 1 1 ){
 +  static const char *azBool[] = { "off", "on", "trigger", "full"};
 +  const char *zOut;
 +  ShellInState *psi = ISS(p);
 +  FILE *out = psi->out;
 +  int i;
 +  utf8_printf(out, "%12.12s: %s\n","echo",
 +              azBool[ShellHasFlag(p, SHFLG_Echo)]);
 +  utf8_printf(out, "%12.12s: %s\n","eqp", azBool[psi->autoEQP&3]);
 +  utf8_printf(out, "%12.12s: %s\n","explain",
 +              psi->mode==MODE_Explain
 +              ? "on" : psi->autoExplain ? "auto" : "off");
 +  utf8_printf(out,"%12.12s: %s\n","headers", azBool[psi->showHeader!=0]);
 +  zOut = modeDescr[psi->mode].zModeName;
 +  i = strlen30(zOut) - modeDescr[psi->mode].bDepluralize;
 +  if( MODE_IS_COLUMNAR(psi->mode) ){
 +    utf8_printf
 +      (out, "%12.12s: %.*s --wrap %d --wordwrap %s --%squote\n", "mode",
 +       i, zOut, psi->cmOpts.iWrap,
 +       psi->cmOpts.bWordWrap ? "on" : "off",
 +       psi->cmOpts.bQuote ? "" : "no");
 +  }else{
 +    utf8_printf(out, "%12.12s: %.*s\n","mode", i, zOut);
 +  }
 +  utf8_printf(out, "%12.12s: ", "nullvalue");
 +  output_c_string(out, psi->nullValue);
 +  raw_printf(out, "\n");
 +  utf8_printf(out,"%12.12s: %s\n","output",
 +              strlen30(psi->outfile) ? psi->outfile : "stdout");
 +  utf8_printf(out,"%12.12s: ", "colseparator");
 +  output_c_string(out, psi->colSeparator);
 +  raw_printf(out, "\n");
 +  utf8_printf(out,"%12.12s: ", "rowseparator");
 +  output_c_string(out, psi->rowSeparator);
 +  raw_printf(out, "\n");
 +  switch( psi->statsOn ){
 +  case 0:  zOut = "off";     break;
 +  default: zOut = "on";      break;
 +  case 2:  zOut = "stmt";    break;
 +  case 3:  zOut = "vmstep";  break;
 +  }
 +  utf8_printf(out, "%12.12s: %s\n","stats", zOut);
 +  utf8_printf(out, "%12.12s: ", "width");
 +  for (i=0;i<p->numWidths;i++) {
 +    raw_printf(out, "%d ", p->pSpecWidths[i]);
 +  }
 +  raw_printf(out, "\n");
 +  utf8_printf(out, "%12.12s: %s\n", "filename",
 +              psi->pAuxDb->zDbFilename ? psi->pAuxDb->zDbFilename : "");
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * The .stats command
 + */
 +COLLECT_HELP_TEXT[
 +  ".stats ?ARG?             Show stats or turn stats on or off",
 +  "   off                      Turn off automatic stat display",
 +  "   on                       Turn on automatic stat display",
 +  "   stmt                     Show statement stats",
 +  "   vmstep                   Show the virtual machine step count only",
 +];
 +DISPATCHABLE_COMMAND( stats ? 0 0 ){
 +  ShellInState *psi = ISS(p);
 +  if( nArg==2 ){
 +    if( strcmp(azArg[1],"stmt")==0 ){
 +      psi->statsOn = 2;
 +    }else if( strcmp(azArg[1],"vmstep")==0 ){
 +      psi->statsOn = 3;
      }else{
 -      rc = 0;
 +      psi->statsOn = (u8)booleanValue(azArg[1]);
      }
 -  }else
 +  }else if( nArg==1 ){
 +    display_stats(DBX(p), psi, 0);
 +  }else{
 +    *pzErr = smprintf("Usage: .stats ?on|off|stmt|vmstep?\n");
 +    return DCR_SayUsage;
 +  }
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * 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, ShellExState *p,
 +                         char **pzErr, char ot){
 +  int rc;
 +  sqlite3_stmt *pStmt;
 +  char **azResult;
 +  int nRow, nAlloc;
 +  int ii;
 +  ShellText s;
 +  initText(&s);
 +  open_db(p, 0);
 +  rc = sqlite3_prepare_v2(DBX(p), "PRAGMA database_list", -1, &pStmt, 0);
 +  if( rc ){
 +    sqlite3_finalize(pStmt);
 +    return shellDatabaseError(DBX(p));
 +  }
 +
 +  if( nArg>2 && ot=='i' ){
 +    /* It is an historical accident that the .indexes command shows an error
 +    ** when called with the wrong number of arguments whereas the .tables
 +    ** command does not. */
 +    *pzErr = smprintf("Usage: .indexes ?LIKE-PATTERN?\n");
 +    sqlite3_finalize(pStmt);
 +    return DCR_SayUsage;
 +  }
 +  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{
 +      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);
 +    }
 +    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(DBX(p), s.z, -1, &pStmt, 0);
 +  }
 +  freeText(&s);
 +  if( rc ) return shellDatabaseError(DBX(p));
 +
 +  /* Run the SQL statement prepared by the above block. Store the results
 +  ** as an array of nul-terminated strings in azResult[].  */
 +  nRow = nAlloc = 0;
 +  azResult = 0;
 +  if( nArg>1 ){
 +    sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT);
 +  }else{
 +    sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC);
 +  }
 +  while( sqlite3_step(pStmt)==SQLITE_ROW ){
 +    if( nRow>=nAlloc ){
 +      char **azNew;
 +      int n2 = nAlloc*2 + 10;
 +      azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2);
 +      shell_check_oom(azNew);
 +      nAlloc = n2;
 +      azResult = azNew;
 +    }
 +    azResult[nRow] = smprintf("%s", sqlite3_column_text(pStmt, 0));
 +    shell_check_oom(azResult[nRow]);
 +    nRow++;
 +  }
 +  if( sqlite3_finalize(pStmt)!=SQLITE_OK ){
 +    rc = shellDatabaseError(DBX(p));
 +  }
 +
 +  /* 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(ISS(p)->out, "%s%-*s", zSp, maxlen,
 +                    azResult[j] ? azResult[j]:"");
 +      }
 +      raw_printf(ISS(p)->out, "\n");
 +    }
 +  }
 +
 +  for(ii=0; ii<nRow; ii++) sqlite3_free(azResult[ii]);
 +  sqlite3_free(azResult);
 +  return DCR_Ok;
 +}
 +
 +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 DCR_Unknown|1;
 +    }
 +  }
 +#endif
 +  return showTableLike(azArg, nArg, p, pzErr, objType);
 +}
 +DISPATCHABLE_COMMAND( indexes 3 1 2 ){
 +  return showTableLike(azArg, nArg, p, pzErr, 'i');
 +}
 +DISPATCHABLE_COMMAND( indices 3 1 2 ){
 +  return showTableLike(azArg, nArg, p, pzErr, 'i');
 +}
  
 -  if( (c=='s' && n==11 && strncmp(azArg[0], "selecttrace", n)==0)
 -   || (c=='t' && n==9  && strncmp(azArg[0], "treetrace", n)==0)
 -  ){
 -    unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
 -    sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x);
 -  }else
 +/*****************
 + * The .testcase, .testctrl, .timeout, .timer and .trace commands
 + */
 +CONDITION_COMMAND( testctrl !defined(SQLITE_UNTESTABLE) );
 +CONDITION_COMMAND( trace !defined(SQLITE_OMIT_TRACE) );
 +COLLECT_HELP_TEXT[
 +  ",testcase NAME           Begin redirecting output to 'testcase-out.txt'",
 +  ",testctrl CMD ...        Run various sqlite3_test_control() operations",
 +  "                            Run \".testctrl\" with no arguments for details",
 +  ".timeout MS              Try opening locked tables for MS milliseconds",
 +  ".timer on|off            Turn SQL timer on or off",
 +  ".trace ?OPTIONS?         Output each SQL statement as it is run",
 +  "    FILE                    Send output to FILE",
 +  "    stdout                  Send output to stdout",
 +  "    stderr                  Send output to stderr",
 +  "    off                     Disable tracing",
 +  "    --expanded              Expand query parameters",
 +#ifdef SQLITE_ENABLE_NORMALIZE
 +  "    --normalized            Normal the SQL statements",
 +#endif
 +  "    --plain                 Show SQL as it is input",
 +  "    --stmt                  Trace statement execution (SQLITE_TRACE_STMT)",
 +  "    --profile               Profile statements (SQLITE_TRACE_PROFILE)",
 +  "    --row                   Trace each row (SQLITE_TRACE_ROW)",
 +  "    --close                 Trace connection close (SQLITE_TRACE_CLOSE)",
 +];
 +
 +/* Begin redirecting output to the file "testcase-out.txt" */
 +DISPATCHABLE_COMMAND( testcase ? 0 0 ){
 +  ShellInState *psi = ISS(p);
 +  output_reset(psi);
 +  psi->out = output_file_open("testcase-out.txt", 0);
 +  if( psi->out==0 ){
 +    raw_printf(STD_ERR, "Error: cannot open 'testcase-out.txt'\n");
 +  }
 +  if( nArg>=2 ){
 +    sqlite3_snprintf(sizeof(psi->zTestcase), psi->zTestcase, "%s", azArg[1]);
 +  }else{
 +    sqlite3_snprintf(sizeof(psi->zTestcase), psi->zTestcase, "?");
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( testctrl ? 0 0 ){
 +  FILE *out = ISS(p)->out;
 +  static const struct {
 +    const char *zCtrlName;   /* Name of a test-control option */
 +    int ctrlCode;            /* Integer code for that option */
 +    int unSafe;              /* Not valid for --safe mode */
 +    const char *zUsage;      /* Usage notes */
 +  } aCtrl[] = {
 +    { "always",             SQLITE_TESTCTRL_ALWAYS, 1,     "BOOLEAN"         },
 +    { "assert",             SQLITE_TESTCTRL_ASSERT, 1,     "BOOLEAN"         },
 +  /*{ "benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS,1, ""        },*/
 +  /*{ "bitvec_test",        SQLITE_TESTCTRL_BITVEC_TEST, 1,  ""              },*/
 +    { "byteorder",          SQLITE_TESTCTRL_BYTEORDER, 0,  ""                },
 +    { "extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,0,"BOOLEAN"  },
 +  /*{ "fault_install",      SQLITE_TESTCTRL_FAULT_INSTALL, 1,""              },*/
 +    { "imposter",         SQLITE_TESTCTRL_IMPOSTER,1,"SCHEMA ON/OFF ROOTPAGE"},
 +    { "internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS,0,""          },
 +    { "localtime_fault",    SQLITE_TESTCTRL_LOCALTIME_FAULT,0,"BOOLEAN"      },
 +    { "never_corrupt",      SQLITE_TESTCTRL_NEVER_CORRUPT,1, "BOOLEAN"       },
 +    { "optimizations",      SQLITE_TESTCTRL_OPTIMIZATIONS,0,"DISABLE-MASK"   },
 +#ifdef YYCOVERAGE
 +    { "parser_coverage",    SQLITE_TESTCTRL_PARSER_COVERAGE,0,""             },
 +#endif
 +    { "pending_byte",       SQLITE_TESTCTRL_PENDING_BYTE,0, "OFFSET  "       },
 +    { "prng_restore",       SQLITE_TESTCTRL_PRNG_RESTORE,0, ""               },
 +    { "prng_save",          SQLITE_TESTCTRL_PRNG_SAVE,   0, ""               },
 +    { "prng_seed",          SQLITE_TESTCTRL_PRNG_SEED,   0, "SEED ?db?"      },
 +    { "seek_count",         SQLITE_TESTCTRL_SEEK_COUNT,  0, ""               },
 +    { "sorter_mmap",        SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX"           },
 +    { "tune",               SQLITE_TESTCTRL_TUNE,        1, "ID VALUE"       },
 +  };
 +  int testctrl = -1;
 +  int iCtrl = -1;
 +  int rc2 = 0;    /* 0: usage.  1: %d  2: %x  3: no-output */
 +  int isOk = 0;
 +  int i, n2;
 +  const char *zCmd = 0;
  
 -#if defined(SQLITE_ENABLE_SESSION)
 -  if( c=='s' && strncmp(azArg[0],"session",n)==0 && n>=3 ){
 -    struct AuxDb *pAuxDb = p->pAuxDb;
 -    OpenSession *pSession = &pAuxDb->aSession[0];
 -    char **azCmd = &azArg[1];
 -    int iSes = 0;
 -    int nCmd = nArg - 1;
 -    int i;
 -    if( nArg<=1 ) goto session_syntax_error;
 -    open_db(p, 0);
 -    if( nArg>=3 ){
 -      for(iSes=0; iSes<pAuxDb->nSession; iSes++){
 -        if( strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break;
 -      }
 -      if( iSes<pAuxDb->nSession ){
 -        pSession = &pAuxDb->aSession[iSes];
 -        azCmd++;
 -        nCmd--;
 -      }else{
 -        pSession = &pAuxDb->aSession[0];
 -        iSes = 0;
 -      }
 +  open_db(p, 0);
 +  zCmd = nArg>=2 ? azArg[1] : "help";
 +
 +  /* The argument can optionally begin with "-" or "--" */
 +  if( zCmd[0]=='-' && zCmd[1] ){
 +    zCmd++;
 +    if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 +  }
 +
 +  /* --help lists all test-controls */
 +  if( strcmp(zCmd,"help")==0 ){
 +    utf8_printf(out, "Available test-controls:\n");
 +    for(i=0; i<ArraySize(aCtrl); i++){
 +      utf8_printf(out, "  .testctrl %s %s\n",
 +                  aCtrl[i].zCtrlName, aCtrl[i].zUsage);
      }
 +    return DCR_CmdErred;
 +  }
  
 -    /* .session attach TABLE
 -    ** Invoke the sqlite3session_attach() interface to attach a particular
 -    ** table so that it is never filtered.
 -    */
 -    if( strcmp(azCmd[0],"attach")==0 ){
 -      if( nCmd!=2 ) goto session_syntax_error;
 -      if( pSession->p==0 ){
 -        session_not_open:
 -        raw_printf(stderr, "ERROR: No sessions are open\n");
 +  /* 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{
 -        rc = sqlite3session_attach(pSession->p, azCmd[1]);
 -        if( rc ){
 -          raw_printf(stderr, "ERROR: sqlite3session_attach() returns %d\n", rc);
 -          rc = 0;
 -        }
 +        *pzErr = smprintf
 +          ("Error: ambiguous test-control: \"%s\"\n"
 +           "Use \".testctrl --help\" for help\n", zCmd);
 +        return DCR_ArgWrong;
        }
 -    }else
 +    }
 +  }
 +  if( testctrl<0 ){
 +    utf8_printf(STD_ERR,"Error: unknown test-control: %s\n"
 +                "Use \".testctrl --help\" for help\n", zCmd);
 +  }else if( aCtrl[iCtrl].unSafe && ISS(p)->bSafeMode ){
 +    utf8_printf(STD_ERR,
 +                "line %d: \".testctrl %s\" may not be used in safe mode\n",
 +                ISS(p)->pInSource->lineno, aCtrl[iCtrl].zCtrlName);
 +    exit(1);
 +  }else{
 +    switch(testctrl){
  
 -    /* .session changeset FILE
 -    ** .session patchset FILE
 -    ** Write a changeset or patchset into a file.  The file is overwritten.
 -    */
 -    if( strcmp(azCmd[0],"changeset")==0 || strcmp(azCmd[0],"patchset")==0 ){
 -      FILE *out = 0;
 -      failIfSafeMode(p, "cannot run \".session %s\" in safe mode", azCmd[0]);
 -      if( nCmd!=2 ) goto session_syntax_error;
 -      if( pSession->p==0 ) goto session_not_open;
 -      out = fopen(azCmd[1], "wb");
 -      if( out==0 ){
 -        utf8_printf(stderr, "ERROR: cannot open \"%s\" for writing\n",
 -                    azCmd[1]);
 -      }else{
 -        int szChng;
 -        void *pChng;
 -        if( azCmd[0][0]=='c' ){
 -          rc = sqlite3session_changeset(pSession->p, &szChng, &pChng);
 -        }else{
 -          rc = sqlite3session_patchset(pSession->p, &szChng, &pChng);
 -        }
 -        if( rc ){
 -          printf("Error: error code %d\n", rc);
 -          rc = 0;
 -        }
 -        if( pChng
 -          && fwrite(pChng, szChng, 1, out)!=1 ){
 -          raw_printf(stderr, "ERROR: Failed to write entire %d-byte output\n",
 -                  szChng);
 -        }
 -        sqlite3_free(pChng);
 -        fclose(out);
 +      /* 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, DBX(p), opt);
 +        isOk = 3;
        }
 -    }else
 +      break;
  
 -    /* .session close
 -    ** Close the identified session
 -    */
 -    if( strcmp(azCmd[0], "close")==0 ){
 -      if( nCmd!=1 ) goto session_syntax_error;
 -      if( pAuxDb->nSession ){
 -        session_close(pSession);
 -        pAuxDb->aSession[iSes] = pAuxDb->aSession[--pAuxDb->nSession];
 +      /* 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;
        }
 -    }else
 +      break;
  
 -    /* .session enable ?BOOLEAN?
 -    ** Query or set the enable flag
 -    */
 -    if( strcmp(azCmd[0], "enable")==0 ){
 -      int ii;
 -      if( nCmd>2 ) goto session_syntax_error;
 -      ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
 -      if( pAuxDb->nSession ){
 -        ii = sqlite3session_enable(pSession->p, ii);
 -        utf8_printf(p->out, "session %s enable flag = %d\n",
 -                    pSession->zName, ii);
 +      /* 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;
        }
 -    }else
 +      break;
  
 -    /* .session filter GLOB ....
 -    ** Set a list of GLOB patterns of table names to be excluded.
 -    */
 -    if( strcmp(azCmd[0], "filter")==0 ){
 -      int ii, nByte;
 -      if( nCmd<2 ) goto session_syntax_error;
 -      if( pAuxDb->nSession ){
 -        for(ii=0; ii<pSession->nFilter; ii++){
 -          sqlite3_free(pSession->azFilter[ii]);
 +      /* sqlite3_test_control(int, int, sqlite3*) */
 +    case SQLITE_TESTCTRL_PRNG_SEED:
 +      if( nArg==3 || nArg==4 ){
 +        int ii = (int)integerValue(azArg[2]);
 +        sqlite3 *db;
 +        if( ii==0 && strcmp(azArg[2],"random")==0 ){
 +          sqlite3_randomness(sizeof(ii),&ii);
 +          fprintf(STD_OUT, "-- random seed: %d\n", ii);
          }
 -        sqlite3_free(pSession->azFilter);
 -        nByte = sizeof(pSession->azFilter[0])*(nCmd-1);
 -        pSession->azFilter = sqlite3_malloc( nByte );
 -        if( pSession->azFilter==0 ){
 -          raw_printf(stderr, "Error: out or memory\n");
 -          exit(1);
 -        }
 -        for(ii=1; ii<nCmd; ii++){
 -          char *x = pSession->azFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]);
 -          shell_check_oom(x);
 +        if( nArg==3 ){
 +          db = 0;
 +        }else{
 +          db = DBX(p);
 +          /* Make sure the schema has been loaded */
 +          sqlite3_table_column_metadata(db, 0, "x", 0, 0, 0, 0, 0, 0);
          }
 -        pSession->nFilter = ii-1;
 +        rc2 = sqlite3_test_control(testctrl, ii, db);
 +        isOk = 3;
        }
 -    }else
 +      break;
  
 -    /* .session indirect ?BOOLEAN?
 -    ** Query or set the indirect flag
 -    */
 -    if( strcmp(azCmd[0], "indirect")==0 ){
 -      int ii;
 -      if( nCmd>2 ) goto session_syntax_error;
 -      ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
 -      if( pAuxDb->nSession ){
 -        ii = sqlite3session_indirect(pSession->p, ii);
 -        utf8_printf(p->out, "session %s indirect flag = %d\n",
 -                    pSession->zName, ii);
 +      /* 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;
        }
 -    }else
 +      break;
  
 -    /* .session isempty
 -    ** Determine if the session is empty
 -    */
 -    if( strcmp(azCmd[0], "isempty")==0 ){
 -      int ii;
 -      if( nCmd!=1 ) goto session_syntax_error;
 -      if( pAuxDb->nSession ){
 -        ii = sqlite3session_isempty(pSession->p);
 -        utf8_printf(p->out, "session %s isempty flag = %d\n",
 -                    pSession->zName, ii);
 +      /* sqlite3_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;
        }
 -    }else
 +      break;
  
 -    /* .session list
 -    ** List all currently open sessions
 -    */
 -    if( strcmp(azCmd[0],"list")==0 ){
 -      for(i=0; i<pAuxDb->nSession; i++){
 -        utf8_printf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName);
 -      }
 -    }else
 +      /* sqlite3_test_control(sqlite3*) */
 +    case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS:
 +      rc2 = sqlite3_test_control(testctrl, DBX(p));
 +      isOk = 3;
 +      break;
  
 -    /* .session open DB NAME
 -    ** Open a new session called NAME on the attached database DB.
 -    ** DB is normally "main".
 -    */
 -    if( strcmp(azCmd[0],"open")==0 ){
 -      char *zName;
 -      if( nCmd!=3 ) goto session_syntax_error;
 -      zName = azCmd[2];
 -      if( zName[0]==0 ) goto session_syntax_error;
 -      for(i=0; i<pAuxDb->nSession; i++){
 -        if( strcmp(pAuxDb->aSession[i].zName,zName)==0 ){
 -          utf8_printf(stderr, "Session \"%s\" already exists\n", zName);
 -          goto meta_command_exit;
 -        }
 +    case SQLITE_TESTCTRL_IMPOSTER:
 +      if( nArg==5 ){
 +        rc2 = sqlite3_test_control(testctrl, DBX(p),
 +                                   azArg[2],
 +                                   integerValue(azArg[3]),
 +                                   integerValue(azArg[4]));
 +        isOk = 3;
        }
 -      if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){
 -        raw_printf(stderr, "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession));
 -        goto meta_command_exit;
 -      }
 -      pSession = &pAuxDb->aSession[pAuxDb->nSession];
 -      rc = sqlite3session_create(p->db, azCmd[1], &pSession->p);
 -      if( rc ){
 -        raw_printf(stderr, "Cannot open session: error code=%d\n", rc);
 -        rc = 0;
 -        goto meta_command_exit;
 +      break;
 +
 +    case SQLITE_TESTCTRL_SEEK_COUNT: {
 +      u64 x = 0;
 +      rc2 = sqlite3_test_control(testctrl, DBX(p), &x);
 +      utf8_printf(out, "%llu\n", x);
 +      isOk = 3;
 +      break;
 +    }
 +
 +#ifdef YYCOVERAGE
 +    case SQLITE_TESTCTRL_PARSER_COVERAGE: {
 +      if( nArg==2 ){
 +        sqlite3_test_control(testctrl, out);
 +        isOk = 3;
        }
 -      pSession->nFilter = 0;
 -      sqlite3session_table_filter(pSession->p, session_filter, pSession);
 -      pAuxDb->nSession++;
 -      pSession->zName = sqlite3_mprintf("%s", zName);
 -      shell_check_oom(pSession->zName);
 -    }else
 -    /* If no command name matches, show a syntax error */
 -    session_syntax_error:
 -    showHelp(p->out, "session");
 -  }else
 +      break;
 +    }
  #endif
 -
  #ifdef SQLITE_DEBUG
 -  /* Undocumented commands for internal testing.  Subject to change
 -  ** without notice. */
 -  if( c=='s' && n>=10 && strncmp(azArg[0], "selftest-", 9)==0 ){
 -    if( strncmp(azArg[0]+9, "boolean", n-9)==0 ){
 -      int i, v;
 -      for(i=1; i<nArg; i++){
 -        v = booleanValue(azArg[i]);
 -        utf8_printf(p->out, "%s: %d 0x%x\n", azArg[i], v, v);
 +    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(out, "  ");
 +          utf8_printf(out, "%d: %d", id, val);
 +          id++;
 +        }
 +        if( id>1 ) utf8_printf(out, "\n");
 +        isOk = 3;
        }
 +      break;
      }
 -    if( strncmp(azArg[0]+9, "integer", n-9)==0 ){
 -      int i; sqlite3_int64 v;
 -      for(i=1; i<nArg; i++){
 -        char zBuf[200];
 -        v = integerValue(azArg[i]);
 -        sqlite3_snprintf(sizeof(zBuf),zBuf,"%s: %lld 0x%llx\n", azArg[i],v,v);
 -        utf8_printf(p->out, "%s", zBuf);
 -      }
 +#endif
      }
 -  }else
 +  }
 +  if( isOk==0 && iCtrl>=0 ){
 +    utf8_printf(out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
 +    return DCR_CmdErred;
 +  }else if( isOk==1 ){
 +    raw_printf(out, "%d\n", rc2);
 +  }else if( isOk==2 ){
 +    raw_printf(out, "0x%08x\n", rc2);
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( timeout 4 1 2 ){
 +  open_db(p, 0);
 +  sqlite3_busy_timeout(DBX(p), nArg>=2 ? (int)integerValue(azArg[1]) : 0);
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( timer ? 2 2 ){
 +  enableTimer = booleanValue(azArg[1]);
 +  if( enableTimer && !HAS_TIMER ){
 +    raw_printf(STD_ERR, "Error: timer not available on this system.\n");
 +    enableTimer = 0;
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( trace ? 0 0 ){
 +  ShellInState *psi = ISS(p);
 +  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") ){
 +        psi->eTraceType = SHELL_TRACE_EXPANDED;
 +      }
 +#ifdef SQLITE_ENABLE_NORMALIZE
 +      else if( optionMatch(z, "normalized") ){
 +        psi->eTraceType = SHELL_TRACE_NORMALIZED;
 +      }
  #endif
 -
 -  if( c=='s' && n>=4 && strncmp(azArg[0],"selftest",n)==0 ){
 -    int bIsInit = 0;         /* True to initialize the SELFTEST table */
 -    int bVerbose = 0;        /* Verbose output */
 -    int bSelftestExists;     /* True if SELFTEST already exists */
 -    int i, k;                /* Loop counters */
 -    int nTest = 0;           /* Number of tests runs */
 -    int nErr = 0;            /* Number of errors seen */
 -    ShellText str;           /* Answer for a query */
 -    sqlite3_stmt *pStmt = 0; /* Query against the SELFTEST table */
 -
 -    open_db(p,0);
 -    for(i=1; i<nArg; i++){
 -      const char *z = azArg[i];
 -      if( z[0]=='-' && z[1]=='-' ) z++;
 -      if( strcmp(z,"-init")==0 ){
 -        bIsInit = 1;
 -      }else
 -      if( strcmp(z,"-v")==0 ){
 -        bVerbose++;
 -      }else
 -      {
 -        utf8_printf(stderr, "Unknown option \"%s\" on \"%s\"\n",
 -                    azArg[i], azArg[0]);
 -        raw_printf(stderr, "Should be one of: --init -v\n");
 -        rc = 1;
 -        goto meta_command_exit;
 +      else if( optionMatch(z, "plain") ){
 +        psi->eTraceType = SHELL_TRACE_PLAIN;
        }
 -    }
 -    if( sqlite3_table_column_metadata(p->db,"main","selftest",0,0,0,0,0,0)
 -           != SQLITE_OK ){
 -      bSelftestExists = 0;
 -    }else{
 -      bSelftestExists = 1;
 -    }
 -    if( bIsInit ){
 -      createSelftestTable(p);
 -      bSelftestExists = 1;
 -    }
 -    initText(&str);
 -    appendText(&str, "x", 0);
 -    for(k=bSelftestExists; k>=0; k--){
 -      if( k==1 ){
 -        rc = sqlite3_prepare_v2(p->db,
 -            "SELECT tno,op,cmd,ans FROM selftest ORDER BY tno",
 -            -1, &pStmt, 0);
 -      }else{
 -        rc = sqlite3_prepare_v2(p->db,
 -          "VALUES(0,'memo','Missing SELFTEST table - default checks only',''),"
 -          "      (1,'run','PRAGMA integrity_check','ok')",
 -          -1, &pStmt, 0);
 +      else if( optionMatch(z, "profile") ){
 +        mType |= SQLITE_TRACE_PROFILE;
        }
 -      if( rc ){
 -        raw_printf(stderr, "Error querying the selftest table\n");
 -        rc = 1;
 -        sqlite3_finalize(pStmt);
 -        goto meta_command_exit;
 -      }
 -      for(i=1; sqlite3_step(pStmt)==SQLITE_ROW; i++){
 -        int tno = sqlite3_column_int(pStmt, 0);
 -        const char *zOp = (const char*)sqlite3_column_text(pStmt, 1);
 -        const char *zSql = (const char*)sqlite3_column_text(pStmt, 2);
 -        const char *zAns = (const char*)sqlite3_column_text(pStmt, 3);
 -
 -        if( zOp==0 ) continue;
 -        if( zSql==0 ) continue;
 -        if( zAns==0 ) continue;
 -        k = 0;
 -        if( bVerbose>0 ){
 -          printf("%d: %s %s\n", tno, zOp, zSql);
 -        }
 -        if( strcmp(zOp,"memo")==0 ){
 -          utf8_printf(p->out, "%s\n", zSql);
 -        }else
 -        if( strcmp(zOp,"run")==0 ){
 -          char *zErrMsg = 0;
 -          str.n = 0;
 -          str.z[0] = 0;
 -          rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg);
 -          nTest++;
 -          if( bVerbose ){
 -            utf8_printf(p->out, "Result: %s\n", str.z);
 -          }
 -          if( rc || zErrMsg ){
 -            nErr++;
 -            rc = 1;
 -            utf8_printf(p->out, "%d: error-code-%d: %s\n", tno, rc, zErrMsg);
 -            sqlite3_free(zErrMsg);
 -          }else if( strcmp(zAns,str.z)!=0 ){
 -            nErr++;
 -            rc = 1;
 -            utf8_printf(p->out, "%d: Expected: [%s]\n", tno, zAns);
 -            utf8_printf(p->out, "%d:      Got: [%s]\n", tno, str.z);
 -          }
 -        }else
 -        {
 -          utf8_printf(stderr,
 -            "Unknown operation \"%s\" on selftest line %d\n", zOp, tno);
 -          rc = 1;
 -          break;
 -        }
 -      } /* End loop over rows of content from SELFTEST */
 -      sqlite3_finalize(pStmt);
 -    } /* End loop over k */
 -    freeText(&str);
 -    utf8_printf(p->out, "%d errors out of %d tests\n", nErr, nTest);
 -  }else
 -
 -  if( c=='s' && strncmp(azArg[0], "separator", n)==0 ){
 -    if( nArg<2 || nArg>3 ){
 -      raw_printf(stderr, "Usage: .separator COL ?ROW?\n");
 -      rc = 1;
 -    }
 -    if( nArg>=2 ){
 -      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator,
 -                       "%.*s", (int)ArraySize(p->colSeparator)-1, azArg[1]);
 -    }
 -    if( nArg>=3 ){
 -      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator,
 -                       "%.*s", (int)ArraySize(p->rowSeparator)-1, azArg[2]);
 -    }
 -  }else
 -
 -  if( c=='s' && n>=4 && strncmp(azArg[0],"sha3sum",n)==0 ){
 -    const char *zLike = 0;   /* Which table to checksum. 0 means everything */
 -    int i;                   /* Loop counter */
 -    int bSchema = 0;         /* Also hash the schema */
 -    int bSeparate = 0;       /* Hash each table separately */
 -    int iSize = 224;         /* Hash algorithm to use */
 -    int bDebug = 0;          /* Only show the query that would have run */
 -    sqlite3_stmt *pStmt;     /* For querying tables names */
 -    char *zSql;              /* SQL to be run */
 -    char *zSep;              /* Separator */
 -    ShellText sSql;          /* Complete SQL for the query to run the hash */
 -    ShellText sQuery;        /* Set of queries used to read all content */
 -    open_db(p, 0);
 -    for(i=1; i<nArg; i++){
 -      const char *z = azArg[i];
 -      if( z[0]=='-' ){
 -        z++;
 -        if( z[0]=='-' ) z++;
 -        if( strcmp(z,"schema")==0 ){
 -          bSchema = 1;
 -        }else
 -        if( strcmp(z,"sha3-224")==0 || strcmp(z,"sha3-256")==0
 -         || strcmp(z,"sha3-384")==0 || strcmp(z,"sha3-512")==0
 -        ){
 -          iSize = atoi(&z[5]);
 -        }else
 -        if( strcmp(z,"debug")==0 ){
 -          bDebug = 1;
 -        }else
 -        {
 -          utf8_printf(stderr, "Unknown option \"%s\" on \"%s\"\n",
 -                      azArg[i], azArg[0]);
 -          showHelp(p->out, azArg[0]);
 -          rc = 1;
 -          goto meta_command_exit;
 -        }
 -      }else if( zLike ){
 -        raw_printf(stderr, "Usage: .sha3sum ?OPTIONS? ?LIKE-PATTERN?\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }else{
 -        zLike = z;
 -        bSeparate = 1;
 -        if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 1;
 +      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 {
 +        return DCR_Unknown|jj;
        }
 -    }
 -    if( bSchema ){
 -      zSql = "SELECT lower(name) FROM sqlite_schema"
 -             " WHERE type='table' AND coalesce(rootpage,0)>1"
 -             " UNION ALL SELECT 'sqlite_schema'"
 -             " ORDER BY 1 collate nocase";
 -    }else{
 -      zSql = "SELECT lower(name) FROM sqlite_schema"
 -             " WHERE type='table' AND coalesce(rootpage,0)>1"
 -             " AND name NOT LIKE 'sqlite_%'"
 -             " ORDER BY 1 collate nocase";
 -    }
 -    sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    initText(&sQuery);
 -    initText(&sSql);
 -    appendText(&sSql, "WITH [sha3sum$query](a,b) AS(",0);
 -    zSep = "VALUES(";
 -    while( SQLITE_ROW==sqlite3_step(pStmt) ){
 -      const char *zTab = (const char*)sqlite3_column_text(pStmt,0);
 -      if( zTab==0 ) continue;
 -      if( zLike && sqlite3_strlike(zLike, zTab, 0)!=0 ) continue;
 -      if( strncmp(zTab, "sqlite_",7)!=0 ){
 -        appendText(&sQuery,"SELECT * FROM ", 0);
 -        appendText(&sQuery,zTab,'"');
 -        appendText(&sQuery," NOT INDEXED;", 0);
 -      }else if( strcmp(zTab, "sqlite_schema")==0 ){
 -        appendText(&sQuery,"SELECT type,name,tbl_name,sql FROM sqlite_schema"
 -                           " ORDER BY name;", 0);
 -      }else if( strcmp(zTab, "sqlite_sequence")==0 ){
 -        appendText(&sQuery,"SELECT name,seq FROM sqlite_sequence"
 -                           " ORDER BY name;", 0);
 -      }else if( strcmp(zTab, "sqlite_stat1")==0 ){
 -        appendText(&sQuery,"SELECT tbl,idx,stat FROM sqlite_stat1"
 -                           " ORDER BY tbl,idx;", 0);
 -      }else if( strcmp(zTab, "sqlite_stat4")==0 ){
 -        appendText(&sQuery, "SELECT * FROM ", 0);
 -        appendText(&sQuery, zTab, 0);
 -        appendText(&sQuery, " ORDER BY tbl, idx, rowid;\n", 0);
 -      }
 -      appendText(&sSql, zSep, 0);
 -      appendText(&sSql, sQuery.z, '\'');
 -      sQuery.n = 0;
 -      appendText(&sSql, ",", 0);
 -      appendText(&sSql, zTab, '\'');
 -      zSep = "),(";
 -    }
 -    sqlite3_finalize(pStmt);
 -    if( bSeparate ){
 -      zSql = sqlite3_mprintf(
 -          "%s))"
 -          " SELECT lower(hex(sha3_query(a,%d))) AS hash, b AS label"
 -          "   FROM [sha3sum$query]",
 -          sSql.z, iSize);
 -    }else{
 -      zSql = sqlite3_mprintf(
 -          "%s))"
 -          " SELECT lower(hex(sha3_query(group_concat(a,''),%d))) AS hash"
 -          "   FROM [sha3sum$query]",
 -          sSql.z, iSize);
 -    }
 -    shell_check_oom(zSql);
 -    freeText(&sQuery);
 -    freeText(&sSql);
 -    if( bDebug ){
 -      utf8_printf(p->out, "%s\n", zSql);
      }else{
 -      shell_exec(p, zSql, 0);
 +      output_file_close(psi->traceOut);
 +      psi->traceOut = output_file_open(azArg[1], 0);
      }
 -    sqlite3_free(zSql);
 -  }else
 +  }
 +  if( psi->traceOut==0 ){
 +    sqlite3_trace_v2(DBX(p), 0, 0, 0);
 +  }else{
 +    if( mType==0 ) mType = SQLITE_TRACE_STMT;
 +    sqlite3_trace_v2(DBX(p), mType, sql_trace_callback, psi);
 +  }
 +  return DCR_Ok;
 +}
  
 -#ifndef SQLITE_NOHAVE_SYSTEM
 -  if( c=='s'
 -   && (strncmp(azArg[0], "shell", n)==0 || strncmp(azArg[0],"system",n)==0)
 -  ){
 -    char *zCmd;
 -    int i, x;
 -    failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
 -    if( nArg<2 ){
 -      raw_printf(stderr, "Usage: .system COMMAND\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    zCmd = sqlite3_mprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]);
 -    for(i=2; i<nArg && zCmd!=0; i++){
 -      zCmd = sqlite3_mprintf(strchr(azArg[i],' ')==0?"%z %s":"%z \"%s\"",
 -                             zCmd, azArg[i]);
 +/*****************
 + * The .unknown command (undocumented)
 + */
 +COLLECT_HELP_TEXT[
 +  ",unknown ?ARGS?          Handle attempt to use an unknown dot command",
 +  "   The invocation dispatcher calls this after replacing azArg[0] with the",
 +  "   mystery command name, leaving remaining arguments as originally passed.",
 +  "   An extension may override this to provide new dot commands dynamically.",
 +  "   This name and operation were inspired by a similar feature of TCL.",
 +];
 +DISPATCHABLE_COMMAND( unknown ? 1 0 ){
 +  /* Dispatcher will call this for dot commands it cannot find. */
 +  return DCR_Unknown|0;
 +}
 +
 +/*****************
 + * 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(DBX(p), nArg>2 ? (const char**)(azArg+2) : 0);
 +  }else{
 +    for(ii=1; ii<nArg; ii++){
 +      sqlite3_create_module(DBX(p), azArg[ii], 0, 0);
      }
 -    x = zCmd!=0 ? system(zCmd) : 1;
 -    sqlite3_free(zCmd);
 -    if( x ) raw_printf(stderr, "System command returns %d\n", x);
 -  }else
 -#endif /* !defined(SQLITE_NOHAVE_SYSTEM) */
 -
 -  if( c=='s' && strncmp(azArg[0], "show", n)==0 ){
 -    static const char *azBool[] = { "off", "on", "trigger", "full"};
 -    const char *zOut;
 -    int i;
 -    if( nArg!=1 ){
 -      raw_printf(stderr, "Usage: .show\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    utf8_printf(p->out, "%12.12s: %s\n","echo",
 -                                  azBool[ShellHasFlag(p, SHFLG_Echo)]);
 -    utf8_printf(p->out, "%12.12s: %s\n","eqp", azBool[p->autoEQP&3]);
 -    utf8_printf(p->out, "%12.12s: %s\n","explain",
 -         p->mode==MODE_Explain ? "on" : p->autoExplain ? "auto" : "off");
 -    utf8_printf(p->out,"%12.12s: %s\n","headers", azBool[p->showHeader!=0]);
 -    if( p->mode==MODE_Column
 -     || (p->mode>=MODE_Markdown && p->mode<=MODE_Box)
 -    ){
 -      utf8_printf
 -        (p->out, "%12.12s: %s --wrap %d --wordwrap %s --%squote\n", "mode",
 -         modeDescr[p->mode], p->cmOpts.iWrap,
 -         p->cmOpts.bWordWrap ? "on" : "off",
 -         p->cmOpts.bQuote ? "" : "no");
 -    }else{
 -      utf8_printf(p->out, "%12.12s: %s\n","mode", modeDescr[p->mode]);
 -    }
 -    utf8_printf(p->out, "%12.12s: ", "nullvalue");
 -      output_c_string(p->out, p->nullValue);
 -      raw_printf(p->out, "\n");
 -    utf8_printf(p->out,"%12.12s: %s\n","output",
 -            strlen30(p->outfile) ? p->outfile : "stdout");
 -    utf8_printf(p->out,"%12.12s: ", "colseparator");
 -      output_c_string(p->out, p->colSeparator);
 -      raw_printf(p->out, "\n");
 -    utf8_printf(p->out,"%12.12s: ", "rowseparator");
 -      output_c_string(p->out, p->rowSeparator);
 -      raw_printf(p->out, "\n");
 -    switch( p->statsOn ){
 -      case 0:  zOut = "off";     break;
 -      default: zOut = "on";      break;
 -      case 2:  zOut = "stmt";    break;
 -      case 3:  zOut = "vmstep";  break;
 -    }
 -    utf8_printf(p->out, "%12.12s: %s\n","stats", zOut);
 -    utf8_printf(p->out, "%12.12s: ", "width");
 -    for (i=0;i<p->nWidth;i++) {
 -      raw_printf(p->out, "%d ", p->colWidth[i]);
 -    }
 -    raw_printf(p->out, "\n");
 -    utf8_printf(p->out, "%12.12s: %s\n", "filename",
 -                p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : "");
 -  }else
 +  }
 +  return DCR_Ok;
 +}
  
 -  if( c=='s' && strncmp(azArg[0], "stats", n)==0 ){
 -    if( nArg==2 ){
 -      if( strcmp(azArg[1],"stmt")==0 ){
 -        p->statsOn = 2;
 -      }else if( strcmp(azArg[1],"vmstep")==0 ){
 -        p->statsOn = 3;
 -      }else{
 -        p->statsOn = (u8)booleanValue(azArg[1]);
 -      }
 -    }else if( nArg==1 ){
 -      display_stats(p->db, p, 0);
 -    }else{
 -      raw_printf(stderr, "Usage: .stats ?on|off|stmt|vmstep?\n");
 -      rc = 1;
 +/*****************
 + * The .user command
 + * Because there is no help text for .user, it does its own argument validation.
 + */
 +CONDITION_COMMAND( user SQLITE_USER_AUTHENTICATION );
 +DISPATCHABLE_COMMAND( user ? 0 0 ){
 +  int rc;
 +  const char *usage
 +    = "Usage: .user SUBCOMMAND ...\n"
 +      "Subcommands are:\n"
 +      "   login USER PASSWORD\n"
 +      "   delete USER\n"
 +      "   add USER PASSWORD ISADMIN\n"
 +      "   edit USER PASSWORD ISADMIN\n"
 +    ;
 +  if( nArg<2 ){
 +  teach_fail:
 +    *pzErr = smprintf(usage);
 +    return DCR_SayUsage;
 +  }
 +  open_db(p, 0);
 +  if( strcmp(azArg[1],"login")==0 ){
 +    if( nArg!=4 ){
 +      goto teach_fail;
      }
 -  }else
 -
 -  if( (c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0)
 -   || (c=='i' && (strncmp(azArg[0], "indices", n)==0
 -                 || strncmp(azArg[0], "indexes", n)==0) )
 -  ){
 -    sqlite3_stmt *pStmt;
 -    char **azResult;
 -    int nRow, nAlloc;
 -    int ii;
 -    ShellText s;
 -    initText(&s);
 -    open_db(p, 0);
 -    rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
 +    rc = sqlite3_user_authenticate(DBX(p), azArg[2], azArg[3],
 +                                   strlen30(azArg[3]));
      if( rc ){
 -      sqlite3_finalize(pStmt);
 -      return shellDatabaseError(p->db);
 +      *pzErr = shellMPrintf(0,"Authentication failed for user %s\n", azArg[2]);
 +      return DCR_Error;
      }
 -
 -    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);
 -      }
 +  }else if( strcmp(azArg[1],"add")==0 ){
 +    if( nArg!=5 ){
 +      goto teach_fail;
      }
 -    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);
 +    rc = sqlite3_user_add(DBX(p), azArg[2], azArg[3], strlen30(azArg[3]),
 +                          booleanValue(azArg[4]));
 +    if( rc ){
 +      *pzErr = shellMPrintf(0,"User-Add failed: %d\n", rc);
 +      return DCR_Error;
      }
 -    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);
 +  }else if( strcmp(azArg[1],"edit")==0 ){
 +    if( nArg!=5 ){
 +      goto teach_fail;
      }
 -    while( sqlite3_step(pStmt)==SQLITE_ROW ){
 -      if( nRow>=nAlloc ){
 -        char **azNew;
 -        int n2 = nAlloc*2 + 10;
 -        azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2);
 -        shell_check_oom(azNew);
 -        nAlloc = n2;
 -        azResult = azNew;
 -      }
 -      azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 -      shell_check_oom(azResult[nRow]);
 -      nRow++;
 -    }
 -    if( sqlite3_finalize(pStmt)!=SQLITE_OK ){
 -      rc = shellDatabaseError(p->db);
 -    }
 -
 -    /* Pretty-print the contents of array azResult[] to the output */
 -    if( rc==0 && nRow>0 ){
 -      int len, maxlen = 0;
 -      int i, j;
 -      int nPrintCol, nPrintRow;
 -      for(i=0; i<nRow; i++){
 -        len = strlen30(azResult[i]);
 -        if( len>maxlen ) maxlen = len;
 -      }
 -      nPrintCol = 80/(maxlen+2);
 -      if( nPrintCol<1 ) nPrintCol = 1;
 -      nPrintRow = (nRow + nPrintCol - 1)/nPrintCol;
 -      for(i=0; i<nPrintRow; i++){
 -        for(j=i; j<nRow; j+=nPrintRow){
 -          char *zSp = j<nPrintRow ? "" : "  ";
 -          utf8_printf(p->out, "%s%-*s", zSp, maxlen,
 -                      azResult[j] ? azResult[j]:"");
 -        }
 -        raw_printf(p->out, "\n");
 -      }
 +    rc = sqlite3_user_change(DBX(p), azArg[2], azArg[3], strlen30(azArg[3]),
 +                             booleanValue(azArg[4]));
 +    if( rc ){
 +      *pzErr = shellMPrintf(0,"User-Edit failed: %d\n", rc);
 +      return DCR_Error;
      }
 -
 -    for(ii=0; ii<nRow; ii++) sqlite3_free(azResult[ii]);
 -    sqlite3_free(azResult);
 -  }else
 -
 -  /* 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");
 +  }else if( strcmp(azArg[1],"delete")==0 ){
 +    if( nArg!=3 ){
 +      goto teach_fail;
      }
 -    if( nArg>=2 ){
 -      sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", azArg[1]);
 -    }else{
 -      sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?");
 +    rc = sqlite3_user_delete(DBX(p), azArg[2]);
 +    if( rc ){
 +      *pzErr = shellMPrintf(0,"User-Delete failed: %d\n", rc);
 +      return DCR_Error;
      }
 -  }else
 +  }else{
 +    goto teach_fail;
 +  }
 +  return DCR_Ok;
 +}
  
 -#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,""             },
 -#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;
 +/*****************
 + * The .vars command
 + */
 +COLLECT_HELP_TEXT[
 +  ".vars ?OPTIONS? ...      Manipulate and display shell variables",
 +  "   clear ?NAMES?           Erase all or only given named variables",
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +  "   edit ?-e? NAME          Use edit() to create or alter variable NAME",
 +  "      With a -e option, the edited value is evaluated as a SQL expression.",
 +#endif
 +  "   list ?PATTERNS?         List shell variables table values",
 +  "      Alternatively, to list just some or all names: ls ?PATTERNS?",
 +  "   load ?FILE? ?NAMES?     Load some or all named variables from FILE",
 +  "      If FILE missing, empty or '~', it defaults to ~/sqlite_vars.sdb",
 +  "   save ?FILE? ?NAMES?     Save some or all named variables into FILE",
 +  "      If FILE missing, empty or '~', it defaults to ~/sqlite_vars.sdb",
 +  "   set NAME VALUE          Give shell variable NAME a value of VALUE",
 +  "      NAME must begin with a letter to be executable by .x, Other leading",
 +  "      characters have special uses. VALUE is the space-joined arguments.",
 +  "   unset ?NAMES?           Remove named variables(s) from variables table",
 +];
 +DISPATCHABLE_COMMAND( vars 2 1 0 ){
 +  DotCmdRC rv = DCR_Ok;
 +  sqlite3 *dbs = p->dbShell;
 +  const char *zCmd = (nArg>1)? azArg[1] : "ls";
 +  int rc = 0;
 +  int ncCmd = strlen30(zCmd);
  
 -    open_db(p, 0);
 -    zCmd = nArg>=2 ? azArg[1] : "help";
 +  if( *zCmd>'e' && ncCmd<2 ) return DCR_Ambiguous|1;
 +#define SUBCMD(scn) (strncmp(zCmd, scn, ncCmd)==0)
  
 -    /* The argument can optionally begin with "-" or "--" */
 -    if( zCmd[0]=='-' && zCmd[1] ){
 -      zCmd++;
 -      if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 -    }
 +  /* This could be done lazily, but with more code. */
 +  if( (dbs && ensure_shvars_table(dbs)!=SQLITE_OK) ){
 +    return DCR_Error;
 +  }else{
 +    if( ensure_shell_db(p)!=SQLITE_OK ) return DCR_Error;
 +    dbs = p->dbShell;
 +    assert(dbs!=0);
 +    if( ensure_shvars_table(dbs)!=SQLITE_OK ) return DCR_Error;
 +  }
  
 -    /* --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;
 +  /* .vars clear  and  .vars unset ?NAMES?
 +  **  Delete some or all key/value pairs from the shell variables table.
 +  **  Without any arguments, clear deletes them all and unset does nothing.
 +  */
 +  if( SUBCMD("clear") || SUBCMD("unset") ){
 +    if( (nArg>2 || zCmd[0]=='c') ){
 +      sqlite3_str *sbZap = sqlite3_str_new(dbs);
 +      char *zSql;
 +      sqlite3_str_appendf
 +        (sbZap, "DELETE FROM "SHVAR_TABLE_SNAME" WHERE key ");
 +      append_in_clause(sbZap,
 +                       (const char **)&azArg[2], (const char **)&azArg[nArg]);
 +      zSql = sqlite3_str_finish(sbZap);
 +      shell_check_oom(zSql);
 +      rc = sqlite3_exec(dbs, zSql, 0, 0, 0);
 +      sqlite3_free(zSql);
      }
 -
 -    /* convert testctrl text option to value. allow any unique prefix
 -    ** of the option name, or a numerical value. */
 -    n2 = strlen30(zCmd);
 -    for(i=0; i<ArraySize(aCtrl); i++){
 -      if( strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
 -        if( testctrl<0 ){
 -          testctrl = aCtrl[i].ctrlCode;
 -          iCtrl = i;
 -        }else{
 -          utf8_printf(stderr, "Error: ambiguous test-control: \"%s\"\n"
 -                              "Use \".testctrl --help\" for help\n", zCmd);
 -          rc = 1;
 -          goto meta_command_exit;
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +  }else if( SUBCMD("edit") ){
 +    ShellInState *psi = ISS(p);
 +    int ia = 2;
 +    int eval = 0;
 +    int edSet;
 +
 +    if( !INSOURCE_IS_INTERACTIVE(psi->pInSource) ){
 +      utf8_printf(STD_ERR, "Error: "
 +                  ".vars edit can only be used interactively.\n");
 +      return DCR_Error;
 +    }
 +    edSet = attempt_editor_set(psi, azArg[0], (nArg>2)? azArg[2] : 0 );
 +    if( edSet < 0 ) return DCR_Error;
 +    else ia += edSet;
 +    while( ia < nArg ){
 +      ParamTableUse ptu;
 +      char *zA = azArg[ia];
 +      char cf = (zA[0]=='-')? zA[1] : 0;
 +      if( cf!=0 && zA[2]==0 ){
 +        ++ia;
 +        switch( cf ){
 +        case 'e': eval = 1; continue;
 +        case 't': eval = 0; continue;
 +        default:
 +          utf8_printf(STD_ERR, "Error: bad .vars edit option: %s\n", zA);
 +          return DCR_Error;
          }
        }
 -    }
 -    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);
 +      ptu = classify_param_name(zA);
 +      if( ptu!=PTU_Script ){
 +        utf8_printf(STD_ERR,
 +                    "Error: %s cannot be a shell variable name.\n", zA);
 +        return DCR_Error;
 +      }
 +      rc = edit_one_kvalue(dbs, zA, eval, ptu, psi->zEditor);
 +      ++ia;
 +      if( rc!=0 ) return DCR_Error;
 +    }
 +#endif
 +  }else if( SUBCMD("list") || SUBCMD("ls") ){
 +    int nTailArgs = nArg - 1 - (nArg>1);
 +    char **pzTailArgs = azArg + 1 + (nArg>1);
 +    list_pov_entries(p, PTU_Script, zCmd[1]=='s', pzTailArgs, nTailArgs);
 +  }else if( SUBCMD("load") ){
 +    rc = kv_pairs_load(dbs, PTU_Script, (const char **)azArg+1, nArg-1);
 +  }else if( SUBCMD("save") ){
 +    rc = kv_pairs_save(dbs, PTU_Script, (const char **)azArg+1, nArg-1);
 +  }else if( SUBCMD("set") ){
 +    ParamTableUse ptu;
 +    if( nArg<4 ) return DCR_Missing;
 +    ptu = classify_param_name(azArg[2]);
 +    if( ptu!=PTU_Script ){
 +      utf8_printf(STD_ERR,
 +                  "Error: %s is not a valid shell variable name.\n", azArg[2]);
 +      rc = 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;
 -
 -        /* 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;
 +      rc = shvar_set(dbs, azArg[2], &azArg[3], &azArg[nArg]);
 +      if( rc!=SQLITE_OK ){
 +        utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(dbs));
 +        rc = 1;
 +      }
 +    }
 +  }else{
 +    showHelp(ISS(p)->out, "vars", p);
 +    return DCR_CmdErred;
 +  }
 +  return DCR_Ok | (rv!=0) | (rc!=0);
 +#undef SUBCMD
 +}
  
 -        /* 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;
 +/*****************
 + * 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 ){
 +  FILE *out = ISS(p)->out;
 +  utf8_printf(out, "SQLite %s %s\n" /*extra-version-info*/,
 +              sqlite3_libversion(), sqlite3_sourceid());
 +#if SQLITE_HAVE_ZLIB
 +  utf8_printf(out, "zlib version %s\n", zlibVersion());
 +#endif
 +#define CTIMEOPT_VAL_(opt) #opt
 +#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt)
 +#if defined(__clang__) && defined(__clang_major__)
 +  utf8_printf(out, "clang-" CTIMEOPT_VAL(__clang_major__) "."
 +              CTIMEOPT_VAL(__clang_minor__) "."
 +              CTIMEOPT_VAL(__clang_patchlevel__) "\n");
 +#elif defined(_MSC_VER)
 +  utf8_printf(out, "msvc-" CTIMEOPT_VAL(_MSC_VER) "\n");
 +#elif defined(__GNUC__) && defined(__VERSION__)
 +  utf8_printf(out, "gcc-" __VERSION__ "\n");
 +#endif
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( vfsinfo ? 1 2 ){
 +  const char *zDbName = nArg==2 ? azArg[1] : "main";
 +  sqlite3_vfs *pVfs = 0;
 +  if( DBX(p) ){
 +    sqlite3_file_control(DBX(p), zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs);
 +    if( pVfs ){
 +      FILE *out = ISS(p)->out;
 +      utf8_printf(out, "vfs.zName      = \"%s\"\n", pVfs->zName);
 +      raw_printf(out, "vfs.iVersion   = %d\n", pVfs->iVersion);
 +      raw_printf(out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
 +      raw_printf(out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
 +    }
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( vfslist ? 1 1 ){
 +  sqlite3_vfs *pVfs;
 +  sqlite3_vfs *pCurrent = 0;
 +  FILE *out = ISS(p)->out;
 +  if( DBX(p) ){
 +    sqlite3_file_control(DBX(p), "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent);
 +  }
 +  for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){
 +    utf8_printf(out, "vfs.zName      = \"%s\"%s\n", pVfs->zName,
 +                pVfs==pCurrent ? "  <--- CURRENT" : "");
 +    raw_printf(out, "vfs.iVersion   = %d\n", pVfs->iVersion);
 +    raw_printf(out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
 +    raw_printf(out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
 +    if( pVfs->pNext ){
 +      raw_printf(out, "-----------------------------------\n");
 +    }
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( vfsname ? 0 0 ){
 +  const char *zDbName = nArg==2 ? azArg[1] : "main";
 +  char *zVfsName = 0;
 +  if( DBX(p) ){
 +    sqlite3_file_control(DBX(p), zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName);
 +    if( zVfsName ){
 +      utf8_printf(ISS(p)->out, "%s\n", zVfsName);
 +      sqlite3_free(zVfsName);
 +    }
 +  }
 +  return DCR_Ok;
 +}
  
 -        /* 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;
 +/*****************
 + * The .width and .wheretrace commands
 + * The .wheretrace command has no help.
 + */
 +static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths){
 +  int j;
 +  p->numWidths = nWidths;
 +  p->pSpecWidths = realloc(p->pSpecWidths, (nWidths+1)*sizeof(int)*2);
 +  if( nWidths>0 ){
 +    shell_check_oom(p->pSpecWidths);
 +    p->pHaveWidths = &p->pSpecWidths[nWidths];
 +    for(j=0; j<nWidths; j++){
 +      p->pSpecWidths[j] = (int)integerValue(azWidths[j]);
 +      p->pHaveWidths[j] = 0;
 +    }
 +  }else p->pHaveWidths = p->pSpecWidths;
 +}
 +COLLECT_HELP_TEXT[
 +  ".width NUM1 NUM2 ...     Set minimum column widths for columnar output",
 +  "   Negative values right-justify",
 +];
 +DISPATCHABLE_COMMAND( width ? 1 0 ){
 +  setColumnWidths(p, azArg+1, nArg-1);
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( wheretrace ? 1 2 ){
 +  unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
 +  sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &x);
 +  return DCR_Ok;
 +}
  
 -        /* 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;
 +/*****************
 + * The .x, .read and .eval commands
 + * These are together because they share some function and implementation.
 + */
  
 -        /* 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;
 +COLLECT_HELP_TEXT[
 +  ".eval ?ARGS?             Process each ARG's content as shell input.",
 +  ".read FILE               Read input from FILE",
 +  "   If FILE begins with \"|\", it is a command that generates the input.",
 +  ".x ?OBJS or FLAGS?  ...  Excecute content of objects as shell input",
 +  "   FLAGS can be any of {-k -s -f} specifying what subsequent arguments are.",
 +  "   Arguments after -k are keys in a key/value table kept by the shell DB,",
 +  "   for which the object content to be executed is the corresponding value.",
 +  "   Arguments after -f name either files (or pipes), which are to be read",
 +  "   and the content executed. Input pipe names begin with '|'; the rest is",
 +  "   an OS-shell command which can be run by the OS shell to produce output.",
 +  "   Arguments after -s are strings, content of which is to be executed.",
 +  "   The default in effect for arguments prior to any FLAG is -k .",
 +  "   Arguments are executed in order until one fails.",
 +];
 +
 +/* Return an allocated string with trailing whitespace trimmed except
 + * for a trailing newline. If empty (or OOM), return 0. Otherwise, the
 + * caller must eventually pass the return to sqlite3_free().
 + */
 +static char *zPrepForEval(const char *zVal, int ntc){
 +  int ixNewline = 0;
 +  char c;
 +  while( ntc>0 && IsSpace(c = zVal[ntc-1]) ){
 +    if( c=='\n' ) ixNewline = ntc-1;
 +    --ntc;
 +  }
 +  if( ntc>0 ){
 +    /* The trailing newline (or some other placeholder) is important
 +     * because one (or some other character) will likely be put in
 +     * its place during process_input() line/group handling, along
 +     * with a terminating NUL character. Without it, the NULL could
 +     * land past the end of the allocation made just below.
 +     */
 +    int nle = ixNewline>0;
 +    return smprintf( "%.*s%s", ntc, zVal, &"\n"[nle] );
 +  }else{
 +    return 0;
 +  }
 +}
  
 -        /* sqlite3_test_control(sqlite3*) */
 -        case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS:
 -          rc2 = sqlite3_test_control(testctrl, p->db);
 -          isOk = 3;
 -          break;
 +/* Evaluate a string as input to the CLI.
 + * zName is the name to be given to the source for error reporting.
 + * Return usual dot command return codes as filtered by process_input().
 + * No provision is made for error emission because, presumably, that
 + * has been done by whatever dot commands or SQL execution is invoked.
 + */
 +static DotCmdRC shellEvalText(char *zIn, const char *zName, ShellExState *psx){
 +  DotCmdRC rv;
 +  ShellInState *psi = ISS(psx);
 +  InSource inRedir
 +    = INSOURCE_STR_REDIR(zIn, zName, psi->pInSource);
 +  psi->pInSource = &inRedir;
 +  rv = process_input(psi);
 +  psi->pInSource = inRedir.pFrom;
 +  return rv;
 +}
  
 -        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;
 +DISPATCHABLE_COMMAND( eval 3 1 0 ){
 +  DotCmdRC rv = DCR_Ok;
 +  int ia = 1;
 +  int nErrors = 0;
 +  while( ia < nArg ){
 +    char *zA = azArg[ia++];
 +    int nc = strlen30(zA);
 +    char *zSubmit = zPrepForEval(zA, nc);
 +    if( zSubmit ){
 +      char zName[] = "eval arg[999]";
 +      sqlite3_snprintf(sizeof(zName), zName,"eval arg[%d]", ia-1);
 +      rv = shellEvalText(zSubmit, zName, p);
 +      sqlite3_free(zSubmit);
 +      if( rv<DCR_ArgIxMask ){
 +        nErrors += (rv & DCR_Error);
 +        /* Handle DCR_Return, DCR_Exit or DCR_Abort */
 +        if( rv>DCR_Error ) break;
 +      }
 +    }
 +  }
 +  rv |= (nErrors>0);
 +  /* If error to be returned, indicate that complaining about it is done. */
 +  return (rv==DCR_Error)? DCR_CmdErred : rv;
 +}
 +
 +DISPATCHABLE_COMMAND( read 3 2 2 ){
 +  DotCmdRC rc = DCR_Ok;
 +  FILE *inUse = 0;
 +  int (*fCloser)(FILE *) = 0;
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +  if( azArg[1][0]=='|' ){
 +#ifdef SQLITE_OMIT_POPEN
 +    *pzErr = smprintf("pipes are not supported in this OS\n");
 +    rc = DCR_Error;
 +    /* p->out = STD_OUT; This was likely not needed. To be investigated. */
 +#else
 +    inUse = popen(azArg[1]+1, "r");
 +    if( inUse==0 ){
 +      *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
 +      rc = DCR_Error;
 +    }else{
 +      fCloser = pclose;
 +    }
 +#endif
 +  }else if( (inUse = openChrSource(azArg[1]))==0 ){
 +    *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
 +    rc = DCR_Error;
 +  }else{
 +    fCloser = fclose;
 +  }
 +  if( inUse!=0 ){
 +    InSource inSourceRedir
 +      = INSOURCE_FILE_REDIR(inUse, azArg[1], ISS(p)->pInSource);
 +    ISS(p)->pInSource = &inSourceRedir;
 +    rc = process_input(ISS(p));
 +    /* If error(s) occured during process, leave complaining to them. */
 +    if( rc==DCR_Error ) rc = DCR_CmdErred;
 +    assert(fCloser!=0);
 +    fCloser(inUse);
 +    ISS(p)->pInSource = inSourceRedir.pFrom;
 +  }
 +  return rc;
 +}
  
 -        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;
 +DISPATCHABLE_COMMAND( x ? 1 0 ){
 +  int ia, nErrors = 0;
 +  sqlite3_stmt *pStmt = 0;
 +  sqlite3 *dbs = p->dbShell;
 +  DotCmdRC rv = DCR_Ok;
 +  enum { AsVar, AsString, AsFile } evalAs = AsVar;
 +
 +  for( ia=1; ia<nArg && nErrors==0; ++ia ){
 +    char *zSubmit = 0;
 +    const char *zOpt = azArg[ia];
 +    if ( *zOpt == '-' ){
 +      static const char *azOpts[] = { "k", "s", "f" };
 +      int io = ArraySize(azOpts);
 +      while( io > 0 ){
 +        if( optionMatch(zOpt, azOpts[--io]) ){
 +          evalAs = io;
 +          zOpt = 0;
            break;
          }
 -
 -#ifdef YYCOVERAGE
 -        case SQLITE_TESTCTRL_PARSER_COVERAGE: {
 -          if( nArg==2 ){
 -            sqlite3_test_control(testctrl, p->out);
 -            isOk = 3;
 -          }
 -          break;
 +      }
 +      if( zOpt==0 ) continue;
 +    }
 +    switch( evalAs ){
 +    case AsVar:
 +      if( pStmt==0 ){
 +        int rc;
 +        if( dbs==0 || !shvars_table_exists(dbs) ){
 +          utf8_printf(STD_ERR,
 +                      "\".x vname\" can only be done after .var set ... .\n");
 +          return DCR_Error;
          }
 -#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;
 +        rc = sqlite3_prepare_v2(dbs, "SELECT value FROM "SHVAR_TABLE_SNAME
 +                                " WHERE key=$1 AND uses="SPTU_Script,
 +                                -1, &pStmt, 0);
 +        if( rc!=SQLITE_OK ){
 +          utf8_printf(STD_ERR, SHVAR_TABLE_SNAME" is wrongly created.\n");
 +          return DCR_Error;
          }
 -#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;
 +      }
 +      if( isalpha(azArg[ia][0]) ){
 +        int rc = sqlite3_reset(pStmt);
 +        rc = sqlite3_bind_text(pStmt, 1, azArg[ia], -1, 0);
 +        rc = sqlite3_step(pStmt);
 +        if( rc==SQLITE_ROW ){
 +          ShellInState *psi = ISS(p);
 +          const unsigned char *zValue = sqlite3_column_text(pStmt, 0);
 +          int nb = sqlite3_column_bytes(pStmt, 0);
 +          zSubmit = zPrepForEval((const char *)zValue, nb);
 +          sqlite3_reset(pStmt); /* End the script read to unlock DB. */
 +          if( zSubmit ){
 +            rv = shellEvalText(zSubmit, azArg[ia], p);
 +            sqlite3_free(zSubmit);
 +          }else{
 +            continue; /* All white (or OOM), ignore. */
            }
 -          break;
 +        }else{
 +          utf8_printf(STD_ERR,
 +                      "Skipping var '%s' (not set and executable.)\n",
 +                      azArg[ia]);
 +          ++nErrors;
 +        }
 +      }else{
 +        utf8_printf(STD_ERR,
 +                    "Skipping badly named %s. Run \".help x\"\n", azArg[ia]);
 +        ++nErrors;
 +      }
 +      break;
 +    case AsString:
 +      {
 +        zSubmit = zPrepForEval(zOpt, strlen30(zOpt));
 +        if( zSubmit ){
 +          char zName[] = "x arg[999]";
 +          sqlite3_snprintf(sizeof(zName), zName,"x arg[%d]", ia);
 +          rv = shellEvalText(zSubmit, zName, p);
 +          sqlite3_free(zSubmit);
 +        }
 +      }
 +      break;
 +    case AsFile:
 +      {
 +        char *av[] = {"read", (char*)zOpt};
 +        rv = readCommand(av, ArraySize(av), p, pzErr);
        }
 +      break;
      }
 -    if( isOk==0 && iCtrl>=0 ){
 -      utf8_printf(p->out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
 -      rc = 1;
 -    }else if( isOk==1 ){
 -      raw_printf(p->out, "%d\n", rc2);
 -    }else if( isOk==2 ){
 -      raw_printf(p->out, "0x%08x\n", rc2);
 +    if( rv<DCR_ArgIxMask ){
 +      nErrors += (rv & DCR_Error);
 +      /* Handle DCR_Return, DCR_Exit or DCR_Abort */
 +      if( rv>DCR_Error ) break;
 +    }else{
 +      ++nErrors;
 +      rv = DCR_Error;
      }
 -  }else
 -#endif /* !defined(SQLITE_UNTESTABLE) */
 +  }
 +  sqlite3_finalize(pStmt);
 +  rv |= (nErrors>0);
 +  /* If error to be returned, indicate that complaining about it is done. */
 +  return (rv==DCR_Error)? DCR_CmdErred : rv;
 +}
  
 -  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);
 +/* End of published, standard dot-command implementation functions
 +COMMENT  Build-time overrides of above dot-commands or new dot-commands may be
 +COMMENT  incorporated into shell.c via: -it COMMAND_CUSTOMIZE=<customize source>
 +COMMENT  where <customize source> names a file using the above methodology to
 +COMMENT  define new or altered dot-commands and their help text.
 +*/
 +INCLUDE( COMMAND_CUSTOMIZE );
 +
 +COMMENT  This help text is set seperately from dot-command definition section
 +COMMENT  for the always-built-in, non-customizable commands with visible help.
 +COLLECT_HELP_TEXT[
 +  ".exit ?CODE?             Exit this program with return-code CODE or 0",
 +  ".quit                    Exit this program",
 +];
 +
 +static void DotCommand_dtor(DotCommand *);
 +static const char * DotCommand_name(DotCommand *);
 +static const char * DotCommand_help(DotCommand *, const char *);
 +static DotCmdRC
 +  DotCommand_argsCheck(DotCommand *, char **, int nArgs, char *azArgs[]);
 +static DotCmdRC
 +  DotCommand_execute(DotCommand *, ShellExState *, char **, int, char *[]);
 +
 +static VTABLE_NAME(DotCommand) dot_cmd_VtabBuiltIn = {
 +  DotCommand_dtor,
 +  DotCommand_name,
 +  DotCommand_help,
 +  DotCommand_argsCheck,
 +  DotCommand_execute
 +};
 +
 +/* Define and populate command dispatch table. */
 +static struct CommandInfo {
 +  VTABLE_NAME(DotCommand) *mcVtabBuiltIn;
 +  const char * cmdName;
 +  DotCmdRC (*cmdDoer)(char *azArg[], int nArg,
 +                               ShellExState *, char **pzErr);
 +  unsigned char minLen, minArgs, maxArgs;
 +  const char *azHelp[2]; /* primary and secondary help text */
 +  void * pCmdData;
 +  } command_table[] = {
 +  COMMENT Emit the dispatch table entries generated and collected above.
- #define DOT_CMD_INFO(cmd, nlenMin, minArgs, maxArgs, ht0, ht1 ) \
-   { &dot_cmd_VtabBuiltIn, #cmd, cmd ## Command, \
-     nlenMin, minArgs, maxArgs, { ht0, ht1 }, 0 \
-   }
++#define DOT_CMD_INFO(cmd, nlenMin, minArgs, maxArgs) \
++  &dot_cmd_VtabBuiltIn, #cmd, cmd ## Command, nlenMin, minArgs, maxArgs
 +  EMIT_DOTCMD_INIT(2);
 +#undef DOT_CMD_INFO
-   { 0, 0, 0, 0, ~0, ~0, {0,0}, 0 }
++  { 0, 0, 0, 0, (u8)~0, (u8)~0, {0,0}, 0 }
 +};
 +static unsigned numCommands
 +  = sizeof(command_table)/sizeof(struct CommandInfo) - 1;
 +
 +static DotCommand *builtInCommand(int ix){
-   if( ix<0 || ix>=numCommands ) return 0;
++  if( ix<0 || (unsigned)ix>=numCommands ) return 0;
 +  return (DotCommand *)&command_table[ix];
 +}
 +
 +static void DotCommand_dtor(DotCommand *pMe){
 +  UNUSED_PARAMETER(pMe);
 +}
 +
 +static const char * DotCommand_name(DotCommand *pMe){
 +  return ((struct CommandInfo *)pMe)->cmdName;
 +}
 +
 +static const char * DotCommand_help(DotCommand *pMe, const char * zWhat){
 +  struct CommandInfo *pci = (struct CommandInfo *)pMe;
 +  if( zWhat==0 ) return pci->azHelp[0];
 +  if( *zWhat==0 ) return pci->azHelp[1];
 +  else return 0;
 +}
 +
 +static DotCmdRC
 +  DotCommand_argsCheck(DotCommand *pMe,
 +                        char **pzErrMsg, int nArgs, char *azArgs[]){
 +  struct CommandInfo *pci = (struct CommandInfo *)pMe;
 +  UNUSED_PARAMETER(azArgs);
 +  if( pci->minArgs > nArgs ){
 +    if( pzErrMsg ){
 +      *pzErrMsg = smprintf("Too few arguments for \".%s\", need %d\n",
 +                           azArgs[0], pci->minArgs-1);
 +    }
 +    return DCR_TooFew;
 +  }else if( pci->maxArgs > 0 && pci->maxArgs < nArgs ){
 +    if( pzErrMsg ){
 +      *pzErrMsg = smprintf("Too many arguments for \".%s\", over %d\n",
 +                           azArgs[0], pci->maxArgs-1);
 +    }
 +    return DCR_TooMany;
 +  }else return DCR_Ok;
 +}
 +
 +static DotCmdRC
 +  DotCommand_execute(DotCommand *pMe, ShellExState *pssx,
 +                      char **pzErrMsg, int nArgs, char *azArgs[]){
 +  return (((struct CommandInfo *)pMe)->cmdDoer)(azArgs, nArgs, pssx, pzErrMsg);
 +}
 +
 +/*****************
 +** DotCommand iteration by name match, used by the .help dot-command.
 +** DotCommands, or their ad-hoc stand-ins, having matching names are produced
 +** in lexical order, with the iterator indicating which has been produced.
 +** If .zAdhocHelpName == 0, it is a regular DotCommand. Otherwise, the
 +** ".unknown" DotCommand is returned, whose help() method is to be used.
 +** Any returned CmdMatchIter must eventually be passed to freeCmdMatchIter().
 +*/
 +typedef struct CmdMatchIter {
 +  ShellExState *psx;
 +  /* 0 indicates prepared statement; non-0 is the glob pattern. */
 +  const char *zPattern;
 +  union {
 +    DotCommand *pDotCmd;
 +#if SHELL_DYNAMIC_EXTENSION
 +    sqlite3_stmt *stmt;
 +#endif
-   };
++  } cursor;
 +#if SHELL_DYNAMIC_EXTENSION
 +  char *zAdhocHelpText; /* registered extension ad-hoc help */
 +#endif
 +} CmdMatchIter;
 +
 +/* Release resources held by the iterator and clear it. */
 +static void freeCmdMatchIter(CmdMatchIter *pMMI){
 +  if( pMMI->zPattern!=0 ){
 +    sqlite3_free((void *)pMMI->zPattern);
 +    pMMI->zPattern = 0;
-     pMMI->pDotCmd = 0;
++    pMMI->cursor.pDotCmd = 0;
 +  }
 +#if SHELL_DYNAMIC_EXTENSION
 +  else{
-     sqlite3_finalize(pMMI->stmt);
-     pMMI->stmt = 0;
++    sqlite3_finalize(pMMI->cursor.stmt);
++    pMMI->cursor.stmt = 0;
 +  }
 +  sqlite3_free(pMMI->zAdhocHelpText);
 +  pMMI->zAdhocHelpText = 0;
 +#endif
 +}
 +
 +/* Prepare an iterator that will produce a sequence of DotCommand
 + * pointers whose referents' names match the given cmdFragment. 
 + * Return how many will match (if iterated upon return.) */
 +static int findMatchingDotCmds(const char *cmdFragment,
 +                                CmdMatchIter *pMMI,
 +                                ShellExState *psx){
 +  CmdMatchIter mmi = { psx, 0, 0 };
 +  int rv = 0;
 +#if SHELL_DYNAMIC_EXTENSION
 +  if( ISS(psx)->bDbDispatch ){
 +    sqlite3_stmt *stmtCount = 0;
 +    /* Prepare rv.stmt to yield results glob-matching cmdFragment. */
 +    static const char * const zSqlIter =
 +      "SELECT name, extIx, cmdIx, help FROM "SHELL_HELP_VIEW" "
 +      "WHERE name glob (?||'*') ORDER BY name";
 +    static const char * const zSqlCount =
 +      "SELECT count(*) FROM "SHELL_HELP_VIEW" "
 +      "WHERE name glob (?||'*')";
 +    if( pMMI ){
-       sqlite3_prepare_v2(psx->dbShell, zSqlIter, -1, &mmi.stmt, 0);
-       sqlite3_bind_text(mmi.stmt, 1, cmdFragment? cmdFragment : "", -1, 0);
++      sqlite3_prepare_v2(psx->dbShell, zSqlIter, -1, &mmi.cursor.stmt, 0);
++      sqlite3_bind_text(mmi.cursor.stmt, 1, cmdFragment? cmdFragment:"", -1, 0);
 +    }
 +    sqlite3_prepare_v2(psx->dbShell, zSqlCount, -1, &stmtCount, 0);
 +    sqlite3_bind_text(stmtCount, 1, cmdFragment? cmdFragment : "", -1, 0);
 +    if( SQLITE_ROW==sqlite3_step(stmtCount) ){
 +      rv = sqlite3_column_int(stmtCount, 0);
 +    }else assert(0);
 +    sqlite3_finalize(stmtCount);
    }else
 +#endif
 +  {
 +    int i = 0;
 +    mmi.zPattern = smprintf("%s*", cmdFragment? cmdFragment : "");
 +    shell_check_oom((void *)mmi.zPattern);
  
 -  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;
 +    struct CommandInfo *pCI = command_table;
-     mmi.pDotCmd = (DotCommand *)command_table;
++    mmi.cursor.pDotCmd = (DotCommand *)command_table;
 +    while( pCI<command_table+numCommands ){
 +      if( sqlite3_strglob(mmi.zPattern, pCI->cmdName)==0 ) ++rv;
 +      ++pCI;
      }
 -  }else
 +  }
 +  if( pMMI ) *pMMI = mmi;
 +  else freeCmdMatchIter(&mmi);
 +  return rv;
 +}
  
 -#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;
 -        }
 +/* Produce the next DotCommand pointer from the iterator, or 0 if no next. */
 +static DotCommand * nextMatchingDotCmd(CmdMatchIter *pMMI){
 +  DotCommand *rv = 0;
 +#if SHELL_DYNAMIC_EXTENSION
 +  if( pMMI->zPattern==0 ){
-     int rc = sqlite3_step(pMMI->stmt);
++    int rc = sqlite3_step(pMMI->cursor.stmt);
 +    if( rc==SQLITE_ROW ){
 +      /* name, extIx, cmdIx, help */
-       int extIx = sqlite3_column_int(pMMI->stmt, 1);
-       int cmdIx = sqlite3_column_int(pMMI->stmt, 2);
++      int extIx = sqlite3_column_int(pMMI->cursor.stmt, 1);
++      int cmdIx = sqlite3_column_int(pMMI->cursor.stmt, 2);
 +      ShellInState *psi = ISS(pMMI->psx);
 +      sqlite3_free(pMMI->zAdhocHelpText);
 +      if( cmdIx>=0 ){
 +        pMMI->zAdhocHelpText = 0;
 +        return command_by_index(psi, extIx, cmdIx);
        }else{
-         const unsigned char *zHT = sqlite3_column_text(pMMI->stmt, 3);
 -        output_file_close(p->traceOut);
 -        p->traceOut = output_file_open(azArg[1], 0);
++        const unsigned char *zHT = sqlite3_column_text(pMMI->cursor.stmt, 3);
 +        assert(psi->pUnknown!=0);
 +        assert(extIx<psi->numExtLoaded && extIx>0);
-         if( zHT==0 ) zHT = sqlite3_column_text(pMMI->stmt, 0);
++        if( zHT==0 ) zHT = sqlite3_column_text(pMMI->cursor.stmt, 0);
 +        pMMI->zAdhocHelpText = sqlite3_mprintf("%s", zHT);
 +        return psi->pShxLoaded[extIx].pUnknown;
        }
 -    }
 -    if( p->traceOut==0 ){
 -      sqlite3_trace_v2(p->db, 0, 0, 0);
      }else{
-       sqlite3_finalize(pMMI->stmt);
-       pMMI->stmt = 0;
 -      if( mType==0 ) mType = SQLITE_TRACE_STMT;
 -      sqlite3_trace_v2(p->db, mType, sql_trace_callback, p);
++      sqlite3_finalize(pMMI->cursor.stmt);
++      pMMI->cursor.stmt = 0;
      }
    }else
 -#endif /* !defined(SQLITE_OMIT_TRACE) */
 -
 -#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_VIRTUALTABLE)
 -  if( c=='u' && strncmp(azArg[0], "unmodule", n)==0 ){
 -    int ii;
 -    int lenOpt;
 -    char *zOpt;
 -    if( nArg<2 ){
 -      raw_printf(stderr, "Usage: .unmodule [--allexcept] NAME ...\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    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
 +  {
-     struct CommandInfo *pCI = (struct CommandInfo *)(pMMI->pDotCmd);
++    struct CommandInfo *pCI = (struct CommandInfo *)(pMMI->cursor.pDotCmd);
 +    assert(pCI>=command_table && pCI<=command_table+numCommands);
 +    while( pCI<command_table+numCommands ){
-       if( sqlite3_strglob(pMMI->zPattern, pCI->cmdName)==0 ) rv = pMMI->pDotCmd;
-       pMMI->pDotCmd = (DotCommand *)(++pCI);
++      if( sqlite3_strglob(pMMI->zPattern, pCI->cmdName)==0 ){
++      rv = pMMI->cursor.pDotCmd;
+       }
++      pMMI->cursor.pDotCmd = (DotCommand *)(++pCI);
 +      if( rv!=0 ) break;
      }
 +  }
 +  return rv;
 +}
 +
 +/*****************
 +** DotCommand lookup
 +**
 +** For the non-extended or non-extensible shell, this function does
 +** a binary search of the fixed list of dot-command info structs.
 +** For an extended shell, it queries the shell's DB. Either way,
 +** this function returns a DotCommand pointer if one can be found
 +** with an adequate match for the given name. Here, "adequate" may
 +** vary according to whether shell extensions have been loaded. If
 +** not, the match must be for as many characters as set within the
 +** above CommandInfo array (set via DISPATCHABLE_COMMAND macro call.)
 +** If shell extensions are loaded, the match must be long enough to
 +** result in a unique lookup.
 +*/
 +DotCommand *findDotCommand(const char *cmdName, ShellExState *psx,
 +                             /* out */ int *pnFound){
 +  if( pnFound ) *pnFound = 0;
 +#if SHELL_DYNAMIC_EXTENSION
 +  if( ISS(psx)->bDbDispatch ){
 +    int rc;
 +    int extIx = -1, cmdIx = -1, nf = 0;
 +    sqlite3_stmt *pStmt = 0;
 +    const char *zSql = "SELECT COUNT(*), extIx, cmdIx"
 +      " FROM "SHELL_DISP_VIEW" WHERE name glob (?||'*')";
 +    rc = sqlite3_prepare_v2(psx->dbShell, zSql, -1, &pStmt, 0);
 +    sqlite3_bind_text(pStmt, 1, cmdName, -1, 0);
 +    rc = sqlite3_step(pStmt);
 +    nf = sqlite3_column_int(pStmt, 0);
 +    extIx = sqlite3_column_int(pStmt, 1);
 +    cmdIx = sqlite3_column_int(pStmt, 2);
 +    sqlite3_finalize(pStmt);
 +    if( rc!= SQLITE_ROW ) return 0;
 +    if( pnFound ) *pnFound = nf;
 +    if( nf!=1 ) return 0; /* Future: indicate collisions if > 1 */
 +    return (cmdIx<0)? 0 : command_by_index(ISS(psx), extIx, cmdIx);
    }else
  #endif
 +  {
 +    int cmdLen = strlen30(cmdName);
 +    struct CommandInfo *pci = 0;
 +    int ixb = 0, ixe = numCommands-1;
 +    while( ixb <= ixe ){
 +      int ixm = (ixb+ixe)/2;
 +      int md = strncmp(cmdName, command_table[ixm].cmdName, cmdLen);
 +      if( md>0 ){
 +        ixb = ixm+1;
 +      }else if( md<0 ){
 +        ixe = ixm-1;
 +      }else{
 +        /* Have a match, see whether it's ambiguous. */
 +        if( command_table[ixm].minLen > cmdLen ){
 +          if( (ixm>0
 +               && !strncmp(cmdName, command_table[ixm-1].cmdName, cmdLen))
 +              ||
 +              (ixm<ixe
 +               && !strncmp(cmdName, command_table[ixm+1].cmdName, cmdLen)) ){
 +            /* Yes, a neighbor matches too. */
 +            if( pnFound ) *pnFound = 2; /* could be more, but >1 suffices */
 +            return 0;
 +          }
 +        }
 +        pci = &command_table[ixm];
 +        if( pnFound ) *pnFound = 1;
 +        break;
 +      }
 +    }
 +    if( pnFound && pci ) *pnFound = 1;
 +    return (DotCommand *)pci;
 +  }
 +}
  
 -#if SQLITE_USER_AUTHENTICATION
 -  if( c=='u' && strncmp(azArg[0], "user", n)==0 ){
 -    if( nArg<2 ){
 -      raw_printf(stderr, "Usage: .user SUBCOMMAND ...\n");
 +/*
 +** Given a DotCommand, desired help level,
 +** ( possibly retreived ad-hoc help text for extensible shell, )
 +** and an optional all-text search pattern, then
 +**   when level==0 and primary help available, output it
 +**   when level==1 and primary or secondary help available, output it
 +**   when level==2 and any help text matches pattern, output it
 +**   when level>2 or no pattern: output all help text
 +** If cLead==0, anything meeting above criteria is output. Otherwise, output
 +** is restricted to those commands whose primary help begins with cLead.
 +** Return 1 if anything output, else 0.
 +*/
 +static int putSelectedCmdHelp(DotCommand *pmc, int iLevel, char cLead,
 +#if SHELL_DYNAMIC_EXTENSION
 +                              const char *zHelpText,
 +#endif
 +                              FILE *out, const char *zSearch){
 +  int rc = 0;
 +  assert(pmc!=0);
 +#if SHELL_DYNAMIC_EXTENSION
 +  if( zHelpText ){
 +    const char *zHT = zHelpText+1; /* skip over classifier */
 +    if( cLead && *zHelpText!= cLead ) return 0;
 +    if( *zHelpText==0 ) return 0;
 +    const char *zLE = zHT;
 +    switch( iLevel ){
 +    case 0:
 +      while( *zLE && *zLE++!='\n' ) {}
 +      utf8_printf(out,".%.*s", (int)(zLE-zHT), zHT);
 +      rc = 1;
 +      break;
 +    case 2:
 +      if( zSearch ){
 +        if( !sqlite3_strlike(zSearch, zHT, 0) ) break;
 +      }
 +      /* else fall thru */
 +    case 1:
 +    default:
 +      utf8_printf(out,".%s", zHT);
        rc = 1;
 -      goto meta_command_exit;
      }
 -    open_db(p, 0);
 -    if( strcmp(azArg[1],"login")==0 ){
 -      if( nArg!=4 ){
 -        raw_printf(stderr, "Usage: .user login USER PASSWORD\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3],
 -                                     strlen30(azArg[3]));
 -      if( rc ){
 -        utf8_printf(stderr, "Authentication failed for user %s\n", azArg[2]);
 -        rc = 1;
 -      }
 -    }else if( strcmp(azArg[1],"add")==0 ){
 -      if( nArg!=5 ){
 -        raw_printf(stderr, "Usage: .user add USER PASSWORD ISADMIN\n");
 +  }else
 +#endif
 +  {
 +    const char *zHTp = pmc->pMethods->help(pmc, 0);
 +    const char *zHTs = pmc->pMethods->help(pmc, "");
 +    if( !zHTp && !zHTs ) return 0;
 +    if( cLead && zHTp && *zHTp!= cLead ) return 0;
 +    switch( iLevel ){
 +    case 0:
 +    case 1:
 +      if( zHTp ){
 +        utf8_printf(out, HELP_TEXT_FMTP, zHTp+1);
          rc = 1;
 -        goto meta_command_exit;
        }
 -      rc = sqlite3_user_add(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
 -                            booleanValue(azArg[4]));
 -      if( rc ){
 -        raw_printf(stderr, "User-Add failed: %d\n", rc);
 +      if( iLevel>0 && zHTs ){
 +        utf8_printf(out, HELP_TEXT_FMTS, zHTs);
          rc = 1;
        }
 -    }else if( strcmp(azArg[1],"edit")==0 ){
 -      if( nArg!=5 ){
 -        raw_printf(stderr, "Usage: .user edit USER PASSWORD ISADMIN\n");
 -        rc = 1;
 -        goto meta_command_exit;
 +      break;
 +    case 2:
 +      if( zSearch ){
 +        int m = 0;
 +        if( zHTp && !sqlite3_strlike(zSearch, zHTp, 0) ) ++m;
 +        if( zHTs && !sqlite3_strlike(zSearch, zHTs, 0) ) ++m;
 +        if( m==0 ) break;
 +      }
 +      /* else fall thru */
 +    default:
 +      if( zHTp ) utf8_printf(out, HELP_TEXT_FMTP, zHTp+1);
 +      if( zHTs ) utf8_printf(out, HELP_TEXT_FMTS, zHTs);
 +      rc = 1;
 +    }
 +  }
 +  return rc;
 +}
 +
 +/*
 +** Output primary (single-line) help for a known command.
 +*/
 +static void showPrimaryHelp(FILE *out, const char *zCmd, ShellExState *psx){
 +  CmdMatchIter cmi = {0};
 +  int nm = findMatchingDotCmds(zCmd, &cmi, psx);
 +  DotCommand *pdc = nextMatchingDotCmd(&cmi);
 +  if( pdc!=0 ){
 +    putSelectedCmdHelp(pdc, 0, 0,
 +#if SHELL_DYNAMIC_EXTENSION
 +                       cmi.zAdhocHelpText,
 +#endif
 +                       out, 0);
 +  }
 +  freeCmdMatchIter(&cmi);
 +}
 +
 +/*
 +** Output various subsets of help text. These 6 are defined:
 +** 1. HO_AllP  For all commands, primary help text only.
 +** 2. HO_AllX  For all commands, complete help text.
 +** 3. HO_LikeP For multiple commands matching pattern, primary help text only.
 +** 4. HO_OneX  For a single matched command, complete help text.
 +** 5. HO_LikeT For commands whose help contains a pattern, complete help text.
 +** 6. HO_Undoc For all internal "undocumented" (without normal help) commands.
 +** These variations are indicated thusly:
 +** 1. zPattern is NULL
 +** 2. zPattern is ""
 +** 3. zPattern is a prefix matching more than one command
 +** 4. zPattern is a word or prefix matching just one command
 +** 5. zPattern is neither case 3 or 4 but is found in complete help text
 +** 6. zPattern is exactly the pointer known locally as zHelpAll.
 +**
 +** Return the number of matches.
 +*/
 +static int showHelp(FILE *out, const char *zPattern, ShellExState *psx){
 +  u8 bNullPattern = zPattern==0;
 +  u8 bShowUndoc = zPattern==zHelpAll;
 +  u8 bEmptyPattern = !bNullPattern && (*zPattern==0 || bShowUndoc);
 +  int npm = 0; /* track how many matches found */
 +  CmdMatchIter cmi = {0};
 +  DotCommand *pdc;
 +  char *zPat = 0;
 +  char cLead = (bShowUndoc)? ',' : '.';
 +  int iLevel = 0;
 +  enum {
 +    HO_Tbd, HO_AllP, HO_AllX, HO_LikeP, HO_OneX, HO_LikeT, HO_Undoc
 +  } hoKind = bShowUndoc? HO_Undoc : HO_Tbd;
 +
 +  if( hoKind==HO_Undoc ){
-     int ixct = 0;
++    unsigned ixct = 0;
 +    utf8_printf(out, "%s\n%s\n",
 +      "The following commands are for SQLite diagnosis and internal testing.",
 +      "They are undocumented and subject to change without notice.");
 +    /* Bypass command lookup/resolution. This is just for internal commands. */
 +    while( ixct<numCommands ){
 +      struct CommandInfo *pci = &command_table[ixct];
 +      const char *zH = pci->azHelp[0];
 +      if( zH && *zH==cLead ){
 +        utf8_printf(out, HELP_TEXT_FMTP, zH+1);
 +        zH = pci->azHelp[1];
 +        if( zH ) utf8_printf(out, HELP_TEXT_FMTS, zH);
 +        ++npm;
 +      }
 +      ++ixct;
 +    }
 +    return npm;
 +  }
 +  npm = findMatchingDotCmds(zPattern, &cmi, psx);
 +  if( bNullPattern ) hoKind = HO_AllP;
 +  else if( bEmptyPattern ) hoKind = HO_AllX;
 +  else if( npm>1 ) hoKind = HO_LikeP;
 +  else if( npm==1 ) hoKind = HO_OneX;
 +  else{
 +    hoKind = HO_LikeT;
 +    zPat = smprintf("%%%s%%", zPattern);
 +    shell_check_oom(zPat);
 +  }
 +  zPattern = 0;
 +  iLevel = 1;
 +  switch( hoKind ){
 +  case HO_AllP: case HO_LikeP:
 +    iLevel = 0;
 +    break;
 +  case HO_AllX:
 +    break;
 +  case HO_OneX:
 +    cLead = 0;
 +    break;
 +  case HO_LikeT:
 +    zPattern = zPat;
 +    break;
 +  default: return 0;
 +  }
 +  npm = 0;
 +  while( 0 != (pdc = nextMatchingDotCmd(&cmi)) ){
 +    npm += putSelectedCmdHelp(pdc, iLevel, cLead,
 +#if SHELL_DYNAMIC_EXTENSION
 +                              cmi.zAdhocHelpText,
 +#endif
 +                              out, zPattern);
 +  }
 +  freeCmdMatchIter(&cmi);
 +  sqlite3_free(zPat);
 +  return npm;
 +}
 +
 +/* Perform preparation needed prior to actually running any dot command. */
 +static void command_prep(ShellInState *psi){
 +  clearTempFile(psi);
 +#ifndef SQLITE_OMIT_VIRTUALTABLE
 +  if( psi->expert.pExpert ){
 +    expertFinish(psi, 1, 0);
 +  }
 +#endif
 +}
 +
 +/* Perform post-execution actions needed after running any dot command. */
 +static void command_post(ShellInState *psi){
 +  if( psi->outCount ){
 +    psi->outCount--;
 +    if( psi->outCount==0 ) output_reset(psi);
 +  }
 +  updateSafeMode(psi);
 +}
 +
 +/* Issue errors per returned DotCmdRC and error message, handle certain
 + * exceptional returns, and translate return to the sanitized first 8.
 + */
 +static DotCmdRC dot_command_errors(char *zErr, char *azArg[], int nArg,
 +                                    DotCmdRC dcr, ShellExState *psx){
 +  if( psx->shellAbruptExit!=0 ){
 +    if( psx->shellAbruptExit>0x1ff ) dcr = DCR_AbortError;
 +    else dcr = DCR_Exit | (dcr & DCR_Error);
 +  }
 +  if( dcr==DCR_CmdErred ){
 +    /* Error message(s) already emitted. Just translate to execute error. */
 +    dcr = DCR_Error;
 +  }else if( dcr==DCR_SayUsage ){
 +    if( zErr ){
 +      utf8_printf(STD_ERR, "%s", zErr);
 +    }else{
 +      utf8_printf(STD_ERR, "Usage:\n");
 +      showPrimaryHelp(STD_ERR, azArg[0], psx);
 +    }
 +    dcr = DCR_Error;
 +  }else if( dcr > DCR_AbortError ){
 +    /* Handle invocation errors. */
 +    int ia = dcr & DCR_ArgIxMask;
 +    const char *pArg1st = (ia>=nArg)? "" : azArg[ia];
 +    const char *pArg2nd = 0;
 +    int ec = dcr & ~DCR_ArgIxMask;
 +    int unknownCmd = 0;
 +    const char *z = 0;
 +    switch( ec ){
 +    case DCR_Unknown:
 +      unknownCmd = ia==0;
 +      if( unknownCmd ) z = "unknown dot command: \".%s\"";
 +      else{
 +        z = "On .%s, unknown option or subcommand: \"%s\"";
 +        pArg2nd = pArg1st;
 +        pArg1st = azArg[0];
        }
 -      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;
 +      break;
 +    case DCR_Ambiguous:
 +      if( ia>0 ){
 +        z = "For .%s, \"%s\" is ambiguous; it matches more than one subcommand";
 +        pArg2nd = pArg1st;
 +        pArg1st = azArg[0];
 +      }else z = "\"%s\" is ambiguous; it matches multiple dot commands";
 +      break;
 +    case DCR_Unpaired:
 +      z = "With .%s, option %s must be paired with a value argument";
 +      pArg2nd = pArg1st;
 +      pArg1st = azArg[0];
 +      break;
 +    case DCR_TooMany:
 +      z = "Excess arguments provided; try .help %s";
 +      pArg1st = azArg[0];
 +      break;
 +    case DCR_TooFew:
 +      z = "Insufficient arguments; try .help %s";
 +      pArg1st = azArg[0];
 +      break;
 +    case DCR_Missing:
 +      z = "Required arguments missing; try .help %s";
 +      pArg1st = azArg[0];
 +      break;
 +    case DCR_ArgWrong:
 +      z = "Argument(s) incorrect; try .help %s";
 +      pArg1st = azArg[0];
 +      break;
 +    default:
 +      z = "? Bizarre return from %s";
 +      break;
 +    }
 +    if( !zErr ){
 +      if( z!=0 ){
 +        char *ze = smprintf("Error: %z\n", smprintf(z, pArg1st, pArg2nd));
 +        utf8_printf(STD_ERR, "%s", ze);
 +        sqlite3_free(ze);
        }
 -    }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{
 +      const char *fmt
 +        = (sqlite3_strnicmp(zErr, "Error", 5)==0)? "%s" : "Error: %s";
 +      utf8_printf(STD_ERR, fmt, zErr);
 +    }
 +    if( INSOURCE_IS_INTERACTIVE(ISS(psx)->pInSource) ){
 +      if( unknownCmd ){
 +        utf8_printf(STD_ERR, "Enter \".help\" for a list of commands.\n");
 +      }else{
 +        utf8_printf(STD_ERR, "Usage:\n");
 +        showPrimaryHelp(STD_ERR, azArg[0], psx);
        }
 -      rc = sqlite3_user_delete(p->db, azArg[2]);
 -      if( rc ){
 -        raw_printf(stderr, "User-Delete failed: %d\n", rc);
 -        rc = 1;
 +    }
 +    /* If the shell DB becomes messed up, at least .quit will be doable. */
 +    if( unknownCmd && psx->dbShell!=0
 +        && sqlite3_strnicmp(azArg[0],"quit",strlen30(azArg[0]))==0 ){
 +      dcr = DCR_Return;
 +    }else{
 +      dcr = DCR_Error;
 +    }
 +  }else{
 +    /* Handle execution errors. */
 +    if( dcr==DCR_AbortError ){
 +      if( zErr ){
 +        utf8_printf(STD_ERR, "Error: \".%s\" may not %s in -safe mode\n",
 +                    azArg[0], zErr);
 +      }else {
 +        utf8_printf(STD_ERR, "Error: \".%s\" forbidden in -safe mode\n",
 +                    azArg[0]);
        }
 +      psx->shellAbruptExit = 0x203;
      }else{
 -      raw_printf(stderr, "Usage: .user login|add|edit|delete ...\n");
 -      rc = 1;
 -      goto meta_command_exit;
 +      int error = dcr & DCR_Error;
 +      int action = dcr & ~DCR_Error;
 +      if( error ){
 +        if( zErr ){
 +          const char *fmt
 +            = (sqlite3_strnicmp(zErr, "Error", 5)==0)? "%s" : "Error: %s";
 +          utf8_printf(STD_ERR, fmt, zErr);
 +        }
 +        else utf8_printf(STD_ERR, "Error: .%s failed\n", azArg[0]);
 +      }
      }
 -  }else
 -#endif /* SQLITE_USER_AUTHENTICATION */
 +  }
 +  return dcr;
 +}
  
 -  if( c=='v' && strncmp(azArg[0], "version", n)==0 ){
 -    utf8_printf(p->out, "SQLite %s %s\n" /*extra-version-info*/,
 -        sqlite3_libversion(), sqlite3_sourceid());
 -#if SQLITE_HAVE_ZLIB
 -    utf8_printf(p->out, "zlib version %s\n", zlibVersion());
 -#endif
 -#define CTIMEOPT_VAL_(opt) #opt
 -#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt)
 -#if defined(__clang__) && defined(__clang_major__)
 -    utf8_printf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "."
 -                    CTIMEOPT_VAL(__clang_minor__) "."
 -                    CTIMEOPT_VAL(__clang_patchlevel__) "\n");
 -#elif defined(_MSC_VER)
 -    utf8_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) "\n");
 -#elif defined(__GNUC__) && defined(__VERSION__)
 -    utf8_printf(p->out, "gcc-" __VERSION__ "\n");
 +/* Argument-check and execute a found DotCommand, wrapping execution
 + * with command_{prep,post}(...), and issue errors as made evident.
 + * Return one of the "Post-execute action and success/error status"
 + * codes from the DotCmdRC enum.
 + *
 + * Note that this function is exposed for shell extensions to use.
 + *
 + * This should be called for "top-level" dot command execution only.
 + * Should an extension wrap or use a DotCommand object to effect its
 + * own functionality, that object's execute() method should be called
 + * directly, without going through this function.
 + */
 +static DotCmdRC runDotCommand(DotCommand *pmc, char *azArg[], int nArg,
 +                               ShellExState *psx){
 +  char *zErr = 0;
 +  DotCmdRC dcr = pmc->pMethods->argsCheck(pmc, &zErr, nArg, azArg);
 +
 +  command_prep(ISS(psx));
 +  if( dcr==DCR_Ok ){
 +    dcr = pmc->pMethods->execute(pmc, psx, &zErr, nArg, azArg);
 +  }
 +  if( dcr!=DCR_Ok ){
 +    dcr = dot_command_errors(zErr, azArg, nArg, dcr, psx);
 +  }
 +  sqlite3_free(zErr);
 +  command_post(ISS(psx));
 +  return dcr;
 +}
 +
 +/*
 +** If an input line or line group begins with "." then invoke this routine
 +** to process that line.
 +**
 +** Returns sanitized DotCmdRC values, with invocation error codes
 +** translated to DCR_Error, so that only these 8 returns are possible:
 +** DCR_Ok, DCR_Return, DCR_Exit or DCR_Abort possibly or'ed with DCR_Error.
 +**
 +** Any applicable error messages are issued along with output messages.
 +*/
 +static DotCmdRC do_dot_command(char *zLine, ShellExState *psx){
 +  int h = 1; /* Passing over leading '.' */
 +  int nArg = 0;
 +  char *azArg[52];
 +  DotCmdRC dcr = DCR_Ok;
 +#if SHELL_VARIABLE_EXPANSION
 +  int ncLineIn = strlen30(zLine);
 +  u8 bExpVars = SHEXT_VAREXP(ISS(psx));
  #endif
 -  }else
  
 -  if( c=='v' && strncmp(azArg[0], "vfsinfo", n)==0 ){
 -    const char *zDbName = nArg==2 ? azArg[1] : "main";
 -    sqlite3_vfs *pVfs = 0;
 -    if( p->db ){
 -      sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs);
 -      if( pVfs ){
 -        utf8_printf(p->out, "vfs.zName      = \"%s\"\n", pVfs->zName);
 -        raw_printf(p->out, "vfs.iVersion   = %d\n", pVfs->iVersion);
 -        raw_printf(p->out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
 -        raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
 +  /* Parse the input line into tokens which are 0-terminated and left in-place.
 +  */
 +  while( zLine[h] && nArg<ArraySize(azArg)-1 ){
 +    /* Future: Complain if this fixed argument count limit is hit. */
 +    while( IsSpace(zLine[h]) ){ h++; }
 +    if( zLine[h]==0 ) break;
 +    if( zLine[h]=='\'' || zLine[h]=='"' ){
 +      int delim = zLine[h++];
 +      azArg[nArg++] = &zLine[h];
 +      while( zLine[h] && zLine[h]!=delim ){
 +        if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++;
 +        h++;
        }
 -    }
 -  }else
 -
 -  if( c=='v' && strncmp(azArg[0], "vfslist", n)==0 ){
 -    sqlite3_vfs *pVfs;
 -    sqlite3_vfs *pCurrent = 0;
 -    if( p->db ){
 -      sqlite3_file_control(p->db, "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent);
 -    }
 -    for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){
 -      utf8_printf(p->out, "vfs.zName      = \"%s\"%s\n", pVfs->zName,
 -           pVfs==pCurrent ? "  <--- CURRENT" : "");
 -      raw_printf(p->out, "vfs.iVersion   = %d\n", pVfs->iVersion);
 -      raw_printf(p->out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
 -      raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
 -      if( pVfs->pNext ){
 -        raw_printf(p->out, "-----------------------------------\n");
 +      if( zLine[h]==delim ){
 +        zLine[h++] = 0;
        }
 +      if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
 +    }else{
 +      azArg[nArg++] = &zLine[h];
 +      while( zLine[h] && !IsSpace(zLine[h]) ){ h++; }
 +      if( zLine[h] ) zLine[h++] = 0;
 +      resolve_backslashes(azArg[nArg-1]);
      }
 -  }else
 +  }
 +  azArg[nArg] = 0; /* No code here relies on this, but some extension might. */
  
 -  if( c=='v' && strncmp(azArg[0], "vfsname", n)==0 ){
 -    const char *zDbName = nArg==2 ? azArg[1] : "main";
 -    char *zVfsName = 0;
 -    if( p->db ){
 -      sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName);
 -      if( zVfsName ){
 -        utf8_printf(p->out, "%s\n", zVfsName);
 -        sqlite3_free(zVfsName);
 +  /* Process the input line. If it was empty, do nothing and declare success.
 +   * Note that "empty" includes a leading '.' followed by nothing else.
 +   */
 +  if( nArg>0 ){
 +    int nFound;
 +    DotCommand *pmc = findDotCommand(azArg[0], psx, &nFound);
 +    if( pmc==0 || nFound>1 ){
 +      if( nFound==0 ){
 +        dcr = DCR_Unknown;
 +#if SHELL_DYNAMIC_EXTENSION
 +        pmc = ISS(psx)->pUnknown;
 +        if( pmc ) dcr = runDotCommand(pmc, azArg, nArg, psx);
 +#endif
 +      }else{
 +        dcr = DCR_Ambiguous;
 +      }
 +      if( dcr > DCR_ArgIxMask ){
 +        /* Issue error for unknown or inadequately specified dot command. */
 +        dcr = dot_command_errors(0, azArg, nArg, dcr, psx);
        }
      }
 -  }else
 -
 -  if( c=='w' && strncmp(azArg[0], "wheretrace", n)==0 ){
 -    unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
 -    sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &x);
 -  }else
 -
 -  if( c=='w' && strncmp(azArg[0], "width", n)==0 ){
 -    int j;
 -    assert( nArg<=ArraySize(azArg) );
 -    p->nWidth = nArg-1;
 -    p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(int)*2);
 -    if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory();
 -    if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth];
 -    for(j=1; j<nArg; j++){
 -      p->colWidth[j-1] = (int)integerValue(azArg[j]);
 +    else{
 +      char *arg0 = azArg[0];
 +      azArg[0] = (char *)(pmc->pMethods->name(pmc));
 +      /* Run found command and issue or handle any errors it may report. */
 +      dcr = runDotCommand(pmc, azArg, nArg, psx);
 +      azArg[0] = arg0;
      }
 -  }else
 -
 -  {
 -    utf8_printf(stderr, "Error: unknown command or invalid arguments: "
 -      " \"%s\". Enter \".help\" for help\n", azArg[0]);
 -    rc = 1;
    }
  
 -meta_command_exit:
 -  if( p->outCount ){
 -    p->outCount--;
 -    if( p->outCount==0 ) output_reset(p);
 +#if SHELL_VARIABLE_EXPANSION
 +  if( bExpVars ){
 +    /* Free any arguments that are allocated rather than tokenized in place. */
 +    for( n=1; n<nArg; ++n ){
 +      int iArgOffset = azArg[n]-zLine;
 +      u8 bInPlace = iArgOffset>0 && iArgOffset<ncLineIn;
 +      if( !bInPlace ) sqlite3_free(azArg[n]);
 +    }
    }
 -  p->bSafeMode = p->bSafeModePersist;
 -  return rc;
 +#endif
 +  return dcr;
  }
  
  /* Line scan result and intermediate states (supporting scan resumption)
@@@ -15834,66 -12364,52 +15836,66 @@@ int SQLITE_CDECL SHELL_MAIN(int argc, w
      */
      if( stdin_is_interactive ){
        char *zHome;
--      char *zHistory;
 -      int nHistory;
 -      printf(
 -        "SQLite version %s %.19s\n" /*extra-version-info*/
 -        "Enter \".help\" for usage hints.\n",
 -        sqlite3_libversion(), sqlite3_sourceid()
 -      );
 -      if( warnInmemoryDb ){
 -        printf("Connected to a ");
 -        printBold("transient in-memory database");
 -        printf(".\nUse \".open FILENAME\" to reopen on a "
 -               "persistent database.\n");
 -      }
 -      zHistory = getenv("SQLITE_HISTORY");
 -      if( zHistory ){
 -        zHistory = strdup(zHistory);
 -      }else if( (zHome = find_home_dir(0))!=0 ){
 -        nHistory = strlen30(zHome) + 20;
 -        if( (zHistory = malloc(nHistory))!=0 ){
 -          sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome);
++      char *zHistory = 0;
 +      if( bQuiet ){
 +        /* bQuiet is almost like normal interactive, but quieter. */
 +        mainPrompt[0] = 0;
 +        continuePrompt[0] = 0;
 +      }else{
 +        fprintf(STD_OUT,
 +          "SQLite version %s %.19s\n" /*extra-version-info*/
 +          "Enter \".help\" for usage hints.\n",
 +          sqlite3_libversion(), sqlite3_sourceid()
 +        );
 +        if( warnInmemoryDb ){
 +          fprintf(STD_OUT, "Connected to a ");
 +          printBold("transient in-memory database");
 +          fprintf(STD_OUT, ".\nUse \".open FILENAME\" to reopen on a "
 +                 "persistent database.\n");
          }
 -      }
 -      if( zHistory ){ shell_read_history(zHistory); }
 +        zHistory = getenv("SQLITE_HISTORY");
 +        if( zHistory ){
 +          zHistory = strdup(zHistory);
 +        }else if( (zHome = find_home_dir(0))!=0 ){
 +          int nHistory = strlen30(zHome) + 20;
 +          if( (zHistory = malloc(nHistory))!=0 ){
 +            sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome);
 +          }
 +        }
 +        if( zHistory ){ shell_read_history(zHistory); }
  #if HAVE_READLINE || HAVE_EDITLINE
 -      rl_attempted_completion_function = readline_completion;
 +        rl_attempted_completion_function = readline_completion;
  #elif HAVE_LINENOISE
 -      linenoiseSetCompletionCallback(linenoise_completion);
 +        linenoiseSetCompletionCallback(linenoise_completion);
  #endif
 -      data.in = 0;
 -      rc = process_input(&data);
 -      if( zHistory ){
 -        shell_stifle_history(2000);
 -        shell_write_history(zHistory);
 -        free(zHistory);
 +      }
 +      data.pInSource = &termInSource; /* read from stdin interactively */
 +      drc = process_input(&data);
 +      rc = (drc>2)? 2 : drc;
 +      if( !bQuiet ){
 +        if( zHistory ){
 +          shell_stifle_history(2000);
 +          shell_write_history(zHistory);
 +          free(zHistory);
 +        }
        }
      }else{
 -      data.in = stdin;
 -      rc = process_input(&data);
 +      data.pInSource = &stdInSource; /* read from stdin without prompts */
 +      drc = process_input(&data);
 +      rc = (drc>2)? 2 : drc;
      }
    }
 -  free(azCmd);
 -  set_table_name(&data, 0);
 -  if( data.db ){
 + shell_bail:
 +  set_table_name(&datax, 0);
 +  if( datax.dbUser ){
      session_close_all(&data, -1);
 -    close_db(data.db);
 +#if SHELL_DYNAMIC_EXTENSION
 +    notify_subscribers(&data, NK_DbAboutToClose, datax.dbUser);
 +#endif
 +    close_db(datax.dbUser);
    }
 +  sqlite3_mutex_free(pGlobalDbLock);
 +  pGlobalDbLock = 0;
    for(i=0; i<ArraySize(data.aAuxDb); i++){
      sqlite3_free(data.aAuxDb[i].zFreeOnClose);
      if( data.aAuxDb[i].db ){
index 6eab72d8ea7d6d3bb6dc40b0b7ca3e0c4b8a275e,0000000000000000000000000000000000000000..872106cba3e89f3f8470e6e0c2384d85322e1298
mode 100644,000000..100644
--- /dev/null
@@@ -1,187 -1,0 +1,194 @@@
 +/*
 +** 2022 Feb 28
 +**
 +** The author disclaims copyright to this source code.  In place of
 +** a legal notice, here is a blessing:
 +**
 +**    May you do good and not evil.
 +**    May you find forgiveness for yourself and forgive others.
 +**    May you share freely, never taking more than you give.
 +**
 +*************************************************************************
 +** Test extension for testing the shell's .load -shellext ... function.
 +** To build from the SQLite project root:
 +** gcc -shared -fPIC -Wall -I. -g src/test_shellext_c.c -o test_shellext_c.so
 +*/
 +#include <stdio.h>
 +#include "shx_link.h"
 +
 +SQLITE_EXTENSION_INIT1;
 +
 +SHELL_EXTENSION_INIT1(pShExtApi, pExtHelpers, shextLinkFetcher);
 +#define SHX_API(entry) pShExtApi->entry
 +#define SHX_HELPER(entry) pExtHelpers->entry
 +
 +typedef struct BatBeing BatBeing;
 +static void sayHowMany( BatBeing *pbb, FILE *out, ShellExState *psx );
 +
 +/* These DERIVED_METHOD(...) macro calls' arguments were copied and
 + * pasted from the DotCommand interface declaration in shext_linkage.h ,
 + * but with "Interface,Derived" substituted for the interface typename.
 + * The function bodies are not so easily written, of course. */
 +
 +DERIVED_METHOD(void, destruct, DotCommand,BatBeing, 0, ()){
++  (void)(pThis);
 +  fprintf(stdout, "BatBeing unbecoming.\n");
 +}
 +
 +DERIVED_METHOD(const char *, name, DotCommand,BatBeing, 0,()){
++  (void)(pThis);
 +  return "bat_being";
 +}
 +
 +DERIVED_METHOD(const char *, help, DotCommand,BatBeing, 1,(const char *zHK)){
++  (void)(pThis);
 +  if( !zHK )
 +    return ".bat_being ?whatever?    Demonstrates vigilantism weekly\n";
 +  if( !*zHK )
 +    return "   Options summon side-kick and villains.\n";
 +  return 0;
 +}
 +
 +DERIVED_METHOD(DotCmdRC, argsCheck, DotCommand,BatBeing, 3,
 +             (char **pzErrMsg, int nArgs, char *azArgs[])){
++  (void)(pThis);
++  (void)(pzErrMsg);
++  (void)(nArgs);
++  (void)(azArgs);
 +  return DCR_Ok;
 +}
 +
 +DERIVED_METHOD(DotCmdRC, execute, DotCommand,BatBeing, 4,
 +           (ShellExState *psx, char **pzErrMsg, int nArgs, char *azArgs[]));
 +
 +/* Define a DotCommand v-table initialized to reference above methods. */
 +DotCommand_IMPLEMENT_VTABLE(BatBeing, batty_methods);
 +
 +/* Define/initialize BatBeing as a DotCommand subclass using above v-table. 
 + * This compiles in a type-safe manner because the batty_methods v-table
 + * and methods it incorporates strictly match the DotCommand interface.
 + */
 +INSTANCE_BEGIN(BatBeing);
 +  int numCalls;
 +  DotCommand * pPrint;
 +  DotCommand * pPrior;
 +INSTANCE_END(BatBeing) batty = {
 +  &batty_methods,
 +  0, 0, 0
 +};
 +
 +DERIVED_METHOD(DotCmdRC, execute, DotCommand,BatBeing, 4,
 +             (ShellExState *psx, char **pzErrMsg, int nArgs, char *azArgs[])){
 +  FILE *out = pExtHelpers->currentOutputFile(psx);
 +  BatBeing *pbb = (BatBeing*)pThis;
 +  switch( nArgs ){
 +  default:
 +    {
 +      if( pbb->pPrior ){
 +        char *az1 = azArgs[1];
 +        for( int i=2; i<nArgs; ++i ) azArgs[i-1] = azArgs[i];
 +        azArgs[nArgs-1] = az1;
 +        return pbb->pPrior->pMethods->execute(pbb->pPrior, psx,
 +                                              pzErrMsg, nArgs, azArgs);
 +      }else{
 +        int cix;
 +        SHX_HELPER(setColumnWidths)(psx, azArgs+1, nArgs-1);
 +        fprintf(out, "Column widths:");
 +        for( cix=0; cix<psx->numWidths; ++cix ){
 +          fprintf(out, " %d", psx->pSpecWidths[cix]);
 +        }
 +        fprintf(out, "\n");
 +      }
 +    }
 +    break;
 +  case 3:
 +    fprintf(out, "The Penguin, Joker and Riddler have teamed up!\n");
 +  case 2: fprintf(out, "The Dynamic Duo arrives, and ... ");
 +  case 1: fprintf(out, "@#$ KaPow! $#@\n");
 +  }
 +  sayHowMany((BatBeing *)pThis, out, psx);
 +  return DCR_Ok;
 +}
 +
 +static void sayHowMany( BatBeing *pbb, FILE *out, ShellExState *psx ){
 +  if( pbb->pPrint ){
 +    char *az[] = { "print", 0 };
 +    char *zErr = 0;
 +    DotCommand * pdcPrint = pbb->pPrint;
 +    DotCmdRC rc;
 +    az[1] = sqlite3_mprintf("This execute has been called %d times.",
 +                            ++pbb->numCalls);
 +    rc = pdcPrint->pMethods->execute(pdcPrint, psx, &zErr, 2, az);
 +    sqlite3_free(az[1]);
 +    if( rc!= DCR_Ok ){
 +      fprintf(out, "print() failed: %d\n", rc);
 +    }
 +  }
 +}
 +
 +static int shellEventHandle(void *pv, NoticeKind nk,
 +                            void *pvSubject, ShellExState *psx){
 +  FILE *out = pExtHelpers->currentOutputFile(psx);
 +  if( nk==NK_ShutdownImminent ){
 +    BatBeing *pbb = (BatBeing *)pv;
 +    fprintf(out, "Bat cave meteor strike detected after %d calls.\n",
 +            pbb->numCalls);
 +  }else if( nk==NK_Unsubscribe ){
 +    fprintf(out, "BatBeing incommunicado.\n");
 +  }else if( nk==NK_DbUserAppeared || nk==NK_DbUserVanishing ){
 +    const char *zWhat = (nk==NK_DbUserAppeared)? "appeared" : "vanishing";
 +    int isDbu = pvSubject==psx->dbUser;
 +    fprintf(out, "db%s %s\n", isDbu? "User" : "?", zWhat);
 +    if( psx->dbUser != pvSubject ) fprintf(out, "not dbx(%p)\n", psx->dbUser);
 +  }else if( nk==NK_DbAboutToClose ){
 +    const char *zdb = (pvSubject==psx->dbUser)? "User"
 +      : (pvSubject==psx->dbShell)? "Shell" : "?";
 +    fprintf(out, "db%s closing\n", zdb);
 +  }
 +  return 0;
 +}
 +
 +/*
 +** Extension load function.
 +*/
 +#ifdef _WIN32
 +__declspec(dllexport)
 +#endif
 +int sqlite3_testshellextc_init(
 +  sqlite3 *db,
 +  char **pzErrMsg,
 +  const sqlite3_api_routines *pApi
 +){
 +  int nErr = 0;
 +  int iLdErr;
 +  SQLITE_EXTENSION_INIT2(pApi);
 +  SHELL_EXTENSION_INIT2(pShExtLink, shextLinkFetcher, db);
 +
 +  SHELL_EXTENSION_INIT3(pShExtApi, pExtHelpers, pShExtLink);
 +  iLdErr = SHELL_EXTENSION_LOADFAIL_WHY(pShExtLink, 5, 5);
 +  if( iLdErr!=EXLD_Ok ){
 +    *pzErrMsg = sqlite3_mprintf("Load failed, cause %d\n", iLdErr);
 +    return SQLITE_ERROR;
 +  }else{
 +    ShellExState *psx = pShExtLink->pSXS;
 +    DotCommand *pdc = (DotCommand *)&batty;
 +    int rc;
 +    char *zLoadArgs = sqlite3_mprintf("Load arguments:");
 +    int ila;
 +
 +    for( ila=0; ila<pShExtLink->nLoadArgs; ++ila ){
 +      zLoadArgs = sqlite3_mprintf("%z %s", zLoadArgs,
 +                                  pShExtLink->azLoadArgs[ila]);
 +    }
 +    if( ila ) fprintf(SHX_HELPER(currentOutputFile)(psx), "%s\n", zLoadArgs);
 +    sqlite3_free(zLoadArgs);
 +    SHX_API(subscribeEvents)(psx, sqlite3_testshellextc_init, &batty,
 +                             NK_CountOf, shellEventHandle);
 +    batty.pPrint = SHX_HELPER(findDotCommand)("print", psx, &rc);
 +    batty.pPrior = SHX_HELPER(findDotCommand)("bat_being", psx, &rc);
 +    rc = SHX_API(registerDotCommand)(psx, sqlite3_testshellextc_init, pdc);
 +    if( rc!=0 ) ++nErr;
 +    pShExtLink->eid = sqlite3_testshellextc_init;
 +  }
 +  return nErr ? SQLITE_ERROR : SQLITE_OK;
 +}
index 6b203b0b221f3d232011ef9c7dc0967157cb68b4,0000000000000000000000000000000000000000..61b2a588a75b17340a982395219c18d628babbbb
mode 100644,000000..100644
--- /dev/null
@@@ -1,184 -1,0 +1,187 @@@
 +/*
 +** 2022 Feb 28
 +**
 +** The author disclaims copyright to this source code.  In place of
 +** a legal notice, here is a blessing:
 +**
 +**    May you do good and not evil.
 +**    May you find forgiveness for yourself and forgive others.
 +**    May you share freely, never taking more than you give.
 +**
 +*************************************************************************
 +** Test extension for testing the shell's .load <extName> -shext function.
 +** To build from the SQLite project root:
 +     g++ -shared -fPIC -Wall -I. -g src/test_shellext_cpp.cpp \
 +      -o test_shellext_cpp.so
 +*/
 +#include <stdio.h>
 +#include "shx_link.h"
 +
 +SQLITE_EXTENSION_INIT1;
 +
 +SHELL_EXTENSION_INIT1(pShExtApi, pExtHelpers, shextLinkFetcher);
 +#define SHX_API(entry) pShExtApi->entry
 +#define SHX_HELPER(entry) pExtHelpers->entry
 +
 +struct BatBeing : DotCommand {
 +
 +  ~BatBeing() {
 +    fprintf(stdout, "BatBeing RIP.\n");
 +  }; // No held resources; copy/assign is fine and dying is easy.
 +
 +  void destruct() {
 +    fprintf(stdout, "BatBeing unbecoming.\n");
 +  }
 +
 +  const char *name() { return "bat_being"; };
 +
 +  const char *help(const char *zHK) {
 +    if( !zHK )
 +      return ".bat_being ?whatever?    Demonstrates vigilantism weekly\n";
 +    if( !*zHK )
 +      return "   Options summon side-kick and villains.\n";
 +    return 0;
 +  };
 +
 +  DotCmdRC argsCheck(char **pzErrMsg, int nArgs, char *azArgs[]) {
++    (void)(pzErrMsg);
++    (void)(nArgs);
++    (void)(azArgs);
 +    return DCR_Ok;
 +  };
 +  DotCmdRC execute(ShellExState *psx, char **pzErrMsg,
 +                   int nArgs, char *azArgs[]);
 +
 +  BatBeing(DotCommand *pp = 0) {
 +    numCalls = 0;
 +    pPrint = pp;
 +    pPrior = 0;
 +    fprintf(stdout, "BatBeing lives.\n");
 +  };
 +
 +  // Default copy/assign are fine; nothing held.
 +
 +  int numCalls;
 +  DotCommand * pPrint;
 +  DotCommand * pPrior;
 +};
 +
 +static void sayHowMany( BatBeing *pbb, FILE *out, ShellExState *psx ){
 +  if( pbb->pPrint ){
 +    static char cmd[] =  "print";
 +    char *az[] = { cmd, 0 };
 +    char *zErr = 0;
 +    DotCmdRC rc;
 +    az[1] = sqlite3_mprintf("This execute has been called %d times.",
 +                            ++pbb->numCalls);
 +    rc = pbb->pPrint->execute(psx, &zErr, 2, az);
 +    sqlite3_free(az[1]);
 +    if( rc!= DCR_Ok ){
 +      fprintf(out, "print() failed: %d\n", rc);
 +    }
 +  }
 +}
 +
 +DotCmdRC BatBeing::execute(ShellExState *psx, char **pzErrMsg,
 +                           int nArgs, char *azArgs[]) {
 +  FILE *out = SHX_HELPER(currentOutputFile)(psx);
 +  switch( nArgs ){
 +  default:
 +    {
 +      if( pPrior ){
 +        char *az1 = azArgs[1];
 +        for( int i=2; i<nArgs; ++i ) azArgs[i-1] = azArgs[i];
 +        azArgs[nArgs-1] = az1;
 +        return pPrior->execute(psx, pzErrMsg, nArgs, azArgs);
 +      }else{
 +        SHX_HELPER(setColumnWidths)(psx, azArgs+1, nArgs-1);
 +        fprintf(out, "Column widths:");
 +        for( int cix=0; cix<psx->numWidths; ++cix ){
 +          fprintf(out, " %d", psx->pSpecWidths[cix]);
 +        }
 +        fprintf(out, "\n");
 +      }
 +    }
 +    break;
 +  case 3:
 +    fprintf(out, "The Penguin, Joker and Riddler have teamed up!\n");
 +  case 2: fprintf(out, "The Dynamic Duo arrives, and ... ");
 +  case 1: fprintf(out, "@#$ KaPow! $#@\n");
 +  }
 +  sayHowMany(this, out, psx);
 +  return DCR_Ok;
 +}
 +
 +/* Define/initialize BatBeing as a DotCommand subclass using above v-table.
 + * This compiles in a type-safe manner because the batty_methods v-table
 + * and methods it incorporates strictly match the DotCommand interface.
 + */
 +static BatBeing batty(0);
 +
 +static int shellEventHandle(void *pv, NoticeKind nk,
 +                            void *pvSubject, ShellExState *psx){
 +  FILE *out = SHX_HELPER(currentOutputFile)(psx);
 +  if( nk==NK_ShutdownImminent ){
 +    BatBeing *pbb = (BatBeing *)pv;
 +    fprintf(out, "Bat cave meteor strike detected after %d calls.\n",
 +            pbb->numCalls);
 +  }else if( nk==NK_Unsubscribe ){
 +    fprintf(out, "BatBeing incommunicado.\n");
 +  }else if( nk==NK_DbUserAppeared || nk==NK_DbUserVanishing ){
 +    const char *zWhat = (nk==NK_DbUserAppeared)? "appeared" : "vanishing";
 +    int isDbu = pvSubject==psx->dbUser;
 +    fprintf(out, "db%s %s\n", isDbu? "User" : "?", zWhat);
 +    if( psx->dbUser != pvSubject ) fprintf(out, "not dbx(%p)\n", psx->dbUser);
 +  }else if( nk==NK_DbAboutToClose ){
 +    const char *zdb = (pvSubject==psx->dbUser)? "User"
 +      : (pvSubject==psx->dbShell)? "Shell" : "?";
 +    fprintf(out, "db%s closing\n", zdb);
 +  }
 +  return 0;
 +}
 +
 +/*
 +** Extension load function.
 +*/
 +extern "C"
 +#ifdef _WIN32
 +__declspec(dllexport)
 +#endif
 +int sqlite3_testshellextcpp_init(
 +  sqlite3 *db,
 +  char **pzErrMsg,
 +  const sqlite3_api_routines *pApi
 +){
 +  int iLdErr;
 +  int nErr = 0;
 +  SQLITE_EXTENSION_INIT2(pApi);
 +  SHELL_EXTENSION_INIT2(pShExtLink, shextLinkFetcher, db);
 +
 +  SHELL_EXTENSION_INIT3(pShExtApi, pExtHelpers, pShExtLink);
 +  iLdErr = SHELL_EXTENSION_LOADFAIL_WHY(pShExtLink, 5, 5);
 +  if( iLdErr!=EXLD_Ok ){
 +    *pzErrMsg = sqlite3_mprintf("Load failed, cause %d\n", iLdErr);
 +    return SQLITE_ERROR;
 +  }else{
 +    ShellExState *psx = pShExtLink->pSXS;
 +    int rc;
 +    char *zLoadArgs = sqlite3_mprintf("Load arguments:");
 +    int ila;
 +
 +    for( ila=0; ila<pShExtLink->nLoadArgs; ++ila ){
 +      zLoadArgs = sqlite3_mprintf("%z %s", zLoadArgs,
 +                                  pShExtLink->azLoadArgs[ila]);
 +    }
 +    if( ila ) fprintf(SHX_HELPER(currentOutputFile)(psx), "%s\n", zLoadArgs);
 +    sqlite3_free(zLoadArgs);
 +    SHX_API(subscribeEvents)(psx, sqlite3_testshellextcpp_init, &batty,
 +                             NK_CountOf, shellEventHandle);
 +    batty.pPrint = SHX_HELPER(findDotCommand)("print", psx, &rc);
 +    batty.pPrior = SHX_HELPER(findDotCommand)(batty.name(), psx, &rc);
 +    rc = SHX_API(registerDotCommand)(psx,
 +                                     sqlite3_testshellextcpp_init, &batty);
 +    if( rc!=0 ) ++nErr;
 +    pShExtLink->eid = sqlite3_testshellextcpp_init;
 +  }
 +  return nErr ? SQLITE_ERROR : SQLITE_OK;
 +}