]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
(WIP) shell extension load and ShellState internal/external partitioning in place...
authorlarrybr <larrybr@noemail.net>
Sun, 6 Mar 2022 03:22:13 +0000 (03:22 +0000)
committerlarrybr <larrybr@noemail.net>
Sun, 6 Mar 2022 03:22:13 +0000 (03:22 +0000)
FossilOrigin-Name: a85679d3e9e1edf71ed6308377783cdaa677c51290f644a28da9a69d429e7be9

Makefile.in
manifest
manifest.uuid
src/shell.c.in
src/shext_linkage.h
src/test_shellext.c [new file with mode: 0644]
tool/mkshellc.tcl

index e5d30d030b4af3a29c9432bdf2104e015f2fb88b..62c64e21029d85f5cf0141c88c3a36270e22c12b 100644 (file)
@@ -1086,22 +1086,24 @@ keywordhash.h:  $(TOP)/tool/mkkeywordhash.c
 
 # Source files that go into making shell.c
 SHELL_SRC = \
-       $(TOP)/src/shell.c.in \
+        $(TOP)/src/shell.c.in \
         $(TOP)/ext/misc/appendvfs.c \
-       $(TOP)/ext/misc/completion.c \
+        $(TOP)/ext/misc/completion.c \
         $(TOP)/ext/misc/decimal.c \
-       $(TOP)/ext/misc/fileio.c \
+        $(TOP)/ext/misc/fileio.c \
         $(TOP)/ext/misc/ieee754.c \
         $(TOP)/ext/misc/regexp.c \
         $(TOP)/ext/misc/series.c \
-       $(TOP)/ext/misc/shathree.c \
-       $(TOP)/ext/misc/sqlar.c \
+        $(TOP)/ext/misc/shathree.c \
+        $(TOP)/ext/misc/sqlar.c \
         $(TOP)/ext/misc/uint.c \
-       $(TOP)/ext/expert/sqlite3expert.c \
-       $(TOP)/ext/expert/sqlite3expert.h \
-       $(TOP)/ext/misc/zipfile.c \
-       $(TOP)/ext/misc/memtrace.c \
-        $(TOP)/src/test_windirent.c
+        $(TOP)/ext/expert/sqlite3expert.c \
+        $(TOP)/ext/expert/sqlite3expert.h \
+        $(TOP)/ext/misc/zipfile.c \
+        $(TOP)/ext/misc/memtrace.c \
+        $(TOP)/src/test_windirent.c \
+        $(TOP)/src/shext_linkage.h \
+        $(TOP)/src/obj_interfaces.h
 
 shell.c:       $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl
        $(TCLSH_CMD) $(TOP)/tool/mkshellc.tcl >shell.c
index 19d125b5505df2cc9e2823595b066e873447ddec..d514a66ceb8c69af775bc54c0dcf08353a3e179b 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,9 +1,9 @@
-C Merge\sshell-tweaks\senhancements\s(with\s3.38)
-D 2022-02-25T00:09:07.697
+C (WIP)\sshell\sextension\sload\sand\sShellState\sinternal/external\spartitioning\sin\splace,\spassing\sshell\stests\swith\strivial\sexceptions
+D 2022-03-06T03:22:13.817
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
-F Makefile.in b210ad2733317f1a4353085dfb9d385ceec30b0e6a61d20a5accabecac6b1949
+F Makefile.in 39fc2a1f2ba85066c41c33c229e84e26fa51e953efa6bb703856153a308d70e2
 F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
 F Makefile.msc b28a8a7a977e7312f6859f560348e1eb110c21bd6cf9fab0d16537c0a514eef3
 F README.md 2dd87a5c1d108b224921f3dd47dea567973f706e1f6959386282a626f459a70c
@@ -555,8 +555,8 @@ F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
 F src/resolve.c ea935b87d6fb36c78b70cdc7b28561dc8f33f2ef37048389549c7b5ef9b0ba5e
 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
 F src/select.c 3baa9dd8cf240654773c7974e2bcce398ac9dd24419c36684156963defe43b35
-F src/shell.c.in 0da11ed800103e60c0cea85538efb025bebdba25c1e8516aa20ee67237e16be6
-F src/shext_linkage.h 5897e8140a06cb733d07a927994b30d41225eb93b5b8cf2ad0e3460cb4e95fd6
+F src/shell.c.in da85b36617af6eee9bc1b3ecb1ef2d7b17bed3acc42b367ff9d1ee8794687532
+F src/shext_linkage.h bff86090e82d1df3a6dd0c566324f38ac67cb75d6e72697d9a8a3065545b4f0c
 F src/sqlite.h.in e30cedf008d9c51511f4027a3739b727a588da553424748b48d2393f85dbde41
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h a95cb9ed106e3d39e2118e4dcc15a14faec3fa50d0093425083d340d9dfd96e6
@@ -605,6 +605,7 @@ F src/test_quota.h 2a8ad1952d1d2ca9af0ce0465e56e6c023b5e15d
 F src/test_rtree.c 671f3fae50ff116ef2e32a3bf1fe21b5615b4b7b
 F src/test_schema.c f5d6067dfc2f2845c4dd56df63e66ee826fb23877855c785f75cc2ca83fd0c1b
 F src/test_server.c a2615049954cbb9cfb4a62e18e2f0616e4dc38fe
+F src/test_shellext.c ea066d41fb25c55a3cf8b65f82e8beacc5ac477a19b770822003c1e48a9ab03b
 F src/test_sqllog.c 540feaea7280cd5f926168aee9deb1065ae136d0bbbe7361e2ef3541783e187a
 F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e
 F src/test_syscall.c 1073306ba2e9bfc886771871a13d3de281ed3939
@@ -1882,7 +1883,7 @@ F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61
 F tool/mkopcodeh.tcl 130b88697da6ec5b89b41844d955d08fb62c2552e889dec8c7bcecb28d8f50bd
 F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa
 F tool/mkpragmatab.tcl bd07bd59d45d0f3448e123d6937e9811195f9908a51e09d774609883055bfd3d
-F tool/mkshellc.tcl 754c73032cd7d279ce42ddeaaa49d1ece71adf96293e10e3b0670c0d6b0f8972
+F tool/mkshellc.tcl 36ecc5f66eab5c4687af1a0a4cb8435e9b5ab8ec9422ea63096b74453f83bf9b
 F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9
 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
 F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f
@@ -1948,8 +1949,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 6d0557244f69e78474f9222aae95c4c47925a466aa15e5666d0ada237538e262 8c9a5fb26ba045edef1269c5f5e8c8d87fa890b88ddb1121be72514a389a845d
-R 791b63806b337836957b5b12020a8987
+P 4c7d94d3f339d5de98ab5488e75b5dfea2c11fadb2cd44a9f6bc4e0769dbd34a
+R 4896c526cf9c30b9709825dd81bf0bdb
 U larrybr
-Z 6cdde194291306233e0d54419a5b14ca
+Z 8541d8e1a52cfb449200764f60f8482a
 # Remove this line to create a well-formed Fossil manifest.
index 632da48fb79120fd66c7f6bd12a0b12e8b38a042..7ba0c374f08dc460cbe7d959c21b2c92fd291436 100644 (file)
@@ -1 +1 @@
-4c7d94d3f339d5de98ab5488e75b5dfea2c11fadb2cd44a9f6bc4e0769dbd34a
\ No newline at end of file
+a85679d3e9e1edf71ed6308377783cdaa677c51290f644a28da9a69d429e7be9
\ No newline at end of file
index d227a9bd495be3b19fa4775bcef6ab886f5593a1..1528586763a3c031544dcc3ed3485bc15b198d20 100644 (file)
@@ -211,10 +211,11 @@ extern LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText);
 #endif
 
 /* Get the shell extension interfaces and structs. */
-INCLUDE shext_linkage.h 
+INCLUDE shext_linkage.h
 
-/* For an embedded shell, allow the 3 standard streams to be specified. 
-** If used, these names will have to refer to something globally reachable.
+/* For an embedded shell, allow the 3 standard streams to be specified.
+** If used, these names will have to refer to something globally reachable
+** from the same thread which called the shell's main().
 **/
 #ifndef STD_IN
 # define STD_IN stdin
@@ -248,6 +249,7 @@ static void setTextMode(FILE *file, int isOutput){
 
 static const char *(azHelp[]);
 static unsigned numCommands;
+static FILE *currentOutputFile(ShellExState *p);
 
 /* True if the timer is enabled */
 static int enableTimer = 0;
@@ -663,7 +665,7 @@ static FILE * openChrSource(const char *zFile){
 
 /*
 ** Arrange for shell input from either a FILE or a string.
-** For applicable invariants, see str_line_get(...) which is
+** For applicable invariants, see strLineGet(...) which is
 ** the only modifier of this struct. (3rd and 4th members)
 ** All other members are simply initialized to select the
 ** input stream and track input sources, set by whatever
@@ -685,12 +687,12 @@ typedef struct InSource {
 /* This instance's address is taken as part of interactive input test. */
 static InSource termInSource = { 0 /*stdin*/, 0, 0, 0, "<terminal>", 0};
 static InSource stdInSource = { 0 /*stdin*/, 0, 0, 0, "<stdin>", 0};
-static void init_std_inputs( FILE *pIn ){
+static void init_std_inputs(FILE *pIn ){
   termInSource.inFile = pIn;
   stdInSource.inFile = pIn;
 }
 
-static char *str_line_get( char *zBuf, int ncMax, InSource *pInSrc){
+static char *strLineGet(char *zBuf, int ncMax, InSource *pInSrc){
   if( pInSrc->inFile!=0 ){
     char *zRet = fgets(zBuf, ncMax, pInSrc->inFile );
     if( zRet!=0 ){
@@ -742,7 +744,7 @@ static char *local_getline(char *zLine, InSource *pInSrc){
       zLine = realloc(zLine, nLine);
       shell_check_oom(zLine);
     }
-    if( str_line_get(&zLine[n], nLine - n, pInSrc)==0 ){
+    if( strLineGet(&zLine[n], nLine - n, pInSrc)==0 ){
       if( n==0 ){
         free(zLine);
         return 0;
@@ -1174,7 +1176,7 @@ struct EQPGraph {
 
 /* By default, omit the extension options that are not done yet. */
 #ifndef SHELL_OMIT_EXTENSIONS
-# define SHELL_OMIT_EXTENSIONS 6
+# define SHELL_OMIT_EXTENSIONS 4
 #endif
 
 /* Selectively omit features with one PP variable. Value is true iff
@@ -1186,35 +1188,35 @@ struct EQPGraph {
 #define SHEXT_PARSING_BIT 0
 #define SHELL_EXTENDED_PARSING \
   NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_PARSING_BIT)
-/* Whether build will include dynamic dot-command extension */
-#define SHEXT_DYNCMDS_BIT 1
-#define SHELL_DYNAMIC_COMMANDS \
-  NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_DYNCMDS_BIT)
+/* Whether build will include runtime extension via .load -extension */
+#define SHEXT_DYNEXT_BIT 1
+#define SHELL_DYNAMIC_EXTENSION ( !defined(SQLITE_OMIT_LOAD_EXTENSION) \
+  && NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_DYNEXT_BIT) )
 /* Whether build will include expansion of variables in dot-commands */
 #define SHEXT_VAREXP_BIT 2
 #define SHELL_VARIABLE_EXPANSION \
   NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_VAREXP_BIT)
 
 #define SHELL_ALL_EXTENSIONS \
-  (1<<SHEXT_PARSING_BIT)+(1<<SHEXT_DYNCMDS_BIT)+(1<<SHEXT_VAREXP_BIT)
+  (1<<SHEXT_PARSING_BIT)+(1<<SHEXT_DYNEXT_BIT)+(1<<SHEXT_VAREXP_BIT)
 #if !defined(SHELL_OMIT_EXTENSIONS)
 # define SHELL_EXTENSIONS SHELL_ALL_EXTENSIONS
 #else
 # define SHELL_EXTENSIONS ((~SHELL_OMIT_EXTENSIONS) & SHELL_ALL_EXTENSIONS)
 #endif
 
-/* Runtime test for shell extended parsing, given ShellState pointer */
+/* Runtime test for shell extended parsing, given ShellInState pointer */
 #if SHELL_EXTENDED_PARSING
-# define SHEXT_PARSING(pSS) ((pSS->bExtendedDotCmds&(1<<SHEXT_PARSING_BIT))!=0)
+# define SHEXT_PARSING(psi) ((psi->bExtendedDotCmds&(1<<SHEXT_PARSING_BIT))!=0)
 #else
-# define SHEXT_PARSING(pSS) 0
+# define SHEXT_PARSING(psi) 0
 #endif
 
-/* Runtime test for shell variable expansion, given ShellState pointer */
+/* Runtime test for shell variable expansion, given ShellInState pointer */
 #if SHELL_EXTENDED_PARSING
-# define SHEXT_VAREXP(pSS) ((pSS->bExtendedDotCmds&(1<<SHEXT_VAREXP_BIT))!=0)
+# define SHEXT_VAREXP(psi) ((psi->bExtendedDotCmds&(1<<SHEXT_VAREXP_BIT))!=0)
 #else
-# define SHEXT_VAREXP(pSS) 0
+# define SHEXT_VAREXP(psi) 0
 #endif
 
 /* Parameters affecting columnar mode result display (defaulting together) */
@@ -1229,9 +1231,9 @@ typedef struct ColModeOpts {
 /*
 ** Stored output mode state, for partial save and later restore.
 ** Returned by:
-**   outputModeSave *outputModeSave(ShellState *p, SaveModeWhat ws).
+**   outputModeSave *outputModeSave(ShellInState *p, SaveModeWhat ws).
 ** Accepted by:
-**   outputModeRestore(ShellState *p, OutputModeSave *pSaved).
+**   outputModeRestore(ShellInState *p, OutputModeSave *pSaved).
 ** See enum SaveWhatMode regarding what to save and restore.
 ** Also see outputModePush(...), outputModePushSome(...) and
 ** outputModePop(...) for usages spanning more than one call.
@@ -1243,14 +1245,35 @@ typedef struct OutputModeSave{
 #define MODE_STACK_MAX 3 /* How many levels of saved output mode to allow. */
 
 /*
-** State information about the database connection is contained in an
-** instance of the following structure.
+** Shell state information is contained in an instance of the following struct
+** and in a ShellExState struct defined in shext_linkage.h, partitioned thusly:
+**
+** Data not exposed for shell extension use, (and which need not be stable),
+** are kept in a ShellInState instance. These include facts about the database
+** connection, specialized display mode setup, safe mode control, and other
+** data associated with the shell's means of operation.
+**
+** Data which is exposed for shell extension use, (and which should be stable
+** or grown only with new members at the end, to preserve layout), is kept
+** in the ShellExState instance, which contains a ShellInState pointer, pSIS.
 */
-typedef struct ShellState ShellState;
-struct ShellState {
-  sqlite3 *db;           /* The database */
+
+/* Oft-used macros for transition to internal/external shell state access
+ * ISS(psx) : internal shell state (pointer)
+ * XSS(psi) : external shell state (pointer)
+ * DBI(psi) : DB from internal pointer
+ * DBX(psx) : DB from external pointer
+ */
+#define ISS(psx) (psx)->pSIS
+#define XSS(psi) (psi)->pSXS
+#define DBI(psi) (psi)->pSXS->dbUser
+#define DBX(psx) (psx)->dbUser
+
+typedef struct ShellInState {
+  /* sqlite3 *db;           user database moved to ShellExState.dbUser */
   int openFlags;         /* Additional flags to open.  (SQLITE_OPEN_NOFOLLOW) */
   u8 openMode;           /* SHELL_OPEN_NORMAL, _APPENDVFS, or _ZIPFILE */
+  sqlite3_int64 szMax;   /* --maxsize argument to .open */
   u8 autoExplain;        /* Automatically turn on .explain mode */
   u8 autoEQP;            /* Run EXPLAIN QUERY PLAN prior to seach SQL stmt */
   u8 autoEQPtest;        /* autoEQP is in test mode */
@@ -1276,22 +1299,27 @@ struct ShellState {
   /* Output mode state-keep for certain ad-hoc save/restore ops: */
   u8 cMode;              /* temporary output mode for the current query */
   u8 normalMode;         /* Output mode before ".explain on" */
+#if 0 /* moved to ShellExState and renamed */
   int *colWidth;         /* Requested width of each column in columnar modes */
   int *actualWidth;      /* Actual width of each column */
   int nWidth;            /* Number of slots in colWidth[] and actualWidth[] */
+#endif
   char nullValue[20];    /* Text to print for NULL retrieved from database */
 
   unsigned statsOn;      /* True to display memory stats before each finalize */
   unsigned mEqpLines;    /* Mask of veritical lines in the EQP output graph */
   int outCount;          /* Revert to stdout when reaching zero */
+#if 0 /* moved to ShellExState as resultCount */
   int cnt;               /* Number of records displayed so far */
-
+#endif
   int inputNesting;      /* Track nesting level of .read and other redirects */
   InSource *pInSource;   /* Read commands and SQL from this stream source */
 
   FILE *out;             /* Write results here */
   FILE *traceOut;        /* Output for sqlite3_trace() */
+#if 0 /* moved to ShellExState */
   int abruptExit;        /* Flag for immediate shell exit, exit code */
+#endif
   int nErr;              /* Number of errors seen */
   int writableSchema;    /* True if PRAGMA writable_schema=ON */
   int nCheck;            /* Number of ".check" commands run */
@@ -1299,8 +1327,6 @@ struct ShellState {
   unsigned mxProgress;   /* Maximum progress callbacks before failing */
   unsigned flgProgress;  /* Flags for the progress callback */
 
-  sqlite3_int64 szMax;   /* --maxsize argument to .open */
-  char *zDestTable;      /* Name of destination table when MODE_Insert */
   char *zTempFile;       /* Temporary file that might need deleting */
   char *zEditor;         /* Name of editor if non-zero, then deletable */
   char zTestcase[30];    /* Name of current test case */
@@ -1323,7 +1349,8 @@ struct ShellState {
   char *zNonce;          /* Nonce for temporary safe-mode suspension */
   EQPGraph sGraph;       /* Information for the graphical EXPLAIN QUERY PLAN */
   ExpertInfo expert;     /* Valid if previous command was ".expert OPT..." */
-};
+  ShellExState *pSXS;    /* Pointer to companion, exposed shell state */
+} ShellInState;
 
 /*
 ** Limit input nesting via .read or any other input redirect.
@@ -1340,28 +1367,28 @@ struct ShellState {
 ** equal 1 => Safe mode is and will remain active.
 ** N > 1 => Safe mode is suspended for N-1 operations.
 */
-static void updateSafeMode(ShellState *pSS){
-  switch( pSS->bSafeModeFuture ){
+static void updateSafeMode(ShellInState *psi){
+  switch( psi->bSafeModeFuture ){
   case 2:
   default:
-    --pSS->bSafeModeFuture;
+    --psi->bSafeModeFuture;
     /* Fall thru, suspension is in effect. */
   case 0:
-    pSS->bSafeMode = 0;
+    psi->bSafeMode = 0;
     break;
   case 1:
-    pSS->bSafeMode = 1;
+    psi->bSafeMode = 1;
   }
 }
 
-/* Allowed values for ShellState.autoEQP
+/* Allowed values for ShellInState.autoEQP
 */
 #define AUTOEQP_off      0           /* Automatic EXPLAIN QUERY PLAN is off */
 #define AUTOEQP_on       1           /* Automatic EQP is on */
 #define AUTOEQP_trigger  2           /* On and also show plans for triggers */
 #define AUTOEQP_full     3           /* Show full EXPLAIN */
 
-/* Allowed values for ShellState.openMode
+/* Allowed values for ShellInState.openMode
 */
 #define SHELL_OPEN_UNSPEC      0      /* No open-mode specified */
 #define SHELL_OPEN_NORMAL      1      /* Normal database file */
@@ -1371,13 +1398,13 @@ static void updateSafeMode(ShellState *pSS){
 #define SHELL_OPEN_DESERIALIZE 5      /* Open using sqlite3_deserialize() */
 #define SHELL_OPEN_HEXDB       6      /* Use "dbtotxt" output as data source */
 
-/* Allowed values for ShellState.eTraceType
+/* Allowed values for ShellInState.eTraceType
 */
 #define SHELL_TRACE_PLAIN      0      /* Show input SQL text */
 #define SHELL_TRACE_EXPANDED   1      /* Show expanded SQL text */
 #define SHELL_TRACE_NORMALIZED 2      /* Show normalized SQL text */
 
-/* Bits in the ShellState.flgProgress variable */
+/* Bits in the ShellInState.flgProgress variable */
 #define SHELL_PROGRESS_QUIET 0x01  /* Omit announcing every progress callback */
 #define SHELL_PROGRESS_RESET 0x02  /* Reset the count when the progres
                                    ** callback limit is reached, and for each
@@ -1401,9 +1428,9 @@ static void updateSafeMode(ShellState *pSS){
 /*
 ** Macros for testing and setting shellFlgs
 */
-#define ShellHasFlag(P,X)    (((P)->shellFlgs & (X))!=0)
-#define ShellSetFlag(P,X)    ((P)->shellFlgs|=(X))
-#define ShellClearFlag(P,X)  ((P)->shellFlgs&=(~(X)))
+#define ShellHasFlag(psx,X)    (((psx)->pSIS->shellFlgs & (X))!=0)
+#define ShellSetFlag(psx,X)    ((psx)->pSIS->shellFlgs|=(X))
+#define ShellClearFlag(psx,X)  ((psx)->pSIS->shellFlgs&=(~(X)))
 
 /*
 ** These are the allowed modes, in search order (for abbreviation matches.)
@@ -1428,7 +1455,7 @@ static void updateSafeMode(ShellState *pSS){
 #define MODE_Explain  17 /* Like MODE_Column, but do not truncate data */
 #define MODE_Pretty   18 /* Pretty-print schemas */
 #define MODE_Semi     19 /* Same as MODE_List but append ";" to each line */
-#define MODE_COUNT_OF 20 /* also an invalid MODE_x value */
+#define MODE_COUNT_OF 20 /* also a known invalid MODE_x value */
 
 /* Note that some of above ordering is assumed for this mode classification. */
 #define MODE_IS_COLUMNAR(m) \
@@ -1479,10 +1506,10 @@ static struct {
 ** A callback for the sqlite3_log() interface.
 */
 static void shellLog(void *pArg, int iErrCode, const char *zMsg){
-  ShellState *p = (ShellState*)pArg;
-  if( p->pLog==0 ) return;
-  utf8_printf(p->pLog, "(%d) %s\n", iErrCode, zMsg);
-  fflush(p->pLog);
+  ShellInState *psi = (ShellInState*)pArg;
+  if( psi->pLog==0 ) return;
+  utf8_printf(psi->pLog, "(%d) %s\n", iErrCode, zMsg);
+  fflush(psi->pLog);
 }
 
 /*
@@ -1496,9 +1523,9 @@ static void shellPutsFunc(
   int nVal,
   sqlite3_value **apVal
 ){
-  ShellState *p = (ShellState*)sqlite3_user_data(pCtx);
+  ShellExState *psx = (ShellExState*)sqlite3_user_data(pCtx);
   (void)nVal;
-  utf8_printf(p->out, "%s\n", sqlite3_value_text(apVal[0]));
+  utf8_printf(ISS(psx)->out, "%s\n", sqlite3_value_text(apVal[0]));
   sqlite3_result_value(pCtx, apVal[0]);
 }
 
@@ -1515,19 +1542,19 @@ static void shellPutsFunc(
 ** The return is true if failing, 0 otherwise.
 */
 static int failIfSafeMode(
-  ShellState *p,
+  ShellExState *psx,
   const char *zErrMsg,
   ...
 ){
-  if( p->bSafeMode==1 ){
+  if( ISS(psx)->bSafeMode==1 ){
     va_list ap;
     char *zMsg;
     va_start(ap, zErrMsg);
     zMsg = sqlite3_vmprintf(zErrMsg, ap);
     va_end(ap);
-    raw_printf(STD_ERR, "line %d: ", p->pInSource->lineno);
+    raw_printf(STD_ERR, "line %d: ", ISS(psx)->pInSource->lineno);
     utf8_printf(STD_ERR, "%s\n", zMsg);
-    p->abruptExit = 3;
+    psx->shellAbruptExit = 3;
     return 1;
   }
   return 0;
@@ -1693,7 +1720,7 @@ static struct {
   size_t size;
 } outputModeCopy[] = {
 #define SS_MEMBER_COPY(mn) \
-  { MEMBER_OFFSET(ShellState,mn), MEMBER_SIZEOF(ShellState,mn) }
+  { MEMBER_OFFSET(ShellInState,mn), MEMBER_SIZEOF(ShellInState,mn) }
   SS_MEMBER_COPY(showHeader), SS_MEMBER_COPY(shellFlgs),
   SS_MEMBER_COPY(mode), SS_MEMBER_COPY(cmOpts),
   SS_MEMBER_COPY(colSeparator), SS_MEMBER_COPY(rowSeparator)
@@ -1701,7 +1728,7 @@ static struct {
 };
 
 /* Allocate a buffer, copy requested output mode data to it, and return it. */
-static OutputModeSave *outputModeSave(ShellState *p, SaveWhatMode w){
+static OutputModeSave *outputModeSave(ShellInState *psi, SaveWhatMode w){
   u16 what = (u16)w;
   int i, nAlloc = sizeof(what)+1, mask = 1;
   char *pSaved = 0, *pFill;
@@ -1716,7 +1743,7 @@ static OutputModeSave *outputModeSave(ShellState *p, SaveWhatMode w){
   for( mask=1, i=0; i<SWM_CountOf; mask<<=1, ++i ){
     if( (what & mask)!=0 ){
       size_t nb = outputModeCopy[i].size;
-      memcpy(pFill, (char*)p+outputModeCopy[i].offset, nb);
+      memcpy(pFill, ((char*)psi)+outputModeCopy[i].offset, nb);
       pFill += nb;
     }
   }
@@ -1728,7 +1755,7 @@ static OutputModeSave *outputModeSave(ShellState *p, SaveWhatMode w){
  * The buffer is freed and its pointer is invalidated.
  * If called with some other buffer, results are undefined, likely bad.
  */
-static void outputModeRestore(ShellState *p, OutputModeSave *pSaved){
+static void outputModeRestore(ShellInState *psi, OutputModeSave *pSaved){
   sqlite3_uint64 nA = sqlite3_msize(pSaved);
   u16 what = (nA>sizeof(what))? *((u16 *)pSaved) : 0;
   int i, nAlloc = sizeof(what)+1, mask = 1;
@@ -1736,7 +1763,7 @@ static void outputModeRestore(ShellState *p, OutputModeSave *pSaved){
   for( i=0; i<SWM_CountOf && nAlloc<nA; mask<<=1, ++i ){
     if( (what & mask)!=0 ){
       size_t nb = outputModeCopy[i].size;
-      memcpy((char*)p+outputModeCopy[i].offset, pTake, nb);
+      memcpy(((char*)psi)+outputModeCopy[i].offset, pTake, nb);
       pTake += nb;
     }
   }
@@ -1747,18 +1774,18 @@ static void outputModeRestore(ShellState *p, OutputModeSave *pSaved){
 /*
 ** Save or restore the current output mode, in whole or in part.
 */
-static void outputModePushSome(ShellState *p, SaveWhatMode w){
+static void outputModePushSome(ShellInState *psi, SaveWhatMode w){
   OutputModeSave *pOMS;
-  assert(p->nSavedModes<MODE_STACK_MAX); /* Fail hard for this logic error. */
-  if( p->nSavedModes>=MODE_STACK_MAX ) return;
-  pOMS = outputModeSave(p, w);
+  assert(psi->nSavedModes<MODE_STACK_MAX); /* Fail hard for this logic error. */
+  if( psi->nSavedModes>=MODE_STACK_MAX ) return;
+  pOMS = outputModeSave(psi, w);
   shell_check_oom(pOMS);
-  p->pModeStack[p->nSavedModes++] = pOMS;
+  psi->pModeStack[psi->nSavedModes++] = pOMS;
 }
-static void outputModePush(ShellState *p){
-  outputModePushSome(p, SWM_everything);
+static void outputModePush(ShellInState *psi){
+  outputModePushSome(psi, SWM_everything);
 }
-static void outputModePop(ShellState *p){
+static void outputModePop(ShellInState *p){
   OutputModeSave *pOMS;
   assert(p->nSavedModes>0); /* Should not be here unless something pushed. */
   if( p->nSavedModes==0 ) return;
@@ -2031,10 +2058,11 @@ static const char needCsvQuote[] = {
 ** the null value.  Strings are quoted if necessary.  The separator
 ** is only issued if bSep is true.
 */
-static void output_csv(ShellState *p, const char *z, int bSep){
-  FILE *out = p->out;
+static void output_csv(ShellExState *psx, const char *z, int bSep){
+  FILE *out = ISS(psx)->out;
+  char *zColSep = psx->zFieldSeparator;
   if( z==0 ){
-    utf8_printf(out,"%s",p->nullValue);
+    utf8_printf(out,"%s",psx->zNullValue);
   }else{
     unsigned i;
     for(i=0; z[i]; i++){
@@ -2043,7 +2071,7 @@ static void output_csv(ShellState *p, const char *z, int bSep){
         break;
       }
     }
-    if( i==0 || strstr(z, p->colSeparator)!=0 ){
+    if( i==0 || strstr(z, zColSep)!=0 ){
       char *zQuoted = sqlite3_mprintf("\"%w\"", z);
       shell_check_oom(zQuoted);
       utf8_printf(out, "%s", zQuoted);
@@ -2053,7 +2081,7 @@ static void output_csv(ShellState *p, const char *z, int bSep){
     }
   }
   if( bSep ){
-    utf8_printf(p->out, "%s", p->colSeparator);
+    utf8_printf(out, "%s", zColSep);
   }
 }
 
@@ -2102,7 +2130,7 @@ static int safeModeAuth(
   const char *zA3,
   const char *zA4
 ){
-  ShellState *p = (ShellState*)pClientData;
+  ShellExState *psx = (ShellExState*)pClientData;
   static const char *azProhibitedFunctions[] = {
     "edit",
     "fts3_tokenizer",
@@ -2117,7 +2145,7 @@ static int safeModeAuth(
   UNUSED_PARAMETER(zA4);
   switch( op ){
     case SQLITE_ATTACH: {
-      if ( failIfSafeMode(p, "cannot run ATTACH in safe mode") )
+      if ( failIfSafeMode(psx, "cannot run ATTACH in safe mode") )
         return SQLITE_ERROR;
       break;
     }
@@ -2125,7 +2153,7 @@ static int safeModeAuth(
       int i;
       for(i=0; i<ArraySize(azProhibitedFunctions); i++){
         if( sqlite3_stricmp(zA1, azProhibitedFunctions[i])==0 ){
-          if( failIfSafeMode(p, "cannot use the %s() function in safe mode",
+          if( failIfSafeMode(psx, "cannot use the %s() function in safe mode",
                              azProhibitedFunctions[i]) )
             return SQLITE_ERROR;
         }
@@ -2148,7 +2176,8 @@ static int shellAuth(
   const char *zA3,
   const char *zA4
 ){
-  ShellState *p = (ShellState*)pClientData;
+  ShellExState *psx = (ShellExState*)pClientData;
+  ShellInState *psi = ISS(psx);
   static const char *azAction[] = { 0,
      "CREATE_INDEX",         "CREATE_TABLE",         "CREATE_TEMP_INDEX",
      "CREATE_TEMP_TABLE",    "CREATE_TEMP_TRIGGER",  "CREATE_TEMP_VIEW",
@@ -2168,17 +2197,17 @@ static int shellAuth(
   az[1] = zA2;
   az[2] = zA3;
   az[3] = zA4;
-  utf8_printf(p->out, "authorizer: %s", azAction[op]);
+  utf8_printf(psi->out, "authorizer: %s", azAction[op]);
   for(i=0; i<4; i++){
-    raw_printf(p->out, " ");
+    raw_printf(psi->out, " ");
     if( az[i] ){
-      output_c_string(p->out, az[i]);
+      output_c_string(psi->out, az[i]);
     }else{
-      raw_printf(p->out, "NULL");
+      raw_printf(psi->out, "NULL");
     }
   }
-  raw_printf(p->out, "\n");
-  if( p->bSafeMode ) (void)safeModeAuth(pClientData, op, zA1, zA2, zA3, zA4);
+  raw_printf(psi->out, "\n");
+  if( psi->bSafeMode ) (void)safeModeAuth(pClientData, op, zA1, zA2, zA3, zA4);
   return SQLITE_OK;
 }
 #endif
@@ -2223,11 +2252,12 @@ static int wsToEol(const char *z){
 /*
 ** Add a new entry to the EXPLAIN QUERY PLAN data
 */
-static void eqp_append(ShellState *p, int iEqpId, int p2, const char *zText){
+static void eqp_append(ShellInState *psi, int iEqpId, int p2,
+                       const char *zText){
   EQPGraphRow *pNew;
   int nText = strlen30(zText);
-  if( p->autoEQPtest ){
-    utf8_printf(p->out, "%d,%d,%s\n", iEqpId, p2, zText);
+  if( psi->autoEQPtest ){
+    utf8_printf(psi->out, "%d,%d,%s\n", iEqpId, p2, zText);
   }
   pNew = sqlite3_malloc64( sizeof(*pNew) + nText );
   shell_check_oom(pNew);
@@ -2235,32 +2265,33 @@ static void eqp_append(ShellState *p, int iEqpId, int p2, const char *zText){
   pNew->iParentId = p2;
   memcpy(pNew->zText, zText, nText+1);
   pNew->pNext = 0;
-  if( p->sGraph.pLast ){
-    p->sGraph.pLast->pNext = pNew;
+  if( psi->sGraph.pLast ){
+    psi->sGraph.pLast->pNext = pNew;
   }else{
-    p->sGraph.pRow = pNew;
+    psi->sGraph.pRow = pNew;
   }
-  p->sGraph.pLast = pNew;
+  psi->sGraph.pLast = pNew;
 }
 
 /*
 ** Free and reset the EXPLAIN QUERY PLAN data that has been collected
 ** in p->sGraph.
 */
-static void eqp_reset(ShellState *p){
+static void eqp_reset(ShellInState *psi){
   EQPGraphRow *pRow, *pNext;
-  for(pRow = p->sGraph.pRow; pRow; pRow = pNext){
+  for(pRow = psi->sGraph.pRow; pRow; pRow = pNext){
     pNext = pRow->pNext;
     sqlite3_free(pRow);
   }
-  memset(&p->sGraph, 0, sizeof(p->sGraph));
+  memset(&psi->sGraph, 0, sizeof(psi->sGraph));
 }
 
 /* Return the next EXPLAIN QUERY PLAN line with iEqpId that occurs after
 ** pOld, or return the first such line if pOld is NULL
 */
-static EQPGraphRow *eqp_next_row(ShellState *p, int iEqpId, EQPGraphRow *pOld){
-  EQPGraphRow *pRow = pOld ? pOld->pNext : p->sGraph.pRow;
+static EQPGraphRow *eqp_next_row(ShellInState *psi, int iEqpId,
+                                 EQPGraphRow *pOld){
+  EQPGraphRow *pRow = pOld ? pOld->pNext : psi->sGraph.pRow;
   while( pRow && pRow->iParentId!=iEqpId ) pRow = pRow->pNext;
   return pRow;
 }
@@ -2268,19 +2299,19 @@ static EQPGraphRow *eqp_next_row(ShellState *p, int iEqpId, EQPGraphRow *pOld){
 /* Render a single level of the graph that has iEqpId as its parent.  Called
 ** recursively to render sublevels.
 */
-static void eqp_render_level(ShellState *p, int iEqpId){
+static void eqp_render_level(ShellInState *psi, int iEqpId){
   EQPGraphRow *pRow, *pNext;
-  int n = strlen30(p->sGraph.zPrefix);
+  int n = strlen30(psi->sGraph.zPrefix);
   char *z;
-  for(pRow = eqp_next_row(p, iEqpId, 0); pRow; pRow = pNext){
-    pNext = eqp_next_row(p, iEqpId, pRow);
+  for(pRow = eqp_next_row(psi, iEqpId, 0); pRow; pRow = pNext){
+    pNext = eqp_next_row(psi, iEqpId, pRow);
     z = pRow->zText;
-    utf8_printf(p->out, "%s%s%s\n", p->sGraph.zPrefix,
+    utf8_printf(psi->out, "%s%s%s\n", psi->sGraph.zPrefix,
                 pNext ? "|--" : "`--", z);
-    if( n<(int)sizeof(p->sGraph.zPrefix)-7 ){
-      memcpy(&p->sGraph.zPrefix[n], pNext ? "|  " : "   ", 4);
-      eqp_render_level(p, pRow->iEqpId);
-      p->sGraph.zPrefix[n] = 0;
+    if( n<(int)sizeof(psi->sGraph.zPrefix)-7 ){
+      memcpy(&psi->sGraph.zPrefix[n], pNext ? "|  " : "   ", 4);
+      eqp_render_level(psi, pRow->iEqpId);
+      psi->sGraph.zPrefix[n] = 0;
     }
   }
 }
@@ -2288,23 +2319,23 @@ static void eqp_render_level(ShellState *p, int iEqpId){
 /*
 ** Display and reset the EXPLAIN QUERY PLAN data
 */
-static void eqp_render(ShellState *p){
-  EQPGraphRow *pRow = p->sGraph.pRow;
+static void eqp_render(ShellInState *psi){
+  EQPGraphRow *pRow = psi->sGraph.pRow;
   if( pRow ){
     if( pRow->zText[0]=='-' ){
       if( pRow->pNext==0 ){
-        eqp_reset(p);
+        eqp_reset(psi);
         return;
       }
-      utf8_printf(p->out, "%s\n", pRow->zText+3);
-      p->sGraph.pRow = pRow->pNext;
+      utf8_printf(psi->out, "%s\n", pRow->zText+3);
+      psi->sGraph.pRow = pRow->pNext;
       sqlite3_free(pRow);
     }else{
-      utf8_printf(p->out, "QUERY PLAN\n");
+      utf8_printf(psi->out, "QUERY PLAN\n");
     }
-    p->sGraph.zPrefix[0] = 0;
-    eqp_render_level(p, 0);
-    eqp_reset(p);
+    psi->sGraph.zPrefix[0] = 0;
+    eqp_render_level(psi, 0);
+    eqp_reset(psi);
   }
 }
 
@@ -2313,16 +2344,16 @@ static void eqp_render(ShellState *p){
 ** Progress handler callback.
 */
 static int progress_handler(void *pClientData) {
-  ShellState *p = (ShellState*)pClientData;
-  p->nProgress++;
-  if( p->nProgress>=p->mxProgress && p->mxProgress>0 ){
-    raw_printf(p->out, "Progress limit reached (%u)\n", p->nProgress);
-    if( p->flgProgress & SHELL_PROGRESS_RESET ) p->nProgress = 0;
-    if( p->flgProgress & SHELL_PROGRESS_ONCE ) p->mxProgress = 0;
+  ShellInState *psi = (ShellInState*)pClientData;
+  psi->nProgress++;
+  if( psi->nProgress>=psi->mxProgress && psi->mxProgress>0 ){
+    raw_printf(psi->out, "Progress limit reached (%u)\n", psi->nProgress);
+    if( psi->flgProgress & SHELL_PROGRESS_RESET ) psi->nProgress = 0;
+    if( psi->flgProgress & SHELL_PROGRESS_ONCE ) psi->mxProgress = 0;
     return 1;
   }
-  if( (p->flgProgress & SHELL_PROGRESS_QUIET)==0 ){
-    raw_printf(p->out, "Progress %u\n", p->nProgress);
+  if( (psi->flgProgress & SHELL_PROGRESS_QUIET)==0 ){
+    raw_printf(psi->out, "Progress %u\n", psi->nProgress);
   }
   return 0;
 }
@@ -2353,21 +2384,21 @@ static void print_dashes(FILE *out, int N){
 ** Print a markdown or table-style row separator using ascii-art
 */
 static void print_row_separator(
-  ShellState *p,
+  ShellExState *psx,
   int nArg,
   const char *zSep
 ){
   int i;
   if( nArg>0 ){
-    fputs(zSep, p->out);
-    print_dashes(p->out, p->actualWidth[0]+2);
+    fputs(zSep, ISS(psx)->out);
+    print_dashes(ISS(psx)->out, psx->pHaveWidths[0]+2);
     for(i=1; i<nArg; i++){
-      fputs(zSep, p->out);
-      print_dashes(p->out, p->actualWidth[i]+2);
+      fputs(zSep, ISS(psx)->out);
+      print_dashes(ISS(psx)->out, psx->pHaveWidths[i]+2);
     }
-    fputs(zSep, p->out);
+    fputs(zSep, ISS(psx)->out);
   }
-  fputs("\n", p->out);
+  fputs("\n", ISS(psx)->out);
 }
 
 /*
@@ -2382,10 +2413,12 @@ static int shell_callback(
   int *aiType      /* Column types.  Might be NULL */
 ){
   int i;
-  ShellState *p = (ShellState*)pArg;
+  ShellExState *psx = (ShellExState*)pArg;
+  ShellInState *psi = ISS(psx);
+  FILE *out = psi->out;
 
   if( azArg==0 ) return 0;
-  switch( p->cMode ){
+  switch( psi->cMode ){
     case MODE_Count:
     case MODE_Off: {
       break;
@@ -2397,10 +2430,10 @@ static int shell_callback(
         int len = strlen30(azCol[i] ? azCol[i] : "");
         if( len>w ) w = len;
       }
-      if( p->cnt++>0 ) utf8_printf(p->out, "%s", p->rowSeparator);
+      if( psx->resultCount++>0 ) utf8_printf(out, "%s", psi->rowSeparator);
       for(i=0; i<nArg; i++){
-        utf8_printf(p->out,"%*s = %s%s", w, azCol[i],
-                azArg[i] ? azArg[i] : p->nullValue, p->rowSeparator);
+        utf8_printf(out,"%*s = %s%s", w, azCol[i],
+                    azArg[i] ? azArg[i] : psi->nullValue, psi->rowSeparator);
       }
       break;
     }
@@ -2409,16 +2442,16 @@ static int shell_callback(
       if( nArg>ArraySize(aExplainWidth) ){
         nArg = ArraySize(aExplainWidth);
       }
-      if( p->cnt++==0 ){
+      if( psx->resultCount++==0 ){
         for(i=0; i<nArg; i++){
           int w = aExplainWidth[i];
-          utf8_width_print(p->out, w, azCol[i]);
-          fputs(i==nArg-1 ? "\n" : "  ", p->out);
+          utf8_width_print(out, w, azCol[i]);
+          fputs(i==nArg-1 ? "\n" : "  ", out);
         }
         for(i=0; i<nArg; i++){
           int w = aExplainWidth[i];
-          print_dashes(p->out, w);
-          fputs(i==nArg-1 ? "\n" : "  ", p->out);
+          print_dashes(out, w);
+          fputs(i==nArg-1 ? "\n" : "  ", out);
         }
       }
       if( azArg==0 ) break;
@@ -2428,19 +2461,19 @@ static int shell_callback(
         if( azArg[i] && strlenChar(azArg[i])>w ){
           w = strlenChar(azArg[i]);
         }
-        if( i==1 && p->aiIndent && p->pStmt ){
-          if( p->iIndent<p->nIndent ){
-            utf8_printf(p->out, "%*.s", p->aiIndent[p->iIndent], "");
+        if( i==1 && psi->aiIndent && psi->pStmt ){
+          if( psi->iIndent<psi->nIndent ){
+            utf8_printf(out, "%*.s", psi->aiIndent[psi->iIndent], "");
           }
-          p->iIndent++;
+          psi->iIndent++;
         }
-        utf8_width_print(p->out, w, azArg[i] ? azArg[i] : p->nullValue);
-        fputs(i==nArg-1 ? "\n" : "  ", p->out);
+        utf8_width_print(out, w, azArg[i] ? azArg[i] : psi->nullValue);
+        fputs(i==nArg-1 ? "\n" : "  ", out);
       }
       break;
     }
     case MODE_Semi: {   /* .schema and .fullschema output */
-      printSchemaLine(p->out, azArg[0], ";\n");
+      printSchemaLine(out, azArg[0], ";\n");
       break;
     }
     case MODE_Pretty: {  /* .schema and .fullschema with --indent */
@@ -2455,7 +2488,7 @@ static int shell_callback(
       if( sqlite3_strlike("CREATE VIEW%", azArg[0], 0)==0
        || sqlite3_strlike("CREATE TRIG%", azArg[0], 0)==0
       ){
-        utf8_printf(p->out, "%s;\n", azArg[0]);
+        utf8_printf(out, "%s;\n", azArg[0]);
         break;
       }
       z = sqlite3_mprintf("%s", azArg[0]);
@@ -2488,7 +2521,7 @@ static int shell_callback(
           }else if( c==')' ){
             nParen--;
             if( nLine>0 && nParen==0 && j>0 ){
-              printSchemaLineN(p->out, z, j, "\n");
+              printSchemaLineN(out, z, j, "\n");
               j = 0;
             }
           }
@@ -2497,7 +2530,7 @@ static int shell_callback(
            && (c=='(' || c=='\n' || (c==',' && !wsToEol(z+i+1)))
           ){
             if( c=='\n' ) j--;
-            printSchemaLineN(p->out, z, j, "\n  ");
+            printSchemaLineN(out, z, j, "\n  ");
             j = 0;
             nLine++;
             while( IsSpace(z[i+1]) ){ i++; }
@@ -2505,123 +2538,123 @@ static int shell_callback(
         }
         z[j] = 0;
       }
-      printSchemaLine(p->out, z, ";\n");
+      printSchemaLine(out, z, ";\n");
       sqlite3_free(z);
       break;
     }
     case MODE_List: {
-      if( p->cnt++==0 && p->showHeader ){
+      if( psx->resultCount++==0 && psi->showHeader ){
         for(i=0; i<nArg; i++){
-          utf8_printf(p->out,"%s%s",azCol[i],
-                  i==nArg-1 ? p->rowSeparator : p->colSeparator);
+          utf8_printf(out,"%s%s",azCol[i],
+                      i==nArg-1 ? psi->rowSeparator : psi->colSeparator);
         }
       }
       if( azArg==0 ) break;
       for(i=0; i<nArg; i++){
         char *z = azArg[i];
-        if( z==0 ) z = p->nullValue;
-        utf8_printf(p->out, "%s", z);
+        if( z==0 ) z = psi->nullValue;
+        utf8_printf(out, "%s", z);
         if( i<nArg-1 ){
-          utf8_printf(p->out, "%s", p->colSeparator);
+          utf8_printf(out, "%s", psi->colSeparator);
         }else{
-          utf8_printf(p->out, "%s", p->rowSeparator);
+          utf8_printf(out, "%s", psi->rowSeparator);
         }
       }
       break;
     }
     case MODE_Html: {
-      if( p->cnt++==0 && p->showHeader ){
-        raw_printf(p->out,"<TR>");
+      if( psx->resultCount++==0 && psi->showHeader ){
+        raw_printf(out,"<TR>");
         for(i=0; i<nArg; i++){
-          raw_printf(p->out,"<TH>");
-          output_html_string(p->out, azCol[i]);
-          raw_printf(p->out,"</TH>\n");
+          raw_printf(out,"<TH>");
+          output_html_string(out, azCol[i]);
+          raw_printf(out,"</TH>\n");
         }
-        raw_printf(p->out,"</TR>\n");
+        raw_printf(out,"</TR>\n");
       }
       if( azArg==0 ) break;
-      raw_printf(p->out,"<TR>");
+      raw_printf(out,"<TR>");
       for(i=0; i<nArg; i++){
-        raw_printf(p->out,"<TD>");
-        output_html_string(p->out, azArg[i] ? azArg[i] : p->nullValue);
-        raw_printf(p->out,"</TD>\n");
+        raw_printf(out,"<TD>");
+        output_html_string(out, azArg[i] ? azArg[i] : psi->nullValue);
+        raw_printf(out,"</TD>\n");
       }
-      raw_printf(p->out,"</TR>\n");
+      raw_printf(out,"</TR>\n");
       break;
     }
     case MODE_Tcl: {
-      if( p->cnt++==0 && p->showHeader ){
+      if( psx->resultCount++==0 && psi->showHeader ){
         for(i=0; i<nArg; i++){
-          output_c_string(p->out,azCol[i] ? azCol[i] : "");
-          if(i<nArg-1) utf8_printf(p->out, "%s", p->colSeparator);
+          output_c_string(out,azCol[i] ? azCol[i] : "");
+          if(i<nArg-1) utf8_printf(out, "%s", psi->colSeparator);
         }
-        utf8_printf(p->out, "%s", p->rowSeparator);
+        utf8_printf(out, "%s", psi->rowSeparator);
       }
       if( azArg==0 ) break;
       for(i=0; i<nArg; i++){
-        output_c_string(p->out, azArg[i] ? azArg[i] : p->nullValue);
-        if(i<nArg-1) utf8_printf(p->out, "%s", p->colSeparator);
+        output_c_string(out, azArg[i] ? azArg[i] : psi->nullValue);
+        if(i<nArg-1) utf8_printf(out, "%s", psi->colSeparator);
       }
-      utf8_printf(p->out, "%s", p->rowSeparator);
+      utf8_printf(out, "%s", psi->rowSeparator);
       break;
     }
     case MODE_Csv: {
-      setBinaryMode(p->out, 1);
-      if( p->cnt++==0 && p->showHeader ){
+      setBinaryMode(out, 1);
+      if( psx->resultCount++==0 && psi->showHeader ){
         for(i=0; i<nArg; i++){
-          output_csv(p, azCol[i] ? azCol[i] : "", i<nArg-1);
+          output_csv(psx, azCol[i] ? azCol[i] : "", i<nArg-1);
         }
-        utf8_printf(p->out, "%s", p->rowSeparator);
+        utf8_printf(out, "%s", psi->rowSeparator);
       }
       if( nArg>0 ){
         for(i=0; i<nArg; i++){
-          output_csv(p, azArg[i], i<nArg-1);
+          output_csv(psx, azArg[i], i<nArg-1);
         }
-        utf8_printf(p->out, "%s", p->rowSeparator);
+        utf8_printf(out, "%s", psi->rowSeparator);
       }
-      setTextMode(p->out, 1);
+      setTextMode(out, 1);
       break;
     }
     case MODE_Insert: {
       if( azArg==0 ) break;
-      utf8_printf(p->out,"INSERT INTO %s",p->zDestTable);
-      if( p->showHeader ){
-        raw_printf(p->out,"(");
+      utf8_printf(out,"INSERT INTO %s",psx->zDestTable);
+      if( psi->showHeader ){
+        raw_printf(out,"(");
         for(i=0; i<nArg; i++){
-          if( i>0 ) raw_printf(p->out, ",");
+          if( i>0 ) raw_printf(out, ",");
           if( quoteChar(azCol[i]) ){
             char *z = sqlite3_mprintf("\"%w\"", azCol[i]);
             shell_check_oom(z);
-            utf8_printf(p->out, "%s", z);
+            utf8_printf(out, "%s", z);
             sqlite3_free(z);
           }else{
-            raw_printf(p->out, "%s", azCol[i]);
+            raw_printf(out, "%s", azCol[i]);
           }
         }
-        raw_printf(p->out,")");
+        raw_printf(out,")");
       }
-      p->cnt++;
+      psx->resultCount++;
       for(i=0; i<nArg; i++){
-        raw_printf(p->out, i>0 ? "," : " VALUES(");
+        raw_printf(out, i>0 ? "," : " VALUES(");
         if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){
-          utf8_printf(p->out,"NULL");
+          utf8_printf(out,"NULL");
         }else if( aiType && aiType[i]==SQLITE_TEXT ){
-          if( ShellHasFlag(p, SHFLG_Newlines) ){
-            output_quoted_string(p->out, azArg[i]);
+          if( ShellHasFlag(psx, SHFLG_Newlines) ){
+            output_quoted_string(out, azArg[i]);
           }else{
-            output_quoted_escaped_string(p->out, azArg[i]);
+            output_quoted_escaped_string(out, azArg[i]);
           }
         }else if( aiType && aiType[i]==SQLITE_INTEGER ){
-          utf8_printf(p->out,"%s", azArg[i]);
+          utf8_printf(out,"%s", azArg[i]);
         }else if( aiType && aiType[i]==SQLITE_FLOAT ){
           char z[50];
-          double r = sqlite3_column_double(p->pStmt, i);
+          double r = sqlite3_column_double(psi->pStmt, i);
           sqlite3_uint64 ur;
           memcpy(&ur,&r,sizeof(r));
           if( ur==0x7ff0000000000000LL ){
-            raw_printf(p->out, "1e999");
+            raw_printf(out, "1e999");
           }else if( ur==0xfff0000000000000LL ){
-            raw_printf(p->out, "-1e999");
+            raw_printf(out, "-1e999");
           }else{
             sqlite3_int64 ir = (sqlite3_int64)r;
             if( r==(double)ir ){
@@ -2629,119 +2662,119 @@ static int shell_callback(
             }else{
               sqlite3_snprintf(50,z,"%!.20g", r);
             }
-            raw_printf(p->out, "%s", z);
+            raw_printf(out, "%s", z);
           }
-        }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){
-          const void *pBlob = sqlite3_column_blob(p->pStmt, i);
-          int nBlob = sqlite3_column_bytes(p->pStmt, i);
-          output_hex_blob(p->out, pBlob, nBlob);
+        }else if( aiType && aiType[i]==SQLITE_BLOB && psi->pStmt ){
+          const void *pBlob = sqlite3_column_blob(psi->pStmt, i);
+          int nBlob = sqlite3_column_bytes(psi->pStmt, i);
+          output_hex_blob(out, pBlob, nBlob);
         }else if( isNumber(azArg[i], 0) ){
-          utf8_printf(p->out,"%s", azArg[i]);
-        }else if( ShellHasFlag(p, SHFLG_Newlines) ){
-          output_quoted_string(p->out, azArg[i]);
+          utf8_printf(out,"%s", azArg[i]);
+        }else if( ShellHasFlag(psx, SHFLG_Newlines) ){
+          output_quoted_string(out, azArg[i]);
         }else{
-          output_quoted_escaped_string(p->out, azArg[i]);
+          output_quoted_escaped_string(out, azArg[i]);
         }
       }
-      raw_printf(p->out,");\n");
+      raw_printf(out,");\n");
       break;
     }
     case MODE_Json: {
       if( azArg==0 ) break;
-      if( p->cnt==0 ){
-        fputs("[{", p->out);
+      if( psx->resultCount==0 ){
+        fputs("[{", out);
       }else{
-        fputs(",\n{", p->out);
+        fputs(",\n{", out);
       }
-      p->cnt++;
+      psx->resultCount++;
       for(i=0; i<nArg; i++){
-        output_json_string(p->out, azCol[i], -1);
-        putc(':', p->out);
+        output_json_string(out, azCol[i], -1);
+        putc(':', out);
         if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){
-          fputs("null",p->out);
+          fputs("null",out);
         }else if( aiType && aiType[i]==SQLITE_FLOAT ){
           char z[50];
-          double r = sqlite3_column_double(p->pStmt, i);
+          double r = sqlite3_column_double(psi->pStmt, i);
           sqlite3_uint64 ur;
           memcpy(&ur,&r,sizeof(r));
           if( ur==0x7ff0000000000000LL ){
-            raw_printf(p->out, "1e999");
+            raw_printf(out, "1e999");
           }else if( ur==0xfff0000000000000LL ){
-            raw_printf(p->out, "-1e999");
+            raw_printf(out, "-1e999");
           }else{
             sqlite3_snprintf(50,z,"%!.20g", r);
-            raw_printf(p->out, "%s", z);
+            raw_printf(out, "%s", z);
           }
-        }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){
-          const void *pBlob = sqlite3_column_blob(p->pStmt, i);
-          int nBlob = sqlite3_column_bytes(p->pStmt, i);
-          output_json_string(p->out, pBlob, nBlob);
+        }else if( aiType && aiType[i]==SQLITE_BLOB && psi->pStmt ){
+          const void *pBlob = sqlite3_column_blob(psi->pStmt, i);
+          int nBlob = sqlite3_column_bytes(psi->pStmt, i);
+          output_json_string(out, pBlob, nBlob);
         }else if( aiType && aiType[i]==SQLITE_TEXT ){
-          output_json_string(p->out, azArg[i], -1);
+          output_json_string(out, azArg[i], -1);
         }else{
-          utf8_printf(p->out,"%s", azArg[i]);
+          utf8_printf(out,"%s", azArg[i]);
         }
         if( i<nArg-1 ){
-          putc(',', p->out);
+          putc(',', out);
         }
       }
-      putc('}', p->out);
+      putc('}', out);
       break;
     }
     case MODE_Quote: {
       if( azArg==0 ) break;
-      if( p->cnt==0 && p->showHeader ){
+      if( psx->resultCount==0 && psi->showHeader ){
         for(i=0; i<nArg; i++){
-          if( i>0 ) fputs(p->colSeparator, p->out);
-          output_quoted_string(p->out, azCol[i]);
+          if( i>0 ) fputs(psi->colSeparator, out);
+          output_quoted_string(out, azCol[i]);
         }
-        fputs(p->rowSeparator, p->out);
+        fputs(psi->rowSeparator, out);
       }
-      p->cnt++;
+      psx->resultCount++;
       for(i=0; i<nArg; i++){
-        if( i>0 ) fputs(p->colSeparator, p->out);
+        if( i>0 ) fputs(psi->colSeparator, out);
         if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){
-          utf8_printf(p->out,"NULL");
+          utf8_printf(out,"NULL");
         }else if( aiType && aiType[i]==SQLITE_TEXT ){
-          output_quoted_string(p->out, azArg[i]);
+          output_quoted_string(out, azArg[i]);
         }else if( aiType && aiType[i]==SQLITE_INTEGER ){
-          utf8_printf(p->out,"%s", azArg[i]);
+          utf8_printf(out,"%s", azArg[i]);
         }else if( aiType && aiType[i]==SQLITE_FLOAT ){
           char z[50];
-          double r = sqlite3_column_double(p->pStmt, i);
+          double r = sqlite3_column_double(psi->pStmt, i);
           sqlite3_snprintf(50,z,"%!.20g", r);
-          raw_printf(p->out, "%s", z);
-        }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){
-          const void *pBlob = sqlite3_column_blob(p->pStmt, i);
-          int nBlob = sqlite3_column_bytes(p->pStmt, i);
-          output_hex_blob(p->out, pBlob, nBlob);
+          raw_printf(out, "%s", z);
+        }else if( aiType && aiType[i]==SQLITE_BLOB && psi->pStmt ){
+          const void *pBlob = sqlite3_column_blob(psi->pStmt, i);
+          int nBlob = sqlite3_column_bytes(psi->pStmt, i);
+          output_hex_blob(out, pBlob, nBlob);
         }else if( isNumber(azArg[i], 0) ){
-          utf8_printf(p->out,"%s", azArg[i]);
+          utf8_printf(out,"%s", azArg[i]);
         }else{
-          output_quoted_string(p->out, azArg[i]);
+          output_quoted_string(out, azArg[i]);
         }
       }
-      fputs(p->rowSeparator, p->out);
+      fputs(psi->rowSeparator, out);
       break;
     }
     case MODE_Ascii: {
-      if( p->cnt++==0 && p->showHeader ){
+      if( psx->resultCount++==0 && psi->showHeader ){
         for(i=0; i<nArg; i++){
-          if( i>0 ) utf8_printf(p->out, "%s", p->colSeparator);
-          utf8_printf(p->out,"%s",azCol[i] ? azCol[i] : "");
+          if( i>0 ) utf8_printf(out, "%s", psi->colSeparator);
+          utf8_printf(out,"%s",azCol[i] ? azCol[i] : "");
         }
-        utf8_printf(p->out, "%s", p->rowSeparator);
+        utf8_printf(out, "%s", psi->rowSeparator);
       }
       if( azArg==0 ) break;
       for(i=0; i<nArg; i++){
-        if( i>0 ) utf8_printf(p->out, "%s", p->colSeparator);
-        utf8_printf(p->out,"%s",azArg[i] ? azArg[i] : p->nullValue);
+        if( i>0 ) utf8_printf(out, "%s", psi->colSeparator);
+        utf8_printf(out,"%s",azArg[i] ? azArg[i] : psi->nullValue);
       }
-      utf8_printf(p->out, "%s", p->rowSeparator);
+      utf8_printf(out, "%s", psi->rowSeparator);
       break;
     }
     case MODE_EQP: {
-      eqp_append(p, atoi(azArg[0]), atoi(azArg[1]), azArg[3]);
+      eqp_append(psi, atoi(azArg[0]), atoi(azArg[1]), azArg[3]);
       break;
     }
   }
@@ -2777,9 +2810,9 @@ static int captureOutputCallback(void *pArg, int nArg, char **azArg, char **az){
 /*
 ** Generate an appropriate SELFTEST table in the main database.
 */
-static void createSelftestTable(ShellState *p){
+static void createSelftestTable(ShellInState *p){
   char *zErrMsg = 0;
-  sqlite3_exec(p->db,
+  sqlite3_exec(DBI(p),
     "SAVEPOINT selftest_init;\n"
     "CREATE TABLE IF NOT EXISTS selftest(\n"
     "  tno INTEGER PRIMARY KEY,\n"   /* Test number */
@@ -2819,29 +2852,29 @@ static void createSelftestTable(ShellState *p){
     utf8_printf(STD_ERR, "SELFTEST initialization failure: %s\n", zErrMsg);
     sqlite3_free(zErrMsg);
   }
-  sqlite3_exec(p->db, "RELEASE selftest_init",0,0,0);
+  sqlite3_exec(DBI(p), "RELEASE selftest_init",0,0,0);
 }
 
 
 /*
-** Set the destination table field of the ShellState structure to
+** Set the destination table field of the shell state structure to
 ** the name of the table given.  Escape any quote characters in the
 ** table name.
 */
-static void set_table_name(ShellState *p, const char *zName){
+static void set_table_name(ShellExState *psx, const char *zName){
   int i, n;
   char cQuote;
   char *z;
 
-  if( p->zDestTable ){
-    free(p->zDestTable);
-    p->zDestTable = 0;
+  if( psx->zDestTable ){
+    free((void *)(psx->zDestTable));
+    psx->zDestTable = 0;
   }
   if( zName==0 ) return;
   cQuote = quoteChar(zName);
   n = strlen30(zName);
   if( cQuote ) n += n+2;
-  z = p->zDestTable = malloc( n+1 );
+  psx->zDestTable = (z = malloc( n+1 ));
   shell_check_oom(z);
   n = 0;
   if( cQuote ) z[n++] = cQuote;
@@ -2909,7 +2942,7 @@ static char *shell_error_context(const char *zSql, sqlite3 *db){
 ** won't consume the semicolon terminator.
 */
 static int run_table_dump_query(
-  ShellState *p,           /* Query context */
+  ShellInState *psi,       /* Query context */
   const char *zSelect      /* SELECT statement to extract content */
 ){
   sqlite3_stmt *pSelect;
@@ -2917,37 +2950,37 @@ static int run_table_dump_query(
   int nResult;
   int i;
   const char *z;
-  rc = sqlite3_prepare_v2(p->db, zSelect, -1, &pSelect, 0);
+  rc = sqlite3_prepare_v2(DBI(psi), zSelect, -1, &pSelect, 0);
   if( rc!=SQLITE_OK || !pSelect ){
-    char *zContext = shell_error_context(zSelect, p->db);
-    utf8_printf(p->out, "/**** ERROR: (%d) %s *****/\n%s", rc,
-                sqlite3_errmsg(p->db), zContext);
+    char *zContext = shell_error_context(zSelect, DBI(psi));
+    utf8_printf(psi->out, "/**** ERROR: (%d) %s *****/\n%s", rc,
+                sqlite3_errmsg(DBI(psi)), zContext);
     sqlite3_free(zContext);
-    if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++;
+    if( (rc&0xff)!=SQLITE_CORRUPT ) psi->nErr++;
     return rc;
   }
   rc = sqlite3_step(pSelect);
   nResult = sqlite3_column_count(pSelect);
   while( rc==SQLITE_ROW ){
     z = (const char*)sqlite3_column_text(pSelect, 0);
-    utf8_printf(p->out, "%s", z);
+    utf8_printf(psi->out, "%s", z);
     for(i=1; i<nResult; i++){
-      utf8_printf(p->out, ",%s", sqlite3_column_text(pSelect, i));
+      utf8_printf(psi->out, ",%s", sqlite3_column_text(pSelect, i));
     }
     if( z==0 ) z = "";
     while( z[0] && (z[0]!='-' || z[1]!='-') ) z++;
     if( z[0] ){
-      raw_printf(p->out, "\n;\n");
+      raw_printf(psi->out, "\n;\n");
     }else{
-      raw_printf(p->out, ";\n");
+      raw_printf(psi->out, ";\n");
     }
     rc = sqlite3_step(pSelect);
   }
   rc = sqlite3_finalize(pSelect);
   if( rc!=SQLITE_OK ){
-    utf8_printf(p->out, "/**** ERROR: (%d) %s *****/\n", rc,
-                sqlite3_errmsg(p->db));
-    if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++;
+    utf8_printf(psi->out, "/**** ERROR: (%d) %s *****/\n", rc,
+                sqlite3_errmsg(DBI(psi)));
+    if( (rc&0xff)!=SQLITE_CORRUPT ) psi->nErr++;
   }
   return rc;
 }
@@ -3019,7 +3052,7 @@ static void displayLinuxIoStats(FILE *out){
 ** Display a single line of status using 64-bit values.
 */
 static void displayStatLine(
-  ShellState *p,            /* The shell context */
+  ShellInState *psi,        /* The shell context (internal) */
   char *zLabel,             /* Label for this one line */
   char *zFormat,            /* Format for the result */
   int iStatusCtrl,          /* Which status to display */
@@ -3038,7 +3071,7 @@ static void displayStatLine(
   }else{
     sqlite3_snprintf(sizeof(zLine), zLine, zFormat, iHiwtr);
   }
-  raw_printf(p->out, "%-36s %s\n", zLabel, zLine);
+  raw_printf(psi->out, "%-36s %s\n", zLabel, zLine);
 }
 
 /*
@@ -3046,18 +3079,18 @@ static void displayStatLine(
 */
 static int display_stats(
   sqlite3 *db,                /* Database to query */
-  ShellState *pArg,           /* Pointer to ShellState */
+  ShellInState *psi,          /* Pointer to shell internal state */
   int bReset                  /* True to reset the stats */
 ){
   int iCur;
   int iHiwtr;
   FILE *out;
-  if( pArg==0 || pArg->out==0 ) return 0;
-  out = pArg->out;
+  if( psi==0 || psi->out==0 ) return 0;
+  out = psi->out;
 
-  if( pArg->pStmt && pArg->statsOn==2 ){
+  if( psi->pStmt && psi->statsOn==2 ){
     int nCol, i, x;
-    sqlite3_stmt *pStmt = pArg->pStmt;
+    sqlite3_stmt *pStmt = psi->pStmt;
     char z[100];
     nCol = sqlite3_column_count(pStmt);
     raw_printf(out, "%-36s %d\n", "Number of output columns:", nCol);
@@ -3079,107 +3112,107 @@ static int display_stats(
     }
   }
 
-  if( pArg->statsOn==3 ){
-    if( pArg->pStmt ){
-      iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset);
-      raw_printf(pArg->out, "VM-steps: %d\n", iCur);
+  if( psi->statsOn==3 ){
+    if( psi->pStmt ){
+      iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset);
+      raw_printf(psi->out, "VM-steps: %d\n", iCur);
     }
     return 0;
   }
 
-  displayStatLine(pArg, "Memory Used:",
+  displayStatLine(psi, "Memory Used:",
      "%lld (max %lld) bytes", SQLITE_STATUS_MEMORY_USED, bReset);
-  displayStatLine(pArg, "Number of Outstanding Allocations:",
+  displayStatLine(psi, "Number of Outstanding Allocations:",
      "%lld (max %lld)", SQLITE_STATUS_MALLOC_COUNT, bReset);
-  if( pArg->shellFlgs & SHFLG_Pagecache ){
-    displayStatLine(pArg, "Number of Pcache Pages Used:",
+  if( psi->shellFlgs & SHFLG_Pagecache ){
+    displayStatLine(psi, "Number of Pcache Pages Used:",
        "%lld (max %lld) pages", SQLITE_STATUS_PAGECACHE_USED, bReset);
   }
-  displayStatLine(pArg, "Number of Pcache Overflow Bytes:",
+  displayStatLine(psi, "Number of Pcache Overflow Bytes:",
      "%lld (max %lld) bytes", SQLITE_STATUS_PAGECACHE_OVERFLOW, bReset);
-  displayStatLine(pArg, "Largest Allocation:",
+  displayStatLine(psi, "Largest Allocation:",
      "%lld bytes", SQLITE_STATUS_MALLOC_SIZE, bReset);
-  displayStatLine(pArg, "Largest Pcache Allocation:",
+  displayStatLine(psi, "Largest Pcache Allocation:",
      "%lld bytes", SQLITE_STATUS_PAGECACHE_SIZE, bReset);
 #ifdef YYTRACKMAXSTACKDEPTH
-  displayStatLine(pArg, "Deepest Parser Stack:",
+  displayStatLine(psi, "Deepest Parser Stack:",
      "%lld (max %lld)", SQLITE_STATUS_PARSER_STACK, bReset);
 #endif
 
   if( db ){
-    if( pArg->shellFlgs & SHFLG_Lookaside ){
+    if( psi->shellFlgs & SHFLG_Lookaside ){
       iHiwtr = iCur = -1;
       sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED,
                         &iCur, &iHiwtr, bReset);
-      raw_printf(pArg->out,
+      raw_printf(psi->out,
               "Lookaside Slots Used:                %d (max %d)\n",
               iCur, iHiwtr);
       sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_HIT,
                         &iCur, &iHiwtr, bReset);
-      raw_printf(pArg->out, "Successful lookaside attempts:       %d\n",
+      raw_printf(psi->out, "Successful lookaside attempts:       %d\n",
               iHiwtr);
       sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE,
                         &iCur, &iHiwtr, bReset);
-      raw_printf(pArg->out, "Lookaside failures due to size:      %d\n",
+      raw_printf(psi->out, "Lookaside failures due to size:      %d\n",
               iHiwtr);
       sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL,
                         &iCur, &iHiwtr, bReset);
-      raw_printf(pArg->out, "Lookaside failures due to OOM:       %d\n",
+      raw_printf(psi->out, "Lookaside failures due to OOM:       %d\n",
               iHiwtr);
     }
     iHiwtr = iCur = -1;
     sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHiwtr, bReset);
-    raw_printf(pArg->out, "Pager Heap Usage:                    %d bytes\n",
+    raw_printf(psi->out, "Pager Heap Usage:                    %d bytes\n",
             iCur);
     iHiwtr = iCur = -1;
     sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_HIT, &iCur, &iHiwtr, 1);
-    raw_printf(pArg->out, "Page cache hits:                     %d\n", iCur);
+    raw_printf(psi->out, "Page cache hits:                     %d\n", iCur);
     iHiwtr = iCur = -1;
     sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1);
-    raw_printf(pArg->out, "Page cache misses:                   %d\n", iCur);
+    raw_printf(psi->out, "Page cache misses:                   %d\n", iCur);
     iHiwtr = iCur = -1;
     sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1);
-    raw_printf(pArg->out, "Page cache writes:                   %d\n", iCur);
+    raw_printf(psi->out, "Page cache writes:                   %d\n", iCur);
     iHiwtr = iCur = -1;
     sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_SPILL, &iCur, &iHiwtr, 1);
-    raw_printf(pArg->out, "Page cache spills:                   %d\n", iCur);
+    raw_printf(psi->out, "Page cache spills:                   %d\n", iCur);
     iHiwtr = iCur = -1;
     sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset);
-    raw_printf(pArg->out, "Schema Heap Usage:                   %d bytes\n",
+    raw_printf(psi->out, "Schema Heap Usage:                   %d bytes\n",
             iCur);
     iHiwtr = iCur = -1;
     sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHiwtr, bReset);
-    raw_printf(pArg->out, "Statement Heap/Lookaside Usage:      %d bytes\n",
+    raw_printf(psi->out, "Statement Heap/Lookaside Usage:      %d bytes\n",
             iCur);
   }
 
-  if( pArg->pStmt ){
+  if( psi->pStmt ){
     int iHit, iMiss;
-    iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP,
+    iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP,
                                bReset);
-    raw_printf(pArg->out, "Fullscan Steps:                      %d\n", iCur);
-    iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_SORT, bReset);
-    raw_printf(pArg->out, "Sort Operations:                     %d\n", iCur);
-    iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX,bReset);
-    raw_printf(pArg->out, "Autoindex Inserts:                   %d\n", iCur);
-    iHit = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_HIT, bReset);
-    iMiss = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_MISS, bReset);
+    raw_printf(psi->out, "Fullscan Steps:                      %d\n", iCur);
+    iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_SORT, bReset);
+    raw_printf(psi->out, "Sort Operations:                     %d\n", iCur);
+    iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_AUTOINDEX,bReset);
+    raw_printf(psi->out, "Autoindex Inserts:                   %d\n", iCur);
+    iHit = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_FILTER_HIT, bReset);
+    iMiss = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_FILTER_MISS, bReset);
     if( iHit || iMiss ){
-      raw_printf(pArg->out, "Bloom filter bypass taken:           %d/%d\n",
+      raw_printf(psi->out, "Bloom filter bypass taken:           %d/%d\n",
             iHit, iHit+iMiss);
     }
-    iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset);
-    raw_printf(pArg->out, "Virtual Machine Steps:               %d\n", iCur);
-    iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_REPREPARE,bReset);
-    raw_printf(pArg->out, "Reprepare operations:                %d\n", iCur);
-    iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_RUN, bReset);
-    raw_printf(pArg->out, "Number of times run:                 %d\n", iCur);
-    iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_MEMUSED, bReset);
-    raw_printf(pArg->out, "Memory used by prepared stmt:        %d\n", iCur);
+    iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset);
+    raw_printf(psi->out, "Virtual Machine Steps:               %d\n", iCur);
+    iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_REPREPARE,bReset);
+    raw_printf(psi->out, "Reprepare operations:                %d\n", iCur);
+    iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_RUN, bReset);
+    raw_printf(psi->out, "Number of times run:                 %d\n", iCur);
+    iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_MEMUSED, bReset);
+    raw_printf(psi->out, "Memory used by prepared stmt:        %d\n", iCur);
   }
 
 #ifdef __linux__
-  displayLinuxIoStats(pArg->out);
+  displayLinuxIoStats(psi->out);
 #endif
 
   /* Do not remove this machine readable comment: extra-stats-output-here */
@@ -3190,21 +3223,17 @@ static int display_stats(
 /*
 ** Display scan stats.
 */
-static void display_scanstats(
-  sqlite3 *db,                    /* Database to query */
-  ShellState *pArg                /* Pointer to ShellState */
-){
+static void display_scanstats(ShellInState *psi){
 #ifndef SQLITE_ENABLE_STMT_SCANSTATUS
-  UNUSED_PARAMETER(db);
-  UNUSED_PARAMETER(pArg);
+  UNUSED_PARAMETER(psi);
 #else
   int i, k, n, mx;
-  raw_printf(pArg->out, "-------- scanstats --------\n");
+  raw_printf(psi->out, "-------- scanstats --------\n");
   mx = 0;
   for(k=0; k<=mx; k++){
     double rEstLoop = 1.0;
     for(i=n=0; 1; i++){
-      sqlite3_stmt *p = pArg->pStmt;
+      sqlite3_stmt *p = psi->pStmt;
       sqlite3_int64 nLoop, nVisit;
       double rEst;
       int iSid;
@@ -3217,21 +3246,21 @@ static void display_scanstats(
       if( iSid!=k ) continue;
       if( n==0 ){
         rEstLoop = (double)nLoop;
-        if( k>0 ) raw_printf(pArg->out, "-------- subquery %d -------\n", k);
+        if( k>0 ) raw_printf(psi->out, "-------- subquery %d -------\n", k);
       }
       n++;
       sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_NVISIT, (void*)&nVisit);
       sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_EST, (void*)&rEst);
       sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_EXPLAIN, (void*)&zExplain);
-      utf8_printf(pArg->out, "Loop %2d: %s\n", n, zExplain);
+      utf8_printf(psi->out, "Loop %2d: %s\n", n, zExplain);
       rEstLoop *= rEst;
-      raw_printf(pArg->out,
+      raw_printf(psi->out,
           "         nLoop=%-8lld nRow=%-8lld estRow=%-8lld estRow/Loop=%-8g\n",
           nLoop, nVisit, (sqlite3_int64)(rEstLoop+0.5), rEst
       );
     }
   }
-  raw_printf(pArg->out, "---------------------------\n");
+  raw_printf(psi->out, "---------------------------\n");
 #endif
 }
 
@@ -3251,7 +3280,7 @@ static int str_in_array(const char *zStr, const char **azArray){
 
 /*
 ** If compiled statement pSql appears to be an EXPLAIN statement, allocate
-** and populate the ShellState.aiIndent[] array with the number of
+** and populate the ShellInState.aiIndent[] array with the number of
 ** spaces each opcode should be indented before it is output.
 **
 ** The indenting rules are:
@@ -3267,7 +3296,7 @@ static int str_in_array(const char *zStr, const char **azArray){
 **       then indent all opcodes between the earlier instruction
 **       and "Goto" by 2 spaces.
 */
-static void explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){
+static void explain_data_prepare(ShellInState *psi, sqlite3_stmt *pSql){
   const char *zSql;               /* The text of the SQL statement */
   const char *z;                  /* Used to check if this is an EXPLAIN */
   int *abYield = 0;               /* True if op is an OP_Yield */
@@ -3282,14 +3311,14 @@ static void explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){
   /* Try to figure out if this is really an EXPLAIN statement. If this
   ** cannot be verified, return early.  */
   if( sqlite3_column_count(pSql)!=8 ){
-    p->cMode = p->mode;
+    psi->cMode = psi->mode;
     return;
   }
   zSql = sqlite3_sql(pSql);
   if( zSql==0 ) return;
   for(z=zSql; *z==' ' || *z=='\t' || *z=='\n' || *z=='\f' || *z=='\r'; z++);
   if( sqlite3_strnicmp(z, "explain", 7) ){
-    p->cMode = p->mode;
+    psi->cMode = psi->mode;
     return;
   }
 
@@ -3316,33 +3345,34 @@ static void explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){
         int jj;
         for(jj=0; jj<ArraySize(explainCols); jj++){
           if( strcmp(sqlite3_column_name(pSql,jj),explainCols[jj])!=0 ){
-            p->cMode = p->mode;
+            psi->cMode = psi->mode;
             sqlite3_reset(pSql);
             return;
           }
         }
       }
       nAlloc += 100;
-      p->aiIndent = (int*)sqlite3_realloc64(p->aiIndent, nAlloc*sizeof(int));
-      shell_check_oom(p->aiIndent);
+      psi->aiIndent =
+        (int*)sqlite3_realloc64(psi->aiIndent, nAlloc*sizeof(int));
+      shell_check_oom(psi->aiIndent);
       abYield = (int*)sqlite3_realloc64(abYield, nAlloc*sizeof(int));
       shell_check_oom(abYield);
     }
     abYield[iOp] = str_in_array(zOp, azYield);
-    p->aiIndent[iOp] = 0;
-    p->nIndent = iOp+1;
+    psi->aiIndent[iOp] = 0;
+    psi->nIndent = iOp+1;
 
     if( str_in_array(zOp, azNext) ){
-      for(i=p2op; i<iOp; i++) p->aiIndent[i] += 2;
+      for(i=p2op; i<iOp; i++) psi->aiIndent[i] += 2;
     }
-    if( str_in_array(zOp, azGoto) && p2op<p->nIndent
+    if( str_in_array(zOp, azGoto) && p2op<psi->nIndent
      && (abYield[p2op] || sqlite3_column_int(pSql, 2))
     ){
-      for(i=p2op; i<iOp; i++) p->aiIndent[i] += 2;
+      for(i=p2op; i<iOp; i++) psi->aiIndent[i] += 2;
     }
   }
 
-  p->iIndent = 0;
+  psi->iIndent = 0;
   sqlite3_free(abYield);
   sqlite3_reset(pSql);
 }
@@ -3350,11 +3380,11 @@ static void explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){
 /*
 ** Free the array allocated by explain_data_prepare().
 */
-static void explain_data_delete(ShellState *p){
-  sqlite3_free(p->aiIndent);
-  p->aiIndent = 0;
-  p->nIndent = 0;
-  p->iIndent = 0;
+static void explain_data_delete(ShellInState *psi){
+  sqlite3_free(psi->aiIndent);
+  psi->aiIndent = 0;
+  psi->nIndent = 0;
+  psi->iIndent = 0;
 }
 
 /*
@@ -3424,16 +3454,16 @@ static ParamTableUse classify_param_name( const char *zName ){
 #define PARAM_STORE_SNAME PARAM_STORE_SCHEMA"."PARAM_STORE_NAME
 
 /* Create the TEMP table used to store parameter bindings and SQL statements */
-static void param_table_init(ShellState *p){
-  DbProtectState dps = allow_sys_schema_change(p->db);
-  sqlite3_exec(p->db,
+static void param_table_init(sqlite3 *db){
+  DbProtectState dps = allow_sys_schema_change(db);
+  sqlite3_exec(db,
     "CREATE TABLE IF NOT EXISTS "PARAM_TABLE_SNAME"(\n"
     "  key TEXT PRIMARY KEY,\n"
     "  value,\n"
     "  uses INT DEFAULT (0)" /* aka PTU_Binding */
     ") WITHOUT ROWID;",
     0, 0, 0);
-  restore_sys_schema_protection( p->db, &dps );
+  restore_sys_schema_protection( db, &dps );
 }
 
 /* Tell whether the above-created table exists, return true iff exists. */
@@ -3530,23 +3560,24 @@ static void print_box_line(FILE *out, int N){
 ** Draw a horizontal separator for a MODE_Box table.
 */
 static void print_box_row_separator(
-  ShellState *p,
+  ShellExState *psx,
   int nArg,
   const char *zSep1,
   const char *zSep2,
   const char *zSep3
 ){
   int i;
+  FILE *out = ISS(psx)->out;
   if( nArg>0 ){
-    utf8_printf(p->out, "%s", zSep1);
-    print_box_line(p->out, p->actualWidth[0]+2);
+    utf8_printf(out, "%s", zSep1);
+    print_box_line(out, psx->pHaveWidths[0]+2);
     for(i=1; i<nArg; i++){
-      utf8_printf(p->out, "%s", zSep2);
-      print_box_line(p->out, p->actualWidth[i]+2);
+      utf8_printf(out, "%s", zSep2);
+      print_box_line(out, psx->pHaveWidths[i]+2);
     }
-    utf8_printf(p->out, "%s", zSep3);
+    utf8_printf(out, "%s", zSep3);
   }
-  fputs("\n", p->out);
+  fputs("\n", out);
 }
 
 /*
@@ -3689,9 +3720,10 @@ static char *quoted_column(sqlite3_stmt *pStmt, int i){
 ** any output.
 */
 static void exec_prepared_stmt_columnar(
-  ShellState *p,                        /* Pointer to ShellState */
+  ShellExState *psx,                    /* Pointer to shell state */
   sqlite3_stmt *pStmt                   /* Statment to run */
 ){
+  ShellInState *psi = ISS(psx);
   sqlite3_int64 nRow = 0;
   int nColumn = 0;
   char **azData = 0;
@@ -3708,7 +3740,7 @@ static void exec_prepared_stmt_columnar(
   const unsigned char **azNextLine = 0;
   int bNextLine = 0;
   int bMultiLineRowExists = 0;
-  int bw = p->cmOpts.bWordWrap;
+  int bw = psi->cmOpts.bWordWrap;
 
   rc = sqlite3_step(pStmt);
   if( rc!=SQLITE_ROW ) return;
@@ -3720,31 +3752,31 @@ static void exec_prepared_stmt_columnar(
   azNextLine = sqlite3_malloc64( nColumn*sizeof(char*) );
   shell_check_oom((void*)azNextLine);
   memset((void*)azNextLine, 0, nColumn*sizeof(char*) );
-  if( p->cmOpts.bQuote ){
+  if( psi->cmOpts.bQuote ){
     azQuoted = sqlite3_malloc64( nColumn*sizeof(char*) );
     shell_check_oom(azQuoted);
     memset(azQuoted, 0, nColumn*sizeof(char*) );
   }
   abRowDiv = sqlite3_malloc64( nAlloc/nColumn );
   shell_check_oom(abRowDiv);
-  if( nColumn>p->nWidth ){
-    p->colWidth = realloc(p->colWidth, (nColumn+1)*2*sizeof(int));
-    shell_check_oom(p->colWidth);
-    for(i=p->nWidth; i<nColumn; i++) p->colWidth[i] = 0;
-    p->nWidth = nColumn;
-    p->actualWidth = &p->colWidth[nColumn];
-  }
-  memset(p->actualWidth, 0, nColumn*sizeof(int));
+  if( nColumn>psx->numWidths ){
+    psx->pSpecWidths = realloc(psx->pSpecWidths, (nColumn+1)*2*sizeof(int));
+    shell_check_oom(psx->pSpecWidths);
+    for(i=psx->numWidths; i<nColumn; i++) psx->pSpecWidths[i] = 0;
+    psx->numWidths = nColumn;
+    psx->pHaveWidths = &psx->pSpecWidths[nColumn];
+  }
+  memset(psx->pHaveWidths, 0, nColumn*sizeof(int));
   for(i=0; i<nColumn; i++){
-    w = p->colWidth[i];
+    w = psx->pSpecWidths[i];
     if( w<0 ) w = -w;
-    p->actualWidth[i] = w;
+    psx->pHaveWidths[i] = w;
   }
   for(i=0; i<nColumn; i++){
     const unsigned char *zNotUsed;
-    int wx = p->colWidth[i];
+    int wx = psx->pSpecWidths[i];
     if( wx==0 ){
-      wx = p->cmOpts.iWrap;
+      wx = psi->cmOpts.iWrap;
     }
     if( wx<0 ) wx = -wx;
     uz = (const unsigned char*)sqlite3_column_name(pStmt,i);
@@ -3763,14 +3795,14 @@ static void exec_prepared_stmt_columnar(
     abRowDiv[nRow] = 1;
     nRow++;
     for(i=0; i<nColumn; i++){
-      int wx = p->colWidth[i];
+      int wx = psx->pSpecWidths[i];
       if( wx==0 ){
-        wx = p->cmOpts.iWrap;
+        wx = psi->cmOpts.iWrap;
       }
       if( wx<0 ) wx = -wx;
       if( useNextLine ){
         uz = azNextLine[i];
-      }else if( p->cmOpts.bQuote ){
+      }else if( psi->cmOpts.bQuote ){
         sqlite3_free(azQuoted[i]);
         azQuoted[i] = quoted_column(pStmt,i);
         uz = (const unsigned char*)azQuoted[i];
@@ -3789,27 +3821,27 @@ static void exec_prepared_stmt_columnar(
   nTotal = nColumn*(nRow+1);
   for(i=0; i<nTotal; i++){
     z = azData[i];
-    if( z==0 ) z = p->nullValue;
+    if( z==0 ) z = psi->nullValue;
     n = strlenChar(z);
     j = i%nColumn;
-    if( n>p->actualWidth[j] ) p->actualWidth[j] = n;
+    if( n>psx->pHaveWidths[j] ) psx->pHaveWidths[j] = n;
   }
   if( seenInterrupt ) goto columnar_end;
   if( nColumn==0 ) goto columnar_end;
-  switch( p->cMode ){
+  switch( psi->cMode ){
     case MODE_Column: {
       colSep = "  ";
       rowSep = "\n";
-      if( p->showHeader ){
+      if( psi->showHeader ){
         for(i=0; i<nColumn; i++){
-          w = p->actualWidth[i];
-          if( p->colWidth[i]<0 ) w = -w;
-          utf8_width_print(p->out, w, azData[i]);
-          fputs(i==nColumn-1?"\n":"  ", p->out);
+          w = psx->pHaveWidths[i];
+          if( psx->pSpecWidths[i]<0 ) w = -w;
+          utf8_width_print(psi->out, w, azData[i]);
+          fputs(i==nColumn-1?"\n":"  ", psi->out);
         }
         for(i=0; i<nColumn; i++){
-          print_dashes(p->out, p->actualWidth[i]);
-          fputs(i==nColumn-1?"\n":"  ", p->out);
+          print_dashes(psi->out, psx->pHaveWidths[i]);
+          fputs(i==nColumn-1?"\n":"  ", psi->out);
         }
       }
       break;
@@ -3817,80 +3849,82 @@ static void exec_prepared_stmt_columnar(
     case MODE_Table: {
       colSep = " | ";
       rowSep = " |\n";
-      print_row_separator(p, nColumn, "+");
-      fputs("| ", p->out);
+      print_row_separator(psx, nColumn, "+");
+      fputs("| ", psi->out);
       for(i=0; i<nColumn; i++){
-        w = p->actualWidth[i];
+        w = psx->pHaveWidths[i];
         n = strlenChar(azData[i]);
-        utf8_printf(p->out, "%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, "");
-        fputs(i==nColumn-1?" |\n":" | ", p->out);
+        utf8_printf(psi->out, "%*s%s%*s",
+                    (w-n)/2, "", azData[i], (w-n+1)/2, "");
+        fputs(i==nColumn-1?" |\n":" | ", psi->out);
       }
-      print_row_separator(p, nColumn, "+");
+      print_row_separator(psx, nColumn, "+");
       break;
     }
     case MODE_Markdown: {
       colSep = " | ";
       rowSep = " |\n";
-      fputs("| ", p->out);
+      fputs("| ", psi->out);
       for(i=0; i<nColumn; i++){
-        w = p->actualWidth[i];
+        w = psx->pHaveWidths[i];
         n = strlenChar(azData[i]);
-        utf8_printf(p->out, "%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, "");
-        fputs(i==nColumn-1?" |\n":" | ", p->out);
+        utf8_printf(psi->out, "%*s%s%*s",
+                    (w-n)/2, "", azData[i], (w-n+1)/2, "");
+        fputs(i==nColumn-1?" |\n":" | ", psi->out);
       }
-      print_row_separator(p, nColumn, "|");
+      print_row_separator(psx, nColumn, "|");
       break;
     }
     case MODE_Box: {
       colSep = " " BOX_13 " ";
       rowSep = " " BOX_13 "\n";
-      print_box_row_separator(p, nColumn, BOX_23, BOX_234, BOX_34);
-      utf8_printf(p->out, BOX_13 " ");
+      print_box_row_separator(psx, nColumn, BOX_23, BOX_234, BOX_34);
+      utf8_printf(psi->out, BOX_13 " ");
       for(i=0; i<nColumn; i++){
-        w = p->actualWidth[i];
+        w = psx->pHaveWidths[i];
         n = strlenChar(azData[i]);
-        utf8_printf(p->out, "%*s%s%*s%s",
+        utf8_printf(psi->out, "%*s%s%*s%s",
             (w-n)/2, "", azData[i], (w-n+1)/2, "",
             i==nColumn-1?" "BOX_13"\n":" "BOX_13" ");
       }
-      print_box_row_separator(p, nColumn, BOX_123, BOX_1234, BOX_134);
+      print_box_row_separator(psx, nColumn, BOX_123, BOX_1234, BOX_134);
       break;
     }
   }
   for(i=nColumn, j=0; i<nTotal; i++, j++){
-    if( j==0 && p->cMode!=MODE_Column ){
-      utf8_printf(p->out, "%s", p->cMode==MODE_Box?BOX_13" ":"| ");
+    if( j==0 && psi->cMode!=MODE_Column ){
+      utf8_printf(psi->out, "%s", psi->cMode==MODE_Box?BOX_13" ":"| ");
     }
     z = azData[i];
-    if( z==0 ) z = p->nullValue;
-    w = p->actualWidth[j];
-    if( p->colWidth[j]<0 ) w = -w;
-    utf8_width_print(p->out, w, z);
+    if( z==0 ) z = psi->nullValue;
+    w = psx->pHaveWidths[j];
+    if( psx->pSpecWidths[j]<0 ) w = -w;
+    utf8_width_print(psi->out, w, z);
     if( j==nColumn-1 ){
-      utf8_printf(p->out, "%s", rowSep);
+      utf8_printf(psi->out, "%s", rowSep);
       if( bMultiLineRowExists && abRowDiv[i/nColumn-1] && i+1<nTotal ){
-        if( p->cMode==MODE_Table ){
-          print_row_separator(p, nColumn, "+");
-        }else if( p->cMode==MODE_Box ){
-          print_box_row_separator(p, nColumn, BOX_123, BOX_1234, BOX_134);
-        }else if( p->cMode==MODE_Column ){
-          raw_printf(p->out, "\n");
+        if( psi->cMode==MODE_Table ){
+          print_row_separator(psx, nColumn, "+");
+        }else if( psi->cMode==MODE_Box ){
+          print_box_row_separator(psx, nColumn, BOX_123, BOX_1234, BOX_134);
+        }else if( psi->cMode==MODE_Column ){
+          raw_printf(psi->out, "\n");
         }
       }
       j = -1;
       if( seenInterrupt ) goto columnar_end;
     }else{
-      utf8_printf(p->out, "%s", colSep);
+      utf8_printf(psi->out, "%s", colSep);
     }
   }
-  if( p->cMode==MODE_Table ){
-    print_row_separator(p, nColumn, "+");
-  }else if( p->cMode==MODE_Box ){
-    print_box_row_separator(p, nColumn, BOX_12, BOX_124, BOX_14);
+  if( psi->cMode==MODE_Table ){
+    print_row_separator(psx, nColumn, "+");
+  }else if( psi->cMode==MODE_Box ){
+    print_box_row_separator(psx, nColumn, BOX_12, BOX_124, BOX_14);
   }
 columnar_end:
   if( seenInterrupt ){
-    utf8_printf(p->out, "Interrupt\n");
+    utf8_printf(psi->out, "Interrupt\n");
   }
   nData = (nRow+1)*nColumn;
   for(i=0; i<nData; i++) free(azData[i]);
@@ -3907,13 +3941,14 @@ columnar_end:
 ** Run a prepared statement
 */
 static void exec_prepared_stmt(
-  ShellState *pArg,                                /* Pointer to ShellState */
+  ShellExState *pArg,                              /* Pointer to shell state */
   sqlite3_stmt *pStmt                              /* Statment to run */
 ){
   int rc;
+  ShellInState *psi = ISS(pArg);
   sqlite3_uint64 nRow = 0;
 
-  if( MODE_IS_COLUMNAR(pArg->cMode) ){
+  if( MODE_IS_COLUMNAR(psi->cMode) ){
     exec_prepared_stmt_columnar(pArg, pStmt);
     return;
   }
@@ -3945,7 +3980,7 @@ static void exec_prepared_stmt(
         for(i=0; i<nCol; i++){
           aiTypes[i] = x = sqlite3_column_type(pStmt, i);
           if( x==SQLITE_BLOB && pArg
-           && (pArg->cMode==MODE_Insert || pArg->cMode==MODE_Quote) ){
+           && (psi->cMode==MODE_Insert || psi->cMode==MODE_Quote) ){
             azVals[i] = "";
           }else{
             azVals[i] = (char*)sqlite3_column_text(pStmt, i);
@@ -3967,10 +4002,10 @@ static void exec_prepared_stmt(
         }
       } while( SQLITE_ROW == rc );
       sqlite3_free(pData);
-      if( pArg->cMode==MODE_Json ){
-        fputs("]\n", pArg->out);
-      }else if( pArg->cMode==MODE_Count ){
-        printf("%llu row%s\n", nRow, nRow!=1 ? "s" : "");
+      if( psi->cMode==MODE_Json ){
+        fputs("]\n", psi->out);
+      }else if( psi->cMode==MODE_Count ){
+        utf8_printf(psi->out, "%llu row%s\n", nRow, nRow!=1 ? "s" : "");
       }
     }
   }
@@ -3988,13 +4023,13 @@ static void exec_prepared_stmt(
 ** caller to eventually free this buffer using sqlite3_free().
 */
 static int expertHandleSQL(
-  ShellState *pState,
+  ShellInState *psi,
   const char *zSql,
   char **pzErr
 ){
-  assert( pState->expert.pExpert );
+  assert( psi->expert.pExpert );
   assert( pzErr==0 || *pzErr==0 );
-  return sqlite3_expert_sql(pState->expert.pExpert, zSql, pzErr);
+  return sqlite3_expert_sql(psi->expert.pExpert, zSql, pzErr);
 }
 
 /*
@@ -4008,17 +4043,17 @@ static int expertHandleSQL(
 ** caller to eventually free this buffer using sqlite3_free().
 */
 static int expertFinish(
-  ShellState *pState,
+  ShellInState *psi,
   int bCancel,
   char **pzErr
 ){
   int rc = SQLITE_OK;
-  sqlite3expert *p = pState->expert.pExpert;
+  sqlite3expert *p = psi->expert.pExpert;
   assert( p );
   assert( bCancel || pzErr==0 || *pzErr==0 );
   if( bCancel==0 ){
-    FILE *out = pState->out;
-    int bVerbose = pState->expert.bVerbose;
+    FILE *out = psi->out;
+    int bVerbose = psi->expert.bVerbose;
 
     rc = sqlite3_expert_analyze(p, pzErr);
     if( rc==SQLITE_OK ){
@@ -4045,7 +4080,7 @@ static int expertFinish(
     }
   }
   sqlite3_expert_destroy(p);
-  pState->expert.pExpert = 0;
+  psi->expert.pExpert = 0;
   return rc;
 }
 
@@ -4053,7 +4088,7 @@ static int expertFinish(
 ** Implementation of ".expert" dot command.
 */
 static int expertDotCommand(
-  ShellState *pState,             /* Current shell tool state */
+  ShellInState *psi,              /* Current shell tool state */
   char **azArg,                   /* Array of arguments passed to dot command */
   int nArg                        /* Number of entries in azArg[] */
 ){
@@ -4062,14 +4097,14 @@ static int expertDotCommand(
   int i;
   int iSample = 0;
 
-  if( pState->bSafeMode ){
+  if( psi->bSafeMode ){
     raw_printf(STD_ERR,
                "Cannot run experimental commands such as \"%s\" in safe mode\n",
                azArg[0]);
     return 1;
   }
-  assert( pState->expert.pExpert==0 );
-  memset(&pState->expert, 0, sizeof(ExpertInfo));
+  assert( psi->expert.pExpert==0 );
+  memset(&psi->expert, 0, sizeof(ExpertInfo));
 
   for(i=1; rc==SQLITE_OK && i<nArg; i++){
     char *z = azArg[i];
@@ -4077,7 +4112,7 @@ static int expertDotCommand(
     if( z[0]=='-' && z[1]=='-' ) z++;
     n = strlen30(z);
     if( n>=2 && 0==strncmp(z, "-verbose", n) ){
-      pState->expert.bVerbose = 1;
+      psi->expert.bVerbose = 1;
     }
     else if( n>=2 && 0==strncmp(z, "-sample", n) ){
       if( i==(nArg-1) ){
@@ -4098,14 +4133,14 @@ static int expertDotCommand(
   }
 
   if( rc==SQLITE_OK ){
-    pState->expert.pExpert = sqlite3_expert_new(pState->db, &zErr);
-    if( pState->expert.pExpert==0 ){
+    psi->expert.pExpert = sqlite3_expert_new(DBI(psi), &zErr);
+    if( psi->expert.pExpert==0 ){
       raw_printf(STD_ERR, "sqlite3_expert_new: %s\n",
                  zErr ? zErr : "out of memory");
       rc = SQLITE_ERROR;
     }else{
       sqlite3_expert_config(
-          pState->expert.pExpert, EXPERT_CONFIG_SAMPLE, iSample
+          psi->expert.pExpert, EXPERT_CONFIG_SAMPLE, iSample
       );
     }
   }
@@ -4119,8 +4154,8 @@ static int expertDotCommand(
 ** not about to dumped as a no-op. Someday, a tracing facility may enhance
 ** this function's output to show where the input group has originated.
 */
-static void echo_group_input(ShellState *p, const char *zDo){
-  if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zDo);
+static void echo_group_input(ShellInState *psi, const char *zDo){
+  if( (psi->shellFlgs & SHFLG_Echo)!=0 ) printf("%s\n", zDo);
 }
 
 /*
@@ -4133,24 +4168,25 @@ static void echo_group_input(ShellState *p, const char *zDo){
 ** and callback data argument.
 */
 static int shell_exec(
-  ShellState *pArg,                         /* Pointer to ShellState */
+  ShellExState *psx,                       /* Pointer to shell state */
   const char *zSql,                         /* SQL to be evaluated */
   char **pzErrMsg                           /* Error msg written here */
 ){
+  ShellInState *psi = ISS(psx);
   sqlite3_stmt *pStmt = NULL;     /* Statement to execute. */
   int rc = SQLITE_OK;             /* Return Code */
   int rc2;
   const char *zLeftover;          /* Tail of unprocessed SQL */
-  sqlite3 *db = pArg->db;
+  sqlite3 *db = DBX(psx);
 
   if( pzErrMsg ){
     *pzErrMsg = NULL;
   }
 
 #ifndef SQLITE_OMIT_VIRTUALTABLE
-  if( pArg->expert.pExpert ){
-    rc = expertHandleSQL(pArg, zSql, pzErrMsg);
-    return expertFinish(pArg, (rc!=SQLITE_OK), pzErrMsg);
+  if( psi->expert.pExpert ){
+    rc = expertHandleSQL(psi, zSql, pzErrMsg);
+    return expertFinish(psi, (rc!=SQLITE_OK), pzErrMsg);
   }
 #endif
 
@@ -4172,22 +4208,22 @@ static int shell_exec(
       else zStmtSql = skipWhite(zStmtSql);
 
       /* save off the prepared statment handle and reset row count */
-      if( pArg ){
-        pArg->pStmt = pStmt;
-        pArg->cnt = 0;
+      if( psx ){
+        psi->pStmt = pStmt;
+        psx->resultCount = 0;
       }
 
       /* echo the sql statement if echo on */
-      if( pArg ) echo_group_input( pArg, zStmtSql ? zStmtSql : zSql);
+      if( psx ) echo_group_input( psi, zStmtSql ? zStmtSql : zSql);
 
       /* Show the EXPLAIN QUERY PLAN if .eqp is on */
-      if( pArg && pArg->autoEQP && sqlite3_stmt_isexplain(pStmt)==0 ){
+      if( psx && psi->autoEQP && sqlite3_stmt_isexplain(pStmt)==0 ){
         sqlite3_stmt *pExplain;
         char *zEQP;
         int triggerEQP = 0;
         disable_debug_trace_modes();
         sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, -1, &triggerEQP);
-        if( pArg->autoEQP>=AUTOEQP_trigger ){
+        if( psi->autoEQP>=AUTOEQP_trigger ){
           sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 1, 0);
         }
         zEQP = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", zStmtSql);
@@ -4199,68 +4235,68 @@ static int shell_exec(
             int iEqpId = sqlite3_column_int(pExplain, 0);
             int iParentId = sqlite3_column_int(pExplain, 1);
             if( zEQPLine==0 ) zEQPLine = "";
-            if( zEQPLine[0]=='-' ) eqp_render(pArg);
-            eqp_append(pArg, iEqpId, iParentId, zEQPLine);
+            if( zEQPLine[0]=='-' ) eqp_render(psi);
+            eqp_append(psi, iEqpId, iParentId, zEQPLine);
           }
-          eqp_render(pArg);
+          eqp_render(psi);
         }
         sqlite3_finalize(pExplain);
         sqlite3_free(zEQP);
-        if( pArg->autoEQP>=AUTOEQP_full ){
+        if( psi->autoEQP>=AUTOEQP_full ){
           /* Also do an EXPLAIN for ".eqp full" mode */
           zEQP = sqlite3_mprintf("EXPLAIN %s", zStmtSql);
           shell_check_oom(zEQP);
           rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
           if( rc==SQLITE_OK ){
-            pArg->cMode = MODE_Explain;
-            explain_data_prepare(pArg, pExplain);
-            exec_prepared_stmt(pArg, pExplain);
-            explain_data_delete(pArg);
+            psi->cMode = MODE_Explain;
+            explain_data_prepare(psi, pExplain);
+            exec_prepared_stmt(psx, pExplain);
+            explain_data_delete(psi);
           }
           sqlite3_finalize(pExplain);
           sqlite3_free(zEQP);
         }
-        if( pArg->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){
+        if( psi->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){
           sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 0, 0);
           /* Reprepare pStmt before reactiving trace modes */
           sqlite3_finalize(pStmt);
           sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
-          if( pArg ) pArg->pStmt = pStmt;
+          if( psx ) psi->pStmt = pStmt;
         }
         restore_debug_trace_modes();
       }
 
-      if( pArg ){
-        pArg->cMode = pArg->mode;
-        if( pArg->autoExplain ){
+      if( psx ){
+        psi->cMode = psi->mode;
+        if( psi->autoExplain ){
           if( sqlite3_stmt_isexplain(pStmt)==1 ){
-            pArg->cMode = MODE_Explain;
+            psi->cMode = MODE_Explain;
           }
           if( sqlite3_stmt_isexplain(pStmt)==2 ){
-            pArg->cMode = MODE_EQP;
+            psi->cMode = MODE_EQP;
           }
         }
 
         /* If the shell is currently in ".explain" mode, gather the extra
         ** data required to add indents to the output.*/
-        if( pArg->cMode==MODE_Explain ){
-          explain_data_prepare(pArg, pStmt);
+        if( psi->cMode==MODE_Explain ){
+          explain_data_prepare(psi, pStmt);
         }
       }
 
-      bind_prepared_stmt(pArg->db, pStmt);
-      exec_prepared_stmt(pArg, pStmt);
-      explain_data_delete(pArg);
-      eqp_render(pArg);
+      bind_prepared_stmt(DBX(psx), pStmt);
+      exec_prepared_stmt(psx, pStmt);
+      explain_data_delete(psi);
+      eqp_render(psi);
 
       /* print usage stats if stats on */
-      if( pArg && pArg->statsOn ){
-        display_stats(db, pArg, 0);
+      if( psx && psi->statsOn ){
+        display_stats(db, psi, 0);
       }
 
       /* print loop-counters if required */
-      if( pArg && pArg->scanstatsOn ){
-        display_scanstats(db, pArg);
+      if( psx && psi->scanstatsOn ){
+        display_scanstats(psi);
       }
 
       /* Finalize the statement just executed. If this fails, save a
@@ -4275,8 +4311,8 @@ static int shell_exec(
       }
 
       /* clear saved stmt handle */
-      if( pArg ){
-        pArg->pStmt = NULL;
+      if( psx ){
+        psi->pStmt = NULL;
       }
     }
   } /* end while */
@@ -4298,7 +4334,7 @@ static void freeColumnList(char **azCol){
 
 /*
 ** Return a list of pointers to strings which are the names of all
-** columns in table zTab.   The memory to hold the names is dynamically
+** columns in table zTab. The memory to hold the names is dynamically
 ** allocated and must be released by the caller using a subsequent call
 ** to freeColumnList().
 **
@@ -4309,7 +4345,7 @@ static void freeColumnList(char **azCol){
 ** The first regular column in the table is azCol[1].  The list is terminated
 ** by an entry with azCol[i]==0.
 */
-static char **tableColumnList(ShellState *p, const char *zTab){
+static char **tableColumnList(sqlite3 *db, const char *zTab, int preserveRowid){
   char **azCol = 0;
   sqlite3_stmt *pStmt;
   char *zSql;
@@ -4317,12 +4353,11 @@ static char **tableColumnList(ShellState *p, const char *zTab){
   int nAlloc = 0;
   int nPK = 0;       /* Number of PRIMARY KEY columns seen */
   int isIPK = 0;     /* True if one PRIMARY KEY column of type INTEGER */
-  int preserveRowid = ShellHasFlag(p, SHFLG_PreserveRowid);
   int rc;
 
   zSql = sqlite3_mprintf("PRAGMA table_info=%Q", zTab);
   shell_check_oom(zSql);
-  rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
   sqlite3_free(zSql);
   if( rc ) return 0;
   while( sqlite3_step(pStmt)==SQLITE_ROW ){
@@ -4367,7 +4402,7 @@ static char **tableColumnList(ShellState *p, const char *zTab){
     zSql = sqlite3_mprintf("SELECT 1 FROM pragma_index_list(%Q)"
                            " WHERE origin='pk'", zTab);
     shell_check_oom(zSql);
-    rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+    rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
     sqlite3_free(zSql);
     if( rc ){
       freeColumnList(azCol);
@@ -4391,7 +4426,7 @@ static char **tableColumnList(ShellState *p, const char *zTab){
         ** ordinary column in the table.  Verify that azRowid[j] is a valid
         ** name for the rowid before adding it to azCol[0].  WITHOUT ROWID
         ** tables will fail this last check */
-        rc = sqlite3_table_column_metadata(p->db,0,zTab,azRowid[j],0,0,0,0,0);
+        rc = sqlite3_table_column_metadata(db,0,zTab,azRowid[j],0,0,0,0,0);
         if( rc==SQLITE_OK ) azCol[0] = azRowid[j];
         break;
       }
@@ -4428,7 +4463,8 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){
   const char *zTable;
   const char *zType;
   const char *zSql;
-  ShellState *p = (ShellState *)pArg;
+  ShellInState *psi = (ShellInState *)pArg;
+  ShellExState *psx = XSS(psi);
   int dataOnly;
   int noSys;
 
@@ -4437,33 +4473,33 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){
   zTable = azArg[0];
   zType = azArg[1];
   zSql = azArg[2];
-  dataOnly = (p->shellFlgs & SHFLG_DumpDataOnly)!=0;
-  noSys    = (p->shellFlgs & SHFLG_DumpNoSys)!=0;
+  dataOnly = (psi->shellFlgs & SHFLG_DumpDataOnly)!=0;
+  noSys    = (psi->shellFlgs & SHFLG_DumpNoSys)!=0;
 
   if( strcmp(zTable, "sqlite_sequence")==0 && !noSys ){
-    if( !dataOnly ) raw_printf(p->out, "DELETE FROM sqlite_sequence;\n");
+    if( !dataOnly ) raw_printf(psi->out, "DELETE FROM sqlite_sequence;\n");
   }else if( sqlite3_strglob("sqlite_stat?", zTable)==0 && !noSys ){
-    if( !dataOnly ) raw_printf(p->out, "ANALYZE sqlite_schema;\n");
-  }else if( p->bAllowSysDump==0 && strncmp(zTable, "sqlite_", 7)==0 ){
+    if( !dataOnly ) raw_printf(psi->out, "ANALYZE sqlite_schema;\n");
+  }else if( psi->bAllowSysDump==0 && strncmp(zTable, "sqlite_", 7)==0 ){
     return 0;
   }else if( dataOnly ){
     /* no-op */
   }else if( strncmp(zSql, "CREATE VIRTUAL TABLE", 20)==0 ){
     char *zIns;
-    if( !p->writableSchema ){
-      raw_printf(p->out, "PRAGMA writable_schema=ON;\n");
-      p->writableSchema = 1;
+    if( !psi->writableSchema ){
+      raw_printf(psi->out, "PRAGMA writable_schema=ON;\n");
+      psi->writableSchema = 1;
     }
     zIns = sqlite3_mprintf(
        "INSERT INTO sqlite_schema(type,name,tbl_name,rootpage,sql)"
        "VALUES('table','%q','%q',0,'%q');",
        zTable, zTable, zSql);
     shell_check_oom(zIns);
-    utf8_printf(p->out, "%s\n", zIns);
+    utf8_printf(psi->out, "%s\n", zIns);
     sqlite3_free(zIns);
     return 0;
   }else{
-    printSchemaLine(p->out, zSql, ";\n");
+    printSchemaLine(psi->out, zSql, ";\n");
   }
 
   if( strcmp(zType, "table")==0 ){
@@ -4471,12 +4507,13 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){
     ShellText sTable;
     char **azCol;
     int i;
-    char *savedDestTable;
+    const char *savedDestTable;
     int savedMode;
+    int preserveRowid = (psi->shellFlgs & SHFLG_PreserveRowid)!=0;
 
-    azCol = tableColumnList(p, zTable);
+    azCol = tableColumnList(DBI(psi), zTable, preserveRowid);
     if( azCol==0 ){
-      p->nErr++;
+      psi->nErr++;
       return 0;
     }
 
@@ -4515,22 +4552,22 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){
     appendText(&sSelect, " FROM ", 0);
     appendText(&sSelect, zTable, quoteChar(zTable));
 
-    savedDestTable = p->zDestTable;
-    savedMode = p->mode;
-    p->zDestTable = sTable.z;
-    p->mode = p->cMode = MODE_Insert;
-    rc = shell_exec(p, sSelect.z, 0);
+    savedDestTable = psx->zDestTable;
+    savedMode = psi->mode;
+    psx->zDestTable = sTable.z;
+    psi->mode = psi->cMode = MODE_Insert;
+    rc = shell_exec(psx, sSelect.z, 0);
     if( (rc&0xff)==SQLITE_CORRUPT ){
-      raw_printf(p->out, "/****** CORRUPTION ERROR *******/\n");
-      toggleSelectOrder(p->db);
-      shell_exec(p, sSelect.z, 0);
-      toggleSelectOrder(p->db);
+      raw_printf(psi->out, "/****** CORRUPTION ERROR *******/\n");
+      toggleSelectOrder(DBX(psx));
+      shell_exec(psx, sSelect.z, 0);
+      toggleSelectOrder(DBX(psx));
     }
-    p->zDestTable = savedDestTable;
-    p->mode = savedMode;
+    psx->zDestTable = savedDestTable;
+    psi->mode = savedMode;
     freeText(&sTable);
     freeText(&sSelect);
-    if( rc ) p->nErr++;
+    if( rc ) psi->nErr++;
   }
   return 0;
 }
@@ -4543,27 +4580,27 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){
 ** "ORDER BY rowid DESC" to the end.
 */
 static int run_schema_dump_query(
-  ShellState *p,
+  ShellInState *psi,
   const char *zQuery
 ){
   int rc;
   char *zErr = 0;
-  rc = sqlite3_exec(p->db, zQuery, dump_callback, p, &zErr);
+  rc = sqlite3_exec(DBI(psi), zQuery, dump_callback, psi, &zErr);
   if( rc==SQLITE_CORRUPT ){
     char *zQ2;
     int len = strlen30(zQuery);
-    raw_printf(p->out, "/****** CORRUPTION ERROR *******/\n");
+    raw_printf(psi->out, "/****** CORRUPTION ERROR *******/\n");
     if( zErr ){
-      utf8_printf(p->out, "/****** %s ******/\n", zErr);
+      utf8_printf(psi->out, "/****** %s ******/\n", zErr);
       sqlite3_free(zErr);
       zErr = 0;
     }
     zQ2 = malloc( len+100 );
     if( zQ2==0 ) return rc;
     sqlite3_snprintf(len+100, zQ2, "%s ORDER BY rowid DESC", zQuery);
-    rc = sqlite3_exec(p->db, zQ2, dump_callback, p, &zErr);
+    rc = sqlite3_exec(DBI(psi), zQ2, dump_callback, psi, &zErr);
     if( rc ){
-      utf8_printf(p->out, "/****** ERROR: %s ******/\n", zErr);
+      utf8_printf(psi->out, "/****** ERROR: %s ******/\n", zErr);
     }else{
       rc = SQLITE_CORRUPT;
     }
@@ -4573,8 +4610,9 @@ static int run_schema_dump_query(
   return rc;
 }
 
-/* Configure help text generation to have coalesced secondary
- * help lines with trailing newlines on all help lines.
+/* Configure help text generation to have coalesced secondary help lines
+ * with trailing newlines on all help lines. This allow help text to be
+ * representable as an array of two C-strings per meta-command.
  */
 DISPATCH_CONFIG[
   HELP_COALESCE=1
@@ -4669,7 +4707,7 @@ static int showHelp(FILE *out, const char *zPattern){
 }
 
 /* Forward reference */
-static int process_input(ShellState *p);
+static int process_input(ShellInState *psx);
 
 /*
 ** Read the content of file zName into memory obtained from sqlite3_malloc64()
@@ -4729,9 +4767,9 @@ static void session_close(OpenSession *pSession){
 ** Close all OpenSession objects and release all associated resources.
 */
 #if defined(SQLITE_ENABLE_SESSION)
-static void session_close_all(ShellState *p, int i){
+static void session_close_all(ShellInState *psi, int i){
   int j;
-  struct AuxDb *pAuxDb = i<0 ? p->pAuxDb : &p->aAuxDb[i];
+  struct AuxDb *pAuxDb = i<0 ? psi->pAuxDb : &psi->aAuxDb[i];
   for(j=0; j<pAuxDb->nSession; j++){
     session_close(&pAuxDb->aSession[j]);
   }
@@ -4806,7 +4844,7 @@ u8 deduceDatabaseType(const char *zName, int dfltZip){
 ** program.  Read content from the file in p->aAuxDb[].zDbFilename.
 ** If p->aAuxDb[].zDbFilename is 0, then read from the present input.
 */
-static unsigned char *readHexDb(ShellState *p, int *pnData){
+static unsigned char *readHexDb(ShellInState *psi, int *pnData){
   unsigned char *a = 0;
   int n = 0;
   int pgsz = 0;
@@ -4814,9 +4852,9 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){
   int j, k, nlError;
   int rc;
   static const char *zEndMarker = "| end ";
-  const char *zDbFilename = p->pAuxDb->zDbFilename;
+  const char *zDbFilename = psi->pAuxDb->zDbFilename;
   /* Need next two objects only if redirecting input to get the hex. */
-  InSource inRedir = INSOURCE_FILE_REDIR(0, zDbFilename, p->pInSource);
+  InSource inRedir = INSOURCE_FILE_REDIR(0, zDbFilename, psi->pInSource);
   unsigned int x[16];
   char zLine[1000];
   if( zDbFilename ){
@@ -4825,17 +4863,17 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){
       utf8_printf(STD_ERR, "cannot open \"%s\" for reading\n", zDbFilename);
       return 0;
     }
-    p->pInSource = &inRedir;
+    psi->pInSource = &inRedir;
   }else{
     /* Will read hex DB lines inline from present input, without redirect. */
-    if( INSOURCE_IS_INTERACTIVE(p->pInSource) ){
+    if( INSOURCE_IS_INTERACTIVE(psi->pInSource) ){
       printf("Reading hex DB from \"%s\", until end-marker input like:\n%s\n",
-             p->pInSource->zSourceSay, zEndMarker);
+             psi->pInSource->zSourceSay, zEndMarker);
       fflush(STD_OUT);
     }
   }
   *pnData = 0;
-  if( str_line_get(zLine,sizeof(zLine), p->pInSource)==0 ) goto readHexDb_error;
+  if( strLineGet(zLine,sizeof(zLine), psi->pInSource)==0 ) goto readHexDb_error;
   rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz);
   if( rc!=2 ) goto readHexDb_error;
   if( n<0 ) goto readHexDb_error;
@@ -4848,7 +4886,7 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){
     utf8_printf(STD_ERR, "invalid pagesize\n");
     goto readHexDb_error;
   }
-  while( str_line_get(zLine,sizeof(zLine), p->pInSource)!=0 ){
+  while( strLineGet(zLine,sizeof(zLine), psi->pInSource)!=0 ){
     rc = sscanf(zLine, "| page %d offset %d", &j, &k);
     if( rc==2 ){
       iOffset = k;
@@ -4870,21 +4908,19 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){
   }
   *pnData = n; /* Record success and size. */
  readHexDb_cleanup:
-  if( p->pInSource==&inRedir ){
+  if( psi->pInSource==&inRedir ){
     fclose( inRedir.inFile );
-    p->pInSource = inRedir.pFrom;
+    psi->pInSource = inRedir.pFrom;
   }
   return a;
 
  readHexDb_error:
-  nlError = p->pInSource->lineno;
-  if( p->pInSource!=&inRedir ){
+  nlError = psi->pInSource->lineno;
+  if( psi->pInSource!=&inRedir ){
     /* Since taking input inline, consume through its end marker. */
-    while( str_line_get(zLine, sizeof(zLine), p->pInSource)!=0 ){
-
+    while( strLineGet(zLine, sizeof(zLine), psi->pInSource)!=0 ){
       if(strncmp(zLine, zEndMarker, 6)==0 ) break;
     }
-
   }
   sqlite3_free(a);
   a = 0;
@@ -5062,127 +5098,135 @@ static void shellEscapeCrnl(
 ** Make sure the database is open.  If it is not, then open it.  If
 ** the database fails to open, print an error message and exit.
 */
-static void open_db(ShellState *p, int openFlags){
-  if( p->db==0 ){
-    const char *zDbFilename = p->pAuxDb->zDbFilename;
-    if( p->openMode==SHELL_OPEN_UNSPEC ){
+static void open_db(ShellExState *psx, int openFlags){
+  ShellInState *psi = ISS(psx);
+  if( DBX(psx)==0 ){
+    sqlite3 **pDb = &DBX(psx);
+    const char *zDbFilename = psi->pAuxDb->zDbFilename;
+    if( psi->openMode==SHELL_OPEN_UNSPEC ){
       if( zDbFilename==0 || zDbFilename[0]==0 ){
-        p->openMode = SHELL_OPEN_NORMAL;
+        psi->openMode = SHELL_OPEN_NORMAL;
       }else{
-        p->openMode = deduceDatabaseType(p->pAuxDb->zDbFilename,
-                                         (openFlags & OPEN_DB_ZIPFILE)!=0);
+        psi->openMode = deduceDatabaseType(psi->pAuxDb->zDbFilename,
+                                           (openFlags & OPEN_DB_ZIPFILE)!=0);
       }
     }
-    switch( p->openMode ){
+    switch( psi->openMode ){
       case SHELL_OPEN_APPENDVFS: {
-        sqlite3_open_v2(zDbFilename, &p->db,
-           SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, "apndvfs");
+        sqlite3_open_v2
+          (zDbFilename, pDb,
+           SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|psi->openFlags,
+           "apndvfs");
         break;
       }
       case SHELL_OPEN_HEXDB:
       case SHELL_OPEN_DESERIALIZE: {
-        sqlite3_open(0, &p->db);
+        sqlite3_open(0, pDb);
         break;
       }
       case SHELL_OPEN_ZIPFILE: {
-        sqlite3_open(":memory:", &p->db);
+        sqlite3_open(":memory:", pDb);
         break;
       }
       case SHELL_OPEN_READONLY: {
-        sqlite3_open_v2(zDbFilename, &p->db,
-            SQLITE_OPEN_READONLY|p->openFlags, 0);
+        sqlite3_open_v2(zDbFilename, pDb,
+            SQLITE_OPEN_READONLY|psi->openFlags, 0);
         break;
       }
       case SHELL_OPEN_UNSPEC:
       case SHELL_OPEN_NORMAL: {
-        sqlite3_open_v2(zDbFilename, &p->db,
-           SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, 0);
+        sqlite3_open_v2(zDbFilename, pDb,
+           SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|psi->openFlags, 0);
         break;
       }
     }
-    globalDb = p->db;
-    if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){
+    globalDb = DBX(psx);
+    if( DBX(psx)==0 || SQLITE_OK!=sqlite3_errcode(DBX(psx)) ){
+      const char *zWhy = (DBX(psx)==0)? "(?)" : sqlite3_errmsg(DBX(psx));
       utf8_printf(STD_ERR,"Error: unable to open database \"%s\": %s\n",
-          zDbFilename, sqlite3_errmsg(p->db));
+          zDbFilename, zWhy);
       if( openFlags & OPEN_DB_KEEPALIVE ){
-        sqlite3_open(":memory:", &p->db);
+        sqlite3_open(":memory:", pDb);
+        globalDb = *pDb;
         return;
       }
       exit(1);
     }
 #ifndef SQLITE_OMIT_LOAD_EXTENSION
-    sqlite3_enable_load_extension(p->db, 1);
-#endif
-    sqlite3_fileio_init(p->db, 0, 0);
-    sqlite3_shathree_init(p->db, 0, 0);
-    sqlite3_completion_init(p->db, 0, 0);
-    sqlite3_uint_init(p->db, 0, 0);
-    sqlite3_decimal_init(p->db, 0, 0);
-    sqlite3_regexp_init(p->db, 0, 0);
-    sqlite3_ieee_init(p->db, 0, 0);
-    sqlite3_series_init(p->db, 0, 0);
+    sqlite3_enable_load_extension(globalDb, 1);
+#endif
+    sqlite3_fileio_init(globalDb, 0, 0);
+    sqlite3_shathree_init(globalDb, 0, 0);
+    sqlite3_completion_init(globalDb, 0, 0);
+    sqlite3_uint_init(globalDb, 0, 0);
+    sqlite3_decimal_init(globalDb, 0, 0);
+    sqlite3_regexp_init(globalDb, 0, 0);
+    sqlite3_ieee_init(globalDb, 0, 0);
+    sqlite3_series_init(globalDb, 0, 0);
 #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
-    sqlite3_dbdata_init(p->db, 0, 0);
+    sqlite3_dbdata_init(globalDb, 0, 0);
 #endif
 #ifdef SQLITE_HAVE_ZLIB
-    sqlite3_zipfile_init(p->db, 0, 0);
-    sqlite3_sqlar_init(p->db, 0, 0);
+    sqlite3_zipfile_init(globalDb, 0, 0);
+    sqlite3_sqlar_init(globalDb, 0, 0);
 #endif
-    sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0,
+    sqlite3_create_function(globalDb, "shell_add_schema", 3, SQLITE_UTF8, 0,
                             shellAddSchemaName, 0, 0);
-    sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0,
+    sqlite3_create_function(globalDb, "shell_module_schema", 1, SQLITE_UTF8, 0,
                             shellModuleSchema, 0, 0);
-    sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p,
+    sqlite3_create_function(globalDb, "shell_putsnl", 1, SQLITE_UTF8, psx,
                             shellPutsFunc, 0, 0);
-    sqlite3_create_function(p->db, "shell_escape_crnl", 1, SQLITE_UTF8, 0,
+    sqlite3_create_function(globalDb, "shell_escape_crnl", 1, SQLITE_UTF8, 0,
                             shellEscapeCrnl, 0, 0);
-    sqlite3_create_function(p->db, "shell_int32", 2, SQLITE_UTF8, 0,
+    sqlite3_create_function(globalDb, "shell_int32", 2, SQLITE_UTF8, 0,
                             shellInt32, 0, 0);
-    sqlite3_create_function(p->db, "shell_idquote", 1, SQLITE_UTF8, 0,
+    sqlite3_create_function(globalDb, "shell_idquote", 1, SQLITE_UTF8, 0,
                             shellIdQuote, 0, 0);
-    sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0,
+    sqlite3_create_function(globalDb, "usleep",1,SQLITE_UTF8, 0,
                             shellUSleepFunc, 0, 0);
 #ifndef SQLITE_NOHAVE_SYSTEM
-    sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0,
+    sqlite3_create_function(globalDb, "edit", 1, SQLITE_UTF8, 0,
                             editFunc, 0, 0);
-    sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0,
+    sqlite3_create_function(globalDb, "edit", 2, SQLITE_UTF8, 0,
                             editFunc, 0, 0);
 #endif
-    if( p->openMode==SHELL_OPEN_ZIPFILE ){
+    if( psi->openMode==SHELL_OPEN_ZIPFILE ){
       char *zSql = sqlite3_mprintf(
          "CREATE VIRTUAL TABLE zip USING zipfile(%Q);", zDbFilename);
       shell_check_oom(zSql);
-      sqlite3_exec(p->db, zSql, 0, 0, 0);
+      sqlite3_exec(DBX(psx), zSql, 0, 0, 0);
       sqlite3_free(zSql);
     }
 #ifndef SQLITE_OMIT_DESERIALIZE
     else
-    if( p->openMode==SHELL_OPEN_DESERIALIZE || p->openMode==SHELL_OPEN_HEXDB ){
+    if( psi->openMode==SHELL_OPEN_DESERIALIZE
+        || psi->openMode==SHELL_OPEN_HEXDB ){
       int rc;
       int nData = 0;
       unsigned char *aData;
-      if( p->openMode==SHELL_OPEN_DESERIALIZE ){
+      if( psi->openMode==SHELL_OPEN_DESERIALIZE ){
         aData = (unsigned char*)readFile(zDbFilename, &nData);
       }else{
-        aData = readHexDb(p, &nData);
+        aData = readHexDb(psi, &nData);
         if( aData==0 ){
           return;
         }
       }
-      rc = sqlite3_deserialize(p->db, "main", aData, nData, nData,
+      rc = sqlite3_deserialize(DBX(psx), "main", aData, nData, nData,
                    SQLITE_DESERIALIZE_RESIZEABLE |
                    SQLITE_DESERIALIZE_FREEONCLOSE);
       if( rc ){
         utf8_printf(STD_ERR, "Error: sqlite3_deserialize() returns %d\n", rc);
       }
-      if( p->szMax>0 ){
-        sqlite3_file_control(p->db, "main", SQLITE_FCNTL_SIZE_LIMIT, &p->szMax);
+      if( psi->szMax>0 ){
+        sqlite3_file_control(DBX(psx), "main", SQLITE_FCNTL_SIZE_LIMIT,
+                             &psi->szMax);
       }
     }
 #endif
   }
-  if( p->bSafeModeFuture && p->db!=0 ){
-    sqlite3_set_authorizer(p->db, safeModeAuth, p);
+  if( psi->bSafeModeFuture && DBX(psx)!=0 ){
+    sqlite3_set_authorizer(DBX(psx), safeModeAuth, psx);
   }
 }
 
@@ -5365,16 +5409,16 @@ static int booleanValue(const char *zArg){
 /*
 ** Set or clear a shell flag according to a boolean value.
 */
-static void setOrClearFlag(ShellState *p, unsigned mFlag, const char *zArg){
+static void setOrClearFlag(ShellExState *psx, unsigned mFlag, const char *zArg){
   if( booleanValue(zArg) ){
-    ShellSetFlag(p, mFlag);
+    ShellSetFlag(psx, mFlag);
   }else{
-    ShellClearFlag(p, mFlag);
+    ShellClearFlag(psx, mFlag);
   }
 }
 
 /*
-** Close an output file, assuming it is not stderr or stdout
+** Close an output file, provided it is not stderr or stdout
 */
 static void output_file_close(FILE *f){
   if( f && f!=STD_OUT && f!=STD_ERR ) fclose(f);
@@ -5408,24 +5452,24 @@ static FILE *output_file_open(const char *zFile, int bTextMode){
 */
 static int sql_trace_callback(
   unsigned mType,         /* The trace type */
-  void *pArg,             /* The ShellState pointer */
+  void *pArg,             /* The shell state pointer */
   void *pP,               /* Usually a pointer to sqlite_stmt */
   void *pX                /* Auxiliary output */
 ){
-  ShellState *p = (ShellState*)pArg;
+  ShellInState *psi = (ShellInState*)pArg;
   sqlite3_stmt *pStmt;
   const char *zSql;
   int nSql;
-  if( p->traceOut==0 ) return 0;
+  if( psi->traceOut==0 ) return 0;
   if( mType==SQLITE_TRACE_CLOSE ){
-    utf8_printf(p->traceOut, "-- closing database connection\n");
+    utf8_printf(psi->traceOut, "-- closing database connection\n");
     return 0;
   }
   if( mType!=SQLITE_TRACE_ROW && ((const char*)pX)[0]=='-' ){
     zSql = (const char*)pX;
   }else{
     pStmt = (sqlite3_stmt*)pP;
-    switch( p->eTraceType ){
+    switch( psi->eTraceType ){
       case SHELL_TRACE_EXPANDED: {
         zSql = sqlite3_expanded_sql(pStmt);
         break;
@@ -5448,12 +5492,12 @@ static int sql_trace_callback(
   switch( mType ){
     case SQLITE_TRACE_ROW:
     case SQLITE_TRACE_STMT: {
-      utf8_printf(p->traceOut, "%.*s;\n", nSql, zSql);
+      utf8_printf(psi->traceOut, "%.*s;\n", nSql, zSql);
       break;
     }
     case SQLITE_TRACE_PROFILE: {
       sqlite3_int64 nNanosec = *(sqlite3_int64*)pX;
-      utf8_printf(p->traceOut, "%.*s; -- %lld ns\n", nSql, zSql, nNanosec);
+      utf8_printf(psi->traceOut, "%.*s; -- %lld ns\n", nSql, zSql, nNanosec);
       break;
     }
   }
@@ -5641,7 +5685,7 @@ static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p){
 ** work for WITHOUT ROWID tables.
 */
 static void tryToCloneData(
-  ShellState *p,
+  ShellExState *psx,
   sqlite3 *newDb,
   const char *zTable
 ){
@@ -5658,10 +5702,10 @@ static void tryToCloneData(
 
   zQuery = sqlite3_mprintf("SELECT * FROM \"%w\"", zTable);
   shell_check_oom(zQuery);
-  rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0);
+  rc = sqlite3_prepare_v2(DBX(psx), zQuery, -1, &pQuery, 0);
   if( rc ){
     utf8_printf(STD_ERR, "Error %d: %s on [%s]\n",
-            sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db),
+            sqlite3_extended_errcode(DBX(psx)), sqlite3_errmsg(DBX(psx)),
             zQuery);
     goto end_data_xfer;
   }
@@ -5731,7 +5775,7 @@ static void tryToCloneData(
     zQuery = sqlite3_mprintf("SELECT * FROM \"%w\" ORDER BY rowid DESC;",
                              zTable);
     shell_check_oom(zQuery);
-    rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0);
+    rc = sqlite3_prepare_v2(DBX(psx), zQuery, -1, &pQuery, 0);
     if( rc ){
       utf8_printf(STD_ERR, "Warning: cannot step \"%s\" backwards", zTable);
       break;
@@ -5753,10 +5797,10 @@ end_data_xfer:
 ** sqlite_schema table, try again moving backwards.
 */
 static void tryToCloneSchema(
-  ShellState *p,
+  ShellExState *psx,
   sqlite3 *newDb,
   const char *zWhere,
-  void (*xForEach)(ShellState*,sqlite3*,const char*)
+  void (*xForEach)(ShellExState*,sqlite3*,const char*)
 ){
   sqlite3_stmt *pQuery = 0;
   char *zQuery = 0;
@@ -5768,17 +5812,18 @@ static void tryToCloneSchema(
   zQuery = sqlite3_mprintf("SELECT name, sql FROM sqlite_schema"
                            " WHERE %s", zWhere);
   shell_check_oom(zQuery);
-  rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0);
+  rc = sqlite3_prepare_v2(DBX(psx), zQuery, -1, &pQuery, 0);
   if( rc ){
     utf8_printf(STD_ERR, "Error: (%d) %s on [%s]\n",
-                    sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db),
-                    zQuery);
+                sqlite3_extended_errcode(DBX(psx)),
+                sqlite3_errmsg(DBX(psx)), zQuery);
     goto end_schema_xfer;
   }
   while( sqlite3_step(pQuery)==SQLITE_ROW ){
     zName = sqlite3_column_text(pQuery, 0);
     zSql = sqlite3_column_text(pQuery, 1);
     if( zName==0 || zSql==0 ) continue;
+    /* Consider directing this output to current output. */
     fprintf(STD_OUT, "%s... ", zName); fflush(STD_OUT);
     sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg);
     if( zErrMsg ){
@@ -5787,8 +5832,9 @@ static void tryToCloneSchema(
       zErrMsg = 0;
     }
     if( xForEach ){
-      xForEach(p, newDb, (const char*)zName);
+      xForEach(psx, newDb, (const char*)zName);
     }
+    /* Consider directing this output to current output. */
     fprintf(STD_OUT, "done\n");
   }
   if( rc!=SQLITE_DONE ){
@@ -5797,17 +5843,18 @@ static void tryToCloneSchema(
     zQuery = sqlite3_mprintf("SELECT name, sql FROM sqlite_schema"
                              " WHERE %s ORDER BY rowid DESC", zWhere);
     shell_check_oom(zQuery);
-    rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0);
+    rc = sqlite3_prepare_v2(DBX(psx), zQuery, -1, &pQuery, 0);
     if( rc ){
       utf8_printf(STD_ERR, "Error: (%d) %s on [%s]\n",
-                      sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db),
-                      zQuery);
+                  sqlite3_extended_errcode(DBX(psx)),
+                  sqlite3_errmsg(DBX(psx)), zQuery);
       goto end_schema_xfer;
     }
     while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){
       zName = sqlite3_column_text(pQuery, 0);
       zSql = sqlite3_column_text(pQuery, 1);
       if( zName==0 || zSql==0 ) continue;
+      /* Consider directing ... */
       fprintf(STD_OUT, "%s... ", zName); fflush(STD_OUT);
       sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg);
       if( zErrMsg ){
@@ -5816,8 +5863,9 @@ static void tryToCloneSchema(
         zErrMsg = 0;
       }
       if( xForEach ){
-        xForEach(p, newDb, (const char*)zName);
+        xForEach(psx, newDb, (const char*)zName);
       }
+      /* Consider directing ... */
       fprintf(STD_OUT, "done\n");
     }
   }
@@ -5831,7 +5879,7 @@ end_schema_xfer:
 ** as possible out of the main database (which might be corrupt) and write it
 ** into zNewDb.
 */
-static void tryToClone(ShellState *p, const char *zNewDb){
+static void tryToClone(ShellExState *psx, const char *zNewDb){
   int rc;
   sqlite3 *newDb = 0;
   if( access(zNewDb,0)==0 ){
@@ -5843,12 +5891,12 @@ static void tryToClone(ShellState *p, const char *zNewDb){
     utf8_printf(STD_ERR, "Cannot create output database: %s\n",
             sqlite3_errmsg(newDb));
   }else{
-    sqlite3_exec(p->db, "PRAGMA writable_schema=ON;", 0, 0, 0);
+    sqlite3_exec(DBX(psx), "PRAGMA writable_schema=ON;", 0, 0, 0);
     sqlite3_exec(newDb, "BEGIN EXCLUSIVE;", 0, 0, 0);
-    tryToCloneSchema(p, newDb, "type='table'", tryToCloneData);
-    tryToCloneSchema(p, newDb, "type!='table'", 0);
+    tryToCloneSchema(psx, newDb, "type='table'", tryToCloneData);
+    tryToCloneSchema(psx, newDb, "type!='table'", 0);
     sqlite3_exec(newDb, "COMMIT;", 0, 0, 0);
-    sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0);
+    sqlite3_exec(DBX(psx), "PRAGMA writable_schema=OFF;", 0, 0, 0);
   }
   close_db(newDb);
 }
@@ -5856,19 +5904,19 @@ static void tryToClone(ShellState *p, const char *zNewDb){
 /*
 ** Change the output file back to stdout.
 **
-** If the p->doXdgOpen flag is set, that means the output was being
-** redirected to a temporary file named by p->zTempFile.  In that case,
+** If the psi->doXdgOpen flag is set, that means the output was being
+** redirected to a temporary file named by psi->zTempFile.  In that case,
 ** launch start/open/xdg-open on that temporary file.
 */
-static void output_reset(ShellState *p){
-  if( p->outfile[0]=='|' ){
+static void output_reset(ShellInState *psi){
+  if( psi->outfile[0]=='|' ){
 #ifndef SQLITE_OMIT_POPEN
-    pclose(p->out);
+    pclose(psi->out);
 #endif
   }else{
-    output_file_close(p->out);
+    output_file_close(psi->out);
 #ifndef SQLITE_NOHAVE_SYSTEM
-    if( p->doXdgOpen ){
+    if( psi->doXdgOpen ){
       const char *zXdgOpenCmd =
 #if defined(_WIN32)
       "start";
@@ -5878,23 +5926,23 @@ static void output_reset(ShellState *p){
       "xdg-open";
 #endif
       char *zCmd;
-      zCmd = sqlite3_mprintf("%s %s", zXdgOpenCmd, p->zTempFile);
+      zCmd = sqlite3_mprintf("%s %s", zXdgOpenCmd, psi->zTempFile);
       if( system(zCmd) ){
         utf8_printf(STD_ERR, "Failed: [%s]\n", zCmd);
       }else{
         /* Give the start/open/xdg-open command some time to get
         ** going before we continue, and potential delete the
-        ** p->zTempFile data file out from under it */
+        ** psi->zTempFile data file out from under it */
         sqlite3_sleep(2000);
       }
       sqlite3_free(zCmd);
-      outputModePop(p);
-      p->doXdgOpen = 0;
+      outputModePop(psi);
+      psi->doXdgOpen = 0;
     }
 #endif /* !defined(SQLITE_NOHAVE_SYSTEM) */
   }
-  p->outfile[0] = 0;
-  p->out = STD_OUT;
+  psi->outfile[0] = 0;
+  psi->out = STD_OUT;
 }
 
 /*
@@ -5947,7 +5995,7 @@ static unsigned int get4byteInt(unsigned char *a){
 **
 ** Return 1 on error, 2 to exit, and 0 otherwise.
 */
-static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){
+static int shell_dbinfo_command(ShellExState *psx, int nArg, char **azArg){
   static const struct { const char *zName; int ofst; } aField[] = {
      { "file change counter:",  24  },
      { "database page count:",  28  },
@@ -5980,13 +6028,15 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){
   char *zDb = nArg>=2 ? azArg[1] : "main";
   sqlite3_stmt *pStmt = 0;
   unsigned char aHdr[100];
-  open_db(p, 0);
-  if( p->db==0 ) return 1;
-  rc = sqlite3_prepare_v2(p->db,
+  FILE *out = ISS(psx)->out;
+
+  open_db(psx, 0);
+  if( DBX(psx)==0 ) return 1;
+  rc = sqlite3_prepare_v2(DBX(psx),
              "SELECT data FROM sqlite_dbpage(?1) WHERE pgno=1",
              -1, &pStmt, 0);
   if( rc ){
-    utf8_printf(STD_ERR, "error: %s\n", sqlite3_errmsg(p->db));
+    utf8_printf(STD_ERR, "error: %s\n", sqlite3_errmsg(DBX(psx)));
     sqlite3_finalize(pStmt);
     return 1;
   }
@@ -6003,22 +6053,22 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){
   }
   i = get2byteInt(aHdr+16);
   if( i==1 ) i = 65536;
-  utf8_printf(p->out, "%-20s %d\n", "database page size:", i);
-  utf8_printf(p->out, "%-20s %d\n", "write format:", aHdr[18]);
-  utf8_printf(p->out, "%-20s %d\n", "read format:", aHdr[19]);
-  utf8_printf(p->out, "%-20s %d\n", "reserved bytes:", aHdr[20]);
+  utf8_printf(out, "%-20s %d\n", "database page size:", i);
+  utf8_printf(out, "%-20s %d\n", "write format:", aHdr[18]);
+  utf8_printf(out, "%-20s %d\n", "read format:", aHdr[19]);
+  utf8_printf(out, "%-20s %d\n", "reserved bytes:", aHdr[20]);
   for(i=0; i<ArraySize(aField); i++){
     int ofst = aField[i].ofst;
     unsigned int val = get4byteInt(aHdr + ofst);
-    utf8_printf(p->out, "%-20s %u", aField[i].zName, val);
+    utf8_printf(out, "%-20s %u", aField[i].zName, val);
     switch( ofst ){
       case 56: {
-        if( val==1 ) raw_printf(p->out, " (utf8)");
-        if( val==2 ) raw_printf(p->out, " (utf16le)");
-        if( val==3 ) raw_printf(p->out, " (utf16be)");
+        if( val==1 ) raw_printf(out, " (utf8)");
+        if( val==2 ) raw_printf(out, " (utf16le)");
+        if( val==3 ) raw_printf(out, " (utf16be)");
       }
     }
-    raw_printf(p->out, "\n");
+    raw_printf(out, "\n");
   }
   if( zDb==0 ){
     zSchemaTab = sqlite3_mprintf("main.sqlite_schema");
@@ -6029,13 +6079,13 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){
   }
   for(i=0; i<ArraySize(aQuery); i++){
     char *zSql = sqlite3_mprintf(aQuery[i].zSql, zSchemaTab);
-    int val = db_int(p->db, zSql);
+    int val = db_int(DBX(psx), zSql);
     sqlite3_free(zSql);
-    utf8_printf(p->out, "%-20s %d\n", aQuery[i].zName, val);
+    utf8_printf(out, "%-20s %d\n", aQuery[i].zName, val);
   }
   sqlite3_free(zSchemaTab);
-  sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion);
-  utf8_printf(p->out, "%-20s %u\n", "data version", iDataVersion);
+  sqlite3_file_control(DBX(psx), zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion);
+  utf8_printf(out, "%-20s %u\n", "data version", iDataVersion);
   return 0;
 }
 
@@ -6174,26 +6224,27 @@ int shellDeleteFile(const char *zFilename){
 ** Try to delete the temporary file (if there is one) and free the
 ** memory used to hold the name of the temp file.
 */
-static void clearTempFile(ShellState *p){
-  if( p->zTempFile==0 ) return;
-  if( p->doXdgOpen ) return;
-  if( shellDeleteFile(p->zTempFile) ) return;
-  sqlite3_free(p->zTempFile);
-  p->zTempFile = 0;
+static void clearTempFile(ShellInState *psi){
+  if( psi->zTempFile==0 ) return;
+  if( psi->doXdgOpen ) return;
+  if( shellDeleteFile(psi->zTempFile) ) return;
+  sqlite3_free(psi->zTempFile);
+  psi->zTempFile = 0;
 }
 
 /*
 ** Create a new temp file name with the given suffix.
 */
-static void newTempFile(ShellState *p, const char *zSuffix){
-  clearTempFile(p);
-  sqlite3_free(p->zTempFile);
-  p->zTempFile = 0;
-  if( p->db ){
-    sqlite3_file_control(p->db, 0, SQLITE_FCNTL_TEMPFILENAME, &p->zTempFile);
+static void newTempFile(ShellInState *psi, const char *zSuffix){
+  clearTempFile(psi);
+  sqlite3_free(psi->zTempFile);
+  psi->zTempFile = 0;
+  if( DBI(psi) ){
+    sqlite3_file_control(DBI(psi), 0, SQLITE_FCNTL_TEMPFILENAME,
+                         &psi->zTempFile);
   }
-  if( p->zTempFile==0 ){
-    /* If p->db is an in-memory database then the TEMPFILENAME file-control
+  if( psi->zTempFile==0 ){
+    /* If DB is an in-memory database then the TEMPFILENAME file-control
     ** will not work and we will need to fallback to guessing */
     char *zTemp;
     sqlite3_uint64 r;
@@ -6207,11 +6258,11 @@ static void newTempFile(ShellState *p, const char *zSuffix){
       zTemp = "/tmp";
 #endif
     }
-    p->zTempFile = sqlite3_mprintf("%s/temp%llx.%s", zTemp, r, zSuffix);
+    psi->zTempFile = sqlite3_mprintf("%s/temp%llx.%s", zTemp, r, zSuffix);
   }else{
-    p->zTempFile = sqlite3_mprintf("%z.%s", p->zTempFile, zSuffix);
+    psi->zTempFile = sqlite3_mprintf("%z.%s", psi->zTempFile, zSuffix);
   }
-  shell_check_oom(p->zTempFile);
+  shell_check_oom(psi->zTempFile);
 }
 
 /*
@@ -6266,183 +6317,6 @@ static void shellFkeyCollateClause(
   }
 }
 
-
-/*
-** The implementation of dot-command ".lint fkey-indexes".
-*/
-static int lintFkeyIndexes(
-  ShellState *pState,             /* Current shell tool state */
-  char **azArg,                   /* Array of arguments passed to dot command */
-  int nArg                        /* Number of entries in azArg[] */
-){
-  sqlite3 *db = pState->db;       /* Database handle to query "main" db of */
-  FILE *out = pState->out;        /* Stream to write non-error output to */
-  int bVerbose = 0;               /* If -verbose is present */
-  int bGroupByParent = 0;         /* If -groupbyparent is present */
-  int i;                          /* To iterate through azArg[] */
-  const char *zIndent = "";       /* How much to indent CREATE INDEX by */
-  int rc;                         /* Return code */
-  sqlite3_stmt *pSql = 0;         /* Compiled version of SQL statement below */
-
-  /*
-  ** This SELECT statement returns one row for each foreign key constraint
-  ** in the schema of the main database. The column values are:
-  **
-  ** 0. The text of an SQL statement similar to:
-  **
-  **      "EXPLAIN QUERY PLAN SELECT 1 FROM child_table WHERE child_key=?"
-  **
-  **    This SELECT is similar to the one that the foreign keys implementation
-  **    needs to run internally on child tables. If there is an index that can
-  **    be used to optimize this query, then it can also be used by the FK
-  **    implementation to optimize DELETE or UPDATE statements on the parent
-  **    table.
-  **
-  ** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by
-  **    the EXPLAIN QUERY PLAN command matches this pattern, then the schema
-  **    contains an index that can be used to optimize the query.
-  **
-  ** 2. Human readable text that describes the child table and columns. e.g.
-  **
-  **       "child_table(child_key1, child_key2)"
-  **
-  ** 3. Human readable text that describes the parent table and columns. e.g.
-  **
-  **       "parent_table(parent_key1, parent_key2)"
-  **
-  ** 4. A full CREATE INDEX statement for an index that could be used to
-  **    optimize DELETE or UPDATE statements on the parent table. e.g.
-  **
-  **       "CREATE INDEX child_table_child_key ON child_table(child_key)"
-  **
-  ** 5. The name of the parent table.
-  **
-  ** These six values are used by the C logic below to generate the report.
-  */
-  const char *zSql =
-  "SELECT "
-    "     'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '"
-    "  || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' "
-    "  || fkey_collate_clause("
-    "       f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')"
-    ", "
-    "     'SEARCH ' || s.name || ' USING COVERING INDEX*('"
-    "  || group_concat('*=?', ' AND ') || ')'"
-    ", "
-    "     s.name  || '(' || group_concat(f.[from],  ', ') || ')'"
-    ", "
-    "     f.[table] || '(' || group_concat(COALESCE(f.[to], p.[name])) || ')'"
-    ", "
-    "     'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))"
-    "  || ' ON ' || quote(s.name) || '('"
-    "  || group_concat(quote(f.[from]) ||"
-    "        fkey_collate_clause("
-    "          f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')"
-    "  || ');'"
-    ", "
-    "     f.[table] "
-    "FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f "
-    "LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) "
-    "GROUP BY s.name, f.id "
-    "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)"
-  ;
-  const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)";
-
-  for(i=2; i<nArg; i++){
-    int n = strlen30(azArg[i]);
-    if( n>1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){
-      bVerbose = 1;
-    }
-    else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){
-      bGroupByParent = 1;
-      zIndent = "    ";
-    }
-    else{
-      raw_printf(STD_ERR, "Usage: %s %s ?-verbose? ?-groupbyparent?\n",
-          azArg[0], azArg[1]
-      );
-      return SQLITE_ERROR;
-    }
-  }
-
-  /* Register the fkey_collate_clause() SQL function */
-  rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8,
-      0, shellFkeyCollateClause, 0, 0
-  );
-
-
-  if( rc==SQLITE_OK ){
-    rc = sqlite3_prepare_v2(db, zSql, -1, &pSql, 0);
-  }
-  if( rc==SQLITE_OK ){
-    sqlite3_bind_int(pSql, 1, bGroupByParent);
-  }
-
-  if( rc==SQLITE_OK ){
-    int rc2;
-    char *zPrev = 0;
-    while( SQLITE_ROW==sqlite3_step(pSql) ){
-      int res = -1;
-      sqlite3_stmt *pExplain = 0;
-      const char *zEQP = (const char*)sqlite3_column_text(pSql, 0);
-      const char *zGlob = (const char*)sqlite3_column_text(pSql, 1);
-      const char *zFrom = (const char*)sqlite3_column_text(pSql, 2);
-      const char *zTarget = (const char*)sqlite3_column_text(pSql, 3);
-      const char *zCI = (const char*)sqlite3_column_text(pSql, 4);
-      const char *zParent = (const char*)sqlite3_column_text(pSql, 5);
-
-      if( zEQP==0 || zGlob==0 ) continue;
-      rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
-      if( rc!=SQLITE_OK ) break;
-      if( SQLITE_ROW==sqlite3_step(pExplain) ){
-        const char *zPlan = (const char*)sqlite3_column_text(pExplain, 3);
-        res = zPlan!=0 && (  0==sqlite3_strglob(zGlob, zPlan)
-                             || 0==sqlite3_strglob(zGlobIPK, zPlan));
-      }
-      rc = sqlite3_finalize(pExplain);
-      if( rc!=SQLITE_OK ) break;
-
-      if( res<0 ){
-        raw_printf(STD_ERR, "Error: internal error");
-        break;
-      }else{
-        if( bGroupByParent
-        && (bVerbose || res==0)
-        && (zPrev==0 || sqlite3_stricmp(zParent, zPrev))
-        ){
-          raw_printf(out, "-- Parent table %s\n", zParent);
-          sqlite3_free(zPrev);
-          zPrev = sqlite3_mprintf("%s", zParent);
-        }
-
-        if( res==0 ){
-          raw_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget);
-        }else if( bVerbose ){
-          raw_printf(out, "%s/* no extra indexes required for %s -> %s */\n",
-              zIndent, zFrom, zTarget
-          );
-        }
-      }
-    }
-    sqlite3_free(zPrev);
-
-    if( rc!=SQLITE_OK ){
-      raw_printf(STD_ERR, "%s\n", sqlite3_errmsg(db));
-    }
-
-    rc2 = sqlite3_finalize(pSql);
-    if( rc==SQLITE_OK && rc2!=SQLITE_OK ){
-      rc = rc2;
-      raw_printf(STD_ERR, "%s\n", sqlite3_errmsg(db));
-    }
-  }else{
-    raw_printf(STD_ERR, "%s\n", sqlite3_errmsg(db));
-  }
-
-  return rc;
-}
-
-
 #if !defined SQLITE_OMIT_VIRTUALTABLE
 static void shellPrepare(
   sqlite3 *db,
@@ -6556,7 +6430,8 @@ struct ArCommand {
   const char *zFile;              /* --file argument, or NULL */
   const char *zDir;               /* --directory argument, or NULL */
   char **azArg;                   /* Array of command arguments */
-  ShellState *p;                  /* Shell state */
+  ShellExState *p;                /* Shell state */
+  FILE *out;                      /* Where to put normal messages or info */
   sqlite3 *db;                    /* Database containing the archive */
 };
 
@@ -6900,18 +6775,18 @@ static int arListCommand(ArCommand *pAr){
   shellPreparePrintf(pAr->db, &rc, &pSql, zSql, azCols[pAr->bVerbose],
                      pAr->zSrcTable, zWhere);
   if( pAr->bDryRun ){
-    utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql));
+    utf8_printf(pAr->out, "%s\n", sqlite3_sql(pSql));
   }else{
     while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
       if( pAr->bVerbose ){
-        utf8_printf(pAr->p->out, "%s % 10d  %s  %s\n",
+        utf8_printf(pAr->out, "%s % 10d  %s  %s\n",
             sqlite3_column_text(pSql, 0),
             sqlite3_column_int(pSql, 1),
             sqlite3_column_text(pSql, 2),
             sqlite3_column_text(pSql, 3)
         );
       }else{
-        utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0));
+        utf8_printf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0));
       }
     }
   }
@@ -6939,7 +6814,7 @@ static int arRemoveCommand(ArCommand *pAr){
     zSql = sqlite3_mprintf("DELETE FROM %s WHERE %s;",
                            pAr->zSrcTable, zWhere);
     if( pAr->bDryRun ){
-      utf8_printf(pAr->p->out, "%s\n", zSql);
+      utf8_printf(pAr->out, "%s\n", zSql);
     }else{
       char *zErr = 0;
       rc = sqlite3_exec(pAr->db, "SAVEPOINT ar;", 0, 0, 0);
@@ -7016,11 +6891,11 @@ static int arExtractCommand(ArCommand *pAr){
       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));
+        utf8_printf(pAr->out, "%s\n", sqlite3_sql(pSql));
       }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));
+            utf8_printf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0));
           }
         }
       }
@@ -7040,7 +6915,7 @@ static int arExtractCommand(ArCommand *pAr){
 static int arExecSql(ArCommand *pAr, const char *zSql){
   int rc;
   if( pAr->bDryRun ){
-    utf8_printf(pAr->p->out, "%s\n", zSql);
+    utf8_printf(pAr->out, "%s\n", zSql);
     rc = SQLITE_OK;
   }else{
     char *zErr = 0;
@@ -7182,7 +7057,7 @@ end_ar_transaction:
 ** Implementation of ".ar" dot command.
 */
 static int arDotCommand(
-  ShellState *pState,          /* Current shell tool state */
+  ShellExState *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[] */
@@ -7192,14 +7067,15 @@ static int arDotCommand(
   memset(&cmd, 0, sizeof(cmd));
   cmd.fromCmdLine = fromCmdLine;
   rc = arParseCommand(azArg, nArg, &cmd);
+  cmd.out = currentOutputFile(pState);
   if( rc==SQLITE_OK ){
     int eDbType = SHELL_OPEN_UNSPEC;
     cmd.p = pState;
-    cmd.db = pState->db;
+    cmd.db = DBX(pState);
     if( cmd.zFile ){
       eDbType = deduceDatabaseType(cmd.zFile, 1);
     }else{
-      eDbType = pState->openMode;
+      eDbType = ISS(pState)->openMode;
     }
     if( eDbType==SHELL_OPEN_ZIPFILE ){
       if( cmd.eCmd==AR_CMD_EXTRACT || cmd.eCmd==AR_CMD_LIST ){
@@ -7221,7 +7097,7 @@ static int arDotCommand(
       }
       cmd.db = 0;
       if( cmd.bDryRun ){
-        utf8_printf(pState->out, "-- open database '%s'%s\n", cmd.zFile,
+        utf8_printf(cmd.out, "-- open database '%s'%s\n", cmd.zFile,
              eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : "");
       }
       rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags,
@@ -7236,7 +7112,6 @@ static int arDotCommand(
       sqlite3_sqlar_init(cmd.db, 0, 0);
       sqlite3_create_function(cmd.db, "shell_putsnl", 1, SQLITE_UTF8, cmd.p,
                               shellPutsFunc, 0, 0);
-
     }
     if( cmd.zSrcTable==0 && cmd.bZip==0 && cmd.eCmd!=AR_CMD_HELP ){
       if( cmd.eCmd!=AR_CMD_CREATE
@@ -7263,7 +7138,7 @@ static int arDotCommand(
         break;
 
       case AR_CMD_HELP:
-        arUsage(pState->out);
+        arUsage(cmd.out);
         break;
 
       case AR_CMD_INSERT:
@@ -7281,7 +7156,7 @@ static int arDotCommand(
     }
   }
 end_ar_command:
-  if( cmd.db!=pState->db ){
+  if( cmd.db!=DBX(pState) ){
     close_db(cmd.db);
   }
   sqlite3_free(cmd.zSrcTable);
@@ -7547,7 +7422,7 @@ static RecoverTable *recoverNewTable(
 ** the caller should write data to the orphans table.
 */
 static RecoverTable *recoverFindTable(
-  ShellState *pState,             /* Shell state object */
+  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 */
@@ -7561,7 +7436,7 @@ static RecoverTable *recoverFindTable(
   const char *zName = 0;
 
   /* Search the recovered schema for an object with root page iRoot. */
-  shellPreparePrintf(pState->db, pRc, &pStmt,
+  shellPreparePrintf(db, pRc, &pStmt,
       "SELECT type, name, sql FROM recovery.schema WHERE rootpage=%d", iRoot
   );
   while( *pRc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
@@ -7589,7 +7464,8 @@ static RecoverTable *recoverFindTable(
 ** Return a RecoverTable object representing the orphans table.
 */
 static RecoverTable *recoverOrphanTable(
-  ShellState *pState,             /* Shell state object */
+  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 */
@@ -7605,7 +7481,7 @@ static RecoverTable *recoverOrphanTable(
     int iTab = 0;
     char *zTab = shellMPrintf(pRc, "%s", zLostAndFound);
     sqlite3_stmt *pTest = 0;
-    shellPrepare(pState->db, pRc,
+    shellPrepare(db, pRc,
         "SELECT 1 FROM recovery.schema WHERE name=?", &pTest
     );
     if( pTest ) sqlite3_bind_text(pTest, 1, zTab, -1, SQLITE_TRANSIENT);
@@ -7636,14 +7512,14 @@ static RecoverTable *recoverOrphanTable(
         recoverFreeTable(pTab);
         pTab = 0;
       }else{
-        raw_printf(pState->out,
+        raw_printf(out,
             "CREATE TABLE %s(rootpgno INTEGER, "
             "pgno INTEGER, nfield INTEGER, id INTEGER", pTab->zQuoted
         );
         for(i=0; i<nCol; i++){
-          raw_printf(pState->out, ", c%d", i);
+          raw_printf(out, ", c%d", i);
         }
-        raw_printf(pState->out, ");\n");
+        raw_printf(out, ");\n");
       }
     }
     sqlite3_free(zTab);
@@ -7652,7 +7528,7 @@ static RecoverTable *recoverOrphanTable(
 }
 #endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
 
-static int writeDb( char *azArg[], int nArg, ShellState *p, char **pzErr ){
+static int writeDb( char *azArg[], int nArg, ShellExState *psx, char **pzErr ){
   int rc = 0;
   const char *zDestFile = 0;
   const char *zDb = 0;
@@ -7661,7 +7537,7 @@ static int writeDb( char *azArg[], int nArg, ShellState *p, char **pzErr ){
   int j;
   int bAsync = 0;
   const char *zVfs = 0;
-  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
+  if( ISS(psx)->bSafeMode ) return SHELL_FORBIDDEN_OP;
   for(j=1; j<nArg; j++){
     const char *z = azArg[j];
     if( z[0]=='-' ){
@@ -7700,8 +7576,8 @@ static int writeDb( char *azArg[], int nArg, ShellState *p, char **pzErr ){
     sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
                  0, 0, 0);
   }
-  open_db(p, 0);
-  pBackup = sqlite3_backup_init(pDest, "main", p->db, zDb);
+  open_db(psx, 0);
+  pBackup = sqlite3_backup_init(pDest, "main", DBX(psx), zDb);
   if( pBackup==0 ){
     utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(pDest));
     close_db(pDest);
@@ -7933,8 +7809,118 @@ FROM (\
   }
 }
 
+#if SHELL_DYNAMIC_EXTENSION
+/* Register a meta-command */
+static int register_meta_command(ShellExState *p,
+                                 ExtensionId eid, MetaCommand *pMC){
+  return SQLITE_ERROR;
+}
+
+/* Register an output data display (or other disposition) mode */
+static int register_out_mode(ShellExState *p,
+                             ExtensionId eid, OutModeHandler *pOMH){
+  return SQLITE_ERROR;
+}
+
+/* Register an import variation from (various sources) for .import */
+static int register_importer(ShellExState *p,
+                             ExtensionId eid, ImportHandler *pIH){
+  return SQLITE_ERROR;
+}
+
+static FILE *currentOutputFile(ShellExState *p){
+  return ISS(p)->out;
+}
+
+static struct InSource *currentInputSource(ShellExState *p){
+  return ISS(p)->pInSource;
+}
+
+static int nowInteractive(ShellExState *p){
+  return INSOURCE_IS_INTERACTIVE(ISS(p)->pInSource);
+}
+
+static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths);
+
+static ExtensionHelpers extHelpers = {
+  6,
+  {
+    failIfSafeMode,
+    currentOutputFile,
+    currentInputSource,
+    strLineGet,
+    setColumnWidths,
+    nowInteractive,
+    0
+  }
+};
+
+static ShellExtensionAPI shellExtAPI = {
+  &extHelpers, 3, {
+    register_meta_command,
+    register_out_mode,
+    register_importer,
+    0
+  }
+};
+
+/* This SQL function provides a way for a just-loaded shell extension to
+ * obtain a ShellExtensionLink pointer from the shell core while using
+ * the same sqlite3_load_extension API used for SQLite extensions.
+ * This serves as an alternative to deriving the same pointer from
+ * the pzErr argument passed into that API.
+ */
+static void shell_linkage(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  int linkKind = 0;
+  if( argc>0 ){
+    linkKind = sqlite3_value_int(argv[0]);
+  }
+  switch (linkKind){
+  case 0:
+    sqlite3_result_pointer(context, sqlite3_user_data(context),
+                           SHELLEXT_API_POINTERS, 0);
+    break;
+  default:
+    sqlite3_result_null(context);
+  }
+}
+
+static int load_shell_extension(ShellExState *psx, const char *zFile,
+                                const char *zProc, char **pzErr){
+  ShellExtensionLink shxLink = {
+    sizeof(ShellExtensionLink),
+    &shellExtAPI,
+    0, /* zErrMsg */
+    0, /* ExtensionId */
+    0  /* Extension destructor */
+  };
+  int rc;
+  if( psx->dbShell==0 ){
+    rc = sqlite3_open(":memory:", &psx->dbShell);
+    if( rc!=SQLITE_OK ) return 1;
+    sqlite3_enable_load_extension(psx->dbShell, 1);
+  }
+  sqlite3_create_function(psx->dbShell, "shext_pointer", 1,
+                          SQLITE_DIRECTONLY|SQLITE_UTF8,
+                          &shxLink, shell_linkage, 0, 0);
+  rc = sqlite3_load_extension(psx->dbShell, zFile, zProc, &shxLink.zErrMsg);
+  sqlite3_create_function(psx->dbShell, "shext_pointer", 1,
+                          SQLITE_DIRECTONLY|SQLITE_UTF8,
+                          0, 0, 0, 0); /* deregister */
+  if( pzErr!=0 ) *pzErr = shxLink.zErrMsg;
+  if( rc==SQLITE_OK ){
+    /* Keep extension's id and destructor for later disposal. */
+  }
+  return rc!=SQLITE_OK;
+}
+#endif
+
 #ifndef OBJECTIFY_COMMANDS
-# define OBJECTIFY_COMMANDS 1
+# define OBJECTIFY_COMMANDS 1 /* This value required for extensibility. */
 #endif
 
 /* Meta-command implementation functions are defined in this section.
@@ -7948,13 +7934,14 @@ COMMENT  Generally, this section defines dispatchable functions inline and
 COMMENT  causes collection of dispatch and help table entries, to be later
 COMMENT  emitted by certain macros. (See EMIT_* further on.)
 ** All dispatchable meta-command execute functions have this signature:
-static int someCommand(char *azArg[], int nArg, ShellState *p, char **pzErr);
+static int someCommand(char *azArg[], int nArg, ShellExState *p, char **pzErr);
 */
 DISPATCH_CONFIG[
   RETURN_TYPE=int
   STORAGE_CLASS=static
-  ARGS_SIGNATURE=char *$arg4\[\], int $arg5, ShellState *$arg6, char **$arg7
+  ARGS_SIGNATURE=char *$arg4\[\], int $arg5, ShellExState *$arg6, char **$arg7
   DISPATCH_ENTRY={ "$cmd", ${cmd}Command, $arg1, $arg2, $arg3 },
+  METACMD_INIT=META_CMD_INFO(${cmd}, $arg1,$arg2,$arg3, <HT0>, <HT1>),
   CMD_CAPTURE_RE=^\s*{\s*"(\w+)"
   DISPATCHEE_NAME=${cmd}Command
   DC_ARG1_DEFAULT=[string length $cmd]
@@ -7977,7 +7964,7 @@ COLLECT_HELP_TEXT[
 DISPATCHABLE_COMMAND( seeargs ? 0 0 azArg nArg p ){
   int rc = 0;
   for (rc=1; rc<nArg; ++rc)
-    raw_printf(p->out, "%s%s", azArg[rc], (rc==nArg-1)? "|\n" : "|");
+    raw_printf(ISS(p)->out, "%s%s", azArg[rc], (rc==nArg-1)? "|\n" : "|");
   return 0;
 }
 
@@ -8010,7 +7997,7 @@ COLLECT_HELP_TEXT[
 ];
 DISPATCHABLE_COMMAND( archive ? 0 0 azArg nArg p ){
   open_db(p, 0);
-  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
+  if( ISS(p)->bSafeMode ) return SHELL_FORBIDDEN_OP;
   return arDotCommand(p, 0, azArg, nArg);
 }
 
@@ -8025,11 +8012,11 @@ DISPATCHABLE_COMMAND( auth 3 2 2 azArg nArg p ){
   int rc = 0;
   open_db(p, 0);
   if( booleanValue(azArg[1]) ){
-    sqlite3_set_authorizer(p->db, shellAuth, p);
-  }else if( p->bSafeModeFuture ){
-    sqlite3_set_authorizer(p->db, safeModeAuth, p);
+    sqlite3_set_authorizer(DBX(p), shellAuth, p);
+  }else if( ISS(p)->bSafeModeFuture ){
+    sqlite3_set_authorizer(DBX(p), safeModeAuth, p);
   }else{
-    sqlite3_set_authorizer(p->db, 0, 0);
+    sqlite3_set_authorizer(DBX(p), 0, 0);
   }
   return rc;
 }
@@ -8048,10 +8035,12 @@ COLLECT_HELP_TEXT[
   "     --append               Use the appendvfs",
   "     --async                Write the FILE without journal and fsync()",
 ];
-COLLECT_DISPATCH( * )[
-  { "backup", writeDb, 4, 2, 5 },
-  { "save", writeDb, 3, 2, 5 },
-];
+DISPATCHABLE_COMMAND( backup 4 2 5 ){
+  return writeDb( azArg, nArg, p, pzErr);
+}
+DISPATCHABLE_COMMAND( save 3 2 5 ){
+  return writeDb( azArg, nArg, p, pzErr);
+}
 
 /*****************
  * The .bail command
@@ -8073,16 +8062,16 @@ COLLECT_HELP_TEXT[
 ];
 DISPATCHABLE_COMMAND( binary 3 2 2 ){
   if( booleanValue(azArg[1]) ){
-    setBinaryMode(p->out, 1);
+    setBinaryMode(ISS(p)->out, 1);
   }else{
-    setTextMode(p->out, 1);
+    setTextMode(ISS(p)->out, 1);
   }
   return 0;
 }
 
 DISPATCHABLE_COMMAND( cd ? 2 2 ){
   int rc=0;
-  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
+  if( ISS(p)->bSafeMode ) return SHELL_FORBIDDEN_OP;
 #if defined(_WIN32) || defined(WIN32)
   wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
   rc = !SetCurrentDirectoryW(z);
@@ -8125,7 +8114,7 @@ DISPATCHABLE_COMMAND( check 3 0 0 ){
   */
   char *zRes = 0;
   int rc=0;
-  output_reset(p);
+  output_reset(ISS(p));
   if( nArg!=2 ){
     return SHELL_INVALID_ARGS;
   }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){
@@ -8135,59 +8124,60 @@ DISPATCHABLE_COMMAND( check 3 0 0 ){
     *pzErr =
       shellMPrintf(&rc,
                    "testcase-%s FAILED\n Expected: [%s]\n      Got: [%s]\n",
-                   p->zTestcase, azArg[1], zRes);
+                   ISS(p)->zTestcase, azArg[1], zRes);
     rc = 1;
   }else{
-    utf8_printf(STD_OUT, "testcase-%s ok\n", p->zTestcase);
-    p->nCheck++;
+    utf8_printf(STD_OUT, "testcase-%s ok\n", ISS(p)->zTestcase);
+    ISS(p)->nCheck++;
   }
   sqlite3_free(zRes);
   return rc;
 }
 DISPATCHABLE_COMMAND( clone ? 2 2 ){
-  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
+  if( ISS(p)->bSafeMode ) return SHELL_FORBIDDEN_OP;
   tryToClone(p, azArg[1]);
   return 0;
 }
 DISPATCHABLE_COMMAND( connection ? 1 4 ){
+  ShellInState *psi = ISS(p);
   if( nArg==1 ){
     /* List available connections */
     int i;
-    for(i=0; i<ArraySize(p->aAuxDb); i++){
-      const char *zFile = p->aAuxDb[i].zDbFilename;
-      if( p->aAuxDb[i].db==0 && p->pAuxDb!=&p->aAuxDb[i] ){
-       zFile = "(not open)";
+    for(i=0; i<ArraySize(psi->aAuxDb); i++){
+      const char *zFile = psi->aAuxDb[i].zDbFilename;
+      if( psi->aAuxDb[i].db==0 && psi->pAuxDb!=&psi->aAuxDb[i] ){
+        zFile = "(not open)";
       }else if( zFile==0 ){
-       zFile = "(memory)";
+        zFile = "(memory)";
       }else if( zFile[0]==0 ){
-       zFile = "(temporary-file)";
+        zFile = "(temporary-file)";
       }
-      if( p->pAuxDb == &p->aAuxDb[i] ){
-       utf8_printf(STD_OUT, "ACTIVE %d: %s\n", i, zFile);
-      }else if( p->aAuxDb[i].db!=0 ){
-       utf8_printf(STD_OUT, "       %d: %s\n", i, zFile);
+      if( psi->pAuxDb == &psi->aAuxDb[i] ){
+        utf8_printf(STD_OUT, "ACTIVE %d: %s\n", i, zFile);
+      }else if( psi->aAuxDb[i].db!=0 ){
+        utf8_printf(STD_OUT, "       %d: %s\n", i, zFile);
       }
     }
   }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){
     int i = azArg[1][0] - '0';
-    if( p->pAuxDb != &p->aAuxDb[i] && i>=0 && i<ArraySize(p->aAuxDb) ){
-      p->pAuxDb->db = p->db;
-      p->pAuxDb = &p->aAuxDb[i];
-      globalDb = p->db = p->pAuxDb->db;
-      p->pAuxDb->db = 0;
+    if( psi->pAuxDb != &psi->aAuxDb[i] && i>=0 && i<ArraySize(psi->aAuxDb) ){
+      psi->pAuxDb->db = DBI(psi);
+      psi->pAuxDb = &psi->aAuxDb[i];
+      globalDb = DBI(psi) = psi->pAuxDb->db;
+      psi->pAuxDb->db = 0;
     }
   }else if( nArg==3 && strcmp(azArg[1], "close")==0
-           && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){
+            && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){
     int i = azArg[2][0] - '0';
-    if( i<0 || i>=ArraySize(p->aAuxDb) ){
+    if( i<0 || i>=ArraySize(psi->aAuxDb) ){
       /* No-op */
-    }else if( p->pAuxDb == &p->aAuxDb[i] ){
+    }else if( psi->pAuxDb == &psi->aAuxDb[i] ){
       raw_printf(STD_ERR, "cannot close the active database connection\n");
       return 1;
-    }else if( p->aAuxDb[i].db ){
-      session_close_all(p, i);
-      close_db(p->aAuxDb[i].db);
-      p->aAuxDb[i].db = 0;
+    }else if( psi->aAuxDb[i].db ){
+      session_close_all(psi, i);
+      close_db(psi->aAuxDb[i].db);
+      psi->aAuxDb[i].db = 0;
     }
   }else{
     return SHELL_INVALID_ARGS;
@@ -8209,11 +8199,13 @@ DISPATCHABLE_COMMAND( databases 2 1 0 ){
   char **azName = 0;
   int nName = 0;
   sqlite3_stmt *pStmt;
+  sqlite3 *db;
   int i;
   open_db(p, 0);
-  rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
+  db = DBX(p);
+  rc = sqlite3_prepare_v2(db, "PRAGMA database_list", -1, &pStmt, 0);
   if( rc ){
-    *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
+    *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(db));
     rc = 1;
   }else{
     while( sqlite3_step(pStmt)==SQLITE_ROW ){
@@ -8231,10 +8223,10 @@ DISPATCHABLE_COMMAND( databases 2 1 0 ){
   }
   sqlite3_finalize(pStmt);
   for(i=0; i<nName; i++){
-    int eTxn = sqlite3_txn_state(p->db, azName[i*2]);
-    int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]);
+    int eTxn = sqlite3_txn_state(db, azName[i*2]);
+    int bRdonly = sqlite3_db_readonly(db, azName[i*2]);
     const char *z = azName[i*2+1];
-    utf8_printf(p->out, "%s: %s %s%s\n",
+    utf8_printf(ISS(p)->out, "%s: %s %s%s\n",
                 azName[i*2],
                 z && z[0] ? z : "\"\"",
                 bRdonly ? "r/o" : "r/w",
@@ -8273,10 +8265,11 @@ DISPATCHABLE_COMMAND( dbconfig 3 1 3 ){
   for(ii=0; ii<ArraySize(aDbConfig); ii++){
     if( nArg>1 && strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue;
     if( nArg>=3 ){
-      sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0);
+      sqlite3_db_config(DBX(p), aDbConfig[ii].op, booleanValue(azArg[2]), 0);
     }
-    sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v);
-    utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off");
+    sqlite3_db_config(DBX(p), aDbConfig[ii].op, -1, &v);
+    utf8_printf(ISS(p)->out, "%19s %s\n",
+                aDbConfig[ii].zName, v ? "on" : "off");
     if( nArg>1 ) break;
   }
   if( nArg>1 && ii==ArraySize(aDbConfig) ){
@@ -8315,12 +8308,13 @@ COLLECT_HELP_TEXT[
   "      trigger               Like \"full\" but also show trigger bytecode",
 ];
 DISPATCHABLE_COMMAND( dump ? 1 2 ){
+  ShellInState *psi = ISS(p);
   char *zLike = 0;
   char *zSchema = "main";
   char *zSql;
   int i;
-  int savedShowHeader = p->showHeader;
-  int savedShellFlags = p->shellFlgs;
+  int savedShowHeader = psi->showHeader;
+  int savedShellFlags = psi->shellFlgs;
   ShellClearFlag(p,
      SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo
      |SHFLG_DumpDataOnly|SHFLG_DumpNoSys);
@@ -8379,20 +8373,20 @@ DISPATCHABLE_COMMAND( dump ? 1 2 ){
 
   open_db(p, 0);
 
-  if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
+  if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
     /* When playing back a "dump", the content might appear in an order
     ** which causes immediate foreign key constraints to be violated.
     ** So disable foreign-key constraint enforcement to prevent problems. */
-    raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n");
-    raw_printf(p->out, "BEGIN TRANSACTION;\n");
+    raw_printf(psi->out, "PRAGMA foreign_keys=OFF;\n");
+    raw_printf(psi->out, "BEGIN TRANSACTION;\n");
   }
-  p->writableSchema = 0;
-  p->showHeader = 0;
+  psi->writableSchema = 0;
+  psi->showHeader = 0;
   /* Set writable_schema=ON since doing so forces SQLite to initialize
   ** as much of the schema as it can even if the sqlite_schema table is
   ** corrupt. */
-  sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
-  p->nErr = 0;
+  sqlite3_exec(DBX(p), "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
+  psi->nErr = 0;
   if( zLike==0 ) zLike = sqlite3_mprintf("true");
   zSql = sqlite3_mprintf(
            "SELECT name, type, sql FROM %w.sqlite_schema AS o "
@@ -8401,30 +8395,30 @@ DISPATCHABLE_COMMAND( dump ? 1 2 ){
            " ORDER BY tbl_name='sqlite_sequence', rowid",
            zSchema, zLike
          );
-  run_schema_dump_query(p,zSql);
+  run_schema_dump_query(psi,zSql);
   sqlite3_free(zSql);
-  if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
+  if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
     zSql = sqlite3_mprintf(
              "SELECT sql FROM sqlite_schema AS o "
              "WHERE (%s) AND sql NOT NULL"
              "  AND type IN ('index','trigger','view')",
              zLike
            );
-    run_table_dump_query(p, zSql);
+    run_table_dump_query(psi, zSql);
     sqlite3_free(zSql);
   }
   sqlite3_free(zLike);
-  if( p->writableSchema ){
-    raw_printf(p->out, "PRAGMA writable_schema=OFF;\n");
-    p->writableSchema = 0;
+  if( psi->writableSchema ){
+    raw_printf(psi->out, "PRAGMA writable_schema=OFF;\n");
+    psi->writableSchema = 0;
   }
-  sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0);
-  sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0);
-  if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
-    raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n");
+  sqlite3_exec(DBX(p), "PRAGMA writable_schema=OFF;", 0, 0, 0);
+  sqlite3_exec(DBX(p), "RELEASE dump;", 0, 0, 0);
+  if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
+    raw_printf(psi->out, psi->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n");
   }
-  p->showHeader = savedShowHeader;
-  p->shellFlgs = savedShellFlags;
+  psi->showHeader = savedShowHeader;
+  psi->shellFlgs = savedShellFlags;
 
   return 0;
 }
@@ -8433,29 +8427,30 @@ DISPATCHABLE_COMMAND( echo ? 2 2 ){
   return 0;
 }
 DISPATCHABLE_COMMAND( eqp ? 0 0 ){
+  ShellInState *psi = ISS(p);
   if( nArg==2 ){
-    p->autoEQPtest = 0;
-    if( p->autoEQPtrace ){
-      if( p->db ) sqlite3_exec(p->db, "PRAGMA vdbe_trace=OFF;", 0, 0, 0);
-      p->autoEQPtrace = 0;
+    psi->autoEQPtest = 0;
+    if( psi->autoEQPtrace ){
+      if( DBX(p) ) sqlite3_exec(DBX(p), "PRAGMA vdbe_trace=OFF;", 0, 0, 0);
+      psi->autoEQPtrace = 0;
     }
     if( strcmp(azArg[1],"full")==0 ){
-      p->autoEQP = AUTOEQP_full;
+      psi->autoEQP = AUTOEQP_full;
     }else if( strcmp(azArg[1],"trigger")==0 ){
-      p->autoEQP = AUTOEQP_trigger;
+      psi->autoEQP = AUTOEQP_trigger;
 #ifdef SQLITE_DEBUG
     }else if( strcmp(azArg[1],"test")==0 ){
-      p->autoEQP = AUTOEQP_on;
-      p->autoEQPtest = 1;
+      psi->autoEQP = AUTOEQP_on;
+      psi->autoEQPtest = 1;
     }else if( strcmp(azArg[1],"trace")==0 ){
-      p->autoEQP = AUTOEQP_full;
-      p->autoEQPtrace = 1;
+      psi->autoEQP = AUTOEQP_full;
+      psi->autoEQPtrace = 1;
       open_db(p, 0);
-      sqlite3_exec(p->db, "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0);
-      sqlite3_exec(p->db, "PRAGMA vdbe_trace=ON;", 0, 0, 0);
+      sqlite3_exec(DBX(p), "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0);
+      sqlite3_exec(DBX(p), "PRAGMA vdbe_trace=ON;", 0, 0, 0);
 #endif
     }else{
-      p->autoEQP = (u8)booleanValue(azArg[1]);
+      psi->autoEQP = (u8)booleanValue(azArg[1]);
     }
   }else{
     return SHELL_INVALID_ARGS;
@@ -8468,16 +8463,17 @@ DISPATCHABLE_COMMAND( eqp ? 0 0 ){
 CONDITION_COMMAND( expert !defined(SQLITE_OMIT_VIRTUALTABLE) );
 COLLECT_HELP_TEXT[
   ".expert                  Suggest indexes for queries",
-  ".explain ?on|off|auto?   Change the EXPLAIN formatting mode.  Default: auto",
+  ".explain ?on|off|auto?   Change the EXPLAIN formatting mode. Default: auto",
 ];
 DISPATCHABLE_COMMAND( expert ? 1 1 ){
   open_db(p, 0);
-  expertDotCommand(p, azArg, nArg);
+  expertDotCommand(ISS(p), azArg, nArg);
   return 0;
 }
 DISPATCHABLE_COMMAND( explain ? 1 2 ){
   /* The ".explain" command is automatic now.  It is largely
   ** pointless, retained purely for backwards compatibility */
+  ShellInState *psi = ISS(p);
   int val = 1;
   if( nArg>1 ){
     if( strcmp(azArg[1],"auto")==0 ){
@@ -8486,16 +8482,16 @@ DISPATCHABLE_COMMAND( explain ? 1 2 ){
       val = booleanValue(azArg[1]);
     }
   }
-  if( val==1 && p->mode!=MODE_Explain ){
-    p->normalMode = p->mode;
-    p->mode = MODE_Explain;
-    p->autoExplain = 0;
+  if( val==1 && psi->mode!=MODE_Explain ){
+    psi->normalMode = psi->mode;
+    psi->mode = MODE_Explain;
+    psi->autoExplain = 0;
   }else if( val==0 ){
-    if( p->mode==MODE_Explain ) p->mode = p->normalMode;
-    p->autoExplain = 0;
+    if( psi->mode==MODE_Explain ) psi->mode = psi->normalMode;
+    psi->autoExplain = 0;
   }else if( val==99 ){
-    if( p->mode==MODE_Explain ) p->mode = p->normalMode;
-    p->autoExplain = 1;
+    if( psi->mode==MODE_Explain ) psi->mode = psi->normalMode;
+    psi->autoExplain = 1;
   }
   return 0;
 }
@@ -8520,19 +8516,19 @@ COLLECT_HELP_TEXT[
   "     -e                    Send output to the system text editor",
   "     -x       Send output as CSV to a spreadsheet (same as \".excel\")",
 ];
-static int outputRedirs(char *[], int, ShellState *,
+static int outputRedirs(char *[], int, ShellInState *,
                         char **pzErr, int bOnce, int eMode);
 DISPATCHABLE_COMMAND( excel ? 1 2 ){
-  return outputRedirs(azArg, nArg, p, pzErr, 2, 'x');
+  return outputRedirs(azArg, nArg, ISS(p), pzErr, 2, 'x');
 }
 DISPATCHABLE_COMMAND( once ? 1 6 ){
-  return outputRedirs(azArg, nArg, p, pzErr, 1, 0);
+  return outputRedirs(azArg, nArg, ISS(p), pzErr, 1, 0);
 }
 DISPATCHABLE_COMMAND( output ? 1 6 ){
-  return outputRedirs(azArg, nArg, p, pzErr, 0, 0);
+  return outputRedirs(azArg, nArg, ISS(p), pzErr, 0, 0);
 }
 
-static int outputRedirs(char *azArg[], int nArg, ShellState *p,
+static int outputRedirs(char *azArg[], int nArg, ShellInState *psi,
                         char **pzErr, int bOnce, int eMode){
   /* bOnce => 0: .output, 1: .once, 2: .excel */
   /* eMode => 'x' for excel, else 0 */
@@ -8541,7 +8537,7 @@ static int outputRedirs(char *azArg[], int nArg, ShellState *p,
   int bTxtMode = 0;
   int i;
   int bBOM = 0;
-  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
+  if( psi->bSafeMode ) return SHELL_FORBIDDEN_OP;
   for(i=1; i<nArg; i++){
     char *z = azArg[i];
     if( z[0]=='-' ){
@@ -8577,29 +8573,29 @@ static int outputRedirs(char *azArg[], int nArg, ShellState *p,
     shell_check_oom(zFile);
   }
   if( bOnce ){
-    p->outCount = 2;
+    psi->outCount = 2;
   }else{
-    p->outCount = 0;
+    psi->outCount = 0;
   }
-  output_reset(p);
+  output_reset(psi);
 #ifndef SQLITE_NOHAVE_SYSTEM
   if( eMode=='e' || eMode=='x' ){
-    p->doXdgOpen = 1;
-    outputModePush(p);
+    psi->doXdgOpen = 1;
+    outputModePush(psi);
     if( eMode=='x' ){
       /* spreadsheet mode.  Output as CSV. */
-      newTempFile(p, "csv");
-      ShellClearFlag(p, SHFLG_Echo);
-      p->mode = MODE_Csv;
-      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
-      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf);
+      newTempFile(psi, "csv");
+      psi->shellFlgs &= ~SHFLG_Echo;
+      psi->mode = MODE_Csv;
+      sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator, SEP_Comma);
+      sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, SEP_CrLf);
     }else{
       /* text editor mode */
-      newTempFile(p, "txt");
+      newTempFile(psi, "txt");
       bTxtMode = 1;
     }
     sqlite3_free(zFile);
-    zFile = sqlite3_mprintf("%s", p->zTempFile);
+    zFile = sqlite3_mprintf("%s", psi->zTempFile);
   }
 #endif /* SQLITE_NOHAVE_SYSTEM */
   shell_check_oom(zFile);
@@ -8607,30 +8603,30 @@ static int outputRedirs(char *azArg[], int nArg, ShellState *p,
 #ifdef SQLITE_OMIT_POPEN
     *pzErr = shellMPrintf(&rc, "Error: pipes are not supported in this OS\n");
     rc = 1;
-    p->out = STD_OUT;
+    psi->out = STD_OUT;
 #else
-    p->out = popen(zFile + 1, "w");
-    if( p->out==0 ){
+    psi->out = popen(zFile + 1, "w");
+    if( psi->out==0 ){
       *pzErr = shellMPrintf(&rc, "Error: cannot open pipe \"%s\"\n", zFile + 1);
-      p->out = STD_OUT;
+      psi->out = STD_OUT;
       rc = 1;
     }else{
-      if( bBOM ) fprintf(p->out,"\357\273\277");
-      sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
+      if( bBOM ) fprintf(psi->out,"\357\273\277");
+      sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile);
     }
 #endif
   }else{
-    p->out = output_file_open(zFile, bTxtMode);
-    if( p->out==0 ){
+    psi->out = output_file_open(zFile, bTxtMode);
+    if( psi->out==0 ){
       if( strcmp(zFile,"off")!=0 ){
         *pzErr = shellMPrintf
           (&rc, "Error: cannot write to \"%s\"\n", zFile);
       }
-      p->out = STD_OUT;
+      psi->out = STD_OUT;
       rc = 1;
     } else {
-      if( bBOM ) fprintf(p->out,"\357\273\277");
-      sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
+      if( bBOM ) fprintf(psi->out,"\357\273\277");
+      sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile);
     }
   }
   sqlite3_free(zFile);
@@ -8665,6 +8661,7 @@ DISPATCHABLE_COMMAND( filectrl ? 2 0 ){
     { "tempfilename",   SQLITE_FCNTL_TEMPFILENAME,    ""               },
  /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY,  "COUNT DELAY"    },*/
   };
+  ShellInState *psi = ISS(p);
   int filectrl = -1;
   int iCtrl = -1;
   sqlite3_int64 iRes = 0;  /* Integer result to display if rc2==1 */
@@ -8694,14 +8691,14 @@ DISPATCHABLE_COMMAND( filectrl ? 2 0 ){
 
   /* --help lists all file-controls */
   if( strcmp(zCmd,"help")==0 ){
-    utf8_printf(p->out, "Available file-controls:\n");
+    utf8_printf(psi->out, "Available file-controls:\n");
     for(i=0; i<ArraySize(aCtrl); i++){
-      utf8_printf(p->out, "  .filectrl %s %s\n",
+      utf8_printf(psi->out, "  .filectrl %s %s\n",
                   aCtrl[i].zCtrlName, aCtrl[i].zUsage);
     }
     return 1;
   }
-  
+
   /* Convert filectrl text option to value. Allow any
   ** unique prefix of the option name, or a numerical value. */
   n2 = strlen30(zCmd);
@@ -8725,7 +8722,7 @@ DISPATCHABLE_COMMAND( filectrl ? 2 0 ){
     case SQLITE_FCNTL_SIZE_LIMIT: {
       if( nArg!=2 && nArg!=3 ) break;
       iRes = nArg==3 ? integerValue(azArg[2]) : -1;
-      sqlite3_file_control(p->db, zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes);
+      sqlite3_file_control(DBX(p), zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes);
       isOk = 1;
       break;
     }
@@ -8734,7 +8731,7 @@ DISPATCHABLE_COMMAND( filectrl ? 2 0 ){
       int x;
       if( nArg!=3 ) break;
       x = (int)integerValue(azArg[2]);
-      sqlite3_file_control(p->db, zSchema, filectrl, &x);
+      sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
       isOk = 2;
       break;
     }
@@ -8743,7 +8740,7 @@ DISPATCHABLE_COMMAND( filectrl ? 2 0 ){
       int x;
       if( nArg!=2 && nArg!=3 ) break;
       x = nArg==3 ? booleanValue(azArg[2]) : -1;
-      sqlite3_file_control(p->db, zSchema, filectrl, &x);
+      sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
       iRes = x;
       isOk = 1;
       break;
@@ -8752,7 +8749,7 @@ DISPATCHABLE_COMMAND( filectrl ? 2 0 ){
     case SQLITE_FCNTL_HAS_MOVED: {
       int x;
       if( nArg!=2 ) break;
-      sqlite3_file_control(p->db, zSchema, filectrl, &x);
+      sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
       iRes = x;
       isOk = 1;
       break;
@@ -8760,9 +8757,9 @@ DISPATCHABLE_COMMAND( filectrl ? 2 0 ){
     case SQLITE_FCNTL_TEMPFILENAME: {
       char *z = 0;
       if( nArg!=2 ) break;
-      sqlite3_file_control(p->db, zSchema, filectrl, &z);
+      sqlite3_file_control(DBX(p), zSchema, filectrl, &z);
       if( z ){
-        utf8_printf(p->out, "%s\n", z);
+        utf8_printf(psi->out, "%s\n", z);
         sqlite3_free(z);
       }
       isOk = 2;
@@ -8772,32 +8769,37 @@ DISPATCHABLE_COMMAND( filectrl ? 2 0 ){
       int x;
       if( nArg>=3 ){
         x = atoi(azArg[2]);
-        sqlite3_file_control(p->db, zSchema, filectrl, &x);
+        sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
       }
       x = -1;
-      sqlite3_file_control(p->db, zSchema, filectrl, &x);
-      utf8_printf(p->out,"%d\n", x);
+      sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
+      utf8_printf(psi->out,"%d\n", x);
       isOk = 2;
       break;
     }
    }
   }
   if( isOk==0 && iCtrl>=0 ){
-    utf8_printf(p->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
+    utf8_printf(psi->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
     return 1;
   }else if( isOk==1 ){
     char zBuf[100];
     sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes);
-    raw_printf(p->out, "%s\n", zBuf);
+    raw_printf(psi->out, "%s\n", zBuf);
   }
   return 0;
 }
 
 DISPATCHABLE_COMMAND( fullschema ? 1 2 ){
   int rc;
-  ShellState data;
+  ShellInState data;
+  ShellExState datax;
   int doStats = 0;
-  memcpy(&data, p, sizeof(data));
+  /* Consider some refactoring to avoid this wholesale copying. */
+  memcpy(&data, ISS(p), sizeof(data));
+  memcpy(&datax, p, sizeof(datax));
+  data.pSXS = &datax;
+  datax.pSIS = &data;
   data.showHeader = 0;
   data.cMode = data.mode = MODE_Semi;
   if( nArg==2 && optionMatch(azArg[1], "indent") ){
@@ -8808,18 +8810,18 @@ DISPATCHABLE_COMMAND( fullschema ? 1 2 ){
     return SHELL_INVALID_ARGS;
   }
   open_db(p, 0);
-  rc = sqlite3_exec(p->db,
+  rc = sqlite3_exec(datax.dbUser,
     "SELECT sql FROM"
     "  (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x"
     "     FROM sqlite_schema UNION ALL"
     "   SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) "
     "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' "
     "ORDER BY x",
-    callback, &data, 0
+    callback, &datax, 0
   );
   if( rc==SQLITE_OK ){
     sqlite3_stmt *pStmt;
-    rc = sqlite3_prepare_v2(p->db,
+    rc = sqlite3_prepare_v2(datax.dbUser,
                             "SELECT rowid FROM sqlite_schema"
                             " WHERE name GLOB 'sqlite_stat[134]'",
                             -1, &pStmt, 0);
@@ -8827,15 +8829,15 @@ DISPATCHABLE_COMMAND( fullschema ? 1 2 ){
     sqlite3_finalize(pStmt);
   }
   if( doStats==0 ){
-    raw_printf(p->out, "/* No STAT tables available */\n");
+    raw_printf(data.out, "/* No STAT tables available */\n");
   }else{
-    raw_printf(p->out, "ANALYZE sqlite_schema;\n");
+    raw_printf(data.out, "ANALYZE sqlite_schema;\n");
     data.cMode = data.mode = MODE_Insert;
-    data.zDestTable = "sqlite_stat1";
-    shell_exec(&data, "SELECT * FROM sqlite_stat1", 0);
-    data.zDestTable = "sqlite_stat4";
-    shell_exec(&data, "SELECT * FROM sqlite_stat4", 0);
-    raw_printf(p->out, "ANALYZE sqlite_schema;\n");
+    datax.zDestTable = "sqlite_stat1";
+    shell_exec(&datax, "SELECT * FROM sqlite_stat1", 0);
+    datax.zDestTable = "sqlite_stat4";
+    shell_exec(&datax, "SELECT * FROM sqlite_stat4", 0);
+    raw_printf(data.out, "ANALYZE sqlite_schema;\n");
   }
   return rc > 0;
 }
@@ -8847,8 +8849,8 @@ COLLECT_HELP_TEXT[
   ".headers on|off          Turn display of headers on or off",
 ];
 DISPATCHABLE_COMMAND( headers 6 2 2 ){
-  p->showHeader = booleanValue(azArg[1]);
-  p->shellFlgs |= SHFLG_HeaderSet;
+  ISS(p)->showHeader = booleanValue(azArg[1]);
+  ISS(p)->shellFlgs |= SHFLG_HeaderSet;
   return 0;
 }
 
@@ -8871,8 +8873,8 @@ DISPATCHABLE_COMMAND( help 3 1 2 ){
       zPat = z;
     }
   }
-  if( showHelp(p->out, zPat)==0 ){
-    utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]);
+  if( showHelp(ISS(p)->out, zPat)==0 ){
+    utf8_printf(ISS(p)->out, "Nothing matches '%s'\n", azArg[1]);
   }
   /* Help pleas never fail! */
   return 0;
@@ -8906,22 +8908,24 @@ DISPATCHABLE_COMMAND( import ? 3 7 ){
   int nByte;                  /* Number of bytes in an SQL string */
   int i, j;                   /* Loop counters */
   int needCommit;             /* True to COMMIT or ROLLBACK at end */
-  int nSep;                   /* Number of bytes in p->colSeparator[] */
+  int nSep;                   /* Number of bytes in psi->colSeparator[] */
   char *zSql;                 /* An SQL statement */
   ImportCtx sCtx;             /* Reader context */
   char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
   int eVerbose = 0;           /* Larger for more console output */
   int nSkip = 0;              /* Initial lines to skip */
   int useOutputMode = 1;      /* Use output mode to determine separators */
+  FILE *out = ISS(p)->out;    /* output stream */
+  ShellInState *psi = ISS(p);
   int rc = 0;
 
-  if(p->bSafeMode) return SHELL_FORBIDDEN_OP;
+  if(psi->bSafeMode) return SHELL_FORBIDDEN_OP;
   memset(&sCtx, 0, sizeof(sCtx));
   if( 0==(sCtx.z = sqlite3_malloc64(120)) ){
     shell_out_of_memory();
   }
 
-  if( p->mode==MODE_Ascii ){
+  if( psi->mode==MODE_Ascii ){
     xRead = ascii_read_one_field;
   }else{
     xRead = csv_read_one_field;
@@ -8970,7 +8974,7 @@ DISPATCHABLE_COMMAND( import ? 3 7 ){
     const char *zYap = 0;
     /* If neither the --csv or --ascii options are specified, then set
     ** the column and row separator characters from the output mode. */
-    nSep = strlen30(p->colSeparator);
+    nSep = strlen30(psi->colSeparator);
     if( nSep==0 ){
       zYap = "Error: non-null column separator required for import";
     }
@@ -8978,7 +8982,7 @@ DISPATCHABLE_COMMAND( import ? 3 7 ){
       zYap = "Error: multi-character or multi-byte column separators"
         " not allowed for import";
     }
-    nSep = strlen30(p->rowSeparator);
+    nSep = strlen30(psi->rowSeparator);
     if( nSep==0 ){
       zYap = "Error: non-null row separator required for import";
     }
@@ -8986,21 +8990,22 @@ DISPATCHABLE_COMMAND( import ? 3 7 ){
       *pzErr = shellMPrintf(0,"%s\n", zYap);
       return 1;
     }
-    if( nSep==2 && p->mode==MODE_Csv && strcmp(p->rowSeparator,SEP_CrLf)==0 ){
+    if( nSep==2 && psi->mode==MODE_Csv
+        && strcmp(psi->rowSeparator,SEP_CrLf)==0 ){
       /* When importing CSV (only), if the row separator is set to the
       ** default output row separator, change it to the default input
       ** row separator.  This avoids having to maintain different input
       ** and output row separators. */
-      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
-      nSep = strlen30(p->rowSeparator);
+      sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, SEP_Row);
+      nSep = strlen30(psi->rowSeparator);
     }
     if( nSep>1 ){
       *pzErr = sqlite3_mprintf
         ("Error: multi-character row separators not allowed for import\n");
       return 1;
     }
-    sCtx.cColSep = p->colSeparator[0];
-    sCtx.cRowSep = p->rowSeparator[0];
+    sCtx.cColSep = psi->colSeparator[0];
+    sCtx.cRowSep = psi->rowSeparator[0];
   }
   sCtx.zFile = zFile;
   sCtx.nLine = 1;
@@ -9027,12 +9032,12 @@ DISPATCHABLE_COMMAND( import ? 3 7 ){
     char zSep[2];
     zSep[1] = 0;
     zSep[0] = sCtx.cColSep;
-    utf8_printf(p->out, "Column separator ");
-    output_c_string(p->out, zSep);
-    utf8_printf(p->out, ", row separator ");
+    utf8_printf(out, "Column separator ");
+    output_c_string(out, zSep);
+    utf8_printf(out, ", row separator ");
     zSep[0] = sCtx.cRowSep;
-    output_c_string(p->out, zSep);
-    utf8_printf(p->out, "\n");
+    output_c_string(out, zSep);
+    utf8_printf(out, "\n");
   }
   while( (nSkip--)>0 ){
     while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
@@ -9043,9 +9048,9 @@ DISPATCHABLE_COMMAND( import ? 3 7 ){
     shell_out_of_memory();
   }
   nByte = strlen30(zSql);
-  rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+  rc = sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0);
   import_append_char(&sCtx, 0);    /* To ensure sCtx.z is allocated */
-  if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){
+  if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(DBX(p)))==0 ){
     char *zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"",
                                     zSchema, zTable);
     sqlite3 *dbCols = 0;
@@ -9053,12 +9058,11 @@ DISPATCHABLE_COMMAND( import ? 3 7 ){
     char *zColDefs;
     while( xRead(&sCtx) ){
       zAutoColumn(sCtx.z, &dbCols, 0);
-
       if( sCtx.cTerm!=sCtx.cColSep ) break;
     }
     zColDefs = zAutoColumn(0, &dbCols, &zRenames);
     if( zRenames!=0 ){
-      FILE *fh = INSOURCE_IS_INTERACTIVE(p->pInSource)?  p->out : STD_ERR;
+      FILE *fh = INSOURCE_IS_INTERACTIVE(psi->pInSource)?  out : STD_ERR;
       utf8_printf(fh, "Columns renamed during .import %s due to duplicates:\n"
                   "%s\n", sCtx.zFile, zRenames);
       sqlite3_free(zRenames);
@@ -9072,22 +9076,22 @@ DISPATCHABLE_COMMAND( import ? 3 7 ){
     }
     zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs);
     if( eVerbose>=1 ){
-      utf8_printf(p->out, "%s\n", zCreate);
+      utf8_printf(out, "%s\n", zCreate);
     }
-    rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
+    rc = sqlite3_exec(DBX(p), zCreate, 0, 0, 0);
     if( rc ){
-      utf8_printf(STD_ERR, "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
+      utf8_printf(STD_ERR, "%s failed:\n%s\n", zCreate, sqlite3_errmsg(DBX(p)));
       sqlite3_free(zCreate);
       import_cleanup(&sCtx);
       return 1;
     }
     sqlite3_free(zCreate);
-    rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+    rc = sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0);
   }
   sqlite3_free(zSql);
   if( rc ){
     if (pStmt) sqlite3_finalize(pStmt);
-    *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
+    *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(DBX(p)));
     import_cleanup(&sCtx);
     return 1;
   }
@@ -9110,18 +9114,18 @@ DISPATCHABLE_COMMAND( import ? 3 7 ){
   zSql[j++] = ')';
   zSql[j] = 0;
   if( eVerbose>=2 ){
-    utf8_printf(p->out, "Insert using: %s\n", zSql);
+    utf8_printf(psi->out, "Insert using: %s\n", zSql);
   }
-  rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+  rc = sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0);
   sqlite3_free(zSql);
   if( rc ){
-    *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
+    *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(DBX(p)));
     if (pStmt) sqlite3_finalize(pStmt);
     import_cleanup(&sCtx);
     return 1;
   }
-  needCommit = sqlite3_get_autocommit(p->db);
-  if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
+  needCommit = sqlite3_get_autocommit(DBX(p));
+  if( needCommit ) sqlite3_exec(DBX(p), "BEGIN", 0, 0, 0);
   do{
     int startLine = sCtx.nLine;
     for(i=0; i<nCol; i++){
@@ -9136,7 +9140,7 @@ DISPATCHABLE_COMMAND( import ? 3 7 ){
       ** columns in ASCII mode?  If so, stop instead of NULL filling
       ** the remaining columns.
       */
-      if( p->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
+      if( psi->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
       sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
       if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
         utf8_printf(STD_ERR, "%s:%d: expected %d columns but found %d - "
@@ -9160,7 +9164,7 @@ DISPATCHABLE_COMMAND( import ? 3 7 ){
       rc = sqlite3_reset(pStmt);
       if( rc!=SQLITE_OK ){
         utf8_printf(STD_ERR, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
-                    startLine, sqlite3_errmsg(p->db));
+                    startLine, sqlite3_errmsg(DBX(p)));
         sCtx.nErr++;
       }else{
         sCtx.nRow++;
@@ -9170,9 +9174,9 @@ DISPATCHABLE_COMMAND( import ? 3 7 ){
 
   import_cleanup(&sCtx);
   sqlite3_finalize(pStmt);
-  if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0);
+  if( needCommit ) sqlite3_exec(DBX(p), "COMMIT", 0, 0, 0);
   if( eVerbose>0 ){
-    utf8_printf(p->out,
+    utf8_printf(out,
       "Added %d rows with %d errors using %d lines of input\n",
       sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
   }
@@ -9187,6 +9191,7 @@ COLLECT_HELP_TEXT[
   ".keyword ?KW?            List keywords, or say whether KW is one.",
 ];
 DISPATCHABLE_COMMAND( keyword ? 1 2 ){
+  FILE *out = ISS(p)->out;
   if( nArg<2 ){
     int i = 0;
     int nk = sqlite3_keyword_count();
@@ -9204,15 +9209,15 @@ DISPATCHABLE_COMMAND( keyword ? 1 2 ){
           }
           memcpy(kwBuf, zKW, szKW);
           kwBuf[szKW] = 0;
-          utf8_printf(p->out, "%s%s", kwBuf, zSep);
+          utf8_printf(out, "%s%s", kwBuf, zSep);
         }
       }
     }
-    if( nCol>0 ) utf8_printf(p->out, "\n");
+    if( nCol>0 ) utf8_printf(out, "\n");
   }else{
     int szKW = strlen30(azArg[1]);
     int isKeyword = sqlite3_keyword_check(azArg[1], szKW);
-    utf8_printf(p->out, "%s is%s a keyword\n",
+    utf8_printf(out, "%s is%s a keyword\n",
                 azArg[1], (isKeyword)? "" : " not");
   }
   return 0;
@@ -9232,6 +9237,9 @@ COLLECT_HELP_TEXT[
   "     Options:",
   "        fkey-indexes     Find missing foreign key indexes",
   ".load FILE ?ENTRY?       Load an extension library",
+#if SHELL_DYNAMIC_EXTENSION
+  "   Option -shellext will load the library as a shell extension.",
+#endif
   ".log FILE|off            Turn logging on or off.  FILE can be stderr/stdout",
 ];
 DISPATCHABLE_COMMAND( imposter ? 3 3 ){
@@ -9239,6 +9247,7 @@ DISPATCHABLE_COMMAND( imposter ? 3 3 ){
   char *zSql;
   char *zCollist = 0;
   sqlite3_stmt *pStmt;
+  sqlite3 *db;
   int tnum = 0;
   int isWO = 0;  /* True if making an imposter of a WITHOUT ROWID table */
   int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */
@@ -9256,8 +9265,9 @@ DISPATCHABLE_COMMAND( imposter ? 3 3 ){
     return 1;
   }
   open_db(p, 0);
+  db = DBX(p);
   if( nArg==2 ){
-    sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 1);
+    sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 1);
     return 0;
   }
   zSql = sqlite3_mprintf(
@@ -9269,7 +9279,7 @@ DISPATCHABLE_COMMAND( imposter ? 3 3 ){
                          "   AND sql LIKE '%%without%%rowid%%'",
                          azArg[1], azArg[1]
                          );
-  sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+  sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
   sqlite3_free(zSql);
   if( sqlite3_step(pStmt)==SQLITE_ROW ){
     tnum = sqlite3_column_int(pStmt, 0);
@@ -9277,7 +9287,7 @@ DISPATCHABLE_COMMAND( imposter ? 3 3 ){
   }
   sqlite3_finalize(pStmt);
   zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]);
-  rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
   sqlite3_free(zSql);
   i = 0;
   while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
@@ -9308,21 +9318,20 @@ DISPATCHABLE_COMMAND( imposter ? 3 3 ){
     return 1;
   }
   if( lenPK==0 ) lenPK = 100000;
-  zSql = sqlite3_mprintf(
-                         "CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))WITHOUT ROWID",
-                         azArg[2], zCollist, lenPK, zCollist);
+  zSql = sqlite3_mprintf("CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))"
+                         "WITHOUT ROWID", azArg[2], zCollist, lenPK, zCollist);
   sqlite3_free(zCollist);
-  rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 1, tnum);
+  rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 1, tnum);
   if( rc==SQLITE_OK ){
-    rc = sqlite3_exec(p->db, zSql, 0, 0, 0);
-    sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0);
+    rc = sqlite3_exec(db, zSql, 0, 0, 0);
+    sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 0);
     if( rc ){
       *pzErr = shellMPrintf(0,"Error in [%s]: %s\n",
-                               zSql, sqlite3_errmsg(p->db));
+                               zSql, sqlite3_errmsg(db));
     }else{
       utf8_printf(STD_OUT, "%s;\n", zSql);
-      raw_printf(STD_OUT,
-                 "WARNING: writing to an imposter table will corrupt the \"%s\" %s!\n",
+      raw_printf(STD_OUT, "WARNING: "
+                 "writing to an imposter table will corrupt the \"%s\" %s!\n",
                  azArg[1], isWO ? "table" : "index"
                  );
     }
@@ -9376,7 +9385,7 @@ DISPATCHABLE_COMMAND( limits 5 1 3 ){
   if( nArg==1 ){
     for(i=0; i<ArraySize(aLimit); i++){
       fprintf(STD_OUT, "%20s %d\n", aLimit[i].zLimitName,
-             sqlite3_limit(p->db, aLimit[i].limitCode, -1));
+             sqlite3_limit(DBX(p), aLimit[i].limitCode, -1));
     }
   }else if( nArg>3 ){
     return SHELL_INVALID_ARGS;
@@ -9401,50 +9410,231 @@ DISPATCHABLE_COMMAND( limits 5 1 3 ){
       return 1;
     }
     if( nArg==3 ){
-      sqlite3_limit(p->db, aLimit[iLimit].limitCode,
+      sqlite3_limit(DBX(p), aLimit[iLimit].limitCode,
                     (int)integerValue(azArg[2]));
     }
     fprintf(STD_OUT, "%20s %d\n", aLimit[iLimit].zLimitName,
-           sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1));
+           sqlite3_limit(DBX(p), aLimit[iLimit].limitCode, -1));
   }
   return 0;
 }
 
 DISPATCHABLE_COMMAND( lint 3 1 0 ){
-  open_db(p, 0);
-  int n = (nArg>=2 ? strlen30(azArg[1]) : 0);
-  if( n>0 && !sqlite3_strnicmp(azArg[1], "fkey-indexes", n) ){
-    return lintFkeyIndexes(p, azArg, nArg);
-  }
-  *pzErr = sqlite3_mprintf
-    ("Usage %s sub-command ?switches...?\n"
-     "Where sub-commands are:\n"
-     "    fkey-indexes\n", azArg[0]);
-  return 1;
-}
+  sqlite3 *db;                    /* Database handle to query "main" db of */
+  FILE *out = ISS(p)->out;        /* Stream to write non-error output to */
+  int bVerbose = 0;               /* If -verbose is present */
+  int bGroupByParent = 0;         /* If -groupbyparent is present */
+  int i;                          /* To iterate through azArg[] */
+  const char *zIndent = "";       /* How much to indent CREATE INDEX by */
+  int rc;                         /* Return code */
+  sqlite3_stmt *pSql = 0;         /* Compiled version of SQL statement below */
 
-DISPATCHABLE_COMMAND( load ? 2 3 ){
-  const char *zFile, *zProc;
-  char *zErrMsg = 0;
-  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
-  zFile = azArg[1];
-  zProc = nArg>=3 ? azArg[2] : 0;
-  open_db(p, 0);
-  if( SQLITE_OK!=sqlite3_load_extension(p->db, zFile, zProc, pzErr) ){
+  i = (nArg>=2 ? strlen30(azArg[1]) : 0);
+  if( i==0 || 0!=sqlite3_strnicmp(azArg[1], "fkey-indexes", i) ){
+    *pzErr = sqlite3_mprintf
+      ("Usage %s sub-command ?switches...?\n"
+       "Where sub-commands are:\n"
+       "    fkey-indexes\n", azArg[0]);
     return 1;
   }
-  return 0;
+  open_db(p, 0);
+  db = DBX(p);
+
+  /*
+  ** This SELECT statement returns one row for each foreign key constraint
+  ** in the schema of the main database. The column values are:
+  **
+  ** 0. The text of an SQL statement similar to:
+  **
+  **      "EXPLAIN QUERY PLAN SELECT 1 FROM child_table WHERE child_key=?"
+  **
+  **    This SELECT is similar to the one that the foreign keys implementation
+  **    needs to run internally on child tables. If there is an index that can
+  **    be used to optimize this query, then it can also be used by the FK
+  **    implementation to optimize DELETE or UPDATE statements on the parent
+  **    table.
+  **
+  ** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by
+  **    the EXPLAIN QUERY PLAN command matches this pattern, then the schema
+  **    contains an index that can be used to optimize the query.
+  **
+  ** 2. Human readable text that describes the child table and columns. e.g.
+  **
+  **       "child_table(child_key1, child_key2)"
+  **
+  ** 3. Human readable text that describes the parent table and columns. e.g.
+  **
+  **       "parent_table(parent_key1, parent_key2)"
+  **
+  ** 4. A full CREATE INDEX statement for an index that could be used to
+  **    optimize DELETE or UPDATE statements on the parent table. e.g.
+  **
+  **       "CREATE INDEX child_table_child_key ON child_table(child_key)"
+  **
+  ** 5. The name of the parent table.
+  **
+  ** These six values are used by the C logic below to generate the report.
+  */
+  const char *zSql =
+  "SELECT "
+    "     'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '"
+    "  || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' "
+    "  || fkey_collate_clause("
+    "       f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')"
+    ", "
+    "     'SEARCH ' || s.name || ' USING COVERING INDEX*('"
+    "  || group_concat('*=?', ' AND ') || ')'"
+    ", "
+    "     s.name  || '(' || group_concat(f.[from],  ', ') || ')'"
+    ", "
+    "     f.[table] || '(' || group_concat(COALESCE(f.[to], p.[name])) || ')'"
+    ", "
+    "     'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))"
+    "  || ' ON ' || quote(s.name) || '('"
+    "  || group_concat(quote(f.[from]) ||"
+    "        fkey_collate_clause("
+    "          f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')"
+    "  || ');'"
+    ", "
+    "     f.[table] "
+    "FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f "
+    "LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) "
+    "GROUP BY s.name, f.id "
+    "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)"
+  ;
+  const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)";
+
+  for(i=2; i<nArg; i++){
+    int n = strlen30(azArg[i]);
+    if( n>1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){
+      bVerbose = 1;
+    }
+    else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){
+      bGroupByParent = 1;
+      zIndent = "    ";
+    }
+    else{
+      raw_printf(STD_ERR, "Usage: %s %s ?-verbose? ?-groupbyparent?\n",
+          azArg[0], azArg[1]
+      );
+      return SQLITE_ERROR;
+    }
+  }
+
+  /* Register the fkey_collate_clause() SQL function */
+  rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8,
+      0, shellFkeyCollateClause, 0, 0
+  );
+
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_prepare_v2(db, zSql, -1, &pSql, 0);
+  }
+  if( rc==SQLITE_OK ){
+    sqlite3_bind_int(pSql, 1, bGroupByParent);
+  }
+
+  if( rc==SQLITE_OK ){
+    int rc2;
+    char *zPrev = 0;
+    while( SQLITE_ROW==sqlite3_step(pSql) ){
+      int res = -1;
+      sqlite3_stmt *pExplain = 0;
+      const char *zEQP = (const char*)sqlite3_column_text(pSql, 0);
+      const char *zGlob = (const char*)sqlite3_column_text(pSql, 1);
+      const char *zFrom = (const char*)sqlite3_column_text(pSql, 2);
+      const char *zTarget = (const char*)sqlite3_column_text(pSql, 3);
+      const char *zCI = (const char*)sqlite3_column_text(pSql, 4);
+      const char *zParent = (const char*)sqlite3_column_text(pSql, 5);
+
+      if( zEQP==0 || zGlob==0 ) continue;
+      rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
+      if( rc!=SQLITE_OK ) break;
+      if( SQLITE_ROW==sqlite3_step(pExplain) ){
+        const char *zPlan = (const char*)sqlite3_column_text(pExplain, 3);
+        res = zPlan!=0 && (  0==sqlite3_strglob(zGlob, zPlan)
+                             || 0==sqlite3_strglob(zGlobIPK, zPlan));
+      }
+      rc = sqlite3_finalize(pExplain);
+      if( rc!=SQLITE_OK ) break;
+
+      if( res<0 ){
+        raw_printf(STD_ERR, "Error: internal error");
+        break;
+      }else{
+        if( bGroupByParent
+        && (bVerbose || res==0)
+        && (zPrev==0 || sqlite3_stricmp(zParent, zPrev))
+        ){
+          raw_printf(out, "-- Parent table %s\n", zParent);
+          sqlite3_free(zPrev);
+          zPrev = sqlite3_mprintf("%s", zParent);
+        }
+
+        if( res==0 ){
+          raw_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget);
+        }else if( bVerbose ){
+          raw_printf(out, "%s/* no extra indexes required for %s -> %s */\n",
+                     zIndent, zFrom, zTarget);
+        }
+      }
+    }
+    sqlite3_free(zPrev);
+
+    if( rc!=SQLITE_OK ){
+      raw_printf(STD_ERR, "%s\n", sqlite3_errmsg(db));
+    }
+
+    rc2 = sqlite3_finalize(pSql);
+    if( rc==SQLITE_OK && rc2!=SQLITE_OK ){
+      rc = rc2;
+      raw_printf(STD_ERR, "%s\n", sqlite3_errmsg(db));
+    }
+  }else{
+    raw_printf(STD_ERR, "%s\n", sqlite3_errmsg(db));
+  }
+
+  return rc;
+}
+
+DISPATCHABLE_COMMAND( load ? 2 4 ){
+  const char *zFile = 0, *zProc = 0;
+  char *zErrMsg = 0;
+  int ai = 1, rc;
+#if SHELL_DYNAMIC_EXTENSION
+  u8 bLoadExt = 0;
+#endif
+  if( ISS(p)->bSafeMode ) return SHELL_FORBIDDEN_OP;
+  while( ai<nArg ){
+    const char *zA = azArg[ai++];
+#if SHELL_DYNAMIC_EXTENSION
+    if( optionMatch(zA, "shellext") ) bLoadExt = 1;
+    else
+#endif
+    if( zFile==0 ) zFile = zA;
+    else if( zProc==0 ) zProc = zA;
+    else return SHELL_INVALID_ARGS;
+  }
+#if SHELL_DYNAMIC_EXTENSION
+  if( bLoadExt ){
+    rc = load_shell_extension(p, zFile, zProc, pzErr);
+  }else
+#endif
+  {
+    open_db(p, 0);
+    rc = sqlite3_load_extension(DBX(p), zFile, zProc, pzErr);
+  }
+  return rc!=SQLITE_OK;
 }
 
 DISPATCHABLE_COMMAND( log ? 2 2 ){
   const char *zFile = azArg[1];
-  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
-  output_file_close(p->pLog);
-  p->pLog = output_file_open(zFile, 0);
+  if( ISS(p)->bSafeMode ) return SHELL_FORBIDDEN_OP;
+  output_file_close(ISS(p)->pLog);
+  ISS(p)->pLog = output_file_open(zFile, 0);
   return 0;
 }
 
-static void effectMode(ShellState *p, u8 modeRequest, u8 modeNominal){
+static void effectMode(ShellInState *psi, u8 modeRequest, u8 modeNominal){
   /* Effect the specified mode change. */
   const char *zColSep = 0, *zRowSep = 0;
   assert(modeNominal!=MODE_COUNT_OF);
@@ -9453,8 +9643,8 @@ static void effectMode(ShellState *p, u8 modeRequest, u8 modeNominal){
     zRowSep = SEP_Row;
     break;
   case MODE_Column:
-    if( (p->shellFlgs & SHFLG_HeaderSet)==0 ){
-      p->showHeader = 1;
+    if( (psi->shellFlgs & SHFLG_HeaderSet)==0 ){
+      psi->showHeader = 1;
     }
     zRowSep = SEP_Row;
     break;
@@ -9502,12 +9692,12 @@ static void effectMode(ShellState *p, u8 modeRequest, u8 modeNominal){
     return;
   }
   if( zRowSep!=0 ){
-    sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, zRowSep);
+    sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, zRowSep);
   }
   if( zColSep!=0 ){
-    sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, zColSep);
+    sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator, zColSep);
   }
-  p->mode = modeNominal;
+  psi->mode = modeNominal;
 }
 
 /*****************
@@ -9540,6 +9730,7 @@ COLLECT_HELP_TEXT[
   "     TABLE          The name of SQL table used for \"insert\" mode",
 ];
 DISPATCHABLE_COMMAND( mode ? 1 0 ){
+  ShellInState *psi = ISS(p);
   const char *zTabname = 0;
   const char *zArg;
   int i;
@@ -9583,30 +9774,31 @@ DISPATCHABLE_COMMAND( mode ? 1 0 ){
     }
   } /* Arg loop */
   if( foundMode==MODE_COUNT_OF ){
+    FILE *out = psi->out;
     const char *zMode;
     int nms;
-    i = p->mode;
+    i = psi->mode;
     assert(i>=0 && i<MODE_COUNT_OF);
     zMode = modeDescr[i].zModeName;
     nms = strlen30(zMode)-modeDescr[i].bDepluralize;
     /* Mode not specified. Show present mode (and toss any options set.) */
-    if( MODE_IS_COLUMNAR(p->mode) ){
+    if( MODE_IS_COLUMNAR(psi->mode) ){
       raw_printf
-        (p->out, "current output mode: %.*s --wrap %d --wordwrap %s --%squote\n",
-         nms, zMode, p->cmOpts.iWrap,
-         p->cmOpts.bWordWrap ? "on" : "off",
-         p->cmOpts.bQuote ? "" : "no");
+        (out, "current output mode: %.*s --wrap %d --wordwrap %s --%squote\n",
+         nms, zMode, psi->cmOpts.iWrap,
+         psi->cmOpts.bWordWrap ? "on" : "off",
+         psi->cmOpts.bQuote ? "" : "no");
     }else{
-      raw_printf(p->out, "current output mode: %.*s\n", nms, zMode);
+      raw_printf(out, "current output mode: %.*s\n", nms, zMode);
     }
   }else{
-    effectMode(p, foundMode, setMode);
-    if( MODE_IS_COLUMNAR(setMode) ) p->cmOpts = cmOpts;
+    effectMode(psi, foundMode, setMode);
+    if( MODE_IS_COLUMNAR(setMode) ) psi->cmOpts = cmOpts;
     else if( setMode==MODE_Insert ){
       set_table_name(p, zTabname ? zTabname : "table");
     }
   }
-  p->cMode = p->mode;
+  psi->cMode = psi->mode;
   return 0;
  flag_unknown:
   utf8_printf(STD_ERR, "Error: Unknown .mode option: %s\nValid options:\n%s",
@@ -9647,11 +9839,14 @@ COLLECT_HELP_TEXT[
   ".nullvalue STRING        Use STRING in place of NULL values",
 ];
 DISPATCHABLE_COMMAND( open 3 1 0 ){
+  ShellInState *psi = ISS(p);
   const char *zFN = 0;     /* Pointer to constant filename */
   char *zNewFilename = 0;  /* Name of the database file to open */
   int iName = 1;           /* Index in azArg[] of the filename */
   int newFlag = 0;         /* True to delete file before opening */
   u8 openMode = SHELL_OPEN_UNSPEC;
+  int openFlags = 0;
+  sqlite3_int64 szMax = 0;
   int rc = 0;
   /* Check for command-line arguments */
   for(iName=1; iName<nArg; iName++){
@@ -9667,14 +9862,14 @@ DISPATCHABLE_COMMAND( open 3 1 0 ){
     }else if( optionMatch(z, "readonly") ){
       openMode = SHELL_OPEN_READONLY;
     }else if( optionMatch(z, "nofollow") ){
-      p->openFlags |= SQLITE_OPEN_NOFOLLOW;
+      openFlags |= SQLITE_OPEN_NOFOLLOW;
 #ifndef SQLITE_OMIT_DESERIALIZE
     }else if( optionMatch(z, "deserialize") ){
       openMode = SHELL_OPEN_DESERIALIZE;
     }else if( optionMatch(z, "hexdb") ){
       openMode = SHELL_OPEN_HEXDB;
     }else if( optionMatch(z, "maxsize") && iName+1<nArg ){
-      p->szMax = integerValue(azArg[++iName]);
+      szMax = integerValue(azArg[++iName]);
 #endif /* SQLITE_OMIT_DESERIALIZE */
     }else if( z[0]=='-' ){
       *pzErr = shellMPrintf(0,"unknown option: %s\n", z);
@@ -9688,21 +9883,21 @@ DISPATCHABLE_COMMAND( open 3 1 0 ){
   }
 
   /* Close the existing database */
-  session_close_all(p, -1);
-  close_db(p->db);
-  p->db = 0;
-  p->pAuxDb->zDbFilename = 0;
-  sqlite3_free(p->pAuxDb->zFreeOnClose);
-  p->pAuxDb->zFreeOnClose = 0;
-  p->openMode = openMode;
-  p->openFlags = 0;
-  p->szMax = 0;
+  session_close_all(psi, -1);
+  close_db(DBX(p));
+  DBX(p) = 0;
+  psi->pAuxDb->zDbFilename = 0;
+  sqlite3_free(psi->pAuxDb->zFreeOnClose);
+  psi->pAuxDb->zFreeOnClose = 0;
+  psi->openMode = openMode;
+  psi->openFlags = 0;
+  psi->szMax = 0;
 
   /* If a filename is specified, try to open it first */
-  if( zFN || p->openMode==SHELL_OPEN_HEXDB ){
-    if( newFlag && zFN && !p->bSafeMode ) shellDeleteFile(zFN);
-    if( p->bSafeMode
-        && p->openMode!=SHELL_OPEN_HEXDB
+  if( zFN || psi->openMode==SHELL_OPEN_HEXDB ){
+    if( newFlag && zFN && !psi->bSafeMode ) shellDeleteFile(zFN);
+    if( psi->bSafeMode
+        && psi->openMode!=SHELL_OPEN_HEXDB
         && zFN
         && strcmp(zFN,":memory:")!=0
         ){
@@ -9715,38 +9910,41 @@ DISPATCHABLE_COMMAND( open 3 1 0 ){
     }else{
       zNewFilename = 0;
     }
-    p->pAuxDb->zDbFilename = zNewFilename;
+    psi->pAuxDb->zDbFilename = zNewFilename;
+    psi->openFlags = openFlags;
+    psi->szMax = szMax;
     open_db(p, OPEN_DB_KEEPALIVE);
-    if( p->db==0 ){
+    if( DBX(p)==0 ){
       *pzErr = shellMPrintf(0,"Error: cannot open '%s'\n", zNewFilename);
       sqlite3_free(zNewFilename);
       rc = 1;
     }else{
-      p->pAuxDb->zFreeOnClose = zNewFilename;
+      psi->pAuxDb->zFreeOnClose = zNewFilename;
     }
   }
-  if( p->db==0 ){
+  if( DBX(p)==0 ){
     /* As a fall-back open a TEMP database */
-    p->pAuxDb->zDbFilename = 0;
+    psi->pAuxDb->zDbFilename = 0;
     open_db(p, 0);
   }
   return rc;
 }
 
 DISPATCHABLE_COMMAND( nonce ? 2 2 ){
-  if( p->zNonce==0 || strcmp(azArg[1],p->zNonce)!=0 ){
+  ShellInState *psi = ISS(p);
+  if( psi->zNonce==0 || strcmp(azArg[1],psi->zNonce)!=0 ){
     raw_printf(STD_ERR, "line %d: incorrect nonce: \"%s\"\n",
-               p->pInSource->lineno, azArg[1]);
+               psi->pInSource->lineno, azArg[1]);
     exit(1);
   }
   /* Suspend safe mode for 1 meta-command after this. */
-  p->bSafeModeFuture = 2;
+  psi->bSafeModeFuture = 2;
   return 0;
 }
 
 DISPATCHABLE_COMMAND( nullvalue ? 2 2 ){
-  sqlite3_snprintf(sizeof(p->nullValue), p->nullValue,
-                   "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]);
+  sqlite3_snprintf(sizeof(ISS(p)->nullValue), ISS(p)->nullValue, "%.*s",
+                   (int)ArraySize(ISS(p)->nullValue)-1, azArg[1]);
   return 0;
 }
 
@@ -10106,10 +10304,11 @@ static int param_set(sqlite3 *db, char cCast,
 }
 
 /* list or ls subcommand for .parameter dot-command */
-static void list_params(ShellState *p, ParamTableUse ptu, u8 bShort,
+static void list_params(ShellExState *psx, ParamTableUse ptu, u8 bShort,
                         char **pzArgs, int nArg){
   sqlite3_stmt *pStmt = 0;
-  sqlite3_str *sbList = sqlite3_str_new(p->db);
+  sqlite3 *db = DBX(psx);
+  sqlite3_str *sbList = sqlite3_str_new(db);
   int len = 0, rc;
   char *zFromWhere = 0;
   char *zSql = 0;
@@ -10121,7 +10320,7 @@ static void list_params(ShellState *p, ParamTableUse ptu, u8 bShort,
   shell_check_oom(zFromWhere);
   zSql = sqlite3_mprintf("SELECT max(length(key)) %s", zFromWhere);
   shell_check_oom(zSql);
-  rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
   if( rc==SQLITE_OK ){
     sqlite3_bind_int(pStmt, 1, ptu);
     if( sqlite3_step(pStmt)==SQLITE_ROW ){
@@ -10133,30 +10332,31 @@ static void list_params(ShellState *p, ParamTableUse ptu, u8 bShort,
   sqlite3_finalize(pStmt);
   pStmt = 0;
   if( len ){
+    FILE *out = ISS(psx)->out;
     sqlite3_free(zSql);
     if( !bShort ){
       int nBindings = 0, nScripts = 0;
       zSql = sqlite3_mprintf("SELECT key, uses, iif(uses, value, quote(value))"
                              " %z ORDER BY uses, key", zFromWhere);
       shell_check_oom(zSql);
-      rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+      rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
       sqlite3_bind_int(pStmt, 1, ptu);
       while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
         ParamTableUse ptux = sqlite3_column_int(pStmt,1);
         switch( ptux ){
         case PTU_Binding:
           if( nBindings++ == 0 ){
-            utf8_printf(p->out, "Binding Values:\n%-*s %s\n",
+            utf8_printf(out, "Binding Values:\n%-*s %s\n",
                         len, "name", "value");
           }
-          utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
+          utf8_printf(out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
                       sqlite3_column_text(pStmt,2));
           break;
         case PTU_Script:
           if( nScripts++ == 0 ){
-            utf8_printf(p->out, "Scripts\n%-*s %s\n", len, "name", "value");
+            utf8_printf(out, "Scripts\n%-*s %s\n", len, "name", "value");
           }
-          utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
+          utf8_printf(out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
                       sqlite3_column_text(pStmt,2));
           break;
         default: break; /* Ignore */
@@ -10166,13 +10366,13 @@ static void list_params(ShellState *p, ParamTableUse ptu, u8 bShort,
       int nc = 0, ncw = 78/(len+2);
       zSql = sqlite3_mprintf("SELECT key %z ORDER BY key", zFromWhere);
       shell_check_oom(zSql);
-      rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+      rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
       sqlite3_bind_int(pStmt, 1, ptu);
       while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
-        utf8_printf(p->out, "%s  %-*s", ((++nc%ncw==0)? "\n" : ""),
+        utf8_printf(out, "%s  %-*s", ((++nc%ncw==0)? "\n" : ""),
                     len, sqlite3_column_text(pStmt,0));
       }
-      if( nc>0 ) utf8_printf(p->out, "\n");
+      if( nc>0 ) utf8_printf(out, "\n");
     }
     sqlite3_finalize(pStmt);
   }else{
@@ -10232,7 +10432,7 @@ COLLECT_HELP_TEXT[
   "   clear ?NAMES?           Erase all or only given named parameters",
 #ifndef SQLITE_NOHAVE_SYSTEM
   "   edit ?OPT? NAME ...     Use edit() to create or alter parameter NAME",
-  "      OPT may be -t or -e to use edited value as text or evaluate it first.",
+  "      OPT may be -t to use edited value as text or -e to evaluate it.",
 #endif
   "   init                    Initialize TEMP table for bindings and scripts",
   "   list ?PATTERNS?         List parameters table binding and script values",
@@ -10243,7 +10443,7 @@ COLLECT_HELP_TEXT[
   "      If FILE missing, empty or '~', it defaults to ~/sqlite_params.sdb",
   "   set ?TOPT? NAME VALUE   Give SQL parameter NAME a value of VALUE",
   "      NAME must begin with one of $,:,@,? for bindings, or with a letter",
-  "      to be executable; the value is following argument list, space-joined.",
+  "      to be executable; value is the space-joined argument list.",
   "      Option TOPT may be one of {-b -i -n -r -t} to cast effective value",
   "      to BLOB, INT, NUMERIC, REAL or TEXT respectively.",
   "   unset ?NAMES?           Remove named parameter(s) from parameters table",
@@ -10251,14 +10451,15 @@ COLLECT_HELP_TEXT[
 DISPATCHABLE_COMMAND( parameter 2 2 0 ){
   int rc = 0;
   open_db(p,0);
+  sqlite3 *db = DBX(p);
 
   /* .parameter clear  and  .parameter unset ?NAMES?
   **  Delete some or all parameters from the TEMP table that holds them.
   **  Without any arguments, clear deletes them all and unset does nothing.
   */
   if( strcmp(azArg[1],"clear")==0 || strcmp(azArg[1],"unset")==0 ){
-    if( param_table_exists(p->db) && (nArg>2 || azArg[1][0]=='c') ){
-      sqlite3_str *sbZap = sqlite3_str_new(p->db);
+    if( param_table_exists(db) && (nArg>2 || azArg[1][0]=='c') ){
+      sqlite3_str *sbZap = sqlite3_str_new(db);
       char *zSql;
       sqlite3_str_appendf
         (sbZap, "DELETE FROM "PARAM_TABLE_SNAME" WHERE key ");
@@ -10266,7 +10467,7 @@ DISPATCHABLE_COMMAND( parameter 2 2 0 ){
                        (const char **)&azArg[2], (const char **)&azArg[nArg]);
       zSql = sqlite3_str_finish(sbZap);
       shell_check_oom(zSql);
-      sqlite3_exec(p->db, zSql, 0, 0, 0);
+      sqlite3_exec(db, zSql, 0, 0, 0);
       sqlite3_free(zSql);
     }
   }else
@@ -10276,28 +10477,29 @@ DISPATCHABLE_COMMAND( parameter 2 2 0 ){
   ** New ones get a uses tag auto-selected by their leading char.
   */
   if( strcmp(azArg[1],"edit")==0 ){
+    ShellInState *psi = ISS(p);
     int ia = 2;
     int eval = 0;
-    if( !INSOURCE_IS_INTERACTIVE(p->pInSource) ){
+    if( !INSOURCE_IS_INTERACTIVE(psi->pInSource) ){
       utf8_printf(STD_ERR, "Error: "
                   ".parameter edit can only be used interactively.\n");
       return 1;
     }
-    param_table_init(p);
-    if( p->zEditor==0 ){
+    param_table_init(db);
+    if( psi->zEditor==0 ){
       const char *zE = getenv("VISUAL");
-      if( zE!=0 ) p->zEditor = sqlite3_mprintf("%s", zE);
+      if( zE!=0 ) psi->zEditor = sqlite3_mprintf("%s", zE);
     }
     if( nArg>=3 && azArg[2][0]=='-' ){
       char *zArg = (azArg[2][1]=='-')? azArg[2]+2 : azArg[2]+1;
       if( strncmp(zArg,"editor=",7)==0 ){
-        sqlite3_free(p->zEditor);
+        sqlite3_free(psi->zEditor);
         /* Accept an initial -editor=? option. */
-        p->zEditor = sqlite3_mprintf("%s", zArg+7);
+        psi->zEditor = sqlite3_mprintf("%s", zArg+7);
         ++ia;
       }
     }
-    if( p->zEditor==0 ){
+    if( psi->zEditor==0 ){
       utf8_printf(STD_ERR,
                   "Either set env-var VISUAL to name an"
                   " editor and restart, or rerun\n "
@@ -10328,7 +10530,7 @@ DISPATCHABLE_COMMAND( parameter 2 2 0 ){
                     " parameter name.\n", azArg[ia]);
         return 1;
       }
-      rc = edit_one_param(p->db, azArg[ia], eval, ptu, p->zEditor);
+      rc = edit_one_param(db, azArg[ia], eval, ptu, psi->zEditor);
       ++ia;
       if( rc!=0 ) return rc;
     }
@@ -10340,7 +10542,7 @@ DISPATCHABLE_COMMAND( parameter 2 2 0 ){
   ** Create it if necessary.
   */
   if( nArg==2 && strcmp(azArg[1],"init")==0 ){
-    param_table_init(p);
+    param_table_init(db);
   }else
 
   /* .parameter list|ls
@@ -10357,15 +10559,15 @@ DISPATCHABLE_COMMAND( parameter 2 2 0 ){
   ** Load all or named parameters from specified or default (DB) file.
   */
   if( strcmp(azArg[1],"load")==0 ){
-    param_table_init(p);
-    rc = parameters_load(p->db, (const char **)azArg+1, nArg-1);
+    param_table_init(db);
+    rc = parameters_load(db, (const char **)azArg+1, nArg-1);
   }else
 
   /* .parameter save
   ** Save all or named parameters into specified or default (DB) file.
   */
   if( strcmp(azArg[1],"save")==0 ){
-    rc = parameters_save(p->db, (const char **)azArg+1, nArg-1);
+    rc = parameters_save(db, (const char **)azArg+1, nArg-1);
   }else
 
   /* .parameter set NAME VALUE
@@ -10383,18 +10585,18 @@ DISPATCHABLE_COMMAND( parameter 2 2 0 ){
                   "Error: %s is not a usable parameter name.\n", azArg[inv]);
       rc = 1;
     }else{
-      param_table_init(p);
-      rc = param_set(p->db, cCast, azArg[inv],
+      param_table_init(db);
+      rc = param_set(db, cCast, azArg[inv],
                      &azArg[inv+1], &azArg[nArg], ptu);
       if( rc!=SQLITE_OK ){
-        utf8_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db));
+        utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(db));
         rc = 1;
       }
     }
   }else
 
   {  /* If no command name and arg count matches, show a syntax error */
-    showHelp(p->out, "parameter");
+    showHelp(ISS(p)->out, "parameter");
     return 1;
   }
 
@@ -10417,33 +10619,32 @@ COLLECT_HELP_TEXT[
 DISPATCHABLE_COMMAND( print 3 1 0 ){
   int i;
   for(i=1; i<nArg; i++){
-    if( i>1 ) raw_printf(p->out, " ");
-    utf8_printf(p->out, "%s", azArg[i]);
+    utf8_printf(ISS(p)->out, "%s%s", azArg[i], (i==nArg-1)? "\n" : " ");
   }
-  raw_printf(p->out, "\n");
   return 0;
 }
 DISPATCHABLE_COMMAND( progress 3 2 0 ){
+  ShellInState *psi = ISS(p);
   int i;
   int nn = 0;
-  p->flgProgress = 0;
-  p->mxProgress = 0;
-  p->nProgress = 0;
+  psi->flgProgress = 0;
+  psi->mxProgress = 0;
+  psi->nProgress = 0;
   for(i=1; i<nArg; i++){
     const char *z = azArg[i];
     if( z[0]=='-' ){
       z++;
       if( z[0]=='-' ) z++;
       if( strcmp(z,"quiet")==0 || strcmp(z,"q")==0 ){
-        p->flgProgress |= SHELL_PROGRESS_QUIET;
+        psi->flgProgress |= SHELL_PROGRESS_QUIET;
         continue;
       }
       if( strcmp(z,"reset")==0 ){
-        p->flgProgress |= SHELL_PROGRESS_RESET;
+        psi->flgProgress |= SHELL_PROGRESS_RESET;
         continue;
       }
       if( strcmp(z,"once")==0 ){
-        p->flgProgress |= SHELL_PROGRESS_ONCE;
+        psi->flgProgress |= SHELL_PROGRESS_ONCE;
         continue;
       }
       if( strcmp(z,"limit")==0 ){
@@ -10451,18 +10652,18 @@ DISPATCHABLE_COMMAND( progress 3 2 0 ){
           *pzErr = shellMPrintf(0,"Error: missing argument on --limit\n");
           return SHELL_INVALID_ARGS;
         }else{
-          p->mxProgress = (int)integerValue(azArg[++i]);
+          psi->mxProgress = (int)integerValue(azArg[++i]);
         }
         continue;
       }
-      *pzErr = shellMPrintf(0,"Error: unknown option: \"%s\"\n", azArg[i]);
+      *pzErr = shellMPrintf(0, "Error: unknown option: \"%s\"\n", azArg[i]);
       return SHELL_INVALID_ARGS;
     }else{
       nn = (int)integerValue(z);
     }
   }
   open_db(p, 0);
-  sqlite3_progress_handler(p->db, nn, progress_handler, p);
+  sqlite3_progress_handler(DBX(p), nn, progress_handler, psi);
   return 0;
 }
 /* Allow too few arguments by tradition, (a form of no-op.) */
@@ -10495,12 +10696,12 @@ DISPATCHABLE_COMMAND( read 3 2 2 ){
   int rc = 0;
   FILE *inUse = 0;
   int (*fCloser)(FILE *) = 0;
-  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
+  if( ISS(p)->bSafeMode ) return SHELL_FORBIDDEN_OP;
   if( azArg[1][0]=='|' ){
 #ifdef SQLITE_OMIT_POPEN
     *pzErr = shellMPrintf(0,"Error: pipes are not supported in this OS\n");
     rc = 1;
-    p->out = STD_OUT; /* This is likely not needed. To be investigated. */
+    /* p->out = STD_OUT; This was likely not needed. To be investigated. */
 #else
     inUse = popen(azArg[1]+1, "r");
     if( inUse==0 ){
@@ -10518,12 +10719,12 @@ DISPATCHABLE_COMMAND( read 3 2 2 ){
   }
   if( inUse!=0 ){
     InSource inSourceRedir
-      = INSOURCE_FILE_REDIR(inUse, azArg[1], p->pInSource);
-    p->pInSource = &inSourceRedir;
-    rc = process_input(p);
+      = INSOURCE_FILE_REDIR(inUse, azArg[1], ISS(p)->pInSource);
+    ISS(p)->pInSource = &inSourceRedir;
+    rc = process_input(ISS(p));
     assert(fCloser!=0);
     fCloser(inUse);
-    p->pInSource = p->pInSource->pFrom;
+    ISS(p)->pInSource = inSourceRedir.pFrom;
   }
   return rc;
 }
@@ -10534,7 +10735,8 @@ DISPATCHABLE_COMMAND( read 3 2 2 ){
 ** on stream pState->out.
 */
 DISPATCHABLE_COMMAND( recover ? 1 7 ){
-  open_db(p, 0);
+  FILE *out = ISS(p)->out;
+  sqlite3 *db;
   int rc = SQLITE_OK;
   sqlite3_stmt *pLoop = 0;        /* Loop through all root pages */
   sqlite3_stmt *pPages = 0;       /* Loop through all pages in a group */
@@ -10545,6 +10747,9 @@ DISPATCHABLE_COMMAND( recover ? 1 7 ){
   int nOrphan = -1;
   RecoverTable *pOrphan = 0;
 
+  open_db(p, 0);
+  db = DBX(p);
+
   int bFreelist = 1;              /* 0 if --freelist-corrupt is specified */
   int bRowids = 1;                /* 0 if --no-rowids */
   for(i=1; i<nArg; i++){
@@ -10568,12 +10773,12 @@ DISPATCHABLE_COMMAND( recover ? 1 7 ){
     }
     else{
       *pzErr = shellMPrintf(0,"unexpected option: %s\n", azArg[i]);
-      showHelp(p->out, azArg[0]);
+      showHelp(out, azArg[0]);
       return 1;
     }
   }
 
-  shellExecPrintf(p->db, &rc,
+  shellExecPrintf(db, &rc,
     /* Attach an in-memory database named 'recovery'. Create an indexed
     ** cache of the sqlite_dbptr virtual table. */
     "PRAGMA writable_schema = on;"
@@ -10586,7 +10791,7 @@ DISPATCHABLE_COMMAND( recover ? 1 7 ){
   );
 
   if( bFreelist ){
-    shellExec(p->db, &rc,
+    shellExec(db, &rc,
       "WITH trunk(pgno) AS ("
       "  SELECT shell_int32("
       "      (SELECT data FROM sqlite_dbpage WHERE pgno=1), 8) AS x "
@@ -10610,7 +10815,7 @@ DISPATCHABLE_COMMAND( recover ? 1 7 ){
   /* If this is an auto-vacuum database, add all pointer-map pages to
   ** the freelist table. Do this regardless of whether or not
   ** --freelist-corrupt was specified.  */
-  shellExec(p->db, &rc,
+  shellExec(db, &rc,
     "WITH ptrmap(pgno) AS ("
     "  SELECT 2 WHERE shell_int32("
     "    (SELECT data FROM sqlite_dbpage WHERE pgno=1), 13"
@@ -10622,7 +10827,7 @@ DISPATCHABLE_COMMAND( recover ? 1 7 ){
     "REPLACE INTO recovery.freelist SELECT pgno FROM ptrmap"
   );
 
-  shellExec(p->db, &rc,
+  shellExec(db, &rc,
     "CREATE TABLE recovery.dbptr("
     "      pgno, child, PRIMARY KEY(child, pgno)"
     ") WITHOUT ROWID;"
@@ -10699,25 +10904,23 @@ DISPATCHABLE_COMMAND( recover ? 1 7 ){
     ** foreign key constraints to be violated. So disable foreign-key
     ** constraint enforcement to prevent problems when running the output
     ** script. */
-    raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n");
-    raw_printf(p->out, "BEGIN;\n");
-    raw_printf(p->out, "PRAGMA writable_schema = on;\n");
-    shellPrepare(p->db, &rc,
+    raw_printf(out, "PRAGMA foreign_keys=OFF;\n");
+    raw_printf(out, "BEGIN;\n");
+    raw_printf(out, "PRAGMA writable_schema = on;\n");
+    shellPrepare(db, &rc,
         "SELECT sql FROM recovery.schema "
         "WHERE type='table' AND sql LIKE 'create table%'", &pStmt
     );
     while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
       const char *zCreateTable = (const char*)sqlite3_column_text(pStmt, 0);
-      raw_printf(p->out, "CREATE TABLE IF NOT EXISTS %s;\n",
-          &zCreateTable[12]
-      );
+      raw_printf(out, "CREATE TABLE IF NOT EXISTS %s;\n", &zCreateTable[12]);
     }
     shellFinalize(&rc, pStmt);
   }
 
   /* Figure out if an orphan table will be required. And if so, how many
   ** user columns it should contain */
-  shellPrepare(p->db, &rc,
+  shellPrepare(db, &rc,
       "SELECT coalesce(max(maxlen), -2) FROM recovery.map WHERE root>1"
       , &pLoop
   );
@@ -10727,11 +10930,11 @@ DISPATCHABLE_COMMAND( recover ? 1 7 ){
   shellFinalize(&rc, pLoop);
   pLoop = 0;
 
-  shellPrepare(p->db, &rc,
+  shellPrepare(db, &rc,
       "SELECT pgno FROM recovery.map WHERE root=?", &pPages
   );
 
-  shellPrepare(p->db, &rc,
+  shellPrepare(db, &rc,
       "SELECT max(field), group_concat(shell_escape_crnl(quote"
       "(case when (? AND field<0) then NULL else value end)"
       "), ', ')"
@@ -10741,7 +10944,7 @@ DISPATCHABLE_COMMAND( recover ? 1 7 ){
   );
 
   /* Loop through each root page. */
-  shellPrepare(p->db, &rc,
+  shellPrepare(db, &rc,
       "SELECT root, intkey, max(maxlen) FROM recovery.map"
       " WHERE root>1 GROUP BY root, intkey ORDER BY root=("
       "  SELECT rootpage FROM recovery.schema WHERE name='sqlite_sequence'"
@@ -10755,18 +10958,18 @@ DISPATCHABLE_COMMAND( recover ? 1 7 ){
     RecoverTable *pTab;
 
     assert( bIntkey==0 || bIntkey==1 );
-    pTab = recoverFindTable(p, &rc, iRoot, bIntkey, nCol, &bNoop);
+    pTab = recoverFindTable(db, &rc, iRoot, bIntkey, nCol, &bNoop);
     if( bNoop || rc ) continue;
     if( pTab==0 ){
       if( pOrphan==0 ){
-        pOrphan = recoverOrphanTable(p, &rc, zLostAndFound, nOrphan);
+        pOrphan = recoverOrphanTable(db, out, &rc, zLostAndFound, nOrphan);
       }
       pTab = pOrphan;
       if( pTab==0 ) break;
     }
 
     if( 0==sqlite3_stricmp(pTab->zQuoted, "\"sqlite_sequence\"") ){
-      raw_printf(p->out, "DELETE FROM sqlite_sequence;\n");
+      raw_printf(out, "DELETE FROM sqlite_sequence;\n");
     }
     sqlite3_bind_int(pPages, 1, iRoot);
     if( bRowids==0 && pTab->iPk<0 ){
@@ -10787,7 +10990,7 @@ DISPATCHABLE_COMMAND( recover ? 1 7 ){
         RecoverTable *pTab2 = pTab;
         if( pTab!=pOrphan && (iMin<0)!=bIntkey ){
           if( pOrphan==0 ){
-            pOrphan = recoverOrphanTable(p, &rc, zLostAndFound, nOrphan);
+            pOrphan = recoverOrphanTable(db, out, &rc, zLostAndFound, nOrphan);
           }
           pTab2 = pOrphan;
           if( pTab2==0 ) break;
@@ -10795,13 +10998,13 @@ DISPATCHABLE_COMMAND( recover ? 1 7 ){
 
         nField = nField+1;
         if( pTab2==pOrphan ){
-          raw_printf(p->out,
+          raw_printf(out,
               "INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n",
               pTab2->zQuoted, iRoot, iPgno, nField,
               iMin<0 ? "" : "NULL, ", zVal, pTab2->azlCol[nField]
           );
         }else{
-          raw_printf(p->out, "INSERT INTO %s(%s) VALUES( %s );\n",
+          raw_printf(out, "INSERT INTO %s(%s) VALUES( %s );\n",
               pTab2->zQuoted, pTab2->azlCol[nField], zVal
           );
         }
@@ -10819,7 +11022,7 @@ DISPATCHABLE_COMMAND( recover ? 1 7 ){
   /* The rest of the schema */
   if( rc==SQLITE_OK ){
     sqlite3_stmt *pStmt = 0;
-    shellPrepare(p->db, &rc,
+    shellPrepare(db, &rc,
         "SELECT sql, name FROM recovery.schema "
         "WHERE sql NOT LIKE 'create table%'", &pStmt
     );
@@ -10831,20 +11034,20 @@ DISPATCHABLE_COMMAND( recover ? 1 7 ){
           "INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)",
           zName, zName, zSql
         );
-        raw_printf(p->out, "%s;\n", zPrint);
+        raw_printf(out, "%s;\n", zPrint);
         sqlite3_free(zPrint);
       }else{
-        raw_printf(p->out, "%s;\n", zSql);
+        raw_printf(out, "%s;\n", zSql);
       }
     }
     shellFinalize(&rc, pStmt);
   }
 
   if( rc==SQLITE_OK ){
-    raw_printf(p->out, "PRAGMA writable_schema = off;\n");
-    raw_printf(p->out, "COMMIT;\n");
+    raw_printf(out, "PRAGMA writable_schema = off;\n");
+    raw_printf(out, "COMMIT;\n");
   }
-  sqlite3_exec(p->db, "DETACH recovery", 0, 0, 0);
+  sqlite3_exec(db, "DETACH recovery", 0, 0, 0);
   return rc;
 }
 
@@ -10856,7 +11059,7 @@ DISPATCHABLE_COMMAND( restore ? 2 3 ){
   sqlite3_backup *pBackup;
   int nTimeout = 0;
 
-  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
+  if( ISS(p)->bSafeMode ) return SHELL_FORBIDDEN_OP;
   if( nArg==2 ){
     zSrcFile = azArg[1];
     zDb = "main";
@@ -10873,9 +11076,9 @@ DISPATCHABLE_COMMAND( restore ? 2 3 ){
     return 1;
   }
   open_db(p, 0);
-  pBackup = sqlite3_backup_init(p->db, zDb, pSrc, "main");
+  pBackup = sqlite3_backup_init(DBX(p), zDb, pSrc, "main");
   if( pBackup==0 ){
-    *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
+    *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(DBX(p)));
     close_db(pSrc);
     return 1;
   }
@@ -10893,7 +11096,7 @@ DISPATCHABLE_COMMAND( restore ? 2 3 ){
     *pzErr = shellMPrintf(0,"Error: source database is busy\n");
     rc = 1;
   }else{
-    *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
+    *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(DBX(p)));
     rc = 1;
   }
   close_db(pSrc);
@@ -10911,16 +11114,17 @@ COLLECT_HELP_TEXT[
   "      --nosys              Omit objects whose names start with \"sqlite_\"",
 ];
 DISPATCHABLE_COMMAND( scanstats ? 2 2 ){
-    p->scanstatsOn = (u8)booleanValue(azArg[1]);
+  ISS(p)->scanstatsOn = (u8)booleanValue(azArg[1]);
 #ifndef SQLITE_ENABLE_STMT_SCANSTATUS
-    raw_printf(STD_ERR, "Warning: .scanstats not available in this build.\n");
+  raw_printf(STD_ERR, "Warning: .scanstats not available in this build.\n");
 #endif
   return 0;
 }
 DISPATCHABLE_COMMAND( schema ? 1 2 ){
   int rc;
   ShellText sSelect;
-  ShellState data;
+  ShellInState data;
+  ShellExState datax;
   char *zErrMsg = 0;
   const char *zDiv = "(";
   const char *zName = 0;
@@ -10930,7 +11134,12 @@ DISPATCHABLE_COMMAND( schema ? 1 2 ){
   int ii;
 
   open_db(p, 0);
-  memcpy(&data, p, sizeof(data));
+  /* Consider some refactoring to avoid duplicative wholesale copying. */
+  memcpy(&data, ISS(p), sizeof(data));
+  memcpy(&datax, p, sizeof(datax));
+  data.pSXS = &datax;
+  datax.pSIS = &data;
+
   data.showHeader = 0;
   data.cMode = data.mode = MODE_Semi;
   initText(&sSelect);
@@ -10969,16 +11178,17 @@ DISPATCHABLE_COMMAND( schema ? 1 2 ){
       new_argv[1] = 0;
       new_colv[0] = "sql";
       new_colv[1] = 0;
-      callback(&data, 1, new_argv, new_colv);
+      callback(&datax, 1, new_argv, new_colv);
       sqlite3_free(new_argv[0]);
     }
   }
   if( zDiv ){
     sqlite3_stmt *pStmt = 0;
-    rc = sqlite3_prepare_v2(p->db, "SELECT name FROM pragma_database_list",
+    rc = sqlite3_prepare_v2(datax.dbUser,
+                            "SELECT name FROM pragma_database_list",
                             -1, &pStmt, 0);
     if( rc ){
-      *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
+      *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(datax.dbUser));
       sqlite3_finalize(pStmt);
       return 1;
     }
@@ -11040,22 +11250,21 @@ DISPATCHABLE_COMMAND( schema ? 1 2 ){
     appendText(&sSelect, "sql IS NOT NULL"
                " ORDER BY snum, rowid", 0);
     if( bDebug ){
-      utf8_printf(p->out, "SQL: %s;\n", sSelect.z);
+      utf8_printf(data.out, "SQL: %s;\n", sSelect.z);
     }else{
-      rc = sqlite3_exec(p->db, sSelect.z, callback, &data, &zErrMsg);
+      rc = sqlite3_exec(datax.dbUser, sSelect.z, callback, &datax, &zErrMsg);
     }
     freeText(&sSelect);
   }
   if( zErrMsg ){
     *pzErr = zErrMsg;
-    rc = 1;
+    return 1;
   }else if( rc != SQLITE_OK ){
     *pzErr = shellMPrintf(0,"Error: querying schema information\n");
-    rc = 1;
+    return 1;
   }else{
-    rc = 0;
+    return 0;
   }
-  return rc;
 }
 
 /*****************
@@ -11093,19 +11302,20 @@ DISPATCHABLE_COMMAND( selecttrace ? 1 0 ){
 }
 DISPATCHABLE_COMMAND( separator ? 2 3 ){
   if( nArg>=2 ){
-    sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator,
-                     "%.*s", (int)ArraySize(p->colSeparator)-1, azArg[1]);
+    sqlite3_snprintf(sizeof(ISS(p)->colSeparator), ISS(p)->colSeparator,
+                     "%.*s", (int)ArraySize(ISS(p)->colSeparator)-1, azArg[1]);
   }
   if( nArg>=3 ){
-    sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator,
-                     "%.*s", (int)ArraySize(p->rowSeparator)-1, azArg[2]);
+    sqlite3_snprintf(sizeof(ISS(p)->rowSeparator), ISS(p)->rowSeparator,
+                     "%.*s", (int)ArraySize(ISS(p)->rowSeparator)-1, azArg[2]);
   }
   return 0;
 }
 DISPATCHABLE_COMMAND( session 3 2 0 ){
   int rc = 0;
-  struct AuxDb *pAuxDb = p->pAuxDb;
+  struct AuxDb *pAuxDb = ISS(p)->pAuxDb;
   OpenSession *pSession = &pAuxDb->aSession[0];
+  FILE *out = ISS(p)->out;
   char **azCmd = &azArg[1];
   int iSes = 0;
   int nCmd = nArg - 1;
@@ -11148,15 +11358,15 @@ DISPATCHABLE_COMMAND( session 3 2 0 ){
   ** Write a changeset or patchset into a file.  The file is overwritten.
   */
   if( strcmp(azCmd[0],"changeset")==0 || strcmp(azCmd[0],"patchset")==0 ){
-    FILE *out = 0;
+    FILE *cs_out = 0;
     if( failIfSafeMode
         (p, "cannot run \".session %s\" in safe mode", azCmd[0]) ){
       rc = SHELL_FORBIDDEN_OP;
     }else{
       if( nCmd!=2 ) goto session_syntax_error;
       if( pSession->p==0 ) goto session_not_open;
-      out = fopen(azCmd[1], "wb");
-      if( out==0 ){
+      cs_out = fopen(azCmd[1], "wb");
+      if( cs_out==0 ){
         *pzErr = sqlite3_mprintf
           ("ERROR: cannot open \"%s\" for writing\n", azCmd[1]);
         rc = 1;
@@ -11169,15 +11379,15 @@ DISPATCHABLE_COMMAND( session 3 2 0 ){
           rc = sqlite3session_patchset(pSession->p, &szChng, &pChng);
         }
         if( rc ){
-          fprintf(STD_OUT, "Error: error code %d\n", rc);
+          fprintf(out, "Error: error code %d\n", rc);
           rc = 0;
         }
-        if( pChng && fwrite(pChng, szChng, 1, out)!=1 ){
+        if( pChng && fwrite(pChng, szChng, 1, cs_out)!=1 ){
           raw_printf(STD_ERR, "ERROR: Failed to write entire %d-byte output\n",
                      szChng);
         }
         sqlite3_free(pChng);
-        fclose(out);
+        fclose(cs_out);
       }
     }
   }else
@@ -11202,7 +11412,7 @@ DISPATCHABLE_COMMAND( session 3 2 0 ){
     ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
     if( pAuxDb->nSession ){
       ii = sqlite3session_enable(pSession->p, ii);
-      utf8_printf(p->out, "session %s enable flag = %d\n",
+      utf8_printf(out, "session %s enable flag = %d\n",
                   pSession->zName, ii);
     }
   }else
@@ -11240,7 +11450,7 @@ DISPATCHABLE_COMMAND( session 3 2 0 ){
     ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
     if( pAuxDb->nSession ){
       ii = sqlite3session_indirect(pSession->p, ii);
-      utf8_printf(p->out, "session %s indirect flag = %d\n",
+      utf8_printf(out, "session %s indirect flag = %d\n",
                   pSession->zName, ii);
     }
   }else
@@ -11253,7 +11463,7 @@ DISPATCHABLE_COMMAND( session 3 2 0 ){
     if( nCmd!=1 ) goto session_syntax_error;
     if( pAuxDb->nSession ){
       ii = sqlite3session_isempty(pSession->p);
-      utf8_printf(p->out, "session %s isempty flag = %d\n",
+      utf8_printf(out, "session %s isempty flag = %d\n",
                   pSession->zName, ii);
     }
   }else
@@ -11263,7 +11473,7 @@ DISPATCHABLE_COMMAND( session 3 2 0 ){
   */
   if( strcmp(azCmd[0],"list")==0 ){
     for(i=0; i<pAuxDb->nSession; i++){
-      utf8_printf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName);
+      utf8_printf(out, "%d %s\n", i, pAuxDb->aSession[i].zName);
     }
   }else
 
@@ -11288,7 +11498,7 @@ DISPATCHABLE_COMMAND( session 3 2 0 ){
       return rc;
     }
     pSession = &pAuxDb->aSession[pAuxDb->nSession];
-    rc = sqlite3session_create(p->db, azCmd[1], &pSession->p);
+    rc = sqlite3session_create(DBX(p), azCmd[1], &pSession->p);
     if( rc ){
       *pzErr = sqlite3_mprintf
         ("Cannot open session: error code=%d\n", rc);
@@ -11302,7 +11512,7 @@ DISPATCHABLE_COMMAND( session 3 2 0 ){
 
   /* If no command name matches, show a syntax error */
   session_syntax_error:
-    showHelp(p->out, "session");
+    showHelp(out, "session");
     return 1;
   }
   return rc;
@@ -11360,7 +11570,7 @@ DISPATCHABLE_COMMAND( sha3sum 4 1 1 ){
       " AND name NOT LIKE 'sqlite_%'"
       " ORDER BY 1 collate nocase";
   }
-  sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+  sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0);
   initText(&sQuery);
   initText(&sSql);
   appendText(&sSql, "WITH [sha3sum$query](a,b) AS(",0);
@@ -11412,7 +11622,7 @@ DISPATCHABLE_COMMAND( sha3sum 4 1 1 ){
   freeText(&sQuery);
   freeText(&sSql);
   if( bDebug ){
-    utf8_printf(p->out, "%s\n", zSql);
+    utf8_printf(ISS(p)->out, "%s\n", zSql);
   }else{
     shell_exec(p, zSql, 0);
   }
@@ -11444,6 +11654,7 @@ COLLECT_HELP_TEXT[
 ];
 DISPATCHABLE_COMMAND( selftest 4 0 0 ){
   int rc;
+  ShellInState *psi = ISS(p);
   int bIsInit = 0;         /* True to initialize the SELFTEST table */
   int bVerbose = 0;        /* Verbose output */
   int bSelftestExists;     /* True if SELFTEST already exists */
@@ -11453,7 +11664,6 @@ DISPATCHABLE_COMMAND( selftest 4 0 0 ){
   ShellText str;           /* Answer for a query */
   sqlite3_stmt *pStmt = 0; /* Query against the SELFTEST table */
 
-  open_db(p,0);
   for(i=1; i<nArg; i++){
     const char *z = azArg[i];
     if( z[0]=='-' && z[1]=='-' ) z++;
@@ -11470,28 +11680,29 @@ DISPATCHABLE_COMMAND( selftest 4 0 0 ){
           return 1;
         }
   }
-  if( sqlite3_table_column_metadata(p->db,"main","selftest",0,0,0,0,0,0)
+  open_db(p,0);
+  if( sqlite3_table_column_metadata(DBX(p),"main","selftest",0,0,0,0,0,0)
       != SQLITE_OK ){
     bSelftestExists = 0;
   }else{
     bSelftestExists = 1;
   }
   if( bIsInit ){
-    createSelftestTable(p);
+    createSelftestTable(ISS(p));
     bSelftestExists = 1;
   }
   initText(&str);
   appendText(&str, "x", 0);
   for(k=bSelftestExists; k>=0; k--){
     if( k==1 ){
-      rc = sqlite3_prepare_v2(p->db,
+      rc = sqlite3_prepare_v2(DBX(p),
               "SELECT tno,op,cmd,ans FROM selftest ORDER BY tno",
               -1, &pStmt, 0);
     }else{
-      rc = sqlite3_prepare_v2(p->db,
-              "VALUES(0,'memo','Missing SELFTEST table - default checks only',''),"
-              "      (1,'run','PRAGMA integrity_check','ok')",
-              -1, &pStmt, 0);
+      rc = sqlite3_prepare_v2(DBX(p),
+          "VALUES(0,'memo','Missing SELFTEST table - default checks only',''),"
+          "      (1,'run','PRAGMA integrity_check','ok')",
+          -1, &pStmt, 0);
     }
     if( rc ){
       *pzErr = shellMPrintf(0,"Error querying the selftest table\n");
@@ -11507,29 +11718,30 @@ DISPATCHABLE_COMMAND( selftest 4 0 0 ){
       if( zOp==0 || zSql==0 || zAns==0 ) continue;
       k = 0;
       if( bVerbose>0 ){
+        /* This unusually directed output is for test purposes. */
         fprintf(STD_OUT, "%d: %s %s\n", tno, zOp, zSql);
       }
       if( strcmp(zOp,"memo")==0 ){
-        utf8_printf(p->out, "%s\n", zSql);
+        utf8_printf(psi->out, "%s\n", zSql);
       }else if( strcmp(zOp,"run")==0 ){
         char *zErrMsg = 0;
         str.n = 0;
         str.z[0] = 0;
-        rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg);
+        rc = sqlite3_exec(DBX(p), zSql, captureOutputCallback, &str, &zErrMsg);
         nTest++;
         if( bVerbose ){
-          utf8_printf(p->out, "Result: %s\n", str.z);
+          utf8_printf(psi->out, "Result: %s\n", str.z);
         }
         if( rc || zErrMsg ){
           nErr++;
           rc = 1;
-          utf8_printf(p->out, "%d: error-code-%d: %s\n", tno, rc, zErrMsg);
+          utf8_printf(psi->out, "%d: error-code-%d: %s\n", tno, rc, zErrMsg);
           sqlite3_free(zErrMsg);
         }else if( strcmp(zAns,str.z)!=0 ){
           nErr++;
           rc = 1;
-          utf8_printf(p->out, "%d: Expected: [%s]\n", tno, zAns);
-          utf8_printf(p->out, "%d:      Got: [%s]\n", tno, str.z);
+          utf8_printf(psi->out, "%d: Expected: [%s]\n", tno, zAns);
+          utf8_printf(psi->out, "%d:      Got: [%s]\n", tno, str.z);
         }
       }else{
         *pzErr = sqlite3_mprintf
@@ -11541,15 +11753,14 @@ DISPATCHABLE_COMMAND( selftest 4 0 0 ){
     sqlite3_finalize(pStmt);
   } /* End loop over k */
   freeText(&str);
-  utf8_printf(p->out, "%d errors out of %d tests\n", nErr, nTest);
+  utf8_printf(psi->out, "%d errors out of %d tests\n", nErr, nTest);
   return rc > 0;
 }
 
-#ifndef SQLITE_NOHAVE_SYSTEM
-static int shellOut(char *azArg[], int nArg, ShellState *p, char **pzErr){
+DISPATCHABLE_COMMAND( shell ? 2 0 ){
   char *zCmd;
   int i, x;
-  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
+  if( ISS(p)->bSafeMode ) return SHELL_FORBIDDEN_OP;
   zCmd = sqlite3_mprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]);
   shell_check_oom(zCmd);
   for(i=2; i<nArg; i++){
@@ -11562,10 +11773,6 @@ static int shellOut(char *azArg[], int nArg, ShellState *p, char **pzErr){
   if( x ) raw_printf(STD_ERR, "%s command returns %d\n", azArg[0], x);
   return 0;
 }
-#endif
-DISPATCHABLE_COMMAND( shell ? 2 0 ){
-  return shellOut(azArg, nArg, p, pzErr);
-}
 DISPATCHABLE_COMMAND( shxopts 3 0 0 ){
   static struct { const char *name; u8 mask; } shopts[] = {
 #if SHELL_DYNAMIC_COMMANDS
@@ -11580,6 +11787,7 @@ DISPATCHABLE_COMMAND( shxopts 3 0 0 ){
     {"all_opts", SHELL_ALL_EXTENSIONS}
   };
   const char *zMoan = 0, *zAbout = 0;
+  ShellInState *psi = ISS(p);
   int ia, io;
   if( nArg>1 ){
     for( ia=1; ia<nArg; ++ia ){
@@ -11591,8 +11799,8 @@ DISPATCHABLE_COMMAND( shxopts 3 0 0 ){
       }
       for( io=0; io<ArraySize(shopts); ++io ){
         if( strcmp(azArg[ia]+1, shopts[io].name)==0 ){
-          if( cs=='+' ) p->bExtendedDotCmds |= shopts[io].mask;
-          else p->bExtendedDotCmds &= ~shopts[io].mask;
+          if( cs=='+' ) psi->bExtendedDotCmds |= shopts[io].mask;
+          else psi->bExtendedDotCmds &= ~shopts[io].mask;
           break;
         }
       }
@@ -11603,13 +11811,13 @@ DISPATCHABLE_COMMAND( shxopts 3 0 0 ){
       }
     }
   }else{
-    raw_printf(p->out,
+    raw_printf(psi->out,
                "     name    value  \"-shxopts set\"\n"
                "   --------  -----  ---------------\n");
     for( io=0; io<ArraySize(shopts); ++io ){
       unsigned m = shopts[io].mask;
-      unsigned v = ((p->bExtendedDotCmds & m) == m)? 1 : 0;
-      raw_printf(p->out,
+      unsigned v = ((psi->bExtendedDotCmds & m) == m)? 1 : 0;
+      raw_printf(psi->out,
                  "  %9s   %2d    \"-shxopts 0x%02X\"\n",
                  shopts[io].name,  v, m);
     }
@@ -11620,67 +11828,71 @@ DISPATCHABLE_COMMAND( shxopts 3 0 0 ){
   return 1;
 }
 DISPATCHABLE_COMMAND( system ? 2 0 ){
-  return shellOut(azArg, nArg, p, pzErr);
+  return shellCommand(azArg, nArg, p, pzErr);
 }
 DISPATCHABLE_COMMAND( show ? 1 1 ){
   static const char *azBool[] = { "off", "on", "trigger", "full"};
   const char *zOut;
+  ShellInState *psi = ISS(p);
+  FILE *out = psi->out;
   int i;
-  utf8_printf(p->out, "%12.12s: %s\n","echo",
+  utf8_printf(out, "%12.12s: %s\n","echo",
               azBool[ShellHasFlag(p, SHFLG_Echo)]);
-  utf8_printf(p->out, "%12.12s: %s\n","eqp", azBool[p->autoEQP&3]);
-  utf8_printf(p->out, "%12.12s: %s\n","explain",
-              p->mode==MODE_Explain ? "on" : p->autoExplain ? "auto" : "off");
-  utf8_printf(p->out,"%12.12s: %s\n","headers", azBool[p->showHeader!=0]);
-  zOut = modeDescr[p->mode].zModeName;
-  i = strlen30(zOut) - modeDescr[p->mode].bDepluralize;
-  if( MODE_IS_COLUMNAR(p->mode) ){
+  utf8_printf(out, "%12.12s: %s\n","eqp", azBool[psi->autoEQP&3]);
+  utf8_printf(out, "%12.12s: %s\n","explain",
+              psi->mode==MODE_Explain
+              ? "on" : psi->autoExplain ? "auto" : "off");
+  utf8_printf(out,"%12.12s: %s\n","headers", azBool[psi->showHeader!=0]);
+  zOut = modeDescr[psi->mode].zModeName;
+  i = strlen30(zOut) - modeDescr[psi->mode].bDepluralize;
+  if( MODE_IS_COLUMNAR(psi->mode) ){
     utf8_printf
-      (p->out, "%12.12s: %.*s --wrap %d --wordwrap %s --%squote\n", "mode",
-       i, zOut, p->cmOpts.iWrap,
-       p->cmOpts.bWordWrap ? "on" : "off",
-       p->cmOpts.bQuote ? "" : "no");
+      (out, "%12.12s: %.*s --wrap %d --wordwrap %s --%squote\n", "mode",
+       i, zOut, psi->cmOpts.iWrap,
+       psi->cmOpts.bWordWrap ? "on" : "off",
+       psi->cmOpts.bQuote ? "" : "no");
   }else{
-    utf8_printf(p->out, "%12.12s: %.*s\n","mode", i, zOut);
-  }
-  utf8_printf(p->out, "%12.12s: ", "nullvalue");
-  output_c_string(p->out, p->nullValue);
-  raw_printf(p->out, "\n");
-  utf8_printf(p->out,"%12.12s: %s\n","output",
-              strlen30(p->outfile) ? p->outfile : "stdout");
-  utf8_printf(p->out,"%12.12s: ", "colseparator");
-  output_c_string(p->out, p->colSeparator);
-  raw_printf(p->out, "\n");
-  utf8_printf(p->out,"%12.12s: ", "rowseparator");
-  output_c_string(p->out, p->rowSeparator);
-  raw_printf(p->out, "\n");
-  switch( p->statsOn ){
+    utf8_printf(out, "%12.12s: %.*s\n","mode", i, zOut);
+  }
+  utf8_printf(out, "%12.12s: ", "nullvalue");
+  output_c_string(out, psi->nullValue);
+  raw_printf(out, "\n");
+  utf8_printf(out,"%12.12s: %s\n","output",
+              strlen30(psi->outfile) ? psi->outfile : "stdout");
+  utf8_printf(out,"%12.12s: ", "colseparator");
+  output_c_string(out, psi->colSeparator);
+  raw_printf(out, "\n");
+  utf8_printf(out,"%12.12s: ", "rowseparator");
+  output_c_string(out, psi->rowSeparator);
+  raw_printf(out, "\n");
+  switch( psi->statsOn ){
   case 0:  zOut = "off";     break;
   default: zOut = "on";      break;
   case 2:  zOut = "stmt";    break;
   case 3:  zOut = "vmstep";  break;
   }
-  utf8_printf(p->out, "%12.12s: %s\n","stats", zOut);
-  utf8_printf(p->out, "%12.12s: ", "width");
-  for (i=0;i<p->nWidth;i++) {
-    raw_printf(p->out, "%d ", p->colWidth[i]);
+  utf8_printf(out, "%12.12s: %s\n","stats", zOut);
+  utf8_printf(out, "%12.12s: ", "width");
+  for (i=0;i<p->numWidths;i++) {
+    raw_printf(out, "%d ", p->pSpecWidths[i]);
   }
-  raw_printf(p->out, "\n");
-  utf8_printf(p->out, "%12.12s: %s\n", "filename",
-              p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : "");
+  raw_printf(out, "\n");
+  utf8_printf(out, "%12.12s: %s\n", "filename",
+              psi->pAuxDb->zDbFilename ? psi->pAuxDb->zDbFilename : "");
   return 0;
 }
 DISPATCHABLE_COMMAND( stats ? 0 0 ){
+  ShellInState *psi = ISS(p);
   if( nArg==2 ){
     if( strcmp(azArg[1],"stmt")==0 ){
-      p->statsOn = 2;
+      psi->statsOn = 2;
     }else if( strcmp(azArg[1],"vmstep")==0 ){
-      p->statsOn = 3;
+      psi->statsOn = 3;
     }else{
-      p->statsOn = (u8)booleanValue(azArg[1]);
+      psi->statsOn = (u8)booleanValue(azArg[1]);
     }
   }else if( nArg==1 ){
-    display_stats(p->db, p, 0);
+    display_stats(DBX(p), psi, 0);
   }else{
     *pzErr = shellMPrintf(0,"Usage: .stats ?on|off|stmt|vmstep?\n");
     return 1;
@@ -11697,7 +11909,7 @@ COLLECT_HELP_TEXT[
   "                           If TABLE is specified, only show indexes for",
   "                           tables matching TABLE using the LIKE operator.",
 ];
-static int showTableLike(char *azArg[], int nArg, ShellState *p,
+static int showTableLike(char *azArg[], int nArg, ShellExState *p,
                          char **pzErr, char ot){
   int rc;
   sqlite3_stmt *pStmt;
@@ -11707,10 +11919,10 @@ static int showTableLike(char *azArg[], int nArg, ShellState *p,
   ShellText s;
   initText(&s);
   open_db(p, 0);
-  rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
+  rc = sqlite3_prepare_v2(DBX(p), "PRAGMA database_list", -1, &pStmt, 0);
   if( rc ){
     sqlite3_finalize(pStmt);
-    return shellDatabaseError(p->db);
+    return shellDatabaseError(DBX(p));
   }
 
   if( nArg>2 && ot=='i' ){
@@ -11765,10 +11977,10 @@ static int showTableLike(char *azArg[], int nArg, ShellState *p,
   rc = sqlite3_finalize(pStmt);
   if( rc==SQLITE_OK ){
     appendText(&s, " ORDER BY 1", 0);
-    rc = sqlite3_prepare_v2(p->db, s.z, -1, &pStmt, 0);
+    rc = sqlite3_prepare_v2(DBX(p), s.z, -1, &pStmt, 0);
   }
   freeText(&s);
-  if( rc ) return shellDatabaseError(p->db);
+  if( rc ) return shellDatabaseError(DBX(p));
 
   /* Run the SQL statement prepared by the above block. Store the results
   ** as an array of nul-terminated strings in azResult[].  */
@@ -11793,7 +12005,7 @@ static int showTableLike(char *azArg[], int nArg, ShellState *p,
     nRow++;
   }
   if( sqlite3_finalize(pStmt)!=SQLITE_OK ){
-    rc = shellDatabaseError(p->db);
+    rc = shellDatabaseError(DBX(p));
   }
 
   /* Pretty-print the contents of array azResult[] to the output */
@@ -11811,10 +12023,10 @@ static int showTableLike(char *azArg[], int nArg, ShellState *p,
     for(i=0; i<nPrintRow; i++){
       for(j=i; j<nRow; j+=nPrintRow){
         char *zSp = j<nPrintRow ? "" : "  ";
-        utf8_printf(p->out, "%s%-*s", zSp, maxlen,
+        utf8_printf(ISS(p)->out, "%s%-*s", zSp, maxlen,
                     azResult[j] ? azResult[j]:"");
       }
-      raw_printf(p->out, "\n");
+      raw_printf(ISS(p)->out, "\n");
     }
   }
 
@@ -11859,33 +12071,6 @@ DISPATCHABLE_COMMAND( indices 3 1 2 ){
   return showTableLike(azArg, nArg, p, pzErr, 'i');
 }
 
-/*****************
- * The .unmodule command
- */
-CONDITION_COMMAND( unmodule defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_VIRTUALTABLE) );
-COLLECT_HELP_TEXT[
-  ".unmodule NAME ...       Unregister virtual table modules",
-  "    --allexcept             Unregister everything except those named",
-];
-DISPATCHABLE_COMMAND( unmodule ? 2 0 ){
-  int ii;
-  int lenOpt;
-  char *zOpt;
-  open_db(p, 0);
-  zOpt = azArg[1];
-  if( zOpt[0]=='-' && zOpt[1]=='-' && zOpt[2]!=0 ) zOpt++;
-  lenOpt = (int)strlen(zOpt);
-  if( lenOpt>=3 && strncmp(zOpt, "-allexcept",lenOpt)==0 ){
-    assert( azArg[nArg]==0 );
-    sqlite3_drop_modules(p->db, nArg>2 ? (const char**)(azArg+2) : 0);
-  }else{
-    for(ii=1; ii<nArg; ii++){
-      sqlite3_create_module(p->db, azArg[ii], 0, 0);
-    }
-  }
-  return 0;
-}
-
 /*****************
  * The .testcase, .testctrl, .timeout, .timer and .trace commands
  */
@@ -11915,18 +12100,21 @@ COLLECT_HELP_TEXT[
 
 /* Begin redirecting output to the file "testcase-out.txt" */
 DISPATCHABLE_COMMAND( testcase ? 0 0 ){
-  output_reset(p);
-  p->out = output_file_open("testcase-out.txt", 0);
-  if( p->out==0 ){
+  ShellInState *psi = ISS(p);
+  output_reset(psi);
+  psi->out = output_file_open("testcase-out.txt", 0);
+  if( psi->out==0 ){
     raw_printf(STD_ERR, "Error: cannot open 'testcase-out.txt'\n");
   }
   if( nArg>=2 ){
-    sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", azArg[1]);
+    sqlite3_snprintf(sizeof(psi->zTestcase), psi->zTestcase, "%s", azArg[1]);
   }else{
-    sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?");
+    sqlite3_snprintf(sizeof(psi->zTestcase), psi->zTestcase, "?");
   }
+  return 0;
 }
 DISPATCHABLE_COMMAND( testctrl ? 0 0 ){
+  FILE *out = ISS(p)->out;
   static const struct {
     const char *zCtrlName;   /* Name of a test-control option */
     int ctrlCode;            /* Integer code for that option */
@@ -11974,9 +12162,9 @@ DISPATCHABLE_COMMAND( testctrl ? 0 0 ){
 
   /* --help lists all test-controls */
   if( strcmp(zCmd,"help")==0 ){
-    utf8_printf(p->out, "Available test-controls:\n");
+    utf8_printf(out, "Available test-controls:\n");
     for(i=0; i<ArraySize(aCtrl); i++){
-      utf8_printf(p->out, "  .testctrl %s %s\n",
+      utf8_printf(out, "  .testctrl %s %s\n",
                   aCtrl[i].zCtrlName, aCtrl[i].zUsage);
     }
     return 1;
@@ -12001,10 +12189,10 @@ DISPATCHABLE_COMMAND( testctrl ? 0 0 ){
   if( testctrl<0 ){
     utf8_printf(STD_ERR,"Error: unknown test-control: %s\n"
                 "Use \".testctrl --help\" for help\n", zCmd);
-  }else if( aCtrl[iCtrl].unSafe && p->bSafeMode ){
+  }else if( aCtrl[iCtrl].unSafe && ISS(p)->bSafeMode ){
     utf8_printf(STD_ERR,
-       "line %d: \".testctrl %s\" may not be used in safe mode\n",
-       p->pInSource->lineno, aCtrl[iCtrl].zCtrlName);
+                "line %d: \".testctrl %s\" may not be used in safe mode\n",
+                ISS(p)->pInSource->lineno, aCtrl[iCtrl].zCtrlName);
     exit(1);
   }else{
     switch(testctrl){
@@ -12013,7 +12201,7 @@ DISPATCHABLE_COMMAND( testctrl ? 0 0 ){
     case SQLITE_TESTCTRL_OPTIMIZATIONS:
       if( nArg==3 ){
         unsigned int opt = (unsigned int)strtol(azArg[2], 0, 0);
-        rc2 = sqlite3_test_control(testctrl, p->db, opt);
+        rc2 = sqlite3_test_control(testctrl, DBX(p), opt);
         isOk = 3;
       }
       break;
@@ -12049,7 +12237,7 @@ DISPATCHABLE_COMMAND( testctrl ? 0 0 ){
         if( nArg==3 ){
           db = 0;
         }else{
-          db = p->db;
+          db = DBX(p);
           /* Make sure the schema has been loaded */
           sqlite3_table_column_metadata(db, 0, "x", 0, 0, 0, 0, 0, 0);
         }
@@ -12080,13 +12268,13 @@ DISPATCHABLE_COMMAND( testctrl ? 0 0 ){
 
       /* sqlite3_test_control(sqlite3*) */
     case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS:
-      rc2 = sqlite3_test_control(testctrl, p->db);
+      rc2 = sqlite3_test_control(testctrl, DBX(p));
       isOk = 3;
       break;
 
     case SQLITE_TESTCTRL_IMPOSTER:
       if( nArg==5 ){
-        rc2 = sqlite3_test_control(testctrl, p->db,
+        rc2 = sqlite3_test_control(testctrl, DBX(p),
                                    azArg[2],
                                    integerValue(azArg[3]),
                                    integerValue(azArg[4]));
@@ -12096,8 +12284,8 @@ DISPATCHABLE_COMMAND( testctrl ? 0 0 ){
 
     case SQLITE_TESTCTRL_SEEK_COUNT: {
       u64 x = 0;
-      rc2 = sqlite3_test_control(testctrl, p->db, &x);
-      utf8_printf(p->out, "%llu\n", x);
+      rc2 = sqlite3_test_control(testctrl, DBX(p), &x);
+      utf8_printf(out, "%llu\n", x);
       isOk = 3;
       break;
     }
@@ -12105,7 +12293,7 @@ DISPATCHABLE_COMMAND( testctrl ? 0 0 ){
 #ifdef YYCOVERAGE
     case SQLITE_TESTCTRL_PARSER_COVERAGE: {
       if( nArg==2 ){
-        sqlite3_test_control(testctrl, p->out);
+        sqlite3_test_control(testctrl, out);
         isOk = 3;
       }
       break;
@@ -12128,11 +12316,11 @@ DISPATCHABLE_COMMAND( testctrl ? 0 0 ){
           int val = 0;
           rc2 = sqlite3_test_control(testctrl, -id, &val);
           if( rc2!=SQLITE_OK ) break;
-          if( id>1 ) utf8_printf(p->out, "  ");
-          utf8_printf(p->out, "%d: %d", id, val);
+          if( id>1 ) utf8_printf(out, "  ");
+          utf8_printf(out, "%d: %d", id, val);
           id++;
         }
-        if( id>1 ) utf8_printf(p->out, "\n");
+        if( id>1 ) utf8_printf(out, "\n");
         isOk = 3;
       }
       break;
@@ -12141,18 +12329,18 @@ DISPATCHABLE_COMMAND( testctrl ? 0 0 ){
     }
   }
   if( isOk==0 && iCtrl>=0 ){
-    utf8_printf(p->out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
+    utf8_printf(out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
     return 1;
   }else if( isOk==1 ){
-    raw_printf(p->out, "%d\n", rc2);
+    raw_printf(out, "%d\n", rc2);
   }else if( isOk==2 ){
-    raw_printf(p->out, "0x%08x\n", rc2);
+    raw_printf(out, "0x%08x\n", rc2);
   }
   return 0;
 }
 DISPATCHABLE_COMMAND( timeout 4 1 2 ){
   open_db(p, 0);
-  sqlite3_busy_timeout(p->db, nArg>=2 ? (int)integerValue(azArg[1]) : 0);
+  sqlite3_busy_timeout(DBX(p), nArg>=2 ? (int)integerValue(azArg[1]) : 0);
   return 0;
 }
 DISPATCHABLE_COMMAND( timer ? 2 2 ){
@@ -12164,6 +12352,7 @@ DISPATCHABLE_COMMAND( timer ? 2 2 ){
   return 0;
 }
 DISPATCHABLE_COMMAND( trace ? 0 0 ){
+  ShellInState *psi = ISS(p);
   int mType = 0;
   int jj;
   open_db(p, 0);
@@ -12171,15 +12360,15 @@ DISPATCHABLE_COMMAND( trace ? 0 0 ){
     const char *z = azArg[jj];
     if( z[0]=='-' ){
       if( optionMatch(z, "expanded") ){
-        p->eTraceType = SHELL_TRACE_EXPANDED;
+        psi->eTraceType = SHELL_TRACE_EXPANDED;
       }
 #ifdef SQLITE_ENABLE_NORMALIZE
       else if( optionMatch(z, "normalized") ){
-        p->eTraceType = SHELL_TRACE_NORMALIZED;
+        psi->eTraceType = SHELL_TRACE_NORMALIZED;
       }
 #endif
       else if( optionMatch(z, "plain") ){
-        p->eTraceType = SHELL_TRACE_PLAIN;
+        psi->eTraceType = SHELL_TRACE_PLAIN;
       }
       else if( optionMatch(z, "profile") ){
         mType |= SQLITE_TRACE_PROFILE;
@@ -12198,15 +12387,42 @@ DISPATCHABLE_COMMAND( trace ? 0 0 ){
         return 1;
       }
     }else{
-      output_file_close(p->traceOut);
-      p->traceOut = output_file_open(azArg[1], 0);
+      output_file_close(psi->traceOut);
+      psi->traceOut = output_file_open(azArg[1], 0);
     }
   }
-  if( p->traceOut==0 ){
-    sqlite3_trace_v2(p->db, 0, 0, 0);
+  if( psi->traceOut==0 ){
+    sqlite3_trace_v2(DBX(p), 0, 0, 0);
   }else{
     if( mType==0 ) mType = SQLITE_TRACE_STMT;
-    sqlite3_trace_v2(p->db, mType, sql_trace_callback, p);
+    sqlite3_trace_v2(DBX(p), mType, sql_trace_callback, psi);
+  }
+  return 0;
+}
+
+/*****************
+ * The .unmodule command
+ */
+CONDITION_COMMAND( unmodule defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_VIRTUALTABLE) );
+COLLECT_HELP_TEXT[
+  ".unmodule NAME ...       Unregister virtual table modules",
+  "    --allexcept             Unregister everything except those named",
+];
+DISPATCHABLE_COMMAND( unmodule ? 2 0 ){
+  int ii;
+  int lenOpt;
+  char *zOpt;
+  open_db(p, 0);
+  zOpt = azArg[1];
+  if( zOpt[0]=='-' && zOpt[1]=='-' && zOpt[2]!=0 ) zOpt++;
+  lenOpt = (int)strlen(zOpt);
+  if( lenOpt>=3 && strncmp(zOpt, "-allexcept",lenOpt)==0 ){
+    assert( azArg[nArg]==0 );
+    sqlite3_drop_modules(DBX(p), nArg>2 ? (const char**)(azArg+2) : 0);
+  }else{
+    for(ii=1; ii<nArg; ii++){
+      sqlite3_create_module(DBX(p), azArg[ii], 0, 0);
+    }
   }
   return 0;
 }
@@ -12236,7 +12452,7 @@ DISPATCHABLE_COMMAND( user ? 0 0 ){
     if( nArg!=4 ){
       goto teach_fail;
     }
-    rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3],
+    rc = sqlite3_user_authenticate(DBX(p), azArg[2], azArg[3],
                                    strlen30(azArg[3]));
     if( rc ){
       *pzErr = shellMPrintf(0,"Authentication failed for user %s\n", azArg[2]);
@@ -12246,7 +12462,7 @@ DISPATCHABLE_COMMAND( user ? 0 0 ){
     if( nArg!=5 ){
       goto teach_fail;
     }
-    rc = sqlite3_user_add(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
+    rc = sqlite3_user_add(DBX(p), azArg[2], azArg[3], strlen30(azArg[3]),
                           booleanValue(azArg[4]));
     if( rc ){
       *pzErr = shellMPrintf(0,"User-Add failed: %d\n", rc);
@@ -12256,7 +12472,7 @@ DISPATCHABLE_COMMAND( user ? 0 0 ){
     if( nArg!=5 ){
       goto teach_fail;
     }
-    rc = sqlite3_user_change(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
+    rc = sqlite3_user_change(DBX(p), azArg[2], azArg[3], strlen30(azArg[3]),
                              booleanValue(azArg[4]));
     if( rc ){
       *pzErr = shellMPrintf(0,"User-Edit failed: %d\n", rc);
@@ -12266,7 +12482,7 @@ DISPATCHABLE_COMMAND( user ? 0 0 ){
     if( nArg!=3 ){
       goto teach_fail;
     }
-    rc = sqlite3_user_delete(p->db, azArg[2]);
+    rc = sqlite3_user_delete(DBX(p), azArg[2]);
     if( rc ){
       *pzErr = shellMPrintf(0,"User-Delete failed: %d\n", rc);
       return 1;
@@ -12287,34 +12503,36 @@ COLLECT_HELP_TEXT[
   ".vfsname ?AUX?           Print the name of the VFS stack",
 ];
 DISPATCHABLE_COMMAND( version ? 1 1 ){
-  utf8_printf(p->out, "SQLite %s %s\n" /*extra-version-info*/,
+  FILE *out = ISS(p)->out;
+  utf8_printf(out, "SQLite %s %s\n" /*extra-version-info*/,
               sqlite3_libversion(), sqlite3_sourceid());
 #if SQLITE_HAVE_ZLIB
-  utf8_printf(p->out, "zlib version %s\n", zlibVersion());
+  utf8_printf(out, "zlib version %s\n", zlibVersion());
 #endif
 #define CTIMEOPT_VAL_(opt) #opt
 #define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt)
 #if defined(__clang__) && defined(__clang_major__)
-  utf8_printf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "."
+  utf8_printf(out, "clang-" CTIMEOPT_VAL(__clang_major__) "."
               CTIMEOPT_VAL(__clang_minor__) "."
               CTIMEOPT_VAL(__clang_patchlevel__) "\n");
 #elif defined(_MSC_VER)
-  utf8_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) "\n");
+  utf8_printf(out, "msvc-" CTIMEOPT_VAL(_MSC_VER) "\n");
 #elif defined(__GNUC__) && defined(__VERSION__)
-  utf8_printf(p->out, "gcc-" __VERSION__ "\n");
+  utf8_printf(out, "gcc-" __VERSION__ "\n");
 #endif
   return 0;
 }
 DISPATCHABLE_COMMAND( vfsinfo ? 1 2 ){
   const char *zDbName = nArg==2 ? azArg[1] : "main";
   sqlite3_vfs *pVfs = 0;
-  if( p->db ){
-    sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs);
+  if( DBX(p) ){
+    sqlite3_file_control(DBX(p), zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs);
     if( pVfs ){
-      utf8_printf(p->out, "vfs.zName      = \"%s\"\n", pVfs->zName);
-      raw_printf(p->out, "vfs.iVersion   = %d\n", pVfs->iVersion);
-      raw_printf(p->out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
-      raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
+      FILE *out = ISS(p)->out;
+      utf8_printf(out, "vfs.zName      = \"%s\"\n", pVfs->zName);
+      raw_printf(out, "vfs.iVersion   = %d\n", pVfs->iVersion);
+      raw_printf(out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
+      raw_printf(out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
     }
   }
   return 0;
@@ -12322,17 +12540,18 @@ DISPATCHABLE_COMMAND( vfsinfo ? 1 2 ){
 DISPATCHABLE_COMMAND( vfslist ? 1 1 ){
   sqlite3_vfs *pVfs;
   sqlite3_vfs *pCurrent = 0;
-  if( p->db ){
-    sqlite3_file_control(p->db, "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent);
+  FILE *out = ISS(p)->out;
+  if( DBX(p) ){
+    sqlite3_file_control(DBX(p), "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent);
   }
   for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){
-    utf8_printf(p->out, "vfs.zName      = \"%s\"%s\n", pVfs->zName,
+    utf8_printf(out, "vfs.zName      = \"%s\"%s\n", pVfs->zName,
                 pVfs==pCurrent ? "  <--- CURRENT" : "");
-    raw_printf(p->out, "vfs.iVersion   = %d\n", pVfs->iVersion);
-    raw_printf(p->out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
-    raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
+    raw_printf(out, "vfs.iVersion   = %d\n", pVfs->iVersion);
+    raw_printf(out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
+    raw_printf(out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
     if( pVfs->pNext ){
-      raw_printf(p->out, "-----------------------------------\n");
+      raw_printf(out, "-----------------------------------\n");
     }
   }
   return 0;
@@ -12340,10 +12559,10 @@ DISPATCHABLE_COMMAND( vfslist ? 1 1 ){
 DISPATCHABLE_COMMAND( vfsname ? 0 0 ){
   const char *zDbName = nArg==2 ? azArg[1] : "main";
   char *zVfsName = 0;
-  if( p->db ){
-    sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName);
+  if( DBX(p) ){
+    sqlite3_file_control(DBX(p), zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName);
     if( zVfsName ){
-      utf8_printf(p->out, "%s\n", zVfsName);
+      utf8_printf(ISS(p)->out, "%s\n", zVfsName);
       sqlite3_free(zVfsName);
     }
   }
@@ -12354,19 +12573,25 @@ DISPATCHABLE_COMMAND( vfsname ? 0 0 ){
  * The .width and .wheretrace commands
  * The .wheretrace command has no help.
  */
+static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths){
+  int j;
+  p->numWidths = nWidths;
+  p->pSpecWidths = realloc(p->pSpecWidths, (nWidths+1)*sizeof(int)*2);
+  if( nWidths>0 ){
+    shell_check_oom(p->pSpecWidths);
+    p->pHaveWidths = &p->pSpecWidths[nWidths];
+    for(j=0; j<nWidths; j++){
+      p->pSpecWidths[j] = (int)integerValue(azWidths[j]);
+      p->pHaveWidths[j] = 0;
+    }
+  }else p->pHaveWidths = p->pSpecWidths;
+}
 COLLECT_HELP_TEXT[
   ".width NUM1 NUM2 ...     Set minimum column widths for columnar output",
   "   Negative values right-justify",
 ];
 DISPATCHABLE_COMMAND( width ? 1 0 ){
-  int j;
-  p->nWidth = nArg-1;
-  p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(int)*2);
-  if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory();
-  if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth];
-  for(j=1; j<nArg; j++){
-    p->colWidth[j-1] = (int)integerValue(azArg[j]);
-  }
+  setColumnWidths(p, azArg+1, nArg-1);
   return 0;
 }
 DISPATCHABLE_COMMAND( wheretrace ? 1 2 ){
@@ -12385,21 +12610,22 @@ COLLECT_HELP_TEXT[
 DISPATCHABLE_COMMAND( x ? 1 0 ){
   int ia, rc, nErrors = 0;
   sqlite3_stmt *pStmt = 0;
+  sqlite3 *db;
+
   open_db(p, 0);
-  if( p->db==0 ){
+  db = DBX(p);
+  if( db==0 ){
     utf8_printf(STD_ERR, ".x can only be done with a database open.\n");
     return 1;
   }
-  if( sqlite3_table_column_metadata(p->db, PARAM_TABLE_SCHEMA,
+  if( sqlite3_table_column_metadata(db, PARAM_TABLE_SCHEMA,
                                     PARAM_TABLE_NAME,
                                     "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){
     utf8_printf(STD_ERR, "No "PARAM_TABLE_SNAME" table exists.\n");
     return 1;
   }
-  rc = sqlite3_prepare_v2
-    (p->db, "SELECT value FROM "PARAM_TABLE_SNAME
-     " WHERE key=$1 AND uses=1",
-     -1, &pStmt, 0);
+  rc = sqlite3_prepare_v2(db, "SELECT value FROM "PARAM_TABLE_SNAME
+                          " WHERE key=$1 AND uses=1", -1, &pStmt, 0);
   if( rc!=SQLITE_OK ){
     utf8_printf(STD_ERR, PARAM_TABLE_SNAME" is wrongly created.\n");
     return 1;
@@ -12422,13 +12648,14 @@ DISPATCHABLE_COMMAND( x ? 1 0 ){
            */
           int nle = zValue[nb-1]=='\n';
           char *zSubmit = sqlite3_mprintf( "%.*s%s", nb, zValue, "\n"+nle );
+          ShellInState *psi = ISS(p);
           InSource inRedir
-            = INSOURCE_STR_REDIR(zSubmit, azArg[ia], p->pInSource);
+            = INSOURCE_STR_REDIR(zSubmit, azArg[ia], psi->pInSource);
           shell_check_oom(zSubmit);
-          p->pInSource = &inRedir;
-          rc = process_input(p);
+          psi->pInSource = &inRedir;
+          rc = process_input(psi);
           sqlite3_free(zSubmit);
-          p->pInSource = inRedir.pFrom;
+          psi->pInSource = inRedir.pFrom;
         }else{
           continue; /* All white, ignore. */
         }
@@ -12451,17 +12678,40 @@ DISPATCHABLE_COMMAND( x ? 1 0 ){
 /* End of published, standard meta-command implementation functions
 COMMENT  Build-time overrides of above meta-commands or new meta-commands may be
 COMMENT  incorporated into shell.c via: -it COMMAND_CUSTOMIZE=<customize source>
-COMMENT  where <customize source> names a file using the methodology of the above
-COMMENT  section to define new or altered meta-commands and their help text.
+COMMENT  where <customize source> names a file using the above methodology to
+COMMENT  define new or altered meta-commands and their help text.
 */
 INCLUDE( COMMAND_CUSTOMIZE );
 
-typedef struct MetaCommand MetaCommand;
+COMMENT  This help text is set seperately from meta-command definition section
+COMMENT  for the always-built-in, non-customizable commands with visible help.
+COLLECT_HELP_TEXT[
+  ".exit ?CODE?             Exit this program with return-code CODE or 0",
+  ".quit                    Exit this program",
+];
+
+static void MetaCommand_dtor(MetaCommand *);
+static const char * MetaCommand_name(MetaCommand *);
+static const char * MetaCommand_help(MetaCommand *, int);
+static int MetaCommand_argsCheck(MetaCommand *, char **, int, char *azArgs[]);
+static int MetaCommand_execute(MetaCommand *, ShellExState *,
+                               char **, int, char *azArgs[]);
+
+static VTABLE_NAME(MetaCommand) meta_cmd_VtabBuiltIn = {
+  MetaCommand_dtor,
+  MetaCommand_name,
+  MetaCommand_help,
+  MetaCommand_argsCheck,
+  MetaCommand_execute
+};
 
 /* Define and populate command dispatch table. */
 static struct CommandInfo {
+#if OBJECTIFY_COMMANDS
+  VTABLE_NAME(MetaCommand) *mcVtabBuiltIn;
+#endif
   const char * cmdName;
-  int (*cmdDoer)(char *azArg[], int nArg, ShellState *, char **pzErr);
+  int (*cmdDoer)(char *azArg[], int nArg, ShellExState *, char **pzErr);
   unsigned char minLen, minArgs, maxArgs;
 #if OBJECTIFY_COMMANDS
   const char *azHelp[2]; /* primary and secondary help text */
@@ -12469,18 +12719,49 @@ static struct CommandInfo {
 #endif
 } command_table[] = {
   COMMENT Emit the dispatch table entries generated and collected above.
+#if OBJECTIFY_COMMANDS
+# define META_CMD_INFO(cmd, nlenMin, minArgs, maxArgs, ht0, ht1 ) \
+  { &meta_cmd_VtabBuiltIn, #cmd, cmd ## Command, \
+    nlenMin, minArgs, maxArgs, { ht0, ht1 }, 0 \
+  }
+  EMIT_METACMD_INIT(2);
+  { 0, 0, 0, 0, ~0, ~0, {0,0}, 0 }
+#else
   EMIT_DISPATCH(2);
-  { 0, 0, 0, -1, -1 }
+  { 0, 0, 0, ~0, ~0 }
+#endif
 };
 static unsigned numCommands
   = sizeof(command_table)/sizeof(struct CommandInfo) - 1;
 
-COMMENT  This help text is set seperately from meta-command definition section
-COMMENT  for the always-built-in, non-customizable commands with visible help.
-COLLECT_HELP_TEXT[
-  ".exit ?CODE?             Exit this program with return-code CODE or 0",
-  ".quit                    Exit this program",
-];
+static void MetaCommand_dtor(MetaCommand *pMe){
+  UNUSED_PARAMETER(pMe);
+}
+
+static const char * MetaCommand_name(MetaCommand *pMe){
+  return ((struct CommandInfo *)pMe)->cmdName;
+}
+
+static const char * MetaCommand_help(MetaCommand *pMe, int more){
+  if( more>=0 && more<2 ) return ((struct CommandInfo *)pMe)->azHelp[more];
+  else return 0;
+}
+
+static int MetaCommand_argsCheck(MetaCommand *pMe,
+                                 char **pzErrMsg, int nArgs, char *azArgs[]){
+  struct CommandInfo *pci = (struct CommandInfo *)pMe;
+  UNUSED_PARAMETER(pzErrMsg); /* Future: Make this more informative. */
+  UNUSED_PARAMETER(nArgs);
+  UNUSED_PARAMETER(azArgs);
+  if( pci->minArgs > nArgs||(pci->maxArgs > 0 && pci->maxArgs < nArgs) ){
+    return SHELL_INVALID_ARGS;
+  }else return 0;
+}
+
+static int MetaCommand_execute(MetaCommand *pMe, ShellExState *pssx,
+                               char **pzErrMsg, int nArgs, char *azArgs[]){
+  return (((struct CommandInfo *)pMe)->cmdDoer)(azArgs, nArgs, pssx, pzErrMsg);
+}
 
 /*
 ** Text of help messages.
@@ -12517,7 +12798,7 @@ static const char *(azHelp[]) = {
 ** The return is either a dispatch error or whatever the dispatched
 ** meta-command returns.
 */
-int dispatchCommand(char *azArg[], int nArg, ShellState *pSS, char **pzErr){
+int dispatchCommand(char *azArg[], int nArg, ShellExState *psx, char **pzErr){
   const char *cmdName = azArg[0];
   int cmdLen = strlen30(cmdName);
   struct CommandInfo *pci = 0;
@@ -12545,7 +12826,7 @@ int dispatchCommand(char *azArg[], int nArg, ShellState *pSS, char **pzErr){
   }
   /* Replace any user-shortened command name with its whole name. */
   azArg[0] = (char *)pci->cmdName;
-  return (pci->cmdDoer)(azArg, nArg, pSS, pzErr);
+  return (pci->cmdDoer)(azArg, nArg, psx, pzErr);
 }
 
 /*
@@ -12554,7 +12835,7 @@ int dispatchCommand(char *azArg[], int nArg, ShellState *pSS, char **pzErr){
 **
 ** Return 1 on error, 2 to exit, and 0 otherwise.
 */
-static int do_meta_command(char *zLine, ShellState *p){
+static int do_meta_command(char *zLine, ShellExState *psx){
   int h = 1;
   int nArg = 0;
   int n, c;
@@ -12562,12 +12843,12 @@ static int do_meta_command(char *zLine, ShellState *p){
   char *azArg[52];
 #if SHELL_VARIABLE_EXPANSION
   int ncLineIn = strlen30(zLine);
-  u8 bExpVars = SHEXT_VAREXP(p);
+  u8 bExpVars = SHEXT_VAREXP(ISS(psx));
 #endif
 
 #ifndef SQLITE_OMIT_VIRTUALTABLE
-  if( p->expert.pExpert ){
-    expertFinish(p, 1, 0);
+  if( ISS(psx)->expert.pExpert ){
+    expertFinish(ISS(psx), 1, 0);
   }
 #endif
 
@@ -12601,14 +12882,14 @@ static int do_meta_command(char *zLine, ShellState *p){
   if( nArg==0 ) return 0; /* no tokens, no error */
   n = strlen30(azArg[0]);
   c = azArg[0][0];
-  clearTempFile(p);
+  clearTempFile(ISS(psx));
 
   /* Check for the special, non-dispatched meta-commands.
   */
 
   if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){
     if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 )
-      p->abruptExit = rc;
+      psx->shellAbruptExit = rc;
     rc = 2;
   }else
 
@@ -12626,7 +12907,7 @@ static int do_meta_command(char *zLine, ShellState *p){
       int i, v;
       for(i=1; i<nArg; i++){
         v = booleanValue(azArg[i]);
-        utf8_printf(p->out, "%s: %d 0x%x\n", azArg[i], v, v);
+        utf8_printf(ISS(psx)->out, "%s: %d 0x%x\n", azArg[i], v, v);
       }
     }
     if( strncmp(azArg[0]+9, "integer", n-9)==0 ){
@@ -12635,7 +12916,7 @@ static int do_meta_command(char *zLine, ShellState *p){
         char zBuf[200];
         v = integerValue(azArg[i]);
         sqlite3_snprintf(sizeof(zBuf),zBuf,"%s: %lld 0x%llx\n", azArg[i],v,v);
-        utf8_printf(p->out, "%s", zBuf);
+        utf8_printf(ISS(psx)->out, "%s", zBuf);
       }
     }
   }else
@@ -12643,8 +12924,8 @@ static int do_meta_command(char *zLine, ShellState *p){
     /* The meta-command is not among the specially handled ones. Dispatch it. */
   {
     char *zErr = 0;
-    int dispatchResult = dispatchCommand(azArg, nArg, p, &zErr);
-    if( p->abruptExit!=0 ){
+    int dispatchResult = dispatchCommand(azArg, nArg, psx, &zErr);
+    if( psx->shellAbruptExit!=0 ){
       dispatchResult = SHELL_FORBIDDEN_OP;
     }
     switch( dispatchResult ){
@@ -12676,7 +12957,7 @@ static int do_meta_command(char *zLine, ShellState *p){
         utf8_printf(STD_ERR,
                     "Error: \".%s\" forbidden in --safe mode\n", azArg[0]);
       }
-      p->abruptExit = 3;
+      psx->shellAbruptExit = 3;
       rc = 2;
     default:
       if( 0!=dispatchResult ) rc = 1;
@@ -12688,11 +12969,11 @@ static int do_meta_command(char *zLine, ShellState *p){
   }
 
 meta_command_exit:
-  if( p->outCount ){
-    p->outCount--;
-    if( p->outCount==0 ) output_reset(p);
+  if( ISS(psx)->outCount ){
+    ISS(psx)->outCount--;
+    if( ISS(psx)->outCount==0 ) output_reset(ISS(psx));
   }
-  updateSafeMode(p);
+  updateSafeMode(ISS(psx));
 #if SHELL_VARIABLE_EXPANSION
   if( bExpVars ){
     /* Free any arguments that are allocated rather than tokenized in place. */
@@ -12851,15 +13132,17 @@ static int line_is_complete(char *zSql, int nSql){
 ** Run a single line of SQL.  Return the number of errors.
 ** bAltIn indicates that input has been redirected in some way.
 */
-static int runOneSqlLine(ShellState *p, char *zSql, int bAltIn, int startline){
+static int runOneSqlLine(ShellExState *psx, char *zSql,
+                         int bAltIn, int startline){
   int rc;
   char *zErrMsg = 0;
 
-  open_db(p, 0);
-  if( ShellHasFlag(p,SHFLG_Backslash) ) resolve_backslashes(zSql);
-  if( p->flgProgress & SHELL_PROGRESS_RESET ) p->nProgress = 0;
+  open_db(psx, 0);
+  if( ShellHasFlag(psx,SHFLG_Backslash) ) resolve_backslashes(zSql);
+  if( ISS(psx)->flgProgress & SHELL_PROGRESS_RESET ) ISS(psx)->nProgress = 0;
   BEGIN_TIMER;
-  rc = shell_exec(p, zSql, &zErrMsg);
+  /* This shell_exec call will also echo the submitted SQL when echo is on. */
+  rc = shell_exec(psx, zSql, &zErrMsg);
   END_TIMER;
   if( rc || zErrMsg ){
     char zPrefix[100];
@@ -12867,7 +13150,7 @@ static int runOneSqlLine(ShellState *p, char *zSql, int bAltIn, int startline){
     const char *zErrorType;
     if( zErrMsg==0 ){
       zErrorType = "Error";
-      zErrorTail = sqlite3_errmsg(p->db);
+      zErrorTail = sqlite3_errmsg(DBX(psx));
     }else if( strncmp(zErrMsg, "in prepare, ",12)==0 ){
       zErrorType = "Parse error";
       zErrorTail = &zErrMsg[12];
@@ -12887,12 +13170,12 @@ static int runOneSqlLine(ShellState *p, char *zSql, int bAltIn, int startline){
     utf8_printf(STD_ERR, "%s %s\n", zPrefix, zErrorTail);
     sqlite3_free(zErrMsg);
     return 1;
-  }else if( ShellHasFlag(p, SHFLG_CountChanges) ){
+  }else if( ShellHasFlag(psx, SHFLG_CountChanges) ){
     char zLineBuf[2000];
     sqlite3_snprintf(sizeof(zLineBuf), zLineBuf,
             "changes: %lld   total_changes: %lld",
-            sqlite3_changes64(p->db), sqlite3_total_changes64(p->db));
-    raw_printf(p->out, "%s\n", zLineBuf);
+            sqlite3_changes64(DBX(psx)), sqlite3_total_changes64(DBX(psx)));
+    raw_printf(ISS(psx)->out, "%s\n", zLineBuf);
   }
   return 0;
 }
@@ -13051,7 +13334,7 @@ static void grow_line_buffer(char **pz, int *pna, int ncNeed){
 **   2 => exit demanded, no errors.
 **   3 => exit demanded, errors>0
 */
-static int process_input(ShellState *p){
+static int process_input(ShellInState *psi){
   char *zLineInput = 0;  /* a line-at-a-time input buffer or usable result */
   char *zLineAccum = 0;  /* accumulation buffer, used for multi-line input */
   /* Above two pointers could be local to the group handling loop, but are
@@ -13061,12 +13344,12 @@ static int process_input(ShellState *p){
   /* Some flags for ending the overall group processing loop, always 1 or 0 */
   u8 bErrorBail=0, bInputEnd=0, bExitDemand=0, bInterrupted=0;
   /* Flag to affect prompting and interrupt action */
-  u8 bInteractive = (p->pInSource==&stdInSource && stdin_is_interactive);
+  u8 bInteractive = INSOURCE_IS_INTERACTIVE(psi->pInSource);
   int nErrors = 0;       /* count of errors during execution or its prep */
 
   /* Block overly-recursive or absurdly nested input redirects. */
-  if( p->inputNesting==MAX_INPUT_NESTING ){
-    InSource *pInSrc = p->pInSource->pFrom;
+  if( psi->inputNesting>=MAX_INPUT_NESTING ){
+    InSource *pInSrc = psi->pInSource->pFrom;
     const char *zLead = "Input nesting limit ("
       SHELL_STRINGIFY(MAX_INPUT_NESTING)") reached,";
     int i = 3;
@@ -13081,7 +13364,7 @@ static int process_input(ShellState *p){
     utf8_printf(STD_ERR, " ...\nERROR: Check recursion.\n");
     return 1;
   }
-  ++p->inputNesting;
+  ++psi->inputNesting;
 
   /* line-group processing loop (per SQL block, dot-command or comment) */
   while( !bErrorBail && !bInputEnd && !bExitDemand && !bInterrupted ){
@@ -13111,8 +13394,8 @@ static int process_input(ShellState *p){
     int *pncLineUse = &ncLineIn;      /* ref that line's char count */
     int iStartline = 0;               /* starting line number of group */
 
-    fflush(p->out);
-    zLineInput = one_input_line(p->pInSource, zLineInput, nGroupLines>0);
+    fflush(psi->out);
+    zLineInput = one_input_line(psi->pInSource, zLineInput, nGroupLines>0);
     if( zLineInput==0 ){
       bInputEnd = 1;
       inKind = Eof;
@@ -13120,17 +13403,17 @@ static int process_input(ShellState *p){
       if( bInteractive ) printf("\n");
     }else{
       ++nGroupLines;
-      iStartline = p->pInSource->lineno;
+      iStartline = psi->pInSource->lineno;
       ncLineIn = strlen30(zLineInput);
       if( seenInterrupt ){
-        if( p->pInSource!=0 ) break;
+        if( psi->pInSource!=0 ) break;
         bInterrupted = 1; /* This will be honored, or not, later. */
         seenInterrupt = 0;
         disposition = Dumpable;
       }
       /* Classify and check for single-line dispositions, prep for more. */
 #if SHELL_EXTENDED_PARSING
-      ndcLeadWhite = (SHEXT_PARSING(p))
+      ndcLeadWhite = (SHEXT_PARSING(psi))
         ? skipWhite(zLineInput)-zLineInput
         : 0; /* Disallow leading whitespace for . or # in legacy mode. */
 #endif
@@ -13171,7 +13454,7 @@ static int process_input(ShellState *p){
         disposition = Dumpable;
       case Cmd:
 #if SHELL_EXTENDED_PARSING
-        if( SHEXT_PARSING(p) ){
+        if( SHEXT_PARSING(psi) ){
           if( line_join_ends(dcScanState, *pzLineUse, pncLineUse, &cLineEnd) ){
             disposition = Runnable;
           }
@@ -13214,7 +13497,7 @@ static int process_input(ShellState *p){
           pncLineUse = &ncLineAcc;
         }
         /* Read in next line of group, (if available.) */
-        zLineInput = one_input_line(p->pInSource, zLineInput, nGroupLines>0);
+        zLineInput = one_input_line(psi->pInSource, zLineInput, nGroupLines>0);
         if( zLineInput==0 ){
           bInputEnd = 1;
           inKind = Eof;
@@ -13248,20 +13531,20 @@ static int process_input(ShellState *p){
     /* Here, the group is fully collected or known to be incomplete forever. */
     switch( disposition ){
     case Dumpable:
-      echo_group_input(p, *pzLineUse);
+      echo_group_input(psi, *pzLineUse);
       break;
     case Runnable:
       switch( inKind ){
       case Sql:
         /* runOneSqlLine() does its own echo when requested. */
-        nErrors += runOneSqlLine(p, *pzLineUse,
-                                 INSOURCE_IS_INTERACTIVE(p->pInSource),
+        nErrors += runOneSqlLine(XSS(psi), *pzLineUse,
+                                 INSOURCE_IS_INTERACTIVE(psi->pInSource),
                                  iStartline);
         if( bail_on_error && nErrors>0 ) bErrorBail = 1;
         break;
       case Cmd:
-        echo_group_input(p, *pzLineUse);
-        switch( do_meta_command(*pzLineUse+ndcLeadWhite, p) ){
+        echo_group_input(psi, *pzLineUse);
+        switch( do_meta_command(*pzLineUse+ndcLeadWhite, XSS(psi)) ){
         default: ++nErrors; /* fall thru */
         case 0: break;
         case 2: bExitDemand = 1; break;
@@ -13271,11 +13554,11 @@ static int process_input(ShellState *p){
         assert(inKind!=Tbd);
         break;
       }
-      if( p->abruptExit!=0 ) bExitDemand = 1;
+      if( XSS(psi)->shellAbruptExit!=0 ) bExitDemand = 1;
       break;
     case Erroneous:
       utf8_printf(STD_ERR, "Error: Input incomplete at line %d of \"%s\"\n",
-                  p->pInSource->lineno, p->pInSource->zSourceSay);
+                  psi->pInSource->lineno, psi->pInSource->zSourceSay);
       ++nErrors;
       break;
     case Ignore:
@@ -13366,11 +13649,11 @@ static char *find_home_dir(int clearFlag){
 ** This is a convenience function, used in several places, all of
 ** demand a shell exit upon failure, which leads to return 2.
  */
-static int run_single_query(ShellState *p, char *zSql){
+static int run_single_query(ShellExState *psx, char *zSql){
   char *zErrMsg = 0;
   int rc;
-  open_db(p, 0);
-  rc = shell_exec(p, zSql, &zErrMsg);
+  open_db(psx, 0);
+  rc = shell_exec(psx, zSql, &zErrMsg);
   if( zErrMsg!=0 || rc>0 ){
     /* Some kind of error, to result in exit before REPL. */
     if( zErrMsg!=0 ){
@@ -13379,7 +13662,7 @@ static int run_single_query(ShellState *p, char *zSql){
     }else{
       utf8_printf(STD_ERR,"Error: unable to process SQL \"%s\"\n", zSql);
     }
-    p->abruptExit = (rc==0)? 1 : rc;
+    psx->shellAbruptExit = (rc==0)? 1 : rc;
     rc = 2;
   }
   return rc;
@@ -13392,7 +13675,7 @@ static int run_single_query(ShellState *p, char *zSql){
 ** The return is similar to process_input() (0 success, 1 error, x abort)
 */
 static void process_sqliterc(
-  ShellState *p,                  /* Configuration data */
+  ShellInState *psi,              /* shell state (internal) */
   const char *sqliterc_override   /* Name of config file. NULL to use default */
 ){
   char *home_dir = NULL;
@@ -13414,19 +13697,19 @@ static void process_sqliterc(
   inUse = fopen(sqliterc,"rb");
   if( inUse!=0 ){
     InSource inSourceRedir
-      = INSOURCE_FILE_REDIR(inUse, sqliterc, p->pInSource);
+      = INSOURCE_FILE_REDIR(inUse, sqliterc, psi->pInSource);
     int rc;
-    p->pInSource = &inSourceRedir;
+    psi->pInSource = &inSourceRedir;
     if( stdin_is_interactive ){
       utf8_printf(STD_ERR,"-- Loading resources from %s\n",sqliterc);
     }
-    rc = process_input(p);
+    rc = process_input(psi);
     fclose(inUse);
-    p->pInSource = inSourceRedir.pFrom;
-    if( rc!=0 && bail_on_error ) p->abruptExit = rc;
+    psi->pInSource = inSourceRedir.pFrom;
+    if( rc!=0 && bail_on_error ) XSS(psi)->shellAbruptExit = rc;
   }else if( sqliterc_override!=0 ){
     utf8_printf(STD_ERR,"cannot open: \"%s\"\n", sqliterc);
-    if( bail_on_error ) exit(1);
+    if( bail_on_error ) exit(1); /* Future: Exit shell rather than process. */
   }
   sqlite3_free(zBuf);
 }
@@ -13523,21 +13806,30 @@ static void verify_uninitialized(void){
 }
 
 /*
-** Initialize the state information in data
-*/
-static void main_init(ShellState *data) {
-  memset(data, 0, sizeof(*data));
-  data->out = STD_OUT;
-  data->normalMode = data->cMode = data->mode = MODE_List;
-  data->autoExplain = 1;
-  data->pAuxDb = &data->aAuxDb[0];
-  memcpy(data->colSeparator,SEP_Column, 2);
-  memcpy(data->rowSeparator,SEP_Row, 2);
-  data->showHeader = 0;
-  data->shellFlgs = SHFLG_Lookaside;
+** Initialize the state information in data and datax
+*/
+  static void main_init(ShellInState *pData, ShellExState *pDatax) {
+  memset(pData, 0, sizeof(*pData));
+  memset(pDatax, 0, sizeof(*pDatax));
+  pDatax->sizeofThis = sizeof(*pDatax);
+  pDatax->pSIS = pData;
+  pData->pSXS = pDatax;
+  pDatax->pShowHeader = &pData->showHeader;
+  pDatax->zFieldSeparator = &pData->colSeparator[0];
+  pDatax->zRecordSeparator = &pData->rowSeparator[0];
+  pDatax->zNullValue = &pData->nullValue[0];
+  pData->out = STD_OUT;
+  pDatax->ppCurrentOutput = &pData->out;
+  pData->normalMode = pData->cMode = pData->mode = MODE_List;
+  pData->autoExplain = 1;
+  pData->pAuxDb = &pData->aAuxDb[0];
+  memcpy(pData->colSeparator,SEP_Column, 2);
+  memcpy(pData->rowSeparator,SEP_Row, 2);
+  pData->showHeader = 0;
+  pData->shellFlgs = SHFLG_Lookaside;
   verify_uninitialized();
   sqlite3_config(SQLITE_CONFIG_URI, 1);
-  sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data);
+  sqlite3_config(SQLITE_CONFIG_LOG, shellLog, pData);
   sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
   sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> ");
   sqlite3_snprintf(sizeof(continuePrompt), continuePrompt,"   ...> ");
@@ -13604,10 +13896,11 @@ int SQLITE_CDECL SHELL_MAIN(int argc, char **argv){
 int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
   char **argv;
 #endif
-  ShellState data;
+  ShellInState data;
+  ShellExState datax;
   const char *zInitFile = 0;
   int bQuiet = 0; /* for testing, to suppress banner and history actions */
-  int i;
+  int i, aec;
   int rc = 0;
   int warnInmemoryDb = 0;
   int readStdin = 1;
@@ -13651,7 +13944,7 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
     exit(1);
   }
 #endif
-  main_init(&data);
+  main_init(&data,&datax);
 
   /* On Windows, we must translate command-line arguments into UTF-8.
   ** The SQLite memory allocator subsystem has to be enabled in order to
@@ -13893,7 +14186,7 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
   ** to the sqlite command-line tool.
   */
   if( access(data.pAuxDb->zDbFilename, 0)==0 ){
-    open_db(&data, 0);
+    open_db(&datax, 0);
   }
 
   /* Process the initialization file if there is one.  If no -init option
@@ -13965,12 +14258,12 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
                        "%s",cmdline_option_value(argc,argv,++i));
     }else if( strcmp(z,"-header")==0 ){
       data.showHeader = 1;
-      ShellSetFlag(&data, SHFLG_HeaderSet);
+      ShellSetFlag(&datax, SHFLG_HeaderSet);
     }else if( strcmp(z,"-noheader")==0 ){
       data.showHeader = 0;
-      ShellSetFlag(&data, SHFLG_HeaderSet);
+      ShellSetFlag(&datax, SHFLG_HeaderSet);
     }else if( strcmp(z,"-echo")==0 ){
-      ShellSetFlag(&data, SHFLG_Echo);
+      ShellSetFlag(&datax, SHFLG_Echo);
     }else if( strcmp(z,"-eqp")==0 ){
       data.autoEQP = AUTOEQP_on;
     }else if( strcmp(z,"-eqpfull")==0 ){
@@ -13985,7 +14278,7 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
       ** prior to sending the SQL into SQLite.  Useful for injecting
       ** crazy bytes in the middle of SQL statements for testing and debugging.
       */
-      ShellSetFlag(&data, SHFLG_Backslash);
+      ShellSetFlag(&datax, SHFLG_Backslash);
     }else if( strcmp(z,"-bail")==0 ){
       /* No-op.  The bail_on_error flag should already be set. */
 #if SHELL_EXTENSIONS
@@ -14037,9 +14330,9 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
       if( i==argc-1 ) break;
       z = cmdline_option_value(argc,argv,++i);
       if( z[0]=='.' ){
-        rc = do_meta_command(z, &data);
+        rc = do_meta_command(z, &datax);
       }else{
-        rc = run_single_query(&data, z);
+        rc = run_single_query(&datax, z);
       }
       if( rc && bail_on_error ){
         if( rc==2 ) rc = 0;
@@ -14053,12 +14346,12 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
         rc = 1;
         goto shell_bail;
       }
-      open_db(&data, OPEN_DB_ZIPFILE);
+      open_db(&datax, OPEN_DB_ZIPFILE);
       if( z[2] ){
         argv[i] = &z[2];
-        rc = arDotCommand(&data, 1, argv+(i-1), argc-(i-1));
+        rc = arDotCommand(&datax, 1, argv+(i-1), argc-(i-1));
       }else{
-        rc = arDotCommand(&data, 1, argv+i, argc-i);
+        rc = arDotCommand(&datax, 1, argv+i, argc-i);
       }
       readStdin = 0;
       break;
@@ -14074,7 +14367,7 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
     }
     if( zModeSet!=0 ){
       char *azModeCmd[] = { ".mode", zModeSet+1 };
-      modeCommand(azModeCmd, 2, &data, 0);
+      modeCommand(azModeCmd, 2, &datax, 0);
       data.cMode = data.mode;
     }
   }
@@ -14086,9 +14379,9 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
     */
     for(i=0; i<nCmd && rc<2; i++){
       if( azCmd[i][0]=='.' ){
-        rc = do_meta_command(azCmd[i], &data);
+        rc = do_meta_command(azCmd[i], &datax);
       }else{
-        rc = run_single_query(&data, azCmd[i]);
+        rc = run_single_query(&datax, azCmd[i]);
       }
       if( rc && bail_on_error ){
         goto shell_bail;
@@ -14148,10 +14441,10 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
     }
   }
  shell_bail:
-  set_table_name(&data, 0);
-  if( data.db ){
+  set_table_name(&datax, 0);
+  if( datax.dbUser ){
     session_close_all(&data, -1);
-    close_db(data.db);
+    close_db(datax.dbUser);
   }
   sqlite3_mutex_free(pGlobalDbLock);
   pGlobalDbLock = 0;
@@ -14167,25 +14460,31 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
   data.doXdgOpen = 0;
   clearTempFile(&data);
   sqlite3_free(data.zEditor);
+#if SHELL_DYNAMIC_EXTENSION
+  sqlite3_close(datax.dbShell);
+#endif
 #if !SQLITE_SHELL_IS_UTF8
   for(i=0; i<argcToFree; i++) free(argvToFree[i]);
   free(argvToFree);
 #endif
-  free(data.colWidth);
+  free(datax.pSpecWidths);
   free(data.zNonce);
   for(i=0; i<data.nSavedModes; ++i) sqlite3_free(data.pModeStack[i]);
   free(azCmd);
-  /* Clear ShellState objects so that valgrind detects real memory leaks. */
+  aec = datax.shellAbruptExit;
+  /* Clear shell state objects so that valgrind detects real memory leaks. */
   memset(&data, 0, sizeof(data));
-  /* Process exit codes to yield single shell exit code. 
+  memset(&datax, 0, sizeof(datax));
+  /* Process exit codes to yield single shell exit code.
    * rc == 2 is a quit signal, resulting in no error by itself.
-   * data.bAbruptExit conveys either an normal (success or error) exit
-   * code or an abnormal exit code. Its abnormal values take priority.
+   * datax.shellAbruptExit conveyed either an normal (success or error)
+   * exit code or an abnormal exit code. Its abnormal values take priority.
    */
   /* Check for an abnormal exit, and issue error if so. */
-  if( rc>2 || data.abruptExit>1 ){
-    rc = (rc<data.abruptExit)? data.abruptExit : rc;
-    raw_printf(STD_ERR, "Abnormal exit (%d)\n", data.abruptExit);
+  if( rc>2 || aec>1 ){
+    rc = (rc<aec)? aec : rc;
+    if( rc>255 ) rc = 255; /* Clamp to valid error exit range. */
+    raw_printf(STD_ERR, "Abnormal exit (%d)\n", rc);
   }else{
     /* rc is one of 0,1,2, mapping to 0,1,0 shellexit codes. */
     rc &= ~2;
index d8f832595bec2a0425996ee63a6d7124489c6b58..a8496f4a44f71a3a6835c70fa6e46b849bd166d8 100644 (file)
@@ -1,5 +1,10 @@
 #ifndef SQLITE3SHX_H
 #define SQLITE3SHX_H
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+
 #include "sqlite3ext.h"
 
 #include "obj_interfaces.h"
@@ -9,63 +14,68 @@ extern "C" {
 #endif
 
 /* Convey data to, from and/or between I/O handlers and meta-commands. */
-typedef struct {
+typedef struct ShellExState {
+  /* A sizeof(*) to permit extensions to guard against too-old hosts */
+  int sizeofThis;
+
   /* A semi-transient holder of arbitrary data used during operations
    * not interrupted by meta-command invocations. Any not-null pointer
    * left after a meta-command has completed is, by contract, to be
    * freeable using sqlite3_free(). It is otherwise unconstrained. */
   void *pvHandlerData;
 
-  /* The user's currently open and primary DB connection */
-  sqlite3 *db;
-  /* The DB connection used for shell's dynamical data */
+  /* The user's currently open and primary DB connection
+   * Extensions may use this DB, but must not modify this pointer.
+   */
+  sqlite3 *dbUser;
+  /* DB connection for shell dynamical data and extension management
+   * Extensions may use this DB, but should not alter content created
+   * by the shell nor depend upon its schema. Names with prefix "shell_"
+   * or "shext_" are reserved for the shell's use.
+   */
   sqlite3 *dbShell;
 
-  /* Input stream providing shell's command or query input */
-  FILE *pCurrentInputStream;
-  /* Output stream to which shell's text output to be written */
-  FILE *pCurrentOutputStream;
+  /* Output stream to which shell's text output to be written (reference) */
+  FILE **ppCurrentOutput;
 
   /* Whether to exit as command completes.
    * 0 => no exit
    * ~0 => a non-error (0) exit
    * other => exit with process exit code other
-   * For embedded shell, "exit" means "return from REPL".
+   * For embedded shell, "exit" means "return from REPL function".
    */
-  int shellExit;
+  int shellAbruptExit;
 
   /* Number of lines written during a query result output */
   int resultCount;
-  /* Whether to show column names for certain output modes */
-  int showHeader;
-  /* Column separator character for some modes */
+  /* Whether to show column names for certain output modes (reference) */
+  u8 *pShowHeader;
+  /* Column separator character for some modes, read-only */
   char *zFieldSeparator;
-  /* Row separator character for some modes (MODE_Ascii) */
+  /* Row separator character for some modes (MODE_Ascii), read-only */
   char *zRecordSeparator;
-  /* Row set prefix for some modes */
+  /* Row set prefix for some modes if not 0 */
   char *zRecordLead;
-  /* Row set suffix for some modes */
-  char *zRecordTrail;
-  /* Text to represent a NULL in external data formats */
+  /* Row set suffix for some modes if not 0 */
+  char *zRecordTail;
+  /* Text to represent a NULL in external data formats, read-only */
   char *zNullValue;
-  /* Number of column widths presently desired or tracked */
+  /* Name of table for which inserts are to be written or performed */
+  const char *zDestTable;
+  /* Next 3 members should be set and/or allocated by .width meta-command.
+   * The values of pSpecWidths[i] and pHaveWidths[i] can be modified or
+   * used by extensions, but setColumnWidths(...) must resize those lists.
+   */
+  /* Number of column widths presently desired or tracked, read-only */
   int  numWidths; /* known allocation count of next 2 members */
   /* The column widths last specified via .width command */
-  int  *pWantWidths;
-  /* The column widths last observed in query results */
+  int  *pSpecWidths;
+  /* The column widths last observed in query results, read-only */
   int  *pHaveWidths;
+  /* Internal and opaque shell state, not for use by extensions */
+  struct ShellInState *pSIS; /* Offset of this member is NOT STABLE. */
 } ShellExState;
 
-/* The shell's state, shared among meta-command implementations.
- * The ShellStateX object includes a private partition whose content
- * and usage are opaque to shell extensions compiled separately
- * from the shell.c core. (As defined here, it is wholly opaque.)
- */
-typedef struct ShellStateX {
-  ShellExState sxs;       /* sizeof(ShellExState) will never shrink. */
-  struct ShellState *pSS; /* The offset of this member is NOT STABLE. */
-} ShellStateX;
-
 /* This function pointer has the same signature as the sqlite3_X_init()
  * function that is called as SQLite3 completes loading an extension.
  */
@@ -83,10 +93,10 @@ typedef int (*ExtensionId)
 INTERFACE_BEGIN( MetaCommand );
 PURE_VMETHOD(const char *, name, MetaCommand, 0,());
 PURE_VMETHOD(const char *, help, MetaCommand, 1,(int more));
-PURE_VMETHOD(struct {unsigned minArgs; unsigned maxArgs;},
-             argsRange, MetaCommand, 0,());
+PURE_VMETHOD(int, argsCheck, MetaCommand,
+             3, (char **pzErrMsg, int nArgs, char *azArgs[]));
 PURE_VMETHOD(int, execute, MetaCommand,
-             4,(ShellStateX *, char **pzErrMsg, int nArgs, char *azArgs[]));
+             4,(ShellExState *, char **pzErrMsg, int nArgs, char *azArgs[]));
 INTERFACE_END( MetaCommand );
 
 /* Define error codes to be returned either by a meta-command during
@@ -134,26 +144,60 @@ PURE_VMETHOD(void, closeDataInStream, ImportHandler,
 INTERFACE_END( ImportHandlerVtable );
 
 typedef struct {
-  int helperCount;
+  int helperCount; /* Helper count, not including sentinel */
   union ExtHelp {
     struct {
-      void (*failIfSafeMode)(ShellStateX *p, const char *zErrMsg, ...);
+      int (*failIfSafeMode)(ShellExState *p, const char *zErrMsg, ...);
+      FILE * (*currentOutputFile)(ShellExState *p);
+      struct InSource * (*currentInputSource)(ShellExState *p);
+      char * (*strLineGet)(char *zBuf, int ncMax, struct InSource *pInSrc);
+      void (*setColumnWidths)(ShellExState *p, char *azWidths[], int nWidths);
+      int (*nowInteractive)(ShellExState *p);
+      void (*sentinel)(void);
     } named ;
-    void (*nameless[2])(); /* Same as named but anonymous plus a sentinel. */
+    void (*nameless[5+1])(); /* Same as named but anonymous plus a sentinel. */
   } helpers;
 } ExtensionHelpers;
 
-#define SHELLEXT_VALIDITY_MARK "ExtensibleShell"
+/* Various shell extension helpers and feature registration functions */
+typedef struct ShellExtensionAPI {
+  /* Utility functions for use by extensions */
+  ExtensionHelpers * pExtHelp;
 
+  /* Functions for extension to register its implementors with shell */
+  const int numRegistrars; /* 3 for this version */
+  union {
+    struct ShExtAPI {
+      /* Register a meta-command */
+      int (*registerMetaCommand)(ShellExState *p,
+                                 ExtensionId eid, MetaCommand *pMC);
+      /* Register query result data display (or other disposition) mode */
+      int (*registerOutMode)(ShellExState *p,
+                             ExtensionId eid, OutModeHandler *pOMH);
+      /* Register an import variation from (various sources) for .import */
+      int (*registerImporter)(ShellExState *p,
+                              ExtensionId eid, ImportHandler *pIH);
+      /* Preset to 0 at extension load, a sentinel for expansion */
+      void (*sentinel)(void);
+    } named;
+    void (*pFunctions[4])(); /* 0-terminated sequence of function pointers */
+  } api;
+} ShellExtensionAPI;
+
+/* Struct passed to extension init function to establish linkage. The
+ * lifetime of instances spans only the init call itself. Extensions
+ * should make a copy, if needed, of pShellExtensionAPI for later use.
+ * Its referant is static, persisting for the process duration.
+ */
 typedef struct ShellExtensionLink {
-  char validityMark[16]; /* Preset to contain "ExtensibleShell\x00" */
-  char *zErrMsg;         /* Extension puts error message here if any. */
-  int sizeOfThis;           /* sizeof(struct ShellExtensionLink) */
-  const char *shellVersion; /* Preset to "3.??.??\x00" or similar */
+  int sizeOfThis;        /* sizeof(ShellExtensionLink) for expansion */
+  ShellExtensionAPI *pShellExtensionAPI;
+  ShellExState *pSSX;    /* For use in extension feature registrations */
+  char *zErrMsg;         /* Extension error messages land here, if any. */
 
   /* An init "out" parameter, used as the loaded extension ID. Unless
    * this is set within sqlite3_X_init() prior to register*() calls,
-   * the extension cannot be unloaded. 
+   * the extension cannot be unloaded.
    */
   ExtensionId eid;
 
@@ -162,55 +206,23 @@ typedef struct ShellExtensionLink {
    */
   void (*extensionDestruct)(void *);
 
-  /* Various shell extension helpers and feature registration functions
-   */
-  ExtensionHelpers * pExtHelp;
-
-  union ShellExtensionAPI {
-    struct ShExtAPI {
-      /* Register a meta-command */
-      int (*registerMetaCommand)(ExtensionId eid, MetaCommand *pMC);
-      /* Register an output data display (or other disposition) mode */
-      int (*registerOutMode)(ExtensionId eid, OutModeHandler *pOMH);
-      /* Register an import variation from (various sources) for .import */
-      int (*registerImporter)(ExtensionId eid, ImportHandler *pIH);
-      /* Preset to 0 at extension load, a sentinel for expansion */
-      void (*pExtra)(void); 
-    } named;
-    void (*pFunctions[4])(); /* 0-terminated sequence of function pointers */
-  } api;
 } ShellExtensionLink;
 
-/* Test whether a char ** references a ShellExtensionLink instance's
- * validityMark, and if so return the instance's address, else return 0.
- * This macro may be used by a shell extension's sqlite3_X_init() function
- * to obtain a pointer to the ShellExtensionLink struct, derived from the
- * error message pointer (pzErrMsg) passed as the 2nd argument. This enables
- * the extension to incorporate its features into a running shell process.
- */
-#define EXTENSION_LINKAGE_PTR(pzem) ( \
-  pzem != 0 && *pzem != 0 && strcmp(*pzem, SHELLEXT_VALIDITY_MARK) == 0 \
-  && *pzem == (char *)pzem \
-  + offsetof(ShellExtensionLink, validityMark) \
-  - offsetof(ShellExtensionLink, zErrMsg) ) \
-  ? (ShellExtensionLink *) \
-    ((char *)pzem-offsetof(ShellExtensionLink,zErrMsg)) \
-  : 0
-
-/* String used with SQLite "Pointer Passing Interfaces" as a type marker. 
+/* String used with SQLite "Pointer Passing Interfaces" as a type marker.
  * That API subset is used by the shell to pass its extension API to the
- * sqlite3_X_init() function of extensions, via the DB parameter.
+ * sqlite3_X_init() function of shell extensions, via the DB parameter.
  */
 #define SHELLEXT_API_POINTERS "shellext_api_pointers"
 
 /* Pre-write a function to retrieve a ShellExtensionLink pointer from the
- * shell's DB. This is an alternative to use of the EXTENSION_LINKAGE_PTR
- * macro above. It takes some more code, replicated across extensions.
+ * shell's DB. This macro defines a function which will return either a
+ * pointer to a ShellExtensionLink instance during an extension's *init*()
+ * call (during shell extension load) or 0 (during SQLite extension load.)
  */
 #define DEFINE_SHDB_TO_SHEXT_API(func_name) \
  static ShellExtensionLink * func_name(sqlite3 * db){ \
   ShellExtensionLink *rv = 0; sqlite3_stmt *pStmt = 0; \
-  if( SQLITE_OK==sqlite3_prepare(db,"SELECT shext_pointer(0)",-1,&pStmt,0) \
+  if( SQLITE_OK==sqlite3_prepare_v2(db,"SELECT shext_pointer(0)",-1,&pStmt,0) \
       && SQLITE_ROW == sqlite3_step(pStmt) ) \
     rv = (ShellExtensionLink *)sqlite3_value_pointer \
      (sqlite3_column_value(pStmt, 0), SHELLEXT_API_POINTERS); \
diff --git a/src/test_shellext.c b/src/test_shellext.c
new file mode 100644 (file)
index 0000000..1777ae0
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+** 2022 Feb 28
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**    May you do good and not evil.
+**    May you find forgiveness for yourself and forgive others.
+**    May you share freely, never taking more than you give.
+**
+*************************************************************************
+** Test extension for testing the shell's .load -shellext ... function.
+** gcc -shared -fPIC -Wall -I$srcdir -I.. -g test_shellext.c -o test_shellext.so
+*/
+#include <stdio.h>
+#include "shext_linkage.h"
+
+SQLITE_EXTENSION_INIT1;
+
+DEFINE_SHDB_TO_SHEXT_API(shext_api);
+
+/*
+** Extension load function.
+*/
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int sqlite3_testshellext_init(
+  sqlite3 *db,
+  char **pzErrMsg,
+  const sqlite3_api_routines *pApi
+){
+  int nErr = 0;
+  ShellExtensionLink *papi;
+  SQLITE_EXTENSION_INIT2(pApi);
+  papi = shext_api(db);
+  if( papi ){
+    printf("Got papi, equality=%d\n", &papi->zErrMsg==pzErrMsg);
+  }
+  else
+    printf("No papi pointer.\n");
+  return nErr ? SQLITE_ERROR : SQLITE_OK;
+}
index cdb7d894a7318f9f60e840e8d82cc36d9b6e31c1..0da8fb267e61f9c2e9f08ac9a486ad45dfd1b051 100644 (file)
@@ -200,6 +200,7 @@ proc emit_sync { lines ostrm precLines {fromFile ""} } {
 array set ::cmd_help {}
 array set ::cmd_dispatch {}
 array set ::cmd_condition {}
+array set ::metacmd_init {}
 array set ::inc_type_files {}
 set ::iShuffleErrors 0
 # Ease use of { and } in literals. Instead, $::lb and $::rb can be used.
@@ -214,9 +215,6 @@ set ::parametersHelp {
    ARGS_SIGNATURE sets the formal argument list for the dispatchable functions.
    DISPATCH_ENTRY sets the text of each entry line in emitted dispatch table.
    DISPATCHEE_NAME sets the name to be generated for dispatchable functions.
-   CMD_CAPTURE_RE sets a regular expression to be used for capturing the name
-     to be used for meta-commands within a line passed into COLLECT_DISPATCH,
-     (which is needed to permit them to be emitted in lexical order by name.)
    DC_ARG_COUNT sets the effective argument count for DISPATCHABLE_COMMAND().
    DC_ARG#_DEFAULT sets a default value, DISPATCHABLE_COMMAND() #'th argument.
    HELP_COALESCE sets whether to coalesce secondary help text and add newlines.
@@ -236,8 +234,9 @@ array set ::dispCfg [list \
   DISPATCH_ENTRY \
    "{ \"\$cmd\", \${cmd}Command, \$arg1,\$arg2,\$arg3 }," \
   DISPATCHEE_NAME {${cmd}Command} \
-  CMD_CAPTURE_RE "^\\s*$::lb\\s*\"(\\w+)\"" \
   HELP_COALESCE 0 \
+  METACMD_INIT \
+   "META_CMD_INFO( \${cmd}, \$arg1,\$arg2,\$arg3,\n <HT0>,\n <HT1> )," \
 ]
 # Other config keys:
 #  DC_ARG_COUNT=<number of arguments to DISPATCHABLE_COMMAND()>
@@ -337,7 +336,6 @@ proc chunkify_help {htin} {
 }
 
 array set ::macroTailREs [list \
-  COLLECT_DISPATCH {^\(\s*([\w\*]+)\s*\)\[} \
   COLLECT_HELP_TEXT {^\[} \
   COMMENT {\s+(.*)$} \
   CONDITION_COMMAND {^\(\s*(\w+)\s+([^;]+)\);} \
@@ -345,6 +343,7 @@ array set ::macroTailREs [list \
   DISPATCHABLE_COMMAND {^\(([\w\? ]+)\)(\S)\s*$} \
   EMIT_DISPATCH {^\((\d*)\)} \
   EMIT_HELP_TEXT {^\((\d*)\)} \
+  EMIT_METACMD_INIT {^\((\d*)\)} \
   INCLUDE {^(?:\(\s*(\w+)\s*\))|(?:\s+([\w./\\]+)\M)} \
   IGNORE_COMMANDS {^\(\s*([-+\w ]*)\)\s*;\s*} \
 ]
@@ -352,16 +351,15 @@ array set ::macroTailREs [list \
 # COMMENT tailCapture_Commentary
 # CONDITION_COMMAND tailCapture_Cmd_Condition
 # CONFIGURE_DISPATCH tailCapture_Empty
-# COLLECT_DISPATCH tailCapture_Cmd
 # COLLECT_HELP_TEXT tailCapture_Empty
 # DISPATCHABLE_COMMAND tailCapture_ArgsGlom_TrailChar
 # EMIT_DISPATCH tailCapture_Indent
 # EMIT_HELP_TEXT tailCapture_Indent
+# EMIT_METACMD_INIT tailCapture_Indent
 # IGNORED_COMMANDS tailCapture_SignedCmdGlom
 # INCLUDE tailCapture_IncType_Filename
 
 array set ::macroUsages [list \
-  COLLECT_DISPATCH "\[\n   <dispatch table entry lines>\n  \];" \
   COLLECT_HELP_TEXT "\[\n   <help text lines>\n  \];" \
   COMMENT " <arbitrary characters to end of line>" \
   CONDITION_COMMAND "( name pp_expr );" \
@@ -370,6 +368,7 @@ array set ::macroUsages [list \
       "( name args... ){\n   <implementation code lines>\n  }" \
   EMIT_DISPATCH "( indent );" \
   EMIT_HELP_TEXT "( indent );" \
+  EMIT_METACMD_INIT "( indent );" \
   INCLUDE {( <inc_type> )} \
   SKIP_COMMANDS "( <signed_names> );" \
 ]
@@ -405,32 +404,6 @@ proc IGNORED_COMMANDS {inSrc tcSignedCmdGlom ostrm} {
   return 1
 }
 
-proc COLLECT_DISPATCH {inSrc tailCaptureCmdOrStar ostrm} {
-  # Collect dispatch table entries, along with cmd(s) as ordering info.
-  foreach {infile istrm inLineNum} $inSrc {}
-  foreach {cmd} $tailCaptureCmdOrStar {}
-  set iAte 2
-  set lx [gets $istrm]
-  set disp_frag {}
-  while {![eof $istrm] && ![regexp {^\s*\];} $lx]} {
-    lappend disp_frag $lx
-    set grabCmd $::dispCfg(CMD_CAPTURE_RE)
-    if {![regexp $grabCmd $lx ma dcmd]} {
-      puts stderr "malformed dispatch element:\n $lx"
-      incr ::iShuffleErrors
-    } elseif {$cmd ne "*" && $dcmd ne $cmd} {
-      puts stderr "misdeclared dispatch element:\n $lx"
-      incr ::iShuffleErrors
-    } else {
-      set ::cmd_dispatch($dcmd) [list $lx]
-      set_src_tags dispatch $dcmd $inSrc
-    }
-    set lx [gets $istrm]
-    incr iAte
-  }
-  return $iAte
-}
-
 proc COMMENT {inSrc tailCaptureIgnore ostrm} {
   # Allow comments in an input file which have no effect on output.
   return 1
@@ -572,8 +545,10 @@ proc DISPATCHABLE_COMMAND {inSrc tailCapture ostrm} {
       set fname [subst $::dispCfg(DISPATCHEE_NAME)]
       set funcOpen "$rsct $fname\($argexp\)$::lb"
       set dispEntry [subst $::dispCfg(DISPATCH_ENTRY)]
+      set mcInit [subst $::dispCfg(METACMD_INIT)]
       emit_conditionally $cmd [linsert $body 0 $funcOpen] $inSrc $ostrm
       set ::cmd_dispatch($cmd) [list $dispEntry]
+      set ::metacmd_init($cmd) $mcInit
       set_src_tags dispatch $cmd $inSrc
     }
   }
@@ -589,6 +564,42 @@ proc EMIT_DISPATCH {inSrc tailCap ostrm} {
   return 1
 }
 
+proc EMIT_METACMD_INIT {inSrc tailCap ostrm} {
+  # Emit the collected metacommand init table entries, in command order, maybe
+  # wrapped with a conditional construct as set by CONDITION_COMMAND(). Prior
+  # to the emit, substitute markers <HT{0,1}> with help text for the command.
+  foreach cmd [lsort [array names ::metacmd_init]] {
+    set initem $::metacmd_init($cmd)
+    set ht0i -1
+    if {[info exists ::cmd_help($cmd)]} {
+      set ht $::cmd_help($cmd) ; # ht is a list.
+      # HT0 is its content through first trailing comma.
+      # HT1 is the remainder.
+      for {set itc 0} {$itc < [llength $ht]} {incr itc} {
+        if {[regexp {,\w*$} [lindex $ht $itc]]} {
+          set ht0i $itc
+          break
+        }
+      }
+    }
+    if {$ht0i != -1} {
+      set ht0 [regsub {,\w*$} [join [lrange $ht 0 $ht0i] "\n"] "\n"]
+      incr ht0i
+      set ht1 [regsub {,\w*$} [join [lrange $ht $ht0i end] "\n"] "\n"]
+      set ht0 "\n$ht0"
+      set ht1 "\n$ht1"
+    } else {
+      set ht0 {0 /* help or commas missing */}
+      set ht1 0
+    }
+    set initem [regsub {<HT0>} $initem $ht0]
+    set initem [regsub {<HT1>} $initem $ht1]
+    set initem [split $initem "\n"]
+    emit_conditionally $cmd $initem $inSrc $ostrm $tailCap
+  }
+  return 1
+}
+
 proc EMIT_HELP_TEXT {inSrc tailCap ostrm} {
   # Emit the collected help text table entries, in command order, maybe
   # wrapped with a conditional construct as set by CONDITION_COMMAND().