spin_sdk/sqlite.rs
1use crate::wit_bindgen;
2
3#[doc(hidden)]
4/// Module containing wit bindgen generated code.
5///
6/// This is only meant for internal consumption.
7pub mod wit {
8 #![allow(missing_docs)]
9 use crate::wit_bindgen;
10
11 wit_bindgen::generate!({
12 runtime_path: "crate::wit_bindgen::rt",
13 world: "spin-sdk-sqlite",
14 path: "wit",
15 generate_all,
16 });
17
18 pub use spin::sqlite::sqlite;
19}
20
21#[doc(inline)]
22pub use wit::sqlite::{Error, Value};
23
24/// An open connection to a SQLite database.
25///
26/// [Connection::execute()] returns a tuple of `(columns, rows_stream, finish_future)`
27/// where rows are consumed from a stream and the finish future is awaited to check
28/// for errors.
29///
30/// # Examples
31///
32/// Open the default database, query rows, and iterate over the stream.
33///
34/// ```no_run
35/// # async fn run() -> anyhow::Result<()> {
36/// use spin_sdk::sqlite::{Connection, Value};
37///
38/// let min_age = 0;
39/// let db = Connection::open_default().await?;
40///
41/// let mut query_result = db.execute(
42/// "SELECT * FROM users WHERE age >= ?",
43/// [Value::Integer(min_age)],
44/// ).await?;
45///
46/// let name_idx = query_result.columns().iter().position(|c| c == "name").unwrap();
47///
48/// while let Some(row) = query_result.next().await {
49/// let name: &str = row.get(name_idx).unwrap();
50/// println!("Found user {name}");
51/// }
52///
53/// query_result.result().await?;
54/// # Ok(())
55/// # }
56/// ```
57///
58/// Perform an aggregate (scalar) operation over a named database.
59///
60/// ```no_run
61/// # async fn run() -> anyhow::Result<()> {
62/// use spin_sdk::sqlite::Connection;
63///
64/// let db = Connection::open("customer-data").await?;
65/// let mut query_result = db.execute("SELECT COUNT(*) FROM users", []).await?;
66///
67/// if let Some(row) = query_result.next().await {
68/// let count: i64 = row.get(0).unwrap();
69/// println!("Total users: {count}");
70/// }
71///
72/// query_result.result().await?;
73/// # Ok(())
74/// # }
75/// ```
76///
77/// Delete rows from a database. The row stream will be empty, but the finish
78/// future must still be awaited.
79///
80/// ```no_run
81/// # async fn run() -> anyhow::Result<()> {
82/// use spin_sdk::sqlite::{Connection, Value};
83///
84/// let min_age = 18;
85/// let db = Connection::open("customer-data").await?;
86/// let query_result = db.execute(
87/// "DELETE FROM users WHERE age < ?",
88/// [Value::Integer(min_age)],
89/// ).await?;
90///
91/// query_result.result().await?;
92/// # Ok(())
93/// # }
94/// ```
95pub struct Connection(wit::sqlite::Connection);
96
97impl Connection {
98 /// Open a connection to the default database
99 pub async fn open_default() -> Result<Self, Error> {
100 Self::open("default").await
101 }
102
103 /// Open a connection to a named database instance.
104 ///
105 /// If `database` is "default", the default instance is opened.
106 ///
107 /// `error::no-such-database` will be raised if the `name` is not recognized.
108 pub async fn open(database: impl AsRef<str>) -> Result<Self, Error> {
109 wit::sqlite::Connection::open_async(database.as_ref().to_string())
110 .await
111 .map(Connection)
112 }
113
114 /// Execute a statement returning back data if there is any
115 pub async fn execute(
116 &self,
117 statement: impl AsRef<str>,
118 parameters: impl IntoIterator<Item = Value>,
119 ) -> Result<QueryResult, Error> {
120 let (columns, rows, result) = self
121 .0
122 .execute_async(
123 statement.as_ref().to_string(),
124 parameters.into_iter().collect(),
125 )
126 .await?;
127 Ok(QueryResult {
128 columns,
129 rows,
130 result,
131 })
132 }
133
134 /// The SQLite rowid of the most recent successful INSERT on the connection, or 0 if
135 /// there has not yet been an INSERT on the connection.
136 pub async fn last_insert_rowid(&self) -> i64 {
137 self.0.last_insert_rowid_async().await
138 }
139
140 /// The number of rows modified, inserted or deleted by the most recently completed
141 /// INSERT, UPDATE or DELETE statement on the connection.
142 pub async fn changes(&self) -> u64 {
143 self.0.changes_async().await
144 }
145}
146
147/// The result of a [`Connection::execute`] operation.
148pub struct QueryResult {
149 columns: Vec<String>,
150 rows: wit_bindgen::StreamReader<RowResult>,
151 result: wit_bindgen::FutureReader<Result<(), Error>>,
152}
153
154impl QueryResult {
155 /// The columns in the query result.
156 pub fn columns(&self) -> &[String] {
157 &self.columns
158 }
159
160 /// Gets the next row in the result set.
161 ///
162 /// If this is `None`, there are no more rows available. You _must_
163 /// await [`QueryResult::result()`] to determine if all rows
164 /// were read successfully.
165 pub async fn next(&mut self) -> Option<RowResult> {
166 self.rows.next().await
167 }
168
169 /// Whether the query completed successfully or with an error.
170 pub async fn result(self) -> Result<(), Error> {
171 self.result.await
172 }
173
174 /// Collect all rows in the result set.
175 ///
176 /// This is provided for when the result set is small enough to fit in
177 /// memory and you do not require streaming behaviour.
178 pub async fn collect(self) -> Result<Vec<RowResult>, Error> {
179 let rows = self.rows.collect().await;
180 self.result.await?;
181 Ok(rows)
182 }
183
184 /// Extracts the underlying Wasm Component Model results of the query.
185 #[allow(clippy::type_complexity, reason = "that's what the inner bits are")]
186 pub fn into_inner(
187 self,
188 ) -> (
189 Vec<String>,
190 wit_bindgen::StreamReader<RowResult>,
191 wit_bindgen::FutureReader<Result<(), Error>>,
192 ) {
193 (self.columns, self.rows, self.result)
194 }
195}
196
197/// A single row from a SQLite query result.
198///
199/// `RowResult` provides index-based access to column values via [`RowResult::get()`].
200///
201/// # Examples
202///
203/// Consume rows from the async streaming API:
204///
205/// ```no_run
206/// # async fn run() -> anyhow::Result<()> {
207/// use spin_sdk::sqlite::{Connection, Value};
208///
209/// let db = Connection::open_default().await?;
210/// let mut query_result = db.execute(
211/// "SELECT name, age FROM users WHERE age >= ?",
212/// [Value::Integer(0)],
213/// ).await?;
214///
215/// let name_idx = query_result.columns().iter().position(|c| c == "name").unwrap();
216///
217/// while let Some(row) = query_result.next().await {
218/// let name: &str = row.get(name_idx).unwrap();
219/// println!("Found user {name}");
220/// }
221///
222/// query_result.result().await?;
223/// # Ok(())
224/// # }
225/// ```
226#[doc(inline)]
227pub use wit::sqlite::RowResult;
228
229impl RowResult {
230 /// Get a value by its column name. The value is converted to the target type.
231 ///
232 /// * SQLite integers are convertible to Rust integer types (i8, u8, i16, etc. including usize and isize) and bool.
233 /// * SQLite strings are convertible to Rust &str or &[u8] (encoded as UTF-8).
234 /// * SQLite reals are convertible to Rust f64.
235 /// * SQLite blobs are convertible to Rust &[u8] or &str (interpreted as UTF-8).
236 ///
237 /// To look up by name, you can use `QueryResult::rows()` or obtain the invoice from `QueryResult::columns`.
238 /// If you do not know the type of a value, access the underlying [Value] enum directly
239 /// via the [RowResult::values] field
240 ///
241 /// # Examples
242 ///
243 /// ```no_run
244 /// # async fn run() -> anyhow::Result<()> {
245 /// use spin_sdk::sqlite::{Connection, Value};
246 ///
247 /// let db = Connection::open_default().await?;
248 /// let mut query_result = db.execute(
249 /// "SELECT name, age FROM users WHERE id = ?",
250 /// [Value::Integer(0)],
251 /// ).await?;
252 ///
253 /// if let Some(row) = query_result.next().await {
254 /// let name: &str = row.get(0).unwrap();
255 /// let age: u16 = row.get(1).unwrap();
256 /// println!("{name} is {age} years old");
257 /// }
258 ///
259 /// query_result.result().await?;
260 /// # Ok(())
261 /// # }
262 /// ```
263 pub fn get<'a, T: TryFrom<&'a Value>>(&'a self, index: usize) -> Option<T> {
264 self.values.get(index).and_then(|c| c.try_into().ok())
265 }
266}
267
268impl<'a> TryFrom<&'a Value> for bool {
269 type Error = ();
270
271 fn try_from(value: &'a Value) -> Result<Self, Self::Error> {
272 match value {
273 Value::Integer(i) => Ok(*i != 0),
274 _ => Err(()),
275 }
276 }
277}
278
279macro_rules! int_conversions {
280 ($($t:ty),*) => {
281 $(impl<'a> TryFrom<&'a Value> for $t {
282 type Error = ();
283
284 fn try_from(value: &'a Value) -> Result<Self, Self::Error> {
285 match value {
286 Value::Integer(i) => (*i).try_into().map_err(|_| ()),
287 _ => Err(()),
288 }
289 }
290 })*
291 };
292}
293
294int_conversions!(u8, u16, u32, u64, i8, i16, i32, i64, usize, isize);
295
296impl<'a> TryFrom<&'a Value> for f64 {
297 type Error = ();
298
299 fn try_from(value: &'a Value) -> Result<Self, Self::Error> {
300 match value {
301 Value::Real(f) => Ok(*f),
302 _ => Err(()),
303 }
304 }
305}
306
307impl<'a> TryFrom<&'a Value> for &'a str {
308 type Error = ();
309
310 fn try_from(value: &'a Value) -> Result<Self, Self::Error> {
311 match value {
312 Value::Text(s) => Ok(s.as_str()),
313 Value::Blob(b) => std::str::from_utf8(b).map_err(|_| ()),
314 _ => Err(()),
315 }
316 }
317}
318
319impl<'a> TryFrom<&'a Value> for &'a [u8] {
320 type Error = ();
321
322 fn try_from(value: &'a Value) -> Result<Self, Self::Error> {
323 match value {
324 Value::Blob(b) => Ok(b.as_slice()),
325 Value::Text(s) => Ok(s.as_bytes()),
326 _ => Err(()),
327 }
328 }
329}