aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.c82
-rw-r--r--src/pager.c28
-rw-r--r--src/pager.h4
-rw-r--r--src/sqlite.h.in23
-rw-r--r--src/test1.c87
-rw-r--r--src/wal.c78
-rw-r--r--src/wal.h5
7 files changed, 303 insertions, 4 deletions
diff --git a/src/main.c b/src/main.c
index d552f7fbc..902954b60 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 bf74eac54..2c8dceb75 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 cf9cda625..ba4eec438 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 e79757106..e8940b5e0 100644
--- a/src/sqlite.h.in
+++ b/src/sqlite.h.in
@@ -7879,6 +7879,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 186e4e468..31f506ea4 100644
--- a/src/test1.c
+++ b/src/test1.c
@@ -2270,6 +2270,88 @@ static int vfsCurrentTimeInt64(
}
/*
+** 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
**
** Return the next statment in sequence after 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 144db27a3..c4823c7cd 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; i<WAL_NREADER; i++){
u32 thisMark = pInfo->aReadMark[i];
- if( mxReadMark<=thisMark && thisMark<=pWal->hdr.mxFrame ){
+ if( mxReadMark<=thisMark && thisMark<=mxFrame ){
assert( thisMark!=READMARK_NOT_USED );
mxReadMark = thisMark;
mxI = i;
@@ -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
- && (mxReadMark<pWal->hdr.mxFrame || mxI==0)
+ && (mxReadMark<mxFrame || mxI==0)
){
for(i=1; i<WAL_NREADER; i++){
rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1);
if( rc==SQLITE_OK ){
- mxReadMark = pInfo->aReadMark[i] = 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 092546354..94a049493 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).