From: dan Date: Wed, 24 Mar 2021 19:54:20 +0000 (+0000) Subject: Add experimental extension "snapshotrevert". X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=d5d7eec07f28b1a84e332b8305cc7dc9db6d0291;p=thirdparty%2Fsqlite.git Add experimental extension "snapshotrevert". FossilOrigin-Name: 2d5ee3bad19ad0d959d3f6fbc592031b0b764c4111bc2aa1b34df6822250ca7e --- diff --git a/Makefile.in b/Makefile.in index a88f7f4cfe..5ad1fd166e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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 \ diff --git a/Makefile.msc b/Makefile.msc index 545719e41b..51cd7534d5 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -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 index 0000000000..8ac8ed422c --- /dev/null +++ b/ext/misc/snapshotrevert.c @@ -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 + +#include +#include + +#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 7b3f3fd3ee..0cdfb19b42 100644 --- 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 \ diff --git a/manifest b/manifest index b0feca2b3a..e7d114e40d 100644 --- 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 diff --git a/manifest.uuid b/manifest.uuid index 6be944fd32..c8c1520ff0 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7e38c889e7eece521db24c379cb1be7c4c49e2ba4dc04c2a10151793a6bb04df \ No newline at end of file +2d5ee3bad19ad0d959d3f6fbc592031b0b764c4111bc2aa1b34df6822250ca7e \ No newline at end of file diff --git a/src/test1.c b/src/test1.c index 99bfecb0f8..bc255c3981 100644 --- a/src/test1.c +++ b/src/test1.c @@ -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 index 0000000000..58ae5e81b7 --- /dev/null +++ b/test/snapshot_revert.test @@ -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 +