Add the sqlite3_snapshot_recover() interface and related functionality.
FossilOrigin-Name: b70c85ce6d07b08e13f4ea99edb8f4855a6d7580
This commit is contained in:
+41
-8
@@ -3981,7 +3981,6 @@ int sqlite3_snapshot_get(
|
||||
){
|
||||
int rc = SQLITE_ERROR;
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
int iDb;
|
||||
|
||||
#ifdef SQLITE_ENABLE_API_ARMOR
|
||||
if( !sqlite3SafetyCheckOk(db) ){
|
||||
@@ -3990,13 +3989,15 @@ int sqlite3_snapshot_get(
|
||||
#endif
|
||||
sqlite3_mutex_enter(db->mutex);
|
||||
|
||||
iDb = sqlite3FindDbName(db, zDb);
|
||||
if( iDb==0 || iDb>1 ){
|
||||
Btree *pBt = db->aDb[iDb].pBt;
|
||||
if( 0==sqlite3BtreeIsInTrans(pBt) ){
|
||||
rc = sqlite3BtreeBeginTrans(pBt, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3PagerSnapshotGet(sqlite3BtreePager(pBt), ppSnapshot);
|
||||
if( db->autoCommit==0 ){
|
||||
int iDb = sqlite3FindDbName(db, zDb);
|
||||
if( iDb==0 || iDb>1 ){
|
||||
Btree *pBt = db->aDb[iDb].pBt;
|
||||
if( 0==sqlite3BtreeIsInTrans(pBt) ){
|
||||
rc = sqlite3BtreeBeginTrans(pBt, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3PagerSnapshotGet(sqlite3BtreePager(pBt), ppSnapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4043,6 +4044,38 @@ int sqlite3_snapshot_open(
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Recover as many snapshots as possible from the wal file associated with
|
||||
** schema zDb of database db.
|
||||
*/
|
||||
int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb){
|
||||
int rc = SQLITE_ERROR;
|
||||
int iDb;
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
|
||||
#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) ){
|
||||
rc = sqlite3BtreeBeginTrans(pBt, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3PagerSnapshotRecover(sqlite3BtreePager(pBt));
|
||||
sqlite3BtreeCommit(pBt);
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlite3_mutex_leave(db->mutex);
|
||||
#endif /* SQLITE_OMIT_WAL */
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Free a snapshot handle obtained from sqlite3_snapshot_get().
|
||||
*/
|
||||
|
||||
+14
@@ -7405,6 +7405,20 @@ int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot){
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** If this is a WAL database, call sqlite3WalSnapshotRecover(). If this
|
||||
** is not a WAL database, return an error.
|
||||
*/
|
||||
int sqlite3PagerSnapshotRecover(Pager *pPager){
|
||||
int rc;
|
||||
if( pPager->pWal ){
|
||||
rc = sqlite3WalSnapshotRecover(pPager->pWal);
|
||||
}else{
|
||||
rc = SQLITE_ERROR;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_SNAPSHOT */
|
||||
#endif /* !SQLITE_OMIT_WAL */
|
||||
|
||||
|
||||
@@ -182,6 +182,7 @@ int sqlite3PagerSharedLock(Pager *pPager);
|
||||
# ifdef SQLITE_ENABLE_SNAPSHOT
|
||||
int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot);
|
||||
int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot);
|
||||
int sqlite3PagerSnapshotRecover(Pager *pPager);
|
||||
# endif
|
||||
#else
|
||||
# define sqlite3PagerUseWal(x) 0
|
||||
|
||||
+51
-4
@@ -8287,7 +8287,9 @@ int sqlite3_system_errno(sqlite3*);
|
||||
** to an historical snapshot (if possible). The destructor for
|
||||
** sqlite3_snapshot objects is [sqlite3_snapshot_free()].
|
||||
*/
|
||||
typedef struct sqlite3_snapshot sqlite3_snapshot;
|
||||
typedef struct sqlite3_snapshot {
|
||||
unsigned char hidden[48];
|
||||
} sqlite3_snapshot;
|
||||
|
||||
/*
|
||||
** CAPI3REF: Record A Database Snapshot
|
||||
@@ -8298,9 +8300,32 @@ typedef struct sqlite3_snapshot sqlite3_snapshot;
|
||||
** 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].
|
||||
** If there is not already a read-transaction open on schema S when
|
||||
** this function is called, one is opened automatically.
|
||||
**
|
||||
** The following must be true for this function to succeed. If any of
|
||||
** the following statements are false when sqlite3_snapshot_get() is
|
||||
** called, SQLITE_ERROR is returned. The final value of *P is undefined
|
||||
** in this case.
|
||||
**
|
||||
** <ul>
|
||||
** <li> The database handle must be in [autocommit mode].
|
||||
**
|
||||
** <li> Schema S of [database connection] D must be a [WAL mode] database.
|
||||
**
|
||||
** <li> There must not be a write transaction open on schema S of database
|
||||
** connection D.
|
||||
**
|
||||
** <li> One or more transactions must have been written to the current wal
|
||||
** file since it was created on disk (by any connection). This means
|
||||
** that a snapshot cannot be taken on a wal mode database with no wal
|
||||
** file immediately after it is first opened. At least one transaction
|
||||
** must be written to it first.
|
||||
** </ul>
|
||||
**
|
||||
** This function may also return SQLITE_NOMEM. If it is called with the
|
||||
** database handle in autocommit mode but fails for some other reason,
|
||||
** whether or not a read transaction is opened on schema S is undefined.
|
||||
**
|
||||
** The [sqlite3_snapshot] object returned from a successful call to
|
||||
** [sqlite3_snapshot_get()] must be freed using [sqlite3_snapshot_free()]
|
||||
@@ -8393,6 +8418,28 @@ SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
|
||||
sqlite3_snapshot *p2
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Recover snapshots from a wal file
|
||||
** EXPERIMENTAL
|
||||
**
|
||||
** If all connections disconnect from a database file but do not perform
|
||||
** a checkpoint, the existing wal file is opened along with the database
|
||||
** file the next time the database is opened. At this point it is only
|
||||
** possible to successfully call sqlite3_snapshot_open() to open the most
|
||||
** recent snapshot of the database (the one at the head of the wal file),
|
||||
** even though the wal file may contain other valid snapshots for which
|
||||
** clients have sqlite3_snapshot handles.
|
||||
**
|
||||
** This function attempts to scan the wal file associated with database zDb
|
||||
** of database handle db and make all valid snapshots available to
|
||||
** sqlite3_snapshot_open(). It is an error if there is already a read
|
||||
** transaction open on the database, or if the database is not a wal mode
|
||||
** database.
|
||||
**
|
||||
** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
|
||||
*/
|
||||
SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);
|
||||
|
||||
/*
|
||||
** Undo the hack that converts floating point types to integer for
|
||||
** builds on processors without floating point support.
|
||||
|
||||
+143
@@ -2310,6 +2310,38 @@ static int SQLITE_TCLAPI test_snapshot_get(
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_SNAPSHOT */
|
||||
|
||||
#ifdef SQLITE_ENABLE_SNAPSHOT
|
||||
/*
|
||||
** Usage: sqlite3_snapshot_recover DB DBNAME
|
||||
*/
|
||||
static int SQLITE_TCLAPI test_snapshot_recover(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
int rc;
|
||||
sqlite3 *db;
|
||||
char *zName;
|
||||
|
||||
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_recover(db, zName);
|
||||
if( rc!=SQLITE_OK ){
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
|
||||
return TCL_ERROR;
|
||||
}else{
|
||||
Tcl_ResetResult(interp);
|
||||
}
|
||||
return TCL_OK;
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_SNAPSHOT */
|
||||
|
||||
#ifdef SQLITE_ENABLE_SNAPSHOT
|
||||
/*
|
||||
** Usage: sqlite3_snapshot_open DB DBNAME SNAPSHOT
|
||||
@@ -2388,6 +2420,113 @@ static int SQLITE_TCLAPI test_snapshot_cmp(
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_SNAPSHOT */
|
||||
|
||||
#ifdef SQLITE_ENABLE_SNAPSHOT
|
||||
/*
|
||||
** Usage: sqlite3_snapshot_get_blob DB DBNAME
|
||||
*/
|
||||
static int SQLITE_TCLAPI test_snapshot_get_blob(
|
||||
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{
|
||||
Tcl_SetObjResult(interp,
|
||||
Tcl_NewByteArrayObj((unsigned char*)pSnapshot, sizeof(sqlite3_snapshot))
|
||||
);
|
||||
sqlite3_snapshot_free(pSnapshot);
|
||||
}
|
||||
return TCL_OK;
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_SNAPSHOT */
|
||||
|
||||
#ifdef SQLITE_ENABLE_SNAPSHOT
|
||||
/*
|
||||
** Usage: sqlite3_snapshot_open_blob DB DBNAME SNAPSHOT
|
||||
*/
|
||||
static int SQLITE_TCLAPI test_snapshot_open_blob(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
int rc;
|
||||
sqlite3 *db;
|
||||
char *zName;
|
||||
unsigned char *pBlob;
|
||||
int nBlob;
|
||||
|
||||
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]);
|
||||
pBlob = Tcl_GetByteArrayFromObj(objv[3], &nBlob);
|
||||
if( nBlob!=sizeof(sqlite3_snapshot) ){
|
||||
Tcl_AppendResult(interp, "bad SNAPSHOT", 0);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
rc = sqlite3_snapshot_open(db, zName, (sqlite3_snapshot*)pBlob);
|
||||
if( rc!=SQLITE_OK ){
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
|
||||
return TCL_ERROR;
|
||||
}
|
||||
return TCL_OK;
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_SNAPSHOT */
|
||||
|
||||
#ifdef SQLITE_ENABLE_SNAPSHOT
|
||||
/*
|
||||
** Usage: sqlite3_snapshot_cmp_blob SNAPSHOT1 SNAPSHOT2
|
||||
*/
|
||||
static int SQLITE_TCLAPI test_snapshot_cmp_blob(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
int res;
|
||||
unsigned char *p1;
|
||||
unsigned char *p2;
|
||||
int n1;
|
||||
int n2;
|
||||
|
||||
if( objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "SNAPSHOT1 SNAPSHOT2");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
p1 = Tcl_GetByteArrayFromObj(objv[1], &n1);
|
||||
p2 = Tcl_GetByteArrayFromObj(objv[2], &n2);
|
||||
|
||||
if( n1!=sizeof(sqlite3_snapshot) || n1!=n2 ){
|
||||
Tcl_AppendResult(interp, "bad SNAPSHOT", 0);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
res = sqlite3_snapshot_cmp((sqlite3_snapshot*)p1, (sqlite3_snapshot*)p2);
|
||||
Tcl_SetObjResult(interp, Tcl_NewIntObj(res));
|
||||
return TCL_OK;
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_SNAPSHOT */
|
||||
|
||||
/*
|
||||
** Usage: sqlite3_delete_database FILENAME
|
||||
*/
|
||||
@@ -7539,6 +7678,10 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
|
||||
{ "sqlite3_snapshot_open", test_snapshot_open, 0 },
|
||||
{ "sqlite3_snapshot_free", test_snapshot_free, 0 },
|
||||
{ "sqlite3_snapshot_cmp", test_snapshot_cmp, 0 },
|
||||
{ "sqlite3_snapshot_recover", test_snapshot_recover, 0 },
|
||||
{ "sqlite3_snapshot_get_blob", test_snapshot_get_blob, 0 },
|
||||
{ "sqlite3_snapshot_open_blob", test_snapshot_open_blob, 0 },
|
||||
{ "sqlite3_snapshot_cmp_blob", test_snapshot_cmp_blob, 0 },
|
||||
#endif
|
||||
{ "sqlite3_delete_database", test_delete_database, 0 },
|
||||
};
|
||||
|
||||
@@ -2379,6 +2379,84 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_ENABLE_SNAPSHOT
|
||||
/*
|
||||
** Attempt to reduce the value of the WalCkptInfo.nBackfillAttempted
|
||||
** variable so that older snapshots can be accessed. To do this, loop
|
||||
** through all wal frames from nBackfillAttempted to (nBackfill+1),
|
||||
** comparing their content to the corresponding page with the database
|
||||
** file, if any. Set nBackfillAttempted to the frame number of the
|
||||
** first frame for which the wal file content matches the db file.
|
||||
**
|
||||
** This is only really safe if the file-system is such that any page
|
||||
** writes made by earlier checkpointers were atomic operations, which
|
||||
** is not always true. It is also possible that nBackfillAttempted
|
||||
** may be left set to a value larger than expected, if a wal frame
|
||||
** contains content that duplicate of an earlier version of the same
|
||||
** page.
|
||||
**
|
||||
** SQLITE_OK is returned if successful, or an SQLite error code if an
|
||||
** error occurs. It is not an error if nBackfillAttempted cannot be
|
||||
** decreased at all.
|
||||
*/
|
||||
int sqlite3WalSnapshotRecover(Wal *pWal){
|
||||
int rc;
|
||||
|
||||
assert( pWal->readLock>=0 );
|
||||
rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1);
|
||||
if( rc==SQLITE_OK ){
|
||||
volatile WalCkptInfo *pInfo = walCkptInfo(pWal);
|
||||
int szPage = (int)pWal->szPage;
|
||||
i64 szDb; /* Size of db file in bytes */
|
||||
|
||||
rc = sqlite3OsFileSize(pWal->pDbFd, &szDb);
|
||||
if( rc==SQLITE_OK ){
|
||||
void *pBuf1 = sqlite3_malloc(szPage);
|
||||
void *pBuf2 = sqlite3_malloc(szPage);
|
||||
if( pBuf1==0 || pBuf2==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
u32 i = pInfo->nBackfillAttempted;
|
||||
for(i=pInfo->nBackfillAttempted; i>pInfo->nBackfill; i--){
|
||||
volatile ht_slot *dummy;
|
||||
volatile u32 *aPgno; /* Array of page numbers */
|
||||
u32 iZero; /* Frame corresponding to aPgno[0] */
|
||||
u32 pgno; /* Page number in db file */
|
||||
i64 iDbOff; /* Offset of db file entry */
|
||||
i64 iWalOff; /* Offset of wal file entry */
|
||||
|
||||
rc = walHashGet(pWal, walFramePage(i), &dummy, &aPgno, &iZero);
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
pgno = aPgno[i-iZero];
|
||||
iDbOff = (i64)(pgno-1) * szPage;
|
||||
|
||||
if( iDbOff+szPage<=szDb ){
|
||||
iWalOff = walFrameOffset(i, szPage) + WAL_FRAME_HDRSIZE;
|
||||
rc = sqlite3OsRead(pWal->pWalFd, pBuf1, szPage, iWalOff);
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3OsRead(pWal->pDbFd, pBuf2, szPage, iDbOff);
|
||||
}
|
||||
|
||||
if( rc!=SQLITE_OK || 0==memcmp(pBuf1, pBuf2, szPage) ){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pInfo->nBackfillAttempted = i-1;
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3_free(pBuf1);
|
||||
sqlite3_free(pBuf2);
|
||||
}
|
||||
walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_SNAPSHOT */
|
||||
|
||||
/*
|
||||
** Begin a read transaction on the database.
|
||||
**
|
||||
@@ -2441,7 +2519,11 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){
|
||||
** 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. */
|
||||
** before checking pInfo->nBackfillAttempted.
|
||||
**
|
||||
** TODO: Does the aReadMark[] lock prevent a checkpointer from doing
|
||||
** this already?
|
||||
*/
|
||||
rc = walLockShared(pWal, WAL_CKPT_LOCK);
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
@@ -3393,9 +3475,14 @@ int sqlite3WalHeapMemory(Wal *pWal){
|
||||
int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot){
|
||||
int rc = SQLITE_OK;
|
||||
WalIndexHdr *pRet;
|
||||
static const u32 aZero[4] = { 0, 0, 0, 0 };
|
||||
|
||||
assert( pWal->readLock>=0 && pWal->writeLock==0 );
|
||||
|
||||
if( memcmp(&pWal->hdr.aFrameCksum[0],aZero,16)==0 ){
|
||||
*ppSnapshot = 0;
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
pRet = (WalIndexHdr*)sqlite3_malloc(sizeof(WalIndexHdr));
|
||||
if( pRet==0 ){
|
||||
rc = SQLITE_NOMEM_BKPT;
|
||||
|
||||
@@ -131,6 +131,7 @@ int sqlite3WalHeapMemory(Wal *pWal);
|
||||
#ifdef SQLITE_ENABLE_SNAPSHOT
|
||||
int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot);
|
||||
void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot);
|
||||
int sqlite3WalSnapshotRecover(Wal *pWal);
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_ENABLE_ZIPVFS
|
||||
|
||||
Reference in New Issue
Block a user