]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Sync with trunk
authorlarrybr <larrybr@noemail.net>
Tue, 15 Feb 2022 17:04:37 +0000 (17:04 +0000)
committerlarrybr <larrybr@noemail.net>
Tue, 15 Feb 2022 17:04:37 +0000 (17:04 +0000)
FossilOrigin-Name: 2b4a295c58a908288840157c3e08289af8c1d287f72e5f14b7adfd98065bf241

1  2 
manifest
manifest.uuid
src/shell.c.in
test/shell3.test
test/shell9.test

diff --cc manifest
index 33ec5c364d64c50febfea0c09c2d03660f046273,220535e5db7139e3fe1ac0d1e4956bfa7b4bab80..9aeb5cef0644b291457b9b5244bf5381bbebda9f
+++ b/manifest
@@@ -1,5 -1,5 +1,5 @@@
- C CLI\sextended\sparsing\sand\s.x\sfeatures\smostly\stested
- D 2022-02-06T14:30:16.552
 -C Improved\srendering\sof\sfloating\spoint\snumbers\swithout\sa\sfractional\spart\sin\n".dump"\soutput\sfrom\sthe\sCLI.\n[forum:/forumpost/550d877659f37cb2|Forum\spost\s550d877659f37cb2].
 -D 2022-02-15T13:23:09.386
++C Sync\swith\strunk
++D 2022-02-15T17:04:37.699
  F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
  F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
  F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@@ -548,16 -548,16 +548,16 @@@ F src/pcache1.c 54881292a9a5db202b2c0ac
  F src/pragma.c 7c024d690a3dc93f61830f11f900e4af2357f31d081b0c79099ca5e28919cba7
  F src/pragma.h 87330ed2fbfa2a1274de93ca0ab850fba336189228cb256089202c3b52766fad
  F src/prepare.c a187dade741c1f09ae118fcbbf0302511807bfc0355880927d7152eb75b8260d
- F src/printf.c ceadf2ee3ad2f4bb22d4557611b514f51be3ac0ce2b6ebd1d17aed26ce023dc9
+ F src/printf.c 05d8dfd2018bc4fc3ddb8b37eb97ccef7abf985643fa1caebdcf2916ca90fa32
  F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
- F src/resolve.c 868a88d47b11f4f6b8b413db72c9290c211972ee91316c1ece318846600ff725
+ F src/resolve.c ea935b87d6fb36c78b70cdc7b28561dc8f33f2ef37048389549c7b5ef9b0ba5e
  F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
  F src/select.c 3baa9dd8cf240654773c7974e2bcce398ac9dd24419c36684156963defe43b35
- F src/shell.c.in 683c927a205999a3a13b268762de0309427d2f474ac1bd44d97c22b8064b1128 x
- F src/sqlite.h.in 800d6509517d383d38188e71bb5f1802c7db097a1282af7bf5d6c9a6da5a15ed
 -F src/shell.c.in b5b44c2ebfd3942e60dbcc47b94a74337a482a5f1c3766fbfb9f578a605ecf50
++F src/shell.c.in c87d57f80663efb5aff3935bda7e6264d25636f41108a58edca9b628d9bc3b02
+ F src/sqlite.h.in 7047c4b60fa550264d6363bb1d983540e7828fb19d2d1e5aa43b52ca13144807
  F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
  F src/sqlite3ext.h a95cb9ed106e3d39e2118e4dcc15a14faec3fa50d0093425083d340d9dfd96e6
- F src/sqliteInt.h b6619030ed13b2a8d49c0b5cb0525db1f727966b65ab1ec40b5f11102af7254d
+ F src/sqliteInt.h f8814239fb1f95056555e2d7fa475750e64681cac4221fb03610d1fde0b79d53
  F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657
  F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1
  F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
@@@ -1387,15 -1388,14 +1388,15 @@@ 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 ce2f370886645f38fabdde44976c14a004400f166edea8fdd9741079b645fef6
- F test/shell2.test f00a0501c00583cbc46f7510e1d713366326b2b3e63d06d15937284171a8787c
- F test/shell3.test c073c2adda6d1aabe4450024ade4d45607f34d2fb76c2551cc89e70871191a3c
+ F test/shell1.test b224e0793c5f48aa3749e65d8c64b93a30731bd206f2e41e6c5f1bee1bdb16c6
+ F test/shell2.test 89e4b2db062d52baed75022227b462d085cff495809de1699652779d8e0257d6
 -F test/shell3.test a50628ab1d78d90889d9d3f32fb2c084ee15674771e96afe954aaa0accd1de3c
 -F test/shell4.test 8f6c0fce4abed19a8a7f7262517149812a04caa905d01bdc8f5e92573504b759
++F test/shell3.test 4ddea2bd182e7e03249911b23ae249e7cb8a91cdc86e695198725affabe8ecd3
 +F test/shell4.test 867e0675d7b096d6b93de534541e07c7f5ffafa3e6612695ecf55180802e1115
- F test/shell5.test b85069bfcf3159b225228629ab2c3e69aa923d098fea8ea074b5dcd743522e2c
+ F test/shell5.test 3be444397eb1e91619ce289a6216a8df9ac690cc45d5e9595f60e750a944161f
  F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3
  F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f
  F test/shell8.test 388471d16e4de767333107e30653983f186232c0e863f4490bb230419e830aae
- F test/shell9.test 7fbfb85a2a9c2ea29d96cfa9ae2a116ecba7b0261ee366ed0d650c4c9054ba5c
++F test/shell9.test b6f07789fef57b5d194a3b2b8aba8292d363f23273cdc4541de7626ea659bc91
  F test/shmlock.test 3dbf017d34ab0c60abe6a44e447d3552154bd0c87b41eaf5ceacd408dd13fda5
  F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3
  F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5
@@@ -1944,8 -1944,8 +1945,8 @@@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a9
  F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
  F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
  F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
- P 2e72dc4621bcccd6ef4f01d059ff052bc39b8ea122b006d842be6102736460f4
- R 25fb77e100f746489d50fee668ec40ea
 -P d74ec88c2f9b9e056988add8322186750229e14a609d1a6969ba393a5b8c5174
 -R d44a4dfcecd9d59ae3b06dca8b590f27
 -U drh
 -Z 28d22f7919d651ae0bfc81a1c20dfd1c
++P 9c664984fd61d5858b436952d876bf3560333ee0edd8e1956cfe74cf9649511d 9edaeed56f2282fd4da935454178c38ab49d259aed96d4e720aae09050a53006
++R 241a1161ccd2087a62b9ac8bd7212d06
 +U larrybr
- Z 7b3a3cb6deab772402e5b78d5f0789c5
++Z bc8dcf303171aacc369bf09b47a22c1e
  # Remove this line to create a well-formed Fossil manifest.
diff --cc manifest.uuid
index 80ab8ac2b814f0b04595453a1afda462c47420b9,cf24b67f55d60373e3186fdcf36b36a90e108da1..92ab286c2ce015d807b965ff635e16e2809c6449
@@@ -1,1 -1,1 +1,1 @@@
- 9c664984fd61d5858b436952d876bf3560333ee0edd8e1956cfe74cf9649511d
 -9edaeed56f2282fd4da935454178c38ab49d259aed96d4e720aae09050a53006
++2b4a295c58a908288840157c3e08289af8c1d287f72e5f14b7adfd98065bf241
diff --cc src/shell.c.in
index feee1211c6789fbee2160ec2c8583ec0873de65e,3ff1f62c72da3e63a1c7b740e436250fee244a1a..35b5d75c8f4390f79993fb473b98b26848f559be
mode 100755,100644..100644
  #define _CRT_SECURE_NO_WARNINGS
  #endif
  
 +/* Define stringification of a bare word (or anything without commas.) */
 +# define SHELL_STRINGIFY_(f) #f
 +# define SHELL_STRINGIFY(f) SHELL_STRINGIFY_(f)
 +
  /*
  ** Optionally #include a user-defined header, whereby compilation options
--** may be set prior to where they take effect, but after platform setup. 
++** may be set prior to where they take effect, but after platform setup.
  ** If SQLITE_CUSTOM_INCLUDE=? is defined, its value names the #include
  ** file. Note that this macro has a like effect on sqlite3.c compilation.
  */
@@@ -1195,22 -1075,6 +1197,22 @@@ typedef struct ColModeOpts 
  #define ColModeOpts_default { 60, 0, 0 }
  #define ColModeOpts_default_qbox { 60, 1, 0 }
  
- ** Accepted by: 
 +/*
 +** Stored output mode state, for partial save and later restore.
 +** Returned by:
 +**   outputModeSave *outputModeSave(ShellState *p, SaveModeWhat ws).
++** Accepted by:
 +**   outputModeRestore(ShellState *p, OutputModeSave *pSaved).
 +** See enum SaveWhatMode regarding what to save and restore.
 +** Also see outputModePush(...), outputModePushSome(...) and
 +** outputModePop(...) for usages spanning more than one call.
 +*/
 +typedef struct OutputModeSave{
 +  u16 what;           /* Set upon creation. See SaveWhatMode for values. */
 +  char itsValues[1];  /* This size is inaccurate unless nothing is saved. */
 +} OutputModeSave;
 +#define MODE_STACK_MAX 3 /* How many levels of saved output mode to allow. */
 +
  /*
  ** State information about the database connection is contained in an
  ** instance of the following structure.
@@@ -1584,7 -1444,7 +1586,7 @@@ static void editFunc
        }
        sz = j;
        p[sz] = 0;
--    } 
++    }
      sqlite3_result_text64(context, (const char*)p, sz,
                            sqlite3_free, SQLITE_UTF8);
    }
@@@ -1599,97 -1459,19 +1601,97 @@@ edit_func_end
  #endif /* SQLITE_NOHAVE_SYSTEM */
  
  /*
 -** Save or restore the current output mode
 +** Save or restore the current output mode, partially per spec. (OM_STATE)
 +*/
 +typedef enum {
 +  SWM_showHeader = 1, SWM_shellFlags = 2, SWM_mode = 4, SWM_cmOpts = 8,
 +  SWM_colSeparator = 0x10, SWM_rowSeparator = 0x20, SWM_everything = 0x3F,
 +  SWM_CountOf = 6
 +} SaveWhatMode;
 +
 +/* This is available in most C89+ C compilers as offsetof(...), but since we
 + * cater to the most arcane C89-like compilers around, define it for sure:
 + */
 +#define MEMBER_OFFSET(stype, member) ((size_t)&(((stype*)0)->member))
 +#define MEMBER_SIZEOF(stype, member) (sizeof(((stype*)0)->member))
 +static struct {
 +  size_t offset;
 +  size_t size;
 +} outputModeCopy[] = {
 +#define SS_MEMBER_COPY(mn) \
 +  { MEMBER_OFFSET(ShellState,mn), MEMBER_SIZEOF(ShellState,mn) }
 +  SS_MEMBER_COPY(showHeader), SS_MEMBER_COPY(shellFlgs),
 +  SS_MEMBER_COPY(mode), SS_MEMBER_COPY(cmOpts),
 +  SS_MEMBER_COPY(colSeparator), SS_MEMBER_COPY(rowSeparator)
 +#undef SS_MEMBER_COPY
 +};
 +
 +/* Allocate a buffer, copy requested output mode data to it, and return it. */
 +static OutputModeSave *outputModeSave(ShellState *p, SaveWhatMode w){
 +  u16 what = (u16)w;
 +  int i, nAlloc = sizeof(what)+1, mask = 1;
 +  char *pSaved = 0, *pFill;
 +  for( i=0; i<SWM_CountOf; mask<<=1, ++i ){
 +    if( (what & mask)!=0 ) nAlloc += (int)outputModeCopy[i].size;
 +  }
 +  assert(i==ArraySize(outputModeCopy));
 +  pSaved = sqlite3_malloc(nAlloc);
 +  if( pSaved==0 ) return 0;
 +  *(u16 *)pSaved = what;
 +  pFill = pSaved + sizeof(what);
 +  for( mask=1, i=0; i<SWM_CountOf; mask<<=1, ++i ){
 +    if( (what & mask)!=0 ){
 +      size_t nb = outputModeCopy[i].size;
 +      memcpy(pFill, (char*)p+outputModeCopy[i].offset, nb);
 +      pFill += nb;
 +    }
 +  }
 +  *pFill = 0xA5;
 +  return (OutputModeSave *)pSaved;
 +}
 +
 +/* From a buffer returned by outputModeSave, restore output mode data.
-  * The buffer is freed and its pointer is invalidated. 
++ * The buffer is freed and its pointer is invalidated.
 + * If called with some other buffer, results are undefined, likely bad.
 + */
 +static void outputModeRestore(ShellState *p, OutputModeSave *pSaved){
 +  sqlite3_uint64 nA = sqlite3_msize(pSaved);
 +  u16 what = (nA>sizeof(what))? *((u16 *)pSaved) : 0;
 +  int i, nAlloc = sizeof(what)+1, mask = 1;
 +  char *pTake = (char *)pSaved + sizeof(what);
 +  for( i=0; i<SWM_CountOf && nAlloc<nA; mask<<=1, ++i ){
 +    if( (what & mask)!=0 ){
 +      size_t nb = outputModeCopy[i].size;
 +      memcpy((char*)p+outputModeCopy[i].offset, pTake, nb);
 +      pTake += nb;
 +    }
 +  }
 +  assert(*pTake==0xA5);
 +  sqlite3_free(pSaved);
 +}
 +
 +/*
 +** Save or restore the current output mode, in whole or in part.
  */
 +static void outputModePushSome(ShellState *p, SaveWhatMode w){
 +  OutputModeSave *pOMS;
 +  assert(p->nSavedModes<MODE_STACK_MAX); /* Fail hard for this logic error. */
 +  if( p->nSavedModes>=MODE_STACK_MAX ) return;
 +  pOMS = outputModeSave(p, w);
 +  shell_check_oom(pOMS);
 +  p->pModeStack[p->nSavedModes++] = pOMS;
 +}
  static void outputModePush(ShellState *p){
 -  p->modePrior = p->mode;
 -  p->priorShFlgs = p->shellFlgs;
 -  memcpy(p->colSepPrior, p->colSeparator, sizeof(p->colSeparator));
 -  memcpy(p->rowSepPrior, p->rowSeparator, sizeof(p->rowSeparator));
 +  outputModePushSome(p, SWM_everything);
  }
  static void outputModePop(ShellState *p){
 -  p->mode = p->modePrior;
 -  p->shellFlgs = p->priorShFlgs;
 -  memcpy(p->colSeparator, p->colSepPrior, sizeof(p->colSeparator));
 -  memcpy(p->rowSeparator, p->rowSepPrior, sizeof(p->rowSeparator));
 +  OutputModeSave *pOMS;
 +  assert(p->nSavedModes>0); /* Should not be here unless something pushed. */
 +  if( p->nSavedModes==0 ) return;
 +  pOMS = p->pModeStack[--p->nSavedModes];
 +  assert(pOMS!=0);
 +  p->pModeStack[p->nSavedModes] = 0;
 +  outputModeRestore(p, pOMS);
  }
  
  /*
@@@ -3416,7 -3151,7 +3430,7 @@@ static void bind_prepared_stmt(sqlite3 
  ** characters
  */
  static void print_box_line(FILE *out, int N){
--  const char zDash[] = 
++  const char zDash[] =
        BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24
        BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24;
    const int nDash = sizeof(zDash) - 1;
@@@ -3545,7 -3280,7 +3559,7 @@@ static char *translateForDisplayAndDup
      break;
    }
    zOut[j] = 0;
--  return (char*)zOut;  
++  return (char*)zOut;
  }
  
  /* Extract the value of the i-th current column for pStmt as an SQL literal
@@@ -3899,8 -3634,8 +3913,8 @@@ static void exec_prepared_stmt
  ** caller to eventually free this buffer using sqlite3_free().
  */
  static int expertHandleSQL(
--  ShellState *pState, 
--  const char *zSql, 
++  ShellState *pState,
++  const char *zSql,
    char **pzErr
  ){
    assert( pState->expert.pExpert );
  
  /*
  ** This function is called either to silently clean up the object
--** created by the ".expert" command (if bCancel==1), or to generate a 
++** created by the ".expert" command (if bCancel==1), or to generate a
  ** report from it and then clean it up (if bCancel==0).
  **
  ** If successful, SQLITE_OK is returned. Otherwise, an SQLite error
@@@ -4174,9 -3903,10 +4188,9 @@@ static int shell_exec
        rc2 = sqlite3_finalize(pStmt);
        if( rc!=SQLITE_NOMEM ) rc = rc2;
        if( rc==SQLITE_OK ){
 -        zSql = zLeftover;
 -        while( IsSpace(zSql[0]) ) zSql++;
 +        zSql = skipWhite(zLeftover);
        }else if( pzErrMsg ){
-         *pzErrMsg = save_err_msg(db, "stepping, %s (%d)", rc, 0);
+         *pzErrMsg = save_err_msg(db, "stepping", rc, 0);
        }
  
        /* clear saved stmt handle */
@@@ -4997,7 -4672,7 +5011,7 @@@ u8 deduceDatabaseType(const char *zName
      }
    }
    fclose(f);
--  return rc;  
++  return rc;
  }
  
  #ifndef SQLITE_OMIT_DESERIALIZE
@@@ -5098,8 -4771,8 +5112,8 @@@ static unsigned char *readHexDb(ShellSt
  ** offset (4*<arg2>) of the blob.
  */
  static void shellInt32(
--  sqlite3_context *context, 
--  int argc, 
++  sqlite3_context *context,
++  int argc,
    sqlite3_value **argv
  ){
    const unsigned char *pBlob;
  ** using "..." with internal double-quote characters doubled.
  */
  static void shellIdQuote(
--  sqlite3_context *context, 
--  int argc, 
++  sqlite3_context *context,
++  int argc,
    sqlite3_value **argv
  ){
    const char *zName = (const char*)sqlite3_value_text(argv[0]);
  ** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X.
  */
  static void shellUSleepFunc(
--  sqlite3_context *context, 
--  int argcUnused, 
++  sqlite3_context *context,
++  int argcUnused,
    sqlite3_value **argv
  ){
    int sleep = sqlite3_value_int(argv[0]);
  /*
  ** Scalar function "shell_escape_crnl" used by the .recover command.
  ** The argument passed to this function is the output of built-in
--** function quote(). If the first character of the input is "'", 
++** function quote(). If the first character of the input is "'",
  ** indicating that the value passed to quote() was a text value,
  ** then this function searches the input for "\n" and "\r" characters
  ** and adds a wrapper similar to the following:
  ** of the input is returned.
  */
  static void shellEscapeCrnl(
--  sqlite3_context *context, 
--  int argc, 
++  sqlite3_context *context,
++  int argc,
    sqlite3_value **argv
  ){
    const char *zText = (const char*)sqlite3_value_text(argv[0]);
@@@ -5273,7 -4946,7 +5287,7 @@@ static void open_db(ShellState *p, int 
      }
      switch( p->openMode ){
        case SHELL_OPEN_APPENDVFS: {
--        sqlite3_open_v2(zDbFilename, &p->db, 
++        sqlite3_open_v2(zDbFilename, &p->db,
             SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, "apndvfs");
          break;
        }
@@@ -5392,7 -5065,7 +5406,7 @@@ void close_db(sqlite3 *db)
    if( rc ){
      utf8_printf(stderr, "Error: sqlite3_close() returns %d: %s\n",
          rc, sqlite3_errmsg(db));
--  } 
++  }
  }
  
  #if HAVE_READLINE || HAVE_EDITLINE
@@@ -6654,16 -6303,16 +6668,16 @@@ static int lintDotCommand
  
  #if !defined SQLITE_OMIT_VIRTUALTABLE
  static void shellPrepare(
--  sqlite3 *db, 
--  int *pRc, 
--  const char *zSql, 
++  sqlite3 *db,
++  int *pRc,
++  const char *zSql,
    sqlite3_stmt **ppStmt
  ){
    *ppStmt = 0;
    if( *pRc==SQLITE_OK ){
      int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0);
      if( rc!=SQLITE_OK ){
--      raw_printf(stderr, "sql error: %s (%d)\n", 
++      raw_printf(stderr, "sql error: %s (%d)\n",
            sqlite3_errmsg(db), sqlite3_errcode(db)
        );
        *pRc = rc;
  ** nuisance compiler warnings about "defined but not used".
  */
  void shellPreparePrintf(
--  sqlite3 *db, 
--  int *pRc, 
++  sqlite3 *db,
++  int *pRc,
    sqlite3_stmt **ppStmt,
--  const char *zFmt, 
++  const char *zFmt,
    ...
  ){
    *ppStmt = 0;
  ** nuisance compiler warnings about "defined but not used".
  */
  void shellFinalize(
--  int *pRc, 
++  int *pRc,
    sqlite3_stmt *pStmt
  ){
    if( pStmt ){
  ** nuisance compiler warnings about "defined but not used".
  */
  void shellReset(
--  int *pRc, 
++  int *pRc,
    sqlite3_stmt *pStmt
  ){
    int rc = sqlite3_reset(pStmt);
@@@ -6778,7 -6427,7 +6792,7 @@@ static int arUsage(FILE *f)
  }
  
  /*
--** Print an error message for the .ar command to stderr and return 
++** Print an error message for the .ar command to stderr and return
  ** SQLITE_ERROR.
  */
  static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){
@@@ -6859,7 -6508,7 +6873,7 @@@ static int arProcessSwitch(ArCommand *p
  /*
  ** Parse the command line for an ".ar" command. The results are written into
  ** structure (*pAr). SQLITE_OK is returned if the command line is parsed
--** successfully, otherwise an error message is written to stderr and 
++** successfully, otherwise an error message is written to stderr and
  ** SQLITE_ERROR returned.
  */
  static int arParseCommand(
@@@ -7055,7 -6704,7 +7069,7 @@@ static int arCheckEntries(ArCommand *pA
  ** when pAr->bGlob is false and GLOB match when pAr->bGlob is true.
  */
  static void arWhereClause(
--  int *pRc, 
++  int *pRc,
    ArCommand *pAr,
    char **pzWhere                  /* OUT: New WHERE clause */
  ){
        for(i=0; i<pAr->nArg; i++){
          const char *z = pAr->azArg[i];
          zWhere = sqlite3_mprintf(
--          "%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'", 
++          "%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'",
            zWhere, zSep, zSameOp, z, strlen30(z)+1, zSameOp, z
          );
          if( zWhere==0 ){
  }
  
  /*
--** Implementation of .ar "lisT" command. 
++** Implementation of .ar "lisT" command.
  */
  static int arListCommand(ArCommand *pAr){
--  const char *zSql = "SELECT %s FROM %s WHERE %s"; 
++  const char *zSql = "SELECT %s FROM %s WHERE %s";
    const char *azCols[] = {
      "name",
      "lsmode(mode), sz, datetime(mtime, 'unixepoch'), name"
        if( pAr->bVerbose ){
          utf8_printf(pAr->p->out, "%s % 10d  %s  %s\n",
              sqlite3_column_text(pSql, 0),
--            sqlite3_column_int(pSql, 1), 
++            sqlite3_column_int(pSql, 1),
              sqlite3_column_text(pSql, 2),
              sqlite3_column_text(pSql, 3)
          );
@@@ -7167,17 -6816,17 +7181,17 @@@ static int arRemoveCommand(ArCommand *p
  }
  
  /*
--** Implementation of .ar "eXtract" command. 
++** Implementation of .ar "eXtract" command.
  */
  static int arExtractCommand(ArCommand *pAr){
--  const char *zSql1 = 
++  const char *zSql1 =
      "SELECT "
      " ($dir || name),"
      " writefile(($dir || name), %s, mode, mtime) "
      "FROM %s WHERE (%s) AND (data IS NULL OR $dirOnly = 0)"
      " AND name NOT GLOB '*..[/\\]*'";
  
--  const char *azExtraArg[] = { 
++  const char *azExtraArg[] = {
      "sqlar_uncompress(data, sz)",
      "data"
    };
      if( zDir==0 ) rc = SQLITE_NOMEM;
    }
  
--  shellPreparePrintf(pAr->db, &rc, &pSql, zSql1, 
++  shellPreparePrintf(pAr->db, &rc, &pSql, zSql1,
        azExtraArg[pAr->bZip], pAr->zSrcTable, zWhere
    );
  
@@@ -7281,7 -6930,7 +7295,7 @@@ static int arCreateOrUpdateCommand
    int bUpdate,                    /* true for a --create. */
    int bOnlyIfChanged              /* Only update if file has changed */
  ){
--  const char *zCreate = 
++  const char *zCreate =
        "CREATE TABLE IF NOT EXISTS sqlar(\n"
        "  name TEXT PRIMARY KEY,  -- name of the file\n"
        "  mode INT,               -- access permissions\n"
    arExecSql(pAr, "PRAGMA page_size=512");
    rc = arExecSql(pAr, "SAVEPOINT ar;");
    if( rc!=SQLITE_OK ) return rc;
--  zTemp[0] = 0; 
++  zTemp[0] = 0;
    if( pAr->bZip ){
      /* Initialize the zipfile virtual table, if necessary */
      if( pAr->zFile ){
@@@ -7417,7 -7066,7 +7431,7 @@@ static int arDotCommand
      }else if( cmd.zFile ){
        int flags;
        if( cmd.bAppend ) eDbType = SHELL_OPEN_APPENDVFS;
--      if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_INSERT 
++      if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_INSERT
             || cmd.eCmd==AR_CMD_REMOVE || cmd.eCmd==AR_CMD_UPDATE ){
          flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE;
        }else{
          utf8_printf(pState->out, "-- open database '%s'%s\n", cmd.zFile,
               eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : "");
        }
--      rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, 
++      rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags,
               eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0);
        if( rc!=SQLITE_OK ){
--        utf8_printf(stderr, "cannot open file: %s (%s)\n", 
++        utf8_printf(stderr, "cannot open file: %s (%s)\n",
              cmd.zFile, sqlite3_errmsg(cmd.db)
          );
          goto end_ar_command;
@@@ -7557,12 -7206,12 +7571,12 @@@ static void *shellMalloc(int *pRc, sqli
  /*
  ** If *pRc is not SQLITE_OK when this function is called, it is a no-op.
  ** Otherwise, zFmt is treated as a printf() style string. The result of
--** formatting it along with any trailing arguments is written into a 
++** formatting it along with any trailing arguments is written into a
  ** buffer obtained from sqlite3_malloc(), and pointer to which is returned.
  ** It is the responsibility of the caller to eventually free this buffer
  ** using a call to sqlite3_free().
--** 
--** If an OOM error occurs, (*pRc) is set to SQLITE_NOMEM and a NULL 
++**
++** If an OOM error occurs, (*pRc) is set to SQLITE_NOMEM and a NULL
  ** pointer returned.
  */
  static char *shellMPrintf(int *pRc, const char *zFmt, ...){
@@@ -7621,7 -7271,7 +7636,7 @@@ static RecoverTable *recoverNewTable
    int *pRc,                       /* IN/OUT: Error code */
    const char *zName,              /* Name of table */
    const char *zSql,               /* CREATE TABLE statement */
--  int bIntkey, 
++  int bIntkey,
    int nCol
  ){
    sqlite3 *dbtmp = 0;             /* sqlite3 handle for testing CREATE TABLE */
      int nSqlCol = 0;
      int bSqlIntkey = 0;
      sqlite3_stmt *pStmt = 0;
--    
++
      rc = sqlite3_open("", &dbtmp);
      if( rc==SQLITE_OK ){
        sqlite3_create_function(dbtmp, "shell_idquote", 1, SQLITE_UTF8, 0,
          goto finished;
        }
      }
--    shellPreparePrintf(dbtmp, &rc, &pStmt, 
++    shellPreparePrintf(dbtmp, &rc, &pStmt,
          "SELECT count(*) FROM pragma_table_info(%Q)", zName
      );
      if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
        goto finished;
      }
  
--    shellPreparePrintf(dbtmp, &rc, &pStmt, 
++    shellPreparePrintf(dbtmp, &rc, &pStmt,
        "SELECT ("
        "  SELECT substr(data,1,1)==X'0D' FROM sqlite_dbpage WHERE pgno=rootpage"
        ") FROM sqlite_schema WHERE name = %Q", zName
        ** leave zPk as "_rowid_" and pTab->iPk at -2.  */
        pTab->iPk = -2;
        if( bIntkey ){
--        shellPreparePrintf(dbtmp, &rc, &pPkFinder, 
++        shellPreparePrintf(dbtmp, &rc, &pPkFinder,
            "SELECT cid, name FROM pragma_table_info(%Q) "
            "  WHERE pk=1 AND type='integer' COLLATE nocase"
            "  AND NOT EXISTS (SELECT cid FROM pragma_table_info(%Q) WHERE pk=2)"
          pTab->azlCol[0] = shellMPrintf(&rc, "");
        }
        i = 1;
--      shellPreparePrintf(dbtmp, &rc, &pStmt, 
++      shellPreparePrintf(dbtmp, &rc, &pStmt,
            "SELECT %Q || group_concat(shell_idquote(name), ', ') "
            "  FILTER (WHERE cid!=%d) OVER (ORDER BY %s cid) "
--          "FROM pragma_table_info(%Q)", 
--          bIntkey ? ", " : "", pTab->iPk, 
++          "FROM pragma_table_info(%Q)",
++          bIntkey ? ", " : "", pTab->iPk,
            bIntkey ? "" : "(CASE WHEN pk=0 THEN 1000000 ELSE pk END), ",
            zName
        );
  ** those.
  **
  ** If a table is found, a (RecoverTable*) object is returned. Or, if
--** no such table is found, but bIntkey is false and iRoot is the 
++** no such table is found, but bIntkey is false and iRoot is the
  ** root page of an index in the recovered schema, then (*pbNoop) is
  ** set to true and NULL returned. Or, if there is no such table or
  ** index, NULL is returned and (*pbNoop) set to 0, indicating that
@@@ -7840,7 -7490,7 +7855,7 @@@ static RecoverTable *recoverOrphanTable
          recoverFreeTable(pTab);
          pTab = 0;
        }else{
--        raw_printf(pState->out, 
++        raw_printf(pState->out,
              "CREATE TABLE %s(rootpgno INTEGER, "
              "pgno INTEGER, nfield INTEGER, id INTEGER", pTab->zQuoted
          );
@@@ -7893,14 -7543,14 +7908,14 @@@ static int recoverDatabaseCmd(ShellStat
        bRowids = 0;
      }
      else{
--      utf8_printf(stderr, "unexpected option: %s\n", azArg[i]); 
++      utf8_printf(stderr, "unexpected option: %s\n", azArg[i]);
        showHelp(pState->out, azArg[0]);
        return 1;
      }
    }
  
    shellExecPrintf(pState->db, &rc,
--    /* Attach an in-memory database named 'recovery'. Create an indexed 
++    /* 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;"
    }
  
    /* If this is an auto-vacuum database, add all pointer-map pages to
--  ** the freelist table. Do this regardless of whether or not 
++  ** the freelist table. Do this regardless of whether or not
    ** --freelist-corrupt was specified.  */
--  shellExec(pState->db, &rc, 
++  shellExec(pState->db, &rc,
      "WITH ptrmap(pgno) AS ("
      "  SELECT 2 WHERE shell_int32("
      "    (SELECT data FROM sqlite_dbpage WHERE pgno=1), 13"
      "REPLACE INTO recovery.freelist SELECT pgno FROM ptrmap"
    );
  
--  shellExec(pState->db, &rc, 
++  shellExec(pState->db, &rc,
      "CREATE TABLE recovery.dbptr("
      "      pgno, child, PRIMARY KEY(child, pgno)"
      ") WITHOUT ROWID;"
      ");"
  
      /* Create the "map" table that will (eventually) contain instructions
--    ** for dealing with each page in the db that contains one or more 
++    ** 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"
      "CREATE INDEX recovery.schema_rootpage ON schema(rootpage);"
    );
  
--  /* Open a transaction, then print out all non-virtual, non-"sqlite_%" 
++  /* 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;
      );
      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", 
++      raw_printf(pState->out, "CREATE TABLE IF NOT EXISTS %s;\n",
            &zCreateTable[12]
        );
      }
  
    /* Figure out if an orphan table will be required. And if so, how many
    ** user columns it should contain */
--  shellPrepare(pState->db, &rc, 
++  shellPrepare(pState->db, &rc,
        "SELECT coalesce(max(maxlen), -2) FROM recovery.map WHERE root>1"
        , &pLoop
    );
    );
  
    /* Loop through each root page. */
--  shellPrepare(pState->db, &rc, 
--      "SELECT root, intkey, max(maxlen) FROM recovery.map" 
++  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
  
          nField = nField+1;
          if( pTab2==pOrphan ){
--          raw_printf(pState->out, 
++          raw_printf(pState->out,
                "INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n",
                pTab2->zQuoted, iRoot, iPgno, nField,
                iMin<0 ? "" : "NULL, ", zVal, pTab2->azlCol[nField]
            );
          }else{
--          raw_printf(pState->out, "INSERT INTO %s(%s) VALUES( %s );\n", 
++          raw_printf(pState->out, "INSERT INTO %s(%s) VALUES( %s );\n",
                pTab2->zQuoted, pTab2->azlCol[nField], zVal
            );
          }
    /* The rest of the schema */
    if( rc==SQLITE_OK ){
      sqlite3_stmt *pStmt = 0;
--    shellPrepare(pState->db, &rc, 
++    shellPrepare(pState->db, &rc,
          "SELECT sql, name FROM recovery.schema "
          "WHERE sql NOT LIKE 'create table%'", &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, 
++        char *zPrint = shellMPrintf(&rc,
            "INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)",
            zName, zName, zSql
          );
  }
  #endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
  
- /* 
++/*
 +** The .shxopts command, for setting or listing shell extension options.
 + */
 +#if SHELL_EXTENSIONS
 +static int shxoptsCommand(char *azArg[], int nArg, ShellState *p, char **pzE){
 +  static struct { const char *name; u8 mask; } shopts[] = {
 +#if SHELL_DYNAMIC_COMMANDS
 +    {"dyn_cmds", 1<<SHEXT_DYNCMDS_BIT},
 +#endif
 +#if SHELL_EXTENDED_PARSING
 +    {"parsing", 1<<SHEXT_PARSING_BIT},
 +#endif
 +#if SHELL_VARIABLE_EXPANSION
 +    {"dot_vars", 1<<SHEXT_VAREXP_BIT},
 +#endif
 +    {"all_opts", SHELL_ALL_EXTENSIONS}
 +  };
 +  const char *zMoan = 0, *zAbout = 0;
 +  int ia, io;
 +  if( nArg>1 ){
 +    for( ia=1; ia<nArg; ++ia ){
 +      char cs = azArg[ia][0];
 +      if( cs!='+' && cs!='-' ){
 +        zMoan = "arguments must have a sign prefix.";
 +        zAbout = azArg[0];
 +        goto moan_error;
 +      }
 +      for( io=0; io<ArraySize(shopts); ++io ){
 +        if( strcmp(azArg[ia]+1, shopts[io].name)==0 ){
 +          if( cs=='+' ) p->bExtendedDotCmds |= shopts[io].mask;
 +          else p->bExtendedDotCmds &= ~shopts[io].mask;
 +          break;
 +        }
 +      }
 +      if( io==ArraySize(shopts) ){
 +        zAbout = azArg[ia];
 +        zMoan = "is not a recognized option name";
 +        goto moan_error;
 +      }
 +    }
 +  }else{
 +    raw_printf(p->out,
 +               "     name    value  \"-shxopts set\"\n"
 +               "   --------  -----  ---------------\n");
 +    for( io=0; io<ArraySize(shopts); ++io ){
 +      unsigned m = shopts[io].mask;
 +      unsigned v = ((p->bExtendedDotCmds & m) == m)? 1 : 0;
 +      raw_printf(p->out,
 +                 "  %9s   %2d    \"-shxopts 0x%02X\"\n",
 +                 shopts[io].name,  v, m);
 +    }
 +  }
 +  return 0;
 + moan_error:
 +  raw_printf(stderr, "Error: %s %s\n", zAbout, zMoan);
 +  return 1;
 +}
 +#endif
 +
 +static int execute_variables(char *azArg[], int nArg, ShellState *p){
 +  int ia, rc, nErrors = 0;
 +  sqlite3_stmt *pStmt = 0;
 +  open_db(p, 0);
 +  if( p->db==0 ){
 +    utf8_printf(stderr, ".x can only be done with a database open.\n");
 +    return 1;
 +  }
 +  if( sqlite3_table_column_metadata(p->db, PARAM_TABLE_SCHEMA,
 +                                    PARAM_TABLE_NAME,
 +                                    "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){
 +    utf8_printf(stderr, "No "PARAM_TABLE_SNAME" table exists.\n");
 +    return 1;
 +  }
 +  rc = sqlite3_prepare_v2
 +    (p->db, "SELECT value FROM "PARAM_TABLE_SNAME
 +     " WHERE key=$1 AND uses=1",
 +     -1, &pStmt, 0);
 +  if( rc!=SQLITE_OK ){
 +    utf8_printf(stderr, PARAM_TABLE_SNAME" is wrongly created.\n");
 +    return 1;
 +  }
 +  for( ia=1; ia < nArg; ++ia ){
 +    if( isalpha(azArg[ia][0]) ){
 +      rc = sqlite3_reset(pStmt);
 +      rc = sqlite3_bind_text(pStmt, 1, azArg[ia], -1, 0);
 +      rc = sqlite3_step(pStmt);
 +      if( rc==SQLITE_ROW ){
 +        const unsigned char *zValue = sqlite3_column_text(pStmt, 0);
 +        int nb = sqlite3_column_bytes(pStmt, 0);
 +        while( nb>0 && IsSpace(zValue[nb-1]) ) --nb;
 +        if( nb>0 ){
 +          /* The trailing newline (or some other placeholder) is important
 +           * because one (or some other character) will likely be put in
 +           * its place during process_input() line/group handling, along
 +           * with a terminating NUL character. Without it, the NULL could
 +           * land past the end of the allocation made just below.
 +           */
 +          int nle = zValue[nb-1]=='\n';
 +          char *zSubmit = sqlite3_mprintf( "%.*s%s", nb, zValue, "\n"+nle );
 +          InSource inRedir
 +            = INSOURCE_STR_REDIR(zSubmit, azArg[ia], p->pInSource);
 +          shell_check_oom(zSubmit);
 +          p->pInSource = &inRedir;
 +          rc = process_input(p);
 +          sqlite3_free(zSubmit);
 +          p->pInSource = inRedir.pFrom;
 +        }else{
 +          continue; /* All white, ignore. */
 +        }
 +      }else{
 +        utf8_printf(stderr,
 +                    "Skipping parameter '%s' (not set and executable.)\n",
 +                    azArg[ia]);
 +        ++nErrors;
 +      }
 +    }else{
 +      utf8_printf(stderr,
 +                  "Skipping badly named %s. Run \".help x\"\n", azArg[ia]);
 +      ++nErrors;
 +    }
 +  }
 +  sqlite3_finalize(pStmt);
 +  return (rc==2)? 2 : nErrors>0;
 +}
 +
 +struct param_row { char * value; int uses; int hits; };
 +
 +static int param_find_callback(void *pData, int nc, char **pV, char **pC){
 +  assert(nc>=1);
 +  assert(strcmp(pC[0],"value")==0);
 +  struct param_row *pParam = (struct param_row *)pData;
 +  assert(pParam->value==0); /* key values are supposedly unique. */
 +  if( pParam->value!=0 ) sqlite3_free( pParam->value );
 +  pParam->value = sqlite3_mprintf("%s", pV[0]); /* source owned by statement */
 +  if( nc>1 ) pParam->uses = (int)integerValue(pV[1]);
 +  ++pParam->hits;
 +  return 0;
 +}
 +
 +static void append_in_clause(sqlite3_str *pStr,
 +                             const char **azBeg, const char **azLim);
 +static char *find_home_dir(int clearFlag);
 +
 +/* Create a home-relative pathname from ~ prefixed path.
 + * Return it, or 0 for any error.
 + * Caller must sqlite3_free() it.
 + */
 +static char *home_based_path( const char *zPath ){
 +  char *zHome = find_home_dir(0);
 +  char *zErr = 0;
 +  assert( zPath[0]=='~' );
 +  if( zHome==0 ){
 +    zErr = "Cannot find home directory.";
 +  }else if( zPath[0]==0 || (zPath[1]!='/'
 +#if defined(_WIN32) || defined(WIN32)
 +                            && zPath[1]!='\\'
 +#endif
 +                            ) ){
 +    zErr = "Malformed pathname";
 +  }else{
 +    return sqlite3_mprintf("%s%s", zHome, zPath+1);
 +  }
 +  utf8_printf(stderr, "Error: %s\n", zErr);
 +  return 0;
 +}
 +
 +/* Transfer selected parameters between two parameter tables, for save/load.
 + * Argument bSaveNotLoad determines transfer direction and other actions.
 + * If it is true, the store DB will be created if not existent, and its
 + * table for keeping parameters will be created. Or failure is returned.
 + * If it is false, the store DB will be opened for read and its presumed
 + * table for keeping parameters will be read. Or failure is returned.
 + *
 + * Arguments azNames and nNames reference the ?NAMES? save/load arguments.
 + * If it is an empty list, all parameters will be saved or loaded.
-  * Otherwise, only the named parameters are transferred, if they exist. 
++ * Otherwise, only the named parameters are transferred, if they exist.
 + * It is not an error to specify a name that cannot be transferred
 + * because it does not exist in the source table.
 + *
 + * Returns are SQLITE_OK for success, or other codes for failure.
 + */
 +static int param_xfr_table(sqlite3 *db, const char *zStoreDbName,
 +                           int bSaveNotLoad, const char *azNames[], int nNames){
 +  int rc = 0;
 +  char *zSql = 0; /* to be sqlite3_free()'ed */
 +  sqlite3_str *sbCopy = 0;
 +  const char *zHere = PARAM_TABLE_SNAME;
 +  const char *zThere = PARAM_STORE_SNAME;
 +  const char *zTo = (bSaveNotLoad)? zThere : zHere;
 +  const char *zFrom = (bSaveNotLoad)? zHere : zThere;
 +  sqlite3 *dbStore = 0;
 +  int openFlags = (bSaveNotLoad)
 +    ? SQLITE_OPEN_URI|SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE
 +    : SQLITE_OPEN_READONLY;
 +
 +  /* Ensure store DB can be opened and/or created appropriately. */
 +  rc = sqlite3_open_v2(zStoreDbName, &dbStore, openFlags, 0);
 +  if( rc!=SQLITE_OK ){
 +    utf8_printf(stderr, "Error: Cannot %s parameter store DB %s\n",
 +                bSaveNotLoad? "open/create" : "read", zStoreDbName);
 +    return rc;
 +  }
 +  /* Ensure it has the parameter store table, or handle its absence. */
 +  assert(dbStore!=0);
 +  if( sqlite3_table_column_metadata
 +      (dbStore, "main", PARAM_STORE_NAME, 0, 0, 0, 0, 0, 0)!=SQLITE_OK ){
 +    if( !bSaveNotLoad ){
 +      utf8_printf(stderr, "Error: No parameters ever stored in DB %s\n",
 +                  zStoreDbName);
 +      rc = 1;
 +    }else{
 +      /* The saved parameters table is not there yet; create it. */
 +      const char *zCT =
 +        "CREATE TABLE IF NOT EXISTS "PARAM_STORE_NAME"(\n"
 +        "  key TEXT PRIMARY KEY,\n"
 +        "  value,\n"
 +        "  uses INT\n"
 +        ") WITHOUT ROWID;";
 +      rc = sqlite3_exec(dbStore, zCT, 0, 0, 0);
 +      if( rc!=SQLITE_OK ){
 +        utf8_printf(stderr, "Cannot create table %s. Nothing saved.", zThere);
 +      }
 +    }
 +  }
 +  sqlite3_close(dbStore);
 +  if( rc!=0 ) return rc;
 +
 +  zSql = sqlite3_mprintf("ATTACH %Q AS %s;", zStoreDbName, PARAM_STORE_SCHEMA);
 +  shell_check_oom(zSql);
 +  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 +  sqlite3_free(zSql);
 +  if( rc!=SQLITE_OK ) return rc;
 +
 +  sbCopy = sqlite3_str_new(db);
 +  sqlite3_str_appendf
 +      (sbCopy, "INSERT OR REPLACE INTO %s(key,value,uses)"
 +       "SELECT key, value, uses FROM %s WHERE key ", zTo, zFrom);
 +  append_in_clause(sbCopy, azNames, azNames+nNames);
 +  zSql = sqlite3_str_finish(sbCopy);
 +  shell_check_oom(zSql);
 +  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 +  sqlite3_free(zSql);
 +
 +  sqlite3_exec(db, "DETACH "PARAM_STORE_SCHEMA";", 0, 0, 0);
 +  return rc;
 +}
 +
 +/* Default location of parameters store DB for .parameters save/load. */
 +static const char *zDefaultParamStore = "~/sqlite_params.sdb";
 +
 +/* Possibly generate a derived path from input spec, with defaulting
 + * and conversion of leading (or only) tilde as home directory.
 + * The above-set default is used for zSpec NULL, "" or "~".
 + * When return is 0, there is an error; what needs doing cannnot be done.
 + * If the return is exactly the input, it must not be sqlite3_free()'ed.
 + * If the return differs from the input, it must be sqlite3_free()'ed.
 + */
 +static const char *params_store_path(const char *zSpec){
 +  if( zSpec==0 || zSpec[0]==0 || strcmp(zSpec,"~")==0 ){
 +    return home_based_path(zDefaultParamStore);
 +  }else if ( zSpec[0]=='~' ){
 +    return home_based_path(zSpec);
 +  }
 +  return zSpec;
 +}
 +
 +/* Load some or all parameters. Arguments are "load FILE ?NAMES?". */
 +static int parameters_load(sqlite3 *db, const char *azArg[], int nArg){
 +  const char *zStore = params_store_path((nArg>1)? azArg[1] : 0);
 +  if( zStore==0 ){
 +    utf8_printf(stderr, "Cannot form parameter load path. Nothing loaded.\n");
 +    return 1;
 +  }else{
 +    const char **pzFirst = (nArg>2)? azArg+2 : 0;
 +    int nNames = (nArg>2)? nArg-2 : 0;
 +    int rc = param_xfr_table(db, zStore, 0, pzFirst, nNames);
 +    if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore);
 +    return rc;
 +  }
 +}
 +
 +/* Save some or all parameters. Arguments are "save FILE ?NAMES?". */
 +static int parameters_save(sqlite3 *db, const char *azArg[], int nArg){
 +  const char *zStore = params_store_path((nArg>1)? azArg[1] : 0);
 +  if( zStore==0 ){
 +    utf8_printf(stderr, "Cannot form parameter save path. Nothing saved.\n");
 +    return 1;
 +  }else{
 +    const char **pzFirst = (nArg>2)? azArg+2 : 0;
 +    int nNames = (nArg>2)? nArg-2 : 0;
 +    int rc = param_xfr_table(db, zStore, 1, pzFirst, nNames);
 +    if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore);
 +    return rc;
 +  }
 +}
 +
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +/*
 + * Edit one named parameter in the parameters table. If it does not
 + * yet exist, create it. If eval is true, the value is treated as a
 + * bare expression, otherwise it is a text value. The uses argument
 + * sets the 3rd column in the parameters table, and may also serve
 + * to partition the key namespace. (This is not done now.)
 + */
 +static int edit_one_param(sqlite3 *db, char *name, int eval,
 +                          ParamTableUse uses, const char * zEditor){
 +  struct param_row paramVU = {0,0,0};
 +  int rc;
 +  char * zVal = 0;
 +  char * zSql = sqlite3_mprintf
 +    ("SELECT value, uses FROM " PARAM_TABLE_SNAME " WHERE key=%Q", name);
 +  shell_check_oom(zSql);
 +  sqlite3_exec(db, zSql, param_find_callback, &paramVU, 0);
 +  sqlite3_free(zSql);
 +  assert(paramVU.hits<2);
 +  if( paramVU.hits==1 && paramVU.uses==uses){
 +    /* Editing an existing value of same kind. */
 +    sqlite3_free(paramVU.value);
 +    if( eval!=0 ){
 +      zSql = sqlite3_mprintf
 +        ("SELECT edit(value, %Q) FROM " PARAM_TABLE_SNAME
 +         " WHERE key=%Q AND uses=%d", zEditor, name, uses);
 +      zVal = db_text(db, zSql, 1);
 +      sqlite3_free(zSql);
 +      zSql = sqlite3_mprintf
 +        ("UPDATE "PARAM_TABLE_SNAME" SET value=(SELECT %s) WHERE"
 +         " key=%Q AND uses=%d", zVal, name, uses);
 +    }else{
 +      zSql = sqlite3_mprintf
 +        ("UPDATE "PARAM_TABLE_SNAME" SET value=edit(value, %Q) WHERE"
 +         " key=%Q AND uses=%d", zEditor, name, uses);
 +    }
 +  }else{
 +    /* Editing a new value of same kind. */
 +    assert(paramVU.value==0 || paramVU.uses!=uses);
 +    if( eval!=0 ){
 +      zSql = sqlite3_mprintf
 +        ("SELECT edit('-- %q%s', %Q)", name, "\n", zEditor);
 +      zVal = db_text(db, zSql, 1);
 +      sqlite3_free(zSql);
 +      zSql = sqlite3_mprintf
 +        ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 +         " VALUES (%Q,(SELECT %s LIMIT 1),%d)",
 +         name, zVal, uses);
 +    }else{
 +      zSql = sqlite3_mprintf
 +        ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 +         " VALUES (%Q,edit('-- %q%s', %Q),%d)",
 +         name, name, "\n", zEditor, uses);
 +    }
 +  }
 +  shell_check_oom(zSql);
 +  if( eval!=0 ){
 +  }
 +  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 +  sqlite3_free(zSql);
 +  sqlite3_free(zVal);
 +  return rc!=SQLITE_OK;
 +}
 +#endif
 +
 +/* Space-join values in an argument list. *valLim is not included. */
 +char *values_join( char **valBeg, char **valLim ){
 +  char *z = 0;
 +  const char *zSep = 0;
 +  while( valBeg < valLim ){
 +    z = sqlite3_mprintf("%z%s%s", z, zSep, *valBeg);
 +    zSep = " ";
 +    ++valBeg;
 +  }
 +  return z;
 +}
 +
 +/* Get a named parameter value in form of stepped prepared statement,
 + * ready to have its value taken from the 0th column. If the name
 + * cannot be found for the given ParamTableUse, 0 is returned.
 + * The caller is responsible for calling sqlite3_finalize(pStmt),
 + * where pStmt is the return from this function.
 + */
 +static sqlite3_stmt *get_param_value(sqlite3 *db, char *name,
 +                                     ParamTableUse ptu){
 +  sqlite3_stmt *rv = 0;
 +  int rc;
 +  char *zSql = sqlite3_mprintf
 +    ( "SELECT value FROM "PARAM_TABLE_SNAME
 +      " WHERE key=%Q AND uses=%d", name, ptu );
 +  shell_check_oom(zSql);
 +  rc = sqlite3_prepare_v2(db, zSql, -1, &rv, 0);
 +  sqlite3_free(zSql);
 +  if( SQLITE_OK==rc ){
 +    if( SQLITE_ROW==sqlite3_step(rv) ) return rv;
 +    sqlite3_finalize(rv);
 +  }
 +  return 0;
 +}
 +
 +static struct ParamSetOpts {
 +  const char cCast;
 +  const char *zTypename;
 +  int evalKind;
 +} param_set_opts[] = {
 +  /* { 'q', 0, 2 }, */
 +  /* { 'x', 0, 1 }, */
 +  { 'i', "INT", 1 },
 +  { 'r', "REAL", 1 },
 +  { 'b', "BLOB", 1 },
 +  { 't', "TEXT", 0 },
 +  { 'n', "NUMERIC", 1 }
 +};
 +
 +/* Return an option character if it is single and prefixed by - or --,
 + * else return 0.
 + */
 +static char option_char(char *zArg){
 +  if( zArg[0]=='-' ){
 +    ++zArg;
 +    if( zArg[0]=='-' ) ++zArg;
 +    if( zArg[0]!=0 && zArg[1]==0 ) return zArg[0];
 +  }
 +  return 0;
 +}
 +
 +static int param_set(sqlite3 *db, char cCast,
 +                     char *name, char **valBeg, char **valLim,
 +                     ParamTableUse ptu){
 +  char *zSql = 0;
 +  int rc = SQLITE_OK, retries = 0, needsEval = 1;
 +  char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0;
 +  sqlite3_stmt *pStmtSet = 0;
 +  const char *zCastTo = 0;
 +  char *zValue = (zValGlom==0)? *valBeg : zValGlom;
 +  if( cCast ){
 +    struct ParamSetOpts *pSO = param_set_opts;
 +    for(; pSO-param_set_opts < ArraySize(param_set_opts); ++pSO ){
 +      if( cCast==pSO->cCast ){
 +        zCastTo = pSO->zTypename;
 +        needsEval = pSO->evalKind > 0;
 +        break;
 +      }
 +    }
 +  }
 +  if( needsEval ){
 +    if( zCastTo!=0 ){
 +      zSql = sqlite3_mprintf
 +        ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 +          " VALUES(%Q,CAST((%s) AS %s),%d);", name, zValue, zCastTo, ptu );
 +    }else{
 +      zSql = sqlite3_mprintf
 +        ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 +          "VALUES(%Q,(%s),%d);", name, zValue, ptu );
 +    }
 +    shell_check_oom(zSql);
 +    rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0);
 +    sqlite3_free(zSql);
 +  }
 +  if( !needsEval || rc!=SQLITE_OK ){
 +    /* Reach here when value either requested to be cast to text, or must be. */
 +    sqlite3_finalize(pStmtSet);
 +    pStmtSet = 0;
 +    zSql = sqlite3_mprintf
 +      ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 +        "VALUES(%Q,%Q,%d);", name, zValue, ptu );
 +    shell_check_oom(zSql);
 +    rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0);
 +    assert(rc==SQLITE_OK);
 +    sqlite3_free(zSql);
 +  }
 +  sqlite3_step(pStmtSet);
 +  sqlite3_finalize(pStmtSet);
 +  sqlite3_free(zValGlom);
 +  return rc;
 +}
 +
 +/* list subcommand for .parameter dot-command */
 +static void list_params(ShellState *p, ParamTableUse ptu){
 +  sqlite3_stmt *pStmt = 0;
 +  int len = 0;
 +  int rc = sqlite3_prepare_v2
 +    (p->db, "SELECT max(length(key)) FROM "
 +     PARAM_TABLE_SNAME" WHERE ?1=3 OR uses=?1", -1, &pStmt, 0);
 +  if( rc==SQLITE_OK ){
 +    sqlite3_bind_int(pStmt, 1, ptu);
 +    if( sqlite3_step(pStmt)==SQLITE_ROW ){
 +      len = sqlite3_column_int(pStmt, 0);
 +      if( len>40 ) len = 40;
 +      if( len<4 ) len = 4;
 +    }
 +  }
 +  sqlite3_finalize(pStmt);
 +  pStmt = 0;
 +  if( len ){
 +    utf8_printf(p->out, "%-*s %-8s %s\n", len, "name", "usage", "value");
 +    rc = sqlite3_prepare_v2
 +      (p->db, "SELECT key, uses, quote(value) FROM " PARAM_TABLE_SNAME
 +       " WHERE ?1=3 OR uses=?1 ORDER BY key", -1, &pStmt, 0);
 +    sqlite3_bind_int(pStmt, 1, ptu);
 +    while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 +      ParamTableUse ptux = sqlite3_column_int(pStmt,1);
 +      const char *zUse;
 +      switch( ptux ){
 +      case PTU_Binding: zUse = "binding"; break;
 +      case PTU_Script:  zUse = "script"; break;
 +      default:          zUse = "unknown";
 +      }
 +      utf8_printf(p->out, "%-*s %-8s %s\n", len, sqlite3_column_text(pStmt,0),
 +                  zUse, sqlite3_column_text(pStmt,2));
 +    }
 +    sqlite3_finalize(pStmt);
 +  }
- }  
++}
 +
 +/* Append either an IN clause or an always true test to some SQL.
 + *
 + * An empty IN list is the same as always true (for non-NULL LHS)
 + * for this clause, which assumes a trailing LHS operand and space.
 + * If that is not the right result, guard the call against it.
 + * This is used for ".parameter dostuff ?NAMES?" options,
 + * where a missing list means all the qualifying entries.
 + *
 + * The empty list may be signified by azBeg and azLim both 0.
 + */
 +static void append_in_clause(sqlite3_str *pStr,
 +                             const char **azBeg, const char **azLim){
 +  if( azBeg==azLim ) sqlite3_str_appendf(pStr, "NOT NULL");
 +  else{
 +    char cSep = '(';
 +    sqlite3_str_appendf(pStr, "IN");
 +    while( azBeg<azLim ){
 +      sqlite3_str_appendf(pStr, "%c%Q", cSep, *azBeg);
 +      cSep = ',';
 +      ++azBeg;
 +    }
 +    sqlite3_str_appendf(pStr, ")");
 +  }
 +}
 +
 -/* 
++/*
+  * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it.
+  * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE,
+  *   close db and set it to 0, and return the columns spec, to later
+  *   be sqlite3_free()'ed by the caller.
+  * The return is 0 when either:
+  *   (a) The db was not initialized and zCol==0 (There are no columns.)
+  *   (b) zCol!=0  (Column was added, db initialized as needed.)
+  * The 3rd argument, pRenamed, references an out parameter. If the
+  * pointer is non-zero, its referent will be set to a summary of renames
+  * done if renaming was necessary, or set to 0 if none was done. The out
+  * string (if any) must be sqlite3_free()'ed by the caller.
+  */
+ #ifdef SHELL_DEBUG
+ #define rc_err_oom_die(rc) \
+   if( rc==SQLITE_NOMEM ) shell_check_oom(0); \
+   else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \
+     fprintf(stderr,"E:%d\n",rc), assert(0)
+ #else
+ static void rc_err_oom_die(int rc){
+   if( rc==SQLITE_NOMEM ) shell_check_oom(0);
+   assert(rc==SQLITE_OK||rc==SQLITE_DONE);
+ }
+ #endif
+ #ifdef SHELL_COLFIX_DB /* If this is set, the DB can be in a file. */
+ static char zCOL_DB[] = SHELL_STRINGIFY(SHELL_COLFIX_DB);
+ #else  /* Otherwise, memory is faster/better for the transient DB. */
+ static const char *zCOL_DB = ":memory:";
+ #endif
+ /* Define character (as C string) to separate generated column ordinal
+  * from protected part of incoming column names. This defaults to "_"
+  * so that incoming column identifiers that did not need not be quoted
+  * remain usable without being quoted. It must be one character.
+  */
+ #ifndef SHELL_AUTOCOLUMN_SEP
+ # define AUTOCOLUMN_SEP "_"
+ #else
+ # define AUTOCOLUMN_SEP SHELL_STRINGIFY(SHELL_AUTOCOLUMN_SEP)
+ #endif
+ static char *zAutoColumn(const char *zColNew, sqlite3 **pDb, char **pzRenamed){
+   /* Queries and D{D,M}L used here */
+   static const char * const zTabMake = "\
+ CREATE TABLE ColNames(\
+  cpos INTEGER PRIMARY KEY,\
+  name TEXT, nlen INT, chop INT, reps INT, suff TEXT);\
+ CREATE VIEW RepeatedNames AS \
+ SELECT DISTINCT t.name FROM ColNames t \
+ WHERE t.name COLLATE NOCASE IN (\
+  SELECT o.name FROM ColNames o WHERE o.cpos<>t.cpos\
+ );\
+ ";
+   static const char * const zTabFill = "\
+ INSERT INTO ColNames(name,nlen,chop,reps,suff)\
+  VALUES(iif(length(?1)>0,?1,'?'),max(length(?1),1),0,0,'')\
+ ";
+   static const char * const zHasDupes = "\
+ SELECT count(DISTINCT (substring(name,1,nlen-chop)||suff) COLLATE NOCASE)\
+  <count(name) FROM ColNames\
+ ";
+ #ifdef SHELL_COLUMN_RENAME_CLEAN
+   static const char * const zDedoctor = "\
+ UPDATE ColNames SET chop=iif(\
+   (substring(name,nlen,1) BETWEEN '0' AND '9')\
+   AND (rtrim(name,'0123456790') glob '*"AUTOCOLUMN_SEP"'),\
+  nlen-length(rtrim(name, '"AUTOCOLUMN_SEP"0123456789')),\
+  0\
+ )\
+ ";
+ #endif
+   static const char * const zSetReps = "\
+ UPDATE ColNames AS t SET reps=\
+ (SELECT count(*) FROM ColNames d \
+  WHERE substring(t.name,1,t.nlen-t.chop)=substring(d.name,1,d.nlen-d.chop)\
+  COLLATE NOCASE\
+ )\
+ ";
+ #ifdef SQLITE_ENABLE_MATH_FUNCTIONS
+   static const char * const zColDigits = "\
+ SELECT CAST(ceil(log(count(*)+0.5)) AS INT) FROM ColNames \
+ ";
+ #endif
+   static const char * const zRenameRank =
+ #ifdef SHELL_COLUMN_RENAME_CLEAN
+     "UPDATE ColNames AS t SET suff="
+     "iif(reps>1, printf('%c%0*d', '"AUTOCOLUMN_SEP"', $1, cpos), '')"
+ #else /* ...RENAME_MINIMAL_ONE_PASS */
+ "WITH Lzn(nlz) AS (" /* Find minimum extraneous leading 0's for uniqueness */
+ "  SELECT 0 AS nlz"
+ "  UNION"
+ "  SELECT nlz+1 AS nlz FROM Lzn"
+ "  WHERE EXISTS("
+ "   SELECT 1"
+ "   FROM ColNames t, ColNames o"
+ "   WHERE"
+ "    iif(t.name IN (SELECT * FROM RepeatedNames),"
+ "     printf('%s"AUTOCOLUMN_SEP"%s',"
+ "      t.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,t.cpos),2)),"
+ "     t.name"
+ "    )"
+ "    ="
+ "    iif(o.name IN (SELECT * FROM RepeatedNames),"
+ "     printf('%s"AUTOCOLUMN_SEP"%s',"
+ "      o.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,o.cpos),2)),"
+ "     o.name"
+ "    )"
+ "    COLLATE NOCASE"
+ "    AND o.cpos<>t.cpos"
+ "   GROUP BY t.cpos"
+ "  )"
+ ") UPDATE Colnames AS t SET"
+ " chop = 0," /* No chopping, never touch incoming names. */
+ " suff = iif(name IN (SELECT * FROM RepeatedNames),"
+ "  printf('"AUTOCOLUMN_SEP"%s', substring("
+ "   printf('%.*c%0.*d',(SELECT max(nlz) FROM Lzn)+1,'0',1,t.cpos),2)),"
+ "  ''"
+ " )"
+ #endif
+     ;
+   static const char * const zCollectVar = "\
+ SELECT\
+  '('||x'0a'\
+  || group_concat(\
+   cname||' TEXT',\
+   ','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\
+  ||')' AS ColsSpec \
+ FROM (\
+  SELECT cpos, printf('\"%w\"',printf('%.*s%s', nlen-chop,name,suff)) AS cname \
+  FROM ColNames ORDER BY cpos\
+ )";
+   static const char * const zRenamesDone =
+     "SELECT group_concat("
+     " printf('\"%w\" to \"%w\"',name,printf('%.*s%s', nlen-chop, name, suff)),"
+     " ','||x'0a')"
+     "FROM ColNames WHERE suff<>'' OR chop!=0"
+     ;
+   int rc;
+   sqlite3_stmt *pStmt = 0;
+   assert(pDb!=0);
+   if( zColNew ){
+     /* Add initial or additional column. Init db if necessary. */
+     if( *pDb==0 ){
+       if( SQLITE_OK!=sqlite3_open(zCOL_DB, pDb) ) return 0;
+ #ifdef SHELL_COLFIX_DB
+       if(*zCOL_DB!=':')
+         sqlite3_exec(*pDb,"drop table if exists ColNames;"
+                      "drop view if exists RepeatedNames;",0,0,0);
+ #endif
+       rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0);
+       rc_err_oom_die(rc);
+     }
+     assert(*pDb!=0);
+     rc = sqlite3_prepare_v2(*pDb, zTabFill, -1, &pStmt, 0);
+     rc_err_oom_die(rc);
+     rc = sqlite3_bind_text(pStmt, 1, zColNew, -1, 0);
+     rc_err_oom_die(rc);
+     rc = sqlite3_step(pStmt);
+     rc_err_oom_die(rc);
+     sqlite3_finalize(pStmt);
+     return 0;
+   }else if( *pDb==0 ){
+     return 0;
+   }else{
+     /* Formulate the columns spec, close the DB, zero *pDb. */
+     char *zColsSpec = 0;
+     int hasDupes = db_int(*pDb, zHasDupes);
+ #ifdef SQLITE_ENABLE_MATH_FUNCTIONS
+     int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0;
+ #else
+ # define nDigits 2
+ #endif
+     if( hasDupes ){
+ #ifdef SHELL_COLUMN_RENAME_CLEAN
+       rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0);
+       rc_err_oom_die(rc);
+ #endif
+       rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0);
+       rc_err_oom_die(rc);
+       rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0);
+       rc_err_oom_die(rc);
+       sqlite3_bind_int(pStmt, 1, nDigits);
+       rc = sqlite3_step(pStmt);
+       sqlite3_finalize(pStmt);
+       assert(rc==SQLITE_DONE);
+     }
+     assert(db_int(*pDb, zHasDupes)==0); /* Consider: remove this */
+     rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0);
+     rc_err_oom_die(rc);
+     rc = sqlite3_step(pStmt);
+     if( rc==SQLITE_ROW ){
+       zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
+     }else{
+       zColsSpec = 0;
+     }
+     if( pzRenamed!=0 ){
+       if( !hasDupes ) *pzRenamed = 0;
+       else{
+         sqlite3_finalize(pStmt);
+         if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0)
+             && SQLITE_ROW==sqlite3_step(pStmt) ){
+           *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
+         }else
+           *pzRenamed = 0;
+       }
+     }
+     sqlite3_finalize(pStmt);
+     sqlite3_close(*pDb);
+     *pDb = 0;
+     return zColsSpec;
+   }
+ }
  /*
  ** If an input line begins with "." then invoke this routine to
  ** process that line.
@@@ -8832,7 -8157,7 +9062,7 @@@ static int do_meta_command(char *zLine
        return 1;
      }
      if( zDb==0 ) zDb = "main";
--    rc = sqlite3_open_v2(zDestFile, &pDest, 
++    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);
      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( c=='d' && n>=3 && strncmp(azArg[0], "dbinfo", n)==0 ){
      int i;
      int savedShowHeader = p->showHeader;
      int savedShellFlags = p->shellFlgs;
--    ShellClearFlag(p, 
++    ShellClearFlag(p,
         SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo
         |SHFLG_DumpDataOnly|SHFLG_DumpNoSys);
      for(i=1; i<nArg; i++){
              "    name LIKE %Q ESCAPE '\\' AND"
              "    sql LIKE 'CREATE VIRTUAL TABLE%%' AND"
              "    substr(o.name, 1, length(name)+1) == (name||'_')"
 -            ")", azArg[i], azArg[i]
 +            ")", azArg[i], zSchema, azArg[i]
          );
--      
++
          if( zLike ){
            zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr);
          }else{
  #ifndef SQLITE_OMIT_VIRTUALTABLE
    if( c=='e' && strncmp(azArg[0], "expert", n)==0 ){
      if( p->bSafeMode ){
--      raw_printf(stderr, 
++      raw_printf(stderr,
          "Cannot run experimental commands such as \"%s\" in safe mode\n",
          azArg[0]);
        rc = 1;
      } aCtrl[] = {
        { "chunk_size",     SQLITE_FCNTL_CHUNK_SIZE,      "SIZE"           },
        { "data_version",   SQLITE_FCNTL_DATA_VERSION,    ""               },
--      { "has_moved",      SQLITE_FCNTL_HAS_MOVED,       ""               },  
++      { "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"       },*/
      open_db(p, 0);
      zCmd = nArg>=2 ? azArg[1] : "help";
  
--    if( zCmd[0]=='-' 
++    if( zCmd[0]=='-'
       && (strcmp(zCmd,"--schema")==0 || strcmp(zCmd,"-schema")==0)
       && nArg>=4
      ){
          goto meta_command_exit;
        }
        if( nSep>1 ){
--        raw_printf(stderr, 
++        raw_printf(stderr,
                "Error: multi-character column separators not allowed"
                " for import\n");
          rc = 1;
      if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){
        char *zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"",
                                        zSchema, zTable);
-       char cSep = '(';
+       sqlite3 *dbCols = 0;
+       char *zRenames = 0;
+       char *zColDefs;
        while( xRead(&sCtx) ){
-         zCreate = sqlite3_mprintf("%z%c\n  \"%w\" TEXT", zCreate, cSep, sCtx.z);
-         cSep = ',';
+         zAutoColumn(sCtx.z, &dbCols, 0);
          if( sCtx.cTerm!=sCtx.cColSep ) break;
        }
-       if( cSep=='(' ){
+       zColDefs = zAutoColumn(0, &dbCols, &zRenames);
+       if( zRenames!=0 ){
 -        utf8_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr,
++        utf8_printf(INSOURCE_IS_INTERACTIVE(p->pInSource)? p->out : stderr,
+                     "Columns renamed during .import %s due to duplicates:\n"
+                     "%s\n", sCtx.zFile, zRenames);
+         sqlite3_free(zRenames);
+       }
+       assert(dbCols==0);
+       if( zColDefs==0 ){
          sqlite3_free(zCreate);
          import_cleanup(&sCtx);
          utf8_printf(stderr,"%s: empty file\n", sCtx.zFile);
      ** VALUE can be in either SQL literal notation, or if not it will be
      ** understood to be a text string.
      */
 -    if( nArg==4 && strcmp(azArg[1],"set")==0 ){
 -      int rx;
 -      char *zSql;
 -      sqlite3_stmt *pStmt;
 -      const char *zKey = azArg[2];
 -      const char *zValue = azArg[3];
 -      bind_table_init(p);
 -      zSql = sqlite3_mprintf(
 -                  "REPLACE INTO temp.sqlite_parameters(key,value)"
 -                  "VALUES(%Q,%s);", zKey, zValue);
 -      shell_check_oom(zSql);
 -      pStmt = 0;
 -      rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -      sqlite3_free(zSql);
 -      if( rx!=SQLITE_OK ){
 -        sqlite3_finalize(pStmt);
 -        pStmt = 0;
 -        zSql = sqlite3_mprintf(
 -                   "REPLACE INTO temp.sqlite_parameters(key,value)"
 -                   "VALUES(%Q,%Q);", zKey, zValue);
 -        shell_check_oom(zSql);
 -        rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -        sqlite3_free(zSql);
 -        if( rx!=SQLITE_OK ){
 +    if( nArg>=4 && strcmp(azArg[1],"set")==0 ){
 +      char cCast = option_char(azArg[2]);
 +      int inv = 2 + (cCast != 0);
 +      ParamTableUse ptu = classify_param_name(azArg[inv]);
 +      if( ptu==PTU_Nil ){
 +        utf8_printf(stderr,
 +                    "Error: %s is not a usable parameter name.\n", azArg[inv]);
 +        rc = 1;
 +      }else{
 +        param_table_init(p);
 +        rc = param_set(p->db, cCast, azArg[inv],
-                        &azArg[inv+1], &azArg[nArg], ptu); 
++                       &azArg[inv+1], &azArg[nArg], ptu);
 +        if( rc!=SQLITE_OK ){
            utf8_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db));
 -          sqlite3_finalize(pStmt);
 -          pStmt = 0;
            rc = 1;
          }
        }
      }
    }else
  
-     } 
 +  if( c=='s' && n==7 && strncmp(azArg[0], "seeargs", n)==0 ){
 +    for( n=1; n<nArg; ++n ){
 +      raw_printf(p->out, "%s%s", azArg[n], (n==nArg-1)? "|\n" : "|");
++    }
 +  }else
 +
    if( c=='s' && n==11 && strncmp(azArg[0], "selecttrace", n)==0 ){
 -    unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
 +    unsigned int x
 +      = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
      sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x);
    }else
  
@@@ -12114,9 -11351,9 +12353,9 @@@ static void sql_prescan(char *zLine, Sq
            /* fall thru */
          case ']':
            cWait = 0;
 -          qss = QSS_SETV(qss, 0);
 +          sss = SSS_SETV(sss, 0);
            goto PlainScan;
--        default: assert(0); 
++        default: assert(0);
          }
        }
      }
@@@ -12185,19 -11416,30 +12424,29 @@@ static int runOneSqlLine(ShellState *p
    END_TIMER;
    if( rc || zErrMsg ){
      char zPrefix[100];
-     if( bAltIn || !stdin_is_interactive ){
-       sqlite3_snprintf(sizeof(zPrefix), zPrefix,
-                        "Error: near line %d:", startline);
+     const char *zErrorTail;
+     const char *zErrorType;
+     if( zErrMsg==0 ){
+       zErrorType = "Error";
+       zErrorTail = sqlite3_errmsg(p->db);
+     }else if( strncmp(zErrMsg, "in prepare, ",12)==0 ){
+       zErrorType = "Parse error";
+       zErrorTail = &zErrMsg[12];
+     }else if( strncmp(zErrMsg, "stepping, ", 10)==0 ){
+       zErrorType = "Runtime error";
+       zErrorTail = &zErrMsg[10];
      }else{
-       sqlite3_snprintf(sizeof(zPrefix), zPrefix, "Error:");
+       zErrorType = "Error";
+       zErrorTail = zErrMsg;
      }
-     if( zErrMsg!=0 ){
-       utf8_printf(stderr, "%s %s\n", zPrefix, zErrMsg);
-       sqlite3_free(zErrMsg);
-       zErrMsg = 0;
 -    if( in!=0 || !stdin_is_interactive ){
++    if( bAltIn || !stdin_is_interactive ){
+       sqlite3_snprintf(sizeof(zPrefix), zPrefix,
+                        "%s near line %d:", zErrorType, startline);
      }else{
-       utf8_printf(stderr, "%s %s\n", zPrefix, sqlite3_errmsg(p->db));
+       sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType);
      }
 -    zErrMsg = 0;
+     utf8_printf(stderr, "%s %s\n", zPrefix, zErrorTail);
+     sqlite3_free(zErrMsg);
      return 1;
    }else if( ShellHasFlag(p, SHFLG_CountChanges) ){
      char zLineBuf[2000];
    return 0;
  }
  
-         if( (c=*zCmd++)==0 ) goto atEnd; 
 +#if SHELL_EXTENDED_PARSING
 +/* Resumable line classsifier for dot-commands
 +**
 +** Determines if a dot-command is open, having either an unclosed
 +** quoted argument or an escape sequence opener ('\') at its end.
 +**
 +** The FSM design/behavior assumes/requires that a terminating '\'
 +** is not part of the character sequence being classified -- that
 +** it represents an escaped newline which is removed as physical
 +** lines are spliced to accumulate logical lines.
 +**
 +** The line or added line-portion is passed as zCmd.
 +** The pScanState pointer must reference an (opaque) DCmd_ScanState,
 +** which must be set to DCSS_Start to initialize the scanner state.
 +** Resumed scanning should always be done with zCmd logically just
 +** past the last non-0 char of the text previously passed in, with
 +** any previously scanned, trailing newline escape first trimmed.
 +** Returns are: 0 => not open (aka complete), 1 => is open (incomplete)
 +** The following macros may be applied to the scan state:
 +*/
 +#define DCSS_InDarkArg(dcss) (((dcss)&argPosMask)==inDqArg)
 +#define DCSS_EndEscaped(dcss) (((dcss)&endEscaped)!=0)
 +#define DCSS_IsOpen(dcss) (((dcss)&isOpenMask)!=0)
 +typedef enum {
 +  DCSS_Start = 0,
 +  twixtArgs = 0, inSqArg = 1, inDarkArg = 2, inDqArg = 3, /* ordered */
 +  endEscaped = 4, /* bit used */
 +  argPosMask = 3, /* bits used */
 +  isOpenMask = 1|4 /* bit test */
 +} DCmd_ScanState;
 +
 +static void dot_command_scan(char *zCmd, DCmd_ScanState *pScanState){
 +  DCmd_ScanState ss = *pScanState & ~endEscaped;
 +  char c = (ss&isOpenMask)? 1 : *zCmd++;
 +  while( c!=0 ){
 +    switch( ss ){
 +    case twixtArgs:
 +      while( IsSpace(c) ){
-         if( (c=*zCmd++)==0 ) goto atEnd; 
++        if( (c=*zCmd++)==0 ) goto atEnd;
 +      }
 +      switch( c ){
 +      case '\\':
 +        if( *zCmd==0 ){
 +          ss |= endEscaped;
 +          goto atEnd;
 +        }else goto inDark;
 +      case '\'': ss = inSqArg; goto inSq;
 +      case '"': ss = inDqArg; goto inDq;
 +      default: ss = inDarkArg; goto inDark;
 +      }
 +    inSq:
 +    case inSqArg:
 +      while( (c=*zCmd++)!='\'' ){
 +        if( c==0 ) goto atEnd;
 +        if( c=='\\' && *zCmd==0 ){
 +          ss |= endEscaped;
 +          goto atEnd;
 +        }
 +      }
 +      ss = twixtArgs;
 +      c = *zCmd++;
 +      continue;
 +    inDq:
 +    case inDqArg:
 +      do {
 +        if( (c=*zCmd++)==0 ) goto atEnd;
 +        if( c=='\\' ){
 +          if( (c=*zCmd++)==0 ){
 +            ss |= endEscaped;
 +            goto atEnd;
 +          }
 +          if( (c=*zCmd++)==0 ) goto atEnd;
 +        }
 +      } while( c!='"' );
 +      ss = twixtArgs;
 +      c = *zCmd++;
 +      continue;
 +    inDark:
 +    case inDarkArg:
 +      while( !IsSpace(c) ){
 +        if( c=='\\' && *zCmd==0 ){
 +          ss |= endEscaped;
 +          goto atEnd;
 +        }
- /* 
++        if( (c=*zCmd++)==0 ) goto atEnd;
 +      }
 +      ss = twixtArgs;
 +      c = *zCmd++;
 +      continue;
 +    }
 +  }
 + atEnd:
 +  *pScanState = ss;
 +}
 +#else
 +# define dot_command_scan(x,y)
 +#endif
 +
 +/* Utility functions for process_input. */
 +
 +#if SHELL_EXTENDED_PARSING
 +/*
 +** Process dot-command line with its scan state to:
 +** 1. Setup for requested line-splicing; and
 +** 2. Say whether it is complete.
 +** The last two out parameters are the line's length, which may be
 +** adjusted, and the char to be used for joining a subsequent line.
 +** This is broken out of process_input() mainly for readability.
 +** The return is TRUE for dot-command ready to run, else false.
 +*/
 +static int line_join_ends(DCmd_ScanState dcss, char *zLine,
 +                          int *pnLength, char *pcLE){
 +  /* It is ready only if has no open argument or escaped newline. */
 +  int bOpen = DCSS_IsOpen(dcss);
 +  if( !DCSS_EndEscaped(dcss) ){
 +    *pcLE = '\n';
 +    return !bOpen;
 +  }else{
 +    *pcLE = (bOpen || DCSS_InDarkArg(dcss))? 0 : ' ';
 +    /* Swallow the trailing escape character. */
 +    zLine[--*pnLength] = 0;
 +    return 0;
 +  }
 +}
 +#endif
 +
++/*
 +** Grow the accumulation line buffer to accommodate ncNeed chars.
 +** In/out parameters pz and pna reference the buffer and its size.
 +** The buffer must eventually be sqlite3_free()'ed by the caller.
 +*/
 +static void grow_line_buffer(char **pz, int *pna, int ncNeed){
 +  if( ncNeed > *pna ){
 +    *pna += *pna + (*pna>>1) + 100;
 +    *pz = sqlite3_realloc(*pz, *pna);
 +    shell_check_oom(*pz);
 +  }
 +}
  
  /*
 -** Read input from *in and process it.  If *in==0 then input
 -** is interactive - the user is typing it it.  Otherwise, input
 -** is coming from a file or device.  A prompt is issued and history
 -** is saved only if input is interactive.  An interrupt signal will
 -** cause this routine to exit immediately, unless input is interactive.
 +** Read input from designated source (p->pInSource) and process it.
 +** If pInSource==0 then input is interactive - the user is typing it.
 +** Otherwise, input is coming from a file, stream device or string.
 +** Prompts issue and history is saved only for interactive input.
 +** An interrupt signal will cause this routine to exit immediately,
 +** with "exit demanded" code returned, unless input is interactive.
  **
 -** Return the number of errors.
 +** Returns:
 +**   0 => no errors
 +**   1 => errors>0
 +**   2 => exit demanded, no errors.
 +**   3 => exit demanded, errors>0
  */
  static int process_input(ShellState *p){
 -  char *zLine = 0;          /* A single input line */
 -  char *zSql = 0;           /* Accumulated SQL text */
 -  int nLine;                /* Length of current line */
 -  int nSql = 0;             /* Bytes of zSql[] used */
 -  int nAlloc = 0;           /* Allocated zSql[] space */
 -  int rc;                   /* Error 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) */
 -
 +  char *zLineInput = 0;  /* a line-at-a-time input buffer or usable result */
 +  char *zLineAccum = 0;  /* accumulation buffer, used for multi-line input */
 +  /* Above two pointers could be local to the group handling loop, but are
 +   * not so that the number of memory allocations can be reduced. They are
 +   * reused from one incoming group to another, realloc()'ed as needed. */
 +  int naAccum = 0;       /* tracking how big zLineAccum buffer has become */
 +  /* Some flags for ending the overall group processing loop, always 1 or 0 */
 +  u8 bErrorBail=0, bInputEnd=0, bExitDemand=0, bInterrupted=0;
 +  /* Flag to affect prompting and interrupt action */
 +  u8 bInteractive = (p->pInSource==&stdInSource && stdin_is_interactive);
 +  int nErrors = 0;       /* count of errors during execution or its prep */
 +
 +  /* Block overly-recursive or absurdly nested input redirects. */
    if( p->inputNesting==MAX_INPUT_NESTING ){
 -    /* This will be more informative in a later version. */
 -    utf8_printf(stderr,"Input nesting limit (%d) reached at line %d."
 -                " Check recursion.\n", MAX_INPUT_NESTING, p->lineno);
 +    InSource *pInSrc = p->pInSource->pFrom;
 +    const char *zLead = "Input nesting limit ("
 +      SHELL_STRINGIFY(MAX_INPUT_NESTING)") reached,";
 +    int i = 3;
 +    assert(pInSrc!=0 && MAX_INPUT_NESTING>0);
 +    while( i-->0 && pInSrc!=0 ){
 +      utf8_printf(stderr,
 +                  "%s from line %d of \"%s\"",
 +                  zLead, pInSrc->lineno, pInSrc->zSourceSay);
 +      zLead = (i%2==0)? "\n" : "";
 +      pInSrc=pInSrc->pFrom;
 +    }
 +    utf8_printf(stderr, " ...\nERROR: Check recursion.\n");
      return 1;
    }
    ++p->inputNesting;
index b42f2d534dc580489387e35fc23ef2b1f8d27488,e5a0c124e0d4af134cc57097fef0e524a95e4ba9..e0791fb29d11b16197a85bcc59137b34af284f18
@@@ -97,9 -97,9 +97,10 @@@ do_test shell3-2.6 
    catchcmd "foo.db" ".tables"
  } {0 {}}
  do_test shell3-2.7 {
--  catchcmd "foo.db" "CREATE TABLE"
- } {1 {Error: Input incomplete at line 1 of "<stdin>"}}
 -} {1 {Parse error near line 1: incomplete input}}
--
++  catchcmd "foo.db" "CREATE TABLE;"
++} {1 {Parse error near line 1: near ";": syntax error
++  CREATE TABLE;
++              ^--- error here}}
  
  #----------------------------------------------------------------------------
  #   shell3-3.*: Basic tests for processing odd SQL constructs.
index 50121832978e47e421e2164ccd94886b3868b9f3,0000000000000000000000000000000000000000..d5df53d58690bd7aa0f949a696b32dd0c7502e11
mode 100644,000000..100644
--- /dev/null
@@@ -1,249 -1,0 +1,253 @@@
-    .pa set @name Fido
-    .pa edit -t dog
-    .x dog
- }
 +# 2022 Feb 5
 +#
 +# The author disclaims copyright to this source code.  In place of
 +# a legal notice, here is a blessing:
 +#
 +#    May you do good and not evil.
 +#    May you find forgiveness for yourself and forgive others.
 +#    May you share freely, never taking more than you give.
 +#
 +#***********************************************************************
 +#
 +# The focus of this file is testing the CLI shell tool enhanced parsing,
 +# new .parameter subcommands and uses, and the new .x meta-command.
 +#
 +#
 +
 +# Test plan:
 +#
 +#   shell9-1.*: command line parsing and acting accordingly
 +#   shell9-2.*: Basic "dot" command, cross-line token parsing
 +#   shell9-3.*: .parameter set options and types
 +#   shell9-4.*: .parameter save/load operation
 +#   shell9-5.*: Ensure "dot" commands and SQL intermix ok.
 +#   shell9-6.*: .x command operation and refusal
 +#
 +set testdir [file dirname $argv0]
 +source $testdir/tester.tcl
 +set CLI [test_find_cli]
 +db close
 +forcedelete test.db test.db-journal test.db-wal
 +forcedelete x.db xn.db
 +
 +sqlite3 db test.db
 +
 +#----------------------------------------------------------------------------
 +# Test cases shell9-1.*: command line parsing and acting accordingly
 +
 +do_test shell9-1.1 {
 +  set res [catchcmd ":memory: -cmd .quit" ""]
 +} {0 {}}
 +
 +do_test shell9-1.2 {
 +  set res [catchcmd ":memory: -shxopts 1 -cmd .shxopts -cmd .quit" ""]
 +} {0 {     name    value  "-shxopts set"
 +   --------  -----  ---------------
 +    parsing    1    "-shxopts 0x01"
 +   all_opts    0    "-shxopts 0x07"}}
 +
 +do_test shell9-1.3 {
 +  set res [catchcmd ":memory: -cmd .shxopts -cmd .quit" ""]
 +} {0 {     name    value  "-shxopts set"
 +   --------  -----  ---------------
 +    parsing    0    "-shxopts 0x01"
 +   all_opts    0    "-shxopts 0x07"}}
 +
 +#----------------------------------------------------------------------------
 +# Test cases shell9-2.*: Basic "dot" command, cross-line token parsing
 +
 +set cmds ".print 'l1\nl2'\n.print 'a\\\nb'"
 +do_test shell9-2.1 {
 +  set res [catchcmd ":memory: -shxopts 1" $cmds]
 +} {0 {l1
 +l2
 +ab}}
 +
 +set cmds " .print \"l1\nl2\"\n .print \"a\\\nb\" \n# c\n  ## c"
 +do_test shell9-2.2 {
 +  set res [catchcmd ":memory: -shxopts 1" $cmds]
 +} {0 {l1
 +l2
 +ab}}
 +
 +set cmds ".echo on\n.seeargs 'a'\\\n'b'\n#!"
 +do_test shell9-2.3 {
 +  set res [catchcmd ":memory: -shxopts 1" $cmds]
 +} {0 {.seeargs 'a''b'
 +a|b|}}
 +
 +set cmds ".echo on\n.seeargs a\\\nb\n#!"
 +do_test shell9-2.4 {
 +  set res [catchcmd ":memory: -shxopts 1" $cmds]
 +} {0 {.seeargs ab
 +ab|}}
 +
 +set cmds ".echo 1\n.print \"\\\"\nq\\\"\""
 +do_test shell9-2.5 {
 +  set res [catchcmd ":memory: -shxopts 1" $cmds]
 +} {0 {.print "\"
 +q\""
 +"
 +q"}}
 +
 +#----------------------------------------------------------------------------
 +# Test cases shell9-3.*: .parameter set options and types
 +
 +set cmds {
 +.pa set -b b x'a5a5'
 +.pa set -i ii 33-11
 +.pa set -i ir 3.3-1.1
 +.pa set -n ni 3-1
 +.pa set -n nr 3.3-1.1
 +.pa set -r ri 1
 +.pa set -r rr 1.2
 +.pa set -t t 123
 +.mode list
 +select typeof(value) from temp.sqlite_parameters order by key;
 +}
 +do_test shell9-3.1 {
 +  set res [catchcmd ":memory:" $cmds]
 +} {0 {blob
 +integer
 +integer
 +integer
 +real
 +real
 +real
 +text}}
 +
 +set cmds {
 +.pa set expr 1 + 2 * 3
 +.pa set text "'1 + 2*3'"
 +.pa set -t ttext 1 + 2*3
 +.pa list
 +}
 +do_test shell9-3.2 {
 +  set res [catchcmd ":memory:" $cmds]
 +} {0 {name  usage    value
 +expr  script   7
 +text  script   '1 + 2*3'
 +ttext script   '1 + 2*3'}}
 +
 +set cmds {
 +.pa set a "'a'"
 +.pa set b "'b'"
 +.pa set c "'c'"
 +.pa unset
 +.pa list
 +.pa clear a
 +.pa list
 +.pa unset b
 +.pa list
 +.pa clear
 +.pa list
 +.pa set d "'e'"
 +.pa set e "'e'"
 +.pa unset d e
 +.pa list
 +}
 +do_test shell9-3.3 {
 +  set res [catchcmd ":memory:" $cmds]
 +} {0 {name usage    value
 +a    script   'a'
 +b    script   'b'
 +c    script   'c'
 +name usage    value
 +b    script   'b'
 +c    script   'c'
 +name usage    value
 +c    script   'c'
 +name usage    value
 +name usage    value}}
 +
 +if {$::tcl_platform(platform)=="unix"} {
 +  proc set_ed {sayWhat} {
 +    global env
 +    set env(VISUAL) "echo SELECT $sayWhat ';' >"
 +    return 1
 +  }
 +} elseif {$::tcl_platform(platform)=="windows"} {
 +  proc set_ed {sayWhat} {
 +    global env
 +    set env(VISUAL) "echo SELECT $sayWhat ; >"
 +    return 1
 +  }
 +} else { return 0 }
 +
 +if {[set_ed @name]} {
 +  set cmds {
-   } {0 Fido}
++.pa set @name Fido
++.pa edit -t dog
++.x dog
++  }
 +  do_test shell9-3.4 {
 +    set res [catchcmd ":memory: -quiet 1 -shxopts 1 -interactive" $cmds]
++  } {0 {.pa set @name Fido
++.pa edit -t dog
++.x dog
++Fido
++  }}
 +}
 +
 +#----------------------------------------------------------------------------
 +# Test cases shell9-4.*: .parameter save/load operation
 +
 +set cmds {
 +  .pa set -t x '.print Ex'
 +  .pa set -i $n 7
 +  .pa save xn.db
 +  .pa save x.db x
 +  .pa clear
 +  .pa load xn.db
 +  .pa list
 +  .pa clear
 +  .pa load x.db
 +  .pa list
 +  .pa clear
 +  .pa load xn.db $n
 +  .pa list
 +}
 +do_test shell9-4.1 {
 +  set res [catchcmd ":memory: -shxopts 1" $cmds]
 +} {0 {name usage    value
 +$n   binding  7
 +x    script   '.print Ex'
 +name usage    value
 +x    script   '.print Ex'
 +name usage    value
 +$n   binding  7}}
 +
 +forcedelete x.db xn.db
 +
 +#----------------------------------------------------------------------------
 +# Test cases shell9-5.*: Ensure "dot" commands and SQL intermix ok.
 +
 +set cmds {
 + .pa set -t mixed "
 + .print Hi.
 + select 'Hi.';
 + .print 'Good\
 + Bye.'
 + select 'Good'||
 + ' Bye.';
 + "
 + .x mixed
 +}
 +do_test shell9-5.1 {
 +  set res [catchcmd ":memory: -shxopts 1" $cmds]
 +} {0 {Hi.
 +Hi.
 +Good Bye.
 +Good Bye.}}
 +
 +#----------------------------------------------------------------------------
 +# Test cases shell9-6.*: .x command operation and refusal
 +set cmds {
 + .pa set -t $v '.print Ok'
 + .x $v
 +}
 +do_test shell9-6.1 {
 +  set res [catchcmd ":memory: -bail -shxopts 1" $cmds]
 +} {1 {Skipping badly named $v. Run ".help x"}}
 +
 +finish_test