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