From fc1acf33b88a23a7475aaa14287a72044674fcda Mon Sep 17 00:00:00 2001 From: dan Date: Sat, 5 Dec 2015 20:51:54 +0000 Subject: [PATCH 01/15] Add untested implementations of experimental APIs sqlite3_snapshot_get(), _open() and _free(). FossilOrigin-Name: 0715eb00aa8891400cd50a15509d3d7b13789626 --- manifest | 29 +++++++++-------- manifest.uuid | 2 +- src/main.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++ src/pager.c | 28 ++++++++++++++++ src/pager.h | 4 +++ src/sqlite.h.in | 23 +++++++++++++ src/test1.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++ src/wal.c | 78 +++++++++++++++++++++++++++++++++++++++++--- src/wal.h | 5 +++ 9 files changed, 320 insertions(+), 18 deletions(-) diff --git a/manifest b/manifest index 00191f9fd0..2447acbb5a 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Remove\sthe\sdependence\son\s"exec\sls\s-U"\sfrom\sthe\svtabH.test\smodule,\sas\sthe\s-U\noption\sto\s"ls"\sis\snot\suniversally\savailable. -D 2015-12-04T13:44:07.797 +C Add\suntested\simplementations\sof\sexperimental\sAPIs\ssqlite3_snapshot_get(),\s_open()\sand\s_free(). +D 2015-12-05T20:51:54.748 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 3dc84d9bd722fb16c196a867d39acf86b8f72b70 F src/malloc.c 337bbe9c7d436ef9b7d06b5dd10bbfc8f3025972 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 2563734669b06432cea640cbb4f7e9d543f227b9 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 2796237990d42e6a5a7beafee65ef70cc8767d21 -F src/sqlite.h.in 1248a78548024bdc8ef5893faa0ff9552b4cceb4 +F src/sqlite.h.in fc8a2875a318df1b9dabd82cb00b1ac98081423a F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad F src/sqlite3ext.h dfbe62ffd95b99afe2140d8c35b180d11924072d F src/sqliteInt.h 64256d193a16a147d9f6317cc4e095fdd3e0a2e9 @@ -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 4004bcc1b3b361a9137acd1d875599ecbdd6f961 +F src/test1.c de18fc36e0830039058e9829ac201bb203d31718 F src/test2.c 5586f43fcd9a1be0830793cf9d354082c261b25b F src/test3.c a8887dabbbee3059af338f20d290084a63ed1b0f F src/test4.c d168f83cc78d02e8d35567bb5630e40dcd85ac1e @@ -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 b9b1d5a1dd6e9b4f14f62326f34d719d14b33f08 +F src/wal.h 907943dfdef10b583e81906679a347e0ec6f1b1b F src/walker.c 2e14d17f592d176b6dc879c33fbdec4fbccaa2ba F src/where.c b18edbb9e5afabb77f4f27550c471c5c824e0fe7 F src/whereInt.h e20801d89e34de1912bb6a3babb30c390da27add @@ -1408,7 +1408,10 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 042738ad3b769ad70fd7603f928d5b94a952267d -R d4a001dfc820878042f471ef37d7e668 -U drh -Z 2a98a6e0179300d3f1024c7c0c5f05bd +P 4ecbc75b465533cf80e166a9d0879b9afd3fe2be +R 89117c604019561442a571d3627293e1 +T *branch * snapshot-get +T *sym-snapshot-get * +T -sym-trunk * +U dan +Z 6ebcf0f2bc9e753be007db23f7cb64c3 diff --git a/manifest.uuid b/manifest.uuid index b3054dd7aa..f4219011d6 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -4ecbc75b465533cf80e166a9d0879b9afd3fe2be \ No newline at end of file +0715eb00aa8891400cd50a15509d3d7b13789626 \ No newline at end of file diff --git a/src/main.c b/src/main.c index d552f7fbc8..902954b60d 100644 --- a/src/main.c +++ b/src/main.c @@ -3866,3 +3866,85 @@ 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!=sqlite3BtreeIsInReadTrans(pBt) + && 0==sqlite3BtreeIsInTrans(pBt) + ){ + 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 */ + diff --git a/src/pager.c b/src/pager.c index bf74eac549..2c8dceb750 100644 --- a/src/pager.c +++ b/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 diff --git a/src/pager.h b/src/pager.h index cf9cda625d..ba4eec438d 100644 --- a/src/pager.h +++ b/src/pager.h @@ -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 diff --git a/src/sqlite.h.in b/src/sqlite.h.in index e797571062..e8940b5e08 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -7878,6 +7878,29 @@ void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*); */ int sqlite3_db_cacheflush(sqlite3*); +/* +** CAPI3REF: Open old database snapshots. +** +** The second argument passed to sqlite3_snapshot_get() must be the name +** of a database file attached to the database handle passed as the first. +** The database handle must have an open read transaction on the named +** database, which must be in wal mode. +** +** If successful, sqlite3_snapshot_get() sets *ppSnapshot to point to a new +** snapshot handle that may be used with sqlite3_snapshot_open() and returns +** SQLITE_OK. +** +** If the specified database does not exist, or is not a wal mode database, +** or the database handle does not have an open read transaction on it, +** SQLITE_ERROR is returned. If any other error occurs, for example an IO +** error or an OOM condition, the corresponding SQLite error code is +** returned. +*/ +typedef struct sqlite3_snapshot sqlite3_snapshot; +int sqlite3_snapshot_get(sqlite3*, const char*, sqlite3_snapshot **ppSnapshot); +int sqlite3_snapshot_open(sqlite3*, const char*, sqlite3_snapshot*); +void sqlite3_snapshot_free(sqlite3_snapshot*); + /* ** Undo the hack that converts floating point types to integer for ** builds on processors without floating point support. diff --git a/src/test1.c b/src/test1.c index 186e4e4684..31f506ea4f 100644 --- a/src/test1.c +++ b/src/test1.c @@ -2269,6 +2269,88 @@ static int vfsCurrentTimeInt64( return TCL_OK; } +/* +** 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; +} + +/* +** 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; +} + +/* +** 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; +} + /* ** Usage: sqlite3_next_stmt DB STMT ** @@ -7083,6 +7165,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); diff --git a/src/wal.c b/src/wal.c index 144db27a30..c4823c7cd2 100644 --- a/src/wal.c +++ b/src/wal.c @@ -434,6 +434,9 @@ struct Wal { #ifdef SQLITE_DEBUG u8 lockError; /* True if a locking error has occurred */ #endif +#ifdef SQLITE_ENABLE_SNAPSHOT + WalIndexHdr *pSnapshot; +#endif }; /* @@ -2147,6 +2150,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 */ + int mxFrame; /* Wal frame to lock to */ assert( pWal->readLock<0 ); /* Not currently locked */ @@ -2210,7 +2214,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,9 +2257,13 @@ 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 ) mxFrame = pWal->pSnapshot->mxFrame; +#endif for(i=1; iaReadMark[i]; - if( mxReadMark<=thisMark && thisMark<=pWal->hdr.mxFrame ){ + if( mxReadMark<=thisMark && thisMark<=mxFrame ){ assert( thisMark!=READMARK_NOT_USED ); mxReadMark = thisMark; mxI = i; @@ -2259,12 +2272,12 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ /* There was once an "if" here. The extra "{" is to preserve indentation. */ { if( (pWal->readOnly & WAL_SHM_RDONLY)==0 - && (mxReadMarkhdr.mxFrame || mxI==0) + && (mxReadMarkaReadMark[i] = pWal->hdr.mxFrame; + mxReadMark = pInfo->aReadMark[i] = mxFrame; mxI = i; walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); break; @@ -2349,6 +2362,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))){ + bChanged = 1; + } +#endif + do{ rc = walTryBeginRead(pWal, pChanged, 0, ++cnt); }while( rc==WAL_RETRY ); @@ -2356,6 +2377,32 @@ 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)) ){ + volatile WalCkptInfo *pInfo = walCkptInfo(pWal); + rc = walLockShared(pWal, WAL_READ_LOCK(0)); + if( rc==SQLITE_OK ){ + if( pInfo->nBackfill<=pSnapshot->mxFrame + && pSnapshot->aSalt[0]==pWal->hdr.aSalt[0] + && pSnapshot->aSalt[1]==pWal->hdr.aSalt[1] + ){ + assert( pWal->readLock>0 ); + assert( pInfo->aReadMark[pWal->readLock]<=pSnapshot->mxFrame ); + memcpy(&pWal->hdr, pSnapshot, sizeof(WalIndexHdr)); + *pChanged = bChanged; + }else{ + rc = SQLITE_BUSY_SNAPSHOT; + } + walUnlockShared(pWal, WAL_READ_LOCK(0)); + } + if( rc!=SQLITE_OK ){ + sqlite3WalEndReadTransaction(pWal); + } + } + } +#endif return rc; } @@ -3165,6 +3212,29 @@ int sqlite3WalHeapMemory(Wal *pWal){ return (pWal && pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ); } +#ifdef SQLITE_ENABLE_SNAPSHOT +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; +} + +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 diff --git a/src/wal.h b/src/wal.h index 092546354b..94a049493e 100644 --- a/src/wal.h +++ b/src/wal.h @@ -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). From 818b11aebba69c0aee29030075c19aa100683aa6 Mon Sep 17 00:00:00 2001 From: dan Date: Mon, 7 Dec 2015 14:33:07 +0000 Subject: [PATCH 02/15] Add tests for snapshot_get(), _open() and _free(). FossilOrigin-Name: 502cc6f353358946080d9bcd335aed526825b88a --- manifest | 16 ++-- manifest.uuid | 2 +- src/wal.c | 4 +- test/snapshot.test | 218 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 229 insertions(+), 11 deletions(-) create mode 100644 test/snapshot.test diff --git a/manifest b/manifest index 2447acbb5a..1e9eafc39e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\suntested\simplementations\sof\sexperimental\sAPIs\ssqlite3_snapshot_get(),\s_open()\sand\s_free(). -D 2015-12-05T20:51:54.748 +C Add\stests\sfor\ssnapshot_get(),\s_open()\sand\s_free(). +D 2015-12-07T14:33:07.393 F Makefile.in 28bcd6149e050dff35d4dcfd97e890cd387a499d F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc e8fdca1cb89a1b58b5f4d3a130ea9a3d28cb314d @@ -415,7 +415,7 @@ F src/vdbesort.c a7ec02da4494c59dfd071126dd3726be5a11459d F src/vdbetrace.c 8befe829faff6d9e6f6e4dee5a7d3f85cc85f1a0 F src/vtab.c 2a8b44aa372c33f6154208e7a7f6c44254549806 F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb -F src/wal.c b9b1d5a1dd6e9b4f14f62326f34d719d14b33f08 +F src/wal.c abce669053edf5cd1cd1751d654d48d74ed47839 F src/wal.h 907943dfdef10b583e81906679a347e0ec6f1b1b F src/walker.c 2e14d17f592d176b6dc879c33fbdec4fbccaa2ba F src/where.c b18edbb9e5afabb77f4f27550c471c5c824e0fe7 @@ -1020,6 +1020,7 @@ F test/skipscan2.test d1d1450952b7275f0b0a3a981f0230532743951a F test/skipscan3.test ec5bab3f81c7038b43450e7b3062e04a198bdbb5 F test/skipscan5.test 67817a4b6857c47e0e33ba3e506da6f23ef68de2 F test/skipscan6.test 5866039d03a56f5bd0b3d172a012074a1d90a15b +F test/snapshot.test 061dc75b77ca65c0e9c5976499625abe5be7a5c0 F test/soak.test 0b5b6375c9f4110c828070b826b3b4b0bb65cd5f F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087 F test/sort.test 3f492e5b7be1d3f756728d2ff6edf4f6091e84cb @@ -1408,10 +1409,7 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 4ecbc75b465533cf80e166a9d0879b9afd3fe2be -R 89117c604019561442a571d3627293e1 -T *branch * snapshot-get -T *sym-snapshot-get * -T -sym-trunk * +P 0715eb00aa8891400cd50a15509d3d7b13789626 +R e71fd4e8a4bba215d14ff5f093a4f95d U dan -Z 6ebcf0f2bc9e753be007db23f7cb64c3 +Z b314e37499d3a0450136f5ece5d06812 diff --git a/manifest.uuid b/manifest.uuid index f4219011d6..d3b0195b25 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -0715eb00aa8891400cd50a15509d3d7b13789626 \ No newline at end of file +502cc6f353358946080d9bcd335aed526825b88a \ No newline at end of file diff --git a/src/wal.c b/src/wal.c index c4823c7cd2..49811fb9b4 100644 --- a/src/wal.c +++ b/src/wal.c @@ -2259,7 +2259,9 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ mxI = 0; mxFrame = pWal->hdr.mxFrame; #ifdef SQLITE_ENABLE_SNAPSHOT - if( pWal->pSnapshot ) mxFrame = pWal->pSnapshot->mxFrame; + if( pWal->pSnapshot && pWal->pSnapshot->mxFramepSnapshot->mxFrame; + } #endif for(i=1; iaReadMark[i]; diff --git a/test/snapshot.test b/test/snapshot.test new file mode 100644 index 0000000000..4d94225701 --- /dev/null +++ b/test/snapshot.test @@ -0,0 +1,218 @@ +# 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 testing the SELECT statement. +# +# $Id: select1.test,v 1.70 2009/05/28 01:00:56 drh Exp $ + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +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. +# 2) there is no open read transaction on the database, or +# 3) 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; + INSERT INTO t1 VALUES(5, 6); + } + list [catch { sqlite3_snapshot_get db main } msg] $msg +} {1 SQLITE_ERROR} + +do_test 1.3.1 { + execsql { + BEGIN; + 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.0 { + BEGIN; + SELECT * FROM t1; +} {1 2 3 4 5 6 7 8} + +do_test 2.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.2 { + execsql BEGIN + sqlite3_snapshot_open db main $snapshot + execsql { SELECT * FROM t1 } +} {1 2 3 4 5 6 7 8} + +do_test 2.3 { + sqlite3_snapshot_free $snapshot + execsql COMMIT +} {} + +#------------------------------------------------------------------------- +# 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 +} {} + +finish_test + From 65127cd57d20a2dc302dc2cc51a389f1089bad13 Mon Sep 17 00:00:00 2001 From: dan Date: Wed, 9 Dec 2015 20:05:27 +0000 Subject: [PATCH 03/15] Update sqlite3_snapshot_open() to reduce the chances of reading a corrupt snapshot created by a checkpointer process exiting unexpectedly. FossilOrigin-Name: 7315f7cbf4179aadda0f1a0baa1526a9b9f9729f --- manifest | 18 ++++++------- manifest.uuid | 2 +- src/sqlite.h.in | 30 ++++++++++++++++++++- src/wal.c | 66 ++++++++++++++++++++++++++++++++++++---------- test/snapshot.test | 46 +++++++++++++++++++++++++++----- 5 files changed, 130 insertions(+), 32 deletions(-) diff --git a/manifest b/manifest index f06c0af37a..09955e2042 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\sunrelated\sfixes\sfrom\strunk. -D 2015-12-09T16:04:06.348 +C Update\ssqlite3_snapshot_open()\sto\sreduce\sthe\schances\sof\sreading\sa\scorrupt\ssnapshot\screated\sby\sa\scheckpointer\sprocess\sexiting\sunexpectedly. +D 2015-12-09T20:05:27.534 F Makefile.in 28bcd6149e050dff35d4dcfd97e890cd387a499d F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc e8fdca1cb89a1b58b5f4d3a130ea9a3d28cb314d @@ -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 fc8a2875a318df1b9dabd82cb00b1ac98081423a +F src/sqlite.h.in 19dea4862ccfcc1a733d0fd18d4744b02a505ac6 F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad F src/sqlite3ext.h dfbe62ffd95b99afe2140d8c35b180d11924072d F src/sqliteInt.h 5caacf37a776f9d6178e519cb0b5248ca22a3828 @@ -415,7 +415,7 @@ F src/vdbesort.c a7ec02da4494c59dfd071126dd3726be5a11459d F src/vdbetrace.c 8befe829faff6d9e6f6e4dee5a7d3f85cc85f1a0 F src/vtab.c 2a8b44aa372c33f6154208e7a7f6c44254549806 F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb -F src/wal.c abce669053edf5cd1cd1751d654d48d74ed47839 +F src/wal.c 0bd8aa8e0db924493af4c72f527afc9b9e22257a F src/wal.h 907943dfdef10b583e81906679a347e0ec6f1b1b F src/walker.c 2e14d17f592d176b6dc879c33fbdec4fbccaa2ba F src/where.c b18edbb9e5afabb77f4f27550c471c5c824e0fe7 @@ -1020,7 +1020,7 @@ F test/skipscan2.test d1d1450952b7275f0b0a3a981f0230532743951a F test/skipscan3.test ec5bab3f81c7038b43450e7b3062e04a198bdbb5 F test/skipscan5.test 67817a4b6857c47e0e33ba3e506da6f23ef68de2 F test/skipscan6.test 5866039d03a56f5bd0b3d172a012074a1d90a15b -F test/snapshot.test 061dc75b77ca65c0e9c5976499625abe5be7a5c0 +F test/snapshot.test 800e0be4488acb88dd38ff9e9b83edb71d9d5a9d F test/soak.test 0b5b6375c9f4110c828070b826b3b4b0bb65cd5f F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087 F test/sort.test 3f492e5b7be1d3f756728d2ff6edf4f6091e84cb @@ -1409,7 +1409,7 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 502cc6f353358946080d9bcd335aed526825b88a 901d0b8f3b72e96ffa8e9436993a12980f5ebd51 -R 9b1d7228446872f8d72f41cfd12e515e -U drh -Z 3ae5744205bfcf5cc63a2630633fbd47 +P 362615b4df94358d0264b0991c3090a0878f054c +R cfc950fbd468350b7777662be2b401a3 +U dan +Z 51de2f0ad57bbe117dc864890e31b83c diff --git a/manifest.uuid b/manifest.uuid index 44bc42da2d..33aefc5d3b 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -362615b4df94358d0264b0991c3090a0878f054c \ No newline at end of file +7315f7cbf4179aadda0f1a0baa1526a9b9f9729f \ No newline at end of file diff --git a/src/sqlite.h.in b/src/sqlite.h.in index e8940b5e08..601415b907 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -7895,11 +7895,39 @@ int sqlite3_db_cacheflush(sqlite3*); ** SQLITE_ERROR is returned. If any other error occurs, for example an IO ** error or an OOM condition, the corresponding SQLite error code is ** returned. +** +** Each successful call to sqlite3_snapshot_get() must be matched by a call +** to sqlite3_snapshot_free() to delete the snapshot handle. Not doing so +** is a memory leak. The results of using a snapshot handle after it has +** been deleted by sqlite3_snapshot_free() are undefined. +** +** Given a snapshot handle, the sqlite3_snapshot_open() API function may be +** used to open a read transaction on the same database snapshot that was +** being read when sqlite3_snapshot_get() was called to obtain it. The +** combination of the first two arguments to sqlite3_snapshot_open() - a +** database handle and the name (e.g. "main") of one of its attached +** databases - must refer to the same database file as that identified by +** the arguments passed to the sqlite3_snapshot_get() call. The database +** handle must not have an open read or write transaction on this database +** file, and must not be in auto-commit mode. +** +** An old database snapshot may only be opened if SQLite is able to +** determine that it is still valid. The only way for an application to +** guarantee that a snapshot remains valid is by holding an open +** read-transaction on it or on an older snapshot of the same database +** file. If SQLite cannot determine that the snapshot identified by the +** snapshot handle, SQLITE_BUSY_SNAPSHOT is returned. +** +** Otherwise, if the read transaction is successfully opened, SQLITE_OK is +** returned. If the named database is not in wal mode or if the database +** handle already has an open read or write transaction on it, or if the +** database handle is in auto-commit mode, SQLITE_ERROR is returned. If +** an OOM or IO error occurs, the associated SQLite error code is returned. */ typedef struct sqlite3_snapshot sqlite3_snapshot; int sqlite3_snapshot_get(sqlite3*, const char*, sqlite3_snapshot **ppSnapshot); -int sqlite3_snapshot_open(sqlite3*, const char*, sqlite3_snapshot*); void sqlite3_snapshot_free(sqlite3_snapshot*); +int sqlite3_snapshot_open(sqlite3*, const char*, sqlite3_snapshot*); /* ** Undo the hack that converts floating point types to integer for diff --git a/src/wal.c b/src/wal.c index 49811fb9b4..af93694e8d 100644 --- a/src/wal.c +++ b/src/wal.c @@ -2275,6 +2275,9 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ { if( (pWal->readOnly & WAL_SHM_RDONLY)==0 && (mxReadMarkpSnapshot==0 +#endif ){ for(i=1; ipSnapshot ) return SQLITE_BUSY_SNAPSHOT; +#endif assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 ); return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTLOCK; } @@ -2383,22 +2389,54 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ #ifdef SQLITE_ENABLE_SNAPSHOT if( rc==SQLITE_OK ){ if( pSnapshot && memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr)) ){ + /* At this point the client has a lock on an aReadMark[] slot holding + ** a value equal to or smaller than pSnapshot->mxFrame. This client + ** did not populate the aReadMark[] slot. pWal->hdr is populated with + ** the wal-index header for the snapshot currently at the head of the + ** wal file, which is different from pSnapshot. + ** + ** The presence of the aReadMark[] slot entry makes it very likely + ** that either there is currently another read-transaction open on + ** pSnapshot, or that there has been one more recently than the last + ** checkpoint of any frames greater than pSnapshot->mxFrame was + ** started. There is an exception though: client 1 may have called + ** walTryBeginRead and started to open snapshot pSnapshot, setting + ** the aReadMark[] slot to do so. At the same time, client 2 may + ** have committed a new snapshot to disk and started a checkpoint. + ** In this circumstance client 1 does not end up reading pSnapshot, + ** but may leave the aReadMark[] slot populated. + ** + ** The race condition above is difficult to detect. One approach would + ** be to check the aReadMark[] slot for another client. But this is + ** prone to false-positives from other snapshot clients. And there + ** is no equivalent to xCheckReservedLock() for wal locks. Another + ** approach would be to take the checkpointer lock and check that + ** fewer than pSnapshot->mxFrame frames have been checkpointed. But + ** that does not account for checkpointer processes that failed after + ** checkpointing frames but before updating WalCkptInfo.nBackfill. + ** And it would mean that this function would block on checkpointers + ** and vice versa. + ** + ** TODO: For now, this race condition is ignored. + */ volatile WalCkptInfo *pInfo = walCkptInfo(pWal); - rc = walLockShared(pWal, WAL_READ_LOCK(0)); - if( rc==SQLITE_OK ){ - if( pInfo->nBackfill<=pSnapshot->mxFrame - && pSnapshot->aSalt[0]==pWal->hdr.aSalt[0] - && pSnapshot->aSalt[1]==pWal->hdr.aSalt[1] - ){ - assert( pWal->readLock>0 ); - assert( pInfo->aReadMark[pWal->readLock]<=pSnapshot->mxFrame ); - memcpy(&pWal->hdr, pSnapshot, sizeof(WalIndexHdr)); - *pChanged = bChanged; - }else{ - rc = SQLITE_BUSY_SNAPSHOT; - } - walUnlockShared(pWal, WAL_READ_LOCK(0)); + + assert( pWal->readLock>0 ); + assert( pInfo->aReadMark[pWal->readLock]<=pSnapshot->mxFrame ); + + /* Check that the wal file has not been wrapped. Assuming it has not, + ** overwrite pWal->hdr with *pSnapshot and set *pChanged as appropriate + ** for opening the snapshot. Or, if the wal file has been wrapped + ** since pSnapshot was written, return SQLITE_BUSY_SNAPSHOT. */ + if( pSnapshot->aSalt[0]==pWal->hdr.aSalt[0] + && pSnapshot->aSalt[1]==pWal->hdr.aSalt[1] + ){ + memcpy(&pWal->hdr, pSnapshot, sizeof(WalIndexHdr)); + *pChanged = bChanged; + }else{ + rc = SQLITE_BUSY_SNAPSHOT; } + if( rc!=SQLITE_OK ){ sqlite3WalEndReadTransaction(pWal); } diff --git a/test/snapshot.test b/test/snapshot.test index 4d94225701..5d3758c010 100644 --- a/test/snapshot.test +++ b/test/snapshot.test @@ -57,12 +57,14 @@ 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.0 { +# UPDATE: This case (2.1) no longer works. 2.2 does. +# +do_execsql_test 2.1.0 { BEGIN; SELECT * FROM t1; } {1 2 3 4 5 6 7 8} -do_test 2.1 { +do_test 2.1.1 { set snapshot [sqlite3_snapshot_get db main] execsql { COMMIT; @@ -71,17 +73,47 @@ do_test 2.1 { } } {1 2 3 4 5 6 7 8 9 10} -do_test 2.2 { +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} + list [catch { sqlite3_snapshot_open db main $snapshot } msg] $msg +} {1 SQLITE_BUSY_SNAPSHOT} -do_test 2.3 { +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.1.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.1.3 { + sqlite3_snapshot_free $snapshot + execsql COMMIT + execsql COMMIT db2 + db2 close +} {} + #------------------------------------------------------------------------- # Check some errors in sqlite3_snapshot_open(). It is an error if: # From 998147ec38ce64fff6307891b067192d2c089097 Mon Sep 17 00:00:00 2001 From: drh Date: Thu, 10 Dec 2015 02:15:03 +0000 Subject: [PATCH 04/15] Add the nBackfillAttempted field in formerly unused space in WalCkptInfo and use that field to close the race condition on opening a snapshot. FossilOrigin-Name: cb68e9d0738fc7db7316947b4d2aab91aae819f2 --- manifest | 14 ++-- manifest.uuid | 2 +- src/wal.c | 214 +++++++++++++++++++++++++------------------------- 3 files changed, 116 insertions(+), 114 deletions(-) diff --git a/manifest b/manifest index 09955e2042..32f43abbab 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Update\ssqlite3_snapshot_open()\sto\sreduce\sthe\schances\sof\sreading\sa\scorrupt\ssnapshot\screated\sby\sa\scheckpointer\sprocess\sexiting\sunexpectedly. -D 2015-12-09T20:05:27.534 +C Add\sthe\snBackfillAttempted\sfield\sin\sformerly\sunused\sspace\sin\sWalCkptInfo\sand\nuse\sthat\sfield\sto\sclose\sthe\srace\scondition\son\sopening\sa\ssnapshot. +D 2015-12-10T02:15:03.333 F Makefile.in 28bcd6149e050dff35d4dcfd97e890cd387a499d F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc e8fdca1cb89a1b58b5f4d3a130ea9a3d28cb314d @@ -415,7 +415,7 @@ F src/vdbesort.c a7ec02da4494c59dfd071126dd3726be5a11459d F src/vdbetrace.c 8befe829faff6d9e6f6e4dee5a7d3f85cc85f1a0 F src/vtab.c 2a8b44aa372c33f6154208e7a7f6c44254549806 F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb -F src/wal.c 0bd8aa8e0db924493af4c72f527afc9b9e22257a +F src/wal.c 115765a38fa4a03d7334b6ba77db0cedae682eb0 F src/wal.h 907943dfdef10b583e81906679a347e0ec6f1b1b F src/walker.c 2e14d17f592d176b6dc879c33fbdec4fbccaa2ba F src/where.c b18edbb9e5afabb77f4f27550c471c5c824e0fe7 @@ -1409,7 +1409,7 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 362615b4df94358d0264b0991c3090a0878f054c -R cfc950fbd468350b7777662be2b401a3 -U dan -Z 51de2f0ad57bbe117dc864890e31b83c +P 7315f7cbf4179aadda0f1a0baa1526a9b9f9729f +R 70ffd1e2449a67c034eb4185ab7bdad1 +U drh +Z 159fdbba63f7473f88a04e9240766d6d diff --git a/manifest.uuid b/manifest.uuid index 33aefc5d3b..028205ec43 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7315f7cbf4179aadda0f1a0baa1526a9b9f9729f \ No newline at end of file +cb68e9d0738fc7db7316947b4d2aab91aae819f2 \ No newline at end of file diff --git a/src/wal.c b/src/wal.c index af93694e8d..784f993bfc 100644 --- a/src/wal.c +++ b/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 afte rall 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 @@ -435,7 +451,7 @@ struct Wal { u8 lockError; /* True if a locking error has occurred */ #endif #ifdef SQLITE_ENABLE_SNAPSHOT - WalIndexHdr *pSnapshot; + WalIndexHdr *pSnapshot; /* Start transaction here if not NULL */ #endif }; @@ -1201,6 +1217,7 @@ finished: */ pInfo = walCkptInfo(pWal); pInfo->nBackfill = 0; + pInfo->nBackfillAttempted = 0; pInfo->aReadMark[0] = 0; for(i=1; iaReadMark[i] = READMARK_NOT_USED; if( pWal->hdr.mxFrame ) pInfo->aReadMark[1] = pWal->hdr.mxFrame; @@ -1272,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 @@ -1658,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; iaReadMark[i] = READMARK_NOT_USED; assert( pInfo->aReadMark[0]==0 ); @@ -1735,6 +1757,7 @@ static int walCheckpoint( ** cannot be backfilled from the WAL. */ mxSafeFrame = pWal->hdr.mxFrame; + pInfo->nBackfillAttempted = mxSafeFrame; mxPage = pWal->hdr.nPage; for(i=1; ireadOnly & WAL_SHM_RDONLY)==0 - && (mxReadMarkreadOnly & WAL_SHM_RDONLY)==0 + && (mxReadMarkpSnapshot==0 + && pWal->pSnapshot==0 #endif - ){ - for(i=1; iaReadMark[i] = mxFrame; - mxI = i; - walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); - break; - }else if( rc!=SQLITE_BUSY ){ - return rc; - } + ){ + for(i=1; iaReadMark[i] = mxFrame; + mxI = i; + walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); + break; + }else if( rc!=SQLITE_BUSY ){ + return rc; } } - if( mxI==0 ){ + } + if( mxI==0 ){ #ifdef SQLITE_ENABLE_SNAPSHOT - if( pWal->pSnapshot ) return SQLITE_BUSY_SNAPSHOT; + if( pWal->pSnapshot ) return SQLITE_BUSY_SNAPSHOT; #endif - assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 ); - return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTLOCK; - } + 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; } @@ -2373,7 +2393,7 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ #ifdef SQLITE_ENABLE_SNAPSHOT int bChanged = 0; WalIndexHdr *pSnapshot = pWal->pSnapshot; - if( pSnapshot && memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr))){ + if( pSnapshot && memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr))!=0 ){ bChanged = 1; } #endif @@ -2388,36 +2408,18 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ #ifdef SQLITE_ENABLE_SNAPSHOT if( rc==SQLITE_OK ){ - if( pSnapshot && memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr)) ){ + 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. This client - ** did not populate the aReadMark[] slot. pWal->hdr is populated with - ** the wal-index header for the snapshot currently at the head of the - ** wal file, which is different from pSnapshot. + ** a value equal to or smaller than pSnapshot->mxFrame. Verify that + ** pSnapshot is still valid before continuing. Reasons why pSnapshot + ** might no longer be valid: ** - ** The presence of the aReadMark[] slot entry makes it very likely - ** that either there is currently another read-transaction open on - ** pSnapshot, or that there has been one more recently than the last - ** checkpoint of any frames greater than pSnapshot->mxFrame was - ** started. There is an exception though: client 1 may have called - ** walTryBeginRead and started to open snapshot pSnapshot, setting - ** the aReadMark[] slot to do so. At the same time, client 2 may - ** have committed a new snapshot to disk and started a checkpoint. - ** In this circumstance client 1 does not end up reading pSnapshot, - ** but may leave the aReadMark[] slot populated. + ** (1) The WAL file has been reset since the snapshot was taken. + ** In this case, the salt will have changed. ** - ** The race condition above is difficult to detect. One approach would - ** be to check the aReadMark[] slot for another client. But this is - ** prone to false-positives from other snapshot clients. And there - ** is no equivalent to xCheckReservedLock() for wal locks. Another - ** approach would be to take the checkpointer lock and check that - ** fewer than pSnapshot->mxFrame frames have been checkpointed. But - ** that does not account for checkpointer processes that failed after - ** checkpointing frames but before updating WalCkptInfo.nBackfill. - ** And it would mean that this function would block on checkpointers - ** and vice versa. - ** - ** TODO: For now, this race condition is ignored. + ** (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); @@ -2428,8 +2430,8 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ ** overwrite pWal->hdr with *pSnapshot and set *pChanged as appropriate ** for opening the snapshot. Or, if the wal file has been wrapped ** since pSnapshot was written, return SQLITE_BUSY_SNAPSHOT. */ - if( pSnapshot->aSalt[0]==pWal->hdr.aSalt[0] - && pSnapshot->aSalt[1]==pWal->hdr.aSalt[1] + if( memcmp(pSnapshot->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt))==0 + && pSnapshot->mxFrame>=pInfo->nBackfillAttempted ){ memcpy(&pWal->hdr, pSnapshot, sizeof(WalIndexHdr)); *pChanged = bChanged; From c9fb38e7adf81cd7ca2b1af4f64bb5787c7dad05 Mon Sep 17 00:00:00 2001 From: mistachkin Date: Thu, 10 Dec 2015 03:16:47 +0000 Subject: [PATCH 05/15] Fix spacing typo in comment. No changes to code. FossilOrigin-Name: 3a18526fc2253658dad84c5e600481c8a62efe40 --- manifest | 14 +++++++------- manifest.uuid | 2 +- src/wal.c | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/manifest b/manifest index 32f43abbab..a92f8a6ff1 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sthe\snBackfillAttempted\sfield\sin\sformerly\sunused\sspace\sin\sWalCkptInfo\sand\nuse\sthat\sfield\sto\sclose\sthe\srace\scondition\son\sopening\sa\ssnapshot. -D 2015-12-10T02:15:03.333 +C Fix\sspacing\stypo\sin\scomment.\s\sNo\schanges\sto\scode. +D 2015-12-10T03:16:47.044 F Makefile.in 28bcd6149e050dff35d4dcfd97e890cd387a499d F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc e8fdca1cb89a1b58b5f4d3a130ea9a3d28cb314d @@ -415,7 +415,7 @@ F src/vdbesort.c a7ec02da4494c59dfd071126dd3726be5a11459d F src/vdbetrace.c 8befe829faff6d9e6f6e4dee5a7d3f85cc85f1a0 F src/vtab.c 2a8b44aa372c33f6154208e7a7f6c44254549806 F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb -F src/wal.c 115765a38fa4a03d7334b6ba77db0cedae682eb0 +F src/wal.c 964af61902acead7de6e95035d0ce597f7019da9 F src/wal.h 907943dfdef10b583e81906679a347e0ec6f1b1b F src/walker.c 2e14d17f592d176b6dc879c33fbdec4fbccaa2ba F src/where.c b18edbb9e5afabb77f4f27550c471c5c824e0fe7 @@ -1409,7 +1409,7 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 7315f7cbf4179aadda0f1a0baa1526a9b9f9729f -R 70ffd1e2449a67c034eb4185ab7bdad1 -U drh -Z 159fdbba63f7473f88a04e9240766d6d +P cb68e9d0738fc7db7316947b4d2aab91aae819f2 +R 76ea636367cbd8afb53a2670ba03ae31 +U mistachkin +Z 1d7611d28a33701f209bc754328c05f3 diff --git a/manifest.uuid b/manifest.uuid index 028205ec43..d5273835aa 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -cb68e9d0738fc7db7316947b4d2aab91aae819f2 \ No newline at end of file +3a18526fc2253658dad84c5e600481c8a62efe40 \ No newline at end of file diff --git a/src/wal.c b/src/wal.c index 784f993bfc..0115e06027 100644 --- a/src/wal.c +++ b/src/wal.c @@ -332,7 +332,7 @@ struct WalIndexHdr { ** 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 afte rall backfilling completes. So if a checkpoint +** 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. ** From 3bf83ccd70bec2af3c47e43ff3c4157356dbbede Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 10 Dec 2015 15:45:15 +0000 Subject: [PATCH 06/15] Have sqlite3_snapshot_open() avoid a race condition by taking a shared CHECKPOINTER lock while checking pInfo->nBackfillAttempted. FossilOrigin-Name: 8084eae0bc4f6513b1147fb890a6b2813f1c0a09 --- manifest | 16 ++++++++-------- manifest.uuid | 2 +- src/wal.c | 41 ++++++++++++++++++++++++++--------------- test/snapshot.test | 13 +++++++------ 4 files changed, 42 insertions(+), 30 deletions(-) diff --git a/manifest b/manifest index a92f8a6ff1..e571284281 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sspacing\stypo\sin\scomment.\s\sNo\schanges\sto\scode. -D 2015-12-10T03:16:47.044 +C Have\ssqlite3_snapshot_open()\savoid\sa\srace\scondition\sby\staking\sa\sshared\sCHECKPOINTER\slock\swhile\schecking\spInfo->nBackfillAttempted. +D 2015-12-10T15:45:15.186 F Makefile.in 28bcd6149e050dff35d4dcfd97e890cd387a499d F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc e8fdca1cb89a1b58b5f4d3a130ea9a3d28cb314d @@ -415,7 +415,7 @@ F src/vdbesort.c a7ec02da4494c59dfd071126dd3726be5a11459d F src/vdbetrace.c 8befe829faff6d9e6f6e4dee5a7d3f85cc85f1a0 F src/vtab.c 2a8b44aa372c33f6154208e7a7f6c44254549806 F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb -F src/wal.c 964af61902acead7de6e95035d0ce597f7019da9 +F src/wal.c 32ee7dc4b689321d1650fba2e937ddc3bccfb06f F src/wal.h 907943dfdef10b583e81906679a347e0ec6f1b1b F src/walker.c 2e14d17f592d176b6dc879c33fbdec4fbccaa2ba F src/where.c b18edbb9e5afabb77f4f27550c471c5c824e0fe7 @@ -1020,7 +1020,7 @@ F test/skipscan2.test d1d1450952b7275f0b0a3a981f0230532743951a F test/skipscan3.test ec5bab3f81c7038b43450e7b3062e04a198bdbb5 F test/skipscan5.test 67817a4b6857c47e0e33ba3e506da6f23ef68de2 F test/skipscan6.test 5866039d03a56f5bd0b3d172a012074a1d90a15b -F test/snapshot.test 800e0be4488acb88dd38ff9e9b83edb71d9d5a9d +F test/snapshot.test f91d907460e7acc01d531834d068e1215ccac7e4 F test/soak.test 0b5b6375c9f4110c828070b826b3b4b0bb65cd5f F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087 F test/sort.test 3f492e5b7be1d3f756728d2ff6edf4f6091e84cb @@ -1409,7 +1409,7 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P cb68e9d0738fc7db7316947b4d2aab91aae819f2 -R 76ea636367cbd8afb53a2670ba03ae31 -U mistachkin -Z 1d7611d28a33701f209bc754328c05f3 +P 3a18526fc2253658dad84c5e600481c8a62efe40 +R 7bb35c02cb12ea2ff304c777229238a5 +U dan +Z 0b9f17bd41106b46c64f60d98c85b1e3 diff --git a/manifest.uuid b/manifest.uuid index d5273835aa..825f8d1181 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -3a18526fc2253658dad84c5e600481c8a62efe40 \ No newline at end of file +8084eae0bc4f6513b1147fb890a6b2813f1c0a09 \ No newline at end of file diff --git a/src/wal.c b/src/wal.c index 0115e06027..0439ed4253 100644 --- a/src/wal.c +++ b/src/wal.c @@ -1217,7 +1217,7 @@ finished: */ pInfo = walCkptInfo(pWal); pInfo->nBackfill = 0; - pInfo->nBackfillAttempted = 0; + pInfo->nBackfillAttempted = pWal->hdr.mxFrame; pInfo->aReadMark[0] = 0; for(i=1; iaReadMark[i] = READMARK_NOT_USED; if( pWal->hdr.mxFrame ) pInfo->aReadMark[1] = pWal->hdr.mxFrame; @@ -1757,7 +1757,6 @@ static int walCheckpoint( ** cannot be backfilled from the WAL. */ mxSafeFrame = pWal->hdr.mxFrame; - pInfo->nBackfillAttempted = mxSafeFrame; mxPage = pWal->hdr.nPage; for(i=1; inBackfill; + pInfo->nBackfillAttempted = mxSafeFrame; + /* Sync the WAL to disk */ if( sync_flags ){ rc = sqlite3OsSync(pWal->pWalFd, sync_flags); @@ -2296,9 +2297,6 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ } if( (pWal->readOnly & WAL_SHM_RDONLY)==0 && (mxReadMarkpSnapshot==0 -#endif ){ for(i=1; ipSnapshot ) return SQLITE_BUSY_SNAPSHOT; -#endif assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 ); return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTLOCK; } @@ -2410,9 +2405,10 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ 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. Verify that - ** pSnapshot is still valid before continuing. Reasons why pSnapshot - ** might no longer be valid: + ** 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. @@ -2426,10 +2422,22 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ assert( pWal->readLock>0 ); assert( pInfo->aReadMark[pWal->readLock]<=pSnapshot->mxFrame ); - /* Check that the wal file has not been wrapped. Assuming it has not, - ** overwrite pWal->hdr with *pSnapshot and set *pChanged as appropriate - ** for opening the snapshot. Or, if the wal file has been wrapped - ** since pSnapshot was written, return SQLITE_BUSY_SNAPSHOT. */ + /* 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); + + /* 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 SQLTIE_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))==0 && pSnapshot->mxFrame>=pInfo->nBackfillAttempted ){ @@ -2439,6 +2447,9 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ rc = SQLITE_BUSY_SNAPSHOT; } + /* Release the shared CKPT lock obtained above. */ + walUnlockShared(pWal, WAL_CKPT_LOCK); + if( rc!=SQLITE_OK ){ sqlite3WalEndReadTransaction(pWal); } diff --git a/test/snapshot.test b/test/snapshot.test index 5d3758c010..965e10b683 100644 --- a/test/snapshot.test +++ b/test/snapshot.test @@ -57,8 +57,6 @@ do_execsql_test 1.3.2 COMMIT # Check that a simple case works. Reuse the database created by the # block of tests above. # -# UPDATE: This case (2.1) no longer works. 2.2 does. -# do_execsql_test 2.1.0 { BEGIN; SELECT * FROM t1; @@ -75,8 +73,11 @@ do_test 2.1.1 { do_test 2.1.2 { execsql BEGIN - list [catch { sqlite3_snapshot_open db main $snapshot } msg] $msg -} {1 SQLITE_BUSY_SNAPSHOT} + 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 @@ -99,7 +100,7 @@ do_test 2.2.1 { } } {1 2 3 4 5 6 7 8 9 10 11 12} -do_test 2.1.2 { +do_test 2.2.2 { execsql BEGIN sqlite3_snapshot_open db main $snapshot execsql { @@ -107,7 +108,7 @@ do_test 2.1.2 { } } {1 2 3 4 5 6 7 8 9 10} -do_test 2.1.3 { +do_test 2.2.3 { sqlite3_snapshot_free $snapshot execsql COMMIT execsql COMMIT db2 From 7d15979420c6cf911862945bc07f69a309894fba Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 10 Dec 2015 18:06:21 +0000 Subject: [PATCH 07/15] Add tests to ensure that an sqlite3_snapshot_open() client cannot be tricked into reading a corrupt snapshot even if another process fails mid-checkpoint. FossilOrigin-Name: b908048b6cfa1ee2fe1f7a17bae475ddd9d0376c --- manifest | 13 ++-- manifest.uuid | 2 +- test/snapshot.test | 5 +- test/snapshot_fault.test | 164 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 174 insertions(+), 10 deletions(-) create mode 100644 test/snapshot_fault.test diff --git a/manifest b/manifest index e571284281..2b87b9f307 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Have\ssqlite3_snapshot_open()\savoid\sa\srace\scondition\sby\staking\sa\sshared\sCHECKPOINTER\slock\swhile\schecking\spInfo->nBackfillAttempted. -D 2015-12-10T15:45:15.186 +C Add\stests\sto\sensure\sthat\san\ssqlite3_snapshot_open()\sclient\scannot\sbe\stricked\sinto\sreading\sa\scorrupt\ssnapshot\seven\sif\sanother\sprocess\sfails\smid-checkpoint. +D 2015-12-10T18:06:21.050 F Makefile.in 28bcd6149e050dff35d4dcfd97e890cd387a499d F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc e8fdca1cb89a1b58b5f4d3a130ea9a3d28cb314d @@ -1020,7 +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 f91d907460e7acc01d531834d068e1215ccac7e4 +F test/snapshot.test 5631669a133c96aaa228f7233989f6d2d069fb02 +F test/snapshot_fault.test 06472056c516be4610834de4688ea3357e8bde01 F test/soak.test 0b5b6375c9f4110c828070b826b3b4b0bb65cd5f F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087 F test/sort.test 3f492e5b7be1d3f756728d2ff6edf4f6091e84cb @@ -1409,7 +1410,7 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 3a18526fc2253658dad84c5e600481c8a62efe40 -R 7bb35c02cb12ea2ff304c777229238a5 +P 8084eae0bc4f6513b1147fb890a6b2813f1c0a09 +R b7a6ff87063d246e894cc3fd88f3e89f U dan -Z 0b9f17bd41106b46c64f60d98c85b1e3 +Z 1fe7db1e51b0eef5aa529631bb7527d1 diff --git a/manifest.uuid b/manifest.uuid index 825f8d1181..82eea9660d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -8084eae0bc4f6513b1147fb890a6b2813f1c0a09 \ No newline at end of file +b908048b6cfa1ee2fe1f7a17bae475ddd9d0376c \ No newline at end of file diff --git a/test/snapshot.test b/test/snapshot.test index 965e10b683..c74a2859fe 100644 --- a/test/snapshot.test +++ b/test/snapshot.test @@ -8,10 +8,9 @@ # May you share freely, never taking more than you give. # #*********************************************************************** -# This file implements regression tests for SQLite library. The -# focus of this file is testing the SELECT statement. +# This file implements regression tests for SQLite library. The focus +# of this file is the sqlite3_snapshot_xxx() APIs. # -# $Id: select1.test,v 1.70 2009/05/28 01:00:56 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl diff --git a/test/snapshot_fault.test b/test/snapshot_fault.test new file mode 100644 index 0000000000..a7731b8161 --- /dev/null +++ b/test/snapshot_fault.test @@ -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 +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 + From a7aeb398362466eca9052c4532f620303003fc67 Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 10 Dec 2015 19:11:34 +0000 Subject: [PATCH 08/15] Return SQLITE_BUSY (not SQLITE_BUSY_SNAPSHOT) if sqlite3_snapshot_open() fails to obtain the shared checkpointer lock. FossilOrigin-Name: 5343060bcc6c99029f731f8020d2cba3f405f207 --- manifest | 14 +++++++------- manifest.uuid | 2 +- src/wal.c | 33 ++++++++++++++++++--------------- test/snapshot.test | 44 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 23 deletions(-) diff --git a/manifest b/manifest index 2b87b9f307..a3a772f96e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\stests\sto\sensure\sthat\san\ssqlite3_snapshot_open()\sclient\scannot\sbe\stricked\sinto\sreading\sa\scorrupt\ssnapshot\seven\sif\sanother\sprocess\sfails\smid-checkpoint. -D 2015-12-10T18:06:21.050 +C Return\sSQLITE_BUSY\s(not\sSQLITE_BUSY_SNAPSHOT)\sif\ssqlite3_snapshot_open()\sfails\sto\sobtain\sthe\sshared\scheckpointer\slock. +D 2015-12-10T19:11:34.013 F Makefile.in 28bcd6149e050dff35d4dcfd97e890cd387a499d F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc e8fdca1cb89a1b58b5f4d3a130ea9a3d28cb314d @@ -415,7 +415,7 @@ F src/vdbesort.c a7ec02da4494c59dfd071126dd3726be5a11459d F src/vdbetrace.c 8befe829faff6d9e6f6e4dee5a7d3f85cc85f1a0 F src/vtab.c 2a8b44aa372c33f6154208e7a7f6c44254549806 F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb -F src/wal.c 32ee7dc4b689321d1650fba2e937ddc3bccfb06f +F src/wal.c 94576d273e7feeae598188ebec641906a5b93a2f F src/wal.h 907943dfdef10b583e81906679a347e0ec6f1b1b F src/walker.c 2e14d17f592d176b6dc879c33fbdec4fbccaa2ba F src/where.c b18edbb9e5afabb77f4f27550c471c5c824e0fe7 @@ -1020,7 +1020,7 @@ F test/skipscan2.test d1d1450952b7275f0b0a3a981f0230532743951a F test/skipscan3.test ec5bab3f81c7038b43450e7b3062e04a198bdbb5 F test/skipscan5.test 67817a4b6857c47e0e33ba3e506da6f23ef68de2 F test/skipscan6.test 5866039d03a56f5bd0b3d172a012074a1d90a15b -F test/snapshot.test 5631669a133c96aaa228f7233989f6d2d069fb02 +F test/snapshot.test 184c7ed21f204a5bd909246560edfac308467609 F test/snapshot_fault.test 06472056c516be4610834de4688ea3357e8bde01 F test/soak.test 0b5b6375c9f4110c828070b826b3b4b0bb65cd5f F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087 @@ -1410,7 +1410,7 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 8084eae0bc4f6513b1147fb890a6b2813f1c0a09 -R b7a6ff87063d246e894cc3fd88f3e89f +P b908048b6cfa1ee2fe1f7a17bae475ddd9d0376c +R 1311c20224293debd3e251bf775d0f5c U dan -Z 1fe7db1e51b0eef5aa529631bb7527d1 +Z ae2ff3e1ce7fc4e495f8fcae9626097f diff --git a/manifest.uuid b/manifest.uuid index 82eea9660d..202cc78fbb 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -b908048b6cfa1ee2fe1f7a17bae475ddd9d0376c \ No newline at end of file +5343060bcc6c99029f731f8020d2cba3f405f207 \ No newline at end of file diff --git a/src/wal.c b/src/wal.c index 0439ed4253..e5527029ac 100644 --- a/src/wal.c +++ b/src/wal.c @@ -2432,23 +2432,26 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ ** before checking pInfo->nBackfillAttempted. */ rc = walLockShared(pWal, WAL_CKPT_LOCK); - /* 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 SQLTIE_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))==0 - && pSnapshot->mxFrame>=pInfo->nBackfillAttempted - ){ - memcpy(&pWal->hdr, pSnapshot, sizeof(WalIndexHdr)); - *pChanged = bChanged; - }else{ - rc = SQLITE_BUSY_SNAPSHOT; + 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); } - /* Release the shared CKPT lock obtained above. */ - walUnlockShared(pWal, WAL_CKPT_LOCK); if( rc!=SQLITE_OK ){ sqlite3WalEndReadTransaction(pWal); diff --git a/test/snapshot.test b/test/snapshot.test index c74a2859fe..564881cae1 100644 --- a/test/snapshot.test +++ b/test/snapshot.test @@ -246,5 +246,49 @@ 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} { +breakpoint + 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 + finish_test From 745c14eb15d8ce2048e2e9185b3780ddb5671d9b Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 10 Dec 2015 19:44:34 +0000 Subject: [PATCH 09/15] Add tests to snapshot.test. FossilOrigin-Name: f3b743623a4501833478c8a86c0922931955aeb6 --- manifest | 12 ++++++------ manifest.uuid | 2 +- test/snapshot.test | 29 ++++++++++++++++++++++++++++- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/manifest b/manifest index a3a772f96e..c962225c27 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Return\sSQLITE_BUSY\s(not\sSQLITE_BUSY_SNAPSHOT)\sif\ssqlite3_snapshot_open()\sfails\sto\sobtain\sthe\sshared\scheckpointer\slock. -D 2015-12-10T19:11:34.013 +C Add\stests\sto\ssnapshot.test. +D 2015-12-10T19:44:34.237 F Makefile.in 28bcd6149e050dff35d4dcfd97e890cd387a499d F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc e8fdca1cb89a1b58b5f4d3a130ea9a3d28cb314d @@ -1020,7 +1020,7 @@ F test/skipscan2.test d1d1450952b7275f0b0a3a981f0230532743951a F test/skipscan3.test ec5bab3f81c7038b43450e7b3062e04a198bdbb5 F test/skipscan5.test 67817a4b6857c47e0e33ba3e506da6f23ef68de2 F test/skipscan6.test 5866039d03a56f5bd0b3d172a012074a1d90a15b -F test/snapshot.test 184c7ed21f204a5bd909246560edfac308467609 +F test/snapshot.test 62983ac46f05b898d5317a8d1b91a7bd8a355988 F test/snapshot_fault.test 06472056c516be4610834de4688ea3357e8bde01 F test/soak.test 0b5b6375c9f4110c828070b826b3b4b0bb65cd5f F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087 @@ -1410,7 +1410,7 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P b908048b6cfa1ee2fe1f7a17bae475ddd9d0376c -R 1311c20224293debd3e251bf775d0f5c +P 5343060bcc6c99029f731f8020d2cba3f405f207 +R cf3f8934dbec5c5165c0ac82e4a8ba25 U dan -Z ae2ff3e1ce7fc4e495f8fcae9626097f +Z 7e377350f279588c8392c3c3e03a4f17 diff --git a/manifest.uuid b/manifest.uuid index 202cc78fbb..7a16425ff5 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -5343060bcc6c99029f731f8020d2cba3f405f207 \ No newline at end of file +f3b743623a4501833478c8a86c0922931955aeb6 \ No newline at end of file diff --git a/test/snapshot.test b/test/snapshot.test index 564881cae1..37af5b8e04 100644 --- a/test/snapshot.test +++ b/test/snapshot.test @@ -274,7 +274,6 @@ do_test 5.2 { set t53 0 proc write_callback {args} { -breakpoint do_test 5.3.[incr ::t53] { execsql BEGIN list [catch { sqlite3_snapshot_open db main $::snapshot } msg] $msg @@ -290,5 +289,33 @@ 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 From 7116dc60dc1017472e254a4e475a13c621996e6d Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 10 Dec 2015 20:03:08 +0000 Subject: [PATCH 10/15] Update the sqlite3_snapshot_get() API so that if the caller does not have an open read transaction on the named database, one is automatically opened. FossilOrigin-Name: b9c90f10297d8516a661449e8af898e682c930aa --- manifest | 16 ++++++++-------- manifest.uuid | 2 +- src/main.c | 9 +++++---- src/sqlite.h.in | 6 ++++-- test/snapshot.test | 41 ++++++++++++++++++++++++++++++----------- 5 files changed, 48 insertions(+), 26 deletions(-) diff --git a/manifest b/manifest index c962225c27..8387bc5312 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\stests\sto\ssnapshot.test. -D 2015-12-10T19:44:34.237 +C Update\sthe\ssqlite3_snapshot_get()\sAPI\sso\sthat\sif\sthe\scaller\sdoes\snot\shave\san\sopen\sread\stransaction\son\sthe\snamed\sdatabase,\sone\sis\sautomatically\sopened. +D 2015-12-10T20:03:08.672 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 3dc84d9bd722fb16c196a867d39acf86b8f72b70 +F src/main.c 2f33510a6a392c606fe64964e695e55ad124242f F src/malloc.c 337bbe9c7d436ef9b7d06b5dd10bbfc8f3025972 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c 6919bcf12f221868ea066eec27e579fed95ce98b @@ -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 19dea4862ccfcc1a733d0fd18d4744b02a505ac6 +F src/sqlite.h.in fa2c9d85cb144fad389864f9e40c4f59336d31ed F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad F src/sqlite3ext.h dfbe62ffd95b99afe2140d8c35b180d11924072d F src/sqliteInt.h 5caacf37a776f9d6178e519cb0b5248ca22a3828 @@ -1020,7 +1020,7 @@ F test/skipscan2.test d1d1450952b7275f0b0a3a981f0230532743951a F test/skipscan3.test ec5bab3f81c7038b43450e7b3062e04a198bdbb5 F test/skipscan5.test 67817a4b6857c47e0e33ba3e506da6f23ef68de2 F test/skipscan6.test 5866039d03a56f5bd0b3d172a012074a1d90a15b -F test/snapshot.test 62983ac46f05b898d5317a8d1b91a7bd8a355988 +F test/snapshot.test bfcf728577d674d85b7da4bc69786ecafd2acebe F test/snapshot_fault.test 06472056c516be4610834de4688ea3357e8bde01 F test/soak.test 0b5b6375c9f4110c828070b826b3b4b0bb65cd5f F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087 @@ -1410,7 +1410,7 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 5343060bcc6c99029f731f8020d2cba3f405f207 -R cf3f8934dbec5c5165c0ac82e4a8ba25 +P f3b743623a4501833478c8a86c0922931955aeb6 +R 50bb5d50d586d96f69cac8095fa25e56 U dan -Z 7e377350f279588c8392c3c3e03a4f17 +Z 07077a92d084d80e6cdd7a283dbdc283 diff --git a/manifest.uuid b/manifest.uuid index 7a16425ff5..3dacbbd066 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -f3b743623a4501833478c8a86c0922931955aeb6 \ No newline at end of file +b9c90f10297d8516a661449e8af898e682c930aa \ No newline at end of file diff --git a/src/main.c b/src/main.c index 902954b60d..5b2130511e 100644 --- a/src/main.c +++ b/src/main.c @@ -3891,10 +3891,11 @@ int sqlite3_snapshot_get( iDb = sqlite3FindDbName(db, zDb); if( iDb==0 || iDb>1 ){ Btree *pBt = db->aDb[iDb].pBt; - if( 0!=sqlite3BtreeIsInReadTrans(pBt) - && 0==sqlite3BtreeIsInTrans(pBt) - ){ - rc = sqlite3PagerSnapshotGet(sqlite3BtreePager(pBt), ppSnapshot); + if( 0==sqlite3BtreeIsInTrans(pBt) ){ + rc = sqlite3BtreeBeginTrans(pBt, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3PagerSnapshotGet(sqlite3BtreePager(pBt), ppSnapshot); + } } } diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 601415b907..8dcf99eb2a 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -7883,8 +7883,10 @@ int sqlite3_db_cacheflush(sqlite3*); ** ** The second argument passed to sqlite3_snapshot_get() must be the name ** of a database file attached to the database handle passed as the first. -** The database handle must have an open read transaction on the named -** database, which must be in wal mode. +** The database handle must not have an open write transaction on the named +** database, which must be in wal mode. If the database handle does not +** have an open read transaction on the named file, this function opens +** one. ** ** If successful, sqlite3_snapshot_get() sets *ppSnapshot to point to a new ** snapshot handle that may be used with sqlite3_snapshot_open() and returns diff --git a/test/snapshot.test b/test/snapshot.test index 37af5b8e04..a01ac5a754 100644 --- a/test/snapshot.test +++ b/test/snapshot.test @@ -19,9 +19,8 @@ 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. -# 2) there is no open read transaction on the database, or -# 3) there is an open write transaction on the database. +# 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); @@ -37,15 +36,9 @@ do_execsql_test 1.1.2 COMMIT do_test 1.2.1 { execsql { - PRAGMA journal_mode = wal; - INSERT INTO t1 VALUES(5, 6); - } - list [catch { sqlite3_snapshot_get db main } msg] $msg -} {1 SQLITE_ERROR} - -do_test 1.3.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 @@ -61,6 +54,7 @@ do_execsql_test 2.1.0 { 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 { @@ -114,6 +108,31 @@ do_test 2.2.3 { 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: # From e230a8994e1b0f4d76482e5c04954859e2cd32da Mon Sep 17 00:00:00 2001 From: drh Date: Thu, 10 Dec 2015 22:48:22 +0000 Subject: [PATCH 11/15] Snapshot documentation updates. Comment changes only - no changes to code. FossilOrigin-Name: ef51a398a04963508389ffe2f9d8359c1ec48261 --- manifest | 16 +++--- manifest.uuid | 2 +- src/sqlite.h.in | 142 ++++++++++++++++++++++++++++-------------------- src/wal.c | 6 ++ 4 files changed, 98 insertions(+), 68 deletions(-) diff --git a/manifest b/manifest index 8387bc5312..1988a1e007 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Update\sthe\ssqlite3_snapshot_get()\sAPI\sso\sthat\sif\sthe\scaller\sdoes\snot\shave\san\sopen\sread\stransaction\son\sthe\snamed\sdatabase,\sone\sis\sautomatically\sopened. -D 2015-12-10T20:03:08.672 +C Snapshot\sdocumentation\supdates.\s\sComment\schanges\sonly\s-\sno\schanges\sto\scode. +D 2015-12-10T22:48:22.684 F Makefile.in 28bcd6149e050dff35d4dcfd97e890cd387a499d F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc e8fdca1cb89a1b58b5f4d3a130ea9a3d28cb314d @@ -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 fa2c9d85cb144fad389864f9e40c4f59336d31ed +F src/sqlite.h.in e24a1d2e36970ca46beb8002623e1c73eab32063 F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad F src/sqlite3ext.h dfbe62ffd95b99afe2140d8c35b180d11924072d F src/sqliteInt.h 5caacf37a776f9d6178e519cb0b5248ca22a3828 @@ -415,7 +415,7 @@ F src/vdbesort.c a7ec02da4494c59dfd071126dd3726be5a11459d F src/vdbetrace.c 8befe829faff6d9e6f6e4dee5a7d3f85cc85f1a0 F src/vtab.c 2a8b44aa372c33f6154208e7a7f6c44254549806 F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb -F src/wal.c 94576d273e7feeae598188ebec641906a5b93a2f +F src/wal.c 4ecd256ad59675472fbf211001b511cb31877a74 F src/wal.h 907943dfdef10b583e81906679a347e0ec6f1b1b F src/walker.c 2e14d17f592d176b6dc879c33fbdec4fbccaa2ba F src/where.c b18edbb9e5afabb77f4f27550c471c5c824e0fe7 @@ -1410,7 +1410,7 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P f3b743623a4501833478c8a86c0922931955aeb6 -R 50bb5d50d586d96f69cac8095fa25e56 -U dan -Z 07077a92d084d80e6cdd7a283dbdc283 +P b9c90f10297d8516a661449e8af898e682c930aa +R 359f52ff04c40d68d5a15fc7ff658a21 +U drh +Z f10084cbd288f6e41be166b6d28c4cd6 diff --git a/manifest.uuid b/manifest.uuid index 3dacbbd066..d0158534a5 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -b9c90f10297d8516a661449e8af898e682c930aa \ No newline at end of file +ef51a398a04963508389ffe2f9d8359c1ec48261 \ No newline at end of file diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 8dcf99eb2a..780a702307 100644 --- a/src/sqlite.h.in +++ b/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,85 +7851,109 @@ 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: Open old database snapshots. +** CAPI3REF: Database Snapshot +** KEYWORDS: {snapshot} +** EXPERIMENTAL ** -** The second argument passed to sqlite3_snapshot_get() must be the name -** of a database file attached to the database handle passed as the first. -** The database handle must not have an open write transaction on the named -** database, which must be in wal mode. If the database handle does not -** have an open read transaction on the named file, this function opens -** one. +** An instance of the snapshot object records the state of a [WAL mode] +** database for some specific point in history. ** -** If successful, sqlite3_snapshot_get() sets *ppSnapshot to point to a new -** snapshot handle that may be used with sqlite3_snapshot_open() and returns -** SQLITE_OK. +** 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. ** -** If the specified database does not exist, or is not a wal mode database, -** or the database handle does not have an open read transaction on it, -** SQLITE_ERROR is returned. If any other error occurs, for example an IO -** error or an OOM condition, the corresponding SQLite error code is -** returned. +** 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. ** -** Each successful call to sqlite3_snapshot_get() must be matched by a call -** to sqlite3_snapshot_free() to delete the snapshot handle. Not doing so -** is a memory leak. The results of using a snapshot handle after it has -** been deleted by sqlite3_snapshot_free() are undefined. -** -** Given a snapshot handle, the sqlite3_snapshot_open() API function may be -** used to open a read transaction on the same database snapshot that was -** being read when sqlite3_snapshot_get() was called to obtain it. The -** combination of the first two arguments to sqlite3_snapshot_open() - a -** database handle and the name (e.g. "main") of one of its attached -** databases - must refer to the same database file as that identified by -** the arguments passed to the sqlite3_snapshot_get() call. The database -** handle must not have an open read or write transaction on this database -** file, and must not be in auto-commit mode. -** -** An old database snapshot may only be opened if SQLite is able to -** determine that it is still valid. The only way for an application to -** guarantee that a snapshot remains valid is by holding an open -** read-transaction on it or on an older snapshot of the same database -** file. If SQLite cannot determine that the snapshot identified by the -** snapshot handle, SQLITE_BUSY_SNAPSHOT is returned. -** -** Otherwise, if the read transaction is successfully opened, SQLITE_OK is -** returned. If the named database is not in wal mode or if the database -** handle already has an open read or write transaction on it, or if the -** database handle is in auto-commit mode, SQLITE_ERROR is returned. If -** an OOM or IO error occurs, the associated SQLite error code is returned. +** 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; -int sqlite3_snapshot_get(sqlite3*, const char*, sqlite3_snapshot **ppSnapshot); -void sqlite3_snapshot_free(sqlite3_snapshot*); -int sqlite3_snapshot_open(sqlite3*, const char*, 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. +*/ +SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(sqlite3*,const char*,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]. +*/ +SQLITE_EXPERIMENTAL int sqlite3_snapshot_open(sqlite3*,const char*,sqlite3_snapshot*); + +/* +** 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. +*/ +SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*); /* ** Undo the hack that converts floating point types to integer for diff --git a/src/wal.c b/src/wal.c index e5527029ac..bfc5050908 100644 --- a/src/wal.c +++ b/src/wal.c @@ -3269,6 +3269,10 @@ int sqlite3WalHeapMemory(Wal *pWal){ } #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; @@ -3286,6 +3290,8 @@ int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot){ return rc; } +/* Try to open on pSnapshot when the next read-transaction starts +*/ void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot){ pWal->pSnapshot = (WalIndexHdr*)pSnapshot; } From 71b62fa456178675bb85e3f111e87e39a3defd9a Mon Sep 17 00:00:00 2001 From: drh Date: Fri, 11 Dec 2015 01:22:22 +0000 Subject: [PATCH 12/15] Fix a bad assert related to snapshots. FossilOrigin-Name: 767ee30efa5dd469e3a51d4e44cbe473061819b9 --- manifest | 12 ++++++------ manifest.uuid | 2 +- src/wal.c | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/manifest b/manifest index 1988a1e007..84339a7e31 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Snapshot\sdocumentation\supdates.\s\sComment\schanges\sonly\s-\sno\schanges\sto\scode. -D 2015-12-10T22:48:22.684 +C Fix\sa\sbad\sassert\srelated\sto\ssnapshots. +D 2015-12-11T01:22:22.051 F Makefile.in 28bcd6149e050dff35d4dcfd97e890cd387a499d F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc e8fdca1cb89a1b58b5f4d3a130ea9a3d28cb314d @@ -415,7 +415,7 @@ F src/vdbesort.c a7ec02da4494c59dfd071126dd3726be5a11459d F src/vdbetrace.c 8befe829faff6d9e6f6e4dee5a7d3f85cc85f1a0 F src/vtab.c 2a8b44aa372c33f6154208e7a7f6c44254549806 F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb -F src/wal.c 4ecd256ad59675472fbf211001b511cb31877a74 +F src/wal.c 9f6568377ace5b49bca27b1331bfa39c932ba07e F src/wal.h 907943dfdef10b583e81906679a347e0ec6f1b1b F src/walker.c 2e14d17f592d176b6dc879c33fbdec4fbccaa2ba F src/where.c b18edbb9e5afabb77f4f27550c471c5c824e0fe7 @@ -1410,7 +1410,7 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P b9c90f10297d8516a661449e8af898e682c930aa -R 359f52ff04c40d68d5a15fc7ff658a21 +P ef51a398a04963508389ffe2f9d8359c1ec48261 +R e1d10abd98dcf1e7229a5f36920c7ecf U drh -Z f10084cbd288f6e41be166b6d28c4cd6 +Z 7fb95d54ba87149061a67ceb88009af9 diff --git a/manifest.uuid b/manifest.uuid index d0158534a5..cf46736bac 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -ef51a398a04963508389ffe2f9d8359c1ec48261 \ No newline at end of file +767ee30efa5dd469e3a51d4e44cbe473061819b9 \ No newline at end of file diff --git a/src/wal.c b/src/wal.c index bfc5050908..504316964d 100644 --- a/src/wal.c +++ b/src/wal.c @@ -2419,7 +2419,7 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ */ volatile WalCkptInfo *pInfo = walCkptInfo(pWal); - assert( pWal->readLock>0 ); + 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 From c49e960d41c9abb1c553cb527b083b9efc18c389 Mon Sep 17 00:00:00 2001 From: drh Date: Fri, 11 Dec 2015 03:16:54 +0000 Subject: [PATCH 13/15] Fix a variable type mismatch problem in the snapshot logic. FossilOrigin-Name: 93fb8010e4999b6b832d63a7c5a152f65d4415a3 --- manifest | 12 ++++++------ manifest.uuid | 2 +- src/wal.c | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/manifest b/manifest index 84339a7e31..416a40d864 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sa\sbad\sassert\srelated\sto\ssnapshots. -D 2015-12-11T01:22:22.051 +C Fix\sa\svariable\stype\smismatch\sproblem\sin\sthe\ssnapshot\slogic. +D 2015-12-11T03:16:54.491 F Makefile.in 28bcd6149e050dff35d4dcfd97e890cd387a499d F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc e8fdca1cb89a1b58b5f4d3a130ea9a3d28cb314d @@ -415,7 +415,7 @@ F src/vdbesort.c a7ec02da4494c59dfd071126dd3726be5a11459d F src/vdbetrace.c 8befe829faff6d9e6f6e4dee5a7d3f85cc85f1a0 F src/vtab.c 2a8b44aa372c33f6154208e7a7f6c44254549806 F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb -F src/wal.c 9f6568377ace5b49bca27b1331bfa39c932ba07e +F src/wal.c cb709aa42fc11b1ea92a00c8c7e6214de0995fa3 F src/wal.h 907943dfdef10b583e81906679a347e0ec6f1b1b F src/walker.c 2e14d17f592d176b6dc879c33fbdec4fbccaa2ba F src/where.c b18edbb9e5afabb77f4f27550c471c5c824e0fe7 @@ -1410,7 +1410,7 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P ef51a398a04963508389ffe2f9d8359c1ec48261 -R e1d10abd98dcf1e7229a5f36920c7ecf +P 767ee30efa5dd469e3a51d4e44cbe473061819b9 +R eb5f4e9ed632ef0781ac556c357a0ce5 U drh -Z 7fb95d54ba87149061a67ceb88009af9 +Z feb7b065ed13d6bdb0be293940a48a66 diff --git a/manifest.uuid b/manifest.uuid index cf46736bac..27b39abfda 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -767ee30efa5dd469e3a51d4e44cbe473061819b9 \ No newline at end of file +93fb8010e4999b6b832d63a7c5a152f65d4415a3 \ No newline at end of file diff --git a/src/wal.c b/src/wal.c index 504316964d..e4d2cf544c 100644 --- a/src/wal.c +++ b/src/wal.c @@ -2174,7 +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 */ - int mxFrame; /* Wal frame to lock to */ + u32 mxFrame; /* Wal frame to lock to */ assert( pWal->readLock<0 ); /* Not currently locked */ From 68d28ea4f06d3bff268d6b9ca9c3dc35a3e2a2ae Mon Sep 17 00:00:00 2001 From: drh Date: Fri, 11 Dec 2015 03:20:39 +0000 Subject: [PATCH 14/15] Disable the snapshot test scripts if not compiled with SQLITE_ENABLE_SNAPSHOT. FossilOrigin-Name: 5fd008f0433833e4341d526dcc2387570ffe1fea --- manifest | 16 ++++++++-------- manifest.uuid | 2 +- src/test_config.c | 6 ++++++ test/snapshot.test | 2 +- test/snapshot_fault.test | 2 +- 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/manifest b/manifest index 416a40d864..315698b1df 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sa\svariable\stype\smismatch\sproblem\sin\sthe\ssnapshot\slogic. -D 2015-12-11T03:16:54.491 +C Disable\sthe\ssnapshot\stest\sscripts\sif\snot\scompiled\swith\sSQLITE_ENABLE_SNAPSHOT. +D 2015-12-11T03:20:39.887 F Makefile.in 28bcd6149e050dff35d4dcfd97e890cd387a499d F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc e8fdca1cb89a1b58b5f4d3a130ea9a3d28cb314d @@ -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 @@ -1020,8 +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 bfcf728577d674d85b7da4bc69786ecafd2acebe -F test/snapshot_fault.test 06472056c516be4610834de4688ea3357e8bde01 +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 @@ -1410,7 +1410,7 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 767ee30efa5dd469e3a51d4e44cbe473061819b9 -R eb5f4e9ed632ef0781ac556c357a0ce5 +P 93fb8010e4999b6b832d63a7c5a152f65d4415a3 +R f04d367372fb1067c3ab40fd75809ec7 U drh -Z feb7b065ed13d6bdb0be293940a48a66 +Z 1fa92043878ab0279cb81464491eddf8 diff --git a/manifest.uuid b/manifest.uuid index 27b39abfda..bf47079e76 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -93fb8010e4999b6b832d63a7c5a152f65d4415a3 \ No newline at end of file +5fd008f0433833e4341d526dcc2387570ffe1fea \ No newline at end of file diff --git a/src/test_config.c b/src/test_config.c index a9ef182e02..30b421e00b 100644 --- a/src/test_config.c +++ b/src/test_config.c @@ -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 diff --git a/test/snapshot.test b/test/snapshot.test index a01ac5a754..6e160166c3 100644 --- a/test/snapshot.test +++ b/test/snapshot.test @@ -14,6 +14,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +ifcapable !snapshot {finish_test; return} set testprefix snapshot #------------------------------------------------------------------------- @@ -337,4 +338,3 @@ do_test 6.3 { sqlite3_snapshot_free $snapshot finish_test - diff --git a/test/snapshot_fault.test b/test/snapshot_fault.test index a7731b8161..3ac13daefd 100644 --- a/test/snapshot_fault.test +++ b/test/snapshot_fault.test @@ -14,6 +14,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +ifcapable !snapshot {finish_test; return} set testprefix snapshot_fault #------------------------------------------------------------------------- @@ -161,4 +162,3 @@ do_faultsim_test 3.0 -prep { finish_test - From 5a6e89c644f3dfd9bf085670f7a2c2252f824481 Mon Sep 17 00:00:00 2001 From: drh Date: Fri, 11 Dec 2015 03:27:36 +0000 Subject: [PATCH 15/15] Mention that the snapshot interfaces are only available if SQLite is compiled with SQLITE_ENABLE_SNAPSHOT. FossilOrigin-Name: 843c15a552657ca43ff200edb7da7566752d7941 --- manifest | 12 ++++++------ manifest.uuid | 2 +- src/sqlite.h.in | 21 +++++++++++++++++++-- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/manifest b/manifest index 315698b1df..268c0bf42d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Disable\sthe\ssnapshot\stest\sscripts\sif\snot\scompiled\swith\sSQLITE_ENABLE_SNAPSHOT. -D 2015-12-11T03:20:39.887 +C Mention\sthat\sthe\ssnapshot\sinterfaces\sare\sonly\savailable\sif\sSQLite\sis\scompiled\nwith\sSQLITE_ENABLE_SNAPSHOT. +D 2015-12-11T03:27:36.868 F Makefile.in 28bcd6149e050dff35d4dcfd97e890cd387a499d F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc e8fdca1cb89a1b58b5f4d3a130ea9a3d28cb314d @@ -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 e24a1d2e36970ca46beb8002623e1c73eab32063 +F src/sqlite.h.in 7d87d71b9a4689c51fa092f48f16590ff71558e3 F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad F src/sqlite3ext.h dfbe62ffd95b99afe2140d8c35b180d11924072d F src/sqliteInt.h 5caacf37a776f9d6178e519cb0b5248ca22a3828 @@ -1410,7 +1410,7 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 93fb8010e4999b6b832d63a7c5a152f65d4415a3 -R f04d367372fb1067c3ab40fd75809ec7 +P 5fd008f0433833e4341d526dcc2387570ffe1fea +R debac3c9c8655611fddbcca7514e6f4e U drh -Z 1fa92043878ab0279cb81464491eddf8 +Z a48afa3f9104a922fecee9583a7109ec diff --git a/manifest.uuid b/manifest.uuid index bf47079e76..b9d344ad3f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -5fd008f0433833e4341d526dcc2387570ffe1fea \ No newline at end of file +843c15a552657ca43ff200edb7da7566752d7941 \ No newline at end of file diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 780a702307..be58c7cd1f 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -7924,8 +7924,15 @@ typedef struct sqlite3_snapshot sqlite3_snapshot; ** 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*,const char*,sqlite3_snapshot **ppSnapshot); +SQLITE_EXPERIMENTAL int sqlite3_snapshot_get( + sqlite3 *db, + const char *zSchema, + sqlite3_snapshot **ppSnapshot +); /* ** CAPI3REF: Start a read transaction on an historical snapshot @@ -7942,8 +7949,15 @@ SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(sqlite3*,const char*,sqlite3_snapsh ** 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*,const char*,sqlite3_snapshot*); +SQLITE_EXPERIMENTAL int sqlite3_snapshot_open( + sqlite3 *db, + const char *zSchema, + sqlite3_snapshot *pSnapshot +); /* ** CAPI3REF: Destroy a snapshot @@ -7952,6 +7966,9 @@ SQLITE_EXPERIMENTAL int sqlite3_snapshot_open(sqlite3*,const char*,sqlite3_snaps ** ^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*);