Index: src/main.c
==================================================================
--- src/main.c
+++ src/main.c
@@ -3979,26 +3979,27 @@
const char *zDb,
sqlite3_snapshot **ppSnapshot
){
int rc = SQLITE_ERROR;
#ifndef SQLITE_OMIT_WAL
- int iDb;
#ifdef SQLITE_ENABLE_API_ARMOR
if( !sqlite3SafetyCheckOk(db) ){
return SQLITE_MISUSE_BKPT;
}
#endif
sqlite3_mutex_enter(db->mutex);
- iDb = sqlite3FindDbName(db, zDb);
- if( iDb==0 || iDb>1 ){
- Btree *pBt = db->aDb[iDb].pBt;
- if( 0==sqlite3BtreeIsInTrans(pBt) ){
- rc = sqlite3BtreeBeginTrans(pBt, 0);
- if( rc==SQLITE_OK ){
- rc = sqlite3PagerSnapshotGet(sqlite3BtreePager(pBt), ppSnapshot);
+ if( db->autoCommit==0 ){
+ int iDb = sqlite3FindDbName(db, zDb);
+ if( iDb==0 || iDb>1 ){
+ Btree *pBt = db->aDb[iDb].pBt;
+ if( 0==sqlite3BtreeIsInTrans(pBt) ){
+ rc = sqlite3BtreeBeginTrans(pBt, 0);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3PagerSnapshotGet(sqlite3BtreePager(pBt), ppSnapshot);
+ }
}
}
}
sqlite3_mutex_leave(db->mutex);
@@ -4036,10 +4037,42 @@
}
}
}
}
+ sqlite3_mutex_leave(db->mutex);
+#endif /* SQLITE_OMIT_WAL */
+ return rc;
+}
+
+/*
+** Recover as many snapshots as possible from the wal file associated with
+** schema zDb of database db.
+*/
+int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb){
+ int rc = SQLITE_ERROR;
+ int iDb;
+#ifndef SQLITE_OMIT_WAL
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ return SQLITE_MISUSE_BKPT;
+ }
+#endif
+
+ sqlite3_mutex_enter(db->mutex);
+ iDb = sqlite3FindDbName(db, zDb);
+ if( iDb==0 || iDb>1 ){
+ Btree *pBt = db->aDb[iDb].pBt;
+ if( 0==sqlite3BtreeIsInReadTrans(pBt) ){
+ rc = sqlite3BtreeBeginTrans(pBt, 0);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3PagerSnapshotRecover(sqlite3BtreePager(pBt));
+ sqlite3BtreeCommit(pBt);
+ }
+ }
+ }
sqlite3_mutex_leave(db->mutex);
#endif /* SQLITE_OMIT_WAL */
return rc;
}
Index: src/pager.c
==================================================================
--- src/pager.c
+++ src/pager.c
@@ -7403,10 +7403,24 @@
}else{
rc = SQLITE_ERROR;
}
return rc;
}
+
+/*
+** If this is a WAL database, call sqlite3WalSnapshotRecover(). If this
+** is not a WAL database, return an error.
+*/
+int sqlite3PagerSnapshotRecover(Pager *pPager){
+ int rc;
+ if( pPager->pWal ){
+ rc = sqlite3WalSnapshotRecover(pPager->pWal);
+ }else{
+ rc = SQLITE_ERROR;
+ }
+ return rc;
+}
#endif /* SQLITE_ENABLE_SNAPSHOT */
#endif /* !SQLITE_OMIT_WAL */
#ifdef SQLITE_ENABLE_ZIPVFS
/*
Index: src/pager.h
==================================================================
--- src/pager.h
+++ src/pager.h
@@ -180,10 +180,11 @@
int sqlite3PagerCloseWal(Pager *pPager, sqlite3*);
int sqlite3PagerUseWal(Pager *pPager);
# ifdef SQLITE_ENABLE_SNAPSHOT
int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot);
int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot);
+ int sqlite3PagerSnapshotRecover(Pager *pPager);
# endif
#else
# define sqlite3PagerUseWal(x) 0
#endif
Index: src/sqlite.h.in
==================================================================
--- src/sqlite.h.in
+++ src/sqlite.h.in
@@ -8281,11 +8281,13 @@
** The constructor for this object is [sqlite3_snapshot_get()]. The
** [sqlite3_snapshot_open()] method causes a fresh read transaction to refer
** to an historical snapshot (if possible). The destructor for
** sqlite3_snapshot objects is [sqlite3_snapshot_free()].
*/
-typedef struct sqlite3_snapshot sqlite3_snapshot;
+typedef struct sqlite3_snapshot {
+ unsigned char hidden[48];
+} sqlite3_snapshot;
/*
** CAPI3REF: Record A Database Snapshot
** EXPERIMENTAL
**
@@ -8292,13 +8294,36 @@
** ^The [sqlite3_snapshot_get(D,S,P)] interface attempts to make a
** new [sqlite3_snapshot] object that records the current state of
** schema S in database connection D. ^On success, the
** [sqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly
** created [sqlite3_snapshot] object into *P and returns SQLITE_OK.
-** ^If schema S of [database connection] D is not a [WAL mode] database
-** that is in a read transaction, then [sqlite3_snapshot_get(D,S,P)]
-** leaves the *P value unchanged and returns an appropriate [error code].
+** If there is not already a read-transaction open on schema S when
+** this function is called, one is opened automatically.
+**
+** The following must be true for this function to succeed. If any of
+** the following statements are false when sqlite3_snapshot_get() is
+** called, SQLITE_ERROR is returned. The final value of *P is undefined
+** in this case.
+**
+**
+** - The database handle must be in [autocommit mode].
+**
+**
- Schema S of [database connection] D must be a [WAL mode] database.
+**
+**
- There must not be a write transaction open on schema S of database
+** connection D.
+**
+**
- One or more transactions must have been written to the current wal
+** file since it was created on disk (by any connection). This means
+** that a snapshot cannot be taken on a wal mode database with no wal
+** file immediately after it is first opened. At least one transaction
+** must be written to it first.
+**
+**
+** This function may also return SQLITE_NOMEM. If it is called with the
+** database handle in autocommit mode but fails for some other reason,
+** whether or not a read transaction is opened on schema S is undefined.
**
** The [sqlite3_snapshot] object returned from a successful call to
** [sqlite3_snapshot_get()] must be freed using [sqlite3_snapshot_free()]
** to avoid a memory leak.
**
@@ -8387,10 +8412,32 @@
SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
sqlite3_snapshot *p1,
sqlite3_snapshot *p2
);
+/*
+** CAPI3REF: Recover snapshots from a wal file
+** EXPERIMENTAL
+**
+** If all connections disconnect from a database file but do not perform
+** a checkpoint, the existing wal file is opened along with the database
+** file the next time the database is opened. At this point it is only
+** possible to successfully call sqlite3_snapshot_open() to open the most
+** recent snapshot of the database (the one at the head of the wal file),
+** even though the wal file may contain other valid snapshots for which
+** clients have sqlite3_snapshot handles.
+**
+** This function attempts to scan the wal file associated with database zDb
+** of database handle db and make all valid snapshots available to
+** sqlite3_snapshot_open(). It is an error if there is already a read
+** transaction open on the database, or if the database is not a wal mode
+** database.
+**
+** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
+*/
+SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);
+
/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.
*/
#ifdef SQLITE_OMIT_FLOATING_POINT
Index: src/test1.c
==================================================================
--- src/test1.c
+++ src/test1.c
@@ -2308,10 +2308,42 @@
}
return TCL_OK;
}
#endif /* SQLITE_ENABLE_SNAPSHOT */
+#ifdef SQLITE_ENABLE_SNAPSHOT
+/*
+** Usage: sqlite3_snapshot_recover DB DBNAME
+*/
+static int SQLITE_TCLAPI test_snapshot_recover(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ int rc;
+ sqlite3 *db;
+ char *zName;
+
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME");
+ return TCL_ERROR;
+ }
+ if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+ zName = Tcl_GetString(objv[2]);
+
+ rc = sqlite3_snapshot_recover(db, zName);
+ if( rc!=SQLITE_OK ){
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+ return TCL_ERROR;
+ }else{
+ Tcl_ResetResult(interp);
+ }
+ return TCL_OK;
+}
+#endif /* SQLITE_ENABLE_SNAPSHOT */
+
#ifdef SQLITE_ENABLE_SNAPSHOT
/*
** Usage: sqlite3_snapshot_open DB DBNAME SNAPSHOT
*/
static int SQLITE_TCLAPI test_snapshot_open(
@@ -2383,10 +2415,117 @@
p1 = (sqlite3_snapshot*)sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
p2 = (sqlite3_snapshot*)sqlite3TestTextToPtr(Tcl_GetString(objv[2]));
res = sqlite3_snapshot_cmp(p1, p2);
Tcl_SetObjResult(interp, Tcl_NewIntObj(res));
return TCL_OK;
+}
+#endif /* SQLITE_ENABLE_SNAPSHOT */
+
+#ifdef SQLITE_ENABLE_SNAPSHOT
+/*
+** Usage: sqlite3_snapshot_get_blob DB DBNAME
+*/
+static int SQLITE_TCLAPI test_snapshot_get_blob(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ int rc;
+ sqlite3 *db;
+ char *zName;
+ sqlite3_snapshot *pSnapshot = 0;
+
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME");
+ return TCL_ERROR;
+ }
+ if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+ zName = Tcl_GetString(objv[2]);
+
+ rc = sqlite3_snapshot_get(db, zName, &pSnapshot);
+ if( rc!=SQLITE_OK ){
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+ return TCL_ERROR;
+ }else{
+ Tcl_SetObjResult(interp,
+ Tcl_NewByteArrayObj((unsigned char*)pSnapshot, sizeof(sqlite3_snapshot))
+ );
+ sqlite3_snapshot_free(pSnapshot);
+ }
+ return TCL_OK;
+}
+#endif /* SQLITE_ENABLE_SNAPSHOT */
+
+#ifdef SQLITE_ENABLE_SNAPSHOT
+ /*
+ ** Usage: sqlite3_snapshot_open_blob DB DBNAME SNAPSHOT
+*/
+static int SQLITE_TCLAPI test_snapshot_open_blob(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ int rc;
+ sqlite3 *db;
+ char *zName;
+ unsigned char *pBlob;
+ int nBlob;
+
+ if( objc!=4 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME SNAPSHOT");
+ return TCL_ERROR;
+ }
+ if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+ zName = Tcl_GetString(objv[2]);
+ pBlob = Tcl_GetByteArrayFromObj(objv[3], &nBlob);
+ if( nBlob!=sizeof(sqlite3_snapshot) ){
+ Tcl_AppendResult(interp, "bad SNAPSHOT", 0);
+ return TCL_ERROR;
+ }
+ rc = sqlite3_snapshot_open(db, zName, (sqlite3_snapshot*)pBlob);
+ if( rc!=SQLITE_OK ){
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+ return TCL_ERROR;
+ }
+ return TCL_OK;
+}
+#endif /* SQLITE_ENABLE_SNAPSHOT */
+
+#ifdef SQLITE_ENABLE_SNAPSHOT
+/*
+** Usage: sqlite3_snapshot_cmp_blob SNAPSHOT1 SNAPSHOT2
+*/
+static int SQLITE_TCLAPI test_snapshot_cmp_blob(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ int res;
+ unsigned char *p1;
+ unsigned char *p2;
+ int n1;
+ int n2;
+
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "SNAPSHOT1 SNAPSHOT2");
+ return TCL_ERROR;
+ }
+
+ p1 = Tcl_GetByteArrayFromObj(objv[1], &n1);
+ p2 = Tcl_GetByteArrayFromObj(objv[2], &n2);
+
+ if( n1!=sizeof(sqlite3_snapshot) || n1!=n2 ){
+ Tcl_AppendResult(interp, "bad SNAPSHOT", 0);
+ return TCL_ERROR;
+ }
+
+ res = sqlite3_snapshot_cmp((sqlite3_snapshot*)p1, (sqlite3_snapshot*)p2);
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(res));
+ return TCL_OK;
}
#endif /* SQLITE_ENABLE_SNAPSHOT */
/*
** Usage: sqlite3_delete_database FILENAME
@@ -7537,10 +7676,14 @@
#ifdef SQLITE_ENABLE_SNAPSHOT
{ "sqlite3_snapshot_get", test_snapshot_get, 0 },
{ "sqlite3_snapshot_open", test_snapshot_open, 0 },
{ "sqlite3_snapshot_free", test_snapshot_free, 0 },
{ "sqlite3_snapshot_cmp", test_snapshot_cmp, 0 },
+ { "sqlite3_snapshot_recover", test_snapshot_recover, 0 },
+ { "sqlite3_snapshot_get_blob", test_snapshot_get_blob, 0 },
+ { "sqlite3_snapshot_open_blob", test_snapshot_open_blob, 0 },
+ { "sqlite3_snapshot_cmp_blob", test_snapshot_cmp_blob, 0 },
#endif
{ "sqlite3_delete_database", test_delete_database, 0 },
};
static int bitmask_size = sizeof(Bitmask)*8;
static int longdouble_size = sizeof(LONGDOUBLE_TYPE);
Index: src/wal.c
==================================================================
--- src/wal.c
+++ src/wal.c
@@ -2377,10 +2377,88 @@
pWal->readLock = (i16)mxI;
}
return rc;
}
+#ifdef SQLITE_ENABLE_SNAPSHOT
+/*
+** Attempt to reduce the value of the WalCkptInfo.nBackfillAttempted
+** variable so that older snapshots can be accessed. To do this, loop
+** through all wal frames from nBackfillAttempted to (nBackfill+1),
+** comparing their content to the corresponding page with the database
+** file, if any. Set nBackfillAttempted to the frame number of the
+** first frame for which the wal file content matches the db file.
+**
+** This is only really safe if the file-system is such that any page
+** writes made by earlier checkpointers were atomic operations, which
+** is not always true. It is also possible that nBackfillAttempted
+** may be left set to a value larger than expected, if a wal frame
+** contains content that duplicate of an earlier version of the same
+** page.
+**
+** SQLITE_OK is returned if successful, or an SQLite error code if an
+** error occurs. It is not an error if nBackfillAttempted cannot be
+** decreased at all.
+*/
+int sqlite3WalSnapshotRecover(Wal *pWal){
+ int rc;
+
+ assert( pWal->readLock>=0 );
+ rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1);
+ if( rc==SQLITE_OK ){
+ volatile WalCkptInfo *pInfo = walCkptInfo(pWal);
+ int szPage = (int)pWal->szPage;
+ i64 szDb; /* Size of db file in bytes */
+
+ rc = sqlite3OsFileSize(pWal->pDbFd, &szDb);
+ if( rc==SQLITE_OK ){
+ void *pBuf1 = sqlite3_malloc(szPage);
+ void *pBuf2 = sqlite3_malloc(szPage);
+ if( pBuf1==0 || pBuf2==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ u32 i = pInfo->nBackfillAttempted;
+ for(i=pInfo->nBackfillAttempted; i>pInfo->nBackfill; i--){
+ volatile ht_slot *dummy;
+ volatile u32 *aPgno; /* Array of page numbers */
+ u32 iZero; /* Frame corresponding to aPgno[0] */
+ u32 pgno; /* Page number in db file */
+ i64 iDbOff; /* Offset of db file entry */
+ i64 iWalOff; /* Offset of wal file entry */
+
+ rc = walHashGet(pWal, walFramePage(i), &dummy, &aPgno, &iZero);
+ if( rc!=SQLITE_OK ) break;
+ pgno = aPgno[i-iZero];
+ iDbOff = (i64)(pgno-1) * szPage;
+
+ if( iDbOff+szPage<=szDb ){
+ iWalOff = walFrameOffset(i, szPage) + WAL_FRAME_HDRSIZE;
+ rc = sqlite3OsRead(pWal->pWalFd, pBuf1, szPage, iWalOff);
+
+ if( rc==SQLITE_OK ){
+ rc = sqlite3OsRead(pWal->pDbFd, pBuf2, szPage, iDbOff);
+ }
+
+ if( rc!=SQLITE_OK || 0==memcmp(pBuf1, pBuf2, szPage) ){
+ break;
+ }
+ }
+
+ pInfo->nBackfillAttempted = i-1;
+ }
+ }
+
+ sqlite3_free(pBuf1);
+ sqlite3_free(pBuf2);
+ }
+ walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1);
+ }
+
+ return rc;
+}
+#endif /* SQLITE_ENABLE_SNAPSHOT */
+
/*
** Begin a read transaction on the database.
**
** This routine used to be called sqlite3OpenSnapshot() and with good reason:
** it takes a snapshot of the state of the WAL and wal-index for the current
@@ -2439,11 +2517,15 @@
** checkpointer has already determined that it will checkpoint
** snapshot X, where X is later in the wal file than pSnapshot, but
** has not yet set the pInfo->nBackfillAttempted variable to indicate
** its intent. To avoid the race condition this leads to, ensure that
** there is no checkpointer process by taking a shared CKPT lock
- ** before checking pInfo->nBackfillAttempted. */
+ ** before checking pInfo->nBackfillAttempted.
+ **
+ ** TODO: Does the aReadMark[] lock prevent a checkpointer from doing
+ ** this already?
+ */
rc = walLockShared(pWal, WAL_CKPT_LOCK);
if( rc==SQLITE_OK ){
/* Check that the wal file has not been wrapped. Assuming that it has
** not, also check that no checkpointer has attempted to checkpoint any
@@ -3391,13 +3473,18 @@
** in the object.
*/
int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot){
int rc = SQLITE_OK;
WalIndexHdr *pRet;
+ static const u32 aZero[4] = { 0, 0, 0, 0 };
assert( pWal->readLock>=0 && pWal->writeLock==0 );
+ if( memcmp(&pWal->hdr.aFrameCksum[0],aZero,16)==0 ){
+ *ppSnapshot = 0;
+ return SQLITE_ERROR;
+ }
pRet = (WalIndexHdr*)sqlite3_malloc(sizeof(WalIndexHdr));
if( pRet==0 ){
rc = SQLITE_NOMEM_BKPT;
}else{
memcpy(pRet, &pWal->hdr, sizeof(WalIndexHdr));
Index: src/wal.h
==================================================================
--- src/wal.h
+++ src/wal.h
@@ -129,10 +129,11 @@
int sqlite3WalHeapMemory(Wal *pWal);
#ifdef SQLITE_ENABLE_SNAPSHOT
int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot);
void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot);
+int sqlite3WalSnapshotRecover(Wal *pWal);
#endif
#ifdef SQLITE_ENABLE_ZIPVFS
/* If the WAL file is not empty, return the number of bytes of content
** stored in each frame (i.e. the db page-size when the WAL was created).
Index: test/snapshot.test
==================================================================
--- test/snapshot.test
+++ test/snapshot.test
@@ -24,415 +24,459 @@
if {[permutation]=="inmemory_journal"} {
finish_test
return
}
-#-------------------------------------------------------------------------
-# Check some error conditions in snapshot_get(). It is an error if:
-#
-# 1) snapshot_get() is called on a non-WAL database, or
-# 2) there is an open write transaction on the database.
-#
-do_execsql_test 1.0 {
- CREATE TABLE t1(a, b);
- INSERT INTO t1 VALUES(1, 2);
- INSERT INTO t1 VALUES(3, 4);
-}
-
-do_test 1.1.1 {
- execsql { BEGIN; SELECT * FROM t1; }
- list [catch { sqlite3_snapshot_get db main } msg] $msg
-} {1 SQLITE_ERROR}
-do_execsql_test 1.1.2 COMMIT
-
-do_test 1.2.1 {
- execsql {
- PRAGMA journal_mode = WAL;
- BEGIN;
- INSERT INTO t1 VALUES(5, 6);
- INSERT INTO t1 VALUES(7, 8);
- }
- list [catch { sqlite3_snapshot_get db main } msg] $msg
-} {1 SQLITE_ERROR}
-do_execsql_test 1.3.2 COMMIT
-
-#-------------------------------------------------------------------------
-# Check that a simple case works. Reuse the database created by the
-# block of tests above.
-#
-do_execsql_test 2.1.0 {
- BEGIN;
- SELECT * FROM t1;
-} {1 2 3 4 5 6 7 8}
-
-do_test 2.1.1 {
- set snapshot [sqlite3_snapshot_get db main]
- execsql {
- COMMIT;
- INSERT INTO t1 VALUES(9, 10);
- SELECT * FROM t1;
- }
-} {1 2 3 4 5 6 7 8 9 10}
-
-do_test 2.1.2 {
- execsql BEGIN
- sqlite3_snapshot_open db main $snapshot
- execsql {
- SELECT * FROM t1;
- }
-} {1 2 3 4 5 6 7 8}
-
-do_test 2.1.3 {
- sqlite3_snapshot_free $snapshot
- execsql COMMIT
-} {}
-
-do_test 2.2.0 {
- sqlite3 db2 test.db
- execsql {
- BEGIN;
- SELECT * FROM t1;
- } db2
-} {1 2 3 4 5 6 7 8 9 10}
-
-do_test 2.2.1 {
- set snapshot [sqlite3_snapshot_get db2 main]
- execsql {
- INSERT INTO t1 VALUES(11, 12);
- SELECT * FROM t1;
- }
-} {1 2 3 4 5 6 7 8 9 10 11 12}
-
-do_test 2.2.2 {
- execsql BEGIN
- sqlite3_snapshot_open db main $snapshot
- execsql {
- SELECT * FROM t1;
- }
-} {1 2 3 4 5 6 7 8 9 10}
-
-do_test 2.2.3 {
- sqlite3_snapshot_free $snapshot
- execsql COMMIT
- execsql COMMIT db2
- db2 close
-} {}
-
-do_test 2.3.1 {
- execsql { DELETE FROM t1 WHERE a>6 }
- set snapshot [sqlite3_snapshot_get db main]
- execsql {
- INSERT INTO t1 VALUES('a', 'b');
- INSERT INTO t1 VALUES('c', 'd');
- SELECT * FROM t1;
- }
-} {1 2 3 4 5 6 a b c d}
-do_test 2.3.2 {
- execsql BEGIN
- sqlite3_snapshot_open db main $snapshot
- execsql { SELECT * FROM t1 }
-} {1 2 3 4 5 6}
-
-do_test 2.3.3 {
- catchsql {
- INSERT INTO t1 VALUES('x','y')
- }
-} {1 {database is locked}}
-do_test 2.3.4 {
- execsql COMMIT
- sqlite3_snapshot_free $snapshot
-} {}
-
-#-------------------------------------------------------------------------
-# Check some errors in sqlite3_snapshot_open(). It is an error if:
-#
-# 1) the db is in auto-commit mode,
-# 2) the db has an open (read or write) transaction,
-# 3) the db is not a wal database,
-#
-# Reuse the database created by earlier tests.
-#
-do_execsql_test 3.0.0 {
- CREATE TABLE t2(x, y);
- INSERT INTO t2 VALUES('a', 'b');
- INSERT INTO t2 VALUES('c', 'd');
- BEGIN;
- SELECT * FROM t2;
-} {a b c d}
-do_test 3.0.1 {
- set snapshot [sqlite3_snapshot_get db main]
- execsql { COMMIT }
- execsql { INSERT INTO t2 VALUES('e', 'f'); }
-} {}
-
-do_test 3.1 {
- list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg
-} {1 SQLITE_ERROR}
-
-do_test 3.2.1 {
- execsql {
- BEGIN;
- SELECT * FROM t2;
- }
-} {a b c d e f}
-do_test 3.2.2 {
- list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg
-} {1 SQLITE_ERROR}
-
-do_test 3.2.3 {
- execsql {
- COMMIT;
- BEGIN;
- INSERT INTO t2 VALUES('g', 'h');
- }
- list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg
-} {1 SQLITE_ERROR}
-do_execsql_test 3.2.4 COMMIT
-
-do_test 3.3.1 {
- execsql { PRAGMA journal_mode = DELETE }
- execsql { BEGIN }
- list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg
-} {1 SQLITE_ERROR}
-
-do_test 3.3.2 {
- sqlite3_snapshot_free $snapshot
- execsql COMMIT
-} {}
-
-#-------------------------------------------------------------------------
-# Check that SQLITE_BUSY_SNAPSHOT is returned if the specified snapshot
-# no longer exists because the wal file has been checkpointed.
-#
-# 1. Reading a snapshot from the middle of a wal file is not possible
-# after the wal file has been checkpointed.
-#
-# 2. That a snapshot from the end of a wal file can not be read once
-# the wal file has been wrapped.
-#
-do_execsql_test 4.1.0 {
- PRAGMA journal_mode = wal;
- CREATE TABLE t3(i, j);
- INSERT INTO t3 VALUES('o', 't');
- INSERT INTO t3 VALUES('t', 'f');
- BEGIN;
- SELECT * FROM t3;
-} {wal o t t f}
-
-do_test 4.1.1 {
- set snapshot [sqlite3_snapshot_get db main]
- execsql COMMIT
-} {}
-do_test 4.1.2 {
- execsql {
- INSERT INTO t3 VALUES('f', 's');
- BEGIN;
- }
- sqlite3_snapshot_open db main $snapshot
- execsql { SELECT * FROM t3 }
-} {o t t f}
-
-do_test 4.1.3 {
- execsql {
- COMMIT;
- PRAGMA wal_checkpoint;
- BEGIN;
- }
- list [catch {sqlite3_snapshot_open db main $snapshot} msg] $msg
-} {1 SQLITE_BUSY_SNAPSHOT}
-do_test 4.1.4 {
- sqlite3_snapshot_free $snapshot
- execsql COMMIT
-} {}
-
-do_test 4.2.1 {
- execsql {
- INSERT INTO t3 VALUES('s', 'e');
- INSERT INTO t3 VALUES('n', 't');
- BEGIN;
- SELECT * FROM t3;
- }
-} {o t t f f s s e n t}
-do_test 4.2.2 {
- set snapshot [sqlite3_snapshot_get db main]
- execsql {
- COMMIT;
- PRAGMA wal_checkpoint;
- BEGIN;
- }
- sqlite3_snapshot_open db main $snapshot
- execsql { SELECT * FROM t3 }
-} {o t t f f s s e n t}
-do_test 4.2.3 {
- execsql {
- COMMIT;
- INSERT INTO t3 VALUES('e', 't');
- BEGIN;
- }
- list [catch {sqlite3_snapshot_open db main $snapshot} msg] $msg
-} {1 SQLITE_BUSY_SNAPSHOT}
-do_test 4.2.4 {
- sqlite3_snapshot_free $snapshot
-} {}
-
-#-------------------------------------------------------------------------
-# Check that SQLITE_BUSY is returned if a checkpoint is running when
-# sqlite3_snapshot_open() is called.
-#
-reset_db
-db close
-testvfs tvfs
-sqlite3 db test.db -vfs tvfs
-
-do_execsql_test 5.1 {
- PRAGMA journal_mode = wal;
- CREATE TABLE x1(x, xx, xxx);
- INSERT INTO x1 VALUES('z', 'zz', 'zzz');
- BEGIN;
- SELECT * FROM x1;
-} {wal z zz zzz}
-
-do_test 5.2 {
- set ::snapshot [sqlite3_snapshot_get db main]
- sqlite3 db2 test.db -vfs tvfs
- execsql {
- INSERT INTO x1 VALUES('a', 'aa', 'aaa');
- COMMIT;
- }
-} {}
-
-set t53 0
-proc write_callback {args} {
- do_test 5.3.[incr ::t53] {
- execsql BEGIN
- list [catch { sqlite3_snapshot_open db main $::snapshot } msg] $msg
- } {1 SQLITE_BUSY}
- catchsql COMMIT
-}
-
-tvfs filter xWrite
-tvfs script write_callback
-db2 eval { PRAGMA wal_checkpoint }
-db close
-db2 close
-tvfs delete
-sqlite3_snapshot_free $snapshot
-
-#-------------------------------------------------------------------------
-# Test that sqlite3_snapshot_get() may be called immediately after
-# "BEGIN; PRAGMA user_version;". And that sqlite3_snapshot_open() may
-# be called after opening the db handle and running the script
-# "PRAGMA user_version; BEGIN".
-reset_db
-do_execsql_test 6.1 {
- PRAGMA journal_mode = wal;
- CREATE TABLE x1(x, xx, xxx);
- INSERT INTO x1 VALUES('z', 'zz', 'zzz');
- BEGIN;
- PRAGMA user_version;
-} {wal 0}
-do_test 6.2 {
- set ::snapshot [sqlite3_snapshot_get db main]
- execsql {
- INSERT INTO x1 VALUES('a', 'aa', 'aaa');
- COMMIT;
- }
-} {}
-do_test 6.3 {
- sqlite3 db2 test.db
- db2 eval "PRAGMA user_version ; BEGIN"
- sqlite3_snapshot_open db2 main $::snapshot
- db2 eval { SELECT * FROM x1 }
-} {z zz zzz}
-do_test 6.4 {
- db2 close
- sqlite3 db2 test.db
- db2 eval "PRAGMA application_id"
- db2 eval "BEGIN"
- sqlite3_snapshot_open db2 main $::snapshot
- db2 eval { SELECT * FROM x1 }
-} {z zz zzz}
-
-do_test 6.5 {
- db2 close
- sqlite3 db2 test.db
- db2 eval "BEGIN"
- list [catch {sqlite3_snapshot_open db2 main $::snapshot} msg] $msg
-} {1 SQLITE_ERROR}
-
-sqlite3_snapshot_free $snapshot
-
-#-------------------------------------------------------------------------
-# The following tests investigate the sqlite3_snapshot_cmp() API.
-#
-
-# Compare snapshots $p1 and $p2, checking that the result is $r.
-#
-proc do_snapshot_cmp_test {tn p1 p2 r} {
- uplevel [list do_test $tn.1 [list sqlite3_snapshot_cmp $p1 $p2] $r]
- uplevel [list do_test $tn.2 [list sqlite3_snapshot_cmp $p2 $p1] [expr $r*-1]]
- uplevel [list do_test $tn.3 [list sqlite3_snapshot_cmp $p1 $p1] 0]
- uplevel [list do_test $tn.4 [list sqlite3_snapshot_cmp $p2 $p2] 0]
-}
-
-catch { db2 close }
-reset_db
-
-do_execsql_test 7.1 {
- PRAGMA journal_mode = wal;
- CREATE TABLE t1(x);
-} wal
-
-do_test 7.1.2 {
- execsql { BEGIN ; PRAGMA application_id }
- set p1 [sqlite3_snapshot_get db main]
- execsql {
- INSERT INTO t1 VALUES(10);
- COMMIT;
- }
- execsql { BEGIN ; PRAGMA application_id }
- set p2 [sqlite3_snapshot_get db main]
- execsql COMMIT
-} {}
-
-do_snapshot_cmp_test 7.1.3 $p1 $p2 -1
-sqlite3_snapshot_free $p1
-sqlite3_snapshot_free $p2
-
-do_execsql_test 7.2.1 {
- INSERT INTO t1 VALUES(11);
- INSERT INTO t1 VALUES(12);
- INSERT INTO t1 VALUES(13);
- BEGIN;
- PRAGMA application_id;
-} {0}
-do_test 7.2.2 {
- set p1 [sqlite3_snapshot_get db main]
- execsql {
- COMMIT;
- INSERT INTO t1 VALUES(14);
- PRAGMA wal_checkpoint;
- BEGIN;
- PRAGMA application_id;
- }
- set p2 [sqlite3_snapshot_get db main]
- execsql COMMIT
-} {}
-
-do_snapshot_cmp_test 7.2.3 $p1 $p2 -1
-sqlite3_snapshot_free $p2
-
-do_test 7.3.1 {
- execsql {
- INSERT INTO t1 VALUES(14);
- BEGIN;
- PRAGMA application_id;
- }
- set p2 [sqlite3_snapshot_get db main]
- execsql COMMIT
-} {}
-
-do_snapshot_cmp_test 7.3.2 $p1 $p2 -1
-sqlite3_snapshot_free $p1
-sqlite3_snapshot_free $p2
+foreach {tn tcl} {
+ 1 {
+ proc snapshot_get {DB DBNAME} {
+ uplevel [list sqlite3_snapshot_get $DB $DBNAME]
+ }
+ proc snapshot_open {DB DBNAME SNAPSHOT} {
+ uplevel [list sqlite3_snapshot_open $DB $DBNAME $SNAPSHOT]
+ }
+ proc snapshot_free {SNAPSHOT} {
+ uplevel [list sqlite3_snapshot_free $SNAPSHOT]
+ }
+ proc snapshot_cmp {SNAPSHOT1 SNAPSHOT2} {
+ uplevel [list sqlite3_snapshot_cmp $SNAPSHOT1 $SNAPSHOT2]
+ }
+ }
+
+ 2 {
+ proc snapshot_get {DB DBNAME} {
+ uplevel [list sqlite3_snapshot_get_blob $DB $DBNAME]
+ }
+ proc snapshot_open {DB DBNAME SNAPSHOT} {
+ uplevel [list sqlite3_snapshot_open_blob $DB $DBNAME $SNAPSHOT]
+ }
+ proc snapshot_free {SNAPSHOT} {
+ }
+ proc snapshot_cmp {SNAPSHOT1 SNAPSHOT2} {
+ uplevel [list sqlite3_snapshot_cmp_blob $SNAPSHOT1 $SNAPSHOT2]
+ }
+ }
+} {
+
+ reset_db
+ eval $tcl
+
+ #-------------------------------------------------------------------------
+ # Check some error conditions in snapshot_get(). It is an error if:
+ #
+ # 1) snapshot_get() is called on a non-WAL database, or
+ # 2) there is an open write transaction on the database.
+ # 3) the database handle is in auto-commit mode
+ #
+ do_execsql_test $tn.1.0 {
+ CREATE TABLE t1(a, b);
+ INSERT INTO t1 VALUES(1, 2);
+ INSERT INTO t1 VALUES(3, 4);
+ }
+
+ do_test $tn.1.1.1 {
+ execsql { BEGIN; SELECT * FROM t1; }
+ list [catch { snapshot_get db main } msg] $msg
+ } {1 SQLITE_ERROR}
+ do_execsql_test $tn.1.1.2 COMMIT
+
+ do_test $tn.1.2.1 {
+ execsql {
+ PRAGMA journal_mode = WAL;
+ BEGIN;
+ INSERT INTO t1 VALUES(5, 6);
+ INSERT INTO t1 VALUES(7, 8);
+ }
+ list [catch { snapshot_get db main } msg] $msg
+ } {1 SQLITE_ERROR}
+ do_execsql_test $tn.1.2.2 COMMIT
+
+ do_test $tn.1.3.1 {
+ list [catch { snapshot_get db main } msg] $msg
+ } {1 SQLITE_ERROR}
+ do_test $tn.1.3.2 {
+ db trans { set snap [snapshot_get db main] }
+ snapshot_free $snap
+ } {}
+
+ #-------------------------------------------------------------------------
+ # Check that a simple case works. Reuse the database created by the
+ # block of tests above.
+ #
+ do_execsql_test $tn.2.1.0 {
+ BEGIN;
+ SELECT * FROM t1;
+ } {1 2 3 4 5 6 7 8}
+
+ do_test $tn.2.1.1 {
+ set snapshot [snapshot_get db main]
+ execsql {
+ COMMIT;
+ INSERT INTO t1 VALUES(9, 10);
+ SELECT * FROM t1;
+ }
+ } {1 2 3 4 5 6 7 8 9 10}
+
+ do_test $tn.2.1.2 {
+ execsql BEGIN
+ snapshot_open db main $snapshot
+ execsql {
+ SELECT * FROM t1;
+ }
+ } {1 2 3 4 5 6 7 8}
+
+ do_test $tn.2.1.3 {
+ snapshot_free $snapshot
+ execsql COMMIT
+ } {}
+
+ do_test $tn.2.2.0 {
+ sqlite3 db2 test.db
+ execsql {
+ BEGIN;
+ SELECT * FROM t1;
+ } db2
+ } {1 2 3 4 5 6 7 8 9 10}
+
+ do_test $tn.2.2.1 {
+ set snapshot [snapshot_get db2 main]
+ execsql {
+ INSERT INTO t1 VALUES(11, 12);
+ SELECT * FROM t1;
+ }
+ } {1 2 3 4 5 6 7 8 9 10 11 12}
+
+ do_test $tn.2.2.2 {
+ execsql BEGIN
+ snapshot_open db main $snapshot
+ execsql {
+ SELECT * FROM t1;
+ }
+ } {1 2 3 4 5 6 7 8 9 10}
+
+ do_test $tn.2.2.3 {
+ snapshot_free $snapshot
+ execsql COMMIT
+ execsql COMMIT db2
+ db2 close
+ } {}
+
+ do_test $tn.2.3.1 {
+ execsql { DELETE FROM t1 WHERE a>6 }
+ db trans { set snapshot [snapshot_get db main] }
+ execsql {
+ INSERT INTO t1 VALUES('a', 'b');
+ INSERT INTO t1 VALUES('c', 'd');
+ SELECT * FROM t1;
+ }
+ } {1 2 3 4 5 6 a b c d}
+ do_test $tn.2.3.2 {
+ execsql BEGIN
+ snapshot_open db main $snapshot
+ execsql { SELECT * FROM t1 }
+ } {1 2 3 4 5 6}
+
+ do_test $tn.2.3.3 {
+ catchsql {
+ INSERT INTO t1 VALUES('x','y')
+ }
+ } {1 {database is locked}}
+ do_test $tn.2.3.4 {
+ execsql COMMIT
+ snapshot_free $snapshot
+ } {}
+
+ #-------------------------------------------------------------------------
+ # Check some errors in snapshot_open(). It is an error if:
+ #
+ # 1) the db is in auto-commit mode,
+ # 2) the db has an open (read or write) transaction,
+ # 3) the db is not a wal database,
+ #
+ # Reuse the database created by earlier tests.
+ #
+ do_execsql_test $tn.3.0.0 {
+ CREATE TABLE t2(x, y);
+ INSERT INTO t2 VALUES('a', 'b');
+ INSERT INTO t2 VALUES('c', 'd');
+ BEGIN;
+ SELECT * FROM t2;
+ } {a b c d}
+ do_test $tn.3.0.1 {
+ set snapshot [snapshot_get db main]
+ execsql { COMMIT }
+ execsql { INSERT INTO t2 VALUES('e', 'f'); }
+ } {}
+
+ do_test $tn.3.1 {
+ list [catch {snapshot_open db main $snapshot } msg] $msg
+ } {1 SQLITE_ERROR}
+
+ do_test $tn.3.2.1 {
+ execsql {
+ BEGIN;
+ SELECT * FROM t2;
+ }
+ } {a b c d e f}
+ do_test $tn.3.2.2 {
+ list [catch {snapshot_open db main $snapshot } msg] $msg
+ } {1 SQLITE_ERROR}
+
+ do_test $tn.3.2.3 {
+ execsql {
+ COMMIT;
+ BEGIN;
+ INSERT INTO t2 VALUES('g', 'h');
+ }
+ list [catch {snapshot_open db main $snapshot } msg] $msg
+ } {1 SQLITE_ERROR}
+ do_execsql_test $tn.3.2.4 COMMIT
+
+ do_test $tn.3.3.1 {
+ execsql { PRAGMA journal_mode = DELETE }
+ execsql { BEGIN }
+ list [catch {snapshot_open db main $snapshot } msg] $msg
+ } {1 SQLITE_ERROR}
+
+ do_test $tn.$tn.3.3.2 {
+ snapshot_free $snapshot
+ execsql COMMIT
+ } {}
+
+ #-------------------------------------------------------------------------
+ # Check that SQLITE_BUSY_SNAPSHOT is returned if the specified snapshot
+ # no longer exists because the wal file has been checkpointed.
+ #
+ # 1. Reading a snapshot from the middle of a wal file is not possible
+ # after the wal file has been checkpointed.
+ #
+ # 2. That a snapshot from the end of a wal file can not be read once
+ # the wal file has been wrapped.
+ #
+ do_execsql_test $tn.4.1.0 {
+ PRAGMA journal_mode = wal;
+ CREATE TABLE t3(i, j);
+ INSERT INTO t3 VALUES('o', 't');
+ INSERT INTO t3 VALUES('t', 'f');
+ BEGIN;
+ SELECT * FROM t3;
+ } {wal o t t f}
+
+ do_test $tn.4.1.1 {
+ set snapshot [snapshot_get db main]
+ execsql COMMIT
+ } {}
+ do_test $tn.4.1.2 {
+ execsql {
+ INSERT INTO t3 VALUES('f', 's');
+ BEGIN;
+ }
+ snapshot_open db main $snapshot
+ execsql { SELECT * FROM t3 }
+ } {o t t f}
+
+ do_test $tn.4.1.3 {
+ execsql {
+ COMMIT;
+ PRAGMA wal_checkpoint;
+ BEGIN;
+ }
+ list [catch {snapshot_open db main $snapshot} msg] $msg
+ } {1 SQLITE_BUSY_SNAPSHOT}
+ do_test $tn.4.1.4 {
+ snapshot_free $snapshot
+ execsql COMMIT
+ } {}
+
+ do_test $tn.4.2.1 {
+ execsql {
+ INSERT INTO t3 VALUES('s', 'e');
+ INSERT INTO t3 VALUES('n', 't');
+ BEGIN;
+ SELECT * FROM t3;
+ }
+ } {o t t f f s s e n t}
+ do_test $tn.4.2.2 {
+ set snapshot [snapshot_get db main]
+ execsql {
+ COMMIT;
+ PRAGMA wal_checkpoint;
+ BEGIN;
+ }
+ snapshot_open db main $snapshot
+ execsql { SELECT * FROM t3 }
+ } {o t t f f s s e n t}
+ do_test $tn.4.2.3 {
+ execsql {
+ COMMIT;
+ INSERT INTO t3 VALUES('e', 't');
+ BEGIN;
+ }
+ list [catch {snapshot_open db main $snapshot} msg] $msg
+ } {1 SQLITE_BUSY_SNAPSHOT}
+ do_test $tn.4.2.4 {
+ snapshot_free $snapshot
+ } {}
+
+ #-------------------------------------------------------------------------
+ # Check that SQLITE_BUSY is returned if a checkpoint is running when
+ # sqlite3_snapshot_open() is called.
+ #
+ reset_db
+ db close
+ testvfs tvfs
+ sqlite3 db test.db -vfs tvfs
+
+ do_execsql_test $tn.5.1 {
+ PRAGMA journal_mode = wal;
+ CREATE TABLE x1(x, xx, xxx);
+ INSERT INTO x1 VALUES('z', 'zz', 'zzz');
+ BEGIN;
+ SELECT * FROM x1;
+ } {wal z zz zzz}
+
+ do_test $tn.5.2 {
+ set ::snapshot [snapshot_get db main]
+ sqlite3 db2 test.db -vfs tvfs
+ execsql {
+ INSERT INTO x1 VALUES('a', 'aa', 'aaa');
+ COMMIT;
+ }
+ } {}
+
+ set t53 0
+ proc write_callback {args} {
+ do_test $tn.5.3.[incr ::t53] {
+ execsql BEGIN
+ list [catch { snapshot_open db main $::snapshot } msg] $msg
+ } {1 SQLITE_BUSY}
+ catchsql COMMIT
+ }
+
+ tvfs filter xWrite
+ tvfs script write_callback
+ db2 eval { PRAGMA wal_checkpoint }
+ db close
+ db2 close
+ tvfs delete
+ snapshot_free $snapshot
+
+ #-------------------------------------------------------------------------
+ # Test that sqlite3_snapshot_get() may be called immediately after
+ # "BEGIN; PRAGMA user_version;". And that sqlite3_snapshot_open() may
+ # be called after opening the db handle and running the script
+ # "PRAGMA user_version; BEGIN".
+ reset_db
+ do_execsql_test $tn.6.1 {
+ PRAGMA journal_mode = wal;
+ CREATE TABLE x1(x, xx, xxx);
+ INSERT INTO x1 VALUES('z', 'zz', 'zzz');
+ BEGIN;
+ PRAGMA user_version;
+ } {wal 0}
+ do_test $tn.6.2 {
+ set ::snapshot [snapshot_get db main]
+ execsql {
+ INSERT INTO x1 VALUES('a', 'aa', 'aaa');
+ COMMIT;
+ }
+ } {}
+ do_test $tn.6.3 {
+ sqlite3 db2 test.db
+ db2 eval "PRAGMA user_version ; BEGIN"
+ snapshot_open db2 main $::snapshot
+ db2 eval { SELECT * FROM x1 }
+ } {z zz zzz}
+ do_test $tn.6.4 {
+ db2 close
+ sqlite3 db2 test.db
+ db2 eval "PRAGMA application_id"
+ db2 eval "BEGIN"
+ snapshot_open db2 main $::snapshot
+ db2 eval { SELECT * FROM x1 }
+ } {z zz zzz}
+
+ do_test $tn.6.5 {
+ db2 close
+ sqlite3 db2 test.db
+ db2 eval "BEGIN"
+ list [catch {snapshot_open db2 main $::snapshot} msg] $msg
+ } {1 SQLITE_ERROR}
+
+ snapshot_free $snapshot
+
+ #-------------------------------------------------------------------------
+ # The following tests investigate the sqlite3_snapshot_cmp() API.
+ #
+
+ # Compare snapshots $p1 and $p2, checking that the result is $r.
+ #
+ proc do_snapshot_cmp_test {tn p1 p2 r} {
+ uplevel [list do_test $tn.1 [list snapshot_cmp $p1 $p2] $r]
+ uplevel [list do_test $tn.2 [list snapshot_cmp $p2 $p1] [expr $r*-1]]
+ uplevel [list do_test $tn.3 [list snapshot_cmp $p1 $p1] 0]
+ uplevel [list do_test $tn.4 [list snapshot_cmp $p2 $p2] 0]
+ }
+
+ catch { db2 close }
+ reset_db
+
+ do_execsql_test $tn.7.1 {
+ PRAGMA journal_mode = wal;
+ CREATE TABLE t1(x);
+ } wal
+
+ do_test $tn.7.1.2 {
+ execsql { BEGIN ; PRAGMA application_id }
+ set p1 [snapshot_get db main]
+ execsql {
+ INSERT INTO t1 VALUES(10);
+ COMMIT;
+ }
+ execsql { BEGIN ; PRAGMA application_id }
+ set p2 [snapshot_get db main]
+ execsql COMMIT
+ } {}
+
+ do_snapshot_cmp_test $tn.7.1.3 $p1 $p2 -1
+ snapshot_free $p1
+ snapshot_free $p2
+
+ do_execsql_test $tn.7.2.1 {
+ INSERT INTO t1 VALUES(11);
+ INSERT INTO t1 VALUES(12);
+ INSERT INTO t1 VALUES(13);
+ BEGIN;
+ PRAGMA application_id;
+ } {0}
+ do_test $tn.7.2.2 {
+ set p1 [snapshot_get db main]
+ execsql {
+ COMMIT;
+ INSERT INTO t1 VALUES(14);
+ PRAGMA wal_checkpoint;
+ BEGIN;
+ PRAGMA application_id;
+ }
+ set p2 [snapshot_get db main]
+ execsql COMMIT
+ } {}
+
+ do_snapshot_cmp_test $tn.7.2.3 $p1 $p2 -1
+ snapshot_free $p2
+
+ do_test $tn.7.3.1 {
+ execsql {
+ INSERT INTO t1 VALUES(14);
+ BEGIN;
+ PRAGMA application_id;
+ }
+ set p2 [snapshot_get db main]
+ execsql COMMIT
+ } {}
+
+ do_snapshot_cmp_test $tn.7.3.2 $p1 $p2 -1
+ snapshot_free $p1
+ snapshot_free $p2
+}
finish_test
ADDED test/snapshot2.test
Index: test/snapshot2.test
==================================================================
--- /dev/null
+++ test/snapshot2.test
@@ -0,0 +1,202 @@
+# 2016 November 18
+#
+# 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 the sqlite3_snapshot_xxx() APIs.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+ifcapable !snapshot {finish_test; return}
+set testprefix snapshot2
+
+# This test does not work with the inmemory_journal permutation. The reason
+# is that each connection opened as part of this permutation executes
+# "PRAGMA journal_mode=memory", which fails if the database is in wal mode
+# and there are one or more existing connections.
+if {[permutation]=="inmemory_journal"} {
+ finish_test
+ return
+}
+
+#-------------------------------------------------------------------------
+# Check that it is not possible to obtain a snapshot immediately after
+# a wal mode database with an empty wal file is opened. But it is after
+# the file has been written, even by some other connection.
+#
+do_execsql_test 1.0 {
+ PRAGMA journal_mode = wal;
+ CREATE TABLE t1(a, b, c);
+ INSERT INTO t1 VALUES(1, 2, 3);
+ INSERT INTO t1 VALUES(4, 5, 6);
+} {wal}
+
+db close
+do_test 1.1.1 { list [file exists test.db] [file exists test.db-wal] } {1 0}
+
+sqlite3 db test.db
+do_execsql_test 1.1.2 { SELECT * FROM t1 } {1 2 3 4 5 6}
+
+do_test 1.1.3 {
+ execsql BEGIN
+ list [catch { sqlite3_snapshot_get_blob db main } msg] $msg
+} {1 SQLITE_ERROR}
+execsql COMMIT
+
+do_test 1.1.4 {
+ execsql { INSERT INTO t1 VALUES(7, 8, 9) }
+ execsql BEGIN
+ string length [sqlite3_snapshot_get_blob db main]
+} 48
+execsql COMMIT
+
+db close
+do_test 1.2.1 { list [file exists test.db] [file exists test.db-wal] } {1 0}
+
+sqlite3 db test.db
+do_execsql_test 1.2.2 { SELECT * FROM t1 } {1 2 3 4 5 6 7 8 9}
+
+do_test 1.2.3 {
+ execsql BEGIN
+ list [catch { sqlite3_snapshot_get_blob db main } msg] $msg
+} {1 SQLITE_ERROR}
+execsql COMMIT
+
+do_test 1.2.4 {
+ sqlite3 db2 test.db
+ execsql { INSERT INTO t1 VALUES(10, 11, 12) } db2
+ execsql BEGIN
+ string length [sqlite3_snapshot_get_blob db main]
+} 48
+execsql COMMIT
+db2 close
+
+#-------------------------------------------------------------------------
+# Simple tests for sqlite3_snapshot_recover().
+#
+reset_db
+do_execsql_test 2.0 {
+ CREATE TABLE t1(x);
+ PRAGMA journal_mode = wal;
+ INSERT INTO t1 VALUES(1);
+ INSERT INTO t1 VALUES(2);
+} {wal}
+
+do_test 2.1 {
+ db trans { set snap [sqlite3_snapshot_get_blob db main] }
+ sqlite3_db_config db NO_CKPT_ON_CLOSE 1
+ db close
+ sqlite3 db test.db
+
+ execsql {SELECT * FROM sqlite_master}
+ execsql BEGIN
+ sqlite3_snapshot_open_blob db main $snap
+ execsql COMMIT;
+ execsql { INSERT INTO t1 VALUES(3); }
+} {}
+
+do_test 2.2 {
+ sqlite3_db_config db NO_CKPT_ON_CLOSE 1
+ db close
+ sqlite3 db test.db
+
+ execsql {SELECT * FROM sqlite_master}
+ execsql BEGIN
+ list [catch { sqlite3_snapshot_open_blob db main $snap } msg] $msg
+} {1 SQLITE_BUSY_SNAPSHOT}
+
+do_test 2.3 {
+ execsql COMMIT
+ sqlite3_snapshot_recover db main
+ execsql BEGIN
+ sqlite3_snapshot_open_blob db main $snap
+ execsql { SELECT * FROM t1 }
+} {1 2}
+
+do_test 2.4 {
+ execsql COMMIT
+ execsql { SELECT * FROM t1 }
+} {1 2 3}
+
+do_test 2.5 {
+ execsql { PRAGMA wal_checkpoint }
+ sqlite3_db_config db NO_CKPT_ON_CLOSE 1
+ db close
+ sqlite3 db test.db
+
+ sqlite3_snapshot_recover db main
+ execsql BEGIN
+ list [catch { sqlite3_snapshot_open_blob db main $snap } msg] $msg
+} {1 SQLITE_BUSY_SNAPSHOT}
+
+#-------------------------------------------------------------------------
+# Check that calling sqlite3_snapshot_recover() does not confuse the
+# pager cache.
+reset_db
+do_execsql_test 3.0 {
+ PRAGMA journal_mode = wal;
+ CREATE TABLE t1(x, y);
+ INSERT INTO t1 VALUES('a', 'b');
+ INSERT INTO t1 VALUES('c', 'd');
+} {wal}
+do_test 3.1 {
+ sqlite3 db2 test.db
+ execsql { INSERT INTO t1 VALUES('e', 'f') } db2
+ db2 close
+ sqlite3_snapshot_recover db main
+} {}
+do_execsql_test 3.2 {
+ SELECT * FROM t1;
+} {a b c d e f}
+
+#-------------------------------------------------------------------------
+# Check that sqlite3_snapshot_recover() returns an error if it is called
+# with an open read-transaction. Or on a database that does not exist. Or
+# on the temp database. Or on a db that is not in wal mode.
+#
+do_test 4.1 {
+ sqlite3_snapshot_recover db main
+} {}
+do_test 4.2 {
+ execsql {
+ BEGIN;
+ SELECT * FROM sqlite_master;
+ }
+ list [catch { sqlite3_snapshot_recover db main } msg] $msg
+} {1 SQLITE_ERROR}
+do_test 4.3 {
+ execsql COMMIT
+ sqlite3_snapshot_recover db main
+} {}
+do_test 4.4 {
+ list [catch { sqlite3_snapshot_recover db aux } msg] $msg
+} {1 SQLITE_ERROR}
+do_test 4.5 {
+ forcedelete test.db2
+ execsql {
+ ATTACH 'test.db2' AS aux;
+ PRAGMA aux.journal_mode = wal;
+ CREATE TABLE aux.t2(x, y);
+ }
+ list [catch { sqlite3_snapshot_recover db aux } msg] $msg
+} {0 {}}
+do_test 4.6 {
+ list [catch { sqlite3_snapshot_recover db temp } msg] $msg
+} {1 SQLITE_ERROR}
+do_test 4.7 {
+ execsql {
+ PRAGMA aux.journal_mode = delete;
+ }
+ list [catch { sqlite3_snapshot_recover db aux } msg] $msg
+} {1 SQLITE_ERROR}
+
+finish_test
+
+
Index: test/snapshot_fault.test
==================================================================
--- test/snapshot_fault.test
+++ test/snapshot_fault.test
@@ -156,9 +156,71 @@
if {$res != "1 2 3 ok"} { error "res is $res" }
}
sqlite3_snapshot_free $::snapshot
}
+
+#-------------------------------------------------------------------------
+# Test the handling of faults that occur within sqlite3_snapshot_recover().
+#
+reset_db
+do_execsql_test 4.0 {
+ PRAGMA journal_mode = wal;
+ CREATE TABLE t1(zzz);
+ INSERT INTO t1 VALUES('abc');
+ INSERT INTO t1 VALUES('def');
+} {wal}
+faultsim_save_and_close
+
+do_test 4.0.1 {
+ faultsim_restore_and_reopen
+ db eval { SELECT * FROM sqlite_master }
+ sqlite3_snapshot_recover db main
+} {}
+db close
+
+do_faultsim_test 4.0 -faults oom* -prep {
+ faultsim_restore_and_reopen
+ db eval { SELECT * FROM sqlite_master }
+} -body {
+ sqlite3_snapshot_recover db main
+} -test {
+ faultsim_test_result {0 {}} {1 SQLITE_NOMEM} {1 SQLITE_IOERR_NOMEM}
+}
+
+# The following test cases contrive to call sqlite3_snapshot_recover()
+# before all pages of the *-shm file have been mapped. This tests an
+# extra branch of error handling logic in snapshot_recover().
+#
+reset_db
+do_execsql_test 4.1.0 {
+ PRAGMA page_size = 512;
+ PRAGMA journal_mode = wal;
+ PRAGMA wal_autocheckpoint = 0;
+ CREATE TABLE t1(zzz);
+ INSERT INTO t1 VALUES(randomblob( 500 * 9500 ));
+ PRAGMA user_version = 211;
+} {wal 0}
+
+do_test 4.1.1 {
+ list [file size test.db-shm] [file size test.db]
+} {98304 512}
+
+faultsim_save_and_close
+do_faultsim_test 4.1 -faults shm* -prep {
+ catch { db2 close }
+ catch { db close }
+ faultsim_restore_and_reopen
+ sqlite3 db2 test.db
+ db2 eval { SELECT * FROM sqlite_master }
+ db eval BEGIN
+ sqlite3_snapshot_get_blob db main
+ db eval COMMIT
+} -body {
+ sqlite3_snapshot_recover db main
+} -test {
+ faultsim_test_result {0 {}} {1 SQLITE_IOERR}
+}
finish_test