Skip to content

Commit

Permalink
Allow sqlite3_snapshot_open() to be called to change the snapshot aft…
Browse files Browse the repository at this point in the history
…er a read

transaction is already open on database.
  • Loading branch information
danielk-1977 committed Aug 15, 2018
2 parents b3460e6 + be860a1 commit 005d08c
Show file tree
Hide file tree
Showing 9 changed files with 325 additions and 22 deletions.
24 changes: 21 additions & 3 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -4209,11 +4209,29 @@ int sqlite3_snapshot_open(
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( sqlite3BtreeIsInTrans(pBt)==0 ){
Pager *pPager = sqlite3BtreePager(pBt);
int bUnlock = 0;
if( sqlite3BtreeIsInReadTrans(pBt) ){
if( db->nVdbeActive==0 ){
rc = sqlite3PagerSnapshotCheck(pPager, pSnapshot);
if( rc==SQLITE_OK ){
bUnlock = 1;
rc = sqlite3BtreeCommit(pBt);
}
}
}else{
rc = SQLITE_OK;
}
if( rc==SQLITE_OK ){
rc = sqlite3PagerSnapshotOpen(pPager, pSnapshot);
}
if( rc==SQLITE_OK ){
rc = sqlite3BtreeBeginTrans(pBt, 0, 0);
sqlite3PagerSnapshotOpen(sqlite3BtreePager(pBt), 0);
sqlite3PagerSnapshotOpen(pPager, 0);
}
if( bUnlock ){
sqlite3PagerSnapshotUnlock(pPager);
}
}
}
Expand Down
32 changes: 32 additions & 0 deletions src/pager.c
Original file line number Diff line number Diff line change
Expand Up @@ -7653,6 +7653,38 @@ int sqlite3PagerSnapshotRecover(Pager *pPager){
}
return rc;
}

/*
** The caller currently has a read transaction open on the database.
** If this is not a WAL database, SQLITE_ERROR is returned. Otherwise,
** this function takes a SHARED lock on the CHECKPOINTER slot and then
** checks if the snapshot passed as the second argument is still
** available. If so, SQLITE_OK is returned.
**
** If the snapshot is not available, SQLITE_ERROR is returned. Or, if
** the CHECKPOINTER lock cannot be obtained, SQLITE_BUSY. If any error
** occurs (any value other than SQLITE_OK is returned), the CHECKPOINTER
** lock is released before returning.
*/
int sqlite3PagerSnapshotCheck(Pager *pPager, sqlite3_snapshot *pSnapshot){
int rc;
if( pPager->pWal ){
rc = sqlite3WalSnapshotCheck(pPager->pWal, pSnapshot);
}else{
rc = SQLITE_ERROR;
}
return rc;
}

/*
** Release a lock obtained by an earlier successful call to
** sqlite3PagerSnapshotCheck().
*/
void sqlite3PagerSnapshotUnlock(Pager *pPager){
assert( pPager->pWal );
return sqlite3WalSnapshotUnlock(pPager->pWal);
}

#endif /* SQLITE_ENABLE_SNAPSHOT */
#endif /* !SQLITE_OMIT_WAL */

Expand Down
2 changes: 2 additions & 0 deletions src/pager.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ int sqlite3PagerSharedLock(Pager *pPager);
int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot);
int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot);
int sqlite3PagerSnapshotRecover(Pager *pPager);
int sqlite3PagerSnapshotCheck(Pager *pPager, sqlite3_snapshot *pSnapshot);
void sqlite3PagerSnapshotUnlock(Pager *pPager);
# endif
#else
# define sqlite3PagerUseWal(x,y) 0
Expand Down
43 changes: 27 additions & 16 deletions src/sqlite.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -9035,22 +9035,33 @@ SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
** CAPI3REF: Start a read transaction on an historical snapshot
** METHOD: sqlite3_snapshot
**
** ^The [sqlite3_snapshot_open(D,S,P)] interface starts a
** read transaction for schema S of
** [database connection] D such that the read transaction
** refers to historical [snapshot] P, rather than the most
** recent change to the database.
** ^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 following the [BEGIN] that takes the schema S
** out of [autocommit mode].
** ^In other words, schema S must not currently be in
** a transaction for [sqlite3_snapshot_open(D,S,P)] to work, but the
** database connection D must be out of [autocommit mode].
** ^A [snapshot] will fail to open if it has been overwritten by a
** [checkpoint].
** ^The [sqlite3_snapshot_open(D,S,P)] interface either starts a new read
** transaction or upgrades an existing one for schema S of
** [database connection] D such that the read transaction refers to
** historical [snapshot] P, rather than the most recent change to the
** database. ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK
** on success or an appropriate [error code] if it fails.
**
** ^In order to succeed, the database connection must not be in
** [autocommit mode] when [sqlite3_snapshot_open(D,S,P)] is called. If there
** is already a read transaction open on schema S, then the database handle
** must have no active statements (SELECT statements that have been passed
** to sqlite3_step() but not sqlite3_reset() or sqlite3_finalize()).
** SQLITE_ERROR is returned if either of these conditions is violated, or
** if schema S does not exist, or if the snapshot object is invalid.
**
** ^A call to sqlite3_snapshot_open() will fail to open if the specified
** snapshot has been overwritten by a [checkpoint]. In this case
** SQLITE_BUSY_SNAPSHOT is returned.
**
** If there is already a read transaction open when this function is
** invoked, then the same read transaction remains open (on the same
** database snapshot) if SQLITE_ERROR, SQLITE_BUSY or SQLITE_BUSY_SNAPSHOT
** is returned. If another error code - for example SQLITE_PROTOCOL or an
** SQLITE_IOERR error code - is returned, then the final state of the
** read transaction is undefined. If SQLITE_OK is returned, then the
** read transaction is now open on database snapshot P.
**
** ^(A call to [sqlite3_snapshot_open(D,S,P)] will fail if the
** database connection D does not know that the database file for
** schema S is in [WAL mode]. A database connection might not know
Expand Down
2 changes: 2 additions & 0 deletions src/test1.c
Original file line number Diff line number Diff line change
Expand Up @@ -2393,6 +2393,8 @@ static int SQLITE_TCLAPI test_snapshot_open(
if( rc!=SQLITE_OK ){
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
return TCL_ERROR;
}else{
Tcl_ResetResult(interp);
}
return TCL_OK;
}
Expand Down
37 changes: 37 additions & 0 deletions src/wal.c
Original file line number Diff line number Diff line change
Expand Up @@ -3769,6 +3769,43 @@ int sqlite3_snapshot_cmp(sqlite3_snapshot *p1, sqlite3_snapshot *p2){
if( pHdr1->mxFrame>pHdr2->mxFrame ) return +1;
return 0;
}

/*
** The caller currently has a read transaction open on the database.
** This function takes a SHARED lock on the CHECKPOINTER slot and then
** checks if the snapshot passed as the second argument is still
** available. If so, SQLITE_OK is returned.
**
** If the snapshot is not available, SQLITE_ERROR is returned. Or, if
** the CHECKPOINTER lock cannot be obtained, SQLITE_BUSY. If any error
** occurs (any value other than SQLITE_OK is returned), the CHECKPOINTER
** lock is released before returning.
*/
int sqlite3WalSnapshotCheck(Wal *pWal, sqlite3_snapshot *pSnapshot){
int rc;
rc = walLockShared(pWal, WAL_CKPT_LOCK);
if( rc==SQLITE_OK ){
WalIndexHdr *pNew = (WalIndexHdr*)pSnapshot;
if( memcmp(pNew->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt))
|| pNew->mxFrame<walCkptInfo(pWal)->nBackfillAttempted
){
rc = SQLITE_BUSY_SNAPSHOT;
walUnlockShared(pWal, WAL_CKPT_LOCK);
}
}
return rc;
}

/*
** Release a lock obtained by an earlier successful call to
** sqlite3WalSnapshotCheck().
*/
void sqlite3WalSnapshotUnlock(Wal *pWal){
assert( pWal );
walUnlockShared(pWal, WAL_CKPT_LOCK);
}


#endif /* SQLITE_ENABLE_SNAPSHOT */

#ifdef SQLITE_ENABLE_ZIPVFS
Expand Down
2 changes: 2 additions & 0 deletions src/wal.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ int sqlite3WalHeapMemory(Wal *pWal);
int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot);
void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot);
int sqlite3WalSnapshotRecover(Wal *pWal);
int sqlite3WalSnapshotCheck(Wal *pWal, sqlite3_snapshot *pSnapshot);
void sqlite3WalSnapshotUnlock(Wal *pWal);
#endif

#ifdef SQLITE_ENABLE_ZIPVFS
Expand Down
21 changes: 18 additions & 3 deletions test/snapshot.test
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,19 @@ foreach {tn tcl} {
SELECT * FROM t2;
}
} {a b c d e f}
do_test $tn.3.2.2 {
list [catch {snapshot_open db main $snapshot } msg] $msg

# Update - it is no longer an error to have a read-transaction open,
# provided there are no active SELECT statements.
do_test $tn.3.2.2a {
db eval "SELECT * FROM t2" {
set res [list [catch {snapshot_open db main $snapshot } msg] $msg]
break
}
set res
} {1 SQLITE_ERROR}
do_test $tn.3.2.2b {
snapshot_open db main $snapshot
} {}

do_test $tn.3.2.3 {
execsql {
Expand All @@ -231,12 +241,17 @@ foreach {tn tcl} {
} {1 SQLITE_ERROR}
do_execsql_test $tn.3.2.4 COMMIT

do_test $tn.3.3.1 {
do_test $tn.3.3.1a {
execsql { PRAGMA journal_mode = DELETE }
execsql { BEGIN }
list [catch {snapshot_open db main $snapshot } msg] $msg
} {1 SQLITE_ERROR}

do_test $tn.3.3.1b {
execsql { COMMIT ; BEGIN ; SELECT * FROM t2 }
list [catch {snapshot_open db main $snapshot } msg] $msg
} {1 SQLITE_ERROR}

do_test $tn.$tn.3.3.2 {
snapshot_free $snapshot
execsql COMMIT
Expand Down
Loading

0 comments on commit 005d08c

Please sign in to comment.