Add integration tests for sqlite3_exec multi-statements

This commit is contained in:
PThorpe92
2025-10-22 15:02:49 -04:00
parent d0fd258ab5
commit 921f2e72bd

View File

@@ -20,6 +20,21 @@ extern "C" {
fn sqlite3_close(db: *mut sqlite3) -> i32;
fn sqlite3_open(filename: *const libc::c_char, db: *mut *mut sqlite3) -> i32;
fn sqlite3_db_filename(db: *mut sqlite3, db_name: *const libc::c_char) -> *const libc::c_char;
fn sqlite3_exec(
db: *mut sqlite3,
sql: *const libc::c_char,
callback: Option<
unsafe extern "C" fn(
arg1: *mut libc::c_void,
arg2: libc::c_int,
arg3: *mut *mut libc::c_char,
arg4: *mut *mut libc::c_char,
) -> libc::c_int,
>,
arg: *mut libc::c_void,
errmsg: *mut *mut libc::c_char,
) -> i32;
fn sqlite3_free(ptr: *mut libc::c_void);
fn sqlite3_prepare_v2(
db: *mut sqlite3,
sql: *const libc::c_char,
@@ -106,6 +121,7 @@ const SQLITE_CHECKPOINT_RESTART: i32 = 2;
const SQLITE_CHECKPOINT_TRUNCATE: i32 = 3;
const SQLITE_INTEGER: i32 = 1;
const SQLITE_FLOAT: i32 = 2;
const SQLITE_ABORT: i32 = 4;
const SQLITE_TEXT: i32 = 3;
const SQLITE3_TEXT: i32 = 3;
const SQLITE_BLOB: i32 = 4;
@@ -762,6 +778,749 @@ mod tests {
}
}
#[test]
fn test_exec_multi_statement_dml() {
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);
// Multiple DML statements in one exec call
let rc = sqlite3_exec(
db,
c"CREATE TABLE bind_text(x TEXT);\
INSERT INTO bind_text(x) VALUES('TEXT1');\
INSERT INTO bind_text(x) VALUES('TEXT2');"
.as_ptr(),
None,
ptr::null_mut(),
ptr::null_mut(),
);
assert_eq!(rc, SQLITE_OK);
// Verify the data was inserted
let mut stmt = ptr::null_mut();
assert_eq!(
sqlite3_prepare_v2(
db,
c"SELECT COUNT(*) FROM bind_text".as_ptr(),
-1,
&mut stmt,
ptr::null_mut(),
),
SQLITE_OK
);
assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
assert_eq!(sqlite3_column_int(stmt, 0), 2);
assert_eq!(sqlite3_finalize(stmt), SQLITE_OK);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
fn test_exec_multi_statement_with_semicolons_in_strings() {
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);
// Semicolons inside strings should not split statements
let rc = sqlite3_exec(
db,
c"CREATE TABLE test_semicolon(x TEXT);\
INSERT INTO test_semicolon(x) VALUES('value;with;semicolons');\
INSERT INTO test_semicolon(x) VALUES(\"another;value\");"
.as_ptr(),
None,
ptr::null_mut(),
ptr::null_mut(),
);
assert_eq!(rc, SQLITE_OK);
// Verify the values contain semicolons
let mut stmt = ptr::null_mut();
assert_eq!(
sqlite3_prepare_v2(
db,
c"SELECT x FROM test_semicolon ORDER BY rowid".as_ptr(),
-1,
&mut stmt,
ptr::null_mut(),
),
SQLITE_OK
);
assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
let val1 = std::ffi::CStr::from_ptr(sqlite3_column_text(stmt, 0))
.to_str()
.unwrap();
assert_eq!(val1, "value;with;semicolons");
assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
let val2 = std::ffi::CStr::from_ptr(sqlite3_column_text(stmt, 0))
.to_str()
.unwrap();
assert_eq!(val2, "another;value");
assert_eq!(sqlite3_finalize(stmt), SQLITE_OK);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
fn test_exec_multi_statement_with_escaped_quotes() {
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);
// Test escaped quotes
let rc = sqlite3_exec(
db,
c"CREATE TABLE test_quotes(x TEXT);\
INSERT INTO test_quotes(x) VALUES('it''s working');\
INSERT INTO test_quotes(x) VALUES(\"quote\"\"test\"\"\");"
.as_ptr(),
None,
ptr::null_mut(),
ptr::null_mut(),
);
assert_eq!(rc, SQLITE_OK);
let mut stmt = ptr::null_mut();
assert_eq!(
sqlite3_prepare_v2(
db,
c"SELECT x FROM test_quotes ORDER BY rowid".as_ptr(),
-1,
&mut stmt,
ptr::null_mut(),
),
SQLITE_OK
);
assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
let val1 = std::ffi::CStr::from_ptr(sqlite3_column_text(stmt, 0))
.to_str()
.unwrap();
assert_eq!(val1, "it's working");
assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
let val2 = std::ffi::CStr::from_ptr(sqlite3_column_text(stmt, 0))
.to_str()
.unwrap();
assert_eq!(val2, "quote\"test\"");
assert_eq!(sqlite3_finalize(stmt), SQLITE_OK);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
fn test_exec_with_select_callback() {
unsafe {
// Callback that collects results
unsafe extern "C" fn exec_callback(
context: *mut std::ffi::c_void,
n_cols: std::ffi::c_int,
values: *mut *mut std::ffi::c_char,
_cols: *mut *mut std::ffi::c_char,
) -> std::ffi::c_int {
let results = &mut *(context as *mut Vec<Vec<String>>);
let mut row = Vec::new();
for i in 0..n_cols as isize {
let value_ptr = *values.offset(i);
let value = if value_ptr.is_null() {
String::from("NULL")
} else {
std::ffi::CStr::from_ptr(value_ptr)
.to_str()
.unwrap()
.to_owned()
};
row.push(value);
}
results.push(row);
0 // Continue
}
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);
// Setup data
let rc = sqlite3_exec(
db,
c"CREATE TABLE test_select(id INTEGER, name TEXT);\
INSERT INTO test_select VALUES(1, 'Alice');\
INSERT INTO test_select VALUES(2, 'Bob');"
.as_ptr(),
None,
ptr::null_mut(),
ptr::null_mut(),
);
assert_eq!(rc, SQLITE_OK);
// Execute SELECT with callback
let mut results: Vec<Vec<String>> = Vec::new();
let rc = sqlite3_exec(
db,
c"SELECT id, name FROM test_select ORDER BY id".as_ptr(),
Some(exec_callback),
&mut results as *mut _ as *mut std::ffi::c_void,
ptr::null_mut(),
);
assert_eq!(rc, SQLITE_OK);
assert_eq!(results.len(), 2);
assert_eq!(results[0], vec!["1", "Alice"]);
assert_eq!(results[1], vec!["2", "Bob"]);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
fn test_exec_multi_statement_mixed_dml_select() {
unsafe {
// Callback that counts invocations
unsafe extern "C" fn count_callback(
context: *mut std::ffi::c_void,
_n_cols: std::ffi::c_int,
_values: *mut *mut std::ffi::c_char,
_cols: *mut *mut std::ffi::c_char,
) -> std::ffi::c_int {
let count = &mut *(context as *mut i32);
*count += 1;
0
}
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 callback_count = 0;
// Mix of DDL/DML/DQL
let rc = sqlite3_exec(
db,
c"CREATE TABLE mixed(x INTEGER);\
INSERT INTO mixed VALUES(1);\
INSERT INTO mixed VALUES(2);\
SELECT x FROM mixed;\
INSERT INTO mixed VALUES(3);\
SELECT COUNT(*) FROM mixed;"
.as_ptr(),
Some(count_callback),
&mut callback_count as *mut _ as *mut std::ffi::c_void,
ptr::null_mut(),
);
assert_eq!(rc, SQLITE_OK);
// Callback should be called 3 times total:
// 2 times for first SELECT (2 rows)
// 1 time for second SELECT (1 row with COUNT)
assert_eq!(callback_count, 3);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
fn test_exec_select_without_callback_fails() {
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);
sqlite3_exec(
db,
c"CREATE TABLE test(x INTEGER)".as_ptr(),
None,
ptr::null_mut(),
ptr::null_mut(),
);
// SELECT without callback should fail
let mut err_msg = ptr::null_mut();
let rc = sqlite3_exec(
db,
c"SELECT * FROM test".as_ptr(),
None,
ptr::null_mut(),
&mut err_msg,
);
assert_eq!(rc, SQLITE_MISUSE);
if !err_msg.is_null() {
let msg = std::ffi::CStr::from_ptr(err_msg).to_str().unwrap();
println!("Error message: {msg:?}");
assert!(msg.contains("callback") || msg.contains("prepare"));
// Free the error message
sqlite3_free(err_msg as *mut std::ffi::c_void);
}
let rc = sqlite3_close(db);
println!("RESULT: {rc}");
assert_eq!(rc, SQLITE_OK);
}
}
#[test]
fn test_exec_callback_abort() {
unsafe {
// Callback that aborts after first row
unsafe extern "C" fn abort_callback(
context: *mut std::ffi::c_void,
_n_cols: std::ffi::c_int,
_values: *mut *mut std::ffi::c_char,
_cols: *mut *mut std::ffi::c_char,
) -> std::ffi::c_int {
let count = &mut *(context as *mut i32);
*count += 1;
if *count >= 1 {
return 1; // Abort
}
0
}
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);
sqlite3_exec(
db,
c"CREATE TABLE test(x INTEGER);\
INSERT INTO test VALUES(1),(2),(3);"
.as_ptr(),
None,
ptr::null_mut(),
ptr::null_mut(),
);
let mut count = 0;
let rc = sqlite3_exec(
db,
c"SELECT x FROM test".as_ptr(),
Some(abort_callback),
&mut count as *mut _ as *mut std::ffi::c_void,
ptr::null_mut(),
);
assert_eq!(rc, SQLITE_ABORT);
assert_eq!(count, 1); // Only processed one row before aborting
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
fn test_exec_error_stops_execution() {
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 err_msg = ptr::null_mut();
// Second statement has error, third should not execute
let rc = sqlite3_exec(
db,
c"CREATE TABLE test(x INTEGER);\
INSERT INTO nonexistent VALUES(1);\
CREATE TABLE should_not_exist(y INTEGER);"
.as_ptr(),
None,
ptr::null_mut(),
&mut err_msg,
);
assert_eq!(rc, SQLITE_ERROR);
// Verify third statement didn't execute
let mut stmt = ptr::null_mut();
let check_rc = sqlite3_prepare_v2(
db,
c"SELECT name FROM sqlite_master WHERE type='table' AND name='should_not_exist'"
.as_ptr(),
-1,
&mut stmt,
ptr::null_mut(),
);
assert_eq!(check_rc, SQLITE_OK);
assert_eq!(sqlite3_step(stmt), SQLITE_DONE); // No rows = table doesn't exist
assert_eq!(sqlite3_finalize(stmt), SQLITE_OK);
if !err_msg.is_null() {
sqlite3_free(err_msg as *mut std::ffi::c_void);
}
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
fn test_exec_empty_statements() {
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);
// Multiple semicolons and whitespace should be handled gracefully
let rc = sqlite3_exec(
db,
c"CREATE TABLE test(x INTEGER);;;\n\n;\t;INSERT INTO test VALUES(1);;;".as_ptr(),
None,
ptr::null_mut(),
ptr::null_mut(),
);
assert_eq!(rc, SQLITE_OK);
// Verify both statements executed
let mut stmt = ptr::null_mut();
assert_eq!(
sqlite3_prepare_v2(
db,
c"SELECT x FROM test".as_ptr(),
-1,
&mut stmt,
ptr::null_mut(),
),
SQLITE_OK
);
assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
assert_eq!(sqlite3_column_int(stmt, 0), 1);
assert_eq!(sqlite3_finalize(stmt), SQLITE_OK);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
fn test_exec_with_comments() {
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);
// SQL comments shouldn't affect statement splitting
let rc = sqlite3_exec(
db,
c"-- This is a comment\n\
CREATE TABLE test(x INTEGER); -- inline comment\n\
INSERT INTO test VALUES(1); -- semicolon in comment ;\n\
INSERT INTO test VALUES(2) -- end with comment"
.as_ptr(),
None,
ptr::null_mut(),
ptr::null_mut(),
);
assert_eq!(rc, SQLITE_OK);
// Verify both inserts worked
let mut stmt = ptr::null_mut();
assert_eq!(
sqlite3_prepare_v2(
db,
c"SELECT COUNT(*) FROM test".as_ptr(),
-1,
&mut stmt,
ptr::null_mut(),
),
SQLITE_OK
);
assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
assert_eq!(sqlite3_column_int(stmt, 0), 2);
assert_eq!(sqlite3_finalize(stmt), SQLITE_OK);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
fn test_exec_nested_quotes() {
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);
// Mix of quote types and nesting
let rc = sqlite3_exec(
db,
c"CREATE TABLE test(x TEXT);\
INSERT INTO test VALUES('single \"double\" inside');\
INSERT INTO test VALUES(\"double 'single' inside\");\
INSERT INTO test VALUES('mix;\"quote\";types');"
.as_ptr(),
None,
ptr::null_mut(),
ptr::null_mut(),
);
assert_eq!(rc, SQLITE_OK);
// Verify values
let mut stmt = ptr::null_mut();
assert_eq!(
sqlite3_prepare_v2(
db,
c"SELECT x FROM test ORDER BY rowid".as_ptr(),
-1,
&mut stmt,
ptr::null_mut(),
),
SQLITE_OK
);
assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
let val1 = std::ffi::CStr::from_ptr(sqlite3_column_text(stmt, 0))
.to_str()
.unwrap();
assert_eq!(val1, "single \"double\" inside");
assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
let val2 = std::ffi::CStr::from_ptr(sqlite3_column_text(stmt, 0))
.to_str()
.unwrap();
assert_eq!(val2, "double 'single' inside");
assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
let val3 = std::ffi::CStr::from_ptr(sqlite3_column_text(stmt, 0))
.to_str()
.unwrap();
assert_eq!(val3, "mix;\"quote\";types");
assert_eq!(sqlite3_finalize(stmt), SQLITE_OK);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
fn test_exec_transaction_rollback() {
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);
// Test transaction rollback in multi-statement
let rc = sqlite3_exec(
db,
c"CREATE TABLE test(x INTEGER);\
BEGIN TRANSACTION;\
INSERT INTO test VALUES(1);\
INSERT INTO test VALUES(2);\
ROLLBACK;"
.as_ptr(),
None,
ptr::null_mut(),
ptr::null_mut(),
);
assert_eq!(rc, SQLITE_OK);
// Table should exist but be empty due to rollback
let mut stmt = ptr::null_mut();
assert_eq!(
sqlite3_prepare_v2(
db,
c"SELECT COUNT(*) FROM test".as_ptr(),
-1,
&mut stmt,
ptr::null_mut(),
),
SQLITE_OK
);
assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
assert_eq!(sqlite3_column_int(stmt, 0), 0); // No rows due to rollback
assert_eq!(sqlite3_finalize(stmt), SQLITE_OK);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
fn test_exec_with_pragma() {
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);
// Callback to capture pragma results
unsafe extern "C" fn pragma_callback(
context: *mut std::ffi::c_void,
_n_cols: std::ffi::c_int,
_values: *mut *mut std::ffi::c_char,
_cols: *mut *mut std::ffi::c_char,
) -> std::ffi::c_int {
let count = &mut *(context as *mut i32);
*count += 1;
0
}
let mut callback_count = 0;
// PRAGMA should be treated as DQL when it returns results
let rc = sqlite3_exec(
db,
c"CREATE TABLE test(x INTEGER);\
PRAGMA table_info(test);"
.as_ptr(),
Some(pragma_callback),
&mut callback_count as *mut _ as *mut std::ffi::c_void,
ptr::null_mut(),
);
assert_eq!(rc, SQLITE_OK);
assert!(callback_count > 0); // PRAGMA should return at least one row
// PRAGMA without callback should fail
let mut err_msg = ptr::null_mut();
let rc = sqlite3_exec(
db,
c"PRAGMA table_info(test)".as_ptr(),
None,
ptr::null_mut(),
&mut err_msg,
);
assert_eq!(rc, SQLITE_MISUSE);
if !err_msg.is_null() {
sqlite3_free(err_msg as *mut std::ffi::c_void);
}
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
fn test_exec_with_cte() {
unsafe {
// Callback that collects results
unsafe extern "C" fn exec_callback(
context: *mut std::ffi::c_void,
n_cols: std::ffi::c_int,
values: *mut *mut std::ffi::c_char,
_cols: *mut *mut std::ffi::c_char,
) -> std::ffi::c_int {
let results = &mut *(context as *mut Vec<Vec<String>>);
let mut row = Vec::new();
for i in 0..n_cols as isize {
let value_ptr = *values.offset(i);
let value = if value_ptr.is_null() {
String::from("NULL")
} else {
std::ffi::CStr::from_ptr(value_ptr)
.to_str()
.unwrap()
.to_owned()
};
row.push(value);
}
results.push(row);
0
}
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);
// CTE should be recognized as DQL
let mut results: Vec<Vec<String>> = Vec::new();
let rc = sqlite3_exec(
db,
c"CREATE TABLE test(x INTEGER);\
INSERT INTO test VALUES(1),(2),(3);\
WITH cte AS (SELECT x FROM test WHERE x > 1) SELECT * FROM cte;"
.as_ptr(),
Some(exec_callback),
&mut results as *mut _ as *mut std::ffi::c_void,
ptr::null_mut(),
);
assert_eq!(rc, SQLITE_OK);
assert_eq!(results.len(), 2); // Should get 2 and 3
assert_eq!(results[0], vec!["2"]);
assert_eq!(results[1], vec!["3"]);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
fn test_exec_with_returning_clause() {
unsafe {
// Callback for RETURNING results
unsafe extern "C" fn exec_callback(
context: *mut std::ffi::c_void,
n_cols: std::ffi::c_int,
values: *mut *mut std::ffi::c_char,
_cols: *mut *mut std::ffi::c_char,
) -> std::ffi::c_int {
let results = &mut *(context as *mut Vec<Vec<String>>);
let mut row = Vec::new();
for i in 0..n_cols as isize {
let value_ptr = *values.offset(i);
let value = if value_ptr.is_null() {
String::from("NULL")
} else {
std::ffi::CStr::from_ptr(value_ptr)
.to_str()
.unwrap()
.to_owned()
};
row.push(value);
}
results.push(row);
0
}
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 results: Vec<Vec<String>> = Vec::new();
// INSERT...RETURNING should be treated as DQL
let rc = sqlite3_exec(
db,
c"CREATE TABLE test(id INTEGER PRIMARY KEY, x INTEGER);\
INSERT INTO test(x) VALUES(42) RETURNING id, x;"
.as_ptr(),
Some(exec_callback),
&mut results as *mut _ as *mut std::ffi::c_void,
ptr::null_mut(),
);
assert_eq!(rc, SQLITE_OK);
assert_eq!(results.len(), 1);
assert_eq!(results[0][1], "42"); // x value
// RETURNING without callback should fail
let mut err_msg = ptr::null_mut();
let rc = sqlite3_exec(
db,
c"DELETE FROM test WHERE x=42 RETURNING id".as_ptr(),
None,
ptr::null_mut(),
&mut err_msg,
);
assert_eq!(rc, SQLITE_MISUSE);
if !err_msg.is_null() {
sqlite3_free(err_msg as *mut std::ffi::c_void);
}
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[cfg(not(feature = "sqlite3"))]
mod libsql_ext {