SQLite

Changes On Branch snapshot-revert
Login

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

Changes In Branch snapshot-revert Excluding Merge-Ins

This is equivalent to a diff from 7e38c889 to 00af3bd2

2021-03-26
13:31
Merge from 3.35.3 into the begin-concurrent branch. (check-in: 988da36c user: drh tags: begin-concurrent)
2021-03-24
19:57
Add a comment to snapshotrevert.c warning of the undefined effects reverting to an old snapshot may have on newer readers. (Leaf check-in: 00af3bd2 user: dan tags: snapshot-revert)
19:54
Add experimental extension "snapshotrevert". (check-in: 2d5ee3ba user: dan tags: snapshot-revert)
2021-03-12
16:18
Merge version 3.35.0 changes into the begin-concurrent branch. (check-in: 7e38c889 user: drh tags: begin-concurrent)
15:10
Version 3.35.0 (check-in: acd63062 user: drh tags: trunk, release, major-release, version-3.35.0)
2021-03-11
18:52
Update this branch with latest changes from trunk. (check-in: 79d44ebd user: dan tags: begin-concurrent)

Changes to Makefile.in.

458
459
460
461
462
463
464

465
466
467
468
469
470
471
  $(TOP)/ext/misc/nextchar.c \
  $(TOP)/ext/misc/normalize.c \
  $(TOP)/ext/misc/percentile.c \
  $(TOP)/ext/misc/prefixes.c \
  $(TOP)/ext/misc/regexp.c \
  $(TOP)/ext/misc/remember.c \
  $(TOP)/ext/misc/series.c \

  $(TOP)/ext/misc/spellfix.c \
  $(TOP)/ext/misc/totype.c \
  $(TOP)/ext/misc/unionvtab.c \
  $(TOP)/ext/misc/wholenumber.c \
  $(TOP)/ext/misc/zipfile.c \
  $(TOP)/ext/userauth/userauth.c








>







458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
  $(TOP)/ext/misc/nextchar.c \
  $(TOP)/ext/misc/normalize.c \
  $(TOP)/ext/misc/percentile.c \
  $(TOP)/ext/misc/prefixes.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 \
  $(TOP)/ext/misc/wholenumber.c \
  $(TOP)/ext/misc/zipfile.c \
  $(TOP)/ext/userauth/userauth.c

Changes to Makefile.msc.

1578
1579
1580
1581
1582
1583
1584

1585
1586
1587
1588
1589
1590
1591
  $(TOP)\ext\misc\nextchar.c \
  $(TOP)\ext\misc\normalize.c \
  $(TOP)\ext\misc\percentile.c \
  $(TOP)\ext\misc\prefixes.c \
  $(TOP)\ext\misc\regexp.c \
  $(TOP)\ext\misc\remember.c \
  $(TOP)\ext\misc\series.c \

  $(TOP)\ext\misc\spellfix.c \
  $(TOP)\ext\misc\totype.c \
  $(TOP)\ext\misc\unionvtab.c \
  $(TOP)\ext\misc\wholenumber.c

# If use of zlib is enabled, add the "zipfile.c" source file.
#







>







1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
  $(TOP)\ext\misc\nextchar.c \
  $(TOP)\ext\misc\normalize.c \
  $(TOP)\ext\misc\percentile.c \
  $(TOP)\ext\misc\prefixes.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 \
  $(TOP)\ext\misc\wholenumber.c

# If use of zlib is enabled, add the "zipfile.c" source file.
#

Added ext/misc/snapshotrevert.c.





























































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/*
** 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.
**
** This function uses normal SQLite locks to ensure that the database is
** not corrupted by a simultaneous writer or checkpointer. However, the
** effects of a successful call to this function on readers that are
** reading from a snapshot newer than the snapshot supplied as the 
** third argument are undefined.
**
** 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) */

Changes to main.mk.

375
376
377
378
379
380
381

382
383
384
385
386
387
388
  $(TOP)/ext/misc/nextchar.c \
  $(TOP)/ext/misc/normalize.c \
  $(TOP)/ext/misc/percentile.c \
  $(TOP)/ext/misc/prefixes.c \
  $(TOP)/ext/misc/regexp.c \
  $(TOP)/ext/misc/remember.c \
  $(TOP)/ext/misc/series.c \

  $(TOP)/ext/misc/spellfix.c \
  $(TOP)/ext/misc/totype.c \
  $(TOP)/ext/misc/unionvtab.c \
  $(TOP)/ext/misc/wholenumber.c \
  $(TOP)/ext/misc/zipfile.c \
  $(TOP)/ext/fts5/fts5_tcl.c \
  $(TOP)/ext/fts5/fts5_test_mi.c \







>







375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
  $(TOP)/ext/misc/nextchar.c \
  $(TOP)/ext/misc/normalize.c \
  $(TOP)/ext/misc/percentile.c \
  $(TOP)/ext/misc/prefixes.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 \
  $(TOP)/ext/misc/wholenumber.c \
  $(TOP)/ext/misc/zipfile.c \
  $(TOP)/ext/fts5/fts5_tcl.c \
  $(TOP)/ext/fts5/fts5_test_mi.c \

Changes to src/test1.c.

2570
2571
2572
2573
2574
2575
2576



































2577
2578
2579
2580
2581
2582
2583

  res = sqlite3_snapshot_cmp((sqlite3_snapshot*)p1, (sqlite3_snapshot*)p2);
  Tcl_SetObjResult(interp, Tcl_NewIntObj(res));
  return TCL_OK;
}
#endif /* SQLITE_ENABLE_SNAPSHOT */




































/*
** Usage: sqlite3_delete_database FILENAME
*/
int sqlite3_delete_database(const char*);   /* in test_delete.c */
static int SQLITE_TCLAPI test_delete_database(
  void * clientData,
  Tcl_Interp *interp,







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618

  res = sqlite3_snapshot_cmp((sqlite3_snapshot*)p1, (sqlite3_snapshot*)p2);
  Tcl_SetObjResult(interp, Tcl_NewIntObj(res));
  return TCL_OK;
}
#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
*/
int sqlite3_delete_database(const char*);   /* in test_delete.c */
static int SQLITE_TCLAPI test_delete_database(
  void * clientData,
  Tcl_Interp *interp,
8521
8522
8523
8524
8525
8526
8527

8528
8529
8530
8531
8532
8533
8534
     { "sqlite3_snapshot_open", test_snapshot_open, 0 },
     { "sqlite3_snapshot_free", test_snapshot_free, 0 },
     { "sqlite3_snapshot_cmp", test_snapshot_cmp, 0 },
     { "sqlite3_snapshot_recover", test_snapshot_recover, 0 },
     { "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 },

#endif
     { "sqlite3_delete_database", test_delete_database, 0 },
     { "sqlite3_wal_info", test_wal_info, 0 },
     { "atomic_batch_write",      test_atomic_batch_write,     0   },
     { "sqlite3_mmap_warm",       test_mmap_warm,          0 },
     { "sqlite3_config_sorterref", test_config_sorterref,   0 },
     { "decode_hexdb",             test_decode_hexdb,       0 },







>







8556
8557
8558
8559
8560
8561
8562
8563
8564
8565
8566
8567
8568
8569
8570
     { "sqlite3_snapshot_open", test_snapshot_open, 0 },
     { "sqlite3_snapshot_free", test_snapshot_free, 0 },
     { "sqlite3_snapshot_cmp", test_snapshot_cmp, 0 },
     { "sqlite3_snapshot_recover", test_snapshot_recover, 0 },
     { "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 },
     { "atomic_batch_write",      test_atomic_batch_write,     0   },
     { "sqlite3_mmap_warm",       test_mmap_warm,          0 },
     { "sqlite3_config_sorterref", test_config_sorterref,   0 },
     { "decode_hexdb",             test_decode_hexdb,       0 },

Added test/snapshot_revert.test.



























































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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