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}