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_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}