1919
2020use crate :: utils:: { get_scalar_value_from_args, get_signed_integer} ;
2121
22- use arrow:: buffer:: NullBuffer ;
22+ use arrow:: buffer:: BooleanBuffer ;
2323use arrow:: datatypes:: FieldRef ;
24+ use arrow:: util:: bit_chunk_iterator:: UnalignedBitChunk ;
2425use datafusion_common:: arrow:: array:: ArrayRef ;
2526use datafusion_common:: arrow:: datatypes:: { DataType , Field } ;
2627use datafusion_common:: { Result , ScalarValue , exec_datafusion_err, exec_err} ;
@@ -487,14 +488,15 @@ impl PartitionEvaluator for NthValueEvaluator {
487488impl NthValueEvaluator {
488489 fn valid_index ( & self , array : & ArrayRef , range : & Range < usize > ) -> Option < usize > {
489490 let n_range = range. end - range. start ;
490- if self . ignore_nulls {
491- // Calculate valid indices, inside the window frame boundaries.
492- let slice = array. slice ( range. start , n_range) ;
493- if let Some ( nulls) = slice. nulls ( )
494- && nulls. null_count ( ) > 0
495- {
496- return self . valid_index_with_nulls ( nulls, range. start ) ;
497- }
491+ if self . ignore_nulls
492+ && let Some ( nulls) = array. nulls ( )
493+ {
494+ // Read the validity bits inside the window frame boundaries in place. Slicing
495+ // `array` instead would allocate and count the frame's nulls for every row.
496+ let valid = nulls. inner ( ) . slice ( range. start , n_range) ;
497+ return self
498+ . valid_index_with_nulls ( & valid)
499+ . map ( |idx| idx + range. start ) ;
498500 }
499501 // Either no nulls, or nulls are regarded as valid rows
500502 match self . state . kind {
@@ -525,27 +527,26 @@ impl NthValueEvaluator {
525527 }
526528 }
527529
528- fn valid_index_with_nulls ( & self , nulls : & NullBuffer , offset : usize ) -> Option < usize > {
530+ /// Returns the index within the frame of the value to return, given the frame's validity
531+ /// bits.
532+ fn valid_index_with_nulls ( & self , valid : & BooleanBuffer ) -> Option < usize > {
529533 match self . state . kind {
530- NthValueKind :: First => nulls . valid_indices ( ) . next ( ) . map ( |idx| idx + offset ) ,
531- NthValueKind :: Last => nulls . valid_indices ( ) . last ( ) . map ( |idx| idx + offset ) ,
534+ NthValueKind :: First => valid . set_indices ( ) . next ( ) ,
535+ NthValueKind :: Last => last_set_bit ( valid ) ,
532536 NthValueKind :: Nth => {
533537 match self . n . cmp ( & 0 ) {
534538 Ordering :: Greater => {
535539 // SQL indices are not 0-based.
536540 let index = ( self . n as usize ) - 1 ;
537- nulls . valid_indices ( ) . nth ( index) . map ( |idx| idx + offset )
541+ valid . set_indices ( ) . nth ( index)
538542 }
539543 Ordering :: Less => {
540544 let reverse_index = ( -self . n ) as usize ;
541- let valid_indices_len = nulls . len ( ) - nulls . null_count ( ) ;
545+ let valid_indices_len = valid . count_set_bits ( ) ;
542546 if reverse_index > valid_indices_len {
543547 return None ;
544548 }
545- nulls
546- . valid_indices ( )
547- . nth ( valid_indices_len - reverse_index)
548- . map ( |idx| idx + offset)
549+ valid. set_indices ( ) . nth ( valid_indices_len - reverse_index)
549550 }
550551 Ordering :: Equal => None ,
551552 }
@@ -554,6 +555,27 @@ impl NthValueEvaluator {
554555 }
555556}
556557
558+ /// Returns the index of the last set bit in `bits`, or `None` if no bit is set.
559+ ///
560+ /// The bits are read a 64-bit word at a time from the end, so the cost grows with the distance
561+ /// from the end to the last set bit rather than with the length of `bits`. `LAST_VALUE ...
562+ /// IGNORE NULLS` looks up the last valid row of every frame, and a frame that starts at the
563+ /// partition's first row grows with every row, so enumerating the frame's valid rows from its
564+ /// start makes the partition quadratic.
565+ fn last_set_bit ( bits : & BooleanBuffer ) -> Option < usize > {
566+ let chunk = UnalignedBitChunk :: new ( bits. values ( ) , bits. offset ( ) , bits. len ( ) ) ;
567+ // The words hold `lead_padding` unset bits before `bits` and `trailing_padding` after it.
568+ let mut word_start = chunk. lead_padding ( ) + bits. len ( ) + chunk. trailing_padding ( ) ;
569+ for word in chunk. iter ( ) . rev ( ) {
570+ word_start -= 64 ;
571+ if word != 0 {
572+ let bit = 63 - word. leading_zeros ( ) as usize ;
573+ return Some ( word_start + bit - chunk. lead_padding ( ) ) ;
574+ }
575+ }
576+ None
577+ }
578+
557579#[ cfg( test) ]
558580mod tests {
559581 use super :: * ;
@@ -709,4 +731,37 @@ mod tests {
709731 )
710732 ) ;
711733 }
734+
735+ #[ test]
736+ fn last_set_bit_matches_a_scan_from_the_end ( ) {
737+ // The slices cover every bit offset within a word, byte-aligned and unaligned starts,
738+ // and lengths from empty to several words, which are the cases `UnalignedBitChunk`
739+ // splits into a prefix, aligned words and a suffix.
740+ let mut state = 0x9E37_79B9_7F4A_7C15_u64 ;
741+ let random = BooleanBuffer :: collect_bool ( 640 , |_| {
742+ state ^= state << 13 ;
743+ state ^= state >> 7 ;
744+ state ^= state << 17 ;
745+ state. is_multiple_of ( 5 )
746+ } ) ;
747+ let patterns = [
748+ random,
749+ BooleanBuffer :: collect_bool ( 640 , |i| i. is_multiple_of ( 97 ) ) ,
750+ BooleanBuffer :: new_set ( 640 ) ,
751+ BooleanBuffer :: new_unset ( 640 ) ,
752+ ] ;
753+ for bits in & patterns {
754+ for offset in 0 ..70 {
755+ for len in 0 ..=200 {
756+ let slice = bits. slice ( offset, len) ;
757+ let expected = ( 0 ..len) . rev ( ) . find ( |& i| slice. value ( i) ) ;
758+ assert_eq ! (
759+ last_set_bit( & slice) ,
760+ expected,
761+ "offset {offset}, len {len}"
762+ ) ;
763+ }
764+ }
765+ }
766+ }
712767}
0 commit comments