nanosql/
param.rs

1//! Serializing strongly-typed arguments as bound statement parameters.
2
3use core::num::{
4    NonZeroI8,
5    NonZeroU8,
6    NonZeroI16,
7    NonZeroU16,
8    NonZeroI32,
9    NonZeroU32,
10    NonZeroI64,
11    NonZeroU64,
12    NonZeroIsize,
13    NonZeroUsize,
14};
15use core::str::FromStr;
16use core::fmt::{self, Display, Formatter, Write};
17use std::borrow::Cow;
18use std::rc::Rc;
19use std::sync::Arc;
20use std::collections::{HashMap, BTreeMap};
21use rusqlite::{Statement, ToSql, types::{Value, ValueRef, Null, ToSqlOutput}};
22#[cfg(feature = "not-nan")]
23use ordered_float::NotNan;
24#[cfg(feature = "chrono")]
25use chrono::{DateTime, Utc, FixedOffset, Local};
26#[cfg(feature = "uuid")]
27use uuid::Uuid;
28#[cfg(feature = "json")]
29use serde_json::Value as JsonValue;
30use crate::error::{Error, Result};
31
32
33/// A parameter prefix character, preceding the name or index of a bound parameter.
34/// One of `$`, `:`, `?`, or `@`.
35///
36/// The default value is `$`, because it's the most flexible one.
37///
38/// Variants are in ASCII/Unicode code point order.
39#[repr(u8)]
40#[allow(missing_docs)]
41#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
42pub enum ParamPrefix {
43    #[default]
44    Dollar = b'$',
45    Colon = b':',
46    Question = b'?',
47    At = b'@',
48}
49
50impl ParamPrefix {
51    /// Returns the underlying raw byte.
52    pub const fn as_byte(self) -> u8 {
53        self as u8
54    }
55
56    /// Returns the underlying raw character.
57    pub const fn as_char(self) -> char {
58        self as u8 as char
59    }
60}
61
62impl From<ParamPrefix> for u8 {
63    fn from(prefix: ParamPrefix) -> Self {
64        prefix.as_byte()
65    }
66}
67
68impl From<ParamPrefix> for char {
69    fn from(prefix: ParamPrefix) -> Self {
70        prefix.as_char()
71    }
72}
73
74impl TryFrom<char> for ParamPrefix {
75    type Error = Error;
76
77    fn try_from(ch: char) -> Result<Self, Self::Error> {
78        match ch {
79            '$' => Ok(ParamPrefix::Dollar),
80            ':' => Ok(ParamPrefix::Colon),
81            '?' => Ok(ParamPrefix::Question),
82            '@' => Ok(ParamPrefix::At),
83            _   => Err(Error::message(format_args!("invalid parameter prefix: `{ch}`"))),
84        }
85    }
86}
87
88impl TryFrom<u8> for ParamPrefix {
89    type Error = Error;
90
91    fn try_from(byte: u8) -> Result<Self, Self::Error> {
92        char::from(byte).try_into()
93    }
94}
95
96impl Display for ParamPrefix {
97    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
98        f.write_char(self.as_char())
99    }
100}
101
102impl FromStr for ParamPrefix {
103    type Err = Error;
104
105    fn from_str(s: &str) -> Result<Self, Self::Err> {
106        char::from_str(s).map_err(Error::other).and_then(Self::try_from)
107    }
108}
109
110/// Describes types that can be bound as parameters to a compiled statement.
111///
112/// The kinds of types implementing this trait include:
113///
114/// * Primitives (numeric types, strings, blobs, etc.)
115/// * Optionals of primitives
116/// * Tuples or structs of any of the above
117/// * Singleton/forwarding wrappers of any of the above, e.g. `&T` and `Box`
118///
119/// When derived on a `struct`, the `rename_all` (type-level), `rename` and
120/// `ignore` (field-level) attributes work identically to those of
121/// [`Table`](crate::table::Table); see its documentation for more details.
122pub trait Param {
123    /// The leading symbol in parameter names. (Must be consistent across parameters.)
124    const PREFIX: ParamPrefix;
125
126    /// Binds the primitive or the field(s) of a tuple to a raw `rusqlite::Statement`.
127    fn bind(&self, statement: &mut Statement<'_>) -> Result<()>;
128}
129
130/// Private helper for ensuring that exactly 1 parameter is expected when binding a primitive
131/// as a top-level parameter "list".
132fn bind_primitive<T: ToSql>(statement: &mut Statement<'_>, value: T) -> Result<()> {
133    let expected = statement.parameter_count();
134    let actual = 1;
135
136    if actual == expected {
137        statement.raw_bind_parameter(1, value)?;
138        Ok(())
139    } else {
140        Err(Error::ParamCountMismatch { expected, actual })
141    }
142}
143
144macro_rules! impl_param_for_primitive {
145    ($($ty:ty,)*) => {$(
146        impl Param for $ty {
147            /// Primitives are bound as positional parameters, hence the prefix is '?'
148            const PREFIX: ParamPrefix = ParamPrefix::Question;
149
150            fn bind(&self, statement: &mut Statement<'_>) -> Result<()> {
151                bind_primitive(statement, self)
152            }
153        }
154    )*}
155}
156
157impl_param_for_primitive!{
158    bool,
159    i8,
160    u8,
161    i16,
162    u16,
163    i32,
164    u32,
165    i64,
166    u64,
167    isize,
168    usize,
169    NonZeroI8,
170    NonZeroU8,
171    NonZeroI16,
172    NonZeroU16,
173    NonZeroI32,
174    NonZeroU32,
175    NonZeroI64,
176    NonZeroU64,
177    NonZeroIsize,
178    NonZeroUsize,
179    f32,
180    f64,
181    str,
182    [u8],
183    String,
184    Vec<u8>,
185    Value,
186    ToSqlOutput<'_>,
187    Null,
188}
189
190#[cfg(feature = "not-nan")]
191impl Param for NotNan<f32> {
192    /// Primitives are bound as positional parameters, hence the prefix is '?'
193    const PREFIX: ParamPrefix = ParamPrefix::Question;
194
195    fn bind(&self, statement: &mut Statement<'_>) -> Result<()> {
196        bind_primitive(statement, self.into_inner())
197    }
198}
199
200#[cfg(feature = "not-nan")]
201impl Param for NotNan<f64> {
202    /// Primitives are bound as positional parameters, hence the prefix is '?'
203    const PREFIX: ParamPrefix = ParamPrefix::Question;
204
205    fn bind(&self, statement: &mut Statement<'_>) -> Result<()> {
206        bind_primitive(statement, self.into_inner())
207    }
208}
209
210#[cfg(feature = "chrono")]
211impl_param_for_primitive! {
212    DateTime<Utc>,
213    DateTime<FixedOffset>,
214    DateTime<Local>,
215}
216
217#[cfg(feature = "uuid")]
218impl_param_for_primitive!{
219    Uuid,
220}
221
222#[cfg(feature = "json")]
223impl_param_for_primitive!{
224    JsonValue,
225}
226
227impl Param for ValueRef<'_> {
228    /// Primitives are bound as positional parameters, hence the prefix is '?'
229    const PREFIX: ParamPrefix = ParamPrefix::Question;
230
231    fn bind(&self, statement: &mut Statement<'_>) -> Result<()> {
232        bind_primitive(statement, ToSqlOutput::Borrowed(*self))
233    }
234}
235
236impl<const N: usize> Param for [u8; N] {
237    /// Primitives are bound as positional parameters, hence the prefix is '?'
238    const PREFIX: ParamPrefix = ParamPrefix::Question;
239
240    fn bind(&self, statement: &mut Statement<'_>) -> Result<()> {
241        bind_primitive(statement, self)
242    }
243}
244
245macro_rules! impl_param_for_tuple {
246    () => {
247        impl Param for () {
248            /// Tuples use positional parameters, hence the prefix is '?'
249            const PREFIX: ParamPrefix = ParamPrefix::Question;
250
251            fn bind(&self, statement: &mut Statement<'_>) -> Result<()> {
252                let expected = statement.parameter_count();
253                let actual = 0;
254
255                if actual == expected {
256                    Ok(())
257                } else {
258                    Err(Error::ParamCountMismatch { expected, actual })
259                }
260            }
261        }
262    };
263    ($head_id:ident => $head_ty:ident; $($rest_id:ident => $rest_ty:ident;)*) => {
264        impl<$head_ty, $($rest_ty,)*> Param for ($head_ty, $($rest_ty,)*)
265        where
266            $head_ty: ToSql,
267            $($rest_ty: ToSql,)*
268        {
269            /// Tuples use positional parameters, hence the prefix is '?'
270            const PREFIX: ParamPrefix = ParamPrefix::Question;
271
272            fn bind(&self, statement: &mut Statement<'_>) -> Result<()> {
273                let ($head_id, $($rest_id,)*) = self;
274
275                #[allow(unused_mut)]
276                let mut index = 1;
277                statement.raw_bind_parameter(index, $head_id)?;
278
279                $(
280                    index += 1;
281                    statement.raw_bind_parameter(index, $rest_id)?;
282                )*
283
284                let expected = statement.parameter_count();
285                let actual = index;
286
287                if actual == expected {
288                    Ok(())
289                } else {
290                    Err(Error::ParamCountMismatch { expected, actual })
291                }
292            }
293        }
294        impl_param_for_tuple!($($rest_id => $rest_ty;)*);
295    };
296}
297
298impl_param_for_tuple!{
299    a => A;
300    b => B;
301    c => C;
302    d => D;
303    e => E;
304    f => F;
305    g => G;
306    h => H;
307    i => I;
308    j => J;
309    k => K;
310    l => L;
311    m => M;
312    n => N;
313    o => O;
314    p => P;
315    q => Q;
316    r => R;
317    s => S;
318    t => T;
319    u => U;
320    v => V;
321    w => W;
322    x => X;
323    y => Y;
324    z => Z;
325}
326
327macro_rules! impl_param_for_wrapper {
328    ($($ty:ty;)*) => {$(
329        impl<T: ?Sized + Param> Param for $ty {
330            const PREFIX: ParamPrefix = T::PREFIX;
331
332            fn bind(&self, statement: &mut Statement<'_>) -> Result<()> {
333                let body = |value: &$ty, statement| Param::bind(&**value, statement);
334                body(self, statement)
335            }
336        }
337    )*}
338}
339
340impl_param_for_wrapper! {
341    &T;
342    &mut T;
343    Box<T>;
344    Rc<T>;
345    Arc<T>;
346}
347
348impl<T> Param for Cow<'_, T>
349where
350    T: ?Sized + ToOwned + Param,
351{
352    const PREFIX: ParamPrefix = T::PREFIX;
353
354    fn bind(&self, statement: &mut Statement<'_>) -> Result<()> {
355        Param::bind(&**self, statement)
356    }
357}
358
359impl<T: ToSql> Param for Option<T> {
360    /// Primitives are bound as positional parameters, hence the prefix is '?'
361    const PREFIX: ParamPrefix = ParamPrefix::Question;
362
363    fn bind(&self, statement: &mut Statement<'_>) -> Result<()> {
364        bind_primitive(statement, self)
365    }
366}
367
368impl<K, V> Param for HashMap<K, V>
369where
370    K: Display,
371    V: ToSql,
372{
373    /// Dynamic maps use `$` by default because it's the most flexible prefix.
374    const PREFIX: ParamPrefix = ParamPrefix::Dollar;
375
376    fn bind(&self, statement: &mut Statement<'_>) -> Result<()> {
377        let expected = statement.parameter_count();
378        let actual = self.len();
379
380        if actual != expected {
381            return Err(Error::ParamCountMismatch { expected, actual });
382        }
383
384        // re-use parameter name construction buffer in order to save allocations
385        let mut name_buf = String::new();
386
387        for (key, value) in self {
388            name_buf.clear();
389            write!(name_buf, "{}{}", Self::PREFIX, key)?;
390
391            let index = statement.parameter_index(name_buf.as_str())?.ok_or_else(|| {
392                Error::unknown_param_dyn(&name_buf)
393            })?;
394
395            statement.raw_bind_parameter(index, value)?;
396        }
397
398        Ok(())
399    }
400}
401
402impl<K, V> Param for BTreeMap<K, V>
403where
404    K: Display,
405    V: ToSql,
406{
407    /// Dynamic maps use `$` by default because it's the most flexible prefix.
408    const PREFIX: ParamPrefix = ParamPrefix::Dollar;
409
410    fn bind(&self, statement: &mut Statement<'_>) -> Result<()> {
411        let expected = statement.parameter_count();
412        let actual = self.len();
413
414        if actual != expected {
415            return Err(Error::ParamCountMismatch { expected, actual });
416        }
417
418        // re-use parameter name construction buffer in order to save allocations
419        let mut name_buf = String::new();
420
421        for (key, value) in self {
422            name_buf.clear();
423            write!(name_buf, "{}{}", Self::PREFIX, key)?;
424
425            let index = statement.parameter_index(name_buf.as_str())?.ok_or_else(|| {
426                Error::unknown_param_dyn(&name_buf)
427            })?;
428
429            statement.raw_bind_parameter(index, value)?;
430        }
431
432        Ok(())
433    }
434}