diff --git a/sqlite3/src/lib.rs b/sqlite3/src/lib.rs index 5fcbfbd43..0d6e4d36b 100644 --- a/sqlite3/src/lib.rs +++ b/sqlite3/src/lib.rs @@ -561,8 +561,15 @@ pub unsafe extern "C" fn sqlite3_column_name( } #[no_mangle] -pub unsafe extern "C" fn sqlite3_column_int64(_stmt: *mut sqlite3_stmt, _idx: ffi::c_int) -> i64 { - stub!(); +pub unsafe extern "C" fn sqlite3_column_int64(stmt: *mut sqlite3_stmt, idx: ffi::c_int) -> i64 { + // Attempt to convert idx to usize + let idx = idx.try_into().unwrap(); + let stmt = &mut *stmt; + let row = stmt + .stmt + .row() + .expect("Function should only be called after `SQLITE_ROW`"); + row.get(idx).unwrap() } #[no_mangle] @@ -1188,6 +1195,13 @@ pub unsafe extern "C" fn libsql_wal_get_frame( } } +/// Disable WAL checkpointing. +/// +/// Note: This function disables WAL checkpointing entirely, including when +/// the last database connection is closed. This is different from +/// sqlite3_wal_autocheckpoint() which only disables automatic checkpoints +/// for the current connection, but still allows checkpointing when the +/// connection is closed. #[no_mangle] pub unsafe extern "C" fn libsql_wal_disable_checkpoint(db: *mut sqlite3) -> ffi::c_int { if db.is_null() { diff --git a/sqlite3/tests/compat/mod.rs b/sqlite3/tests/compat/mod.rs index e97bf7dad..10691071f 100644 --- a/sqlite3/tests/compat/mod.rs +++ b/sqlite3/tests/compat/mod.rs @@ -37,6 +37,7 @@ extern "C" { log_size: *mut i32, checkpoint_count: *mut i32, ) -> i32; + fn sqlite3_column_int64(stmt: *mut sqlite3_stmt, idx: i32) -> i64; fn libsql_wal_frame_count(db: *mut sqlite3, p_frame_count: *mut u32) -> i32; fn libsql_wal_get_frame( db: *mut sqlite3, @@ -44,10 +45,12 @@ extern "C" { p_frame: *mut u8, frame_len: u32, ) -> i32; + fn libsql_wal_disable_checkpoint(db: *mut sqlite3) -> i32; } const SQLITE_OK: i32 = 0; const SQLITE_CANTOPEN: i32 = 14; +const SQLITE_ROW: i32 = 100; const SQLITE_DONE: i32 = 101; const SQLITE_CHECKPOINT_PASSIVE: i32 = 0; @@ -301,5 +304,108 @@ mod tests { assert_eq!(sqlite3_close(db), SQLITE_OK); } } + + #[test] + fn test_disable_wal_checkpoint() { + let temp_file = tempfile::NamedTempFile::with_suffix(".db").unwrap(); + unsafe { + let mut db = ptr::null_mut(); + let path = temp_file.path(); + let c_path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); + assert_eq!(sqlite3_open(c_path.as_ptr(), &mut db), SQLITE_OK); + // Create a table and insert a row. + let mut stmt = ptr::null_mut(); + assert_eq!( + sqlite3_prepare_v2( + db, + c"CREATE TABLE test (id INTEGER PRIMARY KEY)".as_ptr(), + -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, + c"INSERT INTO test (id) VALUES (0)".as_ptr(), + -1, + &mut stmt, + ptr::null_mut() + ), + SQLITE_OK + ); + assert_eq!(sqlite3_step(stmt), SQLITE_DONE); + assert_eq!(sqlite3_finalize(stmt), SQLITE_OK); + + let mut log_size = 0; + let mut checkpoint_count = 0; + + assert_eq!( + sqlite3_wal_checkpoint_v2( + db, + ptr::null(), + SQLITE_CHECKPOINT_PASSIVE, + &mut log_size, + &mut checkpoint_count + ), + SQLITE_OK + ); + } + let mut wal_path = temp_file.path().to_path_buf(); + assert!(wal_path.set_extension("db-wal")); + std::fs::remove_file(wal_path.clone()).unwrap(); + + { + let mut db = ptr::null_mut(); + unsafe { + let path = temp_file.path(); + let c_path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); + assert_eq!(sqlite3_open(c_path.as_ptr(), &mut db), SQLITE_OK); + assert_eq!(libsql_wal_disable_checkpoint(db), SQLITE_OK); + // Insert at least 1000 rows to go over checkpoint threshold. + let mut stmt = ptr::null_mut(); + for i in 1..2000 { + let sql = + std::ffi::CString::new(format!("INSERT INTO test (id) VALUES ({})", i)) + .unwrap(); + assert_eq!( + sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut()), + SQLITE_OK + ); + assert_eq!(sqlite3_step(stmt), SQLITE_DONE); + assert_eq!(sqlite3_finalize(stmt), SQLITE_OK); + } + } + } + + std::fs::remove_file(wal_path).unwrap(); + let mut db = ptr::null_mut(); + unsafe { + let path = temp_file.path(); + let c_path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); + assert_eq!(sqlite3_open(c_path.as_ptr(), &mut db), SQLITE_OK); + // Insert at least 1000 rows to go over checkpoint threshold. + let mut stmt = ptr::null_mut(); + assert_eq!( + sqlite3_prepare_v2( + db, + c"SELECT count() FROM test".as_ptr(), + -1, + &mut stmt, + ptr::null_mut() + ), + SQLITE_OK + ); + assert_eq!(sqlite3_step(stmt), SQLITE_ROW); + let count = sqlite3_column_int64(stmt, 0); + assert_eq!(count, 1); + assert_eq!(sqlite3_step(stmt), SQLITE_DONE); + assert_eq!(sqlite3_finalize(stmt), SQLITE_OK); + } + } } }