Skip to main content

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