diff --git a/core/lib.rs b/core/lib.rs index a6d6a020b..d457fa72a 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -486,6 +486,10 @@ impl Connection { Ok(()) } + pub fn wal_frame_count(&self) -> Result { + self.pager.wal_frame_count() + } + pub fn cacheflush(&self) -> Result { self.pager.cacheflush() } diff --git a/core/storage/pager.rs b/core/storage/pager.rs index 36a0936a3..6dbd1c961 100644 --- a/core/storage/pager.rs +++ b/core/storage/pager.rs @@ -398,6 +398,15 @@ impl Pager { dirty_pages.insert(page_id); } + pub fn wal_frame_count(&self) -> Result { + let mut frame_count = 0; + let wal = self.wal.clone(); + if let Some(wal) = &wal { + frame_count = wal.borrow().get_max_frame_in_wal(); + } + Ok(frame_count) + } + pub fn cacheflush(&self) -> Result { let mut checkpoint_result = CheckpointResult::default(); loop { diff --git a/docs/manual.md b/docs/manual.md new file mode 100644 index 000000000..39cc095f0 --- /dev/null +++ b/docs/manual.md @@ -0,0 +1,34 @@ +# Limbo Documentation + +## SQLite C API + +### WAL manipulation + +#### `libsql_wal_frame_count` + +Get the number of frames in the WAL. + +**Synopsis:** + +```c +int libsql_wal_frame_count(sqlite3 *db, uint32_t *p_frame_count); +``` + +**Description:** + +The `libsql_wal_frame_count` function returns the number of frames in the WAL +in the `p_frame_count` parameter. + +**Return Values:** + +* `SQLITE_OK` if the number of frames in the WAL file is successfully returned. +* `SQLITE_MISUSE` if the `db` is NULL. +* SQLITE_ERROR if an error occurs while getting the number of frames in the WAL + file. + +**Safety Requirements:** + +* The `db` parameter must be a valid pointer to a `sqlite3` database + connection. +* The `p_frame_count` must be a valid pointer to a `u32` that will store the +* number of frames in the WAL file. diff --git a/sqlite3/include/sqlite3.h b/sqlite3/include/sqlite3.h index 9db1a9b04..01703842a 100644 --- a/sqlite3/include/sqlite3.h +++ b/sqlite3/include/sqlite3.h @@ -266,6 +266,28 @@ int sqlite3_wal_checkpoint(sqlite3 *_db, const char *_db_name); int sqlite3_wal_checkpoint_v2(sqlite3 *db, const char *_db_name, int _mode, int *_log_size, int *_checkpoint_count); +/** + * Get the number of frames in the WAL. + * + * The `libsql_wal_frame_count` function returns the number of frames + * in the WAL in the `p_frame_count` parameter. + * + * # Returns + * + * - `SQLITE_OK` if the number of frames in the WAL file is + * successfully returned. + * - `SQLITE_MISUSE` if the `db` is `NULL`. + * - `SQLITE_ERROR` if an error occurs while getting the number of frames + * in the WAL file. + * + * # Safety + * + * - The `db` must be a valid pointer to a `sqlite3` database connection. + * - The `p_frame_count` must be a valid pointer to a `u32` that will store + * the number of frames in the WAL file. + */ +int libsql_wal_frame_count(sqlite3 *db, uint32_t *p_frame_count); + #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/sqlite3/src/lib.rs b/sqlite3/src/lib.rs index f2324a8fb..86443fb40 100644 --- a/sqlite3/src/lib.rs +++ b/sqlite3/src/lib.rs @@ -40,6 +40,7 @@ pub mod util; use util::sqlite3_safety_check_sick_or_ok; pub struct sqlite3 { + pub(crate) io: Arc, pub(crate) _db: Arc, pub(crate) conn: Rc, pub(crate) err_code: ffi::c_int, @@ -50,8 +51,13 @@ pub struct sqlite3 { } impl sqlite3 { - pub fn new(db: Arc, conn: Rc) -> Self { + pub fn new( + io: Arc, + db: Arc, + conn: Rc, + ) -> Self { Self { + io, _db: db, conn, err_code: SQLITE_OK, @@ -64,12 +70,13 @@ impl sqlite3 { } pub struct sqlite3_stmt { + pub(crate) db: *mut sqlite3, pub(crate) stmt: limbo_core::Statement, } impl sqlite3_stmt { - pub fn new(stmt: limbo_core::Statement) -> Self { - Self { stmt } + pub fn new(db: *mut sqlite3, stmt: limbo_core::Statement) -> Self { + Self { db, stmt } } } @@ -117,10 +124,10 @@ pub unsafe extern "C" fn sqlite3_open( Err(_) => return SQLITE_CANTOPEN, }, }; - match limbo_core::Database::open_file(io, filename, false) { + match limbo_core::Database::open_file(io.clone(), filename, false) { Ok(db) => { let conn = db.connect().unwrap(); - *db_out = Box::leak(Box::new(sqlite3::new(db, conn))); + *db_out = Box::leak(Box::new(sqlite3::new(io, db, conn))); SQLITE_OK } Err(e) => { @@ -219,7 +226,7 @@ pub unsafe extern "C" fn sqlite3_prepare_v2( Ok(stmt) => stmt, Err(_) => return SQLITE_ERROR, }; - *out_stmt = Box::leak(Box::new(sqlite3_stmt::new(stmt))); + *out_stmt = Box::leak(Box::new(sqlite3_stmt::new(db, stmt))); SQLITE_OK } @@ -235,16 +242,23 @@ pub unsafe extern "C" fn sqlite3_finalize(stmt: *mut sqlite3_stmt) -> ffi::c_int #[no_mangle] pub unsafe extern "C" fn sqlite3_step(stmt: *mut sqlite3_stmt) -> ffi::c_int { let stmt = &mut *stmt; - if let Ok(result) = stmt.stmt.step() { - match result { - limbo_core::StepResult::IO => SQLITE_BUSY, - limbo_core::StepResult::Done => SQLITE_DONE, - limbo_core::StepResult::Interrupt => SQLITE_INTERRUPT, - limbo_core::StepResult::Row => SQLITE_ROW, - limbo_core::StepResult::Busy => SQLITE_BUSY, + let db = &mut *stmt.db; + loop { + if let Ok(result) = stmt.stmt.step() { + match result { + limbo_core::StepResult::IO => { + let io = db.io.clone(); + io.run_once().unwrap(); + continue; + } + limbo_core::StepResult::Done => return SQLITE_DONE, + limbo_core::StepResult::Interrupt => return SQLITE_INTERRUPT, + limbo_core::StepResult::Row => return SQLITE_ROW, + limbo_core::StepResult::Busy => return SQLITE_BUSY, + } + } else { + return SQLITE_ERROR; } - } else { - SQLITE_ERROR } } @@ -1083,3 +1097,38 @@ pub unsafe extern "C" fn sqlite3_wal_checkpoint_v2( } SQLITE_OK } + +/// Get the number of frames in the WAL. +/// +/// The `libsql_wal_frame_count` function returns the number of frames +/// in the WAL in the `p_frame_count` parameter. +/// +/// # Returns +/// +/// - `SQLITE_OK` if the number of frames in the WAL file is +/// successfully returned. +/// - `SQLITE_MISUSE` if the `db` is `NULL`. +/// - `SQLITE_ERROR` if an error occurs while getting the number of frames +/// in the WAL file. +/// +/// # Safety +/// +/// - The `db` must be a valid pointer to a `sqlite3` database connection. +/// - The `p_frame_count` must be a valid pointer to a `u32` that will store +/// the number of frames in the WAL file. +#[no_mangle] +pub unsafe extern "C" fn libsql_wal_frame_count( + db: *mut sqlite3, + p_frame_count: *mut u32, +) -> ffi::c_int { + if db.is_null() { + return SQLITE_MISUSE; + } + let db: &mut sqlite3 = &mut *db; + let frame_count = match db.conn.wal_frame_count() { + Ok(count) => count as u32, + Err(_) => return SQLITE_ERROR, + }; + *p_frame_count = frame_count; + SQLITE_OK +} diff --git a/sqlite3/tests/compat/mod.rs b/sqlite3/tests/compat/mod.rs index 1166e4a15..6dba7edba 100644 --- a/sqlite3/tests/compat/mod.rs +++ b/sqlite3/tests/compat/mod.rs @@ -1,4 +1,5 @@ #![allow(non_camel_case_types)] +#![allow(dead_code)] use std::ptr; @@ -26,6 +27,7 @@ extern "C" { stmt: *mut *mut sqlite3_stmt, tail: *mut *const libc::c_char, ) -> i32; + fn sqlite3_step(stmt: *mut sqlite3_stmt) -> i32; fn sqlite3_finalize(stmt: *mut sqlite3_stmt) -> i32; fn sqlite3_wal_checkpoint(db: *mut sqlite3, db_name: *const libc::c_char) -> i32; fn sqlite3_wal_checkpoint_v2( @@ -35,10 +37,13 @@ extern "C" { log_size: *mut i32, checkpoint_count: *mut i32, ) -> i32; + fn libsql_wal_frame_count(db: *mut sqlite3, p_frame_count: *mut u32) -> i32; } const SQLITE_OK: i32 = 0; const SQLITE_CANTOPEN: i32 = 14; +const SQLITE_DONE: i32 = 101; + const SQLITE_CHECKPOINT_PASSIVE: i32 = 0; const SQLITE_CHECKPOINT_FULL: i32 = 1; const SQLITE_CHECKPOINT_RESTART: i32 = 2; @@ -195,4 +200,55 @@ mod tests { assert_eq!(sqlite3_close(db), SQLITE_OK); } } + + #[cfg(not(feature = "sqlite3"))] + mod libsql_ext { + use super::*; + + #[test] + fn test_wal_frame_count() { + unsafe { + let mut db = ptr::null_mut(); + assert_eq!( + sqlite3_open(b"../testing/testing.db\0".as_ptr() as *const i8, &mut db), + SQLITE_OK + ); + // Ensure that WAL is initially empty. + let mut frame_count = 0; + assert_eq!(libsql_wal_frame_count(db, &mut frame_count), SQLITE_OK); + assert_eq!(frame_count, 0); + // Create a table and insert a row. + let mut stmt = ptr::null_mut(); + assert_eq!( + sqlite3_prepare_v2( + db, + b"CREATE TABLE test (id INTEGER PRIMARY KEY)\0".as_ptr() as *const i8, + -1, + &mut stmt, + ptr::null_mut() + ), + SQLITE_OK + ); + assert_eq!(sqlite3_step(stmt), SQLITE_DONE); + assert_eq!(sqlite3_finalize(stmt), SQLITE_OK); + let mut stmt = ptr::null_mut(); + assert_eq!( + sqlite3_prepare_v2( + db, + b"INSERT INTO test (id) VALUES (1)\0".as_ptr() as *const i8, + -1, + &mut stmt, + ptr::null_mut() + ), + SQLITE_OK + ); + assert_eq!(sqlite3_step(stmt), SQLITE_DONE); + assert_eq!(sqlite3_finalize(stmt), SQLITE_OK); + // Check that WAL has three frames. + assert_eq!(libsql_wal_frame_count(db, &mut frame_count), SQLITE_OK); + assert_eq!(frame_count, 3); + assert_eq!(sqlite3_close(db), SQLITE_OK); + } + } + } }