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

vortex-data / vortex / 16204612549

10 Jul 2025 07:50PM UTC coverage: 81.152% (+2.9%) from 78.263%
16204612549

Pull #3825

github

web-flow
Merge d0d2717da into be9c2fd3e
Pull Request #3825: feat: Add optimize ArrayOp with VBView implementation

178 of 211 new or added lines in 4 files covered. (84.36%)

330 existing lines in 34 files now uncovered.

45433 of 55985 relevant lines covered (81.15%)

145951.87 hits per line

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

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

4
use std::fmt::Display;
5

6
use itertools::Itertools;
7
use vortex_array::{ArrayRef, DeserializeMetadata, EmptyMetadata, IntoArray, ToCanonical};
8
use vortex_dtype::{DType, FieldNames};
9
use vortex_error::{VortexResult, vortex_bail, vortex_err};
10

11
use crate::field::DisplayFieldNames;
12
use crate::{AnalysisExpr, ExprEncodingRef, ExprId, ExprRef, IntoExpr, Scope, VTable, vtable};
13

14
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15
pub enum SelectField {
16
    Include(FieldNames),
17
    Exclude(FieldNames),
18
}
19

20
vtable!(Select);
21

22
#[derive(Debug, Clone, Hash)]
23
#[allow(clippy::derived_hash_with_manual_eq)]
24
pub struct SelectExpr {
25
    fields: SelectField,
26
    child: ExprRef,
27
}
28

29
impl PartialEq for SelectExpr {
UNCOV
30
    fn eq(&self, other: &Self) -> bool {
×
UNCOV
31
        self.fields == other.fields && self.child.eq(&other.child)
×
32
    }
×
33
}
34

35
pub struct SelectExprEncoding;
36

37
impl VTable for SelectVTable {
38
    type Expr = SelectExpr;
39
    type Encoding = SelectExprEncoding;
40
    type Metadata = EmptyMetadata;
41

42
    fn id(_encoding: &Self::Encoding) -> ExprId {
123✔
43
        ExprId::new_ref("select")
123✔
44
    }
123✔
45

UNCOV
46
    fn encoding(_expr: &Self::Expr) -> ExprEncodingRef {
×
UNCOV
47
        ExprEncodingRef::new_ref(SelectExprEncoding.as_ref())
×
48
    }
×
49

50
    fn metadata(_expr: &Self::Expr) -> Option<Self::Metadata> {
×
UNCOV
51
        // Select does not support serialization
×
52
        None
×
53
    }
×
54

55
    fn children(expr: &Self::Expr) -> Vec<&ExprRef> {
5,972✔
56
        vec![&expr.child]
5,972✔
57
    }
5,972✔
58

UNCOV
59
    fn with_children(expr: &Self::Expr, children: Vec<ExprRef>) -> VortexResult<Self::Expr> {
×
UNCOV
60
        Ok(SelectExpr {
×
61
            fields: expr.fields.clone(),
×
62
            child: children[0].clone(),
×
63
        })
×
64
    }
×
65

66
    fn build(
×
UNCOV
67
        _encoding: &Self::Encoding,
×
68
        _metadata: &<Self::Metadata as DeserializeMetadata>::Output,
×
69
        _children: Vec<ExprRef>,
×
70
    ) -> VortexResult<Self::Expr> {
×
71
        vortex_bail!("Select does not support deserialization")
×
72
    }
×
73

74
    fn evaluate(expr: &Self::Expr, scope: &Scope) -> VortexResult<ArrayRef> {
2✔
75
        let batch = expr.child.unchecked_evaluate(scope)?.to_struct()?;
2✔
76
        Ok(match &expr.fields {
2✔
77
            SelectField::Include(f) => batch.project(f.as_ref()),
1✔
78
            SelectField::Exclude(names) => {
1✔
79
                let included_names = batch
1✔
80
                    .names()
1✔
81
                    .iter()
1✔
82
                    .filter(|&f| !names.as_ref().contains(f))
2✔
83
                    .cloned()
1✔
84
                    .collect::<Vec<_>>();
1✔
85
                batch.project(included_names.as_slice())
1✔
86
            }
UNCOV
87
        }?
×
88
        .into_array())
2✔
89
    }
2✔
90

91
    fn return_dtype(expr: &Self::Expr, scope: &DType) -> VortexResult<DType> {
999✔
92
        let child_dtype = expr.child.return_dtype(scope)?;
999✔
93
        let child_struct_dtype = child_dtype
999✔
94
            .as_struct()
999✔
95
            .ok_or_else(|| vortex_err!("Select child not a struct dtype"))?;
999✔
96

97
        let projected = match &expr.fields {
999✔
98
            SelectField::Include(fields) => child_struct_dtype.project(fields.as_ref())?,
996✔
99
            SelectField::Exclude(fields) => child_struct_dtype
3✔
100
                .names()
3✔
101
                .iter()
3✔
102
                .cloned()
3✔
103
                .zip_eq(child_struct_dtype.fields())
3✔
104
                .filter(|(name, _)| !fields.as_ref().contains(name))
12✔
105
                .collect(),
3✔
106
        };
107

108
        Ok(DType::Struct(projected, child_dtype.nullability()))
999✔
109
    }
999✔
110
}
111

112
pub fn select(fields: impl Into<FieldNames>, child: ExprRef) -> ExprRef {
676✔
113
    SelectExpr::include_expr(fields.into(), child)
676✔
114
}
676✔
115

116
pub fn select_exclude(fields: impl Into<FieldNames>, child: ExprRef) -> ExprRef {
4✔
117
    SelectExpr::exclude_expr(fields.into(), child)
4✔
118
}
4✔
119

120
impl SelectExpr {
121
    pub fn new(fields: SelectField, child: ExprRef) -> Self {
984✔
122
        Self { fields, child }
984✔
123
    }
984✔
124

125
    pub fn include_expr(columns: FieldNames, child: ExprRef) -> ExprRef {
980✔
126
        Self::new(SelectField::Include(columns), child).into_expr()
980✔
127
    }
980✔
128

129
    pub fn exclude_expr(columns: FieldNames, child: ExprRef) -> ExprRef {
4✔
130
        Self::new(SelectField::Exclude(columns), child).into_expr()
4✔
131
    }
4✔
132

133
    pub fn fields(&self) -> &SelectField {
996✔
134
        &self.fields
996✔
135
    }
996✔
136

137
    pub fn child(&self) -> &ExprRef {
996✔
138
        &self.child
996✔
139
    }
996✔
140

UNCOV
141
    pub fn as_include(&self, field_names: &FieldNames) -> VortexResult<ExprRef> {
×
UNCOV
142
        Ok(Self::new(
×
143
            SelectField::Include(self.fields.as_include_names(field_names)?),
×
144
            self.child.clone(),
×
145
        )
×
146
        .into_expr())
×
147
    }
×
148
}
149

150
impl SelectField {
UNCOV
151
    pub fn include(columns: FieldNames) -> Self {
×
UNCOV
152
        assert_eq!(columns.iter().unique().collect_vec().len(), columns.len());
×
153
        Self::Include(columns)
×
154
    }
×
155

156
    pub fn exclude(columns: FieldNames) -> Self {
×
UNCOV
157
        assert_eq!(columns.iter().unique().collect_vec().len(), columns.len());
×
158
        Self::Exclude(columns)
×
159
    }
×
160

161
    pub fn is_include(&self) -> bool {
×
UNCOV
162
        matches!(self, Self::Include(_))
×
163
    }
×
164

165
    pub fn is_exclude(&self) -> bool {
×
UNCOV
166
        matches!(self, Self::Exclude(_))
×
167
    }
×
168

169
    pub fn fields(&self) -> &FieldNames {
996✔
170
        match self {
996✔
171
            SelectField::Include(fields) => fields,
996✔
UNCOV
172
            SelectField::Exclude(fields) => fields,
×
173
        }
174
    }
996✔
175

176
    pub fn as_include_names(&self, field_names: &FieldNames) -> VortexResult<FieldNames> {
996✔
177
        if self
996✔
178
            .fields()
996✔
179
            .iter()
996✔
180
            .any(|f| !field_names.iter().contains(f))
2,230✔
181
        {
UNCOV
182
            vortex_bail!(
×
UNCOV
183
                "Field {:?} in select not in field names {:?}",
×
184
                self,
×
185
                field_names
×
186
            );
×
187
        }
996✔
188
        match self {
996✔
189
            SelectField::Include(fields) => Ok(fields.clone()),
996✔
UNCOV
190
            SelectField::Exclude(exc_fields) => Ok(field_names
×
UNCOV
191
                .iter()
×
192
                .filter(|f| exc_fields.iter().contains(f))
×
193
                .cloned()
×
194
                .collect()),
×
195
        }
196
    }
996✔
197
}
198

199
impl Display for SelectField {
200
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3✔
201
        match self {
3✔
202
            SelectField::Include(fields) => write!(f, "{{{}}}", DisplayFieldNames(fields)),
2✔
203
            SelectField::Exclude(fields) => write!(f, "~{{{}}}", DisplayFieldNames(fields)),
1✔
204
        }
205
    }
3✔
206
}
207

208
impl Display for SelectExpr {
209
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3✔
210
        write!(f, "{}{}", self.child, self.fields)
3✔
211
    }
3✔
212
}
213

214
impl AnalysisExpr for SelectExpr {}
215

216
#[cfg(test)]
217
mod tests {
218

219
    use vortex_array::arrays::StructArray;
220
    use vortex_array::{IntoArray, ToCanonical};
221
    use vortex_buffer::buffer;
222
    use vortex_dtype::{DType, FieldName, Nullability};
223

224
    use crate::{Scope, root, select, select_exclude, test_harness};
225

226
    fn test_array() -> StructArray {
2✔
227
        StructArray::from_fields(&[
2✔
228
            ("a", buffer![0, 1, 2].into_array()),
2✔
229
            ("b", buffer![4, 5, 6].into_array()),
2✔
230
        ])
2✔
231
        .unwrap()
2✔
232
    }
2✔
233

234
    #[test]
235
    pub fn include_columns() {
1✔
236
        let st = test_array();
1✔
237
        let select = select(vec![FieldName::from("a")], root());
1✔
238
        let selected = select
1✔
239
            .evaluate(&Scope::new(st.to_array()))
1✔
240
            .unwrap()
1✔
241
            .to_struct()
1✔
242
            .unwrap();
1✔
243
        let selected_names = selected.names().clone();
1✔
244
        assert_eq!(selected_names.as_ref(), &["a".into()]);
1✔
245
    }
1✔
246

247
    #[test]
248
    pub fn exclude_columns() {
1✔
249
        let st = test_array();
1✔
250
        let select = select_exclude(vec![FieldName::from("a")], root());
1✔
251
        let selected = select
1✔
252
            .evaluate(&Scope::new(st.to_array()))
1✔
253
            .unwrap()
1✔
254
            .to_struct()
1✔
255
            .unwrap();
1✔
256
        let selected_names = selected.names().clone();
1✔
257
        assert_eq!(selected_names.as_ref(), &["b".into()]);
1✔
258
    }
1✔
259

260
    #[test]
261
    fn dtype() {
1✔
262
        let dtype = test_harness::struct_dtype();
1✔
263

1✔
264
        let select_expr = select(vec![FieldName::from("a")], root());
1✔
265
        let expected_dtype = DType::Struct(
1✔
266
            dtype.as_struct().unwrap().project(&["a".into()]).unwrap(),
1✔
267
            Nullability::NonNullable,
1✔
268
        );
1✔
269
        assert_eq!(select_expr.return_dtype(&dtype).unwrap(), expected_dtype);
1✔
270

271
        let select_expr_exclude = select_exclude(
1✔
272
            vec![
1✔
273
                FieldName::from("col1"),
1✔
274
                FieldName::from("col2"),
1✔
275
                FieldName::from("bool1"),
1✔
276
                FieldName::from("bool2"),
1✔
277
            ],
1✔
278
            root(),
1✔
279
        );
1✔
280
        assert_eq!(
1✔
281
            select_expr_exclude.return_dtype(&dtype).unwrap(),
1✔
282
            expected_dtype
1✔
283
        );
1✔
284

285
        let select_expr_exclude = select_exclude(
1✔
286
            vec![FieldName::from("col1"), FieldName::from("col2")],
1✔
287
            root(),
1✔
288
        );
1✔
289
        assert_eq!(
1✔
290
            select_expr_exclude.return_dtype(&dtype).unwrap(),
1✔
291
            DType::Struct(
1✔
292
                dtype
1✔
293
                    .as_struct()
1✔
294
                    .unwrap()
1✔
295
                    .project(&["a".into(), "bool1".into(), "bool2".into()])
1✔
296
                    .unwrap(),
1✔
297
                Nullability::NonNullable
1✔
298
            )
1✔
299
        );
1✔
300
    }
1✔
301
}
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