walletkit_db/sqlite/
connection.rs1use std::path::Path;
7
8use super::error::{DbResult, Error};
9use super::ffi::{self, RawDb};
10use super::statement::{Row, Statement, StepResult};
11use super::transaction::Transaction;
12use super::value::Value;
13
14pub struct Connection {
20 db: RawDb,
21}
22
23impl Connection {
24 pub fn open(path: &Path, read_only: bool) -> DbResult<Self> {
30 let path_str = path.to_string_lossy();
31 let flags = if read_only {
32 ffi::SQLITE_OPEN_READONLY | ffi::SQLITE_OPEN_FULLMUTEX
33 } else {
34 ffi::SQLITE_OPEN_READWRITE
35 | ffi::SQLITE_OPEN_CREATE
36 | ffi::SQLITE_OPEN_FULLMUTEX
37 };
38 let db = RawDb::open(&path_str, flags)?;
39 Ok(Self { db })
40 }
41
42 pub fn execute_batch(&self, sql: &str) -> DbResult<()> {
51 self.db.exec(sql)
52 }
53
54 pub fn execute_batch_zeroized(&self, sql: &str) -> DbResult<()> {
62 self.db.exec_zeroized(sql)
63 }
64
65 pub fn prepare(&self, sql: &str) -> DbResult<Statement<'_>> {
71 let raw_stmt = self.db.prepare(sql)?;
72 Ok(Statement::new(raw_stmt))
73 }
74
75 pub fn execute(&self, sql: &str, params: &[Value]) -> DbResult<usize> {
83 let mut stmt = self.prepare(sql)?;
84 stmt.bind_values(params)?;
85 stmt.step()?;
86 Ok(usize::try_from(self.db.changes()).unwrap_or(0))
87 }
88
89 pub fn query_row<T>(
98 &self,
99 sql: &str,
100 params: &[Value],
101 mapper: impl FnOnce(&Row<'_, '_>) -> DbResult<T>,
102 ) -> DbResult<T> {
103 let mut stmt = self.prepare(sql)?;
104 stmt.bind_values(params)?;
105 match stmt.step()? {
106 StepResult::Row(row) => mapper(&row),
107 StepResult::Done => {
108 Err(Error::new(ffi::SQLITE_DONE, "query returned no rows"))
109 }
110 }
111 }
112
113 pub fn query_row_optional<T>(
120 &self,
121 sql: &str,
122 params: &[Value],
123 mapper: impl FnOnce(&Row<'_, '_>) -> DbResult<T>,
124 ) -> DbResult<Option<T>> {
125 let mut stmt = self.prepare(sql)?;
126 stmt.bind_values(params)?;
127 match stmt.step()? {
128 StepResult::Row(row) => mapper(&row).map(Some),
129 StepResult::Done => Ok(None),
130 }
131 }
132
133 pub fn transaction(&self) -> DbResult<Transaction<'_>> {
139 Transaction::begin(self, false)
140 }
141
142 pub fn transaction_immediate(&self) -> DbResult<Transaction<'_>> {
148 Transaction::begin(self, true)
149 }
150
151 #[allow(dead_code)]
153 #[must_use]
154 pub fn last_insert_rowid(&self) -> i64 {
155 self.db.last_insert_rowid()
156 }
157
158 #[allow(dead_code)]
160 #[must_use]
161 pub fn changes(&self) -> usize {
162 usize::try_from(self.db.changes()).unwrap_or(0)
163 }
164}
165
166impl std::fmt::Debug for Connection {
167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 f.debug_struct("Connection").finish_non_exhaustive()
169 }
170}
171
172#[cfg(test)]
173impl Connection {
174 pub fn open_in_memory() -> DbResult<Self> {
180 Self::open(Path::new(":memory:"), false)
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::Connection;
187 use crate::params;
188 use crate::sqlite::Value;
189 use crate::test_utils::init_sqlite;
190
191 #[test]
192 fn test_open_in_memory() {
193 init_sqlite();
194 let conn = Connection::open_in_memory().expect("open in-memory db");
195 conn.execute_batch("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
196 .expect("create table");
197 conn.execute(
198 "INSERT INTO t (id, val) VALUES (?1, ?2)",
199 params![1_i64, "hello"],
200 )
201 .expect("insert");
202 let result = conn
203 .query_row("SELECT val FROM t WHERE id = ?1", params![1_i64], |stmt| {
204 Ok(stmt.column_text(0))
205 })
206 .expect("query");
207 assert_eq!(result, "hello");
208 }
209
210 #[test]
211 fn test_query_row_optional_none() {
212 init_sqlite();
213 let conn = Connection::open_in_memory().expect("open in-memory db");
214 conn.execute_batch("CREATE TABLE t (id INTEGER PRIMARY KEY);")
215 .expect("create table");
216 let result = conn
217 .query_row_optional("SELECT id FROM t WHERE id = 999", &[], |stmt| {
218 Ok(stmt.column_i64(0))
219 })
220 .expect("query");
221 assert!(result.is_none());
222 }
223
224 #[test]
225 fn test_blob_round_trip() {
226 init_sqlite();
227 let conn = Connection::open_in_memory().expect("open in-memory db");
228 conn.execute_batch("CREATE TABLE t (id INTEGER PRIMARY KEY, data BLOB);")
229 .expect("create table");
230 let data = vec![0xDE, 0xAD, 0xBE, 0xEF];
231 conn.execute(
232 "INSERT INTO t (id, data) VALUES (?1, ?2)",
233 params![1_i64, data.as_slice()],
234 )
235 .expect("insert");
236 let result = conn
237 .query_row("SELECT data FROM t WHERE id = 1", &[], |stmt| {
238 Ok(stmt.column_blob(0))
239 })
240 .expect("query");
241 assert_eq!(result, data);
242 }
243
244 #[test]
245 fn test_null_handling() {
246 init_sqlite();
247 let conn = Connection::open_in_memory().expect("open in-memory db");
248 conn.execute_batch("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
249 .expect("create table");
250 conn.execute(
251 "INSERT INTO t (id, val) VALUES (?1, ?2)",
252 params![1_i64, Value::Null],
253 )
254 .expect("insert");
255 let result = conn
256 .query_row("SELECT val FROM t WHERE id = 1", &[], |stmt| {
257 Ok(stmt.is_column_null(0))
258 })
259 .expect("query");
260 assert!(result);
261 }
262}