Skip to main content

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_from_value {
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_from_value!(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}
330
331impl Value {
332    /// Creates a Text parameter.
333    pub fn text(value: impl Into<String>) -> Self {
334        Self::Text(value.into())
335    }
336
337    /// Creates an Integer parameter.
338    pub fn integer(value: impl Into<i64>) -> Self {
339        Self::Integer(value.into())
340    }
341
342    /// Creates a Real parameter.
343    pub fn real(value: impl Into<f64>) -> Self {
344        Self::Real(value.into())
345    }
346
347    /// Creates a Blob parameter.
348    pub fn blob(value: impl Into<Vec<u8>>) -> Self {
349        Self::Blob(value.into())
350    }
351}
352
353impl From<&str> for Value {
354    fn from(value: &str) -> Self {
355        Self::Text(value.into())
356    }
357}
358
359impl From<String> for Value {
360    fn from(value: String) -> Self {
361        Self::Text(value)
362    }
363}
364
365macro_rules! value_from_int {
366    ($($t:ty),*) => {
367        $(impl From<$t> for Value {
368            fn from(value: $t) -> Self {
369                Self::integer(value)
370            }
371        })*
372    };
373}
374
375value_from_int!(u8, u16, u32, i8, i16, i32, i64);
376
377impl From<f32> for Value {
378    fn from(value: f32) -> Self {
379        Self::Real(value.into())
380    }
381}
382
383impl From<f64> for Value {
384    fn from(value: f64) -> Self {
385        Self::Real(value)
386    }
387}
388
389impl From<&[u8]> for Value {
390    fn from(value: &[u8]) -> Self {
391        Self::Blob(value.into())
392    }
393}
394
395impl<const N: usize> From<[u8; N]> for Value {
396    fn from(value: [u8; N]) -> Self {
397        Self::Blob(value.into())
398    }
399}
400
401impl<const N: usize> From<&[u8; N]> for Value {
402    fn from(value: &[u8; N]) -> Self {
403        Self::Blob(value.into())
404    }
405}
406
407impl From<Vec<u8>> for Value {
408    fn from(value: Vec<u8>) -> Self {
409        Self::Blob(value)
410    }
411}
412
413impl<T: Into<Value>> From<Option<T>> for Value {
414    fn from(value: Option<T>) -> Self {
415        match value {
416            None => Value::Null,
417            Some(value) => value.into(),
418        }
419    }
420}
421
422impl PartialEq for Value {
423    fn eq(&self, other: &Self) -> bool {
424        match (self, other) {
425            (Self::Integer(l0), Self::Integer(r0)) => l0 == r0,
426            (Self::Real(l0), Self::Real(r0)) => l0 == r0,
427            (Self::Text(l0), Self::Text(r0)) => l0 == r0,
428            (Self::Blob(l0), Self::Blob(r0)) => l0 == r0,
429            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
430        }
431    }
432}
433
434#[cfg(test)]
435mod test {
436    use super::*;
437
438    #[test]
439    fn value_conversions() {
440        let expected_text = Value::Text("a".to_string());
441        let expected_int = Value::Integer(123);
442        let expected_real = Value::Real(1234.5); // the test wants equality and FP stuff is notoriously inexact: use a value for testing that won't incur off-by-0.00000001 errors
443        let expected_real_int = Value::Real(123.0);
444        let expected_blob = Value::Blob(vec![1, 2, 3]);
445
446        assert_eq!(expected_text, Value::text("a"));
447        assert_eq!(expected_text, "a".into());
448        assert_eq!(expected_text, "a".to_string().into());
449
450        assert_eq!(expected_int, Value::integer(123u8));
451        assert_eq!(expected_int, Value::integer(123i16));
452        assert_eq!(expected_int, Value::integer(123u32));
453        assert_eq!(expected_int, Value::integer(123i64));
454        assert_eq!(expected_int, 123u8.into());
455        assert_eq!(expected_int, 123i16.into());
456        assert_eq!(expected_int, 123u32.into());
457        assert_eq!(expected_int, 123i64.into());
458
459        assert_eq!(expected_real, Value::real(1234.5f32));
460        assert_eq!(expected_real, Value::real(1234.5f64));
461        assert_eq!(expected_real, 1234.5f32.into());
462        assert_eq!(expected_real, 1234.5f64.into());
463        // named function allows passing integer to Real case (where `into()` would give you the Integer case)
464        assert_eq!(expected_real_int, Value::real(123u32));
465
466        assert_eq!(expected_blob, Value::blob([1, 2, 3]));
467        assert_eq!(expected_blob, Value::blob(vec![1, 2, 3]));
468        assert_eq!(expected_blob, (&[1, 2, 3]).into());
469        assert_eq!(expected_blob, ([1, 2, 3][..]).into());
470        assert_eq!(expected_blob, [1, 2, 3].into());
471        assert_eq!(expected_blob, (vec![1, 2, 3]).into());
472
473        assert_eq!(Value::Null, None::<i16>.into());
474        assert_eq!(expected_int, Some(123u32).into());
475    }
476}