1515// specific language governing permissions and limitations
1616// under the License.
1717
18- use crate :: arrow:: ProjectionMask ;
1918use crate :: errors:: ParquetError ;
20- use crate :: file:: page_index:: offset_index:: { OffsetIndexMetaData , PageLocation } ;
19+ use crate :: file:: page_index:: offset_index:: PageLocation ;
2120use arrow_array:: { Array , BooleanArray } ;
2221use arrow_buffer:: { BooleanBuffer , BooleanBufferBuilder } ;
2322use arrow_select:: filter:: SlicesIterator ;
2423use std:: cmp:: Ordering ;
2524use std:: collections:: VecDeque ;
2625use std:: ops:: Range ;
26+ use std:: sync:: Arc ;
2727
2828/// Policy for picking a strategy to materialise [`RowSelection`] during execution.
29- ///
30- /// Note that this is a user-provided preference, and the actual strategy used
31- /// may differ based on safety considerations (e.g. page skipping).
3229#[ derive( Clone , Copy , Debug , Eq , PartialEq ) ]
3330pub enum RowSelectionPolicy {
3431 /// Use a queue of [`RowSelector`] values
@@ -50,8 +47,8 @@ impl Default for RowSelectionPolicy {
5047
5148/// Fully resolved strategy for materializing [`RowSelection`] during execution.
5249///
53- /// This is determined from a combination of user preference (via [`RowSelectionPolicy`])
54- /// and safety considerations (e.g. page skipping) .
50+ /// This is determined by [`RowSelectionPolicy`], including selector density for
51+ /// [`RowSelectionPolicy::Auto`] .
5552#[ derive( Clone , Copy , Debug , Eq , PartialEq ) ]
5653pub ( crate ) enum RowSelectionStrategy {
5754 /// Use a queue of [`RowSelector`] values
@@ -250,37 +247,32 @@ impl RowSelection {
250247 ranges
251248 }
252249
253- /// Returns true if this selection would skip any data pages within the provided columns
254- fn selection_skips_any_page (
250+ /// Returns the complete row ranges of the pages selected by [`Self::scan_ranges`].
251+ pub ( crate ) fn row_ranges_for_selected_pages (
255252 & self ,
256- projection : & ProjectionMask ,
257- columns : & [ OffsetIndexMetaData ] ,
258- ) -> bool {
259- columns. iter ( ) . enumerate ( ) . any ( |( leaf_idx, column) | {
260- if !projection. leaf_included ( leaf_idx) {
261- return false ;
262- }
263-
264- let locations = column. page_locations ( ) ;
265- if locations. is_empty ( ) {
266- return false ;
253+ page_locations : & [ PageLocation ] ,
254+ total_rows : usize ,
255+ ) -> Vec < Range < usize > > {
256+ let mut selected_pages = self . scan_ranges ( page_locations) . into_iter ( ) . peekable ( ) ;
257+ let mut row_ranges = Vec :: new ( ) ;
258+
259+ for ( idx, page) in page_locations. iter ( ) . enumerate ( ) {
260+ let Some ( selected_page) = selected_pages. peek ( ) else {
261+ break ;
262+ } ;
263+ if selected_page. start != page. offset as u64 {
264+ continue ;
267265 }
266+ selected_pages. next ( ) ;
268267
269- let ranges = self . scan_ranges ( locations) ;
270- !ranges. is_empty ( ) && ranges. len ( ) < locations. len ( )
271- } )
272- }
273-
274- /// Returns true if selectors should be forced, preventing mask materialisation
275- pub ( crate ) fn should_force_selectors (
276- & self ,
277- projection : & ProjectionMask ,
278- offset_index : Option < & [ OffsetIndexMetaData ] > ,
279- ) -> bool {
280- match offset_index {
281- Some ( columns) => self . selection_skips_any_page ( projection, columns) ,
282- None => false ,
268+ let end = page_locations
269+ . get ( idx + 1 )
270+ . map ( |next| next. first_row_index as usize )
271+ . unwrap_or ( total_rows) ;
272+ row_ranges. push ( page. first_row_index as usize ..end) ;
283273 }
274+
275+ row_ranges
284276 }
285277
286278 /// Splits off the first `row_count` from this [`RowSelection`]
@@ -770,6 +762,8 @@ pub struct MaskCursor {
770762 mask : BooleanBuffer ,
771763 /// Current absolute offset into the selection
772764 position : usize ,
765+ /// Row ranges whose backing pages are loaded for every projected column.
766+ loaded_row_ranges : Option < Arc < LoadedRowRanges > > ,
773767}
774768
775769impl MaskCursor {
@@ -824,6 +818,54 @@ impl MaskCursor {
824818 } )
825819 }
826820
821+ /// Returns the next mask chunk without crossing a row range that was not loaded.
822+ pub ( crate ) fn next_chunk (
823+ & mut self ,
824+ batch_size : usize ,
825+ ) -> Result < Option < MaskChunk > , ParquetError > {
826+ if self . loaded_row_ranges . is_none ( ) {
827+ return Ok ( self . next_mask_chunk ( batch_size) ) ;
828+ }
829+
830+ let start_position = self . position ;
831+ let mut cursor = start_position;
832+ while cursor < self . mask . len ( ) && !self . mask . value ( cursor) {
833+ cursor += 1 ;
834+ }
835+
836+ if cursor == self . mask . len ( ) {
837+ self . position = cursor;
838+ return Ok ( None ) ;
839+ }
840+
841+ let loaded_range_end = self
842+ . loaded_row_ranges
843+ . as_ref ( )
844+ . and_then ( |ranges| ranges. end_containing ( cursor) )
845+ . ok_or_else ( || {
846+ ParquetError :: General ( format ! (
847+ "Internal Error: selected row {cursor} has no loaded page range"
848+ ) )
849+ } ) ?;
850+
851+ let mask_start = cursor;
852+ let mut selected_rows = 0 ;
853+ while cursor < loaded_range_end && cursor < self . mask . len ( ) && selected_rows < batch_size {
854+ if self . mask . value ( cursor) {
855+ selected_rows += 1 ;
856+ }
857+ cursor += 1 ;
858+ }
859+
860+ self . position = cursor;
861+ Ok ( Some ( MaskChunk {
862+ initial_skip : mask_start - start_position,
863+ chunk_rows : cursor - mask_start,
864+ selected_rows,
865+ mask_start,
866+ } ) )
867+ }
868+
827869 /// Materialise the boolean values for a mask-backed chunk
828870 pub fn mask_values_for ( & self , chunk : & MaskChunk ) -> Result < BooleanArray , ParquetError > {
829871 if chunk. mask_start . saturating_add ( chunk. chunk_rows ) > self . mask . len ( ) {
@@ -885,6 +927,39 @@ pub struct MaskChunk {
885927 pub mask_start : usize ,
886928}
887929
930+ /// Row ranges whose backing pages are loaded for every projected column.
931+ #[ derive( Clone , Debug ) ]
932+ pub ( crate ) struct LoadedRowRanges ( Vec < Range < usize > > ) ;
933+
934+ impl LoadedRowRanges {
935+ pub ( crate ) fn from_selection ( selection : RowSelection ) -> Self {
936+ let selectors: Vec < RowSelector > = selection. into ( ) ;
937+ let mut position = 0 ;
938+ let ranges = selectors
939+ . into_iter ( )
940+ . filter_map ( |selector| {
941+ let start = position;
942+ position += selector. row_count ;
943+ ( !selector. skip ) . then_some ( start..position)
944+ } )
945+ . collect ( ) ;
946+ Self ( ranges)
947+ }
948+
949+ fn end_containing ( & self , row : usize ) -> Option < usize > {
950+ let idx = self . 0 . partition_point ( |range| range. end <= row) ;
951+ self . 0
952+ . get ( idx)
953+ . filter ( |range| range. start <= row)
954+ . map ( |range| range. end )
955+ }
956+
957+ #[ cfg( test) ]
958+ pub ( crate ) fn ranges ( & self ) -> & [ Range < usize > ] {
959+ & self . 0
960+ }
961+ }
962+
888963/// Cursor for iterating a [`RowSelection`] during execution within a
889964/// [`ReadPlan`](crate::arrow::arrow_reader::ReadPlan).
890965///
@@ -902,10 +977,14 @@ pub enum RowSelectionCursor {
902977
903978impl RowSelectionCursor {
904979 /// Create a [`MaskCursor`] cursor backed by a bitmask, from an existing set of selectors
905- pub ( crate ) fn new_mask_from_selectors ( selectors : Vec < RowSelector > ) -> Self {
980+ pub ( crate ) fn new_mask_from_selectors (
981+ selectors : Vec < RowSelector > ,
982+ loaded_row_ranges : Option < Arc < LoadedRowRanges > > ,
983+ ) -> Self {
906984 Self :: Mask ( MaskCursor {
907985 mask : boolean_mask_from_selectors ( & selectors) ,
908986 position : 0 ,
987+ loaded_row_ranges,
909988 } )
910989 }
911990
@@ -1466,6 +1545,10 @@ mod tests {
14661545
14671546 // assert_eq!(mask, vec![false, true, true, false, true, true, false]);
14681547 assert_eq ! ( ranges, vec![ 10 ..20 , 20 ..30 , 40 ..50 , 50 ..60 ] ) ;
1548+ assert_eq ! (
1549+ selection. row_ranges_for_selected_pages( & index, 70 ) ,
1550+ vec![ 10 ..20 , 20 ..30 , 40 ..50 , 50 ..60 ]
1551+ ) ;
14691552
14701553 let selection = RowSelection :: from ( vec ! [
14711554 // Skip first page
@@ -1537,6 +1620,24 @@ mod tests {
15371620 assert_eq ! ( ranges, vec![ 10 ..20 , 20 ..30 , 30 ..40 ] ) ;
15381621 }
15391622
1623+ #[ test]
1624+ fn test_loaded_mask_chunk_stops_at_trimmed_mask_end ( ) {
1625+ let loaded = LoadedRowRanges :: from_selection ( RowSelection :: from_consecutive_ranges (
1626+ std:: iter:: once ( 0 ..5 ) ,
1627+ 10 ,
1628+ ) ) ;
1629+ let RowSelectionCursor :: Mask ( mut cursor) = RowSelectionCursor :: new_mask_from_selectors (
1630+ vec ! [ RowSelector :: select( 1 ) ] ,
1631+ Some ( loaded. into ( ) ) ,
1632+ ) else {
1633+ unreachable ! ( )
1634+ } ;
1635+
1636+ let chunk = cursor. next_chunk ( 10 ) . unwrap ( ) . unwrap ( ) ;
1637+ assert_eq ! ( chunk. chunk_rows, 1 ) ;
1638+ assert ! ( cursor. is_empty( ) ) ;
1639+ }
1640+
15401641 #[ test]
15411642 fn test_from_ranges ( ) {
15421643 let ranges = [ 1 ..3 , 4 ..6 , 6 ..6 , 8 ..8 , 9 ..10 ] ;
0 commit comments