@@ -28,6 +28,7 @@ use std::sync::atomic::Ordering;
2828use databend_common_expression:: ColumnId ;
2929use databend_common_expression:: Expr ;
3030use databend_common_expression:: Scalar ;
31+ use databend_common_expression:: types:: DataType ;
3132use databend_storages_common_table_meta:: meta:: ColumnStatistics ;
3233use parking_lot:: RwLock ;
3334use tokio:: sync:: watch;
@@ -39,33 +40,37 @@ use crate::sbbf::Sbbf;
3940pub type RuntimeBloomFilter = Arc < Sbbf > ;
4041pub type RuntimeScanFilterFuture = Pin < Box < dyn Future < Output = ( ) > + Send + ' static > > ;
4142
42- #[ derive( Clone , Copy , Debug , Eq , PartialEq ) ]
43+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
4344pub struct RuntimeScanOrder {
4445 pub column_id : ColumnId ,
46+ pub data_type : DataType ,
4547 pub asc : bool ,
4648 pub nulls_first : bool ,
4749}
4850
4951impl RuntimeScanOrder {
5052 /// Rank column statistics for scheduling: parts more likely to hold top
5153 /// rows under this order rank first.
52- pub fn rank < ' a > (
54+ pub fn rank (
5355 & self ,
54- stats : Option < & ' a HashMap < ColumnId , ColumnStatistics > > ,
55- ) -> RuntimeTopNRank < & ' a Scalar > {
56+ stats : Option < & HashMap < ColumnId , ColumnStatistics > > ,
57+ ) -> RuntimeTopNRank < Scalar > {
5658 let Some ( stat) = stats. and_then ( |stats| stats. get ( & self . column_id ) ) else {
5759 return RuntimeTopNRank :: Unknown ;
5860 } ;
61+ let Some ( view) = stat. try_view ( & self . data_type ) else {
62+ return RuntimeTopNRank :: Unknown ;
63+ } ;
5964 // Under NULLS FIRST null rows sort before every value: parts holding
6065 // nulls are the most promising ones (and are never prunable).
6166 if self . nulls_first && stat. null_count > 0 {
6267 return RuntimeTopNRank :: Best ;
6368 }
64- let key = if self . asc { stat . min ( ) } else { stat . max ( ) } ;
69+ let key = if self . asc { view . min ( ) } else { view . max ( ) } ;
6570 if matches ! ( key, Scalar :: Null ) {
6671 return RuntimeTopNRank :: Unknown ;
6772 }
68- RuntimeTopNRank :: Value ( key)
73+ RuntimeTopNRank :: Value ( key. clone ( ) )
6974 }
7075
7176 /// Compare two scheduling ranks under this order: better-ranked parts
@@ -103,16 +108,6 @@ pub enum RuntimeTopNRank<S> {
103108 Unknown ,
104109}
105110
106- impl RuntimeTopNRank < & Scalar > {
107- pub fn cloned ( self ) -> RuntimeTopNRank < Scalar > {
108- match self {
109- RuntimeTopNRank :: Best => RuntimeTopNRank :: Best ,
110- RuntimeTopNRank :: Value ( value) => RuntimeTopNRank :: Value ( value. clone ( ) ) ,
111- RuntimeTopNRank :: Unknown => RuntimeTopNRank :: Unknown ,
112- }
113- }
114- }
115-
116111pub trait RuntimeScanFilter : Send + Sync {
117112 fn finished ( & self ) -> bool {
118113 false
@@ -209,16 +204,18 @@ impl RuntimeFilterNotify {
209204/// their local boundary tightens, so contention is negligible.
210205pub struct RuntimeTopNFilter {
211206 column_id : u32 ,
207+ data_type : DataType ,
212208 asc : bool ,
213209 nulls_first : bool ,
214210 boundary : RwLock < Option < Scalar > > ,
215211 recheck : RuntimeFilterNotify ,
216212}
217213
218214impl RuntimeTopNFilter {
219- pub fn new ( column_id : u32 , asc : bool , nulls_first : bool ) -> Self {
215+ pub fn new ( column_id : u32 , data_type : DataType , asc : bool , nulls_first : bool ) -> Self {
220216 Self {
221217 column_id,
218+ data_type,
222219 asc,
223220 nulls_first,
224221 boundary : RwLock :: new ( None ) ,
@@ -305,8 +302,11 @@ impl RuntimeScanFilter for RuntimeTopNFilter {
305302 let Some ( stat) = stats. get ( & self . column_id ) else {
306303 return false ;
307304 } ;
305+ let Some ( view) = stat. try_view ( & self . data_type ) else {
306+ return false ;
307+ } ;
308308
309- self . boundary_excludes ( stat . min ( ) , stat . max ( ) , stat . null_count )
309+ self . boundary_excludes ( view . min ( ) , view . max ( ) , view . null_count ( ) )
310310 }
311311
312312 fn recheck_notified ( & self ) -> RuntimeScanFilterFuture {
@@ -316,6 +316,7 @@ impl RuntimeScanFilter for RuntimeTopNFilter {
316316 fn preferred_order ( & self ) -> Option < RuntimeScanOrder > {
317317 Some ( RuntimeScanOrder {
318318 column_id : self . column_id ,
319+ data_type : self . data_type . clone ( ) ,
319320 asc : self . asc ,
320321 nulls_first : self . nulls_first ,
321322 } )
@@ -529,9 +530,20 @@ mod tests {
529530 use databend_common_expression:: ColumnRef ;
530531 use databend_common_expression:: Expr ;
531532 use databend_common_expression:: types:: DataType ;
533+ use databend_common_expression:: types:: DecimalScalar ;
534+ use databend_common_expression:: types:: DecimalSize ;
532535 use databend_common_expression:: types:: NumberDataType ;
533536 use databend_common_expression:: types:: NumberScalar ;
534537 use tokio:: time:: Duration ;
538+
539+ fn int64_filter ( column_id : u32 , asc : bool , nulls_first : bool ) -> RuntimeTopNFilter {
540+ RuntimeTopNFilter :: new (
541+ column_id,
542+ DataType :: Number ( NumberDataType :: Int64 ) ,
543+ asc,
544+ nulls_first,
545+ )
546+ }
535547 use tokio:: time:: timeout;
536548
537549 use super :: * ;
@@ -540,9 +552,43 @@ mod tests {
540552 Scalar :: Number ( NumberScalar :: Int64 ( value) )
541553 }
542554
555+ fn decimal64 ( value : i64 , precision : u8 , scale : u8 ) -> Scalar {
556+ Scalar :: Decimal ( DecimalScalar :: Decimal64 (
557+ value,
558+ DecimalSize :: new ( precision, scale) . unwrap ( ) ,
559+ ) )
560+ }
561+
562+ #[ test]
563+ fn runtime_top_n_filter_aligns_decimal_stats_and_fails_open ( ) {
564+ let current_size = DecimalSize :: new ( 15 , 2 ) . unwrap ( ) ;
565+ let filter = RuntimeTopNFilter :: new ( 7 , DataType :: Decimal ( current_size) , true , false ) ;
566+ filter. update ( & decimal64 ( 500 , 15 , 2 ) ) ;
567+
568+ let widened = HashMap :: from ( [ (
569+ 7 ,
570+ ColumnStatistics :: new ( decimal64 ( 600 , 10 , 2 ) , decimal64 ( 900 , 10 , 2 ) , 0 , 0 , None ) ,
571+ ) ] ) ;
572+ assert ! ( filter. should_prune( Some ( & widened) ) ) ;
573+ assert ! ( matches!(
574+ filter. preferred_order( ) . unwrap( ) . rank( Some ( & widened) ) ,
575+ RuntimeTopNRank :: Value ( value) if value == decimal64( 600 , 15 , 2 )
576+ ) ) ;
577+
578+ let incompatible = HashMap :: from ( [ (
579+ 7 ,
580+ ColumnStatistics :: new ( decimal64 ( 600 , 10 , 3 ) , decimal64 ( 900 , 10 , 3 ) , 0 , 0 , None ) ,
581+ ) ] ) ;
582+ assert ! ( !filter. should_prune( Some ( & incompatible) ) ) ;
583+ assert ! ( matches!(
584+ filter. preferred_order( ) . unwrap( ) . rank( Some ( & incompatible) ) ,
585+ RuntimeTopNRank :: Unknown
586+ ) ) ;
587+ }
588+
543589 #[ test]
544590 fn runtime_top_n_filter_is_monotonic_and_tie_safe ( ) {
545- let asc = RuntimeTopNFilter :: new ( 7 , true , false ) ;
591+ let asc = int64_filter ( 7 , true , false ) ;
546592 assert ! ( !asc. boundary_excludes( & int64( 11 ) , & int64( 20 ) , 0 ) ) ;
547593
548594 asc. update ( & int64 ( 10 ) ) ;
@@ -556,7 +602,7 @@ mod tests {
556602 asc. update ( & int64 ( 8 ) ) ;
557603 assert_eq ! ( asc. boundary( ) , Some ( int64( 8 ) ) ) ;
558604
559- let desc = RuntimeTopNFilter :: new ( 7 , false , false ) ;
605+ let desc = int64_filter ( 7 , false , false ) ;
560606 desc. update ( & int64 ( 10 ) ) ;
561607 assert ! ( desc. boundary_excludes( & int64( 1 ) , & int64( 9 ) , 0 ) ) ;
562608 assert ! ( !desc. boundary_excludes( & int64( 1 ) , & int64( 10 ) , 0 ) ) ;
@@ -571,15 +617,15 @@ mod tests {
571617
572618 #[ test]
573619 fn runtime_top_n_filter_ranks_nulls_by_ordering ( ) {
574- let nulls_last = RuntimeTopNFilter :: new ( 1 , true , false ) ;
620+ let nulls_last = int64_filter ( 1 , true , false ) ;
575621 nulls_last. update ( & int64 ( 10 ) ) ;
576622 // Nulls sort after the boundary, so null rows are prunable too.
577623 assert ! ( nulls_last. boundary_excludes( & int64( 11 ) , & int64( 20 ) , 5 ) ) ;
578624 // All-null blocks sort entirely after the boundary.
579625 assert ! ( nulls_last. boundary_excludes( & Scalar :: Null , & Scalar :: Null , 7 ) ) ;
580626 assert ! ( !nulls_last. boundary_excludes( & int64( 9 ) , & int64( 20 ) , 5 ) ) ;
581627
582- let nulls_first = RuntimeTopNFilter :: new ( 1 , true , true ) ;
628+ let nulls_first = int64_filter ( 1 , true , true ) ;
583629 nulls_first. update ( & int64 ( 10 ) ) ;
584630 // Null rows are always candidates under NULLS FIRST.
585631 assert ! ( !nulls_first. boundary_excludes( & int64( 11 ) , & int64( 20 ) , 1 ) ) ;
@@ -589,7 +635,7 @@ mod tests {
589635
590636 #[ test]
591637 fn runtime_top_n_filter_concurrent_updates_keep_tightest ( ) {
592- let filter = Arc :: new ( RuntimeTopNFilter :: new ( 1 , true , false ) ) ;
638+ let filter = Arc :: new ( int64_filter ( 1 , true , false ) ) ;
593639 let threads: Vec < _ > = ( 0 ..4 )
594640 . map ( |t| {
595641 let filter = filter. clone ( ) ;
@@ -615,7 +661,7 @@ mod tests {
615661
616662 #[ tokio:: test]
617663 async fn runtime_scan_filter_notifications_are_repeatable ( ) {
618- let filter = RuntimeTopNFilter :: new ( 1 , true , false ) ;
664+ let filter = int64_filter ( 1 , true , false ) ;
619665
620666 let first = filter. recheck_notified ( ) ;
621667 filter. update ( & int64 ( 10 ) ) ;
@@ -648,7 +694,7 @@ mod tests {
648694
649695 #[ test]
650696 fn runtime_scan_filters_combine_filters ( ) {
651- let top_n = Arc :: new ( RuntimeTopNFilter :: new ( 1 , true , false ) ) ;
697+ let top_n = Arc :: new ( int64_filter ( 1 , true , false ) ) ;
652698 top_n. update ( & int64 ( 10 ) ) ;
653699 let limit = Arc :: new ( RuntimeLimitFilter :: new ( ) ) ;
654700
@@ -677,7 +723,7 @@ mod tests {
677723
678724 #[ test]
679725 fn runtime_scan_filters_prune_by_column_stats ( ) {
680- let asc_filter = Arc :: new ( RuntimeTopNFilter :: new ( 3 , true , false ) ) ;
726+ let asc_filter = Arc :: new ( int64_filter ( 3 , true , false ) ) ;
681727 asc_filter. update ( & int64 ( 10 ) ) ;
682728 let mut asc = RuntimeScanFilters :: default ( ) ;
683729 asc. push ( asc_filter) ;
@@ -694,13 +740,13 @@ mod tests {
694740 assert ! ( !asc. should_prune( None ) ) ;
695741
696742 // Under NULLS FIRST null rows are always candidates.
697- let nulls_first_filter = Arc :: new ( RuntimeTopNFilter :: new ( 3 , true , true ) ) ;
743+ let nulls_first_filter = Arc :: new ( int64_filter ( 3 , true , true ) ) ;
698744 nulls_first_filter. update ( & int64 ( 10 ) ) ;
699745 let mut nulls_first = RuntimeScanFilters :: default ( ) ;
700746 nulls_first. push ( nulls_first_filter) ;
701747 assert ! ( !nulls_first. should_prune( Some ( & columns) ) ) ;
702748
703- let desc_filter = Arc :: new ( RuntimeTopNFilter :: new ( 3 , false , false ) ) ;
749+ let desc_filter = Arc :: new ( int64_filter ( 3 , false , false ) ) ;
704750 desc_filter. update ( & int64 ( 10 ) ) ;
705751 let mut desc = RuntimeScanFilters :: default ( ) ;
706752 desc. push ( desc_filter) ;
0 commit comments