1use std::any::Any;
2use std::fmt::Display;
3use std::hash::Hash;
4use std::sync::Arc;
5
6use vortex_array::compute::{LikeOptions, like};
7use vortex_array::{Array, ArrayRef};
8use vortex_dtype::DType;
9use vortex_error::VortexResult;
10
11use crate::{ExprRef, 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")]
61mod proto {
62 use vortex_error::{VortexResult, vortex_bail};
63 use vortex_proto::expr::kind::Kind;
64
65 use crate::{ExprSerializable, Like};
66
67 impl ExprSerializable for Like {
68 fn id(&self) -> &'static str {
69 "like"
70 }
71
72 fn serialize_kind(&self) -> VortexResult<Kind> {
73 vortex_bail!(NotImplemented: "", self.id())
74 }
75 }
76}
77
78impl VortexExpr for Like {
79 fn as_any(&self) -> &dyn Any {
80 self
81 }
82
83 fn unchecked_evaluate(&self, batch: &dyn Array) -> VortexResult<ArrayRef> {
84 let child = self.child().evaluate(batch)?;
85 let pattern = self.pattern().evaluate(&child)?;
86 like(
87 &child,
88 &pattern,
89 LikeOptions {
90 negated: self.negated,
91 case_insensitive: self.case_insensitive,
92 },
93 )
94 }
95
96 fn children(&self) -> Vec<&ExprRef> {
97 vec![&self.child, &self.pattern]
98 }
99
100 fn replacing_children(self: Arc<Self>, children: Vec<ExprRef>) -> ExprRef {
101 assert_eq!(children.len(), 2);
102 Like::new_expr(
103 children[0].clone(),
104 children[1].clone(),
105 self.negated,
106 self.case_insensitive,
107 )
108 }
109
110 fn return_dtype(&self, scope_dtype: &DType) -> VortexResult<DType> {
111 let input = self.child().return_dtype(scope_dtype)?;
112 let pattern = self.pattern().return_dtype(scope_dtype)?;
113 Ok(DType::Bool(
114 (input.is_nullable() || pattern.is_nullable()).into(),
115 ))
116 }
117}
118
119impl PartialEq for Like {
120 fn eq(&self, other: &Like) -> bool {
121 other.case_insensitive == self.case_insensitive
122 && other.negated == self.negated
123 && other.pattern.eq(&self.pattern)
124 && other.child.eq(&self.child)
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use vortex_array::ToCanonical;
131 use vortex_array::arrays::BoolArray;
132 use vortex_dtype::{DType, Nullability};
133
134 use crate::{Like, ident, lit, not};
135
136 #[test]
137 fn invert_booleans() {
138 let not_expr = not(ident());
139 let bools = BoolArray::from_iter([false, true, false, false, true, true]);
140 assert_eq!(
141 not_expr
142 .evaluate(&bools)
143 .unwrap()
144 .to_bool()
145 .unwrap()
146 .boolean_buffer()
147 .iter()
148 .collect::<Vec<_>>(),
149 vec![true, false, true, true, false, false]
150 );
151 }
152
153 #[test]
154 fn dtype() {
155 let dtype = DType::Utf8(Nullability::NonNullable);
156 let like_expr = Like::new_expr(ident(), lit("%test%"), false, false);
157 assert_eq!(
158 like_expr.return_dtype(&dtype).unwrap(),
159 DType::Bool(Nullability::NonNullable)
160 );
161 }
162}