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(¶ms)).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(¶ms)).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(¶ms)).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(¶ms)).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}