Add the experimental snapshot interface. Because it is experimental, it is

subject to change or removal at a later date.

FossilOrigin-Name: 9b124a5a310aec0145495c9980bc0c1308fcca02
This commit is contained in:
drh 2015-12-11 12:44:52 +00:00
commit cc99e24491
12 changed files with 1055 additions and 103 deletions

View File

@ -1,5 +1,5 @@
C Fix\sa\smemory\sallocation\sbug\sintroduced\slast\sweek\sby\scheck-in\n[a9e819082ba].\s\sThe\sbug\sonly\sappears\son\ssystems\swhere\sthe\ssize\sof\sa\sstructure\nis\snot\salways\sa\smultiple\sof\s8\s-\swhich\sin\spractice\smeans\sonly\son\s32-bit\s\nwindows\ssystems.
D 2015-12-11T04:11:17.453
C Add\sthe\sexperimental\ssnapshot\sinterface.\s\sBecause\sit\sis\sexperimental,\sit\sis\nsubject\sto\schange\sor\sremoval\sat\sa\slater\sdate.
D 2015-12-11T12:44:52.505
F Makefile.in 28bcd6149e050dff35d4dcfd97e890cd387a499d
F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
F Makefile.msc e8fdca1cb89a1b58b5f4d3a130ea9a3d28cb314d
@ -304,7 +304,7 @@ F src/insert.c e1d20ae8979e25519c2670233718676bedcfedc9
F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d
F src/legacy.c ba1863ea58c4c840335a84ec276fc2b25e22bc4e
F src/loadext.c 84996d7d70a605597d79c1f1d7b2012a5fd34f2b
F src/main.c a950e48920e8c0f0ff82b2b2ccfe11aa89ca11d4
F src/main.c 2f33510a6a392c606fe64964e695e55ad124242f
F src/malloc.c 8f787669e79de26efc42272b5797bc00fff527c6
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
F src/mem1.c 6919bcf12f221868ea066eec27e579fed95ce98b
@ -326,8 +326,8 @@ F src/os_setup.h c9d4553b5aaa6f73391448b265b89bed0b890faa
F src/os_unix.c 0ca6d8710366fbb01a275160f018334cd347cbda
F src/os_win.c 386fba30419e8458b13209781c2af5590eab2811
F src/os_win.h eb7a47aa17b26b77eb97e4823f20a00b8bda12ca
F src/pager.c f92aacd5216d8815136c9e0190041783c602641a
F src/pager.h 9153c71a89dc82a5a77e485f3929792116c70aae
F src/pager.c 58d2593612acb6b542de6715b4af397ea1fa0a35
F src/pager.h bf25005b4656cd805af43487c3139fca9678d0cc
F src/parse.y 23737e649c26ce327603799e57f5c2ff50e5e6ba
F src/pcache.c 73895411fa6b7bd6f0091212feabbe833b358d23
F src/pcache.h 1ff11adce609ba7de139b6abfabaf9a2bac947b5
@ -341,7 +341,7 @@ F src/resolve.c a83b41104e6ff69855d03cd0aaa09e93927ec39f
F src/rowset.c eccf6af6d620aaa4579bd3b72c1b6395d9e9fa1e
F src/select.c f8fded11fc443a9f5a73cc5db069d06b34460e2f
F src/shell.c abbc74ea43dbf2f306ea18282d666683fb5efab2
F src/sqlite.h.in 1248a78548024bdc8ef5893faa0ff9552b4cceb4
F src/sqlite.h.in 7d87d71b9a4689c51fa092f48f16590ff71558e3
F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad
F src/sqlite3ext.h dfbe62ffd95b99afe2140d8c35b180d11924072d
F src/sqliteInt.h beb4a63b94428f52a3d7c7af2ba8bdc7d4682a03
@ -349,7 +349,7 @@ F src/sqliteLimit.h 216557999cb45f2e3578ed53ebefe228d779cb46
F src/status.c 70912d7be68e9e2dbc4010c93d344af61d4c59ba
F src/table.c 51b46b2a62d1b3a959633d593b89bab5e2c9155e
F src/tclsqlite.c d9439b6a910985b7fff43ba6756bcef00de22649
F src/test1.c 90c4e80c7f4b0ad82c6dbe699c8f430b641769f5
F src/test1.c 4f1b42699068b7806af3111786f5ad760c2c1ff7
F src/test2.c 5586f43fcd9a1be0830793cf9d354082c261b25b
F src/test3.c a8887dabbbee3059af338f20d290084a63ed1b0f
F src/test4.c d168f83cc78d02e8d35567bb5630e40dcd85ac1e
@ -363,7 +363,7 @@ F src/test_autoext.c dea8a01a7153b9adc97bd26161e4226329546e12
F src/test_backup.c 2e6e6a081870150f20c526a2e9d0d29cda47d803
F src/test_blob.c e5a7a81d61a780da79101aeb1e60d300af169e07
F src/test_btree.c 2e9978eca99a9a4bfa8cae949efb00886860a64f
F src/test_config.c 48850687dd5abc8260e23835632511054ccae172
F src/test_config.c 0dee90328e3dedf8ba002ee94b6a7e7ea7726fe4
F src/test_demovfs.c 0de72c2c89551629f58486fde5734b7d90758852
F src/test_devsym.c e7498904e72ba7491d142d5c83b476c4e76993bc
F src/test_fs.c 993c7eab65bed6add4bb48cca29775e963f710cf
@ -415,8 +415,8 @@ F src/vdbesort.c a7ec02da4494c59dfd071126dd3726be5a11459d
F src/vdbetrace.c 8befe829faff6d9e6f6e4dee5a7d3f85cc85f1a0
F src/vtab.c 2a8b44aa372c33f6154208e7a7f6c44254549806
F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb
F src/wal.c 1569802364cd192bbd5c4a8ea3fd6de593edecbd
F src/wal.h df01efe09c5cb8c8e391ff1715cca294f89668a4
F src/wal.c cb709aa42fc11b1ea92a00c8c7e6214de0995fa3
F src/wal.h 907943dfdef10b583e81906679a347e0ec6f1b1b
F src/walker.c 2e14d17f592d176b6dc879c33fbdec4fbccaa2ba
F src/where.c b18edbb9e5afabb77f4f27550c471c5c824e0fe7
F src/whereInt.h e20801d89e34de1912bb6a3babb30c390da27add
@ -1020,6 +1020,8 @@ F test/skipscan2.test d1d1450952b7275f0b0a3a981f0230532743951a
F test/skipscan3.test ec5bab3f81c7038b43450e7b3062e04a198bdbb5
F test/skipscan5.test 67817a4b6857c47e0e33ba3e506da6f23ef68de2
F test/skipscan6.test 5866039d03a56f5bd0b3d172a012074a1d90a15b
F test/snapshot.test efc6b4edc5d571161835f9dd8552e181ad1f0ac2
F test/snapshot_fault.test 25973aeb1b86a280800e0bcf1eb5ce70e9ef57ab
F test/soak.test 0b5b6375c9f4110c828070b826b3b4b0bb65cd5f
F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087
F test/sort.test 3f492e5b7be1d3f756728d2ff6edf4f6091e84cb
@ -1408,7 +1410,8 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
P 05bc4f920ce23da48d1da6cd36a956fd6fd7c862
R bb8ff31fbee90cb95998ef6668cbcbeb
P 96d3e99ffe59b5280b8b7fc269c94a72e15c345b 843c15a552657ca43ff200edb7da7566752d7941
R 1e8a48e65f87836435758a04b37e3926
T +closed 843c15a552657ca43ff200edb7da7566752d7941
U drh
Z dbc67560cb8111f117bbc951b94851d2
Z f796eddbea3599c0fdbed9d3e73688d6

View File

@ -1 +1 @@
96d3e99ffe59b5280b8b7fc269c94a72e15c345b
9b124a5a310aec0145495c9980bc0c1308fcca02

View File

@ -3866,3 +3866,86 @@ int sqlite3_db_readonly(sqlite3 *db, const char *zDbName){
pBt = sqlite3DbNameToBtree(db, zDbName);
return pBt ? sqlite3BtreeIsReadonly(pBt) : -1;
}
#ifdef SQLITE_ENABLE_SNAPSHOT
/*
** Obtain a snapshot handle for the snapshot of database zDb currently
** being read by handle db.
*/
int sqlite3_snapshot_get(
sqlite3 *db,
const char *zDb,
sqlite3_snapshot **ppSnapshot
){
int rc = SQLITE_ERROR;
#ifndef SQLITE_OMIT_WAL
int iDb;
#ifdef SQLITE_ENABLE_API_ARMOR
if( !sqlite3SafetyCheckOk(db) ){
return SQLITE_MISUSE_BKPT;
}
#endif
sqlite3_mutex_enter(db->mutex);
iDb = sqlite3FindDbName(db, zDb);
if( iDb==0 || iDb>1 ){
Btree *pBt = db->aDb[iDb].pBt;
if( 0==sqlite3BtreeIsInTrans(pBt) ){
rc = sqlite3BtreeBeginTrans(pBt, 0);
if( rc==SQLITE_OK ){
rc = sqlite3PagerSnapshotGet(sqlite3BtreePager(pBt), ppSnapshot);
}
}
}
sqlite3_mutex_leave(db->mutex);
#endif /* SQLITE_OMIT_WAL */
return rc;
}
/*
** Open a read-transaction on the snapshot idendified by pSnapshot.
*/
int sqlite3_snapshot_open(
sqlite3 *db,
const char *zDb,
sqlite3_snapshot *pSnapshot
){
int rc = SQLITE_ERROR;
#ifndef SQLITE_OMIT_WAL
#ifdef SQLITE_ENABLE_API_ARMOR
if( !sqlite3SafetyCheckOk(db) ){
return SQLITE_MISUSE_BKPT;
}
#endif
sqlite3_mutex_enter(db->mutex);
if( db->autoCommit==0 ){
int iDb;
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( rc==SQLITE_OK ){
rc = sqlite3BtreeBeginTrans(pBt, 0);
sqlite3PagerSnapshotOpen(sqlite3BtreePager(pBt), 0);
}
}
}
}
sqlite3_mutex_leave(db->mutex);
#endif /* SQLITE_OMIT_WAL */
return rc;
}
/*
** Free a snapshot handle obtained from sqlite3_snapshot_get().
*/
void sqlite3_snapshot_free(sqlite3_snapshot *pSnapshot){
sqlite3_free(pSnapshot);
}
#endif /* SQLITE_ENABLE_SNAPSHOT */

View File

@ -7301,6 +7301,34 @@ int sqlite3PagerCloseWal(Pager *pPager){
return rc;
}
#ifdef SQLITE_ENABLE_SNAPSHOT
/*
** If this is a WAL database, obtain a snapshot handle for the snapshot
** currently open. Otherwise, return an error.
*/
int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot){
int rc = SQLITE_ERROR;
if( pPager->pWal ){
rc = sqlite3WalSnapshotGet(pPager->pWal, ppSnapshot);
}
return rc;
}
/*
** If this is a WAL database, store a pointer to pSnapshot. Next time a
** read transaction is opened, attempt to read from the snapshot it
** identifies. If this is not a WAL database, return an error.
*/
int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot){
int rc = SQLITE_OK;
if( pPager->pWal ){
sqlite3WalSnapshotOpen(pPager->pWal, pSnapshot);
}else{
rc = SQLITE_ERROR;
}
return rc;
}
#endif /* SQLITE_ENABLE_SNAPSHOT */
#endif /* !SQLITE_OMIT_WAL */
#ifdef SQLITE_ENABLE_ZIPVFS

View File

@ -168,6 +168,10 @@ int sqlite3PagerSharedLock(Pager *pPager);
int sqlite3PagerWalCallback(Pager *pPager);
int sqlite3PagerOpenWal(Pager *pPager, int *pisOpen);
int sqlite3PagerCloseWal(Pager *pPager);
# ifdef SQLITE_ENABLE_SNAPSHOT
int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot);
int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot);
# endif
#endif
#ifdef SQLITE_ENABLE_ZIPVFS

View File

@ -4405,8 +4405,8 @@ unsigned int sqlite3_value_subtype(sqlite3_value*);
** previously obtained from [sqlite3_value_dup()]. ^If V is a NULL pointer
** then sqlite3_value_free(V) is a harmless no-op.
*/
SQLITE_EXPERIMENTAL sqlite3_value *sqlite3_value_dup(const sqlite3_value*);
SQLITE_EXPERIMENTAL void sqlite3_value_free(sqlite3_value*);
sqlite3_value *sqlite3_value_dup(const sqlite3_value*);
void sqlite3_value_free(sqlite3_value*);
/*
** CAPI3REF: Obtain Aggregate Function Context
@ -7851,33 +7851,127 @@ void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
/*
** CAPI3REF: Flush caches to disk mid-transaction
**
** If a write-transaction is open when this function is called, any dirty
** ^If a write-transaction is open on [database connection] D when the
** [sqlite3_db_cacheflush(D)] interface invoked, any dirty
** pages in the pager-cache that are not currently in use are written out
** to disk. A dirty page may be in use if a database cursor created by an
** active SQL statement is reading from it, or if it is page 1 of a database
** file (page 1 is always "in use"). Dirty pages are flushed for all
** databases - "main", "temp" and any attached databases.
** file (page 1 is always "in use"). ^The [sqlite3_db_cacheflush(D)]
** interface flushes caches for all schemas - "main", "temp", and
** any [attached] databases.
**
** If this function needs to obtain extra database locks before dirty pages
** can be flushed to disk, it does so. If said locks cannot be obtained
** ^If this function needs to obtain extra database locks before dirty pages
** can be flushed to disk, it does so. ^If those locks cannot be obtained
** immediately and there is a busy-handler callback configured, it is invoked
** in the usual manner. If the required lock still cannot be obtained, then
** in the usual manner. ^If the required lock still cannot be obtained, then
** the database is skipped and an attempt made to flush any dirty pages
** belonging to the next (if any) database. If any databases are skipped
** belonging to the next (if any) database. ^If any databases are skipped
** because locks cannot be obtained, but no other error occurs, this
** function returns SQLITE_BUSY.
**
** If any other error occurs while flushing dirty pages to disk (for
** ^If any other error occurs while flushing dirty pages to disk (for
** example an IO error or out-of-memory condition), then processing is
** abandoned and an SQLite error code returned to the caller immediately.
** abandoned and an SQLite [error code] is returned to the caller immediately.
**
** Otherwise, if no error occurs, SQLITE_OK is returned.
** ^Otherwise, if no error occurs, [sqlite3_db_cacheflush()] returns SQLITE_OK.
**
** This function does not set the database handle error code or message
** returned by the sqlite3_errcode() and sqlite3_errmsg() functions.
** ^This function does not set the database handle error code or message
** returned by the [sqlite3_errcode()] and [sqlite3_errmsg()] functions.
*/
int sqlite3_db_cacheflush(sqlite3*);
/*
** CAPI3REF: Database Snapshot
** KEYWORDS: {snapshot}
** EXPERIMENTAL
**
** An instance of the snapshot object records the state of a [WAL mode]
** database for some specific point in history.
**
** In [WAL mode], multiple [database connections] that are open on the
** same database file can each be reading a different historical version
** of the database file. When a [database connection] begins a read
** transaction, that connection sees an unchanging copy of the database
** as it existed for the point in time when the transaction first started.
** Subsequent changes to the database from other connections are not seen
** by the reader until a new read transaction is started.
**
** The sqlite3_snapshot object records state information about an historical
** version of the database file so that it is possible to later open a new read
** transaction that sees that historical version of the database rather than
** the most recent version.
**
** The constructor for this object is [sqlite3_snapshot_get()]. The
** [sqlite3_snapshot_open()] method causes a fresh read transaction to refer
** to an historical snapshot (if possible). The destructor for
** sqlite3_snapshot objects is [sqlite3_snapshot_free()].
*/
typedef struct sqlite3_snapshot sqlite3_snapshot;
/*
** CAPI3REF: Record A Database Snapshot
** EXPERIMENTAL
**
** ^The [sqlite3_snapshot_get(D,S,P)] interface attempts to make a
** new [sqlite3_snapshot] object that records the current state of
** schema S in database connection D. ^On success, the
** [sqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly
** created [sqlite3_snapshot] object into *P and returns SQLITE_OK.
** ^If schema S of [database connection] D is not a [WAL mode] database
** that is in a read transaction, then [sqlite3_snapshot_get(D,S,P)]
** leaves the *P value unchanged and returns an appropriate [error code].
**
** The [sqlite3_snapshot] object returned from a successful call to
** [sqlite3_snapshot_get()] must be freed using [sqlite3_snapshot_free()]
** to avoid a memory leak.
**
** The [sqlite3_snapshot_get()] interface is only available when the
** SQLITE_ENABLE_SNAPSHOT compile-time option is used.
*/
SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
sqlite3 *db,
const char *zSchema,
sqlite3_snapshot **ppSnapshot
);
/*
** CAPI3REF: Start a read transaction on an historical snapshot
** EXPERIMENTAL
**
** ^The [sqlite3_snapshot_open(D,S,P)] interface attempts to move the
** read transaction that is currently open on schema S of
** [database connection] D so that it refers to historical [snapshot] P.
** ^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, apart from other sqlite3_snapshot_open() calls,
** following the [BEGIN] that starts a new read transaction.
** ^A [snapshot] will fail to open if it has been overwritten by a
** [checkpoint].
**
** The [sqlite3_snapshot_open()] interface is only available when the
** SQLITE_ENABLE_SNAPSHOT compile-time option is used.
*/
SQLITE_EXPERIMENTAL int sqlite3_snapshot_open(
sqlite3 *db,
const char *zSchema,
sqlite3_snapshot *pSnapshot
);
/*
** CAPI3REF: Destroy a snapshot
** EXPERIMENTAL
**
** ^The [sqlite3_snapshot_free(P)] interface destroys [sqlite3_snapshot] P.
** The application must eventually free every [sqlite3_snapshot] object
** using this routine to avoid a memory leak.
**
** The [sqlite3_snapshot_free()] interface is only available when the
** SQLITE_ENABLE_SNAPSHOT compile-time option is used.
*/
SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*);
/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.

View File

@ -2269,6 +2269,94 @@ static int vfsCurrentTimeInt64(
return TCL_OK;
}
#ifdef SQLITE_ENABLE_SNAPSHOT
/*
** Usage: sqlite3_snapshot_get DB DBNAME
*/
static int test_snapshot_get(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
int rc;
sqlite3 *db;
char *zName;
sqlite3_snapshot *pSnapshot = 0;
if( objc!=3 ){
Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME");
return TCL_ERROR;
}
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
zName = Tcl_GetString(objv[2]);
rc = sqlite3_snapshot_get(db, zName, &pSnapshot);
if( rc!=SQLITE_OK ){
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
return TCL_ERROR;
}else{
char zBuf[100];
if( sqlite3TestMakePointerStr(interp, zBuf, pSnapshot) ) return TCL_ERROR;
Tcl_SetObjResult(interp, Tcl_NewStringObj(zBuf, -1));
}
return TCL_OK;
}
#endif /* SQLITE_ENABLE_SNAPSHOT */
#ifdef SQLITE_ENABLE_SNAPSHOT
/*
** Usage: sqlite3_snapshot_open DB DBNAME SNAPSHOT
*/
static int test_snapshot_open(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
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_open(db, zName, pSnapshot);
if( rc!=SQLITE_OK ){
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
return TCL_ERROR;
}
return TCL_OK;
}
#endif /* SQLITE_ENABLE_SNAPSHOT */
#ifdef SQLITE_ENABLE_SNAPSHOT
/*
** Usage: sqlite3_snapshot_free SNAPSHOT
*/
static int test_snapshot_free(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
sqlite3_snapshot *pSnapshot;
if( objc!=2 ){
Tcl_WrongNumArgs(interp, 1, objv, "SNAPSHOT");
return TCL_ERROR;
}
pSnapshot = (sqlite3_snapshot*)sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
sqlite3_snapshot_free(pSnapshot);
return TCL_OK;
}
#endif /* SQLITE_ENABLE_SNAPSHOT */
/*
** Usage: sqlite3_next_stmt DB STMT
**
@ -7083,6 +7171,11 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
{ "sqlite3_config_sqllog", test_config_sqllog, 0 },
#endif
{ "vfs_current_time_int64", vfsCurrentTimeInt64, 0 },
#ifdef SQLITE_ENABLE_SNAPSHOT
{ "sqlite3_snapshot_get", test_snapshot_get, 0 },
{ "sqlite3_snapshot_open", test_snapshot_open, 0 },
{ "sqlite3_snapshot_free", test_snapshot_free, 0 },
#endif
};
static int bitmask_size = sizeof(Bitmask)*8;
static int longdouble_size = sizeof(LONGDOUBLE_TYPE);

View File

@ -143,6 +143,12 @@ static void set_options(Tcl_Interp *interp){
Tcl_SetVar2(interp, "sqlite_options", "mem5", "0", TCL_GLOBAL_ONLY);
#endif
#ifdef SQLITE_ENABLE_SNAPSHOT
Tcl_SetVar2(interp, "sqlite_options", "snapshot", "1", TCL_GLOBAL_ONLY);
#else
Tcl_SetVar2(interp, "sqlite_options", "snapshot", "0", TCL_GLOBAL_ONLY);
#endif
#ifdef SQLITE_MUTEX_OMIT
Tcl_SetVar2(interp, "sqlite_options", "mutex", "0", TCL_GLOBAL_ONLY);
#else

282
src/wal.c
View File

@ -272,7 +272,8 @@ int sqlite3WalTrace = 0;
/*
** Indices of various locking bytes. WAL_NREADER is the number
** of available reader locks and should be at least 3.
** of available reader locks and should be at least 3. The default
** is SQLITE_SHM_NLOCK==8 and WAL_NREADER==5.
*/
#define WAL_WRITE_LOCK 0
#define WAL_ALL_BUT_WRITE 1
@ -292,7 +293,10 @@ typedef struct WalCkptInfo WalCkptInfo;
** The following object holds a copy of the wal-index header content.
**
** The actual header in the wal-index consists of two copies of this
** object.
** object followed by one instance of the WalCkptInfo object.
** For all versions of SQLite through 3.10.0 and probably beyond,
** the locking bytes (WalCkptInfo.aLock) start at offset 120 and
** the total header size is 136 bytes.
**
** The szPage value can be any power of 2 between 512 and 32768, inclusive.
** Or it can be 1 to represent a 65536-byte page. The latter case was
@ -325,6 +329,16 @@ struct WalIndexHdr {
** However, a WAL_WRITE_LOCK thread can move the value of nBackfill from
** mxFrame back to zero when the WAL is reset.
**
** nBackfillAttempted is the largest value of nBackfill that a checkpoint
** has attempted to achieve. Normally nBackfill==nBackfillAtempted, however
** the nBackfillAttempted is set before any backfilling is done and the
** nBackfill is only set after all backfilling completes. So if a checkpoint
** crashes, nBackfillAttempted might be larger than nBackfill. The
** WalIndexHdr.mxFrame must never be less than nBackfillAttempted.
**
** The aLock[] field is a set of bytes used for locking. These bytes should
** never be read or written.
**
** There is one entry in aReadMark[] for each reader lock. If a reader
** holds read-lock K, then the value in aReadMark[K] is no greater than
** the mxFrame for that reader. The value READMARK_NOT_USED (0xffffffff)
@ -364,6 +378,9 @@ struct WalIndexHdr {
struct WalCkptInfo {
u32 nBackfill; /* Number of WAL frames backfilled into DB */
u32 aReadMark[WAL_NREADER]; /* Reader marks */
u8 aLock[SQLITE_SHM_NLOCK]; /* Reserved space for locks */
u32 nBackfillAttempted; /* WAL frames perhaps written, or maybe not */
u32 notUsed0; /* Available for future enhancements */
};
#define READMARK_NOT_USED 0xffffffff
@ -373,9 +390,8 @@ struct WalCkptInfo {
** only support mandatory file-locks, we do not read or write data
** from the region of the file on which locks are applied.
*/
#define WALINDEX_LOCK_OFFSET (sizeof(WalIndexHdr)*2 + sizeof(WalCkptInfo))
#define WALINDEX_LOCK_RESERVED 16
#define WALINDEX_HDR_SIZE (WALINDEX_LOCK_OFFSET+WALINDEX_LOCK_RESERVED)
#define WALINDEX_LOCK_OFFSET (sizeof(WalIndexHdr)*2+offsetof(WalCkptInfo,aLock))
#define WALINDEX_HDR_SIZE (sizeof(WalIndexHdr)*2+sizeof(WalCkptInfo))
/* Size of header before each frame in wal */
#define WAL_FRAME_HDRSIZE 24
@ -434,6 +450,9 @@ struct Wal {
#ifdef SQLITE_DEBUG
u8 lockError; /* True if a locking error has occurred */
#endif
#ifdef SQLITE_ENABLE_SNAPSHOT
WalIndexHdr *pSnapshot; /* Start transaction here if not NULL */
#endif
};
/*
@ -1198,6 +1217,7 @@ finished:
*/
pInfo = walCkptInfo(pWal);
pInfo->nBackfill = 0;
pInfo->nBackfillAttempted = pWal->hdr.mxFrame;
pInfo->aReadMark[0] = 0;
for(i=1; i<WAL_NREADER; i++) pInfo->aReadMark[i] = READMARK_NOT_USED;
if( pWal->hdr.mxFrame ) pInfo->aReadMark[1] = pWal->hdr.mxFrame;
@ -1269,7 +1289,11 @@ int sqlite3WalOpen(
/* In the amalgamation, the os_unix.c and os_win.c source files come before
** this source file. Verify that the #defines of the locking byte offsets
** in os_unix.c and os_win.c agree with the WALINDEX_LOCK_OFFSET value.
** For that matter, if the lock offset ever changes from its initial design
** value of 120, we need to know that so there is an assert() to check it.
*/
assert( 120==WALINDEX_LOCK_OFFSET );
assert( 136==WALINDEX_HDR_SIZE );
#ifdef WIN_SHM_BASE
assert( WIN_SHM_BASE==WALINDEX_LOCK_OFFSET );
#endif
@ -1655,6 +1679,7 @@ static void walRestartHdr(Wal *pWal, u32 salt1){
memcpy(&pWal->hdr.aSalt[1], &salt1, 4);
walIndexWriteHdr(pWal);
pInfo->nBackfill = 0;
pInfo->nBackfillAttempted = 0;
pInfo->aReadMark[1] = 0;
for(i=2; i<WAL_NREADER; i++) pInfo->aReadMark[i] = READMARK_NOT_USED;
assert( pInfo->aReadMark[0]==0 );
@ -1764,6 +1789,8 @@ static int walCheckpoint(
i64 nSize; /* Current size of database file */
u32 nBackfill = pInfo->nBackfill;
pInfo->nBackfillAttempted = mxSafeFrame;
/* Sync the WAL to disk */
if( sync_flags ){
rc = sqlite3OsSync(pWal->pWalFd, sync_flags);
@ -2147,6 +2174,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
int mxI; /* Index of largest aReadMark[] value */
int i; /* Loop counter */
int rc = SQLITE_OK; /* Return code */
u32 mxFrame; /* Wal frame to lock to */
assert( pWal->readLock<0 ); /* Not currently locked */
@ -2210,7 +2238,12 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
}
pInfo = walCkptInfo(pWal);
if( !useWal && pInfo->nBackfill==pWal->hdr.mxFrame ){
if( !useWal && pInfo->nBackfill==pWal->hdr.mxFrame
#ifdef SQLITE_ENABLE_SNAPSHOT
&& (pWal->pSnapshot==0 || pWal->hdr.mxFrame==0
|| 0==memcmp(&pWal->hdr, pWal->pSnapshot, sizeof(WalIndexHdr)))
#endif
){
/* The WAL has been completely backfilled (or it is empty).
** and can be safely ignored.
*/
@ -2248,85 +2281,88 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
*/
mxReadMark = 0;
mxI = 0;
mxFrame = pWal->hdr.mxFrame;
#ifdef SQLITE_ENABLE_SNAPSHOT
if( pWal->pSnapshot && pWal->pSnapshot->mxFrame<mxFrame ){
mxFrame = pWal->pSnapshot->mxFrame;
}
#endif
for(i=1; i<WAL_NREADER; i++){
u32 thisMark = pInfo->aReadMark[i];
if( mxReadMark<=thisMark && thisMark<=pWal->hdr.mxFrame ){
if( mxReadMark<=thisMark && thisMark<=mxFrame ){
assert( thisMark!=READMARK_NOT_USED );
mxReadMark = thisMark;
mxI = i;
}
}
/* There was once an "if" here. The extra "{" is to preserve indentation. */
{
if( (pWal->readOnly & WAL_SHM_RDONLY)==0
&& (mxReadMark<pWal->hdr.mxFrame || mxI==0)
){
for(i=1; i<WAL_NREADER; i++){
rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1);
if( rc==SQLITE_OK ){
mxReadMark = pInfo->aReadMark[i] = pWal->hdr.mxFrame;
mxI = i;
walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1);
break;
}else if( rc!=SQLITE_BUSY ){
return rc;
}
if( (pWal->readOnly & WAL_SHM_RDONLY)==0
&& (mxReadMark<mxFrame || mxI==0)
){
for(i=1; i<WAL_NREADER; i++){
rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1);
if( rc==SQLITE_OK ){
mxReadMark = pInfo->aReadMark[i] = mxFrame;
mxI = i;
walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1);
break;
}else if( rc!=SQLITE_BUSY ){
return rc;
}
}
if( mxI==0 ){
assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 );
return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTLOCK;
}
}
if( mxI==0 ){
assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 );
return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTLOCK;
}
rc = walLockShared(pWal, WAL_READ_LOCK(mxI));
if( rc ){
return rc==SQLITE_BUSY ? WAL_RETRY : rc;
}
/* Now that the read-lock has been obtained, check that neither the
** value in the aReadMark[] array or the contents of the wal-index
** header have changed.
**
** It is necessary to check that the wal-index header did not change
** between the time it was read and when the shared-lock was obtained
** on WAL_READ_LOCK(mxI) was obtained to account for the possibility
** that the log file may have been wrapped by a writer, or that frames
** that occur later in the log than pWal->hdr.mxFrame may have been
** copied into the database by a checkpointer. If either of these things
** happened, then reading the database with the current value of
** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry
** instead.
**
** Before checking that the live wal-index header has not changed
** since it was read, set Wal.minFrame to the first frame in the wal
** file that has not yet been checkpointed. This client will not need
** to read any frames earlier than minFrame from the wal file - they
** can be safely read directly from the database file.
**
** Because a ShmBarrier() call is made between taking the copy of
** nBackfill and checking that the wal-header in shared-memory still
** matches the one cached in pWal->hdr, it is guaranteed that the
** checkpointer that set nBackfill was not working with a wal-index
** header newer than that cached in pWal->hdr. If it were, that could
** cause a problem. The checkpointer could omit to checkpoint
** a version of page X that lies before pWal->minFrame (call that version
** A) on the basis that there is a newer version (version B) of the same
** page later in the wal file. But if version B happens to like past
** frame pWal->hdr.mxFrame - then the client would incorrectly assume
** that it can read version A from the database file. However, since
** we can guarantee that the checkpointer that set nBackfill could not
** see any pages past pWal->hdr.mxFrame, this problem does not come up.
*/
pWal->minFrame = pInfo->nBackfill+1;
walShmBarrier(pWal);
if( pInfo->aReadMark[mxI]!=mxReadMark
|| memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr))
){
walUnlockShared(pWal, WAL_READ_LOCK(mxI));
return WAL_RETRY;
}else{
assert( mxReadMark<=pWal->hdr.mxFrame );
pWal->readLock = (i16)mxI;
}
rc = walLockShared(pWal, WAL_READ_LOCK(mxI));
if( rc ){
return rc==SQLITE_BUSY ? WAL_RETRY : rc;
}
/* Now that the read-lock has been obtained, check that neither the
** value in the aReadMark[] array or the contents of the wal-index
** header have changed.
**
** It is necessary to check that the wal-index header did not change
** between the time it was read and when the shared-lock was obtained
** on WAL_READ_LOCK(mxI) was obtained to account for the possibility
** that the log file may have been wrapped by a writer, or that frames
** that occur later in the log than pWal->hdr.mxFrame may have been
** copied into the database by a checkpointer. If either of these things
** happened, then reading the database with the current value of
** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry
** instead.
**
** Before checking that the live wal-index header has not changed
** since it was read, set Wal.minFrame to the first frame in the wal
** file that has not yet been checkpointed. This client will not need
** to read any frames earlier than minFrame from the wal file - they
** can be safely read directly from the database file.
**
** Because a ShmBarrier() call is made between taking the copy of
** nBackfill and checking that the wal-header in shared-memory still
** matches the one cached in pWal->hdr, it is guaranteed that the
** checkpointer that set nBackfill was not working with a wal-index
** header newer than that cached in pWal->hdr. If it were, that could
** cause a problem. The checkpointer could omit to checkpoint
** a version of page X that lies before pWal->minFrame (call that version
** A) on the basis that there is a newer version (version B) of the same
** page later in the wal file. But if version B happens to like past
** frame pWal->hdr.mxFrame - then the client would incorrectly assume
** that it can read version A from the database file. However, since
** we can guarantee that the checkpointer that set nBackfill could not
** see any pages past pWal->hdr.mxFrame, this problem does not come up.
*/
pWal->minFrame = pInfo->nBackfill+1;
walShmBarrier(pWal);
if( pInfo->aReadMark[mxI]!=mxReadMark
|| memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr))
){
walUnlockShared(pWal, WAL_READ_LOCK(mxI));
return WAL_RETRY;
}else{
assert( mxReadMark<=pWal->hdr.mxFrame );
pWal->readLock = (i16)mxI;
}
return rc;
}
@ -2349,6 +2385,14 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){
int rc; /* Return code */
int cnt = 0; /* Number of TryBeginRead attempts */
#ifdef SQLITE_ENABLE_SNAPSHOT
int bChanged = 0;
WalIndexHdr *pSnapshot = pWal->pSnapshot;
if( pSnapshot && memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr))!=0 ){
bChanged = 1;
}
#endif
do{
rc = walTryBeginRead(pWal, pChanged, 0, ++cnt);
}while( rc==WAL_RETRY );
@ -2356,6 +2400,65 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){
testcase( (rc&0xff)==SQLITE_IOERR );
testcase( rc==SQLITE_PROTOCOL );
testcase( rc==SQLITE_OK );
#ifdef SQLITE_ENABLE_SNAPSHOT
if( rc==SQLITE_OK ){
if( pSnapshot && memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr))!=0 ){
/* At this point the client has a lock on an aReadMark[] slot holding
** a value equal to or smaller than pSnapshot->mxFrame, but pWal->hdr
** is populated with the wal-index header corresponding to the head
** of the wal file. Verify that pSnapshot is still valid before
** continuing. Reasons why pSnapshot might no longer be valid:
**
** (1) The WAL file has been reset since the snapshot was taken.
** In this case, the salt will have changed.
**
** (2) A checkpoint as been attempted that wrote frames past
** pSnapshot->mxFrame into the database file. Note that the
** checkpoint need not have completed for this to cause problems.
*/
volatile WalCkptInfo *pInfo = walCkptInfo(pWal);
assert( pWal->readLock>0 || pWal->hdr.mxFrame==0 );
assert( pInfo->aReadMark[pWal->readLock]<=pSnapshot->mxFrame );
/* It is possible that there is a checkpointer thread running
** concurrent with this code. If this is the case, it may be that the
** checkpointer has already determined that it will checkpoint
** snapshot X, where X is later in the wal file than pSnapshot, but
** has not yet set the pInfo->nBackfillAttempted variable to indicate
** its intent. To avoid the race condition this leads to, ensure that
** there is no checkpointer process by taking a shared CKPT lock
** before checking pInfo->nBackfillAttempted. */
rc = walLockShared(pWal, WAL_CKPT_LOCK);
if( rc==SQLITE_OK ){
/* Check that the wal file has not been wrapped. Assuming that it has
** not, also check that no checkpointer has attempted to checkpoint any
** frames beyond pSnapshot->mxFrame. If either of these conditions are
** true, return SQLITE_BUSY_SNAPSHOT. Otherwise, overwrite pWal->hdr
** with *pSnapshot and set *pChanged as appropriate for opening the
** snapshot. */
if( !memcmp(pSnapshot->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt))
&& pSnapshot->mxFrame>=pInfo->nBackfillAttempted
){
memcpy(&pWal->hdr, pSnapshot, sizeof(WalIndexHdr));
*pChanged = bChanged;
}else{
rc = SQLITE_BUSY_SNAPSHOT;
}
/* Release the shared CKPT lock obtained above. */
walUnlockShared(pWal, WAL_CKPT_LOCK);
}
if( rc!=SQLITE_OK ){
sqlite3WalEndReadTransaction(pWal);
}
}
}
#endif
return rc;
}
@ -3165,6 +3268,35 @@ int sqlite3WalHeapMemory(Wal *pWal){
return (pWal && pWal->exclusiveMode==WAL_HEAPMEMORY_MODE );
}
#ifdef SQLITE_ENABLE_SNAPSHOT
/* Create a snapshot object. The content of a snapshot is opaque to
** every other subsystem, so the WAL module can put whatever it needs
** in the object.
*/
int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot){
int rc = SQLITE_OK;
WalIndexHdr *pRet;
assert( pWal->readLock>=0 && pWal->writeLock==0 );
pRet = (WalIndexHdr*)sqlite3_malloc(sizeof(WalIndexHdr));
if( pRet==0 ){
rc = SQLITE_NOMEM;
}else{
memcpy(pRet, &pWal->hdr, sizeof(WalIndexHdr));
*ppSnapshot = (sqlite3_snapshot*)pRet;
}
return rc;
}
/* Try to open on pSnapshot when the next read-transaction starts
*/
void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot){
pWal->pSnapshot = (WalIndexHdr*)pSnapshot;
}
#endif /* SQLITE_ENABLE_SNAPSHOT */
#ifdef SQLITE_ENABLE_ZIPVFS
/*
** If the argument is not NULL, it points to a Wal object that holds a

View File

@ -126,6 +126,11 @@ int sqlite3WalExclusiveMode(Wal *pWal, int op);
*/
int sqlite3WalHeapMemory(Wal *pWal);
#ifdef SQLITE_ENABLE_SNAPSHOT
int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot);
void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot);
#endif
#ifdef SQLITE_ENABLE_ZIPVFS
/* If the WAL file is not empty, return the number of bytes of content
** stored in each frame (i.e. the db page-size when the WAL was created).

340
test/snapshot.test Normal file
View File

@ -0,0 +1,340 @@
# 2015 December 7
#
# 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.
#
#***********************************************************************
# This file implements regression tests for SQLite library. The focus
# of this file is the sqlite3_snapshot_xxx() APIs.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
ifcapable !snapshot {finish_test; return}
set testprefix snapshot
#-------------------------------------------------------------------------
# Check some error conditions in snapshot_get(). It is an error if:
#
# 1) snapshot_get() is called on a non-WAL database, or
# 2) there is an open write transaction on the database.
#
do_execsql_test 1.0 {
CREATE TABLE t1(a, b);
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t1 VALUES(3, 4);
}
do_test 1.1.1 {
execsql { BEGIN; SELECT * FROM t1; }
list [catch { sqlite3_snapshot_get db main } msg] $msg
} {1 SQLITE_ERROR}
do_execsql_test 1.1.2 COMMIT
do_test 1.2.1 {
execsql {
PRAGMA journal_mode = WAL;
BEGIN;
INSERT INTO t1 VALUES(5, 6);
INSERT INTO t1 VALUES(7, 8);
}
list [catch { sqlite3_snapshot_get db main } msg] $msg
} {1 SQLITE_ERROR}
do_execsql_test 1.3.2 COMMIT
#-------------------------------------------------------------------------
# Check that a simple case works. Reuse the database created by the
# block of tests above.
#
do_execsql_test 2.1.0 {
BEGIN;
SELECT * FROM t1;
} {1 2 3 4 5 6 7 8}
breakpoint
do_test 2.1.1 {
set snapshot [sqlite3_snapshot_get db main]
execsql {
COMMIT;
INSERT INTO t1 VALUES(9, 10);
SELECT * FROM t1;
}
} {1 2 3 4 5 6 7 8 9 10}
do_test 2.1.2 {
execsql BEGIN
sqlite3_snapshot_open db main $snapshot
execsql {
SELECT * FROM t1;
}
} {1 2 3 4 5 6 7 8}
do_test 2.1.3 {
sqlite3_snapshot_free $snapshot
execsql COMMIT
} {}
do_test 2.2.0 {
sqlite3 db2 test.db
execsql {
BEGIN;
SELECT * FROM t1;
} db2
} {1 2 3 4 5 6 7 8 9 10}
do_test 2.2.1 {
set snapshot [sqlite3_snapshot_get db2 main]
execsql {
INSERT INTO t1 VALUES(11, 12);
SELECT * FROM t1;
}
} {1 2 3 4 5 6 7 8 9 10 11 12}
do_test 2.2.2 {
execsql BEGIN
sqlite3_snapshot_open db main $snapshot
execsql {
SELECT * FROM t1;
}
} {1 2 3 4 5 6 7 8 9 10}
do_test 2.2.3 {
sqlite3_snapshot_free $snapshot
execsql COMMIT
execsql COMMIT db2
db2 close
} {}
do_test 2.3.1 {
execsql { DELETE FROM t1 WHERE a>6 }
set snapshot [sqlite3_snapshot_get db main]
execsql {
INSERT INTO t1 VALUES('a', 'b');
INSERT INTO t1 VALUES('c', 'd');
SELECT * FROM t1;
}
} {1 2 3 4 5 6 a b c d}
do_test 2.3.2 {
execsql BEGIN
sqlite3_snapshot_open db main $snapshot
execsql { SELECT * FROM t1 }
} {1 2 3 4 5 6}
do_test 2.3.3 {
catchsql {
INSERT INTO t1 VALUES('x','y')
}
} {1 {database is locked}}
do_test 2.3.4 {
execsql COMMIT
sqlite3_snapshot_free $snapshot
} {}
#-------------------------------------------------------------------------
# Check some errors in sqlite3_snapshot_open(). It is an error if:
#
# 1) the db is in auto-commit mode,
# 2) the db has an open (read or write) transaction,
# 3) the db is not a wal database,
#
# Reuse the database created by earlier tests.
#
do_execsql_test 3.0.0 {
CREATE TABLE t2(x, y);
INSERT INTO t2 VALUES('a', 'b');
INSERT INTO t2 VALUES('c', 'd');
BEGIN;
SELECT * FROM t2;
} {a b c d}
do_test 3.0.1 {
set snapshot [sqlite3_snapshot_get db main]
execsql { COMMIT }
execsql { INSERT INTO t2 VALUES('e', 'f'); }
} {}
do_test 3.1 {
list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg
} {1 SQLITE_ERROR}
do_test 3.2.1 {
execsql {
BEGIN;
SELECT * FROM t2;
}
} {a b c d e f}
do_test 3.2.2 {
list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg
} {1 SQLITE_ERROR}
do_test 3.2.3 {
execsql {
COMMIT;
BEGIN;
INSERT INTO t2 VALUES('g', 'h');
}
list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg
} {1 SQLITE_ERROR}
do_execsql_test 3.2.4 COMMIT
do_test 3.3.1 {
execsql { PRAGMA journal_mode = DELETE }
execsql { BEGIN }
list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg
} {1 SQLITE_ERROR}
do_test 3.3.2 {
sqlite3_snapshot_free $snapshot
execsql COMMIT
} {}
#-------------------------------------------------------------------------
# Check that SQLITE_BUSY_SNAPSHOT is returned if the specified snapshot
# no longer exists because the wal file has been checkpointed.
#
# 1. Reading a snapshot from the middle of a wal file is not possible
# after the wal file has been checkpointed.
#
# 2. That a snapshot from the end of a wal file can not be read once
# the wal file has been wrapped.
#
do_execsql_test 4.1.0 {
PRAGMA journal_mode = wal;
CREATE TABLE t3(i, j);
INSERT INTO t3 VALUES('o', 't');
INSERT INTO t3 VALUES('t', 'f');
BEGIN;
SELECT * FROM t3;
} {wal o t t f}
do_test 4.1.1 {
set snapshot [sqlite3_snapshot_get db main]
execsql COMMIT
} {}
do_test 4.1.2 {
execsql {
INSERT INTO t3 VALUES('f', 's');
BEGIN;
}
sqlite3_snapshot_open db main $snapshot
execsql { SELECT * FROM t3 }
} {o t t f}
do_test 4.1.3 {
execsql {
COMMIT;
PRAGMA wal_checkpoint;
BEGIN;
}
list [catch {sqlite3_snapshot_open db main $snapshot} msg] $msg
} {1 SQLITE_BUSY_SNAPSHOT}
do_test 4.1.4 {
sqlite3_snapshot_free $snapshot
execsql COMMIT
} {}
do_test 4.2.1 {
execsql {
INSERT INTO t3 VALUES('s', 'e');
INSERT INTO t3 VALUES('n', 't');
BEGIN;
SELECT * FROM t3;
}
} {o t t f f s s e n t}
do_test 4.2.2 {
set snapshot [sqlite3_snapshot_get db main]
execsql {
COMMIT;
PRAGMA wal_checkpoint;
BEGIN;
}
sqlite3_snapshot_open db main $snapshot
execsql { SELECT * FROM t3 }
} {o t t f f s s e n t}
do_test 4.2.3 {
execsql {
COMMIT;
INSERT INTO t3 VALUES('e', 't');
BEGIN;
}
list [catch {sqlite3_snapshot_open db main $snapshot} msg] $msg
} {1 SQLITE_BUSY_SNAPSHOT}
do_test 4.2.4 {
sqlite3_snapshot_free $snapshot
} {}
#-------------------------------------------------------------------------
# Check that SQLITE_BUSY is returned if a checkpoint is running when
# sqlite3_snapshot_open() is called.
#
reset_db
db close
testvfs tvfs
sqlite3 db test.db -vfs tvfs
do_execsql_test 5.1 {
PRAGMA journal_mode = wal;
CREATE TABLE x1(x, xx, xxx);
INSERT INTO x1 VALUES('z', 'zz', 'zzz');
BEGIN;
SELECT * FROM x1;
} {wal z zz zzz}
do_test 5.2 {
set ::snapshot [sqlite3_snapshot_get db main]
sqlite3 db2 test.db -vfs tvfs
execsql {
INSERT INTO x1 VALUES('a', 'aa', 'aaa');
COMMIT;
}
} {}
set t53 0
proc write_callback {args} {
do_test 5.3.[incr ::t53] {
execsql BEGIN
list [catch { sqlite3_snapshot_open db main $::snapshot } msg] $msg
} {1 SQLITE_BUSY}
catchsql COMMIT
}
tvfs filter xWrite
tvfs script write_callback
db2 eval { PRAGMA wal_checkpoint }
db close
db2 close
tvfs delete
sqlite3_snapshot_free $snapshot
#-------------------------------------------------------------------------
# Test that sqlite3_snapshot_get() may be called immediately after
# "BEGIN; PRAGMA user_version;". And that sqlite3_snapshot_open() may
# be called after opening the db handle and running the script
# "PRAGMA user_version; BEGIN".
reset_db
do_execsql_test 6.1 {
PRAGMA journal_mode = wal;
CREATE TABLE x1(x, xx, xxx);
INSERT INTO x1 VALUES('z', 'zz', 'zzz');
BEGIN;
PRAGMA user_version;
} {wal 0}
do_test 6.2 {
set ::snapshot [sqlite3_snapshot_get db main]
execsql {
INSERT INTO x1 VALUES('a', 'aa', 'aaa');
COMMIT;
}
} {}
do_test 6.3 {
sqlite3 db2 test.db
db2 eval "PRAGMA user_version ; BEGIN"
sqlite3_snapshot_open db2 main $::snapshot
db2 eval { SELECT * FROM x1 }
} {z zz zzz}
sqlite3_snapshot_free $snapshot
finish_test

164
test/snapshot_fault.test Normal file
View File

@ -0,0 +1,164 @@
# 2015 December 10
#
# 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.
#
#***********************************************************************
# This file implements regression tests for SQLite library. The focus
# of this file is the sqlite3_snapshot_xxx() APIs.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
ifcapable !snapshot {finish_test; return}
set testprefix snapshot_fault
#-------------------------------------------------------------------------
# Check that an sqlite3_snapshot_open() client cannot be tricked into
# reading a corrupt snapshot even if a second client fails while
# checkpointing the db.
#
do_faultsim_test 1.0 -prep {
faultsim_delete_and_reopen
sqlite3 db2 test.db
db2 eval {
CREATE TABLE t1(a, b UNIQUE, c UNIQUE);
INSERT INTO t1 VALUES(1, randomblob(500), randomblob(500));
INSERT INTO t1 VALUES(2, randomblob(500), randomblob(500));
PRAGMA journal_mode = wal;
INSERT INTO t1 VALUES(3, randomblob(500), randomblob(500));
BEGIN;
SELECT a FROM t1;
}
set ::snapshot [sqlite3_snapshot_get db2 main]
db2 eval COMMIT
db2 eval {
UPDATE t1 SET b=randomblob(501), c=randomblob(501) WHERE a=1;
INSERT INTO t1 VALUES(4, randomblob(500), randomblob(500));
INSERT INTO t1 VALUES(5, randomblob(500), randomblob(500));
INSERT INTO t1 VALUES(6, randomblob(500), randomblob(500));
}
} -body {
db eval { PRAGMA wal_checkpoint }
} -test {
db2 eval BEGIN
if {[catch { sqlite3_snapshot_open db2 main $::snapshot } msg]} {
if {$msg != "SQLITE_BUSY_SNAPSHOT" && $msg != "SQLITE_BUSY"} {
error "error is $msg"
}
} else {
set res [db2 eval {
SELECT a FROM t1;
PRAGMA integrity_check;
}]
if {$res != "1 2 3 ok"} { error "res is $res" }
}
sqlite3_snapshot_free $::snapshot
}
#-------------------------------------------------------------------------
# This test is similar to the previous one. Except, after the
# "PRAGMA wal_checkpoint" command fails the db is closed and reopened
# so as to require wal file recovery. It should not be possible to open
# a snapshot that is part of the body of a recovered wal file.
#
do_faultsim_test 2.0 -prep {
faultsim_delete_and_reopen
db eval {
CREATE TABLE t1(a, b UNIQUE, c UNIQUE);
INSERT INTO t1 VALUES(1, randomblob(500), randomblob(500));
INSERT INTO t1 VALUES(2, randomblob(500), randomblob(500));
PRAGMA journal_mode = wal;
INSERT INTO t1 VALUES(3, randomblob(500), randomblob(500));
BEGIN;
SELECT a FROM t1;
}
set ::snapshot [sqlite3_snapshot_get db main]
db eval COMMIT
db eval {
UPDATE t1 SET b=randomblob(501), c=randomblob(501) WHERE a=1;
INSERT INTO t1 VALUES(4, randomblob(500), randomblob(500));
INSERT INTO t1 VALUES(5, randomblob(500), randomblob(500));
INSERT INTO t1 VALUES(6, randomblob(500), randomblob(500));
}
} -body {
db eval { PRAGMA wal_checkpoint }
} -test {
db_save
db close
db_restore_and_reopen
db eval { SELECT * FROM t1 }
db eval BEGIN
if {[catch { sqlite3_snapshot_open db main $::snapshot } msg]} {
if {$msg != "SQLITE_BUSY_SNAPSHOT" && $msg != "SQLITE_BUSY"} {
error "error is $msg"
}
} else {
# This branch should actually never be taken. But it was useful in
# determining whether or not this test was actually working (by
# running a modified version of SQLite that allowed snapshots to be
# opened following a recovery).
error "TEST HAS FAILED"
set res [db eval {
SELECT a FROM t1;
PRAGMA integrity_check;
}]
if {$res != "1 2 3 ok"} { error "res is $res" }
}
sqlite3_snapshot_free $::snapshot
}
#-------------------------------------------------------------------------
# Test the handling of faults that occur within sqlite3_snapshot_open().
#
do_faultsim_test 3.0 -prep {
faultsim_delete_and_reopen
db eval {
CREATE TABLE t1(a, b UNIQUE, c UNIQUE);
INSERT INTO t1 VALUES(1, randomblob(500), randomblob(500));
INSERT INTO t1 VALUES(2, randomblob(500), randomblob(500));
PRAGMA journal_mode = wal;
INSERT INTO t1 VALUES(3, randomblob(500), randomblob(500));
BEGIN;
SELECT a FROM t1;
}
set ::snapshot [sqlite3_snapshot_get db main]
db eval COMMIT
db eval {
UPDATE t1 SET b=randomblob(501), c=randomblob(501) WHERE a=1;
INSERT INTO t1 VALUES(4, randomblob(500), randomblob(500));
INSERT INTO t1 VALUES(5, randomblob(500), randomblob(500));
INSERT INTO t1 VALUES(6, randomblob(500), randomblob(500));
BEGIN;
}
} -body {
if { [catch { sqlite3_snapshot_open db main $::snapshot } msg] } {
error $msg
}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_IOERR} \
{1 SQLITE_IOERR_NOMEM} {1 SQLITE_IOERR_READ}
if {$testrc==0} {
set res [db eval {
SELECT a FROM t1;
PRAGMA integrity_check;
}]
if {$res != "1 2 3 ok"} { error "res is $res" }
}
sqlite3_snapshot_free $::snapshot
}
finish_test