]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
All shell tests except shell8.test pass. (a WIP)
authorlarrybr <larrybr@noemail.net>
Fri, 10 Dec 2021 18:11:29 +0000 (18:11 +0000)
committerlarrybr <larrybr@noemail.net>
Fri, 10 Dec 2021 18:11:29 +0000 (18:11 +0000)
FossilOrigin-Name: 653db501b46a4035ea1cb2f39862d781fa40fa0442a6abc84d32a9ac29e3af20

1  2 
manifest
manifest.uuid
src/obj_interfaces.h
src/shell.c.in
src/shext_linkage.h
test/shell1.test

diff --cc manifest
index d290f4e1a6366a97419f578755dc5b2012017752,765acd7045deeb6d4aa478c6d283f22ef5a17003..955c297c47bd53143e359f8d6d9ac7fcc94d8e20
+++ b/manifest
@@@ -1,5 -1,5 +1,5 @@@
- C Pickup\strunk\s.mode\supgrade\s+\scosmetic\sshell\schanges
- D 2021-12-09T16:45:03.203
 -C Add\ssupport\sfor\sBloom-filters\sas\sa\sperformance\soptimization\sfor\sjoins.
 -D 2021-12-09T20:06:18.928
++C All\sshell\stests\sexcept\sshell8.test\spass.\s(a\sWIP)
++D 2021-12-10T18:11:29.142
  F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
  F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
  F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@@ -532,7 -531,6 +532,7 @@@ F src/mutex_noop.c 9d4309c075ba9cc7249e
  F src/mutex_unix.c dd2b3f1cc1863079bc1349ac0fec395a500090c4fe4e11ab775310a49f2f956d
  F src/mutex_w32.c caa50e1c0258ac4443f52e00fe8aaea73b6d0728bd8856bedfff822cae418541
  F src/notify.c 89a97dc854c3aa62ad5f384ef50c5a4a11d70fcc69f86de3e991573421130ed6
- F src/obj_interfaces.h ed6864b7970f5fcc1dc20654c778f6d73dd724e28396a270ec0f3632f0e47e25
++F src/obj_interfaces.h 194b36e15805ee8a1d8056986ccc3cb5762b4ca55ae2deb13ceb5adde965f8e6
  F src/os.c b1c4f2d485961e9a5b6b648c36687d25047c252222e9660b7cc25a6e1ea436ab
  F src/os.h 26890f540b475598cd9881dcc68931377b8d429d3ea3e2eeb64470cde64199f8
  F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85
@@@ -554,12 -552,11 +554,12 @@@ F src/random.c 097dc8b31b8fba5a9aca1697
  F src/resolve.c 4a1db4aadd802683db40ca2dbbb268187bd195f10cbdb7206dbd8ac988795571
  F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
  F src/select.c a7a3d9f54eb24821ec5f67f2e5589b68a5d42d46fc5849d7376886777d93a85a
- F src/shell.c.in 3bdbdf9b8051fbab9f5f654dd3c406f0fd6d743ae270825ea1ee3ec8930d25c0
- F src/shext_linkage.h 1508132ce49c023d8ac445399fa275a01f27667f8686797051aefd1d6e8ae03a
- F src/sqlite.h.in 5cd209ac7dc4180f0e19292846f40440b8488015849ca0110c70b906b57d68f0
 -F src/shell.c.in 239bee1085d94964f02582b0714dc3fc85cfc16e27e95813e4dbc24bb215a7e0
++F src/shell.c.in c7873707062a7d90d3d3e0993386d890ac4312ff735f2fe3d2381a447a23d84d
++F src/shext_linkage.h 5897e8140a06cb733d07a927994b30d41225eb93b5b8cf2ad0e3460cb4e95fd6
+ F src/sqlite.h.in 5999d6db0e65afbd686b76cddc385b310aa3815624edba43987913067f50e209
  F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
  F src/sqlite3ext.h 8ff2fd2c166150b2e48639f5e506fb44e29f1a3f65031710b9e89d1c126ac839
- F src/sqliteInt.h 375bb03b5c2de4c3e2f746e6205f01b8a51abd5835a1a72ab9a1c027f5c9849e
+ F src/sqliteInt.h b4391c3c2ae0a8020ce0f543fc2b529f9bcdf72ab7ba3c31d170e3228169162f
  F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657
  F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1
  F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
@@@ -1383,11 -1380,11 +1383,11 @@@ F test/sharedA.test 49d87ec54ab640fbbc3
  F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e
  F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939
  F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
- F test/shell1.test f7cf041033277c0759aef303fcb42615e569163d23cc8e005deb37a9e29c419a
 -F test/shell1.test c354008b27c904f0166c2138abd7382013ea070b41114114ecbdfb32c726a807
 -F test/shell2.test f00a0501c00583cbc46f7510e1d713366326b2b3e63d06d15937284171a8787c
++F test/shell1.test 022db2884daceef52c40c4ed9f3c684e5b9930f1e0097d66857b420890a1f23c
 +F test/shell2.test de123dd6be4b774b5ebdc81b29b3515c29c4a6a81bd5d2e1c38605d2f775a25a
  F test/shell3.test cb4b835a901742c9719437a89171172ecc4a8823ad97349af8e4e841e6f82566
  F test/shell4.test 3ed6c4b42fd695efcbc25d69ef759dbb15855ca8e52ba6c5ee076f8b435f48be
 -F test/shell5.test 6e4aa0e531dcb8dcf74b7920a2a7442c6712d4dff8422bbc81f768f9dee8a0e3
 +F test/shell5.test 596342db4ada597c6e021081a63f27be87eb5b1d8cf71028f3ec7dc17a8dd42e
  F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3
  F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f
  F test/shell8.test 388471d16e4de767333107e30653983f186232c0e863f4490bb230419e830aae
@@@ -1937,7 -1934,8 +1937,7 @@@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a9
  F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
  F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
  F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
- P 6956e989083462b6745276c45edcb03ac7ab9c31518e1f505bfae9e8f1cd2b2f 1eefd957ff35e961685db565f7ef116c566a04574c5bedad7298b3cc69dd72d2
- R 2caf0db7e891499e6512ba7304c5d674
 -P 1eefd957ff35e961685db565f7ef116c566a04574c5bedad7298b3cc69dd72d2 ce42039f5647b1f276acf5d9911528ecb47df1544a587def72c8cd6b2f664289
 -R c6e1a17bdb20d0994b97369ef3d14311
 -T +closed ce42039f5647b1f276acf5d9911528ecb47df1544a587def72c8cd6b2f664289
 -U drh
 -Z 2960f76524291316688b632931fc780c
++P 8dc69c81b9ad1c45f881f7833efdec736c16d6f542490d14d7f3707d5e0ee1ef 633bfeeea2bccdd44126acf3f61ecca163c9d933bdc787a2c18a697dc9406882
++R 468297f5d055ff0dcc1bb6af44d0c133
 +U larrybr
- Z 2a9012fb2ed359d6d4488b48bf07c732
++Z 38665f90add4e3626669babde7fc6734
diff --cc manifest.uuid
index 7c94d5d54a918ac5b39b50f2d0c9ce14fd14d031,bbb228f8b87b04861aa8a1ed6651ea2b4d10be03..dcc81baffd0769dc5ed7a82ca6b3c754d4c76708
@@@ -1,1 -1,1 +1,1 @@@
- 8dc69c81b9ad1c45f881f7833efdec736c16d6f542490d14d7f3707d5e0ee1ef
 -633bfeeea2bccdd44126acf3f61ecca163c9d933bdc787a2c18a697dc9406882
++653db501b46a4035ea1cb2f39862d781fa40fa0442a6abc84d32a9ac29e3af20
index 8ec18f6cadb78d6227bfbfd3fd4f725f6ee839c2,0000000000000000000000000000000000000000..1e5eec15a457b7ae5e5ff44dce4884cfc2946657
mode 100644,000000..100644
--- /dev/null
@@@ -1,95 -1,0 +1,98 @@@
-  * used for shell extension. These macros are suitable for either C
-  * or C++ implementations. For C implementations, an extra struct is
 +#ifndef OBJIFACE_H
 +#define OBJIFACE_H
 +
 +/* This header defines macros to aid declaration of object interfaces
-  *     IMPLEMENTING( returnType, methodName, ClassName, argCount, args ) [a]
++ * used for shell extension, and to support convenient implementation
++ * of those interfaces.[a] These macros are suitable for either C or
++ * C++ implementations. For C implementations, an extra struct is
 + * defined, whose typename is <InterfaceName>_Vtable, which will need
 + * to be instantiated and populated with function pointers having the
 + * same order and signatures as those declared for the interface. For
 + * C++ implementations, a purely abstract base class (with all public
 + * methods) is declared from which a concrete class will need to be
 + * derived and instantiated. (No *_Vtable is necessary for C++.)
 + *
 + * The macros defined for external use are:
 + *   (for C or C++ implementations)
 + *     INTERFACE_BEGIN( InterfaceName )
 + *     PURE_VMETHOD( returnType, methodName, InterfaceName, argCount, args )
 + *     INTERFACE_END( InterfaceName )
-  *     VTABLE_NAME( ClassName ) [b]
-  *   (for C++ implementations only [c])
++ *     IMPLEMENTING( returnType, methodName, ClassName, argCount, args ) [b]
 + *   (for C implementations only)
-  *   [a. This macro may be useful for function/method definitions which
++ *     VTABLE_NAME( ClassName ) [c]
++ *   (for C++ implementations only [d])
 + *     CONCRETE_BEGIN( InterfaceName, DerivedName )
 + *     CONCRETE_METHOD( returnType, methodName, ClassName, argCount, args )
 + *     CONCRETE_END( DerivedName )
 + * Notes on these macros:
 + *   1. These macros should be used in the order shown. Many should be
 + *      terminated with either ';' or a curly-braced construct (which
 + *      helps auto-indentation tools to operate sanely.)
 + *   2. The "args" parameter is a parenthesized list of the additional
 + *     arguments, those beyond an explicit "InterfaceName *pThis" for C
 + *     or the implicit "this" for C++.
 + *   3. The argCount parameter must number the additional arguments.
 + *   4. A leading method, named "destruct" without additional arguments
 + *     and returning void, is declared for all interfaces. This is not
 + *     the C++ destructor. (It might delegate to a destructor.)
-  *   [b. This macro is useful for populating a C dispatch table whose
++ *   [a. The convenience is that the signatures from the interface may
++ *     be reused for method implementations with a copy and paste. ]
++ *   [b. This macro may be useful for function/method definitions which
 + *     implement methods in an INTERFACE_{BEGIN,...,END} sequence. ]
-  *   [c. These macros are useful for declaring instantiatable classes
++ *   [c. This macro is useful for populating a C dispatch table whose
 + *    layout is declared in the INTERFACE_{BEGIN,...,END} sequence. ]
++ *   [d. These macros are useful for declaring instantiatable classes
 + *    derived from an abstract base class via INTERFACE_{BEGIN,END}. ]
 + */
 +
 +#ifdef __cplusplus
 +
 +# define VMETHOD_BEGIN(rType, mName) virtual rType mName(
 +# define PURE_VMETHOD_END )=0
 +#define ARG_FIRST_0(t)
 +#define ARG_FIRST_1(t)
 +#else
 +# define VMETHOD_BEGIN(rType, mName) rType (*mName)(
 +# define PURE_VMETHOD_END )
 +#define ARG_FIRST_0(t) t *pThis
 +#define ARG_FIRST_1(t) t *pThis,
 +#endif
 +#define ARG_FIRST_2 ARG_FIRST_1
 +#define ARG_FIRST_3 ARG_FIRST_1
 +#define ARG_FIRST_4 ARG_FIRST_1
 +#define ARG_FIRST_5 ARG_FIRST_1
 +#define ARGS_EXPAND(na) ARGS_EXPAND_ ## na
 +#define ARGS_EXPAND_0()
 +#define ARGS_EXPAND_1(a1) a1
 +#define ARGS_EXPAND_2(a1,a2) a1,a2
 +#define ARGS_EXPAND_3(a1,a2,a3) a1,a2,a3
 +#define ARGS_EXPAND_4(a1,a2,a3,a4) a1,a2,a3,a4
 +#define ARGS_EXPAND_5(a1,a2,a3,a4,a5) a1,a2,a3,a4,a5
 +
 +#define PURE_VMETHOD(rt, mn, ot, na, args) VMETHOD_BEGIN(rt, mn) \
 + ARG_FIRST_ ## na(ot) ARGS_EXPAND(na)args PURE_VMETHOD_END
 +#define CONCRETE_METHOD(rt, mn, ot, na, args) rt mn( \
 + ARG_FIRST_ ## na(ot) ARGS_EXPAND(na)args )
 +
 +#ifdef __cplusplus
 +# define INTERFACE_BEGIN(iname) struct iname { \
 +    PURE_VMETHOD(void, destruct, iname, 0, ())
 +# define INTERFACE_END(iname) }
 +# define CONCRETE_BEGIN(iname, derived) class derived : public iname { \
 +    CONCRETE_METHOD(void, destruct, derived, 0, ())
 +# define CONCRETE_END(derived) }
 +# define IMPLEMENTING(rt, mn, derived, na, args) rt derived::mn(  \
 + ARG_FIRST_ ## na(derived) ARGS_EXPAND(na)args )
 +#else
 +# define VTABLE_NAME(name) name ## _Vtable
 +# define INTERFACE_BEGIN(iname) typedef struct iname { \
 +    struct VTABLE_NAME(iname) * pMethods;              \
 +  } iname; typedef struct VTABLE_NAME(iname) {         \
 +  PURE_VMETHOD(void, destruct, iname, 0, ())
 +# define INTERFACE_END(iname) } VTABLE_NAME(iname)
 +# define DECORATE_METHOD(ot, mn)  ot ## _ ## mn
 +# define IMPLEMENTING(rt, mn, ot, na, args) rt DECORATE_METHOD(ot, mn)(  \
 + ARG_FIRST_ ## na(ot) ARGS_EXPAND(na)args )
 +#endif
 +
 +#endif /* !defined(OBJIFACE_H) */
diff --cc src/shell.c.in
index 0a20ba9784e91aecc04843dee3afc9b29e5c9ca0,3262f98c12d01da7aeb8b57e752c86a42f0f2df8..baf568471c25362f169e0e744aeba31c1b1ee33f
@@@ -439,6 -421,6 +439,7 @@@ static int stdout_is_console = 1
  ** by the SIGINT handler to interrupt database processing.
  */
  static sqlite3 *globalDb = 0;
++static sqlite3_mutex *pGlobalDbLock = 0;
  
  /*
  ** True if an interrupt (Control-C) has been received.
@@@ -499,10 -481,10 +500,21 @@@ void utf8_printf(FILE *out, const char 
  # define raw_printf fprintf
  #endif
  
++/*
++** Provide a way for embedding apps to handle OOP condition their way.
++** This is crude, and using it will undoubtedly leak memory and handles.
++** The alternative is extensive recoding of the shell, (not for today.)
++** This could become a longjump(...) (paired with app's setjump(...).)
++*/
++#ifndef SHELL_OOM_EXIT
++# define SHELL_OOM_EXIT exit(1)
++#endif
  /* Indicate out-of-memory and exit. */
  static void shell_out_of_memory(void){
 -  raw_printf(stderr,"Error: out of memory\n");
 -  exit(1);
 +  raw_printf(STD_ERR,"Error: out of memory\n");
-   exit(1);
++  sqlite3_mutex_free(pGlobalDbLock); 
++  pGlobalDbLock = 0;
++  SHELL_OOM_EXIT;
  }
  
  #ifdef SQLITE_DEBUG
@@@ -687,17 -669,17 +699,25 @@@ static FILE * openChrSource(const char 
  }
  
  /*
--** This routine reads a line of text from FILE in, stores
--** the text in memory obtained from malloc() and returns a pointer
--** to the text.  NULL is returned at end of file, or if malloc()
--** fails.
++** This routine reads a line of text from FILE in, stores it in
++** memory obtained from malloc() and returns a pointer to it.
++** NULL is returned at end of file, or if malloc() fails.
  **
--** If zLine is not NULL then it is a malloced buffer returned from
--** a previous call to this routine that may be reused.
++** If zLine is not NULL then it is (and must be) a malloced buffer
++** returned from a previous call to this routine that may be reused.
  */
  static char *local_getline(char *zLine, FILE *in){
--  int nLine = zLine==0 ? 0 : 100;
--  int n = 0;
++#ifdef SQLITE_DEBUG
++  static char *zLinePrior = 0;
++  /* If zLine is passed in, not for the first time, it *must* be prior return,
++  ** otherwise the assumption made here as to its allocated size is shaky. */
++  assert(zLine==0 || zLinePrior==0 || zLine==zLinePrior);
++# define ZLINE_PRIOR(z) zLinePrior = z
++#else
++# define ZLINE_PRIOR(z)
++#endif
++  int nLine = zLine==0 ? 0 : 100; /* Length now (or to be)allocated */
++  int n = 0; /* Count of chars accumulated now */
  
    while( 1 ){
      if( n+100>nLine ){
      }
      if( fgets(&zLine[n], nLine - n, in)==0 ){
        if( n==0 ){
++        /* Empty (EOF) input and zip accumulated. Return so. */
          free(zLine);
++        ZLINE_PRIOR(0);
          return 0;
        }
++      /* NUL-terminate presently obtained input (and done.) */
        zLine[n] = 0;
        break;
      }
++    /* Note non-NUL chars added in this loop. (Tough if input had NULs.) */
      while( zLine[n] ) n++;
--    if( n>0 && zLine[n-1]=='\n' ){
++    /* Replace final line-end with NUL. */
++    while( n>0 && (zLine[n-1]=='\n' || zLine[n-1]=='\r') ){
        n--;
--      if( n>0 && zLine[n-1]=='\r' ) n--;
++    }
++    if( zLine[n]==0 ){
++      continue;
++    }else{
        zLine[n] = 0;
        break;
      }
        int nTrans = strlen30(zTrans)+1;
        if( nTrans>nLine ){
          zLine = realloc(zLine, nTrans);
++        ZLINE_PRIOR(zLine);
          if( zLine==0 ) shell_out_of_memory();
        }
        memcpy(zLine, zTrans, nTrans);
      }
    }
  #endif /* defined(_WIN32) || defined(WIN32) */
++  ZLINE_PRIOR(zLine);
    return zLine;
  }
  
@@@ -774,7 -756,7 +804,6 @@@ static char *one_input_line(FILE *in, c
    return zResult;
  }
  
--
  /*
  ** Return the value of a hexadecimal digit.  Return -1 if the input
  ** is not a hex digit.
@@@ -1132,6 -1114,6 +1161,7 @@@ struct EQPGraph 
  typedef struct ShellState ShellState;
  struct ShellState {
    sqlite3 *db;           /* The database */
++  int abruptExit;        /* Flag for immediate shell exit, exit code */
    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 */
    u8 doXdgOpen;          /* Invoke start/open/xdg-open in output_reset() */
    u8 nEqpLevel;          /* Depth of the EQP output graph */
    u8 eTraceType;         /* SHELL_TRACE_* value for type of trace */
 -  u8 bSafeMode;          /* True to prohibit unsafe operations */
 -  u8 bSafeModePersist;   /* The long-term value of bSafeMode */
 +  u8 bSafeMode;          /* True when unsafe operations are prohibited */
-   u8 bSafeModeFuture;    /* Next bSafeMode - 0, 1 or suspending downto 1 */
++  u8 bSafeModeFuture;    /* See updateSafeMode regarding use of this. */
    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 */
    int *aiIndent;         /* Array of indents used in MODE_Explain */
    int nIndent;           /* Size of array aiIndent[] */
    int iIndent;           /* Index of current op in aiIndent[] */
--  char *zNonce;          /* Nonce for temporary safe-mode excapes */
++  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..." */
  };
  
++/* 
++** This procedure updates the bSafeMode flag after completion of any
++** operation (meta-command or SQL submission) that counts as one for
++** which safe mode might be suspended.
++** bSafeModeFuture has 3 states salient here:
++** equal 0 => Safe mode is and will remain inactive.
++** equal 1 => Safe mode is and will remain active.
++** N > 1 => Safe mode is suspended for N-1 operations.
++*/
++static void updateSafeMode(ShellState *pSS){
++  switch( pSS->bSafeModeFuture ){
++  case 2:
++  default:
++    --pSS->bSafeModeFuture;
++    /* Fall thru, suspension is in effect. */
++  case 0:
++    pSS->bSafeMode = 0;
++    break;
++  case 1:
++    pSS->bSafeMode = 1;
++  }
++}
  
  /* Allowed values for ShellState.autoEQP
  */
@@@ -1338,15 -1320,9 +1390,17 @@@ static void shellPutsFunc
  
  /*
  ** If in safe mode, print an error message described by the arguments
 -** and exit immediately.
 +** and "exit" without returning to the caller. This "exit" will occur
 +** immediately, as a process exit, for normal shell builds. When the
 +** shell is built with options to use it embedded, control returns to
 +** the caller of the shell's main, "do shell things" entry point.
- ** (TBD: Embedded exit arrangement)
++**
 +** It is an error, (perhaps with only minor effect such as memory leak),
 +** for a meta-command to call this function while it holds resources.
++**
++** The return is true if failing, 0 otherwise.
  */
--static void failIfSafeMode(
++static int failIfSafeMode(
    ShellState *p,
    const char *zErrMsg,
    ...
      va_start(ap, zErrMsg);
      zMsg = sqlite3_vmprintf(zErrMsg, ap);
      va_end(ap);
 -    raw_printf(stderr, "line %d: ", p->lineno);
 -    utf8_printf(stderr, "%s\n", zMsg);
 -    exit(1);
 +    raw_printf(STD_ERR, "line %d: ", p->lineno);
 +    utf8_printf(STD_ERR, "%s\n", zMsg);
-     exit(1);
++    p->abruptExit = 3;
++    return 1;
    }
++  return 0;
  }
  
  /*
@@@ -1810,12 -1788,12 +1866,20 @@@ static void output_csv(ShellState *p, c
  
  /*
  ** This routine runs when the user presses Ctrl-C
++** TBD: It will need some changes for embedability.
  */
  static void interrupt_handler(int NotUsed){
    UNUSED_PARAMETER(NotUsed);
    seenInterrupt++;
--  if( seenInterrupt>2 ) exit(1);
--  if( globalDb ) sqlite3_interrupt(globalDb);
++  if( seenInterrupt>2 ){
++    sqlite3_mutex_free(pGlobalDbLock);
++    exit(1);
++  }
++  if( globalDb ){
++    sqlite3_mutex_enter(pGlobalDbLock);
++    sqlite3_interrupt(globalDb);
++    sqlite3_mutex_leave(pGlobalDbLock);
++  }
  }
  
  #if (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE)
@@@ -1860,15 -1838,15 +1924,17 @@@ static int safeModeAuth
    UNUSED_PARAMETER(zA4);
    switch( op ){
      case SQLITE_ATTACH: {
--      failIfSafeMode(p, "cannot run ATTACH in safe mode");
++      if ( failIfSafeMode(p, "cannot run ATTACH in safe mode") )
++        return SQLITE_ERROR;
        break;
      }
      case SQLITE_FUNCTION: {
        int i;
        for(i=0; i<ArraySize(azProhibitedFunctions); i++){
          if( sqlite3_stricmp(zA1, azProhibitedFunctions[i])==0 ){
--          failIfSafeMode(p, "cannot use the %s() function in safe mode",
--                         azProhibitedFunctions[i]);
++          if( failIfSafeMode(p, "cannot use the %s() function in safe mode",
++                             azProhibitedFunctions[i]) )
++            return SQLITE_ERROR;
          }
        }
        break;
@@@ -4594,9 -4811,9 +4667,17 @@@ static void open_db(ShellState *p, int 
  ** Attempt to close the databaes connection.  Report errors.
  */
  void close_db(sqlite3 *db){
--  int rc = sqlite3_close(db);
++  int rc;
++  if( db==globalDb ){
++    sqlite3_mutex_enter(pGlobalDbLock);
++    globalDb = 0;
++    rc = sqlite3_close(db);
++    sqlite3_mutex_leave(pGlobalDbLock);
++  }else{
++    rc = sqlite3_close(db);
++  }
    if( rc ){
 -    utf8_printf(stderr, "Error: sqlite3_close() returns %d: %s\n",
 +    utf8_printf(STD_ERR, "Error: sqlite3_close() returns %d: %s\n",
          rc, sqlite3_errmsg(db));
    } 
  }
@@@ -7012,1803 -7243,1382 +7093,1843 @@@ static RecoverTable *recoverOrphanTable
    }
    return pTab;
  }
 +#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
  
 -/*
 -** This function is called to recover data from the database. A script
 -** to construct a new database containing all recovered data is output
 -** on stream pState->out.
 -*/
 -static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){
 -  int rc = SQLITE_OK;
 -  sqlite3_stmt *pLoop = 0;        /* Loop through all root pages */
 -  sqlite3_stmt *pPages = 0;       /* Loop through all pages in a group */
 -  sqlite3_stmt *pCells = 0;       /* Loop through all cells in a page */
 -  const char *zRecoveryDb = "";   /* Name of "recovery" database */
 -  const char *zLostAndFound = "lost_and_found";
 -  int i;
 -  int nOrphan = -1;
 -  RecoverTable *pOrphan = 0;
 -
 -  int bFreelist = 1;              /* 0 if --freelist-corrupt is specified */
 -  int bRowids = 1;                /* 0 if --no-rowids */
 -  for(i=1; i<nArg; i++){
 -    char *z = azArg[i];
 -    int n;
 -    if( z[0]=='-' && z[1]=='-' ) z++;
 -    n = strlen30(z);
 -    if( n<=17 && memcmp("-freelist-corrupt", z, n)==0 ){
 -      bFreelist = 0;
 -    }else
 -    if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
 -      i++;
 -      zRecoveryDb = azArg[i];
 -    }else
 -    if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
 -      i++;
 -      zLostAndFound = azArg[i];
 -    }else
 -    if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
 -      bRowids = 0;
 -    }
 -    else{
 -      utf8_printf(stderr, "unexpected option: %s\n", azArg[i]); 
 -      showHelp(pState->out, azArg[0]);
 -      return 1;
 +static int writeDb( char *azArg[], int nArg, ShellState *p, char **pzErr ){
 +  int rc = 0;
 +  const char *zDestFile = 0;
 +  const char *zDb = 0;
 +  sqlite3 *pDest;
 +  sqlite3_backup *pBackup;
 +  int j;
 +  int bAsync = 0;
 +  const char *zVfs = 0;
 +  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
 +  for(j=1; j<nArg; j++){
 +    const char *z = azArg[j];
 +    if( z[0]=='-' ){
 +      if( z[1]=='-' ) z++;
 +      if( strcmp(z, "-append")==0 ){
 +        zVfs = "apndvfs";
 +      }else
 +        if( strcmp(z, "-async")==0 ){
 +          bAsync = 1;
 +        }else
 +          {
 +            utf8_printf(STD_ERR, "unknown option: %s\n", azArg[j]);
 +            return SHELL_INVALID_ARGS;
 +          }
 +    }else if( zDestFile==0 ){
 +      zDestFile = azArg[j];
 +    }else if( zDb==0 ){
 +      zDb = zDestFile;
 +      zDestFile = azArg[j];
 +    }else{
 +      return SHELL_INVALID_ARGS;
      }
    }
 -
 -  shellExecPrintf(pState->db, &rc,
 -    /* Attach an in-memory database named 'recovery'. Create an indexed 
 -    ** cache of the sqlite_dbptr virtual table. */
 -    "PRAGMA writable_schema = on;"
 -    "ATTACH %Q AS recovery;"
 -    "DROP TABLE IF EXISTS recovery.dbptr;"
 -    "DROP TABLE IF EXISTS recovery.freelist;"
 -    "DROP TABLE IF EXISTS recovery.map;"
 -    "DROP TABLE IF EXISTS recovery.schema;"
 -    "CREATE TABLE recovery.freelist(pgno INTEGER PRIMARY KEY);", zRecoveryDb
 -  );
 -
 -  if( bFreelist ){
 -    shellExec(pState->db, &rc,
 -      "WITH trunk(pgno) AS ("
 -      "  SELECT shell_int32("
 -      "      (SELECT data FROM sqlite_dbpage WHERE pgno=1), 8) AS x "
 -      "      WHERE x>0"
 -      "    UNION"
 -      "  SELECT shell_int32("
 -      "      (SELECT data FROM sqlite_dbpage WHERE pgno=trunk.pgno), 0) AS x "
 -      "      FROM trunk WHERE x>0"
 -      "),"
 -      "freelist(data, n, freepgno) AS ("
 -      "  SELECT data, min(16384, shell_int32(data, 1)-1), t.pgno "
 -      "      FROM trunk t, sqlite_dbpage s WHERE s.pgno=t.pgno"
 -      "    UNION ALL"
 -      "  SELECT data, n-1, shell_int32(data, 2+n) "
 -      "      FROM freelist WHERE n>=0"
 -      ")"
 -      "REPLACE INTO recovery.freelist SELECT freepgno FROM freelist;"
 -    );
 +  if( zDestFile==0 ){
 +    return SHELL_INVALID_ARGS;
    }
 +  if( zDb==0 ) zDb = "main";
 +  rc = sqlite3_open_v2(zDestFile, &pDest, 
 +                       SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs);
 +  if( rc!=SQLITE_OK ){
 +    utf8_printf(STD_ERR, "Error: cannot open \"%s\"\n", zDestFile);
 +    close_db(pDest);
 +    return 1;
 +  }
 +  if( bAsync ){
 +    sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
 +                 0, 0, 0);
 +  }
 +  open_db(p, 0);
 +  pBackup = sqlite3_backup_init(pDest, "main", p->db, zDb);
 +  if( pBackup==0 ){
 +    utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(pDest));
 +    close_db(pDest);
 +    return 1;
 +  }
 +  while(  (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
 +  sqlite3_backup_finish(pBackup);
 +  if( rc==SQLITE_DONE ){
 +    rc = 0;
 +  }else{
 +    utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(pDest));
 +    rc = 1;
 +  }
 +  close_db(pDest);
 +  return rc;
 +}
  
 -  /* If this is an auto-vacuum database, add all pointer-map pages to
 -  ** the freelist table. Do this regardless of whether or not 
 -  ** --freelist-corrupt was specified.  */
 -  shellExec(pState->db, &rc, 
 -    "WITH ptrmap(pgno) AS ("
 -    "  SELECT 2 WHERE shell_int32("
 -    "    (SELECT data FROM sqlite_dbpage WHERE pgno=1), 13"
 -    "  )"
 -    "    UNION ALL "
 -    "  SELECT pgno+1+(SELECT page_size FROM pragma_page_size)/5 AS pp "
 -    "  FROM ptrmap WHERE pp<=(SELECT page_count FROM pragma_page_count)"
 -    ")"
 -    "REPLACE INTO recovery.freelist SELECT pgno FROM ptrmap"
 -  );
 -
 -  shellExec(pState->db, &rc, 
 -    "CREATE TABLE recovery.dbptr("
 -    "      pgno, child, PRIMARY KEY(child, pgno)"
 -    ") WITHOUT ROWID;"
 -    "INSERT OR IGNORE INTO recovery.dbptr(pgno, child) "
 -    "    SELECT * FROM sqlite_dbptr"
 -    "      WHERE pgno NOT IN freelist AND child NOT IN freelist;"
 -
 -    /* Delete any pointer to page 1. This ensures that page 1 is considered
 -    ** a root page, regardless of how corrupt the db is. */
 -    "DELETE FROM recovery.dbptr WHERE child = 1;"
 -
 -    /* Delete all pointers to any pages that have more than one pointer
 -    ** to them. Such pages will be treated as root pages when recovering
 -    ** data.  */
 -    "DELETE FROM recovery.dbptr WHERE child IN ("
 -    "  SELECT child FROM recovery.dbptr GROUP BY child HAVING count(*)>1"
 -    ");"
 +#ifndef OBJECTIFY_COMMANDS
 +# define OBJECTIFY_COMMANDS 1
 +#endif
 +
 +/* Meta-command implementation functions are defined in this section.
 +COMMENT  Define meta-commands and provide for their dispatch and .help text.
 +COMMENT  These should be kept in command name order for coding convenience
 +COMMENT  except where meta-commands share implementation. (The ordering
 +COMMENT  required for dispatch and help text is effected regardless.) The
 +COMMENT  effect of this configuration can be seen in generated output or by
 +COMMENT  executing tool/mkshellc.tcl --parameters (or --details or --help).
 +COMMENT  Generally, this section defines dispatchable functions inline and
 +COMMENT  causes collection of 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);
 +*/
 +DISPATCH_CONFIG[
 +  RETURN_TYPE=int
 +  STORAGE_CLASS=static
 +  ARGS_SIGNATURE=char *$arg4\[\], int $arg5, ShellState *$arg6, char **$arg7
 +  DISPATCH_ENTRY={ "$cmd", ${cmd}Command, $arg1, $arg2, $arg3 },
 +  CMD_CAPTURE_RE=^\s*{\s*"(\w+)"
 +  DISPATCHEE_NAME=${cmd}Command
 +  DC_ARG1_DEFAULT=[string length $cmd]
 +  DC_ARG2_DEFAULT=0
 +  DC_ARG3_DEFAULT=0
 +  DC_ARG4_DEFAULT=azArg
 +  DC_ARG5_DEFAULT=nArg
 +  DC_ARG6_DEFAULT=p
 +  DC_ARG7_DEFAULT=pzErr
 +  DC_ARG_COUNT=8
 +];
 +
 +CONDITION_COMMAND(seeargs defined(SQLITE_GIMME_SEEARGS));
 +/*****************
 + * The .seeargs command
 + */
 +COLLECT_HELP_TEXT[
 +  ".seeargs                 Echo arguments separated by |",
 +  "    A near-dummy command for use as a template (to vanish soon)",
 +];
 +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" : "|");
 +  return rc;
 +}
  
 -    /* Create the "map" table that will (eventually) contain instructions
 -    ** for dealing with each page in the db that contains one or more 
 -    ** records. */
 -    "CREATE TABLE recovery.map("
 -      "pgno INTEGER PRIMARY KEY, maxlen INT, intkey, root INT"
 -    ");"
 +CONDITION_COMMAND(archive !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB));
 +/*****************
 + * The .archive command
 + */
 +COLLECT_HELP_TEXT[
 +  ".archive ...             Manage SQL archives",
 +  "   Each command must have exactly one of the following options:",
 +  "     -c, --create               Create a new archive",
 +  "     -u, --update               Add or update files with changed mtime",
 +  "     -i, --insert               Like -u but always add even if unchanged",
 +  "     -r, --remove               Remove files from archive",
 +  "     -t, --list                 List contents of archive",
 +  "     -x, --extract              Extract files from archive",
 +  "   Optional arguments:",
 +  "     -v, --verbose              Print each filename as it is processed",
 +  "     -f FILE, --file FILE       Use archive FILE (default is current db)",
 +  "     -a FILE, --append FILE     Open FILE using the apndvfs VFS",
 +  "     -C DIR, --directory DIR    Read/extract files from directory DIR",
 +  "     -g, --glob                 Use glob matching for names in archive",
 +  "     -n, --dryrun               Show the SQL that would have occurred",
 +  "   Examples:",
 +  "     .ar -cf ARCHIVE foo bar  # Create ARCHIVE from files foo and bar",
 +  "     .ar -tf ARCHIVE          # List members of ARCHIVE",
 +  "     .ar -xvf ARCHIVE         # Verbosely extract files from ARCHIVE",
 +  "   See also:",
 +  "      http://sqlite.org/cli.html#sqlite_archive_support",
 +];
 +DISPATCHABLE_COMMAND( archive ? 3 0 azArg nArg p ){
 +  open_db(p, 0);
 +  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
 +  return arDotCommand(p, 0, azArg, nArg);
 +}
  
 -    /* Populate table [map]. If there are circular loops of pages in the
 -    ** database, the following adds all pages in such a loop to the map
 -    ** as individual root pages. This could be handled better.  */
 -    "WITH pages(i, maxlen) AS ("
 -    "  SELECT page_count, ("
 -    "    SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=page_count"
 -    "  ) FROM pragma_page_count WHERE page_count>0"
 -    "    UNION ALL"
 -    "  SELECT i-1, ("
 -    "    SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=i-1"
 -    "  ) FROM pages WHERE i>=2"
 -    ")"
 -    "INSERT INTO recovery.map(pgno, maxlen, intkey, root) "
 -    "  SELECT i, maxlen, NULL, ("
 -    "    WITH p(orig, pgno, parent) AS ("
 -    "      SELECT 0, i, (SELECT pgno FROM recovery.dbptr WHERE child=i)"
 -    "        UNION "
 -    "      SELECT i, p.parent, "
 -    "        (SELECT pgno FROM recovery.dbptr WHERE child=p.parent) FROM p"
 -    "    )"
 -    "    SELECT pgno FROM p WHERE (parent IS NULL OR pgno = orig)"
 -    ") "
 -    "FROM pages WHERE maxlen IS NOT NULL AND i NOT IN freelist;"
 -    "UPDATE recovery.map AS o SET intkey = ("
 -    "  SELECT substr(data, 1, 1)==X'0D' FROM sqlite_dbpage WHERE pgno=o.pgno"
 -    ");"
 +/*****************
 + * The .auth command
 + */
 +CONDITION_COMMAND(auth !defined(SQLITE_OMIT_AUTHORIZATION));
 +COLLECT_HELP_TEXT[
 +  ".auth ON|OFF             Show authorizer callbacks",
 +];
 +DISPATCHABLE_COMMAND( auth 3 2 2 azArg nArg p ){
 +  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);
 +  }else{
 +    sqlite3_set_authorizer(p->db, 0, 0);
 +  }
 +  return rc;
 +}
  
 -    /* Extract data from page 1 and any linked pages into table
 -    ** recovery.schema. With the same schema as an sqlite_schema table.  */
 -    "CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);"
 -    "INSERT INTO recovery.schema SELECT "
 -    "  max(CASE WHEN field=0 THEN value ELSE NULL END),"
 -    "  max(CASE WHEN field=1 THEN value ELSE NULL END),"
 -    "  max(CASE WHEN field=2 THEN value ELSE NULL END),"
 -    "  max(CASE WHEN field=3 THEN value ELSE NULL END),"
 -    "  max(CASE WHEN field=4 THEN value ELSE NULL END)"
 -    "FROM sqlite_dbdata WHERE pgno IN ("
 -    "  SELECT pgno FROM recovery.map WHERE root=1"
 -    ")"
 -    "GROUP BY pgno, cell;"
 -    "CREATE INDEX recovery.schema_rootpage ON schema(rootpage);"
 -  );
 +/*****************
 + * The .backup and .save commands (aliases for each other)
 + * These defer to writeDb in the dispatch table, so are not here.
 + */
 +COLLECT_HELP_TEXT[
 +  ".backup ?DB? FILE        Backup DB (default \"main\") to FILE",
 +  "   Options:",
 +  "     --append               Use the appendvfs",
 +  "     --async                Write the FILE without journal and fsync()",
 +  ".save ?DB? FILE          Write DB (default \"main\") to FILE",
 +  "   Options:",
 +  "     --append               Use the appendvfs",
 +  "     --async                Write the FILE without journal and fsync()",
 +];
 +COLLECT_DISPATCH( * )[
 +  { "backup", writeDb, 4, 2, 5 },
 +  { "save", writeDb, 3, 2, 5 },
 +];
 +
 +/*****************
 + * The .bail command
 + */
 +COLLECT_HELP_TEXT[
 +  ".bail on|off             Stop after hitting an error.  Default OFF",
 +];
 +DISPATCHABLE_COMMAND( bail 3 2 2 ){
 +  bail_on_error = booleanValue(azArg[1]);
 +  return 0;
 +}
  
 -  /* Open a transaction, then print out all non-virtual, non-"sqlite_%" 
 -  ** CREATE TABLE statements that extracted from the existing schema.  */
 -  if( rc==SQLITE_OK ){
 -    sqlite3_stmt *pStmt = 0;
 -    /* ".recover" might output content in an order which causes immediate
 -    ** foreign key constraints to be violated. So disable foreign-key
 -    ** constraint enforcement to prevent problems when running the output
 -    ** script. */
 -    raw_printf(pState->out, "PRAGMA foreign_keys=OFF;\n");
 -    raw_printf(pState->out, "BEGIN;\n");
 -    raw_printf(pState->out, "PRAGMA writable_schema = on;\n");
 -    shellPrepare(pState->db, &rc,
 -        "SELECT sql FROM recovery.schema "
 -        "WHERE type='table' AND sql LIKE 'create table%'", &pStmt
 -    );
 -    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
 -      const char *zCreateTable = (const char*)sqlite3_column_text(pStmt, 0);
 -      raw_printf(pState->out, "CREATE TABLE IF NOT EXISTS %s;\n", 
 -          &zCreateTable[12]
 -      );
 -    }
 -    shellFinalize(&rc, pStmt);
 +/*****************
 + * The .binary and .cd commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".binary on|off           Turn binary output on or off.  Default OFF",
 +  ".cd DIRECTORY            Change the working directory to DIRECTORY",
 +];
 +DISPATCHABLE_COMMAND( binary 3 2 2 ){
 +  if( booleanValue(azArg[1]) ){
 +    setBinaryMode(p->out, 1);
 +  }else{
 +    setTextMode(p->out, 1);
    }
 +  return 0;
 +}
  
 -  /* Figure out if an orphan table will be required. And if so, how many
 -  ** user columns it should contain */
 -  shellPrepare(pState->db, &rc, 
 -      "SELECT coalesce(max(maxlen), -2) FROM recovery.map WHERE root>1"
 -      , &pLoop
 -  );
 -  if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
 -    nOrphan = sqlite3_column_int(pLoop, 0);
 +DISPATCHABLE_COMMAND( cd ? 2 2 ){
 +  int rc=0;
 +  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
 +#if defined(_WIN32) || defined(WIN32)
 +  wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
 +  rc = !SetCurrentDirectoryW(z);
 +  sqlite3_free(z);
 +#else
 +  rc = chdir(azArg[1]);
 +#endif
 +  if( rc ){
 +    utf8_printf(STD_ERR, "Cannot change to directory \"%s\"\n", azArg[1]);
 +    rc = 1;
    }
 -  shellFinalize(&rc, pLoop);
 -  pLoop = 0;
 -
 -  shellPrepare(pState->db, &rc,
 -      "SELECT pgno FROM recovery.map WHERE root=?", &pPages
 -  );
 -
 -  shellPrepare(pState->db, &rc,
 -      "SELECT max(field), group_concat(shell_escape_crnl(quote"
 -      "(case when (? AND field<0) then NULL else value end)"
 -      "), ', ')"
 -      ", min(field) "
 -      "FROM sqlite_dbdata WHERE pgno = ? AND field != ?"
 -      "GROUP BY cell", &pCells
 -  );
 -
 -  /* Loop through each root page. */
 -  shellPrepare(pState->db, &rc, 
 -      "SELECT root, intkey, max(maxlen) FROM recovery.map" 
 -      " WHERE root>1 GROUP BY root, intkey ORDER BY root=("
 -      "  SELECT rootpage FROM recovery.schema WHERE name='sqlite_sequence'"
 -      ")", &pLoop
 -  );
 -  while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
 -    int iRoot = sqlite3_column_int(pLoop, 0);
 -    int bIntkey = sqlite3_column_int(pLoop, 1);
 -    int nCol = sqlite3_column_int(pLoop, 2);
 -    int bNoop = 0;
 -    RecoverTable *pTab;
 +  return rc;
 +}
  
 -    assert( bIntkey==0 || bIntkey==1 );
 -    pTab = recoverFindTable(pState, &rc, iRoot, bIntkey, nCol, &bNoop);
 -    if( bNoop || rc ) continue;
 -    if( pTab==0 ){
 -      if( pOrphan==0 ){
 -        pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan);
 -      }
 -      pTab = pOrphan;
 -      if( pTab==0 ) break;
 +/* The undocumented ".breakpoint" command causes a call
 +** to the no-op routine named test_breakpoint().
 +*/
 +DISPATCHABLE_COMMAND( breakpoint 3 1 1 ){
 +  test_breakpoint();
 +  return 0;
 +}
 +
 +/*****************
 + * The .changes, .check, .clone and .connection commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".changes on|off          Show number of rows changed by SQL",
 +  ".check GLOB              Fail if output since .testcase does not match",
 +  ".clone NEWDB             Clone data into NEWDB from the existing database",
 +  ".connection [close] [#]  Open or close an auxiliary database connection",
 +];
 +DISPATCHABLE_COMMAND( changes 3 2 2 ){
 +  setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( check 3 0 0 ){
 +  /* Cancel output redirection, if it is currently set (by .testcase)
 +  ** Then read the content of the testcase-out.txt file and compare against
 +  ** azArg[1].  If there are differences, report an error and exit.
 +  */
 +  char *zRes = 0;
 +  int rc=0;
 +  output_reset(p);
 +  if( nArg!=2 ){
 +    return SHELL_INVALID_ARGS;
 +  }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){
 +    *pzErr = shellMPrintf(&rc, "Error: cannot read 'testcase-out.txt'");
 +    rc = 2;
 +  }else if( testcase_glob(azArg[1],zRes)==0 ){
 +    *pzErr =
 +      shellMPrintf(&rc,
 +                   "testcase-%s FAILED\n Expected: [%s]\n      Got: [%s]\n",
 +                   p->zTestcase, azArg[1], zRes);
 +    rc = 1;
 +  }else{
 +    utf8_printf(STD_OUT, "testcase-%s ok\n", p->zTestcase);
 +    p->nCheck++;
 +  }
 +  sqlite3_free(zRes);
 +  return rc;
 +}
 +DISPATCHABLE_COMMAND( clone ? 2 2 ){
 +  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
 +  tryToClone(p, azArg[1]);
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( connection ? 1 4 ){
 +  if( nArg==1 ){
 +    /* List available connections */
 +    int i;
 +    for(i=0; i<ArraySize(p->aAuxDb); i++){
 +      const char *zFile = p->aAuxDb[i].zDbFilename;
 +      if( p->aAuxDb[i].db==0 && p->pAuxDb!=&p->aAuxDb[i] ){
 +      zFile = "(not open)";
 +      }else if( zFile==0 ){
 +      zFile = "(memory)";
 +      }else if( zFile[0]==0 ){
 +      zFile = "(temporary-file)";
 +      }
 +      if( p->pAuxDb == &p->aAuxDb[i] ){
 +      utf8_printf(STD_OUT, "ACTIVE %d: %s\n", i, zFile);
 +      }else if( p->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;
 +    }
 +  }else if( nArg==3 && strcmp(azArg[1], "close")==0
 +          && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){
 +    int i = azArg[2][0] - '0';
 +    if( i<0 || i>=ArraySize(p->aAuxDb) ){
 +      /* No-op */
 +    }else if( p->pAuxDb == &p->aAuxDb[i] ){
 +      raw_printf(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{
 +    return SHELL_INVALID_ARGS;
 +  }
 +  return 0;
 +}
  
 -    if( 0==sqlite3_stricmp(pTab->zQuoted, "\"sqlite_sequence\"") ){
 -      raw_printf(pState->out, "DELETE FROM sqlite_sequence;\n");
 +/*****************
 + * The .databases, .dbconfig and .dbinfo commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".databases               List names and files of attached databases",
 +  ".dbconfig ?op? ?val?     List or change sqlite3_db_config() options",
 +  ".dbinfo ?DB?             Show status information about the database",
 +];
 +/* Allow garbage arguments on this, to be ignored. */
 +DISPATCHABLE_COMMAND( databases 2 1 0 ){
 +  int rc;
 +  char **azName = 0;
 +  int nName = 0;
 +  sqlite3_stmt *pStmt;
 +  int i;
 +  open_db(p, 0);
 +  rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
 +  if( rc ){
 +    *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
 +    rc = 1;
 +  }else{
 +    while( sqlite3_step(pStmt)==SQLITE_ROW ){
 +      const char *zSchema = (const char *)sqlite3_column_text(pStmt,1);
 +      const char *zFile = (const char*)sqlite3_column_text(pStmt,2);
 +      azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*));
 +      if( azName==0 ){ shell_out_of_memory();  /* Does not return */ }
 +      azName[nName*2] = strdup(zSchema);
 +      azName[nName*2+1] = strdup(zFile);
 +      nName++;
      }
 -    sqlite3_bind_int(pPages, 1, iRoot);
 -    if( bRowids==0 && pTab->iPk<0 ){
 -      sqlite3_bind_int(pCells, 1, 1);
 -    }else{
 -      sqlite3_bind_int(pCells, 1, 0);
 +  }
 +  sqlite3_finalize(pStmt);
 +  for(i=0; i<nName; i++){
 +    int eTxn = sqlite3_txn_state(p->db, azName[i*2]);
 +    int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]);
 +    const char *z = azName[i*2+1];
 +    utf8_printf(p->out, "%s: %s %s%s\n",
 +                azName[i*2],
 +                z && z[0] ? z : "\"\"",
 +                bRdonly ? "r/o" : "r/w",
 +                eTxn==SQLITE_TXN_NONE ? "" :
 +                eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
 +    free(azName[i*2]);
 +    free(azName[i*2+1]);
 +  }
 +  sqlite3_free(azName);
 +  return rc;
 +}
 +DISPATCHABLE_COMMAND( dbconfig 3 1 3 ){
 +  static const struct DbConfigChoices {
 +    const char *zName;
 +    int op;
 +  } aDbConfig[] = {
 +    { "defensive",          SQLITE_DBCONFIG_DEFENSIVE             },
 +    { "dqs_ddl",            SQLITE_DBCONFIG_DQS_DDL               },
 +    { "dqs_dml",            SQLITE_DBCONFIG_DQS_DML               },
 +    { "enable_fkey",        SQLITE_DBCONFIG_ENABLE_FKEY           },
 +    { "enable_qpsg",        SQLITE_DBCONFIG_ENABLE_QPSG           },
 +    { "enable_trigger",     SQLITE_DBCONFIG_ENABLE_TRIGGER        },
 +    { "enable_view",        SQLITE_DBCONFIG_ENABLE_VIEW           },
 +    { "fts3_tokenizer",     SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER },
 +    { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE    },
 +    { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT    },
 +    { "load_extension",     SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION },
 +    { "no_ckpt_on_close",   SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE      },
 +    { "reset_database",     SQLITE_DBCONFIG_RESET_DATABASE        },
 +    { "trigger_eqp",        SQLITE_DBCONFIG_TRIGGER_EQP           },
 +    { "trusted_schema",     SQLITE_DBCONFIG_TRUSTED_SCHEMA        },
 +    { "writable_schema",    SQLITE_DBCONFIG_WRITABLE_SCHEMA       },
 +  };
 +  int ii, v;
 +  open_db(p, 0);
 +  for(ii=0; ii<ArraySize(aDbConfig); ii++){
 +    if( nArg>1 && strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue;
 +    if( nArg>=3 ){
 +      sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0);
      }
 -    sqlite3_bind_int(pCells, 3, pTab->iPk);
 -
 -    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPages) ){
 -      int iPgno = sqlite3_column_int(pPages, 0);
 -      sqlite3_bind_int(pCells, 2, iPgno);
 -      while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pCells) ){
 -        int nField = sqlite3_column_int(pCells, 0);
 -        int iMin = sqlite3_column_int(pCells, 2);
 -        const char *zVal = (const char*)sqlite3_column_text(pCells, 1);
 -
 -        RecoverTable *pTab2 = pTab;
 -        if( pTab!=pOrphan && (iMin<0)!=bIntkey ){
 -          if( pOrphan==0 ){
 -            pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan);
 -          }
 -          pTab2 = pOrphan;
 -          if( pTab2==0 ) break;
 -        }
 +    sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v);
 +    utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off");
 +    if( nArg>1 ) break;
 +  }
 +  if( nArg>1 && ii==ArraySize(aDbConfig) ){
 +    *pzErr = sqlite3_mprintf
 +      ("Error: unknown dbconfig \"%s\"\n"
 +       "Enter \".dbconfig\" with no arguments for a list\n",
 +       azArg[1]);
 +    return 1;
 +  }   
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( dbinfo 3 1 2 ){
 +  return shell_dbinfo_command(p, nArg, azArg);
 +}
  
 -        nField = nField+1;
 -        if( pTab2==pOrphan ){
 -          raw_printf(pState->out, 
 -              "INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n",
 -              pTab2->zQuoted, iRoot, iPgno, nField,
 -              iMin<0 ? "" : "NULL, ", zVal, pTab2->azlCol[nField]
 -          );
 +/*****************
 + * The .dump, .echo and .eqp commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".dump ?OBJECTS?          Render database content as SQL",
 +  "   Options:",
 +  "     --data-only            Output only INSERT statements",
 +  "     --newlines             Allow unescaped newline characters in output",
 +  "     --nosys                Omit system tables (ex: \"sqlite_stat1\")",
 +  "     --preserve-rowids      Include ROWID values in the output",
 +  "   OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump",
 +  "   Additional LIKE patterns can be given in subsequent arguments",
 +  ".echo on|off             Turn command echo on or off",
 +  ".eqp on|off|full|...     Enable or disable automatic EXPLAIN QUERY PLAN",
 +  "   Other Modes:",
 +#ifdef SQLITE_DEBUG
 +  "      test                  Show raw EXPLAIN QUERY PLAN output",
 +  "      trace                 Like \"full\" but enable \"PRAGMA vdbe_trace\"",
 +#endif
 +  "      trigger               Like \"full\" but also show trigger bytecode",
 +];
 +DISPATCHABLE_COMMAND( dump ? 1 2 ){
 +  char *zLike = 0;
 +  char *zSql;
 +  int i;
 +  int savedShowHeader = p->showHeader;
 +  int savedShellFlags = p->shellFlgs;
 +  ShellClearFlag(p, 
 +     SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo
 +     |SHFLG_DumpDataOnly|SHFLG_DumpNoSys);
 +  for(i=1; i<nArg; i++){
 +    if( azArg[i][0]=='-' ){
 +      const char *z = azArg[i]+1;
 +      if( z[0]=='-' ) z++;
 +      if( strcmp(z,"preserve-rowids")==0 ){
 +#ifdef SQLITE_OMIT_VIRTUALTABLE
 +        *pzErr = sqlite3_mprintf
 +          ("The --preserve-rowids option is not compatible"
 +                   " with SQLITE_OMIT_VIRTUALTABLE\n");
 +        sqlite3_free(zLike);
 +        return 1;
 +#else
 +        ShellSetFlag(p, SHFLG_PreserveRowid);
 +#endif
 +      }else{
 +        if( strcmp(z,"newlines")==0 ){
 +          ShellSetFlag(p, SHFLG_Newlines);
 +        }else if( strcmp(z,"data-only")==0 ){
 +          ShellSetFlag(p, SHFLG_DumpDataOnly);
 +        }else if( strcmp(z,"nosys")==0 ){
 +          ShellSetFlag(p, SHFLG_DumpNoSys);
          }else{
 -          raw_printf(pState->out, "INSERT INTO %s(%s) VALUES( %s );\n", 
 -              pTab2->zQuoted, pTab2->azlCol[nField], zVal
 -          );
 +          *pzErr = sqlite3_mprintf
 +            ("Unknown option \"%s\" on \".dump\"\n", azArg[i]);
 +          sqlite3_free(zLike);
 +          return 1;
          }
        }
 -      shellReset(&rc, pCells);
 -    }
 -    shellReset(&rc, pPages);
 -    if( pTab!=pOrphan ) recoverFreeTable(pTab);
 -  }
 -  shellFinalize(&rc, pLoop);
 -  shellFinalize(&rc, pPages);
 -  shellFinalize(&rc, pCells);
 -  recoverFreeTable(pOrphan);
 -
 -  /* The rest of the schema */
 -  if( rc==SQLITE_OK ){
 -    sqlite3_stmt *pStmt = 0;
 -    shellPrepare(pState->db, &rc, 
 -        "SELECT sql, name FROM recovery.schema "
 -        "WHERE sql NOT LIKE 'create table%'", &pStmt
 -    );
 -    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
 -      const char *zSql = (const char*)sqlite3_column_text(pStmt, 0);
 -      if( sqlite3_strnicmp(zSql, "create virt", 11)==0 ){
 -        const char *zName = (const char*)sqlite3_column_text(pStmt, 1);
 -        char *zPrint = shellMPrintf(&rc, 
 -          "INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)",
 -          zName, zName, zSql
 -        );
 -        raw_printf(pState->out, "%s;\n", zPrint);
 -        sqlite3_free(zPrint);
 +    }else{
 +      /* azArg[i] contains a LIKE pattern. This ".dump" request should
 +      ** only dump data for tables for which either the table name matches
 +      ** the LIKE pattern, or the table appears to be a shadow table of
 +      ** a virtual table for which the name matches the LIKE pattern.
 +      */
 +      char *zExpr = sqlite3_mprintf(
 +                    "name LIKE %Q ESCAPE '\\' OR EXISTS ("
 +                    "  SELECT 1 FROM sqlite_schema WHERE "
 +                    "    name LIKE %Q ESCAPE '\\' AND"
 +                    "    sql LIKE 'CREATE VIRTUAL TABLE%%' AND"
 +                    "    substr(o.name, 1, length(name)+1) == (name||'_')"
 +                    ")", azArg[i], azArg[i]
 +                    );
 +      
 +      if( zLike ){
 +        zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr);
        }else{
 -        raw_printf(pState->out, "%s;\n", zSql);
 +        zLike = zExpr;
        }
      }
 -    shellFinalize(&rc, pStmt);
    }
  
 -  if( rc==SQLITE_OK ){
 -    raw_printf(pState->out, "PRAGMA writable_schema = off;\n");
 -    raw_printf(pState->out, "COMMIT;\n");
 -  }
 -  sqlite3_exec(pState->db, "DETACH recovery", 0, 0, 0);
 -  return rc;
 -}
 -#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
 -
 -/*
 -** If an input line begins with "." then invoke this routine to
 -** process that line.
 -**
 -** Return 1 on error, 2 to exit, and 0 otherwise.
 -*/
 -static int do_meta_command(char *zLine, ShellState *p){
 -  int h = 1;
 -  int nArg = 0;
 -  int n, c;
 -  int rc = 0;
 -  char *azArg[52];
 +  open_db(p, 0);
  
 -#ifndef SQLITE_OMIT_VIRTUALTABLE
 -  if( p->expert.pExpert ){
 -    expertFinish(p, 1, 0);
 +  if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 +    /* When playing back a "dump", the content might appear in an order
 +    ** which causes immediate foreign key constraints to be violated.
 +    ** So disable foreign-key constraint enforcement to prevent problems. */
 +    raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n");
 +    raw_printf(p->out, "BEGIN TRANSACTION;\n");
 +  }
 +  p->writableSchema = 0;
 +  p->showHeader = 0;
 +  /* Set writable_schema=ON since doing so forces SQLite to initialize
 +  ** as much of the schema as it can even if the sqlite_schema table is
 +  ** corrupt. */
 +  sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
 +  p->nErr = 0;
 +  if( zLike==0 ) zLike = sqlite3_mprintf("true");
 +  zSql = sqlite3_mprintf(
 +           "SELECT name, type, sql FROM sqlite_schema AS o "
 +           "WHERE (%s) AND type=='table'"
 +           "  AND sql NOT NULL"
 +           " ORDER BY tbl_name='sqlite_sequence', rowid",
 +           zLike
 +         );
 +  run_schema_dump_query(p,zSql);
 +  sqlite3_free(zSql);
 +  if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 +    zSql = sqlite3_mprintf(
 +             "SELECT sql FROM sqlite_schema AS o "
 +             "WHERE (%s) AND sql NOT NULL"
 +             "  AND type IN ('index','trigger','view')",
 +             zLike
 +           );
 +    run_table_dump_query(p, zSql);
 +    sqlite3_free(zSql);
    }
 -#endif
 +  sqlite3_free(zLike);
 +  if( p->writableSchema ){
 +    raw_printf(p->out, "PRAGMA writable_schema=OFF;\n");
 +    p->writableSchema = 0;
 +  }
 +  sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0);
 +  sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0);
 +  if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 +    raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n");
 +  }
 +  p->showHeader = savedShowHeader;
 +  p->shellFlgs = savedShellFlags;
  
 -  /* Parse the input line into tokens.
 -  */
 -  while( zLine[h] && nArg<ArraySize(azArg)-1 ){
 -    while( IsSpace(zLine[h]) ){ h++; }
 -    if( zLine[h]==0 ) break;
 -    if( zLine[h]=='\'' || zLine[h]=='"' ){
 -      int delim = zLine[h++];
 -      azArg[nArg++] = &zLine[h];
 -      while( zLine[h] && zLine[h]!=delim ){
 -        if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++;
 -        h++;
 -      }
 -      if( zLine[h]==delim ){
 -        zLine[h++] = 0;
 -      }
 -      if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( echo ? 2 2 ){
 +  setOrClearFlag(p, SHFLG_Echo, azArg[1]);
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( eqp ? 0 0 ){
 +  if( nArg==2 ){
 +    p->autoEQPtest = 0;
 +    if( p->autoEQPtrace ){
 +      if( p->db ) sqlite3_exec(p->db, "PRAGMA vdbe_trace=OFF;", 0, 0, 0);
 +      p->autoEQPtrace = 0;
 +    }
 +    if( strcmp(azArg[1],"full")==0 ){
 +      p->autoEQP = AUTOEQP_full;
 +    }else if( strcmp(azArg[1],"trigger")==0 ){
 +      p->autoEQP = AUTOEQP_trigger;
 +#ifdef SQLITE_DEBUG
 +    }else if( strcmp(azArg[1],"test")==0 ){
 +      p->autoEQP = AUTOEQP_on;
 +      p->autoEQPtest = 1;
 +    }else if( strcmp(azArg[1],"trace")==0 ){
 +      p->autoEQP = AUTOEQP_full;
 +      p->autoEQPtrace = 1;
 +      open_db(p, 0);
 +      sqlite3_exec(p->db, "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0);
 +      sqlite3_exec(p->db, "PRAGMA vdbe_trace=ON;", 0, 0, 0);
 +#endif
      }else{
 -      azArg[nArg++] = &zLine[h];
 -      while( zLine[h] && !IsSpace(zLine[h]) ){ h++; }
 -      if( zLine[h] ) zLine[h++] = 0;
 -      resolve_backslashes(azArg[nArg-1]);
 +      p->autoEQP = (u8)booleanValue(azArg[1]);
      }
 +  }else{
 +    return SHELL_INVALID_ARGS;
    }
 -  azArg[nArg] = 0;
 -
 -  /* Process the input line.
 -  */
 -  if( nArg==0 ) return 0; /* no tokens, no error */
 -  n = strlen30(azArg[0]);
 -  c = azArg[0][0];
 -  clearTempFile(p);
 +}
  
 -#ifndef SQLITE_OMIT_AUTHORIZATION
 -  if( c=='a' && strncmp(azArg[0], "auth", n)==0 ){
 -    if( nArg!=2 ){
 -      raw_printf(stderr, "Usage: .auth ON|OFF\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    open_db(p, 0);
 -    if( booleanValue(azArg[1]) ){
 -      sqlite3_set_authorizer(p->db, shellAuth, p);
 -    }else if( p->bSafeModePersist ){
 -      sqlite3_set_authorizer(p->db, safeModeAuth, p);
 +/*****************
 + * The .expert and .explain commands
 + */
 +CONDITION_COMMAND( expert !defined(SQLITE_OMIT_VIRTUALTABLE) );
 +COLLECT_HELP_TEXT[
 +  ".expert                  Suggest indexes for queries",
 +  ".explain ?on|off|auto?   Change the EXPLAIN formatting mode.  Default: auto",
 +];
 +DISPATCHABLE_COMMAND( expert ? 1 1 ){
 +  open_db(p, 0);
 +  expertDotCommand(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 */
 +  int val = 1;
 +  if( nArg>1 ){
 +    if( strcmp(azArg[1],"auto")==0 ){
 +      val = 99;
      }else{
 -      sqlite3_set_authorizer(p->db, 0, 0);
 +      val = booleanValue(azArg[1]);
      }
 -  }else
 -#endif
 +  }
 +  if( val==1 && p->mode!=MODE_Explain ){
 +    p->normalMode = p->mode;
 +    p->mode = MODE_Explain;
 +    p->autoExplain = 0;
 +  }else if( val==0 ){
 +    if( p->mode==MODE_Explain ) p->mode = p->normalMode;
 +    p->autoExplain = 0;
 +  }else if( val==99 ){
 +    if( p->mode==MODE_Explain ) p->mode = p->normalMode;
 +    p->autoExplain = 1;
 +  }
 +  return 0;
 +}
  
 -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
 -  if( c=='a' && strncmp(azArg[0], "archive", n)==0 ){
 -    open_db(p, 0);
 -    failIfSafeMode(p, "cannot run .archive in safe mode");
 -    rc = arDotCommand(p, 0, azArg, nArg);
 -  }else
 -#endif
 +/*****************
 + * The .excel, .once and .output commands
 + * These share much implementation, so they stick together.
 + */
 +COLLECT_HELP_TEXT[
 +  ".excel                   Display the output of next command in spreadsheet",
 +  "   --bom                   Prefix the file with a UTF8 byte-order mark",
 +  ".once ?OPTIONS? ?FILE?   Output for the next SQL command only to FILE",
 +  "   If FILE begins with '|' then open it as a command to be piped into.",
 +  "   Options:",
 +  "     --bom                 Prefix output with a UTF8 byte-order mark",
 +  "     -e                    Send output to the system text editor",
 +  "     -x       Send output as CSV to a spreadsheet (same as \".excel\")",
 +  ".output ?FILE?           Send output to FILE or stdout if FILE is omitted",
 +  "   If FILE begins with '|' then open it as a command to be piped into.",
 +  "   Options:",
 +  "     --bom                 Prefix output with a UTF8 byte-order mark",
 +  "     -e                    Send output to the system text editor",
 +  "     -x       Send output as CSV to a spreadsheet (same as \".excel\")",
 +];
 +static int outputRedirs(char *[], int, ShellState *,
 +                        char **pzErr, int bOnce, int eMode);
 +DISPATCHABLE_COMMAND( excel ? 1 2 ){
 +  return outputRedirs(azArg, nArg, p, pzErr, 2, 'x');
 +}
 +DISPATCHABLE_COMMAND( once ? 1 6 ){
 +  return outputRedirs(azArg, nArg, p, pzErr, 1, 0);
 +}
 +DISPATCHABLE_COMMAND( output ? 1 6 ){
 +  return outputRedirs(azArg, nArg, p, pzErr, 0, 0);
 +}
  
 -  if( (c=='b' && n>=3 && strncmp(azArg[0], "backup", n)==0)
 -   || (c=='s' && n>=3 && strncmp(azArg[0], "save", n)==0)
 -  ){
 -    const char *zDestFile = 0;
 -    const char *zDb = 0;
 -    sqlite3 *pDest;
 -    sqlite3_backup *pBackup;
 -    int j;
 -    int bAsync = 0;
 -    const char *zVfs = 0;
 -    failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
 -    for(j=1; j<nArg; j++){
 -      const char *z = azArg[j];
 -      if( z[0]=='-' ){
 -        if( z[1]=='-' ) z++;
 -        if( strcmp(z, "-append")==0 ){
 -          zVfs = "apndvfs";
 -        }else
 -        if( strcmp(z, "-async")==0 ){
 -          bAsync = 1;
 -        }else
 -        {
 -          utf8_printf(stderr, "unknown option: %s\n", azArg[j]);
 -          return 1;
 -        }
 -      }else if( zDestFile==0 ){
 -        zDestFile = azArg[j];
 -      }else if( zDb==0 ){
 -        zDb = zDestFile;
 -        zDestFile = azArg[j];
 +static int outputRedirs(char *azArg[], int nArg, ShellState *p,
 +                        char **pzErr, int bOnce, int eMode){
 +  /* bOnce => 0: .output, 1: .once, 2: .excel */
 +  /* eMode => 'x' for excel, else 0 */
 +  int rc = 0;
 +  char *zFile = 0;
 +  int bTxtMode = 0;
 +  int i;
 +  int bBOM = 0;
 +  if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
 +  for(i=1; i<nArg; i++){
 +    char *z = azArg[i];
 +    if( z[0]=='-' ){
 +      if( z[1]=='-' ) z++;
 +      if( strcmp(z,"-bom")==0 ){
 +        bBOM = 1;
 +      }else if( bOnce!=2 && strcmp(z,"-x")==0 ){
 +        eMode = 'x';  /* spreadsheet */
 +      }else if( bOnce!=2 && strcmp(z,"-e")==0 ){
 +        eMode = 'e';  /* text editor */
        }else{
 -        raw_printf(stderr, "Usage: .backup ?DB? ?OPTIONS? FILENAME\n");
 -        return 1;
 +        *pzErr = shellMPrintf(0,"       unknown option: \"%s\"\n",azArg[i]);
 +        return SHELL_INVALID_ARGS;
 +      }
 +    }else if( zFile==0 && eMode!='e' && eMode!='x' ){
 +      zFile = sqlite3_mprintf("%s", z);
 +      if( zFile[0]=='|' ){
 +        while( i+1<nArg ) zFile = sqlite3_mprintf("%z %s", zFile, azArg[++i]);
 +        break;
        }
 -    }
 -    if( zDestFile==0 ){
 -      raw_printf(stderr, "missing FILENAME argument on .backup\n");
 -      return 1;
 -    }
 -    if( zDb==0 ) zDb = "main";
 -    rc = sqlite3_open_v2(zDestFile, &pDest, 
 -                  SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs);
 -    if( rc!=SQLITE_OK ){
 -      utf8_printf(stderr, "Error: cannot open \"%s\"\n", zDestFile);
 -      close_db(pDest);
 -      return 1;
 -    }
 -    if( bAsync ){
 -      sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
 -                   0, 0, 0);
 -    }
 -    open_db(p, 0);
 -    pBackup = sqlite3_backup_init(pDest, "main", p->db, zDb);
 -    if( pBackup==0 ){
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
 -      close_db(pDest);
 -      return 1;
 -    }
 -    while(  (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
 -    sqlite3_backup_finish(pBackup);
 -    if( rc==SQLITE_DONE ){
 -      rc = 0;
      }else{
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
 -      rc = 1;
 +      *pzErr = shellMPrintf(0,"       excess argument: \"%s\"\n", azArg[i]);
 +      sqlite3_free(zFile);
 +      return SHELL_INVALID_ARGS;
      }
 -    close_db(pDest);
 -  }else
 -
 -  if( c=='b' && n>=3 && strncmp(azArg[0], "bail", n)==0 ){
 -    if( nArg==2 ){
 -      bail_on_error = booleanValue(azArg[1]);
 +  }
 +  if( zFile==0 ) zFile = sqlite3_mprintf("stdout");
 +  if( bOnce ){
 +    p->outCount = 2;
 +  }else{
 +    p->outCount = 0;
 +  }
 +  output_reset(p);
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +  if( eMode=='e' || eMode=='x' ){
 +    p->doXdgOpen = 1;
 +    outputModePush(p);
 +    if( eMode=='x' ){
 +      /* spreadsheet mode.  Output as CSV. */
 +      newTempFile(p, "csv");
 +      ShellClearFlag(p, SHFLG_Echo);
 +      p->mode = MODE_Csv;
 +      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
 +      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf);
      }else{
 -      raw_printf(stderr, "Usage: .bail on|off\n");
 +      /* text editor mode */
 +      newTempFile(p, "txt");
 +      bTxtMode = 1;
 +    }
 +    sqlite3_free(zFile);
 +    zFile = sqlite3_mprintf("%s", p->zTempFile);
 +  }
 +#endif /* SQLITE_NOHAVE_SYSTEM */
 +  if( zFile[0]=='|' ){
 +#ifdef SQLITE_OMIT_POPEN
 +    *pzErr = shellMPrintf(&rc, "Error: pipes are not supported in this OS\n");
 +    rc = 1;
 +    p->out = STD_OUT;
 +#else
 +    p->out = popen(zFile + 1, "w");
 +    if( p->out==0 ){
 +      *pzErr = shellMPrintf(&rc, "Error: cannot open pipe \"%s\"\n", zFile + 1);
 +      p->out = STD_OUT;
        rc = 1;
 +    }else{
 +      if( bBOM ) fprintf(p->out,"\357\273\277");
 +      sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
      }
 -  }else
 -
 -  if( c=='b' && n>=3 && strncmp(azArg[0], "binary", n)==0 ){
 -    if( nArg==2 ){
 -      if( booleanValue(azArg[1]) ){
 -        setBinaryMode(p->out, 1);
 -      }else{
 -        setTextMode(p->out, 1);
 +#endif
 +  }else{
 +    p->out = output_file_open(zFile, bTxtMode);
 +    if( p->out==0 ){
 +      if( strcmp(zFile,"off")!=0 ){
 +        *pzErr = shellMPrintf
 +          (&rc, "Error: cannot write to \"%s\"\n", zFile);
        }
 -    }else{
 -      raw_printf(stderr, "Usage: .binary on|off\n");
 +      p->out = STD_OUT;
        rc = 1;
 +    } else {
 +      if( bBOM ) fprintf(p->out,"\357\273\277");
 +      sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
      }
 -  }else
 +  }
 +  sqlite3_free(zFile);
 +  return rc;
 +}
  
 -  /* The undocumented ".breakpoint" command causes a call to the no-op
 -  ** routine named test_breakpoint().
 -  */
 -  if( c=='b' && n>=3 && strncmp(azArg[0], "breakpoint", n)==0 ){
 -    test_breakpoint();
 -  }else
  
 -  if( c=='c' && strcmp(azArg[0],"cd")==0 ){
 -    failIfSafeMode(p, "cannot run .cd in safe mode");
 -    if( nArg==2 ){
 -#if defined(_WIN32) || defined(WIN32)
 -      wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
 -      rc = !SetCurrentDirectoryW(z);
 -      sqlite3_free(z);
 -#else
 -      rc = chdir(azArg[1]);
 -#endif
 -      if( rc ){
 -        utf8_printf(stderr, "Cannot change to directory \"%s\"\n", azArg[1]);
 -        rc = 1;
 +/*****************
 + * The .filectrl and fullschema commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".filectrl CMD ...        Run various sqlite3_file_control() operations",
 +  "   --schema SCHEMA         Use SCHEMA instead of \"main\"",
 +  "   --help                  Show CMD details",
 +  ".fullschema ?--indent?   Show schema and the content of sqlite_stat tables",
 +];
 +DISPATCHABLE_COMMAND( filectrl ? 2 0 ){
 +  static const struct {
 +    const char *zCtrlName;   /* Name of a test-control option */
 +    int ctrlCode;            /* Integer code for that option */
 +    const char *zUsage;      /* Usage notes */
 +  } aCtrl[] = {
 +    { "chunk_size",     SQLITE_FCNTL_CHUNK_SIZE,      "SIZE"           },
 +    { "data_version",   SQLITE_FCNTL_DATA_VERSION,    ""               },
 +    { "has_moved",      SQLITE_FCNTL_HAS_MOVED,       ""               },  
 +    { "lock_timeout",   SQLITE_FCNTL_LOCK_TIMEOUT,    "MILLISEC"       },
 +    { "persist_wal",    SQLITE_FCNTL_PERSIST_WAL,     "[BOOLEAN]"      },
 + /* { "pragma",         SQLITE_FCNTL_PRAGMA,          "NAME ARG"       },*/
 +    { "psow",       SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]"      },
 +    { "reserve_bytes",  SQLITE_FCNTL_RESERVE_BYTES,   "[N]"            },
 +    { "size_limit",     SQLITE_FCNTL_SIZE_LIMIT,      "[LIMIT]"        },
 +    { "tempfilename",   SQLITE_FCNTL_TEMPFILENAME,    ""               },
 + /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY,  "COUNT DELAY"    },*/
 +  };
 +  int filectrl = -1;
 +  int iCtrl = -1;
 +  sqlite3_int64 iRes = 0;  /* Integer result to display if rc2==1 */
 +  int isOk = 0;            /* 0: usage  1: %lld  2: no-result */
 +  int n2, i;
 +  const char *zCmd = 0;
 +  const char *zSchema = 0;
 +
 +  open_db(p, 0);
 +  zCmd = nArg>=2 ? azArg[1] : "help";
 +
 +  if( zCmd[0]=='-' 
 +      && (strcmp(zCmd,"--schema")==0 || strcmp(zCmd,"-schema")==0)
 +      && nArg>=4
 +      ){
 +    zSchema = azArg[2];
 +    for(i=3; i<nArg; i++) azArg[i-2] = azArg[i];
 +    nArg -= 2;
 +    zCmd = azArg[1];
 +  }
 +
 +  /* The argument can optionally begin with "-" or "--" */
 +  if( zCmd[0]=='-' && zCmd[1] ){
 +    zCmd++;
 +    if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 +  }
 +
 +  /* --help lists all file-controls */
 +  if( strcmp(zCmd,"help")==0 ){
 +    utf8_printf(p->out, "Available file-controls:\n");
 +    for(i=0; i<ArraySize(aCtrl); i++){
 +      utf8_printf(p->out, "  .filectrl %s %s\n",
 +                  aCtrl[i].zCtrlName, aCtrl[i].zUsage);
 +    }
 +    return 1;
 +  }
 +  
 +  /* Convert filectrl text option to value. Allow any
 +  ** unique prefix of the option name, or a numerical value. */
 +  n2 = strlen30(zCmd);
 +  for(i=0; i<ArraySize(aCtrl); i++){
 +    if( strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
 +      if( filectrl<0 ){
 +        filectrl = aCtrl[i].ctrlCode;
 +        iCtrl = i;
 +      }else{
 +        utf8_printf(STD_ERR, "Error: ambiguous file-control: \"%s\"\n"
 +                    "Use \".filectrl --help\" for help\n", zCmd);
 +        return 1;
        }
 -    }else{
 -      raw_printf(stderr, "Usage: .cd DIRECTORY\n");
 -      rc = 1;
      }
 -  }else
 -
 -  if( c=='c' && n>=3 && strncmp(azArg[0], "changes", n)==0 ){
 -    if( nArg==2 ){
 -      setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
 -    }else{
 -      raw_printf(stderr, "Usage: .changes on|off\n");
 -      rc = 1;
 +  }
 +  if( filectrl<0 ){
 +    utf8_printf(STD_ERR,"Error: unknown file-control: %s\n"
 +                "Use \".filectrl --help\" for help\n", zCmd);
 +  }else{
 +   switch(filectrl){
 +    case SQLITE_FCNTL_SIZE_LIMIT: {
 +      if( nArg!=2 && nArg!=3 ) break;
 +      iRes = nArg==3 ? integerValue(azArg[2]) : -1;
 +      sqlite3_file_control(p->db, zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes);
 +      isOk = 1;
 +      break;
      }
 -  }else
 -
 -  /* Cancel output redirection, if it is currently set (by .testcase)
 -  ** Then read the content of the testcase-out.txt file and compare against
 -  ** azArg[1].  If there are differences, report an error and exit.
 -  */
 -  if( c=='c' && n>=3 && strncmp(azArg[0], "check", n)==0 ){
 -    char *zRes = 0;
 -    output_reset(p);
 -    if( nArg!=2 ){
 -      raw_printf(stderr, "Usage: .check GLOB-PATTERN\n");
 -      rc = 2;
 -    }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){
 -      raw_printf(stderr, "Error: cannot read 'testcase-out.txt'\n");
 -      rc = 2;
 -    }else if( testcase_glob(azArg[1],zRes)==0 ){
 -      utf8_printf(stderr,
 -                 "testcase-%s FAILED\n Expected: [%s]\n      Got: [%s]\n",
 -                 p->zTestcase, azArg[1], zRes);
 -      rc = 1;
 -    }else{
 -      utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase);
 -      p->nCheck++;
 +    case SQLITE_FCNTL_LOCK_TIMEOUT:
 +    case SQLITE_FCNTL_CHUNK_SIZE: {
 +      int x;
 +      if( nArg!=3 ) break;
 +      x = (int)integerValue(azArg[2]);
 +      sqlite3_file_control(p->db, zSchema, filectrl, &x);
 +      isOk = 2;
 +      break;
      }
 -    sqlite3_free(zRes);
 -  }else
 -
 -  if( c=='c' && strncmp(azArg[0], "clone", n)==0 ){
 -    failIfSafeMode(p, "cannot run .clone in safe mode");
 -    if( nArg==2 ){
 -      tryToClone(p, azArg[1]);
 -    }else{
 -      raw_printf(stderr, "Usage: .clone FILENAME\n");
 -      rc = 1;
 +    case SQLITE_FCNTL_PERSIST_WAL:
 +    case SQLITE_FCNTL_POWERSAFE_OVERWRITE: {
 +      int x;
 +      if( nArg!=2 && nArg!=3 ) break;
 +      x = nArg==3 ? booleanValue(azArg[2]) : -1;
 +      sqlite3_file_control(p->db, zSchema, filectrl, &x);
 +      iRes = x;
 +      isOk = 1;
 +      break;
      }
 -  }else
 -
 -  if( c=='c' && strncmp(azArg[0], "connection", n)==0 ){
 -    if( nArg==1 ){
 -      /* List available connections */
 -      int i;
 -      for(i=0; i<ArraySize(p->aAuxDb); i++){
 -        const char *zFile = p->aAuxDb[i].zDbFilename;
 -        if( p->aAuxDb[i].db==0 && p->pAuxDb!=&p->aAuxDb[i] ){
 -          zFile = "(not open)";
 -        }else if( zFile==0 ){
 -          zFile = "(memory)";
 -        }else if( zFile[0]==0 ){
 -          zFile = "(temporary-file)";
 -        }
 -        if( p->pAuxDb == &p->aAuxDb[i] ){
 -          utf8_printf(stdout, "ACTIVE %d: %s\n", i, zFile);
 -        }else if( p->aAuxDb[i].db!=0 ){
 -          utf8_printf(stdout, "       %d: %s\n", i, zFile);
 -        }
 +    case SQLITE_FCNTL_DATA_VERSION:
 +    case SQLITE_FCNTL_HAS_MOVED: {
 +      int x;
 +      if( nArg!=2 ) break;
 +      sqlite3_file_control(p->db, zSchema, filectrl, &x);
 +      iRes = x;
 +      isOk = 1;
 +      break;
 +    }
 +    case SQLITE_FCNTL_TEMPFILENAME: {
 +      char *z = 0;
 +      if( nArg!=2 ) break;
 +      sqlite3_file_control(p->db, zSchema, filectrl, &z);
 +      if( z ){
 +        utf8_printf(p->out, "%s\n", z);
 +        sqlite3_free(z);
        }
 -    }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){
 -      int i = azArg[1][0] - '0';
 -      if( p->pAuxDb != &p->aAuxDb[i] && i>=0 && i<ArraySize(p->aAuxDb) ){
 -        p->pAuxDb->db = p->db;
 -        p->pAuxDb = &p->aAuxDb[i];
 -        globalDb = p->db = p->pAuxDb->db;
 -        p->pAuxDb->db = 0;
 -      }
 -    }else if( nArg==3 && strcmp(azArg[1], "close")==0
 -           && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){
 -      int i = azArg[2][0] - '0';
 -      if( i<0 || i>=ArraySize(p->aAuxDb) ){
 -        /* No-op */
 -      }else if( p->pAuxDb == &p->aAuxDb[i] ){
 -        raw_printf(stderr, "cannot close the active database connection\n");
 -        rc = 1;
 -      }else if( p->aAuxDb[i].db ){
 -        session_close_all(p, i);
 -        close_db(p->aAuxDb[i].db);
 -        p->aAuxDb[i].db = 0;
 +      isOk = 2;
 +      break;
 +    }
 +    case SQLITE_FCNTL_RESERVE_BYTES: {
 +      int x;
 +      if( nArg>=3 ){
 +        x = atoi(azArg[2]);
 +        sqlite3_file_control(p->db, zSchema, filectrl, &x);
        }
 -    }else{
 -      raw_printf(stderr, "Usage: .connection [close] [CONNECTION-NUMBER]\n");
 -      rc = 1;
 +      x = -1;
 +      sqlite3_file_control(p->db, zSchema, filectrl, &x);
 +      utf8_printf(p->out,"%d\n", x);
 +      isOk = 2;
 +      break;
      }
 -  }else
 +   }
 +  }
 +  if( isOk==0 && iCtrl>=0 ){
 +    utf8_printf(p->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);
 +  }
 +  return 0;
 +}
  
 -  if( c=='d' && n>1 && strncmp(azArg[0], "databases", n)==0 ){
 -    char **azName = 0;
 -    int nName = 0;
 +DISPATCHABLE_COMMAND( fullschema ? 1 2 ){
 +  int rc;
 +  ShellState data;
 +  int doStats = 0;
 +  memcpy(&data, p, sizeof(data));
 +  data.showHeader = 0;
 +  data.cMode = data.mode = MODE_Semi;
 +  if( nArg==2 && optionMatch(azArg[1], "indent") ){
 +    data.cMode = data.mode = MODE_Pretty;
 +    nArg = 1;
 +  }
 +  if( nArg!=1 ){
 +    return SHELL_INVALID_ARGS;
 +  }
 +  open_db(p, 0);
 +  rc = sqlite3_exec(p->db,
 +    "SELECT sql FROM"
 +    "  (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x"
 +    "     FROM sqlite_schema UNION ALL"
 +    "   SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) "
 +    "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' "
 +    "ORDER BY x",
 +    callback, &data, 0
 +  );
 +  if( rc==SQLITE_OK ){
      sqlite3_stmt *pStmt;
 -    int i;
 -    open_db(p, 0);
 -    rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
 -    if( rc ){
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 -      rc = 1;
 -    }else{
 -      while( sqlite3_step(pStmt)==SQLITE_ROW ){
 -        const char *zSchema = (const char *)sqlite3_column_text(pStmt,1);
 -        const char *zFile = (const char*)sqlite3_column_text(pStmt,2);
 -        azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*));
 -        if( azName==0 ){ shell_out_of_memory();  /* Does not return */ }
 -        azName[nName*2] = strdup(zSchema);
 -        azName[nName*2+1] = strdup(zFile);
 -        nName++;
 -      }
 -    }
 +    rc = sqlite3_prepare_v2(p->db,
 +                            "SELECT rowid FROM sqlite_schema"
 +                            " WHERE name GLOB 'sqlite_stat[134]'",
 +                            -1, &pStmt, 0);
 +    doStats = sqlite3_step(pStmt)==SQLITE_ROW;
      sqlite3_finalize(pStmt);
 -    for(i=0; i<nName; i++){
 -      int eTxn = sqlite3_txn_state(p->db, azName[i*2]);
 -      int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]);
 -      const char *z = azName[i*2+1];
 -      utf8_printf(p->out, "%s: %s %s%s\n",
 -         azName[i*2],
 -         z && z[0] ? z : "\"\"",
 -         bRdonly ? "r/o" : "r/w",
 -         eTxn==SQLITE_TXN_NONE ? "" :
 -            eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
 -      free(azName[i*2]);
 -      free(azName[i*2+1]);
 -    }
 -    sqlite3_free(azName);
 -  }else
 +  }
 +  if( doStats==0 ){
 +    raw_printf(p->out, "/* No STAT tables available */\n");
 +  }else{
 +    raw_printf(p->out, "ANALYZE sqlite_schema;\n");
 +    data.cMode = data.mode = MODE_Insert;
 +    data.zDestTable = "sqlite_stat1";
 +    shell_exec(&data, "SELECT * FROM sqlite_stat1", 0);
 +    data.zDestTable = "sqlite_stat4";
 +    shell_exec(&data, "SELECT * FROM sqlite_stat4", 0);
 +    raw_printf(p->out, "ANALYZE sqlite_schema;\n");
 +  }
 +  return rc > 0;
 +}
  
 -  if( c=='d' && n>=3 && strncmp(azArg[0], "dbconfig", n)==0 ){
 -    static const struct DbConfigChoices {
 -      const char *zName;
 -      int op;
 -    } aDbConfig[] = {
 -        { "defensive",          SQLITE_DBCONFIG_DEFENSIVE             },
 -        { "dqs_ddl",            SQLITE_DBCONFIG_DQS_DDL               },
 -        { "dqs_dml",            SQLITE_DBCONFIG_DQS_DML               },
 -        { "enable_fkey",        SQLITE_DBCONFIG_ENABLE_FKEY           },
 -        { "enable_qpsg",        SQLITE_DBCONFIG_ENABLE_QPSG           },
 -        { "enable_trigger",     SQLITE_DBCONFIG_ENABLE_TRIGGER        },
 -        { "enable_view",        SQLITE_DBCONFIG_ENABLE_VIEW           },
 -        { "fts3_tokenizer",     SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER },
 -        { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE    },
 -        { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT    },
 -        { "load_extension",     SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION },
 -        { "no_ckpt_on_close",   SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE      },
 -        { "reset_database",     SQLITE_DBCONFIG_RESET_DATABASE        },
 -        { "trigger_eqp",        SQLITE_DBCONFIG_TRIGGER_EQP           },
 -        { "trusted_schema",     SQLITE_DBCONFIG_TRUSTED_SCHEMA        },
 -        { "writable_schema",    SQLITE_DBCONFIG_WRITABLE_SCHEMA       },
 -    };
 -    int ii, v;
 -    open_db(p, 0);
 -    for(ii=0; ii<ArraySize(aDbConfig); ii++){
 -      if( nArg>1 && strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue;
 -      if( nArg>=3 ){
 -        sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0);
 -      }
 -      sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v);
 -      utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off");
 -      if( nArg>1 ) break;
 +/*****************
 + * The .headers command
 + */
 +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;
 +  return 0;
 +}
 +
 +/*****************
 + * The .help command
 + */
 +COLLECT_HELP_TEXT[
 +  ".help ?(PATTERN|-all)? Show help text for some or all command(s)",
 +  "   PATTERN                Show help for matching command(s)",
 +  "   -all                   Show all help for all commands",
 +];
 +DISPATCHABLE_COMMAND( help 3 1 2 ){
 +  const char *zPat = 0;
 +  if( nArg>1 ){
 +    char *z = azArg[1];
 +    if( strcmp(z,"-a")==0
 +        || strcmp(z,"-all")==0
 +        || strcmp(z,"--all")==0 ){
 +      zPat = "";
 +    }else{
 +      zPat = z;
      }
 -    if( nArg>1 && ii==ArraySize(aDbConfig) ){
 -      utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]);
 -      utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n");
 -    }   
 -  }else
 +  }
 +  if( showHelp(p->out, zPat)==0 ){
 +    utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]);
 +  }
 +  /* Help pleas never fail! */
 +  return 0;
 +}
  
 -  if( c=='d' && n>=3 && strncmp(azArg[0], "dbinfo", n)==0 ){
 -    rc = shell_dbinfo_command(p, nArg, azArg);
 -  }else
 +/*****************
 + * The .import command
 + */
 +COLLECT_HELP_TEXT[
 +  ".import FILE TABLE       Import data from FILE into TABLE",
 +  "   Options:",
 +  "     --ascii               Use \\037 and \\036 as column and row separators",
 +  "     --csv                 Use , and \\n as column and row separators",
 +  "     --skip N              Skip the first N rows of input",
 +  "     -v                    \"Verbose\" - increase auxiliary output",
 +  "   Notes:",
 +  "     *  If TABLE does not exist, it is created.  The first row of input",
 +  "        determines the column names.",
 +  "     *  If neither --csv or --ascii are used, the input mode is derived",
 +  "        from the \".mode\" output mode",
 +  "     *  If FILE begins with \"|\" then it is a command that generates the",
 +  "        input text.",
 +];
 +DISPATCHABLE_COMMAND( import ? 3 7 ){
 +  char *zTable = 0;           /* Insert data into this table */
 +  char *zFile = 0;            /* Name of file to extra content from */
 +  sqlite3_stmt *pStmt = NULL; /* A statement */
 +  int nCol;                   /* Number of columns in the table */
 +  int nByte;                  /* Number of bytes in an SQL string */
 +  int i, j;                   /* Loop counters */
 +  int needCommit;             /* True to COMMIT or ROLLBACK at end */
 +  int nSep;                   /* Number of bytes in p->colSeparator[] */
 +  char *zSql;                 /* An SQL statement */
 +  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 */
 +  int rc = 0;
  
 -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
 -  if( c=='r' && strncmp(azArg[0], "recover", n)==0 ){
 -    open_db(p, 0);
 -    rc = recoverDatabaseCmd(p, nArg, azArg);
 -  }else
 -#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
 +  if(p->bSafeMode) return SHELL_FORBIDDEN_OP;
 +  memset(&sCtx, 0, sizeof(sCtx));
 +  if( 0==(sCtx.z = sqlite3_malloc64(120)) ){
 +    shell_out_of_memory();
 +  }
  
 -  if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){
 -    char *zLike = 0;
 -    char *zSql;
 -    int i;
 -    int savedShowHeader = p->showHeader;
 -    int savedShellFlags = p->shellFlgs;
 -    ShellClearFlag(p, 
 -       SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo
 -       |SHFLG_DumpDataOnly|SHFLG_DumpNoSys);
 -    for(i=1; i<nArg; i++){
 -      if( azArg[i][0]=='-' ){
 -        const char *z = azArg[i]+1;
 -        if( z[0]=='-' ) z++;
 -        if( strcmp(z,"preserve-rowids")==0 ){
 -#ifdef SQLITE_OMIT_VIRTUALTABLE
 -          raw_printf(stderr, "The --preserve-rowids option is not compatible"
 -                             " with SQLITE_OMIT_VIRTUALTABLE\n");
 -          rc = 1;
 -          sqlite3_free(zLike);
 -          goto meta_command_exit;
 -#else
 -          ShellSetFlag(p, SHFLG_PreserveRowid);
 -#endif
 -        }else
 -        if( strcmp(z,"newlines")==0 ){
 -          ShellSetFlag(p, SHFLG_Newlines);
 -        }else
 -        if( strcmp(z,"data-only")==0 ){
 -          ShellSetFlag(p, SHFLG_DumpDataOnly);
 -        }else
 -        if( strcmp(z,"nosys")==0 ){
 -          ShellSetFlag(p, SHFLG_DumpNoSys);
 -        }else
 -        {
 -          raw_printf(stderr, "Unknown option \"%s\" on \".dump\"\n", azArg[i]);
 -          rc = 1;
 -          sqlite3_free(zLike);
 -          goto meta_command_exit;
 -        }
 +  if( p->mode==MODE_Ascii ){
 +    xRead = ascii_read_one_field;
 +  }else{
 +    xRead = csv_read_one_field;
 +  }
 +  for(i=1; i<nArg; i++){
 +    char *z = azArg[i];
 +    if( z[0]=='-' && z[1]=='-' ) z++;
 +    if( z[0]!='-' ){
 +      if( zFile==0 ){
 +        zFile = z;
 +      }else if( zTable==0 ){
 +        zTable = z;
        }else{
 -        /* azArg[i] contains a LIKE pattern. This ".dump" request should
 -        ** only dump data for tables for which either the table name matches
 -        ** the LIKE pattern, or the table appears to be a shadow table of
 -        ** a virtual table for which the name matches the LIKE pattern.
 -        */
 -        char *zExpr = sqlite3_mprintf(
 -            "name LIKE %Q ESCAPE '\\' OR EXISTS ("
 -            "  SELECT 1 FROM sqlite_schema WHERE "
 -            "    name LIKE %Q ESCAPE '\\' AND"
 -            "    sql LIKE 'CREATE VIRTUAL TABLE%%' AND"
 -            "    substr(o.name, 1, length(name)+1) == (name||'_')"
 -            ")", azArg[i], azArg[i]
 -        );
 -      
 -        if( zLike ){
 -          zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr);
 -        }else{
 -          zLike = zExpr;
 -        }
 +        *pzErr = shellMPrintf(0,"       surplus argument: \"%s\"\n", z);
 +        return SHELL_INVALID_ARGS;
        }
 +    }else if( strcmp(z,"-v")==0 ){
 +      eVerbose++;
 +    }else if( strcmp(z,"-skip")==0 && i<nArg-1 ){
 +      nSkip = integerValue(azArg[++i]);
 +    }else if( strcmp(z,"-ascii")==0 ){
 +      sCtx.cColSep = SEP_Unit[0];
 +      sCtx.cRowSep = SEP_Record[0];
 +      xRead = ascii_read_one_field;
 +      useOutputMode = 0;
 +    }else if( strcmp(z,"-csv")==0 ){
 +      sCtx.cColSep = ',';
 +      sCtx.cRowSep = '\n';
 +      xRead = csv_read_one_field;
 +      useOutputMode = 0;
 +    }else{
 +      *pzErr = shellMPrintf(0,"       unknown option: \"%s\"", z);
 +      return SHELL_INVALID_ARGS;
      }
 -
 -    open_db(p, 0);
 -
 -    if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 -      /* When playing back a "dump", the content might appear in an order
 -      ** which causes immediate foreign key constraints to be violated.
 -      ** So disable foreign-key constraint enforcement to prevent problems. */
 -      raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n");
 -      raw_printf(p->out, "BEGIN TRANSACTION;\n");
 -    }
 -    p->writableSchema = 0;
 -    p->showHeader = 0;
 -    /* Set writable_schema=ON since doing so forces SQLite to initialize
 -    ** as much of the schema as it can even if the sqlite_schema table is
 -    ** corrupt. */
 -    sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
 -    p->nErr = 0;
 -    if( zLike==0 ) zLike = sqlite3_mprintf("true");
 -    zSql = sqlite3_mprintf(
 -      "SELECT name, type, sql FROM sqlite_schema AS o "
 -      "WHERE (%s) AND type=='table'"
 -      "  AND sql NOT NULL"
 -      " ORDER BY tbl_name='sqlite_sequence', rowid",
 -      zLike
 -    );
 -    run_schema_dump_query(p,zSql);
 -    sqlite3_free(zSql);
 -    if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 -      zSql = sqlite3_mprintf(
 -        "SELECT sql FROM sqlite_schema AS o "
 -        "WHERE (%s) AND sql NOT NULL"
 -        "  AND type IN ('index','trigger','view')",
 -        zLike
 -      );
 -      run_table_dump_query(p, zSql);
 -      sqlite3_free(zSql);
 -    }
 -    sqlite3_free(zLike);
 -    if( p->writableSchema ){
 -      raw_printf(p->out, "PRAGMA writable_schema=OFF;\n");
 -      p->writableSchema = 0;
 +  }
 +  if( zTable==0 ){
 +    *pzErr = shellMPrintf(0,"       missing %s argument.\n",
 +                zFile==0 ? "FILE" : "TABLE");
 +    return SHELL_INVALID_ARGS;
 +  }
 +  seenInterrupt = 0;
 +  open_db(p, 0);
 +  if( useOutputMode ){
 +    const char *zYap = 0;
 +    /* If neither the --csv or --ascii options are specified, then set
 +    ** the column and row separator characters from the output mode. */
 +    nSep = strlen30(p->colSeparator);
 +    if( nSep==0 ){
 +      zYap = "Error: non-null column separator required for import";
 +    }
 +    if( nSep>1 ){
 +      zYap = "Error: multi-character or multi-byte column separators"
 +        " not allowed for import";
 +    }
 +    nSep = strlen30(p->rowSeparator);
 +    if( nSep==0 ){
 +      zYap = "Error: non-null row separator required for import";
 +    }
 +    if( zYap!=0 ){
 +      *pzErr = shellMPrintf(0,"%s\n", zYap);
 +      return 1;
      }
 -    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");
 +    if( nSep==2 && p->mode==MODE_Csv && strcmp(p->rowSeparator,SEP_CrLf)==0 ){
 +      /* When importing CSV (only), if the row separator is set to the
 +      ** default output row separator, change it to the default input
 +      ** row separator.  This avoids having to maintain different input
 +      ** and output row separators. */
 +      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 +      nSep = strlen30(p->rowSeparator);
      }
 -    p->showHeader = savedShowHeader;
 -    p->shellFlgs = savedShellFlags;
 -  }else
 -
 -  if( c=='e' && strncmp(azArg[0], "echo", n)==0 ){
 -    if( nArg==2 ){
 -      setOrClearFlag(p, SHFLG_Echo, azArg[1]);
 -    }else{
 -      raw_printf(stderr, "Usage: .echo on|off\n");
 -      rc = 1;
 +    if( nSep>1 ){
 +      *pzErr = sqlite3_mprintf
 +        ("Error: multi-character row separators not allowed for import\n");
 +      return 1;
      }
 -  }else
 -
 -  if( c=='e' && strncmp(azArg[0], "eqp", n)==0 ){
 -    if( nArg==2 ){
 -      p->autoEQPtest = 0;
 -      if( p->autoEQPtrace ){
 -        if( p->db ) sqlite3_exec(p->db, "PRAGMA vdbe_trace=OFF;", 0, 0, 0);
 -        p->autoEQPtrace = 0;
 -      }
 -      if( strcmp(azArg[1],"full")==0 ){
 -        p->autoEQP = AUTOEQP_full;
 -      }else if( strcmp(azArg[1],"trigger")==0 ){
 -        p->autoEQP = AUTOEQP_trigger;
 -#ifdef SQLITE_DEBUG
 -      }else if( strcmp(azArg[1],"test")==0 ){
 -        p->autoEQP = AUTOEQP_on;
 -        p->autoEQPtest = 1;
 -      }else if( strcmp(azArg[1],"trace")==0 ){
 -        p->autoEQP = AUTOEQP_full;
 -        p->autoEQPtrace = 1;
 -        open_db(p, 0);
 -        sqlite3_exec(p->db, "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0);
 -        sqlite3_exec(p->db, "PRAGMA vdbe_trace=ON;", 0, 0, 0);
 +    sCtx.cColSep = p->colSeparator[0];
 +    sCtx.cRowSep = p->rowSeparator[0];
 +  }
 +  sCtx.zFile = zFile;
 +  sCtx.nLine = 1;
 +  if( sCtx.zFile[0]=='|' ){
 +#ifdef SQLITE_OMIT_POPEN
 +    *pzErr = shellMPrintf(0,"Error: pipes are not supported in this OS\n");
 +    return 1;
 +#else
 +    sCtx.in = popen(sCtx.zFile+1, "r");
 +    sCtx.zFile = "<pipe>";
 +    sCtx.xCloser = pclose;
  #endif
 -      }else{
 -        p->autoEQP = (u8)booleanValue(azArg[1]);
 -      }
 -    }else{
 -      raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n");
 -      rc = 1;
 +  }else{
 +    sCtx.in = fopen(sCtx.zFile, "rb");
 +    sCtx.xCloser = fclose;
 +  }
 +  if( sCtx.in==0 ){
 +        *pzErr = shellMPrintf(0,"Error: cannot open \"%s\"\n", zFile);
 +    import_cleanup(&sCtx);
 +    return 1;
 +  }
 +  if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
 +    char zSep[2];
 +    zSep[1] = 0;
 +    zSep[0] = sCtx.cColSep;
 +    utf8_printf(p->out, "Column separator ");
 +    output_c_string(p->out, zSep);
 +    utf8_printf(p->out, ", row separator ");
 +    zSep[0] = sCtx.cRowSep;
 +    output_c_string(p->out, zSep);
 +    utf8_printf(p->out, "\n");
 +  }
 +  while( (nSkip--)>0 ){
 +    while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
 +  }
 +  zSql = sqlite3_mprintf("SELECT * FROM \"%w\"", zTable);
 +  if( zSql==0 ){
 +    import_cleanup(&sCtx);
 +    shell_out_of_memory();
 +  }
 +  nByte = strlen30(zSql);
 +  rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 +  import_append_char(&sCtx, 0);    /* To ensure sCtx.z is allocated */
 +  if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){
 +    char *zCreate = sqlite3_mprintf("CREATE TABLE \"%w\"", zTable);
 +    char cSep = '(';
 +    while( xRead(&sCtx) ){
 +      zCreate = sqlite3_mprintf("%z%c\n  \"%w\" TEXT", zCreate, cSep, sCtx.z);
 +      cSep = ',';
 +      if( sCtx.cTerm!=sCtx.cColSep ) break;
 +    }
 +    if( cSep=='(' ){
 +      sqlite3_free(zCreate);
 +      import_cleanup(&sCtx);
 +      *pzErr = shellMPrintf(0,"%s: empty file\n", sCtx.zFile);
 +      return 1;
      }
 -  }else
 -
 -  if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){
 -    if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc);
 -    rc = 2;
 -  }else
 -
 -  /* The ".explain" command is automatic now.  It is largely pointless.  It
 -  ** retained purely for backwards compatibility */
 -  if( c=='e' && strncmp(azArg[0], "explain", n)==0 ){
 -    int val = 1;
 -    if( nArg>=2 ){
 -      if( strcmp(azArg[1],"auto")==0 ){
 -        val = 99;
 -      }else{
 -        val =  booleanValue(azArg[1]);
 -      }
 +    zCreate = sqlite3_mprintf("%z\n)", zCreate);
 +    if( eVerbose>=1 ){
 +      utf8_printf(p->out, "%s\n", zCreate);
      }
 -    if( val==1 && p->mode!=MODE_Explain ){
 -      p->normalMode = p->mode;
 -      p->mode = MODE_Explain;
 -      p->autoExplain = 0;
 -    }else if( val==0 ){
 -      if( p->mode==MODE_Explain ) p->mode = p->normalMode;
 -      p->autoExplain = 0;
 -    }else if( val==99 ){
 -      if( p->mode==MODE_Explain ) p->mode = p->normalMode;
 -      p->autoExplain = 1;
 +    rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
 +    sqlite3_free(zCreate);
 +    if( rc ){
 +      *pzErr = shellMPrintf(0,"CREATE TABLE \"%s\"(...) failed: %s\n",
 +                               zTable, sqlite3_errmsg(p->db));
 +      import_cleanup(&sCtx);
 +      return 1;
      }
 -  }else
 -
 -#ifndef SQLITE_OMIT_VIRTUALTABLE
 -  if( c=='e' && strncmp(azArg[0], "expert", n)==0 ){
 -    open_db(p, 0);
 -    expertDotCommand(p, azArg, nArg);
 -  }else
 -#endif
 -
 -  if( c=='f' && strncmp(azArg[0], "filectrl", n)==0 ){
 -    static const struct {
 -       const char *zCtrlName;   /* Name of a test-control option */
 -       int ctrlCode;            /* Integer code for that option */
 -       const char *zUsage;      /* Usage notes */
 -    } aCtrl[] = {
 -      { "chunk_size",     SQLITE_FCNTL_CHUNK_SIZE,      "SIZE"           },
 -      { "data_version",   SQLITE_FCNTL_DATA_VERSION,    ""               },
 -      { "has_moved",      SQLITE_FCNTL_HAS_MOVED,       ""               },  
 -      { "lock_timeout",   SQLITE_FCNTL_LOCK_TIMEOUT,    "MILLISEC"       },
 -      { "persist_wal",    SQLITE_FCNTL_PERSIST_WAL,     "[BOOLEAN]"      },
 -   /* { "pragma",         SQLITE_FCNTL_PRAGMA,          "NAME ARG"       },*/
 -      { "psow",       SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]"      },
 -      { "reserve_bytes",  SQLITE_FCNTL_RESERVE_BYTES,   "[N]"            },
 -      { "size_limit",     SQLITE_FCNTL_SIZE_LIMIT,      "[LIMIT]"        },
 -      { "tempfilename",   SQLITE_FCNTL_TEMPFILENAME,    ""               },
 -   /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY,  "COUNT DELAY"    },*/
 -    };
 -    int filectrl = -1;
 -    int iCtrl = -1;
 -    sqlite3_int64 iRes = 0;  /* Integer result to display if rc2==1 */
 -    int isOk = 0;            /* 0: usage  1: %lld  2: no-result */
 -    int n2, i;
 -    const char *zCmd = 0;
 -    const char *zSchema = 0;
 -
 -    open_db(p, 0);
 -    zCmd = nArg>=2 ? azArg[1] : "help";
 -
 -    if( zCmd[0]=='-' 
 -     && (strcmp(zCmd,"--schema")==0 || strcmp(zCmd,"-schema")==0)
 -     && nArg>=4
 -    ){
 -      zSchema = azArg[2];
 -      for(i=3; i<nArg; i++) azArg[i-2] = azArg[i];
 -      nArg -= 2;
 -      zCmd = azArg[1];
 +    rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 +  }
 +  sqlite3_free(zSql);
 +  if( rc ){
 +    if (pStmt) sqlite3_finalize(pStmt);
 +    *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
 +    import_cleanup(&sCtx);
 +    return 1;
 +  }
 +  nCol = sqlite3_column_count(pStmt);
 +  sqlite3_finalize(pStmt);
 +  pStmt = 0;
 +  if( nCol==0 ) return 0; /* no columns, no error */
 +  zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 );
 +  if( zSql==0 ){
 +    import_cleanup(&sCtx);
 +    shell_out_of_memory();
 +  }
 +  sqlite3_snprintf(nByte+20, zSql, "INSERT INTO \"%w\" VALUES(?", zTable);
 +  j = strlen30(zSql);
 +  for(i=1; i<nCol; i++){
 +    zSql[j++] = ',';
 +    zSql[j++] = '?';
 +  }
 +  zSql[j++] = ')';
 +  zSql[j] = 0;
 +  if( eVerbose>=2 ){
 +    utf8_printf(p->out, "Insert using: %s\n", zSql);
 +  }
 +  rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 +  sqlite3_free(zSql);
 +  if( rc ){
 +    *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
 +    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);
 +  do{
 +    int startLine = sCtx.nLine;
 +    for(i=0; i<nCol; i++){
 +      char *z = xRead(&sCtx);
 +      /*
 +      ** Did we reach end-of-file before finding any columns?
 +      ** If so, stop instead of NULL filling the remaining columns.
 +      */
 +      if( z==0 && i==0 ) break;
 +      /*
 +      ** Did we reach end-of-file OR end-of-line before finding any
 +      ** columns in ASCII mode?  If so, stop instead of NULL filling
 +      ** the remaining columns.
 +      */
 +      if( p->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
 +      sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
 +      if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
 +        utf8_printf(STD_ERR, "%s:%d: expected %d columns but found %d - "
 +                    "filling the rest with NULL\n",
 +                    sCtx.zFile, startLine, nCol, i+1);
 +        i += 2;
 +        while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; }
 +      }
      }
 -
 -    /* The argument can optionally begin with "-" or "--" */
 -    if( zCmd[0]=='-' && zCmd[1] ){
 -      zCmd++;
 -      if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 +    if( sCtx.cTerm==sCtx.cColSep ){
 +      do{
 +        xRead(&sCtx);
 +        i++;
 +      }while( sCtx.cTerm==sCtx.cColSep );
 +      utf8_printf(STD_ERR, "%s:%d: expected %d columns but found %d - "
 +                  "extras ignored\n",
 +                  sCtx.zFile, startLine, nCol, i);
      }
 -
 -    /* --help lists all file-controls */
 -    if( strcmp(zCmd,"help")==0 ){
 -      utf8_printf(p->out, "Available file-controls:\n");
 -      for(i=0; i<ArraySize(aCtrl); i++){
 -        utf8_printf(p->out, "  .filectrl %s %s\n",
 -                    aCtrl[i].zCtrlName, aCtrl[i].zUsage);
 +    if( i>=nCol ){
 +      sqlite3_step(pStmt);
 +      rc = sqlite3_reset(pStmt);
 +      if( rc!=SQLITE_OK ){
 +        utf8_printf(STD_ERR, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
 +                    startLine, sqlite3_errmsg(p->db));
 +        sCtx.nErr++;
 +      }else{
 +        sCtx.nRow++;
        }
 -      rc = 1;
 -      goto meta_command_exit;
      }
 +  }while( sCtx.cTerm!=EOF );
  
 -    /* convert filectrl text option to value. allow any unique prefix
 -    ** of the option name, or a numerical value. */
 -    n2 = strlen30(zCmd);
 -    for(i=0; i<ArraySize(aCtrl); i++){
 -      if( strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
 -        if( filectrl<0 ){
 -          filectrl = aCtrl[i].ctrlCode;
 -          iCtrl = i;
 -        }else{
 -          utf8_printf(stderr, "Error: ambiguous file-control: \"%s\"\n"
 -                              "Use \".filectrl --help\" for help\n", zCmd);
 -          rc = 1;
 -          goto meta_command_exit;
 -        }
 -      }
 -    }
 -    if( filectrl<0 ){
 -      utf8_printf(stderr,"Error: unknown file-control: %s\n"
 -                         "Use \".filectrl --help\" for help\n", zCmd);
 -    }else{
 -      switch(filectrl){
 -        case SQLITE_FCNTL_SIZE_LIMIT: {
 -          if( nArg!=2 && nArg!=3 ) break;
 -          iRes = nArg==3 ? integerValue(azArg[2]) : -1;
 -          sqlite3_file_control(p->db, zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes);
 -          isOk = 1;
 -          break;
 -        }
 -        case SQLITE_FCNTL_LOCK_TIMEOUT:
 -        case SQLITE_FCNTL_CHUNK_SIZE: {
 -          int x;
 -          if( nArg!=3 ) break;
 -          x = (int)integerValue(azArg[2]);
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          isOk = 2;
 -          break;
 -        }
 -        case SQLITE_FCNTL_PERSIST_WAL:
 -        case SQLITE_FCNTL_POWERSAFE_OVERWRITE: {
 -          int x;
 -          if( nArg!=2 && nArg!=3 ) break;
 -          x = nArg==3 ? booleanValue(azArg[2]) : -1;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          iRes = x;
 -          isOk = 1;
 -          break;
 -        }
 -        case SQLITE_FCNTL_DATA_VERSION:
 -        case SQLITE_FCNTL_HAS_MOVED: {
 -          int x;
 -          if( nArg!=2 ) break;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          iRes = x;
 -          isOk = 1;
 -          break;
 -        }
 -        case SQLITE_FCNTL_TEMPFILENAME: {
 -          char *z = 0;
 -          if( nArg!=2 ) break;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &z);
 -          if( z ){
 -            utf8_printf(p->out, "%s\n", z);
 -            sqlite3_free(z);
 -          }
 -          isOk = 2;
 -          break;
 -        }
 -        case SQLITE_FCNTL_RESERVE_BYTES: {
 -          int x;
 -          if( nArg>=3 ){
 -            x = atoi(azArg[2]);
 -            sqlite3_file_control(p->db, zSchema, filectrl, &x);
 +  import_cleanup(&sCtx);
 +  sqlite3_finalize(pStmt);
 +  if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0);
 +  if( eVerbose>0 ){
 +    utf8_printf(p->out,
 +      "Added %d rows with %d errors using %d lines of input\n",
 +      sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
 +  }
 +  return 0;
 +}
 +
++/*****************
++ * The .keyword command
++ */
++CONDITION_COMMAND( keyword !defined(NO_KEYWORD_COMMAND) );
++COLLECT_HELP_TEXT[
++  ".keyword ?KW?            List keywords, or say whether KW is one.",
++];
++DISPATCHABLE_COMMAND( keyword ? 1 2 ){
++  if( nArg<2 ){
++    int i = 0;
++    int nk = sqlite3_keyword_count();
++    int nCol = 0;
++    int szKW;
++    while( i<nk ){
++      const char *zKW = 0;
++      if( SQLITE_OK==sqlite3_keyword_name(i++, &zKW, &szKW) ){
++        char kwBuf[50];
++        if( szKW < sizeof(kwBuf) ){
++          const char *zSep = " ";
++          if( (nCol += (1+szKW))>75){
++            zSep = "\n";
++            nCol = 0;
+           }
 -          x = -1;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          utf8_printf(p->out,"%d\n", x);
 -          isOk = 2;
 -          break;
++          memcpy(kwBuf, zKW, szKW);
++          kwBuf[szKW] = 0;
++          utf8_printf(p->out, "%s%s", kwBuf, zSep);
+         }
+       }
+     }
 -    if( isOk==0 && iCtrl>=0 ){
 -      utf8_printf(p->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
 -      rc = 1;
 -    }else if( isOk==1 ){
 -      char zBuf[100];
 -      sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes);
 -      raw_printf(p->out, "%s\n", zBuf);
 -    }
 -  }else
++    if( nCol>0 ) utf8_printf(p->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",
++                azArg[1], (isKeyword)? "" : " not");
++  }
++  return 0;
++}
  
 -  if( c=='f' && strncmp(azArg[0], "fullschema", n)==0 ){
 -    ShellState data;
 -    int doStats = 0;
 -    memcpy(&data, p, sizeof(data));
 -    data.showHeader = 0;
 -    data.cMode = data.mode = MODE_Semi;
 -    if( nArg==2 && optionMatch(azArg[1], "indent") ){
 -      data.cMode = data.mode = MODE_Pretty;
 -      nArg = 1;
 -    }
 -    if( nArg!=1 ){
 -      raw_printf(stderr, "Usage: .fullschema ?--indent?\n");
 -      rc = 1;
 -      goto meta_command_exit;
 +/*****************
 + * The .imposter, .iotrace, limit, lint, .load and .log commands
 + */
 +CONDITION_COMMAND( imposter !defined(SQLITE_OMIT_TEST_CONTROL) );
 +CONDITION_COMMAND( iotrace defined(SQLITE_ENABLE_IOTRACE) );
 +CONDITION_COMMAND( load !defined(SQLITE_OMIT_LOAD_EXTENSION) );
 +COLLECT_HELP_TEXT[
 +  ".imposter INDEX TABLE    Create imposter table TABLE on index INDEX",
 +  ".iotrace FILE            Enable I/O diagnostic logging to FILE",
 +  ".limit ?LIMIT? ?VAL?     Display or change the value of an SQLITE_LIMIT",
 +  ".lint OPTIONS            Report potential schema issues.",
 +  "     Options:",
 +  "        fkey-indexes     Find missing foreign key indexes",
 +  ".load FILE ?ENTRY?       Load an extension library",
 +  ".log FILE|off            Turn logging on or off.  FILE can be stderr/stdout",
 +];
 +DISPATCHABLE_COMMAND( imposter ? 3 3 ){
 +  int rc = 0;
 +  char *zSql;
 +  char *zCollist = 0;
 +  sqlite3_stmt *pStmt;
 +  int tnum = 0;
 +  int isWO = 0;  /* True if making an imposter of a WITHOUT ROWID table */
 +  int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */
 +  int i;
 +  if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){
 +    *pzErr = shellMPrintf(0,"Usage: .imposter INDEX IMPOSTER\n"
 +                             "       .imposter off\n");
 +    /* Also allowed, but not documented:
 +    **
 +    **    .imposter TABLE IMPOSTER
 +    **
 +    ** where TABLE is a WITHOUT ROWID table.  In that case, the
 +    ** imposter is another WITHOUT ROWID table with the columns in
 +    ** storage order. */
 +    return 1;
 +  }
 +  open_db(p, 0);
 +  if( nArg==2 ){
 +    sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 1);
 +    return 0;
 +  }
 +  zSql = sqlite3_mprintf(
 +                         "SELECT rootpage, 0 FROM sqlite_schema"
 +                         " WHERE name='%q' AND type='index'"
 +                         "UNION ALL "
 +                         "SELECT rootpage, 1 FROM sqlite_schema"
 +                         " WHERE name='%q' AND type='table'"
 +                         "   AND sql LIKE '%%without%%rowid%%'",
 +                         azArg[1], azArg[1]
 +                         );
 +  sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 +  sqlite3_free(zSql);
 +  if( sqlite3_step(pStmt)==SQLITE_ROW ){
 +    tnum = sqlite3_column_int(pStmt, 0);
 +    isWO = sqlite3_column_int(pStmt, 1);
 +  }
 +  sqlite3_finalize(pStmt);
 +  zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]);
 +  rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 +  sqlite3_free(zSql);
 +  i = 0;
 +  while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 +    char zLabel[20];
 +    const char *zCol = (const char*)sqlite3_column_text(pStmt,2);
 +    i++;
 +    if( zCol==0 ){
 +      if( sqlite3_column_int(pStmt,1)==-1 ){
 +        zCol = "_ROWID_";
 +      }else{
 +        sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i);
 +        zCol = zLabel;
 +      }
      }
 -    open_db(p, 0);
 -    rc = sqlite3_exec(p->db,
 -       "SELECT sql FROM"
 -       "  (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x"
 -       "     FROM sqlite_schema UNION ALL"
 -       "   SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) "
 -       "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' "
 -       "ORDER BY x",
 -       callback, &data, 0
 -    );
 -    if( rc==SQLITE_OK ){
 -      sqlite3_stmt *pStmt;
 -      rc = sqlite3_prepare_v2(p->db,
 -               "SELECT rowid FROM sqlite_schema"
 -               " WHERE name GLOB 'sqlite_stat[134]'",
 -               -1, &pStmt, 0);
 -      doStats = sqlite3_step(pStmt)==SQLITE_ROW;
 -      sqlite3_finalize(pStmt);
 +    if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){
 +      lenPK = (int)strlen(zCollist);
      }
 -    if( doStats==0 ){
 -      raw_printf(p->out, "/* No STAT tables available */\n");
 +    if( zCollist==0 ){
 +      zCollist = sqlite3_mprintf("\"%w\"", zCol);
      }else{
 -      raw_printf(p->out, "ANALYZE sqlite_schema;\n");
 -      data.cMode = data.mode = MODE_Insert;
 -      data.zDestTable = "sqlite_stat1";
 -      shell_exec(&data, "SELECT * FROM sqlite_stat1", 0);
 -      data.zDestTable = "sqlite_stat4";
 -      shell_exec(&data, "SELECT * FROM sqlite_stat4", 0);
 -      raw_printf(p->out, "ANALYZE sqlite_schema;\n");
 +      zCollist = sqlite3_mprintf("%z,\"%w\"", zCollist, zCol);
      }
 -  }else
 -
 -  if( c=='h' && strncmp(azArg[0], "headers", n)==0 ){
 -    if( nArg==2 ){
 -      p->showHeader = booleanValue(azArg[1]);
 -      p->shellFlgs |= SHFLG_HeaderSet;
 +  }
 +  sqlite3_finalize(pStmt);
 +  if( i==0 || tnum==0 ){
 +    *pzErr = shellMPrintf(0,"no such index: \"%s\"\n", azArg[1]);
 +    sqlite3_free(zCollist);
 +    return 1;
 +  }
 +  if( lenPK==0 ) lenPK = 100000;
 +  zSql = sqlite3_mprintf(
 +                         "CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))WITHOUT ROWID",
 +                         azArg[2], zCollist, lenPK, zCollist);
 +  sqlite3_free(zCollist);
 +  rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 1, tnum);
 +  if( rc==SQLITE_OK ){
 +    rc = sqlite3_exec(p->db, zSql, 0, 0, 0);
 +    sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0);
 +    if( rc ){
 +      *pzErr = shellMPrintf(0,"Error in [%s]: %s\n",
 +                               zSql, sqlite3_errmsg(p->db));
      }else{
 -      raw_printf(stderr, "Usage: .headers on|off\n");
 -      rc = 1;
 +      utf8_printf(STD_OUT, "%s;\n", zSql);
 +      raw_printf(STD_OUT,
 +                 "WARNING: writing to an imposter table will corrupt the \"%s\" %s!\n",
 +                 azArg[1], isWO ? "table" : "index"
 +                 );
      }
 -  }else
 -
 -  if( c=='h' && strncmp(azArg[0], "help", n)==0 ){
 -    if( nArg>=2 ){
 -      n = showHelp(p->out, azArg[1]);
 -      if( n==0 ){
 -        utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]);
 -      }
 +  }else{
 +    *pzErr = shellMPrintf(0,"SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
 +  }
 +  sqlite3_free(zSql);
 +  return rc != 0;
 +}
 +DISPATCHABLE_COMMAND( iotrace ? 2 2 ){
 +  SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...);
 +  if( iotrace && iotrace!=STD_OUT ) fclose(iotrace);
 +  iotrace = 0;
 +  if( nArg<2 ){
 +    sqlite3IoTrace = 0;
 +  }else if( strcmp(azArg[1], "-")==0 ){
 +    sqlite3IoTrace = iotracePrintf;
 +    iotrace = STD_OUT;
 +  }else{
 +    iotrace = fopen(azArg[1], "w");
 +    if( iotrace==0 ){
 +      *pzErr = shellMPrintf(0,"Error: cannot open \"%s\"\n", azArg[1]);
 +      sqlite3IoTrace = 0;
 +      return 1;
      }else{
 -      showHelp(p->out, 0);
 -    }
 -  }else
 -
 -  if( c=='i' && strncmp(azArg[0], "import", n)==0 ){
 -    char *zTable = 0;           /* Insert data into this table */
 -    char *zFile = 0;            /* Name of file to extra content from */
 -    sqlite3_stmt *pStmt = NULL; /* A statement */
 -    int nCol;                   /* Number of columns in the table */
 -    int nByte;                  /* Number of bytes in an SQL string */
 -    int i, j;                   /* Loop counters */
 -    int needCommit;             /* True to COMMIT or ROLLBACK at end */
 -    int nSep;                   /* Number of bytes in p->colSeparator[] */
 -    char *zSql;                 /* An SQL statement */
 -    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 */
 -
 -    failIfSafeMode(p, "cannot run .import in safe mode");
 -    memset(&sCtx, 0, sizeof(sCtx));
 -    sCtx.z = sqlite3_malloc64(120);
 -    if( sCtx.z==0 ){
 -      import_cleanup(&sCtx);
 -      shell_out_of_memory();
 +      sqlite3IoTrace = iotracePrintf;
      }
 -    if( p->mode==MODE_Ascii ){
 -      xRead = ascii_read_one_field;
 -    }else{
 -      xRead = csv_read_one_field;
 +  }
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( limits 5 1 3 ){
 +  static const struct {
 +    const char *zLimitName;   /* Name of a limit */
 +    int limitCode;            /* Integer code for that limit */
 +  } aLimit[] = {
 +    { "length",                SQLITE_LIMIT_LENGTH                    },
 +    { "sql_length",            SQLITE_LIMIT_SQL_LENGTH                },
 +    { "column",                SQLITE_LIMIT_COLUMN                    },
 +    { "expr_depth",            SQLITE_LIMIT_EXPR_DEPTH                },
 +    { "compound_select",       SQLITE_LIMIT_COMPOUND_SELECT           },
 +    { "vdbe_op",               SQLITE_LIMIT_VDBE_OP                   },
 +    { "function_arg",          SQLITE_LIMIT_FUNCTION_ARG              },
 +    { "attached",              SQLITE_LIMIT_ATTACHED                  },
 +    { "like_pattern_length",   SQLITE_LIMIT_LIKE_PATTERN_LENGTH       },
 +    { "variable_number",       SQLITE_LIMIT_VARIABLE_NUMBER           },
 +    { "trigger_depth",         SQLITE_LIMIT_TRIGGER_DEPTH             },
 +    { "worker_threads",        SQLITE_LIMIT_WORKER_THREADS            },
 +  };
 +  int i, n2;
 +  open_db(p, 0);
 +  if( nArg==1 ){
 +    for(i=0; i<ArraySize(aLimit); i++){
 +      fprintf(STD_OUT, "%20s %d\n", aLimit[i].zLimitName,
 +             sqlite3_limit(p->db, aLimit[i].limitCode, -1));
      }
 -    for(i=1; i<nArg; i++){
 -      char *z = azArg[i];
 -      if( z[0]=='-' && z[1]=='-' ) z++;
 -      if( z[0]!='-' ){
 -        if( zFile==0 ){
 -          zFile = z;
 -        }else if( zTable==0 ){
 -          zTable = z;
 +  }else if( nArg>3 ){
 +    return SHELL_INVALID_ARGS;
 +  }else{
 +    int iLimit = -1;
 +    n2 = strlen30(azArg[1]);
 +    for(i=0; i<ArraySize(aLimit); i++){
 +      if( sqlite3_strnicmp(aLimit[i].zLimitName, azArg[1], n2)==0 ){
 +        if( iLimit<0 ){
 +          iLimit = i;
          }else{
 -          utf8_printf(p->out, "ERROR: extra argument: \"%s\".  Usage:\n", z);
 -          showHelp(p->out, "import");
 -          rc = 1;
 -          goto meta_command_exit;
 +          *pzErr = shellMPrintf(0,"ambiguous limit: \"%s\"\n", azArg[1]);
 +          return 1;
          }
 -      }else if( strcmp(z,"-v")==0 ){
 -        eVerbose++;
 -      }else if( strcmp(z,"-skip")==0 && i<nArg-1 ){
 -        nSkip = integerValue(azArg[++i]);
 -      }else if( strcmp(z,"-ascii")==0 ){
 -        sCtx.cColSep = SEP_Unit[0];
 -        sCtx.cRowSep = SEP_Record[0];
 -        xRead = ascii_read_one_field;
 -        useOutputMode = 0;
 -      }else if( strcmp(z,"-csv")==0 ){
 -        sCtx.cColSep = ',';
 -        sCtx.cRowSep = '\n';
 -        xRead = csv_read_one_field;
 -        useOutputMode = 0;
 -      }else{
 -        utf8_printf(p->out, "ERROR: unknown option: \"%s\".  Usage:\n", z);
 -        showHelp(p->out, "import");
 -        rc = 1;
 -        goto meta_command_exit;
        }
      }
 -    if( zTable==0 ){
 -      utf8_printf(p->out, "ERROR: missing %s argument. Usage:\n",
 -                  zFile==0 ? "FILE" : "TABLE");
 -      showHelp(p->out, "import");
 -      rc = 1;
 -      goto meta_command_exit;
 +    if( iLimit<0 ){
 +      *pzErr = sqlite3_mprintf
 +        ("unknown limit: \"%s\"\n"
 +         "enter \".limits\" with no arguments for a list.\n",
 +         azArg[1]);
 +      return 1;
      }
 -    seenInterrupt = 0;
 -    open_db(p, 0);
 -    if( useOutputMode ){
 -      /* If neither the --csv or --ascii options are specified, then set
 -      ** the column and row separator characters from the output mode. */
 -      nSep = strlen30(p->colSeparator);
 -      if( nSep==0 ){
 -        raw_printf(stderr,
 -                   "Error: non-null column separator required for import\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      if( nSep>1 ){
 -        raw_printf(stderr, 
 -              "Error: multi-character column separators not allowed"
 -              " for import\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      nSep = strlen30(p->rowSeparator);
 -      if( nSep==0 ){
 -        raw_printf(stderr,
 -            "Error: non-null row separator required for import\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      if( nSep==2 && p->mode==MODE_Csv && strcmp(p->rowSeparator,SEP_CrLf)==0 ){
 -        /* When importing CSV (only), if the row separator is set to the
 -        ** default output row separator, change it to the default input
 -        ** row separator.  This avoids having to maintain different input
 -        ** and output row separators. */
 -        sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 -        nSep = strlen30(p->rowSeparator);
 -      }
 -      if( nSep>1 ){
 -        raw_printf(stderr, "Error: multi-character row separators not allowed"
 -                           " for import\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      sCtx.cColSep = p->colSeparator[0];
 -      sCtx.cRowSep = p->rowSeparator[0];
 +    if( nArg==3 ){
 +      sqlite3_limit(p->db, aLimit[iLimit].limitCode,
 +                    (int)integerValue(azArg[2]));
      }
 -    sCtx.zFile = zFile;
 -    sCtx.nLine = 1;
 -    if( sCtx.zFile[0]=='|' ){
 -#ifdef SQLITE_OMIT_POPEN
 -      raw_printf(stderr, "Error: pipes are not supported in this OS\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -#else
 -      sCtx.in = popen(sCtx.zFile+1, "r");
 -      sCtx.zFile = "<pipe>";
 -      sCtx.xCloser = pclose;
 -#endif
 +    fprintf(STD_OUT, "%20s %d\n", aLimit[iLimit].zLimitName,
 +           sqlite3_limit(p->db, 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;
 +}
 +
 +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) ){
 +    return 1;
 +  }
 +  return 0;
 +}
 +
 +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);
 +  return 0;
 +}
 +
 +/*****************
 + * The .mode command
 + */
 +COLLECT_HELP_TEXT[
 +  ".mode MODE ?TABLE?       Set output mode",
 +  "   MODE is one of:",
 +  "     ascii     Columns/rows delimited by 0x1F and 0x1E",
 +  "     box       Tables using unicode box-drawing characters",
 +  "     csv       Comma-separated values",
 +  "     column    Output in columns.  (See .width)",
++  "     count     Output only result row count",
 +  "     html      HTML <table> code",
 +  "     insert    SQL insert statements for TABLE",
 +  "     json      Results in a JSON array",
 +  "     line      One value per line",
 +  "     list      Values delimited by \"|\"",
 +  "     markdown  Markdown table format",
++  "     off       Query output suppressed",
 +  "     quote     Escape answers as for SQL",
 +  "     table     ASCII-art table",
 +  "     tabs      Tab-separated values",
 +  "     tcl       TCL list elements",
 +];
 +DISPATCHABLE_COMMAND( mode ? 1 3 ){
 +  const char *zMode = nArg>=2 ? azArg[1] : "";
 +  int n2 = strlen30(zMode);
 +  int c2 = zMode[0];
 +  if( c2=='i' && strncmp(azArg[1],"insert",n2)==0 ){
 +    p->mode = MODE_Insert;
 +    set_table_name(p, nArg>=3 ? azArg[2] : "table");
 +  }else if( nArg>2 ){
 +    return SHELL_INVALID_ARGS;
 +  }else if( c2=='l' && n2>2 && strncmp(azArg[1],"lines",n2)==0 ){
 +    p->mode = MODE_Line;
 +    sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 +  }else if( c2=='c' && strncmp(azArg[1],"columns",n2)==0 ){
 +    p->mode = MODE_Column;
 +    if( (p->shellFlgs & SHFLG_HeaderSet)==0 ){
 +      p->showHeader = 1;
 +    }
 +    sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 +  }else if( c2=='l' && n2>2 && strncmp(azArg[1],"list",n2)==0 ){
 +    p->mode = MODE_List;
 +    sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Column);
 +    sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 +  }else if( c2=='h' && strncmp(azArg[1],"html",n2)==0 ){
 +    p->mode = MODE_Html;
 +  }else if( c2=='t' && strncmp(azArg[1],"tcl",n2)==0 ){
 +    p->mode = MODE_Tcl;
 +    sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Space);
 +    sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 +  }else if( c2=='c' && strncmp(azArg[1],"csv",n2)==0 ){
 +    p->mode = MODE_Csv;
 +    sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
 +    sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf);
 +  }else if( c2=='t' && strncmp(azArg[1],"tabs",n2)==0 ){
 +    p->mode = MODE_List;
 +    sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Tab);
 +  }else if( c2=='q' && strncmp(azArg[1],"quote",n2)==0 ){
 +    p->mode = MODE_Quote;
 +    sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
 +    sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 +  }else if( c2=='a' && strncmp(azArg[1],"ascii",n2)==0 ){
 +    p->mode = MODE_Ascii;
 +    sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Unit);
 +    sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Record);
 +  }else if( c2=='m' && strncmp(azArg[1],"markdown",n2)==0 ){
 +    p->mode = MODE_Markdown;
 +  }else if( c2=='t' && strncmp(azArg[1],"table",n2)==0 ){
 +    p->mode = MODE_Table;
 +  }else if( c2=='b' && strncmp(azArg[1],"box",n2)==0 ){
 +    p->mode = MODE_Box;
 +  }else if( c2=='c' && strncmp(azArg[1],"count",n2)==0 ){
 +    p->mode = MODE_Count;
 +  }else if( c2=='o' && strncmp(azArg[1],"off",n2)==0 ){
 +    p->mode = MODE_Off;
 +  }else if( c2=='j' && strncmp(azArg[1],"json",n2)==0 ){
 +    p->mode = MODE_Json;
 +  }else if( nArg==1 ){
 +    raw_printf(p->out, "current output mode: %s\n", modeDescr[p->mode]);
 +  }else{
 +    *pzErr = sqlite3_mprintf
 +      ("Error: mode should be one of: ascii box column count csv html\n"
 +       " insert json line list markdown off quote table tabs tcl\n");
 +    return 1;
 +  }
 +  p->cMode = p->mode;
 +  return 0;
 +}
 +
 +/*****************
 + * The .oom command
 + */
 +CONDITION_COMMAND( oom defined(SQLITE_DEBUG) );
 +COLLECT_HELP_TEXT[
 +  ".oom ?--repeat M? ?N?    Simulate an OOM error on the N-th allocation",
 +];
 +DISPATCHABLE_COMMAND( oom ? 1 4 ){
 +  int i;
 +  for(i=1; i<nArg; i++){
 +    const char *z = azArg[i];
 +    if( z[0]=='-' && z[1]=='-' ) z++;
 +    if( strcmp(z,"-repeat")==0 ){
 +      if( i==nArg-1 ){
 +        raw_printf(p->out, "missing argument on \"%s\"\n", azArg[i]);
 +        return 1;
 +      }else{
 +        oomRepeat = (int)integerValue(azArg[++i]);
 +      }
 +    }else if( IsDigit(z[0]) ){
 +      oomCounter = (int)integerValue(azArg[i]);
      }else{
 -      sCtx.in = fopen(sCtx.zFile, "rb");
 -      sCtx.xCloser = fclose;
 -    }
 -    if( sCtx.in==0 ){
 -      utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile);
 -      rc = 1;
 -      import_cleanup(&sCtx);
 -      goto meta_command_exit;
 -    }
 -    if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
 -      char zSep[2];
 -      zSep[1] = 0;
 -      zSep[0] = sCtx.cColSep;
 -      utf8_printf(p->out, "Column separator ");
 -      output_c_string(p->out, zSep);
 -      utf8_printf(p->out, ", row separator ");
 -      zSep[0] = sCtx.cRowSep;
 -      output_c_string(p->out, zSep);
 -      utf8_printf(p->out, "\n");
 -    }
 -    while( (nSkip--)>0 ){
 -      while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
 -    }
 -    zSql = sqlite3_mprintf("SELECT * FROM \"%w\"", zTable);
 -    if( zSql==0 ){
 -      import_cleanup(&sCtx);
 -      shell_out_of_memory();
 +      raw_printf(p->out, "unknown argument: \"%s\"\n", azArg[i]);
 +      raw_printf(p->out, "Usage: .oom [--repeat N] [M]\n");
 +      return 1;
      }
 -    nByte = strlen30(zSql);
 -    rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    import_append_char(&sCtx, 0);    /* To ensure sCtx.z is allocated */
 -    if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){
 -      char *zCreate = sqlite3_mprintf("CREATE TABLE \"%w\"", zTable);
 -      char cSep = '(';
 -      while( xRead(&sCtx) ){
 -        zCreate = sqlite3_mprintf("%z%c\n  \"%w\" TEXT", zCreate, cSep, sCtx.z);
 -        cSep = ',';
 -        if( sCtx.cTerm!=sCtx.cColSep ) break;
 -      }
 -      if( cSep=='(' ){
 -        sqlite3_free(zCreate);
 -        import_cleanup(&sCtx);
 -        utf8_printf(stderr,"%s: empty file\n", sCtx.zFile);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      zCreate = sqlite3_mprintf("%z\n)", zCreate);
 -      if( eVerbose>=1 ){
 -        utf8_printf(p->out, "%s\n", zCreate);
 -      }
 -      rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
 -      sqlite3_free(zCreate);
 -      if( rc ){
 -        utf8_printf(stderr, "CREATE TABLE \"%s\"(...) failed: %s\n", zTable,
 -                sqlite3_errmsg(p->db));
 -        import_cleanup(&sCtx);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 +  }
 +  raw_printf(p->out, "oomCounter = %d\n", oomCounter);
 +  raw_printf(p->out, "oomRepeat  = %d\n", oomRepeat);
 +  return 0;
 +}
 +
 +/*****************
 + * The .open, .nonce and .nullvalue commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".open ?OPTIONS? ?FILE?   Close existing database and reopen FILE",
 +  "     Options:",
 +  "        --append        Use appendvfs to append database to the end of FILE",
 +#ifndef SQLITE_OMIT_DESERIALIZE
 +  "        --deserialize   Load into memory using sqlite3_deserialize()",
 +  "        --hexdb         Load the output of \"dbtotxt\" as an in-memory db",
 +  "        --maxsize N     Maximum size for --hexdb or --deserialized database",
 +#endif
 +  "        --new           Initialize FILE to an empty database",
 +  "        --nofollow      Do not follow symbolic links",
 +  "        --readonly      Open FILE readonly",
 +  "        --zip           FILE is a ZIP archive",
 +  ".nonce STRING            Disable safe mode for one command if nonce matches",
 +  ".nullvalue STRING        Use STRING in place of NULL values",
 +];
 +DISPATCHABLE_COMMAND( open 3 1 0 ){
 +  char *zNewFilename = 0;  /* Name of the database file to open */
 +  int iName = 1;           /* Index in azArg[] of the filename */
 +  int newFlag = 0;         /* True to delete file before opening */
 +  int openMode = SHELL_OPEN_UNSPEC;
 +  int rc = 0;
 +  /* Check for command-line arguments */
 +  for(iName=1; iName<nArg; iName++){
 +    const char *z = azArg[iName];
 +    if( optionMatch(z,"new") ){
 +      newFlag = 1;
 +#ifdef SQLITE_HAVE_ZLIB
 +    }else if( optionMatch(z, "zip") ){
 +      openMode = SHELL_OPEN_ZIPFILE;
 +#endif
 +    }else if( optionMatch(z, "append") ){
 +      openMode = SHELL_OPEN_APPENDVFS;
 +    }else if( optionMatch(z, "readonly") ){
 +      openMode = SHELL_OPEN_READONLY;
 +    }else if( optionMatch(z, "nofollow") ){
 +      p->openFlags |= SQLITE_OPEN_NOFOLLOW;
 +#ifndef SQLITE_OMIT_DESERIALIZE
 +    }else if( optionMatch(z, "deserialize") ){
 +      openMode = SHELL_OPEN_DESERIALIZE;
 +    }else if( optionMatch(z, "hexdb") ){
 +      openMode = SHELL_OPEN_HEXDB;
 +    }else if( optionMatch(z, "maxsize") && iName+1<nArg ){
 +      p->szMax = integerValue(azArg[++iName]);
 +#endif /* SQLITE_OMIT_DESERIALIZE */
 +    }else if( z[0]=='-' ){
 +      *pzErr = shellMPrintf(0,"unknown option: %s\n", z);
 +      return SHELL_INVALID_ARGS;
 +    }else if( zNewFilename ){
 +      *pzErr = shellMPrintf(0,"extra argument: \"%s\"\n", z);
 +      return SHELL_INVALID_ARGS;
 +    }else{
 +      zNewFilename = sqlite3_mprintf("%s", z);
 +    }
 +  }
 +
 +  /* Close the existing database */
 +  session_close_all(p, -1);
 +  close_db(p->db);
 +  p->db = 0;
 +  p->pAuxDb->zDbFilename = 0;
 +  sqlite3_free(p->pAuxDb->zFreeOnClose);
 +  p->pAuxDb->zFreeOnClose = 0;
 +  p->openMode = openMode;
 +  p->openFlags = 0;
 +  p->szMax = 0;
 +
 +  /* If a filename is specified, try to open it first */
 +  if( zNewFilename || p->openMode==SHELL_OPEN_HEXDB ){
 +    if( newFlag && !p->bSafeMode ) shellDeleteFile(zNewFilename);
 +    if( p->bSafeMode
 +        && p->openMode!=SHELL_OPEN_HEXDB
 +        && zNewFilename
 +        && strcmp(zNewFilename,":memory:")!=0
 +        ){
 +      *pzErr = shellMPrintf(0,"open disk-based databases");
 +      return SHELL_FORBIDDEN_OP;
      }
 -    sqlite3_free(zSql);
 -    if( rc ){
 -      if (pStmt) sqlite3_finalize(pStmt);
 -      utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db));
 -      import_cleanup(&sCtx);
 +    p->pAuxDb->zDbFilename = zNewFilename;
 +    open_db(p, OPEN_DB_KEEPALIVE);
 +    if( p->db==0 ){
 +      *pzErr = shellMPrintf(0,"Error: cannot open '%s'\n", zNewFilename);
 +      sqlite3_free(zNewFilename);
        rc = 1;
 -      goto meta_command_exit;
 +    }else{
 +      p->pAuxDb->zFreeOnClose = zNewFilename;
 +    }
 +  }
 +  if( p->db==0 ){
 +    /* As a fall-back open a TEMP database */
 +    p->pAuxDb->zDbFilename = 0;
 +    open_db(p, 0);
 +  }
 +  return rc;
 +}
 +
 +DISPATCHABLE_COMMAND( nonce ? 2 2 ){
 +  if( p->zNonce==0 || strcmp(azArg[1],p->zNonce)!=0 ){
 +    raw_printf(STD_ERR, "line %d: incorrect nonce: \"%s\"\n",
 +               p->lineno, azArg[1]);
 +    exit(1);
 +  }
 +  /* Suspend safe mode for 1 meta-command after this. */
 +  p->bSafeModeFuture = 2;
 +  return 0;
 +}
 +
 +DISPATCHABLE_COMMAND( nullvalue ? 2 2 ){
 +  sqlite3_snprintf(sizeof(p->nullValue), p->nullValue,
 +                   "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]);
 +  return 0;
 +}
 +
 +/*****************
 + * The .parameter command
 + */
 +COLLECT_HELP_TEXT[
 +  ".parameter CMD ...       Manage SQL parameter bindings",
 +  "   clear                   Erase all bindings",
 +  "   init                    Initialize the TEMP table that holds bindings",
 +  "   list                    List the current parameter bindings",
 +  "   set PARAMETER VALUE     Given SQL parameter PARAMETER a value of VALUE",
 +  "                           PARAMETER should start with one of: $ : @ ?",
 +  "   unset PARAMETER         Remove PARAMETER from the binding table",
 +];
 +DISPATCHABLE_COMMAND( parameter 4 2 4 ){
 +  int rc = 0;
 +  open_db(p,0);
 +
 +  /* .parameter clear
 +  ** Clear all bind parameters by dropping the TEMP table that holds them.
 +  */
 +  if( nArg==2 && strcmp(azArg[1],"clear")==0 ){
 +    sqlite3_exec(p->db, "DROP TABLE IF EXISTS temp.sqlite_parameters;",
 +                 0, 0, 0);
 +  }else
 +
 +  /* .parameter list
 +  ** List all bind parameters.
 +  */
 +  if( nArg==2 && strcmp(azArg[1],"list")==0 ){
 +    sqlite3_stmt *pStmt = 0;
 +    int rx;
 +    int len = 0;
 +    rx = sqlite3_prepare_v2(p->db,
 +                            "SELECT max(length(key)) "
 +                            "FROM temp.sqlite_parameters;", -1, &pStmt, 0);
 +    if( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 +      len = sqlite3_column_int(pStmt, 0);
 +      if( len>40 ) len = 40;
      }
 -    nCol = sqlite3_column_count(pStmt);
      sqlite3_finalize(pStmt);
      pStmt = 0;
 -    if( nCol==0 ) return 0; /* no columns, no error */
 -    zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 );
 -    if( zSql==0 ){
 -      import_cleanup(&sCtx);
 -      shell_out_of_memory();
 -    }
 -    sqlite3_snprintf(nByte+20, zSql, "INSERT INTO \"%w\" VALUES(?", zTable);
 -    j = strlen30(zSql);
 -    for(i=1; i<nCol; i++){
 -      zSql[j++] = ',';
 -      zSql[j++] = '?';
 -    }
 -    zSql[j++] = ')';
 -    zSql[j] = 0;
 -    if( eVerbose>=2 ){
 -      utf8_printf(p->out, "Insert using: %s\n", zSql);
 -    }
 -    rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    sqlite3_free(zSql);
 -    if( rc ){
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 -      if (pStmt) sqlite3_finalize(pStmt);
 -      import_cleanup(&sCtx);
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    needCommit = sqlite3_get_autocommit(p->db);
 -    if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
 -    do{
 -      int startLine = sCtx.nLine;
 -      for(i=0; i<nCol; i++){
 -        char *z = xRead(&sCtx);
 -        /*
 -        ** Did we reach end-of-file before finding any columns?
 -        ** If so, stop instead of NULL filling the remaining columns.
 -        */
 -        if( z==0 && i==0 ) break;
 -        /*
 -        ** Did we reach end-of-file OR end-of-line before finding any
 -        ** columns in ASCII mode?  If so, stop instead of NULL filling
 -        ** the remaining columns.
 -        */
 -        if( p->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
 -        sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
 -        if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
 -          utf8_printf(stderr, "%s:%d: expected %d columns but found %d - "
 -                          "filling the rest with NULL\n",
 -                          sCtx.zFile, startLine, nCol, i+1);
 -          i += 2;
 -          while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; }
 -        }
 -      }
 -      if( sCtx.cTerm==sCtx.cColSep ){
 -        do{
 -          xRead(&sCtx);
 -          i++;
 -        }while( sCtx.cTerm==sCtx.cColSep );
 -        utf8_printf(stderr, "%s:%d: expected %d columns but found %d - "
 -                        "extras ignored\n",
 -                        sCtx.zFile, startLine, nCol, i);
 -      }
 -      if( i>=nCol ){
 -        sqlite3_step(pStmt);
 -        rc = sqlite3_reset(pStmt);
 -        if( rc!=SQLITE_OK ){
 -          utf8_printf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
 -                      startLine, sqlite3_errmsg(p->db));
 -          sCtx.nErr++;
 -        }else{
 -          sCtx.nRow++;
 -        }
 +    if( len ){
 +      rx = sqlite3_prepare_v2(p->db,
 +                              "SELECT key, quote(value) "
 +                              "FROM temp.sqlite_parameters;", -1, &pStmt, 0);
 +      while( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 +        utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
 +                    sqlite3_column_text(pStmt,1));
        }
 -    }while( sCtx.cTerm!=EOF );
 -
 -    import_cleanup(&sCtx);
 -    sqlite3_finalize(pStmt);
 -    if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0);
 -    if( eVerbose>0 ){
 -      utf8_printf(p->out,
 -          "Added %d rows with %d errors using %d lines of input\n",
 -          sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
 +      sqlite3_finalize(pStmt);
      }
    }else
  
@@@ -9618,32 -9612,57 +9739,36 @@@ DISPATCHABLE_COMMAND( session 3 2 0 )
      */
      if( strcmp(azCmd[0],"changeset")==0 || strcmp(azCmd[0],"patchset")==0 ){
        FILE *out = 0;
--      failIfSafeMode(p, "cannot run \".session %s\" in safe mode", azCmd[0]);
--      if( nCmd!=2 ) goto session_syntax_error;
--      if( pSession->p==0 ) goto session_not_open;
--      out = fopen(azCmd[1], "wb");
--      if( out==0 ){
-         *pzErr = sqlite3_mprintf
-           ("ERROR: cannot open \"%s\" for writing\n", azCmd[1]);
-         rc = 1;
 -        utf8_printf(stderr, "ERROR: cannot open \"%s\" for writing\n",
 -                    azCmd[1]);
++      if( failIfSafeMode
++          (p, "cannot run \".session %s\" in safe mode", azCmd[0]) ){
++        rc = SHELL_FORBIDDEN_OP;
        }else{
--        int szChng;
--        void *pChng;
--        if( azCmd[0][0]=='c' ){
--          rc = sqlite3session_changeset(pSession->p, &szChng, &pChng);
++        if( nCmd!=2 ) goto session_syntax_error;
++        if( pSession->p==0 ) goto session_not_open;
++        out = fopen(azCmd[1], "wb");
++        if( out==0 ){
++          *pzErr = sqlite3_mprintf
++            ("ERROR: cannot open \"%s\" for writing\n", azCmd[1]);
++          rc = 1;
          }else{
--          rc = sqlite3session_patchset(pSession->p, &szChng, &pChng);
--        }
--        if( rc ){
-           fprintf(STD_OUT, "Error: error code %d\n", rc);
 -          printf("Error: error code %d\n", rc);
--          rc = 0;
--        }
-         if( pChng && fwrite(pChng, szChng, 1, out)!=1 ){
-           raw_printf(STD_ERR, "ERROR: Failed to write entire %d-byte output\n",
-                      szChng);
 -        if( pChng
 -          && fwrite(pChng, szChng, 1, out)!=1 ){
 -          raw_printf(stderr, "ERROR: Failed to write entire %d-byte output\n",
 -                  szChng);
++          int szChng;
++          void *pChng;
++          if( azCmd[0][0]=='c' ){
++            rc = sqlite3session_changeset(pSession->p, &szChng, &pChng);
++          }else{
++            rc = sqlite3session_patchset(pSession->p, &szChng, &pChng);
++          }
++          if( rc ){
++            fprintf(STD_OUT, "Error: error code %d\n", rc);
++            rc = 0;
++          }
++          if( pChng && fwrite(pChng, szChng, 1, out)!=1 ){
++            raw_printf(STD_ERR, "ERROR: Failed to write entire %d-byte output\n",
++                       szChng);
++          }
++          sqlite3_free(pChng);
++          fclose(out);
          }
--        sqlite3_free(pChng);
--        fclose(out);
 -      }
 -    }else
 -
 -    /* .session close
 -    ** Close the identified session
 -    */
 -    if( strcmp(azCmd[0], "close")==0 ){
 -      if( nCmd!=1 ) goto session_syntax_error;
 -      if( pAuxDb->nSession ){
 -        session_close(pSession);
 -        pAuxDb->aSession[iSes] = pAuxDb->aSession[--pAuxDb->nSession];
 -      }
 -    }else
 -
 -    /* .session enable ?BOOLEAN?
 -    ** Query or set the enable flag
 -    */
 -    if( strcmp(azCmd[0], "enable")==0 ){
 -      int ii;
 -      if( nCmd>2 ) goto session_syntax_error;
 -      ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
 -      if( pAuxDb->nSession ){
 -        ii = sqlite3session_enable(pSession->p, ii);
 -        utf8_printf(p->out, "session %s enable flag = %d\n",
 -                    pSession->zName, ii);
        }
      }else
  
@@@ -10711,296 -10661,45 +10836,300 @@@ DISPATCHABLE_COMMAND( vfsinfo ? 1 2 )
        raw_printf(p->out, "vfs.iVersion   = %d\n", pVfs->iVersion);
        raw_printf(p->out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
        raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
 -      if( pVfs->pNext ){
 -        raw_printf(p->out, "-----------------------------------\n");
 +    }
 +  }
 +  return 0;
 +}
 +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);
 +  }
 +  for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){
 +    utf8_printf(p->out, "vfs.zName      = \"%s\"%s\n", pVfs->zName,
 +                pVfs==pCurrent ? "  <--- CURRENT" : "");
 +    raw_printf(p->out, "vfs.iVersion   = %d\n", pVfs->iVersion);
 +    raw_printf(p->out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
 +    raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
 +    if( pVfs->pNext ){
 +      raw_printf(p->out, "-----------------------------------\n");
 +    }
 +  }
 +  return 0;
 +}
 +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( zVfsName ){
 +      utf8_printf(p->out, "%s\n", zVfsName);
 +      sqlite3_free(zVfsName);
 +    }
 +  }
 +  return 0;
 +}
 +
 +/*****************
 + * The .width and .wheretrace commands
 + * The .wheretrace command has no help.
 + */
 +COLLECT_HELP_TEXT[
 +  ".width NUM1 NUM2 ...     Set minimum column widths for columnar output",
 +  "     Negative values right-justify",
 +];
 +DISPATCHABLE_COMMAND( width ? 1 0 ){
 +  int j;
 +  assert( nArg<=ArraySize(azArg) );
 +  p->nWidth = nArg-1;
 +  p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(int)*2);
 +  if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory();
 +  if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth];
 +  for(j=1; j<nArg; j++){
 +    p->colWidth[j-1] = (int)integerValue(azArg[j]);
 +  }
 +  return 0;
 +}
 +DISPATCHABLE_COMMAND( wheretrace ? 1 2 ){
 +  unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
 +  sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &x);
 +  return 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.
 +*/
 +INCLUDE( COMMAND_CUSTOMIZE );
 +
 +typedef struct MetaCommand MetaCommand;
 +
 +/* Define and populate command dispatch table. */
 +static struct CommandInfo {
 +  const char * cmdName;
 +  int (*cmdDoer)(char *azArg[], int nArg, ShellState *, char **pzErr);
 +  unsigned char minLen, minArgs, maxArgs;
 +#if OBJECTIFY_COMMANDS
 +  const char *azHelp[2]; /* primary and secondary help text */
 +  void * pCmdData;
 +#endif
 +} command_table[] = {
 +  COMMENT Emit the dispatch table entries generated and collected above.
 +  EMIT_DISPATCH(2);
 +  { 0, 0, 0, -1, -1 }
 +};
 +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",
 +];
 +
 +/*
 +** Text of help messages.
 +**
 +** The help text for each individual command begins with a line that starts
 +** with ".".  Subsequent lines are supplimental information.
 +**
 +** There must be two or more spaces between the end of the command and the
 +** start of the description of what that command does.
 +*/
 +static const char *(azHelp[]) = {
 +/* Template for help text indents and length:
 +  ".whatever ?arg? ...      Summary of effects (limited to this line's length)",
 +  "   ^ ^                   ^  ^                                              ",
 +*/
 +  COMMENT  Emit the help text fragments collected above via COLLECT_HELP_TEXT.
 +  EMIT_HELP_TEXT(2);
 +  0 /* Sentinel */
 +};
 +
 +
 +#define NO_SUCH_COMMAND SQLITE_NOTFOUND
 +/* SHELL_INVALID_ARGS defined as SQLITE_MISUSE in shext_linkage.h */
 +
 +/*****************
 +** Command dispatcher
 +** For the non-extended or non-extensible shell, this function does
 +** a binary search of the fixed list of meta-command info structs.
 +** For an extended shell, it may (TBD) query the shell's DB. Either
 +** way, this function retains its interface.
 +** After successful command lookup and (simple) argument checking,
 +** it calls the found meta-command with the input arguments (except
 +** that azArg[0] is replaced with the properly spelled command name.)
 +** The return is either a dispatch error or whatever the dispatched
 +** meta-command returns.
 +*/
 +int dispatchCommand(char *azArg[], int nArg, ShellState *pSS, char **pzErr){
 +  const char *cmdName = azArg[0];
 +  int cmdLen = strlen30(cmdName);
 +  struct CommandInfo *pci = 0;
 +  int ixb = 0, ixe = numCommands-1;
 +  while( ixb <= ixe ){
 +    int ixm = (ixb+ixe)/2;
 +    int md = strncmp(cmdName, command_table[ixm].cmdName, cmdLen);
 +    if( md>0 ){
 +      ixb = ixm+1;
 +    }else if( md<0 ){
 +      ixe = ixm-1;
 +    }else{
 +      if( command_table[ixm].minLen > cmdLen ){
 +        return NO_SUCH_COMMAND;
        }
 +      pci = &command_table[ixm];
 +      break;
      }
 -  }else
 +  }
 +  if( 0==pci ){
 +    return NO_SUCH_COMMAND;
 +  }
 +  if( pci->minArgs > nArg||(pci->maxArgs > 0 && pci->maxArgs < nArg) ){
 +    return SHELL_INVALID_ARGS;
 +  }
 +  /* Replace any user-shortened command name with its whole name. */
 +  azArg[0] = (char *)pci->cmdName;
 +  return (pci->cmdDoer)(azArg, nArg, pSS, pzErr);
 +}
 +
 +/*
 +** If an input line begins with "." then invoke this routine to
 +** process that line.
 +**
 +** Return 1 on error, 2 to exit, and 0 otherwise.
 +*/
 +static int do_meta_command(char *zLine, ShellState *p){
 +  int h = 1;
 +  int nArg = 0;
 +  int n, c;
 +  int rc = 0;
 +  char *azArg[52];
  
 -  if( c=='v' && strncmp(azArg[0], "vfsname", n)==0 ){
 -    const char *zDbName = nArg==2 ? azArg[1] : "main";
 -    char *zVfsName = 0;
 -    if( p->db ){
 -      sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName);
 -      if( zVfsName ){
 -        utf8_printf(p->out, "%s\n", zVfsName);
 -        sqlite3_free(zVfsName);
 +#ifndef SQLITE_OMIT_VIRTUALTABLE
 +  if( p->expert.pExpert ){
 +    expertFinish(p, 1, 0);
 +  }
 +#endif
 +
 +  /* Parse the input line into tokens.
 +  */
 +  while( zLine[h] && nArg<ArraySize(azArg)-1 ){
 +    while( IsSpace(zLine[h]) ){ h++; }
 +    if( zLine[h]==0 ) break;
 +    if( zLine[h]=='\'' || zLine[h]=='"' ){
 +      int delim = zLine[h++];
 +      azArg[nArg++] = &zLine[h];
 +      while( zLine[h] && zLine[h]!=delim ){
 +        if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++;
 +        h++;
 +      }
 +      if( zLine[h]==delim ){
 +        zLine[h++] = 0;
        }
 +      if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
 +    }else{
 +      azArg[nArg++] = &zLine[h];
 +      while( zLine[h] && !IsSpace(zLine[h]) ){ h++; }
 +      if( zLine[h] ) zLine[h++] = 0;
 +      resolve_backslashes(azArg[nArg-1]);
      }
-     if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc);
 +  }
 +  azArg[nArg] = 0;
 +
 +  /* Process the input line.
 +  */
 +  if( nArg==0 ) return 0; /* no tokens, no error */
 +  n = strlen30(azArg[0]);
 +  c = azArg[0][0];
 +  clearTempFile(p);
 +
 +  /* 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;
 +    rc = 2;
    }else
  
 -  if( c=='w' && strncmp(azArg[0], "wheretrace", n)==0 ){
 -    unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
 -    sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &x);
 +  if( c=='q' && strncmp(azArg[0], "quit", n)==0 ){
 +    rc = 2;
    }else
  
 -  if( c=='w' && strncmp(azArg[0], "width", n)==0 ){
 -    int j;
 -    assert( nArg<=ArraySize(azArg) );
 -    p->nWidth = nArg-1;
 -    p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(int)*2);
 -    if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory();
 -    if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth];
 -    for(j=1; j<nArg; j++){
 -      p->colWidth[j-1] = (int)integerValue(azArg[j]);
 +#ifdef SQLITE_DEBUG
 +  /* Undocumented commands for internal testing.  
 +   * Subject to change without notice.
 +   * These are not dispatched via lookup because the command word varies.
 +   */
 +  if( c=='s' && n>=10 && strncmp(azArg[0], "selftest-", 9)==0 ){
 +    if( strncmp(azArg[0]+9, "boolean", n-9)==0 ){
 +      int i, v;
 +      for(i=1; i<nArg; i++){
 +        v = booleanValue(azArg[i]);
 +        utf8_printf(p->out, "%s: %d 0x%x\n", azArg[i], v, v);
 +      }
 +    }
 +    if( strncmp(azArg[0]+9, "integer", n-9)==0 ){
 +      int i; sqlite3_int64 v;
 +      for(i=1; i<nArg; i++){
 +        char zBuf[200];
 +        v = integerValue(azArg[i]);
 +        sqlite3_snprintf(sizeof(zBuf),zBuf,"%s: %lld 0x%llx\n", azArg[i],v,v);
 +        utf8_printf(p->out, "%s", zBuf);
 +      }
      }
    }else
 -
 +#endif
 +    /* The meta-command is not among the specially handled ones. Dispatch it. */
    {
 -    utf8_printf(stderr, "Error: unknown command or invalid arguments: "
 -      " \"%s\". Enter \".help\" for help\n", azArg[0]);
 -    rc = 1;
 +    char *zErr = 0;
 +    int dispatchResult = dispatchCommand(azArg, nArg, p, &zErr);
++    if( p->abruptExit!=0 ){
++      dispatchResult = SHELL_FORBIDDEN_OP;
++    }
 +    switch( dispatchResult ){
 +    case NO_SUCH_COMMAND:
 +      utf8_printf(STD_ERR, "Error: unknown command: \"%s\"\n", azArg[0]);
 +      if( stdin_is_interactive )
 +        utf8_printf(STD_ERR, "  Enter \".help\" for a list of commands.\n");
 +      rc = 1;
 +      break;
 +    case SHELL_INVALID_ARGS:
 +      utf8_printf(STD_ERR, "Error: invalid arguments for \".%s\"\n", azArg[0]);
 +      if( stdin_is_interactive ){
 +        if( zErr!=0 ){
 +          utf8_printf(STD_ERR, "  %s\n", zErr);
 +        }else{
 +          utf8_printf(STD_ERR, "Usage: ");
 +          showPrimaryHelp(STD_ERR, azArg[0]);
 +        }
 +      }
 +      rc = 1;
 +      break;
 +    case SHELL_FORBIDDEN_OP:
 +      if( zErr!=0 ){
 +        utf8_printf
 +          (STD_ERR,
 +           "Error: \".%s\" may not %s in --safe mode\n", azArg[0], zErr);
 +        sqlite3_free(zErr);
 +      }else {
 +        utf8_printf(STD_ERR,
 +                    "Error: \".%s\" forbidden in --safe mode\n", azArg[0]);
 +      }
-       exit(1);
++      p->abruptExit = 3;
++      rc = 2;
 +    default:
 +      if( 0!=dispatchResult ) rc = 1;
 +      if( zErr!=0 ){
 +        utf8_printf(STD_ERR, "%s", zErr);
 +        sqlite3_free(zErr);
 +      }
 +    }
    }
  
  meta_command_exit:
      p->outCount--;
      if( p->outCount==0 ) output_reset(p);
    }
-   switch( p->bSafeModeFuture ){
-   default:
-     --p->bSafeModeFuture;
-     /* fall thru */
-   case 0:
-     p->bSafeMode = 0;
-     break;
-   case 1:
-     p->bSafeMode = 1;
-   }
 -  p->bSafeMode = p->bSafeModePersist;
++  updateSafeMode(p);
    return rc;
  }
  
@@@ -11203,7 -10889,7 +11323,8 @@@ static int runOneSqlLine(ShellState *p
  ** is saved only if input is interactive.  An interrupt signal will
  ** cause this routine to exit immediately, unless input is interactive.
  **
--** Return the number of errors.
++** Normally return (number_of_errors > 0),
++** but return SHELL_FORBIDDEN_OP for immediate shell exit.
  */
  static int process_input(ShellState *p){
    char *zLine = 0;          /* A single input line */
    int nLine;                /* Length of current line */
    int nSql = 0;             /* Bytes of zSql[] used */
    int nAlloc = 0;           /* Allocated zSql[] space */
--  int rc;                   /* Error code */
++  int rc = 0;               /* Error or exit code */
    int errCnt = 0;           /* Number of errors seen */
    int startline = 0;        /* Line number for start of current input */
    QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */
  
    p->lineno = 0;
--  while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){
++  while( rc<2
++         &&
++         (errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive)) ){
      fflush(p->out);
      zLine = one_input_line(p->in, zLine, nSql>0);
      if( zLine==0 ){
        continue;
      }
      if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){
 -      if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zLine);
 +      if( ShellHasFlag(p, SHFLG_Echo) ) fprintf(STD_OUT, "%s\n", zLine);
        if( zLine[0]=='.' ){
          rc = do_meta_command(zLine, p);
--        if( rc==2 ){ /* exit requested */
++        if( rc==2 || p->abruptExit!=0 ){ /* exit requested */
            break;
          }else if( rc ){
            errCnt++;
        }else{
          clearTempFile(p);
        }
-       p->bSafeMode = p->bSafeModeFuture!=1;
 -      p->bSafeMode = p->bSafeModePersist;
++      updateSafeMode(p);
        qss = QSS_Start;
      }else if( nSql && QSS_PLAINWHITE(qss) ){
 -      if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zSql);
 +      if( ShellHasFlag(p, SHFLG_Echo) ) fprintf(STD_OUT, "%s\n", zSql);
        nSql = 0;
        qss = QSS_Start;
      }
    }
    if( nSql && QSS_PLAINDARK(qss) ){
      errCnt += runOneSqlLine(p, zSql, p->in, startline);
++    updateSafeMode(p);
    }
    free(zSql);
    free(zLine);
--  return errCnt>0;
++  return (p->abruptExit)? SHELL_FORBIDDEN_OP : errCnt>0;
  }
  
  /*
@@@ -11370,13 -11056,13 +11494,37 @@@ static char *find_home_dir(int clearFla
    return home_dir;
  }
  
++/*
++** Run a single query.
++** 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){
++  char *zErrMsg = 0;
++  int rc;
++  open_db(p, 0);
++  rc = shell_exec(p, zSql, &zErrMsg);
++  if( zErrMsg!=0 || rc>0 ){
++    /* Some kind of error, to result in exit before REPL. */
++    if( zErrMsg!=0 ){
++      utf8_printf(STD_ERR,"Error: %s\n", zErrMsg);
++      free(zErrMsg);
++    }else{
++      utf8_printf(STD_ERR,"Error: unable to process SQL \"%s\"\n", zSql);
++    }
++    p->abruptExit = (rc==0)? 1 : rc;
++    rc = 2;
++  }
++  return rc;
++}
++
++
  /*
  ** Read input from the file given by sqliterc_override.  Or if that
  ** parameter is NULL, take input from ~/.sqliterc
--**
--** Returns the number of errors.
++** The return is similar to process_input() (0 success, 1 error, x abort)
  */
--static void process_sqliterc(
++static int process_sqliterc(
    ShellState *p,                  /* Configuration data */
    const char *sqliterc_override   /* Name of config file. NULL to use default */
  ){
    char *zBuf = 0;
    FILE *inSaved = p->in;
    int savedLineno = p->lineno;
++  int rc;
  
    if (sqliterc == NULL) {
      home_dir = find_home_dir(0);
      if( home_dir==0 ){
 -      raw_printf(stderr, "-- warning: cannot find home directory;"
 +      raw_printf(STD_ERR, "-- warning: cannot find home directory;"
                        " cannot read ~/.sqliterc\n");
--      return;
++      return 1;
      }
      zBuf = sqlite3_mprintf("%s/.sqliterc",home_dir);
      sqliterc = zBuf;
    p->in = fopen(sqliterc,"rb");
    if( p->in ){
      if( stdin_is_interactive ){
 -      utf8_printf(stderr,"-- Loading resources from %s\n",sqliterc);
 +      utf8_printf(STD_ERR,"-- Loading resources from %s\n",sqliterc);
      }
--    if( process_input(p) && bail_on_error ) exit(1);
++    rc = process_input(p);
      fclose(p->in);
    }else if( sqliterc_override!=0 ){
 -    utf8_printf(stderr,"cannot open: \"%s\"\n", sqliterc);
 -    if( bail_on_error ) exit(1);
 +    utf8_printf(STD_ERR,"cannot open: \"%s\"\n", sqliterc);
-     if( bail_on_error ) exit(1);
++    rc = 1;
++  }else{
++    rc = 0;
    }
    p->in = inSaved;
    p->lineno = savedLineno;
    sqlite3_free(zBuf);
++  return rc;
  }
  
  /*
@@@ -11567,21 -11252,13 +11719,20 @@@ static char *cmdline_option_value(int a
  #  endif
  #endif
  
 +#ifndef SHELL_MAIN
 +# if SQLITE_SHELL_IS_UTF8
 +#  define SHELL_MAIN main
 +# else
 +#  define SHELL_MAIN wmain
 +# endif
 +#endif
 +
  #if SQLITE_SHELL_IS_UTF8
 -int SQLITE_CDECL main(int argc, char **argv){
 +int SQLITE_CDECL SHELL_MAIN(int argc, char **argv){
  #else
 -int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
 +int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
    char **argv;
  #endif
--  char *zErrMsg = 0;
    ShellState data;
    const char *zInitFile = 0;
    int i;
    char **argvToFree = 0;
    int argcToFree = 0;
  #endif
--
 -  setBinaryMode(stdin, 0);
 -  setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
 +  setBinaryMode(STD_IN, 0);
 +  setvbuf(STD_ERR, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
    stdin_is_interactive = isatty(0);
    stdout_is_console = isatty(1);
  
    assert( argc>=1 && argv && argv[0] );
    Argv0 = argv[0];
  
--  /* Make sure we have a valid signal handler early, before anything
--  ** else is done.
--  */
--#ifdef SIGINT
--  signal(SIGINT, interrupt_handler);
--#elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE)
--  SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
--#endif
--
  #ifdef SQLITE_SHELL_DBNAME_PROC
    {
      /* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name
    ** and the first command to execute.
    */
    verify_uninitialized();
--  for(i=1; i<argc; i++){
++  for(i=1; i<argc && rc<2; i++){
      char *z;
      z = argv[i];
      if( z[0]!='-' ){
        free(data.zNonce);
        data.zNonce = strdup(argv[++i]);
      }else if( strcmp(z,"-safe")==0 ){
--      /* no-op - catch this on the second pass */
++      /* catch this on the second pass (Unsafe is fine on invocation.) */
      }
    }
    verify_uninitialized();
  
--
  #ifdef SQLITE_SHELL_INIT_PROC
    {
      /* If the SQLITE_SHELL_INIT_PROC macro is defined, then it is the name
    sqlite3_initialize();
  #endif
  
++  /* Register the control-C (SIGINT) handler.
++  ** Make sure we have a valid signal handler early, before anything
++  ** is done that might take long. */
++  pGlobalDbLock = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
++#ifdef SIGINT
++  signal(SIGINT, interrupt_handler);
++#elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE)
++  SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
++#endif
++
    if( zVfs ){
      sqlite3_vfs *pVfs = sqlite3_vfs_find(zVfs);
      if( pVfs ){
        sqlite3_vfs_register(pVfs, 1);
      }else{
 -      utf8_printf(stderr, "no such VFS: \"%s\"\n", argv[i]);
 -      exit(1);
 +      utf8_printf(STD_ERR, "no such VFS: \"%s\"\n", argv[i]);
-       exit(1);
++      rc = 1;
++      goto shell_bail;
      }
    }
  
      data.pAuxDb->zDbFilename = ":memory:";
      warnInmemoryDb = argc==1;
  #else
 -    utf8_printf(stderr,"%s: Error: no database filename specified\n", Argv0);
 -    return 1;
 +    utf8_printf(STD_ERR,"%s: Error: no database filename specified\n", Argv0);
-     return 1;
++    rc = 1;
++    goto shell_bail;
  #endif
    }
 -  data.out = stdout;
 +  data.out = STD_OUT;
    sqlite3_appendvfs_init(0,0,0);
  
    /* Go ahead and open the database file if it already exists.  If the
    ** is given on the command line, look for a file named ~/.sqliterc and
    ** try to process it.
    */
--  process_sqliterc(&data,zInitFile);
++  rc = process_sqliterc(&data,zInitFile);
++  if( rc>1 || (rc!=0 && bail_on_error) ){
++    goto shell_bail;
++  }
  
    /* Make a second pass through the command-line argument and set
    ** options.  This second pass is delayed until after the initialization
    ** file is processed so that the command-line arguments will override
    ** settings in the initialization file.
    */
--  for(i=1; i<argc; i++){
++  for(i=1; i<argc && rc<2; i++){
      char *z = argv[i];
      if( z[0]!='-' ) continue;
      if( z[1]=='-' ){ z++; }
      }else if( strcmp(z,"-bail")==0 ){
        /* No-op.  The bail_on_error flag should already be set. */
      }else if( strcmp(z,"-version")==0 ){
 -      printf("%s %s\n", sqlite3_libversion(), sqlite3_sourceid());
 -      return 0;
 +      fprintf(STD_OUT, "%s %s\n", sqlite3_libversion(), sqlite3_sourceid());
-       return 0;
++      rc = 2;
      }else if( strcmp(z,"-interactive")==0 ){
        stdin_is_interactive = 1;
      }else if( strcmp(z,"-batch")==0 ){
        z = cmdline_option_value(argc,argv,++i);
        if( z[0]=='.' ){
          rc = do_meta_command(z, &data);
--        if( rc && bail_on_error ) return rc==2 ? 0 : rc;
--      }else{
--        open_db(&data, 0);
--        rc = shell_exec(&data, z, &zErrMsg);
--        if( zErrMsg!=0 ){
-           utf8_printf(STD_ERR,"Error: %s\n", zErrMsg);
 -          utf8_printf(stderr,"Error: %s\n", zErrMsg);
--          if( bail_on_error ) return rc!=0 ? rc : 1;
--        }else if( rc!=0 ){
-           utf8_printf(STD_ERR,"Error: unable to process SQL \"%s\"\n", z);
 -          utf8_printf(stderr,"Error: unable to process SQL \"%s\"\n", z);
--          if( bail_on_error ) return rc;
++        if( rc && bail_on_error ){
++          data.abruptExit = rc;
++          rc = 2;
++          goto shell_bail;
          }
++      }else{
++        rc = run_single_query(&data, z);
        }
  #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
      }else if( strncmp(z, "-A", 2)==0 ){
        if( nCmd>0 ){
 -        utf8_printf(stderr, "Error: cannot mix regular SQL or dot-commands"
 +        utf8_printf(STD_ERR, "Error: cannot mix regular SQL or dot-commands"
                              " with \"%s\"\n", z);
--        return 1;
++        rc = 1;
++        goto shell_bail;
        }
        open_db(&data, OPEN_DB_ZIPFILE);
        if( z[2] ){
          argv[i] = &z[2];
--        arDotCommand(&data, 1, argv+(i-1), argc-(i-1));
++        rc = arDotCommand(&data, 1, argv+(i-1), argc-(i-1));
        }else{
--        arDotCommand(&data, 1, argv+i, argc-i);
++        rc = arDotCommand(&data, 1, argv+i, argc-i);
        }
        readStdin = 0;
        break;
  #endif
      }else if( strcmp(z,"-safe")==0 ){
 -      data.bSafeMode = data.bSafeModePersist = 1;
 +      data.bSafeMode = data.bSafeModeFuture = 1;
      }else{
 -      utf8_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z);
 -      raw_printf(stderr,"Use -help for a list of options.\n");
 -      return 1;
 +      utf8_printf(STD_ERR,"%s: Error: unknown option: %s\n", Argv0, z);
 +      raw_printf(STD_ERR,"Use -help for a list of options.\n");
-       return 1;
++      rc = 2;
      }
      data.cMode = data.mode;
    }
      ** command-line inputs, except for the argToSkip argument which contains
      ** the database filename.
      */
--    for(i=0; i<nCmd; i++){
++    for(i=0; i<nCmd && rc<2; i++){
        if( azCmd[i][0]=='.' ){
          rc = do_meta_command(azCmd[i], &data);
--        if( rc ){
--          free(azCmd);
--          return rc==2 ? 0 : rc;
++        if( rc && bail_on_error ){
++          goto shell_bail;
          }
        }else{
--        open_db(&data, 0);
--        rc = shell_exec(&data, azCmd[i], &zErrMsg);
--        if( zErrMsg || rc ){
--          if( zErrMsg!=0 ){
-             utf8_printf(STD_ERR,"Error: %s\n", zErrMsg);
 -            utf8_printf(stderr,"Error: %s\n", zErrMsg);
--          }else{
-             utf8_printf(STD_ERR,"Error: unable to process SQL: %s\n", azCmd[i]);
 -            utf8_printf(stderr,"Error: unable to process SQL: %s\n", azCmd[i]);
--          }
--          sqlite3_free(zErrMsg);
--          free(azCmd);
--          return rc!=0 ? rc : 1;
--        }
++        rc = run_single_query(&data, azCmd[i]);
        }
      }
    }else{
        rc = process_input(&data);
      }
    }
--  free(azCmd);
++ shell_bail:
    set_table_name(&data, 0);
    if( data.db ){
      session_close_all(&data, -1);
      close_db(data.db);
    }
++  sqlite3_mutex_free(pGlobalDbLock);
++  pGlobalDbLock = 0;
    for(i=0; i<ArraySize(data.aAuxDb); i++){
      sqlite3_free(data.aAuxDb[i].zFreeOnClose);
      if( data.aAuxDb[i].db ){
  #endif
    free(data.colWidth);
    free(data.zNonce);
--  /* Clear the global data structure so that valgrind will detect memory
--  ** leaks */
++  /* Freed ShellState objects so that valgrind detects real memory leaks. */
++  free(azCmd);
++  /* 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.
++   */
++  /* 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);
++  }else{
++    /* rc is one of 0,1,2, mapping to 0,1,0 shellexit codes. */
++    rc &= ~1;
++  }
    memset(&data, 0, sizeof(data));
    return rc;
  }
index 4107cd412e662c51176b193a1d3345a4dc14a0d7,0000000000000000000000000000000000000000..d8f832595bec2a0425996ee63a6d7124489c6b58
mode 100644,000000..100644
--- /dev/null
@@@ -1,219 -1,0 +1,224 @@@
-    * freeable using sqlite3_free(). It is otherwise unconstrained.
-    */
 +#ifndef SQLITE3SHX_H
 +#define SQLITE3SHX_H
 +#include "sqlite3ext.h"
 +
 +#include "obj_interfaces.h"
 +
 +#ifdef __cplusplus
 +extern "C" {
 +#endif
 +
 +/* Convey data to, from and/or between I/O handlers and meta-commands. */
 +typedef struct {
 +  /* 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
-   /* Number of lines written during a query result output */
-   int resultCount;
++   * 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 */
 +  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;
++
 +  /* 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".
 +   */
 +  int shellExit;
++
++  /* 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 */
 +  char *zFieldSeparator;
 +  /* Row separator character for some modes (MODE_Ascii) */
 +  char *zRecordSeparator;
 +  /* Row set prefix for some modes */
 +  char *zRecordLead;
 +  /* Row set suffix for some modes */
 +  char *zRecordTrail;
 +  /* Text to represent a NULL in external data formats */
 +  char *zNullValue;
 +  /* Number of column widths presently desired or tracked */
 +  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  *pHaveWidths;
 +} 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.
 + */
 +typedef int (*ExtensionId)
 +  (sqlite3 *, char **, const struct sqlite3_api_routines *);
 +
 +/*****************
 + * See "Shell Extensions, Programming" for purposes and usage of the following
 + * interfaces supporting extended meta-commands and import and output modes.
 + */
 +
 +/* An object implementing below interface is registered with the
 + * shell to make new or overriding meta-commands available to it.
 + */
 +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, execute, MetaCommand,
 +             4,(ShellStateX *, char **pzErrMsg, int nArgs, char *azArgs[]));
 +INTERFACE_END( MetaCommand );
 +
 +/* Define error codes to be returned either by a meta-command during
 + * its own checking or by the dispatcher for bad argument counts.
 + */
 +#define SHELL_INVALID_ARGS SQLITE_MISUSE
 +#define SHELL_FORBIDDEN_OP 0x7ffe /* Action disallowed under --safe.*/
 +
 +/* An object implementing below interface is registered with the
 + * shell to make new or overriding output modes available to it.
 + */
 +INTERFACE_BEGIN( OutModeHandler );
 +PURE_VMETHOD(const char *, name, OutModeHandler, 0,());
 +PURE_VMETHOD(const char *, help, OutModeHandler, 1,(int more));
 +PURE_VMETHOD(int, openResultsOutStream, OutModeHandler,
 +             5,( ShellExState *pSES, char **pzErr,
 +                 int numArgs, char *azArgs[], const char * zName ));
 +PURE_VMETHOD(int, prependResultsOut, OutModeHandler,
 +             3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt ));
 +PURE_VMETHOD(int, rowResultsOut, OutModeHandler,
 +             3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt ));
 +PURE_VMETHOD(int, appendResultsOut, OutModeHandler,
 +             3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt ));
 +PURE_VMETHOD(void, closeResultsOutStream, OutModeHandler,
 +             2,( ShellExState *pSES, char **pzErr ));
 +INTERFACE_END( OutModeHandlerVtable );
 +
 +/* An object implementing below interface is registered with the
 + * shell to make new or overriding data importers available to it.
 + */
 +INTERFACE_BEGIN( ImportHandler );
 +PURE_VMETHOD(const char *, name, ImportHandler, 0,());
 +PURE_VMETHOD(const char *, help, ImportHandler, 1,( int more ));
 +PURE_VMETHOD(int,  openDataInStream, ImportHandler,
 +             5,( ShellExState *pSES, char **pzErr,
 +                 int numArgs, char *azArgs[], const char * zName ));
 +PURE_VMETHOD(int, prepareDataInput, ImportHandler,
 +             3,( ShellExState *pSES, char **pzErr, sqlite3_stmt * *ppStmt ));
 +PURE_VMETHOD(int, rowDataInput, ImportHandler,
 +             3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt ));
 +PURE_VMETHOD(int, finishDataInput, ImportHandler,
 +             3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt ));
 +PURE_VMETHOD(void, closeDataInStream, ImportHandler,
 +             2,( ShellExState *pSES, char **pzErr ));
 +INTERFACE_END( ImportHandlerVtable );
 +
 +typedef struct {
 +  int helperCount;
 +  union ExtHelp {
 +    struct {
 +      void (*failIfSafeMode)(ShellStateX *p, const char *zErrMsg, ...);
 +    } named ;
 +    void (*nameless[2])(); /* Same as named but anonymous plus a sentinel. */
 +  } helpers;
 +} ExtensionHelpers;
 +
 +#define SHELLEXT_VALIDITY_MARK "ExtensibleShell"
 +
 +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 */
 +
 +  /* 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. 
 +   */
 +  ExtensionId eid;
 +
 +  /* Another init "out" parameter, a destructor for extension overall.
 +   * Set to 0 on input and may be left so if no destructor is needed.
 +   */
 +  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. 
 + * 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.
 + */
 +#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.
 + */
 +#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) \
 +      && SQLITE_ROW == sqlite3_step(pStmt) ) \
 +    rv = (ShellExtensionLink *)sqlite3_value_pointer \
 +     (sqlite3_column_value(pStmt, 0), SHELLEXT_API_POINTERS); \
 +  sqlite3_finalize(pStmt); return rv; \
 + }
 +
 +#ifdef __cplusplus
 +} // extern "C"
 +#endif
 +
 +#endif /* !defined(SQLITE3SHX_H) */
index aaf03ce43e0d3cc4a1a82435287969ed14011892,c4e2ceb88b01e254e1296fc3fda755f9e27a0093..4fbde2976a5b4d4fe46d2af0a7627226f2b789a5
@@@ -74,7 -74,7 +74,7 @@@ do_test shell1-1.3.1 
    catchcmd "-init FOO test.db" ""
  } {0 {}}
  do_test shell1-1.3.2 {
--  catchcmd "-init FOO test.db .quit BAD" ""
++  catchcmdex "-init FOO test.db .quit BAD" ""
  } {0 {}}
  do_test shell1-1.3.3 {
    catchcmd "-init FOO test.db BAD .quit" ""
@@@ -182,16 -182,13 +182,16 @@@ do_test shell1-1.16.1 
  # check first token handling
  do_test shell1-2.1.1 {
    catchcmd "test.db" ".foo"
 -} {1 {Error: unknown command or invalid arguments:  "foo". Enter ".help" for help}}
 +} {1 {Error: unknown command: "foo"}}
  do_test shell1-2.1.2 {
    catchcmd "test.db" ".\"foo OFF\""
 -} {1 {Error: unknown command or invalid arguments:  "foo OFF". Enter ".help" for help}}
 +} {1 {Error: unknown command: "foo OFF"}}
  do_test shell1-2.1.3 {
    catchcmd "test.db" ".\'foo OFF\'"
 -} {1 {Error: unknown command or invalid arguments:  "foo OFF". Enter ".help" for help}}
 +} {1 {Error: unknown command: "foo OFF"}}
 +
- set modeShouldBe "Error: mode should be one of: ascii box column csv html
-  insert json line list markdown quote table tabs tcl"
++set modeShouldBe "Error: mode should be one of: ascii box column count csv html
++ insert json line list markdown off quote table tabs tcl"
  
  # unbalanced quotes
  do_test shell1-2.2.1 {
@@@ -405,8 -403,7 +405,7 @@@ do_test shell1-3.11.2 
  do_test shell1-3.11.3 {
    # too many arguments
    catchcmd "test.db" ".import FOO BAR BAD"
- } {1 {Error: invalid arguments for ".import"
-        surplus argument: "BAD"}}
 -} {/1 .ERROR: extra argument: "BAD".*./}
++} {1 {Error: invalid arguments for ".import"}}
  
  # .indexes ?TABLE?       Show names of all indexes
  #                          If TABLE specified, only show indexes for tables
@@@ -503,8 -500,14 +502,7 @@@ do_test shell1-3.15.2 
  do_test shell1-3.15.3 {
    # too many arguments
    catchcmd "test.db" ".output FOO BAD"
- } {1 {Error: invalid arguments for ".output"
-        excess argument: "BAD"}}
 -} {1 {ERROR: extra parameter: "BAD".  Usage:
 -.output ?FILE?           Send output to FILE or stdout if FILE is omitted
 -   If FILE begins with '|' then open it as a pipe.
 -   Options:
 -     --bom                 Prefix output with a UTF8 byte-order mark
 -     -e                    Send output to the system text editor
 -     -x                    Send output as CSV to a spreadsheet
 -child process exited abnormally}}
++} {1 {Error: invalid arguments for ".output"}}
  
  # .output stdout         Send output to the screen
  do_test shell1-3.16.1 {
  do_test shell1-3.16.2 {
    # too many arguments
    catchcmd "test.db" ".output stdout BAD"
- } {1 {Error: invalid arguments for ".output"
-        excess argument: "BAD"}}
 -} {1 {ERROR: extra parameter: "BAD".  Usage:
 -.output ?FILE?           Send output to FILE or stdout if FILE is omitted
 -   If FILE begins with '|' then open it as a pipe.
 -   Options:
 -     --bom                 Prefix output with a UTF8 byte-order mark
 -     -e                    Send output to the system text editor
 -     -x                    Send output as CSV to a spreadsheet
 -child process exited abnormally}}
++} {1 {Error: invalid arguments for ".output"}}
  
  # .prompt MAIN CONTINUE  Replace the standard prompts
  do_test shell1-3.17.1 {