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