vortex_expr/
like.rs

1use std::any::Any;
2use std::fmt::Display;
3use std::hash::Hash;
4use std::sync::Arc;
5
6use vortex_array::ArrayRef;
7use vortex_array::compute::{LikeOptions, like};
8use vortex_dtype::DType;
9use vortex_error::VortexResult;
10
11use crate::{AnalysisExpr, ExprRef, Scope, ScopeDType, VortexExpr};
12
13#[derive(Debug, Eq, Hash)]
14#[allow(clippy::derived_hash_with_manual_eq)]
15pub struct Like {
16    child: ExprRef,
17    pattern: ExprRef,
18    negated: bool,
19    case_insensitive: bool,
20}
21
22impl Like {
23    pub fn new_expr(
24        child: ExprRef,
25        pattern: ExprRef,
26        negated: bool,
27        case_insensitive: bool,
28    ) -> ExprRef {
29        Arc::new(Self {
30            child,
31            pattern,
32            negated,
33            case_insensitive,
34        })
35    }
36
37    pub fn child(&self) -> &ExprRef {
38        &self.child
39    }
40
41    pub fn pattern(&self) -> &ExprRef {
42        &self.pattern
43    }
44
45    pub fn negated(&self) -> bool {
46        self.negated
47    }
48
49    pub fn case_insensitive(&self) -> bool {
50        self.case_insensitive
51    }
52}
53
54impl Display for Like {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        write!(f, "{} LIKE {}", self.child(), self.pattern())
57    }
58}
59
60#[cfg(feature = "proto")]
61pub(crate) mod proto {
62    use vortex_error::{VortexResult, vortex_bail};
63    use vortex_proto::expr::kind;
64    use vortex_proto::expr::kind::Kind;
65
66    use crate::{ExprDeserialize, ExprRef, ExprSerializable, Id, Like};
67
68    pub(crate) struct LikeSerde;
69
70    impl Id for LikeSerde {
71        fn id(&self) -> &'static str {
72            "like"
73        }
74    }
75
76    impl ExprSerializable for Like {
77        fn id(&self) -> &'static str {
78            LikeSerde.id()
79        }
80
81        fn serialize_kind(&self) -> VortexResult<Kind> {
82            Ok(Kind::Like(kind::Like {
83                negated: self.negated,
84                case_insensitive: self.case_insensitive,
85            }))
86        }
87    }
88
89    impl ExprDeserialize for LikeSerde {
90        fn deserialize(&self, kind: &Kind, children: Vec<ExprRef>) -> VortexResult<ExprRef> {
91            let Kind::Like(like) = kind else {
92                vortex_bail!("wrong kind {:?}, want like", kind)
93            };
94
95            Ok(Like::new_expr(
96                children[0].clone(),
97                children[1].clone(),
98                like.negated,
99                like.case_insensitive,
100            ))
101        }
102    }
103}
104
105impl AnalysisExpr for Like {}
106
107impl VortexExpr for Like {
108    fn as_any(&self) -> &dyn Any {
109        self
110    }
111
112    fn unchecked_evaluate(&self, scope: &Scope) -> VortexResult<ArrayRef> {
113        let child = self.child().unchecked_evaluate(scope)?;
114        let pattern = self.pattern().unchecked_evaluate(scope)?;
115        like(
116            &child,
117            &pattern,
118            LikeOptions {
119                negated: self.negated,
120                case_insensitive: self.case_insensitive,
121            },
122        )
123    }
124
125    fn children(&self) -> Vec<&ExprRef> {
126        vec![&self.child, &self.pattern]
127    }
128
129    fn replacing_children(self: Arc<Self>, children: Vec<ExprRef>) -> ExprRef {
130        assert_eq!(children.len(), 2);
131        Like::new_expr(
132            children[0].clone(),
133            children[1].clone(),
134            self.negated,
135            self.case_insensitive,
136        )
137    }
138
139    fn return_dtype(&self, ctx: &ScopeDType) -> VortexResult<DType> {
140        let input = self.child().return_dtype(ctx)?;
141        let pattern = self.pattern().return_dtype(ctx)?;
142        Ok(DType::Bool(
143            (input.is_nullable() || pattern.is_nullable()).into(),
144        ))
145    }
146}
147
148impl PartialEq for Like {
149    fn eq(&self, other: &Like) -> bool {
150        other.case_insensitive == self.case_insensitive
151            && other.negated == self.negated
152            && other.pattern.eq(&self.pattern)
153            && other.child.eq(&self.child)
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use vortex_array::ToCanonical;
160    use vortex_array::arrays::BoolArray;
161    use vortex_dtype::{DType, Nullability};
162
163    use crate::{Like, Scope, ScopeDType, lit, not, root};
164
165    #[test]
166    fn invert_booleans() {
167        let not_expr = not(root());
168        let bools = BoolArray::from_iter([false, true, false, false, true, true]);
169        assert_eq!(
170            not_expr
171                .evaluate(&Scope::new(bools.to_array()))
172                .unwrap()
173                .to_bool()
174                .unwrap()
175                .boolean_buffer()
176                .iter()
177                .collect::<Vec<_>>(),
178            vec![true, false, true, true, false, false]
179        );
180    }
181
182    #[test]
183    fn dtype() {
184        let dtype = DType::Utf8(Nullability::NonNullable);
185        let like_expr = Like::new_expr(root(), lit("%test%"), false, false);
186        assert_eq!(
187            like_expr.return_dtype(&ScopeDType::new(dtype)).unwrap(),
188            DType::Bool(Nullability::NonNullable)
189        );
190    }
191}