aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordrh <>2021-11-02 11:24:37 +0000
committerdrh <>2021-11-02 11:24:37 +0000
commit3d0107dda3e05f74a2ee97aacfb910e21eb96412 (patch)
tree3e1cd73bef351072e6c0f9c19cc5faa06f2494f4
parenta12a40c3f00c40c3da412beff15828ff2d2e0aef (diff)
parent0c0fb9cf80f2b5f9a9214d24390504017c557753 (diff)
downloadsqlite-3d0107dda3e05f74a2ee97aacfb910e21eb96412.tar.gz
sqlite-3d0107dda3e05f74a2ee97aacfb910e21eb96412.zip
Add the --remove and --glob options to the .archive command in the CLI.
FossilOrigin-Name: ea7b12cdf868fdfebc0a20bdcba97aea863284b563d478b0e4cb3d2a8612afee
-rw-r--r--manifest17
-rw-r--r--manifest.uuid2
-rw-r--r--src/shell.c.in98
-rw-r--r--test/shell8.test25
4 files changed, 111 insertions, 31 deletions
diff --git a/manifest b/manifest
index a0e0e79f6..2b508325c 100644
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\s#ifdef\slogic\sto\sos_unix.c\sso\sthat\sit\sbuilds\swith\sSQLITE_OMIT_WAL\sdefined.
-D 2021-11-02T11:09:24.625
+C Add\sthe\s--remove\sand\s--glob\soptions\sto\sthe\s.archive\scommand\sin\sthe\sCLI.
+D 2021-11-02T11:24:37.707
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -550,7 +550,7 @@ F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
F src/resolve.c ae65c88f5d0d4bc0052b203773d407efa2387c2bd6b202f87178006c7bb8632c
F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
F src/select.c 32d25b5af6c708aa63373c78c2e59681910387a7a78c08ec3086cadc77d41627
-F src/shell.c.in 185e4b905c3a399e9376597a04cf668f6f992513290978ac978cf4991954d89f
+F src/shell.c.in de92da03b04734618508a968d719e30e4c4bcc53ec7f341cdbf6a3d68ce9721c
F src/sqlite.h.in 99786216caf1c57aa3d70f95a7f84566dff6a9eeb50174799ea3b387eafd2a22
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h d8f6f67ae9ad990a70dd03c093bcdc8883e159ff4bfd16a496f8fb80c6840b5a
@@ -1384,7 +1384,7 @@ F test/shell4.test 3ed6c4b42fd695efcbc25d69ef759dbb15855ca8e52ba6c5ee076f8b435f4
F test/shell5.test 6e4aa0e531dcb8dcf74b7920a2a7442c6712d4dff8422bbc81f768f9dee8a0e3
F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3
F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f
-F test/shell8.test 96be02ea0c21f05b24c1883d7b711a1fa8525a68ab7b636aacf6057876941013
+F test/shell8.test 388471d16e4de767333107e30653983f186232c0e863f4490bb230419e830aae
F test/shmlock.test 3dbf017d34ab0c60abe6a44e447d3552154bd0c87b41eaf5ceacd408dd13fda5
F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3
F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5
@@ -1930,7 +1930,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 8c3f2536d2853acef05c4e8c07f301abb0687402c56ded5ff8614e79fb049423
-R 8b376094a1e25df4570f2864eb07a1ac
-U dan
-Z 94bd6fda7f782c0744099483ca9f3618
+P 948c2cb2a2f44ba069cfbf5e9a57e56964f3a40db57459bb439bea9c3bd9f211 06a5bf4348d3125bb5407fe9b7d451800b113a91fd1a9dff7d840e2317a7284d
+R f46c66c5f5478852cd34fd93fbf63860
+T +closed 06a5bf4348d3125bb5407fe9b7d451800b113a91fd1a9dff7d840e2317a7284d
+U drh
+Z 771f78dc461a27a09496c36528b0ec04
diff --git a/manifest.uuid b/manifest.uuid
index c18f0de84..044d5f931 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-948c2cb2a2f44ba069cfbf5e9a57e56964f3a40db57459bb439bea9c3bd9f211 \ No newline at end of file
+ea7b12cdf868fdfebc0a20bdcba97aea863284b563d478b0e4cb3d2a8612afee \ No newline at end of file
diff --git a/src/shell.c.in b/src/shell.c.in
index 375c75124..69b1c141d 100644
--- a/src/shell.c.in
+++ b/src/shell.c.in
@@ -3965,6 +3965,7 @@ static const char *(azHelp[]) = {
" -c, --create Create a new archive",
" -u, --update Add or update files with changed mtime",
" -i, --insert Like -u but always add even if unchanged",
+ " -r, --remove Remove files from archive",
" -t, --list List contents of archive",
" -x, --extract Extract files from archive",
" Optional arguments:",
@@ -3972,6 +3973,7 @@ static const char *(azHelp[]) = {
" -f FILE, --file FILE Use archive FILE (default is current db)",
" -a FILE, --append FILE Open FILE using the apndvfs VFS",
" -C DIR, --directory DIR Read/extract files from directory DIR",
+ " -g, --glob Use glob matching for names in archive",
" -n, --dryrun Show the SQL that would have occurred",
" Examples:",
" .ar -cf ARCHIVE foo bar # Create ARCHIVE from files foo and bar",
@@ -6132,6 +6134,7 @@ struct ArCommand {
u8 bZip; /* True if the archive is a ZIP */
u8 bDryRun; /* True if --dry-run */
u8 bAppend; /* True if --append */
+ u8 bGlob; /* True if --glob */
u8 fromCmdLine; /* Run from -A instead of .archive */
int nArg; /* Number of command arguments */
char *zSrcTable; /* "sqlar", "zipfile($file)" or "zip" */
@@ -6179,21 +6182,24 @@ static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){
#define AR_CMD_EXTRACT 4
#define AR_CMD_LIST 5
#define AR_CMD_HELP 6
+#define AR_CMD_REMOVE 7
/*
** Other (non-command) switches.
*/
-#define AR_SWITCH_VERBOSE 7
-#define AR_SWITCH_FILE 8
-#define AR_SWITCH_DIRECTORY 9
-#define AR_SWITCH_APPEND 10
-#define AR_SWITCH_DRYRUN 11
+#define AR_SWITCH_VERBOSE 8
+#define AR_SWITCH_FILE 9
+#define AR_SWITCH_DIRECTORY 10
+#define AR_SWITCH_APPEND 11
+#define AR_SWITCH_DRYRUN 12
+#define AR_SWITCH_GLOB 13
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_REMOVE:
case AR_CMD_UPDATE:
case AR_CMD_INSERT:
case AR_CMD_HELP:
@@ -6206,6 +6212,9 @@ static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){
case AR_SWITCH_DRYRUN:
pAr->bDryRun = 1;
break;
+ case AR_SWITCH_GLOB:
+ pAr->bGlob = 1;
+ break;
case AR_SWITCH_VERBOSE:
pAr->bVerbose = 1;
break;
@@ -6244,6 +6253,7 @@ static int arParseCommand(
{ "extract", 'x', AR_CMD_EXTRACT, 0 },
{ "insert", 'i', AR_CMD_INSERT, 0 },
{ "list", 't', AR_CMD_LIST, 0 },
+ { "remove", 'r', AR_CMD_REMOVE, 0 },
{ "update", 'u', AR_CMD_UPDATE, 0 },
{ "help", 'h', AR_CMD_HELP, 0 },
{ "verbose", 'v', AR_SWITCH_VERBOSE, 0 },
@@ -6251,6 +6261,7 @@ static int arParseCommand(
{ "append", 'a', AR_SWITCH_APPEND, 1 },
{ "directory", 'C', AR_SWITCH_DIRECTORY, 1 },
{ "dryrun", 'n', AR_SWITCH_DRYRUN, 0 },
+ { "glob", 'g', AR_SWITCH_GLOB, 0 },
};
int nSwitch = sizeof(aSwitch) / sizeof(struct ArSwitch);
struct ArSwitch *pEnd = &aSwitch[nSwitch];
@@ -6367,11 +6378,13 @@ static int arParseCommand(
/*
** 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.
+** array refer to archive members, as for the --extract, --list or --remove
+** 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. Here, "present" means either an
+** exact equality when pAr->bGlob is false or a "name GLOB pattern" match
+** when pAr->bGlob is true.
**
** This function strips any trailing '/' characters from each argument.
** This is consistent with the way the [tar] command seems to work on
@@ -6382,11 +6395,11 @@ static int arCheckEntries(ArCommand *pAr){
if( pAr->nArg ){
int i, j;
sqlite3_stmt *pTest = 0;
+ const char *zSel = (pAr->bGlob)
+ ? "SELECT name FROM %s WHERE glob($name,name)"
+ : "SELECT name FROM %s WHERE name=$name";
- shellPreparePrintf(pAr->db, &rc, &pTest,
- "SELECT name FROM %s WHERE name=$name",
- pAr->zSrcTable
- );
+ shellPreparePrintf(pAr->db, &rc, &pTest, zSel, pAr->zSrcTable);
j = sqlite3_bind_parameter_index(pTest, "$name");
for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){
char *z = pAr->azArg[i];
@@ -6414,14 +6427,16 @@ static int arCheckEntries(ArCommand *pAr){
** 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.
+** any non-NULL (*pzWhere) value. Here, "match" means strict equality
+** when pAr->bGlob is false and GLOB match when pAr->bGlob is true.
*/
static void arWhereClause(
int *pRc,
- ArCommand *pAr,
+ ArCommand *pAr,
char **pzWhere /* OUT: New WHERE clause */
){
char *zWhere = 0;
+ const char *zSameOp = (pAr->bGlob)? "GLOB" : "=";
if( *pRc==SQLITE_OK ){
if( pAr->nArg==0 ){
zWhere = sqlite3_mprintf("1");
@@ -6431,8 +6446,8 @@ static void arWhereClause(
for(i=0; i<pAr->nArg; i++){
const char *z = pAr->azArg[i];
zWhere = sqlite3_mprintf(
- "%z%s name = '%q' OR substr(name,1,%d) = '%q/'",
- zWhere, zSep, z, strlen30(z)+1, z
+ "%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'",
+ zWhere, zSep, zSameOp, z, strlen30(z)+1, zSameOp, z
);
if( zWhere==0 ){
*pRc = SQLITE_NOMEM;
@@ -6487,6 +6502,47 @@ static int arListCommand(ArCommand *pAr){
/*
+** Implementation of .ar "Remove" command.
+*/
+static int arRemoveCommand(ArCommand *pAr){
+ int rc = 0;
+ char *zSql = 0;
+ char *zWhere = 0;
+
+ if( pAr->nArg ){
+ /* Verify that args actually exist within the archive before proceeding.
+ ** And formulate a WHERE clause to match them. */
+ rc = arCheckEntries(pAr);
+ arWhereClause(&rc, pAr, &zWhere);
+ }
+ if( rc==SQLITE_OK ){
+ zSql = sqlite3_mprintf("DELETE FROM %s WHERE %s;",
+ pAr->zSrcTable, zWhere);
+ if( pAr->bDryRun ){
+ utf8_printf(pAr->p->out, "%s\n", zSql);
+ }else{
+ char *zErr = 0;
+ rc = sqlite3_exec(pAr->db, "SAVEPOINT ar;", 0, 0, 0);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr);
+ if( rc!=SQLITE_OK ){
+ sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0);
+ }else{
+ rc = sqlite3_exec(pAr->db, "RELEASE ar;", 0, 0, 0);
+ }
+ }
+ if( zErr ){
+ utf8_printf(stdout, "ERROR: %s\n", zErr);
+ sqlite3_free(zErr);
+ }
+ }
+ }
+ sqlite3_free(zWhere);
+ sqlite3_free(zSql);
+ return rc;
+}
+
+/*
** Implementation of .ar "eXtract" command.
*/
static int arExtractCommand(ArCommand *pAr){
@@ -6738,7 +6794,7 @@ static int arDotCommand(
int flags;
if( cmd.bAppend ) eDbType = SHELL_OPEN_APPENDVFS;
if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_INSERT
- || cmd.eCmd==AR_CMD_UPDATE ){
+ || cmd.eCmd==AR_CMD_REMOVE || cmd.eCmd==AR_CMD_UPDATE ){
flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE;
}else{
flags = SQLITE_OPEN_READONLY;
@@ -6794,6 +6850,10 @@ static int arDotCommand(
rc = arCreateOrUpdateCommand(&cmd, 1, 0);
break;
+ case AR_CMD_REMOVE:
+ rc = arRemoveCommand(&cmd);
+ break;
+
default:
assert( cmd.eCmd==AR_CMD_UPDATE );
rc = arCreateOrUpdateCommand(&cmd, 1, 1);
diff --git a/test/shell8.test b/test/shell8.test
index 3658a8ac5..ddb4a47b8 100644
--- a/test/shell8.test
+++ b/test/shell8.test
@@ -44,6 +44,10 @@ proc populate_dir {dirname spec} {
}
}
+proc dir_content {dirname} {
+ lsort [glob -nocomplain $dirname/*]
+}
+
proc dir_to_list {dirname {n -1}} {
if {$n<0} {set n [llength [file split $dirname]]}
@@ -170,8 +174,23 @@ foreach {tn tcl} {
}
}
-finish_test
-
-
+do_test 2.1.1 {
+ populate_dir ar2 {
+ file1 "abcd"
+ file2 "efgh"
+ junk1 "j1"
+ junk2 "j2"
+ dir1/file3 "ijkl"
+ }
+ populate_dir ar4 {
+ file2 "efgh"
+ }
+ catchcmd shell8.db {.ar -c}
+ catchcmd shell8.db {.ar -C ar2 -i .}
+ catchcmd shell8.db {.ar -r ./file2 ./dir1}
+ catchcmd shell8.db {.ar -g -r ./ju*2}
+ catchcmd shell8.db {.ar -C ar4 -x .}
+ regsub -all {ar4} [dir_content ar4] ar2
+} {ar2/file1 ar2/file2 ar2/junk1}
finish_test