diff --git a/sqlite3/include/sqlite3.h b/sqlite3/include/sqlite3.h index 56f3e1896..21695d4f1 100644 --- a/sqlite3/include/sqlite3.h +++ b/sqlite3/include/sqlite3.h @@ -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) diff --git a/sqlite3/src/lib.rs b/sqlite3/src/lib.rs index 30e3af817..6c3946600 100644 --- a/sqlite3/src/lib.rs +++ b/sqlite3/src/lib.rs @@ -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>, } @@ -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] diff --git a/sqlite3/tests/compat/mod.rs b/sqlite3/tests/compat/mod.rs index 836a93d8d..2192a89f9 100644 --- a/sqlite3/tests/compat/mod.rs +++ b/sqlite3/tests/compat/mod.rs @@ -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 { diff --git a/sqlite3/tests/sqlite3_tests.c b/sqlite3/tests/sqlite3_tests.c index 294588dd6..2cd490a93 100644 --- a/sqlite3/tests/sqlite3_tests.c +++ b/sqlite3/tests/sqlite3_tests.c @@ -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); +}