Skip to main content

sqlite3_ext/query/
params.rs

1use super::Statement;
2use crate::{ffi, sqlite3_match_version, sqlite3_require_version, types::*, value::*};
3use sealed::sealed;
4
5/// Create a [Params] with values of mixed types.
6///
7/// If all of the parameters to a query are of the same type, a simple array can be used to
8/// bind them. If you want to pass multiple values, you need to use this macro.
9///
10/// # Syntax
11///
12/// This macro works like an array expression, except the values do not have to be the same
13/// type (and is more performant than an array of `dyn ToParam`).
14///
15/// ```no_run
16/// use sqlite3_ext::{Connection, Result, params};
17///
18/// fn do_thing(conn: &Connection) -> Result<i64> {
19///     conn.execute(
20///         "INSERT INTO tbl VALUES (?, ?)",
21///         params![1024, "one thousand twenty four"],
22///     )
23/// }
24/// ```
25///
26/// Named parameters are always provided as a tuple, and work with this macro or the normal
27/// array syntax.
28///
29/// ```no_run
30/// use sqlite3_ext::{Connection, Result, params};
31///
32/// fn do_thing(conn: &Connection) -> Result<i64> {
33///     conn.execute(
34///         "INSERT INTO tbl VALUES (:number, :name)",
35///         params![(":name", "one thousand twenty four"), (":number", 1024)],
36///     )
37/// }
38/// ```
39#[macro_export]
40macro_rules! params {
41    ($($val:expr),* $(,)?) => {
42        |stmt: &mut $crate::query::Statement| {{
43            #![allow(unused_assignments)]
44            use $crate::query::ToParam;
45            let mut i = 1i32;
46            $(
47            $val.bind_param(stmt, i)?;
48            i += 1;
49            )*
50            Ok(())
51        }}
52    }
53}
54
55/// Trait for collections of parameters to a query.
56///
57/// This is a private trait with no public API. There are existing implementations which should
58/// cover most use cases:
59///
60/// - An empty tuple (`()`) binds no parameters to the query.
61/// - An array binds parameters that are all the same type.
62/// - The [params!] macro binds parameters of arbitrary types.
63/// - A closure can arbitrarily bind parameters.
64///
65/// Named parameters are implemented by using a tuple of `("name", value)`, and can be in any
66/// order. See [params!] for an example.
67///
68/// # Using a closure
69///
70/// If you are dynamically creating SQL queries and need to dynamically bind parameters to
71/// them, you can use a closure to accomplish this.
72///
73/// ```no_run
74/// use sqlite3_ext::{Connection, Result, query::{ Statement, ToParam }};
75///
76/// fn do_thing(conn: &Connection) -> Result<i64> {
77///     conn.prepare("INSERT INTO tbl VALUES (?, ?)")?
78///         .execute(|stmt: &mut Statement| {
79///             "foo".bind_param(stmt, 1)?;
80///             "bar".bind_param(stmt, 2)?;
81///             Ok(())
82///         })
83/// }
84/// ```
85pub trait Params {
86    fn bind_params(self, stmt: &mut Statement) -> Result<()>;
87}
88
89impl Params for () {
90    fn bind_params(self, _: &mut Statement) -> Result<()> {
91        Ok(())
92    }
93}
94
95impl<T> Params for T
96where
97    T: FnOnce(&mut Statement) -> Result<()>,
98{
99    fn bind_params(self, stmt: &mut Statement) -> Result<()> {
100        self(stmt)
101    }
102}
103
104impl<T: ToParam> Params for Vec<T> {
105    fn bind_params(self, stmt: &mut Statement) -> Result<()> {
106        for (pos, val) in self.into_iter().enumerate() {
107            val.bind_param(stmt, pos as i32 + 1)?;
108        }
109        Ok(())
110    }
111}
112
113impl<T: ToParam, const N: usize> Params for [T; N] {
114    fn bind_params(self, stmt: &mut Statement) -> Result<()> {
115        for (pos, val) in self.into_iter().enumerate() {
116            val.bind_param(stmt, pos as i32 + 1)?;
117        }
118        Ok(())
119    }
120}
121
122impl Params for &mut [&mut ValueRef] {
123    fn bind_params(self, stmt: &mut Statement) -> Result<()> {
124        for (pos, val) in self.into_iter().enumerate() {
125            val.bind_param(stmt, pos as i32 + 1)?;
126        }
127        Ok(())
128    }
129}
130
131/// Trait for types which can be passed into SQLite queries as parameters.
132#[sealed]
133pub trait ToParam {
134    /// Bind this value to the prepared Statement at the provided position.
135    ///
136    /// Note: the position of a named parameter can be obtained using
137    /// [Statement::parameter_position].
138    fn bind_param(self, stmt: &mut Statement, position: i32) -> Result<()>;
139}
140
141macro_rules! to_param {
142    ($(#[$attr:meta])* $ty:ty as ($stmt:ident, $pos:ident, $val:ident) => $impl:expr) => {
143        $(#[$attr])*
144        #[sealed]
145        impl ToParam for $ty {
146            fn bind_param(self, stmt: &mut Statement, $pos: i32) -> Result<()> {
147                let $val = self;
148                let $stmt = stmt.base;
149                Error::from_sqlite(unsafe { $impl })
150            }
151        }
152    };
153}
154
155to_param!(() as (stmt, pos, _val) => ffi::sqlite3_bind_null(stmt, pos));
156to_param!(bool as (stmt, pos, val) => ffi::sqlite3_bind_int(stmt, pos, val as i32));
157to_param!(i64 as (stmt, pos, val) => ffi::sqlite3_bind_int64(stmt, pos, val));
158to_param!(f64 as (stmt, pos, val) => ffi::sqlite3_bind_double(stmt, pos, val));
159to_param!(Blob as (stmt, pos, val) => {
160    let len = val.len();
161    let rc = sqlite3_match_version! {
162        3_008_007 => ffi::sqlite3_bind_blob64(stmt, pos, val.into_raw(), len as _, Some(ffi::drop_blob)),
163        _ => ffi::sqlite3_bind_blob(stmt, pos, val.into_raw(), len as _, Some(ffi::drop_blob)),
164    };
165    rc
166});
167to_param!(&mut ValueRef as (stmt, pos, val) => ffi::sqlite3_bind_value(stmt, pos, val.as_ptr()));
168
169#[sealed]
170impl<'a> ToParam for &'a str {
171    fn bind_param(self, stmt: &mut Statement, pos: i32) -> Result<()> {
172        let val = self.as_bytes();
173        let len = val.len();
174        Error::from_sqlite(unsafe {
175            sqlite3_match_version! {
176                3_008_007 => ffi::sqlite3_bind_text64(stmt.base, pos, val.as_ptr() as _, len as _, ffi::sqlite_transient(), ffi::SQLITE_UTF8 as _),
177                _ => ffi::sqlite3_bind_text(stmt.base, pos, val.as_ptr() as _, len as _, ffi::sqlite_transient()),
178            }
179        })
180    }
181}
182
183#[sealed]
184impl<'a> ToParam for &'a ValueRef {
185    fn bind_param(self, stmt: &mut Statement, pos: i32) -> Result<()> {
186        unsafe { Error::from_sqlite(ffi::sqlite3_bind_value(stmt.base, pos, self.as_ptr())) }
187    }
188}
189
190#[sealed]
191impl<'a> ToParam for &'a [u8] {
192    fn bind_param(self, stmt: &mut Statement, pos: i32) -> Result<()> {
193        let len = self.len();
194        unsafe {
195            Error::from_sqlite(sqlite3_match_version! {
196                3_008_007 => ffi::sqlite3_bind_blob64(
197                    stmt.base,
198                    pos,
199                    self.as_ptr() as _,
200                    len as _,
201                    ffi::sqlite_transient(),
202                ),
203                _ => ffi::sqlite3_bind_blob(
204                    stmt.base,
205                    pos,
206                    self.as_ptr() as _,
207                    len as _,
208                    ffi::sqlite_transient(),
209                ),
210            })
211        }
212    }
213}
214
215#[sealed]
216impl<'a, const N: usize> ToParam for &'a [u8; N] {
217    fn bind_param(self, stmt: &mut Statement, pos: i32) -> Result<()> {
218        self.as_slice().bind_param(stmt, pos)
219    }
220}
221
222/// Sets the parameter to a dynamically typed [Value].
223#[sealed]
224impl ToParam for Value {
225    fn bind_param(self, stmt: &mut Statement, pos: i32) -> Result<()> {
226        match self {
227            Value::Integer(x) => x.bind_param(stmt, pos),
228            Value::Float(x) => x.bind_param(stmt, pos),
229            Value::Text(x) => x.bind_param(stmt, pos),
230            Value::Blob(x) => x.bind_param(stmt, pos),
231            Value::Null => ().bind_param(stmt, pos),
232        }
233    }
234}
235
236/// Sets the parameter to the contained value or NULL.
237#[sealed]
238impl<T> ToParam for Option<T>
239where
240    T: ToParam,
241{
242    fn bind_param(self, stmt: &mut Statement, pos: i32) -> Result<()> {
243        match self {
244            Some(x) => x.bind_param(stmt, pos),
245            None => ().bind_param(stmt, pos),
246        }
247    }
248}
249
250/// Sets the parameter to NULL with this value as an associated pointer.
251#[sealed]
252impl<T: 'static> ToParam for PassedRef<T> {
253    fn bind_param(self, stmt: &mut Statement, pos: i32) -> Result<()> {
254        let _ = (POINTER_TAG, &stmt, pos);
255        sqlite3_require_version!(3_020_000, unsafe {
256            Error::from_sqlite(ffi::sqlite3_bind_pointer(
257                stmt.base,
258                pos,
259                Box::into_raw(Box::new(self)) as _,
260                POINTER_TAG,
261                Some(ffi::drop_boxed::<PassedRef<T>>),
262            ))
263        })
264    }
265}
266
267/// Used to bind named parameters. Sets the parameter with the name at `self.0` to the value at
268/// `self.1`.
269#[sealed]
270impl<K, V> ToParam for (K, V)
271where
272    K: Into<Vec<u8>>,
273    V: ToParam,
274{
275    fn bind_param(self, stmt: &mut Statement, _: i32) -> Result<()> {
276        let pos = stmt.parameter_position(self.0);
277        match pos {
278            Some(pos) => self.1.bind_param(stmt, pos.get()),
279            None => Err(SQLITE_RANGE),
280        }
281    }
282}