Skip to main content

parquet/
basic.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//! Contains Rust mappings for Thrift definition. This module contains only mappings for thrift
6//! enums and unions. Thrift structs are handled elsewhere.
7//! Refer to [`parquet.thrift`](https://github.com/apache/parquet-format/blob/master/src/main/thrift/parquet.thrift)
8//! file to see raw definitions.
9
10use std::io::Write;
11use std::str::FromStr;
12use std::{fmt, str};
13
14pub use crate::compression::{BrotliLevel, GzipLevel, ZstdLevel};
15use crate::file::metadata::HeapSize;
16use crate::parquet_thrift::{
17    ElementType, FieldType, ReadThrift, ThriftCompactInputProtocol, ThriftCompactOutputProtocol,
18    WriteThrift, WriteThriftField, validate_list_type,
19};
20use crate::{
21    thrift_enum, thrift_struct, thrift_union_all_empty, thrift_union_with_unknown,
22    write_thrift_field,
23};
24
25use crate::errors::{ParquetError, Result};
26
27// ----------------------------------------------------------------------
28// Types from the Thrift definition
29
30// ----------------------------------------------------------------------
31// Mirrors thrift enum `Type`
32
33thrift_enum!(
34/// Types supported by Parquet.
35///
36/// These physical types are intended to be used in combination with the encodings to
37/// control the on disk storage format.
38/// For example INT16 is not included as a type since a good encoding of INT32
39/// would handle this.
40enum Type {
41  BOOLEAN = 0;
42  INT32 = 1;
43  INT64 = 2;
44  INT96 = 3;  // deprecated, only used by legacy implementations.
45  FLOAT = 4;
46  DOUBLE = 5;
47  BYTE_ARRAY = 6;
48  FIXED_LEN_BYTE_ARRAY = 7;
49}
50);
51
52// ----------------------------------------------------------------------
53// Mirrors thrift enum `ConvertedType`
54
55// TODO(ets): Adding the `NONE` variant to this enum is a bit awkward. We should
56// look into removing it and using `Option<ConvertedType>` instead.
57thrift_enum!(
58/// Common types (converted types) used by frameworks when using Parquet.
59///
60/// This helps map between types in those frameworks to the base types in Parquet.
61/// This is only metadata and not needed to read or write the data.
62///
63/// This struct was renamed from `LogicalType` in version 4.0.0.
64/// If targeting Parquet format 2.4.0 or above, please use [LogicalType] instead.
65enum ConvertedType {
66  /// Not defined in the spec, used internally to indicate no type conversion
67  NONE = -1;
68
69  /// A BYTE_ARRAY actually contains UTF8 encoded chars.
70  UTF8 = 0;
71
72  /// A map is converted as an optional field containing a repeated key/value pair.
73  MAP = 1;
74
75  /// A key/value pair is converted into a group of two fields.
76  MAP_KEY_VALUE = 2;
77
78  /// A list is converted into an optional field containing a repeated field for its
79  /// values.
80  LIST = 3;
81
82  /// An enum is converted into a BYTE_ARRAY field
83  ENUM = 4;
84
85  /// A decimal value.
86  ///
87  /// This may be used to annotate BYTE_ARRAY or FIXED_LEN_BYTE_ARRAY primitive
88  /// types. The underlying byte array stores the unscaled value encoded as two's
89  /// complement using big-endian byte order (the most significant byte is the
90  /// zeroth element). The value of the decimal is the value * 10^{-scale}.
91  ///
92  /// This must be accompanied by a (maximum) precision and a scale in the
93  /// SchemaElement. The precision specifies the number of digits in the decimal
94  /// and the scale stores the location of the decimal point. For example 1.23
95  /// would have precision 3 (3 total digits) and scale 2 (the decimal point is
96  /// 2 digits over).
97  DECIMAL = 5;
98
99  /// A date stored as days since Unix epoch, encoded as the INT32 physical type.
100  DATE = 6;
101
102  /// The total number of milliseconds since midnight. The value is stored as an INT32
103  /// physical type.
104  TIME_MILLIS = 7;
105
106  /// The total number of microseconds since midnight. The value is stored as an INT64
107  /// physical type.
108  TIME_MICROS = 8;
109
110  /// Date and time recorded as milliseconds since the Unix epoch.
111  /// Recorded as a physical type of INT64.
112  TIMESTAMP_MILLIS = 9;
113
114  /// Date and time recorded as microseconds since the Unix epoch.
115  /// The value is stored as an INT64 physical type.
116  TIMESTAMP_MICROS = 10;
117
118  /// An unsigned 8 bit integer value stored as INT32 physical type.
119  UINT_8 = 11;
120
121  /// An unsigned 16 bit integer value stored as INT32 physical type.
122  UINT_16 = 12;
123
124  /// An unsigned 32 bit integer value stored as INT32 physical type.
125  UINT_32 = 13;
126
127  /// An unsigned 64 bit integer value stored as INT64 physical type.
128  UINT_64 = 14;
129
130  /// A signed 8 bit integer value stored as INT32 physical type.
131  INT_8 = 15;
132
133  /// A signed 16 bit integer value stored as INT32 physical type.
134  INT_16 = 16;
135
136  /// A signed 32 bit integer value stored as INT32 physical type.
137  INT_32 = 17;
138
139  /// A signed 64 bit integer value stored as INT64 physical type.
140  INT_64 = 18;
141
142  /// A JSON document embedded within a single UTF8 column.
143  JSON = 19;
144
145   /// A BSON document embedded within a single BINARY column.
146  BSON = 20;
147
148  /// An interval of time
149  ///
150  /// This type annotates data stored as a FIXED_LEN_BYTE_ARRAY of length 12.
151  /// This data is composed of three separate little endian unsigned integers.
152  /// Each stores a component of a duration of time. The first integer identifies
153  /// the number of months associated with the duration, the second identifies
154  /// the number of days associated with the duration and the third identifies
155  /// the number of milliseconds associated with the provided duration.
156  /// This duration of time is independent of any particular timezone or date.
157  INTERVAL = 21;
158}
159);
160
161// ----------------------------------------------------------------------
162// Mirrors thrift union `TimeUnit`
163
164thrift_union_all_empty!(
165/// Time unit for `Time` and `Timestamp` logical types.
166union TimeUnit {
167  1: MilliSeconds MILLIS
168  2: MicroSeconds MICROS
169  3: NanoSeconds NANOS
170}
171);
172
173// ----------------------------------------------------------------------
174// Mirrors thrift union `LogicalType`
175
176thrift_struct!(
177pub struct DecimalType {
178  /// The number of digits in the decimal.
179  1: required i32 scale
180  /// The location of the decimal point.
181  2: required i32 precision
182}
183);
184
185thrift_struct!(
186pub struct TimestampType {
187  /// Whether the timestamp is adjusted to UTC.
188  1: required bool is_adjusted_to_u_t_c
189  /// The unit of time.
190  2: required TimeUnit unit
191}
192);
193
194/// Identical to [`TimestampType`]
195pub use TimestampType as TimeType;
196
197thrift_struct!(
198pub struct IntType {
199  /// The number of bits in the integer.
200  1: required i8 bit_width
201  /// Whether the integer is signed.
202  2: required bool is_signed
203}
204);
205
206thrift_struct!(
207pub struct VariantType {
208  /// The version of the variant specification that the variant was
209  /// written with.
210  1: optional i8 specification_version
211}
212);
213
214thrift_struct!(
215pub struct GeometryType {
216  /// A custom CRS. If unset the CRS `OGC:CRS84` should be used, which means that the geometries
217  /// must be stored in longitude, latitude based on the WGS84 datum.
218  1: optional string crs;
219}
220);
221
222thrift_struct!(
223pub struct GeographyType {
224  /// A custom CRS. If unset the CRS `OGC:CRS84` should be used.
225  1: optional string crs;
226  /// An optional algorithm can be set to correctly interpret edges interpolation
227  /// of the geometries. If unset, the `SPHERICAL` algorithm should be used.
228  2: optional EdgeInterpolationAlgorithm algorithm;
229}
230);
231
232impl GeographyType {
233    /// Accessor for the `GeographyType::algorithm` field. If this field is not set, this
234    /// function returns the default value (currently [`EdgeInterpolationAlgorithm::SPHERICAL`]
235    /// per the Parquet [specification]).
236    ///
237    /// [specification]: https://github.com/apache/parquet-format/blob/master/LogicalTypes.md#geography
238    pub fn algorithm(&self) -> Option<EdgeInterpolationAlgorithm> {
239        Some(self.algorithm.unwrap_or_default())
240    }
241}
242
243thrift_union_with_unknown!(
244/// Logical types used by version 2.4.0+ of the Parquet format.
245///
246/// This is an *entirely new* struct as of version
247/// 4.0.0. The struct previously named `LogicalType` was renamed to
248/// [`ConvertedType`]. Please see the README.md for more details.
249union LogicalType {
250   /// A UTF8 encoded string.
251   1:  String
252   /// A map of key-value pairs.
253   2:  Map
254   /// A list of elements.
255   3:  List
256   /// A set of predefined values.
257   4:  Enum
258   /// A decimal value with a specified scale and precision.
259   5:  (DecimalType) Decimal
260   /// A date stored as days since Unix epoch.
261   6:  Date
262   /// A time stored as [`TimeUnit`] since midnight.
263   7:  (TimeType) Time
264   /// A timestamp stored as [`TimeUnit`] since Unix epoch.
265   8:  (TimestampType) Timestamp
266   // 9: reserved for INTERVAL
267   /// An integer with a specified bit width and signedness.
268   10: (IntType) Integer
269   /// An unknown logical type.
270   11: Unknown
271   /// A JSON document.
272   12: Json
273   /// A BSON document.
274   13: Bson
275   /// A UUID.
276   14: Uuid
277   /// A 16-bit floating point number.
278   15: Float16
279   /// A Variant value.
280   16: (VariantType) Variant
281   /// A geospatial feature in the Well-Known Binary (WKB) format with linear/planar edges interpolation.
282   17: (GeometryType) Geometry
283   /// A geospatial feature in the WKB format with an explicit (non-linear/non-planar) edges interpolation.
284   18: (GeographyType) Geography
285   /// A reference to a range of bytes, stored inline or in an external file.
286   19: File
287}
288);
289
290impl LogicalType {
291    /// Create a [`LogicalType::Integer`] variant with the given `bit_width` and `is_signed`
292    pub fn integer(bit_width: i8, is_signed: bool) -> Self {
293        Self::Integer(IntType {
294            bit_width,
295            is_signed,
296        })
297    }
298
299    /// Create a [`LogicalType::Decimal`] variant with the given `scale` and `precision`
300    pub fn decimal(scale: i32, precision: i32) -> Self {
301        Self::Decimal(DecimalType { scale, precision })
302    }
303
304    /// Create a [`LogicalType::Time`] variant with the given `is_adjusted_to_u_t_c` and `unit`
305    pub fn time(is_adjusted_to_u_t_c: bool, unit: TimeUnit) -> Self {
306        Self::Time(TimeType {
307            is_adjusted_to_u_t_c,
308            unit,
309        })
310    }
311
312    /// Create a [`LogicalType::Timestamp`] variant with the given `is_adjusted_to_u_t_c` and `unit`
313    pub fn timestamp(is_adjusted_to_u_t_c: bool, unit: TimeUnit) -> Self {
314        Self::Timestamp(TimestampType {
315            is_adjusted_to_u_t_c,
316            unit,
317        })
318    }
319
320    /// Create a [`LogicalType::Variant`] variant with the given `specification_version`
321    pub fn variant(specification_version: Option<i8>) -> Self {
322        Self::Variant(VariantType {
323            specification_version,
324        })
325    }
326
327    /// Create a [`LogicalType::Geometry`] variant with the given `crs`
328    pub fn geometry(crs: Option<String>) -> Self {
329        Self::Geometry(GeometryType { crs })
330    }
331
332    /// Create a [`LogicalType::Geography`] variant with the given `crs` and `algorithm`
333    pub fn geography(crs: Option<String>, algorithm: Option<EdgeInterpolationAlgorithm>) -> Self {
334        Self::Geography(GeographyType { crs, algorithm })
335    }
336}
337
338// ----------------------------------------------------------------------
339// Mirrors thrift enum `FieldRepetitionType`
340//
341
342thrift_enum!(
343/// Representation of field types in schema.
344enum FieldRepetitionType {
345  /// This field is required (can not be null) and each row has exactly 1 value.
346  REQUIRED = 0;
347  /// The field is optional (can be null) and each row has 0 or 1 values.
348  OPTIONAL = 1;
349  /// The field is repeated and can contain 0 or more values.
350  REPEATED = 2;
351}
352);
353
354/// Type alias for thrift `FieldRepetitionType`
355pub type Repetition = FieldRepetitionType;
356
357// ----------------------------------------------------------------------
358// Mirrors thrift enum `Encoding`
359
360thrift_enum!(
361/// Encodings supported by Parquet.
362///
363/// Not all encodings are valid for all types. These enums are also used to specify the
364/// encoding of definition and repetition levels.
365///
366/// By default this crate uses [Encoding::PLAIN], [Encoding::RLE], and [Encoding::RLE_DICTIONARY].
367/// These provide very good encode and decode performance, whilst yielding reasonable storage
368/// efficiency and being supported by all major parquet readers.
369///
370/// The delta encodings are also supported and will be used if a newer [WriterVersion] is
371/// configured, however, it should be noted that these sacrifice encode and decode performance for
372/// improved storage efficiency. This performance regression is particularly pronounced in the case
373/// of record skipping as occurs during predicate push-down. It is recommended users assess the
374/// performance impact when evaluating these encodings.
375///
376/// [WriterVersion]: crate::file::properties::WriterVersion
377enum Encoding {
378  /// Default encoding.
379  /// - BOOLEAN - 1 bit per value. 0 is false; 1 is true.
380  /// - INT32 - 4 bytes per value.  Stored as little-endian.
381  /// - INT64 - 8 bytes per value.  Stored as little-endian.
382  /// - FLOAT - 4 bytes per value.  IEEE. Stored as little-endian.
383  /// - DOUBLE - 8 bytes per value.  IEEE. Stored as little-endian.
384  /// - BYTE_ARRAY - 4 byte length stored as little endian, followed by bytes.
385  /// - FIXED_LEN_BYTE_ARRAY - Just the bytes.
386  PLAIN = 0;
387  //  GROUP_VAR_INT = 1;
388  /// **Deprecated** dictionary encoding.
389  ///
390  /// The values in the dictionary are encoded using PLAIN encoding.
391  /// Since it is deprecated, RLE_DICTIONARY encoding is used for a data page, and
392  /// PLAIN encoding is used for dictionary page.
393  PLAIN_DICTIONARY = 2;
394  /// Group packed run length encoding.
395  ///
396  /// Usable for definition/repetition levels encoding and boolean values.
397  RLE = 3;
398  /// **Deprecated** Bit-packed encoding.
399  ///
400  /// This can only be used if the data has a known max width.
401  /// Usable for definition/repetition levels encoding.
402  ///
403  /// There are compatibility issues with files using this encoding.
404  /// The parquet standard specifies the bits to be packed starting from the
405  /// most-significant bit, several implementations do not follow this bit order.
406  /// Several other implementations also have issues reading this encoding
407  /// because of incorrect assumptions about the length of the encoded data.
408  ///
409  /// The RLE/bit-packing hybrid is more cpu and memory efficient and should be used instead.
410  #[deprecated(
411      since = "51.0.0",
412      note = "Please see documentation for compatibility issues and use the RLE/bit-packing hybrid encoding instead"
413  )]
414  BIT_PACKED = 4;
415  /// Delta encoding for integers, either INT32 or INT64.
416  ///
417  /// Works best on sorted data.
418  DELTA_BINARY_PACKED = 5;
419  /// Encoding for byte arrays to separate the length values and the data.
420  ///
421  /// The lengths are encoded using DELTA_BINARY_PACKED encoding.
422  DELTA_LENGTH_BYTE_ARRAY = 6;
423  /// Incremental encoding for byte arrays.
424  ///
425  /// Prefix lengths are encoded using DELTA_BINARY_PACKED encoding.
426  /// Suffixes are stored using DELTA_LENGTH_BYTE_ARRAY encoding.
427  DELTA_BYTE_ARRAY = 7;
428  /// Dictionary encoding.
429  ///
430  /// The ids are encoded using the RLE encoding.
431  RLE_DICTIONARY = 8;
432  /// Encoding for fixed-width data.
433  ///
434  /// K byte-streams are created where K is the size in bytes of the data type.
435  /// The individual bytes of a value are scattered to the corresponding stream and
436  /// the streams are concatenated.
437  /// This itself does not reduce the size of the data but can lead to better compression
438  /// afterwards. Note that the use of this encoding with FIXED_LEN_BYTE_ARRAY(N) data may
439  /// perform poorly for large values of N.
440  BYTE_STREAM_SPLIT = 9;
441  /// Adaptive Lossless floating-Point encoding (ALP).
442  ///
443  /// Currently specified for FLOAT and DOUBLE.
444  ALP = 10;
445}
446);
447
448impl FromStr for Encoding {
449    type Err = ParquetError;
450
451    fn from_str(s: &str) -> Result<Self, Self::Err> {
452        match s {
453            "PLAIN" | "plain" => Ok(Encoding::PLAIN),
454            "PLAIN_DICTIONARY" | "plain_dictionary" => Ok(Encoding::PLAIN_DICTIONARY),
455            "RLE" | "rle" => Ok(Encoding::RLE),
456            #[expect(deprecated)]
457            "BIT_PACKED" | "bit_packed" => Ok(Encoding::BIT_PACKED),
458            "DELTA_BINARY_PACKED" | "delta_binary_packed" => Ok(Encoding::DELTA_BINARY_PACKED),
459            "DELTA_LENGTH_BYTE_ARRAY" | "delta_length_byte_array" => {
460                Ok(Encoding::DELTA_LENGTH_BYTE_ARRAY)
461            }
462            "DELTA_BYTE_ARRAY" | "delta_byte_array" => Ok(Encoding::DELTA_BYTE_ARRAY),
463            "RLE_DICTIONARY" | "rle_dictionary" => Ok(Encoding::RLE_DICTIONARY),
464            "BYTE_STREAM_SPLIT" | "byte_stream_split" => Ok(Encoding::BYTE_STREAM_SPLIT),
465            "ALP" | "alp" => Ok(Encoding::ALP),
466            _ => Err(general_err!("unknown encoding: {}", s)),
467        }
468    }
469}
470
471/// A bitmask representing the [`Encoding`]s employed while encoding a Parquet column chunk.
472///
473/// The Parquet [`ColumnMetaData`] struct contains an array that indicates what encodings were
474/// used when writing that column chunk. For memory and performance reasons, this crate reduces
475/// that array to bitmask, where each bit position represents a different [`Encoding`]. This
476/// struct contains that bitmask, and provides methods to interact with the data.
477///
478/// # Example
479/// ```no_run
480/// # use parquet::file::metadata::ParquetMetaDataReader;
481/// # use parquet::basic::Encoding;
482/// # fn open_parquet_file(path: &str) -> std::fs::File { unimplemented!(); }
483/// // read parquet metadata from a file
484/// let file = open_parquet_file("some_path.parquet");
485/// let mut reader = ParquetMetaDataReader::new();
486/// reader.try_parse(&file).unwrap();
487/// let metadata = reader.finish().unwrap();
488///
489/// // find the encodings used by the first column chunk in the first row group
490/// let col_meta = metadata.row_group(0).column(0);
491/// let encodings = col_meta.encodings_mask();
492///
493/// // check to see if a particular encoding was used
494/// let used_rle = encodings.is_set(Encoding::RLE);
495///
496/// // check to see if all of a set of encodings were used
497/// let used_all = encodings.all_set([Encoding::RLE, Encoding::PLAIN].iter());
498///
499/// // convert mask to a Vec<Encoding>
500/// let encodings_vec = encodings.encodings().collect::<Vec<_>>();
501/// ```
502///
503/// [`ColumnMetaData`]: https://github.com/apache/parquet-format/blob/9fd57b59e0ce1a82a69237dcf8977d3e72a2965d/src/main/thrift/parquet.thrift#L875
504#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
505pub struct EncodingMask(i32);
506
507impl EncodingMask {
508    /// Highest valued discriminant in the [`Encoding`] enum
509    const MAX_ENCODING: i32 = Encoding::MAX_DISCRIMINANT;
510    /// A mask consisting of unused bit positions, used for validation. This includes the never
511    /// used GROUP_VAR_INT encoding value of `1`.
512    const ALLOWED_MASK: u32 =
513        !(1u32 << (EncodingMask::MAX_ENCODING as u32 + 1)).wrapping_sub(1) | (1 << 1);
514
515    /// Attempt to create a new `EncodingMask` from an integer.
516    ///
517    /// This will return an error if a bit outside the allowable range is set.
518    pub fn try_new(val: i32) -> Result<Self> {
519        if val as u32 & Self::ALLOWED_MASK != 0 {
520            return Err(general_err!("Attempt to create invalid mask: 0x{:x}", val));
521        }
522        Ok(Self(val))
523    }
524
525    /// Return an integer representation of this `EncodingMask`.
526    pub fn as_i32(&self) -> i32 {
527        self.0
528    }
529
530    /// Create a new `EncodingMask` from a collection of [`Encoding`]s.
531    pub fn new_from_encodings<'a>(encodings: impl Iterator<Item = &'a Encoding>) -> Self {
532        let mut mask = 0;
533        for &e in encodings {
534            mask |= 1 << (e as i32);
535        }
536        Self(mask)
537    }
538
539    /// Mark the given [`Encoding`] as present in this mask.
540    pub fn insert(&mut self, val: Encoding) {
541        self.0 |= 1 << (val as i32);
542    }
543
544    /// Test if a given [`Encoding`] is present in this mask.
545    pub fn is_set(&self, val: Encoding) -> bool {
546        self.0 & (1 << (val as i32)) != 0
547    }
548
549    /// Test if this mask has only the bit for the given [`Encoding`] set.
550    pub fn is_only(&self, val: Encoding) -> bool {
551        self.0 == (1 << (val as i32))
552    }
553
554    /// Test if all [`Encoding`]s in a given set are present in this mask.
555    pub fn all_set<'a>(&self, mut encodings: impl Iterator<Item = &'a Encoding>) -> bool {
556        encodings.all(|&e| self.is_set(e))
557    }
558
559    /// Return an iterator over all [`Encoding`]s present in this mask.
560    pub fn encodings(&self) -> impl Iterator<Item = Encoding> {
561        Self::mask_to_encodings_iter(self.0)
562    }
563
564    fn mask_to_encodings_iter(mask: i32) -> impl Iterator<Item = Encoding> {
565        (0..=Self::MAX_ENCODING)
566            .filter(move |i| mask & (1 << i) != 0)
567            .map(i32_to_encoding)
568    }
569}
570
571impl HeapSize for EncodingMask {
572    fn heap_size(&self) -> usize {
573        0 // no heap allocations
574    }
575}
576
577impl<'a, R: ThriftCompactInputProtocol<'a>> ReadThrift<'a, R> for EncodingMask {
578    fn read_thrift(prot: &mut R) -> Result<Self> {
579        let mut mask = 0;
580
581        // This reads a Thrift `list<Encoding>` and turns it into a bitmask
582        let list_ident = prot.read_list_begin()?;
583        // check for enum (encoded as I32)
584        validate_list_type(ElementType::I32, &list_ident)?;
585        for _ in 0..list_ident.size {
586            let val = Encoding::read_thrift(prot)?;
587            mask |= 1 << val as i32;
588        }
589        Ok(Self(mask))
590    }
591}
592
593#[expect(deprecated)]
594fn i32_to_encoding(val: i32) -> Encoding {
595    match val {
596        0 => Encoding::PLAIN,
597        2 => Encoding::PLAIN_DICTIONARY,
598        3 => Encoding::RLE,
599        4 => Encoding::BIT_PACKED,
600        5 => Encoding::DELTA_BINARY_PACKED,
601        6 => Encoding::DELTA_LENGTH_BYTE_ARRAY,
602        7 => Encoding::DELTA_BYTE_ARRAY,
603        8 => Encoding::RLE_DICTIONARY,
604        9 => Encoding::BYTE_STREAM_SPLIT,
605        10 => Encoding::ALP,
606        _ => panic!("Impossible encoding {val}"),
607    }
608}
609
610// ----------------------------------------------------------------------
611// Mirrors thrift enum `CompressionCodec`
612
613thrift_enum!(
614/// Supported compression algorithms.
615///
616/// Codecs added in format version X.Y can be read by readers based on X.Y and later.
617/// Codec support may vary between readers based on the format version and
618/// libraries available at runtime.
619///
620/// See [Compression.md] for a detailed specification of these algorithms.
621///
622/// [Compression.md]: https://github.com/apache/parquet-format/blob/master/Compression.md
623enum CompressionCodec {
624  UNCOMPRESSED = 0;
625  SNAPPY = 1;
626  GZIP = 2;
627  LZO = 3;
628  BROTLI = 4;  // Added in 2.4
629  LZ4 = 5;     // DEPRECATED (Added in 2.4)
630  ZSTD = 6;    // Added in 2.4
631  LZ4_RAW = 7; // Added in 2.9
632}
633);
634
635// NOTE: This enum likely belongs in file::properties now, but moving it there would be a
636// breaking API change, that's probably not worth the pain. If a new codec is added to the
637// Parquet specification, or any other breaking changes are made to this enum, this can be
638// revisited.
639
640/// Supported block compression algorithms.
641///
642/// Block compression can yield non-trivial improvements to storage efficiency at the expense
643/// of potentially significantly worse encode and decode performance. Many applications,
644/// especially those making use of high-throughput and low-cost commodity object storage,
645/// may find storage efficiency less important than decode throughput, and therefore may
646/// wish to not make use of block compression.
647///
648/// The writers in this crate default to no block compression for this reason.
649///
650/// Applications that do still wish to use block compression, will find [`Compression::ZSTD`]
651/// to provide a good balance of compression, performance, and ecosystem support. Alternatively,
652/// [`Compression::LZ4_RAW`] provides much faster decompression speeds, at the cost of typically
653/// worse compression ratios. However, it is not as widely supported by the ecosystem, with the
654/// Hadoop ecosystem historically favoring the non-standard and now deprecated [`Compression::LZ4`].
655#[derive(Debug, Clone, Copy, PartialEq, Eq)]
656#[expect(non_camel_case_types)]
657pub enum Compression {
658    /// No compression.
659    UNCOMPRESSED,
660    /// [Snappy compression](https://en.wikipedia.org/wiki/Snappy_(compression))
661    SNAPPY,
662    /// [Gzip compression](https://www.ietf.org/rfc/rfc1952.txt)
663    GZIP(GzipLevel),
664    /// [LZO compression](https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Oberhumer)
665    LZO,
666    /// [Brotli compression](https://datatracker.ietf.org/doc/html/rfc7932)
667    BROTLI(BrotliLevel),
668    /// [LZ4 compression](https://lz4.org/), [(deprecated)](https://issues.apache.org/jira/browse/PARQUET-2032)
669    LZ4,
670    /// [ZSTD compression](https://datatracker.ietf.org/doc/html/rfc8878)
671    ZSTD(ZstdLevel),
672    /// [LZ4 compression](https://lz4.org/).
673    LZ4_RAW,
674}
675
676impl From<CompressionCodec> for Compression {
677    fn from(value: CompressionCodec) -> Self {
678        match value {
679            CompressionCodec::UNCOMPRESSED => Compression::UNCOMPRESSED,
680            CompressionCodec::SNAPPY => Compression::SNAPPY,
681            CompressionCodec::GZIP => Compression::GZIP(Default::default()),
682            CompressionCodec::LZO => Compression::LZO,
683            CompressionCodec::BROTLI => Compression::BROTLI(Default::default()),
684            CompressionCodec::LZ4 => Compression::LZ4,
685            CompressionCodec::ZSTD => Compression::ZSTD(Default::default()),
686            CompressionCodec::LZ4_RAW => Compression::LZ4_RAW,
687        }
688    }
689}
690
691impl From<Compression> for CompressionCodec {
692    fn from(value: Compression) -> Self {
693        match value {
694            Compression::UNCOMPRESSED => CompressionCodec::UNCOMPRESSED,
695            Compression::SNAPPY => CompressionCodec::SNAPPY,
696            Compression::GZIP(_) => CompressionCodec::GZIP,
697            Compression::LZO => CompressionCodec::LZO,
698            Compression::BROTLI(_) => CompressionCodec::BROTLI,
699            Compression::LZ4 => CompressionCodec::LZ4,
700            Compression::ZSTD(_) => CompressionCodec::ZSTD,
701            Compression::LZ4_RAW => CompressionCodec::LZ4_RAW,
702        }
703    }
704}
705
706fn split_compression_string(str_setting: &str) -> Result<(&str, Option<i32>), ParquetError> {
707    let split_setting = str_setting.split_once('(');
708
709    match split_setting {
710        Some((codec, level_str)) => {
711            let level = &level_str[..level_str.len() - 1]
712                .parse::<i32>()
713                .map_err(|_| {
714                    ParquetError::General(format!("invalid compression level: {level_str}"))
715                })?;
716            Ok((codec, Some(*level)))
717        }
718        None => Ok((str_setting, None)),
719    }
720}
721
722fn check_level_is_none(level: Option<i32>) -> Result<(), ParquetError> {
723    if level.is_some() {
724        return Err(ParquetError::General(
725            "compression level is not supported".to_string(),
726        ));
727    }
728
729    Ok(())
730}
731
732fn require_level(codec: &str, level: Option<i32>) -> Result<i32, ParquetError> {
733    level.ok_or(ParquetError::General(format!(
734        "{codec} requires a compression level",
735    )))
736}
737
738impl FromStr for Compression {
739    type Err = ParquetError;
740
741    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
742        let (codec, level) = split_compression_string(s)?;
743
744        let c = match codec {
745            "UNCOMPRESSED" | "uncompressed" => {
746                check_level_is_none(level)?;
747                Compression::UNCOMPRESSED
748            }
749            "SNAPPY" | "snappy" => {
750                check_level_is_none(level)?;
751                Compression::SNAPPY
752            }
753            "GZIP" | "gzip" => {
754                let level = require_level(codec, level)?;
755                Compression::GZIP(GzipLevel::try_new(level.try_into()?)?)
756            }
757            "LZO" | "lzo" => {
758                check_level_is_none(level)?;
759                Compression::LZO
760            }
761            "BROTLI" | "brotli" => {
762                let level = require_level(codec, level)?;
763                Compression::BROTLI(BrotliLevel::try_new(level.try_into()?)?)
764            }
765            "LZ4" | "lz4" => {
766                check_level_is_none(level)?;
767                Compression::LZ4
768            }
769            "ZSTD" | "zstd" => {
770                let level = require_level(codec, level)?;
771                Compression::ZSTD(ZstdLevel::try_new(level)?)
772            }
773            "LZ4_RAW" | "lz4_raw" => {
774                check_level_is_none(level)?;
775                Compression::LZ4_RAW
776            }
777            _ => {
778                return Err(ParquetError::General(format!(
779                    "unsupported compression {codec}"
780                )));
781            }
782        };
783
784        Ok(c)
785    }
786}
787
788// ----------------------------------------------------------------------
789// Mirrors thrift enum `PageType`
790
791thrift_enum!(
792/// Available data pages for Parquet file format.
793/// Note that some of the page types may not be supported.
794enum PageType {
795  DATA_PAGE = 0;
796  INDEX_PAGE = 1;
797  DICTIONARY_PAGE = 2;
798  DATA_PAGE_V2 = 3;
799}
800);
801
802// ----------------------------------------------------------------------
803// Mirrors thrift enum `BoundaryOrder`
804
805thrift_enum!(
806/// Enum to annotate whether lists of min/max elements inside ColumnIndex
807/// are ordered and if so, in which direction.
808enum BoundaryOrder {
809  UNORDERED = 0;
810  ASCENDING = 1;
811  DESCENDING = 2;
812}
813);
814
815// ----------------------------------------------------------------------
816// Mirrors thrift enum `EdgeInterpolationAlgorithm`
817
818// this is hand coded to allow for the _Unknown variant (allows this to be forward compatible)
819
820/// Edge interpolation algorithm for [`LogicalType::Geography`]
821#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
822#[repr(i32)]
823#[derive(Default)]
824pub enum EdgeInterpolationAlgorithm {
825    /// Edges are interpolated as geodesics on a sphere.
826    #[default]
827    SPHERICAL = 0,
828    /// <https://en.wikipedia.org/wiki/Vincenty%27s_formulae>
829    VINCENTY = 1,
830    /// Thomas, Paul D. Spheroidal geodesics, reference systems, & local geometry. US Naval Oceanographic Office, 1970
831    THOMAS = 2,
832    /// Thomas, Paul D. Mathematical models for navigation systems. US Naval Oceanographic Office, 1965.
833    ANDOYER = 3,
834    /// Karney, Charles FF. "Algorithms for geodesics." Journal of Geodesy 87 (2013): 43-55
835    KARNEY = 4,
836    /// Unknown algorithm
837    _Unknown(i32),
838}
839
840#[cfg(feature = "geospatial")]
841impl EdgeInterpolationAlgorithm {
842    /// Converts an [`EdgeInterpolationAlgorithm`] into its corresponding algorithm defined by
843    /// [`parquet_geospatial::WkbEdges`].
844    ///
845    /// This method will only return an Err if the [`EdgeInterpolationAlgorithm`] is the `_Unknown`
846    /// variant.
847    pub fn try_as_edges(&self) -> Result<parquet_geospatial::WkbEdges> {
848        match &self {
849            Self::SPHERICAL => Ok(parquet_geospatial::WkbEdges::Spherical),
850            Self::VINCENTY => Ok(parquet_geospatial::WkbEdges::Vincenty),
851            Self::THOMAS => Ok(parquet_geospatial::WkbEdges::Thomas),
852            Self::ANDOYER => Ok(parquet_geospatial::WkbEdges::Andoyer),
853            Self::KARNEY => Ok(parquet_geospatial::WkbEdges::Karney),
854            unknown @ Self::_Unknown(_) => Err(general_err!(
855                "Unknown edge interpolation algorithm: {}",
856                unknown
857            )),
858        }
859    }
860}
861
862impl fmt::Display for EdgeInterpolationAlgorithm {
863    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
864        f.write_fmt(format_args!("{self:?}"))
865    }
866}
867
868#[cfg(feature = "geospatial")]
869impl From<parquet_geospatial::WkbEdges> for EdgeInterpolationAlgorithm {
870    fn from(value: parquet_geospatial::WkbEdges) -> Self {
871        match value {
872            parquet_geospatial::WkbEdges::Spherical => Self::SPHERICAL,
873            parquet_geospatial::WkbEdges::Vincenty => Self::VINCENTY,
874            parquet_geospatial::WkbEdges::Thomas => Self::THOMAS,
875            parquet_geospatial::WkbEdges::Andoyer => Self::ANDOYER,
876            parquet_geospatial::WkbEdges::Karney => Self::KARNEY,
877        }
878    }
879}
880
881impl FromStr for EdgeInterpolationAlgorithm {
882    type Err = ParquetError;
883
884    fn from_str(s: &str) -> Result<Self> {
885        match s.to_ascii_uppercase().as_str() {
886            "SPHERICAL" => Ok(EdgeInterpolationAlgorithm::SPHERICAL),
887            "VINCENTY" => Ok(EdgeInterpolationAlgorithm::VINCENTY),
888            "THOMAS" => Ok(EdgeInterpolationAlgorithm::THOMAS),
889            "ANDOYER" => Ok(EdgeInterpolationAlgorithm::ANDOYER),
890            "KARNEY" => Ok(EdgeInterpolationAlgorithm::KARNEY),
891            unknown => Err(general_err!(
892                "Unknown edge interpolation algorithm: {}",
893                unknown
894            )),
895        }
896    }
897}
898
899impl<'a, R: ThriftCompactInputProtocol<'a>> ReadThrift<'a, R> for EdgeInterpolationAlgorithm {
900    fn read_thrift(prot: &mut R) -> Result<Self> {
901        let val = prot.read_i32()?;
902        match val {
903            0 => Ok(Self::SPHERICAL),
904            1 => Ok(Self::VINCENTY),
905            2 => Ok(Self::THOMAS),
906            3 => Ok(Self::ANDOYER),
907            4 => Ok(Self::KARNEY),
908            _ => Ok(Self::_Unknown(val)),
909        }
910    }
911}
912
913impl WriteThrift for EdgeInterpolationAlgorithm {
914    const ELEMENT_TYPE: ElementType = ElementType::I32;
915    fn write_thrift<W: Write>(&self, writer: &mut ThriftCompactOutputProtocol<W>) -> Result<()> {
916        let val: i32 = match *self {
917            Self::SPHERICAL => 0,
918            Self::VINCENTY => 1,
919            Self::THOMAS => 2,
920            Self::ANDOYER => 3,
921            Self::KARNEY => 4,
922            Self::_Unknown(i) => i,
923        };
924        writer.write_i32(val)
925    }
926}
927
928write_thrift_field!(EdgeInterpolationAlgorithm, FieldType::I32);
929
930// ----------------------------------------------------------------------
931// Mirrors thrift union `BloomFilterAlgorithm`
932
933thrift_union_all_empty!(
934/// The algorithm used in Bloom filter.
935union BloomFilterAlgorithm {
936  /// Block-based Bloom filter.
937  1: SplitBlockAlgorithm BLOCK;
938}
939);
940
941// ----------------------------------------------------------------------
942// Mirrors thrift union `BloomFilterHash`
943
944thrift_union_all_empty!(
945/// The hash function used in Bloom filter. This function takes the hash of a column value
946/// using plain encoding.
947union BloomFilterHash {
948  /// xxHash Strategy.
949  1: XxHash XXHASH;
950}
951);
952
953// ----------------------------------------------------------------------
954// Mirrors thrift union `BloomFilterCompression`
955
956thrift_union_all_empty!(
957/// The compression used in the Bloom filter.
958union BloomFilterCompression {
959  1: Uncompressed UNCOMPRESSED;
960}
961);
962
963// ----------------------------------------------------------------------
964// Mirrors thrift union `ColumnOrder`
965
966/// Sort order for page and column statistics.
967///
968/// Types are associated with sort orders and column stats are aggregated using a sort
969/// order, and a sort order should be considered when comparing values with statistics
970/// min/max.
971///
972/// See [`ColumnOrder`] for more information.
973#[derive(Debug, Clone, Copy, PartialEq, Eq)]
974#[expect(non_camel_case_types)]
975pub enum SortOrder {
976    /// Signed (either value or legacy byte-wise) comparison.
977    SIGNED,
978    /// Unsigned (depending on physical type either value or byte-wise) comparison.
979    UNSIGNED,
980    /// Comparison is undefined.
981    UNDEFINED,
982    /// Use IEEE 754 total order.
983    TOTAL_ORDER,
984    /// Use INT96 timestamp order (see [parquet-format/#584] and the [Thrift spec]).
985    ///
986    /// [parquet-format/#584]: https://github.com/apache/parquet-format/pull/584
987    /// [Thrift spec]: https://github.com/apache/parquet-format/blob/2076361bb64e2de9ca6a8d06eda025a6fa4e9df6/src/main/thrift/parquet.thrift#L1230-L1233
988    INT96_TIMESTAMP,
989}
990
991impl SortOrder {
992    /// Returns true if this is [`Self::SIGNED`]
993    pub fn is_signed(&self) -> bool {
994        matches!(self, Self::SIGNED)
995    }
996}
997
998/// Column order that specifies what method was used to aggregate min/max values for
999/// statistics.
1000///
1001/// Prior to version 2.4.0, Parquet used signed comparisons when computing min and max
1002/// values for statistics. This caused problems for UTF8 encoded strings, so the
1003/// [`ColumnOrder`] union was added, initially with a single variant `TYPE_ORDER`. The
1004/// sort order for columns was then defined based on the logical or physical type of
1005/// the column, and could use either signed comparison, unsigned comparison, or for some
1006/// types be left undefined. Since then several new `ColumnOrder`s have been added to the
1007/// specification.
1008///
1009/// In this crate, the `ColumnOrder` found in the footer is represented by this enum. To
1010/// convey what actual sort order to use, this crate maps the `ColumnOrder` along with the
1011/// physical and logical type to a [`SortOrder`]. It is this [`SortOrder`] that is used
1012/// internally when deciding how to compute the min/max statistics.
1013///
1014/// If column order is undefined, then it is the legacy behaviour and all values should
1015/// be compared as signed values/bytes.
1016///
1017/// [`ColumnOrder`]: https://github.com/apache/parquet-format/blob/2076361bb64e2de9ca6a8d06eda025a6fa4e9df6/src/main/thrift/parquet.thrift#L1103
1018#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1019#[expect(non_camel_case_types)]
1020pub enum ColumnOrder {
1021    /// Column uses the order defined by its logical or physical type
1022    /// (if there is no logical type), parquet-format 2.4.0+.
1023    TYPE_DEFINED_ORDER(SortOrder),
1024    /// Column ordering to use for floating point types.
1025    IEEE_754_TOTAL_ORDER,
1026    /// Column ordering to use for INT96 types.
1027    INT96_TIMESTAMP_ORDER,
1028    // The following are not defined in the Parquet spec and should always be last.
1029    /// Undefined column order, means legacy behaviour before parquet-format 2.4.0.
1030    /// Sort order is always SIGNED.
1031    UNDEFINED,
1032    /// An unknown but present ColumnOrder. Statistics with an unknown `ColumnOrder`
1033    /// will be ignored.
1034    UNKNOWN,
1035}
1036
1037impl ColumnOrder {
1038    /// Returns the `ColumnOrder` for a physical/logical type.
1039    pub fn column_order_for_type(
1040        logical_type: Option<&LogicalType>,
1041        converted_type: ConvertedType,
1042        physical_type: Type,
1043    ) -> ColumnOrder {
1044        if Some(&LogicalType::Float16) == logical_type
1045            || matches!(physical_type, Type::FLOAT | Type::DOUBLE)
1046        {
1047            ColumnOrder::IEEE_754_TOTAL_ORDER
1048        } else if matches!(physical_type, Type::INT96) {
1049            ColumnOrder::INT96_TIMESTAMP_ORDER
1050        } else {
1051            let sort_order =
1052                Self::get_sort_order_for_type(logical_type, converted_type, physical_type, true);
1053            ColumnOrder::TYPE_DEFINED_ORDER(sort_order)
1054        }
1055    }
1056
1057    /// Returns sort order for a physical/logical type.
1058    ///
1059    /// `is_type_defined` indicates whether the column order for this type is
1060    /// [`ColumnOrder::TYPE_DEFINED_ORDER`].
1061    ///
1062    /// It is now preferred to obtain this via [`Self::sort_order`].
1063    #[deprecated(since = "60.0.0", note = "use `ColumnOrder::sort_order` instead")]
1064    pub fn sort_order_for_type(
1065        logical_type: Option<&LogicalType>,
1066        converted_type: ConvertedType,
1067        physical_type: Type,
1068        is_type_defined: bool,
1069    ) -> SortOrder {
1070        ColumnOrder::get_sort_order_for_type(
1071            logical_type,
1072            converted_type,
1073            physical_type,
1074            is_type_defined,
1075        )
1076    }
1077
1078    // this is pub(crate) so it can be used in the thrift parser to correctly instantiate
1079    // the column_orders vec
1080    pub(crate) fn get_sort_order_for_type(
1081        logical_type: Option<&LogicalType>,
1082        converted_type: ConvertedType,
1083        physical_type: Type,
1084        is_type_defined: bool,
1085    ) -> SortOrder {
1086        match logical_type {
1087            Some(logical) => match logical {
1088                LogicalType::String | LogicalType::Enum | LogicalType::Json | LogicalType::Bson => {
1089                    SortOrder::UNSIGNED
1090                }
1091                LogicalType::Integer(int) => match int.is_signed {
1092                    true => SortOrder::SIGNED,
1093                    false => SortOrder::UNSIGNED,
1094                },
1095                LogicalType::Map | LogicalType::List => SortOrder::UNDEFINED,
1096                LogicalType::Decimal(_) => SortOrder::SIGNED,
1097                LogicalType::Date => SortOrder::SIGNED,
1098                LogicalType::Time(_) => SortOrder::SIGNED,
1099                LogicalType::Timestamp(_) => SortOrder::SIGNED,
1100                LogicalType::Unknown => SortOrder::UNDEFINED,
1101                LogicalType::Uuid => SortOrder::UNSIGNED,
1102                LogicalType::Float16 => {
1103                    if is_type_defined {
1104                        SortOrder::SIGNED
1105                    } else {
1106                        SortOrder::TOTAL_ORDER
1107                    }
1108                }
1109                LogicalType::Variant(_)
1110                | LogicalType::Geometry(_)
1111                | LogicalType::Geography(_)
1112                | LogicalType::File
1113                | LogicalType::_Unknown { .. } => SortOrder::UNDEFINED,
1114            },
1115            // Fall back to converted type
1116            None => Self::get_converted_sort_order(converted_type, physical_type, is_type_defined),
1117        }
1118    }
1119
1120    fn get_converted_sort_order(
1121        converted_type: ConvertedType,
1122        physical_type: Type,
1123        is_type_defined: bool,
1124    ) -> SortOrder {
1125        match converted_type {
1126            // Unsigned byte-wise comparison.
1127            ConvertedType::UTF8
1128            | ConvertedType::JSON
1129            | ConvertedType::BSON
1130            | ConvertedType::ENUM => SortOrder::UNSIGNED,
1131
1132            ConvertedType::INT_8
1133            | ConvertedType::INT_16
1134            | ConvertedType::INT_32
1135            | ConvertedType::INT_64 => SortOrder::SIGNED,
1136
1137            ConvertedType::UINT_8
1138            | ConvertedType::UINT_16
1139            | ConvertedType::UINT_32
1140            | ConvertedType::UINT_64 => SortOrder::UNSIGNED,
1141
1142            // Signed comparison of the represented value.
1143            ConvertedType::DECIMAL => SortOrder::SIGNED,
1144
1145            ConvertedType::DATE => SortOrder::SIGNED,
1146
1147            ConvertedType::TIME_MILLIS
1148            | ConvertedType::TIME_MICROS
1149            | ConvertedType::TIMESTAMP_MILLIS
1150            | ConvertedType::TIMESTAMP_MICROS => SortOrder::SIGNED,
1151
1152            ConvertedType::INTERVAL => SortOrder::UNDEFINED,
1153
1154            ConvertedType::LIST | ConvertedType::MAP | ConvertedType::MAP_KEY_VALUE => {
1155                SortOrder::UNDEFINED
1156            }
1157
1158            // Fall back to physical type.
1159            ConvertedType::NONE => Self::get_default_sort_order(physical_type, is_type_defined),
1160        }
1161    }
1162
1163    /// Returns default sort order based on physical type.
1164    fn get_default_sort_order(physical_type: Type, is_type_defined: bool) -> SortOrder {
1165        match physical_type {
1166            // Order: false, true
1167            Type::BOOLEAN => SortOrder::UNSIGNED,
1168            Type::INT32 | Type::INT64 => SortOrder::SIGNED,
1169            Type::INT96 => {
1170                if is_type_defined {
1171                    SortOrder::UNDEFINED
1172                } else {
1173                    SortOrder::INT96_TIMESTAMP
1174                }
1175            }
1176            // Notes to remember when comparing float/double values:
1177            // If legacy TYPE_DEFINED_ORDER is specified:
1178            //   If the min is a NaN, it should be ignored.
1179            //   If the max is a NaN, it should be ignored.
1180            //   If the min is +0, the row group may contain -0 values as well.
1181            //   If the max is -0, the row group may contain +0 values as well.
1182            //   When looking for NaN values, min and max should be ignored.
1183            // If IEEE_754_TOTAL_ORDER:
1184            //   Examine nan_count to see if NaNs are present.
1185            //   If min/max are NaN, that means only NaNs are present.
1186            //   If min/max are not NaN, they are ordered according to total order.
1187            Type::FLOAT | Type::DOUBLE => {
1188                if is_type_defined {
1189                    SortOrder::SIGNED
1190                } else {
1191                    SortOrder::TOTAL_ORDER
1192                }
1193            }
1194            // Unsigned byte-wise comparison
1195            Type::BYTE_ARRAY | Type::FIXED_LEN_BYTE_ARRAY => SortOrder::UNSIGNED,
1196        }
1197    }
1198
1199    /// Returns sort order associated with this column order.
1200    pub fn sort_order(&self) -> SortOrder {
1201        match *self {
1202            ColumnOrder::TYPE_DEFINED_ORDER(order) => order,
1203            ColumnOrder::IEEE_754_TOTAL_ORDER => SortOrder::TOTAL_ORDER,
1204            ColumnOrder::INT96_TIMESTAMP_ORDER => SortOrder::INT96_TIMESTAMP,
1205            ColumnOrder::UNDEFINED => SortOrder::SIGNED,
1206            ColumnOrder::UNKNOWN => SortOrder::UNDEFINED,
1207        }
1208    }
1209}
1210
1211impl<'a, R: ThriftCompactInputProtocol<'a>> ReadThrift<'a, R> for ColumnOrder {
1212    fn read_thrift(prot: &mut R) -> Result<Self> {
1213        let field_ident = prot.read_field_begin(0)?;
1214        if field_ident.field_type == FieldType::Stop {
1215            return Err(general_err!("Received empty union from remote ColumnOrder"));
1216        }
1217        let ret = match field_ident.id {
1218            1 => {
1219                // NOTE: the sort order needs to be set correctly after parsing.
1220                prot.skip_empty_struct()?;
1221                Self::TYPE_DEFINED_ORDER(SortOrder::SIGNED)
1222            }
1223            2 => {
1224                prot.skip_empty_struct()?;
1225                Self::IEEE_754_TOTAL_ORDER
1226            }
1227            3 => {
1228                prot.skip_empty_struct()?;
1229                Self::INT96_TIMESTAMP_ORDER
1230            }
1231            _ => {
1232                prot.skip(field_ident.field_type)?;
1233                Self::UNKNOWN
1234            }
1235        };
1236        let field_ident = prot.read_field_begin(field_ident.id)?;
1237        if field_ident.field_type != FieldType::Stop {
1238            return Err(general_err!(
1239                "Received multiple fields for union from remote ColumnOrder"
1240            ));
1241        }
1242        Ok(ret)
1243    }
1244}
1245
1246impl WriteThrift for ColumnOrder {
1247    const ELEMENT_TYPE: ElementType = ElementType::Struct;
1248
1249    fn write_thrift<W: Write>(&self, writer: &mut ThriftCompactOutputProtocol<W>) -> Result<()> {
1250        match *self {
1251            Self::TYPE_DEFINED_ORDER(_) => {
1252                writer.write_field_begin(FieldType::Struct, 1, 0)?;
1253                writer.write_struct_end()?;
1254            }
1255            Self::IEEE_754_TOTAL_ORDER => {
1256                writer.write_field_begin(FieldType::Struct, 2, 0)?;
1257                writer.write_struct_end()?;
1258            }
1259            Self::INT96_TIMESTAMP_ORDER => {
1260                writer.write_field_begin(FieldType::Struct, 3, 0)?;
1261                writer.write_struct_end()?;
1262            }
1263            _ => return Err(general_err!("Attempt to write undefined ColumnOrder")),
1264        }
1265        // write end of struct for this union
1266        writer.write_struct_end()
1267    }
1268}
1269
1270// ----------------------------------------------------------------------
1271// Display handlers
1272
1273impl fmt::Display for Compression {
1274    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1275        write!(f, "{self:?}")
1276    }
1277}
1278
1279impl fmt::Display for SortOrder {
1280    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1281        write!(f, "{self:?}")
1282    }
1283}
1284
1285impl fmt::Display for ColumnOrder {
1286    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1287        write!(f, "{self:?}")
1288    }
1289}
1290
1291// ----------------------------------------------------------------------
1292// LogicalType <=> ConvertedType conversion
1293
1294// Note: To prevent type loss when converting from ConvertedType to LogicalType,
1295// the conversion from ConvertedType -> LogicalType is not implemented.
1296// Such type loss includes:
1297// - Not knowing the decimal scale and precision of ConvertedType
1298// - Time and timestamp nanosecond precision, that is not supported in ConvertedType.
1299
1300impl From<Option<LogicalType>> for ConvertedType {
1301    fn from(value: Option<LogicalType>) -> Self {
1302        match value {
1303            Some(value) => match value {
1304                LogicalType::String => ConvertedType::UTF8,
1305                LogicalType::Map => ConvertedType::MAP,
1306                LogicalType::List => ConvertedType::LIST,
1307                LogicalType::Enum => ConvertedType::ENUM,
1308                LogicalType::Decimal { .. } => ConvertedType::DECIMAL,
1309                LogicalType::Date => ConvertedType::DATE,
1310                LogicalType::Time(time) => match time.unit {
1311                    TimeUnit::MILLIS => ConvertedType::TIME_MILLIS,
1312                    TimeUnit::MICROS => ConvertedType::TIME_MICROS,
1313                    TimeUnit::NANOS => ConvertedType::NONE,
1314                },
1315                LogicalType::Timestamp(time) => match time.unit {
1316                    TimeUnit::MILLIS => ConvertedType::TIMESTAMP_MILLIS,
1317                    TimeUnit::MICROS => ConvertedType::TIMESTAMP_MICROS,
1318                    TimeUnit::NANOS => ConvertedType::NONE,
1319                },
1320                LogicalType::Integer(int_type) => match (int_type.bit_width, int_type.is_signed) {
1321                    (8, true) => ConvertedType::INT_8,
1322                    (16, true) => ConvertedType::INT_16,
1323                    (32, true) => ConvertedType::INT_32,
1324                    (64, true) => ConvertedType::INT_64,
1325                    (8, false) => ConvertedType::UINT_8,
1326                    (16, false) => ConvertedType::UINT_16,
1327                    (32, false) => ConvertedType::UINT_32,
1328                    (64, false) => ConvertedType::UINT_64,
1329                    (bit_width, is_signed) => panic!(
1330                        "Integer type bit_width={bit_width}, signed={is_signed} is not supported"
1331                    ),
1332                },
1333                LogicalType::Json => ConvertedType::JSON,
1334                LogicalType::Bson => ConvertedType::BSON,
1335                LogicalType::Uuid
1336                | LogicalType::Float16
1337                | LogicalType::Variant(_)
1338                | LogicalType::Geometry(_)
1339                | LogicalType::Geography(_)
1340                | LogicalType::File
1341                | LogicalType::_Unknown { .. }
1342                | LogicalType::Unknown => ConvertedType::NONE,
1343            },
1344            None => ConvertedType::NONE,
1345        }
1346    }
1347}
1348
1349// ----------------------------------------------------------------------
1350// String conversions for schema parsing.
1351
1352impl str::FromStr for Repetition {
1353    type Err = ParquetError;
1354
1355    fn from_str(s: &str) -> Result<Self> {
1356        match s {
1357            "REQUIRED" => Ok(Repetition::REQUIRED),
1358            "OPTIONAL" => Ok(Repetition::OPTIONAL),
1359            "REPEATED" => Ok(Repetition::REPEATED),
1360            other => Err(general_err!("Invalid parquet repetition {}", other)),
1361        }
1362    }
1363}
1364
1365impl str::FromStr for Type {
1366    type Err = ParquetError;
1367
1368    fn from_str(s: &str) -> Result<Self> {
1369        match s {
1370            "BOOLEAN" => Ok(Type::BOOLEAN),
1371            "INT32" => Ok(Type::INT32),
1372            "INT64" => Ok(Type::INT64),
1373            "INT96" => Ok(Type::INT96),
1374            "FLOAT" => Ok(Type::FLOAT),
1375            "DOUBLE" => Ok(Type::DOUBLE),
1376            "BYTE_ARRAY" | "BINARY" => Ok(Type::BYTE_ARRAY),
1377            "FIXED_LEN_BYTE_ARRAY" => Ok(Type::FIXED_LEN_BYTE_ARRAY),
1378            other => Err(general_err!("Invalid parquet type {}", other)),
1379        }
1380    }
1381}
1382
1383impl str::FromStr for ConvertedType {
1384    type Err = ParquetError;
1385
1386    fn from_str(s: &str) -> Result<Self> {
1387        match s {
1388            "NONE" => Ok(ConvertedType::NONE),
1389            "UTF8" => Ok(ConvertedType::UTF8),
1390            "MAP" => Ok(ConvertedType::MAP),
1391            "MAP_KEY_VALUE" => Ok(ConvertedType::MAP_KEY_VALUE),
1392            "LIST" => Ok(ConvertedType::LIST),
1393            "ENUM" => Ok(ConvertedType::ENUM),
1394            "DECIMAL" => Ok(ConvertedType::DECIMAL),
1395            "DATE" => Ok(ConvertedType::DATE),
1396            "TIME_MILLIS" => Ok(ConvertedType::TIME_MILLIS),
1397            "TIME_MICROS" => Ok(ConvertedType::TIME_MICROS),
1398            "TIMESTAMP_MILLIS" => Ok(ConvertedType::TIMESTAMP_MILLIS),
1399            "TIMESTAMP_MICROS" => Ok(ConvertedType::TIMESTAMP_MICROS),
1400            "UINT_8" => Ok(ConvertedType::UINT_8),
1401            "UINT_16" => Ok(ConvertedType::UINT_16),
1402            "UINT_32" => Ok(ConvertedType::UINT_32),
1403            "UINT_64" => Ok(ConvertedType::UINT_64),
1404            "INT_8" => Ok(ConvertedType::INT_8),
1405            "INT_16" => Ok(ConvertedType::INT_16),
1406            "INT_32" => Ok(ConvertedType::INT_32),
1407            "INT_64" => Ok(ConvertedType::INT_64),
1408            "JSON" => Ok(ConvertedType::JSON),
1409            "BSON" => Ok(ConvertedType::BSON),
1410            "INTERVAL" => Ok(ConvertedType::INTERVAL),
1411            other => Err(general_err!("Invalid parquet converted type {}", other)),
1412        }
1413    }
1414}
1415
1416impl str::FromStr for LogicalType {
1417    type Err = ParquetError;
1418
1419    fn from_str(s: &str) -> Result<Self> {
1420        match s {
1421            // The type is a placeholder that gets updated elsewhere
1422            "INTEGER" => Ok(LogicalType::integer(8, false)),
1423            "MAP" => Ok(LogicalType::Map),
1424            "LIST" => Ok(LogicalType::List),
1425            "ENUM" => Ok(LogicalType::Enum),
1426            "DECIMAL" => Ok(LogicalType::decimal(-1, -1)),
1427            "DATE" => Ok(LogicalType::Date),
1428            "TIME" => Ok(LogicalType::time(false, TimeUnit::MILLIS)),
1429            "TIMESTAMP" => Ok(LogicalType::timestamp(false, TimeUnit::MILLIS)),
1430            "STRING" => Ok(LogicalType::String),
1431            "JSON" => Ok(LogicalType::Json),
1432            "BSON" => Ok(LogicalType::Bson),
1433            "UUID" => Ok(LogicalType::Uuid),
1434            "UNKNOWN" => Ok(LogicalType::Unknown),
1435            "INTERVAL" => Err(general_err!(
1436                "Interval parquet logical type not yet supported"
1437            )),
1438            "FLOAT16" => Ok(LogicalType::Float16),
1439            "VARIANT" => Ok(LogicalType::variant(None)),
1440            "FILE" => Ok(LogicalType::File),
1441            "GEOMETRY" => Ok(LogicalType::geometry(None)),
1442            "GEOGRAPHY" => Ok(LogicalType::geography(
1443                None,
1444                Some(EdgeInterpolationAlgorithm::SPHERICAL),
1445            )),
1446            other => Err(general_err!("Invalid parquet logical type {}", other)),
1447        }
1448    }
1449}
1450
1451#[cfg(test)]
1452#[expect(deprecated)] // allow BIT_PACKED encoding for the whole test module
1453mod tests {
1454    use super::*;
1455    use crate::parquet_thrift::{ThriftSliceInputProtocol, tests::test_roundtrip};
1456
1457    #[test]
1458    fn test_display_type() {
1459        assert_eq!(Type::BOOLEAN.to_string(), "BOOLEAN");
1460        assert_eq!(Type::INT32.to_string(), "INT32");
1461        assert_eq!(Type::INT64.to_string(), "INT64");
1462        assert_eq!(Type::INT96.to_string(), "INT96");
1463        assert_eq!(Type::FLOAT.to_string(), "FLOAT");
1464        assert_eq!(Type::DOUBLE.to_string(), "DOUBLE");
1465        assert_eq!(Type::BYTE_ARRAY.to_string(), "BYTE_ARRAY");
1466        assert_eq!(
1467            Type::FIXED_LEN_BYTE_ARRAY.to_string(),
1468            "FIXED_LEN_BYTE_ARRAY"
1469        );
1470    }
1471
1472    #[test]
1473    fn test_from_string_into_type() {
1474        assert_eq!(
1475            Type::BOOLEAN.to_string().parse::<Type>().unwrap(),
1476            Type::BOOLEAN
1477        );
1478        assert_eq!(
1479            Type::INT32.to_string().parse::<Type>().unwrap(),
1480            Type::INT32
1481        );
1482        assert_eq!(
1483            Type::INT64.to_string().parse::<Type>().unwrap(),
1484            Type::INT64
1485        );
1486        assert_eq!(
1487            Type::INT96.to_string().parse::<Type>().unwrap(),
1488            Type::INT96
1489        );
1490        assert_eq!(
1491            Type::FLOAT.to_string().parse::<Type>().unwrap(),
1492            Type::FLOAT
1493        );
1494        assert_eq!(
1495            Type::DOUBLE.to_string().parse::<Type>().unwrap(),
1496            Type::DOUBLE
1497        );
1498        assert_eq!(
1499            Type::BYTE_ARRAY.to_string().parse::<Type>().unwrap(),
1500            Type::BYTE_ARRAY
1501        );
1502        assert_eq!("BINARY".parse::<Type>().unwrap(), Type::BYTE_ARRAY);
1503        assert_eq!(
1504            Type::FIXED_LEN_BYTE_ARRAY
1505                .to_string()
1506                .parse::<Type>()
1507                .unwrap(),
1508            Type::FIXED_LEN_BYTE_ARRAY
1509        );
1510    }
1511
1512    #[test]
1513    fn test_converted_type_roundtrip() {
1514        test_roundtrip(ConvertedType::UTF8);
1515        test_roundtrip(ConvertedType::MAP);
1516        test_roundtrip(ConvertedType::MAP_KEY_VALUE);
1517        test_roundtrip(ConvertedType::LIST);
1518        test_roundtrip(ConvertedType::ENUM);
1519        test_roundtrip(ConvertedType::DECIMAL);
1520        test_roundtrip(ConvertedType::DATE);
1521        test_roundtrip(ConvertedType::TIME_MILLIS);
1522        test_roundtrip(ConvertedType::TIME_MICROS);
1523        test_roundtrip(ConvertedType::TIMESTAMP_MILLIS);
1524        test_roundtrip(ConvertedType::TIMESTAMP_MICROS);
1525        test_roundtrip(ConvertedType::UINT_8);
1526        test_roundtrip(ConvertedType::UINT_16);
1527        test_roundtrip(ConvertedType::UINT_32);
1528        test_roundtrip(ConvertedType::UINT_64);
1529        test_roundtrip(ConvertedType::INT_8);
1530        test_roundtrip(ConvertedType::INT_16);
1531        test_roundtrip(ConvertedType::INT_32);
1532        test_roundtrip(ConvertedType::INT_64);
1533        test_roundtrip(ConvertedType::JSON);
1534        test_roundtrip(ConvertedType::BSON);
1535        test_roundtrip(ConvertedType::INTERVAL);
1536    }
1537
1538    #[test]
1539    fn test_read_invalid_converted_type() {
1540        let mut prot = ThriftSliceInputProtocol::new(&[0x7eu8]);
1541        let res = ConvertedType::read_thrift(&mut prot);
1542        assert!(res.is_err());
1543        assert_eq!(
1544            res.unwrap_err().to_string(),
1545            "Parquet error: Unexpected ConvertedType 63"
1546        );
1547    }
1548
1549    #[test]
1550    fn test_display_converted_type() {
1551        assert_eq!(ConvertedType::NONE.to_string(), "NONE");
1552        assert_eq!(ConvertedType::UTF8.to_string(), "UTF8");
1553        assert_eq!(ConvertedType::MAP.to_string(), "MAP");
1554        assert_eq!(ConvertedType::MAP_KEY_VALUE.to_string(), "MAP_KEY_VALUE");
1555        assert_eq!(ConvertedType::LIST.to_string(), "LIST");
1556        assert_eq!(ConvertedType::ENUM.to_string(), "ENUM");
1557        assert_eq!(ConvertedType::DECIMAL.to_string(), "DECIMAL");
1558        assert_eq!(ConvertedType::DATE.to_string(), "DATE");
1559        assert_eq!(ConvertedType::TIME_MILLIS.to_string(), "TIME_MILLIS");
1560        assert_eq!(ConvertedType::DATE.to_string(), "DATE");
1561        assert_eq!(ConvertedType::TIME_MICROS.to_string(), "TIME_MICROS");
1562        assert_eq!(
1563            ConvertedType::TIMESTAMP_MILLIS.to_string(),
1564            "TIMESTAMP_MILLIS"
1565        );
1566        assert_eq!(
1567            ConvertedType::TIMESTAMP_MICROS.to_string(),
1568            "TIMESTAMP_MICROS"
1569        );
1570        assert_eq!(ConvertedType::UINT_8.to_string(), "UINT_8");
1571        assert_eq!(ConvertedType::UINT_16.to_string(), "UINT_16");
1572        assert_eq!(ConvertedType::UINT_32.to_string(), "UINT_32");
1573        assert_eq!(ConvertedType::UINT_64.to_string(), "UINT_64");
1574        assert_eq!(ConvertedType::INT_8.to_string(), "INT_8");
1575        assert_eq!(ConvertedType::INT_16.to_string(), "INT_16");
1576        assert_eq!(ConvertedType::INT_32.to_string(), "INT_32");
1577        assert_eq!(ConvertedType::INT_64.to_string(), "INT_64");
1578        assert_eq!(ConvertedType::JSON.to_string(), "JSON");
1579        assert_eq!(ConvertedType::BSON.to_string(), "BSON");
1580        assert_eq!(ConvertedType::INTERVAL.to_string(), "INTERVAL");
1581        assert_eq!(ConvertedType::DECIMAL.to_string(), "DECIMAL")
1582    }
1583
1584    #[test]
1585    fn test_from_string_into_converted_type() {
1586        assert_eq!(
1587            ConvertedType::NONE
1588                .to_string()
1589                .parse::<ConvertedType>()
1590                .unwrap(),
1591            ConvertedType::NONE
1592        );
1593        assert_eq!(
1594            ConvertedType::UTF8
1595                .to_string()
1596                .parse::<ConvertedType>()
1597                .unwrap(),
1598            ConvertedType::UTF8
1599        );
1600        assert_eq!(
1601            ConvertedType::MAP
1602                .to_string()
1603                .parse::<ConvertedType>()
1604                .unwrap(),
1605            ConvertedType::MAP
1606        );
1607        assert_eq!(
1608            ConvertedType::MAP_KEY_VALUE
1609                .to_string()
1610                .parse::<ConvertedType>()
1611                .unwrap(),
1612            ConvertedType::MAP_KEY_VALUE
1613        );
1614        assert_eq!(
1615            ConvertedType::LIST
1616                .to_string()
1617                .parse::<ConvertedType>()
1618                .unwrap(),
1619            ConvertedType::LIST
1620        );
1621        assert_eq!(
1622            ConvertedType::ENUM
1623                .to_string()
1624                .parse::<ConvertedType>()
1625                .unwrap(),
1626            ConvertedType::ENUM
1627        );
1628        assert_eq!(
1629            ConvertedType::DECIMAL
1630                .to_string()
1631                .parse::<ConvertedType>()
1632                .unwrap(),
1633            ConvertedType::DECIMAL
1634        );
1635        assert_eq!(
1636            ConvertedType::DATE
1637                .to_string()
1638                .parse::<ConvertedType>()
1639                .unwrap(),
1640            ConvertedType::DATE
1641        );
1642        assert_eq!(
1643            ConvertedType::TIME_MILLIS
1644                .to_string()
1645                .parse::<ConvertedType>()
1646                .unwrap(),
1647            ConvertedType::TIME_MILLIS
1648        );
1649        assert_eq!(
1650            ConvertedType::TIME_MICROS
1651                .to_string()
1652                .parse::<ConvertedType>()
1653                .unwrap(),
1654            ConvertedType::TIME_MICROS
1655        );
1656        assert_eq!(
1657            ConvertedType::TIMESTAMP_MILLIS
1658                .to_string()
1659                .parse::<ConvertedType>()
1660                .unwrap(),
1661            ConvertedType::TIMESTAMP_MILLIS
1662        );
1663        assert_eq!(
1664            ConvertedType::TIMESTAMP_MICROS
1665                .to_string()
1666                .parse::<ConvertedType>()
1667                .unwrap(),
1668            ConvertedType::TIMESTAMP_MICROS
1669        );
1670        assert_eq!(
1671            ConvertedType::UINT_8
1672                .to_string()
1673                .parse::<ConvertedType>()
1674                .unwrap(),
1675            ConvertedType::UINT_8
1676        );
1677        assert_eq!(
1678            ConvertedType::UINT_16
1679                .to_string()
1680                .parse::<ConvertedType>()
1681                .unwrap(),
1682            ConvertedType::UINT_16
1683        );
1684        assert_eq!(
1685            ConvertedType::UINT_32
1686                .to_string()
1687                .parse::<ConvertedType>()
1688                .unwrap(),
1689            ConvertedType::UINT_32
1690        );
1691        assert_eq!(
1692            ConvertedType::UINT_64
1693                .to_string()
1694                .parse::<ConvertedType>()
1695                .unwrap(),
1696            ConvertedType::UINT_64
1697        );
1698        assert_eq!(
1699            ConvertedType::INT_8
1700                .to_string()
1701                .parse::<ConvertedType>()
1702                .unwrap(),
1703            ConvertedType::INT_8
1704        );
1705        assert_eq!(
1706            ConvertedType::INT_16
1707                .to_string()
1708                .parse::<ConvertedType>()
1709                .unwrap(),
1710            ConvertedType::INT_16
1711        );
1712        assert_eq!(
1713            ConvertedType::INT_32
1714                .to_string()
1715                .parse::<ConvertedType>()
1716                .unwrap(),
1717            ConvertedType::INT_32
1718        );
1719        assert_eq!(
1720            ConvertedType::INT_64
1721                .to_string()
1722                .parse::<ConvertedType>()
1723                .unwrap(),
1724            ConvertedType::INT_64
1725        );
1726        assert_eq!(
1727            ConvertedType::JSON
1728                .to_string()
1729                .parse::<ConvertedType>()
1730                .unwrap(),
1731            ConvertedType::JSON
1732        );
1733        assert_eq!(
1734            ConvertedType::BSON
1735                .to_string()
1736                .parse::<ConvertedType>()
1737                .unwrap(),
1738            ConvertedType::BSON
1739        );
1740        assert_eq!(
1741            ConvertedType::INTERVAL
1742                .to_string()
1743                .parse::<ConvertedType>()
1744                .unwrap(),
1745            ConvertedType::INTERVAL
1746        );
1747        assert_eq!(
1748            ConvertedType::DECIMAL
1749                .to_string()
1750                .parse::<ConvertedType>()
1751                .unwrap(),
1752            ConvertedType::DECIMAL
1753        )
1754    }
1755
1756    #[test]
1757    fn test_logical_to_converted_type() {
1758        let logical_none: Option<LogicalType> = None;
1759        assert_eq!(ConvertedType::from(logical_none), ConvertedType::NONE);
1760        assert_eq!(
1761            ConvertedType::from(Some(LogicalType::decimal(5, 20))),
1762            ConvertedType::DECIMAL
1763        );
1764        assert_eq!(
1765            ConvertedType::from(Some(LogicalType::Bson)),
1766            ConvertedType::BSON
1767        );
1768        assert_eq!(
1769            ConvertedType::from(Some(LogicalType::Json)),
1770            ConvertedType::JSON
1771        );
1772        assert_eq!(
1773            ConvertedType::from(Some(LogicalType::String)),
1774            ConvertedType::UTF8
1775        );
1776        assert_eq!(
1777            ConvertedType::from(Some(LogicalType::Date)),
1778            ConvertedType::DATE
1779        );
1780        assert_eq!(
1781            ConvertedType::from(Some(LogicalType::time(true, TimeUnit::MILLIS))),
1782            ConvertedType::TIME_MILLIS
1783        );
1784        assert_eq!(
1785            ConvertedType::from(Some(LogicalType::time(true, TimeUnit::MICROS))),
1786            ConvertedType::TIME_MICROS
1787        );
1788        assert_eq!(
1789            ConvertedType::from(Some(LogicalType::time(false, TimeUnit::NANOS))),
1790            ConvertedType::NONE
1791        );
1792        assert_eq!(
1793            ConvertedType::from(Some(LogicalType::timestamp(true, TimeUnit::MILLIS))),
1794            ConvertedType::TIMESTAMP_MILLIS
1795        );
1796        assert_eq!(
1797            ConvertedType::from(Some(LogicalType::timestamp(false, TimeUnit::MICROS))),
1798            ConvertedType::TIMESTAMP_MICROS
1799        );
1800        assert_eq!(
1801            ConvertedType::from(Some(LogicalType::timestamp(false, TimeUnit::NANOS))),
1802            ConvertedType::NONE
1803        );
1804        assert_eq!(
1805            ConvertedType::from(Some(LogicalType::integer(8, false))),
1806            ConvertedType::UINT_8
1807        );
1808        assert_eq!(
1809            ConvertedType::from(Some(LogicalType::integer(8, true))),
1810            ConvertedType::INT_8
1811        );
1812        assert_eq!(
1813            ConvertedType::from(Some(LogicalType::integer(16, false))),
1814            ConvertedType::UINT_16
1815        );
1816        assert_eq!(
1817            ConvertedType::from(Some(LogicalType::integer(16, true))),
1818            ConvertedType::INT_16
1819        );
1820        assert_eq!(
1821            ConvertedType::from(Some(LogicalType::integer(32, false))),
1822            ConvertedType::UINT_32
1823        );
1824        assert_eq!(
1825            ConvertedType::from(Some(LogicalType::integer(32, true))),
1826            ConvertedType::INT_32
1827        );
1828        assert_eq!(
1829            ConvertedType::from(Some(LogicalType::integer(64, false))),
1830            ConvertedType::UINT_64
1831        );
1832        assert_eq!(
1833            ConvertedType::from(Some(LogicalType::integer(64, true))),
1834            ConvertedType::INT_64
1835        );
1836        assert_eq!(
1837            ConvertedType::from(Some(LogicalType::List)),
1838            ConvertedType::LIST
1839        );
1840        assert_eq!(
1841            ConvertedType::from(Some(LogicalType::Map)),
1842            ConvertedType::MAP
1843        );
1844        assert_eq!(
1845            ConvertedType::from(Some(LogicalType::Uuid)),
1846            ConvertedType::NONE
1847        );
1848        assert_eq!(
1849            ConvertedType::from(Some(LogicalType::Enum)),
1850            ConvertedType::ENUM
1851        );
1852        assert_eq!(
1853            ConvertedType::from(Some(LogicalType::Float16)),
1854            ConvertedType::NONE
1855        );
1856        assert_eq!(
1857            ConvertedType::from(Some(LogicalType::variant(None))),
1858            ConvertedType::NONE
1859        );
1860        assert_eq!(
1861            ConvertedType::from(Some(LogicalType::geometry(None))),
1862            ConvertedType::NONE
1863        );
1864        assert_eq!(
1865            ConvertedType::from(Some(LogicalType::geography(None, Some(Default::default())))),
1866            ConvertedType::NONE
1867        );
1868        assert_eq!(
1869            ConvertedType::from(Some(LogicalType::Unknown)),
1870            ConvertedType::NONE
1871        );
1872    }
1873
1874    #[test]
1875    fn test_logical_type_roundtrip() {
1876        test_roundtrip(LogicalType::String);
1877        test_roundtrip(LogicalType::Map);
1878        test_roundtrip(LogicalType::List);
1879        test_roundtrip(LogicalType::Enum);
1880        test_roundtrip(LogicalType::decimal(0, 20));
1881        test_roundtrip(LogicalType::Date);
1882        test_roundtrip(LogicalType::time(true, TimeUnit::MICROS));
1883        test_roundtrip(LogicalType::time(false, TimeUnit::MILLIS));
1884        test_roundtrip(LogicalType::time(false, TimeUnit::NANOS));
1885        test_roundtrip(LogicalType::timestamp(false, TimeUnit::MICROS));
1886        test_roundtrip(LogicalType::timestamp(true, TimeUnit::MILLIS));
1887        test_roundtrip(LogicalType::timestamp(true, TimeUnit::NANOS));
1888        test_roundtrip(LogicalType::integer(8, true));
1889        test_roundtrip(LogicalType::integer(16, false));
1890        test_roundtrip(LogicalType::integer(32, true));
1891        test_roundtrip(LogicalType::integer(64, false));
1892        test_roundtrip(LogicalType::Json);
1893        test_roundtrip(LogicalType::Bson);
1894        test_roundtrip(LogicalType::Uuid);
1895        test_roundtrip(LogicalType::Float16);
1896        test_roundtrip(LogicalType::variant(Some(1)));
1897        test_roundtrip(LogicalType::variant(None));
1898        test_roundtrip(LogicalType::geometry(Some("foo".to_owned())));
1899        test_roundtrip(LogicalType::geometry(None));
1900        test_roundtrip(LogicalType::geography(
1901            Some("foo".to_owned()),
1902            Some(EdgeInterpolationAlgorithm::ANDOYER),
1903        ));
1904        test_roundtrip(LogicalType::geography(
1905            None,
1906            Some(EdgeInterpolationAlgorithm::KARNEY),
1907        ));
1908        test_roundtrip(LogicalType::geography(
1909            Some("foo".to_owned()),
1910            Some(EdgeInterpolationAlgorithm::SPHERICAL),
1911        ));
1912        test_roundtrip(LogicalType::geography(
1913            None,
1914            Some(EdgeInterpolationAlgorithm::SPHERICAL),
1915        ));
1916    }
1917
1918    #[test]
1919    fn test_display_repetition() {
1920        assert_eq!(Repetition::REQUIRED.to_string(), "REQUIRED");
1921        assert_eq!(Repetition::OPTIONAL.to_string(), "OPTIONAL");
1922        assert_eq!(Repetition::REPEATED.to_string(), "REPEATED");
1923    }
1924
1925    #[test]
1926    fn test_from_string_into_repetition() {
1927        assert_eq!(
1928            Repetition::REQUIRED
1929                .to_string()
1930                .parse::<Repetition>()
1931                .unwrap(),
1932            Repetition::REQUIRED
1933        );
1934        assert_eq!(
1935            Repetition::OPTIONAL
1936                .to_string()
1937                .parse::<Repetition>()
1938                .unwrap(),
1939            Repetition::OPTIONAL
1940        );
1941        assert_eq!(
1942            Repetition::REPEATED
1943                .to_string()
1944                .parse::<Repetition>()
1945                .unwrap(),
1946            Repetition::REPEATED
1947        );
1948    }
1949
1950    #[test]
1951    fn test_display_encoding() {
1952        assert_eq!(Encoding::PLAIN.to_string(), "PLAIN");
1953        assert_eq!(Encoding::PLAIN_DICTIONARY.to_string(), "PLAIN_DICTIONARY");
1954        assert_eq!(Encoding::RLE.to_string(), "RLE");
1955        assert_eq!(Encoding::BIT_PACKED.to_string(), "BIT_PACKED");
1956        assert_eq!(
1957            Encoding::DELTA_BINARY_PACKED.to_string(),
1958            "DELTA_BINARY_PACKED"
1959        );
1960        assert_eq!(
1961            Encoding::DELTA_LENGTH_BYTE_ARRAY.to_string(),
1962            "DELTA_LENGTH_BYTE_ARRAY"
1963        );
1964        assert_eq!(Encoding::DELTA_BYTE_ARRAY.to_string(), "DELTA_BYTE_ARRAY");
1965        assert_eq!(Encoding::RLE_DICTIONARY.to_string(), "RLE_DICTIONARY");
1966        assert_eq!(Encoding::ALP.to_string(), "ALP");
1967    }
1968
1969    #[test]
1970    fn test_compression_conversion() {
1971        assert_eq!(
1972            CompressionCodec::from(Compression::UNCOMPRESSED),
1973            CompressionCodec::UNCOMPRESSED
1974        );
1975        assert_eq!(
1976            CompressionCodec::from(Compression::SNAPPY),
1977            CompressionCodec::SNAPPY
1978        );
1979        assert_eq!(
1980            CompressionCodec::from(Compression::GZIP(Default::default())),
1981            CompressionCodec::GZIP
1982        );
1983        assert_eq!(
1984            CompressionCodec::from(Compression::LZO),
1985            CompressionCodec::LZO
1986        );
1987        assert_eq!(
1988            CompressionCodec::from(Compression::BROTLI(Default::default())),
1989            CompressionCodec::BROTLI
1990        );
1991        assert_eq!(
1992            CompressionCodec::from(Compression::LZ4),
1993            CompressionCodec::LZ4
1994        );
1995        assert_eq!(
1996            CompressionCodec::from(Compression::ZSTD(Default::default())),
1997            CompressionCodec::ZSTD
1998        );
1999        assert_eq!(
2000            CompressionCodec::from(Compression::LZ4_RAW),
2001            CompressionCodec::LZ4_RAW
2002        );
2003
2004        assert_eq!(
2005            Compression::from(CompressionCodec::UNCOMPRESSED),
2006            Compression::UNCOMPRESSED
2007        );
2008        assert_eq!(
2009            Compression::from(CompressionCodec::SNAPPY),
2010            Compression::SNAPPY
2011        );
2012        assert_eq!(
2013            Compression::from(CompressionCodec::GZIP),
2014            Compression::GZIP(Default::default())
2015        );
2016        assert_eq!(Compression::from(CompressionCodec::LZO), Compression::LZO);
2017        assert_eq!(
2018            Compression::from(CompressionCodec::BROTLI),
2019            Compression::BROTLI(Default::default())
2020        );
2021        assert_eq!(Compression::from(CompressionCodec::LZ4), Compression::LZ4);
2022        assert_eq!(
2023            Compression::from(CompressionCodec::ZSTD),
2024            Compression::ZSTD(Default::default())
2025        );
2026        assert_eq!(
2027            Compression::from(CompressionCodec::LZ4_RAW),
2028            Compression::LZ4_RAW
2029        );
2030    }
2031
2032    #[test]
2033    fn test_display_compression() {
2034        assert_eq!(Compression::UNCOMPRESSED.to_string(), "UNCOMPRESSED");
2035        assert_eq!(Compression::SNAPPY.to_string(), "SNAPPY");
2036        assert_eq!(
2037            Compression::GZIP(Default::default()).to_string(),
2038            "GZIP(GzipLevel(6))"
2039        );
2040        assert_eq!(Compression::LZO.to_string(), "LZO");
2041        assert_eq!(
2042            Compression::BROTLI(Default::default()).to_string(),
2043            "BROTLI(BrotliLevel(1))"
2044        );
2045        assert_eq!(Compression::LZ4.to_string(), "LZ4");
2046        assert_eq!(
2047            Compression::ZSTD(Default::default()).to_string(),
2048            "ZSTD(ZstdLevel(1))"
2049        );
2050    }
2051
2052    #[test]
2053    fn test_display_page_type() {
2054        assert_eq!(PageType::DATA_PAGE.to_string(), "DATA_PAGE");
2055        assert_eq!(PageType::INDEX_PAGE.to_string(), "INDEX_PAGE");
2056        assert_eq!(PageType::DICTIONARY_PAGE.to_string(), "DICTIONARY_PAGE");
2057        assert_eq!(PageType::DATA_PAGE_V2.to_string(), "DATA_PAGE_V2");
2058    }
2059
2060    #[test]
2061    fn test_display_sort_order() {
2062        assert_eq!(SortOrder::SIGNED.to_string(), "SIGNED");
2063        assert_eq!(SortOrder::UNSIGNED.to_string(), "UNSIGNED");
2064        assert_eq!(SortOrder::UNDEFINED.to_string(), "UNDEFINED");
2065        assert_eq!(SortOrder::TOTAL_ORDER.to_string(), "TOTAL_ORDER");
2066        assert_eq!(SortOrder::INT96_TIMESTAMP.to_string(), "INT96_TIMESTAMP");
2067    }
2068
2069    #[test]
2070    fn test_display_column_order() {
2071        assert_eq!(
2072            ColumnOrder::TYPE_DEFINED_ORDER(SortOrder::SIGNED).to_string(),
2073            "TYPE_DEFINED_ORDER(SIGNED)"
2074        );
2075        assert_eq!(
2076            ColumnOrder::TYPE_DEFINED_ORDER(SortOrder::UNSIGNED).to_string(),
2077            "TYPE_DEFINED_ORDER(UNSIGNED)"
2078        );
2079        assert_eq!(
2080            ColumnOrder::TYPE_DEFINED_ORDER(SortOrder::UNDEFINED).to_string(),
2081            "TYPE_DEFINED_ORDER(UNDEFINED)"
2082        );
2083        assert_eq!(
2084            ColumnOrder::IEEE_754_TOTAL_ORDER.to_string(),
2085            "IEEE_754_TOTAL_ORDER"
2086        );
2087        assert_eq!(
2088            ColumnOrder::INT96_TIMESTAMP_ORDER.to_string(),
2089            "INT96_TIMESTAMP_ORDER"
2090        );
2091        assert_eq!(ColumnOrder::UNDEFINED.to_string(), "UNDEFINED");
2092    }
2093
2094    #[test]
2095    fn test_column_order_roundtrip() {
2096        // SortOrder::SIGNED is the default on read.
2097        test_roundtrip(ColumnOrder::TYPE_DEFINED_ORDER(SortOrder::SIGNED))
2098    }
2099
2100    #[test]
2101    fn test_column_order_get_logical_type_sort_order() {
2102        // Helper to check the order in a list of values.
2103        // Only logical type is checked.
2104        fn check_sort_order(types: Vec<LogicalType>, expected_order: SortOrder) {
2105            for type_ in types {
2106                assert_eq!(
2107                    ColumnOrder::column_order_for_type(
2108                        Some(&type_),
2109                        ConvertedType::NONE,
2110                        Type::BYTE_ARRAY
2111                    )
2112                    .sort_order(),
2113                    expected_order
2114                );
2115            }
2116        }
2117
2118        // Unsigned comparison (physical type does not matter)
2119        let unsigned = vec![
2120            LogicalType::String,
2121            LogicalType::Json,
2122            LogicalType::Bson,
2123            LogicalType::Enum,
2124            LogicalType::Uuid,
2125            LogicalType::integer(8, false),
2126            LogicalType::integer(16, false),
2127            LogicalType::integer(32, false),
2128            LogicalType::integer(64, false),
2129        ];
2130        check_sort_order(unsigned, SortOrder::UNSIGNED);
2131
2132        // Signed comparison (physical type does not matter)
2133        let signed = vec![
2134            LogicalType::integer(8, true),
2135            LogicalType::integer(16, true),
2136            LogicalType::integer(32, true),
2137            LogicalType::integer(64, true),
2138            LogicalType::decimal(20, 4),
2139            LogicalType::Date,
2140            LogicalType::time(false, TimeUnit::MILLIS),
2141            LogicalType::time(false, TimeUnit::MICROS),
2142            LogicalType::time(true, TimeUnit::NANOS),
2143            LogicalType::timestamp(false, TimeUnit::MILLIS),
2144            LogicalType::timestamp(false, TimeUnit::MICROS),
2145            LogicalType::timestamp(true, TimeUnit::NANOS),
2146        ];
2147        check_sort_order(signed, SortOrder::SIGNED);
2148
2149        let float = vec![LogicalType::Float16];
2150        check_sort_order(float, SortOrder::TOTAL_ORDER);
2151
2152        // Undefined comparison
2153        let undefined = vec![
2154            LogicalType::List,
2155            LogicalType::Map,
2156            LogicalType::variant(None),
2157            LogicalType::geometry(None),
2158            LogicalType::geography(None, Some(Default::default())),
2159        ];
2160        check_sort_order(undefined, SortOrder::UNDEFINED);
2161    }
2162
2163    #[test]
2164    fn test_column_order_get_converted_type_sort_order() {
2165        // Helper to check the order in a list of values.
2166        // Only converted type is checked.
2167        fn check_sort_order(types: Vec<ConvertedType>, expected_order: SortOrder) {
2168            for type_ in types {
2169                assert_eq!(
2170                    ColumnOrder::column_order_for_type(None, type_, Type::BYTE_ARRAY).sort_order(),
2171                    expected_order
2172                );
2173            }
2174        }
2175
2176        // Unsigned comparison (physical type does not matter)
2177        let unsigned = vec![
2178            ConvertedType::UTF8,
2179            ConvertedType::JSON,
2180            ConvertedType::BSON,
2181            ConvertedType::ENUM,
2182            ConvertedType::UINT_8,
2183            ConvertedType::UINT_16,
2184            ConvertedType::UINT_32,
2185            ConvertedType::UINT_64,
2186        ];
2187        check_sort_order(unsigned, SortOrder::UNSIGNED);
2188
2189        // Signed comparison (physical type does not matter)
2190        let signed = vec![
2191            ConvertedType::INT_8,
2192            ConvertedType::INT_16,
2193            ConvertedType::INT_32,
2194            ConvertedType::INT_64,
2195            ConvertedType::DECIMAL,
2196            ConvertedType::DATE,
2197            ConvertedType::TIME_MILLIS,
2198            ConvertedType::TIME_MICROS,
2199            ConvertedType::TIMESTAMP_MILLIS,
2200            ConvertedType::TIMESTAMP_MICROS,
2201        ];
2202        check_sort_order(signed, SortOrder::SIGNED);
2203
2204        // Undefined comparison
2205        let undefined = vec![
2206            ConvertedType::LIST,
2207            ConvertedType::MAP,
2208            ConvertedType::MAP_KEY_VALUE,
2209            ConvertedType::INTERVAL,
2210        ];
2211        check_sort_order(undefined, SortOrder::UNDEFINED);
2212
2213        // Check None logical type
2214        // This should return a sort order for byte array type.
2215        check_sort_order(vec![ConvertedType::NONE], SortOrder::UNSIGNED);
2216    }
2217
2218    #[test]
2219    fn test_column_order_get_default_sort_order() {
2220        // Comparison based on physical type
2221        assert_eq!(
2222            ColumnOrder::get_default_sort_order(Type::BOOLEAN, true),
2223            SortOrder::UNSIGNED
2224        );
2225        assert_eq!(
2226            ColumnOrder::get_default_sort_order(Type::INT32, true),
2227            SortOrder::SIGNED
2228        );
2229        assert_eq!(
2230            ColumnOrder::get_default_sort_order(Type::INT64, true),
2231            SortOrder::SIGNED
2232        );
2233        assert_eq!(
2234            ColumnOrder::get_default_sort_order(Type::INT96, true),
2235            SortOrder::UNDEFINED
2236        );
2237        assert_eq!(
2238            ColumnOrder::get_default_sort_order(Type::INT96, false),
2239            SortOrder::INT96_TIMESTAMP
2240        );
2241        assert_eq!(
2242            ColumnOrder::get_default_sort_order(Type::FLOAT, false),
2243            SortOrder::TOTAL_ORDER
2244        );
2245        assert_eq!(
2246            ColumnOrder::get_default_sort_order(Type::DOUBLE, false),
2247            SortOrder::TOTAL_ORDER
2248        );
2249        assert_eq!(
2250            ColumnOrder::get_default_sort_order(Type::FLOAT, true),
2251            SortOrder::SIGNED
2252        );
2253        assert_eq!(
2254            ColumnOrder::get_default_sort_order(Type::DOUBLE, true),
2255            SortOrder::SIGNED
2256        );
2257        assert_eq!(
2258            ColumnOrder::get_default_sort_order(Type::BYTE_ARRAY, true),
2259            SortOrder::UNSIGNED
2260        );
2261        assert_eq!(
2262            ColumnOrder::get_default_sort_order(Type::FIXED_LEN_BYTE_ARRAY, true),
2263            SortOrder::UNSIGNED
2264        );
2265    }
2266
2267    #[test]
2268    fn test_column_order_sort_order() {
2269        assert_eq!(
2270            ColumnOrder::TYPE_DEFINED_ORDER(SortOrder::SIGNED).sort_order(),
2271            SortOrder::SIGNED
2272        );
2273        assert_eq!(
2274            ColumnOrder::TYPE_DEFINED_ORDER(SortOrder::UNSIGNED).sort_order(),
2275            SortOrder::UNSIGNED
2276        );
2277        assert_eq!(
2278            ColumnOrder::TYPE_DEFINED_ORDER(SortOrder::UNDEFINED).sort_order(),
2279            SortOrder::UNDEFINED
2280        );
2281        assert_eq!(
2282            ColumnOrder::IEEE_754_TOTAL_ORDER.sort_order(),
2283            SortOrder::TOTAL_ORDER
2284        );
2285        assert_eq!(
2286            ColumnOrder::INT96_TIMESTAMP_ORDER.sort_order(),
2287            SortOrder::INT96_TIMESTAMP
2288        );
2289        assert_eq!(ColumnOrder::UNDEFINED.sort_order(), SortOrder::SIGNED);
2290    }
2291
2292    #[test]
2293    fn test_parse_encoding() {
2294        let mut encoding: Encoding = "PLAIN".parse().unwrap();
2295        assert_eq!(encoding, Encoding::PLAIN);
2296        encoding = "PLAIN_DICTIONARY".parse().unwrap();
2297        assert_eq!(encoding, Encoding::PLAIN_DICTIONARY);
2298        encoding = "RLE".parse().unwrap();
2299        assert_eq!(encoding, Encoding::RLE);
2300        encoding = "BIT_PACKED".parse().unwrap();
2301        assert_eq!(encoding, Encoding::BIT_PACKED);
2302        encoding = "DELTA_BINARY_PACKED".parse().unwrap();
2303        assert_eq!(encoding, Encoding::DELTA_BINARY_PACKED);
2304        encoding = "DELTA_LENGTH_BYTE_ARRAY".parse().unwrap();
2305        assert_eq!(encoding, Encoding::DELTA_LENGTH_BYTE_ARRAY);
2306        encoding = "DELTA_BYTE_ARRAY".parse().unwrap();
2307        assert_eq!(encoding, Encoding::DELTA_BYTE_ARRAY);
2308        encoding = "RLE_DICTIONARY".parse().unwrap();
2309        assert_eq!(encoding, Encoding::RLE_DICTIONARY);
2310        encoding = "BYTE_STREAM_SPLIT".parse().unwrap();
2311        assert_eq!(encoding, Encoding::BYTE_STREAM_SPLIT);
2312        encoding = "alp".parse().unwrap();
2313        assert_eq!(encoding, Encoding::ALP);
2314
2315        // test lowercase
2316        encoding = "byte_stream_split".parse().unwrap();
2317        assert_eq!(encoding, Encoding::BYTE_STREAM_SPLIT);
2318
2319        // test unknown string
2320        match "plain_xxx".parse::<Encoding>() {
2321            Ok(e) => {
2322                panic!("Should not be able to parse {e:?}");
2323            }
2324            Err(e) => {
2325                assert_eq!(e.to_string(), "Parquet error: unknown encoding: plain_xxx");
2326            }
2327        }
2328    }
2329
2330    #[test]
2331    fn test_parse_compression() {
2332        let mut compress: Compression = "snappy".parse().unwrap();
2333        assert_eq!(compress, Compression::SNAPPY);
2334        compress = "lzo".parse().unwrap();
2335        assert_eq!(compress, Compression::LZO);
2336        compress = "zstd(3)".parse().unwrap();
2337        assert_eq!(compress, Compression::ZSTD(ZstdLevel::try_new(3).unwrap()));
2338        compress = "zstd(-3)".parse().unwrap();
2339        assert_eq!(compress, Compression::ZSTD(ZstdLevel::try_new(-3).unwrap()));
2340        compress = "LZ4_RAW".parse().unwrap();
2341        assert_eq!(compress, Compression::LZ4_RAW);
2342        compress = "uncompressed".parse().unwrap();
2343        assert_eq!(compress, Compression::UNCOMPRESSED);
2344        compress = "snappy".parse().unwrap();
2345        assert_eq!(compress, Compression::SNAPPY);
2346        compress = "gzip(9)".parse().unwrap();
2347        assert_eq!(compress, Compression::GZIP(GzipLevel::try_new(9).unwrap()));
2348        compress = "lzo".parse().unwrap();
2349        assert_eq!(compress, Compression::LZO);
2350        compress = "brotli(3)".parse().unwrap();
2351        assert_eq!(
2352            compress,
2353            Compression::BROTLI(BrotliLevel::try_new(3).unwrap())
2354        );
2355        compress = "lz4".parse().unwrap();
2356        assert_eq!(compress, Compression::LZ4);
2357
2358        // test unknown compression
2359        let mut err = "plain_xxx".parse::<Encoding>().unwrap_err();
2360        assert_eq!(
2361            err.to_string(),
2362            "Parquet error: unknown encoding: plain_xxx"
2363        );
2364
2365        // test invalid compress level
2366        err = "gzip(-10)".parse::<Encoding>().unwrap_err();
2367        assert_eq!(
2368            err.to_string(),
2369            "Parquet error: unknown encoding: gzip(-10)"
2370        );
2371    }
2372
2373    #[test]
2374    fn test_display_boundary_order() {
2375        assert_eq!(BoundaryOrder::ASCENDING.to_string(), "ASCENDING");
2376        assert_eq!(BoundaryOrder::DESCENDING.to_string(), "DESCENDING");
2377        assert_eq!(BoundaryOrder::UNORDERED.to_string(), "UNORDERED");
2378    }
2379
2380    #[test]
2381    fn test_display_edge_algo() {
2382        assert_eq!(
2383            EdgeInterpolationAlgorithm::SPHERICAL.to_string(),
2384            "SPHERICAL"
2385        );
2386        assert_eq!(EdgeInterpolationAlgorithm::VINCENTY.to_string(), "VINCENTY");
2387        assert_eq!(EdgeInterpolationAlgorithm::THOMAS.to_string(), "THOMAS");
2388        assert_eq!(EdgeInterpolationAlgorithm::ANDOYER.to_string(), "ANDOYER");
2389        assert_eq!(EdgeInterpolationAlgorithm::KARNEY.to_string(), "KARNEY");
2390    }
2391
2392    #[test]
2393    fn test_from_str_edge_algo() {
2394        assert_eq!(
2395            "spHErical".parse::<EdgeInterpolationAlgorithm>().unwrap(),
2396            EdgeInterpolationAlgorithm::SPHERICAL
2397        );
2398        assert_eq!(
2399            "vinceNTY".parse::<EdgeInterpolationAlgorithm>().unwrap(),
2400            EdgeInterpolationAlgorithm::VINCENTY
2401        );
2402        assert_eq!(
2403            "tHOmas".parse::<EdgeInterpolationAlgorithm>().unwrap(),
2404            EdgeInterpolationAlgorithm::THOMAS
2405        );
2406        assert_eq!(
2407            "anDOYEr".parse::<EdgeInterpolationAlgorithm>().unwrap(),
2408            EdgeInterpolationAlgorithm::ANDOYER
2409        );
2410        assert_eq!(
2411            "kaRNey".parse::<EdgeInterpolationAlgorithm>().unwrap(),
2412            EdgeInterpolationAlgorithm::KARNEY
2413        );
2414        assert!(
2415            "does not exist"
2416                .parse::<EdgeInterpolationAlgorithm>()
2417                .is_err()
2418        );
2419    }
2420
2421    fn encodings_roundtrip(mut encodings: Vec<Encoding>) {
2422        encodings.sort();
2423        let mask = EncodingMask::new_from_encodings(encodings.iter());
2424        assert!(mask.all_set(encodings.iter()));
2425        let v = mask.encodings().collect::<Vec<_>>();
2426        assert_eq!(v, encodings);
2427    }
2428
2429    #[test]
2430    fn test_encoding_roundtrip() {
2431        encodings_roundtrip(
2432            [
2433                Encoding::RLE,
2434                Encoding::PLAIN,
2435                Encoding::DELTA_BINARY_PACKED,
2436            ]
2437            .into(),
2438        );
2439        encodings_roundtrip([Encoding::RLE_DICTIONARY, Encoding::PLAIN_DICTIONARY].into());
2440        encodings_roundtrip([].into());
2441        let encodings = [
2442            Encoding::PLAIN,
2443            Encoding::BIT_PACKED,
2444            Encoding::RLE,
2445            Encoding::DELTA_BINARY_PACKED,
2446            Encoding::DELTA_BYTE_ARRAY,
2447            Encoding::DELTA_LENGTH_BYTE_ARRAY,
2448            Encoding::PLAIN_DICTIONARY,
2449            Encoding::RLE_DICTIONARY,
2450            Encoding::BYTE_STREAM_SPLIT,
2451            Encoding::ALP,
2452        ];
2453        encodings_roundtrip(encodings.into());
2454    }
2455
2456    #[test]
2457    fn test_invalid_encoding_mask() {
2458        // any set bits higher than the max should trigger an error
2459        let res = EncodingMask::try_new(-1);
2460        assert!(res.is_err());
2461        let err = res.unwrap_err();
2462        assert_eq!(
2463            err.to_string(),
2464            "Parquet error: Attempt to create invalid mask: 0xffffffff"
2465        );
2466
2467        // test that GROUP_VAR_INT is disallowed
2468        let res = EncodingMask::try_new(2);
2469        assert!(res.is_err());
2470        let err = res.unwrap_err();
2471        assert_eq!(
2472            err.to_string(),
2473            "Parquet error: Attempt to create invalid mask: 0x2"
2474        );
2475    }
2476
2477    #[test]
2478    fn test_encoding_mask_is_only() {
2479        let mask = EncodingMask::new_from_encodings([Encoding::PLAIN].iter());
2480        assert!(mask.is_only(Encoding::PLAIN));
2481
2482        let mask =
2483            EncodingMask::new_from_encodings([Encoding::PLAIN, Encoding::PLAIN_DICTIONARY].iter());
2484        assert!(!mask.is_only(Encoding::PLAIN));
2485    }
2486}