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)]
2079mod tests {
2080 use super::*;
2081 use std::cmp::Ordering;
2082 use std::collections::HashMap;
2083
2084 use std::fs::File;
2085
2086 use crate::arrow::arrow_reader::{ParquetRecordBatchReader, ParquetRecordBatchReaderBuilder};
2087 use crate::arrow::{ARROW_SCHEMA_META_KEY, PARQUET_FIELD_ID_META_KEY};
2088 use crate::column::page::{Page, PageReader};
2089 use crate::file::metadata::thrift::PageHeader;
2090 use crate::file::page_index::column_index::ColumnIndexMetaData;
2091 use crate::file::reader::SerializedPageReader;
2092 use crate::parquet_thrift::{ReadThrift, ThriftSliceInputProtocol};
2093 use crate::schema::types::ColumnPath;
2094 use arrow::datatypes::ToByteSlice;
2095 use arrow::datatypes::{DataType, Schema};
2096 use arrow::error::Result as ArrowResult;
2097 use arrow::util::data_gen::create_random_array;
2098 use arrow::util::pretty::pretty_format_batches;
2099 use arrow::{array::*, buffer::Buffer};
2100 use arrow_buffer::{IntervalDayTime, IntervalMonthDayNano, NullBuffer, OffsetBuffer, i256};
2101 use arrow_schema::Fields;
2102 use half::f16;
2103 use num_traits::{FromPrimitive, ToPrimitive};
2104 use tempfile::tempfile;
2105
2106 use crate::basic::{Encoding, EncodingMask};
2107 use crate::data_type::AsBytes;
2108 use crate::file::metadata::{ColumnChunkMetaData, ParquetMetaData, ParquetMetaDataReader};
2109 use crate::file::properties::{
2110 BloomFilterPosition, EnabledStatistics, ReaderProperties, WriterVersion,
2111 };
2112 use crate::file::serialized_reader::ReadOptionsBuilder;
2113 use crate::file::{
2114 reader::{FileReader, SerializedFileReader},
2115 statistics::Statistics,
2116 };
2117
2118 #[derive(Debug, Default)]
2123 struct RecordingPageStore {
2124 next: u64,
2125 blobs: HashMap<u64, Bytes>,
2126 puts: Arc<std::sync::atomic::AtomicUsize>,
2127 }
2128
2129 impl PageStore for RecordingPageStore {
2130 fn put(&mut self, value: Bytes) -> Result<PageKey> {
2131 let id = 100 + self.next * 7;
2133 self.next += 1;
2134 self.puts.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2135 self.blobs.insert(id, value);
2136 Ok(PageKey::new(id))
2137 }
2138
2139 fn take(&mut self, key: PageKey) -> Result<Bytes> {
2140 self.blobs
2141 .remove(&key.get())
2142 .ok_or_else(|| ParquetError::General(format!("missing key {}", key.get())))
2143 }
2144 }
2145
2146 #[derive(Debug)]
2147 struct RecordingPageStoreFactory {
2148 puts: Arc<std::sync::atomic::AtomicUsize>,
2149 }
2150
2151 impl PageStoreFactory for RecordingPageStoreFactory {
2152 fn create(&self, _args: &PageStoreArgs<'_>) -> Result<Box<dyn PageStore>> {
2153 Ok(Box::new(RecordingPageStore {
2154 puts: self.puts.clone(),
2155 ..Default::default()
2156 }))
2157 }
2158 }
2159
2160 #[test]
2164 fn custom_page_store_is_byte_identical_to_default() {
2165 let schema = Arc::new(Schema::new(vec![
2166 Field::new("i", DataType::Int32, true),
2167 Field::new("s", DataType::Utf8, true),
2169 ]));
2170 let i = Int32Array::from(vec![Some(1), None, Some(3), Some(4), Some(5), Some(6)]);
2171 let s = StringArray::from(vec![
2172 Some("a"),
2173 Some("bb"),
2174 Some("a"),
2175 None,
2176 Some("bb"),
2177 Some("ccc"),
2178 ]);
2179 let batch = RecordBatch::try_new(schema.clone(), vec![Arc::new(i), Arc::new(s)]).unwrap();
2180
2181 let props = WriterProperties::builder()
2184 .set_max_row_group_row_count(Some(3))
2185 .build();
2186
2187 let write = |factory: Option<Arc<dyn PageStoreFactory>>| {
2188 let mut buffer = Vec::new();
2189 let mut opts = ArrowWriterOptions::new().with_properties(props.clone());
2190 if let Some(factory) = factory {
2191 opts = opts.with_page_store_factory(factory);
2192 }
2193 let mut writer =
2194 ArrowWriter::try_new_with_options(&mut buffer, schema.clone(), opts).unwrap();
2195 writer.write(&batch).unwrap();
2196 writer.close().unwrap();
2197 buffer
2198 };
2199
2200 let default_bytes = write(None);
2201
2202 let puts = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2203 let custom_bytes = write(Some(Arc::new(RecordingPageStoreFactory {
2204 puts: puts.clone(),
2205 })));
2206
2207 assert!(
2208 puts.load(std::sync::atomic::Ordering::Relaxed) > 0,
2209 "custom PageStore was never written to"
2210 );
2211 assert_eq!(
2212 default_bytes, custom_bytes,
2213 "a custom PageStore must produce byte-identical output to the default"
2214 );
2215 }
2216
2217 #[test]
2223 #[cfg_attr(miri, ignore)] fn dictionary_column_round_trips_with_offset_index_disabled() {
2225 let schema = Arc::new(Schema::new(vec![Field::new("k", DataType::Int32, true)]));
2226
2227 let values: Vec<Option<i32>> = (0..50_000).map(|i| Some(i % 8)).collect();
2230 let array = Int32Array::from(values.clone());
2231 let batch = RecordBatch::try_new(schema.clone(), vec![Arc::new(array)]).unwrap();
2232
2233 let props = WriterProperties::builder()
2234 .set_offset_index_disabled(true)
2235 .set_data_page_row_count_limit(4096)
2236 .build();
2237 let opts = ArrowWriterOptions::new().with_properties(props);
2238
2239 let mut buffer = Vec::new();
2240 let mut writer =
2241 ArrowWriter::try_new_with_options(&mut buffer, schema.clone(), opts).unwrap();
2242 writer.write(&batch).unwrap();
2243 writer.close().unwrap();
2244
2245 let reader = ParquetRecordBatchReader::try_new(Bytes::from(buffer), values.len()).unwrap();
2246 let read: Vec<RecordBatch> = reader.collect::<ArrowResult<_>>().unwrap();
2247 let read_values: Vec<Option<i32>> = read
2248 .iter()
2249 .flat_map(|b| b.column(0).as_primitive::<Int32Type>().iter())
2250 .collect();
2251 assert_eq!(read_values, values);
2252 }
2253
2254 #[test]
2259 fn dictionary_page_is_routed_through_the_store() {
2260 #[derive(Debug, Default)]
2262 struct SizeRecordingPageStore {
2263 blobs: Vec<Bytes>,
2264 bytes_put: Arc<std::sync::atomic::AtomicUsize>,
2265 }
2266 impl PageStore for SizeRecordingPageStore {
2267 fn put(&mut self, value: Bytes) -> Result<PageKey> {
2268 self.bytes_put
2269 .fetch_add(value.len(), std::sync::atomic::Ordering::Relaxed);
2270 let key = PageKey::new(self.blobs.len() as u64);
2271 self.blobs.push(value);
2272 Ok(key)
2273 }
2274 fn take(&mut self, key: PageKey) -> Result<Bytes> {
2275 Ok(std::mem::take(&mut self.blobs[key.get() as usize]))
2276 }
2277 }
2278 #[derive(Debug)]
2279 struct Factory {
2280 bytes_put: Arc<std::sync::atomic::AtomicUsize>,
2281 }
2282 impl PageStoreFactory for Factory {
2283 fn create(&self, _args: &PageStoreArgs<'_>) -> Result<Box<dyn PageStore>> {
2284 Ok(Box::new(SizeRecordingPageStore {
2285 bytes_put: self.bytes_put.clone(),
2286 ..Default::default()
2287 }))
2288 }
2289 }
2290
2291 let schema = Arc::new(Schema::new(vec![Field::new("s", DataType::Utf8, false)]));
2292 let values: Vec<&str> = (0..2048)
2295 .map(|i| ["alpha", "beta", "gamma", "delta"][i % 4])
2296 .collect();
2297 let batch = RecordBatch::try_new(schema.clone(), vec![Arc::new(StringArray::from(values))])
2298 .unwrap();
2299
2300 let bytes_put = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2301 let opts = ArrowWriterOptions::new().with_page_store_factory(Arc::new(Factory {
2302 bytes_put: bytes_put.clone(),
2303 }));
2304
2305 let mut buffer = Vec::new();
2308 let mut writer =
2309 ArrowWriter::try_new_with_options(&mut buffer, schema.clone(), opts).unwrap();
2310 writer.write(&batch).unwrap();
2311 writer.close().unwrap();
2312
2313 let reader = SerializedFileReader::new(Bytes::from(buffer)).unwrap();
2314 let column = reader.metadata().row_group(0).column(0);
2315 assert!(
2316 column.dictionary_page_offset().is_some(),
2317 "expected the column to be dictionary-encoded"
2318 );
2319
2320 assert_eq!(
2324 bytes_put.load(std::sync::atomic::Ordering::Relaxed) as i64,
2325 column.compressed_size(),
2326 "the dictionary page must pass through the store like any other page"
2327 );
2328 }
2329
2330 #[test]
2331 fn arrow_writer() {
2332 let schema = Schema::new(vec![
2334 Field::new("a", DataType::Int32, false),
2335 Field::new("b", DataType::Int32, true),
2336 ]);
2337
2338 let a = Int32Array::from(vec![1, 2, 3, 4, 5]);
2340 let b = Int32Array::from(vec![Some(1), None, None, Some(4), Some(5)]);
2341
2342 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(a), Arc::new(b)]).unwrap();
2344
2345 roundtrip(batch, Some(SMALL_SIZE / 2));
2346 }
2347
2348 fn get_bytes_after_close(schema: SchemaRef, expected_batch: &RecordBatch) -> Vec<u8> {
2349 let mut buffer = vec![];
2350
2351 let mut writer = ArrowWriter::try_new(&mut buffer, schema, None).unwrap();
2352 writer.write(expected_batch).unwrap();
2353 writer.close().unwrap();
2354
2355 buffer
2356 }
2357
2358 fn get_bytes_by_into_inner(schema: SchemaRef, expected_batch: &RecordBatch) -> Vec<u8> {
2359 let mut writer = ArrowWriter::try_new(Vec::new(), schema, None).unwrap();
2360 writer.write(expected_batch).unwrap();
2361 writer.into_inner().unwrap()
2362 }
2363
2364 #[test]
2365 fn roundtrip_bytes() {
2366 let schema = Arc::new(Schema::new(vec![
2368 Field::new("a", DataType::Int32, false),
2369 Field::new("b", DataType::Int32, true),
2370 ]));
2371
2372 let a = Int32Array::from(vec![1, 2, 3, 4, 5]);
2374 let b = Int32Array::from(vec![Some(1), None, None, Some(4), Some(5)]);
2375
2376 let expected_batch =
2378 RecordBatch::try_new(schema.clone(), vec![Arc::new(a), Arc::new(b)]).unwrap();
2379
2380 for buffer in [
2381 get_bytes_after_close(schema.clone(), &expected_batch),
2382 get_bytes_by_into_inner(schema, &expected_batch),
2383 ] {
2384 let cursor = Bytes::from(buffer);
2385 let mut record_batch_reader = ParquetRecordBatchReader::try_new(cursor, 1024).unwrap();
2386
2387 let actual_batch = record_batch_reader
2388 .next()
2389 .expect("No batch found")
2390 .expect("Unable to get batch");
2391
2392 assert_eq!(expected_batch.schema(), actual_batch.schema());
2393 assert_eq!(expected_batch.num_columns(), actual_batch.num_columns());
2394 assert_eq!(expected_batch.num_rows(), actual_batch.num_rows());
2395 for i in 0..expected_batch.num_columns() {
2396 let expected_data = expected_batch.column(i).to_data();
2397 let actual_data = actual_batch.column(i).to_data();
2398
2399 assert_eq!(expected_data, actual_data);
2400 }
2401 }
2402 }
2403
2404 #[test]
2405 #[cfg_attr(miri, ignore)] fn arrow_writer_non_null() {
2407 let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
2408 let a = Int32Array::from(vec![1, 2, 3, 4, 5]);
2409
2410 RoundTripTest::new(Arc::new(a))
2411 .with_schema(Arc::new(schema))
2412 .run();
2413 }
2414
2415 #[test]
2416 #[cfg_attr(miri, ignore)] fn arrow_writer_list() {
2418 let schema = Schema::new(vec![Field::new(
2420 "a",
2421 DataType::List(Arc::new(Field::new_list_field(DataType::Int32, false))),
2422 true,
2423 )]);
2424
2425 let a_values = Int32Array::from(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
2427
2428 let a_value_offsets = arrow::buffer::Buffer::from([0, 1, 3, 3, 6, 10].to_byte_slice());
2431
2432 let a_list_data = ArrayData::builder(DataType::List(Arc::new(Field::new_list_field(
2434 DataType::Int32,
2435 false,
2436 ))))
2437 .len(5)
2438 .add_buffer(a_value_offsets)
2439 .add_child_data(a_values.into_data())
2440 .null_bit_buffer(Some(Buffer::from([0b00011011])))
2441 .build()
2442 .unwrap();
2443 let a = ListArray::from(a_list_data);
2444 assert_eq!(a.null_count(), 1);
2445
2446 RoundTripTest::new(Arc::new(a))
2447 .with_schema(Arc::new(schema))
2448 .run();
2449 }
2450
2451 #[test]
2452 #[cfg_attr(miri, ignore)] fn arrow_writer_list_non_null() {
2454 let schema = Schema::new(vec![Field::new(
2456 "a",
2457 DataType::List(Arc::new(Field::new_list_field(DataType::Int32, false))),
2458 false,
2459 )]);
2460
2461 let a_values = Int32Array::from(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
2463
2464 let a_value_offsets = arrow::buffer::Buffer::from([0, 1, 3, 3, 6, 10].to_byte_slice());
2467
2468 let a_list_data = ArrayData::builder(DataType::List(Arc::new(Field::new_list_field(
2470 DataType::Int32,
2471 false,
2472 ))))
2473 .len(5)
2474 .add_buffer(a_value_offsets)
2475 .add_child_data(a_values.into_data())
2476 .build()
2477 .unwrap();
2478 let a = ListArray::from(a_list_data);
2479 assert_eq!(a.null_count(), 0);
2480
2481 RoundTripTest::new(Arc::new(a))
2482 .with_schema(Arc::new(schema))
2483 .run();
2484 }
2485
2486 #[test]
2487 #[cfg_attr(miri, ignore)] fn arrow_writer_list_view() {
2489 let list_field = Arc::new(Field::new_list_field(DataType::Int32, false));
2490 let schema = Schema::new(vec![Field::new(
2491 "a",
2492 DataType::ListView(list_field.clone()),
2493 true,
2494 )]);
2495
2496 let a = ListViewArray::new(
2498 list_field,
2499 vec![0, 1, 0, 3, 6].into(),
2500 vec![1, 2, 0, 3, 4].into(),
2501 Arc::new(Int32Array::from(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10])),
2502 Some(vec![true, true, false, true, true].into()),
2503 );
2504 assert_eq!(a.null_count(), 1);
2505
2506 RoundTripTest::new(Arc::new(a))
2507 .with_schema(Arc::new(schema))
2508 .run();
2509 }
2510
2511 #[test]
2512 #[cfg_attr(miri, ignore)] fn arrow_writer_list_view_non_null() {
2514 let list_field = Arc::new(Field::new_list_field(DataType::Int32, false));
2515 let schema = Schema::new(vec![Field::new(
2516 "a",
2517 DataType::ListView(list_field.clone()),
2518 false,
2519 )]);
2520
2521 let a = ListViewArray::new(
2523 list_field,
2524 vec![0, 1, 0, 3, 6].into(),
2525 vec![1, 2, 0, 3, 4].into(),
2526 Arc::new(Int32Array::from(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10])),
2527 None,
2528 );
2529 assert_eq!(a.null_count(), 0);
2530
2531 RoundTripTest::new(Arc::new(a))
2532 .with_schema(Arc::new(schema))
2533 .run();
2534 }
2535
2536 #[test]
2537 #[cfg_attr(miri, ignore)] fn arrow_writer_list_view_out_of_order() {
2539 let list_field = Arc::new(Field::new_list_field(DataType::Int32, false));
2540 let schema = Schema::new(vec![Field::new(
2541 "a",
2542 DataType::ListView(list_field.clone()),
2543 false,
2544 )]);
2545
2546 let a = ListViewArray::new(
2548 list_field,
2549 vec![0, 1, 0, 6, 3].into(),
2550 vec![1, 2, 0, 4, 3].into(),
2551 Arc::new(Int32Array::from(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10])),
2552 None,
2553 );
2554 assert_eq!(a.null_count(), 0);
2555
2556 RoundTripTest::new(Arc::new(a))
2557 .with_schema(Arc::new(schema))
2558 .run();
2559 }
2560
2561 #[test]
2562 #[cfg_attr(miri, ignore)] fn arrow_writer_large_list_view() {
2564 let list_field = Arc::new(Field::new_list_field(DataType::Int32, false));
2565 let schema = Schema::new(vec![Field::new(
2566 "a",
2567 DataType::LargeListView(list_field.clone()),
2568 true,
2569 )]);
2570
2571 let a = LargeListViewArray::new(
2573 list_field,
2574 vec![0i64, 1, 0, 3, 6].into(),
2575 vec![1i64, 2, 0, 3, 4].into(),
2576 Arc::new(Int32Array::from(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10])),
2577 Some(vec![true, true, false, true, true].into()),
2578 );
2579 assert_eq!(a.null_count(), 1);
2580
2581 RoundTripTest::new(Arc::new(a))
2582 .with_schema(Arc::new(schema))
2583 .run();
2584 }
2585
2586 #[test]
2587 #[cfg_attr(miri, ignore)] fn arrow_writer_list_view_with_struct() {
2589 let struct_fields = Fields::from(vec![
2591 Field::new("id", DataType::Int32, false),
2592 Field::new("name", DataType::Utf8, false),
2593 ]);
2594 let struct_type = DataType::Struct(struct_fields.clone());
2595 let list_field = Arc::new(Field::new("item", struct_type.clone(), false));
2596
2597 let schema = Schema::new(vec![Field::new(
2598 "a",
2599 DataType::ListView(list_field.clone()),
2600 true,
2601 )]);
2602
2603 let id_array = Int32Array::from(vec![1, 2, 3, 4, 5]);
2605 let name_array = StringArray::from(vec!["a", "b", "c", "d", "e"]);
2606 let struct_array = StructArray::new(
2607 struct_fields,
2608 vec![Arc::new(id_array), Arc::new(name_array)],
2609 None,
2610 );
2611
2612 let list_view = ListViewArray::new(
2614 list_field,
2615 vec![0, 2, 2].into(), vec![2, 0, 3].into(), Arc::new(struct_array),
2618 Some(vec![true, false, true].into()),
2619 );
2620 assert_eq!(list_view.null_count(), 1);
2621
2622 RoundTripTest::new(Arc::new(list_view))
2623 .with_schema(Arc::new(schema))
2624 .run();
2625 }
2626
2627 #[test]
2628 #[cfg_attr(miri, ignore)] fn arrow_writer_binary() {
2630 let raw_string_values = vec!["foo", "bar", "baz", "quux"];
2631 let raw_binary_values = [
2632 b"foo".to_vec(),
2633 b"bar".to_vec(),
2634 b"baz".to_vec(),
2635 b"quux".to_vec(),
2636 ];
2637 let raw_binary_value_refs = raw_binary_values
2638 .iter()
2639 .map(|x| x.as_slice())
2640 .collect::<Vec<_>>();
2641
2642 let string_values = StringArray::from(raw_string_values.clone());
2643 let binary_values = BinaryArray::from(raw_binary_value_refs);
2644 assert_eq!(string_values.null_count(), 0);
2645 assert_eq!(binary_values.null_count(), 0);
2646
2647 RoundTripTest::new(Arc::new(string_values)).run();
2648 RoundTripTest::new(Arc::new(binary_values)).run();
2649 }
2650
2651 #[test]
2652 #[cfg_attr(miri, ignore)] fn arrow_writer_binary_view() {
2654 let raw_string_values = vec!["foo", "bar", "large payload over 12 bytes", "lulu"];
2655 let raw_binary_values = vec![
2656 b"foo".to_vec(),
2657 b"bar".to_vec(),
2658 b"large payload over 12 bytes".to_vec(),
2659 b"lulu".to_vec(),
2660 ];
2661 let nullable_string_values =
2662 vec![Some("foo"), None, Some("large payload over 12 bytes"), None];
2663
2664 let string_view_values = StringViewArray::from(raw_string_values);
2665 let binary_view_values = BinaryViewArray::from_iter_values(raw_binary_values);
2666 let nullable_string_view_values = StringViewArray::from(nullable_string_values);
2667
2668 RoundTripTest::new(Arc::new(string_view_values)).run();
2669 RoundTripTest::new(Arc::new(binary_view_values)).run();
2670 RoundTripTest::new(Arc::new(nullable_string_view_values)).run();
2671 }
2672
2673 #[test]
2674 #[cfg_attr(miri, ignore)] fn arrow_writer_binary_view_long_value() {
2676 let long = "a".repeat(128);
2680 let raw_string_values = vec!["foo", long.as_str(), "bar"];
2681 let raw_binary_values = vec![b"foo".to_vec(), long.as_bytes().to_vec(), b"bar".to_vec()];
2682
2683 let string_view_values: ArrayRef = Arc::new(StringViewArray::from(raw_string_values));
2684 let binary_view_values: ArrayRef =
2685 Arc::new(BinaryViewArray::from_iter_values(raw_binary_values));
2686
2687 RoundTripTest::new(Arc::clone(&string_view_values))
2688 .with_nullable(false)
2689 .run();
2690 RoundTripTest::new(Arc::clone(&binary_view_values))
2691 .with_nullable(false)
2692 .run();
2693 }
2694
2695 #[test]
2698 fn arrow_writer_string_view_dictionary() {
2699 let raw_string_values = vec!["a", "b", "large payload over 12 bytes"];
2700 let raw_binary_values = vec![
2701 b"a".to_vec(),
2702 b"b".to_vec(),
2703 b"large payload over 12 bytes".to_vec(),
2704 ];
2705
2706 let keys = UInt32Array::from(vec![Some(0), None, Some(2), Some(1), None]);
2707
2708 let string_view_values = Arc::new(StringViewArray::from(raw_string_values));
2709 let string_dict: ArrayRef = Arc::new(
2710 DictionaryArray::<UInt32Type>::try_new(keys.clone(), string_view_values).unwrap(),
2711 );
2712
2713 let binary_view_values = Arc::new(BinaryViewArray::from_iter_values(raw_binary_values));
2714 let binary_dict: ArrayRef =
2715 Arc::new(DictionaryArray::<UInt32Type>::try_new(keys, binary_view_values).unwrap());
2716
2717 RoundTripTest::new(string_dict).run();
2718 RoundTripTest::new(binary_dict).run();
2719 }
2720
2721 fn get_decimal_batch(precision: u8, scale: i8) -> RecordBatch {
2722 let decimal_field = Field::new("a", DataType::Decimal128(precision, scale), false);
2723 let schema = Schema::new(vec![decimal_field]);
2724
2725 let decimal_values = vec![10_000, 50_000, 0, -100]
2726 .into_iter()
2727 .map(Some)
2728 .collect::<Decimal128Array>()
2729 .with_precision_and_scale(precision, scale)
2730 .unwrap();
2731
2732 RecordBatch::try_new(Arc::new(schema), vec![Arc::new(decimal_values)]).unwrap()
2733 }
2734
2735 #[test]
2736 fn arrow_writer_decimal() {
2737 let batch_int32_decimal = get_decimal_batch(5, 2);
2739 roundtrip(batch_int32_decimal, Some(SMALL_SIZE / 2));
2740 let batch_int64_decimal = get_decimal_batch(12, 2);
2742 roundtrip(batch_int64_decimal, Some(SMALL_SIZE / 2));
2743 let batch_fixed_len_byte_array_decimal = get_decimal_batch(30, 2);
2745 roundtrip(batch_fixed_len_byte_array_decimal, Some(SMALL_SIZE / 2));
2746 }
2747
2748 #[test]
2749 #[cfg_attr(miri, ignore)] fn arrow_writer_complex() {
2751 let struct_field_d = Arc::new(Field::new("d", DataType::Float64, true));
2753 let struct_field_f = Arc::new(Field::new("f", DataType::Float32, true));
2754 let struct_field_g = Arc::new(Field::new_list(
2755 "g",
2756 Field::new_list_field(DataType::Int16, true),
2757 false,
2758 ));
2759 let struct_field_h = Arc::new(Field::new_list(
2760 "h",
2761 Field::new_list_field(DataType::Int16, false),
2762 true,
2763 ));
2764 let struct_field_e = Arc::new(Field::new_struct(
2765 "e",
2766 vec![
2767 struct_field_f.clone(),
2768 struct_field_g.clone(),
2769 struct_field_h.clone(),
2770 ],
2771 false,
2772 ));
2773 let schema = Schema::new(vec![
2774 Field::new("a", DataType::Int32, false),
2775 Field::new("b", DataType::Int32, true),
2776 Field::new_struct(
2777 "c",
2778 vec![struct_field_d.clone(), struct_field_e.clone()],
2779 false,
2780 ),
2781 ]);
2782
2783 let a = Int32Array::from(vec![1, 2, 3, 4, 5]);
2785 let b = Int32Array::from(vec![Some(1), None, None, Some(4), Some(5)]);
2786 let d = Float64Array::from(vec![None, None, None, Some(1.0), None]);
2787 let f = Float32Array::from(vec![Some(0.0), None, Some(333.3), None, Some(5.25)]);
2788
2789 let g_value = Int16Array::from(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
2790
2791 let g_value_offsets = arrow::buffer::Buffer::from([0, 1, 3, 3, 6, 10].to_byte_slice());
2794
2795 let g_list_data = ArrayData::builder(struct_field_g.data_type().clone())
2797 .len(5)
2798 .add_buffer(g_value_offsets.clone())
2799 .add_child_data(g_value.to_data())
2800 .build()
2801 .unwrap();
2802 let g = ListArray::from(g_list_data);
2803 let h_list_data = ArrayData::builder(struct_field_h.data_type().clone())
2805 .len(5)
2806 .add_buffer(g_value_offsets)
2807 .add_child_data(g_value.to_data())
2808 .null_bit_buffer(Some(Buffer::from([0b00011011])))
2809 .build()
2810 .unwrap();
2811 let h = ListArray::from(h_list_data);
2812
2813 let e = StructArray::from(vec![
2814 (struct_field_f, Arc::new(f) as ArrayRef),
2815 (struct_field_g, Arc::new(g) as ArrayRef),
2816 (struct_field_h, Arc::new(h) as ArrayRef),
2817 ]);
2818
2819 let c = StructArray::from(vec![
2820 (struct_field_d, Arc::new(d) as ArrayRef),
2821 (struct_field_e, Arc::new(e) as ArrayRef),
2822 ]);
2823
2824 let batch = RecordBatch::try_new(
2826 Arc::new(schema),
2827 vec![Arc::new(a), Arc::new(b), Arc::new(c)],
2828 )
2829 .unwrap();
2830
2831 roundtrip(batch.clone(), Some(SMALL_SIZE / 2));
2832 roundtrip(batch, Some(SMALL_SIZE / 3));
2833 }
2834
2835 #[test]
2836 fn arrow_writer_complex_mixed() {
2837 let offset_field = Arc::new(Field::new("offset", DataType::Int32, false));
2842 let partition_field = Arc::new(Field::new("partition", DataType::Int64, true));
2843 let topic_field = Arc::new(Field::new("topic", DataType::Utf8, true));
2844 let schema = Schema::new(vec![Field::new(
2845 "some_nested_object",
2846 DataType::Struct(Fields::from(vec![
2847 offset_field.clone(),
2848 partition_field.clone(),
2849 topic_field.clone(),
2850 ])),
2851 false,
2852 )]);
2853
2854 let offset = Int32Array::from(vec![1, 2, 3, 4, 5]);
2856 let partition = Int64Array::from(vec![Some(1), None, None, Some(4), Some(5)]);
2857 let topic = StringArray::from(vec![Some("A"), None, Some("A"), Some(""), None]);
2858
2859 let some_nested_object = StructArray::from(vec![
2860 (offset_field, Arc::new(offset) as ArrayRef),
2861 (partition_field, Arc::new(partition) as ArrayRef),
2862 (topic_field, Arc::new(topic) as ArrayRef),
2863 ]);
2864
2865 let batch =
2867 RecordBatch::try_new(Arc::new(schema), vec![Arc::new(some_nested_object)]).unwrap();
2868
2869 roundtrip(batch, Some(SMALL_SIZE / 2));
2870 }
2871
2872 #[test]
2873 fn arrow_writer_map() {
2874 let json_content = r#"
2876 {"stocks":{"long": "$AAA", "short": "$BBB"}}
2877 {"stocks":{"long": null, "long": "$CCC", "short": null}}
2878 {"stocks":{"hedged": "$YYY", "long": null, "short": "$D"}}
2879 "#;
2880 let entries_struct_type = DataType::Struct(Fields::from(vec![
2881 Field::new(Field::MAP_KEY_FIELD_DEFAULT_NAME, DataType::Utf8, false),
2882 Field::new(Field::MAP_VALUE_FIELD_DEFAULT_NAME, DataType::Utf8, true),
2883 ]));
2884 let stocks_field = Field::new(
2885 "stocks",
2886 DataType::Map(
2887 Arc::new(Field::new(
2888 Field::MAP_ENTRIES_FIELD_DEFAULT_NAME,
2889 entries_struct_type,
2890 false,
2891 )),
2892 false,
2893 ),
2894 true,
2895 );
2896 let schema = Arc::new(Schema::new(vec![stocks_field]));
2897 let builder = arrow::json::ReaderBuilder::new(schema).with_batch_size(64);
2898 let mut reader = builder.build(std::io::Cursor::new(json_content)).unwrap();
2899
2900 let batch = reader.next().unwrap().unwrap();
2901 roundtrip(batch, None);
2902 }
2903
2904 #[test]
2905 fn arrow_writer_2_level_struct() {
2906 let field_c = Field::new("c", DataType::Int32, true);
2908 let field_b = Field::new("b", DataType::Struct(vec![field_c].into()), true);
2909 let type_a = DataType::Struct(vec![field_b.clone()].into());
2910 let field_a = Field::new("a", type_a, true);
2911 let schema = Schema::new(vec![field_a.clone()]);
2912
2913 let c = Int32Array::from(vec![Some(1), None, Some(3), None, None, Some(6)]);
2915 let b_data = ArrayDataBuilder::new(field_b.data_type().clone())
2916 .len(6)
2917 .null_bit_buffer(Some(Buffer::from([0b00100111])))
2918 .add_child_data(c.into_data())
2919 .build()
2920 .unwrap();
2921 let b = StructArray::from(b_data);
2922 let a_data = ArrayDataBuilder::new(field_a.data_type().clone())
2923 .len(6)
2924 .null_bit_buffer(Some(Buffer::from([0b00101111])))
2925 .add_child_data(b.into_data())
2926 .build()
2927 .unwrap();
2928 let a = StructArray::from(a_data);
2929
2930 assert_eq!(a.null_count(), 1);
2931 assert_eq!(a.column(0).null_count(), 2);
2932
2933 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(a)]).unwrap();
2935
2936 roundtrip(batch, Some(SMALL_SIZE / 2));
2937 }
2938
2939 #[test]
2940 fn arrow_writer_2_level_struct_non_null() {
2941 let field_c = Field::new("c", DataType::Int32, false);
2943 let type_b = DataType::Struct(vec![field_c].into());
2944 let field_b = Field::new("b", type_b.clone(), false);
2945 let type_a = DataType::Struct(vec![field_b].into());
2946 let field_a = Field::new("a", type_a.clone(), false);
2947 let schema = Schema::new(vec![field_a]);
2948
2949 let c = Int32Array::from(vec![1, 2, 3, 4, 5, 6]);
2951 let b_data = ArrayDataBuilder::new(type_b)
2952 .len(6)
2953 .add_child_data(c.into_data())
2954 .build()
2955 .unwrap();
2956 let b = StructArray::from(b_data);
2957 let a_data = ArrayDataBuilder::new(type_a)
2958 .len(6)
2959 .add_child_data(b.into_data())
2960 .build()
2961 .unwrap();
2962 let a = StructArray::from(a_data);
2963
2964 assert_eq!(a.null_count(), 0);
2965 assert_eq!(a.column(0).null_count(), 0);
2966
2967 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(a)]).unwrap();
2969
2970 roundtrip(batch, Some(SMALL_SIZE / 2));
2971 }
2972
2973 #[test]
2974 fn arrow_writer_2_level_struct_mixed_null() {
2975 let field_c = Field::new("c", DataType::Int32, false);
2977 let type_b = DataType::Struct(vec![field_c].into());
2978 let field_b = Field::new("b", type_b.clone(), true);
2979 let type_a = DataType::Struct(vec![field_b].into());
2980 let field_a = Field::new("a", type_a.clone(), false);
2981 let schema = Schema::new(vec![field_a]);
2982
2983 let c = Int32Array::from(vec![1, 2, 3, 4, 5, 6]);
2985 let b_data = ArrayDataBuilder::new(type_b)
2986 .len(6)
2987 .null_bit_buffer(Some(Buffer::from([0b00100111])))
2988 .add_child_data(c.into_data())
2989 .build()
2990 .unwrap();
2991 let b = StructArray::from(b_data);
2992 let a_data = ArrayDataBuilder::new(type_a)
2994 .len(6)
2995 .add_child_data(b.into_data())
2996 .build()
2997 .unwrap();
2998 let a = StructArray::from(a_data);
2999
3000 assert_eq!(a.null_count(), 0);
3001 assert_eq!(a.column(0).null_count(), 2);
3002
3003 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(a)]).unwrap();
3005
3006 roundtrip(batch, Some(SMALL_SIZE / 2));
3007 }
3008
3009 #[test]
3010 fn arrow_writer_2_level_struct_mixed_null_2() {
3011 let field_c = Field::new("c", DataType::Int32, false);
3013 let field_d = Field::new("d", DataType::FixedSizeBinary(4), false);
3014 let field_e = Field::new(
3015 "e",
3016 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
3017 false,
3018 );
3019
3020 let field_b = Field::new(
3021 "b",
3022 DataType::Struct(vec![field_c, field_d, field_e].into()),
3023 false,
3024 );
3025 let type_a = DataType::Struct(vec![field_b.clone()].into());
3026 let field_a = Field::new("a", type_a, true);
3027 let schema = Schema::new(vec![field_a.clone()]);
3028
3029 let c = Int32Array::from_iter_values(0..6);
3031 let d = FixedSizeBinaryArray::try_from_iter(
3032 ["aaaa", "bbbb", "cccc", "dddd", "eeee", "ffff"].into_iter(),
3033 )
3034 .expect("four byte values");
3035 let e = Int32DictionaryArray::from_iter(["one", "two", "three", "four", "five", "one"]);
3036 let b_data = ArrayDataBuilder::new(field_b.data_type().clone())
3037 .len(6)
3038 .add_child_data(c.into_data())
3039 .add_child_data(d.into_data())
3040 .add_child_data(e.into_data())
3041 .build()
3042 .unwrap();
3043 let b = StructArray::from(b_data);
3044 let a_data = ArrayDataBuilder::new(field_a.data_type().clone())
3045 .len(6)
3046 .null_bit_buffer(Some(Buffer::from([0b00100101])))
3047 .add_child_data(b.into_data())
3048 .build()
3049 .unwrap();
3050 let a = StructArray::from(a_data);
3051
3052 assert_eq!(a.null_count(), 3);
3053 assert_eq!(a.column(0).null_count(), 0);
3054
3055 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(a)]).unwrap();
3057
3058 roundtrip(batch, Some(SMALL_SIZE / 2));
3059 }
3060
3061 #[test]
3062 fn test_fixed_size_binary_in_dict() {
3063 fn test_fixed_size_binary_in_dict_inner<K>()
3064 where
3065 K: ArrowDictionaryKeyType,
3066 K::Native: FromPrimitive + ToPrimitive + TryFrom<u8>,
3067 <<K as arrow_array::ArrowPrimitiveType>::Native as TryFrom<u8>>::Error: std::fmt::Debug,
3068 {
3069 let field = Field::new(
3070 "a",
3071 DataType::Dictionary(
3072 Box::new(K::DATA_TYPE),
3073 Box::new(DataType::FixedSizeBinary(4)),
3074 ),
3075 false,
3076 );
3077 let schema = Schema::new(vec![field]);
3078
3079 let keys: Vec<K::Native> = vec![
3080 K::Native::try_from(0u8).unwrap(),
3081 K::Native::try_from(0u8).unwrap(),
3082 K::Native::try_from(1u8).unwrap(),
3083 ];
3084 let keys = PrimitiveArray::<K>::from_iter_values(keys);
3085 let values = FixedSizeBinaryArray::try_from_iter(
3086 vec![vec![0, 0, 0, 0], vec![1, 1, 1, 1]].into_iter(),
3087 )
3088 .unwrap();
3089
3090 let data = DictionaryArray::<K>::new(keys, Arc::new(values));
3091 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(data)]).unwrap();
3092 roundtrip(batch, None);
3093 }
3094
3095 test_fixed_size_binary_in_dict_inner::<UInt8Type>();
3096 test_fixed_size_binary_in_dict_inner::<UInt16Type>();
3097 test_fixed_size_binary_in_dict_inner::<UInt32Type>();
3098 test_fixed_size_binary_in_dict_inner::<UInt16Type>();
3099 test_fixed_size_binary_in_dict_inner::<Int8Type>();
3100 test_fixed_size_binary_in_dict_inner::<Int16Type>();
3101 test_fixed_size_binary_in_dict_inner::<Int32Type>();
3102 test_fixed_size_binary_in_dict_inner::<Int64Type>();
3103 }
3104
3105 #[test]
3106 fn test_empty_dict() {
3107 let struct_fields = Fields::from(vec![Field::new(
3108 "dict",
3109 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
3110 false,
3111 )]);
3112
3113 let schema = Schema::new(vec![Field::new_struct(
3114 "struct",
3115 struct_fields.clone(),
3116 true,
3117 )]);
3118 let dictionary = Arc::new(DictionaryArray::new(
3119 Int32Array::new_null(5),
3120 Arc::new(StringArray::new_null(0)),
3121 ));
3122
3123 let s = StructArray::new(
3124 struct_fields,
3125 vec![dictionary],
3126 Some(NullBuffer::new_null(5)),
3127 );
3128
3129 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(s)]).unwrap();
3130 roundtrip(batch, None);
3131 }
3132 #[test]
3133 fn arrow_writer_page_size() {
3134 let schema = Arc::new(Schema::new(vec![Field::new("col", DataType::Utf8, false)]));
3135
3136 let mut builder = StringBuilder::with_capacity(100, 329 * 10_000);
3137
3138 for i in 0..10 {
3140 let value = i
3141 .to_string()
3142 .repeat(10)
3143 .chars()
3144 .take(10)
3145 .collect::<String>();
3146
3147 builder.append_value(value);
3148 }
3149
3150 let array = Arc::new(builder.finish());
3151
3152 let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
3153
3154 let file = tempfile::tempfile().unwrap();
3155
3156 let props = WriterProperties::builder()
3158 .set_data_page_size_limit(1)
3159 .set_dictionary_page_size_limit(1)
3160 .set_write_batch_size(1)
3161 .build();
3162
3163 let mut writer =
3164 ArrowWriter::try_new(file.try_clone().unwrap(), batch.schema(), Some(props))
3165 .expect("Unable to write file");
3166 writer.write(&batch).unwrap();
3167 writer.close().unwrap();
3168
3169 let options = ReadOptionsBuilder::new().with_page_index().build();
3170 let reader =
3171 SerializedFileReader::new_with_options(file.try_clone().unwrap(), options).unwrap();
3172
3173 let column = reader.metadata().row_group(0).columns();
3174
3175 assert_eq!(column.len(), 1);
3176
3177 assert!(
3180 column[0].dictionary_page_offset().is_some(),
3181 "Expected a dictionary page"
3182 );
3183
3184 let page_index = reader
3185 .metadata()
3186 .page_index()
3187 .expect("page index should be present");
3188 let page_locations = page_index
3189 .page_locations(0, 0)
3190 .expect("page locations should exist");
3191
3192 assert_eq!(
3195 page_locations.len(),
3196 10,
3197 "Expected 10 pages but got {page_locations:#?}"
3198 );
3199 }
3200
3201 #[test]
3202 #[cfg_attr(miri, ignore)] fn arrow_writer_float_nans() {
3204 let f16_field = Field::new("a", DataType::Float16, false);
3205 let f32_field = Field::new("b", DataType::Float32, false);
3206 let f64_field = Field::new("c", DataType::Float64, false);
3207 let schema = Schema::new(vec![f16_field, f32_field, f64_field]);
3208
3209 let f16_values = (0..MEDIUM_SIZE)
3210 .map(|i| {
3211 Some(if i % 2 == 0 {
3212 f16::NAN
3213 } else {
3214 f16::from_f32(i as f32)
3215 })
3216 })
3217 .collect::<Float16Array>();
3218
3219 let f32_values = (0..MEDIUM_SIZE)
3220 .map(|i| Some(if i % 2 == 0 { f32::NAN } else { i as f32 }))
3221 .collect::<Float32Array>();
3222
3223 let f64_values = (0..MEDIUM_SIZE)
3224 .map(|i| Some(if i % 2 == 0 { f64::NAN } else { i as f64 }))
3225 .collect::<Float64Array>();
3226
3227 let batch = RecordBatch::try_new(
3228 Arc::new(schema),
3229 vec![
3230 Arc::new(f16_values),
3231 Arc::new(f32_values),
3232 Arc::new(f64_values),
3233 ],
3234 )
3235 .unwrap();
3236
3237 roundtrip(batch, None);
3238 }
3239
3240 const SMALL_SIZE: usize = 7;
3241 const MEDIUM_SIZE: usize = 63;
3242
3243 fn roundtrip(expected_batch: RecordBatch, max_row_group_size: Option<usize>) -> Vec<Bytes> {
3246 let mut files = vec![];
3247 for version in [WriterVersion::PARQUET_1_0, WriterVersion::PARQUET_2_0] {
3248 let mut props = WriterProperties::builder().set_writer_version(version);
3249
3250 if let Some(size) = max_row_group_size {
3251 props = props.set_max_row_group_row_count(Some(size))
3252 }
3253
3254 let props = props.build();
3255 files.push(roundtrip_opts(&expected_batch, props))
3256 }
3257 files
3258 }
3259
3260 fn roundtrip_opts_with_array_validation<F>(
3264 expected_batch: &RecordBatch,
3265 props: WriterProperties,
3266 validate: F,
3267 ) -> Bytes
3268 where
3269 F: Fn(&ArrayData, &ArrayData),
3270 {
3271 let mut file = vec![];
3272
3273 let mut writer = ArrowWriter::try_new(&mut file, expected_batch.schema(), Some(props))
3274 .expect("Unable to write file");
3275 writer.write(expected_batch).unwrap();
3276 writer.close().unwrap();
3277
3278 let file = Bytes::from(file);
3279 let mut record_batch_reader =
3280 ParquetRecordBatchReader::try_new(file.clone(), 1024).unwrap();
3281
3282 let actual_batch = record_batch_reader
3283 .next()
3284 .expect("No batch found")
3285 .expect("Unable to get batch");
3286
3287 assert_eq!(expected_batch.schema(), actual_batch.schema());
3288 assert_eq!(expected_batch.num_columns(), actual_batch.num_columns());
3289 assert_eq!(expected_batch.num_rows(), actual_batch.num_rows());
3290 for i in 0..expected_batch.num_columns() {
3291 let expected_data = expected_batch.column(i).to_data();
3292 let actual_data = actual_batch.column(i).to_data();
3293 validate(&expected_data, &actual_data);
3294 }
3295
3296 file
3297 }
3298
3299 fn roundtrip_opts(expected_batch: &RecordBatch, props: WriterProperties) -> Bytes {
3300 roundtrip_opts_with_array_validation(expected_batch, props, |a, b| {
3301 a.validate_full().expect("valid expected data");
3302 b.validate_full().expect("valid actual data");
3303 assert_eq!(a, b)
3304 })
3305 }
3306
3307 struct RoundTripTest {
3311 values: ArrayRef,
3312 schema: Option<SchemaRef>,
3314 nullable: bool,
3317 bloom_filter: bool,
3318 bloom_filter_ndv: Option<u64>,
3319 bloom_filter_position: BloomFilterPosition,
3320 }
3321
3322 impl RoundTripTest {
3323 fn new(values: ArrayRef) -> Self {
3325 Self {
3326 values,
3327 schema: None,
3328 nullable: true,
3329 bloom_filter: false,
3330 bloom_filter_ndv: None,
3331 bloom_filter_position: BloomFilterPosition::AfterRowGroup,
3332 }
3333 }
3334
3335 fn with_schema(mut self, schema: SchemaRef) -> Self {
3337 self.schema = Some(schema);
3338 self
3339 }
3340
3341 fn with_nullable(mut self, nullable: bool) -> Self {
3343 self.nullable = nullable;
3344 self
3345 }
3346
3347 fn with_bloom_filter(mut self, bloom_filter: bool) -> Self {
3349 self.bloom_filter = bloom_filter;
3350 self
3351 }
3352
3353 fn with_bloom_filter_ndv(mut self, bloom_filter_ndv: u64) -> Self {
3355 self.bloom_filter_ndv = Some(bloom_filter_ndv);
3356 self
3357 }
3358
3359 fn with_bloom_filter_position(
3361 mut self,
3362 bloom_filter_position: BloomFilterPosition,
3363 ) -> Self {
3364 self.bloom_filter_position = bloom_filter_position;
3365 self
3366 }
3367
3368 fn run(self) -> Vec<Bytes> {
3370 let RoundTripTest {
3371 values,
3372 schema,
3373 nullable,
3374 bloom_filter,
3375 bloom_filter_ndv,
3376 bloom_filter_position,
3377 } = self;
3378
3379 let schema = schema.unwrap_or_else(|| {
3380 let data_type = values.data_type().clone();
3381 Arc::new(Schema::new(vec![Field::new("col", data_type, nullable)]))
3382 });
3383
3384 let encodings = match values.data_type() {
3385 DataType::Utf8 | DataType::LargeUtf8 | DataType::Binary | DataType::LargeBinary => {
3386 vec![
3387 Encoding::PLAIN,
3388 Encoding::DELTA_BYTE_ARRAY,
3389 Encoding::DELTA_LENGTH_BYTE_ARRAY,
3390 ]
3391 }
3392 DataType::Int64
3393 | DataType::Int32
3394 | DataType::Int16
3395 | DataType::Int8
3396 | DataType::UInt64
3397 | DataType::UInt32
3398 | DataType::UInt16
3399 | DataType::UInt8 => vec![
3400 Encoding::PLAIN,
3401 Encoding::DELTA_BINARY_PACKED,
3402 Encoding::BYTE_STREAM_SPLIT,
3403 ],
3404 DataType::Float32 | DataType::Float64 => {
3405 vec![Encoding::PLAIN, Encoding::BYTE_STREAM_SPLIT, Encoding::ALP]
3406 }
3407 _ => vec![Encoding::PLAIN],
3408 };
3409
3410 let expected_batch = RecordBatch::try_new(schema, vec![values]).unwrap();
3411
3412 let row_group_sizes = [1024, SMALL_SIZE, SMALL_SIZE / 2, SMALL_SIZE / 2 + 1, 10];
3413
3414 let mut files = vec![];
3415 for dictionary_size in [0, 1, 1024] {
3416 for encoding in &encodings {
3417 for version in [WriterVersion::PARQUET_1_0, WriterVersion::PARQUET_2_0] {
3418 for row_group_size in row_group_sizes {
3419 let mut builder = WriterProperties::builder()
3420 .set_writer_version(version)
3421 .set_max_row_group_row_count(Some(row_group_size))
3422 .set_dictionary_enabled(dictionary_size != 0)
3423 .set_dictionary_page_size_limit(dictionary_size.max(1))
3424 .set_encoding(*encoding)
3425 .set_bloom_filter_enabled(bloom_filter)
3426 .set_bloom_filter_position(bloom_filter_position);
3427 if let Some(ndv) = bloom_filter_ndv {
3428 builder = builder.set_bloom_filter_max_ndv(ndv);
3429 }
3430 let props = builder.build();
3431
3432 files.push(roundtrip_opts(&expected_batch, props))
3433 }
3434 }
3435 }
3436 }
3437 files
3438 }
3439 }
3440
3441 fn values_required<A, I>(iter: I) -> Vec<Bytes>
3442 where
3443 A: From<Vec<I::Item>> + Array + 'static,
3444 I: IntoIterator,
3445 {
3446 let raw_values: Vec<_> = iter.into_iter().collect();
3447 let values = Arc::new(A::from(raw_values));
3448 RoundTripTest::new(values).with_nullable(false).run()
3449 }
3450
3451 fn values_optional<A, I>(iter: I) -> Vec<Bytes>
3452 where
3453 A: From<Vec<Option<I::Item>>> + Array + 'static,
3454 I: IntoIterator,
3455 {
3456 let optional_raw_values: Vec<_> = iter
3457 .into_iter()
3458 .enumerate()
3459 .map(|(i, v)| if i % 2 == 0 { None } else { Some(v) })
3460 .collect();
3461 let optional_values = Arc::new(A::from(optional_raw_values));
3462 RoundTripTest::new(optional_values).run()
3463 }
3464
3465 fn required_and_optional<A, I>(iter: I)
3466 where
3467 A: From<Vec<I::Item>> + From<Vec<Option<I::Item>>> + Array + 'static,
3468 I: IntoIterator + Clone,
3469 {
3470 values_required::<A, I>(iter.clone());
3471 values_optional::<A, I>(iter);
3472 }
3473
3474 fn check_bloom_filter<T: AsBytes>(
3475 files: Vec<Bytes>,
3476 file_column: String,
3477 positive_values: Vec<T>,
3478 negative_values: Vec<T>,
3479 ) {
3480 files.into_iter().take(1).for_each(|file| {
3481 let file_reader = SerializedFileReader::new_with_options(
3482 file,
3483 ReadOptionsBuilder::new()
3484 .with_reader_properties(
3485 ReaderProperties::builder()
3486 .set_read_bloom_filter(true)
3487 .build(),
3488 )
3489 .build(),
3490 )
3491 .expect("Unable to open file as Parquet");
3492 let metadata = file_reader.metadata();
3493
3494 let mut bloom_filters: Vec<_> = vec![];
3496 for (ri, row_group) in metadata.row_groups().iter().enumerate() {
3497 if let Some((column_index, _)) = row_group
3498 .columns()
3499 .iter()
3500 .enumerate()
3501 .find(|(_, column)| column.column_path().string() == file_column)
3502 {
3503 let row_group_reader = file_reader
3504 .get_row_group(ri)
3505 .expect("Unable to read row group");
3506 if let Some(sbbf) = row_group_reader.get_column_bloom_filter(column_index) {
3507 bloom_filters.push(sbbf.clone());
3508 } else {
3509 panic!("No bloom filter for column named {file_column} found");
3510 }
3511 } else {
3512 panic!("No column named {file_column} found");
3513 }
3514 }
3515
3516 positive_values.iter().for_each(|value| {
3517 let found = bloom_filters.iter().find(|sbbf| sbbf.check(value));
3518 assert!(
3519 found.is_some(),
3520 "{}",
3521 format!("Value {:?} should be in bloom filter", value.as_bytes())
3522 );
3523 });
3524
3525 negative_values.iter().for_each(|value| {
3526 let found = bloom_filters.iter().find(|sbbf| sbbf.check(value));
3527 assert!(
3528 found.is_none(),
3529 "{}",
3530 format!("Value {:?} should not be in bloom filter", value.as_bytes())
3531 );
3532 });
3533 });
3534 }
3535
3536 #[test]
3537 #[cfg_attr(miri, ignore)] fn all_null_primitive_single_column() {
3539 let values = Arc::new(Int32Array::from(vec![None; SMALL_SIZE]));
3540 RoundTripTest::new(values).run();
3541 }
3542 #[test]
3543 #[cfg_attr(miri, ignore)] fn null_single_column() {
3545 let values = Arc::new(NullArray::new(SMALL_SIZE));
3546 RoundTripTest::new(values).run();
3547 }
3549
3550 #[test]
3551 #[cfg_attr(miri, ignore)] fn bool_single_column() {
3553 required_and_optional::<BooleanArray, _>(
3554 [true, false].iter().cycle().copied().take(SMALL_SIZE),
3555 );
3556 }
3557
3558 #[test]
3559 #[cfg_attr(miri, ignore)] fn bool_large_single_column() {
3561 let values = Arc::new(
3562 [None, Some(true), Some(false)]
3563 .iter()
3564 .cycle()
3565 .copied()
3566 .take(200_000)
3567 .collect::<BooleanArray>(),
3568 );
3569 let schema = Schema::new(vec![Field::new("col", values.data_type().clone(), true)]);
3570 let expected_batch = RecordBatch::try_new(Arc::new(schema), vec![values]).unwrap();
3571 let file = tempfile::tempfile().unwrap();
3572
3573 let mut writer =
3574 ArrowWriter::try_new(file.try_clone().unwrap(), expected_batch.schema(), None)
3575 .expect("Unable to write file");
3576 writer.write(&expected_batch).unwrap();
3577 writer.close().unwrap();
3578 }
3579
3580 #[test]
3581 fn check_page_offset_index_with_nan() {
3582 let values = Arc::new(Float64Array::from(vec![f64::NAN; 10]));
3583 let schema = Schema::new(vec![Field::new("col", DataType::Float64, true)]);
3584 let batch = RecordBatch::try_new(Arc::new(schema), vec![values]).unwrap();
3585
3586 let mut out = Vec::with_capacity(1024);
3587 let mut writer =
3588 ArrowWriter::try_new(&mut out, batch.schema(), None).expect("Unable to write file");
3589 writer.write(&batch).unwrap();
3590 let file_meta_data = writer.close().unwrap();
3591 for row_group in file_meta_data.row_groups() {
3592 for column in row_group.columns() {
3593 assert!(column.offset_index_offset().is_some());
3594 assert!(column.offset_index_length().is_some());
3595 assert!(column.column_index_offset().is_some());
3596 assert!(column.column_index_length().is_some());
3597 }
3598 }
3599 if let Some(page_index) = file_meta_data.page_index() {
3600 for rg in 0..file_meta_data.num_row_groups() {
3601 for col in 0..file_meta_data.row_group(rg).num_columns() {
3602 let idx = page_index
3603 .column_index(rg, col)
3604 .expect("column index should exist");
3605 assert!(idx.nan_counts().is_some());
3606 let ColumnIndexMetaData::DOUBLE(float_idx) = idx else {
3607 panic!("expected double statistics")
3608 };
3609 for i in 0..idx.num_pages() as usize {
3610 assert_eq!(float_idx.nan_count(i), Some(10));
3611 assert_eq!(
3612 f64::NAN.total_cmp(float_idx.min_value(i).unwrap()),
3613 Ordering::Equal
3614 );
3615 assert_eq!(
3616 f64::NAN.total_cmp(float_idx.max_value(i).unwrap()),
3617 Ordering::Equal
3618 );
3619 }
3620 }
3621 }
3622 } else {
3623 panic!("page index should be present");
3624 }
3625 }
3626
3627 #[test]
3628 fn check_page_offset_index_with_mixed_nan() {
3629 let schema = Arc::new(Schema::new(vec![Field::new(
3630 "col",
3631 DataType::Float64,
3632 true,
3633 )]));
3634
3635 let mut out = Vec::with_capacity(1024);
3636 let props = WriterProperties::builder()
3637 .set_data_page_row_count_limit(10)
3638 .build();
3639 let mut writer = ArrowWriter::try_new(&mut out, schema.clone(), Some(props))
3640 .expect("Unable to write file");
3641
3642 let values = Arc::new(Float64Array::from(vec![f64::NAN; 10]));
3644 let batch = RecordBatch::try_new(schema.clone(), vec![values]).unwrap();
3645 writer.write(&batch).unwrap();
3646
3647 let values = Arc::new(Float64Array::from(vec![-f64::NAN; 10]));
3649 let batch = RecordBatch::try_new(schema.clone(), vec![values]).unwrap();
3650 writer.write(&batch).unwrap();
3651
3652 let values = Arc::new(Float64Array::from(vec![0_f64; 10]));
3654 let batch = RecordBatch::try_new(schema.clone(), vec![values]).unwrap();
3655 writer.write(&batch).unwrap();
3656
3657 let values = Arc::new(Float64Array::from(vec![
3659 -1.0,
3660 0.0,
3661 f64::NAN,
3662 -f64::NAN,
3663 1.0,
3664 ]));
3665 let batch = RecordBatch::try_new(schema.clone(), vec![values]).unwrap();
3666 writer.write(&batch).unwrap();
3667
3668 let file_meta_data = writer.close().unwrap();
3669
3670 let col_stats = file_meta_data
3672 .row_group(0)
3673 .column(0)
3674 .statistics()
3675 .expect("missing column chunk statistics");
3676
3677 assert_eq!(col_stats.nan_count_opt(), Some(22));
3678 assert_eq!(col_stats.min_bytes_opt(), Some((-1.0f64).as_bytes()));
3679 assert_eq!(col_stats.max_bytes_opt(), Some(1.0f64.as_bytes()));
3680
3681 assert!(file_meta_data.page_index().is_some());
3682 let col_idx = &file_meta_data.page_index().unwrap().column_index(0, 0);
3683 assert_eq!(col_idx.as_ref().unwrap().num_pages(), 4);
3684
3685 let Some(ColumnIndexMetaData::DOUBLE(float_idx)) = col_idx else {
3687 panic!("expected double statistics")
3688 };
3689
3690 assert_eq!(float_idx.nan_counts, Some(vec![10, 10, 0, 2]));
3691 assert_eq!(
3692 f64::NAN.total_cmp(float_idx.min_value(0).unwrap()),
3693 Ordering::Equal
3694 );
3695 assert_eq!(
3696 f64::NAN.total_cmp(float_idx.max_value(0).unwrap()),
3697 Ordering::Equal
3698 );
3699 assert_eq!(
3700 (-f64::NAN).total_cmp(float_idx.min_value(1).unwrap()),
3701 Ordering::Equal
3702 );
3703 assert_eq!(
3704 (-f64::NAN).total_cmp(float_idx.max_value(1).unwrap()),
3705 Ordering::Equal
3706 );
3707 assert_eq!(float_idx.min_value(2), Some(&0.0));
3708 assert_eq!(float_idx.max_value(2), Some(&0.0));
3709 assert_eq!(float_idx.min_value(3), Some(&-1.0));
3710 assert_eq!(float_idx.max_value(3), Some(&1.0));
3711 }
3712
3713 #[test]
3714 #[cfg_attr(miri, ignore)] fn i8_single_column() {
3716 required_and_optional::<Int8Array, _>(0..SMALL_SIZE as i8);
3717 }
3718
3719 #[test]
3720 #[cfg_attr(miri, ignore)] fn i16_single_column() {
3722 required_and_optional::<Int16Array, _>(0..SMALL_SIZE as i16);
3723 }
3724
3725 #[test]
3726 #[cfg_attr(miri, ignore)] fn i32_single_column() {
3728 required_and_optional::<Int32Array, _>(0..SMALL_SIZE as i32);
3729 }
3730
3731 #[test]
3732 #[cfg_attr(miri, ignore)] fn i64_single_column() {
3734 required_and_optional::<Int64Array, _>(0..SMALL_SIZE as i64);
3735 }
3736
3737 #[test]
3738 #[cfg_attr(miri, ignore)] fn u8_single_column() {
3740 required_and_optional::<UInt8Array, _>(0..SMALL_SIZE as u8);
3741 }
3742
3743 #[test]
3744 #[cfg_attr(miri, ignore)] fn u16_single_column() {
3746 required_and_optional::<UInt16Array, _>(0..SMALL_SIZE as u16);
3747 }
3748
3749 #[test]
3750 #[cfg_attr(miri, ignore)] fn u32_single_column() {
3752 required_and_optional::<UInt32Array, _>(0..SMALL_SIZE as u32);
3753 }
3754
3755 #[test]
3756 #[cfg_attr(miri, ignore)] fn u64_single_column() {
3758 required_and_optional::<UInt64Array, _>(0..SMALL_SIZE as u64);
3759 }
3760
3761 #[test]
3762 #[cfg_attr(miri, ignore)] fn f32_single_column() {
3764 required_and_optional::<Float32Array, _>((0..SMALL_SIZE).map(|i| i as f32));
3765 }
3766
3767 #[test]
3768 #[cfg_attr(miri, ignore)] fn f64_single_column() {
3770 required_and_optional::<Float64Array, _>((0..SMALL_SIZE).map(|i| i as f64));
3771 }
3772
3773 #[test]
3778 #[cfg_attr(miri, ignore)] fn timestamp_second_single_column() {
3780 let raw_values: Vec<_> = (0..SMALL_SIZE as i64).collect();
3781 let values = Arc::new(TimestampSecondArray::from(raw_values));
3782
3783 RoundTripTest::new(values).with_nullable(false).run();
3784 }
3785
3786 #[test]
3787 #[cfg_attr(miri, ignore)] fn timestamp_millisecond_single_column() {
3789 let raw_values: Vec<_> = (0..SMALL_SIZE as i64).collect();
3790 let values = Arc::new(TimestampMillisecondArray::from(raw_values));
3791
3792 RoundTripTest::new(values).with_nullable(false).run();
3793 }
3794
3795 #[test]
3796 #[cfg_attr(miri, ignore)] fn timestamp_microsecond_single_column() {
3798 let raw_values: Vec<_> = (0..SMALL_SIZE as i64).collect();
3799 let values = Arc::new(TimestampMicrosecondArray::from(raw_values));
3800
3801 RoundTripTest::new(values).with_nullable(false).run();
3802 }
3803
3804 #[test]
3805 #[cfg_attr(miri, ignore)] fn timestamp_nanosecond_single_column() {
3807 let raw_values: Vec<_> = (0..SMALL_SIZE as i64).collect();
3808 let values = Arc::new(TimestampNanosecondArray::from(raw_values));
3809
3810 RoundTripTest::new(values).with_nullable(false).run();
3811 }
3812
3813 #[test]
3814 #[cfg_attr(miri, ignore)] fn date32_single_column() {
3816 required_and_optional::<Date32Array, _>(0..SMALL_SIZE as i32);
3817 }
3818
3819 #[test]
3820 #[cfg_attr(miri, ignore)] fn date64_single_column() {
3822 required_and_optional::<Date64Array, _>(
3824 (0..(SMALL_SIZE as i64 * 86400000)).step_by(86400000),
3825 );
3826 }
3827
3828 #[test]
3829 #[cfg_attr(miri, ignore)] fn time32_second_single_column() {
3831 required_and_optional::<Time32SecondArray, _>(0..SMALL_SIZE as i32);
3832 }
3833
3834 #[test]
3835 #[cfg_attr(miri, ignore)] fn time32_millisecond_single_column() {
3837 required_and_optional::<Time32MillisecondArray, _>(0..SMALL_SIZE as i32);
3838 }
3839
3840 #[test]
3841 #[cfg_attr(miri, ignore)] fn time64_microsecond_single_column() {
3843 required_and_optional::<Time64MicrosecondArray, _>(0..SMALL_SIZE as i64);
3844 }
3845
3846 #[test]
3847 #[cfg_attr(miri, ignore)] fn time64_nanosecond_single_column() {
3849 required_and_optional::<Time64NanosecondArray, _>(0..SMALL_SIZE as i64);
3850 }
3851
3852 #[test]
3853 #[cfg_attr(miri, ignore)] fn duration_second_single_column() {
3855 required_and_optional::<DurationSecondArray, _>(0..SMALL_SIZE as i64);
3856 }
3857
3858 #[test]
3859 #[cfg_attr(miri, ignore)] fn duration_millisecond_single_column() {
3861 required_and_optional::<DurationMillisecondArray, _>(0..SMALL_SIZE as i64);
3862 }
3863
3864 #[test]
3865 #[cfg_attr(miri, ignore)] fn duration_microsecond_single_column() {
3867 required_and_optional::<DurationMicrosecondArray, _>(0..SMALL_SIZE as i64);
3868 }
3869
3870 #[test]
3871 #[cfg_attr(miri, ignore)] fn duration_nanosecond_single_column() {
3873 required_and_optional::<DurationNanosecondArray, _>(0..SMALL_SIZE as i64);
3874 }
3875
3876 #[test]
3877 #[cfg_attr(miri, ignore)] fn interval_year_month_single_column() {
3879 required_and_optional::<IntervalYearMonthArray, _>(0..SMALL_SIZE as i32);
3880 }
3881
3882 #[test]
3883 #[cfg_attr(miri, ignore)] fn interval_day_time_single_column() {
3885 required_and_optional::<IntervalDayTimeArray, _>(vec![
3886 IntervalDayTime::new(0, 1),
3887 IntervalDayTime::new(0, 3),
3888 IntervalDayTime::new(3, -2),
3889 IntervalDayTime::new(-200, 4),
3890 ]);
3891 }
3892
3893 #[test]
3894 #[should_panic(
3895 expected = "Attempting to write an Arrow interval type MonthDayNano to parquet that is not yet implemented"
3896 )]
3897 fn interval_month_day_nano_single_column() {
3898 required_and_optional::<IntervalMonthDayNanoArray, _>(vec![
3899 IntervalMonthDayNano::new(0, 1, 5),
3900 IntervalMonthDayNano::new(0, 3, 2),
3901 IntervalMonthDayNano::new(3, -2, -5),
3902 IntervalMonthDayNano::new(-200, 4, -1),
3903 ]);
3904 }
3905
3906 #[test]
3907 #[cfg_attr(miri, ignore)] fn binary_single_column() {
3909 let one_vec: Vec<u8> = (0..SMALL_SIZE as u8).collect();
3910 let many_vecs: Vec<_> = std::iter::repeat_n(one_vec, SMALL_SIZE).collect();
3911 let many_vecs_iter = many_vecs.iter().map(|v| v.as_slice());
3912
3913 values_required::<BinaryArray, _>(many_vecs_iter);
3915 }
3916
3917 #[test]
3918 #[cfg_attr(miri, ignore)] fn binary_view_single_column() {
3920 let one_vec: Vec<u8> = (0..SMALL_SIZE as u8).collect();
3921 let many_vecs: Vec<_> = std::iter::repeat_n(one_vec, SMALL_SIZE).collect();
3922 let many_vecs_iter = many_vecs.iter().map(|v| v.as_slice());
3923
3924 values_required::<BinaryViewArray, _>(many_vecs_iter);
3926 }
3927
3928 #[test]
3929 #[cfg_attr(miri, ignore)] fn i32_column_bloom_filter_at_end() {
3931 let array = Arc::new(Int32Array::from_iter(0..SMALL_SIZE as i32));
3932 let files = RoundTripTest::new(array)
3933 .with_nullable(false)
3934 .with_bloom_filter(true)
3935 .with_bloom_filter_position(BloomFilterPosition::End)
3936 .run();
3937
3938 check_bloom_filter(
3939 files,
3940 "col".to_string(),
3941 (0..SMALL_SIZE as i32).collect(),
3942 (SMALL_SIZE as i32 + 1..SMALL_SIZE as i32 + 10).collect(),
3943 );
3944 }
3945
3946 #[test]
3947 #[cfg_attr(miri, ignore)] fn i32_column_bloom_filter() {
3949 let array = Arc::new(Int32Array::from_iter(0..SMALL_SIZE as i32));
3950 let files = RoundTripTest::new(array)
3951 .with_nullable(false)
3952 .with_bloom_filter(true)
3953 .run();
3954
3955 check_bloom_filter(
3956 files,
3957 "col".to_string(),
3958 (0..SMALL_SIZE as i32).collect(),
3959 (SMALL_SIZE as i32 + 1..SMALL_SIZE as i32 + 10).collect(),
3960 );
3961 }
3962
3963 fn write_with_bloom_filter(array: ArrayRef, dictionary_page_size_limit: usize) -> Bytes {
3964 let schema = Arc::new(Schema::new(vec![Field::new(
3965 "col",
3966 array.data_type().clone(),
3967 false,
3968 )]));
3969 let batch = RecordBatch::try_new(schema.clone(), vec![array]).unwrap();
3970 let props = WriterProperties::builder()
3971 .set_dictionary_enabled(true)
3972 .set_dictionary_page_size_limit(dictionary_page_size_limit)
3973 .set_write_batch_size(256)
3974 .set_bloom_filter_enabled(true)
3975 .build();
3976 let mut buf = Vec::new();
3977 let mut writer = ArrowWriter::try_new(&mut buf, schema, Some(props)).unwrap();
3978 writer.write(&batch).unwrap();
3979 writer.close().unwrap();
3980 Bytes::from(buf)
3981 }
3982
3983 fn data_page_encoding_mask(file: &Bytes) -> EncodingMask {
3984 let metadata = ParquetMetaDataReader::new().parse_and_finish(file).unwrap();
3985 *metadata
3986 .row_group(0)
3987 .column(0)
3988 .page_encoding_stats_mask()
3989 .unwrap()
3990 }
3991
3992 #[test]
3995 fn string_column_bloom_filter_populated_from_dictionary() {
3996 let values: Vec<String> = (0..2000).map(|i| format!("value-{}", i % 10)).collect();
3997 let array = Arc::new(StringArray::from_iter_values(&values));
3998 let file = write_with_bloom_filter(array, 1024 * 1024);
3999 assert!(data_page_encoding_mask(&file).is_only(Encoding::RLE_DICTIONARY));
4000
4001 check_bloom_filter(
4002 vec![file],
4003 "col".to_string(),
4004 (0..10).map(|i| format!("value-{i}").into_bytes()).collect(),
4005 (10..20)
4006 .map(|i| format!("value-{i}").into_bytes())
4007 .collect(),
4008 );
4009 }
4010
4011 #[test]
4014 fn string_column_bloom_filter_across_dictionary_fallback() {
4015 let values: Vec<String> = (0..2000).map(|i| format!("value-{i}")).collect();
4016 let array = Arc::new(StringArray::from_iter_values(&values));
4017 let file = write_with_bloom_filter(array, 1024);
4018 let encodings = data_page_encoding_mask(&file);
4019 assert!(
4020 encodings.is_set(Encoding::RLE_DICTIONARY) && encodings.is_set(Encoding::PLAIN),
4021 "expected dictionary and plain data pages, got {encodings:?}"
4022 );
4023
4024 check_bloom_filter(
4025 vec![file],
4026 "col".to_string(),
4027 values.into_iter().map(String::into_bytes).collect(),
4028 (2000..2010)
4029 .map(|i| format!("value-{i}").into_bytes())
4030 .collect(),
4031 );
4032 }
4033
4034 #[test]
4035 fn i64_column_bloom_filter_populated_from_dictionary() {
4036 let array = Arc::new(Int64Array::from_iter_values((0..2000).map(|i| i % 10)));
4037 let file = write_with_bloom_filter(array, 1024 * 1024);
4038 assert!(data_page_encoding_mask(&file).is_only(Encoding::RLE_DICTIONARY));
4039
4040 check_bloom_filter(
4041 vec![file],
4042 "col".to_string(),
4043 (0..10i64).collect(),
4044 (10..20i64).collect(),
4045 );
4046 }
4047
4048 #[test]
4049 fn i64_column_bloom_filter_across_dictionary_fallback() {
4050 let array = Arc::new(Int64Array::from_iter_values(0..2000i64));
4051 let file = write_with_bloom_filter(array, 1024);
4052 let encodings = data_page_encoding_mask(&file);
4053 assert!(
4054 encodings.is_set(Encoding::RLE_DICTIONARY) && encodings.is_set(Encoding::PLAIN),
4055 "expected dictionary and plain data pages, got {encodings:?}"
4056 );
4057
4058 check_bloom_filter(
4059 vec![file],
4060 "col".to_string(),
4061 (0..2000i64).collect(),
4062 (2000..2010i64).collect(),
4063 );
4064 }
4065
4066 #[test]
4071 #[cfg_attr(miri, ignore)] fn i32_column_bloom_filter_fixed_ndv() {
4073 let array = Arc::new(Int32Array::from_iter(0..SMALL_SIZE as i32));
4074
4075 let files = RoundTripTest::new(array.clone())
4077 .with_nullable(false)
4078 .with_bloom_filter(true)
4079 .with_bloom_filter_ndv(1_000_000)
4080 .run();
4081
4082 check_bloom_filter(
4083 files,
4084 "col".to_string(),
4085 (0..SMALL_SIZE as i32).collect(),
4086 (SMALL_SIZE as i32 + 1..SMALL_SIZE as i32 + 10).collect(),
4087 );
4088
4089 let files = RoundTripTest::new(array)
4091 .with_nullable(false)
4092 .with_bloom_filter(true)
4093 .with_bloom_filter_ndv(3)
4094 .run();
4095
4096 check_bloom_filter(
4097 files,
4098 "col".to_string(),
4099 (0..SMALL_SIZE as i32).collect(),
4100 (SMALL_SIZE as i32 + 1..SMALL_SIZE as i32 + 10).collect(),
4101 );
4102 }
4103
4104 #[test]
4105 #[cfg_attr(miri, ignore)] fn binary_column_bloom_filter() {
4107 let one_vec: Vec<u8> = (0..SMALL_SIZE as u8).collect();
4108 let many_vecs: Vec<_> = std::iter::repeat_n(one_vec, SMALL_SIZE).collect();
4109 let many_vecs_iter = many_vecs.iter().map(|v| v.as_slice());
4110
4111 let array = Arc::new(BinaryArray::from_iter_values(many_vecs_iter));
4112 let files = RoundTripTest::new(array)
4113 .with_nullable(false)
4114 .with_bloom_filter(true)
4115 .run();
4116
4117 check_bloom_filter(
4118 files,
4119 "col".to_string(),
4120 many_vecs,
4121 vec![vec![(SMALL_SIZE + 1) as u8]],
4122 );
4123 }
4124
4125 #[test]
4126 #[cfg_attr(miri, ignore)] fn empty_string_null_column_bloom_filter() {
4128 let raw_values: Vec<_> = (0..SMALL_SIZE).map(|i| i.to_string()).collect();
4129 let raw_strs = raw_values.iter().map(|s| s.as_str());
4130
4131 let array = Arc::new(StringArray::from_iter_values(raw_strs));
4132 let files = RoundTripTest::new(array)
4133 .with_nullable(false)
4134 .with_bloom_filter(true)
4135 .run();
4136
4137 let optional_raw_values: Vec<_> = raw_values
4138 .iter()
4139 .enumerate()
4140 .filter_map(|(i, v)| if i % 2 == 0 { None } else { Some(v.as_str()) })
4141 .collect();
4142 check_bloom_filter(files, "col".to_string(), optional_raw_values, vec![""]);
4144 }
4145
4146 #[test]
4147 #[cfg_attr(miri, ignore)] fn large_binary_single_column() {
4149 let one_vec: Vec<u8> = (0..SMALL_SIZE as u8).collect();
4150 let many_vecs: Vec<_> = std::iter::repeat_n(one_vec, SMALL_SIZE).collect();
4151 let many_vecs_iter = many_vecs.iter().map(|v| v.as_slice());
4152
4153 values_required::<LargeBinaryArray, _>(many_vecs_iter);
4155 }
4156
4157 #[test]
4158 #[cfg_attr(miri, ignore)] fn fixed_size_binary_single_column() {
4160 let mut builder = FixedSizeBinaryBuilder::new(4);
4161 builder.append_value(b"0123").unwrap();
4162 builder.append_null();
4163 builder.append_value(b"8910").unwrap();
4164 builder.append_value(b"1112").unwrap();
4165 let array = Arc::new(builder.finish());
4166
4167 RoundTripTest::new(array).run();
4168 }
4169
4170 #[test]
4171 #[cfg_attr(miri, ignore)] fn string_single_column() {
4173 let raw_values: Vec<_> = (0..SMALL_SIZE).map(|i| i.to_string()).collect();
4174 let raw_strs = raw_values.iter().map(|s| s.as_str());
4175
4176 required_and_optional::<StringArray, _>(raw_strs);
4177 }
4178
4179 #[test]
4180 #[cfg_attr(miri, ignore)] fn large_string_single_column() {
4182 let raw_values: Vec<_> = (0..SMALL_SIZE).map(|i| i.to_string()).collect();
4183 let raw_strs = raw_values.iter().map(|s| s.as_str());
4184
4185 required_and_optional::<LargeStringArray, _>(raw_strs);
4186 }
4187
4188 #[test]
4189 #[cfg_attr(miri, ignore)] fn string_view_single_column() {
4191 let raw_values: Vec<_> = (0..SMALL_SIZE).map(|i| i.to_string()).collect();
4192 let raw_strs = raw_values.iter().map(|s| s.as_str());
4193
4194 required_and_optional::<StringViewArray, _>(raw_strs);
4195 }
4196
4197 #[test]
4198 fn null_list_single_column() {
4199 let null_field = Field::new_list_field(DataType::Null, true);
4200 let list_field = Field::new("emptylist", DataType::List(Arc::new(null_field)), true);
4201
4202 let schema = Schema::new(vec![list_field]);
4203
4204 let a_values = NullArray::new(2);
4206 let a_value_offsets = arrow::buffer::Buffer::from([0, 0, 0, 2].to_byte_slice());
4207 let a_list_data = ArrayData::builder(DataType::List(Arc::new(Field::new_list_field(
4208 DataType::Null,
4209 true,
4210 ))))
4211 .len(3)
4212 .add_buffer(a_value_offsets)
4213 .null_bit_buffer(Some(Buffer::from([0b00000101])))
4214 .add_child_data(a_values.into_data())
4215 .build()
4216 .unwrap();
4217
4218 let a = ListArray::from(a_list_data);
4219
4220 assert!(a.is_valid(0));
4221 assert!(!a.is_valid(1));
4222 assert!(a.is_valid(2));
4223
4224 assert_eq!(a.value(0).len(), 0);
4225 assert_eq!(a.value(2).len(), 2);
4226 assert_eq!(a.value(2).logical_nulls().unwrap().null_count(), 2);
4227
4228 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(a)]).unwrap();
4229 roundtrip(batch, None);
4230 }
4231
4232 #[test]
4233 #[cfg_attr(miri, ignore)] fn list_single_column() {
4235 let a_values = Int32Array::from(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
4236 let a_value_offsets = arrow::buffer::Buffer::from([0, 1, 3, 3, 6, 10].to_byte_slice());
4237 let a_list_data = ArrayData::builder(DataType::List(Arc::new(Field::new_list_field(
4238 DataType::Int32,
4239 false,
4240 ))))
4241 .len(5)
4242 .add_buffer(a_value_offsets)
4243 .null_bit_buffer(Some(Buffer::from([0b00011011])))
4244 .add_child_data(a_values.into_data())
4245 .build()
4246 .unwrap();
4247
4248 assert_eq!(a_list_data.null_count(), 1);
4249
4250 let a = ListArray::from(a_list_data);
4251 let values = Arc::new(a);
4252
4253 RoundTripTest::new(values).run();
4254 }
4255
4256 #[test]
4257 #[cfg_attr(miri, ignore)] fn large_list_single_column() {
4259 let a_values = Int32Array::from(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
4260 let a_value_offsets = arrow::buffer::Buffer::from([0i64, 1, 3, 3, 6, 10].to_byte_slice());
4261 let a_list_data = ArrayData::builder(DataType::LargeList(Arc::new(Field::new(
4262 "large_item",
4263 DataType::Int32,
4264 true,
4265 ))))
4266 .len(5)
4267 .add_buffer(a_value_offsets)
4268 .add_child_data(a_values.into_data())
4269 .null_bit_buffer(Some(Buffer::from([0b00011011])))
4270 .build()
4271 .unwrap();
4272
4273 assert_eq!(a_list_data.null_count(), 1);
4275
4276 let a = LargeListArray::from(a_list_data);
4277 let values = Arc::new(a);
4278
4279 RoundTripTest::new(values).run();
4280 }
4281
4282 #[test]
4283 #[cfg_attr(miri, ignore)] fn list_nested_nulls() {
4285 use arrow::datatypes::Int32Type;
4286 let data = vec![
4287 Some(vec![Some(1)]),
4288 Some(vec![Some(2), Some(3)]),
4289 None,
4290 Some(vec![Some(4), Some(5), None]),
4291 Some(vec![None]),
4292 Some(vec![Some(6), Some(7)]),
4293 ];
4294
4295 let list = ListArray::from_iter_primitive::<Int32Type, _, _>(data.clone());
4296 RoundTripTest::new(Arc::new(list)).run();
4297
4298 let list = LargeListArray::from_iter_primitive::<Int32Type, _, _>(data);
4299 RoundTripTest::new(Arc::new(list)).run();
4300 }
4301
4302 #[test]
4303 #[cfg_attr(miri, ignore)] fn list_utf8_view_selective_padding_roundtrip() {
4305 let item = Arc::new(Field::new_list_field(DataType::Utf8View, true));
4306 let mut builder = ListBuilder::new(StringViewBuilder::new()).with_field(item);
4307 builder.values().append_value("a");
4308 builder.values().append_null();
4309 builder.append(true);
4310 builder.append(false);
4313 builder.values().append_value("large payload over 12 bytes");
4315 builder.append(true);
4316
4317 RoundTripTest::new(Arc::new(builder.finish())).run();
4318 }
4319
4320 #[test]
4321 #[cfg_attr(miri, ignore)] fn struct_single_column() {
4323 let a_values = Int32Array::from(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
4324 let struct_field_a = Arc::new(Field::new("f", DataType::Int32, false));
4325 let s = StructArray::from(vec![(struct_field_a, Arc::new(a_values) as ArrayRef)]);
4326
4327 let values = Arc::new(s);
4328 RoundTripTest::new(values).with_nullable(false).run();
4329 }
4330
4331 #[test]
4332 fn list_and_map_coerced_names() {
4333 let list_field =
4335 Field::new_list("my_list", Field::new("item", DataType::Int32, false), false);
4336 let map_field = Field::new_map(
4337 "my_map",
4338 "my_entries",
4339 Field::new("my_keys", DataType::Int32, false),
4340 Field::new("my_values", DataType::Int32, true),
4341 false,
4342 true,
4343 );
4344
4345 let list_array = create_random_array(&list_field, 100, 0.0, 0.0).unwrap();
4346 let map_array = create_random_array(&map_field, 100, 0.0, 0.0).unwrap();
4347
4348 let arrow_schema = Arc::new(Schema::new(vec![list_field, map_field]));
4349
4350 let props = Some(WriterProperties::builder().set_coerce_types(true).build());
4352 let file = tempfile::tempfile().unwrap();
4353 let mut writer =
4354 ArrowWriter::try_new(file.try_clone().unwrap(), arrow_schema.clone(), props).unwrap();
4355
4356 let batch = RecordBatch::try_new(arrow_schema, vec![list_array, map_array]).unwrap();
4357 writer.write(&batch).unwrap();
4358 let file_metadata = writer.close().unwrap();
4359
4360 let schema = file_metadata.file_metadata().schema();
4361 let list_field = &schema.get_fields()[0].get_fields()[0];
4363 assert_eq!(list_field.get_fields()[0].name(), "element");
4364
4365 let map_field = &schema.get_fields()[1].get_fields()[0];
4366 assert_eq!(map_field.name(), "key_value");
4368 assert_eq!(map_field.get_fields()[0].name(), "key");
4370 assert_eq!(map_field.get_fields()[1].name(), "value");
4372
4373 let reader = SerializedFileReader::new(file).unwrap();
4375 let file_schema = reader.metadata().file_metadata().schema();
4376 let fields = file_schema.get_fields();
4377 let list_field = &fields[0].get_fields()[0];
4378 assert_eq!(list_field.get_fields()[0].name(), "element");
4379 let map_field = &fields[1].get_fields()[0];
4380 assert_eq!(map_field.name(), "key_value");
4381 assert_eq!(map_field.get_fields()[0].name(), "key");
4382 assert_eq!(map_field.get_fields()[1].name(), "value");
4383 }
4384
4385 #[test]
4386 #[cfg_attr(miri, ignore)] fn fallback_flush_data_page() {
4388 let raw_values: Vec<_> = (0..MEDIUM_SIZE).map(|i| i.to_string()).collect();
4390 let values = Arc::new(StringArray::from(raw_values));
4391 let encodings = vec![
4392 Encoding::DELTA_BYTE_ARRAY,
4393 Encoding::DELTA_LENGTH_BYTE_ARRAY,
4394 ];
4395 let data_type = values.data_type().clone();
4396 let schema = Arc::new(Schema::new(vec![Field::new("col", data_type, false)]));
4397 let expected_batch = RecordBatch::try_new(schema, vec![values]).unwrap();
4398
4399 let row_group_sizes = [1024, SMALL_SIZE, SMALL_SIZE / 2, SMALL_SIZE / 2 + 1, 10];
4400 let data_page_size_limit: usize = 32;
4401 let write_batch_size: usize = 16;
4402
4403 for encoding in &encodings {
4404 for row_group_size in row_group_sizes {
4405 let props = WriterProperties::builder()
4406 .set_writer_version(WriterVersion::PARQUET_2_0)
4407 .set_max_row_group_row_count(Some(row_group_size))
4408 .set_dictionary_enabled(false)
4409 .set_encoding(*encoding)
4410 .set_data_page_size_limit(data_page_size_limit)
4411 .set_write_batch_size(write_batch_size)
4412 .build();
4413
4414 roundtrip_opts_with_array_validation(&expected_batch, props, |a, b| {
4415 let string_array_a = StringArray::from(a.clone());
4416 let string_array_b = StringArray::from(b.clone());
4417 let vec_a: Vec<&str> = string_array_a.iter().map(|v| v.unwrap()).collect();
4418 let vec_b: Vec<&str> = string_array_b.iter().map(|v| v.unwrap()).collect();
4419 assert_eq!(
4420 vec_a, vec_b,
4421 "failed for encoder: {encoding:?} and row_group_size: {row_group_size:?}"
4422 );
4423 });
4424 }
4425 }
4426 }
4427
4428 #[test]
4429 #[cfg_attr(miri, ignore)] fn arrow_writer_string_dictionary() {
4431 #[expect(deprecated)]
4433 let schema = Arc::new(Schema::new(vec![Field::new_dict(
4434 "dictionary",
4435 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
4436 true,
4437 42,
4438 true,
4439 )]));
4440
4441 let d: Int32DictionaryArray = [Some("alpha"), None, Some("beta"), Some("alpha")]
4443 .iter()
4444 .copied()
4445 .collect();
4446
4447 RoundTripTest::new(Arc::new(d)).with_schema(schema).run();
4449 }
4450
4451 #[test]
4452 fn arrow_writer_test_type_compatibility() {
4453 fn ensure_compatible_write<T1, T2>(array1: T1, array2: T2, expected_result: T1)
4454 where
4455 T1: Array + 'static,
4456 T2: Array + 'static,
4457 {
4458 let schema1 = Arc::new(Schema::new(vec![Field::new(
4459 "a",
4460 array1.data_type().clone(),
4461 false,
4462 )]));
4463
4464 let file = tempfile().unwrap();
4465 let mut writer =
4466 ArrowWriter::try_new(file.try_clone().unwrap(), schema1.clone(), None).unwrap();
4467
4468 let rb1 = RecordBatch::try_new(schema1.clone(), vec![Arc::new(array1)]).unwrap();
4469 writer.write(&rb1).unwrap();
4470
4471 let schema2 = Arc::new(Schema::new(vec![Field::new(
4472 "a",
4473 array2.data_type().clone(),
4474 false,
4475 )]));
4476 let rb2 = RecordBatch::try_new(schema2, vec![Arc::new(array2)]).unwrap();
4477 writer.write(&rb2).unwrap();
4478
4479 writer.close().unwrap();
4480
4481 let mut record_batch_reader =
4482 ParquetRecordBatchReader::try_new(file.try_clone().unwrap(), 1024).unwrap();
4483 let actual_batch = record_batch_reader.next().unwrap().unwrap();
4484
4485 let expected_batch =
4486 RecordBatch::try_new(schema1, vec![Arc::new(expected_result)]).unwrap();
4487 assert_eq!(actual_batch, expected_batch);
4488 }
4489
4490 ensure_compatible_write(
4493 DictionaryArray::new(
4494 UInt8Array::from_iter_values(vec![0]),
4495 Arc::new(StringArray::from_iter_values(vec!["parquet"])),
4496 ),
4497 StringArray::from_iter_values(vec!["barquet"]),
4498 DictionaryArray::new(
4499 UInt8Array::from_iter_values(vec![0, 1]),
4500 Arc::new(StringArray::from_iter_values(vec!["parquet", "barquet"])),
4501 ),
4502 );
4503
4504 ensure_compatible_write(
4505 StringArray::from_iter_values(vec!["parquet"]),
4506 DictionaryArray::new(
4507 UInt8Array::from_iter_values(vec![0]),
4508 Arc::new(StringArray::from_iter_values(vec!["barquet"])),
4509 ),
4510 StringArray::from_iter_values(vec!["parquet", "barquet"]),
4511 );
4512
4513 ensure_compatible_write(
4516 DictionaryArray::new(
4517 UInt8Array::from_iter_values(vec![0]),
4518 Arc::new(StringArray::from_iter_values(vec!["parquet"])),
4519 ),
4520 DictionaryArray::new(
4521 UInt16Array::from_iter_values(vec![0]),
4522 Arc::new(StringArray::from_iter_values(vec!["barquet"])),
4523 ),
4524 DictionaryArray::new(
4525 UInt8Array::from_iter_values(vec![0, 1]),
4526 Arc::new(StringArray::from_iter_values(vec!["parquet", "barquet"])),
4527 ),
4528 );
4529
4530 ensure_compatible_write(
4532 DictionaryArray::new(
4533 UInt8Array::from_iter_values(vec![0]),
4534 Arc::new(StringArray::from_iter_values(vec!["parquet"])),
4535 ),
4536 DictionaryArray::new(
4537 UInt8Array::from_iter_values(vec![0]),
4538 Arc::new(LargeStringArray::from_iter_values(vec!["barquet"])),
4539 ),
4540 DictionaryArray::new(
4541 UInt8Array::from_iter_values(vec![0, 1]),
4542 Arc::new(StringArray::from_iter_values(vec!["parquet", "barquet"])),
4543 ),
4544 );
4545
4546 ensure_compatible_write(
4548 DictionaryArray::new(
4549 UInt8Array::from_iter_values(vec![0]),
4550 Arc::new(StringArray::from_iter_values(vec!["parquet"])),
4551 ),
4552 LargeStringArray::from_iter_values(vec!["barquet"]),
4553 DictionaryArray::new(
4554 UInt8Array::from_iter_values(vec![0, 1]),
4555 Arc::new(StringArray::from_iter_values(vec!["parquet", "barquet"])),
4556 ),
4557 );
4558
4559 ensure_compatible_write(
4562 StringArray::from_iter_values(vec!["parquet"]),
4563 LargeStringArray::from_iter_values(vec!["barquet"]),
4564 StringArray::from_iter_values(vec!["parquet", "barquet"]),
4565 );
4566
4567 ensure_compatible_write(
4568 LargeStringArray::from_iter_values(vec!["parquet"]),
4569 StringArray::from_iter_values(vec!["barquet"]),
4570 LargeStringArray::from_iter_values(vec!["parquet", "barquet"]),
4571 );
4572
4573 ensure_compatible_write(
4574 StringArray::from_iter_values(vec!["parquet"]),
4575 StringViewArray::from_iter_values(vec!["barquet"]),
4576 StringArray::from_iter_values(vec!["parquet", "barquet"]),
4577 );
4578
4579 ensure_compatible_write(
4580 StringViewArray::from_iter_values(vec!["parquet"]),
4581 StringArray::from_iter_values(vec!["barquet"]),
4582 StringViewArray::from_iter_values(vec!["parquet", "barquet"]),
4583 );
4584
4585 ensure_compatible_write(
4586 LargeStringArray::from_iter_values(vec!["parquet"]),
4587 StringViewArray::from_iter_values(vec!["barquet"]),
4588 LargeStringArray::from_iter_values(vec!["parquet", "barquet"]),
4589 );
4590
4591 ensure_compatible_write(
4592 StringViewArray::from_iter_values(vec!["parquet"]),
4593 LargeStringArray::from_iter_values(vec!["barquet"]),
4594 StringViewArray::from_iter_values(vec!["parquet", "barquet"]),
4595 );
4596
4597 ensure_compatible_write(
4600 BinaryArray::from_iter_values(vec![b"parquet"]),
4601 LargeBinaryArray::from_iter_values(vec![b"barquet"]),
4602 BinaryArray::from_iter_values(vec![b"parquet", b"barquet"]),
4603 );
4604
4605 ensure_compatible_write(
4606 LargeBinaryArray::from_iter_values(vec![b"parquet"]),
4607 BinaryArray::from_iter_values(vec![b"barquet"]),
4608 LargeBinaryArray::from_iter_values(vec![b"parquet", b"barquet"]),
4609 );
4610
4611 ensure_compatible_write(
4612 BinaryArray::from_iter_values(vec![b"parquet"]),
4613 BinaryViewArray::from_iter_values(vec![b"barquet"]),
4614 BinaryArray::from_iter_values(vec![b"parquet", b"barquet"]),
4615 );
4616
4617 ensure_compatible_write(
4618 BinaryViewArray::from_iter_values(vec![b"parquet"]),
4619 BinaryArray::from_iter_values(vec![b"barquet"]),
4620 BinaryViewArray::from_iter_values(vec![b"parquet", b"barquet"]),
4621 );
4622
4623 ensure_compatible_write(
4624 BinaryViewArray::from_iter_values(vec![b"parquet"]),
4625 LargeBinaryArray::from_iter_values(vec![b"barquet"]),
4626 BinaryViewArray::from_iter_values(vec![b"parquet", b"barquet"]),
4627 );
4628
4629 ensure_compatible_write(
4630 LargeBinaryArray::from_iter_values(vec![b"parquet"]),
4631 BinaryViewArray::from_iter_values(vec![b"barquet"]),
4632 LargeBinaryArray::from_iter_values(vec![b"parquet", b"barquet"]),
4633 );
4634
4635 let list_field_metadata = HashMap::from_iter(vec![(
4638 PARQUET_FIELD_ID_META_KEY.to_string(),
4639 "1".to_string(),
4640 )]);
4641 let list_field = Field::new_list_field(DataType::Int32, false);
4642
4643 let values1 = Arc::new(Int32Array::from(vec![0, 1, 2, 3, 4]));
4644 let offsets1 = OffsetBuffer::new(vec![0, 2, 5].into());
4645
4646 let values2 = Arc::new(Int32Array::from(vec![5, 6, 7, 8, 9]));
4647 let offsets2 = OffsetBuffer::new(vec![0, 3, 5].into());
4648
4649 let values_expected = Arc::new(Int32Array::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]));
4650 let offsets_expected = OffsetBuffer::new(vec![0, 2, 5, 8, 10].into());
4651
4652 ensure_compatible_write(
4653 ListArray::try_new(
4655 Arc::new(
4656 list_field
4657 .clone()
4658 .with_metadata(list_field_metadata.clone()),
4659 ),
4660 offsets1,
4661 values1,
4662 None,
4663 )
4664 .unwrap(),
4665 ListArray::try_new(Arc::new(list_field.clone()), offsets2, values2, None).unwrap(),
4667 ListArray::try_new(
4669 Arc::new(
4670 list_field
4671 .clone()
4672 .with_metadata(list_field_metadata.clone()),
4673 ),
4674 offsets_expected,
4675 values_expected,
4676 None,
4677 )
4678 .unwrap(),
4679 );
4680 }
4681
4682 #[test]
4683 #[cfg_attr(miri, ignore)] fn arrow_writer_primitive_dictionary() {
4685 #[expect(deprecated)]
4687 let schema = Arc::new(Schema::new(vec![Field::new_dict(
4688 "dictionary",
4689 DataType::Dictionary(Box::new(DataType::UInt8), Box::new(DataType::UInt32)),
4690 true,
4691 42,
4692 true,
4693 )]));
4694
4695 let mut builder = PrimitiveDictionaryBuilder::<UInt8Type, UInt32Type>::new();
4697 builder.append(12345678).unwrap();
4698 builder.append_null();
4699 builder.append(22345678).unwrap();
4700 builder.append(12345678).unwrap();
4701 let d = builder.finish();
4702
4703 RoundTripTest::new(Arc::new(d)).with_schema(schema).run();
4704 }
4705
4706 #[test]
4707 #[cfg_attr(miri, ignore)] fn arrow_writer_decimal32_dictionary() {
4709 let integers = vec![12345, 56789, 34567];
4710
4711 let keys = UInt8Array::from(vec![Some(0), None, Some(1), Some(2), Some(1)]);
4712
4713 let values = Decimal32Array::from(integers.clone())
4714 .with_precision_and_scale(5, 2)
4715 .unwrap();
4716
4717 let array = DictionaryArray::new(keys, Arc::new(values));
4718 RoundTripTest::new(Arc::new(array.clone())).run();
4719
4720 let values = Decimal32Array::from(integers)
4721 .with_precision_and_scale(9, 2)
4722 .unwrap();
4723
4724 let array = array.with_values(Arc::new(values));
4725 RoundTripTest::new(Arc::new(array)).run();
4726 }
4727
4728 #[test]
4729 #[cfg_attr(miri, ignore)] fn arrow_writer_decimal64_dictionary() {
4731 let integers = vec![12345, 56789, 34567];
4732
4733 let keys = UInt8Array::from(vec![Some(0), None, Some(1), Some(2), Some(1)]);
4734
4735 let values = Decimal64Array::from(integers.clone())
4736 .with_precision_and_scale(5, 2)
4737 .unwrap();
4738
4739 let array = DictionaryArray::new(keys, Arc::new(values));
4740 RoundTripTest::new(Arc::new(array.clone())).run();
4741
4742 let values = Decimal64Array::from(integers)
4743 .with_precision_and_scale(12, 2)
4744 .unwrap();
4745
4746 let array = array.with_values(Arc::new(values));
4747 RoundTripTest::new(Arc::new(array)).run();
4748 }
4749
4750 #[test]
4751 #[cfg_attr(miri, ignore)] fn arrow_writer_decimal128_dictionary() {
4753 let integers = vec![12345, 56789, 34567];
4754
4755 let keys = UInt8Array::from(vec![Some(0), None, Some(1), Some(2), Some(1)]);
4756
4757 let values = Decimal128Array::from(integers.clone())
4758 .with_precision_and_scale(5, 2)
4759 .unwrap();
4760
4761 let array = DictionaryArray::new(keys, Arc::new(values));
4762 RoundTripTest::new(Arc::new(array.clone())).run();
4763
4764 let values = Decimal128Array::from(integers)
4765 .with_precision_and_scale(12, 2)
4766 .unwrap();
4767
4768 let array = array.with_values(Arc::new(values));
4769 RoundTripTest::new(Arc::new(array)).run();
4770 }
4771
4772 #[test]
4773 #[cfg_attr(miri, ignore)] fn arrow_writer_decimal256_dictionary() {
4775 let integers = vec![
4776 i256::from_i128(12345),
4777 i256::from_i128(56789),
4778 i256::from_i128(34567),
4779 ];
4780
4781 let keys = UInt8Array::from(vec![Some(0), None, Some(1), Some(2), Some(1)]);
4782
4783 let values = Decimal256Array::from(integers.clone())
4784 .with_precision_and_scale(5, 2)
4785 .unwrap();
4786
4787 let array = DictionaryArray::new(keys, Arc::new(values));
4788 RoundTripTest::new(Arc::new(array.clone())).run();
4789
4790 let values = Decimal256Array::from(integers)
4791 .with_precision_and_scale(12, 2)
4792 .unwrap();
4793
4794 let array = array.with_values(Arc::new(values));
4795 RoundTripTest::new(Arc::new(array)).run();
4796 }
4797
4798 #[test]
4799 #[cfg_attr(miri, ignore)] fn arrow_writer_string_dictionary_unsigned_index() {
4801 #[expect(deprecated)]
4803 let schema = Arc::new(Schema::new(vec![Field::new_dict(
4804 "dictionary",
4805 DataType::Dictionary(Box::new(DataType::UInt8), Box::new(DataType::Utf8)),
4806 true,
4807 42,
4808 true,
4809 )]));
4810
4811 let d: UInt8DictionaryArray = [Some("alpha"), None, Some("beta"), Some("alpha")]
4813 .iter()
4814 .copied()
4815 .collect();
4816
4817 RoundTripTest::new(Arc::new(d)).with_schema(schema).run();
4818 }
4819
4820 #[test]
4821 #[cfg_attr(miri, ignore)] fn u32_min_max() {
4823 let src = [
4825 u32::MIN,
4826 1,
4827 (i32::MAX as u32) - 1,
4828 i32::MAX as u32,
4829 (i32::MAX as u32) + 1,
4830 u32::MAX - 1,
4831 u32::MAX,
4832 ];
4833 let values = Arc::new(UInt32Array::from_iter_values(src.iter().copied()));
4834 let files = RoundTripTest::new(values).with_nullable(false).run();
4835
4836 for file in files {
4837 let reader = SerializedFileReader::new(file).unwrap();
4839 let metadata = reader.metadata();
4840
4841 let mut row_offset = 0;
4842 for row_group in metadata.row_groups() {
4843 assert_eq!(row_group.num_columns(), 1);
4844 let column = row_group.column(0);
4845
4846 let num_values = column.num_values() as usize;
4847 let src_slice = &src[row_offset..row_offset + num_values];
4848 row_offset += column.num_values() as usize;
4849
4850 let stats = column.statistics().unwrap();
4851 if let Statistics::Int32(stats) = stats {
4852 assert_eq!(
4853 *stats.min_opt().unwrap() as u32,
4854 *src_slice.iter().min().unwrap()
4855 );
4856 assert_eq!(
4857 *stats.max_opt().unwrap() as u32,
4858 *src_slice.iter().max().unwrap()
4859 );
4860 } else {
4861 panic!("Statistics::Int32 missing")
4862 }
4863 }
4864 }
4865 }
4866
4867 #[test]
4868 #[cfg_attr(miri, ignore)] fn u64_min_max() {
4870 let src = [
4872 u64::MIN,
4873 1,
4874 (i64::MAX as u64) - 1,
4875 i64::MAX as u64,
4876 (i64::MAX as u64) + 1,
4877 u64::MAX - 1,
4878 u64::MAX,
4879 ];
4880 let values = Arc::new(UInt64Array::from_iter_values(src.iter().copied()));
4881 let files = RoundTripTest::new(values).with_nullable(false).run();
4882
4883 for file in files {
4884 let reader = SerializedFileReader::new(file).unwrap();
4886 let metadata = reader.metadata();
4887
4888 let mut row_offset = 0;
4889 for row_group in metadata.row_groups() {
4890 assert_eq!(row_group.num_columns(), 1);
4891 let column = row_group.column(0);
4892
4893 let num_values = column.num_values() as usize;
4894 let src_slice = &src[row_offset..row_offset + num_values];
4895 row_offset += column.num_values() as usize;
4896
4897 let stats = column.statistics().unwrap();
4898 if let Statistics::Int64(stats) = stats {
4899 assert_eq!(
4900 *stats.min_opt().unwrap() as u64,
4901 *src_slice.iter().min().unwrap()
4902 );
4903 assert_eq!(
4904 *stats.max_opt().unwrap() as u64,
4905 *src_slice.iter().max().unwrap()
4906 );
4907 } else {
4908 panic!("Statistics::Int64 missing")
4909 }
4910 }
4911 }
4912 }
4913
4914 #[test]
4915 #[cfg_attr(miri, ignore)] fn statistics_null_counts_only_nulls() {
4917 let values = Arc::new(UInt64Array::from(vec![None, None]));
4919 let files = RoundTripTest::new(values).run();
4920
4921 for file in files {
4922 let reader = SerializedFileReader::new(file).unwrap();
4924 let metadata = reader.metadata();
4925 assert_eq!(metadata.num_row_groups(), 1);
4926 let row_group = metadata.row_group(0);
4927 assert_eq!(row_group.num_columns(), 1);
4928 let column = row_group.column(0);
4929 let stats = column.statistics().unwrap();
4930 assert_eq!(stats.null_count_opt(), Some(2));
4931 }
4932 }
4933
4934 #[test]
4935 #[cfg_attr(miri, ignore)] fn test_list_of_struct_roundtrip() {
4937 let int_field = Field::new("a", DataType::Int32, true);
4939 let int_field2 = Field::new("b", DataType::Int32, true);
4940
4941 let int_builder = Int32Builder::with_capacity(10);
4942 let int_builder2 = Int32Builder::with_capacity(10);
4943
4944 let struct_builder = StructBuilder::new(
4945 vec![int_field, int_field2],
4946 vec![Box::new(int_builder), Box::new(int_builder2)],
4947 );
4948 let mut list_builder = ListBuilder::new(struct_builder);
4949
4950 let values = list_builder.values();
4955 values
4956 .field_builder::<Int32Builder>(0)
4957 .unwrap()
4958 .append_value(1);
4959 values
4960 .field_builder::<Int32Builder>(1)
4961 .unwrap()
4962 .append_value(2);
4963 values.append(true);
4964 list_builder.append(true);
4965
4966 list_builder.append(true);
4968
4969 list_builder.append(false);
4971
4972 let values = list_builder.values();
4974 values
4975 .field_builder::<Int32Builder>(0)
4976 .unwrap()
4977 .append_null();
4978 values
4979 .field_builder::<Int32Builder>(1)
4980 .unwrap()
4981 .append_null();
4982 values.append(false);
4983 values
4984 .field_builder::<Int32Builder>(0)
4985 .unwrap()
4986 .append_null();
4987 values
4988 .field_builder::<Int32Builder>(1)
4989 .unwrap()
4990 .append_null();
4991 values.append(false);
4992 list_builder.append(true);
4993
4994 let values = list_builder.values();
4996 values
4997 .field_builder::<Int32Builder>(0)
4998 .unwrap()
4999 .append_null();
5000 values
5001 .field_builder::<Int32Builder>(1)
5002 .unwrap()
5003 .append_value(3);
5004 values.append(true);
5005 list_builder.append(true);
5006
5007 let values = list_builder.values();
5009 values
5010 .field_builder::<Int32Builder>(0)
5011 .unwrap()
5012 .append_value(2);
5013 values
5014 .field_builder::<Int32Builder>(1)
5015 .unwrap()
5016 .append_null();
5017 values.append(true);
5018 list_builder.append(true);
5019
5020 let array = Arc::new(list_builder.finish());
5021
5022 RoundTripTest::new(array).run();
5023 }
5024
5025 fn row_group_sizes(metadata: &ParquetMetaData) -> Vec<i64> {
5026 metadata.row_groups().iter().map(|x| x.num_rows()).collect()
5027 }
5028
5029 #[test]
5030 fn test_aggregates_records() {
5031 let arrays = [
5032 Int32Array::from((0..100).collect::<Vec<_>>()),
5033 Int32Array::from((0..50).collect::<Vec<_>>()),
5034 Int32Array::from((200..500).collect::<Vec<_>>()),
5035 ];
5036
5037 let schema = Arc::new(Schema::new(vec![Field::new(
5038 "int",
5039 ArrowDataType::Int32,
5040 false,
5041 )]));
5042
5043 let file = tempfile::tempfile().unwrap();
5044
5045 let props = WriterProperties::builder()
5046 .set_max_row_group_row_count(Some(200))
5047 .build();
5048
5049 let mut writer =
5050 ArrowWriter::try_new(file.try_clone().unwrap(), schema.clone(), Some(props)).unwrap();
5051
5052 for array in arrays {
5053 let batch = RecordBatch::try_new(schema.clone(), vec![Arc::new(array)]).unwrap();
5054 writer.write(&batch).unwrap();
5055 }
5056
5057 writer.close().unwrap();
5058
5059 let builder = ParquetRecordBatchReaderBuilder::try_new(file).unwrap();
5060 assert_eq!(&row_group_sizes(builder.metadata()), &[200, 200, 50]);
5061
5062 let batches = builder
5063 .with_batch_size(100)
5064 .build()
5065 .unwrap()
5066 .collect::<ArrowResult<Vec<_>>>()
5067 .unwrap();
5068
5069 assert_eq!(batches.len(), 5);
5070 assert!(batches.iter().all(|x| x.num_columns() == 1));
5071
5072 let batch_sizes: Vec<_> = batches.iter().map(|x| x.num_rows()).collect();
5073
5074 assert_eq!(&batch_sizes, &[100, 100, 100, 100, 50]);
5075
5076 let values: Vec<_> = batches
5077 .iter()
5078 .flat_map(|x| {
5079 x.column(0)
5080 .as_any()
5081 .downcast_ref::<Int32Array>()
5082 .unwrap()
5083 .values()
5084 .iter()
5085 .copied()
5086 })
5087 .collect();
5088
5089 let expected_values: Vec<_> = [0..100, 0..50, 200..500].into_iter().flatten().collect();
5090 assert_eq!(&values, &expected_values)
5091 }
5092
5093 #[test]
5094 fn complex_aggregate() {
5095 let field_a = Arc::new(Field::new("leaf_a", DataType::Int32, false));
5097 let field_b = Arc::new(Field::new("leaf_b", DataType::Int32, true));
5098 let struct_a = Arc::new(Field::new(
5099 "struct_a",
5100 DataType::Struct(vec![field_a.clone(), field_b.clone()].into()),
5101 true,
5102 ));
5103
5104 let list_a = Arc::new(Field::new("list", DataType::List(struct_a), true));
5105 let struct_b = Arc::new(Field::new(
5106 "struct_b",
5107 DataType::Struct(vec![list_a.clone()].into()),
5108 false,
5109 ));
5110
5111 let schema = Arc::new(Schema::new(vec![struct_b]));
5112
5113 let field_a_array = Int32Array::from(vec![1, 2, 3, 4, 5, 6]);
5115 let field_b_array =
5116 Int32Array::from_iter(vec![Some(1), None, Some(2), None, None, Some(6)]);
5117
5118 let struct_a_array = StructArray::from(vec![
5119 (field_a.clone(), Arc::new(field_a_array) as ArrayRef),
5120 (field_b.clone(), Arc::new(field_b_array) as ArrayRef),
5121 ]);
5122
5123 let list_data = ArrayDataBuilder::new(list_a.data_type().clone())
5124 .len(5)
5125 .add_buffer(Buffer::from_iter(vec![
5126 0_i32, 1_i32, 1_i32, 3_i32, 3_i32, 5_i32,
5127 ]))
5128 .null_bit_buffer(Some(Buffer::from_iter(vec![
5129 true, false, true, false, true,
5130 ])))
5131 .child_data(vec![struct_a_array.into_data()])
5132 .build()
5133 .unwrap();
5134
5135 let list_a_array = Arc::new(ListArray::from(list_data)) as ArrayRef;
5136 let struct_b_array = StructArray::from(vec![(list_a.clone(), list_a_array)]);
5137
5138 let batch1 =
5139 RecordBatch::try_from_iter(vec![("struct_b", Arc::new(struct_b_array) as ArrayRef)])
5140 .unwrap();
5141
5142 let field_a_array = Int32Array::from(vec![6, 7, 8, 9, 10]);
5143 let field_b_array = Int32Array::from_iter(vec![None, None, None, Some(1), None]);
5144
5145 let struct_a_array = StructArray::from(vec![
5146 (field_a, Arc::new(field_a_array) as ArrayRef),
5147 (field_b, Arc::new(field_b_array) as ArrayRef),
5148 ]);
5149
5150 let list_data = ArrayDataBuilder::new(list_a.data_type().clone())
5151 .len(2)
5152 .add_buffer(Buffer::from_iter(vec![0_i32, 4_i32, 5_i32]))
5153 .child_data(vec![struct_a_array.into_data()])
5154 .build()
5155 .unwrap();
5156
5157 let list_a_array = Arc::new(ListArray::from(list_data)) as ArrayRef;
5158 let struct_b_array = StructArray::from(vec![(list_a, list_a_array)]);
5159
5160 let batch2 =
5161 RecordBatch::try_from_iter(vec![("struct_b", Arc::new(struct_b_array) as ArrayRef)])
5162 .unwrap();
5163
5164 let batches = &[batch1, batch2];
5165
5166 let expected = r"
5169 +-------------------------------------------------------------------------------------------------------+
5170 | struct_b |
5171 +-------------------------------------------------------------------------------------------------------+
5172 | {list: [{leaf_a: 1, leaf_b: 1}]} |
5173 | {list: } |
5174 | {list: [{leaf_a: 2, leaf_b: }, {leaf_a: 3, leaf_b: 2}]} |
5175 | {list: } |
5176 | {list: [{leaf_a: 4, leaf_b: }, {leaf_a: 5, leaf_b: }]} |
5177 | {list: [{leaf_a: 6, leaf_b: }, {leaf_a: 7, leaf_b: }, {leaf_a: 8, leaf_b: }, {leaf_a: 9, leaf_b: 1}]} |
5178 | {list: [{leaf_a: 10, leaf_b: }]} |
5179 +-------------------------------------------------------------------------------------------------------+
5180 ".trim().split('\n').map(|x| x.trim()).collect::<Vec<_>>().join("\n");
5181
5182 let actual = pretty_format_batches(batches).unwrap().to_string();
5183 assert_eq!(actual, expected);
5184
5185 let file = tempfile::tempfile().unwrap();
5187 let props = WriterProperties::builder()
5188 .set_max_row_group_row_count(Some(6))
5189 .build();
5190
5191 let mut writer =
5192 ArrowWriter::try_new(file.try_clone().unwrap(), schema, Some(props)).unwrap();
5193
5194 for batch in batches {
5195 writer.write(batch).unwrap();
5196 }
5197 writer.close().unwrap();
5198
5199 let builder = ParquetRecordBatchReaderBuilder::try_new(file).unwrap();
5204 assert_eq!(&row_group_sizes(builder.metadata()), &[6, 1]);
5205
5206 let batches = builder
5207 .with_batch_size(2)
5208 .build()
5209 .unwrap()
5210 .collect::<ArrowResult<Vec<_>>>()
5211 .unwrap();
5212
5213 assert_eq!(batches.len(), 4);
5214 let batch_counts: Vec<_> = batches.iter().map(|x| x.num_rows()).collect();
5215 assert_eq!(&batch_counts, &[2, 2, 2, 1]);
5216
5217 let actual = pretty_format_batches(&batches).unwrap().to_string();
5218 assert_eq!(actual, expected);
5219 }
5220
5221 #[test]
5222 fn test_arrow_writer_metadata() {
5223 let batch_schema = Schema::new(vec![Field::new("int32", DataType::Int32, false)]);
5224 let file_schema = batch_schema.clone().with_metadata([("foo", "bar")]);
5225
5226 let batch = RecordBatch::try_new(
5227 Arc::new(batch_schema),
5228 vec![Arc::new(Int32Array::from(vec![1, 2, 3, 4])) as _],
5229 )
5230 .unwrap();
5231
5232 let mut buf = Vec::with_capacity(1024);
5233 let mut writer = ArrowWriter::try_new(&mut buf, Arc::new(file_schema), None).unwrap();
5234 writer.write(&batch).unwrap();
5235 writer.close().unwrap();
5236 }
5237
5238 #[test]
5239 fn test_arrow_writer_nullable() {
5240 let batch_schema = Schema::new(vec![Field::new("int32", DataType::Int32, false)]);
5241 let file_schema = Schema::new(vec![Field::new("int32", DataType::Int32, true)]);
5242 let file_schema = Arc::new(file_schema);
5243
5244 let batch = RecordBatch::try_new(
5245 Arc::new(batch_schema),
5246 vec![Arc::new(Int32Array::from(vec![1, 2, 3, 4])) as _],
5247 )
5248 .unwrap();
5249
5250 let mut buf = Vec::with_capacity(1024);
5251 let mut writer = ArrowWriter::try_new(&mut buf, file_schema.clone(), None).unwrap();
5252 writer.write(&batch).unwrap();
5253 writer.close().unwrap();
5254
5255 let mut read = ParquetRecordBatchReader::try_new(Bytes::from(buf), 1024).unwrap();
5256 let back = read.next().unwrap().unwrap();
5257 assert_eq!(back.schema(), file_schema);
5258 assert_ne!(back.schema(), batch.schema());
5259 assert_eq!(back.column(0).as_ref(), batch.column(0).as_ref());
5260 }
5261
5262 #[test]
5263 fn in_progress_accounting() {
5264 let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
5266
5267 let a = Int32Array::from(vec![1, 2, 3, 4, 5]);
5269
5270 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(a)]).unwrap();
5272
5273 let mut writer = ArrowWriter::try_new(vec![], batch.schema(), None).unwrap();
5274
5275 assert_eq!(writer.in_progress_size(), 0);
5277 assert_eq!(writer.in_progress_rows(), 0);
5278 assert_eq!(writer.memory_size(), 0);
5279 assert_eq!(writer.bytes_written(), 4); writer.write(&batch).unwrap();
5281
5282 let initial_size = writer.in_progress_size();
5284 assert!(initial_size > 0);
5285 assert_eq!(writer.in_progress_rows(), 5);
5286 let initial_memory = writer.memory_size();
5287 assert!(initial_memory > 0);
5288 assert!(
5290 initial_size <= initial_memory,
5291 "{initial_size} <= {initial_memory}"
5292 );
5293
5294 writer.write(&batch).unwrap();
5296 assert!(writer.in_progress_size() > initial_size);
5297 assert_eq!(writer.in_progress_rows(), 10);
5298 assert!(writer.memory_size() > initial_memory);
5299 assert!(
5300 writer.in_progress_size() <= writer.memory_size(),
5301 "in_progress_size {} <= memory_size {}",
5302 writer.in_progress_size(),
5303 writer.memory_size()
5304 );
5305
5306 let pre_flush_bytes_written = writer.bytes_written();
5308 writer.flush().unwrap();
5309 assert_eq!(writer.in_progress_size(), 0);
5310 assert_eq!(writer.memory_size(), 0);
5311 assert!(writer.bytes_written() > pre_flush_bytes_written);
5312
5313 writer.close().unwrap();
5314 }
5315
5316 #[test]
5317 fn test_writer_all_null() {
5318 let a = Int32Array::from(vec![1, 2, 3, 4, 5]);
5319 let b = Int32Array::new(vec![0; 5].into(), Some(NullBuffer::new_null(5)));
5320 let batch = RecordBatch::try_from_iter(vec![
5321 ("a", Arc::new(a) as ArrayRef),
5322 ("b", Arc::new(b) as ArrayRef),
5323 ])
5324 .unwrap();
5325
5326 let mut buf = Vec::with_capacity(1024);
5327 let mut writer = ArrowWriter::try_new(&mut buf, batch.schema(), None).unwrap();
5328 writer.write(&batch).unwrap();
5329 writer.close().unwrap();
5330
5331 let bytes = Bytes::from(buf);
5332 let options = ReadOptionsBuilder::new().with_page_index().build();
5333 let reader = SerializedFileReader::new_with_options(bytes, options).unwrap();
5334 let index = reader.metadata().page_index().unwrap();
5335
5336 assert_eq!(index.num_data_pages(0, 0), Some(1)); assert_eq!(index.num_data_pages(0, 1), Some(1)); }
5339
5340 #[test]
5341 fn test_disabled_statistics_with_page() {
5342 let file_schema = Schema::new(vec![
5343 Field::new("a", DataType::Utf8, true),
5344 Field::new("b", DataType::Utf8, true),
5345 ]);
5346 let file_schema = Arc::new(file_schema);
5347
5348 let batch = RecordBatch::try_new(
5349 file_schema.clone(),
5350 vec![
5351 Arc::new(StringArray::from(vec!["a", "b", "c", "d"])) as _,
5352 Arc::new(StringArray::from(vec!["w", "x", "y", "z"])) as _,
5353 ],
5354 )
5355 .unwrap();
5356
5357 let props = WriterProperties::builder()
5358 .set_statistics_enabled(EnabledStatistics::None)
5359 .set_column_statistics_enabled("a".into(), EnabledStatistics::Page)
5360 .build();
5361
5362 let mut buf = Vec::with_capacity(1024);
5363 let mut writer = ArrowWriter::try_new(&mut buf, file_schema.clone(), Some(props)).unwrap();
5364 writer.write(&batch).unwrap();
5365
5366 let metadata = writer.close().unwrap();
5367 assert_eq!(metadata.num_row_groups(), 1);
5368 let row_group = metadata.row_group(0);
5369 assert_eq!(row_group.num_columns(), 2);
5370 assert!(row_group.column(0).offset_index_offset().is_some());
5372 assert!(row_group.column(0).column_index_offset().is_some());
5373 assert!(row_group.column(1).offset_index_offset().is_some());
5375 assert!(row_group.column(1).column_index_offset().is_none());
5376
5377 let options = ReadOptionsBuilder::new().with_page_index().build();
5378 let reader = SerializedFileReader::new_with_options(Bytes::from(buf), options).unwrap();
5379
5380 let row_group = reader.get_row_group(0).unwrap();
5381 let a_col = row_group.metadata().column(0);
5382 let b_col = row_group.metadata().column(1);
5383
5384 if let Statistics::ByteArray(byte_array_stats) = a_col.statistics().unwrap() {
5386 let min = byte_array_stats.min_opt().unwrap();
5387 let max = byte_array_stats.max_opt().unwrap();
5388
5389 assert_eq!(min.as_bytes(), b"a");
5390 assert_eq!(max.as_bytes(), b"d");
5391 } else {
5392 panic!("expecting Statistics::ByteArray");
5393 }
5394
5395 assert!(b_col.statistics().is_none());
5397
5398 let page_index = reader.metadata().page_index().unwrap();
5399
5400 let a_idx = page_index.column_index(0, 0);
5401 assert!(
5402 matches!(a_idx, Some(ColumnIndexMetaData::BYTE_ARRAY(_))),
5403 "{a_idx:?}"
5404 );
5405 let b_idx = page_index.column_index(0, 1);
5406 assert!(b_idx.is_none(), "{b_idx:?}");
5407 }
5408
5409 #[test]
5410 fn test_disabled_statistics_with_chunk() {
5411 let file_schema = Schema::new(vec![
5412 Field::new("a", DataType::Utf8, true),
5413 Field::new("b", DataType::Utf8, true),
5414 ]);
5415 let file_schema = Arc::new(file_schema);
5416
5417 let batch = RecordBatch::try_new(
5418 file_schema.clone(),
5419 vec![
5420 Arc::new(StringArray::from(vec!["a", "b", "c", "d"])) as _,
5421 Arc::new(StringArray::from(vec!["w", "x", "y", "z"])) as _,
5422 ],
5423 )
5424 .unwrap();
5425
5426 let props = WriterProperties::builder()
5427 .set_statistics_enabled(EnabledStatistics::None)
5428 .set_column_statistics_enabled("a".into(), EnabledStatistics::Chunk)
5429 .build();
5430
5431 let mut buf = Vec::with_capacity(1024);
5432 let mut writer = ArrowWriter::try_new(&mut buf, file_schema.clone(), Some(props)).unwrap();
5433 writer.write(&batch).unwrap();
5434
5435 let metadata = writer.close().unwrap();
5436 assert_eq!(metadata.num_row_groups(), 1);
5437 let row_group = metadata.row_group(0);
5438 assert_eq!(row_group.num_columns(), 2);
5439 assert!(row_group.column(0).offset_index_offset().is_some());
5441 assert!(row_group.column(0).column_index_offset().is_none());
5442 assert!(row_group.column(1).offset_index_offset().is_some());
5444 assert!(row_group.column(1).column_index_offset().is_none());
5445
5446 let options = ReadOptionsBuilder::new().with_page_index().build();
5447 let reader = SerializedFileReader::new_with_options(Bytes::from(buf), options).unwrap();
5448
5449 let row_group = reader.get_row_group(0).unwrap();
5450 let a_col = row_group.metadata().column(0);
5451 let b_col = row_group.metadata().column(1);
5452
5453 if let Statistics::ByteArray(byte_array_stats) = a_col.statistics().unwrap() {
5455 let min = byte_array_stats.min_opt().unwrap();
5456 let max = byte_array_stats.max_opt().unwrap();
5457
5458 assert_eq!(min.as_bytes(), b"a");
5459 assert_eq!(max.as_bytes(), b"d");
5460 } else {
5461 panic!("expecting Statistics::ByteArray");
5462 }
5463
5464 assert!(b_col.statistics().is_none());
5466
5467 let page_index = reader.metadata().page_index().unwrap();
5468
5469 let a_idx = page_index.column_index(0, 0);
5470 assert!(a_idx.is_none(), "{a_idx:?}");
5471 let b_idx = page_index.column_index(0, 1);
5472 assert!(b_idx.is_none(), "{b_idx:?}");
5473 }
5474
5475 #[test]
5476 fn test_arrow_writer_skip_metadata() {
5477 let batch_schema = Schema::new(vec![Field::new("int32", DataType::Int32, false)]);
5478 let file_schema = Arc::new(batch_schema.clone());
5479
5480 let batch = RecordBatch::try_new(
5481 Arc::new(batch_schema),
5482 vec![Arc::new(Int32Array::from(vec![1, 2, 3, 4])) as _],
5483 )
5484 .unwrap();
5485 let skip_options = ArrowWriterOptions::new().with_skip_arrow_metadata(true);
5486
5487 let mut buf = Vec::with_capacity(1024);
5488 let mut writer =
5489 ArrowWriter::try_new_with_options(&mut buf, file_schema.clone(), skip_options).unwrap();
5490 writer.write(&batch).unwrap();
5491 writer.close().unwrap();
5492
5493 let bytes = Bytes::from(buf);
5494 let reader_builder = ParquetRecordBatchReaderBuilder::try_new(bytes).unwrap();
5495 assert_eq!(file_schema, *reader_builder.schema());
5496 if let Some(key_value_metadata) = reader_builder
5497 .metadata()
5498 .file_metadata()
5499 .key_value_metadata()
5500 {
5501 assert!(
5502 !key_value_metadata
5503 .iter()
5504 .any(|kv| kv.key.as_str() == ARROW_SCHEMA_META_KEY)
5505 );
5506 }
5507 }
5508
5509 #[test]
5510 fn test_arrow_writer_skip_path_in_schema() {
5511 let batch_schema = Schema::new(vec![Field::new("int32", DataType::Int32, false)]);
5512 let file_schema = Arc::new(batch_schema.clone());
5513
5514 let batch = RecordBatch::try_new(
5515 Arc::new(batch_schema),
5516 vec![Arc::new(Int32Array::from(vec![1, 2, 3, 4])) as _],
5517 )
5518 .unwrap();
5519
5520 let skip_options = ArrowWriterOptions::new();
5522
5523 let mut buf = Vec::with_capacity(1024);
5524 let mut writer =
5525 ArrowWriter::try_new_with_options(&mut buf, file_schema.clone(), skip_options).unwrap();
5526 writer.write(&batch).unwrap();
5527 writer.close().unwrap();
5528
5529 let skip_options = ArrowWriterOptions::new().with_properties(
5531 WriterProperties::builder()
5532 .set_write_path_in_schema(false)
5533 .build(),
5534 );
5535
5536 let mut buf2 = Vec::with_capacity(1024);
5537 let mut writer =
5538 ArrowWriter::try_new_with_options(&mut buf2, file_schema.clone(), skip_options)
5539 .unwrap();
5540 writer.write(&batch).unwrap();
5541 writer.close().unwrap();
5542
5543 assert!(buf.len() > buf2.len());
5545 }
5546
5547 #[test]
5548 fn mismatched_schemas() {
5549 let batch_schema = Schema::new(vec![Field::new("count", DataType::Int32, false)]);
5550 let file_schema = Arc::new(Schema::new(vec![Field::new(
5551 "temperature",
5552 DataType::Float64,
5553 false,
5554 )]));
5555
5556 let batch = RecordBatch::try_new(
5557 Arc::new(batch_schema),
5558 vec![Arc::new(Int32Array::from(vec![1, 2, 3, 4])) as _],
5559 )
5560 .unwrap();
5561
5562 let mut buf = Vec::with_capacity(1024);
5563 let mut writer = ArrowWriter::try_new(&mut buf, file_schema.clone(), None).unwrap();
5564
5565 let err = writer.write(&batch).unwrap_err().to_string();
5566 assert_eq!(
5567 err,
5568 "Arrow: Incompatible type. Field 'temperature' has type Float64, array has type Int32"
5569 );
5570 }
5571
5572 #[test]
5573 fn test_roundtrip_empty_schema() {
5575 let empty_batch = RecordBatch::try_new_with_options(
5577 Arc::new(Schema::empty()),
5578 vec![],
5579 &RecordBatchOptions::default().with_row_count(Some(0)),
5580 )
5581 .unwrap();
5582
5583 let mut parquet_bytes: Vec<u8> = Vec::new();
5585 let mut writer =
5586 ArrowWriter::try_new(&mut parquet_bytes, empty_batch.schema(), None).unwrap();
5587 writer.write(&empty_batch).unwrap();
5588 writer.close().unwrap();
5589
5590 let bytes = Bytes::from(parquet_bytes);
5592 let reader = ParquetRecordBatchReaderBuilder::try_new(bytes).unwrap();
5593 assert_eq!(reader.schema(), &empty_batch.schema());
5594 let batches: Vec<_> = reader
5595 .build()
5596 .unwrap()
5597 .collect::<ArrowResult<Vec<_>>>()
5598 .unwrap();
5599 assert_eq!(batches.len(), 0);
5600 }
5601
5602 #[test]
5603 fn test_page_stats_not_written_by_default() {
5604 let string_field = Field::new("a", DataType::Utf8, false);
5605 let schema = Schema::new(vec![string_field]);
5606 let raw_string_values = vec!["Blart Versenwald III"];
5607 let string_values = StringArray::from(raw_string_values.clone());
5608 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(string_values)]).unwrap();
5609
5610 let props = WriterProperties::builder()
5611 .set_statistics_enabled(EnabledStatistics::Page)
5612 .set_dictionary_enabled(false)
5613 .set_encoding(Encoding::PLAIN)
5614 .set_compression(crate::basic::Compression::UNCOMPRESSED)
5615 .build();
5616
5617 let file = roundtrip_opts(&batch, props);
5618
5619 let first_page = &file[4..];
5624 let mut prot = ThriftSliceInputProtocol::new(first_page);
5625 let hdr = PageHeader::read_thrift(&mut prot).unwrap();
5626 let stats = hdr.data_page_header.unwrap().statistics;
5627
5628 assert!(stats.is_none());
5629 }
5630
5631 #[test]
5632 fn test_page_stats_when_enabled() {
5633 let string_field = Field::new("a", DataType::Utf8, false);
5634 let schema = Schema::new(vec![string_field]);
5635 let raw_string_values = vec!["Blart Versenwald III", "Andrew Lamb"];
5636 let string_values = StringArray::from(raw_string_values.clone());
5637 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(string_values)]).unwrap();
5638
5639 let props = WriterProperties::builder()
5640 .set_statistics_enabled(EnabledStatistics::Page)
5641 .set_dictionary_enabled(false)
5642 .set_encoding(Encoding::PLAIN)
5643 .set_write_page_header_statistics(true)
5644 .set_compression(crate::basic::Compression::UNCOMPRESSED)
5645 .build();
5646
5647 let file = roundtrip_opts(&batch, props);
5648
5649 let first_page = &file[4..];
5654 let mut prot = ThriftSliceInputProtocol::new(first_page);
5655 let hdr = PageHeader::read_thrift(&mut prot).unwrap();
5656 let stats = hdr.data_page_header.unwrap().statistics;
5657
5658 let stats = stats.unwrap();
5659 assert!(stats.is_max_value_exact.unwrap());
5661 assert!(stats.is_min_value_exact.unwrap());
5662 assert_eq!(stats.max_value.unwrap(), b"Blart Versenwald III");
5663 assert_eq!(stats.min_value.unwrap(), b"Andrew Lamb");
5664 }
5665
5666 #[test]
5667 fn test_page_stats_truncation() {
5668 let string_field = Field::new("a", DataType::Utf8, false);
5669 let binary_field = Field::new("b", DataType::Binary, false);
5670 let schema = Schema::new(vec![string_field, binary_field]);
5671
5672 let raw_string_values = vec!["Blart Versenwald III"];
5673 let raw_binary_values = [b"Blart Versenwald III".to_vec()];
5674 let raw_binary_value_refs = raw_binary_values
5675 .iter()
5676 .map(|x| x.as_slice())
5677 .collect::<Vec<_>>();
5678
5679 let string_values = StringArray::from(raw_string_values.clone());
5680 let binary_values = BinaryArray::from(raw_binary_value_refs);
5681 let batch = RecordBatch::try_new(
5682 Arc::new(schema),
5683 vec![Arc::new(string_values), Arc::new(binary_values)],
5684 )
5685 .unwrap();
5686
5687 let props = WriterProperties::builder()
5688 .set_statistics_truncate_length(Some(2))
5689 .set_dictionary_enabled(false)
5690 .set_encoding(Encoding::PLAIN)
5691 .set_write_page_header_statistics(true)
5692 .set_compression(crate::basic::Compression::UNCOMPRESSED)
5693 .build();
5694
5695 let file = roundtrip_opts(&batch, props);
5696
5697 let first_page = &file[4..];
5702 let mut prot = ThriftSliceInputProtocol::new(first_page);
5703 let hdr = PageHeader::read_thrift(&mut prot).unwrap();
5704 let stats = hdr.data_page_header.unwrap().statistics;
5705 assert!(stats.is_some());
5706 let stats = stats.unwrap();
5707 assert!(!stats.is_max_value_exact.unwrap());
5709 assert!(!stats.is_min_value_exact.unwrap());
5710 assert_eq!(stats.max_value.unwrap(), b"Bm");
5711 assert_eq!(stats.min_value.unwrap(), b"Bl");
5712
5713 let second_page = &prot.as_slice()[hdr.compressed_page_size as usize..];
5715 let mut prot = ThriftSliceInputProtocol::new(second_page);
5716 let hdr = PageHeader::read_thrift(&mut prot).unwrap();
5717 let stats = hdr.data_page_header.unwrap().statistics;
5718 assert!(stats.is_some());
5719 let stats = stats.unwrap();
5720 assert!(!stats.is_max_value_exact.unwrap());
5722 assert!(!stats.is_min_value_exact.unwrap());
5723 assert_eq!(stats.max_value.unwrap(), b"Bm");
5724 assert_eq!(stats.min_value.unwrap(), b"Bl");
5725 }
5726
5727 #[test]
5728 fn test_page_encoding_statistics_roundtrip() {
5729 let batch_schema = Schema::new(vec![Field::new(
5730 "int32",
5731 arrow_schema::DataType::Int32,
5732 false,
5733 )]);
5734
5735 let batch = RecordBatch::try_new(
5736 Arc::new(batch_schema.clone()),
5737 vec![Arc::new(Int32Array::from(vec![1, 2, 3, 4])) as _],
5738 )
5739 .unwrap();
5740
5741 let mut file: File = tempfile::tempfile().unwrap();
5742 let mut writer = ArrowWriter::try_new(&mut file, Arc::new(batch_schema), None).unwrap();
5743 writer.write(&batch).unwrap();
5744 let file_metadata = writer.close().unwrap();
5745
5746 assert_eq!(file_metadata.num_row_groups(), 1);
5747 assert_eq!(file_metadata.row_group(0).num_columns(), 1);
5748 assert!(
5749 file_metadata
5750 .row_group(0)
5751 .column(0)
5752 .page_encoding_stats()
5753 .is_some()
5754 );
5755 let chunk_page_stats = file_metadata
5756 .row_group(0)
5757 .column(0)
5758 .page_encoding_stats()
5759 .unwrap();
5760
5761 let options = ReadOptionsBuilder::new()
5763 .with_page_index()
5764 .with_encoding_stats_as_mask(false)
5765 .build();
5766 let reader = SerializedFileReader::new_with_options(file, options).unwrap();
5767
5768 let rowgroup = reader.get_row_group(0).expect("row group missing");
5769 assert_eq!(rowgroup.num_columns(), 1);
5770 let column = rowgroup.metadata().column(0);
5771 assert!(column.page_encoding_stats().is_some());
5772 let file_page_stats = column.page_encoding_stats().unwrap();
5773 assert_eq!(chunk_page_stats, file_page_stats);
5774 }
5775
5776 #[test]
5777 #[cfg_attr(miri, ignore)] fn test_different_dict_page_size_limit() {
5779 let array = Arc::new(Int64Array::from_iter(0..1024 * 1024));
5780 let schema = Arc::new(Schema::new(vec![
5781 Field::new("col0", arrow_schema::DataType::Int64, false),
5782 Field::new("col1", arrow_schema::DataType::Int64, false),
5783 ]));
5784 let batch =
5785 arrow_array::RecordBatch::try_new(schema.clone(), vec![array.clone(), array]).unwrap();
5786
5787 let props = WriterProperties::builder()
5788 .set_dictionary_page_size_limit(1024 * 1024)
5789 .set_column_dictionary_page_size_limit(ColumnPath::from("col1"), 1024 * 1024 * 4)
5790 .build();
5791 let mut writer = ArrowWriter::try_new(Vec::new(), schema, Some(props)).unwrap();
5792 writer.write(&batch).unwrap();
5793 let data = Bytes::from(writer.into_inner().unwrap());
5794
5795 let mut metadata = ParquetMetaDataReader::new();
5796 metadata.try_parse(&data).unwrap();
5797 let metadata = metadata.finish().unwrap();
5798 let col0_meta = metadata.row_group(0).column(0);
5799 let col1_meta = metadata.row_group(0).column(1);
5800
5801 let get_dict_page_size = move |meta: &ColumnChunkMetaData| {
5802 let mut reader =
5803 SerializedPageReader::new(Arc::new(data.clone()), meta, 0, None).unwrap();
5804 let page = reader.get_next_page().unwrap().unwrap();
5805 match page {
5806 Page::DictionaryPage { buf, .. } => buf.len(),
5807 _ => panic!("expected DictionaryPage"),
5808 }
5809 };
5810
5811 assert_eq!(get_dict_page_size(col0_meta), 1024 * 1024);
5812 assert_eq!(get_dict_page_size(col1_meta), 1024 * 1024 * 4);
5813 }
5814
5815 #[test]
5816 #[cfg_attr(miri, ignore)] fn test_arrow_writer_granular_mode_roundtrip() {
5818 let small = "tiny".to_string();
5827 let big = "x".repeat(64 * 1024);
5828 let strings: Vec<String> = (0..256)
5829 .map(|i| {
5830 if i % 16 == 0 {
5831 big.clone()
5832 } else {
5833 small.clone()
5834 }
5835 })
5836 .collect();
5837
5838 let schema = Arc::new(Schema::new(vec![Field::new(
5839 "col",
5840 ArrowDataType::Utf8,
5841 false,
5842 )]));
5843 let batch = RecordBatch::try_new(
5844 schema.clone(),
5845 vec![Arc::new(StringArray::from(strings.clone())) as _],
5846 )
5847 .unwrap();
5848
5849 let props = WriterProperties::builder()
5850 .set_dictionary_enabled(false)
5851 .set_data_page_size_limit(16 * 1024)
5852 .build();
5853 let mut writer = ArrowWriter::try_new(Vec::new(), schema, Some(props)).unwrap();
5854 writer.write(&batch).unwrap();
5855 let data = Bytes::from(writer.into_inner().unwrap());
5856
5857 let mut reader = ParquetRecordBatchReader::try_new(data, 1024).unwrap();
5858 let read = reader.next().unwrap().unwrap();
5859 assert!(reader.next().is_none(), "expected one batch");
5860 let col = read
5861 .column(0)
5862 .as_any()
5863 .downcast_ref::<StringArray>()
5864 .unwrap();
5865 assert_eq!(col.len(), strings.len());
5866 for (i, expected) in strings.iter().enumerate() {
5867 assert_eq!(
5868 col.value(i),
5869 expected.as_str(),
5870 "value mismatch at index {i}"
5871 );
5872 }
5873 }
5874
5875 #[test]
5876 fn test_arrow_writer_all_null_string_column() {
5877 let num_rows = 1024;
5882 let schema = Arc::new(Schema::new(vec![Field::new(
5883 "col",
5884 ArrowDataType::Utf8,
5885 true,
5886 )]));
5887 let nulls: Vec<Option<&str>> = vec![None; num_rows];
5888 let batch = RecordBatch::try_new(
5889 schema.clone(),
5890 vec![Arc::new(StringArray::from(nulls)) as _],
5891 )
5892 .unwrap();
5893
5894 let props = WriterProperties::builder()
5895 .set_dictionary_enabled(false)
5896 .set_data_page_size_limit(16 * 1024)
5897 .build();
5898 let mut writer = ArrowWriter::try_new(Vec::new(), schema, Some(props)).unwrap();
5899 writer.write(&batch).unwrap();
5900 let data = Bytes::from(writer.into_inner().unwrap());
5901
5902 let mut metadata = ParquetMetaDataReader::new();
5905 metadata.try_parse(&data).unwrap();
5906 let metadata = metadata.finish().unwrap();
5907 let row_group = metadata.row_group(0);
5908 let col_meta = row_group.column(0);
5909 assert_eq!(row_group.num_rows() as usize, num_rows);
5910 if let Some(stats) = col_meta.statistics() {
5913 assert_eq!(
5914 stats.null_count_opt().unwrap_or(0) as usize,
5915 num_rows,
5916 "expected all-null column to report null_count = num_rows"
5917 );
5918 }
5919
5920 let mut reader =
5921 SerializedPageReader::new(Arc::new(data.clone()), col_meta, num_rows, None).unwrap();
5922 let mut total_values = 0u32;
5923 while let Some(page) = reader.get_next_page().unwrap() {
5924 if matches!(page, Page::DataPage { .. } | Page::DataPageV2 { .. }) {
5925 total_values += page.num_values();
5926 }
5927 }
5928 assert_eq!(
5929 total_values as usize, num_rows,
5930 "expected every level position to be represented in some page"
5931 );
5932 }
5933
5934 struct WriteBatchesShape {
5935 num_batches: usize,
5936 rows_per_batch: usize,
5937 row_size: usize,
5938 }
5939
5940 fn write_batches(
5942 WriteBatchesShape {
5943 num_batches,
5944 rows_per_batch,
5945 row_size,
5946 }: WriteBatchesShape,
5947 props: WriterProperties,
5948 ) -> ParquetRecordBatchReaderBuilder<File> {
5949 let schema = Arc::new(Schema::new(vec![Field::new(
5950 "str",
5951 ArrowDataType::Utf8,
5952 false,
5953 )]));
5954 let file = tempfile::tempfile().unwrap();
5955 let mut writer =
5956 ArrowWriter::try_new(file.try_clone().unwrap(), schema.clone(), Some(props)).unwrap();
5957
5958 for batch_idx in 0..num_batches {
5959 let strings: Vec<String> = (0..rows_per_batch)
5960 .map(|i| format!("{:0>width$}", batch_idx * 10 + i, width = row_size))
5961 .collect();
5962 let array = StringArray::from(strings);
5963 let batch = RecordBatch::try_new(schema.clone(), vec![Arc::new(array)]).unwrap();
5964 writer.write(&batch).unwrap();
5965 }
5966 writer.close().unwrap();
5967 ParquetRecordBatchReaderBuilder::try_new(file).unwrap()
5968 }
5969
5970 #[test]
5971 fn test_row_group_limit_none_writes_single_row_group() {
5973 let props = WriterProperties::builder()
5974 .set_max_row_group_row_count(None)
5975 .set_max_row_group_bytes(None)
5976 .build();
5977
5978 let builder = write_batches(
5979 WriteBatchesShape {
5980 num_batches: 1,
5981 rows_per_batch: 1000,
5982 row_size: 4,
5983 },
5984 props,
5985 );
5986
5987 assert_eq!(
5988 &row_group_sizes(builder.metadata()),
5989 &[1000],
5990 "With no limits, all rows should be in a single row group"
5991 );
5992 }
5993
5994 #[test]
5995 fn test_row_group_limit_rows_only() {
5997 let props = WriterProperties::builder()
5998 .set_max_row_group_row_count(Some(300))
5999 .set_max_row_group_bytes(None)
6000 .build();
6001
6002 let builder = write_batches(
6003 WriteBatchesShape {
6004 num_batches: 1,
6005 rows_per_batch: 1000,
6006 row_size: 4,
6007 },
6008 props,
6009 );
6010
6011 assert_eq!(
6012 &row_group_sizes(builder.metadata()),
6013 &[300, 300, 300, 100],
6014 "Row groups should be split by row count"
6015 );
6016 }
6017
6018 #[test]
6019 #[cfg_attr(miri, ignore)] fn test_row_group_limit_rows_only_many_splits() {
6023 let props = WriterProperties::builder()
6024 .set_max_row_group_row_count(Some(1))
6025 .set_max_row_group_bytes(None)
6026 .build();
6027
6028 let rows = 50_000;
6029 let builder = write_batches(
6030 WriteBatchesShape {
6031 num_batches: 1,
6032 rows_per_batch: rows,
6033 row_size: 4,
6034 },
6035 props,
6036 );
6037
6038 let sizes = row_group_sizes(builder.metadata());
6039 assert_eq!(sizes.len(), rows, "Every row should get its own row group");
6040 assert_eq!(
6041 sizes.iter().sum::<i64>(),
6042 rows as i64,
6043 "Total rows should be preserved"
6044 );
6045 }
6046
6047 #[test]
6048 fn test_row_group_limit_bytes_only() {
6050 let props = WriterProperties::builder()
6051 .set_max_row_group_row_count(None)
6052 .set_max_row_group_bytes(Some(3500))
6054 .build();
6055
6056 let builder = write_batches(
6057 WriteBatchesShape {
6058 num_batches: 10,
6059 rows_per_batch: 10,
6060 row_size: 100,
6061 },
6062 props,
6063 );
6064
6065 let sizes = row_group_sizes(builder.metadata());
6066
6067 assert!(
6068 sizes.len() > 1,
6069 "Should have multiple row groups due to byte limit, got {sizes:?}",
6070 );
6071
6072 let total_rows: i64 = sizes.iter().sum();
6073 assert_eq!(total_rows, 100, "Total rows should be preserved");
6074 }
6075
6076 #[test]
6077 fn test_row_group_limit_bytes_flushes_when_current_group_already_too_large() {
6079 let schema = Arc::new(Schema::new(vec![Field::new(
6080 "str",
6081 ArrowDataType::Utf8,
6082 false,
6083 )]));
6084 let file = tempfile::tempfile().unwrap();
6085
6086 let props = WriterProperties::builder()
6088 .set_max_row_group_row_count(None)
6089 .set_max_row_group_bytes(None)
6090 .build();
6091 let mut writer =
6092 ArrowWriter::try_new(file.try_clone().unwrap(), schema.clone(), Some(props)).unwrap();
6093
6094 let first_array = StringArray::from(
6095 (0..10)
6096 .map(|i| format!("{i:0>100}"))
6097 .collect::<Vec<String>>(),
6098 );
6099 let first_batch =
6100 RecordBatch::try_new(schema.clone(), vec![Arc::new(first_array)]).unwrap();
6101 writer.write(&first_batch).unwrap();
6102 assert_eq!(writer.in_progress_rows(), 10);
6103
6104 writer.max_row_group_bytes = Some(1);
6107
6108 let second_array = StringArray::from(vec!["x".to_string()]);
6109 let second_batch =
6110 RecordBatch::try_new(schema.clone(), vec![Arc::new(second_array)]).unwrap();
6111 writer.write(&second_batch).unwrap();
6112 writer.close().unwrap();
6113 let builder = ParquetRecordBatchReaderBuilder::try_new(file).unwrap();
6114
6115 assert_eq!(
6116 &row_group_sizes(builder.metadata()),
6117 &[10, 1],
6118 "The second write should flush an oversized in-progress row group first",
6119 );
6120 }
6121
6122 #[test]
6123 fn test_row_group_limit_both_row_wins_single_batch() {
6125 let props = WriterProperties::builder()
6126 .set_max_row_group_row_count(Some(200)) .set_max_row_group_bytes(Some(1024 * 1024)) .build();
6129
6130 let builder = write_batches(
6131 WriteBatchesShape {
6132 num_batches: 1,
6133 row_size: 4,
6134 rows_per_batch: 1000,
6135 },
6136 props,
6137 );
6138
6139 assert_eq!(
6140 &row_group_sizes(builder.metadata()),
6141 &[200, 200, 200, 200, 200],
6142 "Row limit should trigger before byte limit"
6143 );
6144 }
6145
6146 #[test]
6147 fn test_row_group_limit_both_row_wins_multiple_batches() {
6149 let props = WriterProperties::builder()
6150 .set_max_row_group_row_count(Some(5)) .set_max_row_group_bytes(Some(9999)) .build();
6153
6154 let builder = write_batches(
6155 WriteBatchesShape {
6156 num_batches: 10,
6157 rows_per_batch: 10,
6158 row_size: 100,
6159 },
6160 props,
6161 );
6162
6163 assert_eq!(
6164 &row_group_sizes(builder.metadata()),
6165 &[5; 20],
6166 "Row limit should trigger before byte limit"
6167 );
6168 }
6169
6170 #[test]
6171 fn test_row_group_limit_both_bytes_wins() {
6173 let props = WriterProperties::builder()
6174 .set_max_row_group_row_count(Some(1000)) .set_max_row_group_bytes(Some(3500)) .build();
6177
6178 let builder = write_batches(
6179 WriteBatchesShape {
6180 num_batches: 10,
6181 rows_per_batch: 10,
6182 row_size: 100,
6183 },
6184 props,
6185 );
6186
6187 let sizes = row_group_sizes(builder.metadata());
6188
6189 assert!(
6190 sizes.len() > 1,
6191 "Byte limit should trigger before row limit, got {sizes:?}",
6192 );
6193
6194 assert!(
6195 sizes.iter().all(|&s| s < 1000),
6196 "No row group should hit the row limit"
6197 );
6198
6199 let total_rows: i64 = sizes.iter().sum();
6200 assert_eq!(total_rows, 100, "Total rows should be preserved");
6201 }
6202
6203 #[test]
6204 fn test_row_group_limit_both_apply_to_same_batch() {
6207 let props = WriterProperties::builder()
6208 .set_max_row_group_row_count(Some(15))
6209 .set_max_row_group_bytes(Some(1500))
6210 .build();
6211
6212 let builder = write_batches(
6213 WriteBatchesShape {
6214 num_batches: 2,
6215 rows_per_batch: 10,
6216 row_size: 100,
6217 },
6218 props,
6219 );
6220
6221 assert_eq!(
6222 &row_group_sizes(builder.metadata()),
6223 &[14, 6],
6224 "Byte limit should still apply to a batch the row limit already split"
6225 );
6226 }
6227
6228 #[test]
6229 fn arrow_column_chunk_close_mut_drops_column_index() {
6230 use crate::arrow::ArrowSchemaConverter;
6231 use crate::file::writer::SerializedFileWriter;
6232
6233 let schema = Arc::new(Schema::new(vec![Field::new("i", DataType::Int32, false)]));
6234 let props = Arc::new(
6235 WriterProperties::builder()
6236 .set_statistics_enabled(EnabledStatistics::Page)
6237 .build(),
6238 );
6239 let parquet_schema = ArrowSchemaConverter::new()
6240 .with_coerce_types(props.coerce_types())
6241 .convert(&schema)
6242 .unwrap();
6243
6244 let mut buf = Vec::with_capacity(1024);
6245 let mut writer =
6246 SerializedFileWriter::new(&mut buf, parquet_schema.root_schema_ptr(), props.clone())
6247 .unwrap();
6248
6249 let factory = ArrowRowGroupWriterFactory::new(&writer, Arc::clone(&schema));
6250 let mut col_writers = factory.create_column_writers(0).unwrap();
6251 let arr: ArrayRef = Arc::new(Int32Array::from_iter_values(0..64));
6252 for leaves in compute_leaves(schema.field(0), &arr).unwrap() {
6253 col_writers[0].write(&leaves).unwrap();
6254 }
6255 let mut chunk = col_writers.pop().unwrap().close().unwrap();
6256
6257 assert!(
6259 chunk.close().column_index.is_some(),
6260 "EnabledStatistics::Page should produce a column_index"
6261 );
6262
6263 chunk.close_mut().column_index = None;
6265 assert!(chunk.close().column_index.is_none());
6266
6267 let mut rg = writer.next_row_group().unwrap();
6268 chunk.append_to_row_group(&mut rg).unwrap();
6269 rg.close().unwrap();
6270 let file_meta = writer.close().unwrap();
6271
6272 let cc = file_meta.row_group(0).column(0);
6275 assert!(cc.column_index_range().is_none());
6276 }
6277
6278 fn write_column_to_bytes(array: ArrayRef) -> Bytes {
6280 let schema = Arc::new(Schema::new(vec![Field::new(
6281 "col",
6282 array.data_type().clone(),
6283 true,
6284 )]));
6285 let buf = get_bytes_after_close(
6286 schema.clone(),
6287 &RecordBatch::try_new(schema, vec![array]).unwrap(),
6288 );
6289 Bytes::from(buf)
6290 }
6291
6292 fn read_column_with_schema(bytes: Bytes, schema: SchemaRef) -> ArrayRef {
6296 let opts = crate::arrow::arrow_reader::ArrowReaderOptions::new().with_schema(schema);
6297 ParquetRecordBatchReaderBuilder::try_new_with_options(bytes, opts)
6298 .unwrap()
6299 .build()
6300 .unwrap()
6301 .next()
6302 .unwrap()
6303 .unwrap()
6304 .column(0)
6305 .clone()
6306 }
6307
6308 fn ree_write_read_roundtrip(ree: ArrayRef, flat: ArrayRef) {
6309 let flat_schema = Arc::new(Schema::new(vec![Field::new(
6310 "col",
6311 flat.data_type().clone(),
6312 true,
6313 )]));
6314 let ree_bytes = write_column_to_bytes(ree);
6315 let flat_bytes = write_column_to_bytes(flat.clone());
6316 assert_eq!(
6317 ree_bytes, flat_bytes,
6318 "REE and flat bytes should be identical"
6319 );
6320
6321 let decoded_ree = read_column_with_schema(ree_bytes, flat_schema.clone());
6322 let decoded_flat = read_column_with_schema(flat_bytes, flat_schema);
6323
6324 assert_eq!(decoded_ree.as_ref(), flat.as_ref());
6325 assert_eq!(decoded_ree.as_ref(), decoded_flat.as_ref());
6326 }
6327
6328 #[test]
6329 fn ree_string() {
6330 let ree: ArrayRef = Arc::new(
6331 [Some("a"), Some("a"), None, Some("b"), Some("b")]
6332 .into_iter()
6333 .collect::<Int32RunArray>(),
6334 );
6335 let flat: ArrayRef = Arc::new(StringArray::from(vec![
6336 Some("a"),
6337 Some("a"),
6338 None,
6339 Some("b"),
6340 Some("b"),
6341 ]));
6342 ree_write_read_roundtrip(ree, flat);
6343 }
6344
6345 #[test]
6346 fn ree_int32() {
6347 let mut b = PrimitiveRunBuilder::<Int32Type, Int32Type>::new();
6348 for v in [Some(1), Some(1), None, Some(2), Some(2)] {
6349 b.append_option(v);
6350 }
6351 let ree: ArrayRef = Arc::new(b.finish());
6352 let flat: ArrayRef = Arc::new(Int32Array::from(vec![
6353 Some(1),
6354 Some(1),
6355 None,
6356 Some(2),
6357 Some(2),
6358 ]));
6359 ree_write_read_roundtrip(ree, flat);
6360 }
6361
6362 #[test]
6363 fn ree_bool() {
6364 let ree: ArrayRef = Arc::new(
6366 RunArray::try_new(
6367 &Int32Array::from(vec![3, 5, 7]),
6368 &BooleanArray::from(vec![Some(true), None, Some(false)]),
6369 )
6370 .unwrap(),
6371 );
6372 let flat: ArrayRef = Arc::new(BooleanArray::from(vec![
6373 Some(true),
6374 Some(true),
6375 Some(true),
6376 None,
6377 None,
6378 Some(false),
6379 Some(false),
6380 ]));
6381 ree_write_read_roundtrip(ree, flat);
6382 }
6383
6384 #[test]
6385 fn ree_fixed_size_binary() {
6386 let mk = |vals: &[Option<&[u8]>]| -> FixedSizeBinaryArray {
6387 let mut b = FixedSizeBinaryBuilder::new(2);
6388 for v in vals {
6389 match v {
6390 Some(x) => b.append_value(x).unwrap(),
6391 None => b.append_null(),
6392 }
6393 }
6394 b.finish()
6395 };
6396 let ree: ArrayRef = Arc::new(
6398 RunArray::try_new(
6399 &Int32Array::from(vec![2, 4, 6]),
6400 &mk(&[Some(b"aa"), None, Some(b"bb")]),
6401 )
6402 .unwrap(),
6403 );
6404 let flat: ArrayRef = Arc::new(mk(&[
6405 Some(b"aa"),
6406 Some(b"aa"),
6407 None,
6408 None,
6409 Some(b"bb"),
6410 Some(b"bb"),
6411 ]));
6412 ree_write_read_roundtrip(ree, flat);
6413 }
6414
6415 #[test]
6416 fn ree_single_run() {
6417 let ree: ArrayRef = Arc::new(["x", "x", "x"].into_iter().collect::<Int32RunArray>());
6418 let flat: ArrayRef = Arc::new(StringArray::from(vec!["x", "x", "x"]));
6419 ree_write_read_roundtrip(ree, flat);
6420 }
6421
6422 #[test]
6423 fn ree_float32() {
6424 let ree: ArrayRef = Arc::new(
6426 RunArray::try_new(
6427 &Int32Array::from(vec![2, 4, 5]),
6428 &Float32Array::from(vec![Some(1.0_f32), None, Some(2.5_f32)]),
6429 )
6430 .unwrap(),
6431 );
6432 let flat: ArrayRef = Arc::new(Float32Array::from(vec![
6433 Some(1.0_f32),
6434 Some(1.0_f32),
6435 None,
6436 None,
6437 Some(2.5_f32),
6438 ]));
6439 ree_write_read_roundtrip(ree, flat);
6440 }
6441
6442 #[test]
6443 fn ree_sliced() {
6444 let full: ArrayRef = Arc::new(
6449 RunArray::try_new(
6450 &Int32Array::from(vec![3, 5, 7]),
6451 &StringArray::from(vec!["a", "b", "c"]),
6452 )
6453 .unwrap(),
6454 );
6455 let sliced = full.slice(2, 5);
6456 let flat: ArrayRef = Arc::new(StringArray::from(vec!["a", "b", "b", "c", "c"]));
6457 ree_write_read_roundtrip(sliced, flat);
6458 }
6459
6460 #[test]
6461 #[cfg_attr(miri, ignore)] fn test_number_distinct_values_exact_count() {
6463 let cardinality = 50u32;
6466 let array: ArrayRef = Arc::new(Int32Array::from_iter((0..100_000u32).map(|i| {
6467 if i % 7 == 0 {
6468 None
6469 } else {
6470 Some((i % cardinality) as i32)
6471 }
6472 })));
6473 let schema = Arc::new(Schema::new(vec![Field::new("x", DataType::Int32, true)]));
6474 let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
6475
6476 let props = WriterProperties::builder()
6477 .set_write_row_group_number_distinct_values(true)
6478 .build();
6479 let mut buf = Vec::new();
6480 let mut writer = ArrowWriter::try_new(&mut buf, batch.schema(), Some(props)).unwrap();
6481 writer.write(&batch).unwrap();
6482 let metadata = writer.close().unwrap();
6483
6484 let count = metadata
6485 .row_group(0)
6486 .column(0)
6487 .statistics()
6488 .and_then(|s| s.distinct_count_opt())
6489 .expect("distinct_count should be set");
6490 assert_eq!(count, cardinality as u64);
6492 }
6493
6494 #[test]
6495 fn test_number_distinct_values_view_types() {
6496 let cardinality = 5u32;
6499 let distinct_strings = ["alpha", "beta", "gamma", "delta", "epsilon"];
6500
6501 let string_view_col: ArrayRef = Arc::new(StringViewArray::from_iter((0..30u32).map(|i| {
6502 if i % 4 == 0 {
6503 None
6504 } else {
6505 Some(distinct_strings[(i % cardinality) as usize])
6506 }
6507 })));
6508
6509 let schema = Arc::new(Schema::new(vec![Field::new(
6510 "string_view_col",
6511 DataType::Utf8View,
6512 true,
6513 )]));
6514 let batch = RecordBatch::try_new(schema, vec![string_view_col]).unwrap();
6515
6516 let props = WriterProperties::builder()
6517 .set_write_row_group_number_distinct_values(true)
6518 .build();
6519 let mut parquet_bytes = Vec::new();
6520 let mut writer =
6521 ArrowWriter::try_new(&mut parquet_bytes, batch.schema(), Some(props)).unwrap();
6522 writer.write(&batch).unwrap();
6523 let metadata = writer.close().unwrap();
6524
6525 let distinct_count = metadata
6526 .row_group(0)
6527 .column(0)
6528 .statistics()
6529 .and_then(|s| s.distinct_count_opt())
6530 .expect("distinct_count should be set for Utf8View column");
6531 assert_eq!(distinct_count, cardinality as u64);
6532 }
6533
6534 #[test]
6535 fn test_number_distinct_values_not_written_by_default() {
6536 let array: ArrayRef = Arc::new(Int32Array::from_iter_values(0..100));
6537 let schema = Arc::new(Schema::new(vec![Field::new("x", DataType::Int32, false)]));
6538 let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
6539
6540 let mut buf = Vec::new();
6541 let mut writer = ArrowWriter::try_new(&mut buf, batch.schema(), None).unwrap();
6542 writer.write(&batch).unwrap();
6543 let metadata = writer.close().unwrap();
6544
6545 let count = metadata
6546 .row_group(0)
6547 .column(0)
6548 .statistics()
6549 .and_then(|s| s.distinct_count_opt());
6550 assert!(count.is_none());
6551 }
6552
6553 #[test]
6554 fn test_dictionary_ndv_single_batch() {
6555 let keys = Int32Array::from(vec![0, 1, 2, 0, 1, 2, 0, 1, 2]);
6559 let values: ArrayRef = Arc::new(StringArray::from(vec!["cat", "dog", "bird"]));
6560 let dict: ArrayRef = Arc::new(DictionaryArray::<Int32Type>::try_new(keys, values).unwrap());
6561
6562 let schema = Arc::new(Schema::new(vec![Field::new(
6563 "x",
6564 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
6565 false,
6566 )]));
6567 let batch = RecordBatch::try_new(schema, vec![dict]).unwrap();
6568
6569 let props = WriterProperties::builder()
6570 .set_write_row_group_number_distinct_values(true)
6571 .build();
6572 let mut buf = Vec::new();
6573 let mut writer = ArrowWriter::try_new(&mut buf, batch.schema(), Some(props)).unwrap();
6574 writer.write(&batch).unwrap();
6575 let metadata = writer.close().unwrap();
6576
6577 let count = metadata
6578 .row_group(0)
6579 .column(0)
6580 .statistics()
6581 .and_then(|s| s.distinct_count_opt())
6582 .expect("distinct_count should be set");
6583 assert_eq!(count, 3);
6584 }
6585
6586 #[test]
6587 fn test_dictionary_ndv_excludes_unreferenced_values() {
6588 let keys = Int32Array::from(vec![0, 1, 0, 1]);
6591 let values: ArrayRef = Arc::new(StringArray::from(vec!["cat", "dog", "unreferenced"]));
6592 let dict: ArrayRef = Arc::new(DictionaryArray::<Int32Type>::try_new(keys, values).unwrap());
6593
6594 let schema = Arc::new(Schema::new(vec![Field::new(
6595 "x",
6596 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
6597 false,
6598 )]));
6599 let batch = RecordBatch::try_new(schema, vec![dict]).unwrap();
6600
6601 let props = WriterProperties::builder()
6602 .set_write_row_group_number_distinct_values(true)
6603 .build();
6604 let mut buf = Vec::new();
6605 let mut writer = ArrowWriter::try_new(&mut buf, batch.schema(), Some(props)).unwrap();
6606 writer.write(&batch).unwrap();
6607 let metadata = writer.close().unwrap();
6608
6609 let count = metadata
6610 .row_group(0)
6611 .column(0)
6612 .statistics()
6613 .and_then(|s| s.distinct_count_opt())
6614 .expect("distinct_count should be set");
6615 assert_eq!(
6616 count, 2,
6617 "unreferenced dictionary values must not count toward NDV"
6618 );
6619 }
6620
6621 #[test]
6622 fn test_dictionary_ndv_across_batches_regression() {
6623 let make_dict_batch = |a: &str, b: &str| -> RecordBatch {
6625 let keys = Int32Array::from(vec![0, 1, 0, 1]);
6626 let values: ArrayRef = Arc::new(StringArray::from(vec![a, b]));
6627 let dict: ArrayRef =
6628 Arc::new(DictionaryArray::<Int32Type>::try_new(keys, values).unwrap());
6629 let schema = Arc::new(Schema::new(vec![Field::new(
6630 "x",
6631 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
6632 false,
6633 )]));
6634 RecordBatch::try_new(schema, vec![dict]).unwrap()
6635 };
6636
6637 let batch1 = make_dict_batch("cat", "dog");
6640 let batch2 = make_dict_batch("fish", "cat");
6641
6642 let props = WriterProperties::builder()
6643 .set_write_row_group_number_distinct_values(true)
6644 .build();
6645 let mut buf = Vec::new();
6646 let mut writer = ArrowWriter::try_new(&mut buf, batch1.schema(), Some(props)).unwrap();
6647 writer.write(&batch1).unwrap();
6648 writer.write(&batch2).unwrap();
6649 let metadata = writer.close().unwrap();
6650
6651 let count = metadata
6652 .row_group(0)
6653 .column(0)
6654 .statistics()
6655 .and_then(|s| s.distinct_count_opt())
6656 .expect("distinct_count should be set");
6657 assert_eq!(
6658 count, 3,
6659 "NDV should count distinct values, not distinct key indices"
6660 );
6661 }
6662
6663 #[test]
6664 fn ree_struct_with_ree_child() {
6665 let run_ends = Int32Array::from(vec![2i32, 3, 5]);
6668
6669 let col_a: ArrayRef = Arc::new(
6670 RunArray::try_new(
6671 &run_ends,
6672 &StringArray::from(vec![Some("foo"), None, Some("bar")]),
6673 )
6674 .unwrap(),
6675 );
6676 let col_b: ArrayRef = Arc::new(
6677 RunArray::try_new(&run_ends, &Int32Array::from(vec![Some(1), None, Some(2)])).unwrap(),
6678 );
6679
6680 let struct_array: ArrayRef = Arc::new(StructArray::new(
6681 Fields::from(vec![
6682 Field::new("a", col_a.data_type().clone(), true),
6683 Field::new("b", col_b.data_type().clone(), true),
6684 ]),
6685 vec![col_a, col_b],
6686 None,
6687 ));
6688
6689 let schema = Arc::new(Schema::new(vec![Field::new(
6690 "row",
6691 struct_array.data_type().clone(),
6692 true,
6693 )]));
6694 let batch = RecordBatch::try_new(schema.clone(), vec![struct_array]).unwrap();
6695
6696 let mut buf = Vec::new();
6697 let mut writer = ArrowWriter::try_new(&mut buf, schema, None).unwrap();
6698 writer.write(&batch).unwrap();
6699 let metadata = writer.close().unwrap();
6700
6701 let parquet_schema = metadata.file_metadata().schema_descr();
6702 assert_eq!(parquet_schema.num_columns(), 2);
6703 assert_eq!(
6704 parquet_schema.column(0).physical_type(),
6705 crate::basic::Type::BYTE_ARRAY
6706 );
6707 assert_eq!(parquet_schema.column(0).path().string(), "row.a");
6708 assert_eq!(
6709 parquet_schema.column(1).physical_type(),
6710 crate::basic::Type::INT32
6711 );
6712 assert_eq!(parquet_schema.column(1).path().string(), "row.b");
6713 }
6714}