/ Check-in [9404765e]
Login

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

Overview
Comment:Enhance the ".ar" command in the CLI so that it is able to update and create ZIP Archives.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256:9404765ef7487013f01ecf24c0a1f70040cd11e7dbb6378646d15de4e5660a40
User & Date: drh 2018-03-09 21:54:01
Context
2018-03-09
22:18
Fix the .archive command in the CLI so that it actually compresses content. check-in: 3c2e3c2d user: drh tags: trunk
21:54
Enhance the ".ar" command in the CLI so that it is able to update and create ZIP Archives. check-in: 9404765e user: drh tags: trunk
16:37
Setting ".stats 2" in the CLI causes column metadata for each prepared statement to be displayed. check-in: 7fea00fd user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/shell.c.in.

3428
3429
3430
3431
3432
3433
3434





3435
3436
3437
3438
3439
3440


3441

3442
3443
3444
3445
3446
3447
3448
3449
3450
3451


3452
3453
3454
3455
3456
3457
3458
....
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
....
5254
5255
5256
5257
5258
5259
5260
5261
5262
5263
5264
5265
5266
5267
5268
5269
5270
5271
5272








5273

5274
5275



5276

5277
5278




















5279
5280
5281
5282
5283

5284
5285
5286
5287
5288
5289
5290

5291
5292
5293
5294





5295
5296
5297
5298
5299
5300
5301
....
5309
5310
5311
5312
5313
5314
5315
5316
5317
5318
5319
5320

5321
5322
5323
5324
5325
5326
5327
5328
5329
5330
5331
5332
5333
5334
5335
5336
....
5354
5355
5356
5357
5358
5359
5360
5361
5362
5363
5364
5365
5366
5367
5368
  return 1;
}
#endif

/*
** Try to deduce the type of file for zName based on its content.  Return
** one of the SHELL_OPEN_* constants.





*/
static int deduceDatabaseType(const char *zName){
  FILE *f = fopen(zName, "rb");
  size_t n;
  int rc = SHELL_OPEN_UNSPEC;
  char zBuf[100];


  if( f==0 ) return SHELL_OPEN_NORMAL;

  fseek(f, -25, SEEK_END);
  n = fread(zBuf, 25, 1, f);
  if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){
    rc = SHELL_OPEN_APPENDVFS;
  }else{
    fseek(f, -22, SEEK_END);
    n = fread(zBuf, 22, 1, f);
    if( n==1 && zBuf[0]==0x50 && zBuf[1]==0x4b && zBuf[2]==0x05
       && zBuf[3]==0x06 ){
      rc = SHELL_OPEN_ZIPFILE;


    }
  }
  fclose(f);
  return rc;  
}

/*
................................................................................
** Make sure the database is open.  If it is not, then open it.  If
** the database fails to open, print an error message and exit.
*/
static void open_db(ShellState *p, int keepAlive){
  if( p->db==0 ){
    sqlite3_initialize();
    if( p->openMode==SHELL_OPEN_UNSPEC && access(p->zDbFilename,0)==0 ){
      p->openMode = (u8)deduceDatabaseType(p->zDbFilename);
    }
    switch( p->openMode ){
      case SHELL_OPEN_APPENDVFS: {
        sqlite3_open_v2(p->zDbFilename, &p->db, 
           SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, "apndvfs");
        break;
      }
................................................................................
      "  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 *zInsertFmt = 
     "REPLACE INTO sqlar(name,mode,mtime,sz,data)\n"
     "  SELECT\n"
     "    %s,\n"
     "    mode,\n"
     "    mtime,\n"
     "    CASE substr(lsmode(mode),1,1)\n"
     "      WHEN '-' THEN length(data)\n"
     "      WHEN 'd' THEN 0\n"
     "      ELSE -1 END,\n"
     "    CASE WHEN lsmode(mode) LIKE 'd%%' THEN NULL else data END\n"
     "  FROM fsdir(%Q,%Q)\n"








     "  WHERE lsmode(mode) NOT LIKE '?%%';";

  int i;                          /* For iterating through azFile[] */
  int rc;                         /* Return code */





  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);

  for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){
    char *zSql = sqlite3_mprintf(zInsertFmt,
        pAr->bVerbose ? "shell_putsnl(name)" : "name",
        pAr->azArg[i], pAr->zDir);
    rc = arExecSql(pAr, zSql);
    sqlite3_free(zSql);
  }

  if( rc!=SQLITE_OK ){
    arExecSql(pAr, "ROLLBACK TO ar; RELEASE ar;");
  }else{
    rc = arExecSql(pAr, "RELEASE ar;");





  }
  return rc;
}

/*
** Implementation of ".ar" dot command.
*/
................................................................................
  memset(&cmd, 0, sizeof(cmd));
  rc = arParseCommand(azArg, nArg, &cmd);
  if( rc==SQLITE_OK ){
    int eDbType = SHELL_OPEN_UNSPEC;
    cmd.p = pState;
    cmd.db = pState->db;
    if( cmd.zFile ){
      eDbType = deduceDatabaseType(cmd.zFile);
    }else{
      eDbType = pState->openMode;
    }
    if( eDbType==SHELL_OPEN_ZIPFILE ){

      if( cmd.zFile==0 ){
        cmd.zSrcTable = sqlite3_mprintf("zip");
      }else{
        cmd.zSrcTable = sqlite3_mprintf("zipfile(%Q)", cmd.zFile);
      }
      if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_UPDATE ){
        utf8_printf(stderr, "zip archives are read-only\n");
        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;
................................................................................
#ifdef SQLITE_HAVE_ZLIB
      sqlite3_sqlar_init(cmd.db, 0, 0);
#endif
      sqlite3_create_function(cmd.db, "shell_putsnl", 1, SQLITE_UTF8, cmd.p,
                              shellPutsFunc, 0, 0);

    }
    if( cmd.zSrcTable==0 ){
      if( cmd.eCmd!=AR_CMD_CREATE
       && 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;
      }







>
>
>
>
>

|




>
>
|
>










>
>







 







|







 







|
|








|

>
>
>
>
>
>
>
>
|
>


>
>
>

>


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
|
>

|





>




>
>
>
>
>







 







|




>
|
|
|
|
|
<
<
<
<







 







|







3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
....
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
....
5264
5265
5266
5267
5268
5269
5270
5271
5272
5273
5274
5275
5276
5277
5278
5279
5280
5281
5282
5283
5284
5285
5286
5287
5288
5289
5290
5291
5292
5293
5294
5295
5296
5297
5298
5299
5300
5301
5302
5303
5304
5305
5306
5307
5308
5309
5310
5311
5312
5313
5314
5315
5316
5317
5318
5319
5320
5321
5322
5323
5324
5325
5326
5327
5328
5329
5330
5331
5332
5333
5334
5335
5336
5337
5338
5339
5340
5341
5342
5343
5344
5345
5346
5347
5348
5349
5350
5351
....
5359
5360
5361
5362
5363
5364
5365
5366
5367
5368
5369
5370
5371
5372
5373
5374
5375
5376




5377
5378
5379
5380
5381
5382
5383
....
5401
5402
5403
5404
5405
5406
5407
5408
5409
5410
5411
5412
5413
5414
5415
  return 1;
}
#endif

/*
** Try to deduce the type of file for zName based on its content.  Return
** one of the SHELL_OPEN_* constants.
**
** If the file does not exist or is empty but its name looks like a ZIP
** archive and the dfltZip flag is true, then assume it is a ZIP archive.
** Otherwise, assume an ordinary database regardless of the filename if
** the type cannot be determined from content.
*/
static int deduceDatabaseType(const char *zName, int dfltZip){
  FILE *f = fopen(zName, "rb");
  size_t n;
  int rc = SHELL_OPEN_UNSPEC;
  char zBuf[100];
  if( f==0 ){
    if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ) return SHELL_OPEN_ZIPFILE;
    return SHELL_OPEN_NORMAL;
  }
  fseek(f, -25, SEEK_END);
  n = fread(zBuf, 25, 1, f);
  if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){
    rc = SHELL_OPEN_APPENDVFS;
  }else{
    fseek(f, -22, SEEK_END);
    n = fread(zBuf, 22, 1, f);
    if( n==1 && zBuf[0]==0x50 && zBuf[1]==0x4b && zBuf[2]==0x05
       && zBuf[3]==0x06 ){
      rc = SHELL_OPEN_ZIPFILE;
    }else if( n==0 && dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
      return SHELL_OPEN_ZIPFILE;
    }
  }
  fclose(f);
  return rc;  
}

/*
................................................................................
** Make sure the database is open.  If it is not, then open it.  If
** the database fails to open, print an error message and exit.
*/
static void open_db(ShellState *p, int keepAlive){
  if( p->db==0 ){
    sqlite3_initialize();
    if( p->openMode==SHELL_OPEN_UNSPEC && access(p->zDbFilename,0)==0 ){
      p->openMode = (u8)deduceDatabaseType(p->zDbFilename, 0);
    }
    switch( p->openMode ){
      case SHELL_OPEN_APPENDVFS: {
        sqlite3_open_v2(p->zDbFilename, &p->db, 
           SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, "apndvfs");
        break;
      }
................................................................................
      "  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 *zInsertFmt[2] = {
     "REPLACE INTO %s(name,mode,mtime,sz,data)\n"
     "  SELECT\n"
     "    %s,\n"
     "    mode,\n"
     "    mtime,\n"
     "    CASE substr(lsmode(mode),1,1)\n"
     "      WHEN '-' THEN length(data)\n"
     "      WHEN 'd' THEN 0\n"
     "      ELSE -1 END,\n"
     "    data\n"
     "  FROM fsdir(%Q,%Q)\n"
     "  WHERE lsmode(mode) NOT LIKE '?%%';",
     "REPLACE INTO %s(name,mode,mtime,data)\n"
     "  SELECT\n"
     "    %s,\n"
     "    mode,\n"
     "    mtime,\n"
     "    data\n"
     "  FROM fsdir(%Q,%Q)\n"
     "  WHERE lsmode(mode) NOT LIKE '?%%';"
  };
  int i;                          /* For iterating through azFile[] */
  int rc;                         /* Return code */
  const char *zTab = 0;           /* SQL table into which to insert */
  char *zSql;
  char zTemp[50];

  arExecSql(pAr, "PRAGMA page_size=512");
  rc = arExecSql(pAr, "SAVEPOINT ar;");
  if( rc!=SQLITE_OK ) return rc;
  zTemp[0] = 0; 
  if( pAr->bZip ){
    /* Initialize the zipfile virtual table, if necessary */
    if( pAr->zFile ){
      sqlite3_uint64 r;
      sqlite3_randomness(sizeof(r),&r);
      sqlite3_snprintf(sizeof(zTemp),zTemp,"zip%016llx",r);
      zTab = zTemp;
      zSql = sqlite3_mprintf(
         "CREATE VIRTUAL TABLE temp.%s USING zipfile(%Q)",
         zTab, pAr->zFile
      );
      rc = arExecSql(pAr, zSql);
      sqlite3_free(zSql);
    }else{
      zTab = "zip";
    }
  }else{
    /* Initialize the table for an SQLAR */
    zTab = "sqlar";
    if( bUpdate==0 ){
      rc = arExecSql(pAr, zDrop);
      if( rc!=SQLITE_OK ) goto end_ar_transaction;
    }
    rc = arExecSql(pAr, zCreate);
  }
  for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){
    char *zSql = sqlite3_mprintf(zInsertFmt[pAr->bZip], zTab,
        pAr->bVerbose ? "shell_putsnl(name)" : "name",
        pAr->azArg[i], pAr->zDir);
    rc = arExecSql(pAr, zSql);
    sqlite3_free(zSql);
  }
end_ar_transaction:
  if( rc!=SQLITE_OK ){
    arExecSql(pAr, "ROLLBACK TO ar; RELEASE ar;");
  }else{
    rc = arExecSql(pAr, "RELEASE ar;");
    if( pAr->bZip && pAr->zFile ){
      zSql = sqlite3_mprintf("DROP TABLE %s", zTemp);
      arExecSql(pAr, zSql);
      sqlite3_free(zSql);
    }
  }
  return rc;
}

/*
** Implementation of ".ar" dot command.
*/
................................................................................
  memset(&cmd, 0, sizeof(cmd));
  rc = arParseCommand(azArg, nArg, &cmd);
  if( rc==SQLITE_OK ){
    int eDbType = SHELL_OPEN_UNSPEC;
    cmd.p = pState;
    cmd.db = pState->db;
    if( cmd.zFile ){
      eDbType = deduceDatabaseType(cmd.zFile, 1);
    }else{
      eDbType = pState->openMode;
    }
    if( eDbType==SHELL_OPEN_ZIPFILE ){
      if( cmd.eCmd==AR_CMD_EXTRACT || cmd.eCmd==AR_CMD_LIST ){
        if( cmd.zFile==0 ){
          cmd.zSrcTable = sqlite3_mprintf("zip");
        }else{
          cmd.zSrcTable = sqlite3_mprintf("zipfile(%Q)", cmd.zFile);
        }




      }
      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;
................................................................................
#ifdef SQLITE_HAVE_ZLIB
      sqlite3_sqlar_init(cmd.db, 0, 0);
#endif
      sqlite3_create_function(cmd.db, "shell_putsnl", 1, SQLITE_UTF8, cmd.p,
                              shellPutsFunc, 0, 0);

    }
    if( cmd.zSrcTable==0 && cmd.bZip==0 ){
      if( cmd.eCmd!=AR_CMD_CREATE
       && 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;
      }