• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vortex-data / vortex / 16139349253

08 Jul 2025 09:26AM UTC coverage: 78.057% (-0.2%) from 78.253%
16139349253

push

github

web-flow
VortexExpr VTables (#3713)

Adds the same vtable machinery as arrays and layouts already use. It
uses the "Encoding" naming scheme from arrays and layouts. I don't
particularly like it, but it's consistent. Open to renames later.

Further, adds an expression registry to the Vortex session that will be
used for deserialization.

Expressions only decide their "options" serialization. So in theory, can
support many container formats, not just proto, provided each expression
can deserialize their own options format.

---------

Signed-off-by: Nicholas Gates <nick@nickgates.com>

800 of 1190 new or added lines in 38 files covered. (67.23%)

40 existing lines in 13 files now uncovered.

44100 of 56497 relevant lines covered (78.06%)

54989.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.2
/vortex-expr/src/exprs/like.rs
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3

4
use std::fmt::Display;
5
use std::hash::Hash;
6

7
use vortex_array::compute::{LikeOptions, like};
8
use vortex_array::{ArrayRef, DeserializeMetadata, ProstMetadata};
9
use vortex_dtype::DType;
10
use vortex_error::{VortexResult, vortex_bail};
11
use vortex_proto::expr as pb;
12

13
use crate::{
14
    AnalysisExpr, ExprEncodingRef, ExprId, ExprRef, IntoExpr, Scope, ScopeDType, VTable, vtable,
15
};
16

17
vtable!(Like);
18

19
#[allow(clippy::derived_hash_with_manual_eq)]
20
#[derive(Clone, Debug, Hash)]
21
pub struct LikeExpr {
22
    child: ExprRef,
23
    pattern: ExprRef,
24
    negated: bool,
25
    case_insensitive: bool,
26
}
27

28
impl PartialEq for LikeExpr {
29
    fn eq(&self, other: &Self) -> bool {
126✔
30
        self.child.eq(&other.child)
126✔
31
            && self.pattern.eq(&other.pattern)
126✔
32
            && self.negated == other.negated
126✔
33
            && self.case_insensitive == other.case_insensitive
126✔
34
    }
126✔
35
}
36

37
pub struct LikeExprEncoding;
38

39
impl VTable for LikeVTable {
40
    type Expr = LikeExpr;
41
    type Encoding = LikeExprEncoding;
42
    type Metadata = ProstMetadata<pb::LikeOpts>;
43

44
    fn id(_encoding: &Self::Encoding) -> ExprId {
106✔
45
        ExprId::new_ref("like")
106✔
46
    }
106✔
47

NEW
48
    fn encoding(_expr: &Self::Expr) -> ExprEncodingRef {
×
NEW
49
        ExprEncodingRef::new_ref(LikeExprEncoding.as_ref())
×
UNCOV
50
    }
×
51

NEW
52
    fn metadata(expr: &Self::Expr) -> Option<Self::Metadata> {
×
NEW
53
        Some(ProstMetadata(pb::LikeOpts {
×
NEW
54
            negated: expr.negated,
×
NEW
55
            case_insensitive: expr.case_insensitive,
×
NEW
56
        }))
×
UNCOV
57
    }
×
58

59
    fn children(expr: &Self::Expr) -> Vec<&ExprRef> {
672✔
60
        vec![&expr.child, &expr.pattern]
672✔
61
    }
672✔
62

63
    fn with_children(expr: &Self::Expr, children: Vec<ExprRef>) -> VortexResult<Self::Expr> {
42✔
64
        if children.len() != 2 {
42✔
NEW
65
            vortex_bail!(
×
NEW
66
                "Like expression must have exactly 2 children, got {}",
×
NEW
67
                children.len()
×
NEW
68
            );
×
69
        }
42✔
70
        Ok(LikeExpr::new(
42✔
71
            children[0].clone(),
42✔
72
            children[1].clone(),
42✔
73
            expr.negated,
42✔
74
            expr.case_insensitive,
42✔
75
        ))
42✔
76
    }
42✔
77

NEW
78
    fn build(
×
NEW
79
        _encoding: &Self::Encoding,
×
NEW
80
        metadata: &<Self::Metadata as DeserializeMetadata>::Output,
×
NEW
81
        children: Vec<ExprRef>,
×
NEW
82
    ) -> VortexResult<Self::Expr> {
×
NEW
83
        if children.len() != 2 {
×
NEW
84
            vortex_bail!(
×
NEW
85
                "Like expression must have exactly 2 children, got {}",
×
NEW
86
                children.len()
×
NEW
87
            );
×
UNCOV
88
        }
×
UNCOV
89

×
NEW
90
        Ok(LikeExpr::new(
×
NEW
91
            children[0].clone(),
×
NEW
92
            children[1].clone(),
×
NEW
93
            metadata.negated,
×
NEW
94
            metadata.case_insensitive,
×
NEW
95
        ))
×
UNCOV
96
    }
×
97

98
    fn evaluate(expr: &Self::Expr, scope: &Scope) -> VortexResult<ArrayRef> {
21✔
99
        let child = expr.child().unchecked_evaluate(scope)?;
21✔
100
        let pattern = expr.pattern().unchecked_evaluate(scope)?;
21✔
101
        like(
21✔
102
            &child,
21✔
103
            &pattern,
21✔
104
            LikeOptions {
21✔
105
                negated: expr.negated,
21✔
106
                case_insensitive: expr.case_insensitive,
21✔
107
            },
21✔
108
        )
21✔
109
    }
21✔
110

111
    fn return_dtype(expr: &Self::Expr, scope: &ScopeDType) -> VortexResult<DType> {
43✔
112
        let input = expr.child().return_dtype(scope)?;
43✔
113
        let pattern = expr.pattern().return_dtype(scope)?;
43✔
114
        Ok(DType::Bool(
115
            (input.is_nullable() || pattern.is_nullable()).into(),
43✔
116
        ))
117
    }
43✔
118
}
119

120
impl LikeExpr {
121
    pub fn new(child: ExprRef, pattern: ExprRef, negated: bool, case_insensitive: bool) -> Self {
64✔
122
        Self {
64✔
123
            child,
64✔
124
            pattern,
64✔
125
            negated,
64✔
126
            case_insensitive,
64✔
127
        }
64✔
128
    }
64✔
129

130
    pub fn new_expr(
21✔
131
        child: ExprRef,
21✔
132
        pattern: ExprRef,
21✔
133
        negated: bool,
21✔
134
        case_insensitive: bool,
21✔
135
    ) -> ExprRef {
21✔
136
        Self::new(child, pattern, negated, case_insensitive).into_expr()
21✔
137
    }
21✔
138

139
    pub fn child(&self) -> &ExprRef {
64✔
140
        &self.child
64✔
141
    }
64✔
142

143
    pub fn pattern(&self) -> &ExprRef {
64✔
144
        &self.pattern
64✔
145
    }
64✔
146

NEW
147
    pub fn negated(&self) -> bool {
×
NEW
148
        self.negated
×
NEW
149
    }
×
150

NEW
151
    pub fn case_insensitive(&self) -> bool {
×
NEW
152
        self.case_insensitive
×
UNCOV
153
    }
×
154
}
155

156
impl Display for LikeExpr {
NEW
157
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
NEW
158
        write!(f, "{} LIKE {}", self.child(), self.pattern())
×
UNCOV
159
    }
×
160
}
161

162
impl AnalysisExpr for LikeExpr {}
163

164
#[cfg(test)]
165
mod tests {
166
    use vortex_array::ToCanonical;
167
    use vortex_array::arrays::BoolArray;
168
    use vortex_dtype::{DType, Nullability};
169

170
    use crate::{LikeExpr, Scope, ScopeDType, lit, not, root};
171

172
    #[test]
173
    fn invert_booleans() {
1✔
174
        let not_expr = not(root());
1✔
175
        let bools = BoolArray::from_iter([false, true, false, false, true, true]);
1✔
176
        assert_eq!(
1✔
177
            not_expr
1✔
178
                .evaluate(&Scope::new(bools.to_array()))
1✔
179
                .unwrap()
1✔
180
                .to_bool()
1✔
181
                .unwrap()
1✔
182
                .boolean_buffer()
1✔
183
                .iter()
1✔
184
                .collect::<Vec<_>>(),
1✔
185
            vec![true, false, true, true, false, false]
1✔
186
        );
1✔
187
    }
1✔
188

189
    #[test]
190
    fn dtype() {
1✔
191
        let dtype = DType::Utf8(Nullability::NonNullable);
1✔
192
        let like_expr = LikeExpr::new(root(), lit("%test%"), false, false);
1✔
193
        assert_eq!(
1✔
194
            like_expr.return_dtype(&ScopeDType::new(dtype)).unwrap(),
1✔
195
            DType::Bool(Nullability::NonNullable)
1✔
196
        );
1✔
197
    }
1✔
198
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc