]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Merge enhancements from trunk.
authordrh <drh@noemail.net>
Sat, 23 Dec 2017 18:34:49 +0000 (18:34 +0000)
committerdrh <drh@noemail.net>
Sat, 23 Dec 2017 18:34:49 +0000 (18:34 +0000)
FossilOrigin-Name: 150f07fec1e6d1fc0601820d717d8712fc513fe0d4bed67c8679eb51bca30d53

1  2 
Makefile.msc
main.mk
manifest
manifest.uuid
src/shell.c.in

diff --cc Makefile.msc
Simple merge
diff --cc main.mk
index 07cab6db1d77b3418270a956c736708b5ed932ab,869245044131a3350c4e6bb091a01c971fe411b1..ea0ff8bc2e9938423b1ab42e14fe3d23d51539bd
+++ b/main.mk
@@@ -691,7 -693,8 +693,9 @@@ SHELL_SRC = 
        $(TOP)/ext/misc/shathree.c \
        $(TOP)/ext/misc/fileio.c \
        $(TOP)/ext/misc/completion.c \
-       $(TOP)/ext/misc/sqlar.c
++      $(TOP)/ext/misc/sqlar.c \
+       $(TOP)/ext/expert/sqlite3expert.c \
+       $(TOP)/ext/expert/sqlite3expert.h
  
  shell.c:      $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl
        tclsh $(TOP)/tool/mkshellc.tcl >shell.c
diff --cc manifest
index bbdf7f6ae0a9e072fc1f1c9638221272fdad4eb6,f187cc02903241cf493d054218090e8a14061c7f..0293660ba45d6b38d08149e12ea0647006ecf030
+++ b/manifest
@@@ -1,10 -1,8 +1,10 @@@
- C Do\snot\suse\sthe\scompress()\sand\suncompress()\sfunctions\sin\sext/misc/compress.c\s-\nthey\sare\snot\squite\scompatible\swith\sthe\sspec.\sInstead\suse\snew\sfunctions\sin\next/misc/sqlar.c.
- D 2017-12-16T19:11:26.054
 -C Simplification\sto\sthe\serror\shandling\slogic\sin\sthe\sextension\sloader.
 -D 2017-12-23T14:39:36.160
++C Merge\senhancements\sfrom\strunk.
++D 2017-12-23T18:34:49.545
 +F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 +F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
- F Makefile.in 6a879cbf01e37f9eac131414955f71774b566502d9a57ded1b8585b507503cb8
+ F Makefile.in ceb40bfcb30ebba8e1202b34c56ff7e13e112f9809e2381d99be32c2726058f5
  F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
- F Makefile.msc 72b73110e9a9e95681fbc84f5c43e85f8a506ccbac3a2455df1d3f20ac27df64
 -F Makefile.msc 6480671f7c129e61208d69492b3c71ce4310d49fceac83cfb17f1c081e242b69
++F Makefile.msc 5a740138ce23a88c33a02a65ffea45d129edf458afdc7547a2bdfd54f1ad6801
  F README.md eeae1e552f93ef72ef7c5b8f6647b368a001c28820ad1df179d3dae602bef681
  F VERSION 0c10cdfed866fdd2d80434f64f042c3330f1daaed12e54287beb104f04b3faaf
  F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
@@@ -284,13 -288,12 +290,13 @@@ F ext/misc/rot13.c 540a169cb0d74f15522a
  F ext/misc/scrub.c 1c5bfb8b0cd18b602fcb55755e84abf0023ac2fb
  F ext/misc/series.c f3c0dba5c5c749ce1782b53076108f87cf0b71041eb6023f727a9c50681da564
  F ext/misc/sha1.c 0b9e9b855354910d3ca467bf39099d570e73db56
- F ext/misc/shathree.c fa185d7aee0ad0aca5e091b4a2db7baff11796170e5793b5de99e511a13af448
+ F ext/misc/shathree.c 9e960ba50483214c6a7a4b1517f8d8cef799e9db381195178c3fd3ad207e10c0
  F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52
  F ext/misc/spellfix.c 41cf26c6b89fcaa8798ae10ae64d39c1f1d9d6995152e545bd491c13058b8fac
 +F ext/misc/sqlar.c d355cd8b6e7280d2f61d4737672922acb512a2ab1cee52399ffb88980476e31c
  F ext/misc/stmt.c 6f16443abb3551e3f5813bb13ba19a30e7032830015b0f92fe0c0453045c0a11
  F ext/misc/totype.c 4a167594e791abeed95e0a8db028822b5e8fe512
- F ext/misc/unionvtab.c 1e0ebc5078e1a916db191bcd88f87e94ea7ba4aa563ee30ff706261cb4b39461
+ F ext/misc/unionvtab.c de36c2c45583d68f99e45b392311967066b02e2651d05697da783698b245b387
  F ext/misc/vfslog.c fe40fab5c077a40477f7e5eba994309ecac6cc95
  F ext/misc/vfsstat.c bf10ef0bc51e1ad6756629e1edb142f7a8db1178
  F ext/misc/vtablog.c 31d0d8f4406795679dcd3a67917c213d3a2a5fb3ea5de35f6e773491ed7e13c9
@@@ -398,7 -401,7 +404,7 @@@ F ext/userauth/userauth.c 3410be31283ab
  F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
  F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
  F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
- F main.mk f5f8e5e6bdd9ab3dabd94c6cf929813a601d5dbf312197fe463190c7d927c27c
 -F main.mk 50bac9920024b5485f06398b3980f09e97ab28cd4b5b6dcd829d2a5e3ce22e7a
++F main.mk eef9a9918485b5df70d7a69ed3d0e1dd182bf714740122a144905a59d42da5c6
  F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
  F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
  F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
@@@ -475,12 -478,12 +481,12 @@@ F src/printf.c 9506b4b96e59c0467047155f
  F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384
  F src/resolve.c bbee7e31d369a18a2f4836644769882e9c5d40ef4a3af911db06410b65cb3730
  F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
- F src/select.c 17e220191860a64a18c084141e1a8b7309e166a6f2d42c02021af27ea080d157
- F src/shell.c.in ad8c13b9dee606d1ef11226f90d8e4e084d75986f96e6852e8e5b1c54aa4f0aa
- F src/sqlite.h.in 364515dd186285f3c01f5cab42e7db7edc47c70e87b6a25de389a2e6b8c413fd
+ F src/select.c 8b22abe193e4d8243befa2038e4ae2405802fed1c446e5e502d11f652e09ba74
 -F src/shell.c.in 339169a3d1307b5566ebe9ce15832d03439206106724c78cc3d9125a7b851795
++F src/shell.c.in e739db2809b9ad38ac389a89bead6986542a679ca33b153aef5850856998b525
+ F src/sqlite.h.in 2126192945019d4cdce335cb236b440a05ec75c93e4cd94c9c6d6e7fcc654cc4
  F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
  F src/sqlite3ext.h c02d628cca67f3889c689d82d25c3eb45e2c155db08e4c6089b5840d64687d34
- F src/sqliteInt.h 55b8e7da85947eb61b13d4d2523ccdda7800a13e987c3fc4ca73d8518bbf02fa
+ F src/sqliteInt.h 003b78433baae4e5c997f99f2f9cf98d90754f256baeacb32f8189569a48251f
  F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6a76b
  F src/status.c 9737ed017279a9e0c5da748701c3c7bf1e8ae0dae459aad20dd64fcff97a7e35
  F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34
@@@ -1684,7 -1687,7 +1691,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 7b51269caebe1492885fe9b965892f49a3f8bdb1d666b0203d594c30f9e83938
- R 6fdfedcd54ee170b8eb014864253aebf
- U dan
- Z 7252a7eb50ba8ff9515237c2fa79b757
 -P 05fee1a21ea398f1e4d6f1cf361657eff25ed6cd8f85ab398262dcfd30da57e9
 -R 7fbfe3e61eca395fa5baf5f121a4d2ad
++P 7652b3c2374084047b6c1da3e525e0cac34fe220597f81e793bc4fd9f33358da 07c773148d8db185fa54991df09298b64f4fef28879e6c9395759265e8183977
++R 0cdaf396f246262d8fd00807c02fe094
+ U drh
 -Z d82d9c01768cefc5beb206b92e1398bf
++Z d44ebee6c3c37877974b2b88daec7bfe
diff --cc manifest.uuid
index 4125d09f9b17d33e461943af4c176aae76912b24,8454418cd58e230e62abeb6701fe7cd63bee5ff7..3fe37939e4a20d91365a3169c96398f1f39a90c7
@@@ -1,1 -1,1 +1,1 @@@
- 7652b3c2374084047b6c1da3e525e0cac34fe220597f81e793bc4fd9f33358da
 -07c773148d8db185fa54991df09298b64f4fef28879e6c9395759265e8183977
++150f07fec1e6d1fc0601820d717d8712fc513fe0d4bed67c8679eb51bca30d53
diff --cc src/shell.c.in
index adfbaa96d1a83eb240985e1e9e1c19262c500652,13b1fcde396fafc2a2ac18650b16093393b569b8..444df588995bcd0550426e0818ca11dab0ffbc91
@@@ -796,9 -796,8 +796,11 @@@ static void shellAddSchemaName
  INCLUDE ../ext/misc/shathree.c
  INCLUDE ../ext/misc/fileio.c
  INCLUDE ../ext/misc/completion.c
 +#ifdef SQLITE_HAVE_ZLIB
 +INCLUDE ../ext/misc/sqlar.c
 +#endif
+ INCLUDE ../ext/expert/sqlite3expert.h
+ INCLUDE ../ext/expert/sqlite3expert.c
  
  #if defined(SQLITE_ENABLE_SESSION)
  /*
@@@ -4074,654 -4169,65 +4175,711 @@@ static int lintDotCommand
    return SQLITE_ERROR;
  }
  
 +static void shellPrepare(
 +  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", 
 +          sqlite3_errmsg(db), sqlite3_errcode(db)
 +      );
 +      *pRc = rc;
 +    }
 +  }
 +}
 +
 +static void shellPreparePrintf(
 +  sqlite3 *db, 
 +  int *pRc, 
 +  sqlite3_stmt **ppStmt,
 +  const char *zFmt, 
 +  ...
 +){
 +  *ppStmt = 0;
 +  if( *pRc==SQLITE_OK ){
 +    va_list ap;
 +    char *z;
 +    va_start(ap, zFmt);
 +    z = sqlite3_vmprintf(zFmt, ap);
 +    if( z==0 ){
 +      *pRc = SQLITE_NOMEM;
 +    }else{
 +      shellPrepare(db, pRc, z, ppStmt);
 +      sqlite3_free(z);
 +    }
 +  }
 +}
 +
 +static void shellFinalize(
 +  int *pRc, 
 +  sqlite3_stmt *pStmt
 +){
 +  if( pStmt ){
 +    sqlite3 *db = sqlite3_db_handle(pStmt);
 +    int rc = sqlite3_finalize(pStmt);
 +    if( *pRc==SQLITE_OK ){
 +      if( rc!=SQLITE_OK ){
 +        raw_printf(stderr, "SQL error: %s\n", sqlite3_errmsg(db));
 +      }
 +      *pRc = rc;
 +    }
 +  }
 +}
 +
 +static void shellReset(
 +  int *pRc, 
 +  sqlite3_stmt *pStmt
 +){
 +  int rc = sqlite3_reset(pStmt);
 +  if( *pRc==SQLITE_OK ) *pRc = rc;
 +}
 +
 +/* 
 +** Structure representing a single ".ar" command.
 +*/
 +typedef struct ArCommand ArCommand;
 +struct ArCommand {
 +  int eCmd;                       /* An AR_CMD_* value */
 +  const char *zFile;              /* --file argument, or NULL */
 +  const char *zDir;               /* --directory argument, or NULL */
 +  int bVerbose;                   /* True if --verbose */
 +  int nArg;                       /* Number of command arguments */
 +  char **azArg;                   /* Array of command arguments */
 +};
 +
 +/*
 +** Print a usage message for the .ar command to stderr and return SQLITE_ERROR.
 +*/
 +static int arUsage(FILE *f){
 +  raw_printf(f,
 +"\n"
 +"Usage: .ar [OPTION...] [FILE...]\n"
 +"The .ar command manages sqlar archives.\n"
 +"\n"
 +"Examples:\n"
 +"  .ar -cf archive.sar foo bar    # Create archive.sar from files foo and bar\n"
 +"  .ar -tf archive.sar            # List members of archive.sar\n"
 +"  .ar -xvf archive.sar           # Verbosely extract files from archive.sar\n"
 +"\n"
 +"Each command line must feature exactly one command option:\n"
 +"  -c, --create               Create a new archive\n"
 +"  -u, --update               Update or add files to an existing archive\n"
 +"  -t, --list                 List contents of archive\n"
 +"  -x, --extract              Extract files from archive\n"
 +"\n"
 +"And zero or more optional options:\n"
 +"  -v, --verbose              Print each filename as it is processed\n"
 +"  -f FILE, --file FILE       Operate on archive FILE (default is current db)\n"
 +"  -C DIR, --directory DIR    Change to directory DIR to read/extract files\n"
 +"\n"
 +"See also: http://sqlite.org/cli.html#sqlar_archive_support\n"
 +"\n"
 +);
 +  return SQLITE_ERROR;
 +}
 +
 +/*
 +** Print an error message for the .ar command to stderr and return 
 +** SQLITE_ERROR.
 +*/
 +static int arErrorMsg(const char *zFmt, ...){
 +  va_list ap;
 +  char *z;
 +  va_start(ap, zFmt);
 +  z = sqlite3_vmprintf(zFmt, ap);
 +  va_end(ap);
 +  raw_printf(stderr, "Error: %s (try \".ar --help\")\n", z);
 +  sqlite3_free(z);
 +  return SQLITE_ERROR;
 +}
 +
 +/*
 +** Values for ArCommand.eCmd.
 +*/
 +#define AR_CMD_CREATE       1
 +#define AR_CMD_EXTRACT      2
 +#define AR_CMD_LIST         3
 +#define AR_CMD_UPDATE       4
 +#define AR_CMD_HELP         5
 +
 +/*
 +** Other (non-command) switches.
 +*/
 +#define AR_SWITCH_VERBOSE   6
 +#define AR_SWITCH_FILE      7
 +#define AR_SWITCH_DIRECTORY 8
 +
 +static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){
 +  switch( eSwitch ){
 +    case AR_CMD_CREATE:
 +    case AR_CMD_EXTRACT:
 +    case AR_CMD_LIST:
 +    case AR_CMD_UPDATE:
 +    case AR_CMD_HELP:
 +      if( pAr->eCmd ){
 +        return arErrorMsg("multiple command options");
 +      }
 +      pAr->eCmd = eSwitch;
 +      break;
 +
 +    case AR_SWITCH_VERBOSE:
 +      pAr->bVerbose = 1;
 +      break;
 +
 +    case AR_SWITCH_FILE:
 +      pAr->zFile = zArg;
 +      break;
 +    case AR_SWITCH_DIRECTORY:
 +      pAr->zDir = zArg;
 +      break;
 +  }
 +
 +  return SQLITE_OK;
 +}
 +
 +/*
 +** 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 
 +** SQLITE_ERROR returned.
 +*/
 +static int arParseCommand(
 +  char **azArg,                   /* Array of arguments passed to dot command */
 +  int nArg,                       /* Number of entries in azArg[] */
 +  ArCommand *pAr                  /* Populate this object */
 +){
 +  struct ArSwitch {
 +    char cShort;
 +    const char *zLong;
 +    int eSwitch;
 +    int bArg;
 +  } aSwitch[] = {
 +    { 'c', "create",    AR_CMD_CREATE, 0 },
 +    { 'x', "extract",   AR_CMD_EXTRACT, 0 },
 +    { 't', "list",      AR_CMD_LIST, 0 },
 +    { 'u', "update",    AR_CMD_UPDATE, 0 },
 +    { 'h', "help",      AR_CMD_HELP, 0 },
 +    { 'v', "verbose",   AR_SWITCH_VERBOSE, 0 },
 +    { 'f', "file",      AR_SWITCH_FILE, 1 },
 +    { 'C', "directory", AR_SWITCH_DIRECTORY, 1 }
 +  };
 +  int nSwitch = sizeof(aSwitch) / sizeof(struct ArSwitch);
 +  struct ArSwitch *pEnd = &aSwitch[nSwitch];
 +
 +  if( nArg<=1 ){
 +    return arUsage(stderr);
 +  }else{
 +    char *z = azArg[1];
 +    memset(pAr, 0, sizeof(ArCommand));
 +
 +    if( z[0]!='-' ){
 +      /* Traditional style [tar] invocation */
 +      int i;
 +      int iArg = 2;
 +      for(i=0; z[i]; i++){
 +        const char *zArg = 0;
 +        struct ArSwitch *pOpt;
 +        for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
 +          if( z[i]==pOpt->cShort ) break;
 +        }
 +        if( pOpt==pEnd ){
 +          return arErrorMsg("unrecognized option: %c", z[i]);
 +        }
 +        if( pOpt->bArg ){
 +          if( iArg>=nArg ){
 +            return arErrorMsg("option requires an argument: %c",z[i]);
 +          }
 +          zArg = azArg[iArg++];
 +        }
 +        if( arProcessSwitch(pAr, pOpt->eSwitch, zArg) ) return SQLITE_ERROR;
 +      }
 +      pAr->nArg = nArg-iArg;
 +      if( pAr->nArg>0 ){
 +        pAr->azArg = &azArg[iArg];
 +      }
 +    }else{
 +      /* Non-traditional invocation */
 +      int iArg;
 +      for(iArg=1; iArg<nArg; iArg++){
 +        int n;
 +        z = azArg[iArg];
 +        if( z[0]!='-' ){
 +          /* All remaining command line words are command arguments. */
 +          pAr->azArg = &azArg[iArg];
 +          pAr->nArg = nArg-iArg;
 +          break;
 +        }
 +        n = strlen(z);
 +
 +        if( z[1]!='-' ){
 +          int i;
 +          /* One or more short options */
 +          for(i=1; i<n; i++){
 +            const char *zArg = 0;
 +            struct ArSwitch *pOpt;
 +            for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
 +              if( z[i]==pOpt->cShort ) break;
 +            }
 +            if( pOpt==pEnd ){
 +              return arErrorMsg("unrecognized option: %c\n", z[i]);
 +            }
 +            if( pOpt->bArg ){
 +              if( i<(n-1) ){
 +                zArg = &z[i+1];
 +                i = n;
 +              }else{
 +                if( iArg>=(nArg-1) ){
 +                  return arErrorMsg("option requires an argument: %c\n",z[i]);
 +                }
 +                zArg = azArg[++iArg];
 +              }
 +            }
 +            if( arProcessSwitch(pAr, pOpt->eSwitch, zArg) ) return SQLITE_ERROR;
 +          }
 +        }else if( z[2]=='\0' ){
 +          /* A -- option, indicating that all remaining command line words
 +          ** are command arguments.  */
 +          pAr->azArg = &azArg[iArg+1];
 +          pAr->nArg = nArg-iArg-1;
 +          break;
 +        }else{
 +          /* A long option */
 +          const char *zArg = 0;             /* Argument for option, if any */
 +          struct ArSwitch *pMatch = 0;      /* Matching option */
 +          struct ArSwitch *pOpt;            /* Iterator */
 +          for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
 +            const char *zLong = pOpt->zLong;
 +            if( (n-2)<=strlen(zLong) && 0==memcmp(&z[2], zLong, n-2) ){
 +              if( pMatch ){
 +                return arErrorMsg("ambiguous option: %s",z);
 +              }else{
 +                pMatch = pOpt;
 +              }
 +            }
 +          }
 +
 +          if( pMatch==0 ){
 +            return arErrorMsg("unrecognized option: %s", z);
 +          }
 +          if( pMatch->bArg ){
 +            if( iArg>=(nArg-1) ){
 +              return arErrorMsg("option requires an argument: %s", z);
 +            }
 +            zArg = azArg[++iArg];
 +          }
 +          if( arProcessSwitch(pAr, pMatch->eSwitch, zArg) ) return SQLITE_ERROR;
 +        }
 +      }
 +    }
 +  }
 +
 +  return SQLITE_OK;
 +}
 +
 +/*
 +** This function assumes that all arguments within the ArCommand.azArg[]
 +** array refer to archive members, as for the --extract or --list commands. 
 +** It checks that each of them are present. If any specified file is not
 +** present in the archive, an error is printed to stderr and an error
 +** code returned. Otherwise, if all specified arguments are present in
 +** the archive, SQLITE_OK is returned.
 +**
 +** This function strips any trailing '/' characters from each argument.
 +** This is consistent with the way the [tar] command seems to work on
 +** Linux.
 +*/
 +static int arCheckEntries(sqlite3 *db, ArCommand *pAr){
 +  int rc = SQLITE_OK;
 +  if( pAr->nArg ){
 +    int i;
 +    sqlite3_stmt *pTest = 0;
 +
 +    shellPrepare(db, &rc, "SELECT name FROM sqlar WHERE name=?", &pTest);
 +    for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){
 +      char *z = pAr->azArg[i];
 +      int n = strlen(z);
 +      int bOk = 0;
 +      while( n>0 && z[n-1]=='/' ) n--;
 +      z[n] = '\0';
 +      sqlite3_bind_text(pTest, 1, z, -1, SQLITE_STATIC);
 +      if( SQLITE_ROW==sqlite3_step(pTest) ){
 +        bOk = 1;
 +      }
 +      shellReset(&rc, pTest);
 +      if( rc==SQLITE_OK && bOk==0 ){
 +        raw_printf(stderr, "not found in archive: %s\n", z);
 +        rc = SQLITE_ERROR;
 +      }
 +    }
 +    shellFinalize(&rc, pTest);
 +  }
 +
 +  return rc;
 +}
 +
 +/*
 +** Format a WHERE clause that can be used against the "sqlar" table to
 +** identify all archive members that match the command arguments held
 +** in (*pAr). Leave this WHERE clause in (*pzWhere) before returning.
 +** The caller is responsible for eventually calling sqlite3_free() on
 +** any non-NULL (*pzWhere) value.
 +*/
 +static void arWhereClause(
 +  int *pRc, 
 +  ArCommand *pAr, 
 +  char **pzWhere                  /* OUT: New WHERE clause */
 +){
 +  char *zWhere = 0;
 +  if( *pRc==SQLITE_OK ){
 +    if( pAr->nArg==0 ){
 +      zWhere = sqlite3_mprintf("1");
 +    }else{
 +      int i;
 +      const char *zSep = "";
 +      for(i=0; i<pAr->nArg; i++){
 +        const char *z = pAr->azArg[i];
 +        zWhere = sqlite3_mprintf(
 +            "%z%s name = '%q' OR name BETWEEN '%q/' AND '%q0'", 
 +            zWhere, zSep, z, z, z
 +            );
 +        if( zWhere==0 ){
 +          *pRc = SQLITE_NOMEM;
 +          break;
 +        }
 +        zSep = " OR ";
 +      }
 +    }
 +  }
 +  *pzWhere = zWhere;
 +}
 +
 +/*
 +** Implementation of .ar "lisT" command. 
 +*/
 +static int arListCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){
 +  const char *zSql = "SELECT name FROM sqlar WHERE %s"; 
 +  char *zWhere = 0;
 +  sqlite3_stmt *pSql = 0;
 +  int rc;
 +
 +  rc = arCheckEntries(db, pAr);
 +  arWhereClause(&rc, pAr, &zWhere);
 +
 +  shellPreparePrintf(db, &rc, &pSql, zSql, zWhere);
 +  while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
 +    raw_printf(p->out, "%s\n", sqlite3_column_text(pSql, 0));
 +  }
 +  return rc;
 +}
 +
 +
 +/*
 +** Implementation of .ar "eXtract" command. 
 +*/
 +static int arExtractCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){
 +  const char *zSql1 = 
 +    "SELECT "
 +    "  :1 || name, "
 +    "  writefile(:1 || name, sqlar_uncompress(data, sz), mode, mtime) "
 +    "FROM sqlar WHERE (%s) AND (data IS NULL OR :2 = 0)";
 +
 +  struct timespec times[2];
 +  sqlite3_stmt *pSql = 0;
 +  int rc = SQLITE_OK;
 +  char *zDir = 0;
 +  char *zWhere = 0;
 +  int i;
 +
 +  /* If arguments are specified, check that they actually exist within
 +  ** the archive before proceeding. And formulate a WHERE clause to
 +  ** match them.  */
 +  rc = arCheckEntries(db, pAr);
 +  arWhereClause(&rc, pAr, &zWhere);
 +
 +  if( rc==SQLITE_OK ){
 +    if( pAr->zDir ){
 +      zDir = sqlite3_mprintf("%s/", pAr->zDir);
 +    }else{
 +      zDir = sqlite3_mprintf("");
 +    }
 +    if( zDir==0 ) rc = SQLITE_NOMEM;
 +  }
 +
 +  memset(times, 0, sizeof(times));
 +  times[0].tv_sec = time(0);
 +
 +  shellPreparePrintf(db, &rc, &pSql, zSql1, zWhere);
 +  if( rc==SQLITE_OK ){
 +    sqlite3_bind_text(pSql, 1, zDir, -1, SQLITE_STATIC);
 +
 +    /* Run the SELECT statement twice. The first time, writefile() is called
 +    ** for all archive members that should be extracted. The second time,
 +    ** only for the directories. This is because the timestamps for
 +    ** extracted directories must be reset after they are populated (as
 +    ** populating them changes the timestamp).  */
 +    for(i=0; i<2; i++){
 +      sqlite3_bind_int(pSql, 2, i);
 +      while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
 +        if( i==0 && pAr->bVerbose ){
 +          raw_printf(p->out, "%s\n", sqlite3_column_text(pSql, 0));
 +        }
 +      }
 +      shellReset(&rc, pSql);
 +    }
 +    shellFinalize(&rc, pSql);
 +  }
 +
 +  sqlite3_free(zDir);
 +  sqlite3_free(zWhere);
 +  return rc;
 +}
 +
 +
 +/*
 +** Implementation of .ar "create" and "update" commands.
 +**
 +** Create the "sqlar" table in the database if it does not already exist.
 +** Then add each file in the azFile[] array to the archive. Directories
 +** are added recursively. If argument bVerbose is non-zero, a message is
 +** printed on stdout for each file archived.
 +**
 +** The create command is the same as update, except that it drops
 +** any existing "sqlar" table before beginning.
 +*/
 +static int arCreateUpdate(
 +  ShellState *p,                  /* Shell state pointer */
 +  sqlite3 *db,
 +  ArCommand *pAr,                 /* Command arguments and options */
 +  int bUpdate
 +){
 +  const char *zSql = "SELECT name, mode, mtime, data FROM fsdir(?, ?)";
 +  const char *zCreate = 
 +      "CREATE TABLE IF NOT EXISTS sqlar("
 +      "name TEXT PRIMARY KEY,  -- name of the file\n"
 +      "mode INT,               -- access permissions\n"
 +      "mtime INT,              -- last modification time\n"
 +      "sz INT,                 -- original file size\n"
 +      "data BLOB               -- compressed content\n"
 +  ")";
 +  const char *zDrop = "DROP TABLE IF EXISTS sqlar";
 +  const char *zInsert = "REPLACE INTO sqlar VALUES(?,?,?,?,sqlar_compress(?))";
 +
 +  sqlite3_stmt *pStmt = 0;        /* Directory traverser */
 +  sqlite3_stmt *pInsert = 0;      /* Compilation of zInsert */
 +  int i;                          /* For iterating through azFile[] */
 +  int rc;                         /* Return code */
 +
 +  rc = sqlite3_exec(db, "SAVEPOINT ar;", 0, 0, 0);
 +  if( rc!=SQLITE_OK ) return rc;
 +
 +  if( bUpdate==0 ){
 +    rc = sqlite3_exec(db, zDrop, 0, 0, 0);
 +    if( rc!=SQLITE_OK ) return rc;
 +  }
 +
 +  rc = sqlite3_exec(db, zCreate, 0, 0, 0);
 +  shellPrepare(db, &rc, zInsert, &pInsert);
 +  shellPrepare(db, &rc, zSql, &pStmt);
 +  sqlite3_bind_text(pStmt, 2, pAr->zDir, -1, SQLITE_STATIC);
 +
 +  for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){
 +    sqlite3_bind_text(pStmt, 1, pAr->azArg[i], -1, SQLITE_STATIC);
 +    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
 +      int sz;
 +      const char *zName = (const char*)sqlite3_column_text(pStmt, 0);
 +      int mode = sqlite3_column_int(pStmt, 1);
 +      unsigned int mtime = sqlite3_column_int(pStmt, 2);
 +
 +      if( pAr->bVerbose ){
 +        raw_printf(p->out, "%s\n", zName);
 +      }
 +
 +      sqlite3_bind_text(pInsert, 1, zName, -1, SQLITE_STATIC);
 +      sqlite3_bind_int(pInsert, 2, mode);
 +      sqlite3_bind_int64(pInsert, 3, (sqlite3_int64)mtime);
 +
 +      if( S_ISDIR(mode) ){
 +        sz = 0;
 +        sqlite3_bind_null(pInsert, 5);
 +      }else{
 +        sqlite3_bind_value(pInsert, 5, sqlite3_column_value(pStmt, 3));
 +        if( S_ISLNK(mode) ){
 +          sz = -1;
 +        }else{
 +          sz = sqlite3_column_bytes(pStmt, 3);
 +        }
 +      }
 +
 +      sqlite3_bind_int(pInsert, 4, sz);
 +      sqlite3_step(pInsert);
 +      rc = sqlite3_reset(pInsert);
 +    }
 +    shellReset(&rc, pStmt);
 +  }
 +
 +  if( rc!=SQLITE_OK ){
 +    sqlite3_exec(db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0);
 +  }else{
 +    rc = sqlite3_exec(db, "RELEASE ar;", 0, 0, 0);
 +  }
 +  shellFinalize(&rc, pStmt);
 +  shellFinalize(&rc, pInsert);
 +  return rc;
 +}
 +
 +/*
 +** Implementation of .ar "Create" command. 
 +**
 +** Create the "sqlar" table in the database if it does not already exist.
 +** Then add each file in the azFile[] array to the archive. Directories
 +** are added recursively. If argument bVerbose is non-zero, a message is
 +** printed on stdout for each file archived.
 +*/
 +static int arCreateCommand(
 +  ShellState *p,                  /* Shell state pointer */
 +  sqlite3 *db,
 +  ArCommand *pAr                  /* Command arguments and options */
 +){
 +  return arCreateUpdate(p, db, pAr, 0);
 +}
 +
 +/*
 +** Implementation of .ar "Update" command. 
 +*/
 +static int arUpdateCmd(ShellState *p, sqlite3 *db, ArCommand *pAr){
 +  return arCreateUpdate(p, db, pAr, 1);
 +}
 +
 +
 +/*
 +** Implementation of ".ar" dot command.
 +*/
 +static int arDotCommand(
 +  ShellState *pState,             /* Current shell tool state */
 +  char **azArg,                   /* Array of arguments passed to dot command */
 +  int nArg                        /* Number of entries in azArg[] */
 +){
 +  ArCommand cmd;
 +  int rc;
 +  rc = arParseCommand(azArg, nArg, &cmd);
 +  if( rc==SQLITE_OK ){
 +    sqlite3 *db = 0;              /* Database handle to use as archive */
 +
 +    if( cmd.zFile ){
 +      int flags;
 +      if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_UPDATE ){
 +        flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE;
 +      }else{
 +        flags = SQLITE_OPEN_READONLY;
 +      }
 +      rc = sqlite3_open_v2(cmd.zFile, &db, flags, 0);
 +      if( rc!=SQLITE_OK ){
 +        raw_printf(stderr, "cannot open file: %s (%s)\n", 
 +            cmd.zFile, sqlite3_errmsg(db)
 +        );
 +        sqlite3_close(db);
 +        return rc;
 +      }
 +      sqlite3_fileio_init(db, 0, 0);
 +      sqlite3_sqlar_init(db, 0, 0);
 +    }else{
 +      db = pState->db;
 +    }
 +
 +    switch( cmd.eCmd ){
 +      case AR_CMD_CREATE:
 +        rc = arCreateCommand(pState, db, &cmd);
 +        break;
 +
 +      case AR_CMD_EXTRACT:
 +        rc = arExtractCommand(pState, db, &cmd);
 +        break;
 +
 +      case AR_CMD_LIST:
 +        rc = arListCommand(pState, db, &cmd);
 +        break;
 +
 +      case AR_CMD_HELP:
 +        arUsage(pState->out);
 +        break;
 +
 +      default:
 +        assert( cmd.eCmd==AR_CMD_UPDATE );
 +        rc = arUpdateCmd(pState, db, &cmd);
 +        break;
 +    }
 +
 +    if( cmd.zFile ){
 +      sqlite3_close(db);
 +    }
 +  }
 +
 +  return rc;
 +}
 +
+ /*
+ ** Implementation of ".expert" dot command.
+ */
+ static int expertDotCommand(
+   ShellState *pState,             /* Current shell tool state */
+   char **azArg,                   /* Array of arguments passed to dot command */
+   int nArg                        /* Number of entries in azArg[] */
+ ){
+   int rc = SQLITE_OK;
+   char *zErr = 0;
+   int i;
+   int iSample = 0;
+   assert( pState->expert.pExpert==0 );
+   memset(&pState->expert, 0, sizeof(ExpertInfo));
+   for(i=1; rc==SQLITE_OK && i<nArg; i++){
+     char *z = azArg[i];
+     int n;
+     if( z[0]=='-' && z[1]=='-' ) z++;
+     n = strlen(z);
+     if( n>=2 && 0==strncmp(z, "-verbose", n) ){
+       pState->expert.bVerbose = 1;
+     }
+     else if( n>=2 && 0==strncmp(z, "-sample", n) ){
+       if( i==(nArg-1) ){
+         raw_printf(stderr, "option requires an argument: %s\n", z);
+         rc = SQLITE_ERROR;
+       }else{
+         iSample = (int)integerValue(azArg[++i]);
+         if( iSample<0 || iSample>100 ){
+           raw_printf(stderr, "value out of range: %s\n", azArg[i]);
+           rc = SQLITE_ERROR;
+         }
+       }
+     }
+     else{
+       raw_printf(stderr, "unknown option: %s\n", z);
+       rc = SQLITE_ERROR;
+     }
+   }
+   if( rc==SQLITE_OK ){
+     pState->expert.pExpert = sqlite3_expert_new(pState->db, &zErr);
+     if( pState->expert.pExpert==0 ){
+       raw_printf(stderr, "sqlite3_expert_new: %s\n", zErr);
+       rc = SQLITE_ERROR;
+     }else{
+       sqlite3_expert_config(
+           pState->expert.pExpert, EXPERT_CONFIG_SAMPLE, iSample
+       );
+     }
+   }
+   return rc;
+ }
  
 -
  /*
  ** If an input line begins with "." then invoke this routine to
  ** process that line.