Merge 'SQLite C API improvements: add column type and column decltype' from Danawan Bimantoro

Closes #2676
This commit is contained in:
Pekka Enberg
2025-08-20 11:24:43 +03:00
committed by GitHub
4 changed files with 254 additions and 7 deletions

View File

@@ -41,6 +41,13 @@
#define SQLITE_CHECKPOINT_TRUNCATE 3
#define SQLITE_INTEGER 1
#define SQLITE_FLOAT 2
#define SQLITE_BLOB 4
#define SQLITE_NULL 5
#define SQLITE_TEXT 3
#define SQLITE3_TEXT 3
typedef void (*sqlite3_destructor_type)(void*);
#define SQLITE_STATIC ((sqlite3_destructor_type)0)
#define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1)

View File

@@ -36,6 +36,13 @@ pub const SQLITE_CHECKPOINT_FULL: ffi::c_int = 1;
pub const SQLITE_CHECKPOINT_RESTART: ffi::c_int = 2;
pub const SQLITE_CHECKPOINT_TRUNCATE: ffi::c_int = 3;
pub const SQLITE_INTEGER: ffi::c_int = 1;
pub const SQLITE_FLOAT: ffi::c_int = 2;
pub const SQLITE_TEXT: ffi::c_int = 3;
pub const SQLITE3_TEXT: ffi::c_int = 3;
pub const SQLITE_BLOB: ffi::c_int = 4;
pub const SQLITE_NULL: ffi::c_int = 5;
pub struct sqlite3 {
pub(crate) inner: Arc<Mutex<sqlite3Inner>>,
}
@@ -697,10 +704,22 @@ pub unsafe extern "C" fn sqlite3_bind_blob(
#[no_mangle]
pub unsafe extern "C" fn sqlite3_column_type(
_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()
.expect("Function should only be called after `SQLITE_ROW`");
match row.get::<&Value>(idx as usize) {
Ok(turso_core::Value::Integer(_)) => SQLITE_INTEGER,
Ok(turso_core::Value::Text(_)) => SQLITE_TEXT,
Ok(turso_core::Value::Float(_)) => SQLITE_FLOAT,
Ok(turso_core::Value::Blob(_)) => SQLITE_BLOB,
_ => SQLITE_NULL,
}
}
#[no_mangle]
@@ -711,10 +730,17 @@ pub unsafe extern "C" fn sqlite3_column_count(stmt: *mut sqlite3_stmt) -> ffi::c
#[no_mangle]
pub unsafe extern "C" fn sqlite3_column_decltype(
_stmt: *mut sqlite3_stmt,
_idx: ffi::c_int,
stmt: *mut sqlite3_stmt,
idx: ffi::c_int,
) -> *const ffi::c_char {
stub!();
let stmt = &mut *stmt;
if let Some(val) = stmt.stmt.get_column_type(idx as usize) {
let c_string = CString::new(val).expect("CString::new failed");
c_string.into_raw()
} else {
std::ptr::null()
}
}
#[no_mangle]

View File

@@ -69,6 +69,8 @@ extern "C" {
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;
fn sqlite3_column_type(stmt: *mut sqlite3_stmt, idx: i32) -> i32;
fn sqlite3_column_decltype(stmt: *mut sqlite3_stmt, idx: i32) -> *const libc::c_char;
}
const SQLITE_OK: i32 = 0;
@@ -80,6 +82,12 @@ const SQLITE_CHECKPOINT_PASSIVE: i32 = 0;
const SQLITE_CHECKPOINT_FULL: i32 = 1;
const SQLITE_CHECKPOINT_RESTART: i32 = 2;
const SQLITE_CHECKPOINT_TRUNCATE: i32 = 3;
const SQLITE_INTEGER: i32 = 1;
const SQLITE_FLOAT: i32 = 2;
const SQLITE_TEXT: i32 = 3;
const SQLITE3_TEXT: i32 = 3;
const SQLITE_BLOB: i32 = 4;
const SQLITE_NULL: i32 = 5;
#[cfg(not(target_os = "windows"))]
mod tests {
@@ -647,6 +655,129 @@ mod tests {
}
}
#[test]
fn test_sqlite3_column_type() {
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_types (col_int INTEGER, col_float REAL, col_text TEXT, col_blob BLOB, col_null 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"INSERT INTO test_types VALUES (123, 45.67, 'hello', x'010203', null)"
.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 col_int, col_float, col_text, col_blob, col_null FROM test_types"
.as_ptr(),
-1,
&mut stmt,
std::ptr::null_mut(),
),
SQLITE_OK
);
assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
assert_eq!(sqlite3_column_type(stmt, 0), SQLITE_INTEGER);
assert_eq!(sqlite3_column_type(stmt, 1), SQLITE_FLOAT);
assert_eq!(sqlite3_column_type(stmt, 2), SQLITE_TEXT);
assert_eq!(sqlite3_column_type(stmt, 3), SQLITE_BLOB);
assert_eq!(sqlite3_column_type(stmt, 4), SQLITE_NULL);
assert_eq!(sqlite3_finalize(stmt), SQLITE_OK);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
fn test_sqlite3_column_decltype() {
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_decltype (col_int INTEGER, col_float REAL, col_text TEXT, col_blob BLOB, col_null NULL)".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 col_int, col_float, col_text, col_blob, col_null FROM test_decltype"
.as_ptr(),
-1,
&mut stmt,
std::ptr::null_mut(),
),
SQLITE_OK
);
let expected = [
Some("INTEGER"),
Some("REAL"),
Some("TEXT"),
Some("BLOB"),
None,
];
for i in 0..sqlite3_column_count(stmt) {
let decl = sqlite3_column_decltype(stmt, i);
if decl.is_null() {
assert!(expected[i as usize].is_none());
} else {
let s = std::ffi::CStr::from_ptr(decl)
.to_string_lossy()
.into_owned();
assert_eq!(Some(s.as_str()), expected[i as usize]);
}
}
assert_eq!(sqlite3_finalize(stmt), SQLITE_OK);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[cfg(not(feature = "sqlite3"))]
mod libsql_ext {

View File

@@ -16,6 +16,8 @@ void test_sqlite3_last_insert_rowid();
void test_sqlite3_bind_text();
void test_sqlite3_bind_text2();
void test_sqlite3_bind_blob();
void test_sqlite3_column_type();
void test_sqlite3_column_decltype();
int allocated = 0;
@@ -31,7 +33,8 @@ int main(void)
test_sqlite3_bind_text();
test_sqlite3_bind_text2();
test_sqlite3_bind_blob();
test_sqlite3_column_type();
test_sqlite3_column_decltype();
return 0;
}
@@ -482,3 +485,83 @@ void test_sqlite3_bind_blob()
sqlite3_finalize(stmt);
sqlite3_close(db);
}
void test_sqlite3_column_type()
{
sqlite3 *db;
sqlite3_stmt *stmt;
int rc;
rc = sqlite3_open(":memory:", &db);
assert(rc == SQLITE_OK);
rc = sqlite3_exec(db,
"CREATE TABLE test_column_type (col_int INTEGER, col_float REAL, col_text TEXT, col_blob BLOB, col_null TEXT);",
NULL, NULL, NULL);
assert(rc == SQLITE_OK);
rc = sqlite3_exec(db,
"INSERT INTO test_column_type VALUES (42, 3.14, 'hello', x'010203', NULL);",
NULL, NULL, NULL);
assert(rc == SQLITE_OK);
rc = sqlite3_prepare_v2(db,
"SELECT col_int, col_float, col_text, col_blob, col_null FROM test_column_type;",
-1, &stmt, NULL);
assert(rc == SQLITE_OK);
rc = sqlite3_step(stmt);
assert(rc == SQLITE_ROW);
for (int i = 0; i < sqlite3_column_count(stmt); i++) {
int type = sqlite3_column_type(stmt, i);
switch (i) {
case 0: assert(type == SQLITE_INTEGER); break;
case 1: assert(type == SQLITE_FLOAT); break;
case 2: assert(type == SQLITE_TEXT); break;
case 3: assert(type == SQLITE_BLOB); break;
case 4: assert(type == SQLITE_NULL); break;
}
}
printf("sqlite3_column_type test completed!\n");
sqlite3_finalize(stmt);
sqlite3_close(db);
}
void test_sqlite3_column_decltype()
{
sqlite3 *db;
sqlite3_stmt *stmt;
int rc;
rc = sqlite3_open(":memory:", &db);
assert(rc == SQLITE_OK);
rc = sqlite3_exec(db,
"CREATE TABLE test_decltype (col_int INTEGER, col_float REAL, col_text TEXT, col_blob BLOB, col_null NULL);",
NULL, NULL, NULL);
assert(rc == SQLITE_OK);
rc = sqlite3_prepare_v2(db,
"SELECT col_int, col_float, col_text, col_blob, col_null FROM test_decltype;",
-1, &stmt, NULL);
assert(rc == SQLITE_OK);
const char* expected[] = { "INTEGER", "REAL", "TEXT", "BLOB", NULL};
for (int i = 0; i < sqlite3_column_count(stmt); i++) {
const char* decl = sqlite3_column_decltype(stmt, i);
if (decl == NULL) {
assert(expected[i] == NULL);
} else {
assert(strcmp(decl, expected[i]) == 0);
}
}
printf("sqlite3_column_decltype test completed!\n");
sqlite3_finalize(stmt);
sqlite3_close(db);
}