1use itertools::Itertools;
2use std::collections::BTreeMap;
12
13use crate::column_rel_value::{ColumnOpValue, CompareOp, serde_value_to_single_column_rel_value};
14use crate::value::Value;
15
16#[derive(Clone, Debug, PartialEq)]
17pub enum Combiner {
18 And,
19 Or,
20}
21
22#[derive(Clone, Debug, PartialEq)]
23pub enum ValueOrComposite {
24 Value(ColumnOpValue),
25 Composite(Combiner, Vec<ValueOrComposite>),
26}
27
28impl ValueOrComposite {
29 pub fn into_sql<V, E>(
31 self,
32 column_prefix: Option<&str>,
33 convert: &dyn Fn(&str, Value) -> Result<V, E>,
34 ) -> Result<(String, Vec<(String, V)>), E> {
35 fn render_value<V, E>(
36 column_op_value: ColumnOpValue,
37 column_prefix: Option<&str>,
38 convert: &dyn Fn(&str, Value) -> Result<V, E>,
39 index: &mut usize,
40 ) -> Result<(String, Option<(String, V)>), E> {
41 let v = column_op_value.value;
42 let c = column_op_value.column;
43
44 return match column_op_value.op {
45 CompareOp::Is => {
46 debug_assert!(matches!(v, Value::String(_)), "{v:?}");
47
48 Ok(match column_prefix {
49 Some(p) => (format!(r#"{p}."{c}" IS {v}"#), None),
50 None => (format!(r#""{c}" IS {v}"#), None),
51 })
52 }
53 op => {
54 let param = param_name(*index);
55 *index += 1;
56
57 Ok(match column_prefix {
58 Some(p) => (
59 format!(r#"{p}."{c}" {o} {param}"#, o = op.as_sql()),
60 Some((param, convert(&c, v)?)),
61 ),
62 None => (
63 format!(r#""{c}" {o} {param}"#, o = op.as_sql()),
64 Some((param, convert(&c, v)?)),
65 ),
66 })
67 }
68 };
69 }
70
71 fn recurse<V, E>(
72 v: ValueOrComposite,
73 column_prefix: Option<&str>,
74 convert: &dyn Fn(&str, Value) -> Result<V, E>,
75 index: &mut usize,
76 ) -> Result<(String, Vec<(String, V)>), E> {
77 match v {
78 ValueOrComposite::Value(v) => {
79 return Ok(match render_value(v, column_prefix, convert, index)? {
80 (sql, Some(param)) => (sql, vec![param]),
81 (sql, None) => (sql, vec![]),
82 });
83 }
84 ValueOrComposite::Composite(combiner, vec) => {
85 let mut fragments = Vec::<String>::with_capacity(vec.len());
86 let mut params = Vec::<(String, V)>::with_capacity(vec.len());
87
88 for value_or_composite in vec {
89 let (f, p) = recurse(value_or_composite, column_prefix, convert, index)?;
90 fragments.push(f);
91 params.extend(p);
92 }
93
94 let fragment = format!(
95 "({})",
96 fragments.join(match combiner {
97 Combiner::And => " AND ",
98 Combiner::Or => " OR ",
99 }),
100 );
101 return Ok((fragment, params));
102 }
103 };
104 }
105
106 let mut index: usize = 0;
107 return recurse(self, column_prefix, convert, &mut index);
108 }
109
110 pub fn to_query(&self) -> String {
112 fn render_value(prefix: &str, v: &ColumnOpValue) -> String {
114 let value: std::borrow::Cow<str> = match (&v.op, &v.value) {
115 (CompareOp::Is, Value::String(s)) if s == "NOT NULL" => "!NULL".into(),
116 (CompareOp::Is, Value::String(s)) if s == "NULL" => "NULL".into(),
117 (_, Value::String(s)) => s.into(),
118 (_, Value::Integer(i)) => i.to_string().into(),
119 (_, Value::Double(d)) => d.to_string().into(),
120 };
121
122 let column = &v.column;
123 return if matches!(v.op, CompareOp::Equal) {
124 format!("{prefix}[{column}]={value}")
125 } else {
126 format!("{prefix}[{column}][{}]={value}", v.op.as_query())
127 };
128 }
129
130 fn recurse(v: &ValueOrComposite, prefix: &str) -> Vec<String> {
131 return match v {
132 ValueOrComposite::Value(v) => vec![render_value(prefix, v)],
133 ValueOrComposite::Composite(combiner, vec) => {
134 let comb = match combiner {
135 Combiner::And => "$and",
136 Combiner::Or => "$or",
137 };
138
139 vec
140 .iter()
141 .enumerate()
142 .flat_map(|(i, el)| recurse(el, &format!("{}[{}][{}]", prefix, comb, i)))
143 .collect()
144 }
145 };
146 }
147
148 return recurse(self, "filter").into_iter().join("&");
149 }
150}
151
152fn serde_value_to_value_or_composite<'de, D>(
153 value: serde_value::Value,
154 depth: usize,
155) -> Result<ValueOrComposite, D::Error>
156where
157 D: serde::de::Deserializer<'de>,
158{
159 use serde::de::Error;
160 use serde_value::Value;
161
162 if depth >= 5 {
164 return Err(Error::custom("Recursion limit exceeded"));
165 }
166
167 let Value::Map(mut m) = value else {
169 return Err(Error::invalid_type(
170 crate::util::unexpected(&value),
171 &"[($and|$or)][index][<nested>] or [column_name][$op]=value",
172 ));
173 };
174
175 return match m.len() {
176 0 => Ok(ValueOrComposite::Composite(Combiner::And, vec![])),
177 1 => {
178 let first = m.pop_first().expect("len == 1");
179 let (Value::String(key), v) = first else {
180 return Err(Error::invalid_type(
181 crate::util::unexpected(&first.0),
182 &"String",
183 ));
184 };
185
186 match (key.as_str(), v) {
187 ("$and", Value::Seq(values)) => combine::<D>(Combiner::And, values, depth),
189 ("$and", v) => Err(Error::invalid_type(
190 crate::util::unexpected(&v),
191 &"sequence",
192 )),
193 ("$or", Value::Seq(values)) => combine::<D>(Combiner::Or, values, depth),
194 ("$or", v) => Err(Error::invalid_type(
195 crate::util::unexpected(&v),
196 &"sequence",
197 )),
198 (col_name, Value::Map(m)) if m.len() > 1 => Ok(ValueOrComposite::Composite(
200 Combiner::And,
201 m.into_iter()
202 .map(|(key, value)| {
203 return Ok(ValueOrComposite::Value(
204 serde_value_to_single_column_rel_value::<D>(
205 col_name.to_string(),
206 Value::Map(BTreeMap::from([(key, value)])),
207 )?,
208 ));
209 })
210 .collect::<Result<Vec<_>, _>>()?,
211 )),
212 (_key, v) => Ok(ValueOrComposite::Value(
214 serde_value_to_single_column_rel_value::<D>(key, v)?,
215 )),
216 }
217 }
218 _n => combine::<D>(
221 Combiner::And,
222 m.into_iter()
223 .map(|(k, v)| Value::Map(BTreeMap::from([(k, v)]))),
224 depth,
225 ),
226 };
227}
228
229fn combine<'de, D>(
231 combiner: Combiner,
232 values: impl IntoIterator<Item = serde_value::Value>,
233 depth: usize,
234) -> Result<ValueOrComposite, D::Error>
235where
236 D: serde::de::Deserializer<'de>,
237{
238 return Ok(ValueOrComposite::Composite(
239 combiner,
240 values
241 .into_iter()
242 .map(|v| serde_value_to_value_or_composite::<D>(v, depth + 1))
243 .collect::<Result<Vec<_>, _>>()?,
244 ));
245}
246
247impl<'de> serde::de::Deserialize<'de> for ValueOrComposite {
248 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
249 where
250 D: serde::de::Deserializer<'de>,
251 {
252 return serde_value_to_value_or_composite::<D>(
253 serde_value::Value::deserialize(deserializer)?,
254 0,
255 );
256 }
257}
258
259#[inline]
260fn param_name(index: usize) -> String {
261 let mut s = String::with_capacity(10);
262 s.push_str(":__p");
263 s.push_str(&index.to_string());
264 return s;
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270
271 use rusqlite::types::Value as SqlValue;
272 use serde_qs::Config;
273
274 use crate::column_rel_value::{ColumnOpValue, CompareOp};
275 use crate::query::FilterQuery as Query;
276 use crate::value::Value;
277
278 #[test]
279 fn test_filter_parsing() {
280 let qs = Config::new(5, false);
281
282 let m_empty: Query = qs.deserialize_str("").unwrap();
283 assert_eq!(m_empty.filter, None);
284
285 let q0 = "filter[$and][0][col0]=val0";
286 let f0 = qs.deserialize_str::<Query>(q0).unwrap().filter.unwrap();
287 assert_eq!(
288 f0,
289 ValueOrComposite::Composite(
290 Combiner::And,
291 vec![ValueOrComposite::Value(ColumnOpValue {
292 column: "col0".to_string(),
293 op: CompareOp::Equal,
294 value: Value::String("val0".to_string()),
295 })]
296 ),
297 );
298 assert_eq!(q0, f0.to_query());
299
300 let q1 = "filter[$and][0][col0]=val0&filter[$and][1][col1]=val1";
301 let f1 = qs.deserialize_str::<Query>(q1).unwrap().filter.unwrap();
302 assert_eq!(
303 f1,
304 ValueOrComposite::Composite(
305 Combiner::And,
306 vec![
307 ValueOrComposite::Value(ColumnOpValue {
308 column: "col0".to_string(),
309 op: CompareOp::Equal,
310 value: Value::String("val0".to_string()),
311 }),
312 ValueOrComposite::Value(ColumnOpValue {
313 column: "col1".to_string(),
314 op: CompareOp::Equal,
315 value: Value::String("val1".to_string()),
316 }),
317 ]
318 )
319 );
320 assert_eq!(q1, f1.to_query());
321
322 let q3 = "filter[col0][$is]=!NULL";
323 let f3 = qs.deserialize_str::<Query>(q3).unwrap().filter.unwrap();
324 assert_eq!(
325 f3,
326 ValueOrComposite::Value(ColumnOpValue {
327 column: "col0".to_string(),
328 op: CompareOp::Is,
329 value: Value::String("NOT NULL".to_string()),
330 })
331 );
332 assert_eq!(q3, f3.to_query());
333
334 let m = qs.deserialize_str::<Query>("filter[$and][0][col0]=val0&filter[$or][1][col1]=val1");
336 assert!(m.is_ok(), "M: {m:?}");
337
338 let q2 = "filter[col0]=val0&filter[col1]=val1";
340 let f2 = qs.deserialize_str::<Query>(q2).unwrap().filter.unwrap();
341
342 assert_eq!(
343 f2,
344 ValueOrComposite::Composite(
345 Combiner::And,
346 vec![
347 ValueOrComposite::Value(ColumnOpValue {
348 column: "col0".to_string(),
349 op: CompareOp::Equal,
350 value: Value::String("val0".to_string()),
351 }),
352 ValueOrComposite::Value(ColumnOpValue {
353 column: "col1".to_string(),
354 op: CompareOp::Equal,
355 value: Value::String("val1".to_string()),
356 }),
357 ]
358 )
359 );
360
361 let q2_explicit = "filter[$and][0][col0]=val0&filter[$and][1][col1]=val1";
362 let f2_explicit = qs
363 .deserialize_str::<Query>(q2_explicit)
364 .unwrap()
365 .filter
366 .unwrap();
367
368 assert_eq!(f2_explicit, f2);
369 assert_eq!(q2_explicit, f2.to_query());
370 }
371
372 #[test]
373 fn test_filter_to_sql() {
374 let v0 = ValueOrComposite::Value(ColumnOpValue {
375 column: "col0".to_string(),
376 op: CompareOp::Equal,
377 value: Value::String("val0".to_string()),
378 });
379
380 let convert = |_: &str, value: Value| -> Result<SqlValue, String> {
381 return Ok(match value {
382 Value::String(s) => SqlValue::Text(s),
383 Value::Integer(i) => SqlValue::Integer(i),
384 Value::Double(d) => SqlValue::Real(d),
385 });
386 };
387
388 let sql0 = v0
389 .clone()
390 .into_sql(None, &convert)
391 .unwrap();
392 assert_eq!(sql0.0, r#""col0" = :__p0"#);
393 let sql0 = v0
394 .into_sql(Some("p"), &convert)
395 .unwrap();
396 assert_eq!(sql0.0, r#"p."col0" = :__p0"#);
397
398 let v1 = ValueOrComposite::Value(ColumnOpValue {
399 column: "col0".to_string(),
400 op: CompareOp::Is,
401 value: Value::String("NULL".to_string()),
402 });
403 let sql1 = v1.into_sql(None, &convert).unwrap();
404 assert_eq!(sql1.0, r#""col0" IS NULL"#, "{sql1:?}",);
405 }
406}