Skip to main content

fsqlite_c_api/
lib.rs

1// bd-19u.8: SQLite C API compatibility shim (adoption wedge)
2//
3// Drop-in replacement for sqlite3_open/close/exec/prepare/step/finalize/column_*
4// via C FFI.  Read-only compat is the first milestone; writes via execute() are
5// included but the step-based iteration model is the primary focus.
6//
7// Tracing: span 'compat_api' with fields api_func, duration_us.
8// Log level: INFO API calls via compat layer, WARN for unsupported features.
9// Metric: fsqlite_compat_api_calls_total counter by api_func.
10
11#![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
32/// Drive an engine future to completion on this shim's own runtime.
33///
34/// The C ABI is a hard synchronous boundary — `extern "C"` functions cannot be
35/// `async`, and a C caller has no `Cx` to hand down. So unlike the rest of the
36/// workspace (where AGENTS.md forbids building a runtime), this crate MUST own
37/// one: it is the consumer on behalf of the C caller.
38fn 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
50// ── SQLite result codes ─────────────────────────────────────────────
51
52pub 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
74// ── Column type constants ───────────────────────────────────────────
75
76pub 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
82// ── Metrics ─────────────────────────────────────────────────────────
83
84static 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
143// ── Opaque handle types ─────────────────────────────────────────────
144
145const DEFAULT_ERROR_MESSAGE: &str = "not an error";
146
147/// Opaque database connection handle exposed via C FFI.
148///
149/// Wraps a `Connection` plus the last error message for `sqlite3_errmsg`.
150pub 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        // `try_update` is only stable since Rust 1.95, while this workspace's
206        // MSRV is 1.85. Keep the equivalent MSRV-safe operation until the
207        // workspace toolchain floor advances.
208        #[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/// Opaque prepared statement handle exposed via C FFI.
287///
288/// Wraps the SQL string, the parent connection, and a row cursor so
289/// that `sqlite3_step` can return one row at a time.
290#[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    /// Cached rows from last execution.  `None` means not yet stepped.
301    rows: Option<Vec<fsqlite::Row>>,
302    /// Current row index (0-based, incremented by each `sqlite3_step`).
303    cursor: usize,
304    /// Whether the most recent `sqlite3_step` returned `SQLITE_ROW`.
305    active_row: bool,
306    /// Last sqlite3_step-style result code for sqlite3_reset semantics.
307    last_step_code: c_int,
308    /// Column count from the most recent result set.
309    column_count: c_int,
310    /// Cached NUL-terminated buffers for coerced text/blob accessors (kept
311    /// alive until next step or finalize to satisfy C lifetime expectations,
312    /// including values that contain embedded NUL bytes).
313    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
323// ── Helper: convert FrankenError → c_int ────────────────────────────
324
325fn 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    // SAFETY: the prefix before the first NUL byte contains no interior NULs.
356    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// ── sqlite3_open ────────────────────────────────────────────────────
633
634/// Open a new database connection.
635///
636/// # Safety
637/// `filename` must be a valid null-terminated C string (or null for `:memory:`).
638/// `pp_db` must point to a valid `*mut Sqlite3` location.
639#[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// ── sqlite3_close ───────────────────────────────────────────────────
718
719/// Close a database connection.
720///
721/// # Safety
722/// `db` must have been obtained from `sqlite3_open` and must not be used
723/// after this call.
724#[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); // leak it to prevent further panics
768            SQLITE_ERROR
769        }
770    }
771}
772
773// ── sqlite3_exec ────────────────────────────────────────────────────
774
775/// Execute one or more SQL statements.
776///
777/// # Safety
778/// - `db` must be a valid handle from `sqlite3_open`.
779/// - `sql` must be a valid null-terminated C string.
780/// - `callback` may be null.  If non-null, it is invoked for each result row.
781/// - `errmsg` may be null.  If non-null and an error occurs, it is set to a
782///   malloc'd string that the caller must free with `sqlite3_free`.
783#[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/// Free memory allocated by this library (for errmsg from `sqlite3_exec`).
831///
832/// # Safety
833/// `ptr` must have been allocated by this library or be null.
834#[unsafe(no_mangle)]
835pub unsafe extern "C" fn sqlite3_free(ptr: *mut c_void) {
836    if ptr.is_null() {
837        return;
838    }
839    // We use a simple Vec<u8> allocation strategy: find the length and dealloc.
840    // Since we allocated via libc malloc in sqlite3_exec, use libc free.
841    libc_free(ptr);
842}
843
844// Thin wrappers around libc malloc/free so we don't depend on the libc crate
845// directly (it's in the workspace via nix but we keep the surface minimal).
846const 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    // std::alloc requires the exact Layout for deallocation. Since sqlite3_free
851    // doesn't receive the size, we must prefix the allocation with its size.
852    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// ── sqlite3_prepare_v2 ─────────────────────────────────────────────
882
883/// Compile an SQL statement.
884///
885/// # Safety
886/// - `db` must be a valid handle.
887/// - `sql` must be a valid UTF-8 C string with at least `n_byte` bytes
888///   (or null-terminated if `n_byte` < 0).
889/// - `pp_stmt` must point to a valid `*mut Sqlite3Stmt` location.
890/// - `pz_tail` may be null.
891#[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// ── sqlite3_step ────────────────────────────────────────────────────
992
993/// Step through a prepared statement.
994///
995/// Returns `SQLITE_ROW` when a result row is available, `SQLITE_DONE`
996/// when execution is complete.
997///
998/// # Safety
999/// `stmt` must be a valid handle from `sqlite3_prepare_v2`.
1000#[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    // First call: execute the query and cache all rows.
1013    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    // Advance cursor.
1078    if let Some(ref rows) = s.rows {
1079        if s.cursor < rows.len() {
1080            // Clear text cache for this row.
1081            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// ── sqlite3_finalize ────────────────────────────────────────────────
1107
1108/// Destroy a prepared statement.
1109///
1110/// # Safety
1111/// `stmt` must have been obtained from `sqlite3_prepare_v2` and must not
1112/// be used after this call.  Passing null is safe (no-op).
1113#[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// ── sqlite3_reset ───────────────────────────────────────────────────
1137
1138/// Reset a prepared statement so it can be stepped again.
1139///
1140/// # Safety
1141/// `stmt` must be a valid handle from `sqlite3_prepare_v2`.
1142#[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
1161// ── Column accessors ────────────────────────────────────────────────
1162
1163/// Return a reference to the current row's value at column `i_col`, or None if out of bounds.
1164unsafe 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/// Number of columns in the result set.
1179///
1180/// # Safety
1181/// `stmt` must be a valid handle.
1182#[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/// Type of value in column `i_col` of the current row.
1194///
1195/// # Safety
1196/// `stmt` must be a valid handle, and a row must be available via `sqlite3_step`.
1197#[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/// Get an integer value from column `i_col`.
1211///
1212/// # Safety
1213/// `stmt` must be a valid handle with an active row.
1214#[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/// Get a 32-bit integer value from column `i_col`.
1225///
1226/// # Safety
1227/// `stmt` must be a valid handle with an active row.
1228#[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/// Get a double value from column `i_col`.
1236///
1237/// # Safety
1238/// `stmt` must be a valid handle with an active row.
1239#[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/// Get a text pointer from column `i_col`.
1250///
1251/// The returned pointer is valid until the next `sqlite3_step`,
1252/// `sqlite3_reset`, or `sqlite3_finalize` on this statement.
1253///
1254/// # Safety
1255/// `stmt` must be a valid handle with an active row.
1256#[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/// Get a blob pointer from column `i_col`.
1276///
1277/// The returned pointer is valid until the next `sqlite3_step`,
1278/// `sqlite3_reset`, or `sqlite3_finalize` on this statement.
1279///
1280/// # Safety
1281/// `stmt` must be a valid handle with an active row.
1282#[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/// Get the byte size of a blob or text value in column `i_col`.
1316///
1317/// # Safety
1318/// `stmt` must be a valid handle with an active row.
1319#[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        // C SQLite coerces to text and returns the byte length.
1339        Some(v @ (SqliteValue::Integer(_) | SqliteValue::Float(_))) => v.to_text().len() as c_int,
1340        _ => 0,
1341    }
1342}
1343
1344// ── sqlite3_errmsg / sqlite3_errcode ────────────────────────────────
1345
1346/// Get the most recent error message.
1347///
1348/// # Safety
1349/// `db` must be a valid handle.
1350#[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/// Get the most recent error code.
1369///
1370/// # Safety
1371/// `db` must be a valid handle.
1372#[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// ── sqlite3_changes ─────────────────────────────────────────────────
1381
1382/// Return the number of rows modified by the most recent INSERT/UPDATE/DELETE.
1383///
1384/// # Safety
1385/// `db` must be a valid handle.
1386#[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// ── Tests ───────────────────────────────────────────────────────────
1395
1396#[cfg(test)]
1397mod tests {
1398    use super::*;
1399    use std::ffi::CString;
1400    use std::ptr;
1401
1402    /// Helper: open an in-memory database via C API.
1403    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            // SELECT with callback: pass counter through parg.
1520            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            // Create table and insert data.
1564            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            // Prepare a SELECT.
1570            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            // Step through rows.
1577            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); // X'CAFE' = 2 bytes
1634
1635            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            // Integer as double, text as integer, float as integer.
1667            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            // Int → double.
1674            let f = sqlite3_column_double(stmt, 0);
1675            assert!((f - 42.0).abs() < 0.001);
1676
1677            // Text → int64.
1678            assert_eq!(sqlite3_column_int64(stmt, 1), 123);
1679
1680            // Float → int64 (truncation).
1681            assert_eq!(sqlite3_column_int64(stmt, 2), 3);
1682
1683            // Int → text.
1684            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            // First pass.
1811            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            // Reset and step again.
1816            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()); // Empty SQL → no statement.
1836
1837            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            // Pass a longer buffer but limit via n_byte.
2007            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            // Only prepare "SELECT 99;" (10 chars).
2013            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            // Column 99 is out of range → defaults.
2124            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 // non-zero → abort
2172            }
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); // "hello" = 5 bytes
2445            assert_eq!(sqlite3_column_bytes(stmt, 1), 4); // X'DEADBEEF' = 4 bytes
2446
2447            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            // Null pp_db → MISUSE.
2516            assert_eq!(sqlite3_open(ptr::null(), ptr::null_mut()), SQLITE_MISUSE);
2517
2518            // Null filename with valid pp_db → :memory: (OK).
2519            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            // Full DDL + DML cycle via exec.
2535            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}