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