1use libpqlite3_sys as ffi;
22use std::ffi::{CStr, CString};
23use std::os::raw::c_int;
24use std::ptr;
25
26#[derive(Debug)]
28pub enum Error {
29 SqliteError { code: i32, message: String },
31 Utf8Error(std::str::Utf8Error),
33 NullError,
35}
36
37impl std::fmt::Display for Error {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 match self {
40 Error::SqliteError { code, message } => write!(f, "SQLite error {}: {}", code, message),
41 Error::Utf8Error(e) => write!(f, "UTF-8 error: {}", e),
42 Error::NullError => write!(f, "Null pointer"),
43 }
44 }
45}
46
47impl std::error::Error for Error {}
48
49pub type Result<T> = std::result::Result<T, Error>;
50
51pub struct Connection {
53 db: *mut ffi::sqlite3,
54}
55
56unsafe impl Send for Connection {}
58
59impl Connection {
60 pub fn open(path: &str) -> Result<Self> {
62 let c_path = CString::new(path).map_err(|_| Error::NullError)?;
63 let mut db: *mut ffi::sqlite3 = ptr::null_mut();
64 let rc = unsafe { ffi::sqlite3_open(c_path.as_ptr(), &mut db) };
65 if rc != ffi::SQLITE_OK {
66 let msg = unsafe { Self::errmsg_raw(db) };
67 unsafe { ffi::sqlite3_close(db) };
68 return Err(Error::SqliteError { code: rc as i32, message: msg });
69 }
70 Ok(Connection { db })
71 }
72
73 pub fn open_in_memory() -> Result<Self> {
75 Self::open(":memory:")
76 }
77
78 pub fn execute(&self, sql: &str, params: &[&str]) -> Result<usize> {
80 if params.is_empty() {
82 return self.execute_batch(sql);
83 }
84
85 let c_sql = CString::new(sql).map_err(|_| Error::NullError)?;
86 let mut stmt: *mut ffi::sqlite3_stmt = ptr::null_mut();
87 let rc = unsafe {
88 ffi::sqlite3_prepare_v2(self.db, c_sql.as_ptr(), -1, &mut stmt, ptr::null_mut())
89 };
90 if rc != ffi::SQLITE_OK {
91 return Err(self.last_error());
92 }
93
94 for (i, param) in params.iter().enumerate() {
96 let c_param = CString::new(*param).map_err(|_| Error::NullError)?;
97 unsafe {
98 ffi::sqlite3_bind_text(stmt, (i + 1) as c_int, c_param.as_ptr(), -1, ffi::SQLITE_TRANSIENT);
99 }
100 }
101
102 let rc = unsafe { ffi::sqlite3_step(stmt) };
103 unsafe { ffi::sqlite3_finalize(stmt) };
104
105 match rc {
106 ffi::SQLITE_DONE | ffi::SQLITE_ROW => Ok(0),
107 _ => Err(self.last_error()),
108 }
109 }
110
111 pub fn execute_batch(&self, sql: &str) -> Result<usize> {
113 let c_sql = CString::new(sql).map_err(|_| Error::NullError)?;
114 let mut errmsg: *mut i8 = ptr::null_mut();
115 let rc = unsafe {
116 ffi::sqlite3_exec(self.db, c_sql.as_ptr(), None, ptr::null_mut(), &mut errmsg)
117 };
118 if rc != ffi::SQLITE_OK {
119 let msg = if !errmsg.is_null() {
120 let s = unsafe { CStr::from_ptr(errmsg) }.to_string_lossy().into_owned();
121 unsafe { ffi::sqlite3_free(errmsg as *mut std::os::raw::c_void) };
122 s
123 } else {
124 "unknown error".to_string()
125 };
126 return Err(Error::SqliteError { code: rc as i32, message: msg });
127 }
128 Ok(0)
129 }
130
131 pub fn pqc_key(&self, password: &str) -> Result<()> {
133 self.execute_batch(&format!("PRAGMA pqc_key='{}'", password))
134 .map(|_| ())
135 }
136
137 pub fn pqc_version(&self) -> Result<String> {
139 let c_sql = CString::new("SELECT pqc_version()").unwrap();
140 let mut stmt: *mut ffi::sqlite3_stmt = ptr::null_mut();
141 let rc = unsafe {
142 ffi::sqlite3_prepare_v2(self.db, c_sql.as_ptr(), -1, &mut stmt, ptr::null_mut())
143 };
144 if rc != ffi::SQLITE_OK { return Err(self.last_error()); }
145
146 let rc = unsafe { ffi::sqlite3_step(stmt) };
147 if rc == ffi::SQLITE_ROW {
148 let text = unsafe { ffi::sqlite3_column_text(stmt, 0) };
149 let result = if !text.is_null() {
150 unsafe { CStr::from_ptr(text) }.to_string_lossy().into_owned()
151 } else {
152 String::new()
153 };
154 unsafe { ffi::sqlite3_finalize(stmt) };
155 Ok(result)
156 } else {
157 unsafe { ffi::sqlite3_finalize(stmt) };
158 Err(self.last_error())
159 }
160 }
161
162 fn last_error(&self) -> Error {
163 Error::SqliteError {
164 code: unsafe { ffi::sqlite3_errcode(self.db) } as i32,
165 message: unsafe { Self::errmsg_raw(self.db) },
166 }
167 }
168
169 unsafe fn errmsg_raw(db: *mut ffi::sqlite3) -> String {
170 if db.is_null() { return "null database".to_string(); }
171 let msg = ffi::sqlite3_errmsg(db);
172 if msg.is_null() { return "unknown error".to_string(); }
173 CStr::from_ptr(msg).to_string_lossy().into_owned()
174 }
175}
176
177impl Drop for Connection {
178 fn drop(&mut self) {
179 if !self.db.is_null() {
180 unsafe { ffi::sqlite3_close_v2(self.db) };
181 }
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_open_memory() {
191 let conn = Connection::open_in_memory().unwrap();
192 conn.execute_batch("CREATE TABLE t(x)").unwrap();
193 conn.execute_batch("INSERT INTO t VALUES('hello')").unwrap();
194 }
195}