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}