diff --git a/core/lib.rs b/core/lib.rs index e1e5c6ae4..68d616fbc 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -2095,7 +2095,7 @@ impl Statement { crate::schema::Type::Text => Some("TEXT".to_string()), crate::schema::Type::Blob => Some("BLOB".to_string()), crate::schema::Type::Numeric => Some("NUMERIC".to_string()), - crate::schema::Type::Null => None, + crate::schema::Type::Null => Some("NULL".to_string()), } } _ => None, diff --git a/sqlite3/src/lib.rs b/sqlite3/src/lib.rs index 66d4ff1a1..6c3946600 100644 --- a/sqlite3/src/lib.rs +++ b/sqlite3/src/lib.rs @@ -730,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 bc9fffa98..bbb54f00f 100644 --- a/sqlite3/tests/compat/mod.rs +++ b/sqlite3/tests/compat/mod.rs @@ -70,6 +70,7 @@ extern "C" { 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; @@ -716,6 +717,58 @@ mod tests { } } + #[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 = ["INTEGER", "REAL", "TEXT", "BLOB", "NULL"]; + + for i in 0..sqlite3_column_count(stmt) { + let decl = sqlite3_column_decltype(stmt, i); + assert!(!decl.is_null()); + let s = std::ffi::CStr::from_ptr(decl) + .to_string_lossy() + .into_owned(); + println!("{s}"); + assert_eq!(s, 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 3315c8273..6af119335 100644 --- a/sqlite3/tests/sqlite3_tests.c +++ b/sqlite3/tests/sqlite3_tests.c @@ -17,6 +17,7 @@ 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; @@ -33,6 +34,7 @@ int main(void) test_sqlite3_bind_text2(); test_sqlite3_bind_blob(); test_sqlite3_column_type(); + test_sqlite3_column_decltype(); return 0; } @@ -527,3 +529,36 @@ void test_sqlite3_column_type() 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); + assert(decl != NULL); + assert(strcmp(decl, expected[i]) == 0); + } + + printf("sqlite3_column_decltype test completed!\n"); + + sqlite3_finalize(stmt); + sqlite3_close(db); +}