diff --git a/libsql-ffi/bundled/SQLite3MultipleCiphers/src/sqlite3.c b/libsql-ffi/bundled/SQLite3MultipleCiphers/src/sqlite3.c index c286d77b4f..62a917fb9b 100644 --- a/libsql-ffi/bundled/SQLite3MultipleCiphers/src/sqlite3.c +++ b/libsql-ffi/bundled/SQLite3MultipleCiphers/src/sqlite3.c @@ -14224,7 +14224,7 @@ typedef struct wal_impl wal_impl; typedef struct wal_manager_impl wal_manager_impl; typedef struct libsql_wal_methods { - int iVersion; /* Current version is 1, versioning is here for backward compatibility */ + int iVersion; /* Current version is 2, versioning is here for backward compatibility */ /* Set the limiting size of a WAL file. */ void (*xLimit)(wal_impl* pWal, long long limit); @@ -14326,6 +14326,9 @@ typedef struct libsql_wal_methods { int (*xWriteLock)(wal_impl* pWal, int bLock); void (*xDb)(wal_impl* pWal, sqlite3 *db); + + /* Forget a previously opened savepoint that SQLite no longer needs. */ + void (*xSavepointForget)(wal_impl* pWal, unsigned int *aWalData); } libsql_wal_methods; @@ -57825,7 +57828,7 @@ typedef struct wal_impl wal_impl; typedef struct wal_manager_impl wal_manager_impl; typedef struct libsql_wal_methods { - int iVersion; /* Current version is 1, versioning is here for backward compatibility */ + int iVersion; /* Current version is 2, versioning is here for backward compatibility */ /* Set the limiting size of a WAL file. */ void (*xLimit)(wal_impl* pWal, long long limit); @@ -57927,6 +57930,9 @@ typedef struct libsql_wal_methods { int (*xWriteLock)(wal_impl* pWal, int bLock); void (*xDb)(wal_impl* pWal, sqlite3 *db); + + /* Forget a previously opened savepoint that SQLite no longer needs. */ + void (*xSavepointForget)(wal_impl* pWal, unsigned int *aWalData); } libsql_wal_methods; @@ -65086,6 +65092,14 @@ SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ */ nNew = iSavepoint + (( op==SAVEPOINT_RELEASE ) ? 0 : 1); for(ii=nNew; iinSavepoint; ii++){ + if( pagerUseWal(pPager) + && pPager->wal->methods.iVersion>=2 + && pPager->wal->methods.xSavepointForget + ){ + pPager->wal->methods.xSavepointForget( + pPager->wal->pData, pPager->aSavepoint[ii].aWalData + ); + } sqlite3BitvecDestroy(pPager->aSavepoint[ii].pInSavepoint); } pPager->nSavepoint = nNew; @@ -69661,6 +69675,15 @@ static int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){ return rc; } +/* +** Forget a WAL savepoint context that SQLite has destroyed. The built-in WAL +** implementation does not retain any external state per savepoint. +*/ +static void sqlite3WalSavepointForget(Wal *pWal, u32 *aWalData){ + UNUSED_PARAMETER(pWal); + UNUSED_PARAMETER(aWalData); +} + /* ** This function is called just before writing a set of frames to the log ** file (see sqlite3WalFrames()). It checks to see if, instead of appending @@ -70614,7 +70637,7 @@ static int sqlite3WalOpen( } out->pData = (wal_impl*) pRet; - out->methods.iVersion = 1; + out->methods.iVersion = 2; out->methods.xLimit = (void (*)(wal_impl *, long long))sqlite3WalLimit; out->methods.xBeginReadTransaction = (int (*)(wal_impl *, int *))sqlite3WalBeginReadTransaction; out->methods.xEndReadTransaction = (void (*)(wal_impl *))sqlite3WalEndReadTransaction; @@ -70651,6 +70674,7 @@ static int sqlite3WalOpen( outWal->methods.xWriteLock = sqlite3WalWriteLock; #endif out->methods.xDb = (void (*)(wal_impl *, sqlite3 *))sqlite3WalDb; + out->methods.xSavepointForget = (void (*)(wal_impl *, unsigned int *))sqlite3WalSavepointForget; WALTRACE(("WAL%d: opened\n", pRet)); } diff --git a/libsql-ffi/bundled/SQLite3MultipleCiphers/src/sqlite3.h b/libsql-ffi/bundled/SQLite3MultipleCiphers/src/sqlite3.h index 77de0af6c3..997e7729d1 100644 --- a/libsql-ffi/bundled/SQLite3MultipleCiphers/src/sqlite3.h +++ b/libsql-ffi/bundled/SQLite3MultipleCiphers/src/sqlite3.h @@ -13829,7 +13829,7 @@ typedef struct wal_impl wal_impl; typedef struct wal_manager_impl wal_manager_impl; typedef struct libsql_wal_methods { - int iVersion; /* Current version is 1, versioning is here for backward compatibility */ + int iVersion; /* Current version is 2, versioning is here for backward compatibility */ /* Set the limiting size of a WAL file. */ void (*xLimit)(wal_impl* pWal, long long limit); @@ -13931,6 +13931,9 @@ typedef struct libsql_wal_methods { int (*xWriteLock)(wal_impl* pWal, int bLock); void (*xDb)(wal_impl* pWal, sqlite3 *db); + + /* Forget a previously opened savepoint that SQLite no longer needs. */ + void (*xSavepointForget)(wal_impl* pWal, unsigned int *aWalData); } libsql_wal_methods; diff --git a/libsql-ffi/bundled/bindings/bindgen.rs b/libsql-ffi/bundled/bindings/bindgen.rs index e94ea459b0..56209dcb8c 100644 --- a/libsql-ffi/bundled/bindings/bindgen.rs +++ b/libsql-ffi/bundled/bindings/bindgen.rs @@ -3526,6 +3526,9 @@ pub struct libsql_wal_methods { ) -> ::std::os::raw::c_int, >, pub xDb: ::std::option::Option, + pub xSavepointForget: ::std::option::Option< + unsafe extern "C" fn(pWal: *mut wal_impl, aWalData: *mut ::std::os::raw::c_uint), + >, } #[repr(C)] #[derive(Debug, Copy, Clone)] diff --git a/libsql-ffi/bundled/bindings/session_bindgen.rs b/libsql-ffi/bundled/bindings/session_bindgen.rs index 75cc5037e6..bb168af465 100644 --- a/libsql-ffi/bundled/bindings/session_bindgen.rs +++ b/libsql-ffi/bundled/bindings/session_bindgen.rs @@ -4044,6 +4044,9 @@ pub struct libsql_wal_methods { ) -> ::std::os::raw::c_int, >, pub xDb: ::std::option::Option, + pub xSavepointForget: ::std::option::Option< + unsafe extern "C" fn(pWal: *mut wal_impl, aWalData: *mut ::std::os::raw::c_uint), + >, } #[repr(C)] #[derive(Debug, Copy, Clone)] diff --git a/libsql-ffi/bundled/src/sqlite3.c b/libsql-ffi/bundled/src/sqlite3.c index c286d77b4f..62a917fb9b 100644 --- a/libsql-ffi/bundled/src/sqlite3.c +++ b/libsql-ffi/bundled/src/sqlite3.c @@ -14224,7 +14224,7 @@ typedef struct wal_impl wal_impl; typedef struct wal_manager_impl wal_manager_impl; typedef struct libsql_wal_methods { - int iVersion; /* Current version is 1, versioning is here for backward compatibility */ + int iVersion; /* Current version is 2, versioning is here for backward compatibility */ /* Set the limiting size of a WAL file. */ void (*xLimit)(wal_impl* pWal, long long limit); @@ -14326,6 +14326,9 @@ typedef struct libsql_wal_methods { int (*xWriteLock)(wal_impl* pWal, int bLock); void (*xDb)(wal_impl* pWal, sqlite3 *db); + + /* Forget a previously opened savepoint that SQLite no longer needs. */ + void (*xSavepointForget)(wal_impl* pWal, unsigned int *aWalData); } libsql_wal_methods; @@ -57825,7 +57828,7 @@ typedef struct wal_impl wal_impl; typedef struct wal_manager_impl wal_manager_impl; typedef struct libsql_wal_methods { - int iVersion; /* Current version is 1, versioning is here for backward compatibility */ + int iVersion; /* Current version is 2, versioning is here for backward compatibility */ /* Set the limiting size of a WAL file. */ void (*xLimit)(wal_impl* pWal, long long limit); @@ -57927,6 +57930,9 @@ typedef struct libsql_wal_methods { int (*xWriteLock)(wal_impl* pWal, int bLock); void (*xDb)(wal_impl* pWal, sqlite3 *db); + + /* Forget a previously opened savepoint that SQLite no longer needs. */ + void (*xSavepointForget)(wal_impl* pWal, unsigned int *aWalData); } libsql_wal_methods; @@ -65086,6 +65092,14 @@ SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ */ nNew = iSavepoint + (( op==SAVEPOINT_RELEASE ) ? 0 : 1); for(ii=nNew; iinSavepoint; ii++){ + if( pagerUseWal(pPager) + && pPager->wal->methods.iVersion>=2 + && pPager->wal->methods.xSavepointForget + ){ + pPager->wal->methods.xSavepointForget( + pPager->wal->pData, pPager->aSavepoint[ii].aWalData + ); + } sqlite3BitvecDestroy(pPager->aSavepoint[ii].pInSavepoint); } pPager->nSavepoint = nNew; @@ -69661,6 +69675,15 @@ static int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){ return rc; } +/* +** Forget a WAL savepoint context that SQLite has destroyed. The built-in WAL +** implementation does not retain any external state per savepoint. +*/ +static void sqlite3WalSavepointForget(Wal *pWal, u32 *aWalData){ + UNUSED_PARAMETER(pWal); + UNUSED_PARAMETER(aWalData); +} + /* ** This function is called just before writing a set of frames to the log ** file (see sqlite3WalFrames()). It checks to see if, instead of appending @@ -70614,7 +70637,7 @@ static int sqlite3WalOpen( } out->pData = (wal_impl*) pRet; - out->methods.iVersion = 1; + out->methods.iVersion = 2; out->methods.xLimit = (void (*)(wal_impl *, long long))sqlite3WalLimit; out->methods.xBeginReadTransaction = (int (*)(wal_impl *, int *))sqlite3WalBeginReadTransaction; out->methods.xEndReadTransaction = (void (*)(wal_impl *))sqlite3WalEndReadTransaction; @@ -70651,6 +70674,7 @@ static int sqlite3WalOpen( outWal->methods.xWriteLock = sqlite3WalWriteLock; #endif out->methods.xDb = (void (*)(wal_impl *, sqlite3 *))sqlite3WalDb; + out->methods.xSavepointForget = (void (*)(wal_impl *, unsigned int *))sqlite3WalSavepointForget; WALTRACE(("WAL%d: opened\n", pRet)); } diff --git a/libsql-ffi/bundled/src/sqlite3.h b/libsql-ffi/bundled/src/sqlite3.h index 77de0af6c3..997e7729d1 100644 --- a/libsql-ffi/bundled/src/sqlite3.h +++ b/libsql-ffi/bundled/src/sqlite3.h @@ -13829,7 +13829,7 @@ typedef struct wal_impl wal_impl; typedef struct wal_manager_impl wal_manager_impl; typedef struct libsql_wal_methods { - int iVersion; /* Current version is 1, versioning is here for backward compatibility */ + int iVersion; /* Current version is 2, versioning is here for backward compatibility */ /* Set the limiting size of a WAL file. */ void (*xLimit)(wal_impl* pWal, long long limit); @@ -13931,6 +13931,9 @@ typedef struct libsql_wal_methods { int (*xWriteLock)(wal_impl* pWal, int bLock); void (*xDb)(wal_impl* pWal, sqlite3 *db); + + /* Forget a previously opened savepoint that SQLite no longer needs. */ + void (*xSavepointForget)(wal_impl* pWal, unsigned int *aWalData); } libsql_wal_methods; diff --git a/libsql-sqlite3/src/pager.c b/libsql-sqlite3/src/pager.c index 429e4a9599..9fe7ebe8d7 100644 --- a/libsql-sqlite3/src/pager.c +++ b/libsql-sqlite3/src/pager.c @@ -7065,6 +7065,14 @@ int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ */ nNew = iSavepoint + (( op==SAVEPOINT_RELEASE ) ? 0 : 1); for(ii=nNew; iinSavepoint; ii++){ + if( pagerUseWal(pPager) + && pPager->wal->methods.iVersion>=2 + && pPager->wal->methods.xSavepointForget + ){ + pPager->wal->methods.xSavepointForget( + pPager->wal->pData, pPager->aSavepoint[ii].aWalData + ); + } sqlite3BitvecDestroy(pPager->aSavepoint[ii].pInSavepoint); } pPager->nSavepoint = nNew; diff --git a/libsql-sqlite3/src/wal.c b/libsql-sqlite3/src/wal.c index 0b8d764cf9..984f87c5f7 100644 --- a/libsql-sqlite3/src/wal.c +++ b/libsql-sqlite3/src/wal.c @@ -3609,6 +3609,15 @@ static int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){ return rc; } +/* +** Forget a WAL savepoint context that SQLite has destroyed. The built-in WAL +** implementation does not retain any external state per savepoint. +*/ +static void sqlite3WalSavepointForget(Wal *pWal, u32 *aWalData){ + UNUSED_PARAMETER(pWal); + UNUSED_PARAMETER(aWalData); +} + /* ** This function is called just before writing a set of frames to the log ** file (see sqlite3WalFrames()). It checks to see if, instead of appending @@ -4562,7 +4571,7 @@ static int sqlite3WalOpen( } out->pData = (wal_impl*) pRet; - out->methods.iVersion = 1; + out->methods.iVersion = 2; out->methods.xLimit = (void (*)(wal_impl *, long long))sqlite3WalLimit; out->methods.xBeginReadTransaction = (int (*)(wal_impl *, int *))sqlite3WalBeginReadTransaction; out->methods.xEndReadTransaction = (void (*)(wal_impl *))sqlite3WalEndReadTransaction; @@ -4599,6 +4608,7 @@ static int sqlite3WalOpen( outWal->methods.xWriteLock = sqlite3WalWriteLock; #endif out->methods.xDb = (void (*)(wal_impl *, sqlite3 *))sqlite3WalDb; + out->methods.xSavepointForget = (void (*)(wal_impl *, unsigned int *))sqlite3WalSavepointForget; WALTRACE(("WAL%d: opened\n", pRet)); } diff --git a/libsql-sqlite3/src/wal.h b/libsql-sqlite3/src/wal.h index 02e68e154a..4501d488c3 100644 --- a/libsql-sqlite3/src/wal.h +++ b/libsql-sqlite3/src/wal.h @@ -38,7 +38,7 @@ typedef struct wal_impl wal_impl; typedef struct wal_manager_impl wal_manager_impl; typedef struct libsql_wal_methods { - int iVersion; /* Current version is 1, versioning is here for backward compatibility */ + int iVersion; /* Current version is 2, versioning is here for backward compatibility */ /* Set the limiting size of a WAL file. */ void (*xLimit)(wal_impl* pWal, long long limit); @@ -140,6 +140,9 @@ typedef struct libsql_wal_methods { int (*xWriteLock)(wal_impl* pWal, int bLock); void (*xDb)(wal_impl* pWal, sqlite3 *db); + + /* Forget a previously opened savepoint that SQLite no longer needs. */ + void (*xSavepointForget)(wal_impl* pWal, unsigned int *aWalData); } libsql_wal_methods; diff --git a/libsql-sqlite3/test/rust_suite/src/virtual_wal.rs b/libsql-sqlite3/test/rust_suite/src/virtual_wal.rs index 2395a34b76..01ed79ab93 100644 --- a/libsql-sqlite3/test/rust_suite/src/virtual_wal.rs +++ b/libsql-sqlite3/test/rust_suite/src/virtual_wal.rs @@ -1,7 +1,9 @@ #![allow(improper_ctypes)] #[cfg(test)] mod tests { + use std::cell::RefCell; use std::num::NonZeroU32; + use std::rc::Rc; use libsql_sys::rusqlite::{Connection, OpenFlags}; use libsql_sys::wal::{ @@ -12,6 +14,33 @@ mod tests { /// A wal_manager the simple wraps sqlite3 WAL struct WrapWalManager { inner: Sqlite3WalManager, + recorder: SavepointRecorder, + } + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + enum SavepointEvent { + Savepoint, + Undo, + Forget, + } + + #[derive(Clone, Default)] + struct SavepointRecorder { + events: Rc>>, + } + + impl SavepointRecorder { + fn record(&self, event: SavepointEvent) { + self.events.borrow_mut().push(event); + } + + fn events(&self) -> Vec { + self.events.borrow().clone() + } + + fn clear(&self) { + self.events.borrow_mut().clear(); + } } impl WalManager for WrapWalManager { @@ -31,7 +60,10 @@ mod tests { ) -> libsql_sys::wal::Result { self.inner .open(vfs, file, no_shm_mode, max_log_size, db_path) - .map(WrapWal) + .map(|inner| WrapWal { + inner, + recorder: self.recorder.clone(), + }) } fn close( @@ -41,7 +73,7 @@ mod tests { sync_flags: std::ffi::c_int, scratch: Option<&mut [u8]>, ) -> libsql_sys::wal::Result<()> { - self.inner.close(&mut wal.0, db, sync_flags, scratch) + self.inner.close(&mut wal.inner, db, sync_flags, scratch) } fn destroy_log( @@ -68,26 +100,29 @@ mod tests { } } - struct WrapWal(Sqlite3Wal); + struct WrapWal { + inner: Sqlite3Wal, + recorder: SavepointRecorder, + } impl Wal for WrapWal { fn limit(&mut self, size: i64) { - self.0.limit(size) + self.inner.limit(size) } fn begin_read_txn(&mut self) -> libsql_sys::wal::Result { - self.0.begin_read_txn() + self.inner.begin_read_txn() } fn end_read_txn(&mut self) { - self.0.end_read_txn() + self.inner.end_read_txn() } fn find_frame( &mut self, page_no: NonZeroU32, ) -> libsql_sys::wal::Result> { - self.0.find_frame(page_no) + self.inner.find_frame(page_no) } fn read_frame( @@ -95,7 +130,7 @@ mod tests { frame_no: NonZeroU32, buffer: &mut [u8], ) -> libsql_sys::wal::Result<()> { - self.0.read_frame(frame_no, buffer) + self.inner.read_frame(frame_no, buffer) } fn read_frame_raw( @@ -103,38 +138,45 @@ mod tests { page_no: NonZeroU32, buffer: &mut [u8], ) -> libsql_sys::wal::Result<()> { - self.0.read_frame_raw(page_no, buffer) + self.inner.read_frame_raw(page_no, buffer) } fn frame_count(&self, locked: i32) -> libsql_sys::wal::Result { - self.0.frame_count(locked) + self.inner.frame_count(locked) } fn db_size(&self) -> u32 { - self.0.db_size() + self.inner.db_size() } fn begin_write_txn(&mut self) -> libsql_sys::wal::Result<()> { - self.0.begin_write_txn() + self.inner.begin_write_txn() } fn end_write_txn(&mut self) -> libsql_sys::wal::Result<()> { - self.0.end_write_txn() + self.inner.end_write_txn() } fn undo( &mut self, handler: Option<&mut U>, ) -> libsql_sys::wal::Result<()> { - self.0.undo(handler) + self.inner.undo(handler) } fn savepoint(&mut self, rollback_data: &mut [u32]) { - self.0.savepoint(rollback_data) + self.recorder.record(SavepointEvent::Savepoint); + self.inner.savepoint(rollback_data) } fn savepoint_undo(&mut self, rollback_data: &mut [u32]) -> libsql_sys::wal::Result<()> { - self.0.savepoint_undo(rollback_data) + self.recorder.record(SavepointEvent::Undo); + self.inner.savepoint_undo(rollback_data) + } + + fn savepoint_forget(&mut self, rollback_data: &mut [u32]) { + self.recorder.record(SavepointEvent::Forget); + self.inner.savepoint_forget(rollback_data) } fn insert_frames( @@ -145,7 +187,7 @@ mod tests { is_commit: bool, sync_flags: std::ffi::c_int, ) -> libsql_sys::wal::Result { - self.0 + self.inner .insert_frames(page_size, page_headers, size_after, is_commit, sync_flags) } @@ -161,7 +203,7 @@ mod tests { in_wal: Option<&mut i32>, backfilled: Option<&mut i32>, ) -> libsql_sys::wal::Result<()> { - self.0.checkpoint( + self.inner.checkpoint( db, mode, busy_handler, @@ -174,31 +216,55 @@ mod tests { } fn exclusive_mode(&mut self, op: std::ffi::c_int) -> libsql_sys::wal::Result<()> { - self.0.exclusive_mode(op) + self.inner.exclusive_mode(op) } fn uses_heap_memory(&self) -> bool { - self.0.uses_heap_memory() + self.inner.uses_heap_memory() } fn set_db(&mut self, db: &mut libsql_sys::wal::Sqlite3Db) { - self.0.set_db(db) + self.inner.set_db(db) } fn callback(&self) -> i32 { - self.0.callback() + self.inner.callback() } fn frames_in_wal(&self) -> u32 { - self.0.frames_in_wal() + self.inner.frames_in_wal() } } + fn open_recording_connection() -> (tempfile::NamedTempFile, Connection, SavepointRecorder) { + let tmpfile = tempfile::NamedTempFile::new().unwrap(); + let recorder = SavepointRecorder::default(); + let wal_manager = make_wal_manager(WrapWalManager { + inner: Sqlite3WalManager::new(), + recorder: recorder.clone(), + }); + let conn = + Connection::open_with_flags_and_wal(tmpfile.path(), OpenFlags::default(), wal_manager) + .unwrap(); + + conn.pragma(None, "journal_mode", "wal", |_| Ok(())) + .unwrap(); + conn.execute( + "CREATE TABLE t(id INTEGER PRIMARY KEY, payload BLOB NOT NULL)", + (), + ) + .unwrap(); + recorder.clear(); + + (tmpfile, conn, recorder) + } + #[test] fn test_vwal_register() { let tmpfile = tempfile::NamedTempFile::new().unwrap(); let wal_manager = make_wal_manager(WrapWalManager { inner: Sqlite3WalManager::new(), + recorder: SavepointRecorder::default(), }); let conn = Connection::open_with_flags_and_wal(tmpfile.path(), OpenFlags::default(), wal_manager) @@ -239,4 +305,84 @@ mod tests { assert_eq!(blob.len(), 8193); assert_eq!(forty_two, 42); } + + #[test] + fn successful_multiwrite_statement_forgets_statement_savepoint() { + let (_tmpfile, conn, recorder) = open_recording_connection(); + + conn.execute_batch( + "BEGIN; + INSERT INTO t(id, payload) + WITH RECURSIVE seq(value) AS ( + VALUES(1) + UNION ALL + SELECT value + 1 FROM seq WHERE value < 4 + ) + SELECT value, zeroblob(4096) FROM seq; + COMMIT;", + ) + .unwrap(); + + assert_eq!( + recorder.events(), + vec![SavepointEvent::Savepoint, SavepointEvent::Forget] + ); + } + + #[test] + fn release_forgets_nested_savepoints_in_stack_order() { + let (_tmpfile, conn, recorder) = open_recording_connection(); + + conn.execute_batch( + "BEGIN; + INSERT INTO t VALUES (1, zeroblob(4096)); + SAVEPOINT outer; + INSERT INTO t VALUES (2, zeroblob(4096)); + SAVEPOINT inner; + INSERT INTO t VALUES (3, zeroblob(4096)); + RELEASE inner; + RELEASE outer; + COMMIT;", + ) + .unwrap(); + + assert_eq!( + recorder.events(), + vec![ + SavepointEvent::Savepoint, + SavepointEvent::Savepoint, + SavepointEvent::Forget, + SavepointEvent::Forget, + ] + ); + } + + #[test] + fn rollback_to_forgets_nested_savepoints_before_undoing_target() { + let (_tmpfile, conn, recorder) = open_recording_connection(); + + conn.execute_batch( + "BEGIN; + INSERT INTO t VALUES (1, zeroblob(4096)); + SAVEPOINT outer; + INSERT INTO t VALUES (2, zeroblob(4096)); + SAVEPOINT inner; + INSERT INTO t VALUES (3, zeroblob(4096)); + ROLLBACK TO outer; + RELEASE outer; + COMMIT;", + ) + .unwrap(); + + assert_eq!( + recorder.events(), + vec![ + SavepointEvent::Savepoint, + SavepointEvent::Savepoint, + SavepointEvent::Forget, + SavepointEvent::Undo, + SavepointEvent::Forget, + ] + ); + } } diff --git a/libsql-sys/src/wal/either.rs b/libsql-sys/src/wal/either.rs index dbad73f861..c02d841e92 100644 --- a/libsql-sys/src/wal/either.rs +++ b/libsql-sys/src/wal/either.rs @@ -81,6 +81,12 @@ macro_rules! create_either { } } + fn savepoint_forget(&mut self, rollback_data: &mut [u32]) { + match self { + $( $name::$t(inner) => inner.savepoint_forget(rollback_data) ),* + } + } + fn frame_count(&self, locked: i32) -> super::Result { match self { $( $name::$t(inner) => inner.frame_count(locked) ),* diff --git a/libsql-sys/src/wal/ffi.rs b/libsql-sys/src/wal/ffi.rs index fd3044665a..04bb298bd4 100644 --- a/libsql-sys/src/wal/ffi.rs +++ b/libsql-sys/src/wal/ffi.rs @@ -17,7 +17,7 @@ use super::{PageHeaders, Sqlite3Db, Sqlite3File, Vfs, Wal, WalManager}; pub(crate) fn construct_libsql_wal(wal: *mut W) -> libsql_wal { libsql_wal { methods: libsql_wal_methods { - iVersion: 1, + iVersion: 2, xLimit: Some(limit::), xBeginReadTransaction: Some(begin_read_transaction::), xEndReadTransaction: Some(end_read_transaction::), @@ -45,6 +45,7 @@ pub(crate) fn construct_libsql_wal(wal: *mut W) -> libsql_wal { xFile: None, // TODO: not all wal are single file based xWriteLock: None, xDb: Some(db::), + xSavepointForget: Some(savepoint_forget::), }, pData: wal as *mut _, } @@ -295,6 +296,12 @@ pub unsafe extern "C" fn savepoint_undo(wal: *mut wal_impl, wal_data: *m } } +pub unsafe extern "C" fn savepoint_forget(wal: *mut wal_impl, wal_data: *mut u32) { + let this = &mut (*(wal as *mut T)); + let data = std::slice::from_raw_parts_mut(wal_data, WAL_SAVEPOINT_NDATA as usize); + this.savepoint_forget(data); +} + pub unsafe extern "C" fn frame_count( wal: *mut wal_impl, locked: i32, diff --git a/libsql-sys/src/wal/mod.rs b/libsql-sys/src/wal/mod.rs index 71e0c21ee3..11f9a5260f 100644 --- a/libsql-sys/src/wal/mod.rs +++ b/libsql-sys/src/wal/mod.rs @@ -198,6 +198,7 @@ pub trait Wal { fn savepoint(&mut self, rollback_data: &mut [u32]); fn savepoint_undo(&mut self, rollback_data: &mut [u32]) -> Result<()>; + fn savepoint_forget(&mut self, _rollback_data: &mut [u32]) {} fn frame_count(&self, locked: i32) -> Result; diff --git a/libsql-sys/src/wal/sqlite3_wal.rs b/libsql-sys/src/wal/sqlite3_wal.rs index 0764a79cd9..4b60ed324c 100644 --- a/libsql-sys/src/wal/sqlite3_wal.rs +++ b/libsql-sys/src/wal/sqlite3_wal.rs @@ -288,6 +288,17 @@ impl Wal for Sqlite3Wal { } } + fn savepoint_forget(&mut self, rollback_data: &mut [u32]) { + assert_eq!(rollback_data.len(), WAL_SAVEPOINT_NDATA as usize); + if self.inner.methods.iVersion >= 2 { + if let Some(savepoint_forget) = self.inner.methods.xSavepointForget { + unsafe { + savepoint_forget(self.inner.pData, rollback_data.as_mut_ptr()); + } + } + } + } + fn frame_count(&self, locked: i32) -> Result { let mut out: u32 = 0; let rc = unsafe { diff --git a/libsql-sys/src/wal/wrapper.rs b/libsql-sys/src/wal/wrapper.rs index 4f3fdb7159..7e841e52d2 100644 --- a/libsql-sys/src/wal/wrapper.rs +++ b/libsql-sys/src/wal/wrapper.rs @@ -89,6 +89,10 @@ impl, W: Wal> Wal for WalRef { unsafe { (*self.wrapper).savepoint_undo(&mut *self.wrapped, rollback_data) } } + fn savepoint_forget(&mut self, rollback_data: &mut [u32]) { + unsafe { (*self.wrapper).savepoint_forget(&mut *self.wrapped, rollback_data) } + } + fn frame_count(&self, locked: i32) -> super::Result { unsafe { (*self.wrapper).frame_count(&*self.wrapped, locked) } } @@ -272,6 +276,11 @@ where .savepoint_undo(&mut self.wrapped, rollback_data) } + fn savepoint_forget(&mut self, rollback_data: &mut [u32]) { + self.wrapper + .savepoint_forget(&mut self.wrapped, rollback_data) + } + fn frame_count(&self, locked: i32) -> super::Result { self.wrapper.frame_count(&self.wrapped, locked) } @@ -409,6 +418,10 @@ pub trait WrapWal { wrapped.savepoint_undo(rollback_data) } + fn savepoint_forget(&mut self, wrapped: &mut W, rollback_data: &mut [u32]) { + wrapped.savepoint_forget(rollback_data) + } + fn frame_count(&self, wrapped: &W, locked: i32) -> super::Result { wrapped.frame_count(locked) } @@ -657,6 +670,14 @@ where self.0.savepoint_undo(&mut r, rollback_data) } + fn savepoint_forget(&mut self, wrapped: &mut W, rollback_data: &mut [u32]) { + let mut r = WalRef { + wrapper: &mut self.1, + wrapped, + }; + self.0.savepoint_forget(&mut r, rollback_data) + } + fn insert_frames( &mut self, wrapped: &mut W, @@ -879,6 +900,13 @@ impl, W: Wal> WrapWal for Option { } } + fn savepoint_forget(&mut self, wrapped: &mut W, rollback_data: &mut [u32]) { + match self { + Some(t) => t.savepoint_forget(wrapped, rollback_data), + None => wrapped.savepoint_forget(rollback_data), + } + } + fn insert_frames( &mut self, wrapped: &mut W,