arrow_cast/
pretty.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Utilities for pretty printing [`RecordBatch`]es and [`Array`]s.
19//!
20//! Note this module is not available unless `feature = "prettyprint"` is enabled.
21//!
22//! [`RecordBatch`]: arrow_array::RecordBatch
23//! [`Array`]: arrow_array::Array
24
25use std::fmt::Display;
26
27use comfy_table::{Cell, Table};
28
29use arrow_array::{Array, ArrayRef, RecordBatch};
30use arrow_schema::ArrowError;
31
32use crate::display::{ArrayFormatter, FormatOptions};
33
34/// Create a visual representation of [`RecordBatch`]es
35///
36/// Uses default values for display. See [`pretty_format_batches_with_options`]
37/// for more control.
38///
39/// # Example
40/// ```
41/// # use std::sync::Arc;
42/// # use arrow_array::{ArrayRef, Int32Array, RecordBatch, StringArray};
43/// # use arrow_cast::pretty::pretty_format_batches;
44/// # let batch = RecordBatch::try_from_iter(vec![
45/// #       ("a", Arc::new(Int32Array::from(vec![1, 2, 3, 4, 5])) as ArrayRef),
46/// #       ("b", Arc::new(StringArray::from(vec![Some("a"), Some("b"), None, Some("d"), Some("e")]))),
47/// # ]).unwrap();
48/// // Note, returned object implements `Display`
49/// let pretty_table = pretty_format_batches(&[batch]).unwrap();
50/// let table_str = format!("Batches:\n{pretty_table}");
51/// assert_eq!(table_str,
52/// r#"Batches:
53/// +---+---+
54/// | a | b |
55/// +---+---+
56/// | 1 | a |
57/// | 2 | b |
58/// | 3 |   |
59/// | 4 | d |
60/// | 5 | e |
61/// +---+---+"#);
62/// ```
63pub fn pretty_format_batches(results: &[RecordBatch]) -> Result<impl Display, ArrowError> {
64    let options = FormatOptions::default().with_display_error(true);
65    pretty_format_batches_with_options(results, &options)
66}
67
68/// Create a visual representation of [`RecordBatch`]es with formatting options.
69///
70/// # Arguments
71/// * `results` - A slice of record batches to display
72/// * `options` - [`FormatOptions`] that control the resulting display
73///
74/// # Example
75/// ```
76/// # use std::sync::Arc;
77/// # use arrow_array::{ArrayRef, Int32Array, RecordBatch, StringArray};
78/// # use arrow_cast::display::FormatOptions;
79/// # use arrow_cast::pretty::{pretty_format_batches, pretty_format_batches_with_options};
80/// # let batch = RecordBatch::try_from_iter(vec![
81/// #       ("a", Arc::new(Int32Array::from(vec![1, 2])) as ArrayRef),
82/// #       ("b", Arc::new(StringArray::from(vec![Some("a"), None]))),
83/// # ]).unwrap();
84/// let options = FormatOptions::new()
85///   .with_null("<NULL>");
86/// // Note, returned object implements `Display`
87/// let pretty_table = pretty_format_batches_with_options(&[batch], &options).unwrap();
88/// let table_str = format!("Batches:\n{pretty_table}");
89/// assert_eq!(table_str,
90/// r#"Batches:
91/// +---+--------+
92/// | a | b      |
93/// +---+--------+
94/// | 1 | a      |
95/// | 2 | <NULL> |
96/// +---+--------+"#);
97/// ```
98pub fn pretty_format_batches_with_options(
99    results: &[RecordBatch],
100    options: &FormatOptions,
101) -> Result<impl Display, ArrowError> {
102    create_table(results, options)
103}
104
105/// Create a visual representation of [`ArrayRef`]
106///
107/// Uses default values for display. See [`pretty_format_columns_with_options`]
108///
109/// See [`pretty_format_batches`] for an example
110pub fn pretty_format_columns(
111    col_name: &str,
112    results: &[ArrayRef],
113) -> Result<impl Display, ArrowError> {
114    let options = FormatOptions::default().with_display_error(true);
115    pretty_format_columns_with_options(col_name, results, &options)
116}
117
118/// Create a visual representation of [`ArrayRef`] with formatting options.
119///
120/// See [`pretty_format_batches_with_options`] for an example
121pub fn pretty_format_columns_with_options(
122    col_name: &str,
123    results: &[ArrayRef],
124    options: &FormatOptions,
125) -> Result<impl Display, ArrowError> {
126    create_column(col_name, results, options)
127}
128
129/// Prints a visual representation of record batches to stdout
130pub fn print_batches(results: &[RecordBatch]) -> Result<(), ArrowError> {
131    println!("{}", pretty_format_batches(results)?);
132    Ok(())
133}
134
135/// Prints a visual representation of a list of column to stdout
136pub fn print_columns(col_name: &str, results: &[ArrayRef]) -> Result<(), ArrowError> {
137    println!("{}", pretty_format_columns(col_name, results)?);
138    Ok(())
139}
140
141/// Convert a series of record batches into a table
142fn create_table(results: &[RecordBatch], options: &FormatOptions) -> Result<Table, ArrowError> {
143    let mut table = Table::new();
144    table.load_preset("||--+-++|    ++++++");
145
146    if results.is_empty() {
147        return Ok(table);
148    }
149
150    let schema = results[0].schema();
151
152    let mut header = Vec::new();
153    for field in schema.fields() {
154        if options.types_info() {
155            header.push(Cell::new(format!(
156                "{}\n{}",
157                field.name(),
158                field.data_type()
159            )))
160        } else {
161            header.push(Cell::new(field.name()));
162        }
163    }
164    table.set_header(header);
165
166    for batch in results {
167        let formatters = batch
168            .columns()
169            .iter()
170            .map(|c| ArrayFormatter::try_new(c.as_ref(), options))
171            .collect::<Result<Vec<_>, ArrowError>>()?;
172
173        for row in 0..batch.num_rows() {
174            let mut cells = Vec::new();
175            for formatter in &formatters {
176                cells.push(Cell::new(formatter.value(row)));
177            }
178            table.add_row(cells);
179        }
180    }
181
182    Ok(table)
183}
184
185fn create_column(
186    field: &str,
187    columns: &[ArrayRef],
188    options: &FormatOptions,
189) -> Result<Table, ArrowError> {
190    let mut table = Table::new();
191    table.load_preset("||--+-++|    ++++++");
192
193    if columns.is_empty() {
194        return Ok(table);
195    }
196
197    let header = vec![Cell::new(field)];
198    table.set_header(header);
199
200    for col in columns {
201        let formatter = ArrayFormatter::try_new(col.as_ref(), options)?;
202        for row in 0..col.len() {
203            let cells = vec![Cell::new(formatter.value(row))];
204            table.add_row(cells);
205        }
206    }
207
208    Ok(table)
209}
210
211#[cfg(test)]
212mod tests {
213    use std::fmt::Write;
214    use std::sync::Arc;
215
216    use half::f16;
217
218    use arrow_array::builder::*;
219    use arrow_array::types::*;
220    use arrow_array::*;
221    use arrow_buffer::{IntervalDayTime, IntervalMonthDayNano, ScalarBuffer};
222    use arrow_schema::*;
223
224    use crate::display::array_value_to_string;
225
226    use super::*;
227
228    #[test]
229    fn test_pretty_format_batches() {
230        // define a schema.
231        let schema = Arc::new(Schema::new(vec![
232            Field::new("a", DataType::Utf8, true),
233            Field::new("b", DataType::Int32, true),
234        ]));
235
236        // define data.
237        let batch = RecordBatch::try_new(
238            schema,
239            vec![
240                Arc::new(array::StringArray::from(vec![
241                    Some("a"),
242                    Some("b"),
243                    None,
244                    Some("d"),
245                ])),
246                Arc::new(array::Int32Array::from(vec![
247                    Some(1),
248                    None,
249                    Some(10),
250                    Some(100),
251                ])),
252            ],
253        )
254        .unwrap();
255
256        let table = pretty_format_batches(&[batch]).unwrap().to_string();
257
258        let expected = vec![
259            "+---+-----+",
260            "| a | b   |",
261            "+---+-----+",
262            "| a | 1   |",
263            "| b |     |",
264            "|   | 10  |",
265            "| d | 100 |",
266            "+---+-----+",
267        ];
268
269        let actual: Vec<&str> = table.lines().collect();
270
271        assert_eq!(expected, actual, "Actual result:\n{table}");
272    }
273
274    #[test]
275    fn test_pretty_format_columns() {
276        let columns = vec![
277            Arc::new(array::StringArray::from(vec![
278                Some("a"),
279                Some("b"),
280                None,
281                Some("d"),
282            ])) as ArrayRef,
283            Arc::new(array::StringArray::from(vec![Some("e"), None, Some("g")])),
284        ];
285
286        let table = pretty_format_columns("a", &columns).unwrap().to_string();
287
288        let expected = vec![
289            "+---+", "| a |", "+---+", "| a |", "| b |", "|   |", "| d |", "| e |", "|   |",
290            "| g |", "+---+",
291        ];
292
293        let actual: Vec<&str> = table.lines().collect();
294
295        assert_eq!(expected, actual, "Actual result:\n{table}");
296    }
297
298    #[test]
299    fn test_pretty_format_null() {
300        let schema = Arc::new(Schema::new(vec![
301            Field::new("a", DataType::Utf8, true),
302            Field::new("b", DataType::Int32, true),
303            Field::new("c", DataType::Null, true),
304        ]));
305
306        let num_rows = 4;
307        let arrays = schema
308            .fields()
309            .iter()
310            .map(|f| new_null_array(f.data_type(), num_rows))
311            .collect();
312
313        // define data (null)
314        let batch = RecordBatch::try_new(schema, arrays).unwrap();
315
316        let table = pretty_format_batches(&[batch]).unwrap().to_string();
317
318        let expected = vec![
319            "+---+---+---+",
320            "| a | b | c |",
321            "+---+---+---+",
322            "|   |   |   |",
323            "|   |   |   |",
324            "|   |   |   |",
325            "|   |   |   |",
326            "+---+---+---+",
327        ];
328
329        let actual: Vec<&str> = table.lines().collect();
330
331        assert_eq!(expected, actual, "Actual result:\n{table:#?}");
332    }
333
334    #[test]
335    fn test_pretty_format_dictionary() {
336        // define a schema.
337        let field = Field::new_dictionary("d1", DataType::Int32, DataType::Utf8, true);
338        let schema = Arc::new(Schema::new(vec![field]));
339
340        let mut builder = StringDictionaryBuilder::<Int32Type>::new();
341
342        builder.append_value("one");
343        builder.append_null();
344        builder.append_value("three");
345        let array = Arc::new(builder.finish());
346
347        let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
348
349        let table = pretty_format_batches(&[batch]).unwrap().to_string();
350
351        let expected = vec![
352            "+-------+",
353            "| d1    |",
354            "+-------+",
355            "| one   |",
356            "|       |",
357            "| three |",
358            "+-------+",
359        ];
360
361        let actual: Vec<&str> = table.lines().collect();
362
363        assert_eq!(expected, actual, "Actual result:\n{table}");
364    }
365
366    #[test]
367    fn test_pretty_format_fixed_size_list() {
368        // define a schema.
369        let field_type =
370            DataType::FixedSizeList(Arc::new(Field::new_list_field(DataType::Int32, true)), 3);
371        let schema = Arc::new(Schema::new(vec![Field::new("d1", field_type, true)]));
372
373        let keys_builder = Int32Array::builder(3);
374        let mut builder = FixedSizeListBuilder::new(keys_builder, 3);
375
376        builder.values().append_slice(&[1, 2, 3]);
377        builder.append(true);
378        builder.values().append_slice(&[4, 5, 6]);
379        builder.append(false);
380        builder.values().append_slice(&[7, 8, 9]);
381        builder.append(true);
382
383        let array = Arc::new(builder.finish());
384
385        let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
386        let table = pretty_format_batches(&[batch]).unwrap().to_string();
387        let expected = vec![
388            "+-----------+",
389            "| d1        |",
390            "+-----------+",
391            "| [1, 2, 3] |",
392            "|           |",
393            "| [7, 8, 9] |",
394            "+-----------+",
395        ];
396
397        let actual: Vec<&str> = table.lines().collect();
398
399        assert_eq!(expected, actual, "Actual result:\n{table}");
400    }
401
402    #[test]
403    fn test_pretty_format_string_view() {
404        let schema = Arc::new(Schema::new(vec![Field::new(
405            "d1",
406            DataType::Utf8View,
407            true,
408        )]));
409
410        // Use a small capacity so we end up with multiple views
411        let mut builder = StringViewBuilder::with_capacity(20);
412        builder.append_value("hello");
413        builder.append_null();
414        builder.append_value("longer than 12 bytes");
415        builder.append_value("another than 12 bytes");
416        builder.append_null();
417        builder.append_value("small");
418
419        let array: ArrayRef = Arc::new(builder.finish());
420        let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
421        let table = pretty_format_batches(&[batch]).unwrap().to_string();
422        let expected = vec![
423            "+-----------------------+",
424            "| d1                    |",
425            "+-----------------------+",
426            "| hello                 |",
427            "|                       |",
428            "| longer than 12 bytes  |",
429            "| another than 12 bytes |",
430            "|                       |",
431            "| small                 |",
432            "+-----------------------+",
433        ];
434
435        let actual: Vec<&str> = table.lines().collect();
436
437        assert_eq!(expected, actual, "Actual result:\n{table:#?}");
438    }
439
440    #[test]
441    fn test_pretty_format_binary_view() {
442        let schema = Arc::new(Schema::new(vec![Field::new(
443            "d1",
444            DataType::BinaryView,
445            true,
446        )]));
447
448        // Use a small capacity so we end up with multiple views
449        let mut builder = BinaryViewBuilder::with_capacity(20);
450        builder.append_value(b"hello");
451        builder.append_null();
452        builder.append_value(b"longer than 12 bytes");
453        builder.append_value(b"another than 12 bytes");
454        builder.append_null();
455        builder.append_value(b"small");
456
457        let array: ArrayRef = Arc::new(builder.finish());
458        let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
459        let table = pretty_format_batches(&[batch]).unwrap().to_string();
460        let expected = vec![
461            "+--------------------------------------------+",
462            "| d1                                         |",
463            "+--------------------------------------------+",
464            "| 68656c6c6f                                 |",
465            "|                                            |",
466            "| 6c6f6e676572207468616e203132206279746573   |",
467            "| 616e6f74686572207468616e203132206279746573 |",
468            "|                                            |",
469            "| 736d616c6c                                 |",
470            "+--------------------------------------------+",
471        ];
472
473        let actual: Vec<&str> = table.lines().collect();
474
475        assert_eq!(expected, actual, "Actual result:\n\n{table:#?}");
476    }
477
478    #[test]
479    fn test_pretty_format_fixed_size_binary() {
480        // define a schema.
481        let field_type = DataType::FixedSizeBinary(3);
482        let schema = Arc::new(Schema::new(vec![Field::new("d1", field_type, true)]));
483
484        let mut builder = FixedSizeBinaryBuilder::with_capacity(3, 3);
485
486        builder.append_value([1, 2, 3]).unwrap();
487        builder.append_null();
488        builder.append_value([7, 8, 9]).unwrap();
489
490        let array = Arc::new(builder.finish());
491
492        let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
493        let table = pretty_format_batches(&[batch]).unwrap().to_string();
494        let expected = vec![
495            "+--------+",
496            "| d1     |",
497            "+--------+",
498            "| 010203 |",
499            "|        |",
500            "| 070809 |",
501            "+--------+",
502        ];
503
504        let actual: Vec<&str> = table.lines().collect();
505
506        assert_eq!(expected, actual, "Actual result:\n{table}");
507    }
508
509    /// Generate an array with type $ARRAYTYPE with a numeric value of
510    /// $VALUE, and compare $EXPECTED_RESULT to the output of
511    /// formatting that array with `pretty_format_batches`
512    macro_rules! check_datetime {
513        ($ARRAYTYPE:ident, $VALUE:expr, $EXPECTED_RESULT:expr) => {
514            let mut builder = $ARRAYTYPE::builder(10);
515            builder.append_value($VALUE);
516            builder.append_null();
517            let array = builder.finish();
518
519            let schema = Arc::new(Schema::new(vec![Field::new(
520                "f",
521                array.data_type().clone(),
522                true,
523            )]));
524            let batch = RecordBatch::try_new(schema, vec![Arc::new(array)]).unwrap();
525
526            let table = pretty_format_batches(&[batch])
527                .expect("formatting batches")
528                .to_string();
529
530            let expected = $EXPECTED_RESULT;
531            let actual: Vec<&str> = table.lines().collect();
532
533            assert_eq!(expected, actual, "Actual result:\n\n{actual:#?}\n\n");
534        };
535    }
536
537    fn timestamp_batch<T: ArrowTimestampType>(timezone: &str, value: T::Native) -> RecordBatch {
538        let mut builder = PrimitiveBuilder::<T>::with_capacity(10);
539        builder.append_value(value);
540        builder.append_null();
541        let array = builder.finish();
542        let array = array.with_timezone(timezone);
543
544        let schema = Arc::new(Schema::new(vec![Field::new(
545            "f",
546            array.data_type().clone(),
547            true,
548        )]));
549        RecordBatch::try_new(schema, vec![Arc::new(array)]).unwrap()
550    }
551
552    #[test]
553    fn test_pretty_format_timestamp_second_with_fixed_offset_timezone() {
554        let batch = timestamp_batch::<TimestampSecondType>("+08:00", 11111111);
555        let table = pretty_format_batches(&[batch]).unwrap().to_string();
556
557        let expected = vec![
558            "+---------------------------+",
559            "| f                         |",
560            "+---------------------------+",
561            "| 1970-05-09T22:25:11+08:00 |",
562            "|                           |",
563            "+---------------------------+",
564        ];
565        let actual: Vec<&str> = table.lines().collect();
566        assert_eq!(expected, actual, "Actual result:\n\n{actual:#?}\n\n");
567    }
568
569    #[test]
570    fn test_pretty_format_timestamp_second() {
571        let expected = vec![
572            "+---------------------+",
573            "| f                   |",
574            "+---------------------+",
575            "| 1970-05-09T14:25:11 |",
576            "|                     |",
577            "+---------------------+",
578        ];
579        check_datetime!(TimestampSecondArray, 11111111, expected);
580    }
581
582    #[test]
583    fn test_pretty_format_timestamp_millisecond() {
584        let expected = vec![
585            "+-------------------------+",
586            "| f                       |",
587            "+-------------------------+",
588            "| 1970-01-01T03:05:11.111 |",
589            "|                         |",
590            "+-------------------------+",
591        ];
592        check_datetime!(TimestampMillisecondArray, 11111111, expected);
593    }
594
595    #[test]
596    fn test_pretty_format_timestamp_microsecond() {
597        let expected = vec![
598            "+----------------------------+",
599            "| f                          |",
600            "+----------------------------+",
601            "| 1970-01-01T00:00:11.111111 |",
602            "|                            |",
603            "+----------------------------+",
604        ];
605        check_datetime!(TimestampMicrosecondArray, 11111111, expected);
606    }
607
608    #[test]
609    fn test_pretty_format_timestamp_nanosecond() {
610        let expected = vec![
611            "+-------------------------------+",
612            "| f                             |",
613            "+-------------------------------+",
614            "| 1970-01-01T00:00:00.011111111 |",
615            "|                               |",
616            "+-------------------------------+",
617        ];
618        check_datetime!(TimestampNanosecondArray, 11111111, expected);
619    }
620
621    #[test]
622    fn test_pretty_format_date_32() {
623        let expected = vec![
624            "+------------+",
625            "| f          |",
626            "+------------+",
627            "| 1973-05-19 |",
628            "|            |",
629            "+------------+",
630        ];
631        check_datetime!(Date32Array, 1234, expected);
632    }
633
634    #[test]
635    fn test_pretty_format_date_64() {
636        let expected = vec![
637            "+---------------------+",
638            "| f                   |",
639            "+---------------------+",
640            "| 2005-03-18T01:58:20 |",
641            "|                     |",
642            "+---------------------+",
643        ];
644        check_datetime!(Date64Array, 1111111100000, expected);
645    }
646
647    #[test]
648    fn test_pretty_format_time_32_second() {
649        let expected = vec![
650            "+----------+",
651            "| f        |",
652            "+----------+",
653            "| 00:18:31 |",
654            "|          |",
655            "+----------+",
656        ];
657        check_datetime!(Time32SecondArray, 1111, expected);
658    }
659
660    #[test]
661    fn test_pretty_format_time_32_millisecond() {
662        let expected = vec![
663            "+--------------+",
664            "| f            |",
665            "+--------------+",
666            "| 03:05:11.111 |",
667            "|              |",
668            "+--------------+",
669        ];
670        check_datetime!(Time32MillisecondArray, 11111111, expected);
671    }
672
673    #[test]
674    fn test_pretty_format_time_64_microsecond() {
675        let expected = vec![
676            "+-----------------+",
677            "| f               |",
678            "+-----------------+",
679            "| 00:00:11.111111 |",
680            "|                 |",
681            "+-----------------+",
682        ];
683        check_datetime!(Time64MicrosecondArray, 11111111, expected);
684    }
685
686    #[test]
687    fn test_pretty_format_time_64_nanosecond() {
688        let expected = vec![
689            "+--------------------+",
690            "| f                  |",
691            "+--------------------+",
692            "| 00:00:00.011111111 |",
693            "|                    |",
694            "+--------------------+",
695        ];
696        check_datetime!(Time64NanosecondArray, 11111111, expected);
697    }
698
699    #[test]
700    fn test_int_display() {
701        let array = Arc::new(Int32Array::from(vec![6, 3])) as ArrayRef;
702        let actual_one = array_value_to_string(&array, 0).unwrap();
703        let expected_one = "6";
704
705        let actual_two = array_value_to_string(&array, 1).unwrap();
706        let expected_two = "3";
707        assert_eq!(actual_one, expected_one);
708        assert_eq!(actual_two, expected_two);
709    }
710
711    #[test]
712    fn test_decimal_display() {
713        let precision = 10;
714        let scale = 2;
715
716        let array = [Some(101), None, Some(200), Some(3040)]
717            .into_iter()
718            .collect::<Decimal128Array>()
719            .with_precision_and_scale(precision, scale)
720            .unwrap();
721
722        let dm = Arc::new(array) as ArrayRef;
723
724        let schema = Arc::new(Schema::new(vec![Field::new(
725            "f",
726            dm.data_type().clone(),
727            true,
728        )]));
729
730        let batch = RecordBatch::try_new(schema, vec![dm]).unwrap();
731
732        let table = pretty_format_batches(&[batch]).unwrap().to_string();
733
734        let expected = vec![
735            "+-------+",
736            "| f     |",
737            "+-------+",
738            "| 1.01  |",
739            "|       |",
740            "| 2.00  |",
741            "| 30.40 |",
742            "+-------+",
743        ];
744
745        let actual: Vec<&str> = table.lines().collect();
746        assert_eq!(expected, actual, "Actual result:\n{table}");
747    }
748
749    #[test]
750    fn test_decimal_display_zero_scale() {
751        let precision = 5;
752        let scale = 0;
753
754        let array = [Some(101), None, Some(200), Some(3040)]
755            .into_iter()
756            .collect::<Decimal128Array>()
757            .with_precision_and_scale(precision, scale)
758            .unwrap();
759
760        let dm = Arc::new(array) as ArrayRef;
761
762        let schema = Arc::new(Schema::new(vec![Field::new(
763            "f",
764            dm.data_type().clone(),
765            true,
766        )]));
767
768        let batch = RecordBatch::try_new(schema, vec![dm]).unwrap();
769
770        let table = pretty_format_batches(&[batch]).unwrap().to_string();
771        let expected = vec![
772            "+------+", "| f    |", "+------+", "| 101  |", "|      |", "| 200  |", "| 3040 |",
773            "+------+",
774        ];
775
776        let actual: Vec<&str> = table.lines().collect();
777        assert_eq!(expected, actual, "Actual result:\n{table}");
778    }
779
780    #[test]
781    fn test_pretty_format_struct() {
782        let schema = Schema::new(vec![
783            Field::new_struct(
784                "c1",
785                vec![
786                    Field::new("c11", DataType::Int32, true),
787                    Field::new_struct(
788                        "c12",
789                        vec![Field::new("c121", DataType::Utf8, false)],
790                        false,
791                    ),
792                ],
793                false,
794            ),
795            Field::new("c2", DataType::Utf8, false),
796        ]);
797
798        let c1 = StructArray::from(vec![
799            (
800                Arc::new(Field::new("c11", DataType::Int32, true)),
801                Arc::new(Int32Array::from(vec![Some(1), None, Some(5)])) as ArrayRef,
802            ),
803            (
804                Arc::new(Field::new_struct(
805                    "c12",
806                    vec![Field::new("c121", DataType::Utf8, false)],
807                    false,
808                )),
809                Arc::new(StructArray::from(vec![(
810                    Arc::new(Field::new("c121", DataType::Utf8, false)),
811                    Arc::new(StringArray::from(vec![Some("e"), Some("f"), Some("g")])) as ArrayRef,
812                )])) as ArrayRef,
813            ),
814        ]);
815        let c2 = StringArray::from(vec![Some("a"), Some("b"), Some("c")]);
816
817        let batch =
818            RecordBatch::try_new(Arc::new(schema), vec![Arc::new(c1), Arc::new(c2)]).unwrap();
819
820        let table = pretty_format_batches(&[batch]).unwrap().to_string();
821        let expected = vec![
822            "+--------------------------+----+",
823            "| c1                       | c2 |",
824            "+--------------------------+----+",
825            "| {c11: 1, c12: {c121: e}} | a  |",
826            "| {c11: , c12: {c121: f}}  | b  |",
827            "| {c11: 5, c12: {c121: g}} | c  |",
828            "+--------------------------+----+",
829        ];
830
831        let actual: Vec<&str> = table.lines().collect();
832        assert_eq!(expected, actual, "Actual result:\n{table}");
833    }
834
835    #[test]
836    fn test_pretty_format_dense_union() {
837        let mut builder = UnionBuilder::new_dense();
838        builder.append::<Int32Type>("a", 1).unwrap();
839        builder.append::<Float64Type>("b", 3.2234).unwrap();
840        builder.append_null::<Float64Type>("b").unwrap();
841        builder.append_null::<Int32Type>("a").unwrap();
842        let union = builder.build().unwrap();
843
844        let schema = Schema::new(vec![Field::new_union(
845            "Teamsters",
846            vec![0, 1],
847            vec![
848                Field::new("a", DataType::Int32, false),
849                Field::new("b", DataType::Float64, false),
850            ],
851            UnionMode::Dense,
852        )]);
853
854        let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(union)]).unwrap();
855        let table = pretty_format_batches(&[batch]).unwrap().to_string();
856        let actual: Vec<&str> = table.lines().collect();
857        let expected = vec![
858            "+------------+",
859            "| Teamsters  |",
860            "+------------+",
861            "| {a=1}      |",
862            "| {b=3.2234} |",
863            "| {b=}       |",
864            "| {a=}       |",
865            "+------------+",
866        ];
867
868        assert_eq!(expected, actual);
869    }
870
871    #[test]
872    fn test_pretty_format_sparse_union() {
873        let mut builder = UnionBuilder::new_sparse();
874        builder.append::<Int32Type>("a", 1).unwrap();
875        builder.append::<Float64Type>("b", 3.2234).unwrap();
876        builder.append_null::<Float64Type>("b").unwrap();
877        builder.append_null::<Int32Type>("a").unwrap();
878        let union = builder.build().unwrap();
879
880        let schema = Schema::new(vec![Field::new_union(
881            "Teamsters",
882            vec![0, 1],
883            vec![
884                Field::new("a", DataType::Int32, false),
885                Field::new("b", DataType::Float64, false),
886            ],
887            UnionMode::Sparse,
888        )]);
889
890        let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(union)]).unwrap();
891        let table = pretty_format_batches(&[batch]).unwrap().to_string();
892        let actual: Vec<&str> = table.lines().collect();
893        let expected = vec![
894            "+------------+",
895            "| Teamsters  |",
896            "+------------+",
897            "| {a=1}      |",
898            "| {b=3.2234} |",
899            "| {b=}       |",
900            "| {a=}       |",
901            "+------------+",
902        ];
903
904        assert_eq!(expected, actual);
905    }
906
907    #[test]
908    fn test_pretty_format_nested_union() {
909        //Inner UnionArray
910        let mut builder = UnionBuilder::new_dense();
911        builder.append::<Int32Type>("b", 1).unwrap();
912        builder.append::<Float64Type>("c", 3.2234).unwrap();
913        builder.append_null::<Float64Type>("c").unwrap();
914        builder.append_null::<Int32Type>("b").unwrap();
915        builder.append_null::<Float64Type>("c").unwrap();
916        let inner = builder.build().unwrap();
917
918        let inner_field = Field::new_union(
919            "European Union",
920            vec![0, 1],
921            vec![
922                Field::new("b", DataType::Int32, false),
923                Field::new("c", DataType::Float64, false),
924            ],
925            UnionMode::Dense,
926        );
927
928        // Can't use UnionBuilder with non-primitive types, so manually build outer UnionArray
929        let a_array = Int32Array::from(vec![None, None, None, Some(1234), Some(23)]);
930        let type_ids = [1, 1, 0, 0, 1].into_iter().collect::<ScalarBuffer<i8>>();
931
932        let children = vec![Arc::new(a_array) as Arc<dyn Array>, Arc::new(inner)];
933
934        let union_fields = [
935            (0, Arc::new(Field::new("a", DataType::Int32, true))),
936            (1, Arc::new(inner_field.clone())),
937        ]
938        .into_iter()
939        .collect();
940
941        let outer = UnionArray::try_new(union_fields, type_ids, None, children).unwrap();
942
943        let schema = Schema::new(vec![Field::new_union(
944            "Teamsters",
945            vec![0, 1],
946            vec![Field::new("a", DataType::Int32, true), inner_field],
947            UnionMode::Sparse,
948        )]);
949
950        let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(outer)]).unwrap();
951        let table = pretty_format_batches(&[batch]).unwrap().to_string();
952        let actual: Vec<&str> = table.lines().collect();
953        let expected = vec![
954            "+-----------------------------+",
955            "| Teamsters                   |",
956            "+-----------------------------+",
957            "| {European Union={b=1}}      |",
958            "| {European Union={c=3.2234}} |",
959            "| {a=}                        |",
960            "| {a=1234}                    |",
961            "| {European Union={c=}}       |",
962            "+-----------------------------+",
963        ];
964        assert_eq!(expected, actual);
965    }
966
967    #[test]
968    fn test_writing_formatted_batches() {
969        // define a schema.
970        let schema = Arc::new(Schema::new(vec![
971            Field::new("a", DataType::Utf8, true),
972            Field::new("b", DataType::Int32, true),
973        ]));
974
975        // define data.
976        let batch = RecordBatch::try_new(
977            schema,
978            vec![
979                Arc::new(array::StringArray::from(vec![
980                    Some("a"),
981                    Some("b"),
982                    None,
983                    Some("d"),
984                ])),
985                Arc::new(array::Int32Array::from(vec![
986                    Some(1),
987                    None,
988                    Some(10),
989                    Some(100),
990                ])),
991            ],
992        )
993        .unwrap();
994
995        let mut buf = String::new();
996        write!(&mut buf, "{}", pretty_format_batches(&[batch]).unwrap()).unwrap();
997
998        let s = [
999            "+---+-----+",
1000            "| a | b   |",
1001            "+---+-----+",
1002            "| a | 1   |",
1003            "| b |     |",
1004            "|   | 10  |",
1005            "| d | 100 |",
1006            "+---+-----+",
1007        ];
1008        let expected = s.join("\n");
1009        assert_eq!(expected, buf);
1010    }
1011
1012    #[test]
1013    fn test_float16_display() {
1014        let values = vec![
1015            Some(f16::from_f32(f32::NAN)),
1016            Some(f16::from_f32(4.0)),
1017            Some(f16::from_f32(f32::NEG_INFINITY)),
1018        ];
1019        let array = Arc::new(values.into_iter().collect::<Float16Array>()) as ArrayRef;
1020
1021        let schema = Arc::new(Schema::new(vec![Field::new(
1022            "f16",
1023            array.data_type().clone(),
1024            true,
1025        )]));
1026
1027        let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
1028
1029        let table = pretty_format_batches(&[batch]).unwrap().to_string();
1030
1031        let expected = vec![
1032            "+------+", "| f16  |", "+------+", "| NaN  |", "| 4    |", "| -inf |", "+------+",
1033        ];
1034
1035        let actual: Vec<&str> = table.lines().collect();
1036        assert_eq!(expected, actual, "Actual result:\n{table}");
1037    }
1038
1039    #[test]
1040    fn test_pretty_format_interval_day_time() {
1041        let arr = Arc::new(arrow_array::IntervalDayTimeArray::from(vec![
1042            Some(IntervalDayTime::new(-1, -600_000)),
1043            Some(IntervalDayTime::new(0, -1001)),
1044            Some(IntervalDayTime::new(0, -1)),
1045            Some(IntervalDayTime::new(0, 1)),
1046            Some(IntervalDayTime::new(0, 10)),
1047            Some(IntervalDayTime::new(0, 100)),
1048        ]));
1049
1050        let schema = Arc::new(Schema::new(vec![Field::new(
1051            "IntervalDayTime",
1052            arr.data_type().clone(),
1053            true,
1054        )]));
1055
1056        let batch = RecordBatch::try_new(schema, vec![arr]).unwrap();
1057
1058        let table = pretty_format_batches(&[batch]).unwrap().to_string();
1059
1060        let expected = vec![
1061            "+------------------+",
1062            "| IntervalDayTime  |",
1063            "+------------------+",
1064            "| -1 days -10 mins |",
1065            "| -1.001 secs      |",
1066            "| -0.001 secs      |",
1067            "| 0.001 secs       |",
1068            "| 0.010 secs       |",
1069            "| 0.100 secs       |",
1070            "+------------------+",
1071        ];
1072
1073        let actual: Vec<&str> = table.lines().collect();
1074
1075        assert_eq!(expected, actual, "Actual result:\n{table}");
1076    }
1077
1078    #[test]
1079    fn test_pretty_format_interval_month_day_nano_array() {
1080        let arr = Arc::new(arrow_array::IntervalMonthDayNanoArray::from(vec![
1081            Some(IntervalMonthDayNano::new(-1, -1, -600_000_000_000)),
1082            Some(IntervalMonthDayNano::new(0, 0, -1_000_000_001)),
1083            Some(IntervalMonthDayNano::new(0, 0, -1)),
1084            Some(IntervalMonthDayNano::new(0, 0, 1)),
1085            Some(IntervalMonthDayNano::new(0, 0, 10)),
1086            Some(IntervalMonthDayNano::new(0, 0, 100)),
1087            Some(IntervalMonthDayNano::new(0, 0, 1_000)),
1088            Some(IntervalMonthDayNano::new(0, 0, 10_000)),
1089            Some(IntervalMonthDayNano::new(0, 0, 100_000)),
1090            Some(IntervalMonthDayNano::new(0, 0, 1_000_000)),
1091            Some(IntervalMonthDayNano::new(0, 0, 10_000_000)),
1092            Some(IntervalMonthDayNano::new(0, 0, 100_000_000)),
1093            Some(IntervalMonthDayNano::new(0, 0, 1_000_000_000)),
1094        ]));
1095
1096        let schema = Arc::new(Schema::new(vec![Field::new(
1097            "IntervalMonthDayNano",
1098            arr.data_type().clone(),
1099            true,
1100        )]));
1101
1102        let batch = RecordBatch::try_new(schema, vec![arr]).unwrap();
1103
1104        let table = pretty_format_batches(&[batch]).unwrap().to_string();
1105
1106        let expected = vec![
1107            "+--------------------------+",
1108            "| IntervalMonthDayNano     |",
1109            "+--------------------------+",
1110            "| -1 mons -1 days -10 mins |",
1111            "| -1.000000001 secs        |",
1112            "| -0.000000001 secs        |",
1113            "| 0.000000001 secs         |",
1114            "| 0.000000010 secs         |",
1115            "| 0.000000100 secs         |",
1116            "| 0.000001000 secs         |",
1117            "| 0.000010000 secs         |",
1118            "| 0.000100000 secs         |",
1119            "| 0.001000000 secs         |",
1120            "| 0.010000000 secs         |",
1121            "| 0.100000000 secs         |",
1122            "| 1.000000000 secs         |",
1123            "+--------------------------+",
1124        ];
1125
1126        let actual: Vec<&str> = table.lines().collect();
1127
1128        assert_eq!(expected, actual, "Actual result:\n{table}");
1129    }
1130
1131    #[test]
1132    fn test_format_options() {
1133        let options = FormatOptions::default()
1134            .with_null("null")
1135            .with_types_info(true);
1136        let int32_array = Int32Array::from(vec![Some(1), Some(2), None, Some(3), Some(4)]);
1137        let string_array =
1138            StringArray::from(vec![Some("foo"), Some("bar"), None, Some("baz"), None]);
1139
1140        let batch = RecordBatch::try_from_iter([
1141            ("my_int32_name", Arc::new(int32_array) as _),
1142            ("my_string_name", Arc::new(string_array) as _),
1143        ])
1144        .unwrap();
1145
1146        let column = pretty_format_columns_with_options(
1147            "my_column_name",
1148            &[batch.column(0).clone()],
1149            &options,
1150        )
1151        .unwrap()
1152        .to_string();
1153
1154        let expected_column = vec![
1155            "+----------------+",
1156            "| my_column_name |",
1157            "+----------------+",
1158            "| 1              |",
1159            "| 2              |",
1160            "| null           |",
1161            "| 3              |",
1162            "| 4              |",
1163            "+----------------+",
1164        ];
1165
1166        let actual: Vec<&str> = column.lines().collect();
1167        assert_eq!(expected_column, actual, "Actual result:\n{column}");
1168
1169        let batch = pretty_format_batches_with_options(&[batch], &options)
1170            .unwrap()
1171            .to_string();
1172
1173        let expected_table = vec![
1174            "+---------------+----------------+",
1175            "| my_int32_name | my_string_name |",
1176            "| Int32         | Utf8           |",
1177            "+---------------+----------------+",
1178            "| 1             | foo            |",
1179            "| 2             | bar            |",
1180            "| null          | null           |",
1181            "| 3             | baz            |",
1182            "| 4             | null           |",
1183            "+---------------+----------------+",
1184        ];
1185
1186        let actual: Vec<&str> = batch.lines().collect();
1187        assert_eq!(expected_table, actual, "Actual result:\n{batch}");
1188    }
1189}