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 const NO_PARAMS: [Value; 0] = [];
16pub type RunInfo = SqliteRunInfo;
17pub type Row = BTreeMap<String, Value>;
18
19#[derive(Debug, Clone)]
20pub struct Error {
21    pub code: i32,
22    pub message: String,
23}
24
25impl std::fmt::Display for Error {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        write!(f, "{} (code: {})", self.message, self.code)
28    }
29}
30
31impl std::error::Error for Error {}
32
33impl From<SqliteError> for Error {
34    fn from(value: SqliteError) -> Self {
35        Self {
36            code: value.code,
37            message: value.message,
38        }
39    }
40}
41
42impl From<i64> for Value {
43    fn from(value: i64) -> Self {
44        Self::Integer(value)
45    }
46}
47
48impl From<f64> for Value {
49    fn from(value: f64) -> Self {
50        Self::Real(value)
51    }
52}
53
54impl From<&str> for Value {
55    fn from(value: &str) -> Self {
56        Self::Text(value.to_string())
57    }
58}
59
60impl From<String> for Value {
61    fn from(value: String) -> Self {
62        Self::Text(value)
63    }
64}
65
66impl From<Vec<u8>> for Value {
67    fn from(value: Vec<u8>) -> Self {
68        Self::Blob(value)
69    }
70}
71
72impl From<&[u8]> for Value {
73    fn from(value: &[u8]) -> Self {
74        Self::Blob(value.to_vec())
75    }
76}
77
78pub struct Statement {
79    stmt: bindings::wasm::sqlite_wasi::sqlite::Statement,
80}
81
82impl Statement {
83    pub fn run<P>(&self, params: &[P]) -> Result<RunInfo, Error>
84    where
85        P: Clone + Into<Value>,
86    {
87        let params = params_to_values(params);
88        self.stmt.run(Some(&params)).map_err(Into::into)
89    }
90
91    pub fn one<P>(&self, params: &[P]) -> Result<Option<Row>, Error>
92    where
93        P: Clone + Into<Value>,
94    {
95        let params = params_to_values(params);
96        let row = self.stmt.one(Some(&params)).map_err(Error::from)?;
97        Ok(row.map(row_to_object))
98    }
99
100    pub fn all<P>(&self, params: &[P]) -> Result<Vec<Row>, Error>
101    where
102        P: Clone + Into<Value>,
103    {
104        let params = params_to_values(params);
105        let rows = self.stmt.all(Some(&params)).map_err(Error::from)?;
106        Ok(rows.into_iter().map(row_to_object).collect())
107    }
108
109    pub fn release(&self) -> Result<bool, Error> {
110        Ok(self.stmt.release())
111    }
112}
113
114#[derive(Clone, Copy)]
115pub struct Database {
116    handle: u32,
117}
118
119impl Database {
120    pub fn exec<P>(&self, sql: &str, params: &[P]) -> Result<u64, Error>
121    where
122        P: Clone + Into<Value>,
123    {
124        let params = params_to_values(params);
125        exec(self.handle, sql, Some(&params)).map_err(Error::from)
126    }
127
128    pub fn prepare(&self, sql: &str) -> Result<Statement, Error> {
129        let stmt = prepare(self.handle, sql).map_err(Error::from)?;
130        Ok(Statement { stmt })
131    }
132
133    pub fn transaction<'a, F, T, A>(&'a self, mut f: F) -> impl FnMut(A) -> Result<T, Error> + 'a
134    where
135        F: FnMut(A) -> Result<T, Error> + 'a,
136    {
137        move |arg| {
138            self.exec("BEGIN", &NO_PARAMS)?;
139            match f(arg) {
140                Ok(result) => {
141                    self.exec("COMMIT", &NO_PARAMS)?;
142                    Ok(result)
143                }
144                Err(err) => {
145                    let _ = self.exec("ROLLBACK", &NO_PARAMS);
146                    Err(err)
147                }
148            }
149        }
150    }
151
152    pub fn close(&self) -> Result<(), Error> {
153        close(self.handle).map_err(Error::from)
154    }
155}
156
157pub fn open(uri: &str) -> Result<Database, Error> {
158    let uri = if uri.starts_with("file:") {
159        uri.to_string()
160    } else {
161        format!("file:{uri}")
162    };
163    let handle = open_db(&uri).map_err(Error::from)?;
164    Ok(Database { handle })
165}
166
167pub fn row_value<'a>(row: &'a Row, name: &str) -> Option<&'a Value> {
168    row.get(name)
169}
170
171fn row_to_object(row: bindings::wasm::sqlite_wasi::sqlite::SqliteRow) -> Row {
172    row.columns
173        .into_iter()
174        .zip(row.values)
175        .collect::<BTreeMap<_, _>>()
176}
177
178fn params_to_values<P>(params: &[P]) -> Vec<Value>
179where
180    P: Clone + Into<Value>,
181{
182    params.iter().cloned().map(Into::into).collect()
183}