$(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 \
$(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 \
--- /dev/null
+/*
+** 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) */
$(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 \
-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
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
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
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
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
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
-7e38c889e7eece521db24c379cb1be7c4c49e2ba4dc04c2a10151793a6bb04df
\ No newline at end of file
+2d5ee3bad19ad0d959d3f6fbc592031b0b764c4111bc2aa1b34df6822250ca7e
\ No newline at end of file
}
#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
*/
{ "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 },
--- /dev/null
+# 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
+