Skip to main content

sqlite_wasm_wasi/
lib.rs

1use std::collections::BTreeMap;
2
3mod bindings {
4    wit_bindgen::generate!({
5        path: "wit",
6        world: "sqlite-app",
7    });
8}
9
10use bindings::wasm::sqlite_wasi::sqlite::{
11    close, exec, open as open_db, prepare, SqliteError, SqliteRunInfo, SqliteValue,
12};
13
14pub type Value = SqliteValue;
15pub trait ToParam {
16    fn to_param(&self) -> Value;
17}
18
19pub type RunInfo = SqliteRunInfo;
20pub type Row = BTreeMap<String, Value>;
21
22#[derive(Debug, Clone)]
23pub struct Error {
24    pub code: i32,
25    pub message: String,
26}
27
28impl std::fmt::Display for Error {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        write!(f, "{} (code: {})", self.message, self.code)
31    }
32}
33
34impl std::error::Error for Error {}
35
36impl From<SqliteError> for Error {
37    fn from(value: SqliteError) -> Self {
38        Self {
39            code: value.code,
40            message: value.message,
41        }
42    }
43}
44
45impl From<i64> for Value {
46    fn from(value: i64) -> Self {
47        Self::Integer(value)
48    }
49}
50
51impl From<i32> for Value {
52    fn from(value: i32) -> Self {
53        Self::Integer(value.into())
54    }
55}
56
57impl From<f64> for Value {
58    fn from(value: f64) -> Self {
59        Self::Real(value)
60    }
61}
62
63impl From<f32> for Value {
64    fn from(value: f32) -> Self {
65        Self::Real(value.into())
66    }
67}
68
69impl From<&str> for Value {
70    fn from(value: &str) -> Self {
71        Self::Text(value.to_string())
72    }
73}
74
75impl From<String> for Value {
76    fn from(value: String) -> Self {
77        Self::Text(value)
78    }
79}
80
81impl From<Vec<u8>> for Value {
82    fn from(value: Vec<u8>) -> Self {
83        Self::Blob(value)
84    }
85}
86
87impl From<&[u8]> for Value {
88    fn from(value: &[u8]) -> Self {
89        Self::Blob(value.to_vec())
90    }
91}
92
93impl ToParam for Value {
94    fn to_param(&self) -> Value {
95        self.clone()
96    }
97}
98
99impl ToParam for i64 {
100    fn to_param(&self) -> Value {
101        Value::Integer(*self)
102    }
103}
104
105impl ToParam for i32 {
106    fn to_param(&self) -> Value {
107        Value::Integer((*self).into())
108    }
109}
110
111impl ToParam for f64 {
112    fn to_param(&self) -> Value {
113        Value::Real(*self)
114    }
115}
116
117impl ToParam for f32 {
118    fn to_param(&self) -> Value {
119        Value::Real((*self).into())
120    }
121}
122
123impl ToParam for str {
124    fn to_param(&self) -> Value {
125        Value::Text(self.to_string())
126    }
127}
128
129impl ToParam for String {
130    fn to_param(&self) -> Value {
131        Value::Text(self.clone())
132    }
133}
134
135impl ToParam for Vec<u8> {
136    fn to_param(&self) -> Value {
137        Value::Blob(self.clone())
138    }
139}
140
141impl ToParam for [u8] {
142    fn to_param(&self) -> Value {
143        Value::Blob(self.to_vec())
144    }
145}
146
147impl<T> ToParam for &T
148where
149    T: ToParam + ?Sized,
150{
151    fn to_param(&self) -> Value {
152        (*self).to_param()
153    }
154}
155
156pub struct Statement {
157    stmt: bindings::wasm::sqlite_wasi::sqlite::Statement,
158}
159
160impl Statement {
161    pub fn run(&self, params: &[&dyn ToParam]) -> Result<RunInfo, Error> {
162        let params = params_to_values(params);
163        self.stmt.run(Some(&params)).map_err(Into::into)
164    }
165
166    pub fn one(&self, params: &[&dyn ToParam]) -> Result<Option<Row>, Error> {
167        let params = params_to_values(params);
168        let row = self.stmt.one(Some(&params)).map_err(Error::from)?;
169        Ok(row.map(row_to_object))
170    }
171
172    pub fn all(&self, params: &[&dyn ToParam]) -> Result<Vec<Row>, Error> {
173        let params = params_to_values(params);
174        let rows = self.stmt.all(Some(&params)).map_err(Error::from)?;
175        Ok(rows.into_iter().map(row_to_object).collect())
176    }
177
178    pub fn release(&self) -> Result<bool, Error> {
179        Ok(self.stmt.release())
180    }
181}
182
183#[derive(Clone, Copy)]
184pub struct Database {
185    handle: u32,
186}
187
188impl Database {
189    pub fn exec(&self, sql: &str, params: &[&dyn ToParam]) -> Result<u64, Error> {
190        let params = params_to_values(params);
191        exec(self.handle, sql, Some(&params)).map_err(Error::from)
192    }
193
194    pub fn prepare(&self, sql: &str) -> Result<Statement, Error> {
195        let stmt = prepare(self.handle, sql).map_err(Error::from)?;
196        Ok(Statement { stmt })
197    }
198
199    pub fn transaction<'a, F, T, A>(&'a self, mut f: F) -> impl FnMut(A) -> Result<T, Error> + 'a
200    where
201        F: FnMut(A) -> Result<T, Error> + 'a,
202    {
203        move |arg| {
204            self.exec("BEGIN", &[])?;
205            match f(arg) {
206                Ok(result) => {
207                    self.exec("COMMIT", &[])?;
208                    Ok(result)
209                }
210                Err(err) => {
211                    let _ = self.exec("ROLLBACK", &[]);
212                    Err(err)
213                }
214            }
215        }
216    }
217
218    pub fn close(&self) -> Result<(), Error> {
219        close(self.handle).map_err(Error::from)
220    }
221}
222
223pub fn open(uri: &str) -> Result<Database, Error> {
224    let uri = if uri.starts_with("file:") {
225        uri.to_string()
226    } else {
227        format!("file:{uri}")
228    };
229    let handle = open_db(&uri).map_err(Error::from)?;
230    Ok(Database { handle })
231}
232
233pub fn row_value<'a>(row: &'a Row, name: &str) -> Option<&'a Value> {
234    row.get(name)
235}
236
237fn row_to_object(row: bindings::wasm::sqlite_wasi::sqlite::SqliteRow) -> Row {
238    row.columns
239        .into_iter()
240        .zip(row.values)
241        .collect::<BTreeMap<_, _>>()
242}
243
244fn params_to_values(params: &[&dyn ToParam]) -> Vec<Value> {
245    params.iter().map(|param| param.to_param()).collect()
246}