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:
commit
cc99e24491
29
manifest
29
manifest
@ -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
|
||||
|
@ -1 +1 @@
|
||||
96d3e99ffe59b5280b8b7fc269c94a72e15c345b
|
||||
9b124a5a310aec0145495c9980bc0c1308fcca02
|
83
src/main.c
83
src/main.c
@ -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 */
|
||||
|
||||
|
28
src/pager.c
28
src/pager.c
@ -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
|
||||
|
@ -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
|
||||
|
122
src/sqlite.h.in
122
src/sqlite.h.in
@ -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.
|
||||
|
93
src/test1.c
93
src/test1.c
@ -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);
|
||||
|
@ -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
282
src/wal.c
@ -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
|
||||
|
@ -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
340
test/snapshot.test
Normal 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
164
test/snapshot_fault.test
Normal 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
|
Loading…
Reference in New Issue
Block a user