gluesql_core/translate/
param.rs

1use {
2    crate::{
3        ast::Expr,
4        data::{Interval, Point, Value},
5    },
6    chrono::{NaiveDate, NaiveDateTime, NaiveTime},
7    rust_decimal::Decimal,
8    std::net::IpAddr,
9    uuid::Uuid,
10};
11
12#[derive(Debug, Clone)]
13pub struct ParamLiteral(Value);
14
15impl ParamLiteral {
16    #[must_use]
17    pub const fn null() -> Self {
18        Self(Value::Null)
19    }
20
21    #[must_use]
22    pub fn into_expr(self) -> Expr {
23        Expr::Value(self.0)
24    }
25}
26
27pub trait IntoParamLiteral {
28    /// Converts the value into a [`ParamLiteral`] so it can be bound as a query parameter.
29    fn into_param_literal(self) -> ParamLiteral;
30}
31
32impl IntoParamLiteral for ParamLiteral {
33    fn into_param_literal(self) -> ParamLiteral {
34        self
35    }
36}
37
38macro_rules! impl_into_param_literal {
39    ($($rust_ty:ty => $value_variant:ident),+ $(,)?) => {
40        $(
41            impl IntoParamLiteral for $rust_ty {
42                fn into_param_literal(self) -> ParamLiteral {
43                    ParamLiteral(Value::$value_variant(self))
44                }
45            }
46        )+
47    };
48}
49
50impl_into_param_literal!(
51    bool => Bool,
52    i8 => I8,
53    i16 => I16,
54    i32 => I32,
55    i64 => I64,
56    i128 => I128,
57    u8 => U8,
58    u16 => U16,
59    u32 => U32,
60    u64 => U64,
61    u128 => U128,
62    f32 => F32,
63    f64 => F64,
64    Decimal => Decimal,
65    String => Str,
66    Vec<u8> => Bytea,
67    IpAddr => Inet,
68    NaiveDate => Date,
69    NaiveTime => Time,
70    NaiveDateTime => Timestamp,
71    Point => Point,
72    Interval => Interval,
73);
74
75// Types that need conversion
76impl IntoParamLiteral for isize {
77    fn into_param_literal(self) -> ParamLiteral {
78        ParamLiteral(Value::I64(self as i64))
79    }
80}
81
82impl IntoParamLiteral for usize {
83    fn into_param_literal(self) -> ParamLiteral {
84        ParamLiteral(Value::U64(self as u64))
85    }
86}
87
88impl IntoParamLiteral for &str {
89    fn into_param_literal(self) -> ParamLiteral {
90        ParamLiteral(Value::Str(self.to_owned()))
91    }
92}
93
94impl IntoParamLiteral for &[u8] {
95    fn into_param_literal(self) -> ParamLiteral {
96        ParamLiteral(Value::Bytea(self.to_vec()))
97    }
98}
99
100impl IntoParamLiteral for Uuid {
101    fn into_param_literal(self) -> ParamLiteral {
102        ParamLiteral(Value::Uuid(self.as_u128()))
103    }
104}
105
106impl<T> IntoParamLiteral for Option<T>
107where
108    T: IntoParamLiteral,
109{
110    fn into_param_literal(self) -> ParamLiteral {
111        match self {
112            Some(value) => value.into_param_literal(),
113            None => ParamLiteral::null(),
114        }
115    }
116}
117
118#[macro_export]
119macro_rules! params {
120    ($($expr:expr),* $(,)?) => {
121        vec![
122            $(
123                $crate::translate::IntoParamLiteral::into_param_literal($expr)
124            ),*
125        ]
126    };
127}
128
129#[cfg(test)]
130mod tests {
131    use {
132        super::*,
133        crate::{ast::Expr, data::Point},
134        chrono::{NaiveDate, NaiveTime},
135        rust_decimal::Decimal,
136        std::{net::IpAddr, str::FromStr},
137        uuid::Uuid,
138    };
139
140    #[test]
141    fn accepts_param_literal() {
142        let literal = ParamLiteral::null();
143        let converted = literal.clone().into_param_literal();
144        assert!(matches!(literal.into_expr(), Expr::Value(Value::Null)));
145        assert!(matches!(converted.into_expr(), Expr::Value(Value::Null)));
146    }
147
148    #[test]
149    fn converts_basic_literals() {
150        let expr = true.into_param_literal().into_expr();
151        assert_eq!(expr, Expr::Value(Value::Bool(true)));
152
153        let expr = 42_i64.into_param_literal().into_expr();
154        assert_eq!(expr, Expr::Value(Value::I64(42)));
155
156        let expr = "glue".into_param_literal().into_expr();
157        assert_eq!(expr, Expr::Value(Value::Str("glue".to_owned())));
158
159        let expr = String::from("owned").into_param_literal().into_expr();
160        assert_eq!(expr, Expr::Value(Value::Str("owned".to_owned())));
161
162        let expr = 3_i16.into_param_literal().into_expr();
163        assert_eq!(expr, Expr::Value(Value::I16(3)));
164
165        let expr = 7_u32.into_param_literal().into_expr();
166        assert_eq!(expr, Expr::Value(Value::U32(7)));
167    }
168
169    #[test]
170    fn converts_typed_literals() {
171        let date = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
172        let expr = date.into_param_literal().into_expr();
173        assert_eq!(expr, Expr::Value(Value::Date(date)));
174
175        let time = NaiveTime::from_hms_opt(9, 45, 30).unwrap();
176        let expr = time.into_param_literal().into_expr();
177        assert_eq!(expr, Expr::Value(Value::Time(time)));
178
179        let timestamp = NaiveDate::from_ymd_opt(2024, 1, 15)
180            .unwrap()
181            .and_hms_opt(12, 0, 0)
182            .unwrap();
183        let expr = timestamp.into_param_literal().into_expr();
184        assert_eq!(expr, Expr::Value(Value::Timestamp(timestamp)));
185
186        let timestamp_with_fraction = NaiveDate::from_ymd_opt(2024, 1, 15)
187            .unwrap()
188            .and_hms_micro_opt(12, 0, 0, 123_456)
189            .unwrap();
190        let expr = timestamp_with_fraction.into_param_literal().into_expr();
191        assert_eq!(expr, Expr::Value(Value::Timestamp(timestamp_with_fraction)));
192    }
193
194    #[test]
195    fn converts_interval() {
196        let expr = Interval::Month(2).into_param_literal().into_expr();
197        assert_eq!(expr, Expr::Value(Value::Interval(Interval::Month(2))));
198
199        let expr = Interval::Microsecond(1_500_000)
200            .into_param_literal()
201            .into_expr();
202        assert_eq!(
203            expr,
204            Expr::Value(Value::Interval(Interval::Microsecond(1_500_000)))
205        );
206    }
207
208    #[test]
209    fn converts_option() {
210        let expr = Some(1_i32).into_param_literal().into_expr();
211        assert_eq!(expr, Expr::Value(Value::I32(1)));
212
213        let expr = (None::<i32>).into_param_literal().into_expr();
214        assert_eq!(expr, Expr::Value(Value::Null));
215    }
216
217    #[test]
218    fn converts_scalars_and_structs() {
219        let expr = 1.25_f64.into_param_literal().into_expr();
220        assert_eq!(expr, Expr::Value(Value::F64(1.25)));
221
222        let expr = 1.5_f32.into_param_literal().into_expr();
223        assert_eq!(expr, Expr::Value(Value::F32(1.5)));
224
225        // NaN and Infinity are supported
226        let expr = f32::NAN.into_param_literal().into_expr();
227        assert!(matches!(expr, Expr::Value(Value::F32(v)) if v.is_nan()));
228
229        let expr = f64::INFINITY.into_param_literal().into_expr();
230        assert_eq!(expr, Expr::Value(Value::F64(f64::INFINITY)));
231
232        let expr = 42_isize.into_param_literal().into_expr();
233        assert_eq!(expr, Expr::Value(Value::I64(42)));
234
235        let expr = 42_usize.into_param_literal().into_expr();
236        assert_eq!(expr, Expr::Value(Value::U64(42)));
237
238        let expr = Decimal::new(345, 2).into_param_literal().into_expr();
239        assert_eq!(expr, Expr::Value(Value::Decimal(Decimal::new(345, 2))));
240
241        let expr = vec![0x12_u8, 0xAB].into_param_literal().into_expr();
242        assert_eq!(expr, Expr::Value(Value::Bytea(vec![0x12, 0xAB])));
243
244        let bytes = [0xCD_u8, 0xEF];
245        let expr = bytes.as_slice().into_param_literal().into_expr();
246        assert_eq!(expr, Expr::Value(Value::Bytea(vec![0xCD, 0xEF])));
247
248        let ip = IpAddr::from_str("127.0.0.1").unwrap();
249        let expr = ip.into_param_literal().into_expr();
250        assert_eq!(expr, Expr::Value(Value::Inet(ip)));
251
252        let uuid = Uuid::parse_str("123e4567-e89b-12d3-a456-426614174000").unwrap();
253        let expr = uuid.into_param_literal().into_expr();
254        assert_eq!(expr, Expr::Value(Value::Uuid(uuid.as_u128())));
255
256        let point = Point::new(1.0, 2.0);
257        let expr = point.into_param_literal().into_expr();
258        assert_eq!(expr, Expr::Value(Value::Point(point)));
259    }
260
261    #[test]
262    fn params_macro_collects_literals() {
263        let params = crate::params![1_i64, "Glue", Some(false), None::<i32>];
264
265        assert_eq!(params.len(), 4);
266
267        assert_eq!(params[0].clone().into_expr(), Expr::Value(Value::I64(1)));
268        assert_eq!(
269            params[1].clone().into_expr(),
270            Expr::Value(Value::Str("Glue".to_owned()))
271        );
272        assert_eq!(
273            params[2].clone().into_expr(),
274            Expr::Value(Value::Bool(false))
275        );
276        assert_eq!(params[3].clone().into_expr(), Expr::Value(Value::Null));
277    }
278}