Skip to main content

parquet/schema/
printer.rs

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