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