From b483eba0518170239324b7aac7a76ee7b555d5f1 Mon Sep 17 00:00:00 2001 From: dan Date: Sat, 13 Oct 2012 19:58:11 +0000 Subject: [PATCH] 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. FossilOrigin-Name: af5c9ee4a453f71c03f24ad08824ced6c1b97afb --- manifest | 24 ++++++----- manifest.uuid | 2 +- src/backup.c | 12 +++++- src/btree.c | 14 ++++++ src/btree.h | 2 + test/backup4.test | 106 ++++++++++++++++++++++++++++++++++++++++++++++ test/pager1.test | 2 +- 7 files changed, 148 insertions(+), 14 deletions(-) create mode 100644 test/backup4.test diff --git a/manifest b/manifest index ca0c8d1cc8..fa99540d0a 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Allow\sthe\sshowdb\stool\sto\sbe\scompiled\swith\sMSVC. -D 2012-10-13T09:31:20.156 +C Ensure\sthat\swhen\sthe\ssource\sof\sa\sbackup\sis\sa\sdatabase\sthat\sis\szero\sbytes\sin\ssize,\sthe\sfinal\sdestination\sdatabase\sconsists\sof\sat\sleast\sone\spage.\sTruncating\sit\sto\szero\sbytes\sis\sequivalent\sto\szeroing\sthe\sschema\scookie\sand\schange\scounter,\swhich\scan\scause\sproblems\sfor\sexisting\sclients. +D 2012-10-13T19:58:11.924 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 5f4f26109f9d80829122e0e09f9cda008fa065fb F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -118,11 +118,11 @@ F src/alter.c f8db986c03eb0bfb221523fc9bbb9d0b70de3168 F src/analyze.c 7553068d21e32a57fc33ab6b2393fc8c1ba41410 F src/attach.c 34c15ecd686e58f08e5bb1389e28a0b65c2c83db F src/auth.c 523da7fb4979469955d822ff9298352d6b31de34 -F src/backup.c afc067b9a9050ff48b9d46285c53d096c556a73d +F src/backup.c cab40f2c1fe79d6eb93d3b4086c78c41ad2fa5d0 F src/bitvec.c 26675fe8e431dc555e6f2d0e11e651d172234aa1 F src/btmutex.c 976f45a12e37293e32cae0281b15a21d48a8aaa7 -F src/btree.c 14de53ebb334633ee632ab3c5b9262cfe7cbe455 -F src/btree.h 078f76b28c338ab6eb6dd7324d63ee54463aeb6c +F src/btree.c 299377f0793038ad3ea322907fbda7fbea615094 +F src/btree.h 3ad7964d6c5b1c7bff569aab6adfa075f8bf06cd F src/btreeInt.h 4e5c2bd0f9b36b2a815a6d84f771a61a65830621 F src/build.c f35dac52924a6e8e6346a90f0c195a84e28b6f21 F src/callback.c d7e46f40c3cf53c43550b7da7a1d0479910b62cc @@ -289,6 +289,7 @@ F test/avtrans.test 0252654f4295ddda3b2cce0e894812259e655a85 F test/backcompat.test ecd841f3a3bfb81518721879cc56a760670e3198 F test/backup.test c9cdd23a495864b9edf75a9fa66f5cb7e10fcf62 F test/backup2.test 34986ef926ea522911a51dfdb2f8e99b7b75ebcf +F test/backup4.test cd78366929236357d3b36cbec768a67258817830 F test/backup_ioerr.test 40d208bc9224b666ee3ed423f49bc9062a36a9d0 F test/backup_malloc.test 7162d604ec2b4683c4b3799a48657fb8b5e2d450 F test/badutf.test d5360fc31f643d37a973ab0d8b4fb85799c3169f @@ -637,7 +638,7 @@ F test/openv2.test 0d3040974bf402e19b7df4b783e447289d7ab394 F test/orderby1.test ef4f7c40df81b9a4303a718433d34052f07db47d F test/orderby2.test bc11009f7cd99d96b1b11e57b199b00633eb5b04 F test/oserror.test 50417780d0e0d7cd23cf12a8277bb44024765df3 -F test/pager1.test 2163c6ef119f497a71a84137c957c63763e640ab +F test/pager1.test 07116f72a61960b882952e7472cc2846d161d6e2 F test/pager2.test 745b911dde3d1f24ae0870bd433dfa83d7c658c1 F test/pager3.test 3856d9c80839be0668efee1b74811b1b7f7fc95f F test/pagerfault.test 452f2cc23e3bfcfa935f4442aec1da4fe1dc0442 @@ -1019,7 +1020,10 @@ F tool/vdbe-compress.tcl f12c884766bd14277f4fcedcae07078011717381 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381 F tool/win/sqlite.vsix 67d8a99aceb56384a81b3f30d6c71743146d2cc9 -P de784399ed1f0e27fc875e32719643d19819c8fb -R eae4686c36e29e07358a0f64b1678cc7 -U mistachkin -Z 3d1371a2f131ddfec977348963d5fb7d +P dce391fc6302301e9ac53a5e4fc69f3bd7ba13a7 +R d0a8a8a7a76a5a8279230581af3ba8e0 +T *branch * zero-byte-backup-fix +T *sym-zero-byte-backup-fix * +T -sym-trunk * +U dan +Z 43bd238d77d2ace9bdb6f46c1f518da4 diff --git a/manifest.uuid b/manifest.uuid index 3bf3a77875..b142990ca5 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -dce391fc6302301e9ac53a5e4fc69f3bd7ba13a7 \ No newline at end of file +af5c9ee4a453f71c03f24ad08824ced6c1b97afb \ No newline at end of file diff --git a/src/backup.c b/src/backup.c index 6abc11c20c..b234716d61 100644 --- a/src/backup.c +++ b/src/backup.c @@ -413,7 +413,13 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){ ** same schema version. */ if( rc==SQLITE_DONE ){ - rc = sqlite3BtreeUpdateMeta(p->pDest,1,p->iDestSchema+1); + if( nSrcPage==0 ){ + rc = sqlite3BtreeNewDb(p->pDest); + nSrcPage = 1; + } + if( rc==SQLITE_OK || rc==SQLITE_DONE ){ + rc = sqlite3BtreeUpdateMeta(p->pDest,1,p->iDestSchema+1); + } if( rc==SQLITE_OK ){ if( p->pDestDb ){ sqlite3ResetAllSchemasOfConnection(p->pDestDb); @@ -447,6 +453,7 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){ }else{ nDestTruncate = nSrcPage * (pgszSrc/pgszDest); } + assert( nDestTruncate>0 ); sqlite3PagerTruncateImage(pDestPager, nDestTruncate); if( pgszSrc= iSize || ( + assert( nDestTruncate==0 + || (i64)nDestTruncate*(i64)pgszDest >= iSize || ( nDestTruncate==(int)(PENDING_BYTE_PAGE(p->pDest->pBt)-1) && iSize>=PENDING_BYTE && iSize<=PENDING_BYTE+pgszDest )); diff --git a/src/btree.c b/src/btree.c index 4ee0c860a7..c0cda1281b 100644 --- a/src/btree.c +++ b/src/btree.c @@ -2531,6 +2531,20 @@ static int newDatabase(BtShared *pBt){ return SQLITE_OK; } +/* +** Initialize the first page of the database file (creating a database +** consisting of a single page and no schema objects). Return SQLITE_OK +** if successful, or an SQLite error code otherwise. +*/ +int sqlite3BtreeNewDb(Btree *p){ + int rc; + sqlite3BtreeEnter(p); + p->pBt->nPage = 0; + rc = newDatabase(p->pBt); + sqlite3BtreeLeave(p); + return rc; +} + /* ** Attempt to start a new transaction. A write-transaction ** is started if the second argument is nonzero, otherwise a read- diff --git a/src/btree.h b/src/btree.h index 0efa0cdcdc..d4c9fe37d7 100644 --- a/src/btree.h +++ b/src/btree.h @@ -117,6 +117,8 @@ void sqlite3BtreeTripAllCursors(Btree*, int); void sqlite3BtreeGetMeta(Btree *pBtree, int idx, u32 *pValue); int sqlite3BtreeUpdateMeta(Btree*, int idx, u32 value); +int sqlite3BtreeNewDb(Btree *p); + /* ** The second parameter to sqlite3BtreeGetMeta or sqlite3BtreeUpdateMeta ** should be one of the following values. The integer values are assigned diff --git a/test/backup4.test b/test/backup4.test new file mode 100644 index 0000000000..1d24e16d57 --- /dev/null +++ b/test/backup4.test @@ -0,0 +1,106 @@ +# 2012 October 13 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# The tests in this file verify that if an empty database (zero bytes in +# size) is used as the source of a backup operation, the final destination +# database is one page in size. +# +# The destination must consist of at least one page as truncating a +# database file to zero bytes is equivalent to resetting the database +# schema cookie and change counter. Doing that could cause other clients +# to become confused and continue using out-of-date cache data. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix backup + +#------------------------------------------------------------------------- +# At one point this test was failing because [db] was using an out of +# date schema in test case 1.2. +# +do_execsql_test 1.0 { + CREATE TABLE t1(x, y, UNIQUE(x, y)); + INSERT INTO t1 VALUES('one', 'two'); + SELECT * FROM t1 WHERE x='one'; + PRAGMA integrity_check; +} {one two ok} + +do_test 1.1 { + sqlite3 db1 :memory: + db1 backup test.db + sqlite3 db1 test.db + db1 eval { + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES('one', 'two'); + } + db1 close +} {} + +do_execsql_test 1.2 { + SELECT * FROM t1 WHERE x='one'; + PRAGMA integrity_check; +} {one two ok} + +db close +forcedelete test.db +forcedelete test.db2 +sqlite3 db test.db + +#------------------------------------------------------------------------- +# Test that if the source is zero bytes, the destination database +# consists of a single page only. +# +do_execsql_test 2.1 { + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a, b); +} + +do_test 2.2 { file size test.db } 3072 + +do_test 2.3 { + sqlite3 db1 test.db2 + db1 backup test.db + db1 close + file size test.db +} {1024} + +do_test 2.4 { file size test.db2 } 0 + +db close +forcedelete test.db +forcedelete test.db2 +sqlite3 db test.db + +#------------------------------------------------------------------------- +# Test that if the destination has a page-size larger than the implicit +# page-size of the source, the final destination database still consists +# of a single page. +# +do_execsql_test 3.1 { + PRAGMA page_size = 4096; + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a, b); +} + +do_test 3.2 { file size test.db } 12288 + +do_test 3.3 { + sqlite3 db1 test.db2 + db1 backup test.db + db1 close + file size test.db +} {1024} + +do_test 3.4 { file size test.db2 } 0 + +finish_test + diff --git a/test/pager1.test b/test/pager1.test index 61a0c0ccdb..fac434037e 100644 --- a/test/pager1.test +++ b/test/pager1.test @@ -1365,7 +1365,7 @@ do_test pager1-9.4.1 { } {SQLITE_DONE SQLITE_OK} do_test pager1-9.4.2 { list [file size test.db2] [file size test.db] -} {0 0} +} {1024 0} db2 close #-------------------------------------------------------------------------