Skip to main content

qraft_core/expression/
like.rs

1//! Pattern-matching expressions and SQLite glob preparation.
2
3use std::borrow::Cow;
4
5use super::Operator;
6use crate::{
7    Bool, Likeable, LowerCompatible,
8    expression::Expression,
9    lower::{Instructions, LowerCtx},
10};
11
12/// A `like` predicate with optional negation and case-sensitivity.
13#[derive(Debug)]
14pub struct Like<L, R> {
15    pub case_sensitive: bool,
16    pub negated: bool,
17    pub left: L,
18    pub right: R,
19}
20
21/// Converts SQL `%` and `_` wildcards into the form expected by SQLite `glob`.
22pub fn prepare_sqlite_glob<'a>(value: &'a str) -> Cow<'a, str> {
23    let mut first_special = None;
24    for (i, ch) in value.char_indices() {
25        match ch {
26            '*' | '?' | '%' | '_' => {
27                first_special = Some(i);
28                break;
29            }
30            _ => {}
31        }
32    }
33
34    let Some(idx) = first_special else {
35        return Cow::Borrowed(value);
36    };
37
38    let mut out = String::with_capacity(value.len());
39    out.push_str(&value[..idx]);
40
41    for ch in value[idx..].chars() {
42        match ch {
43            '*' => {
44                out.push('[');
45                out.push('*');
46                out.push(']');
47            }
48            '?' => {
49                out.push('[');
50                out.push('?');
51                out.push(']');
52            }
53            '%' => out.push('*'),
54            '_' => out.push('?'),
55            _ => out.push(ch),
56        }
57    }
58
59    Cow::Owned(out)
60}
61
62#[qraft_expression_macro::as_expression]
63impl<L, R, T> Expression for Like<L, R>
64where
65    T: Likeable,
66    L: Expression<Type = T>,
67    R: LowerCompatible<T>,
68{
69    type Type = Bool;
70
71    fn lower(&self, ctx: &mut LowerCtx) -> usize {
72        let lhs = self.left.lower(ctx);
73        let rhs = self.right.lower_compatible(ctx);
74        ctx.instrs.push_binary(
75            Operator::Like {
76                sensitive: self.case_sensitive,
77                negated: self.negated,
78            },
79            lhs,
80            rhs,
81        );
82        lhs + rhs + 1
83    }
84}
85
86impl<L, R> Like<L, R> {
87    /// Requests the case-sensitive variant where the dialect supports it.
88    pub fn case_sensitive(mut self) -> Self {
89        self.case_sensitive = true;
90        self
91    }
92}
93
94/// Adds `like` and `not like` helpers to string-like expressions.
95pub trait LikeExt<T: Likeable>: Sized + Expression {
96    fn like<E>(self, other: E) -> Like<Self, E>
97    where
98        E: LowerCompatible<T>,
99    {
100        Like {
101            left: self,
102            right: other,
103            case_sensitive: false,
104            negated: false,
105        }
106    }
107
108    fn not_like<E>(self, other: E) -> Like<Self, E>
109    where
110        E: LowerCompatible<T>,
111    {
112        Like {
113            left: self,
114            right: other,
115            case_sensitive: false,
116            negated: true,
117        }
118    }
119}
120
121impl<T: Likeable, V> LikeExt<T> for V where V: Expression<Type = T> {}