1#![allow(
12 unsafe_code,
13 unsafe_op_in_unsafe_fn,
14 clippy::borrow_as_ptr,
15 clippy::cast_sign_loss,
16 clippy::cast_possible_truncation,
17 clippy::cast_possible_wrap
18)]
19
20use std::ffi::{CStr, CString};
21use std::os::raw::{c_char, c_double, c_int, c_void};
22use std::path::{Path, PathBuf};
23use std::sync::LazyLock;
24use std::sync::Mutex;
25use std::sync::atomic::{AtomicI32, AtomicU64, AtomicUsize, Ordering};
26use std::time::{SystemTime, UNIX_EPOCH};
27
28use fsqlite::Connection;
29use fsqlite_ast::Statement;
30use fsqlite_error::{ErrorCode, FrankenError};
31use fsqlite_parser::{Parser, parse_first_statement_with_tail};
32use fsqlite_types::value::SqliteValue;
33
34pub const SQLITE_OK: c_int = ErrorCode::Ok as c_int;
37pub const SQLITE_ERROR: c_int = ErrorCode::Error as c_int;
38pub const SQLITE_INTERNAL: c_int = ErrorCode::Internal as c_int;
39pub const SQLITE_BUSY: c_int = ErrorCode::Busy as c_int;
40pub const SQLITE_NOMEM: c_int = ErrorCode::NoMem as c_int;
41pub const SQLITE_READONLY: c_int = ErrorCode::ReadOnly as c_int;
42pub const SQLITE_IOERR: c_int = ErrorCode::IoErr as c_int;
43pub const SQLITE_CORRUPT: c_int = ErrorCode::Corrupt as c_int;
44pub const SQLITE_FULL: c_int = ErrorCode::Full as c_int;
45pub const SQLITE_CANTOPEN: c_int = ErrorCode::CantOpen as c_int;
46pub const SQLITE_SCHEMA: c_int = ErrorCode::Schema as c_int;
47pub const SQLITE_TOOBIG: c_int = ErrorCode::TooBig as c_int;
48pub const SQLITE_CONSTRAINT: c_int = ErrorCode::Constraint as c_int;
49pub const SQLITE_MISMATCH: c_int = ErrorCode::Mismatch as c_int;
50pub const SQLITE_MISUSE: c_int = ErrorCode::Misuse as c_int;
51pub const SQLITE_AUTH: c_int = ErrorCode::Auth as c_int;
52pub const SQLITE_RANGE: c_int = ErrorCode::Range as c_int;
53pub const SQLITE_NOTADB: c_int = ErrorCode::NotADb as c_int;
54pub const SQLITE_ROW: c_int = ErrorCode::Row as c_int;
55pub const SQLITE_DONE: c_int = ErrorCode::Done as c_int;
56pub const SQLITE_ABORT: c_int = ErrorCode::Abort as c_int;
57
58pub const SQLITE_INTEGER: c_int = 1;
61pub const SQLITE_FLOAT: c_int = 2;
62pub const SQLITE_TEXT: c_int = 3;
63pub const SQLITE_BLOB: c_int = 4;
64pub const SQLITE_NULL: c_int = 5;
65
66static COMPAT_OPEN: AtomicU64 = AtomicU64::new(0);
69static COMPAT_CLOSE: AtomicU64 = AtomicU64::new(0);
70static COMPAT_EXEC: AtomicU64 = AtomicU64::new(0);
71static COMPAT_PREPARE: AtomicU64 = AtomicU64::new(0);
72static COMPAT_STEP: AtomicU64 = AtomicU64::new(0);
73static COMPAT_FINALIZE: AtomicU64 = AtomicU64::new(0);
74static COMPAT_COLUMN: AtomicU64 = AtomicU64::new(0);
75static COMPAT_ERRMSG: AtomicU64 = AtomicU64::new(0);
76static TEMP_DB_COUNTER: AtomicU64 = AtomicU64::new(0);
77
78#[derive(Debug, Clone)]
79pub struct CompatMetricsSnapshot {
80 pub open: u64,
81 pub close: u64,
82 pub exec: u64,
83 pub prepare: u64,
84 pub step: u64,
85 pub finalize: u64,
86 pub column: u64,
87 pub errmsg: u64,
88}
89
90impl CompatMetricsSnapshot {
91 pub fn total(&self) -> u64 {
92 self.open
93 + self.close
94 + self.exec
95 + self.prepare
96 + self.step
97 + self.finalize
98 + self.column
99 + self.errmsg
100 }
101}
102
103pub fn compat_metrics_snapshot() -> CompatMetricsSnapshot {
104 CompatMetricsSnapshot {
105 open: COMPAT_OPEN.load(Ordering::Relaxed),
106 close: COMPAT_CLOSE.load(Ordering::Relaxed),
107 exec: COMPAT_EXEC.load(Ordering::Relaxed),
108 prepare: COMPAT_PREPARE.load(Ordering::Relaxed),
109 step: COMPAT_STEP.load(Ordering::Relaxed),
110 finalize: COMPAT_FINALIZE.load(Ordering::Relaxed),
111 column: COMPAT_COLUMN.load(Ordering::Relaxed),
112 errmsg: COMPAT_ERRMSG.load(Ordering::Relaxed),
113 }
114}
115
116pub fn reset_compat_metrics() {
117 COMPAT_OPEN.store(0, Ordering::Relaxed);
118 COMPAT_CLOSE.store(0, Ordering::Relaxed);
119 COMPAT_EXEC.store(0, Ordering::Relaxed);
120 COMPAT_PREPARE.store(0, Ordering::Relaxed);
121 COMPAT_STEP.store(0, Ordering::Relaxed);
122 COMPAT_FINALIZE.store(0, Ordering::Relaxed);
123 COMPAT_COLUMN.store(0, Ordering::Relaxed);
124 COMPAT_ERRMSG.store(0, Ordering::Relaxed);
125}
126
127const DEFAULT_ERROR_MESSAGE: &str = "not an error";
130
131pub struct Sqlite3 {
135 conn: Connection,
136 temporary_path: Option<PathBuf>,
137 last_error: Mutex<CString>,
138 last_error_code: AtomicI32,
139 last_changes: AtomicI32,
140 active_statements: AtomicUsize,
141}
142
143impl Sqlite3 {
144 fn new(conn: Connection, temporary_path: Option<PathBuf>) -> Self {
145 Self {
146 conn,
147 temporary_path,
148 last_error: Mutex::new(CString::new(DEFAULT_ERROR_MESSAGE).expect("static")),
149 last_error_code: AtomicI32::new(SQLITE_OK),
150 last_changes: AtomicI32::new(0),
151 active_statements: AtomicUsize::new(0),
152 }
153 }
154
155 fn set_error(&self, err: &FrankenError) {
156 self.set_error_message_and_code(&err.to_string(), error_to_code(err));
157 }
158
159 fn set_error_message_and_code(&self, message: &str, code: c_int) {
160 if let Ok(mut guard) = self.last_error.lock() {
161 *guard = c_string_truncate_on_nul(message);
162 }
163 self.last_error_code.store(code, Ordering::Relaxed);
164 }
165
166 fn clear_error(&self) {
167 if let Ok(mut guard) = self.last_error.lock() {
168 *guard = c_string_truncate_on_nul(DEFAULT_ERROR_MESSAGE);
169 }
170 self.last_error_code.store(SQLITE_OK, Ordering::Relaxed);
171 }
172
173 fn refresh_last_changes(&self) {
174 let changes = self
175 .conn
176 .query_row("SELECT changes();")
177 .ok()
178 .and_then(|row| match row.get(0) {
179 Some(SqliteValue::Integer(n)) => Some(i64_to_c_int_saturating(*n)),
180 _ => None,
181 })
182 .unwrap_or(0);
183 self.last_changes.store(changes, Ordering::Relaxed);
184 }
185
186 fn register_statement(&self) {
187 self.active_statements.fetch_add(1, Ordering::Relaxed);
188 }
189
190 fn release_statement(&self) {
191 let _ =
192 self.active_statements
193 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |count| {
194 Some(count.saturating_sub(1))
195 });
196 }
197
198 fn active_statement_count(&self) -> usize {
199 self.active_statements.load(Ordering::Relaxed)
200 }
201}
202
203fn cleanup_temporary_database_artifacts(path: &Path) {
204 let journal_path = {
205 let mut jp = path.as_os_str().to_owned();
206 jp.push("-journal");
207 PathBuf::from(jp)
208 };
209 let wal_path = {
210 let mut wp = path.as_os_str().to_owned();
211 wp.push("-wal");
212 PathBuf::from(wp)
213 };
214 let shm_path = {
215 let mut sp = path.as_os_str().to_owned();
216 sp.push("-shm");
217 PathBuf::from(sp)
218 };
219
220 for candidate in [path, &journal_path, &wal_path, &shm_path] {
221 match std::fs::remove_file(candidate) {
222 Ok(()) => {}
223 Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
224 Err(error) => tracing::warn!(
225 target: "fsqlite.compat",
226 path = %candidate.display(),
227 error = %error,
228 "failed to remove temporary sqlite3_open database artifact"
229 ),
230 }
231 }
232}
233
234enum OpenTarget {
235 Path(String),
236 Temporary(PathBuf),
237}
238
239fn reserve_temporary_database_path() -> std::io::Result<PathBuf> {
240 let temp_dir = std::env::temp_dir();
241 for _ in 0..32 {
242 let counter = TEMP_DB_COUNTER.fetch_add(1, Ordering::Relaxed);
243 let nanos = SystemTime::now()
244 .duration_since(UNIX_EPOCH)
245 .unwrap_or_default()
246 .as_nanos();
247 let path = temp_dir.join(format!(
248 "frankensqlite-c-api-{}-{nanos}-{counter}.db",
249 std::process::id()
250 ));
251 match std::fs::OpenOptions::new()
252 .create_new(true)
253 .write(true)
254 .open(&path)
255 {
256 Ok(_) => return Ok(path),
257 Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {}
258 Err(error) => return Err(error),
259 }
260 }
261
262 Err(std::io::Error::new(
263 std::io::ErrorKind::AlreadyExists,
264 "failed to reserve unique temporary database path",
265 ))
266}
267
268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
273enum PreparedStepMode {
274 Query,
275 Execute,
276}
277
278pub struct Sqlite3Stmt {
279 db: *mut Sqlite3,
280 sql: String,
281 step_mode: PreparedStepMode,
282 rows: Option<Vec<fsqlite::Row>>,
284 cursor: usize,
286 active_row: bool,
288 last_step_code: c_int,
290 column_count: c_int,
292 text_cache: Vec<Option<Vec<u8>>>,
296}
297
298type ExecCallback = unsafe extern "C" fn(
299 parg: *mut c_void,
300 ncols: c_int,
301 values: *mut *mut c_char,
302 names: *mut *mut c_char,
303) -> c_int;
304
305fn error_to_code(err: &FrankenError) -> c_int {
308 parse_embedded_vdbe_result_code(err).unwrap_or_else(|| err.error_code() as c_int)
309}
310
311fn parse_embedded_vdbe_result_code(err: &FrankenError) -> Option<c_int> {
312 let FrankenError::Internal(message) = err else {
313 return None;
314 };
315 let suffix = message.strip_prefix("VDBE halted with code ")?;
316 let (code_text, _) = suffix.split_once(':')?;
317 code_text.parse::<c_int>().ok()
318}
319
320fn i64_to_c_int_saturating(value: i64) -> c_int {
321 if value < i64::from(c_int::MIN) {
322 c_int::MIN
323 } else if value > i64::from(c_int::MAX) {
324 c_int::MAX
325 } else {
326 value as c_int
327 }
328}
329
330fn c_string_truncate_on_nul(value: &str) -> CString {
331 let bytes = value.as_bytes();
332 let nul_index = bytes
333 .iter()
334 .position(|&byte| byte == 0)
335 .unwrap_or(bytes.len());
336
337 unsafe { CString::from_vec_unchecked(bytes[..nul_index].to_vec()) }
339}
340
341fn can_prepare_statement(statement: &Statement) -> bool {
342 matches!(
343 statement,
344 Statement::Select(_) | Statement::Insert(_) | Statement::Update(_) | Statement::Delete(_)
345 )
346}
347
348fn prepared_step_mode(statement: &Statement) -> PreparedStepMode {
349 match statement {
350 Statement::Insert(stmt) if stmt.returning.is_empty() => PreparedStepMode::Execute,
351 Statement::Update(stmt) if stmt.returning.is_empty() => PreparedStepMode::Execute,
352 Statement::Delete(stmt) if stmt.returning.is_empty() => PreparedStepMode::Execute,
353 _ => PreparedStepMode::Query,
354 }
355}
356
357struct PreparedSqlInfo {
358 consumed_sql: String,
359 tail_offset: usize,
360 step_mode: PreparedStepMode,
361 column_count: c_int,
362}
363
364fn validate_and_classify_prepared_sql(
365 conn: &Connection,
366 sql: &str,
367) -> Result<Option<PreparedSqlInfo>, FrankenError> {
368 let Some((statement, tail_offset)) =
369 parse_first_statement_with_tail(sql).map_err(|err| FrankenError::ParseError {
370 offset: err.span.start as usize,
371 detail: err.message,
372 })?
373 else {
374 return Ok(None);
375 };
376 let consumed_sql = &sql[..tail_offset];
377 let step_mode = prepared_step_mode(&statement);
378 let column_count = if can_prepare_statement(&statement) {
379 let prepared = conn.prepare(consumed_sql)?;
380 c_int::try_from(prepared.column_count()).unwrap_or(c_int::MAX)
381 } else {
382 0
383 };
384
385 Ok(Some(PreparedSqlInfo {
386 consumed_sql: consumed_sql.to_owned(),
387 tail_offset,
388 step_mode,
389 column_count,
390 }))
391}
392
393fn first_statement_tail_offset(sql: &str) -> Result<Option<usize>, FrankenError> {
394 parse_first_statement_with_tail(sql)
395 .map_err(|err| FrankenError::ParseError {
396 offset: err.span.start as usize,
397 detail: err.message,
398 })
399 .map(|parsed| parsed.map(|(_, tail_offset)| tail_offset))
400}
401
402fn best_effort_exec_callback_column_names(conn: &Connection, sql: &str) -> Option<Vec<String>> {
403 let mut parser = Parser::from_sql(sql);
404 let (statements, errors) = parser.parse_all();
405 if let Some(error) = errors.into_iter().next() {
406 tracing::warn!(
407 target: "fsqlite.compat",
408 error = %error,
409 "failed to recover sqlite3_exec callback column names"
410 );
411 return None;
412 }
413
414 let statement = statements.last()?;
415 if !can_prepare_statement(statement) {
416 return None;
417 }
418
419 match conn.prepare(&statement.to_string()) {
420 Ok(prepared) => Some(prepared.column_names().to_vec()),
421 Err(error) => {
422 tracing::warn!(
423 target: "fsqlite.compat",
424 error = %error,
425 "failed to prepare sqlite3_exec callback column metadata"
426 );
427 None
428 }
429 }
430}
431
432unsafe fn emit_exec_callback_rows(
433 handle: &Sqlite3,
434 sql: &str,
435 rows: &[fsqlite::Row],
436 callback: ExecCallback,
437 parg: *mut c_void,
438 errmsg: *mut *mut c_char,
439) -> c_int {
440 let callback_column_names = (!rows.is_empty())
441 .then(|| best_effort_exec_callback_column_names(&handle.conn, sql))
442 .flatten();
443 for row in rows {
444 let vals = row.values();
445 let ncols = vals.len() as c_int;
446
447 let mut c_values: Vec<*mut c_char> = Vec::with_capacity(vals.len());
448 let mut c_names: Vec<*mut c_char> = Vec::with_capacity(vals.len());
449 let mut owned_vals: Vec<Option<Vec<u8>>> = Vec::with_capacity(vals.len());
450 let mut owned_names: Vec<CString> = Vec::with_capacity(vals.len());
451
452 for (i, v) in vals.iter().enumerate() {
453 let col_name = callback_column_names
454 .as_ref()
455 .and_then(|names| names.get(i))
456 .cloned()
457 .unwrap_or_else(|| format!("column{i}"));
458 let cname = c_string_truncate_on_nul(&col_name);
459 c_names.push(cname.as_ptr().cast_mut());
460 owned_names.push(cname);
461
462 if matches!(v, SqliteValue::Null) {
463 c_values.push(std::ptr::null_mut());
464 owned_vals.push(None);
465 continue;
466 }
467 let mut text = sqlite_value_to_callback_bytes(v);
468 c_values.push(text.as_mut_ptr().cast());
469 owned_vals.push(Some(text));
470 }
471
472 debug_assert_eq!(owned_vals.len(), c_values.len());
473 debug_assert_eq!(owned_names.len(), c_names.len());
474 let rc = callback(parg, ncols, c_values.as_mut_ptr(), c_names.as_mut_ptr());
475 if rc != SQLITE_OK {
476 let err = FrankenError::Abort;
477 handle.set_error(&err);
478 write_error_message(errmsg, &err.to_string());
479 return SQLITE_ABORT;
480 }
481 }
482 SQLITE_OK
483}
484
485fn sqlite_value_to_callback_bytes(value: &SqliteValue) -> Vec<u8> {
486 let mut bytes = sqlite_value_to_text_bytes(value);
487 bytes.push(0);
488 bytes
489}
490
491unsafe fn cache_stmt_text_bytes(stmt: *mut Sqlite3Stmt, i_col: c_int, text: Vec<u8>) -> *const u8 {
492 let s = &mut *stmt;
493 let mut cached = text;
494 cached.push(0);
495
496 if (i_col as usize) < s.text_cache.len() {
497 s.text_cache[i_col as usize] = Some(cached);
498 return s.text_cache[i_col as usize]
499 .as_ref()
500 .map_or(std::ptr::null(), Vec::as_ptr);
501 }
502
503 while s.text_cache.len() <= i_col as usize {
504 s.text_cache.push(None);
505 }
506 s.text_cache[i_col as usize] = Some(cached);
507 s.text_cache[i_col as usize]
508 .as_ref()
509 .map_or(std::ptr::null(), Vec::as_ptr)
510}
511
512fn sqlite_value_to_text_bytes(value: &SqliteValue) -> Vec<u8> {
513 match value {
514 SqliteValue::Null => Vec::new(),
515 SqliteValue::Integer(number) => number.to_string().into_bytes(),
516 value @ SqliteValue::Float(_) => value.to_text().into_bytes(),
517 SqliteValue::Text(text) => text.as_bytes().to_vec(),
518 SqliteValue::Blob(bytes) => bytes.to_vec(),
519 }
520}
521
522unsafe fn execute_exec_batch(
523 handle: &Sqlite3,
524 sql: &str,
525 callback: Option<ExecCallback>,
526 parg: *mut c_void,
527 errmsg: *mut *mut c_char,
528) -> c_int {
529 let mut remaining = sql;
530
531 loop {
532 let trimmed = remaining.trim_start();
533 if trimmed.is_empty() {
534 handle.clear_error();
535 handle.refresh_last_changes();
536 return SQLITE_OK;
537 }
538 let statement_offset = sql.len().saturating_sub(trimmed.len());
539
540 let tail_offset = match first_statement_tail_offset(trimmed) {
541 Ok(Some(tail_offset)) => tail_offset,
542 Ok(None) => {
543 handle.clear_error();
544 handle.refresh_last_changes();
545 return SQLITE_OK;
546 }
547 Err(FrankenError::ParseError { offset, detail }) => {
548 let error = FrankenError::ParseError {
549 offset: statement_offset.saturating_add(offset),
550 detail,
551 };
552 tracing::warn!(
553 target: "fsqlite.compat",
554 error = %error,
555 "sqlite3_exec failed while parsing statement batch"
556 );
557 handle.set_error(&error);
558 write_error_message(errmsg, &error.to_string());
559 return error_to_code(&error);
560 }
561 Err(error) => {
562 tracing::warn!(
563 target: "fsqlite.compat",
564 error = %error,
565 "sqlite3_exec failed while parsing statement batch"
566 );
567 handle.set_error(&error);
568 write_error_message(errmsg, &error.to_string());
569 return error_to_code(&error);
570 }
571 };
572
573 let statement_sql = &trimmed[..tail_offset];
574 let rows = match handle.conn.query(statement_sql) {
575 Ok(rows) => rows,
576 Err(FrankenError::QueryReturnedNoRows) => Vec::new(),
577 Err(error) => {
578 tracing::warn!(
579 target: "fsqlite.compat",
580 error = %error,
581 statement_sql = %statement_sql,
582 "sqlite3_exec failed while executing statement batch"
583 );
584 handle.set_error(&error);
585 write_error_message(errmsg, &error.to_string());
586 return error_to_code(&error);
587 }
588 };
589
590 if let Some(cb) = callback {
591 let rc = emit_exec_callback_rows(handle, statement_sql, &rows, cb, parg, errmsg);
592 if rc != SQLITE_OK {
593 return rc;
594 }
595 }
596
597 remaining = &trimmed[tail_offset..];
598 }
599}
600
601unsafe fn write_error_message(errmsg: *mut *mut c_char, message: &str) {
602 if errmsg.is_null() {
603 return;
604 }
605 let cmsg = c_string_truncate_on_nul(message);
606 let len = cmsg.as_bytes_with_nul().len();
607 let buf = libc_malloc(len);
608 if !buf.is_null() {
609 std::ptr::copy_nonoverlapping(cmsg.as_ptr(), buf.cast(), len);
610 *errmsg = buf.cast();
611 }
612}
613
614#[unsafe(no_mangle)]
622pub unsafe extern "C" fn sqlite3_open(filename: *const c_char, pp_db: *mut *mut Sqlite3) -> c_int {
623 COMPAT_OPEN.fetch_add(1, Ordering::Relaxed);
624 let _span = tracing::info_span!("compat_api", api_func = "open").entered();
625
626 if pp_db.is_null() {
627 return SQLITE_MISUSE;
628 }
629
630 let open_target = if filename.is_null() {
631 OpenTarget::Path(":memory:".to_owned())
632 } else if let Ok(s) = CStr::from_ptr(filename).to_str() {
633 if s.is_empty() {
634 match reserve_temporary_database_path() {
635 Ok(path) => OpenTarget::Temporary(path),
636 Err(error) => {
637 tracing::warn!(
638 target: "fsqlite.compat",
639 error = %error,
640 "sqlite3_open failed to reserve temporary database path"
641 );
642 *pp_db = std::ptr::null_mut();
643 return SQLITE_CANTOPEN;
644 }
645 }
646 } else {
647 OpenTarget::Path(s.to_owned())
648 }
649 } else {
650 *pp_db = std::ptr::null_mut();
651 return SQLITE_CANTOPEN;
652 };
653
654 let (path, temporary_path) = match open_target {
655 OpenTarget::Path(path) => (path, None),
656 OpenTarget::Temporary(path) => {
657 let path_str = if let Some(path_str) = path.to_str() {
658 path_str.to_owned()
659 } else {
660 let _ = std::fs::remove_file(&path);
661 *pp_db = std::ptr::null_mut();
662 return SQLITE_CANTOPEN;
663 };
664 (path_str, Some(path))
665 }
666 };
667
668 tracing::info!(target: "fsqlite.compat", path = %path, "sqlite3_open");
669
670 let open_result =
671 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| Connection::open(&path)));
672
673 match open_result {
674 Ok(Ok(conn)) => {
675 let handle = Box::new(Sqlite3::new(conn, temporary_path));
676 *pp_db = Box::into_raw(handle);
677 SQLITE_OK
678 }
679 Ok(Err(e)) => {
680 tracing::warn!(target: "fsqlite.compat", error = %e, "sqlite3_open failed");
681 if let Some(path) = temporary_path {
682 let _ = std::fs::remove_file(path);
683 }
684 *pp_db = std::ptr::null_mut();
685 error_to_code(&e)
686 }
687 Err(_) => {
688 tracing::error!(target: "fsqlite.compat", path = %path, "sqlite3_open panicked");
689 if let Some(path) = temporary_path {
690 let _ = std::fs::remove_file(path);
691 }
692 *pp_db = std::ptr::null_mut();
693 SQLITE_ERROR
694 }
695 }
696}
697
698#[unsafe(no_mangle)]
706pub unsafe extern "C" fn sqlite3_close(db: *mut Sqlite3) -> c_int {
707 COMPAT_CLOSE.fetch_add(1, Ordering::Relaxed);
708 let _span = tracing::info_span!("compat_api", api_func = "close").entered();
709
710 if db.is_null() {
711 return SQLITE_OK;
712 }
713
714 tracing::info!(target: "fsqlite.compat", "sqlite3_close");
715
716 let mut handle = Box::from_raw(db);
717 if handle.active_statement_count() != 0 {
718 handle.set_error_message_and_code(
719 "unable to close due to unfinalized statements",
720 SQLITE_BUSY,
721 );
722 let _ = Box::into_raw(handle);
723 return SQLITE_BUSY;
724 }
725
726 let close_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
727 handle.conn.close_in_place()
728 }));
729
730 match close_result {
731 Ok(Ok(())) => {
732 let temporary_path = handle.temporary_path.clone();
733 drop(handle);
734 if let Some(path) = temporary_path.as_deref() {
735 cleanup_temporary_database_artifacts(path);
736 }
737 SQLITE_OK
738 }
739 Ok(Err(e)) => {
740 tracing::warn!(target: "fsqlite.compat", error = %e, "sqlite3_close failed");
741 handle.set_error(&e);
742 let code = error_to_code(&e);
743 let _ = Box::into_raw(handle);
744 code
745 }
746 Err(_) => {
747 tracing::error!(target: "fsqlite.compat", "sqlite3_close panicked");
748 let _ = Box::into_raw(handle); SQLITE_ERROR
750 }
751 }
752}
753
754#[unsafe(no_mangle)]
765pub unsafe extern "C" fn sqlite3_exec(
766 db: *mut Sqlite3,
767 sql: *const c_char,
768 callback: Option<ExecCallback>,
769 parg: *mut c_void,
770 errmsg: *mut *mut c_char,
771) -> c_int {
772 COMPAT_EXEC.fetch_add(1, Ordering::Relaxed);
773 let _span = tracing::info_span!("compat_api", api_func = "exec").entered();
774
775 if db.is_null() || sql.is_null() {
776 return SQLITE_MISUSE;
777 }
778
779 if !errmsg.is_null() {
780 *errmsg = std::ptr::null_mut();
781 }
782
783 let handle = &*db;
784 let Ok(sql_str) = CStr::from_ptr(sql).to_str() else {
785 let err = FrankenError::ParseError {
786 offset: 0,
787 detail: "SQL text is not valid UTF-8".to_owned(),
788 };
789 handle.set_error(&err);
790 write_error_message(errmsg, &err.to_string());
791 return error_to_code(&err);
792 };
793
794 tracing::info!(target: "fsqlite.compat", sql = %sql_str, "sqlite3_exec");
795
796 let exec_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
797 execute_exec_batch(handle, sql_str, callback, parg, errmsg)
798 }));
799
800 if let Ok(rc) = exec_result {
801 rc
802 } else {
803 let e = FrankenError::Internal("Rust panic during sqlite3_exec".to_owned());
804 tracing::error!(target: "fsqlite.compat", error = %e, "sqlite3_exec panicked");
805 handle.set_error(&e);
806 write_error_message(errmsg, &e.to_string());
807 error_to_code(&e)
808 }
809}
810
811#[unsafe(no_mangle)]
816pub unsafe extern "C" fn sqlite3_free(ptr: *mut c_void) {
817 if ptr.is_null() {
818 return;
819 }
820 libc_free(ptr);
823}
824
825const ALLOC_HEADER_SIZE: usize = std::mem::size_of::<usize>();
828const ALLOC_HEADER_ALIGN: usize = std::mem::align_of::<usize>();
829
830unsafe fn libc_malloc(size: usize) -> *mut u8 {
831 let Ok(layout) =
834 std::alloc::Layout::from_size_align(size + ALLOC_HEADER_SIZE, ALLOC_HEADER_ALIGN)
835 else {
836 return std::ptr::null_mut();
837 };
838 let ptr = std::alloc::alloc(layout);
839 if ptr.is_null() {
840 return ptr;
841 }
842 let size_bytes = size.to_ne_bytes();
843 std::ptr::copy_nonoverlapping(size_bytes.as_ptr(), ptr, ALLOC_HEADER_SIZE);
844 ptr.add(ALLOC_HEADER_SIZE)
845}
846
847unsafe fn libc_free(ptr: *mut c_void) {
848 if ptr.is_null() {
849 return;
850 }
851 let real_ptr = ptr.cast::<u8>().sub(ALLOC_HEADER_SIZE);
852 let mut size_bytes = [0_u8; ALLOC_HEADER_SIZE];
853 std::ptr::copy_nonoverlapping(real_ptr, size_bytes.as_mut_ptr(), ALLOC_HEADER_SIZE);
854 let size = usize::from_ne_bytes(size_bytes);
855 if let Ok(layout) =
856 std::alloc::Layout::from_size_align(size + ALLOC_HEADER_SIZE, ALLOC_HEADER_ALIGN)
857 {
858 std::alloc::dealloc(real_ptr, layout);
859 }
860}
861
862#[unsafe(no_mangle)]
873pub unsafe extern "C" fn sqlite3_prepare_v2(
874 db: *mut Sqlite3,
875 sql: *const c_char,
876 n_byte: c_int,
877 pp_stmt: *mut *mut Sqlite3Stmt,
878 pz_tail: *mut *const c_char,
879) -> c_int {
880 COMPAT_PREPARE.fetch_add(1, Ordering::Relaxed);
881 let _span = tracing::info_span!("compat_api", api_func = "prepare_v2").entered();
882
883 if db.is_null() || sql.is_null() || pp_stmt.is_null() {
884 return SQLITE_MISUSE;
885 }
886
887 *pp_stmt = std::ptr::null_mut();
888 if !pz_tail.is_null() {
889 *pz_tail = std::ptr::null();
890 }
891
892 let handle = &*db;
893 let source = if n_byte < 0 {
894 if let Ok(s) = CStr::from_ptr(sql).to_str() {
895 s
896 } else {
897 let err = FrankenError::ParseError {
898 offset: 0,
899 detail: "SQL text is not valid UTF-8".to_owned(),
900 };
901 handle.set_error(&err);
902 return error_to_code(&err);
903 }
904 } else {
905 let slice = std::slice::from_raw_parts(sql.cast::<u8>(), n_byte as usize);
906 let end = slice.iter().position(|&b| b == 0).unwrap_or(slice.len());
907 if let Ok(s) = std::str::from_utf8(&slice[..end]) {
908 s
909 } else {
910 let err = FrankenError::ParseError {
911 offset: 0,
912 detail: "SQL text is not valid UTF-8".to_owned(),
913 };
914 handle.set_error(&err);
915 return error_to_code(&err);
916 }
917 };
918
919 let source_len = source.len();
920
921 let compile_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
922 validate_and_classify_prepared_sql(&handle.conn, source)
923 }));
924
925 match compile_result {
926 Ok(Ok(Some(info))) => {
927 tracing::info!(
928 target: "fsqlite.compat",
929 sql = %info.consumed_sql,
930 "sqlite3_prepare_v2"
931 );
932 handle.clear_error();
933 let stmt = Box::new(Sqlite3Stmt {
934 db,
935 sql: info.consumed_sql,
936 step_mode: info.step_mode,
937 rows: None,
938 cursor: 0,
939 active_row: false,
940 last_step_code: SQLITE_OK,
941 column_count: info.column_count,
942 text_cache: Vec::new(),
943 });
944 handle.register_statement();
945 *pp_stmt = Box::into_raw(stmt);
946 if !pz_tail.is_null() {
947 *pz_tail = sql.add(info.tail_offset);
948 }
949 SQLITE_OK
950 }
951 Ok(Ok(None)) => {
952 handle.clear_error();
953 if !pz_tail.is_null() {
954 *pz_tail = sql.add(source_len);
955 }
956 SQLITE_OK
957 }
958 Ok(Err(e)) => {
959 tracing::warn!(target: "fsqlite.compat", error = %e, "sqlite3_prepare_v2 failed");
960 handle.set_error(&e);
961 error_to_code(&e)
962 }
963 Err(_) => {
964 let e = FrankenError::Internal("Rust panic during sqlite3_prepare_v2".to_owned());
965 tracing::error!(target: "fsqlite.compat", error = %e, "sqlite3_prepare_v2 panicked");
966 handle.set_error(&e);
967 error_to_code(&e)
968 }
969 }
970}
971
972#[unsafe(no_mangle)]
982pub unsafe extern "C" fn sqlite3_step(stmt: *mut Sqlite3Stmt) -> c_int {
983 COMPAT_STEP.fetch_add(1, Ordering::Relaxed);
984 let _span = tracing::info_span!("compat_api", api_func = "step").entered();
985
986 if stmt.is_null() {
987 return SQLITE_MISUSE;
988 }
989
990 let s = &mut *stmt;
991 let db = &*s.db;
992
993 if s.rows.is_none() {
995 tracing::info!(target: "fsqlite.compat", sql = %s.sql, "sqlite3_step (first call)");
996
997 let execute_result =
998 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| match s.step_mode {
999 PreparedStepMode::Query => db.conn.query(&s.sql).map(Some),
1000 PreparedStepMode::Execute => db.conn.execute(&s.sql).map(|_| None),
1001 }));
1002
1003 match execute_result {
1004 Ok(Ok(Some(rows))) => {
1005 db.clear_error();
1006 db.refresh_last_changes();
1007 if s.column_count == 0 {
1008 if let Some(first) = rows.first() {
1009 s.column_count = first.values().len() as c_int;
1010 }
1011 }
1012 s.rows = Some(rows);
1013 s.cursor = 0;
1014 s.active_row = false;
1015 s.last_step_code = SQLITE_OK;
1016 s.text_cache.clear();
1017 }
1018 Ok(Ok(None)) => {
1019 db.clear_error();
1020 db.refresh_last_changes();
1021 s.rows = Some(Vec::new());
1022 s.cursor = 0;
1023 s.active_row = false;
1024 s.last_step_code = SQLITE_OK;
1025 s.text_cache.clear();
1026 }
1027 Ok(Err(ref e)) if matches!(e, FrankenError::QueryReturnedNoRows) => {
1028 db.clear_error();
1029 db.refresh_last_changes();
1030 s.rows = Some(Vec::new());
1031 s.cursor = 0;
1032 s.active_row = false;
1033 s.last_step_code = SQLITE_OK;
1034 s.text_cache.clear();
1035 }
1036 Ok(Err(e)) => {
1037 tracing::warn!(target: "fsqlite.compat", error = %e, "sqlite3_step failed");
1038 s.active_row = false;
1039 s.text_cache.clear();
1040 let code = error_to_code(&e);
1041 s.last_step_code = code;
1042 db.set_error(&e);
1043 return code;
1044 }
1045 Err(_) => {
1046 let e = FrankenError::Internal("Rust panic during statement execution".to_owned());
1047 tracing::error!(target: "fsqlite.compat", error = %e, "sqlite3_step panicked");
1048 s.active_row = false;
1049 s.text_cache.clear();
1050 let code = error_to_code(&e);
1051 s.last_step_code = code;
1052 db.set_error(&e);
1053 return code;
1054 }
1055 }
1056 }
1057
1058 if let Some(ref rows) = s.rows {
1060 if s.cursor < rows.len() {
1061 let ncols = if rows.is_empty() {
1063 0
1064 } else {
1065 rows[s.cursor].values().len()
1066 };
1067 s.text_cache = vec![None; ncols];
1068
1069 s.cursor += 1;
1070 s.active_row = true;
1071 s.last_step_code = SQLITE_ROW;
1072 SQLITE_ROW
1073 } else {
1074 s.active_row = false;
1075 s.last_step_code = SQLITE_DONE;
1076 s.text_cache.clear();
1077 SQLITE_DONE
1078 }
1079 } else {
1080 s.active_row = false;
1081 s.last_step_code = SQLITE_DONE;
1082 s.text_cache.clear();
1083 SQLITE_DONE
1084 }
1085}
1086
1087#[unsafe(no_mangle)]
1095pub unsafe extern "C" fn sqlite3_finalize(stmt: *mut Sqlite3Stmt) -> c_int {
1096 COMPAT_FINALIZE.fetch_add(1, Ordering::Relaxed);
1097 let _span = tracing::info_span!("compat_api", api_func = "finalize").entered();
1098
1099 if stmt.is_null() {
1100 return SQLITE_OK;
1101 }
1102
1103 tracing::info!(target: "fsqlite.compat", "sqlite3_finalize");
1104
1105 let stmt = Box::from_raw(stmt);
1106 let rc = match stmt.last_step_code {
1107 SQLITE_ROW | SQLITE_DONE => SQLITE_OK,
1108 code => code,
1109 };
1110 if !stmt.db.is_null() {
1111 (&*stmt.db).release_statement();
1112 }
1113 drop(stmt);
1114 rc
1115}
1116
1117#[unsafe(no_mangle)]
1124pub unsafe extern "C" fn sqlite3_reset(stmt: *mut Sqlite3Stmt) -> c_int {
1125 if stmt.is_null() {
1126 return SQLITE_MISUSE;
1127 }
1128
1129 let s = &mut *stmt;
1130 let rc = match s.last_step_code {
1131 SQLITE_ROW | SQLITE_DONE => SQLITE_OK,
1132 code => code,
1133 };
1134 s.rows = None;
1135 s.cursor = 0;
1136 s.active_row = false;
1137 s.last_step_code = SQLITE_OK;
1138 s.text_cache.clear();
1139 rc
1140}
1141
1142unsafe fn current_value_ref<'a>(stmt: *const Sqlite3Stmt, i_col: c_int) -> Option<&'a SqliteValue> {
1146 if i_col < 0 {
1147 return None;
1148 }
1149 let s = &*stmt;
1150 if !s.active_row {
1151 return None;
1152 }
1153 let rows = s.rows.as_ref()?;
1154 let row_idx = s.cursor.checked_sub(1)?;
1155 let row = rows.get(row_idx)?;
1156 row.get(i_col as usize)
1157}
1158
1159#[unsafe(no_mangle)]
1164pub unsafe extern "C" fn sqlite3_column_count(stmt: *mut Sqlite3Stmt) -> c_int {
1165 COMPAT_COLUMN.fetch_add(1, Ordering::Relaxed);
1166
1167 if stmt.is_null() {
1168 return 0;
1169 }
1170
1171 (*stmt).column_count
1172}
1173
1174#[unsafe(no_mangle)]
1179pub unsafe extern "C" fn sqlite3_column_type(stmt: *mut Sqlite3Stmt, i_col: c_int) -> c_int {
1180 COMPAT_COLUMN.fetch_add(1, Ordering::Relaxed);
1181
1182 match current_value_ref(stmt, i_col) {
1183 Some(SqliteValue::Integer(_)) => SQLITE_INTEGER,
1184 Some(SqliteValue::Float(_)) => SQLITE_FLOAT,
1185 Some(SqliteValue::Text(_)) => SQLITE_TEXT,
1186 Some(SqliteValue::Blob(_)) => SQLITE_BLOB,
1187 Some(SqliteValue::Null) | None => SQLITE_NULL,
1188 }
1189}
1190
1191#[unsafe(no_mangle)]
1196pub unsafe extern "C" fn sqlite3_column_int64(stmt: *mut Sqlite3Stmt, i_col: c_int) -> i64 {
1197 COMPAT_COLUMN.fetch_add(1, Ordering::Relaxed);
1198
1199 match current_value_ref(stmt, i_col) {
1200 Some(v) => v.to_integer(),
1201 None => 0,
1202 }
1203}
1204
1205#[unsafe(no_mangle)]
1210pub unsafe extern "C" fn sqlite3_column_int(stmt: *mut Sqlite3Stmt, i_col: c_int) -> c_int {
1211 COMPAT_COLUMN.fetch_add(1, Ordering::Relaxed);
1212
1213 sqlite3_column_int64(stmt, i_col) as c_int
1214}
1215
1216#[unsafe(no_mangle)]
1221pub unsafe extern "C" fn sqlite3_column_double(stmt: *mut Sqlite3Stmt, i_col: c_int) -> c_double {
1222 COMPAT_COLUMN.fetch_add(1, Ordering::Relaxed);
1223
1224 match current_value_ref(stmt, i_col) {
1225 Some(v) => v.to_float(),
1226 None => 0.0,
1227 }
1228}
1229
1230#[unsafe(no_mangle)]
1238pub unsafe extern "C" fn sqlite3_column_text(
1239 stmt: *mut Sqlite3Stmt,
1240 i_col: c_int,
1241) -> *const c_char {
1242 COMPAT_COLUMN.fetch_add(1, Ordering::Relaxed);
1243
1244 if stmt.is_null() {
1245 return std::ptr::null();
1246 }
1247
1248 let text = match current_value_ref(stmt, i_col) {
1249 Some(SqliteValue::Null) | None => return std::ptr::null(),
1250 Some(value) => sqlite_value_to_text_bytes(value),
1251 };
1252
1253 cache_stmt_text_bytes(stmt, i_col, text).cast()
1254}
1255
1256#[unsafe(no_mangle)]
1264pub unsafe extern "C" fn sqlite3_column_blob(
1265 stmt: *mut Sqlite3Stmt,
1266 i_col: c_int,
1267) -> *const c_void {
1268 COMPAT_COLUMN.fetch_add(1, Ordering::Relaxed);
1269
1270 if stmt.is_null() {
1271 return std::ptr::null();
1272 }
1273
1274 match current_value_ref(stmt, i_col) {
1275 Some(SqliteValue::Blob(b)) => {
1276 if b.is_empty() {
1277 std::ptr::null()
1278 } else {
1279 b.as_ptr().cast()
1280 }
1281 }
1282 Some(SqliteValue::Text(s)) => {
1283 if s.is_empty() {
1284 std::ptr::null()
1285 } else {
1286 s.as_ptr().cast()
1287 }
1288 }
1289 Some(value @ (SqliteValue::Integer(_) | SqliteValue::Float(_))) => {
1290 cache_stmt_text_bytes(stmt, i_col, sqlite_value_to_text_bytes(value)).cast()
1291 }
1292 _ => std::ptr::null(),
1293 }
1294}
1295
1296#[unsafe(no_mangle)]
1301pub unsafe extern "C" fn sqlite3_column_bytes(stmt: *mut Sqlite3Stmt, i_col: c_int) -> c_int {
1302 COMPAT_COLUMN.fetch_add(1, Ordering::Relaxed);
1303
1304 if stmt.is_null() || i_col < 0 {
1305 return 0;
1306 }
1307
1308 let s = &*stmt;
1309 if !s.active_row {
1310 return 0;
1311 }
1312 if let Some(Some(text)) = s.text_cache.get(i_col as usize) {
1313 return c_int::try_from(text.len().saturating_sub(1)).unwrap_or(c_int::MAX);
1314 }
1315
1316 match current_value_ref(stmt, i_col) {
1317 Some(SqliteValue::Blob(b)) => b.len() as c_int,
1318 Some(SqliteValue::Text(s)) => s.len() as c_int,
1319 Some(v @ (SqliteValue::Integer(_) | SqliteValue::Float(_))) => v.to_text().len() as c_int,
1321 _ => 0,
1322 }
1323}
1324
1325#[unsafe(no_mangle)]
1332pub unsafe extern "C" fn sqlite3_errmsg(db: *mut Sqlite3) -> *const c_char {
1333 static DEFAULT_MSG: LazyLock<CString> =
1334 LazyLock::new(|| CString::new(DEFAULT_ERROR_MESSAGE).expect("static"));
1335
1336 COMPAT_ERRMSG.fetch_add(1, Ordering::Relaxed);
1337
1338 if db.is_null() {
1339 return DEFAULT_MSG.as_ptr();
1340 }
1341
1342 let handle = &*db;
1343 match handle.last_error.lock() {
1344 Ok(guard) => guard.as_ptr(),
1345 Err(_) => DEFAULT_MSG.as_ptr(),
1346 }
1347}
1348
1349#[unsafe(no_mangle)]
1354pub unsafe extern "C" fn sqlite3_errcode(db: *mut Sqlite3) -> c_int {
1355 if db.is_null() {
1356 return SQLITE_OK;
1357 }
1358 (&*db).last_error_code.load(Ordering::Relaxed)
1359}
1360
1361#[unsafe(no_mangle)]
1368pub unsafe extern "C" fn sqlite3_changes(db: *mut Sqlite3) -> c_int {
1369 if db.is_null() {
1370 return 0;
1371 }
1372 (&*db).last_changes.load(Ordering::Relaxed)
1373}
1374
1375#[cfg(test)]
1378mod tests {
1379 use super::*;
1380 use std::ffi::CString;
1381 use std::ptr;
1382
1383 unsafe fn open_memory() -> *mut Sqlite3 {
1385 let mut db: *mut Sqlite3 = ptr::null_mut();
1386 let path = CString::new(":memory:").unwrap();
1387 let rc = sqlite3_open(path.as_ptr(), &mut db);
1388 assert_eq!(rc, SQLITE_OK);
1389 assert!(!db.is_null());
1390 db
1391 }
1392
1393 #[test]
1394 fn test_open_close_memory() {
1395 unsafe {
1396 let db = open_memory();
1397 let rc = sqlite3_close(db);
1398 assert_eq!(rc, SQLITE_OK);
1399 }
1400 }
1401
1402 #[test]
1403 fn test_c_string_truncate_on_nul_discards_suffix() {
1404 let value = c_string_truncate_on_nul("alpha\0beta");
1405 assert_eq!(value.as_bytes(), b"alpha");
1406 }
1407
1408 #[test]
1409 fn test_open_null_filename() {
1410 unsafe {
1411 let mut db: *mut Sqlite3 = ptr::null_mut();
1412 let rc = sqlite3_open(ptr::null(), &mut db);
1413 assert_eq!(rc, SQLITE_OK);
1414 assert!(!db.is_null());
1415 sqlite3_close(db);
1416 }
1417 }
1418
1419 #[test]
1420 fn test_open_empty_filename() {
1421 unsafe {
1422 let mut db: *mut Sqlite3 = ptr::null_mut();
1423 let path = CString::new("").unwrap();
1424 let rc = sqlite3_open(path.as_ptr(), &mut db);
1425 assert_eq!(rc, SQLITE_OK);
1426 assert!(!db.is_null());
1427 let temp_path = (&*db)
1428 .temporary_path
1429 .clone()
1430 .expect("empty filename should create a temporary database");
1431 assert!(
1432 temp_path.exists(),
1433 "temporary database file should exist while handle is open"
1434 );
1435 assert_eq!(sqlite3_close(db), SQLITE_OK);
1436 assert!(
1437 !temp_path.exists(),
1438 "temporary database file should be removed on close"
1439 );
1440 }
1441 }
1442
1443 #[test]
1444 fn test_cleanup_temporary_database_artifacts_removes_sidecars() {
1445 let dir = tempfile::TempDir::new().unwrap();
1446 let path = dir.path().join("temp.db");
1447 let journal_path = PathBuf::from(format!("{}-journal", path.display()));
1448 let wal_path = PathBuf::from(format!("{}-wal", path.display()));
1449 let shm_path = PathBuf::from(format!("{}-shm", path.display()));
1450
1451 std::fs::write(&path, b"db").unwrap();
1452 std::fs::write(&journal_path, b"journal").unwrap();
1453 std::fs::write(&wal_path, b"wal").unwrap();
1454 std::fs::write(&shm_path, b"shm").unwrap();
1455
1456 cleanup_temporary_database_artifacts(&path);
1457
1458 assert!(!path.exists());
1459 assert!(!journal_path.exists());
1460 assert!(!wal_path.exists());
1461 assert!(!shm_path.exists());
1462 }
1463
1464 #[test]
1465 fn test_close_null() {
1466 unsafe {
1467 let rc = sqlite3_close(ptr::null_mut());
1468 assert_eq!(rc, SQLITE_OK);
1469 }
1470 }
1471
1472 #[test]
1473 fn test_exec_create_insert_select() {
1474 unsafe {
1475 unsafe extern "C" fn count_cb(
1476 parg: *mut c_void,
1477 _ncols: c_int,
1478 _values: *mut *mut c_char,
1479 _names: *mut *mut c_char,
1480 ) -> c_int {
1481 let counter = &*(parg.cast::<AtomicU64>());
1482 counter.fetch_add(1, Ordering::Relaxed);
1483 0
1484 }
1485
1486 let db = open_memory();
1487
1488 let sql = CString::new("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT);").unwrap();
1489 let rc = sqlite3_exec(db, sql.as_ptr(), None, ptr::null_mut(), ptr::null_mut());
1490 assert_eq!(rc, SQLITE_OK);
1491
1492 let sql = CString::new("INSERT INTO t1 VALUES(1, 'alice');").unwrap();
1493 let rc = sqlite3_exec(db, sql.as_ptr(), None, ptr::null_mut(), ptr::null_mut());
1494 assert_eq!(rc, SQLITE_OK);
1495
1496 let sql = CString::new("INSERT INTO t1 VALUES(2, 'bob');").unwrap();
1497 let rc = sqlite3_exec(db, sql.as_ptr(), None, ptr::null_mut(), ptr::null_mut());
1498 assert_eq!(rc, SQLITE_OK);
1499
1500 let row_count = AtomicU64::new(0);
1502 let sql = CString::new("SELECT * FROM t1;").unwrap();
1503 let rc = sqlite3_exec(
1504 db,
1505 sql.as_ptr(),
1506 Some(count_cb),
1507 std::ptr::from_ref::<AtomicU64>(&row_count)
1508 .cast_mut()
1509 .cast(),
1510 ptr::null_mut(),
1511 );
1512 assert_eq!(rc, SQLITE_OK);
1513 assert_eq!(row_count.load(Ordering::Relaxed), 2);
1514
1515 sqlite3_close(db);
1516 }
1517 }
1518
1519 #[test]
1520 fn test_exec_error_sets_errmsg() {
1521 unsafe {
1522 let db = open_memory();
1523
1524 let mut errmsg: *mut c_char = ptr::null_mut();
1525 let sql = CString::new("SELEC invalid;").unwrap();
1526 let rc = sqlite3_exec(db, sql.as_ptr(), None, ptr::null_mut(), &mut errmsg);
1527 assert_ne!(rc, SQLITE_OK);
1528
1529 if !errmsg.is_null() {
1530 let msg = CStr::from_ptr(errmsg).to_string_lossy();
1531 assert!(!msg.is_empty());
1532 sqlite3_free(errmsg.cast());
1533 }
1534
1535 sqlite3_close(db);
1536 }
1537 }
1538
1539 #[test]
1540 fn test_prepare_step_finalize() {
1541 unsafe {
1542 let db = open_memory();
1543
1544 let sql = CString::new(
1546 "CREATE TABLE t1(a INTEGER, b TEXT); INSERT INTO t1 VALUES(10, 'hello'); INSERT INTO t1 VALUES(20, 'world');",
1547 ).unwrap();
1548 sqlite3_exec(db, sql.as_ptr(), None, ptr::null_mut(), ptr::null_mut());
1549
1550 let sql = CString::new("SELECT a, b FROM t1;").unwrap();
1552 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
1553 let rc = sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut());
1554 assert_eq!(rc, SQLITE_OK);
1555 assert!(!stmt.is_null());
1556
1557 let rc = sqlite3_step(stmt);
1559 assert_eq!(rc, SQLITE_ROW);
1560 assert_eq!(sqlite3_column_count(stmt), 2);
1561 assert_eq!(sqlite3_column_int64(stmt, 0), 10);
1562 assert_eq!(sqlite3_column_type(stmt, 0), SQLITE_INTEGER);
1563
1564 let text = sqlite3_column_text(stmt, 1);
1565 assert!(!text.is_null());
1566 assert_eq!(CStr::from_ptr(text).to_str().unwrap(), "hello");
1567 assert_eq!(sqlite3_column_type(stmt, 1), SQLITE_TEXT);
1568
1569 let rc = sqlite3_step(stmt);
1570 assert_eq!(rc, SQLITE_ROW);
1571 assert_eq!(sqlite3_column_int64(stmt, 0), 20);
1572
1573 let text = sqlite3_column_text(stmt, 1);
1574 assert!(!text.is_null());
1575 assert_eq!(CStr::from_ptr(text).to_str().unwrap(), "world");
1576
1577 let rc = sqlite3_step(stmt);
1578 assert_eq!(rc, SQLITE_DONE);
1579
1580 let rc = sqlite3_finalize(stmt);
1581 assert_eq!(rc, SQLITE_OK);
1582
1583 sqlite3_close(db);
1584 }
1585 }
1586
1587 #[test]
1588 #[allow(clippy::approx_constant)]
1589 fn test_column_type_variants() {
1590 unsafe {
1591 let db = open_memory();
1592
1593 let sql = CString::new("SELECT 42, 3.14, 'text', X'CAFE', NULL;").unwrap();
1594 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
1595 sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut());
1596
1597 let rc = sqlite3_step(stmt);
1598 assert_eq!(rc, SQLITE_ROW);
1599
1600 assert_eq!(sqlite3_column_type(stmt, 0), SQLITE_INTEGER);
1601 assert_eq!(sqlite3_column_type(stmt, 1), SQLITE_FLOAT);
1602 assert_eq!(sqlite3_column_type(stmt, 2), SQLITE_TEXT);
1603 assert_eq!(sqlite3_column_type(stmt, 3), SQLITE_BLOB);
1604 assert_eq!(sqlite3_column_type(stmt, 4), SQLITE_NULL);
1605
1606 assert_eq!(sqlite3_column_int64(stmt, 0), 42);
1607 let f = sqlite3_column_double(stmt, 1);
1608 assert!((f - 3.14).abs() < 0.001);
1609
1610 let text = sqlite3_column_text(stmt, 2);
1611 assert_eq!(CStr::from_ptr(text).to_str().unwrap(), "text");
1612
1613 let blob_bytes = sqlite3_column_bytes(stmt, 3);
1614 assert_eq!(blob_bytes, 2); let blob_ptr = sqlite3_column_blob(stmt, 3);
1617 assert!(!blob_ptr.is_null());
1618
1619 sqlite3_finalize(stmt);
1620 sqlite3_close(db);
1621 }
1622 }
1623
1624 #[test]
1625 fn test_column_int_32bit() {
1626 unsafe {
1627 let db = open_memory();
1628
1629 let sql = CString::new("SELECT 42;").unwrap();
1630 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
1631 sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut());
1632
1633 let rc = sqlite3_step(stmt);
1634 assert_eq!(rc, SQLITE_ROW);
1635 assert_eq!(sqlite3_column_int(stmt, 0), 42);
1636
1637 sqlite3_finalize(stmt);
1638 sqlite3_close(db);
1639 }
1640 }
1641
1642 #[test]
1643 fn test_column_coercion() {
1644 unsafe {
1645 let db = open_memory();
1646
1647 let sql = CString::new("SELECT 42, '123', 3.7;").unwrap();
1649 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
1650 sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut());
1651
1652 sqlite3_step(stmt);
1653
1654 let f = sqlite3_column_double(stmt, 0);
1656 assert!((f - 42.0).abs() < 0.001);
1657
1658 assert_eq!(sqlite3_column_int64(stmt, 1), 123);
1660
1661 assert_eq!(sqlite3_column_int64(stmt, 2), 3);
1663
1664 let text = sqlite3_column_text(stmt, 0);
1666 assert_eq!(CStr::from_ptr(text).to_str().unwrap(), "42");
1667
1668 sqlite3_finalize(stmt);
1669 sqlite3_close(db);
1670 }
1671 }
1672
1673 #[test]
1674 fn test_column_blob_coerces_numeric_values_to_text_bytes() {
1675 unsafe {
1676 let db = open_memory();
1677
1678 let sql = CString::new("SELECT 42, 3.5;").unwrap();
1679 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
1680 sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut());
1681
1682 assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
1683
1684 let int_blob = sqlite3_column_blob(stmt, 0);
1685 assert!(!int_blob.is_null());
1686 let int_len = sqlite3_column_bytes(stmt, 0) as usize;
1687 assert_eq!(
1688 std::slice::from_raw_parts(int_blob.cast::<u8>(), int_len),
1689 b"42"
1690 );
1691
1692 let float_blob = sqlite3_column_blob(stmt, 1);
1693 assert!(!float_blob.is_null());
1694 let float_len = sqlite3_column_bytes(stmt, 1) as usize;
1695 assert_eq!(
1696 std::slice::from_raw_parts(float_blob.cast::<u8>(), float_len),
1697 b"3.5"
1698 );
1699
1700 sqlite3_finalize(stmt);
1701 sqlite3_close(db);
1702 }
1703 }
1704
1705 #[test]
1706 fn test_errmsg_default() {
1707 unsafe {
1708 let db = open_memory();
1709
1710 let msg = sqlite3_errmsg(db);
1711 assert!(!msg.is_null());
1712 let s = CStr::from_ptr(msg).to_str().unwrap();
1713 assert_eq!(s, "not an error");
1714
1715 sqlite3_close(db);
1716 }
1717 }
1718
1719 #[test]
1720 fn test_errmsg_after_error() {
1721 unsafe {
1722 let db = open_memory();
1723
1724 let sql = CString::new("SELECT * FROM nonexistent;").unwrap();
1725 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
1726 let rc = sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut());
1727 assert_ne!(rc, SQLITE_OK);
1728
1729 let msg = sqlite3_errmsg(db);
1730 assert!(!msg.is_null());
1731 let s = CStr::from_ptr(msg).to_string_lossy();
1732 assert!(
1733 s.contains("no such table") || s.contains("nonexistent"),
1734 "expected error about missing table, got: {s}"
1735 );
1736
1737 sqlite3_close(db);
1738 }
1739 }
1740
1741 #[test]
1742 fn test_errcode_tracks_last_result() {
1743 unsafe {
1744 let db = open_memory();
1745
1746 assert_eq!(sqlite3_errcode(db), SQLITE_OK);
1747
1748 let sql = CString::new("SELEC invalid;").unwrap();
1749 let rc = sqlite3_exec(db, sql.as_ptr(), None, ptr::null_mut(), ptr::null_mut());
1750 assert_ne!(rc, SQLITE_OK);
1751 assert_eq!(sqlite3_errcode(db), rc);
1752
1753 let sql = CString::new("SELECT 1;").unwrap();
1754 assert_eq!(
1755 sqlite3_exec(db, sql.as_ptr(), None, ptr::null_mut(), ptr::null_mut()),
1756 SQLITE_OK
1757 );
1758 assert_eq!(sqlite3_errcode(db), SQLITE_OK);
1759
1760 sqlite3_close(db);
1761 }
1762 }
1763
1764 #[test]
1765 fn test_errmsg_null_db() {
1766 unsafe {
1767 let msg = sqlite3_errmsg(ptr::null_mut());
1768 assert!(!msg.is_null());
1769 let s = CStr::from_ptr(msg).to_str().unwrap();
1770 assert_eq!(s, "not an error");
1771 }
1772 }
1773
1774 #[test]
1775 fn test_finalize_null() {
1776 unsafe {
1777 let rc = sqlite3_finalize(ptr::null_mut());
1778 assert_eq!(rc, SQLITE_OK);
1779 }
1780 }
1781
1782 #[test]
1783 fn test_reset_and_restep() {
1784 unsafe {
1785 let db = open_memory();
1786
1787 let sql = CString::new("SELECT 1, 2, 3;").unwrap();
1788 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
1789 sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut());
1790
1791 assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
1793 assert_eq!(sqlite3_column_int64(stmt, 0), 1);
1794 assert_eq!(sqlite3_step(stmt), SQLITE_DONE);
1795
1796 assert_eq!(sqlite3_reset(stmt), SQLITE_OK);
1798 assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
1799 assert_eq!(sqlite3_column_int64(stmt, 0), 1);
1800 assert_eq!(sqlite3_step(stmt), SQLITE_DONE);
1801
1802 sqlite3_finalize(stmt);
1803 sqlite3_close(db);
1804 }
1805 }
1806
1807 #[test]
1808 fn test_prepare_empty_sql() {
1809 unsafe {
1810 let db = open_memory();
1811
1812 let sql = CString::new(" ").unwrap();
1813 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
1814 let rc = sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut());
1815 assert_eq!(rc, SQLITE_OK);
1816 assert!(stmt.is_null()); sqlite3_close(db);
1819 }
1820 }
1821
1822 #[test]
1823 fn test_reset_returns_last_error_code_once() {
1824 unsafe {
1825 let db = open_memory();
1826
1827 let setup =
1828 CString::new("CREATE TABLE t(x INTEGER PRIMARY KEY); INSERT INTO t VALUES(1);")
1829 .unwrap();
1830 assert_eq!(
1831 sqlite3_exec(db, setup.as_ptr(), None, ptr::null_mut(), ptr::null_mut()),
1832 SQLITE_OK
1833 );
1834
1835 let sql = CString::new("INSERT INTO t VALUES(1);").unwrap();
1836 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
1837 assert_eq!(
1838 sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut()),
1839 SQLITE_OK
1840 );
1841
1842 let step_rc = sqlite3_step(stmt);
1843 assert_ne!(step_rc, SQLITE_OK);
1844 assert_eq!(sqlite3_reset(stmt), step_rc);
1845 assert_eq!(sqlite3_reset(stmt), SQLITE_OK);
1846
1847 sqlite3_finalize(stmt);
1848 sqlite3_close(db);
1849 }
1850 }
1851
1852 #[test]
1853 fn test_finalize_returns_last_error_code() {
1854 unsafe {
1855 let db = open_memory();
1856
1857 let setup =
1858 CString::new("CREATE TABLE t(x INTEGER PRIMARY KEY); INSERT INTO t VALUES(1);")
1859 .unwrap();
1860 assert_eq!(
1861 sqlite3_exec(db, setup.as_ptr(), None, ptr::null_mut(), ptr::null_mut()),
1862 SQLITE_OK
1863 );
1864
1865 let sql = CString::new("INSERT INTO t VALUES(1);").unwrap();
1866 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
1867 assert_eq!(
1868 sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut()),
1869 SQLITE_OK
1870 );
1871
1872 let step_rc = sqlite3_step(stmt);
1873 assert_eq!(step_rc, SQLITE_CONSTRAINT);
1874 assert_eq!(sqlite3_finalize(stmt), step_rc);
1875 assert_eq!(sqlite3_close(db), SQLITE_OK);
1876 }
1877 }
1878
1879 #[test]
1880 fn test_prepare_uses_first_statement_and_sets_tail() {
1881 unsafe {
1882 let db = open_memory();
1883
1884 let sql = CString::new("SELECT 99; SELECT 100;").unwrap();
1885 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
1886 let mut tail: *const c_char = ptr::null();
1887 let rc = sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, &mut tail);
1888 assert_eq!(rc, SQLITE_OK);
1889 assert!(!stmt.is_null());
1890 assert!(!tail.is_null());
1891
1892 assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
1893 assert_eq!(sqlite3_column_int64(stmt, 0), 99);
1894 assert_eq!(CStr::from_ptr(tail).to_str().unwrap(), " SELECT 100;");
1895 sqlite3_finalize(stmt);
1896
1897 let mut tail_stmt: *mut Sqlite3Stmt = ptr::null_mut();
1898 assert_eq!(
1899 sqlite3_prepare_v2(db, tail, -1, &mut tail_stmt, ptr::null_mut()),
1900 SQLITE_OK
1901 );
1902 assert!(!tail_stmt.is_null());
1903 assert_eq!(sqlite3_step(tail_stmt), SQLITE_ROW);
1904 assert_eq!(sqlite3_column_int64(tail_stmt, 0), 100);
1905 assert_eq!(sqlite3_step(tail_stmt), SQLITE_DONE);
1906
1907 sqlite3_finalize(tail_stmt);
1908 sqlite3_close(db);
1909 }
1910 }
1911
1912 #[test]
1913 fn test_prepare_trigger_consumes_full_trigger_statement() {
1914 unsafe {
1915 let db = open_memory();
1916
1917 let setup =
1918 CString::new("CREATE TABLE t(id INTEGER); CREATE TABLE audit(msg TEXT);").unwrap();
1919 assert_eq!(
1920 sqlite3_exec(db, setup.as_ptr(), None, ptr::null_mut(), ptr::null_mut()),
1921 SQLITE_OK
1922 );
1923
1924 let sql = CString::new(
1925 "CREATE TRIGGER trg AFTER INSERT ON t BEGIN INSERT INTO audit VALUES('first'); INSERT INTO audit VALUES('second'); END; SELECT 1;",
1926 )
1927 .unwrap();
1928 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
1929 let mut tail: *const c_char = ptr::null();
1930 assert_eq!(
1931 sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, &mut tail),
1932 SQLITE_OK
1933 );
1934 assert!(!stmt.is_null());
1935 assert!(!tail.is_null());
1936
1937 assert_eq!(sqlite3_step(stmt), SQLITE_DONE);
1938 assert_eq!(CStr::from_ptr(tail).to_str().unwrap(), " SELECT 1;");
1939 sqlite3_finalize(stmt);
1940
1941 let fire = CString::new("INSERT INTO t VALUES(1);").unwrap();
1942 assert_eq!(
1943 sqlite3_exec(db, fire.as_ptr(), None, ptr::null_mut(), ptr::null_mut()),
1944 SQLITE_OK
1945 );
1946
1947 let verify = CString::new("SELECT COUNT(*) FROM audit;").unwrap();
1948 let mut verify_stmt: *mut Sqlite3Stmt = ptr::null_mut();
1949 assert_eq!(
1950 sqlite3_prepare_v2(db, verify.as_ptr(), -1, &mut verify_stmt, ptr::null_mut()),
1951 SQLITE_OK
1952 );
1953 assert_eq!(sqlite3_step(verify_stmt), SQLITE_ROW);
1954 assert_eq!(sqlite3_column_int64(verify_stmt, 0), 2);
1955
1956 sqlite3_finalize(verify_stmt);
1957 sqlite3_close(db);
1958 }
1959 }
1960
1961 #[test]
1962 fn test_prepare_rejects_adjacent_statements_without_separator() {
1963 unsafe {
1964 let db = open_memory();
1965
1966 let sql = CString::new("SELECT 1 SELECT 2").unwrap();
1967 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
1968 let rc = sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut());
1969 assert_ne!(rc, SQLITE_OK);
1970 assert!(stmt.is_null());
1971
1972 let msg = CStr::from_ptr(sqlite3_errmsg(db)).to_string_lossy();
1973 assert!(
1974 msg.contains("separator") || msg.contains("unexpected token"),
1975 "unexpected error: {msg}"
1976 );
1977
1978 sqlite3_close(db);
1979 }
1980 }
1981
1982 #[test]
1983 fn test_prepare_with_n_byte() {
1984 unsafe {
1985 let db = open_memory();
1986
1987 let full_sql = "SELECT 99; SELECT 100;";
1989 let sql = CString::new(full_sql).unwrap();
1990 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
1991 let mut tail: *const c_char = ptr::null();
1992
1993 let rc = sqlite3_prepare_v2(db, sql.as_ptr(), 10, &mut stmt, &mut tail);
1995 assert_eq!(rc, SQLITE_OK);
1996 assert!(!stmt.is_null());
1997
1998 assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
1999 assert_eq!(sqlite3_column_int64(stmt, 0), 99);
2000
2001 sqlite3_finalize(stmt);
2002 sqlite3_close(db);
2003 }
2004 }
2005
2006 #[test]
2007 fn test_close_with_unfinalized_statement_returns_busy() {
2008 unsafe {
2009 let db = open_memory();
2010
2011 let sql = CString::new("SELECT 1;").unwrap();
2012 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
2013 assert_eq!(
2014 sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut()),
2015 SQLITE_OK
2016 );
2017 assert!(!stmt.is_null());
2018
2019 let rc = sqlite3_close(db);
2020 assert_eq!(rc, SQLITE_BUSY);
2021 assert_eq!(sqlite3_errcode(db), SQLITE_BUSY);
2022 let msg = CStr::from_ptr(sqlite3_errmsg(db)).to_str().unwrap();
2023 assert!(msg.contains("unfinalized statements"));
2024
2025 assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
2026 assert_eq!(sqlite3_column_int64(stmt, 0), 1);
2027 assert_eq!(sqlite3_finalize(stmt), SQLITE_OK);
2028 assert_eq!(sqlite3_close(db), SQLITE_OK);
2029 }
2030 }
2031
2032 #[test]
2033 fn test_column_count_preserved_for_empty_result_and_reset() {
2034 unsafe {
2035 let db = open_memory();
2036
2037 let sql = CString::new("SELECT 1 WHERE 0;").unwrap();
2038 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
2039 assert_eq!(
2040 sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut()),
2041 SQLITE_OK
2042 );
2043 assert!(!stmt.is_null());
2044
2045 assert_eq!(sqlite3_column_count(stmt), 1);
2046 assert_eq!(sqlite3_step(stmt), SQLITE_DONE);
2047 assert_eq!(sqlite3_column_count(stmt), 1);
2048
2049 assert_eq!(sqlite3_reset(stmt), SQLITE_OK);
2050 assert_eq!(sqlite3_column_count(stmt), 1);
2051
2052 sqlite3_finalize(stmt);
2053 sqlite3_close(db);
2054 }
2055 }
2056
2057 #[test]
2058 fn test_prepared_insert_steps_through_execute_path() {
2059 unsafe {
2060 let db = open_memory();
2061
2062 let setup = CString::new("CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT);").unwrap();
2063 assert_eq!(
2064 sqlite3_exec(db, setup.as_ptr(), None, ptr::null_mut(), ptr::null_mut()),
2065 SQLITE_OK
2066 );
2067
2068 let sql = CString::new("INSERT INTO t VALUES(1, 'a');").unwrap();
2069 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
2070 assert_eq!(
2071 sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut()),
2072 SQLITE_OK
2073 );
2074 assert!(!stmt.is_null());
2075
2076 assert_eq!(sqlite3_step(stmt), SQLITE_DONE);
2077 assert_eq!(sqlite3_changes(db), 1);
2078 sqlite3_finalize(stmt);
2079
2080 let verify = CString::new("SELECT COUNT(*) FROM t;").unwrap();
2081 let mut verify_stmt: *mut Sqlite3Stmt = ptr::null_mut();
2082 assert_eq!(
2083 sqlite3_prepare_v2(db, verify.as_ptr(), -1, &mut verify_stmt, ptr::null_mut()),
2084 SQLITE_OK
2085 );
2086 assert_eq!(sqlite3_step(verify_stmt), SQLITE_ROW);
2087 assert_eq!(sqlite3_column_int64(verify_stmt, 0), 1);
2088
2089 sqlite3_finalize(verify_stmt);
2090 sqlite3_close(db);
2091 }
2092 }
2093
2094 #[test]
2095 fn test_column_out_of_range() {
2096 unsafe {
2097 let db = open_memory();
2098
2099 let sql = CString::new("SELECT 1;").unwrap();
2100 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
2101 sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut());
2102 sqlite3_step(stmt);
2103
2104 assert_eq!(sqlite3_column_type(stmt, 99), SQLITE_NULL);
2106 assert_eq!(sqlite3_column_int64(stmt, 99), 0);
2107 assert!((sqlite3_column_double(stmt, 99)).abs() < 0.001);
2108 assert!(sqlite3_column_text(stmt, 99).is_null());
2109
2110 sqlite3_finalize(stmt);
2111 sqlite3_close(db);
2112 }
2113 }
2114
2115 #[test]
2116 fn test_column_accessors_clear_after_done() {
2117 unsafe {
2118 let db = open_memory();
2119
2120 let sql = CString::new("SELECT 42, 'hello';").unwrap();
2121 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
2122 sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut());
2123
2124 assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
2125 let text = sqlite3_column_text(stmt, 1);
2126 assert!(!text.is_null());
2127 assert_eq!(CStr::from_ptr(text).to_str().unwrap(), "hello");
2128 assert_eq!(sqlite3_column_bytes(stmt, 1), 5);
2129
2130 assert_eq!(sqlite3_step(stmt), SQLITE_DONE);
2131 assert_eq!(sqlite3_column_type(stmt, 0), SQLITE_NULL);
2132 assert_eq!(sqlite3_column_int64(stmt, 0), 0);
2133 assert!((sqlite3_column_double(stmt, 0)).abs() < 0.001);
2134 assert!(sqlite3_column_text(stmt, 1).is_null());
2135 assert!(sqlite3_column_blob(stmt, 1).is_null());
2136 assert_eq!(sqlite3_column_bytes(stmt, 1), 0);
2137
2138 sqlite3_finalize(stmt);
2139 sqlite3_close(db);
2140 }
2141 }
2142
2143 #[test]
2144 fn test_exec_callback_abort() {
2145 unsafe {
2146 unsafe extern "C" fn abort_cb(
2147 _parg: *mut c_void,
2148 _ncols: c_int,
2149 _values: *mut *mut c_char,
2150 _names: *mut *mut c_char,
2151 ) -> c_int {
2152 1 }
2154
2155 let db = open_memory();
2156
2157 let sql = CString::new(
2158 "CREATE TABLE t1(x); INSERT INTO t1 VALUES(1); INSERT INTO t1 VALUES(2);",
2159 )
2160 .unwrap();
2161 sqlite3_exec(db, sql.as_ptr(), None, ptr::null_mut(), ptr::null_mut());
2162
2163 let sql = CString::new("SELECT * FROM t1;").unwrap();
2164 let rc = sqlite3_exec(
2165 db,
2166 sql.as_ptr(),
2167 Some(abort_cb),
2168 ptr::null_mut(),
2169 ptr::null_mut(),
2170 );
2171 assert_eq!(rc, SQLITE_ABORT);
2172
2173 sqlite3_close(db);
2174 }
2175 }
2176
2177 #[test]
2178 fn test_exec_callback_uses_actual_column_names() {
2179 unsafe {
2180 unsafe extern "C" fn capture_names_cb(
2181 parg: *mut c_void,
2182 ncols: c_int,
2183 _values: *mut *mut c_char,
2184 names: *mut *mut c_char,
2185 ) -> c_int {
2186 let out = &mut *parg.cast::<Vec<String>>();
2187 let names =
2188 std::slice::from_raw_parts(names.cast::<*const c_char>(), ncols as usize);
2189 out.extend(
2190 names
2191 .iter()
2192 .map(|ptr| CStr::from_ptr(*ptr).to_str().unwrap().to_owned()),
2193 );
2194 SQLITE_OK
2195 }
2196
2197 let db = open_memory();
2198 let mut captured_names: Vec<String> = Vec::new();
2199
2200 let sql = CString::new("SELECT 1 AS alpha, 2 AS beta;").unwrap();
2201 assert_eq!(
2202 sqlite3_exec(
2203 db,
2204 sql.as_ptr(),
2205 Some(capture_names_cb),
2206 std::ptr::from_mut(&mut captured_names).cast(),
2207 ptr::null_mut(),
2208 ),
2209 SQLITE_OK
2210 );
2211 assert_eq!(captured_names, vec!["alpha", "beta"]);
2212
2213 sqlite3_close(db);
2214 }
2215 }
2216
2217 #[test]
2218 fn test_exec_callback_runs_for_each_query_statement_in_batch() {
2219 unsafe {
2220 #[derive(Debug, PartialEq, Eq)]
2221 struct CallbackRow {
2222 names: Vec<String>,
2223 values: Vec<Option<String>>,
2224 }
2225
2226 unsafe extern "C" fn capture_rows_cb(
2227 parg: *mut c_void,
2228 ncols: c_int,
2229 values: *mut *mut c_char,
2230 names: *mut *mut c_char,
2231 ) -> c_int {
2232 let out = &mut *parg.cast::<Vec<CallbackRow>>();
2233 let names =
2234 std::slice::from_raw_parts(names.cast::<*const c_char>(), ncols as usize);
2235 let values =
2236 std::slice::from_raw_parts(values.cast::<*const c_char>(), ncols as usize);
2237 out.push(CallbackRow {
2238 names: names
2239 .iter()
2240 .map(|ptr| CStr::from_ptr(*ptr).to_str().unwrap().to_owned())
2241 .collect(),
2242 values: values
2243 .iter()
2244 .map(|ptr| {
2245 (!ptr.is_null())
2246 .then(|| CStr::from_ptr(*ptr).to_str().unwrap().to_owned())
2247 })
2248 .collect(),
2249 });
2250 SQLITE_OK
2251 }
2252
2253 let db = open_memory();
2254 let mut callback_rows: Vec<CallbackRow> = Vec::new();
2255
2256 let sql = CString::new("SELECT 1 AS alpha; SELECT 2 AS beta;").unwrap();
2257 assert_eq!(
2258 sqlite3_exec(
2259 db,
2260 sql.as_ptr(),
2261 Some(capture_rows_cb),
2262 std::ptr::from_mut(&mut callback_rows).cast(),
2263 ptr::null_mut(),
2264 ),
2265 SQLITE_OK
2266 );
2267 assert_eq!(
2268 callback_rows,
2269 vec![
2270 CallbackRow {
2271 names: vec!["alpha".to_owned()],
2272 values: vec![Some("1".to_owned())],
2273 },
2274 CallbackRow {
2275 names: vec!["beta".to_owned()],
2276 values: vec![Some("2".to_owned())],
2277 },
2278 ]
2279 );
2280
2281 sqlite3_close(db);
2282 }
2283 }
2284
2285 #[test]
2286 fn test_column_text_preserves_embedded_nul_bytes() {
2287 unsafe {
2288 let db = open_memory();
2289 (*db).conn.execute("CREATE TABLE t(v TEXT)").unwrap();
2290 (*db)
2291 .conn
2292 .execute_with_params(
2293 "INSERT INTO t(v) VALUES (?)",
2294 &[SqliteValue::Text(fsqlite_types::SmallText::from("a\0b"))],
2295 )
2296 .unwrap();
2297
2298 let sql = CString::new("SELECT v FROM t;").unwrap();
2299 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
2300 assert_eq!(
2301 sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut()),
2302 SQLITE_OK
2303 );
2304 assert_eq!(sqlite3_step(stmt), SQLITE_ROW);
2305
2306 let text = sqlite3_column_text(stmt, 0);
2307 assert!(
2308 !text.is_null(),
2309 "embedded NUL text should still expose a buffer"
2310 );
2311 let bytes = std::slice::from_raw_parts(text.cast::<u8>(), 4);
2312 assert_eq!(bytes, b"a\0b\0");
2313 assert_eq!(sqlite3_column_bytes(stmt, 0), 3);
2314
2315 sqlite3_finalize(stmt);
2316 sqlite3_close(db);
2317 }
2318 }
2319
2320 #[test]
2321 fn test_exec_callback_preserves_embedded_nul_bytes() {
2322 unsafe {
2323 unsafe extern "C" fn capture_bytes_cb(
2324 parg: *mut c_void,
2325 _ncols: c_int,
2326 values: *mut *mut c_char,
2327 _names: *mut *mut c_char,
2328 ) -> c_int {
2329 let out = &mut *parg.cast::<Vec<u8>>();
2330 let value_ptr = *values;
2331 let bytes = std::slice::from_raw_parts(value_ptr.cast::<u8>(), 4);
2332 out.extend_from_slice(bytes);
2333 SQLITE_OK
2334 }
2335
2336 let conn = Connection::open(":memory:").unwrap();
2337 conn.execute("CREATE TABLE t(v TEXT)").unwrap();
2338 conn.execute_with_params(
2339 "INSERT INTO t(v) VALUES (?)",
2340 &[SqliteValue::Text(fsqlite_types::SmallText::from("a\0b"))],
2341 )
2342 .unwrap();
2343 let rows = conn.query("SELECT v FROM t").unwrap();
2344 let handle = Sqlite3::new(conn, None);
2345 let mut captured: Vec<u8> = Vec::new();
2346
2347 assert_eq!(
2348 emit_exec_callback_rows(
2349 &handle,
2350 "SELECT v FROM t",
2351 &rows,
2352 capture_bytes_cb,
2353 std::ptr::from_mut(&mut captured).cast(),
2354 ptr::null_mut(),
2355 ),
2356 SQLITE_OK
2357 );
2358 assert_eq!(captured, b"a\0b\0");
2359 }
2360 }
2361
2362 #[test]
2363 fn test_exec_error_in_later_statement_reports_global_offset() {
2364 unsafe {
2365 let db = open_memory();
2366 let mut errmsg: *mut c_char = ptr::null_mut();
2367 let sql = CString::new("SELECT 1; SELECT * FROM").unwrap();
2368
2369 assert_eq!(
2370 sqlite3_exec(db, sql.as_ptr(), None, ptr::null_mut(), &mut errmsg),
2371 SQLITE_ERROR
2372 );
2373 assert!(!errmsg.is_null());
2374 let message = CStr::from_ptr(errmsg).to_string_lossy().into_owned();
2375 let offset = message
2376 .split("offset ")
2377 .nth(1)
2378 .and_then(|suffix| suffix.split(':').next())
2379 .and_then(|value| value.parse::<usize>().ok())
2380 .expect("errmsg should contain parse offset");
2381 assert!(
2382 offset >= "SELECT 1; ".len(),
2383 "later statement parse error should report an offset in the original SQL string: {message}"
2384 );
2385
2386 sqlite3_free(errmsg.cast());
2387 sqlite3_close(db);
2388 }
2389 }
2390
2391 #[test]
2392 fn test_metrics_increment() {
2393 reset_compat_metrics();
2394
2395 unsafe {
2396 let db = open_memory();
2397
2398 let sql = CString::new("SELECT 1;").unwrap();
2399 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
2400 sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut());
2401 sqlite3_step(stmt);
2402 sqlite3_column_int64(stmt, 0);
2403 sqlite3_finalize(stmt);
2404 sqlite3_close(db);
2405 }
2406
2407 let snap = compat_metrics_snapshot();
2408 assert!(snap.open >= 1);
2409 assert!(snap.prepare >= 1);
2410 assert!(snap.step >= 1);
2411 assert!(snap.column >= 1);
2412 assert!(snap.finalize >= 1);
2413 assert!(snap.close >= 1);
2414 assert!(snap.total() >= 6);
2415 }
2416
2417 #[test]
2418 fn test_column_bytes_text_and_blob() {
2419 unsafe {
2420 let db = open_memory();
2421
2422 let sql = CString::new("SELECT 'hello', X'DEADBEEF';").unwrap();
2423 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
2424 sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut());
2425 sqlite3_step(stmt);
2426
2427 assert_eq!(sqlite3_column_bytes(stmt, 0), 5); assert_eq!(sqlite3_column_bytes(stmt, 1), 4); sqlite3_finalize(stmt);
2431 sqlite3_close(db);
2432 }
2433 }
2434
2435 #[test]
2436 fn test_column_text_exposes_raw_blob_bytes() {
2437 unsafe {
2438 let db = open_memory();
2439
2440 let sql = CString::new("SELECT X'CAFE';").unwrap();
2441 let mut stmt: *mut Sqlite3Stmt = ptr::null_mut();
2442 sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut());
2443 sqlite3_step(stmt);
2444
2445 let text = sqlite3_column_text(stmt, 0);
2446 assert!(!text.is_null());
2447 let bytes = std::slice::from_raw_parts(
2448 text.cast::<u8>(),
2449 sqlite3_column_bytes(stmt, 0) as usize + 1,
2450 );
2451 assert_eq!(bytes, &[0xCA, 0xFE, 0x00]);
2452 assert_eq!(sqlite3_column_bytes(stmt, 0), 2);
2453
2454 sqlite3_finalize(stmt);
2455 sqlite3_close(db);
2456 }
2457 }
2458
2459 #[test]
2460 fn test_exec_callback_exposes_raw_blob_bytes() {
2461 unsafe {
2462 unsafe extern "C" fn capture_blob_cb(
2463 parg: *mut c_void,
2464 _ncols: c_int,
2465 values: *mut *mut c_char,
2466 _names: *mut *mut c_char,
2467 ) -> c_int {
2468 let out = &mut *parg.cast::<Vec<u8>>();
2469 let value_ptr = *values;
2470 let bytes = std::slice::from_raw_parts(value_ptr.cast::<u8>(), 3);
2471 out.extend_from_slice(bytes);
2472 SQLITE_OK
2473 }
2474
2475 let conn = Connection::open(":memory:").unwrap();
2476 let rows = conn.query("SELECT X'CAFE'").unwrap();
2477 let handle = Sqlite3::new(conn, None);
2478 let mut captured: Vec<u8> = Vec::new();
2479
2480 assert_eq!(
2481 emit_exec_callback_rows(
2482 &handle,
2483 "SELECT X'CAFE'",
2484 &rows,
2485 capture_blob_cb,
2486 std::ptr::from_mut(&mut captured).cast(),
2487 ptr::null_mut(),
2488 ),
2489 SQLITE_OK
2490 );
2491 assert_eq!(captured, [0xCA, 0xFE, 0x00]);
2492 }
2493 }
2494
2495 #[test]
2496 fn test_misuse_null_args() {
2497 unsafe {
2498 assert_eq!(sqlite3_open(ptr::null(), ptr::null_mut()), SQLITE_MISUSE);
2500
2501 let mut db: *mut Sqlite3 = ptr::null_mut();
2503 assert_eq!(sqlite3_open(ptr::null(), &mut db), SQLITE_OK);
2504 sqlite3_close(db);
2505
2506 assert_eq!(sqlite3_step(ptr::null_mut()), SQLITE_MISUSE);
2507 assert_eq!(sqlite3_reset(ptr::null_mut()), SQLITE_MISUSE);
2508 assert_eq!(sqlite3_column_count(ptr::null_mut()), 0);
2509 }
2510 }
2511
2512 #[test]
2513 fn test_exec_dml() {
2514 unsafe {
2515 let db = open_memory();
2516
2517 let sql = CString::new("CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT);").unwrap();
2519 assert_eq!(
2520 sqlite3_exec(db, sql.as_ptr(), None, ptr::null_mut(), ptr::null_mut()),
2521 SQLITE_OK
2522 );
2523
2524 let sql = CString::new("INSERT INTO t VALUES(1, 'a');").unwrap();
2525 assert_eq!(
2526 sqlite3_exec(db, sql.as_ptr(), None, ptr::null_mut(), ptr::null_mut()),
2527 SQLITE_OK
2528 );
2529
2530 let sql = CString::new("UPDATE t SET v = 'b' WHERE id = 1;").unwrap();
2531 assert_eq!(
2532 sqlite3_exec(db, sql.as_ptr(), None, ptr::null_mut(), ptr::null_mut()),
2533 SQLITE_OK
2534 );
2535
2536 let sql = CString::new("DELETE FROM t WHERE id = 1;").unwrap();
2537 assert_eq!(
2538 sqlite3_exec(db, sql.as_ptr(), None, ptr::null_mut(), ptr::null_mut()),
2539 SQLITE_OK
2540 );
2541
2542 sqlite3_close(db);
2543 }
2544 }
2545
2546 #[test]
2547 fn test_changes_tracks_last_dml_and_survives_select() {
2548 unsafe {
2549 let db = open_memory();
2550
2551 let sql = CString::new("CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT);").unwrap();
2552 assert_eq!(
2553 sqlite3_exec(db, sql.as_ptr(), None, ptr::null_mut(), ptr::null_mut()),
2554 SQLITE_OK
2555 );
2556 assert_eq!(sqlite3_changes(db), 0);
2557
2558 let sql = CString::new("INSERT INTO t VALUES(1, 'a');").unwrap();
2559 assert_eq!(
2560 sqlite3_exec(db, sql.as_ptr(), None, ptr::null_mut(), ptr::null_mut()),
2561 SQLITE_OK
2562 );
2563 assert_eq!(sqlite3_changes(db), 1);
2564
2565 let sql = CString::new("SELECT v FROM t;").unwrap();
2566 assert_eq!(
2567 sqlite3_exec(db, sql.as_ptr(), None, ptr::null_mut(), ptr::null_mut()),
2568 SQLITE_OK
2569 );
2570 assert_eq!(sqlite3_changes(db), 1);
2571
2572 let sql = CString::new("UPDATE t SET v = 'b' WHERE id = 99;").unwrap();
2573 assert_eq!(
2574 sqlite3_exec(db, sql.as_ptr(), None, ptr::null_mut(), ptr::null_mut()),
2575 SQLITE_OK
2576 );
2577 assert_eq!(sqlite3_changes(db), 0);
2578
2579 sqlite3_close(db);
2580 }
2581 }
2582}