1use crate::compression::CompressionCodec;
24use crate::reader::Decoder;
25use crate::reader::block::{BlockDecoder, BlockDecoderState};
26use arrow_array::RecordBatch;
27use arrow_schema::{ArrowError, SchemaRef};
28use bytes::Bytes;
29use futures::future::BoxFuture;
30use futures::{FutureExt, Stream};
31use std::mem;
32use std::ops::Range;
33use std::pin::Pin;
34use std::task::{Context, Poll};
35
36mod async_file_reader;
37mod builder;
38mod spawn;
39
40pub use async_file_reader::AsyncFileReader;
41pub use builder::{ReaderBuilder, read_header_info};
42pub use spawn::SpawnedReader;
43
44#[cfg(feature = "object_store")]
45mod store;
46
47use crate::errors::AvroError;
48#[allow(deprecated)]
49#[cfg(feature = "object_store")]
50pub use store::AvroObjectReader;
51
52enum FetchNextBehaviour {
53 ReadSyncMarker,
55 DecodeVLQHeader,
57 ContinueDecoding,
59}
60
61enum ReaderState<R> {
62 InvalidState,
64 Idle { reader: R },
66 FetchingData {
68 future: BoxFuture<'static, Result<(R, Bytes), AvroError>>,
69 next_behaviour: FetchNextBehaviour,
70 },
71 DecodingBlock { data: Bytes, reader: R },
73 ReadingBatches {
75 data: Bytes,
76 block_data: Bytes,
77 remaining_in_block: usize,
78 reader: R,
79 },
80 Flushing,
83 Finished,
85}
86
87pub struct AsyncAvroFileReader<R> {
141 range: Range<u64>,
143 file_size: u64,
144
145 decoder: Decoder,
147 block_decoder: BlockDecoder,
148 codec: Option<CompressionCodec>,
149 sync_marker: [u8; 16],
150
151 reader_state: ReaderState<R>,
153 finishing_partial_block: bool,
154}
155
156impl<R> AsyncAvroFileReader<R> {
157 pub fn builder(reader: R, file_size: u64, batch_size: usize) -> ReaderBuilder<R> {
159 ReaderBuilder::new(reader, file_size, batch_size)
160 }
161
162 fn new(
163 range: Range<u64>,
164 file_size: u64,
165 decoder: Decoder,
166 codec: Option<CompressionCodec>,
167 sync_marker: [u8; 16],
168 reader_state: ReaderState<R>,
169 ) -> Self {
170 Self {
171 range,
172 file_size,
173
174 decoder,
175 block_decoder: Default::default(),
176 codec,
177 sync_marker,
178
179 reader_state,
180 finishing_partial_block: false,
181 }
182 }
183
184 pub fn schema(&self) -> SchemaRef {
188 self.decoder.schema()
189 }
190
191 fn remaining_block_range(&self) -> Result<Range<u64>, AvroError> {
195 let remaining = self.block_decoder.bytes_remaining() as u64
196 + match self.block_decoder.state() {
197 BlockDecoderState::Data => 16, BlockDecoderState::Sync => 0,
199 state => {
200 return Err(AvroError::General(format!(
201 "remaining_block_range called in unexpected state: {state:?}"
202 )));
203 }
204 };
205
206 let fetch_end = self.range.end + remaining;
207 if fetch_end > self.file_size {
208 return Err(AvroError::EOF(
209 "Avro block requires more bytes than what exists in the file".into(),
210 ));
211 }
212
213 Ok(self.range.end..fetch_end)
214 }
215
216 #[inline]
218 fn finish_with_error(
219 &mut self,
220 error: AvroError,
221 ) -> Poll<Option<Result<RecordBatch, AvroError>>> {
222 self.reader_state = ReaderState::Finished;
223 Poll::Ready(Some(Err(error)))
224 }
225
226 #[inline]
227 fn start_flushing(&mut self) {
228 self.reader_state = ReaderState::Flushing;
229 }
230
231 #[inline]
233 fn poll_flush(&mut self) -> Poll<Option<Result<RecordBatch, AvroError>>> {
234 match self.decoder.flush_block() {
235 Ok(Some(batch)) => {
236 self.reader_state = ReaderState::Flushing;
237 Poll::Ready(Some(Ok(batch)))
238 }
239 Ok(None) => {
240 self.reader_state = ReaderState::Finished;
241 Poll::Ready(None)
242 }
243 Err(e) => self.finish_with_error(e),
244 }
245 }
246}
247
248impl<R: AsyncFileReader + Unpin + 'static> AsyncAvroFileReader<R> {
249 async fn fetch_bytes(mut reader: R, range: Range<u64>) -> Result<(R, Bytes), AvroError> {
252 let data = reader.get_bytes(range).await?;
253 Ok((reader, data))
254 }
255
256 #[forbid(clippy::question_mark_used)]
257 fn read_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<RecordBatch, AvroError>>> {
258 loop {
259 match mem::replace(&mut self.reader_state, ReaderState::InvalidState) {
260 ReaderState::Idle { reader } => {
261 let range = self.range.clone();
262 if range.start >= range.end {
263 return self.finish_with_error(AvroError::InvalidArgument(format!(
264 "Invalid range specified for Avro file: start {} >= end {}, file_size: {}",
265 range.start, range.end, self.file_size
266 )));
267 }
268
269 let future = Self::fetch_bytes(reader, range).boxed();
270 self.reader_state = ReaderState::FetchingData {
271 future,
272 next_behaviour: FetchNextBehaviour::ReadSyncMarker,
273 };
274 }
275 ReaderState::FetchingData {
276 mut future,
277 next_behaviour,
278 } => {
279 let (reader, data_chunk) = match future.poll_unpin(cx) {
280 Poll::Ready(Ok(data)) => data,
281 Poll::Ready(Err(e)) => return self.finish_with_error(e),
282 Poll::Pending => {
283 self.reader_state = ReaderState::FetchingData {
284 future,
285 next_behaviour,
286 };
287 return Poll::Pending;
288 }
289 };
290
291 match next_behaviour {
292 FetchNextBehaviour::ReadSyncMarker => {
293 let sync_marker_pos = data_chunk
294 .windows(16)
295 .position(|slice| slice == self.sync_marker);
296 let block_start = match sync_marker_pos {
297 Some(pos) => pos + 16, None => {
299 self.reader_state = ReaderState::Finished;
301 return Poll::Ready(None);
302 }
303 };
304
305 self.reader_state = ReaderState::DecodingBlock {
306 reader,
307 data: data_chunk.slice(block_start..),
308 };
309 }
310 FetchNextBehaviour::DecodeVLQHeader => {
311 let mut data = data_chunk;
312
313 while !matches!(self.block_decoder.state(), BlockDecoderState::Data) {
315 if data.is_empty() {
316 return self.finish_with_error(AvroError::EOF(
317 "Unexpected EOF while reading Avro block header".into(),
318 ));
319 }
320 let consumed = match self.block_decoder.decode(&data[..1]) {
321 Ok(consumed) => consumed,
322 Err(e) => return self.finish_with_error(e),
323 };
324 if consumed == 0 {
325 return self.finish_with_error(AvroError::General(
326 "BlockDecoder failed to consume byte during VLQ header parsing"
327 .into(),
328 ));
329 }
330 data = data.slice(consumed..);
331 }
332
333 let bytes_remaining = self.block_decoder.bytes_remaining();
335 let data_to_use = data.slice(..data.len().min(bytes_remaining));
336 let consumed = match self.block_decoder.decode(&data_to_use) {
337 Ok(consumed) => consumed,
338 Err(e) => return self.finish_with_error(e),
339 };
340 if consumed != data_to_use.len() {
341 return self.finish_with_error(AvroError::General(
342 "BlockDecoder failed to consume all bytes after VLQ header parsing"
343 .into(),
344 ));
345 }
346
347 let range_to_fetch = match self.remaining_block_range() {
349 Ok(range) if range.is_empty() => {
350 self.reader_state = ReaderState::DecodingBlock {
352 reader,
353 data: Bytes::new(),
354 };
355 continue;
356 }
357 Ok(range) => range,
358 Err(e) => return self.finish_with_error(e),
359 };
360
361 let future = Self::fetch_bytes(reader, range_to_fetch).boxed();
362 self.reader_state = ReaderState::FetchingData {
363 future,
364 next_behaviour: FetchNextBehaviour::ContinueDecoding,
365 };
366 continue;
367 }
368 FetchNextBehaviour::ContinueDecoding => {
369 self.reader_state = ReaderState::DecodingBlock {
370 reader,
371 data: data_chunk,
372 };
373 }
374 }
375 }
376 ReaderState::InvalidState => {
377 return self.finish_with_error(AvroError::General(
378 "AsyncAvroFileReader in invalid state".into(),
379 ));
380 }
381 ReaderState::DecodingBlock { reader, mut data } => {
382 let consumed = match self.block_decoder.decode(&data) {
384 Ok(consumed) => consumed,
385 Err(e) => return self.finish_with_error(e),
386 };
387 data = data.slice(consumed..);
388
389 if let Some(block) = self.block_decoder.flush() {
391 let block_count = block.count;
393 let block_data = Bytes::from_owner(if let Some(ref codec) = self.codec {
394 match codec.decompress(&block.data) {
395 Ok(decompressed) => decompressed,
396 Err(e) => return self.finish_with_error(e),
397 }
398 } else {
399 block.data
400 });
401
402 self.reader_state = ReaderState::ReadingBatches {
404 reader,
405 data,
406 block_data,
407 remaining_in_block: block_count,
408 };
409 continue;
410 }
411
412 if !data.is_empty() {
414 return self.finish_with_error(AvroError::General(
415 "BlockDecoder failed to make progress decoding Avro block".into(),
416 ));
417 }
418
419 if matches!(self.block_decoder.state(), BlockDecoderState::Finished) {
420 self.finishing_partial_block = false;
422 self.start_flushing();
423 continue;
424 }
425
426 if self.finishing_partial_block {
429 return self.finish_with_error(AvroError::EOF(
430 "Unexpected EOF while reading last Avro block".into(),
431 ));
432 }
433
434 self.finishing_partial_block = true;
440
441 if matches!(
443 self.block_decoder.state(),
444 BlockDecoderState::Count | BlockDecoderState::Size
445 ) {
446 const MAX_VLQ_HEADER_SIZE: u64 = 20;
449 let fetch_end = (self.range.end + MAX_VLQ_HEADER_SIZE).min(self.file_size);
450
451 if fetch_end == self.range.end {
453 return self.finish_with_error(AvroError::EOF(
454 "Unexpected EOF while reading Avro block header".into(),
455 ));
456 }
457
458 let range_to_fetch = self.range.end..fetch_end;
459 self.range.end = fetch_end; let future = Self::fetch_bytes(reader, range_to_fetch).boxed();
462 self.reader_state = ReaderState::FetchingData {
463 future,
464 next_behaviour: FetchNextBehaviour::DecodeVLQHeader,
465 };
466 continue;
467 }
468
469 let range_to_fetch = match self.remaining_block_range() {
471 Ok(range) => range,
472 Err(e) => return self.finish_with_error(e),
473 };
474
475 let future = Self::fetch_bytes(reader, range_to_fetch).boxed();
476 self.reader_state = ReaderState::FetchingData {
477 future,
478 next_behaviour: FetchNextBehaviour::ContinueDecoding,
479 };
480 continue;
481 }
482 ReaderState::ReadingBatches {
483 reader,
484 data,
485 mut block_data,
486 mut remaining_in_block,
487 } => {
488 let (consumed, records_decoded) =
489 match self.decoder.decode_block(&block_data, remaining_in_block) {
490 Ok((consumed, records_decoded)) => (consumed, records_decoded),
491 Err(e) => return self.finish_with_error(e),
492 };
493
494 remaining_in_block -= records_decoded;
495
496 if remaining_in_block == 0 {
497 if data.is_empty() {
498 self.start_flushing();
500 } else {
501 self.reader_state = ReaderState::DecodingBlock { reader, data };
503 }
504 } else {
505 block_data = block_data.slice(consumed..);
507 self.reader_state = ReaderState::ReadingBatches {
508 reader,
509 data,
510 block_data,
511 remaining_in_block,
512 };
513 }
514
515 if self.decoder.batch_is_full() {
518 return match self.decoder.flush_block() {
519 Ok(Some(batch)) => Poll::Ready(Some(Ok(batch))),
520 Ok(None) => self.finish_with_error(AvroError::General(
521 "Decoder reported a full batch, but flush returned None".into(),
522 )),
523 Err(e) => self.finish_with_error(e),
524 };
525 }
526 }
527 ReaderState::Flushing => {
528 return self.poll_flush();
529 }
530 ReaderState::Finished => {
531 self.reader_state = ReaderState::Finished;
533 return Poll::Ready(None);
534 }
535 }
536 }
537 }
538}
539
540impl<R: AsyncFileReader + Unpin + 'static> Stream for AsyncAvroFileReader<R> {
542 type Item = Result<RecordBatch, ArrowError>;
543
544 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
545 self.read_next(cx).map_err(Into::into)
546 }
547}
548
549#[cfg(test)]
550mod tests {
551 use super::*;
552 use crate::codec::Tz;
553 use crate::schema::{
554 AVRO_NAME_METADATA_KEY, AVRO_NAMESPACE_METADATA_KEY, AvroSchema, SCHEMA_METADATA_KEY,
555 };
556 use arrow_array::cast::AsArray;
557 use arrow_array::types::{Int32Type, Int64Type};
558 use arrow_array::*;
559 use arrow_schema::{DataType, Field, Schema, SchemaRef, TimeUnit};
560 use futures::{StreamExt, TryStreamExt};
561 use object_store::local::LocalFileSystem;
562 use object_store::path::Path;
563 use object_store::{ObjectStore, ObjectStoreExt};
564 use std::collections::HashMap;
565 use std::sync::Arc;
566
567 #[derive(Clone, Debug)]
570 struct ObjectStoreReader {
571 store: Arc<dyn ObjectStore>,
572 path: Path,
573 }
574
575 impl ObjectStoreReader {
576 fn new(store: Arc<dyn ObjectStore>, path: Path) -> Self {
577 Self { store, path }
578 }
579 }
580
581 impl AsyncFileReader for ObjectStoreReader {
582 fn get_bytes(&mut self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes, AvroError>> {
583 async move {
584 self.store
585 .get_range(&self.path, range)
586 .await
587 .map_err(|e| AvroError::General(e.to_string()))
588 }
589 .boxed()
590 }
591
592 fn get_byte_ranges(
593 &mut self,
594 ranges: Vec<Range<u64>>,
595 ) -> BoxFuture<'_, Result<Vec<Bytes>, AvroError>> {
596 async move {
597 self.store
598 .get_ranges(&self.path, &ranges)
599 .await
600 .map_err(|e| AvroError::General(e.to_string()))
601 }
602 .boxed()
603 }
604 }
605
606 fn arrow_test_data(file: &str) -> String {
607 let base =
608 std::env::var("ARROW_TEST_DATA").unwrap_or_else(|_| "../testing/data".to_string());
609 format!("{}/{}", base, file)
610 }
611
612 fn get_alltypes_schema() -> SchemaRef {
613 get_alltypes_schema_with_tz("+00:00")
614 }
615
616 fn get_alltypes_schema_with_tz(tz_id: &str) -> SchemaRef {
617 let schema = Schema::new(vec![
618 Field::new("id", DataType::Int32, true),
619 Field::new("bool_col", DataType::Boolean, true),
620 Field::new("tinyint_col", DataType::Int32, true),
621 Field::new("smallint_col", DataType::Int32, true),
622 Field::new("int_col", DataType::Int32, true),
623 Field::new("bigint_col", DataType::Int64, true),
624 Field::new("float_col", DataType::Float32, true),
625 Field::new("double_col", DataType::Float64, true),
626 Field::new("date_string_col", DataType::Binary, true),
627 Field::new("string_col", DataType::Binary, true),
628 Field::new(
629 "timestamp_col",
630 DataType::Timestamp(TimeUnit::Microsecond, Some(tz_id.into())),
631 true,
632 ),
633 ])
634 .with_metadata(HashMap::from([(
635 SCHEMA_METADATA_KEY.into(),
636 r#"{
637 "type": "record",
638 "name": "topLevelRecord",
639 "fields": [
640 {
641 "name": "id",
642 "type": [
643 "int",
644 "null"
645 ]
646 },
647 {
648 "name": "bool_col",
649 "type": [
650 "boolean",
651 "null"
652 ]
653 },
654 {
655 "name": "tinyint_col",
656 "type": [
657 "int",
658 "null"
659 ]
660 },
661 {
662 "name": "smallint_col",
663 "type": [
664 "int",
665 "null"
666 ]
667 },
668 {
669 "name": "int_col",
670 "type": [
671 "int",
672 "null"
673 ]
674 },
675 {
676 "name": "bigint_col",
677 "type": [
678 "long",
679 "null"
680 ]
681 },
682 {
683 "name": "float_col",
684 "type": [
685 "float",
686 "null"
687 ]
688 },
689 {
690 "name": "double_col",
691 "type": [
692 "double",
693 "null"
694 ]
695 },
696 {
697 "name": "date_string_col",
698 "type": [
699 "bytes",
700 "null"
701 ]
702 },
703 {
704 "name": "string_col",
705 "type": [
706 "bytes",
707 "null"
708 ]
709 },
710 {
711 "name": "timestamp_col",
712 "type": [
713 {
714 "type": "long",
715 "logicalType": "timestamp-micros"
716 },
717 "null"
718 ]
719 }
720 ]
721}
722"#
723 .into(),
724 )]));
725 Arc::new(schema)
726 }
727
728 fn get_alltypes_with_nulls_schema() -> SchemaRef {
729 let schema = Schema::new(vec![
730 Field::new("string_col", DataType::Binary, true),
731 Field::new("int_col", DataType::Int32, true),
732 Field::new("bool_col", DataType::Boolean, true),
733 Field::new("bigint_col", DataType::Int64, true),
734 Field::new("float_col", DataType::Float32, true),
735 Field::new("double_col", DataType::Float64, true),
736 Field::new("bytes_col", DataType::Binary, true),
737 ])
738 .with_metadata(HashMap::from([(
739 SCHEMA_METADATA_KEY.into(),
740 r#"{
741 "type": "record",
742 "name": "topLevelRecord",
743 "fields": [
744 {
745 "name": "string_col",
746 "type": [
747 "null",
748 "string"
749 ],
750 "default": null
751 },
752 {
753 "name": "int_col",
754 "type": [
755 "null",
756 "int"
757 ],
758 "default": null
759 },
760 {
761 "name": "bool_col",
762 "type": [
763 "null",
764 "boolean"
765 ],
766 "default": null
767 },
768 {
769 "name": "bigint_col",
770 "type": [
771 "null",
772 "long"
773 ],
774 "default": null
775 },
776 {
777 "name": "float_col",
778 "type": [
779 "null",
780 "float"
781 ],
782 "default": null
783 },
784 {
785 "name": "double_col",
786 "type": [
787 "null",
788 "double"
789 ],
790 "default": null
791 },
792 {
793 "name": "bytes_col",
794 "type": [
795 "null",
796 "bytes"
797 ],
798 "default": null
799 }
800 ]
801}"#
802 .into(),
803 )]));
804
805 Arc::new(schema)
806 }
807
808 fn get_nested_records_schema() -> SchemaRef {
809 let schema = Schema::new(vec![
810 Field::new(
811 "f1",
812 DataType::Struct(
813 vec![
814 Field::new("f1_1", DataType::Utf8, false),
815 Field::new("f1_2", DataType::Int32, false),
816 Field::new(
817 "f1_3",
818 DataType::Struct(
819 vec![Field::new("f1_3_1", DataType::Float64, false)].into(),
820 ),
821 false,
822 )
823 .with_metadata(HashMap::from([
824 (AVRO_NAMESPACE_METADATA_KEY.to_owned(), "ns3".to_owned()),
825 (AVRO_NAME_METADATA_KEY.to_owned(), "record3".to_owned()),
826 ])),
827 ]
828 .into(),
829 ),
830 false,
831 )
832 .with_metadata(HashMap::from([
833 (AVRO_NAMESPACE_METADATA_KEY.to_owned(), "ns2".to_owned()),
834 (AVRO_NAME_METADATA_KEY.to_owned(), "record2".to_owned()),
835 ])),
836 Field::new(
837 "f2",
838 DataType::List(Arc::new(
839 Field::new(
840 "item",
841 DataType::Struct(
842 vec![
843 Field::new("f2_1", DataType::Boolean, false),
844 Field::new("f2_2", DataType::Float32, false),
845 ]
846 .into(),
847 ),
848 false,
849 )
850 .with_metadata(HashMap::from([
851 (AVRO_NAMESPACE_METADATA_KEY.to_owned(), "ns4".to_owned()),
852 (AVRO_NAME_METADATA_KEY.to_owned(), "record4".to_owned()),
853 ])),
854 )),
855 false,
856 ),
857 Field::new(
858 "f3",
859 DataType::Struct(vec![Field::new("f3_1", DataType::Utf8, false)].into()),
860 true,
861 )
862 .with_metadata(HashMap::from([
863 (AVRO_NAMESPACE_METADATA_KEY.to_owned(), "ns5".to_owned()),
864 (AVRO_NAME_METADATA_KEY.to_owned(), "record5".to_owned()),
865 ])),
866 Field::new(
867 "f4",
868 DataType::List(Arc::new(
869 Field::new(
870 "item",
871 DataType::Struct(vec![Field::new("f4_1", DataType::Int64, false)].into()),
872 true,
873 )
874 .with_metadata(HashMap::from([
875 (AVRO_NAMESPACE_METADATA_KEY.to_owned(), "ns6".to_owned()),
876 (AVRO_NAME_METADATA_KEY.to_owned(), "record6".to_owned()),
877 ])),
878 )),
879 false,
880 ),
881 ])
882 .with_metadata(HashMap::from([(
883 SCHEMA_METADATA_KEY.into(),
884 r#"{
885 "type": "record",
886 "namespace": "ns1",
887 "name": "record1",
888 "fields": [
889 {
890 "name": "f1",
891 "type": {
892 "type": "record",
893 "namespace": "ns2",
894 "name": "record2",
895 "fields": [
896 {
897 "name": "f1_1",
898 "type": "string"
899 },
900 {
901 "name": "f1_2",
902 "type": "int"
903 },
904 {
905 "name": "f1_3",
906 "type": {
907 "type": "record",
908 "namespace": "ns3",
909 "name": "record3",
910 "fields": [
911 {
912 "name": "f1_3_1",
913 "type": "double"
914 }
915 ]
916 }
917 }
918 ]
919 }
920 },
921 {
922 "name": "f2",
923 "type": {
924 "type": "array",
925 "items": {
926 "type": "record",
927 "namespace": "ns4",
928 "name": "record4",
929 "fields": [
930 {
931 "name": "f2_1",
932 "type": "boolean"
933 },
934 {
935 "name": "f2_2",
936 "type": "float"
937 }
938 ]
939 }
940 }
941 },
942 {
943 "name": "f3",
944 "type": [
945 "null",
946 {
947 "type": "record",
948 "namespace": "ns5",
949 "name": "record5",
950 "fields": [
951 {
952 "name": "f3_1",
953 "type": "string"
954 }
955 ]
956 }
957 ],
958 "default": null
959 },
960 {
961 "name": "f4",
962 "type": {
963 "type": "array",
964 "items": [
965 "null",
966 {
967 "type": "record",
968 "namespace": "ns6",
969 "name": "record6",
970 "fields": [
971 {
972 "name": "f4_1",
973 "type": "long"
974 }
975 ]
976 }
977 ]
978 }
979 }
980 ]
981}
982"#
983 .into(),
984 )]));
985
986 Arc::new(schema)
987 }
988
989 async fn read_async_file(
990 path: &str,
991 batch_size: usize,
992 range: Option<Range<u64>>,
993 schema: Option<SchemaRef>,
994 projection: Option<Vec<usize>>,
995 ) -> Result<Vec<RecordBatch>, ArrowError> {
996 let store: Arc<dyn ObjectStore> = Arc::new(LocalFileSystem::new());
997 let location = Path::from_filesystem_path(path).unwrap();
998
999 let file_size = store.head(&location).await.unwrap().size;
1000
1001 let file_reader = ObjectStoreReader::new(store, location);
1002 let mut builder = AsyncAvroFileReader::builder(file_reader, file_size, batch_size);
1003
1004 if let Some(s) = schema {
1005 let reader_schema = AvroSchema::try_from(s.as_ref())?;
1006 builder = builder.with_reader_schema(reader_schema);
1007 }
1008
1009 if let Some(proj) = projection {
1010 builder = builder.with_projection(proj);
1011 }
1012
1013 if let Some(range) = range {
1014 builder = builder.with_range(range);
1015 }
1016
1017 let reader = builder.try_build().await?;
1018 reader.try_collect().await
1019 }
1020
1021 #[tokio::test]
1022 async fn test_full_file_read() {
1023 let file = arrow_test_data("avro/alltypes_plain.avro");
1024 let schema = get_alltypes_schema();
1025 let batches = read_async_file(&file, 1024, None, Some(schema), None)
1026 .await
1027 .unwrap();
1028 let batch = &batches[0];
1029
1030 assert_eq!(batch.num_rows(), 8);
1031 assert_eq!(batch.num_columns(), 11);
1032
1033 let id_array = batch
1034 .column(0)
1035 .as_any()
1036 .downcast_ref::<Int32Array>()
1037 .unwrap();
1038 assert_eq!(id_array.value(0), 4);
1039 assert_eq!(id_array.value(7), 1);
1040 }
1041
1042 #[tokio::test]
1043 async fn test_small_batch_size() {
1044 let file = arrow_test_data("avro/alltypes_plain.avro");
1045 let schema = get_alltypes_schema();
1046 let batches = read_async_file(&file, 2, None, Some(schema), None)
1047 .await
1048 .unwrap();
1049 assert_eq!(batches.len(), 4);
1050
1051 let batch = &batches[0];
1052
1053 assert_eq!(batch.num_rows(), 2);
1054 assert_eq!(batch.num_columns(), 11);
1055 }
1056
1057 #[tokio::test]
1058 async fn test_batch_size_one() {
1059 let file = arrow_test_data("avro/alltypes_plain.avro");
1060 let schema = get_alltypes_schema();
1061 let batches = read_async_file(&file, 1, None, Some(schema), None)
1062 .await
1063 .unwrap();
1064 let batch = &batches[0];
1065
1066 assert_eq!(batches.len(), 8);
1067 assert_eq!(batch.num_rows(), 1);
1068 }
1069
1070 #[tokio::test]
1071 async fn test_batch_size_larger_than_file() {
1072 let file = arrow_test_data("avro/alltypes_plain.avro");
1073 let schema = get_alltypes_schema();
1074 let batches = read_async_file(&file, 10000, None, Some(schema), None)
1075 .await
1076 .unwrap();
1077 let batch = &batches[0];
1078
1079 assert_eq!(batch.num_rows(), 8);
1080 }
1081
1082 #[tokio::test]
1083 async fn test_empty_range() {
1084 let file = arrow_test_data("avro/alltypes_plain.avro");
1085 let range = 100..100;
1086 let schema = get_alltypes_schema();
1087 let batches = read_async_file(&file, 1024, Some(range), Some(schema), None)
1088 .await
1089 .unwrap();
1090 assert_eq!(batches.len(), 0);
1091 }
1092
1093 #[tokio::test]
1094 async fn test_range_starting_at_zero() {
1095 let file = arrow_test_data("avro/alltypes_plain.avro");
1097 let store = Arc::new(LocalFileSystem::new());
1098 let location = Path::from_filesystem_path(&file).unwrap();
1099 let meta = store.head(&location).await.unwrap();
1100
1101 let range = 0..meta.size;
1102 let schema = get_alltypes_schema();
1103 let batches = read_async_file(&file, 1024, Some(range), Some(schema), None)
1104 .await
1105 .unwrap();
1106 let batch = &batches[0];
1107
1108 assert_eq!(batch.num_rows(), 8);
1109 }
1110
1111 #[tokio::test]
1112 async fn test_range_after_header() {
1113 let file = arrow_test_data("avro/alltypes_plain.avro");
1114 let store = Arc::new(LocalFileSystem::new());
1115 let location = Path::from_filesystem_path(&file).unwrap();
1116 let meta = store.head(&location).await.unwrap();
1117
1118 let range = 100..meta.size;
1119 let schema = get_alltypes_schema();
1120 let batches = read_async_file(&file, 1024, Some(range), Some(schema), None)
1121 .await
1122 .unwrap();
1123 let batch = &batches[0];
1124
1125 assert!(batch.num_rows() > 0);
1126 }
1127
1128 #[tokio::test]
1129 async fn test_range_no_sync_marker() {
1130 let file = arrow_test_data("avro/alltypes_plain.avro");
1132 let range = 50..150;
1133 let schema = get_alltypes_schema();
1134 let batches = read_async_file(&file, 1024, Some(range), Some(schema), None)
1135 .await
1136 .unwrap();
1137 assert_eq!(batches.len(), 0);
1138 }
1139
1140 #[tokio::test]
1141 async fn test_range_starting_mid_file() {
1142 let file = arrow_test_data("avro/alltypes_plain.avro");
1143
1144 let range = 700..768; let schema = get_alltypes_schema();
1146 let batches = read_async_file(&file, 1024, Some(range), Some(schema), None)
1147 .await
1148 .unwrap();
1149 assert_eq!(batches.len(), 0);
1150 }
1151
1152 #[tokio::test]
1153 async fn test_range_ending_at_file_size() {
1154 let file = arrow_test_data("avro/alltypes_plain.avro");
1155 let store = Arc::new(LocalFileSystem::new());
1156 let location = Path::from_filesystem_path(&file).unwrap();
1157 let meta = store.head(&location).await.unwrap();
1158
1159 let range = 200..meta.size;
1160 let schema = get_alltypes_schema();
1161 let batches = read_async_file(&file, 1024, Some(range), Some(schema), None)
1162 .await
1163 .unwrap();
1164 let batch = &batches[0];
1165
1166 assert_eq!(batch.num_rows(), 8);
1167 }
1168
1169 #[tokio::test]
1170 async fn test_incomplete_block_requires_fetch() {
1171 let file = arrow_test_data("avro/alltypes_plain.avro");
1173 let range = 0..1200;
1174 let schema = get_alltypes_schema();
1175 let batches = read_async_file(&file, 1024, Some(range), Some(schema), None)
1176 .await
1177 .unwrap();
1178 let batch = &batches[0];
1179
1180 assert_eq!(batch.num_rows(), 8)
1181 }
1182
1183 #[tokio::test]
1184 async fn test_partial_vlq_header_requires_fetch() {
1185 let file = arrow_test_data("avro/alltypes_plain.avro");
1187 let range = 16..676; let schema = get_alltypes_schema();
1189 let batches = read_async_file(&file, 1024, Some(range), Some(schema), None)
1190 .await
1191 .unwrap();
1192 let batch = &batches[0];
1193
1194 assert_eq!(batch.num_rows(), 8)
1195 }
1196
1197 #[cfg(feature = "snappy")]
1198 #[tokio::test]
1199 async fn test_snappy_compressed_with_range() {
1200 {
1201 let file = arrow_test_data("avro/alltypes_plain.snappy.avro");
1202 let store = Arc::new(LocalFileSystem::new());
1203 let location = Path::from_filesystem_path(&file).unwrap();
1204 let meta = store.head(&location).await.unwrap();
1205
1206 let range = 200..meta.size;
1207 let schema = get_alltypes_schema();
1208 let batches = read_async_file(&file, 1024, Some(range), Some(schema), None)
1209 .await
1210 .unwrap();
1211 let batch = &batches[0];
1212
1213 assert!(batch.num_rows() > 0);
1214 }
1215 }
1216
1217 #[tokio::test]
1218 async fn test_nulls() {
1219 let file = arrow_test_data("avro/alltypes_nulls_plain.avro");
1220 let schema = get_alltypes_with_nulls_schema();
1221 let batches = read_async_file(&file, 1024, None, Some(schema), None)
1222 .await
1223 .unwrap();
1224 let batch = &batches[0];
1225
1226 assert_eq!(batch.num_rows(), 1);
1227 for col in batch.columns() {
1228 assert!(col.is_null(0));
1229 }
1230 }
1231
1232 #[tokio::test]
1233 async fn test_nested_records() {
1234 let file = arrow_test_data("avro/nested_records.avro");
1235 let schema = get_nested_records_schema();
1236 let batches = read_async_file(&file, 1024, None, Some(schema), None)
1237 .await
1238 .unwrap();
1239 let batch = &batches[0];
1240
1241 assert_eq!(batch.num_rows(), 2);
1242 assert!(batch.num_columns() > 0);
1243 }
1244
1245 #[tokio::test]
1246 async fn test_stream_produces_multiple_batches() {
1247 let file = arrow_test_data("avro/alltypes_plain.avro");
1248 let store = Arc::new(LocalFileSystem::new());
1249 let location = Path::from_filesystem_path(&file).unwrap();
1250
1251 let file_size = store.head(&location).await.unwrap().size;
1252
1253 let file_reader = ObjectStoreReader::new(store, location);
1254 let schema = get_alltypes_schema();
1255 let reader_schema = AvroSchema::try_from(schema.as_ref()).unwrap();
1256 let reader = AsyncAvroFileReader::builder(
1257 file_reader,
1258 file_size,
1259 2, )
1261 .with_reader_schema(reader_schema)
1262 .try_build()
1263 .await
1264 .unwrap();
1265
1266 let batches: Vec<RecordBatch> = reader.try_collect().await.unwrap();
1267
1268 assert!(batches.len() > 1);
1269 let total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
1270 assert_eq!(total_rows, 8);
1271 }
1272
1273 #[tokio::test]
1274 async fn test_stream_early_termination() {
1275 let file = arrow_test_data("avro/alltypes_plain.avro");
1276 let store = Arc::new(LocalFileSystem::new());
1277 let location = Path::from_filesystem_path(&file).unwrap();
1278
1279 let file_size = store.head(&location).await.unwrap().size;
1280
1281 let file_reader = ObjectStoreReader::new(store, location);
1282 let schema = get_alltypes_schema();
1283 let reader_schema = AvroSchema::try_from(schema.as_ref()).unwrap();
1284 let reader = AsyncAvroFileReader::builder(file_reader, file_size, 1)
1285 .with_reader_schema(reader_schema)
1286 .try_build()
1287 .await
1288 .unwrap();
1289
1290 let first_batch = reader.take(1).try_collect::<Vec<_>>().await.unwrap();
1291
1292 assert_eq!(first_batch.len(), 1);
1293 assert!(first_batch[0].num_rows() > 0);
1294 }
1295
1296 #[tokio::test]
1297 async fn test_various_batch_sizes() {
1298 let file = arrow_test_data("avro/alltypes_plain.avro");
1299
1300 for batch_size in [1, 2, 3, 5, 7, 11, 100] {
1301 let schema = get_alltypes_schema();
1302 let batches = read_async_file(&file, batch_size, None, Some(schema), None)
1303 .await
1304 .unwrap();
1305 let batch = &batches[0];
1306
1307 assert_eq!(
1309 batch.num_rows(),
1310 batch_size.min(8),
1311 "Failed with batch_size={}",
1312 batch_size
1313 );
1314 }
1315 }
1316
1317 #[tokio::test]
1318 async fn test_range_larger_than_file() {
1319 let file = arrow_test_data("avro/alltypes_plain.avro");
1320 let store = Arc::new(LocalFileSystem::new());
1321 let location = Path::from_filesystem_path(&file).unwrap();
1322 let meta = store.head(&location).await.unwrap();
1323
1324 let range = 100..(meta.size + 1000);
1326 let schema = get_alltypes_schema();
1327 let batches = read_async_file(&file, 1024, Some(range), Some(schema), None)
1328 .await
1329 .unwrap();
1330 let batch = &batches[0];
1331
1332 assert_eq!(batch.num_rows(), 8);
1334 }
1335
1336 #[tokio::test]
1337 async fn test_builder_with_header_info() {
1338 let file = arrow_test_data("avro/alltypes_plain.avro");
1339 let store = Arc::new(LocalFileSystem::new());
1340 let location = Path::from_filesystem_path(&file).unwrap();
1341
1342 let file_size = store.head(&location).await.unwrap().size;
1343
1344 let mut file_reader = ObjectStoreReader::new(store, location);
1345
1346 let header_info = read_header_info(&mut file_reader, file_size, None)
1347 .await
1348 .unwrap();
1349
1350 assert_eq!(header_info.header_len(), 675);
1351
1352 let writer_schema = header_info.writer_schema().unwrap();
1353 let expected_avro_json: serde_json::Value = serde_json::from_str(
1354 get_alltypes_schema()
1355 .metadata()
1356 .get(SCHEMA_METADATA_KEY)
1357 .unwrap(),
1358 )
1359 .unwrap();
1360 let actual_avro_json: serde_json::Value =
1361 serde_json::from_str(&writer_schema.json_string).unwrap();
1362 assert_eq!(actual_avro_json, expected_avro_json);
1363
1364 let reader = AsyncAvroFileReader::builder(file_reader, file_size, 1024)
1365 .build_with_header(header_info)
1366 .unwrap();
1367
1368 let batches: Vec<RecordBatch> = reader.try_collect().await.unwrap();
1369
1370 let batch = &batches[0];
1371 assert_eq!(batch.num_rows(), 8)
1372 }
1373
1374 #[tokio::test]
1375 async fn test_roundtrip_write_then_async_read() {
1376 use crate::writer::AvroWriter;
1377 use arrow_array::{Float64Array, StringArray};
1378 use std::fs::File;
1379 use std::io::BufWriter;
1380 use tempfile::tempdir;
1381
1382 let schema = Arc::new(Schema::new(vec![
1384 Field::new("id", DataType::Int32, false),
1385 Field::new("name", DataType::Utf8, true),
1386 Field::new("score", DataType::Float64, true),
1387 Field::new("count", DataType::Int64, false),
1388 ]));
1389
1390 let dir = tempdir().unwrap();
1391 let file_path = dir.path().join("roundtrip_test.avro");
1392
1393 {
1395 let file = File::create(&file_path).unwrap();
1396 let writer = BufWriter::new(file);
1397 let mut avro_writer = AvroWriter::new(writer, schema.as_ref().clone()).unwrap();
1398
1399 let batch1 = RecordBatch::try_new(
1401 schema.clone(),
1402 vec![
1403 Arc::new(Int32Array::from(vec![1, 2, 3])),
1404 Arc::new(StringArray::from(vec![
1405 Some("alice"),
1406 None,
1407 Some("charlie"),
1408 ])),
1409 Arc::new(Float64Array::from(vec![Some(95.5), Some(87.3), None])),
1410 Arc::new(Int64Array::from(vec![10, 20, 30])),
1411 ],
1412 )
1413 .unwrap();
1414 avro_writer.write(&batch1).unwrap();
1415
1416 let batch2 = RecordBatch::try_new(
1418 schema.clone(),
1419 vec![
1420 Arc::new(Int32Array::from(vec![4, 5])),
1421 Arc::new(StringArray::from(vec![Some("diana"), Some("eve")])),
1422 Arc::new(Float64Array::from(vec![None, Some(88.0)])),
1423 Arc::new(Int64Array::from(vec![40, 50])),
1424 ],
1425 )
1426 .unwrap();
1427 avro_writer.write(&batch2).unwrap();
1428
1429 avro_writer.finish().unwrap();
1430 }
1431
1432 let store: Arc<dyn ObjectStore> = Arc::new(LocalFileSystem::new());
1434 let location = Path::from_filesystem_path(&file_path).unwrap();
1435 let file_size = store.head(&location).await.unwrap().size;
1436
1437 let file_reader = ObjectStoreReader::new(store, location);
1438 let reader = AsyncAvroFileReader::builder(file_reader, file_size, 2)
1439 .try_build()
1440 .await
1441 .unwrap();
1442
1443 let batches: Vec<RecordBatch> = reader.try_collect().await.unwrap();
1444
1445 assert!(
1447 batches.len() > 1,
1448 "Expected multiple batches with batch_size=2"
1449 );
1450
1451 let total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
1453 assert_eq!(total_rows, 5);
1454
1455 let combined = arrow::compute::concat_batches(&batches[0].schema(), &batches).unwrap();
1457 assert_eq!(combined.num_rows(), 5);
1458 assert_eq!(combined.num_columns(), 4);
1459
1460 let id_array = combined
1462 .column(0)
1463 .as_any()
1464 .downcast_ref::<Int32Array>()
1465 .unwrap();
1466 assert_eq!(id_array.values(), &[1, 2, 3, 4, 5]);
1467
1468 let name_col = combined.column(1);
1471 let name_array = name_col.as_string::<i32>();
1472 assert_eq!(name_array.value(0), "alice");
1473 assert!(name_col.is_null(1)); assert_eq!(name_array.value(2), "charlie");
1475
1476 let score_array = combined
1478 .column(2)
1479 .as_any()
1480 .downcast_ref::<Float64Array>()
1481 .unwrap();
1482 assert!(!score_array.is_null(0));
1483 assert!((score_array.value(0) - 95.5).abs() < f64::EPSILON);
1484 assert!(score_array.is_null(2)); assert!(score_array.is_null(3)); assert!(!score_array.is_null(4));
1487 assert!((score_array.value(4) - 88.0).abs() < f64::EPSILON);
1488
1489 let count_array = combined
1491 .column(3)
1492 .as_any()
1493 .downcast_ref::<Int64Array>()
1494 .unwrap();
1495 assert_eq!(count_array.values(), &[10, 20, 30, 40, 50]);
1496 }
1497
1498 #[tokio::test]
1499 async fn test_alltypes_no_schema_no_projection() {
1500 let file = arrow_test_data("avro/alltypes_plain.avro");
1502 let batches = read_async_file(&file, 1024, None, None, None)
1503 .await
1504 .unwrap();
1505 let batch = &batches[0];
1506
1507 assert_eq!(batch.num_rows(), 8);
1508 assert_eq!(batch.num_columns(), 11);
1509 assert_eq!(batch.schema().field(0).name(), "id");
1510 }
1511
1512 #[tokio::test]
1513 async fn test_alltypes_no_schema_with_projection() {
1514 let file = arrow_test_data("avro/alltypes_plain.avro");
1516 let batches = read_async_file(&file, 1024, None, None, Some(vec![2, 0, 5]))
1518 .await
1519 .unwrap();
1520 let batch = &batches[0];
1521
1522 assert_eq!(batch.num_rows(), 8);
1523 assert_eq!(batch.num_columns(), 3);
1524 assert_eq!(batch.schema().field(0).name(), "tinyint_col");
1525 assert_eq!(batch.schema().field(1).name(), "id");
1526 assert_eq!(batch.schema().field(2).name(), "bigint_col");
1527
1528 let tinyint_col = batch.column(0).as_primitive::<Int32Type>();
1530 assert_eq!(tinyint_col.values(), &[0, 1, 0, 1, 0, 1, 0, 1]);
1531
1532 let id = batch.column(1).as_primitive::<Int32Type>();
1533 assert_eq!(id.values(), &[4, 5, 6, 7, 2, 3, 0, 1]);
1534
1535 let bigint_col = batch.column(2).as_primitive::<Int64Type>();
1536 assert_eq!(bigint_col.values(), &[0, 10, 0, 10, 0, 10, 0, 10]);
1537 }
1538
1539 #[tokio::test]
1540 async fn test_alltypes_with_schema_no_projection() {
1541 let file = arrow_test_data("avro/alltypes_plain.avro");
1543 let schema = get_alltypes_schema();
1544 let batches = read_async_file(&file, 1024, None, Some(schema), None)
1545 .await
1546 .unwrap();
1547 let batch = &batches[0];
1548
1549 assert_eq!(batch.num_rows(), 8);
1550 assert_eq!(batch.num_columns(), 11);
1551 }
1552
1553 #[tokio::test]
1554 async fn test_alltypes_with_schema_with_projection() {
1555 let file = arrow_test_data("avro/alltypes_plain.avro");
1557 let schema = get_alltypes_schema();
1558 let batches = read_async_file(&file, 1024, None, Some(schema), Some(vec![1, 0]))
1560 .await
1561 .unwrap();
1562 let batch = &batches[0];
1563
1564 assert_eq!(batch.num_rows(), 8);
1565 assert_eq!(batch.num_columns(), 2);
1566 assert_eq!(batch.schema().field(0).name(), "bool_col");
1567 assert_eq!(batch.schema().field(1).name(), "id");
1568
1569 let bool_col = batch.column(0).as_boolean();
1570 assert!(bool_col.value(0));
1571 assert!(!bool_col.value(1));
1572
1573 let id = batch.column(1).as_primitive::<Int32Type>();
1574 assert_eq!(id.values(), &[4, 5, 6, 7, 2, 3, 0, 1]);
1575 }
1576
1577 #[tokio::test]
1578 async fn test_alltypes_with_empty_schema_large_batch() {
1579 let file = arrow_test_data("avro/alltypes_plain.avro");
1581 let schema = Arc::new(Schema::new(Vec::<Field>::new()));
1582 let batches = read_async_file(&file, 1024, None, Some(schema), None)
1583 .await
1584 .unwrap();
1585 assert_eq!(batches.len(), 1);
1586 let batch = &batches[0];
1587
1588 assert_eq!(batch.num_rows(), 8);
1589 assert_eq!(batch.num_columns(), 0);
1590 }
1591
1592 #[tokio::test]
1593 async fn test_alltypes_with_empty_schema_small_batch() {
1594 let file = arrow_test_data("avro/alltypes_plain.avro");
1596 let schema = Arc::new(Schema::new(Vec::<Field>::new()));
1597 let batches = read_async_file(&file, 5, None, Some(schema), None)
1598 .await
1599 .unwrap();
1600
1601 assert_eq!(batches.len(), 2);
1602
1603 assert_eq!(batches[0].num_rows(), 5);
1604 assert_eq!(batches[0].num_columns(), 0);
1605 assert_eq!(batches[1].num_rows(), 3);
1606 assert_eq!(batches[1].num_columns(), 0);
1607 }
1608
1609 #[tokio::test]
1610 async fn test_nested_no_schema_no_projection() {
1611 let file = arrow_test_data("avro/nested_records.avro");
1613 let batches = read_async_file(&file, 1024, None, None, None)
1614 .await
1615 .unwrap();
1616 let batch = &batches[0];
1617
1618 assert_eq!(batch.num_rows(), 2);
1619 assert_eq!(batch.num_columns(), 4);
1620 assert_eq!(batch.schema().field(0).name(), "f1");
1621 assert_eq!(batch.schema().field(1).name(), "f2");
1622 assert_eq!(batch.schema().field(2).name(), "f3");
1623 assert_eq!(batch.schema().field(3).name(), "f4");
1624 }
1625
1626 #[tokio::test]
1627 async fn test_nested_no_schema_with_projection() {
1628 let file = arrow_test_data("avro/nested_records.avro");
1630 let batches = read_async_file(&file, 1024, None, None, Some(vec![2, 0]))
1632 .await
1633 .unwrap();
1634 let batch = &batches[0];
1635
1636 assert_eq!(batch.num_rows(), 2);
1637 assert_eq!(batch.num_columns(), 2);
1638 assert_eq!(batch.schema().field(0).name(), "f3");
1639 assert_eq!(batch.schema().field(1).name(), "f1");
1640 }
1641
1642 #[tokio::test]
1643 async fn test_nested_with_schema_no_projection() {
1644 let file = arrow_test_data("avro/nested_records.avro");
1646 let schema = get_nested_records_schema();
1647 let batches = read_async_file(&file, 1024, None, Some(schema), None)
1648 .await
1649 .unwrap();
1650 let batch = &batches[0];
1651
1652 assert_eq!(batch.num_rows(), 2);
1653 assert_eq!(batch.num_columns(), 4);
1654 }
1655
1656 #[tokio::test]
1657 async fn test_nested_with_schema_with_projection() {
1658 let file = arrow_test_data("avro/nested_records.avro");
1660 let schema = get_nested_records_schema();
1661 let batches = read_async_file(&file, 1024, None, Some(schema), Some(vec![3, 1, 0]))
1663 .await
1664 .unwrap();
1665 let batch = &batches[0];
1666
1667 assert_eq!(batch.num_rows(), 2);
1668 assert_eq!(batch.num_columns(), 3);
1669 assert_eq!(batch.schema().field(0).name(), "f4");
1670 assert_eq!(batch.schema().field(1).name(), "f2");
1671 assert_eq!(batch.schema().field(2).name(), "f1");
1672 }
1673
1674 #[tokio::test]
1675 async fn test_nested_with_empty_schema() {
1676 let file = arrow_test_data("avro/nested_records.avro");
1678 let schema = Arc::new(
1679 Schema::new(Vec::<Field>::new()).with_metadata(HashMap::from([(
1680 SCHEMA_METADATA_KEY.into(),
1681 r#"{
1682 "type": "record",
1683 "namespace": "ns1",
1684 "name": "record1",
1685 "fields": []
1686 }"#
1687 .to_owned(),
1688 )])),
1689 );
1690 let batches = read_async_file(&file, 1024, None, Some(schema), None)
1691 .await
1692 .unwrap();
1693 let batch = &batches[0];
1694
1695 assert_eq!(batch.num_rows(), 2);
1696 assert_eq!(batch.num_columns(), 0);
1697 }
1698
1699 #[tokio::test]
1700 async fn test_projection_error_out_of_bounds() {
1701 let file = arrow_test_data("avro/alltypes_plain.avro");
1702 let err = read_async_file(&file, 1024, None, None, Some(vec![100]))
1704 .await
1705 .unwrap_err();
1706 assert!(matches!(err, ArrowError::AvroError(_)));
1707 assert!(err.to_string().contains("out of bounds"));
1708 }
1709
1710 #[tokio::test]
1711 async fn test_projection_error_duplicate_index() {
1712 let file = arrow_test_data("avro/alltypes_plain.avro");
1713 let err = read_async_file(&file, 1024, None, None, Some(vec![0, 0]))
1715 .await
1716 .unwrap_err();
1717 assert!(matches!(err, ArrowError::AvroError(_)));
1718 assert!(err.to_string().contains("Duplicate projection index"));
1719 }
1720
1721 #[tokio::test]
1722 async fn test_arrow_schema_from_reader_no_reader_schema() {
1723 let file = arrow_test_data("avro/alltypes_plain.avro");
1724 let store: Arc<dyn ObjectStore> = Arc::new(LocalFileSystem::new());
1725 let location = Path::from_filesystem_path(&file).unwrap();
1726 let file_size = store.head(&location).await.unwrap().size;
1727
1728 let file_reader = ObjectStoreReader::new(store, location);
1729 let expected_schema = get_alltypes_schema()
1730 .as_ref()
1731 .clone()
1732 .with_metadata(Default::default());
1733
1734 let reader = AsyncAvroFileReader::builder(file_reader, file_size, 1024)
1736 .try_build()
1737 .await
1738 .unwrap();
1739
1740 assert_eq!(reader.schema().as_ref(), &expected_schema);
1741
1742 let batches: Vec<RecordBatch> = reader.try_collect().await.unwrap();
1743 let batch = &batches[0];
1744
1745 assert_eq!(batch.schema().as_ref(), &expected_schema);
1746 }
1747
1748 #[tokio::test]
1749 async fn test_arrow_schema_from_reader_with_reader_schema() {
1750 let file = arrow_test_data("avro/alltypes_plain.avro");
1751 let store: Arc<dyn ObjectStore> = Arc::new(LocalFileSystem::new());
1752 let location = Path::from_filesystem_path(&file).unwrap();
1753 let file_size = store.head(&location).await.unwrap().size;
1754
1755 let file_reader = ObjectStoreReader::new(store, location);
1756 let schema = get_alltypes_schema()
1757 .project(&[0, 1, 7])
1758 .unwrap()
1759 .with_metadata(Default::default());
1760 let reader_schema = AvroSchema::try_from(&schema).unwrap();
1761 let expected_schema = schema.clone();
1762
1763 let reader = AsyncAvroFileReader::builder(file_reader, file_size, 1024)
1765 .with_reader_schema(reader_schema)
1766 .try_build()
1767 .await
1768 .unwrap();
1769
1770 assert_eq!(reader.schema().as_ref(), &expected_schema);
1771
1772 let batches: Vec<RecordBatch> = reader.try_collect().await.unwrap();
1773 let batch = &batches[0];
1774
1775 assert_eq!(batch.schema().as_ref(), &expected_schema);
1776 }
1777
1778 #[tokio::test]
1779 async fn test_arrow_schema_from_reader_nested_records() {
1780 let file = arrow_test_data("avro/nested_records.avro");
1781 let store: Arc<dyn ObjectStore> = Arc::new(LocalFileSystem::new());
1782 let location = Path::from_filesystem_path(&file).unwrap();
1783 let file_size = store.head(&location).await.unwrap().size;
1784
1785 let file_reader = ObjectStoreReader::new(store, location);
1786
1787 let expected_schema = get_nested_records_schema()
1790 .as_ref()
1791 .clone()
1792 .with_metadata(Default::default());
1793
1794 let reader = AsyncAvroFileReader::builder(file_reader, file_size, 1024)
1795 .try_build()
1796 .await
1797 .unwrap();
1798
1799 assert_eq!(reader.schema().as_ref(), &expected_schema);
1800
1801 let batches: Vec<RecordBatch> = reader.try_collect().await.unwrap();
1802 let batch = &batches[0];
1803
1804 assert_eq!(batch.schema().as_ref(), &expected_schema);
1805 }
1806
1807 #[tokio::test]
1808 async fn test_with_header_size_hint_small() {
1809 let file = arrow_test_data("avro/alltypes_plain.avro");
1811 let store: Arc<dyn ObjectStore> = Arc::new(LocalFileSystem::new());
1812 let location = Path::from_filesystem_path(&file).unwrap();
1813 let file_size = store.head(&location).await.unwrap().size;
1814
1815 let file_reader = ObjectStoreReader::new(store, location);
1816 let schema = get_alltypes_schema();
1817 let reader_schema = AvroSchema::try_from(schema.as_ref()).unwrap();
1818
1819 let reader = AsyncAvroFileReader::builder(file_reader, file_size, 1024)
1821 .with_reader_schema(reader_schema)
1822 .with_header_size_hint(64)
1823 .try_build()
1824 .await
1825 .unwrap();
1826
1827 let batches: Vec<RecordBatch> = reader.try_collect().await.unwrap();
1828 let batch = &batches[0];
1829
1830 assert_eq!(batch.num_rows(), 8);
1831 assert_eq!(batch.num_columns(), 11);
1832 }
1833
1834 #[tokio::test]
1835 async fn test_with_header_size_hint_large() {
1836 let file = arrow_test_data("avro/alltypes_plain.avro");
1838 let store: Arc<dyn ObjectStore> = Arc::new(LocalFileSystem::new());
1839 let location = Path::from_filesystem_path(&file).unwrap();
1840 let file_size = store.head(&location).await.unwrap().size;
1841
1842 let file_reader = ObjectStoreReader::new(store, location);
1843 let schema = get_alltypes_schema();
1844 let reader_schema = AvroSchema::try_from(schema.as_ref()).unwrap();
1845
1846 let reader = AsyncAvroFileReader::builder(file_reader, file_size, 1024)
1848 .with_reader_schema(reader_schema)
1849 .with_header_size_hint(64 * 1024)
1850 .try_build()
1851 .await
1852 .unwrap();
1853
1854 let batches: Vec<RecordBatch> = reader.try_collect().await.unwrap();
1855 let batch = &batches[0];
1856
1857 assert_eq!(batch.num_rows(), 8);
1858 assert_eq!(batch.num_columns(), 11);
1859 }
1860
1861 #[tokio::test]
1862 async fn test_with_tz_utc() {
1863 let file = arrow_test_data("avro/alltypes_plain.avro");
1864 let store: Arc<dyn ObjectStore> = Arc::new(LocalFileSystem::new());
1865 let location = Path::from_filesystem_path(&file).unwrap();
1866 let file_size = store.head(&location).await.unwrap().size;
1867
1868 let file_reader = ObjectStoreReader::new(store, location);
1869 let schema = get_alltypes_schema_with_tz("UTC");
1870 let reader_schema = AvroSchema::try_from(schema.as_ref()).unwrap();
1871
1872 let reader = AsyncAvroFileReader::builder(file_reader, file_size, 1024)
1874 .with_reader_schema(reader_schema)
1875 .with_tz(Tz::Utc)
1876 .try_build()
1877 .await
1878 .unwrap();
1879
1880 let batches: Vec<RecordBatch> = reader.try_collect().await.unwrap();
1881 let batch = &batches[0];
1882
1883 assert_eq!(batch.num_columns(), 11);
1884
1885 let schema = batch.schema();
1886 let ts_field = schema.field_with_name("timestamp_col").unwrap();
1887 assert!(
1888 matches!(
1889 ts_field.data_type(),
1890 DataType::Timestamp(TimeUnit::Microsecond, Some(tz)) if tz.as_ref() == "UTC"
1891 ),
1892 "expected Timestamp(Microsecond, Some(\"UTC\")), got {:?}",
1893 ts_field.data_type()
1894 );
1895 }
1896
1897 #[tokio::test]
1898 async fn test_with_utf8_view_enabled() {
1899 let file = arrow_test_data("avro/nested_records.avro");
1901 let store: Arc<dyn ObjectStore> = Arc::new(LocalFileSystem::new());
1902 let location = Path::from_filesystem_path(&file).unwrap();
1903 let file_size = store.head(&location).await.unwrap().size;
1904
1905 let file_reader = ObjectStoreReader::new(store, location);
1906
1907 let reader = AsyncAvroFileReader::builder(file_reader, file_size, 1024)
1908 .with_utf8_view(true)
1909 .try_build()
1910 .await
1911 .unwrap();
1912
1913 let batches: Vec<RecordBatch> = reader.try_collect().await.unwrap();
1914 let batch = &batches[0];
1915
1916 assert_eq!(batch.num_rows(), 2);
1917
1918 let f1_col = batch.column(0);
1921 let f1_struct = f1_col.as_struct();
1922 let f1_1_field = f1_struct.column_by_name("f1_1").unwrap();
1923
1924 assert_eq!(f1_1_field.data_type(), &DataType::Utf8View);
1926 }
1927
1928 #[tokio::test]
1929 async fn test_with_utf8_view_disabled() {
1930 let file = arrow_test_data("avro/nested_records.avro");
1932 let store: Arc<dyn ObjectStore> = Arc::new(LocalFileSystem::new());
1933 let location = Path::from_filesystem_path(&file).unwrap();
1934 let file_size = store.head(&location).await.unwrap().size;
1935
1936 let file_reader = ObjectStoreReader::new(store, location);
1937
1938 let reader = AsyncAvroFileReader::builder(file_reader, file_size, 1024)
1939 .with_utf8_view(false)
1940 .try_build()
1941 .await
1942 .unwrap();
1943
1944 let batches: Vec<RecordBatch> = reader.try_collect().await.unwrap();
1945 let batch = &batches[0];
1946
1947 assert_eq!(batch.num_rows(), 2);
1948
1949 let f1_col = batch.column(0);
1952 let f1_struct = f1_col.as_struct();
1953 let f1_1_field = f1_struct.column_by_name("f1_1").unwrap();
1954
1955 assert_eq!(f1_1_field.data_type(), &DataType::Utf8);
1956 }
1957
1958 #[tokio::test]
1959 async fn test_with_strict_mode_disabled_allows_null_second() {
1960 let file = arrow_test_data("avro/alltypes_nulls_plain.avro");
1963 let store: Arc<dyn ObjectStore> = Arc::new(LocalFileSystem::new());
1964 let location = Path::from_filesystem_path(&file).unwrap();
1965 let file_size = store.head(&location).await.unwrap().size;
1966
1967 let file_reader = ObjectStoreReader::new(store, location);
1968
1969 let reader = AsyncAvroFileReader::builder(file_reader, file_size, 1024)
1971 .with_strict_mode(false)
1972 .try_build()
1973 .await
1974 .unwrap();
1975
1976 let batches: Vec<RecordBatch> = reader.try_collect().await.unwrap();
1977 assert_eq!(batches.len(), 1);
1978 assert_eq!(batches[0].num_rows(), 1);
1979 }
1980
1981 #[tokio::test]
1982 async fn test_with_strict_mode_enabled_rejects_null_second() {
1983 let file = arrow_test_data("avro/alltypes_plain.avro");
1986 let store: Arc<dyn ObjectStore> = Arc::new(LocalFileSystem::new());
1987 let location = Path::from_filesystem_path(&file).unwrap();
1988 let file_size = store.head(&location).await.unwrap().size;
1989
1990 let file_reader = ObjectStoreReader::new(store, location);
1991
1992 let result = AsyncAvroFileReader::builder(file_reader, file_size, 1024)
1994 .with_strict_mode(true)
1995 .try_build()
1996 .await;
1997
1998 match result {
1999 Ok(_) => panic!("Expected error for strict_mode with ['T', 'null'] union"),
2000 Err(err) => {
2001 assert!(
2002 err.to_string().contains("disallowed in strict_mode"),
2003 "Expected strict_mode error, got: {}",
2004 err
2005 );
2006 }
2007 }
2008 }
2009
2010 #[tokio::test]
2011 async fn test_with_strict_mode_enabled_valid_schema() {
2012 let file = arrow_test_data("avro/nested_records.avro");
2015 let store: Arc<dyn ObjectStore> = Arc::new(LocalFileSystem::new());
2016 let location = Path::from_filesystem_path(&file).unwrap();
2017 let file_size = store.head(&location).await.unwrap().size;
2018
2019 let file_reader = ObjectStoreReader::new(store, location);
2020
2021 let reader = AsyncAvroFileReader::builder(file_reader, file_size, 1024)
2023 .with_strict_mode(true)
2024 .try_build()
2025 .await
2026 .unwrap();
2027
2028 let batches: Vec<RecordBatch> = reader.try_collect().await.unwrap();
2029 assert_eq!(batches.len(), 1);
2030 assert_eq!(batches[0].num_rows(), 2);
2031 }
2032
2033 #[tokio::test]
2034 async fn test_builder_options_combined() {
2035 let file = arrow_test_data("avro/nested_records.avro");
2037 let store: Arc<dyn ObjectStore> = Arc::new(LocalFileSystem::new());
2038 let location = Path::from_filesystem_path(&file).unwrap();
2039 let file_size = store.head(&location).await.unwrap().size;
2040
2041 let file_reader = ObjectStoreReader::new(store, location);
2042
2043 let reader = AsyncAvroFileReader::builder(file_reader, file_size, 2)
2044 .with_header_size_hint(128)
2045 .with_utf8_view(true)
2046 .with_strict_mode(true)
2047 .with_projection(vec![0, 2]) .try_build()
2049 .await
2050 .unwrap();
2051
2052 let batches: Vec<RecordBatch> = reader.try_collect().await.unwrap();
2053 let batch = &batches[0];
2054
2055 assert_eq!(batch.num_columns(), 2);
2057 assert_eq!(batch.schema().field(0).name(), "f1");
2058 assert_eq!(batch.schema().field(1).name(), "f3");
2059
2060 let f1_col = batch.column(0);
2062 let f1_struct = f1_col.as_struct();
2063 let f1_1_field = f1_struct.column_by_name("f1_1").unwrap();
2064 assert_eq!(f1_1_field.data_type(), &DataType::Utf8View);
2065 }
2066}