1use std::collections::hash_map::Entry;
19use std::collections::{HashMap, HashSet};
20use std::pin::Pin;
21use std::sync::Arc;
22use std::task::{Context, Poll, ready};
23
24use datafusion_physical_expr::projection::{ProjectionRef, combine_projections};
25use itertools::Itertools;
26
27use super::{
28 ColumnStatistics, DisplayAs, ExecutionPlanProperties, PlanProperties,
29 RecordBatchStream, SendableRecordBatchStream, Statistics,
30};
31use crate::coalesce::{LimitedBatchCoalescer, PushBatchStatus};
32use crate::common::can_project;
33use crate::execution_plan::{CardinalityEffect, replace_children_if_necessary};
34use crate::filter_pushdown::{
35 ChildFilterDescription, ChildPushdownResult, FilterDescription, FilterPushdownPhase,
36 FilterPushdownPropagation, PushedDown,
37};
38use crate::limit::LocalLimitExec;
39use crate::metrics::{MetricBuilder, MetricType};
40use crate::projection::{
41 EmbeddedProjection, ProjectionExec, ProjectionExpr, make_with_child,
42 try_embed_projection, update_expr,
43};
44use crate::statistics::{ChildStats, StatisticsArgs, StatisticsContext};
45use crate::stream::EmptyRecordBatchStream;
46use crate::{ChildrenPropertiesMode, ReplaceChildrenOptions, validate_child_count};
47use crate::{
48 DisplayFormatType, ExecutionPlan,
49 metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet, RatioMetrics},
50};
51
52use arrow::compute::filter_record_batch;
53use arrow::datatypes::{DataType, SchemaRef};
54use arrow::record_batch::RecordBatch;
55use datafusion_common::cast::as_boolean_array;
56use datafusion_common::config::ConfigOptions;
57use datafusion_common::stats::Precision;
58use datafusion_common::tree_node::TreeNodeRecursion;
59use datafusion_common::{
60 DataFusionError, Result, ScalarValue, internal_err, plan_err, project_schema,
61};
62use datafusion_execution::TaskContext;
63use datafusion_expr::Operator;
64use datafusion_physical_expr::equivalence::ProjectionMapping;
65use datafusion_physical_expr::expressions::{
66 BinaryExpr, Column, IsNotNullExpr, Literal, lit,
67};
68use datafusion_physical_expr::intervals::utils::check_support;
69use datafusion_physical_expr::utils::{collect_columns, reassign_expr_columns};
70use datafusion_physical_expr::{
71 AcrossPartitions, AnalysisContext, ConstExpr, ExprBoundaries, PhysicalExpr, analyze,
72 conjunction, split_conjunction,
73};
74
75use datafusion_physical_expr_common::physical_expr::fmt_sql;
76use futures::stream::{Stream, StreamExt};
77use log::trace;
78
79const FILTER_EXEC_DEFAULT_SELECTIVITY: u8 = 20;
80const FILTER_EXEC_DEFAULT_BATCH_SIZE: usize = 8192;
81
82#[derive(Debug, Clone)]
85pub struct FilterExec {
86 predicate: Arc<dyn PhysicalExpr>,
88 input: Arc<dyn ExecutionPlan>,
90 metrics: ExecutionPlanMetricsSet,
92 default_selectivity: u8,
94 cache: Arc<PlanProperties>,
96 projection: Option<ProjectionRef>,
98 batch_size: usize,
100 fetch: Option<usize>,
102}
103
104pub struct FilterExecBuilder {
106 predicate: Arc<dyn PhysicalExpr>,
107 input: Arc<dyn ExecutionPlan>,
108 projection: Option<ProjectionRef>,
109 default_selectivity: u8,
110 batch_size: usize,
111 fetch: Option<usize>,
112}
113
114impl FilterExecBuilder {
115 pub fn new(predicate: Arc<dyn PhysicalExpr>, input: Arc<dyn ExecutionPlan>) -> Self {
117 Self {
118 predicate,
119 input,
120 projection: None,
121 default_selectivity: FILTER_EXEC_DEFAULT_SELECTIVITY,
122 batch_size: FILTER_EXEC_DEFAULT_BATCH_SIZE,
123 fetch: None,
124 }
125 }
126
127 pub fn with_input(mut self, input: Arc<dyn ExecutionPlan>) -> Self {
129 self.input = input;
130 self
131 }
132
133 pub fn with_predicate(mut self, predicate: Arc<dyn PhysicalExpr>) -> Self {
135 self.predicate = predicate;
136 self
137 }
138
139 pub fn apply_projection(self, projection: Option<Vec<usize>>) -> Result<Self> {
149 let projection = projection.map(Into::into);
150 self.apply_projection_by_ref(projection.as_ref())
151 }
152
153 pub fn apply_projection_by_ref(
155 mut self,
156 projection: Option<&ProjectionRef>,
157 ) -> Result<Self> {
158 can_project(&self.input.schema(), projection.map(AsRef::as_ref))?;
160 self.projection = combine_projections(projection, self.projection.as_ref())?;
161 Ok(self)
162 }
163
164 pub fn with_default_selectivity(mut self, default_selectivity: u8) -> Self {
166 self.default_selectivity = default_selectivity;
167 self
168 }
169
170 pub fn with_batch_size(mut self, batch_size: usize) -> Self {
172 self.batch_size = batch_size;
173 self
174 }
175
176 pub fn with_fetch(mut self, fetch: Option<usize>) -> Self {
178 self.fetch = fetch;
179 self
180 }
181
182 pub fn build(self) -> Result<FilterExec> {
184 match self.predicate.data_type(self.input.schema().as_ref())? {
186 DataType::Boolean => {}
187 other => {
188 return plan_err!(
189 "Filter predicate must return BOOLEAN values, got {other:?}"
190 );
191 }
192 }
193
194 if self.default_selectivity > 100 {
196 return plan_err!(
197 "Default filter selectivity value needs to be less than or equal to 100"
198 );
199 }
200
201 can_project(&self.input.schema(), self.projection.as_deref())?;
203
204 let cache = FilterExec::compute_properties(
206 &self.input,
207 &self.predicate,
208 self.default_selectivity,
209 self.projection.as_deref(),
210 )?;
211
212 Ok(FilterExec {
213 predicate: self.predicate,
214 input: self.input,
215 metrics: ExecutionPlanMetricsSet::new(),
216 default_selectivity: self.default_selectivity,
217 cache: Arc::new(cache),
218 projection: self.projection,
219 batch_size: self.batch_size,
220 fetch: self.fetch,
221 })
222 }
223}
224
225impl From<&FilterExec> for FilterExecBuilder {
226 fn from(exec: &FilterExec) -> Self {
227 Self {
228 predicate: Arc::clone(&exec.predicate),
229 input: Arc::clone(&exec.input),
230 projection: exec.projection.clone(),
231 default_selectivity: exec.default_selectivity,
232 batch_size: exec.batch_size,
233 fetch: exec.fetch,
234 }
239 }
240}
241
242impl FilterExec {
243 pub fn try_new(
245 predicate: Arc<dyn PhysicalExpr>,
246 input: Arc<dyn ExecutionPlan>,
247 ) -> Result<Self> {
248 FilterExecBuilder::new(predicate, input).build()
249 }
250
251 pub fn batch_size(&self) -> usize {
253 self.batch_size
254 }
255
256 pub fn with_default_selectivity(
258 mut self,
259 default_selectivity: u8,
260 ) -> Result<Self, DataFusionError> {
261 if default_selectivity > 100 {
262 return plan_err!(
263 "Default filter selectivity value needs to be less than or equal to 100"
264 );
265 }
266 self.default_selectivity = default_selectivity;
267 Ok(self)
268 }
269
270 #[deprecated(
275 since = "52.0.0",
276 note = "Use FilterExecBuilder::apply_projection instead"
277 )]
278 pub fn with_projection(&self, projection: Option<Vec<usize>>) -> Result<Self> {
279 let builder = FilterExecBuilder::from(self);
280 builder.apply_projection(projection)?.build()
281 }
282
283 pub fn with_batch_size(&self, batch_size: usize) -> Result<Self> {
285 Ok(Self {
286 predicate: Arc::clone(&self.predicate),
287 input: Arc::clone(&self.input),
288 metrics: self.metrics.clone(),
289 default_selectivity: self.default_selectivity,
290 cache: Arc::clone(&self.cache),
291 projection: self.projection.clone(),
292 batch_size,
293 fetch: self.fetch,
294 })
295 }
296
297 pub fn predicate(&self) -> &Arc<dyn PhysicalExpr> {
299 &self.predicate
300 }
301
302 pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
304 &self.input
305 }
306
307 pub fn default_selectivity(&self) -> u8 {
309 self.default_selectivity
310 }
311
312 pub fn projection(&self) -> &Option<ProjectionRef> {
314 &self.projection
315 }
316
317 pub(crate) fn statistics_helper(
336 schema: &SchemaRef,
337 input_stats: Statistics,
338 predicate: &Arc<dyn PhysicalExpr>,
339 default_selectivity: u8,
340 ) -> Result<Statistics> {
341 let (eq_columns, is_infeasible) = collect_equality_columns(predicate);
342
343 let input_num_rows = input_stats.num_rows;
344 let input_total_byte_size = input_stats.total_byte_size;
345
346 let (selectivity, num_rows, column_statistics) = if is_infeasible {
347 let mut cs = input_stats.to_inexact().column_statistics;
350 for col_stat in &mut cs {
351 col_stat.distinct_count = Precision::Exact(0);
352 col_stat.null_count = Precision::Exact(0);
353 col_stat.min_value = Precision::Absent;
354 col_stat.max_value = Precision::Absent;
355 col_stat.sum_value = Precision::Absent;
356 col_stat.byte_size = Precision::Exact(0);
357 }
358 (0.0, Precision::Exact(0), cs)
359 } else {
360 let null_rejecting_columns = collect_null_rejecting_columns(predicate);
361
362 if check_support(predicate, schema) {
363 let input_analysis_ctx = AnalysisContext::try_from_statistics(
364 schema,
365 &input_stats.column_statistics,
366 )?;
367 let analysis_ctx = analyze(predicate, input_analysis_ctx, schema)?;
368 let selectivity = analysis_ctx.selectivity.unwrap_or(1.0);
369 let filtered_num_rows =
370 input_num_rows.with_estimated_selectivity(selectivity);
371 let cs = collect_new_statistics(
372 schema,
373 &input_stats.column_statistics,
374 analysis_ctx.boundaries,
375 selectivity,
376 &null_rejecting_columns,
377 filtered_num_rows,
378 );
379 (selectivity, filtered_num_rows, cs)
380 } else {
381 let selectivity = default_selectivity as f64 / 100.0;
385 let filtered_num_rows =
386 input_num_rows.with_estimated_selectivity(selectivity);
387 let mut cs = input_stats.to_inexact().column_statistics;
388 for (idx, col_stat) in cs.iter_mut().enumerate() {
389 col_stat.byte_size = scale_byte_size_at_rows(
390 col_stat.byte_size,
391 selectivity,
392 filtered_num_rows,
393 );
394 col_stat.null_count = if null_rejecting_columns.contains(&idx) {
395 Precision::Exact(0)
396 } else {
397 cap_at_rows(col_stat.null_count, filtered_num_rows)
398 };
399 col_stat.distinct_count = if eq_columns.contains(&idx) {
400 distinct_count_for_singleton_domain(filtered_num_rows)
401 } else {
402 cap_at_rows(col_stat.distinct_count, filtered_num_rows)
403 };
404 }
405 (selectivity, filtered_num_rows, cs)
406 }
407 };
408
409 let total_byte_size =
410 scale_byte_size_at_rows(input_total_byte_size, selectivity, num_rows);
411
412 Ok(Statistics {
413 num_rows,
414 total_byte_size,
415 column_statistics,
416 })
417 }
418
419 fn compute_properties(
421 input: &Arc<dyn ExecutionPlan>,
422 predicate: &Arc<dyn PhysicalExpr>,
423 default_selectivity: u8,
424 projection: Option<&[usize]>,
425 ) -> Result<PlanProperties> {
426 let schema = input.schema();
429 let stats = Self::statistics_helper(
430 &schema,
431 Arc::unwrap_or_clone(
432 StatisticsContext::new()
433 .compute(input.as_ref(), &StatisticsArgs::new())?,
434 ),
435 predicate,
436 default_selectivity,
437 )?;
438 let mut eq_properties = input.equivalence_properties().clone();
439 let (equal_pairs, _) = collect_columns_from_predicate_inner(predicate);
440 for (lhs, rhs) in equal_pairs {
441 eq_properties.add_equal_conditions(Arc::clone(lhs), Arc::clone(rhs))?
442 }
443 let constants = collect_columns(predicate)
446 .into_iter()
447 .filter(|column| stats.column_statistics[column.index()].is_singleton())
448 .map(|column| {
449 let value = stats.column_statistics[column.index()]
450 .min_value
451 .get_value();
452 let expr = Arc::new(column) as _;
453 ConstExpr::new(expr, AcrossPartitions::Uniform(value.cloned()))
454 });
455 eq_properties.add_constants(constants)?;
457 eq_properties.add_constants(ConstExpr::collect_predicate_constants(
460 input.equivalence_properties(),
461 predicate,
462 ))?;
463
464 let mut output_partitioning = input.output_partitioning().clone();
465 if let Some(projection) = projection {
467 let schema = eq_properties.schema();
468 let projection_mapping = ProjectionMapping::from_indices(projection, schema)?;
469 let out_schema = project_schema(schema, Some(&projection))?;
470 output_partitioning =
471 output_partitioning.project(&projection_mapping, &eq_properties);
472 eq_properties = eq_properties.project(&projection_mapping, out_schema);
473 }
474
475 Ok(PlanProperties::new(
476 eq_properties,
477 output_partitioning,
478 input.pipeline_behavior(),
479 input.boundedness(),
480 ))
481 }
482}
483
484impl DisplayAs for FilterExec {
485 fn fmt_as(
486 &self,
487 t: DisplayFormatType,
488 f: &mut std::fmt::Formatter,
489 ) -> std::fmt::Result {
490 match t {
491 DisplayFormatType::Default | DisplayFormatType::Verbose => {
492 let display_projections = if let Some(projection) =
493 self.projection.as_ref()
494 {
495 format!(
496 ", projection=[{}]",
497 projection
498 .iter()
499 .map(|index| format!(
500 "{}@{}",
501 self.input.schema().fields().get(*index).unwrap().name(),
502 index
503 ))
504 .collect::<Vec<_>>()
505 .join(", ")
506 )
507 } else {
508 "".to_string()
509 };
510 let fetch = self
511 .fetch
512 .map_or_else(|| "".to_string(), |f| format!(", fetch={f}"));
513 write!(
514 f,
515 "FilterExec: {}{}{}",
516 self.predicate, display_projections, fetch
517 )
518 }
519 DisplayFormatType::TreeRender => {
520 if let Some(fetch) = self.fetch {
521 writeln!(f, "fetch={fetch}")?;
522 }
523 write!(f, "predicate={}", fmt_sql(self.predicate.as_ref()))
524 }
525 }
526 }
527}
528
529impl ExecutionPlan for FilterExec {
530 fn name(&self) -> &'static str {
531 "FilterExec"
532 }
533
534 fn properties(&self) -> &Arc<PlanProperties> {
536 &self.cache
537 }
538
539 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
540 vec![&self.input]
541 }
542
543 fn apply_expressions(
544 &self,
545 f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
546 ) -> Result<TreeNodeRecursion> {
547 crate::apply_expression_roots([&self.predicate], f)
548 }
549
550 fn maintains_input_order(&self) -> Vec<bool> {
551 vec![true]
553 }
554
555 fn replace_children(
556 self: Arc<Self>,
557 mut children: Vec<Arc<dyn ExecutionPlan>>,
558 options: ReplaceChildrenOptions,
559 ) -> Result<Arc<dyn ExecutionPlan>> {
560 validate_child_count!(self, children);
561 match options.children_properties {
562 ChildrenPropertiesMode::Keep => Ok(Arc::new(Self {
563 input: children.swap_remove(0),
564 metrics: ExecutionPlanMetricsSet::new(),
565 ..Self::clone(&*self)
566 })),
567 ChildrenPropertiesMode::Recompute => {
568 let new_input = children.swap_remove(0);
569 FilterExecBuilder::from(&*self)
570 .with_input(new_input)
571 .build()
572 .map(|e| Arc::new(e) as _)
573 }
574 }
575 }
576
577 fn with_new_children(
578 self: Arc<Self>,
579 children: Vec<Arc<dyn ExecutionPlan>>,
580 ) -> Result<Arc<dyn ExecutionPlan>> {
581 self.replace_children(
582 children,
583 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
584 )
585 }
586
587 fn with_new_children_and_same_properties(
588 self: Arc<Self>,
589 children: Vec<Arc<dyn ExecutionPlan>>,
590 ) -> Result<Arc<dyn ExecutionPlan>> {
591 self.replace_children(
592 children,
593 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Keep),
594 )
595 }
596
597 fn execute(
598 &self,
599 partition: usize,
600 context: Arc<TaskContext>,
601 ) -> Result<SendableRecordBatchStream> {
602 trace!(
603 "Start FilterExec::execute for partition {} of context session_id {} and task_id {:?}",
604 partition,
605 context.session_id(),
606 context.task_id()
607 );
608 let metrics = FilterExecMetrics::new(&self.metrics, partition);
609 Ok(Box::pin(FilterExecStream {
610 schema: self.schema(),
611 predicate: Arc::clone(&self.predicate),
612 input: self.input.execute(partition, context)?,
613 metrics,
614 projection: self.projection.clone(),
615 batch_coalescer: LimitedBatchCoalescer::new(
616 self.schema(),
617 self.batch_size,
618 self.fetch,
619 ),
620 }))
621 }
622
623 fn metrics(&self) -> Option<MetricsSet> {
624 Some(self.metrics.clone_inner())
625 }
626
627 fn child_stats_requests(&self, partition: Option<usize>) -> Vec<ChildStats> {
628 vec![ChildStats::At(partition)]
629 }
630
631 fn statistics_from_inputs(
634 &self,
635 input_stats: &[Arc<Statistics>],
636 _args: &StatisticsArgs,
637 ) -> Result<Arc<Statistics>> {
638 let input_stats = input_stats[0].as_ref().clone();
639 let stats = Self::statistics_helper(
640 &self.input.schema(),
641 input_stats,
642 self.predicate(),
643 self.default_selectivity,
644 )?;
645 Ok(Arc::new(stats.project(self.projection.as_ref())))
646 }
647
648 fn cardinality_effect(&self) -> CardinalityEffect {
649 CardinalityEffect::LowerEqual
650 }
651
652 fn try_swapping_with_projection(
655 &self,
656 projection: &ProjectionExec,
657 ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
658 if projection.expr().len() < projection.input().schema().fields().len() {
660 if let Some(new_predicate) =
662 update_expr(self.predicate(), projection.expr(), false)?
663 {
664 return FilterExecBuilder::from(self)
665 .with_input(make_with_child(projection, self.input())?)
666 .with_predicate(new_predicate)
667 .apply_projection(None)?
671 .build()
672 .map(|e| Some(Arc::new(e) as _));
673 }
674 }
675 try_embed_projection(projection, self)
676 }
677
678 fn gather_filters_for_pushdown(
679 &self,
680 phase: FilterPushdownPhase,
681 parent_filters: Vec<Arc<dyn PhysicalExpr>>,
682 _config: &ConfigOptions,
683 ) -> Result<FilterDescription> {
684 if phase != FilterPushdownPhase::Pre {
685 let child =
686 ChildFilterDescription::from_child(&parent_filters, self.input())?;
687 return Ok(FilterDescription::new().with_child(child));
688 }
689
690 let child = ChildFilterDescription::from_child(&parent_filters, self.input())?
691 .with_self_filters(
692 split_conjunction(&self.predicate)
693 .into_iter()
694 .cloned()
695 .collect(),
696 );
697
698 Ok(FilterDescription::new().with_child(child))
699 }
700
701 fn handle_child_pushdown_result(
702 &self,
703 phase: FilterPushdownPhase,
704 child_pushdown_result: ChildPushdownResult,
705 _config: &ConfigOptions,
706 ) -> Result<FilterPushdownPropagation<Arc<dyn ExecutionPlan>>> {
707 if phase != FilterPushdownPhase::Pre {
708 return Ok(FilterPushdownPropagation::if_all(child_pushdown_result));
709 }
710 let mut unsupported_parent_filters: Vec<Arc<dyn PhysicalExpr>> =
712 child_pushdown_result
713 .parent_filters
714 .iter()
715 .filter_map(|f| {
716 matches!(f.all(), PushedDown::No).then_some(Arc::clone(&f.filter))
717 })
718 .collect();
719
720 if self.projection.is_some() {
724 let input_schema = self.input().schema();
725 unsupported_parent_filters = unsupported_parent_filters
726 .into_iter()
727 .map(|expr| reassign_expr_columns(expr, &input_schema))
728 .collect::<Result<Vec<_>>>()?;
729 }
730
731 let unsupported_self_filters = child_pushdown_result
732 .self_filters
733 .first()
734 .expect("we have exactly one child")
735 .iter()
736 .filter_map(|f| match f.discriminant {
737 PushedDown::Yes => None,
738 PushedDown::No => Some(&f.predicate),
739 })
740 .cloned();
741
742 let unhandled_filters = unsupported_parent_filters
743 .into_iter()
744 .chain(unsupported_self_filters)
745 .collect_vec();
746
747 let filter_input = Arc::clone(self.input());
749 let new_predicate = conjunction(unhandled_filters);
750 let updated_node = if new_predicate.eq(&lit(true)) {
751 let filter_input = if let Some(outer_fetch) = self.fetch {
756 let effective_fetch = match filter_input.fetch() {
757 Some(inner_fetch) => outer_fetch.min(inner_fetch),
758 None => outer_fetch,
759 };
760 match filter_input.with_fetch(Some(effective_fetch)) {
761 Some(node) => node,
762 None => Arc::new(LocalLimitExec::new(filter_input, effective_fetch)),
763 }
764 } else {
765 filter_input
766 };
767 match self.projection().as_ref() {
768 Some(projection_indices) => {
769 let filter_child_schema = filter_input.schema();
770 let proj_exprs = projection_indices
771 .iter()
772 .map(|p| {
773 let field = filter_child_schema.field(*p).clone();
774 ProjectionExpr {
775 expr: Arc::new(Column::new(field.name(), *p))
776 as Arc<dyn PhysicalExpr>,
777 alias: field.name().to_string(),
778 }
779 })
780 .collect::<Vec<_>>();
781 Some(Arc::new(ProjectionExec::try_new(proj_exprs, filter_input)?)
782 as Arc<dyn ExecutionPlan>)
783 }
784 None => {
785 Some(filter_input)
787 }
788 }
789 } else if new_predicate.eq(&self.predicate) {
790 None
792 } else {
793 let new = FilterExec {
795 predicate: Arc::clone(&new_predicate),
796 input: Arc::clone(&filter_input),
797 metrics: self.metrics.clone(),
798 default_selectivity: self.default_selectivity,
799 cache: Arc::new(Self::compute_properties(
800 &filter_input,
801 &new_predicate,
802 self.default_selectivity,
803 self.projection.as_deref(),
804 )?),
805 projection: self.projection.clone(),
806 batch_size: self.batch_size,
807 fetch: self.fetch,
808 };
809 Some(Arc::new(new) as _)
810 };
811
812 Ok(FilterPushdownPropagation {
813 filters: vec![PushedDown::Yes; child_pushdown_result.parent_filters.len()],
814 updated_node,
815 })
816 }
817
818 fn fetch(&self) -> Option<usize> {
819 self.fetch
820 }
821
822 fn with_fetch(&self, fetch: Option<usize>) -> Option<Arc<dyn ExecutionPlan>> {
823 Some(Arc::new(Self {
824 predicate: Arc::clone(&self.predicate),
825 input: Arc::clone(&self.input),
826 metrics: self.metrics.clone(),
827 default_selectivity: self.default_selectivity,
828 cache: Arc::clone(&self.cache),
829 projection: self.projection.clone(),
830 batch_size: self.batch_size,
831 fetch,
832 }))
833 }
834
835 fn with_preserve_order(
836 &self,
837 preserve_order: bool,
838 ) -> Option<Arc<dyn ExecutionPlan>> {
839 self.input
840 .with_preserve_order(preserve_order)
841 .and_then(|new_input| {
842 replace_children_if_necessary(Arc::new(self.clone()), vec![new_input])
843 .ok()
844 })
845 }
846
847 #[cfg(feature = "proto")]
848 fn try_to_proto(
849 &self,
850 ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
851 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
852 use datafusion_proto_models::protobuf;
853 let input = ctx.encode_child(self.input())?;
854 let expr = ctx.encode_expr(self.predicate())?;
855 let projection = if let Some(v) = self.projection() {
859 v.iter().map(|x| *x as u32).collect()
860 } else {
861 (0..self.input().schema().fields().len())
862 .map(|i| i as u32)
863 .collect()
864 };
865 Ok(Some(protobuf::PhysicalPlanNode {
866 physical_plan_type: Some(
867 protobuf::physical_plan_node::PhysicalPlanType::Filter(Box::new(
868 protobuf::FilterExecNode {
869 input: Some(Box::new(input)),
870 expr: Some(expr),
871 default_filter_selectivity: self.default_selectivity() as u32,
872 projection,
873 batch_size: self.batch_size() as u32,
874 fetch: self.fetch().map(|f| f as u32),
875 },
876 )),
877 ),
878 }))
879 }
880}
881
882#[cfg(feature = "proto")]
883impl FilterExec {
884 pub fn try_from_proto(
892 node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
893 ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
894 ) -> Result<Arc<dyn ExecutionPlan>> {
895 use datafusion_proto_models::protobuf;
896 let filter = crate::expect_plan_variant!(
897 node,
898 protobuf::physical_plan_node::PhysicalPlanType::Filter,
899 "FilterExec",
900 );
901 let input =
902 ctx.decode_required_child(filter.input.as_deref(), "FilterExec", "input")?;
903 let predicate = ctx.decode_required_expr(
904 filter.expr.as_ref(),
905 input.schema().as_ref(),
906 "FilterExec",
907 "expr",
908 )?;
909 let filter_selectivity = filter.default_filter_selectivity.try_into();
910
911 let num_fields = input.schema().fields().len();
915 let mut is_full_projection = filter.projection.len() == num_fields;
916 let mut projection_vec: Vec<usize> = Vec::with_capacity(filter.projection.len());
917 for (i, idx) in filter.projection.iter().enumerate() {
918 let idx = *idx as usize;
919 is_full_projection &= idx == i;
920 projection_vec.push(idx);
921 }
922 let projection = if is_full_projection {
923 None
924 } else {
925 Some(projection_vec)
926 };
927 let filter = FilterExecBuilder::new(predicate, input)
928 .apply_projection(projection)?
929 .with_batch_size(filter.batch_size as usize)
930 .with_fetch(filter.fetch.map(|f| f as usize))
931 .build()?;
932 match filter_selectivity {
933 Ok(filter_selectivity) => Ok(Arc::new(
934 filter.with_default_selectivity(filter_selectivity)?,
935 )),
936 Err(_) => Err(datafusion_common::internal_datafusion_err!(
937 "filter_selectivity in PhysicalPlanNode is invalid"
938 )),
939 }
940 }
941}
942
943impl EmbeddedProjection for FilterExec {
944 fn with_projection(&self, projection: Option<Vec<usize>>) -> Result<Self> {
945 FilterExecBuilder::from(self)
946 .apply_projection(projection)?
947 .build()
948 }
949}
950
951fn collect_equality_columns(predicate: &Arc<dyn PhysicalExpr>) -> (HashSet<usize>, bool) {
963 let mut eq_values: HashMap<usize, ScalarValue> = HashMap::new();
964 let mut infeasible = false;
965
966 for expr in split_conjunction(predicate) {
967 let Some(binary) = expr.downcast_ref::<BinaryExpr>() else {
968 continue;
969 };
970 if *binary.op() != Operator::Eq {
971 continue;
972 }
973 let left = binary.left();
974 let right = binary.right();
975 let pair = if let Some(col) = left.downcast_ref::<Column>()
976 && let Some(lit) = right.downcast_ref::<Literal>()
977 && !lit.value().is_null()
978 {
979 Some((col.index(), lit.value().clone()))
980 } else if let Some(col) = right.downcast_ref::<Column>()
981 && let Some(lit) = left.downcast_ref::<Literal>()
982 && !lit.value().is_null()
983 {
984 Some((col.index(), lit.value().clone()))
985 } else {
986 None
987 };
988
989 if let Some((idx, value)) = pair {
990 match eq_values.entry(idx) {
991 Entry::Occupied(prev) => {
992 if *prev.get() != value {
993 infeasible = true;
994 break;
995 }
996 }
997 Entry::Vacant(slot) => {
998 slot.insert(value);
999 }
1000 }
1001 }
1002 }
1003
1004 (eq_values.into_keys().collect(), infeasible)
1005}
1006
1007fn collect_null_rejecting_columns(predicate: &Arc<dyn PhysicalExpr>) -> HashSet<usize> {
1020 let mut columns = HashSet::new();
1021
1022 for expr in split_conjunction(predicate) {
1023 if let Some(is_not_null) = expr.downcast_ref::<IsNotNullExpr>() {
1025 if let Some(col) = is_not_null.arg().downcast_ref::<Column>() {
1026 columns.insert(col.index());
1027 }
1028 continue;
1029 }
1030
1031 if let Some(binary) = expr.downcast_ref::<BinaryExpr>() {
1034 if !binary.op().returns_null_on_null() {
1035 continue;
1036 }
1037 if let Some(col) = binary.left().downcast_ref::<Column>() {
1038 columns.insert(col.index());
1039 }
1040 if let Some(col) = binary.right().downcast_ref::<Column>() {
1041 columns.insert(col.index());
1042 }
1043 }
1044 }
1045
1046 columns
1047}
1048
1049fn interval_bound_to_precision(
1052 bound: ScalarValue,
1053 is_exact: bool,
1054) -> Precision<ScalarValue> {
1055 if bound.is_null() {
1056 Precision::Absent
1057 } else if is_exact {
1058 Precision::Exact(bound)
1059 } else {
1060 Precision::Inexact(bound)
1061 }
1062}
1063
1064fn cap_at_rows(
1070 value: Precision<usize>,
1071 filtered_num_rows: Precision<usize>,
1072) -> Precision<usize> {
1073 match filtered_num_rows {
1074 Precision::Absent => value.to_inexact(),
1075 Precision::Exact(0) => Precision::Exact(0),
1076 rows => value.to_inexact().min(&rows),
1077 }
1078}
1079
1080fn scale_byte_size_at_rows(
1083 byte_size: Precision<usize>,
1084 selectivity: f64,
1085 filtered_num_rows: Precision<usize>,
1086) -> Precision<usize> {
1087 if filtered_num_rows == Precision::Exact(0) {
1088 Precision::Exact(0)
1089 } else {
1090 byte_size.with_estimated_selectivity(selectivity)
1091 }
1092}
1093
1094fn distinct_count_for_singleton_domain(
1102 filtered_num_rows: Precision<usize>,
1103) -> Precision<usize> {
1104 match filtered_num_rows {
1105 Precision::Exact(0) | Precision::Inexact(0) => filtered_num_rows,
1106 Precision::Absent => Precision::Inexact(1),
1109 _ => Precision::Exact(1),
1110 }
1111}
1112
1113fn collect_new_statistics(
1119 schema: &SchemaRef,
1120 input_column_stats: &[ColumnStatistics],
1121 analysis_boundaries: Vec<ExprBoundaries>,
1122 selectivity: f64,
1123 null_rejecting_columns: &HashSet<usize>,
1124 filtered_num_rows: Precision<usize>,
1125) -> Vec<ColumnStatistics> {
1126 analysis_boundaries
1127 .into_iter()
1128 .enumerate()
1129 .map(
1130 |(
1131 idx,
1132 ExprBoundaries {
1133 interval,
1134 distinct_count,
1135 ..
1136 },
1137 )| {
1138 let Some(interval) = interval else {
1139 let typed_null = ScalarValue::try_from(schema.field(idx).data_type())
1144 .unwrap_or(ScalarValue::Null);
1145 return ColumnStatistics {
1146 null_count: Precision::Exact(0),
1147 max_value: Precision::Exact(typed_null.clone()),
1148 min_value: Precision::Exact(typed_null.clone()),
1149 sum_value: Precision::Exact(typed_null),
1150 distinct_count: Precision::Exact(0),
1151 byte_size: Precision::Exact(0),
1152 };
1153 };
1154 let (lower, upper) = interval.into_bounds();
1155 let is_single_value =
1156 !lower.is_null() && !upper.is_null() && lower == upper;
1157 let min_value = interval_bound_to_precision(lower, is_single_value);
1158 let max_value = interval_bound_to_precision(upper, is_single_value);
1159
1160 let capped_distinct_count = if is_single_value {
1164 distinct_count_for_singleton_domain(filtered_num_rows)
1165 } else {
1166 cap_at_rows(distinct_count, filtered_num_rows)
1167 };
1168 let capped_null_count = if null_rejecting_columns.contains(&idx) {
1169 Precision::Exact(0)
1170 } else {
1171 cap_at_rows(input_column_stats[idx].null_count, filtered_num_rows)
1172 };
1173 let byte_size = scale_byte_size_at_rows(
1174 input_column_stats[idx].byte_size,
1175 selectivity,
1176 filtered_num_rows,
1177 );
1178 ColumnStatistics {
1179 null_count: capped_null_count,
1180 max_value,
1181 min_value,
1182 sum_value: Precision::Absent,
1183 distinct_count: capped_distinct_count,
1184 byte_size,
1185 }
1186 },
1187 )
1188 .collect()
1189}
1190
1191struct FilterExecStream {
1194 schema: SchemaRef,
1196 predicate: Arc<dyn PhysicalExpr>,
1198 input: SendableRecordBatchStream,
1200 metrics: FilterExecMetrics,
1202 projection: Option<ProjectionRef>,
1204 batch_coalescer: LimitedBatchCoalescer,
1206}
1207
1208struct FilterExecMetrics {
1210 baseline_metrics: BaselineMetrics,
1212 selectivity: RatioMetrics,
1214 }
1217
1218impl FilterExecMetrics {
1219 pub fn new(metrics: &ExecutionPlanMetricsSet, partition: usize) -> Self {
1220 Self {
1221 baseline_metrics: BaselineMetrics::new(metrics, partition),
1222 selectivity: MetricBuilder::new(metrics)
1223 .with_type(MetricType::Summary)
1224 .ratio_metrics("selectivity", partition),
1225 }
1226 }
1227}
1228
1229pub fn batch_filter(
1230 batch: &RecordBatch,
1231 predicate: &Arc<dyn PhysicalExpr>,
1232) -> Result<RecordBatch> {
1233 filter_and_project(batch, predicate, None)
1234}
1235
1236fn filter_and_project(
1237 batch: &RecordBatch,
1238 predicate: &Arc<dyn PhysicalExpr>,
1239 projection: Option<&Vec<usize>>,
1240) -> Result<RecordBatch> {
1241 predicate
1242 .evaluate(batch)
1243 .and_then(|v| v.into_array(batch.num_rows()))
1244 .and_then(|array| {
1245 Ok(match (as_boolean_array(&array), projection) {
1246 (Ok(filter_array), None) => filter_record_batch(batch, filter_array)?,
1248 (Ok(filter_array), Some(projection)) => {
1249 let projected_batch = batch.project(projection)?;
1250 filter_record_batch(&projected_batch, filter_array)?
1251 }
1252 (Err(_), _) => {
1253 return internal_err!(
1254 "Cannot create filter_array from non-boolean predicates"
1255 );
1256 }
1257 })
1258 })
1259}
1260
1261impl Stream for FilterExecStream {
1262 type Item = Result<RecordBatch>;
1263
1264 fn poll_next(
1265 mut self: Pin<&mut Self>,
1266 cx: &mut Context<'_>,
1267 ) -> Poll<Option<Self::Item>> {
1268 let elapsed_compute = self.metrics.baseline_metrics.elapsed_compute().clone();
1269 loop {
1270 if let Some(batch) = self.batch_coalescer.next_completed_batch() {
1272 self.metrics.selectivity.add_part(batch.num_rows());
1273 let poll = Poll::Ready(Some(Ok(batch)));
1274 return self.metrics.baseline_metrics.record_poll(poll);
1275 }
1276
1277 if self.batch_coalescer.is_finished() {
1278 return Poll::Ready(None);
1280 }
1281
1282 match ready!(self.input.poll_next_unpin(cx)) {
1284 None => {
1285 self.batch_coalescer.finish()?;
1286 let input_schema = self.input.schema();
1288 self.input = Box::pin(EmptyRecordBatchStream::new(input_schema));
1289 }
1291 Some(Ok(batch)) => {
1292 let timer = elapsed_compute.timer();
1293 let status = self.predicate.as_ref()
1294 .evaluate(&batch)
1295 .and_then(|v| v.into_array(batch.num_rows()))
1296 .and_then(|array| {
1297 Ok(match self.projection.as_ref() {
1298 Some(projection) => {
1299 let projected_batch = batch.project(projection)?;
1300 (array, projected_batch)
1301 },
1302 None => (array, batch)
1303 })
1304 }).and_then(|(array, batch)| {
1305 match as_boolean_array(&array) {
1306 Ok(filter_array) => {
1307 self.metrics.selectivity.add_total(batch.num_rows());
1308 let batch = filter_record_batch(&batch, filter_array)?;
1310 let state = self.batch_coalescer.push_batch(batch)?;
1311 Ok(state)
1312 }
1313 Err(_) => {
1314 internal_err!(
1315 "Cannot create filter_array from non-boolean predicates"
1316 )
1317 }
1318 }
1319 })?;
1320 timer.done();
1321
1322 match status {
1323 PushBatchStatus::Continue => {
1324 }
1326 PushBatchStatus::LimitReached => {
1327 self.batch_coalescer.finish()?;
1329 let input_schema = self.input.schema();
1331 self.input =
1332 Box::pin(EmptyRecordBatchStream::new(input_schema));
1333 }
1335 }
1336 }
1337
1338 other => return Poll::Ready(other),
1340 }
1341 }
1342 }
1343
1344 fn size_hint(&self) -> (usize, Option<usize>) {
1345 self.input.size_hint()
1347 }
1348}
1349impl RecordBatchStream for FilterExecStream {
1350 fn schema(&self) -> SchemaRef {
1351 Arc::clone(&self.schema)
1352 }
1353}
1354
1355#[deprecated(
1357 since = "51.0.0",
1358 note = "This function will be internal in the future"
1359)]
1360pub fn collect_columns_from_predicate(
1361 predicate: &'_ Arc<dyn PhysicalExpr>,
1362) -> EqualAndNonEqual<'_> {
1363 collect_columns_from_predicate_inner(predicate)
1364}
1365
1366fn collect_columns_from_predicate_inner(
1367 predicate: &'_ Arc<dyn PhysicalExpr>,
1368) -> EqualAndNonEqual<'_> {
1369 let mut eq_predicate_columns = Vec::<PhysicalExprPairRef>::new();
1370 let mut ne_predicate_columns = Vec::<PhysicalExprPairRef>::new();
1371
1372 let predicates = split_conjunction(predicate);
1373 predicates.into_iter().for_each(|p| {
1374 if let Some(binary) = p.downcast_ref::<BinaryExpr>() {
1375 let has_direct_column_operand =
1383 binary.left().downcast_ref::<Column>().is_some()
1384 || binary.right().downcast_ref::<Column>().is_some();
1385 if !has_direct_column_operand {
1386 return;
1387 }
1388 match binary.op() {
1389 Operator::Eq => {
1390 eq_predicate_columns.push((binary.left(), binary.right()))
1391 }
1392 Operator::NotEq => {
1393 ne_predicate_columns.push((binary.left(), binary.right()))
1394 }
1395 _ => {}
1396 }
1397 }
1398 });
1399
1400 (eq_predicate_columns, ne_predicate_columns)
1401}
1402
1403pub type PhysicalExprPairRef<'a> = (&'a Arc<dyn PhysicalExpr>, &'a Arc<dyn PhysicalExpr>);
1405
1406pub type EqualAndNonEqual<'a> =
1408 (Vec<PhysicalExprPairRef<'a>>, Vec<PhysicalExprPairRef<'a>>);
1409
1410#[cfg(test)]
1411mod tests {
1412 use super::*;
1413 use crate::empty::EmptyExec;
1414 use crate::expressions::*;
1415 use crate::statistics::{StatisticsArgs, StatisticsContext};
1416 use crate::test;
1417 use crate::test::exec::StatisticsExec;
1418 use arrow::datatypes::{Field, Schema, UnionFields, UnionMode};
1419
1420 #[tokio::test]
1421 async fn collect_columns_predicates() -> Result<()> {
1422 let schema = test::aggr_test_schema();
1423 let predicate: Arc<dyn PhysicalExpr> = binary(
1424 binary(
1425 binary(col("c2", &schema)?, Operator::GtEq, lit(1u32), &schema)?,
1426 Operator::And,
1427 binary(col("c2", &schema)?, Operator::Eq, lit(4u32), &schema)?,
1428 &schema,
1429 )?,
1430 Operator::And,
1431 binary(
1432 binary(
1433 col("c2", &schema)?,
1434 Operator::Eq,
1435 col("c9", &schema)?,
1436 &schema,
1437 )?,
1438 Operator::And,
1439 binary(
1440 col("c1", &schema)?,
1441 Operator::NotEq,
1442 col("c13", &schema)?,
1443 &schema,
1444 )?,
1445 &schema,
1446 )?,
1447 &schema,
1448 )?;
1449
1450 let (equal_pairs, ne_pairs) = collect_columns_from_predicate_inner(&predicate);
1451 assert_eq!(2, equal_pairs.len());
1452 assert!(equal_pairs[0].0.eq(&col("c2", &schema)?));
1453 assert!(equal_pairs[0].1.eq(&lit(4u32)));
1454
1455 assert!(equal_pairs[1].0.eq(&col("c2", &schema)?));
1456 assert!(equal_pairs[1].1.eq(&col("c9", &schema)?));
1457
1458 assert_eq!(1, ne_pairs.len());
1459 assert!(ne_pairs[0].0.eq(&col("c1", &schema)?));
1460 assert!(ne_pairs[0].1.eq(&col("c13", &schema)?));
1461
1462 Ok(())
1463 }
1464
1465 #[tokio::test]
1466 async fn test_filter_statistics_basic_expr() -> Result<()> {
1467 let bytes_per_row = 4;
1470 let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
1471 let input = Arc::new(StatisticsExec::new(
1472 Statistics {
1473 num_rows: Precision::Inexact(100),
1474 total_byte_size: Precision::Inexact(100 * bytes_per_row),
1475 column_statistics: vec![ColumnStatistics {
1476 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
1477 max_value: Precision::Inexact(ScalarValue::Int32(Some(100))),
1478 ..Default::default()
1479 }],
1480 },
1481 schema.clone(),
1482 ));
1483
1484 let predicate: Arc<dyn PhysicalExpr> =
1486 binary(col("a", &schema)?, Operator::LtEq, lit(25i32), &schema)?;
1487
1488 let filter: Arc<dyn ExecutionPlan> =
1490 Arc::new(FilterExec::try_new(predicate, input)?);
1491
1492 let statistics =
1493 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
1494 assert_eq!(statistics.num_rows, Precision::Inexact(25));
1495 assert_eq!(
1496 statistics.total_byte_size,
1497 Precision::Inexact(25 * bytes_per_row)
1498 );
1499 assert_eq!(
1500 statistics.column_statistics,
1501 vec![ColumnStatistics {
1502 null_count: Precision::Exact(0),
1504 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
1505 max_value: Precision::Inexact(ScalarValue::Int32(Some(25))),
1506 ..Default::default()
1507 }]
1508 );
1509
1510 Ok(())
1511 }
1512
1513 #[tokio::test]
1514 async fn test_filter_statistics_column_level_nested() -> Result<()> {
1515 let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
1518 let input = Arc::new(StatisticsExec::new(
1519 Statistics {
1520 num_rows: Precision::Inexact(100),
1521 column_statistics: vec![ColumnStatistics {
1522 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
1523 max_value: Precision::Inexact(ScalarValue::Int32(Some(100))),
1524 ..Default::default()
1525 }],
1526 total_byte_size: Precision::Absent,
1527 },
1528 schema.clone(),
1529 ));
1530
1531 let sub_filter: Arc<dyn ExecutionPlan> = Arc::new(FilterExec::try_new(
1533 binary(col("a", &schema)?, Operator::LtEq, lit(25i32), &schema)?,
1534 input,
1535 )?);
1536
1537 let filter: Arc<dyn ExecutionPlan> = Arc::new(FilterExec::try_new(
1541 binary(col("a", &schema)?, Operator::GtEq, lit(10i32), &schema)?,
1542 sub_filter,
1543 )?);
1544
1545 let statistics =
1546 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
1547 assert_eq!(statistics.num_rows, Precision::Inexact(16));
1548 assert_eq!(
1549 statistics.column_statistics,
1550 vec![ColumnStatistics {
1551 null_count: Precision::Exact(0),
1553 min_value: Precision::Inexact(ScalarValue::Int32(Some(10))),
1554 max_value: Precision::Inexact(ScalarValue::Int32(Some(25))),
1555 ..Default::default()
1556 }]
1557 );
1558
1559 Ok(())
1560 }
1561
1562 #[tokio::test]
1563 async fn test_filter_statistics_column_level_nested_multiple() -> Result<()> {
1564 let schema = Schema::new(vec![
1568 Field::new("a", DataType::Int32, false),
1569 Field::new("b", DataType::Int32, false),
1570 ]);
1571 let input = Arc::new(StatisticsExec::new(
1572 Statistics {
1573 num_rows: Precision::Inexact(100),
1574 column_statistics: vec![
1575 ColumnStatistics {
1576 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
1577 max_value: Precision::Inexact(ScalarValue::Int32(Some(100))),
1578 ..Default::default()
1579 },
1580 ColumnStatistics {
1581 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
1582 max_value: Precision::Inexact(ScalarValue::Int32(Some(50))),
1583 ..Default::default()
1584 },
1585 ],
1586 total_byte_size: Precision::Absent,
1587 },
1588 schema.clone(),
1589 ));
1590
1591 let a_lte_25: Arc<dyn ExecutionPlan> = Arc::new(FilterExec::try_new(
1593 binary(col("a", &schema)?, Operator::LtEq, lit(25i32), &schema)?,
1594 input,
1595 )?);
1596
1597 let b_gt_5: Arc<dyn ExecutionPlan> = Arc::new(FilterExec::try_new(
1599 binary(col("b", &schema)?, Operator::Gt, lit(45i32), &schema)?,
1600 a_lte_25,
1601 )?);
1602
1603 let filter: Arc<dyn ExecutionPlan> = Arc::new(FilterExec::try_new(
1605 binary(col("a", &schema)?, Operator::GtEq, lit(10i32), &schema)?,
1606 b_gt_5,
1607 )?);
1608 let statistics =
1609 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
1610 assert_eq!(statistics.num_rows, Precision::Inexact(2));
1617 assert_eq!(
1618 statistics.column_statistics,
1619 vec![
1620 ColumnStatistics {
1621 null_count: Precision::Exact(0),
1623 min_value: Precision::Inexact(ScalarValue::Int32(Some(10))),
1624 max_value: Precision::Inexact(ScalarValue::Int32(Some(25))),
1625 ..Default::default()
1626 },
1627 ColumnStatistics {
1628 null_count: Precision::Inexact(0),
1631 min_value: Precision::Inexact(ScalarValue::Int32(Some(46))),
1632 max_value: Precision::Inexact(ScalarValue::Int32(Some(50))),
1633 ..Default::default()
1634 }
1635 ]
1636 );
1637
1638 Ok(())
1639 }
1640
1641 #[tokio::test]
1642 async fn test_filter_statistics_when_input_stats_missing() -> Result<()> {
1643 let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
1646 let input = Arc::new(StatisticsExec::new(
1647 Statistics::new_unknown(&schema),
1648 schema.clone(),
1649 ));
1650
1651 let predicate: Arc<dyn PhysicalExpr> =
1653 binary(col("a", &schema)?, Operator::LtEq, lit(25i32), &schema)?;
1654
1655 let filter: Arc<dyn ExecutionPlan> =
1657 Arc::new(FilterExec::try_new(predicate, input)?);
1658
1659 let statistics =
1660 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
1661 assert_eq!(statistics.num_rows, Precision::Absent);
1662
1663 Ok(())
1664 }
1665
1666 #[tokio::test]
1667 async fn test_filter_statistics_multiple_columns() -> Result<()> {
1668 let schema = Schema::new(vec![
1673 Field::new("a", DataType::Int32, false),
1674 Field::new("b", DataType::Int32, false),
1675 Field::new("c", DataType::Float32, false),
1676 ]);
1677 let input = Arc::new(StatisticsExec::new(
1678 Statistics {
1679 num_rows: Precision::Inexact(1000),
1680 total_byte_size: Precision::Inexact(4000),
1681 column_statistics: vec![
1682 ColumnStatistics {
1683 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
1684 max_value: Precision::Inexact(ScalarValue::Int32(Some(100))),
1685 ..Default::default()
1686 },
1687 ColumnStatistics {
1688 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
1689 max_value: Precision::Inexact(ScalarValue::Int32(Some(3))),
1690 ..Default::default()
1691 },
1692 ColumnStatistics {
1693 min_value: Precision::Inexact(ScalarValue::Float32(Some(1000.0))),
1694 max_value: Precision::Inexact(ScalarValue::Float32(Some(1100.0))),
1695 ..Default::default()
1696 },
1697 ],
1698 },
1699 schema,
1700 ));
1701 let predicate = Arc::new(BinaryExpr::new(
1703 Arc::new(BinaryExpr::new(
1704 Arc::new(Column::new("a", 0)),
1705 Operator::LtEq,
1706 Arc::new(Literal::new(ScalarValue::Int32(Some(53)))),
1707 )),
1708 Operator::And,
1709 Arc::new(BinaryExpr::new(
1710 Arc::new(BinaryExpr::new(
1711 Arc::new(Column::new("b", 1)),
1712 Operator::Eq,
1713 Arc::new(Literal::new(ScalarValue::Int32(Some(3)))),
1714 )),
1715 Operator::And,
1716 Arc::new(BinaryExpr::new(
1717 Arc::new(BinaryExpr::new(
1718 Arc::new(Column::new("c", 2)),
1719 Operator::LtEq,
1720 Arc::new(Literal::new(ScalarValue::Float32(Some(1075.0)))),
1721 )),
1722 Operator::And,
1723 Arc::new(BinaryExpr::new(
1724 Arc::new(Column::new("a", 0)),
1725 Operator::Gt,
1726 Arc::new(Column::new("b", 1)),
1727 )),
1728 )),
1729 )),
1730 ));
1731 let filter: Arc<dyn ExecutionPlan> =
1732 Arc::new(FilterExec::try_new(predicate, input)?);
1733 let statistics =
1734 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
1735 assert_eq!(statistics.num_rows, Precision::Inexact(134));
1739 assert_eq!(statistics.total_byte_size, Precision::Inexact(533));
1740 let exp_col_stats = vec![
1741 ColumnStatistics {
1742 min_value: Precision::Inexact(ScalarValue::Int32(Some(4))),
1743 max_value: Precision::Inexact(ScalarValue::Int32(Some(53))),
1744 ..Default::default()
1745 },
1746 ColumnStatistics {
1747 min_value: Precision::Inexact(ScalarValue::Int32(Some(3))),
1748 max_value: Precision::Inexact(ScalarValue::Int32(Some(3))),
1749 ..Default::default()
1750 },
1751 ColumnStatistics {
1752 min_value: Precision::Inexact(ScalarValue::Float32(Some(1000.0))),
1753 max_value: Precision::Inexact(ScalarValue::Float32(Some(1075.0))),
1754 ..Default::default()
1755 },
1756 ];
1757 let _ = exp_col_stats
1758 .into_iter()
1759 .zip(statistics.column_statistics.clone())
1760 .map(|(expected, actual)| {
1761 if let Some(val) = actual.min_value.get_value() {
1762 if val.data_type().is_floating() {
1763 let actual_min = actual.min_value.get_value().unwrap();
1766 let actual_max = actual.max_value.get_value().unwrap();
1767 let expected_min = expected.min_value.get_value().unwrap();
1768 let expected_max = expected.max_value.get_value().unwrap();
1769 let eps = ScalarValue::Float32(Some(1e-6));
1770
1771 assert!(actual_min.sub(expected_min).unwrap() < eps);
1772 assert!(actual_min.sub(expected_min).unwrap() < eps);
1773
1774 assert!(actual_max.sub(expected_max).unwrap() < eps);
1775 assert!(actual_max.sub(expected_max).unwrap() < eps);
1776 } else {
1777 assert_eq!(actual, expected);
1778 }
1779 } else {
1780 assert_eq!(actual, expected);
1781 }
1782 });
1783
1784 Ok(())
1785 }
1786
1787 #[tokio::test]
1788 async fn test_filter_statistics_full_selective() -> Result<()> {
1789 let schema = Schema::new(vec![
1793 Field::new("a", DataType::Int32, false),
1794 Field::new("b", DataType::Int32, false),
1795 ]);
1796 let input = Arc::new(StatisticsExec::new(
1797 Statistics {
1798 num_rows: Precision::Inexact(1000),
1799 total_byte_size: Precision::Inexact(4000),
1800 column_statistics: vec![
1801 ColumnStatistics {
1802 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
1803 max_value: Precision::Inexact(ScalarValue::Int32(Some(100))),
1804 ..Default::default()
1805 },
1806 ColumnStatistics {
1807 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
1808 max_value: Precision::Inexact(ScalarValue::Int32(Some(3))),
1809 ..Default::default()
1810 },
1811 ],
1812 },
1813 schema,
1814 ));
1815 let predicate = Arc::new(BinaryExpr::new(
1817 Arc::new(BinaryExpr::new(
1818 Arc::new(Column::new("a", 0)),
1819 Operator::Lt,
1820 Arc::new(Literal::new(ScalarValue::Int32(Some(200)))),
1821 )),
1822 Operator::And,
1823 Arc::new(BinaryExpr::new(
1824 Arc::new(Literal::new(ScalarValue::Int32(Some(1)))),
1825 Operator::LtEq,
1826 Arc::new(Column::new("b", 1)),
1827 )),
1828 ));
1829 let mut expected = StatisticsContext::new()
1833 .compute(input.as_ref(), &StatisticsArgs::new())?
1834 .column_statistics
1835 .clone();
1836 for col in &mut expected {
1837 col.null_count = Precision::Exact(0);
1838 }
1839 let filter: Arc<dyn ExecutionPlan> =
1840 Arc::new(FilterExec::try_new(predicate, input)?);
1841 let statistics =
1842 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
1843
1844 assert_eq!(statistics.num_rows, Precision::Inexact(1000));
1845 assert_eq!(statistics.total_byte_size, Precision::Inexact(4000));
1846 assert_eq!(statistics.column_statistics, expected);
1847
1848 Ok(())
1849 }
1850
1851 #[tokio::test]
1852 async fn test_filter_statistics_zero_selective() -> Result<()> {
1853 let schema = Schema::new(vec![
1857 Field::new("a", DataType::Int32, false),
1858 Field::new("b", DataType::Int32, false),
1859 ]);
1860 let input = Arc::new(StatisticsExec::new(
1861 Statistics {
1862 num_rows: Precision::Inexact(1000),
1863 total_byte_size: Precision::Inexact(4000),
1864 column_statistics: vec![
1865 ColumnStatistics {
1866 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
1867 max_value: Precision::Inexact(ScalarValue::Int32(Some(100))),
1868 ..Default::default()
1869 },
1870 ColumnStatistics {
1871 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
1872 max_value: Precision::Inexact(ScalarValue::Int32(Some(3))),
1873 ..Default::default()
1874 },
1875 ],
1876 },
1877 schema,
1878 ));
1879 let predicate = Arc::new(BinaryExpr::new(
1881 Arc::new(BinaryExpr::new(
1882 Arc::new(Column::new("a", 0)),
1883 Operator::Gt,
1884 Arc::new(Literal::new(ScalarValue::Int32(Some(200)))),
1885 )),
1886 Operator::And,
1887 Arc::new(BinaryExpr::new(
1888 Arc::new(Literal::new(ScalarValue::Int32(Some(1)))),
1889 Operator::LtEq,
1890 Arc::new(Column::new("b", 1)),
1891 )),
1892 ));
1893 let filter: Arc<dyn ExecutionPlan> =
1894 Arc::new(FilterExec::try_new(predicate, input)?);
1895 let statistics =
1896 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
1897
1898 assert_eq!(statistics.num_rows, Precision::Inexact(0));
1899 assert_eq!(statistics.total_byte_size, Precision::Inexact(0));
1900 assert_eq!(
1901 statistics.column_statistics,
1902 vec![
1903 ColumnStatistics {
1904 min_value: Precision::Exact(ScalarValue::Int32(None)),
1905 max_value: Precision::Exact(ScalarValue::Int32(None)),
1906 sum_value: Precision::Exact(ScalarValue::Int32(None)),
1907 distinct_count: Precision::Exact(0),
1908 null_count: Precision::Exact(0),
1909 byte_size: Precision::Exact(0),
1910 },
1911 ColumnStatistics {
1912 min_value: Precision::Exact(ScalarValue::Int32(None)),
1913 max_value: Precision::Exact(ScalarValue::Int32(None)),
1914 sum_value: Precision::Exact(ScalarValue::Int32(None)),
1915 distinct_count: Precision::Exact(0),
1916 null_count: Precision::Exact(0),
1917 byte_size: Precision::Exact(0),
1918 },
1919 ]
1920 );
1921
1922 Ok(())
1923 }
1924
1925 #[tokio::test]
1935 async fn test_nested_filter_with_zero_selectivity_inner() -> Result<()> {
1936 let schema = Schema::new(vec![
1938 Field::new("a", DataType::Int32, false),
1939 Field::new("b", DataType::Int32, false),
1940 ]);
1941 let input = Arc::new(StatisticsExec::new(
1942 Statistics {
1943 num_rows: Precision::Inexact(1000),
1944 total_byte_size: Precision::Inexact(4000),
1945 column_statistics: vec![
1946 ColumnStatistics {
1947 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
1948 max_value: Precision::Inexact(ScalarValue::Int32(Some(100))),
1949 ..Default::default()
1950 },
1951 ColumnStatistics {
1952 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
1953 max_value: Precision::Inexact(ScalarValue::Int32(Some(3))),
1954 ..Default::default()
1955 },
1956 ],
1957 },
1958 schema,
1959 ));
1960
1961 let inner_predicate: Arc<dyn PhysicalExpr> = Arc::new(BinaryExpr::new(
1963 Arc::new(Column::new("a", 0)),
1964 Operator::Gt,
1965 Arc::new(Literal::new(ScalarValue::Int32(Some(200)))),
1966 ));
1967 let inner_filter: Arc<dyn ExecutionPlan> =
1968 Arc::new(FilterExec::try_new(inner_predicate, input)?);
1969
1970 let outer_predicate: Arc<dyn PhysicalExpr> = Arc::new(BinaryExpr::new(
1975 Arc::new(Column::new("a", 0)),
1976 Operator::Eq,
1977 Arc::new(Literal::new(ScalarValue::Int32(Some(50)))),
1978 ));
1979 let outer_filter: Arc<dyn ExecutionPlan> =
1980 Arc::new(FilterExec::try_new(outer_predicate, inner_filter)?);
1981
1982 let statistics = StatisticsContext::new()
1984 .compute(outer_filter.as_ref(), &StatisticsArgs::new())?;
1985 assert_eq!(statistics.num_rows, Precision::Inexact(0));
1986
1987 Ok(())
1988 }
1989
1990 #[tokio::test]
1991 async fn test_filter_statistics_more_inputs() -> Result<()> {
1992 let schema = Schema::new(vec![
1993 Field::new("a", DataType::Int32, false),
1994 Field::new("b", DataType::Int32, false),
1995 ]);
1996 let input = Arc::new(StatisticsExec::new(
1997 Statistics {
1998 num_rows: Precision::Inexact(1000),
1999 total_byte_size: Precision::Inexact(4000),
2000 column_statistics: vec![
2001 ColumnStatistics {
2002 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
2003 max_value: Precision::Inexact(ScalarValue::Int32(Some(100))),
2004 ..Default::default()
2005 },
2006 ColumnStatistics {
2007 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
2008 max_value: Precision::Inexact(ScalarValue::Int32(Some(100))),
2009 ..Default::default()
2010 },
2011 ],
2012 },
2013 schema,
2014 ));
2015 let predicate = Arc::new(BinaryExpr::new(
2017 Arc::new(Column::new("a", 0)),
2018 Operator::Lt,
2019 Arc::new(Literal::new(ScalarValue::Int32(Some(50)))),
2020 ));
2021 let filter: Arc<dyn ExecutionPlan> =
2022 Arc::new(FilterExec::try_new(predicate, input)?);
2023 let statistics =
2024 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
2025
2026 assert_eq!(statistics.num_rows, Precision::Inexact(490));
2027 assert_eq!(statistics.total_byte_size, Precision::Inexact(1960));
2028 assert_eq!(
2029 statistics.column_statistics,
2030 vec![
2031 ColumnStatistics {
2032 null_count: Precision::Exact(0),
2034 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
2035 max_value: Precision::Inexact(ScalarValue::Int32(Some(49))),
2036 ..Default::default()
2037 },
2038 ColumnStatistics {
2041 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
2042 max_value: Precision::Inexact(ScalarValue::Int32(Some(100))),
2043 ..Default::default()
2044 },
2045 ]
2046 );
2047
2048 Ok(())
2049 }
2050
2051 #[tokio::test]
2052 async fn test_empty_input_statistics() -> Result<()> {
2053 let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
2054 let input = Arc::new(StatisticsExec::new(
2055 Statistics::new_unknown(&schema),
2056 schema,
2057 ));
2058 let predicate = Arc::new(BinaryExpr::new(
2060 Arc::new(BinaryExpr::new(
2061 Arc::new(Column::new("a", 0)),
2062 Operator::LtEq,
2063 Arc::new(Literal::new(ScalarValue::Int32(Some(10)))),
2064 )),
2065 Operator::And,
2066 Arc::new(BinaryExpr::new(
2067 Arc::new(Literal::new(ScalarValue::Int32(Some(0)))),
2068 Operator::LtEq,
2069 Arc::new(BinaryExpr::new(
2070 Arc::new(Column::new("a", 0)),
2071 Operator::Minus,
2072 Arc::new(Literal::new(ScalarValue::Int32(Some(5)))),
2073 )),
2074 )),
2075 ));
2076 let filter: Arc<dyn ExecutionPlan> =
2077 Arc::new(FilterExec::try_new(predicate, input)?);
2078 let filter_statistics =
2079 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
2080
2081 let expected_filter_statistics = Statistics {
2082 num_rows: Precision::Absent,
2083 total_byte_size: Precision::Absent,
2084 column_statistics: vec![ColumnStatistics {
2085 null_count: Precision::Exact(0),
2088 min_value: Precision::Inexact(ScalarValue::Int32(Some(5))),
2089 max_value: Precision::Inexact(ScalarValue::Int32(Some(10))),
2090 sum_value: Precision::Absent,
2091 distinct_count: Precision::Absent,
2092 byte_size: Precision::Absent,
2093 }],
2094 };
2095
2096 assert_eq!(*filter_statistics, expected_filter_statistics);
2097
2098 Ok(())
2099 }
2100
2101 #[tokio::test]
2102 async fn test_statistics_with_constant_column() -> Result<()> {
2103 let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
2104 let input = Arc::new(StatisticsExec::new(
2105 Statistics::new_unknown(&schema),
2106 schema,
2107 ));
2108 let predicate = Arc::new(BinaryExpr::new(
2110 Arc::new(Column::new("a", 0)),
2111 Operator::Eq,
2112 Arc::new(Literal::new(ScalarValue::Int32(Some(10)))),
2113 ));
2114 let filter: Arc<dyn ExecutionPlan> =
2115 Arc::new(FilterExec::try_new(predicate, input)?);
2116 let filter_statistics =
2117 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
2118 assert!(filter_statistics.column_statistics[0].is_singleton());
2120
2121 Ok(())
2122 }
2123
2124 #[tokio::test]
2125 async fn test_validation_filter_selectivity() -> Result<()> {
2126 let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
2127 let input = Arc::new(StatisticsExec::new(
2128 Statistics::new_unknown(&schema),
2129 schema,
2130 ));
2131 let predicate = Arc::new(BinaryExpr::new(
2133 Arc::new(Column::new("a", 0)),
2134 Operator::Eq,
2135 Arc::new(Literal::new(ScalarValue::Int32(Some(10)))),
2136 ));
2137 let filter = FilterExec::try_new(predicate, input)?;
2138 assert!(filter.with_default_selectivity(120).is_err());
2139 Ok(())
2140 }
2141
2142 #[tokio::test]
2143 async fn test_custom_filter_selectivity() -> Result<()> {
2144 let schema =
2146 Schema::new(vec![Field::new("a", DataType::Decimal128(2, 3), false)]);
2147 let input = Arc::new(StatisticsExec::new(
2148 Statistics {
2149 num_rows: Precision::Inexact(1000),
2150 total_byte_size: Precision::Inexact(4000),
2151 column_statistics: vec![ColumnStatistics {
2152 ..Default::default()
2153 }],
2154 },
2155 schema,
2156 ));
2157 let predicate = Arc::new(BinaryExpr::new(
2159 Arc::new(Column::new("a", 0)),
2160 Operator::Eq,
2161 Arc::new(Literal::new(ScalarValue::Decimal128(Some(10), 10, 10))),
2162 ));
2163 let filter = FilterExec::try_new(predicate, input)?;
2164 let statistics =
2165 StatisticsContext::new().compute(&filter, &StatisticsArgs::new())?;
2166 assert_eq!(statistics.num_rows, Precision::Inexact(200));
2167 assert_eq!(statistics.total_byte_size, Precision::Inexact(800));
2168 let filter = filter.with_default_selectivity(40)?;
2169 let statistics =
2170 StatisticsContext::new().compute(&filter, &StatisticsArgs::new())?;
2171 assert_eq!(statistics.num_rows, Precision::Inexact(400));
2172 assert_eq!(statistics.total_byte_size, Precision::Inexact(1600));
2173 Ok(())
2174 }
2175
2176 #[test]
2177 fn test_equivalence_properties_union_type() -> Result<()> {
2178 let union_type = DataType::Union(
2179 UnionFields::try_new(
2180 vec![0, 1],
2181 vec![
2182 Field::new("f1", DataType::Int32, true),
2183 Field::new("f2", DataType::Utf8, true),
2184 ],
2185 )
2186 .unwrap(),
2187 UnionMode::Sparse,
2188 );
2189
2190 let schema = Arc::new(Schema::new(vec![
2191 Field::new("c1", DataType::Int32, true),
2192 Field::new("c2", union_type, true),
2193 ]));
2194
2195 let exec = FilterExec::try_new(
2196 binary(
2197 binary(col("c1", &schema)?, Operator::GtEq, lit(1i32), &schema)?,
2198 Operator::And,
2199 binary(col("c1", &schema)?, Operator::LtEq, lit(4i32), &schema)?,
2200 &schema,
2201 )?,
2202 Arc::new(EmptyExec::new(Arc::clone(&schema))),
2203 )?;
2204
2205 StatisticsContext::new()
2206 .compute(&exec, &StatisticsArgs::new())
2207 .unwrap();
2208
2209 Ok(())
2210 }
2211
2212 #[tokio::test]
2213 async fn test_builder_with_projection() -> Result<()> {
2214 let schema = Arc::new(Schema::new(vec![
2216 Field::new("a", DataType::Int32, false),
2217 Field::new("b", DataType::Int32, false),
2218 Field::new("c", DataType::Int32, false),
2219 ]));
2220
2221 let input = Arc::new(EmptyExec::new(Arc::clone(&schema)));
2222
2223 let predicate = Arc::new(BinaryExpr::new(
2225 Arc::new(Column::new("a", 0)),
2226 Operator::Gt,
2227 Arc::new(Literal::new(ScalarValue::Int32(Some(10)))),
2228 ));
2229
2230 let projection = Some(vec![0, 2]);
2232 let filter = FilterExecBuilder::new(predicate, input)
2233 .apply_projection(projection.clone())
2234 .unwrap()
2235 .build()?;
2236
2237 assert_eq!(filter.projection(), &Some([0, 2].into()));
2239
2240 let output_schema = filter.schema();
2242 assert_eq!(output_schema.fields().len(), 2);
2243 assert_eq!(output_schema.field(0).name(), "a");
2244 assert_eq!(output_schema.field(1).name(), "c");
2245
2246 Ok(())
2247 }
2248
2249 #[tokio::test]
2250 async fn test_builder_without_projection() -> Result<()> {
2251 let schema = Arc::new(Schema::new(vec![
2252 Field::new("a", DataType::Int32, false),
2253 Field::new("b", DataType::Int32, false),
2254 ]));
2255
2256 let input = Arc::new(EmptyExec::new(Arc::clone(&schema)));
2257
2258 let predicate = Arc::new(BinaryExpr::new(
2259 Arc::new(Column::new("a", 0)),
2260 Operator::Gt,
2261 Arc::new(Literal::new(ScalarValue::Int32(Some(5)))),
2262 ));
2263
2264 let filter = FilterExecBuilder::new(predicate, input).build()?;
2266
2267 assert!(filter.projection().is_none());
2269
2270 let output_schema = filter.schema();
2272 assert_eq!(output_schema.fields().len(), 2);
2273
2274 Ok(())
2275 }
2276
2277 #[tokio::test]
2278 async fn test_builder_invalid_projection() -> Result<()> {
2279 let schema = Arc::new(Schema::new(vec![
2280 Field::new("a", DataType::Int32, false),
2281 Field::new("b", DataType::Int32, false),
2282 ]));
2283
2284 let input = Arc::new(EmptyExec::new(Arc::clone(&schema)));
2285
2286 let predicate = Arc::new(BinaryExpr::new(
2287 Arc::new(Column::new("a", 0)),
2288 Operator::Gt,
2289 Arc::new(Literal::new(ScalarValue::Int32(Some(5)))),
2290 ));
2291
2292 let result =
2294 FilterExecBuilder::new(predicate, input).apply_projection(Some(vec![0, 5])); assert!(result.is_err());
2298
2299 Ok(())
2300 }
2301
2302 #[tokio::test]
2303 async fn test_builder_vs_with_projection() -> Result<()> {
2304 let schema = Schema::new(vec![
2307 Field::new("a", DataType::Int32, false),
2308 Field::new("b", DataType::Int32, false),
2309 Field::new("c", DataType::Int32, false),
2310 Field::new("d", DataType::Int32, false),
2311 ]);
2312
2313 let input = Arc::new(StatisticsExec::new(
2314 Statistics {
2315 num_rows: Precision::Inexact(1000),
2316 total_byte_size: Precision::Inexact(4000),
2317 column_statistics: vec![
2318 ColumnStatistics {
2319 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
2320 max_value: Precision::Inexact(ScalarValue::Int32(Some(100))),
2321 ..Default::default()
2322 },
2323 ColumnStatistics {
2324 ..Default::default()
2325 },
2326 ColumnStatistics {
2327 ..Default::default()
2328 },
2329 ColumnStatistics {
2330 ..Default::default()
2331 },
2332 ],
2333 },
2334 schema,
2335 ));
2336 let input: Arc<dyn ExecutionPlan> = input;
2337
2338 let predicate: Arc<dyn PhysicalExpr> = Arc::new(BinaryExpr::new(
2339 Arc::new(Column::new("a", 0)),
2340 Operator::Lt,
2341 Arc::new(Literal::new(ScalarValue::Int32(Some(50)))),
2342 ));
2343
2344 let projection = Some(vec![0, 2]);
2345
2346 let filter1 = FilterExecBuilder::new(Arc::clone(&predicate), Arc::clone(&input))
2348 .apply_projection(projection.clone())
2349 .unwrap()
2350 .build()?;
2351
2352 let filter2 = FilterExecBuilder::new(predicate, input)
2354 .apply_projection(projection)
2355 .unwrap()
2356 .build()?;
2357
2358 assert_eq!(filter1.schema(), filter2.schema());
2360 assert_eq!(filter1.projection(), filter2.projection());
2361
2362 let stats1 =
2364 StatisticsContext::new().compute(&filter1, &StatisticsArgs::new())?;
2365 let stats2 =
2366 StatisticsContext::new().compute(&filter2, &StatisticsArgs::new())?;
2367 assert_eq!(stats1.num_rows, stats2.num_rows);
2368 assert_eq!(stats1.total_byte_size, stats2.total_byte_size);
2369
2370 Ok(())
2371 }
2372
2373 #[tokio::test]
2374 async fn test_builder_statistics_with_projection() -> Result<()> {
2375 let schema = Schema::new(vec![
2377 Field::new("a", DataType::Int32, false),
2378 Field::new("b", DataType::Int32, false),
2379 Field::new("c", DataType::Int32, false),
2380 ]);
2381
2382 let input = Arc::new(StatisticsExec::new(
2383 Statistics {
2384 num_rows: Precision::Inexact(1000),
2385 total_byte_size: Precision::Inexact(12000),
2386 column_statistics: vec![
2387 ColumnStatistics {
2388 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
2389 max_value: Precision::Inexact(ScalarValue::Int32(Some(100))),
2390 ..Default::default()
2391 },
2392 ColumnStatistics {
2393 min_value: Precision::Inexact(ScalarValue::Int32(Some(10))),
2394 max_value: Precision::Inexact(ScalarValue::Int32(Some(200))),
2395 ..Default::default()
2396 },
2397 ColumnStatistics {
2398 min_value: Precision::Inexact(ScalarValue::Int32(Some(5))),
2399 max_value: Precision::Inexact(ScalarValue::Int32(Some(50))),
2400 ..Default::default()
2401 },
2402 ],
2403 },
2404 schema,
2405 ));
2406
2407 let predicate = Arc::new(BinaryExpr::new(
2409 Arc::new(Column::new("a", 0)),
2410 Operator::Lt,
2411 Arc::new(Literal::new(ScalarValue::Int32(Some(50)))),
2412 ));
2413
2414 let filter = FilterExecBuilder::new(predicate, input)
2415 .apply_projection(Some(vec![0, 2]))
2416 .unwrap()
2417 .build()?;
2418
2419 let statistics =
2420 StatisticsContext::new().compute(&filter, &StatisticsArgs::new())?;
2421
2422 assert!(matches!(statistics.num_rows, Precision::Inexact(_)));
2424
2425 assert_eq!(filter.schema().fields().len(), 2);
2427
2428 Ok(())
2429 }
2430
2431 #[test]
2432 fn test_builder_predicate_validation() -> Result<()> {
2433 let schema = Arc::new(Schema::new(vec![
2435 Field::new("a", DataType::Int32, false),
2436 Field::new("b", DataType::Int32, false),
2437 ]));
2438
2439 let input = Arc::new(EmptyExec::new(Arc::clone(&schema)));
2440
2441 let invalid_predicate = Arc::new(Column::new("a", 0));
2443
2444 let result = FilterExecBuilder::new(invalid_predicate, input)
2446 .apply_projection(Some(vec![0]))
2447 .unwrap()
2448 .build();
2449
2450 assert!(result.is_err());
2451
2452 Ok(())
2453 }
2454
2455 #[tokio::test]
2456 async fn test_builder_projection_composition() -> Result<()> {
2457 let schema = Arc::new(Schema::new(vec![
2461 Field::new("a", DataType::Int32, false),
2462 Field::new("b", DataType::Int32, false),
2463 Field::new("c", DataType::Int32, false),
2464 Field::new("d", DataType::Int32, false),
2465 ]));
2466
2467 let input = Arc::new(EmptyExec::new(Arc::clone(&schema)));
2468
2469 let predicate = Arc::new(BinaryExpr::new(
2471 Arc::new(Column::new("a", 0)),
2472 Operator::Gt,
2473 Arc::new(Literal::new(ScalarValue::Int32(Some(10)))),
2474 ));
2475
2476 let filter = FilterExecBuilder::new(predicate, input)
2480 .apply_projection(Some(vec![0, 2, 3]))?
2481 .apply_projection(Some(vec![0, 2]))?
2482 .build()?;
2483
2484 assert_eq!(filter.projection(), &Some([0, 3].into()));
2486
2487 let output_schema = filter.schema();
2489 assert_eq!(output_schema.fields().len(), 2);
2490 assert_eq!(output_schema.field(0).name(), "a");
2491 assert_eq!(output_schema.field(1).name(), "d");
2492
2493 Ok(())
2494 }
2495
2496 #[tokio::test]
2497 async fn test_builder_projection_composition_none_clears() -> Result<()> {
2498 let schema = Arc::new(Schema::new(vec![
2500 Field::new("a", DataType::Int32, false),
2501 Field::new("b", DataType::Int32, false),
2502 ]));
2503
2504 let input = Arc::new(EmptyExec::new(Arc::clone(&schema)));
2505
2506 let predicate = Arc::new(BinaryExpr::new(
2507 Arc::new(Column::new("a", 0)),
2508 Operator::Gt,
2509 Arc::new(Literal::new(ScalarValue::Int32(Some(10)))),
2510 ));
2511
2512 let filter = FilterExecBuilder::new(predicate, input)
2514 .apply_projection(Some(vec![0]))?
2515 .apply_projection(None)?
2516 .build()?;
2517
2518 assert_eq!(filter.projection(), &None);
2520
2521 let output_schema = filter.schema();
2523 assert_eq!(output_schema.fields().len(), 2);
2524
2525 Ok(())
2526 }
2527
2528 #[test]
2529 fn test_filter_with_projection_remaps_post_phase_parent_filters() -> Result<()> {
2530 let input_schema = Arc::new(Schema::new(vec![
2534 Field::new("a", DataType::Int32, false),
2535 Field::new("b", DataType::Utf8, false),
2536 Field::new("c", DataType::Float64, false),
2537 ]));
2538 let input = Arc::new(EmptyExec::new(Arc::clone(&input_schema)));
2539
2540 let predicate = Arc::new(BinaryExpr::new(
2542 Arc::new(Column::new("a", 0)),
2543 Operator::Gt,
2544 Arc::new(Literal::new(ScalarValue::Int32(Some(0)))),
2545 ));
2546 let filter = FilterExecBuilder::new(predicate, input)
2547 .apply_projection(Some(vec![2]))?
2548 .build()?;
2549
2550 let output_schema = filter.schema();
2552 assert_eq!(output_schema.fields().len(), 1);
2553 assert_eq!(output_schema.field(0).name(), "c");
2554
2555 let parent_filter: Arc<dyn PhysicalExpr> = Arc::new(Column::new("c", 0));
2557
2558 let config = ConfigOptions::new();
2559 let desc = filter.gather_filters_for_pushdown(
2560 FilterPushdownPhase::Post,
2561 vec![parent_filter],
2562 &config,
2563 )?;
2564
2565 let parent_filters = desc.parent_filters();
2568 assert_eq!(parent_filters.len(), 1); assert_eq!(parent_filters[0].len(), 1); let remapped = &parent_filters[0][0].predicate;
2571 let display = format!("{remapped}");
2572 assert_eq!(
2573 display, "c@2",
2574 "Post-phase parent filter column index must be remapped \
2575 from output schema (c@0) to input schema (c@2)"
2576 );
2577
2578 Ok(())
2579 }
2580
2581 #[test]
2589 fn test_collect_columns_skips_non_column_pairs() -> Result<()> {
2590 let schema = test::aggr_test_schema();
2591
2592 let complex_expr: Arc<dyn PhysicalExpr> = binary(
2595 col("c2", &schema)?,
2596 Operator::IsDistinctFrom,
2597 lit(0u32),
2598 &schema,
2599 )?;
2600 let predicate: Arc<dyn PhysicalExpr> =
2601 binary(complex_expr, Operator::Eq, lit(0u32), &schema)?;
2602
2603 let (equal_pairs, _) = collect_columns_from_predicate_inner(&predicate);
2604 assert_eq!(
2605 0,
2606 equal_pairs.len(),
2607 "Should not extract equality pairs where neither side is a Column"
2608 );
2609
2610 let predicate: Arc<dyn PhysicalExpr> =
2612 binary(col("c2", &schema)?, Operator::Eq, lit(0u32), &schema)?;
2613 let (equal_pairs, _) = collect_columns_from_predicate_inner(&predicate);
2614 assert_eq!(
2615 1,
2616 equal_pairs.len(),
2617 "Should extract equality pairs where one side is a Column"
2618 );
2619
2620 Ok(())
2621 }
2622
2623 #[tokio::test]
2626 async fn test_filter_statistics_absent_columns_stay_absent() -> Result<()> {
2627 let schema = Schema::new(vec![
2628 Field::new("a", DataType::Int32, false),
2629 Field::new("b", DataType::Int32, false),
2630 ]);
2631 let input = Arc::new(StatisticsExec::new(
2632 Statistics {
2633 num_rows: Precision::Inexact(1000),
2634 total_byte_size: Precision::Absent,
2635 column_statistics: vec![
2636 ColumnStatistics::default(),
2637 ColumnStatistics::default(),
2638 ],
2639 },
2640 schema.clone(),
2641 ));
2642
2643 let predicate = Arc::new(BinaryExpr::new(
2644 Arc::new(Column::new("a", 0)),
2645 Operator::Eq,
2646 Arc::new(Literal::new(ScalarValue::Int32(Some(42)))),
2647 ));
2648 let filter: Arc<dyn ExecutionPlan> =
2649 Arc::new(FilterExec::try_new(predicate, input)?);
2650
2651 let statistics =
2652 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
2653 let col_b_stats = &statistics.column_statistics[1];
2654 assert_eq!(col_b_stats.min_value, Precision::Absent);
2655 assert_eq!(col_b_stats.max_value, Precision::Absent);
2656
2657 Ok(())
2658 }
2659
2660 #[tokio::test]
2661 async fn test_filter_statistics_equality_ndv() -> Result<()> {
2662 #[expect(clippy::type_complexity)]
2663 let cases: Vec<(
2664 &str,
2665 Vec<Field>,
2666 Vec<ColumnStatistics>,
2667 Arc<dyn PhysicalExpr>,
2668 Vec<Precision<usize>>,
2669 )> = vec![
2670 (
2671 "utf8 equality",
2672 vec![Field::new("name", DataType::Utf8, false)],
2673 vec![ColumnStatistics {
2674 distinct_count: Precision::Inexact(50),
2675 ..Default::default()
2676 }],
2677 Arc::new(BinaryExpr::new(
2678 Arc::new(Column::new("name", 0)),
2679 Operator::Eq,
2680 Arc::new(Literal::new(ScalarValue::Utf8(Some("hello".to_string())))),
2681 )),
2682 vec![Precision::Exact(1)],
2683 ),
2684 (
2685 "utf8view equality",
2686 vec![Field::new("name", DataType::Utf8View, false)],
2687 vec![ColumnStatistics {
2688 distinct_count: Precision::Inexact(50),
2689 ..Default::default()
2690 }],
2691 Arc::new(BinaryExpr::new(
2692 Arc::new(Column::new("name", 0)),
2693 Operator::Eq,
2694 Arc::new(Literal::new(ScalarValue::Utf8View(Some(
2695 "hello".to_string(),
2696 )))),
2697 )),
2698 vec![Precision::Exact(1)],
2699 ),
2700 (
2701 "largeutf8 equality",
2702 vec![Field::new("name", DataType::LargeUtf8, false)],
2703 vec![ColumnStatistics {
2704 distinct_count: Precision::Inexact(50),
2705 ..Default::default()
2706 }],
2707 Arc::new(BinaryExpr::new(
2708 Arc::new(Column::new("name", 0)),
2709 Operator::Eq,
2710 Arc::new(Literal::new(ScalarValue::LargeUtf8(Some(
2711 "hello".to_string(),
2712 )))),
2713 )),
2714 vec![Precision::Exact(1)],
2715 ),
2716 (
2717 "utf8 reversed (literal = column)",
2718 vec![Field::new("name", DataType::Utf8, false)],
2719 vec![ColumnStatistics {
2720 distinct_count: Precision::Inexact(50),
2721 ..Default::default()
2722 }],
2723 Arc::new(BinaryExpr::new(
2724 Arc::new(Literal::new(ScalarValue::Utf8(Some("hello".to_string())))),
2725 Operator::Eq,
2726 Arc::new(Column::new("name", 0)),
2727 )),
2728 vec![Precision::Exact(1)],
2729 ),
2730 (
2731 "OR is not collapsed to NDV=1, but NDV is capped at filtered rows",
2732 vec![Field::new("name", DataType::Utf8, false)],
2733 vec![ColumnStatistics {
2734 distinct_count: Precision::Inexact(50),
2735 ..Default::default()
2736 }],
2737 Arc::new(BinaryExpr::new(
2738 Arc::new(BinaryExpr::new(
2739 Arc::new(Column::new("name", 0)),
2740 Operator::Eq,
2741 Arc::new(Literal::new(ScalarValue::Utf8(Some("a".to_string())))),
2742 )),
2743 Operator::Or,
2744 Arc::new(BinaryExpr::new(
2745 Arc::new(Column::new("name", 0)),
2746 Operator::Eq,
2747 Arc::new(Literal::new(ScalarValue::Utf8(Some("b".to_string())))),
2748 )),
2749 )),
2750 vec![Precision::Inexact(20)],
2753 ),
2754 (
2755 "AND with mixed types (Utf8 + Int32)",
2756 vec![
2757 Field::new("name", DataType::Utf8, false),
2758 Field::new("age", DataType::Int32, false),
2759 ],
2760 vec![
2761 ColumnStatistics {
2762 distinct_count: Precision::Inexact(50),
2763 ..Default::default()
2764 },
2765 ColumnStatistics {
2766 distinct_count: Precision::Inexact(80),
2767 ..Default::default()
2768 },
2769 ],
2770 Arc::new(BinaryExpr::new(
2771 Arc::new(BinaryExpr::new(
2772 Arc::new(Column::new("name", 0)),
2773 Operator::Eq,
2774 Arc::new(Literal::new(ScalarValue::Utf8(Some(
2775 "hello".to_string(),
2776 )))),
2777 )),
2778 Operator::And,
2779 Arc::new(BinaryExpr::new(
2780 Arc::new(Column::new("age", 1)),
2781 Operator::Eq,
2782 Arc::new(Literal::new(ScalarValue::Int32(Some(42)))),
2783 )),
2784 )),
2785 vec![Precision::Exact(1), Precision::Exact(1)],
2786 ),
2787 (
2788 "numeric equality with min/max bounds (interval analysis path)",
2789 vec![Field::new("a", DataType::Int32, false)],
2790 vec![ColumnStatistics {
2791 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
2792 max_value: Precision::Inexact(ScalarValue::Int32(Some(100))),
2793 distinct_count: Precision::Inexact(80),
2794 ..Default::default()
2795 }],
2796 Arc::new(BinaryExpr::new(
2797 Arc::new(Column::new("a", 0)),
2798 Operator::Eq,
2799 Arc::new(Literal::new(ScalarValue::Int32(Some(42)))),
2800 )),
2801 vec![Precision::Exact(1)],
2802 ),
2803 (
2804 "timestamp equality",
2805 vec![Field::new(
2806 "ts",
2807 DataType::Timestamp(arrow::datatypes::TimeUnit::Nanosecond, None),
2808 false,
2809 )],
2810 vec![ColumnStatistics {
2811 distinct_count: Precision::Inexact(500),
2812 ..Default::default()
2813 }],
2814 Arc::new(BinaryExpr::new(
2815 Arc::new(Column::new("ts", 0)),
2816 Operator::Eq,
2817 Arc::new(Literal::new(ScalarValue::TimestampNanosecond(
2818 Some(1_609_459_200_000_000_000),
2819 None,
2820 ))),
2821 )),
2822 vec![Precision::Exact(1)],
2823 ),
2824 (
2825 "contradictory numeric equality (infeasible)",
2826 vec![Field::new("a", DataType::Int32, false)],
2827 vec![ColumnStatistics {
2828 distinct_count: Precision::Inexact(50),
2829 ..Default::default()
2830 }],
2831 Arc::new(BinaryExpr::new(
2832 Arc::new(BinaryExpr::new(
2833 Arc::new(Column::new("a", 0)),
2834 Operator::Eq,
2835 Arc::new(Literal::new(ScalarValue::Int32(Some(42)))),
2836 )),
2837 Operator::And,
2838 Arc::new(BinaryExpr::new(
2839 Arc::new(Column::new("a", 0)),
2840 Operator::Eq,
2841 Arc::new(Literal::new(ScalarValue::Int32(Some(99)))),
2842 )),
2843 )),
2844 vec![Precision::Exact(0)],
2845 ),
2846 (
2847 "utf8 equality with absent input NDV",
2848 vec![Field::new("name", DataType::Utf8, false)],
2849 vec![ColumnStatistics {
2850 distinct_count: Precision::Absent,
2851 ..Default::default()
2852 }],
2853 Arc::new(BinaryExpr::new(
2854 Arc::new(Column::new("name", 0)),
2855 Operator::Eq,
2856 Arc::new(Literal::new(ScalarValue::Utf8(Some("hello".to_string())))),
2857 )),
2858 vec![Precision::Exact(1)],
2859 ),
2860 (
2861 "contradictory utf8 equality (infeasible)",
2862 vec![Field::new("name", DataType::Utf8, false)],
2863 vec![ColumnStatistics {
2864 distinct_count: Precision::Inexact(100),
2865 ..Default::default()
2866 }],
2867 Arc::new(BinaryExpr::new(
2868 Arc::new(BinaryExpr::new(
2869 Arc::new(Column::new("name", 0)),
2870 Operator::Eq,
2871 Arc::new(Literal::new(ScalarValue::Utf8(Some(
2872 "alice".to_string(),
2873 )))),
2874 )),
2875 Operator::And,
2876 Arc::new(BinaryExpr::new(
2877 Arc::new(Column::new("name", 0)),
2878 Operator::Eq,
2879 Arc::new(Literal::new(ScalarValue::Utf8(Some(
2880 "bob".to_string(),
2881 )))),
2882 )),
2883 )),
2884 vec![Precision::Exact(0)],
2885 ),
2886 (
2887 "redundant same-value equality combined with another column",
2888 vec![
2889 Field::new("a", DataType::Int32, false),
2890 Field::new("b", DataType::Int32, false),
2891 ],
2892 vec![
2893 ColumnStatistics {
2894 distinct_count: Precision::Inexact(80),
2895 ..Default::default()
2896 },
2897 ColumnStatistics {
2898 distinct_count: Precision::Inexact(40),
2899 ..Default::default()
2900 },
2901 ],
2902 Arc::new(BinaryExpr::new(
2903 Arc::new(BinaryExpr::new(
2904 Arc::new(BinaryExpr::new(
2905 Arc::new(Column::new("a", 0)),
2906 Operator::Eq,
2907 Arc::new(Literal::new(ScalarValue::Int32(Some(1)))),
2908 )),
2909 Operator::And,
2910 Arc::new(BinaryExpr::new(
2911 Arc::new(Column::new("a", 0)),
2912 Operator::Eq,
2913 Arc::new(Literal::new(ScalarValue::Int32(Some(1)))),
2914 )),
2915 )),
2916 Operator::And,
2917 Arc::new(BinaryExpr::new(
2918 Arc::new(Column::new("b", 1)),
2919 Operator::Eq,
2920 Arc::new(Literal::new(ScalarValue::Int32(Some(2)))),
2921 )),
2922 )),
2923 vec![Precision::Exact(1), Precision::Exact(1)],
2924 ),
2925 ];
2926
2927 for (desc, fields, col_stats, predicate, expected_ndvs) in cases {
2928 let schema = Schema::new(fields);
2929 let input = Arc::new(StatisticsExec::new(
2930 Statistics {
2931 num_rows: Precision::Inexact(100),
2932 total_byte_size: Precision::Inexact(1000),
2933 column_statistics: col_stats,
2934 },
2935 schema.clone(),
2936 ));
2937 let filter: Arc<dyn ExecutionPlan> =
2938 Arc::new(FilterExec::try_new(predicate, input)?);
2939 let statistics = StatisticsContext::new()
2940 .compute(filter.as_ref(), &StatisticsArgs::new())?;
2941
2942 for (i, expected) in expected_ndvs.iter().enumerate() {
2943 assert_eq!(
2944 statistics.column_statistics[i].distinct_count, *expected,
2945 "case '{desc}': column {i} NDV mismatch"
2946 );
2947 }
2948 }
2949 Ok(())
2950 }
2951
2952 #[tokio::test]
2953 async fn test_filter_statistics_preserves_exactly_empty_input() -> Result<()> {
2954 let schema = Schema::new(vec![
2959 Field::new("a", DataType::Int32, true),
2960 Field::new("b", DataType::Int32, true),
2961 ]);
2962 let input_stats = Statistics {
2963 num_rows: Precision::Exact(0),
2964 total_byte_size: Precision::Exact(0),
2965 column_statistics: vec![
2966 ColumnStatistics {
2967 null_count: Precision::Exact(0),
2968 byte_size: Precision::Exact(0),
2969 ..Default::default()
2970 },
2971 ColumnStatistics {
2972 null_count: Precision::Exact(3),
2973 distinct_count: Precision::Exact(7),
2974 byte_size: Precision::Exact(0),
2975 ..Default::default()
2976 },
2977 ],
2978 };
2979 let predicate = Arc::new(BinaryExpr::new(
2980 Arc::new(Column::new("a", 0)),
2981 Operator::Gt,
2982 Arc::new(Literal::new(ScalarValue::Int32(Some(5)))),
2983 ));
2984
2985 let input = Arc::new(StatisticsExec::new(input_stats, schema.clone()));
2986 let filter: Arc<dyn ExecutionPlan> =
2987 Arc::new(FilterExec::try_new(predicate, input)?);
2988 let statistics =
2989 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
2990
2991 assert_eq!(statistics.num_rows, Precision::Exact(0));
2992 assert_eq!(statistics.total_byte_size, Precision::Exact(0));
2993 assert_eq!(
2994 statistics.column_statistics[0].byte_size,
2995 Precision::Exact(0)
2996 );
2997 assert_eq!(
2998 statistics.column_statistics[1].null_count,
2999 Precision::Exact(0)
3000 );
3001 assert_eq!(
3002 statistics.column_statistics[1].distinct_count,
3003 Precision::Exact(0)
3004 );
3005
3006 let input = Arc::new(StatisticsExec::new(
3009 Statistics {
3010 num_rows: Precision::Inexact(1000),
3011 total_byte_size: Precision::Inexact(8000),
3012 column_statistics: vec![ColumnStatistics::new_unknown(); 2],
3013 },
3014 schema,
3015 ));
3016 let contradiction = Arc::new(BinaryExpr::new(
3017 Arc::new(BinaryExpr::new(
3018 Arc::new(Column::new("a", 0)),
3019 Operator::Eq,
3020 Arc::new(Literal::new(ScalarValue::Int32(Some(1)))),
3021 )),
3022 Operator::And,
3023 Arc::new(BinaryExpr::new(
3024 Arc::new(Column::new("a", 0)),
3025 Operator::Eq,
3026 Arc::new(Literal::new(ScalarValue::Int32(Some(2)))),
3027 )),
3028 ));
3029 let filter: Arc<dyn ExecutionPlan> =
3030 Arc::new(FilterExec::try_new(contradiction, input)?);
3031 let statistics =
3032 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
3033
3034 assert_eq!(statistics.num_rows, Precision::Exact(0));
3035 assert_eq!(statistics.total_byte_size, Precision::Exact(0));
3036
3037 Ok(())
3038 }
3039
3040 #[tokio::test]
3041 async fn test_filter_statistics_exact_empty_input_zeroes_byte_size() -> Result<()> {
3042 let cases = [
3043 ("absent", Precision::Absent, Precision::Absent),
3044 ("inexact", Precision::Inexact(8000), Precision::Inexact(400)),
3045 ];
3046
3047 for (desc, input_total_byte_size, input_byte_size) in cases {
3048 let schema = Schema::new(vec![Field::new("a", DataType::Int32, true)]);
3049 let input_stats = Statistics {
3050 num_rows: Precision::Exact(0),
3051 total_byte_size: input_total_byte_size,
3052 column_statistics: vec![ColumnStatistics {
3053 byte_size: input_byte_size,
3054 ..Default::default()
3055 }],
3056 };
3057 let predicate = Arc::new(BinaryExpr::new(
3058 Arc::new(Column::new("a", 0)),
3059 Operator::Gt,
3060 Arc::new(Literal::new(ScalarValue::Int32(Some(5)))),
3061 ));
3062
3063 let input = Arc::new(StatisticsExec::new(input_stats, schema));
3064 let filter: Arc<dyn ExecutionPlan> =
3065 Arc::new(FilterExec::try_new(predicate, input)?);
3066 let statistics = StatisticsContext::new()
3067 .compute(filter.as_ref(), &StatisticsArgs::new())?;
3068
3069 assert_eq!(
3070 statistics.num_rows,
3071 Precision::Exact(0),
3072 "case '{desc}': num_rows mismatch"
3073 );
3074 assert_eq!(
3075 statistics.total_byte_size,
3076 Precision::Exact(0),
3077 "case '{desc}': total_byte_size mismatch"
3078 );
3079 assert_eq!(
3080 statistics.column_statistics[0].byte_size,
3081 Precision::Exact(0),
3082 "case '{desc}': byte_size mismatch"
3083 );
3084 }
3085
3086 Ok(())
3087 }
3088
3089 #[tokio::test]
3090 async fn test_filter_statistics_empty_input_equality_ndv_zero() -> Result<()> {
3091 let cases: Vec<(&str, Schema, Statistics, Arc<dyn PhysicalExpr>)> = vec![
3092 (
3093 "fallback string equality",
3094 Schema::new(vec![Field::new("name", DataType::Utf8, true)]),
3095 Statistics {
3096 num_rows: Precision::Exact(0),
3097 total_byte_size: Precision::Exact(0),
3098 column_statistics: vec![ColumnStatistics {
3099 distinct_count: Precision::Exact(0),
3100 null_count: Precision::Exact(0),
3101 byte_size: Precision::Exact(0),
3102 ..Default::default()
3103 }],
3104 },
3105 Arc::new(BinaryExpr::new(
3106 Arc::new(Column::new("name", 0)),
3107 Operator::Eq,
3108 Arc::new(Literal::new(ScalarValue::Utf8(Some("x".to_string())))),
3109 )),
3110 ),
3111 (
3112 "interval numeric equality",
3113 Schema::new(vec![Field::new("a", DataType::Int32, true)]),
3114 Statistics {
3115 num_rows: Precision::Exact(0),
3116 total_byte_size: Precision::Exact(0),
3117 column_statistics: vec![ColumnStatistics {
3118 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
3119 max_value: Precision::Inexact(ScalarValue::Int32(Some(10))),
3120 distinct_count: Precision::Exact(0),
3121 null_count: Precision::Exact(0),
3122 byte_size: Precision::Exact(0),
3123 ..Default::default()
3124 }],
3125 },
3126 Arc::new(BinaryExpr::new(
3127 Arc::new(Column::new("a", 0)),
3128 Operator::Eq,
3129 Arc::new(Literal::new(ScalarValue::Int32(Some(5)))),
3130 )),
3131 ),
3132 ];
3133
3134 for (desc, schema, input_stats, predicate) in cases {
3135 let input = Arc::new(StatisticsExec::new(input_stats, schema));
3136 let filter: Arc<dyn ExecutionPlan> =
3137 Arc::new(FilterExec::try_new(predicate, input)?);
3138 let statistics = StatisticsContext::new()
3139 .compute(filter.as_ref(), &StatisticsArgs::new())?;
3140
3141 assert_eq!(
3142 statistics.num_rows,
3143 Precision::Exact(0),
3144 "case '{desc}': row count mismatch"
3145 );
3146 assert_eq!(
3147 statistics.column_statistics[0].distinct_count,
3148 Precision::Exact(0),
3149 "case '{desc}': NDV should be capped at zero rows"
3150 );
3151 }
3152 Ok(())
3153 }
3154
3155 #[tokio::test]
3156 async fn test_filter_statistics_and_equality_ndv() -> Result<()> {
3157 let schema = Schema::new(vec![
3158 Field::new("a", DataType::Int32, false),
3159 Field::new("b", DataType::Int32, false),
3160 Field::new("c", DataType::Int32, false),
3161 ]);
3162 let input = Arc::new(StatisticsExec::new(
3163 Statistics {
3164 num_rows: Precision::Inexact(100),
3165 total_byte_size: Precision::Inexact(1200),
3166 column_statistics: vec![
3167 ColumnStatistics {
3168 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
3169 max_value: Precision::Inexact(ScalarValue::Int32(Some(100))),
3170 null_count: Precision::Inexact(80),
3171 distinct_count: Precision::Inexact(80),
3172 ..Default::default()
3173 },
3174 ColumnStatistics {
3175 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
3176 max_value: Precision::Inexact(ScalarValue::Int32(Some(50))),
3177 distinct_count: Precision::Inexact(40),
3178 ..Default::default()
3179 },
3180 ColumnStatistics {
3181 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
3182 max_value: Precision::Inexact(ScalarValue::Int32(Some(200))),
3183 null_count: Precision::Inexact(90),
3184 distinct_count: Precision::Inexact(150),
3185 ..Default::default()
3186 },
3187 ],
3188 },
3189 schema.clone(),
3190 ));
3191
3192 let predicate = Arc::new(BinaryExpr::new(
3194 Arc::new(BinaryExpr::new(
3195 Arc::new(BinaryExpr::new(
3196 Arc::new(Column::new("a", 0)),
3197 Operator::Eq,
3198 Arc::new(Literal::new(ScalarValue::Int32(Some(42)))),
3199 )),
3200 Operator::And,
3201 Arc::new(BinaryExpr::new(
3202 Arc::new(Column::new("b", 1)),
3203 Operator::Gt,
3204 Arc::new(Literal::new(ScalarValue::Int32(Some(10)))),
3205 )),
3206 )),
3207 Operator::And,
3208 Arc::new(BinaryExpr::new(
3209 Arc::new(Column::new("c", 2)),
3210 Operator::Eq,
3211 Arc::new(Literal::new(ScalarValue::Int32(Some(7)))),
3212 )),
3213 ));
3214 let filter: Arc<dyn ExecutionPlan> =
3215 Arc::new(FilterExec::try_new(predicate, input)?);
3216 let statistics =
3217 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
3218 assert_eq!(
3220 statistics.column_statistics[0].distinct_count,
3221 Precision::Exact(1)
3222 );
3223 assert_eq!(
3224 statistics.column_statistics[0].null_count,
3225 Precision::Exact(0)
3226 );
3227 assert_eq!(
3231 statistics.column_statistics[1].distinct_count,
3232 Precision::Inexact(1)
3233 );
3234 assert_eq!(
3235 statistics.column_statistics[2].distinct_count,
3236 Precision::Exact(1)
3237 );
3238 assert_eq!(
3239 statistics.column_statistics[2].null_count,
3240 Precision::Exact(0)
3241 );
3242 Ok(())
3243 }
3244
3245 #[tokio::test]
3246 async fn test_filter_statistics_equality_absent_bounds_ndv() -> Result<()> {
3247 let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
3249 let input = Arc::new(StatisticsExec::new(
3250 Statistics {
3251 num_rows: Precision::Inexact(100),
3252 total_byte_size: Precision::Inexact(400),
3253 column_statistics: vec![ColumnStatistics {
3254 distinct_count: Precision::Inexact(80),
3255 ..Default::default()
3256 }],
3257 },
3258 schema.clone(),
3259 ));
3260
3261 let predicate = Arc::new(BinaryExpr::new(
3264 Arc::new(Column::new("a", 0)),
3265 Operator::Eq,
3266 Arc::new(Literal::new(ScalarValue::Int32(Some(42)))),
3267 ));
3268 let filter: Arc<dyn ExecutionPlan> =
3269 Arc::new(FilterExec::try_new(predicate, input)?);
3270 let statistics =
3271 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
3272 assert_eq!(
3273 statistics.column_statistics[0].distinct_count,
3274 Precision::Exact(1)
3275 );
3276 Ok(())
3277 }
3278
3279 #[tokio::test]
3280 async fn test_filter_statistics_equality_int8_ndv() -> Result<()> {
3281 let schema = Schema::new(vec![Field::new("a", DataType::Int8, false)]);
3283 let input = Arc::new(StatisticsExec::new(
3284 Statistics {
3285 num_rows: Precision::Inexact(100),
3286 total_byte_size: Precision::Inexact(100),
3287 column_statistics: vec![ColumnStatistics {
3288 min_value: Precision::Inexact(ScalarValue::Int8(Some(-100))),
3289 max_value: Precision::Inexact(ScalarValue::Int8(Some(100))),
3290 distinct_count: Precision::Inexact(50),
3291 ..Default::default()
3292 }],
3293 },
3294 schema.clone(),
3295 ));
3296
3297 let predicate = Arc::new(BinaryExpr::new(
3298 Arc::new(Column::new("a", 0)),
3299 Operator::Eq,
3300 Arc::new(Literal::new(ScalarValue::Int8(Some(42)))),
3301 ));
3302 let filter: Arc<dyn ExecutionPlan> =
3303 Arc::new(FilterExec::try_new(predicate, input)?);
3304 let statistics =
3305 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
3306 assert_eq!(
3307 statistics.column_statistics[0].distinct_count,
3308 Precision::Exact(1)
3309 );
3310 Ok(())
3311 }
3312
3313 #[tokio::test]
3314 async fn test_filter_statistics_equality_int64_ndv() -> Result<()> {
3315 let schema = Schema::new(vec![Field::new("a", DataType::Int64, false)]);
3317 let input = Arc::new(StatisticsExec::new(
3318 Statistics {
3319 num_rows: Precision::Inexact(100_000),
3320 total_byte_size: Precision::Inexact(800_000),
3321 column_statistics: vec![ColumnStatistics {
3322 min_value: Precision::Inexact(ScalarValue::Int64(Some(0))),
3323 max_value: Precision::Inexact(ScalarValue::Int64(Some(1_000_000))),
3324 distinct_count: Precision::Inexact(100_000),
3325 ..Default::default()
3326 }],
3327 },
3328 schema.clone(),
3329 ));
3330
3331 let predicate = Arc::new(BinaryExpr::new(
3332 Arc::new(Column::new("a", 0)),
3333 Operator::Eq,
3334 Arc::new(Literal::new(ScalarValue::Int64(Some(42)))),
3335 ));
3336 let filter: Arc<dyn ExecutionPlan> =
3337 Arc::new(FilterExec::try_new(predicate, input)?);
3338 let statistics =
3339 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
3340 assert_eq!(
3341 statistics.column_statistics[0].distinct_count,
3342 Precision::Exact(1)
3343 );
3344 Ok(())
3345 }
3346
3347 #[tokio::test]
3348 async fn test_filter_statistics_equality_float32_ndv() -> Result<()> {
3349 let schema = Schema::new(vec![Field::new("a", DataType::Float32, false)]);
3351 let input = Arc::new(StatisticsExec::new(
3352 Statistics {
3353 num_rows: Precision::Inexact(100),
3354 total_byte_size: Precision::Inexact(400),
3355 column_statistics: vec![ColumnStatistics {
3356 min_value: Precision::Inexact(ScalarValue::Float32(Some(0.0))),
3357 max_value: Precision::Inexact(ScalarValue::Float32(Some(100.0))),
3358 distinct_count: Precision::Inexact(50),
3359 ..Default::default()
3360 }],
3361 },
3362 schema.clone(),
3363 ));
3364
3365 let predicate = Arc::new(BinaryExpr::new(
3366 Arc::new(Column::new("a", 0)),
3367 Operator::Eq,
3368 Arc::new(Literal::new(ScalarValue::Float32(Some(42.5)))),
3369 ));
3370 let filter: Arc<dyn ExecutionPlan> =
3371 Arc::new(FilterExec::try_new(predicate, input)?);
3372 let statistics =
3373 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
3374 assert_eq!(
3375 statistics.column_statistics[0].distinct_count,
3376 Precision::Exact(1)
3377 );
3378 Ok(())
3379 }
3380
3381 #[tokio::test]
3382 async fn test_filter_statistics_equality_reversed_ndv() -> Result<()> {
3383 let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
3385 let input = Arc::new(StatisticsExec::new(
3386 Statistics {
3387 num_rows: Precision::Inexact(100),
3388 total_byte_size: Precision::Inexact(400),
3389 column_statistics: vec![ColumnStatistics {
3390 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
3391 max_value: Precision::Inexact(ScalarValue::Int32(Some(100))),
3392 distinct_count: Precision::Inexact(80),
3393 ..Default::default()
3394 }],
3395 },
3396 schema.clone(),
3397 ));
3398
3399 let predicate = Arc::new(BinaryExpr::new(
3401 Arc::new(Literal::new(ScalarValue::Int32(Some(42)))),
3402 Operator::Eq,
3403 Arc::new(Column::new("a", 0)),
3404 ));
3405 let filter: Arc<dyn ExecutionPlan> =
3406 Arc::new(FilterExec::try_new(predicate, input)?);
3407 let statistics =
3408 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
3409 assert_eq!(
3410 statistics.column_statistics[0].distinct_count,
3411 Precision::Exact(1)
3412 );
3413 Ok(())
3414 }
3415
3416 #[tokio::test]
3417 async fn test_filter_statistics_equality_timestamp_ndv() -> Result<()> {
3418 let schema = Schema::new(vec![Field::new(
3420 "ts",
3421 DataType::Timestamp(arrow::datatypes::TimeUnit::Nanosecond, None),
3422 false,
3423 )]);
3424 let input = Arc::new(StatisticsExec::new(
3425 Statistics {
3426 num_rows: Precision::Inexact(1000),
3427 total_byte_size: Precision::Inexact(8000),
3428 column_statistics: vec![ColumnStatistics {
3429 min_value: Precision::Inexact(ScalarValue::TimestampNanosecond(
3430 Some(1_000_000_000),
3431 None,
3432 )),
3433 max_value: Precision::Inexact(ScalarValue::TimestampNanosecond(
3434 Some(2_000_000_000),
3435 None,
3436 )),
3437 distinct_count: Precision::Inexact(500),
3438 ..Default::default()
3439 }],
3440 },
3441 schema.clone(),
3442 ));
3443
3444 let predicate = Arc::new(BinaryExpr::new(
3445 Arc::new(Column::new("ts", 0)),
3446 Operator::Eq,
3447 Arc::new(Literal::new(ScalarValue::TimestampNanosecond(
3448 Some(1_500_000_000),
3449 None,
3450 ))),
3451 ));
3452 let filter: Arc<dyn ExecutionPlan> =
3453 Arc::new(FilterExec::try_new(predicate, input)?);
3454 let statistics =
3455 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
3456 assert_eq!(
3457 statistics.column_statistics[0].distinct_count,
3458 Precision::Exact(1)
3459 );
3460 Ok(())
3461 }
3462
3463 #[test]
3464 fn test_collect_equality_columns() {
3465 use std::collections::HashSet;
3466 #[expect(clippy::type_complexity)]
3468 let cases: Vec<(&str, Arc<dyn PhysicalExpr>, Vec<usize>, bool)> = vec![
3469 (
3470 "simple col = literal",
3471 Arc::new(BinaryExpr::new(
3472 Arc::new(Column::new("a", 0)),
3473 Operator::Eq,
3474 Arc::new(Literal::new(ScalarValue::Int32(Some(42)))),
3475 )),
3476 vec![0],
3477 false,
3478 ),
3479 (
3480 "reversed literal = col",
3481 Arc::new(BinaryExpr::new(
3482 Arc::new(Literal::new(ScalarValue::Int32(Some(42)))),
3483 Operator::Eq,
3484 Arc::new(Column::new("a", 0)),
3485 )),
3486 vec![0],
3487 false,
3488 ),
3489 (
3490 "AND with two equalities",
3491 Arc::new(BinaryExpr::new(
3492 Arc::new(BinaryExpr::new(
3493 Arc::new(Column::new("a", 0)),
3494 Operator::Eq,
3495 Arc::new(Literal::new(ScalarValue::Int32(Some(42)))),
3496 )),
3497 Operator::And,
3498 Arc::new(BinaryExpr::new(
3499 Arc::new(Column::new("b", 1)),
3500 Operator::Eq,
3501 Arc::new(Literal::new(ScalarValue::Utf8(Some(
3502 "hello".to_string(),
3503 )))),
3504 )),
3505 )),
3506 vec![0, 1],
3507 false,
3508 ),
3509 (
3510 "OR produces empty set",
3511 Arc::new(BinaryExpr::new(
3512 Arc::new(BinaryExpr::new(
3513 Arc::new(Column::new("a", 0)),
3514 Operator::Eq,
3515 Arc::new(Literal::new(ScalarValue::Int32(Some(42)))),
3516 )),
3517 Operator::Or,
3518 Arc::new(BinaryExpr::new(
3519 Arc::new(Column::new("a", 0)),
3520 Operator::Eq,
3521 Arc::new(Literal::new(ScalarValue::Int32(Some(99)))),
3522 )),
3523 )),
3524 vec![],
3525 false,
3526 ),
3527 (
3528 "greater-than produces empty set",
3529 Arc::new(BinaryExpr::new(
3530 Arc::new(Column::new("a", 0)),
3531 Operator::Gt,
3532 Arc::new(Literal::new(ScalarValue::Int32(Some(42)))),
3533 )),
3534 vec![],
3535 false,
3536 ),
3537 (
3538 "col = col produces empty set",
3539 Arc::new(BinaryExpr::new(
3540 Arc::new(Column::new("a", 0)),
3541 Operator::Eq,
3542 Arc::new(Column::new("b", 1)),
3543 )),
3544 vec![],
3545 false,
3546 ),
3547 (
3548 "nested AND with three equalities",
3549 Arc::new(BinaryExpr::new(
3550 Arc::new(BinaryExpr::new(
3551 Arc::new(BinaryExpr::new(
3552 Arc::new(Column::new("a", 0)),
3553 Operator::Eq,
3554 Arc::new(Literal::new(ScalarValue::Int32(Some(1)))),
3555 )),
3556 Operator::And,
3557 Arc::new(BinaryExpr::new(
3558 Arc::new(Column::new("b", 1)),
3559 Operator::Eq,
3560 Arc::new(Literal::new(ScalarValue::Int32(Some(2)))),
3561 )),
3562 )),
3563 Operator::And,
3564 Arc::new(BinaryExpr::new(
3565 Arc::new(Column::new("c", 2)),
3566 Operator::Eq,
3567 Arc::new(Literal::new(ScalarValue::Int32(Some(3)))),
3568 )),
3569 )),
3570 vec![0, 1, 2],
3571 false,
3572 ),
3573 (
3574 "AND with mixed equality and non-equality",
3575 Arc::new(BinaryExpr::new(
3576 Arc::new(BinaryExpr::new(
3577 Arc::new(Column::new("a", 0)),
3578 Operator::Eq,
3579 Arc::new(Literal::new(ScalarValue::Int32(Some(42)))),
3580 )),
3581 Operator::And,
3582 Arc::new(BinaryExpr::new(
3583 Arc::new(Column::new("b", 1)),
3584 Operator::Gt,
3585 Arc::new(Literal::new(ScalarValue::Int32(Some(10)))),
3586 )),
3587 )),
3588 vec![0],
3589 false,
3590 ),
3591 (
3592 "col = NULL is excluded",
3593 Arc::new(BinaryExpr::new(
3594 Arc::new(Column::new("a", 0)),
3595 Operator::Eq,
3596 Arc::new(Literal::new(ScalarValue::Int32(None))),
3597 )),
3598 vec![],
3599 false,
3600 ),
3601 (
3602 "NULL = col is excluded",
3603 Arc::new(BinaryExpr::new(
3604 Arc::new(Literal::new(ScalarValue::Utf8(None))),
3605 Operator::Eq,
3606 Arc::new(Column::new("a", 0)),
3607 )),
3608 vec![],
3609 false,
3610 ),
3611 (
3612 "contradictory: same col, different literals",
3613 Arc::new(BinaryExpr::new(
3614 Arc::new(BinaryExpr::new(
3615 Arc::new(Column::new("a", 0)),
3616 Operator::Eq,
3617 Arc::new(Literal::new(ScalarValue::Utf8(Some(
3618 "alice".to_string(),
3619 )))),
3620 )),
3621 Operator::And,
3622 Arc::new(BinaryExpr::new(
3623 Arc::new(Column::new("a", 0)),
3624 Operator::Eq,
3625 Arc::new(Literal::new(ScalarValue::Utf8(Some(
3626 "bob".to_string(),
3627 )))),
3628 )),
3629 )),
3630 vec![0],
3631 true,
3632 ),
3633 (
3634 "same col, same literal is not contradictory",
3635 Arc::new(BinaryExpr::new(
3636 Arc::new(BinaryExpr::new(
3637 Arc::new(Column::new("a", 0)),
3638 Operator::Eq,
3639 Arc::new(Literal::new(ScalarValue::Int32(Some(42)))),
3640 )),
3641 Operator::And,
3642 Arc::new(BinaryExpr::new(
3643 Arc::new(Column::new("a", 0)),
3644 Operator::Eq,
3645 Arc::new(Literal::new(ScalarValue::Int32(Some(42)))),
3646 )),
3647 )),
3648 vec![0],
3649 false,
3650 ),
3651 ];
3652
3653 for (desc, expr, expected_cols, expected_infeasible) in cases {
3654 let (result, infeasible) = collect_equality_columns(&expr);
3655 let expected: HashSet<usize> = expected_cols.into_iter().collect();
3656 if expected_infeasible {
3657 assert!(infeasible, "case '{desc}': expected infeasible");
3661 } else {
3662 assert_eq!(result, expected, "case '{desc}': columns mismatch");
3663 assert!(!infeasible, "case '{desc}': expected feasible");
3664 }
3665 }
3666 }
3667
3668 #[test]
3678 fn test_filter_with_projection_swap_does_not_panic() -> Result<()> {
3679 use crate::projection::ProjectionExpr;
3680 use datafusion_physical_expr::expressions::col;
3681
3682 let schema = Arc::new(Schema::new(vec![
3684 Field::new("ts", DataType::Int64, false),
3685 Field::new("tokens", DataType::Int64, false),
3686 Field::new("svc", DataType::Utf8, false),
3687 ]));
3688 let input = Arc::new(EmptyExec::new(Arc::clone(&schema)));
3689
3690 let predicate = Arc::new(BinaryExpr::new(
3692 Arc::new(Column::new("ts", 0)),
3693 Operator::Gt,
3694 Arc::new(Literal::new(ScalarValue::Int64(Some(0)))),
3695 ));
3696 let filter = Arc::new(
3697 FilterExecBuilder::new(predicate, input)
3698 .apply_projection(Some(vec![0, 1, 2]))?
3699 .build()?,
3700 );
3701
3702 let proj_exprs = vec![
3704 ProjectionExpr {
3705 expr: col("ts", &filter.schema())?,
3706 alias: "ts".to_string(),
3707 },
3708 ProjectionExpr {
3709 expr: col("tokens", &filter.schema())?,
3710 alias: "tokens".to_string(),
3711 },
3712 ];
3713 let projection = Arc::new(ProjectionExec::try_new(
3714 proj_exprs,
3715 Arc::clone(&filter) as _,
3716 )?);
3717
3718 let result = filter.try_swapping_with_projection(&projection)?;
3720 assert!(result.is_some(), "swap should succeed");
3721
3722 let new_plan = result.unwrap();
3723 let out_schema = new_plan.schema();
3725 assert_eq!(out_schema.fields().len(), 2);
3726 assert_eq!(out_schema.field(0).name(), "ts");
3727 assert_eq!(out_schema.field(1).name(), "tokens");
3728 Ok(())
3729 }
3730
3731 #[tokio::test]
3732 async fn test_filter_statistics_ndv_capped_at_row_count() -> Result<()> {
3733 let schema = Schema::new(vec![Field::new("a", DataType::Int32, true)]);
3734 let input = Arc::new(StatisticsExec::new(
3735 Statistics {
3736 num_rows: Precision::Inexact(100),
3737 total_byte_size: Precision::Inexact(1000),
3738 column_statistics: vec![ColumnStatistics {
3739 min_value: Precision::Inexact(ScalarValue::Int32(Some(1))),
3740 max_value: Precision::Inexact(ScalarValue::Int32(Some(100))),
3741 null_count: Precision::Inexact(80),
3742 distinct_count: Precision::Inexact(80),
3743 byte_size: Precision::Exact(1000),
3744 ..Default::default()
3745 }],
3746 },
3747 schema.clone(),
3748 ));
3749
3750 let predicate: Arc<dyn PhysicalExpr> =
3752 binary(col("a", &schema)?, Operator::LtEq, lit(10i32), &schema)?;
3753
3754 let filter: Arc<dyn ExecutionPlan> =
3755 Arc::new(FilterExec::try_new(predicate, input)?);
3756
3757 let statistics =
3758 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
3759 assert_eq!(statistics.num_rows, Precision::Inexact(10));
3761 let ndv = &statistics.column_statistics[0].distinct_count;
3762 assert!(
3763 ndv.get_value().copied() <= Some(10),
3764 "Expected NDV <= 10 (filtered row count), got {ndv:?}"
3765 );
3766 assert_eq!(
3768 statistics.column_statistics[0].null_count,
3769 Precision::Exact(0)
3770 );
3771 assert_eq!(
3773 statistics.column_statistics[0].byte_size,
3774 Precision::Inexact(100)
3775 );
3776 Ok(())
3777 }
3778
3779 #[tokio::test]
3780 async fn test_filter_statistics_default_selectivity_column_stats() -> Result<()> {
3781 let schema = Schema::new(vec![Field::new("name", DataType::Utf8, true)]);
3782 let input = Arc::new(StatisticsExec::new(
3783 Statistics {
3784 num_rows: Precision::Inexact(100),
3785 total_byte_size: Precision::Inexact(1000),
3786 column_statistics: vec![ColumnStatistics {
3787 null_count: Precision::Inexact(80),
3788 distinct_count: Precision::Inexact(60),
3789 byte_size: Precision::Exact(1000),
3790 ..Default::default()
3791 }],
3792 },
3793 schema.clone(),
3794 ));
3795
3796 let predicate: Arc<dyn PhysicalExpr> =
3800 binary(col("name", &schema)?, Operator::Gt, lit("m"), &schema)?;
3801 let filter: Arc<dyn ExecutionPlan> =
3802 Arc::new(FilterExec::try_new(predicate, input)?);
3803
3804 let statistics =
3805 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
3806 assert_eq!(statistics.num_rows, Precision::Inexact(20));
3807 assert_eq!(
3808 statistics.column_statistics[0].null_count,
3809 Precision::Exact(0)
3810 );
3811 assert_eq!(
3812 statistics.column_statistics[0].byte_size,
3813 Precision::Inexact(200)
3814 );
3815 assert_eq!(
3816 statistics.column_statistics[0].distinct_count,
3817 Precision::Inexact(20)
3818 );
3819 Ok(())
3820 }
3821
3822 #[tokio::test]
3823 async fn test_filter_statistics_or_does_not_reject_nulls() -> Result<()> {
3824 let schema = Schema::new(vec![Field::new("name", DataType::Utf8, true)]);
3825 let input = Arc::new(StatisticsExec::new(
3826 Statistics {
3827 num_rows: Precision::Inexact(100),
3828 total_byte_size: Precision::Inexact(1000),
3829 column_statistics: vec![ColumnStatistics {
3830 null_count: Precision::Inexact(80),
3831 distinct_count: Precision::Inexact(60),
3832 byte_size: Precision::Exact(1000),
3833 ..Default::default()
3834 }],
3835 },
3836 schema.clone(),
3837 ));
3838
3839 let predicate: Arc<dyn PhysicalExpr> = binary(
3840 binary(col("name", &schema)?, Operator::Gt, lit("m"), &schema)?,
3841 Operator::Or,
3842 is_null(col("name", &schema)?)?,
3843 &schema,
3844 )?;
3845 let filter: Arc<dyn ExecutionPlan> =
3846 Arc::new(FilterExec::try_new(predicate, input)?);
3847
3848 let statistics =
3849 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
3850 assert_eq!(statistics.num_rows, Precision::Inexact(20));
3851 assert_eq!(
3852 statistics.column_statistics[0].null_count,
3853 Precision::Inexact(20)
3854 );
3855 assert_eq!(
3856 statistics.column_statistics[0].byte_size,
3857 Precision::Inexact(200)
3858 );
3859 assert_eq!(
3860 statistics.column_statistics[0].distinct_count,
3861 Precision::Inexact(20)
3862 );
3863 Ok(())
3864 }
3865
3866 #[tokio::test]
3867 async fn test_filter_statistics_is_not_null_rejects_nulls() -> Result<()> {
3868 let schema = Schema::new(vec![Field::new("name", DataType::Utf8, true)]);
3869 let input = Arc::new(StatisticsExec::new(
3870 Statistics {
3871 num_rows: Precision::Inexact(100),
3872 total_byte_size: Precision::Inexact(1000),
3873 column_statistics: vec![ColumnStatistics {
3874 null_count: Precision::Inexact(80),
3875 distinct_count: Precision::Inexact(60),
3876 byte_size: Precision::Exact(1000),
3877 ..Default::default()
3878 }],
3879 },
3880 schema.clone(),
3881 ));
3882
3883 let predicate: Arc<dyn PhysicalExpr> = is_not_null(col("name", &schema)?)?;
3887 let filter: Arc<dyn ExecutionPlan> =
3888 Arc::new(FilterExec::try_new(predicate, input)?);
3889
3890 let statistics =
3891 StatisticsContext::new().compute(filter.as_ref(), &StatisticsArgs::new())?;
3892 assert_eq!(statistics.num_rows, Precision::Inexact(20));
3893 assert_eq!(
3894 statistics.column_statistics[0].null_count,
3895 Precision::Exact(0)
3896 );
3897 assert_eq!(
3898 statistics.column_statistics[0].byte_size,
3899 Precision::Inexact(200)
3900 );
3901 assert_eq!(
3902 statistics.column_statistics[0].distinct_count,
3903 Precision::Inexact(20)
3904 );
3905 Ok(())
3906 }
3907}