add option to disable wal checkpoint

This commit is contained in:
pedrocarlo 2025-07-01 18:07:11 -03:00
parent 9e92325bad
commit db005c81a0
6 changed files with 44 additions and 17 deletions

View file

@ -118,8 +118,8 @@ pub struct Database {
maybe_shared_wal: RwLock<Option<Arc<UnsafeCell<WalFileShared>>>>,
is_empty: Arc<AtomicUsize>,
init_lock: Arc<Mutex<()>>,
open_flags: OpenFlags,
wal_checkpoint_disabled: Cell<bool>,
}
unsafe impl Send for Database {}
@ -211,6 +211,7 @@ impl Database {
open_flags: flags,
is_empty: Arc::new(AtomicUsize::new(is_empty)),
init_lock: Arc::new(Mutex::new(())),
wal_checkpoint_disabled: Cell::new(false),
};
let db = Arc::new(db);
@ -674,7 +675,8 @@ impl Connection {
/// If the WAL size is over the checkpoint threshold, it will checkpoint the WAL to
/// the database file and then fsync the database file.
pub fn cacheflush(&self) -> Result<PagerCacheflushStatus> {
self.pager.cacheflush()
self.pager
.cacheflush(self._db.wal_checkpoint_disabled.get())
}
pub fn clear_page_cache(&self) -> Result<()> {
@ -683,12 +685,18 @@ impl Connection {
}
pub fn checkpoint(&self) -> Result<CheckpointResult> {
self.pager.wal_checkpoint()
self.pager
.wal_checkpoint(self._db.wal_checkpoint_disabled.get())
}
/// Close a connection and checkpoint.
pub fn close(&self) -> Result<()> {
self.pager.checkpoint_shutdown()
self.pager
.checkpoint_shutdown(self._db.wal_checkpoint_disabled.get())
}
pub fn wal_disable_checkpoint(&self) {
self._db.wal_checkpoint_disabled.set(true);
}
pub fn last_insert_rowid(&self) -> i64 {

View file

@ -7057,7 +7057,7 @@ mod tests {
)
.unwrap();
loop {
match pager.end_tx(false, false, &conn).unwrap() {
match pager.end_tx(false, false, &conn, false).unwrap() {
crate::PagerCacheflushStatus::Done(_) => break,
crate::PagerCacheflushStatus::IO => {
pager.io.run_once().unwrap();
@ -7183,7 +7183,7 @@ mod tests {
.unwrap();
cursor.move_to_root();
loop {
match pager.end_tx(false, false, &conn).unwrap() {
match pager.end_tx(false, false, &conn, false).unwrap() {
crate::PagerCacheflushStatus::Done(_) => break,
crate::PagerCacheflushStatus::IO => {
pager.io.run_once().unwrap();

View file

@ -636,6 +636,7 @@ impl Pager {
rollback: bool,
change_schema: bool,
connection: &Connection,
wal_checkpoint_disabled: bool,
) -> Result<PagerCacheflushStatus> {
tracing::trace!("end_tx(rollback={})", rollback);
if rollback {
@ -643,7 +644,7 @@ impl Pager {
self.wal.borrow().end_read_tx()?;
return Ok(PagerCacheflushStatus::Done(PagerCacheflushResult::Rollback));
}
let cacheflush_status = self.cacheflush()?;
let cacheflush_status = self.cacheflush(wal_checkpoint_disabled)?;
match cacheflush_status {
PagerCacheflushStatus::IO => Ok(PagerCacheflushStatus::IO),
PagerCacheflushStatus::Done(_) => {
@ -758,7 +759,7 @@ impl Pager {
/// In the base case, it will write the dirty pages to the WAL and then fsync the WAL.
/// If the WAL size is over the checkpoint threshold, it will checkpoint the WAL to
/// the database file and then fsync the database file.
pub fn cacheflush(&self) -> Result<PagerCacheflushStatus> {
pub fn cacheflush(&self, wal_checkpoint_disabled: bool) -> Result<PagerCacheflushStatus> {
let mut checkpoint_result = CheckpointResult::default();
loop {
let state = self.flush_info.borrow().state;
@ -804,7 +805,7 @@ impl Pager {
return Ok(PagerCacheflushStatus::IO);
}
if !self.wal.borrow().should_checkpoint() {
if wal_checkpoint_disabled || !self.wal.borrow().should_checkpoint() {
self.flush_info.borrow_mut().state = FlushState::Start;
return Ok(PagerCacheflushStatus::Done(
PagerCacheflushResult::WalWritten,
@ -912,7 +913,7 @@ impl Pager {
.expect("Failed to clear page cache");
}
pub fn checkpoint_shutdown(&self) -> Result<()> {
pub fn checkpoint_shutdown(&self, wal_checkpoint_disabled: bool) -> Result<()> {
let mut attempts = 0;
{
let mut wal = self.wal.borrow_mut();
@ -927,11 +928,17 @@ impl Pager {
attempts += 1;
}
}
self.wal_checkpoint()?;
self.wal_checkpoint(wal_checkpoint_disabled)?;
Ok(())
}
pub fn wal_checkpoint(&self) -> Result<CheckpointResult> {
pub fn wal_checkpoint(&self, wal_checkpoint_disabled: bool) -> Result<CheckpointResult> {
if wal_checkpoint_disabled {
return Ok(CheckpointResult {
num_wal_frames: 0,
num_checkpointed_frames: 0,
});
}
let checkpoint_result: CheckpointResult;
loop {
match self.wal.borrow_mut().checkpoint(

View file

@ -469,7 +469,8 @@ impl Program {
rollback: bool,
change_schema: bool,
) -> Result<StepResult> {
let cacheflush_status = pager.end_tx(rollback, change_schema, connection)?;
let cacheflush_status =
pager.end_tx(rollback, change_schema, connection, connection._db.wal_checkpoint_disabled.get())?;
match cacheflush_status {
PagerCacheflushStatus::Done(_) => {
if self.change_cnt_on {

View file

@ -1,14 +1,14 @@
# SQLite3 Implementation for Limbo
# SQLite3 Implementation for Turso
This directory contains a Rust implementation of the SQLite3 C API. The implementation serves as a compatibility layer between SQLite's C API and Limbo's native Rust database implementation.
This directory contains a Rust implementation of the SQLite3 C API. The implementation serves as a compatibility layer between SQLite's C API and Turso's native Rust database implementation.
## Purpose
This implementation provides SQLite3 API compatibility for Limbo, allowing existing applications that use SQLite to work with Limbo without modification. The code:
This implementation provides SQLite3 API compatibility for Turso, allowing existing applications that use SQLite to work with Turso without modification. The code:
1. Implements the SQLite3 C API functions in Rust
2. Translates between C and Rust data structures
3. Maps SQLite operations to equivalent Limbo operations
3. Maps SQLite operations to equivalent Turso operations
4. Maintains API compatibility with SQLite version 3.42.0
## Testing Strategy

View file

@ -1188,6 +1188,17 @@ pub unsafe extern "C" fn libsql_wal_get_frame(
}
}
#[no_mangle]
pub unsafe extern "C" fn libsql_wal_disable_checkpoint(db: *mut sqlite3) -> ffi::c_int {
if db.is_null() {
return SQLITE_MISUSE;
}
let db: &mut sqlite3 = &mut *db;
let db = db.inner.lock().unwrap();
db.conn.wal_disable_checkpoint();
SQLITE_OK
}
fn sqlite3_safety_check_sick_or_ok(db: &sqlite3Inner) -> bool {
match db.e_open_state {
SQLITE_STATE_SICK | SQLITE_STATE_OPEN | SQLITE_STATE_BUSY => true,