diff --git a/sqlite3/include/sqlite3.h b/sqlite3/include/sqlite3.h index 01703842a..6e3496cf8 100644 --- a/sqlite3/include/sqlite3.h +++ b/sqlite3/include/sqlite3.h @@ -44,6 +44,8 @@ typedef struct sqlite3 sqlite3; typedef struct sqlite3_stmt sqlite3_stmt; +typedef int64_t sqlite3_int64; +typedef sqlite3_int64 sqlite_int64; typedef int (*exec_callback)(void *context, int n_column, char **argv, char **colv); diff --git a/sqlite3/src/lib.rs b/sqlite3/src/lib.rs index 1f3ce9110..13e319606 100644 --- a/sqlite3/src/lib.rs +++ b/sqlite3/src/lib.rs @@ -2,6 +2,7 @@ #![allow(non_camel_case_types)] use std::ffi::{self, CStr, CString}; +use std::num::NonZero; use tracing::trace; use turso_core::{CheckpointMode, LimboError, Value}; @@ -22,6 +23,7 @@ pub const SQLITE_INTERRUPT: ffi::c_int = 9; pub const SQLITE_NOTFOUND: ffi::c_int = 12; pub const SQLITE_CANTOPEN: ffi::c_int = 14; pub const SQLITE_MISUSE: ffi::c_int = 21; +pub const SQLITE_RANGE: ffi::c_int = 25; pub const SQLITE_ROW: ffi::c_int = 100; pub const SQLITE_DONE: ffi::c_int = 101; pub const SQLITE_ABORT_ROLLBACK: ffi::c_int = SQLITE_ABORT | (2 << 8); @@ -313,8 +315,10 @@ pub unsafe extern "C" fn sqlite3_reset(stmt: *mut sqlite3_stmt) -> ffi::c_int { } #[no_mangle] -pub unsafe extern "C" fn sqlite3_changes(_db: *mut sqlite3) -> ffi::c_int { - stub!(); +pub unsafe extern "C" fn sqlite3_changes(db: *mut sqlite3) -> ffi::c_int { + let db: &mut sqlite3 = &mut *db; + let inner = db.inner.lock().unwrap(); + return inner.conn.changes() as ffi::c_int; } #[no_mangle] @@ -355,13 +359,17 @@ pub unsafe extern "C" fn sqlite3_get_autocommit(_db: *mut sqlite3) -> ffi::c_int } #[no_mangle] -pub unsafe extern "C" fn sqlite3_total_changes(_db: *mut sqlite3) -> ffi::c_int { - stub!(); +pub unsafe extern "C" fn sqlite3_total_changes(db: *mut sqlite3) -> ffi::c_int { + let db: &mut sqlite3 = &mut *db; + let inner = db.inner.lock().unwrap(); + return inner.conn.total_changes() as ffi::c_int; } #[no_mangle] -pub unsafe extern "C" fn sqlite3_last_insert_rowid(_db: *mut sqlite3) -> i64 { - stub!(); +pub unsafe extern "C" fn sqlite3_last_insert_rowid(db: *mut sqlite3) -> ffi::c_int { + let db: &mut sqlite3 = &mut *db; + let inner = db.inner.lock().unwrap(); + return inner.conn.last_insert_rowid() as ffi::c_int; } #[no_mangle] @@ -476,42 +484,92 @@ pub unsafe extern "C" fn sqlite3_data_count(stmt: *mut sqlite3_stmt) -> ffi::c_i } #[no_mangle] -pub unsafe extern "C" fn sqlite3_bind_parameter_count(_stmt: *mut sqlite3_stmt) -> ffi::c_int { - stub!(); +pub unsafe extern "C" fn sqlite3_bind_parameter_count(stmt: *mut sqlite3_stmt) -> ffi::c_int { + let stmt = &*stmt; + return stmt.stmt.parameters_count() as ffi::c_int; } #[no_mangle] pub unsafe extern "C" fn sqlite3_bind_parameter_name( - _stmt: *mut sqlite3_stmt, - _idx: ffi::c_int, + stmt: *mut sqlite3_stmt, + idx: ffi::c_int, ) -> *const ffi::c_char { - stub!(); + let stmt = &*stmt; + let index = NonZero::new_unchecked(idx as usize); + + if let Some(val) = stmt.stmt.parameters().name(index) { + let c_string = CString::new(val).expect("CString::new failed"); + let c_ptr = c_string.into_raw(); + return c_ptr; + } else { + return std::ptr::null(); + } } #[no_mangle] -pub unsafe extern "C" fn sqlite3_bind_null( - _stmt: *mut sqlite3_stmt, - _idx: ffi::c_int, +pub unsafe extern "C" fn sqlite3_bind_null(stmt: *mut sqlite3_stmt, idx: ffi::c_int) -> ffi::c_int { + if stmt.is_null() { + return SQLITE_MISUSE; + } + + if idx <= 0 { + return SQLITE_RANGE; + } + let stmt = &mut *stmt; + + stmt.stmt + .bind_at(NonZero::new_unchecked(idx as usize), Value::Null); + return SQLITE_OK; +} + +#[no_mangle] +pub unsafe extern "C" fn sqlite3_bind_int( + stmt: *mut sqlite3_stmt, + idx: ffi::c_int, + val: i64, ) -> ffi::c_int { - stub!(); + sqlite3_bind_int64(stmt, idx, val) } #[no_mangle] pub unsafe extern "C" fn sqlite3_bind_int64( - _stmt: *mut sqlite3_stmt, - _idx: ffi::c_int, - _val: i64, + stmt: *mut sqlite3_stmt, + idx: ffi::c_int, + val: i64, ) -> ffi::c_int { - stub!(); + if stmt.is_null() { + return SQLITE_MISUSE; + } + if idx <= 0 { + return SQLITE_RANGE; + } + let stmt = &mut *stmt; + + stmt.stmt + .bind_at(NonZero::new_unchecked(idx as usize), Value::Integer(val)); + + SQLITE_OK } #[no_mangle] pub unsafe extern "C" fn sqlite3_bind_double( - _stmt: *mut sqlite3_stmt, - _idx: ffi::c_int, - _val: f64, + stmt: *mut sqlite3_stmt, + idx: ffi::c_int, + val: f64, ) -> ffi::c_int { - stub!(); + println!("Bind Double Rust"); + if stmt.is_null() { + return SQLITE_MISUSE; + } + if idx <= 0 { + return SQLITE_RANGE; + } + let stmt = &mut *stmt; + + stmt.stmt + .bind_at(NonZero::new_unchecked(idx as usize), Value::Float(val)); + + SQLITE_OK } #[no_mangle] @@ -545,8 +603,9 @@ pub unsafe extern "C" fn sqlite3_column_type( } #[no_mangle] -pub unsafe extern "C" fn sqlite3_column_count(_stmt: *mut sqlite3_stmt) -> ffi::c_int { - stub!(); +pub unsafe extern "C" fn sqlite3_column_count(stmt: *mut sqlite3_stmt) -> ffi::c_int { + let stmt = &mut *stmt; + return stmt.stmt.num_columns() as ffi::c_int; } #[no_mangle] @@ -559,10 +618,17 @@ pub unsafe extern "C" fn sqlite3_column_decltype( #[no_mangle] pub unsafe extern "C" fn sqlite3_column_name( - _stmt: *mut sqlite3_stmt, - _idx: ffi::c_int, + stmt: *mut sqlite3_stmt, + idx: ffi::c_int, ) -> *const ffi::c_char { - stub!(); + let idx = idx.try_into().unwrap(); + let stmt = &mut *stmt; + + let binding = stmt.stmt.get_column_name(idx).into_owned(); + let val = binding.as_str(); + let c_string = CString::new(val).expect("CString::new failed"); + let c_ptr = c_string.into_raw(); + c_ptr } #[no_mangle] @@ -578,8 +644,19 @@ pub unsafe extern "C" fn sqlite3_column_int64(stmt: *mut sqlite3_stmt, idx: ffi: } #[no_mangle] -pub unsafe extern "C" fn sqlite3_column_double(_stmt: *mut sqlite3_stmt, _idx: ffi::c_int) -> f64 { - stub!(); +pub unsafe extern "C" fn sqlite3_column_int(stmt: *mut sqlite3_stmt, idx: ffi::c_int) -> i64 { + sqlite3_column_int64(stmt, idx) +} + +#[no_mangle] +pub unsafe extern "C" fn sqlite3_column_double(stmt: *mut sqlite3_stmt, idx: ffi::c_int) -> f64 { + let idx = idx.try_into().unwrap(); + let stmt = &mut *stmt; + let row = stmt + .stmt + .row() + .expect("Function should only be called after `SQLITE_ROW`"); + row.get(idx).unwrap() } #[no_mangle] diff --git a/sqlite3/tests/Makefile b/sqlite3/tests/Makefile new file mode 100644 index 000000000..4390d9be6 --- /dev/null +++ b/sqlite3/tests/Makefile @@ -0,0 +1,10 @@ +CC=gcc +CFLAGS=-Wall -Wextra -g -I../include +TARGET=sqlite3-tests +SRC=sqlite3_tests.c + +all: + $(CC) $(CFLAGS) -o $(TARGET) $(SRC) $(LIBS) + +clean: + rm -f $(TARGET) diff --git a/sqlite3/tests/compat/mod.rs b/sqlite3/tests/compat/mod.rs index 43843a236..8f788d734 100644 --- a/sqlite3/tests/compat/mod.rs +++ b/sqlite3/tests/compat/mod.rs @@ -1,6 +1,5 @@ #![allow(non_camel_case_types)] #![allow(dead_code)] - use std::ptr; #[repr(C)] @@ -46,6 +45,12 @@ extern "C" { frame_len: u32, ) -> i32; fn libsql_wal_disable_checkpoint(db: *mut sqlite3) -> i32; + fn sqlite3_column_int(stmt: *mut sqlite3_stmt, idx: i32) -> i64; + fn sqlite3_bind_int(stmt: *mut sqlite3_stmt, idx: i32, val: i64) -> i32; + fn sqlite3_bind_parameter_count(stmt: *mut sqlite3_stmt) -> i32; + 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; } const SQLITE_OK: i32 = 0; @@ -204,6 +209,181 @@ mod tests { assert_eq!(sqlite3_close(db), SQLITE_OK); } } + #[test] + fn test_sqlite3_bind_int() { + 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 (id INTEGER PRIMARY KEY, value INTEGER)".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 (value) VALUES (?)".as_ptr(), + -1, + &mut stmt, + ptr::null_mut(), + ), + SQLITE_OK + ); + assert_eq!(sqlite3_bind_int(stmt, 1, 42), 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 LIMIT 1".as_ptr(), + -1, + &mut stmt, + ptr::null_mut(), + ), + SQLITE_OK + ); + assert_eq!(sqlite3_step(stmt), SQLITE_ROW); + assert_eq!(sqlite3_column_int(stmt, 0), 42); + assert_eq!(sqlite3_finalize(stmt), SQLITE_OK); + + assert_eq!(sqlite3_close(db), SQLITE_OK); + } + } + #[test] + fn test_sqlite3_bind_parameter_name_and_count() { + 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_params (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 mut stmt = ptr::null_mut(); + assert_eq!( + sqlite3_prepare_v2( + db, + c"INSERT INTO test_params (id, value) VALUES (?1, ?2)".as_ptr(), + -1, + &mut stmt, + ptr::null_mut(), + ), + SQLITE_OK + ); + + let param_count = sqlite3_bind_parameter_count(stmt); + assert_eq!(param_count, 2); + + println!("parameter count {}", param_count); + + let name1 = sqlite3_bind_parameter_name(stmt, 1); + assert!(!name1.is_null()); + let name1_str = std::ffi::CStr::from_ptr(name1).to_str().unwrap(); + assert_eq!(name1_str, "?1"); + + let name2 = sqlite3_bind_parameter_name(stmt, 2); + assert!(!name2.is_null()); + let name2_str = std::ffi::CStr::from_ptr(name2).to_str().unwrap(); + assert_eq!(name2_str, "?2"); + + let invalid_name = sqlite3_bind_parameter_name(stmt, 99); + assert!(invalid_name.is_null()); + + assert_eq!(sqlite3_finalize(stmt), SQLITE_OK); + assert_eq!(sqlite3_close(db), SQLITE_OK); + } + } + + #[test] + fn test_sqlite3_last_insert_rowid() { + 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_rowid (value INTEGER)".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_rowid (value) VALUES (6)".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 last_rowid = sqlite3_last_insert_rowid(db); + assert!(last_rowid > 0); + println!("last insert rowid: {}", last_rowid); + + let query = format!("SELECT value FROM test_rowid WHERE rowid = {}", last_rowid); + let query_cstring = std::ffi::CString::new(query).unwrap(); + + let mut stmt = std::ptr::null_mut(); + assert_eq!( + sqlite3_prepare_v2( + db, + query_cstring.as_ptr(), + -1, + &mut stmt, + std::ptr::null_mut(), + ), + SQLITE_OK + ); + + assert_eq!(sqlite3_step(stmt), SQLITE_ROW); + let value_int = sqlite3_column_int(stmt, 0); + assert_eq!(value_int, 6); + + 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 new file mode 100644 index 000000000..6060aacc5 --- /dev/null +++ b/sqlite3/tests/sqlite3_tests.c @@ -0,0 +1,311 @@ +#include +#include +#include +#include +#include +#include +#include + +void test_sqlite3_changes(); +void test_sqlite3_bind_int64(); +void test_sqlite3_bind_double(); +void test_sqlite3_bind_parameter_name(); +void test_sqlite3_bind_parameter_count(); +void test_sqlite3_column_name(); +void test_sqlite3_last_insert_rowid(); + +int main(void) +{ + test_sqlite3_changes(); + test_sqlite3_bind_int64(); + test_sqlite3_bind_double(); + test_sqlite3_bind_parameter_name(); + test_sqlite3_bind_parameter_count(); + test_sqlite3_column_name(); + test_sqlite3_last_insert_rowid(); + return 0; +} + + +void test_sqlite3_changes() +{ + sqlite3 *db; + char *err_msg = NULL; + int rc; + + rc = sqlite3_open("../../testing/testing.db", &db); + assert(rc == SQLITE_OK); + + rc = sqlite3_exec(db, + "CREATE TABLE IF NOT EXISTS turso_test_changes (id INTEGER PRIMARY KEY, name TEXT);", + NULL, NULL, &err_msg); + assert(rc == SQLITE_OK); + if (err_msg) { sqlite3_free(err_msg); err_msg = NULL; } + + rc = sqlite3_exec(db, "DELETE FROM turso_test_changes;", NULL, NULL, &err_msg); + assert(rc == SQLITE_OK); + if (err_msg) { sqlite3_free(err_msg); err_msg = NULL; } + + sqlite3_close(db); + rc = sqlite3_open("../../testing/testing.db", &db); + assert(rc == SQLITE_OK); + + assert(sqlite3_changes(db) == 0); + + rc = sqlite3_exec(db, + "INSERT INTO turso_test_changes (name) VALUES ('abc');", + NULL, NULL, &err_msg); + assert(rc == SQLITE_OK); + if (err_msg) { sqlite3_free(err_msg); err_msg = NULL; } + assert(sqlite3_changes(db) == 1); + + + rc = sqlite3_exec(db, + "INSERT INTO turso_test_changes (name) VALUES ('def'),('ghi'),('jkl');", + NULL, NULL, &err_msg); + assert(rc == SQLITE_OK); + if (err_msg) { sqlite3_free(err_msg); err_msg = NULL; } + assert(sqlite3_changes(db) == 3); + + sqlite3_close(db); +} + + +void test_sqlite3_bind_int64() +{ + sqlite3 *db; + sqlite3_stmt *stmt; + char *err_msg = NULL; + int rc; + + rc = sqlite3_open("../../testing/testing.db", &db); + assert(rc == SQLITE_OK); + + rc = sqlite3_exec(db, + "CREATE TABLE IF NOT EXISTS turso_test_int64 (id INTEGER PRIMARY KEY, value INTEGER);", + NULL, NULL, &err_msg); + assert(rc == SQLITE_OK); + + if (err_msg) { sqlite3_free(err_msg); err_msg = NULL; } + rc = sqlite3_exec(db, "DELETE FROM turso_test_int64;", NULL, NULL, &err_msg); + assert(rc == SQLITE_OK); + if (err_msg) { sqlite3_free(err_msg); err_msg = NULL; } + + rc = sqlite3_prepare_v2(db, "INSERT INTO turso_test_int64 (value) VALUES (?);", -1, &stmt, NULL); + assert(rc == SQLITE_OK); + + sqlite_int64 big_value = (sqlite_int64)9223372036854775807LL; + rc = sqlite3_bind_int64(stmt, 1, big_value); + assert(rc == SQLITE_OK); + + rc = sqlite3_step(stmt); + assert(rc == SQLITE_DONE); + + rc = sqlite3_finalize(stmt); + assert(rc == SQLITE_OK); + + rc = sqlite3_prepare_v2(db, "SELECT value FROM turso_test_int64 LIMIT 1;", -1, &stmt, NULL); + assert(rc == SQLITE_OK); + + rc = sqlite3_step(stmt); + assert(rc == SQLITE_ROW); + + sqlite_int64 fetched = sqlite3_column_int64(stmt, 0); + assert(fetched == big_value); + + printf("Inserted value: %lld, Fetched value: %lld\n", (long long)big_value, (long long)fetched); + + sqlite3_finalize(stmt); + sqlite3_close(db); +} + +void test_sqlite3_bind_double() +{ + sqlite3 *db; + sqlite3_stmt *stmt; + char *err_msg = NULL; + int rc; + + rc = sqlite3_open("../../testing/testing.db", &db); + assert(rc == SQLITE_OK); + + rc = sqlite3_exec(db, + "CREATE TABLE IF NOT EXISTS turso_test_double (id INTEGER PRIMARY KEY, value REAL);", + NULL, NULL, &err_msg); + assert(rc == SQLITE_OK); + if (err_msg) { sqlite3_free(err_msg); err_msg = NULL; } + + rc = sqlite3_exec(db, "DELETE FROM turso_test_double;", NULL, NULL, &err_msg); + assert(rc == SQLITE_OK); + if (err_msg) { sqlite3_free(err_msg); err_msg = NULL; } + + rc = sqlite3_prepare_v2(db, "INSERT INTO turso_test_double (value) VALUES (?);", -1, &stmt, NULL); + assert(rc == SQLITE_OK); + + double big_value = 1234567890.123456; + rc = sqlite3_bind_double(stmt, 1, big_value); + assert(rc == SQLITE_OK); + + rc = sqlite3_step(stmt); + assert(rc == SQLITE_DONE); + + rc = sqlite3_finalize(stmt); + assert(rc == SQLITE_OK); + + rc = sqlite3_prepare_v2(db, "SELECT value FROM turso_test_double LIMIT 1;", -1, &stmt, NULL); + assert(rc == SQLITE_OK); + + rc = sqlite3_step(stmt); + assert(rc == SQLITE_ROW); + + double fetched = sqlite3_column_double(stmt, 0); + assert(fabs(fetched - big_value) < 1e-9); + + printf("Inserted value: %.15f, Fetched value: %.15f\n", big_value, fetched); + + sqlite3_finalize(stmt); + sqlite3_close(db); +} + + +void test_sqlite3_bind_parameter_name() { + sqlite3 *db; + sqlite3_stmt *stmt; + int rc; + + rc = sqlite3_open(":memory:", &db); + assert(rc == SQLITE_OK); + + const char *sql = "INSERT INTO test_parameter_name (value) VALUES (:val);"; + rc = sqlite3_exec(db, "CREATE TABLE test_parameter_name (id INTEGER PRIMARY KEY, value INTEGER);", NULL, NULL, NULL); + assert(rc == SQLITE_OK); + + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + assert(rc == SQLITE_OK); + + const char *param_name = sqlite3_bind_parameter_name(stmt, 1); + assert(param_name != NULL); + printf("Parameter name: %s\n", param_name); + assert(strcmp(param_name, ":val") == 0); + + const char *invalid_name = sqlite3_bind_parameter_name(stmt, 99); + assert(invalid_name == NULL); + + sqlite3_finalize(stmt); + sqlite3_close(db); +} + + +void test_sqlite3_bind_parameter_count() { + sqlite3 *db; + sqlite3_stmt *stmt; + int rc; + + rc = sqlite3_open(":memory:", &db); + assert(rc == SQLITE_OK); + + rc = sqlite3_exec(db, "CREATE TABLE test_parameter_count (id INTEGER PRIMARY KEY, value TEXT);", NULL, NULL, NULL); + assert(rc == SQLITE_OK); + + const char *sql = "INSERT INTO test_parameter_count (id, value) VALUES (?1, ?2);"; + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + assert(rc == SQLITE_OK); + + int param_count = sqlite3_bind_parameter_count(stmt); + printf("Parameter count: %d\n", param_count); + assert(param_count == 2); + sqlite3_finalize(stmt); + + rc = sqlite3_prepare_v2(db, "SELECT * FROM test_parameter_count;", -1, &stmt, NULL); + assert(rc == SQLITE_OK); + param_count = sqlite3_bind_parameter_count(stmt); + printf("Parameter count (no params): %d\n", param_count); + assert(param_count == 0); + + sqlite3_finalize(stmt); + sqlite3_close(db); +} + +void test_sqlite3_column_name() { + sqlite3 *db; + sqlite3_stmt *stmt; + int rc; + + rc = sqlite3_open(":memory:", &db); + assert(rc == SQLITE_OK); + + rc = sqlite3_exec(db, + "CREATE TABLE test_column_name (id INTEGER PRIMARY KEY, name TEXT, age INTEGER);", + NULL, NULL, NULL); + assert(rc == SQLITE_OK); + + const char *sql = "SELECT id, name AS full_name, age FROM test_column_name;"; + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + assert(rc == SQLITE_OK); + + int col_count = sqlite3_column_count(stmt); + assert(col_count == 3); + + const char *col0 = sqlite3_column_name(stmt, 0); + const char *col1 = sqlite3_column_name(stmt, 1); + const char *col2 = sqlite3_column_name(stmt, 2); + + printf("Column 0 name: %s\n", col0); + printf("Column 1 name: %s\n", col1); + printf("Column 2 name: %s\n", col2); + + assert(strcmp(col0, "id") == 0); + assert(strcmp(col1, "full_name") == 0); + assert(strcmp(col2, "age") == 0); + + const char *invalid_col = sqlite3_column_name(stmt, 99); + assert(invalid_col == NULL); + + sqlite3_finalize(stmt); + sqlite3_close(db); +} + + +void test_sqlite3_last_insert_rowid() { + sqlite3 *db; + sqlite3_stmt *stmt; + int rc; + + rc = sqlite3_open(":memory:", &db); + assert(rc == SQLITE_OK); + + rc = sqlite3_exec(db, + "CREATE TABLE test_last_insert (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT);", + NULL, NULL, NULL); + assert(rc == SQLITE_OK); + + rc = sqlite3_prepare_v2(db, + "INSERT INTO test_last_insert (name) VALUES ('first');", + -1, &stmt, NULL); + assert(rc == SQLITE_OK); + + rc = sqlite3_step(stmt); + assert(rc == SQLITE_DONE); + + sqlite3_finalize(stmt); + + sqlite3_int64 rowid1 = sqlite3_last_insert_rowid(db); + printf("first: %lld\n", (long long)rowid1); + assert(rowid1 == 1); + + rc = sqlite3_prepare_v2(db, + "INSERT INTO test_last_insert (name) VALUES ('second');", + -1, &stmt, NULL); + assert(rc == SQLITE_OK); + + rc = sqlite3_step(stmt); + assert(rc == SQLITE_DONE); + + sqlite3_finalize(stmt); + + sqlite3_int64 rowid2 = sqlite3_last_insert_rowid(db); + printf("second: %lld\n", (long long)rowid2); + assert(rowid2 == 2); + + sqlite3_close(db); +}