Skip to main content

parquet/file/
writer.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//! [`SerializedFileWriter`]: Low level Parquet writer API
19
20use crate::bloom_filter::Sbbf;
21use crate::file::metadata::thrift::PageHeader;
22use crate::file::page_index::column_index::ColumnIndexMetaData;
23use crate::file::page_index::offset_index::OffsetIndexMetaData;
24use crate::parquet_thrift::{ThriftCompactOutputProtocol, WriteThrift};
25#[cfg(feature = "arrow")]
26use bytes::Bytes;
27use std::fmt::Debug;
28use std::io::{BufWriter, IoSlice, Read};
29use std::{io::Write, sync::Arc};
30
31use crate::column::page_encryption::PageEncryptor;
32use crate::column::writer::{ColumnCloseResult, ColumnWriterImpl, get_typed_column_writer_mut};
33use crate::column::{
34    page::{CompressedPage, PageWriteSpec, PageWriter},
35    writer::{ColumnWriter, get_column_writer},
36};
37use crate::data_type::DataType;
38#[cfg(feature = "encryption")]
39use crate::encryption::encrypt::{
40    FileEncryptionProperties, FileEncryptor, get_column_crypto_metadata,
41};
42use crate::errors::{ParquetError, Result};
43#[cfg(feature = "encryption")]
44use crate::file::PARQUET_MAGIC_ENCR_FOOTER;
45use crate::file::properties::{BloomFilterPosition, WriterPropertiesPtr};
46use crate::file::reader::ChunkReader;
47use crate::file::{PARQUET_MAGIC, metadata::*};
48use crate::schema::types::{ColumnDescPtr, SchemaDescPtr, SchemaDescriptor, TypePtr};
49
50/// A wrapper around a [`Write`] that keeps track of the number
51/// of bytes that have been written. The given [`Write`] is wrapped
52/// with a [`BufWriter`] to optimize writing performance.
53pub struct TrackedWrite<W: Write> {
54    inner: BufWriter<W>,
55    bytes_written: usize,
56}
57
58impl<W: Write> TrackedWrite<W> {
59    /// Create a new [`TrackedWrite`] from a [`Write`]
60    pub fn new(inner: W) -> Self {
61        let buf_write = BufWriter::new(inner);
62        Self {
63            inner: buf_write,
64            bytes_written: 0,
65        }
66    }
67
68    /// Returns the number of bytes written to this instance
69    pub fn bytes_written(&self) -> usize {
70        self.bytes_written
71    }
72
73    /// Returns a reference to the underlying writer.
74    pub fn inner(&self) -> &W {
75        self.inner.get_ref()
76    }
77
78    /// Returns a mutable reference to the underlying writer.
79    ///
80    /// It is inadvisable to directly write to the underlying writer, doing so
81    /// will likely result in data corruption
82    pub fn inner_mut(&mut self) -> &mut W {
83        self.inner.get_mut()
84    }
85
86    /// Returns the underlying writer.
87    pub fn into_inner(self) -> Result<W> {
88        self.inner.into_inner().map_err(|err| {
89            ParquetError::General(format!("fail to get inner writer: {:?}", err.to_string()))
90        })
91    }
92}
93
94impl<W: Write> Write for TrackedWrite<W> {
95    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
96        let bytes = self.inner.write(buf)?;
97        self.bytes_written += bytes;
98        Ok(bytes)
99    }
100
101    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> std::io::Result<usize> {
102        let bytes = self.inner.write_vectored(bufs)?;
103        self.bytes_written += bytes;
104        Ok(bytes)
105    }
106
107    fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
108        self.inner.write_all(buf)?;
109        self.bytes_written += buf.len();
110
111        Ok(())
112    }
113
114    fn flush(&mut self) -> std::io::Result<()> {
115        self.inner.flush()
116    }
117}
118
119/// Callback invoked on closing a column chunk
120pub type OnCloseColumnChunk<'a> = Box<dyn FnOnce(ColumnCloseResult) -> Result<()> + 'a>;
121
122/// Callback invoked on closing a row group, arguments are:
123///
124/// - the row group metadata
125/// - the column index for each column chunk
126/// - the offset index for each column chunk
127pub type OnCloseRowGroup<'a, W> = Box<
128    dyn FnOnce(
129            &'a mut TrackedWrite<W>,
130            RowGroupMetaData,
131            Vec<Option<Sbbf>>,
132            Vec<Option<ColumnIndexMetaData>>,
133            Vec<Option<OffsetIndexMetaData>>,
134        ) -> Result<()>
135        + 'a
136        + Send,
137>;
138
139// ----------------------------------------------------------------------
140// Serialized impl for file & row group writers
141
142/// Parquet file writer API.
143///
144/// This is a low level API for writing Parquet files directly, and handles
145/// tracking the location of file structures such as row groups and column
146/// chunks, and writing the metadata and file footer.
147///
148/// Data is written to row groups using  [`SerializedRowGroupWriter`] and
149/// columns using [`SerializedColumnWriter`]. The `SerializedFileWriter` tracks
150/// where all the data is written, and assembles the final file metadata.
151///
152/// The main workflow should be as following:
153/// - Create file writer, this will open a new file and potentially write some metadata.
154/// - Request a new row group writer by calling `next_row_group`.
155/// - Once finished writing row group, close row group writer by calling `close`
156/// - Write subsequent row groups, if necessary.
157/// - After all row groups have been written, close the file writer using `close` method.
158pub struct SerializedFileWriter<W: Write> {
159    buf: TrackedWrite<W>,
160    descr: SchemaDescPtr,
161    props: WriterPropertiesPtr,
162    row_groups: Vec<RowGroupMetaData>,
163    bloom_filters: Vec<Vec<Option<Sbbf>>>,
164    column_indexes: Vec<Vec<Option<ColumnIndexMetaData>>>,
165    offset_indexes: Vec<Vec<Option<OffsetIndexMetaData>>>,
166    row_group_index: usize,
167    // kv_metadatas will be appended to `props` when `write_metadata`
168    kv_metadatas: Vec<KeyValue>,
169    finished: bool,
170    #[cfg(feature = "encryption")]
171    file_encryptor: Option<Arc<FileEncryptor>>,
172}
173
174impl<W: Write> Debug for SerializedFileWriter<W> {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        // implement Debug so this can be used with #[derive(Debug)]
177        // in client code rather than actually listing all the fields
178        f.debug_struct("SerializedFileWriter")
179            .field("descr", &self.descr)
180            .field("row_group_index", &self.row_group_index)
181            .field("kv_metadatas", &self.kv_metadatas)
182            .finish_non_exhaustive()
183    }
184}
185
186impl<W: Write + Send> SerializedFileWriter<W> {
187    /// Creates new file writer.
188    pub fn new(buf: W, schema: TypePtr, properties: WriterPropertiesPtr) -> Result<Self> {
189        let mut buf = TrackedWrite::new(buf);
190
191        let schema_descriptor = SchemaDescriptor::new(schema.clone());
192
193        #[cfg(feature = "encryption")]
194        let file_encryptor = Self::get_file_encryptor(&properties, &schema_descriptor)?;
195
196        Self::start_file(&properties, &mut buf)?;
197        Ok(Self {
198            buf,
199            descr: Arc::new(schema_descriptor),
200            props: properties,
201            row_groups: vec![],
202            bloom_filters: vec![],
203            column_indexes: Vec::new(),
204            offset_indexes: Vec::new(),
205            row_group_index: 0,
206            kv_metadatas: Vec::new(),
207            finished: false,
208            #[cfg(feature = "encryption")]
209            file_encryptor,
210        })
211    }
212
213    #[cfg(feature = "encryption")]
214    fn get_file_encryptor(
215        properties: &WriterPropertiesPtr,
216        schema_descriptor: &SchemaDescriptor,
217    ) -> Result<Option<Arc<FileEncryptor>>> {
218        if let Some(file_encryption_properties) = properties.file_encryption_properties() {
219            file_encryption_properties.validate_encrypted_column_names(schema_descriptor)?;
220
221            Ok(Some(Arc::new(FileEncryptor::new(Arc::clone(
222                file_encryption_properties,
223            ))?)))
224        } else {
225            Ok(None)
226        }
227    }
228
229    /// Creates new row group from this file writer.
230    ///
231    /// Note: Parquet files are limited to at most 2^31 row groups in a file. If encryption is
232    /// enabled, this is reduced to 2^15, and row groups must be written sequentially.
233    ///
234    /// Every time the next row group is requested, the previous row group must
235    /// be finalised and closed using the [`SerializedRowGroupWriter::close`]
236    /// method or an error will be returned.
237    pub fn next_row_group(&mut self) -> Result<SerializedRowGroupWriter<'_, W>> {
238        self.assert_previous_writer_closed()?;
239        let ordinal = self.row_group_index;
240
241        // Thrift cannot encode lists with more than i32::MAX elements
242        let ordinal: i32 = ordinal.try_into().map_err(|_| {
243            ParquetError::General(format!(
244                "Parquet does not support more than {} row groups per file (currently: {})",
245                i32::MAX,
246                ordinal
247            ))
248        })?;
249
250        // If encryption is enabled, the max is 32767
251        #[cfg(feature = "encryption")]
252        if self.file_encryptor.is_some() && ordinal > i16::MAX as i32 {
253            return Err(ParquetError::General(format!(
254                "Parquet with encryption does not support more than {} row groups per file (currently: {})",
255                i16::MAX,
256                ordinal
257            )));
258        }
259
260        self.row_group_index = self
261            .row_group_index
262            .checked_add(1)
263            .ok_or_else(|| ParquetError::General("Row group index overflowed".to_string()))?;
264
265        let bloom_filter_position = self.properties().bloom_filter_position();
266        let row_groups = &mut self.row_groups;
267        let row_bloom_filters = &mut self.bloom_filters;
268        let row_column_indexes = &mut self.column_indexes;
269        let row_offset_indexes = &mut self.offset_indexes;
270        let on_close = move |buf,
271                             mut metadata,
272                             row_group_bloom_filter,
273                             row_group_column_index,
274                             row_group_offset_index| {
275            row_bloom_filters.push(row_group_bloom_filter);
276            row_column_indexes.push(row_group_column_index);
277            row_offset_indexes.push(row_group_offset_index);
278            // write bloom filters out immediately after the row group if requested
279            match bloom_filter_position {
280                BloomFilterPosition::AfterRowGroup => {
281                    write_bloom_filters(buf, row_bloom_filters, &mut metadata)?
282                }
283                BloomFilterPosition::End => (),
284            }
285            row_groups.push(metadata);
286            Ok(())
287        };
288
289        let row_group_writer = SerializedRowGroupWriter::new(
290            self.descr.clone(),
291            self.props.clone(),
292            &mut self.buf,
293            ordinal,
294            Some(Box::new(on_close)),
295        );
296        #[cfg(feature = "encryption")]
297        let row_group_writer = row_group_writer.with_file_encryptor(self.file_encryptor.clone());
298
299        Ok(row_group_writer)
300    }
301
302    /// Returns metadata for any flushed row groups
303    pub fn flushed_row_groups(&self) -> &[RowGroupMetaData] {
304        &self.row_groups
305    }
306
307    /// Close and finalize the underlying Parquet writer
308    ///
309    /// Unlike [`Self::close`] this does not consume self
310    ///
311    /// Attempting to write after calling finish will result in an error
312    pub fn finish(&mut self) -> Result<ParquetMetaData> {
313        self.assert_previous_writer_closed()?;
314        let metadata = self.write_metadata()?;
315        self.buf.flush()?;
316        Ok(metadata)
317    }
318
319    /// Closes and finalises file writer, returning the file metadata.
320    pub fn close(mut self) -> Result<ParquetMetaData> {
321        self.finish()
322    }
323
324    /// Writes magic bytes at the beginning of the file.
325    #[cfg(not(feature = "encryption"))]
326    fn start_file(_properties: &WriterPropertiesPtr, buf: &mut TrackedWrite<W>) -> Result<()> {
327        buf.write_all(get_file_magic())?;
328        Ok(())
329    }
330
331    /// Writes magic bytes at the beginning of the file.
332    #[cfg(feature = "encryption")]
333    fn start_file(properties: &WriterPropertiesPtr, buf: &mut TrackedWrite<W>) -> Result<()> {
334        let magic = get_file_magic(properties.file_encryption_properties.as_ref());
335
336        buf.write_all(magic)?;
337        Ok(())
338    }
339
340    /// Assembles and writes metadata at the end of the file. This will take ownership
341    /// of `row_groups` and the page index structures.
342    fn write_metadata(&mut self) -> Result<ParquetMetaData> {
343        self.finished = true;
344
345        // write out any remaining bloom filters after all row groups
346        for row_group in &mut self.row_groups {
347            write_bloom_filters(&mut self.buf, &mut self.bloom_filters, row_group)?;
348        }
349
350        let key_value_metadata = match self.props.key_value_metadata() {
351            Some(kv) => Some(kv.iter().chain(&self.kv_metadatas).cloned().collect()),
352            None if self.kv_metadatas.is_empty() => None,
353            None => Some(self.kv_metadatas.clone()),
354        };
355
356        // take ownership of metadata
357        let row_groups = std::mem::take(&mut self.row_groups);
358        let column_indexes = std::mem::take(&mut self.column_indexes);
359        let offset_indexes = std::mem::take(&mut self.offset_indexes);
360
361        let write_path_in_schema = self.props.write_path_in_schema();
362        let mut encoder = ThriftMetadataWriter::new(
363            &mut self.buf,
364            &self.descr,
365            row_groups,
366            Some(self.props.created_by().to_string()),
367            self.props.writer_version().as_num(),
368            write_path_in_schema,
369        );
370
371        #[cfg(feature = "encryption")]
372        {
373            encoder = encoder.with_file_encryptor(self.file_encryptor.clone());
374        }
375
376        if let Some(key_value_metadata) = key_value_metadata {
377            encoder = encoder.with_key_value_metadata(key_value_metadata)
378        }
379
380        encoder = encoder.with_column_indexes(column_indexes);
381        if !self.props.offset_index_disabled() {
382            encoder = encoder.with_offset_indexes(offset_indexes);
383        }
384        encoder.finish()
385    }
386
387    #[inline]
388    fn assert_previous_writer_closed(&self) -> Result<()> {
389        if self.finished {
390            return Err(general_err!("SerializedFileWriter already finished"));
391        }
392
393        if self.row_group_index != self.row_groups.len() {
394            Err(general_err!("Previous row group writer was not closed"))
395        } else {
396            Ok(())
397        }
398    }
399
400    /// Add a [`KeyValue`] to the file writer's metadata
401    pub fn append_key_value_metadata(&mut self, kv_metadata: KeyValue) {
402        self.kv_metadatas.push(kv_metadata);
403    }
404
405    /// Returns a reference to schema descriptor.
406    pub fn schema_descr(&self) -> &SchemaDescriptor {
407        &self.descr
408    }
409
410    /// Returns a reference to schema descriptor Arc.
411    #[cfg(feature = "arrow")]
412    pub(crate) fn schema_descr_ptr(&self) -> &SchemaDescPtr {
413        &self.descr
414    }
415
416    /// Returns a reference to the writer properties
417    pub fn properties(&self) -> &WriterPropertiesPtr {
418        &self.props
419    }
420
421    /// Returns a reference to the underlying writer.
422    pub fn inner(&self) -> &W {
423        self.buf.inner()
424    }
425
426    /// Writes the given buf bytes to the internal buffer.
427    ///
428    /// This can be used to write raw data to an in-progress Parquet file, for
429    /// example, custom index structures or other payloads. Other Parquet readers
430    /// will skip this data when reading the files.
431    ///
432    /// It's safe to use this method to write data to the underlying writer,
433    /// because it will ensure that the buffering and byte‐counting layers are used.
434    pub fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
435        self.buf.write_all(buf)
436    }
437
438    /// Flushes underlying writer
439    pub fn flush(&mut self) -> std::io::Result<()> {
440        self.buf.flush()
441    }
442
443    /// Returns a mutable reference to the underlying writer.
444    ///
445    /// **Warning**: if you write directly to this writer, you will skip
446    /// the `TrackedWrite` buffering and byte‐counting layers, which can cause
447    /// the file footer’s recorded offsets and sizes to diverge from reality,
448    /// resulting in an unreadable or corrupted Parquet file.
449    ///
450    /// If you want to write safely to the underlying writer, use [`Self::write_all`].
451    pub fn inner_mut(&mut self) -> &mut W {
452        self.buf.inner_mut()
453    }
454
455    /// Writes the file footer and returns the underlying writer.
456    pub fn into_inner(mut self) -> Result<W> {
457        self.assert_previous_writer_closed()?;
458        let _ = self.write_metadata()?;
459
460        self.buf.into_inner()
461    }
462
463    /// Returns the number of bytes written to this instance
464    pub fn bytes_written(&self) -> usize {
465        self.buf.bytes_written()
466    }
467
468    /// Get the file encryptor used by this instance to encrypt data
469    #[cfg(feature = "encryption")]
470    pub(crate) fn file_encryptor(&self) -> Option<Arc<FileEncryptor>> {
471        self.file_encryptor.clone()
472    }
473}
474
475/// Serialize all the bloom filters of the given row group to the given buffer,
476/// and returns the updated row group metadata.
477fn write_bloom_filters<W: Write + Send>(
478    buf: &mut TrackedWrite<W>,
479    bloom_filters: &mut [Vec<Option<Sbbf>>],
480    row_group: &mut RowGroupMetaData,
481) -> Result<()> {
482    // iter row group
483    // iter each column
484    // write bloom filter to the file
485
486    let row_group_idx: u32 = row_group
487        .ordinal()
488        .expect("Missing row group ordinal")
489        .try_into()
490        .map_err(|_| {
491            ParquetError::General(format!(
492                "Negative row group ordinal: {})",
493                row_group.ordinal().unwrap()
494            ))
495        })?;
496    let row_group_idx = row_group_idx as usize;
497    for (column_idx, column_chunk) in row_group.columns_mut().iter_mut().enumerate() {
498        if let Some(bloom_filter) = bloom_filters[row_group_idx][column_idx].take() {
499            let start_offset = buf.bytes_written();
500            bloom_filter.write(&mut *buf)?;
501            let end_offset = buf.bytes_written();
502            // set offset and index for bloom filter
503            *column_chunk = column_chunk
504                .clone()
505                .into_builder()
506                .set_bloom_filter_offset(Some(start_offset as i64))
507                .set_bloom_filter_length(Some((end_offset - start_offset) as i32))
508                .build()?;
509        }
510    }
511    Ok(())
512}
513
514/// Parquet row group writer API.
515///
516/// Provides methods to access column writers in an iterator-like fashion, order is
517/// guaranteed to match the order of schema leaves (column descriptors).
518///
519/// All columns should be written sequentially; the main workflow is:
520/// - Request the next column using `next_column` method - this will return `None` if no
521///   more columns are available to write.
522/// - Once done writing a column, close column writer with `close`
523/// - Once all columns have been written, close row group writer with `close`
524///   method. The close method will return row group metadata and is no-op
525///   on already closed row group.
526pub struct SerializedRowGroupWriter<'a, W: Write> {
527    descr: SchemaDescPtr,
528    props: WriterPropertiesPtr,
529    buf: &'a mut TrackedWrite<W>,
530    total_rows_written: Option<u64>,
531    total_bytes_written: u64,
532    total_uncompressed_bytes: i64,
533    column_index: usize,
534    row_group_metadata: Option<RowGroupMetaDataPtr>,
535    column_chunks: Vec<ColumnChunkMetaData>,
536    bloom_filters: Vec<Option<Sbbf>>,
537    column_indexes: Vec<Option<ColumnIndexMetaData>>,
538    offset_indexes: Vec<Option<OffsetIndexMetaData>>,
539    row_group_index: i32,
540    file_offset: i64,
541    on_close: Option<OnCloseRowGroup<'a, W>>,
542    #[cfg(feature = "encryption")]
543    file_encryptor: Option<Arc<FileEncryptor>>,
544}
545
546impl<'a, W: Write + Send> SerializedRowGroupWriter<'a, W> {
547    /// Creates a new `SerializedRowGroupWriter` with:
548    ///
549    /// - `schema_descr` - the schema to write
550    /// - `properties` - writer properties
551    /// - `buf` - the buffer to write data to
552    /// - `row_group_index` - row group index in this parquet file.
553    /// - `file_offset` - file offset of this row group in this parquet file.
554    /// - `on_close` - an optional callback that will invoked on [`Self::close`]
555    pub fn new(
556        schema_descr: SchemaDescPtr,
557        properties: WriterPropertiesPtr,
558        buf: &'a mut TrackedWrite<W>,
559        row_group_index: i32,
560        on_close: Option<OnCloseRowGroup<'a, W>>,
561    ) -> Self {
562        let num_columns = schema_descr.num_columns();
563        let file_offset = buf.bytes_written() as i64;
564        Self {
565            buf,
566            row_group_index,
567            file_offset,
568            on_close,
569            total_rows_written: None,
570            descr: schema_descr,
571            props: properties,
572            column_index: 0,
573            row_group_metadata: None,
574            column_chunks: Vec::with_capacity(num_columns),
575            bloom_filters: Vec::with_capacity(num_columns),
576            column_indexes: Vec::with_capacity(num_columns),
577            offset_indexes: Vec::with_capacity(num_columns),
578            total_bytes_written: 0,
579            total_uncompressed_bytes: 0,
580            #[cfg(feature = "encryption")]
581            file_encryptor: None,
582        }
583    }
584
585    #[cfg(feature = "encryption")]
586    /// Set the file encryptor to use for encrypting row group data and metadata
587    pub(crate) fn with_file_encryptor(
588        mut self,
589        file_encryptor: Option<Arc<FileEncryptor>>,
590    ) -> Self {
591        self.file_encryptor = file_encryptor;
592        self
593    }
594
595    /// Advance `self.column_index` returning the next [`ColumnDescPtr`] if any
596    fn next_column_desc(&mut self) -> Option<ColumnDescPtr> {
597        let ret = self.descr.columns().get(self.column_index)?.clone();
598        self.column_index += 1;
599        Some(ret)
600    }
601
602    /// Returns [`OnCloseColumnChunk`] for the next writer
603    fn get_on_close(&mut self) -> (&mut TrackedWrite<W>, OnCloseColumnChunk<'_>) {
604        let total_bytes_written = &mut self.total_bytes_written;
605        let total_uncompressed_bytes = &mut self.total_uncompressed_bytes;
606        let total_rows_written = &mut self.total_rows_written;
607        let column_chunks = &mut self.column_chunks;
608        let column_indexes = &mut self.column_indexes;
609        let offset_indexes = &mut self.offset_indexes;
610        let bloom_filters = &mut self.bloom_filters;
611
612        let on_close = |r: ColumnCloseResult| {
613            // Update row group writer metrics
614            *total_bytes_written += r.bytes_written;
615            *total_uncompressed_bytes += r.metadata.uncompressed_size();
616            column_chunks.push(r.metadata);
617            bloom_filters.push(r.bloom_filter);
618            column_indexes.push(r.column_index);
619            offset_indexes.push(r.offset_index);
620
621            if let Some(rows) = *total_rows_written {
622                if rows != r.rows_written {
623                    return Err(general_err!(
624                        "Incorrect number of rows, expected {} != {} rows",
625                        rows,
626                        r.rows_written
627                    ));
628                }
629            } else {
630                *total_rows_written = Some(r.rows_written);
631            }
632
633            Ok(())
634        };
635        (self.buf, Box::new(on_close))
636    }
637
638    /// Returns the next column writer, if available, using the factory function;
639    /// otherwise returns `None`.
640    pub(crate) fn next_column_with_factory<'b, F, C>(&'b mut self, factory: F) -> Result<Option<C>>
641    where
642        F: FnOnce(
643            ColumnDescPtr,
644            WriterPropertiesPtr,
645            Box<dyn PageWriter + 'b>,
646            OnCloseColumnChunk<'b>,
647        ) -> Result<C>,
648    {
649        self.assert_previous_writer_closed()?;
650
651        let encryptor_context = self.get_page_encryptor_context();
652
653        Ok(match self.next_column_desc() {
654            Some(column) => {
655                let props = self.props.clone();
656                let (buf, on_close) = self.get_on_close();
657
658                let page_writer = SerializedPageWriter::new(buf);
659                let page_writer =
660                    Self::set_page_writer_encryptor(&column, encryptor_context, page_writer)?;
661
662                Some(factory(
663                    column,
664                    props,
665                    Box::new(page_writer),
666                    Box::new(on_close),
667                )?)
668            }
669            None => None,
670        })
671    }
672
673    /// Returns the next column writer, if available; otherwise returns `None`.
674    /// In case of any IO error or Thrift error, or if row group writer has already been
675    /// closed returns `Err`.
676    pub fn next_column(&mut self) -> Result<Option<SerializedColumnWriter<'_>>> {
677        self.next_column_with_factory(|descr, props, page_writer, on_close| {
678            let column_writer = get_column_writer(descr, props, page_writer);
679            Ok(SerializedColumnWriter::new(column_writer, Some(on_close)))
680        })
681    }
682
683    /// Append an encoded column chunk from `reader` directly to the underlying
684    /// writer.
685    ///
686    /// This method can be used for efficiently concatenating or projecting
687    /// Parquet data, or encoding Parquet data to temporary in-memory buffers.
688    ///
689    /// Arguments:
690    /// - `reader`: a [`ChunkReader`] containing the encoded column data
691    /// - `close`: the [`ColumnCloseResult`] metadata returned from closing
692    ///   the column writer that wrote the data in `reader`.
693    ///
694    /// See Also:
695    /// 1. [`get_column_writer`]  for creating writers that can encode data.
696    /// 2. [`Self::next_column`] for writing data that isn't already encoded
697    pub fn append_column<R: ChunkReader>(
698        &mut self,
699        reader: &R,
700        close: ColumnCloseResult,
701    ) -> Result<()> {
702        // Position a reader at the start of the buffered chunk, then splice the
703        // bytes through the shared streaming path.
704        let metadata = &close.metadata;
705        let src_offset = metadata
706            .dictionary_page_offset()
707            .unwrap_or_else(|| metadata.data_page_offset());
708        let read = reader.get_read(src_offset as _)?;
709        self.append_column_from_read(read, close)
710    }
711
712    /// Splice an already-encoded column chunk into the row group, reading its
713    /// bytes sequentially from `read`.
714    ///
715    /// `read` must be positioned at the start of the chunk (the dictionary page
716    /// if present, otherwise the first data page — i.e. `src_offset` below) and
717    /// yield exactly the chunk's compressed bytes. Unlike [`Self::append_column`]
718    /// this consumes an owned [`Read`], which lets the caller stream the bytes
719    /// back from a [`PageStore`](crate::column::page_store::PageStore) one page
720    /// at a time without materializing the whole chunk in memory.
721    pub(crate) fn append_column_from_read<R: Read>(
722        &mut self,
723        read: R,
724        close: ColumnCloseResult,
725    ) -> Result<()> {
726        let (src_offset, src_length, write_offset) = self.begin_appended_column(&close)?;
727
728        let mut read = read.take(src_length as _);
729        let write_length = std::io::copy(&mut read, &mut self.buf)?;
730
731        if src_length as u64 != write_length {
732            return Err(general_err!(
733                "Failed to splice column data, expected {src_length} got {write_length}"
734            ));
735        }
736
737        self.finish_appended_column(close, src_offset, write_offset)
738    }
739
740    /// Splice an already-encoded column chunk into the row group from an
741    /// in-order sequence of byte buffers (typically its serialized pages).
742    ///
743    /// This is a lower-overhead alternative to [`Self::append_column`] /
744    /// [`Self::append_column_from_read`] for callers that already hold the
745    /// chunk as owned [`Bytes`]: each buffer is written straight to the output
746    /// with a single `write_all`, skipping the intermediate copy through
747    /// [`std::io::copy`]'s fixed-size buffer.
748    ///
749    /// `pages` must yield the chunk's compressed bytes in final file order
750    /// (the dictionary page, if any, first) and together total exactly the
751    /// compressed size recorded in `close`.
752    #[cfg(feature = "arrow")]
753    pub(crate) fn append_column_from_pages<I>(
754        &mut self,
755        pages: I,
756        close: ColumnCloseResult,
757    ) -> Result<()>
758    where
759        I: IntoIterator<Item = Result<Bytes>>,
760    {
761        let (src_offset, src_length, write_offset) = self.begin_appended_column(&close)?;
762
763        let mut write_length = 0u64;
764        for page in pages {
765            let page = page?;
766            self.buf.write_all(&page)?;
767            write_length += page.len() as u64;
768        }
769
770        if src_length as u64 != write_length {
771            return Err(general_err!(
772                "Failed to splice column data, expected {src_length} got {write_length}"
773            ));
774        }
775
776        self.finish_appended_column(close, src_offset, write_offset)
777    }
778
779    /// [`Self::append_column_from_read`] / [`Self::append_column_from_pages`]
780    /// preamble: validates the writer state and that `close` matches the next
781    /// expected column.
782    ///
783    /// Returns `(src_offset, src_length, write_offset)`: the chunk's start
784    /// offset and length in the source buffer, and the offset at which it will
785    /// land in the output file.
786    fn begin_appended_column(&mut self, close: &ColumnCloseResult) -> Result<(i64, i64, usize)> {
787        self.assert_previous_writer_closed()?;
788        let desc = self
789            .next_column_desc()
790            .ok_or_else(|| general_err!("exhausted columns in SerializedRowGroupWriter"))?;
791
792        let metadata = &close.metadata;
793
794        if metadata.column_descr() != desc.as_ref() {
795            return Err(general_err!(
796                "column descriptor mismatch, expected {:?} got {:?}",
797                desc,
798                metadata.column_descr()
799            ));
800        }
801
802        let src_offset = metadata
803            .dictionary_page_offset()
804            .unwrap_or_else(|| metadata.data_page_offset());
805        let src_length = metadata.compressed_size();
806        let write_offset = self.buf.bytes_written();
807        Ok((src_offset, src_length, write_offset))
808    }
809
810    /// [`Self::append_column_from_read`] / [`Self::append_column_from_pages`]
811    /// epilogue: rewrites the buffer-relative page offsets recorded in `close`
812    /// to their final positions in the output file and closes the column.
813    fn finish_appended_column(
814        &mut self,
815        mut close: ColumnCloseResult,
816        src_offset: i64,
817        write_offset: usize,
818    ) -> Result<()> {
819        let metadata = close.metadata;
820        let src_dictionary_offset = metadata.dictionary_page_offset();
821        let src_data_offset = metadata.data_page_offset();
822
823        let map_offset = |x| x - src_offset + write_offset as i64;
824        let mut builder = ColumnChunkMetaData::builder(metadata.column_descr_ptr())
825            .set_compression_codec(metadata.compression_codec())
826            .set_encodings_mask(*metadata.encodings_mask())
827            .set_total_compressed_size(metadata.compressed_size())
828            .set_total_uncompressed_size(metadata.uncompressed_size())
829            .set_num_values(metadata.num_values())
830            .set_data_page_offset(map_offset(src_data_offset))
831            .set_dictionary_page_offset(src_dictionary_offset.map(map_offset))
832            .set_unencoded_byte_array_data_bytes(metadata.unencoded_byte_array_data_bytes());
833
834        if let Some(rep_hist) = metadata.repetition_level_histogram() {
835            builder = builder.set_repetition_level_histogram(Some(rep_hist.clone()))
836        }
837        if let Some(def_hist) = metadata.definition_level_histogram() {
838            builder = builder.set_definition_level_histogram(Some(def_hist.clone()))
839        }
840        if let Some(statistics) = metadata.statistics() {
841            builder = builder.set_statistics(statistics.clone())
842        }
843        if let Some(geo_statistics) = metadata.geo_statistics() {
844            builder = builder.set_geo_statistics(Box::new(geo_statistics.clone()))
845        }
846        if let Some(page_encoding_stats) = metadata.page_encoding_stats() {
847            builder = builder.set_page_encoding_stats(page_encoding_stats.clone())
848        }
849        builder = self.set_column_crypto_metadata(builder, &metadata);
850        close.metadata = builder.build()?;
851
852        if let Some(offsets) = close.offset_index.as_mut() {
853            for location in &mut offsets.page_locations {
854                location.offset = map_offset(location.offset)
855            }
856        }
857
858        let (_, on_close) = self.get_on_close();
859        on_close(close)
860    }
861
862    /// Closes this row group writer and returns row group metadata.
863    pub fn close(mut self) -> Result<RowGroupMetaDataPtr> {
864        if self.row_group_metadata.is_none() {
865            self.assert_previous_writer_closed()?;
866
867            let column_chunks = std::mem::take(&mut self.column_chunks);
868            let row_group_metadata = RowGroupMetaData::builder(self.descr.clone())
869                .set_column_metadata(column_chunks)
870                .set_total_byte_size(self.total_uncompressed_bytes)
871                .set_num_rows(self.total_rows_written.unwrap_or(0) as i64)
872                .set_sorting_columns(self.props.sorting_columns().cloned())
873                .set_ordinal(self.row_group_index)
874                .set_file_offset(self.file_offset)
875                .build()?;
876
877            self.row_group_metadata = Some(Arc::new(row_group_metadata.clone()));
878
879            if let Some(on_close) = self.on_close.take() {
880                on_close(
881                    self.buf,
882                    row_group_metadata,
883                    self.bloom_filters,
884                    self.column_indexes,
885                    self.offset_indexes,
886                )?
887            }
888        }
889
890        let metadata = self.row_group_metadata.as_ref().unwrap().clone();
891        Ok(metadata)
892    }
893
894    /// Set the column crypto metadata for a column chunk
895    #[cfg(feature = "encryption")]
896    fn set_column_crypto_metadata(
897        &self,
898        builder: ColumnChunkMetaDataBuilder,
899        metadata: &ColumnChunkMetaData,
900    ) -> ColumnChunkMetaDataBuilder {
901        if let Some(file_encryptor) = self.file_encryptor.as_ref() {
902            builder.set_column_crypto_metadata(get_column_crypto_metadata(
903                file_encryptor.properties(),
904                &metadata.column_descr_ptr(),
905            ))
906        } else {
907            builder
908        }
909    }
910
911    /// Get context required to create a [`PageEncryptor`] for a column
912    #[cfg(feature = "encryption")]
913    fn get_page_encryptor_context(&self) -> PageEncryptorContext {
914        PageEncryptorContext {
915            file_encryptor: self.file_encryptor.clone(),
916            row_group_index: self.row_group_index as usize,
917            column_index: self.column_index,
918        }
919    }
920
921    /// Set the [`PageEncryptor`] on a page writer if a column is encrypted
922    #[cfg(feature = "encryption")]
923    fn set_page_writer_encryptor<'b>(
924        column: &ColumnDescPtr,
925        context: PageEncryptorContext,
926        page_writer: SerializedPageWriter<'b, W>,
927    ) -> Result<SerializedPageWriter<'b, W>> {
928        let page_encryptor = PageEncryptor::create_if_column_encrypted(
929            context.file_encryptor.as_ref(),
930            context.row_group_index,
931            context.column_index,
932            &column.path().string(),
933        )?;
934
935        Ok(page_writer.with_page_encryptor(page_encryptor))
936    }
937
938    /// No-op implementation of setting the column crypto metadata for a column chunk
939    #[cfg(not(feature = "encryption"))]
940    fn set_column_crypto_metadata(
941        &self,
942        builder: ColumnChunkMetaDataBuilder,
943        _metadata: &ColumnChunkMetaData,
944    ) -> ColumnChunkMetaDataBuilder {
945        builder
946    }
947
948    #[cfg(not(feature = "encryption"))]
949    fn get_page_encryptor_context(&self) -> PageEncryptorContext {
950        PageEncryptorContext {}
951    }
952
953    /// No-op implementation of setting a [`PageEncryptor`] for when encryption is disabled
954    #[cfg(not(feature = "encryption"))]
955    fn set_page_writer_encryptor<'b>(
956        _column: &ColumnDescPtr,
957        _context: PageEncryptorContext,
958        page_writer: SerializedPageWriter<'b, W>,
959    ) -> Result<SerializedPageWriter<'b, W>> {
960        Ok(page_writer)
961    }
962
963    #[inline]
964    fn assert_previous_writer_closed(&self) -> Result<()> {
965        if self.column_index != self.column_chunks.len() {
966            Err(general_err!("Previous column writer was not closed"))
967        } else {
968            Ok(())
969        }
970    }
971}
972
973/// Context required to create a [`PageEncryptor`] for a column
974#[cfg(feature = "encryption")]
975struct PageEncryptorContext {
976    file_encryptor: Option<Arc<FileEncryptor>>,
977    row_group_index: usize,
978    column_index: usize,
979}
980
981#[cfg(not(feature = "encryption"))]
982struct PageEncryptorContext {}
983
984/// A wrapper around a [`ColumnWriter`] that invokes a callback on [`Self::close`]
985pub struct SerializedColumnWriter<'a> {
986    inner: ColumnWriter<'a>,
987    on_close: Option<OnCloseColumnChunk<'a>>,
988}
989
990impl<'a> SerializedColumnWriter<'a> {
991    /// Create a new [`SerializedColumnWriter`] from a [`ColumnWriter`] and an
992    /// optional callback to be invoked on [`Self::close`]
993    pub fn new(inner: ColumnWriter<'a>, on_close: Option<OnCloseColumnChunk<'a>>) -> Self {
994        Self { inner, on_close }
995    }
996
997    /// Returns a reference to an untyped [`ColumnWriter`]
998    pub fn untyped(&mut self) -> &mut ColumnWriter<'a> {
999        &mut self.inner
1000    }
1001
1002    /// Returns a reference to a typed [`ColumnWriterImpl`]
1003    pub fn typed<T: DataType>(&mut self) -> &mut ColumnWriterImpl<'a, T> {
1004        get_typed_column_writer_mut(&mut self.inner)
1005    }
1006
1007    /// Close this [`SerializedColumnWriter`]
1008    pub fn close(mut self) -> Result<()> {
1009        let r = self.inner.close()?;
1010        if let Some(on_close) = self.on_close.take() {
1011            on_close(r)?
1012        }
1013
1014        Ok(())
1015    }
1016}
1017
1018/// A serialized implementation for Parquet [`PageWriter`].
1019/// Writes and serializes pages and metadata into output stream.
1020///
1021/// `SerializedPageWriter` should not be used after calling `close()`.
1022pub struct SerializedPageWriter<'a, W: Write> {
1023    sink: &'a mut TrackedWrite<W>,
1024    #[cfg(feature = "encryption")]
1025    page_encryptor: Option<PageEncryptor>,
1026}
1027
1028impl<'a, W: Write> SerializedPageWriter<'a, W> {
1029    /// Creates new page writer.
1030    pub fn new(sink: &'a mut TrackedWrite<W>) -> Self {
1031        Self {
1032            sink,
1033            #[cfg(feature = "encryption")]
1034            page_encryptor: None,
1035        }
1036    }
1037
1038    /// Serializes page header into Thrift.
1039    /// Returns number of bytes that have been written into the sink.
1040    #[inline]
1041    fn serialize_page_header(&mut self, header: PageHeader) -> Result<usize> {
1042        let start_pos = self.sink.bytes_written();
1043        match self.page_encryptor_and_sink_mut() {
1044            Some((page_encryptor, sink)) => {
1045                page_encryptor.encrypt_page_header(&header, sink)?;
1046            }
1047            None => {
1048                let mut protocol = ThriftCompactOutputProtocol::new(&mut self.sink);
1049                header.write_thrift(&mut protocol)?;
1050            }
1051        }
1052        Ok(self.sink.bytes_written() - start_pos)
1053    }
1054}
1055
1056#[cfg(feature = "encryption")]
1057impl<W: Write> SerializedPageWriter<'_, W> {
1058    /// Set the encryptor to use to encrypt page data
1059    fn with_page_encryptor(mut self, page_encryptor: Option<PageEncryptor>) -> Self {
1060        self.page_encryptor = page_encryptor;
1061        self
1062    }
1063
1064    fn page_encryptor_mut(&mut self) -> Option<&mut PageEncryptor> {
1065        self.page_encryptor.as_mut()
1066    }
1067
1068    fn page_encryptor_and_sink_mut(
1069        &mut self,
1070    ) -> Option<(&mut PageEncryptor, &mut TrackedWrite<W>)> {
1071        self.page_encryptor.as_mut().map(|pe| (pe, &mut *self.sink))
1072    }
1073}
1074
1075// These mirror the signatures of the encryption-enabled versions above, so that the
1076// callers do not need a `cfg` of their own.
1077#[cfg(not(feature = "encryption"))]
1078#[expect(
1079    clippy::needless_pass_by_ref_mut,
1080    reason = "mirrors the encryption-enabled signatures"
1081)]
1082impl<W: Write> SerializedPageWriter<'_, W> {
1083    fn page_encryptor_mut(&mut self) -> Option<&mut PageEncryptor> {
1084        None
1085    }
1086
1087    fn page_encryptor_and_sink_mut(
1088        &mut self,
1089    ) -> Option<(&mut PageEncryptor, &mut TrackedWrite<W>)> {
1090        None
1091    }
1092}
1093
1094impl<W: Write + Send> PageWriter for SerializedPageWriter<'_, W> {
1095    fn write_page(&mut self, page: CompressedPage) -> Result<PageWriteSpec> {
1096        let page = match self.page_encryptor_mut() {
1097            Some(page_encryptor) => page_encryptor.encrypt_compressed_page(page)?,
1098            None => page,
1099        };
1100
1101        let page_type = page.page_type();
1102        let start_pos = self.sink.bytes_written() as u64;
1103
1104        let page_header = page.to_thrift_header()?;
1105        let header_size = self.serialize_page_header(page_header)?;
1106
1107        self.sink.write_all(page.data())?;
1108
1109        let mut spec = PageWriteSpec::new();
1110        spec.page_type = page_type;
1111        spec.uncompressed_size = page.uncompressed_size() + header_size;
1112        spec.compressed_size = page.compressed_size() + header_size;
1113        spec.offset = start_pos;
1114        spec.bytes_written = self.sink.bytes_written() as u64 - start_pos;
1115        spec.num_values = page.num_values();
1116
1117        if let Some(page_encryptor) = self.page_encryptor_mut()
1118            && page.compressed_page().is_data_page()
1119        {
1120            page_encryptor.increment_page();
1121        }
1122        Ok(spec)
1123    }
1124
1125    fn close(&mut self) -> Result<()> {
1126        self.sink.flush()?;
1127        Ok(())
1128    }
1129}
1130
1131/// Get the magic bytes at the start and end of the file that identify this
1132/// as a Parquet file.
1133#[cfg(feature = "encryption")]
1134pub(crate) fn get_file_magic(
1135    file_encryption_properties: Option<&Arc<FileEncryptionProperties>>,
1136) -> &'static [u8; 4] {
1137    match file_encryption_properties.as_ref() {
1138        Some(encryption_properties) if encryption_properties.encrypt_footer() => {
1139            &PARQUET_MAGIC_ENCR_FOOTER
1140        }
1141        _ => &PARQUET_MAGIC,
1142    }
1143}
1144
1145#[cfg(not(feature = "encryption"))]
1146pub(crate) fn get_file_magic() -> &'static [u8; 4] {
1147    &PARQUET_MAGIC
1148}
1149
1150#[cfg(test)]
1151mod tests {
1152    use super::*;
1153
1154    #[cfg(feature = "arrow")]
1155    use arrow_array::RecordBatchReader;
1156    use bytes::Bytes;
1157    use std::fs::File;
1158
1159    #[cfg(feature = "arrow")]
1160    use crate::arrow::ArrowWriter;
1161    #[cfg(feature = "arrow")]
1162    use crate::arrow::arrow_reader::ParquetRecordBatchReaderBuilder;
1163    use crate::basic::{
1164        ColumnOrder, Compression, ConvertedType, Encoding, LogicalType, Repetition, SortOrder, Type,
1165    };
1166    use crate::column::page::{Page, PageReader};
1167    use crate::column::reader::get_typed_column_reader;
1168    use crate::compression::{Codec, CodecOptionsBuilder, create_codec};
1169    use crate::data_type::{BoolType, ByteArrayType, Int32Type, Int96, Int96Type};
1170    use crate::file::page_index::column_index::ColumnIndexMetaData;
1171    use crate::file::properties::EnabledStatistics;
1172    use crate::file::serialized_reader::ReadOptionsBuilder;
1173    use crate::file::statistics::{from_thrift_page_stats, page_stats_to_thrift};
1174    use crate::file::{
1175        properties::{ReaderProperties, WriterProperties, WriterVersion},
1176        reader::{FileReader, SerializedFileReader, SerializedPageReader},
1177        statistics::Statistics,
1178    };
1179    use crate::record::{Row, RowAccessor};
1180    use crate::schema::parser::parse_message_type;
1181    use crate::schema::types;
1182    use crate::schema::types::{ColumnDescriptor, ColumnPath};
1183    use crate::util::test_common::file_util::get_test_file;
1184    use crate::util::test_common::rand_gen::RandGen;
1185
1186    #[test]
1187    fn test_row_group_writer_error_not_all_columns_written() {
1188        let file = tempfile::tempfile().unwrap();
1189        let schema = Arc::new(
1190            types::Type::group_type_builder("schema")
1191                .with_fields(vec![Arc::new(
1192                    types::Type::primitive_type_builder("col1", Type::INT32)
1193                        .build()
1194                        .unwrap(),
1195                )])
1196                .build()
1197                .unwrap(),
1198        );
1199        let props = Default::default();
1200        let mut writer = SerializedFileWriter::new(file, schema, props).unwrap();
1201        let row_group_writer = writer.next_row_group().unwrap();
1202        let res = row_group_writer.close();
1203        assert!(res.is_err());
1204        if let Err(err) = res {
1205            assert_eq!(
1206                format!("{err}"),
1207                "Parquet error: Column length mismatch: 1 != 0"
1208            );
1209        }
1210    }
1211
1212    #[test]
1213    fn test_row_group_writer_num_records_mismatch() {
1214        let file = tempfile::tempfile().unwrap();
1215        let schema = Arc::new(
1216            types::Type::group_type_builder("schema")
1217                .with_fields(vec![
1218                    Arc::new(
1219                        types::Type::primitive_type_builder("col1", Type::INT32)
1220                            .with_repetition(Repetition::REQUIRED)
1221                            .build()
1222                            .unwrap(),
1223                    ),
1224                    Arc::new(
1225                        types::Type::primitive_type_builder("col2", Type::INT32)
1226                            .with_repetition(Repetition::REQUIRED)
1227                            .build()
1228                            .unwrap(),
1229                    ),
1230                ])
1231                .build()
1232                .unwrap(),
1233        );
1234        let props = Default::default();
1235        let mut writer = SerializedFileWriter::new(file, schema, props).unwrap();
1236        let mut row_group_writer = writer.next_row_group().unwrap();
1237
1238        let mut col_writer = row_group_writer.next_column().unwrap().unwrap();
1239        col_writer
1240            .typed::<Int32Type>()
1241            .write_batch(&[1, 2, 3], None, None)
1242            .unwrap();
1243        col_writer.close().unwrap();
1244
1245        let mut col_writer = row_group_writer.next_column().unwrap().unwrap();
1246        col_writer
1247            .typed::<Int32Type>()
1248            .write_batch(&[1, 2], None, None)
1249            .unwrap();
1250
1251        let err = col_writer.close().unwrap_err();
1252        assert_eq!(
1253            err.to_string(),
1254            "Parquet error: Incorrect number of rows, expected 3 != 2 rows"
1255        );
1256    }
1257
1258    #[test]
1259    fn test_file_writer_empty_file() {
1260        let file = tempfile::tempfile().unwrap();
1261
1262        let schema = Arc::new(
1263            types::Type::group_type_builder("schema")
1264                .with_fields(vec![Arc::new(
1265                    types::Type::primitive_type_builder("col1", Type::INT32)
1266                        .build()
1267                        .unwrap(),
1268                )])
1269                .build()
1270                .unwrap(),
1271        );
1272        let props = Default::default();
1273        let writer = SerializedFileWriter::new(file.try_clone().unwrap(), schema, props).unwrap();
1274        writer.close().unwrap();
1275
1276        let reader = SerializedFileReader::new(file).unwrap();
1277        assert_eq!(reader.get_row_iter(None).unwrap().count(), 0);
1278    }
1279
1280    #[test]
1281    fn test_file_writer_column_orders_populated() {
1282        let file = tempfile::tempfile().unwrap();
1283
1284        let schema = Arc::new(
1285            types::Type::group_type_builder("schema")
1286                .with_fields(vec![
1287                    Arc::new(
1288                        types::Type::primitive_type_builder("col1", Type::INT32)
1289                            .build()
1290                            .unwrap(),
1291                    ),
1292                    Arc::new(
1293                        types::Type::primitive_type_builder("col2", Type::FIXED_LEN_BYTE_ARRAY)
1294                            .with_converted_type(ConvertedType::INTERVAL)
1295                            .with_length(12)
1296                            .build()
1297                            .unwrap(),
1298                    ),
1299                    Arc::new(
1300                        types::Type::group_type_builder("nested")
1301                            .with_repetition(Repetition::REQUIRED)
1302                            .with_fields(vec![
1303                                Arc::new(
1304                                    types::Type::primitive_type_builder(
1305                                        "col3",
1306                                        Type::FIXED_LEN_BYTE_ARRAY,
1307                                    )
1308                                    .with_logical_type(Some(LogicalType::Float16))
1309                                    .with_length(2)
1310                                    .build()
1311                                    .unwrap(),
1312                                ),
1313                                Arc::new(
1314                                    types::Type::primitive_type_builder("col4", Type::BYTE_ARRAY)
1315                                        .with_logical_type(Some(LogicalType::String))
1316                                        .build()
1317                                        .unwrap(),
1318                                ),
1319                            ])
1320                            .build()
1321                            .unwrap(),
1322                    ),
1323                    Arc::new(
1324                        types::Type::primitive_type_builder("col5", Type::FLOAT)
1325                            .build()
1326                            .unwrap(),
1327                    ),
1328                    Arc::new(
1329                        types::Type::primitive_type_builder("col6", Type::DOUBLE)
1330                            .build()
1331                            .unwrap(),
1332                    ),
1333                ])
1334                .build()
1335                .unwrap(),
1336        );
1337
1338        let props = Default::default();
1339        let writer = SerializedFileWriter::new(file.try_clone().unwrap(), schema, props).unwrap();
1340        writer.close().unwrap();
1341
1342        let reader = SerializedFileReader::new(file).unwrap();
1343
1344        // only leaves
1345        let expected = vec![
1346            // INT32
1347            ColumnOrder::TYPE_DEFINED_ORDER(SortOrder::SIGNED),
1348            // INTERVAL
1349            ColumnOrder::TYPE_DEFINED_ORDER(SortOrder::UNDEFINED),
1350            // Float16
1351            ColumnOrder::IEEE_754_TOTAL_ORDER,
1352            // String
1353            ColumnOrder::TYPE_DEFINED_ORDER(SortOrder::UNSIGNED),
1354            // FLOAT
1355            ColumnOrder::IEEE_754_TOTAL_ORDER,
1356            // DOUBLE
1357            ColumnOrder::IEEE_754_TOTAL_ORDER,
1358        ];
1359        let actual = reader.metadata().file_metadata().column_orders();
1360
1361        assert!(actual.is_some());
1362        let actual = actual.unwrap();
1363        assert_eq!(*actual, expected);
1364    }
1365
1366    #[test]
1367    fn test_file_writer_with_metadata() {
1368        let file = tempfile::tempfile().unwrap();
1369
1370        let schema = Arc::new(
1371            types::Type::group_type_builder("schema")
1372                .with_fields(vec![Arc::new(
1373                    types::Type::primitive_type_builder("col1", Type::INT32)
1374                        .build()
1375                        .unwrap(),
1376                )])
1377                .build()
1378                .unwrap(),
1379        );
1380        let props = Arc::new(
1381            WriterProperties::builder()
1382                .set_key_value_metadata(Some(vec![KeyValue::new(
1383                    "key".to_string(),
1384                    "value".to_string(),
1385                )]))
1386                .build(),
1387        );
1388        let writer = SerializedFileWriter::new(file.try_clone().unwrap(), schema, props).unwrap();
1389        writer.close().unwrap();
1390
1391        let reader = SerializedFileReader::new(file).unwrap();
1392        assert_eq!(
1393            reader
1394                .metadata()
1395                .file_metadata()
1396                .key_value_metadata()
1397                .unwrap()
1398                .len(),
1399            1
1400        );
1401    }
1402
1403    #[test]
1404    fn test_file_writer_v2_with_metadata() {
1405        let file = tempfile::tempfile().unwrap();
1406        let field_logical_type = Some(LogicalType::integer(8, false));
1407        let field = Arc::new(
1408            types::Type::primitive_type_builder("col1", Type::INT32)
1409                .with_logical_type(field_logical_type.clone())
1410                .with_converted_type(field_logical_type.into())
1411                .build()
1412                .unwrap(),
1413        );
1414        let schema = Arc::new(
1415            types::Type::group_type_builder("schema")
1416                .with_fields(vec![field.clone()])
1417                .build()
1418                .unwrap(),
1419        );
1420        let props = Arc::new(
1421            WriterProperties::builder()
1422                .set_key_value_metadata(Some(vec![KeyValue::new(
1423                    "key".to_string(),
1424                    "value".to_string(),
1425                )]))
1426                .set_writer_version(WriterVersion::PARQUET_2_0)
1427                .build(),
1428        );
1429        let writer = SerializedFileWriter::new(file.try_clone().unwrap(), schema, props).unwrap();
1430        writer.close().unwrap();
1431
1432        let reader = SerializedFileReader::new(file).unwrap();
1433
1434        assert_eq!(
1435            reader
1436                .metadata()
1437                .file_metadata()
1438                .key_value_metadata()
1439                .unwrap()
1440                .len(),
1441            1
1442        );
1443
1444        // ARROW-11803: Test that the converted and logical types have been populated
1445        let fields = reader.metadata().file_metadata().schema().get_fields();
1446        assert_eq!(fields.len(), 1);
1447        assert_eq!(fields[0], field);
1448    }
1449
1450    #[test]
1451    fn test_file_writer_with_sorting_columns_metadata() {
1452        let file = tempfile::tempfile().unwrap();
1453
1454        let schema = Arc::new(
1455            types::Type::group_type_builder("schema")
1456                .with_fields(vec![
1457                    Arc::new(
1458                        types::Type::primitive_type_builder("col1", Type::INT32)
1459                            .build()
1460                            .unwrap(),
1461                    ),
1462                    Arc::new(
1463                        types::Type::primitive_type_builder("col2", Type::INT32)
1464                            .build()
1465                            .unwrap(),
1466                    ),
1467                ])
1468                .build()
1469                .unwrap(),
1470        );
1471        let expected_result = Some(vec![SortingColumn {
1472            column_idx: 0,
1473            descending: false,
1474            nulls_first: true,
1475        }]);
1476        let props = Arc::new(
1477            WriterProperties::builder()
1478                .set_key_value_metadata(Some(vec![KeyValue::new(
1479                    "key".to_string(),
1480                    "value".to_string(),
1481                )]))
1482                .set_sorting_columns(expected_result.clone())
1483                .build(),
1484        );
1485        let mut writer =
1486            SerializedFileWriter::new(file.try_clone().unwrap(), schema, props).unwrap();
1487        let mut row_group_writer = writer.next_row_group().expect("get row group writer");
1488
1489        let col_writer = row_group_writer.next_column().unwrap().unwrap();
1490        col_writer.close().unwrap();
1491
1492        let col_writer = row_group_writer.next_column().unwrap().unwrap();
1493        col_writer.close().unwrap();
1494
1495        row_group_writer.close().unwrap();
1496        writer.close().unwrap();
1497
1498        let reader = SerializedFileReader::new(file).unwrap();
1499        let result: Vec<Option<&Vec<SortingColumn>>> = reader
1500            .metadata()
1501            .row_groups()
1502            .iter()
1503            .map(|f| f.sorting_columns())
1504            .collect();
1505        // validate the sorting column read match the one written above
1506        assert_eq!(expected_result.as_ref(), result[0]);
1507    }
1508
1509    #[test]
1510    fn test_file_writer_empty_row_groups() {
1511        let file = tempfile::tempfile().unwrap();
1512        test_file_roundtrip(file, vec![]);
1513    }
1514
1515    #[test]
1516    fn test_file_writer_single_row_group() {
1517        let file = tempfile::tempfile().unwrap();
1518        test_file_roundtrip(file, vec![vec![1, 2, 3, 4, 5]]);
1519    }
1520
1521    #[test]
1522    fn test_file_writer_multiple_row_groups() {
1523        let file = tempfile::tempfile().unwrap();
1524        test_file_roundtrip(
1525            file,
1526            vec![
1527                vec![1, 2, 3, 4, 5],
1528                vec![1, 2, 3],
1529                vec![1],
1530                vec![1, 2, 3, 4, 5, 6],
1531            ],
1532        );
1533    }
1534
1535    #[test]
1536    fn test_file_writer_multiple_large_row_groups() {
1537        let file = tempfile::tempfile().unwrap();
1538        test_file_roundtrip(
1539            file,
1540            vec![vec![123; 1024], vec![124; 1000], vec![125; 15], vec![]],
1541        );
1542    }
1543
1544    #[test]
1545    fn test_page_writer_data_pages() {
1546        let pages = [
1547            Page::DataPage {
1548                buf: Bytes::from(vec![1, 2, 3, 4, 5, 6, 7, 8]),
1549                num_values: 10,
1550                encoding: Encoding::DELTA_BINARY_PACKED,
1551                def_level_encoding: Encoding::RLE,
1552                rep_level_encoding: Encoding::RLE,
1553                statistics: Some(Statistics::int32(Some(1), Some(3), None, Some(7), true)),
1554            },
1555            Page::DataPageV2 {
1556                buf: Bytes::from(vec![4; 128]),
1557                num_values: 10,
1558                encoding: Encoding::DELTA_BINARY_PACKED,
1559                num_nulls: 2,
1560                num_rows: 12,
1561                def_levels_byte_len: 24,
1562                rep_levels_byte_len: 32,
1563                is_compressed: false,
1564                statistics: Some(Statistics::int32(Some(1), Some(3), None, Some(7), true)),
1565            },
1566        ];
1567
1568        test_page_roundtrip(&pages[..], Compression::SNAPPY, Type::INT32);
1569        test_page_roundtrip(&pages[..], Compression::UNCOMPRESSED, Type::INT32);
1570    }
1571
1572    #[test]
1573    fn test_page_writer_dict_pages() {
1574        let pages = [
1575            Page::DictionaryPage {
1576                buf: Bytes::from(vec![1, 2, 3, 4, 5]),
1577                num_values: 5,
1578                encoding: Encoding::RLE_DICTIONARY,
1579                is_sorted: false,
1580            },
1581            Page::DataPage {
1582                buf: Bytes::from(vec![1, 2, 3, 4, 5, 6, 7, 8]),
1583                num_values: 10,
1584                encoding: Encoding::DELTA_BINARY_PACKED,
1585                def_level_encoding: Encoding::RLE,
1586                rep_level_encoding: Encoding::RLE,
1587                statistics: Some(Statistics::int32(Some(1), Some(3), None, Some(7), true)),
1588            },
1589            Page::DataPageV2 {
1590                buf: Bytes::from(vec![4; 128]),
1591                num_values: 10,
1592                encoding: Encoding::DELTA_BINARY_PACKED,
1593                num_nulls: 2,
1594                num_rows: 12,
1595                def_levels_byte_len: 24,
1596                rep_levels_byte_len: 32,
1597                is_compressed: false,
1598                statistics: None,
1599            },
1600        ];
1601
1602        test_page_roundtrip(&pages[..], Compression::SNAPPY, Type::INT32);
1603        test_page_roundtrip(&pages[..], Compression::UNCOMPRESSED, Type::INT32);
1604    }
1605
1606    /// Tests writing and reading pages.
1607    /// Physical type is for statistics only, should match any defined statistics type in
1608    /// pages.
1609    fn test_page_roundtrip(pages: &[Page], codec: Compression, physical_type: Type) {
1610        let mut compressed_pages = vec![];
1611        let mut total_num_values = 0i64;
1612        let codec_options = CodecOptionsBuilder::default()
1613            .set_backward_compatible_lz4(false)
1614            .build();
1615        let mut compressor = create_codec(codec, &codec_options).unwrap();
1616
1617        for page in pages {
1618            let uncompressed_len = page.buffer().len();
1619
1620            let compressed_page = match *page {
1621                Page::DataPage {
1622                    ref buf,
1623                    num_values,
1624                    encoding,
1625                    def_level_encoding,
1626                    rep_level_encoding,
1627                    ref statistics,
1628                } => {
1629                    total_num_values += num_values as i64;
1630                    let output_buf = compress_helper(compressor.as_mut(), buf);
1631
1632                    Page::DataPage {
1633                        buf: Bytes::from(output_buf),
1634                        num_values,
1635                        encoding,
1636                        def_level_encoding,
1637                        rep_level_encoding,
1638                        statistics: from_thrift_page_stats(
1639                            physical_type,
1640                            page_stats_to_thrift(statistics.as_ref()),
1641                        )
1642                        .unwrap(),
1643                    }
1644                }
1645                Page::DataPageV2 {
1646                    ref buf,
1647                    num_values,
1648                    encoding,
1649                    num_nulls,
1650                    num_rows,
1651                    def_levels_byte_len,
1652                    rep_levels_byte_len,
1653                    ref statistics,
1654                    ..
1655                } => {
1656                    total_num_values += num_values as i64;
1657                    let offset = (def_levels_byte_len + rep_levels_byte_len) as usize;
1658                    let cmp_buf = compress_helper(compressor.as_mut(), &buf[offset..]);
1659                    let mut output_buf = Vec::from(&buf[..offset]);
1660                    output_buf.extend_from_slice(&cmp_buf[..]);
1661
1662                    Page::DataPageV2 {
1663                        buf: Bytes::from(output_buf),
1664                        num_values,
1665                        encoding,
1666                        num_nulls,
1667                        num_rows,
1668                        def_levels_byte_len,
1669                        rep_levels_byte_len,
1670                        is_compressed: compressor.is_some(),
1671                        statistics: from_thrift_page_stats(
1672                            physical_type,
1673                            page_stats_to_thrift(statistics.as_ref()),
1674                        )
1675                        .unwrap(),
1676                    }
1677                }
1678                Page::DictionaryPage {
1679                    ref buf,
1680                    num_values,
1681                    encoding,
1682                    is_sorted,
1683                } => {
1684                    let output_buf = compress_helper(compressor.as_mut(), buf);
1685
1686                    Page::DictionaryPage {
1687                        buf: Bytes::from(output_buf),
1688                        num_values,
1689                        encoding,
1690                        is_sorted,
1691                    }
1692                }
1693            };
1694
1695            let compressed_page = CompressedPage::new(compressed_page, uncompressed_len);
1696            compressed_pages.push(compressed_page);
1697        }
1698
1699        let mut buffer: Vec<u8> = vec![];
1700        let mut result_pages: Vec<Page> = vec![];
1701        {
1702            let mut writer = TrackedWrite::new(&mut buffer);
1703            let mut page_writer = SerializedPageWriter::new(&mut writer);
1704
1705            for page in compressed_pages {
1706                page_writer.write_page(page).unwrap();
1707            }
1708            page_writer.close().unwrap();
1709        }
1710        {
1711            let reader = bytes::Bytes::from(buffer);
1712
1713            let t = types::Type::primitive_type_builder("t", physical_type)
1714                .build()
1715                .unwrap();
1716
1717            let desc = ColumnDescriptor::new(Arc::new(t), 0, 0, ColumnPath::new(vec![]));
1718            let meta = ColumnChunkMetaData::builder(Arc::new(desc))
1719                .set_compression_codec(codec.into())
1720                .set_total_compressed_size(reader.len() as i64)
1721                .set_num_values(total_num_values)
1722                .build()
1723                .unwrap();
1724
1725            let props = ReaderProperties::builder()
1726                .set_backward_compatible_lz4(false)
1727                .set_read_page_statistics(true)
1728                .build();
1729            let mut page_reader = SerializedPageReader::new_with_properties(
1730                Arc::new(reader),
1731                &meta,
1732                total_num_values as usize,
1733                None,
1734                Arc::new(props),
1735            )
1736            .unwrap();
1737
1738            while let Some(page) = page_reader.get_next_page().unwrap() {
1739                result_pages.push(page);
1740            }
1741        }
1742
1743        assert_eq!(result_pages.len(), pages.len());
1744        for i in 0..result_pages.len() {
1745            assert_page(&result_pages[i], &pages[i]);
1746        }
1747    }
1748
1749    /// Helper function to compress a slice
1750    fn compress_helper(compressor: Option<&mut Box<dyn Codec>>, data: &[u8]) -> Vec<u8> {
1751        let mut output_buf = vec![];
1752        if let Some(cmpr) = compressor {
1753            cmpr.compress(data, &mut output_buf).unwrap();
1754        } else {
1755            output_buf.extend_from_slice(data);
1756        }
1757        output_buf
1758    }
1759
1760    /// Check if pages match.
1761    fn assert_page(left: &Page, right: &Page) {
1762        assert_eq!(left.page_type(), right.page_type());
1763        assert_eq!(&left.buffer(), &right.buffer());
1764        assert_eq!(left.num_values(), right.num_values());
1765        assert_eq!(left.encoding(), right.encoding());
1766        assert_eq!(
1767            page_stats_to_thrift(left.statistics()),
1768            page_stats_to_thrift(right.statistics())
1769        );
1770    }
1771
1772    /// Tests roundtrip of i32 data written using `W` and read using `R`
1773    fn test_roundtrip_i32<W, R>(
1774        file: W,
1775        data: Vec<Vec<i32>>,
1776        compression: Compression,
1777    ) -> ParquetMetaData
1778    where
1779        W: Write + Send,
1780        R: ChunkReader + From<W> + 'static,
1781    {
1782        test_roundtrip::<W, R, Int32Type, _>(file, data, |r| r.get_int(0).unwrap(), compression)
1783    }
1784
1785    /// Tests roundtrip of data of type `D` written using `W` and read using `R`
1786    /// and the provided `values` function
1787    fn test_roundtrip<W, R, D, F>(
1788        mut file: W,
1789        data: Vec<Vec<D::T>>,
1790        value: F,
1791        compression: Compression,
1792    ) -> ParquetMetaData
1793    where
1794        W: Write + Send,
1795        R: ChunkReader + From<W> + 'static,
1796        D: DataType,
1797        F: Fn(Row) -> D::T,
1798    {
1799        let schema = Arc::new(
1800            types::Type::group_type_builder("schema")
1801                .with_fields(vec![Arc::new(
1802                    types::Type::primitive_type_builder("col1", D::get_physical_type())
1803                        .with_repetition(Repetition::REQUIRED)
1804                        .build()
1805                        .unwrap(),
1806                )])
1807                .build()
1808                .unwrap(),
1809        );
1810        let props = Arc::new(
1811            WriterProperties::builder()
1812                .set_compression(compression)
1813                .build(),
1814        );
1815        let mut file_writer = SerializedFileWriter::new(&mut file, schema, props).unwrap();
1816        let mut rows: i64 = 0;
1817
1818        for (idx, subset) in data.iter().enumerate() {
1819            let row_group_file_offset = file_writer.buf.bytes_written();
1820            let mut row_group_writer = file_writer.next_row_group().unwrap();
1821            if let Some(mut writer) = row_group_writer.next_column().unwrap() {
1822                rows += writer
1823                    .typed::<D>()
1824                    .write_batch(&subset[..], None, None)
1825                    .unwrap() as i64;
1826                writer.close().unwrap();
1827            }
1828            let last_group = row_group_writer.close().unwrap();
1829            let flushed = file_writer.flushed_row_groups();
1830            assert_eq!(flushed.len(), idx + 1);
1831            assert_eq!(Some(idx as i32), last_group.ordinal());
1832            assert_eq!(Some(row_group_file_offset as i64), last_group.file_offset());
1833            assert_eq!(&flushed[idx], last_group.as_ref());
1834        }
1835        let file_metadata = file_writer.close().unwrap();
1836
1837        let reader = SerializedFileReader::new(R::from(file)).unwrap();
1838        assert_eq!(reader.num_row_groups(), data.len());
1839        assert_eq!(
1840            reader.metadata().file_metadata().num_rows(),
1841            rows,
1842            "row count in metadata not equal to number of rows written"
1843        );
1844        for (i, item) in data.iter().enumerate().take(reader.num_row_groups()) {
1845            let row_group_reader = reader.get_row_group(i).unwrap();
1846            let iter = row_group_reader.get_row_iter(None).unwrap();
1847            let res: Vec<_> = iter.map(|row| row.unwrap()).map(&value).collect();
1848            let row_group_size = row_group_reader.metadata().total_byte_size();
1849            let uncompressed_size: i64 = row_group_reader
1850                .metadata()
1851                .columns()
1852                .iter()
1853                .map(|v| v.uncompressed_size())
1854                .sum();
1855            assert_eq!(row_group_size, uncompressed_size);
1856            assert_eq!(res, *item);
1857        }
1858        file_metadata
1859    }
1860
1861    /// File write-read roundtrip.
1862    /// `data` consists of arrays of values for each row group.
1863    fn test_file_roundtrip(file: File, data: Vec<Vec<i32>>) -> ParquetMetaData {
1864        test_roundtrip_i32::<File, File>(file, data, Compression::UNCOMPRESSED)
1865    }
1866
1867    #[test]
1868    fn test_bytes_writer_empty_row_groups() {
1869        test_bytes_roundtrip(vec![], Compression::UNCOMPRESSED);
1870    }
1871
1872    #[test]
1873    fn test_bytes_writer_single_row_group() {
1874        test_bytes_roundtrip(vec![vec![1, 2, 3, 4, 5]], Compression::UNCOMPRESSED);
1875    }
1876
1877    #[test]
1878    fn test_bytes_writer_multiple_row_groups() {
1879        test_bytes_roundtrip(
1880            vec![
1881                vec![1, 2, 3, 4, 5],
1882                vec![1, 2, 3],
1883                vec![1],
1884                vec![1, 2, 3, 4, 5, 6],
1885            ],
1886            Compression::UNCOMPRESSED,
1887        );
1888    }
1889
1890    #[test]
1891    fn test_bytes_writer_single_row_group_compressed() {
1892        test_bytes_roundtrip(vec![vec![1, 2, 3, 4, 5]], Compression::SNAPPY);
1893    }
1894
1895    #[test]
1896    fn test_bytes_writer_multiple_row_groups_compressed() {
1897        test_bytes_roundtrip(
1898            vec![
1899                vec![1, 2, 3, 4, 5],
1900                vec![1, 2, 3],
1901                vec![1],
1902                vec![1, 2, 3, 4, 5, 6],
1903            ],
1904            Compression::SNAPPY,
1905        );
1906    }
1907
1908    fn test_bytes_roundtrip(data: Vec<Vec<i32>>, compression: Compression) {
1909        test_roundtrip_i32::<Vec<u8>, Bytes>(Vec::with_capacity(1024), data, compression);
1910    }
1911
1912    #[test]
1913    fn test_boolean_roundtrip() {
1914        let my_bool_values: Vec<_> = (0..2049).map(|idx| idx % 2 == 0).collect();
1915        test_roundtrip::<Vec<u8>, Bytes, BoolType, _>(
1916            Vec::with_capacity(1024),
1917            vec![my_bool_values],
1918            |r| r.get_bool(0).unwrap(),
1919            Compression::UNCOMPRESSED,
1920        );
1921    }
1922
1923    #[test]
1924    fn test_boolean_compressed_roundtrip() {
1925        let my_bool_values: Vec<_> = (0..2049).map(|idx| idx % 2 == 0).collect();
1926        test_roundtrip::<Vec<u8>, Bytes, BoolType, _>(
1927            Vec::with_capacity(1024),
1928            vec![my_bool_values],
1929            |r| r.get_bool(0).unwrap(),
1930            Compression::SNAPPY,
1931        );
1932    }
1933
1934    #[test]
1935    fn test_column_offset_index_file() {
1936        let file = tempfile::tempfile().unwrap();
1937        let file_metadata = test_file_roundtrip(file, vec![vec![1, 2, 3, 4, 5]]);
1938        file_metadata.row_groups().iter().for_each(|row_group| {
1939            row_group.columns().iter().for_each(|column_chunk| {
1940                assert!(column_chunk.column_index_offset().is_some());
1941                assert!(column_chunk.column_index_length().is_some());
1942                assert!(column_chunk.offset_index_offset().is_some());
1943                assert!(column_chunk.offset_index_length().is_some());
1944            })
1945        });
1946    }
1947
1948    fn test_kv_metadata(initial_kv: Option<Vec<KeyValue>>, final_kv: Option<Vec<KeyValue>>) {
1949        let schema = Arc::new(
1950            types::Type::group_type_builder("schema")
1951                .with_fields(vec![Arc::new(
1952                    types::Type::primitive_type_builder("col1", Type::INT32)
1953                        .with_repetition(Repetition::REQUIRED)
1954                        .build()
1955                        .unwrap(),
1956                )])
1957                .build()
1958                .unwrap(),
1959        );
1960        let mut out = Vec::with_capacity(1024);
1961        let props = Arc::new(
1962            WriterProperties::builder()
1963                .set_key_value_metadata(initial_kv.clone())
1964                .build(),
1965        );
1966        let mut writer = SerializedFileWriter::new(&mut out, schema, props).unwrap();
1967        let mut row_group_writer = writer.next_row_group().unwrap();
1968        let column = row_group_writer.next_column().unwrap().unwrap();
1969        column.close().unwrap();
1970        row_group_writer.close().unwrap();
1971        if let Some(kvs) = &final_kv {
1972            for kv in kvs {
1973                writer.append_key_value_metadata(kv.clone())
1974            }
1975        }
1976        writer.close().unwrap();
1977
1978        let reader = SerializedFileReader::new(Bytes::from(out)).unwrap();
1979        let metadata = reader.metadata().file_metadata();
1980        let keys = metadata.key_value_metadata();
1981
1982        match (initial_kv, final_kv) {
1983            (Some(a), Some(b)) => {
1984                let keys = keys.unwrap();
1985                assert_eq!(keys.len(), a.len() + b.len());
1986                assert_eq!(&keys[..a.len()], a.as_slice());
1987                assert_eq!(&keys[a.len()..], b.as_slice());
1988            }
1989            (Some(v), None) => assert_eq!(keys.unwrap(), &v),
1990            (None, Some(v)) if !v.is_empty() => assert_eq!(keys.unwrap(), &v),
1991            _ => assert!(keys.is_none()),
1992        }
1993    }
1994
1995    #[test]
1996    fn test_append_metadata() {
1997        let kv1 = KeyValue::new("cupcakes".to_string(), "awesome".to_string());
1998        let kv2 = KeyValue::new("bingo".to_string(), "bongo".to_string());
1999
2000        test_kv_metadata(None, None);
2001        test_kv_metadata(Some(vec![kv1.clone()]), None);
2002        test_kv_metadata(None, Some(vec![kv2.clone()]));
2003        test_kv_metadata(Some(vec![kv1.clone()]), Some(vec![kv2.clone()]));
2004        test_kv_metadata(Some(vec![]), Some(vec![kv2]));
2005        test_kv_metadata(Some(vec![]), Some(vec![]));
2006        test_kv_metadata(Some(vec![kv1]), Some(vec![]));
2007        test_kv_metadata(None, Some(vec![]));
2008    }
2009
2010    #[test]
2011    fn test_backwards_compatible_statistics() {
2012        let message_type = "
2013            message test_schema {
2014                REQUIRED INT32 decimal1 (DECIMAL(8,2));
2015                REQUIRED INT32 i32 (INTEGER(32,true));
2016                REQUIRED INT32 u32 (INTEGER(32,false));
2017            }
2018        ";
2019
2020        let schema = Arc::new(parse_message_type(message_type).unwrap());
2021        let props = Default::default();
2022        let mut writer = SerializedFileWriter::new(vec![], schema, props).unwrap();
2023        let mut row_group_writer = writer.next_row_group().unwrap();
2024
2025        for _ in 0..3 {
2026            let mut writer = row_group_writer.next_column().unwrap().unwrap();
2027            writer
2028                .typed::<Int32Type>()
2029                .write_batch(&[1, 2, 3], None, None)
2030                .unwrap();
2031            writer.close().unwrap();
2032        }
2033        let metadata = row_group_writer.close().unwrap();
2034        writer.close().unwrap();
2035
2036        // decimal
2037        let s = page_stats_to_thrift(metadata.column(0).statistics()).unwrap();
2038        assert_eq!(s.min.as_deref(), Some(1_i32.to_le_bytes().as_ref()));
2039        assert_eq!(s.max.as_deref(), Some(3_i32.to_le_bytes().as_ref()));
2040        assert_eq!(s.min_value.as_deref(), Some(1_i32.to_le_bytes().as_ref()));
2041        assert_eq!(s.max_value.as_deref(), Some(3_i32.to_le_bytes().as_ref()));
2042
2043        // i32
2044        let s = page_stats_to_thrift(metadata.column(1).statistics()).unwrap();
2045        assert_eq!(s.min.as_deref(), Some(1_i32.to_le_bytes().as_ref()));
2046        assert_eq!(s.max.as_deref(), Some(3_i32.to_le_bytes().as_ref()));
2047        assert_eq!(s.min_value.as_deref(), Some(1_i32.to_le_bytes().as_ref()));
2048        assert_eq!(s.max_value.as_deref(), Some(3_i32.to_le_bytes().as_ref()));
2049
2050        // u32
2051        let s = page_stats_to_thrift(metadata.column(2).statistics()).unwrap();
2052        assert_eq!(s.min.as_deref(), None);
2053        assert_eq!(s.max.as_deref(), None);
2054        assert_eq!(s.min_value.as_deref(), Some(1_i32.to_le_bytes().as_ref()));
2055        assert_eq!(s.max_value.as_deref(), Some(3_i32.to_le_bytes().as_ref()));
2056    }
2057
2058    #[test]
2059    fn test_spliced_write() {
2060        let message_type = "
2061            message test_schema {
2062                REQUIRED INT32 i32 (INTEGER(32,true));
2063                REQUIRED INT32 u32 (INTEGER(32,false));
2064            }
2065        ";
2066        let schema = Arc::new(parse_message_type(message_type).unwrap());
2067        let props = Arc::new(WriterProperties::builder().build());
2068
2069        let mut file = Vec::with_capacity(1024);
2070        let mut file_writer = SerializedFileWriter::new(&mut file, schema, props.clone()).unwrap();
2071
2072        let columns = file_writer.descr.columns();
2073        let mut column_state: Vec<(_, Option<ColumnCloseResult>)> = columns
2074            .iter()
2075            .map(|_| (TrackedWrite::new(Vec::with_capacity(1024)), None))
2076            .collect();
2077
2078        let mut column_state_slice = column_state.as_mut_slice();
2079        let mut column_writers = Vec::with_capacity(columns.len());
2080        for c in columns {
2081            let ((buf, out), tail) = column_state_slice.split_first_mut().unwrap();
2082            column_state_slice = tail;
2083
2084            let page_writer = Box::new(SerializedPageWriter::new(buf));
2085            let col_writer = get_column_writer(c.clone(), props.clone(), page_writer);
2086            column_writers.push(SerializedColumnWriter::new(
2087                col_writer,
2088                Some(Box::new(|on_close| {
2089                    *out = Some(on_close);
2090                    Ok(())
2091                })),
2092            ));
2093        }
2094
2095        let column_data = [[1, 2, 3, 4], [7, 3, 7, 3]];
2096
2097        // Interleaved writing to the column writers
2098        for (writer, batch) in column_writers.iter_mut().zip(column_data) {
2099            let writer = writer.typed::<Int32Type>();
2100            writer.write_batch(&batch, None, None).unwrap();
2101        }
2102
2103        // Close the column writers
2104        for writer in column_writers {
2105            writer.close().unwrap()
2106        }
2107
2108        // Splice column data into a row group
2109        let mut row_group_writer = file_writer.next_row_group().unwrap();
2110        for (write, close) in column_state {
2111            let buf = Bytes::from(write.into_inner().unwrap());
2112            row_group_writer
2113                .append_column(&buf, close.unwrap())
2114                .unwrap();
2115        }
2116        row_group_writer.close().unwrap();
2117        file_writer.close().unwrap();
2118
2119        // Check data was written correctly
2120        let file = Bytes::from(file);
2121        let test_read = |reader: SerializedFileReader<Bytes>| {
2122            let row_group = reader.get_row_group(0).unwrap();
2123
2124            let mut out = Vec::with_capacity(4);
2125            let c1 = row_group.get_column_reader(0).unwrap();
2126            let mut c1 = get_typed_column_reader::<Int32Type>(c1);
2127            c1.read_records(4, None, None, &mut out).unwrap();
2128            assert_eq!(out, column_data[0]);
2129
2130            out.clear();
2131
2132            let c2 = row_group.get_column_reader(1).unwrap();
2133            let mut c2 = get_typed_column_reader::<Int32Type>(c2);
2134            c2.read_records(4, None, None, &mut out).unwrap();
2135            assert_eq!(out, column_data[1]);
2136        };
2137
2138        let reader = SerializedFileReader::new(file.clone()).unwrap();
2139        test_read(reader);
2140
2141        let options = ReadOptionsBuilder::new().with_page_index().build();
2142        let reader = SerializedFileReader::new_with_options(file, options).unwrap();
2143        test_read(reader);
2144    }
2145
2146    #[test]
2147    fn test_disabled_statistics() {
2148        let message_type = "
2149            message test_schema {
2150                REQUIRED INT32 a;
2151                REQUIRED INT32 b;
2152            }
2153        ";
2154        let schema = Arc::new(parse_message_type(message_type).unwrap());
2155        let props = WriterProperties::builder()
2156            .set_statistics_enabled(EnabledStatistics::None)
2157            .set_column_statistics_enabled("a".into(), EnabledStatistics::Page)
2158            .set_offset_index_disabled(true) // this should be ignored because of the line above
2159            .build();
2160        let mut file = Vec::with_capacity(1024);
2161        let mut file_writer =
2162            SerializedFileWriter::new(&mut file, schema, Arc::new(props)).unwrap();
2163
2164        let mut row_group_writer = file_writer.next_row_group().unwrap();
2165        let mut a_writer = row_group_writer.next_column().unwrap().unwrap();
2166        let col_writer = a_writer.typed::<Int32Type>();
2167        col_writer.write_batch(&[1, 2, 3], None, None).unwrap();
2168        a_writer.close().unwrap();
2169
2170        let mut b_writer = row_group_writer.next_column().unwrap().unwrap();
2171        let col_writer = b_writer.typed::<Int32Type>();
2172        col_writer.write_batch(&[4, 5, 6], None, None).unwrap();
2173        b_writer.close().unwrap();
2174        row_group_writer.close().unwrap();
2175
2176        let metadata = file_writer.finish().unwrap();
2177        assert_eq!(metadata.num_row_groups(), 1);
2178        let row_group = metadata.row_group(0);
2179        assert_eq!(row_group.num_columns(), 2);
2180        // Column "a" has both offset and column index, as requested
2181        assert!(row_group.column(0).offset_index_offset().is_some());
2182        assert!(row_group.column(0).column_index_offset().is_some());
2183        // Column "b" should only have offset index
2184        assert!(row_group.column(1).offset_index_offset().is_some());
2185        assert!(row_group.column(1).column_index_offset().is_none());
2186
2187        let err = file_writer.next_row_group().err().unwrap().to_string();
2188        assert_eq!(err, "Parquet error: SerializedFileWriter already finished");
2189
2190        drop(file_writer);
2191
2192        let options = ReadOptionsBuilder::new().with_page_index().build();
2193        let reader = SerializedFileReader::new_with_options(Bytes::from(file), options).unwrap();
2194
2195        let a_idx = reader.metadata().page_index().unwrap().column_index(0, 0);
2196        assert!(
2197            matches!(a_idx, Some(ColumnIndexMetaData::INT32(_))),
2198            "{a_idx:?}"
2199        );
2200        let b_idx = reader.metadata().page_index().unwrap().column_index(0, 1);
2201        assert!(b_idx.is_none(), "{b_idx:?}");
2202    }
2203
2204    #[test]
2205    fn test_byte_array_size_statistics() {
2206        let message_type = "
2207            message test_schema {
2208                OPTIONAL BYTE_ARRAY a (UTF8);
2209            }
2210        ";
2211        let schema = Arc::new(parse_message_type(message_type).unwrap());
2212        let data = ByteArrayType::gen_vec(32, 7);
2213        let def_levels = [1, 1, 1, 1, 0, 1, 0, 1, 0, 1];
2214        let unenc_size: i64 = data.iter().map(|x| x.len() as i64).sum();
2215        let file: File = tempfile::tempfile().unwrap();
2216        let props = Arc::new(
2217            WriterProperties::builder()
2218                .set_statistics_enabled(EnabledStatistics::Page)
2219                .build(),
2220        );
2221
2222        let mut writer = SerializedFileWriter::new(&file, schema, props).unwrap();
2223        let mut row_group_writer = writer.next_row_group().unwrap();
2224
2225        let mut col_writer = row_group_writer.next_column().unwrap().unwrap();
2226        col_writer
2227            .typed::<ByteArrayType>()
2228            .write_batch(&data, Some(&def_levels), None)
2229            .unwrap();
2230        col_writer.close().unwrap();
2231        row_group_writer.close().unwrap();
2232        let file_metadata = writer.close().unwrap();
2233
2234        assert_eq!(file_metadata.num_row_groups(), 1);
2235        assert_eq!(file_metadata.row_group(0).num_columns(), 1);
2236
2237        let check_def_hist = |def_hist: &[i64]| {
2238            assert_eq!(def_hist.len(), 2);
2239            assert_eq!(def_hist[0], 3);
2240            assert_eq!(def_hist[1], 7);
2241        };
2242
2243        let meta_data = file_metadata.row_group(0).column(0);
2244
2245        assert!(meta_data.repetition_level_histogram().is_none());
2246        assert!(meta_data.definition_level_histogram().is_some());
2247        assert!(meta_data.unencoded_byte_array_data_bytes().is_some());
2248        assert_eq!(
2249            unenc_size,
2250            meta_data.unencoded_byte_array_data_bytes().unwrap()
2251        );
2252        check_def_hist(meta_data.definition_level_histogram().unwrap().values());
2253
2254        // check that the read metadata is also correct
2255        let options = ReadOptionsBuilder::new().with_page_index().build();
2256        let reader = SerializedFileReader::new_with_options(file, options).unwrap();
2257
2258        let rfile_metadata = reader.metadata().file_metadata();
2259        assert_eq!(
2260            rfile_metadata.num_rows(),
2261            file_metadata.file_metadata().num_rows()
2262        );
2263        assert_eq!(reader.num_row_groups(), 1);
2264        let rowgroup = reader.get_row_group(0).unwrap();
2265        assert_eq!(rowgroup.num_columns(), 1);
2266        let column = rowgroup.metadata().column(0);
2267        assert!(column.definition_level_histogram().is_some());
2268        assert!(column.repetition_level_histogram().is_none());
2269        assert!(column.unencoded_byte_array_data_bytes().is_some());
2270        check_def_hist(column.definition_level_histogram().unwrap().values());
2271        assert_eq!(
2272            unenc_size,
2273            column.unencoded_byte_array_data_bytes().unwrap()
2274        );
2275
2276        // check histogram in column index as well
2277        assert!(reader.metadata().page_index().is_some());
2278        let page_index = reader.metadata().page_index().unwrap();
2279        let col_idx =
2280            if let Some(ColumnIndexMetaData::BYTE_ARRAY(index)) = page_index.column_index(0, 0) {
2281                assert_eq!(index.num_pages(), 1);
2282                index
2283            } else {
2284                unreachable!()
2285            };
2286
2287        assert!(col_idx.repetition_level_histogram(0).is_none());
2288        assert!(col_idx.definition_level_histogram(0).is_some());
2289        check_def_hist(col_idx.definition_level_histogram(0).unwrap());
2290
2291        assert!(page_index.offset_index(0, 0).is_some());
2292        assert!(
2293            page_index
2294                .offset_index(0, 0)
2295                .unwrap()
2296                .unencoded_byte_array_data_bytes
2297                .is_some()
2298        );
2299        let page_sizes = page_index
2300            .offset_index(0, 0)
2301            .unwrap()
2302            .unencoded_byte_array_data_bytes
2303            .as_ref()
2304            .unwrap();
2305        assert_eq!(page_sizes.len(), 1);
2306        assert_eq!(page_sizes[0], unenc_size);
2307    }
2308
2309    #[cfg(feature = "encryption")]
2310    #[test]
2311    fn test_too_many_rowgroups() {
2312        let message_type = "
2313            message test_schema {
2314                REQUIRED BYTE_ARRAY a (UTF8);
2315            }
2316        ";
2317        let schema = Arc::new(parse_message_type(message_type).unwrap());
2318        let file: File = tempfile::tempfile().unwrap();
2319
2320        const AES_128_FOOTER_KEY: &[u8; 16] = b"0123456789012345"; // 128bit/16
2321        let footer_key = AES_128_FOOTER_KEY;
2322        let file_encryption_properties = FileEncryptionProperties::builder(footer_key.to_vec())
2323            .build()
2324            .unwrap();
2325        let props = Arc::new(
2326            WriterProperties::builder()
2327                .set_statistics_enabled(EnabledStatistics::None)
2328                .set_max_row_group_row_count(Some(1))
2329                .with_file_encryption_properties(file_encryption_properties)
2330                .build(),
2331        );
2332        let mut writer = SerializedFileWriter::new(&file, schema, props).unwrap();
2333
2334        // Create 32k empty rowgroups. Should error when i == 32768.
2335        for i in 0..0x8001 {
2336            match writer.next_row_group() {
2337                Ok(mut row_group_writer) => {
2338                    assert_ne!(i, 0x8000);
2339                    let col_writer = row_group_writer.next_column().unwrap().unwrap();
2340                    col_writer.close().unwrap();
2341                    row_group_writer.close().unwrap();
2342                }
2343                Err(e) => {
2344                    assert_eq!(i, 0x8000);
2345                    assert_eq!(
2346                        e.to_string(),
2347                        "Parquet error: Parquet with encryption does not support more than 32767 row groups per file (currently: 32768)"
2348                    );
2349                }
2350            }
2351        }
2352        writer.close().unwrap();
2353    }
2354
2355    #[test]
2356    #[cfg_attr(miri, ignore)] // Takes too long
2357    fn test_32k_rowgroups() {
2358        let message_type = "
2359            message test_schema {
2360                REQUIRED BYTE_ARRAY a (UTF8);
2361            }
2362        ";
2363        let schema = Arc::new(parse_message_type(message_type).unwrap());
2364        let file: File = tempfile::tempfile().unwrap();
2365        let props = Arc::new(
2366            WriterProperties::builder()
2367                .set_statistics_enabled(EnabledStatistics::None)
2368                .set_max_row_group_row_count(Some(1))
2369                .build(),
2370        );
2371        let mut writer = SerializedFileWriter::new(&file, schema, props).unwrap();
2372
2373        // Create 32k + 1 empty rowgroups. No row group ordinals should be written (but we can't
2374        // test for that).
2375        for _ in 0..0x8001 {
2376            let mut row_group_writer = writer.next_row_group().unwrap();
2377            let col_writer = row_group_writer.next_column().unwrap().unwrap();
2378            col_writer.close().unwrap();
2379            row_group_writer.close().unwrap();
2380        }
2381        writer.close().unwrap();
2382
2383        // Parse the written metadata and check that ordinals were replaced.
2384        let reader = SerializedFileReader::new(file).unwrap();
2385        let metadata = reader.metadata();
2386
2387        for (i, rg) in metadata.row_groups().iter().enumerate() {
2388            assert_eq!(i as i32, rg.ordinal().unwrap());
2389        }
2390    }
2391
2392    #[test]
2393    fn test_size_statistics_with_repetition_and_nulls() {
2394        let message_type = "
2395            message test_schema {
2396                OPTIONAL group i32_list (LIST) {
2397                    REPEATED group list {
2398                        OPTIONAL INT32 element;
2399                    }
2400                }
2401            }
2402        ";
2403        // column is:
2404        // row 0: [1, 2]
2405        // row 1: NULL
2406        // row 2: [4, NULL]
2407        // row 3: []
2408        // row 4: [7, 8, 9, 10]
2409        let schema = Arc::new(parse_message_type(message_type).unwrap());
2410        let data = [1, 2, 4, 7, 8, 9, 10];
2411        let def_levels = [3, 3, 0, 3, 2, 1, 3, 3, 3, 3];
2412        let rep_levels = [0, 1, 0, 0, 1, 0, 0, 1, 1, 1];
2413        let file = tempfile::tempfile().unwrap();
2414        let props = Arc::new(
2415            WriterProperties::builder()
2416                .set_statistics_enabled(EnabledStatistics::Page)
2417                .build(),
2418        );
2419        let mut writer = SerializedFileWriter::new(&file, schema, props).unwrap();
2420        let mut row_group_writer = writer.next_row_group().unwrap();
2421
2422        let mut col_writer = row_group_writer.next_column().unwrap().unwrap();
2423        col_writer
2424            .typed::<Int32Type>()
2425            .write_batch(&data, Some(&def_levels), Some(&rep_levels))
2426            .unwrap();
2427        col_writer.close().unwrap();
2428        row_group_writer.close().unwrap();
2429        let file_metadata = writer.close().unwrap();
2430
2431        assert_eq!(file_metadata.num_row_groups(), 1);
2432        assert_eq!(file_metadata.row_group(0).num_columns(), 1);
2433
2434        let check_def_hist = |def_hist: &[i64]| {
2435            assert_eq!(def_hist.len(), 4);
2436            assert_eq!(def_hist[0], 1);
2437            assert_eq!(def_hist[1], 1);
2438            assert_eq!(def_hist[2], 1);
2439            assert_eq!(def_hist[3], 7);
2440        };
2441
2442        let check_rep_hist = |rep_hist: &[i64]| {
2443            assert_eq!(rep_hist.len(), 2);
2444            assert_eq!(rep_hist[0], 5);
2445            assert_eq!(rep_hist[1], 5);
2446        };
2447
2448        // check that histograms are set properly in the write and read metadata
2449        // also check that unencoded_byte_array_data_bytes is not set
2450        let meta_data = file_metadata.row_group(0).column(0);
2451        assert!(meta_data.repetition_level_histogram().is_some());
2452        assert!(meta_data.definition_level_histogram().is_some());
2453        assert!(meta_data.unencoded_byte_array_data_bytes().is_none());
2454        check_def_hist(meta_data.definition_level_histogram().unwrap().values());
2455        check_rep_hist(meta_data.repetition_level_histogram().unwrap().values());
2456
2457        // check that the read metadata is also correct
2458        let options = ReadOptionsBuilder::new().with_page_index().build();
2459        let reader = SerializedFileReader::new_with_options(file, options).unwrap();
2460
2461        let rfile_metadata = reader.metadata().file_metadata();
2462        assert_eq!(
2463            rfile_metadata.num_rows(),
2464            file_metadata.file_metadata().num_rows()
2465        );
2466        assert_eq!(reader.num_row_groups(), 1);
2467        let rowgroup = reader.get_row_group(0).unwrap();
2468        assert_eq!(rowgroup.num_columns(), 1);
2469        let column = rowgroup.metadata().column(0);
2470        assert!(column.definition_level_histogram().is_some());
2471        assert!(column.repetition_level_histogram().is_some());
2472        assert!(column.unencoded_byte_array_data_bytes().is_none());
2473        check_def_hist(column.definition_level_histogram().unwrap().values());
2474        check_rep_hist(column.repetition_level_histogram().unwrap().values());
2475
2476        assert!(
2477            reader
2478                .metadata()
2479                .page_index()
2480                .is_some_and(PageIndex::is_complete)
2481        );
2482        let page_index = reader.metadata().page_index().unwrap();
2483
2484        // check histogram in column index as well
2485        let col_idx = if let Some(ColumnIndexMetaData::INT32(index)) = page_index.column_index(0, 0)
2486        {
2487            assert_eq!(index.num_pages(), 1);
2488            index
2489        } else {
2490            unreachable!()
2491        };
2492
2493        check_def_hist(col_idx.definition_level_histogram(0).unwrap());
2494        check_rep_hist(col_idx.repetition_level_histogram(0).unwrap());
2495
2496        assert!(
2497            page_index
2498                .offset_index(0, 0)
2499                .unwrap()
2500                .unencoded_byte_array_data_bytes
2501                .is_none()
2502        );
2503    }
2504
2505    #[test]
2506    #[cfg_attr(miri, ignore)] // Takes too long
2507    #[cfg(feature = "arrow")]
2508    fn test_byte_stream_split_extended_roundtrip() {
2509        let path = format!(
2510            "{}/byte_stream_split_extended.gzip.parquet",
2511            arrow::util::test_util::parquet_test_data(),
2512        );
2513        let file = File::open(path).unwrap();
2514
2515        // Read in test file and rewrite to tmp
2516        let parquet_reader = ParquetRecordBatchReaderBuilder::try_new(file)
2517            .expect("parquet open")
2518            .build()
2519            .expect("parquet open");
2520
2521        let file = tempfile::tempfile().unwrap();
2522        let props = WriterProperties::builder()
2523            .set_dictionary_enabled(false)
2524            .set_column_encoding(
2525                ColumnPath::from("float16_byte_stream_split"),
2526                Encoding::BYTE_STREAM_SPLIT,
2527            )
2528            .set_column_encoding(
2529                ColumnPath::from("float_byte_stream_split"),
2530                Encoding::BYTE_STREAM_SPLIT,
2531            )
2532            .set_column_encoding(
2533                ColumnPath::from("double_byte_stream_split"),
2534                Encoding::BYTE_STREAM_SPLIT,
2535            )
2536            .set_column_encoding(
2537                ColumnPath::from("int32_byte_stream_split"),
2538                Encoding::BYTE_STREAM_SPLIT,
2539            )
2540            .set_column_encoding(
2541                ColumnPath::from("int64_byte_stream_split"),
2542                Encoding::BYTE_STREAM_SPLIT,
2543            )
2544            .set_column_encoding(
2545                ColumnPath::from("flba5_byte_stream_split"),
2546                Encoding::BYTE_STREAM_SPLIT,
2547            )
2548            .set_column_encoding(
2549                ColumnPath::from("decimal_byte_stream_split"),
2550                Encoding::BYTE_STREAM_SPLIT,
2551            )
2552            .build();
2553
2554        let mut parquet_writer = ArrowWriter::try_new(
2555            file.try_clone().expect("cannot open file"),
2556            parquet_reader.schema(),
2557            Some(props),
2558        )
2559        .expect("create arrow writer");
2560
2561        for maybe_batch in parquet_reader {
2562            let batch = maybe_batch.expect("reading batch");
2563            parquet_writer.write(&batch).expect("writing data");
2564        }
2565
2566        parquet_writer.close().expect("finalizing file");
2567
2568        let reader = SerializedFileReader::new(file).expect("Failed to create reader");
2569        let filemeta = reader.metadata();
2570
2571        // Make sure byte_stream_split encoding was used
2572        let check_encoding = |x: usize, filemeta: &ParquetMetaData| {
2573            assert!(
2574                filemeta
2575                    .row_group(0)
2576                    .column(x)
2577                    .encodings()
2578                    .collect::<Vec<_>>()
2579                    .contains(&Encoding::BYTE_STREAM_SPLIT)
2580            );
2581        };
2582
2583        check_encoding(1, filemeta);
2584        check_encoding(3, filemeta);
2585        check_encoding(5, filemeta);
2586        check_encoding(7, filemeta);
2587        check_encoding(9, filemeta);
2588        check_encoding(11, filemeta);
2589        check_encoding(13, filemeta);
2590
2591        // Read back tmpfile and make sure all values are correct
2592        let mut iter = reader
2593            .get_row_iter(None)
2594            .expect("Failed to create row iterator");
2595
2596        let mut start = 0;
2597        let end = reader.metadata().file_metadata().num_rows();
2598
2599        let check_row = |row: Result<Row, ParquetError>| {
2600            assert!(row.is_ok());
2601            let r = row.unwrap();
2602            assert_eq!(r.get_float16(0).unwrap(), r.get_float16(1).unwrap());
2603            assert_eq!(r.get_float(2).unwrap(), r.get_float(3).unwrap());
2604            assert_eq!(r.get_double(4).unwrap(), r.get_double(5).unwrap());
2605            assert_eq!(r.get_int(6).unwrap(), r.get_int(7).unwrap());
2606            assert_eq!(r.get_long(8).unwrap(), r.get_long(9).unwrap());
2607            assert_eq!(r.get_bytes(10).unwrap(), r.get_bytes(11).unwrap());
2608            assert_eq!(r.get_decimal(12).unwrap(), r.get_decimal(13).unwrap());
2609        };
2610
2611        while start < end {
2612            match iter.next() {
2613                Some(row) => check_row(row),
2614                None => break,
2615            }
2616            start += 1;
2617        }
2618    }
2619
2620    #[test]
2621    #[cfg_attr(miri, ignore)] // Takes too long
2622    fn test_rewrite_no_page_indexes() {
2623        let file = get_test_file("alltypes_tiny_pages.parquet");
2624        let metadata = ParquetMetaDataReader::new()
2625            .with_page_index_policy(PageIndexPolicy::Optional)
2626            .parse_and_finish(&file)
2627            .unwrap();
2628
2629        let props = Arc::new(WriterProperties::builder().build());
2630        let schema = metadata.file_metadata().schema_descr().root_schema_ptr();
2631        let output = Vec::<u8>::new();
2632        let mut writer = SerializedFileWriter::new(output, schema, props).unwrap();
2633
2634        for rg in metadata.row_groups() {
2635            let mut rg_out = writer.next_row_group().unwrap();
2636            for column in rg.columns() {
2637                let result = ColumnCloseResult {
2638                    bytes_written: column.compressed_size() as _,
2639                    rows_written: rg.num_rows() as _,
2640                    metadata: column.clone(),
2641                    bloom_filter: None,
2642                    column_index: None,
2643                    offset_index: None,
2644                };
2645                rg_out.append_column(&file, result).unwrap();
2646            }
2647            rg_out.close().unwrap();
2648        }
2649        writer.close().unwrap();
2650    }
2651
2652    #[test]
2653    #[cfg_attr(miri, ignore)] // Takes too long
2654    fn test_rewrite_missing_column_index() {
2655        // this file has an INT96 column that lacks a column index entry
2656        let file = get_test_file("alltypes_tiny_pages.parquet");
2657        let metadata = ParquetMetaDataReader::new()
2658            .with_page_index_policy(PageIndexPolicy::Optional)
2659            .parse_and_finish(&file)
2660            .unwrap();
2661
2662        let props = Arc::new(WriterProperties::builder().build());
2663        let schema = metadata.file_metadata().schema_descr().root_schema_ptr();
2664        let output = Vec::<u8>::new();
2665        let mut writer = SerializedFileWriter::new(output, schema, props).unwrap();
2666
2667        let page_index = metadata.page_index();
2668
2669        for (rg_idx, rg) in metadata.row_groups().iter().enumerate() {
2670            let rg_column_indexes =
2671                page_index.and_then(|pi| pi.column_indexes_for_rowgroup(rg_idx));
2672            let rg_offset_indexes =
2673                page_index.and_then(|pi| pi.offset_indexes_for_rowgroup(rg_idx));
2674            let mut rg_out = writer.next_row_group().unwrap();
2675            for (col_idx, column) in rg.columns().iter().enumerate() {
2676                let column_index = rg_column_indexes.and_then(|row| {
2677                    let c = row.get(col_idx)?;
2678                    c.clone()
2679                });
2680                let offset_index = rg_offset_indexes.and_then(|row| {
2681                    let o = row.get(col_idx)?;
2682                    o.clone()
2683                });
2684
2685                let result = ColumnCloseResult {
2686                    bytes_written: column.compressed_size() as _,
2687                    rows_written: rg.num_rows() as _,
2688                    metadata: column.clone(),
2689                    bloom_filter: None,
2690                    column_index,
2691                    offset_index,
2692                };
2693                rg_out.append_column(&file, result).unwrap();
2694            }
2695            rg_out.close().unwrap();
2696        }
2697        writer.close().unwrap();
2698    }
2699
2700    #[test]
2701    fn test_int96_interop() {
2702        // this file has an INT96 column. rewrite it with min/max statistics sorted per
2703        // recent changes to the spec. (see https://github.com/apache/parquet-format/pull/584)
2704        let file = get_test_file("int96_timestamp_order.parquet");
2705        let read_opts = ReadOptionsBuilder::new().with_page_index().build();
2706        let reader = SerializedFileReader::new_with_options(file, read_opts).unwrap();
2707        let file_metadata = reader.metadata().file_metadata();
2708        let schema = file_metadata.schema_descr().root_schema_ptr();
2709
2710        // helper function to extract Int96 min/max from column metadata and the column index
2711        fn retrieve_stats(metadata: &ParquetMetaData) -> (&[u8], &[u8], &Int96, &Int96) {
2712            // sanity check that the proper column order is specified
2713            let column_orders = metadata
2714                .file_metadata()
2715                .column_orders()
2716                .expect("column_orders is missing");
2717            assert_eq!(column_orders[0], ColumnOrder::INT96_TIMESTAMP_ORDER);
2718
2719            let stats = metadata
2720                .row_group(0)
2721                .column(0)
2722                .statistics()
2723                .expect("statistics missing");
2724            let min = stats.min_bytes_opt().expect("min stats missing");
2725            let max = stats.max_bytes_opt().expect("max stats missing");
2726
2727            assert!(
2728                metadata
2729                    .page_index()
2730                    .is_some_and(|pi| pi.has_column_indexes())
2731            );
2732            let Some(ColumnIndexMetaData::INT96(col0)) =
2733                metadata.page_index().unwrap().column_index(0, 0)
2734            else {
2735                panic!("expected INT96 stats")
2736            };
2737            let col_min = col0.min_value(0).expect("ColumnIndex min not present");
2738            let col_max = col0.max_value(0).expect("ColumnIndex max not present");
2739
2740            (min, max, col_min, col_max)
2741        }
2742
2743        // save read stats for later
2744        let (exp_min, exp_max, exp_col_min, exp_col_max) = retrieve_stats(reader.metadata());
2745
2746        // write file back out again
2747        let props = Arc::new(WriterProperties::builder().build());
2748        let output = Vec::<u8>::new();
2749        let mut writer = SerializedFileWriter::new(output, schema, props).unwrap();
2750
2751        let mut rg_out = writer.next_row_group().unwrap();
2752        let rg_in = reader.get_row_group(0).unwrap();
2753
2754        // int96 is column 0
2755        let col_in = rg_in.get_column_reader(0).unwrap();
2756        let mut typed_in = get_typed_column_reader::<Int96Type>(col_in);
2757
2758        let mut values = Vec::new();
2759        typed_in.read_records(4, None, None, &mut values).unwrap();
2760
2761        let mut col_out = rg_out.next_column().unwrap().unwrap();
2762        col_out
2763            .typed::<Int96Type>()
2764            .write_batch(&values, None, None)
2765            .unwrap();
2766        col_out.close().unwrap();
2767        rg_out.close().unwrap();
2768
2769        let new_metadata = writer.close().unwrap();
2770
2771        // check that new stats match the original stats
2772        let (new_min, new_max, new_col_min, new_col_max) = retrieve_stats(&new_metadata);
2773        assert_eq!(new_min, exp_min);
2774        assert_eq!(new_max, exp_max);
2775        assert_eq!(new_col_min, exp_col_min);
2776        assert_eq!(new_col_max, exp_col_max);
2777    }
2778}