mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-25 12:04:21 +01:00
Merge 'Return null terminated strings from sqlite3_column_text' from Preston Thorpe
closes #3811 adds `text_cache` which owns the null terminated bytes, which get cached if a subsequent call to `sqlite3_column_text` is made. #3809 depends on this fix Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com> Closes #3817
This commit is contained in:
@@ -94,15 +94,25 @@ pub struct sqlite3_stmt {
|
||||
*mut ffi::c_void,
|
||||
)>,
|
||||
pub(crate) next: *mut sqlite3_stmt,
|
||||
pub(crate) text_cache: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl sqlite3_stmt {
|
||||
pub fn new(db: *mut sqlite3, stmt: turso_core::Statement) -> Self {
|
||||
let n_cols = stmt.num_columns();
|
||||
Self {
|
||||
db,
|
||||
stmt,
|
||||
destructors: Vec::new(),
|
||||
next: std::ptr::null_mut(),
|
||||
text_cache: vec![vec![]; n_cols],
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
fn clear_text_cache(&mut self) {
|
||||
// Drop per-column buffers for the previous row
|
||||
for r in &mut self.text_cache {
|
||||
r.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -323,7 +333,7 @@ pub unsafe extern "C" fn sqlite3_finalize(stmt: *mut sqlite3_stmt) -> ffi::c_int
|
||||
destructor_fn(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
stmt_ref.clear_text_cache();
|
||||
let _ = Box::from_raw(stmt);
|
||||
SQLITE_OK
|
||||
}
|
||||
@@ -340,9 +350,15 @@ pub unsafe extern "C" fn sqlite3_step(stmt: *mut sqlite3_stmt) -> ffi::c_int {
|
||||
stmt.stmt.run_once().unwrap();
|
||||
continue;
|
||||
}
|
||||
turso_core::StepResult::Done => return SQLITE_DONE,
|
||||
turso_core::StepResult::Done => {
|
||||
stmt.clear_text_cache();
|
||||
return SQLITE_DONE;
|
||||
}
|
||||
turso_core::StepResult::Interrupt => return SQLITE_INTERRUPT,
|
||||
turso_core::StepResult::Row => return SQLITE_ROW,
|
||||
turso_core::StepResult::Row => {
|
||||
stmt.clear_text_cache();
|
||||
return SQLITE_ROW;
|
||||
}
|
||||
turso_core::StepResult::Busy => return SQLITE_BUSY,
|
||||
}
|
||||
} else {
|
||||
@@ -389,6 +405,7 @@ pub unsafe extern "C" fn sqlite3_exec(
|
||||
pub unsafe extern "C" fn sqlite3_reset(stmt: *mut sqlite3_stmt) -> ffi::c_int {
|
||||
let stmt = &mut *stmt;
|
||||
stmt.stmt.reset();
|
||||
stmt.clear_text_cache();
|
||||
SQLITE_OK
|
||||
}
|
||||
|
||||
@@ -1048,14 +1065,30 @@ pub unsafe extern "C" fn sqlite3_column_text(
|
||||
stmt: *mut sqlite3_stmt,
|
||||
idx: ffi::c_int,
|
||||
) -> *const ffi::c_uchar {
|
||||
if stmt.is_null() || idx < 0 {
|
||||
return std::ptr::null();
|
||||
}
|
||||
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::Text(text)) => text.as_str().as_ptr(),
|
||||
let i = idx as usize;
|
||||
if i >= stmt.text_cache.len() {
|
||||
return std::ptr::null();
|
||||
}
|
||||
if !stmt.text_cache[i].is_empty() {
|
||||
// we have already cached this value
|
||||
return stmt.text_cache[i].as_ptr() as *const ffi::c_uchar;
|
||||
}
|
||||
match row.get::<&Value>(i) {
|
||||
Ok(turso_core::Value::Text(text)) => {
|
||||
let buf = &mut stmt.text_cache[i];
|
||||
buf.extend(text.as_str().as_bytes());
|
||||
buf.push(0);
|
||||
buf.as_ptr() as *const ffi::c_uchar
|
||||
}
|
||||
_ => std::ptr::null(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,6 +412,42 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn column_text_is_nul_terminated_and_bytes_match() {
|
||||
unsafe {
|
||||
let mut db = std::ptr::null_mut();
|
||||
assert_eq!(
|
||||
sqlite3_open(c"../testing/testing.db".as_ptr(), &mut db),
|
||||
SQLITE_OK
|
||||
);
|
||||
let mut stmt = std::ptr::null_mut();
|
||||
assert_eq!(
|
||||
sqlite3_prepare_v2(
|
||||
db,
|
||||
c"SELECT first_name FROM users ORDER BY rowid ASC LIMIT 1;".as_ptr(),
|
||||
-1,
|
||||
&mut stmt,
|
||||
std::ptr::null_mut()
|
||||
),
|
||||
SQLITE_OK
|
||||
);
|
||||
assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
|
||||
let p = sqlite3_column_text(stmt, 0);
|
||||
assert!(!p.is_null());
|
||||
let bytes = sqlite3_column_bytes(stmt, 0) as usize;
|
||||
// NUL at [bytes], and no extra counted
|
||||
let slice = std::slice::from_raw_parts(p, bytes + 1);
|
||||
assert_eq!(slice[bytes], 0);
|
||||
assert_eq!(libc::strlen(p), bytes);
|
||||
|
||||
let s = std::ffi::CStr::from_ptr(p).to_str().unwrap();
|
||||
assert_eq!(s, "Jamie");
|
||||
assert_eq!(sqlite3_finalize(stmt), SQLITE_OK);
|
||||
assert_eq!(sqlite3_close(db), SQLITE_OK);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sqlite3_bind_text() {
|
||||
unsafe {
|
||||
|
||||
Reference in New Issue
Block a user