@@ -33,7 +33,7 @@ pub trait StatComparisonOp {
3333 const SELECT_LESS : bool ;
3434 const INCLUDE_EQUAL : bool ;
3535
36- fn estimate_minmax_range_true_count (
36+ fn range_true_count (
3737 ndv : Ndv ,
3838 cardinality : f64 ,
3939 cmp_min : Ordering ,
@@ -138,7 +138,7 @@ pub trait ConstantComparisonAdapter {
138138}
139139
140140impl < ' s , ' a , A : ConstantComparisonAdapter > ConstantComparison < ' s , ' a , A > {
141- pub fn from_constant_args ( stat : & ' s StatBinaryArg < ' a > ) -> Result < Option < ( Self , bool ) > , String > {
141+ pub fn from_args ( stat : & ' s StatBinaryArg < ' a > ) -> Result < Option < ( Self , bool ) > , String > {
142142 if let Some ( input) =
143143 Self :: new ( & stat. args [ 0 ] , & stat. args [ 1 ] , stat. cardinality ) ?. map ( |input| ( input, false ) )
144144 {
@@ -196,30 +196,32 @@ impl<'s, 'a, A: ConstantComparisonAdapter> ConstantComparison<'s, 'a, A> {
196196 }
197197
198198 pub fn boolean_stat ( & self , true_count : StatEstimate ) -> ReturnStat {
199+ let has_true = true_count. upper > 0.0 ;
200+ let has_false = true_count. lower < self . non_null_cardinality ;
201+ let boolean_domain = BooleanDomain {
202+ has_true,
203+ has_false,
204+ } ;
205+ let value_domain = ( has_true || has_false || self . null_count == 0 )
206+ . then ( || Box :: new ( Domain :: Boolean ( boolean_domain) ) ) ;
199207 let domain = if self . nullable {
200208 Domain :: Nullable ( NullableDomain {
201209 has_null : self . null_count != 0 ,
202- value : Some ( Box :: new ( Domain :: Boolean ( BooleanDomain {
203- has_true : true ,
204- has_false : true ,
205- } ) ) ) ,
210+ value : value_domain,
206211 } )
207212 } else {
208- Domain :: Boolean ( BooleanDomain {
209- has_true : true ,
210- has_false : true ,
211- } )
213+ Domain :: Boolean ( boolean_domain)
212214 } ;
213-
215+ let possible_values = has_true as u8 + has_false as u8 ;
214216 ReturnStat {
215217 domain,
216- ndv : Ndv :: Stat ( 2.0 ) ,
218+ ndv : Ndv :: Stat ( possible_values as f64 ) ,
217219 null_count : self . null_count ,
218220 distribution : OwnedDistribution :: Boolean ( BooleanDistribution { true_count } ) ,
219221 }
220222 }
221223
222- pub fn constant_equality_true_count (
224+ pub fn equality_true_count (
223225 & self ,
224226 minmax_cmp : Option < ( Ordering , Ordering ) > ,
225227 not_eq : bool ,
@@ -271,6 +273,131 @@ pub fn estimate_ndv_true_count(ndv: Ndv, not_eq: bool, cardinality: f64) -> Stat
271273 let expected = selectivity * cardinality;
272274 match ndv {
273275 Ndv :: Stat ( _) => StatEstimate :: exact ( expected) ,
274- Ndv :: Max ( _) => StatEstimate :: new ( 0.0 , expected, cardinality) ,
276+ Ndv :: Max ( _) => {
277+ if not_eq {
278+ StatEstimate :: new ( 0.0 , expected, expected)
279+ } else {
280+ StatEstimate :: new ( expected, expected, cardinality)
281+ }
282+ }
283+ }
284+ }
285+
286+ #[ cfg( test) ]
287+ mod tests {
288+ use super :: * ;
289+
290+ struct TestAdapter ;
291+
292+ impl ConstantComparisonAdapter for TestAdapter {
293+ type Value = ( ) ;
294+ type Domain = ( ) ;
295+
296+ fn constant ( _scalar : Scalar ) -> Result < Self :: Value , String > {
297+ unimplemented ! ( )
298+ }
299+
300+ fn domain ( _domain : & Domain ) -> Result < Self :: Domain , String > {
301+ unimplemented ! ( )
302+ }
303+
304+ fn compare ( _left : & Self :: Value , _right : & Self :: Value ) -> Ordering {
305+ unimplemented ! ( )
306+ }
307+ }
308+
309+ fn test_comparison < ' a > (
310+ stat : & ' a ArgStat < ' a > ,
311+ non_null_cardinality : f64 ,
312+ null_count : u64 ,
313+ nullable : bool ,
314+ ) -> ConstantComparison < ' a , ' a , TestAdapter > {
315+ ConstantComparison {
316+ stat,
317+ constant : ( ) ,
318+ domain : None ,
319+ non_null_cardinality,
320+ null_count,
321+ nullable,
322+ _a : PhantomData ,
323+ }
324+ }
325+
326+ #[ test]
327+ fn test_boolean_stat_uses_true_count_domain_and_ndv ( ) {
328+ let stat = ArgStat {
329+ domain : Domain :: Boolean ( BooleanDomain {
330+ has_true : true ,
331+ has_false : true ,
332+ } ) ,
333+ ndv : Ndv :: Stat ( 2.0 ) ,
334+ null_count : 0 ,
335+ distribution : crate :: stat_distribution:: BorrowedDistribution :: Unknown ,
336+ } ;
337+ let comparison = test_comparison ( & stat, 100.0 , 0 , false ) ;
338+
339+ let all_false = comparison. boolean_stat ( StatEstimate :: exact ( 0.0 ) ) ;
340+ assert_eq ! (
341+ all_false. domain,
342+ Domain :: Boolean ( BooleanDomain {
343+ has_true: false ,
344+ has_false: true ,
345+ } )
346+ ) ;
347+ assert ! ( matches!( all_false. ndv, Ndv :: Stat ( 1.0 ) ) ) ;
348+
349+ let all_true = comparison. boolean_stat ( StatEstimate :: exact ( 100.0 ) ) ;
350+ assert_eq ! (
351+ all_true. domain,
352+ Domain :: Boolean ( BooleanDomain {
353+ has_true: true ,
354+ has_false: false ,
355+ } )
356+ ) ;
357+ assert ! ( matches!( all_true. ndv, Ndv :: Stat ( 1.0 ) ) ) ;
358+
359+ let uncertain = comparison. boolean_stat ( StatEstimate :: new ( 10.0 , 10.0 , 100.0 ) ) ;
360+ assert_eq ! (
361+ uncertain. domain,
362+ Domain :: Boolean ( BooleanDomain {
363+ has_true: true ,
364+ has_false: true ,
365+ } )
366+ ) ;
367+ assert ! ( matches!( uncertain. ndv, Ndv :: Stat ( 2.0 ) ) ) ;
368+ }
369+
370+ #[ test]
371+ fn test_boolean_stat_omits_nullable_value_domain_without_non_null_values ( ) {
372+ let stat = ArgStat {
373+ domain : Domain :: Nullable ( NullableDomain {
374+ has_null : true ,
375+ value : None ,
376+ } ) ,
377+ ndv : Ndv :: Stat ( 0.0 ) ,
378+ null_count : 10 ,
379+ distribution : crate :: stat_distribution:: BorrowedDistribution :: Unknown ,
380+ } ;
381+ let comparison = test_comparison ( & stat, 0.0 , 10 , true ) ;
382+
383+ let output = comparison. boolean_stat ( StatEstimate :: exact ( 0.0 ) ) ;
384+ assert_eq ! (
385+ output. domain,
386+ Domain :: Nullable ( NullableDomain {
387+ has_null: true ,
388+ value: None ,
389+ } )
390+ ) ;
391+ assert ! ( matches!( output. ndv, Ndv :: Stat ( 0.0 ) ) ) ;
392+ output. check_consistency ( ) . unwrap ( ) ;
393+ }
394+
395+ #[ test]
396+ fn test_estimate_ndv_true_count_uses_max_ndv_bounds ( ) {
397+ let eq_count = estimate_ndv_true_count ( Ndv :: Max ( 10.0 ) , false , 100.0 ) ;
398+ assert_eq ! ( eq_count, StatEstimate :: new( 10.0 , 10.0 , 100.0 ) ) ;
399+
400+ let not_eq_count = estimate_ndv_true_count ( Ndv :: Max ( 10.0 ) , true , 100.0 ) ;
401+ assert_eq ! ( not_eq_count, StatEstimate :: new( 0.0 , 90.0 , 90.0 ) ) ;
275402 }
276403}
0 commit comments