Skip to main content

parquet/
basic.rs

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