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