]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Sync w/trunk, for .import fix.
authorlarrybr <larrybr@noemail.net>
Sat, 9 Apr 2022 19:39:58 +0000 (19:39 +0000)
committerlarrybr <larrybr@noemail.net>
Sat, 9 Apr 2022 19:39:58 +0000 (19:39 +0000)
FossilOrigin-Name: 861ab023be283782131df5ccd9eff50cf2f06f48f1ea7defd3f08177a3df5889

1  2 
ext/misc/tclshext.c.in
manifest
manifest.uuid
src/shell.c.in
test/shell5.test

index 5374c66701ff286c2914591c57d5df74dbc4c596,0000000000000000000000000000000000000000..b27c698cf971993a72bbddaf421e7804257f1963
mode 100644,000000..100644
--- /dev/null
@@@ -1,1305 -1,0 +1,1305 @@@
-         if {$result != "" && $interactive} {
 +/*
 +** 2022 March 20
 +**
 +** The author disclaims copyright to this source code.  In place of
 +** a legal notice, here is a blessing:
 +**
 +**    May you do good and not evil.
 +**    May you find forgiveness for yourself and forgive others.
 +**    May you share freely, never taking more than you give.
 +**
 +*************************************************************************
 +** This file contains code to implement the "tclshext" shell extension
 +** for use with the extensible "sqlite3" CLI shell. On *Nix, build thusly:
 +     tool/mkshellc.tcl ext/misc/tclshext.c.in > tclshext.c
 +     gcc -shared -fPIC -O2 -I. -Isrc -I/usr/include/tcl8.6 \
 +       tclshext.c -o tclshext.so -ltcl8.6
 +** Or, (after ./configure ...), use the provided Makefile thusly:
 +     make tcl_shell_extension
 +** If the Tk library is available, it can be linked and used thusly:
 +     gcc -shared -fPIC -O2 -I. -Isrc -I/usr/include/tcl8.6 \
 +       -DSHELL_ENABLE_TK tclshext.c -o tclshext.so -ltcl8.6 -ltk8.6
 +** Or, make the same target with Tk thusly:
 +     make tcl_shell_extension WITH_TK=1
 +** Later TCL versions can be used if desired (and installed.)
 +**
 +** TCL scripting support is added with a registerScripting() call (in the
 +** ShellExtensionAPI), meeting ScriptingSupport interface requirements.
 +*/
 +static const char * const zTclHelp =
 +  "This extension adds these features to the host shell:\n"
 +  " 1. TCL scripting support is added.\n"
 +  " 2. TCL commands are added: udb shdb now_interactive get_tcl_group ..\n"
 +  " 3. The .tcl and .unknown dot commands are added.\n"
 +  " 4. If built with Tk capability, the gui TCL command will be added if this\n"
 +  "  extension was loaded using shell command: .load tclshext -shext -tk .\n"
 +  "  Any other arguments beyond -shext are copied into TCL's argv variable.\n"
 +  "Operation:\n"
 +  " Shell input groups beginning with \"..\" are treated as TCL input, in\n"
 +  " these ways: (1) When a bare \"..\" is entered, a TCL REPL loop is run\n"
 +  " until the end of input is seen; (2) When \"..D ...\" is entered, (where\n"
 +  " \"D\" is a dot-command name), the D dot command will be run in its normal\n"
 +  " fashion, but its arguments will be collected according to TCL parsing\n"
 +  " rules then expanded as usual for TCL commands; and (3) when \".. T ...\"\n"
 +  " is entered, (where \"T\" is a TCL command name), that TCL command and its\n"
 +  " arguments will be collected and expanded according to TCL parsing rules,\n"
 +  " then run in the TCL execution environment (in its global namespace), but\n"
 +  " the shell REPL and execution environment remains in effect afterward.\n"
 +  "\n"
 +  " Note that cases 2 and 3 differ in having space after the leading \"..\".\n"
 +  "\n"
 +  " The phrase \"end of input\" means either: end-of-file is seen on a file,\n"
 +  " pipe or string stream input, or a lone \".\" on the first and only line\n"
 +  " of an input line group is seen. This convention is useful in scripting\n"
 +  " when it is expedient to switch execution environments from within the\n"
 +  " same input stream. This could be input piped in from another process.\n"
 +  "\n"
 +  ;
 +/*
 +** For example:
 +     # From shell, enter TCL REPL:
 +     ..
 +     # Initialize some variables and insert into the DB:
 +     set var1 [compute_however ...]
 +     set var2 [derive_somehow ...]
 +     udb eval { INSERT INTO SomeTable(col1, col2) VALUES($var1, var2) }
 +     # Leave REPL
 +     .
 +     # Generate and keep pretty output:
 +     .mode box -ww
 +     .header on
 +     .once prettified.txt
 +     SELECT * FROM SomeTable;
 +     # Alternatively, the query can be run from the TCL environment:
 +     ..
 +     set tstamp [clock format [clock seconds] -format %Y-%m-%d]
 +     .once "backup of prettified.txt made $tstamp"
 +     .eval {SELECT col1, col2 FROM SomeTable}
 +     # Return to shell environment:
 +     .
 +**
 +** For any of these ways of providing TCL input, the same TCL interpreter
 +** is used, with its state maintained from one input to the next. In this
 +** way, .sqliterc or other preparatory shell scripts (or typing) can be
 +** made to provide useful, user-defined shell enhancements or specialized
 +** procedures (aka "TCL commands") for oft-repeated tasks.
 +**
 +** The added TCL commands are:
 +**   udb shdb ; # exposes the user DB and shell DB for access via TCL
 +**   now_interactive ; # indicates whether current input is interactive
 +**   get_tcl_group ; # gets one TCL input line group from current input
 +**   register_adhoc_command ; # aids creation of dot commands with help
 +**   .. ; # does nothing, silently and without error
 +**
 +** The .. command exists so that a lone ".." on an input line suffices
 +** to ensure the TCL REPL is running. This is symmetric with a lone "."
 +** input to the TCL REPL because it either terminates the loop or, if
 +** entered in the shell environment, quietly does nothing without error.
 +**
 +** The added .tcl dot command may be used to enter a TCL REPL, or with
 +** arguments, it will read files as TCL. (This is somewhat extraneous,
 +** as the same can be done with TCL commands, but it is more easily done
 +** from the shell invocation, and the .tcl command's integration into
 +** the .help facility provides a way for users to get help for "..".)
 +**
 +** The added .unknown dot command overrides the shell's .unknown so
 +** that new dot commands can be implemented in TCL and then be run
 +** from the shell in the dot command execution context.
 +*/
 +
 +#include "shx_link.h"
 +
 +/* Extension boiler-plate to dynamically link into host's SQLite library */
 +SQLITE_EXTENSION_INIT1;
 +
 +/* Extension boiler-plate for a function to get ShellExtensionLink pointer
 + * from db passed to extension init() and define a pair of static API refs.
 + */
 +SHELL_EXTENSION_INIT1(pShExtApi, pExtHelpers, shextLinkFetcher);
 +#define SHX_API(entry) pShExtApi->entry
 +#define SHX_HELPER(entry) pExtHelpers->entry
 +
 +/* This is not found in the API pointer table published for extensions: */
 +#define sqlite3_enable_load_extension SHX_HELPER(enable_load_extension)
 +
 +/* Forward reference for use as ExtensionId */
 +#ifdef _WIN32
 +__declspec(dllexport)
 +#endif
 +int sqlite3_tclshext_init(sqlite3*, char**, const sqlite3_api_routines*);
 +
 +/* Control how tclsqlite.c compiles. (REPL is effected here, not there.) */
 +#define STATIC_BUILD /* Not publishing TCL API */
 +#undef SQLITE_AMALGAMATION
 +#undef TCLSH
 +#include "tclOO.h"
 +#ifdef SHELL_ENABLE_TK
 +#include "tk.h" /* Only used if option -tk passed during load. */
 +#endif
 +INCLUDE tclsqlite.c
 +
 +#if defined(_WIN32) || defined(WIN32)
 +# define getDir(cArray) _getwd(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, 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, MetaCommand,TclCmd, 0, ()){
 +  /* Nothing to do, instance data is static. */
 +  (void)(pThis);
 +}
 +static DERIVED_METHOD(void, destruct, MetaCommand,UnkCmd, 0, ());
 +
 +DERIVED_METHOD(void, destruct, ScriptSupport,TclSS, 0, ()){
 +  /* Nothing to do, instance data is static. */
 +  (void)(pThis);
 +}
 +
 +DERIVED_METHOD(const char *, name, MetaCommand,TclCmd, 0,()){
 +  return "tcl";
 +}
 +DERIVED_METHOD(const char *, name, MetaCommand,UnkCmd, 0,()){
 +  return "unknown";
 +}
 +
 +DERIVED_METHOD(const char *, help, MetaCommand,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, MetaCommand,UnkCmd, 1,(const char *zHK));
 +
 +DERIVED_METHOD(DotCmdRC, argsCheck, MetaCommand,TclCmd, 3,
 +             (char **pzErrMsg, int nArgs, char *azArgs[])){
 +  return DCR_Ok;
 +}
 +DERIVED_METHOD(DotCmdRC, argsCheck, MetaCommand,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 {$interactive && $trimmed ne "."} {puts {}}
++        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, MetaCommand,TclCmd, 4,
 +             (ShellExState *psx, char **pzErrMsg, int nArgs, char *azArgs[])){
 +  FILE *out = SHX_HELPER(currentOutputFile)(psx);
 +  TclCmd *ptc = (TclCmd *)pThis;
 +  if( nArgs>1 ){
 +    /* Read named files into the interpreter. */
 +    int rc = TCL_OK;
 +    int aix;
 +    for( aix=0; aix<(nArgs-1) && rc==TCL_OK; ++aix ){
 +      rc = Tcl_EvalFile(getInterp(), azArgs[aix+1]);
 +    }
 +    if( rc!=TCL_OK ){
 +      copy_complaint(pzErrMsg, getInterp());
 +      return DCR_Error;
 +    }
 +    return DCR_Ok;
 +  }else{
 +    /* Enter a REPL */
 +    return runTclREPL(getInterp(), pzErrMsg);
 +  }
 +}
 +
 +DERIVED_METHOD(DotCmdRC, execute, MetaCommand,UnkCmd, 4,
 +             (ShellExState *psx, char **pzErrMsg, int nArgs, char *azArgs[])){
 +  Tcl_Interp *interp = getInterp();
 +  Tcl_Obj **ppo;
 +  char zName[50];
 +  int ia, rc;
 +
 +  if( interp==0 || nArgs==0 ) return DCR_Unknown;
 +
 +  sqlite3_snprintf(sizeof(zName), zName, ".%s", azArgs[0]);
 +  if( !Tcl_FindCommand(interp, zName, 0, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) ){
 +    if( !SHX_HELPER(nowInteractive)(psx) ){
 +      *pzErrMsg = sqlite3_mprintf("Command %s not found.\n", zName);
 +      return DCR_Unknown;
 +    }else{
 +      FILE *out = SHX_HELPER(currentOutputFile)(psx);
 +      fprintf(stderr, "The %s command does not yet exist.\n", zName);
 +      fprintf(out, "Run .help to see existent dot commands,"
 +              " or create %s as a TCL proc.\n", zName);
 +      return DCR_CmdErred;
 +    }
 +  }
 +  ppo = sqlite3_malloc((nArgs+1)*sizeof(Tcl_Obj*));
 +  if( ppo==0 ) return TCL_ERROR;
 +  for( ia=0; ia<nArgs; ++ia ){
 +    ppo[ia] = Tcl_NewStringObj((ia)? azArgs[ia] : zName, -1);
 +    Tcl_IncrRefCount(ppo[ia]);
 +  }
 +  rc = Tcl_EvalObjv(interp, nArgs, ppo, TCL_EVAL_GLOBAL);
 +  for( ia=0; ia<nArgs; ++ia ) Tcl_DecrRefCount(ppo[ia]);
 +  sqlite3_free(ppo);
 +  /* Translate TCL return to a dot command return. */
 +  switch( rc ){
 +  case TCL_OK:
 +    return DCR_Ok;
 +  case TCL_ERROR:
 +    *pzErrMsg = sqlite3_mprintf("%s\n", Tcl_GetStringResult(interp));
 +    return DCR_Error;
 +  case TCL_RETURN: case TCL_BREAK: case TCL_CONTINUE:
 +    return DCR_Return;
 +  default:
 +    return DCR_Exit;
 +  }
 +}
 +
 +/* Define MetaCommand v-tables initialized to reference most above methods. */
 +MetaCommand_IMPLEMENT_VTABLE(TclCmd, tclcmd_methods);
 +MetaCommand_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, MetaCommand,UnkCmd, 0, ()){
 +  (void)(pThis);
 +}
 +
 +DERIVED_METHOD(const char *, help, MetaCommand,UnkCmd, 1,(const char *zHK)){
 +  (void)(pThis);
 +  if( !zHK )
 +    return
 +  ",unknown ?ARGS?          Retry unknown dot command if it is a TCL command\n";
 +  if( !*zHK )
 +    return
 +  "   There is little use for this dot command without the TCL extension, as\n"
 +  "   the shell's version merely does some error reporting. However, with it\n"
 +  "   overridden, (as it is now), it provides a retry mechanism whereby, if\n"
 +  "   the command can be found defined in the TCL environment, that command\n"
 +  "   can be run with whatever arguments it was provided.\n"
 +  "\n"
 +  "   If the TCL command, register_adhoc_command is run, this command's help\n"
 +  "   method can be made to provide help text for the registered TCL command.\n"
 +  ;
 +  return 0;
 +}
 +
 +#if TCL_REPL==1 || TCL_REPL==2
 +#define GETLINE_MAXLEN 1000
 +
 +/* C implementation of TCL proc, get_input_line */
 +static int getInputLine(void *pvSS, Tcl_Interp *interp,
 +                        int nArgs, const char *azArgs[]){
 +  if( nArgs==1 ){
 +    char buffer[GETLINE_MAXLEN+1];
 +    ShellExState *psx = (ShellExState *)pvSS;
 +    struct InSource *pis = SHX_HELPER(currentInputSource)(psx);
 +    if( SHX_HELPER(strLineGet)(buffer, GETLINE_MAXLEN, pis) ){
 +      Tcl_SetResult(interp, buffer, TCL_VOLATILE);
 +    }else{
 +      Tcl_SetResult(interp, 0, 0);
 +    }
 +    return TCL_OK;
 +  }else{
 +    Tcl_SetResult(interp, "too many arguments", TCL_STATIC);
 +    return TCL_ERROR;
 +  }
 +}
 +#endif
 +
 +#if TCL_REPL==3
 +/* C implementation of TCL proc, get_tcl_group 
 + * This routine returns a 2 element list consisting of:
 + *   the collected input lines, joined with "\n", as a string
 + * and
 + *   the line group status, as an integer.
 + * The status is either 0, meaning input EOF was encountered,
 + * or 1, meaning the input is a complete TCL line group.
 + * There are only these return combinations:
 + *   { Empty 0 } => no input obtained and no more to be had
 + *   { Other 0 } => input collected, but is invalid TCL
 + *   { Other 1 } => input collected, may be valid TCL
 + * By design, this combination is never returned:
 + *   { Empty 1 } => no input collected but valid TCL
 + */
 +static int getTclGroup(void *pvSS, Tcl_Interp *interp,
 +                       int objc, Tcl_Obj *const objv[]){
 +  if( objc==1 ){
 +    static Prompts cueTcl = { "tcl% ", "   > " };
 +    ShellExState *psx = (ShellExState *)pvSS;
 +    struct InSource *pis = SHX_HELPER(currentInputSource)(psx);
 +    int isComplete = 0;
 +    char *zIn = 0;
 +    int isContinuation = 0;
 +    do {
 +      zIn = SHX_HELPER(oneInputLine)(pis, zIn, isContinuation, &cueTcl);
 +      if( isContinuation ){
 +        if( zIn ){
 +          Tcl_AppendResult(interp, "\n", zIn, (char*)0);
 +          isComplete = Tcl_CommandComplete(Tcl_GetStringResult(interp));
 +        }
 +      }else if( zIn ){
 +        isComplete = Tcl_CommandComplete(zIn);
 +        Tcl_SetResult(interp, zIn, TCL_VOLATILE);
 +      }
 +      isContinuation = 1;
 +    } while( zIn && !isComplete );
 +    if( zIn ) SHX_HELPER(freeInputLine)(zIn);
 +    {
 +      Tcl_Obj *const objv[] = {
 +        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, objv));
 +    }
 +    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;
 +  MetaCommand *pmc = 0;
 +  int nFound = 0;
 +  int ia, rc;
 +
 +  if( name ) pmc = SHX_HELPER(findMetaCommand)(name, psx, &nFound);
 +  if( pmc==(MetaCommand*)&tclcmd && nArgs==2 ){
 +    /* Will not do a nested REPL, just silently semi-fake it. */
 +    return TCL_OK;
 +  }
 +  if( pmc && nFound==1 ){
 +    /* Run the dot command and interpret its returns. */
 +    DotCmdRC drc = SHX_HELPER(runMetaCommand)(pmc, (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;
 +  int nshift;
 +  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);
 +  pudb->numSdb==0;
 +  if( pudb->ppSdb ) Tcl_Free((char*)pudb->ppSdb);
 +  memset(pudb, 0, sizeof(UserDb));
 +  pudb->ixuSdb = -1;
 +}
 +
 +/* Hunt for given db in UserDb's list. Return its index if found, else -1. */
 +static int udbIndexOfDb(UserDb *pudb, sqlite3 *psdb){
 +  int ix = 0;
 +  while( ix < pudb->numSdb ){
 +    if( psdb==pudb->ppSdb[ix]->db ) return ix;
 +    else ++ix;
 +  }
 +  return -1;
 +}
 +
 +/* The event handler used to keep udb command's wrapped DB in sync with
 + * changes to the ShellExState .dbUser member. This task is complicated
 + * by effects of these dot commands: .open ; .connection ; and .quit,
 + * .exit or various other shell exit causes. The intention is to always
 + * have an orderly and leak-free shutdown (excepting kill/OOM aborts.)
 + */
 +static int udbEventHandle(void *pv, NoticeKind nk, void *pvSubject,
 +                          ShellExState *psx){
 +  UserDb *pudb = (UserDb*)pv;
 +  if( nk==NK_ShutdownImminent ){
 +    udbCleanup(pudb);
 +  }else if( nk==NK_Unsubscribe ){
 +    assert(pudb==0 || pudb->numSdb==0);
 +  }else if( nk==NK_DbUserAppeared || nk==NK_DbUserVanishing
 +            || nk==NK_DbAboutToClose || nk==NK_ExtensionUnload ){
 +    sqlite3 *dbSubject = (sqlite3*)pvSubject;
 +    int ix = udbIndexOfDb(pudb, dbSubject);
 +    switch( nk ){
 +    case NK_DbUserAppeared:
 +      if( ix>=0 ) pudb->ixuSdb = ix;
 +      else pudb->ixuSdb = udbAdd(pudb, dbSubject);
 +      break;
 +    case NK_DbUserVanishing:
 +      if( ix>=0 ) pudb->ixuSdb = -1;
 +      break;
 +    case NK_ExtensionUnload:
 +      SHX_API(subscribeEvents)(psx, sqlite3_tclshext_init, 0,
 +                               NK_Unsubscribe, udbEventHandle);
 +      /* fall thru */
 +    case NK_DbAboutToClose:
 +      if( ix>=0 ) udbRemove(pudb, ix);
 +      break;
 +    }
 +  }
 +  return 0;
 +}
 +
 +/* Create the UserDb object supporting TCL "udb" command operations. 
 + * It's not wholly created because it is a singleton. Any subsequent
 + * creation is ignored; instead, the singleton is returned. This
 + * object is made to release resources only upon shutdown. If a TCL
 + * user removes the udb command, this avoids problems arising from
 + * this object holding references to databases that may still be in
 + * use, either as the active .dbUser or as a blob streaming store. */
 +static struct UserDb *udbCreate(Tcl_Interp *interp, ShellExState *psx){
 +  static UserDb *rv = 0;
 +  static UserDb udb = { 0 };
 +  if( interp==0 || psx==0 ) return &udb;
 +  if( rv==0 ){
 +    sqlite3 *sdbS = psx->dbShell;
 +    sqlite3 *sdbU = psx->dbUser;
 +    rv = &udb;
 +    rv->interp = interp;
 +    rv->psx = psx;
 +    rv->ppSdb = (SqliteDb**)Tcl_Alloc(6*sizeof(SqliteDb*));
 +    memset(rv->ppSdb, 0, 6*sizeof(SqliteDb*));
 +    assert(sdbS!=0);
 +    udbAdd(rv, sdbS);
 +    if( sdbU!=0 ){
 +      rv->ixuSdb = udbAdd(rv, sdbU);
 +    } else rv->ixuSdb = -1;
 +    rv->nRef = 1;
 +    /* Arrange that this object tracks lifetimes and visibility of the
 +     * ShellExState .dbUser member values which udb purports to wrap,
 +     * and that shdb ceases wrapping the .dbShell member at shutdown.
 +     * This subscription eventually leads to a udbCleanup() call. */
 +    SHX_API(subscribeEvents)(psx, sqlite3_tclshext_init,
 +                             rv, NK_CountOf, udbEventHandle);
 +  }
 +  return rv;
 +}
 +
 +static const char *azDbNames[] = { "shdb", "udb", 0 };
 +static const int numDbNames = 2;
 +
 +/* C implementation behind added TCL udb command */
 +static int UserDbObjCmd(void *cd, Tcl_Interp *interp,
 +                        int objc, Tcl_Obj * const * objv){
 +  UserDb *pudb = (UserDb*)cd;
 +  static const char *azDoHere[] = { "close", 0 };
 +  enum DbDoWhat { DDW_Close };
 +  int doWhat;
 +  int whichDb = -1;
 +  const char *zMoan;
 +
 +  if( Tcl_GetIndexFromObj(interp, objv[0], azDbNames,
 +                          "shell DB command", 0, &whichDb)){
 +    zMoan = " is not a wrapped DB.\n";
 +    goto complain_fail;
 +  }
 +  if( whichDb>0 ) whichDb = pudb->ixuSdb;
 +  /* Delegate all subcommands except above to the now-visible SqliteDb. */
 +  if( objc>=2 && !Tcl_GetIndexFromObj(interp, objv[1], azDoHere,
 +                                      "subcommand", 0, &doWhat)){
 +    switch( doWhat ){
 +    case DDW_Close:
 +      zMoan = " close is disallowd. It is a wrapped DB.\n";
 +      goto complain_fail;
 +    }
 +  }
 +  if( pudb->numSdb==0 || whichDb<0 ){
 +    zMoan = " references no DB yet.\n";
 +    goto complain_fail;
 +  }
 +  return DbObjCmd(pudb->ppSdb[whichDb], interp, objc, objv);
 +
 + complain_fail:
 +  Tcl_AppendResult(interp,
 +                   Tcl_GetStringFromObj(objv[0], (int*)0), zMoan, (char*)0);
 +  return TCL_ERROR;
 +}
 +
 +/* Get the udb command subsystem initialized and create "udb" TCL command. */
 +static int userDbInit(Tcl_Interp *interp, ShellExState *psx){
 +  UserDb *pudb = udbCreate(interp, psx);
 +  int nCreate = 0;
 +  int ic;
 +  for( ic=0; ic<numDbNames; ++ic ){
 +    nCreate +=
 +      0 != Tcl_CreateObjCommand(interp, azDbNames[ic],
 +                                (Tcl_ObjCmdProc*)UserDbObjCmd,
 +                                (char *)pudb, 0);
 +  }
 +  if( nCreate==ic ){
 +    ++pudb->nRef;
 +    return TCL_OK;
 +  }
 +  return TCL_ERROR;
 +}
 +
 +/*
 +** Extension load function.
 +*/
 +#ifdef _WIN32
 +__declspec(dllexport)
 +#endif
 +int sqlite3_tclshext_init(
 +  sqlite3 *db,
 +  char **pzErrMsg,
 +  const sqlite3_api_routines *pApi
 +){
 +  static const char * const azLoadFailures[] = {
 +    "Extension load failed unexpectedly.",
 +    "No ShellExtensionLink.\n Use '.load tclshext -shext' to load.",
 +    "Outdated shell host extension API.\n Update the shell.",
 +    "Outdated shell host helper API.\n Use a newer shell.",
 +  };
 +  int iLoadStatus;
 +  SQLITE_EXTENSION_INIT2(pApi);
 +  SHELL_EXTENSION_INIT2(pShExtLink, shextLinkFetcher, db);
 +
 +  SHELL_EXTENSION_INIT3(pShExtApi, pExtHelpers, pShExtLink);
 +  iLoadStatus = SHELL_EXTENSION_LOADFAIL_WHY(pShExtLink, 6, 10);
 +  if( iLoadStatus!=EXLD_Ok ){
 +    if( iLoadStatus>=sizeof(azLoadFailures)/sizeof(azLoadFailures[0]) ){
 +      iLoadStatus = 0;
 +    }
 +    *pzErrMsg = sqlite3_mprintf("%s\n", azLoadFailures[iLoadStatus]);
 +    return SQLITE_ERROR;
 +  }else{
 +    ShellExState *psx = pShExtLink->pSXS;
 +    Tcl_Obj *targv = Tcl_NewListObj(0, NULL);
 +    const char *zAppName = "tclshext";
 +    int tnarg = 0;
 +#ifdef SHELL_ENABLE_TK
 +    int ldTk = 0;
 +#endif
 +    int rc = 0;
 +
 +    if( pShExtLink->nLoadArgs>0 ){
 +      int ila;
 +      for( ila=0; ila<pShExtLink->nLoadArgs; ++ila ){
 +        const char *zA = pShExtLink->azLoadArgs[ila];
 +        if( strcmp(zA,"-tk")==0 ){
 +#ifdef SHELL_ENABLE_TK
 +          ldTk = 1;
 +#else
 +          *pzErrMsg = sqlite3_mprintf("Option -tk not supported by this "
 +                                      "tclshext extension as built.\n");
 +          return SQLITE_ERROR;
 +#endif
 +        }else{
 +          /* Collect args not affecting init into the argv list. */
 +          Tcl_ListObjAppendElement(NULL, targv, Tcl_NewStringObj(zA, -1));
 +          ++tnarg;
 +        }
 +      }
 +    }
 +    rc = SHX_API(registerMetaCommand)(psx, sqlite3_tclshext_init,
 +                                      (MetaCommand *)&unkcmd);
 +    rc = SHX_API(registerMetaCommand)(psx, sqlite3_tclshext_init,
 +                                      (MetaCommand *)&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 84a243c4b5cd74f908cc26d24f8cdff9fd5c532a,e767080f57e57f3e6b00193082ffe05b4995bb66..50183c67da386083037fcdfeab823c5b9ab5b77e
+++ b/manifest
@@@ -1,5 -1,5 +1,5 @@@
- C Get\stclshext\smade\swith\sTk,\soptionally,\sand\smake\sunknown\swork\sas\sin\stclsh.
- D 2022-04-09T14:57:17.905
 -C Fix\s.import\sbug\sreported\sat\shttps://sqlite.org/forum/forumpost/14db09d7e765b819\s.\szAutoColumn\smade\sto\sdeliver\scharacters,\snot\sbytes.
 -D 2022-04-09T18:51:49.784
++C Sync\sw/trunk,\sfor\s.import\sfix.
++D 2022-04-09T19:39:58.635
  F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
  F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
  F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@@ -329,7 -328,6 +329,7 @@@ F ext/misc/showauth.c 732578f0fe4ce42d5
  F ext/misc/spellfix.c 94df9bbfa514a563c1484f684a2df3d128a2f7209a84ca3ca100c68a0163e29f
  F ext/misc/sqlar.c 0ace5d3c10fe736dc584bf1159a36b8e2e60fab309d310cd8a0eecd9036621b6
  F ext/misc/stmt.c 35063044a388ead95557e4b84b89c1b93accc2f1c6ddea3f9710e8486a7af94a
- F ext/misc/tclshext.c.in 026416fd5c1e84eb8c8e98edc9dcd826e3c0d32fd0883ab306829642867128e8
++F ext/misc/tclshext.c.in c29c3aa45cc477106aa6995308c8e1f8fe68e6ff4b10b2d906acefbbd62c4196
  F ext/misc/templatevtab.c 8a16a91a5ceaccfcbd6aaaa56d46828806e460dd194965b3f77bf38f14b942c4
  F ext/misc/totype.c fa4aedeb07f66169005dffa8de3b0a2b621779fd44f85c103228a42afa71853b
  F ext/misc/uint.c 053fed3bce2e89583afcd4bf804d75d659879bbcedac74d0fa9ed548839a030b
@@@ -555,9 -552,8 +555,9 @@@ F src/printf.c 05d8dfd2018bc4fc3ddb8b37
  F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
  F src/resolve.c 18d99e7146852d6064559561769fcca0743eb32b14a97da6dbed373a30ee0e76
  F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
- F src/select.c d6c04eb93395024af80f61a8c278a33c2a0333aeb7d57bb6aa737a6f1c4af4b8
- F src/shell.c.in 8e2ad2912ae381786653ce9db04522f8058873d690f434016ecda5a419c0cdc5
+ F src/select.c 7c106b3f36d483242b0a9c696614cd53d6f29e1ac81da6a3f0e9ea92f4211cc3
 -F src/shell.c.in eb7f10d5e2c47bd014d92ec5db1def21fcc1ed56ffaaa4ee715b6c37c370b47f
++F src/shell.c.in 3984e1de9e70357ea0a017409f4421eacf94175a30969d68f6cc135a4c1fb9f9
 +F src/shext_linkage.h 88a3f215fdb090fcc3b3577a05bafd234f1a556bad3f2f4ac990a177aebf4b2c
  F src/sqlite.h.in 2a35f62185eb5e7ecc64a2f68442b538ce9be74f80f28a00abc24837edcf1c17
  F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
  F src/sqlite3ext.h f49e28c25bd941e79794db5415fdf7b202deb3bc072ed6f1ed273d578703684e
@@@ -1394,11 -1389,11 +1394,11 @@@ F test/sharedA.test 49d87ec54ab640fbbc3
  F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e
  F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939
  F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
 -F test/shell1.test b224e0793c5f48aa3749e65d8c64b93a30731bd206f2e41e6c5f1bee1bdb16c6
 -F test/shell2.test 7a3a23a9f57b99453f1679b1fe8072cb30e382a622874c0c4d97695fadb0a787
 -F test/shell3.test a50628ab1d78d90889d9d3f32fb2c084ee15674771e96afe954aaa0accd1de3c
 -F test/shell4.test 8f6c0fce4abed19a8a7f7262517149812a04caa905d01bdc8f5e92573504b759
 -F test/shell5.test 78a7a8516b1e7de560748881424f621321549023d3e5f7ed2e1c56497f64c06c
 +F test/shell1.test 9401659c4f7319586ddd36aac15aaadff0092caa786589fc20ad069fc2cb1c74
 +F test/shell2.test fc6bb55f5ceaaffa284cb994aa00fd56f7ead09949c9db01c3846d65a76a7748
 +F test/shell3.test 4ddea2bd182e7e03249911b23ae249e7cb8a91cdc86e695198725affabe8ecd3
 +F test/shell4.test b232688061cce531f42ec067f3b5760e31d12409e566e2ae230951036dd156f1
- F test/shell5.test 04b46462b3297de7195aaed99a0cd99fdb9f8de211a96abea46a81db55c5ef77
++F test/shell5.test 39d2cffb3c1c67456b2c42eb5e46bec1e3780c68a6d71bb156e012d3f53733c5
  F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3
  F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f
  F test/shell8.test 388471d16e4de767333107e30653983f186232c0e863f4490bb230419e830aae
@@@ -1951,8 -1945,8 +1951,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 0b3bb0e793f8c7fd79042322b7748a75d17a3c3d0752cfb0f2c544af49b1d6d6
- R fd77dc9b9d6010cf74b598e30b2c6a7a
 -P f237e1d8cc41b937f34288daebfacf5f7b0990a807a805e0cb6b45bc730192d6
 -R 32d747db1a8abc3115c9e0c3c720312e
++P 43eb311e517b79cde9e17c1a80baed8d13d9d943dd9ee44b31831159df8715fc 21e96600d90c1cda84777abe22a11058eba46c9faefeb05f8c31bc0e7fa84b19
++R 67ea5330fa92b3979fbd7e883fdffd1f
  U larrybr
- Z 41d0aa7cc99a4d24d4e075af36bc0e99
 -Z 31d16ad9966991b7487e6e03ff8edde4
++Z b47b9cb62392429660769c050bed79ba
  # Remove this line to create a well-formed Fossil manifest.
diff --cc manifest.uuid
index 3448532c48603b87b410b02235a788c8474c6915,b96920f440b6f85d94a161240d7d80776dca81eb..6826a57cdb69bf5acee453d6de5354e99c35d5ef
@@@ -1,1 -1,1 +1,1 @@@
- 43eb311e517b79cde9e17c1a80baed8d13d9d943dd9ee44b31831159df8715fc
 -21e96600d90c1cda84777abe22a11058eba46c9faefeb05f8c31bc0e7fa84b19
++861ab023be283782131df5ccd9eff50cf2f06f48f1ea7defd3f08177a3df5889
diff --cc src/shell.c.in
index e8a3046395dfb188f365166987145c73296ce4a7,996a7f942bbbf944502a1949c7a92ff802d244c8..74c6ed74e0e959807e6c0ea98b6248688e0c8d4e
@@@ -7404,715 -6820,355 +7404,715 @@@ static void recoverFreeTable(RecoverTab
  }
  
  /*
 -** Implementation of .ar "eXtract" command. 
 -*/
 -static int arExtractCommand(ArCommand *pAr){
 -  const char *zSql1 = 
 -    "SELECT "
 -    " ($dir || name),"
 -    " writefile(($dir || name), %s, mode, mtime) "
 -    "FROM %s WHERE (%s) AND (data IS NULL OR $dirOnly = 0)"
 -    " AND name NOT GLOB '*..[/\\]*'";
 +** This function is a no-op if (*pRc) is not SQLITE_OK when it is called.
 +** Otherwise, it allocates and returns a RecoverTable object based on the
 +** final four arguments passed to this function. It is the responsibility
 +** of the caller to eventually free the returned object using
 +** recoverFreeTable().
 +*/
 +static RecoverTable *recoverNewTable(
 +  int *pRc,                       /* IN/OUT: Error code */
 +  const char *zName,              /* Name of table */
 +  const char *zSql,               /* CREATE TABLE statement */
 +  int bIntkey,
 +  int nCol
 +){
 +  sqlite3 *dbtmp = 0;             /* sqlite3 handle for testing CREATE TABLE */
 +  int rc = *pRc;
 +  RecoverTable *pTab = 0;
  
 -  const char *azExtraArg[] = { 
 -    "sqlar_uncompress(data, sz)",
 -    "data"
 -  };
 +  pTab = (RecoverTable*)shellMalloc(&rc, sizeof(RecoverTable));
 +  if( rc==SQLITE_OK ){
 +    int nSqlCol = 0;
 +    int bSqlIntkey = 0;
 +    sqlite3_stmt *pStmt = 0;
  
 -  sqlite3_stmt *pSql = 0;
 -  int rc = SQLITE_OK;
 -  char *zDir = 0;
 -  char *zWhere = 0;
 -  int i, j;
 +    rc = sqlite3_open("", &dbtmp);
 +    if( rc==SQLITE_OK ){
 +      sqlite3_create_function(dbtmp, "shell_idquote", 1, SQLITE_UTF8, 0,
 +                              shellIdQuote, 0, 0);
 +    }
 +    if( rc==SQLITE_OK ){
 +      rc = sqlite3_exec(dbtmp, "PRAGMA writable_schema = on", 0, 0, 0);
 +    }
 +    if( rc==SQLITE_OK ){
 +      rc = sqlite3_exec(dbtmp, zSql, 0, 0, 0);
 +      if( rc==SQLITE_ERROR ){
 +        rc = SQLITE_OK;
 +        goto finished;
 +      }
 +    }
 +    shellPreparePrintf(dbtmp, &rc, &pStmt,
 +        "SELECT count(*) FROM pragma_table_info(%Q)", zName
 +    );
 +    if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
 +      nSqlCol = sqlite3_column_int(pStmt, 0);
 +    }
 +    shellFinalize(&rc, pStmt);
  
 -  /* If arguments are specified, check that they actually exist within
 -  ** the archive before proceeding. And formulate a WHERE clause to
 -  ** match them.  */
 -  rc = arCheckEntries(pAr);
 -  arWhereClause(&rc, pAr, &zWhere);
 +    if( rc!=SQLITE_OK || nSqlCol<nCol ){
 +      goto finished;
 +    }
  
 -  if( rc==SQLITE_OK ){
 -    if( pAr->zDir ){
 -      zDir = sqlite3_mprintf("%s/", pAr->zDir);
 -    }else{
 -      zDir = sqlite3_mprintf("");
 +    shellPreparePrintf(dbtmp, &rc, &pStmt,
 +      "SELECT ("
 +      "  SELECT substr(data,1,1)==X'0D' FROM sqlite_dbpage WHERE pgno=rootpage"
 +      ") FROM sqlite_schema WHERE name = %Q", zName
 +    );
 +    if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
 +      bSqlIntkey = sqlite3_column_int(pStmt, 0);
      }
 -    if( zDir==0 ) rc = SQLITE_NOMEM;
 -  }
 +    shellFinalize(&rc, pStmt);
  
 -  shellPreparePrintf(pAr->db, &rc, &pSql, zSql1, 
 -      azExtraArg[pAr->bZip], pAr->zSrcTable, zWhere
 -  );
 +    if( bIntkey==bSqlIntkey ){
 +      int i;
 +      const char *zPk = "_rowid_";
 +      sqlite3_stmt *pPkFinder = 0;
  
 -  if( rc==SQLITE_OK ){
 -    j = sqlite3_bind_parameter_index(pSql, "$dir");
 -    sqlite3_bind_text(pSql, j, zDir, -1, SQLITE_STATIC);
 +      /* If this is an intkey table and there is an INTEGER PRIMARY KEY,
 +      ** set zPk to the name of the PK column, and pTab->iPk to the index
 +      ** of the column, where columns are 0-numbered from left to right.
 +      ** Or, if this is a WITHOUT ROWID table or if there is no IPK column,
 +      ** leave zPk as "_rowid_" and pTab->iPk at -2.  */
 +      pTab->iPk = -2;
 +      if( bIntkey ){
 +        shellPreparePrintf(dbtmp, &rc, &pPkFinder,
 +          "SELECT cid, name FROM pragma_table_info(%Q) "
 +          "  WHERE pk=1 AND type='integer' COLLATE nocase"
 +          "  AND NOT EXISTS (SELECT cid FROM pragma_table_info(%Q) WHERE pk=2)"
 +          , zName, zName
 +        );
 +        if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPkFinder) ){
 +          pTab->iPk = sqlite3_column_int(pPkFinder, 0);
 +          zPk = (const char*)sqlite3_column_text(pPkFinder, 1);
 +          if( zPk==0 ){ zPk = "_";  /* Defensive.  Should never happen */ }
 +        }
 +      }
  
 -    /* Run the SELECT statement twice. The first time, writefile() is called
 -    ** for all archive members that should be extracted. The second time,
 -    ** only for the directories. This is because the timestamps for
 -    ** extracted directories must be reset after they are populated (as
 -    ** populating them changes the timestamp).  */
 -    for(i=0; i<2; i++){
 -      j = sqlite3_bind_parameter_index(pSql, "$dirOnly");
 -      sqlite3_bind_int(pSql, j, i);
 -      if( pAr->bDryRun ){
 -        utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql));
 +      pTab->zQuoted = shellMPrintf(&rc, "\"%w\"", zName);
 +      pTab->azlCol = (char**)shellMalloc(&rc, sizeof(char*) * (nSqlCol+1));
 +      pTab->nCol = nSqlCol;
 +
 +      if( bIntkey ){
 +        pTab->azlCol[0] = shellMPrintf(&rc, "\"%w\"", zPk);
        }else{
 -        while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
 -          if( i==0 && pAr->bVerbose ){
 -            utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0));
 -          }
 -        }
 +        pTab->azlCol[0] = shellMPrintf(&rc, "");
        }
 -      shellReset(&rc, pSql);
 +      i = 1;
 +      shellPreparePrintf(dbtmp, &rc, &pStmt,
 +          "SELECT %Q || group_concat(shell_idquote(name), ', ') "
 +          "  FILTER (WHERE cid!=%d) OVER (ORDER BY %s cid) "
 +          "FROM pragma_table_info(%Q)",
 +          bIntkey ? ", " : "", pTab->iPk,
 +          bIntkey ? "" : "(CASE WHEN pk=0 THEN 1000000 ELSE pk END), ",
 +          zName
 +      );
 +      while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
 +        const char *zText = (const char*)sqlite3_column_text(pStmt, 0);
 +        pTab->azlCol[i] = shellMPrintf(&rc, "%s%s", pTab->azlCol[0], zText);
 +        i++;
 +      }
 +      shellFinalize(&rc, pStmt);
 +
 +      shellFinalize(&rc, pPkFinder);
      }
 -    shellFinalize(&rc, pSql);
    }
  
 -  sqlite3_free(zDir);
 -  sqlite3_free(zWhere);
 -  return rc;
 -}
 -
 -/*
 -** Run the SQL statement in zSql.  Or if doing a --dryrun, merely print it out.
 -*/
 -static int arExecSql(ArCommand *pAr, const char *zSql){
 -  int rc;
 -  if( pAr->bDryRun ){
 -    utf8_printf(pAr->p->out, "%s\n", zSql);
 -    rc = SQLITE_OK;
 -  }else{
 -    char *zErr = 0;
 -    rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr);
 -    if( zErr ){
 -      utf8_printf(stdout, "ERROR: %s\n", zErr);
 -      sqlite3_free(zErr);
 -    }
 + finished:
 +  sqlite3_close(dbtmp);
 +  *pRc = rc;
 +  if( rc!=SQLITE_OK || (pTab && pTab->zQuoted==0) ){
 +    recoverFreeTable(pTab);
 +    pTab = 0;
    }
 -  return rc;
 +  return pTab;
  }
  
 -
  /*
 -** Implementation of .ar "create", "insert", and "update" commands.
 -**
 -**     create    ->     Create a new SQL archive
 -**     insert    ->     Insert or reinsert all files listed
 -**     update    ->     Insert files that have changed or that were not
 -**                      previously in the archive
 -**
 -** Create the "sqlar" table in the database if it does not already exist.
 -** Then add each file in the azFile[] array to the archive. Directories
 -** are added recursively. If argument bVerbose is non-zero, a message is
 -** printed on stdout for each file archived.
 +** This function is called to search the schema recovered from the
 +** sqlite_schema table of the (possibly) corrupt database as part
 +** of a ".recover" command. Specifically, for a table with root page
 +** iRoot and at least nCol columns. Additionally, if bIntkey is 0, the
 +** table must be a WITHOUT ROWID table, or if non-zero, not one of
 +** those.
  **
 -** The create command is the same as update, except that it drops
 -** any existing "sqlar" table before beginning.  The "insert" command
 -** always overwrites every file named on the command-line, where as
 -** "update" only overwrites if the size or mtime or mode has changed.
 +** If a table is found, a (RecoverTable*) object is returned. Or, if
 +** no such table is found, but bIntkey is false and iRoot is the
 +** root page of an index in the recovered schema, then (*pbNoop) is
 +** set to true and NULL returned. Or, if there is no such table or
 +** index, NULL is returned and (*pbNoop) set to 0, indicating that
 +** the caller should write data to the orphans table.
  */
 -static int arCreateOrUpdateCommand(
 -  ArCommand *pAr,                 /* Command arguments and options */
 -  int bUpdate,                    /* true for a --create. */
 -  int bOnlyIfChanged              /* Only update if file has changed */
 +static RecoverTable *recoverFindTable(
 +  sqlite3 *db,                    /* DB from which to recover */
 +  int *pRc,                       /* IN/OUT: Error code */
 +  int iRoot,                      /* Root page of table */
 +  int bIntkey,                    /* True for an intkey table */
 +  int nCol,                       /* Number of columns in table */
 +  int *pbNoop                     /* OUT: True if iRoot is root of index */
  ){
 -  const char *zCreate = 
 -      "CREATE TABLE IF NOT EXISTS sqlar(\n"
 -      "  name TEXT PRIMARY KEY,  -- name of the file\n"
 -      "  mode INT,               -- access permissions\n"
 -      "  mtime INT,              -- last modification time\n"
 -      "  sz INT,                 -- original file size\n"
 -      "  data BLOB               -- compressed content\n"
 -      ")";
 -  const char *zDrop = "DROP TABLE IF EXISTS sqlar";
 -  const char *zInsertFmt[2] = {
 -     "REPLACE INTO %s(name,mode,mtime,sz,data)\n"
 -     "  SELECT\n"
 -     "    %s,\n"
 -     "    mode,\n"
 -     "    mtime,\n"
 -     "    CASE substr(lsmode(mode),1,1)\n"
 -     "      WHEN '-' THEN length(data)\n"
 -     "      WHEN 'd' THEN 0\n"
 -     "      ELSE -1 END,\n"
 -     "    sqlar_compress(data)\n"
 -     "  FROM fsdir(%Q,%Q) AS disk\n"
 -     "  WHERE lsmode(mode) NOT LIKE '?%%'%s;"
 -     ,
 -     "REPLACE INTO %s(name,mode,mtime,data)\n"
 -     "  SELECT\n"
 -     "    %s,\n"
 -     "    mode,\n"
 -     "    mtime,\n"
 -     "    data\n"
 -     "  FROM fsdir(%Q,%Q) AS disk\n"
 -     "  WHERE lsmode(mode) NOT LIKE '?%%'%s;"
 -  };
 -  int i;                          /* For iterating through azFile[] */
 -  int rc;                         /* Return code */
 -  const char *zTab = 0;           /* SQL table into which to insert */
 -  char *zSql;
 -  char zTemp[50];
 -  char *zExists = 0;
 +  sqlite3_stmt *pStmt = 0;
 +  RecoverTable *pRet = 0;
 +  int bNoop = 0;
 +  const char *zSql = 0;
 +  const char *zName = 0;
 +
 +  /* Search the recovered schema for an object with root page iRoot. */
 +  shellPreparePrintf(db, pRc, &pStmt,
 +      "SELECT type, name, sql FROM recovery.schema WHERE rootpage=%d", iRoot
 +  );
 +  while( *pRc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
 +    const char *zType = (const char*)sqlite3_column_text(pStmt, 0);
 +    if( bIntkey==0 && sqlite3_stricmp(zType, "index")==0 ){
 +      bNoop = 1;
 +      break;
 +    }
 +    if( sqlite3_stricmp(zType, "table")==0 ){
 +      zName = (const char*)sqlite3_column_text(pStmt, 1);
 +      zSql = (const char*)sqlite3_column_text(pStmt, 2);
 +      if( zName!=0 && zSql!=0 ){
 +        pRet = recoverNewTable(pRc, zName, zSql, bIntkey, nCol);
 +        break;
 +      }
 +    }
 +  }
 +
 +  shellFinalize(pRc, pStmt);
 +  *pbNoop = bNoop;
 +  return pRet;
 +}
  
 -  arExecSql(pAr, "PRAGMA page_size=512");
 -  rc = arExecSql(pAr, "SAVEPOINT ar;");
 -  if( rc!=SQLITE_OK ) return rc;
 -  zTemp[0] = 0; 
 -  if( pAr->bZip ){
 -    /* Initialize the zipfile virtual table, if necessary */
 -    if( pAr->zFile ){
 -      sqlite3_uint64 r;
 -      sqlite3_randomness(sizeof(r),&r);
 -      sqlite3_snprintf(sizeof(zTemp),zTemp,"zip%016llx",r);
 -      zTab = zTemp;
 -      zSql = sqlite3_mprintf(
 -         "CREATE VIRTUAL TABLE temp.%s USING zipfile(%Q)",
 -         zTab, pAr->zFile
 -      );
 -      rc = arExecSql(pAr, zSql);
 -      sqlite3_free(zSql);
 -    }else{
 -      zTab = "zip";
 +/*
 +** Return a RecoverTable object representing the orphans table.
 +*/
 +static RecoverTable *recoverOrphanTable(
 +  sqlite3 *db,                    /* DB from which to recover */
 +  FILE *out,                      /* Where to put recovery DDL */
 +  int *pRc,                       /* IN/OUT: Error code */
 +  const char *zLostAndFound,      /* Base name for orphans table */
 +  int nCol                        /* Number of user data columns */
 +){
 +  RecoverTable *pTab = 0;
 +  if( nCol>=0 && *pRc==SQLITE_OK ){
 +    int i;
 +
 +    /* This block determines the name of the orphan table. The prefered
 +    ** name is zLostAndFound. But if that clashes with another name
 +    ** in the recovered schema, try zLostAndFound_0, zLostAndFound_1
 +    ** and so on until a non-clashing name is found.  */
 +    int iTab = 0;
 +    char *zTab = shellMPrintf(pRc, "%s", zLostAndFound);
 +    sqlite3_stmt *pTest = 0;
 +    shellPrepare(db, pRc,
 +        "SELECT 1 FROM recovery.schema WHERE name=?", &pTest
 +    );
 +    if( pTest ) sqlite3_bind_text(pTest, 1, zTab, -1, SQLITE_TRANSIENT);
 +    while( *pRc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pTest) ){
 +      shellReset(pRc, pTest);
 +      sqlite3_free(zTab);
 +      zTab = shellMPrintf(pRc, "%s_%d", zLostAndFound, iTab++);
 +      sqlite3_bind_text(pTest, 1, zTab, -1, SQLITE_TRANSIENT);
      }
 -  }else{
 -    /* Initialize the table for an SQLAR */
 -    zTab = "sqlar";
 -    if( bUpdate==0 ){
 -      rc = arExecSql(pAr, zDrop);
 -      if( rc!=SQLITE_OK ) goto end_ar_transaction;
 +    shellFinalize(pRc, pTest);
 +
 +    pTab = (RecoverTable*)shellMalloc(pRc, sizeof(RecoverTable));
 +    if( pTab ){
 +      pTab->zQuoted = shellMPrintf(pRc, "\"%w\"", zTab);
 +      pTab->nCol = nCol;
 +      pTab->iPk = -2;
 +      if( nCol>0 ){
 +        pTab->azlCol = (char**)shellMalloc(pRc, sizeof(char*) * (nCol+1));
 +        if( pTab->azlCol ){
 +          pTab->azlCol[nCol] = shellMPrintf(pRc, "");
 +          for(i=nCol-1; i>=0; i--){
 +            pTab->azlCol[i] = shellMPrintf(pRc, "%s, NULL", pTab->azlCol[i+1]);
 +          }
 +        }
 +      }
 +
 +      if( *pRc!=SQLITE_OK ){
 +        recoverFreeTable(pTab);
 +        pTab = 0;
 +      }else{
 +        raw_printf(out,
 +            "CREATE TABLE %s(rootpgno INTEGER, "
 +            "pgno INTEGER, nfield INTEGER, id INTEGER", pTab->zQuoted
 +        );
 +        for(i=0; i<nCol; i++){
 +          raw_printf(out, ", c%d", i);
 +        }
 +        raw_printf(out, ");\n");
 +      }
      }
 -    rc = arExecSql(pAr, zCreate);
 +    sqlite3_free(zTab);
    }
 -  if( bOnlyIfChanged ){
 -    zExists = sqlite3_mprintf(
 -      " AND NOT EXISTS("
 -          "SELECT 1 FROM %s AS mem"
 -          " WHERE mem.name=disk.name"
 -          " AND mem.mtime=disk.mtime"
 -          " AND mem.mode=disk.mode)", zTab);
 -  }else{
 -    zExists = sqlite3_mprintf("");
 +  return pTab;
 +}
 +#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
 +
 +static DotCmdRC
 +writeDb( char *azArg[], int nArg, ShellExState *psx, char **pzErr ){
 +  int rc = 0;
 +  const char *zDestFile = 0;
 +  const char *zDb = 0;
 +  sqlite3 *pDest;
 +  sqlite3_backup *pBackup;
 +  int j;
 +  int bAsync = 0;
 +  const char *zVfs = 0;
 +  if( ISS(psx)->bSafeMode ) return DCR_AbortError;
 +  for(j=1; j<nArg; j++){
 +    const char *z = azArg[j];
 +    if( z[0]=='-' ){
 +      if( z[1]=='-' ) z++;
 +      if( strcmp(z, "-append")==0 ){
 +        zVfs = "apndvfs";
 +      }else if( strcmp(z, "-async")==0 ){
 +        bAsync = 1;
 +      }else{
 +        return DCR_Unknown|j;
 +      }
 +    }else if( zDestFile==0 ){
 +      zDestFile = azArg[j];
 +    }else if( zDb==0 ){
 +      zDb = zDestFile;
 +      zDestFile = azArg[j];
 +    }else{
 +      return DCR_TooMany|j;
 +    }
    }
 -  if( zExists==0 ) rc = SQLITE_NOMEM;
 -  for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){
 -    char *zSql2 = sqlite3_mprintf(zInsertFmt[pAr->bZip], zTab,
 -        pAr->bVerbose ? "shell_putsnl(name)" : "name",
 -        pAr->azArg[i], pAr->zDir, zExists);
 -    rc = arExecSql(pAr, zSql2);
 -    sqlite3_free(zSql2);
 +  if( zDestFile==0 ){
 +    return DCR_Missing;
    }
 -end_ar_transaction:
 +  if( zDb==0 ) zDb = "main";
 +  rc = sqlite3_open_v2(zDestFile, &pDest,
 +                       SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs);
    if( rc!=SQLITE_OK ){
 -    sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0);
 +    *pzErr = smprintf("cannot open \"%s\"\n", zDestFile);
 +    close_db(pDest);
 +    return DCR_Error;
 +  }
 +  if( bAsync ){
 +    sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
 +                 0, 0, 0);
 +  }
 +  open_db(psx, 0);
 +  pBackup = sqlite3_backup_init(pDest, "main", DBX(psx), zDb);
 +  if( pBackup==0 ){
 +    *pzErr = smprintf("%s failed, %s\n", azArg[0], sqlite3_errmsg(pDest));
 +    close_db(pDest);
 +    return DCR_Error;
 +  }
 +  while(  (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
 +  sqlite3_backup_finish(pBackup);
 +  if( rc==SQLITE_DONE ){
 +    rc = 0;
 +  }else{
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(pDest));
 +    rc = 1;
 +  }
 +  close_db(pDest);
 +  return DCR_Ok|rc;
 +}
 +
 +/*
 + * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it.
 + * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE,
 + *   close db and set it to 0, and return the columns spec, to later
 + *   be sqlite3_free()'ed by the caller.
 + * The return is 0 when either:
 + *   (a) The db was not initialized and zCol==0 (There are no columns.)
 + *   (b) zCol!=0  (Column was added, db initialized as needed.)
 + * The 3rd argument, pRenamed, references an out parameter. If the
 + * pointer is non-zero, its referent will be set to a summary of renames
 + * done if renaming was necessary, or set to 0 if none was done. The out
 + * string (if any) must be sqlite3_free()'ed by the caller.
 + */
 +#ifdef SHELL_DEBUG
 +#define rc_err_oom_die(rc) \
 +  if( rc==SQLITE_NOMEM ) shell_check_oom(0); \
 +  else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \
 +    fprintf(STD_ERR,"E:%d\n",rc), assert(0)
 +#else
 +static void rc_err_oom_die(int rc){
 +  if( rc==SQLITE_NOMEM ) shell_check_oom(0);
 +  assert(rc==SQLITE_OK||rc==SQLITE_DONE);
 +}
 +#endif
 +
 +#ifdef SHELL_COLFIX_DB /* If this is set, the DB can be in a file. */
 +static char zCOL_DB[] = SHELL_STRINGIFY(SHELL_COLFIX_DB);
 +#else  /* Otherwise, memory is faster/better for the transient DB. */
 +static const char *zCOL_DB = ":memory:";
 +#endif
 +
 +/* Define character (as C string) to separate generated column ordinal
 + * from protected part of incoming column names. This defaults to "_"
 + * so that incoming column identifiers that did not need not be quoted
 + * remain usable without being quoted. It must be one character.
 + */
 +#ifndef SHELL_AUTOCOLUMN_SEP
 +# define AUTOCOLUMN_SEP "_"
 +#else
 +# define AUTOCOLUMN_SEP SHELL_STRINGIFY(SHELL_AUTOCOLUMN_SEP)
 +#endif
 +
 +static char *zAutoColumn(const char *zColNew, sqlite3 **pDb, char **pzRenamed){
 +  /* Queries and D{D,M}L used here */
 +  static const char * const zTabMake = "\
 +CREATE TABLE ColNames(\
 + cpos INTEGER PRIMARY KEY,\
 + name TEXT, nlen INT, chop INT, reps INT, suff TEXT);\
 +CREATE VIEW RepeatedNames AS \
 +SELECT DISTINCT t.name FROM ColNames t \
 +WHERE t.name COLLATE NOCASE IN (\
 + SELECT o.name FROM ColNames o WHERE o.cpos<>t.cpos\
 +);\
 +";
 +  static const char * const zTabFill = "\
 +INSERT INTO ColNames(name,nlen,chop,reps,suff)\
 + VALUES(iif(length(?1)>0,?1,'?'),max(length(?1),1),0,0,'')\
 +";
 +  static const char * const zHasDupes = "\
 +SELECT count(DISTINCT (substring(name,1,nlen-chop)||suff) COLLATE NOCASE)\
 + <count(name) FROM ColNames\
 +";
 +#ifdef SHELL_COLUMN_RENAME_CLEAN
 +  static const char * const zDedoctor = "\
 +UPDATE ColNames SET chop=iif(\
 +  (substring(name,nlen,1) BETWEEN '0' AND '9')\
 +  AND (rtrim(name,'0123456790') glob '*"AUTOCOLUMN_SEP"'),\
 + nlen-length(rtrim(name, '"AUTOCOLUMN_SEP"0123456789')),\
 + 0\
 +)\
 +";
 +#endif
 +  static const char * const zSetReps = "\
 +UPDATE ColNames AS t SET reps=\
 +(SELECT count(*) FROM ColNames d \
 + WHERE substring(t.name,1,t.nlen-t.chop)=substring(d.name,1,d.nlen-d.chop)\
 + COLLATE NOCASE\
 +)\
 +";
 +#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
 +  static const char * const zColDigits = "\
 +SELECT CAST(ceil(log(count(*)+0.5)) AS INT) FROM ColNames \
 +";
 +#endif
 +  static const char * const zRenameRank =
 +#ifdef SHELL_COLUMN_RENAME_CLEAN
 +    "UPDATE ColNames AS t SET suff="
 +    "iif(reps>1, printf('%c%0*d', '"AUTOCOLUMN_SEP"', $1, cpos), '')"
 +#else /* ...RENAME_MINIMAL_ONE_PASS */
 +"WITH Lzn(nlz) AS (" /* Find minimum extraneous leading 0's for uniqueness */
 +"  SELECT 0 AS nlz"
 +"  UNION"
 +"  SELECT nlz+1 AS nlz FROM Lzn"
 +"  WHERE EXISTS("
 +"   SELECT 1"
 +"   FROM ColNames t, ColNames o"
 +"   WHERE"
 +"    iif(t.name IN (SELECT * FROM RepeatedNames),"
 +"     printf('%s"AUTOCOLUMN_SEP"%s',"
 +"      t.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,t.cpos),2)),"
 +"     t.name"
 +"    )"
 +"    ="
 +"    iif(o.name IN (SELECT * FROM RepeatedNames),"
 +"     printf('%s"AUTOCOLUMN_SEP"%s',"
 +"      o.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,o.cpos),2)),"
 +"     o.name"
 +"    )"
 +"    COLLATE NOCASE"
 +"    AND o.cpos<>t.cpos"
 +"   GROUP BY t.cpos"
 +"  )"
 +") UPDATE Colnames AS t SET"
 +" chop = 0," /* No chopping, never touch incoming names. */
 +" suff = iif(name IN (SELECT * FROM RepeatedNames),"
 +"  printf('"AUTOCOLUMN_SEP"%s', substring("
 +"   printf('%.*c%0.*d',(SELECT max(nlz) FROM Lzn)+1,'0',1,t.cpos),2)),"
 +"  ''"
 +" )"
 +#endif
 +    ;
 +  static const char * const zCollectVar = "\
 +SELECT\
 + '('||x'0a'\
 + || group_concat(\
 +  cname||' TEXT',\
 +  ','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\
 + ||')' AS ColsSpec \
 +FROM (\
-  SELECT cpos, printf('\"%w\"',printf('%.*s%s', nlen-chop,name,suff)) AS cname \
++ SELECT cpos, printf('\"%w\"',printf('%!.*s%s', nlen-chop,name,suff)) AS cname \
 + FROM ColNames ORDER BY cpos\
 +)";
 +  static const char * const zRenamesDone =
 +    "SELECT group_concat("
-     " printf('\"%w\" to \"%w\"',name,printf('%.*s%s', nlen-chop, name, suff)),"
++    " printf('\"%w\" to \"%w\"',name,printf('%!.*s%s', nlen-chop, name, suff)),"
 +    " ','||x'0a')"
 +    "FROM ColNames WHERE suff<>'' OR chop!=0"
 +    ;
 +  int rc;
 +  sqlite3_stmt *pStmt = 0;
 +  assert(pDb!=0);
 +  if( zColNew ){
 +    /* Add initial or additional column. Init db if necessary. */
 +    if( *pDb==0 ){
 +      if( SQLITE_OK!=sqlite3_open(zCOL_DB, pDb) ) return 0;
 +#ifdef SHELL_COLFIX_DB
 +      if(*zCOL_DB!=':')
 +        sqlite3_exec(*pDb,"drop table if exists ColNames;"
 +                     "drop view if exists RepeatedNames;",0,0,0);
 +#endif
 +      rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0);
 +      rc_err_oom_die(rc);
 +    }
 +    assert(*pDb!=0);
 +    rc = sqlite3_prepare_v2(*pDb, zTabFill, -1, &pStmt, 0);
 +    rc_err_oom_die(rc);
 +    rc = sqlite3_bind_text(pStmt, 1, zColNew, -1, 0);
 +    rc_err_oom_die(rc);
 +    rc = sqlite3_step(pStmt);
 +    rc_err_oom_die(rc);
 +    sqlite3_finalize(pStmt);
 +    return 0;
 +  }else if( *pDb==0 ){
 +    return 0;
    }else{
 -    rc = arExecSql(pAr, "RELEASE ar;");
 -    if( pAr->bZip && pAr->zFile ){
 -      zSql = sqlite3_mprintf("DROP TABLE %s", zTemp);
 -      arExecSql(pAr, zSql);
 -      sqlite3_free(zSql);
 +    /* Formulate the columns spec, close the DB, zero *pDb. */
 +    char *zColsSpec = 0;
 +    int hasDupes = db_int(*pDb, zHasDupes);
 +#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
 +    int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0;
 +#else
 +# define nDigits 2
 +#endif
 +    if( hasDupes ){
 +#ifdef SHELL_COLUMN_RENAME_CLEAN
 +      rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0);
 +      rc_err_oom_die(rc);
 +#endif
 +      rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0);
 +      rc_err_oom_die(rc);
 +      rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0);
 +      rc_err_oom_die(rc);
 +      sqlite3_bind_int(pStmt, 1, nDigits);
 +      rc = sqlite3_step(pStmt);
 +      sqlite3_finalize(pStmt);
 +      assert(rc==SQLITE_DONE);
      }
 -  }
 -  sqlite3_free(zExists);
 -  return rc;
 -}
 -
 -/*
 -** Implementation of ".ar" dot command.
 -*/
 -static int arDotCommand(
 -  ShellState *pState,          /* Current shell tool state */
 -  int fromCmdLine,             /* True if -A command-line option, not .ar cmd */
 -  char **azArg,                /* Array of arguments passed to dot command */
 -  int nArg                     /* Number of entries in azArg[] */
 -){
 -  ArCommand cmd;
 -  int rc;
 -  memset(&cmd, 0, sizeof(cmd));
 -  cmd.fromCmdLine = fromCmdLine;
 -  rc = arParseCommand(azArg, nArg, &cmd);
 -  if( rc==SQLITE_OK ){
 -    int eDbType = SHELL_OPEN_UNSPEC;
 -    cmd.p = pState;
 -    cmd.db = pState->db;
 -    if( cmd.zFile ){
 -      eDbType = deduceDatabaseType(cmd.zFile, 1);
 +    /* This assert is maybe overly cautious for above de-dup DML, but that can
 +     * be replaced via #define's. So this check is made for debug builds. */
 +    assert(db_int(*pDb, zHasDupes)==0);
 +    rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0);
 +    rc_err_oom_die(rc);
 +    rc = sqlite3_step(pStmt);
 +    if( rc==SQLITE_ROW ){
 +      zColsSpec = smprintf("%s", sqlite3_column_text(pStmt, 0));
      }else{
 -      eDbType = pState->openMode;
 -    }
 -    if( eDbType==SHELL_OPEN_ZIPFILE ){
 -      if( cmd.eCmd==AR_CMD_EXTRACT || cmd.eCmd==AR_CMD_LIST ){
 -        if( cmd.zFile==0 ){
 -          cmd.zSrcTable = sqlite3_mprintf("zip");
 -        }else{
 -          cmd.zSrcTable = sqlite3_mprintf("zipfile(%Q)", cmd.zFile);
 -        }
 -      }
 -      cmd.bZip = 1;
 -    }else if( cmd.zFile ){
 -      int flags;
 -      if( cmd.bAppend ) eDbType = SHELL_OPEN_APPENDVFS;
 -      if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_INSERT 
 -           || cmd.eCmd==AR_CMD_REMOVE || cmd.eCmd==AR_CMD_UPDATE ){
 -        flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE;
 -      }else{
 -        flags = SQLITE_OPEN_READONLY;
 -      }
 -      cmd.db = 0;
 -      if( cmd.bDryRun ){
 -        utf8_printf(pState->out, "-- open database '%s'%s\n", cmd.zFile,
 -             eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : "");
 -      }
 -      rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, 
 -             eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0);
 -      if( rc!=SQLITE_OK ){
 -        utf8_printf(stderr, "cannot open file: %s (%s)\n", 
 -            cmd.zFile, sqlite3_errmsg(cmd.db)
 -        );
 -        goto end_ar_command;
 -      }
 -      sqlite3_fileio_init(cmd.db, 0, 0);
 -      sqlite3_sqlar_init(cmd.db, 0, 0);
 -      sqlite3_create_function(cmd.db, "shell_putsnl", 1, SQLITE_UTF8, cmd.p,
 -                              shellPutsFunc, 0, 0);
 -
 +      zColsSpec = 0;
      }
 -    if( cmd.zSrcTable==0 && cmd.bZip==0 && cmd.eCmd!=AR_CMD_HELP ){
 -      if( cmd.eCmd!=AR_CMD_CREATE
 -       && sqlite3_table_column_metadata(cmd.db,0,"sqlar","name",0,0,0,0,0)
 -      ){
 -        utf8_printf(stderr, "database does not contain an 'sqlar' table\n");
 -        rc = SQLITE_ERROR;
 -        goto end_ar_command;
 +    if( pzRenamed!=0 ){
 +      if( !hasDupes ) *pzRenamed = 0;
 +      else{
 +        sqlite3_finalize(pStmt);
 +        if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0)
 +            && SQLITE_ROW==sqlite3_step(pStmt) ){
 +          *pzRenamed = smprintf("%s", sqlite3_column_text(pStmt, 0));
 +        }else
 +          *pzRenamed = 0;
        }
 -      cmd.zSrcTable = sqlite3_mprintf("sqlar");
      }
 +    sqlite3_finalize(pStmt);
 +    sqlite3_close(*pDb);
 +    *pDb = 0;
 +    return zColsSpec;
 +  }
 +}
  
 -    switch( cmd.eCmd ){
 -      case AR_CMD_CREATE:
 -        rc = arCreateOrUpdateCommand(&cmd, 0, 0);
 -        break;
 +static FILE *currentOutputFile(ShellExState *p){
 +  return ISS(p)->out;
 +}
  
 -      case AR_CMD_EXTRACT:
 -        rc = arExtractCommand(&cmd);
 -        break;
 +#if SHELL_DYNAMIC_EXTENSION
 +/* Ensure there is room in loaded extension info list for one being loaded.
 + * On exit, psi->ixExtPending can be used to index into psi->pShxLoaded.
 + */
 +static ShExtInfo *pending_ext_info(ShellInState *psi){
 +  int ixpe = psi->ixExtPending;
 +  assert(ixpe!=0);
 +  if( ixpe >= psi->numExtLoaded ){
 +    psi->pShxLoaded = sqlite3_realloc(psi->pShxLoaded,
 +                                      (ixpe+1)*sizeof(ShExtInfo));
 +    shell_check_oom(psi->pShxLoaded);
 +    ++psi->numExtLoaded;
 +    memset(psi->pShxLoaded+ixpe, 0, sizeof(ShExtInfo));
 +  }
 +  return &psi->pShxLoaded[ixpe];
 +}
 +
 +/* Register a meta-command, to be called during extension load/init. */
 +static int register_meta_command(ShellExState *p,
 +                                 ExtensionId eid, MetaCommand *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->numMetaCommands;
 +    int rc;
 +    if( psei->extId!=0 && psei->extId!=eid ) return SQLITE_MISUSE;
 +    psei->extId = eid;
 +    rc = sqlite3_prepare_v2(p->dbShell, zSql, -1, &pStmt, 0);
 +    if( rc!=SQLITE_OK ) return rc;
 +    psei->ppMetaCommands
 +      = sqlite3_realloc(psei->ppMetaCommands, (nc+1)*sizeof(MetaCommand *));
 +    shell_check_oom(psei->ppMetaCommands);
 +    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->ppMetaCommands[nc++] = pMC;
 +      psei->numMetaCommands = nc;
 +      notify_subscribers(psi, NK_NewDotCommand, pMC);
 +      if( strcmp("unknown", zName)==0 ){
 +        psi->pUnknown = pMC;
 +        psei->pUnknown = pMC;
 +      }
 +      return SQLITE_OK;
 +    }else{
 +      psei->ppMetaCommands[nc] = 0;
 +    }
 +  }
 +  return SQLITE_ERROR;
 +}
  
 -      case AR_CMD_LIST:
 -        rc = arListCommand(&cmd);
 -        break;
 +/* Register an output data display (or other disposition) mode */
 +static int register_exporter(ShellExState *p,
 +                             ExtensionId eid, ExportHandler *pEH){
 +  return SQLITE_ERROR;
 +}
  
 -      case AR_CMD_HELP:
 -        arUsage(pState->out);
 -        break;
 +/* Register an import variation from (various sources) for .import */
 +static int register_importer(ShellExState *p,
 +                             ExtensionId eid, ImportHandler *pIH){
 +  return SQLITE_ERROR;
 +}
  
 -      case AR_CMD_INSERT:
 -        rc = arCreateOrUpdateCommand(&cmd, 1, 0);
 -        break;
 +/* 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;
 +}
  
 -      case AR_CMD_REMOVE:
 -        rc = arRemoveCommand(&cmd);
 -        break;
 +/* See registerAdHocCommand API in shext_linkage.h re detailed behavior.
 + * Depending on zHelp==0, either register or unregister ad-hoc treatment
 + * of zName for this extension (identified by eid.)
 + */
 +static int register_adhoc_command(ShellExState *p, ExtensionId eid,
 +                                  const char *zName, const char *zHelp){
 +  ShellInState *psi = ISS(p);
 +  u8 bRegNotRemove = zHelp!=0;
 +  const char *zSql = bRegNotRemove
 +    ? "INSERT OR REPLACE INTO "SHELL_AHELP_TAB
 +    "(name, extIx, helpText) VALUES(?, ?, ?||?)"
 +    : "DELETE FROM "SHELL_AHELP_TAB" WHERE name=? AND extIx=?";
 +  sqlite3_stmt *pStmt;
 +  int rc, ie;
  
 -      default:
 -        assert( cmd.eCmd==AR_CMD_UPDATE );
 -        rc = arCreateOrUpdateCommand(&cmd, 1, 1);
 -        break;
 -    }
 +  assert(psi->pShxLoaded!=0 && p->dbShell!=0);
 +  for( ie=psi->numExtLoaded-1; ie>0; --ie ){
 +    if( psi->pShxLoaded[ie].extId==eid ) break;
    }
 -end_ar_command:
 -  if( cmd.db!=pState->db ){
 -    close_db(cmd.db);
 +  if( !zName || ie==0 || psi->pShxLoaded[ie].pUnknown==0 ) return SQLITE_MISUSE;
 +  rc = sqlite3_prepare_v2(p->dbShell, zSql, -1, &pStmt, 0);
 +  if( rc!=SQLITE_OK ) return rc;
 +  sqlite3_bind_text(pStmt, 1, zName, -1, 0);
 +  sqlite3_bind_int(pStmt, 2, ie);
 +  if( bRegNotRemove ){
 +    int nc = strlen30(zHelp);
 +    const char *zLE = (nc>0 && zHelp[nc-1]!='\n')? "\n" : "";
 +    sqlite3_bind_text(pStmt, 3, zHelp, -1, 0);
 +    sqlite3_bind_text(pStmt, 4, zLE, -1, 0);
    }
 -  sqlite3_free(cmd.zSrcTable);
 -
 -  return rc;
 +  rc = sqlite3_step(pStmt);
 +  sqlite3_finalize(pStmt);
 +  return (rc==SQLITE_DONE)? SQLITE_OK : SQLITE_ERROR;
  }
 -/* End of the ".archive" or ".ar" command logic
 -*******************************************************************************/
 -#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) */
  
 -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
  /*
 -** If (*pRc) is not SQLITE_OK when this function is called, it is a no-op.
 -** Otherwise, the SQL statement or statements in zSql are executed using
 -** database connection db and the error code written to *pRc before
 -** this function returns.
 -*/
 -static void shellExec(sqlite3 *db, int *pRc, const char *zSql){
 -  int rc = *pRc;
 -  if( rc==SQLITE_OK ){
 -    char *zErr = 0;
 -    rc = sqlite3_exec(db, zSql, 0, 0, &zErr);
 -    if( rc!=SQLITE_OK ){
 -      raw_printf(stderr, "SQL error: %s\n", zErr);
 + * Subscribe to (or unsubscribe from) messages about various changes. 
 + * Unsubscribe, effected when nkMin==NK_Unsubscribe, always succeeds.
 + * Return SQLITE_OK on success, or one of these error codes:
 + * SQLITE_ERROR when the nkMin value is unsupported by this host;
 + * SQLITE_NOMEM when a required allocation failed; or
 + * SQLITE_MISUSE when the provided eid or eventHandler is invalid.
 + */
 +static int subscribe_events(ShellExState *p, ExtensionId eid, void *pvUserData,
 +                            NoticeKind nkMin, ShellEventNotify eventHandler){
 +  ShellInState *psi = ISS(p);
 +  struct EventSubscription *pes = psi->pSubscriptions;
 +  struct EventSubscription *pesLim = pes + psi->numSubscriptions;
 +  if( nkMin==NK_Unsubscribe ){
 +    /* unsubscribe (if now subscribed) */
 +    while( pes < pesLim ){
 +      if( (eventHandler==0 || eventHandler==pes->eventHandler)
 +          && (pes->eid==0 || pes->eid==eid)
 +          && (eid!=0 || eventHandler!=0 ||/*for shell use*/ pvUserData==p ) ){
 +        int nLeft = pesLim - pes;
 +        assert(pes->eventHandler!=0);
 +        pes->eventHandler(pes->pvUserData, NK_Unsubscribe, pes->eid, p);
 +        if( nLeft>1 ) memmove(pes, pes+1, (nLeft-1)*sizeof(*pes));
 +        --pesLim;
 +        --psi->numSubscriptions;
 +      }else{
 +        ++pes;
 +      }
      }
 -    sqlite3_free(zErr);
 -    *pRc = rc;
 +    if( psi->numSubscriptions==0 ){
 +      sqlite3_free(psi->pSubscriptions);
 +      psi->pSubscriptions = 0;
 +    }
 +    return SQLITE_OK;
 +  }else{
 +    /* subscribe only if minimum NoticeKind supported by this host */
 +    if( nkMin > NK_CountOf ) return SQLITE_ERROR;
 +    if( eventHandler==0 || eid==0 ) return SQLITE_MISUSE;
 +    while( pes < pesLim ){
 +      /* Never add duplicate handlers, but may renew their user data. */
 +      if( pes->eid==eid && pes->eventHandler==eventHandler ){
 +        pes->pvUserData = pvUserData;
 +        return SQLITE_OK;
 +      }
 +    }
 +    assert(pes==pesLim);
 +    pes = sqlite3_realloc(pes, (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;
    }
  }
  
Simple merge