1use std::borrow::Cow;
2
3use crate::value::Value;
4use crate::writer::DynExpr;
5
6use super::node::Expr;
7
8pub trait IntoExpr {
40 fn into_expr(self) -> Expr;
42}
43
44pub trait IntoExprList {
54 fn into_expr_list(self) -> Vec<Expr>;
56}
57
58pub trait IntoIdent {
65 fn into_ident_parts(self) -> Vec<Cow<'static, str>>;
67}
68
69impl IntoExpr for Expr {
70 fn into_expr(self) -> Expr {
71 self
72 }
73}
74
75impl IntoExprList for Expr {
76 fn into_expr_list(self) -> Vec<Expr> {
77 vec![self]
78 }
79}
80
81impl IntoExprList for () {
83 fn into_expr_list(self) -> Vec<Expr> {
84 Vec::new()
85 }
86}
87
88impl IntoExpr for Value {
89 fn into_expr(self) -> Expr {
90 Expr::Arg(self)
91 }
92}
93
94impl IntoExprList for Value {
95 fn into_expr_list(self) -> Vec<Expr> {
96 vec![Expr::Arg(self)]
97 }
98}
99
100impl IntoExpr for DynExpr {
101 fn into_expr(self) -> Expr {
102 Expr::Custom(self)
103 }
104}
105
106impl IntoExprList for DynExpr {
107 fn into_expr_list(self) -> Vec<Expr> {
108 vec![Expr::Custom(self)]
109 }
110}
111
112macro_rules! impl_from_text {
114 ($($t:ty),+ $(,)?) => {
115 $(
116 impl IntoExpr for $t {
117 fn into_expr(self) -> Expr {
118 Expr::Raw(self.into())
119 }
120 }
121
122 impl IntoExprList for $t {
123 fn into_expr_list(self) -> Vec<Expr> {
124 vec![Expr::Raw(self.into())]
125 }
126 }
127 )+
128 };
129}
130
131impl_from_text!(&'static str, String, Cow<'static, str>);
132
133macro_rules! impl_from_scalar {
137 ($($t:ty),+ $(,)?) => {
138 $(
139 impl IntoExpr for $t {
140 fn into_expr(self) -> Expr {
141 Expr::Raw(Cow::Owned(self.to_string()))
142 }
143 }
144
145 impl IntoExprList for $t {
146 fn into_expr_list(self) -> Vec<Expr> {
147 vec![self.into_expr()]
148 }
149 }
150 )+
151 };
152}
153
154impl_from_scalar!(
155 bool, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64
156);
157
158impl<T: IntoExpr, const N: usize> IntoExprList for [T; N] {
159 fn into_expr_list(self) -> Vec<Expr> {
160 self.into_iter().map(IntoExpr::into_expr).collect()
161 }
162}
163
164impl<T: IntoExpr> IntoExprList for Vec<T> {
165 fn into_expr_list(self) -> Vec<Expr> {
166 self.into_iter().map(IntoExpr::into_expr).collect()
167 }
168}
169
170macro_rules! impl_expr_list_tuple {
171 ($($name:ident),+) => {
172 #[allow(non_snake_case)]
173 impl<$($name: IntoExpr),+> IntoExprList for ($($name,)+) {
174 fn into_expr_list(self) -> Vec<Expr> {
175 let ($($name,)+) = self;
176 vec![$($name.into_expr()),+]
177 }
178 }
179 };
180}
181
182impl_expr_list_tuple!(A);
183impl_expr_list_tuple!(A, B);
184impl_expr_list_tuple!(A, B, C);
185impl_expr_list_tuple!(A, B, C, D);
186impl_expr_list_tuple!(A, B, C, D, E);
187impl_expr_list_tuple!(A, B, C, D, E, F);
188impl_expr_list_tuple!(A, B, C, D, E, F, G);
189impl_expr_list_tuple!(A, B, C, D, E, F, G, H);
190impl_expr_list_tuple!(A, B, C, D, E, F, G, H, I);
191impl_expr_list_tuple!(A, B, C, D, E, F, G, H, I, J);
192impl_expr_list_tuple!(A, B, C, D, E, F, G, H, I, J, K);
193impl_expr_list_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);
194impl_expr_list_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M);
195impl_expr_list_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
196impl_expr_list_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
197impl_expr_list_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
198
199macro_rules! impl_ident_from_text {
200 ($($t:ty),+ $(,)?) => {
201 $(
202 impl IntoIdent for $t {
203 fn into_ident_parts(self) -> Vec<Cow<'static, str>> {
204 vec![self.into()]
205 }
206 }
207 )+
208 };
209}
210
211impl_ident_from_text!(&'static str, String, Cow<'static, str>);
212
213impl<T: Into<Cow<'static, str>>, const N: usize> IntoIdent for [T; N] {
214 fn into_ident_parts(self) -> Vec<Cow<'static, str>> {
215 self.into_iter().map(Into::into).collect()
216 }
217}
218
219impl<T: Into<Cow<'static, str>>> IntoIdent for Vec<T> {
220 fn into_ident_parts(self) -> Vec<Cow<'static, str>> {
221 self.into_iter().map(Into::into).collect()
222 }
223}
224
225macro_rules! impl_ident_tuple {
226 ($($name:ident),+) => {
227 #[allow(non_snake_case)]
228 impl<$($name: Into<Cow<'static, str>>),+> IntoIdent for ($($name,)+) {
229 fn into_ident_parts(self) -> Vec<Cow<'static, str>> {
230 let ($($name,)+) = self;
231 vec![$($name.into()),+]
232 }
233 }
234 };
235}
236
237impl_ident_tuple!(A);
239impl_ident_tuple!(A, B);
240impl_ident_tuple!(A, B, C);
241impl_ident_tuple!(A, B, C, D);
242
243#[cfg(test)]
244mod tests {
245 use keelson_sqlcheck::testing::assert_frag_sql;
246
247 use super::*;
248 use crate::dialect::testing::Numbered;
249 use crate::writer::build;
250
251 const COND: &str = r#"SELECT "id" FROM users WHERE {}"#;
252 const VALUE: &str = r#"SELECT {} FROM users"#;
253 const EQ: &str = r#"SELECT "id" FROM users WHERE "age" = {}"#;
254
255 fn rendered(e: Expr) -> (String, Vec<Value>) {
256 build(&Numbered, &e).expect("render")
257 }
258
259 #[test]
260 fn text_becomes_raw_sql() {
261 for e in [
262 "age = 1".into_expr(),
263 String::from("age = 1").into_expr(),
264 Cow::Borrowed("age = 1").into_expr(),
265 ] {
266 assert!(matches!(e, Expr::Raw(_)));
267 assert_frag_sql(COND, &rendered(e).0, "age = 1");
268 }
269 }
270
271 #[test]
272 fn numbers_become_literals_and_values_become_arguments() {
273 let (sql, args) = rendered(20i64.into_expr());
274 assert_frag_sql(VALUE, &sql, "20");
275 assert!(args.is_empty(), "a literal binds nothing");
276
277 let (sql, args) = rendered(Value::I64(20).into_expr());
278 assert_frag_sql(EQ, &sql, "$1");
279 assert_eq!(args, vec![Value::I64(20)]);
280 }
281
282 #[test]
283 fn booleans_and_floats_render_as_written() {
284 assert_frag_sql(VALUE, &rendered(true.into_expr()).0, "true");
285 assert_frag_sql(VALUE, &rendered(1.5f64.into_expr()).0, "1.5");
286 }
287
288 #[test]
289 fn a_list_can_be_a_tuple_an_array_a_vec_or_nothing() {
290 assert_eq!(().into_expr_list().len(), 0);
291 assert_eq!(("a", 1, Value::I32(2)).into_expr_list().len(), 3);
292 assert_eq!(["a", "b"].into_expr_list().len(), 2);
293 assert_eq!(vec![1i32, 2, 3].into_expr_list().len(), 3);
294 assert_eq!(Expr::raw("a").into_expr_list().len(), 1);
295 assert_eq!("a".into_expr_list().len(), 1);
296 }
297
298 #[test]
299 fn a_heterogeneous_tuple_keeps_each_conversion() {
300 let list = ("created_date", 1i32, Value::Text("x".into())).into_expr_list();
301 assert!(matches!(list[0], Expr::Raw(_)));
302 assert!(matches!(list[1], Expr::Raw(_)));
303 assert!(matches!(list[2], Expr::Arg(_)));
304 }
305
306 #[test]
307 fn an_identifier_takes_one_part_or_several() {
308 assert_frag_sql(VALUE, &rendered(Expr::ident("age")).0, r#""age""#);
309 assert_frag_sql(
310 VALUE,
311 &rendered(Expr::ident(("users", "id"))).0,
312 r#""users"."id""#,
313 );
314 assert_frag_sql(
317 VALUE,
318 &rendered(Expr::ident(["public", "users", "id"])).0,
319 r#""public"."users"."id""#,
320 );
321 assert_frag_sql(
322 VALUE,
323 &rendered(Expr::ident(vec![String::from("users"), String::from("id")])).0,
324 r#""users"."id""#,
325 );
326 assert_frag_sql(VALUE, &rendered(Expr::ident(("", "id"))).0, r#""id""#);
328 }
329
330 #[test]
331 fn an_erased_expression_arrives_as_custom() {
332 let e: DynExpr = crate::writer::dyn_expr("1 + 1");
333 assert!(matches!(e.clone().into_expr(), Expr::Custom(_)));
334 assert_frag_sql(VALUE, &rendered(e.into_expr()).0, "1 + 1");
335 }
336}