parquet/schema/
printer.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//! Parquet schema printer.
19//! Provides methods to print Parquet file schema and list file metadata.
20//!
21//! # Example
22//!
23//! ```rust
24//! use parquet::{
25//!     file::reader::{FileReader, SerializedFileReader},
26//!     schema::printer::{print_file_metadata, print_parquet_metadata, print_schema},
27//! };
28//! use std::{fs::File, path::Path};
29//!
30//! // Open a file
31//! let path = Path::new("test.parquet");
32//! if let Ok(file) = File::open(&path) {
33//!     let reader = SerializedFileReader::new(file).unwrap();
34//!     let parquet_metadata = reader.metadata();
35//!
36//!     print_parquet_metadata(&mut std::io::stdout(), &parquet_metadata);
37//!     print_file_metadata(&mut std::io::stdout(), &parquet_metadata.file_metadata());
38//!
39//!     print_schema(
40//!         &mut std::io::stdout(),
41//!         &parquet_metadata.file_metadata().schema(),
42//!     );
43//! }
44//! ```
45
46use std::{fmt, io};
47
48use crate::basic::{ConvertedType, LogicalType, TimeUnit, Type as PhysicalType};
49use crate::file::metadata::{ColumnChunkMetaData, FileMetaData, ParquetMetaData, RowGroupMetaData};
50use crate::schema::types::Type;
51
52/// Prints Parquet metadata [`ParquetMetaData`] information.
53#[allow(unused_must_use)]
54pub fn print_parquet_metadata(out: &mut dyn io::Write, metadata: &ParquetMetaData) {
55    print_file_metadata(out, metadata.file_metadata());
56    writeln!(out);
57    writeln!(out);
58    writeln!(out, "num of row groups: {}", metadata.num_row_groups());
59    writeln!(out, "row groups:");
60    writeln!(out);
61    for (i, rg) in metadata.row_groups().iter().enumerate() {
62        writeln!(out, "row group {i}:");
63        print_dashes(out, 80);
64        print_row_group_metadata(out, rg);
65    }
66}
67
68/// Prints file metadata [`FileMetaData`] information.
69#[allow(unused_must_use)]
70pub fn print_file_metadata(out: &mut dyn io::Write, file_metadata: &FileMetaData) {
71    writeln!(out, "version: {}", file_metadata.version());
72    writeln!(out, "num of rows: {}", file_metadata.num_rows());
73    if let Some(created_by) = file_metadata.created_by().as_ref() {
74        writeln!(out, "created by: {created_by}");
75    }
76    if let Some(metadata) = file_metadata.key_value_metadata() {
77        writeln!(out, "metadata:");
78        for kv in metadata.iter() {
79            writeln!(
80                out,
81                "  {}: {}",
82                &kv.key,
83                kv.value.as_ref().unwrap_or(&"".to_owned())
84            );
85        }
86    }
87    let schema = file_metadata.schema();
88    print_schema(out, schema);
89}
90
91/// Prints Parquet [`Type`] information.
92#[allow(unused_must_use)]
93pub fn print_schema(out: &mut dyn io::Write, tp: &Type) {
94    // TODO: better if we can pass fmt::Write to Printer.
95    // But how can we make it to accept both io::Write & fmt::Write?
96    let mut s = String::new();
97    {
98        let mut printer = Printer::new(&mut s);
99        printer.print(tp);
100    }
101    writeln!(out, "{s}");
102}
103
104#[allow(unused_must_use)]
105fn print_row_group_metadata(out: &mut dyn io::Write, rg_metadata: &RowGroupMetaData) {
106    writeln!(out, "total byte size: {}", rg_metadata.total_byte_size());
107    writeln!(out, "num of rows: {}", rg_metadata.num_rows());
108    writeln!(out);
109    writeln!(out, "num of columns: {}", rg_metadata.num_columns());
110    writeln!(out, "columns: ");
111    for (i, cc) in rg_metadata.columns().iter().enumerate() {
112        writeln!(out);
113        writeln!(out, "column {i}:");
114        print_dashes(out, 80);
115        print_column_chunk_metadata(out, cc);
116    }
117}
118
119#[allow(unused_must_use)]
120fn print_column_chunk_metadata(out: &mut dyn io::Write, cc_metadata: &ColumnChunkMetaData) {
121    writeln!(out, "column type: {}", cc_metadata.column_type());
122    writeln!(out, "column path: {}", cc_metadata.column_path());
123    let encoding_strs: Vec<_> = cc_metadata
124        .encodings()
125        .iter()
126        .map(|e| format!("{e}"))
127        .collect();
128    writeln!(out, "encodings: {}", encoding_strs.join(" "));
129    let file_path_str = cc_metadata.file_path().unwrap_or("N/A");
130    writeln!(out, "file path: {file_path_str}");
131    writeln!(out, "file offset: {}", cc_metadata.file_offset());
132    writeln!(out, "num of values: {}", cc_metadata.num_values());
133    writeln!(
134        out,
135        "compression: {}",
136        cc_metadata.compression().codec_to_string()
137    );
138    writeln!(
139        out,
140        "total compressed size (in bytes): {}",
141        cc_metadata.compressed_size()
142    );
143    writeln!(
144        out,
145        "total uncompressed size (in bytes): {}",
146        cc_metadata.uncompressed_size()
147    );
148    writeln!(out, "data page offset: {}", cc_metadata.data_page_offset());
149    let index_page_offset_str = match cc_metadata.index_page_offset() {
150        None => "N/A".to_owned(),
151        Some(ipo) => ipo.to_string(),
152    };
153    writeln!(out, "index page offset: {index_page_offset_str}");
154    let dict_page_offset_str = match cc_metadata.dictionary_page_offset() {
155        None => "N/A".to_owned(),
156        Some(dpo) => dpo.to_string(),
157    };
158    writeln!(out, "dictionary page offset: {dict_page_offset_str}");
159    let statistics_str = match cc_metadata.statistics() {
160        None => "N/A".to_owned(),
161        Some(stats) => stats.to_string(),
162    };
163    writeln!(out, "statistics: {statistics_str}");
164    let bloom_filter_offset_str = match cc_metadata.bloom_filter_offset() {
165        None => "N/A".to_owned(),
166        Some(bfo) => bfo.to_string(),
167    };
168    writeln!(out, "bloom filter offset: {bloom_filter_offset_str}");
169    let bloom_filter_length_str = match cc_metadata.bloom_filter_length() {
170        None => "N/A".to_owned(),
171        Some(bfo) => bfo.to_string(),
172    };
173    writeln!(out, "bloom filter length: {bloom_filter_length_str}");
174    let offset_index_offset_str = match cc_metadata.offset_index_offset() {
175        None => "N/A".to_owned(),
176        Some(oio) => oio.to_string(),
177    };
178    writeln!(out, "offset index offset: {offset_index_offset_str}");
179    let offset_index_length_str = match cc_metadata.offset_index_length() {
180        None => "N/A".to_owned(),
181        Some(oil) => oil.to_string(),
182    };
183    writeln!(out, "offset index length: {offset_index_length_str}");
184    let column_index_offset_str = match cc_metadata.column_index_offset() {
185        None => "N/A".to_owned(),
186        Some(cio) => cio.to_string(),
187    };
188    writeln!(out, "column index offset: {column_index_offset_str}");
189    let column_index_length_str = match cc_metadata.column_index_length() {
190        None => "N/A".to_owned(),
191        Some(cil) => cil.to_string(),
192    };
193    writeln!(out, "column index length: {column_index_length_str}");
194    writeln!(out);
195}
196
197#[allow(unused_must_use)]
198fn print_dashes(out: &mut dyn io::Write, num: i32) {
199    for _ in 0..num {
200        write!(out, "-");
201    }
202    writeln!(out);
203}
204
205const INDENT_WIDTH: i32 = 2;
206
207/// Struct for printing Parquet message type.
208struct Printer<'a> {
209    output: &'a mut dyn fmt::Write,
210    indent: i32,
211}
212
213#[allow(unused_must_use)]
214impl<'a> Printer<'a> {
215    fn new(output: &'a mut dyn fmt::Write) -> Self {
216        Printer { output, indent: 0 }
217    }
218
219    fn print_indent(&mut self) {
220        for _ in 0..self.indent {
221            write!(self.output, " ");
222        }
223    }
224}
225
226#[inline]
227fn print_timeunit(unit: &TimeUnit) -> &str {
228    match unit {
229        TimeUnit::MILLIS(_) => "MILLIS",
230        TimeUnit::MICROS(_) => "MICROS",
231        TimeUnit::NANOS(_) => "NANOS",
232    }
233}
234
235#[inline]
236fn print_logical_and_converted(
237    logical_type: Option<&LogicalType>,
238    converted_type: ConvertedType,
239    precision: i32,
240    scale: i32,
241) -> String {
242    match logical_type {
243        Some(logical_type) => match logical_type {
244            LogicalType::Integer {
245                bit_width,
246                is_signed,
247            } => {
248                format!("INTEGER({bit_width},{is_signed})")
249            }
250            LogicalType::Decimal { scale, precision } => {
251                format!("DECIMAL({precision},{scale})")
252            }
253            LogicalType::Timestamp {
254                is_adjusted_to_u_t_c,
255                unit,
256            } => {
257                format!(
258                    "TIMESTAMP({},{})",
259                    print_timeunit(unit),
260                    is_adjusted_to_u_t_c
261                )
262            }
263            LogicalType::Time {
264                is_adjusted_to_u_t_c,
265                unit,
266            } => {
267                format!("TIME({},{})", print_timeunit(unit), is_adjusted_to_u_t_c)
268            }
269            LogicalType::Date => "DATE".to_string(),
270            LogicalType::Bson => "BSON".to_string(),
271            LogicalType::Json => "JSON".to_string(),
272            LogicalType::String => "STRING".to_string(),
273            LogicalType::Uuid => "UUID".to_string(),
274            LogicalType::Enum => "ENUM".to_string(),
275            LogicalType::List => "LIST".to_string(),
276            LogicalType::Map => "MAP".to_string(),
277            LogicalType::Float16 => "FLOAT16".to_string(),
278            LogicalType::Unknown => "UNKNOWN".to_string(),
279        },
280        None => {
281            // Also print converted type if it is available
282            match converted_type {
283                ConvertedType::NONE => String::new(),
284                decimal @ ConvertedType::DECIMAL => {
285                    // For decimal type we should print precision and scale if they
286                    // are > 0, e.g. DECIMAL(9,2) -
287                    // DECIMAL(9) - DECIMAL
288                    let precision_scale = match (precision, scale) {
289                        (p, s) if p > 0 && s > 0 => {
290                            format!("({p},{s})")
291                        }
292                        (p, 0) if p > 0 => format!("({p})"),
293                        _ => String::new(),
294                    };
295                    format!("{decimal}{precision_scale}")
296                }
297                other_converted_type => {
298                    format!("{other_converted_type}")
299                }
300            }
301        }
302    }
303}
304
305#[allow(unused_must_use)]
306impl Printer<'_> {
307    pub fn print(&mut self, tp: &Type) {
308        self.print_indent();
309        match *tp {
310            Type::PrimitiveType {
311                ref basic_info,
312                physical_type,
313                type_length,
314                scale,
315                precision,
316            } => {
317                let phys_type_str = match physical_type {
318                    PhysicalType::FIXED_LEN_BYTE_ARRAY => {
319                        // We need to include length for fixed byte array
320                        format!("{physical_type} ({type_length})")
321                    }
322                    _ => format!("{physical_type}"),
323                };
324                // Also print logical type if it is available
325                // If there is a logical type, do not print converted type
326                let logical_type_str = print_logical_and_converted(
327                    basic_info.logical_type().as_ref(),
328                    basic_info.converted_type(),
329                    precision,
330                    scale,
331                );
332                if logical_type_str.is_empty() {
333                    write!(
334                        self.output,
335                        "{} {} {};",
336                        basic_info.repetition(),
337                        phys_type_str,
338                        basic_info.name()
339                    );
340                } else {
341                    write!(
342                        self.output,
343                        "{} {} {} ({});",
344                        basic_info.repetition(),
345                        phys_type_str,
346                        basic_info.name(),
347                        logical_type_str
348                    );
349                }
350            }
351            Type::GroupType {
352                ref basic_info,
353                ref fields,
354            } => {
355                if basic_info.has_repetition() {
356                    let r = basic_info.repetition();
357                    write!(self.output, "{} group {} ", r, basic_info.name());
358                    let logical_str = print_logical_and_converted(
359                        basic_info.logical_type().as_ref(),
360                        basic_info.converted_type(),
361                        0,
362                        0,
363                    );
364                    if !logical_str.is_empty() {
365                        write!(self.output, "({logical_str}) ");
366                    }
367                    writeln!(self.output, "{{");
368                } else {
369                    writeln!(self.output, "message {} {{", basic_info.name());
370                }
371
372                self.indent += INDENT_WIDTH;
373                for c in fields {
374                    self.print(c);
375                    writeln!(self.output);
376                }
377                self.indent -= INDENT_WIDTH;
378                self.print_indent();
379                write!(self.output, "}}");
380            }
381        }
382    }
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388
389    use std::sync::Arc;
390
391    use crate::basic::{Repetition, Type as PhysicalType};
392    use crate::errors::Result;
393    use crate::schema::parser::parse_message_type;
394
395    fn assert_print_parse_message(message: Type) {
396        let mut s = String::new();
397        {
398            let mut p = Printer::new(&mut s);
399            p.print(&message);
400        }
401        println!("{}", &s);
402        let parsed = parse_message_type(&s).unwrap();
403        assert_eq!(message, parsed);
404    }
405
406    #[test]
407    fn test_print_primitive_type() {
408        let mut s = String::new();
409        {
410            let mut p = Printer::new(&mut s);
411            let field = Type::primitive_type_builder("field", PhysicalType::INT32)
412                .with_repetition(Repetition::REQUIRED)
413                .with_converted_type(ConvertedType::INT_32)
414                .build()
415                .unwrap();
416            p.print(&field);
417        }
418        assert_eq!(&mut s, "REQUIRED INT32 field (INT_32);");
419    }
420
421    #[inline]
422    fn build_primitive_type(
423        name: &str,
424        physical_type: PhysicalType,
425        logical_type: Option<LogicalType>,
426        converted_type: ConvertedType,
427        repetition: Repetition,
428    ) -> Result<Type> {
429        Type::primitive_type_builder(name, physical_type)
430            .with_repetition(repetition)
431            .with_logical_type(logical_type)
432            .with_converted_type(converted_type)
433            .build()
434    }
435
436    #[test]
437    fn test_print_logical_types() {
438        let types_and_strings = vec![
439            (
440                build_primitive_type(
441                    "field",
442                    PhysicalType::INT32,
443                    Some(LogicalType::Integer {
444                        bit_width: 32,
445                        is_signed: true,
446                    }),
447                    ConvertedType::NONE,
448                    Repetition::REQUIRED,
449                )
450                .unwrap(),
451                "REQUIRED INT32 field (INTEGER(32,true));",
452            ),
453            (
454                build_primitive_type(
455                    "field",
456                    PhysicalType::INT32,
457                    Some(LogicalType::Integer {
458                        bit_width: 8,
459                        is_signed: false,
460                    }),
461                    ConvertedType::NONE,
462                    Repetition::OPTIONAL,
463                )
464                .unwrap(),
465                "OPTIONAL INT32 field (INTEGER(8,false));",
466            ),
467            (
468                build_primitive_type(
469                    "field",
470                    PhysicalType::INT32,
471                    Some(LogicalType::Integer {
472                        bit_width: 16,
473                        is_signed: true,
474                    }),
475                    ConvertedType::INT_16,
476                    Repetition::REPEATED,
477                )
478                .unwrap(),
479                "REPEATED INT32 field (INTEGER(16,true));",
480            ),
481            (
482                build_primitive_type(
483                    "field",
484                    PhysicalType::INT64,
485                    None,
486                    ConvertedType::NONE,
487                    Repetition::REPEATED,
488                )
489                .unwrap(),
490                "REPEATED INT64 field;",
491            ),
492            (
493                build_primitive_type(
494                    "field",
495                    PhysicalType::FLOAT,
496                    None,
497                    ConvertedType::NONE,
498                    Repetition::REQUIRED,
499                )
500                .unwrap(),
501                "REQUIRED FLOAT field;",
502            ),
503            (
504                build_primitive_type(
505                    "booleans",
506                    PhysicalType::BOOLEAN,
507                    None,
508                    ConvertedType::NONE,
509                    Repetition::OPTIONAL,
510                )
511                .unwrap(),
512                "OPTIONAL BOOLEAN booleans;",
513            ),
514            (
515                build_primitive_type(
516                    "field",
517                    PhysicalType::INT64,
518                    Some(LogicalType::Timestamp {
519                        is_adjusted_to_u_t_c: true,
520                        unit: TimeUnit::MILLIS(Default::default()),
521                    }),
522                    ConvertedType::NONE,
523                    Repetition::REQUIRED,
524                )
525                .unwrap(),
526                "REQUIRED INT64 field (TIMESTAMP(MILLIS,true));",
527            ),
528            (
529                build_primitive_type(
530                    "field",
531                    PhysicalType::INT32,
532                    Some(LogicalType::Date),
533                    ConvertedType::NONE,
534                    Repetition::OPTIONAL,
535                )
536                .unwrap(),
537                "OPTIONAL INT32 field (DATE);",
538            ),
539            (
540                build_primitive_type(
541                    "field",
542                    PhysicalType::INT32,
543                    Some(LogicalType::Time {
544                        unit: TimeUnit::MILLIS(Default::default()),
545                        is_adjusted_to_u_t_c: false,
546                    }),
547                    ConvertedType::TIME_MILLIS,
548                    Repetition::REQUIRED,
549                )
550                .unwrap(),
551                "REQUIRED INT32 field (TIME(MILLIS,false));",
552            ),
553            (
554                build_primitive_type(
555                    "field",
556                    PhysicalType::BYTE_ARRAY,
557                    None,
558                    ConvertedType::NONE,
559                    Repetition::REQUIRED,
560                )
561                .unwrap(),
562                "REQUIRED BYTE_ARRAY field;",
563            ),
564            (
565                build_primitive_type(
566                    "field",
567                    PhysicalType::BYTE_ARRAY,
568                    None,
569                    ConvertedType::UTF8,
570                    Repetition::REQUIRED,
571                )
572                .unwrap(),
573                "REQUIRED BYTE_ARRAY field (UTF8);",
574            ),
575            (
576                build_primitive_type(
577                    "field",
578                    PhysicalType::BYTE_ARRAY,
579                    Some(LogicalType::Json),
580                    ConvertedType::JSON,
581                    Repetition::REQUIRED,
582                )
583                .unwrap(),
584                "REQUIRED BYTE_ARRAY field (JSON);",
585            ),
586            (
587                build_primitive_type(
588                    "field",
589                    PhysicalType::BYTE_ARRAY,
590                    Some(LogicalType::Bson),
591                    ConvertedType::BSON,
592                    Repetition::REQUIRED,
593                )
594                .unwrap(),
595                "REQUIRED BYTE_ARRAY field (BSON);",
596            ),
597            (
598                build_primitive_type(
599                    "field",
600                    PhysicalType::BYTE_ARRAY,
601                    Some(LogicalType::String),
602                    ConvertedType::NONE,
603                    Repetition::REQUIRED,
604                )
605                .unwrap(),
606                "REQUIRED BYTE_ARRAY field (STRING);",
607            ),
608        ];
609
610        types_and_strings.into_iter().for_each(|(field, expected)| {
611            let mut s = String::new();
612            {
613                let mut p = Printer::new(&mut s);
614                p.print(&field);
615            }
616            assert_eq!(&s, expected)
617        });
618    }
619
620    #[inline]
621    fn decimal_length_from_precision(precision: usize) -> i32 {
622        let max_val = 10.0_f64.powi(precision as i32) - 1.0;
623        let bits_unsigned = max_val.log2().ceil();
624        let bits_signed = bits_unsigned + 1.0;
625        (bits_signed / 8.0).ceil() as i32
626    }
627
628    #[test]
629    fn test_print_flba_logical_types() {
630        let types_and_strings = vec![
631            (
632                Type::primitive_type_builder("field", PhysicalType::FIXED_LEN_BYTE_ARRAY)
633                    .with_logical_type(None)
634                    .with_converted_type(ConvertedType::INTERVAL)
635                    .with_length(12)
636                    .with_repetition(Repetition::REQUIRED)
637                    .build()
638                    .unwrap(),
639                "REQUIRED FIXED_LEN_BYTE_ARRAY (12) field (INTERVAL);",
640            ),
641            (
642                Type::primitive_type_builder("field", PhysicalType::FIXED_LEN_BYTE_ARRAY)
643                    .with_logical_type(Some(LogicalType::Uuid))
644                    .with_length(16)
645                    .with_repetition(Repetition::REQUIRED)
646                    .build()
647                    .unwrap(),
648                "REQUIRED FIXED_LEN_BYTE_ARRAY (16) field (UUID);",
649            ),
650            (
651                Type::primitive_type_builder("decimal", PhysicalType::FIXED_LEN_BYTE_ARRAY)
652                    .with_logical_type(Some(LogicalType::Decimal {
653                        precision: 32,
654                        scale: 20,
655                    }))
656                    .with_precision(32)
657                    .with_scale(20)
658                    .with_length(decimal_length_from_precision(32))
659                    .with_repetition(Repetition::REPEATED)
660                    .build()
661                    .unwrap(),
662                "REPEATED FIXED_LEN_BYTE_ARRAY (14) decimal (DECIMAL(32,20));",
663            ),
664            (
665                Type::primitive_type_builder("decimal", PhysicalType::FIXED_LEN_BYTE_ARRAY)
666                    .with_converted_type(ConvertedType::DECIMAL)
667                    .with_precision(19)
668                    .with_scale(4)
669                    .with_length(decimal_length_from_precision(19))
670                    .with_repetition(Repetition::OPTIONAL)
671                    .build()
672                    .unwrap(),
673                "OPTIONAL FIXED_LEN_BYTE_ARRAY (9) decimal (DECIMAL(19,4));",
674            ),
675            (
676                Type::primitive_type_builder("float16", PhysicalType::FIXED_LEN_BYTE_ARRAY)
677                    .with_logical_type(Some(LogicalType::Float16))
678                    .with_length(2)
679                    .with_repetition(Repetition::REQUIRED)
680                    .build()
681                    .unwrap(),
682                "REQUIRED FIXED_LEN_BYTE_ARRAY (2) float16 (FLOAT16);",
683            ),
684        ];
685
686        types_and_strings.into_iter().for_each(|(field, expected)| {
687            let mut s = String::new();
688            {
689                let mut p = Printer::new(&mut s);
690                p.print(&field);
691            }
692            assert_eq!(&s, expected)
693        });
694    }
695
696    #[test]
697    fn test_print_group_type() {
698        let mut s = String::new();
699        {
700            let mut p = Printer::new(&mut s);
701            let f1 = Type::primitive_type_builder("f1", PhysicalType::INT32)
702                .with_repetition(Repetition::REQUIRED)
703                .with_converted_type(ConvertedType::INT_32)
704                .with_id(Some(0))
705                .build();
706            let f2 = Type::primitive_type_builder("f2", PhysicalType::BYTE_ARRAY)
707                .with_converted_type(ConvertedType::UTF8)
708                .with_id(Some(1))
709                .build();
710            let f3 = Type::primitive_type_builder("f3", PhysicalType::BYTE_ARRAY)
711                .with_logical_type(Some(LogicalType::String))
712                .with_id(Some(1))
713                .build();
714            let f4 = Type::primitive_type_builder("f4", PhysicalType::FIXED_LEN_BYTE_ARRAY)
715                .with_repetition(Repetition::REPEATED)
716                .with_converted_type(ConvertedType::INTERVAL)
717                .with_length(12)
718                .with_id(Some(2))
719                .build();
720
721            let struct_fields = vec![
722                Arc::new(f1.unwrap()),
723                Arc::new(f2.unwrap()),
724                Arc::new(f3.unwrap()),
725            ];
726            let field = Type::group_type_builder("field")
727                .with_repetition(Repetition::OPTIONAL)
728                .with_fields(struct_fields)
729                .with_id(Some(1))
730                .build()
731                .unwrap();
732
733            let fields = vec![Arc::new(field), Arc::new(f4.unwrap())];
734            let message = Type::group_type_builder("schema")
735                .with_fields(fields)
736                .with_id(Some(2))
737                .build()
738                .unwrap();
739            p.print(&message);
740        }
741        let expected = "message schema {
742  OPTIONAL group field {
743    REQUIRED INT32 f1 (INT_32);
744    OPTIONAL BYTE_ARRAY f2 (UTF8);
745    OPTIONAL BYTE_ARRAY f3 (STRING);
746  }
747  REPEATED FIXED_LEN_BYTE_ARRAY (12) f4 (INTERVAL);
748}";
749        assert_eq!(&mut s, expected);
750    }
751
752    #[test]
753    fn test_print_and_parse_primitive() {
754        let a2 = Type::primitive_type_builder("a2", PhysicalType::BYTE_ARRAY)
755            .with_repetition(Repetition::REPEATED)
756            .with_converted_type(ConvertedType::UTF8)
757            .build()
758            .unwrap();
759
760        let a1 = Type::group_type_builder("a1")
761            .with_repetition(Repetition::OPTIONAL)
762            .with_logical_type(Some(LogicalType::List))
763            .with_converted_type(ConvertedType::LIST)
764            .with_fields(vec![Arc::new(a2)])
765            .build()
766            .unwrap();
767
768        let b3 = Type::primitive_type_builder("b3", PhysicalType::INT32)
769            .with_repetition(Repetition::OPTIONAL)
770            .build()
771            .unwrap();
772
773        let b4 = Type::primitive_type_builder("b4", PhysicalType::DOUBLE)
774            .with_repetition(Repetition::OPTIONAL)
775            .build()
776            .unwrap();
777
778        let b2 = Type::group_type_builder("b2")
779            .with_repetition(Repetition::REPEATED)
780            .with_converted_type(ConvertedType::NONE)
781            .with_fields(vec![Arc::new(b3), Arc::new(b4)])
782            .build()
783            .unwrap();
784
785        let b1 = Type::group_type_builder("b1")
786            .with_repetition(Repetition::OPTIONAL)
787            .with_logical_type(Some(LogicalType::List))
788            .with_converted_type(ConvertedType::LIST)
789            .with_fields(vec![Arc::new(b2)])
790            .build()
791            .unwrap();
792
793        let a0 = Type::group_type_builder("a0")
794            .with_repetition(Repetition::REQUIRED)
795            .with_fields(vec![Arc::new(a1), Arc::new(b1)])
796            .build()
797            .unwrap();
798
799        let message = Type::group_type_builder("root")
800            .with_fields(vec![Arc::new(a0)])
801            .build()
802            .unwrap();
803
804        assert_print_parse_message(message);
805    }
806
807    #[test]
808    fn test_print_and_parse_nested() {
809        let f1 = Type::primitive_type_builder("f1", PhysicalType::INT32)
810            .with_repetition(Repetition::REQUIRED)
811            .with_converted_type(ConvertedType::INT_32)
812            .build()
813            .unwrap();
814
815        let f2 = Type::primitive_type_builder("f2", PhysicalType::BYTE_ARRAY)
816            .with_repetition(Repetition::OPTIONAL)
817            .with_converted_type(ConvertedType::UTF8)
818            .build()
819            .unwrap();
820
821        let field = Type::group_type_builder("field")
822            .with_repetition(Repetition::OPTIONAL)
823            .with_fields(vec![Arc::new(f1), Arc::new(f2)])
824            .build()
825            .unwrap();
826
827        let f3 = Type::primitive_type_builder("f3", PhysicalType::FIXED_LEN_BYTE_ARRAY)
828            .with_repetition(Repetition::REPEATED)
829            .with_converted_type(ConvertedType::INTERVAL)
830            .with_length(12)
831            .build()
832            .unwrap();
833
834        let message = Type::group_type_builder("schema")
835            .with_fields(vec![Arc::new(field), Arc::new(f3)])
836            .build()
837            .unwrap();
838
839        assert_print_parse_message(message);
840    }
841
842    #[test]
843    fn test_print_and_parse_decimal() {
844        let f1 = Type::primitive_type_builder("f1", PhysicalType::INT32)
845            .with_repetition(Repetition::OPTIONAL)
846            .with_logical_type(Some(LogicalType::Decimal {
847                precision: 9,
848                scale: 2,
849            }))
850            .with_converted_type(ConvertedType::DECIMAL)
851            .with_precision(9)
852            .with_scale(2)
853            .build()
854            .unwrap();
855
856        let f2 = Type::primitive_type_builder("f2", PhysicalType::INT32)
857            .with_repetition(Repetition::OPTIONAL)
858            .with_logical_type(Some(LogicalType::Decimal {
859                precision: 9,
860                scale: 0,
861            }))
862            .with_converted_type(ConvertedType::DECIMAL)
863            .with_precision(9)
864            .with_scale(0)
865            .build()
866            .unwrap();
867
868        let message = Type::group_type_builder("schema")
869            .with_fields(vec![Arc::new(f1), Arc::new(f2)])
870            .build()
871            .unwrap();
872
873        assert_print_parse_message(message);
874    }
875}