Skip to main content

parquet/arrow/
mod.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//! API for reading/writing Arrow [`RecordBatch`]es and [`Array`]s to/from
19//! Parquet Files.
20//!
21//! See the [crate-level documentation](crate) for more details on other APIs
22//!
23//! # Schema Conversion
24//!
25//! These APIs ensure that data in Arrow [`RecordBatch`]es written to Parquet are
26//! read back as [`RecordBatch`]es with the exact same types and values.
27//!
28//! Parquet and Arrow have different type systems, and there is not
29//! always a one to one mapping between the systems. For example, data
30//! stored as a Parquet [`BYTE_ARRAY`] can be read as either an Arrow
31//! [`BinaryViewArray`] or [`BinaryArray`].
32//!
33//! To recover the original Arrow types, the writers in this module add a "hint" to
34//! the metadata in the [`ARROW_SCHEMA_META_KEY`] key which records the original Arrow
35//! schema. The metadata hint follows the same convention as arrow-cpp based
36//! implementations such as `pyarrow`. The reader looks for the schema hint in the
37//! metadata to determine Arrow types, and if it is not present, infers the Arrow schema
38//! from the Parquet schema.
39//!
40//! In situations where the embedded Arrow schema is not compatible with the Parquet
41//! schema, the Parquet schema takes precedence and no error is raised.
42//! See [#1663](https://github.com/apache/arrow-rs/issues/1663)
43//!
44//! You can also control the type conversion process in more detail using:
45//!
46//! * [`ArrowSchemaConverter`] control the conversion of Arrow types to Parquet
47//!   types.
48//!
49//! * [`ArrowReaderOptions::with_schema`] to explicitly specify your own Arrow schema hint
50//!   to use when reading Parquet, overriding any metadata that may be present.
51//!
52//! [`RecordBatch`]: arrow_array::RecordBatch
53//! [`Array`]: arrow_array::Array
54//! [`BYTE_ARRAY`]: crate::basic::Type::BYTE_ARRAY
55//! [`BinaryViewArray`]: arrow_array::BinaryViewArray
56//! [`BinaryArray`]: arrow_array::BinaryArray
57//! [`ArrowReaderOptions::with_schema`]: arrow_reader::ArrowReaderOptions::with_schema
58//!
59//! # Example: Writing Arrow `RecordBatch` to Parquet file
60//!
61//!```rust
62//! # use arrow_array::{Int32Array, ArrayRef};
63//! # use arrow_array::RecordBatch;
64//! # use parquet::arrow::arrow_writer::ArrowWriter;
65//! # use parquet::file::properties::WriterProperties;
66//! # use tempfile::tempfile;
67//! # use std::sync::Arc;
68//! # use parquet::basic::Compression;
69//! let ids = Int32Array::from(vec![1, 2, 3, 4]);
70//! let vals = Int32Array::from(vec![5, 6, 7, 8]);
71//! let batch = RecordBatch::try_from_iter(vec![
72//!   ("id", Arc::new(ids) as ArrayRef),
73//!   ("val", Arc::new(vals) as ArrayRef),
74//! ]).unwrap();
75//!
76//! let file = tempfile().unwrap();
77//!
78//! // WriterProperties can be used to set Parquet file options
79//! let props = WriterProperties::builder()
80//!     .set_compression(Compression::SNAPPY)
81//!     .build();
82//!
83//! let mut writer = ArrowWriter::try_new(file, batch.schema(), Some(props)).unwrap();
84//!
85//! writer.write(&batch).expect("Writing batch");
86//!
87//! // writer must be closed to write footer
88//! writer.close().unwrap();
89//! ```
90//!
91//! # Example: Reading Parquet file into Arrow `RecordBatch`
92//!
93//! ```rust
94//! # use std::fs::File;
95//! # use parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder;
96//! # use std::sync::Arc;
97//! # use arrow_array::Int32Array;
98//! # use arrow::datatypes::{DataType, Field, Schema};
99//! # use arrow_array::RecordBatch;
100//! # use parquet::arrow::arrow_writer::ArrowWriter;
101//! #
102//! # let ids = Int32Array::from(vec![1, 2, 3, 4]);
103//! # let schema = Arc::new(Schema::new(vec![
104//! #     Field::new("id", DataType::Int32, false),
105//! # ]));
106//! #
107//! # let file = File::create("data.parquet").unwrap();
108//! #
109//! # let batch = RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(ids)]).unwrap();
110//! # let batches = vec![batch];
111//! #
112//! # let mut writer = ArrowWriter::try_new(file, Arc::clone(&schema), None).unwrap();
113//! #
114//! # for batch in batches {
115//! #     writer.write(&batch).expect("Writing batch");
116//! # }
117//! # writer.close().unwrap();
118//! #
119//! let file = File::open("data.parquet").unwrap();
120//!
121//! let builder = ParquetRecordBatchReaderBuilder::try_new(file).unwrap();
122//! println!("Converted arrow schema is: {}", builder.schema());
123//!
124//! let mut reader = builder.build().unwrap();
125//!
126//! let record_batch = reader.next().unwrap().unwrap();
127//!
128//! println!("Read {} records.", record_batch.num_rows());
129//! ```
130//!
131//! # Example: Reading non-uniformly encrypted parquet file into arrow record batch
132//!
133//! Note: This requires the experimental `encryption` feature to be enabled at compile time.
134//!
135#![cfg_attr(feature = "encryption", doc = "```rust")]
136#![cfg_attr(not(feature = "encryption"), doc = "```ignore")]
137//! # use arrow_array::{Int32Array, ArrayRef};
138//! # use arrow_array::{types, RecordBatch};
139//! # use parquet::arrow::arrow_reader::{
140//! #     ArrowReaderMetadata, ArrowReaderOptions, ParquetRecordBatchReaderBuilder,
141//! # };
142//! # use arrow_array::cast::AsArray;
143//! # use parquet::file::metadata::ParquetMetaData;
144//! # use tempfile::tempfile;
145//! # use std::fs::File;
146//! # use parquet::encryption::decrypt::FileDecryptionProperties;
147//! # let test_data = arrow::util::test_util::parquet_test_data();
148//! # let path = format!("{test_data}/encrypt_columns_and_footer.parquet.encrypted");
149//! #
150//! let file = File::open(path).unwrap();
151//!
152//! // Define the AES encryption keys required required for decrypting the footer metadata
153//! // and column-specific data. If only a footer key is used then it is assumed that the
154//! // file uses uniform encryption and all columns are encrypted with the footer key.
155//! // If any column keys are specified, other columns without a key provided are assumed
156//! // to be unencrypted
157//! let footer_key = "0123456789012345".as_bytes(); // Keys are 128 bits (16 bytes)
158//! let column_1_key = "1234567890123450".as_bytes();
159//! let column_2_key = "1234567890123451".as_bytes();
160//!
161//! let decryption_properties = FileDecryptionProperties::builder(footer_key.to_vec())
162//!     .with_column_key("double_field", column_1_key.to_vec())
163//!     .with_column_key("float_field", column_2_key.to_vec())
164//!     .build()
165//!     .unwrap();
166//!
167//! let options = ArrowReaderOptions::default()
168//!  .with_file_decryption_properties(decryption_properties);
169//! let reader_metadata = ArrowReaderMetadata::load(&file, options.clone()).unwrap();
170//! let file_metadata = reader_metadata.metadata().file_metadata();
171//! assert_eq!(50, file_metadata.num_rows());
172//!
173//! let mut reader = ParquetRecordBatchReaderBuilder::try_new_with_options(file, options)
174//!   .unwrap()
175//!   .build()
176//!   .unwrap();
177//!
178//! let record_batch = reader.next().unwrap().unwrap();
179//! assert_eq!(50, record_batch.num_rows());
180//! ```
181
182experimental!(mod array_reader);
183pub mod arrow_reader;
184pub mod arrow_writer;
185mod buffer;
186mod decoder;
187
188#[cfg(feature = "async")]
189pub mod async_reader;
190#[cfg(feature = "async")]
191pub mod async_writer;
192
193pub mod push_decoder;
194
195mod in_memory_row_group;
196mod record_reader;
197
198experimental!(mod schema);
199
200use std::fmt::Debug;
201
202pub use self::arrow_writer::ArrowWriter;
203#[cfg(feature = "async")]
204pub use self::async_reader::ParquetRecordBatchStreamBuilder;
205#[cfg(feature = "async")]
206pub use self::async_writer::AsyncArrowWriter;
207use crate::schema::types::SchemaDescriptor;
208use arrow_schema::{FieldRef, Schema};
209
210pub use self::schema::{
211    ArrowSchemaConverter, FieldLevels, add_encoded_arrow_schema_to_metadata, encode_arrow_schema,
212    parquet_to_arrow_field_levels, parquet_to_arrow_field_levels_with_virtual,
213    parquet_to_arrow_schema, parquet_to_arrow_schema_by_columns, virtual_type::*,
214};
215
216/// Schema metadata key used to store serialized Arrow schema
217///
218/// The Arrow schema is encoded using the Arrow IPC format, and then base64
219/// encoded. This is the same format used by arrow-cpp systems, such as pyarrow.
220pub const ARROW_SCHEMA_META_KEY: &str = "ARROW:schema";
221
222/// The value of this metadata key, if present on [`Field::metadata`], will be used
223/// to populate [`BasicTypeInfo::id`]
224///
225/// [`Field::metadata`]: arrow_schema::Field::metadata
226/// [`BasicTypeInfo::id`]: crate::schema::types::BasicTypeInfo::id
227pub const PARQUET_FIELD_ID_META_KEY: &str = "PARQUET:field_id";
228
229/// A [`ProjectionMask`] identifies a set of columns within a potentially nested schema to project
230///
231/// In particular, a [`ProjectionMask`] can be constructed from a list of leaf column indices
232/// or root column indices where:
233///
234/// * Root columns are the direct children of the root schema, enumerated in order
235/// * Leaf columns are the child-less leaves of the schema as enumerated by a depth-first search
236///
237/// For example, the schema
238///
239/// ```ignore
240/// message schema {
241///   REQUIRED boolean         leaf_1;
242///   REQUIRED GROUP group {
243///     OPTIONAL int32 leaf_2;
244///     OPTIONAL int64 leaf_3;
245///   }
246/// }
247/// ```
248///
249/// Has roots `["leaf_1", "group"]` and leaves `["leaf_1", "leaf_2", "leaf_3"]`
250///
251/// For non-nested schemas, i.e. those containing only primitive columns, the root
252/// and leaves are the same
253///
254#[derive(Debug, Clone, PartialEq, Eq)]
255pub struct ProjectionMask {
256    /// If `Some`, a leaf column should be included if the value at
257    /// the corresponding index is true
258    ///
259    /// If `None`, all columns should be included
260    ///
261    /// # Examples
262    ///
263    /// Given the original parquet schema with leaf columns is `[a, b, c, d]`
264    ///
265    /// A mask of `[true, false, true, false]` will result in a schema 2
266    /// elements long:
267    /// * `fields[0]`: `a`
268    /// * `fields[1]`: `c`
269    ///
270    /// A mask of `None` will result in a schema 4 elements long:
271    /// * `fields[0]`: `a`
272    /// * `fields[1]`: `b`
273    /// * `fields[2]`: `c`
274    /// * `fields[3]`: `d`
275    mask: Option<Vec<bool>>,
276}
277
278impl ProjectionMask {
279    /// Create a [`ProjectionMask`] which selects all columns
280    pub fn all() -> Self {
281        Self { mask: None }
282    }
283
284    /// Create a [`ProjectionMask`] which selects no columns
285    pub fn none(len: usize) -> Self {
286        Self {
287            mask: Some(vec![false; len]),
288        }
289    }
290
291    /// Create a [`ProjectionMask`] which selects only the specified leaf columns
292    ///
293    /// Note: repeated or out of order indices will not impact the final mask
294    ///
295    /// i.e. `[0, 1, 2]` will construct the same mask as `[1, 0, 0, 2]`
296    pub fn leaves(schema: &SchemaDescriptor, indices: impl IntoIterator<Item = usize>) -> Self {
297        let mut mask = vec![false; schema.num_columns()];
298        for leaf_idx in indices {
299            mask[leaf_idx] = true;
300        }
301        Self { mask: Some(mask) }
302    }
303
304    /// Create a [`ProjectionMask`] which selects only the specified root columns
305    ///
306    /// Note: repeated or out of order indices will not impact the final mask
307    ///
308    /// i.e. `[0, 1, 2]` will construct the same mask as `[1, 0, 0, 2]`
309    pub fn roots(schema: &SchemaDescriptor, indices: impl IntoIterator<Item = usize>) -> Self {
310        let num_root_columns = schema.root_schema().get_fields().len();
311        let mut root_mask = vec![false; num_root_columns];
312        for root_idx in indices {
313            root_mask[root_idx] = true;
314        }
315
316        let mask = (0..schema.num_columns())
317            .map(|leaf_idx| {
318                let root_idx = schema.get_column_root_idx(leaf_idx);
319                root_mask[root_idx]
320            })
321            .collect();
322
323        Self { mask: Some(mask) }
324    }
325
326    /// Create a [`ProjectionMask`] which selects only the named columns
327    ///
328    /// All leaf columns that fall below a given name will be selected. For example, given
329    /// the schema
330    /// ```ignore
331    /// message schema {
332    ///   OPTIONAL group a (MAP) {
333    ///     REPEATED group key_value {
334    ///       REQUIRED BYTE_ARRAY key (UTF8);  // leaf index 0
335    ///       OPTIONAL group value (MAP) {
336    ///         REPEATED group key_value {
337    ///           REQUIRED INT32 key;          // leaf index 1
338    ///           REQUIRED BOOLEAN value;      // leaf index 2
339    ///         }
340    ///       }
341    ///     }
342    ///   }
343    ///   REQUIRED INT32 b;                    // leaf index 3
344    ///   REQUIRED DOUBLE c;                   // leaf index 4
345    /// }
346    /// ```
347    /// `["a.key_value.value", "c"]` would return leaf columns 1, 2, and 4. `["a"]` would return
348    /// columns 0, 1, and 2.
349    ///
350    /// Note: repeated or out of order indices will not impact the final mask.
351    ///
352    /// i.e. `["b", "c"]` will construct the same mask as `["c", "b", "c"]`.
353    ///
354    /// Also, this will not produce the desired results if a column contains a '.' in its name.
355    /// Use [`Self::leaves`] or [`Self::roots`] in that case.
356    pub fn columns<'a>(
357        schema: &SchemaDescriptor,
358        names: impl IntoIterator<Item = &'a str>,
359    ) -> Self {
360        let mut mask = vec![false; schema.num_columns()];
361        for name in names {
362            let name_path: Vec<&str> = name.split('.').collect();
363            for (idx, col) in schema.columns().iter().enumerate() {
364                let path = col.path().parts();
365                // searching for "a.b.c" cannot match "a.b"
366                if name_path.len() > path.len() {
367                    continue;
368                }
369                // now path >= name_path, so check that each element in name_path matches
370                if name_path.iter().zip(path.iter()).all(|(a, b)| a == b) {
371                    mask[idx] = true;
372                }
373            }
374        }
375
376        Self { mask: Some(mask) }
377    }
378
379    /// Returns true if the leaf column `leaf_idx` is included by the mask
380    pub fn leaf_included(&self, leaf_idx: usize) -> bool {
381        self.mask.as_ref().map(|m| m[leaf_idx]).unwrap_or(true)
382    }
383
384    /// Union two projection masks
385    ///
386    /// Example:
387    /// ```text
388    /// mask1 = [true, false, true]
389    /// mask2 = [false, true, true]
390    /// union(mask1, mask2) = [true, true, true]
391    /// ```
392    pub fn union(&mut self, other: &Self) {
393        match (self.mask.as_ref(), other.mask.as_ref()) {
394            (None, _) | (_, None) => self.mask = None,
395            (Some(a), Some(b)) => {
396                debug_assert_eq!(a.len(), b.len());
397                let mask = a.iter().zip(b.iter()).map(|(&a, &b)| a || b).collect();
398                self.mask = Some(mask);
399            }
400        }
401    }
402
403    /// Intersect two projection masks
404    ///
405    /// Example:
406    /// ```text
407    /// mask1 = [true, false, true]
408    /// mask2 = [false, true, true]
409    /// intersect(mask1, mask2) = [false, false, true]
410    /// ```
411    pub fn intersect(&mut self, other: &Self) {
412        match (self.mask.as_ref(), other.mask.as_ref()) {
413            (None, _) => self.mask.clone_from(&other.mask),
414            (_, None) => {}
415            (Some(a), Some(b)) => {
416                debug_assert_eq!(a.len(), b.len());
417                let mask = a.iter().zip(b.iter()).map(|(&a, &b)| a && b).collect();
418                self.mask = Some(mask);
419            }
420        }
421    }
422
423    /// Return a new [`ProjectionMask`] that excludes any leaf columns that are
424    /// part of a nested type, such as struct, list, or map
425    ///
426    /// If there are no non-nested columns in the mask, returns `None`
427    pub(crate) fn without_nested_types(&self, schema: &SchemaDescriptor) -> Option<Self> {
428        let num_leaves = schema.num_columns();
429
430        // Count how many leaves each root column has
431        let num_roots = schema.root_schema().get_fields().len();
432        let mut root_leaf_counts = vec![0usize; num_roots];
433        for leaf_idx in 0..num_leaves {
434            let root_idx = schema.get_column_root_idx(leaf_idx);
435            root_leaf_counts[root_idx] += 1;
436        }
437
438        // Cache only top-level primitive columns.
439        // Even a one-leaf group is nested; caching it drops parent def levels.
440        let mut included_leaves = Vec::new();
441        for leaf_idx in 0..num_leaves {
442            if self.leaf_included(leaf_idx) {
443                let root = schema.get_column_root(leaf_idx);
444                let root_idx = schema.get_column_root_idx(leaf_idx);
445                if root_leaf_counts[root_idx] == 1 && root.is_primitive() {
446                    included_leaves.push(leaf_idx);
447                }
448            }
449        }
450
451        if included_leaves.is_empty() {
452            None
453        } else {
454            Some(ProjectionMask::leaves(schema, included_leaves))
455        }
456    }
457}
458
459/// Lookups up the parquet column by name
460///
461/// Returns the parquet column index and the corresponding arrow field
462pub fn parquet_column<'a>(
463    parquet_schema: &SchemaDescriptor,
464    arrow_schema: &'a Schema,
465    name: &str,
466) -> Option<(usize, &'a FieldRef)> {
467    let (root_idx, field) = arrow_schema.fields.find(name)?;
468    if field.data_type().is_nested() {
469        // Nested fields are not supported and require non-trivial logic
470        // to correctly walk the parquet schema accounting for the
471        // logical type rules - <https://github.com/apache/parquet-format/blob/master/LogicalTypes.md>
472        //
473        // For example a ListArray could correspond to anything from 1 to 3 levels
474        // in the parquet schema
475        return None;
476    }
477
478    // This could be made more efficient (#TBD)
479    let parquet_idx = (0..parquet_schema.columns().len())
480        .find(|x| parquet_schema.get_column_root_idx(*x) == root_idx)?;
481    Some((parquet_idx, field))
482}
483
484#[cfg(test)]
485mod test {
486    use crate::arrow::ArrowWriter;
487    use crate::file::metadata::{
488        PageIndexPolicy, ParquetMetaData, ParquetMetaDataOptions, ParquetMetaDataReader,
489        ParquetMetaDataWriter,
490    };
491    use crate::file::properties::{EnabledStatistics, WriterProperties};
492    use crate::schema::parser::parse_message_type;
493    use crate::schema::types::SchemaDescriptor;
494    use arrow_array::{ArrayRef, Int32Array, RecordBatch};
495    use bytes::Bytes;
496    use std::sync::Arc;
497
498    use super::ProjectionMask;
499
500    #[test]
501    // Reproducer for https://github.com/apache/arrow-rs/issues/6464
502    fn test_metadata_read_write_partial_offset() {
503        let parquet_bytes = create_parquet_file();
504
505        // read the metadata from the file WITHOUT the page index structures
506        let options = ParquetMetaDataOptions::new().with_encoding_stats_as_mask(false);
507        let original_metadata = ParquetMetaDataReader::new()
508            .with_metadata_options(Some(options))
509            .parse_and_finish(&parquet_bytes)
510            .unwrap();
511
512        // this should error because the page indexes are not present, but have offsets specified
513        let metadata_bytes = metadata_to_bytes(&original_metadata);
514        let options = ParquetMetaDataOptions::new().with_encoding_stats_as_mask(false);
515        let err = ParquetMetaDataReader::new()
516            .with_metadata_options(Some(options))
517            .with_page_index_policy(PageIndexPolicy::Required) // there are no page indexes in the metadata
518            .parse_and_finish(&metadata_bytes)
519            .err()
520            .unwrap();
521        assert_eq!(
522            err.to_string(),
523            "EOF: Parquet file too small. Page index range 82..115 overlaps with file metadata 0..357"
524        );
525    }
526
527    #[test]
528    fn test_metadata_read_write_roundtrip() {
529        let parquet_bytes = create_parquet_file();
530
531        // read the metadata from the file
532        let options = ParquetMetaDataOptions::new().with_encoding_stats_as_mask(false);
533        let original_metadata = ParquetMetaDataReader::new()
534            .with_metadata_options(Some(options))
535            .parse_and_finish(&parquet_bytes)
536            .unwrap();
537
538        // read metadata back from the serialized bytes and ensure it is the same
539        let metadata_bytes = metadata_to_bytes(&original_metadata);
540        assert_ne!(
541            metadata_bytes.len(),
542            parquet_bytes.len(),
543            "metadata is subset of parquet"
544        );
545
546        let options = ParquetMetaDataOptions::new().with_encoding_stats_as_mask(false);
547        let roundtrip_metadata = ParquetMetaDataReader::new()
548            .with_metadata_options(Some(options))
549            .parse_and_finish(&metadata_bytes)
550            .unwrap();
551
552        assert_eq!(original_metadata, roundtrip_metadata);
553    }
554
555    #[test]
556    #[cfg_attr(miri, ignore)] // Takes too long
557    fn test_metadata_read_write_roundtrip_page_index() {
558        let parquet_bytes = create_parquet_file();
559
560        // read the metadata from the file including the page index structures
561        // (which are stored elsewhere in the footer)
562        let options = ParquetMetaDataOptions::new().with_encoding_stats_as_mask(false);
563        let original_metadata = ParquetMetaDataReader::new()
564            .with_metadata_options(Some(options))
565            .with_page_index_policy(PageIndexPolicy::Required)
566            .parse_and_finish(&parquet_bytes)
567            .unwrap();
568
569        // read metadata back from the serialized bytes and ensure it is the same
570        let metadata_bytes = metadata_to_bytes(&original_metadata);
571        let options = ParquetMetaDataOptions::new().with_encoding_stats_as_mask(false);
572        let roundtrip_metadata = ParquetMetaDataReader::new()
573            .with_metadata_options(Some(options))
574            .with_page_index_policy(PageIndexPolicy::Required)
575            .parse_and_finish(&metadata_bytes)
576            .unwrap();
577
578        // Need to normalize the metadata first to remove offsets in data
579        let original_metadata = normalize_locations(original_metadata);
580        let roundtrip_metadata = normalize_locations(roundtrip_metadata);
581        assert_eq!(
582            format!("{original_metadata:#?}"),
583            format!("{roundtrip_metadata:#?}")
584        );
585        assert_eq!(original_metadata, roundtrip_metadata);
586    }
587
588    /// Sets the page index offset locations in the metadata to `None`
589    ///
590    /// This is because the offsets are used to find the relative location of the index
591    /// structures, and thus differ depending on how the structures are stored.
592    fn normalize_locations(metadata: ParquetMetaData) -> ParquetMetaData {
593        let mut metadata_builder = metadata.into_builder();
594        for rg in metadata_builder.take_row_groups() {
595            let mut rg_builder = rg.into_builder();
596            for col in rg_builder.take_columns() {
597                rg_builder = rg_builder.add_column_metadata(
598                    col.into_builder()
599                        .set_offset_index_offset(None)
600                        .set_index_page_offset(None)
601                        .set_column_index_offset(None)
602                        .build()
603                        .unwrap(),
604                );
605            }
606            let rg = rg_builder.build().unwrap();
607            metadata_builder = metadata_builder.add_row_group(rg);
608        }
609        metadata_builder.build()
610    }
611
612    /// Write a parquet filed into an in memory buffer
613    fn create_parquet_file() -> Bytes {
614        let mut buf = vec![];
615        let data = vec![100, 200, 201, 300, 102, 33];
616        let array: ArrayRef = Arc::new(Int32Array::from(data));
617        let batch = RecordBatch::try_from_iter(vec![("id", array)]).unwrap();
618        let props = WriterProperties::builder()
619            .set_statistics_enabled(EnabledStatistics::Page)
620            .set_write_page_header_statistics(true)
621            .build();
622
623        let mut writer = ArrowWriter::try_new(&mut buf, batch.schema(), Some(props)).unwrap();
624        writer.write(&batch).unwrap();
625        writer.finish().unwrap();
626        drop(writer);
627
628        Bytes::from(buf)
629    }
630
631    /// Serializes `ParquetMetaData` into a memory buffer, using `ParquetMetadataWriter
632    fn metadata_to_bytes(metadata: &ParquetMetaData) -> Bytes {
633        let mut buf = vec![];
634        ParquetMetaDataWriter::new(&mut buf, metadata)
635            .finish()
636            .unwrap();
637        Bytes::from(buf)
638    }
639
640    #[test]
641    fn test_mask_from_column_names() {
642        let schema = parse_schema(
643            "
644            message test_schema {
645                OPTIONAL group a (MAP) {
646                    REPEATED group key_value {
647                        REQUIRED BYTE_ARRAY key (UTF8);
648                        OPTIONAL group value (MAP) {
649                            REPEATED group key_value {
650                                REQUIRED INT32 key;
651                                REQUIRED BOOLEAN value;
652                            }
653                        }
654                    }
655                }
656                REQUIRED INT32 b;
657                REQUIRED DOUBLE c;
658            }
659            ",
660        );
661
662        let mask = ProjectionMask::columns(&schema, ["foo", "bar"]);
663        assert_eq!(mask.mask.unwrap(), vec![false; 5]);
664
665        let mask = ProjectionMask::columns(&schema, []);
666        assert_eq!(mask.mask.unwrap(), vec![false; 5]);
667
668        let mask = ProjectionMask::columns(&schema, ["a", "c"]);
669        assert_eq!(mask.mask.unwrap(), [true, true, true, false, true]);
670
671        let mask = ProjectionMask::columns(&schema, ["a.key_value.key", "c"]);
672        assert_eq!(mask.mask.unwrap(), [true, false, false, false, true]);
673
674        let mask = ProjectionMask::columns(&schema, ["a.key_value.value", "b"]);
675        assert_eq!(mask.mask.unwrap(), [false, true, true, true, false]);
676
677        let schema = parse_schema(
678            "
679            message test_schema {
680                OPTIONAL group a (LIST) {
681                    REPEATED group list {
682                        OPTIONAL group element (LIST) {
683                            REPEATED group list {
684                                OPTIONAL group element (LIST) {
685                                    REPEATED group list {
686                                        OPTIONAL BYTE_ARRAY element (UTF8);
687                                    }
688                                }
689                            }
690                        }
691                    }
692                }
693                REQUIRED INT32 b;
694            }
695            ",
696        );
697
698        let mask = ProjectionMask::columns(&schema, ["a", "b"]);
699        assert_eq!(mask.mask.unwrap(), [true, true]);
700
701        let mask = ProjectionMask::columns(&schema, ["a.list.element", "b"]);
702        assert_eq!(mask.mask.unwrap(), [true, true]);
703
704        let mask =
705            ProjectionMask::columns(&schema, ["a.list.element.list.element.list.element", "b"]);
706        assert_eq!(mask.mask.unwrap(), [true, true]);
707
708        let mask = ProjectionMask::columns(&schema, ["b"]);
709        assert_eq!(mask.mask.unwrap(), [false, true]);
710
711        let schema = parse_schema(
712            "
713            message test_schema {
714                OPTIONAL INT32 a;
715                OPTIONAL INT32 b;
716                OPTIONAL INT32 c;
717                OPTIONAL INT32 d;
718                OPTIONAL INT32 e;
719            }
720            ",
721        );
722
723        let mask = ProjectionMask::columns(&schema, ["a", "b"]);
724        assert_eq!(mask.mask.unwrap(), [true, true, false, false, false]);
725
726        let mask = ProjectionMask::columns(&schema, ["d", "b", "d"]);
727        assert_eq!(mask.mask.unwrap(), [false, true, false, true, false]);
728
729        let schema = parse_schema(
730            "
731            message test_schema {
732                OPTIONAL INT32 a;
733                OPTIONAL INT32 b;
734                OPTIONAL INT32 a;
735                OPTIONAL INT32 d;
736                OPTIONAL INT32 e;
737            }
738            ",
739        );
740
741        let mask = ProjectionMask::columns(&schema, ["a", "e"]);
742        assert_eq!(mask.mask.unwrap(), [true, false, true, false, true]);
743
744        let schema = parse_schema(
745            "
746            message test_schema {
747                OPTIONAL INT32 a;
748                OPTIONAL INT32 aa;
749            }
750            ",
751        );
752
753        let mask = ProjectionMask::columns(&schema, ["a"]);
754        assert_eq!(mask.mask.unwrap(), [true, false]);
755    }
756
757    #[test]
758    fn test_projection_mask_union() {
759        let mut mask1 = ProjectionMask {
760            mask: Some(vec![true, false, true]),
761        };
762        let mask2 = ProjectionMask {
763            mask: Some(vec![false, true, true]),
764        };
765        mask1.union(&mask2);
766        assert_eq!(mask1.mask, Some(vec![true, true, true]));
767
768        let mut mask1 = ProjectionMask { mask: None };
769        let mask2 = ProjectionMask {
770            mask: Some(vec![false, true, true]),
771        };
772        mask1.union(&mask2);
773        assert_eq!(mask1.mask, None);
774
775        let mut mask1 = ProjectionMask {
776            mask: Some(vec![true, false, true]),
777        };
778        let mask2 = ProjectionMask { mask: None };
779        mask1.union(&mask2);
780        assert_eq!(mask1.mask, None);
781
782        let mut mask1 = ProjectionMask { mask: None };
783        let mask2 = ProjectionMask { mask: None };
784        mask1.union(&mask2);
785        assert_eq!(mask1.mask, None);
786    }
787
788    #[test]
789    fn test_projection_mask_intersect() {
790        let mut mask1 = ProjectionMask {
791            mask: Some(vec![true, false, true]),
792        };
793        let mask2 = ProjectionMask {
794            mask: Some(vec![false, true, true]),
795        };
796        mask1.intersect(&mask2);
797        assert_eq!(mask1.mask, Some(vec![false, false, true]));
798
799        let mut mask1 = ProjectionMask { mask: None };
800        let mask2 = ProjectionMask {
801            mask: Some(vec![false, true, true]),
802        };
803        mask1.intersect(&mask2);
804        assert_eq!(mask1.mask, Some(vec![false, true, true]));
805
806        let mut mask1 = ProjectionMask {
807            mask: Some(vec![true, false, true]),
808        };
809        let mask2 = ProjectionMask { mask: None };
810        mask1.intersect(&mask2);
811        assert_eq!(mask1.mask, Some(vec![true, false, true]));
812
813        let mut mask1 = ProjectionMask { mask: None };
814        let mask2 = ProjectionMask { mask: None };
815        mask1.intersect(&mask2);
816        assert_eq!(mask1.mask, None);
817    }
818
819    #[test]
820    fn test_projection_mask_without_nested_no_nested() {
821        // Schema with no nested types
822        let schema = parse_schema(
823            "
824            message test_schema {
825                OPTIONAL INT32 a;
826                OPTIONAL INT32 b;
827                REQUIRED DOUBLE d;
828            }
829            ",
830        );
831
832        let mask = ProjectionMask::all();
833        // All columns are non-nested, but without_nested_types returns a new mask
834        assert_eq!(
835            Some(ProjectionMask::leaves(&schema, [0, 1, 2])),
836            mask.without_nested_types(&schema)
837        );
838
839        // select b, c
840        let mask = ProjectionMask::leaves(&schema, [1, 2]);
841        assert_eq!(Some(mask.clone()), mask.without_nested_types(&schema));
842    }
843
844    #[test]
845    fn test_projection_mask_without_nested_nested() {
846        // Schema with nested types (structs)
847        let schema = parse_schema(
848            "
849            message test_schema {
850                OPTIONAL INT32 a;
851                OPTIONAL group b {
852                    REQUIRED INT32 b1;
853                    OPTIONAL INT64 b2;
854                }
855                OPTIONAL group c (LIST) {
856                    REPEATED group list {
857                        OPTIONAL INT32 element;
858                    }
859                }
860                REQUIRED DOUBLE d;
861            }
862            ",
863        );
864
865        // all leaves --> a, d
866        let mask = ProjectionMask::all();
867        assert_eq!(
868            Some(ProjectionMask::leaves(&schema, [0, 4])),
869            mask.without_nested_types(&schema)
870        );
871
872        // b1 --> empty (it is nested)
873        let mask = ProjectionMask::leaves(&schema, [1]);
874        assert_eq!(None, mask.without_nested_types(&schema));
875
876        // b2, d --> d
877        let mask = ProjectionMask::leaves(&schema, [1, 4]);
878        assert_eq!(
879            Some(ProjectionMask::leaves(&schema, [4])),
880            mask.without_nested_types(&schema)
881        );
882
883        // element --> empty (it is nested)
884        let mask = ProjectionMask::leaves(&schema, [3]);
885        assert_eq!(None, mask.without_nested_types(&schema));
886    }
887
888    #[test]
889    fn test_projection_mask_without_nested_map_only() {
890        // Example from https://github.com/apache/parquet-format/blob/master/LogicalTypes.md
891        let schema = parse_schema(
892            "
893            message test_schema {
894                required group my_map (MAP) {
895                    repeated group key_value {
896                        required binary key (STRING);
897                        optional int32 value;
898                    }
899                }
900            }
901            ",
902        );
903
904        let mask = ProjectionMask::all();
905        assert_eq!(None, mask.without_nested_types(&schema));
906
907        // key --> empty (it is nested)
908        let mask = ProjectionMask::leaves(&schema, [0]);
909        assert_eq!(None, mask.without_nested_types(&schema));
910
911        // value --> empty (it is nested)
912        let mask = ProjectionMask::leaves(&schema, [1]);
913        assert_eq!(None, mask.without_nested_types(&schema));
914    }
915
916    #[test]
917    fn test_projection_mask_without_nested_map_with_non_nested() {
918        // Example from https://github.com/apache/parquet-format/blob/master/LogicalTypes.md
919        // with an additional non-nested field
920        let schema = parse_schema(
921            "
922            message test_schema {
923                REQUIRED INT32 a;
924                required group my_map (MAP) {
925                    repeated group key_value {
926                        required binary key (STRING);
927                        optional int32 value;
928                    }
929                }
930                REQUIRED INT32 b;
931            }
932            ",
933        );
934
935        // all leaves --> a, b which are the only non nested ones
936        let mask = ProjectionMask::all();
937        assert_eq!(
938            Some(ProjectionMask::leaves(&schema, [0, 3])),
939            mask.without_nested_types(&schema)
940        );
941
942        // key, value, b --> b (the only non-nested one)
943        let mask = ProjectionMask::leaves(&schema, [1, 2, 3]);
944        assert_eq!(
945            Some(ProjectionMask::leaves(&schema, [3])),
946            mask.without_nested_types(&schema)
947        );
948
949        // key, value --> NONE
950        let mask = ProjectionMask::leaves(&schema, [1, 2]);
951        assert_eq!(None, mask.without_nested_types(&schema));
952    }
953
954    #[test]
955    fn test_projection_mask_without_nested_deeply_nested() {
956        // Map of Maps
957        let schema = parse_schema(
958            "
959            message test_schema {
960                OPTIONAL group a (MAP) {
961                    REPEATED group key_value {
962                        REQUIRED BYTE_ARRAY key (UTF8);
963                        OPTIONAL group value (MAP) {
964                            REPEATED group key_value {
965                                REQUIRED INT32 key;
966                                REQUIRED BOOLEAN value;
967                            }
968                        }
969                    }
970                }
971                REQUIRED INT32 b;
972                REQUIRED DOUBLE c;
973            ",
974        );
975
976        let mask = ProjectionMask::all();
977        assert_eq!(
978            Some(ProjectionMask::leaves(&schema, [3, 4])),
979            mask.without_nested_types(&schema)
980        );
981
982        // (first) key, c --> c (the only non-nested one)
983        let mask = ProjectionMask::leaves(&schema, [0, 4]);
984        assert_eq!(
985            Some(ProjectionMask::leaves(&schema, [4])),
986            mask.without_nested_types(&schema)
987        );
988
989        // (second) key, value, b --> b (the only non-nested one)
990        let mask = ProjectionMask::leaves(&schema, [1, 2, 3]);
991        assert_eq!(
992            Some(ProjectionMask::leaves(&schema, [3])),
993            mask.without_nested_types(&schema)
994        );
995
996        // key --> NONE (the only non-nested one)
997        let mask = ProjectionMask::leaves(&schema, [0]);
998        assert_eq!(None, mask.without_nested_types(&schema));
999    }
1000
1001    #[test]
1002    fn test_projection_mask_without_nested_list() {
1003        // Example from https://github.com/apache/parquet-format/blob/master/LogicalTypes.md#lists
1004        let schema = parse_schema(
1005            "
1006            message test_schema {
1007                required group my_list (LIST) {
1008                    repeated group list {
1009                        optional binary element (STRING);
1010                    }
1011                }
1012                REQUIRED INT32 b;
1013            }
1014            ",
1015        );
1016
1017        let mask = ProjectionMask::all();
1018        assert_eq!(
1019            Some(ProjectionMask::leaves(&schema, [1])),
1020            mask.without_nested_types(&schema),
1021        );
1022
1023        // element --> empty (it is nested)
1024        let mask = ProjectionMask::leaves(&schema, [0]);
1025        assert_eq!(None, mask.without_nested_types(&schema));
1026
1027        // element, b --> b (it is nested)
1028        let mask = ProjectionMask::leaves(&schema, [0, 1]);
1029        assert_eq!(
1030            Some(ProjectionMask::leaves(&schema, [1])),
1031            mask.without_nested_types(&schema),
1032        );
1033    }
1034
1035    #[test]
1036    fn test_projection_mask_without_nested_single_leaf_struct() {
1037        // Regression: a single-leaf struct is still nested.
1038        let schema = parse_schema(
1039            "
1040            message test_schema {
1041                OPTIONAL group address {
1042                    REQUIRED BYTE_ARRAY street (UTF8);
1043                }
1044                REQUIRED INT32 id;
1045            }
1046            ",
1047        );
1048
1049        // street -> empty; root is a struct
1050        let mask = ProjectionMask::leaves(&schema, [0]);
1051        assert_eq!(None, mask.without_nested_types(&schema));
1052
1053        // street, id --> id only
1054        let mask = ProjectionMask::leaves(&schema, [0, 1]);
1055        assert_eq!(
1056            Some(ProjectionMask::leaves(&schema, [1])),
1057            mask.without_nested_types(&schema)
1058        );
1059
1060        // all --> id only
1061        let mask = ProjectionMask::all();
1062        assert_eq!(
1063            Some(ProjectionMask::leaves(&schema, [1])),
1064            mask.without_nested_types(&schema)
1065        );
1066    }
1067
1068    /// Converts a schema string into a `SchemaDescriptor`
1069    fn parse_schema(schema: &str) -> SchemaDescriptor {
1070        let parquet_group_type = parse_message_type(schema).unwrap();
1071        SchemaDescriptor::new(Arc::new(parquet_group_type))
1072    }
1073}