@@ -28,6 +28,7 @@ use async_trait::async_trait;
2828use bytes:: Bytes ;
2929use futures:: StreamExt ;
3030use paimon_mosaic_core:: reader:: { InputFile , MosaicReader , ReaderAccess } ;
31+ use std:: collections:: HashSet ;
3132use std:: io;
3233
3334pub ( crate ) struct MosaicFormatReader ;
@@ -46,16 +47,28 @@ impl FormatFileReader for MosaicFormatReader {
4647 row_selection : Option < Vec < RowRange > > ,
4748 ) -> crate :: Result < ArrowRecordBatchStream > {
4849 // Mosaic predicates are currently residual; callers must re-check them for exact filtering.
49- let target_schema = build_target_arrow_schema ( read_fields) ?;
50- validate_mosaic_schema ( & target_schema) ?;
51-
5250 let file_bytes = reader. read ( 0 ..file_size) . await ?;
5351 let mosaic_reader = MosaicReader :: new ( MemoryInputFile :: new ( file_bytes) , file_size)
5452 . map_err ( mosaic_read_error) ?;
55- let projected_names = read_fields
53+
54+ let file_column_names = mosaic_reader
55+ . schema ( )
56+ . columns
57+ . iter ( )
58+ . map ( |column| column. name . as_str ( ) )
59+ . collect :: < HashSet < _ > > ( ) ;
60+ let existing_read_fields = read_fields
61+ . iter ( )
62+ . filter ( |field| file_column_names. contains ( field. name ( ) ) )
63+ . cloned ( )
64+ . collect :: < Vec < _ > > ( ) ;
65+ let read_schema = build_target_arrow_schema ( & existing_read_fields) ?;
66+ validate_mosaic_schema ( & read_schema) ?;
67+ let projected_names = existing_read_fields
5668 . iter ( )
5769 . map ( |field| field. name ( ) . to_string ( ) )
5870 . collect :: < Vec < _ > > ( ) ;
71+ let all_projected_columns_missing = !read_fields. is_empty ( ) && projected_names. is_empty ( ) ;
5972 let batch_size = batch_size. unwrap_or ( DEFAULT_BATCH_SIZE ) ;
6073
6174 Ok ( try_stream ! {
@@ -82,24 +95,25 @@ impl FormatFileReader for MosaicFormatReader {
8295 }
8396 }
8497
85- let mut row_group_reader = if projected_names. is_empty( ) {
86- mosaic_reader
87- . row_group_reader_by_names( row_group_index, & [ ] )
88- . map_err( mosaic_read_error) ?
98+ let batch = if all_projected_columns_missing {
99+ let row_count = selected_indices
100+ . as_ref( )
101+ . map_or( row_group_rows, UInt64Array :: len) ;
102+ empty_batch( read_schema. clone( ) , row_count) ?
89103 } else {
90104 let names = projected_names
91105 . iter( )
92106 . map( String :: as_str)
93107 . collect:: <Vec <_>>( ) ;
94- mosaic_reader
108+ let mut row_group_reader = mosaic_reader
95109 . row_group_reader_by_names( row_group_index, & names)
96- . map_err( mosaic_read_error) ?
97- } ;
110+ . map_err( mosaic_read_error) ?;
98111
99- let batch = row_group_reader
100- . read_columns( )
101- . map_err( mosaic_read_error) ?;
102- let batch = take_rows( batch, selected_indices. as_ref( ) , & target_schema) ?;
112+ let batch = row_group_reader
113+ . read_columns( )
114+ . map_err( mosaic_read_error) ?;
115+ take_rows( batch, selected_indices. as_ref( ) , & read_schema) ?
116+ } ;
103117 for chunk in split_batch( batch, batch_size) {
104118 yield chunk;
105119 }
@@ -251,15 +265,7 @@ fn take_rows(
251265 } ;
252266
253267 if batch. num_columns ( ) == 0 {
254- return RecordBatch :: try_new_with_options (
255- target_schema. clone ( ) ,
256- Vec :: new ( ) ,
257- & RecordBatchOptions :: new ( ) . with_row_count ( Some ( indices. len ( ) ) ) ,
258- )
259- . map_err ( |e| Error :: UnexpectedError {
260- message : format ! ( "Failed to build empty Mosaic RecordBatch: {e}" ) ,
261- source : Some ( Box :: new ( e) ) ,
262- } ) ;
268+ return empty_batch ( target_schema. clone ( ) , indices. len ( ) ) ;
263269 }
264270
265271 let columns = batch
@@ -287,15 +293,7 @@ fn ensure_schema(batch: RecordBatch, target_schema: &SchemaRef) -> crate::Result
287293 }
288294
289295 if batch. num_columns ( ) == 0 {
290- return RecordBatch :: try_new_with_options (
291- target_schema. clone ( ) ,
292- Vec :: new ( ) ,
293- & RecordBatchOptions :: new ( ) . with_row_count ( Some ( batch. num_rows ( ) ) ) ,
294- )
295- . map_err ( |e| Error :: UnexpectedError {
296- message : format ! ( "Failed to build empty Mosaic RecordBatch: {e}" ) ,
297- source : Some ( Box :: new ( e) ) ,
298- } ) ;
296+ return empty_batch ( target_schema. clone ( ) , batch. num_rows ( ) ) ;
299297 }
300298
301299 RecordBatch :: try_new ( target_schema. clone ( ) , batch. columns ( ) . to_vec ( ) ) . map_err ( |e| {
@@ -306,6 +304,18 @@ fn ensure_schema(batch: RecordBatch, target_schema: &SchemaRef) -> crate::Result
306304 } )
307305}
308306
307+ fn empty_batch ( schema : SchemaRef , row_count : usize ) -> crate :: Result < RecordBatch > {
308+ RecordBatch :: try_new_with_options (
309+ schema,
310+ Vec :: new ( ) ,
311+ & RecordBatchOptions :: new ( ) . with_row_count ( Some ( row_count) ) ,
312+ )
313+ . map_err ( |e| Error :: UnexpectedError {
314+ message : format ! ( "Failed to build empty Mosaic RecordBatch: {e}" ) ,
315+ source : Some ( Box :: new ( e) ) ,
316+ } )
317+ }
318+
309319fn split_batch ( batch : RecordBatch , batch_size : usize ) -> Vec < RecordBatch > {
310320 if batch_size == 0 || batch. num_rows ( ) <= batch_size {
311321 return vec ! [ batch] ;
@@ -400,6 +410,10 @@ mod tests {
400410 ]
401411 }
402412
413+ fn field ( id : i32 , name : & str , data_type : DataType ) -> DataField {
414+ DataField :: new ( id, name. to_string ( ) , data_type)
415+ }
416+
403417 fn arrow_schema ( ) -> SchemaRef {
404418 Arc :: new ( Schema :: new ( vec ! [
405419 Field :: new( "id" , ArrowDataType :: Int32 , false ) ,
@@ -526,32 +540,128 @@ mod tests {
526540 }
527541
528542 #[ tokio:: test]
529- async fn test_unsupported_type_returns_error ( ) {
530- let unsupported = vec ! [ DataField :: new(
543+ async fn test_read_projection_with_missing_column ( ) {
544+ let fields = data_fields ( ) ;
545+ let projected = vec ! [
546+ fields[ 0 ] . clone( ) ,
547+ field( 3 , "new_score" , DataType :: Int ( IntType :: with_nullable( true ) ) ) ,
548+ fields[ 1 ] . clone( ) ,
549+ ] ;
550+ let data = write_mosaic ( & sample_batch ( ) ) ;
551+ let batches = read_batches ( data, & projected, None ) . await . unwrap ( ) ;
552+
553+ assert_eq ! ( batches. len( ) , 1 ) ;
554+ assert_eq ! ( batches[ 0 ] . num_rows( ) , 5 ) ;
555+ assert_eq ! ( batches[ 0 ] . num_columns( ) , 2 ) ;
556+ assert_eq ! ( batches[ 0 ] . schema( ) . field( 0 ) . name( ) , "id" ) ;
557+ assert_eq ! ( batches[ 0 ] . schema( ) . field( 1 ) . name( ) , "name" ) ;
558+ let ids = batches[ 0 ]
559+ . column ( 0 )
560+ . as_any ( )
561+ . downcast_ref :: < Int32Array > ( )
562+ . unwrap ( ) ;
563+ assert_eq ! ( ids. values( ) , & [ 1 , 2 , 3 , 4 , 5 ] ) ;
564+ }
565+
566+ #[ tokio:: test]
567+ async fn test_read_projection_with_missing_unsupported_column ( ) {
568+ let fields = data_fields ( ) ;
569+ let projected = vec ! [
570+ fields[ 0 ] . clone( ) ,
571+ field(
572+ 3 ,
573+ "new_items" ,
574+ DataType :: Array ( ArrayType :: new( DataType :: Int ( IntType :: new( ) ) ) ) ,
575+ ) ,
576+ ] ;
577+ let data = write_mosaic ( & sample_batch ( ) ) ;
578+ let batches = read_batches ( data, & projected, None ) . await . unwrap ( ) ;
579+
580+ assert_eq ! ( batches. len( ) , 1 ) ;
581+ assert_eq ! ( batches[ 0 ] . num_rows( ) , 5 ) ;
582+ assert_eq ! ( batches[ 0 ] . num_columns( ) , 1 ) ;
583+ assert_eq ! ( batches[ 0 ] . schema( ) . field( 0 ) . name( ) , "id" ) ;
584+ }
585+
586+ #[ tokio:: test]
587+ async fn test_read_projection_with_existing_unsupported_column_returns_error ( ) {
588+ let projected = vec ! [ field(
531589 0 ,
532- "items" . to_string ( ) ,
590+ "id" ,
533591 DataType :: Array ( ArrayType :: new( DataType :: Int ( IntType :: new( ) ) ) ) ,
534592 ) ] ;
535- let result = MosaicFormatReader
536- . read_batch_stream (
537- Box :: new ( TestFileRead { data : Bytes :: new ( ) } ) ,
538- 0 ,
539- & unsupported,
540- None ,
541- None ,
542- None ,
543- )
544- . await ;
545- let err = match result {
546- Ok ( _) => panic ! ( "expected unsupported Mosaic type error" ) ,
547- Err ( err) => err,
548- } ;
593+ let data = write_mosaic ( & sample_batch ( ) ) ;
594+ let err = read_batches ( data, & projected, None ) . await . unwrap_err ( ) ;
549595
550596 assert ! (
551- matches!( err, Error :: Unsupported { message } if message. contains( "Mosaic format does not support column 'items '" ) )
597+ matches!( err, Error :: Unsupported { message } if message. contains( "Mosaic format does not support column 'id '" ) )
552598 ) ;
553599 }
554600
601+ #[ tokio:: test]
602+ async fn test_read_projection_all_columns_missing ( ) {
603+ let projected = vec ! [
604+ field( 3 , "new_score" , DataType :: Int ( IntType :: with_nullable( true ) ) ) ,
605+ field(
606+ 4 ,
607+ "new_name" ,
608+ DataType :: VarChar ( VarCharType :: with_nullable( true , 20 ) . unwrap( ) ) ,
609+ ) ,
610+ ] ;
611+ let data = write_mosaic ( & sample_batch ( ) ) ;
612+ let batches = read_batches ( data, & projected, None ) . await . unwrap ( ) ;
613+
614+ assert_eq ! ( batches. len( ) , 1 ) ;
615+ assert_eq ! ( batches[ 0 ] . num_rows( ) , 5 ) ;
616+ assert_eq ! ( batches[ 0 ] . num_columns( ) , 0 ) ;
617+ assert ! ( batches[ 0 ] . schema( ) . fields( ) . is_empty( ) ) ;
618+ }
619+
620+ #[ tokio:: test]
621+ async fn test_read_projection_all_columns_missing_with_row_selection ( ) {
622+ let projected = vec ! [ field(
623+ 3 ,
624+ "new_score" ,
625+ DataType :: Int ( IntType :: with_nullable( true ) ) ,
626+ ) ] ;
627+ let data = write_mosaic ( & sample_batch ( ) ) ;
628+ let batches = read_batches ( data, & projected, Some ( vec ! [ RowRange :: new( 1 , 3 ) ] ) )
629+ . await
630+ . unwrap ( ) ;
631+
632+ assert_eq ! ( batches. len( ) , 1 ) ;
633+ assert_eq ! ( batches[ 0 ] . num_rows( ) , 3 ) ;
634+ assert_eq ! ( batches[ 0 ] . num_columns( ) , 0 ) ;
635+ }
636+
637+ #[ tokio:: test]
638+ async fn test_read_projection_with_missing_column_and_row_selection ( ) {
639+ let fields = data_fields ( ) ;
640+ let projected = vec ! [
641+ fields[ 2 ] . clone( ) ,
642+ field( 3 , "new_id" , DataType :: Int ( IntType :: with_nullable( true ) ) ) ,
643+ ] ;
644+ let data = write_mosaic ( & sample_batch ( ) ) ;
645+ let batches = read_batches (
646+ data,
647+ & projected,
648+ Some ( vec ! [ RowRange :: new( 0 , 1 ) , RowRange :: new( 4 , 4 ) ] ) ,
649+ )
650+ . await
651+ . unwrap ( ) ;
652+
653+ assert_eq ! ( batches. len( ) , 1 ) ;
654+ assert_eq ! ( batches[ 0 ] . num_rows( ) , 3 ) ;
655+ assert_eq ! ( batches[ 0 ] . num_columns( ) , 1 ) ;
656+ assert_eq ! ( batches[ 0 ] . schema( ) . field( 0 ) . name( ) , "score" ) ;
657+ let scores = batches[ 0 ]
658+ . column ( 0 )
659+ . as_any ( )
660+ . downcast_ref :: < Int32Array > ( )
661+ . unwrap ( ) ;
662+ assert_eq ! ( scores. values( ) , & [ 10 , 20 , 50 ] ) ;
663+ }
664+
555665 #[ test]
556666 fn test_validate_row_type_as_unsupported ( ) {
557667 let unsupported = vec ! [ DataField :: new(
0 commit comments