]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add experimental extension "snapshotrevert".
authordan <Dan Kennedy>
Wed, 24 Mar 2021 19:54:20 +0000 (19:54 +0000)
committerdan <Dan Kennedy>
Wed, 24 Mar 2021 19:54:20 +0000 (19:54 +0000)
FossilOrigin-Name: 2d5ee3bad19ad0d959d3f6fbc592031b0b764c4111bc2aa1b34df6822250ca7e

Makefile.in
Makefile.msc
ext/misc/snapshotrevert.c [new file with mode: 0644]
main.mk
manifest
manifest.uuid
src/test1.c
test/snapshot_revert.test [new file with mode: 0644]

index a88f7f4cfe672c2f06158878373fa1a6566f82a3..5ad1fd166e9ab36b92a3425c4413cd06d0c58266 100644 (file)
@@ -462,6 +462,7 @@ TESTSRC += \
   $(TOP)/ext/misc/regexp.c \
   $(TOP)/ext/misc/remember.c \
   $(TOP)/ext/misc/series.c \
+  $(TOP)/ext/misc/snapshotrevert.c \
   $(TOP)/ext/misc/spellfix.c \
   $(TOP)/ext/misc/totype.c \
   $(TOP)/ext/misc/unionvtab.c \
index 545719e41b6e0b9adbb00a0a01a3ca515c52dfe5..51cd7534d552ab83684711b18ea969022604e2e6 100644 (file)
@@ -1582,6 +1582,7 @@ TESTEXT = \
   $(TOP)\ext\misc\regexp.c \
   $(TOP)\ext\misc\remember.c \
   $(TOP)\ext\misc\series.c \
+  $(TOP)\ext\misc\snapshotrevert.c \
   $(TOP)\ext\misc\spellfix.c \
   $(TOP)\ext\misc\totype.c \
   $(TOP)\ext\misc\unionvtab.c \
diff --git a/ext/misc/snapshotrevert.c b/ext/misc/snapshotrevert.c
new file mode 100644 (file)
index 0000000..8ac8ed4
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+** 2021 March 25
+**
+** 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.
+**
+*************************************************************************
+**
+** Code to revert a database to a snapshot. The procedure for reverting a 
+** live database to the supplied snapshot is:
+**
+**   1. Open snapshot for reading.
+**   2. Take exclusive CHECKPOINTER lock. 
+**   3.  Take exclusive WRITER lock.
+**   4.   Clobber the current wal-index header with the snapshot.
+**   5.   Set nBackfill to 0. nBackfillAttempted is not modified.
+**   6.   Truncate wal file.
+**   7.  Release write lock.
+**   8. Release checkpoint lock.
+**   9. Close snapshot transaction.
+**
+** This extension exports a single API function:
+**
+**     int sqlite3_snapshot_revert(
+**       sqlite3 *db, 
+**       const char *zDb, 
+**       sqlite3_snapshot *pSnap
+**     );
+**
+** See comments above the implementation of this function below for details.
+*/
+#include <sqlite3.h>
+
+#include <string.h>
+#include <assert.h>
+
+#if !defined(SQLITE_TEST) || defined(SQLITE_ENABLE_SNAPSHOT)
+
+/*
+** Values for the eLock parameter accepted by snapshotRevertLock() and 
+** snapshotRevertUnlock().
+*/
+#define SNAPSHOT_REVERT_CHECKPOINTER  2
+#define SNAPSHOT_REVERT_WRITER        0
+
+static int snapshotRevertLock(sqlite3_file *pFd, int eLock){
+  int f = SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE;
+  return pFd->pMethods->xShmLock(pFd, eLock, 1, f);
+}
+
+static int snapshotRevertUnlock(sqlite3_file *pFd, int eLock){
+  int f = SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE;
+  return pFd->pMethods->xShmLock(pFd, eLock, 1, f);
+}
+
+/*
+** Revert database zDb of connection db to the state it was in when snapshot
+** pSnap was taken. The database handle must be in auto-commit mode and
+** not have an open read or write transction on zDb when this function is
+** called.
+**
+** Return SQLITE_OK if successful, or an SQLite error code otherwise.
+*/
+int sqlite3_snapshot_revert(
+  sqlite3 *db, 
+  const char *zDb, 
+  sqlite3_snapshot *pSnap
+){
+  sqlite3_file *pDbFd = 0;
+  sqlite3_file *pWalFd = 0;
+  int rc;
+  volatile void *pShm = 0;
+  sqlite3_stmt *pCommit = 0;
+  int nLock = 0;                  /* Successful snapshotRevertLock() calls */
+
+  /* Put the db handle in non-auto-commit mode, as required by the
+  ** sqlite3_snapshot_open() API.
+  **
+  ** Also prepare a "COMMIT" command to end the transaction. Such a VM does not
+  ** need to allocate memory or do anything else that is likely to fail,
+  ** so we ignore the error code when it is eventually executed and assume
+  ** that the transaction was successfully closed.  */
+  rc = sqlite3_prepare_v2(db, "COMMIT", -1, &pCommit, 0);
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_exec(db, "BEGIN", 0, 0, 0);
+  }
+  if( rc!=SQLITE_OK ){
+    sqlite3_finalize(pCommit);
+    return rc;
+  }
+
+  /* 1. Open snapshot for reading */
+  rc = sqlite3_snapshot_open(db, zDb, pSnap);
+
+  /* Obtain pointers to the database file handle, the shared-memory mapping,
+  ** and the wal file handle.  */
+  if( rc==SQLITE_OK ){
+    const int op = SQLITE_FCNTL_FILE_POINTER;
+    rc = sqlite3_file_control(db, zDb, op, (void*)&pDbFd);
+  }
+  if( rc==SQLITE_OK ){
+    rc = pDbFd->pMethods->xShmMap(pDbFd, 0, 32*1024, 1, &pShm);
+  }
+  if( rc==SQLITE_OK ){
+    const int op = SQLITE_FCNTL_JOURNAL_POINTER;
+    rc = sqlite3_file_control(db, zDb, op, (void*)&pWalFd);
+  }
+
+  /* 2. Take exclusive CHECKPOINTER lock */
+  if( rc==SQLITE_OK ){
+    rc = snapshotRevertLock(pDbFd, SNAPSHOT_REVERT_CHECKPOINTER);
+    if( rc==SQLITE_OK ) nLock = 1;
+  }
+
+  /* 3. Take exclusive WRITER lock */
+  if( rc==SQLITE_OK ){
+    rc = snapshotRevertLock(pDbFd, SNAPSHOT_REVERT_WRITER);
+    if( rc==SQLITE_OK ) nLock = 2;
+  }
+
+  if( rc==SQLITE_OK ){
+    /* Constants from https://www.sqlite.org/walformat.html#walidxfmt */
+    const int nWalHdrSz = 32;     /* Size of wal file header */
+    const int nIdxHdrSz = 48;     /* Size of each WalIndexHdr */
+    const int nFrameHdrSz = 24;   /* Size of each frame header */
+    const int iHdrOff1 = 0;       /* Offset of first WalIndexHdr */
+    const int iHdrOff2 = 48;      /* Offset of second WalIndexHdr */
+    const int iBackfillOff = 96;  /* offset of 32-bit nBackfill value  */
+    const int iPgszOff = 14;      /* Offset of 16-bit page-size value */
+    const int iMxFrameOff = 16;   /* Offset of 32-bit mxFrame value */
+
+    unsigned char *a = (unsigned char*)pShm;
+    int pgsz;                     /* Database page size */
+    int mxFrame;                  /* Valid frames in wal file after revert */
+    sqlite3_int64 szWal;          /* Size in bytes to truncate wal file to */
+
+    /* 4. Clobber the current wal-index header with the snapshot. */
+    memcpy(&a[iHdrOff1], pSnap, nIdxHdrSz);
+    memcpy(&a[iHdrOff2], pSnap, nIdxHdrSz);
+
+    /* 5. Set nBackfill to 0. nBackfillAttempted is not modified. */
+    *(int*)&a[iBackfillOff] = 0;
+
+    /* 6. Truncate the wal file */
+    assert( sizeof(unsigned short int)==2 );
+    pgsz = *(unsigned short int*)&a[iPgszOff];
+    if( pgsz==1 ) pgsz = 65536;
+    mxFrame = *(int*)&a[iMxFrameOff];
+    szWal = (sqlite3_int64)mxFrame * (pgsz + nFrameHdrSz) + nWalHdrSz;
+    rc = pWalFd->pMethods->xTruncate(pWalFd, szWal);
+  }
+
+  /* Steps 8 and 9 - drop locks if they were acquired */
+  if( nLock==2 ) snapshotRevertUnlock(pDbFd, SNAPSHOT_REVERT_WRITER);
+  if( nLock>0 ) snapshotRevertUnlock(pDbFd, SNAPSHOT_REVERT_CHECKPOINTER);
+
+  /* End the snapshot transaction, if one was opened. */
+  sqlite3_step(pCommit);
+  sqlite3_finalize(pCommit);
+
+  return rc;
+}
+
+#endif /* !defined(SQLITE_TEST) || defined(SQLITE_ENABLE_SNAPSHOT) */
diff --git a/main.mk b/main.mk
index 7b3f3fd3ee87857e0e401424a8adfcfd4014781e..0cdfb19b42c411c10df2f97ffd84a252b80cf08b 100644 (file)
--- a/main.mk
+++ b/main.mk
@@ -379,6 +379,7 @@ TESTSRC += \
   $(TOP)/ext/misc/regexp.c \
   $(TOP)/ext/misc/remember.c \
   $(TOP)/ext/misc/series.c \
+  $(TOP)/ext/misc/snapshotrevert.c \
   $(TOP)/ext/misc/spellfix.c \
   $(TOP)/ext/misc/totype.c \
   $(TOP)/ext/misc/unionvtab.c \
index b0feca2b3a3b1b0e7f7f7152482e57b0ecd1f52b..e7d114e40d65a5902cd7bd358abf4fd9fb0d5b2b 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,11 +1,11 @@
-C Merge\sversion\s3.35.0\schanges\sinto\sthe\sbegin-concurrent\sbranch.
-D 2021-03-12T16:18:00.571
+C Add\sexperimental\sextension\s"snapshotrevert".
+D 2021-03-24T19:54:20.842
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
-F Makefile.in fe6cc1db11e02b308f3ab0ec2504344697b9eaaa410fa73f48d16a143462e5d3
+F Makefile.in 86edce9a85fd02577d58b51fffa2a1529953f4b03669ca6ea3690a62068e17a5
 F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
-F Makefile.msc 6b5428cef2af1288e02faeb602dabe68fbf2de7bc8a8e29c7299778ee08cd35c
+F Makefile.msc f56461848060f1b6243ac4c709defbe6baaf01649c2af5671af3a95f03a4b1c1
 F README.md 1514a365ffca3c138e00c5cc839906108a01011a6b082bad19b09781e3aa498a
 F VERSION 92f3e4c5cdee6f0779aef1eae857dfc21d0eabb1f2af169dc90e63cd76b15bb2
 F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
@@ -324,6 +324,7 @@ F ext/misc/series.c c6bd5d249e5199a1b55aeee4d0e6576ff3a68702fc475dbd64503a329035
 F ext/misc/sha1.c c8f2253c8792ffab9517695ea7d88c079f0395a5505eefef5c8198fe184ed5ac
 F ext/misc/shathree.c e984f31731de4cf302a0386be5fe664580f63d8204c47b9b41cc4b997745f9ec
 F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52
+F ext/misc/snapshotrevert.c 8cc92c76cab8c9eb50f362427f634f1f20b1d553312a534e1077468d9bd0ebc4
 F ext/misc/spellfix.c 94df9bbfa514a563c1484f684a2df3d128a2f7209a84ca3ca100c68a0163e29f
 F ext/misc/sqlar.c 0ace5d3c10fe736dc584bf1159a36b8e2e60fab309d310cd8a0eecd9036621b6
 F ext/misc/stmt.c 35063044a388ead95557e4b84b89c1b93accc2f1c6ddea3f9710e8486a7af94a
@@ -469,7 +470,7 @@ F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865
 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
-F main.mk 4d3bf0ef1b454e7308ea7307b6a50f480e242cfdccfff63a42a3a450ae22e186
+F main.mk 17828fe7132a6471d6fa17fd104f34faef3a1b63f5eb1c2901ea70a0c76d97e3
 F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
@@ -557,7 +558,7 @@ F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a3
 F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1
 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
 F src/tclsqlite.c 986b6391f02cd9b53c1d688be55899f6ffddeb8e8014cd83c1b73ff912579a71
-F src/test1.c 6de6fac373fbf2533e8e772543398cb826d09e6aa38e199a496e09e2a4d1c75d
+F src/test1.c a8da2ac9bb15811cbf03d55f21dee4f4bc235722e3c2d8ec4bc1a2a421cc6709
 F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5
 F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644
 F src/test4.c 7c4420e01c577b5c4add2cb03119743b1a357543d347773b9e717195ea967159
@@ -1402,6 +1403,7 @@ F test/snapshot2.test 8d6ff5dd9cc503f6e12d408a30409c3f9c653507b24408d9cd7195931c
 F test/snapshot3.test 8744313270c55f6e18574283553d3c5c5fe4c5970585663613a0e75c151e599b
 F test/snapshot4.test d4e9347ef2fcabc491fc893506c7bbaf334da3be111d6eb4f3a97cc623b78322
 F test/snapshot_fault.test f6c5ef7cb93bf92fbb4e864ecc5c87df7d3a250064838822db5b4d3a5563ede4
+F test/snapshot_revert.test c7bf8ca03fcdecd1e814698a0a3b0625d705e842fe7018c3b6d2b8300961b8a0
 F test/snapshot_up.test a0a29c4cf33475fcef07c3f8e64af795e24ab91b4cc68295863402a393cdd41c
 F test/soak.test 18944cf21b94a7fe0df02016a6ee1e9632bc4e8d095a0cb49d95e15d5cca2d5c
 F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087
@@ -1925,7 +1927,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 79d44ebd5313fe261d7b51826deb119ac6d46388f955457c020ce623b9a3e53d acd63062eb06748bfe9e4886639e4f2b54ea6a496a83f10716abbaba4115500b
-R 4673fbd71d3eb75f335042ef512ee328
-U drh
-Z a1d9d8527ce80fdf72331a27ec483abf
+P 7e38c889e7eece521db24c379cb1be7c4c49e2ba4dc04c2a10151793a6bb04df
+R 2122e6e8bbc48457bd5128ae227294a2
+U dan
+Z 4f7dd46938d9c48c2bba28374f57caaf
index 6be944fd321bc1383be7cccb374b4f893ab2fd91..c8c1520ff0fad5a73490a91df0d5b09ecab95474 100644 (file)
@@ -1 +1 @@
-7e38c889e7eece521db24c379cb1be7c4c49e2ba4dc04c2a10151793a6bb04df
\ No newline at end of file
+2d5ee3bad19ad0d959d3f6fbc592031b0b764c4111bc2aa1b34df6822250ca7e
\ No newline at end of file
index 99bfecb0f8db55b61640c285ae1b802cc42cfa5a..bc255c3981c6af8b0f6ad32f203f6df2c3990842 100644 (file)
@@ -2574,6 +2574,41 @@ static int SQLITE_TCLAPI test_snapshot_cmp_blob(
 }
 #endif /* SQLITE_ENABLE_SNAPSHOT */
 
+#ifdef SQLITE_ENABLE_SNAPSHOT
+/*
+** Usage: sqlite3_snapshot_revert DB DBNAME SNAPSHOT
+*/
+static int SQLITE_TCLAPI test_snapshot_revert(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  extern int sqlite3_snapshot_revert(sqlite3*, const char*, sqlite3_snapshot*);
+  int rc;
+  sqlite3 *db;
+  char *zName;
+  sqlite3_snapshot *pSnapshot;
+
+  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]);
+  pSnapshot = (sqlite3_snapshot*)sqlite3TestTextToPtr(Tcl_GetString(objv[3]));
+
+  rc = sqlite3_snapshot_revert(db, zName, pSnapshot);
+  if( rc!=SQLITE_OK ){
+    Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+    return TCL_ERROR;
+  }else{
+    Tcl_ResetResult(interp);
+  }
+  return TCL_OK;
+}
+#endif
+
 /*
 ** Usage: sqlite3_delete_database FILENAME
 */
@@ -8525,6 +8560,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
      { "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 },
+     { "sqlite3_snapshot_revert", test_snapshot_revert, 0 },
 #endif
      { "sqlite3_delete_database", test_delete_database, 0 },
      { "sqlite3_wal_info", test_wal_info, 0 },
diff --git a/test/snapshot_revert.test b/test/snapshot_revert.test
new file mode 100644 (file)
index 0000000..58ae5e8
--- /dev/null
@@ -0,0 +1,157 @@
+# 2021 March 25
+#
+# 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.
+#
+#***********************************************************************
+#
+# Tests for the sqlite3_snapshot_revert() extension.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+ifcapable !snapshot {finish_test; return}
+set testprefix snapshot_revert
+
+do_execsql_test 1.0 {
+  CREATE TABLE t1(a, b, c);
+  PRAGMA journal_mode = wal;
+  INSERT INTO t1 VALUES(1, 2, 3);
+  INSERT INTO t1 VALUES(4, 5, 6);
+  INSERT INTO t1 VALUES(7, 8, 9);
+} {wal}
+
+do_test 1.1 {
+  execsql BEGIN
+  set ::snap1 [sqlite3_snapshot_get db main]
+  execsql COMMIT
+} {}
+
+do_execsql_test 1.2 {
+  INSERT INTO t1 VALUES(10, 11, 12);
+  INSERT INTO t1 VALUES(13, 14, 15);
+}
+
+sqlite3 db2 test.db
+do_execsql_test -db db2 1.3 {
+  SELECT * FROM t1
+} {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15}
+
+do_test 1.4 {
+  sqlite3_snapshot_revert db main $::snap1
+} {}
+sqlite3_snapshot_free $::snap1
+
+do_execsql_test -db db2 1.5 {
+  SELECT * FROM t1
+} {1 2 3 4 5 6 7 8 9}
+db2 close
+
+db_save_and_close
+db_restore_and_reopen
+
+do_execsql_test 1.6 {
+  SELECT * FROM t1
+} {1 2 3 4 5 6 7 8 9}
+
+#-------------------------------------------------------------------------
+# Test some error conditions:
+#
+#   2.1.* Error - already in non-auto-commit mode.
+#   2.2.* Error - snapshot has already been checkpointed away.
+#   2.3.* Error - cannot get WRITER lock.
+#   2.4.* Check that CHECKPOINTER is released if cannot get WRITER
+#
+reset_db
+do_execsql_test 2.0.1 {
+  PRAGMA auto_vacuum = 0;
+  PRAGMA journal_mode = wal;
+  CREATE TABLE x1(x, y);
+  INSERT INTO x1 VALUES('A', 'B'), ('C', 'D');
+} {wal}
+do_test 2.0.2 {
+  execsql BEGIN
+  set ::snap1 [sqlite3_snapshot_get db main]
+  execsql COMMIT
+} {}
+
+do_test 2.1.0 {
+  execsql BEGIN
+  list [catch { sqlite3_snapshot_revert db main $::snap1 } msg] $msg
+} {1 SQLITE_ERROR}
+do_test 2.1.1 {
+  execsql COMMIT
+  list [catch { sqlite3_snapshot_revert db main $::snap1 } msg] $msg
+} {0 {}}
+
+do_test 2.2.0 {
+  execsql {
+    INSERT INTO x1 VALUES('E', 'F');
+    DELETE FROM x1 WHERE x='A';
+    PRAGMA wal_checkpoint;
+  }
+  list [catch { sqlite3_snapshot_revert db main $::snap1 } msg] $msg
+} {1 SQLITE_ERROR_SNAPSHOT}
+sqlite3_snapshot_free $::snap1
+
+sqlite3 db2 test.db
+do_test 2.3.0 {
+  execsql {
+    INSERT INTO x1 VALUES('G', 'H');
+    BEGIN;
+  }
+  set ::snap1 [sqlite3_snapshot_get db main]
+  execsql {
+    DELETE FROM x1 WHERE x='C';
+    COMMIT;
+  }
+} {}
+do_test 2.3.1 {
+  execsql { BEGIN EXCLUSIVE } db2
+  list [catch { sqlite3_snapshot_revert db main $::snap1 } msg] $msg
+} {1 SQLITE_BUSY}
+do_execsql_test 2.3.2 {
+  SELECT * FROM x1
+} {E F G H}
+do_test 2.3.3 {
+  execsql { COMMIT } db2
+  list [catch { sqlite3_snapshot_revert db main $::snap1 } msg] $msg
+} {0 {}}
+do_execsql_test 2.3.4 {
+  SELECT * FROM x1
+} {C D E F G H}
+sqlite3_snapshot_free $::snap1
+
+do_test 2.4.0 {
+  execsql {
+    BEGIN;
+  }
+  set ::snap1 [sqlite3_snapshot_get db main]
+  execsql {
+    INSERT INTO x1 VALUES('I', 'J');
+    DELETE FROM x1 WHERE x IN ('C', 'E');
+    COMMIT;
+  }
+} {}
+do_execsql_test -db db2 2.4.1 {
+  BEGIN EXCLUSIVE;
+  SELECT * FROM x1;
+} {G H I J}
+do_test 2.4.2 {
+  list [catch { sqlite3_snapshot_revert db main $::snap1 } msg] $msg
+} {1 SQLITE_BUSY}
+do_execsql_test -db db2 2.4.3 {
+  COMMIT;
+  PRAGMA wal_checkpoint;
+} {0 2 2}
+sqlite3_snapshot_free $::snap1
+
+
+db close
+db2 close
+finish_test
+