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