1use 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
50pub struct TrackedWrite<W: Write> {
54 inner: BufWriter<W>,
55 bytes_written: usize,
56}
57
58impl<W: Write> TrackedWrite<W> {
59 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 pub fn bytes_written(&self) -> usize {
70 self.bytes_written
71 }
72
73 pub fn inner(&self) -> &W {
75 self.inner.get_ref()
76 }
77
78 pub fn inner_mut(&mut self) -> &mut W {
83 self.inner.get_mut()
84 }
85
86 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
119pub type OnCloseColumnChunk<'a> = Box<dyn FnOnce(ColumnCloseResult) -> Result<()> + 'a>;
121
122pub 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
139pub 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: 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 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 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 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 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 #[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 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 pub fn flushed_row_groups(&self) -> &[RowGroupMetaData] {
304 &self.row_groups
305 }
306
307 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 pub fn close(mut self) -> Result<ParquetMetaData> {
321 self.finish()
322 }
323
324 #[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 #[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 fn write_metadata(&mut self) -> Result<ParquetMetaData> {
343 self.finished = true;
344
345 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 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 pub fn append_key_value_metadata(&mut self, kv_metadata: KeyValue) {
402 self.kv_metadatas.push(kv_metadata);
403 }
404
405 pub fn schema_descr(&self) -> &SchemaDescriptor {
407 &self.descr
408 }
409
410 #[cfg(feature = "arrow")]
412 pub(crate) fn schema_descr_ptr(&self) -> &SchemaDescPtr {
413 &self.descr
414 }
415
416 pub fn properties(&self) -> &WriterPropertiesPtr {
418 &self.props
419 }
420
421 pub fn inner(&self) -> &W {
423 self.buf.inner()
424 }
425
426 pub fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
435 self.buf.write_all(buf)
436 }
437
438 pub fn flush(&mut self) -> std::io::Result<()> {
440 self.buf.flush()
441 }
442
443 pub fn inner_mut(&mut self) -> &mut W {
452 self.buf.inner_mut()
453 }
454
455 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 pub fn bytes_written(&self) -> usize {
465 self.buf.bytes_written()
466 }
467
468 #[cfg(feature = "encryption")]
470 pub(crate) fn file_encryptor(&self) -> Option<Arc<FileEncryptor>> {
471 self.file_encryptor.clone()
472 }
473}
474
475fn 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 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 *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
514pub 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 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 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 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 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 *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 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 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 pub fn append_column<R: ChunkReader>(
698 &mut self,
699 reader: &R,
700 close: ColumnCloseResult,
701 ) -> Result<()> {
702 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 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 #[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 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 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 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 #[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 #[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 #[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 #[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 #[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#[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
984pub struct SerializedColumnWriter<'a> {
986 inner: ColumnWriter<'a>,
987 on_close: Option<OnCloseColumnChunk<'a>>,
988}
989
990impl<'a> SerializedColumnWriter<'a> {
991 pub fn new(inner: ColumnWriter<'a>, on_close: Option<OnCloseColumnChunk<'a>>) -> Self {
994 Self { inner, on_close }
995 }
996
997 pub fn untyped(&mut self) -> &mut ColumnWriter<'a> {
999 &mut self.inner
1000 }
1001
1002 pub fn typed<T: DataType>(&mut self) -> &mut ColumnWriterImpl<'a, T> {
1004 get_typed_column_writer_mut(&mut self.inner)
1005 }
1006
1007 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
1018pub 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 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 #[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 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#[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#[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 let expected = vec![
1346 ColumnOrder::TYPE_DEFINED_ORDER(SortOrder::SIGNED),
1348 ColumnOrder::TYPE_DEFINED_ORDER(SortOrder::UNDEFINED),
1350 ColumnOrder::IEEE_754_TOTAL_ORDER,
1352 ColumnOrder::TYPE_DEFINED_ORDER(SortOrder::UNSIGNED),
1354 ColumnOrder::IEEE_754_TOTAL_ORDER,
1356 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 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 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 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 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 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 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 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 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 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 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 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 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 for writer in column_writers {
2105 writer.close().unwrap()
2106 }
2107
2108 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 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) .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 assert!(row_group.column(0).offset_index_offset().is_some());
2182 assert!(row_group.column(0).column_index_offset().is_some());
2183 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 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 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"; 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 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)] 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 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 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 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 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 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 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)] #[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 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 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 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)] 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)] fn test_rewrite_missing_column_index() {
2655 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 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 fn retrieve_stats(metadata: &ParquetMetaData) -> (&[u8], &[u8], &Int96, &Int96) {
2712 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 let (exp_min, exp_max, exp_col_min, exp_col_max) = retrieve_stats(reader.metadata());
2745
2746 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 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 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}