1use arrow_array::{Array, ArrayRef, RecordBatch};
26use arrow_schema::{ArrowError, SchemaRef};
27use comfy_table::{Cell, Table};
28use std::fmt::Display;
29
30use crate::display::{ArrayFormatter, FormatOptions, make_array_formatter};
31
32pub fn pretty_format_batches(results: &[RecordBatch]) -> Result<impl Display + use<>, ArrowError> {
62 let options = FormatOptions::default().with_display_error(true);
63 pretty_format_batches_with_options(results, &options)
64}
65
66pub fn pretty_format_batches_with_schema(
91 schema: SchemaRef,
92 results: &[RecordBatch],
93) -> Result<impl Display + use<>, ArrowError> {
94 let options = FormatOptions::default().with_display_error(true);
95 create_table(Some(schema), results, &options)
96}
97
98pub fn pretty_format_batches_with_options(
129 results: &[RecordBatch],
130 options: &FormatOptions,
131) -> Result<impl Display + use<>, ArrowError> {
132 create_table(None, results, options)
133}
134
135pub fn pretty_format_columns(
141 col_name: &str,
142 results: &[ArrayRef],
143) -> Result<impl Display + use<>, ArrowError> {
144 let options = FormatOptions::default().with_display_error(true);
145 pretty_format_columns_with_options(col_name, results, &options)
146}
147
148pub fn pretty_format_columns_with_options(
152 col_name: &str,
153 results: &[ArrayRef],
154 options: &FormatOptions,
155) -> Result<impl Display + use<>, ArrowError> {
156 create_column(col_name, results, options)
157}
158
159pub fn print_batches(results: &[RecordBatch]) -> Result<(), ArrowError> {
161 println!("{}", pretty_format_batches(results)?);
162 Ok(())
163}
164
165pub fn print_columns(col_name: &str, results: &[ArrayRef]) -> Result<(), ArrowError> {
167 println!("{}", pretty_format_columns(col_name, results)?);
168 Ok(())
169}
170
171fn create_table(
173 schema_opt: Option<SchemaRef>,
174 results: &[RecordBatch],
175 options: &FormatOptions,
176) -> Result<Table, ArrowError> {
177 let mut table = Table::new();
178 table.load_preset("||--+-++| ++++++");
179
180 let schema_opt = schema_opt.or_else(|| {
181 if results.is_empty() {
182 None
183 } else {
184 Some(results[0].schema())
185 }
186 });
187
188 if let Some(schema) = &schema_opt {
189 let mut header = Vec::new();
190 for field in schema.fields() {
191 if options.types_info() {
192 header.push(Cell::new(format!(
193 "{}\n{}",
194 field.name(),
195 field.data_type()
196 )))
197 } else {
198 header.push(Cell::new(field.name()));
199 }
200 }
201 table.set_header(header);
202 }
203
204 if results.is_empty() {
205 return Ok(table);
206 }
207
208 for batch in results {
209 let schema = schema_opt.as_ref().unwrap_or(batch.schema_ref());
210
211 if batch.columns().len() != schema.fields().len() {
213 return Err(ArrowError::InvalidArgumentError(format!(
214 "Expected the same number of columns in a record batch ({}) as the number of fields ({}) in the schema",
215 batch.columns().len(),
216 schema.fields.len()
217 )));
218 }
219
220 let formatters = batch
221 .columns()
222 .iter()
223 .zip(schema.fields().iter())
224 .map(|(c, field)| make_array_formatter(c, options, Some(field)))
225 .collect::<Result<Vec<_>, ArrowError>>()?;
226
227 for row in 0..batch.num_rows() {
228 let mut cells = Vec::new();
229 for formatter in &formatters {
230 cells.push(Cell::new(formatter.value(row)));
231 }
232 table.add_row(cells);
233 }
234 }
235
236 Ok(table)
237}
238
239fn create_column(
240 field: &str,
241 columns: &[ArrayRef],
242 options: &FormatOptions,
243) -> Result<Table, ArrowError> {
244 let mut table = Table::new();
245 table.load_preset("||--+-++| ++++++");
246
247 if columns.is_empty() {
248 return Ok(table);
249 }
250
251 let header = vec![Cell::new(field)];
252 table.set_header(header);
253
254 for col in columns {
255 let formatter = match options.formatter_factory() {
256 None => ArrayFormatter::try_new(col.as_ref(), options)?,
257 Some(formatters) => formatters
258 .create_array_formatter(col.as_ref(), options, None)
259 .transpose()
260 .unwrap_or_else(|| ArrayFormatter::try_new(col.as_ref(), options))?,
261 };
262 for row in 0..col.len() {
263 let cells = vec![Cell::new(formatter.value(row))];
264 table.add_row(cells);
265 }
266 }
267
268 Ok(table)
269}
270
271#[cfg(test)]
272mod tests {
273 use std::collections::HashMap;
274 use std::fmt::Write;
275 use std::sync::Arc;
276
277 use arrow_array::builder::*;
278 use arrow_array::cast::AsArray;
279 use arrow_array::types::*;
280 use arrow_array::*;
281 use arrow_buffer::{IntervalDayTime, IntervalMonthDayNano, ScalarBuffer};
282 use arrow_schema::*;
283 use half::f16;
284
285 use crate::display::{
286 ArrayFormatterFactory, DisplayIndex, DurationFormat, array_value_to_string,
287 };
288
289 use super::*;
290
291 #[test]
292 fn test_pretty_format_batches() {
293 let schema = Arc::new(Schema::new(vec![
295 Field::new("a", DataType::Utf8, true),
296 Field::new("b", DataType::Int32, true),
297 ]));
298
299 let batch = RecordBatch::try_new(
301 schema,
302 vec![
303 Arc::new(array::StringArray::from(vec![
304 Some("a"),
305 Some("b"),
306 None,
307 Some("d"),
308 ])),
309 Arc::new(array::Int32Array::from(vec![
310 Some(1),
311 None,
312 Some(10),
313 Some(100),
314 ])),
315 ],
316 )
317 .unwrap();
318
319 let table = pretty_format_batches(&[batch]).unwrap().to_string();
320
321 insta::assert_snapshot!(table, @"
322 +---+-----+
323 | a | b |
324 +---+-----+
325 | a | 1 |
326 | b | |
327 | | 10 |
328 | d | 100 |
329 +---+-----+
330 ");
331 }
332
333 #[test]
334 fn test_pretty_format_columns() {
335 let columns = vec![
336 Arc::new(array::StringArray::from(vec![
337 Some("a"),
338 Some("b"),
339 None,
340 Some("d"),
341 ])) as ArrayRef,
342 Arc::new(array::StringArray::from(vec![Some("e"), None, Some("g")])),
343 ];
344
345 let table = pretty_format_columns("a", &columns).unwrap().to_string();
346
347 insta::assert_snapshot!(table, @"
348 +---+
349 | a |
350 +---+
351 | a |
352 | b |
353 | |
354 | d |
355 | e |
356 | |
357 | g |
358 +---+
359 ");
360 }
361
362 #[test]
363 fn test_pretty_format_null() {
364 let schema = Arc::new(Schema::new(vec![
365 Field::new("a", DataType::Utf8, true),
366 Field::new("b", DataType::Int32, true),
367 Field::new("c", DataType::Null, true),
368 ]));
369
370 let num_rows = 4;
371 let arrays = schema
372 .fields()
373 .iter()
374 .map(|f| new_null_array(f.data_type(), num_rows))
375 .collect();
376
377 let batch = RecordBatch::try_new(schema, arrays).unwrap();
379
380 let table = pretty_format_batches(&[batch]).unwrap().to_string();
381
382 insta::assert_snapshot!(table, @"
383 +---+---+---+
384 | a | b | c |
385 +---+---+---+
386 | | | |
387 | | | |
388 | | | |
389 | | | |
390 +---+---+---+
391 ");
392 }
393
394 #[test]
395 fn test_pretty_format_dictionary() {
396 let field = Field::new_dictionary("d1", DataType::Int32, DataType::Utf8, true);
398 let schema = Arc::new(Schema::new(vec![field]));
399
400 let mut builder = StringDictionaryBuilder::<Int32Type>::new();
401
402 builder.append_value("one");
403 builder.append_null();
404 builder.append_value("three");
405 let array = Arc::new(builder.finish());
406
407 let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
408
409 let table = pretty_format_batches(&[batch]).unwrap().to_string();
410
411 insta::assert_snapshot!(table, @"
412 +-------+
413 | d1 |
414 +-------+
415 | one |
416 | |
417 | three |
418 +-------+
419 ");
420 }
421
422 #[test]
423 fn test_pretty_format_fixed_size_list() {
424 let field_type =
426 DataType::FixedSizeList(Arc::new(Field::new_list_field(DataType::Int32, true)), 3);
427 let schema = Arc::new(Schema::new(vec![Field::new("d1", field_type, true)]));
428
429 let keys_builder = Int32Array::builder(3);
430 let mut builder = FixedSizeListBuilder::new(keys_builder, 3);
431
432 builder.values().append_slice(&[1, 2, 3]);
433 builder.append(true);
434 builder.values().append_slice(&[4, 5, 6]);
435 builder.append(false);
436 builder.values().append_slice(&[7, 8, 9]);
437 builder.append(true);
438
439 let array = Arc::new(builder.finish());
440
441 let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
442 let table = pretty_format_batches(&[batch]).unwrap().to_string();
443
444 insta::assert_snapshot!(table, @"
445 +-----------+
446 | d1 |
447 +-----------+
448 | [1, 2, 3] |
449 | |
450 | [7, 8, 9] |
451 +-----------+
452 ");
453 }
454
455 #[test]
456 fn test_pretty_format_string_view() {
457 let schema = Arc::new(Schema::new(vec![Field::new(
458 "d1",
459 DataType::Utf8View,
460 true,
461 )]));
462
463 let mut builder = StringViewBuilder::with_capacity(20);
465 builder.append_value("hello");
466 builder.append_null();
467 builder.append_value("longer than 12 bytes");
468 builder.append_value("another than 12 bytes");
469 builder.append_null();
470 builder.append_value("small");
471
472 let array: ArrayRef = Arc::new(builder.finish());
473 let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
474 let table = pretty_format_batches(&[batch]).unwrap().to_string();
475
476 insta::assert_snapshot!(table, @"
477 +-----------------------+
478 | d1 |
479 +-----------------------+
480 | hello |
481 | |
482 | longer than 12 bytes |
483 | another than 12 bytes |
484 | |
485 | small |
486 +-----------------------+
487 ");
488 }
489
490 #[test]
491 fn test_pretty_format_binary_view() {
492 let schema = Arc::new(Schema::new(vec![Field::new(
493 "d1",
494 DataType::BinaryView,
495 true,
496 )]));
497
498 let mut builder = BinaryViewBuilder::with_capacity(20);
500 builder.append_value(b"hello");
501 builder.append_null();
502 builder.append_value(b"longer than 12 bytes");
503 builder.append_value(b"another than 12 bytes");
504 builder.append_null();
505 builder.append_value(b"small");
506
507 let array: ArrayRef = Arc::new(builder.finish());
508 let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
509 let table = pretty_format_batches(&[batch]).unwrap().to_string();
510
511 insta::assert_snapshot!(table, @"
512 +--------------------------------------------+
513 | d1 |
514 +--------------------------------------------+
515 | 68656c6c6f |
516 | |
517 | 6c6f6e676572207468616e203132206279746573 |
518 | 616e6f74686572207468616e203132206279746573 |
519 | |
520 | 736d616c6c |
521 +--------------------------------------------+
522 ");
523 }
524
525 #[test]
526 fn test_pretty_format_fixed_size_binary() {
527 let field_type = DataType::FixedSizeBinary(3);
529 let schema = Arc::new(Schema::new(vec![Field::new("d1", field_type, true)]));
530
531 let mut builder = FixedSizeBinaryBuilder::with_capacity(3, 3);
532
533 builder.append_value([1, 2, 3]).unwrap();
534 builder.append_null();
535 builder.append_value([7, 8, 9]).unwrap();
536
537 let array = Arc::new(builder.finish());
538
539 let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
540 let table = pretty_format_batches(&[batch]).unwrap().to_string();
541
542 insta::assert_snapshot!(table, @"
543 +--------+
544 | d1 |
545 +--------+
546 | 010203 |
547 | |
548 | 070809 |
549 +--------+
550 ");
551 }
552
553 fn format_primitive_batch<T: ArrowPrimitiveType>(value: T::Native) -> String {
556 let mut builder = PrimitiveBuilder::<T>::with_capacity(10);
557 builder.append_value(value);
558 builder.append_null();
559 let array = builder.finish();
560 let schema = Arc::new(Schema::new(vec![Field::new(
561 "f",
562 array.data_type().clone(),
563 true,
564 )]));
565 let batch = RecordBatch::try_new(schema, vec![Arc::new(array)]).unwrap();
566 pretty_format_batches(&[batch])
567 .expect("formatting batches")
568 .to_string()
569 }
570
571 fn timestamp_batch<T: ArrowTimestampType>(timezone: &str, value: T::Native) -> RecordBatch {
572 let mut builder = PrimitiveBuilder::<T>::with_capacity(10);
573 builder.append_value(value);
574 builder.append_null();
575 let array = builder.finish();
576 let array = array.with_timezone(timezone);
577
578 let schema = Arc::new(Schema::new(vec![Field::new(
579 "f",
580 array.data_type().clone(),
581 true,
582 )]));
583 RecordBatch::try_new(schema, vec![Arc::new(array)]).unwrap()
584 }
585
586 #[test]
587 fn test_pretty_format_timestamp_second_with_fixed_offset_timezone() {
588 let batch = timestamp_batch::<TimestampSecondType>("+08:00", 11111111);
589 let table = pretty_format_batches(&[batch]).unwrap().to_string();
590
591 insta::assert_snapshot!(table, @"
592 +---------------------------+
593 | f |
594 +---------------------------+
595 | 1970-05-09T22:25:11+08:00 |
596 | |
597 +---------------------------+
598 ");
599 }
600
601 #[test]
602 fn test_pretty_format_timestamp_second() {
603 let table = format_primitive_batch::<TimestampSecondType>(11111111);
604 insta::assert_snapshot!(table, @"
605 +---------------------+
606 | f |
607 +---------------------+
608 | 1970-05-09T14:25:11 |
609 | |
610 +---------------------+
611 ");
612 }
613
614 #[test]
615 fn test_pretty_format_timestamp_millisecond() {
616 let table = format_primitive_batch::<TimestampMillisecondType>(11111111);
617 insta::assert_snapshot!(table, @"
618 +-------------------------+
619 | f |
620 +-------------------------+
621 | 1970-01-01T03:05:11.111 |
622 | |
623 +-------------------------+
624 ");
625 }
626
627 #[test]
628 fn test_pretty_format_timestamp_microsecond() {
629 let table = format_primitive_batch::<TimestampMicrosecondType>(11111111);
630 insta::assert_snapshot!(table, @"
631 +----------------------------+
632 | f |
633 +----------------------------+
634 | 1970-01-01T00:00:11.111111 |
635 | |
636 +----------------------------+
637 ");
638 }
639
640 #[test]
641 fn test_pretty_format_timestamp_nanosecond() {
642 let table = format_primitive_batch::<TimestampNanosecondType>(11111111);
643 insta::assert_snapshot!(table, @"
644 +-------------------------------+
645 | f |
646 +-------------------------------+
647 | 1970-01-01T00:00:00.011111111 |
648 | |
649 +-------------------------------+
650 ");
651 }
652
653 #[test]
654 fn test_pretty_format_date_32() {
655 let table = format_primitive_batch::<Date32Type>(1234);
656 insta::assert_snapshot!(table, @"
657 +------------+
658 | f |
659 +------------+
660 | 1973-05-19 |
661 | |
662 +------------+
663 ");
664 }
665
666 #[test]
667 fn test_pretty_format_date_64() {
668 let table = format_primitive_batch::<Date64Type>(1111111100000);
669 insta::assert_snapshot!(table, @"
670 +---------------------+
671 | f |
672 +---------------------+
673 | 2005-03-18T01:58:20 |
674 | |
675 +---------------------+
676 ");
677 }
678
679 #[test]
680 fn test_pretty_format_time_32_second() {
681 let table = format_primitive_batch::<Time32SecondType>(1111);
682 insta::assert_snapshot!(table, @"
683 +----------+
684 | f |
685 +----------+
686 | 00:18:31 |
687 | |
688 +----------+
689 ");
690 }
691
692 #[test]
693 fn test_pretty_format_time_32_millisecond() {
694 let table = format_primitive_batch::<Time32MillisecondType>(11111111);
695 insta::assert_snapshot!(table, @"
696 +--------------+
697 | f |
698 +--------------+
699 | 03:05:11.111 |
700 | |
701 +--------------+
702 ");
703 }
704
705 #[test]
706 fn test_pretty_format_time_64_microsecond() {
707 let table = format_primitive_batch::<Time64MicrosecondType>(11111111);
708 insta::assert_snapshot!(table, @"
709 +-----------------+
710 | f |
711 +-----------------+
712 | 00:00:11.111111 |
713 | |
714 +-----------------+
715 ");
716 }
717
718 #[test]
719 fn test_pretty_format_time_64_nanosecond() {
720 let table = format_primitive_batch::<Time64NanosecondType>(11111111);
721 insta::assert_snapshot!(table, @"
722 +--------------------+
723 | f |
724 +--------------------+
725 | 00:00:00.011111111 |
726 | |
727 +--------------------+
728 ");
729 }
730
731 #[test]
732 fn test_int_display() {
733 let array = Arc::new(Int32Array::from(vec![6, 3])) as ArrayRef;
734 insta::assert_snapshot!(array_value_to_string(&array, 0).unwrap(), @"6");
735 insta::assert_snapshot!(array_value_to_string(&array, 1).unwrap(), @"3");
736 }
737
738 #[test]
739 fn test_decimal_display() {
740 let precision = 10;
741 let scale = 2;
742
743 let array = [Some(101), None, Some(200), Some(3040)]
744 .into_iter()
745 .collect::<Decimal128Array>()
746 .with_precision_and_scale(precision, scale)
747 .unwrap();
748
749 let dm = Arc::new(array) as ArrayRef;
750
751 let schema = Arc::new(Schema::new(vec![Field::new(
752 "f",
753 dm.data_type().clone(),
754 true,
755 )]));
756
757 let batch = RecordBatch::try_new(schema, vec![dm]).unwrap();
758
759 let table = pretty_format_batches(&[batch]).unwrap().to_string();
760
761 insta::assert_snapshot!(table, @"
762 +-------+
763 | f |
764 +-------+
765 | 1.01 |
766 | |
767 | 2.00 |
768 | 30.40 |
769 +-------+
770 ");
771 }
772
773 #[test]
774 fn test_decimal_display_zero_scale() {
775 let precision = 5;
776 let scale = 0;
777
778 let array = [Some(101), None, Some(200), Some(3040)]
779 .into_iter()
780 .collect::<Decimal128Array>()
781 .with_precision_and_scale(precision, scale)
782 .unwrap();
783
784 let dm = Arc::new(array) as ArrayRef;
785
786 let schema = Arc::new(Schema::new(vec![Field::new(
787 "f",
788 dm.data_type().clone(),
789 true,
790 )]));
791
792 let batch = RecordBatch::try_new(schema, vec![dm]).unwrap();
793
794 let table = pretty_format_batches(&[batch]).unwrap().to_string();
795
796 insta::assert_snapshot!(table, @"
797 +------+
798 | f |
799 +------+
800 | 101 |
801 | |
802 | 200 |
803 | 3040 |
804 +------+
805 ");
806 }
807
808 #[test]
809 fn test_pretty_format_struct() {
810 let schema = Schema::new(vec![
811 Field::new_struct(
812 "c1",
813 vec![
814 Field::new("c11", DataType::Int32, true),
815 Field::new_struct(
816 "c12",
817 vec![Field::new("c121", DataType::Utf8, false)],
818 false,
819 ),
820 ],
821 false,
822 ),
823 Field::new("c2", DataType::Utf8, false),
824 ]);
825
826 let c1 = StructArray::from(vec![
827 (
828 Arc::new(Field::new("c11", DataType::Int32, true)),
829 Arc::new(Int32Array::from(vec![Some(1), None, Some(5)])) as ArrayRef,
830 ),
831 (
832 Arc::new(Field::new_struct(
833 "c12",
834 vec![Field::new("c121", DataType::Utf8, false)],
835 false,
836 )),
837 Arc::new(StructArray::from(vec![(
838 Arc::new(Field::new("c121", DataType::Utf8, false)),
839 Arc::new(StringArray::from(vec![Some("e"), Some("f"), Some("g")])) as ArrayRef,
840 )])) as ArrayRef,
841 ),
842 ]);
843 let c2 = StringArray::from(vec![Some("a"), Some("b"), Some("c")]);
844
845 let batch =
846 RecordBatch::try_new(Arc::new(schema), vec![Arc::new(c1), Arc::new(c2)]).unwrap();
847
848 let table = pretty_format_batches(&[batch]).unwrap().to_string();
849
850 insta::assert_snapshot!(table, @"
851 +--------------------------+----+
852 | c1 | c2 |
853 +--------------------------+----+
854 | {c11: 1, c12: {c121: e}} | a |
855 | {c11: , c12: {c121: f}} | b |
856 | {c11: 5, c12: {c121: g}} | c |
857 +--------------------------+----+
858 ");
859 }
860
861 #[test]
862 fn test_pretty_format_dense_union() {
863 let mut builder = UnionBuilder::new_dense();
864 builder.append::<Int32Type>("a", 1).unwrap();
865 builder.append::<Float64Type>("b", 3.2234).unwrap();
866 builder.append_null::<Float64Type>("b").unwrap();
867 builder.append_null::<Int32Type>("a").unwrap();
868 let union = builder.build().unwrap();
869
870 let schema = Schema::new(vec![Field::new_union(
871 "Teamsters",
872 vec![0, 1],
873 vec![
874 Field::new("a", DataType::Int32, false),
875 Field::new("b", DataType::Float64, false),
876 ],
877 UnionMode::Dense,
878 )]);
879
880 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(union)]).unwrap();
881 let table = pretty_format_batches(&[batch]).unwrap().to_string();
882
883 insta::assert_snapshot!(table, @"
884 +------------+
885 | Teamsters |
886 +------------+
887 | {a=1} |
888 | {b=3.2234} |
889 | {b=} |
890 | {a=} |
891 +------------+
892 ");
893 }
894
895 #[test]
896 fn test_pretty_format_sparse_union() {
897 let mut builder = UnionBuilder::new_sparse();
898 builder.append::<Int32Type>("a", 1).unwrap();
899 builder.append::<Float64Type>("b", 3.2234).unwrap();
900 builder.append_null::<Float64Type>("b").unwrap();
901 builder.append_null::<Int32Type>("a").unwrap();
902 let union = builder.build().unwrap();
903
904 let schema = Schema::new(vec![Field::new_union(
905 "Teamsters",
906 vec![0, 1],
907 vec![
908 Field::new("a", DataType::Int32, false),
909 Field::new("b", DataType::Float64, false),
910 ],
911 UnionMode::Sparse,
912 )]);
913
914 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(union)]).unwrap();
915 let table = pretty_format_batches(&[batch]).unwrap().to_string();
916
917 insta::assert_snapshot!(table, @"
918 +------------+
919 | Teamsters |
920 +------------+
921 | {a=1} |
922 | {b=3.2234} |
923 | {b=} |
924 | {a=} |
925 +------------+
926 ");
927 }
928
929 #[test]
930 fn test_pretty_format_nested_union() {
931 let mut builder = UnionBuilder::new_dense();
933 builder.append::<Int32Type>("b", 1).unwrap();
934 builder.append::<Float64Type>("c", 3.2234).unwrap();
935 builder.append_null::<Float64Type>("c").unwrap();
936 builder.append_null::<Int32Type>("b").unwrap();
937 builder.append_null::<Float64Type>("c").unwrap();
938 let inner = builder.build().unwrap();
939
940 let inner_field = Field::new_union(
941 "European Union",
942 vec![0, 1],
943 vec![
944 Field::new("b", DataType::Int32, false),
945 Field::new("c", DataType::Float64, false),
946 ],
947 UnionMode::Dense,
948 );
949
950 let a_array = Int32Array::from(vec![None, None, None, Some(1234), Some(23)]);
952 let type_ids = [1, 1, 0, 0, 1].into_iter().collect::<ScalarBuffer<i8>>();
953
954 let children = vec![Arc::new(a_array) as Arc<dyn Array>, Arc::new(inner)];
955
956 let union_fields = [
957 (0, Arc::new(Field::new("a", DataType::Int32, true))),
958 (1, Arc::new(inner_field.clone())),
959 ]
960 .into_iter()
961 .collect();
962
963 let outer = UnionArray::try_new(union_fields, type_ids, None, children).unwrap();
964
965 let schema = Schema::new(vec![Field::new_union(
966 "Teamsters",
967 vec![0, 1],
968 vec![Field::new("a", DataType::Int32, true), inner_field],
969 UnionMode::Sparse,
970 )]);
971
972 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(outer)]).unwrap();
973 let table = pretty_format_batches(&[batch]).unwrap().to_string();
974
975 insta::assert_snapshot!(table, @"
976 +-----------------------------+
977 | Teamsters |
978 +-----------------------------+
979 | {European Union={b=1}} |
980 | {European Union={c=3.2234}} |
981 | {a=} |
982 | {a=1234} |
983 | {European Union={c=}} |
984 +-----------------------------+
985 ");
986 }
987
988 #[test]
989 fn test_writing_formatted_batches() {
990 let schema = Arc::new(Schema::new(vec![
992 Field::new("a", DataType::Utf8, true),
993 Field::new("b", DataType::Int32, true),
994 ]));
995
996 let batch = RecordBatch::try_new(
998 schema,
999 vec![
1000 Arc::new(array::StringArray::from(vec![
1001 Some("a"),
1002 Some("b"),
1003 None,
1004 Some("d"),
1005 ])),
1006 Arc::new(array::Int32Array::from(vec![
1007 Some(1),
1008 None,
1009 Some(10),
1010 Some(100),
1011 ])),
1012 ],
1013 )
1014 .unwrap();
1015
1016 let table = pretty_format_batches(&[batch]).unwrap().to_string();
1017
1018 insta::assert_snapshot!(table, @"
1019 +---+-----+
1020 | a | b |
1021 +---+-----+
1022 | a | 1 |
1023 | b | |
1024 | | 10 |
1025 | d | 100 |
1026 +---+-----+
1027 ");
1028 }
1029
1030 #[test]
1031 #[cfg_attr(miri, ignore)] fn test_float16_display() {
1033 let values = vec![
1034 Some(f16::from_f32(f32::NAN)),
1035 Some(f16::from_f32(4.0)),
1036 Some(f16::from_f32(f32::NEG_INFINITY)),
1037 ];
1038 let array = Arc::new(values.into_iter().collect::<Float16Array>()) as ArrayRef;
1039
1040 let schema = Arc::new(Schema::new(vec![Field::new(
1041 "f16",
1042 array.data_type().clone(),
1043 true,
1044 )]));
1045
1046 let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
1047
1048 let table = pretty_format_batches(&[batch]).unwrap().to_string();
1049
1050 insta::assert_snapshot!(table, @"
1051 +------+
1052 | f16 |
1053 +------+
1054 | NaN |
1055 | 4 |
1056 | -inf |
1057 +------+
1058 ");
1059 }
1060
1061 #[test]
1062 fn test_pretty_format_interval_day_time() {
1063 let arr = Arc::new(arrow_array::IntervalDayTimeArray::from(vec![
1064 Some(IntervalDayTime::new(-1, -600_000)),
1065 Some(IntervalDayTime::new(0, -1001)),
1066 Some(IntervalDayTime::new(0, -1)),
1067 Some(IntervalDayTime::new(0, 1)),
1068 Some(IntervalDayTime::new(0, 10)),
1069 Some(IntervalDayTime::new(0, 100)),
1070 Some(IntervalDayTime::new(0, 0)),
1071 ]));
1072
1073 let schema = Arc::new(Schema::new(vec![Field::new(
1074 "IntervalDayTime",
1075 arr.data_type().clone(),
1076 true,
1077 )]));
1078
1079 let batch = RecordBatch::try_new(schema, vec![arr]).unwrap();
1080
1081 let table = pretty_format_batches(&[batch]).unwrap().to_string();
1082
1083 insta::assert_snapshot!(table, @"
1084 +------------------+
1085 | IntervalDayTime |
1086 +------------------+
1087 | -1 days -10 mins |
1088 | -1.001 secs |
1089 | -0.001 secs |
1090 | 0.001 secs |
1091 | 0.010 secs |
1092 | 0.100 secs |
1093 | 0 secs |
1094 +------------------+
1095 ");
1096 }
1097
1098 #[test]
1099 fn test_pretty_format_interval_month_day_nano_array() {
1100 let arr = Arc::new(arrow_array::IntervalMonthDayNanoArray::from(vec![
1101 Some(IntervalMonthDayNano::new(-1, -1, -600_000_000_000)),
1102 Some(IntervalMonthDayNano::new(0, 0, -1_000_000_001)),
1103 Some(IntervalMonthDayNano::new(0, 0, -1)),
1104 Some(IntervalMonthDayNano::new(0, 0, 1)),
1105 Some(IntervalMonthDayNano::new(0, 0, 10)),
1106 Some(IntervalMonthDayNano::new(0, 0, 100)),
1107 Some(IntervalMonthDayNano::new(0, 0, 1_000)),
1108 Some(IntervalMonthDayNano::new(0, 0, 10_000)),
1109 Some(IntervalMonthDayNano::new(0, 0, 100_000)),
1110 Some(IntervalMonthDayNano::new(0, 0, 1_000_000)),
1111 Some(IntervalMonthDayNano::new(0, 0, 10_000_000)),
1112 Some(IntervalMonthDayNano::new(0, 0, 100_000_000)),
1113 Some(IntervalMonthDayNano::new(0, 0, 1_000_000_000)),
1114 Some(IntervalMonthDayNano::new(0, 0, 0)),
1115 ]));
1116
1117 let schema = Arc::new(Schema::new(vec![Field::new(
1118 "IntervalMonthDayNano",
1119 arr.data_type().clone(),
1120 true,
1121 )]));
1122
1123 let batch = RecordBatch::try_new(schema, vec![arr]).unwrap();
1124
1125 let table = pretty_format_batches(&[batch]).unwrap().to_string();
1126
1127 insta::assert_snapshot!(table, @"
1128 +--------------------------+
1129 | IntervalMonthDayNano |
1130 +--------------------------+
1131 | -1 mons -1 days -10 mins |
1132 | -1.000000001 secs |
1133 | -0.000000001 secs |
1134 | 0.000000001 secs |
1135 | 0.000000010 secs |
1136 | 0.000000100 secs |
1137 | 0.000001000 secs |
1138 | 0.000010000 secs |
1139 | 0.000100000 secs |
1140 | 0.001000000 secs |
1141 | 0.010000000 secs |
1142 | 0.100000000 secs |
1143 | 1.000000000 secs |
1144 | 0 secs |
1145 +--------------------------+
1146 ");
1147 }
1148
1149 #[test]
1150 fn test_format_options() {
1151 let options = FormatOptions::default()
1152 .with_null("null")
1153 .with_types_info(true);
1154 let int32_array = Int32Array::from(vec![Some(1), Some(2), None, Some(3), Some(4)]);
1155 let string_array =
1156 StringArray::from(vec![Some("foo"), Some("bar"), None, Some("baz"), None]);
1157
1158 let batch = RecordBatch::try_from_iter([
1159 ("my_int32_name", Arc::new(int32_array) as _),
1160 ("my_string_name", Arc::new(string_array) as _),
1161 ])
1162 .unwrap();
1163
1164 let column = pretty_format_columns_with_options(
1165 "my_column_name",
1166 &[batch.column(0).clone()],
1167 &options,
1168 )
1169 .unwrap()
1170 .to_string();
1171
1172 insta::assert_snapshot!(column, @"
1173 +----------------+
1174 | my_column_name |
1175 +----------------+
1176 | 1 |
1177 | 2 |
1178 | null |
1179 | 3 |
1180 | 4 |
1181 +----------------+
1182 ");
1183
1184 let table = pretty_format_batches_with_options(&[batch], &options)
1185 .unwrap()
1186 .to_string();
1187
1188 insta::assert_snapshot!(table, @"
1189 +---------------+----------------+
1190 | my_int32_name | my_string_name |
1191 | Int32 | Utf8 |
1192 +---------------+----------------+
1193 | 1 | foo |
1194 | 2 | bar |
1195 | null | null |
1196 | 3 | baz |
1197 | 4 | null |
1198 +---------------+----------------+
1199 ");
1200 }
1201
1202 #[test]
1203 fn duration_pretty_and_iso_extremes() {
1204 let arr = DurationSecondArray::from(vec![Some(i64::MIN), Some(i64::MAX), Some(3661), None]);
1206 let array: ArrayRef = Arc::new(arr);
1207
1208 let opts = FormatOptions::default().with_null("null");
1210 let opts = opts.with_duration_format(DurationFormat::Pretty);
1211 let pretty =
1212 pretty_format_columns_with_options("pretty", std::slice::from_ref(&array), &opts)
1213 .unwrap()
1214 .to_string();
1215
1216 insta::assert_snapshot!(pretty, @"
1217 +------------------------------+
1218 | pretty |
1219 +------------------------------+
1220 | <invalid> |
1221 | <invalid> |
1222 | 0 days 1 hours 1 mins 1 secs |
1223 | null |
1224 +------------------------------+
1225 ");
1226
1227 let opts_iso = FormatOptions::default()
1229 .with_null("null")
1230 .with_duration_format(DurationFormat::ISO8601);
1231 let iso = pretty_format_columns_with_options("iso", &[array], &opts_iso)
1232 .unwrap()
1233 .to_string();
1234
1235 insta::assert_snapshot!(iso, @"
1236 +-----------+
1237 | iso |
1238 +-----------+
1239 | <invalid> |
1240 | <invalid> |
1241 | PT3661S |
1242 | null |
1243 +-----------+
1244 ");
1245 }
1246
1247 #[derive(Debug)]
1253 struct TestFormatters {}
1254
1255 impl ArrayFormatterFactory for TestFormatters {
1256 fn create_array_formatter<'formatter>(
1257 &self,
1258 array: &'formatter dyn Array,
1259 options: &FormatOptions<'formatter>,
1260 field: Option<&'formatter Field>,
1261 ) -> Result<Option<ArrayFormatter<'formatter>>, ArrowError> {
1262 if field
1263 .map(|f| f.extension_type_name() == Some("my_money"))
1264 .unwrap_or(false)
1265 {
1266 let array = array.as_primitive();
1268 let display_index = Box::new(MyMoneyFormatter {
1269 array,
1270 options: options.clone(),
1271 });
1272 return Ok(Some(ArrayFormatter::new(display_index, options.safe())));
1273 }
1274
1275 if array.data_type() == &DataType::Int32 {
1276 let array = array.as_primitive();
1277 let display_index = Box::new(MyInt32Formatter {
1278 array,
1279 options: options.clone(),
1280 });
1281 return Ok(Some(ArrayFormatter::new(display_index, options.safe())));
1282 }
1283
1284 Ok(None)
1285 }
1286 }
1287
1288 struct MyMoneyFormatter<'a> {
1290 array: &'a Int32Array,
1291 options: FormatOptions<'a>,
1292 }
1293
1294 impl<'a> DisplayIndex for MyMoneyFormatter<'a> {
1295 fn write(&self, idx: usize, f: &mut dyn Write) -> crate::display::FormatResult {
1296 match self.array.is_valid(idx) {
1297 true => write!(f, "{} €", self.array.value(idx))?,
1298 false => write!(f, "{}", self.options.null())?,
1299 }
1300
1301 Ok(())
1302 }
1303 }
1304
1305 struct MyInt32Formatter<'a> {
1307 array: &'a Int32Array,
1308 options: FormatOptions<'a>,
1309 }
1310
1311 impl<'a> DisplayIndex for MyInt32Formatter<'a> {
1312 fn write(&self, idx: usize, f: &mut dyn Write) -> crate::display::FormatResult {
1313 match self.array.is_valid(idx) {
1314 true => write!(f, "{} (32-Bit)", self.array.value(idx))?,
1315 false => write!(f, "{}", self.options.null())?,
1316 }
1317
1318 Ok(())
1319 }
1320 }
1321
1322 #[test]
1323 fn test_format_batches_with_custom_formatters() {
1324 let options = FormatOptions::new()
1326 .with_null("<NULL>")
1327 .with_formatter_factory(Some(&TestFormatters {}));
1328 let money_metadata = HashMap::from([(
1329 extension::EXTENSION_TYPE_NAME_KEY.to_owned(),
1330 "my_money".to_owned(),
1331 )]);
1332 let schema = Arc::new(Schema::new(vec![
1333 Field::new("income", DataType::Int32, true).with_metadata(money_metadata.clone()),
1334 ]));
1335
1336 let batch = RecordBatch::try_new(
1338 schema,
1339 vec![Arc::new(array::Int32Array::from(vec![
1340 Some(1),
1341 None,
1342 Some(10),
1343 Some(100),
1344 ]))],
1345 )
1346 .unwrap();
1347
1348 let table = pretty_format_batches_with_options(&[batch], &options)
1349 .unwrap()
1350 .to_string();
1351
1352 insta::assert_snapshot!(table, @"
1353 +--------+
1354 | income |
1355 +--------+
1356 | 1 € |
1357 | <NULL> |
1358 | 10 € |
1359 | 100 € |
1360 +--------+
1361 ");
1362 }
1363
1364 #[test]
1365 fn test_format_batches_with_custom_formatters_multi_nested_list() {
1366 let options = FormatOptions::new()
1368 .with_null("<NULL>")
1369 .with_formatter_factory(Some(&TestFormatters {}));
1370 let money_metadata = HashMap::from([(
1371 extension::EXTENSION_TYPE_NAME_KEY.to_owned(),
1372 "my_money".to_owned(),
1373 )]);
1374 let nested_field = Arc::new(
1375 Field::new_list_field(DataType::Int32, true).with_metadata(money_metadata.clone()),
1376 );
1377
1378 let inner_list = ListBuilder::new(Int32Builder::new()).with_field(nested_field);
1380 let mut outer_list = FixedSizeListBuilder::new(inner_list, 2);
1381 outer_list.values().append_value([Some(1)]);
1382 outer_list.values().append_null();
1383 outer_list.append(true);
1384 outer_list.values().append_value([Some(2), Some(8)]);
1385 outer_list
1386 .values()
1387 .append_value([Some(50), Some(25), Some(25)]);
1388 outer_list.append(true);
1389 let outer_list = outer_list.finish();
1390
1391 let schema = Arc::new(Schema::new(vec![Field::new(
1392 "income",
1393 outer_list.data_type().clone(),
1394 true,
1395 )]));
1396
1397 let batch = RecordBatch::try_new(schema, vec![Arc::new(outer_list)]).unwrap();
1399
1400 let table = pretty_format_batches_with_options(&[batch], &options)
1401 .unwrap()
1402 .to_string();
1403
1404 insta::assert_snapshot!(table, @"
1405 +----------------------------------+
1406 | income |
1407 +----------------------------------+
1408 | [[1 €], <NULL>] |
1409 | [[2 €, 8 €], [50 €, 25 €, 25 €]] |
1410 +----------------------------------+
1411 ");
1412 }
1413
1414 #[test]
1415 fn test_format_batches_with_custom_formatters_nested_struct() {
1416 let options = FormatOptions::new()
1418 .with_null("<NULL>")
1419 .with_formatter_factory(Some(&TestFormatters {}));
1420 let money_metadata = HashMap::from([(
1421 extension::EXTENSION_TYPE_NAME_KEY.to_owned(),
1422 "my_money".to_owned(),
1423 )]);
1424 let fields = Fields::from(vec![
1425 Field::new("name", DataType::Utf8, true),
1426 Field::new("income", DataType::Int32, true).with_metadata(money_metadata.clone()),
1427 ]);
1428
1429 let schema = Arc::new(Schema::new(vec![Field::new(
1430 "income",
1431 DataType::Struct(fields.clone()),
1432 true,
1433 )]));
1434
1435 let mut nested_data = StructBuilder::new(
1437 fields,
1438 vec![
1439 Box::new(StringBuilder::new()),
1440 Box::new(Int32Builder::new()),
1441 ],
1442 );
1443 nested_data
1444 .field_builder::<StringBuilder>(0)
1445 .unwrap()
1446 .extend([Some("Gimli"), Some("Legolas"), Some("Aragorn")]);
1447 nested_data
1448 .field_builder::<Int32Builder>(1)
1449 .unwrap()
1450 .extend([Some(10), None, Some(30)]);
1451 nested_data.append(true);
1452 nested_data.append(true);
1453 nested_data.append(true);
1454
1455 let batch = RecordBatch::try_new(schema, vec![Arc::new(nested_data.finish())]).unwrap();
1457
1458 let table = pretty_format_batches_with_options(&[batch], &options)
1459 .unwrap()
1460 .to_string();
1461
1462 insta::assert_snapshot!(table, @"
1463 +---------------------------------+
1464 | income |
1465 +---------------------------------+
1466 | {name: Gimli, income: 10 €} |
1467 | {name: Legolas, income: <NULL>} |
1468 | {name: Aragorn, income: 30 €} |
1469 +---------------------------------+
1470 ");
1471 }
1472
1473 #[test]
1474 fn test_format_batches_with_custom_formatters_nested_map() {
1475 let options = FormatOptions::new()
1477 .with_null("<NULL>")
1478 .with_formatter_factory(Some(&TestFormatters {}));
1479 let money_metadata = HashMap::from([(
1480 extension::EXTENSION_TYPE_NAME_KEY.to_owned(),
1481 "my_money".to_owned(),
1482 )]);
1483
1484 let mut array = MapBuilder::<StringBuilder, Int32Builder>::new(
1485 None,
1486 StringBuilder::new(),
1487 Int32Builder::new(),
1488 )
1489 .with_values_field(
1490 Field::new("my_values", DataType::Int32, true).with_metadata(money_metadata.clone()),
1491 );
1492 array
1493 .keys()
1494 .extend([Some("Gimli"), Some("Legolas"), Some("Aragorn")]);
1495 array.values().extend([Some(10), None, Some(30)]);
1496 array.append(true).unwrap();
1497 let array = array.finish();
1498
1499 let schema = Arc::new(Schema::new(vec![Field::new(
1501 "income",
1502 array.data_type().clone(),
1503 true,
1504 )]));
1505 let batch = RecordBatch::try_new(schema, vec![Arc::new(array)]).unwrap();
1506
1507 let table = pretty_format_batches_with_options(&[batch], &options)
1508 .unwrap()
1509 .to_string();
1510
1511 insta::assert_snapshot!(table, @"
1512 +-----------------------------------------------+
1513 | income |
1514 +-----------------------------------------------+
1515 | {Gimli: 10 €, Legolas: <NULL>, Aragorn: 30 €} |
1516 +-----------------------------------------------+
1517 ");
1518 }
1519
1520 #[test]
1521 fn test_format_batches_with_custom_formatters_nested_union() {
1522 let options = FormatOptions::new()
1524 .with_null("<NULL>")
1525 .with_formatter_factory(Some(&TestFormatters {}));
1526 let money_metadata = HashMap::from([(
1527 extension::EXTENSION_TYPE_NAME_KEY.to_owned(),
1528 "my_money".to_owned(),
1529 )]);
1530 let fields = UnionFields::try_new(
1531 vec![0],
1532 vec![Field::new("income", DataType::Int32, true).with_metadata(money_metadata.clone())],
1533 )
1534 .unwrap();
1535
1536 let mut array_builder = UnionBuilder::new_dense();
1538 array_builder.append::<Int32Type>("income", 1).unwrap();
1539 let (_, type_ids, offsets, children) = array_builder.build().unwrap().into_parts();
1540 let array = UnionArray::try_new(fields, type_ids, offsets, children).unwrap();
1541
1542 let schema = Arc::new(Schema::new(vec![Field::new(
1543 "income",
1544 array.data_type().clone(),
1545 true,
1546 )]));
1547
1548 let batch = RecordBatch::try_new(schema, vec![Arc::new(array)]).unwrap();
1550
1551 let table = pretty_format_batches_with_options(&[batch], &options)
1552 .unwrap()
1553 .to_string();
1554
1555 insta::assert_snapshot!(table, @"
1556 +--------------+
1557 | income |
1558 +--------------+
1559 | {income=1 €} |
1560 +--------------+
1561 ");
1562 }
1563
1564 #[test]
1565 fn test_format_batches_with_custom_formatters_custom_schema_overrules_batch_schema() {
1566 let options = FormatOptions::new().with_formatter_factory(Some(&TestFormatters {}));
1568 let money_metadata = HashMap::from([(
1569 extension::EXTENSION_TYPE_NAME_KEY.to_owned(),
1570 "my_money".to_owned(),
1571 )]);
1572 let schema = Arc::new(Schema::new(vec![
1573 Field::new("income", DataType::Int32, true).with_metadata(money_metadata.clone()),
1574 ]));
1575
1576 let batch = RecordBatch::try_new(
1578 schema,
1579 vec![Arc::new(array::Int32Array::from(vec![
1580 Some(1),
1581 None,
1582 Some(10),
1583 Some(100),
1584 ]))],
1585 )
1586 .unwrap();
1587
1588 let table = create_table(
1589 Some(Arc::new(Schema::new(vec![Field::new(
1591 "income",
1592 DataType::Int32,
1593 true,
1594 )]))),
1595 &[batch],
1596 &options,
1597 )
1598 .unwrap()
1599 .to_string();
1600
1601 insta::assert_snapshot!(table, @"
1603 +--------------+
1604 | income |
1605 +--------------+
1606 | 1 (32-Bit) |
1607 | |
1608 | 10 (32-Bit) |
1609 | 100 (32-Bit) |
1610 +--------------+
1611 ");
1612 }
1613
1614 #[test]
1615 fn test_format_column_with_custom_formatters() {
1616 let array = Arc::new(array::Int32Array::from(vec![
1618 Some(1),
1619 None,
1620 Some(10),
1621 Some(100),
1622 ]));
1623
1624 let table = pretty_format_columns_with_options(
1625 "income",
1626 &[array],
1627 &FormatOptions::default().with_formatter_factory(Some(&TestFormatters {})),
1628 )
1629 .unwrap()
1630 .to_string();
1631
1632 insta::assert_snapshot!(table, @"
1633 +--------------+
1634 | income |
1635 +--------------+
1636 | 1 (32-Bit) |
1637 | |
1638 | 10 (32-Bit) |
1639 | 100 (32-Bit) |
1640 +--------------+
1641 ");
1642 }
1643
1644 #[test]
1645 fn test_pretty_format_batches_with_schema_with_wrong_number_of_fields() {
1646 let schema_a = Arc::new(Schema::new(vec![
1647 Field::new("a", DataType::Int32, true),
1648 Field::new("b", DataType::Utf8, true),
1649 ]));
1650 let schema_b = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, true)]));
1651
1652 let batch = RecordBatch::try_new(
1654 schema_b,
1655 vec![Arc::new(array::Int32Array::from(vec![
1656 Some(1),
1657 None,
1658 Some(10),
1659 Some(100),
1660 ]))],
1661 )
1662 .unwrap();
1663
1664 let error = pretty_format_batches_with_schema(schema_a, &[batch])
1665 .err()
1666 .unwrap();
1667 insta::assert_snapshot!(error, @"Invalid argument error: Expected the same number of columns in a record batch (1) as the number of fields (2) in the schema");
1668 }
1669
1670 #[test]
1671 fn test_quoted_strings() {
1672 let schema = Arc::new(Schema::new(vec![Field::new(
1673 "strings",
1674 DataType::Utf8,
1675 true,
1676 )]));
1677
1678 let string_array = StringArray::from(vec![
1679 Some("hello"),
1680 Some("world"),
1681 Some(""),
1682 Some("tab\there"),
1683 Some("newline\ntest"),
1684 Some("quote\"test"),
1685 Some("backslash\\test"),
1686 None,
1687 ]);
1688
1689 let batch = RecordBatch::try_new(schema.clone(), vec![Arc::new(string_array)]).unwrap();
1690
1691 let options_none = FormatOptions::new().with_null("NULL");
1692 let table = pretty_format_batches_with_options(std::slice::from_ref(&batch), &options_none)
1693 .unwrap()
1694 .to_string();
1695
1696 insta::assert_snapshot!(table, @"
1697 +----------------+
1698 | strings |
1699 +----------------+
1700 | hello |
1701 | world |
1702 | |
1703 | tab here |
1704 | newline |
1705 | test |
1706 | quote\"test |
1707 | backslash\\test |
1708 | NULL |
1709 +----------------+
1710 ");
1711
1712 let options_quoted = FormatOptions::new()
1713 .with_null("NULL")
1714 .with_quoted_strings(true);
1715
1716 let table = pretty_format_batches_with_options(&[batch], &options_quoted)
1717 .unwrap()
1718 .to_string();
1719
1720 insta::assert_snapshot!(table, @r#"
1721 +-------------------+
1722 | strings |
1723 +-------------------+
1724 | "hello" |
1725 | "world" |
1726 | "" |
1727 | "tab\there" |
1728 | "newline\ntest" |
1729 | "quote\"test" |
1730 | "backslash\\test" |
1731 | NULL |
1732 +-------------------+
1733 "#);
1734 }
1735
1736 #[test]
1737 fn test_string_view_quoted() {
1738 let schema = Arc::new(Schema::new(vec![Field::new(
1739 "view_strings",
1740 DataType::Utf8View,
1741 true,
1742 )]));
1743
1744 let mut builder = StringViewBuilder::new();
1745 builder.append_value("hello");
1746 builder.append_null();
1747 builder.append_value("quote\"test");
1748
1749 let array: ArrayRef = Arc::new(builder.finish());
1750 let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
1751
1752 let options = FormatOptions::new().with_quoted_strings(true);
1753
1754 let table = pretty_format_batches_with_options(&[batch], &options)
1755 .unwrap()
1756 .to_string();
1757
1758 insta::assert_snapshot!(table, @"
1759 +---------------+
1760 | view_strings |
1761 +---------------+
1762 | \"hello\" |
1763 | |
1764 | \"quote\\\"test\" |
1765 +---------------+
1766 ");
1767 }
1768
1769 #[test]
1770 fn test_quoted_strings_in_struct() {
1771 let string_builder = StringBuilder::new();
1772 let mut name_builder = string_builder;
1773 name_builder.append_value("Alice");
1774 name_builder.append_value("");
1775 name_builder.append_value("Bob");
1776
1777 let fields = vec![Field::new("name", DataType::Utf8, false)];
1778 let mut struct_builder = StructBuilder::new(fields, vec![Box::new(name_builder)]);
1779 struct_builder.append(true);
1780 struct_builder.append(true);
1781 struct_builder.append(true);
1782
1783 let struct_array = struct_builder.finish();
1784
1785 let schema = Arc::new(Schema::new(vec![Field::new(
1786 "person",
1787 struct_array.data_type().clone(),
1788 false,
1789 )]));
1790
1791 let batch = RecordBatch::try_new(schema, vec![Arc::new(struct_array)]).unwrap();
1792
1793 let options_none = FormatOptions::new();
1794 let table = pretty_format_batches_with_options(std::slice::from_ref(&batch), &options_none)
1795 .unwrap()
1796 .to_string();
1797
1798 insta::assert_snapshot!(table, @"
1799 +---------------+
1800 | person |
1801 +---------------+
1802 | {name: Alice} |
1803 | {name: } |
1804 | {name: Bob} |
1805 +---------------+
1806 ");
1807
1808 let options_quoted = FormatOptions::new().with_quoted_strings(true);
1809 let table = pretty_format_batches_with_options(&[batch], &options_quoted)
1810 .unwrap()
1811 .to_string();
1812
1813 insta::assert_snapshot!(table, @"
1814 +-----------------+
1815 | person |
1816 +-----------------+
1817 | {name: \"Alice\"} |
1818 | {name: \"\"} |
1819 | {name: \"Bob\"} |
1820 +-----------------+
1821 ");
1822 }
1823}