$(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 \
#
TESTSRC2 = \
$(TOP)/src/attach.c \
+ $(TOP)/src/backup.c \
$(TOP)/src/bitvec.c \
$(TOP)/src/btree.c \
$(TOP)/src/build.c \
$(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 \
# 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 \
$(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 \
$(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 \
#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 \
-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
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
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
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
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
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
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
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
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
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
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
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
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
-2e45c2a85183f7430225aa8dd89ee05028afecf2
\ No newline at end of file
+663479b417fc06ba1790a544f28694f8797cee57
\ No newline at end of file
*************************************************************************
** 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"
"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;
}
--- /dev/null
+/*
+** 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 */
+
** 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.
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.
*/
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
** 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_
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);
** 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
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 */
};
** 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"
}
/*
-** 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:
**
** 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"
}
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);
** 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"
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 */
};
/*
/*
** 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);
}
}
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
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));
** 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);
/* 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
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
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 */
** 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_
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);
const char *sqlite3PagerJournalname(Pager*);
int sqlite3PagerNosync(Pager*);
void *sqlite3PagerTempSpace(Pager*);
+int sqlite3PagerIsMemdb(Pager*);
/* Functions used to truncate the database file. */
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
** 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_
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.
*************************************************************************
** 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_
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);
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
*/
** 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>
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);
SqlitetestThread_Init(interp);
SqlitetestOnefile_Init(interp);
SqlitetestOsinst_Init(interp);
+ Sqlitetestbackup_Init(interp);
#ifdef SQLITE_SSE
Sqlitetestsse_Init(interp);
** 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"
}
/*
-** 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.
--- /dev/null
+/*
+** 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;
+}
+
** 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"
#ifndef SQLITE_OMIT_AUTOVACUUM
sqlite3BtreeSetAutoVacuum(pMain, sqlite3BtreeGetAutoVacuum(pTemp));
#endif
- rc = sqlite3BtreeCommit(pMain);
}
if( rc==SQLITE_OK ){
** 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"
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);
--- /dev/null
+# 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
--- /dev/null
+# 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
+
--- /dev/null
+# 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
#***********************************************************************
# 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
async.test
async2.test
async3.test
+ backup_ioerr.test
corrupt.test
corruptC.test
crash.test
# 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
# 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}
}
}
btmutex.c
btree.c
+ backup.c
vdbemem.c
vdbeaux.c