1use crate::column::chunker::ContentDefinedChunker;
21
22use bytes::Bytes;
23use std::io::Write;
24use std::slice::Iter;
25use std::sync::{Arc, Mutex};
26use std::vec::IntoIter;
27
28use arrow_array::cast::AsArray;
29use arrow_array::{ArrayRef, Int32Array, RecordBatch, RecordBatchWriter};
30use arrow_array::{PrimitiveArray, types::*};
31use arrow_schema::{
32 ArrowError, DataType as ArrowDataType, Field, IntervalUnit, SchemaRef, TimeUnit,
33};
34
35use super::schema::{add_encoded_arrow_schema_to_metadata, decimal_length_from_precision};
36
37use crate::arrow::ArrowSchemaConverter;
38use crate::arrow::arrow_writer::byte_array::ByteArrayEncoder;
39use crate::basic::PageType;
40use crate::column::page::{CompressedPage, PageWriteSpec, PageWriter};
41use crate::column::page_encryption::PageEncryptor;
42use crate::column::writer::encoder::ColumnValueEncoder;
43use crate::column::writer::{
44 ColumnCloseResult, ColumnWriter, GenericColumnWriter, get_column_writer,
45};
46use crate::data_type::{ByteArray, FixedLenByteArray};
47use std::collections::HashSet;
48type DistinctValuesSet = HashSet<u64>;
49#[cfg(feature = "encryption")]
50use crate::encryption::encrypt::FileEncryptor;
51use crate::errors::{ParquetError, Result};
52use crate::file::metadata::{KeyValue, ParquetMetaData, RowGroupMetaData};
53use crate::file::properties::{WriterProperties, WriterPropertiesPtr};
54use crate::file::writer::{SerializedFileWriter, SerializedRowGroupWriter};
55use crate::parquet_thrift::{ThriftCompactOutputProtocol, WriteThrift};
56use crate::schema::types::{ColumnDescPtr, SchemaDescPtr, SchemaDescriptor};
57use levels::{ArrayLevels, calculate_array_levels};
58
59mod byte_array;
60mod levels;
61
62#[doc(inline)]
63pub use crate::column::page_store::{
64 InMemoryPageStore, InMemoryPageStoreFactory, PageKey, PageStore, PageStoreArgs,
65 PageStoreFactory,
66};
67
68pub struct ArrowWriter<W: Write> {
185 writer: SerializedFileWriter<W>,
187
188 in_progress: Option<ArrowRowGroupWriter>,
190
191 arrow_schema: SchemaRef,
195
196 row_group_writer_factory: ArrowRowGroupWriterFactory,
198
199 max_row_group_row_count: Option<usize>,
201
202 max_row_group_bytes: Option<usize>,
204
205 cdc_chunkers: Option<Vec<ContentDefinedChunker>>,
207}
208
209impl<W: Write + Send> std::fmt::Debug for ArrowWriter<W> {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 let buffered_memory = self.in_progress_size();
212 f.debug_struct("ArrowWriter")
213 .field("writer", &self.writer)
214 .field("in_progress_size", &format_args!("{buffered_memory} bytes"))
215 .field("in_progress_rows", &self.in_progress_rows())
216 .field("arrow_schema", &self.arrow_schema)
217 .field("max_row_group_row_count", &self.max_row_group_row_count)
218 .field("max_row_group_bytes", &self.max_row_group_bytes)
219 .finish()
220 }
221}
222
223impl<W: Write + Send> ArrowWriter<W> {
224 pub fn try_new(
230 writer: W,
231 arrow_schema: SchemaRef,
232 props: Option<WriterProperties>,
233 ) -> Result<Self> {
234 let options = ArrowWriterOptions::new().with_properties(props.unwrap_or_default());
235 Self::try_new_with_options(writer, arrow_schema, options)
236 }
237
238 pub fn try_new_with_options(
244 writer: W,
245 arrow_schema: SchemaRef,
246 options: ArrowWriterOptions,
247 ) -> Result<Self> {
248 let mut props = options.properties;
249
250 let schema = if let Some(parquet_schema) = options.schema_descr {
251 parquet_schema.clone()
252 } else {
253 let mut converter = ArrowSchemaConverter::new().with_coerce_types(props.coerce_types());
254 if let Some(schema_root) = &options.schema_root {
255 converter = converter.schema_root(schema_root);
256 }
257
258 converter.convert(&arrow_schema)?
259 };
260
261 if !options.skip_arrow_metadata {
262 add_encoded_arrow_schema_to_metadata(&arrow_schema, &mut props);
264 }
265
266 let max_row_group_row_count = props.max_row_group_row_count();
267 let max_row_group_bytes = props.max_row_group_bytes();
268
269 let props_ptr = Arc::new(props);
270 let file_writer =
271 SerializedFileWriter::new(writer, schema.root_schema_ptr(), Arc::clone(&props_ptr))?;
272
273 let mut row_group_writer_factory =
274 ArrowRowGroupWriterFactory::new(&file_writer, arrow_schema.clone());
275 if let Some(page_store_factory) = options.page_store_factory {
276 row_group_writer_factory =
277 row_group_writer_factory.with_page_store_factory(page_store_factory);
278 }
279
280 let cdc_chunkers = props_ptr
281 .content_defined_chunking()
282 .map(|opts| {
283 file_writer
284 .schema_descr()
285 .columns()
286 .iter()
287 .map(|desc| ContentDefinedChunker::new(desc, opts))
288 .collect::<Result<Vec<_>>>()
289 })
290 .transpose()?;
291
292 Ok(Self {
293 writer: file_writer,
294 in_progress: None,
295 arrow_schema,
296 row_group_writer_factory,
297 max_row_group_row_count,
298 max_row_group_bytes,
299 cdc_chunkers,
300 })
301 }
302
303 pub fn flushed_row_groups(&self) -> &[RowGroupMetaData] {
305 self.writer.flushed_row_groups()
306 }
307
308 pub fn memory_size(&self) -> usize {
313 match &self.in_progress {
314 Some(in_progress) => in_progress.writers.iter().map(|x| x.memory_size()).sum(),
315 None => 0,
316 }
317 }
318
319 pub fn in_progress_size(&self) -> usize {
326 match &self.in_progress {
327 Some(in_progress) => in_progress
328 .writers
329 .iter()
330 .map(|x| x.get_estimated_total_bytes())
331 .sum(),
332 None => 0,
333 }
334 }
335
336 pub fn in_progress_rows(&self) -> usize {
338 self.in_progress
339 .as_ref()
340 .map(|x| x.buffered_rows)
341 .unwrap_or_default()
342 }
343
344 pub fn bytes_written(&self) -> usize {
346 self.writer.bytes_written()
347 }
348
349 pub fn write(&mut self, batch: &RecordBatch) -> Result<()> {
361 if batch.num_rows() == 0 {
362 return Ok(());
363 }
364
365 let mut remaining = batch.clone();
368
369 loop {
370 let in_progress = match &mut self.in_progress {
371 Some(in_progress) => in_progress,
372 x => x.insert(
373 self.row_group_writer_factory
374 .create_row_group_writer(self.writer.flushed_row_groups().len())?,
375 ),
376 };
377 let buffered_rows = in_progress.buffered_rows;
378
379 let mut split_at = match self.max_row_group_row_count {
382 Some(max_rows) if buffered_rows + remaining.num_rows() > max_rows => {
383 Some(max_rows - buffered_rows)
384 }
385 _ => None,
386 };
387
388 let candidate_rows = split_at.unwrap_or_else(|| remaining.num_rows());
393
394 if let Some(max_bytes) = self.max_row_group_bytes
395 && buffered_rows > 0
396 {
397 let current_bytes = in_progress.get_estimated_total_bytes();
398
399 if current_bytes >= max_bytes {
400 self.flush()?;
401 continue;
402 }
403
404 if let Some(avg_row_bytes) = current_bytes
405 .checked_div(buffered_rows)
406 .filter(|avg_row_bytes| *avg_row_bytes > 0)
407 {
408 let remaining_bytes = max_bytes - current_bytes;
410 let rows_that_fit = remaining_bytes.checked_div(avg_row_bytes).unwrap_or(0);
411
412 if candidate_rows > rows_that_fit {
413 if rows_that_fit > 0 {
414 split_at = Some(rows_that_fit);
415 } else {
416 self.flush()?;
417 continue;
418 }
419 }
420 }
421 }
422
423 let rest = split_at.map(|to_write| {
424 let rest = remaining.slice(to_write, remaining.num_rows() - to_write);
425 remaining = remaining.slice(0, to_write);
426 rest
427 });
428
429 let in_progress = self.in_progress.as_mut().unwrap();
430 match self.cdc_chunkers.as_mut() {
431 Some(chunkers) => in_progress.write_with_chunkers(&remaining, chunkers)?,
432 None => in_progress.write(&remaining)?,
433 }
434
435 let should_flush = self
436 .max_row_group_row_count
437 .is_some_and(|max| in_progress.buffered_rows >= max)
438 || self
439 .max_row_group_bytes
440 .is_some_and(|max| in_progress.get_estimated_total_bytes() >= max);
441
442 if should_flush {
443 self.flush()?
444 }
445
446 match rest {
447 Some(rest) => remaining = rest,
448 None => return Ok(()),
449 }
450 }
451 }
452
453 pub fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
458 self.writer.write_all(buf)
459 }
460
461 pub fn sync(&mut self) -> std::io::Result<()> {
463 self.writer.flush()
464 }
465
466 pub fn flush(&mut self) -> Result<()> {
471 let Some(in_progress) = self.in_progress.take() else {
472 return Ok(());
473 };
474
475 let mut row_group_writer = self.writer.next_row_group()?;
476 for chunk in in_progress.close()? {
477 chunk.append_to_row_group(&mut row_group_writer)?;
478 }
479 row_group_writer.close()?;
480 Ok(())
481 }
482
483 pub fn append_key_value_metadata(&mut self, kv_metadata: KeyValue) {
487 self.writer.append_key_value_metadata(kv_metadata)
488 }
489
490 pub fn inner(&self) -> &W {
492 self.writer.inner()
493 }
494
495 pub fn inner_mut(&mut self) -> &mut W {
504 self.writer.inner_mut()
505 }
506
507 pub fn into_inner(mut self) -> Result<W> {
509 self.flush()?;
510 self.writer.into_inner()
511 }
512
513 pub fn finish(&mut self) -> Result<ParquetMetaData> {
519 self.flush()?;
520 self.writer.finish()
521 }
522
523 pub fn close(mut self) -> Result<ParquetMetaData> {
525 self.finish()
526 }
527
528 pub fn into_serialized_writer(
535 mut self,
536 ) -> Result<(SerializedFileWriter<W>, ArrowRowGroupWriterFactory)> {
537 self.flush()?;
538 Ok((self.writer, self.row_group_writer_factory))
539 }
540}
541
542impl<W: Write + Send> RecordBatchWriter for ArrowWriter<W> {
543 fn write(&mut self, batch: &RecordBatch) -> Result<(), ArrowError> {
544 self.write(batch).map_err(|e| e.into())
545 }
546
547 fn close(self) -> std::result::Result<(), ArrowError> {
548 self.close()?;
549 Ok(())
550 }
551}
552
553#[derive(Debug, Clone, Default)]
557pub struct ArrowWriterOptions {
558 properties: WriterProperties,
559 skip_arrow_metadata: bool,
560 schema_root: Option<String>,
561 schema_descr: Option<SchemaDescriptor>,
562 page_store_factory: Option<Arc<dyn PageStoreFactory>>,
563}
564
565impl ArrowWriterOptions {
566 pub fn new() -> Self {
568 Self::default()
569 }
570
571 pub fn with_properties(self, properties: WriterProperties) -> Self {
573 Self { properties, ..self }
574 }
575
576 pub fn with_page_store_factory(self, page_store_factory: Arc<dyn PageStoreFactory>) -> Self {
662 Self {
663 page_store_factory: Some(page_store_factory),
664 ..self
665 }
666 }
667
668 pub fn with_skip_arrow_metadata(self, skip_arrow_metadata: bool) -> Self {
675 Self {
676 skip_arrow_metadata,
677 ..self
678 }
679 }
680
681 pub fn with_schema_root(self, schema_root: String) -> Self {
683 Self {
684 schema_root: Some(schema_root),
685 ..self
686 }
687 }
688
689 pub fn with_parquet_schema(self, schema_descr: SchemaDescriptor) -> Self {
695 Self {
696 schema_descr: Some(schema_descr),
697 ..self
698 }
699 }
700}
701
702struct ArrowColumnChunkData {
708 length: usize,
709 store: Box<dyn PageStore>,
710 keys: Vec<PageKey>,
711 dictionary_keys: Vec<PageKey>,
722 dictionary_len: usize,
726}
727
728impl ArrowColumnChunkData {
729 fn new(store: Box<dyn PageStore>) -> Self {
730 Self {
731 length: 0,
732 store,
733 keys: Vec::new(),
734 dictionary_keys: Vec::new(),
735 dictionary_len: 0,
736 }
737 }
738
739 fn push(&mut self, value: Bytes) -> Result<()> {
742 let key = self.store.put(value)?;
743 self.keys.push(key);
744 Ok(())
745 }
746
747 fn push_dictionary(&mut self, value: Bytes) -> Result<()> {
751 self.dictionary_len += value.len();
752 let key = self.store.put(value)?;
753 self.dictionary_keys.push(key);
754 Ok(())
755 }
756
757 fn memory_size(&self) -> usize {
760 self.store.memory_size()
761 }
762}
763
764struct StreamingColumnChunkPages {
773 store: Box<dyn PageStore>,
774 keys: IntoIter<PageKey>,
777}
778
779impl StreamingColumnChunkPages {
780 fn new(data: ArrowColumnChunkData) -> Self {
781 let keys = if data.dictionary_keys.is_empty() {
784 data.keys
785 } else {
786 let mut keys = Vec::with_capacity(data.dictionary_keys.len() + data.keys.len());
787 keys.extend(data.dictionary_keys);
788 keys.extend(data.keys);
789 keys
790 };
791 Self {
792 store: data.store,
793 keys: keys.into_iter(),
794 }
795 }
796}
797
798impl Iterator for StreamingColumnChunkPages {
799 type Item = Result<Bytes>;
800
801 fn next(&mut self) -> Option<Self::Item> {
802 let key = self.keys.next()?;
803 Some(self.store.take(key))
804 }
805}
806
807type SharedColumnChunk = Arc<Mutex<ArrowColumnChunkData>>;
812
813struct ArrowPageWriter {
814 buffer: SharedColumnChunk,
815 #[cfg(feature = "encryption")]
816 page_encryptor: Option<PageEncryptor>,
817}
818
819impl ArrowPageWriter {
820 fn new(store: Box<dyn PageStore>) -> Self {
822 Self {
823 buffer: Arc::new(Mutex::new(ArrowColumnChunkData::new(store))),
824 #[cfg(feature = "encryption")]
825 page_encryptor: None,
826 }
827 }
828
829 #[cfg(feature = "encryption")]
830 pub fn with_encryptor(mut self, page_encryptor: Option<PageEncryptor>) -> Self {
831 self.page_encryptor = page_encryptor;
832 self
833 }
834
835 #[cfg(feature = "encryption")]
836 fn page_encryptor_mut(&mut self) -> Option<&mut PageEncryptor> {
837 self.page_encryptor.as_mut()
838 }
839
840 #[cfg(not(feature = "encryption"))]
843 #[expect(
844 clippy::needless_pass_by_ref_mut,
845 reason = "mirrors the encryption-enabled signature"
846 )]
847 fn page_encryptor_mut(&mut self) -> Option<&mut PageEncryptor> {
848 None
849 }
850}
851
852impl PageWriter for ArrowPageWriter {
853 fn write_page(&mut self, page: CompressedPage) -> Result<PageWriteSpec> {
854 let page = match self.page_encryptor_mut() {
855 Some(page_encryptor) => page_encryptor.encrypt_compressed_page(page)?,
856 None => page,
857 };
858
859 let page_header = page.to_thrift_header()?;
860 let header = {
861 let mut header = Vec::with_capacity(1024);
862
863 match self.page_encryptor_mut() {
864 Some(page_encryptor) => {
865 page_encryptor.encrypt_page_header(&page_header, &mut header)?;
866 if page.compressed_page().is_data_page() {
867 page_encryptor.increment_page();
868 }
869 }
870 None => {
871 let mut protocol = ThriftCompactOutputProtocol::new(&mut header);
872 page_header.write_thrift(&mut protocol)?;
873 }
874 }
875
876 Bytes::from(header)
877 };
878
879 let mut buf = self.buffer.try_lock().unwrap();
880
881 let data = page.compressed_page().buffer().clone();
882 let compressed_size = data.len() + header.len();
883
884 let mut spec = PageWriteSpec::new();
885 spec.page_type = page.page_type();
886 spec.num_values = page.num_values();
887 spec.uncompressed_size = page.uncompressed_size() + header.len();
888 spec.offset = buf.length as u64;
889 spec.compressed_size = compressed_size;
890 spec.bytes_written = compressed_size as u64;
891
892 buf.length += compressed_size;
893 if spec.page_type == PageType::DICTIONARY_PAGE {
894 buf.push_dictionary(header)?;
897 buf.push_dictionary(data)?;
898 } else {
899 buf.push(header)?;
900 buf.push(data)?;
901 }
902
903 Ok(spec)
904 }
905
906 fn defers_dictionary_ordering(&self) -> bool {
907 true
912 }
913
914 fn buffered_memory_size(&self) -> usize {
915 self.buffer.try_lock().unwrap().memory_size()
918 }
919
920 fn close(&mut self) -> Result<()> {
921 Ok(())
922 }
923}
924
925#[derive(Debug)]
927pub struct ArrowLeafColumn(ArrayLevels);
928
929pub fn compute_leaves(field: &Field, array: &ArrayRef) -> Result<Vec<ArrowLeafColumn>> {
934 let levels = calculate_array_levels(array, field)?;
935 Ok(levels.into_iter().map(ArrowLeafColumn).collect())
936}
937
938pub struct ArrowColumnChunk {
940 data: ArrowColumnChunkData,
941 close: ColumnCloseResult,
942}
943
944impl std::fmt::Debug for ArrowColumnChunk {
945 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
946 f.debug_struct("ArrowColumnChunk")
947 .field("length", &self.data.length)
948 .finish_non_exhaustive()
949 }
950}
951
952impl ArrowColumnChunk {
953 pub fn close(&self) -> &ColumnCloseResult {
960 &self.close
961 }
962
963 pub fn close_mut(&mut self) -> &mut ColumnCloseResult {
970 &mut self.close
971 }
972
973 pub fn append_to_row_group<W: Write + Send>(
976 self,
977 writer: &mut SerializedRowGroupWriter<'_, W>,
978 ) -> Result<()> {
979 let ArrowColumnChunk { data, close } = self;
980
981 let close = close.update_dictionary_location(data.dictionary_len)?;
985
986 let pages = StreamingColumnChunkPages::new(data);
987 writer.append_column_from_pages(pages, close)
988 }
989}
990
991pub struct ArrowColumnWriter {
1089 writer: ArrowColumnWriterImpl,
1090 chunk: SharedColumnChunk,
1091 distinct_values_seen: Option<DistinctValuesSet>,
1094}
1095
1096impl std::fmt::Debug for ArrowColumnWriter {
1097 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1098 f.debug_struct("ArrowColumnWriter").finish_non_exhaustive()
1099 }
1100}
1101
1102enum ArrowColumnWriterImpl {
1103 ByteArray(GenericColumnWriter<'static, ByteArrayEncoder>),
1104 Column(ColumnWriter<'static>),
1105}
1106
1107impl ArrowColumnWriter {
1108 pub fn write(&mut self, col: &ArrowLeafColumn) -> Result<()> {
1110 self.write_internal(&col.0)
1111 }
1112
1113 fn write_with_chunker(
1115 &mut self,
1116 col: &ArrowLeafColumn,
1117 chunker: &mut ContentDefinedChunker,
1118 ) -> Result<()> {
1119 let levels = &col.0;
1120 let chunks = chunker.get_arrow_chunks(
1121 levels.def_level_data().as_ref(),
1122 levels.rep_level_data().as_ref(),
1123 levels.array(),
1124 )?;
1125
1126 let num_chunks = chunks.len();
1127 for (i, chunk) in chunks.iter().enumerate() {
1128 let chunk_levels = levels.slice_for_chunk(chunk);
1129 self.write_internal(&chunk_levels)?;
1130
1131 if i + 1 < num_chunks {
1133 match &mut self.writer {
1134 ArrowColumnWriterImpl::Column(c) => c.add_data_page()?,
1135 ArrowColumnWriterImpl::ByteArray(c) => c.add_data_page()?,
1136 }
1137 }
1138 }
1139 Ok(())
1140 }
1141
1142 fn write_internal(&mut self, levels: &ArrayLevels) -> Result<()> {
1143 if let Some(seen) = &mut self.distinct_values_seen {
1144 let array = levels.array();
1145 let non_null = levels.non_null_indices();
1146 match array.as_any_dictionary_opt() {
1147 Some(dict) => {
1148 let values = dict.values();
1151 let keys = dict.normalized_keys();
1152 let referenced_value_indices: Vec<usize> = non_null
1153 .iter()
1154 .map(|&pos| keys[pos])
1155 .filter(|&val_idx| values.is_valid(val_idx))
1156 .collect();
1157 update_distinct_values_seen(values.as_ref(), &referenced_value_indices, seen);
1158 }
1159 None => update_distinct_values_seen(array.as_ref(), non_null, seen),
1161 }
1162 }
1163
1164 match &mut self.writer {
1165 ArrowColumnWriterImpl::Column(c) => {
1166 let leaf = levels.array();
1167 match leaf.as_any_dictionary_opt() {
1168 Some(dictionary) => {
1169 let materialized =
1170 arrow_select::take::take(dictionary.values(), dictionary.keys(), None)?;
1171 write_leaf(c, &materialized, levels)?
1172 }
1173 None => write_leaf(c, leaf, levels)?,
1174 };
1175 }
1176 ArrowColumnWriterImpl::ByteArray(c) => {
1177 write_primitive(c, levels.array().as_ref(), levels)?;
1178 }
1179 }
1180 Ok(())
1181 }
1182
1183 pub fn close(self) -> Result<ArrowColumnChunk> {
1190 let distinct_count = self
1191 .distinct_values_seen
1192 .as_ref()
1193 .filter(|s| !s.is_empty())
1194 .map(|s| s.len() as u64);
1195 let close = match self.writer {
1196 ArrowColumnWriterImpl::ByteArray(mut c) => {
1197 if let Some(count) = distinct_count {
1198 c.set_distinct_count_override(count);
1199 }
1200 c.close()?
1201 }
1202 ArrowColumnWriterImpl::Column(mut c) => {
1203 if let Some(count) = distinct_count {
1204 c.set_distinct_count_override(count);
1205 }
1206 c.close()?
1207 }
1208 };
1209 let chunk = Arc::try_unwrap(self.chunk)
1211 .map_err(|_| general_err!("Internal Error: the column chunk is still shared"))?;
1212 let data = chunk
1213 .into_inner()
1214 .map_err(|_| general_err!("The column chunk lock is poisoned"))?;
1215 Ok(ArrowColumnChunk { data, close })
1216 }
1217
1218 pub fn memory_size(&self) -> usize {
1229 match &self.writer {
1230 ArrowColumnWriterImpl::ByteArray(c) => c.memory_size(),
1231 ArrowColumnWriterImpl::Column(c) => c.memory_size(),
1232 }
1233 }
1234
1235 pub fn get_estimated_total_bytes(&self) -> usize {
1243 match &self.writer {
1244 ArrowColumnWriterImpl::ByteArray(c) => c.get_estimated_total_bytes() as _,
1245 ArrowColumnWriterImpl::Column(c) => c.get_estimated_total_bytes() as _,
1246 }
1247 }
1248}
1249
1250#[derive(Debug)]
1257struct ArrowRowGroupWriter {
1258 writers: Vec<ArrowColumnWriter>,
1259 schema: SchemaRef,
1260 buffered_rows: usize,
1261}
1262
1263impl ArrowRowGroupWriter {
1264 fn new(writers: Vec<ArrowColumnWriter>, arrow: &SchemaRef) -> Self {
1265 Self {
1266 writers,
1267 schema: arrow.clone(),
1268 buffered_rows: 0,
1269 }
1270 }
1271
1272 fn write(&mut self, batch: &RecordBatch) -> Result<()> {
1273 self.buffered_rows += batch.num_rows();
1274 let mut writers = self.writers.iter_mut();
1275 for (field, column) in self.schema.fields().iter().zip(batch.columns()) {
1276 for leaf in compute_leaves(field.as_ref(), column)? {
1277 writers.next().unwrap().write(&leaf)?;
1278 }
1279 }
1280 Ok(())
1281 }
1282
1283 fn write_with_chunkers(
1284 &mut self,
1285 batch: &RecordBatch,
1286 chunkers: &mut [ContentDefinedChunker],
1287 ) -> Result<()> {
1288 self.buffered_rows += batch.num_rows();
1289 let mut writers = self.writers.iter_mut();
1290 let mut chunkers = chunkers.iter_mut();
1291 for (field, column) in self.schema.fields().iter().zip(batch.columns()) {
1292 for leaf in compute_leaves(field.as_ref(), column)? {
1293 writers
1294 .next()
1295 .unwrap()
1296 .write_with_chunker(&leaf, chunkers.next().unwrap())?;
1297 }
1298 }
1299 Ok(())
1300 }
1301
1302 fn get_estimated_total_bytes(&self) -> usize {
1304 self.writers
1305 .iter()
1306 .map(|x| x.get_estimated_total_bytes())
1307 .sum()
1308 }
1309
1310 fn close(self) -> Result<Vec<ArrowColumnChunk>> {
1311 self.writers
1312 .into_iter()
1313 .map(|writer| writer.close())
1314 .collect()
1315 }
1316}
1317
1318#[derive(Debug)]
1323pub struct ArrowRowGroupWriterFactory {
1324 schema: SchemaDescPtr,
1325 arrow_schema: SchemaRef,
1326 props: WriterPropertiesPtr,
1327 page_store_factory: Arc<dyn PageStoreFactory>,
1328 #[cfg(feature = "encryption")]
1329 file_encryptor: Option<Arc<FileEncryptor>>,
1330}
1331
1332impl ArrowRowGroupWriterFactory {
1333 pub fn new<W: Write + Send>(
1335 file_writer: &SerializedFileWriter<W>,
1336 arrow_schema: SchemaRef,
1337 ) -> Self {
1338 let schema = Arc::clone(file_writer.schema_descr_ptr());
1339 let props = Arc::clone(file_writer.properties());
1340 Self {
1341 schema,
1342 arrow_schema,
1343 props,
1344 page_store_factory: Arc::new(InMemoryPageStoreFactory),
1345 #[cfg(feature = "encryption")]
1346 file_encryptor: file_writer.file_encryptor(),
1347 }
1348 }
1349
1350 pub fn with_page_store_factory(
1354 mut self,
1355 page_store_factory: Arc<dyn PageStoreFactory>,
1356 ) -> Self {
1357 self.page_store_factory = page_store_factory;
1358 self
1359 }
1360
1361 fn create_row_group_writer(&self, row_group_index: usize) -> Result<ArrowRowGroupWriter> {
1362 let writers = self.create_column_writers(row_group_index)?;
1363 Ok(ArrowRowGroupWriter::new(writers, &self.arrow_schema))
1364 }
1365
1366 pub fn create_column_writers(&self, row_group_index: usize) -> Result<Vec<ArrowColumnWriter>> {
1368 let mut writers = Vec::with_capacity(self.arrow_schema.fields.len());
1369 let mut leaves = self.schema.columns().iter();
1370 let column_factory = self.column_writer_factory(row_group_index);
1371 for field in &self.arrow_schema.fields {
1372 column_factory.get_arrow_column_writer(
1373 field.data_type(),
1374 &self.props,
1375 &mut leaves,
1376 &mut writers,
1377 )?;
1378 }
1379 Ok(writers)
1380 }
1381
1382 #[cfg(feature = "encryption")]
1383 fn column_writer_factory(&self, row_group_idx: usize) -> ArrowColumnWriterFactory {
1384 ArrowColumnWriterFactory::new()
1385 .with_page_store_factory(self.page_store_factory.clone())
1386 .with_file_encryptor(row_group_idx, self.file_encryptor.clone())
1387 }
1388
1389 #[cfg(not(feature = "encryption"))]
1390 fn column_writer_factory(&self, _row_group_idx: usize) -> ArrowColumnWriterFactory {
1391 ArrowColumnWriterFactory::new().with_page_store_factory(self.page_store_factory.clone())
1392 }
1393}
1394
1395struct ArrowColumnWriterFactory {
1397 page_store_factory: Arc<dyn PageStoreFactory>,
1399 #[cfg(feature = "encryption")]
1400 row_group_index: usize,
1401 #[cfg(feature = "encryption")]
1402 file_encryptor: Option<Arc<FileEncryptor>>,
1403}
1404
1405impl ArrowColumnWriterFactory {
1406 pub fn new() -> Self {
1407 Self {
1408 page_store_factory: Arc::new(InMemoryPageStoreFactory),
1409 #[cfg(feature = "encryption")]
1410 row_group_index: 0,
1411 #[cfg(feature = "encryption")]
1412 file_encryptor: None,
1413 }
1414 }
1415
1416 pub fn with_page_store_factory(
1418 mut self,
1419 page_store_factory: Arc<dyn PageStoreFactory>,
1420 ) -> Self {
1421 self.page_store_factory = page_store_factory;
1422 self
1423 }
1424
1425 #[cfg(feature = "encryption")]
1426 pub fn with_file_encryptor(
1427 mut self,
1428 row_group_index: usize,
1429 file_encryptor: Option<Arc<FileEncryptor>>,
1430 ) -> Self {
1431 self.row_group_index = row_group_index;
1432 self.file_encryptor = file_encryptor;
1433 self
1434 }
1435
1436 #[cfg(feature = "encryption")]
1437 fn create_page_writer(
1438 &self,
1439 column_descriptor: &ColumnDescPtr,
1440 column_index: usize,
1441 ) -> Result<Box<ArrowPageWriter>> {
1442 let column_path = column_descriptor.path().string();
1443 let page_encryptor = PageEncryptor::create_if_column_encrypted(
1444 self.file_encryptor.as_ref(),
1445 self.row_group_index,
1446 column_index,
1447 &column_path,
1448 )?;
1449 let args = PageStoreArgs::new(column_index, column_descriptor);
1450 let store = self.page_store_factory.create(&args)?;
1451 Ok(Box::new(
1452 ArrowPageWriter::new(store).with_encryptor(page_encryptor),
1453 ))
1454 }
1455
1456 #[cfg(not(feature = "encryption"))]
1457 fn create_page_writer(
1458 &self,
1459 column_descriptor: &ColumnDescPtr,
1460 column_index: usize,
1461 ) -> Result<Box<ArrowPageWriter>> {
1462 let args = PageStoreArgs::new(column_index, column_descriptor);
1463 let store = self.page_store_factory.create(&args)?;
1464 Ok(Box::new(ArrowPageWriter::new(store)))
1465 }
1466
1467 fn get_arrow_column_writer(
1470 &self,
1471 data_type: &ArrowDataType,
1472 props: &WriterPropertiesPtr,
1473 leaves: &mut Iter<'_, ColumnDescPtr>,
1474 out: &mut Vec<ArrowColumnWriter>,
1475 ) -> Result<()> {
1476 let write_distinct_values = props.write_row_group_number_distinct_values();
1477
1478 let col = |desc: &ColumnDescPtr| -> Result<ArrowColumnWriter> {
1480 let page_writer = self.create_page_writer(desc, out.len())?;
1481 let chunk = page_writer.buffer.clone();
1482 let writer = get_column_writer(desc.clone(), props.clone(), page_writer);
1483 Ok(ArrowColumnWriter {
1484 chunk,
1485 writer: ArrowColumnWriterImpl::Column(writer),
1486 distinct_values_seen: write_distinct_values.then(HashSet::new),
1487 })
1488 };
1489
1490 let bytes = |desc: &ColumnDescPtr| -> Result<ArrowColumnWriter> {
1492 let page_writer = self.create_page_writer(desc, out.len())?;
1493 let chunk = page_writer.buffer.clone();
1494 let writer = GenericColumnWriter::new(desc.clone(), props.clone(), page_writer);
1495 Ok(ArrowColumnWriter {
1496 chunk,
1497 writer: ArrowColumnWriterImpl::ByteArray(writer),
1498 distinct_values_seen: write_distinct_values.then(HashSet::new),
1499 })
1500 };
1501
1502 match data_type {
1503 _ if data_type.is_primitive() => out.push(col(leaves.next().unwrap())?),
1504 ArrowDataType::FixedSizeBinary(_) | ArrowDataType::Boolean | ArrowDataType::Null => {
1505 out.push(col(leaves.next().unwrap())?)
1506 }
1507 ArrowDataType::LargeBinary
1508 | ArrowDataType::Binary
1509 | ArrowDataType::Utf8
1510 | ArrowDataType::LargeUtf8
1511 | ArrowDataType::BinaryView
1512 | ArrowDataType::Utf8View => out.push(bytes(leaves.next().unwrap())?),
1513 ArrowDataType::List(f)
1514 | ArrowDataType::LargeList(f)
1515 | ArrowDataType::FixedSizeList(f, _)
1516 | ArrowDataType::ListView(f)
1517 | ArrowDataType::LargeListView(f) => {
1518 self.get_arrow_column_writer(f.data_type(), props, leaves, out)?
1519 }
1520 ArrowDataType::Struct(fields) => {
1521 for field in fields {
1522 self.get_arrow_column_writer(field.data_type(), props, leaves, out)?
1523 }
1524 }
1525 ArrowDataType::Map(f, _) => match f.data_type() {
1526 ArrowDataType::Struct(f) => {
1527 self.get_arrow_column_writer(f[0].data_type(), props, leaves, out)?;
1528 self.get_arrow_column_writer(f[1].data_type(), props, leaves, out)?
1529 }
1530 _ => unreachable!("invalid map type"),
1531 },
1532 ArrowDataType::Dictionary(_, value_type) => match value_type.as_ref() {
1533 ArrowDataType::Utf8
1534 | ArrowDataType::LargeUtf8
1535 | ArrowDataType::Binary
1536 | ArrowDataType::LargeBinary => out.push(bytes(leaves.next().unwrap())?),
1537 ArrowDataType::Utf8View | ArrowDataType::BinaryView => {
1538 out.push(bytes(leaves.next().unwrap())?)
1539 }
1540 ArrowDataType::FixedSizeBinary(_) => out.push(bytes(leaves.next().unwrap())?),
1541 _ => out.push(col(leaves.next().unwrap())?),
1542 },
1543 ArrowDataType::RunEndEncoded(_, value_field) => {
1544 self.get_arrow_column_writer(value_field.data_type(), props, leaves, out)?
1545 }
1546 _ => {
1547 return Err(ParquetError::NYI(format!(
1548 "Attempting to write an Arrow type {data_type} to parquet that is not yet implemented"
1549 )));
1550 }
1551 }
1552 Ok(())
1553 }
1554}
1555
1556fn write_leaf(
1557 writer: &mut ColumnWriter<'_>,
1558 column: &dyn arrow_array::Array,
1559 levels: &ArrayLevels,
1560) -> Result<usize> {
1561 let indices = levels.non_null_indices();
1562
1563 match writer {
1564 ColumnWriter::Int32ColumnWriter(typed) => {
1566 match column.data_type() {
1567 ArrowDataType::Null => {
1568 let array = Int32Array::new_null(column.len());
1569 write_primitive(typed, array.values(), levels)
1570 }
1571 ArrowDataType::Int8 => {
1572 let array: Int32Array = column.as_primitive::<Int8Type>().unary(|x| x as i32);
1573 write_primitive(typed, array.values(), levels)
1574 }
1575 ArrowDataType::Int16 => {
1576 let array: Int32Array = column.as_primitive::<Int16Type>().unary(|x| x as i32);
1577 write_primitive(typed, array.values(), levels)
1578 }
1579 ArrowDataType::Int32 => {
1580 write_primitive(typed, column.as_primitive::<Int32Type>().values(), levels)
1581 }
1582 ArrowDataType::UInt8 => {
1583 let array: Int32Array = column.as_primitive::<UInt8Type>().unary(|x| x as i32);
1584 write_primitive(typed, array.values(), levels)
1585 }
1586 ArrowDataType::UInt16 => {
1587 let array: Int32Array = column.as_primitive::<UInt16Type>().unary(|x| x as i32);
1588 write_primitive(typed, array.values(), levels)
1589 }
1590 ArrowDataType::UInt32 => {
1591 let array = column.as_primitive::<UInt32Type>();
1594 write_primitive(typed, array.values().inner().typed_data(), levels)
1595 }
1596 ArrowDataType::Date32 => {
1597 let array = column.as_primitive::<Date32Type>();
1598 write_primitive(typed, array.values(), levels)
1599 }
1600 ArrowDataType::Time32(TimeUnit::Second) => {
1601 let array = column.as_primitive::<Time32SecondType>();
1602 write_primitive(typed, array.values(), levels)
1603 }
1604 ArrowDataType::Time32(TimeUnit::Millisecond) => {
1605 let array = column.as_primitive::<Time32MillisecondType>();
1606 write_primitive(typed, array.values(), levels)
1607 }
1608 ArrowDataType::Date64 => {
1609 let array: Int32Array = column
1611 .as_primitive::<Date64Type>()
1612 .unary(|x| (x / 86_400_000) as _);
1613
1614 write_primitive(typed, array.values(), levels)
1615 }
1616 ArrowDataType::Decimal32(_, _) => {
1617 let array = column
1618 .as_primitive::<Decimal32Type>()
1619 .unary::<_, Int32Type>(|v| v);
1620 write_primitive(typed, array.values(), levels)
1621 }
1622 ArrowDataType::Decimal64(_, _) => {
1623 let array = column
1625 .as_primitive::<Decimal64Type>()
1626 .unary::<_, Int32Type>(|v| v as i32);
1627 write_primitive(typed, array.values(), levels)
1628 }
1629 ArrowDataType::Decimal128(_, _) => {
1630 let array = column
1632 .as_primitive::<Decimal128Type>()
1633 .unary::<_, Int32Type>(|v| v as i32);
1634 write_primitive(typed, array.values(), levels)
1635 }
1636 ArrowDataType::Decimal256(_, _) => {
1637 let array = column
1639 .as_primitive::<Decimal256Type>()
1640 .unary::<_, Int32Type>(|v| v.as_i128() as i32);
1641 write_primitive(typed, array.values(), levels)
1642 }
1643 d => Err(ParquetError::General(format!("Cannot coerce {d} to I32"))),
1644 }
1645 }
1646 ColumnWriter::BoolColumnWriter(typed) => {
1647 let array = column.as_boolean();
1648 let values = get_bool_array_slice(array, indices.iter().copied());
1649 typed.write_batch_internal(
1650 values.as_slice(),
1651 None,
1652 levels.def_level_data().as_ref(),
1653 levels.rep_level_data().as_ref(),
1654 None,
1655 None,
1656 None,
1657 )
1658 }
1659 ColumnWriter::Int64ColumnWriter(typed) => {
1660 match column.data_type() {
1661 ArrowDataType::Date64 => {
1662 let array = column
1663 .as_primitive::<Date64Type>()
1664 .reinterpret_cast::<Int64Type>();
1665
1666 write_primitive(typed, array.values(), levels)
1667 }
1668 ArrowDataType::Int64 => {
1669 let array = column.as_primitive::<Int64Type>();
1670 write_primitive(typed, array.values(), levels)
1671 }
1672 ArrowDataType::UInt64 => {
1673 let values = column.as_primitive::<UInt64Type>().values();
1674 let array = values.inner().typed_data::<i64>();
1677 write_primitive(typed, array, levels)
1678 }
1679 ArrowDataType::Time64(TimeUnit::Microsecond) => {
1680 let array = column.as_primitive::<Time64MicrosecondType>();
1681 write_primitive(typed, array.values(), levels)
1682 }
1683 ArrowDataType::Time64(TimeUnit::Nanosecond) => {
1684 let array = column.as_primitive::<Time64NanosecondType>();
1685 write_primitive(typed, array.values(), levels)
1686 }
1687 ArrowDataType::Timestamp(unit, _) => match unit {
1688 TimeUnit::Second => {
1689 let array = column.as_primitive::<TimestampSecondType>();
1690 write_primitive(typed, array.values(), levels)
1691 }
1692 TimeUnit::Millisecond => {
1693 let array = column.as_primitive::<TimestampMillisecondType>();
1694 write_primitive(typed, array.values(), levels)
1695 }
1696 TimeUnit::Microsecond => {
1697 let array = column.as_primitive::<TimestampMicrosecondType>();
1698 write_primitive(typed, array.values(), levels)
1699 }
1700 TimeUnit::Nanosecond => {
1701 let array = column.as_primitive::<TimestampNanosecondType>();
1702 write_primitive(typed, array.values(), levels)
1703 }
1704 },
1705 ArrowDataType::Duration(unit) => match unit {
1706 TimeUnit::Second => {
1707 let array = column.as_primitive::<DurationSecondType>();
1708 write_primitive(typed, array.values(), levels)
1709 }
1710 TimeUnit::Millisecond => {
1711 let array = column.as_primitive::<DurationMillisecondType>();
1712 write_primitive(typed, array.values(), levels)
1713 }
1714 TimeUnit::Microsecond => {
1715 let array = column.as_primitive::<DurationMicrosecondType>();
1716 write_primitive(typed, array.values(), levels)
1717 }
1718 TimeUnit::Nanosecond => {
1719 let array = column.as_primitive::<DurationNanosecondType>();
1720 write_primitive(typed, array.values(), levels)
1721 }
1722 },
1723 ArrowDataType::Decimal64(_, _) => {
1724 let array = column
1725 .as_primitive::<Decimal64Type>()
1726 .reinterpret_cast::<Int64Type>();
1727 write_primitive(typed, array.values(), levels)
1728 }
1729 ArrowDataType::Decimal128(_, _) => {
1730 let array = column
1732 .as_primitive::<Decimal128Type>()
1733 .unary::<_, Int64Type>(|v| v as i64);
1734 write_primitive(typed, array.values(), levels)
1735 }
1736 ArrowDataType::Decimal256(_, _) => {
1737 let array = column
1739 .as_primitive::<Decimal256Type>()
1740 .unary::<_, Int64Type>(|v| v.as_i128() as i64);
1741 write_primitive(typed, array.values(), levels)
1742 }
1743 d => Err(ParquetError::General(format!("Cannot coerce {d} to I64"))),
1744 }
1745 }
1746 ColumnWriter::Int96ColumnWriter(_typed) => {
1747 unreachable!("Currently unreachable because data type not supported")
1748 }
1749 ColumnWriter::FloatColumnWriter(typed) => {
1750 let array = column.as_primitive::<Float32Type>();
1751 write_primitive(typed, array.values(), levels)
1752 }
1753 ColumnWriter::DoubleColumnWriter(typed) => {
1754 let array = column.as_primitive::<Float64Type>();
1755 write_primitive(typed, array.values(), levels)
1756 }
1757 ColumnWriter::ByteArrayColumnWriter(_) => {
1758 unreachable!("should use ByteArrayWriter")
1759 }
1760 ColumnWriter::FixedLenByteArrayColumnWriter(typed) => {
1761 let bytes = match column.data_type() {
1762 ArrowDataType::Interval(interval_unit) => match interval_unit {
1763 IntervalUnit::YearMonth => {
1764 let array = column.as_primitive::<IntervalYearMonthType>();
1765 get_interval_ym_array_slice(array, indices.iter().copied())
1766 }
1767 IntervalUnit::DayTime => {
1768 let array = column.as_primitive::<IntervalDayTimeType>();
1769 get_interval_dt_array_slice(array, indices.iter().copied())
1770 }
1771 IntervalUnit::MonthDayNano => {
1772 return Err(ParquetError::NYI(format!(
1773 "Attempting to write an Arrow interval type {interval_unit:?} to parquet that is not yet implemented"
1774 )));
1775 }
1776 },
1777 ArrowDataType::FixedSizeBinary(_) => {
1778 let array = column.as_fixed_size_binary();
1779 get_fsb_array_slice(array, indices.iter().copied())
1780 }
1781 ArrowDataType::Decimal32(_, _) => {
1782 let array = column.as_primitive::<Decimal32Type>();
1783 get_decimal_array_slice(array, indices.iter().copied())
1784 }
1785 ArrowDataType::Decimal64(_, _) => {
1786 let array = column.as_primitive::<Decimal64Type>();
1787 get_decimal_array_slice(array, indices.iter().copied())
1788 }
1789 ArrowDataType::Decimal128(_, _) => {
1790 let array = column.as_primitive::<Decimal128Type>();
1791 get_decimal_array_slice(array, indices.iter().copied())
1792 }
1793 ArrowDataType::Decimal256(_, _) => {
1794 let array = column.as_primitive::<Decimal256Type>();
1795 get_decimal_array_slice(array, indices.iter().copied())
1796 }
1797 ArrowDataType::Float16 => {
1798 let array = column.as_primitive::<Float16Type>();
1799 get_float_16_array_slice(array, indices.iter().copied())
1800 }
1801 _ => {
1802 return Err(ParquetError::NYI(
1803 "Attempting to write an Arrow type that is not yet implemented".to_string(),
1804 ));
1805 }
1806 };
1807 typed.write_batch_internal(
1808 bytes.as_slice(),
1809 None,
1810 levels.def_level_data().as_ref(),
1811 levels.rep_level_data().as_ref(),
1812 None,
1813 None,
1814 None,
1815 )
1816 }
1817 }
1818}
1819
1820fn write_primitive<E: ColumnValueEncoder>(
1821 writer: &mut GenericColumnWriter<E>,
1822 values: &E::Values,
1823 levels: &ArrayLevels,
1824) -> Result<usize> {
1825 writer.write_batch_internal(
1826 values,
1827 Some(levels.non_null_indices()),
1828 levels.def_level_data().as_ref(),
1829 levels.rep_level_data().as_ref(),
1830 None,
1831 None,
1832 None,
1833 )
1834}
1835
1836fn get_bool_array_slice(
1837 array: &arrow_array::BooleanArray,
1838 indices: impl ExactSizeIterator<Item = usize>,
1839) -> Vec<bool> {
1840 let mut values = Vec::with_capacity(indices.len());
1841 for i in indices {
1842 values.push(array.value(i))
1843 }
1844 values
1845}
1846
1847fn get_interval_ym_array_slice(
1850 array: &arrow_array::IntervalYearMonthArray,
1851 indices: impl ExactSizeIterator<Item = usize>,
1852) -> Vec<FixedLenByteArray> {
1853 chunk_array_slice(12, indices, move |i, chunk| {
1854 let value = array.value(i);
1855 chunk[0..4].copy_from_slice(&value.to_le_bytes());
1856 })
1857}
1858
1859fn get_interval_dt_array_slice(
1862 array: &arrow_array::IntervalDayTimeArray,
1863 indices: impl ExactSizeIterator<Item = usize>,
1864) -> Vec<FixedLenByteArray> {
1865 chunk_array_slice(12, indices, move |i, chunk| {
1866 let value = array.value(i);
1867 chunk[4..8].copy_from_slice(&value.days.to_le_bytes());
1868 chunk[8..12].copy_from_slice(&value.milliseconds.to_le_bytes());
1869 })
1870}
1871
1872trait NativeDecimalType: DecimalType {
1873 type NativeBytes: AsRef<[u8]>;
1874
1875 fn to_be_bytes(value: Self::Native) -> Self::NativeBytes;
1876}
1877impl NativeDecimalType for Decimal32Type {
1878 type NativeBytes = [u8; Self::BYTE_LENGTH];
1879
1880 fn to_be_bytes(value: Self::Native) -> Self::NativeBytes {
1881 value.to_be_bytes()
1882 }
1883}
1884impl NativeDecimalType for Decimal64Type {
1885 type NativeBytes = [u8; Self::BYTE_LENGTH];
1886
1887 fn to_be_bytes(value: Self::Native) -> Self::NativeBytes {
1888 value.to_be_bytes()
1889 }
1890}
1891impl NativeDecimalType for Decimal128Type {
1892 type NativeBytes = [u8; Self::BYTE_LENGTH];
1893
1894 fn to_be_bytes(value: Self::Native) -> Self::NativeBytes {
1895 value.to_be_bytes()
1896 }
1897}
1898impl NativeDecimalType for Decimal256Type {
1899 type NativeBytes = [u8; Self::BYTE_LENGTH];
1900
1901 fn to_be_bytes(value: Self::Native) -> Self::NativeBytes {
1902 value.to_be_bytes()
1903 }
1904}
1905
1906fn get_decimal_array_slice<T: NativeDecimalType>(
1907 array: &PrimitiveArray<T>,
1908 indices: impl ExactSizeIterator<Item = usize>,
1909) -> Vec<FixedLenByteArray> {
1910 let chunk_size = decimal_length_from_precision(array.precision());
1911 assert!(chunk_size <= T::BYTE_LENGTH);
1912
1913 if chunk_size == T::BYTE_LENGTH {
1914 chunk_array_slice(chunk_size, indices, move |i, chunk| {
1916 let as_be_bytes = T::to_be_bytes(array.value(i));
1917 chunk.copy_from_slice(as_be_bytes.as_ref());
1918 })
1919 } else {
1920 chunk_array_slice(chunk_size, indices, move |i, chunk| {
1921 let as_be_bytes = T::to_be_bytes(array.value(i));
1922 let resized_value = &as_be_bytes.as_ref()[(T::BYTE_LENGTH - chunk.len())..];
1923 chunk.copy_from_slice(resized_value);
1924 })
1925 }
1926}
1927
1928fn get_float_16_array_slice(
1929 array: &arrow_array::Float16Array,
1930 indices: impl ExactSizeIterator<Item = usize>,
1931) -> Vec<FixedLenByteArray> {
1932 chunk_array_slice(2, indices, move |i, chunk| {
1933 let value = array.value(i).to_le_bytes();
1934 chunk.copy_from_slice(&value);
1935 })
1936}
1937
1938fn get_fsb_array_slice(
1939 array: &arrow_array::FixedSizeBinaryArray,
1940 indices: impl ExactSizeIterator<Item = usize>,
1941) -> Vec<FixedLenByteArray> {
1942 chunk_array_slice(array.value_size(), indices, move |i, chunk| {
1943 let value = array.value(i);
1944 chunk.copy_from_slice(value);
1945 })
1946}
1947
1948#[inline]
1949fn chunk_array_slice(
1950 chunk_size: usize,
1951 indices: impl ExactSizeIterator<Item = usize>,
1952 writer: impl Fn(usize, &mut [u8]),
1953) -> Vec<FixedLenByteArray> {
1954 let capacity = indices.len() * chunk_size;
1955 let mut arena = vec![0; capacity];
1958 for (i, chunk) in indices.zip(arena.chunks_exact_mut(chunk_size)) {
1959 writer(i, chunk);
1960 }
1961 chunk_contiguous_vec(arena, chunk_size)
1962}
1963
1964fn chunk_contiguous_vec(arena: Vec<u8>, chunk_size: usize) -> Vec<FixedLenByteArray> {
1965 let mut values = Vec::with_capacity(arena.len() / chunk_size);
1966 let mut arena = Bytes::from(arena);
1967 while arena.len() >= chunk_size {
1968 let slice = arena.split_to(chunk_size);
1969 values.push(FixedLenByteArray::from(ByteArray::from(slice)));
1970 }
1971 values
1972}
1973
1974#[inline]
1976fn hash_bytes(bytes: &[u8]) -> u64 {
1977 twox_hash::XxHash64::oneshot(0, bytes)
1978}
1979
1980fn fixed_byte_width(dt: &ArrowDataType) -> Option<usize> {
1982 use ArrowDataType::*;
1983 match dt {
1984 Int8 | UInt8 => Some(1),
1985 Int16 | UInt16 | Float16 => Some(2),
1986 Int32 | UInt32 | Float32 | Date32 | Time32(_) | Decimal32(_, _) => Some(4),
1987 Int64
1988 | UInt64
1989 | Float64
1990 | Date64
1991 | Time64(_)
1992 | Timestamp(_, _)
1993 | Duration(_)
1994 | Decimal64(_, _) => Some(8),
1995 Interval(IntervalUnit::YearMonth) => Some(4),
1996 Interval(IntervalUnit::DayTime) => Some(8),
1997 Interval(IntervalUnit::MonthDayNano) => Some(16),
1998 Decimal128(_, _) => Some(16),
1999 Decimal256(_, _) => Some(32),
2000 _ => None,
2001 }
2002}
2003
2004fn update_distinct_values_seen(
2010 array: &dyn arrow_array::Array,
2011 non_null_indices: &[usize],
2012 seen: &mut DistinctValuesSet,
2013) {
2014 let data = array.to_data();
2015 let offset = data.offset();
2016
2017 match array.data_type() {
2018 ArrowDataType::Boolean => {
2019 let arr = array
2020 .as_any()
2021 .downcast_ref::<arrow_array::BooleanArray>()
2022 .unwrap();
2023 for &row in non_null_indices {
2024 seen.insert(arr.value(row) as u64);
2025 }
2026 }
2027 ArrowDataType::Utf8 | ArrowDataType::Binary => {
2028 let offsets = data.buffers()[0].typed_data::<i32>();
2029 let values = data.buffers()[1].as_slice();
2030 for &row in non_null_indices {
2031 let start = offsets[offset + row] as usize;
2032 let end = offsets[offset + row + 1] as usize;
2033 seen.insert(hash_bytes(&values[start..end]));
2034 }
2035 }
2036 ArrowDataType::LargeUtf8 | ArrowDataType::LargeBinary => {
2037 let offsets = data.buffers()[0].typed_data::<i64>();
2038 let values = data.buffers()[1].as_slice();
2039 for &row in non_null_indices {
2040 let start = offsets[offset + row] as usize;
2041 let end = offsets[offset + row + 1] as usize;
2042 seen.insert(hash_bytes(&values[start..end]));
2043 }
2044 }
2045 ArrowDataType::FixedSizeBinary(byte_width) => {
2046 let byte_width = *byte_width as usize;
2047 let buffer = data.buffers()[0].as_slice();
2048 for &row in non_null_indices {
2049 let start = (offset + row) * byte_width;
2050 seen.insert(hash_bytes(&buffer[start..start + byte_width]));
2051 }
2052 }
2053 ArrowDataType::Utf8View => {
2054 let string_view_array = array.as_string_view();
2055 for &row in non_null_indices {
2056 seen.insert(hash_bytes(string_view_array.value(row).as_bytes()));
2057 }
2058 }
2059 ArrowDataType::BinaryView => {
2060 let binary_view_array = array.as_binary_view();
2061 for &row in non_null_indices {
2062 seen.insert(hash_bytes(binary_view_array.value(row)));
2063 }
2064 }
2065 data_type => {
2066 if let Some(width) = fixed_byte_width(data_type) {
2067 let buffer = data.buffers()[0].as_slice();
2068 for &row in non_null_indices {
2069 let pos = (offset + row) * width;
2070 seen.insert(hash_bytes(&buffer[pos..pos + width]));
2071 }
2072 }
2073 }
2075 }
2076}
2077
2078#[cfg(test)]
2080use crate as parquet_crate;
2081
2082#[cfg(test)]
2083#[path = "../../../tests/arrow_writer/roundtrip_helpers.rs"]
2084mod roundtrip_helpers;
2085
2086#[cfg(test)]
2087mod tests {
2088 use super::roundtrip_helpers::{
2089 RoundTripTest, SMALL_SIZE, required_and_optional, roundtrip, roundtrip_opts,
2090 roundtrip_opts_with_array_validation,
2091 };
2092 use super::*;
2093 use std::cmp::Ordering;
2094 use std::collections::HashMap;
2095
2096 use std::fs::File;
2097
2098 use crate::arrow::arrow_reader::{ParquetRecordBatchReader, ParquetRecordBatchReaderBuilder};
2099 use crate::arrow::{ARROW_SCHEMA_META_KEY, PARQUET_FIELD_ID_META_KEY};
2100 use crate::column::page::{Page, PageReader};
2101 use crate::file::metadata::thrift::PageHeader;
2102 use crate::file::page_index::column_index::ColumnIndexMetaData;
2103 use crate::file::reader::SerializedPageReader;
2104 use crate::parquet_thrift::{ReadThrift, ThriftSliceInputProtocol};
2105 use crate::schema::types::ColumnPath;
2106 use arrow::datatypes::{DataType, Schema};
2107 use arrow::error::Result as ArrowResult;
2108 use arrow::util::data_gen::create_random_array;
2109 use arrow::util::pretty::pretty_format_batches;
2110 use arrow::{array::*, buffer::Buffer};
2111 use arrow_buffer::{IntervalDayTime, IntervalMonthDayNano, NullBuffer, OffsetBuffer};
2112 use arrow_schema::Fields;
2113 use half::f16;
2114 use tempfile::tempfile;
2115
2116 use crate::basic::{Encoding, EncodingMask};
2117 use crate::data_type::AsBytes;
2118 use crate::file::metadata::{ColumnChunkMetaData, ParquetMetaData, ParquetMetaDataReader};
2119 use crate::file::properties::{
2120 BloomFilterPosition, EnabledStatistics, ReaderProperties, WriterVersion,
2121 };
2122 use crate::file::serialized_reader::ReadOptionsBuilder;
2123 use crate::file::{
2124 reader::{FileReader, SerializedFileReader},
2125 statistics::Statistics,
2126 };
2127
2128 #[derive(Debug, Default)]
2133 struct RecordingPageStore {
2134 next: u64,
2135 blobs: HashMap<u64, Bytes>,
2136 puts: Arc<std::sync::atomic::AtomicUsize>,
2137 }
2138
2139 impl PageStore for RecordingPageStore {
2140 fn put(&mut self, value: Bytes) -> Result<PageKey> {
2141 let id = 100 + self.next * 7;
2143 self.next += 1;
2144 self.puts.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2145 self.blobs.insert(id, value);
2146 Ok(PageKey::new(id))
2147 }
2148
2149 fn take(&mut self, key: PageKey) -> Result<Bytes> {
2150 self.blobs
2151 .remove(&key.get())
2152 .ok_or_else(|| ParquetError::General(format!("missing key {}", key.get())))
2153 }
2154 }
2155
2156 #[derive(Debug)]
2157 struct RecordingPageStoreFactory {
2158 puts: Arc<std::sync::atomic::AtomicUsize>,
2159 }
2160
2161 impl PageStoreFactory for RecordingPageStoreFactory {
2162 fn create(&self, _args: &PageStoreArgs<'_>) -> Result<Box<dyn PageStore>> {
2163 Ok(Box::new(RecordingPageStore {
2164 puts: self.puts.clone(),
2165 ..Default::default()
2166 }))
2167 }
2168 }
2169
2170 #[test]
2174 fn custom_page_store_is_byte_identical_to_default() {
2175 let schema = Arc::new(Schema::new(vec![
2176 Field::new("i", DataType::Int32, true),
2177 Field::new("s", DataType::Utf8, true),
2179 ]));
2180 let i = Int32Array::from(vec![Some(1), None, Some(3), Some(4), Some(5), Some(6)]);
2181 let s = StringArray::from(vec![
2182 Some("a"),
2183 Some("bb"),
2184 Some("a"),
2185 None,
2186 Some("bb"),
2187 Some("ccc"),
2188 ]);
2189 let batch = RecordBatch::try_new(schema.clone(), vec![Arc::new(i), Arc::new(s)]).unwrap();
2190
2191 let props = WriterProperties::builder()
2194 .set_max_row_group_row_count(Some(3))
2195 .build();
2196
2197 let write = |factory: Option<Arc<dyn PageStoreFactory>>| {
2198 let mut buffer = Vec::new();
2199 let mut opts = ArrowWriterOptions::new().with_properties(props.clone());
2200 if let Some(factory) = factory {
2201 opts = opts.with_page_store_factory(factory);
2202 }
2203 let mut writer =
2204 ArrowWriter::try_new_with_options(&mut buffer, schema.clone(), opts).unwrap();
2205 writer.write(&batch).unwrap();
2206 writer.close().unwrap();
2207 buffer
2208 };
2209
2210 let default_bytes = write(None);
2211
2212 let puts = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2213 let custom_bytes = write(Some(Arc::new(RecordingPageStoreFactory {
2214 puts: puts.clone(),
2215 })));
2216
2217 assert!(
2218 puts.load(std::sync::atomic::Ordering::Relaxed) > 0,
2219 "custom PageStore was never written to"
2220 );
2221 assert_eq!(
2222 default_bytes, custom_bytes,
2223 "a custom PageStore must produce byte-identical output to the default"
2224 );
2225 }
2226
2227 #[test]
2233 #[cfg_attr(miri, ignore)] fn dictionary_column_round_trips_with_offset_index_disabled() {
2235 let schema = Arc::new(Schema::new(vec![Field::new("k", DataType::Int32, true)]));
2236
2237 let values: Vec<Option<i32>> = (0..50_000).map(|i| Some(i % 8)).collect();
2240 let array = Int32Array::from(values.clone());
2241 let batch = RecordBatch::try_new(schema.clone(), vec![Arc::new(array)]).unwrap();
2242
2243 let props = WriterProperties::builder()
2244 .set_offset_index_disabled(true)
2245 .set_data_page_row_count_limit(4096)
2246 .build();
2247 let opts = ArrowWriterOptions::new().with_properties(props);
2248
2249 let mut buffer = Vec::new();
2250 let mut writer =
2251 ArrowWriter::try_new_with_options(&mut buffer, schema.clone(), opts).unwrap();
2252 writer.write(&batch).unwrap();
2253 writer.close().unwrap();
2254
2255 let reader = ParquetRecordBatchReader::try_new(Bytes::from(buffer), values.len()).unwrap();
2256 let read: Vec<RecordBatch> = reader.collect::<ArrowResult<_>>().unwrap();
2257 let read_values: Vec<Option<i32>> = read
2258 .iter()
2259 .flat_map(|b| b.column(0).as_primitive::<Int32Type>().iter())
2260 .collect();
2261 assert_eq!(read_values, values);
2262 }
2263
2264 #[test]
2269 fn dictionary_page_is_routed_through_the_store() {
2270 #[derive(Debug, Default)]
2272 struct SizeRecordingPageStore {
2273 blobs: Vec<Bytes>,
2274 bytes_put: Arc<std::sync::atomic::AtomicUsize>,
2275 }
2276 impl PageStore for SizeRecordingPageStore {
2277 fn put(&mut self, value: Bytes) -> Result<PageKey> {
2278 self.bytes_put
2279 .fetch_add(value.len(), std::sync::atomic::Ordering::Relaxed);
2280 let key = PageKey::new(self.blobs.len() as u64);
2281 self.blobs.push(value);
2282 Ok(key)
2283 }
2284 fn take(&mut self, key: PageKey) -> Result<Bytes> {
2285 Ok(std::mem::take(&mut self.blobs[key.get() as usize]))
2286 }
2287 }
2288 #[derive(Debug)]
2289 struct Factory {
2290 bytes_put: Arc<std::sync::atomic::AtomicUsize>,
2291 }
2292 impl PageStoreFactory for Factory {
2293 fn create(&self, _args: &PageStoreArgs<'_>) -> Result<Box<dyn PageStore>> {
2294 Ok(Box::new(SizeRecordingPageStore {
2295 bytes_put: self.bytes_put.clone(),
2296 ..Default::default()
2297 }))
2298 }
2299 }
2300
2301 let schema = Arc::new(Schema::new(vec![Field::new("s", DataType::Utf8, false)]));
2302 let values: Vec<&str> = (0..2048)
2305 .map(|i| ["alpha", "beta", "gamma", "delta"][i % 4])
2306 .collect();
2307 let batch = RecordBatch::try_new(schema.clone(), vec![Arc::new(StringArray::from(values))])
2308 .unwrap();
2309
2310 let bytes_put = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2311 let opts = ArrowWriterOptions::new().with_page_store_factory(Arc::new(Factory {
2312 bytes_put: bytes_put.clone(),
2313 }));
2314
2315 let mut buffer = Vec::new();
2318 let mut writer =
2319 ArrowWriter::try_new_with_options(&mut buffer, schema.clone(), opts).unwrap();
2320 writer.write(&batch).unwrap();
2321 writer.close().unwrap();
2322
2323 let reader = SerializedFileReader::new(Bytes::from(buffer)).unwrap();
2324 let column = reader.metadata().row_group(0).column(0);
2325 assert!(
2326 column.dictionary_page_offset().is_some(),
2327 "expected the column to be dictionary-encoded"
2328 );
2329
2330 assert_eq!(
2334 bytes_put.load(std::sync::atomic::Ordering::Relaxed) as i64,
2335 column.compressed_size(),
2336 "the dictionary page must pass through the store like any other page"
2337 );
2338 }
2339
2340 #[test]
2341 fn arrow_writer() {
2342 let schema = Schema::new(vec![
2344 Field::new("a", DataType::Int32, false),
2345 Field::new("b", DataType::Int32, true),
2346 ]);
2347
2348 let a = Int32Array::from(vec![1, 2, 3, 4, 5]);
2350 let b = Int32Array::from(vec![Some(1), None, None, Some(4), Some(5)]);
2351
2352 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(a), Arc::new(b)]).unwrap();
2354
2355 roundtrip(batch, Some(SMALL_SIZE / 2));
2356 }
2357
2358 fn get_bytes_after_close(schema: SchemaRef, expected_batch: &RecordBatch) -> Vec<u8> {
2359 let mut buffer = vec![];
2360
2361 let mut writer = ArrowWriter::try_new(&mut buffer, schema, None).unwrap();
2362 writer.write(expected_batch).unwrap();
2363 writer.close().unwrap();
2364
2365 buffer
2366 }
2367
2368 fn get_bytes_by_into_inner(schema: SchemaRef, expected_batch: &RecordBatch) -> Vec<u8> {
2369 let mut writer = ArrowWriter::try_new(Vec::new(), schema, None).unwrap();
2370 writer.write(expected_batch).unwrap();
2371 writer.into_inner().unwrap()
2372 }
2373
2374 #[test]
2375 fn roundtrip_bytes() {
2376 let schema = Arc::new(Schema::new(vec![
2378 Field::new("a", DataType::Int32, false),
2379 Field::new("b", DataType::Int32, true),
2380 ]));
2381
2382 let a = Int32Array::from(vec![1, 2, 3, 4, 5]);
2384 let b = Int32Array::from(vec![Some(1), None, None, Some(4), Some(5)]);
2385
2386 let expected_batch =
2388 RecordBatch::try_new(schema.clone(), vec![Arc::new(a), Arc::new(b)]).unwrap();
2389
2390 for buffer in [
2391 get_bytes_after_close(schema.clone(), &expected_batch),
2392 get_bytes_by_into_inner(schema, &expected_batch),
2393 ] {
2394 let cursor = Bytes::from(buffer);
2395 let mut record_batch_reader = ParquetRecordBatchReader::try_new(cursor, 1024).unwrap();
2396
2397 let actual_batch = record_batch_reader
2398 .next()
2399 .expect("No batch found")
2400 .expect("Unable to get batch");
2401
2402 assert_eq!(expected_batch.schema(), actual_batch.schema());
2403 assert_eq!(expected_batch.num_columns(), actual_batch.num_columns());
2404 assert_eq!(expected_batch.num_rows(), actual_batch.num_rows());
2405 for i in 0..expected_batch.num_columns() {
2406 let expected_data = expected_batch.column(i).to_data();
2407 let actual_data = actual_batch.column(i).to_data();
2408
2409 assert_eq!(expected_data, actual_data);
2410 }
2411 }
2412 }
2413
2414 #[test]
2415 #[cfg_attr(miri, ignore)] fn arrow_writer_non_null() {
2417 let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
2418 let a = Int32Array::from(vec![1, 2, 3, 4, 5]);
2419
2420 RoundTripTest::new(Arc::new(a))
2421 .with_schema(Arc::new(schema))
2422 .run();
2423 }
2424
2425 #[test]
2426 #[cfg_attr(miri, ignore)] fn arrow_writer_binary() {
2428 let raw_string_values = vec!["foo", "bar", "baz", "quux"];
2429 let raw_binary_values = [
2430 b"foo".to_vec(),
2431 b"bar".to_vec(),
2432 b"baz".to_vec(),
2433 b"quux".to_vec(),
2434 ];
2435 let raw_binary_value_refs = raw_binary_values
2436 .iter()
2437 .map(|x| x.as_slice())
2438 .collect::<Vec<_>>();
2439
2440 let string_values = StringArray::from(raw_string_values.clone());
2441 let binary_values = BinaryArray::from(raw_binary_value_refs);
2442 assert_eq!(string_values.null_count(), 0);
2443 assert_eq!(binary_values.null_count(), 0);
2444
2445 RoundTripTest::new(Arc::new(string_values)).run();
2446 RoundTripTest::new(Arc::new(binary_values)).run();
2447 }
2448
2449 #[test]
2450 #[cfg_attr(miri, ignore)] fn arrow_writer_binary_view() {
2452 let raw_string_values = vec!["foo", "bar", "large payload over 12 bytes", "lulu"];
2453 let raw_binary_values = vec![
2454 b"foo".to_vec(),
2455 b"bar".to_vec(),
2456 b"large payload over 12 bytes".to_vec(),
2457 b"lulu".to_vec(),
2458 ];
2459 let nullable_string_values =
2460 vec![Some("foo"), None, Some("large payload over 12 bytes"), None];
2461
2462 let string_view_values = StringViewArray::from(raw_string_values);
2463 let binary_view_values = BinaryViewArray::from_iter_values(raw_binary_values);
2464 let nullable_string_view_values = StringViewArray::from(nullable_string_values);
2465
2466 RoundTripTest::new(Arc::new(string_view_values)).run();
2467 RoundTripTest::new(Arc::new(binary_view_values)).run();
2468 RoundTripTest::new(Arc::new(nullable_string_view_values)).run();
2469 }
2470
2471 #[test]
2472 #[cfg_attr(miri, ignore)] fn arrow_writer_binary_view_long_value() {
2474 let long = "a".repeat(128);
2478 let raw_string_values = vec!["foo", long.as_str(), "bar"];
2479 let raw_binary_values = vec![b"foo".to_vec(), long.as_bytes().to_vec(), b"bar".to_vec()];
2480
2481 let string_view_values: ArrayRef = Arc::new(StringViewArray::from(raw_string_values));
2482 let binary_view_values: ArrayRef =
2483 Arc::new(BinaryViewArray::from_iter_values(raw_binary_values));
2484
2485 RoundTripTest::new(Arc::clone(&string_view_values))
2486 .with_nullable(false)
2487 .run();
2488 RoundTripTest::new(Arc::clone(&binary_view_values))
2489 .with_nullable(false)
2490 .run();
2491 }
2492
2493 fn get_decimal_batch(precision: u8, scale: i8) -> RecordBatch {
2494 let decimal_field = Field::new("a", DataType::Decimal128(precision, scale), false);
2495 let schema = Schema::new(vec![decimal_field]);
2496
2497 let decimal_values = vec![10_000, 50_000, 0, -100]
2498 .into_iter()
2499 .map(Some)
2500 .collect::<Decimal128Array>()
2501 .with_precision_and_scale(precision, scale)
2502 .unwrap();
2503
2504 RecordBatch::try_new(Arc::new(schema), vec![Arc::new(decimal_values)]).unwrap()
2505 }
2506
2507 #[test]
2508 fn arrow_writer_decimal() {
2509 let batch_int32_decimal = get_decimal_batch(5, 2);
2511 roundtrip(batch_int32_decimal, Some(SMALL_SIZE / 2));
2512 let batch_int64_decimal = get_decimal_batch(12, 2);
2514 roundtrip(batch_int64_decimal, Some(SMALL_SIZE / 2));
2515 let batch_fixed_len_byte_array_decimal = get_decimal_batch(30, 2);
2517 roundtrip(batch_fixed_len_byte_array_decimal, Some(SMALL_SIZE / 2));
2518 }
2519
2520 #[test]
2521 fn arrow_writer_page_size() {
2522 let schema = Arc::new(Schema::new(vec![Field::new("col", DataType::Utf8, false)]));
2523
2524 let mut builder = StringBuilder::with_capacity(100, 329 * 10_000);
2525
2526 for i in 0..10 {
2528 let value = i
2529 .to_string()
2530 .repeat(10)
2531 .chars()
2532 .take(10)
2533 .collect::<String>();
2534
2535 builder.append_value(value);
2536 }
2537
2538 let array = Arc::new(builder.finish());
2539
2540 let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
2541
2542 let file = tempfile::tempfile().unwrap();
2543
2544 let props = WriterProperties::builder()
2546 .set_data_page_size_limit(1)
2547 .set_dictionary_page_size_limit(1)
2548 .set_write_batch_size(1)
2549 .build();
2550
2551 let mut writer =
2552 ArrowWriter::try_new(file.try_clone().unwrap(), batch.schema(), Some(props))
2553 .expect("Unable to write file");
2554 writer.write(&batch).unwrap();
2555 writer.close().unwrap();
2556
2557 let options = ReadOptionsBuilder::new().with_page_index().build();
2558 let reader =
2559 SerializedFileReader::new_with_options(file.try_clone().unwrap(), options).unwrap();
2560
2561 let column = reader.metadata().row_group(0).columns();
2562
2563 assert_eq!(column.len(), 1);
2564
2565 assert!(
2568 column[0].dictionary_page_offset().is_some(),
2569 "Expected a dictionary page"
2570 );
2571
2572 let page_index = reader
2573 .metadata()
2574 .page_index()
2575 .expect("page index should be present");
2576 let page_locations = page_index
2577 .page_locations(0, 0)
2578 .expect("page locations should exist");
2579
2580 assert_eq!(
2583 page_locations.len(),
2584 10,
2585 "Expected 10 pages but got {page_locations:#?}"
2586 );
2587 }
2588
2589 #[test]
2590 #[cfg_attr(miri, ignore)] fn arrow_writer_float_nans() {
2592 let f16_field = Field::new("a", DataType::Float16, false);
2593 let f32_field = Field::new("b", DataType::Float32, false);
2594 let f64_field = Field::new("c", DataType::Float64, false);
2595 let schema = Schema::new(vec![f16_field, f32_field, f64_field]);
2596
2597 let f16_values = (0..MEDIUM_SIZE)
2598 .map(|i| {
2599 Some(if i % 2 == 0 {
2600 f16::NAN
2601 } else {
2602 f16::from_f32(i as f32)
2603 })
2604 })
2605 .collect::<Float16Array>();
2606
2607 let f32_values = (0..MEDIUM_SIZE)
2608 .map(|i| Some(if i % 2 == 0 { f32::NAN } else { i as f32 }))
2609 .collect::<Float32Array>();
2610
2611 let f64_values = (0..MEDIUM_SIZE)
2612 .map(|i| Some(if i % 2 == 0 { f64::NAN } else { i as f64 }))
2613 .collect::<Float64Array>();
2614
2615 let batch = RecordBatch::try_new(
2616 Arc::new(schema),
2617 vec![
2618 Arc::new(f16_values),
2619 Arc::new(f32_values),
2620 Arc::new(f64_values),
2621 ],
2622 )
2623 .unwrap();
2624
2625 roundtrip(batch, None);
2626 }
2627
2628 const MEDIUM_SIZE: usize = 63;
2629
2630 fn check_bloom_filter<T: AsBytes>(
2631 files: Vec<Bytes>,
2632 file_column: String,
2633 positive_values: Vec<T>,
2634 negative_values: Vec<T>,
2635 ) {
2636 files.into_iter().take(1).for_each(|file| {
2637 let file_reader = SerializedFileReader::new_with_options(
2638 file,
2639 ReadOptionsBuilder::new()
2640 .with_reader_properties(
2641 ReaderProperties::builder()
2642 .set_read_bloom_filter(true)
2643 .build(),
2644 )
2645 .build(),
2646 )
2647 .expect("Unable to open file as Parquet");
2648 let metadata = file_reader.metadata();
2649
2650 let mut bloom_filters: Vec<_> = vec![];
2652 for (ri, row_group) in metadata.row_groups().iter().enumerate() {
2653 if let Some((column_index, _)) = row_group
2654 .columns()
2655 .iter()
2656 .enumerate()
2657 .find(|(_, column)| column.column_path().string() == file_column)
2658 {
2659 let row_group_reader = file_reader
2660 .get_row_group(ri)
2661 .expect("Unable to read row group");
2662 if let Some(sbbf) = row_group_reader.get_column_bloom_filter(column_index) {
2663 bloom_filters.push(sbbf.clone());
2664 } else {
2665 panic!("No bloom filter for column named {file_column} found");
2666 }
2667 } else {
2668 panic!("No column named {file_column} found");
2669 }
2670 }
2671
2672 positive_values.iter().for_each(|value| {
2673 let found = bloom_filters.iter().find(|sbbf| sbbf.check(value));
2674 assert!(
2675 found.is_some(),
2676 "{}",
2677 format!("Value {:?} should be in bloom filter", value.as_bytes())
2678 );
2679 });
2680
2681 negative_values.iter().for_each(|value| {
2682 let found = bloom_filters.iter().find(|sbbf| sbbf.check(value));
2683 assert!(
2684 found.is_none(),
2685 "{}",
2686 format!("Value {:?} should not be in bloom filter", value.as_bytes())
2687 );
2688 });
2689 });
2690 }
2691
2692 #[test]
2693 #[cfg_attr(miri, ignore)] fn all_null_primitive_single_column() {
2695 let values = Arc::new(Int32Array::from(vec![None; SMALL_SIZE]));
2696 RoundTripTest::new(values).run();
2697 }
2698 #[test]
2699 #[cfg_attr(miri, ignore)] fn null_single_column() {
2701 let values = Arc::new(NullArray::new(SMALL_SIZE));
2702 RoundTripTest::new(values).run();
2703 }
2705
2706 #[test]
2707 #[cfg_attr(miri, ignore)] fn bool_single_column() {
2709 required_and_optional::<BooleanArray, _>(
2710 [true, false].iter().cycle().copied().take(SMALL_SIZE),
2711 );
2712 }
2713
2714 #[test]
2715 #[cfg_attr(miri, ignore)] fn bool_large_single_column() {
2717 let values = Arc::new(
2718 [None, Some(true), Some(false)]
2719 .iter()
2720 .cycle()
2721 .copied()
2722 .take(200_000)
2723 .collect::<BooleanArray>(),
2724 );
2725 let schema = Schema::new(vec![Field::new("col", values.data_type().clone(), true)]);
2726 let expected_batch = RecordBatch::try_new(Arc::new(schema), vec![values]).unwrap();
2727 let file = tempfile::tempfile().unwrap();
2728
2729 let mut writer =
2730 ArrowWriter::try_new(file.try_clone().unwrap(), expected_batch.schema(), None)
2731 .expect("Unable to write file");
2732 writer.write(&expected_batch).unwrap();
2733 writer.close().unwrap();
2734 }
2735
2736 #[test]
2737 fn check_page_offset_index_with_nan() {
2738 let values = Arc::new(Float64Array::from(vec![f64::NAN; 10]));
2739 let schema = Schema::new(vec![Field::new("col", DataType::Float64, true)]);
2740 let batch = RecordBatch::try_new(Arc::new(schema), vec![values]).unwrap();
2741
2742 let mut out = Vec::with_capacity(1024);
2743 let mut writer =
2744 ArrowWriter::try_new(&mut out, batch.schema(), None).expect("Unable to write file");
2745 writer.write(&batch).unwrap();
2746 let file_meta_data = writer.close().unwrap();
2747 for row_group in file_meta_data.row_groups() {
2748 for column in row_group.columns() {
2749 assert!(column.offset_index_offset().is_some());
2750 assert!(column.offset_index_length().is_some());
2751 assert!(column.column_index_offset().is_some());
2752 assert!(column.column_index_length().is_some());
2753 }
2754 }
2755 if let Some(page_index) = file_meta_data.page_index() {
2756 for rg in 0..file_meta_data.num_row_groups() {
2757 for col in 0..file_meta_data.row_group(rg).num_columns() {
2758 let idx = page_index
2759 .column_index(rg, col)
2760 .expect("column index should exist");
2761 assert!(idx.nan_counts().is_some());
2762 let ColumnIndexMetaData::DOUBLE(float_idx) = idx else {
2763 panic!("expected double statistics")
2764 };
2765 for i in 0..idx.num_pages() as usize {
2766 assert_eq!(float_idx.nan_count(i), Some(10));
2767 assert_eq!(
2768 f64::NAN.total_cmp(float_idx.min_value(i).unwrap()),
2769 Ordering::Equal
2770 );
2771 assert_eq!(
2772 f64::NAN.total_cmp(float_idx.max_value(i).unwrap()),
2773 Ordering::Equal
2774 );
2775 }
2776 }
2777 }
2778 } else {
2779 panic!("page index should be present");
2780 }
2781 }
2782
2783 #[test]
2784 fn check_page_offset_index_with_mixed_nan() {
2785 let schema = Arc::new(Schema::new(vec![Field::new(
2786 "col",
2787 DataType::Float64,
2788 true,
2789 )]));
2790
2791 let mut out = Vec::with_capacity(1024);
2792 let props = WriterProperties::builder()
2793 .set_data_page_row_count_limit(10)
2794 .build();
2795 let mut writer = ArrowWriter::try_new(&mut out, schema.clone(), Some(props))
2796 .expect("Unable to write file");
2797
2798 let values = Arc::new(Float64Array::from(vec![f64::NAN; 10]));
2800 let batch = RecordBatch::try_new(schema.clone(), vec![values]).unwrap();
2801 writer.write(&batch).unwrap();
2802
2803 let values = Arc::new(Float64Array::from(vec![-f64::NAN; 10]));
2805 let batch = RecordBatch::try_new(schema.clone(), vec![values]).unwrap();
2806 writer.write(&batch).unwrap();
2807
2808 let values = Arc::new(Float64Array::from(vec![0_f64; 10]));
2810 let batch = RecordBatch::try_new(schema.clone(), vec![values]).unwrap();
2811 writer.write(&batch).unwrap();
2812
2813 let values = Arc::new(Float64Array::from(vec![
2815 -1.0,
2816 0.0,
2817 f64::NAN,
2818 -f64::NAN,
2819 1.0,
2820 ]));
2821 let batch = RecordBatch::try_new(schema.clone(), vec![values]).unwrap();
2822 writer.write(&batch).unwrap();
2823
2824 let file_meta_data = writer.close().unwrap();
2825
2826 let col_stats = file_meta_data
2828 .row_group(0)
2829 .column(0)
2830 .statistics()
2831 .expect("missing column chunk statistics");
2832
2833 assert_eq!(col_stats.nan_count_opt(), Some(22));
2834 assert_eq!(col_stats.min_bytes_opt(), Some((-1.0f64).as_bytes()));
2835 assert_eq!(col_stats.max_bytes_opt(), Some(1.0f64.as_bytes()));
2836
2837 assert!(file_meta_data.page_index().is_some());
2838 let col_idx = &file_meta_data.page_index().unwrap().column_index(0, 0);
2839 assert_eq!(col_idx.as_ref().unwrap().num_pages(), 4);
2840
2841 let Some(ColumnIndexMetaData::DOUBLE(float_idx)) = col_idx else {
2843 panic!("expected double statistics")
2844 };
2845
2846 assert_eq!(float_idx.nan_counts, Some(vec![10, 10, 0, 2]));
2847 assert_eq!(
2848 f64::NAN.total_cmp(float_idx.min_value(0).unwrap()),
2849 Ordering::Equal
2850 );
2851 assert_eq!(
2852 f64::NAN.total_cmp(float_idx.max_value(0).unwrap()),
2853 Ordering::Equal
2854 );
2855 assert_eq!(
2856 (-f64::NAN).total_cmp(float_idx.min_value(1).unwrap()),
2857 Ordering::Equal
2858 );
2859 assert_eq!(
2860 (-f64::NAN).total_cmp(float_idx.max_value(1).unwrap()),
2861 Ordering::Equal
2862 );
2863 assert_eq!(float_idx.min_value(2), Some(&0.0));
2864 assert_eq!(float_idx.max_value(2), Some(&0.0));
2865 assert_eq!(float_idx.min_value(3), Some(&-1.0));
2866 assert_eq!(float_idx.max_value(3), Some(&1.0));
2867 }
2868
2869 #[test]
2870 #[cfg_attr(miri, ignore)] fn interval_year_month_single_column() {
2872 required_and_optional::<IntervalYearMonthArray, _>(0..SMALL_SIZE as i32);
2873 }
2874
2875 #[test]
2876 #[cfg_attr(miri, ignore)] fn interval_day_time_single_column() {
2878 required_and_optional::<IntervalDayTimeArray, _>(vec![
2879 IntervalDayTime::new(0, 1),
2880 IntervalDayTime::new(0, 3),
2881 IntervalDayTime::new(3, -2),
2882 IntervalDayTime::new(-200, 4),
2883 ]);
2884 }
2885
2886 #[test]
2887 #[should_panic(
2888 expected = "Attempting to write an Arrow interval type MonthDayNano to parquet that is not yet implemented"
2889 )]
2890 fn interval_month_day_nano_single_column() {
2891 required_and_optional::<IntervalMonthDayNanoArray, _>(vec![
2892 IntervalMonthDayNano::new(0, 1, 5),
2893 IntervalMonthDayNano::new(0, 3, 2),
2894 IntervalMonthDayNano::new(3, -2, -5),
2895 IntervalMonthDayNano::new(-200, 4, -1),
2896 ]);
2897 }
2898
2899 #[test]
2900 #[cfg_attr(miri, ignore)] fn i32_column_bloom_filter_at_end() {
2902 let array = Arc::new(Int32Array::from_iter(0..SMALL_SIZE as i32));
2903 let files = RoundTripTest::new(array)
2904 .with_nullable(false)
2905 .with_bloom_filter(true)
2906 .with_bloom_filter_position(BloomFilterPosition::End)
2907 .run();
2908
2909 check_bloom_filter(
2910 files,
2911 "col".to_string(),
2912 (0..SMALL_SIZE as i32).collect(),
2913 (SMALL_SIZE as i32 + 1..SMALL_SIZE as i32 + 10).collect(),
2914 );
2915 }
2916
2917 #[test]
2918 #[cfg_attr(miri, ignore)] fn i32_column_bloom_filter() {
2920 let array = Arc::new(Int32Array::from_iter(0..SMALL_SIZE as i32));
2921 let files = RoundTripTest::new(array)
2922 .with_nullable(false)
2923 .with_bloom_filter(true)
2924 .run();
2925
2926 check_bloom_filter(
2927 files,
2928 "col".to_string(),
2929 (0..SMALL_SIZE as i32).collect(),
2930 (SMALL_SIZE as i32 + 1..SMALL_SIZE as i32 + 10).collect(),
2931 );
2932 }
2933
2934 fn write_with_bloom_filter(array: ArrayRef, dictionary_page_size_limit: usize) -> Bytes {
2935 let schema = Arc::new(Schema::new(vec![Field::new(
2936 "col",
2937 array.data_type().clone(),
2938 false,
2939 )]));
2940 let batch = RecordBatch::try_new(schema.clone(), vec![array]).unwrap();
2941 let props = WriterProperties::builder()
2942 .set_dictionary_enabled(true)
2943 .set_dictionary_page_size_limit(dictionary_page_size_limit)
2944 .set_write_batch_size(256)
2945 .set_bloom_filter_enabled(true)
2946 .build();
2947 let mut buf = Vec::new();
2948 let mut writer = ArrowWriter::try_new(&mut buf, schema, Some(props)).unwrap();
2949 writer.write(&batch).unwrap();
2950 writer.close().unwrap();
2951 Bytes::from(buf)
2952 }
2953
2954 fn data_page_encoding_mask(file: &Bytes) -> EncodingMask {
2955 let metadata = ParquetMetaDataReader::new().parse_and_finish(file).unwrap();
2956 *metadata
2957 .row_group(0)
2958 .column(0)
2959 .page_encoding_stats_mask()
2960 .unwrap()
2961 }
2962
2963 #[test]
2966 fn string_column_bloom_filter_populated_from_dictionary() {
2967 let values: Vec<String> = (0..2000).map(|i| format!("value-{}", i % 10)).collect();
2968 let array = Arc::new(StringArray::from_iter_values(&values));
2969 let file = write_with_bloom_filter(array, 1024 * 1024);
2970 assert!(data_page_encoding_mask(&file).is_only(Encoding::RLE_DICTIONARY));
2971
2972 check_bloom_filter(
2973 vec![file],
2974 "col".to_string(),
2975 (0..10).map(|i| format!("value-{i}").into_bytes()).collect(),
2976 (10..20)
2977 .map(|i| format!("value-{i}").into_bytes())
2978 .collect(),
2979 );
2980 }
2981
2982 #[test]
2985 fn string_column_bloom_filter_across_dictionary_fallback() {
2986 let values: Vec<String> = (0..2000).map(|i| format!("value-{i}")).collect();
2987 let array = Arc::new(StringArray::from_iter_values(&values));
2988 let file = write_with_bloom_filter(array, 1024);
2989 let encodings = data_page_encoding_mask(&file);
2990 assert!(
2991 encodings.is_set(Encoding::RLE_DICTIONARY) && encodings.is_set(Encoding::PLAIN),
2992 "expected dictionary and plain data pages, got {encodings:?}"
2993 );
2994
2995 check_bloom_filter(
2996 vec![file],
2997 "col".to_string(),
2998 values.into_iter().map(String::into_bytes).collect(),
2999 (2000..2010)
3000 .map(|i| format!("value-{i}").into_bytes())
3001 .collect(),
3002 );
3003 }
3004
3005 #[test]
3006 fn i64_column_bloom_filter_populated_from_dictionary() {
3007 let array = Arc::new(Int64Array::from_iter_values((0..2000).map(|i| i % 10)));
3008 let file = write_with_bloom_filter(array, 1024 * 1024);
3009 assert!(data_page_encoding_mask(&file).is_only(Encoding::RLE_DICTIONARY));
3010
3011 check_bloom_filter(
3012 vec![file],
3013 "col".to_string(),
3014 (0..10i64).collect(),
3015 (10..20i64).collect(),
3016 );
3017 }
3018
3019 #[test]
3020 fn i64_column_bloom_filter_across_dictionary_fallback() {
3021 let array = Arc::new(Int64Array::from_iter_values(0..2000i64));
3022 let file = write_with_bloom_filter(array, 1024);
3023 let encodings = data_page_encoding_mask(&file);
3024 assert!(
3025 encodings.is_set(Encoding::RLE_DICTIONARY) && encodings.is_set(Encoding::PLAIN),
3026 "expected dictionary and plain data pages, got {encodings:?}"
3027 );
3028
3029 check_bloom_filter(
3030 vec![file],
3031 "col".to_string(),
3032 (0..2000i64).collect(),
3033 (2000..2010i64).collect(),
3034 );
3035 }
3036
3037 #[test]
3042 #[cfg_attr(miri, ignore)] fn i32_column_bloom_filter_fixed_ndv() {
3044 let array = Arc::new(Int32Array::from_iter(0..SMALL_SIZE as i32));
3045
3046 let files = RoundTripTest::new(array.clone())
3048 .with_nullable(false)
3049 .with_bloom_filter(true)
3050 .with_bloom_filter_ndv(1_000_000)
3051 .run();
3052
3053 check_bloom_filter(
3054 files,
3055 "col".to_string(),
3056 (0..SMALL_SIZE as i32).collect(),
3057 (SMALL_SIZE as i32 + 1..SMALL_SIZE as i32 + 10).collect(),
3058 );
3059
3060 let files = RoundTripTest::new(array)
3062 .with_nullable(false)
3063 .with_bloom_filter(true)
3064 .with_bloom_filter_ndv(3)
3065 .run();
3066
3067 check_bloom_filter(
3068 files,
3069 "col".to_string(),
3070 (0..SMALL_SIZE as i32).collect(),
3071 (SMALL_SIZE as i32 + 1..SMALL_SIZE as i32 + 10).collect(),
3072 );
3073 }
3074
3075 #[test]
3076 #[cfg_attr(miri, ignore)] fn binary_column_bloom_filter() {
3078 let one_vec: Vec<u8> = (0..SMALL_SIZE as u8).collect();
3079 let many_vecs: Vec<_> = std::iter::repeat_n(one_vec, SMALL_SIZE).collect();
3080 let many_vecs_iter = many_vecs.iter().map(|v| v.as_slice());
3081
3082 let array = Arc::new(BinaryArray::from_iter_values(many_vecs_iter));
3083 let files = RoundTripTest::new(array)
3084 .with_nullable(false)
3085 .with_bloom_filter(true)
3086 .run();
3087
3088 check_bloom_filter(
3089 files,
3090 "col".to_string(),
3091 many_vecs,
3092 vec![vec![(SMALL_SIZE + 1) as u8]],
3093 );
3094 }
3095
3096 #[test]
3097 #[cfg_attr(miri, ignore)] fn empty_string_null_column_bloom_filter() {
3099 let raw_values: Vec<_> = (0..SMALL_SIZE).map(|i| i.to_string()).collect();
3100 let raw_strs = raw_values.iter().map(|s| s.as_str());
3101
3102 let array = Arc::new(StringArray::from_iter_values(raw_strs));
3103 let files = RoundTripTest::new(array)
3104 .with_nullable(false)
3105 .with_bloom_filter(true)
3106 .run();
3107
3108 let optional_raw_values: Vec<_> = raw_values
3109 .iter()
3110 .enumerate()
3111 .filter_map(|(i, v)| if i % 2 == 0 { None } else { Some(v.as_str()) })
3112 .collect();
3113 check_bloom_filter(files, "col".to_string(), optional_raw_values, vec![""]);
3115 }
3116
3117 #[test]
3118 fn list_and_map_coerced_names() {
3119 let list_field =
3121 Field::new_list("my_list", Field::new("item", DataType::Int32, false), false);
3122 let map_field = Field::new_map(
3123 "my_map",
3124 "my_entries",
3125 Field::new("my_keys", DataType::Int32, false),
3126 Field::new("my_values", DataType::Int32, true),
3127 false,
3128 true,
3129 );
3130
3131 let list_array = create_random_array(&list_field, 100, 0.0, 0.0).unwrap();
3132 let map_array = create_random_array(&map_field, 100, 0.0, 0.0).unwrap();
3133
3134 let arrow_schema = Arc::new(Schema::new(vec![list_field, map_field]));
3135
3136 let props = Some(WriterProperties::builder().set_coerce_types(true).build());
3138 let file = tempfile::tempfile().unwrap();
3139 let mut writer =
3140 ArrowWriter::try_new(file.try_clone().unwrap(), arrow_schema.clone(), props).unwrap();
3141
3142 let batch = RecordBatch::try_new(arrow_schema, vec![list_array, map_array]).unwrap();
3143 writer.write(&batch).unwrap();
3144 let file_metadata = writer.close().unwrap();
3145
3146 let schema = file_metadata.file_metadata().schema();
3147 let list_field = &schema.get_fields()[0].get_fields()[0];
3149 assert_eq!(list_field.get_fields()[0].name(), "element");
3150
3151 let map_field = &schema.get_fields()[1].get_fields()[0];
3152 assert_eq!(map_field.name(), "key_value");
3154 assert_eq!(map_field.get_fields()[0].name(), "key");
3156 assert_eq!(map_field.get_fields()[1].name(), "value");
3158
3159 let reader = SerializedFileReader::new(file).unwrap();
3161 let file_schema = reader.metadata().file_metadata().schema();
3162 let fields = file_schema.get_fields();
3163 let list_field = &fields[0].get_fields()[0];
3164 assert_eq!(list_field.get_fields()[0].name(), "element");
3165 let map_field = &fields[1].get_fields()[0];
3166 assert_eq!(map_field.name(), "key_value");
3167 assert_eq!(map_field.get_fields()[0].name(), "key");
3168 assert_eq!(map_field.get_fields()[1].name(), "value");
3169 }
3170
3171 #[test]
3172 #[cfg_attr(miri, ignore)] fn fallback_flush_data_page() {
3174 let raw_values: Vec<_> = (0..MEDIUM_SIZE).map(|i| i.to_string()).collect();
3176 let values = Arc::new(StringArray::from(raw_values));
3177 let encodings = vec![
3178 Encoding::DELTA_BYTE_ARRAY,
3179 Encoding::DELTA_LENGTH_BYTE_ARRAY,
3180 ];
3181 let data_type = values.data_type().clone();
3182 let schema = Arc::new(Schema::new(vec![Field::new("col", data_type, false)]));
3183 let expected_batch = RecordBatch::try_new(schema, vec![values]).unwrap();
3184
3185 let row_group_sizes = [1024, SMALL_SIZE, SMALL_SIZE / 2, SMALL_SIZE / 2 + 1, 10];
3186 let data_page_size_limit: usize = 32;
3187 let write_batch_size: usize = 16;
3188
3189 for encoding in &encodings {
3190 for row_group_size in row_group_sizes {
3191 let props = WriterProperties::builder()
3192 .set_writer_version(WriterVersion::PARQUET_2_0)
3193 .set_max_row_group_row_count(Some(row_group_size))
3194 .set_dictionary_enabled(false)
3195 .set_encoding(*encoding)
3196 .set_data_page_size_limit(data_page_size_limit)
3197 .set_write_batch_size(write_batch_size)
3198 .build();
3199
3200 roundtrip_opts_with_array_validation(&expected_batch, props, |a, b| {
3201 let string_array_a = StringArray::from(a.clone());
3202 let string_array_b = StringArray::from(b.clone());
3203 let vec_a: Vec<&str> = string_array_a.iter().map(|v| v.unwrap()).collect();
3204 let vec_b: Vec<&str> = string_array_b.iter().map(|v| v.unwrap()).collect();
3205 assert_eq!(
3206 vec_a, vec_b,
3207 "failed for encoder: {encoding:?} and row_group_size: {row_group_size:?}"
3208 );
3209 });
3210 }
3211 }
3212 }
3213
3214 #[test]
3215 fn arrow_writer_test_type_compatibility() {
3216 fn ensure_compatible_write<T1, T2>(array1: T1, array2: T2, expected_result: T1)
3217 where
3218 T1: Array + 'static,
3219 T2: Array + 'static,
3220 {
3221 let schema1 = Arc::new(Schema::new(vec![Field::new(
3222 "a",
3223 array1.data_type().clone(),
3224 false,
3225 )]));
3226
3227 let file = tempfile().unwrap();
3228 let mut writer =
3229 ArrowWriter::try_new(file.try_clone().unwrap(), schema1.clone(), None).unwrap();
3230
3231 let rb1 = RecordBatch::try_new(schema1.clone(), vec![Arc::new(array1)]).unwrap();
3232 writer.write(&rb1).unwrap();
3233
3234 let schema2 = Arc::new(Schema::new(vec![Field::new(
3235 "a",
3236 array2.data_type().clone(),
3237 false,
3238 )]));
3239 let rb2 = RecordBatch::try_new(schema2, vec![Arc::new(array2)]).unwrap();
3240 writer.write(&rb2).unwrap();
3241
3242 writer.close().unwrap();
3243
3244 let mut record_batch_reader =
3245 ParquetRecordBatchReader::try_new(file.try_clone().unwrap(), 1024).unwrap();
3246 let actual_batch = record_batch_reader.next().unwrap().unwrap();
3247
3248 let expected_batch =
3249 RecordBatch::try_new(schema1, vec![Arc::new(expected_result)]).unwrap();
3250 assert_eq!(actual_batch, expected_batch);
3251 }
3252
3253 ensure_compatible_write(
3256 DictionaryArray::new(
3257 UInt8Array::from_iter_values(vec![0]),
3258 Arc::new(StringArray::from_iter_values(vec!["parquet"])),
3259 ),
3260 StringArray::from_iter_values(vec!["barquet"]),
3261 DictionaryArray::new(
3262 UInt8Array::from_iter_values(vec![0, 1]),
3263 Arc::new(StringArray::from_iter_values(vec!["parquet", "barquet"])),
3264 ),
3265 );
3266
3267 ensure_compatible_write(
3268 StringArray::from_iter_values(vec!["parquet"]),
3269 DictionaryArray::new(
3270 UInt8Array::from_iter_values(vec![0]),
3271 Arc::new(StringArray::from_iter_values(vec!["barquet"])),
3272 ),
3273 StringArray::from_iter_values(vec!["parquet", "barquet"]),
3274 );
3275
3276 ensure_compatible_write(
3279 DictionaryArray::new(
3280 UInt8Array::from_iter_values(vec![0]),
3281 Arc::new(StringArray::from_iter_values(vec!["parquet"])),
3282 ),
3283 DictionaryArray::new(
3284 UInt16Array::from_iter_values(vec![0]),
3285 Arc::new(StringArray::from_iter_values(vec!["barquet"])),
3286 ),
3287 DictionaryArray::new(
3288 UInt8Array::from_iter_values(vec![0, 1]),
3289 Arc::new(StringArray::from_iter_values(vec!["parquet", "barquet"])),
3290 ),
3291 );
3292
3293 ensure_compatible_write(
3295 DictionaryArray::new(
3296 UInt8Array::from_iter_values(vec![0]),
3297 Arc::new(StringArray::from_iter_values(vec!["parquet"])),
3298 ),
3299 DictionaryArray::new(
3300 UInt8Array::from_iter_values(vec![0]),
3301 Arc::new(LargeStringArray::from_iter_values(vec!["barquet"])),
3302 ),
3303 DictionaryArray::new(
3304 UInt8Array::from_iter_values(vec![0, 1]),
3305 Arc::new(StringArray::from_iter_values(vec!["parquet", "barquet"])),
3306 ),
3307 );
3308
3309 ensure_compatible_write(
3311 DictionaryArray::new(
3312 UInt8Array::from_iter_values(vec![0]),
3313 Arc::new(StringArray::from_iter_values(vec!["parquet"])),
3314 ),
3315 LargeStringArray::from_iter_values(vec!["barquet"]),
3316 DictionaryArray::new(
3317 UInt8Array::from_iter_values(vec![0, 1]),
3318 Arc::new(StringArray::from_iter_values(vec!["parquet", "barquet"])),
3319 ),
3320 );
3321
3322 ensure_compatible_write(
3325 StringArray::from_iter_values(vec!["parquet"]),
3326 LargeStringArray::from_iter_values(vec!["barquet"]),
3327 StringArray::from_iter_values(vec!["parquet", "barquet"]),
3328 );
3329
3330 ensure_compatible_write(
3331 LargeStringArray::from_iter_values(vec!["parquet"]),
3332 StringArray::from_iter_values(vec!["barquet"]),
3333 LargeStringArray::from_iter_values(vec!["parquet", "barquet"]),
3334 );
3335
3336 ensure_compatible_write(
3337 StringArray::from_iter_values(vec!["parquet"]),
3338 StringViewArray::from_iter_values(vec!["barquet"]),
3339 StringArray::from_iter_values(vec!["parquet", "barquet"]),
3340 );
3341
3342 ensure_compatible_write(
3343 StringViewArray::from_iter_values(vec!["parquet"]),
3344 StringArray::from_iter_values(vec!["barquet"]),
3345 StringViewArray::from_iter_values(vec!["parquet", "barquet"]),
3346 );
3347
3348 ensure_compatible_write(
3349 LargeStringArray::from_iter_values(vec!["parquet"]),
3350 StringViewArray::from_iter_values(vec!["barquet"]),
3351 LargeStringArray::from_iter_values(vec!["parquet", "barquet"]),
3352 );
3353
3354 ensure_compatible_write(
3355 StringViewArray::from_iter_values(vec!["parquet"]),
3356 LargeStringArray::from_iter_values(vec!["barquet"]),
3357 StringViewArray::from_iter_values(vec!["parquet", "barquet"]),
3358 );
3359
3360 ensure_compatible_write(
3363 BinaryArray::from_iter_values(vec![b"parquet"]),
3364 LargeBinaryArray::from_iter_values(vec![b"barquet"]),
3365 BinaryArray::from_iter_values(vec![b"parquet", b"barquet"]),
3366 );
3367
3368 ensure_compatible_write(
3369 LargeBinaryArray::from_iter_values(vec![b"parquet"]),
3370 BinaryArray::from_iter_values(vec![b"barquet"]),
3371 LargeBinaryArray::from_iter_values(vec![b"parquet", b"barquet"]),
3372 );
3373
3374 ensure_compatible_write(
3375 BinaryArray::from_iter_values(vec![b"parquet"]),
3376 BinaryViewArray::from_iter_values(vec![b"barquet"]),
3377 BinaryArray::from_iter_values(vec![b"parquet", b"barquet"]),
3378 );
3379
3380 ensure_compatible_write(
3381 BinaryViewArray::from_iter_values(vec![b"parquet"]),
3382 BinaryArray::from_iter_values(vec![b"barquet"]),
3383 BinaryViewArray::from_iter_values(vec![b"parquet", b"barquet"]),
3384 );
3385
3386 ensure_compatible_write(
3387 BinaryViewArray::from_iter_values(vec![b"parquet"]),
3388 LargeBinaryArray::from_iter_values(vec![b"barquet"]),
3389 BinaryViewArray::from_iter_values(vec![b"parquet", b"barquet"]),
3390 );
3391
3392 ensure_compatible_write(
3393 LargeBinaryArray::from_iter_values(vec![b"parquet"]),
3394 BinaryViewArray::from_iter_values(vec![b"barquet"]),
3395 LargeBinaryArray::from_iter_values(vec![b"parquet", b"barquet"]),
3396 );
3397
3398 let list_field_metadata = HashMap::from_iter(vec![(
3401 PARQUET_FIELD_ID_META_KEY.to_string(),
3402 "1".to_string(),
3403 )]);
3404 let list_field = Field::new_list_field(DataType::Int32, false);
3405
3406 let values1 = Arc::new(Int32Array::from(vec![0, 1, 2, 3, 4]));
3407 let offsets1 = OffsetBuffer::new(vec![0, 2, 5].into());
3408
3409 let values2 = Arc::new(Int32Array::from(vec![5, 6, 7, 8, 9]));
3410 let offsets2 = OffsetBuffer::new(vec![0, 3, 5].into());
3411
3412 let values_expected = Arc::new(Int32Array::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]));
3413 let offsets_expected = OffsetBuffer::new(vec![0, 2, 5, 8, 10].into());
3414
3415 ensure_compatible_write(
3416 ListArray::try_new(
3418 Arc::new(
3419 list_field
3420 .clone()
3421 .with_metadata(list_field_metadata.clone()),
3422 ),
3423 offsets1,
3424 values1,
3425 None,
3426 )
3427 .unwrap(),
3428 ListArray::try_new(Arc::new(list_field.clone()), offsets2, values2, None).unwrap(),
3430 ListArray::try_new(
3432 Arc::new(
3433 list_field
3434 .clone()
3435 .with_metadata(list_field_metadata.clone()),
3436 ),
3437 offsets_expected,
3438 values_expected,
3439 None,
3440 )
3441 .unwrap(),
3442 );
3443 }
3444
3445 #[test]
3446 #[cfg_attr(miri, ignore)] fn u32_min_max() {
3448 let src = [
3450 u32::MIN,
3451 1,
3452 (i32::MAX as u32) - 1,
3453 i32::MAX as u32,
3454 (i32::MAX as u32) + 1,
3455 u32::MAX - 1,
3456 u32::MAX,
3457 ];
3458 let values = Arc::new(UInt32Array::from_iter_values(src.iter().copied()));
3459 let files = RoundTripTest::new(values).with_nullable(false).run();
3460
3461 for file in files {
3462 let reader = SerializedFileReader::new(file).unwrap();
3464 let metadata = reader.metadata();
3465
3466 let mut row_offset = 0;
3467 for row_group in metadata.row_groups() {
3468 assert_eq!(row_group.num_columns(), 1);
3469 let column = row_group.column(0);
3470
3471 let num_values = column.num_values() as usize;
3472 let src_slice = &src[row_offset..row_offset + num_values];
3473 row_offset += column.num_values() as usize;
3474
3475 let stats = column.statistics().unwrap();
3476 if let Statistics::Int32(stats) = stats {
3477 assert_eq!(
3478 *stats.min_opt().unwrap() as u32,
3479 *src_slice.iter().min().unwrap()
3480 );
3481 assert_eq!(
3482 *stats.max_opt().unwrap() as u32,
3483 *src_slice.iter().max().unwrap()
3484 );
3485 } else {
3486 panic!("Statistics::Int32 missing")
3487 }
3488 }
3489 }
3490 }
3491
3492 #[test]
3493 #[cfg_attr(miri, ignore)] fn u64_min_max() {
3495 let src = [
3497 u64::MIN,
3498 1,
3499 (i64::MAX as u64) - 1,
3500 i64::MAX as u64,
3501 (i64::MAX as u64) + 1,
3502 u64::MAX - 1,
3503 u64::MAX,
3504 ];
3505 let values = Arc::new(UInt64Array::from_iter_values(src.iter().copied()));
3506 let files = RoundTripTest::new(values).with_nullable(false).run();
3507
3508 for file in files {
3509 let reader = SerializedFileReader::new(file).unwrap();
3511 let metadata = reader.metadata();
3512
3513 let mut row_offset = 0;
3514 for row_group in metadata.row_groups() {
3515 assert_eq!(row_group.num_columns(), 1);
3516 let column = row_group.column(0);
3517
3518 let num_values = column.num_values() as usize;
3519 let src_slice = &src[row_offset..row_offset + num_values];
3520 row_offset += column.num_values() as usize;
3521
3522 let stats = column.statistics().unwrap();
3523 if let Statistics::Int64(stats) = stats {
3524 assert_eq!(
3525 *stats.min_opt().unwrap() as u64,
3526 *src_slice.iter().min().unwrap()
3527 );
3528 assert_eq!(
3529 *stats.max_opt().unwrap() as u64,
3530 *src_slice.iter().max().unwrap()
3531 );
3532 } else {
3533 panic!("Statistics::Int64 missing")
3534 }
3535 }
3536 }
3537 }
3538
3539 #[test]
3540 #[cfg_attr(miri, ignore)] fn statistics_null_counts_only_nulls() {
3542 let values = Arc::new(UInt64Array::from(vec![None, None]));
3544 let files = RoundTripTest::new(values).run();
3545
3546 for file in files {
3547 let reader = SerializedFileReader::new(file).unwrap();
3549 let metadata = reader.metadata();
3550 assert_eq!(metadata.num_row_groups(), 1);
3551 let row_group = metadata.row_group(0);
3552 assert_eq!(row_group.num_columns(), 1);
3553 let column = row_group.column(0);
3554 let stats = column.statistics().unwrap();
3555 assert_eq!(stats.null_count_opt(), Some(2));
3556 }
3557 }
3558
3559 #[test]
3560 #[cfg_attr(miri, ignore)] fn test_list_of_struct_roundtrip() {
3562 let int_field = Field::new("a", DataType::Int32, true);
3564 let int_field2 = Field::new("b", DataType::Int32, true);
3565
3566 let int_builder = Int32Builder::with_capacity(10);
3567 let int_builder2 = Int32Builder::with_capacity(10);
3568
3569 let struct_builder = StructBuilder::new(
3570 vec![int_field, int_field2],
3571 vec![Box::new(int_builder), Box::new(int_builder2)],
3572 );
3573 let mut list_builder = ListBuilder::new(struct_builder);
3574
3575 let values = list_builder.values();
3580 values
3581 .field_builder::<Int32Builder>(0)
3582 .unwrap()
3583 .append_value(1);
3584 values
3585 .field_builder::<Int32Builder>(1)
3586 .unwrap()
3587 .append_value(2);
3588 values.append(true);
3589 list_builder.append(true);
3590
3591 list_builder.append(true);
3593
3594 list_builder.append(false);
3596
3597 let values = list_builder.values();
3599 values
3600 .field_builder::<Int32Builder>(0)
3601 .unwrap()
3602 .append_null();
3603 values
3604 .field_builder::<Int32Builder>(1)
3605 .unwrap()
3606 .append_null();
3607 values.append(false);
3608 values
3609 .field_builder::<Int32Builder>(0)
3610 .unwrap()
3611 .append_null();
3612 values
3613 .field_builder::<Int32Builder>(1)
3614 .unwrap()
3615 .append_null();
3616 values.append(false);
3617 list_builder.append(true);
3618
3619 let values = list_builder.values();
3621 values
3622 .field_builder::<Int32Builder>(0)
3623 .unwrap()
3624 .append_null();
3625 values
3626 .field_builder::<Int32Builder>(1)
3627 .unwrap()
3628 .append_value(3);
3629 values.append(true);
3630 list_builder.append(true);
3631
3632 let values = list_builder.values();
3634 values
3635 .field_builder::<Int32Builder>(0)
3636 .unwrap()
3637 .append_value(2);
3638 values
3639 .field_builder::<Int32Builder>(1)
3640 .unwrap()
3641 .append_null();
3642 values.append(true);
3643 list_builder.append(true);
3644
3645 let array = Arc::new(list_builder.finish());
3646
3647 RoundTripTest::new(array).run();
3648 }
3649
3650 fn row_group_sizes(metadata: &ParquetMetaData) -> Vec<i64> {
3651 metadata.row_groups().iter().map(|x| x.num_rows()).collect()
3652 }
3653
3654 #[test]
3655 fn test_aggregates_records() {
3656 let arrays = [
3657 Int32Array::from((0..100).collect::<Vec<_>>()),
3658 Int32Array::from((0..50).collect::<Vec<_>>()),
3659 Int32Array::from((200..500).collect::<Vec<_>>()),
3660 ];
3661
3662 let schema = Arc::new(Schema::new(vec![Field::new(
3663 "int",
3664 ArrowDataType::Int32,
3665 false,
3666 )]));
3667
3668 let file = tempfile::tempfile().unwrap();
3669
3670 let props = WriterProperties::builder()
3671 .set_max_row_group_row_count(Some(200))
3672 .build();
3673
3674 let mut writer =
3675 ArrowWriter::try_new(file.try_clone().unwrap(), schema.clone(), Some(props)).unwrap();
3676
3677 for array in arrays {
3678 let batch = RecordBatch::try_new(schema.clone(), vec![Arc::new(array)]).unwrap();
3679 writer.write(&batch).unwrap();
3680 }
3681
3682 writer.close().unwrap();
3683
3684 let builder = ParquetRecordBatchReaderBuilder::try_new(file).unwrap();
3685 assert_eq!(&row_group_sizes(builder.metadata()), &[200, 200, 50]);
3686
3687 let batches = builder
3688 .with_batch_size(100)
3689 .build()
3690 .unwrap()
3691 .collect::<ArrowResult<Vec<_>>>()
3692 .unwrap();
3693
3694 assert_eq!(batches.len(), 5);
3695 assert!(batches.iter().all(|x| x.num_columns() == 1));
3696
3697 let batch_sizes: Vec<_> = batches.iter().map(|x| x.num_rows()).collect();
3698
3699 assert_eq!(&batch_sizes, &[100, 100, 100, 100, 50]);
3700
3701 let values: Vec<_> = batches
3702 .iter()
3703 .flat_map(|x| {
3704 x.column(0)
3705 .as_any()
3706 .downcast_ref::<Int32Array>()
3707 .unwrap()
3708 .values()
3709 .iter()
3710 .copied()
3711 })
3712 .collect();
3713
3714 let expected_values: Vec<_> = [0..100, 0..50, 200..500].into_iter().flatten().collect();
3715 assert_eq!(&values, &expected_values)
3716 }
3717
3718 #[test]
3719 fn complex_aggregate() {
3720 let field_a = Arc::new(Field::new("leaf_a", DataType::Int32, false));
3722 let field_b = Arc::new(Field::new("leaf_b", DataType::Int32, true));
3723 let struct_a = Arc::new(Field::new(
3724 "struct_a",
3725 DataType::Struct(vec![field_a.clone(), field_b.clone()].into()),
3726 true,
3727 ));
3728
3729 let list_a = Arc::new(Field::new("list", DataType::List(struct_a), true));
3730 let struct_b = Arc::new(Field::new(
3731 "struct_b",
3732 DataType::Struct(vec![list_a.clone()].into()),
3733 false,
3734 ));
3735
3736 let schema = Arc::new(Schema::new(vec![struct_b]));
3737
3738 let field_a_array = Int32Array::from(vec![1, 2, 3, 4, 5, 6]);
3740 let field_b_array =
3741 Int32Array::from_iter(vec![Some(1), None, Some(2), None, None, Some(6)]);
3742
3743 let struct_a_array = StructArray::from(vec![
3744 (field_a.clone(), Arc::new(field_a_array) as ArrayRef),
3745 (field_b.clone(), Arc::new(field_b_array) as ArrayRef),
3746 ]);
3747
3748 let list_data = ArrayDataBuilder::new(list_a.data_type().clone())
3749 .len(5)
3750 .add_buffer(Buffer::from_iter(vec![
3751 0_i32, 1_i32, 1_i32, 3_i32, 3_i32, 5_i32,
3752 ]))
3753 .null_bit_buffer(Some(Buffer::from_iter(vec![
3754 true, false, true, false, true,
3755 ])))
3756 .child_data(vec![struct_a_array.into_data()])
3757 .build()
3758 .unwrap();
3759
3760 let list_a_array = Arc::new(ListArray::from(list_data)) as ArrayRef;
3761 let struct_b_array = StructArray::from(vec![(list_a.clone(), list_a_array)]);
3762
3763 let batch1 =
3764 RecordBatch::try_from_iter(vec![("struct_b", Arc::new(struct_b_array) as ArrayRef)])
3765 .unwrap();
3766
3767 let field_a_array = Int32Array::from(vec![6, 7, 8, 9, 10]);
3768 let field_b_array = Int32Array::from_iter(vec![None, None, None, Some(1), None]);
3769
3770 let struct_a_array = StructArray::from(vec![
3771 (field_a, Arc::new(field_a_array) as ArrayRef),
3772 (field_b, Arc::new(field_b_array) as ArrayRef),
3773 ]);
3774
3775 let list_data = ArrayDataBuilder::new(list_a.data_type().clone())
3776 .len(2)
3777 .add_buffer(Buffer::from_iter(vec![0_i32, 4_i32, 5_i32]))
3778 .child_data(vec![struct_a_array.into_data()])
3779 .build()
3780 .unwrap();
3781
3782 let list_a_array = Arc::new(ListArray::from(list_data)) as ArrayRef;
3783 let struct_b_array = StructArray::from(vec![(list_a, list_a_array)]);
3784
3785 let batch2 =
3786 RecordBatch::try_from_iter(vec![("struct_b", Arc::new(struct_b_array) as ArrayRef)])
3787 .unwrap();
3788
3789 let batches = &[batch1, batch2];
3790
3791 let expected = r"
3794 +-------------------------------------------------------------------------------------------------------+
3795 | struct_b |
3796 +-------------------------------------------------------------------------------------------------------+
3797 | {list: [{leaf_a: 1, leaf_b: 1}]} |
3798 | {list: } |
3799 | {list: [{leaf_a: 2, leaf_b: }, {leaf_a: 3, leaf_b: 2}]} |
3800 | {list: } |
3801 | {list: [{leaf_a: 4, leaf_b: }, {leaf_a: 5, leaf_b: }]} |
3802 | {list: [{leaf_a: 6, leaf_b: }, {leaf_a: 7, leaf_b: }, {leaf_a: 8, leaf_b: }, {leaf_a: 9, leaf_b: 1}]} |
3803 | {list: [{leaf_a: 10, leaf_b: }]} |
3804 +-------------------------------------------------------------------------------------------------------+
3805 ".trim().split('\n').map(|x| x.trim()).collect::<Vec<_>>().join("\n");
3806
3807 let actual = pretty_format_batches(batches).unwrap().to_string();
3808 assert_eq!(actual, expected);
3809
3810 let file = tempfile::tempfile().unwrap();
3812 let props = WriterProperties::builder()
3813 .set_max_row_group_row_count(Some(6))
3814 .build();
3815
3816 let mut writer =
3817 ArrowWriter::try_new(file.try_clone().unwrap(), schema, Some(props)).unwrap();
3818
3819 for batch in batches {
3820 writer.write(batch).unwrap();
3821 }
3822 writer.close().unwrap();
3823
3824 let builder = ParquetRecordBatchReaderBuilder::try_new(file).unwrap();
3829 assert_eq!(&row_group_sizes(builder.metadata()), &[6, 1]);
3830
3831 let batches = builder
3832 .with_batch_size(2)
3833 .build()
3834 .unwrap()
3835 .collect::<ArrowResult<Vec<_>>>()
3836 .unwrap();
3837
3838 assert_eq!(batches.len(), 4);
3839 let batch_counts: Vec<_> = batches.iter().map(|x| x.num_rows()).collect();
3840 assert_eq!(&batch_counts, &[2, 2, 2, 1]);
3841
3842 let actual = pretty_format_batches(&batches).unwrap().to_string();
3843 assert_eq!(actual, expected);
3844 }
3845
3846 #[test]
3847 fn test_arrow_writer_metadata() {
3848 let batch_schema = Schema::new(vec![Field::new("int32", DataType::Int32, false)]);
3849 let file_schema = batch_schema.clone().with_metadata([("foo", "bar")]);
3850
3851 let batch = RecordBatch::try_new(
3852 Arc::new(batch_schema),
3853 vec![Arc::new(Int32Array::from(vec![1, 2, 3, 4])) as _],
3854 )
3855 .unwrap();
3856
3857 let mut buf = Vec::with_capacity(1024);
3858 let mut writer = ArrowWriter::try_new(&mut buf, Arc::new(file_schema), None).unwrap();
3859 writer.write(&batch).unwrap();
3860 writer.close().unwrap();
3861 }
3862
3863 #[test]
3864 fn test_arrow_writer_nullable() {
3865 let batch_schema = Schema::new(vec![Field::new("int32", DataType::Int32, false)]);
3866 let file_schema = Schema::new(vec![Field::new("int32", DataType::Int32, true)]);
3867 let file_schema = Arc::new(file_schema);
3868
3869 let batch = RecordBatch::try_new(
3870 Arc::new(batch_schema),
3871 vec![Arc::new(Int32Array::from(vec![1, 2, 3, 4])) as _],
3872 )
3873 .unwrap();
3874
3875 let mut buf = Vec::with_capacity(1024);
3876 let mut writer = ArrowWriter::try_new(&mut buf, file_schema.clone(), None).unwrap();
3877 writer.write(&batch).unwrap();
3878 writer.close().unwrap();
3879
3880 let mut read = ParquetRecordBatchReader::try_new(Bytes::from(buf), 1024).unwrap();
3881 let back = read.next().unwrap().unwrap();
3882 assert_eq!(back.schema(), file_schema);
3883 assert_ne!(back.schema(), batch.schema());
3884 assert_eq!(back.column(0).as_ref(), batch.column(0).as_ref());
3885 }
3886
3887 #[test]
3888 fn in_progress_accounting() {
3889 let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
3891
3892 let a = Int32Array::from(vec![1, 2, 3, 4, 5]);
3894
3895 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(a)]).unwrap();
3897
3898 let mut writer = ArrowWriter::try_new(vec![], batch.schema(), None).unwrap();
3899
3900 assert_eq!(writer.in_progress_size(), 0);
3902 assert_eq!(writer.in_progress_rows(), 0);
3903 assert_eq!(writer.memory_size(), 0);
3904 assert_eq!(writer.bytes_written(), 4); writer.write(&batch).unwrap();
3906
3907 let initial_size = writer.in_progress_size();
3909 assert!(initial_size > 0);
3910 assert_eq!(writer.in_progress_rows(), 5);
3911 let initial_memory = writer.memory_size();
3912 assert!(initial_memory > 0);
3913 assert!(
3915 initial_size <= initial_memory,
3916 "{initial_size} <= {initial_memory}"
3917 );
3918
3919 writer.write(&batch).unwrap();
3921 assert!(writer.in_progress_size() > initial_size);
3922 assert_eq!(writer.in_progress_rows(), 10);
3923 assert!(writer.memory_size() > initial_memory);
3924 assert!(
3925 writer.in_progress_size() <= writer.memory_size(),
3926 "in_progress_size {} <= memory_size {}",
3927 writer.in_progress_size(),
3928 writer.memory_size()
3929 );
3930
3931 let pre_flush_bytes_written = writer.bytes_written();
3933 writer.flush().unwrap();
3934 assert_eq!(writer.in_progress_size(), 0);
3935 assert_eq!(writer.memory_size(), 0);
3936 assert!(writer.bytes_written() > pre_flush_bytes_written);
3937
3938 writer.close().unwrap();
3939 }
3940
3941 #[test]
3942 fn test_writer_all_null() {
3943 let a = Int32Array::from(vec![1, 2, 3, 4, 5]);
3944 let b = Int32Array::new(vec![0; 5].into(), Some(NullBuffer::new_null(5)));
3945 let batch = RecordBatch::try_from_iter(vec![
3946 ("a", Arc::new(a) as ArrayRef),
3947 ("b", Arc::new(b) as ArrayRef),
3948 ])
3949 .unwrap();
3950
3951 let mut buf = Vec::with_capacity(1024);
3952 let mut writer = ArrowWriter::try_new(&mut buf, batch.schema(), None).unwrap();
3953 writer.write(&batch).unwrap();
3954 writer.close().unwrap();
3955
3956 let bytes = Bytes::from(buf);
3957 let options = ReadOptionsBuilder::new().with_page_index().build();
3958 let reader = SerializedFileReader::new_with_options(bytes, options).unwrap();
3959 let index = reader.metadata().page_index().unwrap();
3960
3961 assert_eq!(index.num_data_pages(0, 0), Some(1)); assert_eq!(index.num_data_pages(0, 1), Some(1)); }
3964
3965 #[test]
3966 fn test_disabled_statistics_with_page() {
3967 let file_schema = Schema::new(vec![
3968 Field::new("a", DataType::Utf8, true),
3969 Field::new("b", DataType::Utf8, true),
3970 ]);
3971 let file_schema = Arc::new(file_schema);
3972
3973 let batch = RecordBatch::try_new(
3974 file_schema.clone(),
3975 vec![
3976 Arc::new(StringArray::from(vec!["a", "b", "c", "d"])) as _,
3977 Arc::new(StringArray::from(vec!["w", "x", "y", "z"])) as _,
3978 ],
3979 )
3980 .unwrap();
3981
3982 let props = WriterProperties::builder()
3983 .set_statistics_enabled(EnabledStatistics::None)
3984 .set_column_statistics_enabled("a".into(), EnabledStatistics::Page)
3985 .build();
3986
3987 let mut buf = Vec::with_capacity(1024);
3988 let mut writer = ArrowWriter::try_new(&mut buf, file_schema.clone(), Some(props)).unwrap();
3989 writer.write(&batch).unwrap();
3990
3991 let metadata = writer.close().unwrap();
3992 assert_eq!(metadata.num_row_groups(), 1);
3993 let row_group = metadata.row_group(0);
3994 assert_eq!(row_group.num_columns(), 2);
3995 assert!(row_group.column(0).offset_index_offset().is_some());
3997 assert!(row_group.column(0).column_index_offset().is_some());
3998 assert!(row_group.column(1).offset_index_offset().is_some());
4000 assert!(row_group.column(1).column_index_offset().is_none());
4001
4002 let options = ReadOptionsBuilder::new().with_page_index().build();
4003 let reader = SerializedFileReader::new_with_options(Bytes::from(buf), options).unwrap();
4004
4005 let row_group = reader.get_row_group(0).unwrap();
4006 let a_col = row_group.metadata().column(0);
4007 let b_col = row_group.metadata().column(1);
4008
4009 if let Statistics::ByteArray(byte_array_stats) = a_col.statistics().unwrap() {
4011 let min = byte_array_stats.min_opt().unwrap();
4012 let max = byte_array_stats.max_opt().unwrap();
4013
4014 assert_eq!(min.as_bytes(), b"a");
4015 assert_eq!(max.as_bytes(), b"d");
4016 } else {
4017 panic!("expecting Statistics::ByteArray");
4018 }
4019
4020 assert!(b_col.statistics().is_none());
4022
4023 let page_index = reader.metadata().page_index().unwrap();
4024
4025 let a_idx = page_index.column_index(0, 0);
4026 assert!(
4027 matches!(a_idx, Some(ColumnIndexMetaData::BYTE_ARRAY(_))),
4028 "{a_idx:?}"
4029 );
4030 let b_idx = page_index.column_index(0, 1);
4031 assert!(b_idx.is_none(), "{b_idx:?}");
4032 }
4033
4034 #[test]
4035 fn test_disabled_statistics_with_chunk() {
4036 let file_schema = Schema::new(vec![
4037 Field::new("a", DataType::Utf8, true),
4038 Field::new("b", DataType::Utf8, true),
4039 ]);
4040 let file_schema = Arc::new(file_schema);
4041
4042 let batch = RecordBatch::try_new(
4043 file_schema.clone(),
4044 vec![
4045 Arc::new(StringArray::from(vec!["a", "b", "c", "d"])) as _,
4046 Arc::new(StringArray::from(vec!["w", "x", "y", "z"])) as _,
4047 ],
4048 )
4049 .unwrap();
4050
4051 let props = WriterProperties::builder()
4052 .set_statistics_enabled(EnabledStatistics::None)
4053 .set_column_statistics_enabled("a".into(), EnabledStatistics::Chunk)
4054 .build();
4055
4056 let mut buf = Vec::with_capacity(1024);
4057 let mut writer = ArrowWriter::try_new(&mut buf, file_schema.clone(), Some(props)).unwrap();
4058 writer.write(&batch).unwrap();
4059
4060 let metadata = writer.close().unwrap();
4061 assert_eq!(metadata.num_row_groups(), 1);
4062 let row_group = metadata.row_group(0);
4063 assert_eq!(row_group.num_columns(), 2);
4064 assert!(row_group.column(0).offset_index_offset().is_some());
4066 assert!(row_group.column(0).column_index_offset().is_none());
4067 assert!(row_group.column(1).offset_index_offset().is_some());
4069 assert!(row_group.column(1).column_index_offset().is_none());
4070
4071 let options = ReadOptionsBuilder::new().with_page_index().build();
4072 let reader = SerializedFileReader::new_with_options(Bytes::from(buf), options).unwrap();
4073
4074 let row_group = reader.get_row_group(0).unwrap();
4075 let a_col = row_group.metadata().column(0);
4076 let b_col = row_group.metadata().column(1);
4077
4078 if let Statistics::ByteArray(byte_array_stats) = a_col.statistics().unwrap() {
4080 let min = byte_array_stats.min_opt().unwrap();
4081 let max = byte_array_stats.max_opt().unwrap();
4082
4083 assert_eq!(min.as_bytes(), b"a");
4084 assert_eq!(max.as_bytes(), b"d");
4085 } else {
4086 panic!("expecting Statistics::ByteArray");
4087 }
4088
4089 assert!(b_col.statistics().is_none());
4091
4092 let page_index = reader.metadata().page_index().unwrap();
4093
4094 let a_idx = page_index.column_index(0, 0);
4095 assert!(a_idx.is_none(), "{a_idx:?}");
4096 let b_idx = page_index.column_index(0, 1);
4097 assert!(b_idx.is_none(), "{b_idx:?}");
4098 }
4099
4100 #[test]
4101 fn test_arrow_writer_skip_metadata() {
4102 let batch_schema = Schema::new(vec![Field::new("int32", DataType::Int32, false)]);
4103 let file_schema = Arc::new(batch_schema.clone());
4104
4105 let batch = RecordBatch::try_new(
4106 Arc::new(batch_schema),
4107 vec![Arc::new(Int32Array::from(vec![1, 2, 3, 4])) as _],
4108 )
4109 .unwrap();
4110 let skip_options = ArrowWriterOptions::new().with_skip_arrow_metadata(true);
4111
4112 let mut buf = Vec::with_capacity(1024);
4113 let mut writer =
4114 ArrowWriter::try_new_with_options(&mut buf, file_schema.clone(), skip_options).unwrap();
4115 writer.write(&batch).unwrap();
4116 writer.close().unwrap();
4117
4118 let bytes = Bytes::from(buf);
4119 let reader_builder = ParquetRecordBatchReaderBuilder::try_new(bytes).unwrap();
4120 assert_eq!(file_schema, *reader_builder.schema());
4121 if let Some(key_value_metadata) = reader_builder
4122 .metadata()
4123 .file_metadata()
4124 .key_value_metadata()
4125 {
4126 assert!(
4127 !key_value_metadata
4128 .iter()
4129 .any(|kv| kv.key.as_str() == ARROW_SCHEMA_META_KEY)
4130 );
4131 }
4132 }
4133
4134 #[test]
4135 fn test_arrow_writer_skip_path_in_schema() {
4136 let batch_schema = Schema::new(vec![Field::new("int32", DataType::Int32, false)]);
4137 let file_schema = Arc::new(batch_schema.clone());
4138
4139 let batch = RecordBatch::try_new(
4140 Arc::new(batch_schema),
4141 vec![Arc::new(Int32Array::from(vec![1, 2, 3, 4])) as _],
4142 )
4143 .unwrap();
4144
4145 let skip_options = ArrowWriterOptions::new();
4147
4148 let mut buf = Vec::with_capacity(1024);
4149 let mut writer =
4150 ArrowWriter::try_new_with_options(&mut buf, file_schema.clone(), skip_options).unwrap();
4151 writer.write(&batch).unwrap();
4152 writer.close().unwrap();
4153
4154 let skip_options = ArrowWriterOptions::new().with_properties(
4156 WriterProperties::builder()
4157 .set_write_path_in_schema(false)
4158 .build(),
4159 );
4160
4161 let mut buf2 = Vec::with_capacity(1024);
4162 let mut writer =
4163 ArrowWriter::try_new_with_options(&mut buf2, file_schema.clone(), skip_options)
4164 .unwrap();
4165 writer.write(&batch).unwrap();
4166 writer.close().unwrap();
4167
4168 assert!(buf.len() > buf2.len());
4170 }
4171
4172 #[test]
4173 fn mismatched_schemas() {
4174 let batch_schema = Schema::new(vec![Field::new("count", DataType::Int32, false)]);
4175 let file_schema = Arc::new(Schema::new(vec![Field::new(
4176 "temperature",
4177 DataType::Float64,
4178 false,
4179 )]));
4180
4181 let batch = RecordBatch::try_new(
4182 Arc::new(batch_schema),
4183 vec![Arc::new(Int32Array::from(vec![1, 2, 3, 4])) as _],
4184 )
4185 .unwrap();
4186
4187 let mut buf = Vec::with_capacity(1024);
4188 let mut writer = ArrowWriter::try_new(&mut buf, file_schema.clone(), None).unwrap();
4189
4190 let err = writer.write(&batch).unwrap_err().to_string();
4191 assert_eq!(
4192 err,
4193 "Arrow: Incompatible type. Field 'temperature' has type Float64, array has type Int32"
4194 );
4195 }
4196
4197 #[test]
4198 fn test_roundtrip_empty_schema() {
4200 let empty_batch = RecordBatch::try_new_with_options(
4202 Arc::new(Schema::empty()),
4203 vec![],
4204 &RecordBatchOptions::default().with_row_count(Some(0)),
4205 )
4206 .unwrap();
4207
4208 let mut parquet_bytes: Vec<u8> = Vec::new();
4210 let mut writer =
4211 ArrowWriter::try_new(&mut parquet_bytes, empty_batch.schema(), None).unwrap();
4212 writer.write(&empty_batch).unwrap();
4213 writer.close().unwrap();
4214
4215 let bytes = Bytes::from(parquet_bytes);
4217 let reader = ParquetRecordBatchReaderBuilder::try_new(bytes).unwrap();
4218 assert_eq!(reader.schema(), &empty_batch.schema());
4219 let batches: Vec<_> = reader
4220 .build()
4221 .unwrap()
4222 .collect::<ArrowResult<Vec<_>>>()
4223 .unwrap();
4224 assert_eq!(batches.len(), 0);
4225 }
4226
4227 #[test]
4228 fn test_page_stats_not_written_by_default() {
4229 let string_field = Field::new("a", DataType::Utf8, false);
4230 let schema = Schema::new(vec![string_field]);
4231 let raw_string_values = vec!["Blart Versenwald III"];
4232 let string_values = StringArray::from(raw_string_values.clone());
4233 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(string_values)]).unwrap();
4234
4235 let props = WriterProperties::builder()
4236 .set_statistics_enabled(EnabledStatistics::Page)
4237 .set_dictionary_enabled(false)
4238 .set_encoding(Encoding::PLAIN)
4239 .set_compression(crate::basic::Compression::UNCOMPRESSED)
4240 .build();
4241
4242 let file = roundtrip_opts(&batch, props);
4243
4244 let first_page = &file[4..];
4249 let mut prot = ThriftSliceInputProtocol::new(first_page);
4250 let hdr = PageHeader::read_thrift(&mut prot).unwrap();
4251 let stats = hdr.data_page_header.unwrap().statistics;
4252
4253 assert!(stats.is_none());
4254 }
4255
4256 #[test]
4257 fn test_page_stats_when_enabled() {
4258 let string_field = Field::new("a", DataType::Utf8, false);
4259 let schema = Schema::new(vec![string_field]);
4260 let raw_string_values = vec!["Blart Versenwald III", "Andrew Lamb"];
4261 let string_values = StringArray::from(raw_string_values.clone());
4262 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(string_values)]).unwrap();
4263
4264 let props = WriterProperties::builder()
4265 .set_statistics_enabled(EnabledStatistics::Page)
4266 .set_dictionary_enabled(false)
4267 .set_encoding(Encoding::PLAIN)
4268 .set_write_page_header_statistics(true)
4269 .set_compression(crate::basic::Compression::UNCOMPRESSED)
4270 .build();
4271
4272 let file = roundtrip_opts(&batch, props);
4273
4274 let first_page = &file[4..];
4279 let mut prot = ThriftSliceInputProtocol::new(first_page);
4280 let hdr = PageHeader::read_thrift(&mut prot).unwrap();
4281 let stats = hdr.data_page_header.unwrap().statistics;
4282
4283 let stats = stats.unwrap();
4284 assert!(stats.is_max_value_exact.unwrap());
4286 assert!(stats.is_min_value_exact.unwrap());
4287 assert_eq!(stats.max_value.unwrap(), b"Blart Versenwald III");
4288 assert_eq!(stats.min_value.unwrap(), b"Andrew Lamb");
4289 }
4290
4291 #[test]
4292 fn test_page_stats_truncation() {
4293 let string_field = Field::new("a", DataType::Utf8, false);
4294 let binary_field = Field::new("b", DataType::Binary, false);
4295 let schema = Schema::new(vec![string_field, binary_field]);
4296
4297 let raw_string_values = vec!["Blart Versenwald III"];
4298 let raw_binary_values = [b"Blart Versenwald III".to_vec()];
4299 let raw_binary_value_refs = raw_binary_values
4300 .iter()
4301 .map(|x| x.as_slice())
4302 .collect::<Vec<_>>();
4303
4304 let string_values = StringArray::from(raw_string_values.clone());
4305 let binary_values = BinaryArray::from(raw_binary_value_refs);
4306 let batch = RecordBatch::try_new(
4307 Arc::new(schema),
4308 vec![Arc::new(string_values), Arc::new(binary_values)],
4309 )
4310 .unwrap();
4311
4312 let props = WriterProperties::builder()
4313 .set_statistics_truncate_length(Some(2))
4314 .set_dictionary_enabled(false)
4315 .set_encoding(Encoding::PLAIN)
4316 .set_write_page_header_statistics(true)
4317 .set_compression(crate::basic::Compression::UNCOMPRESSED)
4318 .build();
4319
4320 let file = roundtrip_opts(&batch, props);
4321
4322 let first_page = &file[4..];
4327 let mut prot = ThriftSliceInputProtocol::new(first_page);
4328 let hdr = PageHeader::read_thrift(&mut prot).unwrap();
4329 let stats = hdr.data_page_header.unwrap().statistics;
4330 assert!(stats.is_some());
4331 let stats = stats.unwrap();
4332 assert!(!stats.is_max_value_exact.unwrap());
4334 assert!(!stats.is_min_value_exact.unwrap());
4335 assert_eq!(stats.max_value.unwrap(), b"Bm");
4336 assert_eq!(stats.min_value.unwrap(), b"Bl");
4337
4338 let second_page = &prot.as_slice()[hdr.compressed_page_size as usize..];
4340 let mut prot = ThriftSliceInputProtocol::new(second_page);
4341 let hdr = PageHeader::read_thrift(&mut prot).unwrap();
4342 let stats = hdr.data_page_header.unwrap().statistics;
4343 assert!(stats.is_some());
4344 let stats = stats.unwrap();
4345 assert!(!stats.is_max_value_exact.unwrap());
4347 assert!(!stats.is_min_value_exact.unwrap());
4348 assert_eq!(stats.max_value.unwrap(), b"Bm");
4349 assert_eq!(stats.min_value.unwrap(), b"Bl");
4350 }
4351
4352 #[test]
4353 fn test_page_encoding_statistics_roundtrip() {
4354 let batch_schema = Schema::new(vec![Field::new(
4355 "int32",
4356 arrow_schema::DataType::Int32,
4357 false,
4358 )]);
4359
4360 let batch = RecordBatch::try_new(
4361 Arc::new(batch_schema.clone()),
4362 vec![Arc::new(Int32Array::from(vec![1, 2, 3, 4])) as _],
4363 )
4364 .unwrap();
4365
4366 let mut file: File = tempfile::tempfile().unwrap();
4367 let mut writer = ArrowWriter::try_new(&mut file, Arc::new(batch_schema), None).unwrap();
4368 writer.write(&batch).unwrap();
4369 let file_metadata = writer.close().unwrap();
4370
4371 assert_eq!(file_metadata.num_row_groups(), 1);
4372 assert_eq!(file_metadata.row_group(0).num_columns(), 1);
4373 assert!(
4374 file_metadata
4375 .row_group(0)
4376 .column(0)
4377 .page_encoding_stats()
4378 .is_some()
4379 );
4380 let chunk_page_stats = file_metadata
4381 .row_group(0)
4382 .column(0)
4383 .page_encoding_stats()
4384 .unwrap();
4385
4386 let options = ReadOptionsBuilder::new()
4388 .with_page_index()
4389 .with_encoding_stats_as_mask(false)
4390 .build();
4391 let reader = SerializedFileReader::new_with_options(file, options).unwrap();
4392
4393 let rowgroup = reader.get_row_group(0).expect("row group missing");
4394 assert_eq!(rowgroup.num_columns(), 1);
4395 let column = rowgroup.metadata().column(0);
4396 assert!(column.page_encoding_stats().is_some());
4397 let file_page_stats = column.page_encoding_stats().unwrap();
4398 assert_eq!(chunk_page_stats, file_page_stats);
4399 }
4400
4401 #[test]
4402 #[cfg_attr(miri, ignore)] fn test_different_dict_page_size_limit() {
4404 let array = Arc::new(Int64Array::from_iter(0..1024 * 1024));
4405 let schema = Arc::new(Schema::new(vec![
4406 Field::new("col0", arrow_schema::DataType::Int64, false),
4407 Field::new("col1", arrow_schema::DataType::Int64, false),
4408 ]));
4409 let batch =
4410 arrow_array::RecordBatch::try_new(schema.clone(), vec![array.clone(), array]).unwrap();
4411
4412 let props = WriterProperties::builder()
4413 .set_dictionary_page_size_limit(1024 * 1024)
4414 .set_column_dictionary_page_size_limit(ColumnPath::from("col1"), 1024 * 1024 * 4)
4415 .build();
4416 let mut writer = ArrowWriter::try_new(Vec::new(), schema, Some(props)).unwrap();
4417 writer.write(&batch).unwrap();
4418 let data = Bytes::from(writer.into_inner().unwrap());
4419
4420 let mut metadata = ParquetMetaDataReader::new();
4421 metadata.try_parse(&data).unwrap();
4422 let metadata = metadata.finish().unwrap();
4423 let col0_meta = metadata.row_group(0).column(0);
4424 let col1_meta = metadata.row_group(0).column(1);
4425
4426 let get_dict_page_size = move |meta: &ColumnChunkMetaData| {
4427 let mut reader =
4428 SerializedPageReader::new(Arc::new(data.clone()), meta, 0, None).unwrap();
4429 let page = reader.get_next_page().unwrap().unwrap();
4430 match page {
4431 Page::DictionaryPage { buf, .. } => buf.len(),
4432 _ => panic!("expected DictionaryPage"),
4433 }
4434 };
4435
4436 assert_eq!(get_dict_page_size(col0_meta), 1024 * 1024);
4437 assert_eq!(get_dict_page_size(col1_meta), 1024 * 1024 * 4);
4438 }
4439
4440 #[test]
4441 #[cfg_attr(miri, ignore)] fn test_arrow_writer_granular_mode_roundtrip() {
4443 let small = "tiny".to_string();
4452 let big = "x".repeat(64 * 1024);
4453 let strings: Vec<String> = (0..256)
4454 .map(|i| {
4455 if i % 16 == 0 {
4456 big.clone()
4457 } else {
4458 small.clone()
4459 }
4460 })
4461 .collect();
4462
4463 let schema = Arc::new(Schema::new(vec![Field::new(
4464 "col",
4465 ArrowDataType::Utf8,
4466 false,
4467 )]));
4468 let batch = RecordBatch::try_new(
4469 schema.clone(),
4470 vec![Arc::new(StringArray::from(strings.clone())) as _],
4471 )
4472 .unwrap();
4473
4474 let props = WriterProperties::builder()
4475 .set_dictionary_enabled(false)
4476 .set_data_page_size_limit(16 * 1024)
4477 .build();
4478 let mut writer = ArrowWriter::try_new(Vec::new(), schema, Some(props)).unwrap();
4479 writer.write(&batch).unwrap();
4480 let data = Bytes::from(writer.into_inner().unwrap());
4481
4482 let mut reader = ParquetRecordBatchReader::try_new(data, 1024).unwrap();
4483 let read = reader.next().unwrap().unwrap();
4484 assert!(reader.next().is_none(), "expected one batch");
4485 let col = read
4486 .column(0)
4487 .as_any()
4488 .downcast_ref::<StringArray>()
4489 .unwrap();
4490 assert_eq!(col.len(), strings.len());
4491 for (i, expected) in strings.iter().enumerate() {
4492 assert_eq!(
4493 col.value(i),
4494 expected.as_str(),
4495 "value mismatch at index {i}"
4496 );
4497 }
4498 }
4499
4500 #[test]
4501 fn test_arrow_writer_all_null_string_column() {
4502 let num_rows = 1024;
4507 let schema = Arc::new(Schema::new(vec![Field::new(
4508 "col",
4509 ArrowDataType::Utf8,
4510 true,
4511 )]));
4512 let nulls: Vec<Option<&str>> = vec![None; num_rows];
4513 let batch = RecordBatch::try_new(
4514 schema.clone(),
4515 vec![Arc::new(StringArray::from(nulls)) as _],
4516 )
4517 .unwrap();
4518
4519 let props = WriterProperties::builder()
4520 .set_dictionary_enabled(false)
4521 .set_data_page_size_limit(16 * 1024)
4522 .build();
4523 let mut writer = ArrowWriter::try_new(Vec::new(), schema, Some(props)).unwrap();
4524 writer.write(&batch).unwrap();
4525 let data = Bytes::from(writer.into_inner().unwrap());
4526
4527 let mut metadata = ParquetMetaDataReader::new();
4530 metadata.try_parse(&data).unwrap();
4531 let metadata = metadata.finish().unwrap();
4532 let row_group = metadata.row_group(0);
4533 let col_meta = row_group.column(0);
4534 assert_eq!(row_group.num_rows() as usize, num_rows);
4535 if let Some(stats) = col_meta.statistics() {
4538 assert_eq!(
4539 stats.null_count_opt().unwrap_or(0) as usize,
4540 num_rows,
4541 "expected all-null column to report null_count = num_rows"
4542 );
4543 }
4544
4545 let mut reader =
4546 SerializedPageReader::new(Arc::new(data.clone()), col_meta, num_rows, None).unwrap();
4547 let mut total_values = 0u32;
4548 while let Some(page) = reader.get_next_page().unwrap() {
4549 if matches!(page, Page::DataPage { .. } | Page::DataPageV2 { .. }) {
4550 total_values += page.num_values();
4551 }
4552 }
4553 assert_eq!(
4554 total_values as usize, num_rows,
4555 "expected every level position to be represented in some page"
4556 );
4557 }
4558
4559 struct WriteBatchesShape {
4560 num_batches: usize,
4561 rows_per_batch: usize,
4562 row_size: usize,
4563 }
4564
4565 fn write_batches(
4567 WriteBatchesShape {
4568 num_batches,
4569 rows_per_batch,
4570 row_size,
4571 }: WriteBatchesShape,
4572 props: WriterProperties,
4573 ) -> ParquetRecordBatchReaderBuilder<File> {
4574 let schema = Arc::new(Schema::new(vec![Field::new(
4575 "str",
4576 ArrowDataType::Utf8,
4577 false,
4578 )]));
4579 let file = tempfile::tempfile().unwrap();
4580 let mut writer =
4581 ArrowWriter::try_new(file.try_clone().unwrap(), schema.clone(), Some(props)).unwrap();
4582
4583 for batch_idx in 0..num_batches {
4584 let strings: Vec<String> = (0..rows_per_batch)
4585 .map(|i| format!("{:0>width$}", batch_idx * 10 + i, width = row_size))
4586 .collect();
4587 let array = StringArray::from(strings);
4588 let batch = RecordBatch::try_new(schema.clone(), vec![Arc::new(array)]).unwrap();
4589 writer.write(&batch).unwrap();
4590 }
4591 writer.close().unwrap();
4592 ParquetRecordBatchReaderBuilder::try_new(file).unwrap()
4593 }
4594
4595 #[test]
4596 fn test_row_group_limit_none_writes_single_row_group() {
4598 let props = WriterProperties::builder()
4599 .set_max_row_group_row_count(None)
4600 .set_max_row_group_bytes(None)
4601 .build();
4602
4603 let builder = write_batches(
4604 WriteBatchesShape {
4605 num_batches: 1,
4606 rows_per_batch: 1000,
4607 row_size: 4,
4608 },
4609 props,
4610 );
4611
4612 assert_eq!(
4613 &row_group_sizes(builder.metadata()),
4614 &[1000],
4615 "With no limits, all rows should be in a single row group"
4616 );
4617 }
4618
4619 #[test]
4620 fn test_row_group_limit_rows_only() {
4622 let props = WriterProperties::builder()
4623 .set_max_row_group_row_count(Some(300))
4624 .set_max_row_group_bytes(None)
4625 .build();
4626
4627 let builder = write_batches(
4628 WriteBatchesShape {
4629 num_batches: 1,
4630 rows_per_batch: 1000,
4631 row_size: 4,
4632 },
4633 props,
4634 );
4635
4636 assert_eq!(
4637 &row_group_sizes(builder.metadata()),
4638 &[300, 300, 300, 100],
4639 "Row groups should be split by row count"
4640 );
4641 }
4642
4643 #[test]
4644 #[cfg_attr(miri, ignore)] fn test_row_group_limit_rows_only_many_splits() {
4648 let props = WriterProperties::builder()
4649 .set_max_row_group_row_count(Some(1))
4650 .set_max_row_group_bytes(None)
4651 .build();
4652
4653 let rows = 50_000;
4654 let builder = write_batches(
4655 WriteBatchesShape {
4656 num_batches: 1,
4657 rows_per_batch: rows,
4658 row_size: 4,
4659 },
4660 props,
4661 );
4662
4663 let sizes = row_group_sizes(builder.metadata());
4664 assert_eq!(sizes.len(), rows, "Every row should get its own row group");
4665 assert_eq!(
4666 sizes.iter().sum::<i64>(),
4667 rows as i64,
4668 "Total rows should be preserved"
4669 );
4670 }
4671
4672 #[test]
4673 fn test_row_group_limit_bytes_only() {
4675 let props = WriterProperties::builder()
4676 .set_max_row_group_row_count(None)
4677 .set_max_row_group_bytes(Some(3500))
4679 .build();
4680
4681 let builder = write_batches(
4682 WriteBatchesShape {
4683 num_batches: 10,
4684 rows_per_batch: 10,
4685 row_size: 100,
4686 },
4687 props,
4688 );
4689
4690 let sizes = row_group_sizes(builder.metadata());
4691
4692 assert!(
4693 sizes.len() > 1,
4694 "Should have multiple row groups due to byte limit, got {sizes:?}",
4695 );
4696
4697 let total_rows: i64 = sizes.iter().sum();
4698 assert_eq!(total_rows, 100, "Total rows should be preserved");
4699 }
4700
4701 #[test]
4702 fn test_row_group_limit_bytes_flushes_when_current_group_already_too_large() {
4704 let schema = Arc::new(Schema::new(vec![Field::new(
4705 "str",
4706 ArrowDataType::Utf8,
4707 false,
4708 )]));
4709 let file = tempfile::tempfile().unwrap();
4710
4711 let props = WriterProperties::builder()
4713 .set_max_row_group_row_count(None)
4714 .set_max_row_group_bytes(None)
4715 .build();
4716 let mut writer =
4717 ArrowWriter::try_new(file.try_clone().unwrap(), schema.clone(), Some(props)).unwrap();
4718
4719 let first_array = StringArray::from(
4720 (0..10)
4721 .map(|i| format!("{i:0>100}"))
4722 .collect::<Vec<String>>(),
4723 );
4724 let first_batch =
4725 RecordBatch::try_new(schema.clone(), vec![Arc::new(first_array)]).unwrap();
4726 writer.write(&first_batch).unwrap();
4727 assert_eq!(writer.in_progress_rows(), 10);
4728
4729 writer.max_row_group_bytes = Some(1);
4732
4733 let second_array = StringArray::from(vec!["x".to_string()]);
4734 let second_batch =
4735 RecordBatch::try_new(schema.clone(), vec![Arc::new(second_array)]).unwrap();
4736 writer.write(&second_batch).unwrap();
4737 writer.close().unwrap();
4738 let builder = ParquetRecordBatchReaderBuilder::try_new(file).unwrap();
4739
4740 assert_eq!(
4741 &row_group_sizes(builder.metadata()),
4742 &[10, 1],
4743 "The second write should flush an oversized in-progress row group first",
4744 );
4745 }
4746
4747 #[test]
4748 fn test_row_group_limit_both_row_wins_single_batch() {
4750 let props = WriterProperties::builder()
4751 .set_max_row_group_row_count(Some(200)) .set_max_row_group_bytes(Some(1024 * 1024)) .build();
4754
4755 let builder = write_batches(
4756 WriteBatchesShape {
4757 num_batches: 1,
4758 row_size: 4,
4759 rows_per_batch: 1000,
4760 },
4761 props,
4762 );
4763
4764 assert_eq!(
4765 &row_group_sizes(builder.metadata()),
4766 &[200, 200, 200, 200, 200],
4767 "Row limit should trigger before byte limit"
4768 );
4769 }
4770
4771 #[test]
4772 fn test_row_group_limit_both_row_wins_multiple_batches() {
4774 let props = WriterProperties::builder()
4775 .set_max_row_group_row_count(Some(5)) .set_max_row_group_bytes(Some(9999)) .build();
4778
4779 let builder = write_batches(
4780 WriteBatchesShape {
4781 num_batches: 10,
4782 rows_per_batch: 10,
4783 row_size: 100,
4784 },
4785 props,
4786 );
4787
4788 assert_eq!(
4789 &row_group_sizes(builder.metadata()),
4790 &[5; 20],
4791 "Row limit should trigger before byte limit"
4792 );
4793 }
4794
4795 #[test]
4796 fn test_row_group_limit_both_bytes_wins() {
4798 let props = WriterProperties::builder()
4799 .set_max_row_group_row_count(Some(1000)) .set_max_row_group_bytes(Some(3500)) .build();
4802
4803 let builder = write_batches(
4804 WriteBatchesShape {
4805 num_batches: 10,
4806 rows_per_batch: 10,
4807 row_size: 100,
4808 },
4809 props,
4810 );
4811
4812 let sizes = row_group_sizes(builder.metadata());
4813
4814 assert!(
4815 sizes.len() > 1,
4816 "Byte limit should trigger before row limit, got {sizes:?}",
4817 );
4818
4819 assert!(
4820 sizes.iter().all(|&s| s < 1000),
4821 "No row group should hit the row limit"
4822 );
4823
4824 let total_rows: i64 = sizes.iter().sum();
4825 assert_eq!(total_rows, 100, "Total rows should be preserved");
4826 }
4827
4828 #[test]
4829 fn test_row_group_limit_both_apply_to_same_batch() {
4832 let props = WriterProperties::builder()
4833 .set_max_row_group_row_count(Some(15))
4834 .set_max_row_group_bytes(Some(1500))
4835 .build();
4836
4837 let builder = write_batches(
4838 WriteBatchesShape {
4839 num_batches: 2,
4840 rows_per_batch: 10,
4841 row_size: 100,
4842 },
4843 props,
4844 );
4845
4846 assert_eq!(
4847 &row_group_sizes(builder.metadata()),
4848 &[14, 6],
4849 "Byte limit should still apply to a batch the row limit already split"
4850 );
4851 }
4852
4853 #[test]
4854 fn arrow_column_chunk_close_mut_drops_column_index() {
4855 use crate::arrow::ArrowSchemaConverter;
4856 use crate::file::writer::SerializedFileWriter;
4857
4858 let schema = Arc::new(Schema::new(vec![Field::new("i", DataType::Int32, false)]));
4859 let props = Arc::new(
4860 WriterProperties::builder()
4861 .set_statistics_enabled(EnabledStatistics::Page)
4862 .build(),
4863 );
4864 let parquet_schema = ArrowSchemaConverter::new()
4865 .with_coerce_types(props.coerce_types())
4866 .convert(&schema)
4867 .unwrap();
4868
4869 let mut buf = Vec::with_capacity(1024);
4870 let mut writer =
4871 SerializedFileWriter::new(&mut buf, parquet_schema.root_schema_ptr(), props.clone())
4872 .unwrap();
4873
4874 let factory = ArrowRowGroupWriterFactory::new(&writer, Arc::clone(&schema));
4875 let mut col_writers = factory.create_column_writers(0).unwrap();
4876 let arr: ArrayRef = Arc::new(Int32Array::from_iter_values(0..64));
4877 for leaves in compute_leaves(schema.field(0), &arr).unwrap() {
4878 col_writers[0].write(&leaves).unwrap();
4879 }
4880 let mut chunk = col_writers.pop().unwrap().close().unwrap();
4881
4882 assert!(
4884 chunk.close().column_index.is_some(),
4885 "EnabledStatistics::Page should produce a column_index"
4886 );
4887
4888 chunk.close_mut().column_index = None;
4890 assert!(chunk.close().column_index.is_none());
4891
4892 let mut rg = writer.next_row_group().unwrap();
4893 chunk.append_to_row_group(&mut rg).unwrap();
4894 rg.close().unwrap();
4895 let file_meta = writer.close().unwrap();
4896
4897 let cc = file_meta.row_group(0).column(0);
4900 assert!(cc.column_index_range().is_none());
4901 }
4902
4903 fn write_column_to_bytes(array: ArrayRef) -> Bytes {
4905 let schema = Arc::new(Schema::new(vec![Field::new(
4906 "col",
4907 array.data_type().clone(),
4908 true,
4909 )]));
4910 let buf = get_bytes_after_close(
4911 schema.clone(),
4912 &RecordBatch::try_new(schema, vec![array]).unwrap(),
4913 );
4914 Bytes::from(buf)
4915 }
4916
4917 fn read_column_with_schema(bytes: Bytes, schema: SchemaRef) -> ArrayRef {
4921 let opts = crate::arrow::arrow_reader::ArrowReaderOptions::new().with_schema(schema);
4922 ParquetRecordBatchReaderBuilder::try_new_with_options(bytes, opts)
4923 .unwrap()
4924 .build()
4925 .unwrap()
4926 .next()
4927 .unwrap()
4928 .unwrap()
4929 .column(0)
4930 .clone()
4931 }
4932
4933 fn ree_write_read_roundtrip(ree: ArrayRef, flat: ArrayRef) {
4934 let flat_schema = Arc::new(Schema::new(vec![Field::new(
4935 "col",
4936 flat.data_type().clone(),
4937 true,
4938 )]));
4939 let ree_bytes = write_column_to_bytes(ree);
4940 let flat_bytes = write_column_to_bytes(flat.clone());
4941 assert_eq!(
4942 ree_bytes, flat_bytes,
4943 "REE and flat bytes should be identical"
4944 );
4945
4946 let decoded_ree = read_column_with_schema(ree_bytes, flat_schema.clone());
4947 let decoded_flat = read_column_with_schema(flat_bytes, flat_schema);
4948
4949 assert_eq!(decoded_ree.as_ref(), flat.as_ref());
4950 assert_eq!(decoded_ree.as_ref(), decoded_flat.as_ref());
4951 }
4952
4953 #[test]
4954 fn ree_string() {
4955 let ree: ArrayRef = Arc::new(
4956 [Some("a"), Some("a"), None, Some("b"), Some("b")]
4957 .into_iter()
4958 .collect::<Int32RunArray>(),
4959 );
4960 let flat: ArrayRef = Arc::new(StringArray::from(vec![
4961 Some("a"),
4962 Some("a"),
4963 None,
4964 Some("b"),
4965 Some("b"),
4966 ]));
4967 ree_write_read_roundtrip(ree, flat);
4968 }
4969
4970 #[test]
4971 fn ree_int32() {
4972 let mut b = PrimitiveRunBuilder::<Int32Type, Int32Type>::new();
4973 for v in [Some(1), Some(1), None, Some(2), Some(2)] {
4974 b.append_option(v);
4975 }
4976 let ree: ArrayRef = Arc::new(b.finish());
4977 let flat: ArrayRef = Arc::new(Int32Array::from(vec![
4978 Some(1),
4979 Some(1),
4980 None,
4981 Some(2),
4982 Some(2),
4983 ]));
4984 ree_write_read_roundtrip(ree, flat);
4985 }
4986
4987 #[test]
4988 fn ree_bool() {
4989 let ree: ArrayRef = Arc::new(
4991 RunArray::try_new(
4992 &Int32Array::from(vec![3, 5, 7]),
4993 &BooleanArray::from(vec![Some(true), None, Some(false)]),
4994 )
4995 .unwrap(),
4996 );
4997 let flat: ArrayRef = Arc::new(BooleanArray::from(vec![
4998 Some(true),
4999 Some(true),
5000 Some(true),
5001 None,
5002 None,
5003 Some(false),
5004 Some(false),
5005 ]));
5006 ree_write_read_roundtrip(ree, flat);
5007 }
5008
5009 #[test]
5010 fn ree_fixed_size_binary() {
5011 let mk = |vals: &[Option<&[u8]>]| -> FixedSizeBinaryArray {
5012 let mut b = FixedSizeBinaryBuilder::new(2);
5013 for v in vals {
5014 match v {
5015 Some(x) => b.append_value(x).unwrap(),
5016 None => b.append_null(),
5017 }
5018 }
5019 b.finish()
5020 };
5021 let ree: ArrayRef = Arc::new(
5023 RunArray::try_new(
5024 &Int32Array::from(vec![2, 4, 6]),
5025 &mk(&[Some(b"aa"), None, Some(b"bb")]),
5026 )
5027 .unwrap(),
5028 );
5029 let flat: ArrayRef = Arc::new(mk(&[
5030 Some(b"aa"),
5031 Some(b"aa"),
5032 None,
5033 None,
5034 Some(b"bb"),
5035 Some(b"bb"),
5036 ]));
5037 ree_write_read_roundtrip(ree, flat);
5038 }
5039
5040 #[test]
5041 fn ree_single_run() {
5042 let ree: ArrayRef = Arc::new(["x", "x", "x"].into_iter().collect::<Int32RunArray>());
5043 let flat: ArrayRef = Arc::new(StringArray::from(vec!["x", "x", "x"]));
5044 ree_write_read_roundtrip(ree, flat);
5045 }
5046
5047 #[test]
5048 fn ree_float32() {
5049 let ree: ArrayRef = Arc::new(
5051 RunArray::try_new(
5052 &Int32Array::from(vec![2, 4, 5]),
5053 &Float32Array::from(vec![Some(1.0_f32), None, Some(2.5_f32)]),
5054 )
5055 .unwrap(),
5056 );
5057 let flat: ArrayRef = Arc::new(Float32Array::from(vec![
5058 Some(1.0_f32),
5059 Some(1.0_f32),
5060 None,
5061 None,
5062 Some(2.5_f32),
5063 ]));
5064 ree_write_read_roundtrip(ree, flat);
5065 }
5066
5067 #[test]
5068 fn ree_sliced() {
5069 let full: ArrayRef = Arc::new(
5074 RunArray::try_new(
5075 &Int32Array::from(vec![3, 5, 7]),
5076 &StringArray::from(vec!["a", "b", "c"]),
5077 )
5078 .unwrap(),
5079 );
5080 let sliced = full.slice(2, 5);
5081 let flat: ArrayRef = Arc::new(StringArray::from(vec!["a", "b", "b", "c", "c"]));
5082 ree_write_read_roundtrip(sliced, flat);
5083 }
5084
5085 #[test]
5086 #[cfg_attr(miri, ignore)] fn test_number_distinct_values_exact_count() {
5088 let cardinality = 50u32;
5091 let array: ArrayRef = Arc::new(Int32Array::from_iter((0..100_000u32).map(|i| {
5092 if i % 7 == 0 {
5093 None
5094 } else {
5095 Some((i % cardinality) as i32)
5096 }
5097 })));
5098 let schema = Arc::new(Schema::new(vec![Field::new("x", DataType::Int32, true)]));
5099 let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
5100
5101 let props = WriterProperties::builder()
5102 .set_write_row_group_number_distinct_values(true)
5103 .build();
5104 let mut buf = Vec::new();
5105 let mut writer = ArrowWriter::try_new(&mut buf, batch.schema(), Some(props)).unwrap();
5106 writer.write(&batch).unwrap();
5107 let metadata = writer.close().unwrap();
5108
5109 let count = metadata
5110 .row_group(0)
5111 .column(0)
5112 .statistics()
5113 .and_then(|s| s.distinct_count_opt())
5114 .expect("distinct_count should be set");
5115 assert_eq!(count, cardinality as u64);
5117 }
5118
5119 #[test]
5120 fn test_number_distinct_values_view_types() {
5121 let cardinality = 5u32;
5124 let distinct_strings = ["alpha", "beta", "gamma", "delta", "epsilon"];
5125
5126 let string_view_col: ArrayRef = Arc::new(StringViewArray::from_iter((0..30u32).map(|i| {
5127 if i % 4 == 0 {
5128 None
5129 } else {
5130 Some(distinct_strings[(i % cardinality) as usize])
5131 }
5132 })));
5133
5134 let schema = Arc::new(Schema::new(vec![Field::new(
5135 "string_view_col",
5136 DataType::Utf8View,
5137 true,
5138 )]));
5139 let batch = RecordBatch::try_new(schema, vec![string_view_col]).unwrap();
5140
5141 let props = WriterProperties::builder()
5142 .set_write_row_group_number_distinct_values(true)
5143 .build();
5144 let mut parquet_bytes = Vec::new();
5145 let mut writer =
5146 ArrowWriter::try_new(&mut parquet_bytes, batch.schema(), Some(props)).unwrap();
5147 writer.write(&batch).unwrap();
5148 let metadata = writer.close().unwrap();
5149
5150 let distinct_count = metadata
5151 .row_group(0)
5152 .column(0)
5153 .statistics()
5154 .and_then(|s| s.distinct_count_opt())
5155 .expect("distinct_count should be set for Utf8View column");
5156 assert_eq!(distinct_count, cardinality as u64);
5157 }
5158
5159 #[test]
5160 fn test_number_distinct_values_not_written_by_default() {
5161 let array: ArrayRef = Arc::new(Int32Array::from_iter_values(0..100));
5162 let schema = Arc::new(Schema::new(vec![Field::new("x", DataType::Int32, false)]));
5163 let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
5164
5165 let mut buf = Vec::new();
5166 let mut writer = ArrowWriter::try_new(&mut buf, batch.schema(), None).unwrap();
5167 writer.write(&batch).unwrap();
5168 let metadata = writer.close().unwrap();
5169
5170 let count = metadata
5171 .row_group(0)
5172 .column(0)
5173 .statistics()
5174 .and_then(|s| s.distinct_count_opt());
5175 assert!(count.is_none());
5176 }
5177
5178 #[test]
5179 fn test_dictionary_ndv_single_batch() {
5180 let keys = Int32Array::from(vec![0, 1, 2, 0, 1, 2, 0, 1, 2]);
5184 let values: ArrayRef = Arc::new(StringArray::from(vec!["cat", "dog", "bird"]));
5185 let dict: ArrayRef = Arc::new(DictionaryArray::<Int32Type>::try_new(keys, values).unwrap());
5186
5187 let schema = Arc::new(Schema::new(vec![Field::new(
5188 "x",
5189 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
5190 false,
5191 )]));
5192 let batch = RecordBatch::try_new(schema, vec![dict]).unwrap();
5193
5194 let props = WriterProperties::builder()
5195 .set_write_row_group_number_distinct_values(true)
5196 .build();
5197 let mut buf = Vec::new();
5198 let mut writer = ArrowWriter::try_new(&mut buf, batch.schema(), Some(props)).unwrap();
5199 writer.write(&batch).unwrap();
5200 let metadata = writer.close().unwrap();
5201
5202 let count = metadata
5203 .row_group(0)
5204 .column(0)
5205 .statistics()
5206 .and_then(|s| s.distinct_count_opt())
5207 .expect("distinct_count should be set");
5208 assert_eq!(count, 3);
5209 }
5210
5211 #[test]
5212 fn test_dictionary_ndv_excludes_unreferenced_values() {
5213 let keys = Int32Array::from(vec![0, 1, 0, 1]);
5216 let values: ArrayRef = Arc::new(StringArray::from(vec!["cat", "dog", "unreferenced"]));
5217 let dict: ArrayRef = Arc::new(DictionaryArray::<Int32Type>::try_new(keys, values).unwrap());
5218
5219 let schema = Arc::new(Schema::new(vec![Field::new(
5220 "x",
5221 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
5222 false,
5223 )]));
5224 let batch = RecordBatch::try_new(schema, vec![dict]).unwrap();
5225
5226 let props = WriterProperties::builder()
5227 .set_write_row_group_number_distinct_values(true)
5228 .build();
5229 let mut buf = Vec::new();
5230 let mut writer = ArrowWriter::try_new(&mut buf, batch.schema(), Some(props)).unwrap();
5231 writer.write(&batch).unwrap();
5232 let metadata = writer.close().unwrap();
5233
5234 let count = metadata
5235 .row_group(0)
5236 .column(0)
5237 .statistics()
5238 .and_then(|s| s.distinct_count_opt())
5239 .expect("distinct_count should be set");
5240 assert_eq!(
5241 count, 2,
5242 "unreferenced dictionary values must not count toward NDV"
5243 );
5244 }
5245
5246 #[test]
5247 fn test_dictionary_ndv_across_batches_regression() {
5248 let make_dict_batch = |a: &str, b: &str| -> RecordBatch {
5250 let keys = Int32Array::from(vec![0, 1, 0, 1]);
5251 let values: ArrayRef = Arc::new(StringArray::from(vec![a, b]));
5252 let dict: ArrayRef =
5253 Arc::new(DictionaryArray::<Int32Type>::try_new(keys, values).unwrap());
5254 let schema = Arc::new(Schema::new(vec![Field::new(
5255 "x",
5256 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
5257 false,
5258 )]));
5259 RecordBatch::try_new(schema, vec![dict]).unwrap()
5260 };
5261
5262 let batch1 = make_dict_batch("cat", "dog");
5265 let batch2 = make_dict_batch("fish", "cat");
5266
5267 let props = WriterProperties::builder()
5268 .set_write_row_group_number_distinct_values(true)
5269 .build();
5270 let mut buf = Vec::new();
5271 let mut writer = ArrowWriter::try_new(&mut buf, batch1.schema(), Some(props)).unwrap();
5272 writer.write(&batch1).unwrap();
5273 writer.write(&batch2).unwrap();
5274 let metadata = writer.close().unwrap();
5275
5276 let count = metadata
5277 .row_group(0)
5278 .column(0)
5279 .statistics()
5280 .and_then(|s| s.distinct_count_opt())
5281 .expect("distinct_count should be set");
5282 assert_eq!(
5283 count, 3,
5284 "NDV should count distinct values, not distinct key indices"
5285 );
5286 }
5287
5288 #[test]
5289 fn ree_struct_with_ree_child() {
5290 let run_ends = Int32Array::from(vec![2i32, 3, 5]);
5293
5294 let col_a: ArrayRef = Arc::new(
5295 RunArray::try_new(
5296 &run_ends,
5297 &StringArray::from(vec![Some("foo"), None, Some("bar")]),
5298 )
5299 .unwrap(),
5300 );
5301 let col_b: ArrayRef = Arc::new(
5302 RunArray::try_new(&run_ends, &Int32Array::from(vec![Some(1), None, Some(2)])).unwrap(),
5303 );
5304
5305 let struct_array: ArrayRef = Arc::new(StructArray::new(
5306 Fields::from(vec![
5307 Field::new("a", col_a.data_type().clone(), true),
5308 Field::new("b", col_b.data_type().clone(), true),
5309 ]),
5310 vec![col_a, col_b],
5311 None,
5312 ));
5313
5314 let schema = Arc::new(Schema::new(vec![Field::new(
5315 "row",
5316 struct_array.data_type().clone(),
5317 true,
5318 )]));
5319 let batch = RecordBatch::try_new(schema.clone(), vec![struct_array]).unwrap();
5320
5321 let mut buf = Vec::new();
5322 let mut writer = ArrowWriter::try_new(&mut buf, schema, None).unwrap();
5323 writer.write(&batch).unwrap();
5324 let metadata = writer.close().unwrap();
5325
5326 let parquet_schema = metadata.file_metadata().schema_descr();
5327 assert_eq!(parquet_schema.num_columns(), 2);
5328 assert_eq!(
5329 parquet_schema.column(0).physical_type(),
5330 crate::basic::Type::BYTE_ARRAY
5331 );
5332 assert_eq!(parquet_schema.column(0).path().string(), "row.a");
5333 assert_eq!(
5334 parquet_schema.column(1).physical_type(),
5335 crate::basic::Type::INT32
5336 );
5337 assert_eq!(parquet_schema.column(1).path().string(), "row.b");
5338 }
5339}