1use std::fmt::{self, Debug};
90use std::sync::Arc;
91
92use datafusion_common::extensions::Extensions;
93use datafusion_common::stats::Precision;
94use datafusion_common::{Result, Statistics};
95
96use crate::ExecutionPlan;
97use crate::statistics::{StatisticsArgs, StatisticsContext};
98
99#[derive(Debug, Clone, Default)]
127pub struct ExtendedStatistics {
128 base: Arc<Statistics>,
130 extensions: Extensions,
132}
133
134impl ExtendedStatistics {
135 pub fn new(base: Statistics) -> Self {
137 Self {
138 base: Arc::new(base),
139 extensions: Extensions::new(),
140 }
141 }
142
143 pub fn new_arc(base: Arc<Statistics>) -> Self {
145 Self {
146 base,
147 extensions: Extensions::new(),
148 }
149 }
150
151 pub fn base(&self) -> &Statistics {
153 &self.base
154 }
155
156 pub fn base_arc(&self) -> &Arc<Statistics> {
158 &self.base
159 }
160
161 pub fn get_extension<T: 'static + Send + Sync>(&self) -> Option<&T> {
163 self.extensions.get::<T>()
164 }
165
166 pub fn set_extension<T: 'static + Send + Sync>(&mut self, value: T) {
168 self.extensions.insert(value);
169 }
170
171 pub fn has_extension<T: 'static + Send + Sync>(&self) -> bool {
173 self.extensions.contains::<T>()
174 }
175
176 pub fn merge_extensions(&mut self, other: &ExtendedStatistics) {
178 self.extensions.merge(&other.extensions);
179 }
180}
181
182impl From<Statistics> for ExtendedStatistics {
183 fn from(base: Statistics) -> Self {
184 Self::new(base)
185 }
186}
187
188impl From<Arc<Statistics>> for ExtendedStatistics {
189 fn from(base: Arc<Statistics>) -> Self {
190 Self::new_arc(base)
191 }
192}
193
194impl From<ExtendedStatistics> for Statistics {
195 fn from(extended: ExtendedStatistics) -> Self {
196 Arc::unwrap_or_clone(extended.base)
197 }
198}
199
200#[derive(Debug)]
206pub enum StatisticsResult {
207 Computed(ExtendedStatistics),
209 Delegate,
211}
212
213pub trait StatisticsProvider: Debug + Send + Sync {
242 fn compute_statistics(
253 &self,
254 plan: &dyn ExecutionPlan,
255 child_stats: &[ExtendedStatistics],
256 ) -> Result<StatisticsResult>;
257}
258
259#[derive(Debug, Default)]
262pub struct DefaultStatisticsProvider;
263
264impl StatisticsProvider for DefaultStatisticsProvider {
265 fn compute_statistics(
266 &self,
267 plan: &dyn ExecutionPlan,
268 _child_stats: &[ExtendedStatistics],
269 ) -> Result<StatisticsResult> {
270 let base = StatisticsContext::new().compute(plan, &StatisticsArgs::new())?;
271 Ok(StatisticsResult::Computed(ExtendedStatistics::new_arc(
272 base,
273 )))
274 }
275}
276
277#[derive(Clone)]
282pub struct StatisticsRegistry {
283 providers: Vec<Arc<dyn StatisticsProvider>>,
284}
285
286impl Debug for StatisticsRegistry {
287 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288 write!(f, "StatisticsRegistry({} providers)", self.providers.len())
289 }
290}
291
292impl Default for StatisticsRegistry {
293 fn default() -> Self {
294 Self::new()
295 }
296}
297
298impl StatisticsRegistry {
299 pub fn new() -> Self {
305 Self {
306 providers: Vec::new(),
307 }
308 }
309
310 pub fn with_providers(providers: Vec<Arc<dyn StatisticsProvider>>) -> Self {
312 Self { providers }
313 }
314
315 pub fn default_with_builtin_providers() -> Self {
327 Self::with_providers(vec![
328 Arc::new(FilterStatisticsProvider),
329 Arc::new(ProjectionStatisticsProvider),
330 Arc::new(PassthroughStatisticsProvider),
331 Arc::new(AggregateStatisticsProvider),
332 Arc::new(JoinStatisticsProvider),
333 Arc::new(LimitStatisticsProvider),
334 Arc::new(UnionStatisticsProvider),
335 Arc::new(DefaultStatisticsProvider),
336 ])
337 }
338
339 pub fn register(&mut self, provider: Arc<dyn StatisticsProvider>) {
341 self.providers.insert(0, provider);
342 }
343
344 pub fn providers(&self) -> &[Arc<dyn StatisticsProvider>] {
346 &self.providers
347 }
348
349 pub fn compute(&self, plan: &dyn ExecutionPlan) -> Result<ExtendedStatistics> {
360 if self.providers.is_empty() {
362 let base = StatisticsContext::new().compute(plan, &StatisticsArgs::new())?;
363 return Ok(ExtendedStatistics::new_arc(base));
364 }
365
366 let children = plan.children();
367
368 let child_stats: Vec<ExtendedStatistics> = if children.is_empty() {
371 Vec::new()
372 } else {
373 children
374 .iter()
375 .map(|child| self.compute(child.as_ref()))
376 .collect::<Result<Vec<_>>>()?
377 };
378
379 for provider in &self.providers {
380 match provider.compute_statistics(plan, &child_stats)? {
381 StatisticsResult::Computed(stats) => return Ok(stats),
382 StatisticsResult::Delegate => continue,
383 }
384 }
385 let base = StatisticsContext::new().compute(plan, &StatisticsArgs::new())?;
387 Ok(ExtendedStatistics::new_arc(base))
388 }
389
390 pub fn compute_base(&self, plan: &dyn ExecutionPlan) -> Result<Statistics> {
394 Ok(self.compute(plan)?.base().clone())
395 }
396}
397
398pub fn num_distinct_vals(domain_size: usize, num_selected: usize) -> usize {
415 if domain_size == 0 || num_selected == 0 {
416 return 0;
417 }
418
419 if num_selected >= domain_size {
420 return domain_size;
421 }
422
423 let n = domain_size as f64;
424 let k = num_selected as f64;
425
426 let expected = if domain_size > 1000 {
430 n * (1.0 - (-k / n).exp())
431 } else {
432 n * (1.0 - (1.0 - 1.0 / n).powf(k))
433 };
434
435 let result = expected.round() as usize;
436 result.clamp(1, domain_size)
437}
438
439pub fn ndv_after_selectivity(
450 original_ndv: usize,
451 original_rows: usize,
452 selectivity: f64,
453) -> usize {
454 if selectivity <= 0.0 || original_ndv == 0 || original_rows == 0 {
455 return 0;
456 }
457 if selectivity >= 1.0 {
458 return original_ndv;
459 }
460
461 let ndv = original_ndv as f64;
462 let rows = original_rows as f64;
463
464 let rows_per_value = rows / ndv;
465 let survival_prob = 1.0 - (1.0 - selectivity).powf(rows_per_value);
466 let expected_ndv = ndv * survival_prob;
467
468 (expected_ndv.round() as usize).clamp(1, original_ndv)
469}
470
471fn rescale_byte_size(stats: &mut Statistics, new_num_rows: Precision<usize>) {
478 let old_rows = stats.num_rows;
479 stats.num_rows = new_num_rows;
480 stats.total_byte_size = match (old_rows, new_num_rows, stats.total_byte_size) {
481 (Precision::Exact(old), Precision::Exact(new), Precision::Exact(bytes))
482 if old > 0 =>
483 {
484 Precision::Exact((bytes as f64 * new as f64 / old as f64).round() as usize)
485 }
486 _ => match (
487 old_rows.get_value(),
488 new_num_rows.get_value(),
489 stats.total_byte_size.get_value(),
490 ) {
491 (Some(&old), Some(&new), Some(&bytes)) if old > 0 => Precision::Inexact(
492 (bytes as f64 * new as f64 / old as f64).round() as usize,
493 ),
494 _ => stats.total_byte_size,
495 },
496 };
497}
498
499fn computed_with_row_count(
506 plan: &dyn ExecutionPlan,
507 num_rows: Precision<usize>,
508) -> Result<StatisticsResult> {
509 let mut base = Arc::unwrap_or_clone(
510 StatisticsContext::new().compute(plan, &StatisticsArgs::new())?,
511 );
512 rescale_byte_size(&mut base, num_rows);
513 Ok(StatisticsResult::Computed(ExtendedStatistics::new(base)))
514}
515
516#[derive(Debug, Default)]
526pub struct FilterStatisticsProvider;
527
528impl StatisticsProvider for FilterStatisticsProvider {
529 fn compute_statistics(
530 &self,
531 plan: &dyn ExecutionPlan,
532 child_stats: &[ExtendedStatistics],
533 ) -> Result<StatisticsResult> {
534 use crate::filter::FilterExec;
535
536 let Some(filter) = plan.downcast_ref::<FilterExec>() else {
537 return Ok(StatisticsResult::Delegate);
538 };
539 if child_stats.is_empty() {
540 return Ok(StatisticsResult::Delegate);
541 }
542
543 let input_stats = (*child_stats[0].base).clone();
544 let input_rows = input_stats.num_rows;
545 let mut stats = FilterExec::statistics_helper(
546 &filter.input().schema(),
547 input_stats,
548 filter.predicate(),
549 filter.default_selectivity(),
550 )?;
552
553 if let (Some(&orig_rows), Some(&filtered_rows)) =
557 (input_rows.get_value(), stats.num_rows.get_value())
558 && orig_rows > 0
559 && filtered_rows < orig_rows
560 {
561 let selectivity = filtered_rows as f64 / orig_rows as f64;
562 for col_stat in &mut stats.column_statistics {
563 if let Some(&ndv) = col_stat.distinct_count.get_value() {
564 let adjusted = ndv_after_selectivity(ndv, orig_rows, selectivity);
565 col_stat.distinct_count = Precision::Inexact(adjusted);
566 }
567 }
568 }
569
570 let stats = stats.project(filter.projection().as_ref());
571 Ok(StatisticsResult::Computed(ExtendedStatistics::new(stats)))
572 }
573}
574
575#[derive(Debug, Default)]
582pub struct ProjectionStatisticsProvider;
583
584impl StatisticsProvider for ProjectionStatisticsProvider {
585 fn compute_statistics(
586 &self,
587 plan: &dyn ExecutionPlan,
588 child_stats: &[ExtendedStatistics],
589 ) -> Result<StatisticsResult> {
590 use crate::projection::ProjectionExec;
591
592 let Some(proj) = plan.downcast_ref::<ProjectionExec>() else {
593 return Ok(StatisticsResult::Delegate);
594 };
595 if child_stats.is_empty() {
596 return Ok(StatisticsResult::Delegate);
597 }
598
599 let input_stats = (*child_stats[0].base).clone();
600 let output_schema = proj.schema();
601 let stats = proj
604 .projection_expr()
605 .project_statistics(input_stats, &output_schema)?;
606 Ok(StatisticsResult::Computed(ExtendedStatistics::new(stats)))
607 }
608}
609
610#[derive(Debug, Default)]
618pub struct PassthroughStatisticsProvider;
619
620impl StatisticsProvider for PassthroughStatisticsProvider {
621 fn compute_statistics(
622 &self,
623 plan: &dyn ExecutionPlan,
624 child_stats: &[ExtendedStatistics],
625 ) -> Result<StatisticsResult> {
626 use crate::execution_plan::CardinalityEffect;
627
628 if child_stats.len() != 1
629 || !matches!(plan.cardinality_effect(), CardinalityEffect::Equal)
630 {
631 return Ok(StatisticsResult::Delegate);
632 }
633
634 let input_cols = child_stats[0].base.column_statistics.len();
638 let output_cols = plan.schema().fields().len();
639 if input_cols != output_cols {
640 return Ok(StatisticsResult::Delegate);
641 }
642
643 Ok(StatisticsResult::Computed(child_stats[0].clone()))
644 }
645}
646
647#[derive(Debug, Default)]
666pub struct AggregateStatisticsProvider;
667
668impl StatisticsProvider for AggregateStatisticsProvider {
669 fn compute_statistics(
670 &self,
671 plan: &dyn ExecutionPlan,
672 child_stats: &[ExtendedStatistics],
673 ) -> Result<StatisticsResult> {
674 use crate::aggregates::AggregateExec;
675 use datafusion_physical_expr::expressions::Column;
676
677 use crate::aggregates::AggregateMode;
678
679 let Some(agg) = plan.downcast_ref::<AggregateExec>() else {
680 return Ok(StatisticsResult::Delegate);
681 };
682
683 if matches!(agg.mode(), AggregateMode::Partial) {
686 return Ok(StatisticsResult::Delegate);
687 }
688
689 if child_stats.is_empty() || agg.group_expr().expr().is_empty() {
690 return Ok(StatisticsResult::Delegate);
691 }
692
693 let input_stats = &child_stats[0].base;
694
695 let mut ndv_product: Option<usize> = None;
697 for (expr, _) in agg.group_expr().expr().iter() {
698 let Some(col) = expr.downcast_ref::<Column>() else {
699 return Ok(StatisticsResult::Delegate);
700 };
701 let Some(&ndv) = input_stats
702 .column_statistics
703 .get(col.index())
704 .and_then(|s| s.distinct_count.get_value())
705 else {
706 return Ok(StatisticsResult::Delegate);
707 };
708 if ndv == 0 {
709 return Ok(StatisticsResult::Delegate);
710 }
711 ndv_product = Some(match ndv_product {
712 Some(prev) => prev.saturating_mul(ndv),
713 None => ndv,
714 });
715 }
716
717 let Some(product) = ndv_product else {
718 return Ok(StatisticsResult::Delegate);
719 };
720
721 if agg.group_expr().groups().len() > 1 {
724 return Ok(StatisticsResult::Delegate);
725 }
726
727 let estimate = match input_stats.num_rows.get_value() {
729 Some(&rows) => product.min(rows),
730 None => product,
731 };
732
733 let num_rows = Precision::Inexact(estimate);
734
735 computed_with_row_count(plan, num_rows)
736 }
737}
738
739#[derive(Debug, Default)]
758pub struct JoinStatisticsProvider;
759
760impl StatisticsProvider for JoinStatisticsProvider {
761 fn compute_statistics(
762 &self,
763 plan: &dyn ExecutionPlan,
764 child_stats: &[ExtendedStatistics],
765 ) -> Result<StatisticsResult> {
766 use crate::joins::{CrossJoinExec, HashJoinExec, SortMergeJoinExec};
767 use datafusion_common::JoinType;
768 use datafusion_physical_expr::expressions::Column;
769
770 if child_stats.len() < 2 {
771 return Ok(StatisticsResult::Delegate);
772 }
773
774 let left = &child_stats[0].base;
775 let right = &child_stats[1].base;
776
777 let (Some(&left_rows), Some(&right_rows)) =
778 (left.num_rows.get_value(), right.num_rows.get_value())
779 else {
780 return Ok(StatisticsResult::Delegate);
781 };
782
783 use crate::joins::JoinOnRef;
784
785 fn equi_join_estimate(
789 on: JoinOnRef,
790 left: &Statistics,
791 right: &Statistics,
792 left_rows: usize,
793 right_rows: usize,
794 ) -> usize {
795 if on.is_empty() {
796 return left_rows.saturating_mul(right_rows);
797 }
798 let mut ndv_divisor: usize = 1;
799 for (left_key, right_key) in on {
800 let left_ndv = left_key
801 .downcast_ref::<Column>()
802 .and_then(|c| left.column_statistics.get(c.index()))
803 .and_then(|s| s.distinct_count.get_value().copied());
804 let right_ndv = right_key
805 .downcast_ref::<Column>()
806 .and_then(|c| right.column_statistics.get(c.index()))
807 .and_then(|s| s.distinct_count.get_value().copied());
808 match (left_ndv, right_ndv) {
809 (Some(l), Some(r)) if l > 0 && r > 0 => {
810 ndv_divisor = ndv_divisor.saturating_mul(l.max(r));
811 }
812 _ => return left_rows.saturating_mul(right_rows),
813 }
814 }
815 let max_rows = left_rows.saturating_mul(right_rows);
816 max_rows.checked_div(ndv_divisor).unwrap_or(max_rows)
817 }
818
819 let (inner_estimate, is_exact_cartesian, join_type) = if let Some(hash_join) =
820 plan.downcast_ref::<HashJoinExec>()
821 {
822 let est =
823 equi_join_estimate(hash_join.on(), left, right, left_rows, right_rows);
824 (est, false, *hash_join.join_type())
825 } else if let Some(smj) = plan.downcast_ref::<SortMergeJoinExec>() {
826 let est = equi_join_estimate(smj.on(), left, right, left_rows, right_rows);
827 (est, false, smj.join_type())
828 } else if plan.downcast_ref::<CrossJoinExec>().is_some() {
829 let both_exact = left.num_rows.is_exact().unwrap_or(false)
830 && right.num_rows.is_exact().unwrap_or(false);
831 (
832 left_rows.saturating_mul(right_rows),
833 both_exact,
834 JoinType::Inner,
835 )
836 } else {
837 return Ok(StatisticsResult::Delegate);
838 };
839
840 let estimated = match join_type {
842 JoinType::Inner => inner_estimate,
843 JoinType::Left => inner_estimate.max(left_rows),
844 JoinType::Right => inner_estimate.max(right_rows),
845 JoinType::Full => {
846 let outer_bound = left_rows
848 .saturating_add(right_rows)
849 .saturating_sub(inner_estimate);
850 inner_estimate.max(outer_bound)
851 }
852 JoinType::LeftSemi => inner_estimate.min(left_rows),
853 JoinType::RightSemi => inner_estimate.min(right_rows),
854 JoinType::LeftAnti => left_rows.saturating_sub(inner_estimate.min(left_rows)),
855 JoinType::RightAnti => {
856 right_rows.saturating_sub(inner_estimate.min(right_rows))
857 }
858 JoinType::LeftMark => left_rows,
859 JoinType::RightMark => right_rows,
860 };
861
862 let num_rows = if is_exact_cartesian && join_type == JoinType::Inner {
865 Precision::Exact(estimated)
866 } else {
867 Precision::Inexact(estimated)
868 };
869
870 computed_with_row_count(plan, num_rows)
871 }
872}
873
874#[derive(Debug, Default)]
880pub struct LimitStatisticsProvider;
881
882impl StatisticsProvider for LimitStatisticsProvider {
883 fn compute_statistics(
884 &self,
885 plan: &dyn ExecutionPlan,
886 child_stats: &[ExtendedStatistics],
887 ) -> Result<StatisticsResult> {
888 use crate::limit::{GlobalLimitExec, LocalLimitExec};
889
890 if child_stats.is_empty() {
891 return Ok(StatisticsResult::Delegate);
892 }
893
894 let (skip, fetch) = if let Some(limit) = plan.downcast_ref::<LocalLimitExec>() {
895 (0usize, Some(limit.fetch()))
896 } else if let Some(limit) = plan.downcast_ref::<GlobalLimitExec>() {
897 (limit.skip(), limit.fetch())
898 } else {
899 return Ok(StatisticsResult::Delegate);
900 };
901
902 let num_rows = match child_stats[0].base.num_rows {
903 Precision::Exact(rows) => {
904 let available = rows.saturating_sub(skip);
905 Precision::Exact(fetch.map_or(available, |f| available.min(f)))
906 }
907 Precision::Inexact(rows) => {
908 let available = rows.saturating_sub(skip);
909 match fetch {
910 Some(f) => Precision::Inexact(available.min(f)),
911 None => Precision::Inexact(available),
912 }
913 }
914 Precision::Absent => match fetch {
915 Some(f) => Precision::Inexact(f),
916 None => Precision::Absent,
917 },
918 };
919
920 computed_with_row_count(plan, num_rows)
921 }
922}
923
924#[derive(Debug, Default)]
928pub struct UnionStatisticsProvider;
929
930impl StatisticsProvider for UnionStatisticsProvider {
931 fn compute_statistics(
932 &self,
933 plan: &dyn ExecutionPlan,
934 child_stats: &[ExtendedStatistics],
935 ) -> Result<StatisticsResult> {
936 use crate::union::UnionExec;
937
938 if plan.downcast_ref::<UnionExec>().is_none() {
939 return Ok(StatisticsResult::Delegate);
940 }
941
942 let total = child_stats.iter().try_fold(
943 Precision::Exact(0usize),
944 |acc, s| -> Result<Precision<usize>> {
945 Ok(match (acc, s.base.num_rows) {
946 (Precision::Absent, _) | (_, Precision::Absent) => Precision::Absent,
947 (Precision::Exact(a), Precision::Exact(b)) => {
948 Precision::Exact(a.saturating_add(b))
949 }
950 (Precision::Inexact(a), Precision::Exact(b))
951 | (Precision::Exact(a), Precision::Inexact(b))
952 | (Precision::Inexact(a), Precision::Inexact(b)) => {
953 Precision::Inexact(a.saturating_add(b))
954 }
955 })
956 },
957 )?;
958
959 computed_with_row_count(plan, total)
960 }
961}
962
963type ProviderFn = dyn Fn(&dyn ExecutionPlan, &[ExtendedStatistics]) -> Result<StatisticsResult>
964 + Send
965 + Sync;
966
967pub struct ClosureStatisticsProvider {
993 f: Box<ProviderFn>,
994}
995
996impl ClosureStatisticsProvider {
997 pub fn new(
999 f: impl Fn(&dyn ExecutionPlan, &[ExtendedStatistics]) -> Result<StatisticsResult>
1000 + Send
1001 + Sync
1002 + 'static,
1003 ) -> Self {
1004 Self { f: Box::new(f) }
1005 }
1006}
1007
1008impl Debug for ClosureStatisticsProvider {
1009 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1010 write!(f, "ClosureStatisticsProvider")
1011 }
1012}
1013
1014impl StatisticsProvider for ClosureStatisticsProvider {
1015 fn compute_statistics(
1016 &self,
1017 plan: &dyn ExecutionPlan,
1018 child_stats: &[ExtendedStatistics],
1019 ) -> Result<StatisticsResult> {
1020 (self.f)(plan, child_stats)
1021 }
1022}
1023
1024#[cfg(test)]
1025mod tests {
1026 use super::*;
1027 use crate::filter::FilterExec;
1028 use crate::projection::ProjectionExec;
1029 use crate::statistics::StatisticsArgs;
1030 use crate::{
1031 ChildrenPropertiesMode, DisplayAs, DisplayFormatType, PlanProperties,
1032 ReplaceChildrenOptions,
1033 };
1034 use arrow::datatypes::{DataType, Field, Schema};
1035 use datafusion_common::stats::Precision;
1036 use datafusion_common::{ColumnStatistics, ScalarValue};
1037 use datafusion_expr::Operator;
1038 use datafusion_physical_expr::PhysicalExpr;
1039 use datafusion_physical_expr::expressions::{BinaryExpr, Column, Literal, col, lit};
1040 use datafusion_physical_expr::{EquivalenceProperties, Partitioning};
1041 use std::fmt;
1042
1043 use crate::execution_plan::{Boundedness, EmissionType};
1044 use datafusion_common::tree_node::TreeNodeRecursion;
1045
1046 fn make_schema() -> Arc<Schema> {
1047 Arc::new(Schema::new(vec![
1048 Field::new("a", DataType::Int32, false),
1049 Field::new("b", DataType::Int32, false),
1050 ]))
1051 }
1052
1053 #[derive(Debug)]
1054 struct MockSourceExec {
1055 schema: Arc<Schema>,
1056 stats: Statistics,
1057 cache: Arc<PlanProperties>,
1058 }
1059
1060 impl MockSourceExec {
1061 fn new(schema: Arc<Schema>, num_rows: Precision<usize>) -> Self {
1062 let num_cols = schema.fields().len();
1063 Self::with_column_stats(
1064 schema,
1065 num_rows,
1066 vec![ColumnStatistics::new_unknown(); num_cols],
1067 )
1068 }
1069
1070 fn with_column_stats(
1071 schema: Arc<Schema>,
1072 num_rows: Precision<usize>,
1073 column_statistics: Vec<ColumnStatistics>,
1074 ) -> Self {
1075 let eq_properties = EquivalenceProperties::new(Arc::clone(&schema));
1076 let cache = Arc::new(PlanProperties::new(
1077 eq_properties,
1078 Partitioning::UnknownPartitioning(1),
1079 EmissionType::Incremental,
1080 Boundedness::Bounded,
1081 ));
1082 Self {
1083 schema,
1084 stats: Statistics {
1085 num_rows,
1086 total_byte_size: Precision::Absent,
1087 column_statistics,
1088 },
1089 cache,
1090 }
1091 }
1092 }
1093
1094 impl DisplayAs for MockSourceExec {
1095 fn fmt_as(&self, _t: DisplayFormatType, f: &mut fmt::Formatter) -> fmt::Result {
1096 write!(f, "MockSourceExec")
1097 }
1098 }
1099
1100 impl ExecutionPlan for MockSourceExec {
1101 fn name(&self) -> &str {
1102 "MockSourceExec"
1103 }
1104
1105 fn schema(&self) -> Arc<Schema> {
1106 Arc::clone(&self.schema)
1107 }
1108
1109 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
1110 vec![]
1111 }
1112
1113 fn replace_children(
1114 self: Arc<Self>,
1115 _: Vec<Arc<dyn ExecutionPlan>>,
1116 _: ReplaceChildrenOptions,
1117 ) -> Result<Arc<dyn ExecutionPlan>> {
1118 Ok(self)
1119 }
1120
1121 fn with_new_children(
1122 self: Arc<Self>,
1123 children: Vec<Arc<dyn ExecutionPlan>>,
1124 ) -> Result<Arc<dyn ExecutionPlan>> {
1125 self.replace_children(
1126 children,
1127 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
1128 )
1129 }
1130
1131 fn properties(&self) -> &Arc<PlanProperties> {
1132 &self.cache
1133 }
1134
1135 fn apply_expressions(
1136 &self,
1137 _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
1138 ) -> Result<TreeNodeRecursion> {
1139 Ok(TreeNodeRecursion::Continue)
1140 }
1141
1142 fn execute(
1143 &self,
1144 _partition: usize,
1145 _context: Arc<datafusion_execution::TaskContext>,
1146 ) -> Result<crate::SendableRecordBatchStream> {
1147 unimplemented!()
1148 }
1149
1150 fn statistics_from_inputs(
1151 &self,
1152 _input_stats: &[Arc<Statistics>],
1153 _args: &StatisticsArgs,
1154 ) -> Result<Arc<Statistics>> {
1155 Ok(Arc::new(self.stats.clone()))
1156 }
1157 }
1158
1159 fn make_source(num_rows: usize) -> Arc<dyn ExecutionPlan> {
1160 Arc::new(MockSourceExec::new(
1161 make_schema(),
1162 Precision::Exact(num_rows),
1163 ))
1164 }
1165
1166 #[test]
1167 fn test_default_provider() -> Result<()> {
1168 let engine = StatisticsRegistry::new();
1169 let source = make_source(1000);
1170
1171 let stats = engine.compute(source.as_ref())?;
1172 assert!(matches!(stats.base.num_rows, Precision::Exact(1000)));
1173 Ok(())
1174 }
1175
1176 #[test]
1177 fn test_custom_chain_configuration() -> Result<()> {
1178 let source = make_source(1000);
1179
1180 let custom_only =
1182 StatisticsRegistry::with_providers(vec![Arc::new(CustomStatisticsProvider)]);
1183 let filter: Arc<dyn ExecutionPlan> =
1186 Arc::new(FilterExec::try_new(lit(true), Arc::clone(&source))?);
1187 let stats = custom_only.compute(filter.as_ref())?;
1188 assert!(stats.base.num_rows.get_value().is_some());
1190
1191 let with_override =
1193 StatisticsRegistry::with_providers(vec![Arc::new(OverrideFilterProvider {
1194 fixed_selectivity: 0.25,
1195 })
1196 as Arc<dyn StatisticsProvider>]);
1197 let stats = with_override.compute(filter.as_ref())?;
1199 assert!(matches!(stats.base.num_rows, Precision::Inexact(250)));
1200
1201 assert_eq!(with_override.providers().len(), 1);
1203
1204 Ok(())
1205 }
1206
1207 #[derive(Debug)]
1208 struct CustomExec {
1209 input: Arc<dyn ExecutionPlan>,
1210 }
1211
1212 impl DisplayAs for CustomExec {
1213 fn fmt_as(&self, _t: DisplayFormatType, f: &mut fmt::Formatter) -> fmt::Result {
1214 write!(f, "CustomExec")
1215 }
1216 }
1217
1218 impl ExecutionPlan for CustomExec {
1219 fn name(&self) -> &str {
1220 "CustomExec"
1221 }
1222
1223 fn schema(&self) -> Arc<Schema> {
1224 self.input.schema()
1225 }
1226
1227 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
1228 vec![&self.input]
1229 }
1230
1231 fn replace_children(
1232 self: Arc<Self>,
1233 children: Vec<Arc<dyn ExecutionPlan>>,
1234 _: ReplaceChildrenOptions,
1235 ) -> Result<Arc<dyn ExecutionPlan>> {
1236 Ok(Arc::new(CustomExec {
1237 input: Arc::clone(&children[0]),
1238 }))
1239 }
1240
1241 fn with_new_children(
1242 self: Arc<Self>,
1243 children: Vec<Arc<dyn ExecutionPlan>>,
1244 ) -> Result<Arc<dyn ExecutionPlan>> {
1245 self.replace_children(
1246 children,
1247 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
1248 )
1249 }
1250
1251 fn properties(&self) -> &Arc<PlanProperties> {
1252 self.input.properties()
1253 }
1254
1255 fn apply_expressions(
1256 &self,
1257 _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
1258 ) -> Result<TreeNodeRecursion> {
1259 Ok(TreeNodeRecursion::Continue)
1260 }
1261
1262 fn execute(
1263 &self,
1264 _partition: usize,
1265 _context: Arc<datafusion_execution::TaskContext>,
1266 ) -> Result<crate::SendableRecordBatchStream> {
1267 unimplemented!()
1268 }
1269 }
1270
1271 #[derive(Debug)]
1272 struct CustomStatisticsProvider;
1273
1274 impl StatisticsProvider for CustomStatisticsProvider {
1275 fn compute_statistics(
1276 &self,
1277 plan: &dyn ExecutionPlan,
1278 child_stats: &[ExtendedStatistics],
1279 ) -> Result<StatisticsResult> {
1280 if plan.downcast_ref::<CustomExec>().is_some() {
1281 Ok(StatisticsResult::Computed(child_stats[0].clone()))
1282 } else {
1283 Ok(StatisticsResult::Delegate)
1284 }
1285 }
1286 }
1287
1288 #[test]
1289 fn test_custom_provider_for_custom_exec() -> Result<()> {
1290 let mut engine = StatisticsRegistry::new();
1291 engine.register(Arc::new(CustomStatisticsProvider));
1292
1293 let source = make_source(1000);
1294 let custom: Arc<dyn ExecutionPlan> = Arc::new(CustomExec { input: source });
1295
1296 let stats = engine.compute(custom.as_ref())?;
1297 assert!(matches!(stats.base.num_rows, Precision::Exact(1000)));
1298 Ok(())
1299 }
1300
1301 #[derive(Debug)]
1302 struct OverrideFilterProvider {
1303 fixed_selectivity: f64,
1304 }
1305
1306 impl StatisticsProvider for OverrideFilterProvider {
1307 fn compute_statistics(
1308 &self,
1309 plan: &dyn ExecutionPlan,
1310 child_stats: &[ExtendedStatistics],
1311 ) -> Result<StatisticsResult> {
1312 if plan.downcast_ref::<FilterExec>().is_some() {
1313 if let Some(&input_rows) = child_stats[0].base.num_rows.get_value() {
1314 let estimated = (input_rows as f64 * self.fixed_selectivity) as usize;
1315 Ok(StatisticsResult::Computed(ExtendedStatistics::from(
1316 Statistics {
1317 num_rows: Precision::Inexact(estimated),
1318 total_byte_size: Precision::Absent,
1319 column_statistics: child_stats[0]
1320 .base
1321 .column_statistics
1322 .clone(),
1323 },
1324 )))
1325 } else {
1326 Ok(StatisticsResult::Delegate)
1327 }
1328 } else {
1329 Ok(StatisticsResult::Delegate)
1330 }
1331 }
1332 }
1333
1334 #[test]
1335 fn test_override_builtin_operator() -> Result<()> {
1336 let mut engine = StatisticsRegistry::new();
1337 engine.register(Arc::new(OverrideFilterProvider {
1338 fixed_selectivity: 0.1,
1339 }));
1340
1341 let source = make_source(1000);
1342 let filter: Arc<dyn ExecutionPlan> =
1343 Arc::new(FilterExec::try_new(lit(true), source)?);
1344
1345 let stats = engine.compute(filter.as_ref())?;
1346 assert!(matches!(stats.base.num_rows, Precision::Inexact(100)));
1347 Ok(())
1348 }
1349
1350 #[test]
1351 fn test_filter_statistics_propagation() -> Result<()> {
1352 let engine = StatisticsRegistry::new();
1353 let source = make_source(1000);
1354 let predicate = lit(true);
1355 let filter: Arc<dyn ExecutionPlan> =
1356 Arc::new(FilterExec::try_new(predicate, source)?);
1357
1358 let stats = engine.compute(filter.as_ref())?;
1359 assert!(stats.base.num_rows.get_value().unwrap_or(&0) <= &1000);
1360 Ok(())
1361 }
1362
1363 #[test]
1364 fn test_filter_adjusts_ndv_by_selectivity() -> Result<()> {
1365 use datafusion_common::ScalarValue;
1366 use datafusion_expr::Operator;
1367 use datafusion_physical_expr::expressions::{
1368 BinaryExpr, Column as PhysColumn, Literal,
1369 };
1370
1371 let schema = make_schema(); let col_stats = vec![
1376 {
1377 let mut cs = ColumnStatistics::new_unknown();
1378 cs.distinct_count = Precision::Exact(1000);
1379 cs.min_value = Precision::Exact(ScalarValue::Int32(Some(1)));
1380 cs.max_value = Precision::Exact(ScalarValue::Int32(Some(1000)));
1381 cs
1382 },
1383 {
1384 let mut cs = ColumnStatistics::new_unknown();
1385 cs.distinct_count = Precision::Exact(800);
1386 cs.min_value = Precision::Exact(ScalarValue::Int32(Some(1)));
1387 cs.max_value = Precision::Exact(ScalarValue::Int32(Some(800)));
1388 cs
1389 },
1390 ];
1391 let source: Arc<dyn ExecutionPlan> = Arc::new(MockSourceExec::with_column_stats(
1392 schema,
1393 Precision::Exact(1000),
1394 col_stats,
1395 ));
1396
1397 let predicate: Arc<dyn PhysicalExpr> = Arc::new(BinaryExpr::new(
1399 Arc::new(PhysColumn::new("a", 0)),
1400 Operator::Gt,
1401 Arc::new(Literal::new(ScalarValue::Int32(Some(900)))),
1402 ));
1403 let filter: Arc<dyn ExecutionPlan> =
1404 Arc::new(FilterExec::try_new(predicate, source)?);
1405
1406 let registry = StatisticsRegistry::with_providers(vec![
1407 Arc::new(FilterStatisticsProvider),
1408 Arc::new(DefaultStatisticsProvider),
1409 ]);
1410 let stats = registry.compute(filter.as_ref())?;
1411
1412 let output_ndv_a = stats.base.column_statistics[0]
1413 .distinct_count
1414 .get_value()
1415 .copied()
1416 .unwrap_or(0);
1417 let output_ndv_b = stats.base.column_statistics[1]
1418 .distinct_count
1419 .get_value()
1420 .copied()
1421 .unwrap_or(0);
1422
1423 assert!(
1425 output_ndv_a <= 100,
1426 "Expected NDV(a) <= 100 after filter, got {output_ndv_a}"
1427 );
1428 assert!(
1432 output_ndv_b < 200,
1433 "Expected NDV(b) < 200 after filter, got {output_ndv_b}"
1434 );
1435 Ok(())
1436 }
1437
1438 #[test]
1439 fn test_projection_statistics_propagation() -> Result<()> {
1440 let engine = StatisticsRegistry::new();
1441 let source = make_source(1000);
1442 let schema = make_schema();
1443 let proj: Arc<dyn ExecutionPlan> = Arc::new(ProjectionExec::try_new(
1444 vec![(col("a", &schema)?, "a".to_string())],
1445 source,
1446 )?);
1447
1448 let stats = engine.compute(proj.as_ref())?;
1449 assert!(matches!(stats.base.num_rows, Precision::Exact(1000)));
1450 Ok(())
1451 }
1452
1453 #[test]
1454 fn test_passthrough_statistics_propagation() -> Result<()> {
1455 use crate::coalesce_partitions::CoalescePartitionsExec;
1456
1457 let engine = StatisticsRegistry::new();
1458 let source = make_source(1000);
1459 let coalesce: Arc<dyn ExecutionPlan> =
1460 Arc::new(CoalescePartitionsExec::new(source));
1461
1462 let stats = engine.compute(coalesce.as_ref())?;
1463 assert_eq!(stats.base.num_rows, Precision::Exact(1000));
1465 Ok(())
1466 }
1467
1468 #[test]
1469 fn test_chain_priority() -> Result<()> {
1470 let mut engine = StatisticsRegistry::new();
1471 engine.register(Arc::new(OverrideFilterProvider {
1472 fixed_selectivity: 0.5,
1473 }));
1474 engine.register(Arc::new(CustomStatisticsProvider));
1475
1476 let source = make_source(1000);
1477
1478 let custom: Arc<dyn ExecutionPlan> = Arc::new(CustomExec {
1480 input: Arc::clone(&source),
1481 });
1482 let stats = engine.compute(custom.as_ref())?;
1483 assert!(matches!(stats.base.num_rows, Precision::Exact(1000)));
1484
1485 let filter: Arc<dyn ExecutionPlan> =
1487 Arc::new(FilterExec::try_new(lit(true), source)?);
1488 let stats = engine.compute(filter.as_ref())?;
1489 assert!(matches!(stats.base.num_rows, Precision::Inexact(500)));
1490
1491 Ok(())
1492 }
1493
1494 #[test]
1499 fn test_num_distinct_vals_basic() {
1500 assert_eq!(num_distinct_vals(0, 100), 0);
1501 assert_eq!(num_distinct_vals(100, 0), 0);
1502 assert_eq!(num_distinct_vals(100, 100), 100);
1503 assert_eq!(num_distinct_vals(100, 200), 100);
1504
1505 let ndv = num_distinct_vals(1000, 100);
1506 assert!((90..=100).contains(&ndv), "Expected ~95, got {ndv}");
1507
1508 let ndv = num_distinct_vals(1000, 500);
1509 assert!((350..=450).contains(&ndv), "Expected ~393, got {ndv}");
1510
1511 let ndv = num_distinct_vals(1_000_000, 10_000);
1512 assert!((9900..=10000).contains(&ndv), "Expected ~9950, got {ndv}");
1513
1514 let ndv = num_distinct_vals(1_000_000, 100);
1515 assert!((99..=100).contains(&ndv), "Expected ~100, got {ndv}");
1516 }
1517
1518 #[test]
1519 fn test_num_distinct_vals_small_domain() {
1520 let ndv = num_distinct_vals(10, 5);
1521 assert!((3..=5).contains(&ndv), "Expected ~4, got {ndv}");
1522
1523 assert_eq!(num_distinct_vals(10, 20), 10);
1524 assert_eq!(num_distinct_vals(10, 1), 1);
1525 }
1526
1527 #[test]
1528 fn test_ndv_after_selectivity() {
1529 let ndv = ndv_after_selectivity(1000, 10000, 0.1);
1530 assert!((600..=700).contains(&ndv), "Expected ~632, got {ndv}");
1531
1532 let ndv = ndv_after_selectivity(1000, 10000, 0.01);
1533 assert!((90..=100).contains(&ndv), "Expected ~95, got {ndv}");
1534
1535 assert_eq!(ndv_after_selectivity(1000, 10000, 0.0), 0);
1536 assert_eq!(ndv_after_selectivity(1000, 10000, 1.0), 1000);
1537 assert_eq!(ndv_after_selectivity(0, 10000, 0.5), 0);
1538 }
1539
1540 use crate::aggregates::{AggregateExec, AggregateMode, PhysicalGroupBy};
1545
1546 fn make_source_with_ndv(
1547 num_rows: usize,
1548 col_ndvs: Vec<Option<usize>>,
1549 ) -> Arc<dyn ExecutionPlan> {
1550 let fields: Vec<Field> = col_ndvs
1551 .iter()
1552 .enumerate()
1553 .map(|(i, _)| Field::new(format!("c{i}"), DataType::Int32, false))
1554 .collect();
1555 let schema = Arc::new(Schema::new(fields));
1556 let col_stats = col_ndvs
1557 .into_iter()
1558 .map(|ndv| {
1559 let mut cs = ColumnStatistics::new_unknown();
1560 if let Some(n) = ndv {
1561 cs.distinct_count = Precision::Exact(n);
1562 }
1563 cs
1564 })
1565 .collect();
1566 Arc::new(MockSourceExec::with_column_stats(
1567 schema,
1568 Precision::Exact(num_rows),
1569 col_stats,
1570 ))
1571 }
1572
1573 fn make_aggregate(
1574 input: Arc<dyn ExecutionPlan>,
1575 group_by: PhysicalGroupBy,
1576 ) -> Result<Arc<dyn ExecutionPlan>> {
1577 Ok(Arc::new(AggregateExec::try_new(
1578 AggregateMode::Single,
1579 group_by,
1580 vec![],
1581 vec![],
1582 Arc::clone(&input),
1583 input.schema(),
1584 )?))
1585 }
1586
1587 #[test]
1588 fn test_aggregate_provider_with_ndv() -> Result<()> {
1589 let source = make_source_with_ndv(100, vec![Some(10)]);
1590 let group_by = PhysicalGroupBy::new_single(vec![(
1591 Arc::new(Column::new("c0", 0)),
1592 "c0".to_string(),
1593 )]);
1594 let agg = make_aggregate(source, group_by)?;
1595
1596 let registry = StatisticsRegistry::with_providers(vec![
1597 Arc::new(AggregateStatisticsProvider),
1598 Arc::new(DefaultStatisticsProvider),
1599 ]);
1600 let stats = registry.compute(agg.as_ref())?;
1601 assert_eq!(stats.base.num_rows, Precision::Inexact(10));
1602 Ok(())
1603 }
1604
1605 #[test]
1606 fn test_aggregate_provider_multi_column() -> Result<()> {
1607 let source = make_source_with_ndv(1000, vec![Some(10), Some(5)]);
1608 let group_by = PhysicalGroupBy::new_single(vec![
1609 (Arc::new(Column::new("c0", 0)), "c0".to_string()),
1610 (Arc::new(Column::new("c1", 1)), "c1".to_string()),
1611 ]);
1612 let agg = make_aggregate(source, group_by)?;
1613
1614 let registry = StatisticsRegistry::with_providers(vec![
1615 Arc::new(AggregateStatisticsProvider),
1616 Arc::new(DefaultStatisticsProvider),
1617 ]);
1618 let stats = registry.compute(agg.as_ref())?;
1619 assert_eq!(stats.base.num_rows, Precision::Inexact(50));
1621 Ok(())
1622 }
1623
1624 #[test]
1625 fn test_aggregate_provider_caps_at_input_rows() -> Result<()> {
1626 let source = make_source_with_ndv(500, vec![Some(100), Some(100)]);
1628 let group_by = PhysicalGroupBy::new_single(vec![
1629 (Arc::new(Column::new("c0", 0)), "c0".to_string()),
1630 (Arc::new(Column::new("c1", 1)), "c1".to_string()),
1631 ]);
1632 let agg = make_aggregate(source, group_by)?;
1633
1634 let registry = StatisticsRegistry::with_providers(vec![
1635 Arc::new(AggregateStatisticsProvider),
1636 Arc::new(DefaultStatisticsProvider),
1637 ]);
1638 let stats = registry.compute(agg.as_ref())?;
1639 assert_eq!(stats.base.num_rows, Precision::Inexact(500));
1640 Ok(())
1641 }
1642
1643 #[test]
1644 fn test_aggregate_provider_no_ndv_delegates() -> Result<()> {
1645 let source = make_source_with_ndv(100, vec![None]);
1647 let group_by = PhysicalGroupBy::new_single(vec![(
1648 Arc::new(Column::new("c0", 0)),
1649 "c0".to_string(),
1650 )]);
1651 let agg = make_aggregate(source, group_by)?;
1652
1653 let registry = StatisticsRegistry::with_providers(vec![
1654 Arc::new(AggregateStatisticsProvider),
1655 Arc::new(DefaultStatisticsProvider),
1656 ]);
1657 let stats = registry.compute(agg.as_ref())?;
1658 assert!(
1660 stats.base.num_rows.get_value().is_some()
1661 || matches!(stats.base.num_rows, Precision::Absent)
1662 );
1663 Ok(())
1664 }
1665
1666 #[test]
1667 fn test_aggregate_provider_non_column_expr_delegates() -> Result<()> {
1668 let source = make_source_with_ndv(100, vec![Some(10), Some(5)]);
1669 let expr: Arc<dyn PhysicalExpr> = Arc::new(BinaryExpr::new(
1671 Arc::new(Column::new("c0", 0)),
1672 Operator::Plus,
1673 Arc::new(Column::new("c1", 1)),
1674 ));
1675 let group_by = PhysicalGroupBy::new_single(vec![(expr, "sum".to_string())]);
1676 let agg = make_aggregate(source, group_by)?;
1677
1678 let registry = StatisticsRegistry::with_providers(vec![
1679 Arc::new(AggregateStatisticsProvider),
1680 Arc::new(DefaultStatisticsProvider),
1681 ]);
1682 let stats = registry.compute(agg.as_ref())?;
1683 assert!(
1685 stats.base.num_rows.get_value().is_some()
1686 || matches!(stats.base.num_rows, Precision::Absent)
1687 );
1688 Ok(())
1689 }
1690
1691 #[test]
1692 fn test_aggregate_provider_grouping_sets() -> Result<()> {
1693 let source = make_source_with_ndv(1000, vec![Some(10), Some(5)]);
1694 let group_by = PhysicalGroupBy::new(
1696 vec![
1697 (Arc::new(Column::new("c0", 0)), "c0".to_string()),
1698 (Arc::new(Column::new("c1", 1)), "c1".to_string()),
1699 ],
1700 vec![
1701 (
1702 Arc::new(Literal::new(ScalarValue::Int32(None))),
1703 "c0".to_string(),
1704 ),
1705 (
1706 Arc::new(Literal::new(ScalarValue::Int32(None))),
1707 "c1".to_string(),
1708 ),
1709 ],
1710 vec![
1711 vec![false, true], vec![true, false], vec![false, false], ],
1715 true,
1716 );
1717 let agg = make_aggregate(source, group_by)?;
1718
1719 let registry = StatisticsRegistry::with_providers(vec![
1720 Arc::new(AggregateStatisticsProvider),
1721 Arc::new(DefaultStatisticsProvider),
1722 ]);
1723 let stats = registry.compute(agg.as_ref())?;
1724 assert!(
1728 stats.base.num_rows.get_value().is_some()
1729 || matches!(stats.base.num_rows, Precision::Absent)
1730 );
1731 Ok(())
1732 }
1733
1734 #[test]
1735 fn test_aggregate_provider_partial_delegates() -> Result<()> {
1736 let source = make_source_with_ndv(100, vec![Some(10)]);
1739 let group_by = PhysicalGroupBy::new_single(vec![(
1740 Arc::new(Column::new("c0", 0)),
1741 "c0".to_string(),
1742 )]);
1743 let agg: Arc<dyn ExecutionPlan> = Arc::new(AggregateExec::try_new(
1744 AggregateMode::Partial,
1745 group_by,
1746 vec![],
1747 vec![],
1748 Arc::clone(&source),
1749 source.schema(),
1750 )?);
1751
1752 let registry = StatisticsRegistry::with_providers(vec![
1753 Arc::new(AggregateStatisticsProvider),
1754 Arc::new(DefaultStatisticsProvider),
1755 ]);
1756 let stats = registry.compute(agg.as_ref())?;
1757 assert!(
1760 stats.base.num_rows.get_value().is_some()
1761 || matches!(stats.base.num_rows, Precision::Absent)
1762 );
1763 Ok(())
1764 }
1765
1766 use crate::joins::{HashJoinExec, PartitionMode};
1771 use datafusion_common::{JoinType, NullEquality};
1772
1773 fn make_source_with_ndv_2col(
1774 num_rows: usize,
1775 ndv_a: Option<usize>,
1776 ) -> Arc<dyn ExecutionPlan> {
1777 let schema = make_schema(); let col_stats = vec![
1779 {
1780 let mut cs = ColumnStatistics::new_unknown();
1781 if let Some(n) = ndv_a {
1782 cs.distinct_count = Precision::Exact(n);
1783 }
1784 cs
1785 },
1786 ColumnStatistics::new_unknown(),
1787 ];
1788 Arc::new(MockSourceExec::with_column_stats(
1789 schema,
1790 Precision::Exact(num_rows),
1791 col_stats,
1792 ))
1793 }
1794
1795 fn make_hash_join(
1796 left: Arc<dyn ExecutionPlan>,
1797 right: Arc<dyn ExecutionPlan>,
1798 ) -> Result<Arc<dyn ExecutionPlan>> {
1799 let _schema = make_schema();
1800 let on: crate::joins::JoinOn = vec![(
1801 Arc::new(Column::new("a", 0)) as Arc<dyn PhysicalExpr>,
1802 Arc::new(Column::new("a", 0)) as Arc<dyn PhysicalExpr>,
1803 )];
1804 Ok(Arc::new(HashJoinExec::try_new(
1805 left,
1806 right,
1807 on,
1808 None,
1809 &JoinType::Inner,
1810 None,
1811 PartitionMode::CollectLeft,
1812 NullEquality::NullEqualsNull,
1813 false,
1814 )?))
1815 }
1816
1817 #[test]
1818 fn test_join_provider_with_ndv() -> Result<()> {
1819 let left = make_source_with_ndv_2col(1000, Some(100));
1822 let right = make_source_with_ndv_2col(500, Some(50));
1823 let join = make_hash_join(left, right)?;
1824
1825 let registry = StatisticsRegistry::with_providers(vec![
1826 Arc::new(JoinStatisticsProvider),
1827 Arc::new(DefaultStatisticsProvider),
1828 ]);
1829 let stats = registry.compute(join.as_ref())?;
1830 assert_eq!(stats.base.num_rows, Precision::Inexact(5000));
1831 Ok(())
1832 }
1833
1834 #[test]
1835 fn test_join_provider_uses_actual_key_column_ndv() -> Result<()> {
1836 let schema = make_schema(); let make_source_ndv_b =
1843 |num_rows: usize, ndv_b: usize| -> Arc<dyn ExecutionPlan> {
1844 let col_stats = vec![
1845 ColumnStatistics::new_unknown(), {
1847 let mut cs = ColumnStatistics::new_unknown();
1848 cs.distinct_count = Precision::Exact(ndv_b);
1849 cs
1850 },
1851 ];
1852 Arc::new(MockSourceExec::with_column_stats(
1853 Arc::clone(&schema),
1854 Precision::Exact(num_rows),
1855 col_stats,
1856 ))
1857 };
1858
1859 let left = make_source_ndv_b(1000, 50);
1860 let right = make_source_ndv_b(500, 25);
1861
1862 let on: crate::joins::JoinOn = vec![(
1864 Arc::new(Column::new("b", 1)) as Arc<dyn PhysicalExpr>,
1865 Arc::new(Column::new("b", 1)) as Arc<dyn PhysicalExpr>,
1866 )];
1867 let join: Arc<dyn ExecutionPlan> = Arc::new(HashJoinExec::try_new(
1868 left,
1869 right,
1870 on,
1871 None,
1872 &JoinType::Inner,
1873 None,
1874 PartitionMode::CollectLeft,
1875 NullEquality::NullEqualsNull,
1876 false,
1877 )?);
1878
1879 let registry = StatisticsRegistry::with_providers(vec![
1880 Arc::new(JoinStatisticsProvider),
1881 Arc::new(DefaultStatisticsProvider),
1882 ]);
1883 let stats = registry.compute(join.as_ref())?;
1884 assert_eq!(stats.base.num_rows, Precision::Inexact(10_000));
1885 Ok(())
1886 }
1887
1888 #[test]
1889 fn test_join_provider_multi_key_ndv() -> Result<()> {
1890 let schema = make_schema(); let make_source_2ndv =
1896 |num_rows: usize, ndv_a: usize, ndv_b: usize| -> Arc<dyn ExecutionPlan> {
1897 let col_stats = vec![
1898 {
1899 let mut cs = ColumnStatistics::new_unknown();
1900 cs.distinct_count = Precision::Exact(ndv_a);
1901 cs
1902 },
1903 {
1904 let mut cs = ColumnStatistics::new_unknown();
1905 cs.distinct_count = Precision::Exact(ndv_b);
1906 cs
1907 },
1908 ];
1909 Arc::new(MockSourceExec::with_column_stats(
1910 Arc::clone(&schema),
1911 Precision::Exact(num_rows),
1912 col_stats,
1913 ))
1914 };
1915
1916 let left = make_source_2ndv(1000, 100, 20);
1917 let right = make_source_2ndv(500, 50, 10);
1918
1919 let on: crate::joins::JoinOn = vec![
1920 (
1921 Arc::new(Column::new("a", 0)) as Arc<dyn PhysicalExpr>,
1922 Arc::new(Column::new("a", 0)) as Arc<dyn PhysicalExpr>,
1923 ),
1924 (
1925 Arc::new(Column::new("b", 1)) as Arc<dyn PhysicalExpr>,
1926 Arc::new(Column::new("b", 1)) as Arc<dyn PhysicalExpr>,
1927 ),
1928 ];
1929 let join: Arc<dyn ExecutionPlan> = Arc::new(HashJoinExec::try_new(
1930 left,
1931 right,
1932 on,
1933 None,
1934 &JoinType::Inner,
1935 None,
1936 PartitionMode::CollectLeft,
1937 NullEquality::NullEqualsNull,
1938 false,
1939 )?);
1940
1941 let registry = StatisticsRegistry::with_providers(vec![
1942 Arc::new(JoinStatisticsProvider),
1943 Arc::new(DefaultStatisticsProvider),
1944 ]);
1945 let stats = registry.compute(join.as_ref())?;
1946 assert_eq!(stats.base.num_rows, Precision::Inexact(250));
1947 Ok(())
1948 }
1949
1950 #[test]
1951 fn test_join_provider_fallback_cartesian() -> Result<()> {
1952 let left = make_source_with_ndv_2col(100, None);
1954 let right = make_source_with_ndv_2col(200, None);
1955 let join = make_hash_join(left, right)?;
1956
1957 let registry = StatisticsRegistry::with_providers(vec![
1958 Arc::new(JoinStatisticsProvider),
1959 Arc::new(DefaultStatisticsProvider),
1960 ]);
1961 let stats = registry.compute(join.as_ref())?;
1962 assert_eq!(stats.base.num_rows, Precision::Inexact(20_000));
1963 Ok(())
1964 }
1965
1966 #[test]
1967 fn test_nl_join_delegates() -> Result<()> {
1968 use crate::joins::NestedLoopJoinExec;
1969
1970 let left = make_source(100);
1973 let right = make_source(200);
1974 let join: Arc<dyn ExecutionPlan> = Arc::new(NestedLoopJoinExec::try_new(
1975 left,
1976 right,
1977 None,
1978 &JoinType::Inner,
1979 None,
1980 )?);
1981
1982 let registry = StatisticsRegistry::with_providers(vec![
1983 Arc::new(JoinStatisticsProvider),
1984 Arc::new(DefaultStatisticsProvider),
1985 ]);
1986 let stats = registry.compute(join.as_ref())?;
1987 assert!(
1989 stats.base.num_rows.get_value().is_some()
1990 || matches!(stats.base.num_rows, Precision::Absent)
1991 );
1992 Ok(())
1993 }
1994
1995 fn make_hash_join_typed(
1996 left: Arc<dyn ExecutionPlan>,
1997 right: Arc<dyn ExecutionPlan>,
1998 join_type: JoinType,
1999 ) -> Result<Arc<dyn ExecutionPlan>> {
2000 let on: crate::joins::JoinOn = vec![(
2001 Arc::new(Column::new("a", 0)) as Arc<dyn PhysicalExpr>,
2002 Arc::new(Column::new("a", 0)) as Arc<dyn PhysicalExpr>,
2003 )];
2004 Ok(Arc::new(HashJoinExec::try_new(
2005 left,
2006 right,
2007 on,
2008 None,
2009 &join_type,
2010 None,
2011 PartitionMode::CollectLeft,
2012 NullEquality::NullEqualsNull,
2013 false,
2014 )?))
2015 }
2016
2017 fn compute_join_rows(
2018 left_rows: usize,
2019 left_ndv: Option<usize>,
2020 right_rows: usize,
2021 right_ndv: Option<usize>,
2022 join_type: JoinType,
2023 ) -> Result<Precision<usize>> {
2024 let left = make_source_with_ndv_2col(left_rows, left_ndv);
2025 let right = make_source_with_ndv_2col(right_rows, right_ndv);
2026 let join = make_hash_join_typed(left, right, join_type)?;
2027 let registry = StatisticsRegistry::with_providers(vec![
2028 Arc::new(JoinStatisticsProvider),
2029 Arc::new(DefaultStatisticsProvider),
2030 ]);
2031 Ok(registry.compute(join.as_ref())?.base.num_rows)
2032 }
2033
2034 #[test]
2035 fn test_join_provider_left_outer() -> Result<()> {
2036 assert_eq!(
2040 compute_join_rows(1000, Some(100), 500, Some(50), JoinType::Left)?,
2041 Precision::Inexact(5000)
2042 );
2043 assert_eq!(
2046 compute_join_rows(1000, Some(100), 10, Some(100), JoinType::Left)?,
2047 Precision::Inexact(1000)
2048 );
2049 Ok(())
2050 }
2051
2052 #[test]
2053 fn test_join_provider_right_outer() -> Result<()> {
2054 assert_eq!(
2056 compute_join_rows(1000, Some(100), 10, Some(100), JoinType::Right)?,
2057 Precision::Inexact(100)
2058 );
2059 assert_eq!(
2061 compute_join_rows(10, Some(100), 1000, Some(100), JoinType::Right)?,
2062 Precision::Inexact(1000)
2063 );
2064 Ok(())
2065 }
2066
2067 #[test]
2068 fn test_join_provider_semi_join() -> Result<()> {
2069 assert_eq!(
2071 compute_join_rows(1000, Some(100), 500, Some(50), JoinType::LeftSemi)?,
2072 Precision::Inexact(1000)
2073 );
2074 assert_eq!(
2076 compute_join_rows(1000, Some(100), 500, Some(50), JoinType::RightSemi)?,
2077 Precision::Inexact(500)
2078 );
2079 assert_eq!(
2082 compute_join_rows(1000, None, 500, None, JoinType::LeftSemi)?,
2083 Precision::Inexact(1000)
2084 );
2085 Ok(())
2086 }
2087
2088 #[test]
2089 fn test_join_provider_anti_join() -> Result<()> {
2090 assert_eq!(
2092 compute_join_rows(1000, Some(100), 10, Some(100), JoinType::LeftAnti)?,
2093 Precision::Inexact(900)
2094 );
2095 assert_eq!(
2097 compute_join_rows(1000, Some(100), 500, Some(50), JoinType::RightAnti)?,
2098 Precision::Inexact(0)
2099 );
2100 Ok(())
2101 }
2102
2103 #[test]
2108 fn test_cross_join_provider_exact() -> Result<()> {
2109 use crate::joins::CrossJoinExec;
2110 let left = make_source(100);
2111 let right = make_source(200);
2112 let join: Arc<dyn ExecutionPlan> = Arc::new(CrossJoinExec::new(left, right));
2113
2114 let registry = StatisticsRegistry::with_providers(vec![
2115 Arc::new(JoinStatisticsProvider),
2116 Arc::new(DefaultStatisticsProvider),
2117 ]);
2118 let stats = registry.compute(join.as_ref())?;
2119 assert_eq!(stats.base.num_rows, Precision::Exact(20_000));
2121 Ok(())
2122 }
2123
2124 use crate::limit::{GlobalLimitExec, LocalLimitExec};
2129
2130 #[test]
2131 fn test_limit_provider_caps_output() -> Result<()> {
2132 let source = make_source(1000);
2134 let limit: Arc<dyn ExecutionPlan> = Arc::new(LocalLimitExec::new(source, 100));
2135
2136 let registry = StatisticsRegistry::with_providers(vec![
2137 Arc::new(LimitStatisticsProvider),
2138 Arc::new(DefaultStatisticsProvider),
2139 ]);
2140 let stats = registry.compute(limit.as_ref())?;
2141 assert_eq!(stats.base.num_rows, Precision::Exact(100));
2142 Ok(())
2143 }
2144
2145 #[test]
2146 fn test_limit_provider_input_smaller_than_fetch() -> Result<()> {
2147 let source = make_source(50);
2149 let limit: Arc<dyn ExecutionPlan> = Arc::new(LocalLimitExec::new(source, 200));
2150
2151 let registry = StatisticsRegistry::with_providers(vec![
2152 Arc::new(LimitStatisticsProvider),
2153 Arc::new(DefaultStatisticsProvider),
2154 ]);
2155 let stats = registry.compute(limit.as_ref())?;
2156 assert_eq!(stats.base.num_rows, Precision::Exact(50));
2157 Ok(())
2158 }
2159
2160 #[test]
2161 fn test_global_limit_provider_skip_and_fetch() -> Result<()> {
2162 let source = make_source(1000);
2164 let limit: Arc<dyn ExecutionPlan> =
2165 Arc::new(GlobalLimitExec::new(source, 200, Some(100)));
2166
2167 let registry = StatisticsRegistry::with_providers(vec![
2168 Arc::new(LimitStatisticsProvider),
2169 Arc::new(DefaultStatisticsProvider),
2170 ]);
2171 let stats = registry.compute(limit.as_ref())?;
2172 assert_eq!(stats.base.num_rows, Precision::Exact(100));
2173 Ok(())
2174 }
2175
2176 #[test]
2177 fn test_global_limit_provider_skip_exceeds_rows() -> Result<()> {
2178 let source = make_source(100);
2180 let limit: Arc<dyn ExecutionPlan> =
2181 Arc::new(GlobalLimitExec::new(source, 200, Some(50)));
2182
2183 let registry = StatisticsRegistry::with_providers(vec![
2184 Arc::new(LimitStatisticsProvider),
2185 Arc::new(DefaultStatisticsProvider),
2186 ]);
2187 let stats = registry.compute(limit.as_ref())?;
2188 assert_eq!(stats.base.num_rows, Precision::Exact(0));
2189 Ok(())
2190 }
2191
2192 #[test]
2193 fn test_limit_provider_inexact_input() -> Result<()> {
2194 let source = make_source_with_precision(Precision::Inexact(1000));
2197 let limit: Arc<dyn ExecutionPlan> = Arc::new(LocalLimitExec::new(source, 100));
2198
2199 let registry = StatisticsRegistry::with_providers(vec![
2200 Arc::new(LimitStatisticsProvider),
2201 Arc::new(DefaultStatisticsProvider),
2202 ]);
2203 let stats = registry.compute(limit.as_ref())?;
2204 assert_eq!(stats.base.num_rows, Precision::Inexact(100));
2205 Ok(())
2206 }
2207
2208 use crate::union::UnionExec;
2213
2214 fn make_source_with_precision(num_rows: Precision<usize>) -> Arc<dyn ExecutionPlan> {
2215 Arc::new(MockSourceExec::new(make_schema(), num_rows))
2216 }
2217
2218 #[test]
2219 fn test_union_provider_sums_rows() -> Result<()> {
2220 let union = UnionExec::try_new(vec![make_source(300), make_source(700)])?;
2221
2222 let registry = StatisticsRegistry::with_providers(vec![
2223 Arc::new(UnionStatisticsProvider),
2224 Arc::new(DefaultStatisticsProvider),
2225 ]);
2226 let stats = registry.compute(union.as_ref())?;
2227 assert_eq!(stats.base.num_rows, Precision::Exact(1000));
2228 Ok(())
2229 }
2230
2231 #[test]
2232 fn test_union_provider_three_inputs() -> Result<()> {
2233 let union = UnionExec::try_new(vec![
2234 make_source(100),
2235 make_source(200),
2236 make_source(300),
2237 ])?;
2238
2239 let registry = StatisticsRegistry::with_providers(vec![
2240 Arc::new(UnionStatisticsProvider),
2241 Arc::new(DefaultStatisticsProvider),
2242 ]);
2243 let stats = registry.compute(union.as_ref())?;
2244 assert_eq!(stats.base.num_rows, Precision::Exact(600));
2245 Ok(())
2246 }
2247
2248 #[test]
2249 fn test_union_provider_absent_propagates() -> Result<()> {
2250 let union = UnionExec::try_new(vec![
2252 make_source(300),
2253 make_source_with_precision(Precision::Absent),
2254 ])?;
2255
2256 let registry = StatisticsRegistry::with_providers(vec![
2257 Arc::new(UnionStatisticsProvider),
2258 Arc::new(DefaultStatisticsProvider),
2259 ]);
2260 let stats = registry.compute(union.as_ref())?;
2261 assert_eq!(stats.base.num_rows, Precision::Absent);
2262 Ok(())
2263 }
2264
2265 #[test]
2270 fn test_closure_provider_basic() -> Result<()> {
2271 let provider = ClosureStatisticsProvider::new(|plan, _child_stats| {
2273 if plan.downcast_ref::<FilterExec>().is_some() {
2274 Ok(StatisticsResult::Computed(ExtendedStatistics::from(
2275 Statistics {
2276 num_rows: Precision::Inexact(42),
2277 total_byte_size: Precision::Absent,
2278 column_statistics: vec![],
2279 },
2280 )))
2281 } else {
2282 Ok(StatisticsResult::Delegate)
2283 }
2284 });
2285
2286 let registry = StatisticsRegistry::with_providers(vec![
2287 Arc::new(provider),
2288 Arc::new(DefaultStatisticsProvider),
2289 ]);
2290
2291 let source = make_source(1000);
2292 let filter: Arc<dyn ExecutionPlan> =
2293 Arc::new(FilterExec::try_new(lit(true), source)?);
2294 let stats = registry.compute(filter.as_ref())?;
2295 assert_eq!(stats.base.num_rows, Precision::Inexact(42));
2296 Ok(())
2297 }
2298
2299 #[test]
2300 fn test_closure_provider_distinguishes_nodes_by_child_stats() -> Result<()> {
2301 let provider = ClosureStatisticsProvider::new(|plan, child_stats| {
2306 if plan.downcast_ref::<FilterExec>().is_none() {
2307 return Ok(StatisticsResult::Delegate);
2308 }
2309 match child_stats[0].base.num_rows.get_value().copied() {
2310 Some(500) => Ok(StatisticsResult::Computed(ExtendedStatistics::from(
2311 Statistics {
2312 num_rows: Precision::Inexact(100),
2313 total_byte_size: Precision::Absent,
2314 column_statistics: vec![],
2315 },
2316 ))),
2317 Some(200) => Ok(StatisticsResult::Computed(ExtendedStatistics::from(
2318 Statistics {
2319 num_rows: Precision::Inexact(50),
2320 total_byte_size: Precision::Absent,
2321 column_statistics: vec![],
2322 },
2323 ))),
2324 _ => Ok(StatisticsResult::Delegate),
2325 }
2326 });
2327
2328 let registry = StatisticsRegistry::with_providers(vec![Arc::new(provider)]);
2329
2330 let filter_a: Arc<dyn ExecutionPlan> =
2331 Arc::new(FilterExec::try_new(lit(true), make_source(500))?);
2332 let filter_b: Arc<dyn ExecutionPlan> =
2333 Arc::new(FilterExec::try_new(lit(true), make_source(200))?);
2334
2335 let stats_a = registry.compute(filter_a.as_ref())?;
2336 let stats_b = registry.compute(filter_b.as_ref())?;
2337
2338 assert_eq!(stats_a.base.num_rows, Precision::Inexact(100));
2339 assert_eq!(stats_b.base.num_rows, Precision::Inexact(50));
2340 Ok(())
2341 }
2342}