/ Check-in [4139916995]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Allow sqlite3_snapshot_open() to be called to change the snapshot after a read transaction is already open on database.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 41399169954f9bef53c3fa89879f39823b80bd127f76cf60abbe24217878a571
User & Date: dan 2018-08-15 14:03:26
Context
2018-08-16
15:29
Fix EXPLAIN QUERY PLAN so that it describes IN operators implemented using a ROWID lookup. check-in: 60045fbf52 user: drh tags: trunk
2018-08-15
14:03
Allow sqlite3_snapshot_open() to be called to change the snapshot after a read transaction is already open on database. check-in: 4139916995 user: dan tags: trunk
2018-08-14
15:12
Fix UPSERT so that it checks the target-constraint first and fires the DO UPDATE if that constraint is violated regardless of whether or not other constraints are in violation. This aligns SQLite behavior with what PostgreSQL does. Fix for ticket [908f001483982c43cdb476dfb590a1a]. check-in: 529fb55e3d user: drh tags: trunk
2018-08-06
17:12
Allow sqlite3_snapshot_open() to be called to change the snapshot after a read transaction is already open on database. Closed-Leaf check-in: 051ac01520 user: dan tags: exp-snapshot-open
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to src/main.c.

  4205   4205   #endif
  4206   4206     sqlite3_mutex_enter(db->mutex);
  4207   4207     if( db->autoCommit==0 ){
  4208   4208       int iDb;
  4209   4209       iDb = sqlite3FindDbName(db, zDb);
  4210   4210       if( iDb==0 || iDb>1 ){
  4211   4211         Btree *pBt = db->aDb[iDb].pBt;
  4212         -      if( 0==sqlite3BtreeIsInReadTrans(pBt) ){
  4213         -        rc = sqlite3PagerSnapshotOpen(sqlite3BtreePager(pBt), pSnapshot);
         4212  +      if( sqlite3BtreeIsInTrans(pBt)==0 ){
         4213  +        Pager *pPager = sqlite3BtreePager(pBt);
         4214  +        int bUnlock = 0;
         4215  +        if( sqlite3BtreeIsInReadTrans(pBt) ){
         4216  +          if( db->nVdbeActive==0 ){
         4217  +            rc = sqlite3PagerSnapshotCheck(pPager, pSnapshot);
         4218  +            if( rc==SQLITE_OK ){
         4219  +              bUnlock = 1;
         4220  +              rc = sqlite3BtreeCommit(pBt);
         4221  +            }
         4222  +          }
         4223  +        }else{
         4224  +          rc = SQLITE_OK;
         4225  +        }
         4226  +        if( rc==SQLITE_OK ){
         4227  +          rc = sqlite3PagerSnapshotOpen(pPager, pSnapshot);
         4228  +        }
  4214   4229           if( rc==SQLITE_OK ){
  4215   4230             rc = sqlite3BtreeBeginTrans(pBt, 0, 0);
  4216         -          sqlite3PagerSnapshotOpen(sqlite3BtreePager(pBt), 0);
         4231  +          sqlite3PagerSnapshotOpen(pPager, 0);
         4232  +        }
         4233  +        if( bUnlock ){
         4234  +          sqlite3PagerSnapshotUnlock(pPager);
  4217   4235           }
  4218   4236         }
  4219   4237       }
  4220   4238     }
  4221   4239   
  4222   4240     sqlite3_mutex_leave(db->mutex);
  4223   4241   #endif   /* SQLITE_OMIT_WAL */

Changes to src/pager.c.

  7649   7649     if( pPager->pWal ){
  7650   7650       rc = sqlite3WalSnapshotRecover(pPager->pWal);
  7651   7651     }else{
  7652   7652       rc = SQLITE_ERROR;
  7653   7653     }
  7654   7654     return rc;
  7655   7655   }
         7656  +
         7657  +/*
         7658  +** The caller currently has a read transaction open on the database.
         7659  +** If this is not a WAL database, SQLITE_ERROR is returned. Otherwise,
         7660  +** this function takes a SHARED lock on the CHECKPOINTER slot and then
         7661  +** checks if the snapshot passed as the second argument is still 
         7662  +** available. If so, SQLITE_OK is returned.
         7663  +**
         7664  +** If the snapshot is not available, SQLITE_ERROR is returned. Or, if
         7665  +** the CHECKPOINTER lock cannot be obtained, SQLITE_BUSY. If any error
         7666  +** occurs (any value other than SQLITE_OK is returned), the CHECKPOINTER
         7667  +** lock is released before returning.
         7668  +*/
         7669  +int sqlite3PagerSnapshotCheck(Pager *pPager, sqlite3_snapshot *pSnapshot){
         7670  +  int rc;
         7671  +  if( pPager->pWal ){
         7672  +    rc = sqlite3WalSnapshotCheck(pPager->pWal, pSnapshot);
         7673  +  }else{
         7674  +    rc = SQLITE_ERROR;
         7675  +  }
         7676  +  return rc;
         7677  +}
         7678  +
         7679  +/*
         7680  +** Release a lock obtained by an earlier successful call to
         7681  +** sqlite3PagerSnapshotCheck().
         7682  +*/
         7683  +void sqlite3PagerSnapshotUnlock(Pager *pPager){
         7684  +  assert( pPager->pWal );
         7685  +  return sqlite3WalSnapshotUnlock(pPager->pWal);
         7686  +}
         7687  +
  7656   7688   #endif /* SQLITE_ENABLE_SNAPSHOT */
  7657   7689   #endif /* !SQLITE_OMIT_WAL */
  7658   7690   
  7659   7691   #ifdef SQLITE_ENABLE_ZIPVFS
  7660   7692   /*
  7661   7693   ** A read-lock must be held on the pager when this function is called. If
  7662   7694   ** the pager is in WAL mode and the WAL file currently contains one or more

Changes to src/pager.h.

   182    182   # ifdef SQLITE_DIRECT_OVERFLOW_READ
   183    183     int sqlite3PagerUseWal(Pager *pPager, Pgno);
   184    184   # endif
   185    185   # ifdef SQLITE_ENABLE_SNAPSHOT
   186    186     int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot);
   187    187     int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot);
   188    188     int sqlite3PagerSnapshotRecover(Pager *pPager);
          189  +  int sqlite3PagerSnapshotCheck(Pager *pPager, sqlite3_snapshot *pSnapshot);
          190  +  void sqlite3PagerSnapshotUnlock(Pager *pPager);
   189    191   # endif
   190    192   #else
   191    193   # define sqlite3PagerUseWal(x,y) 0
   192    194   #endif
   193    195   
   194    196   #ifdef SQLITE_ENABLE_ZIPVFS
   195    197     int sqlite3PagerWalFramesize(Pager *pPager);

Changes to src/sqlite.h.in.

  9031   9031     sqlite3_snapshot **ppSnapshot
  9032   9032   );
  9033   9033   
  9034   9034   /*
  9035   9035   ** CAPI3REF: Start a read transaction on an historical snapshot
  9036   9036   ** METHOD: sqlite3_snapshot
  9037   9037   **
  9038         -** ^The [sqlite3_snapshot_open(D,S,P)] interface starts a
  9039         -** read transaction for schema S of
  9040         -** [database connection] D such that the read transaction
  9041         -** refers to historical [snapshot] P, rather than the most
  9042         -** recent change to the database.
  9043         -** ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK on success
  9044         -** or an appropriate [error code] if it fails.
  9045         -**
  9046         -** ^In order to succeed, a call to [sqlite3_snapshot_open(D,S,P)] must be
  9047         -** the first operation following the [BEGIN] that takes the schema S
  9048         -** out of [autocommit mode].
  9049         -** ^In other words, schema S must not currently be in
  9050         -** a transaction for [sqlite3_snapshot_open(D,S,P)] to work, but the
  9051         -** database connection D must be out of [autocommit mode].
  9052         -** ^A [snapshot] will fail to open if it has been overwritten by a
  9053         -** [checkpoint].
         9038  +** ^The [sqlite3_snapshot_open(D,S,P)] interface either starts a new read 
         9039  +** transaction or upgrades an existing one for schema S of 
         9040  +** [database connection] D such that the read transaction refers to 
         9041  +** historical [snapshot] P, rather than the most recent change to the 
         9042  +** database. ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK 
         9043  +** on success or an appropriate [error code] if it fails.
         9044  +**
         9045  +** ^In order to succeed, the database connection must not be in 
         9046  +** [autocommit mode] when [sqlite3_snapshot_open(D,S,P)] is called. If there
         9047  +** is already a read transaction open on schema S, then the database handle
         9048  +** must have no active statements (SELECT statements that have been passed
         9049  +** to sqlite3_step() but not sqlite3_reset() or sqlite3_finalize()). 
         9050  +** SQLITE_ERROR is returned if either of these conditions is violated, or
         9051  +** if schema S does not exist, or if the snapshot object is invalid.
         9052  +**
         9053  +** ^A call to sqlite3_snapshot_open() will fail to open if the specified
         9054  +** snapshot has been overwritten by a [checkpoint]. In this case 
         9055  +** SQLITE_BUSY_SNAPSHOT is returned.
         9056  +**
         9057  +** If there is already a read transaction open when this function is 
         9058  +** invoked, then the same read transaction remains open (on the same
         9059  +** database snapshot) if SQLITE_ERROR, SQLITE_BUSY or SQLITE_BUSY_SNAPSHOT
         9060  +** is returned. If another error code - for example SQLITE_PROTOCOL or an
         9061  +** SQLITE_IOERR error code - is returned, then the final state of the
         9062  +** read transaction is undefined. If SQLITE_OK is returned, then the 
         9063  +** read transaction is now open on database snapshot P.
         9064  +**
  9054   9065   ** ^(A call to [sqlite3_snapshot_open(D,S,P)] will fail if the
  9055   9066   ** database connection D does not know that the database file for
  9056   9067   ** schema S is in [WAL mode].  A database connection might not know
  9057   9068   ** that the database file is in [WAL mode] if there has been no prior
  9058   9069   ** I/O on that database connection, or if the database entered [WAL mode] 
  9059   9070   ** after the most recent I/O on the database connection.)^
  9060   9071   ** (Hint: Run "[PRAGMA application_id]" against a newly opened

Changes to src/test1.c.

  2389   2389     zName = Tcl_GetString(objv[2]);
  2390   2390     pSnapshot = (sqlite3_snapshot*)sqlite3TestTextToPtr(Tcl_GetString(objv[3]));
  2391   2391   
  2392   2392     rc = sqlite3_snapshot_open(db, zName, pSnapshot);
  2393   2393     if( rc!=SQLITE_OK ){
  2394   2394       Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
  2395   2395       return TCL_ERROR;
         2396  +  }else{
         2397  +    Tcl_ResetResult(interp);
  2396   2398     }
  2397   2399     return TCL_OK;
  2398   2400   }
  2399   2401   #endif /* SQLITE_ENABLE_SNAPSHOT */
  2400   2402   
  2401   2403   #ifdef SQLITE_ENABLE_SNAPSHOT
  2402   2404   /*

Changes to src/wal.c.

  3765   3765     ** is incremented each time the wal file is restarted.  */
  3766   3766     if( pHdr1->aSalt[0]<pHdr2->aSalt[0] ) return -1;
  3767   3767     if( pHdr1->aSalt[0]>pHdr2->aSalt[0] ) return +1;
  3768   3768     if( pHdr1->mxFrame<pHdr2->mxFrame ) return -1;
  3769   3769     if( pHdr1->mxFrame>pHdr2->mxFrame ) return +1;
  3770   3770     return 0;
  3771   3771   }
         3772  +
         3773  +/*
         3774  +** The caller currently has a read transaction open on the database.
         3775  +** This function takes a SHARED lock on the CHECKPOINTER slot and then
         3776  +** checks if the snapshot passed as the second argument is still 
         3777  +** available. If so, SQLITE_OK is returned.
         3778  +**
         3779  +** If the snapshot is not available, SQLITE_ERROR is returned. Or, if
         3780  +** the CHECKPOINTER lock cannot be obtained, SQLITE_BUSY. If any error
         3781  +** occurs (any value other than SQLITE_OK is returned), the CHECKPOINTER
         3782  +** lock is released before returning.
         3783  +*/
         3784  +int sqlite3WalSnapshotCheck(Wal *pWal, sqlite3_snapshot *pSnapshot){
         3785  +  int rc;
         3786  +  rc = walLockShared(pWal, WAL_CKPT_LOCK);
         3787  +  if( rc==SQLITE_OK ){
         3788  +    WalIndexHdr *pNew = (WalIndexHdr*)pSnapshot;
         3789  +    if( memcmp(pNew->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt))
         3790  +     || pNew->mxFrame<walCkptInfo(pWal)->nBackfillAttempted
         3791  +    ){
         3792  +      rc = SQLITE_BUSY_SNAPSHOT;
         3793  +      walUnlockShared(pWal, WAL_CKPT_LOCK);
         3794  +    }
         3795  +  }
         3796  +  return rc;
         3797  +}
         3798  +
         3799  +/*
         3800  +** Release a lock obtained by an earlier successful call to
         3801  +** sqlite3WalSnapshotCheck().
         3802  +*/
         3803  +void sqlite3WalSnapshotUnlock(Wal *pWal){
         3804  +  assert( pWal );
         3805  +  walUnlockShared(pWal, WAL_CKPT_LOCK);
         3806  +}
         3807  +
         3808  +
  3772   3809   #endif /* SQLITE_ENABLE_SNAPSHOT */
  3773   3810   
  3774   3811   #ifdef SQLITE_ENABLE_ZIPVFS
  3775   3812   /*
  3776   3813   ** If the argument is not NULL, it points to a Wal object that holds a
  3777   3814   ** read-lock. This function returns the database page-size if it is known,
  3778   3815   ** or zero if it is not (or if pWal is NULL).

Changes to src/wal.h.

   128    128   */
   129    129   int sqlite3WalHeapMemory(Wal *pWal);
   130    130   
   131    131   #ifdef SQLITE_ENABLE_SNAPSHOT
   132    132   int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot);
   133    133   void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot);
   134    134   int sqlite3WalSnapshotRecover(Wal *pWal);
          135  +int sqlite3WalSnapshotCheck(Wal *pWal, sqlite3_snapshot *pSnapshot);
          136  +void sqlite3WalSnapshotUnlock(Wal *pWal);
   135    137   #endif
   136    138   
   137    139   #ifdef SQLITE_ENABLE_ZIPVFS
   138    140   /* If the WAL file is not empty, return the number of bytes of content
   139    141   ** stored in each frame (i.e. the db page-size when the WAL was created).
   140    142   */
   141    143   int sqlite3WalFramesize(Wal *pWal);

Changes to test/snapshot.test.

   213    213   
   214    214     do_test $tn.3.2.1 {
   215    215       execsql {
   216    216         BEGIN;
   217    217           SELECT * FROM t2;
   218    218       }
   219    219     } {a b c d e f}
   220         -  do_test $tn.3.2.2 {
   221         -    list [catch {snapshot_open db main $snapshot } msg] $msg
          220  +
          221  +  # Update - it is no longer an error to have a read-transaction open, 
          222  +  # provided there are no active SELECT statements.
          223  +  do_test $tn.3.2.2a {
          224  +    db eval "SELECT * FROM t2" {
          225  +      set res [list [catch {snapshot_open db main $snapshot } msg] $msg]
          226  +      break
          227  +    }
          228  +    set res
   222    229     } {1 SQLITE_ERROR}
          230  +  do_test $tn.3.2.2b {
          231  +    snapshot_open db main $snapshot
          232  +  } {}
   223    233   
   224    234     do_test $tn.3.2.3 {
   225    235       execsql {
   226    236         COMMIT;
   227    237         BEGIN;
   228    238           INSERT INTO t2 VALUES('g', 'h');
   229    239       }
   230    240       list [catch {snapshot_open db main $snapshot } msg] $msg
   231    241     } {1 SQLITE_ERROR}
   232    242     do_execsql_test $tn.3.2.4 COMMIT
   233    243   
   234         -  do_test $tn.3.3.1 {
          244  +  do_test $tn.3.3.1a {
   235    245       execsql { PRAGMA journal_mode = DELETE }
   236    246       execsql { BEGIN }
   237    247       list [catch {snapshot_open db main $snapshot } msg] $msg
   238    248     } {1 SQLITE_ERROR}
          249  +
          250  +  do_test $tn.3.3.1b {
          251  +    execsql { COMMIT ; BEGIN ; SELECT * FROM t2 }
          252  +    list [catch {snapshot_open db main $snapshot } msg] $msg
          253  +  } {1 SQLITE_ERROR}
   239    254   
   240    255     do_test $tn.$tn.3.3.2 {
   241    256       snapshot_free $snapshot
   242    257       execsql COMMIT
   243    258     } {}
   244    259   
   245    260     #-------------------------------------------------------------------------

Added test/snapshot_up.test.

            1  +# 2018 August 6
            2  +#
            3  +# The author disclaims copyright to this source code.  In place of
            4  +# a legal notice, here is a blessing:
            5  +#
            6  +#    May you do good and not evil.
            7  +#    May you find forgiveness for yourself and forgive others.
            8  +#    May you share freely, never taking more than you give.
            9  +#
           10  +#***********************************************************************
           11  +#
           12  +# Tests for calling sqlite3_snapshot_open() when there is already
           13  +# a read transaction open on the database.
           14  +#
           15  +
           16  +set testdir [file dirname $argv0]
           17  +source $testdir/tester.tcl
           18  +ifcapable !snapshot {finish_test; return}
           19  +set testprefix snapshot_up
           20  +
           21  +# This test does not work with the inmemory_journal permutation. The reason
           22  +# is that each connection opened as part of this permutation executes
           23  +# "PRAGMA journal_mode=memory", which fails if the database is in wal mode
           24  +# and there are one or more existing connections.
           25  +if {[permutation]=="inmemory_journal"} {
           26  +  finish_test
           27  +  return
           28  +}
           29  +
           30  +do_execsql_test 1.0 {
           31  +  CREATE TABLE t1(a, b, c);
           32  +  PRAGMA journal_mode = wal;
           33  +  INSERT INTO t1 VALUES(1, 2, 3);
           34  +  INSERT INTO t1 VALUES(4, 5, 6);
           35  +  INSERT INTO t1 VALUES(7, 8, 9);
           36  +} {wal}
           37  +
           38  +do_test 1.1 {
           39  +  execsql BEGIN
           40  +  set ::snap1 [sqlite3_snapshot_get db main]
           41  +  execsql COMMIT
           42  +  execsql { INSERT INTO t1 VALUES(10, 11, 12); }
           43  +  execsql BEGIN
           44  +  set ::snap2 [sqlite3_snapshot_get db main]
           45  +  execsql COMMIT
           46  +  execsql { INSERT INTO t1 VALUES(13, 14, 15); }
           47  +  execsql BEGIN
           48  +  set ::snap3 [sqlite3_snapshot_get db main]
           49  +  execsql COMMIT
           50  +} {}
           51  +
           52  +do_execsql_test 1.2 {
           53  +  BEGIN;
           54  +    SELECT * FROM t1
           55  +} {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15}
           56  +
           57  +do_test 1.3 {
           58  +  sqlite3_snapshot_open db main $::snap1
           59  +  execsql { SELECT * FROM t1 }
           60  +} {1 2 3 4 5 6 7 8 9}
           61  +
           62  +do_test 1.4 {
           63  +  sqlite3_snapshot_open db main $::snap2
           64  +  execsql { SELECT * FROM t1 }
           65  +} {1 2 3 4 5 6 7 8 9 10 11 12}
           66  +
           67  +do_test 1.5 {
           68  +  sqlite3 db2 test.db
           69  +  execsql { PRAGMA wal_checkpoint } db2
           70  +} {0 5 4}
           71  +
           72  +do_execsql_test 1.6 {
           73  +  SELECT * FROM t1
           74  +} {1 2 3 4 5 6 7 8 9 10 11 12}
           75  +
           76  +do_test 1.7 {
           77  +  list [catch { sqlite3_snapshot_open db main $::snap1 } msg] $msg
           78  +} {1 SQLITE_BUSY_SNAPSHOT}
           79  +
           80  +do_execsql_test 1.8 {
           81  +  SELECT * FROM t1
           82  +} {1 2 3 4 5 6 7 8 9 10 11 12}
           83  +
           84  +do_test 1.9 {
           85  +  execsql { COMMIT ; BEGIN }
           86  +  list [catch { sqlite3_snapshot_open db main $::snap1 } msg] $msg
           87  +} {1 SQLITE_BUSY_SNAPSHOT}
           88  +
           89  +do_test 1.10 {
           90  +  execsql { COMMIT }
           91  +  execsql {
           92  +    PRAGMA wal_checkpoint;
           93  +    DELETE FROM t1 WHERE a = 1;
           94  +  } db2
           95  +  execsql BEGIN
           96  +  set ::snap4 [sqlite3_snapshot_get db main]
           97  +  execsql COMMIT
           98  +  execsql {
           99  +    DELETE FROM t1 WHERE a = 4;
          100  +  } db2
          101  +} {}
          102  +
          103  +do_test 1.11 {
          104  +  execsql { 
          105  +    BEGIN;
          106  +      SELECT * FROM t1
          107  +  }
          108  +} {7 8 9 10 11 12 13 14 15}
          109  +do_test 1.12 {
          110  +  sqlite3_snapshot_open db main $::snap4
          111  +  execsql { SELECT * FROM t1 }
          112  +} {4 5 6 7 8 9 10 11 12 13 14 15}
          113  +
          114  +do_test 1.13 {
          115  +  list [catch { sqlite3_snapshot_open db main $::snap3 } msg] $msg
          116  +} {1 SQLITE_BUSY_SNAPSHOT}
          117  +do_test 1.14 {
          118  +  execsql { SELECT * FROM t1 }
          119  +} {4 5 6 7 8 9 10 11 12 13 14 15}
          120  +
          121  +db close
          122  +db2 close
          123  +sqlite3 db test.db
          124  +do_execsql_test 1.15 {
          125  +  BEGIN;
          126  +    SELECT * FROM t1
          127  +} {7 8 9 10 11 12 13 14 15}
          128  +do_test 1.16 {
          129  +  list [catch { sqlite3_snapshot_open db main $::snap4 } msg] $msg
          130  +} {1 SQLITE_BUSY_SNAPSHOT}
          131  +do_execsql_test 1.17 { COMMIT }
          132  +
          133  +sqlite3_snapshot_free $::snap1
          134  +sqlite3_snapshot_free $::snap2
          135  +sqlite3_snapshot_free $::snap3
          136  +sqlite3_snapshot_free $::snap4
          137  +
          138  +#-------------------------------------------------------------------------
          139  +catch { db close }
          140  +sqlite3 db test.db
          141  +sqlite3 db2 test.db
          142  +sqlite3 db3 test.db
          143  +
          144  +proc xBusy {args} { return 1 }
          145  +db3 busy xBusy
          146  +
          147  +do_test 2.1 {
          148  +  execsql { INSERT INTO t1 VALUES(16, 17, 18) } db2
          149  +  execsql BEGIN
          150  +  set ::snap1 [sqlite3_snapshot_get db main]
          151  +  execsql COMMIT
          152  +  execsql { INSERT INTO t1 VALUES(19, 20, 21) } db2
          153  +  execsql BEGIN
          154  +  set ::snap2 [sqlite3_snapshot_get db main]
          155  +  execsql COMMIT
          156  +  set {} {}
          157  +} {}
          158  +
          159  +do_execsql_test -db db2 2.2 {
          160  +  BEGIN;
          161  +    INSERT INTO t1 VALUES(19, 20, 21);
          162  +}
          163  +
          164  +do_test 2.3 {
          165  +  execsql BEGIN
          166  +  sqlite3_snapshot_open db main $::snap1
          167  +  execsql { SELECT * FROM t1 }
          168  +} {7 8 9 10 11 12 13 14 15 16 17 18}
          169  +
          170  +proc xBusy {args} { 
          171  +  set ::res [list [catch { sqlite3_snapshot_open db main $::snap2 } msg] $msg]
          172  +  return 1
          173  +}
          174  +db3 busy xBusy
          175  +do_test 2.4 {
          176  +  execsql {PRAGMA wal_checkpoint = restart} db3
          177  +  set ::res
          178  +} {1 SQLITE_BUSY}
          179  +
          180  +sqlite3_snapshot_free $::snap1
          181  +sqlite3_snapshot_free $::snap2
          182  +
          183  +finish_test
          184  +