- 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
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
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
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
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
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)
/*
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.