• 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

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

4
use std::fmt::Display;
5
use std::ops::Not;
6

7
use vortex_array::arrays::{BoolArray, ConstantArray};
8
use vortex_array::{Array, ArrayRef, DeserializeMetadata, EmptyMetadata, IntoArray};
9
use vortex_dtype::{DType, Nullability};
10
use vortex_error::{VortexResult, vortex_bail};
11
use vortex_mask::Mask;
12

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

17
vtable!(IsNull);
18

19
#[allow(clippy::derived_hash_with_manual_eq)]
20
#[derive(Clone, Debug, Hash)]
21
pub struct IsNullExpr {
22
    child: ExprRef,
23
}
24

25
impl PartialEq for IsNullExpr {
UNCOV
26
    fn eq(&self, other: &Self) -> bool {
×
27
        self.child.eq(&other.child)
×
28
    }
×
29
}
30

31
pub struct IsNullExprEncoding;
32

33
impl VTable for IsNullVTable {
34
    type Expr = IsNullExpr;
35
    type Encoding = IsNullExprEncoding;
36
    type Metadata = EmptyMetadata;
37

NEW
38
    fn id(_encoding: &Self::Encoding) -> ExprId {
×
NEW
39
        ExprId::new_ref("is_null")
×
UNCOV
40
    }
×
41

NEW
42
    fn encoding(_expr: &Self::Expr) -> ExprEncodingRef {
×
NEW
43
        ExprEncodingRef::new_ref(IsNullExprEncoding.as_ref())
×
NEW
44
    }
×
45

NEW
46
    fn metadata(_expr: &Self::Expr) -> Option<Self::Metadata> {
×
NEW
47
        Some(EmptyMetadata)
×
UNCOV
48
    }
×
49

NEW
50
    fn children(expr: &Self::Expr) -> Vec<&ExprRef> {
×
NEW
51
        vec![&expr.child]
×
NEW
52
    }
×
53

54
    fn with_children(_expr: &Self::Expr, children: Vec<ExprRef>) -> VortexResult<Self::Expr> {
1✔
55
        if children.len() != 1 {
1✔
NEW
56
            vortex_bail!("IsNull expects exactly one child, got {}", children.len());
×
57
        }
1✔
58
        Ok(IsNullExpr::new(children[0].clone()))
1✔
59
    }
1✔
60

NEW
61
    fn build(
×
NEW
62
        _encoding: &Self::Encoding,
×
NEW
63
        _metadata: &<Self::Metadata as DeserializeMetadata>::Output,
×
NEW
64
        children: Vec<ExprRef>,
×
NEW
65
    ) -> VortexResult<Self::Expr> {
×
NEW
66
        if children.len() != 1 {
×
NEW
67
            vortex_bail!("IsNull expects exactly one child, got {}", children.len());
×
NEW
68
        }
×
NEW
69
        Ok(IsNullExpr::new(children[0].clone()))
×
UNCOV
70
    }
×
71

72
    fn evaluate(expr: &Self::Expr, scope: &Scope) -> VortexResult<ArrayRef> {
25✔
73
        let array = expr.child.unchecked_evaluate(scope)?;
25✔
74
        match array.validity_mask()? {
25✔
75
            Mask::AllTrue(len) => Ok(ConstantArray::new(false, len).into_array()),
1✔
76
            Mask::AllFalse(len) => Ok(ConstantArray::new(true, len).into_array()),
1✔
77
            Mask::Values(mask) => Ok(BoolArray::from(mask.boolean_buffer().not()).into_array()),
23✔
78
        }
79
    }
25✔
80

81
    fn return_dtype(_expr: &Self::Expr, _scope: &ScopeDType) -> VortexResult<DType> {
26✔
82
        Ok(DType::Bool(Nullability::NonNullable))
26✔
83
    }
26✔
84
}
85

86
impl IsNullExpr {
87
    pub fn new(child: ExprRef) -> Self {
28✔
88
        Self { child }
28✔
89
    }
28✔
90
}
91

92
impl Display for IsNullExpr {
NEW
93
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
NEW
94
        write!(f, "is_null({})", self.child)
×
UNCOV
95
    }
×
96
}
97

98
impl AnalysisExpr for IsNullExpr {}
99

100
pub fn is_null(child: ExprRef) -> ExprRef {
27✔
101
    IsNullExpr::new(child).into_expr()
27✔
102
}
27✔
103

104
#[cfg(test)]
105
mod tests {
106
    use vortex_array::IntoArray;
107
    use vortex_array::arrays::{PrimitiveArray, StructArray};
108
    use vortex_dtype::{DType, Nullability};
109
    use vortex_scalar::Scalar;
110

111
    use crate::is_null::is_null;
112
    use crate::{Scope, ScopeDType, get_item, root, test_harness};
113

114
    #[test]
115
    fn dtype() {
1✔
116
        let dtype = test_harness::struct_dtype();
1✔
117
        assert_eq!(
1✔
118
            is_null(root())
1✔
119
                .return_dtype(&ScopeDType::new(dtype))
1✔
120
                .unwrap(),
1✔
121
            DType::Bool(Nullability::NonNullable)
1✔
122
        );
1✔
123
    }
1✔
124

125
    #[test]
126
    fn replace_children() {
1✔
127
        let expr = is_null(root());
1✔
128
        let _ = expr.with_children(vec![root()]);
1✔
129
    }
1✔
130

131
    #[test]
132
    fn evaluate_mask() {
1✔
133
        let test_array =
1✔
134
            PrimitiveArray::from_option_iter(vec![Some(1), None, Some(2), None, Some(3)])
1✔
135
                .into_array();
1✔
136
        let expected = [false, true, false, true, false];
1✔
137

1✔
138
        let result = is_null(root())
1✔
139
            .evaluate(&Scope::new(test_array.clone()))
1✔
140
            .unwrap();
1✔
141

1✔
142
        assert_eq!(result.len(), test_array.len());
1✔
143
        assert_eq!(result.dtype(), &DType::Bool(Nullability::NonNullable));
1✔
144

145
        for (i, expected_value) in expected.iter().enumerate() {
5✔
146
            assert_eq!(
5✔
147
                result.scalar_at(i).unwrap(),
5✔
148
                Scalar::bool(*expected_value, Nullability::NonNullable)
5✔
149
            );
5✔
150
        }
151
    }
1✔
152

153
    #[test]
154
    fn evaluate_all_false() {
1✔
155
        let test_array = PrimitiveArray::from_iter(vec![1, 2, 3, 4, 5]).into_array();
1✔
156

1✔
157
        let result = is_null(root())
1✔
158
            .evaluate(&Scope::new(test_array.clone()))
1✔
159
            .unwrap();
1✔
160

1✔
161
        assert_eq!(result.len(), test_array.len());
1✔
162
        assert_eq!(
1✔
163
            result.as_constant().unwrap(),
1✔
164
            Scalar::bool(false, Nullability::NonNullable)
1✔
165
        );
1✔
166
    }
1✔
167

168
    #[test]
169
    fn evaluate_all_true() {
1✔
170
        let test_array =
1✔
171
            PrimitiveArray::from_option_iter(vec![None::<i32>, None, None, None, None])
1✔
172
                .into_array();
1✔
173

1✔
174
        let result = is_null(root())
1✔
175
            .evaluate(&Scope::new(test_array.clone()))
1✔
176
            .unwrap();
1✔
177

1✔
178
        assert_eq!(result.len(), test_array.len());
1✔
179
        assert_eq!(
1✔
180
            result.as_constant().unwrap(),
1✔
181
            Scalar::bool(true, Nullability::NonNullable)
1✔
182
        );
1✔
183
    }
1✔
184

185
    #[test]
186
    fn evaluate_struct() {
1✔
187
        let test_array = StructArray::from_fields(&[(
1✔
188
            "a",
1✔
189
            PrimitiveArray::from_option_iter(vec![Some(1), None, Some(2), None, Some(3)])
1✔
190
                .into_array(),
1✔
191
        )])
1✔
192
        .unwrap()
1✔
193
        .into_array();
1✔
194
        let expected = [false, true, false, true, false];
1✔
195

1✔
196
        let result = is_null(get_item("a", root()))
1✔
197
            .evaluate(&Scope::new(test_array.clone()))
1✔
198
            .unwrap();
1✔
199

1✔
200
        assert_eq!(result.len(), test_array.len());
1✔
201
        assert_eq!(result.dtype(), &DType::Bool(Nullability::NonNullable));
1✔
202

203
        for (i, expected_value) in expected.iter().enumerate() {
5✔
204
            assert_eq!(
5✔
205
                result.scalar_at(i).unwrap(),
5✔
206
                Scalar::bool(*expected_value, Nullability::NonNullable)
5✔
207
            );
5✔
208
        }
209
    }
1✔
210
}
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