--- /dev/null
+# 2021 November 06
+#
+# 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.
+#
+#***********************************************************************
+#
+
+source [file join [file dirname [info script]] rbu_common.tcl]
+set ::testprefix rbuexlock
+
+db close
+sqlite3_shutdown
+sqlite3_config_uri 1
+
+# Create a simple RBU database. That expects to write to a table:
+#
+# CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
+#
+proc create_rbu {filename} {
+ forcedelete $filename
+ sqlite3 rbu1 $filename
+ rbu1 eval {
+ CREATE TABLE data_t1(a, b, c, rbu_control);
+ INSERT INTO data_t1 VALUES(10, random(), random(), 0);
+ INSERT INTO data_t1 VALUES(20, random(), random(), 0);
+ INSERT INTO data_t1 VALUES(30, random(), random(), 0);
+ INSERT INTO data_t1 VALUES(40, random(), random(), 0);
+ INSERT INTO data_t1 VALUES(50, random(), random(), 0);
+ INSERT INTO data_t1 VALUES(60, random(), random(), 0);
+ INSERT INTO data_t1 VALUES(70, random(), random(), 0);
+ INSERT INTO data_t1 VALUES(80, random(), random(), 0);
+ }
+ rbu1 close
+ return $filename
+}
+
+reset_db
+
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a PRIMARY KEY, b INT, c INT);
+ CREATE INDEX t1b ON t1(b);
+ CREATE INDEX t1c ON t1(c);
+ INSERT INTO t1 VALUES(1, 2, 3);
+}
+create_rbu rbu1.db
+
+do_test 1.1.0 {
+ sqlite3rbu rbu file:test.db?rbu_exclusive_checkpoint=1 rbu1.db
+ rbu step
+} SQLITE_OK
+do_catchsql_test 1.1.1 { SELECT * FROM t1 } {0 {1 2 3}}
+
+do_test 1.2.0 {
+ for {set ii 0} {$ii < 10} {incr ii} {
+ rbu step
+ }
+ rbu step
+} SQLITE_OK
+do_catchsql_test 1.2.1 { SELECT * FROM t1 } {0 {1 2 3}}
+
+do_test 1.3.0 {
+ while {[file exists test.db-wal]==0} {
+ rbu step
+ }
+} {}
+do_catchsql_test 1.3.1 { SELECT * FROM t1 } {1 {database is locked}}
+
+do_test 1.4.0 {
+ rbu step
+} SQLITE_OK
+do_catchsql_test 1.4.1 { SELECT * FROM t1 } {1 {database is locked}}
+
+rbu close
+
+do_test 1.5.0 {
+ file exists test.db-wal
+} {1}
+do_test 1.5.1 {
+ sqlite3rbu rbu file:test.db?rbu_exclusive_checkpoint=1 rbu1.db
+ file exists test.db-wal
+} 1
+do_catchsql_test 1.5.2 { SELECT * FROM t1 } {1 {database is locked}}
+
+do_test 1.6.0 {
+ rbu step
+} SQLITE_OK
+do_catchsql_test 1.6.1 { SELECT * FROM t1 } {1 {database is locked}}
+
+do_test 1.7.0 {
+ while {[rbu step]=="SQLITE_OK"} {}
+ rbu close
+} SQLITE_DONE
+do_catchsql_test 1.7.2 { SELECT count(*) FROM t1 } {0 9}
+
+finish_test
+
+
# define SWAP(TYPE,A,B) {TYPE t=A; A=B; B=t;}
#endif
+/*
+** Name of the URI option that causes RBU to take an exclusive lock as
+** part of the incremental checkpoint operation.
+*/
+#define RBU_EXCLUSIVE_CHECKPOINT "rbu_exclusive_checkpoint"
+
+
/*
** The rbu_state table is used to save the state of a partially applied
** update so that it can be resumed later. The table consists of integer
/*
** Open the database handle and attach the RBU database as "rbu". If an
** error occurs, leave an error code and message in the RBU handle.
+**
+** If argument dbMain is not NULL, then it is a database handle already
+** open on the target database. Use this handle instead of opening a new
+** one.
*/
-static void rbuOpenDatabase(sqlite3rbu *p, int *pbRetry){
+static void rbuOpenDatabase(sqlite3rbu *p, sqlite3 *dbMain, int *pbRetry){
assert( p->rc || (p->dbMain==0 && p->dbRbu==0) );
assert( p->rc || rbuIsVacuum(p) || p->zTarget!=0 );
+ assert( dbMain==0 || rbuIsVacuum(p)==0 );
/* Open the RBU database */
p->dbRbu = rbuOpenDbhandle(p, p->zRbu, 1);
+ p->dbMain = dbMain;
if( p->rc==SQLITE_OK && rbuIsVacuum(p) ){
sqlite3_file_control(p->dbRbu, "main", SQLITE_FCNTL_RBUCNT, (void*)p);
/*
-** Take an EXCLUSIVE lock on the database file.
+** Take an EXCLUSIVE lock on the database file. Return SQLITE_OK if
+** successful, or an SQLite error code otherwise.
*/
-static void rbuLockDatabase(sqlite3rbu *p){
- sqlite3_file *pReal = p->pTargetFd->pReal;
- assert( p->rc==SQLITE_OK );
- p->rc = pReal->pMethods->xLock(pReal, SQLITE_LOCK_SHARED);
- if( p->rc==SQLITE_OK ){
- p->rc = pReal->pMethods->xLock(pReal, SQLITE_LOCK_EXCLUSIVE);
+static int rbuLockDatabase(sqlite3 *db){
+ int rc = SQLITE_OK;
+ sqlite3_file *fd = 0;
+ sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, &fd);
+
+ if( fd->pMethods ){
+ rc = fd->pMethods->xLock(fd, SQLITE_LOCK_SHARED);
+ if( rc==SQLITE_OK ){
+ rc = fd->pMethods->xLock(fd, SQLITE_LOCK_EXCLUSIVE);
+ }
}
+ return rc;
+}
+
+/*
+** Return true if the database handle passed as the only argument
+** was opened with the rbu_exclusive_checkpoint=1 URI parameter
+** specified. Or false otherwise.
+*/
+static int rbuExclusiveCheckpoint(sqlite3 *db){
+ const char *zUri = sqlite3_db_filename(db, 0);
+ return sqlite3_uri_boolean(zUri, RBU_EXCLUSIVE_CHECKPOINT, 0);
}
#if defined(_WIN32_WCE)
** In order to ensure that there are no database readers, an EXCLUSIVE
** lock is obtained here before the *-oal is moved to *-wal.
*/
- rbuLockDatabase(p);
- if( p->rc==SQLITE_OK ){
- rbuFileSuffix3(zBase, zWal);
- rbuFileSuffix3(zBase, zOal);
+ sqlite3 *dbMain = 0;
+ rbuFileSuffix3(zBase, zWal);
+ rbuFileSuffix3(zBase, zOal);
+
+ /* Re-open the databases. */
+ rbuObjIterFinalize(&p->objiter);
+ sqlite3_close(p->dbRbu);
+ sqlite3_close(p->dbMain);
+ p->dbMain = 0;
+ p->dbRbu = 0;
- /* Re-open the databases. */
- rbuObjIterFinalize(&p->objiter);
- sqlite3_close(p->dbRbu);
- sqlite3_close(p->dbMain);
- p->dbMain = 0;
- p->dbRbu = 0;
+ dbMain = rbuOpenDbhandle(p, p->zTarget, 1);
+ if( dbMain ){
+ assert( p->rc==SQLITE_OK );
+ p->rc = rbuLockDatabase(dbMain);
+ }
+ if( p->rc==SQLITE_OK ){
#if defined(_WIN32_WCE)
{
LPWSTR zWideOal;
#else
p->rc = rename(zOal, zWal) ? SQLITE_IOERR : SQLITE_OK;
#endif
+ }
- if( p->rc==SQLITE_OK ){
- rbuOpenDatabase(p, 0);
- rbuSetupCheckpoint(p, 0);
- }
+ if( p->rc!=SQLITE_OK
+ || rbuIsVacuum(p)
+ || rbuExclusiveCheckpoint(dbMain)==0
+ ){
+ sqlite3_close(dbMain);
+ dbMain = 0;
+ }
+
+ if( p->rc==SQLITE_OK ){
+ rbuOpenDatabase(p, dbMain, 0);
+ rbuSetupCheckpoint(p, 0);
}
}
** If this is the case, it will have been checkpointed and deleted
** when the handle was closed and a second attempt to open the
** database may succeed. */
- rbuOpenDatabase(p, &bRetry);
+ rbuOpenDatabase(p, 0, &bRetry);
if( bRetry ){
- rbuOpenDatabase(p, 0);
+ rbuOpenDatabase(p, 0, 0);
}
}
}else if( p->eStage==RBU_STAGE_MOVE ){
/* no-op */
}else if( p->eStage==RBU_STAGE_CKPT ){
+ if( !rbuIsVacuum(p) && rbuExclusiveCheckpoint(p->dbMain) ){
+ /* If the rbu_exclusive_checkpoint=1 URI parameter was specified
+ ** and an incremental checkpoint is being resumed, attempt an
+ ** exclusive lock on the db file. If this fails, so be it. */
+ p->eStage = RBU_STAGE_DONE;
+ rbuLockDatabase(p->dbMain);
+ p->eStage = RBU_STAGE_CKPT;
+ }
rbuSetupCheckpoint(p, pState);
}else if( p->eStage==RBU_STAGE_DONE ){
p->rc = SQLITE_DONE;
const char *zState
){
if( zTarget==0 || zRbu==0 ){ return rbuMisuseError(); }
- /* TODO: Check that zTarget and zRbu are non-NULL */
return openRbuHandle(zTarget, zRbu, zState);
}
-C Update\ssqllimits1.test\sto\saccount\sfor\srecent\schanges\sto\sSQL\sfunction\sstrftime().
-D 2021-11-05T14:13:12.260
+C If\sthe\starget\sdatabase\sURI\spassed\sto\ssqlite3rbu_open()\sis\scontains\sthe\soption\s"rbu_exclusive_checkpoint=1",\shold\san\sexclusive\slock\sfor\sthe\sduration\sof\sany\sincremental\scheckpoint\soperation.
+D 2021-11-05T19:04:01.138
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
F ext/rbu/rbucrash2.test efa143cc94228eb0266d3f1abfbee60a5838a84cef7cc3fcb8c145b74d96fd41
F ext/rbu/rbudiff.test abe895a8d479e4d33acb40e244e3d8e2cd25f55a18dfa8b9f83e13d00073f600
F ext/rbu/rbudor.test e3e8623926012f43eebe51fedf06a102df2640750d971596b052495f2536db20
+F ext/rbu/rbuexlock.test ce99289f37d43553aaae5bf9b0bb6f37015ffcc98a9c561ef597151f522bd3cb
F ext/rbu/rbuexpr.test 10d0420537c3bc7666e576d72adeffe7e86cfbb00dcc30aa9ce096c042415190
F ext/rbu/rbufault.test 2d7f567b79d558f6e093c58808cab4354f8a174e3802f69e7790a9689b3c09f8
F ext/rbu/rbufault2.test c81327a3ac2c385b9b954db3644d4e0df93eeebfc3de9f1f29975a1e73fd3d0c
F ext/rbu/rbuvacuum2.test b8e5b51dc8b2c0153373d024c0936be3f66f9234acbd6d0baab0869d56b14e6b
F ext/rbu/rbuvacuum3.test 8addd82e4b83b4c93fa47428eae4fd0dbf410f8512c186f38e348feb49ba03dc
F ext/rbu/rbuvacuum4.test a78898e438a44803eb2bc897ba3323373c9f277418e2d6d76e90f2f1dbccfd10
-F ext/rbu/sqlite3rbu.c d020eda938e3ed1c5227a7e1425751a9870f5d143542d806f8098c8cfa2eafa0
+F ext/rbu/sqlite3rbu.c 3658f1c6603955c7426952b74a6896337b1f672d326cd565e5af20e18d5744f0
F ext/rbu/sqlite3rbu.h 1dc88ab7bd32d0f15890ea08d23476c4198d3da3056985403991f8c9cd389812
F ext/rbu/test_rbu.c 03f6f177096a5f822d68d8e4069ad8907fe572c62ff2d19b141f59742821828a
F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 029c59cdf9e7dbb431f5d110bc69c3597458edc9b6b009b2e91422de705a19fa
-R 15a965cec05acb9aa4a49131dbd146c1
+P 7bba415f91884a20f665e982376c2c5e91a4431e218c8eefe275be3684f2e59a
+R e2424ac8d0557dd9d5d724897de5e654
U dan
-Z 1ded954f2120e1480d077b81f101d3f7
+Z 27db83bf4d55dcba53397a29f8b19719