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