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

vortex-data / vortex / 16331938722

16 Jul 2025 10:49PM UTC coverage: 80.702% (-0.9%) from 81.557%
16331938722

push

github

web-flow
feat: build with stable rust (#3881)

120 of 173 new or added lines in 28 files covered. (69.36%)

174 existing lines in 102 files now uncovered.

41861 of 51871 relevant lines covered (80.7%)

157487.71 hits per line

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

84.44
/vortex-expr/src/exprs/pack.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 itertools::Itertools as _;
8
use vortex_array::arrays::StructArray;
9
use vortex_array::validity::Validity;
10
use vortex_array::{ArrayRef, DeserializeMetadata, IntoArray, ProstMetadata};
11
use vortex_dtype::{DType, FieldName, FieldNames, Nullability, StructFields};
12
use vortex_error::{VortexExpect as _, VortexResult, vortex_bail, vortex_err};
13
use vortex_proto::expr as pb;
14

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

17
vtable!(Pack);
18

19
/// Pack zero or more expressions into a structure with named fields.
20
///
21
/// # Examples
22
///
23
/// ```
24
/// use vortex_array::{IntoArray, ToCanonical};
25
/// use vortex_buffer::buffer;
26
/// use vortex_expr::{root, PackExpr, Scope, VortexExpr};
27
/// use vortex_scalar::Scalar;
28
/// use vortex_dtype::Nullability;
29
///
30
/// let example = PackExpr::try_new(
31
///     ["x", "x copy", "second x copy"].into(),
32
///     vec![root(), root(), root()],
33
///     Nullability::NonNullable,
34
/// ).unwrap();
35
/// let packed = example.evaluate(&Scope::new(buffer![100, 110, 200].into_array())).unwrap();
36
/// let x_copy = packed
37
///     .to_struct()
38
///     .unwrap()
39
///     .field_by_name("x copy")
40
///     .unwrap()
41
///     .clone();
42
/// assert_eq!(x_copy.scalar_at(0).unwrap(), Scalar::from(100));
43
/// assert_eq!(x_copy.scalar_at(1).unwrap(), Scalar::from(110));
44
/// assert_eq!(x_copy.scalar_at(2).unwrap(), Scalar::from(200));
45
/// ```
46
///
47
#[allow(clippy::derived_hash_with_manual_eq)]
48
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
49
pub struct PackExpr {
50
    names: FieldNames,
51
    values: Vec<ExprRef>,
52
    nullability: Nullability,
53
}
54

55
pub struct PackExprEncoding;
56

57
impl VTable for PackVTable {
58
    type Expr = PackExpr;
59
    type Encoding = PackExprEncoding;
60
    type Metadata = ProstMetadata<pb::PackOpts>;
61

62
    fn id(_encoding: &Self::Encoding) -> ExprId {
123✔
63
        ExprId::new_ref("pack")
123✔
64
    }
123✔
65

66
    fn encoding(_expr: &Self::Expr) -> ExprEncodingRef {
×
67
        ExprEncodingRef::new_ref(PackExprEncoding.as_ref())
×
68
    }
×
69

70
    fn metadata(expr: &Self::Expr) -> Option<Self::Metadata> {
×
71
        Some(ProstMetadata(pb::PackOpts {
72
            paths: expr.names.iter().map(|n| n.to_string()).collect(),
×
73
            nullable: expr.nullability.into(),
×
74
        }))
75
    }
×
76

77
    fn children(expr: &Self::Expr) -> Vec<&ExprRef> {
153,704✔
78
        expr.values.iter().collect()
153,704✔
79
    }
153,704✔
80

81
    fn with_children(expr: &Self::Expr, children: Vec<ExprRef>) -> VortexResult<Self::Expr> {
5,996✔
82
        PackExpr::try_new(expr.names.clone(), children, expr.nullability)
5,996✔
83
    }
5,996✔
84

85
    fn build(
×
86
        _encoding: &Self::Encoding,
×
87
        metadata: &<Self::Metadata as DeserializeMetadata>::Output,
×
88
        children: Vec<ExprRef>,
×
89
    ) -> VortexResult<Self::Expr> {
×
90
        if children.len() != metadata.paths.len() {
×
91
            vortex_bail!(
×
92
                "Pack expression expects {} children, got {}",
×
93
                metadata.paths.len(),
×
94
                children.len()
×
95
            );
96
        }
×
97
        let names: FieldNames = metadata
×
98
            .paths
×
99
            .iter()
×
100
            .map(|name| FieldName::from(name.as_str()))
×
101
            .collect();
×
102
        PackExpr::try_new(names, children, metadata.nullable.into())
×
103
    }
×
104

105
    fn evaluate(expr: &Self::Expr, scope: &Scope) -> VortexResult<ArrayRef> {
7,851✔
106
        let len = scope.len();
7,851✔
107
        let value_arrays = expr
7,851✔
108
            .values
7,851✔
109
            .iter()
7,851✔
110
            .map(|value_expr| value_expr.unchecked_evaluate(scope))
11,287✔
111
            .process_results(|it| it.collect::<Vec<_>>())?;
7,851✔
112
        let validity = match expr.nullability {
7,851✔
113
            Nullability::NonNullable => Validity::NonNullable,
7,830✔
114
            Nullability::Nullable => Validity::AllValid,
21✔
115
        };
116
        Ok(StructArray::try_new(expr.names.clone(), value_arrays, len, validity)?.into_array())
7,851✔
117
    }
7,851✔
118

119
    fn return_dtype(expr: &Self::Expr, scope: &DType) -> VortexResult<DType> {
19,692✔
120
        let value_dtypes = expr
19,692✔
121
            .values
19,692✔
122
            .iter()
19,692✔
123
            .map(|value_expr| value_expr.return_dtype(scope))
24,446✔
124
            .process_results(|it| it.collect())?;
19,692✔
125
        Ok(DType::Struct(
19,692✔
126
            StructFields::new(expr.names.clone(), value_dtypes),
19,692✔
127
            expr.nullability,
19,692✔
128
        ))
19,692✔
129
    }
19,692✔
130
}
131

132
impl PackExpr {
133
    pub fn try_new(
14,528✔
134
        names: FieldNames,
14,528✔
135
        values: Vec<ExprRef>,
14,528✔
136
        nullability: Nullability,
14,528✔
137
    ) -> VortexResult<Self> {
14,528✔
138
        if names.len() != values.len() {
14,528✔
139
            vortex_bail!("length mismatch {} {}", names.len(), values.len());
×
140
        }
14,528✔
141
        Ok(PackExpr {
14,528✔
142
            names,
14,528✔
143
            values,
14,528✔
144
            nullability,
14,528✔
145
        })
14,528✔
146
    }
14,528✔
147

148
    pub fn try_new_expr(
20✔
149
        names: FieldNames,
20✔
150
        values: Vec<ExprRef>,
20✔
151
        nullability: Nullability,
20✔
152
    ) -> VortexResult<ExprRef> {
20✔
153
        Self::try_new(names, values, nullability).map(|v| v.into_expr())
20✔
154
    }
20✔
155

156
    pub fn names(&self) -> &FieldNames {
×
157
        &self.names
×
158
    }
×
159

160
    pub fn field(&self, field_name: &FieldName) -> VortexResult<ExprRef> {
3,263✔
161
        let idx = self
3,263✔
162
            .names
3,263✔
163
            .iter()
3,263✔
164
            .position(|name| name == field_name)
11,932✔
165
            .ok_or_else(|| {
3,263✔
166
                vortex_err!(
×
167
                    "Cannot find field {} in pack fields {:?}",
×
168
                    field_name,
169
                    self.names
170
                )
UNCOV
171
            })?;
×
172

173
        self.values
3,263✔
174
            .get(idx)
3,263✔
175
            .cloned()
3,263✔
176
            .ok_or_else(|| vortex_err!("field index out of bounds: {}", idx))
3,263✔
177
    }
3,263✔
178

179
    pub fn nullability(&self) -> Nullability {
×
180
        self.nullability
×
181
    }
×
182
}
183

184
pub fn pack(
7,986✔
185
    elements: impl IntoIterator<Item = (impl Into<FieldName>, ExprRef)>,
7,986✔
186
    nullability: Nullability,
7,986✔
187
) -> ExprRef {
7,986✔
188
    let (names, values): (Vec<_>, Vec<_>) = elements
7,986✔
189
        .into_iter()
7,986✔
190
        .map(|(name, value)| (name.into(), value))
12,833✔
191
        .unzip();
7,986✔
192
    PackExpr::try_new(names.into(), values, nullability)
7,986✔
193
        .vortex_expect("pack names and values have the same length")
7,986✔
194
        .into_expr()
7,986✔
195
}
7,986✔
196

197
impl Display for PackExpr {
198
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1✔
199
        write!(
1✔
200
            f,
1✔
201
            "pack({}){}",
1✔
202
            self.names
1✔
203
                .iter()
1✔
204
                .zip(&self.values)
1✔
205
                .format_with(", ", |(name, expr), f| f(&format_args!("{name}: {expr}"))),
2✔
206
            self.nullability
207
        )
208
    }
1✔
209
}
210

211
impl AnalysisExpr for PackExpr {}
212

213
#[cfg(test)]
214
mod tests {
215

216
    use vortex_array::arrays::{PrimitiveArray, StructArray};
217
    use vortex_array::validity::Validity;
218
    use vortex_array::vtable::ValidityHelper;
219
    use vortex_array::{Array, ArrayRef, IntoArray, ToCanonical};
220
    use vortex_buffer::buffer;
221
    use vortex_dtype::{FieldNames, Nullability};
222
    use vortex_error::{VortexResult, vortex_bail};
223

224
    use crate::{IntoExpr, PackExpr, Scope, col};
225

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

235
    fn primitive_field(array: &dyn Array, field_path: &[&str]) -> VortexResult<PrimitiveArray> {
7✔
236
        let mut field_path = field_path.iter();
7✔
237

238
        let Some(field) = field_path.next() else {
7✔
239
            vortex_bail!("empty field path");
240
        };
241

242
        let mut array = array.to_struct()?.field_by_name(field)?.clone();
7✔
243
        for field in field_path {
9✔
244
            array = array.to_struct()?.field_by_name(field)?.clone();
2✔
245
        }
246
        Ok(array.to_primitive().unwrap())
7✔
247
    }
7✔
248

249
    #[test]
250
    pub fn test_empty_pack() {
1✔
251
        let expr =
1✔
252
            PackExpr::try_new(FieldNames::default(), Vec::new(), Nullability::NonNullable).unwrap();
1✔
253

254
        let test_array = test_array();
1✔
255
        let actual_array = expr.evaluate(&Scope::new(test_array.clone())).unwrap();
1✔
256
        assert_eq!(actual_array.len(), test_array.len());
1✔
257
        assert_eq!(
1✔
258
            actual_array.to_struct().unwrap().struct_fields().nfields(),
1✔
259
            0
260
        );
261
    }
1✔
262

263
    #[test]
264
    pub fn test_simple_pack() {
1✔
265
        let expr = PackExpr::try_new(
1✔
266
            ["one", "two", "three"].into(),
1✔
267
            vec![col("a"), col("b"), col("a")],
1✔
268
            Nullability::NonNullable,
1✔
269
        )
270
        .unwrap();
1✔
271

272
        let actual_array = expr
1✔
273
            .evaluate(&Scope::new(test_array()))
1✔
274
            .unwrap()
1✔
275
            .to_struct()
1✔
276
            .unwrap();
1✔
277
        let expected_names: FieldNames = ["one", "two", "three"].into();
1✔
278
        assert_eq!(actual_array.names(), &expected_names);
1✔
279
        assert_eq!(actual_array.validity(), &Validity::NonNullable);
1✔
280

281
        assert_eq!(
1✔
282
            primitive_field(actual_array.as_ref(), &["one"])
1✔
283
                .unwrap()
1✔
284
                .as_slice::<i32>(),
1✔
285
            [0, 1, 2]
286
        );
287
        assert_eq!(
1✔
288
            primitive_field(actual_array.as_ref(), &["two"])
1✔
289
                .unwrap()
1✔
290
                .as_slice::<i32>(),
1✔
291
            [4, 5, 6]
292
        );
293
        assert_eq!(
1✔
294
            primitive_field(actual_array.as_ref(), &["three"])
1✔
295
                .unwrap()
1✔
296
                .as_slice::<i32>(),
1✔
297
            [0, 1, 2]
298
        );
299
    }
1✔
300

301
    #[test]
302
    pub fn test_nested_pack() {
1✔
303
        let expr = PackExpr::try_new(
1✔
304
            ["one", "two", "three"].into(),
1✔
305
            vec![
1✔
306
                col("a"),
1✔
307
                PackExpr::try_new(
1✔
308
                    ["two_one", "two_two"].into(),
1✔
309
                    vec![col("b"), col("b")],
1✔
310
                    Nullability::NonNullable,
1✔
311
                )
1✔
312
                .unwrap()
1✔
313
                .into_expr(),
1✔
314
                col("a"),
1✔
315
            ],
316
            Nullability::NonNullable,
1✔
317
        )
318
        .unwrap();
1✔
319

320
        let actual_array = expr
1✔
321
            .evaluate(&Scope::new(test_array()))
1✔
322
            .unwrap()
1✔
323
            .to_struct()
1✔
324
            .unwrap();
1✔
325
        let expected_names = FieldNames::from(["one", "two", "three"]);
1✔
326
        assert_eq!(actual_array.names(), &expected_names);
1✔
327

328
        assert_eq!(
1✔
329
            primitive_field(actual_array.as_ref(), &["one"])
1✔
330
                .unwrap()
1✔
331
                .as_slice::<i32>(),
1✔
332
            [0, 1, 2]
333
        );
334
        assert_eq!(
1✔
335
            primitive_field(actual_array.as_ref(), &["two", "two_one"])
1✔
336
                .unwrap()
1✔
337
                .as_slice::<i32>(),
1✔
338
            [4, 5, 6]
339
        );
340
        assert_eq!(
1✔
341
            primitive_field(actual_array.as_ref(), &["two", "two_two"])
1✔
342
                .unwrap()
1✔
343
                .as_slice::<i32>(),
1✔
344
            [4, 5, 6]
345
        );
346
        assert_eq!(
1✔
347
            primitive_field(actual_array.as_ref(), &["three"])
1✔
348
                .unwrap()
1✔
349
                .as_slice::<i32>(),
1✔
350
            [0, 1, 2]
351
        );
352
    }
1✔
353

354
    #[test]
355
    pub fn test_pack_nullable() {
1✔
356
        let expr = PackExpr::try_new(
1✔
357
            ["one", "two", "three"].into(),
1✔
358
            vec![col("a"), col("b"), col("a")],
1✔
359
            Nullability::Nullable,
1✔
360
        )
361
        .unwrap();
1✔
362

363
        let actual_array = expr
1✔
364
            .evaluate(&Scope::new(test_array()))
1✔
365
            .unwrap()
1✔
366
            .to_struct()
1✔
367
            .unwrap();
1✔
368
        let expected_names: FieldNames = ["one", "two", "three"].into();
1✔
369
        assert_eq!(actual_array.names(), &expected_names);
1✔
370
        assert_eq!(actual_array.validity(), &Validity::AllValid);
1✔
371
    }
1✔
372
}
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

© 2025 Coveralls, Inc