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}