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 type RunInfo = SqliteRunInfo;
16pub type Row = BTreeMap<String, Value>;
17
18#[derive(Debug, Clone)]
19pub struct Error {
20    pub code: i32,
21    pub message: String,
22}
23
24impl std::fmt::Display for Error {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        write!(f, "{} (code: {})", self.message, self.code)
27    }
28}
29
30impl std::error::Error for Error {}
31
32impl From<SqliteError> for Error {
33    fn from(value: SqliteError) -> Self {
34        Self {
35            code: value.code,
36            message: value.message,
37        }
38    }
39}
40
41pub struct Statement {
42    stmt: bindings::wasm::sqlite_wasi::sqlite::Statement,
43}
44
45impl Statement {
46    pub fn run(&self, params: &[Value]) -> Result<RunInfo, Error> {
47        self.stmt.run(Some(params)).map_err(Into::into)
48    }
49
50    pub fn one(&self, params: &[Value]) -> Result<Option<Row>, Error> {
51        let row = self.stmt.one(Some(params)).map_err(Error::from)?;
52        Ok(row.map(row_to_object))
53    }
54
55    pub fn all(&self, params: &[Value]) -> Result<Vec<Row>, Error> {
56        let rows = self.stmt.all(Some(params)).map_err(Error::from)?;
57        Ok(rows.into_iter().map(row_to_object).collect())
58    }
59
60    pub fn release(&self) -> Result<bool, Error> {
61        Ok(self.stmt.release())
62    }
63}
64
65#[derive(Clone, Copy)]
66pub struct Database {
67    handle: u32,
68}
69
70impl Database {
71    pub fn exec(&self, sql: &str, params: &[Value]) -> Result<u64, Error> {
72        exec(self.handle, sql, Some(params)).map_err(Error::from)
73    }
74
75    pub fn prepare(&self, sql: &str) -> Result<Statement, Error> {
76        let stmt = prepare(self.handle, sql).map_err(Error::from)?;
77        Ok(Statement { stmt })
78    }
79
80    pub fn transaction<'a, F, T, A>(&'a self, mut f: F) -> impl FnMut(A) -> Result<T, Error> + 'a
81    where
82        F: FnMut(A) -> Result<T, Error> + 'a,
83    {
84        move |arg| {
85            self.exec("BEGIN", &[])?;
86            match f(arg) {
87                Ok(result) => {
88                    self.exec("COMMIT", &[])?;
89                    Ok(result)
90                }
91                Err(err) => {
92                    let _ = self.exec("ROLLBACK", &[]);
93                    Err(err)
94                }
95            }
96        }
97    }
98
99    pub fn close(&self) -> Result<(), Error> {
100        close(self.handle).map_err(Error::from)
101    }
102}
103
104pub fn open(uri: &str) -> Result<Database, Error> {
105    let uri = if uri.starts_with("file:") {
106        uri.to_string()
107    } else {
108        format!("file:{uri}")
109    };
110    let handle = open_db(&uri).map_err(Error::from)?;
111    Ok(Database { handle })
112}
113
114pub fn row_value<'a>(row: &'a Row, name: &str) -> Option<&'a Value> {
115    row.get(name)
116}
117
118fn row_to_object(row: bindings::wasm::sqlite_wasi::sqlite::SqliteRow) -> Row {
119    row.columns
120        .into_iter()
121        .zip(row.values)
122        .collect::<BTreeMap<_, _>>()
123}