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