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

vortex-data / vortex / 17075133033

19 Aug 2025 04:01PM UTC coverage: 87.949% (+0.09%) from 87.856%
17075133033

push

github

web-flow
feat: ArrayOperations infallible, eager validation + new_unchecked (#4177)

ArrayOperations currently return VortexResult<>, but they really should
just be infallible. A failed array op is generally indicative of
programmer or encoding error. There's really nothing interesting we can
do to handle an out-of-bounds slice() or scalar_at.

There's a lot that falls out of this, like fixing a bunch of tests,
tweaking our scalar value casting to return Option instead of Result,
etc.

---------

Signed-off-by: Andrew Duffy <andrew@a10y.dev>

1744 of 1985 new or added lines in 195 files covered. (87.86%)

36 existing lines in 27 files now uncovered.

56745 of 64520 relevant lines covered (87.95%)

624082.56 hits per line

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

94.64
/vortex-array/src/array/display/mod.rs
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3

4
mod tree;
5

6
use std::fmt::Display;
7

8
use itertools::Itertools as _;
9
use tree::TreeDisplayWrapper;
10
#[cfg(feature = "table-display")]
11
use vortex_error::VortexExpect as _;
12

13
use crate::Array;
14

15
/// Describe how to convert an array to a string.
16
///
17
/// See also:
18
/// [Array::display_as](../trait.Array.html#method.display_as)
19
/// and [DisplayArrayAs].
20
pub enum DisplayOptions {
21
    /// Only the top-level encoding id and limited metadata: `vortex.primitive(i16, len=5)`.
22
    ///
23
    /// ```
24
    /// # use vortex_array::display::DisplayOptions;
25
    /// # use vortex_array::IntoArray;
26
    /// # use vortex_buffer::buffer;
27
    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
28
    /// assert_eq!(
29
    ///     format!("{}", array.display_as(DisplayOptions::MetadataOnly)),
30
    ///     "vortex.primitive(i16, len=5)",
31
    /// );
32
    /// ```
33
    MetadataOnly,
34
    /// Only the logical values of the array: `[0i16, 1i16, 2i16, 3i16, 4i16]`.
35
    ///
36
    /// ```
37
    /// # use vortex_array::display::DisplayOptions;
38
    /// # use vortex_array::IntoArray;
39
    /// # use vortex_buffer::buffer;
40
    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
41
    /// assert_eq!(
42
    ///     format!("{}", array.display_as(DisplayOptions::default())),
43
    ///     "[0i16, 1i16, 2i16, 3i16, 4i16]",
44
    /// );
45
    /// assert_eq!(
46
    ///     format!("{}", array.display_as(DisplayOptions::default())),
47
    ///     format!("{}", array.display_values()),
48
    /// );
49
    /// ```
50
    CommaSeparatedScalars { omit_comma_after_space: bool },
51
    /// The tree of encodings and all metadata but no values.
52
    ///
53
    /// ```
54
    /// # use vortex_array::display::DisplayOptions;
55
    /// # use vortex_array::IntoArray;
56
    /// # use vortex_buffer::buffer;
57
    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
58
    /// let expected = "root: vortex.primitive(i16, len=5) nbytes=10 B (100.00%)
59
    ///   metadata: EmptyMetadata
60
    ///   buffer (align=2): 10 B (100.00%)
61
    /// ";
62
    /// assert_eq!(format!("{}", array.display_as(DisplayOptions::TreeDisplay)), expected);
63
    /// ```
64
    TreeDisplay,
65
    /// Display values in a formatted table with columns.
66
    ///
67
    /// For struct arrays, displays a column for each field in the struct.
68
    /// For regular arrays, displays a single column with values.
69
    ///
70
    /// ```
71
    /// # use vortex_array::display::DisplayOptions;
72
    /// # use vortex_array::arrays::StructArray;
73
    /// # use vortex_array::IntoArray;
74
    /// # use vortex_buffer::buffer;
75
    /// let s = StructArray::from_fields(&[
76
    ///     ("x", buffer![1, 2].into_array()),
77
    ///     ("y", buffer![3, 4].into_array()),
78
    /// ]).unwrap().into_array();
79
    /// let expected = "
80
    /// ┌──────┬──────┐
81
    /// │  x   │  y   │
82
    /// ├──────┼──────┤
83
    /// │ 1i32 │ 3i32 │
84
    /// ├──────┼──────┤
85
    /// │ 2i32 │ 4i32 │
86
    /// └──────┴──────┘".trim();
87
    /// assert_eq!(format!("{}", s.display_as(DisplayOptions::TableDisplay)), expected);
88
    /// ```
89
    #[cfg(feature = "table-display")]
90
    TableDisplay,
91
}
92

93
impl Default for DisplayOptions {
94
    fn default() -> Self {
×
95
        Self::CommaSeparatedScalars {
×
96
            omit_comma_after_space: false,
×
97
        }
×
98
    }
×
99
}
100

101
/// A shim used to display an array as specified in the options.
102
///
103
/// See also:
104
/// [Array::display_as](../trait.Array.html#method.display_as)
105
/// and [DisplayOptions].
106
pub struct DisplayArrayAs<'a>(pub &'a dyn Array, pub DisplayOptions);
107

108
impl Display for DisplayArrayAs<'_> {
109
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1,493✔
110
        self.0.fmt_as(f, &self.1)
1,493✔
111
    }
1,493✔
112
}
113

114
/// Display the encoding and limited metadata of this array.
115
///
116
/// # Examples
117
/// ```
118
/// # use vortex_array::IntoArray;
119
/// # use vortex_buffer::buffer;
120
/// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
121
/// assert_eq!(
122
///     format!("{}", array),
123
///     "vortex.primitive(i16, len=5)",
124
/// );
125
/// ```
126
impl Display for dyn Array + '_ {
127
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
128
        self.fmt_as(f, &DisplayOptions::MetadataOnly)
×
129
    }
×
130
}
131

132
impl dyn Array + '_ {
133
    /// Display logical values of the array
134
    ///
135
    /// For example, an `i16` typed array containing the first five non-negative integers is displayed
136
    /// as: `[0i16, 1i16, 2i16, 3i16, 4i16]`.
137
    ///
138
    /// # Examples
139
    ///
140
    /// ```
141
    /// # use vortex_array::IntoArray;
142
    /// # use vortex_buffer::buffer;
143
    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
144
    /// assert_eq!(
145
    ///     format!("{}", array.display_values()),
146
    ///     "[0i16, 1i16, 2i16, 3i16, 4i16]",
147
    /// )
148
    /// ```
149
    ///
150
    /// See also:
151
    /// [Array::display_as](..//trait.Array.html#method.display_as),
152
    /// [DisplayArrayAs], and [DisplayOptions].
153
    pub fn display_values(&self) -> impl Display {
201✔
154
        DisplayArrayAs(
201✔
155
            self,
201✔
156
            DisplayOptions::CommaSeparatedScalars {
201✔
157
                omit_comma_after_space: false,
201✔
158
            },
201✔
159
        )
201✔
160
    }
201✔
161

162
    /// Display the array as specified by the options.
163
    ///
164
    /// See [DisplayOptions] for examples.
165
    pub fn display_as(&self, options: DisplayOptions) -> impl Display {
901✔
166
        DisplayArrayAs(self, options)
901✔
167
    }
901✔
168

169
    /// Display the tree of encodings of this array as an indented lists.
170
    ///
171
    /// While some metadata (such as length, bytes and validity-rate) are included, the logical
172
    /// values of the array are not displayed. To view the logical values see
173
    /// [Array::display_as](../trait.Array.html#method.display_as)
174
    /// and [DisplayOptions].
175
    ///
176
    /// # Examples
177
    /// ```
178
    /// # use vortex_array::display::DisplayOptions;
179
    /// # use vortex_array::IntoArray;
180
    /// # use vortex_buffer::buffer;
181
    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
182
    /// let expected = "root: vortex.primitive(i16, len=5) nbytes=10 B (100.00%)
183
    ///   metadata: EmptyMetadata
184
    ///   buffer (align=2): 10 B (100.00%)
185
    /// ";
186
    /// assert_eq!(format!("{}", array.display_tree()), expected);
187
    /// ```
188
    pub fn display_tree(&self) -> impl Display {
391✔
189
        DisplayArrayAs(self, DisplayOptions::TreeDisplay)
391✔
190
    }
391✔
191

192
    /// Display the array as a formatted table.
193
    ///
194
    /// For struct arrays, displays a column for each field in the struct.
195
    /// For regular arrays, displays a single column with values.
196
    ///
197
    /// # Examples
198
    /// ```
199
    /// # #[cfg(feature = "table-display")]
200
    /// # {
201
    /// # use vortex_array::arrays::StructArray;
202
    /// # use vortex_array::IntoArray;
203
    /// # use vortex_buffer::buffer;
204
    /// let s = StructArray::from_fields(&[
205
    ///     ("x", buffer![1, 2].into_array()),
206
    ///     ("y", buffer![3, 4].into_array()),
207
    /// ]).unwrap().into_array();
208
    /// let expected = "
209
    /// ┌──────┬──────┐
210
    /// │  x   │  y   │
211
    /// ├──────┼──────┤
212
    /// │ 1i32 │ 3i32 │
213
    /// ├──────┼──────┤
214
    /// │ 2i32 │ 4i32 │
215
    /// └──────┴──────┘".trim();
216
    /// assert_eq!(format!("{}", s.display_table()), expected);
217
    /// # }
218
    /// ```
219
    #[cfg(feature = "table-display")]
220
    pub fn display_table(&self) -> impl Display {
221
        DisplayArrayAs(self, DisplayOptions::TableDisplay)
222
    }
223

224
    fn fmt_as(&self, f: &mut std::fmt::Formatter, options: &DisplayOptions) -> std::fmt::Result {
1,493✔
225
        match options {
1,493✔
226
            DisplayOptions::MetadataOnly => {
227
                write!(
899✔
228
                    f,
899✔
229
                    "{}({}, len={})",
899✔
230
                    self.encoding_id(),
899✔
231
                    self.dtype(),
899✔
232
                    self.len()
899✔
233
                )
234
            }
235
            DisplayOptions::CommaSeparatedScalars {
236
                omit_comma_after_space,
201✔
237
            } => {
238
                write!(f, "[")?;
201✔
239
                let sep = if *omit_comma_after_space { "," } else { ", " };
201✔
240
                write!(
201✔
241
                    f,
201✔
242
                    "{}",
201✔
243
                    (0..self.len()).map(|i| self.scalar_at(i)).format(sep)
1,187✔
UNCOV
244
                )?;
×
245
                write!(f, "]")
201✔
246
            }
247
            DisplayOptions::TreeDisplay => write!(f, "{}", TreeDisplayWrapper(self.to_array())),
391✔
248
            #[cfg(feature = "table-display")]
249
            DisplayOptions::TableDisplay => {
250
                use crate::canonical::ToCanonical;
251
                let mut builder = tabled::builder::Builder::default();
2✔
252

253
                let table = match self.dtype() {
2✔
254
                    vortex_dtype::DType::Struct(sf, _) => {
1✔
255
                        let struct_ = self.to_struct().vortex_expect("struct array");
1✔
256
                        builder.push_record(sf.names().iter().map(|name| name.to_string()));
2✔
257

258
                        for row_idx in 0..self.len() {
4✔
259
                            if !self.is_valid(row_idx).vortex_expect("index in bounds") {
4✔
260
                                let null_row = vec!["null".to_string(); sf.names().len()];
1✔
261
                                builder.push_record(null_row);
1✔
262
                            } else {
1✔
263
                                let mut row = Vec::new();
3✔
264
                                for field_array in struct_.fields() {
6✔
265
                                    let value = field_array.scalar_at(row_idx);
6✔
266
                                    row.push(value.to_string());
6✔
267
                                }
6✔
268
                                builder.push_record(row);
3✔
269
                            }
270
                        }
271

272
                        let mut table = builder.build();
1✔
273
                        table.with(tabled::settings::Style::modern());
1✔
274

275
                        // Center headers
276
                        for col_idx in 0..sf.names().len() {
2✔
277
                            table.modify((0, col_idx), tabled::settings::Alignment::center());
2✔
278
                        }
2✔
279

280
                        for row_idx in 0..self.len() {
4✔
281
                            if !self.is_valid(row_idx).vortex_expect("index is in bounds") {
4✔
282
                                table.modify(
1✔
283
                                    (1 + row_idx, 0),
1✔
284
                                    tabled::settings::Span::column(sf.names().len() as isize),
1✔
285
                                );
1✔
286
                                table.modify(
1✔
287
                                    (1 + row_idx, 0),
1✔
288
                                    tabled::settings::Alignment::center(),
1✔
289
                                );
1✔
290
                            }
3✔
291
                        }
292
                        table
1✔
293
                    }
294
                    _ => {
295
                        // For non-struct arrays, display a single column table without header
296
                        for row_idx in 0..self.len() {
4✔
297
                            let value = self.scalar_at(row_idx);
4✔
298
                            builder.push_record([value.to_string()]);
4✔
299
                        }
4✔
300

301
                        let mut table = builder.build();
1✔
302
                        table.with(tabled::settings::Style::modern());
1✔
303

304
                        table
1✔
305
                    }
306
                };
307
                write!(f, "{table}")
2✔
308
            }
309
        }
310
    }
1,493✔
311
}
312

313
#[cfg(test)]
314
mod test {
315
    use vortex_buffer::{Buffer, buffer};
316
    use vortex_dtype::FieldNames;
317

318
    use crate::IntoArray as _;
319
    use crate::arrays::{BoolArray, ListArray, StructArray};
320
    use crate::validity::Validity;
321

322
    #[test]
323
    fn test_primitive() {
1✔
324
        let x = Buffer::<u32>::empty().into_array();
1✔
325
        assert_eq!(x.display_values().to_string(), "[]");
1✔
326

327
        let x = buffer![1].into_array();
1✔
328
        assert_eq!(x.display_values().to_string(), "[1i32]");
1✔
329

330
        let x = buffer![1, 2, 3, 4].into_array();
1✔
331
        assert_eq!(x.display_values().to_string(), "[1i32, 2i32, 3i32, 4i32]");
1✔
332
    }
1✔
333

334
    #[test]
335
    fn test_empty_struct() {
1✔
336
        let s = StructArray::try_new(
1✔
337
            FieldNames::from(vec![]),
1✔
338
            vec![],
1✔
339
            3,
340
            Validity::Array(BoolArray::from_iter([true, false, true]).into_array()),
1✔
341
        )
342
        .unwrap()
1✔
343
        .into_array();
1✔
344
        assert_eq!(s.display_values().to_string(), "[{}, null, {}]");
1✔
345
    }
1✔
346

347
    #[test]
348
    fn test_simple_struct() {
1✔
349
        let s = StructArray::from_fields(&[
1✔
350
            ("x", buffer![1, 2, 3, 4].into_array()),
1✔
351
            ("y", buffer![-1, -2, -3, -4].into_array()),
1✔
352
        ])
1✔
353
        .unwrap()
1✔
354
        .into_array();
1✔
355
        assert_eq!(
1✔
356
            s.display_values().to_string(),
1✔
357
            "[{x: 1i32, y: -1i32}, {x: 2i32, y: -2i32}, {x: 3i32, y: -3i32}, {x: 4i32, y: -4i32}]"
358
        );
359
    }
1✔
360

361
    #[test]
362
    fn test_list() {
1✔
363
        let x = ListArray::try_new(
1✔
364
            buffer![1, 2, 3, 4].into_array(),
1✔
365
            buffer![0, 0, 1, 1, 2, 4].into_array(),
1✔
366
            Validity::Array(BoolArray::from_iter([true, true, false, true, true]).into_array()),
1✔
367
        )
368
        .unwrap()
1✔
369
        .into_array();
1✔
370
        assert_eq!(
1✔
371
            x.display_values().to_string(),
1✔
372
            "[[], [1i32], null, [2i32], [3i32, 4i32]]"
373
        );
374
    }
1✔
375

376
    #[test]
377
    #[cfg(feature = "table-display")]
378
    fn test_table_display_primitive() {
1✔
379
        use crate::display::DisplayOptions;
380

381
        let array = buffer![1, 2, 3, 4].into_array();
1✔
382
        let table_display = array.display_as(DisplayOptions::TableDisplay);
1✔
383
        assert_eq!(
1✔
384
            table_display.to_string(),
1✔
385
            r"
1✔
386
┌──────┐
1✔
387
│ 1i32 │
1✔
388
├──────┤
1✔
389
│ 2i32 │
1✔
390
├──────┤
1✔
391
│ 3i32 │
1✔
392
├──────┤
1✔
393
│ 4i32 │
1✔
394
└──────┘"
1✔
395
                .trim()
1✔
396
        );
397
    }
1✔
398

399
    #[test]
400
    #[cfg(feature = "table-display")]
401
    fn test_table_display() {
1✔
402
        use crate::display::DisplayOptions;
403

404
        let array = crate::arrays::PrimitiveArray::from_option_iter(vec![
1✔
405
            Some(-1),
1✔
406
            Some(-2),
1✔
407
            Some(-3),
1✔
408
            None,
1✔
409
        ])
410
        .into_array();
1✔
411

412
        let struct_ = StructArray::try_from_iter_with_validity(
1✔
413
            [("x", buffer![1, 2, 3, 4].into_array()), ("y", array)],
1✔
414
            Validity::Array(BoolArray::from_iter([true, false, true, true]).into_array()),
1✔
415
        )
416
        .unwrap()
1✔
417
        .into_array();
1✔
418

419
        let table_display = struct_.display_as(DisplayOptions::TableDisplay);
1✔
420
        assert_eq!(
1✔
421
            table_display.to_string(),
1✔
422
            r"
1✔
423
┌──────┬───────┐
1✔
424
│  x   │   y   │
1✔
425
├──────┼───────┤
1✔
426
│ 1i32 │ -1i32 │
1✔
427
├──────┼───────┤
1✔
428
│     null     │
1✔
429
├──────┼───────┤
1✔
430
│ 3i32 │ -3i32 │
1✔
431
├──────┼───────┤
1✔
432
│ 4i32 │ null  │
1✔
433
└──────┴───────┘"
1✔
434
                .trim()
1✔
435
        );
436
    }
1✔
437
}
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