halo_space/
cond.rs

1//! Cond: helpers to build WHERE clause expressions.
2
3use crate::args::Args;
4use crate::flavor::Flavor;
5use crate::macros::{IntoStrings, collect_into_strings};
6use crate::modifiers::{Arg, Builder};
7use crate::string_builder::{StringBuilder, filter_empty_strings};
8use std::cell::RefCell;
9use std::rc::Rc;
10
11const MIN_INDEX_BASE: usize = 256;
12
13pub type ArgsRef = Rc<RefCell<Args>>;
14
15/// Cond provides helper methods for conditional expressions.
16#[derive(Debug, Clone)]
17pub struct Cond {
18    pub(crate) args: ArgsRef,
19}
20
21impl Cond {
22    /// Create an independent Cond; uses a larger index_base to avoid accidental recursion.
23    pub fn new() -> Self {
24        let a = Args {
25            index_base: MIN_INDEX_BASE,
26            ..Args::default()
27        };
28        Self {
29            args: Rc::new(RefCell::new(a)),
30        }
31    }
32
33    pub(crate) fn with_args(args: ArgsRef) -> Self {
34        Self { args }
35    }
36
37    /// Var: store a value into Args and return the `$n` placeholder.
38    pub fn var(&self, value: impl Into<Arg>) -> String {
39        self.args.borrow_mut().add(value)
40    }
41
42    fn expr_builder(&self, f: impl Fn(Flavor, &[Arg]) -> (String, Vec<Arg>) + 'static) -> String {
43        self.var(Arg::Builder(Box::new(CondDynBuilder::new(f))))
44    }
45
46    pub fn equal(&self, field: &str, value: impl Into<Arg>) -> String {
47        if field.is_empty() {
48            return String::new();
49        }
50        let field = field.to_string();
51        let value: Arg = value.into();
52        self.expr_builder(move |flavor, initial| {
53            let mut a = Args {
54                flavor,
55                ..Args::default()
56            };
57            let v = a.add(value.clone());
58            let fmt = format!("{field} = {v}");
59            a.compile_with_flavor(&fmt, flavor, initial)
60        })
61    }
62    pub fn e(&self, field: &str, value: impl Into<Arg>) -> String {
63        self.equal(field, value)
64    }
65    pub fn eq(&self, field: &str, value: impl Into<Arg>) -> String {
66        self.equal(field, value)
67    }
68
69    pub fn not_equal(&self, field: &str, value: impl Into<Arg>) -> String {
70        if field.is_empty() {
71            return String::new();
72        }
73        let field = field.to_string();
74        let value: Arg = value.into();
75        self.expr_builder(move |flavor, initial| {
76            let mut a = Args {
77                flavor,
78                ..Args::default()
79            };
80            let v = a.add(value.clone());
81            let fmt = format!("{field} <> {v}");
82            a.compile_with_flavor(&fmt, flavor, initial)
83        })
84    }
85    pub fn ne(&self, field: &str, value: impl Into<Arg>) -> String {
86        self.not_equal(field, value)
87    }
88    pub fn neq(&self, field: &str, value: impl Into<Arg>) -> String {
89        self.not_equal(field, value)
90    }
91
92    pub fn greater_than(&self, field: &str, value: impl Into<Arg>) -> String {
93        if field.is_empty() {
94            return String::new();
95        }
96        let field = field.to_string();
97        let value: Arg = value.into();
98        self.expr_builder(move |flavor, initial| {
99            let mut a = Args {
100                flavor,
101                ..Args::default()
102            };
103            let v = a.add(value.clone());
104            let fmt = format!("{field} > {v}");
105            a.compile_with_flavor(&fmt, flavor, initial)
106        })
107    }
108    pub fn g(&self, field: &str, value: impl Into<Arg>) -> String {
109        self.greater_than(field, value)
110    }
111    pub fn gt(&self, field: &str, value: impl Into<Arg>) -> String {
112        self.greater_than(field, value)
113    }
114
115    pub fn greater_equal_than(&self, field: &str, value: impl Into<Arg>) -> String {
116        if field.is_empty() {
117            return String::new();
118        }
119        let field = field.to_string();
120        let value: Arg = value.into();
121        self.expr_builder(move |flavor, initial| {
122            let mut a = Args {
123                flavor,
124                ..Args::default()
125            };
126            let v = a.add(value.clone());
127            let fmt = format!("{field} >= {v}");
128            a.compile_with_flavor(&fmt, flavor, initial)
129        })
130    }
131    pub fn ge(&self, field: &str, value: impl Into<Arg>) -> String {
132        self.greater_equal_than(field, value)
133    }
134    pub fn gte(&self, field: &str, value: impl Into<Arg>) -> String {
135        self.greater_equal_than(field, value)
136    }
137
138    pub fn less_than(&self, field: &str, value: impl Into<Arg>) -> String {
139        if field.is_empty() {
140            return String::new();
141        }
142        let field = field.to_string();
143        let value: Arg = value.into();
144        self.expr_builder(move |flavor, initial| {
145            let mut a = Args {
146                flavor,
147                ..Args::default()
148            };
149            let v = a.add(value.clone());
150            let fmt = format!("{field} < {v}");
151            a.compile_with_flavor(&fmt, flavor, initial)
152        })
153    }
154    pub fn l(&self, field: &str, value: impl Into<Arg>) -> String {
155        self.less_than(field, value)
156    }
157    pub fn lt(&self, field: &str, value: impl Into<Arg>) -> String {
158        self.less_than(field, value)
159    }
160
161    pub fn less_equal_than(&self, field: &str, value: impl Into<Arg>) -> String {
162        if field.is_empty() {
163            return String::new();
164        }
165        let field = field.to_string();
166        let value: Arg = value.into();
167        self.expr_builder(move |flavor, initial| {
168            let mut a = Args {
169                flavor,
170                ..Args::default()
171            };
172            let v = a.add(value.clone());
173            let fmt = format!("{field} <= {v}");
174            a.compile_with_flavor(&fmt, flavor, initial)
175        })
176    }
177    pub fn le(&self, field: &str, value: impl Into<Arg>) -> String {
178        self.less_equal_than(field, value)
179    }
180    pub fn lte(&self, field: &str, value: impl Into<Arg>) -> String {
181        self.less_equal_than(field, value)
182    }
183
184    pub fn like(&self, field: &str, value: impl Into<Arg>) -> String {
185        if field.is_empty() {
186            return String::new();
187        }
188        let field = field.to_string();
189        let value: Arg = value.into();
190        self.expr_builder(move |flavor, initial| {
191            let mut a = Args {
192                flavor,
193                ..Args::default()
194            };
195            let v = a.add(value.clone());
196            let fmt = format!("{field} LIKE {v}");
197            a.compile_with_flavor(&fmt, flavor, initial)
198        })
199    }
200
201    pub fn ilike(&self, field: &str, value: impl Into<Arg>) -> String {
202        if field.is_empty() {
203            return String::new();
204        }
205
206        let field = field.to_string();
207        let value: Arg = value.into();
208
209        // Choose ILIKE or LOWER(...) LIKE LOWER(...) based on flavor
210        let b = CondDynBuilder::new(move |flavor, initial| {
211            let mut a = Args {
212                flavor,
213                ..Args::default()
214            };
215            let v = a.add(value.clone());
216            let fmt = match flavor {
217                Flavor::PostgreSQL | Flavor::SQLite => format!("{} ILIKE {v}", field),
218                _ => format!("LOWER({}) LIKE LOWER({v})", field),
219            };
220            a.compile_with_flavor(&fmt, flavor, initial)
221        });
222        self.var(Arg::Builder(Box::new(b)))
223    }
224
225    pub fn not_like(&self, field: &str, value: impl Into<Arg>) -> String {
226        if field.is_empty() {
227            return String::new();
228        }
229        let field = field.to_string();
230        let value: Arg = value.into();
231        self.expr_builder(move |flavor, initial| {
232            let mut a = Args {
233                flavor,
234                ..Args::default()
235            };
236            let v = a.add(value.clone());
237            let fmt = format!("{field} NOT LIKE {v}");
238            a.compile_with_flavor(&fmt, flavor, initial)
239        })
240    }
241
242    pub fn not_ilike(&self, field: &str, value: impl Into<Arg>) -> String {
243        if field.is_empty() {
244            return String::new();
245        }
246
247        let field = field.to_string();
248        let value: Arg = value.into();
249
250        let b = CondDynBuilder::new(move |flavor, initial| {
251            let mut a = Args {
252                flavor,
253                ..Args::default()
254            };
255            let v = a.add(value.clone());
256            let fmt = match flavor {
257                Flavor::PostgreSQL | Flavor::SQLite => format!("{} NOT ILIKE {v}", field),
258                _ => format!("LOWER({}) NOT LIKE LOWER({v})", field),
259            };
260            a.compile_with_flavor(&fmt, flavor, initial)
261        });
262        self.var(Arg::Builder(Box::new(b)))
263    }
264
265    pub fn is_null(&self, field: &str) -> String {
266        if field.is_empty() {
267            return String::new();
268        }
269        let field = field.to_string();
270        self.expr_builder(move |_flavor, initial| (format!("{field} IS NULL"), initial.to_vec()))
271    }
272
273    pub fn is_not_null(&self, field: &str) -> String {
274        if field.is_empty() {
275            return String::new();
276        }
277        let field = field.to_string();
278        self.expr_builder(move |_flavor, initial| {
279            (format!("{field} IS NOT NULL"), initial.to_vec())
280        })
281    }
282
283    pub fn between(&self, field: &str, lower: impl Into<Arg>, upper: impl Into<Arg>) -> String {
284        if field.is_empty() {
285            return String::new();
286        }
287        let field = field.to_string();
288        let lower: Arg = lower.into();
289        let upper: Arg = upper.into();
290        self.expr_builder(move |flavor, initial| {
291            let mut a = Args {
292                flavor,
293                ..Args::default()
294            };
295            let l = a.add(lower.clone());
296            let u = a.add(upper.clone());
297            let fmt = format!("{field} BETWEEN {l} AND {u}");
298            a.compile_with_flavor(&fmt, flavor, initial)
299        })
300    }
301
302    pub fn not_between(&self, field: &str, lower: impl Into<Arg>, upper: impl Into<Arg>) -> String {
303        if field.is_empty() {
304            return String::new();
305        }
306        let field = field.to_string();
307        let lower: Arg = lower.into();
308        let upper: Arg = upper.into();
309        self.expr_builder(move |flavor, initial| {
310            let mut a = Args {
311                flavor,
312                ..Args::default()
313            };
314            let l = a.add(lower.clone());
315            let u = a.add(upper.clone());
316            let fmt = format!("{field} NOT BETWEEN {l} AND {u}");
317            a.compile_with_flavor(&fmt, flavor, initial)
318        })
319    }
320
321    pub fn in_(&self, field: &str, values: impl IntoIterator<Item = impl Into<Arg>>) -> String {
322        if field.is_empty() {
323            return String::new();
324        }
325        let values: Vec<Arg> = values.into_iter().map(|v| v.into()).collect();
326        if values.is_empty() {
327            return "0 = 1".to_string();
328        }
329        let field = field.to_string();
330        self.expr_builder(move |flavor, initial| {
331            let mut a = Args {
332                flavor,
333                ..Args::default()
334            };
335            let vals: Vec<String> = values.iter().cloned().map(|v| a.add(v)).collect();
336            let fmt = format!("{field} IN ({})", vals.join(", "));
337            a.compile_with_flavor(&fmt, flavor, initial)
338        })
339    }
340
341    pub fn not_in(&self, field: &str, values: impl IntoIterator<Item = impl Into<Arg>>) -> String {
342        if field.is_empty() {
343            return String::new();
344        }
345        let values: Vec<Arg> = values.into_iter().map(|v| v.into()).collect();
346        if values.is_empty() {
347            return "0 = 0".to_string();
348        }
349        let field = field.to_string();
350        self.expr_builder(move |flavor, initial| {
351            let mut a = Args {
352                flavor,
353                ..Args::default()
354            };
355            let vals: Vec<String> = values.iter().cloned().map(|v| a.add(v)).collect();
356            let fmt = format!("{field} NOT IN ({})", vals.join(", "));
357            a.compile_with_flavor(&fmt, flavor, initial)
358        })
359    }
360
361    pub fn or<T>(&self, exprs: T) -> String
362    where
363        T: IntoStrings,
364    {
365        let exprs = filter_empty_strings(collect_into_strings(exprs));
366        if exprs.is_empty() {
367            return String::new();
368        }
369        let mut buf = StringBuilder::new();
370        buf.write_str("(");
371        buf.write_strings(&exprs, " OR ");
372        buf.write_str(")");
373        buf.into_string()
374    }
375
376    pub fn and<T>(&self, exprs: T) -> String
377    where
378        T: IntoStrings,
379    {
380        let exprs = filter_empty_strings(collect_into_strings(exprs));
381        if exprs.is_empty() {
382            return String::new();
383        }
384        let mut buf = StringBuilder::new();
385        buf.write_str("(");
386        buf.write_strings(&exprs, " AND ");
387        buf.write_str(")");
388        buf.into_string()
389    }
390
391    pub fn not(&self, expr: impl Into<String>) -> String {
392        let expr = expr.into();
393        if expr.is_empty() {
394            return String::new();
395        }
396        format!("NOT {expr}")
397    }
398
399    pub fn exists(&self, subquery: impl Into<Arg>) -> String {
400        let subquery: Arg = subquery.into();
401        self.expr_builder(move |flavor, initial| {
402            let mut a = Args {
403                flavor,
404                ..Args::default()
405            };
406            let v = a.add(subquery.clone());
407            let fmt = format!("EXISTS ({v})");
408            a.compile_with_flavor(&fmt, flavor, initial)
409        })
410    }
411
412    pub fn not_exists(&self, subquery: impl Into<Arg>) -> String {
413        let subquery: Arg = subquery.into();
414        self.expr_builder(move |flavor, initial| {
415            let mut a = Args {
416                flavor,
417                ..Args::default()
418            };
419            let v = a.add(subquery.clone());
420            let fmt = format!("NOT EXISTS ({v})");
421            a.compile_with_flavor(&fmt, flavor, initial)
422        })
423    }
424
425    pub fn any(
426        &self,
427        field: &str,
428        op: &str,
429        values: impl IntoIterator<Item = impl Into<Arg>>,
430    ) -> String {
431        if field.is_empty() || op.is_empty() {
432            return String::new();
433        }
434        let values: Vec<Arg> = values.into_iter().map(|v| v.into()).collect();
435        if values.is_empty() {
436            return "0 = 1".to_string();
437        }
438        let field = field.to_string();
439        let op = op.to_string();
440        self.expr_builder(move |flavor, initial| {
441            let mut a = Args {
442                flavor,
443                ..Args::default()
444            };
445            let vals: Vec<String> = values.iter().cloned().map(|v| a.add(v)).collect();
446            let fmt = format!("{field} {op} ANY ({})", vals.join(", "));
447            a.compile_with_flavor(&fmt, flavor, initial)
448        })
449    }
450
451    pub fn all(
452        &self,
453        field: &str,
454        op: &str,
455        values: impl IntoIterator<Item = impl Into<Arg>>,
456    ) -> String {
457        if field.is_empty() || op.is_empty() {
458            return String::new();
459        }
460        let values: Vec<Arg> = values.into_iter().map(|v| v.into()).collect();
461        if values.is_empty() {
462            return "0 = 1".to_string();
463        }
464        let field = field.to_string();
465        let op = op.to_string();
466        self.expr_builder(move |flavor, initial| {
467            let mut a = Args {
468                flavor,
469                ..Args::default()
470            };
471            let vals: Vec<String> = values.iter().cloned().map(|v| a.add(v)).collect();
472            let fmt = format!("{field} {op} ALL ({})", vals.join(", "));
473            a.compile_with_flavor(&fmt, flavor, initial)
474        })
475    }
476
477    pub fn some(
478        &self,
479        field: &str,
480        op: &str,
481        values: impl IntoIterator<Item = impl Into<Arg>>,
482    ) -> String {
483        if field.is_empty() || op.is_empty() {
484            return String::new();
485        }
486        let values: Vec<Arg> = values.into_iter().map(|v| v.into()).collect();
487        if values.is_empty() {
488            return "0 = 1".to_string();
489        }
490        let field = field.to_string();
491        let op = op.to_string();
492        self.expr_builder(move |flavor, initial| {
493            let mut a = Args {
494                flavor,
495                ..Args::default()
496            };
497            let vals: Vec<String> = values.iter().cloned().map(|v| a.add(v)).collect();
498            let fmt = format!("{field} {op} SOME ({})", vals.join(", "));
499            a.compile_with_flavor(&fmt, flavor, initial)
500        })
501    }
502
503    pub fn is_distinct_from(&self, field: &str, value: impl Into<Arg>) -> String {
504        if field.is_empty() {
505            return String::new();
506        }
507
508        let field = field.to_string();
509        let value: Arg = value.into();
510
511        let b = CondDynBuilder::new(move |flavor, initial| {
512            let mut a = Args {
513                flavor,
514                ..Args::default()
515            };
516            let fmt = match flavor {
517                Flavor::PostgreSQL | Flavor::SQLite | Flavor::SQLServer => {
518                    let v = a.add(value.clone());
519                    format!("{field} IS DISTINCT FROM {v}")
520                }
521                Flavor::MySQL => {
522                    let v = a.add(value.clone());
523                    format!("NOT {field} <=> {v}")
524                }
525                _ => {
526                    // CASE
527                    //     WHEN field IS NULL AND value IS NULL THEN 0
528                    //     WHEN field IS NOT NULL AND value IS NOT NULL AND field = value THEN 0
529                    //     ELSE 1
530                    // END = 1
531                    let v1 = a.add(value.clone());
532                    let v2 = a.add(value.clone());
533                    let v3 = a.add(value.clone());
534                    format!(
535                        "CASE WHEN {field} IS NULL AND {v1} IS NULL THEN 0 WHEN {field} IS NOT NULL AND {v2} IS NOT NULL AND {field} = {v3} THEN 0 ELSE 1 END = 1"
536                    )
537                }
538            };
539            a.compile_with_flavor(&fmt, flavor, initial)
540        });
541        self.var(Arg::Builder(Box::new(b)))
542    }
543
544    pub fn is_not_distinct_from(&self, field: &str, value: impl Into<Arg>) -> String {
545        if field.is_empty() {
546            return String::new();
547        }
548
549        let field = field.to_string();
550        let value: Arg = value.into();
551
552        let b = CondDynBuilder::new(move |flavor, initial| {
553            let mut a = Args {
554                flavor,
555                ..Args::default()
556            };
557            let fmt = match flavor {
558                Flavor::PostgreSQL | Flavor::SQLite | Flavor::SQLServer => {
559                    let v = a.add(value.clone());
560                    format!("{field} IS NOT DISTINCT FROM {v}")
561                }
562                Flavor::MySQL => {
563                    let v = a.add(value.clone());
564                    format!("{field} <=> {v}")
565                }
566                _ => {
567                    // CASE
568                    //     WHEN field IS NULL AND value IS NULL THEN 1
569                    //     WHEN field IS NOT NULL AND value IS NOT NULL AND field = value THEN 1
570                    //     ELSE 0
571                    // END = 1
572                    let v1 = a.add(value.clone());
573                    let v2 = a.add(value.clone());
574                    let v3 = a.add(value.clone());
575                    format!(
576                        "CASE WHEN {field} IS NULL AND {v1} IS NULL THEN 1 WHEN {field} IS NOT NULL AND {v2} IS NOT NULL AND {field} = {v3} THEN 1 ELSE 0 END = 1"
577                    )
578                }
579            };
580            a.compile_with_flavor(&fmt, flavor, initial)
581        });
582        self.var(Arg::Builder(Box::new(b)))
583    }
584}
585
586/// Internal helper for flavor-dependent conditional expressions.
587#[derive(Clone)]
588struct CondDynBuilder {
589    f: Rc<CondBuildFn>,
590}
591
592type CondBuildFn = dyn Fn(Flavor, &[Arg]) -> (String, Vec<Arg>);
593
594impl CondDynBuilder {
595    fn new(f: impl Fn(Flavor, &[Arg]) -> (String, Vec<Arg>) + 'static) -> Self {
596        Self { f: Rc::new(f) }
597    }
598}
599
600impl Default for Cond {
601    fn default() -> Self {
602        Self::new()
603    }
604}
605
606impl Builder for CondDynBuilder {
607    fn build_with_flavor(&self, flavor: Flavor, initial_arg: &[Arg]) -> (String, Vec<Arg>) {
608        (self.f)(flavor, initial_arg)
609    }
610
611    fn flavor(&self) -> Flavor {
612        Flavor::default()
613    }
614}