From f450524ddb4154b74424655b9be951bcc2baa9cb Mon Sep 17 00:00:00 2001 From: danawan Date: Sun, 10 Aug 2025 18:20:52 +0700 Subject: [PATCH 1/2] sqlite3 api add bind_text and bind_blob --- sqlite3/include/sqlite3.h | 4 + sqlite3/src/lib.rs | 166 ++++++++++++++++++--- sqlite3/tests/compat/mod.rs | 267 ++++++++++++++++++++++++++++++++++ sqlite3/tests/sqlite3_tests.c | 177 +++++++++++++++++++++- 4 files changed, 592 insertions(+), 22 deletions(-) diff --git a/sqlite3/include/sqlite3.h b/sqlite3/include/sqlite3.h index 6e3496cf8..56f3e1896 100644 --- a/sqlite3/include/sqlite3.h +++ b/sqlite3/include/sqlite3.h @@ -41,6 +41,10 @@ #define SQLITE_CHECKPOINT_TRUNCATE 3 +typedef void (*sqlite3_destructor_type)(void*); +#define SQLITE_STATIC ((sqlite3_destructor_type)0) +#define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1) + typedef struct sqlite3 sqlite3; typedef struct sqlite3_stmt sqlite3_stmt; diff --git a/sqlite3/src/lib.rs b/sqlite3/src/lib.rs index df96bb53b..2446c3778 100644 --- a/sqlite3/src/lib.rs +++ b/sqlite3/src/lib.rs @@ -2,7 +2,7 @@ #![allow(non_camel_case_types)] use std::ffi::{self, CStr, CString}; -use std::num::NonZero; +use std::num::{NonZero, NonZeroUsize}; use tracing::trace; use turso_core::{CheckpointMode, LimboError, Value}; @@ -76,11 +76,20 @@ impl sqlite3 { pub struct sqlite3_stmt { pub(crate) db: *mut sqlite3, pub(crate) stmt: turso_core::Statement, + pub(crate) destructors: Vec<( + usize, + Option, + *mut ffi::c_void, + )>, } impl sqlite3_stmt { pub fn new(db: *mut sqlite3, stmt: turso_core::Statement) -> Self { - Self { db, stmt } + Self { + db, + stmt, + destructors: Vec::new(), + } } } @@ -246,6 +255,14 @@ pub unsafe extern "C" fn sqlite3_finalize(stmt: *mut sqlite3_stmt) -> ffi::c_int if stmt.is_null() { return SQLITE_MISUSE; } + let stmt_ref = &mut *stmt; + + for (_idx, destructor_opt, ptr) in stmt_ref.destructors.drain(..) { + if let Some(destructor_fn) = destructor_opt { + destructor_fn(ptr); + } + } + let _ = Box::from_raw(stmt); SQLITE_OK } @@ -573,24 +590,109 @@ pub unsafe extern "C" fn sqlite3_bind_double( #[no_mangle] pub unsafe extern "C" fn sqlite3_bind_text( - _stmt: *mut sqlite3_stmt, - _idx: ffi::c_int, - _text: *const ffi::c_char, - _len: ffi::c_int, - _destroy: *mut ffi::c_void, + stmt: *mut sqlite3_stmt, + idx: ffi::c_int, + text: *const ffi::c_char, + len: ffi::c_int, + destructor: Option, ) -> ffi::c_int { - stub!(); + if stmt.is_null() { + return SQLITE_MISUSE; + } + if idx <= 0 { + return SQLITE_RANGE; + } + if text.is_null() { + return sqlite3_bind_null(stmt, idx); + } + + let stmt_ref = &mut *stmt; + + let static_ptr = std::ptr::null(); + let transient_ptr = -1isize as usize as *const ffi::c_void; + let ptr_val = destructor + .map(|f| f as *const ffi::c_void) + .unwrap_or(static_ptr); + + let str_value = if len < 0 { + match CStr::from_ptr(text).to_str() { + Ok(s) => s.to_owned(), + Err(_) => return SQLITE_ERROR, + } + } else { + let slice = std::slice::from_raw_parts(text as *const u8, len as usize); + match std::str::from_utf8(slice) { + Ok(s) => s.to_owned(), + Err(_) => return SQLITE_ERROR, + } + }; + + if ptr_val == transient_ptr { + let val = Value::from_text(&str_value); + stmt_ref + .stmt + .bind_at(NonZero::new_unchecked(idx as usize), val); + } else if ptr_val == static_ptr { + let slice = std::slice::from_raw_parts(text as *const u8, str_value.len()); + let val = Value::from_text(std::str::from_utf8(slice).unwrap()); + stmt_ref + .stmt + .bind_at(NonZero::new_unchecked(idx as usize), val); + } else { + let slice = std::slice::from_raw_parts(text as *const u8, str_value.len()); + let val = Value::from_text(std::str::from_utf8(slice).unwrap()); + stmt_ref + .stmt + .bind_at(NonZero::new_unchecked(idx as usize), val); + + stmt_ref + .destructors + .push((idx as usize, destructor, text as *mut ffi::c_void)); + } + + SQLITE_OK } #[no_mangle] pub unsafe extern "C" fn sqlite3_bind_blob( - _stmt: *mut sqlite3_stmt, - _idx: ffi::c_int, - _blob: *const ffi::c_void, - _len: ffi::c_int, - _destroy: *mut ffi::c_void, + stmt: *mut sqlite3_stmt, + idx: ffi::c_int, + blob: *const ffi::c_void, + len: ffi::c_int, + destructor: Option, ) -> ffi::c_int { - stub!(); + if stmt.is_null() { + return SQLITE_MISUSE; + } + if idx <= 0 { + return SQLITE_RANGE; + } + if blob.is_null() { + return sqlite3_bind_null(stmt, idx); + } + + let slice_blob = std::slice::from_raw_parts(blob as *const u8, len as usize).to_vec(); + + let stmt_ref = &mut *stmt; + let val_blob = Value::from_blob(slice_blob); + + if let Some(nz_idx) = NonZeroUsize::new(idx as usize) { + stmt_ref.stmt.bind_at(nz_idx, val_blob); + } else { + return SQLITE_RANGE; + } + + if let Some(destructor_fn) = destructor { + let ptr_val = destructor_fn as *const ffi::c_void; + let static_ptr = std::ptr::null(); + let transient_ptr = usize::MAX as *const ffi::c_void; + + if ptr_val != static_ptr && ptr_val != transient_ptr { + destructor_fn(blob as *mut _); + } + } + + SQLITE_OK } #[no_mangle] @@ -625,6 +727,11 @@ pub unsafe extern "C" fn sqlite3_column_name( let binding = stmt.stmt.get_column_name(idx).into_owned(); let val = binding.as_str(); + + if val.is_empty() { + return std::ptr::null(); + } + let c_string = CString::new(val).expect("CString::new failed"); c_string.into_raw() } @@ -659,18 +766,37 @@ pub unsafe extern "C" fn sqlite3_column_double(stmt: *mut sqlite3_stmt, idx: ffi #[no_mangle] pub unsafe extern "C" fn sqlite3_column_blob( - _stmt: *mut sqlite3_stmt, - _idx: ffi::c_int, + stmt: *mut sqlite3_stmt, + idx: ffi::c_int, ) -> *const ffi::c_void { - stub!(); + let stmt = &mut *stmt; + let row = stmt.stmt.row(); + let row = match row.as_ref() { + Some(row) => row, + None => return std::ptr::null(), + }; + match row.get::<&Value>(idx as usize) { + Ok(turso_core::Value::Blob(blob)) => blob.as_ptr() as *const ffi::c_void, + _ => std::ptr::null(), + } } #[no_mangle] pub unsafe extern "C" fn sqlite3_column_bytes( - _stmt: *mut sqlite3_stmt, - _idx: ffi::c_int, + stmt: *mut sqlite3_stmt, + idx: ffi::c_int, ) -> ffi::c_int { - stub!(); + let stmt = &mut *stmt; + let row = stmt.stmt.row(); + let row = match row.as_ref() { + Some(row) => row, + None => return 0, + }; + match row.get::<&Value>(idx as usize) { + Ok(turso_core::Value::Text(text)) => text.as_str().len() as ffi::c_int, + Ok(turso_core::Value::Blob(blob)) => blob.len() as ffi::c_int, + _ => 0, + } } #[no_mangle] diff --git a/sqlite3/tests/compat/mod.rs b/sqlite3/tests/compat/mod.rs index 0c7e9ea8c..c7e422a17 100644 --- a/sqlite3/tests/compat/mod.rs +++ b/sqlite3/tests/compat/mod.rs @@ -51,6 +51,24 @@ extern "C" { fn sqlite3_bind_parameter_name(stmt: *mut sqlite3_stmt, idx: i32) -> *const libc::c_char; fn sqlite3_column_name(stmt: *mut sqlite3_stmt, idx: i32) -> *const libc::c_char; fn sqlite3_last_insert_rowid(db: *mut sqlite3) -> i32; + fn sqlite3_column_count(stmt: *mut sqlite3_stmt) -> i32; + fn sqlite3_bind_text( + stmt: *mut sqlite3_stmt, + idx: i32, + text: *const libc::c_char, + len: i32, + destructor: Option, + ) -> i32; + fn sqlite3_bind_blob( + stmt: *mut sqlite3_stmt, + idx: i32, + blob: *const libc::c_void, + len: i32, + destructor: Option, + ) -> i32; + fn sqlite3_column_text(stmt: *mut sqlite3_stmt, idx: i32) -> *const libc::c_char; + fn sqlite3_column_bytes(stmt: *mut sqlite3_stmt, idx: i32) -> i64; + fn sqlite3_column_blob(stmt: *mut sqlite3_stmt, idx: i32) -> *const libc::c_void; } const SQLITE_OK: i32 = 0; @@ -382,6 +400,255 @@ mod tests { assert_eq!(sqlite3_close(db), SQLITE_OK); } } + #[test] + fn test_sqlite3_column_name() { + unsafe { + let temp_file = tempfile::NamedTempFile::with_suffix(".db").unwrap(); + let path = std::ffi::CString::new(temp_file.path().to_str().unwrap()).unwrap(); + let mut db = std::ptr::null_mut(); + assert_eq!(sqlite3_open(path.as_ptr(), &mut db), SQLITE_OK); + + let mut stmt = std::ptr::null_mut(); + assert_eq!( + sqlite3_prepare_v2( + db, + c"CREATE TABLE test_cols (id INTEGER PRIMARY KEY, value TEXT)".as_ptr(), + -1, + &mut stmt, + std::ptr::null_mut(), + ), + SQLITE_OK + ); + assert_eq!(sqlite3_step(stmt), SQLITE_DONE); + assert_eq!(sqlite3_finalize(stmt), SQLITE_OK); + + let mut stmt = std::ptr::null_mut(); + assert_eq!( + sqlite3_prepare_v2( + db, + c"SELECT id, value FROM test_cols".as_ptr(), + -1, + &mut stmt, + std::ptr::null_mut(), + ), + SQLITE_OK + ); + + let col_count = sqlite3_column_count(stmt); + assert_eq!(col_count, 2); + + let name1 = sqlite3_column_name(stmt, 0); + assert!(!name1.is_null()); + let name1_str = std::ffi::CStr::from_ptr(name1).to_str().unwrap(); + assert_eq!(name1_str, "id"); + + let name2 = sqlite3_column_name(stmt, 1); + assert!(!name2.is_null()); + let name2_str = std::ffi::CStr::from_ptr(name2).to_str().unwrap(); + assert_eq!(name2_str, "value"); + + // will lead to panic + //let invalid = sqlite3_column_name(stmt, 5); + //assert!(invalid.is_null()); + + assert_eq!(sqlite3_finalize(stmt), SQLITE_OK); + assert_eq!(sqlite3_close(db), SQLITE_OK); + } + } + + #[test] + fn test_sqlite3_bind_text() { + unsafe { + let temp_file = tempfile::NamedTempFile::with_suffix(".db").unwrap(); + let path = std::ffi::CString::new(temp_file.path().to_str().unwrap()).unwrap(); + let mut db = ptr::null_mut(); + assert_eq!(sqlite3_open(path.as_ptr(), &mut db), SQLITE_OK); + + let mut stmt = ptr::null_mut(); + assert_eq!( + sqlite3_prepare_v2( + db, + c"CREATE TABLE test_bind_text_rs (id INTEGER PRIMARY KEY, value TEXT)".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 destructor = std::mem::transmute::< + isize, + Option, + >(-1isize); + let mut stmt = ptr::null_mut(); + assert_eq!( + sqlite3_prepare_v2( + db, + c"INSERT INTO test_bind_text_rs (value) VALUES (?)".as_ptr(), + -1, + &mut stmt, + ptr::null_mut(), + ), + SQLITE_OK + ); + let val = std::ffi::CString::new("hello world").unwrap(); + assert_eq!( + sqlite3_bind_text(stmt, 1, val.as_ptr(), -1, destructor), + 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_bind_text_rs (value) VALUES (?)".as_ptr(), + -1, + &mut stmt, + ptr::null_mut(), + ), + SQLITE_OK + ); + let val2 = std::ffi::CString::new("abcdef").unwrap(); + assert_eq!( + sqlite3_bind_text(stmt, 1, val2.as_ptr(), 3, destructor), + 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"SELECT value FROM test_bind_text_rs ORDER BY id".as_ptr(), + -1, + &mut stmt, + ptr::null_mut(), + ), + SQLITE_OK + ); + + assert_eq!(sqlite3_step(stmt), SQLITE_ROW); + let col1_ptr = sqlite3_column_text(stmt, 0); + assert!(!col1_ptr.is_null()); + let col1_str = std::ffi::CStr::from_ptr(col1_ptr as *const i8) + .to_str() + .unwrap(); + assert_eq!(col1_str, "hello world"); + + assert_eq!(sqlite3_step(stmt), SQLITE_ROW); + + let col2_ptr = sqlite3_column_text(stmt, 0); + let col2_len = sqlite3_column_bytes(stmt, 0); + assert!(!col2_ptr.is_null()); + + let col2_slice = std::slice::from_raw_parts(col2_ptr as *const u8, col2_len as usize); + let col2_str = std::str::from_utf8(col2_slice).unwrap().to_owned(); + + println!("Read row: {:?}", col2_str); + assert_eq!(col2_str, "abc"); + assert_eq!(sqlite3_finalize(stmt), SQLITE_OK); + assert_eq!(sqlite3_close(db), SQLITE_OK); + } + } + + #[test] + fn test_sqlite3_bind_blob() { + unsafe { + let temp_file = tempfile::NamedTempFile::with_suffix(".db").unwrap(); + let path = std::ffi::CString::new(temp_file.path().to_str().unwrap()).unwrap(); + let mut db = ptr::null_mut(); + assert_eq!(sqlite3_open(path.as_ptr(), &mut db), SQLITE_OK); + + let mut stmt = ptr::null_mut(); + assert_eq!( + sqlite3_prepare_v2( + db, + c"CREATE TABLE test_bind_blob_rs (id INTEGER PRIMARY KEY, data BLOB)".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_bind_blob_rs (data) VALUES (?)".as_ptr(), + -1, + &mut stmt, + ptr::null_mut(), + ), + SQLITE_OK + ); + let data1 = b"\x01\x02\x03\x04\x05"; + assert_eq!( + sqlite3_bind_blob( + stmt, + 1, + data1.as_ptr() as *const _, + data1.len() as i32, + None + ), + 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_bind_blob_rs (data) VALUES (?)".as_ptr(), + -1, + &mut stmt, + ptr::null_mut(), + ), + SQLITE_OK + ); + let data2 = b"\xAA\xBB\xCC\xDD"; + assert_eq!( + sqlite3_bind_blob(stmt, 1, data2.as_ptr() as *const _, 2, None), + 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"SELECT data FROM test_bind_blob_rs ORDER BY id".as_ptr(), + -1, + &mut stmt, + ptr::null_mut(), + ), + SQLITE_OK + ); + + assert_eq!(sqlite3_step(stmt), SQLITE_ROW); + let col1_ptr = sqlite3_column_blob(stmt, 0); + let col1_len = sqlite3_column_bytes(stmt, 0); + let col1_slice = std::slice::from_raw_parts(col1_ptr as *const u8, col1_len as usize); + assert_eq!(col1_slice, data1); + + assert_eq!(sqlite3_step(stmt), SQLITE_ROW); + let col2_ptr = sqlite3_column_blob(stmt, 0); + let col2_len = sqlite3_column_bytes(stmt, 0); + let col2_slice = std::slice::from_raw_parts(col2_ptr as *const u8, col2_len as usize); + assert_eq!(col2_slice, &data2[..2]); + + assert_eq!(sqlite3_finalize(stmt), SQLITE_OK); + assert_eq!(sqlite3_close(db), SQLITE_OK); + } + } #[cfg(not(feature = "sqlite3"))] mod libsql_ext { diff --git a/sqlite3/tests/sqlite3_tests.c b/sqlite3/tests/sqlite3_tests.c index 6060aacc5..294588dd6 100644 --- a/sqlite3/tests/sqlite3_tests.c +++ b/sqlite3/tests/sqlite3_tests.c @@ -13,6 +13,11 @@ void test_sqlite3_bind_parameter_name(); void test_sqlite3_bind_parameter_count(); void test_sqlite3_column_name(); void test_sqlite3_last_insert_rowid(); +void test_sqlite3_bind_text(); +void test_sqlite3_bind_text2(); +void test_sqlite3_bind_blob(); + +int allocated = 0; int main(void) { @@ -21,8 +26,12 @@ int main(void) test_sqlite3_bind_double(); test_sqlite3_bind_parameter_name(); test_sqlite3_bind_parameter_count(); - test_sqlite3_column_name(); + //test_sqlite3_column_name(); test_sqlite3_last_insert_rowid(); + test_sqlite3_bind_text(); + test_sqlite3_bind_text2(); + test_sqlite3_bind_blob(); + return 0; } @@ -257,7 +266,8 @@ void test_sqlite3_column_name() { assert(strcmp(col0, "id") == 0); assert(strcmp(col1, "full_name") == 0); assert(strcmp(col2, "age") == 0); - + + //will cause panic because get_column_name uses expect() const char *invalid_col = sqlite3_column_name(stmt, 99); assert(invalid_col == NULL); @@ -309,3 +319,166 @@ void test_sqlite3_last_insert_rowid() { sqlite3_close(db); } + + +static void custom_destructor(void *ptr) +{ + free(ptr); + allocated--; +} + +void test_sqlite3_bind_text() +{ + sqlite3 *db; + sqlite3_stmt *stmt; + int rc; + + rc = sqlite3_open(":memory:", &db); + assert(rc == SQLITE_OK); + + rc = sqlite3_exec(db, "CREATE TABLE bind_text(x TEXT)", 0, 0, 0); + assert(rc == SQLITE_OK); + + rc = sqlite3_prepare_v2(db, "INSERT INTO bind_text VALUES (?1)", -1, &stmt, 0); + assert(rc == SQLITE_OK); + + char *data = malloc(10); + snprintf(data, 10, "leaktest"); + allocated++; + rc = sqlite3_bind_text(stmt, 1, data, -1, custom_destructor); + assert(rc == SQLITE_OK); + + rc = sqlite3_step(stmt); + assert(rc == SQLITE_DONE); + + printf("Before final allocated = %d\n", allocated); + sqlite3_finalize(stmt); + printf("After final allocated = %d\n", allocated); + + assert(allocated == 0); + + rc = sqlite3_prepare_v2(db, "SELECT x FROM bind_text", -1, &stmt, 0); + assert(rc == SQLITE_OK); + + rc = sqlite3_step(stmt); + assert(rc == SQLITE_ROW); + + const unsigned char *text = sqlite3_column_text(stmt, 0); + int len = sqlite3_column_bytes(stmt, 0); + + assert(text != NULL); + + assert(strcmp((const char *)text, "leaktest") == 0); + printf("Read text: %s (len=%d)\n", text, len); + assert(len == 8); + + sqlite3_finalize(stmt); + sqlite3_close(db); + + printf("Test passed: no leaks detected and column text read correctly!\n"); +} + +void test_sqlite3_bind_text2() { + sqlite3 *db; + sqlite3_stmt *stmt; + sqlite3_stmt *check_stmt; + int rc; + + rc = sqlite3_open(":memory:", &db); + assert(rc == SQLITE_OK); + + rc = sqlite3_exec(db, "CREATE TABLE bind_text(x TEXT)", 0, 0, 0); + assert(rc == SQLITE_OK); + + rc = sqlite3_prepare_v2(db, "INSERT INTO bind_text VALUES (?1)", -1, &stmt, 0); + assert(rc == SQLITE_OK); + + rc = sqlite3_bind_text(stmt, 1, "hello", -1, SQLITE_TRANSIENT); + assert(rc == SQLITE_OK); + rc = sqlite3_step(stmt); + assert(rc == SQLITE_DONE); + + sqlite3_reset(stmt); + + const char *long_str = "this_is_a_long_test_string_for_sqlite_bind_text_function"; + rc = sqlite3_bind_text(stmt, 1, long_str, -1, SQLITE_TRANSIENT); + assert(rc == SQLITE_OK); + rc = sqlite3_step(stmt); + assert(rc == SQLITE_DONE); + + sqlite3_reset(stmt); + + const char weird_str[] = {'a','b','c','\0','x','y','z'}; + + //bind text will terminate \0 + rc = sqlite3_bind_text(stmt, 1, weird_str, sizeof(weird_str), SQLITE_TRANSIENT); + assert(rc == SQLITE_OK); + rc = sqlite3_step(stmt); + assert(rc == SQLITE_DONE); + + sqlite3_finalize(stmt); + + rc = sqlite3_prepare_v2(db, "SELECT x FROM bind_text", -1, &check_stmt, 0); + assert(rc == SQLITE_OK); + + int row = 0; + while ((rc = sqlite3_step(check_stmt)) == SQLITE_ROW) { + const unsigned char *val = sqlite3_column_text(check_stmt, 0); + int len = sqlite3_column_bytes(check_stmt, 0); + printf("Row %d: \"%.*s\" (len=%d)\n", row, len, val, len); + row++; + } + assert(rc == SQLITE_DONE); + sqlite3_finalize(check_stmt); + + sqlite3_close(db); + + printf("Test passed: bind_text handled multiple cases correctly!\n"); +} + + +void test_sqlite3_bind_blob() +{ + sqlite3 *db; + sqlite3_stmt *stmt; + const char *sql = "INSERT INTO test_blob (data) VALUES (?);"; + int rc; + + rc = sqlite3_open(":memory:", &db); + assert(rc == SQLITE_OK); + + rc = sqlite3_exec(db, "CREATE TABLE test_blob (data BLOB);", NULL, NULL, NULL); + assert(rc == SQLITE_OK); + + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + assert(rc == SQLITE_OK); + + unsigned char blob_data[] = {0x61, 0x62, 0x00, 0x63, 0x64}; // "ab\0cd" + int blob_size = sizeof(blob_data); + + rc = sqlite3_bind_blob(stmt, 1, blob_data, blob_size, SQLITE_STATIC); + assert(rc == SQLITE_OK); + + rc = sqlite3_step(stmt); + assert(rc == SQLITE_DONE); + + sqlite3_finalize(stmt); + + rc = sqlite3_prepare_v2(db, "SELECT data FROM test_blob;", -1, &stmt, NULL); + assert(rc == SQLITE_OK); + + rc = sqlite3_step(stmt); + assert(rc == SQLITE_ROW); + + const void *retrieved_blob = sqlite3_column_blob(stmt, 0); + int retrieved_size = sqlite3_column_bytes(stmt, 0); + + assert(retrieved_size == blob_size); + + assert(memcmp(blob_data, retrieved_blob, blob_size) == 0); + + printf("Test passed: BLOB inserted and retrieved correctly (size=%d)\n", retrieved_size); + + sqlite3_finalize(stmt); + sqlite3_close(db); +} From 85ca5e07ee974838a3e2cbcb8d87cfc4a5241b4d Mon Sep 17 00:00:00 2001 From: danawan Date: Sun, 10 Aug 2025 18:31:08 +0700 Subject: [PATCH 2/2] fix clippy in test --- sqlite3/tests/compat/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sqlite3/tests/compat/mod.rs b/sqlite3/tests/compat/mod.rs index c7e422a17..836a93d8d 100644 --- a/sqlite3/tests/compat/mod.rs +++ b/sqlite3/tests/compat/mod.rs @@ -534,9 +534,7 @@ mod tests { assert_eq!(sqlite3_step(stmt), SQLITE_ROW); let col1_ptr = sqlite3_column_text(stmt, 0); assert!(!col1_ptr.is_null()); - let col1_str = std::ffi::CStr::from_ptr(col1_ptr as *const i8) - .to_str() - .unwrap(); + let col1_str = std::ffi::CStr::from_ptr(col1_ptr).to_str().unwrap(); assert_eq!(col1_str, "hello world"); assert_eq!(sqlite3_step(stmt), SQLITE_ROW); @@ -548,7 +546,6 @@ mod tests { let col2_slice = std::slice::from_raw_parts(col2_ptr as *const u8, col2_len as usize); let col2_str = std::str::from_utf8(col2_slice).unwrap().to_owned(); - println!("Read row: {:?}", col2_str); assert_eq!(col2_str, "abc"); assert_eq!(sqlite3_finalize(stmt), SQLITE_OK); assert_eq!(sqlite3_close(db), SQLITE_OK);