Skip to main content

ic_sqlite_vfs/db/
connection.rs

1//! Thin SQLite C connection wrapper bound to the `icstable` VFS.
2//!
3//! `rusqlite` refuses `SQLITE_THREADSAFE=0`, so this crate keeps a small FFI
4//! facade. Connections are per-message and never shared.
5
6use crate::config::{SQLITE_URI, VFS_NAME};
7use crate::db::row::Row;
8use crate::db::statement::Statement;
9use crate::db::value::{ToSql, Value};
10use crate::db::{pragmas, DbError};
11use crate::sqlite_vfs::ffi;
12use std::ffi::{c_char, c_int, c_void, CStr, CString};
13use std::ptr::{self, NonNull};
14
15pub struct Connection {
16    raw: NonNull<ffi::sqlite3>,
17}
18
19pub fn open_read_write() -> Result<Connection, DbError> {
20    let flags = ffi::SQLITE_OPEN_READWRITE
21        | ffi::SQLITE_OPEN_CREATE
22        | ffi::SQLITE_OPEN_URI
23        | ffi::SQLITE_OPEN_NOMUTEX;
24    let connection = Connection::open(flags)?;
25    pragmas::apply_read_write(&connection)?;
26    Ok(connection)
27}
28
29pub fn open_read_only() -> Result<Connection, DbError> {
30    let flags = ffi::SQLITE_OPEN_READONLY | ffi::SQLITE_OPEN_URI | ffi::SQLITE_OPEN_NOMUTEX;
31    let connection = Connection::open(flags)?;
32    pragmas::apply_read_only(&connection)?;
33    Ok(connection)
34}
35
36impl Connection {
37    fn open(flags: c_int) -> Result<Self, DbError> {
38        let filename = CString::new(SQLITE_URI).map_err(|_| DbError::InteriorNul)?;
39        let vfs = CString::new(VFS_NAME).map_err(|_| DbError::InteriorNul)?;
40        let mut db = ptr::null_mut();
41        let rc = unsafe { ffi::sqlite3_open_v2(filename.as_ptr(), &mut db, flags, vfs.as_ptr()) };
42        let Some(raw) = NonNull::new(db) else {
43            return Err(DbError::Sqlite(
44                rc,
45                "sqlite3_open_v2 returned null".to_string(),
46            ));
47        };
48        if rc != ffi::SQLITE_OK {
49            let error = sqlite_error(raw.as_ptr(), rc);
50            unsafe {
51                ffi::sqlite3_close(raw.as_ptr());
52            }
53            return Err(error);
54        }
55        Ok(Self { raw })
56    }
57
58    pub fn raw(&self) -> *mut ffi::sqlite3 {
59        self.raw.as_ptr()
60    }
61
62    pub fn execute_batch(&self, sql: &str) -> Result<(), DbError> {
63        let sql = CString::new(sql).map_err(|_| DbError::InteriorNul)?;
64        let mut error = ptr::null_mut();
65        let rc = unsafe {
66            ffi::sqlite3_exec(
67                self.raw.as_ptr(),
68                sql.as_ptr(),
69                None,
70                ptr::null_mut(),
71                &mut error,
72            )
73        };
74        if rc == ffi::SQLITE_OK {
75            return Ok(());
76        }
77        Err(classify_sqlite_error(rc, take_error_message(error)))
78    }
79
80    pub fn execute(&self, sql: &str, values: &[&dyn ToSql]) -> Result<(), DbError> {
81        let mut statement = self.prepare(sql)?;
82        statement.execute(values)
83    }
84
85    pub fn execute_named(&self, sql: &str, values: &[(&str, &dyn ToSql)]) -> Result<(), DbError> {
86        let mut statement = self.prepare(sql)?;
87        statement.execute_named(values)
88    }
89
90    pub fn execute_with_texts(&self, sql: &str, values: &[&str]) -> Result<(), DbError> {
91        let values = values
92            .iter()
93            .map(|value| value as &dyn ToSql)
94            .collect::<Vec<_>>();
95        self.execute(sql, &values)
96    }
97
98    pub fn prepare(&self, sql: &str) -> Result<Statement<'_>, DbError> {
99        let sql = CString::new(sql).map_err(|_| DbError::InteriorNul)?;
100        let mut statement = ptr::null_mut();
101        let rc = unsafe {
102            ffi::sqlite3_prepare_v2(
103                self.raw.as_ptr(),
104                sql.as_ptr(),
105                -1,
106                &mut statement,
107                ptr::null_mut(),
108            )
109        };
110        if rc != ffi::SQLITE_OK {
111            return Err(sqlite_error(self.raw.as_ptr(), rc));
112        }
113        let Some(raw) = NonNull::new(statement) else {
114            return Err(DbError::Sqlite(
115                rc,
116                "sqlite3_prepare_v2 returned null".to_string(),
117            ));
118        };
119        Ok(Statement::new(self.raw.as_ptr(), raw))
120    }
121
122    pub fn query_one<T, F>(&self, sql: &str, values: &[&dyn ToSql], f: F) -> Result<T, DbError>
123    where
124        F: FnOnce(&Row<'_>) -> Result<T, DbError>,
125    {
126        let mut statement = self.prepare(sql)?;
127        statement.query_one(values, f)
128    }
129
130    pub fn query_one_named<T, F>(
131        &self,
132        sql: &str,
133        values: &[(&str, &dyn ToSql)],
134        f: F,
135    ) -> Result<T, DbError>
136    where
137        F: FnOnce(&Row<'_>) -> Result<T, DbError>,
138    {
139        let mut statement = self.prepare(sql)?;
140        statement.query_one_named(values, f)
141    }
142
143    pub fn query_optional<T, F>(
144        &self,
145        sql: &str,
146        values: &[&dyn ToSql],
147        f: F,
148    ) -> Result<Option<T>, DbError>
149    where
150        F: FnOnce(&Row<'_>) -> Result<T, DbError>,
151    {
152        let mut statement = self.prepare(sql)?;
153        statement.query_optional(values, f)
154    }
155
156    pub fn query_optional_named<T, F>(
157        &self,
158        sql: &str,
159        values: &[(&str, &dyn ToSql)],
160        f: F,
161    ) -> Result<Option<T>, DbError>
162    where
163        F: FnOnce(&Row<'_>) -> Result<T, DbError>,
164    {
165        let mut statement = self.prepare(sql)?;
166        statement.query_optional_named(values, f)
167    }
168
169    pub fn query_all<T, F>(&self, sql: &str, values: &[&dyn ToSql], f: F) -> Result<Vec<T>, DbError>
170    where
171        F: FnMut(&Row<'_>) -> Result<T, DbError>,
172    {
173        let mut statement = self.prepare(sql)?;
174        statement.query_all(values, f)
175    }
176
177    pub fn query_all_named<T, F>(
178        &self,
179        sql: &str,
180        values: &[(&str, &dyn ToSql)],
181        f: F,
182    ) -> Result<Vec<T>, DbError>
183    where
184        F: FnMut(&Row<'_>) -> Result<T, DbError>,
185    {
186        let mut statement = self.prepare(sql)?;
187        statement.query_all_named(values, f)
188    }
189
190    pub fn exists(&self, sql: &str, values: &[&dyn ToSql]) -> Result<bool, DbError> {
191        self.query_optional(sql, values, |row| row.get::<i64>(0))
192            .map(|value| value.unwrap_or(0) != 0)
193    }
194
195    pub fn query_i64(&self, sql: &str) -> Result<i64, DbError> {
196        self.query_one(sql, &[], |row| row.get(0))
197    }
198
199    pub fn query_string(&self, sql: &str) -> Result<String, DbError> {
200        self.query_one(sql, &[], |row| row.get(0))
201    }
202
203    pub fn query_optional_string_with_text(
204        &self,
205        sql: &str,
206        value: &str,
207    ) -> Result<Option<String>, DbError> {
208        let value = Value::Text(value);
209        self.query_optional(sql, &[&value], |row| row.get(0))
210    }
211}
212
213impl Drop for Connection {
214    fn drop(&mut self) {
215        unsafe {
216            ffi::sqlite3_close(self.raw.as_ptr());
217        }
218    }
219}
220
221pub(crate) fn sqlite_error(db: *mut ffi::sqlite3, code: c_int) -> DbError {
222    let message = unsafe {
223        let ptr = ffi::sqlite3_errmsg(db);
224        if ptr.is_null() {
225            "unknown sqlite error".to_string()
226        } else {
227            CStr::from_ptr(ptr).to_string_lossy().into_owned()
228        }
229    };
230    classify_sqlite_error(code, message)
231}
232
233fn classify_sqlite_error(code: c_int, message: String) -> DbError {
234    if code == ffi::SQLITE_CONSTRAINT {
235        DbError::Constraint(message)
236    } else {
237        DbError::Sqlite(code, message)
238    }
239}
240
241fn take_error_message(error: *mut c_char) -> String {
242    if error.is_null() {
243        return "unknown sqlite error".to_string();
244    }
245    let message = unsafe { CStr::from_ptr(error).to_string_lossy().into_owned() };
246    unsafe {
247        ffi::sqlite3_free(error.cast::<c_void>());
248    }
249    message
250}