diff --git a/core/lib.rs b/core/lib.rs index a6d6a020..d457fa72 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 36a0936a..6dbd1c96 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 00000000..39cc095f --- /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 9db1a9b0..01703842 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 032e39b8..86443fb4 100644 --- a/sqlite3/src/lib.rs +++ b/sqlite3/src/lib.rs @@ -1097,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 1166e4a1..6dba7edb 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); + } + } + } }