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