Commit first version of the 'backup' feature. (CVS 6241)

FossilOrigin-Name: 663479b417fc06ba1790a544f28694f8797cee57
This commit is contained in:
danielk1977 2009-02-03 16:51:24 +00:00
parent 7ed0cae237
commit 0410302e58
26 changed files with 2157 additions and 288 deletions

View File

@ -195,6 +195,7 @@ SRC = \
$(TOP)/src/analyze.c \
$(TOP)/src/attach.c \
$(TOP)/src/auth.c \
$(TOP)/src/backup.c \
$(TOP)/src/bitvec.c \
$(TOP)/src/btmutex.c \
$(TOP)/src/btree.c \
@ -328,6 +329,7 @@ SRC += \
#
TESTSRC2 = \
$(TOP)/src/attach.c \
$(TOP)/src/backup.c \
$(TOP)/src/bitvec.c \
$(TOP)/src/btree.c \
$(TOP)/src/build.c \
@ -372,6 +374,7 @@ TESTSRC = \
$(TOP)/src/test9.c \
$(TOP)/src/test_autoext.c \
$(TOP)/src/test_async.c \
$(TOP)/src/test_backup.c \
$(TOP)/src/test_btree.c \
$(TOP)/src/test_config.c \
$(TOP)/src/test_devsym.c \

View File

@ -50,7 +50,7 @@ TCCX += -I$(TOP)/ext/rtree -I$(TOP)/ext/icu -I$(TOP)/ext/fts3
# Object files for the SQLite library.
#
LIBOBJ+= alter.o analyze.o attach.o auth.o \
bitvec.o btmutex.o btree.o build.o \
backup.o bitvec.o btmutex.o btree.o build.o \
callback.o complete.o date.o delete.o expr.o fault.o \
fts3.o fts3_expr.o fts3_hash.o fts3_icu.o fts3_porter.o \
fts3_tokenizer.o fts3_tokenizer1.o \
@ -76,6 +76,7 @@ SRC = \
$(TOP)/src/analyze.c \
$(TOP)/src/attach.c \
$(TOP)/src/auth.c \
$(TOP)/src/backup.c \
$(TOP)/src/bitvec.c \
$(TOP)/src/btmutex.c \
$(TOP)/src/btree.c \
@ -220,6 +221,7 @@ TESTSRC = \
$(TOP)/src/test9.c \
$(TOP)/src/test_autoext.c \
$(TOP)/src/test_async.c \
$(TOP)/src/test_backup.c \
$(TOP)/src/test_btree.c \
$(TOP)/src/test_config.c \
$(TOP)/src/test_devsym.c \
@ -242,7 +244,8 @@ TESTSRC = \
#TESTSRC += $(TOP)/ext/fts3/fts3_tokenizer.c
TESTSRC2 = \
$(TOP)/src/attach.c $(TOP)/src/btree.c $(TOP)/src/build.c $(TOP)/src/date.c \
$(TOP)/src/attach.c $(TOP)/src/backup.c $(TOP)/src/btree.c \
$(TOP)/src/build.c $(TOP)/src/date.c \
$(TOP)/src/expr.c $(TOP)/src/func.c $(TOP)/src/insert.c $(TOP)/src/os.c \
$(TOP)/src/os_os2.c $(TOP)/src/os_unix.c $(TOP)/src/os_win.c \
$(TOP)/src/pager.c $(TOP)/src/pragma.c $(TOP)/src/prepare.c \

View File

@ -1,7 +1,7 @@
C Fix\sthe\ssqlite3_mprintf_long\stest\scommand\s(added\sby\scheck-in\s(6224)\sin\sorder\nto\saddress\sticket\s#3621)\sso\sthat\sit\sworks\son\ssystems\swith\ssizeof(int)==4\sand\nsizeof(long)==8.\s(CVS\s6240)
D 2009-02-03T16:25:48
C Commit\sfirst\sversion\sof\sthe\s'backup'\sfeature.\s(CVS\s6241)
D 2009-02-03T16:51:25
F Makefile.arm-wince-mingw32ce-gcc fcd5e9cd67fe88836360bb4f9ef4cb7f8e2fb5a0
F Makefile.in 3871d308188cefcb7c5ab20da4c7b6aad023bc52
F Makefile.in c7a5a30fb6852bd7839b1024e1661da8549878ee
F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654
F Makefile.vxwSH4 d53b4be86491060d498b22148951b6d765884cab
F README b974cdc3f9f12b87e851b04e75996d720ebf81ac
@ -83,7 +83,7 @@ F ext/rtree/tkt3363.test 6662237ea75bb431cd5d262dfc9535e1023315fc
F ext/rtree/viewrtree.tcl 09526398dae87a5a87c5aac2b3854dbaf8376869
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F main.mk 189d17c22bc35a9223f2de0eb9ac6e818439cef7
F main.mk 2193e5939dbf91449f9b72178d543d31b2315360
F mkdll.sh 7d09b23c05d56532e9d44a50868eb4b12ff4f74a
F mkextu.sh 416f9b7089d80e5590a29692c9d9280a10dbad9f
F mkextw.sh 4123480947681d9b434a5e7b1ee08135abe409ac
@ -99,14 +99,15 @@ F sqlite3.def a1be7b9a4b8b51ac41c6ff6e8e44a14ef66b338b
F sqlite3.pc.in ae6f59a76e862f5c561eb32a380228a02afc3cad
F src/alter.c 0ec29744c36c6e976596ce38c16289ebc5dc94db
F src/analyze.c c86fd6a1425b22b3a46ce72ad403e4280026364f
F src/attach.c 1c35f95da3c62d19de75b44cfefd12c81c1791b3
F src/attach.c 81d37d1948f409146a7b22b96998fd90649d1fd3
F src/auth.c c8b2ab5c8bad4bd90ed7c294694f48269162c627
F src/backup.c a7605687863424d5d5a7ff8271f0bbcfd4fc0b57
F src/bitvec.c 44f7059ac1f874d364b34af31b9617e52223ba75
F src/btmutex.c 63c5cc4ad5715690767ffcb741e185d7bc35ec1a
F src/btree.c dfbbfc396fdd8cbc29754864a97c4df484b78870
F src/btree.h 07359623fa24748709dd61212a32364a6adc6b56
F src/btreeInt.h 44bcbfe387ba99a3a9f2527bd12fa1bb8bc574b3
F src/build.c 1d755e4920d94b7b470f47d1915b131b92fe6f6b
F src/btree.c 800a065686c49a0cdefc933779a750a7c3c0509f
F src/btree.h 4eab72af6adf95f0b08b61a72ef9781bdb0bf63f
F src/btreeInt.h 0a4884e6152d7cae9c741e91b830064c19fd2c05
F src/build.c ed7a59fa45823464b7d5fdca7712a5fb3433f757
F src/callback.c 5f10bca853e59a2c272bbfd5b720303f8b69e520
F src/complete.c cb14e06dbe79dee031031f0d9e686ff306afe07c
F src/date.c 870770dde3fb56772ab247dfb6a6eda44d16cfbc
@ -122,7 +123,7 @@ F src/insert.c f6db1e6f43aae337e64a755208abb6ff124edc19
F src/journal.c e00df0c0da8413ab6e1bb7d7cab5665d4a9000d0
F src/legacy.c 8b3b95d48d202614946d7ce7256e7ba898905c3b
F src/loadext.c 3f96631089fc4f3871a67f02f2e4fc7ea4d51edc
F src/main.c a7d7fd7df4e9f8fa3418258619436c969234fd82
F src/main.c da51988dd4d75de4ccc66d2c99dd1b5b3b266e6c
F src/malloc.c bc408056b126db37b6fba00e170d578cc67be6b3
F src/mem0.c f2f84062d1f35814d6535c9f9e33de3bfb3b132c
F src/mem1.c 3bfb39e4f60b0179713a7c087b2d4f0dc205735f
@ -142,8 +143,8 @@ F src/os_common.h 24525d8b7bce66c374dfc1810a6c9043f3359b60
F src/os_os2.c bed77dc26e3a95ce4a204936b9a1ca6fe612fcc5
F src/os_unix.c f0fce3042011d462b8ae633564a5668260bd3636
F src/os_win.c ec133f2a3c0da786995ea09ba67056af8f18cc2e
F src/pager.c 72f4e7b3076584889ce6286cd15ff2d985325026
F src/pager.h eccf5cdeebd79006ba7f9577dd30d8179b1430da
F src/pager.c 9e7c6db1635be2caf31ff3d407ecb2e145f89a8a
F src/pager.h 0c9f3520c00d8a3b8e792ca56c9a11b6b02b4b0f
F src/parse.y 4f4d16aee0d11f69fec2adb77dac88878043ed8d
F src/pcache.c fcf7738c83c4d3e9d45836b2334c8a368cc41274
F src/pcache.h 9b927ccc5a538e31b4c3bc7eec4f976db42a1324
@ -156,14 +157,14 @@ F src/resolve.c 18dc9f0df1d60048e012ce6632251063e0dd356a
F src/rowset.c ba9375f37053d422dd76965a9c370a13b6e1aac4
F src/select.c ae72b604e47092521c4d9ae54e1b1cbeb872a747
F src/shell.c 8965cf0cd7a7dac39d586a43c97adb00930e025d
F src/sqlite.h.in 8821a61dceff26993ed6689239b6fbcd8d8f6e50
F src/sqlite.h.in e0d54b3a93489154151f49007a2f1219171945fa
F src/sqlite3ext.h 1db7d63ab5de4b3e6b83dd03d1a4e64fef6d2a17
F src/sqliteInt.h 3ee870a4d5886992cd09af62f0d13dc7a6033f9f
F src/sqliteInt.h 73c1d4f9716fe21f202f9d05c4fd9e6281f2636f
F src/sqliteLimit.h ffe93f5a0c4e7bd13e70cd7bf84cfb5c3465f45d
F src/status.c 237b193efae0cf6ac3f0817a208de6c6c6ef6d76
F src/table.c 332ab0ea691e63862e2a8bdfe2c0617ee61062a3
F src/tclsqlite.c 7d77c3899d0244804d2773c9157e783788627762
F src/test1.c 461b793df7db8a8d48bf261a813b2a7ef2417e6d
F src/tclsqlite.c 7b3e7fc4856e8280939c9ca0c3a6e49bd2c4bb46
F src/test1.c f88b447699786d58a0136a3a48b12990abc72c8a
F src/test2.c 9689e7d3b7791da8c03f9acd1ea801802cb83c17
F src/test3.c 88a246b56b824275300e6c899634fbac1dc94b14
F src/test4.c f79ab52d27ff49b784b631a42e2ccd52cfd5c84c
@ -174,6 +175,7 @@ F src/test8.c 3637439424d0d21ff2dcf9b015c30fcc1e7bcb24
F src/test9.c 904ebe0ed1472d6bad17a81e2ecbfc20017dc237
F src/test_async.c 45024094ed7cf780c5d5dccda645145f95cf78ef
F src/test_autoext.c f53b0cdf7bf5f08100009572a5d65cdb540bd0ad
F src/test_backup.c 5b41518c5499dafe65177b0813b71ac356ee9df1
F src/test_btree.c d7b8716544611c323860370ee364e897c861f1b0
F src/test_config.c 9dd62f4bb725ad87d28b187b07377cb4f4a43197
F src/test_devsym.c 9f4bc2551e267ce7aeda195f3897d0f30c5228f4
@ -197,12 +199,12 @@ F src/trigger.c ca6d78f7c1314053800386ca64361e487774fda3
F src/update.c 8c4925f9ca664effc8a1faaad67449d2074567b1
F src/utf.c 1da9c832dba0fa8f865b5b902d93f420a1ee4245
F src/util.c f1ac1bcd3ec5e3300982031504659b6f9435de33
F src/vacuum.c b78c2bfdefc1b1d9aa5d82d57c333c5fde7be5a6
F src/vacuum.c 4929a585ef0fb1dfaf46302f8a9c4aa30c2d9cf5
F src/vdbe.c 81120d5a5ba2d93eb7d7f66e814bbc811305daa2
F src/vdbe.h 03516f28bf5aca00a53c4dccd6c313f96adb94f6
F src/vdbeInt.h 13cb4868ea579b5a8f6b6b5098caa99cd5a14078
F src/vdbeapi.c 85c33cfbfa56249cbe627831610afafba754477d
F src/vdbeaux.c 30c1bbc1d2876c5bbe84d52dab9980ed032bca98
F src/vdbeaux.c 75c3ac2a3c37747ae66ea0935f8f48bb1879234a
F src/vdbeblob.c b0dcebfafedcf9c0addc7901ad98f6f986c08935
F src/vdbemem.c c6127c335f802ba159c6fec4e3284ba82a070602
F src/vtab.c e39e011d7443a8d574b1b9cde207a35522e6df43
@ -231,6 +233,9 @@ F test/autoinc.test ab549b48b389cabd92967b86c379ec8b31fa6c16
F test/autovacuum.test 61260e25744189ff766f61ca3df23c1eeec0060e
F test/autovacuum_ioerr2.test 598b0663074d3673a9c1bc9a16e80971313bafe6
F test/avtrans.test 1e901d8102706b63534dbd2bdd4d8f16c4082650
F test/backup.test bd478dd20a092a99d98943dee9d92d69823a6820
F test/backup_ioerr.test 2edd5e347e263733cae8c08f41bf3dbd7277b33d
F test/backup_malloc.test 471fb098dae228ca840d4d51e41481901ac03578
F test/badutf.test d5360fc31f643d37a973ab0d8b4fb85799c3169f
F test/between.test 16b1776c6323faadb097a52d673e8e3d8be7d070
F test/bigfile.test 6adfef13d24bbe0c504b4547f292b9a170184f25
@ -487,7 +492,7 @@ F test/pragma2.test 5364893491b9231dd170e3459bfc2e2342658b47
F test/printf.test 47e9e5bbec8509023479d54ceb71c9d05a95308a
F test/progress.test 5b075c3c790c7b2a61419bc199db87aaf48b8301 x
F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc
F test/quick.test 9ab91798b047684f0dd26ee698920dbb69a30a10
F test/quick.test 4a09b89a44be46b3ee5a5dbe25b72cb1e5c3ead8
F test/quote.test 215897dbe8de1a6f701265836d6601cc6ed103e6
F test/randexpr1.tcl 40dec52119ed3a2b8b2a773bce24b63a3a746459
F test/randexpr1.test 1084050991e9ba22c1c10edd8d84673b501cc25a
@ -546,7 +551,7 @@ F test/tableapi.test 505031f15b18a750184d967d2c896cf88fcc969c
F test/tclsqlite.test 30636c3151ccc2d553aa09020b885054141a1963
F test/tempdb.test b88ac8a19823cf771d742bf61eef93ef337c06b1
F test/temptable.test 19b851b9e3e64d91e9867619b2a3f5fffee6e125
F test/tester.tcl 57b8ad3e60bd14e93c88c9b8f1106221e677d17e
F test/tester.tcl 3d11a8c1d05535400880ac4f8c5402b8dee14b7f
F test/thread001.test 71dca5edec5e44b56a9043da1ce7651c12216fe1
F test/thread002.test 84c03a9fc4f7a5f92eefe551266afa840c2eb6ae
F test/thread003.test e17754799649c2b732c295620dca041c32f01e16
@ -681,7 +686,7 @@ F tool/lempar.c aeba88b8566ff66f8a67c96b3eb2dd95e7d8908d
F tool/mkkeywordhash.c 8e57fbe8c4fe2f1800f9190fd361231cb8558407
F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e x
F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
F tool/mksqlite3c.tcl d4668afb9b48533eed969c98787fea9a3d07b565
F tool/mksqlite3c.tcl b3dcc7a9610baf36545dab6acf19605505016409
F tool/mksqlite3internalh.tcl 7b43894e21bcb1bb39e11547ce7e38a063357e87
F tool/omittest.tcl 27d6f6e3b1e95aeb26a1c140e6eb57771c6d794a
F tool/opcodeDoc.awk b3a2a3d5d3075b8bd90b7afe24283efdd586659c
@ -695,7 +700,7 @@ F tool/speedtest16.c c8a9c793df96db7e4933f0852abb7a03d48f2e81
F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
P 85e9196d79ef8500300abb215a31e0519b2e8d02
R 8b6fc22195ba110d87608f7198abea10
U drh
Z b41cee41042712f485446405aaaaa49c
P 2e45c2a85183f7430225aa8dd89ee05028afecf2
R 4e142df07079d9b7b2e171c0f7313ba2
U danielk1977
Z aac63fa067e376f14e29eb95cbe2ffd4

View File

@ -1 +1 @@
2e45c2a85183f7430225aa8dd89ee05028afecf2
663479b417fc06ba1790a544f28694f8797cee57

View File

@ -11,7 +11,7 @@
*************************************************************************
** This file contains code used to implement the ATTACH and DETACH commands.
**
** $Id: attach.c,v 1.81 2008/12/10 16:45:51 drh Exp $
** $Id: attach.c,v 1.82 2009/02/03 16:51:25 danielk1977 Exp $
*/
#include "sqliteInt.h"
@ -265,7 +265,7 @@ static void detachFunc(
"cannot DETACH database within transaction");
goto detach_error;
}
if( sqlite3BtreeIsInReadTrans(pDb->pBt) ){
if( sqlite3BtreeIsInReadTrans(pDb->pBt) || sqlite3BtreeIsInBackup(pDb->pBt) ){
sqlite3_snprintf(sizeof(zErr),zErr, "database %s is locked", zName);
goto detach_error;
}

559
src/backup.c Normal file
View File

@ -0,0 +1,559 @@
/*
** 2009 January 28
**
** 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.
**
*************************************************************************
** This file contains the implementation of the sqlite3_backup_XXX()
** API functions and the related features.
**
** $Id: backup.c,v 1.1 2009/02/03 16:51:25 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include "btreeInt.h"
/* Macro to find the minimum of two numeric values.
*/
#ifndef MIN
# define MIN(x,y) ((x)<(y)?(x):(y))
#endif
/*
** Structure allocated for each backup operation.
*/
struct sqlite3_backup {
sqlite3* pDestDb; /* Destination database handle */
Btree *pDest; /* Destination b-tree file */
u32 iDestSchema; /* Original schema cookie in destination */
int bDestLocked; /* True once a write-transaction is open on pDest */
Pgno iNext; /* Page number of the next source page to copy */
sqlite3* pSrcDb; /* Source database handle */
Btree *pSrc; /* Source b-tree file */
int rc; /* Backup process error code */
/* These two variables are set by every call to backup_step(). They are
** read by calls to backup_remaining() and backup_pagecount().
*/
Pgno nRemaining; /* Number of pages left to copy */
Pgno nPagecount; /* Total number of pages to copy */
sqlite3_backup *pNext; /* Next backup associated with source pager */
};
/*
** THREAD SAFETY NOTES:
**
** Once it has been created using backup_init(), a single sqlite3_backup
** structure may be accessed via two groups of thread-safe entry points:
**
** * Via the sqlite3_backup_XXX() API function backup_step() and
** backup_finish(). Both these functions obtain the source database
** handle mutex and the mutex associated with the source BtShared
** structure, in that order.
**
** * Via the BackupUpdate() and BackupRestart() functions, which are
** invoked by the pager layer to report various state changes in
** the page cache associated with the source database. The mutex
** associated with the source database BtShared structure will always
** be held when either of these functions are invoked.
**
** The other sqlite3_backup_XXX() API functions, backup_remaining() and
** backup_pagecount() are not thread-safe functions. If they are called
** while some other thread is calling backup_step() or backup_finish(),
** the values returned may be invalid. There is no way for a call to
** BackupUpdate() or BackupRestart() to interfere with backup_remaining()
** or backup_pagecount().
**
** Depending on the SQLite configuration, the database handles and/or
** the Btree objects may have their own mutexes that require locking.
** Non-sharable Btrees (in-memory databases for example), do not have
** associated mutexes.
*/
/*
** Return a pointer corresponding to database zDb (i.e. "main", "temp")
** in connection handle pDb. If such a database cannot be found, return
** a NULL pointer and write an error message to pErrorDb.
**
** If the "temp" database is requested, it may need to be opened by this
** function. If an error occurs while doing so, return 0 and write an
** error message to pErrorDb.
*/
static Btree *findBtree(sqlite3 *pErrorDb, sqlite3 *pDb, const char *zDb){
int i = sqlite3FindDbName(pDb, zDb);
if( i==1 ){
Parse sParse;
memset(&sParse, 0, sizeof(sParse));
sParse.db = pDb;
if( sqlite3OpenTempDatabase(&sParse) ){
sqlite3ErrorClear(&sParse);
sqlite3Error(pErrorDb, sParse.rc, "%s", sParse.zErrMsg);
return 0;
}
assert( sParse.zErrMsg==0 );
}
if( i<0 ){
sqlite3Error(pErrorDb, SQLITE_ERROR, "unknown database %s", zDb);
return 0;
}
return pDb->aDb[i].pBt;
}
/*
** Create an sqlite3_backup process to copy the contents of zSrcDb from
** connection handle pSrcDb to zDestDb in pDestDb. If successful, return
** a pointer to the new sqlite3_backup object.
**
** If an error occurs, NULL is returned and an error code and error message
** stored in database handle pDestDb.
*/
sqlite3_backup *sqlite3_backup_init(
sqlite3* pDestDb, /* Database to write to */
const char *zDestDb, /* Name of database within pDestDb */
sqlite3* pSrcDb, /* Database connection to read from */
const char *zSrcDb /* Name of database within pSrcDb */
){
sqlite3_backup *p; /* Value to return */
/* Lock the source database handle. The destination database
** handle is not locked. The user is required to ensure that no
** other thread accesses the destination handle for the duration
** of the backup operation.
*/
sqlite3_mutex_enter(pSrcDb->mutex);
if( pSrcDb==pDestDb ){
sqlite3Error(
pDestDb, SQLITE_ERROR, "Source and destination handles must be distinct"
);
p = 0;
}else {
/* Allocate space for a new sqlite3_backup object */
p = (sqlite3_backup *)sqlite3_malloc(sizeof(sqlite3_backup));
if( !p ){
sqlite3Error(pDestDb, SQLITE_NOMEM, 0);
}
}
/* If the allocation succeeded, populate the new object. */
if( p ){
memset(p, 0, sizeof(sqlite3_backup));
p->pSrc = findBtree(pDestDb, pSrcDb, zSrcDb);
p->pDest = findBtree(pDestDb, pDestDb, zDestDb);
p->pDestDb = pDestDb;
p->pSrcDb = pSrcDb;
p->iNext = 1;
if( 0==p->pSrc || 0==p->pDest ){
/* One (or both) of the named databases did not exist. An error has
** already been written into the pDestDb handle. All that is left
** to do here is free the sqlite3_backup structure.
*/
sqlite3_free(p);
p = 0;
}
}
/* If everything has gone as planned, attach the backup object to the
** source pager. The source pager calls BackupUpdate() and BackupRestart()
** to notify this module if the source file is modified mid-backup.
*/
if( p ){
sqlite3_backup **pp; /* Pointer to head of pagers backup list */
sqlite3BtreeEnter(p->pSrc);
pp = sqlite3PagerBackupPtr(sqlite3BtreePager(p->pSrc));
p->pNext = *pp;
*pp = p;
sqlite3BtreeLeave(p->pSrc);
p->pSrc->nBackup++;
}
sqlite3_mutex_leave(pSrcDb->mutex);
return p;
}
/*
** Parameter zSrcData points to a buffer containing the data for
** page iSrcPg from the source database. Copy this data into the
** destination database.
*/
static int backupOnePage(sqlite3_backup *p, Pgno iSrcPg, const u8 *zSrcData){
Pager * const pDestPager = sqlite3BtreePager(p->pDest);
const int nSrcPgsz = sqlite3BtreeGetPageSize(p->pSrc);
int nDestPgsz = sqlite3BtreeGetPageSize(p->pDest);
const int nCopy = MIN(nSrcPgsz, nDestPgsz);
const i64 iEnd = (i64)iSrcPg*(i64)nSrcPgsz;
int rc = SQLITE_OK;
i64 iOff;
assert( p->bDestLocked );
assert( p->rc==SQLITE_OK );
assert( iSrcPg!=PENDING_BYTE_PAGE(p->pSrc->pBt) );
assert( zSrcData );
/* Catch the case where the destination is an in-memory database and the
** page sizes of the source and destination differ.
*/
if( nSrcPgsz!=nDestPgsz && sqlite3PagerIsMemdb(sqlite3BtreePager(p->pDest)) ){
rc = SQLITE_READONLY;
}
/* This loop runs once for each destination page spanned by the source
** page. For each iteration, variable iOff is set to the byte offset
** of the destination page.
*/
for(iOff=iEnd-(i64)nSrcPgsz; rc==SQLITE_OK && iOff<iEnd; iOff+=nDestPgsz){
DbPage *pDestPg = 0;
Pgno iDest = (Pgno)(iOff/nDestPgsz)+1;
if( iDest==PENDING_BYTE_PAGE(p->pDest->pBt) ) continue;
if( SQLITE_OK==(rc = sqlite3PagerGet(pDestPager, iDest, &pDestPg))
&& SQLITE_OK==(rc = sqlite3PagerWrite(pDestPg))
){
const u8 *zIn = &zSrcData[iOff%nSrcPgsz];
u8 *zDestData = sqlite3PagerGetData(pDestPg);
u8 *zOut = &zDestData[iOff%nDestPgsz];
/* Copy the data from the source page into the destination page.
** Then clear the Btree layer MemPage.isInit flag. Both this module
** and the pager code use this trick (clearing the first byte
** of the page 'extra' space to invalidate the Btree layers
** cached parse of the page). MemPage.isInit is marked
** "MUST BE FIRST" for this purpose.
*/
memcpy(zOut, zIn, nCopy);
((u8 *)sqlite3PagerGetExtra(pDestPg))[0] = 0;
}
sqlite3PagerUnref(pDestPg);
}
return rc;
}
/*
** Copy nPage pages from the source b-tree to the destination.
*/
int sqlite3_backup_step(sqlite3_backup *p, int nPage){
int rc;
sqlite3_mutex_enter(p->pSrcDb->mutex);
sqlite3BtreeEnter(p->pSrc);
rc = p->rc;
if( rc==SQLITE_OK ){
Pager * const pSrcPager = sqlite3BtreePager(p->pSrc); /* Source pager */
Pager * const pDestPager = sqlite3BtreePager(p->pDest); /* Dest pager */
int ii; /* Iterator variable */
int nSrcPage; /* Size of source db in pages */
int bCloseTrans = 0; /* True if src db requires unlocking */
/* If the source pager is currently in a write-transaction, return
** SQLITE_LOCKED immediately.
*/
if( p->pDestDb && p->pSrc->pBt->inTransaction==TRANS_WRITE ){
rc = SQLITE_LOCKED;
}
/* Lock the destination database, if it is not locked already. */
if( SQLITE_OK==rc && p->bDestLocked==0
&& SQLITE_OK==(rc = sqlite3BtreeBeginTrans(p->pDest, 2))
){
p->bDestLocked = 1;
rc = sqlite3BtreeGetMeta(p->pDest, 1, &p->iDestSchema);
}
/* If there is no open read-transaction on the source database, open
** one now. If a transaction is opened here, then it will be closed
** before this function exits.
*/
if( rc==SQLITE_OK && 0==sqlite3BtreeIsInReadTrans(p->pSrc) ){
rc = sqlite3BtreeBeginTrans(p->pSrc, 0);
bCloseTrans = 1;
}
/* Now that there is a read-lock on the source database, query the
** source pager for the number of pages in the database.
*/
if( rc==SQLITE_OK ){
rc = sqlite3PagerPagecount(pSrcPager, &nSrcPage);
}
for(ii=0; ii<nPage && p->iNext<=nSrcPage && rc==SQLITE_OK; ii++){
const Pgno iSrcPg = p->iNext; /* Source page number */
if( iSrcPg!=PENDING_BYTE_PAGE(p->pSrc->pBt) ){
DbPage *pSrcPg; /* Source page object */
rc = sqlite3PagerGet(pSrcPager, iSrcPg, &pSrcPg);
if( rc==SQLITE_OK ){
rc = backupOnePage(p, iSrcPg, sqlite3PagerGetData(pSrcPg));
sqlite3PagerUnref(pSrcPg);
}
}
p->iNext++;
}
if( rc==SQLITE_OK ){
p->nPagecount = nSrcPage;
p->nRemaining = nSrcPage+1-p->iNext;
if( p->iNext>nSrcPage ){
rc = SQLITE_DONE;
}
}
if( rc==SQLITE_DONE ){
const int nSrcPagesize = sqlite3BtreeGetPageSize(p->pSrc);
const int nDestPagesize = sqlite3BtreeGetPageSize(p->pDest);
int nDestTruncate;
sqlite3_file *pFile = 0;
i64 iSize;
/* Update the schema version field in the destination database. This
** is to make sure that the schema-version really does change in
** the case where the source and destination databases have the
** same schema version.
*/
sqlite3BtreeUpdateMeta(p->pDest, 1, p->iDestSchema+1);
/* Set nDestTruncate to the final number of pages in the destination
** database. The complication here is that the destination page
** size may be different to the source page size.
**
** If the source page size is smaller than the destination page size,
** round up. In this case the call to sqlite3OsTruncate() below will
** fix the size of the file. However it is important to call
** sqlite3PagerTruncateImage() here so that any pages in the
** destination file that lie beyond the nDestTruncate page mark are
** journalled by PagerCommitPhaseOne() before they are destroyed
** by the file truncation.
*/
if( nSrcPagesize<nDestPagesize ){
int ratio = nDestPagesize/nSrcPagesize;
nDestTruncate = (nSrcPage+ratio-1)/ratio;
}else{
nDestTruncate = nSrcPage * (nSrcPagesize/nDestPagesize);
}
sqlite3PagerTruncateImage(pDestPager, nDestTruncate);
if( nSrcPagesize<nDestPagesize ){
/* If the source page-size is smaller than the destination page-size,
** two extra things may need to happen:
**
** * The destination may need to be truncated, and
**
** * Data stored on the pages immediately following the
** pending-byte page in the source database may need to be
** copied into the destination database.
*/
iSize = (i64)nSrcPagesize * (i64)nSrcPage;
pFile = sqlite3PagerFile(pDestPager);
assert( pFile );
if( SQLITE_OK==(rc = sqlite3PagerCommitPhaseOne(pDestPager, 0, 1))
&& SQLITE_OK==(rc = sqlite3OsTruncate(pFile, iSize))
&& SQLITE_OK==(rc = sqlite3PagerSync(pDestPager))
){
i64 iOff;
i64 iEnd = MIN(PENDING_BYTE + nDestPagesize, iSize);
for(
iOff=PENDING_BYTE+nSrcPagesize;
rc==SQLITE_OK && iOff<iEnd;
iOff+=nSrcPagesize
){
PgHdr *pSrcPg = 0;
const Pgno iSrcPg = (iOff/nSrcPagesize)+1;
rc = sqlite3PagerGet(pSrcPager, iSrcPg, &pSrcPg);
if( rc==SQLITE_OK ){
u8 *zData = sqlite3PagerGetData(pSrcPg);
rc = sqlite3OsWrite(pFile, zData, nSrcPagesize, iOff);
}
sqlite3PagerUnref(pSrcPg);
}
}
}else{
rc = sqlite3PagerCommitPhaseOne(pDestPager, 0, 0);
}
/* Finish committing the transaction to the destination database. */
if( SQLITE_OK==rc
&& SQLITE_OK==(rc = sqlite3BtreeCommitPhaseTwo(p->pDest))
){
rc = SQLITE_DONE;
}
}
/* If bCloseTrans is true, then this function opened a read transaction
** on the source database. Close the read transaction here. There is
** no need to check the return values of the btree methods here, as
** "committing" a read-only transaction cannot fail.
*/
if( bCloseTrans ){
TESTONLY( int rc2 );
TESTONLY( rc2 = ) sqlite3BtreeCommitPhaseOne(p->pSrc, 0);
TESTONLY( rc2 |= ) sqlite3BtreeCommitPhaseTwo(p->pSrc);
assert( rc2==SQLITE_OK );
}
if( rc!=SQLITE_LOCKED && rc!=SQLITE_BUSY ){
p->rc = rc;
}
}
sqlite3BtreeLeave(p->pSrc);
sqlite3_mutex_leave(p->pSrcDb->mutex);
return rc;
}
/*
** Release all resources associated with an sqlite3_backup* handle.
*/
int sqlite3_backup_finish(sqlite3_backup *p){
sqlite3_backup **pp; /* Ptr to head of pagers backup list */
sqlite3_mutex *mutex; /* Mutex to protect source database */
int rc; /* Value to return */
/* Enter the mutexes */
sqlite3_mutex_enter(p->pSrcDb->mutex);
sqlite3BtreeEnter(p->pSrc);
mutex = p->pSrcDb->mutex;
/* Detach this backup from the source pager. */
if( p->pDestDb ){
pp = sqlite3PagerBackupPtr(sqlite3BtreePager(p->pSrc));
while( *pp!=p ){
pp = &(*pp)->pNext;
}
*pp = p->pNext;
p->pSrc->nBackup--;
}
/* If a transaction is still open on the Btree, roll it back. */
sqlite3BtreeRollback(p->pDest);
/* Set the error code of the destination database handle. */
rc = (p->rc==SQLITE_DONE) ? SQLITE_OK : p->rc;
sqlite3Error(p->pDestDb, rc, 0);
/* Exit the mutexes and free the backup context structure. */
sqlite3BtreeLeave(p->pSrc);
if( p->pDestDb ){
sqlite3_free(p);
}
sqlite3_mutex_leave(mutex);
return rc;
}
/*
** Return the number of pages still to be backed up as of the most recent
** call to sqlite3_backup_step().
*/
int sqlite3_backup_remaining(sqlite3_backup *p){
return p->nRemaining;
}
/*
** Return the total number of pages in the source database as of the most
** recent call to sqlite3_backup_step().
*/
int sqlite3_backup_pagecount(sqlite3_backup *p){
return p->nPagecount;
}
/*
** This function is called after the contents of page iPage of the
** source database have been modified. If page iPage has already been
** copied into the destination database, then the data written to the
** destination is now invalidated. The destination copy of iPage needs
** to be updated with the new data before the backup operation is
** complete.
**
** It is assumed that the mutex associated with the BtShared object
** corresponding to the source database is held when this function is
** called.
*/
void sqlite3BackupUpdate(sqlite3_backup *pBackup, Pgno iPage, const u8 *aData){
sqlite3_backup *p; /* Iterator variable */
for(p=pBackup; p; p=p->pNext){
assert( sqlite3_mutex_held(p->pSrc->pBt->mutex) );
if( p->rc==SQLITE_OK && iPage<p->iNext ){
/* The backup process p has already copied page iPage. But now it
** has been modified by a transaction on the source pager. Copy
** the new data into the backup.
*/
int rc = backupOnePage(p, iPage, aData);
if( rc!=SQLITE_OK ){
p->rc = rc;
}
}
}
}
/*
** Restart the backup process. This is called when the pager layer
** detects that the database has been modified by an external database
** connection. In this case there is no way of knowing which of the
** pages that have been copied into the destination database are still
** valid and which are not, so the entire process needs to be restarted.
**
** It is assumed that the mutex associated with the BtShared object
** corresponding to the source database is held when this function is
** called.
*/
void sqlite3BackupRestart(sqlite3_backup *pBackup){
sqlite3_backup *p; /* Iterator variable */
for(p=pBackup; p; p=p->pNext){
assert( sqlite3_mutex_held(p->pSrc->pBt->mutex) );
p->iNext = 1;
}
}
#ifndef SQLITE_OMIT_VACUUM
/*
** Copy the complete content of pBtFrom into pBtTo. A transaction
** must be active for both files.
**
** The size of file pTo may be reduced by this operation. If anything
** goes wrong, the transaction on pTo is rolled back. If successful, the
** transaction is committed before returning.
*/
int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){
int rc;
sqlite3_backup b;
sqlite3BtreeEnter(pTo);
sqlite3BtreeEnter(pFrom);
/* Set up an sqlite3_backup object. sqlite3_backup.pDestDb must be set
** to 0. This is used by the implementations of sqlite3_backup_step()
** and sqlite3_backup_finish() to detect that they are being called
** from this function, not directly by the user.
*/
memset(&b, 0, sizeof(b));
b.pSrcDb = pFrom->db;
b.pSrc = pFrom;
b.pDest = pTo;
b.iNext = 1;
/* 0x7FFFFFFF is the hard limit for the number of pages in a database
** file. By passing this as the number of pages to copy to
** sqlite3_backup_step(), we can guarantee that the copy finishes
** within a single call (unless an error occurs). The assert() statement
** checks this assumption - (p->rc) should be set to either SQLITE_DONE
** or an error code.
*/
sqlite3_backup_step(&b, 0x7FFFFFFF);
assert( b.rc!=SQLITE_OK );
rc = sqlite3_backup_finish(&b);
if( rc==SQLITE_OK ){
pTo->pBt->pageSizeFixed = 0;
}
sqlite3BtreeLeave(pFrom);
sqlite3BtreeLeave(pTo);
return rc;
}
#endif /* SQLITE_OMIT_VACUUM */

View File

@ -9,7 +9,7 @@
** May you share freely, never taking more than you give.
**
*************************************************************************
** $Id: btree.c,v 1.563 2009/01/31 14:54:07 danielk1977 Exp $
** $Id: btree.c,v 1.564 2009/02/03 16:51:25 danielk1977 Exp $
**
** This file implements a external (disk-based) database using BTrees.
** See the header comment on "btreeInt.h" for additional information.
@ -7239,225 +7239,6 @@ const char *sqlite3BtreeGetJournalname(Btree *p){
return sqlite3PagerJournalname(p->pBt->pPager);
}
#ifndef SQLITE_OMIT_VACUUM
/*
** Copy the complete content of pBtFrom into pBtTo. A transaction
** must be active for both files.
**
** The size of file pTo may be reduced by this operation.
** If anything goes wrong, the transaction on pTo is rolled back.
**
** If successful, CommitPhaseOne() may be called on pTo before returning.
** The caller should finish committing the transaction on pTo by calling
** sqlite3BtreeCommit().
*/
static int btreeCopyFile(Btree *pTo, Btree *pFrom){
int rc = SQLITE_OK;
Pgno i;
Pgno nFromPage; /* Number of pages in pFrom */
Pgno nToPage; /* Number of pages in pTo */
Pgno nNewPage; /* Number of pages in pTo after the copy */
Pgno iSkip; /* Pending byte page in pTo */
int nToPageSize; /* Page size of pTo in bytes */
int nFromPageSize; /* Page size of pFrom in bytes */
BtShared *pBtTo = pTo->pBt;
BtShared *pBtFrom = pFrom->pBt;
pBtTo->db = pTo->db;
pBtFrom->db = pFrom->db;
nToPageSize = pBtTo->pageSize;
nFromPageSize = pBtFrom->pageSize;
assert( pTo->inTrans==TRANS_WRITE );
assert( pFrom->inTrans==TRANS_WRITE );
if( NEVER(pBtTo->pCursor) ){
return SQLITE_BUSY;
}
nToPage = pagerPagecount(pBtTo);
nFromPage = pagerPagecount(pBtFrom);
iSkip = PENDING_BYTE_PAGE(pBtTo);
/* Variable nNewPage is the number of pages required to store the
** contents of pFrom using the current page-size of pTo.
*/
nNewPage = (Pgno)
(((i64)nFromPage*(i64)nFromPageSize+(i64)nToPageSize-1)/(i64)nToPageSize);
for(i=1; rc==SQLITE_OK && (i<=nToPage || i<=nNewPage); i++){
/* Journal the original page.
**
** iSkip is the page number of the locking page (PENDING_BYTE_PAGE)
** in database *pTo (before the copy). This page is never written
** into the journal file. Unless i==iSkip or the page was not
** present in pTo before the copy operation, journal page i from pTo.
*/
if( i!=iSkip && i<=nToPage ){
DbPage *pDbPage = 0;
rc = sqlite3PagerGet(pBtTo->pPager, i, &pDbPage);
if( rc==SQLITE_OK ){
rc = sqlite3PagerWrite(pDbPage);
if( rc==SQLITE_OK && i>nFromPage ){
/* Yeah. It seems wierd to call DontWrite() right after Write(). But
** that is because the names of those procedures do not exactly
** represent what they do. Write() really means "put this page in the
** rollback journal and mark it as dirty so that it will be written
** to the database file later." DontWrite() undoes the second part of
** that and prevents the page from being written to the database. The
** page is still on the rollback journal, though. And that is the
** whole point of this block: to put pages on the rollback journal.
*/
sqlite3PagerDontWrite(pDbPage);
}
sqlite3PagerUnref(pDbPage);
}
}
/* Overwrite the data in page i of the target database */
if( rc==SQLITE_OK && i!=iSkip && i<=nNewPage ){
DbPage *pToPage = 0;
sqlite3_int64 iOff;
rc = sqlite3PagerGet(pBtTo->pPager, i, &pToPage);
if( rc==SQLITE_OK ){
rc = sqlite3PagerWrite(pToPage);
}
for(
iOff=(i-1)*nToPageSize;
rc==SQLITE_OK && iOff<i*nToPageSize;
iOff += nFromPageSize
){
DbPage *pFromPage = 0;
Pgno iFrom = (Pgno)(iOff/nFromPageSize)+1;
if( iFrom==PENDING_BYTE_PAGE(pBtFrom) ){
continue;
}
rc = sqlite3PagerGet(pBtFrom->pPager, iFrom, &pFromPage);
if( rc==SQLITE_OK ){
char *zTo = sqlite3PagerGetData(pToPage);
char *zFrom = sqlite3PagerGetData(pFromPage);
int nCopy;
if( nFromPageSize>=nToPageSize ){
zFrom += ((i-1)*nToPageSize - ((iFrom-1)*nFromPageSize));
nCopy = nToPageSize;
}else{
zTo += (((iFrom-1)*nFromPageSize) - (i-1)*nToPageSize);
nCopy = nFromPageSize;
}
memcpy(zTo, zFrom, nCopy);
sqlite3PagerUnref(pFromPage);
}
}
if( pToPage ){
MemPage *p = (MemPage *)sqlite3PagerGetExtra(pToPage);
p->isInit = 0;
sqlite3PagerUnref(pToPage);
}
}
}
/* If things have worked so far, the database file may need to be
** truncated. The complex part is that it may need to be truncated to
** a size that is not an integer multiple of nToPageSize - the current
** page size used by the pager associated with B-Tree pTo.
**
** For example, say the page-size of pTo is 2048 bytes and the original
** number of pages is 5 (10 KB file). If pFrom has a page size of 1024
** bytes and 9 pages, then the file needs to be truncated to 9KB.
*/
if( rc==SQLITE_OK ){
sqlite3_file *pFile = sqlite3PagerFile(pBtTo->pPager);
i64 iSize = (i64)nFromPageSize * (i64)nFromPage;
i64 iNow = (i64)((nToPage>nNewPage)?nToPage:nNewPage) * (i64)nToPageSize;
i64 iPending = ((i64)PENDING_BYTE_PAGE(pBtTo)-1) *(i64)nToPageSize;
assert( iSize<=iNow );
/* Commit phase one syncs the journal file associated with pTo
** containing the original data. It does not sync the database file
** itself. After doing this it is safe to use OsTruncate() and other
** file APIs on the database file directly.
*/
pBtTo->db = pTo->db;
if( nFromPageSize==nToPageSize ){
sqlite3PagerTruncateImage(pBtTo->pPager, nFromPage);
iNow = iSize;
}
rc = sqlite3PagerCommitPhaseOne(pBtTo->pPager, 0, 1);
if( iSize<iNow && rc==SQLITE_OK ){
rc = sqlite3OsTruncate(pFile, iSize);
}
/* The loop that copied data from database pFrom to pTo did not
** populate the locking page of database pTo. If the page-size of
** pFrom is smaller than that of pTo, this means some data will
** not have been copied.
**
** This block copies the missing data from database pFrom to pTo
** using file APIs. This is safe because at this point we know that
** all of the original data from pTo has been synced into the
** journal file. At this point it would be safe to do anything at
** all to the database file except truncate it to zero bytes.
*/
if( rc==SQLITE_OK && nFromPageSize<nToPageSize && iSize>iPending){
i64 iOff;
for(
iOff=iPending;
rc==SQLITE_OK && iOff<(iPending+nToPageSize);
iOff += nFromPageSize
){
DbPage *pFromPage = 0;
Pgno iFrom = (Pgno)(iOff/nFromPageSize)+1;
if( iFrom==PENDING_BYTE_PAGE(pBtFrom) || iFrom>nFromPage ){
continue;
}
rc = sqlite3PagerGet(pBtFrom->pPager, iFrom, &pFromPage);
if( rc==SQLITE_OK ){
char *zFrom = sqlite3PagerGetData(pFromPage);
rc = sqlite3OsWrite(pFile, zFrom, nFromPageSize, iOff);
sqlite3PagerUnref(pFromPage);
}
}
}
}
/* Sync the database file */
if( rc==SQLITE_OK ){
rc = sqlite3PagerSync(pBtTo->pPager);
}
if( rc==SQLITE_OK ){
pBtTo->pageSizeFixed = 0;
}else{
sqlite3BtreeRollback(pTo);
}
return rc;
}
int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){
int rc;
sqlite3BtreeEnter(pTo);
sqlite3BtreeEnter(pFrom);
rc = btreeCopyFile(pTo, pFrom);
sqlite3BtreeLeave(pFrom);
sqlite3BtreeLeave(pTo);
return rc;
}
#endif /* SQLITE_OMIT_VACUUM */
/*
** Return non-zero if a transaction is active.
*/
@ -7483,6 +7264,12 @@ int sqlite3BtreeIsInReadTrans(Btree *p){
return p->inTrans!=TRANS_NONE;
}
int sqlite3BtreeIsInBackup(Btree *p){
assert( p );
assert( sqlite3_mutex_held(p->db->mutex) );
return p->nBackup!=0;
}
/*
** This function returns a pointer to a blob of memory associated with
** a single shared-btree. The memory is used by client code for its own

View File

@ -13,7 +13,7 @@
** subsystem. See comments in the source code for a detailed description
** of what each interface routine does.
**
** @(#) $Id: btree.h,v 1.107 2009/01/24 11:30:43 drh Exp $
** @(#) $Id: btree.h,v 1.108 2009/02/03 16:51:25 danielk1977 Exp $
*/
#ifndef _BTREE_H_
#define _BTREE_H_
@ -98,6 +98,7 @@ int sqlite3BtreeCreateTable(Btree*, int*, int flags);
int sqlite3BtreeIsInTrans(Btree*);
int sqlite3BtreeIsInStmt(Btree*);
int sqlite3BtreeIsInReadTrans(Btree*);
int sqlite3BtreeIsInBackup(Btree*);
void *sqlite3BtreeSchema(Btree *, int, void(*)(void *));
int sqlite3BtreeSchemaLocked(Btree *);
int sqlite3BtreeLockTable(Btree *, int, u8);

View File

@ -9,7 +9,7 @@
** May you share freely, never taking more than you give.
**
*************************************************************************
** $Id: btreeInt.h,v 1.41 2009/01/20 17:06:27 danielk1977 Exp $
** $Id: btreeInt.h,v 1.42 2009/02/03 16:51:25 danielk1977 Exp $
**
** This file implements a external (disk-based) database using BTrees.
** For a detailed discussion of BTrees, refer to
@ -324,6 +324,7 @@ struct Btree {
u8 sharable; /* True if we can share pBt with another db */
u8 locked; /* True if db currently has pBt locked */
int wantToLock; /* Number of nested calls to sqlite3BtreeEnter() */
int nBackup; /* Number of backup operations reading this btree */
Btree *pNext; /* List of other sharable Btrees from the same db */
Btree *pPrev; /* Back pointer of the same list */
};

View File

@ -22,7 +22,7 @@
** COMMIT
** ROLLBACK
**
** $Id: build.c,v 1.514 2009/02/03 15:50:34 drh Exp $
** $Id: build.c,v 1.515 2009/02/03 16:51:25 danielk1977 Exp $
*/
#include "sqliteInt.h"
@ -621,31 +621,41 @@ void sqlite3OpenMasterTable(Parse *p, int iDb){
}
/*
** The token *pName contains the name of a database (either "main" or
** "temp" or the name of an attached db). This routine returns the
** index of the named database in db->aDb[], or -1 if the named db
** does not exist.
** Parameter zName points to a nul-terminated buffer containing the name
** of a database ("main", "temp" or the name of an attached db). This
** function returns the index of the named database in db->aDb[], or
** -1 if the named db cannot be found.
*/
int sqlite3FindDb(sqlite3 *db, Token *pName){
int i = -1; /* Database number */
int n; /* Number of characters in the name */
Db *pDb; /* A database whose name space is being searched */
char *zName; /* Name we are searching for */
zName = sqlite3NameFromToken(db, pName);
int sqlite3FindDbName(sqlite3 *db, const char *zName){
int i = -1; /* Database number */
if( zName ){
n = sqlite3Strlen30(zName);
Db *pDb;
int n = sqlite3Strlen30(zName);
for(i=(db->nDb-1), pDb=&db->aDb[i]; i>=0; i--, pDb--){
if( (!OMIT_TEMPDB || i!=1 ) && n==sqlite3Strlen30(pDb->zName) &&
0==sqlite3StrICmp(pDb->zName, zName) ){
break;
}
}
sqlite3DbFree(db, zName);
}
return i;
}
/*
** The token *pName contains the name of a database (either "main" or
** "temp" or the name of an attached db). This routine returns the
** index of the named database in db->aDb[], or -1 if the named db
** does not exist.
*/
int sqlite3FindDb(sqlite3 *db, Token *pName){
int i; /* Database number */
char *zName; /* Name we are searching for */
zName = sqlite3NameFromToken(db, pName);
i = sqlite3FindDbName(db, zName);
sqlite3DbFree(db, zName);
return i;
}
/* The table or view or trigger name is passed to this routine via tokens
** pName1 and pName2. If the table name was fully qualified, for example:
**

View File

@ -14,7 +14,7 @@
** other files are for internal use by SQLite and should not be
** accessed by users of the library.
**
** $Id: main.c,v 1.524 2009/02/03 15:50:34 drh Exp $
** $Id: main.c,v 1.525 2009/02/03 16:51:25 danielk1977 Exp $
*/
#include "sqliteInt.h"
@ -603,6 +603,16 @@ int sqlite3_close(sqlite3 *db){
}
assert( sqlite3SafetyCheckSickOrOk(db) );
for(j=0; j<db->nDb; j++){
Btree *pBt = db->aDb[j].pBt;
if( pBt && sqlite3BtreeIsInBackup(pBt) ){
sqlite3Error(db, SQLITE_BUSY,
"Unable to close due to unfinished backup operation");
sqlite3_mutex_leave(db->mutex);
return SQLITE_BUSY;
}
}
/* Free any outstanding Savepoint structures. */
sqlite3CloseSavepoints(db);

View File

@ -18,7 +18,7 @@
** file simultaneously, or one process from reading the database while
** another is writing.
**
** @(#) $Id: pager.c,v 1.561 2009/01/31 14:54:07 danielk1977 Exp $
** @(#) $Id: pager.c,v 1.562 2009/02/03 16:51:25 danielk1977 Exp $
*/
#ifndef SQLITE_OMIT_DISKIO
#include "sqliteInt.h"
@ -306,6 +306,7 @@ struct Pager {
char *pTmpSpace; /* Pager.pageSize bytes of space for tmp use */
i64 journalSizeLimit; /* Size limit for persistent journal files */
PCache *pPCache; /* Pointer to page cache object */
sqlite3_backup *pBackup; /* Pointer to list of ongoing backup processes */
};
/*
@ -1039,9 +1040,12 @@ static PgHdr *pager_lookup(Pager *pPager, Pgno pgno){
/*
** Unless the pager is in error-state, discard all in-memory pages. If
** the pager is in error-state, then this call is a no-op.
**
** TODO: Why can we not reset the pager while in error state?
*/
static void pager_reset(Pager *pPager){
if( SQLITE_OK==pPager->errCode ){
sqlite3BackupRestart(pPager->pBackup);
sqlite3PcacheClear(pPager->pPCache);
}
}
@ -1513,6 +1517,7 @@ static int pager_playback_one_page(
if( pgno>pPager->dbFileSize ){
pPager->dbFileSize = pgno;
}
sqlite3BackupUpdate(pPager->pBackup, pgno, aData);
}else if( !isMainJrnl && pPg==0 ){
/* If this is a rollback of a savepoint and data was not written to
** the database and the page is not in-memory, there is a potential
@ -2875,6 +2880,9 @@ static int pager_write_pagelist(PgHdr *pList){
pPager->dbFileSize = pgno;
}
/* Update any backup objects copying the contents of this pager. */
sqlite3BackupUpdate(pPager->pBackup, pgno, (u8 *)pData);
PAGERTRACE(("STORE %d page %d hash(%08x)\n",
PAGERID(pPager), pgno, pager_pagehash(pList)));
IOTRACE(("PGOUT %p %d\n", pPager, pgno));
@ -3549,7 +3557,7 @@ static int pagerSharedLock(Pager *pPager){
** playing back the hot-journal so that we don't end up with
** an inconsistent cache.
*/
sqlite3PcacheClear(pPager->pPCache);
pager_reset(pPager);
rc = pager_playback(pPager, 1);
if( rc!=SQLITE_OK ){
rc = pager_error(pPager, rc);
@ -4436,7 +4444,9 @@ int sqlite3PagerCommitPhaseOne(
/* If this is an in-memory db, or no pages have been written to, or this
** function has already been called, it is a no-op.
*/
if( pPager->state!=PAGER_SYNCED && !MEMDB && pPager->dbModified ){
if( MEMDB && pPager->dbModified ){
sqlite3BackupRestart(pPager->pBackup);
}else if( pPager->state!=PAGER_SYNCED && pPager->dbModified ){
/* The following block updates the change-counter. Exactly how it
** does this depends on whether or not the atomic-update optimization
@ -4747,10 +4757,14 @@ int *sqlite3PagerStats(Pager *pPager){
a[10] = pPager->nWrite;
return a;
}
#endif
/*
** Return true if this is an in-memory pager.
*/
int sqlite3PagerIsMemdb(Pager *pPager){
return MEMDB;
}
#endif
/*
** Check that there are at least nSavepoint savepoints open. If there are
@ -5152,4 +5166,14 @@ i64 sqlite3PagerJournalSizeLimit(Pager *pPager, i64 iLimit){
return pPager->journalSizeLimit;
}
/*
** Return a pointer to the pPager->pBackup variable. The backup module
** in backup.c maintains the content of this variable. This module
** uses it opaquely as an argument to sqlite3BackupRestart() and
** sqlite3BackupUpdate() only.
*/
sqlite3_backup **sqlite3PagerBackupPtr(Pager *pPager){
return &pPager->pBackup;
}
#endif /* SQLITE_OMIT_DISKIO */

View File

@ -13,7 +13,7 @@
** subsystem. The page cache subsystem reads and writes a file a page
** at a time and provides a journal for rollback.
**
** @(#) $Id: pager.h,v 1.99 2009/01/31 14:54:07 danielk1977 Exp $
** @(#) $Id: pager.h,v 1.100 2009/02/03 16:51:25 danielk1977 Exp $
*/
#ifndef _PAGER_H_
@ -100,6 +100,7 @@ void sqlite3PagerSetSafetyLevel(Pager*,int,int);
int sqlite3PagerLockingMode(Pager *, int);
int sqlite3PagerJournalMode(Pager *, int);
i64 sqlite3PagerJournalSizeLimit(Pager *, i64);
sqlite3_backup **sqlite3PagerBackupPtr(Pager*);
/* Functions used to obtain and release page references. */
int sqlite3PagerAcquire(Pager *pPager, Pgno pgno, DbPage **ppPage, int clrFlag);
@ -135,6 +136,7 @@ sqlite3_file *sqlite3PagerFile(Pager*);
const char *sqlite3PagerJournalname(Pager*);
int sqlite3PagerNosync(Pager*);
void *sqlite3PagerTempSpace(Pager*);
int sqlite3PagerIsMemdb(Pager*);
/* Functions used to truncate the database file. */
void sqlite3PagerTruncateImage(Pager*,Pgno);
@ -152,7 +154,6 @@ void sqlite3PagerTruncateImage(Pager*,Pgno);
#ifdef SQLITE_TEST
int *sqlite3PagerStats(Pager*);
void sqlite3PagerRefdump(Pager*);
int sqlite3PagerIsMemdb(Pager*);
void disable_simulated_io_errors(void);
void enable_simulated_io_errors(void);
#else

View File

@ -30,7 +30,7 @@
** the version number) and changes its name to "sqlite3.h" as
** part of the build process.
**
** @(#) $Id: sqlite.h.in,v 1.422 2009/01/23 16:45:01 danielk1977 Exp $
** @(#) $Id: sqlite.h.in,v 1.423 2009/02/03 16:51:25 danielk1977 Exp $
*/
#ifndef _SQLITE3_H_
#define _SQLITE3_H_
@ -6722,6 +6722,178 @@ struct sqlite3_pcache_methods {
void (*xDestroy)(sqlite3_pcache*);
};
/*
** CAPI3REF: Online Backup API.
** EXPERIMENTAL
**
** This API is used to overwrite the contents of one database with that
** of another. It is useful either for creating backups of databases or
** for copying in-memory databases to or from persistent files.
**
** Exclusive access is required to the destination database for the
** duration of the operation. However the source database is only
** read-locked while it is actually being read, it is not locked
** continuously for the entire operation. Thus, the backup may be
** performed on a live database without preventing other users from
** writing to the database for an extended period of time.
**
** To perform a backup operation:
** <ol>
** <li>[sqlite3_backup_init()] is called once to initialize the backup,
** <li>[sqlite3_backup_step()] is called one or more times to transfer
** the data between the two databases, and finally
** <li>[sqlite3_backup_finish()] is called to release all resources
** associated with the backup operation.
** </ol>
** There should be exactly one call to sqlite3_backup_finish() for each
** successful call to sqlite3_backup_init().
**
** <b>sqlite3_backup_init()</b>
**
** The first two arguments passed to [sqlite3_backup_init()] are the database
** handle associated with the destination database and the database name
** used to attach the destination database to the handle. The database name
** is "main" for the main database, "temp" for the temporary database, or
** the name specified as part of the ATTACH statement if the destination is
** an attached database. The third and fourth arguments passed to
** sqlite3_backup_init() identify the database handle and database name used
** to access the source database. The values passed for the source and
** destination database handle parameters must not be the same.
**
** If an error occurs within sqlite3_backup_init(), then NULL is returned
** and an error code and error message written into the database handle
** passed as the first argument. They may be retrieved using the
** sqlite3_errcode(), sqlite3_errmsg() and sqlite3_errmsg16() functions.
** Otherwise, if successful, an opaque handle of type sqlite3_backup* is
** returned. This handle may be used with the sqlite3_backup_step() and
** sqlite3_backup_finish() functions to perform the specified backup
** operation.
**
** <b>sqlite3_backup_step()</b>
**
** Function [sqlite3_backup_step()] is used to copy up to nPage pages between
** the source and destination databases, where nPage is the value of the
** second parameter passed to sqlite3_backup_step(). If nPage pages are
** succesfully copied, but there are still more pages to copy before the
** backup is complete, it returns SQLITE_OK. If no error occured and there
** are no more pages to copy, then SQLITE_DONE is returned. If an error
** occurs, then an SQLite error code is returned. As well as SQLITE_OK and
** SQLITE_DONE, a call to sqlite3_backup_step() may return SQLITE_READONLY,
** SQLITE_NOMEM, SQLITE_BUSY, SQLITE_LOCKED or an SQLITE_IOERR_XXX error code.
**
** As well as the case where the destination database file was opened for
** read-only access, sqlite3_backup_step() may return SQLITE_READONLY if
** the destination is an in-memory database with a different page size
** from the source database.
**
** If sqlite3_backup_step() cannot obtain a required file-system lock, then
** the busy-handler function is invoked (if one is specified). If the
** busy-handler returns non-zero before the lock is available, then
** SQLITE_BUSY is returned to the caller. In this case the call to
** sqlite3_backup_step() can be retried later. If the source database handle
** is being used to write to the source database when sqlite3_backup_step()
** is called, then SQLITE_LOCKED is returned immediately. Again, in this
** case the call to sqlite3_backup_step() can be retried later on. If
** SQLITE_IOERR_XXX, SQLITE_NOMEM or SQLITE_READONLY is returned, then
** there is no point in retrying the call to sqlite3_backup_step(). These
** errors are considered fatal. At this point the application must accept
** that the backup operation has failed and pass the backup operation handle
** to the sqlite3_backup_finish() to release associated resources.
**
** Following the first call to sqlite3_backup_step(), an exclusive lock is
** obtained on the destination file. It is not released until either
** sqlite3_backup_finish() is called or the backup operation is complete
** and sqlite3_backup_step() returns SQLITE_DONE. Additionally, each time
** a call to sqlite3_backup_step() is made a shared lock is obtained on
** the source database file. This lock is released before the
** sqlite3_backup_step() call returns. Because the source database is not
** locked between calls to sqlite3_backup_step(), it may be modified mid-way
** through the backup procedure. If the source database is modified by an
** external process or via a database connection other than the one being
** used by the backup operation, then the backup will be transparently
** restarted by the next call to sqlite3_backup_step(). If the source
** database is modified by the using the same database connection as is used
** by the backup operation, then the backup database is transparently
** updated at the same time.
**
** <b>sqlite3_backup_finish()</b>
**
** Once sqlite3_backup_step() has returned SQLITE_DONE, or when the
** application wishes to abandon the backup operation, the sqlite3_backup*
** handle should be passed to sqlite3_backup_finish(). This releases all
** resources associated with the backup operation. If sqlite3_backup_step()
** has not yet returned SQLITE_DONE, then any active write-transaction on the
** destination database is rolled back. The sqlite3_backup* handle is invalid
** and may not be used following a call to sqlite3_backup_finish().
**
** The value returned by sqlite3_backup_finish is SQLITE_OK if no error
** occured, regardless or whether or not sqlite3_backup_step() was called
** a sufficient number of times to complete the backup operation. Or, if
** an out-of-memory condition or IO error occured during a call to
** sqlite3_backup_step() then SQLITE_NOMEM or an SQLITE_IOERR_XXX error code
** is returned. In this case the error code and an error message are
** written to the destination database handle.
**
** A return of SQLITE_BUSY or SQLITE_LOCKED from sqlite3_backup_step() is
** not considered an error and does not affect the return value of
** sqlite3_backup_finish().
**
** <b>sqlite3_backup_remaining(), sqlite3_backup_pagecount()</b>
**
** Each call to sqlite3_backup_step() sets two values stored internally
** by an sqlite3_backup* handle. The number of pages still to be backed
** up, which may be queried by sqlite3_backup_remaining(), and the total
** number of pages in the source database file, which may be queried by
** sqlite3_backup_pagecount().
**
** The values returned by these functions are only updated by
** sqlite3_backup_step(). If the source database is modified during a backup
** operation, then the values are not updated to account for any extra
** pages that need to be updated or the size of the source database file
** changing.
**
** <b>Concurrent Usage of Database Handles</b>
**
** The source database handle may be used by the application for other
** purposes while a backup operation is underway or being initialized.
** If SQLite is compiled and configured to support threadsafe database
** connections, then the source database connection may be used concurrently
** from within other threads.
**
** However, the application must guarantee that the destination database
** connection handle is not passed to any other API (by any thread) after
** sqlite3_backup_init() is called and before the corresponding call to
** sqlite3_backup_finish(). Unfortunately SQLite does not currently check
** for this, if the application does use the destination database handle
** for some other purpose during a backup operation, things may appear to
** work correctly but in fact be subtly malfunctioning.
**
** Furthermore, if running in shared cache mode, the application must
** guarantee that the shared cache used by the destination database
** is not accessed while the backup is running. In practice this means
** that the application must guarantee that the file-system file being
** backed up to is not accessed by any connection within the process,
** not just the specific connection that was passed to sqlite3_backup_init().
**
** The sqlite3_backup handle itself is partially threadsafe. Multiple
** threads may safely make multiple concurrent calls to sqlite3_backup_step().
** However, the sqlite3_backup_remaining() and sqlite3_backup_pagecount()
** APIs are not strictly speaking threadsafe. If they are invoked at the
** same time as another thread is invoking sqlite3_backup_step() it is
** possible that they return invalid values.
*/
typedef struct sqlite3_backup sqlite3_backup;
sqlite3_backup *sqlite3_backup_init(
sqlite3 *pDest, /* Destination database handle */
const char *zDestName, /* Destination database name */
sqlite3 *pSource, /* Source database handle */
const char *zSourceName /* Source database name */
);
int sqlite3_backup_step(sqlite3_backup *p, int nPage);
int sqlite3_backup_finish(sqlite3_backup *p);
int sqlite3_backup_remaining(sqlite3_backup *p);
int sqlite3_backup_pagecount(sqlite3_backup *p);
/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.

View File

@ -11,7 +11,7 @@
*************************************************************************
** Internal interface definitions for SQLite.
**
** @(#) $Id: sqliteInt.h,v 1.830 2009/01/24 11:30:43 drh Exp $
** @(#) $Id: sqliteInt.h,v 1.831 2009/02/03 16:51:25 danielk1977 Exp $
*/
#ifndef _SQLITEINT_H_
#define _SQLITEINT_H_
@ -2552,6 +2552,7 @@ char sqlite3AffinityType(const Token*);
void sqlite3Analyze(Parse*, Token*, Token*);
int sqlite3InvokeBusyHandler(BusyHandler*);
int sqlite3FindDb(sqlite3*, Token*);
int sqlite3FindDbName(sqlite3 *, const char *);
int sqlite3AnalysisLoad(sqlite3*,int iDB);
void sqlite3DefaultRowEst(Index*);
void sqlite3RegisterLikeFunctions(sqlite3*, int);
@ -2573,6 +2574,9 @@ char *sqlite3StrAccumFinish(StrAccum*);
void sqlite3StrAccumReset(StrAccum*);
void sqlite3SelectDestInit(SelectDest*,int,int);
void sqlite3BackupRestart(sqlite3_backup *);
void sqlite3BackupUpdate(sqlite3_backup *, Pgno, const u8 *);
/*
** The interface to the LEMON-generated parser
*/

View File

@ -12,7 +12,7 @@
** A TCL Interface to SQLite. Append this file to sqlite3.c and
** compile the whole thing to build a TCL-enabled version of SQLite.
**
** $Id: tclsqlite.c,v 1.234 2009/01/14 23:38:03 drh Exp $
** $Id: tclsqlite.c,v 1.235 2009/02/03 16:51:25 danielk1977 Exp $
*/
#include "tcl.h"
#include <errno.h>
@ -2663,6 +2663,7 @@ int TCLSH_MAIN(int argc, char **argv){
extern int SqlitetestThread_Init(Tcl_Interp*);
extern int SqlitetestOnefile_Init();
extern int SqlitetestOsinst_Init(Tcl_Interp*);
extern int Sqlitetestbackup_Init(Tcl_Interp*);
Md5_Init(interp);
Sqliteconfig_Init(interp);
@ -2686,6 +2687,7 @@ int TCLSH_MAIN(int argc, char **argv){
SqlitetestThread_Init(interp);
SqlitetestOnefile_Init(interp);
SqlitetestOsinst_Init(interp);
Sqlitetestbackup_Init(interp);
#ifdef SQLITE_SSE
Sqlitetestsse_Init(interp);

View File

@ -13,7 +13,7 @@
** is not included in the SQLite library. It is used for automated
** testing of the SQLite library.
**
** $Id: test1.c,v 1.346 2009/02/03 16:25:48 drh Exp $
** $Id: test1.c,v 1.347 2009/02/03 16:51:25 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include "tcl.h"
@ -3217,7 +3217,7 @@ static int test_errcode(
}
/*
** Usage: test_errmsg DB
** Usage: sqlite3_errmsg DB
**
** Returns the UTF-8 representation of the error message string for the
** most recent sqlite3_* API call.

148
src/test_backup.c Normal file
View File

@ -0,0 +1,148 @@
/*
** 2009 January 28
**
** 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.
**
*************************************************************************
**
** $Id: test_backup.c,v 1.1 2009/02/03 16:51:25 danielk1977 Exp $
*/
#include "tcl.h"
#include <sqlite3.h>
#include <assert.h>
/* These functions are implemented in test1.c. */
int getDbPointer(Tcl_Interp *, const char *, sqlite3 **);
const char *sqlite3TestErrorName(int);
static int backupTestCmd(
ClientData clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const*objv
){
enum BackupSubCommandEnum {
BACKUP_STEP, BACKUP_FINISH, BACKUP_REMAINING, BACKUP_PAGECOUNT
};
struct BackupSubCommand {
const char *zCmd;
enum BackupSubCommandEnum eCmd;
int nArg;
const char *zArg;
} aSub[] = {
{"step", BACKUP_STEP , 1, "npage" },
{"finish", BACKUP_FINISH , 0, "" },
{"remaining", BACKUP_REMAINING , 0, "" },
{"pagecount", BACKUP_PAGECOUNT , 0, "" },
{0, 0, 0, 0}
};
sqlite3_backup *p = (sqlite3_backup *)clientData;
int iCmd;
int rc;
rc = Tcl_GetIndexFromObjStruct(
interp, objv[1], aSub, sizeof(aSub[0]), "option", 0, &iCmd
);
if( rc!=TCL_OK ){
return rc;
}
if( objc!=(2 + aSub[iCmd].nArg) ){
Tcl_WrongNumArgs(interp, 2, objv, aSub[iCmd].zArg);
return TCL_ERROR;
}
switch( aSub[iCmd].eCmd ){
case BACKUP_FINISH: {
Tcl_CmdInfo cmdInfo;
Tcl_Command cmd = Tcl_GetCommandFromObj(interp, objv[0]);
Tcl_GetCommandInfoFromToken(cmd, &cmdInfo);
cmdInfo.deleteProc = 0;
Tcl_SetCommandInfoFromToken(cmd, &cmdInfo);
Tcl_DeleteCommandFromToken(interp, cmd);
rc = sqlite3_backup_finish(p);
Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
break;
}
case BACKUP_STEP: {
int nPage;
if( TCL_OK!=Tcl_GetIntFromObj(interp, objv[2], &nPage) ){
return TCL_ERROR;
}
rc = sqlite3_backup_step(p, nPage);
Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
break;
}
case BACKUP_REMAINING:
Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_backup_remaining(p)));
break;
case BACKUP_PAGECOUNT:
Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_backup_pagecount(p)));
break;
}
return TCL_OK;
}
static void backupTestFinish(ClientData clientData){
sqlite3_backup *pBackup = (sqlite3_backup *)clientData;
sqlite3_backup_finish(pBackup);
}
/*
** sqlite3_backup CMDNAME DESTHANDLE DESTNAME SRCHANDLE SRCNAME
**
*/
static int backupTestInit(
ClientData clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const*objv
){
sqlite3_backup *pBackup;
sqlite3 *pDestDb;
sqlite3 *pSrcDb;
const char *zDestName;
const char *zSrcName;
const char *zCmd;
if( objc!=6 ){
Tcl_WrongNumArgs(
interp, 1, objv, "CMDNAME DESTHANDLE DESTNAME SRCHANDLE SRCNAME"
);
return TCL_ERROR;
}
zCmd = Tcl_GetString(objv[1]);
getDbPointer(interp, Tcl_GetString(objv[2]), &pDestDb);
zDestName = Tcl_GetString(objv[3]);
getDbPointer(interp, Tcl_GetString(objv[4]), &pSrcDb);
zSrcName = Tcl_GetString(objv[5]);
pBackup = sqlite3_backup_init(pDestDb, zDestName, pSrcDb, zSrcName);
if( !pBackup ){
Tcl_AppendResult(interp, "sqlite3_backup_init() failed", 0);
return TCL_ERROR;
}
Tcl_CreateObjCommand(interp, zCmd, backupTestCmd, pBackup, backupTestFinish);
Tcl_SetObjResult(interp, objv[1]);
return TCL_OK;
}
int Sqlitetestbackup_Init(Tcl_Interp *interp){
Tcl_CreateObjCommand(interp, "sqlite3_backup", backupTestInit, 0, 0);
return TCL_OK;
}

View File

@ -14,7 +14,7 @@
** Most of the code in this file may be omitted by defining the
** SQLITE_OMIT_VACUUM macro.
**
** $Id: vacuum.c,v 1.85 2009/01/22 23:04:46 drh Exp $
** $Id: vacuum.c,v 1.86 2009/02/03 16:51:25 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include "vdbeInt.h"
@ -265,7 +265,6 @@ int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){
#ifndef SQLITE_OMIT_AUTOVACUUM
sqlite3BtreeSetAutoVacuum(pMain, sqlite3BtreeGetAutoVacuum(pTemp));
#endif
rc = sqlite3BtreeCommit(pMain);
}
if( rc==SQLITE_OK ){

View File

@ -14,7 +14,7 @@
** to version 2.8.7, all this code was combined into the vdbe.c source file.
** But that file was getting too big so this subroutines were split out.
**
** $Id: vdbeaux.c,v 1.434 2009/01/20 17:06:27 danielk1977 Exp $
** $Id: vdbeaux.c,v 1.435 2009/02/03 16:51:25 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include "vdbeInt.h"
@ -1298,7 +1298,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
if( 0==sqlite3Strlen30(sqlite3BtreeGetFilename(db->aDb[0].pBt))
|| nTrans<=1
){
for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
Btree *pBt = db->aDb[i].pBt;
if( pBt ){
rc = sqlite3BtreeCommitPhaseOne(pBt, 0);

767
test/backup.test Normal file
View File

@ -0,0 +1,767 @@
# 2008 January 30
#
# 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.
#
#***********************************************************************
# This file implements regression tests for SQLite library. The
# focus of this file is testing the sqlite3_backup_XXX API.
#
# $Id: backup.test,v 1.1 2009/02/03 16:51:25 danielk1977 Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
#---------------------------------------------------------------------
# Test organization:
#
# backup-1.*: Warm-body tests.
#
# backup-2.*: Test backup under various conditions. To and from in-memory
# databases. To and from empty/populated databases. etc.
#
# backup-3.*: Verify that the locking-page (pending byte page) is handled.
#
# backup-4.*: Test various error conditions.
#
# backup-5.*: Test the source database being modified during a backup.
#
# backup-6.*: Test the backup_remaining() and backup_pagecount() APIs.
#
# backup-7.*: Test SQLITE_BUSY and SQLITE_LOCKED errors.
#
# backup-8.*: Test multiple simultaneous backup operations.
#
proc data_checksum {db file} { $db one "SELECT md5sum(a, b) FROM ${file}.t1" }
proc test_contents {name db1 file1 db2 file2} {
$db2 eval {select * from sqlite_master}
$db1 eval {select * from sqlite_master}
set checksum [data_checksum $db2 $file2]
uplevel [list do_test $name [list data_checksum $db1 $file1] $checksum]
}
do_test backup-1.1 {
execsql {
BEGIN;
CREATE TABLE t1(a, b);
CREATE INDEX i1 ON t1(a, b);
INSERT INTO t1 VALUES(1, randstr(1000,1000));
INSERT INTO t1 VALUES(2, randstr(1000,1000));
INSERT INTO t1 VALUES(3, randstr(1000,1000));
INSERT INTO t1 VALUES(4, randstr(1000,1000));
INSERT INTO t1 VALUES(5, randstr(1000,1000));
COMMIT;
}
} {}
# Sanity check to verify that the [test_contents] proc works.
#
test_contents backup-1.2 db main db main
# Check that it is possible to create and finish backup operations.
#
do_test backup-1.3.1 {
file delete test2.db
sqlite3 db2 test2.db
sqlite3_backup B db2 main db main
} {B}
do_test backup-1.3.2 {
B finish
} {SQLITE_OK}
do_test backup-1.3.3 {
info commands B
} {}
# Simplest backup operation. Backup test.db to test2.db. test2.db is
# initially empty. test.db uses the default page size.
#
do_test backup-1.4.1 {
sqlite3_backup B db2 main db main
} {B}
do_test backup-1.4.2 {
B step 200
} {SQLITE_DONE}
do_test backup-1.4.3 {
B finish
} {SQLITE_OK}
do_test backup-1.4.4 {
info commands B
} {}
test_contents backup-1.4.5 db2 main db main
db close
db2 close
#
# End of backup-1.* tests.
#---------------------------------------------------------------------
#---------------------------------------------------------------------
# The following tests, backup-2.*, are based on the following procedure:
#
# 1) Populate the source database.
# 2) Populate the destination database.
# 3) Run the backup to completion. (backup-2.*.1)
# 4) Integrity check the destination db. (backup-2.*.2)
# 5) Check that the contents of the destination db is the same as that
# of the source db. (backup-2.*.3)
#
# The test is run with all possible combinations of the following
# input parameters, except that if the destination is an in-memory
# database, the only page size tested is 1024 bytes (the same as the
# source page-size).
#
# * Source database is an in-memory database, OR
# * Source database is a file-backed database.
#
# * Target database is an in-memory database, OR
# * Target database is a file-backed database.
#
# * Destination database is a main file, OR
# * Destination database is an attached file, OR
# * Destination database is a temp database.
#
# * Target database is empty (zero bytes), OR
# * Target database is larger than the source, OR
# * Target database is smaller than the source.
#
# * Target database page-size is the same as the source, OR
# * Target database page-size is larger than the source, OR
# * Target database page-size is smaller than the source.
#
# * Each call to step copies a single page, OR
# * A single call to step copies the entire source database.
#
set iTest 1
foreach zSrcFile {test.db :memory:} {
foreach zDestFile {test2.db :memory:} {
foreach zOpenScript [list {
sqlite3 db $zSrcFile
sqlite3 db2 $zSrcFile
db2 eval "ATTACH '$zDestFile' AS bak"
set db_dest db2
set file_dest bak
} {
sqlite3 db $zSrcFile
sqlite3 db2 $zDestFile
set db_dest db2
set file_dest main
} {
sqlite3 db $zSrcFile
sqlite3 db2 $zDestFile
set db_dest db2
set file_dest temp
}] {
foreach rows_dest {0 3 10} {
foreach pgsz_dest {512 1024 2048} {
foreach nPagePerStep {1 200} {
# Open the databases.
catch { file delete test.db }
catch { file delete test2.db }
eval $zOpenScript
if {$zDestFile ne ":memory:" || $pgsz_dest == 1024 } {
if 0 {
puts -nonewline "Test $iTest: src=$zSrcFile dest=$zDestFile"
puts -nonewline " (as $db_dest.$file_dest)"
puts -nonewline " rows_dest=$rows_dest pgsz_dest=$pgsz_dest"
puts ""
}
# Set up the content of the source database.
execsql {
BEGIN;
CREATE TABLE t1(a, b);
CREATE INDEX i1 ON t1(a, b);
INSERT INTO t1 VALUES(1, randstr(1000,1000));
INSERT INTO t1 VALUES(2, randstr(1000,1000));
INSERT INTO t1 VALUES(3, randstr(1000,1000));
INSERT INTO t1 VALUES(4, randstr(1000,1000));
INSERT INTO t1 VALUES(5, randstr(1000,1000));
COMMIT;
}
# Set up the content of the target database.
execsql "PRAGMA ${file_dest}.page_size = ${pgsz_dest}" $db_dest
if {$rows_dest != 0} {
execsql "
BEGIN;
CREATE TABLE ${file_dest}.t1(a, b);
CREATE INDEX ${file_dest}.i1 ON t1(a, b);
" $db_dest
for {set ii 0} {$ii < $rows_dest} {incr ii} {
execsql "
INSERT INTO ${file_dest}.t1 VALUES(1, randstr(1000,1000))
" $db_dest
}
}
# Backup the source database.
do_test backup-2.$iTest.1 {
sqlite3_backup B $db_dest $file_dest db main
while {[B step $nPagePerStep]=="SQLITE_OK"} {}
B finish
} {SQLITE_OK}
# Run integrity check on the backup.
do_test backup-2.$iTest.2 {
execsql "PRAGMA ${file_dest}.integrity_check" $db_dest
} {ok}
test_contents backup-2.$iTest.3 db main $db_dest $file_dest
}
db close
catch {db2 close}
incr iTest
} } } } } }
#
# End of backup-2.* tests.
#---------------------------------------------------------------------
#---------------------------------------------------------------------
# These tests, backup-3.*, ensure that nothing goes wrong if either
# the source or destination database are large enough to include the
# the locking-page (the page that contains the range of bytes that
# the locks are applied to). These tests assume that the pending
# byte is at offset 0x00010000 (64KB offset), as set by tester.tcl,
# not at the 1GB offset as it usually is.
#
# The test procedure is as follows (same procedure as used for
# the backup-2.* tests):
#
# 1) Populate the source database.
# 2) Populate the destination database.
# 3) Run the backup to completion. (backup-3.*.1)
# 4) Integrity check the destination db. (backup-3.*.2)
# 5) Check that the contents of the destination db is the same as that
# of the source db. (backup-3.*.3)
#
# The test procedure is run with the following parameters varied:
#
# * Source database includes pending-byte page.
# * Source database does not include pending-byte page.
#
# * Target database includes pending-byte page.
# * Target database does not include pending-byte page.
#
# * Target database page-size is the same as the source, OR
# * Target database page-size is larger than the source, OR
# * Target database page-size is smaller than the source.
#
set iTest 1
foreach nSrcRow {10 100} {
foreach nDestRow {10 100} {
foreach nDestPgsz {512 1024 2048 4096} {
catch { file delete test.db }
catch { file delete test2.db }
sqlite3 db test.db
sqlite3 db2 test2.db
# Set up the content of the two databases.
#
foreach {db nRow} [list db $nSrcRow db2 $nDestRow] {
execsql {
BEGIN;
CREATE TABLE t1(a, b);
CREATE INDEX i1 ON t1(a, b);
} $db
for {set ii 0} {$ii < $nSrcRow} {incr ii} {
execsql { INSERT INTO t1 VALUES($ii, randstr(1000,1000)) } $db
}
execsql COMMIT $db
}
# Backup the source database.
do_test backup-3.$iTest.1 {
sqlite3_backup B db main db2 main
while {[B step 10]=="SQLITE_OK"} {}
B finish
} {SQLITE_OK}
# Run integrity check on the backup.
do_test backup-3.$iTest.2 {
execsql "PRAGMA integrity_check" db2
} {ok}
test_contents backup-3.$iTest.3 db main db2 main
db close
db2 close
incr iTest
}
}
}
#
# End of backup-3.* tests.
#---------------------------------------------------------------------
#---------------------------------------------------------------------
# The following tests, backup-4.*, test various error conditions:
#
# backup-4.1.*: Test invalid database names.
#
# backup-4.2.*: Test that the source database cannot be detached while
# a backup is in progress.
#
# backup-4.3.*: Test that the source database handle cannot be closed
# while a backup is in progress.
#
# backup-4.4.*: Test an attempt to specify the same handle for the
# source and destination databases.
#
# backup-4.5.*: Test that an in-memory destination with a different
# page-size to the source database is an error.
#
sqlite3 db test.db
sqlite3 db2 test2.db
do_test backup-4.1.1 {
catch { sqlite3_backup B db aux db2 main }
} {1}
do_test backup-4.1.2 {
sqlite3_errmsg db
} {unknown database aux}
do_test backup-4.1.3 {
catch { sqlite3_backup B db main db2 aux }
} {1}
do_test backup-4.1.4 {
sqlite3_errmsg db
} {unknown database aux}
do_test backup-4.2.1 {
execsql { ATTACH 'test3.db' AS aux1 }
execsql { ATTACH 'test4.db' AS aux2 } db2
sqlite3_backup B db aux1 db2 aux2
} {B}
do_test backup-4.2.2 {
catchsql { DETACH aux2 } db2
} {1 {database aux2 is locked}}
do_test backup-4.2.3 {
B step 50
} {SQLITE_DONE}
do_test backup-4.2.4 {
B finish
} {SQLITE_OK}
do_test backup-4.3.1 {
sqlite3_backup B db aux1 db2 aux2
} {B}
do_test backup-4.3.2 {
db2 cache flush
sqlite3_close db2
} {SQLITE_BUSY}
do_test backup-4.3.3 {
sqlite3_errmsg db2
} {Unable to close due to unfinished backup operation}
do_test backup-4.3.4 {
B step 50
} {SQLITE_DONE}
do_test backup-4.3.5 {
B finish
} {SQLITE_OK}
do_test backup-4.4.1 {
set rc [catch {sqlite3_backup B db main db aux1}]
list $rc [sqlite3_errcode db] [sqlite3_errmsg db]
} {1 SQLITE_ERROR {Source and destination handles must be distinct}}
db close
db2 close
do_test backup-4.5.1 {
catch { file delete -force test.db }
sqlite3 db test.db
sqlite3 db2 :memory:
execsql {
CREATE TABLE t1(a, b);
INSERT INTO t1 VALUES(1, 2);
}
execsql {
PRAGMA page_size = 4096;
CREATE TABLE t2(a, b);
INSERT INTO t2 VALUES(3, 4);
} db2
sqlite3_backup B db2 main db main
} {B}
do_test backup-4.5.2 {
B step 5000
} {SQLITE_READONLY}
do_test backup-4.5.3 {
B finish
} {SQLITE_READONLY}
db close
db2 close
#
# End of backup-5.* tests.
#---------------------------------------------------------------------
#---------------------------------------------------------------------
# The following tests, backup-5.*, test that the backup works properly
# when the source database is modified during the backup. Test cases
# are organized as follows:
#
# backup-5.x.1.*: Nothing special. Modify the database mid-backup.
#
# backup-5.x.2.*: Modify the database mid-backup so that one or more
# pages are written out due to cache stress. Then
# rollback the transaction.
#
# backup-5.x.3.*: Database is vacuumed.
#
# backup-5.x.4.*: Database is vacuumed and the page-size modified.
#
# backup-5.x.5.*: Database is shrunk via incr-vacuum.
#
# Each test is run three times, in the following configurations:
#
# 1) Backing up file-to-file. The writer writes via an external pager.
# 2) Backing up file-to-file. The writer writes via the same pager as
# is used by the backup operation.
# 3) Backing up memory-to-file.
#
set iTest 0
foreach {writer file} {db test.db db3 test.db db :memory:} {
incr iTest
catch { file delete bak.db }
sqlite3 db2 bak.db
catch { file delete $file }
sqlite3 db $file
sqlite3 db3 $file
do_test backup-5.$iTest.1.1 {
execsql {
BEGIN;
CREATE TABLE t1(a, b);
CREATE INDEX i1 ON t1(a, b);
INSERT INTO t1 VALUES(1, randstr(1000,1000));
INSERT INTO t1 VALUES(2, randstr(1000,1000));
INSERT INTO t1 VALUES(3, randstr(1000,1000));
INSERT INTO t1 VALUES(4, randstr(1000,1000));
INSERT INTO t1 VALUES(5, randstr(1000,1000));
COMMIT;
}
expr {[execsql {PRAGMA page_count}] > 10}
} {1}
do_test backup-5.$iTest.1.2 {
sqlite3_backup B db2 main db main
B step 5
} {SQLITE_OK}
do_test backup-5.$iTest.1.3 {
execsql { UPDATE t1 SET a = a + 1 } $writer
B step 50
} {SQLITE_DONE}
do_test backup-5.$iTest.1.4 {
B finish
} {SQLITE_OK}
integrity_check backup-5.$iTest.1.5 db2
test_contents backup-5.$iTest.1.6 db main db2 main
do_test backup-5.$iTest.2.1 {
execsql {
PRAGMA cache_size = 10;
BEGIN;
INSERT INTO t1 SELECT '', randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT '', randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT '', randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT '', randstr(1000,1000) FROM t1;
COMMIT;
}
} {}
do_test backup-5.$iTest.2.2 {
sqlite3_backup B db2 main db main
B step 50
} {SQLITE_OK}
do_test backup-5.$iTest.2.3 {
execsql {
BEGIN;
UPDATE t1 SET a = a + 1;
ROLLBACK;
} $writer
B step 5000
} {SQLITE_DONE}
do_test backup-5.$iTest.2.4 {
B finish
} {SQLITE_OK}
integrity_check backup-5.$iTest.2.5 db2
test_contents backup-5.$iTest.2.6 db main db2 main
do_test backup-5.$iTest.3.1 {
execsql { UPDATE t1 SET b = randstr(1000,1000) }
} {}
do_test backup-5.$iTest.3.2 {
sqlite3_backup B db2 main db main
B step 50
} {SQLITE_OK}
do_test backup-5.$iTest.3.3 {
execsql { VACUUM } $writer
B step 5000
} {SQLITE_DONE}
do_test backup-5.$iTest.3.4 {
B finish
} {SQLITE_OK}
integrity_check backup-5.$iTest.3.5 db2
test_contents backup-5.$iTest.3.6 db main db2 main
do_test backup-5.$iTest.4.1 {
execsql { UPDATE t1 SET b = randstr(1000,1000) }
} {}
do_test backup-5.$iTest.4.2 {
sqlite3_backup B db2 main db main
B step 50
} {SQLITE_OK}
do_test backup-5.$iTest.4.3 {
execsql {
PRAGMA page_size = 2048;
VACUUM;
} $writer
B step 5000
} {SQLITE_DONE}
do_test backup-5.$iTest.4.4 {
B finish
} {SQLITE_OK}
integrity_check backup-5.$iTest.4.5 db2
test_contents backup-5.$iTest.4.6 db main db2 main
catch { file delete bak.db }
sqlite3 db2 bak.db
catch { file delete $file }
sqlite3 db $file
sqlite3 db3 $file
do_test backup-5.$iTest.5.1 {
execsql {
PRAGMA auto_vacuum = incremental;
BEGIN;
CREATE TABLE t1(a, b);
CREATE INDEX i1 ON t1(a, b);
INSERT INTO t1 VALUES(1, randstr(1000,1000));
INSERT INTO t1 VALUES(2, randstr(1000,1000));
INSERT INTO t1 VALUES(3, randstr(1000,1000));
INSERT INTO t1 VALUES(4, randstr(1000,1000));
INSERT INTO t1 VALUES(5, randstr(1000,1000));
COMMIT;
}
} {}
do_test backup-5.$iTest.5.2 {
sqlite3_backup B db2 main db main
B step 8
} {SQLITE_OK}
do_test backup-5.$iTest.5.3 {
execsql {
DELETE FROM t1;
PRAGMA incremental_vacuum;
} $writer
B step 50
} {SQLITE_DONE}
do_test backup-5.$iTest.5.4 {
B finish
} {SQLITE_OK}
integrity_check backup-5.$iTest.5.5 db2
test_contents backup-5.$iTest.5.6 db main db2 main
}
catch {db close}
catch {db2 close}
catch {db3 close}
#
# End of backup-5.* tests.
#---------------------------------------------------------------------
#---------------------------------------------------------------------
# Test the sqlite3_backup_remaining() and backup_pagecount() APIs.
#
do_test backup-6.1 {
catch { file delete -force test.db }
catch { file delete -force test2.db }
sqlite3 db test.db
sqlite3 db2 test2.db
execsql {
BEGIN;
CREATE TABLE t1(a, b);
CREATE INDEX i1 ON t1(a, b);
INSERT INTO t1 VALUES(1, randstr(1000,1000));
INSERT INTO t1 VALUES(2, randstr(1000,1000));
INSERT INTO t1 VALUES(3, randstr(1000,1000));
INSERT INTO t1 VALUES(4, randstr(1000,1000));
INSERT INTO t1 VALUES(5, randstr(1000,1000));
COMMIT;
}
} {}
do_test backup-6.2 {
set nTotal [expr {[file size test.db]/1024}]
sqlite3_backup B db2 main db main
B step 1
} {SQLITE_OK}
do_test backup-6.3 {
B pagecount
} $nTotal
do_test backup-6.4 {
B remaining
} [expr $nTotal-1]
do_test backup-6.5 {
B step 5
list [B remaining] [B pagecount]
} [list [expr $nTotal-6] $nTotal]
do_test backup-6.6 {
execsql { CREATE TABLE t2(a PRIMARY KEY, b) }
B step 1
list [B remaining] [B pagecount]
} [list [expr $nTotal-5] [expr $nTotal+2]]
do_test backup-6.X {
B finish
} {SQLITE_OK}
#---------------------------------------------------------------------
# Test cases backup-7.* test that SQLITE_BUSY and SQLITE_LOCKED errors
# are returned correctly:
#
# backup-7.1.*: Source database is externally locked (return SQLITE_BUSY).
#
# backup-7.2.*: Attempt to step the backup process while a
# write-transaction is underway on the source pager (return
# SQLITE_LOCKED).
#
# backup-7.3.*: Destination database is externally locked (return SQLITE_BUSY).
#
do_test backup-7.0 {
catch { file delete -force test.db }
catch { file delete -force test2.db }
sqlite3 db2 test2.db
sqlite3 db test.db
execsql {
CREATE TABLE t1(a, b);
CREATE INDEX i1 ON t1(a, b);
INSERT INTO t1 VALUES(1, randstr(1000,1000));
INSERT INTO t1 SELECT a+ 1, randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT a+ 2, randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT a+ 4, randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT a+ 8, randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT a+16, randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT a+32, randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT a+64, randstr(1000,1000) FROM t1;
}
} {}
do_test backup-7.1.1 {
sqlite3_backup B db2 main db main
B step 5
} {SQLITE_OK}
do_test backup-7.1.2 {
sqlite3 db3 test.db
execsql { BEGIN EXCLUSIVE } db3
B step 5
} {SQLITE_BUSY}
do_test backup-7.1.3 {
execsql { ROLLBACK } db3
B step 5
} {SQLITE_OK}
do_test backup-7.2.1 {
execsql {
BEGIN;
INSERT INTO t1 VALUES(1, 4);
}
} {}
do_test backup-7.2.2 {
B step 5000
} {SQLITE_LOCKED}
do_test backup-7.2.3 {
execsql { ROLLBACK }
B step 5000
} {SQLITE_DONE}
do_test backup-7.2.4 {
B finish
} {SQLITE_OK}
test_contents backup-7.2.5 db main db2 main
integrity_check backup-7.3.6 db2
do_test backup-7.3.1 {
db2 close
db3 close
file delete -force test2.db
sqlite3 db2 test2.db
sqlite3 db3 test2.db
sqlite3_backup B db2 main db main
execsql { BEGIN ; CREATE TABLE t2(a, b); } db3
B step 5
} {SQLITE_BUSY}
do_test backup-7.3.2 {
execsql { COMMIT } db3
B step 5000
} {SQLITE_DONE}
do_test backup-7.3.3 {
B finish
} {SQLITE_OK}
test_contents backup-7.3.4 db main db2 main
integrity_check backup-7.3.5 db2
catch { db2 close }
catch { db3 close }
#-----------------------------------------------------------------------
# The following tests, backup-8.*, test attaching multiple backup
# processes to the same source database. Also, reading from the source
# database while a read transaction is active.
#
# These tests reuse the database "test.db" left over from backup-7.*.
#
do_test backup-8.1 {
catch { file delete -force test2.db }
catch { file delete -force test3.db }
sqlite3 db2 test2.db
sqlite3 db3 test3.db
sqlite3_backup B2 db2 main db main
sqlite3_backup B3 db3 main db main
list [B2 finish] [B3 finish]
} {SQLITE_OK SQLITE_OK}
do_test backup-8.2 {
sqlite3_backup B3 db3 main db main
sqlite3_backup B2 db2 main db main
list [B2 finish] [B3 finish]
} {SQLITE_OK SQLITE_OK}
do_test backup-8.3 {
sqlite3_backup B2 db2 main db main
sqlite3_backup B3 db3 main db main
B2 step 5
} {SQLITE_OK}
do_test backup-8.4 {
execsql {
BEGIN;
SELECT * FROM sqlite_master;
}
B3 step 5
} {SQLITE_OK}
do_test backup-8.5 {
list [B3 step 5000] [B3 finish]
} {SQLITE_DONE SQLITE_OK}
do_test backup-8.6 {
list [B2 step 5000] [B2 finish]
} {SQLITE_DONE SQLITE_OK}
test_contents backup-8.7 db main db2 main
test_contents backup-8.8 db main db3 main
do_test backup-8.9 {
execsql { PRAGMA lock_status }
} {main shared temp closed}
do_test backup-8.10 {
execsql COMMIT
} {}
catch { db2 close }
catch { db3 close }
finish_test

287
test/backup_ioerr.test Normal file
View File

@ -0,0 +1,287 @@
# 2008 January 30
#
# 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.
#
#***********************************************************************
# This file implements regression tests for SQLite library. The
# focus of this file is testing the handling of IO errors by the
# sqlite3_backup_XXX APIs.
#
# $Id: backup_ioerr.test,v 1.1 2009/02/03 16:51:25 danielk1977 Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
proc data_checksum {db file} {
$db one "SELECT md5sum(a, b) FROM ${file}.t1"
}
proc test_contents {name db1 file1 db2 file2} {
$db2 eval {select * from sqlite_master}
$db1 eval {select * from sqlite_master}
set checksum [data_checksum $db2 $file2]
uplevel [list do_test $name [list data_checksum $db1 $file1] $checksum]
}
#--------------------------------------------------------------------
# This proc creates a database of approximately 290 pages. Depending
# on whether or not auto-vacuum is configured. Test cases backup_ioerr-1.*
# verify nothing more than this assumption.
#
proc populate_database {db {xtra_large 0}} {
execsql {
BEGIN;
CREATE TABLE t1(a, b);
INSERT INTO t1 VALUES(1, randstr(1000,1000));
INSERT INTO t1 SELECT a+ 1, randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT a+ 2, randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT a+ 4, randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT a+ 8, randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT a+16, randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT a+32, randstr(1000,1000) FROM t1;
CREATE INDEX i1 ON t1(b);
COMMIT;
} $db
if {$xtra_large} {
execsql { INSERT INTO t1 SELECT a+64, randstr(1000,1000) FROM t1 } $db
}
}
do_test backup_ioerr-1.1 {
populate_database db
set nPage [expr {[file size test.db] / 1024}]
expr {$nPage>140 && $nPage<150}
} {1}
do_test backup_ioerr-1.2 {
expr {[file size test.db] > $sqlite_pending_byte}
} {1}
do_test backup_ioerr-1.3 {
db close
file delete -force test.db
} {}
# Turn off IO error simulation.
#
proc clear_ioerr_simulation {} {
set ::sqlite_io_error_hit 0
set ::sqlite_io_error_hardhit 0
set ::sqlite_io_error_pending 0
set ::sqlite_io_error_persist 0
}
#--------------------------------------------------------------------
# The following procedure runs with SQLite's IO error simulation
# enabled.
#
# 1) Start with a reasonably sized database. One that includes the
# pending-byte (locking) page.
#
# 2) Open a backup process. Set the cache-size for the destination
# database to 10 pages only.
#
# 3) Step the backup process N times to partially backup the database
# file. If an IO error is reported, then the backup process is
# concluded with a call to backup_finish().
#
# If an IO error occurs, verify that:
#
# * the call to backup_step() returns an SQLITE_IOERR_XXX error code.
#
# * after the failed call to backup_step() but before the call to
# backup_finish() the destination database handle error code and
# error message remain unchanged.
#
# * the call to backup_finish() returns an SQLITE_IOERR_XXX error code.
#
# * following the call to backup_finish(), the destination database
# handle has been populated with an error code and error message.
#
# 4) Write to the database via the source database connection. Check
# that:
#
# * If an IO error occurs while writing the source database, the
# write operation should report an IO error. The backup should
# proceed as normal.
#
# * If an IO error occurs while updating the backup, the write
# operation should proceed normally. The error should be reported
# from the next call to backup_step() (in step 5 of this test
# procedure).
#
# 5) Step the backup process to finish the backup. If an IO error is
# reported, then the backup process is concluded with a call to
# backup_finish().
#
# Test that if an IO error occurs, or if one occured while updating
# the backup database during step 4, then the conditions listed
# under step 3 are all true.
#
# 6) Finish the backup process.
#
# * If the backup succeeds (backup_finish() returns SQLITE_OK), then
# the contents of the backup database should match that of the
# source database.
#
# * If the backup fails (backup_finish() returns other than SQLITE_OK),
# then the contents of the backup database should be as they were
# before the operation was started.
#
# The following factors are varied:
#
# * Destination database is initially larger than the source database, OR
# * Destination database is initially smaller than the source database.
#
# * IO errors are transient, OR
# * IO errors are persistent.
#
# * Destination page-size is smaller than the source.
# * Destination page-size is the same as the source.
# * Destination page-size is larger than the source.
#
set iTest 1
foreach bPersist {0 1} {
foreach iDestPagesize {512 1024 4096} {
foreach zSetupBak [list "" {populate_database ddb 1}] {
incr iTest
set bStop 0
for {set iError 1} {$bStop == 0} {incr iError} {
# Disable IO error simulation.
clear_ioerr_simulation
catch { ddb close }
catch { sdb close }
catch { file delete -force test.db }
catch { file delete -force bak.db }
# Open the source and destination databases.
sqlite3 sdb test.db
sqlite3 ddb bak.db
# Step 1: Populate the source and destination databases.
populate_database sdb
ddb eval "PRAGMA page_size = $iDestPagesize"
ddb eval "PRAGMA cache_size = 10"
eval $zSetupBak
# Step 2: Open the backup process.
sqlite3_backup B ddb main sdb main
# Enable IO error simulation.
set ::sqlite_io_error_pending $iError
set ::sqlite_io_error_persist $bPersist
# Step 3: Partially backup the database. If an IO error occurs, check
# a few things then skip to the next iteration of the loop.
#
set rc [B step 100]
if {$::sqlite_io_error_hardhit} {
do_test backup_ioerr-$iTest.$iError.1 {
string match SQLITE_IOERR* $rc
} {1}
do_test backup_ioerr-$iTest.$iError.2 {
list [sqlite3_errcode ddb] [sqlite3_errmsg ddb]
} {SQLITE_OK {not an error}}
set rc [B finish]
do_test backup_ioerr-$iTest.$iError.3 {
string match SQLITE_IOERR* $rc
} {1}
do_test backup_ioerr-$iTest.$iError.4 {
sqlite3_errmsg ddb
} {disk I/O error}
clear_ioerr_simulation
sqlite3 ddb bak.db
integrity_check backup_ioerr-$iTest.$iError.5 ddb
continue
}
# No IO error was encountered during step 3. Check that backup_step()
# returned SQLITE_OK before proceding.
do_test backup_ioerr-$iTest.$iError.6 {
expr {$rc eq "SQLITE_OK"}
} {1}
# Step 4: Write to the source database.
set rc [catchsql { UPDATE t1 SET b = randstr(1000,1000) WHERE a < 50 } sdb]
if {[lindex $rc 0] && $::sqlite_io_error_persist==0} {
# The IO error occured while updating the source database. In this
# case the backup should be able to continue.
set rc [B step 5000]
if { $rc != "SQLITE_IOERR_UNLOCK" } {
do_test backup_ioerr-$iTest.$iError.7 {
list [B step 5000] [B finish]
} {SQLITE_DONE SQLITE_OK}
clear_ioerr_simulation
test_contents backup_ioerr-$iTest.$iError.8 ddb main sdb main
integrity_check backup_ioerr-$iTest.$iError.9 ddb
} else {
do_test backup_ioerr-$iTest.$iError.10 {
B finish
} {SQLITE_IOERR_UNLOCK}
}
clear_ioerr_simulation
sqlite3 ddb bak.db
integrity_check backup_ioerr-$iTest.$iError.11 ddb
continue
}
# Step 5: Finish the backup operation. If an IO error occurs, check that
# it is reported correctly and skip to the next iteration of the loop.
#
set rc [B step 5000]
if {$rc != "SQLITE_DONE"} {
do_test backup_ioerr-$iTest.$iError.12 {
string match SQLITE_IOERR* $rc
} {1}
do_test backup_ioerr-$iTest.$iError.13 {
list [sqlite3_errcode ddb] [sqlite3_errmsg ddb]
} {SQLITE_OK {not an error}}
set rc [B finish]
do_test backup_ioerr-$iTest.$iError.14 {
string match SQLITE_IOERR* $rc
} {1}
do_test backup_ioerr-$iTest.$iError.15 {
sqlite3_errmsg ddb
} {disk I/O error}
clear_ioerr_simulation
sqlite3 ddb bak.db
integrity_check backup_ioerr-$iTest.$iError.16 ddb
continue
}
# The backup was successfully completed.
#
do_test backup_ioerr-$iTest.$iError.17 {
list [set rc] [B finish]
} {SQLITE_DONE SQLITE_OK}
clear_ioerr_simulation
sqlite3 sdb test.db
sqlite3 ddb bak.db
test_contents backup_ioerr-$iTest.$iError.18 ddb main sdb main
integrity_check backup_ioerr-$iTest.$iError.19 ddb
set bStop [expr $::sqlite_io_error_pending<=0]
}}}}
catch { sdb close }
catch { ddb close }
finish_test

86
test/backup_malloc.test Normal file
View File

@ -0,0 +1,86 @@
# 2008 January 30
#
# 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.
#
#***********************************************************************
# This file implements regression tests for SQLite library. The
# focus of this file is testing the handling of OOM errors by the
# sqlite3_backup_XXX APIs.
#
# $Id: backup_malloc.test,v 1.1 2009/02/03 16:51:25 danielk1977 Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/malloc_common.tcl
do_malloc_test backup_malloc-1 -tclprep {
execsql {
PRAGMA cache_size = 10;
BEGIN;
CREATE TABLE t1(a, b);
INSERT INTO t1 VALUES(1, randstr(1000,1000));
INSERT INTO t1 SELECT a+ 1, randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT a+ 2, randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT a+ 4, randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT a+ 8, randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT a+16, randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT a+32, randstr(1000,1000) FROM t1;
INSERT INTO t1 SELECT a+64, randstr(1000,1000) FROM t1;
CREATE INDEX i1 ON t1(b);
COMMIT;
}
sqlite3 db2 test2.db
execsql { PRAGMA cache_size = 10 } db2
} -tclbody {
# Create a backup object.
#
set rc [catch {sqlite3_backup B db2 main db main}]
if {$rc && [sqlite3_errcode db2] == "SQLITE_NOMEM"} {
error "out of memory"
}
# Run the backup process some.
#
set rc [B step 50]
if {$rc == "SQLITE_NOMEM" || $rc == "SQLITE_IOERR_NOMEM"} {
error "out of memory"
}
# Update the database.
#
execsql { UPDATE t1 SET a = a + 1 }
# Finish doing the backup.
#
set rc [B step 5000]
if {$rc == "SQLITE_NOMEM" || $rc == "SQLITE_IOERR_NOMEM"} {
error "out of memory"
}
# Finalize the backup.
B finish
} -cleanup {
catch { B finish }
}
do_malloc_test backup_malloc-1 -tclprep {
sqlite3 db2 test2.db
} -tclbody {
set rc [catch {sqlite3_backup B db2 temp db main}]
set errcode [sqlite3_errcode db2]
if {$rc && ($errcode == "SQLITE_NOMEM" || $errcode == "SQLITE_IOERR_NOMEM")} {
error "out of memory"
}
} -cleanup {
catch { B finish }
db2 close
}
finish_test

View File

@ -6,7 +6,7 @@
#***********************************************************************
# This file runs all tests.
#
# $Id: quick.test,v 1.91 2009/01/03 10:41:29 danielk1977 Exp $
# $Id: quick.test,v 1.92 2009/02/03 16:51:25 danielk1977 Exp $
proc lshift {lvar} {
upvar $lvar l
@ -46,6 +46,7 @@ set EXCLUDE {
async.test
async2.test
async3.test
backup_ioerr.test
corrupt.test
corruptC.test
crash.test

View File

@ -11,7 +11,7 @@
# This file implements some common TCL routines used for regression
# testing the SQLite library
#
# $Id: tester.tcl,v 1.136 2009/01/09 10:49:14 danielk1977 Exp $
# $Id: tester.tcl,v 1.137 2009/02/03 16:51:25 danielk1977 Exp $
#
# What for user input before continuing. This gives an opportunity
@ -485,11 +485,9 @@ proc forcedelete {filename} {
# Do an integrity check of the entire database
#
proc integrity_check {name} {
proc integrity_check {name {db db}} {
ifcapable integrityck {
do_test $name {
execsql {PRAGMA integrity_check}
} {ok}
do_test $name [list execsql {PRAGMA integrity_check} $db] {ok}
}
}

View File

@ -247,6 +247,7 @@ foreach file {
btmutex.c
btree.c
backup.c
vdbemem.c
vdbeaux.c