From b376b3d6aa5648e1ffe9ca5ff09b2d29003834c7 Mon Sep 17 00:00:00 2001 From: drh Date: Wed, 10 Jan 2018 13:11:51 +0000 Subject: [PATCH] Work on the ".archive" command. (1) Add the --dryrun option. (2) Do not require --file when open on a ZIP archive. (3) Miscellaneous code simplifications. This is an incremental check-in of work in progress. FossilOrigin-Name: a2baada429e84dc4b7243173a056e3c8bc042682f7efb01fdf8d2cc452c97e04 --- manifest | 15 +-- manifest.uuid | 2 +- src/shell.c.in | 269 ++++++++++++++++++++++++++----------------------- 3 files changed, 152 insertions(+), 134 deletions(-) diff --git a/manifest b/manifest index 73254e8c88..2b380e8b60 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Rearrange\ssome\sroutines\sin\sshell.c\sto\savoid\sthe\sneed\sto\sforward\sreference\sa\nstatic\sfunction. -D 2018-01-10T00:53:55.338 +C Work\son\sthe\s".archive"\scommand.\n(1)\sAdd\sthe\s--dryrun\soption.\n(2)\sDo\snot\srequire\s--file\swhen\sopen\son\sa\sZIP\sarchive.\n(3)\sMiscellaneous\scode\ssimplifications.\nThis\sis\san\sincremental\scheck-in\sof\swork\sin\sprogress. +D 2018-01-10T13:11:51.661 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 12b6daa4bdb03fa87da27cbc205ff88ace645475b5be79414a3038b68ade14cb @@ -484,7 +484,7 @@ F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384 F src/resolve.c bbee7e31d369a18a2f4836644769882e9c5d40ef4a3af911db06410b65cb3730 F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac F src/select.c 8b22abe193e4d8243befa2038e4ae2405802fed1c446e5e502d11f652e09ba74 -F src/shell.c.in 1e50d66dc88bcc61d6300a5b8fb71d2d3821ec2d1418aee69337e391061514dd +F src/shell.c.in f86200b08a8201fab1c775edbff4a81cb7b07e5926b1ed1ad7c043453fdfa7b3 F src/sqlite.h.in 1f1a2da222ec57465794e8984d77f32d0bd0da80cdc136beadda461a0be9d80c F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h c02d628cca67f3889c689d82d25c3eb45e2c155db08e4c6089b5840d64687d34 @@ -1697,7 +1697,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 36b89d728ff13d395fe0e1db8e7c01263f73dccb278b3ece27f6ef78e909b492 -R c0e5b3be89f9671131b70568ccd2de3b +P fd7f51a107806666d5c3a3a7a62528ec3e1fb71c4256f49d57b4dcdac4bf8680 +R da66d185dcc66f20b0c30262a6b67fe7 +T *branch * archive-improvements +T *sym-archive-improvements * +T -sym-trunk * U drh -Z 058862bc11ed7b27f8f707a776eec676 +Z 75859d5801ea825c715cde8533b487b8 diff --git a/manifest.uuid b/manifest.uuid index b802c2b17a..822237285a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -fd7f51a107806666d5c3a3a7a62528ec3e1fb71c4256f49d57b4dcdac4bf8680 \ No newline at end of file +a2baada429e84dc4b7243173a056e3c8bc042682f7efb01fdf8d2cc452c97e04 \ No newline at end of file diff --git a/src/shell.c.in b/src/shell.c.in index 253b1c764d..4bfc1c85cc 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -4503,13 +4503,17 @@ static void shellReset( */ typedef struct ArCommand ArCommand; struct ArCommand { - int eCmd; /* An AR_CMD_* value */ + u8 eCmd; /* An AR_CMD_* value */ + u8 bVerbose; /* True if --verbose */ + u8 bZip; /* True if --zip */ + u8 bDryRun; /* True if --dry-run */ + int nArg; /* Number of command arguments */ + const char *zSrcTable; /* "sqlar", "zipfile($file)" or "zip" */ const char *zFile; /* --file argument, or NULL */ const char *zDir; /* --directory argument, or NULL */ - int bVerbose; /* True if --verbose */ - int bZip; /* True if --zip */ - int nArg; /* Number of command arguments */ char **azArg; /* Array of command arguments */ + ShellState *p; /* Shell state */ + sqlite3 *db; /* Database containing the archive */ }; /* @@ -4536,6 +4540,8 @@ static int arUsage(FILE *f){ " -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, --dryrun Show the SQL that would have occurred\n" +" -z, --zip Operate on a ZIP archive instead of an SQLAR\n" "\n" "See also: http://sqlite.org/cli.html#sqlar_archive_support\n" "\n" @@ -4570,10 +4576,11 @@ static int arErrorMsg(const char *zFmt, ...){ /* ** Other (non-command) switches. */ -#define AR_SWITCH_VERBOSE 6 -#define AR_SWITCH_FILE 7 -#define AR_SWITCH_DIRECTORY 8 -#define AR_SWITCH_ZIP 9 +#define AR_SWITCH_VERBOSE 6 +#define AR_SWITCH_FILE 7 +#define AR_SWITCH_DIRECTORY 8 +#define AR_SWITCH_ZIP 9 +#define AR_SWITCH_DRYRUN 10 static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){ switch( eSwitch ){ @@ -4588,6 +4595,9 @@ static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){ pAr->eCmd = eSwitch; break; + case AR_SWITCH_DRYRUN: + pAr->bDryRun = 1; + break; case AR_SWITCH_VERBOSE: pAr->bVerbose = 1; break; @@ -4618,20 +4628,21 @@ static int arParseCommand( ArCommand *pAr /* Populate this object */ ){ struct ArSwitch { - char cShort; const char *zLong; - int eSwitch; - int bArg; + char cShort; + u8 eSwitch; + u8 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 }, - { 'z', "zip", AR_SWITCH_ZIP, 0 } + { "create", 'c', AR_CMD_CREATE, 0 }, + { "extract", 'x', AR_CMD_EXTRACT, 0 }, + { "list", 't', AR_CMD_LIST, 0 }, + { "update", 'u', AR_CMD_UPDATE, 0 }, + { "help", 'h', AR_CMD_HELP, 0 }, + { "verbose", 'v', AR_SWITCH_VERBOSE, 0 }, + { "file", 'f', AR_SWITCH_FILE, 1 }, + { "directory", 'C', AR_SWITCH_DIRECTORY, 1 }, + { "zip", 'z', AR_SWITCH_ZIP, 0 }, + { "dryrun", 'n', AR_SWITCH_DRYRUN, 0 }, }; int nSwitch = sizeof(aSwitch) / sizeof(struct ArSwitch); struct ArSwitch *pEnd = &aSwitch[nSwitch]; @@ -4758,37 +4769,40 @@ static int arParseCommand( ** This is consistent with the way the [tar] command seems to work on ** Linux. */ -static int arCheckEntries(sqlite3 *db, ArCommand *pAr){ +static int arCheckEntries(ArCommand *pAr){ int rc = SQLITE_OK; if( pAr->nArg ){ - int i; + int i, j; sqlite3_stmt *pTest = 0; - shellPreparePrintf(db, &rc, &pTest, "SELECT name FROM %s WHERE name=?1", - pAr->bZip ? "zipfile(?2)" : "sqlar" + shellPreparePrintf(pAr->db, &rc, &pTest, + "SELECT name FROM %s WHERE name=$name", + pAr->zSrcTable ); - if( rc==SQLITE_OK && pAr->bZip ){ - sqlite3_bind_text(pTest, 2, pAr->zFile, -1, SQLITE_TRANSIENT); + if( rc==SQLITE_OK + && (j = sqlite3_bind_parameter_index(pTest, "$archiveFile"))>0 + ){ + sqlite3_bind_text(pTest, j, pAr->zFile, -1, SQLITE_TRANSIENT); } + j = sqlite3_bind_parameter_index(pTest, "$name"); for(i=0; inArg && rc==SQLITE_OK; i++){ char *z = pAr->azArg[i]; int n = strlen30(z); int bOk = 0; while( n>0 && z[n-1]=='/' ) n--; z[n] = '\0'; - sqlite3_bind_text(pTest, 1, z, -1, SQLITE_STATIC); + sqlite3_bind_text(pTest, j, 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); + utf8_printf(stderr, "not found in archive: %s\n", z); rc = SQLITE_ERROR; } } shellFinalize(&rc, pTest); } - return rc; } @@ -4814,9 +4828,9 @@ static void arWhereClause( 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 - ); + "%z%s name = '%q' OR substr(name,1,%d) = '%q/'", + zWhere, zSep, z, strlen30(z)+1, z + ); if( zWhere==0 ){ *pRc = SQLITE_NOMEM; break; @@ -4858,9 +4872,8 @@ static void shellModeToString(char *zMode, int mode){ /* ** Implementation of .ar "lisT" command. */ -static int arListCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){ +static int arListCommand(ArCommand *pAr){ const char *zSql = "SELECT %s FROM %s WHERE %s"; - const char *zTbl = (pAr->bZip ? "zipfile(?)" : "sqlar"); const char *azCols[] = { "name", "mode, sz, datetime(mtime, 'unixepoch'), name" @@ -4869,29 +4882,36 @@ static int arListCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){ char *zWhere = 0; sqlite3_stmt *pSql = 0; int rc; + int j; - rc = arCheckEntries(db, pAr); + rc = arCheckEntries(pAr); arWhereClause(&rc, pAr, &zWhere); - shellPreparePrintf(db, &rc, &pSql, zSql, azCols[pAr->bVerbose], zTbl, zWhere); - if( rc==SQLITE_OK && pAr->bZip ){ - sqlite3_bind_text(pSql, 1, pAr->zFile, -1, SQLITE_TRANSIENT); + shellPreparePrintf(pAr->db, &rc, &pSql, zSql, azCols[pAr->bVerbose], + pAr->zSrcTable, zWhere); + if( rc==SQLITE_OK + && (j = sqlite3_bind_parameter_index(pSql, "$archiveFile"))>0 + ){ + sqlite3_bind_text(pSql, j, pAr->zFile, -1, SQLITE_TRANSIENT); } - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ - if( pAr->bVerbose ){ - char zMode[11]; - shellModeToString(zMode, sqlite3_column_int(pSql, 0)); - - raw_printf(p->out, "%s % 10d %s %s\n", zMode, - sqlite3_column_int(pSql, 1), - sqlite3_column_text(pSql, 2), - sqlite3_column_text(pSql, 3) - ); - }else{ - raw_printf(p->out, "%s\n", sqlite3_column_text(pSql, 0)); + if( pAr->bDryRun ){ + utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql)); + }else{ + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ + if( pAr->bVerbose ){ + char zMode[11]; + shellModeToString(zMode, sqlite3_column_int(pSql, 0)); + + utf8_printf(pAr->p->out, "%s % 10d %s %s\n", zMode, + sqlite3_column_int(pSql, 1), + sqlite3_column_text(pSql, 2), + sqlite3_column_text(pSql, 3) + ); + }else{ + utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0)); + } } } - shellFinalize(&rc, pSql); return rc; } @@ -4900,31 +4920,28 @@ static int arListCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){ /* ** Implementation of .ar "eXtract" command. */ -static int arExtractCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){ +static int arExtractCommand(ArCommand *pAr){ const char *zSql1 = "SELECT " - " :1 || name, " - " writefile(?1 || name, %s, mode, mtime) " - "FROM %s WHERE (%s) AND (data IS NULL OR ?2 = 0)"; + " ($dir || name)," + " writefile(($dir || name), %s, mode, mtime) " + "FROM %s WHERE (%s) AND (data IS NULL OR $dirOnly = 0)"; const char *azExtraArg[] = { "sqlar_uncompress(data, sz)", "data" }; - const char *azSource[] = { - "sqlar", "zipfile(?3)" - }; sqlite3_stmt *pSql = 0; int rc = SQLITE_OK; char *zDir = 0; char *zWhere = 0; - int i; + int i, j; /* 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); + rc = arCheckEntries(pAr); arWhereClause(&rc, pAr, &zWhere); if( rc==SQLITE_OK ){ @@ -4936,14 +4953,16 @@ static int arExtractCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){ if( zDir==0 ) rc = SQLITE_NOMEM; } - shellPreparePrintf(db, &rc, &pSql, zSql1, - azExtraArg[pAr->bZip], azSource[pAr->bZip], zWhere + shellPreparePrintf(pAr->db, &rc, &pSql, zSql1, + azExtraArg[pAr->bZip], pAr->zSrcTable, zWhere ); if( rc==SQLITE_OK ){ - sqlite3_bind_text(pSql, 1, zDir, -1, SQLITE_STATIC); - if( pAr->bZip ){ - sqlite3_bind_text(pSql, 3, pAr->zFile, -1, SQLITE_STATIC); + j = sqlite3_bind_parameter_index(pSql, "$dir"); + sqlite3_bind_text(pSql, j, zDir, -1, SQLITE_STATIC); + j = sqlite3_bind_parameter_index(pSql, "$archiveFile"); + if( j ){ + sqlite3_bind_text(pSql, j, pAr->zFile, -1, SQLITE_STATIC); } /* Run the SELECT statement twice. The first time, writefile() is called @@ -4952,10 +4971,15 @@ static int arExtractCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){ ** 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)); + j = sqlite3_bind_parameter_index(pSql, "$dirOnly"); + sqlite3_bind_int(pSql, j, i); + if( pAr->bDryRun ){ + utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql)); + }else{ + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ + if( i==0 && pAr->bVerbose ){ + utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0)); + } } } shellReset(&rc, pSql); @@ -4968,6 +4992,20 @@ static int arExtractCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){ return rc; } +/* +** Run the SQL statement in zSql. Or if doing a --dryrun, merely print it out. +*/ +static int arExecSql(ArCommand *pAr, const char *zSql){ + int rc; + if( pAr->bDryRun ){ + utf8_printf(pAr->p->out, "%s\n", zSql); + rc = SQLITE_OK; + }else{ + rc = sqlite3_exec(pAr->db, zSql, 0, 0, 0); + } + return rc; +} + /* ** Implementation of .ar "create" and "update" commands. @@ -4980,11 +5018,9 @@ static int arExtractCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){ ** 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, +static int arCreateOrUpdateCommand( ArCommand *pAr, /* Command arguments and options */ - int bUpdate + int bUpdate /* true for a --create. false for --update */ ){ const char *zSql = "SELECT name, mode, mtime, data FROM fsdir(?, ?)"; const char *zCreate = @@ -5005,17 +5041,17 @@ static int arCreateUpdate( assert( pAr->bZip==0 ); - rc = sqlite3_exec(db, "SAVEPOINT ar;", 0, 0, 0); + rc = arExecSql(pAr, "SAVEPOINT ar;"); if( rc!=SQLITE_OK ) return rc; if( bUpdate==0 ){ - rc = sqlite3_exec(db, zDrop, 0, 0, 0); + rc = arExecSql(pAr, zDrop); if( rc!=SQLITE_OK ) return rc; } - rc = sqlite3_exec(db, zCreate, 0, 0, 0); - shellPrepare(db, &rc, zInsert, &pInsert); - shellPrepare(db, &rc, zSql, &pStmt); + rc = arExecSql(pAr, zCreate); + shellPrepare(pAr->db, &rc, zInsert, &pInsert); + shellPrepare(pAr->db, &rc, zSql, &pStmt); sqlite3_bind_text(pStmt, 2, pAr->zDir, -1, SQLITE_STATIC); for(i=0; inArg && rc==SQLITE_OK; i++){ @@ -5027,7 +5063,7 @@ static int arCreateUpdate( unsigned int mtime = sqlite3_column_int(pStmt, 2); if( pAr->bVerbose ){ - raw_printf(p->out, "%s\n", zName); + utf8_printf(pAr->p->out, "%s\n", zName); } sqlite3_bind_text(pInsert, 1, zName, -1, SQLITE_STATIC); @@ -5047,46 +5083,26 @@ static int arCreateUpdate( } sqlite3_bind_int(pInsert, 4, sz); - sqlite3_step(pInsert); + if( pAr->bDryRun ){ + utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pInsert)); + }else{ + 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); + arExecSql(pAr, "ROLLBACK TO ar; RELEASE ar;"); }else{ - rc = sqlite3_exec(db, "RELEASE ar;", 0, 0, 0); + rc = arExecSql(pAr, "RELEASE ar;"); } 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. */ @@ -5099,18 +5115,19 @@ static int arDotCommand( int rc; rc = arParseCommand(azArg, nArg, &cmd); if( rc==SQLITE_OK ){ - sqlite3 *db = 0; /* Database handle to use as archive */ - + cmd.p = pState; + cmd.db = pState->db; + cmd.zSrcTable = "sqlar"; if( cmd.bZip ){ - if( cmd.zFile==0 ){ - raw_printf(stderr, "zip format requires a --file switch\n"); - return SQLITE_ERROR; - }else + if( pState->openMode==SHELL_OPEN_ZIPFILE ){ + cmd.zSrcTable = "zip"; + }else{ + cmd.zSrcTable = "zipfile($archiveFile)"; + } if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_UPDATE ){ - raw_printf(stderr, "zip archives are read-only\n"); + utf8_printf(stderr, "zip archives are read-only\n"); return SQLITE_ERROR; } - db = pState->db; }else if( cmd.zFile ){ int flags; if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_UPDATE ){ @@ -5118,33 +5135,31 @@ static int arDotCommand( }else{ flags = SQLITE_OPEN_READONLY; } - rc = sqlite3_open_v2(cmd.zFile, &db, flags, 0); + rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, 0); if( rc!=SQLITE_OK ){ - raw_printf(stderr, "cannot open file: %s (%s)\n", - cmd.zFile, sqlite3_errmsg(db) + utf8_printf(stderr, "cannot open file: %s (%s)\n", + cmd.zFile, sqlite3_errmsg(cmd.db) ); - sqlite3_close(db); + sqlite3_close(cmd.db); return rc; } - sqlite3_fileio_init(db, 0, 0); + sqlite3_fileio_init(cmd.db, 0, 0); #ifdef SQLITE_HAVE_ZLIB - sqlite3_sqlar_init(db, 0, 0); + sqlite3_sqlar_init(cmd.db, 0, 0); #endif - }else{ - db = pState->db; } switch( cmd.eCmd ){ case AR_CMD_CREATE: - rc = arCreateCommand(pState, db, &cmd); + rc = arCreateOrUpdateCommand(&cmd, 0); break; case AR_CMD_EXTRACT: - rc = arExtractCommand(pState, db, &cmd); + rc = arExtractCommand(&cmd); break; case AR_CMD_LIST: - rc = arListCommand(pState, db, &cmd); + rc = arListCommand(&cmd); break; case AR_CMD_HELP: @@ -5153,12 +5168,12 @@ static int arDotCommand( default: assert( cmd.eCmd==AR_CMD_UPDATE ); - rc = arUpdateCmd(pState, db, &cmd); + rc = arCreateOrUpdateCommand(&cmd, 1); break; } if( cmd.zFile ){ - sqlite3_close(db); + sqlite3_close(cmd.db); } }