vortex_expr/
like.rs

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}