]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Allow sqlite3_snapshot_open() to be called to change the snapshot after a exp-snapshot-open
authordan <dan@noemail.net>
Mon, 6 Aug 2018 17:12:36 +0000 (17:12 +0000)
committerdan <dan@noemail.net>
Mon, 6 Aug 2018 17:12:36 +0000 (17:12 +0000)
read transaction is already open on database.

FossilOrigin-Name: 051ac0152048ef52723196c26ca5f2629dafb782aec1c66fc30531bf54335043

manifest
manifest.uuid
src/main.c
src/pager.c
src/pager.h
src/sqlite.h.in
src/test1.c
src/wal.c
src/wal.h
test/snapshot.test
test/snapshot_up.test [new file with mode: 0644]

index 7ddefd3b49732838c0a78abc6ba0efa2d01056a3..ab55cf5445b1e7008cc9ca0d750a33244275a42c 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Enhance\sthe\sedit()\sfunction\sso\sthat\sit\sconverts\stext\sfrom\s\\r\\n\sback\sinto\s\\n\nonly\sif\sthe\soriginal\sunedited\scopy\scontained\sno\s\\r\\n\svalues.
-D 2018-08-06T02:08:53.886
+C Allow\ssqlite3_snapshot_open()\sto\sbe\scalled\sto\schange\sthe\ssnapshot\safter\sa\nread\stransaction\sis\salready\sopen\son\sdatabase.
+D 2018-08-06T17:12:36.835
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F Makefile.in 0a3a6c81e6fcb969ff9106e882f0a08547014ba463cb6beca4c4efaecc924ee6
@@ -462,7 +462,7 @@ F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
 F src/insert.c 894594952bcda1dc6e1549871e4022517563545ffc7a3f4e9e5f3faa788893fd
 F src/legacy.c 134ab3e3fae00a0f67a5187981d6935b24b337bcf0f4b3e5c9fa5763da95bf4e
 F src/loadext.c 6aae5739198d96c51ae6eb97c4a5b1744c22ed7a5a565a5399a717780d48a36b
-F src/main.c dc023f468eda20aed1fb7c300673cbb40617607b5771840e4229ec239dade250
+F src/main.c 3c7f2159687af1b1b0d0e0776f888adb94534e0bc52e7a1a1509f83e24cb6297
 F src/malloc.c 07295435093ce354c6d9063ac05a2eeae28bd251d2e63c48b3d67c12c76f7e18
 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
 F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de
@@ -485,8 +485,8 @@ F src/os_setup.h 0dbaea40a7d36bf311613d31342e0b99e2536586
 F src/os_unix.c d9cf5ae0c79f31019d8325e8736c83914aeed64d8327a8d91a62b6439b748948
 F src/os_win.c 070cdbb400097c6cda54aa005356095afdc2f3ee691d17192c54724ef146a971
 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
-F src/pager.c 76d29b8a960dcb8b67210f095899d91e4a90673a6674ea58cfd1115b705a7fb9
-F src/pager.h c571b064df842ec8f2e90855dead9acf4cbe0d1b2c05afe0ef0d0145f7fd0388
+F src/pager.c 705de01dff9c3df9739c37a6d3b58cd2b1734fdabcef829b16cdc7721a9eeaa4
+F src/pager.h ecc554a55bc55d1c4ba5e17137b72e238e00bd81e72ff2662d8b9c8c10ae3963
 F src/parse.y 704c94624d41d7d46a5467574130e55aa8029a563f4df538f0121475eae46e34
 F src/pcache.c 135ef0bc6fb2e3b7178d49ab5c9176254c8a691832c1bceb1156b2fbdd0869bd
 F src/pcache.h 072f94d29281cffd99e46c1539849f248c4b56ae7684c1f36626797fee375170
@@ -500,7 +500,7 @@ F src/resolve.c 797088662ed61102485e3070ba3b3f7828bd5ef6a588223ba6865d77d52f6cea
 F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
 F src/select.c ae7396a314cc1bb1d767947cd57094e3a9ffcbb155ebc1b1c391e028c44a9a04
 F src/shell.c.in 6e0aad854be738a5d0368940459399be211e9ac43aebe92bb9ed46cfe38d0e1f
-F src/sqlite.h.in c6451bb876adced3aba5b1682c6317d215c5eceaba21a6ce979e71a0b8d0bf95
+F src/sqlite.h.in 82b5768e36ce796ecf93c73bd88bad99def831ce7d470138e213ac693bf4ceab
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h 9887b27e69c01e79c2cbe74ef73bf01af5b5703d6a7f0a4371e386d7249cb1c7
 F src/sqliteInt.h a5d212bb7ae5cfc0540af6fb09eee2092a45fe083fac4191ee64ff70e7d4d78a
@@ -508,7 +508,7 @@ F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6
 F src/status.c 46e7aec11f79dad50965a5ca5fa9de009f7d6bde08be2156f1538a0a296d4d0e
 F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34
 F src/tclsqlite.c e0bf71a6d24b8c23393c000abffab05979bbca2a72d0b0f79260e2cf1527fda5
-F src/test1.c 55424c026dd93c06ad84ff4e46cec64aa3e12e767d50c31886e6a69ee53fe81e
+F src/test1.c 31c491ccb536bd9916a084e732ffe783b3c8973f2586d5a56aed0e3a9701dfff
 F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5
 F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644
 F src/test4.c 18ec393bb4d0ad1de729f0b94da7267270f3d8e6
@@ -580,8 +580,8 @@ F src/vdbesort.c 731a09e5cb9e96b70c394c1b7cf3860fbe84acca7682e178615eb941a3a0ef2
 F src/vdbetrace.c 79d6dbbc479267b255a7de8080eee6e729928a0ef93ed9b0bfa5618875b48392
 F src/vtab.c 678992ac8ec677a3f9b08126aaf891441083805e3b42574e3654d44538381c14
 F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
-F src/wal.c d44a0811afd2155b1157c38b33141d4ac028fda6232485bed664015bb05819ca
-F src/wal.h 8de5d2d3de0956d6f6cb48c83a4012d5f227b8fe940f3a349a4b7e85ebcb492a
+F src/wal.c d0d541116c378937fad99d89737cf36cfc67ff94ba1d29b2bf93bc7333e07e25
+F src/wal.h 606292549f5a7be50b6227bd685fa76e3a4affad71bb8ac5ce4cb5c79f6a176a
 F src/walker.c ba7225773931760cf60bf22f34d0cce2588df7ce5ce0f215a52eb88234b55ac4
 F src/where.c 155809967fbab889374dedf970ea6561b8fb519fcb165d6ba00776552ecc5cde
 F src/whereInt.h b90ef9b9707ef750eab2a7a080c48fb4900315033274689def32d0cf5a81ebe4
@@ -1269,10 +1269,11 @@ F test/skipscan2.test ef143c6e4a5ba4f19c1d1e3f517811f7942bdf2142736cc568feb34e0b
 F test/skipscan3.test ec5bab3f81c7038b43450e7b3062e04a198bdbb5
 F test/skipscan5.test 67817a4b6857c47e0e33ba3e506da6f23ef68de2
 F test/skipscan6.test 0b4cd1b4ac9f84d91454df513c99a4932fa07e8f27b8049bea605068b3e34ac7
-F test/snapshot.test 85735bd997a4f6d710140c28fd860519a299649f
+F test/snapshot.test fef12fc5c16ff21c4748509401cfba7d9a3d91156f1bfe23fb881d3bfc65ddfe
 F test/snapshot2.test 925e42427e923262db63c9d7155183f889e3e99feaedec4075f659e51608344f
 F test/snapshot3.test 9719443594a04778861bd20d12596c5f880af177d6cd62f111da3198cafc6096
 F test/snapshot_fault.test 52c5e97ebd218846a8ae2da4d147d3e77d71f963
+F test/snapshot_up.test b778a04561a67b8bfde828f473a8d31dbde23e3f648e36237e0369421e08f23c
 F test/soak.test 18944cf21b94a7fe0df02016a6ee1e9632bc4e8d095a0cb49d95e15d5cca2d5c
 F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087
 F test/sort.test c2adc635c2564241fefec0b3a68391ef6868fd3b
@@ -1754,7 +1755,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 1caaaaa70f21fe71fbe0af227eea8d1367870e2575eedf248cc2a0b515783390
-R 599391bf767ca60a524e57a614c06c9d
-U drh
-Z e1c623c52da1d23ca607e4f314a88bce
+P 20c995d3f0f4de5410962172cb59da0f25edf0c62e199420186cc59ea874e981
+R 462315bcd4a2eb76f7dc221ad2f185d1
+T *branch * exp-snapshot-open
+T *sym-exp-snapshot-open *
+T -sym-trunk *
+U dan
+Z f721373f748dbb5b0d3c5c4f1daf5230
index 1b923067c8d6ef64dde9da6f2d2c01face919aa8..ec1e91062fac5fb5dada2bfb7c66ca2c56b3797f 100644 (file)
@@ -1 +1 @@
-20c995d3f0f4de5410962172cb59da0f25edf0c62e199420186cc59ea874e981
\ No newline at end of file
+051ac0152048ef52723196c26ca5f2629dafb782aec1c66fc30531bf54335043
\ No newline at end of file
index 4ae5c7ecdd4826ecc9440df2863f969f6c74f101..4ea7c20779420e2f99f6fd60d7ede6bd9687fb22 100644 (file)
@@ -4209,11 +4209,29 @@ int sqlite3_snapshot_open(
     iDb = sqlite3FindDbName(db, zDb);
     if( iDb==0 || iDb>1 ){
       Btree *pBt = db->aDb[iDb].pBt;
-      if( 0==sqlite3BtreeIsInReadTrans(pBt) ){
-        rc = sqlite3PagerSnapshotOpen(sqlite3BtreePager(pBt), pSnapshot);
+      if( sqlite3BtreeIsInTrans(pBt)==0 ){
+        Pager *pPager = sqlite3BtreePager(pBt);
+        int bUnlock = 0;
+        if( sqlite3BtreeIsInReadTrans(pBt) ){
+          if( db->nVdbeActive==0 ){
+            rc = sqlite3PagerSnapshotCheck(pPager, pSnapshot);
+            if( rc==SQLITE_OK ){
+              bUnlock = 1;
+              rc = sqlite3BtreeCommit(pBt);
+            }
+          }
+        }else{
+          rc = SQLITE_OK;
+        }
+        if( rc==SQLITE_OK ){
+          rc = sqlite3PagerSnapshotOpen(pPager, pSnapshot);
+        }
         if( rc==SQLITE_OK ){
           rc = sqlite3BtreeBeginTrans(pBt, 0, 0);
-          sqlite3PagerSnapshotOpen(sqlite3BtreePager(pBt), 0);
+          sqlite3PagerSnapshotOpen(pPager, 0);
+        }
+        if( bUnlock ){
+          sqlite3PagerSnapshotUnlock(pPager);
         }
       }
     }
index 9fafa17cb015a1d2164ff4dc6200703dee6ccb1a..a7d076ab0b04f30fe80846cdccc20b7acef8d5f4 100644 (file)
@@ -7653,6 +7653,38 @@ int sqlite3PagerSnapshotRecover(Pager *pPager){
   }
   return rc;
 }
+
+/*
+** The caller currently has a read transaction open on the database.
+** If this is not a WAL database, SQLITE_ERROR is returned. Otherwise,
+** this function takes a SHARED lock on the CHECKPOINTER slot and then
+** checks if the snapshot passed as the second argument is still 
+** available. If so, SQLITE_OK is returned.
+**
+** If the snapshot is not available, SQLITE_ERROR is returned. Or, if
+** the CHECKPOINTER lock cannot be obtained, SQLITE_BUSY. If any error
+** occurs (any value other than SQLITE_OK is returned), the CHECKPOINTER
+** lock is released before returning.
+*/
+int sqlite3PagerSnapshotCheck(Pager *pPager, sqlite3_snapshot *pSnapshot){
+  int rc;
+  if( pPager->pWal ){
+    rc = sqlite3WalSnapshotCheck(pPager->pWal, pSnapshot);
+  }else{
+    rc = SQLITE_ERROR;
+  }
+  return rc;
+}
+
+/*
+** Release a lock obtained by an earlier successful call to
+** sqlite3PagerSnapshotCheck().
+*/
+void sqlite3PagerSnapshotUnlock(Pager *pPager){
+  assert( pPager->pWal );
+  return sqlite3WalSnapshotUnlock(pPager->pWal);
+}
+
 #endif /* SQLITE_ENABLE_SNAPSHOT */
 #endif /* !SQLITE_OMIT_WAL */
 
index 730e366fbb09c01ec3c26a03c30f978174de1a2f..5b07a226b8223575475b9ef17713aa82349975f2 100644 (file)
@@ -186,6 +186,8 @@ int sqlite3PagerSharedLock(Pager *pPager);
   int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot);
   int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot);
   int sqlite3PagerSnapshotRecover(Pager *pPager);
+  int sqlite3PagerSnapshotCheck(Pager *pPager, sqlite3_snapshot *pSnapshot);
+  void sqlite3PagerSnapshotUnlock(Pager *pPager);
 # endif
 #else
 # define sqlite3PagerUseWal(x,y) 0
index bdecac5d10ccfacdfd08c9e5ee8cfee7ef052e89..5222cee39196e37523d46144b43b6b5e2b9e85b0 100644 (file)
@@ -9035,22 +9035,33 @@ SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
 ** CAPI3REF: Start a read transaction on an historical snapshot
 ** METHOD: sqlite3_snapshot
 **
-** ^The [sqlite3_snapshot_open(D,S,P)] interface starts a
-** read transaction for schema S of
-** [database connection] D such that the read transaction
-** refers to historical [snapshot] P, rather than the most
-** recent change to the database.
-** ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK on success
-** or an appropriate [error code] if it fails.
-**
-** ^In order to succeed, a call to [sqlite3_snapshot_open(D,S,P)] must be
-** the first operation following the [BEGIN] that takes the schema S
-** out of [autocommit mode].
-** ^In other words, schema S must not currently be in
-** a transaction for [sqlite3_snapshot_open(D,S,P)] to work, but the
-** database connection D must be out of [autocommit mode].
-** ^A [snapshot] will fail to open if it has been overwritten by a
-** [checkpoint].
+** ^The [sqlite3_snapshot_open(D,S,P)] interface either starts a new read 
+** transaction or upgrades an existing one for schema S of 
+** [database connection] D such that the read transaction refers to 
+** historical [snapshot] P, rather than the most recent change to the 
+** database. ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK 
+** on success or an appropriate [error code] if it fails.
+**
+** ^In order to succeed, the database connection must not be in 
+** [autocommit mode] when [sqlite3_snapshot_open(D,S,P)] is called. If there
+** is already a read transaction open on schema S, then the database handle
+** must have no active statements (SELECT statements that have been passed
+** to sqlite3_step() but not sqlite3_reset() or sqlite3_finalize()). 
+** SQLITE_ERROR is returned if either of these conditions is violated, or
+** if schema S does not exist, or if the snapshot object is invalid.
+**
+** ^A call to sqlite3_snapshot_open() will fail to open if the specified
+** snapshot has been overwritten by a [checkpoint]. In this case 
+** SQLITE_BUSY_SNAPSHOT is returned.
+**
+** If there is already a read transaction open when this function is 
+** invoked, then the same read transaction remains open (on the same
+** database snapshot) if SQLITE_ERROR, SQLITE_BUSY or SQLITE_BUSY_SNAPSHOT
+** is returned. If another error code - for example SQLITE_PROTOCOL or an
+** SQLITE_IOERR error code - is returned, then the final state of the
+** read transaction is undefined. If SQLITE_OK is returned, then the 
+** read transaction is now open on database snapshot P.
+**
 ** ^(A call to [sqlite3_snapshot_open(D,S,P)] will fail if the
 ** database connection D does not know that the database file for
 ** schema S is in [WAL mode].  A database connection might not know
index 93c71aa3202413e3c38c5eb5251ccd8b75f8d035..7f301714bd8298a0d459118b17d33d2312b9a302 100644 (file)
@@ -2393,6 +2393,8 @@ static int SQLITE_TCLAPI test_snapshot_open(
   if( rc!=SQLITE_OK ){
     Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
     return TCL_ERROR;
+  }else{
+    Tcl_ResetResult(interp);
   }
   return TCL_OK;
 }
index 885a2bd733468b2fca32187bfc096b96d02fd051..0ea327161b33f59ebc2bcf06c8a69f6260fcd8a4 100644 (file)
--- a/src/wal.c
+++ b/src/wal.c
@@ -3769,6 +3769,43 @@ int sqlite3_snapshot_cmp(sqlite3_snapshot *p1, sqlite3_snapshot *p2){
   if( pHdr1->mxFrame>pHdr2->mxFrame ) return +1;
   return 0;
 }
+
+/*
+** The caller currently has a read transaction open on the database.
+** This function takes a SHARED lock on the CHECKPOINTER slot and then
+** checks if the snapshot passed as the second argument is still 
+** available. If so, SQLITE_OK is returned.
+**
+** If the snapshot is not available, SQLITE_ERROR is returned. Or, if
+** the CHECKPOINTER lock cannot be obtained, SQLITE_BUSY. If any error
+** occurs (any value other than SQLITE_OK is returned), the CHECKPOINTER
+** lock is released before returning.
+*/
+int sqlite3WalSnapshotCheck(Wal *pWal, sqlite3_snapshot *pSnapshot){
+  int rc;
+  rc = walLockShared(pWal, WAL_CKPT_LOCK);
+  if( rc==SQLITE_OK ){
+    WalIndexHdr *pNew = (WalIndexHdr*)pSnapshot;
+    if( memcmp(pNew->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt))
+     || pNew->mxFrame<walCkptInfo(pWal)->nBackfillAttempted
+    ){
+      rc = SQLITE_BUSY_SNAPSHOT;
+      walUnlockShared(pWal, WAL_CKPT_LOCK);
+    }
+  }
+  return rc;
+}
+
+/*
+** Release a lock obtained by an earlier successful call to
+** sqlite3WalSnapshotCheck().
+*/
+void sqlite3WalSnapshotUnlock(Wal *pWal){
+  assert( pWal );
+  walUnlockShared(pWal, WAL_CKPT_LOCK);
+}
+
+
 #endif /* SQLITE_ENABLE_SNAPSHOT */
 
 #ifdef SQLITE_ENABLE_ZIPVFS
index d97300a684762092752d0c278916927ba9fd290e..16106074813527abd218c75ecf6f972d9f52100c 100644 (file)
--- a/src/wal.h
+++ b/src/wal.h
@@ -132,6 +132,8 @@ int sqlite3WalHeapMemory(Wal *pWal);
 int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot);
 void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot);
 int sqlite3WalSnapshotRecover(Wal *pWal);
+int sqlite3WalSnapshotCheck(Wal *pWal, sqlite3_snapshot *pSnapshot);
+void sqlite3WalSnapshotUnlock(Wal *pWal);
 #endif
 
 #ifdef SQLITE_ENABLE_ZIPVFS
index 99d3ed47c366387e3ef46789ea0e214a20fd325f..10400523dabdbe305563c567098394035483d0b8 100644 (file)
@@ -217,9 +217,19 @@ foreach {tn tcl} {
         SELECT * FROM t2;
     }
   } {a b c d e f}
-  do_test $tn.3.2.2 {
-    list [catch {snapshot_open db main $snapshot } msg] $msg
+
+  # Update - it is no longer an error to have a read-transaction open, 
+  # provided there are no active SELECT statements.
+  do_test $tn.3.2.2a {
+    db eval "SELECT * FROM t2" {
+      set res [list [catch {snapshot_open db main $snapshot } msg] $msg]
+      break
+    }
+    set res
   } {1 SQLITE_ERROR}
+  do_test $tn.3.2.2b {
+    snapshot_open db main $snapshot
+  } {}
 
   do_test $tn.3.2.3 {
     execsql {
@@ -231,12 +241,17 @@ foreach {tn tcl} {
   } {1 SQLITE_ERROR}
   do_execsql_test $tn.3.2.4 COMMIT
 
-  do_test $tn.3.3.1 {
+  do_test $tn.3.3.1a {
     execsql { PRAGMA journal_mode = DELETE }
     execsql { BEGIN }
     list [catch {snapshot_open db main $snapshot } msg] $msg
   } {1 SQLITE_ERROR}
 
+  do_test $tn.3.3.1b {
+    execsql { COMMIT ; BEGIN ; SELECT * FROM t2 }
+    list [catch {snapshot_open db main $snapshot } msg] $msg
+  } {1 SQLITE_ERROR}
+
   do_test $tn.$tn.3.3.2 {
     snapshot_free $snapshot
     execsql COMMIT
diff --git a/test/snapshot_up.test b/test/snapshot_up.test
new file mode 100644 (file)
index 0000000..f7c2e44
--- /dev/null
@@ -0,0 +1,184 @@
+# 2018 August 6
+#
+# 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 calling sqlite3_snapshot_open() when there is already
+# a read transaction open on the database.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+ifcapable !snapshot {finish_test; return}
+set testprefix snapshot_up
+
+# 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
+}
+
+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
+  execsql { INSERT INTO t1 VALUES(10, 11, 12); }
+  execsql BEGIN
+  set ::snap2 [sqlite3_snapshot_get db main]
+  execsql COMMIT
+  execsql { INSERT INTO t1 VALUES(13, 14, 15); }
+  execsql BEGIN
+  set ::snap3 [sqlite3_snapshot_get db main]
+  execsql COMMIT
+} {}
+
+do_execsql_test 1.2 {
+  BEGIN;
+    SELECT * FROM t1
+} {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15}
+
+do_test 1.3 {
+  sqlite3_snapshot_open db main $::snap1
+  execsql { SELECT * FROM t1 }
+} {1 2 3 4 5 6 7 8 9}
+
+do_test 1.4 {
+  sqlite3_snapshot_open db main $::snap2
+  execsql { SELECT * FROM t1 }
+} {1 2 3 4 5 6 7 8 9 10 11 12}
+
+do_test 1.5 {
+  sqlite3 db2 test.db
+  execsql { PRAGMA wal_checkpoint } db2
+} {0 5 4}
+
+do_execsql_test 1.6 {
+  SELECT * FROM t1
+} {1 2 3 4 5 6 7 8 9 10 11 12}
+
+do_test 1.7 {
+  list [catch { sqlite3_snapshot_open db main $::snap1 } msg] $msg
+} {1 SQLITE_BUSY_SNAPSHOT}
+
+do_execsql_test 1.8 {
+  SELECT * FROM t1
+} {1 2 3 4 5 6 7 8 9 10 11 12}
+
+do_test 1.9 {
+  execsql { COMMIT ; BEGIN }
+  list [catch { sqlite3_snapshot_open db main $::snap1 } msg] $msg
+} {1 SQLITE_BUSY_SNAPSHOT}
+
+do_test 1.10 {
+  execsql { COMMIT }
+  execsql {
+    PRAGMA wal_checkpoint;
+    DELETE FROM t1 WHERE a = 1;
+  } db2
+  execsql BEGIN
+  set ::snap4 [sqlite3_snapshot_get db main]
+  execsql COMMIT
+  execsql {
+    DELETE FROM t1 WHERE a = 4;
+  } db2
+} {}
+
+do_test 1.11 {
+  execsql { 
+    BEGIN;
+      SELECT * FROM t1
+  }
+} {7 8 9 10 11 12 13 14 15}
+do_test 1.12 {
+  sqlite3_snapshot_open db main $::snap4
+  execsql { SELECT * FROM t1 }
+} {4 5 6 7 8 9 10 11 12 13 14 15}
+
+do_test 1.13 {
+  list [catch { sqlite3_snapshot_open db main $::snap3 } msg] $msg
+} {1 SQLITE_BUSY_SNAPSHOT}
+do_test 1.14 {
+  execsql { SELECT * FROM t1 }
+} {4 5 6 7 8 9 10 11 12 13 14 15}
+
+db close
+db2 close
+sqlite3 db test.db
+do_execsql_test 1.15 {
+  BEGIN;
+    SELECT * FROM t1
+} {7 8 9 10 11 12 13 14 15}
+do_test 1.16 {
+  list [catch { sqlite3_snapshot_open db main $::snap4 } msg] $msg
+} {1 SQLITE_BUSY_SNAPSHOT}
+do_execsql_test 1.17 { COMMIT }
+
+sqlite3_snapshot_free $::snap1
+sqlite3_snapshot_free $::snap2
+sqlite3_snapshot_free $::snap3
+sqlite3_snapshot_free $::snap4
+
+#-------------------------------------------------------------------------
+catch { db close }
+sqlite3 db test.db
+sqlite3 db2 test.db
+sqlite3 db3 test.db
+
+proc xBusy {args} { return 1 }
+db3 busy xBusy
+
+do_test 2.1 {
+  execsql { INSERT INTO t1 VALUES(16, 17, 18) } db2
+  execsql BEGIN
+  set ::snap1 [sqlite3_snapshot_get db main]
+  execsql COMMIT
+  execsql { INSERT INTO t1 VALUES(19, 20, 21) } db2
+  execsql BEGIN
+  set ::snap2 [sqlite3_snapshot_get db main]
+  execsql COMMIT
+  set {} {}
+} {}
+
+do_execsql_test -db db2 2.2 {
+  BEGIN;
+    INSERT INTO t1 VALUES(19, 20, 21);
+}
+
+do_test 2.3 {
+  execsql BEGIN
+  sqlite3_snapshot_open db main $::snap1
+  execsql { SELECT * FROM t1 }
+} {7 8 9 10 11 12 13 14 15 16 17 18}
+
+proc xBusy {args} { 
+  set ::res [list [catch { sqlite3_snapshot_open db main $::snap2 } msg] $msg]
+  return 1
+}
+db3 busy xBusy
+do_test 2.4 {
+  execsql {PRAGMA wal_checkpoint = restart} db3
+  set ::res
+} {1 SQLITE_BUSY}
+
+sqlite3_snapshot_free $::snap1
+sqlite3_snapshot_free $::snap2
+
+finish_test
+