duckdb/params.rs
1use crate::{Error, Result, Statement, ToSql};
2use std::{
3 borrow::Borrow,
4 collections::{HashMap, HashSet},
5 hash::{BuildHasher, Hash},
6};
7
8mod sealed {
9 /// This trait exists just to ensure that the only impls of `trait Params`
10 /// that are allowed are ones in this crate.
11 pub trait Sealed {}
12}
13use sealed::Sealed;
14
15/// Trait used for [sets of parameter][params] passed into SQL
16/// statements/queries.
17///
18/// [params]: https://duckdb.org/docs/stable/clients/c/prepared.html
19///
20/// Note: Currently, this trait can only be implemented inside this crate.
21/// Additionally, its methods (which are `doc(hidden)`) should currently not be
22/// considered part of the stable API, although it's possible they will
23/// stabilize in the future.
24///
25/// # Passing parameters to DuckDB
26///
27/// Many functions in this library let you pass parameters to DuckDB. Doing this
28/// lets you avoid any risk of SQL injection, and is simpler than escaping
29/// things manually. Aside from deprecated functions and a few helpers, this is
30/// indicated by the function taking a generic argument that implements `Params`
31/// (this trait).
32///
33/// ## Positional parameters
34///
35/// For cases where you want to pass a list of parameters where the number of
36/// parameters is known at compile time, this can be done in one of the
37/// following ways:
38///
39/// - Using the [`duckdb::params!`](crate::params!) macro, e.g.
40/// `thing.query(duckdb::params![1, "foo", bar])`. This is mostly useful for
41/// heterogeneous lists of parameters, or lists where the number of parameters
42/// exceeds 32.
43///
44/// - For small heterogeneous lists of parameters, they can either be passed as:
45///
46/// - an array, as in `thing.query([1i32, 2, 3, 4])` or `thing.query(["foo",
47/// "bar", "baz"])`.
48///
49/// - a reference to an array of references, as in `thing.query(&["foo",
50/// "bar", "baz"])` or `thing.query(&[&1i32, &2, &3])`.
51///
52/// (Note: in this case we don't implement this for slices for coherence
53/// reasons, so it really is only for the "reference to array" types —
54/// hence why the number of parameters must be <= 32 or you need to
55/// reach for `duckdb::params!`)
56///
57/// - a tuple of mixed types (up to 16 elements), as in
58/// `thing.execute((1i32, "foo", 3.14f64))`. This is the most ergonomic
59/// way to pass heterogeneous parameters.
60///
61/// Unfortunately, in the current design it's not possible to allow this for
62/// references to arrays of non-references (e.g. `&[1i32, 2, 3]`). Code like
63/// this should instead either use `params!`, an array literal, a `&[&dyn
64/// ToSql]` or if none of those work, [`ParamsFromIter`].
65///
66/// - As a slice of `ToSql` trait object references, e.g. `&[&dyn ToSql]`. This
67/// is mostly useful for passing parameter lists around as arguments without
68/// having every function take a generic `P: Params`.
69///
70/// ### Example (positional)
71///
72/// ```rust,no_run
73/// # use duckdb::{Connection, Result, params};
74/// fn update_rows(conn: &Connection) -> Result<()> {
75/// let mut stmt = conn.prepare("INSERT INTO test (a, b) VALUES (?, ?)")?;
76///
77/// // Using `duckdb::params!`:
78/// stmt.execute(params![1i32, "blah"])?;
79///
80/// // array literal — non-references
81/// stmt.execute([2i32, 3i32])?;
82///
83/// // array literal — references
84/// stmt.execute(["foo", "bar"])?;
85///
86/// // Slice literal, references:
87/// stmt.execute(&[&2i32, &3i32])?;
88///
89/// // Note: The types behind the references don't have to be `Sized`
90/// stmt.execute(&["foo", "bar"])?;
91///
92/// // Tuple of mixed types:
93/// stmt.execute((1i32, "blah"))?;
94///
95/// // However, this doesn't work (see above):
96/// // stmt.execute(&[1i32, 2i32])?;
97/// Ok(())
98/// }
99/// ```
100///
101/// ## Named parameters
102///
103/// Named parameters can be passed using [`duckdb::named_params!`](crate::named_params!)
104/// or a manually constructed `&[(&str, &dyn ToSql)]` for heterogeneous values,
105/// or as any `HashMap` whose keys borrow as `str` and whose values implement a
106/// single concrete [`ToSql`] type, including maps with custom hashers. The keys
107/// must not include the `$` prefix. Named-parameter APIs must be used with
108/// named SQL placeholders such as `$name`, not positional placeholders such as
109/// `?` or `?1`.
110///
111/// Returns [`Error::InvalidParameterName`](crate::Error::InvalidParameterName)
112/// if a SQL placeholder has no matching key, a provided key is not present in
113/// the SQL, slice parameters contain duplicate keys, or positional placeholders
114/// are used with named-parameter APIs.
115///
116/// ### Example (named parameters with a HashMap)
117///
118/// ```rust,no_run
119/// use std::collections::HashMap;
120/// use duckdb::{Connection, Result};
121///
122/// fn execute_query(conn: &Connection) -> Result<Vec<String>> {
123/// let params = HashMap::from([("min", 23), ("max", 42)]);
124/// let mut stmt = conn.prepare("SELECT name FROM people WHERE age BETWEEN $min AND $max")?;
125/// let rows = stmt.query_map(¶ms, |row| row.get::<_, String>(0))?;
126/// rows.collect()
127/// }
128/// ```
129///
130/// ### Example (named parameters with mixed types)
131///
132/// ```rust,no_run
133/// use duckdb::{Connection, Result, named_params};
134///
135/// fn execute_query(conn: &Connection, min_age: i32, name: &str) -> Result<bool> {
136/// conn.query_row(
137/// "SELECT $name = 'Alice' AND $age >= 18",
138/// named_params! {
139/// "name": name,
140/// "age": min_age,
141/// },
142/// |row| row.get(0),
143/// )
144/// }
145/// ```
146///
147/// ## No parameters
148///
149/// You can use an empty array literal or the empty tuple for no params.
150///
151/// ### Example (no parameters)
152///
153/// ```rust,no_run
154/// # use duckdb::{Connection, Result, params};
155/// fn delete_all_users(conn: &Connection) -> Result<()> {
156/// // Empty array:
157/// conn.execute("DELETE FROM users", [])?;
158/// // Or empty tuple:
159/// conn.execute("DELETE FROM users", ())?;
160/// Ok(())
161/// }
162/// ```
163///
164/// ## Dynamic parameter list
165///
166/// If you have a number of parameters which is unknown at compile time (for
167/// example, building a dynamic query at runtime), you have two choices:
168///
169/// - Use a `&[&dyn ToSql]`, which is nice if you have one otherwise might be
170/// annoying.
171/// - Use the [`ParamsFromIter`] type. This essentially lets you wrap an
172/// iterator some `T: ToSql` with something that implements `Params`.
173///
174/// A lot of the considerations here are similar either way, so you should see
175/// the [`ParamsFromIter`] documentation for more info / examples.
176pub trait Params: Sealed {
177 // XXX not public api, might not need to expose.
178 //
179 // Binds the parameters to the statement. It is unlikely calling this
180 // explicitly will do what you want. Please use `Statement::query` or
181 // similar directly.
182 //
183 // For now, just hide the function in the docs...
184 #[doc(hidden)]
185 fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()>;
186}
187
188// Explicitly impl for empty array. Critically, for `conn.execute([])` to be
189// unambiguous, this must be the *only* implementation for an empty array. This
190// avoids `NO_PARAMS` being a necessary part of the API.
191impl Sealed for [&dyn ToSql; 0] {}
192impl Params for [&dyn ToSql; 0] {
193 #[inline]
194 fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
195 // Note: Can't just return `Ok(())` — `Statement::bind_parameters`
196 // checks that the right number of params were passed too.
197 // TODO: we should have tests for `Error::InvalidParameterCount`...
198 stmt.bind_parameters(&[] as &[&dyn ToSql])
199 }
200}
201
202// Empty tuple — allows `conn.execute("...", ())`.
203impl Sealed for () {}
204impl Params for () {
205 #[inline]
206 fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
207 stmt.bind_parameters(&[] as &[&dyn ToSql])
208 }
209}
210
211impl Sealed for &[&dyn ToSql] {}
212impl Params for &[&dyn ToSql] {
213 #[inline]
214 fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
215 stmt.bind_parameters(self)
216 }
217}
218
219macro_rules! impl_for_array_ref {
220 ($($N:literal)+) => {$(
221 // These are already generic, and there's a shedload of them, so lets
222 // avoid the compile time hit from making them all inline for now.
223 impl<T: ToSql + ?Sized> Sealed for &[&T; $N] {}
224 impl<T: ToSql + ?Sized> Params for &[&T; $N] {
225 fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
226 stmt.bind_parameters(self)
227 }
228 }
229 impl<T: ToSql> Sealed for [T; $N] {}
230 impl<T: ToSql> Params for [T; $N] {
231 #[inline]
232 fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
233 stmt.bind_parameters(&self)
234 }
235 }
236 )+};
237}
238
239// Following libstd/libcore's (old) lead, implement this for arrays up to `[_;
240// 32]`. Note `[_; 0]` is intentionally omitted for coherence reasons, see the
241// note above the impl of `[&dyn ToSql; 0]` for more information.
242impl_for_array_ref!(
243 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
244 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
245);
246
247macro_rules! impl_params_for_tuple {
248 ($($T:ident),+) => {
249 impl<$($T: ToSql),+> Sealed for ($($T,)+) {}
250 impl<$($T: ToSql),+> Params for ($($T,)+) {
251 fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
252 #[allow(non_snake_case)]
253 let ($($T,)+) = &self;
254 stmt.bind_parameters(&[$($T as &dyn ToSql),+])
255 }
256 }
257 };
258}
259
260// Implement Params for tuples up to arity 16, matching rusqlite.
261impl_params_for_tuple!(T1);
262impl_params_for_tuple!(T1, T2);
263impl_params_for_tuple!(T1, T2, T3);
264impl_params_for_tuple!(T1, T2, T3, T4);
265impl_params_for_tuple!(T1, T2, T3, T4, T5);
266impl_params_for_tuple!(T1, T2, T3, T4, T5, T6);
267impl_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7);
268impl_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8);
269impl_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9);
270impl_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
271impl_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
272impl_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
273impl_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
274impl_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14);
275impl_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15);
276impl_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16);
277
278/// Adapter type which allows any iterator over [`ToSql`] values to implement
279/// [`Params`].
280///
281/// This struct is created by the [`params_from_iter`] function.
282///
283/// This can be useful if you have something like an `&[String]` (of unknown
284/// length), and you want to use them with an API that wants something
285/// implementing `Params`. This way, you can avoid having to allocate storage
286/// for something like a `&[&dyn ToSql]`.
287///
288/// This essentially is only ever actually needed when dynamically generating
289/// SQL — static SQL (by definition) has the number of parameters known
290/// statically. As dynamically generating SQL is itself pretty advanced, this
291/// API is itself for advanced use cases (See "Realistic use case" in the
292/// examples).
293///
294/// # Example
295///
296/// ## Basic usage
297///
298/// ```rust,no_run
299/// use duckdb::{Connection, Result, params_from_iter};
300/// use std::collections::BTreeSet;
301///
302/// fn query(conn: &Connection, ids: &BTreeSet<String>) -> Result<()> {
303/// assert_eq!(ids.len(), 3, "Unrealistic sample code");
304///
305/// let mut stmt = conn.prepare("SELECT * FROM users WHERE id IN (?, ?, ?)")?;
306/// let _rows = stmt.query(params_from_iter(ids.iter()))?;
307///
308/// // use _rows...
309/// Ok(())
310/// }
311/// ```
312///
313/// ## Realistic use case
314///
315/// Here's how you'd use `ParamsFromIter` to call [`Statement::exists`] with a
316/// dynamic number of parameters.
317///
318/// ```rust,no_run
319/// use duckdb::{Connection, Result};
320///
321/// pub fn any_active_users(conn: &Connection, usernames: &[String]) -> Result<bool> {
322/// if usernames.is_empty() {
323/// return Ok(false);
324/// }
325///
326/// // Note: `repeat_vars` never returns anything attacker-controlled, so
327/// // it's fine to use it in a dynamically-built SQL string.
328/// let vars = repeat_vars(usernames.len());
329///
330/// let sql = format!(
331/// // In practice this would probably be better as an `EXISTS` query.
332/// "SELECT 1 FROM user WHERE is_active AND name IN ({}) LIMIT 1",
333/// vars,
334/// );
335/// let mut stmt = conn.prepare(&sql)?;
336/// stmt.exists(duckdb::params_from_iter(usernames))
337/// }
338///
339/// // Helper function to return a comma-separated sequence of `?`.
340/// // - `repeat_vars(0) => panic!(...)`
341/// // - `repeat_vars(1) => "?"`
342/// // - `repeat_vars(2) => "?,?"`
343/// // - `repeat_vars(3) => "?,?,?"`
344/// // - ...
345/// fn repeat_vars(count: usize) -> String {
346/// assert_ne!(count, 0);
347/// let mut s = "?,".repeat(count);
348/// // Remove trailing comma
349/// s.pop();
350/// s
351/// }
352/// ```
353///
354/// That is fairly complex, and even so would need even more work to be fully
355/// production-ready:
356///
357/// - `repeat_vars` can be implemented in a way that avoids needing to allocate
358/// a String.
359///
360/// - Etc...
361///
362/// This complexity reflects the fact that `ParamsFromIter` is mainly intended
363/// for advanced use cases — most of the time you should know how many
364/// parameters you have statically (and if you don't, you're either doing
365/// something tricky, or should take a moment to think about the design).
366#[derive(Clone, Debug)]
367pub struct ParamsFromIter<I>(I);
368
369/// Constructor function for a [`ParamsFromIter`]. See its documentation for
370/// more.
371#[inline]
372pub fn params_from_iter<I>(iter: I) -> ParamsFromIter<I>
373where
374 I: IntoIterator,
375 I::Item: ToSql,
376{
377 ParamsFromIter(iter)
378}
379
380impl<I> Sealed for ParamsFromIter<I>
381where
382 I: IntoIterator,
383 I::Item: ToSql,
384{
385}
386
387impl<I> Params for ParamsFromIter<I>
388where
389 I: IntoIterator,
390 I::Item: ToSql,
391{
392 #[inline]
393 fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
394 stmt.bind_parameters(self.0)
395 }
396}
397
398fn reject_positional(name: &str) -> Result<()> {
399 if !name.is_empty() && name.bytes().all(|byte| byte.is_ascii_digit()) {
400 return Err(Error::InvalidParameterName(format!(
401 "positional parameter {name} cannot be used with named parameters"
402 )));
403 }
404 Ok(())
405}
406
407impl Sealed for &[(&str, &dyn ToSql)] {}
408
409impl Params for &[(&str, &dyn ToSql)] {
410 fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
411 let n = stmt.parameter_count();
412 let mut seen: HashSet<&str> = HashSet::with_capacity(self.len());
413 for (key, _) in self {
414 if !seen.insert(*key) {
415 return Err(Error::InvalidParameterName(format!("duplicate parameter name: {key}")));
416 }
417 }
418
419 let mut params = Vec::with_capacity(n);
420 let mut used = vec![false; self.len()];
421 for i in 1..=n {
422 let name = stmt.parameter_name(i)?;
423 reject_positional(&name)?;
424 let (idx, value) = self
425 .iter()
426 .enumerate()
427 .find_map(|(idx, (key, value))| (*key == name).then_some((idx, *value)))
428 .ok_or(Error::InvalidParameterName(name))?;
429 used[idx] = true;
430 params.push(value);
431 }
432
433 if let Some(idx) = used.iter().position(|u| !u) {
434 return Err(Error::InvalidParameterName(self[idx].0.to_string()));
435 }
436
437 stmt.bind_parameters(params)
438 }
439}
440
441impl<K, V, S> Sealed for &HashMap<K, V, S>
442where
443 K: Borrow<str> + Eq + Hash,
444 V: ToSql,
445 S: BuildHasher,
446{
447}
448
449impl<K, V, S> Params for &HashMap<K, V, S>
450where
451 K: Borrow<str> + Eq + Hash,
452 V: ToSql,
453 S: BuildHasher,
454{
455 fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
456 let n = stmt.parameter_count();
457 let mut params = Vec::with_capacity(n);
458 let mut bound: HashSet<&str> = HashSet::with_capacity(n);
459 for i in 1..=n {
460 let name = stmt.parameter_name(i)?;
461 reject_positional(&name)?;
462 let (key, value) = self
463 .get_key_value(name.as_str())
464 .ok_or(Error::InvalidParameterName(name))?;
465 bound.insert(key.borrow());
466 params.push(value as &dyn ToSql);
467 }
468
469 // HashMap iteration order is arbitrary, so choose a stable extra key for diagnostics.
470 if let Some(extra) = self
471 .keys()
472 .map(|key| -> &str { key.borrow() })
473 .filter(|key| !bound.contains(*key))
474 .min()
475 {
476 return Err(Error::InvalidParameterName(extra.to_string()));
477 }
478
479 stmt.bind_parameters(params)
480 }
481}