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

vortex-data / vortex / 16653212320

31 Jul 2025 03:25PM UTC coverage: 83.027% (-0.02%) from 83.045%
16653212320

Pull #4077

github

web-flow
Merge f6a87576a into 5fc0a2318
Pull Request #4077: feat: Add serialization support for Select expression

3 of 16 new or added lines in 2 files covered. (18.75%)

2 existing lines in 2 files now uncovered.

46013 of 55419 relevant lines covered (83.03%)

452769.13 hits per line

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

69.0
/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, IntoArray, ProstMetadata, ToCanonical};
8
use vortex_dtype::{DType, FieldNames};
9
use vortex_error::{VortexResult, vortex_bail, vortex_err};
10
use vortex_proto::expr::{FieldNames as ProtoFieldNames, SelectOpts};
11

12
use vortex_proto::expr::select_opts::Opts;
13

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

17
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18
pub enum SelectField {
19
    Include(FieldNames),
20
    Exclude(FieldNames),
21
}
22

23
vtable!(Select);
24

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

32
impl PartialEq for SelectExpr {
33
    fn eq(&self, other: &Self) -> bool {
×
34
        self.fields == other.fields && self.child.eq(&other.child)
×
35
    }
×
36
}
37

38
pub struct SelectExprEncoding;
39

40
impl VTable for SelectVTable {
41
    type Expr = SelectExpr;
42
    type Encoding = SelectExprEncoding;
43
    type Metadata = ProstMetadata<SelectOpts>;
44

45
    fn id(_encoding: &Self::Encoding) -> ExprId {
128✔
46
        ExprId::new_ref("select")
128✔
47
    }
128✔
48

49
    fn encoding(_expr: &Self::Expr) -> ExprEncodingRef {
×
50
        ExprEncodingRef::new_ref(SelectExprEncoding.as_ref())
×
51
    }
×
52

NEW
53
    fn metadata(expr: &Self::Expr) -> Option<Self::Metadata> {
×
NEW
54
        let names = expr
×
NEW
55
            .fields()
×
NEW
56
            .fields()
×
NEW
57
            .iter()
×
NEW
58
            .map(|f| f.to_string())
×
NEW
59
            .collect_vec();
×
60

NEW
61
        let opts = if expr.fields().is_include() {
×
NEW
62
            Opts::Include(ProtoFieldNames { names })
×
63
        } else {
NEW
64
            Opts::Exclude(ProtoFieldNames { names })
×
65
        };
66

NEW
67
        Some(ProstMetadata(SelectOpts { opts: Some(opts) }))
×
UNCOV
68
    }
×
69

70
    fn children(expr: &Self::Expr) -> Vec<&ExprRef> {
3,037✔
71
        vec![&expr.child]
3,037✔
72
    }
3,037✔
73

74
    fn with_children(expr: &Self::Expr, children: Vec<ExprRef>) -> VortexResult<Self::Expr> {
×
75
        Ok(SelectExpr {
×
76
            fields: expr.fields.clone(),
×
77
            child: children[0].clone(),
×
78
        })
×
79
    }
×
80

81
    fn build(
×
82
        _encoding: &Self::Encoding,
×
83
        _metadata: &<Self::Metadata as DeserializeMetadata>::Output,
×
84
        _children: Vec<ExprRef>,
×
85
    ) -> VortexResult<Self::Expr> {
×
86
        vortex_bail!("Select does not support deserialization")
×
87
    }
×
88

89
    fn evaluate(expr: &Self::Expr, scope: &Scope) -> VortexResult<ArrayRef> {
2✔
90
        let batch = expr.child.unchecked_evaluate(scope)?.to_struct()?;
2✔
91
        Ok(match &expr.fields {
2✔
92
            SelectField::Include(f) => batch.project(f.as_ref()),
1✔
93
            SelectField::Exclude(names) => {
1✔
94
                let included_names = batch
1✔
95
                    .names()
1✔
96
                    .iter()
1✔
97
                    .filter(|&f| !names.as_ref().contains(f))
2✔
98
                    .cloned()
1✔
99
                    .collect::<Vec<_>>();
1✔
100
                batch.project(included_names.as_slice())
1✔
101
            }
102
        }?
×
103
        .into_array())
2✔
104
    }
2✔
105

106
    fn return_dtype(expr: &Self::Expr, scope: &DType) -> VortexResult<DType> {
68✔
107
        let child_dtype = expr.child.return_dtype(scope)?;
68✔
108
        let child_struct_dtype = child_dtype
68✔
109
            .as_struct()
68✔
110
            .ok_or_else(|| vortex_err!("Select child not a struct dtype"))?;
68✔
111

112
        let projected = match &expr.fields {
68✔
113
            SelectField::Include(fields) => child_struct_dtype.project(fields.as_ref())?,
65✔
114
            SelectField::Exclude(fields) => child_struct_dtype
3✔
115
                .names()
3✔
116
                .iter()
3✔
117
                .cloned()
3✔
118
                .zip_eq(child_struct_dtype.fields())
3✔
119
                .filter(|(name, _)| !fields.as_ref().contains(name))
12✔
120
                .collect(),
3✔
121
        };
122

123
        Ok(DType::Struct(projected, child_dtype.nullability()))
68✔
124
    }
68✔
125
}
126

127
pub fn select(fields: impl Into<FieldNames>, child: ExprRef) -> ExprRef {
676✔
128
    SelectExpr::include_expr(fields.into(), child)
676✔
129
}
676✔
130

131
pub fn select_exclude(fields: impl Into<FieldNames>, child: ExprRef) -> ExprRef {
4✔
132
    SelectExpr::exclude_expr(fields.into(), child)
4✔
133
}
4✔
134

135
impl SelectExpr {
136
    pub fn new(fields: SelectField, child: ExprRef) -> Self {
1,000✔
137
        Self { fields, child }
1,000✔
138
    }
1,000✔
139

140
    pub fn new_expr(fields: SelectField, child: ExprRef) -> ExprRef {
×
141
        Self::new(fields, child).into_expr()
×
142
    }
×
143

144
    pub fn include_expr(columns: FieldNames, child: ExprRef) -> ExprRef {
996✔
145
        Self::new(SelectField::Include(columns), child).into_expr()
996✔
146
    }
996✔
147

148
    pub fn exclude_expr(columns: FieldNames, child: ExprRef) -> ExprRef {
4✔
149
        Self::new(SelectField::Exclude(columns), child).into_expr()
4✔
150
    }
4✔
151

152
    pub fn fields(&self) -> &SelectField {
1,013✔
153
        &self.fields
1,013✔
154
    }
1,013✔
155

156
    pub fn child(&self) -> &ExprRef {
1,013✔
157
        &self.child
1,013✔
158
    }
1,013✔
159

160
    pub fn as_include(&self, field_names: &FieldNames) -> VortexResult<ExprRef> {
×
161
        Ok(Self::new(
×
162
            SelectField::Include(self.fields.as_include_names(field_names)?),
×
163
            self.child.clone(),
×
164
        )
165
        .into_expr())
×
166
    }
×
167
}
168

169
impl SelectField {
170
    pub fn include(columns: FieldNames) -> Self {
×
171
        assert_eq!(columns.iter().unique().collect_vec().len(), columns.len());
×
172
        Self::Include(columns)
×
173
    }
×
174

175
    pub fn exclude(columns: FieldNames) -> Self {
×
176
        assert_eq!(columns.iter().unique().collect_vec().len(), columns.len());
×
177
        Self::Exclude(columns)
×
178
    }
×
179

180
    pub fn is_include(&self) -> bool {
×
181
        matches!(self, Self::Include(_))
×
182
    }
×
183

184
    pub fn is_exclude(&self) -> bool {
×
185
        matches!(self, Self::Exclude(_))
×
186
    }
×
187

188
    pub fn fields(&self) -> &FieldNames {
1,013✔
189
        let (SelectField::Include(fields) | SelectField::Exclude(fields)) = self;
1,013✔
190

191
        fields
1,013✔
192
    }
1,013✔
193

194
    pub fn as_include_names(&self, field_names: &FieldNames) -> VortexResult<FieldNames> {
1,013✔
195
        if self
1,013✔
196
            .fields()
1,013✔
197
            .iter()
1,013✔
198
            .any(|f| !field_names.iter().contains(f))
2,248✔
199
        {
200
            vortex_bail!(
×
201
                "Field {:?} in select not in field names {:?}",
×
202
                self,
203
                field_names
204
            );
205
        }
1,013✔
206
        match self {
1,013✔
207
            SelectField::Include(fields) => Ok(fields.clone()),
1,013✔
208
            SelectField::Exclude(exc_fields) => Ok(field_names
×
209
                .iter()
×
210
                .filter(|f| exc_fields.iter().contains(f))
×
211
                .cloned()
×
212
                .collect()),
×
213
        }
214
    }
1,013✔
215
}
216

217
impl Display for SelectField {
218
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3✔
219
        match self {
3✔
220
            SelectField::Include(fields) => write!(f, "{{{}}}", DisplayFieldNames(fields)),
2✔
221
            SelectField::Exclude(fields) => write!(f, "~{{{}}}", DisplayFieldNames(fields)),
1✔
222
        }
223
    }
3✔
224
}
225

226
impl Display for SelectExpr {
227
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3✔
228
        write!(f, "{}{}", self.child, self.fields)
3✔
229
    }
3✔
230
}
231

232
impl AnalysisExpr for SelectExpr {}
233

234
#[cfg(test)]
235
mod tests {
236

237
    use vortex_array::arrays::StructArray;
238
    use vortex_array::{IntoArray, ToCanonical};
239
    use vortex_buffer::buffer;
240
    use vortex_dtype::{DType, FieldName, Nullability};
241

242
    use crate::{Scope, root, select, select_exclude, test_harness};
243

244
    fn test_array() -> StructArray {
2✔
245
        StructArray::from_fields(&[
2✔
246
            ("a", buffer![0, 1, 2].into_array()),
2✔
247
            ("b", buffer![4, 5, 6].into_array()),
2✔
248
        ])
2✔
249
        .unwrap()
2✔
250
    }
2✔
251

252
    #[test]
253
    pub fn include_columns() {
1✔
254
        let st = test_array();
1✔
255
        let select = select(vec![FieldName::from("a")], root());
1✔
256
        let selected = select
1✔
257
            .evaluate(&Scope::new(st.to_array()))
1✔
258
            .unwrap()
1✔
259
            .to_struct()
1✔
260
            .unwrap();
1✔
261
        let selected_names = selected.names().clone();
1✔
262
        assert_eq!(selected_names.as_ref(), &["a".into()]);
1✔
263
    }
1✔
264

265
    #[test]
266
    pub fn exclude_columns() {
1✔
267
        let st = test_array();
1✔
268
        let select = select_exclude(vec![FieldName::from("a")], root());
1✔
269
        let selected = select
1✔
270
            .evaluate(&Scope::new(st.to_array()))
1✔
271
            .unwrap()
1✔
272
            .to_struct()
1✔
273
            .unwrap();
1✔
274
        let selected_names = selected.names().clone();
1✔
275
        assert_eq!(selected_names.as_ref(), &["b".into()]);
1✔
276
    }
1✔
277

278
    #[test]
279
    fn dtype() {
1✔
280
        let dtype = test_harness::struct_dtype();
1✔
281

282
        let select_expr = select(vec![FieldName::from("a")], root());
1✔
283
        let expected_dtype = DType::Struct(
1✔
284
            dtype.as_struct().unwrap().project(&["a".into()]).unwrap(),
1✔
285
            Nullability::NonNullable,
1✔
286
        );
1✔
287
        assert_eq!(select_expr.return_dtype(&dtype).unwrap(), expected_dtype);
1✔
288

289
        let select_expr_exclude = select_exclude(
1✔
290
            vec![
1✔
291
                FieldName::from("col1"),
1✔
292
                FieldName::from("col2"),
1✔
293
                FieldName::from("bool1"),
1✔
294
                FieldName::from("bool2"),
1✔
295
            ],
296
            root(),
1✔
297
        );
298
        assert_eq!(
1✔
299
            select_expr_exclude.return_dtype(&dtype).unwrap(),
1✔
300
            expected_dtype
301
        );
302

303
        let select_expr_exclude = select_exclude(
1✔
304
            vec![FieldName::from("col1"), FieldName::from("col2")],
1✔
305
            root(),
1✔
306
        );
307
        assert_eq!(
1✔
308
            select_expr_exclude.return_dtype(&dtype).unwrap(),
1✔
309
            DType::Struct(
1✔
310
                dtype
1✔
311
                    .as_struct()
1✔
312
                    .unwrap()
1✔
313
                    .project(&["a".into(), "bool1".into(), "bool2".into()])
1✔
314
                    .unwrap(),
1✔
315
                Nullability::NonNullable
1✔
316
            )
1✔
317
        );
318
    }
1✔
319
}
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