SQLite

Check-in [430d1a7daa]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:The ".ar" command deduces whether or not the target file is a ZIP or SQLAR and does the appropropriate thing. The "-z" option is omitted. The "--append" option is added to open auxiliary databases using apndvfs.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | archive-improvements
Files: files | file ages | folders
SHA3-256: 430d1a7daa823ae53606b7a158af4e7c16f62ff9b072b90606524e7c3f6131df
User & Date: drh 2018-01-10 15:17:34.832
Context
2018-01-10
15:53
Add the "filetype()" SQL function for interpreting file modes to the fileio.c extension. (check-in: 58c0c74c40 user: drh tags: archive-improvements)
15:17
The ".ar" command deduces whether or not the target file is a ZIP or SQLAR and does the appropropriate thing. The "-z" option is omitted. The "--append" option is added to open auxiliary databases using apndvfs. (check-in: 430d1a7daa user: drh tags: archive-improvements)
14:00
Allow the use of ".ar -t" without specifying an archive file or the "-z" option when the command-line shell is opened on a ZIP archive. (check-in: 9340a2c145 user: drh tags: archive-improvements)
Changes
Side-by-Side Diff Ignore Whitespace Patch
Changes to src/shell.c.in.
4501
4502
4503
4504
4505
4506
4507
4508

4509

4510
4511

4512
4513
4514
4515
4516
4517
4518
4501
4502
4503
4504
4505
4506
4507

4508
4509
4510
4511

4512
4513
4514
4515
4516
4517
4518
4519







-
+

+

-
+







/*
** Structure representing a single ".ar" command.
*/
typedef struct ArCommand ArCommand;
struct ArCommand {
  u8 eCmd;                        /* An AR_CMD_* value */
  u8 bVerbose;                    /* True if --verbose */
  u8 bZip;                        /* True if --zip */
  u8 bZip;                        /* True if the archive is a ZIP */
  u8 bDryRun;                     /* True if --dry-run */
  u8 bAppend;                     /* True if --append */
  int nArg;                       /* Number of command arguments */
  const char *zSrcTable;          /* "sqlar", "zipfile($file)" or "zip" */
  char *zSrcTable;                /* "sqlar", "zipfile($file)" or "zip" */
  const char *zFile;              /* --file argument, or NULL */
  const char *zDir;               /* --directory argument, or NULL */
  char **azArg;                   /* Array of command arguments */
  ShellState *p;                  /* Shell state */
  sqlite3 *db;                    /* Database containing the archive */
};

4537
4538
4539
4540
4541
4542
4543
4544

4545
4546
4547
4548
4549
4550
4551
4538
4539
4540
4541
4542
4543
4544

4545
4546
4547
4548
4549
4550
4551
4552







-
+







"  -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, --dryrun               Show the SQL that would have occurred\n"
"  -z, --zip                  Operate on a ZIP archive instead of an SQLAR\n"
"  -a, --append               Append the SQLAR to an existing file\n"
"\n"
"See also: http://sqlite.org/cli.html#sqlar_archive_support\n"
"\n"
);
  return SQLITE_ERROR;
}

4575
4576
4577
4578
4579
4580
4581
4582

4583
4584
4585
4586
4587
4588
4589
4576
4577
4578
4579
4580
4581
4582

4583
4584
4585
4586
4587
4588
4589
4590







-
+








/*
** 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_APPEND      9
#define AR_SWITCH_DRYRUN     10

static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){
  switch( eSwitch ){
    case AR_CMD_CREATE:
    case AR_CMD_EXTRACT:
    case AR_CMD_LIST:
4597
4598
4599
4600
4601
4602
4603
4604
4605


4606
4607
4608
4609
4610
4611
4612
4598
4599
4600
4601
4602
4603
4604


4605
4606
4607
4608
4609
4610
4611
4612
4613







-
-
+
+








    case AR_SWITCH_DRYRUN:
      pAr->bDryRun = 1;
      break;
    case AR_SWITCH_VERBOSE:
      pAr->bVerbose = 1;
      break;
    case AR_SWITCH_ZIP:
      pAr->bZip = 1;
    case AR_SWITCH_APPEND:
      pAr->bAppend = 1;
      break;

    case AR_SWITCH_FILE:
      pAr->zFile = zArg;
      break;
    case AR_SWITCH_DIRECTORY:
      pAr->zDir = zArg;
4637
4638
4639
4640
4641
4642
4643
4644

4645
4646
4647
4648
4649
4650
4651
4638
4639
4640
4641
4642
4643
4644

4645
4646
4647
4648
4649
4650
4651
4652







-
+







    { "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 },
    { "append",    'a', AR_SWITCH_APPEND,    0 },
    { "dryrun",    'n', AR_SWITCH_DRYRUN,    0 },
  };
  int nSwitch = sizeof(aSwitch) / sizeof(struct ArSwitch);
  struct ArSwitch *pEnd = &aSwitch[nSwitch];

  if( nArg<=1 ){
    return arUsage(stderr);
4775
4776
4777
4778
4779
4780
4781
4782
4783
4784
4785
4786
4787
4788
4789
4790
4791
4792
4793
4776
4777
4778
4779
4780
4781
4782





4783
4784
4785
4786
4787
4788
4789







-
-
-
-
-







    int i, j;
    sqlite3_stmt *pTest = 0;

    shellPreparePrintf(pAr->db, &rc, &pTest,
        "SELECT name FROM %s WHERE name=$name", 
        pAr->zSrcTable
    );
    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; i<pAr->nArg && 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';
4878
4879
4880
4881
4882
4883
4884
4885
4886
4887
4888
4889
4890
4891
4892
4893
4894
4895
4896
4897
4898
4899
4900
4901
4902
4903
4874
4875
4876
4877
4878
4879
4880

4881
4882
4883
4884
4885
4886





4887
4888
4889
4890
4891
4892
4893







-






-
-
-
-
-







    "name",
    "mode, sz, datetime(mtime, 'unixepoch'), name"
  };

  char *zWhere = 0;
  sqlite3_stmt *pSql = 0;
  int rc;
  int j;

  rc = arCheckEntries(pAr);
  arWhereClause(&rc, pAr, &zWhere);

  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);
  }
  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));
4956
4957
4958
4959
4960
4961
4962
4963
4964
4965
4966
4967
4968
4969
4970
4971
4972
4973
4946
4947
4948
4949
4950
4951
4952




4953
4954
4955
4956
4957
4958
4959







-
-
-
-







  shellPreparePrintf(pAr->db, &rc, &pSql, zSql1, 
      azExtraArg[pAr->bZip], pAr->zSrcTable, zWhere
  );

  if( rc==SQLITE_OK ){
    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
    ** 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++){
5018
5019
5020
5021
5022
5023
5024
5025

5026
5027
5028
5029
5030
5031
5032
5033
5034
5035
5036
5037
5038
5039

5040
5041
5042
5043
5044
5045
5046
5047
5048
5049
5050
5051
5052

5053


5054

5055
5056





5057

5058

5059
5060
5061
5062
5063
5064
5065
5066




5067
5068
5069
5070
5071
5072
5073
5004
5005
5006
5007
5008
5009
5010

5011
5012
5013
5014
5015
5016
5017
5018
5019
5020
5021
5022
5023
5024
5025
5026
5027
5028
5029
5030
5031
5032
5033
5034
5035
5036
5037
5038
5039
5040

5041
5042
5043
5044


5045
5046
5047
5048
5049
5050
5051

5052
5053
5054
5055
5056
5057
5058
5059
5060
5061
5062
5063
5064
5065
5066
5067
5068
5069
5070
5071







-
+














+













+
-
+
+

+
-
-
+
+
+
+
+

+
-
+








+
+
+
+







** The create command is the same as update, except that it drops
** any existing "sqlar" table before beginning.
*/
static int arCreateOrUpdateCommand(
  ArCommand *pAr,                 /* Command arguments and options */
  int bUpdate                     /* true for a --create.  false for --update */
){
  const char *zSql = "SELECT name, mode, mtime, data FROM fsdir(?, ?)";
  const char *zSql = "SELECT name, mode, mtime, data FROM fsdir($name, $dir)";
  const char *zCreate = 
      "CREATE TABLE IF NOT EXISTS sqlar(\n"
      "  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 j;                          /* Parameter index */
  int rc;                         /* Return code */

  assert( pAr->bZip==0 );

  rc = arExecSql(pAr, "SAVEPOINT ar;");
  if( rc!=SQLITE_OK ) return rc;

  if( bUpdate==0 ){
    rc = arExecSql(pAr, zDrop);
    if( rc!=SQLITE_OK ) return rc;
  }

  rc = arExecSql(pAr, zCreate);
  if( !pAr->bDryRun ){
  shellPrepare(pAr->db, &rc, zInsert, &pInsert);
    shellPrepare(pAr->db, &rc, zInsert, &pInsert);
  }
  shellPrepare(pAr->db, &rc, zSql, &pStmt);
  j = sqlite3_bind_parameter_index(pStmt, "$dir");
  sqlite3_bind_text(pStmt, 2, pAr->zDir, -1, SQLITE_STATIC);

  sqlite3_bind_text(pStmt, j, pAr->zDir, -1, SQLITE_STATIC);
  if( pAr->bDryRun ){
    utf8_printf(pAr->p->out, "%s;\n", sqlite3_sql(pStmt));
  }

  for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){
    j = sqlite3_bind_parameter_index(pStmt, "$name");
    sqlite3_bind_text(pStmt, 1, pAr->azArg[i], -1, SQLITE_STATIC);
    sqlite3_bind_text(pStmt, j, 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 ){
        utf8_printf(pAr->p->out, "%s\n", zName);
      }
      if( pAr->bDryRun ){
        utf8_printf(pAr->p->out, "%s;\n", zInsert);
        continue;
      }

      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) ){
5111
5112
5113
5114
5115
5116
5117

5118
5119
5120
5121
5122








5123
5124
5125

5126
5127
5128
5129

5130
5131
5132
5133
5134


5135

5136
5137

5138
5139
5140
5141
5142
5143




5144


5145
5146
5147
5148
5149

5150
5151
5152
5153
5154
5155
5156








5157
5158
5159
5160
5161
5162
5163
5109
5110
5111
5112
5113
5114
5115
5116
5117
5118



5119
5120
5121
5122
5123
5124
5125
5126



5127


5128

5129

5130
5131
5132

5133
5134
5135
5136
5137
5138
5139
5140
5141
5142
5143
5144
5145
5146
5147
5148
5149

5150
5151
5152
5153
5154
5155

5156

5157
5158
5159
5160
5161
5162
5163
5164
5165
5166
5167
5168
5169
5170
5171
5172
5173
5174
5175
5176
5177







+


-
-
-
+
+
+
+
+
+
+
+
-
-
-
+
-
-

-
+
-



-
+
+

+


+






+
+
+
+
-
+
+




-
+
-






+
+
+
+
+
+
+
+







  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 ){
    int eDbType = SHELL_OPEN_UNSPEC;
    cmd.p = pState;
    cmd.db = pState->db;
    cmd.zSrcTable = "sqlar";
    if( cmd.bZip || pState->openMode==SHELL_OPEN_ZIPFILE ){
      if( cmd.zFile==0
    cmd.zSrcTable = 0;
    if( cmd.zFile ){
      eDbType = deduceDatabaseType(cmd.zFile);
    }else{
      eDbType = pState->openMode;
    }
    if( eDbType==SHELL_OPEN_ZIPFILE ){
      if( cmd.zFile==0 ){
       && sqlite3_table_column_metadata(cmd.db,0,"zip","name",0,0,0,0,0)==SQLITE_OK
      ){
        cmd.zSrcTable = "zip";
        cmd.zSrcTable = sqlite3_mprintf("zip");
      }else if( cmd.zFile!=0 ){
        cmd.zSrcTable = "zipfile($archiveFile)";
      }else{
        utf8_printf(stderr, "no zip archive file specified\n");
        cmd.zSrcTable = sqlite3_mprintf("zipfile(%Q)", cmd.zFile);
        return SQLITE_ERROR;
      }
      if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_UPDATE ){
        utf8_printf(stderr, "zip archives are read-only\n");
        return SQLITE_ERROR;
        rc = SQLITE_ERROR;
        goto end_ar_command;
      }
      cmd.bZip = 1;
    }else if( cmd.zFile ){
      int flags;
      if( cmd.bAppend ) eDbType = SHELL_OPEN_APPENDVFS;
      if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_UPDATE ){
        flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE;
      }else{
        flags = SQLITE_OPEN_READONLY;
      }
      cmd.db = 0;
      if( cmd.bDryRun ){
        utf8_printf(pState->out, "-- open database '%s'%s\n", cmd.zFile,
             eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : "");
      }
      rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, 0);
      rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, 
             eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0);
      if( rc!=SQLITE_OK ){
        utf8_printf(stderr, "cannot open file: %s (%s)\n", 
            cmd.zFile, sqlite3_errmsg(cmd.db)
        );
        sqlite3_close(cmd.db);
        goto end_ar_command;
        return rc;
      }
      sqlite3_fileio_init(cmd.db, 0, 0);
#ifdef SQLITE_HAVE_ZLIB
      sqlite3_sqlar_init(cmd.db, 0, 0);
#endif
    }
    if( cmd.zSrcTable==0 ){
      if( sqlite3_table_column_metadata(cmd.db,0,"sqlar","name",0,0,0,0,0) ){
        utf8_printf(stderr, "database does not contain an 'sqlar' table\n");
        rc = SQLITE_ERROR;
        goto end_ar_command;
      }
      cmd.zSrcTable = sqlite3_mprintf("sqlar");
    }

    switch( cmd.eCmd ){
      case AR_CMD_CREATE:
        rc = arCreateOrUpdateCommand(&cmd, 0);
        break;

      case AR_CMD_EXTRACT:
5173
5174
5175
5176
5177
5178
5179
5180
5181
5182
5183





5184

5185
5186
5187
5188
5189
5190
5191
5187
5188
5189
5190
5191
5192
5193




5194
5195
5196
5197
5198

5199
5200
5201
5202
5203
5204
5205
5206







-
-
-
-
+
+
+
+
+
-
+







        break;

      default:
        assert( cmd.eCmd==AR_CMD_UPDATE );
        rc = arCreateOrUpdateCommand(&cmd, 1);
        break;
    }

    if( cmd.db!=pState->db ){
      sqlite3_close(cmd.db);
    }
  }
end_ar_command:
  if( cmd.db!=pState->db ){
    sqlite3_close(cmd.db);
  }
  }
  sqlite3_free(cmd.zSrcTable);

  return rc;
}
/* End of the ".archive" or ".ar" command logic
**********************************************************************************/
#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) */

Changes to test/shell8.test.
97
98
99
100
101
102
103
104

105
106
107
108
109
110
111
97
98
99
100
101
102
103

104
105
106
107
108
109
110
111







-
+







    set c1 ".ar --create ar1"
    set x1 ".ar --extract"

    set c2 ".ar --directory ar1 --create ."
    set x2 ".ar --extract --dir ar3"

    set c3 ".ar --creat --dir ar1 --file test_xyz.db ."
    set x3 ".ar --e  --d ar3 --f test_xyz.db"
    set x3 ".ar --e  --dir ar3 --f test_xyz.db"
  }

  4 {
    set c1 ".ar --cr ar1"
    set x1 ".ar --e"

    set c2 ".ar -C ar1 -c ."
165
166
167
168
169
170
171
172
165
166
167
168
169
170
171








-
}

finish_test



finish_test