Merge 'Add libsql_wal_frame_count() API' from Pekka Enberg

Reviewed-by: Pere Diaz Bou <pere-altea@homail.com>

Closes #1489
This commit is contained in:
Pekka Enberg
2025-05-15 12:01:45 +03:00
6 changed files with 189 additions and 15 deletions

View File

@@ -486,6 +486,10 @@ impl Connection {
Ok(())
}
pub fn wal_frame_count(&self) -> Result<u64> {
self.pager.wal_frame_count()
}
pub fn cacheflush(&self) -> Result<CheckpointStatus> {
self.pager.cacheflush()
}

View File

@@ -398,6 +398,15 @@ impl Pager {
dirty_pages.insert(page_id);
}
pub fn wal_frame_count(&self) -> Result<u64> {
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<CheckpointStatus> {
let mut checkpoint_result = CheckpointResult::default();
loop {

34
docs/manual.md Normal file
View File

@@ -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.

View File

@@ -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

View File

@@ -40,6 +40,7 @@ pub mod util;
use util::sqlite3_safety_check_sick_or_ok;
pub struct sqlite3 {
pub(crate) io: Arc<dyn limbo_core::IO>,
pub(crate) _db: Arc<limbo_core::Database>,
pub(crate) conn: Rc<limbo_core::Connection>,
pub(crate) err_code: ffi::c_int,
@@ -50,8 +51,13 @@ pub struct sqlite3 {
}
impl sqlite3 {
pub fn new(db: Arc<limbo_core::Database>, conn: Rc<limbo_core::Connection>) -> Self {
pub fn new(
io: Arc<dyn limbo_core::IO>,
db: Arc<limbo_core::Database>,
conn: Rc<limbo_core::Connection>,
) -> 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
}

View File

@@ -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);
}
}
}
}