Skip to main content

limbo/
params.rs

1//! This module contains all `Param` related utilities and traits.
2
3use crate::{Error, Result, Value};
4use std::borrow::Cow;
5
6mod sealed {
7    pub trait Sealed {}
8}
9
10use sealed::Sealed;
11
12/// Converts some type into parameters that can be passed
13/// to libsql.
14///
15/// The trait is sealed and not designed to be implemented by hand
16/// but instead provides a few ways to use it.
17///
18/// # Passing parameters to libsql
19///
20/// Many functions in this library let you pass parameters to libsql. Doing this
21/// lets you avoid any risk of SQL injection, and is simpler than escaping
22/// things manually. These functions generally contain some parameter that generically
23/// accepts some implementation this trait.
24///
25/// # Positional parameters
26///
27/// These can be supplied in a few ways:
28///
29/// - For heterogeneous parameter lists of 16 or less items a tuple syntax is supported
30///     by doing `(1, "foo")`.
31/// - For heterogeneous parameter lists of 16 or greater, the `limbo::params!` macro is supported
32///     by doing `limbo::params![1, "foo"]`.
33/// - For homogeneous parameter types (where they are all the same type), const arrays are
34///     supported by doing `[1, 2, 3]`.
35///
36/// # Example (positional)
37///
38/// ```rust,no_run
39/// # use limbo::{Connection, params};
40/// # async fn run(conn: Connection) -> limbo::Result<()> {
41/// let mut stmt = conn.prepare("INSERT INTO test (a, b) VALUES (?1, ?2)").await?;
42///
43/// // Using a tuple:
44/// stmt.execute((0, "foobar")).await?;
45///
46/// // Using `limbo::params!`:
47/// stmt.execute(params![1i32, "blah"]).await?;
48///
49/// // array literal — non-references
50/// stmt.execute([2i32, 3i32]).await?;
51///
52/// // array literal — references
53/// stmt.execute(["foo", "bar"]).await?;
54///
55/// // Slice literal, references:
56/// stmt.execute([2i32, 3i32]).await?;
57///
58/// #    Ok(())
59/// # }
60/// ```
61///
62/// # Named parameters
63///
64/// - For heterogeneous parameter lists of 16 or less items a tuple syntax is supported
65///     by doing `(("key1", 1), ("key2", "foo"))`.
66/// - For heterogeneous parameter lists of 16 or greater, the `limbo::params!` macro is supported
67///     by doing `limbo::named_params!["key1": 1, "key2": "foo"]`.
68/// - For homogeneous parameter types (where they are all the same type), const arrays are
69///     supported by doing `[("key1", 1), ("key2, 2), ("key3", 3)]`.
70///
71/// # Example (named)
72///
73/// ```rust,no_run
74/// # use limbo::{Connection, named_params};
75/// # async fn run(conn: Connection) -> limbo::Result<()> {
76/// let mut stmt = conn.prepare("INSERT INTO test (a, b) VALUES (:key1, :key2)").await?;
77///
78/// // Using a tuple:
79/// stmt.execute(((":key1", 0), (":key2", "foobar"))).await?;
80///
81/// // Using `limbo::named_params!`:
82/// stmt.execute(named_params! {":key1": 1i32, ":key2": "blah" }).await?;
83///
84/// // const array:
85/// stmt.execute([(":key1", 2i32), (":key2", 3i32)]).await?;
86///
87/// #   Ok(())
88/// # }
89/// ```
90pub trait IntoParams: Sealed {
91    // Hide this because users should not be implementing this
92    // themselves. We should consider sealing this trait.
93    #[doc(hidden)]
94    fn into_params(self) -> Result<Params>;
95}
96
97#[derive(Debug, Clone)]
98#[doc(hidden)]
99pub enum Params {
100    None,
101    Positional(Vec<Value>),
102    /// `(name, value)` pairs. `name` includes its `:`/`@`/`$`/`#` prefix
103    /// character, exactly as `oxisqlite_core::parameters::Parameters` stores
104    /// and looks it up. `Cow` so the overwhelmingly common case — a
105    /// `'static` placeholder-name literal, e.g. `[(":name", value)]` — is
106    /// borrowed rather than allocated; owned `String` keys still work and
107    /// allocate as before.
108    Named(Vec<(Cow<'static, str>, Value)>),
109}
110
111/// Convert an owned iterator into Params.
112///
113/// # Example
114///
115/// ```rust
116/// # use limbo::{Connection, params_from_iter, Rows};
117/// # async fn run(conn: &Connection) {
118///
119/// let iter = vec![1, 2, 3];
120///
121/// conn.query(
122///     "SELECT * FROM users WHERE id IN (?1, ?2, ?3)",
123///     params_from_iter(iter)
124/// )
125/// .await
126/// .unwrap();
127/// # }
128/// ```
129pub fn params_from_iter<I>(iter: I) -> impl IntoParams
130where
131    I: IntoIterator,
132    I::Item: IntoValue,
133{
134    iter.into_iter().collect::<Vec<_>>()
135}
136
137impl Sealed for () {}
138impl IntoParams for () {
139    fn into_params(self) -> Result<Params> {
140        Ok(Params::None)
141    }
142}
143
144impl Sealed for Params {}
145impl IntoParams for Params {
146    fn into_params(self) -> Result<Params> {
147        Ok(self)
148    }
149}
150
151impl<T: IntoValue> Sealed for Vec<T> {}
152impl<T: IntoValue> IntoParams for Vec<T> {
153    fn into_params(self) -> Result<Params> {
154        let values = self
155            .into_iter()
156            .map(|i| i.into_value())
157            .collect::<Result<Vec<_>>>()?;
158
159        Ok(Params::Positional(values))
160    }
161}
162
163impl<T: IntoValue> Sealed for Vec<(String, T)> {}
164impl<T: IntoValue> IntoParams for Vec<(String, T)> {
165    fn into_params(self) -> Result<Params> {
166        let values = self
167            .into_iter()
168            .map(|(k, v)| Ok((Cow::Owned(k), v.into_value()?)))
169            .collect::<Result<Vec<_>>>()?;
170
171        Ok(Params::Named(values))
172    }
173}
174
175impl<T: IntoValue, const N: usize> Sealed for [T; N] {}
176impl<T: IntoValue, const N: usize> IntoParams for [T; N] {
177    fn into_params(self) -> Result<Params> {
178        self.into_iter().collect::<Vec<_>>().into_params()
179    }
180}
181
182impl<T: IntoValue, const N: usize> Sealed for [(&'static str, T); N] {}
183impl<T: IntoValue, const N: usize> IntoParams for [(&'static str, T); N] {
184    fn into_params(self) -> Result<Params> {
185        // `k` is `&'static str` — almost always a placeholder-name literal
186        // such as `":name"` — so it borrows straight into `Params::Named`
187        // with no allocation (see `Params::Named`'s `Cow` field).
188        let values = self
189            .into_iter()
190            .map(|(k, v)| Ok((Cow::Borrowed(k), v.into_value()?)))
191            .collect::<Result<Vec<_>>>()?;
192
193        Ok(Params::Named(values))
194    }
195}
196
197impl<T: IntoValue + Clone, const N: usize> Sealed for &[T; N] {}
198impl<T: IntoValue + Clone, const N: usize> IntoParams for &[T; N] {
199    fn into_params(self) -> Result<Params> {
200        self.iter().cloned().collect::<Vec<_>>().into_params()
201    }
202}
203
204// NOTICE: heavily inspired by rusqlite
205macro_rules! tuple_into_params {
206    ($count:literal : $(($field:tt $ftype:ident)),* $(,)?) => {
207        impl<$($ftype,)*> Sealed for ($($ftype,)*) where $($ftype: IntoValue,)* {}
208        impl<$($ftype,)*> IntoParams for ($($ftype,)*) where $($ftype: IntoValue,)* {
209            fn into_params(self) -> Result<Params> {
210                let params = Params::Positional(vec![$(self.$field.into_value()?),*]);
211                Ok(params)
212            }
213        }
214    }
215}
216
217macro_rules! named_tuple_into_params {
218    ($count:literal : $(($field:tt $ftype:ident)),* $(,)?) => {
219        impl<$($ftype,)*> Sealed for ($((&'static str, $ftype),)*) where $($ftype: IntoValue,)* {}
220        impl<$($ftype,)*> IntoParams for ($((&'static str, $ftype),)*) where $($ftype: IntoValue,)* {
221            fn into_params(self) -> Result<Params> {
222                // `self.$field.0` is `&'static str`, so it borrows straight
223                // into `Cow` with no allocation (see `Params::Named`).
224                let params = Params::Named(vec![$((Cow::Borrowed(self.$field.0), self.$field.1.into_value()?)),*]);
225                Ok(params)
226            }
227        }
228    }
229}
230
231named_tuple_into_params!(2: (0 A), (1 B));
232named_tuple_into_params!(3: (0 A), (1 B), (2 C));
233named_tuple_into_params!(4: (0 A), (1 B), (2 C), (3 D));
234named_tuple_into_params!(5: (0 A), (1 B), (2 C), (3 D), (4 E));
235named_tuple_into_params!(6: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F));
236named_tuple_into_params!(7: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G));
237named_tuple_into_params!(8: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H));
238named_tuple_into_params!(9: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I));
239named_tuple_into_params!(10: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J));
240named_tuple_into_params!(11: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K));
241named_tuple_into_params!(12: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L));
242named_tuple_into_params!(13: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M));
243named_tuple_into_params!(14: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N));
244named_tuple_into_params!(15: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O));
245named_tuple_into_params!(16: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O), (15 P));
246
247tuple_into_params!(2: (0 A), (1 B));
248tuple_into_params!(3: (0 A), (1 B), (2 C));
249tuple_into_params!(4: (0 A), (1 B), (2 C), (3 D));
250tuple_into_params!(5: (0 A), (1 B), (2 C), (3 D), (4 E));
251tuple_into_params!(6: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F));
252tuple_into_params!(7: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G));
253tuple_into_params!(8: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H));
254tuple_into_params!(9: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I));
255tuple_into_params!(10: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J));
256tuple_into_params!(11: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K));
257tuple_into_params!(12: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L));
258tuple_into_params!(13: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M));
259tuple_into_params!(14: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N));
260tuple_into_params!(15: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O));
261tuple_into_params!(16: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O), (15 P));
262
263// TODO: Should we rename this to `ToSql` which makes less sense but
264// matches the error variant we have in `Error`. Or should we change the
265// error variant to match this breaking the few people that currently use
266// this error variant.
267pub trait IntoValue {
268    fn into_value(self) -> Result<Value>;
269}
270
271impl<T> IntoValue for T
272where
273    T: TryInto<Value>,
274    T::Error: Into<crate::BoxError>,
275{
276    fn into_value(self) -> Result<Value> {
277        self.try_into()
278            .map_err(|e| Error::ToSqlConversionFailure(e.into()))
279    }
280}
281
282impl IntoValue for Result<Value> {
283    fn into_value(self) -> Result<Value> {
284        self
285    }
286}
287
288/// Construct positional params from a hetergeneous set of params types.
289#[macro_export]
290macro_rules! params {
291    () => {
292       ()
293    };
294    ($($value:expr),* $(,)?) => {{
295        use $crate::params::IntoValue;
296        [$($value.into_value()),*]
297
298    }};
299}
300
301/// Construct named params from a hetergeneous set of params types.
302#[macro_export]
303macro_rules! named_params {
304    () => {
305        ()
306    };
307    ($($param_name:literal: $value:expr),* $(,)?) => {{
308        use $crate::params::IntoValue;
309        [$(($param_name, $value.into_value())),*]
310    }};
311}
312
313#[cfg(test)]
314mod tests {
315    use crate::Value;
316
317    #[test]
318    fn test_serialize_array() {
319        assert_eq!(
320            params!([0; 16])[0].as_ref().unwrap(),
321            &Value::Blob(vec![0; 16])
322        );
323    }
324}