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