Skip to main content

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::{
49    ConvertedType, DecimalType, GeographyType, GeometryType, IntType, LogicalType, TimeUnit,
50    Type as PhysicalType, VariantType,
51};
52use crate::file::metadata::{ColumnChunkMetaData, FileMetaData, ParquetMetaData, RowGroupMetaData};
53use crate::schema::types::Type;
54
55/// Prints Parquet metadata [`ParquetMetaData`] information.
56#[expect(unused_must_use)]
57pub fn print_parquet_metadata(out: &mut dyn io::Write, metadata: &ParquetMetaData) {
58    print_file_metadata(out, metadata.file_metadata());
59    writeln!(out);
60    writeln!(out);
61    writeln!(out, "num of row groups: {}", metadata.num_row_groups());
62    writeln!(out, "row groups:");
63    writeln!(out);
64    for (i, rg) in metadata.row_groups().iter().enumerate() {
65        writeln!(out, "row group {i}:");
66        print_dashes(out, 80);
67        print_row_group_metadata(out, rg);
68    }
69}
70
71/// Prints file metadata [`FileMetaData`] information.
72#[expect(unused_must_use)]
73pub fn print_file_metadata(out: &mut dyn io::Write, file_metadata: &FileMetaData) {
74    writeln!(out, "version: {}", file_metadata.version());
75    writeln!(out, "num of rows: {}", file_metadata.num_rows());
76    if let Some(created_by) = file_metadata.created_by().as_ref() {
77        writeln!(out, "created by: {created_by}");
78    }
79    if let Some(metadata) = file_metadata.key_value_metadata() {
80        writeln!(out, "metadata:");
81        for kv in metadata {
82            writeln!(
83                out,
84                "  {}: {}",
85                kv.key,
86                kv.value.as_ref().unwrap_or(&String::new())
87            );
88        }
89    }
90    let schema = file_metadata.schema();
91    print_schema(out, schema);
92}
93
94/// Prints Parquet [`Type`] information.
95///
96/// # Example
97///
98/// ```rust
99/// use parquet::{
100///     basic::{ConvertedType, Repetition, Type as PhysicalType},
101///     schema::{printer::print_schema, types::Type},
102/// };
103/// use std::sync::Arc;
104///
105/// let field_a = Type::primitive_type_builder("a", PhysicalType::BYTE_ARRAY)
106///     .with_id(Some(42))
107///     .with_converted_type(ConvertedType::UTF8)
108///     .build()
109///     .unwrap();
110///
111/// let field_b = Type::primitive_type_builder("b", PhysicalType::INT32)
112///     .with_repetition(Repetition::REQUIRED)
113///     .build()
114///     .unwrap();
115///
116/// let field_d = Type::primitive_type_builder("d", PhysicalType::INT64)
117///     .with_id(Some(99))
118///     .build()
119///     .unwrap();
120///
121/// let field_c = Type::group_type_builder("c")
122///     .with_id(Some(43))
123///     .with_fields(vec![Arc::new(field_d)])
124///     .build()
125///     .unwrap();
126///
127/// let schema = Type::group_type_builder("schema")
128///     .with_fields(vec![Arc::new(field_a), Arc::new(field_b), Arc::new(field_c)])
129///     .build()
130///     .unwrap();
131///
132/// print_schema(&mut std::io::stdout(), &schema);
133/// ```
134///
135/// outputs
136///
137/// ```text
138/// message schema {
139///   OPTIONAL BYTE_ARRAY a [42] (UTF8);
140///   REQUIRED INT32 b;
141///   message c [43] {
142///     OPTIONAL INT64 d [99];
143///   }
144/// }
145/// ```
146#[expect(unused_must_use)]
147pub fn print_schema(out: &mut dyn io::Write, tp: &Type) {
148    // TODO: better if we can pass fmt::Write to Printer.
149    // But how can we make it to accept both io::Write & fmt::Write?
150    let mut s = String::new();
151    {
152        let mut printer = Printer::new(&mut s);
153        printer.print(tp);
154    }
155    writeln!(out, "{s}");
156}
157
158#[expect(unused_must_use)]
159fn print_row_group_metadata(out: &mut dyn io::Write, rg_metadata: &RowGroupMetaData) {
160    writeln!(out, "total byte size: {}", rg_metadata.total_byte_size());
161    writeln!(out, "num of rows: {}", rg_metadata.num_rows());
162    writeln!(out);
163    writeln!(out, "num of columns: {}", rg_metadata.num_columns());
164    writeln!(out, "columns: ");
165    for (i, cc) in rg_metadata.columns().iter().enumerate() {
166        writeln!(out);
167        writeln!(out, "column {i}:");
168        print_dashes(out, 80);
169        print_column_chunk_metadata(out, cc);
170    }
171}
172
173#[expect(unused_must_use)]
174fn print_column_chunk_metadata(out: &mut dyn io::Write, cc_metadata: &ColumnChunkMetaData) {
175    writeln!(out, "column type: {}", cc_metadata.column_type());
176    writeln!(out, "column path: {}", cc_metadata.column_path());
177    let encoding_strs: Vec<_> = cc_metadata.encodings().map(|e| format!("{e}")).collect();
178    writeln!(out, "encodings: {}", encoding_strs.join(" "));
179    let file_path_str = cc_metadata.file_path().unwrap_or("N/A");
180    writeln!(out, "file path: {file_path_str}");
181    writeln!(out, "file offset: {}", cc_metadata.file_offset());
182    writeln!(out, "num of values: {}", cc_metadata.num_values());
183    writeln!(out, "compression: {}", cc_metadata.compression_codec());
184    writeln!(
185        out,
186        "total compressed size (in bytes): {}",
187        cc_metadata.compressed_size()
188    );
189    writeln!(
190        out,
191        "total uncompressed size (in bytes): {}",
192        cc_metadata.uncompressed_size()
193    );
194    writeln!(out, "data page offset: {}", cc_metadata.data_page_offset());
195    let index_page_offset_str = match cc_metadata.index_page_offset() {
196        None => "N/A".to_owned(),
197        Some(ipo) => ipo.to_string(),
198    };
199    writeln!(out, "index page offset: {index_page_offset_str}");
200    let dict_page_offset_str = match cc_metadata.dictionary_page_offset() {
201        None => "N/A".to_owned(),
202        Some(dpo) => dpo.to_string(),
203    };
204    writeln!(out, "dictionary page offset: {dict_page_offset_str}");
205    let statistics_str = match cc_metadata.statistics() {
206        None => "N/A".to_owned(),
207        Some(stats) => stats.to_string(),
208    };
209    writeln!(out, "statistics: {statistics_str}");
210    let bloom_filter_offset_str = match cc_metadata.bloom_filter_offset() {
211        None => "N/A".to_owned(),
212        Some(bfo) => bfo.to_string(),
213    };
214    writeln!(out, "bloom filter offset: {bloom_filter_offset_str}");
215    let bloom_filter_length_str = match cc_metadata.bloom_filter_length() {
216        None => "N/A".to_owned(),
217        Some(bfo) => bfo.to_string(),
218    };
219    writeln!(out, "bloom filter length: {bloom_filter_length_str}");
220    let offset_index_offset_str = match cc_metadata.offset_index_offset() {
221        None => "N/A".to_owned(),
222        Some(oio) => oio.to_string(),
223    };
224    writeln!(out, "offset index offset: {offset_index_offset_str}");
225    let offset_index_length_str = match cc_metadata.offset_index_length() {
226        None => "N/A".to_owned(),
227        Some(oil) => oil.to_string(),
228    };
229    writeln!(out, "offset index length: {offset_index_length_str}");
230    let column_index_offset_str = match cc_metadata.column_index_offset() {
231        None => "N/A".to_owned(),
232        Some(cio) => cio.to_string(),
233    };
234    writeln!(out, "column index offset: {column_index_offset_str}");
235    let column_index_length_str = match cc_metadata.column_index_length() {
236        None => "N/A".to_owned(),
237        Some(cil) => cil.to_string(),
238    };
239    writeln!(out, "column index length: {column_index_length_str}");
240    writeln!(out);
241}
242
243#[expect(unused_must_use)]
244fn print_dashes(out: &mut dyn io::Write, num: i32) {
245    for _ in 0..num {
246        write!(out, "-");
247    }
248    writeln!(out);
249}
250
251const INDENT_WIDTH: i32 = 2;
252
253/// Struct for printing Parquet message type.
254struct Printer<'a> {
255    output: &'a mut dyn fmt::Write,
256    indent: i32,
257}
258
259#[expect(unused_must_use)]
260impl<'a> Printer<'a> {
261    fn new(output: &'a mut dyn fmt::Write) -> Self {
262        Printer { output, indent: 0 }
263    }
264
265    fn print_indent(&mut self) {
266        for _ in 0..self.indent {
267            write!(self.output, " ");
268        }
269    }
270}
271
272#[inline]
273fn print_timeunit(unit: &TimeUnit) -> &str {
274    match unit {
275        TimeUnit::MILLIS => "MILLIS",
276        TimeUnit::MICROS => "MICROS",
277        TimeUnit::NANOS => "NANOS",
278    }
279}
280
281#[inline]
282fn print_logical_and_converted(
283    logical_type: Option<&LogicalType>,
284    converted_type: ConvertedType,
285    precision: i32,
286    scale: i32,
287) -> String {
288    match logical_type {
289        Some(logical_type) => match logical_type {
290            LogicalType::Integer(IntType {
291                bit_width,
292                is_signed,
293            }) => {
294                format!("INTEGER({bit_width},{is_signed})")
295            }
296            LogicalType::Decimal(DecimalType { scale, precision }) => {
297                format!("DECIMAL({precision},{scale})")
298            }
299            LogicalType::Timestamp(timestamp) => {
300                format!(
301                    "TIMESTAMP({},{})",
302                    print_timeunit(&timestamp.unit),
303                    timestamp.is_adjusted_to_u_t_c
304                )
305            }
306            LogicalType::Time(time) => {
307                format!(
308                    "TIME({},{})",
309                    print_timeunit(&time.unit),
310                    time.is_adjusted_to_u_t_c
311                )
312            }
313            LogicalType::Date => "DATE".to_string(),
314            LogicalType::Bson => "BSON".to_string(),
315            LogicalType::Json => "JSON".to_string(),
316            LogicalType::String => "STRING".to_string(),
317            LogicalType::Uuid => "UUID".to_string(),
318            LogicalType::Enum => "ENUM".to_string(),
319            LogicalType::List => "LIST".to_string(),
320            LogicalType::Map => "MAP".to_string(),
321            LogicalType::Float16 => "FLOAT16".to_string(),
322            LogicalType::Variant(VariantType {
323                specification_version,
324            }) => format!("VARIANT({specification_version:?})"),
325            LogicalType::Geometry(GeometryType { crs }) => {
326                if let Some(crs) = crs {
327                    format!("GEOMETRY({crs})")
328                } else {
329                    "GEOMETRY".to_string()
330                }
331            }
332            LogicalType::Geography(GeographyType { crs, algorithm }) => {
333                let algorithm = algorithm.unwrap_or_default();
334                if let Some(crs) = crs {
335                    format!("GEOGRAPHY({algorithm}, {crs})")
336                } else {
337                    format!("GEOGRAPHY({algorithm})")
338                }
339            }
340            LogicalType::File => "FILE".to_string(),
341            LogicalType::Unknown => "UNKNOWN".to_string(),
342            LogicalType::_Unknown { field_id } => format!("_Unknown({field_id})"),
343        },
344        None => {
345            // Also print converted type if it is available
346            match converted_type {
347                ConvertedType::NONE => String::new(),
348                decimal @ ConvertedType::DECIMAL => {
349                    // For decimal type we should print precision and scale if they
350                    // are > 0, e.g. DECIMAL(9,2) -
351                    // DECIMAL(9) - DECIMAL
352                    let precision_scale = match (precision, scale) {
353                        (p, s) if p > 0 && s > 0 => {
354                            format!("({p},{s})")
355                        }
356                        (p, 0) if p > 0 => format!("({p})"),
357                        _ => String::new(),
358                    };
359                    format!("{decimal}{precision_scale}")
360                }
361                other_converted_type => {
362                    format!("{other_converted_type}")
363                }
364            }
365        }
366    }
367}
368
369#[expect(unused_must_use)]
370impl Printer<'_> {
371    pub fn print(&mut self, tp: &Type) {
372        self.print_indent();
373        match *tp {
374            Type::PrimitiveType {
375                ref basic_info,
376                physical_type,
377                type_length,
378                scale,
379                precision,
380            } => {
381                let phys_type_str = match physical_type {
382                    PhysicalType::FIXED_LEN_BYTE_ARRAY => {
383                        // We need to include length for fixed byte array
384                        format!("{physical_type} ({type_length})")
385                    }
386                    _ => format!("{physical_type}"),
387                };
388                write!(
389                    self.output,
390                    "{} {} {}",
391                    basic_info.repetition(),
392                    phys_type_str,
393                    basic_info.name()
394                );
395                if basic_info.has_id() {
396                    write!(self.output, " [{}]", basic_info.id());
397                }
398                // Also print logical type if it is available
399                // If there is a logical type, do not print converted type
400                let logical_type_str = print_logical_and_converted(
401                    basic_info.logical_type_ref(),
402                    basic_info.converted_type(),
403                    precision,
404                    scale,
405                );
406                if !logical_type_str.is_empty() {
407                    write!(self.output, " ({logical_type_str});");
408                } else {
409                    write!(self.output, ";");
410                }
411            }
412            Type::GroupType {
413                ref basic_info,
414                ref fields,
415            } => {
416                if basic_info.has_repetition() {
417                    write!(
418                        self.output,
419                        "{} group {} ",
420                        basic_info.repetition(),
421                        basic_info.name()
422                    );
423                    if basic_info.has_id() {
424                        write!(self.output, "[{}] ", basic_info.id());
425                    }
426                    let logical_str = print_logical_and_converted(
427                        basic_info.logical_type_ref(),
428                        basic_info.converted_type(),
429                        0,
430                        0,
431                    );
432                    if !logical_str.is_empty() {
433                        write!(self.output, "({logical_str}) ");
434                    }
435                } else {
436                    write!(self.output, "message {} ", basic_info.name());
437                    if basic_info.has_id() {
438                        write!(self.output, "[{}] ", basic_info.id());
439                    }
440                }
441                writeln!(self.output, "{{");
442
443                self.indent += INDENT_WIDTH;
444                for c in fields {
445                    self.print(c);
446                    writeln!(self.output);
447                }
448                self.indent -= INDENT_WIDTH;
449                self.print_indent();
450                write!(self.output, "}}");
451            }
452        }
453    }
454}
455
456#[cfg(test)]
457mod tests {
458    use super::*;
459
460    use std::sync::Arc;
461
462    use crate::basic::{Repetition, Type as PhysicalType};
463    use crate::errors::Result;
464    use crate::schema::parser::parse_message_type;
465
466    fn assert_print_parse_message(message: Type) {
467        let mut s = String::new();
468        {
469            let mut p = Printer::new(&mut s);
470            p.print(&message);
471        }
472        println!("{s}");
473        let parsed = parse_message_type(&s).unwrap();
474        assert_eq!(message, parsed);
475    }
476
477    #[test]
478    fn test_print_primitive_type() {
479        let types_and_strings = vec![
480            (
481                Type::primitive_type_builder("field", PhysicalType::INT32)
482                    .with_repetition(Repetition::REQUIRED)
483                    .with_converted_type(ConvertedType::INT_32)
484                    .build()
485                    .unwrap(),
486                "REQUIRED INT32 field (INT_32);",
487            ),
488            (
489                Type::primitive_type_builder("field", PhysicalType::INT32)
490                    .with_repetition(Repetition::REQUIRED)
491                    .with_converted_type(ConvertedType::INT_32)
492                    .with_id(Some(42))
493                    .build()
494                    .unwrap(),
495                "REQUIRED INT32 field [42] (INT_32);",
496            ),
497            (
498                Type::primitive_type_builder("field", PhysicalType::INT32)
499                    .with_repetition(Repetition::REQUIRED)
500                    .build()
501                    .unwrap(),
502                "REQUIRED INT32 field;",
503            ),
504            (
505                Type::primitive_type_builder("field", PhysicalType::INT32)
506                    .with_repetition(Repetition::REQUIRED)
507                    .with_id(Some(42))
508                    .build()
509                    .unwrap(),
510                "REQUIRED INT32 field [42];",
511            ),
512        ];
513        types_and_strings.into_iter().for_each(|(field, expected)| {
514            let mut s = String::new();
515            {
516                let mut p = Printer::new(&mut s);
517                p.print(&field);
518            }
519            assert_eq!(&s, expected)
520        });
521    }
522
523    #[inline]
524    fn build_primitive_type(
525        name: &str,
526        id: Option<i32>,
527        physical_type: PhysicalType,
528        logical_type: Option<LogicalType>,
529        converted_type: ConvertedType,
530        repetition: Repetition,
531    ) -> Result<Type> {
532        Type::primitive_type_builder(name, physical_type)
533            .with_id(id)
534            .with_repetition(repetition)
535            .with_logical_type(logical_type)
536            .with_converted_type(converted_type)
537            .build()
538    }
539
540    #[test]
541    fn test_print_logical_types() {
542        let types_and_strings = vec![
543            (
544                build_primitive_type(
545                    "field",
546                    None,
547                    PhysicalType::INT32,
548                    Some(LogicalType::integer(32, true)),
549                    ConvertedType::NONE,
550                    Repetition::REQUIRED,
551                )
552                .unwrap(),
553                "REQUIRED INT32 field (INTEGER(32,true));",
554            ),
555            (
556                build_primitive_type(
557                    "field",
558                    None,
559                    PhysicalType::INT32,
560                    Some(LogicalType::integer(8, false)),
561                    ConvertedType::NONE,
562                    Repetition::OPTIONAL,
563                )
564                .unwrap(),
565                "OPTIONAL INT32 field (INTEGER(8,false));",
566            ),
567            (
568                build_primitive_type(
569                    "field",
570                    None,
571                    PhysicalType::INT32,
572                    Some(LogicalType::integer(16, true)),
573                    ConvertedType::INT_16,
574                    Repetition::REPEATED,
575                )
576                .unwrap(),
577                "REPEATED INT32 field (INTEGER(16,true));",
578            ),
579            (
580                build_primitive_type(
581                    "field",
582                    Some(42),
583                    PhysicalType::INT32,
584                    Some(LogicalType::integer(16, true)),
585                    ConvertedType::INT_16,
586                    Repetition::REPEATED,
587                )
588                .unwrap(),
589                "REPEATED INT32 field [42] (INTEGER(16,true));",
590            ),
591            (
592                build_primitive_type(
593                    "field",
594                    None,
595                    PhysicalType::INT64,
596                    None,
597                    ConvertedType::NONE,
598                    Repetition::REPEATED,
599                )
600                .unwrap(),
601                "REPEATED INT64 field;",
602            ),
603            (
604                build_primitive_type(
605                    "field",
606                    None,
607                    PhysicalType::FLOAT,
608                    None,
609                    ConvertedType::NONE,
610                    Repetition::REQUIRED,
611                )
612                .unwrap(),
613                "REQUIRED FLOAT field;",
614            ),
615            (
616                build_primitive_type(
617                    "booleans",
618                    None,
619                    PhysicalType::BOOLEAN,
620                    None,
621                    ConvertedType::NONE,
622                    Repetition::OPTIONAL,
623                )
624                .unwrap(),
625                "OPTIONAL BOOLEAN booleans;",
626            ),
627            (
628                build_primitive_type(
629                    "booleans",
630                    Some(42),
631                    PhysicalType::BOOLEAN,
632                    None,
633                    ConvertedType::NONE,
634                    Repetition::OPTIONAL,
635                )
636                .unwrap(),
637                "OPTIONAL BOOLEAN booleans [42];",
638            ),
639            (
640                build_primitive_type(
641                    "field",
642                    None,
643                    PhysicalType::INT64,
644                    Some(LogicalType::timestamp(true, TimeUnit::MILLIS)),
645                    ConvertedType::NONE,
646                    Repetition::REQUIRED,
647                )
648                .unwrap(),
649                "REQUIRED INT64 field (TIMESTAMP(MILLIS,true));",
650            ),
651            (
652                build_primitive_type(
653                    "field",
654                    None,
655                    PhysicalType::INT32,
656                    Some(LogicalType::Date),
657                    ConvertedType::NONE,
658                    Repetition::OPTIONAL,
659                )
660                .unwrap(),
661                "OPTIONAL INT32 field (DATE);",
662            ),
663            (
664                build_primitive_type(
665                    "field",
666                    None,
667                    PhysicalType::INT32,
668                    Some(LogicalType::time(false, TimeUnit::MILLIS)),
669                    ConvertedType::TIME_MILLIS,
670                    Repetition::REQUIRED,
671                )
672                .unwrap(),
673                "REQUIRED INT32 field (TIME(MILLIS,false));",
674            ),
675            (
676                build_primitive_type(
677                    "field",
678                    Some(42),
679                    PhysicalType::INT32,
680                    Some(LogicalType::time(false, TimeUnit::MILLIS)),
681                    ConvertedType::TIME_MILLIS,
682                    Repetition::REQUIRED,
683                )
684                .unwrap(),
685                "REQUIRED INT32 field [42] (TIME(MILLIS,false));",
686            ),
687            (
688                build_primitive_type(
689                    "field",
690                    None,
691                    PhysicalType::BYTE_ARRAY,
692                    None,
693                    ConvertedType::NONE,
694                    Repetition::REQUIRED,
695                )
696                .unwrap(),
697                "REQUIRED BYTE_ARRAY field;",
698            ),
699            (
700                build_primitive_type(
701                    "field",
702                    Some(42),
703                    PhysicalType::BYTE_ARRAY,
704                    None,
705                    ConvertedType::NONE,
706                    Repetition::REQUIRED,
707                )
708                .unwrap(),
709                "REQUIRED BYTE_ARRAY field [42];",
710            ),
711            (
712                build_primitive_type(
713                    "field",
714                    None,
715                    PhysicalType::BYTE_ARRAY,
716                    None,
717                    ConvertedType::UTF8,
718                    Repetition::REQUIRED,
719                )
720                .unwrap(),
721                "REQUIRED BYTE_ARRAY field (UTF8);",
722            ),
723            (
724                build_primitive_type(
725                    "field",
726                    None,
727                    PhysicalType::BYTE_ARRAY,
728                    Some(LogicalType::Json),
729                    ConvertedType::JSON,
730                    Repetition::REQUIRED,
731                )
732                .unwrap(),
733                "REQUIRED BYTE_ARRAY field (JSON);",
734            ),
735            (
736                build_primitive_type(
737                    "field",
738                    None,
739                    PhysicalType::BYTE_ARRAY,
740                    Some(LogicalType::Bson),
741                    ConvertedType::BSON,
742                    Repetition::REQUIRED,
743                )
744                .unwrap(),
745                "REQUIRED BYTE_ARRAY field (BSON);",
746            ),
747            (
748                build_primitive_type(
749                    "field",
750                    None,
751                    PhysicalType::BYTE_ARRAY,
752                    Some(LogicalType::String),
753                    ConvertedType::NONE,
754                    Repetition::REQUIRED,
755                )
756                .unwrap(),
757                "REQUIRED BYTE_ARRAY field (STRING);",
758            ),
759            (
760                build_primitive_type(
761                    "field",
762                    Some(42),
763                    PhysicalType::BYTE_ARRAY,
764                    Some(LogicalType::String),
765                    ConvertedType::NONE,
766                    Repetition::REQUIRED,
767                )
768                .unwrap(),
769                "REQUIRED BYTE_ARRAY field [42] (STRING);",
770            ),
771            (
772                build_primitive_type(
773                    "field",
774                    None,
775                    PhysicalType::BYTE_ARRAY,
776                    Some(LogicalType::geometry(None)),
777                    ConvertedType::NONE,
778                    Repetition::REQUIRED,
779                )
780                .unwrap(),
781                "REQUIRED BYTE_ARRAY field (GEOMETRY);",
782            ),
783            (
784                build_primitive_type(
785                    "field",
786                    None,
787                    PhysicalType::BYTE_ARRAY,
788                    Some(LogicalType::geometry(Some("non-missing CRS".to_string()))),
789                    ConvertedType::NONE,
790                    Repetition::REQUIRED,
791                )
792                .unwrap(),
793                "REQUIRED BYTE_ARRAY field (GEOMETRY(non-missing CRS));",
794            ),
795            (
796                build_primitive_type(
797                    "field",
798                    None,
799                    PhysicalType::BYTE_ARRAY,
800                    Some(LogicalType::geography(None, Some(Default::default()))),
801                    ConvertedType::NONE,
802                    Repetition::REQUIRED,
803                )
804                .unwrap(),
805                "REQUIRED BYTE_ARRAY field (GEOGRAPHY(SPHERICAL));",
806            ),
807            (
808                build_primitive_type(
809                    "field",
810                    None,
811                    PhysicalType::BYTE_ARRAY,
812                    Some(LogicalType::geography(
813                        Some("non-missing CRS".to_string()),
814                        Some(Default::default()),
815                    )),
816                    ConvertedType::NONE,
817                    Repetition::REQUIRED,
818                )
819                .unwrap(),
820                "REQUIRED BYTE_ARRAY field (GEOGRAPHY(SPHERICAL, non-missing CRS));",
821            ),
822        ];
823
824        types_and_strings.into_iter().for_each(|(field, expected)| {
825            let mut s = String::new();
826            {
827                let mut p = Printer::new(&mut s);
828                p.print(&field);
829            }
830            assert_eq!(&s, expected)
831        });
832    }
833
834    #[inline]
835    fn decimal_length_from_precision(precision: usize) -> i32 {
836        let max_val = 10.0_f64.powi(precision as i32) - 1.0;
837        let bits_unsigned = max_val.log2().ceil();
838        let bits_signed = bits_unsigned + 1.0;
839        (bits_signed / 8.0).ceil() as i32
840    }
841
842    #[test]
843    fn test_print_flba_logical_types() {
844        let types_and_strings = vec![
845            (
846                Type::primitive_type_builder("field", PhysicalType::FIXED_LEN_BYTE_ARRAY)
847                    .with_logical_type(None)
848                    .with_converted_type(ConvertedType::INTERVAL)
849                    .with_length(12)
850                    .with_repetition(Repetition::REQUIRED)
851                    .build()
852                    .unwrap(),
853                "REQUIRED FIXED_LEN_BYTE_ARRAY (12) field (INTERVAL);",
854            ),
855            (
856                Type::primitive_type_builder("field", PhysicalType::FIXED_LEN_BYTE_ARRAY)
857                    .with_logical_type(Some(LogicalType::Uuid))
858                    .with_length(16)
859                    .with_repetition(Repetition::REQUIRED)
860                    .build()
861                    .unwrap(),
862                "REQUIRED FIXED_LEN_BYTE_ARRAY (16) field (UUID);",
863            ),
864            (
865                Type::primitive_type_builder("decimal", PhysicalType::FIXED_LEN_BYTE_ARRAY)
866                    .with_logical_type(Some(LogicalType::decimal(20, 32)))
867                    .with_precision(32)
868                    .with_scale(20)
869                    .with_length(decimal_length_from_precision(32))
870                    .with_repetition(Repetition::REPEATED)
871                    .build()
872                    .unwrap(),
873                "REPEATED FIXED_LEN_BYTE_ARRAY (14) decimal (DECIMAL(32,20));",
874            ),
875            (
876                Type::primitive_type_builder("decimal", PhysicalType::FIXED_LEN_BYTE_ARRAY)
877                    .with_converted_type(ConvertedType::DECIMAL)
878                    .with_precision(19)
879                    .with_scale(4)
880                    .with_length(decimal_length_from_precision(19))
881                    .with_repetition(Repetition::OPTIONAL)
882                    .build()
883                    .unwrap(),
884                "OPTIONAL FIXED_LEN_BYTE_ARRAY (9) decimal (DECIMAL(19,4));",
885            ),
886            (
887                Type::primitive_type_builder("float16", PhysicalType::FIXED_LEN_BYTE_ARRAY)
888                    .with_logical_type(Some(LogicalType::Float16))
889                    .with_length(2)
890                    .with_repetition(Repetition::REQUIRED)
891                    .build()
892                    .unwrap(),
893                "REQUIRED FIXED_LEN_BYTE_ARRAY (2) float16 (FLOAT16);",
894            ),
895        ];
896
897        types_and_strings.into_iter().for_each(|(field, expected)| {
898            let mut s = String::new();
899            {
900                let mut p = Printer::new(&mut s);
901                p.print(&field);
902            }
903            assert_eq!(&s, expected)
904        });
905    }
906
907    #[test]
908    fn test_print_schema_documentation() {
909        let mut s = String::new();
910        {
911            let mut p = Printer::new(&mut s);
912            let field_a = Type::primitive_type_builder("a", PhysicalType::BYTE_ARRAY)
913                .with_id(Some(42))
914                .with_converted_type(ConvertedType::UTF8)
915                .build()
916                .unwrap();
917
918            let field_b = Type::primitive_type_builder("b", PhysicalType::INT32)
919                .with_repetition(Repetition::REQUIRED)
920                .build()
921                .unwrap();
922
923            let field_d = Type::primitive_type_builder("d", PhysicalType::INT64)
924                .with_id(Some(99))
925                .build()
926                .unwrap();
927
928            let field_c = Type::group_type_builder("c")
929                .with_id(Some(43))
930                .with_fields(vec![Arc::new(field_d)])
931                .build()
932                .unwrap();
933
934            let schema = Type::group_type_builder("schema")
935                .with_fields(vec![
936                    Arc::new(field_a),
937                    Arc::new(field_b),
938                    Arc::new(field_c),
939                ])
940                .build()
941                .unwrap();
942            p.print(&schema);
943        }
944        let expected = "message schema {
945  OPTIONAL BYTE_ARRAY a [42] (UTF8);
946  REQUIRED INT32 b;
947  message c [43] {
948    OPTIONAL INT64 d [99];
949  }
950}";
951        assert_eq!(&mut s, expected);
952    }
953
954    #[test]
955    fn test_print_group_type() {
956        let mut s = String::new();
957        {
958            let mut p = Printer::new(&mut s);
959            let f1 = Type::primitive_type_builder("f1", PhysicalType::INT32)
960                .with_repetition(Repetition::REQUIRED)
961                .with_converted_type(ConvertedType::INT_32)
962                .build();
963            let f2 = Type::primitive_type_builder("f2", PhysicalType::BYTE_ARRAY)
964                .with_converted_type(ConvertedType::UTF8)
965                .build();
966            let f3 = Type::primitive_type_builder("f3", PhysicalType::BYTE_ARRAY)
967                .with_logical_type(Some(LogicalType::String))
968                .build();
969            let f4 = Type::primitive_type_builder("f4", PhysicalType::FIXED_LEN_BYTE_ARRAY)
970                .with_repetition(Repetition::REPEATED)
971                .with_converted_type(ConvertedType::INTERVAL)
972                .with_length(12)
973                .build();
974
975            let struct_fields = vec![
976                Arc::new(f1.unwrap()),
977                Arc::new(f2.unwrap()),
978                Arc::new(f3.unwrap()),
979            ];
980            let field = Type::group_type_builder("field")
981                .with_repetition(Repetition::OPTIONAL)
982                .with_fields(struct_fields)
983                .build()
984                .unwrap();
985
986            let fields = vec![Arc::new(field), Arc::new(f4.unwrap())];
987            let message = Type::group_type_builder("schema")
988                .with_fields(fields)
989                .build()
990                .unwrap();
991            p.print(&message);
992        }
993        let expected = "message schema {
994  OPTIONAL group field {
995    REQUIRED INT32 f1 (INT_32);
996    OPTIONAL BYTE_ARRAY f2 (UTF8);
997    OPTIONAL BYTE_ARRAY f3 (STRING);
998  }
999  REPEATED FIXED_LEN_BYTE_ARRAY (12) f4 (INTERVAL);
1000}";
1001        assert_eq!(&mut s, expected);
1002    }
1003
1004    #[test]
1005    fn test_print_group_type_with_ids() {
1006        let mut s = String::new();
1007        {
1008            let mut p = Printer::new(&mut s);
1009            let f1 = Type::primitive_type_builder("f1", PhysicalType::INT32)
1010                .with_repetition(Repetition::REQUIRED)
1011                .with_converted_type(ConvertedType::INT_32)
1012                .with_id(Some(0))
1013                .build();
1014            let f2 = Type::primitive_type_builder("f2", PhysicalType::BYTE_ARRAY)
1015                .with_converted_type(ConvertedType::UTF8)
1016                .with_id(Some(1))
1017                .build();
1018            let f3 = Type::primitive_type_builder("f3", PhysicalType::BYTE_ARRAY)
1019                .with_logical_type(Some(LogicalType::String))
1020                .with_id(Some(1))
1021                .build();
1022            let f4 = Type::primitive_type_builder("f4", PhysicalType::FIXED_LEN_BYTE_ARRAY)
1023                .with_repetition(Repetition::REPEATED)
1024                .with_converted_type(ConvertedType::INTERVAL)
1025                .with_length(12)
1026                .with_id(Some(2))
1027                .build();
1028
1029            let struct_fields = vec![
1030                Arc::new(f1.unwrap()),
1031                Arc::new(f2.unwrap()),
1032                Arc::new(f3.unwrap()),
1033            ];
1034            let field = Type::group_type_builder("field")
1035                .with_repetition(Repetition::OPTIONAL)
1036                .with_fields(struct_fields)
1037                .with_id(Some(1))
1038                .build()
1039                .unwrap();
1040
1041            let fields = vec![Arc::new(field), Arc::new(f4.unwrap())];
1042            let message = Type::group_type_builder("schema")
1043                .with_fields(fields)
1044                .with_id(Some(2))
1045                .build()
1046                .unwrap();
1047            p.print(&message);
1048        }
1049        let expected = "message schema [2] {
1050  OPTIONAL group field [1] {
1051    REQUIRED INT32 f1 [0] (INT_32);
1052    OPTIONAL BYTE_ARRAY f2 [1] (UTF8);
1053    OPTIONAL BYTE_ARRAY f3 [1] (STRING);
1054  }
1055  REPEATED FIXED_LEN_BYTE_ARRAY (12) f4 [2] (INTERVAL);
1056}";
1057        assert_eq!(&mut s, expected);
1058    }
1059
1060    #[test]
1061    fn test_print_and_parse_primitive() {
1062        let a2 = Type::primitive_type_builder("a2", PhysicalType::BYTE_ARRAY)
1063            .with_repetition(Repetition::REPEATED)
1064            .with_converted_type(ConvertedType::UTF8)
1065            .build()
1066            .unwrap();
1067
1068        let a1 = Type::group_type_builder("a1")
1069            .with_repetition(Repetition::OPTIONAL)
1070            .with_logical_type(Some(LogicalType::List))
1071            .with_converted_type(ConvertedType::LIST)
1072            .with_fields(vec![Arc::new(a2)])
1073            .build()
1074            .unwrap();
1075
1076        let b3 = Type::primitive_type_builder("b3", PhysicalType::INT32)
1077            .with_repetition(Repetition::OPTIONAL)
1078            .build()
1079            .unwrap();
1080
1081        let b4 = Type::primitive_type_builder("b4", PhysicalType::DOUBLE)
1082            .with_repetition(Repetition::OPTIONAL)
1083            .build()
1084            .unwrap();
1085
1086        let b2 = Type::group_type_builder("b2")
1087            .with_repetition(Repetition::REPEATED)
1088            .with_converted_type(ConvertedType::NONE)
1089            .with_fields(vec![Arc::new(b3), Arc::new(b4)])
1090            .build()
1091            .unwrap();
1092
1093        let b1 = Type::group_type_builder("b1")
1094            .with_repetition(Repetition::OPTIONAL)
1095            .with_logical_type(Some(LogicalType::List))
1096            .with_converted_type(ConvertedType::LIST)
1097            .with_fields(vec![Arc::new(b2)])
1098            .build()
1099            .unwrap();
1100
1101        let a0 = Type::group_type_builder("a0")
1102            .with_repetition(Repetition::REQUIRED)
1103            .with_fields(vec![Arc::new(a1), Arc::new(b1)])
1104            .build()
1105            .unwrap();
1106
1107        let message = Type::group_type_builder("root")
1108            .with_fields(vec![Arc::new(a0)])
1109            .build()
1110            .unwrap();
1111
1112        assert_print_parse_message(message);
1113    }
1114
1115    #[test]
1116    fn test_print_and_parse_nested() {
1117        let f1 = Type::primitive_type_builder("f1", PhysicalType::INT32)
1118            .with_repetition(Repetition::REQUIRED)
1119            .with_converted_type(ConvertedType::INT_32)
1120            .build()
1121            .unwrap();
1122
1123        let f2 = Type::primitive_type_builder("f2", PhysicalType::BYTE_ARRAY)
1124            .with_repetition(Repetition::OPTIONAL)
1125            .with_converted_type(ConvertedType::UTF8)
1126            .build()
1127            .unwrap();
1128
1129        let field = Type::group_type_builder("field")
1130            .with_repetition(Repetition::OPTIONAL)
1131            .with_fields(vec![Arc::new(f1), Arc::new(f2)])
1132            .build()
1133            .unwrap();
1134
1135        let f3 = Type::primitive_type_builder("f3", PhysicalType::FIXED_LEN_BYTE_ARRAY)
1136            .with_repetition(Repetition::REPEATED)
1137            .with_converted_type(ConvertedType::INTERVAL)
1138            .with_length(12)
1139            .build()
1140            .unwrap();
1141
1142        let message = Type::group_type_builder("schema")
1143            .with_fields(vec![Arc::new(field), Arc::new(f3)])
1144            .build()
1145            .unwrap();
1146
1147        assert_print_parse_message(message);
1148    }
1149
1150    #[test]
1151    fn test_print_and_parse_decimal() {
1152        let f1 = Type::primitive_type_builder("f1", PhysicalType::INT32)
1153            .with_repetition(Repetition::OPTIONAL)
1154            .with_logical_type(Some(LogicalType::decimal(2, 9)))
1155            .with_converted_type(ConvertedType::DECIMAL)
1156            .with_precision(9)
1157            .with_scale(2)
1158            .build()
1159            .unwrap();
1160
1161        let f2 = Type::primitive_type_builder("f2", PhysicalType::INT32)
1162            .with_repetition(Repetition::OPTIONAL)
1163            .with_logical_type(Some(LogicalType::decimal(0, 9)))
1164            .with_converted_type(ConvertedType::DECIMAL)
1165            .with_precision(9)
1166            .with_scale(0)
1167            .build()
1168            .unwrap();
1169
1170        let message = Type::group_type_builder("schema")
1171            .with_fields(vec![Arc::new(f1), Arc::new(f2)])
1172            .build()
1173            .unwrap();
1174
1175        assert_print_parse_message(message);
1176    }
1177
1178    #[test]
1179    fn test_print_file_type() {
1180        let mut s = String::new();
1181        {
1182            let mut p = Printer::new(&mut s);
1183            let uri_field = Arc::new(
1184                Type::primitive_type_builder("uri", PhysicalType::BYTE_ARRAY)
1185                    .with_repetition(Repetition::OPTIONAL)
1186                    .with_logical_type(Some(LogicalType::String))
1187                    .build()
1188                    .unwrap(),
1189            );
1190            let size_field = Arc::new(
1191                Type::primitive_type_builder("size", PhysicalType::INT64)
1192                    .with_repetition(Repetition::OPTIONAL)
1193                    .build()
1194                    .unwrap(),
1195            );
1196            let file_field = Type::group_type_builder("f")
1197                .with_repetition(Repetition::REQUIRED)
1198                .with_logical_type(Some(LogicalType::File))
1199                .with_fields(vec![uri_field, size_field])
1200                .build()
1201                .unwrap();
1202            let message = Type::group_type_builder("schema")
1203                .with_fields(vec![Arc::new(file_field)])
1204                .build()
1205                .unwrap();
1206            p.print(&message);
1207        }
1208        let expected = "message schema {
1209  REQUIRED group f (FILE) {
1210    OPTIONAL BYTE_ARRAY uri (STRING);
1211    OPTIONAL INT64 size;
1212  }
1213}";
1214        assert_eq!(&mut s, expected);
1215    }
1216}