1use std::io::Write;
19
20use arrow::{
21 array::RecordBatch,
22 datatypes::{DataType as ArrowDataType, SchemaRef},
23};
24use prost::Message;
25use snafu::{ensure, ResultExt};
26
27use crate::{
28 compression::{CompressionType, Compressor},
29 error::{IoSnafu, Result, UnexpectedSnafu},
30 memory::EstimateMemory,
31 proto,
32 writer::stripe::{StripeInformation, StripeWriter},
33};
34
35pub struct ArrowWriterBuilder<W> {
38 writer: W,
39 schema: SchemaRef,
40 batch_size: usize,
41 stripe_byte_size: usize,
42 compression: Option<CompressionType>,
43 compression_block_size: Option<usize>,
44}
45
46impl<W: Write> ArrowWriterBuilder<W> {
47 pub fn new(writer: W, schema: SchemaRef) -> Self {
50 Self {
51 writer,
52 schema,
53 batch_size: 1024,
54 stripe_byte_size: 64 * 1024 * 1024,
56 compression: None,
57 compression_block_size: None,
58 }
59 }
60
61 pub fn with_batch_size(mut self, batch_size: usize) -> Self {
64 self.batch_size = batch_size;
65 self
66 }
67
68 pub fn with_stripe_byte_size(mut self, stripe_byte_size: usize) -> Self {
70 self.stripe_byte_size = stripe_byte_size;
71 self
72 }
73
74 pub fn with_compression(mut self, compression: CompressionType) -> Self {
76 self.compression = Some(compression);
77 self
78 }
79
80 pub fn with_compression_block_size(mut self, block_size: usize) -> Self {
82 self.compression_block_size = Some(block_size);
83 self
84 }
85
86 pub fn try_build(mut self) -> Result<ArrowWriter<W>> {
89 let timezone = self
90 .schema
91 .fields()
92 .iter()
93 .any(|field| matches!(field.data_type(), ArrowDataType::Timestamp(_, None)))
94 .then(|| "UTC".to_owned());
95 let compressor = Compressor::new(self.compression, self.compression_block_size)?;
96 self.writer.write_all(b"ORC").context(IoSnafu)?;
98 let writer = StripeWriter::new(self.writer, &self.schema, compressor, timezone);
99 Ok(ArrowWriter {
100 writer,
101 schema: self.schema,
102 batch_size: self.batch_size,
103 stripe_byte_size: self.stripe_byte_size,
104 written_stripes: vec![],
105 total_bytes_written: 3,
107 })
108 }
109}
110
111pub struct ArrowWriter<W> {
115 writer: StripeWriter<W>,
116 schema: SchemaRef,
117 batch_size: usize,
118 stripe_byte_size: usize,
119 written_stripes: Vec<StripeInformation>,
120 total_bytes_written: u64,
122}
123
124impl<W: Write> ArrowWriter<W> {
125 pub fn write(&mut self, batch: &RecordBatch) -> Result<()> {
128 ensure!(
129 batch.schema() == self.schema,
130 UnexpectedSnafu {
131 msg: "RecordBatch doesn't match expected schema"
132 }
133 );
134
135 for offset in (0..batch.num_rows()).step_by(self.batch_size) {
136 let length = self.batch_size.min(batch.num_rows() - offset);
137 let batch = batch.slice(offset, length);
138 self.writer.encode_batch(&batch)?;
139
140 if self.writer.estimate_memory_size() > self.stripe_byte_size {
143 self.flush_stripe()?;
144 }
145 }
146 Ok(())
147 }
148
149 pub fn flush_stripe(&mut self) -> Result<()> {
152 let info = self.writer.finish_stripe(self.total_bytes_written)?;
153 self.total_bytes_written += info.total_byte_size();
154 self.written_stripes.push(info);
155 Ok(())
156 }
157
158 pub fn close(mut self) -> Result<()> {
161 if self.writer.row_count > 0 {
163 self.flush_stripe()?;
164 }
165 let footer = serialize_footer(&self.written_stripes, &self.schema).encode_to_vec();
166 let metadata = proto::Metadata::default().encode_to_vec();
167
168 let (mut writer, mut compressor) = self.writer.finish();
169 let metadata = compressor.compress(&metadata)?;
170 let footer = compressor.compress(&footer)?;
171
172 let postscript = serialize_postscript(
173 footer.len() as u64,
174 metadata.len() as u64,
175 compressor.compression(),
176 compressor.block_size().map(|size| size as u64),
177 )
178 .encode_to_vec();
179 let postscript_len = postscript.len() as u8;
180
181 writer.write_all(&metadata).context(IoSnafu)?;
182 writer.write_all(&footer).context(IoSnafu)?;
183 writer.write_all(&postscript).context(IoSnafu)?;
184 writer.write_all(&[postscript_len]).context(IoSnafu)?;
186
187 Ok(())
189 }
190}
191
192fn serialize_schema(schema: &SchemaRef) -> Vec<proto::Type> {
193 let mut types = vec![];
194
195 let field_names = schema
196 .fields()
197 .iter()
198 .map(|f| f.name().to_owned())
199 .collect();
200 let subtypes = (1..(schema.fields().len() as u32 + 1)).collect();
202 let root_type = proto::Type {
203 kind: Some(proto::r#type::Kind::Struct.into()),
204 subtypes,
205 field_names,
206 maximum_length: None,
207 precision: None,
208 scale: None,
209 attributes: vec![],
210 };
211 types.push(root_type);
212 for field in schema.fields() {
213 let t = match field.data_type() {
214 ArrowDataType::Float32 => proto::Type {
215 kind: Some(proto::r#type::Kind::Float.into()),
216 ..Default::default()
217 },
218 ArrowDataType::Float64 => proto::Type {
219 kind: Some(proto::r#type::Kind::Double.into()),
220 ..Default::default()
221 },
222 ArrowDataType::Int8 => proto::Type {
223 kind: Some(proto::r#type::Kind::Byte.into()),
224 ..Default::default()
225 },
226 ArrowDataType::Int16 => proto::Type {
227 kind: Some(proto::r#type::Kind::Short.into()),
228 ..Default::default()
229 },
230 ArrowDataType::Int32 => proto::Type {
231 kind: Some(proto::r#type::Kind::Int.into()),
232 ..Default::default()
233 },
234 ArrowDataType::Int64 => proto::Type {
235 kind: Some(proto::r#type::Kind::Long.into()),
236 ..Default::default()
237 },
238 ArrowDataType::Date32 => proto::Type {
239 kind: Some(proto::r#type::Kind::Date.into()),
240 ..Default::default()
241 },
242 ArrowDataType::Timestamp(_, None) => proto::Type {
243 kind: Some(proto::r#type::Kind::Timestamp.into()),
244 ..Default::default()
245 },
246 ArrowDataType::Timestamp(_, Some(timezone)) if timezone.as_ref() == "UTC" => {
247 proto::Type {
248 kind: Some(proto::r#type::Kind::TimestampInstant.into()),
249 ..Default::default()
250 }
251 }
252 ArrowDataType::Utf8 | ArrowDataType::LargeUtf8 => proto::Type {
253 kind: Some(proto::r#type::Kind::String.into()),
254 ..Default::default()
255 },
256 ArrowDataType::Binary | ArrowDataType::LargeBinary => proto::Type {
257 kind: Some(proto::r#type::Kind::Binary.into()),
258 ..Default::default()
259 },
260 ArrowDataType::Boolean => proto::Type {
261 kind: Some(proto::r#type::Kind::Boolean.into()),
262 ..Default::default()
263 },
264 _ => unimplemented!("unsupported datatype"),
266 };
267 types.push(t);
268 }
269 types
270}
271
272fn serialize_footer(stripes: &[StripeInformation], schema: &SchemaRef) -> proto::Footer {
273 let body_length = stripes
274 .iter()
275 .map(|s| s.index_length + s.data_length + s.footer_length)
276 .sum::<u64>();
277 let number_of_rows = stripes.iter().map(|s| s.row_count as u64).sum::<u64>();
278 let stripes = stripes.iter().map(From::from).collect();
279 let types = serialize_schema(schema);
280 proto::Footer {
281 header_length: Some(3),
282 content_length: Some(body_length + 3),
283 stripes,
284 types,
285 metadata: vec![],
286 number_of_rows: Some(number_of_rows),
287 statistics: vec![],
288 row_index_stride: None,
289 writer: Some(u32::MAX),
290 encryption: None,
291 calendar: None,
292 software_version: None,
293 }
294}
295
296fn serialize_postscript(
297 footer_length: u64,
298 metadata_length: u64,
299 compression: Option<CompressionType>,
300 compression_block_size: Option<u64>,
301) -> proto::PostScript {
302 proto::PostScript {
303 footer_length: Some(footer_length),
304 compression: Some(
305 compression
306 .map(CompressionType::to_proto)
307 .unwrap_or(proto::CompressionKind::None)
308 .into(),
309 ),
310 compression_block_size,
311 version: vec![0, 12],
312 metadata_length: Some(metadata_length),
313 writer_version: Some(u32::MAX), stripe_statistics_length: None,
315 magic: Some("ORC".to_string()),
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use std::{io::Read, sync::Arc};
322
323 use arrow::{
324 array::{
325 Array, ArrayRef, BinaryArray, BooleanArray, Date32Array, Float32Array, Float64Array,
326 Int16Array, Int32Array, Int64Array, Int8Array, LargeBinaryArray, LargeStringArray,
327 RecordBatchReader, StringArray, TimestampMicrosecondArray, TimestampMillisecondArray,
328 TimestampNanosecondArray, TimestampSecondArray,
329 },
330 buffer::NullBuffer,
331 compute::concat_batches,
332 datatypes::{DataType as ArrowDataType, Field, Schema, TimeUnit},
333 };
334 use bytes::Bytes;
335 use prost::Message;
336
337 use crate::{
338 compression::{CompressionType, Decompressor},
339 schema::DataType as OrcDataType,
340 stripe::Stripe,
341 ArrowReaderBuilder,
342 };
343
344 use super::*;
345
346 fn encode(batches: &[RecordBatch]) -> Bytes {
347 let mut f = vec![];
348 let mut writer = ArrowWriterBuilder::new(&mut f, batches[0].schema())
349 .try_build()
350 .unwrap();
351 for batch in batches {
352 writer.write(batch).unwrap();
353 }
354 writer.close().unwrap();
355
356 Bytes::from(f)
357 }
358
359 fn roundtrip(batches: &[RecordBatch]) -> Vec<RecordBatch> {
360 let f = encode(batches);
361 let reader = ArrowReaderBuilder::try_new(f).unwrap().build();
362 reader.collect::<Result<Vec<_>, _>>().unwrap()
363 }
364
365 #[test]
366 fn test_roundtrip_write() {
367 let f32_array = Arc::new(Float32Array::from(vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0]));
368 let f64_array = Arc::new(Float64Array::from(vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0]));
369 let int8_array = Arc::new(Int8Array::from(vec![0, 1, 2, 3, 4, 5, 6]));
370 let int16_array = Arc::new(Int16Array::from(vec![0, 1, 2, 3, 4, 5, 6]));
371 let int32_array = Arc::new(Int32Array::from(vec![0, 1, 2, 3, 4, 5, 6]));
372 let int64_array = Arc::new(Int64Array::from(vec![0, 1, 2, 3, 4, 5, 6]));
373 let utf8_array = Arc::new(StringArray::from(vec![
374 "Hello",
375 "there",
376 "楡井希実",
377 "💯",
378 "ORC",
379 "",
380 "123",
381 ]));
382 let binary_array = Arc::new(BinaryArray::from(vec![
383 "Hello".as_bytes(),
384 "there".as_bytes(),
385 "楡井希実".as_bytes(),
386 "💯".as_bytes(),
387 "ORC".as_bytes(),
388 "".as_bytes(),
389 "123".as_bytes(),
390 ]));
391 let boolean_array = Arc::new(BooleanArray::from(vec![
392 true, false, true, false, true, true, false,
393 ]));
394 let schema = Schema::new(vec![
395 Field::new("f32", ArrowDataType::Float32, false),
396 Field::new("f64", ArrowDataType::Float64, false),
397 Field::new("int8", ArrowDataType::Int8, false),
398 Field::new("int16", ArrowDataType::Int16, false),
399 Field::new("int32", ArrowDataType::Int32, false),
400 Field::new("int64", ArrowDataType::Int64, false),
401 Field::new("utf8", ArrowDataType::Utf8, false),
402 Field::new("binary", ArrowDataType::Binary, false),
403 Field::new("boolean", ArrowDataType::Boolean, false),
404 ]);
405
406 let batch = RecordBatch::try_new(
407 Arc::new(schema),
408 vec![
409 f32_array,
410 f64_array,
411 int8_array,
412 int16_array,
413 int32_array,
414 int64_array,
415 utf8_array,
416 binary_array,
417 boolean_array,
418 ],
419 )
420 .unwrap();
421
422 let rows = roundtrip(std::slice::from_ref(&batch));
423 assert_eq!(batch, rows[0]);
424 }
425
426 #[test]
427 fn test_roundtrip_write_large_type() {
428 let large_utf8_array = Arc::new(LargeStringArray::from(vec![
429 "Hello",
430 "there",
431 "楡井希実",
432 "💯",
433 "ORC",
434 "",
435 "123",
436 ]));
437 let large_binary_array = Arc::new(LargeBinaryArray::from(vec![
438 "Hello".as_bytes(),
439 "there".as_bytes(),
440 "楡井希実".as_bytes(),
441 "💯".as_bytes(),
442 "ORC".as_bytes(),
443 "".as_bytes(),
444 "123".as_bytes(),
445 ]));
446 let schema = Schema::new(vec![
447 Field::new("large_utf8", ArrowDataType::LargeUtf8, false),
448 Field::new("large_binary", ArrowDataType::LargeBinary, false),
449 ]);
450 let batch =
451 RecordBatch::try_new(Arc::new(schema), vec![large_utf8_array, large_binary_array])
452 .unwrap();
453
454 let rows = roundtrip(&[batch]);
455
456 let utf8_array = Arc::new(StringArray::from(vec![
458 "Hello",
459 "there",
460 "楡井希実",
461 "💯",
462 "ORC",
463 "",
464 "123",
465 ]));
466 let binary_array = Arc::new(BinaryArray::from(vec![
467 "Hello".as_bytes(),
468 "there".as_bytes(),
469 "楡井希実".as_bytes(),
470 "💯".as_bytes(),
471 "ORC".as_bytes(),
472 "".as_bytes(),
473 "123".as_bytes(),
474 ]));
475 let schema = Schema::new(vec![
476 Field::new("large_utf8", ArrowDataType::Utf8, false),
477 Field::new("large_binary", ArrowDataType::Binary, false),
478 ]);
479 let batch = RecordBatch::try_new(Arc::new(schema), vec![utf8_array, binary_array]).unwrap();
480 assert_eq!(batch, rows[0]);
481 }
482
483 #[test]
484 fn test_roundtrip_write_date32() {
485 let schema = Arc::new(Schema::new(vec![Field::new(
486 "date",
487 ArrowDataType::Date32,
488 true,
489 )]));
490
491 let batch_without_nulls = RecordBatch::try_new(
492 schema.clone(),
493 vec![Arc::new(Date32Array::from(vec![-1, 0, 1, 11_016]))],
494 )
495 .unwrap();
496 let batch_with_nulls = RecordBatch::try_new(
497 schema.clone(),
498 vec![Arc::new(Date32Array::from(vec![
499 Some(18_321),
500 None,
501 Some(19_782),
502 ]))],
503 )
504 .unwrap();
505
506 let file = encode(&[batch_without_nulls, batch_with_nulls]);
507 let builder = ArrowReaderBuilder::try_new(file).unwrap();
508 assert!(matches!(
509 builder.file_metadata().root_data_type().children()[0].data_type(),
510 OrcDataType::Date { .. }
511 ));
512
513 let rows = builder.build().collect::<Result<Vec<_>, _>>().unwrap();
514 let actual = concat_batches(&schema, rows.iter()).unwrap();
515 let expected = RecordBatch::try_new(
516 schema,
517 vec![Arc::new(Date32Array::from(vec![
518 Some(-1),
519 Some(0),
520 Some(1),
521 Some(11_016),
522 Some(18_321),
523 None,
524 Some(19_782),
525 ]))],
526 )
527 .unwrap();
528
529 assert_eq!(expected, actual);
530 }
531
532 #[test]
533 fn test_roundtrip_write_timestamps() {
534 let second = vec![Some(-2), Some(-1), Some(0), None, Some(1), Some(2)];
535 let millisecond = vec![
536 Some(-1_001),
537 Some(-1_000),
538 Some(-1_999),
539 None,
540 Some(1_001),
541 Some(1_234),
542 ];
543 let microsecond = vec![
544 Some(-1_001_001),
545 Some(-1_000_000),
546 Some(-999_999),
547 None,
548 Some(1_001_001),
549 Some(1_234_567),
550 ];
551 let nanosecond = vec![
552 Some(-1_001_001_001),
553 Some(-1_000_000_000),
554 Some(-999_999_999),
555 None,
556 Some(1_001_001_001),
557 Some(1_234_567_890),
558 ];
559
560 let arrays: Vec<ArrayRef> = vec![
561 Arc::new(TimestampSecondArray::from(second.clone())),
562 Arc::new(TimestampMillisecondArray::from(millisecond.clone())),
563 Arc::new(TimestampMicrosecondArray::from(microsecond.clone())),
564 Arc::new(TimestampNanosecondArray::from(nanosecond.clone())),
565 Arc::new(TimestampSecondArray::from(second).with_timezone("UTC")),
566 Arc::new(TimestampMillisecondArray::from(millisecond).with_timezone("UTC")),
567 Arc::new(TimestampMicrosecondArray::from(microsecond).with_timezone("UTC")),
568 Arc::new(TimestampNanosecondArray::from(nanosecond).with_timezone("UTC")),
569 ];
570 let schema = Arc::new(Schema::new(
571 arrays
572 .iter()
573 .enumerate()
574 .map(|(index, array)| {
575 Field::new(
576 format!("timestamp_{index}"),
577 array.data_type().clone(),
578 true,
579 )
580 })
581 .collect::<Vec<_>>(),
582 ));
583 let batch = RecordBatch::try_new(schema.clone(), arrays).unwrap();
584
585 let mut file = Vec::new();
586 let mut writer = ArrowWriterBuilder::new(&mut file, schema.clone())
587 .try_build()
588 .unwrap();
589 writer.write(&batch.slice(0, 3)).unwrap();
590 writer.flush_stripe().unwrap();
591 writer.write(&batch.slice(3, 3)).unwrap();
592 writer.close().unwrap();
593
594 let file = Bytes::from(file);
595 let builder = ArrowReaderBuilder::try_new(file.clone()).unwrap();
596 let children = builder.file_metadata().root_data_type().children();
597 for child in &children[..4] {
598 assert!(matches!(child.data_type(), OrcDataType::Timestamp { .. }));
599 }
600 for child in &children[4..] {
601 assert!(matches!(
602 child.data_type(),
603 OrcDataType::TimestampWithLocalTimezone { .. }
604 ));
605 }
606
607 let stripes = builder.file_metadata().stripe_metadatas();
608 assert_eq!(stripes.len(), 2);
609 for stripe in stripes {
610 let footer_start = stripe.footer_offset() as usize;
611 let footer_end = footer_start + stripe.footer_length() as usize;
612 let mut decoded_footer = Vec::new();
613 Decompressor::new(
614 file.slice(footer_start..footer_end),
615 builder.file_metadata().compression(),
616 Vec::new(),
617 )
618 .read_to_end(&mut decoded_footer)
619 .unwrap();
620 let footer = proto::StripeFooter::decode(decoded_footer.as_slice()).unwrap();
621 assert_eq!(footer.writer_timezone.as_deref(), Some("UTC"));
622 assert_eq!(
623 footer
624 .streams
625 .iter()
626 .filter(|stream| stream.kind() == proto::stream::Kind::Secondary)
627 .count(),
628 8
629 );
630 }
631
632 let rows = builder
633 .with_schema(schema.clone())
634 .build()
635 .collect::<Result<Vec<_>, _>>()
636 .unwrap();
637 let actual = concat_batches(&schema, rows.iter()).unwrap();
638 assert_eq!(batch, actual);
639 }
640
641 #[test]
642 fn test_write_timestamps_with_inconsistent_null_buffers() {
643 let schema = Arc::new(Schema::new(vec![Field::new(
644 "timestamp",
645 ArrowDataType::Timestamp(TimeUnit::Nanosecond, None),
646 true,
647 )]));
648
649 let without_nulls = Arc::new(TimestampNanosecondArray::from(vec![1, 2, 3]));
650 let with_nulls = Arc::new(TimestampNanosecondArray::from(vec![None, Some(4), None]));
651 assert!(without_nulls.nulls().is_none());
652 assert!(with_nulls.nulls().is_some());
653
654 let batch1 = RecordBatch::try_new(schema.clone(), vec![without_nulls]).unwrap();
655 let batch2 = RecordBatch::try_new(schema.clone(), vec![with_nulls]).unwrap();
656 let expected = RecordBatch::try_new(
657 schema,
658 vec![Arc::new(TimestampNanosecondArray::from(vec![
659 Some(1),
660 Some(2),
661 Some(3),
662 None,
663 Some(4),
664 None,
665 ]))],
666 )
667 .unwrap();
668
669 let rows = roundtrip(&[batch1, batch2]);
670 assert_eq!(expected, rows[0]);
671 }
672
673 #[test]
674 fn test_write_small_stripes() {
675 let data: Vec<i64> = (0..1_000_000).collect();
677 let int64_array = Arc::new(Int64Array::from(data));
678 let schema = Schema::new(vec![Field::new("int64", ArrowDataType::Int64, true)]);
679
680 let batch = RecordBatch::try_new(Arc::new(schema), vec![int64_array]).unwrap();
681
682 let mut f = vec![];
683 let mut writer = ArrowWriterBuilder::new(&mut f, batch.schema())
684 .with_stripe_byte_size(256)
685 .try_build()
686 .unwrap();
687 writer.write(&batch).unwrap();
688 writer.close().unwrap();
689
690 let f = Bytes::from(f);
691 let reader = ArrowReaderBuilder::try_new(f).unwrap().build();
692 let schema = reader.schema();
693 let rows = reader.collect::<Result<Vec<_>, _>>().unwrap();
696 assert!(
697 rows.len() > 1,
698 "must have written more than 1 stripe (each stripe read as separate recordbatch)"
699 );
700 let actual = concat_batches(&schema, rows.iter()).unwrap();
701 assert_eq!(batch, actual);
702 }
703
704 #[test]
705 fn test_write_inconsistent_null_buffers() {
706 let schema = Arc::new(Schema::new(vec![Field::new(
708 "int64",
709 ArrowDataType::Int64,
710 true,
711 )]));
712
713 let array_no_nulls = Arc::new(Int64Array::from(vec![1, 2, 3]));
715 assert!(array_no_nulls.nulls().is_none());
716 let array_with_nulls = Arc::new(Int64Array::from(vec![None, Some(4), None]));
718 assert!(array_with_nulls.nulls().is_some());
719
720 let batch1 = RecordBatch::try_new(schema.clone(), vec![array_no_nulls]).unwrap();
721 let batch2 = RecordBatch::try_new(schema.clone(), vec![array_with_nulls]).unwrap();
722
723 let expected_array = Arc::new(Int64Array::from(vec![
725 Some(1),
726 Some(2),
727 Some(3),
728 None,
729 Some(4),
730 None,
731 ]));
732 let expected_batch = RecordBatch::try_new(schema, vec![expected_array]).unwrap();
733
734 let rows = roundtrip(&[batch1, batch2]);
735 assert_eq!(expected_batch, rows[0]);
736 }
737
738 #[test]
739 fn test_empty_null_buffers() {
740 let schema = Arc::new(Schema::new(vec![Field::new(
744 "int64",
745 ArrowDataType::Int64,
746 true,
747 )]));
748
749 let array_empty_nulls = Arc::new(Int64Array::from_iter_values_with_nulls(
751 vec![1],
752 Some(NullBuffer::from_iter(vec![true])),
753 ));
754 assert!(array_empty_nulls.nulls().is_some());
755 assert!(array_empty_nulls.null_count() == 0);
756
757 let batch = RecordBatch::try_new(schema, vec![array_empty_nulls]).unwrap();
758
759 let mut f = vec![];
761 let mut writer = ArrowWriterBuilder::new(&mut f, batch.schema())
762 .try_build()
763 .unwrap();
764 writer.write(&batch).unwrap();
765 writer.close().unwrap();
766 let mut f = Bytes::from(f);
767 let builder = ArrowReaderBuilder::try_new(f.clone()).unwrap();
768
769 let stripe = Stripe::new(
771 &mut f,
772 &builder.file_metadata,
773 builder.file_metadata().root_data_type(),
774 &builder.file_metadata().stripe_metadatas()[0],
775 )
776 .unwrap();
777 assert_eq!(stripe.columns().len(), 1);
778 assert_eq!(stripe.columns()[0].name(), "int64");
780 let present_stream = stripe
782 .stream_map()
783 .get_opt(&stripe.columns()[0], proto::stream::Kind::Present);
784 assert!(present_stream.is_some());
785
786 let reader = builder.build();
788 let rows = reader.collect::<Result<Vec<_>, _>>().unwrap();
789
790 assert_eq!(rows.len(), 1);
791 assert_eq!(rows[0].num_columns(), 1);
792 assert!(rows[0].column(0).nulls().is_none());
794 }
795
796 fn write_file(
797 schema: SchemaRef,
798 batch: Option<&RecordBatch>,
799 compression: Option<CompressionType>,
800 block_size: Option<usize>,
801 stripe_byte_size: usize,
802 ) -> Bytes {
803 let mut file = Vec::new();
804 let mut builder =
805 ArrowWriterBuilder::new(&mut file, schema).with_stripe_byte_size(stripe_byte_size);
806 if let Some(compression) = compression {
807 builder = builder.with_compression(compression);
808 }
809 if let Some(block_size) = block_size {
810 builder = builder.with_compression_block_size(block_size);
811 }
812 let mut writer = builder.try_build().unwrap();
813 if let Some(batch) = batch {
814 writer.write(batch).unwrap();
815 }
816 writer.close().unwrap();
817 Bytes::from(file)
818 }
819
820 fn decode_postscript(file: &Bytes) -> proto::PostScript {
821 let postscript_len = file[file.len() - 1] as usize;
822 let postscript_start = file.len() - 1 - postscript_len;
823 proto::PostScript::decode(&file[postscript_start..file.len() - 1]).unwrap()
824 }
825
826 #[test]
827 fn test_writer_compression_modes_and_postscript() {
828 let values = (0..4096)
829 .map(|index| format!("compressible-value-{}", index % 8))
830 .collect::<Vec<_>>();
831 let array = Arc::new(StringArray::from(values));
832 let schema = Arc::new(Schema::new(vec![Field::new(
833 "value",
834 ArrowDataType::Utf8,
835 false,
836 )]));
837 let batch = RecordBatch::try_new(schema.clone(), vec![array]).unwrap();
838
839 for (compression, expected_kind, expected_block_size) in [
840 (None, proto::CompressionKind::None, None),
841 (
842 Some(CompressionType::Zlib),
843 proto::CompressionKind::Zlib,
844 Some(64),
845 ),
846 (
847 Some(CompressionType::Snappy),
848 proto::CompressionKind::Snappy,
849 Some(64),
850 ),
851 (
852 Some(CompressionType::Lz4),
853 proto::CompressionKind::Lz4,
854 Some(64),
855 ),
856 (
857 Some(CompressionType::Zstd),
858 proto::CompressionKind::Zstd,
859 Some(64),
860 ),
861 ] {
862 let block_size = compression.map(|_| 64);
863 let file = write_file(
864 schema.clone(),
865 Some(&batch),
866 compression,
867 block_size,
868 usize::MAX,
869 );
870 let postscript = decode_postscript(&file);
871 assert_eq!(postscript.compression(), expected_kind);
872 assert_eq!(postscript.compression_block_size, expected_block_size);
873 assert_eq!(postscript.metadata_length, Some(0));
874 assert!(postscript.footer_length.unwrap() > 0);
875
876 let reader = ArrowReaderBuilder::try_new(file).unwrap().build();
877 let rows = reader.collect::<Result<Vec<_>, _>>().unwrap();
878 assert_eq!(rows, vec![batch.clone()]);
879 }
880
881 let file = write_file(
882 schema,
883 None,
884 Some(CompressionType::Zstd),
885 Some(64),
886 usize::MAX,
887 );
888 let postscript = decode_postscript(&file);
889 assert_eq!(postscript.compression(), proto::CompressionKind::Zstd);
890 assert_eq!(postscript.compression_block_size, Some(64));
891 assert_eq!(postscript.metadata_length, Some(0));
892
893 let reader = ArrowReaderBuilder::try_new(file).unwrap();
894 assert_eq!(reader.file_metadata().number_of_rows(), 0);
895 assert!(reader.file_metadata().stripe_metadatas().is_empty());
896 assert!(reader
897 .build()
898 .collect::<Result<Vec<_>, _>>()
899 .unwrap()
900 .is_empty());
901 }
902
903 #[test]
904 fn test_writer_rejects_invalid_compression_configuration() {
905 let schema = Arc::new(Schema::new(vec![Field::new(
906 "value",
907 ArrowDataType::Int64,
908 false,
909 )]));
910
911 for compression in [None, Some(CompressionType::Zstd)] {
912 for block_size in [0, 1 << 23] {
913 let mut file = Vec::new();
914 let mut builder = ArrowWriterBuilder::new(&mut file, schema.clone())
915 .with_compression_block_size(block_size);
916 if let Some(compression) = compression {
917 builder = builder.with_compression(compression);
918 }
919 assert!(builder.try_build().is_err());
920 assert!(file.is_empty());
921 }
922 }
923
924 let mut file = Vec::new();
925 let result = ArrowWriterBuilder::new(&mut file, schema)
926 .with_compression(CompressionType::Lzo)
927 .try_build();
928 assert!(result.is_err());
929 assert!(file.is_empty());
930 }
931
932 #[test]
933 fn test_compressed_multi_stripe_physical_offsets() {
934 let data = (0..100_000).collect::<Vec<i64>>();
935 let schema = Arc::new(Schema::new(vec![Field::new(
936 "value",
937 ArrowDataType::Int64,
938 false,
939 )]));
940 let batch = RecordBatch::try_new(schema, vec![Arc::new(Int64Array::from(data))]).unwrap();
941 let file = write_file(
942 batch.schema(),
943 Some(&batch),
944 Some(CompressionType::Zstd),
945 Some(64),
946 256,
947 );
948
949 let builder = ArrowReaderBuilder::try_new(file.clone()).unwrap();
950 let stripes = builder.file_metadata().stripe_metadatas();
951 assert!(stripes.len() > 1);
952 assert_eq!(stripes[0].offset(), 3);
953 for pair in stripes.windows(2) {
954 let previous = &pair[0];
955 let next = &pair[1];
956 assert_eq!(
957 next.offset(),
958 previous.offset()
959 + previous.index_length()
960 + previous.data_length()
961 + previous.footer_length()
962 );
963 }
964
965 for stripe in stripes {
966 let footer_start = stripe.footer_offset() as usize;
967 let footer_end = footer_start + stripe.footer_length() as usize;
968 let mut decoded_footer = Vec::new();
969 Decompressor::new(
970 file.slice(footer_start..footer_end),
971 builder.file_metadata().compression(),
972 Vec::new(),
973 )
974 .read_to_end(&mut decoded_footer)
975 .unwrap();
976 let footer = proto::StripeFooter::decode(decoded_footer.as_slice()).unwrap();
977 let stream_bytes = footer.streams.iter().map(|s| s.length()).sum::<u64>();
978 assert_eq!(stream_bytes, stripe.data_length());
979 assert_eq!(stripe.index_length(), 0);
980 }
981
982 let reader = builder.build();
983 let rows = reader.collect::<Result<Vec<_>, _>>().unwrap();
984 let actual = concat_batches(&batch.schema(), rows.iter()).unwrap();
985 assert_eq!(actual, batch);
986 }
987}