]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Sync w/trunk
authorlarrybr <larrybr@noemail.net>
Thu, 29 Jun 2023 17:58:51 +0000 (17:58 +0000)
committerlarrybr <larrybr@noemail.net>
Thu, 29 Jun 2023 17:58:51 +0000 (17:58 +0000)
FossilOrigin-Name: fe9aa2e9c17f33a8f6045e9cf755f8251d261c34f89a59d658b72f7c99a6a12c

1  2  3 
README.md
ext/misc/tclshext.c.in
manifest
manifest.uuid
src/btree.c
src/expr.c
src/func.c
src/shell.c.in
src/util.c
src/vdbe.c
test/shell2.test

diff --cc README.md
index 0e60376bab971a9320b9df64cc8ec1a142a2c295,10262653e729b452a1941d846051a85468222ca6,10262653e729b452a1941d846051a85468222ca6..10262653e729b452a1941d846051a85468222ca6
mode 100755,100644,100644..100755
+++ b/README.md
index 38ae492817b122c05a11228c4d376a241571d45e,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..f64125161286b8cfec3cf09fb6b2c5292e8bc842
mode 100755,000000,000000..100755
--- /dev/null
--- /dev/null
@@@@ -1,1308 -1,0 -1,0 +1,1316 @@@@
-  /* C implementation of TCL proc, get_tcl_group 
 ++/*
 ++** 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
 ++#define oprintf pExtHelpers->utf8CurrentOutPrintf
 ++
 ++/* 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 ../../src/tclsqlite.c
 ++
 ++#if defined(_WIN32) || defined(WIN32)
 ++# 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++=='.' ){
 ++    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[])){
 ++  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{
 ++      fprintf(stderr, "The %s command does not yet exist.\n", zName);
 ++      oprintf(psx, "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
-    if( objc==1 ){
-      static Prompts cueTcl = { "tcl% ", "   > " };
+++/* C implementation of TCL proc, get_tcl_group
+++ * It accepts one or two optional arguments, a primary prompt
+++ * and a continuation prompt, defaulting to "tcl% " and "   > ".
 ++ * 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 && objc<=3 ){
+++    Prompts cueTcl = { "tcl% ", "   > " };
 ++    ShellExState *psx = (ShellExState *)pvSS;
 ++    struct InSource *pis = SHX_HELPER(currentInputSource)(psx);
 ++    int isComplete = 0;
 ++    char *zIn = 0;
 ++    int isContinuation = 0;
+++
+++    switch( objc ){
+++    case 3: cueTcl.zContinue = (const char*)Tcl_GetStringFromObj(objv[2],0);
+++    case 2: cueTcl.zMain = (const char*)Tcl_GetStringFromObj(objv[1],0);
+++    default: break;
+++    }
 ++    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_Obj *const objvv[] = {
 ++        Tcl_NewStringObj(Tcl_GetStringResult(interp) , -1),
 ++        Tcl_NewIntObj(isComplete)
 ++      }; /* These unowned objects go directly into result, becoming owned. */
 ++      Tcl_ResetResult(interp);
 ++      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->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;
 ++
 ++      /* Only above need handling. (So, clever tools, be quiet!) */
 ++    default: 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 = -1;
 ++  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_OK==Tcl_GetIndexFromObj(0, objv[1], azDoHere, "", 0, &doWhat)){
 ++    switch( doWhat ){
 ++    case DDW_Close:
 ++      zMoan = " close disallowed. It is a wrapped DB belonging to the shell.";
 ++      goto complain_fail;
 ++    default: ; /* Fine */
 ++    }
 ++  }
 ++  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 89552890ee0cd6d91c625810227ef3635d574d34,3d0377b14df94cd9e048a7c2b7f3a8fc1cda8233,897ab5ae64761df51a1ef0ec95e64fb25cc2a8d6..69a2ecc342a13848cf08d1c744cbb1533b92f27a
+++ b/manifest
-  C Sync\sw/trunk\s(for\sCLI\s-cachetrace)
-  D 2023-06-21T14:48:41.994
 - C Fix\sharmless\scompiler\swarnings\sabout\sunused\sfunction\sarguments.
 - D 2023-06-29T17:48:32.337
  -C Further\srefine\sthe\sdtostr()\stesting\sfunction\sin\sthe\sCLI\sso\sthat\sit\stakes\san\noptional\ssecond\sparameter\swhich\sis\sthe\snumber\sof\ssignificant\sdigits\sto\sdisplay.
  -D 2023-06-29T17:26:21.732
+++C Sync\sw/trunk
+++D 2023-06-29T17:58:51.696
   F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
   F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
   F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
 --F Makefile.in 0f4cb3955aaff8a40ec3857ba1784bd98b69802e51eff979f874b65713b627b2
 ++F Makefile.in dcc5b1491fff94b9a8f16f00714ed6a6c886d853970d0cd4a4066296063e96fd x
   F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
 --F Makefile.msc 7248d860f71ab164b4cec3c415e6cc1bd9fee860c370d65bd8bb49e9572521e2
 --F README.md c1c4218efcc4071a6e26db2b517fdbc1035696a29b370edd655faddbef02b224
 --F VERSION c6366dc72582d3144ce87b013cc35fe48d62f6d07d5be0c9716ea33c862144aa
 ++F Makefile.msc 7b31410d0f91251d4268b1abcab7b1a26b52d37193dd7ead87b2acb302ecf6b6 x
-  F README.md 8ff80689b9cb9f6e9b842edf31a3358ff53bc538c351799e03dd3e5455e637e5 x
+++F README.md c1c4218efcc4071a6e26db2b517fdbc1035696a29b370edd655faddbef02b224 x
 ++F VERSION c6366dc72582d3144ce87b013cc35fe48d62f6d07d5be0c9716ea33c862144aa x
   F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
   F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2
   F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90
@@@@ -290,10 -287,10 -287,10 +290,10 @@@@ F ext/misc/completion.c 6dafd7f4348eecc
   F ext/misc/compress.c 3354c77a7c8e86e07d849916000cdac451ed96500bfb5bd83b20eb61eee012c9
   F ext/misc/csv.c ca8d6dafc5469639de81937cb66ae2e6b358542aba94c4f791910d355a8e7f73
   F ext/misc/dbdump.c b8592f6f2da292c62991a13864a60d6c573c47a9cc58362131b9e6a64f823e01
-  F ext/misc/decimal.c 57d85fa20a5a74d3b0dfc78ab7934ae6c9f5aa8eed915faa2b5246bec87ddc6d
  -F ext/misc/decimal.c 17303ac06f50d750fdb635c8be1f8eb820fa95896fd08c08af332b62fe8bcfce
+ +F ext/misc/decimal.c 24ccb63e9af6ed7de2e8e3b300061ad91169a44cc0c35dab92b7d2e1e7574f28
   F ext/misc/eval.c 04bc9aada78c888394204b4ed996ab834b99726fb59603b0ee3ed6e049755dc1
   F ext/misc/explain.c 0086fab288d4352ea638cf40ac382aad3b0dc5e845a1ea829a694c015fd970fe
 --F ext/misc/fileio.c 4e7f7cd30de8df4820c552f14af3c9ca451c5ffe1f2e7bef34d598a12ebfb720
 ++F ext/misc/fileio.c 37f19acaf22562bae05f530c81c7b24b2c5c091503b115b54ec127958fb5c8bb
   F ext/misc/fossildelta.c 1240b2d3e52eab1d50c160c7fe1902a9bd210e052dc209200a750bbf885402d5
   F ext/misc/fuzzer.c eae560134f66333e9e1ca4c8ffea75df42056e2ce8456734565dbe1c2a92bf3d
   F ext/misc/ieee754.c 984d51fe23e956484ec1049df6f5257002e3ab338cabceb39761c2e80ad10bf4
@@@@ -320,7 -317,6 -317,6 +320,7 @@@@ F ext/misc/showauth.c 732578f0fe4ce42d5
   F ext/misc/spellfix.c 94df9bbfa514a563c1484f684a2df3d128a2f7209a84ca3ca100c68a0163e29f
   F ext/misc/sqlar.c 53e7d48f68d699a24f1a92e68e71eca8b3a9ff991fe9588c2a05bde103c6e7b7
   F ext/misc/stmt.c bc30d60d55e70d0133f10ac6103fe9336543f673740b73946f98758a2bb16dd7
-  F ext/misc/tclshext.c.in 732f33460956d1416f5ed7a991cfd367d3767f35ad20cb68343a54e50b32ba74 x
+++F ext/misc/tclshext.c.in eaf544f41cedc1a824e369ab782e3bf3e36f227c9da10d8bdf7187937f80e10a x
   F ext/misc/templatevtab.c 8a16a91a5ceaccfcbd6aaaa56d46828806e460dd194965b3f77bf38f14b942c4
   F ext/misc/totype.c fa4aedeb07f66169005dffa8de3b0a2b621779fd44f85c103228a42afa71853b
   F ext/misc/uint.c 053fed3bce2e89583afcd4bf804d75d659879bbcedac74d0fa9ed548839a030b
@@@@ -514,10 -510,10 -510,10 +514,10 @@@@ F ext/wasm/api/sqlite3-worker1.c-pp.js 
   F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8
   F ext/wasm/batch-runner.js 0dad6a02ad796f1003d3b7048947d275c4d6277f63767b8e685c27df8fdac93e
   F ext/wasm/c-pp.c 6d80d8569d85713effe8b0818a3cf51dc779e3f0bf8dc88771b8998552ee25b4
 --F ext/wasm/common/SqliteTestUtil.js 7adaeffef757d8708418dc9190f72df22367b531831775804b31598b44f6aa51
 ++F ext/wasm/common/SqliteTestUtil.js 7adaeffef757d8708418dc9190f72df22367b531831775804b31598b44f6aa51 x
   F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15
   F ext/wasm/common/testing.css 0ff15602a3ab2bad8aef2c3bd120c7ee3fd1c2054ad2ace7e214187ae68d926f
-  F ext/wasm/common/whwasmutil.js 749a1f81f85835e9a384e9706f2a955a7158f2b8cc9da33f41105cac7775a305
+  F ext/wasm/common/whwasmutil.js 03407d7b61b817fd135c82401987e56688a45ee4d6b9c0eced160c0000d6e4c2
   F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed
   F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508
   F ext/wasm/demo-123.js ebae30756585bca655b4ab2553ec9236a87c23ad24fc8652115dcedb06d28df6
@@@@ -573,16 -569,16 -569,16 +573,16 @@@@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47
   F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786
   F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a
   F sqlite_cfg.h.in baf2e409c63d4e7a765e17769b6ff17c5a82bbd9cbf1e284fd2e4cefaff3fcf2
 --F src/alter.c 3ff8c2fca0c0636d43459154bb40d79c882df1b34df77f89c4ec47ab2e2389f5
 --F src/analyze.c d4cc28738c29e009640ec20ebb6936ba6fcefff0d11aa93398d9bb9a5ead6c1f
 ++F src/alter.c 3ff8c2fca0c0636d43459154bb40d79c882df1b34df77f89c4ec47ab2e2389f5 x
 ++F src/analyze.c d4cc28738c29e009640ec20ebb6936ba6fcefff0d11aa93398d9bb9a5ead6c1f x
   F src/attach.c cc9d00d30da916ff656038211410ccf04ed784b7564639b9b61d1839ed69fd39
 --F src/auth.c 19b7ccacae3dfba23fc6f1d0af68134fa216e9040e53b0681b4715445ea030b4
 ++F src/auth.c 19b7ccacae3dfba23fc6f1d0af68134fa216e9040e53b0681b4715445ea030b4 x
   F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523
 --F src/bitvec.c 9eac5f42c11914d5ef00a75605bb205e934f435c579687f985f1f8b0995c8645
 --F src/btmutex.c 79a43670447eacc651519a429f6ece9fd638563cf95b469d6891185ddae2b522
 --F src/btree.c c0c93b6cb4dc133b528c1290bb4ad0f2414452f9a5758ff2b106af718874f39e
 ++F src/bitvec.c 9eac5f42c11914d5ef00a75605bb205e934f435c579687f985f1f8b0995c8645 x
 ++F src/btmutex.c 79a43670447eacc651519a429f6ece9fd638563cf95b469d6891185ddae2b522 x
-  F src/btree.c 481666a3dd26b1cb16a9e9baaa3f6b17cab52a1d7a5836e5dcf5b45b85d4a51d x
+++F src/btree.c c0c93b6cb4dc133b528c1290bb4ad0f2414452f9a5758ff2b106af718874f39e x
   F src/btree.h aa354b9bad4120af71e214666b35132712b8f2ec11869cb2315c52c81fad45cc
 --F src/btreeInt.h 3b4eff7155c0cea6971dc51f62e3529934a15a6640ec607dd42a767e379cb3a9
 ++F src/btreeInt.h 3b4eff7155c0cea6971dc51f62e3529934a15a6640ec607dd42a767e379cb3a9 x
   F src/build.c a8ae3b32d9aa9bbd2c0e97d7c0dd80def9fbca408425de1608f57ee6f47f45f4
   F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490
   F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
@@@@ -591,23 -587,23 -587,23 +591,23 @@@@ F src/date.c f73f203b3877cef866c60ab402
   F src/dbpage.c f3eea5f7ec47e09ee7da40f42b25092ecbe961fc59566b8e5f705f34335b2387
   F src/dbstat.c ec92074baa61d883de58c945162d9e666c13cd7cf3a23bc38b4d1c4d0b2c2bef
   F src/delete.c cd5f5cd06ed0b6a882ec1a8c2a0d73b3cecb28479ad19e9931c4706c5e2182be
-  F src/expr.c 36f6a47c8a2c20ec3c267a60fc598857876edd60af0cb40caf7b69b651fd73bf x
 --F src/expr.c 8d1656b65e26af3e34f78e947ac423f0d20c214ed25a67486e433bf16ca6b543
+++F src/expr.c 8d1656b65e26af3e34f78e947ac423f0d20c214ed25a67486e433bf16ca6b543 x
   F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
 --F src/fkey.c a7fcbf7e66d14dbb73cf49f31489ebf66d0e6006c62b95246924a3bae9f37b36
 --F src/func.c 8ada46d362a153b8dfb5385c3aaa9a7d75ebf306b33d2e663aa03920126a1bc3
 ++F src/fkey.c a7fcbf7e66d14dbb73cf49f31489ebf66d0e6006c62b95246924a3bae9f37b36 x
-  F src/func.c 6303e1ccb80dbd0d9b52f902a01d3b105981486fdfd66f9e1ddfd74aaf3032fc x
+++F src/func.c 8ada46d362a153b8dfb5385c3aaa9a7d75ebf306b33d2e663aa03920126a1bc3 x
   F src/global.c bd0892ade7289f6e20bff44c07d06371f2ff9b53cea359e7854b9b72f65adc30
 --F src/hash.c 9ee4269fb1d6632a6fecfb9479c93a1f29271bddbbaf215dd60420bcb80c7220
 ++F src/hash.c 9ee4269fb1d6632a6fecfb9479c93a1f29271bddbbaf215dd60420bcb80c7220 x
   F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51
 --F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6
 ++F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 x
   F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
   F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276
 --F src/json.c 14c474fb1249a46eb44e878e2361f36abfe686b134039b0d1883d93d61505b4a
 ++F src/json.c 14c474fb1249a46eb44e878e2361f36abfe686b134039b0d1883d93d61505b4a x
   F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa
 --F src/loadext.c 176d6b2cb18a6ad73b133db17f6fc351c4d9a2d510deebdb76c22bde9cfd1465
 --F src/main.c 5fd4b65d61ae6155f36756ed508a39b38b49355b031188961e8d923f43f4bc49
 ++F src/loadext.c 176d6b2cb18a6ad73b133db17f6fc351c4d9a2d510deebdb76c22bde9cfd1465 x
 ++F src/main.c 5fd4b65d61ae6155f36756ed508a39b38b49355b031188961e8d923f43f4bc49 x
   F src/malloc.c 47b82c5daad557d9b963e3873e99c22570fb470719082c6658bf64e3012f7d23
   F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
 --F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2
 ++F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2 x
   F src/mem2.c c8bfc9446fd0798bddd495eb5d9dbafa7d4b7287d8c22d50a83ac9daa26d8a75
   F src/mem3.c 30301196cace2a085cbedee1326a49f4b26deff0af68774ca82c1f7c06fda4f6
   F src/mem5.c b7da5c10a726aacacc9ad7cdcb0667deec643e117591cc69cf9b4b9e7f3e96ff
@@@@ -626,42 -621,39 -621,39 +626,42 @@@@ F src/os.h 1ff5ae51d339d0e30d8a9d814f4b
   F src/os_common.h 6c0eb8dd40ef3e12fe585a13e709710267a258e2c8dd1c40b1948a1d14582e06
   F src/os_kv.c 4d39e1f1c180b11162c6dc4aa8ad34053873a639bac6baae23272fc03349986a
   F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d872107
 --F src/os_unix.c 95b407307deb902a3bd9a5d5666c7838709cccb337baeee6ef0a53f512d3673e
 --F src/os_win.c 7038223a1cda0a47e2ab4db47f63bf1833fe53ba0542f0f283a062ea13894103
 ++F src/os_unix.c 95b407307deb902a3bd9a5d5666c7838709cccb337baeee6ef0a53f512d3673e x
 ++F src/os_win.c 7038223a1cda0a47e2ab4db47f63bf1833fe53ba0542f0f283a062ea13894103 x
   F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
 --F src/pager.c 5ddf3a74c633a008ea6b2f5b3186167e88e2c8ca8a252ecab06ab3f1eb48e60f
 ++F src/pager.c 5ddf3a74c633a008ea6b2f5b3186167e88e2c8ca8a252ecab06ab3f1eb48e60f x
   F src/pager.h f82e9844166e1585f5786837ddc7709966138ced17f568c16af7ccf946c2baa3
 --F src/parse.y 8828f9e15f04d469eab9c0f2aed504e534b1c97c68836bed6f07afab29c2ac0b
 --F src/pcache.c 4cd4a0043167da9ba7e19b4d179a0e6354e7fe32c16f781ecf9bf0a5ff63b40b
 ++F src/parse.y 8828f9e15f04d469eab9c0f2aed504e534b1c97c68836bed6f07afab29c2ac0b x
 ++F src/pcache.c 4cd4a0043167da9ba7e19b4d179a0e6354e7fe32c16f781ecf9bf0a5ff63b40b x
   F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5
 --F src/pcache1.c 602acb23c471bb8d557a6f0083cc2be641d6cafcafa19e481eba7ef4c9ca0f00
 --F src/pragma.c 37b8fb02d090262280c86e1e2654bf59d8dbfbfe8dc6733f2b968a11374c095a
 ++F src/pcache1.c 602acb23c471bb8d557a6f0083cc2be641d6cafcafa19e481eba7ef4c9ca0f00 x
 ++F src/pragma.c 37b8fb02d090262280c86e1e2654bf59d8dbfbfe8dc6733f2b968a11374c095a x
   F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7
 --F src/prepare.c d6c4354f8ea0dc06962fbabc4b68c4471a45276a2918c929be00f9f537f69eb1
  -F src/printf.c cb8357e7810848a2148acacfa8897c84905cadc3b040c2e7e0d2b000a3bcef82
 ++F src/prepare.c d6c4354f8ea0dc06962fbabc4b68c4471a45276a2918c929be00f9f537f69eb1 x
-  F src/printf.c b9320cdbeca0b336c3f139fd36dd121e4167dd62b35fbe9ccaa9bab44c0af38d
+ +F src/printf.c a87473be34fa2acafa27692b8ae078275c7e23360956c93c07ff22f5d609cbd7
   F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
 ++F src/resmanage.c b85f77821ea0280c73a6afc1dc3b6884ba3a402515c6bf27ba1567408e4f121d x
 ++F src/resmanage.h eb63130e49d7b696a33c6d39d1c437a050bbef42b4f6f4405a15fc563c7dd61e x
   F src/resolve.c 37953a5f36c60bea413c3c04efcd433b6177009f508ef2ace0494728912fe2e9
 --F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
 --F src/select.c 383b9dba12493c365ee2036bcadd73013b7c0f7d2afcda0c378317c335d60ac2
 - F src/shell.c.in 2c02c819349de410d63fcc0217763dfe5a42dbe58f2d68046d4ea8a376d12c26
  -F src/shell.c.in 63e3b76912c92ece8165577df0f7981808d52c5fd169fa5ab92d2e668f7d8bdd
 --F src/sqlite.h.in 3076d78836b6dac53b3ab0875fc8fd15bca8077aad4d33c85336e05af6aef8c7
 ++F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 x
 ++F src/select.c 383b9dba12493c365ee2036bcadd73013b7c0f7d2afcda0c378317c335d60ac2 x
-  F src/shell.c.in bfaedead984f71ca65601b7c2692516c00b144fcbb8634851233b45c95838a91 x
+++F src/shell.c.in 3d9053cee4ccb6744e4b18f1f980d037bfebc8a01c162fbc6f35e194858a4ab1
 ++F src/shext_linkage.h 4a686427844d5d2b71f2095cb032280fb262490795f0710487ebbedb3732f1cb x
 ++F src/sqlite.h.in 3076d78836b6dac53b3ab0875fc8fd15bca8077aad4d33c85336e05af6aef8c7 x
   F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
   F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4
-  F src/sqliteInt.h 8974b60740b108269f51e833e85191be6bf9f06f317ee34a53b7ec215762cf8c
+  F src/sqliteInt.h f7e904f7fdeccfd5606ca4457122e647dcbaf307ed8615ac4865f8b5f536f77b
 --F src/sqliteLimit.h 33b1c9baba578d34efe7dfdb43193b366111cdf41476b1e82699e14c11ee1fb6
 ++F src/sqliteLimit.h 33b1c9baba578d34efe7dfdb43193b366111cdf41476b1e82699e14c11ee1fb6 x
   F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749
   F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
 --F src/tclsqlite.c ecbc3c99c0d0c3ed122a913f143026c26d38d57f33e06bb71185dd5c1efe37cd
 --F src/test1.c e6ab4a00671f052366a01bcb7fdf2e2f6bb4aa884cd01e738c5590dcf47a99ca
 ++F src/tclsqlite.c ecbc3c99c0d0c3ed122a913f143026c26d38d57f33e06bb71185dd5c1efe37cd x
 ++F src/test1.c e6ab4a00671f052366a01bcb7fdf2e2f6bb4aa884cd01e738c5590dcf47a99ca x
   F src/test2.c 827446e259a3b7ab949da1542953edda7b5117982576d3e6f1c24a0dd20a5cef
 --F src/test3.c e5178558c41ff53236ae0271e9acb3d6885a94981d2eb939536ee6474598840e
 ++F src/test3.c e5178558c41ff53236ae0271e9acb3d6885a94981d2eb939536ee6474598840e x
   F src/test4.c 4533b76419e7feb41b40582554663ed3cd77aaa54e135cf76b3205098cd6e664
   F src/test5.c 328aae2c010c57a9829d255dc099d6899311672d
 --F src/test6.c e53bc69dc3cb3815fb74df74f38159ec05ba6dd5273216062e26bc797f925530
 --F src/test8.c ccc5d3e2a2bf7248f7da185e2afc4c08b4c6840447f5eb4dd106db165fddbdbc
 ++F src/test6.c e53bc69dc3cb3815fb74df74f38159ec05ba6dd5273216062e26bc797f925530 x
 ++F src/test8.c ccc5d3e2a2bf7248f7da185e2afc4c08b4c6840447f5eb4dd106db165fddbdbc x
   F src/test9.c 12e5ba554d2d1cbe0158f6ab3f7ffcd7a86ee4e5
   F src/test_async.c 195ab49da082053fdb0f949c114b806a49ca770a
   F src/test_autoext.c 915d245e736652a219a907909bb6710f0d587871
@@@@ -715,28 -705,28 -705,28 +715,28 @@@@ F src/trigger.c ad6ab9452715fa9a8075442
   F src/update.c 0aa36561167a7c40d01163238c297297962f31a15a8d742216b3c37cdf25f731
   F src/upsert.c 5303dc6c518fa7d4b280ec65170f465c7a70b7ac2b22491598f6d0b4875b3145
   F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0
-  F src/util.c 6f9d2f278dcc8d41c618980cd3cfe88e1bafc0626209b917c6773d8202d29ef6 x
 --F src/util.c a6b41c67ff2a5379b46b77e587b2e0adb2d2ddcc5669691674ca4d28e2755ae4
 --F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104
 --F src/vdbe.c c993304c609326cf625b4ad30cbb0e15a3f64c941cf2c9713d0c360b4abbaa98
 --F src/vdbe.h 41485521f68e9437fdb7ec4a90f9d86ab294e9bb8281e33b235915e29122cfc0
 --F src/vdbeInt.h 7bd49eef8f89c1a271fbf12d80a206bf56c876814c5fc6bee340f4e1907095ae
 --F src/vdbeapi.c de9703f8705afc393cc2864669ce28cf9516983c8331d59aa2b978de01634365
+++F src/util.c a6b41c67ff2a5379b46b77e587b2e0adb2d2ddcc5669691674ca4d28e2755ae4 x
 ++F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104 x
-  F src/vdbe.c 6c0de640ef3be08cf2992d588a7501aee0f1003027bc952a6916a35f6e33b4cf x
+++F src/vdbe.c c993304c609326cf625b4ad30cbb0e15a3f64c941cf2c9713d0c360b4abbaa98 x
 ++F src/vdbe.h 41485521f68e9437fdb7ec4a90f9d86ab294e9bb8281e33b235915e29122cfc0 x
 ++F src/vdbeInt.h 7bd49eef8f89c1a271fbf12d80a206bf56c876814c5fc6bee340f4e1907095ae x
 ++F src/vdbeapi.c de9703f8705afc393cc2864669ce28cf9516983c8331d59aa2b978de01634365 x
   F src/vdbeaux.c 4d5e68a3850d0b193a692eca6442d7afe35252aaf29728a67adcb542ecabd9ce
   F src/vdbeblob.c 2516697b3ee8154eb8915f29466fb5d4f1ae39ee8b755ea909cefaf57ec5e2ce
 --F src/vdbemem.c 710119a8e35e47813681c48703d65a80ba22792192de90bc51dc0d6366f2a79e
 --F src/vdbesort.c 0d40dca073c94e158ead752ef4225f4fee22dee84145e8c00ca2309afb489015
 ++F src/vdbemem.c 710119a8e35e47813681c48703d65a80ba22792192de90bc51dc0d6366f2a79e x
 ++F src/vdbesort.c 0d40dca073c94e158ead752ef4225f4fee22dee84145e8c00ca2309afb489015 x
   F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823
   F src/vdbevtab.c aae4bd769410eb7e1d02c42613eec961d514459b1c3c1c63cfc84e92a137daac
 --F src/vtab.c 1ecf8c3745d29275688d583e12822fa984d421e0286b5ef50c137bc3bf6d7a64
 ++F src/vtab.c 1ecf8c3745d29275688d583e12822fa984d421e0286b5ef50c137bc3bf6d7a64 x
   F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
 --F src/wal.c cbfeeb7415baa545efa244dd34bb5af4ae953a206fed720c6fa7f1ef763ec122
 ++F src/wal.c cbfeeb7415baa545efa244dd34bb5af4ae953a206fed720c6fa7f1ef763ec122 x
   F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a
   F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2
 --F src/where.c 2dc708cf8b6a691fb79f16bbc46567497ee6f991043318d421e294b2da114d93
 --F src/whereInt.h c7d19902863beadec1d04e66aca39c0bcd60b74f05f0eaa7422c7005dfc5d51a
 --F src/wherecode.c bff0bc56cb1a382de266c2db3a691135c18a4360b6ad5e069e5c415d57eb0c38
 --F src/whereexpr.c dc5096eca5ed503999be3bdee8a90c51361289a678d396a220912e9cb73b3c00
 --F src/window.c b7ad9cff3ce8ae6f8cc25e18e1a258426cb6bd2999aace6f5248d781b2a74098
 ++F src/where.c 94c90f838057e8f1ac6ab6e4caf64bbf487f4d0d33e64643731ff99e80a50b54 x
 ++F src/whereInt.h c7d19902863beadec1d04e66aca39c0bcd60b74f05f0eaa7422c7005dfc5d51a x
 ++F src/wherecode.c bff0bc56cb1a382de266c2db3a691135c18a4360b6ad5e069e5c415d57eb0c38 x
 ++F src/whereexpr.c dc5096eca5ed503999be3bdee8a90c51361289a678d396a220912e9cb73b3c00 x
 ++F src/window.c b7ad9cff3ce8ae6f8cc25e18e1a258426cb6bd2999aace6f5248d781b2a74098 x
   F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
   F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627
   F test/affinity3.test f094773025eddf31135c7ad4cde722b7696f8eb07b97511f98585addf2a510a9
@@@@ -1478,11 -1468,11 -1468,11 +1478,11 @@@@ F test/sharedA.test 64bdd21216dda2c6a3b
   F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707
   F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939
   F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
 --F test/shell1.test 300b77328aaafb9f3e7a53a26e4162fbf92181d92251d259ff105a2275ff998d
 --F test/shell2.test 35226c070a8c7f64fd016dfac2a0db2a40f709b3131f61daacd9dad61536c9cb
 --F test/shell3.test 91febeac0412812bf6370abb8ed72700e32bf8f9878849414518f662dfd55e8a
 --F test/shell4.test 9abd0c12a7e20a4c49e84d5be208d2124fa6c09e728f56f1f4bee0f02853935f
 --F test/shell5.test c8b6c54f26ec537f8558273d7ed293ca3725ef42e6b12b8f151718628bd1473b
 ++F test/shell1.test cac1fd2d681e0b79de9488069e4b2287da30788ce96a34788cbffbfe57e7b0a4
-  F test/shell2.test 71f2f0dbc9fc042f78bc4403b8d4cf0d5ff2ae5eb14285fdec6c1abc945bc87c
+++F test/shell2.test c4471c97c0da342c9b02160abd5263de118b4bbb411b3318ee2a179d7bf18a08
 ++F test/shell3.test 5dc710deede6e811e7af2bd91867d4128f023edc5a6cff6b8ad89e32af54c033
 ++F test/shell4.test 8116d7b9dbefe6e2237908afbd6738637e0426486373231d3ad8984471d4e04c
 ++F test/shell5.test ec82248dda87329f7dfced8637515b68db4ee75d94c8129dc017376fb61a8247
   F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3
   F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f
   F test/shell8.test 3fd093d481aaa94dc77fb73f1044c1f19c7efe3477a395cc4f7450133bc54915
@@@@ -2053,8 -2041,8 -2041,8 +2053,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 e9f2119106f687ecf9bc9c5f78c043ce7dd91874d3fb516d7621dbd806d8a174 61dfa92b44ad38a7aac76a09e167819ce5d0acace3e06ba9ed17b3264cc043c1
-  R 9a3f8a0af0f755f036bc314cbeda13d9
 - P 41580ba452fdbc3f73da60d8030289d38614c4cab8d24140d7cc44a54b2da8d2
 - R 479219669b5b9c2257f0511e6dc18154
  -P d758859f6ab94ddb9b3ee6f6f5f24b16e2b7a7712761080cfc6540d68b5a0c97
  -R a4c45eeed81feef61bc930db9802b9d0
 --U drh
 - Z e7211a376de07c93dd5c0d56ad175411
  -Z 04c984163e7286ef19d8dbc870dfbf03
+++P a61f9377014ee582ec469f1066196e07b745295724c6d3ff4baffcaed22ae5a1 24927c1377314a10177da4a57191593440aa97fd0c5949fdf25a22df1d947600 2f9d4444aa503102a00d6e6769dadc57d4b26a2c07f145f23f2f28e0c161246d
+++R bd3140ab1e0bb2a93e6d3c105e3f3689
 ++U larrybr
-  Z b13b22bfc690f89590baa97daf429a4b
+++Z 497f78a875664dfe4baa50f499afe15b
   # Remove this line to create a well-formed Fossil manifest.
diff --cc manifest.uuid
index 8dc805f7687d20e8d5bcec71423b5bd42c17b3c2,5f3f75a4be9d19b9d7e87ef4cf38e7b4a1c2310d,ceb4c37b4bfd90e262d5f2cafcad42c588e632eb..4b532a727a85f6299ff9ebd3bbf84610847aa174
@@@@ -1,1 -1,1 -1,1 +1,1 @@@@
-  a61f9377014ee582ec469f1066196e07b745295724c6d3ff4baffcaed22ae5a1
 - 24927c1377314a10177da4a57191593440aa97fd0c5949fdf25a22df1d947600
  -2f9d4444aa503102a00d6e6769dadc57d4b26a2c07f145f23f2f28e0c161246d
+++fe9aa2e9c17f33a8f6045e9cf755f8251d261c34f89a59d658b72f7c99a6a12c
diff --cc src/btree.c
index 7f79327324a9f9ada4412161119e72ee8980b49a,aa4e286013c415f2da9404845bc7f9259657d0c2,aa4e286013c415f2da9404845bc7f9259657d0c2..aa4e286013c415f2da9404845bc7f9259657d0c2
mode 100755,100644,100644..100755
diff --cc src/expr.c
index b0fe32a7eb12afb2153d960fc39345353bcf4916,a8e552f2c71b19bb4d864b2b951c7a4f366992bb,a8e552f2c71b19bb4d864b2b951c7a4f366992bb..a8e552f2c71b19bb4d864b2b951c7a4f366992bb
mode 100755,100644,100644..100755
diff --cc src/func.c
index 14f485d4dea57d326f9284c4840267cf94a4f78b,c505c37d69ac79bf009a444d9c8fab04dd41db59,c505c37d69ac79bf009a444d9c8fab04dd41db59..c505c37d69ac79bf009a444d9c8fab04dd41db59
mode 100755,100644,100644..100755
diff --cc src/shell.c.in
index 519dc68e3e651115b90a4f5796fd6a15b83e235d,2b0884e1c08202a2c3a8eca9890b900f1deb775a,fef9d45c324348bbbc97f1ad8118f75fbcdd572a..802a2cb57b1c2daf1e9192ead6f88e8a00be395f
mode 100755,100644,100644..100644
@@@@ -1566,13 -1198,55 -1198,54 +1566,52 @@@@ static char *shellFakeSchema
       cQuote = quoteChar(zCol);
       appendText(&s, zCol, cQuote);
     }
 ++  if( rc==SQLITE_NOMEM ) shell_out_of_memory();
     appendText(&s, ")", 0);
 --  sqlite3_finalize(pStmt);
 --  if( nRow==0 ){
 --    freeText(&s);
 --    s.z = 0;
 --  }
 --  return s.z;
 ++  if( nRow!=0 ) rv = takeText(&s);
 ++  RESOURCE_FREE(rm_mark);
 ++  return rv;
   }
   
 --
+  /*
+  ** SQL function:  strtod(X)
+  **
+  ** Use the C-library strtod() function to convert string X into a double.
+  ** Used for comparing the accuracy of SQLite's internal text-to-float conversion
+  ** routines against the C-library.
+  */
+  static void shellStrtod(
+    sqlite3_context *pCtx,
+    int nVal,
+    sqlite3_value **apVal
+  ){
+    char *z = (char*)sqlite3_value_text(apVal[0]);
+ +  UNUSED_PARAMETER(nVal);
+    if( z==0 ) return;
+    sqlite3_result_double(pCtx, strtod(z,0));
+  }
+  
+  /*
+  ** SQL function:  dtostr(X)
+  **
+  ** Use the C-library printf() function to convert real value X into a string.
+  ** Used for comparing the accuracy of SQLite's internal float-to-text conversion
+  ** routines against the C-library.
+  */
+  static void shellDtostr(
+    sqlite3_context *pCtx,
+    int nVal,
+    sqlite3_value **apVal
+  ){
+    double r = sqlite3_value_double(apVal[0]);
+    int n = nVal>=2 ? sqlite3_value_int(apVal[1]) : 26;
+    char z[400];
+    if( n<1 ) n = 1;
+    if( n>350 ) n = 350;
+    sprintf(z, "%#+.*e", n, r);
+    sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT);
+  }
+  
   /*
   ** SQL function:  shell_module_schema(X)
   **
@@@@ -1995,78 -1516,24 -1515,24 +2034,78 @@@@ typedef struct ShellInState 
     int *aiIndent;         /* Array of indents used in MODE_Explain */
     int nIndent;           /* Size of array aiIndent[] */
     int iIndent;           /* Index of current op in aiIndent[] */
 --  char *zNonce;          /* Nonce for temporary safe-mode escapes */
 ++  char *zNonce;          /* Nonce for temporary safe-mode suspension */
     EQPGraph sGraph;       /* Information for the graphical EXPLAIN QUERY PLAN */
     ExpertInfo expert;     /* Valid if previous command was ".expert OPT..." */
 ++
   #ifdef SQLITE_SHELL_FIDDLE
 --  struct {
 --    const char * zInput; /* Input string from wasm/JS proxy */
 --    const char * zPos;   /* Cursor pos into zInput */
 --    const char * zDefaultDbName; /* Default name for db file */
 --  } wasm;
 ++  const char * zDefaultDbName; /* Default name for db file (? not used here) */
 + #endif
  -};
 ++
 ++#if SHELL_DYNAMIC_EXTENSION
 ++  /* extension management */
 ++  int numExtLoaded;      /* Number of extensions presently loaded or emulated */
 ++  ShExtInfo *pShxLoaded; /* Tracking and use info for loaded shell extensions */
 ++  int ixExtPending;      /* Index of pending extension load operations if !0  */
 ++  /* scripting integration */
 ++  ScriptSupport *script; /* Scripting support, if any, from loaded extension */
 ++  ExtensionId scriptXid; /* Id of extension which is supporting scripting */
 ++  /* shell event subscription list */
 ++  int numSubscriptions;  /* Number of active entries in below list */
 ++  struct EventSubscription {
 ++    ExtensionId eid;
 ++    void *pvUserData;
 ++    ShellEventNotify eventHandler;
 ++  } *pSubscriptions;     /* The current shell event subscriptions */
 ++  u8 bDbDispatch;        /* Cache fact of dbShell dispatch table */
 ++  DotCommand *pUnknown;  /* Last registered "unknown" dot command */
 ++#endif
 ++
 ++#if SHELL_DATAIO_EXT
 ++  ExportHandler *pFreeformExporter;  /* Default free-form mode exporter */
 ++  ExportHandler *pColumnarExporter;  /* Default columnar mode exporter */
 ++  ExportHandler *pActiveExporter;    /* Presently active exporter */
  +#endif
 - };
 ++
 ++  ShellExState *pSXS;    /* Pointer to companion, exposed shell state */
 ++} ShellInState;
   
   #ifdef SQLITE_SHELL_FIDDLE
 --static ShellState shellState;
 ++/* For WASM, keep a static instance so pseudo-main can be called repeatedly. */
 ++static ShellInState shellStateI;
 ++static ShellExState shellStateX;
   #endif
   
 ++/*
 ++** Limit input nesting via .read or any other input redirect.
 ++** It's not too expensive, so a generous allowance can be made.
 ++*/
 ++#define MAX_INPUT_NESTING 25
 ++
 ++/*
 ++** This procedure updates the bSafeMode flag after completion of any
 ++** operation (dot-command, SQL submission, or script execution) that
 ++** counts as one for which safe mode might be suspended.
 ++** bSafeModeFuture has 3 states salient here:
 ++** equal 0 => Safe mode is and will remain inactive.
 ++** equal 1 => Safe mode is and will remain active.
 ++** N > 1 => Safe mode is suspended for N-1 operations.
 ++*/
 ++static void updateSafeMode(ShellInState *psi){
 ++  switch( psi->bSafeModeFuture ){
 ++  case 2:
 ++  default:
 ++    --psi->bSafeModeFuture;
 ++    deliberate_fall_through;
 ++  case 0:
 ++    psi->bSafeMode = 0;
 ++    break;
 ++  case 1:
 ++    psi->bSafeMode = 1;
 ++  }
 ++}
   
 --/* Allowed values for ShellState.autoEQP
 ++/* Allowed values for ShellInState.autoEQP
   */
   #define AUTOEQP_off      0           /* Automatic EXPLAIN QUERY PLAN is off */
   #define AUTOEQP_on       1           /* Automatic EQP is on */
@@@@ -2250,25 -1698,8 -1697,8 +2289,25 @@@@ static int failIfSafeMode
   }
   
   /*
 --** SQL function:   edit(VALUE)
 - **                 edit(VALUE,EDITOR)
 ++** Emit formatted output to shell's current output, possibly translated
 ++** for the legacy console on the Windows platform. This is exposed as
 ++** a helper for extensions so that they may share a common buffering
 ++** for FILE* output or share output capture when/if that is implemented.
 ++*/
 ++static void utf8_out_printf(ShellExState *p, const char *zFormat, ...){
 ++  va_list ap;
 ++  va_start(ap, zFormat);
 ++#if defined(_WIN32) || defined(WIN32)
 ++  vf_utf8_printf(ISS(p)->out, zFormat, ap);
 ++#else
 ++  vfprintf(ISS(p)->out, zFormat, ap);
 ++#endif
 ++  va_end(ap);
 ++}
 ++
 ++/*
 ++** SQL function:   edit(VALUE)
 + **                 edit(VALUE,EDITOR)
   **
   ** These steps:
   **
@@@@ -2413,100 -1842,19 -1841,19 +2452,100 @@@@ edit_func_end
   #endif /* SQLITE_NOHAVE_SYSTEM */
   
   /*
 --** Save or restore the current output mode
 ++** Save or restore the current output mode, partially per spec. (OM_STATE)
 ++*/
 ++typedef enum {
 ++  SWM_showHeader = 1, SWM_shellFlags = 2, SWM_mode = 4, SWM_cmOpts = 8,
 ++  SWM_colSeparator = 0x10, SWM_rowSeparator = 0x20, SWM_everything = 0x3F,
 ++  SWM_CountOf = 6
 ++} SaveWhatMode;
 ++
 ++/* This is available in most C89+ C compilers as offsetof(...), but since we
 ++ * cater to the most arcane C89-like compilers around, define it for sure:
 ++ */
 ++#define MEMBER_OFFSET(stype, member) ((size_t)&(((stype*)0)->member))
 ++#define MEMBER_SIZEOF(stype, member) (sizeof(((stype*)0)->member))
 ++static struct {
 ++  size_t offset;
 ++  size_t size;
 ++} outputModeCopy[] = {
 ++#define SS_MEMBER_COPY(mn) \
 ++  { MEMBER_OFFSET(ShellInState,mn), MEMBER_SIZEOF(ShellInState,mn) }
 ++  SS_MEMBER_COPY(showHeader), SS_MEMBER_COPY(shellFlgs),
 ++  SS_MEMBER_COPY(mode), SS_MEMBER_COPY(cmOpts),
 ++  SS_MEMBER_COPY(colSeparator), SS_MEMBER_COPY(rowSeparator)
 ++#undef SS_MEMBER_COPY
 ++};
 ++
 ++/* Allocate a buffer, copy requested output mode data to it, and return it.
 ++ * This never fails under OOM conditions. Instead, it returns 0.
 ++ */
 ++static OutputModeSave *outputModeSave(ShellInState *psi, SaveWhatMode w){
 ++  u16 what = (u16)w;
 ++  int i, nAlloc = sizeof(what)+1, mask = 1;
 ++  char *pSaved = 0, *pFill;
 ++  for( i=0; i<SWM_CountOf; mask<<=1, ++i ){
 ++    if( (what & mask)!=0 ) nAlloc += (int)outputModeCopy[i].size;
 ++  }
 ++  assert(i==ArraySize(outputModeCopy));
 ++  pSaved = sqlite3_malloc(nAlloc);
 ++  if( pSaved==0 ) return 0;
 ++  *(u16 *)pSaved = what;
 ++  pFill = pSaved + sizeof(what);
 ++  for( mask=1, i=0; i<SWM_CountOf; mask<<=1, ++i ){
 ++    if( (what & mask)!=0 ){
 ++      size_t nb = outputModeCopy[i].size;
 ++      memcpy(pFill, ((char*)psi)+outputModeCopy[i].offset, nb);
 ++      pFill += nb;
 ++    }
 ++  }
 ++  *pFill = 0xA5;
 ++  return (OutputModeSave *)pSaved;
 ++}
 ++
 ++/* From a buffer returned by outputModeSave, restore output mode data.
 ++ * The buffer is freed and its pointer is invalidated.
 ++ * If called with some other buffer, results are undefined, likely bad.
 ++ */
 ++static void outputModeRestore(ShellInState *psi, OutputModeSave *pSaved){
 ++  sqlite3_uint64 nA = sqlite3_msize(pSaved);
 ++  u16 what = (nA>sizeof(what))? *((u16 *)pSaved) : 0;
 ++  int i, nAlloc = sizeof(what)+1, mask = 1;
 ++  char *pTake = (char *)pSaved + sizeof(what);
 ++  for( i=0; i<SWM_CountOf && nAlloc<nA; mask<<=1, ++i ){
 ++    if( (what & mask)!=0 ){
 ++      size_t nb = outputModeCopy[i].size;
 ++      memcpy(((char*)psi)+outputModeCopy[i].offset, pTake, nb);
 ++      pTake += nb;
 ++    }
 ++  }
 ++  assert(*pTake==(char)0xA5);
 ++  sqlite3_free(pSaved);
 ++}
 ++
 ++/*
 ++** Save or restore the current output mode, in whole or in part.
 ++** A save may abort on OOM.
   */
 --static void outputModePush(ShellState *p){
 --  p->modePrior = p->mode;
 --  p->priorShFlgs = p->shellFlgs;
 --  memcpy(p->colSepPrior, p->colSeparator, sizeof(p->colSeparator));
 --  memcpy(p->rowSepPrior, p->rowSeparator, sizeof(p->rowSeparator));
 ++static void outputModePushSome(ShellInState *psi, SaveWhatMode w){
 ++  OutputModeSave *pOMS;
 ++  assert(psi->nSavedModes<MODE_STACK_MAX); /* Fail hard for this logic error. */
 ++  if( psi->nSavedModes>=MODE_STACK_MAX ) return;
 ++  pOMS = outputModeSave(psi, w);
 ++  shell_check_ooms(pOMS);
 ++  psi->pModeStack[psi->nSavedModes++] = pOMS;
  +}
 - static void outputModePop(ShellState *p){
 -   p->mode = p->modePrior;
 -   p->shellFlgs = p->priorShFlgs;
 -   memcpy(p->colSeparator, p->colSepPrior, sizeof(p->colSeparator));
 -   memcpy(p->rowSeparator, p->rowSepPrior, sizeof(p->rowSeparator));
 ++static void outputModePush(ShellInState *psi){
 ++  outputModePushSome(psi, SWM_everything);
 + }
  -static void outputModePop(ShellState *p){
  -  p->mode = p->modePrior;
  -  p->shellFlgs = p->priorShFlgs;
  -  memcpy(p->colSeparator, p->colSepPrior, sizeof(p->colSeparator));
  -  memcpy(p->rowSeparator, p->rowSepPrior, sizeof(p->rowSeparator));
 ++static void outputModePop(ShellInState *p){
 ++  OutputModeSave *pOMS;
 ++  assert(p->nSavedModes>0); /* Should not be here unless something pushed. */
 ++  if( p->nSavedModes==0 ) return;
 ++  pOMS = p->pModeStack[--p->nSavedModes];
 ++  assert(pOMS!=0);
 ++  p->pModeStack[p->nSavedModes] = 0;
 ++  outputModeRestore(p, pOMS);
   }
   
   /*
@@@@ -5691,40 -4725,422 -4724,422 +5730,40 @@@@ static int run_schema_dump_query
     return rc;
   }
   
 ++/* Configure help text generation to have coalesced secondary help lines
 ++ * with trailing newlines on all help lines. This allow help text to be
 ++ * representable as an array of two C-strings per dot-command.
 ++ */
 ++DISPATCH_CONFIG[
 ++  HELP_COALESCE=1
 ++];
 ++#define HELP_TEXT_FMTP ".%s"
 ++#define HELP_TEXT_FMTS "%s"
 ++/* Above HELP_COALESCE config and HELP_TEXT_FMT PP vars must track.
 ++ * Alternative is 0, ".%s\n" and "%s\n" .
 ++ */
 ++
 ++/* Forward references */
 ++static int showHelp(FILE *out, const char *zPattern, ShellExState *);
 ++static DotCmdRC process_input(ShellInState *psx);
 ++static DotCommand *builtInCommand(int ix);
 ++
   /*
 --** Text of help messages.
 ++** Read the content of file zName into memory obtained from sqlite3_malloc64()
 ++** and return a pointer to the buffer. The caller is responsible for freeing
 ++** the memory.
 + **
  -** The help text for each individual command begins with a line that starts
  -** with ".".  Subsequent lines are supplemental information.
 ++** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes
 ++** read.
 ++**
 ++** For convenience, a nul-terminator byte is always appended to the data read
 ++** from the file before the buffer is returned. This byte is not included in
 ++** the final value of (*pnByte), if applicable.
  +**
 - ** The help text for each individual command begins with a line that starts
 - ** with ".".  Subsequent lines are supplemental information.
 ++** NULL is returned if any error is encountered. The final value of *pnByte
 ++** is undefined in this case.
   **
 --** There must be two or more spaces between the end of the command and the
 --** start of the description of what that command does.
 --*/
 --static const char *(azHelp[]) = {
 --#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) \
 --  && !defined(SQLITE_SHELL_FIDDLE)
 --  ".archive ...             Manage SQL archives",
 --  "   Each command must have exactly one of the following options:",
 --  "     -c, --create               Create a new archive",
 --  "     -u, --update               Add or update files with changed mtime",
 --  "     -i, --insert               Like -u but always add even if unchanged",
 --  "     -r, --remove               Remove files from archive",
 --  "     -t, --list                 List contents of archive",
 --  "     -x, --extract              Extract files from archive",
 --  "   Optional arguments:",
 --  "     -v, --verbose              Print each filename as it is processed",
 --  "     -f FILE, --file FILE       Use archive FILE (default is current db)",
 --  "     -a FILE, --append FILE     Open FILE using the apndvfs VFS",
 --  "     -C DIR, --directory DIR    Read/extract files from directory DIR",
 --  "     -g, --glob                 Use glob matching for names in archive",
 --  "     -n, --dryrun               Show the SQL that would have occurred",
 --  "   Examples:",
 --  "     .ar -cf ARCHIVE foo bar  # Create ARCHIVE from files foo and bar",
 --  "     .ar -tf ARCHIVE          # List members of ARCHIVE",
 --  "     .ar -xvf ARCHIVE         # Verbosely extract files from ARCHIVE",
 --  "   See also:",
 --  "      http://sqlite.org/cli.html#sqlite_archive_support",
 --#endif
 --#ifndef SQLITE_OMIT_AUTHORIZATION
 --  ".auth ON|OFF             Show authorizer callbacks",
 --#endif
 --#ifndef SQLITE_SHELL_FIDDLE
 --  ".backup ?DB? FILE        Backup DB (default \"main\") to FILE",
 --  "   Options:",
 --  "       --append            Use the appendvfs",
 --  "       --async             Write to FILE without journal and fsync()",
 --#endif
 --  ".bail on|off             Stop after hitting an error.  Default OFF",
 --  ".binary on|off           Turn binary output on or off.  Default OFF",
 --#ifndef SQLITE_SHELL_FIDDLE
 --  ".cd DIRECTORY            Change the working directory to DIRECTORY",
 --#endif
 --  ".changes on|off          Show number of rows changed by SQL",
 --#ifndef SQLITE_SHELL_FIDDLE
 --  ".check GLOB              Fail if output since .testcase does not match",
 --  ".clone NEWDB             Clone data into NEWDB from the existing database",
 --#endif
 --  ".connection [close] [#]  Open or close an auxiliary database connection",
 --  ".databases               List names and files of attached databases",
 --  ".dbconfig ?op? ?val?     List or change sqlite3_db_config() options",
 --#if SQLITE_SHELL_HAVE_RECOVER
 --  ".dbinfo ?DB?             Show status information about the database",
 --#endif
 --  ".dump ?OBJECTS?          Render database content as SQL",
 --  "   Options:",
 --  "     --data-only            Output only INSERT statements",
 --  "     --newlines             Allow unescaped newline characters in output",
 --  "     --nosys                Omit system tables (ex: \"sqlite_stat1\")",
 --  "     --preserve-rowids      Include ROWID values in the output",
 --  "   OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump",
 --  "   Additional LIKE patterns can be given in subsequent arguments",
 --  ".echo on|off             Turn command echo on or off",
 --  ".eqp on|off|full|...     Enable or disable automatic EXPLAIN QUERY PLAN",
 --  "   Other Modes:",
 --#ifdef SQLITE_DEBUG
 --  "      test                  Show raw EXPLAIN QUERY PLAN output",
 --  "      trace                 Like \"full\" but enable \"PRAGMA vdbe_trace\"",
 --#endif
 --  "      trigger               Like \"full\" but also show trigger bytecode",
 --#ifndef SQLITE_SHELL_FIDDLE
 --  ".excel                   Display the output of next command in spreadsheet",
 --  "   --bom                   Put a UTF8 byte-order mark on intermediate file",
 --#endif
 --#ifndef SQLITE_SHELL_FIDDLE
 --  ".exit ?CODE?             Exit this program with return-code CODE",
 --#endif
 --  ".expert                  EXPERIMENTAL. Suggest indexes for queries",
 --  ".explain ?on|off|auto?   Change the EXPLAIN formatting mode.  Default: auto",
 --  ".filectrl CMD ...        Run various sqlite3_file_control() operations",
 --  "   --schema SCHEMA         Use SCHEMA instead of \"main\"",
 --  "   --help                  Show CMD details",
 --  ".fullschema ?--indent?   Show schema and the content of sqlite_stat tables",
 --  ".headers on|off          Turn display of headers on or off",
 --  ".help ?-all? ?PATTERN?   Show help text for PATTERN",
 --#ifndef SQLITE_SHELL_FIDDLE
 --  ".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.",
 --#endif
 --#ifndef SQLITE_OMIT_TEST_CONTROL
 --  ",imposter INDEX TABLE    Create imposter table TABLE on index INDEX",
 --#endif
 --  ".indexes ?TABLE?         Show names of indexes",
 --  "                           If TABLE is specified, only show indexes for",
 --  "                           tables matching TABLE using the LIKE operator.",
 --#ifdef SQLITE_ENABLE_IOTRACE
 --  ",iotrace FILE            Enable I/O diagnostic logging to FILE",
 --#endif
 --  ".limit ?LIMIT? ?VAL?     Display or change the value of an SQLITE_LIMIT",
 --  ".lint OPTIONS            Report potential schema issues.",
 --  "     Options:",
 --  "        fkey-indexes     Find missing foreign key indexes",
 --#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE)
 --  ".load FILE ?ENTRY?       Load an extension library",
 --#endif
 --#if !defined(SQLITE_SHELL_FIDDLE)
 --  ".log FILE|on|off         Turn logging on or off.  FILE can be stderr/stdout",
 --#else
 --  ".log on|off              Turn logging on or off.",
 --#endif
 --  ".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 --wrap 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",
 --#ifndef SQLITE_SHELL_FIDDLE
 --  ".nonce STRING            Suspend safe mode for one command if nonce matches",
 --#endif
 --  ".nullvalue STRING        Use STRING in place of NULL values",
 --#ifndef SQLITE_SHELL_FIDDLE
 --  ".once ?OPTIONS? ?FILE?   Output for the next SQL command only to FILE",
 --  "     If FILE begins with '|' then open as a pipe",
 --  "       --bom  Put a UTF8 byte-order mark at the beginning",
 --  "       -e     Send output to the system text editor",
 --  "       -x     Send output as CSV to a spreadsheet (same as \".excel\")",
 --  /* Note that .open is (partially) available in WASM builds but is
 --  ** currently only intended to be used by the fiddle tool, not
 --  ** end users, so is "undocumented." */
 --  ".open ?OPTIONS? ?FILE?   Close existing database and reopen FILE",
 --  "     Options:",
 --  "        --append        Use appendvfs to append database to the end of FILE",
 --#endif
 --#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",
 --#ifndef SQLITE_SHELL_FIDDLE
 --  ".output ?FILE?           Send output to FILE or stdout if FILE is omitted",
 --  "   If FILE begins with '|' then open it as a pipe.",
 --  "   Options:",
 --  "     --bom                 Prefix output with a UTF8 byte-order mark",
 --  "     -e                    Send output to the system text editor",
 --  "     -x                    Send output as CSV to a spreadsheet",
 --#endif
 --  ".parameter CMD ...       Manage SQL parameter bindings",
 --  "   clear                   Erase all bindings",
 --  "   init                    Initialize the TEMP table that holds bindings",
 --  "   list                    List the current parameter bindings",
 --  "   set PARAMETER VALUE     Given SQL parameter PARAMETER a value of VALUE",
 --  "                           PARAMETER should start with one of: $ : @ ?",
 --  "   unset PARAMETER         Remove PARAMETER from the binding table",
 --  ".print STRING...         Print literal STRING",
 --#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
 --  ".progress N              Invoke progress handler after every N opcodes",
 --  "   --limit N                 Interrupt after N progress callbacks",
 --  "   --once                    Do no more than one progress interrupt",
 --  "   --quiet|-q                No output except at interrupts",
 --  "   --reset                   Reset the count for each input and interrupt",
 --#endif
 --  ".prompt MAIN CONTINUE    Replace the standard prompts",
 --#ifndef SQLITE_SHELL_FIDDLE
 --  ".quit                    Stop interpreting input stream, exit if primary.",
 --  ".read FILE               Read input from FILE or command output",
 --  "    If FILE begins with \"|\", it is a command that generates the input.",
 --#endif
 --#if SQLITE_SHELL_HAVE_RECOVER
 --  ".recover                 Recover as much data as possible from corrupt db.",
 --  "   --ignore-freelist        Ignore pages that appear to be on db freelist",
 --  "   --lost-and-found TABLE   Alternative name for the lost-and-found table",
 --  "   --no-rowids              Do not attempt to recover rowid values",
 --  "                            that are not also INTEGER PRIMARY KEYs",
 --#endif
 --#ifndef SQLITE_SHELL_FIDDLE
 --  ".restore ?DB? FILE       Restore content of DB (default \"main\") from FILE",
 --  ".save ?OPTIONS? FILE     Write database to FILE (an alias for .backup ...)",
 --#endif
 --  ".scanstats on|off|est    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_\"",
 --  ",selftest ?OPTIONS?      Run tests defined in the SELFTEST table",
 --  "    Options:",
 --  "       --init               Create a new SELFTEST table",
 --  "       -v                   Verbose output",
 --  ".separator COL ?ROW?     Change the column and row separators",
 --#if defined(SQLITE_ENABLE_SESSION)
 --  ".session ?NAME? CMD ...  Create or control sessions",
 --  "   Subcommands:",
 --  "     attach TABLE             Attach TABLE",
 --  "     changeset FILE           Write a changeset into FILE",
 --  "     close                    Close one session",
 --  "     enable ?BOOLEAN?         Set or query the enable bit",
 --  "     filter GLOB...           Reject tables matching GLOBs",
 --  "     indirect ?BOOLEAN?       Mark or query the indirect status",
 --  "     isempty                  Query whether the session is empty",
 --  "     list                     List currently open session names",
 --  "     open DB NAME             Open a new session on DB",
 --  "     patchset FILE            Write a patchset into FILE",
 --  "   If ?NAME? is omitted, the first defined session is used.",
 --#endif
 --  ".sha3sum ...             Compute a SHA3 hash of database content",
 --  "    Options:",
 --  "      --schema              Also hash the sqlite_schema table",
 --  "      --sha3-224            Use the sha3-224 algorithm",
 --  "      --sha3-256            Use the sha3-256 algorithm (default)",
 --  "      --sha3-384            Use the sha3-384 algorithm",
 --  "      --sha3-512            Use the sha3-512 algorithm",
 --  "    Any other argument is a LIKE pattern for tables to hash",
 --#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
 --  ".shell CMD ARGS...       Run CMD ARGS... in a system shell",
 --#endif
 --  ".show                    Show the current values for various settings",
 --  ".stats ?ARG?             Show stats or turn stats on or off",
 --  "   off                      Turn off automatic stat display",
 --  "   on                       Turn on automatic stat display",
 --  "   stmt                     Show statement stats",
 --  "   vmstep                   Show the virtual machine step count only",
 --#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
 --  ".system CMD ARGS...      Run CMD ARGS... in a system shell",
 --#endif
 --  ".tables ?TABLE?          List names of tables matching LIKE pattern TABLE",
 --#ifndef SQLITE_SHELL_FIDDLE
 --  ",testcase NAME           Begin redirecting output to 'testcase-out.txt'",
 --#endif
 --  ",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",
 --#ifndef SQLITE_OMIT_TRACE
 --  ".trace ?OPTIONS?         Output each SQL statement as it is run",
 --  "    FILE                    Send output to FILE",
 --  "    stdout                  Send output to stdout",
 --  "    stderr                  Send output to stderr",
 --  "    off                     Disable tracing",
 --  "    --expanded              Expand query parameters",
 --#ifdef SQLITE_ENABLE_NORMALIZE
 --  "    --normalized            Normal the SQL statements",
 --#endif
 --  "    --plain                 Show SQL as it is input",
 --  "    --stmt                  Trace statement execution (SQLITE_TRACE_STMT)",
 --  "    --profile               Profile statements (SQLITE_TRACE_PROFILE)",
 --  "    --row                   Trace each row (SQLITE_TRACE_ROW)",
 --  "    --close                 Trace connection close (SQLITE_TRACE_CLOSE)",
 --#endif /* SQLITE_OMIT_TRACE */
 --#ifdef SQLITE_DEBUG
 --  ".unmodule NAME ...       Unregister virtual table modules",
 --  "    --allexcept             Unregister everything except those named",
 --#endif
 --  ".version                 Show source, library and compiler versions",
 --  ".vfsinfo ?AUX?           Information about the top-level VFS",
 --  ".vfslist                 List all available VFSes",
 --  ".vfsname ?AUX?           Print the name of the VFS stack",
 --  ".width NUM1 NUM2 ...     Set minimum column widths for columnar output",
 --  "     Negative values right-justify",
 --};
 --
 --/*
 --** Output help text.
 --**
 --** zPattern describes the set of commands for which help text is provided.
 --** If zPattern is NULL, then show all commands, but only give a one-line
 --** description of each.
 --**
 --** Return the number of matches.
 --*/
 --static int showHelp(FILE *out, const char *zPattern){
 --  int i = 0;
 --  int j = 0;
 --  int n = 0;
 --  char *zPat;
 --  if( zPattern==0
 --   || zPattern[0]=='0'
 --   || cli_strcmp(zPattern,"-a")==0
 --   || cli_strcmp(zPattern,"-all")==0
 --   || cli_strcmp(zPattern,"--all")==0
 --  ){
 --    enum HelpWanted { HW_NoCull = 0, HW_SummaryOnly = 1, HW_Undoc = 2 };
 --    enum HelpHave { HH_Undoc = 2, HH_Summary = 1, HH_More = 0 };
 --    /* Show all or most commands
 --    ** *zPattern==0   => summary of documented commands only
 --    ** *zPattern=='0' => whole help for undocumented commands
 --    ** Otherwise      => whole help for documented commands
 --    */
 --    enum HelpWanted hw = HW_SummaryOnly;
 --    enum HelpHave hh = HH_More;
 --    if( zPattern!=0 ){
 --      hw = (*zPattern=='0')? HW_NoCull|HW_Undoc : HW_NoCull;
 --    }
 --    for(i=0; i<ArraySize(azHelp); i++){
 --      switch( azHelp[i][0] ){
 --      case ',':
 --        hh = HH_Summary|HH_Undoc;
 --        break;
 --      case '.':
 --        hh = HH_Summary;
 --        break;
 --      default:
 --        hh &= ~HH_Summary;
 --        break;
 --      }
 --      if( ((hw^hh)&HH_Undoc)==0 ){
 --        if( (hh&HH_Summary)!=0 ){
 --          utf8_printf(out, ".%s\n", azHelp[i]+1);
 --          ++n;
 --        }else if( (hw&HW_SummaryOnly)==0 ){
 --          utf8_printf(out, "%s\n", azHelp[i]);
 --        }
 --      }
 --    }
 --  }else{
 --    /* Seek documented commands for which zPattern is an exact prefix */
 --    zPat = sqlite3_mprintf(".%s*", zPattern);
 --    shell_check_oom(zPat);
 --    for(i=0; i<ArraySize(azHelp); i++){
 --      if( sqlite3_strglob(zPat, azHelp[i])==0 ){
 --        utf8_printf(out, "%s\n", azHelp[i]);
 --        j = i+1;
 --        n++;
 --      }
 --    }
 --    sqlite3_free(zPat);
 --    if( n ){
 --      if( n==1 ){
 --        /* when zPattern is a prefix of exactly one command, then include
 --        ** the details of that command, which should begin at offset j */
 --        while( j<ArraySize(azHelp)-1 && azHelp[j][0]==' ' ){
 --          utf8_printf(out, "%s\n", azHelp[j]);
 --          j++;
 --        }
 --      }
 --      return n;
 --    }
 --    /* Look for documented commands that contain zPattern anywhere.
 --    ** Show complete text of all documented commands that match. */
 --    zPat = sqlite3_mprintf("%%%s%%", zPattern);
 --    shell_check_oom(zPat);
 --    for(i=0; i<ArraySize(azHelp); i++){
 --      if( azHelp[i][0]==',' ){
 --        while( i<ArraySize(azHelp)-1 && azHelp[i+1][0]==' ' ) ++i;
 --        continue;
 --      }
 --      if( azHelp[i][0]=='.' ) j = i;
 --      if( sqlite3_strlike(zPat, azHelp[i], 0)==0 ){
 --        utf8_printf(out, "%s\n", azHelp[j]);
 --        while( j<ArraySize(azHelp)-1 && azHelp[j+1][0]==' ' ){
 --          j++;
 --          utf8_printf(out, "%s\n", azHelp[j]);
 --        }
 --        i = j;
 --        n++;
 --      }
 --    }
 --    sqlite3_free(zPat);
 --  }
 --  return n;
 --}
 --
 --/* Forward reference */
 --static int process_input(ShellState *p);
 --
 --/*
 --** Read the content of file zName into memory obtained from sqlite3_malloc64()
 --** and return a pointer to the buffer. The caller is responsible for freeing
 --** the memory.
 --**
 --** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes
 --** read.
 --**
 --** For convenience, a nul-terminator byte is always appended to the data read
 --** from the file before the buffer is returned. This byte is not included in
 --** the final value of (*pnByte), if applicable.
 --**
 --** NULL is returned if any error is encountered. The final value of *pnByte
 --** is undefined in this case.
 ++** This function always returns; no abrupt OOM exits are taken.
   */
   static char *readFile(const char *zName, int *pnByte){
     FILE *in = fopen(zName, "rb");
@@@@ -5807,20 -5223,7 -5222,7 +5846,20 @@@@ static int session_filter(void *pCtx, c
   }
   #endif
   
  -/*
 ++#if SHELL_DYNAMIC_EXTENSION
 ++static int notify_subscribers(ShellInState *psi, NoticeKind nk, void *pvs) {
 ++  int six = 0;
 ++  int rcFlags = 0;
 ++  ShellExState *psx = XSS(psi);
 ++  while( six < psi->numSubscriptions ){
 ++    struct EventSubscription *pes = psi->pSubscriptions + six++;
 ++    rcFlags |= pes->eventHandler(pes->pvUserData, nk, pvs, psx);
 ++  }
 ++  return rcFlags;
 ++}
 ++#endif
 ++
  +/*
   ** Try to deduce the type of file for zName based on its content.  Return
   ** one of the SHELL_OPEN_* constants.
   **
@@@@ -6130,31 -5503,37 -5502,37 +6169,37 @@@@ static sqlite3 * open_db(ShellExState *
       }
   #endif
   
 --    sqlite3_create_function(p->db, "strtod", 1, SQLITE_UTF8, 0,
+++    sqlite3_create_function(GLOBAL_DB, "strtod", 1, SQLITE_UTF8, 0,
+                              shellStrtod, 0, 0);
 --    sqlite3_create_function(p->db, "dtostr", 1, SQLITE_UTF8, 0,
+++    sqlite3_create_function(GLOBAL_DB, "dtostr", 1, SQLITE_UTF8, 0,
+                              shellDtostr, 0, 0);
 --    sqlite3_create_function(p->db, "dtostr", 2, SQLITE_UTF8, 0,
+++    sqlite3_create_function(GLOBAL_DB, "dtostr", 2, SQLITE_UTF8, 0,
+                              shellDtostr, 0, 0);
 --    sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0,
 ++    sqlite3_create_function(GLOBAL_DB, "shell_add_schema", 3,SQLITE_UTF8,0,
                               shellAddSchemaName, 0, 0);
 --    sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0,
 ++    sqlite3_create_function(GLOBAL_DB, "shell_module_schema", 1,SQLITE_UTF8,0,
                               shellModuleSchema, 0, 0);
 --    sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p,
 ++    sqlite3_create_function(GLOBAL_DB, "shell_putsnl", 1,SQLITE_UTF8,psx,
                               shellPutsFunc, 0, 0);
 --    sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0,
 ++    sqlite3_create_function(GLOBAL_DB, "usleep", 1,SQLITE_UTF8,0,
                               shellUSleepFunc, 0, 0);
   #ifndef SQLITE_NOHAVE_SYSTEM
 --    sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0,
 ++    sqlite3_create_function(GLOBAL_DB, "edit", 1, SQLITE_UTF8, 0,
                               editFunc, 0, 0);
 --    sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0,
 ++    sqlite3_create_function(GLOBAL_DB, "edit", 2, SQLITE_UTF8, 0,
                               editFunc, 0, 0);
   #endif
 --
 --    if( p->openMode==SHELL_OPEN_ZIPFILE ){
 --      char *zSql = sqlite3_mprintf(
 --         "CREATE VIRTUAL TABLE zip USING zipfile(%Q);", zDbFilename);
 --      shell_check_oom(zSql);
 --      sqlite3_exec(p->db, zSql, 0, 0, 0);
 --      sqlite3_free(zSql);
 ++    if( psi->openMode==SHELL_OPEN_ZIPFILE ){
 ++      char *zSql = smprintf("CREATE VIRTUAL TABLE zip USING zipfile(%Q);",
 ++                            zDbFilename);
 ++      shell_check_ooms(zSql);
 ++      sstr_holder(zSql);
 ++      s3_exec_noom(DBX(psx), zSql, 0, 0, 0);
 ++      release_holder();
       }
   #ifndef SQLITE_OMIT_DESERIALIZE
 --    else
 --    if( p->openMode==SHELL_OPEN_DESERIALIZE || p->openMode==SHELL_OPEN_HEXDB ){
 ++    else if( psi->openMode==SHELL_OPEN_DESERIALIZE
 ++        || psi->openMode==SHELL_OPEN_HEXDB ){
         int rc;
         int nData = 0;
         unsigned char *aData;
@@@@ -6350,11 -5729,14 -5728,14 +6395,11 @@@@ static int booleanValue(const char *zAr
       for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){}
     }
     if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff);
 --  if( sqlite3_stricmp(zArg, "on")==0 || sqlite3_stricmp(zArg,"yes")==0 ){
 --    return 1;
  -  }
  -  if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){
  -    return 0;
 ++  for( i=0; zBoolNames[i]!=0; ++i ){
 ++    if( sqlite3_stricmp(zArg, zBoolNames[i])==0 ) return i&1;
     }
 -   if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){
 -     return 0;
 -   }
 --  utf8_printf(stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n",
 --          zArg);
 ++  utf8_printf(STD_ERR, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n",
 ++              zArg);
     return 0;
   }
   
     }
   }
   
 ++#if SHELL_DATAIO_EXT
 ++
   /*
 --** If an input line begins with "." then invoke this routine to
 --** process that line.
 --**
 --** Return 1 on error, 2 to exit, and 0 otherwise.
 ++** Standard ExportHandlers
 ++** These implement the built-in renderers of query results.
 ++** Two are provided, one for free-form results, the other for columnar results.
   */
 --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);
 --  }
 --#endif
 ++static void EH_FF_destruct(ExportHandler *pMe);
 ++static void EH_CM_destruct(ExportHandler *pMe);
 ++static const char *EH_FF_name(ExportHandler *pMe);
 ++static const char *EH_CM_name(ExportHandler *pMe);
 ++static const char *EH_help(ExportHandler *pMe, const char *zWhat);
 ++static int EH_FF_config(ExportHandler *pMe, int io, char **pzTell);
 ++static int EH_CM_config(ExportHandler *pMe, int io, char **pzTell);
 ++static int EH_openResultsOutStream(ExportHandler *pMe,
 ++                                   ShellExState *pSES, char **pzErr,
 ++                                   int numArgs, char *azArgs[],
 ++                                   const char * zName);
 ++static int EH_FF_prependResultsOut(ExportHandler *pMe,
 ++                                   ShellExState *pSES, char **pzErr,
 ++                                   sqlite3_stmt *pStmt);
 ++static int EH_CM_prependResultsOut(ExportHandler *pMe,
 ++                                   ShellExState *pSES, char **pzErr,
 ++                                   sqlite3_stmt *pStmt);
 ++static int EH_FF_rowResultsOut(ExportHandler *pMe,
 ++                               ShellExState *pSES, char **pzErr,
 ++                               sqlite3_stmt *pStmt);
 ++static int EH_CM_rowResultsOut(ExportHandler *pMe,
 ++                               ShellExState *pSES, char **pzErr,
 ++                               sqlite3_stmt *pStmt);
 ++static int EH_FF_appendResultsOut(ExportHandler *pMe,
 ++                                  ShellExState *pSES, char **pzErr,
 ++                                  sqlite3_stmt *pStmt);
 ++static int EH_CM_appendResultsOut(ExportHandler *pMe,
 ++                                  ShellExState *pSES, char **pzErr,
 ++                                  sqlite3_stmt *pStmt);
 ++static void EH_closeResultsOutStream(ExportHandler *pMe,
 ++                                     ShellExState *pSES,
 ++                                     char **pzErr);
 ++
 ++static VTABLE_NAME(ExportHandler) exporter_Vtab_FF = {
 ++  EH_FF_destruct,
 ++  EH_FF_name,
 ++  EH_help,
 ++  EH_FF_config,
 ++  EH_openResultsOutStream,
 ++  EH_FF_prependResultsOut,
 ++  EH_FF_rowResultsOut,
 ++  EH_FF_appendResultsOut,
 ++  EH_closeResultsOutStream
 ++};
   
 --  /* Parse the input line into tokens.
 --  */
 --  while( zLine[h] && nArg<ArraySize(azArg)-1 ){
 --    while( IsSpace(zLine[h]) ){ h++; }
 --    if( zLine[h]==0 ) break;
 --    if( zLine[h]=='\'' || zLine[h]=='"' ){
 --      int delim = zLine[h++];
 --      azArg[nArg++] = &zLine[h];
 --      while( zLine[h] && zLine[h]!=delim ){
 --        if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++;
 --        h++;
 --      }
 --      if( zLine[h]==delim ){
 --        zLine[h++] = 0;
 --      }
 --      if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
 --    }else{
 --      azArg[nArg++] = &zLine[h];
 --      while( zLine[h] && !IsSpace(zLine[h]) ){ h++; }
 --      if( zLine[h] ) zLine[h++] = 0;
 --      resolve_backslashes(azArg[nArg-1]);
 --    }
 ++static VTABLE_NAME(ExportHandler) exporter_Vtab_CM = {
 ++  EH_CM_destruct,
 ++  EH_CM_name,
 ++  EH_help,
 ++  EH_CM_config,
 ++  EH_openResultsOutStream,
 ++  EH_CM_prependResultsOut,
 ++  EH_CM_rowResultsOut,
 ++  EH_CM_appendResultsOut,
 ++  EH_closeResultsOutStream
 ++};
 ++
 ++typedef struct {
 ++  char **azCols; /* Names of result columns */
 ++  char **azVals; /* Results */
 ++  int *aiTypes;  /* Result types */
 ++} ColumnsInfo;
 ++
 ++typedef struct {
 ++  VTABLE_NAME(ExportHandler) *pMethods;
 ++  ShellInState *psi;
 ++  int nCol;
 ++  sqlite3_uint64 nRow;
 ++  void *pData;
 ++  void *pRowInfo;
 ++  ColumnsInfo colInfo;
 ++} BuiltInFFExporter;
 ++#define BI_FF_EXPORTER_INIT(psi) { & exporter_Vtab_FF, psi, 0, 0, 0, 0 }
 ++
 ++typedef struct {
 ++  VTABLE_NAME(ExportHandler) *pMethods;
 ++  ShellInState *psi;
 ++  int nCol;
 ++  sqlite3_uint64 nRow;
 ++  void *pData;
 ++  void *pRowInfo;
 ++  const char *colSep;
 ++  const char *rowSep;
 ++} BuiltInCMExporter;
 ++#define BI_CM_EXPORTER_INIT(psi) { & exporter_Vtab_CM, psi, 0, 0, 0, 0 }
 ++
 ++static void EH_FF_destruct(ExportHandler *pMe){
 ++  /* This serves two purposes: idempotent reinitialize, and final takedown */
 ++  BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
 ++  if( pbie->nCol!=0 ){
 ++    sqlite3_free(pbie->pData);
 ++    pbie->pData = 0;
 ++  }
 ++  pbie->nRow = 0;
 ++  pbie->nCol = 0;
 ++}
 ++
 ++static const char *zEmpty = "";
 ++
 ++static void EH_CM_destruct(ExportHandler *pMe){
 ++  /* This serves two purposes: idempotent reinitialize, and final takedown */
 ++  BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
 ++  if( pbie->nCol!=0 ){
 ++    sqlite3_uint64 nData = pbie->nCol * (pbie->nRow + 1), i;
 ++    const char *zNull = pbie->psi->nullValue;
 ++    char **azData = (char**)pbie->pData;
 ++    for(i=0; i<nData; i++){
 ++      char *z = azData[i];
 ++      if( z!=zEmpty && z!=zNull ) sqlite3_free(z);
 ++    }
 ++    sqlite3_free(pbie->pData);
 ++    sqlite3_free(pbie->pRowInfo);
 ++    pbie->pData = 0;
 ++    pbie->pRowInfo = 0;
 ++  }
 ++  pbie->nCol = 0;
 ++  pbie->nRow = 0;
 ++  pbie->colSep = 0;
 ++  pbie->rowSep = 0;
 ++}
 ++
 ++static int EH_FF_config(ExportHandler *pMe, int io, char **pzTell){
 ++  BuiltInCMExporter *pThis = (BuiltInCMExporter*)pMe;
 ++  UNUSED_PARAMETER(io);
 ++  UNUSED_PARAMETER(pzTell);
 ++  return SQLITE_OK;
 ++}
 ++static int EH_CM_config(ExportHandler *pMe, int io, char **pzTell){
 ++  BuiltInFFExporter *pThis = (BuiltInFFExporter*)pMe;
 ++  ShellInState *psi = pThis->psi;
 ++  if( io==0 && pzTell!=0 ){
 ++    *pzTell = smprintf("--wrap %d --wordwrap %s --%squote", psi->cmOpts.iWrap,
 ++                       psi->cmOpts.bWordWrap ? "on" : "off",
 ++                       psi->cmOpts.bQuote ? "" : "no");
     }
 --  azArg[nArg] = 0;
 ++  return SQLITE_OK;
 ++}
   
 --  /* Process the input line.
 --  */
 --  if( nArg==0 ) return 0; /* no tokens, no error */
 --  n = strlen30(azArg[0]);
 --  c = azArg[0][0];
 --  clearTempFile(p);
 ++static const char *zModeName(ShellInState *psi){
 ++  int mi = psi->mode;
 ++  return (mi>=0 && mi<MODE_COUNT_OF)? modeDescr[mi].zModeName : 0;
 ++}
 ++static const char *EH_FF_name(ExportHandler *pMe){
 ++  return zModeName(((BuiltInFFExporter*)pMe)->psi);
 ++}
 ++static const char *EH_CM_name(ExportHandler *pMe){
 ++  return zModeName(((BuiltInCMExporter*)pMe)->psi);
 ++}
   
 --#ifndef SQLITE_OMIT_AUTHORIZATION
 --  if( c=='a' && cli_strncmp(azArg[0], "auth", n)==0 ){
 --    if( nArg!=2 ){
 --      raw_printf(stderr, "Usage: .auth ON|OFF\n");
 --      rc = 1;
 --      goto meta_command_exit;
 ++static const char *EH_help(ExportHandler *pMe, const char *zWhat){
 ++  (void)(pMe);
 ++  (void)(zWhat);
 ++  return 0;
 ++}
 ++
 ++static int EH_openResultsOutStream(ExportHandler *pMe,
 ++                                   ShellExState *pSES, char **pzErr,
 ++                                   int numArgs, char *azArgs[],
 ++                                   const char * zName){
 ++  /* The built-in exporters have a predetermined destination, and their
 ++   * action is set by the shell state .mode member, so this method has
 ++   * nothing to do. For similar reasons, the shell never calls it. That
 ++   * could change if .mode command functionality is moved to here.
 ++   */
 ++  (void)(pMe);
 ++  (void)(pSES);
 ++  (void)(pzErr);
 ++  (void)(numArgs);
 ++  (void)(azArgs);
 ++  (void)(zName);
 ++  return 0;
 ++}
 ++
 ++static int EH_CM_prependResultsOut(ExportHandler *pMe,
 ++                                   ShellExState *psx, char **pzErr,
 ++                                   sqlite3_stmt *pStmt){
 ++  BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
 ++  ShellInState *psi = ISS(psx);
 ++  sqlite3_int64 nRow = 0;
 ++  char **azData = 0;
 ++  sqlite3_int64 nAlloc = 0;
 ++  char *abRowDiv = 0;
 ++  const unsigned char *uz;
 ++  const char *z;
 ++  char **azQuoted = 0;
 ++  int rc;
 ++  sqlite3_int64 i, nData;
 ++  int j, w, n;
 ++  const unsigned char **azNextLine = 0;
 ++  int bNextLine = 0;
 ++  int bMultiLineRowExists = 0;
 ++  int bw = psi->cmOpts.bWordWrap;
 ++  int nColumn = sqlite3_column_count(pStmt);
 ++
 ++  if( nColumn==0 ){
 ++    rc = sqlite3_step(pStmt);
 ++    assert(rc!=SQLITE_ROW);
 ++    return rc;
 ++  }
 ++  EH_CM_destruct(pMe);
 ++
 ++  nAlloc = nColumn*4;
 ++  if( nAlloc<=0 ) nAlloc = 1;
 ++  azData = sqlite3_malloc64( nAlloc*sizeof(char*) );
 ++  shell_check_ooms(azData);
 ++  azNextLine = sqlite3_malloc64( nColumn*sizeof(char*) );
 ++  shell_check_ooms((void*)azNextLine);
 ++  memset((void*)azNextLine, 0, nColumn*sizeof(char*) );
 ++  if( psi->cmOpts.bQuote ){
 ++    azQuoted = sqlite3_malloc64( nColumn*sizeof(char*) );
 ++    shell_check_ooms(azQuoted);
 ++    memset(azQuoted, 0, nColumn*sizeof(char*) );
 ++  }
 ++  abRowDiv = sqlite3_malloc64( nAlloc/nColumn );
 ++  shell_check_ooms(abRowDiv);
 ++  if( nColumn>psx->numWidths ){
 ++    psx->pSpecWidths = realloc(psx->pSpecWidths, (nColumn+1)*2*sizeof(int));
 ++    shell_check_oomm(psx->pSpecWidths);
 ++    for(i=psx->numWidths; i<nColumn; i++) psx->pSpecWidths[i] = 0;
 ++    psx->numWidths = nColumn;
 ++    psx->pHaveWidths = &psx->pSpecWidths[nColumn];
 ++  }
 ++  memset(psx->pHaveWidths, 0, nColumn*sizeof(int));
 ++  for(i=0; i<nColumn; i++){
 ++    w = psx->pSpecWidths[i];
 ++    if( w<0 ) w = -w;
 ++    psx->pHaveWidths[i] = w;
 ++  }
 ++  for(i=0; i<nColumn; i++){
 ++    const unsigned char *zNotUsed;
 ++    int wx = psx->pSpecWidths[i];
 ++    if( wx==0 ){
 ++      wx = psi->cmOpts.iWrap;
       }
 --    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);
 ++    if( wx<0 ) wx = -wx;
 ++    uz = (const unsigned char*)sqlite3_column_name(pStmt,i);
 ++    if( uz==0 ) uz = (u8*)"";
 ++    azData[i] = translateForDisplayAndDup(uz, &zNotUsed, wx, bw);
 ++  }
 ++  while( bNextLine || SQLITE_ROW==sqlite3_step(pStmt) ){
 ++    int useNextLine = bNextLine;
 ++    bNextLine = 0;
 ++    if( (nRow+2)*nColumn >= nAlloc ){
 ++      nAlloc *= 2;
 ++      azData = sqlite3_realloc64(azData, nAlloc*sizeof(char*));
 ++      shell_check_ooms(azData);
 ++      abRowDiv = sqlite3_realloc64(abRowDiv, nAlloc/nColumn);
 ++      shell_check_ooms(abRowDiv);
       }
 --  }else
 --#endif
 ++    abRowDiv[nRow] = 1;
 ++    nRow++;
 ++    for(i=0; i<nColumn; i++){
 ++      int wx = psx->pSpecWidths[i];
 ++      if( wx==0 ){
 ++        wx = psi->cmOpts.iWrap;
 ++      }
 ++      if( wx<0 ) wx = -wx;
 ++      if( useNextLine ){
 ++        uz = azNextLine[i];
 ++        if( uz==0 ) uz = (u8*)zEmpty;
 ++      }else if( psi->cmOpts.bQuote ){
 ++        sqlite3_free(azQuoted[i]);
 ++        azQuoted[i] = quoted_column(pStmt,i);
 ++        uz = (const unsigned char*)azQuoted[i];
 ++      }else{
 ++        uz = (const unsigned char*)sqlite3_column_text(pStmt,i);
 ++        if( uz==0 ) uz = (u8*)psi->nullValue;
 ++      }
 ++      azData[nRow*nColumn + i]
 ++        = translateForDisplayAndDup(uz, &azNextLine[i], wx, bw);
 ++      if( azNextLine[i] ){
 ++        bNextLine = 1;
 ++        abRowDiv[nRow-1] = 0;
 ++        bMultiLineRowExists = 1;
 ++      }
 ++    }
 ++  }
 ++  sqlite3_free((void*)azNextLine);
 ++  if( azQuoted ){
 ++    for(i=0; i<nColumn; i++) sqlite3_free(azQuoted[i]);
 ++    sqlite3_free(azQuoted);
 ++  }
 ++  if( nRow==0 ){
 ++    EH_CM_destruct(pMe);
 ++    return SQLITE_DONE;
 ++  }
   
 --#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) \
 --  && !defined(SQLITE_SHELL_FIDDLE)
 --  if( c=='a' && cli_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
 ++  nData = nColumn*(nRow+1);
   
 --#ifndef SQLITE_SHELL_FIDDLE
 --  if( (c=='b' && n>=3 && cli_strncmp(azArg[0], "backup", n)==0)
 --   || (c=='s' && n>=3 && cli_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( cli_strcmp(z, "-append")==0 ){
 --          zVfs = "apndvfs";
 --        }else
 --        if( cli_strcmp(z, "-async")==0 ){
 --          bAsync = 1;
 --        }else
 --        {
 --          utf8_printf(stderr, "unknown option: %s\n", azArg[j]);
 --          return 1;
 ++  for(i=0; i<nData; i++){
 ++    z = azData[i];
 ++    if( z==0 ) z = (char*)zEmpty;
 ++    n = strlenChar(z);
 ++    j = i%nColumn;
 ++    if( n>psx->pHaveWidths[j] ) psx->pHaveWidths[j] = n;
 ++  }
 ++  if( seenInterrupt ) goto done;
 ++  switch( psi->cMode ){
 ++    case MODE_Column: {
 ++      pbie->colSep = "  ";
 ++      pbie->rowSep = "\n";
 ++      if( psi->showHeader ){
 ++        for(i=0; i<nColumn; i++){
 ++          w = psx->pHaveWidths[i];
 ++          if( psx->pSpecWidths[i]<0 ) w = -w;
 ++          utf8_width_print(psi->out, w, azData[i]);
 ++          fputs(i==nColumn-1?"\n":"  ", psi->out);
 ++        }
 ++        for(i=0; i<nColumn; i++){
 ++          print_dashes(psi->out, psx->pHaveWidths[i]);
 ++          fputs(i==nColumn-1?"\n":"  ", psi->out);
           }
 --      }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;
         }
 ++      break;
       }
 --    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);
 ++    case MODE_Table: {
 ++      pbie->colSep = " | ";
 ++      pbie->rowSep = " |\n";
 ++      print_row_separator(psx, nColumn, "+");
 ++      fputs("| ", psi->out);
 ++      for(i=0; i<nColumn; i++){
 ++        w = psx->pHaveWidths[i];
 ++        n = strlenChar(azData[i]);
 ++        utf8_printf(psi->out, "%*s%s%*s",
 ++                    (w-n)/2, "", azData[i], (w-n+1)/2, "");
 ++        fputs(i==nColumn-1?" |\n":" | ", psi->out);
 ++      }
 ++      print_row_separator(psx, nColumn, "+");
 ++      break;
       }
 --    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;
 ++    case MODE_Markdown: {
 ++      pbie->colSep = " | ";
 ++      pbie->rowSep = " |\n";
 ++      fputs("| ", psi->out);
 ++      for(i=0; i<nColumn; i++){
 ++        w = psx->pHaveWidths[i];
 ++        n = strlenChar(azData[i]);
 ++        utf8_printf(psi->out, "%*s%s%*s",
 ++                    (w-n)/2, "", azData[i], (w-n+1)/2, "");
 ++        fputs(i==nColumn-1?" |\n":" | ", psi->out);
 ++      }
 ++      print_row_separator(psx, nColumn, "|");
 ++      break;
       }
 --    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;
 ++    case MODE_Box: {
 ++      pbie->colSep = " " BOX_13 " ";
 ++      pbie->rowSep = " " BOX_13 "\n";
 ++      print_box_row_separator(psx, nColumn, BOX_23, BOX_234, BOX_34);
 ++      utf8_printf(psi->out, BOX_13 " ");
 ++      for(i=0; i<nColumn; i++){
 ++        w = psx->pHaveWidths[i];
 ++        n = strlenChar(azData[i]);
 ++        utf8_printf(psi->out, "%*s%s%*s%s",
 ++            (w-n)/2, "", azData[i], (w-n+1)/2, "",
 ++            i==nColumn-1?" "BOX_13"\n":" "BOX_13" ");
 ++      }
 ++      print_box_row_separator(psx, nColumn, BOX_123, BOX_1234, BOX_134);
 ++      break;
       }
 --    close_db(pDest);
 --  }else
 --#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 ++  }
 ++ done:
 ++  pbie->nCol = nColumn;
 ++  pbie->pData = azData;
 ++  pbie->nRow = nRow;
 ++  if( bMultiLineRowExists ){
 ++    pbie->pRowInfo = abRowDiv;
 ++  }else{
 ++    pbie->pRowInfo = 0;
 ++    sqlite3_free(abRowDiv);
 ++  }
 ++  if( seenInterrupt ){
 ++    EH_CM_destruct(pMe);
 ++    return SQLITE_INTERRUPT;
 ++  }
 ++  return SQLITE_OK;
 ++}
   
 --  if( c=='b' && n>=3 && cli_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
 ++static int EH_CM_rowResultsOut(ExportHandler *pMe,
 ++                               ShellExState *psx, char **pzErr,
 ++                               sqlite3_stmt *pStmt){
 ++  BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
 ++  ShellInState *psi = pbie->psi;
 ++  sqlite3_int64 nRow = pbie->nRow;
 ++  int nColumn = pbie->nCol, j, w;
 ++  char **azData = (char**)(pbie->pData);
 ++  sqlite3_int64 nData = (nRow+1)*nColumn, i;
 ++  char *abRowDiv = pbie->pRowInfo;
 ++  const char *z;
   
 --  if( c=='b' && n>=3 && cli_strncmp(azArg[0], "binary", n)==0 ){
 --    if( nArg==2 ){
 --      if( booleanValue(azArg[1]) ){
 --        setBinaryMode(p->out, 1);
 --      }else{
 --        setTextMode(p->out, 1);
 ++  (void)(pzErr);
 ++  (void)(pStmt);
 ++  if( nRow==0 || nColumn==0 ) return SQLITE_INTERRUPT;
 ++  for(i=nColumn, j=0; i<nData; i++, j++){
 ++    if( j==0 && psi->cMode!=MODE_Column ){
 ++      utf8_printf(psi->out, "%s", psi->cMode==MODE_Box?BOX_13" ":"| ");
 ++    }
 ++    z = azData[i];
 ++    if( z==0 ) z = zEmpty;
 ++    w = psx->pHaveWidths[j];
 ++    if( psx->pSpecWidths[j]<0 ) w = -w;
 ++    utf8_width_print(psi->out, w, z);
 ++    if( j==nColumn-1 ){
 ++      utf8_printf(psi->out, "%s", pbie->rowSep);
 ++      if( abRowDiv!=0 && abRowDiv[i/nColumn-1] && i+1<nData ){
 ++        if( psi->cMode==MODE_Table ){
 ++          print_row_separator(psx, nColumn, "+");
 ++        }else if( psi->cMode==MODE_Box ){
 ++          print_box_row_separator(psx, nColumn, BOX_123, BOX_1234, BOX_134);
 ++        }else if( psi->cMode==MODE_Column ){
 ++          raw_printf(psi->out, "\n");
 ++        }
 ++      }
 ++      j = -1;
 ++      if( seenInterrupt ){
 ++        EH_CM_destruct(pMe);
 ++        return SQLITE_INTERRUPT;
         }
       }else{
 --      raw_printf(stderr, "Usage: .binary on|off\n");
 --      rc = 1;
 ++      utf8_printf(psi->out, "%s", pbie->colSep);
       }
 -   }else
 - 
 -   /* The undocumented ".breakpoint" command causes a call to the no-op
 -   ** routine named test_breakpoint().
 -   */
 -   if( c=='b' && n>=3 && cli_strncmp(azArg[0], "breakpoint", n)==0 ){
 -     test_breakpoint();
 --  }else
 ++  }
 ++  return SQLITE_DONE;
 ++}
   
 - #ifndef SQLITE_SHELL_FIDDLE
 -   if( c=='c' && cli_strcmp(azArg[0],"cd")==0 ){
 -     failIfSafeMode(p, "cannot run .cd in safe mode");
 -     if( nArg==2 ){
 - #if defined(_WIN32) || defined(WIN32)
 -       wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
 -       rc = !SetCurrentDirectoryW(z);
 -       sqlite3_free(z);
 - #else
 -       rc = chdir(azArg[1]);
 - #endif
 -       if( rc ){
 -         utf8_printf(stderr, "Cannot change to directory \"%s\"\n", azArg[1]);
 -         rc = 1;
 -       }
 -     }else{
 -       raw_printf(stderr, "Usage: .cd DIRECTORY\n");
 -       rc = 1;
 -     }
  -  /* The undocumented ".breakpoint" command causes a call to the no-op
  -  ** routine named test_breakpoint().
  -  */
  -  if( c=='b' && n>=3 && cli_strncmp(azArg[0], "breakpoint", n)==0 ){
  -    test_breakpoint();
 --  }else
 - #endif /* !defined(SQLITE_SHELL_FIDDLE) */
 ++static int EH_CM_appendResultsOut(ExportHandler *pMe,
 ++                                  ShellExState *psx, char **pzErr,
 ++                                  sqlite3_stmt *pStmt){
 ++  BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
 ++  ShellInState *psi = ISS(psx);
 ++  sqlite3_int64 nRow = pbie->nRow;
 ++  int nColumn = pbie->nCol;
 ++  char **azData = (char**)(pbie->pData);
 ++  sqlite3_int64 nData = (nRow+1)*nColumn;
   
 -   if( c=='c' && n>=3 && cli_strncmp(azArg[0], "changes", n)==0 ){
  -#ifndef SQLITE_SHELL_FIDDLE
  -  if( c=='c' && cli_strcmp(azArg[0],"cd")==0 ){
  -    failIfSafeMode(p, "cannot run .cd in safe mode");
 --    if( nArg==2 ){
 -       setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
  -#if defined(_WIN32) || defined(WIN32)
  -      wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
  -      rc = !SetCurrentDirectoryW(z);
  -      sqlite3_free(z);
  -#else
  -      rc = chdir(azArg[1]);
  -#endif
  -      if( rc ){
  -        utf8_printf(stderr, "Cannot change to directory \"%s\"\n", azArg[1]);
  -        rc = 1;
  -      }
 --    }else{
 -       raw_printf(stderr, "Usage: .changes on|off\n");
  -      raw_printf(stderr, "Usage: .cd DIRECTORY\n");
 --      rc = 1;
 --    }
 --  }else
  -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 ++  if( nRow==0 || nColumn==0 ) return SQLITE_INTERRUPT;
   
 - #ifndef SQLITE_SHELL_FIDDLE
 -   /* 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 && cli_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 ){
 -       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;
  -  if( c=='c' && n>=3 && cli_strncmp(azArg[0], "changes", n)==0 ){
  -    if( nArg==2 ){
  -      setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
 --    }else{
 -       utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase);
 -       p->nCheck++;
  -      raw_printf(stderr, "Usage: .changes on|off\n");
  -      rc = 1;
 --    }
 -     sqlite3_free(zRes);
 --  }else
 - #endif /* !defined(SQLITE_SHELL_FIDDLE) */
 ++  if( psi->cMode==MODE_Table ){
 ++    print_row_separator(psx, nColumn, "+");
 ++  }else if( psi->cMode==MODE_Box ){
 ++    print_box_row_separator(psx, nColumn, BOX_12, BOX_124, BOX_14);
 ++  }
 ++  EH_CM_destruct(pMe);
 ++  return SQLITE_OK;
 ++}
   
 --#ifndef SQLITE_SHELL_FIDDLE
 -   if( c=='c' && cli_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");
  -  /* 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 && cli_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 ){
  -      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
 --#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 ++static int EH_FF_prependResultsOut(ExportHandler *pMe,
 ++                                   ShellExState *pSES, char **pzErr,
 ++                                   sqlite3_stmt *pStmt){
 ++  BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
 ++  int nc = sqlite3_column_count(pStmt);
 ++  int rc;
   
 -   if( c=='c' && cli_strncmp(azArg[0], "connection", n)==0 ){
 -     if( nArg==1 ){
 -       /* List available connections */
  -#ifndef SQLITE_SHELL_FIDDLE
  -  if( c=='c' && cli_strncmp(azArg[0], "clone", n)==0 ){
  -    failIfSafeMode(p, "cannot run .clone in safe mode");
  -    if( nArg==2 ){
  -      tryToClone(p, azArg[1]);
 ++  pbie->pMethods->destruct(pMe);
 ++  if( nc>0 ){
 ++    /* allocate space for col name ptr, value ptr, and type */
 ++    pbie->pData = sqlite3_malloc64(3*nc*sizeof(const char*) + 1);
 ++    if( !pbie->pData ){
 ++      shell_out_of_memory();
 +     }else{
  -      raw_printf(stderr, "Usage: .clone FILENAME\n");
  -      rc = 1;
  -    }
  -  }else
  -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
  -
  -  if( c=='c' && cli_strncmp(azArg[0], "connection", n)==0 ){
  -    if( nArg==1 ){
  -      /* List available connections */
 ++      ColumnsInfo ci
 ++        = { (char **)pbie->pData, &ci.azCols[nc], (int *)&ci.azVals[nc] };
         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 && cli_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;
 ++      assert(sizeof(int) <= sizeof(char *));
 ++      pbie->nCol = nc;
 ++      pbie->colInfo = ci;
 ++      /* save off ptrs to column names */
 ++      for(i=0; i<nc; i++){
 ++        pbie->colInfo.azCols[i] = (char *)sqlite3_column_name(pStmt, i);
         }
 --    }else{
 --      raw_printf(stderr, "Usage: .connection [close] [CONNECTION-NUMBER]\n");
 --      rc = 1;
       }
 --  }else
 ++    return SQLITE_OK;
 ++  }
 ++  rc = sqlite3_step(pStmt);
 ++  assert(rc!=SQLITE_ROW);
 ++  return rc;
 ++}
   
 --  if( c=='d' && n>1 && cli_strncmp(azArg[0], "databases", n)==0 ){
 --    char **azName = 0;
 --    int nName = 0;
 --    sqlite3_stmt *pStmt;
 --    int i;
 --    open_db(p, 0);
 --    rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
 --    if( rc ){
 --      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 --      rc = 1;
 --    }else{
 --      while( sqlite3_step(pStmt)==SQLITE_ROW ){
 --        const char *zSchema = (const char *)sqlite3_column_text(pStmt,1);
 --        const char *zFile = (const char*)sqlite3_column_text(pStmt,2);
 --        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++;
 ++static int EH_FF_rowResultsOut(ExportHandler *pMe,
 ++                               ShellExState *pSES, char **pzErr,
 ++                               sqlite3_stmt *pStmt){
 ++  BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
 ++  ShellInState *psi = ISS(pSES);
 ++  int rc = sqlite3_step(pStmt);
 ++  int i, x, nc = pbie->nCol;
 ++  if( rc==SQLITE_ROW ){
 ++    ColumnsInfo *pc = &pbie->colInfo;
 ++    sqlite3_uint64 nr = ++(pbie->nRow);
 ++    for( i=0; i<nc; ++i ){
 ++      pc->aiTypes[i] = x = sqlite3_column_type(pStmt, i);
 ++      if( x==SQLITE_BLOB
 ++          && (psi->cMode==MODE_Insert || psi->cMode==MODE_Quote) ){
 ++        pc->azVals[i] = "";
 ++      }else{
 ++        pc->azVals[i] = (char*)sqlite3_column_text(pStmt, i);
         }
 --    }
 --    sqlite3_finalize(pStmt);
 --    for(i=0; i<nName; i++){
 --      int eTxn = sqlite3_txn_state(p->db, azName[i*2]);
 --      int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]);
 --      const char *z = azName[i*2+1];
 --      utf8_printf(p->out, "%s: %s %s%s\n",
 --         azName[i*2],
 --         z && z[0] ? z : "\"\"",
 --         bRdonly ? "r/o" : "r/w",
 --         eTxn==SQLITE_TXN_NONE ? "" :
 --            eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
 --      free(azName[i*2]);
 --      free(azName[i*2+1]);
 --    }
 --    sqlite3_free(azName);
 --  }else
 --
 --  if( c=='d' && n>=3 && cli_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        },
 --        { "reverse_scanorder",  SQLITE_DBCONFIG_REVERSE_SCANORDER     },
 --        { "stmt_scanstatus",    SQLITE_DBCONFIG_STMT_SCANSTATUS       },
 --        { "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 && cli_strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue;
 --      if( nArg>=3 ){
 --        sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0);
 ++      if( !pc->azVals[i] && (x!=SQLITE_NULL) ){
 ++        rc = SQLITE_NOMEM;
 ++        break; /* from for */
         }
 --      sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v);
 --      utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off");
 --      if( nArg>1 ) break;
       }
 --    if( nArg>1 && ii==ArraySize(aDbConfig) ){
 --      utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]);
 --      utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n");
 ++    /* if data and types extracted successfully... */
 ++    if( SQLITE_ROW==rc ){
 ++      /* call the supplied callback with the result row data */
 ++      if( shell_callback(pSES, nc, pc->azVals, pc->azCols, pc->aiTypes) ){
 ++        rc = SQLITE_ABORT;
 ++      }
       }
 --  }else
 ++  }
 ++  return rc;
 ++}
   
 --#if SQLITE_SHELL_HAVE_RECOVER
 --  if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbinfo", n)==0 ){
 --    rc = shell_dbinfo_command(p, nArg, azArg);
 --  }else
 ++static int EH_FF_appendResultsOut(ExportHandler *pMe,
 ++                                  ShellExState *pSES, char **pzErr,
 ++                                  sqlite3_stmt *pStmt){
 ++  BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
 ++  ShellInState *psi = ISS(pSES);
 ++  if( psi->cMode==MODE_Json ){
 ++    fputs("]\n", psi->out);
 ++  }else if( psi->cMode==MODE_Count ){
 ++    utf8_printf(psi->out, "%llu row%s\n", pbie->nRow, pbie->nRow!=1 ? "s" : "");
 ++  }
 ++  EH_FF_destruct(pMe);
 ++  return SQLITE_OK;
 ++}
   
 --  if( c=='r' && cli_strncmp(azArg[0], "recover", n)==0 ){
 --    open_db(p, 0);
 --    rc = recoverDatabaseCmd(p, nArg, azArg);
 --  }else
 --#endif /* SQLITE_SHELL_HAVE_RECOVER */
 ++static void EH_closeResultsOutStream(ExportHandler *pMe,
 ++                                     ShellExState *pSES,
 ++                                     char **pzErr){
 ++  /* The built-in exporters have a predetermined destination which is
 ++   * never "closed", so this method has nothing to do. For similar
 ++   * reasons, it is not called by the shell.
 ++   */
 ++  (void)(pMe);
 ++  (void)(pSES);
 ++  (void)(pzErr);
 ++}
 ++#endif /* SHELL_DATAIO_EXT */
   
 --  if( c=='d' && cli_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( cli_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( cli_strcmp(z,"newlines")==0 ){
 --          ShellSetFlag(p, SHFLG_Newlines);
 --        }else
 --        if( cli_strcmp(z,"data-only")==0 ){
 --          ShellSetFlag(p, SHFLG_DumpDataOnly);
 --        }else
 --        if( cli_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 SHELL_DYNAMIC_EXTENSION
   
 --        if( zLike ){
 --          zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr);
 --        }else{
 --          zLike = zExpr;
 --        }
 --      }
 ++/* 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_ooms(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 = s3_prepare_v2_noom(p->dbShell, zSql, -1, &pStmt, 0);
 ++    if( rc!=SQLITE_OK ) return rc;
 ++    psei->ppDotCommands
 ++      = sqlite3_realloc(psei->ppDotCommands, (nc+1)*sizeof(DotCommand *));
 ++    shell_check_ooms(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( cli_strcmp("unknown", zName)==0 ){
 ++        psi->pUnknown = pMC;
 ++        psei->pUnknown = pMC;
 ++      }
 ++      return SQLITE_OK;
 ++    }else{
 ++      psei->ppDotCommands[nc] = 0;
       }
 ++  }
 ++  return SQLITE_ERROR;
 ++}
   
 --    open_db(p, 0);
 ++/* Register an output data display (or other disposition) mode */
 ++static int register_exporter(ShellExState *p,
 ++                             ExtensionId eid, ExportHandler *pEH){
 ++  return SQLITE_ERROR;
 ++}
   
 --    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
 ++/* Register an import variation from (various sources) for .import */
 ++static int register_importer(ShellExState *p,
 ++                             ExtensionId eid, ImportHandler *pIH){
 ++  return SQLITE_ERROR;
 ++}
   
 --  if( c=='e' && cli_strncmp(azArg[0], "echo", n)==0 ){
 --    if( nArg==2 ){
 --      setOrClearFlag(p, SHFLG_Echo, azArg[1]);
 --    }else{
 --      raw_printf(stderr, "Usage: .echo on|off\n");
 --      rc = 1;
 --    }
 --  }else
 ++/* 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;
 ++}
   
 --  if( c=='e' && cli_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( cli_strcmp(azArg[1],"full")==0 ){
 --        p->autoEQP = AUTOEQP_full;
 --      }else if( cli_strcmp(azArg[1],"trigger")==0 ){
 --        p->autoEQP = AUTOEQP_trigger;
 --#ifdef SQLITE_DEBUG
 --      }else if( cli_strcmp(azArg[1],"test")==0 ){
 --        p->autoEQP = AUTOEQP_on;
 --        p->autoEQPtest = 1;
 --      }else if( cli_strcmp(azArg[1],"trace")==0 ){
 --        p->autoEQP = AUTOEQP_full;
 --        p->autoEQPtrace = 1;
 --        open_db(p, 0);
 --        sqlite3_exec(p->db, "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0);
 --        sqlite3_exec(p->db, "PRAGMA vdbe_trace=ON;", 0, 0, 0);
 --#endif
 --      }else{
 --        p->autoEQP = (u8)booleanValue(azArg[1]);
 --      }
 --    }else{
 --      raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n");
 --      rc = 1;
 --    }
 --  }else
 ++/* See registerAdHocCommand API in shext_linkage.h re detailed behavior.
 ++ * Depending on zHelp==0, either register or unregister treatment of
 ++ * 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;
   
 --#ifndef SQLITE_SHELL_FIDDLE
 --  if( c=='e' && cli_strncmp(azArg[0], "exit", n)==0 ){
 --    if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc);
 --    rc = 2;
 --  }else
 --#endif
 ++  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 = s3_prepare_v2_noom(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;
 ++}
   
 --  /* The ".explain" command is automatic now.  It is largely pointless.  It
 --  ** retained purely for backwards compatibility */
 --  if( c=='e' && cli_strncmp(azArg[0], "explain", n)==0 ){
 --    int val = 1;
 --    if( nArg>=2 ){
 --      if( cli_strcmp(azArg[1],"auto")==0 ){
 --        val = 99;
 ++/*
 ++ * 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{
 --        val =  booleanValue(azArg[1]);
 ++        ++pes;
         }
       }
 --    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;
 ++    if( psi->numSubscriptions==0 ){
 ++      sqlite3_free(psi->pSubscriptions);
 ++      psi->pSubscriptions = 0;
       }
 --  }else
 ++    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;
 ++  }
 ++}
   
 --#ifndef SQLITE_OMIT_VIRTUALTABLE
 --  if( c=='e' && cli_strncmp(azArg[0], "expert", n)==0 ){
 --    if( p->bSafeMode ){
 --      raw_printf(stderr,
 --        "Cannot run experimental commands such as \"%s\" in safe mode\n",
 --        azArg[0]);
 --      rc = 1;
 --    }else{
 --      open_db(p, 0);
 --      expertDotCommand(p, azArg, nArg);
 --    }
 --  }else
 --#endif
 ++/*
 ++ * 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;
 ++  }
 ++}
   
 --  if( c=='f' && cli_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;
 ++static struct InSource *currentInputSource(ShellExState *p){
 ++  return ISS(p)->pInSource;
 ++}
   
 --    open_db(p, 0);
 --    zCmd = nArg>=2 ? azArg[1] : "help";
 ++static int nowInteractive(ShellExState *p){
 ++  return INSOURCE_IS_INTERACTIVE(ISS(p)->pInSource);
 ++}
   
 --    if( zCmd[0]=='-'
 --     && (cli_strcmp(zCmd,"--schema")==0 || cli_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];
 --    }
 ++static const char *shellInvokedAs(void){
 ++  return Argv0;
 ++}
   
 --    /* The argument can optionally begin with "-" or "--" */
 --    if( zCmd[0]=='-' && zCmd[1] ){
 --      zCmd++;
 --      if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 --    }
 ++static const char *shellStartupDir(void){
 ++  return startupDir;
 ++}
   
 --    /* --help lists all file-controls */
 --    if( cli_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);
 --      }
 --      rc = 1;
 --      goto meta_command_exit;
 --    }
 ++static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths);
 ++static DotCommand * findDotCommand(const char *, ShellExState *, int *);
 ++static DotCmdRC runDotCommand(DotCommand*, char *[], int na, ShellExState*);
   
 --    /* 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( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
 --        if( filectrl<0 ){
 --          filectrl = aCtrl[i].ctrlCode;
 --          iCtrl = i;
 --        }else{
 --          utf8_printf(stderr, "Error: ambiguous file-control: \"%s\"\n"
 --                              "Use \".filectrl --help\" for help\n", zCmd);
 --          rc = 1;
 --          goto meta_command_exit;
 --        }
 ++static ExtensionHelpers extHelpers = {
 ++  13,
 ++  {
 ++    failIfSafeMode,
 ++    utf8_out_printf,
 ++    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);
 ++}
 ++
 ++/* Free the memory held by a ShExtInfo object but not the object itself.
 ++ * No notifications associated with takedown and termination are done. */
 ++static void free_ShExtInfo( ShExtInfo *psei ){
 ++  if( psei ){
 ++    if( psei->ppDotCommands ) sqlite3_free(psei->ppDotCommands);
 ++    if( psei->ppExportHandlers ) sqlite3_free(psei->ppExportHandlers);
 ++    if( psei->ppImportHandlers ) sqlite3_free(psei->ppImportHandlers);
 ++    memset(psei, 0, sizeof(ShExtInfo));
 ++  }
 ++}
 ++
 ++/* 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 = SHEXT_INFO_INIT;
 ++  AnyResourceHolder arh_sei = {&sei, (GenericFreer)free_ShExtInfo};
 ++  ResourceMark mark = holder_mark();
 ++
 ++  sstr_ptr_holder(&zErr);
 ++  /* 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));
 ++  shell_check_ooms(psi->pShxLoaded);
 ++  /* The ShellInState object now owns above allocation, so initialize it. */
 ++  memset(psi->pShxLoaded, 0, 2*sizeof(ShExtInfo));
 ++  any_ref_holder(&arh_sei); /* protect against early aborts */
 ++  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<(int)numCommands; ++ic ){
 ++    sei.ppDotCommands[ic] = builtInCommand(ic);
 ++  }
 ++  sei.numDotCommands = ic;
 ++  zSql = "INSERT INTO "SHELL_DISP_TAB"(name, extIx, cmdIx) VALUES(?, 0, ?)";
 ++  rc1 = s3_prepare_v2_noom(psx->dbShell, zSql, -1, &pStmt, 0);
 ++  stmt_holder(pStmt);
 ++  rc2 = s3_exec_noom(psx->dbShell, "BEGIN TRANSACTION", 0, 0, &zErr);
 ++  if( rc1!=SQLITE_OK || rc2!=SQLITE_OK ){
 ++    rc = SQLITE_ERROR;
 ++  }else{
 ++    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);
 ++      shell_check_nomem(sqlite3_bind_text(pStmt, 1, zName, -1, 0));
 ++      sqlite3_bind_int(pStmt, 2, ic);
 ++      rc = s3_step_noom(pStmt);
 ++      if( rc!=SQLITE_DONE ){
 ++        sqlite3_exec(psx->dbShell, "ABORT", 0, 0, 0);
 ++        break;
         }
       }
 --    if( filectrl<0 ){
 --      utf8_printf(stderr,"Error: unknown file-control: %s\n"
 --                         "Use \".filectrl --help\" for help\n", zCmd);
 ++    if( rc!=SQLITE_DONE ){
 ++      rc = SQLITE_ERROR;
 ++      zSql = "ABORT";
       }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;
 --        }
 --      }
 ++      rc = SQLITE_OK;
 ++      zSql = "COMMIT";
       }
 --    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);
 ++    rc2 = s3_exec_noom(psx->dbShell, zSql, 0, 0, &zErr);
 ++    if( SQLITE_OK==rc ){
 ++      /* Transfer just-built ShExtInfo to ShellInState use and ownership. */
 ++      psi->pShxLoaded[psi->numExtLoaded++] = sei;
 ++      arh_sei.pAny = 0;
 ++      sqlite3_enable_load_extension(psx->dbShell, 1);
 ++      psi->bDbDispatch = 1;
       }
 --  }else
 ++  }
 ++  RESOURCE_FREE(mark);
   
 --  if( c=='f' && cli_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
 ++  return rc;
 ++}
 + 
  -  if( c=='h' && cli_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;
 ++/* Call one loaded extension's destructors, in reverse order of their
 ++ * objects' creation.
 ++ */
 ++static void run_one_shext_dtors(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);
       }
 -     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
 ++  }
 ++  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);
  +    }
 -   }else
 - 
 -   if( c=='h' && cli_strncmp(azArg[0], "headers", n)==0 ){
 -     if( nArg==2 ){
 -       p->showHeader = booleanValue(azArg[1]);
 -       p->shellFlgs |= SHFLG_HeaderSet;
 ++  }
 ++  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);
 ++    }
 ++  }
 ++  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 dynamic 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];
 ++      run_one_shext_dtors(psei);
 ++      free_ShExtInfo(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;
 ++      }
 ++    }
 ++    free_ShExtInfo(psi->pShxLoaded);
 ++    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); /* unregister */
 ++  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 ){
 ++      run_one_shext_dtors(psi->pShxLoaded+psi->ixExtPending);
 ++      free_ShExtInfo(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 = smprintf("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 macro invocation. (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>}, 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 ARCHIVE_ENABLE && !defined(SQLITE_SHELL_FIDDLE));
 ++/*****************
 ++ * 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 ? 2 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.
 ++ */
 ++CONDITION_COMMAND(backup !defined(SQLITE_SHELL_FIDDLE));
 ++CONDITION_COMMAND(save !defined(SQLITE_SHELL_FIDDLE) );
 ++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;
 ++}
 ++
 ++CONDITION_COMMAND(cd !defined(SQLITE_SHELL_FIDDLE));
 ++/*****************
 ++ * 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;
 ++  else{
 ++#if defined(_WIN32) || defined(WIN32)
 ++    wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
 ++    shell_check_ooms(z);
 ++    rc = (z)? !SetCurrentDirectoryW(z) : 1;
 ++    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;
 ++}
 ++
 ++CONDITION_COMMAND(check !defined(SQLITE_SHELL_FIDDLE));
 ++CONDITION_COMMAND(clone !defined(SQLITE_SHELL_FIDDLE));
 ++/*****************
 ++ * 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;
 ++  DotCmdRC rv = DCR_Ok;
 ++  output_reset(ISS(p));
 ++  if( nArg!=2 ){
 ++    return DCR_ArgWrong;
 ++  }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){
 ++    *pzErr = smprintf("Error: cannot read 'testcase-out.txt'\n");
 ++    rv = DCR_Return;
 ++  }else if( testcase_glob(azArg[1],zRes)==0 ){
 ++    *pzErr =
 ++      smprintf("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 (zRes==0)? 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 = DBX(p);
 ++      psi->pAuxDb = &psi->aAuxDb[i];
 ++#if SHELL_DYNAMIC_EXTENSION
 ++      if( DBX(p)!=0 ) notify_subscribers(psi, NK_DbUserVanishing, DBX(p));
 ++#endif
 ++      globalDb = DBX(p) = psi->pAuxDb->db;
 ++#if SHELL_DYNAMIC_EXTENSION
 ++      if( DBX(p)!=0 ) notify_subscribers(psi, NK_DbUserAppeared, DBX(p));
 ++#endif
 ++      psi->pAuxDb->db = 0;
 ++    }
 ++  }else if( nArg==3 && cli_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;
 ++}
 ++
 ++CONDITION_COMMAND(dbinfo SQLITE_SHELL_HAVE_RECOVER);
 ++/*****************
 ++ * 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 = 0;
 ++  sqlite3 *db = open_db(p, 0);
 ++
 ++  rc = s3_prepare_v2_noom(db, "PRAGMA database_list", -1, &pStmt, 0);
 ++  stmt_holder(pStmt);
 ++  if( rc || pStmt==0 ){
 ++    *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
 ++    rc = 1;
 ++  }else{
 ++    while( s3_step_noom(pStmt)==SQLITE_ROW ){
 ++      int eTxn, bRdonly;
 ++      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;
 ++      eTxn = sqlite3_txn_state(db, zSchema);
 ++      bRdonly = sqlite3_db_readonly(db, zSchema);
 ++      utf8_printf(ISS(p)->out, "%s: %s %s%s\n",
 ++                  zSchema,
 ++                  zFile[0] ? zFile : "\"\"",
 ++                  bRdonly ? "r/o" : "r/w",
 ++                  eTxn==SQLITE_TXN_NONE ? "" :
 ++                  eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
 ++    }
 ++  }
 ++  release_holder();
 ++  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        },
 ++    { "reverse_scanorder",  SQLITE_DBCONFIG_REVERSE_SCANORDER     },
 ++    { "stmt_scanstatus",    SQLITE_DBCONFIG_STMT_SCANSTATUS       },
 ++    { "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 && cli_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;
 ++  sstr_ptr_holder(&zLike);
 ++  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( cli_strcmp(z,"preserve-rowids")==0 ){
 ++#ifdef SQLITE_OMIT_VIRTUALTABLE
 ++        *pzErr = smprintf("The --preserve-rowids option is not compatible"
 ++                          " with SQLITE_OMIT_VIRTUALTABLE\n");
 ++        release_holder();
 ++        return DCR_ArgWrong;
 ++#else
 ++        ShellSetFlag(p, SHFLG_PreserveRowid);
 ++#endif
 ++      }else{
 ++        if( cli_strcmp(z,"newlines")==0 ){
 ++          ShellSetFlag(p, SHFLG_Newlines);
 ++        }else if( cli_strcmp(z,"data-only")==0 ){
 ++          ShellSetFlag(p, SHFLG_DumpDataOnly);
 ++        }else if( cli_strcmp(z,"nosys")==0 ){
 ++          ShellSetFlag(p, SHFLG_DumpNoSys);
 ++        }else if( cli_strcmp(z,"schema")==0 && ++i<nArg ){
 ++          zSchema = azArg[i];
 ++        }else{
 ++          *pzErr = smprintf("Unknown option \"%s\" on \".dump\"\n", azArg[i]);
 ++          release_holder();
 ++          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]
 ++                    );
 ++
 ++      shell_check_ooms(zExpr);
 ++      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);
 ++  shell_check_ooms(zSql);
 ++  sstr_ptr_holder(&zSql);
 ++  run_schema_dump_query(psi,zSql);
 ++  if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 ++    sqlite3_free(zSql);
 ++    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);
 ++  }
 ++  release_holder(); /* zSql */
 ++  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;
 ++  release_holder(); /* zLike */
 ++
 ++  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( cli_strcmp(azArg[1],"full")==0 ){
 ++      psi->autoEQP = AUTOEQP_full;
 ++    }else if( cli_strcmp(azArg[1],"trigger")==0 ){
 ++      psi->autoEQP = AUTOEQP_trigger;
 ++#ifdef SQLITE_DEBUG
 ++    }else if( cli_strcmp(azArg[1],"test")==0 ){
 ++      psi->autoEQP = AUTOEQP_on;
 ++      psi->autoEQPtest = 1;
 ++    }else if( cli_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;
 ++}
 ++
 ++CONDITION_COMMAND(cease !defined(SQLITE_SHELL_FIDDLE));
 ++CONDITION_COMMAND(exit !defined(SQLITE_SHELL_FIDDLE));
 ++CONDITION_COMMAND(quit !defined(SQLITE_SHELL_FIDDLE));
 ++/*****************
 ++ * 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                    Stop interpreting input stream, done if primary.",
 ++];
 ++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(). */
 ++#ifdef SHELL_EXIT_EXITS_PROCESS
 ++    terminate_actions();
 ++    exit(rc);
 ++#else
 ++    p->shellAbruptExit = 0x100|rc;
 ++#endif
 ++  }
 ++  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);
 ++
 ++  for(i=1; i<nArg; i++){
 ++    char *z = azArg[i];
 ++    int n;
 ++    if( z[0]=='-' && z[1]=='-' ) z++;
 ++    n = strlen30(z);
 ++    if( n>=2 && 0==cli_strncmp(z, "-verbose", n) ){
 ++      psi->expert.bVerbose = 1;
 ++    }
 ++    else if( n>=2 && 0==cli_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( cli_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.
 ++ */
 ++CONDITION_COMMAND(excel !defined(SQLITE_SHELL_FIDDLE));
 ++CONDITION_COMMAND(once !defined(SQLITE_SHELL_FIDDLE));
 ++CONDITION_COMMAND(output !defined(SQLITE_SHELL_FIDDLE));
 ++
 ++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\")",
 ++];
 ++#ifndef SQLITE_SHELL_FIDDLE
 ++/* 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};
 ++
 ++  sstr_ptr_holder(&zFile);
 ++  if( psi->bSafeMode ) return DCR_AbortError;
 ++  for(i=1; i<nArg; i++){
 ++    char *z = azArg[i];
 ++    if( z[0]=='-' ){
 ++      if( z[1]=='-' ) z++;
 ++      if( cli_strcmp(z,"-bom")==0 ){
 ++        bPutBOM = 1;
 ++      }else if( bOnce!=2 && cli_strcmp(z,"-x")==0 ){
 ++        eMode = 'x';  /* spreadsheet */
 ++      }else if( bOnce!=2 && cli_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_ooms(zFile);
 ++      if( zFile[0]=='|' ){
 ++        while( i+1<nArg ){
 ++          zFile = smprintf("%z %s", zFile, azArg[++i]);
 ++          shell_check_ooms(zFile);
 ++        }
 ++        break;
 ++      }
 ++    }else{
 ++      release_holder();
 ++      return DCR_TooMany|i;
 ++    }
 ++  }
 ++  if( zFile==0 ){
 ++    zFile = smprintf("stdout");
 ++    shell_check_ooms(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_ooms(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( cli_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);
 ++    }
 ++  }
 ++  release_holder();
 ++  return DCR_Ok|rc;
 ++}
 ++#endif /* !defined(SQLITE_SHELL_FIDDLE)*/
 ++
 ++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]=='-'
 ++      && (cli_strcmp(zCmd,"--schema")==0 || cli_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( cli_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( cli_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;
 ++    }
 ++    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 ){
 ++    *pzErr = smprintf("Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
 ++    return DCR_CmdErred;
 ++  }else if( isOk==1 ){
 ++    char zBuf[21];
 ++    sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes);
 ++    raw_printf(psi->out, "%s\n", zBuf);
 ++  }
 ++  return DCR_Ok;
 ++}
 ++
 ++static void modePopper(ShellInState *psi){
 ++  outputModePop(psi);
 ++}
 ++
 ++DISPATCHABLE_COMMAND( fullschema ? 1 2 ){
 ++  int rc;
 ++  int doStats = 0;
 ++  ShellInState *psi = ISS(p);
 ++  u8 useMode = MODE_Semi;
 ++  AnyResourceHolder arh = {psi, (GenericFreer)modePopper};
 ++
 ++  if( nArg==2 && optionMatch(azArg[1], "indent") ){
 ++    useMode = MODE_Pretty;
 ++    nArg = 1;
 ++  }
 ++  if( nArg!=1 ){
 ++    return DCR_TooMany|1;
 ++  }
 ++  outputModePush(psi); /* Can fail to return due to OOM. */
 ++  any_ref_holder(&arh);
 ++  psi->showHeader = 0;
 ++  psi->cMode = psi->mode = useMode;
 ++  open_db(p, 0);
 ++  rc = s3_exec_noom(DBX(p),
 ++    "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, p, 0
 ++  );
 ++  if( rc==SQLITE_OK ){
 ++    sqlite3_stmt *pStmt;
 ++    rc = s3_prepare_v2_noom(p->dbUser,
 ++                            "SELECT rowid FROM sqlite_schema"
 ++                            " WHERE name GLOB 'sqlite_stat[134]'",
 ++                            -1, &pStmt, 0);
 ++    stmt_holder(pStmt);
 ++    doStats = s3_step_noom(pStmt)==SQLITE_ROW;
 ++    release_holder();
 ++  }
 ++  if( doStats==0 ){
 ++    raw_printf(psi->out, "/* No STAT tables available */\n");
 ++  }else{
 ++    const char *zOldDestTable = p->zDestTable;
 ++    raw_printf(psi->out, "ANALYZE sqlite_schema;\n");
 ++    psi->cMode = psi->mode = MODE_Insert;
 ++    p->zDestTable = "sqlite_stat1";
 ++    shell_exec(p, "SELECT * FROM sqlite_stat1", 0);
 ++    p->zDestTable = "sqlite_stat4";
 ++    shell_exec(p, "SELECT * FROM sqlite_stat4", 0);
 ++    raw_printf(psi->out, "ANALYZE sqlite_schema;\n");
 ++    p->zDestTable = zOldDestTable;
 ++  }
 ++  release_holder(); /* Restore shell state */
 ++  return rc > 0;
 ++}
 ++
 ++/*****************
 ++ * The .headers command
 ++ */
 ++COLLECT_HELP_TEXT[
 ++  ".headers on|off          Turn display of headers on or off",
 ++];
 ++DISPATCHABLE_COMMAND( headers 6 2 2 ){
 ++  ISS(p)->showHeader = booleanValue(azArg[1]);
 ++  ISS(p)->shellFlgs |= SHFLG_HeaderSet;
 ++  return DCR_Ok;
 ++}
 ++
 ++/*****************
 ++ * The .help command
 ++ */
 ++
 ++/* 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==2 && azArg[1][0]=='0' && azArg[1][1]==0)
 ++       || (nArg==3 && cli_strcmp(z, zHelpAll)==0
 ++           && cli_strcmp(azArg[2], zHelpAll)==0) ){
 ++      /* Show the undocumented command help */
 ++      zPat = zHelpAll;
 ++    }else if( cli_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;
 ++}
 ++
 ++CONDITION_COMMAND(import !defined(SQLITE_SHELL_FIDDLE));
 ++/*****************
 ++ * 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 = 0;             /* An SQL statement */
 ++  char *zFullTabName = 0;     /* Table name with schema if applicable */
 ++  ImportCtx sCtx = {0};       /* Reader context */
 ++  AnyResourceHolder arh = { &sCtx, (GenericFreer)import_cleanup };
 ++  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);
 ++  ResourceMark mark = holder_mark();
 ++  int rc = 0;
 ++
 ++  if(psi->bSafeMode) return DCR_AbortError;
 ++  memset(&sCtx, 0, sizeof(sCtx));
 ++  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( cli_strcmp(z,"-v")==0 ){
 ++      eVerbose++;
 ++    }else if( cli_strcmp(z,"-schema")==0 && i<nArg-1 ){
 ++      zSchema = azArg[++i];
 ++    }else if( cli_strcmp(z,"-skip")==0 && i<nArg-1 ){
 ++      nSkip = integerValue(azArg[++i]);
 ++    }else if( cli_strcmp(z,"-ascii")==0 ){
 ++      sCtx.cColSep = SEP_Unit[0];
 ++      sCtx.cRowSep = SEP_Record[0];
 ++      xRead = ascii_read_one_field;
 ++      useOutputMode = 0;
 ++    }else if( cli_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;
 ++  }
 ++  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
 ++        && cli_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 = (u8)psi->colSeparator[0];
 ++    sCtx.cRowSep = (u8)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);
 ++    return DCR_Error;
 ++  }
 ++  /* Here and below, resources must be freed before exit. */
 ++  any_ref_holder(&arh);
 ++  sCtx.z = sqlite3_malloc64(120);
 ++  shell_check_ooms(sCtx.z);
 ++  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);
 ++  }
 ++  shell_check_ooms(zFullTabName);
 ++  sstr_ptr_holder(&zFullTabName);
 ++  zSql = smprintf("SELECT * FROM %s", zFullTabName);
 ++  shell_check_ooms(zSql);
 ++  sstr_ptr_holder(&zSql);
 ++  nByte = strlen30(zSql);
 ++  rc = s3_prepare_v2_noom(DBX(p), zSql, -1, &pStmt, 0);
 ++  stmt_ptr_holder(&pStmt);
 ++  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;
 ++    shell_check_ooms(zCreate);
 ++    sstr_ptr_holder(&zCreate); /* +1 */
 ++    sstr_ptr_holder(&zRenames); /* +2 */
 ++    sstr_ptr_holder(&zColDefs); /* +3 */
 ++    conn_ptr_holder(&dbCols);
 ++    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);
 ++    }
 ++    assert(dbCols==0);
 ++    drop_holder(); /* dbCols */
 ++    if( zColDefs==0 ){
 ++      *pzErr = smprintf("%s: empty file\n", sCtx.zFile);
 ++    import_fail: /* entry from outer blocks */
 ++      RESOURCE_FREE(mark);
 ++      return DCR_Error;
 ++    }
 ++    zCreate = smprintf("%z%z\n", zCreate, zColDefs);
 ++    zColDefs = 0;
 ++    shell_check_ooms(zCreate);
 ++    if( eVerbose>=1 ){
 ++      utf8_printf(out, "%s\n", zCreate);
 ++    }
 ++    rc = s3_exec_noom(DBX(p), zCreate, 0, 0, 0);
 ++    if( rc ){
 ++      *pzErr = smprintf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(DBX(p)));
 ++      goto import_fail;
 ++    }
 ++    rc = s3_prepare_v2_noom(DBX(p), zSql, -1, &pStmt, 0);
 ++  }
 ++  if( rc ){
 ++    *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
 ++    goto import_fail;
 ++  }
 ++  nCol = sqlite3_column_count(pStmt);
 ++  sqlite3_finalize(pStmt);
 ++  pStmt = 0;
 ++  if( nCol==0 ) return DCR_Ok; /* no columns, no error */
 ++  sqlite3_free(zSql);
 ++  zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 );
 ++  shell_check_ooms(zSql);
 ++  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 = s3_prepare_v2_noom(DBX(p), zSql, -1, &pStmt, 0);
 ++  if( rc ){
 ++    *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
 ++    goto import_fail;
 ++  }
 ++  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 );
 + 
  -  if( c=='h' && cli_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]);
 ++  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);
 ++  }
 ++  RESOURCE_FREE(mark);
 ++  return DCR_Ok|(sCtx.nErr>0);
 ++}
 ++
 ++/*****************
 ++ * The .keyword command
 ++ */
 ++CONDITION_COMMAND( keyword !defined(NO_KEYWORD_COMMAND) );
 ++COLLECT_HELP_TEXT[
 ++  ".keyword ?KW?            List keywords, or say whether KW is one.",
 ++];
 ++DISPATCHABLE_COMMAND( keyword ? 1 2 ){
 ++  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
 ++ */
 ++#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE)
 ++# define LOAD_ENABLE 1
 ++#else
 ++# define LOAD_ENABLE 0
 ++#endif
 ++CONDITION_COMMAND( imposter !defined(SQLITE_OMIT_TEST_CONTROL) );
 ++CONDITION_COMMAND( iotrace defined(SQLITE_ENABLE_IOTRACE) );
 ++CONDITION_COMMAND( load LOAD_ENABLE );
 ++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",
 ++];
 ++COLLECT_HELP_TEXT[
 ++#if !defined(SQLITE_SHELL_FIDDLE)
 ++  ".log FILE|on|off         Turn logging on or off.  FILE can be stderr/stdout",
 ++#else
 ++  ".log on|off              Turn logging on or off.",
 ++#endif
 ++];
 ++DISPATCHABLE_COMMAND( imposter ? 3 3 ){
 ++  int rc = 0;
 ++  char *zSql = 0;
 ++  char *zCollist = 0;
 ++  sqlite3_stmt *pStmt = 0;
 ++  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;
 ++  ResourceMark mark = holder_mark();
 ++
 ++  if( !ShellHasFlag(p,SHFLG_TestingMode) ){
 ++    utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n",
 ++                "imposter");
 ++    return DCR_Error;
 ++  }
 ++  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;
 ++  }
 ++  db = open_db(p, 0);
 ++  if( nArg==2 ){
 ++    sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 1);
 ++    return DCR_Ok;
 ++  }
 ++  sstr_ptr_holder(&zSql);
 ++  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]);
 ++  rc = s3_prep_noom_free(db, &zSql, &pStmt);
 ++  if( rc!=SQLITE_OK ){
 ++    release_holder();
 ++    return DCR_Error;
 ++  }
 ++  stmt_ptr_holder(&pStmt);
 ++  if( s3_step_noom(pStmt)==SQLITE_ROW ){
 ++    tnum = sqlite3_column_int(pStmt, 0);
 ++    isWO = sqlite3_column_int(pStmt, 1);
 ++  }
 ++  zSql = smprintf("PRAGMA index_xinfo='%q'", azArg[1]);
 ++  sqlite3_finalize(pStmt);
 ++  pStmt = 0;
 ++  rc = s3_prep_noom_free(db, &zSql, &pStmt);
 ++  i = 0;
 ++  sstr_ptr_holder(&zCollist);
 ++  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);
 ++    }
 ++  }
 ++  if( i==0 || tnum==0 ){
 ++    *pzErr = smprintf("no such index: \"%s\"\n", azArg[1]);
 ++    RESOURCE_FREE(mark);
 ++    return DCR_Error;
 ++  }
 ++  if( lenPK==0 ) lenPK = 100000;
 ++  zSql = smprintf("CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))"
 ++                  "WITHOUT ROWID", azArg[2], zCollist, lenPK, zCollist);
 ++  shell_check_ooms(zSql);
 ++  rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 1, tnum);
 ++  if( rc==SQLITE_OK ){
 ++    rc = s3_exec_noom(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"
 ++                 );
 ++    }
 ++  }else{
 ++    *pzErr = smprintf("SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
 ++  }
 ++  RESOURCE_FREE(mark);
 ++  return DCR_Ok|(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( cli_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;
 ++}
 ++
 ++/*****************
 ++ * 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));
 ++  }
 ++  return DCR_Ok;
 ++}
 ++
 ++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 */
 ++  ResourceMark mark = holder_mark();
 ++
 ++  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;
 ++  }
 ++  db = open_db(p, 0);
 ++
 ++  /*
 ++  ** 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=?)";
 ++
 ++  for(i=2; i<nArg; i++){
 ++    int n = strlen30(azArg[i]);
 ++    if( n>1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){
 ++      bVerbose = 1;
 ++    }
 ++    else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){
 ++      bGroupByParent = 1;
 ++      zIndent = "    ";
 ++    }
 ++    else{
 ++      raw_printf(STD_ERR, "Usage: %s %s ?-verbose? ?-groupbyparent?\n",
 ++                 azArg[0], azArg[1]
 ++      );
 ++      return DCR_Unknown|i;
 ++    }
 ++  }
 ++
 ++  /* Register the fkey_collate_clause() SQL function */
 ++  rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8,
 ++      0, shellFkeyCollateClause, 0, 0
 ++  );
 ++
 ++  if( rc==SQLITE_OK ){
 ++    rc = s3_prepare_v2_noom(db, zSql, -1, &pSql, 0);
 ++  }
 ++  /* Track resources after here. */
 ++  stmt_ptr_holder(&pSql);
 ++  if( rc==SQLITE_OK ){
 ++    sqlite3_bind_int(pSql, 1, bGroupByParent);
 ++  }
 ++
 ++  if( rc==SQLITE_OK ){
 ++    char *zPrev = 0;
 ++    sqlite3_stmt *pExplain = 0;
 ++    sstr_ptr_holder(&zPrev);
 ++    stmt_ptr_holder(&pExplain);
 ++    while( SQLITE_ROW==s3_step_noom(pSql) ){
 ++      int res = -1;
 ++      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 = s3_prepare_v2_noom(db, zEQP, -1, &pExplain, 0);
 ++      if( rc!=SQLITE_OK ) break;
 ++      if( SQLITE_ROW==s3_step_noom(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);
 ++      pExplain = 0;
 ++      if( rc!=SQLITE_OK ) break;
 ++
 ++      if( res<0 ){
 ++        raw_printf(STD_ERR, "Error: internal error");
 ++        break;
 ++      }else{
 ++        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);
 ++          shell_check_ooms(zPrev);
 ++        }
 ++
 ++        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);
 ++        }
 +       }
 ++    }
 ++
 ++    if( rc!=SQLITE_OK ){
 ++      *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
 ++    }else{
 ++      rc = sqlite3_finalize(pSql);
 ++      pSql = 0;
 ++      if( rc!=SQLITE_OK ) *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
 ++    }
 ++  }else{
 ++    *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
 ++  }
 ++  RESOURCE_FREE(mark);
 ++
 ++  return DCR_Ok|(rc!=0);
 ++}
 ++
 ++DISPATCHABLE_COMMAND( load ? 2 3 ){
 ++  const char *zFile = 0, *zProc = 0;
 ++  int ai = 1, rc;
 ++  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 ++  if( nArg<2 || azArg[1][0]==0 ){
 ++    /* Must have a non-empty FILE. (Will not load self.) */
 ++    return DCR_SayUsage;
 ++  }
 ++  while( ai<nArg ){
 ++    const char *zA = azArg[ai++];
 ++    if( zFile==0 ) zFile = zA;
 ++    else if( zProc==0 ) zProc = zA;
 ++    else return DCR_TooMany|ai;
 ++  }
 ++  rc = sqlite3_load_extension(open_db(p, 0), zFile, zProc, pzErr);
 ++  return DCR_Ok|(rc!=SQLITE_OK);
 ++}
 ++
 ++DISPATCHABLE_COMMAND( log ? 2 2 ){
 ++  const char *zFile = azArg[1];
 ++  int bOn = cli_strcmp(zFile,"on")==0;
 ++  int bOff = cli_strcmp(zFile,"off")==0;
 ++#if defined(SQLITE_SHELL_FIDDLE)
 ++  if( !bOn && !bOff ) return DCR_SayUsage;
 ++#else
 ++  if( ISS(p)->bSafeMode && !bOn && !bOff ) return DCR_AbortError;
 ++#endif
 ++  output_file_close(ISS(p)->pLog);
 ++  if( bOff ){
 ++    ISS(p)->pLog = 0;
 ++    return DCR_Ok;
 ++  }
 ++  if( bOn ) zFile = "stdout";
 ++  ISS(p)->pLog = output_file_open(zFile, 0);
 ++  return DCR_Ok|(ISS(p)->pLog==0);
 ++}
 ++
 ++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;
 ++}
 ++
 ++/*****************
 ++ * The .mode command
 ++ */
 ++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 --wrap 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 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{
 -       raw_printf(stderr, "Usage: .headers on|off\n");
 -       rc = 1;
 ++      /* 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);
 ++        int isPlural = (nza>0 && zArg[nza-1]=='s');
 ++        if( foundMode!=MODE_COUNT_OF ) goto mode_badarg;
 ++        for( im=0; im<MODE_COUNT_OF; ++im ){
 ++          int nz = nza - (modeDescr[im].bMayPluralize && isPlural);
 ++          if( modeDescr[im].bUserBlocked ) continue;
 ++          if( cli_strncmp(zArg,modeDescr[im].zModeName,nz)==0 ){
 ++            if( nz<nza && nz!=strlen30(modeDescr[im].zModeName) ) continue;
 ++            foundMode = (u8)im;
 ++            setMode = modeDescr[im].iAliasFor;
 ++            break;
 ++          }
 ++        }
 ++        if( cli_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;
 ++#if SHELL_DATAIO_EXT
 ++    char *zTell = 0;
 ++    int mrc;
 ++    zMode = psi->pActiveExporter->pMethods->name(psi->pActiveExporter);
 ++    mrc = psi->pActiveExporter->pMethods->config(psi->pActiveExporter,0,&zTell);
 ++    if( zTell!=0 ){
 ++      raw_printf(out, "current output mode: %s %s\n", zMode, zTell);
 ++      sqlite3_free(zTell);
 +     }else{
  -      showHelp(p->out, 0);
 ++      raw_printf(out, "current output mode: %s\n", zMode);
       }
 --  }else
 - 
 -   if( c=='h' && cli_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
 ++    int i = psi->mode;
 ++    assert(i>=0 && i<MODE_COUNT_OF);
 ++    zMode = modeDescr[i].zModeName;
 ++    /* 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",
 ++         zMode, psi->cmOpts.iWrap,
 ++         psi->cmOpts.bWordWrap ? "on" : "off",
 ++         psi->cmOpts.bQuote ? "" : "no");
  +    }else{
 -       showHelp(p->out, 0);
 ++      raw_printf(out, "current output mode: %.*s\n", nms, zMode);
  +    }
 -   }else
 ++#endif
 ++  }else{
 ++    effectMode(psi, foundMode, setMode);
 ++    if( MODE_IS_COLUMNAR(setMode) ){
 ++      psi->cmOpts = cmOpts;
 ++#if SHELL_DATAIO_EXT
 ++      psi->pActiveExporter = psi->pColumnarExporter;
 ++#endif
 ++    }else{
 ++#if SHELL_DATAIO_EXT
 ++      psi->pActiveExporter = psi->pFreeformExporter;
 ++#endif
 ++      if( setMode==MODE_Insert ){
 ++        set_table_name(p, zTabname ? zTabname : "table");
 ++      }
 ++    }
 ++  }
 ++  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 .oomfake command
 ++ */
 ++CONDITION_COMMAND(oomfake defined(SQLITE_DEBUG));
 ++COLLECT_HELP_TEXT[
 ++  ",oomfake [how_soon]      Set how soon or whether to simulate OOM condition",
 ++];
 ++DISPATCHABLE_COMMAND( oomfake ? 1 2 azArg nArg p ){
 ++  if( nArg>1 ){
 ++    int oomf = (int)integerValue(azArg[1]);
 ++    fake_oom_countdown = oomf;
 ++  }
 ++  else raw_printf(ISS(p)->out, "OOM sim in %d allocations\n",
 ++                  fake_oom_countdown);
 ++  return DCR_Ok;
 ++}
   
 ++/* Note that .open is (partially) available in WASM builds but is
 ++** currently only intended to be used by the fiddle tool, not
 ++** end users, so is "undocumented." */
 ++#ifdef SQLITE_SHELL_FIDDLE
 ++# define HOPEN ",open"
 ++#else
 ++# define HOPEN ".open"
 ++#endif
 ++/*****************
 ++ * The .nonce, .nullvalue and .open commands
 ++ */
 ++CONDITION_COMMAND(nonce !defined(SQLITE_SHELL_FIDDLE));
 ++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];
   #ifndef SQLITE_SHELL_FIDDLE
 --  if( c=='i' && cli_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));
 --    if( p->mode==MODE_Ascii ){
 --      xRead = ascii_read_one_field;
 ++    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
 ++#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 ++    if( z[0]=='-' ){
 ++      return DCR_Unknown|iName;
 ++    }else if( zFN ){
 ++      *pzErr = smprintf("extra argument: \"%s\"\n", z);
 ++      return DCR_TooMany;
       }else{
 --      xRead = csv_read_one_field;
 --    }
 --    rc = 1;
 --    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");
 --          goto meta_command_exit;
 --        }
 --      }else if( cli_strcmp(z,"-v")==0 ){
 --        eVerbose++;
 --      }else if( cli_strcmp(z,"-schema")==0 && i<nArg-1 ){
 --        zSchema = azArg[++i];
 --      }else if( cli_strcmp(z,"-skip")==0 && i<nArg-1 ){
 --        nSkip = integerValue(azArg[++i]);
 --      }else if( cli_strcmp(z,"-ascii")==0 ){
 --        sCtx.cColSep = SEP_Unit[0];
 --        sCtx.cRowSep = SEP_Record[0];
 --        xRead = ascii_read_one_field;
 --        useOutputMode = 0;
 --      }else if( cli_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");
 --        goto meta_command_exit;
 --      }
 ++      zFN = z;
       }
 --    if( zTable==0 ){
 --      utf8_printf(p->out, "ERROR: missing %s argument. Usage:\n",
 --                  zFile==0 ? "FILE" : "TABLE");
 --      showHelp(p->out, "import");
 --      goto meta_command_exit;
 ++  }
 ++
 ++  /* 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);
 ++#ifndef SQLITE_SHELL_FIDDLE
 ++    if( psi->bSafeMode
 ++        && psi->openMode!=SHELL_OPEN_HEXDB
 ++        && zFN
 ++        && cli_strcmp(zFN,":memory:")!=0
 ++        ){
 ++      *pzErr = smprintf("cannot open database files in safe mode");
 ++      return DCR_AbortError;
       }
 --    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");
 --        goto meta_command_exit;
 --      }
 --      if( nSep>1 ){
 --        raw_printf(stderr,
 --              "Error: multi-character column separators not allowed"
 --              " for import\n");
 --        goto meta_command_exit;
 --      }
 --      nSep = strlen30(p->rowSeparator);
 --      if( nSep==0 ){
 --        raw_printf(stderr,
 --            "Error: non-null row separator required for import\n");
 --        goto meta_command_exit;
 --      }
 --      if( nSep==2 && p->mode==MODE_Csv
 --       && cli_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");
 --        goto meta_command_exit;
 --      }
 --      sCtx.cColSep = (u8)p->colSeparator[0];
 --      sCtx.cRowSep = (u8)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");
 --      goto meta_command_exit;
   #else
 --      sCtx.in = popen(sCtx.zFile+1, "r");
 --      sCtx.zFile = "<pipe>";
 --      sCtx.xCloser = pclose;
 ++      /* WASM mode has its own sandboxed pseudo-filesystem. */
   #endif
 ++    if( zFN ){
 ++      zNewFilename = smprintf("%s", zFN);
 ++      shell_check_ooms(zNewFilename);
       }else{
 --      sCtx.in = fopen(sCtx.zFile, "rb");
 --      sCtx.xCloser = fclose;
 --    }
 --    if( sCtx.in==0 ){
 --      utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile);
 --      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");
 --    }
 --    sCtx.z = sqlite3_malloc64(120);
 --    if( sCtx.z==0 ){
 --      import_cleanup(&sCtx);
 --      shell_out_of_memory();
 --    }
 --    /* 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);
 ++      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 '%z'\n", zNewFilename);
 ++      rc = 1;
       }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;
 ++      psi->pAuxDb->zFreeOnClose = zNewFilename;
       }
 --    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 );
 ++  }
 ++  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);
 ++}
   
 --    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
 --#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 ++DISPATCHABLE_COMMAND( nonce ? 2 2 ){
 ++  ShellInState *psi = ISS(p);
 ++  if( psi->zNonce==0 || cli_strcmp(azArg[1],psi->zNonce)!=0 ){
 ++    raw_printf(STD_ERR, "line %d: incorrect nonce: \"%s\"\n",
 ++               psi->pInSource->lineno, azArg[1]);
 ++    p->shellAbruptExit = 0x102;
 ++    return DCR_Abort;
 ++  }
 ++  /* Suspend safe mode for 1 dot-command after this. */
 ++  psi->bSafeModeFuture = 2;
 ++  return DCR_Ok;
 ++}
   
 --#ifndef SQLITE_UNTESTABLE
 --  if( c=='i' && cli_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( !ShellHasFlag(p,SHFLG_TestingMode) ){
 --      utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n",
 --                  "imposter");
 --      rc = 1;
 --      goto meta_command_exit;
 --    }
 --    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. */
 ++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(cli_strcmp(pC[0],"value")==0);
 ++  struct keyval_row *pParam = (struct keyval_row *)pData;
 ++  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;
 ++}
 ++
 ++/* 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){
 ++  char *zSql = 0; /* to be sqlite3_free()'ed */
 ++  sqlite3_str *sbCopy = 0;
 ++  sqlite3 *dbStore = 0;
 ++  const char *zHere = 0;
 ++  const char *zThere = SH_KV_STORE_SNAME;
 ++  const char *zTo;
 ++  const char *zFrom;
 ++  int rc = 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 = shell_check_nomem(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 key/value store table, or handle its absence. */
 ++  assert(dbStore!=0);
 ++  conn_ptr_holder(&dbStore);
 ++  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;
 --      goto meta_command_exit;
 --    }
 --    open_db(p, 0);
 --    if( nArg==2 ){
 --      sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 1);
 --      goto meta_command_exit;
 --    }
 --    zSql = sqlite3_mprintf(
 --      "SELECT rootpage, 0 FROM sqlite_schema"
 --      " WHERE name='%q' AND type='index'"
 --      "UNION ALL "
 --      "SELECT rootpage, 1 FROM sqlite_schema"
 --      " WHERE name='%q' AND type='table'"
 --      "   AND sql LIKE '%%without%%rowid%%'",
 --      azArg[1], azArg[1]
 --    );
 --    sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 --    sqlite3_free(zSql);
 --    if( sqlite3_step(pStmt)==SQLITE_ROW ){
 --      tnum = sqlite3_column_int(pStmt, 0);
 --      isWO = sqlite3_column_int(pStmt, 1);
 --    }
 --    sqlite3_finalize(pStmt);
 --    zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]);
 --    rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 --    sqlite3_free(zSql);
 --    i = 0;
 --    while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 --      char zLabel[20];
 --      const char *zCol = (const char*)sqlite3_column_text(pStmt,2);
 --      i++;
 --      if( zCol==0 ){
 --        if( sqlite3_column_int(pStmt,1)==-1 ){
 --          zCol = "_ROWID_";
 --        }else{
 --          sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i);
 --          zCol = zLabel;
 --        }
 --      }
 --      if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){
 --        lenPK = (int)strlen(zCollist);
 --      }
 --      if( zCollist==0 ){
 --        zCollist = sqlite3_mprintf("\"%w\"", zCol);
 --      }else{
 --        zCollist = sqlite3_mprintf("%z,\"%w\"", zCollist, zCol);
 ++    }else{
 ++      /* 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 = s3_exec_noom(dbStore, zCT, 0, 0, 0);
 ++      if( rc!=SQLITE_OK ){
 ++        utf8_printf(STD_ERR, "Cannot create table %s. Nothing saved.", zThere);
         }
       }
 --    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"
 --        );
 --      }
 ++  }
 ++  release_holder();
 ++  assert(dbStore==0);
 ++  if( rc!=0 ) return rc;
 ++
 ++  zSql = smprintf("ATTACH %Q AS %s;", zStoreDbName, SH_KV_STORE_SCHEMA);
 ++  shell_check_ooms(zSql);
 ++  sstr_ptr_holder(&zSql);
 ++  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 ++  release_holder();
 ++  if( rc!=SQLITE_OK ) return rc;
 ++
 ++  sbCopy = sqlite3_str_new(db);
 ++  sqst_ptr_holder(&sbCopy);
 ++  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);
 ++  drop_holder();
 ++  shell_check_ooms(zSql);
 ++  sstr_ptr_holder(&zSql);
 ++  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 ++  release_holder();
 ++
 ++  s3_exec_noom(db, "DETACH "SH_KV_STORE_SCHEMA";", 0, 0, 0);
 ++  return rc;
 ++}
 ++
 ++/* Default locations of key/value DBs for .parameters and .vars save/load. */
 ++static const char *zDefaultParamStore = "~/sqlite_params.sdb";
 ++static const char *zDefaultVarStore = "~/sqlite_vars.sdb";
 ++
 ++/* Possibly generate a derived path from input spec, with defaulting
 ++ * and conversion of leading (or only) tilde as home directory.
 ++ * The above-set default is used for zSpec NULL, "" or "~".
 ++ * When return is 0, there is an error; what needs doing cannot be done.
 ++ * The return must eventually be sqlite3_free()'ed.
 ++ */
 ++static char *kv_store_path(const char *zSpec, ParamTableUse ptu){
 ++  if( zSpec==0 || zSpec[0]==0 || cli_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 smprintf("%s", zSpec);
 ++}
 ++
 ++/* Load some or all key/value pairs. Arguments are "load FILE ?NAMES?". */
 ++static int kv_pairs_load(sqlite3 *db, ParamTableUse ptu,
 ++                         const char *azArg[], int nArg){
 ++  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;
 ++    sstr_holder(zStore);
 ++    rc = kv_xfr_table(db, zStore, 0, ptu, pzFirst, nNames);
 ++    release_holder();
 ++    return rc;
 ++  }
 ++}
 ++
 ++/* Save some or all parameters. Arguments are "save FILE ?NAMES?". */
 ++static int kv_pairs_save(sqlite3 *db, ParamTableUse ptu,
 ++                         const char *azArg[], int nArg){
 ++  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;
 ++    sstr_holder(zStore);
 ++    rc = kv_xfr_table(db, zStore, 1, ptu, pzFirst, nNames);
 ++    release_holder();
 ++    return rc;
 ++  }
 ++}
 ++
 ++#ifndef SQLITE_NOHAVE_SYSTEM
 ++/* Remove leading comment line of form "-- tag\n" from a by-ref string.
 ++ * The *pz_nt argument is dynamically allocated (ala sqlite3_malloc() and is
 ++ * replaced if the removal is done. This is a helper for 2nd next function.
 ++ */
 ++static void remove_name_tag(char **pz_nt, char *tag){
 ++  int tagLen;
 ++  char *zUntag;
 ++  assert(pz_nt!=0 && tag!=0);
 ++  if( *pz_nt==0 || strstr(*pz_nt, "-- ")!=*pz_nt ) return;
 ++  if( strstr((*pz_nt)+3, tag)!=(*pz_nt)+3 ) return;
 ++  tagLen = strlen30(tag)+3;
 ++  if( (*pz_nt)[tagLen] != '\n' ) return;
 ++  zUntag = smprintf("%s", *pz_nt+tagLen+1);
 ++  shell_check_ooms(zUntag);
 ++  sqlite3_free(*pz_nt);
 ++  *pz_nt = zUntag;
 ++}
 ++
 ++/*
 ++ * 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.
 ++ *
 ++ * During editing, the 1st line of the text shows the variable name.
 ++ * If that is left as-is, it is removed after editing is done.
 ++ */
 ++static int edit_one_kvalue(sqlite3 *db, char *name, int eval,
 ++                           ParamTableUse uses, const char * zEd, char **pzErr){
 ++  struct keyval_row kvRow = {0,0,0};
 ++  int rc;
 ++  char * zVal = 0;
 ++  const char *zTab = 0;
 ++  char * zSql = 0;
 ++  const char *zFmt;
 ++  char * zEntrySql = 0;
 ++
 ++  sstr_ptr_holder(&zVal); /* +1 */
 ++  switch( uses ){
 ++  case PTU_Script:  zTab = SHVAR_TABLE_SNAME; break;
 ++  case PTU_Binding: zTab = PARAM_TABLE_SNAME; break;
 ++  default: assert(0);
 ++  }
 ++  if( eval ){
 ++    zSql = smprintf("SELECT value, uses FROM %s WHERE key=%Q"
 ++                    " AND uses IN (%d, "SPTU_Entry") ORDER BY uses",
 ++                    zTab, name, uses);
 ++  }else{
 ++    zSql = smprintf("SELECT value, uses FROM %s "
 ++                    "WHERE key=%Q AND uses=%d", zTab, name, uses);
 ++  }
 ++  shell_check_ooms(zSql);
 ++  sstr_ptr_holder(&kvRow.value); /* +2 */
 ++  sstr_holder(zSql); /* +3 */
 ++  s3_exec_noom(db, zSql, kv_find_callback, &kvRow, 0);
 ++  release_holder(); /* zSql =2 */
 ++  assert(kvRow.hits<3);
 ++  if( kvRow.hits>=1 ){
 ++    /* Editing an existing value of same kind. */
 ++    zVal = kvRow.value;
 ++    drop_holder(); /* kvRow.value =1 */
 ++    zSql = smprintf("SELECT edit('-- %s\n'||%Q, %Q)", name, zVal, zEd);
 ++    shell_check_ooms(zSql);
 ++    sstr_holder(zSql);
 ++    zVal = db_text(db, zSql, 0);
 ++    release_holder(); /* zSql =1 */
 ++    remove_name_tag(&zVal, name);
 ++    zFmt = (!eval)? "UPDATE %s SET value=%Q WHERE key=%Q AND uses=%d"
 ++      : "UPDATE %s SET value=(SELECT %s) WHERE key=%Q AND uses=%d";
 ++    zSql = smprintf(zFmt, zTab, zVal, name, uses);
 ++  }else{
 ++    /* Editing a new value of same kind. */
 ++    assert(kvRow.value==0 || kvRow.uses!=uses);
 ++    drop_holder(); /* kvRow.value =1 */
 ++    zSql = smprintf("SELECT edit('-- %s\n', %Q)", name, zEd);
 ++    shell_check_ooms(zSql);
 ++    sstr_holder(zSql);
 ++    zVal = db_text(db, zSql, 1);
 ++    release_holder(); /* zSql =1 */
 ++    remove_name_tag(&zVal, name);
 ++    zFmt = (!eval)? "INSERT INTO %s(key,value,uses) VALUES (%Q,%Q,%d)"
 ++      : "INSERT INTO %s(key,value,uses) VALUES(%Q,(SELECT %s LIMIT 1),%d)";
 ++    zSql = smprintf(zFmt, zTab, name, zVal, uses);
 ++  }
 ++  shell_check_ooms(zSql);
 ++  sstr_holder(zSql); /* +2 */
 ++  if( eval ){
 ++    zEntrySql = smprintf("INSERT OR REPLACE INTO %s(key,value,uses)"
 ++                         " VALUES(%Q,%Q,"SPTU_Entry")", zTab, name,zVal);
 ++  }else{
 ++    zEntrySql = smprintf("DELETE FROM %s WHERE key=%Q AND uses="SPTU_Entry,
 ++                         zTab, name);
 ++  }
 ++  if( zEntrySql ){
 ++    sqlite3_exec(db, zEntrySql, 0, 0, 0);
 ++    sqlite3_free(zEntrySql);
 ++  }
 ++  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 ++  if( rc!=SQLITE_OK && pzErr!=0 ){
 ++    if( eval ){
 ++      *pzErr = smprintf("Cannot evaluate SELECT %s\n(Fails with %s)\n",
 ++                        zVal, sqlite3_errmsg(db));
       }else{
 --      raw_printf(stderr, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
 --      rc = 1;
 ++      *pzErr = smprintf("Cannot execute %s\n(Fails with %s)\n",
 ++                        zSql, sqlite3_errmsg(db));
       }
 ++  }
 ++  release_holders(2); /* =0 */
 ++  return rc!=SQLITE_OK;
 ++}
 ++#endif
 ++
 ++/* Space-join values in an argument list. *valLim is not included. */
 ++char *values_join( char **valBeg, char **valLim ){
 ++  char *z = 0;
 ++  const char *zSep = 0;
 ++  while( valBeg < valLim ){
 ++    z = smprintf("%z%s%s", z, zSep, *valBeg);
 ++    zSep = " ";
 ++    ++valBeg;
 ++  }
 ++  return z;
 ++}
 ++
 ++#define INT_RE "((\\d+)|(0[xX][0-9a-fA-F]+))"
 ++#define REAL_RE "((\\d+(\\.\\d+)?([eE][-+]?\\d{1,4})?)|(\\.\\d+))"
 ++#define HEXP_RE "([0-9a-fA-F]{2,2})"
 ++const char *param_set_literals[] = {
 ++  /* int */ "^[-+]?" INT_RE "$",
 ++  /* real */ "^[-+]?" REAL_RE "$",
 ++  /* blob */ "^[xX]'"HEXP_RE"*'$",
 ++  /* text */ "^'([^']|'')*'$"
 ++};
 ++#undef INT_RE
 ++#undef REAL_RE
 ++#undef HEXP_RE
 ++
 ++/* 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);
 ++  sstr_holder(zValGlom);
 ++  rc = s3_prep_noom_free(db, &zSql, &pStmtSet);
 ++  assert(rc==SQLITE_OK);
 ++  stmt_holder(pStmtSet);
 ++  rc = s3_step_noom(pStmtSet);
 ++  rc = (SQLITE_DONE==rc)? SQLITE_OK : SQLITE_ERROR;
 ++  release_holders(2);
 ++  return rc;
 ++}
 ++
 ++
 ++/* Effect most of the .parameter set subcommand (per help text.)
 ++ * Return SQLITE_OK on success, else SQLITE_ERROR. Error
 ++ * explanation is placed in *pzErr, to be later sqlite3_free()'ed.
 ++ */
 ++static int param_set(sqlite3 *db, u8 bEval, char *name,
 ++                     char **valBeg, char **valLim, char **pzErr){
 ++  char *zSql = 0;
 ++  char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0;
 ++  sqlite3_stmt *pStmtSet = 0;
 ++  /* Above objects are managed. */
 ++  const char **pSL;
 ++  u8 bLitMatch = 0;
 ++  int rc = SQLITE_OK, retries = 0;
 ++  char *zValue = (zValGlom==0)? *valBeg : zValGlom;
 ++
 ++  sstr_holder(zValGlom); /* +1 */
 ++  if( !bEval ){
 ++    /* No eval specified; see if the value matches a common literal form. */
 ++    zSql = smprintf("SELECT regexp($re,%Q)", zValue);
 ++    rc = s3_prep_noom_free(db, &zSql, &pStmtSet);
 ++    stmt_ptr_holder(&pStmtSet); /* +2 */
 ++    for( pSL=param_set_literals; pSL<PastArray(param_set_literals); ++pSL ){
 ++      sqlite3_reset(pStmtSet);
 ++      rc = sqlite3_bind_text(pStmtSet,1,*pSL,-1,SQLITE_STATIC);
 ++      shell_check_nomem(rc);
 ++      rc = shell_check_nomem(sqlite3_step(pStmtSet));
 ++      assert(rc==SQLITE_ROW);
 ++      if( 0!=(bLitMatch = sqlite3_column_int(pStmtSet, 0)) ) break;
 ++    }
 ++    release_holder(); /* =1 */
 ++  }
 ++  /* These possible conditions may exist, to be handled thus:
 ++   * 1. No evaluation was specified.
 ++   *   Value must be a recognizable literal or will be treated as text.
 ++   *   Success is assured (absent OOM.)
 ++   * 2. Evaluation was specified.
 ++   *   Just do it and take whatever type results.
 ++   *   Report failure if that occurs.
 ++   * In both cases, a PTU_Entry row is set to the input value.
 ++   */
 ++  stmt_ptr_holder(&pStmtSet); /* +2 */
 ++  if( bEval || bLitMatch ){
 ++    zSql = smprintf
 ++      ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 ++        " VALUES(%Q,(%s),"SPTU_Binding")", name, zValue );
 ++  }else{
 ++    zSql = smprintf
 ++      ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 ++        "VALUES(%Q,%Q,"SPTU_Binding")", name, zValue );
 ++  }
 ++  rc = s3_prep_noom_free(db, &zSql, &pStmtSet);
 ++  if( rc!=SQLITE_OK ){
 ++    /* Reach here when value matches no literal and fails evaluation. */
 ++    sqlite3_finalize(pStmtSet);
 ++    pStmtSet = 0;
 ++    if( pzErr ){
 ++      *pzErr = smprintf("Parameter %s set as text.\nCompiling \"%s\" fails.\n"
 ++                        "(%s)", name, zValue, sqlite3_errmsg(db));
 ++    }
 ++    zSql = smprintf
 ++      ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 ++        "VALUES(%Q,%Q,"SPTU_Binding")", name, zValue );
 ++    rc = s3_prep_noom_free(db, &zSql, &pStmtSet);
 ++    assert(rc==SQLITE_OK);
 ++  }
 ++  zSql = smprintf("INSERT OR REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 ++                  " VALUES(%Q,%Q,"SPTU_Entry")", name, zValue);
 ++  if( zSql ){
 ++    sqlite3_exec(db, zSql, 0, 0, 0);
       sqlite3_free(zSql);
 --  }else
 --#endif /* !defined(SQLITE_OMIT_TEST_CONTROL) */
 ++  }
 ++  sqlite3_step(pStmtSet);
 ++  release_holders(2);
 ++  return rc;
 ++}
   
 --#ifdef SQLITE_ENABLE_IOTRACE
 --  if( c=='i' && cli_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( cli_strcmp(azArg[1], "-")==0 ){
 --      sqlite3IoTrace = iotracePrintf;
 --      iotrace = stdout;
 ++/* 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 = 0;
 ++  char *zFromWhere = 0;
 ++  char *zSql = 0;
 ++  /* Above objects are managed. */
 ++  RESOURCE_MARK(mark);
 ++  sqlite3 *db;
 ++  int len = 0, rc;
 ++  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);
 ++  sqst_holder(sbList);
 ++  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);
 ++  shell_check_nomem(sqlite3_str_errcode(sbList));
 ++  zFromWhere = sqlite3_str_finish(sbList);
 ++  drop_holder();
 ++  shell_check_ooms(zFromWhere);
 ++  sstr_holder(zFromWhere); /* +1 */
 ++  zSql = smprintf("SELECT max(length(key)) %s", zFromWhere);
 ++  sstr_ptr_holder(&zSql);
 ++  rc = s3_prep_noom_free(db, &zSql, &pStmt);
 ++  stmt_ptr_holder(&pStmt);
 ++  if( rc==SQLITE_OK ){
 ++    sqlite3_bind_int(pStmt, 1, ptu);
 ++    if( s3_step_noom(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);
 ++    zSql = 0;
 ++    if( !bShort ){
 ++      int nBindings = 0, nScripts = 0;
 ++      zSql = smprintf("SELECT key, uses,"
 ++                      "CASE typeof(value)"
 ++                      " WHEN 'text' THEN quote(value)"
 ++                      " WHEN 'blob' THEN 'x'''||hex(value)||''''"
 ++                      " ELSE value END"
 ++                      " %s ORDER BY uses, key", zFromWhere);
 ++      rc = s3_prep_noom_free(db, &zSql, &pStmt);
 ++      sqlite3_bind_int(pStmt, 1, ptu);
 ++      while( rc==SQLITE_OK && s3_step_noom(pStmt)==SQLITE_ROW ){
 ++        ParamTableUse ptux = sqlite3_column_int(pStmt,1);
 ++        const char *zName = (const char*)sqlite3_column_text(pStmt,0);
 ++        const char *zValue = (const char*)sqlite3_column_text(pStmt,2);
 ++        if( !zName ) zName = "?";
 ++        if( !zValue ) zValue = "?";
 ++        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, zName, zValue);
 ++          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, zName, zValue);
 ++          break;
 ++        default: break; /* Ignore */
 ++        }
 ++      }
       }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;
 ++      int nc = 0, ncw = 78/(len+2);
 ++      zSql = smprintf("SELECT key %s ORDER BY key", zFromWhere);
 ++      rc = s3_prep_noom_free(db, &zSql, &pStmt);
 ++      sqlite3_bind_int(pStmt, 1, ptu);
 ++      while( rc==SQLITE_OK && s3_step_noom(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");
       }
 --  }else
 ++  }
 ++  RESOURCE_FREE(mark);
 ++}
 ++
 ++/* 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 mumble ?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;
 ++    }
 ++    sqlite3_str_appendf(pStr, ")");
 ++  }
 ++}
 ++
 ++/*****************
 ++ * 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(s)",
 ++  "      OPT may be -t to use edited value(s) as text or -e to evaluate it,",
 ++  "      or -m or -r to edit missing or referenced parameters from last query.",
   #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 ?OPT? NAME VALUE    Give SQL parameter NAME a value of VALUE",
 ++  "      NAME must begin with one of $,:,@,? for bindings, VALUE is the space-",
 ++  "      joined arguments. OPT may -e to evaluate VALUE as a SQL expression.",
 ++  "   unset ?NAMES?           Remove named parameter(s) from parameters table",
 ++];
 ++DISPATCHABLE_COMMAND( parameter 2 2 0 ){
 ++  int rc = 0;
 ++  char *zErr = 0;
 ++  sqlite3 *db = open_db(p,0);
   
 --  if( c=='l' && n>=5 && cli_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;
 --          }
 ++  /* .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( cli_strcmp(azArg[1],"clear")==0 || cli_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 = 0;
 ++      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_ooms(zSql);
 ++      sstr_holder(zSql);
 ++      sqlite3_exec(db, zSql, 0, 0, 0);
 ++      release_holder();
 ++    }
 ++  }else
 ++#ifndef SQLITE_NOHAVE_SYSTEM
 ++  /* .parameter edit ?NAMES?
 ++  ** Edit the named parameters. Any that do not exist are created.
 ++  */
 ++  if( cli_strcmp(azArg[1],"edit")==0 ){
 ++    ShellInState *psi = ISS(p);
 ++    int ia = 2;
 ++    int eval = 0;
 ++    int reffed = 0;
 ++    int missing = 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;
 ++        case 'm': missing = 1; reffed = 0; continue;
 ++        case 'r': reffed = 1; missing = 0; continue;
 ++        default:
 ++          utf8_printf(STD_ERR, "Error: bad .parameter name: %s\n", zA);
 ++          return DCR_Error;
           }
         }
 --      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;
 ++      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;
         }
 --      if( nArg==3 ){
 --        sqlite3_limit(p->db, aLimit[iLimit].limitCode,
 --                      (int)integerValue(azArg[2]));
 ++      rc = edit_one_kvalue(db, zA, eval, ptu, psi->zEditor, &zErr);
 ++      ++ia;
 ++      if( zErr!=0 ){
 ++        utf8_printf(STD_ERR, "%s", zErr);
 ++        sqlite3_free(zErr);
 ++        zErr = 0;
 ++      }
 ++      if( rc!=0 ) return DCR_Error;
 ++    }
 ++    if( reffed|missing ){
 ++      char acSqlCount[] = "SELECT count(*) FROM "PARAM_TABLE_SNAME
 ++        " WHERE uses="SPTU_Ref" AND value<=0";
 ++      const char *zSqlName = "SELECT key FROM "PARAM_TABLE_SNAME
 ++        " WHERE uses="SPTU_Ref" AND value<=%d ORDER BY rowid"
 ++        " LIMIT 1 OFFSET %d";
 ++      int nve, vix;
 ++      acSqlCount[sizeof(acSqlCount)-2] = (reffed)? '1' : '0';
 ++      nve = db_int(db, acSqlCount);
 ++      for( vix=0; vix<nve; ++vix ){
 ++        char *zQV = smprintf(zSqlName, (int)reffed, vix);
 ++        char *zVar = 0;
 ++        sstr_holder(zQV);
 ++        sstr_ptr_holder(&zVar);
 ++        shell_check_ooms(zQV);
 ++        zVar = db_text(db, zQV, 0);
 ++        rc = edit_one_kvalue(db, zVar, eval, PTU_Binding, psi->zEditor, &zErr);
 ++        release_holders(2);
 ++        if( zErr!=0 ){
 ++          utf8_printf(STD_ERR, "%s", zErr);
 ++          sqlite3_free(zErr);
 ++          zErr = 0;
 ++        }
 ++        if( rc!=0 ) return DCR_Error;
         }
 --      printf("%20s %d\n", aLimit[iLimit].zLimitName,
 --             sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1));
 ++      /* Edit */
       }
     }else
 ++#endif
   
 --  if( c=='l' && n>2 && cli_strncmp(azArg[0], "lint", n)==0 ){
 --    open_db(p, 0);
 --    lintDotCommand(p, azArg, nArg);
 ++  /* .parameter init
 ++  ** Make sure the TEMP table used to hold bind parameters exists.
 ++  ** Create it if necessary.
 ++  */
 ++  if( nArg==2 && cli_strcmp(azArg[1],"init")==0 ){
 ++    param_table_init(db);
     }else
   
 --#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE)
 --  if( c=='l' && cli_strncmp(azArg[0], "load", n)==0 ){
 --    const char *zFile, *zProc;
 --    char *zErrMsg = 0;
 --    failIfSafeMode(p, "cannot run .load in safe mode");
 --    if( nArg<2 || azArg[1][0]==0 ){
 --      /* Must have a non-empty FILE. (Will not load self.) */
 --      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;
 --    }
 ++  /* .parameter list|ls
 ++  ** List all or selected bind parameters.
 ++  ** list displays names, values and uses.
 ++  ** ls displays just the names.
 ++  */
 ++  if( nArg>=2 && ((cli_strcmp(azArg[1],"list")==0)
 ++                  || (cli_strcmp(azArg[1],"ls")==0)) ){
 ++    list_pov_entries(p, PTU_Binding, azArg[1][1]=='s', azArg+2, nArg-2);
     }else
 --#endif
   
 --  if( c=='l' && cli_strncmp(azArg[0], "log", n)==0 ){
 --    if( nArg!=2 ){
 --      raw_printf(stderr, "Usage: .log FILENAME\n");
 --      rc = 1;
 --    }else{
 --      const char *zFile = azArg[1];
 --      if( p->bSafeMode
 --       && cli_strcmp(zFile,"on")!=0
 --       && cli_strcmp(zFile,"off")!=0
 --      ){
 --        raw_printf(stdout, "cannot set .log to anything other "
 --                   "than \"on\" or \"off\"\n");
 --        zFile = "off";
 --      }
 --      output_file_close(p->pLog);
 --      if( cli_strcmp(zFile,"on")==0 ) zFile = "stdout";
 --      p->pLog = output_file_open(zFile, 0);
 --    }
 ++  /* .parameter load
 ++  ** Load all or named parameters from specified or default (DB) file.
 ++  */
 ++  if( cli_strcmp(azArg[1],"load")==0 ){
 ++    param_table_init(db);
 ++    rc = kv_pairs_load(db, PTU_Binding, (const char **)azArg+1, nArg-1);
     }else
   
 --  if( c=='m' && cli_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-mode.  If that
 --         * overwrites already-set values, user was informed of this.
 --         */
 --        if( cli_strcmp(z, "qbox")==0 ){
 --          ColModeOpts cmo = ColModeOpts_default_qbox;
 --          zMode = "box";
 --          cmOpts = cmo;
 --        }
 --      }else if( zTabname==0 ){
 --        zTabname = z;
 --      }else if( z[0]=='-' ){
 --        utf8_printf(stderr, "unknown option: %s\n", z);
 --        utf8_printf(stderr, "options:\n"
 --                            "  --noquote\n"
 --                            "  --quote\n"
 --                            "  --wordwrap on/off\n"
 --                            "  --wrap N\n"
 --                            "  --ww\n");
 --        rc = 1;
 --        goto meta_command_exit;
 --      }else{
 --        utf8_printf(stderr, "extra argument: \"%s\"\n", z);
 --        rc = 1;
 --        goto meta_command_exit;
 --      }
 --    }
 --    if( zMode==0 ){
 --      if( p->mode==MODE_Column
 --       || (p->mode>=MODE_Markdown && p->mode<=MODE_Box)
 --      ){
 --        raw_printf
 --          (p->out,
 --           "current output mode: %s --wrap %d --wordwrap %s --%squote\n",
 --           modeDescr[p->mode], p->cmOpts.iWrap,
 --           p->cmOpts.bWordWrap ? "on" : "off",
 --           p->cmOpts.bQuote ? "" : "no");
 --      }else{
 --        raw_printf(p->out, "current output mode: %s\n", modeDescr[p->mode]);
 --      }
 --      zMode = modeDescr[p->mode];
 --    }
 --    n2 = strlen30(zMode);
 --    if( cli_strncmp(zMode,"lines",n2)==0 ){
 --      p->mode = MODE_Line;
 --      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 --    }else if( cli_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( cli_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( cli_strncmp(zMode,"html",n2)==0 ){
 --      p->mode = MODE_Html;
 --    }else if( cli_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( cli_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( cli_strncmp(zMode,"tabs",n2)==0 ){
 --      p->mode = MODE_List;
 --      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Tab);
 --    }else if( cli_strncmp(zMode,"insert",n2)==0 ){
 --      p->mode = MODE_Insert;
 --      set_table_name(p, zTabname ? zTabname : "table");
 --    }else if( cli_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( cli_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( cli_strncmp(zMode,"markdown",n2)==0 ){
 --      p->mode = MODE_Markdown;
 --      p->cmOpts = cmOpts;
 --    }else if( cli_strncmp(zMode,"table",n2)==0 ){
 --      p->mode = MODE_Table;
 --      p->cmOpts = cmOpts;
 --    }else if( cli_strncmp(zMode,"box",n2)==0 ){
 --      p->mode = MODE_Box;
 --      p->cmOpts = cmOpts;
 --    }else if( cli_strncmp(zMode,"count",n2)==0 ){
 --      p->mode = MODE_Count;
 --    }else if( cli_strncmp(zMode,"off",n2)==0 ){
 --      p->mode = MODE_Off;
 --    }else if( cli_strncmp(zMode,"json",n2)==0 ){
 --      p->mode = MODE_Json;
 --    }else{
 --      raw_printf(stderr, "Error: mode should be one of: "
 --         "ascii box column csv html insert json line list markdown "
 --         "qbox quote table tabs tcl\n");
 --      rc = 1;
 --    }
 --    p->cMode = p->mode;
 ++  /* .parameter save
 ++  ** Save all or named parameters into specified or default (DB) file.
 ++  */
 ++  if( cli_strcmp(azArg[1],"save")==0 ){
 ++    rc = kv_pairs_save(db, PTU_Binding, (const char **)azArg+1, nArg-1);
     }else
   
 --#ifndef SQLITE_SHELL_FIDDLE
 --  if( c=='n' && cli_strcmp(azArg[0], "nonce")==0 ){
 --    if( nArg!=2 ){
 --      raw_printf(stderr, "Usage: .nonce NONCE\n");
 ++  /* .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 && cli_strcmp(azArg[1],"set")==0 ){
 ++    int inv = 2;
 ++    u8 bEval = 0;
 ++    char cOpt;
 ++    ParamTableUse ptu;
 ++
 ++    while( inv<nArg && (cOpt = option_char(azArg[inv]))!=0 ){
 ++      if( cOpt!='e' ) goto param_fail;
 ++      bEval = 1;
 ++      ++inv;
 ++    }
 ++    if( nArg-inv < 2 ) goto param_fail;
 ++    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 if( p->zNonce==0 || cli_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
 --#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 --
 --  if( c=='n' && cli_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;
 ++      param_table_init(db);
 ++      rc = param_set(db, bEval, azArg[inv], &azArg[inv+1], &azArg[nArg], &zErr);
 ++      if( rc!=SQLITE_OK || zErr ){
 ++        utf8_printf(STD_ERR, "%s: %s\n", (rc)? "Error" : "Warning",
 ++                    (zErr)? zErr : sqlite3_errmsg(db));
 ++        sqlite3_free(zErr);
 ++      }
       }
     }else
   
@@@@ -13071,883 -10451,409 -10450,409 +13116,884 @@@@ DISPATCHABLE_COMMAND( session 3 2 0 )
       }
     }else
   
 --  if( c=='s' && n>=4 && cli_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( cli_strcmp(z,"schema")==0 ){
 --          bSchema = 1;
 --        }else
 ++  /* .session indirect ?BOOLEAN?
 ++  ** Query or set the indirect flag
 ++  */
 ++  if( cli_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);
 ++    }
 ++  }else
 ++
 ++  /* .session isempty
 ++  ** Determine if the session is empty
 ++  */
 ++  if( cli_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);
 ++    }
 ++  }else
 ++
 ++  /* .session list
 ++  ** List all currently open sessions
 ++  */
 ++  if( cli_strcmp(azCmd[0],"list")==0 ){
 ++    for(i=0; i<pAuxDb->nSession; i++){
 ++      utf8_printf(out, "%d %s\n", i, pAuxDb->aSession[i].zName);
 ++    }
 ++  }else
 ++
 ++  /* .session open DB NAME
 ++  ** Open a new session called NAME on the attached database DB.
 ++  ** DB is normally "main".
 ++  */
 ++  if( cli_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( cli_strcmp(pAuxDb->aSession[i].zName,zName)==0 ){
 ++        utf8_printf(STD_ERR, "Session \"%s\" already exists\n", zName);
 ++        return DCR_Error;
 ++      }
 ++    }
 ++    if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){
 ++      raw_printf
 ++        (STD_ERR, "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession));
 ++      return DCR_Error;
 ++    }
 ++    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 DCR_Error;
 ++    }
 ++    pSession->nFilter = 0;
 ++    sqlite3session_table_filter(pSession->p, session_filter, pSession);
 ++    pAuxDb->nSession++;
 ++    zName = smprintf("%s", zName);
 ++    shell_check_ooms(zName);
 ++    pSession->zName = zName;
 ++  }else{
 ++
 ++  /* If no command name matches, show a syntax error */
 ++  session_syntax_error:
 ++    showHelp(out, "session", p);
 ++    return DCR_CmdErred;
 ++  }
 ++  return DCR_Ok|(rc!=0);
 ++}
 ++
 ++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 */
 ++  RESOURCE_MARK(mark);
 ++
 ++  open_db(p, 0);
 ++  for(i=1; i<nArg; i++){
 ++    const char *z = azArg[i];
 ++    if( z[0]=='-' ){
 ++      z++;
 ++      if( z[0]=='-' ) z++;
 ++      if( cli_strcmp(z,"schema")==0 ){
 ++        bSchema = 1;
 ++      }else
           if( cli_strcmp(z,"sha3-224")==0 || cli_strcmp(z,"sha3-256")==0
 --         || cli_strcmp(z,"sha3-384")==0 || cli_strcmp(z,"sha3-512")==0
 --        ){
 ++            || cli_strcmp(z,"sha3-384")==0 || cli_strcmp(z,"sha3-512")==0
 ++            ){
             iSize = atoi(&z[5]);
           }else
 --        if( cli_strcmp(z,"debug")==0 ){
 --          bDebug = 1;
 --        }else
 --        {
 --          utf8_printf(stderr, "Unknown option \"%s\" on \"%s\"\n",
 --                      azArg[i], azArg[0]);
 --          showHelp(p->out, azArg[0]);
 --          rc = 1;
 --          goto meta_command_exit;
 --        }
 --      }else if( zLike ){
 --        raw_printf(stderr, "Usage: .sha3sum ?OPTIONS? ?LIKE-PATTERN?\n");
 --        rc = 1;
 --        goto meta_command_exit;
 --      }else{
 --        zLike = z;
 --        bSeparate = 1;
 --        if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 1;
 --      }
 --    }
 --    if( bSchema ){
 --      zSql = "SELECT lower(name) as tname 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) as tname 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( cli_strncmp(zTab, "sqlite_",7)!=0 ){
 --        appendText(&sQuery,"SELECT * FROM ", 0);
 --        appendText(&sQuery,zTab,'"');
 --        appendText(&sQuery," NOT INDEXED;", 0);
 --      }else if( cli_strcmp(zTab, "sqlite_schema")==0 ){
 --        appendText(&sQuery,"SELECT type,name,tbl_name,sql FROM sqlite_schema"
 --                           " ORDER BY name;", 0);
 --      }else if( cli_strcmp(zTab, "sqlite_sequence")==0 ){
 --        appendText(&sQuery,"SELECT name,seq FROM sqlite_sequence"
 --                           " ORDER BY name;", 0);
 --      }else if( cli_strcmp(zTab, "sqlite_stat1")==0 ){
 --        appendText(&sQuery,"SELECT tbl,idx,stat FROM sqlite_stat1"
 --                           " ORDER BY tbl,idx;", 0);
 --      }else if( cli_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);
 ++          if( cli_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{
 --      shell_exec(p, zSql, 0);
 ++      zLike = z;
 ++      bSeparate = 1;
 ++      if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 1;
       }
 ++  }
 ++  if( bSchema ){
 ++    zSql = "SELECT lower(name) AS tname 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) AS tname FROM sqlite_schema"
 ++      " WHERE type='table' AND coalesce(rootpage,0)>1"
 ++      " AND name NOT LIKE 'sqlite_%'"
 ++      " ORDER BY 1 collate nocase";
 ++  }
 ++  s3_prepare_v2_noom(DBX(p), zSql, -1, &pStmt, 0);
 ++  stmt_ptr_holder(&pStmt); /* +1 */
 ++  initText(&sQuery);
 ++  text_ref_holder(&sQuery); /* +2 */
 ++  initText(&sSql);
 ++  text_ref_holder(&sSql); /* +3 */
 ++  appendText(&sSql, "WITH [sha3sum$query](a,b) AS(",0);
 ++  zSep = "VALUES(";
 ++  while( SQLITE_ROW==s3_step_noom(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( cli_strncmp(zTab, "sqlite_",7)!=0 ){
 ++      appendText(&sQuery,"SELECT * FROM ", 0);
 ++      appendText(&sQuery,zTab,'"');
 ++      appendText(&sQuery," NOT INDEXED;", 0);
 ++    }else if( cli_strcmp(zTab, "sqlite_schema")==0 ){
 ++      appendText(&sQuery,"SELECT type,name,tbl_name,sql FROM sqlite_schema"
 ++                 " ORDER BY name;", 0);
 ++    }else if( cli_strcmp(zTab, "sqlite_sequence")==0 ){
 ++      appendText(&sQuery,"SELECT name,seq FROM sqlite_sequence"
 ++                 " ORDER BY name;", 0);
 ++    }else if( cli_strcmp(zTab, "sqlite_stat1")==0 ){
 ++      appendText(&sQuery,"SELECT tbl,idx,stat FROM sqlite_stat1"
 ++                 " ORDER BY tbl,idx;", 0);
 ++    }else if( cli_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 = "),(";
 ++  }
 ++  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);
 ++  }
 ++  release_holders(mark);
 ++  shell_check_ooms(zSql);
 ++  sstr_holder(zSql); /* +1 */
 ++  if( bDebug ){
 ++    utf8_printf(ISS(p)->out, "%s\n", zSql);
 ++  }else{
 ++    shell_exec(p, zSql, 0);
 ++  }
 ++  release_holder(); /* 0 */
   #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) && !defined(SQLITE_OMIT_VIRTUALTABLE)
 --    {
 --      int lrc;
 --      char *zRevText = /* Query for reversible to-blob-to-text check */
 --        "SELECT lower(name) as tname FROM sqlite_schema\n"
 --        "WHERE type='table' AND coalesce(rootpage,0)>1\n"
 --        "AND name NOT LIKE 'sqlite_%%'%s\n"
 --        "ORDER BY 1 collate nocase";
 --      zRevText = sqlite3_mprintf(zRevText, zLike? " AND name LIKE $tspec" : "");
 --      zRevText = sqlite3_mprintf(
 --          /* lower-case query is first run, producing upper-case query. */
 --          "with tabcols as materialized(\n"
 --          "select tname, cname\n"
 --          "from ("
 --          " select printf('\"%%w\"',ss.tname) as tname,"
 --          " printf('\"%%w\"',ti.name) as cname\n"
 --          " from (%z) ss\n inner join pragma_table_info(tname) ti))\n"
 --          "select 'SELECT total(bad_text_count) AS bad_text_count\n"
 --          "FROM ('||group_concat(query, ' UNION ALL ')||')' as btc_query\n"
 --          " from (select 'SELECT COUNT(*) AS bad_text_count\n"
 --          "FROM '||tname||' WHERE '\n"
 --          "||group_concat('CAST(CAST('||cname||' AS BLOB) AS TEXT)<>'||cname\n"
 --          "|| ' AND typeof('||cname||')=''text'' ',\n"
 --          "' OR ') as query, tname from tabcols group by tname)"
 --          , zRevText);
 --      shell_check_oom(zRevText);
 --      if( bDebug ) utf8_printf(p->out, "%s\n", zRevText);
 --      lrc = sqlite3_prepare_v2(p->db, zRevText, -1, &pStmt, 0);
 --      if( lrc!=SQLITE_OK ){
 --        /* assert(lrc==SQLITE_NOMEM); // might also be SQLITE_ERROR if the
 --        ** user does cruel and unnatural things like ".limit expr_depth 0". */
 --        rc = 1;
 ++  {
 ++    int lrc;
 ++    char *zRevText = /* Query for reversible to-blob-to-text check */
 ++      "SELECT lower(name) as tname FROM sqlite_schema\n"
 ++      "WHERE type='table' AND coalesce(rootpage,0)>1\n"
 ++      "AND name NOT LIKE 'sqlite_%%'%s\n"
 ++      "ORDER BY 1 collate nocase";
 ++    zRevText = smprintf(zRevText, zLike? " AND name LIKE $tspec" : "");
 ++    zRevText = smprintf(
 ++        /* lower-case query is first run, producing upper-case query. */
 ++        "with tabcols as materialized(\n"
 ++        "select tname, cname\n"
 ++        "from ("
-          " select ss.tname as tname, ti.name as cname\n"
+++        " select printf('\"%%w\"',ss.tname) as tname,"
+++        " printf('\"%%w\"',ti.name) as cname\n"
 ++        " from (%z) ss\n inner join pragma_table_info(tname) ti))\n"
 ++        "select 'SELECT total(bad_text_count) AS bad_text_count\n"
 ++        "FROM ('||group_concat(query, ' UNION ALL ')||')' as btc_query\n"
 ++        " from (select 'SELECT COUNT(*) AS bad_text_count\n"
 ++        "FROM '||tname||' WHERE '\n"
 ++        "||group_concat('CAST(CAST('||cname||' AS BLOB) AS TEXT)<>'||cname\n"
 ++        "|| ' AND typeof('||cname||')=''text'' ',\n"
 ++        "' OR ') as query, tname from tabcols group by tname)"
 ++        , zRevText);
 ++    if( bDebug ) utf8_printf(ISS(p)->out, "%s\n", zRevText);
 ++    lrc = s3_prep_noom_free(DBX(p), &zRevText, &pStmt);
 ++    if( lrc!=SQLITE_OK ){
 ++      RESOURCE_FREE(mark);
 ++      return DCR_Error;
 ++    }
 ++    stmt_holder(pStmt);
 ++    if( zLike ) sqlite3_bind_text(pStmt,1,zLike,-1,SQLITE_STATIC);
 ++    lrc = SQLITE_ROW==s3_step_noom(pStmt);
 ++    if( lrc ){
 ++      const char *zGenQuery = (char*)sqlite3_column_text(pStmt,0);
 ++      sqlite3_stmt *pCheckStmt;
 ++      lrc = s3_prepare_v2_noom(DBX(p), zGenQuery, -1, &pCheckStmt, 0);
 ++      if( bDebug ) utf8_printf(ISS(p)->out, "%s\n", zGenQuery);
 ++      stmt_holder(pCheckStmt);
 ++      if( SQLITE_OK!=lrc ){
 ++        RESOURCE_FREE(mark);
 ++        return DCR_Error;
         }else{
 --        if( zLike ) sqlite3_bind_text(pStmt,1,zLike,-1,SQLITE_STATIC);
 --        lrc = SQLITE_ROW==sqlite3_step(pStmt);
 --        if( lrc ){
 --          const char *zGenQuery = (char*)sqlite3_column_text(pStmt,0);
 --          sqlite3_stmt *pCheckStmt;
 --          lrc = sqlite3_prepare_v2(p->db, zGenQuery, -1, &pCheckStmt, 0);
 --          if( bDebug ) utf8_printf(p->out, "%s\n", zGenQuery);
 --          if( lrc!=SQLITE_OK ){
 --            rc = 1;
 --          }else{
 --            if( SQLITE_ROW==sqlite3_step(pCheckStmt) ){
 --              double countIrreversible = sqlite3_column_double(pCheckStmt, 0);
 --              if( countIrreversible>0 ){
 --                int sz = (int)(countIrreversible + 0.5);
 --                utf8_printf(stderr,
 --                     "Digest includes %d invalidly encoded text field%s.\n",
 --                            sz, (sz>1)? "s": "");
 --              }
 --            }
 --            sqlite3_finalize(pCheckStmt);
 ++        if( SQLITE_ROW==sqlite3_step(pCheckStmt) ){
 ++          double countIrreversible = sqlite3_column_double(pCheckStmt, 0);
 ++          if( countIrreversible>0 ){
 ++            int sz = (int)(countIrreversible + 0.5);
 ++            utf8_printf(stderr,
 ++               "Digest includes %d invalidly encoded text field%s.\n",
 ++               sz, (sz>1)? "s": "");
             }
 --          sqlite3_finalize(pStmt);
           }
         }
 --      if( rc ) utf8_printf(stderr, ".sha3sum failed.\n");
 --      sqlite3_free(zRevText);
       }
 ++  }
   #endif /* !defined(*_OMIT_SCHEMA_PRAGMAS) && !defined(*_OMIT_VIRTUALTABLE) */
 --    sqlite3_free(zSql);
 --  }else
 ++  RESOURCE_FREE(mark);
 ++  return DCR_Ok;
 ++}
   
 --#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
 --  if( c=='s'
 --   && (cli_strncmp(azArg[0], "shell", n)==0
 --       || cli_strncmp(azArg[0],"system",n)==0)
 --  ){
 --    char *zCmd;
 --    int i, x;
 --    failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
 --    if( nArg<2 ){
 --      raw_printf(stderr, "Usage: .system COMMAND\n");
 --      rc = 1;
 --      goto meta_command_exit;
 --    }
 --    zCmd = sqlite3_mprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]);
 --    for(i=2; i<nArg && zCmd!=0; i++){
 --      zCmd = sqlite3_mprintf(strchr(azArg[i],' ')==0?"%z %s":"%z \"%s\"",
 --                             zCmd, azArg[i]);
 --    }
 --    x = zCmd!=0 ? system(zCmd) : 1;
 --    sqlite3_free(zCmd);
 --    if( x ) raw_printf(stderr, "System command returns %d\n", x);
 --  }else
 --#endif /* !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) */
 ++/*****************
 ++ * The .selftest* 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",
 ++];
   
 --  if( c=='s' && cli_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
 ++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;
 ++}
   
 --  if( c=='s' && cli_strncmp(azArg[0], "stats", n)==0 ){
 --    if( nArg==2 ){
 --      if( cli_strcmp(azArg[1],"stmt")==0 ){
 --        p->statsOn = 2;
 --      }else if( cli_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);
 ++DISPATCHABLE_COMMAND( selftest 4 0 0 ){
 ++  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 */
 ++  RESOURCE_MARK(mark);
 ++  ShellText str = {0};     /* 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( cli_strcmp(z,"-init")==0 ){
 ++      bIsInit = 1;
 ++    }else
 ++      if( cli_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;
 ++        }
 ++  }
 ++  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);
 ++  text_ref_holder(&str);
 ++  appendText(&str, "x", 0);
 ++  stmt_ptr_holder(&pStmt);
 ++  for(k=bSelftestExists; k>=0; k--){
 ++    if( k==1 ){
 ++      rc = s3_prepare_v2_noom(DBX(p),
 ++              "SELECT tno,op,cmd,ans FROM selftest ORDER BY tno",
 ++              -1, &pStmt, 0);
       }else{
 --      raw_printf(stderr, "Usage: .stats ?on|off|stmt|vmstep?\n");
 --      rc = 1;
 ++      rc = s3_prepare_v2_noom(DBX(p),
 ++          "VALUES(0,'memo','Missing SELFTEST table - default checks only',''),"
 ++          "      (1,'run','PRAGMA integrity_check','ok')",
 ++          -1, &pStmt, 0);
       }
 --  }else
 --
 --  if( (c=='t' && n>1 && cli_strncmp(azArg[0], "tables", n)==0)
 --   || (c=='i' && (cli_strncmp(azArg[0], "indices", n)==0
 --                 || cli_strncmp(azArg[0], "indexes", n)==0) )
 --  ){
 --    sqlite3_stmt *pStmt;
 --    char **azResult;
 --    int nRow, nAlloc;
 --    int ii;
 --    ShellText s;
 --    initText(&s);
 --    open_db(p, 0);
 --    rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
       if( rc ){
 --      sqlite3_finalize(pStmt);
 --      return shellDatabaseError(p->db);
 --    }
 --
 --    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);
 ++      *pzErr = smprintf("Error querying the selftest table\n");
 ++      RESOURCE_FREE(mark);
 ++      return DCR_Error;
 ++    }
 ++    for(i=1; s3_step_noom(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( cli_strcmp(zOp,"memo")==0 ){
 ++        utf8_printf(psi->out, "%s\n", zSql);
 ++      }else if( cli_strcmp(zOp,"run")==0 ){
 ++        char *zErrMsg = 0;
 ++        sstr_ptr_holder(&zErrMsg);
 ++        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);
 ++        }
 ++        if( rc || zErrMsg ){
 ++          nErr++;
 ++          rc = 1;
 ++          utf8_printf(psi->out, "%d: error-code-%d: %s\n", tno, rc,
 ++                      (zErrMsg)? zErrMsg : "");
 ++        }else if( cli_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);
 ++        }
 ++        release_holder();
         }else{
 --        appendText(&s," WHERE type='index'"
 --                      "   AND tbl_name LIKE ?1", 0);
 ++        *pzErr = smprintf
 ++          ("Unknown operation \"%s\" on selftest line %d\n", zOp, tno);
 ++        rc = 1;
 ++        break;
         }
 ++    } /* End loop over rows of content from SELFTEST */
 ++  } /* End loop over k */
 ++  RESOURCE_FREE(mark);
 ++  utf8_printf(psi->out, "%d errors out of %d tests\n", nErr, nTest);
 ++  return rc > 0;
 ++}
 ++
 ++/*****************
 ++ * The .shell and .system commands
 ++ */
 ++#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
 ++# define SHELLOUT_ENABLE 1
 ++#else
 ++# define SHELLOUT_ENABLE 0
 ++#endif
 ++CONDITION_COMMAND( shell SHELLOUT_ENABLE );
 ++CONDITION_COMMAND( system SHELLOUT_ENABLE );
 ++COLLECT_HELP_TEXT[
 ++  ".shell CMD ARGS...       Run CMD ARGS... in a system shell",
 ++  ".system CMD ARGS...      Run CMD ARGS... in a system shell",
 ++];
 ++
 ++#if SHELLOUT_ENABLE
 ++static DotCmdRC shellOut(char *azArg[], int nArg,
 ++                         ShellExState *psx, char **pzErr){
 ++  char *zCmd = 0;
 ++  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_ooms(zCmd);
 ++  sstr_holder(zCmd);
 ++  x = system(zCmd);
 ++  release_holder();
 ++  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( cli_strcmp(zA, "--")==0 ){
 ++      pzExtArgs = azArg + ai;
 ++      break;
       }
 --    rc = sqlite3_finalize(pStmt);
 --    if( rc==SQLITE_OK ){
 --      appendText(&s, " ORDER BY 1", 0);
 --      rc = sqlite3_prepare_v2(p->db, s.z, -1, &pStmt, 0);
 --    }
 --    freeText(&s);
 --    if( rc ) return shellDatabaseError(p->db);
 --
 --    /* Run the SQL statement prepared by the above block. Store the results
 --    ** as an array of nul-terminated strings in azResult[].  */
 --    nRow = nAlloc = 0;
 --    azResult = 0;
 --    if( nArg>1 ){
 --      sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT);
 --    }else{
 --      sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC);
 --    }
 --    while( sqlite3_step(pStmt)==SQLITE_ROW ){
 --      if( nRow>=nAlloc ){
 --        char **azNew;
 --        int n2 = nAlloc*2 + 10;
 --        azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2);
 --        shell_check_oom(azNew);
 --        nAlloc = n2;
 --        azResult = azNew;
 --      }
 --      azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 --      shell_check_oom(azResult[nRow]);
 --      nRow++;
 --    }
 --    if( sqlite3_finalize(pStmt)!=SQLITE_OK ){
 --      rc = shellDatabaseError(p->db);
 --    }
 --
 --    /* Pretty-print the contents of array azResult[] to the output */
 --    if( rc==0 && nRow>0 ){
 --      int len, maxlen = 0;
 --      int i, j;
 --      int nPrintCol, nPrintRow;
 --      for(i=0; i<nRow; i++){
 --        len = strlen30(azResult[i]);
 --        if( len>maxlen ) maxlen = len;
 --      }
 --      nPrintCol = 80/(maxlen+2);
 --      if( nPrintCol<1 ) nPrintCol = 1;
 --      nPrintRow = (nRow + nPrintCol - 1)/nPrintCol;
 --      for(i=0; i<nPrintRow; i++){
 --        for(j=i; j<nRow; j+=nPrintRow){
 --          char *zSp = j<nPrintRow ? "" : "  ";
 --          utf8_printf(p->out, "%s%-*s", zSp, maxlen,
 --                      azResult[j] ? azResult[j]:"");
 ++    if( zFile==0 ) zFile = zA;
 ++    else if( zProc==0 ) zProc = zA;
 ++  }
 ++  if( zFile==0 ) return DCR_Missing;
 ++  if( pzExtArgs==0 ) pzExtArgs = azArg + ai;
 ++  open_db(p, 0);
 ++  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( cli_strcmp(azArg[ia]+1, shopts[io].name)==0 ){
 ++          if( cs=='+' ) psi->bExtendedDotCmds |= shopts[io].mask;
 ++          else psi->bExtendedDotCmds &= ~shopts[io].mask;
 ++          break;
           }
 --        raw_printf(p->out, "\n");
 ++      }
 ++      if( io==ArraySize(shopts) ){
 ++        zAbout = azArg[ia];
 ++        zMoan = "is not a recognized option name";
 ++        goto moan_error;
         }
       }
 ++  }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:
 ++  *pzErr = smprintf("Error: %s %s\n", zAbout, zMoan);
 ++  return DCR_CmdErred;
 ++}
   
 --    for(ii=0; ii<nRow; ii++) sqlite3_free(azResult[ii]);
 --    sqlite3_free(azResult);
 --  }else
 ++static void showWidths(ShellExState *p){
 ++  int i = 0;
 ++  while( i < p->numWidths ){
 ++    raw_printf(ISS(p)->out," %d",p->pSpecWidths[i++]);
 ++  }
 ++  raw_printf(ISS(p)->out,"\n");
 ++}
   
 --#ifndef SQLITE_SHELL_FIDDLE
 --  /* Begin redirecting output to the file "testcase-out.txt" */
 --  if( c=='t' && cli_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");
 --    }
 --    if( nArg>=2 ){
 --      sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", azArg[1]);
 ++DISPATCHABLE_COMMAND( show ? 1 1 ){
 ++  static const char *azBool[] = { "off", "on", "trigger", "full"};
 ++  const char *zOut;
 ++  ShellInState *psi = ISS(p);
 ++  FILE *out = psi->out;
 ++
 ++  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]);
 ++#if SHELL_DATAIO_EXT
 ++  {
 ++    char *zTell = 0;
 ++    int mrc;
 ++    zOut = psi->pActiveExporter->pMethods->name(psi->pActiveExporter);
 ++    mrc = psi->pActiveExporter->pMethods->config(psi->pActiveExporter,0,&zTell);
 ++    if( zTell!=0 ){
 ++      utf8_printf(out, "%12.12s: %s %s\n", "mode", zOut, zTell);
       }else{
 --      sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?");
 ++      utf8_printf(out, "%12.12s: %s\n", "mode", zOut);
       }
 --  }else
 --#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 ++    sqlite3_free(zTell);
 ++  }
 ++#else
 ++  zOut = modeDescr[psi->mode].zModeName;
 ++  if( MODE_IS_COLUMNAR(psi->mode) ){
 ++    utf8_printf(out, "%12.12s: %s --wrap %d --wordwrap %s --%squote\n", "mode",
 ++                zOut, psi->cmOpts.iWrap,
 ++                psi->cmOpts.bWordWrap ? "on" : "off",
 ++                psi->cmOpts.bQuote ? "" : "no");
 ++  }else{
 ++    utf8_printf(out, "%12.12s: %s\n","mode", zOut);
 ++  }
 ++#endif
 ++  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");
 ++  showWidths(p);
 ++  utf8_printf(out, "%12.12s: %s\n", "filename",
 ++              psi->pAuxDb->zDbFilename ? psi->pAuxDb->zDbFilename : "");
 ++  return DCR_Ok;
 ++}
   
 --#ifndef SQLITE_UNTESTABLE
 --  if( c=='t' && n>=8 && cli_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 unless --unsafe-testing */
 --       const char *zUsage;      /* Usage notes */
 --    } aCtrl[] = {
 ++/*****************
 ++ * 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( cli_strcmp(azArg[1],"stmt")==0 ){
 ++      psi->statsOn = 2;
 ++    }else if( cli_strcmp(azArg[1],"vmstep")==0 ){
 ++      psi->statsOn = 3;
 ++    }else{
 ++      psi->statsOn = (u8)booleanValue(azArg[1]);
 ++    }
 ++  }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 commands
 ++ * 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 = 0;
 ++  ShellText s = {0};
 ++  char **azResult = 0;
 ++  AnyResourceHolder arh = { 0, (GenericFreer)freeNameList };
 ++  int nRow, nAlloc;
 ++  int ii;
 ++  RESOURCE_MARK(mark);
 ++
 ++  initText(&s);
 ++  text_ref_holder(&s);
 ++  open_db(p, 0);
 ++  rc = sqlite3_prepare_v2(DBX(p), "PRAGMA database_list", -1, &pStmt, 0);
 ++  stmt_ptr_holder(&pStmt);
 ++  if( shell_check_nomem(rc) ){
 ++  db_err_bail:
 ++    RESOURCE_FREE(mark);
 ++    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");
 ++    RESOURCE_FREE(mark);
 ++    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_%'";
 ++      deliberate_fall_through;
 ++    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);
 ++  pStmt = 0;
 ++  if( rc==SQLITE_OK ){
 ++    appendText(&s, " ORDER BY 1", 0);
 ++    rc = s3_prepare_v2_noom(DBX(p), s.z, -1, &pStmt, 0);
 ++  }
 ++  if( rc ) goto db_err_bail;
 ++
 ++  /* Run the SQL statement prepared by the above block. Store the results
 ++  ** as an array of nul-terminated strings in azResult[]. The 0th element
 ++  ** of azResult[] is not used so that freeNameList() can free it. */
 ++  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);
 ++  }
 ++  any_ref_holder(&arh);
 ++  while( s3_step_noom(pStmt)==SQLITE_ROW ){
 ++    if( nRow+2 > nAlloc ){
 ++      char **azNew;
 ++      int n2 = nAlloc*2 + 10;
 ++      azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2);
 ++      shell_check_ooms(azNew);
 ++      /* Keep the object usable by freeNameList at all times. */
 ++      memset(azNew+nAlloc, 0, (n2-nAlloc)*sizeof(azResult[0]));
 ++      nAlloc = n2;
 ++      arh.pAny = azNew;
 ++      azResult = azNew;
 ++    }
 ++    ++nRow;
 ++    azResult[nRow] = smprintf("%s", sqlite3_column_text(pStmt, 0));
 ++    shell_check_ooms(azResult[nRow]);
 ++  }
 ++  if( sqlite3_finalize(pStmt)!=SQLITE_OK ){
 ++    pStmt = 0;
 ++    goto db_err_bail;
 ++  }
 ++  pStmt = 0;
 ++  /* 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=1; 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=1; 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");
 ++    }
 ++  }
 ++  RESOURCE_FREE(mark);
 ++  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');
 ++}
 ++
 ++/*****************
 ++ * The .selecttrace, .treetrace and .wheretrace commands (undocumented)
 ++ */
 ++static DotCmdRC setTrace( char *azArg[], int nArg, ShellExState *psx, int ts ){
 ++  unsigned int x = nArg>1 ? (unsigned int)integerValue(azArg[1]) : ~0;
 ++  sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, ts, &x);
 ++  return DCR_Ok;
 ++}
 ++DISPATCHABLE_COMMAND( selecttrace 0 1 2 ){
 ++  return setTrace(azArg, nArg, p, 1);
 ++}
 ++DISPATCHABLE_COMMAND( treetrace 0 1 2 ){
 ++  return setTrace(azArg, nArg, p, 1);
 ++}
 ++DISPATCHABLE_COMMAND( wheretrace 0 1 2 ){
 ++  return setTrace(azArg, nArg, p, 3);
 ++}
 ++
 ++/*****************
 ++ * The .testcase, .testctrl, .timeout, .timer and .trace commands
 ++ */
 ++CONDITION_COMMAND( testcase !defined(SQLITE_SHELL_FIDDLE) );
 ++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 */
+++    int unSafe;              /* Not valid unless --unsafe-testing */
 ++    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, ""        },*/
       {"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;
 ++  };
 ++  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( !ShellHasFlag(p,SHFLG_TestingMode) ){
-      utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n",
-                  "testctrl");
-      return DCR_Error;
-    }
 --    open_db(p, 0);
 --    zCmd = nArg>=2 ? azArg[1] : "help";
 ++  open_db(p, 0);
 ++  zCmd = nArg>=2 ? azArg[1] : "help";
 ++
 ++  /* The argument can optionally begin with "-" or "--" */
 ++  if( zCmd[0]=='-' && zCmd[1] ){
 ++    zCmd++;
 ++    if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 ++  }
   
 --    /* The argument can optionally begin with "-" or "--" */
 --    if( zCmd[0]=='-' && zCmd[1] ){
 --      zCmd++;
 --      if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 ++  /* --help lists all test-controls */
 ++  if( cli_strcmp(zCmd,"help")==0 ){
 ++    utf8_printf(out, "Available test-controls:\n");
 ++    for(i=0; i<ArraySize(aCtrl); i++){
+++      if( aCtrl[i].unSafe && !ShellHasFlag(p,SHFLG_TestingMode) ) continue;
 ++      utf8_printf(out, "  .testctrl %s %s\n",
 ++                  aCtrl[i].zCtrlName, aCtrl[i].zUsage);
       }
 ++    return DCR_CmdErred;
 ++  }
   
 --    /* --help lists all test-controls */
 --    if( cli_strcmp(zCmd,"help")==0 ){
 --      utf8_printf(p->out, "Available test-controls:\n");
 --      for(i=0; i<ArraySize(aCtrl); i++){
 --        if( aCtrl[i].unSafe && !ShellHasFlag(p,SHFLG_TestingMode) ) continue;
 --        utf8_printf(p->out, "  .testctrl %s %s\n",
 --                    aCtrl[i].zCtrlName, aCtrl[i].zUsage);
 ++  /* convert testctrl text option to value. allow any unique prefix
 ++  ** of the option name, or a numerical value. */
 ++  n2 = strlen30(zCmd);
 ++  for(i=0; i<ArraySize(aCtrl); i++){
+++    if( aCtrl[i].unSafe && !ShellHasFlag(p,SHFLG_TestingMode) ) continue;
 ++    if( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
 ++      if( testctrl<0 ){
 ++        testctrl = aCtrl[i].ctrlCode;
 ++        iCtrl = i;
 ++      }else{
 ++        *pzErr = smprintf
 ++          ("Error: ambiguous test-control: \"%s\"\n"
 ++           "Use \".testctrl --help\" for help\n", zCmd);
 ++        return DCR_ArgWrong;
         }
 --      rc = 1;
 --      goto meta_command_exit;
       }
-    }else if( aCtrl[iCtrl].unSafe
-              && failIfSafeMode(p,"line %d: \".testctrl %s\" "
-                                "may not be used in safe mode\n",
-                                ISS(p)->pInSource->lineno,
-                                aCtrl[iCtrl].zCtrlName) ){
-      return DCR_Abort;
 ++  }
 ++  if( testctrl<0 ){
 ++    utf8_printf(STD_ERR,"Error: unknown test-control: %s\n"
 ++                "Use \".testctrl --help\" for help\n", zCmd);
 ++  }else{
 ++    switch(testctrl){
   
 --    /* convert testctrl text option to value. allow any unique prefix
 --    ** of the option name, or a numerical value. */
 --    n2 = strlen30(zCmd);
 --    for(i=0; i<ArraySize(aCtrl); i++){
 --      if( aCtrl[i].unSafe && !ShellHasFlag(p,SHFLG_TestingMode) ) continue;
 --      if( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
 --        if( testctrl<0 ){
 --          testctrl = aCtrl[i].ctrlCode;
 --          iCtrl = i;
 ++      /* 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;
 ++      }
 ++      break;
 ++
 ++      /* sqlite3_test_control(int) */
 ++    case SQLITE_TESTCTRL_PRNG_SAVE:
 ++    case SQLITE_TESTCTRL_PRNG_RESTORE:
 ++    case SQLITE_TESTCTRL_BYTEORDER:
 ++      if( nArg==2 ){
 ++        rc2 = sqlite3_test_control(testctrl);
 ++        isOk = testctrl==SQLITE_TESTCTRL_BYTEORDER ? 1 : 3;
 ++      }
 ++      break;
 ++
 ++      /* sqlite3_test_control(int, uint) */
 ++    case SQLITE_TESTCTRL_PENDING_BYTE:
 ++      if( nArg==3 ){
 ++        unsigned int opt = (unsigned int)integerValue(azArg[2]);
 ++        rc2 = sqlite3_test_control(testctrl, opt);
 ++        isOk = 3;
 ++      }
 ++      break;
 ++
 ++      /* sqlite3_test_control(int, int, sqlite3*) */
 ++    case SQLITE_TESTCTRL_PRNG_SEED:
 ++      if( nArg==3 || nArg==4 ){
 ++        int ii = (int)integerValue(azArg[2]);
 ++        sqlite3 *db;
 ++        if( ii==0 && cli_strcmp(azArg[2],"random")==0 ){
 ++          sqlite3_randomness(sizeof(ii),&ii);
 ++          fprintf(STD_OUT, "-- random seed: %d\n", ii);
 ++        }
 ++        if( nArg==3 ){
 ++          db = 0;
           }else{
 --          utf8_printf(stderr, "Error: ambiguous test-control: \"%s\"\n"
 --                              "Use \".testctrl --help\" for help\n", zCmd);
 --          rc = 1;
 --          goto meta_command_exit;
 ++          db = DBX(p);
 ++          /* Make sure the schema has been loaded */
 ++          sqlite3_table_column_metadata(db, 0, "x", 0, 0, 0, 0, 0, 0);
 ++        }
 ++        rc2 = sqlite3_test_control(testctrl, ii, db);
 ++        isOk = 3;
 ++      }
 ++      break;
 ++
 ++      /* sqlite3_test_control(int, int) */
 ++    case SQLITE_TESTCTRL_ASSERT:
 ++    case SQLITE_TESTCTRL_ALWAYS:
 ++      if( nArg==3 ){
 ++        int opt = booleanValue(azArg[2]);
 ++        rc2 = sqlite3_test_control(testctrl, opt);
 ++        isOk = 1;
 ++      }
 ++      break;
 ++
 ++      /* sqlite3_test_control(int, int) */
 ++    case SQLITE_TESTCTRL_LOCALTIME_FAULT:
 ++    case SQLITE_TESTCTRL_NEVER_CORRUPT:
 ++      if( nArg==3 ){
 ++        int opt = booleanValue(azArg[2]);
 ++        rc2 = sqlite3_test_control(testctrl, opt);
 ++        isOk = 3;
 ++      }
 ++      break;
 ++
 ++      /* sqlite3_test_control(sqlite3*) */
 ++    case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS:
 ++      rc2 = sqlite3_test_control(testctrl, DBX(p));
 ++      isOk = 3;
 ++      break;
 ++
 ++    case SQLITE_TESTCTRL_IMPOSTER:
 ++      if( nArg==5 ){
 ++        rc2 = sqlite3_test_control(testctrl, DBX(p),
 ++                                   azArg[2],
 ++                                   integerValue(azArg[3]),
 ++                                   integerValue(azArg[4]));
 ++        isOk = 3;
 ++      }
 ++      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;
 ++      }
 ++      break;
 ++    }
 ++#endif
 ++#ifdef SQLITE_DEBUG
 ++    case SQLITE_TESTCTRL_TUNE: {
 ++      if( nArg==4 ){
 ++        int id = (int)integerValue(azArg[2]);
 ++        int val = (int)integerValue(azArg[3]);
 ++        sqlite3_test_control(testctrl, id, &val);
 ++        isOk = 3;
 ++      }else if( nArg==3 ){
 ++        int id = (int)integerValue(azArg[2]);
 ++        sqlite3_test_control(testctrl, -id, &rc2);
 ++        isOk = 1;
 ++      }else if( nArg==2 ){
 ++        int id = 1;
 ++        while(1){
 ++          int val = 0;
 ++          rc2 = sqlite3_test_control(testctrl, -id, &val);
 ++          if( rc2!=SQLITE_OK ) break;
 ++          if( id>1 ) utf8_printf(out, "  ");
 ++          utf8_printf(out, "%d: %d", id, val);
 ++          id++;
           }
 ++        if( id>1 ) utf8_printf(out, "\n");
 ++        isOk = 3;
         }
 ++      break;
 ++    }
 ++#endif
       }
 --    if( testctrl<0 ){
 --      utf8_printf(stderr,"Error: unknown test-control: %s\n"
 --                         "Use \".testctrl --help\" for help\n", zCmd);
 ++  }
 ++  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
 ++      else if( optionMatch(z, "plain") ){
 ++        psi->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 {
 ++        return DCR_Unknown|jj;
 ++      }
       }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;
 ++      output_file_close(psi->traceOut);
 ++      psi->traceOut = output_file_open(z, 0);
 ++    }
 ++  }
 ++  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;
 ++}
   
 --        /* 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;
 ++/*****************
 ++ * 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;
 ++}
   
 --        /* 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 .unmodule command
 ++ */
 ++#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_VIRTUALTABLE)
 ++# define UNMODULE_ENABLE 1
 ++#else
 ++# define UNMODULE_ENABLE 0
 ++#endif
 ++CONDITION_COMMAND( unmodule UNMODULE_ENABLE );
 ++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;
   
 --        /* 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 && cli_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;
 ++  open_db(p, 0);
 ++  zOpt = azArg[1];
 ++  if( zOpt[0]=='-' && zOpt[1]=='-' && zOpt[2]!=0 ) zOpt++;
 ++  lenOpt = (int)strlen(zOpt);
 ++  if( lenOpt>=3 && cli_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);
 ++    }
 ++  }
 ++  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;
 --          }
 ++/*****************
 ++ * 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( cli_strcmp(azArg[1],"login")==0 ){
 ++    if( nArg!=4 ){
 ++      goto teach_fail;
 ++    }
 ++    rc = sqlite3_user_authenticate(DBX(p), azArg[2], azArg[3],
 ++                                   strlen30(azArg[3]));
 ++    if( rc ){
 ++      *pzErr = smprintf(0,"Authentication failed for user %s\n", azArg[2]);
 ++      return DCR_Error;
 ++    }
 ++  }else if( cli_strcmp(azArg[1],"add")==0 ){
 ++    if( nArg!=5 ){
 ++      goto teach_fail;
 ++    }
 ++    rc = sqlite3_user_add(DBX(p), azArg[2], azArg[3], strlen30(azArg[3]),
 ++                          booleanValue(azArg[4]));
 ++    if( rc ){
 ++      *pzErr = smprintf(0,"User-Add failed: %d\n", rc);
 ++      return DCR_Error;
 ++    }
 ++  }else if( cli_strcmp(azArg[1],"edit")==0 ){
 ++    if( nArg!=5 ){
 ++      goto teach_fail;
 ++    }
 ++    rc = sqlite3_user_change(DBX(p), azArg[2], azArg[3], strlen30(azArg[3]),
 ++                             booleanValue(azArg[4]));
 ++    if( rc ){
 ++      *pzErr = smprintf(0,"User-Edit failed: %d\n", rc);
 ++      return DCR_Error;
 ++    }
 ++  }else if( cli_strcmp(azArg[1],"delete")==0 ){
 ++    if( nArg!=3 ){
 ++      goto teach_fail;
 ++    }
 ++    rc = sqlite3_user_delete(DBX(p), azArg[2]);
 ++    if( rc ){
 ++      *pzErr = smprintf(0,"User-Delete failed: %d\n", rc);
 ++      return DCR_Error;
 ++    }
 ++  }else{
 ++    goto teach_fail;
 ++  }
 ++  return DCR_Ok;
 ++}
 ++
 ++/*****************
 ++ * 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;
 ++  char *zErr = 0;
 ++  sqlite3 *dbs = p->dbShell;
 ++  const char *zCmd = (nArg>1)? azArg[1] : "ls";
 ++  int rc = 0;
 ++  int ncCmd = strlen30(zCmd);
 ++
 ++  if( *zCmd>'e' && ncCmd<2 ) return DCR_Ambiguous|1;
 ++#define SUBCMD(scn) (cli_strncmp(zCmd, scn, ncCmd)==0)
 ++
 ++  /* 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;
 ++  }
 ++
 ++  /* .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;
 ++      sqst_ptr_holder(&sbZap);
 ++      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);
 ++      drop_holder();
 ++      shell_check_ooms(zSql);
 ++      sstr_holder(zSql);
 ++      rc = sqlite3_exec(dbs, zSql, 0, 0, 0);
 ++      release_holder();
 ++    }
 ++#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;
 ++        }
 ++      }
 ++      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, &zErr);
 ++      ++ia;
 ++      if( zErr!=0 ){
 ++        utf8_printf(STD_ERR, "%s", zErr);
 ++        sqlite3_free(zErr);
 ++        zErr = 0;
 ++      }
 ++      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{
 ++      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
 ++}
 ++
 ++/*****************
 ++ * The .vfsinfo, .vfslist, .vfsname and .version commands
 ++ */
 ++COLLECT_HELP_TEXT[
 ++  ".version                 Show source, library and compiler versions",
 ++  ".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;
 ++}
 ++
 ++/*****************
 ++ * The .width command
 ++ */
 ++static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths){
 ++  int j;
 ++  int *pSW = p->pSpecWidths;
 ++  p->numWidths = nWidths;
 ++  pSW = realloc(pSW, (nWidths+1)*sizeof(int)*2);;
 ++  shell_check_oomm(pSW);
 ++  p->pSpecWidths = pSW;
 ++  if( nWidths>0 ){
 ++    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. A lone ? shows the current values.",
 ++];
 ++DISPATCHABLE_COMMAND( width ? 1 0 ){
 ++  if( nArg==2 && cli_strcmp(azArg[1],"?")==0 ) showWidths(p);
 ++  else setColumnWidths(p, azArg+1, nArg-1);
 ++  return DCR_Ok;
 ++}
 ++
 ++/*****************
 ++ * The .x, .read and .eval commands
 ++ * These are together because they share some function and implementation.
 ++ */
 ++CONDITION_COMMAND(read !defined(SQLITE_SHELL_FIDDLE));
 ++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;
 ++  }
 ++}
 ++
 ++/* 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);
 ++  AnyResourceHolder arh = { &psi->pInSource, (GenericFreer)finish_InSource };
 ++  psi->pInSource = &inRedir;
 ++  any_ref_holder(&arh);
 ++  rv = process_input(psi);
 ++  release_holder();
 ++  return rv;
 ++}
 ++
 ++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;
 ++  ShellInState *psi = ISS(p);
 ++  InSource inSourceRedir
 ++    = INSOURCE_FILE_REDIR(0, azArg[1], psi->pInSource);
 ++
 ++  if( psi->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;
 ++#else
 ++    if( (inSourceRedir.inFile = popen(azArg[1]+1, "r"))==0 ){
 ++      *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
 ++      rc = DCR_Error;
 ++    }else{
 ++      inSourceRedir.closer.stream = pclose;
 ++    }
 ++#endif
 ++  }else if( (inSourceRedir.inFile = openChrSource(azArg[1]))==0 ){
 ++    *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
 ++    rc = DCR_Error;
 ++  }else{
 ++    inSourceRedir.closer.stream = fclose;
 ++  }
 ++  if( inSourceRedir.inFile!=0 ){
 ++    AnyResourceHolder arh = { &(psi->pInSource),(GenericFreer)finish_InSource };
 ++    psi->pInSource = &inSourceRedir;
 ++    any_ref_holder(&arh);
 ++    rc = process_input(psi);
 ++    /* If error(s) occurred during process, leave complaining to them. */
 ++    if( rc==DCR_Error ) rc = DCR_CmdErred;
 ++    release_holder();
 ++  }
 ++  return rc;
 ++}
 ++
 ++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;
 ++        }
 ++      }
 ++      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;
 ++        }
 ++        rc = s3_prepare_v2_noom(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;
 ++        }
 ++      }
 ++      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. */
 ++          }
 ++        }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( 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;
 ++    }
 ++  }
 ++  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;
 ++}
 ++
 ++/* 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 );
 ++
 ++static void DotCommand_destruct(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_destruct,
 ++  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) \
 ++  &dot_cmd_VtabBuiltIn, #cmd, cmd ## Command, nlenMin, minArgs, maxArgs
 ++  EMIT_DOTCMD_INIT(2);
 ++#undef DOT_CMD_INFO
 ++  { 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 || (unsigned)ix>=numCommands ) return 0;
 ++  return (DotCommand *)&command_table[ix];
 ++}
   
 --        /* 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;
 ++static void DotCommand_destruct(DotCommand *pMe){
 ++  UNUSED_PARAMETER(pMe);
 ++}
   
 --        /* sqlite3_test_control(sqlite3*) */
 --        case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS:
 --          rc2 = sqlite3_test_control(testctrl, p->db);
 --          isOk = 3;
 --          break;
 ++static const char * DotCommand_name(DotCommand *pMe){
 ++  return ((struct CommandInfo *)pMe)->cmdName;
 ++}
   
 --        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;
 ++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;
 ++}
   
 --        case SQLITE_TESTCTRL_SEEK_COUNT: {
 --          u64 x = 0;
 --          rc2 = sqlite3_test_control(testctrl, p->db, &x);
 --          utf8_printf(p->out, "%llu\n", x);
 --          isOk = 3;
 --          break;
 --        }
 ++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;
 ++}
   
 --#ifdef YYCOVERAGE
 --        case SQLITE_TESTCTRL_PARSER_COVERAGE: {
 --          if( nArg==2 ){
 --            sqlite3_test_control(testctrl, p->out);
 --            isOk = 3;
 --          }
 --          break;
 --        }
 ++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 improvised 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
 --#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;
 --        }
 ++  } cursor;
 ++#if SHELL_DYNAMIC_EXTENSION
 ++  char *zAdhocHelpText; /* registered extension improvised help */
   #endif
 --        case SQLITE_TESTCTRL_SORTER_MMAP:
 --          if( nArg==3 ){
 --            int opt = (unsigned int)integerValue(azArg[2]);
 --            rc2 = sqlite3_test_control(testctrl, p->db, opt);
 --            isOk = 3;
 --          }
 --          break;
 --      }
 --    }
 --    if( isOk==0 && iCtrl>=0 ){
 --      utf8_printf(p->out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
 --      rc = 1;
 --    }else if( isOk==1 ){
 --      raw_printf(p->out, "%d\n", rc2);
 --    }else if( isOk==2 ){
 --      raw_printf(p->out, "0x%08x\n", rc2);
 --    }
 --  }else
 --#endif /* !defined(SQLITE_UNTESTABLE) */
 ++} 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->cursor.pDotCmd = 0;
 ++  }
 ++#if SHELL_DYNAMIC_EXTENSION
 ++  else{
 ++    sqlite3_finalize(pMMI->cursor.stmt);
 ++    pMMI->cursor.stmt = 0;
 ++  }
 ++  sqlite3_free(pMMI->zAdhocHelpText);
 ++  pMMI->zAdhocHelpText = 0;
 ++#endif
 ++}
   
 --  if( c=='t' && n>4 && cli_strncmp(azArg[0], "timeout", n)==0 ){
 --    open_db(p, 0);
 --    sqlite3_busy_timeout(p->db, nArg>=2 ? (int)integerValue(azArg[1]) : 0);
 ++/* 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 ){
 ++      s3_prepare_v2_noom(psx->dbShell, zSqlIter, -1, &mmi.cursor.stmt, 0);
 ++      sqlite3_bind_text(mmi.cursor.stmt, 1, cmdFragment? cmdFragment:"", -1, 0);
 ++    }
 ++    s3_prepare_v2_noom(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_ooms((void *)mmi.zPattern);
 ++
 ++    struct CommandInfo *pCI = command_table;
 ++    mmi.cursor.pDotCmd = (DotCommand *)command_table;
 ++    while( pCI<command_table+numCommands ){
 ++      if( sqlite3_strglob(mmi.zPattern, pCI->cmdName)==0 ) ++rv;
 ++      ++pCI;
 ++    }
 ++  }
 ++  if( pMMI ) *pMMI = mmi;
 ++  else freeCmdMatchIter(&mmi);
 ++  return rv;
 ++}
   
 --  if( c=='t' && n>=5 && cli_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;
 ++/* 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->cursor.stmt);
 ++    if( rc==SQLITE_ROW ){
 ++      /* name, extIx, cmdIx, help */
 ++      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->cursor.stmt, 3);
 ++        assert(psi->pUnknown!=0);
 ++        assert(extIx<psi->numExtLoaded && extIx>0);
 ++        if( zHT==0 ) zHT = sqlite3_column_text(pMMI->cursor.stmt, 0);
 ++        pMMI->zAdhocHelpText = smprintf("%s", zHT);
 ++        return psi->pShxLoaded[extIx].pUnknown;
         }
       }else{
 --      raw_printf(stderr, "Usage: .timer on|off\n");
 --      rc = 1;
 ++      sqlite3_finalize(pMMI->cursor.stmt);
 ++      pMMI->cursor.stmt = 0;
       }
     }else
 ++#endif
 ++  {
 ++    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->cursor.pDotCmd;
 ++      }
 ++      pMMI->cursor.pDotCmd = (DotCommand *)(++pCI);
 ++      if( rv!=0 ) break;
 ++    }
 ++  }
 ++  return rv;
 ++}
   
 --#ifndef SQLITE_OMIT_TRACE
 --  if( c=='t' && cli_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;
 --        }
 ++/*****************
 ++** 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 = s3_prepare_v2_noom(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
 --        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;
 --        }
 ++  {
 ++    int cmdLen = strlen30(cmdName);
 ++    struct CommandInfo *pci = 0;
 ++    int ixb = 0, ixe = numCommands-1;
 ++    while( ixb <= ixe ){
 ++      int ixm = (ixb+ixe)/2;
 ++      int md = cli_strncmp(cmdName, command_table[ixm].cmdName, cmdLen);
 ++      if( md>0 ){
 ++        ixb = ixm+1;
 ++      }else if( md<0 ){
 ++        ixe = ixm-1;
         }else{
 --        output_file_close(p->traceOut);
 --        p->traceOut = output_file_open(z, 0);
 ++        /* Have a match, see whether it's ambiguous. */
 ++        if( command_table[ixm].minLen > cmdLen ){
 ++          if( (ixm>0
 ++               && !cli_strncmp(cmdName, command_table[ixm-1].cmdName, cmdLen))
 ++              ||
 ++              (ixm<ixe
 ++               && !cli_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( p->traceOut==0 ){
 --      sqlite3_trace_v2(p->db, 0, 0, 0);
 --    }else{
 --      if( mType==0 ) mType = SQLITE_TRACE_STMT;
 --      sqlite3_trace_v2(p->db, mType, sql_trace_callback, p);
 --    }
 --  }else
 --#endif /* !defined(SQLITE_OMIT_TRACE) */
 ++    if( pnFound && pci ) *pnFound = 1;
 ++    return (DotCommand *)pci;
 ++  }
 ++}
   
 --#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_VIRTUALTABLE)
 --  if( c=='u' && cli_strncmp(azArg[0], "unmodule", n)==0 ){
 --    int ii;
 --    int lenOpt;
 --    char *zOpt;
 --    if( nArg<2 ){
 --      raw_printf(stderr, "Usage: .unmodule [--allexcept] NAME ...\n");
 ++/*
 ++** Given a DotCommand, desired help level,
 ++** ( possibly retrieved improvised 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;
 --      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 && cli_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);
 ++      break;
 ++    case 2:
 ++      if( zSearch ){
 ++        if( !sqlite3_strlike(zSearch, zHT, 0) ) break;
         }
 ++      deliberate_fall_through;
 ++    case 1:
 ++    default:
 ++      utf8_printf(out,".%s", zHT);
 ++      rc = 1;
       }
     }else
   #endif
diff --cc src/util.c
index bed86a569085f17edd6bdc6dd5ea22ff9f43913e,256ec7c5c7458cd7abda1b82b8e76121d2d335a3,256ec7c5c7458cd7abda1b82b8e76121d2d335a3..256ec7c5c7458cd7abda1b82b8e76121d2d335a3
mode 100755,100644,100644..100755
diff --cc src/vdbe.c
index 7045a1e4bd3ab75424ada566c7d1bf01581b78f7,8255f6c4a9dcccb4d27f3602299f20189b0f8079,8255f6c4a9dcccb4d27f3602299f20189b0f8079..8255f6c4a9dcccb4d27f3602299f20189b0f8079
mode 100755,100644,100644..100755
Simple merge