@@ -639,14 +639,17 @@ mod tests {
639639 }
640640
641641 use crate :: catalog:: Identifier ;
642+ use crate :: deletion_vector:: DeletionVector ;
642643 use crate :: io:: FileIOBuilder ;
643644 use crate :: spec:: {
644645 BinaryRow , DataField , DataType , IntType , Predicate , PredicateBuilder , Schema , TableSchema ,
645646 VarCharType ,
646647 } ;
647- use crate :: table:: { query_auth_table, DataSplitBuilder , Table } ;
648+ use crate :: table:: { query_auth_table, DataSplitBuilder , DeletionFile , Table } ;
648649 use arrow_array:: { Int32Array , RecordBatch } ;
650+ use bytes:: Bytes ;
649651 use futures:: TryStreamExt ;
652+ use roaring:: RoaringBitmap ;
650653 use std:: collections:: { HashMap , HashSet } ;
651654 use std:: fs;
652655 use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
@@ -675,6 +678,31 @@ mod tests {
675678 . collect ( )
676679 }
677680
681+ async fn write_test_deletion_file (
682+ file_io : & crate :: io:: FileIO ,
683+ path : & str ,
684+ deleted_rows : & [ u32 ] ,
685+ ) -> DeletionFile {
686+ let bitmap = deleted_rows. iter ( ) . copied ( ) . collect :: < RoaringBitmap > ( ) ;
687+ let bytes = DeletionVector :: from_bitmap ( bitmap)
688+ . serialize_to_bytes ( )
689+ . unwrap ( ) ;
690+ let bitmap_length = i32:: from_be_bytes ( bytes[ 0 ..4 ] . try_into ( ) . unwrap ( ) ) ;
691+ file_io
692+ . new_output ( path)
693+ . unwrap ( )
694+ . write ( Bytes :: from ( bytes) )
695+ . await
696+ . unwrap ( ) ;
697+
698+ DeletionFile :: new (
699+ path. to_string ( ) ,
700+ 0 ,
701+ bitmap_length as i64 ,
702+ Some ( deleted_rows. len ( ) as i64 ) ,
703+ )
704+ }
705+
678706 fn simple_table ( ) -> Table {
679707 let file_io = FileIOBuilder :: new ( "file" ) . build ( ) . unwrap ( ) ;
680708 let table_schema = TableSchema :: new (
@@ -695,28 +723,70 @@ mod tests {
695723 )
696724 }
697725
698- fn partial_update_dv_pk_table ( ) -> Table {
726+ fn dv_pk_table ( table_path : & str , merge_engine : & str ) -> Table {
699727 let file_io = FileIOBuilder :: new ( "file" ) . build ( ) . unwrap ( ) ;
700728 let table_schema = TableSchema :: new (
701729 0 ,
702730 & Schema :: builder ( )
703731 . column ( "id" , DataType :: Int ( IntType :: new ( ) ) )
704732 . column ( "value" , DataType :: Int ( IntType :: new ( ) ) )
705733 . primary_key ( [ "id" ] )
706- . option ( "merge-engine" , "partial-update" )
734+ . option ( "merge-engine" , merge_engine )
707735 . option ( "deletion-vectors.enabled" , "true" )
708736 . build ( )
709737 . unwrap ( ) ,
710738 ) ;
711739 Table :: new (
712740 file_io,
713741 Identifier :: new ( "default" , "partial_update_dv_t" ) ,
714- "/tmp/test-partial-update-dv-read-builder" . to_string ( ) ,
742+ table_path . to_string ( ) ,
715743 table_schema,
716744 None ,
717745 )
718746 }
719747
748+ async fn read_compacted_dv_table ( merge_engine : & str ) -> Vec < RecordBatch > {
749+ let tempdir = tempdir ( ) . unwrap ( ) ;
750+ let table_path = local_file_path ( tempdir. path ( ) ) ;
751+ let bucket_dir = tempdir. path ( ) . join ( "bucket-0" ) ;
752+ let index_dir = tempdir. path ( ) . join ( "index" ) ;
753+ fs:: create_dir_all ( & bucket_dir) . unwrap ( ) ;
754+ fs:: create_dir_all ( & index_dir) . unwrap ( ) ;
755+
756+ let data_path = bucket_dir. join ( "data.parquet" ) ;
757+ write_int_parquet_file (
758+ & data_path,
759+ vec ! [ ( "id" , vec![ 1 , 2 , 3 ] ) , ( "value" , vec![ 10 , 20 , 30 ] ) ] ,
760+ None ,
761+ ) ;
762+ let file_size = fs:: metadata ( & data_path) . unwrap ( ) . len ( ) as i64 ;
763+ let file_io = FileIOBuilder :: new ( "file" ) . build ( ) . unwrap ( ) ;
764+ let deletion_file =
765+ write_test_deletion_file ( & file_io, & local_file_path ( & index_dir. join ( "dv" ) ) , & [ 1 ] ) . await ;
766+
767+ let table = dv_pk_table ( & table_path, merge_engine) ;
768+ let mut data_file =
769+ test_data_file :: < crate :: spec:: DataFileMeta > ( "data.parquet" , 3 , file_size) ;
770+ data_file. delete_row_count = Some ( 0 ) ;
771+ let split = DataSplitBuilder :: new ( )
772+ . with_snapshot ( 1 )
773+ . with_partition ( BinaryRow :: new ( 0 ) )
774+ . with_bucket ( 0 )
775+ . with_bucket_path ( local_file_path ( & bucket_dir) )
776+ . with_total_buckets ( 1 )
777+ . with_data_files ( vec ! [ data_file] )
778+ . with_data_deletion_files ( vec ! [ Some ( deletion_file) ] )
779+ . build ( )
780+ . unwrap ( ) ;
781+
782+ TableRead :: new ( & table, table. schema ( ) . fields ( ) . to_vec ( ) , Vec :: new ( ) )
783+ . to_arrow ( & [ split] )
784+ . unwrap ( )
785+ . try_collect :: < Vec < _ > > ( )
786+ . await
787+ . unwrap ( )
788+ }
789+
720790 #[ test]
721791 fn test_read_fails_closed_when_query_auth_enabled ( ) {
722792 let table = query_auth_table ( ) ;
@@ -1540,33 +1610,50 @@ mod tests {
15401610 }
15411611
15421612 #[ tokio:: test]
1543- async fn test_direct_table_read_rejects_partial_update_with_deletion_vectors ( ) {
1544- let table = partial_update_dv_pk_table ( ) ;
1613+ async fn test_direct_table_read_reads_compacted_partial_update_with_deletion_vectors ( ) {
1614+ let batches = read_compacted_dv_table ( "partial-update" ) . await ;
1615+
1616+ assert_eq ! ( collect_int_column( & batches, "id" ) , vec![ 1 , 3 ] ) ;
1617+ assert_eq ! ( collect_int_column( & batches, "value" ) , vec![ 10 , 30 ] ) ;
1618+ }
1619+
1620+ #[ tokio:: test]
1621+ async fn test_direct_table_read_reads_compacted_aggregation_with_deletion_vectors ( ) {
1622+ let batches = read_compacted_dv_table ( "aggregation" ) . await ;
1623+
1624+ assert_eq ! ( collect_int_column( & batches, "id" ) , vec![ 1 , 3 ] ) ;
1625+ assert_eq ! ( collect_int_column( & batches, "value" ) , vec![ 10 , 30 ] ) ;
1626+ }
1627+
1628+ #[ test]
1629+ fn test_direct_table_read_rejects_partial_update_dv_merge_on_read ( ) {
1630+ let table = dv_pk_table (
1631+ "/tmp/test-partial-update-dv-merge-on-read" ,
1632+ "partial-update" ,
1633+ )
1634+ . copy_with_options ( HashMap :: from ( [ (
1635+ "deletion-vectors.merge-on-read" . to_string ( ) ,
1636+ "true" . to_string ( ) ,
1637+ ) ] ) ) ;
1638+ let mut data_file = test_data_file :: < crate :: spec:: DataFileMeta > ( "data.parquet" , 1 , 0 ) ;
1639+ data_file. delete_row_count = Some ( 0 ) ;
15451640 let split = DataSplitBuilder :: new ( )
15461641 . with_snapshot ( 1 )
15471642 . with_partition ( BinaryRow :: new ( 0 ) )
15481643 . with_bucket ( 0 )
1549- . with_bucket_path ( "/tmp/test-partial-update-dv-read-builder /bucket-0" . to_string ( ) )
1644+ . with_bucket_path ( "/tmp/test-partial-update-dv-merge-on-read /bucket-0" . to_string ( ) )
15501645 . with_total_buckets ( 1 )
1551- . with_data_files ( vec ! [ test_data_file( "data.parquet" , 1 , 0 ) ] )
1552- . with_data_deletion_files ( vec ! [ Some ( crate :: table:: source:: DeletionFile :: new(
1553- "/tmp/test-partial-update-dv-read-builder/index/dv" . to_string( ) ,
1554- 0 ,
1555- 0 ,
1556- None ,
1557- ) ) ] )
1646+ . with_data_files ( vec ! [ data_file] )
15581647 . build ( )
15591648 . unwrap ( ) ;
1560- let err = TableRead :: new ( & table, table. schema ( ) . fields ( ) . to_vec ( ) , Vec :: new ( ) )
1561- . to_arrow ( & [ split] )
1562- . unwrap ( )
1563- . try_collect :: < Vec < _ > > ( )
1564- . await
1565- . unwrap_err ( ) ;
15661649
1567- assert ! (
1568- matches!( err, crate :: Error :: Unsupported { ref message } if message. contains( "deletion vectors" ) ) ,
1569- "expected partial-update+DV read to fail fast with Unsupported, got {err:?}"
1570- ) ;
1650+ let result =
1651+ TableRead :: new ( & table, table. schema ( ) . fields ( ) . to_vec ( ) , Vec :: new ( ) ) . to_arrow ( & [ split] ) ;
1652+
1653+ assert ! ( matches!(
1654+ result,
1655+ Err ( crate :: Error :: Unsupported { ref message } )
1656+ if message. contains( "merge-on-read" )
1657+ ) ) ;
15711658 }
15721659}
0 commit comments