From: drh Date: Sat, 23 Dec 2017 18:34:49 +0000 (+0000) Subject: Merge enhancements from trunk. X-Git-Tag: version-3.22.0~108^2~21 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=60ba57d9c5c90c187705fa808549197ff4fefb1e;p=thirdparty%2Fsqlite.git Merge enhancements from trunk. FossilOrigin-Name: 150f07fec1e6d1fc0601820d717d8712fc513fe0d4bed67c8679eb51bca30d53 --- 60ba57d9c5c90c187705fa808549197ff4fefb1e diff --cc main.mk index 07cab6db1d,8692450441..ea0ff8bc2e --- a/main.mk +++ 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 bbdf7f6ae0,f187cc0290..0293660ba4 --- a/manifest +++ 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 4125d09f9b,8454418cd5..3fe37939e4 --- a/manifest.uuid +++ b/manifest.uuid @@@ -1,1 -1,1 +1,1 @@@ - 7652b3c2374084047b6c1da3e525e0cac34fe220597f81e793bc4fd9f33358da -07c773148d8db185fa54991df09298b64f4fef28879e6c9395759265e8183977 ++150f07fec1e6d1fc0601820d717d8712fc513fe0d4bed67c8679eb51bca30d53 diff --cc src/shell.c.in index adfbaa96d1,13b1fcde39..444df58899 --- a/src/shell.c.in +++ b/src/shell.c.in @@@ -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]; pOptcShort ) 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; iArgazArg = &azArg[iArg]; + pAr->nArg = nArg-iArg; + break; + } + n = strlen(z); + + if( z[1]!='-' ){ + int i; + /* One or more short options */ + for(i=1; icShort ) 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]; pOptzLong; + 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; inArg && 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; inArg; 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; inArg && 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=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.