Skip to main content

vortex_array/display/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4mod extractor;
5mod extractors;
6mod tree_display;
7
8use std::fmt::Display;
9
10pub use extractor::IndentedFormatter;
11pub use extractor::TreeContext;
12pub use extractor::TreeExtractor;
13pub use extractors::BufferExtractor;
14pub use extractors::EncodingSummaryExtractor;
15pub use extractors::MetadataExtractor;
16pub use extractors::NbytesExtractor;
17pub use extractors::StatsExtractor;
18use itertools::Itertools as _;
19pub use tree_display::TreeDisplay;
20
21use crate::ArrayRef;
22
23/// Describe how to convert an array to a string.
24///
25/// See also:
26/// [Array::display_as](../trait.Array.html#method.display_as)
27/// and [DisplayArrayAs].
28pub enum DisplayOptions {
29    /// Only the top-level encoding id and limited metadata: `vortex.primitive(i16, len=5)`.
30    ///
31    /// ```
32    /// # use vortex_array::display::DisplayOptions;
33    /// # use vortex_array::IntoArray;
34    /// # use vortex_buffer::buffer;
35    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
36    /// assert_eq!(
37    ///     format!("{}", array.display_as(DisplayOptions::MetadataOnly)),
38    ///     "vortex.primitive(i16, len=5)",
39    /// );
40    /// ```
41    MetadataOnly,
42    /// Only the logical values of the array: `[0i16, 1i16, 2i16, 3i16, 4i16]`.
43    ///
44    /// ```
45    /// # use vortex_array::display::DisplayOptions;
46    /// # use vortex_array::IntoArray;
47    /// # use vortex_buffer::buffer;
48    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
49    /// assert_eq!(
50    ///     format!("{}", array.display_as(DisplayOptions::default())),
51    ///     "[0i16, 1i16, 2i16, 3i16, 4i16]",
52    /// );
53    /// assert_eq!(
54    ///     format!("{}", array.display_as(DisplayOptions::default())),
55    ///     format!("{}", array.display_values()),
56    /// );
57    /// ```
58    CommaSeparatedScalars { omit_comma_after_space: bool },
59    /// The tree of encodings without any concrete values.
60    ///
61    /// With buffers, metadata, and stats:
62    ///
63    /// ```
64    /// # use vortex_array::display::DisplayOptions;
65    /// # use vortex_array::IntoArray;
66    /// # use vortex_buffer::buffer;
67    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
68    /// let expected = "root: vortex.primitive(i16, len=5) nbytes=10 B (100.00%)
69    ///   metadata: ptype: i16
70    ///   buffer: values host 10 B (align=2) (100.00%)
71    /// ";
72    /// assert_eq!(format!("{}", array.display_as(DisplayOptions::TreeDisplay { buffers: true, metadata: true, stats: true })), expected);
73    ///
74    /// # use vortex_array::arrays::StructArray;
75    /// let array = StructArray::from_fields(&[
76    ///     ("x", buffer![1, 2].into_array()),
77    ///     ("y", buffer![3, 4].into_array()),
78    /// ]).unwrap().into_array();
79    /// let expected = "root: vortex.struct({x=i32, y=i32}, len=2) nbytes=16 B (100.00%)
80    ///   metadata:\x20
81    ///   x: vortex.primitive(i32, len=2) nbytes=8 B (50.00%)
82    ///     metadata: ptype: i32
83    ///     buffer: values host 8 B (align=4) (100.00%)
84    ///   y: vortex.primitive(i32, len=2) nbytes=8 B (50.00%)
85    ///     metadata: ptype: i32
86    ///     buffer: values host 8 B (align=4) (100.00%)
87    /// ";
88    /// assert_eq!(format!("{}", array.display_as(DisplayOptions::TreeDisplay { buffers: true, metadata: true, stats: true })), expected);
89    /// ```
90    ///
91    /// With metadata and stats but no buffers:
92    ///
93    /// ```
94    /// # use vortex_array::display::DisplayOptions;
95    /// # use vortex_array::IntoArray;
96    /// # use vortex_buffer::buffer;
97    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
98    /// let expected = "root: vortex.primitive(i16, len=5) nbytes=10 B (100.00%)
99    ///   metadata: ptype: i16
100    /// ";
101    /// assert_eq!(format!("{}", array.display_as(DisplayOptions::TreeDisplay { buffers: false, metadata: true, stats: true })), expected);
102    ///
103    /// # use vortex_array::arrays::StructArray;
104    /// let array = StructArray::from_fields(&[
105    ///     ("x", buffer![1, 2].into_array()),
106    ///     ("y", buffer![3, 4].into_array()),
107    /// ]).unwrap().into_array();
108    /// let expected = "root: vortex.struct({x=i32, y=i32}, len=2) nbytes=16 B (100.00%)
109    ///   metadata:\x20
110    ///   x: vortex.primitive(i32, len=2) nbytes=8 B (50.00%)
111    ///     metadata: ptype: i32
112    ///   y: vortex.primitive(i32, len=2) nbytes=8 B (50.00%)
113    ///     metadata: ptype: i32
114    /// ";
115    /// assert_eq!(format!("{}", array.display_as(DisplayOptions::TreeDisplay { buffers: false, metadata: true, stats: true })), expected);
116    /// ```
117    ///
118    /// With metadata and buffers but no stats:
119    ///
120    /// ```
121    /// # use vortex_array::display::DisplayOptions;
122    /// # use vortex_array::IntoArray;
123    /// # use vortex_buffer::buffer;
124    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
125    /// let expected = "root: vortex.primitive(i16, len=5)
126    ///   metadata: ptype: i16
127    ///   buffer: values host 10 B (align=2)
128    /// ";
129    /// assert_eq!(format!("{}", array.display_as(DisplayOptions::TreeDisplay { buffers: true, metadata: true, stats: false })), expected);
130    ///
131    /// # use vortex_array::arrays::StructArray;
132    /// let array = StructArray::from_fields(&[
133    ///     ("x", buffer![1, 2].into_array()),
134    ///     ("y", buffer![3, 4].into_array()),
135    /// ]).unwrap().into_array();
136    /// let expected = "root: vortex.struct({x=i32, y=i32}, len=2)
137    ///   metadata:\x20
138    ///   x: vortex.primitive(i32, len=2)
139    ///     metadata: ptype: i32
140    ///     buffer: values host 8 B (align=4)
141    ///   y: vortex.primitive(i32, len=2)
142    ///     metadata: ptype: i32
143    ///     buffer: values host 8 B (align=4)
144    /// ";
145    /// assert_eq!(format!("{}", array.display_as(DisplayOptions::TreeDisplay { buffers: true, metadata: true, stats: false })), expected);
146    /// ```
147    ///
148    /// With buffers and stats but no metadata:
149    ///
150    /// ```
151    /// # use vortex_array::display::DisplayOptions;
152    /// # use vortex_array::IntoArray;
153    /// # use vortex_buffer::buffer;
154    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
155    /// let expected = "root: vortex.primitive(i16, len=5) nbytes=10 B (100.00%)
156    ///   buffer: values host 10 B (align=2) (100.00%)
157    /// ";
158    /// assert_eq!(format!("{}", array.display_as(DisplayOptions::TreeDisplay { buffers: true, metadata: false, stats: true })), expected);
159    ///
160    /// # use vortex_array::arrays::StructArray;
161    /// let array = StructArray::from_fields(&[
162    ///     ("x", buffer![1, 2].into_array()),
163    ///     ("y", buffer![3, 4].into_array()),
164    /// ]).unwrap().into_array();
165    /// let expected = "root: vortex.struct({x=i32, y=i32}, len=2) nbytes=16 B (100.00%)
166    ///   x: vortex.primitive(i32, len=2) nbytes=8 B (50.00%)
167    ///     buffer: values host 8 B (align=4) (100.00%)
168    ///   y: vortex.primitive(i32, len=2) nbytes=8 B (50.00%)
169    ///     buffer: values host 8 B (align=4) (100.00%)
170    /// ";
171    /// assert_eq!(format!("{}", array.display_as(DisplayOptions::TreeDisplay { buffers: true, metadata: false, stats: true })), expected);
172    /// ```
173    ///
174    /// With just buffers:
175    ///
176    /// ```
177    /// # use vortex_array::display::DisplayOptions;
178    /// # use vortex_array::IntoArray;
179    /// # use vortex_buffer::buffer;
180    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
181    /// let expected = "root: vortex.primitive(i16, len=5)
182    ///   buffer: values host 10 B (align=2)
183    /// ";
184    /// assert_eq!(format!("{}", array.display_as(DisplayOptions::TreeDisplay { buffers: true, metadata: false, stats: false })), expected);
185    ///
186    /// # use vortex_array::arrays::StructArray;
187    /// let array = StructArray::from_fields(&[
188    ///     ("x", buffer![1, 2].into_array()),
189    ///     ("y", buffer![3, 4].into_array()),
190    /// ]).unwrap().into_array();
191    /// let expected = "root: vortex.struct({x=i32, y=i32}, len=2)
192    ///   x: vortex.primitive(i32, len=2)
193    ///     buffer: values host 8 B (align=4)
194    ///   y: vortex.primitive(i32, len=2)
195    ///     buffer: values host 8 B (align=4)
196    /// ";
197    /// assert_eq!(format!("{}", array.display_as(DisplayOptions::TreeDisplay { buffers: true, metadata: false, stats: false })), expected);
198    /// ```
199    ///
200    /// With just metadata:
201    ///
202    /// ```
203    /// # use vortex_array::display::DisplayOptions;
204    /// # use vortex_array::IntoArray;
205    /// # use vortex_buffer::buffer;
206    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
207    /// let expected = "root: vortex.primitive(i16, len=5)
208    ///   metadata: ptype: i16
209    /// ";
210    /// assert_eq!(format!("{}", array.display_as(DisplayOptions::TreeDisplay { buffers: false, metadata: true, stats: false })), expected);
211    ///
212    /// # use vortex_array::arrays::StructArray;
213    /// let array = StructArray::from_fields(&[
214    ///     ("x", buffer![1, 2].into_array()),
215    ///     ("y", buffer![3, 4].into_array()),
216    /// ]).unwrap().into_array();
217    /// let expected = "root: vortex.struct({x=i32, y=i32}, len=2)
218    ///   metadata:\x20
219    ///   x: vortex.primitive(i32, len=2)
220    ///     metadata: ptype: i32
221    ///   y: vortex.primitive(i32, len=2)
222    ///     metadata: ptype: i32
223    /// ";
224    /// assert_eq!(format!("{}", array.display_as(DisplayOptions::TreeDisplay { buffers: false, metadata: true, stats: false })), expected);
225    /// ```
226    ///
227    /// With just stats:
228    ///
229    /// ```
230    /// # use vortex_array::display::DisplayOptions;
231    /// # use vortex_array::IntoArray;
232    /// # use vortex_buffer::buffer;
233    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
234    /// let expected = "root: vortex.primitive(i16, len=5) nbytes=10 B (100.00%)
235    /// ";
236    /// assert_eq!(format!("{}", array.display_as(DisplayOptions::TreeDisplay { buffers: false, metadata: false, stats: true })), expected);
237    ///
238    /// # use vortex_array::arrays::StructArray;
239    /// let array = StructArray::from_fields(&[
240    ///     ("x", buffer![1, 2].into_array()),
241    ///     ("y", buffer![3, 4].into_array()),
242    /// ]).unwrap().into_array();
243    /// let expected = "root: vortex.struct({x=i32, y=i32}, len=2) nbytes=16 B (100.00%)
244    ///   x: vortex.primitive(i32, len=2) nbytes=8 B (50.00%)
245    ///   y: vortex.primitive(i32, len=2) nbytes=8 B (50.00%)
246    /// ";
247    /// assert_eq!(format!("{}", array.display_as(DisplayOptions::TreeDisplay { buffers: false, metadata: false, stats: true })), expected);
248    /// ```
249    ///
250    /// With neither buffers, metadata, stats, nor values:
251    ///
252    /// ```
253    /// # use vortex_array::display::DisplayOptions;
254    /// # use vortex_array::IntoArray;
255    /// # use vortex_buffer::buffer;
256    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
257    /// let expected = "root: vortex.primitive(i16, len=5)\n";
258    /// assert_eq!(format!("{}", array.display_as(DisplayOptions::TreeDisplay { buffers: false, metadata: false, stats: false })), expected);
259    ///
260    /// # use vortex_array::arrays::StructArray;
261    /// let array = StructArray::from_fields(&[
262    ///     ("x", buffer![1, 2].into_array()),
263    ///     ("y", buffer![3, 4].into_array()),
264    /// ]).unwrap().into_array();
265    /// let expected = "root: vortex.struct({x=i32, y=i32}, len=2)
266    ///   x: vortex.primitive(i32, len=2)
267    ///   y: vortex.primitive(i32, len=2)
268    /// ";
269    /// assert_eq!(format!("{}", array.display_as(DisplayOptions::TreeDisplay { buffers: false, metadata: false, stats: false })), expected);
270    /// ```
271    TreeDisplay {
272        buffers: bool,
273        metadata: bool,
274        stats: bool,
275    },
276    /// Display values in a formatted table with columns.
277    ///
278    /// For struct arrays, displays a column for each field in the struct.
279    /// For regular arrays, displays a single column with values.
280    ///
281    /// ```
282    /// # use vortex_array::display::DisplayOptions;
283    /// # use vortex_array::arrays::StructArray;
284    /// # use vortex_array::IntoArray;
285    /// # use vortex_buffer::buffer;
286    /// let s = StructArray::from_fields(&[
287    ///     ("x", buffer![1, 2].into_array()),
288    ///     ("y", buffer![3, 4].into_array()),
289    /// ]).unwrap().into_array();
290    /// let expected = "
291    /// ┌──────┬──────┐
292    /// │  x   │  y   │
293    /// ├──────┼──────┤
294    /// │ 1i32 │ 3i32 │
295    /// ├──────┼──────┤
296    /// │ 2i32 │ 4i32 │
297    /// └──────┴──────┘".trim();
298    /// assert_eq!(format!("{}", s.display_as(DisplayOptions::TableDisplay)), expected);
299    /// ```
300    #[cfg(feature = "table-display")]
301    TableDisplay,
302}
303
304impl Default for DisplayOptions {
305    fn default() -> Self {
306        Self::CommaSeparatedScalars {
307            omit_comma_after_space: false,
308        }
309    }
310}
311
312/// A shim used to display an array as specified in the options.
313///
314/// See also:
315/// [Array::display_as](../trait.Array.html#method.display_as)
316/// and [DisplayOptions].
317pub struct DisplayArrayAs<'a>(pub &'a ArrayRef, pub DisplayOptions);
318
319impl Display for DisplayArrayAs<'_> {
320    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321        self.0.fmt_as(f, &self.1)
322    }
323}
324
325/// Display the encoding and limited metadata of this array.
326///
327/// # Examples
328/// ```
329/// # use vortex_array::IntoArray;
330/// # use vortex_buffer::buffer;
331/// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
332/// assert_eq!(
333///     format!("{}", array),
334///     "vortex.primitive(i16, len=5)",
335/// );
336/// ```
337impl Display for ArrayRef {
338    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339        self.fmt_as(f, &DisplayOptions::MetadataOnly)
340    }
341}
342
343const DISPLAY_LIMIT: usize = 16;
344impl ArrayRef {
345    /// Display logical values of the array
346    ///
347    /// For example, an `i16` typed array containing the first five non-negative integers is displayed
348    /// as: `[0i16, 1i16, 2i16, 3i16, 4i16]`.
349    ///
350    /// # Examples
351    ///
352    /// ```
353    /// # use vortex_array::IntoArray;
354    /// # use vortex_buffer::buffer;
355    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
356    /// assert_eq!(
357    ///     format!("{}", array.display_values()),
358    ///     "[0i16, 1i16, 2i16, 3i16, 4i16]",
359    /// )
360    /// ```
361    ///
362    /// See also:
363    /// [Array::display_as](..//trait.Array.html#method.display_as),
364    /// [DisplayArrayAs], and [DisplayOptions].
365    pub fn display_values(&self) -> impl Display {
366        DisplayArrayAs(
367            self,
368            DisplayOptions::CommaSeparatedScalars {
369                omit_comma_after_space: false,
370            },
371        )
372    }
373
374    /// Display the array as specified by the options.
375    ///
376    /// See [DisplayOptions] for examples.
377    pub fn display_as(&self, options: DisplayOptions) -> impl Display {
378        DisplayArrayAs(self, options)
379    }
380
381    /// Display the tree of array encodings and lengths without metadata, buffers, or stats.
382    ///
383    /// # Examples
384    /// ```
385    /// # use vortex_array::display::DisplayOptions;
386    /// # use vortex_array::IntoArray;
387    /// # use vortex_buffer::buffer;
388    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
389    /// let expected = "root: vortex.primitive(i16, len=5)\n";
390    /// assert_eq!(format!("{}", array.display_tree_encodings_only()), expected);
391    ///
392    /// # use vortex_array::arrays::StructArray;
393    /// let array = StructArray::from_fields(&[
394    ///     ("x", buffer![1, 2].into_array()),
395    ///     ("y", buffer![3, 4].into_array()),
396    /// ]).unwrap().into_array();
397    /// let expected = "root: vortex.struct({x=i32, y=i32}, len=2)
398    ///   x: vortex.primitive(i32, len=2)
399    ///   y: vortex.primitive(i32, len=2)
400    /// ";
401    /// assert_eq!(format!("{}", array.display_tree_encodings_only()), expected);
402    /// ```
403    pub fn display_tree_encodings_only(&self) -> TreeDisplay {
404        self.tree_display_builder().with(EncodingSummaryExtractor)
405    }
406
407    /// Display the tree of encodings of this array as an indented lists.
408    ///
409    /// While some metadata (such as length, bytes and validity-rate) are included, the logical
410    /// values of the array are not displayed. To view the logical values see
411    /// [Array::display_as](../trait.Array.html#method.display_as)
412    /// and [DisplayOptions].
413    ///
414    /// # Examples
415    /// ```
416    /// # use vortex_array::display::DisplayOptions;
417    /// # use vortex_array::IntoArray;
418    /// # use vortex_buffer::buffer;
419    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
420    /// let expected = "root: vortex.primitive(i16, len=5) nbytes=10 B (100.00%)
421    ///   metadata: ptype: i16
422    ///   buffer: values host 10 B (align=2) (100.00%)
423    /// ";
424    /// assert_eq!(format!("{}", array.display_tree()), expected);
425    /// ```
426    pub fn display_tree(&self) -> TreeDisplay {
427        TreeDisplay::default_display(self.clone())
428    }
429
430    /// Create a tree display with all built-in extractors (nbytes, stats, metadata, buffers).
431    ///
432    /// This is the default, fully-detailed tree display. Use
433    /// `tree_display_builder()` for a blank slate.
434    ///
435    /// # Examples
436    /// ```
437    /// # use vortex_array::IntoArray;
438    /// # use vortex_buffer::buffer;
439    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
440    /// let expected = "root: vortex.primitive(i16, len=5) nbytes=10 B (100.00%)
441    ///   metadata: ptype: i16
442    ///   buffer: values host 10 B (align=2) (100.00%)
443    /// ";
444    /// assert_eq!(array.tree_display().to_string(), expected);
445    /// ```
446    pub fn tree_display(&self) -> TreeDisplay {
447        TreeDisplay::default_display(self.clone())
448    }
449
450    /// Create a composable tree display builder with no extractors.
451    ///
452    /// With no extractors, only the node names are shown.
453    /// Add extractors with [`.with()`][TreeDisplay::with] to include additional information.
454    /// Most builders should start with [`EncodingSummaryExtractor`] to include encoding headers.
455    ///
456    /// # Examples
457    /// ```
458    /// # use vortex_array::IntoArray;
459    /// # use vortex_buffer::buffer;
460    /// use vortex_array::display::{EncodingSummaryExtractor, NbytesExtractor, MetadataExtractor, BufferExtractor};
461    ///
462    /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
463    ///
464    /// // Encodings only
465    /// let encodings = array.tree_display_builder()
466    ///     .with(EncodingSummaryExtractor)
467    ///     .to_string();
468    /// assert_eq!(encodings, "root: vortex.primitive(i16, len=5)\n");
469    ///
470    /// // With encoding + nbytes
471    /// let with_nbytes = array.tree_display_builder()
472    ///     .with(EncodingSummaryExtractor)
473    ///     .with(NbytesExtractor)
474    ///     .to_string();
475    /// assert_eq!(with_nbytes, "root: vortex.primitive(i16, len=5) nbytes=10 B (100.00%)\n");
476    ///
477    /// // With encoding, metadata, and buffers
478    /// let detailed = array.tree_display_builder()
479    ///     .with(EncodingSummaryExtractor)
480    ///     .with(MetadataExtractor)
481    ///     .with(BufferExtractor { show_percent: false })
482    ///     .to_string();
483    /// let expected = "root: vortex.primitive(i16, len=5)\n  metadata: ptype: i16\n  buffer: values host 10 B (align=2)\n";
484    /// assert_eq!(detailed, expected);
485    /// ```
486    pub fn tree_display_builder(&self) -> TreeDisplay {
487        TreeDisplay::new(self.clone())
488    }
489
490    /// Display the array as a formatted table.
491    ///
492    /// For struct arrays, displays a column for each field in the struct.
493    /// For regular arrays, displays a single column with values.
494    ///
495    /// # Examples
496    /// ```
497    /// # #[cfg(feature = "table-display")]
498    /// # {
499    /// # use vortex_array::arrays::StructArray;
500    /// # use vortex_array::IntoArray;
501    /// # use vortex_buffer::buffer;
502    /// let s = StructArray::from_fields(&[
503    ///     ("x", buffer![1, 2].into_array()),
504    ///     ("y", buffer![3, 4].into_array()),
505    /// ]).unwrap().into_array();
506    /// let expected = "
507    /// ┌──────┬──────┐
508    /// │  x   │  y   │
509    /// ├──────┼──────┤
510    /// │ 1i32 │ 3i32 │
511    /// ├──────┼──────┤
512    /// │ 2i32 │ 4i32 │
513    /// └──────┴──────┘".trim();
514    /// assert_eq!(format!("{}", s.display_table()), expected);
515    /// # }
516    /// ```
517    #[cfg(feature = "table-display")]
518    pub fn display_table(&self) -> impl Display {
519        DisplayArrayAs(self, DisplayOptions::TableDisplay)
520    }
521
522    fn fmt_as(&self, f: &mut std::fmt::Formatter, options: &DisplayOptions) -> std::fmt::Result {
523        match options {
524            DisplayOptions::MetadataOnly => EncodingSummaryExtractor::write(self, f),
525            DisplayOptions::CommaSeparatedScalars {
526                omit_comma_after_space,
527            } => {
528                let opening_brace = if f.alternate() { "[\n" } else { "[" };
529                let closing_brace = if f.alternate() { "\n]" } else { "]" };
530
531                let sep = if *omit_comma_after_space { "," } else { ", " };
532                let sep = if f.alternate() { ",\n" } else { sep };
533                let limit = self.len().min(f.precision().unwrap_or(DISPLAY_LIMIT));
534                let is_truncated = self.len() > limit;
535
536                let fmt_scalar = |i| {
537                    self.scalar_at(i)
538                        .map_or_else(|e| format!("<error: {e}>"), |s| s.to_string())
539                };
540                write!(
541                    f,
542                    "{opening_brace}{}{closing_brace}",
543                    (0..limit.saturating_sub(3))
544                        .map(fmt_scalar)
545                        .chain(std::iter::repeat_n(
546                            "...".to_string(),
547                            is_truncated as usize
548                        ))
549                        .chain((self.len().saturating_sub(3)..self.len()).map(fmt_scalar))
550                        .format(sep)
551                )
552            }
553            DisplayOptions::TreeDisplay {
554                buffers,
555                metadata,
556                stats,
557            } => {
558                let extractors: [(bool, Box<dyn TreeExtractor>); 5] = [
559                    (true, Box::new(EncodingSummaryExtractor)),
560                    (*stats, Box::new(NbytesExtractor)),
561                    (*stats, Box::new(StatsExtractor)),
562                    (*metadata, Box::new(MetadataExtractor)),
563                    (
564                        *buffers,
565                        Box::new(BufferExtractor {
566                            show_percent: *stats,
567                        }),
568                    ),
569                ];
570                let mut display = TreeDisplay::new(self.clone());
571                for (enabled, extractor) in extractors {
572                    if enabled {
573                        display = display.with_boxed(extractor);
574                    }
575                }
576                write!(f, "{display}")
577            }
578            #[cfg(feature = "table-display")]
579            DisplayOptions::TableDisplay => {
580                use crate::canonical::ToCanonical;
581                use crate::dtype::DType;
582
583                let mut builder = tabled::builder::Builder::default();
584
585                // Special logic for struct arrays.
586                let DType::Struct(sf, _) = self.dtype() else {
587                    // For non-struct arrays, simply display a single column table without header.
588                    for row_idx in 0..self.len() {
589                        let value = self
590                            .scalar_at(row_idx)
591                            .map_or_else(|e| format!("<error: {e}>"), |s| s.to_string());
592                        builder.push_record([value]);
593                    }
594
595                    let mut table = builder.build();
596                    table.with(tabled::settings::Style::modern());
597
598                    return write!(f, "{table}");
599                };
600
601                let struct_ = self.to_struct();
602                builder.push_record(sf.names().iter().map(|name| name.to_string()));
603
604                for row_idx in 0..self.len() {
605                    if !self.is_valid(row_idx).unwrap_or(false) {
606                        let null_row = vec!["null".to_string(); sf.names().len()];
607                        builder.push_record(null_row);
608                    } else {
609                        let mut row = Vec::new();
610                        for field_array in
611                            crate::arrays::struct_::StructArrayExt::iter_unmasked_fields(&struct_)
612                        {
613                            let value = field_array
614                                .scalar_at(row_idx)
615                                .map_or_else(|e| format!("<error: {e}>"), |s| s.to_string());
616                            row.push(value);
617                        }
618                        builder.push_record(row);
619                    }
620                }
621
622                let mut table = builder.build();
623                table.with(tabled::settings::Style::modern());
624
625                // Center headers
626                for col_idx in 0..sf.names().len() {
627                    table.modify((0, col_idx), tabled::settings::Alignment::center());
628                }
629
630                for row_idx in 0..self.len() {
631                    if !self.is_valid(row_idx).unwrap_or(false) {
632                        table.modify(
633                            (1 + row_idx, 0),
634                            tabled::settings::Span::column(sf.names().len() as isize),
635                        );
636                        table.modify((1 + row_idx, 0), tabled::settings::Alignment::center());
637                    }
638                }
639
640                write!(f, "{table}")
641            }
642        }
643    }
644}
645
646#[cfg(test)]
647mod test {
648    use vortex_buffer::Buffer;
649    use vortex_buffer::buffer;
650
651    use crate::IntoArray as _;
652    use crate::arrays::BoolArray;
653    use crate::arrays::ListArray;
654    use crate::arrays::PrimitiveArray;
655    use crate::arrays::StructArray;
656    use crate::display::DISPLAY_LIMIT;
657    use crate::dtype::FieldNames;
658    use crate::validity::Validity;
659
660    #[test]
661    fn test_primitive() {
662        let x = Buffer::<u32>::empty().into_array();
663        assert_eq!(x.display_values().to_string(), "[]");
664
665        let x = buffer![1].into_array();
666        assert_eq!(x.display_values().to_string(), "[1i32]");
667
668        let x = buffer![1, 2, 3, 4].into_array();
669        assert_eq!(x.display_values().to_string(), "[1i32, 2i32, 3i32, 4i32]");
670
671        let x =
672            PrimitiveArray::from_iter(0i32..i32::try_from(DISPLAY_LIMIT).unwrap() + 1).into_array();
673        assert_eq!(
674            x.display_values().to_string(),
675            "[0i32, 1i32, 2i32, 3i32, 4i32, 5i32, 6i32, 7i32, 8i32, 9i32, 10i32, 11i32, 12i32, ..., 14i32, 15i32, 16i32]"
676        );
677    }
678
679    #[test]
680    fn test_empty_struct() {
681        let s = StructArray::try_new(
682            FieldNames::empty(),
683            vec![],
684            3,
685            Validity::Array(BoolArray::from_iter([true, false, true]).into_array()),
686        )
687        .unwrap()
688        .into_array();
689        assert_eq!(s.display_values().to_string(), "[{}, null, {}]");
690    }
691
692    #[test]
693    fn test_simple_struct() {
694        let s = StructArray::from_fields(&[
695            ("x", buffer![1, 2, 3, 4].into_array()),
696            ("y", buffer![-1, -2, -3, -4].into_array()),
697        ])
698        .unwrap()
699        .into_array();
700        assert_eq!(
701            s.display_values().to_string(),
702            "[{x: 1i32, y: -1i32}, {x: 2i32, y: -2i32}, {x: 3i32, y: -3i32}, {x: 4i32, y: -4i32}]"
703        );
704    }
705
706    #[test]
707    fn test_list() {
708        let x = ListArray::try_new(
709            buffer![1, 2, 3, 4].into_array(),
710            buffer![0, 0, 1, 1, 2, 4].into_array(),
711            Validity::Array(BoolArray::from_iter([true, true, false, true, true]).into_array()),
712        )
713        .unwrap()
714        .into_array();
715        assert_eq!(
716            x.display_values().to_string(),
717            "[[], [1i32], null, [2i32], [3i32, 4i32]]"
718        );
719    }
720
721    #[test]
722    fn test_display_tree_nullable_primitive_validity_child() {
723        let array =
724            PrimitiveArray::from_option_iter([Some(1i64), Some(2), None, Some(3)]).into_array();
725        let expected = "root: vortex.primitive(i64?, len=4) nbytes=33 B (100.00%)\n  metadata: ptype: i64\n  buffer: values host 32 B (align=8) (96.97%)\n  validity: vortex.bool(bool, len=4) nbytes=1 B (3.03%)\n    metadata: offset: 0\n    buffer: bits host 1 B (align=1) (100.00%)\n";
726        assert_eq!(format!("{}", array.display_tree()), expected);
727    }
728
729    #[test]
730    fn test_table_display_primitive() {
731        use crate::display::DisplayOptions;
732
733        let array = buffer![1, 2, 3, 4].into_array();
734        let table_display = array.display_as(DisplayOptions::TableDisplay);
735        assert_eq!(
736            table_display.to_string(),
737            r"
738┌──────┐
739│ 1i32 │
740├──────┤
741│ 2i32 │
742├──────┤
743│ 3i32 │
744├──────┤
745│ 4i32 │
746└──────┘"
747                .trim()
748        );
749    }
750
751    #[test]
752    fn test_table_display() {
753        use crate::display::DisplayOptions;
754
755        let array =
756            PrimitiveArray::from_option_iter(vec![Some(-1), Some(-2), Some(-3), None]).into_array();
757
758        let struct_ = StructArray::try_from_iter_with_validity(
759            [("x", buffer![1, 2, 3, 4].into_array()), ("y", array)],
760            Validity::Array(BoolArray::from_iter([true, false, true, true]).into_array()),
761        )
762        .unwrap()
763        .into_array();
764
765        let table_display = struct_.display_as(DisplayOptions::TableDisplay);
766        assert_eq!(
767            table_display.to_string(),
768            r"
769┌──────┬───────┐
770│  x   │   y   │
771├──────┼───────┤
772│ 1i32 │ -1i32 │
773├──────┼───────┤
774│     null     │
775├──────┼───────┤
776│ 3i32 │ -3i32 │
777├──────┼───────┤
778│ 4i32 │ null  │
779└──────┴───────┘"
780                .trim()
781        );
782    }
783}