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