/ Check-in [af5c9ee4]
Login
SQLite training in Houston TX on 2019-11-05 (details)
Part of the 2019 Tcl Conference

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

Overview
Comment:Ensure that when the source of a backup is a database that is zero bytes in size, the final destination database consists of at least one page. Truncating it to zero bytes is equivalent to zeroing the schema cookie and change counter, which can cause problems for existing clients.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | zero-byte-backup-fix
Files: files | file ages | folders
SHA1: af5c9ee4a453f71c03f24ad08824ced6c1b97afb
User & Date: dan 2012-10-13 19:58:11
Context
2012-10-13
20:20
Modify backup4 test name prefix to make the resulting test names unique. check-in: 637fb1c1 user: mistachkin tags: zero-byte-backup-fix
19:58
Ensure that when the source of a backup is a database that is zero bytes in size, the final destination database consists of at least one page. Truncating it to zero bytes is equivalent to zeroing the schema cookie and change counter, which can cause problems for existing clients. check-in: af5c9ee4 user: dan tags: zero-byte-backup-fix
09:31
Allow the showdb tool to be compiled with MSVC. check-in: dce391fc user: mistachkin tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to src/backup.c.

   409    409     
   410    410       /* Update the schema version field in the destination database. This
   411    411       ** is to make sure that the schema-version really does change in
   412    412       ** the case where the source and destination databases have the
   413    413       ** same schema version.
   414    414       */
   415    415       if( rc==SQLITE_DONE ){
   416         -      rc = sqlite3BtreeUpdateMeta(p->pDest,1,p->iDestSchema+1);
          416  +      if( nSrcPage==0 ){
          417  +        rc = sqlite3BtreeNewDb(p->pDest);
          418  +        nSrcPage = 1;
          419  +      }
          420  +      if( rc==SQLITE_OK || rc==SQLITE_DONE ){
          421  +        rc = sqlite3BtreeUpdateMeta(p->pDest,1,p->iDestSchema+1);
          422  +      }
   417    423         if( rc==SQLITE_OK ){
   418    424           if( p->pDestDb ){
   419    425             sqlite3ResetAllSchemasOfConnection(p->pDestDb);
   420    426           }
   421    427           if( destMode==PAGER_JOURNALMODE_WAL ){
   422    428             rc = sqlite3BtreeSetVersion(p->pDest, 2);
   423    429           }
................................................................................
   443    449             nDestTruncate = (nSrcPage+ratio-1)/ratio;
   444    450             if( nDestTruncate==(int)PENDING_BYTE_PAGE(p->pDest->pBt) ){
   445    451               nDestTruncate--;
   446    452             }
   447    453           }else{
   448    454             nDestTruncate = nSrcPage * (pgszSrc/pgszDest);
   449    455           }
          456  +        assert( nDestTruncate>0 );
   450    457           sqlite3PagerTruncateImage(pDestPager, nDestTruncate);
   451    458   
   452    459           if( pgszSrc<pgszDest ){
   453    460             /* If the source page-size is smaller than the destination page-size,
   454    461             ** two extra things may need to happen:
   455    462             **
   456    463             **   * The destination may need to be truncated, and
................................................................................
   461    468             */
   462    469             const i64 iSize = (i64)pgszSrc * (i64)nSrcPage;
   463    470             sqlite3_file * const pFile = sqlite3PagerFile(pDestPager);
   464    471             i64 iOff;
   465    472             i64 iEnd;
   466    473   
   467    474             assert( pFile );
   468         -          assert( (i64)nDestTruncate*(i64)pgszDest >= iSize || (
          475  +          assert( nDestTruncate==0 
          476  +              || (i64)nDestTruncate*(i64)pgszDest >= iSize || (
   469    477                   nDestTruncate==(int)(PENDING_BYTE_PAGE(p->pDest->pBt)-1)
   470    478                && iSize>=PENDING_BYTE && iSize<=PENDING_BYTE+pgszDest
   471    479             ));
   472    480   
   473    481             /* This call ensures that all data required to recreate the original
   474    482             ** database has been stored in the journal for pDestPager and the
   475    483             ** journal synced to disk. So at this point we may safely modify

Changes to src/btree.c.

  2526   2526     put4byte(&data[36 + 4*4], pBt->autoVacuum);
  2527   2527     put4byte(&data[36 + 7*4], pBt->incrVacuum);
  2528   2528   #endif
  2529   2529     pBt->nPage = 1;
  2530   2530     data[31] = 1;
  2531   2531     return SQLITE_OK;
  2532   2532   }
         2533  +
         2534  +/*
         2535  +** Initialize the first page of the database file (creating a database
         2536  +** consisting of a single page and no schema objects). Return SQLITE_OK
         2537  +** if successful, or an SQLite error code otherwise.
         2538  +*/
         2539  +int sqlite3BtreeNewDb(Btree *p){
         2540  +  int rc;
         2541  +  sqlite3BtreeEnter(p);
         2542  +  p->pBt->nPage = 0;
         2543  +  rc = newDatabase(p->pBt);
         2544  +  sqlite3BtreeLeave(p);
         2545  +  return rc;
         2546  +}
  2533   2547   
  2534   2548   /*
  2535   2549   ** Attempt to start a new transaction. A write-transaction
  2536   2550   ** is started if the second argument is nonzero, otherwise a read-
  2537   2551   ** transaction.  If the second argument is 2 or more and exclusive
  2538   2552   ** transaction is started, meaning that no other process is allowed
  2539   2553   ** to access the database.  A preexisting transaction may not be

Changes to src/btree.h.

   112    112   
   113    113   int sqlite3BtreeDropTable(Btree*, int, int*);
   114    114   int sqlite3BtreeClearTable(Btree*, int, int*);
   115    115   void sqlite3BtreeTripAllCursors(Btree*, int);
   116    116   
   117    117   void sqlite3BtreeGetMeta(Btree *pBtree, int idx, u32 *pValue);
   118    118   int sqlite3BtreeUpdateMeta(Btree*, int idx, u32 value);
          119  +
          120  +int sqlite3BtreeNewDb(Btree *p);
   119    121   
   120    122   /*
   121    123   ** The second parameter to sqlite3BtreeGetMeta or sqlite3BtreeUpdateMeta
   122    124   ** should be one of the following values. The integer values are assigned 
   123    125   ** to constants so that the offset of the corresponding field in an
   124    126   ** SQLite database header may be found using the following formula:
   125    127   **

Added test/backup4.test.

            1  +# 2012 October 13
            2  +#
            3  +# The author disclaims copyright to this source code.  In place of
            4  +# a legal notice, here is a blessing:
            5  +#
            6  +#    May you do good and not evil.
            7  +#    May you find forgiveness for yourself and forgive others.
            8  +#    May you share freely, never taking more than you give.
            9  +#
           10  +#***********************************************************************
           11  +#
           12  +# The tests in this file verify that if an empty database (zero bytes in 
           13  +# size) is used as the source of a backup operation, the final destination
           14  +# database is one page in size.
           15  +#
           16  +# The destination must consist of at least one page as truncating a 
           17  +# database file to zero bytes is equivalent to resetting the database
           18  +# schema cookie and change counter. Doing that could cause other clients
           19  +# to become confused and continue using out-of-date cache data.
           20  +#
           21  +
           22  +set testdir [file dirname $argv0]
           23  +source $testdir/tester.tcl
           24  +set testprefix backup
           25  +
           26  +#-------------------------------------------------------------------------
           27  +# At one point this test was failing because [db] was using an out of
           28  +# date schema in test case 1.2.
           29  +#
           30  +do_execsql_test 1.0 {
           31  +  CREATE TABLE t1(x, y, UNIQUE(x, y));
           32  +  INSERT INTO t1 VALUES('one', 'two');
           33  +  SELECT * FROM t1 WHERE x='one';
           34  +  PRAGMA integrity_check;
           35  +} {one two ok}
           36  +
           37  +do_test 1.1 {
           38  +  sqlite3 db1 :memory:
           39  +  db1 backup test.db
           40  +  sqlite3 db1 test.db
           41  +  db1 eval {
           42  +    CREATE TABLE t1(x, y);
           43  +    INSERT INTO t1 VALUES('one', 'two');
           44  +  }
           45  +  db1 close
           46  +} {}
           47  +
           48  +do_execsql_test 1.2 {
           49  +  SELECT * FROM t1 WHERE x='one';
           50  +  PRAGMA integrity_check;
           51  +} {one two ok}
           52  +
           53  +db close
           54  +forcedelete test.db
           55  +forcedelete test.db2
           56  +sqlite3 db test.db
           57  +
           58  +#-------------------------------------------------------------------------
           59  +# Test that if the source is zero bytes, the destination database 
           60  +# consists of a single page only.
           61  +#
           62  +do_execsql_test 2.1 {
           63  +  CREATE TABLE t1(a, b);
           64  +  CREATE INDEX i1 ON t1(a, b);
           65  +}
           66  +
           67  +do_test 2.2 { file size test.db } 3072
           68  +
           69  +do_test 2.3 {
           70  +  sqlite3 db1 test.db2
           71  +  db1 backup test.db
           72  +  db1 close
           73  +  file size test.db
           74  +} {1024}
           75  +
           76  +do_test 2.4 { file size test.db2 } 0
           77  +
           78  +db close
           79  +forcedelete test.db
           80  +forcedelete test.db2
           81  +sqlite3 db test.db
           82  +
           83  +#-------------------------------------------------------------------------
           84  +# Test that if the destination has a page-size larger than the implicit
           85  +# page-size of the source, the final destination database still consists
           86  +# of a single page.
           87  +#
           88  +do_execsql_test 3.1 {
           89  +  PRAGMA page_size = 4096;
           90  +  CREATE TABLE t1(a, b);
           91  +  CREATE INDEX i1 ON t1(a, b);
           92  +}
           93  +
           94  +do_test 3.2 { file size test.db } 12288
           95  +
           96  +do_test 3.3 {
           97  +  sqlite3 db1 test.db2
           98  +  db1 backup test.db
           99  +  db1 close
          100  +  file size test.db
          101  +} {1024}
          102  +
          103  +do_test 3.4 { file size test.db2 } 0
          104  +
          105  +finish_test
          106  +

Changes to test/pager1.test.

  1361   1361       CREATE TABLE t2(a, b);
  1362   1362     } db2
  1363   1363     sqlite3_backup B db2 main db main
  1364   1364     list [B step 10000] [B finish]
  1365   1365   } {SQLITE_DONE SQLITE_OK}
  1366   1366   do_test pager1-9.4.2 {
  1367   1367     list [file size test.db2] [file size test.db]
  1368         -} {0 0}
         1368  +} {1024 0}
  1369   1369   db2 close
  1370   1370   
  1371   1371   #-------------------------------------------------------------------------
  1372   1372   # Test that regardless of the value returned by xSectorSize(), the
  1373   1373   # minimum effective sector-size is 512 and the maximum 65536 bytes.
  1374   1374   #
  1375   1375   testvfs tv -default 1