Skip to main content

sqlmodel_sqlite/
ffi.rs

1//! Low-level FFI bindings to libsqlite3.
2//!
3//! These bindings are manually written to provide full control over the
4//! interface. We only expose what we need for the driver implementation.
5
6#![allow(non_camel_case_types)]
7#![allow(clippy::upper_case_acronyms)]
8#![allow(clippy::unreadable_literal)] // FFI constants use standard hex format
9
10use std::ffi::{c_char, c_double, c_int, c_void};
11
12/// Opaque sqlite3 database connection handle.
13#[repr(C)]
14pub struct sqlite3 {
15    _private: [u8; 0],
16}
17
18/// Opaque sqlite3_stmt prepared statement handle.
19#[repr(C)]
20pub struct sqlite3_stmt {
21    _private: [u8; 0],
22}
23
24// SQLite result codes
25pub const SQLITE_OK: c_int = 0;
26pub const SQLITE_ERROR: c_int = 1;
27pub const SQLITE_INTERNAL: c_int = 2;
28pub const SQLITE_PERM: c_int = 3;
29pub const SQLITE_ABORT: c_int = 4;
30pub const SQLITE_BUSY: c_int = 5;
31pub const SQLITE_LOCKED: c_int = 6;
32pub const SQLITE_NOMEM: c_int = 7;
33pub const SQLITE_READONLY: c_int = 8;
34pub const SQLITE_INTERRUPT: c_int = 9;
35pub const SQLITE_IOERR: c_int = 10;
36pub const SQLITE_CORRUPT: c_int = 11;
37pub const SQLITE_NOTFOUND: c_int = 12;
38pub const SQLITE_FULL: c_int = 13;
39pub const SQLITE_CANTOPEN: c_int = 14;
40pub const SQLITE_PROTOCOL: c_int = 15;
41pub const SQLITE_EMPTY: c_int = 16;
42pub const SQLITE_SCHEMA: c_int = 17;
43pub const SQLITE_TOOBIG: c_int = 18;
44pub const SQLITE_CONSTRAINT: c_int = 19;
45pub const SQLITE_MISMATCH: c_int = 20;
46pub const SQLITE_MISUSE: c_int = 21;
47pub const SQLITE_NOLFS: c_int = 22;
48pub const SQLITE_AUTH: c_int = 23;
49pub const SQLITE_FORMAT: c_int = 24;
50pub const SQLITE_RANGE: c_int = 25;
51pub const SQLITE_NOTADB: c_int = 26;
52pub const SQLITE_NOTICE: c_int = 27;
53pub const SQLITE_WARNING: c_int = 28;
54pub const SQLITE_ROW: c_int = 100;
55pub const SQLITE_DONE: c_int = 101;
56
57// sqlite3_open_v2 flags
58pub const SQLITE_OPEN_READONLY: c_int = 0x00000001;
59pub const SQLITE_OPEN_READWRITE: c_int = 0x00000002;
60pub const SQLITE_OPEN_CREATE: c_int = 0x00000004;
61pub const SQLITE_OPEN_URI: c_int = 0x00000040;
62pub const SQLITE_OPEN_MEMORY: c_int = 0x00000080;
63pub const SQLITE_OPEN_NOMUTEX: c_int = 0x00008000;
64pub const SQLITE_OPEN_FULLMUTEX: c_int = 0x00010000;
65pub const SQLITE_OPEN_SHAREDCACHE: c_int = 0x00020000;
66pub const SQLITE_OPEN_PRIVATECACHE: c_int = 0x00040000;
67
68// Fundamental data types
69pub const SQLITE_INTEGER: c_int = 1;
70pub const SQLITE_FLOAT: c_int = 2;
71pub const SQLITE_TEXT: c_int = 3;
72pub const SQLITE_BLOB: c_int = 4;
73pub const SQLITE_NULL: c_int = 5;
74
75// Type alias for destructor callback
76pub type sqlite3_destructor_type = Option<unsafe extern "C" fn(*mut c_void)>;
77
78// Special destructor value that tells SQLite to copy the data immediately.
79// SQLITE_TRANSIENT is defined in SQLite as ((void(*)(void*))(-1))
80// We use transmute at runtime since const transmute is unstable.
81/// Returns the SQLITE_TRANSIENT destructor value.
82///
83/// This value tells SQLite to immediately copy any bound string or blob data.
84/// It is the safest option when the source data's lifetime is uncertain.
85#[inline]
86pub fn sqlite_transient() -> sqlite3_destructor_type {
87    // SAFETY: This is how SQLite defines SQLITE_TRANSIENT in its C API.
88    // The address 0xFFFF...FFFF is never a valid function pointer, and
89    // SQLite specifically checks for this sentinel value.
90    unsafe { std::mem::transmute(!0usize) }
91}
92
93#[link(name = "sqlite3")]
94unsafe extern "C" {
95    // Connection management
96    pub fn sqlite3_open(filename: *const c_char, ppDb: *mut *mut sqlite3) -> c_int;
97
98    pub fn sqlite3_open_v2(
99        filename: *const c_char,
100        ppDb: *mut *mut sqlite3,
101        flags: c_int,
102        zVfs: *const c_char,
103    ) -> c_int;
104
105    pub fn sqlite3_close(db: *mut sqlite3) -> c_int;
106    pub fn sqlite3_close_v2(db: *mut sqlite3) -> c_int;
107
108    // Error handling
109    pub fn sqlite3_errmsg(db: *mut sqlite3) -> *const c_char;
110    pub fn sqlite3_errcode(db: *mut sqlite3) -> c_int;
111    pub fn sqlite3_extended_errcode(db: *mut sqlite3) -> c_int;
112    pub fn sqlite3_errstr(errcode: c_int) -> *const c_char;
113
114    // Statement preparation
115    pub fn sqlite3_prepare_v2(
116        db: *mut sqlite3,
117        zSql: *const c_char,
118        nByte: c_int,
119        ppStmt: *mut *mut sqlite3_stmt,
120        pzTail: *mut *const c_char,
121    ) -> c_int;
122
123    pub fn sqlite3_finalize(pStmt: *mut sqlite3_stmt) -> c_int;
124    pub fn sqlite3_reset(pStmt: *mut sqlite3_stmt) -> c_int;
125    pub fn sqlite3_clear_bindings(pStmt: *mut sqlite3_stmt) -> c_int;
126
127    // Parameter binding
128    pub fn sqlite3_bind_null(pStmt: *mut sqlite3_stmt, index: c_int) -> c_int;
129
130    pub fn sqlite3_bind_int(pStmt: *mut sqlite3_stmt, index: c_int, value: c_int) -> c_int;
131
132    pub fn sqlite3_bind_int64(pStmt: *mut sqlite3_stmt, index: c_int, value: i64) -> c_int;
133
134    pub fn sqlite3_bind_double(pStmt: *mut sqlite3_stmt, index: c_int, value: c_double) -> c_int;
135
136    pub fn sqlite3_bind_text(
137        pStmt: *mut sqlite3_stmt,
138        index: c_int,
139        value: *const c_char,
140        nBytes: c_int,
141        destructor: sqlite3_destructor_type,
142    ) -> c_int;
143
144    pub fn sqlite3_bind_blob(
145        pStmt: *mut sqlite3_stmt,
146        index: c_int,
147        value: *const c_void,
148        nBytes: c_int,
149        destructor: sqlite3_destructor_type,
150    ) -> c_int;
151
152    pub fn sqlite3_bind_parameter_count(pStmt: *mut sqlite3_stmt) -> c_int;
153    pub fn sqlite3_bind_parameter_index(pStmt: *mut sqlite3_stmt, name: *const c_char) -> c_int;
154    pub fn sqlite3_bind_parameter_name(pStmt: *mut sqlite3_stmt, index: c_int) -> *const c_char;
155
156    // Stepping through results
157    pub fn sqlite3_step(pStmt: *mut sqlite3_stmt) -> c_int;
158
159    // Result column information
160    pub fn sqlite3_column_count(pStmt: *mut sqlite3_stmt) -> c_int;
161    pub fn sqlite3_column_name(pStmt: *mut sqlite3_stmt, index: c_int) -> *const c_char;
162    pub fn sqlite3_column_type(pStmt: *mut sqlite3_stmt, index: c_int) -> c_int;
163    pub fn sqlite3_column_decltype(pStmt: *mut sqlite3_stmt, index: c_int) -> *const c_char;
164
165    // Result column values
166    pub fn sqlite3_column_int(pStmt: *mut sqlite3_stmt, index: c_int) -> c_int;
167    pub fn sqlite3_column_int64(pStmt: *mut sqlite3_stmt, index: c_int) -> i64;
168    pub fn sqlite3_column_double(pStmt: *mut sqlite3_stmt, index: c_int) -> c_double;
169    pub fn sqlite3_column_text(pStmt: *mut sqlite3_stmt, index: c_int) -> *const c_char;
170    pub fn sqlite3_column_blob(pStmt: *mut sqlite3_stmt, index: c_int) -> *const c_void;
171    pub fn sqlite3_column_bytes(pStmt: *mut sqlite3_stmt, index: c_int) -> c_int;
172
173    // Execution helpers
174    pub fn sqlite3_exec(
175        db: *mut sqlite3,
176        sql: *const c_char,
177        callback: Option<
178            unsafe extern "C" fn(*mut c_void, c_int, *mut *mut c_char, *mut *mut c_char) -> c_int,
179        >,
180        arg: *mut c_void,
181        errmsg: *mut *mut c_char,
182    ) -> c_int;
183
184    pub fn sqlite3_free(ptr: *mut c_void);
185
186    // Metadata
187    pub fn sqlite3_changes(db: *mut sqlite3) -> c_int;
188    pub fn sqlite3_total_changes(db: *mut sqlite3) -> c_int;
189    pub fn sqlite3_last_insert_rowid(db: *mut sqlite3) -> i64;
190
191    // Configuration
192    pub fn sqlite3_busy_timeout(db: *mut sqlite3, ms: c_int) -> c_int;
193
194    // Version info
195    pub fn sqlite3_libversion() -> *const c_char;
196    pub fn sqlite3_libversion_number() -> c_int;
197}
198
199/// Get the SQLite library version as a string.
200pub fn version() -> &'static str {
201    // SAFETY: sqlite3_libversion returns a static string
202    unsafe {
203        let ptr = sqlite3_libversion();
204        std::ffi::CStr::from_ptr(ptr).to_str().unwrap_or("unknown")
205    }
206}
207
208/// Get the SQLite library version as a number.
209pub fn version_number() -> i32 {
210    // SAFETY: sqlite3_libversion_number is always safe to call
211    unsafe { sqlite3_libversion_number() }
212}
213
214/// Convert an SQLite result code to a human-readable string.
215pub fn error_string(code: c_int) -> &'static str {
216    // SAFETY: sqlite3_errstr returns a static string
217    unsafe {
218        let ptr = sqlite3_errstr(code);
219        std::ffi::CStr::from_ptr(ptr)
220            .to_str()
221            .unwrap_or("unknown error")
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228
229    #[test]
230    fn test_version() {
231        let v = version();
232        assert!(!v.is_empty());
233        // SQLite version should start with 3.
234        assert!(v.starts_with('3'));
235    }
236
237    #[test]
238    fn test_version_number() {
239        let v = version_number();
240        // SQLite 3.x.x version numbers are in the form 3XXYYZZ
241        // e.g., 3.45.0 = 3045000
242        assert!(v >= 3_000_000);
243    }
244
245    #[test]
246    fn test_error_string() {
247        assert_eq!(error_string(SQLITE_OK), "not an error");
248        assert_eq!(error_string(SQLITE_ERROR), "SQL logic error");
249        assert_eq!(error_string(SQLITE_BUSY), "database is locked");
250        assert_eq!(error_string(SQLITE_CONSTRAINT), "constraint failed");
251    }
252
253    #[test]
254    fn test_result_codes() {
255        // Verify result code constants match expected values
256        assert_eq!(SQLITE_OK, 0);
257        assert_eq!(SQLITE_ROW, 100);
258        assert_eq!(SQLITE_DONE, 101);
259    }
260}