mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-03 00:14:21 +01:00
Merge 'SQLite C API improvements: add bind_text and bind_blob' from Danawan Bimantoro
Add support for more of the SQLite C API. Bind functions: - sqlite3_bind_text (with destructor callback) - sqlite3_bind_blob Column functions: - sqlite3_column_text - sqlite3_column_blob - sqlite3_column_bytes Closes #2528
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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<unsafe extern "C" fn(*mut ffi::c_void)>,
|
||||
*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<unsafe extern "C" fn(*mut ffi::c_void)>,
|
||||
) -> 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<unsafe extern "C" fn(*mut ffi::c_void)>,
|
||||
) -> 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]
|
||||
|
||||
@@ -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<unsafe extern "C" fn(*mut libc::c_void)>,
|
||||
) -> i32;
|
||||
fn sqlite3_bind_blob(
|
||||
stmt: *mut sqlite3_stmt,
|
||||
idx: i32,
|
||||
blob: *const libc::c_void,
|
||||
len: i32,
|
||||
destructor: Option<unsafe extern "C" fn(*mut libc::c_void)>,
|
||||
) -> 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,252 @@ 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<unsafe extern "C" fn(*mut std::ffi::c_void)>,
|
||||
>(-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).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();
|
||||
|
||||
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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user