@@ -71,6 +71,35 @@ impl DataFileReader {
7171 self
7272 }
7373
74+ // These three accessors exist for the sibling `pk_vector_position_read`,
75+ // `pk_vector_indexed_split_read`, and `pk_vector_orchestrator` modules. The
76+ // read path that drives that chain lands in a later change, so under clippy
77+ // -D warnings they read as dead_code until then.
78+ /// Return a copy with a replaced read-type. Used by `pk_vector_position_read`
79+ /// to inject the internal `_ROW_ID` column for physical-position recovery.
80+ #[ allow( dead_code) ]
81+ pub ( super ) fn with_read_type ( mut self , read_type : Vec < DataField > ) -> Self {
82+ self . read_type = read_type;
83+ self
84+ }
85+
86+ /// The effective read-type (requested output fields) of this reader.
87+ /// Exposed for the sibling `pk_vector_position_read` module.
88+ #[ allow( dead_code) ]
89+ pub ( super ) fn read_type ( & self ) -> & [ DataField ] {
90+ & self . read_type
91+ }
92+
93+ /// True if any configured predicate can actually drop rows. A lone
94+ /// `Predicate::AlwaysTrue` keeps every row in order and is not row-filtering,
95+ /// matching `reject_row_id_with_predicate`'s notion.
96+ #[ allow( dead_code) ]
97+ pub ( super ) fn has_row_filtering_predicate ( & self ) -> bool {
98+ self . predicates
99+ . iter ( )
100+ . any ( |p| !matches ! ( p, Predicate :: AlwaysTrue ) )
101+ }
102+
74103 /// Reject projecting `_ROW_ID` alongside a data predicate. `_ROW_ID` is
75104 /// assigned positionally from post-filter batch row counts, so a residual
76105 /// filter that drops rows would desync it. (`_ROW_ID` predicates travel via
@@ -111,35 +140,16 @@ impl DataFileReader {
111140 Ok ( try_stream ! {
112141 for split in splits {
113142 // Create DV factory for this split only.
114- let dv_factory = if split
115- . data_deletion_files( )
116- . is_some_and( |files| files. iter( ) . any( Option :: is_some) )
117- {
118- Some (
119- DeletionVectorFactory :: new(
120- & reader. file_io,
121- split. data_files( ) ,
122- split. data_deletion_files( ) ,
123- )
124- . await ?,
125- )
126- } else {
127- None
128- } ;
143+ let dv_factory = reader. build_split_dv_factory( & split) . await ?;
129144
130145 for file_meta in split. data_files( ) . to_vec( ) {
131- let dv = dv_factory
132- . as_ref( )
133- . and_then ( |factory| factory . get_deletion_vector ( & file_meta. file_name) )
134- . cloned ( ) ;
146+ let dv = DataFileReader :: deletion_vector_for_file (
147+ dv_factory . as_ref( ) ,
148+ & file_meta. file_name,
149+ ) ;
135150
136151 // Load data file's schema if it differs from the table schema.
137- let data_fields: Option <Vec <DataField >> = if file_meta. schema_id != reader. table_schema_id {
138- let data_schema = reader. schema_manager. schema( file_meta. schema_id) . await ?;
139- Some ( data_schema. fields( ) . to_vec( ) )
140- } else {
141- None
142- } ;
152+ let data_fields = reader. derive_data_fields( & file_meta) . await ?;
143153
144154 let mut stream = reader. read_single_file_stream(
145155 & split,
@@ -157,6 +167,55 @@ impl DataFileReader {
157167 . boxed ( ) )
158168 }
159169
170+ /// Build the deletion-vector factory for a split, or `None` when the split
171+ /// carries no deletion files. One factory per split (not per file), matching
172+ /// `read`. Shared with `pk_vector_indexed_split_read` so that path reuses the
173+ /// exact production derivation instead of duplicating it.
174+ pub ( super ) async fn build_split_dv_factory (
175+ & self ,
176+ split : & DataSplit ,
177+ ) -> crate :: Result < Option < DeletionVectorFactory > > {
178+ if split
179+ . data_deletion_files ( )
180+ . is_some_and ( |files| files. iter ( ) . any ( Option :: is_some) )
181+ {
182+ Ok ( Some (
183+ DeletionVectorFactory :: new (
184+ & self . file_io ,
185+ split. data_files ( ) ,
186+ split. data_deletion_files ( ) ,
187+ )
188+ . await ?,
189+ ) )
190+ } else {
191+ Ok ( None )
192+ }
193+ }
194+
195+ /// Look up the deletion vector for one file from a split-level factory.
196+ pub ( super ) fn deletion_vector_for_file (
197+ factory : Option < & DeletionVectorFactory > ,
198+ file_name : & str ,
199+ ) -> Option < Arc < DeletionVector > > {
200+ factory
201+ . and_then ( |factory| factory. get_deletion_vector ( file_name) )
202+ . cloned ( )
203+ }
204+
205+ /// Load the data file's own schema fields when its `schema_id` differs from
206+ /// the table schema id (schema evolution); `None` when they match.
207+ pub ( super ) async fn derive_data_fields (
208+ & self ,
209+ file_meta : & DataFileMeta ,
210+ ) -> crate :: Result < Option < Vec < DataField > > > {
211+ if file_meta. schema_id != self . table_schema_id {
212+ let data_schema = self . schema_manager . schema ( file_meta. schema_id ) . await ?;
213+ Ok ( Some ( data_schema. fields ( ) . to_vec ( ) ) )
214+ } else {
215+ Ok ( None )
216+ }
217+ }
218+
160219 /// Read a single parquet file from a split, returning a lazy stream of batches.
161220 /// Optionally applies a deletion vector.
162221 ///
@@ -994,6 +1053,58 @@ mod tests {
9941053 use roaring:: RoaringBitmap ;
9951054 use std:: io;
9961055
1056+ #[ test]
1057+ fn test_accessors_expose_read_type_and_row_filtering_predicate ( ) {
1058+ use crate :: spec:: { DataField , DataType , IntType } ;
1059+ let fields = vec ! [ DataField :: new(
1060+ 0 ,
1061+ "id" . to_string( ) ,
1062+ DataType :: Int ( IntType :: new( ) ) ,
1063+ ) ] ;
1064+ let file_io = crate :: io:: FileIOBuilder :: new ( "memory" ) . build ( ) . unwrap ( ) ;
1065+ let schema_manager = SchemaManager :: new ( file_io. clone ( ) , "memory:/acc" . to_string ( ) ) ;
1066+
1067+ let no_pred = DataFileReader :: new (
1068+ file_io. clone ( ) ,
1069+ schema_manager. clone ( ) ,
1070+ 1 ,
1071+ fields. clone ( ) ,
1072+ fields. clone ( ) ,
1073+ vec ! [ ] ,
1074+ ) ;
1075+ assert_eq ! ( no_pred. read_type( ) . len( ) , 1 ) ;
1076+ assert ! ( !no_pred. has_row_filtering_predicate( ) ) ;
1077+
1078+ let always_true = DataFileReader :: new (
1079+ file_io. clone ( ) ,
1080+ schema_manager. clone ( ) ,
1081+ 1 ,
1082+ fields. clone ( ) ,
1083+ fields. clone ( ) ,
1084+ vec ! [ crate :: spec:: Predicate :: AlwaysTrue ] ,
1085+ ) ;
1086+ assert ! (
1087+ !always_true. has_row_filtering_predicate( ) ,
1088+ "AlwaysTrue is not row-filtering"
1089+ ) ;
1090+
1091+ let filtering = PredicateBuilder :: new ( & fields)
1092+ . equal ( "id" , crate :: spec:: Datum :: Int ( 10 ) )
1093+ . unwrap ( ) ;
1094+ let with_filter = DataFileReader :: new (
1095+ file_io. clone ( ) ,
1096+ schema_manager. clone ( ) ,
1097+ 1 ,
1098+ fields. clone ( ) ,
1099+ fields. clone ( ) ,
1100+ vec ! [ filtering] ,
1101+ ) ;
1102+ assert ! (
1103+ with_filter. has_row_filtering_predicate( ) ,
1104+ "a real (non-AlwaysTrue) predicate is row-filtering"
1105+ ) ;
1106+ }
1107+
9971108 struct MemOutputFile {
9981109 data : Vec < u8 > ,
9991110 }
0 commit comments