rsfbclient_core/
params.rs

1//! Sql parameter types and traits
2
3use crate::{error::FbError, ibase, SqlType};
4use regex::{Captures, Regex};
5use std::collections::HashMap;
6
7pub use SqlType::*;
8
9/// Max length that can be sent without creating a BLOB
10pub const MAX_TEXT_LENGTH: usize = 32767;
11
12impl SqlType {
13    /// Convert the sql value to interbase format
14    pub fn sql_type_and_subtype(&self) -> (u32, u32) {
15        match self {
16            Text(s) => {
17                if s.len() > MAX_TEXT_LENGTH {
18                    (ibase::SQL_BLOB + 1, 1)
19                } else {
20                    (ibase::SQL_TEXT + 1, 0)
21                }
22            }
23            Integer(_) => (ibase::SQL_INT64 + 1, 0),
24            Floating(_) => (ibase::SQL_DOUBLE + 1, 0),
25            Timestamp(_) => (ibase::SQL_TIMESTAMP + 1, 0),
26            Null => (ibase::SQL_TEXT + 1, 0),
27            Binary(_) => (ibase::SQL_BLOB + 1, 0),
28            Boolean(_) => (ibase::SQL_BOOLEAN + 1, 0),
29        }
30    }
31}
32
33/// Implemented for types that can be sent as parameters
34pub trait IntoParam {
35    fn into_param(self) -> SqlType;
36}
37
38impl IntoParam for Vec<u8> {
39    fn into_param(self) -> SqlType {
40        Binary(self)
41    }
42}
43
44impl IntoParam for String {
45    fn into_param(self) -> SqlType {
46        Text(self)
47    }
48}
49
50impl IntoParam for i64 {
51    fn into_param(self) -> SqlType {
52        Integer(self)
53    }
54}
55
56impl IntoParam for bool {
57    fn into_param(self) -> SqlType {
58        Boolean(self)
59    }
60}
61
62/// Implements AsParam for integers
63macro_rules! impl_param_int {
64    ( $( $t: ident ),+ ) => {
65        $(
66            impl IntoParam for $t {
67                fn into_param(self) -> SqlType {
68                    (self as i64).into_param()
69                }
70            }
71        )+
72    };
73}
74
75impl_param_int!(i32, u32, i16, u16, i8, u8);
76
77impl IntoParam for f64 {
78    fn into_param(self) -> SqlType {
79        Floating(self)
80    }
81}
82
83impl IntoParam for f32 {
84    fn into_param(self) -> SqlType {
85        (self as f64).into_param()
86    }
87}
88
89/// Implements `IntoParam` for all nullable variants
90impl<T> IntoParam for Option<T>
91where
92    T: IntoParam,
93{
94    fn into_param(self) -> SqlType {
95        if let Some(v) = self {
96            v.into_param()
97        } else {
98            Null
99        }
100    }
101}
102
103/// Implements `IntoParam` for all borrowed variants (&str, Cow and etc)
104impl<T, B> IntoParam for &B
105where
106    B: ToOwned<Owned = T> + ?Sized,
107    T: core::borrow::Borrow<B> + IntoParam,
108{
109    fn into_param(self) -> SqlType {
110        self.to_owned().into_param()
111    }
112}
113
114/// Implement From / Into conversions
115impl<T> From<T> for SqlType
116where
117    T: IntoParam,
118{
119    fn from(param: T) -> Self {
120        param.into_param()
121    }
122}
123
124/// Parameters type
125pub enum ParamsType {
126    /// Positional parameters, using '?'. This is the default option.
127    ///
128    /// Firebird provides direct support for this kind of parameter, which this crate makes use of.
129    Positional(Vec<SqlType>),
130
131    /// Named parameters, using the common `:`-prefixed `:param` syntax.
132    ///
133    /// Support for this kind of parameter is provided by this library.
134    ///
135    /// Currently only a naive regex-based approach is used, to support very basic
136    /// select, insert, etc statements
137    ///
138    /// **CAUTION!**
139    /// Named parameter support is still very preliminary.
140    /// Use of named parameters may currently give unexpected results. Please test your queries carefully
141    /// when using this feature.
142    ///
143    /// In particular, the simple regex-based parser is known to definitely to have trouble with:
144    ///   * occurences of apostrophe (`'`) anywhere except as string literal delimiters (for example, in comments)
145    ///   * statements with closed variable bindings (which uses the `:var` syntax) (for example, in PSQL via `EXECUTE BLOCK` or `EXECUTE PROCEDURE`)
146    ///
147    ///
148    /// This crate provides a [derive macro](prelude/derive.IntoParams.html) for supplying arguments via the fields of a struct and their labels.
149    Named(HashMap<String, SqlType>),
150}
151
152impl ParamsType {
153    pub fn named(&self) -> bool {
154        match self {
155            ParamsType::Positional(_) => false,
156            ParamsType::Named(_) => true,
157        }
158    }
159}
160
161#[allow(clippy::wrong_self_convention)]
162/// Types with an associated boolean flag function, `named()` indiciating support for named or positional parameters.
163///
164///
165/// With both named (as a struct field) or positional (as a Vector or tuple element) parameters, `Option<T>`, with `T` an `IntoParam`,  may be used to indicate a nullable argument, wherein the `None` variant provides a `null` value.
166///
167/// This crate provides a [derive macro](prelude/derive.IntoParams.html) for supplying arguments via the fields of a struct and their labels.
168pub trait IntoParams {
169    fn to_params(self) -> ParamsType;
170}
171
172impl IntoParams for ParamsType {
173    fn to_params(self) -> ParamsType {
174        self
175    }
176}
177
178/// Allow use of a vector instead of tuples, for run-time-determined parameter count, or
179/// for when there are too many parameters to use one of the provided tuple implementations
180impl IntoParams for Vec<SqlType> {
181    fn to_params(self) -> ParamsType {
182        ParamsType::Positional(self)
183    }
184}
185
186/// Represents 0 parameters
187impl IntoParams for () {
188    fn to_params(self) -> ParamsType {
189        ParamsType::Positional(vec![])
190    }
191}
192
193/// Generates IntoParams implementations for a tuple
194macro_rules! impl_into_params {
195    ($([$t: ident, $v: ident]),+) => {
196        impl<$($t),+> IntoParams for ($($t,)+)
197        where
198            $( $t: IntoParam, )+
199        {
200            fn to_params(self) -> ParamsType {
201                let ( $($v,)+ ) = self;
202
203                ParamsType::Positional(vec![ $(
204                    $v.into_param(),
205                )+ ])
206            }
207        }
208    };
209}
210
211/// Generates FromRow implementations for various tuples
212macro_rules! impls_into_params {
213    ([$t: ident, $v: ident]) => {
214        impl_into_params!([$t, $v]);
215    };
216
217    ([$t: ident, $v: ident], $([$ts: ident, $vs: ident]),+ ) => {
218        impls_into_params!($([$ts, $vs]),+);
219
220        impl_into_params!([$t, $v], $([$ts, $vs]),+);
221    };
222}
223
224impls_into_params!(
225    [A, a],
226    [B, b],
227    [C, c],
228    [D, d],
229    [E, e],
230    [F, f],
231    [G, g],
232    [H, h],
233    [I, i],
234    [J, j],
235    [K, k],
236    [L, l],
237    [M, m],
238    [N, n],
239    [O, o]
240);
241
242/// Named params implementation.
243///
244/// Works on top of firebird positional parameters (`?`)
245pub struct NamedParams {
246    pub sql: String,
247    params_names: Vec<String>,
248}
249
250impl NamedParams {
251    /// Parse the sql statement and return a
252    /// structure representing the named parameters found
253    pub fn parse(raw_sql: &str) -> Result<Self, FbError> {
254        let rparams = Regex::new(r#"('[^']*')|:\w+"#)
255            .map_err(|e| FbError::from(format!("Error on start the regex for named params: {}", e)))
256            .unwrap();
257
258        let mut params_names = vec![];
259        let sql = rparams
260            .replace_all(raw_sql, |caps: &Captures| match caps.get(1) {
261                Some(same) => same.as_str().to_string(),
262                None => "?".to_string(),
263            })
264            .to_string();
265
266        for params in rparams.captures_iter(raw_sql) {
267            for param in params
268                .iter()
269                .filter_map(|p| p.map(|m| m.as_str()))
270                .filter(|p| p.starts_with(':'))
271            {
272                params_names.push(param.replace(':', ""));
273            }
274        }
275
276        Ok(NamedParams { sql, params_names })
277    }
278
279    /// Returns the sql as is, disabling named parameter function
280    pub fn empty(raw_sql: &str) -> Self {
281        Self {
282            sql: raw_sql.to_string(),
283            params_names: Default::default(),
284        }
285    }
286
287    /// Re-sort/convert the parameters, applying
288    /// the named params support
289    pub fn convert<P>(&self, params: P) -> Result<Vec<SqlType>, FbError>
290    where
291        P: IntoParams,
292    {
293        match params.to_params() {
294            ParamsType::Named(names) => {
295                let mut new_params = vec![];
296
297                for qname in &self.params_names {
298                    if let Some(param) = names.get(qname) {
299                        new_params.push(param.clone());
300                    } else {
301                        return Err(FbError::from(format!(
302                            "Param :{} not found in the provided struct",
303                            qname
304                        )));
305                    }
306                }
307
308                Ok(new_params)
309            }
310            ParamsType::Positional(p) => Ok(p),
311        }
312    }
313}