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

vortex-data / vortex / 16351290243

17 Jul 2025 05:01PM UTC coverage: 80.74% (+0.04%) from 80.704%
16351290243

Pull #3906

github

web-flow
Merge 23a7a5618 into d53d06603
Pull Request #3906: feat: display as table

86 of 86 new or added lines in 1 file covered. (100.0%)

3 existing lines in 1 file now uncovered.

41954 of 51962 relevant lines covered (80.74%)

157220.13 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
//! Convert an array into a human-readable string representation.
4

5
mod tree;
6

7
use std::fmt::Display;
8

9
use itertools::Itertools as _;
10
use tree::TreeDisplayWrapper;
11
use vortex_dtype::DType;
12
use vortex_error::VortexExpect as _;
13

14
use crate::{Array, ToCanonical};
15

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

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

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

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

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

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

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

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

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

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

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

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

276
                        let mut table = builder.build();
1✔
277
                        table.with(tabled::settings::Style::modern());
1✔
278

279
                        // Center headers
280
                        for col_idx in 0..sf.names().len() {
2✔
281
                            table.modify((0, col_idx), tabled::settings::Alignment::center());
2✔
282
                        }
2✔
283

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

305
                        let mut table = builder.build();
1✔
306
                        table.with(tabled::settings::Style::modern());
1✔
307

308
                        table
1✔
309
                    }
310
                };
311
                write!(f, "{}", table)
2✔
312
            }
313
        }
314
    }
1,343✔
315
}
316

317
#[cfg(test)]
318
mod test {
319
    use vortex_buffer::{Buffer, buffer};
320
    use vortex_dtype::FieldNames;
321

322
    use crate::IntoArray as _;
323
    use crate::arrays::{BoolArray, ListArray, PrimitiveArray, StructArray};
324
    use crate::validity::Validity;
325

326
    #[test]
327
    fn test_primitive() {
1✔
328
        let x = Buffer::<u32>::empty().into_array();
1✔
329
        assert_eq!(x.display_values().to_string(), "[]");
1✔
330

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

334
        let x = buffer![1, 2, 3, 4].into_array();
1✔
335
        assert_eq!(x.display_values().to_string(), "[1i32, 2i32, 3i32, 4i32]");
1✔
336
    }
1✔
337

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

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

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

380
    #[test]
381
    #[cfg(feature = "table-display")]
382
    fn test_table_display_primitive() {
1✔
383
        use crate::display::DisplayOptions;
384

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

403
    #[test]
404
    #[cfg(feature = "table-display")]
405
    fn test_table_display() {
1✔
406
        use crate::display::DisplayOptions;
407

408
        let array =
1✔
409
            PrimitiveArray::from_option_iter(vec![Some(-1), Some(-2), Some(-3), None]).into_array();
1✔
410

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

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