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