1use super::expressions::Column;
24use super::metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet};
25use super::{
26 DisplayAs, ExecutionPlanProperties, PlanProperties, RecordBatchStream,
27 SendableRecordBatchStream, SortOrderPushdownResult, Statistics,
28};
29use crate::column_rewriter::PhysicalColumnRewriter;
30use crate::execution_plan::{CardinalityEffect, replace_children_if_necessary};
31use crate::filter_pushdown::{
32 ChildFilterDescription, ChildPushdownResult, FilterDescription, FilterPushdownPhase,
33 FilterPushdownPropagation, FilterRemapper, PushedDownPredicate,
34};
35use crate::joins::utils::{ColumnIndex, JoinFilter, JoinOn, JoinOnRef};
36use crate::statistics::{ChildStats, StatisticsArgs};
37use crate::{
38 ChildrenPropertiesMode, DisplayFormatType, ExecutionPlan, PhysicalExpr,
39 ReplaceChildrenOptions, validate_child_count,
40};
41use std::collections::HashMap;
42use std::pin::Pin;
43use std::sync::Arc;
44use std::task::{Context, Poll};
45
46use arrow::datatypes::{Schema, SchemaRef};
47use arrow::record_batch::RecordBatch;
48use datafusion_common::config::ConfigOptions;
49use datafusion_common::tree_node::{
50 Transformed, TransformedResult, TreeNode, TreeNodeRecursion,
51};
52use datafusion_common::{DataFusionError, JoinSide, Result, internal_err, plan_err};
53use datafusion_execution::TaskContext;
54use datafusion_expr::ExpressionPlacement;
55use datafusion_physical_expr::equivalence::ProjectionMapping;
56use datafusion_physical_expr::projection::Projector;
57use datafusion_physical_expr_common::physical_expr::{PhysicalExprRef, fmt_sql};
58use datafusion_physical_expr_common::sort_expr::{
59 LexOrdering, LexRequirement, PhysicalSortExpr,
60};
61pub use datafusion_physical_expr::projection::{
64 ProjectionExpr, ProjectionExprs, update_expr,
65};
66
67use futures::stream::{Stream, StreamExt};
68use log::trace;
69
70#[derive(Debug, Clone)]
75pub struct ProjectionExec {
76 projector: Projector,
79 input: Arc<dyn ExecutionPlan>,
81 metrics: ExecutionPlanMetricsSet,
83 cache: Arc<PlanProperties>,
85}
86
87impl ProjectionExec {
88 pub fn try_new<I, E>(expr: I, input: Arc<dyn ExecutionPlan>) -> Result<Self>
138 where
139 I: IntoIterator<Item = E>,
140 E: Into<ProjectionExpr>,
141 {
142 let input_schema = input.schema();
143 let expr_arc = expr.into_iter().map(Into::into).collect::<Arc<_>>();
144 let projection = ProjectionExprs::from_expressions(expr_arc);
145 let projector = projection.make_projector(&input_schema)?;
146 Self::try_from_projector(projector, input)
147 }
148
149 pub fn try_new_with_schema_metadata<I, E>(
161 expr: I,
162 input: Arc<dyn ExecutionPlan>,
163 projected_schema: &Schema,
164 ) -> Result<Self>
165 where
166 I: IntoIterator<Item = E>,
167 E: Into<ProjectionExpr>,
168 {
169 let input_schema = input.schema();
170 let expr_arc = expr.into_iter().map(Into::into).collect::<Arc<_>>();
171 let projection = ProjectionExprs::from_expressions(expr_arc);
172 let projector = projection
173 .make_projector_with_schema_metadata(&input_schema, projected_schema)?;
174 Self::try_from_projector(projector, input)
175 }
176
177 fn try_from_projector(
178 projector: Projector,
179 input: Arc<dyn ExecutionPlan>,
180 ) -> Result<Self> {
181 let projection_mapping =
183 projector.projection().projection_mapping(&input.schema())?;
184 let cache = Self::compute_properties(
185 &input,
186 &projection_mapping,
187 Arc::clone(projector.output_schema()),
188 )?;
189 Ok(Self {
190 projector,
191 input,
192 metrics: ExecutionPlanMetricsSet::new(),
193 cache: Arc::new(cache),
194 })
195 }
196
197 pub fn expr(&self) -> &[ProjectionExpr] {
199 self.projector.projection().as_ref()
200 }
201
202 pub fn projection_expr(&self) -> &ProjectionExprs {
204 self.projector.projection()
205 }
206
207 pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
209 &self.input
210 }
211
212 fn compute_properties(
214 input: &Arc<dyn ExecutionPlan>,
215 projection_mapping: &ProjectionMapping,
216 schema: SchemaRef,
217 ) -> Result<PlanProperties> {
218 let input_eq_properties = input.equivalence_properties();
220 let eq_properties = input_eq_properties.project(projection_mapping, schema);
221 let output_partitioning = input
223 .output_partitioning()
224 .project(projection_mapping, input_eq_properties);
225
226 Ok(PlanProperties::new(
227 eq_properties,
228 output_partitioning,
229 input.pipeline_behavior(),
230 input.boundedness(),
231 ))
232 }
233
234 fn collect_reverse_alias(
237 &self,
238 ) -> Result<datafusion_common::HashMap<Column, Arc<dyn PhysicalExpr>>> {
239 let mut alias_map = datafusion_common::HashMap::new();
240 for projection in self.projection_expr().iter() {
241 let (aliased_index, _output_field) = self
242 .projector
243 .output_schema()
244 .column_with_name(&projection.alias)
245 .ok_or_else(|| {
246 DataFusionError::Internal(format!(
247 "Expr {} with alias {} not found in output schema",
248 projection.expr, projection.alias
249 ))
250 })?;
251 let aliased_col = Column::new(&projection.alias, aliased_index);
252 alias_map.insert(aliased_col, Arc::clone(&projection.expr));
253 }
254 Ok(alias_map)
255 }
256}
257
258impl DisplayAs for ProjectionExec {
259 fn fmt_as(
260 &self,
261 t: DisplayFormatType,
262 f: &mut std::fmt::Formatter,
263 ) -> std::fmt::Result {
264 match t {
265 DisplayFormatType::Default | DisplayFormatType::Verbose => {
266 let expr: Vec<String> = self
267 .projector
268 .projection()
269 .as_ref()
270 .iter()
271 .map(|proj_expr| {
272 let e = proj_expr.expr.to_string();
273 if e != proj_expr.alias {
274 format!("{e} as {}", proj_expr.alias)
275 } else {
276 e
277 }
278 })
279 .collect();
280
281 write!(f, "ProjectionExec: expr=[{}]", expr.join(", "))
282 }
283 DisplayFormatType::TreeRender => {
284 for (i, proj_expr) in self.expr().iter().enumerate() {
285 let expr_sql = fmt_sql(proj_expr.expr.as_ref());
286 if proj_expr.expr.to_string() == proj_expr.alias {
287 writeln!(f, "expr{i}={expr_sql}")?;
288 } else {
289 writeln!(f, "{}={expr_sql}", proj_expr.alias)?;
290 }
291 }
292
293 Ok(())
294 }
295 }
296 }
297}
298
299impl ExecutionPlan for ProjectionExec {
300 fn name(&self) -> &'static str {
301 "ProjectionExec"
302 }
303
304 fn properties(&self) -> &Arc<PlanProperties> {
306 &self.cache
307 }
308
309 fn maintains_input_order(&self) -> Vec<bool> {
310 vec![true]
312 }
313
314 fn benefits_from_input_partitioning(&self) -> Vec<bool> {
315 let all_simple_exprs =
316 self.projector
317 .projection()
318 .as_ref()
319 .iter()
320 .all(|proj_expr| {
321 !matches!(
322 proj_expr.expr.placement(),
323 ExpressionPlacement::KeepInPlace
324 )
325 });
326 vec![!all_simple_exprs]
330 }
331
332 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
333 vec![&self.input]
334 }
335
336 fn apply_expressions(
337 &self,
338 f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
339 ) -> Result<TreeNodeRecursion> {
340 crate::apply_expression_roots(self.projector.projection().as_ref().iter(), f)
341 }
342
343 fn replace_children(
344 self: Arc<Self>,
345 mut children: Vec<Arc<dyn ExecutionPlan>>,
346 options: ReplaceChildrenOptions,
347 ) -> Result<Arc<dyn ExecutionPlan>> {
348 validate_child_count!(self, children);
349 match options.children_properties {
350 ChildrenPropertiesMode::Keep => Ok(Arc::new(Self {
351 input: children.swap_remove(0),
352 metrics: ExecutionPlanMetricsSet::new(),
353 ..Self::clone(&*self)
354 })),
355 ChildrenPropertiesMode::Recompute => ProjectionExec::try_from_projector(
356 self.projector.clone(),
357 children.swap_remove(0),
358 )
359 .map(|p| Arc::new(p) as _),
360 }
361 }
362
363 fn with_new_children(
364 self: Arc<Self>,
365 children: Vec<Arc<dyn ExecutionPlan>>,
366 ) -> Result<Arc<dyn ExecutionPlan>> {
367 self.replace_children(
368 children,
369 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
370 )
371 }
372
373 fn with_new_children_and_same_properties(
374 self: Arc<Self>,
375 children: Vec<Arc<dyn ExecutionPlan>>,
376 ) -> Result<Arc<dyn ExecutionPlan>> {
377 self.replace_children(
378 children,
379 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Keep),
380 )
381 }
382
383 fn execute(
384 &self,
385 partition: usize,
386 context: Arc<TaskContext>,
387 ) -> Result<SendableRecordBatchStream> {
388 trace!(
389 "Start ProjectionExec::execute for partition {} of context session_id {} and task_id {:?}",
390 partition,
391 context.session_id(),
392 context.task_id()
393 );
394
395 let projector = self.projector.with_metrics(&self.metrics, partition);
396 Ok(Box::pin(ProjectionStream::new(
397 projector,
398 self.input.execute(partition, context)?,
399 BaselineMetrics::new(&self.metrics, partition),
400 )?))
401 }
402
403 fn metrics(&self) -> Option<MetricsSet> {
404 Some(self.metrics.clone_inner())
405 }
406
407 fn child_stats_requests(&self, partition: Option<usize>) -> Vec<ChildStats> {
408 vec![ChildStats::At(partition)]
409 }
410
411 fn statistics_from_inputs(
412 &self,
413 input_stats: &[Arc<Statistics>],
414 _args: &StatisticsArgs,
415 ) -> Result<Arc<Statistics>> {
416 let input_stats = input_stats[0].as_ref().clone();
417 let output_schema = self.schema();
418 Ok(Arc::new(
419 self.projector
420 .projection()
421 .project_statistics(input_stats, &output_schema)?,
422 ))
423 }
424
425 fn supports_limit_pushdown(&self) -> bool {
426 true
427 }
428
429 fn cardinality_effect(&self) -> CardinalityEffect {
430 CardinalityEffect::Equal
431 }
432
433 fn try_swapping_with_projection(
434 &self,
435 projection: &ProjectionExec,
436 ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
437 match try_collapse_projection_chain(projection)? {
438 Some(plan) => Ok(Some(plan)),
439 None => Ok(Some(Arc::new(projection.clone()))),
440 }
441 }
442
443 fn gather_filters_for_pushdown(
444 &self,
445 _phase: FilterPushdownPhase,
446 parent_filters: Vec<Arc<dyn PhysicalExpr>>,
447 _config: &ConfigOptions,
448 ) -> Result<FilterDescription> {
449 let invert_alias_map = self.collect_reverse_alias()?;
451 let output_schema = self.schema();
452 let remapper = FilterRemapper::new(output_schema);
453 let mut child_parent_filters = Vec::with_capacity(parent_filters.len());
454
455 for filter in parent_filters {
456 if let Some(reassigned) = remapper.try_remap(&filter)? {
458 let mut rewriter = PhysicalColumnRewriter::new(&invert_alias_map);
460 let rewritten = reassigned.rewrite(&mut rewriter)?.data;
461 child_parent_filters.push(PushedDownPredicate::supported(rewritten));
462 } else {
463 child_parent_filters.push(PushedDownPredicate::unsupported(filter));
464 }
465 }
466
467 Ok(FilterDescription::new().with_child(ChildFilterDescription {
468 parent_filters: child_parent_filters,
469 self_filters: vec![],
470 }))
471 }
472
473 fn handle_child_pushdown_result(
474 &self,
475 _phase: FilterPushdownPhase,
476 child_pushdown_result: ChildPushdownResult,
477 _config: &ConfigOptions,
478 ) -> Result<FilterPushdownPropagation<Arc<dyn ExecutionPlan>>> {
479 Ok(FilterPushdownPropagation::if_all(child_pushdown_result))
480 }
481
482 fn try_pushdown_sort(
483 &self,
484 order: &[PhysicalSortExpr],
485 ) -> Result<SortOrderPushdownResult<Arc<dyn ExecutionPlan>>> {
486 let child = self.input();
487 let mut child_order = Vec::new();
488
489 for sort_expr in order {
491 let mut can_pushdown = true;
493 let transformed = Arc::clone(&sort_expr.expr).transform(|expr| {
494 if let Some(col) = expr.downcast_ref::<Column>() {
495 if col.index() >= self.expr().len() {
498 can_pushdown = false;
499 return Ok(Transformed::no(expr));
500 }
501
502 let proj_expr = &self.expr()[col.index()];
503
504 if let Some(child_col) = proj_expr.expr.downcast_ref::<Column>() {
508 Ok(Transformed::yes(Arc::new(child_col.clone()) as _))
510 } else {
511 can_pushdown = false;
513 Ok(Transformed::no(expr))
514 }
515 } else {
516 Ok(Transformed::no(expr))
517 }
518 })?;
519
520 if !can_pushdown {
521 return Ok(SortOrderPushdownResult::Unsupported);
522 }
523
524 child_order.push(PhysicalSortExpr {
525 expr: transformed.data,
526 options: sort_expr.options,
527 });
528 }
529
530 match child.try_pushdown_sort(&child_order)? {
532 SortOrderPushdownResult::Exact { inner } => {
533 let new_exec =
534 replace_children_if_necessary(Arc::new(self.clone()), vec![inner])?;
535 Ok(SortOrderPushdownResult::Exact { inner: new_exec })
536 }
537 SortOrderPushdownResult::Inexact { inner } => {
538 let new_exec =
539 replace_children_if_necessary(Arc::new(self.clone()), vec![inner])?;
540 Ok(SortOrderPushdownResult::Inexact { inner: new_exec })
541 }
542 SortOrderPushdownResult::Unsupported => {
543 Ok(SortOrderPushdownResult::Unsupported)
544 }
545 }
546 }
547
548 fn with_preserve_order(
549 &self,
550 preserve_order: bool,
551 ) -> Option<Arc<dyn ExecutionPlan>> {
552 self.input
553 .with_preserve_order(preserve_order)
554 .and_then(|new_input| {
555 replace_children_if_necessary(Arc::new(self.clone()), vec![new_input])
556 .ok()
557 })
558 }
559
560 #[cfg(feature = "proto")]
561 fn try_to_proto(
562 &self,
563 ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
564 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
565 use datafusion_proto_models::protobuf;
566 let input = ctx.encode_child(self.input())?;
567 let expr = ctx.encode_expressions(self.expr().iter().map(|p| &p.expr))?;
568 let expr_name = self.expr().iter().map(|p| p.alias.clone()).collect();
569 Ok(Some(protobuf::PhysicalPlanNode {
570 physical_plan_type: Some(
571 protobuf::physical_plan_node::PhysicalPlanType::Projection(Box::new(
572 protobuf::ProjectionExecNode {
573 input: Some(Box::new(input)),
574 expr,
575 expr_name,
576 },
577 )),
578 ),
579 }))
580 }
581}
582
583#[cfg(feature = "proto")]
584impl ProjectionExec {
585 pub fn try_from_proto(
596 node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
597 ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
598 ) -> Result<Arc<dyn ExecutionPlan>> {
599 use datafusion_proto_models::protobuf;
600 let projection = crate::expect_plan_variant!(
601 node,
602 protobuf::physical_plan_node::PhysicalPlanType::Projection,
603 "ProjectionExec",
604 );
605 let input = ctx.decode_required_child(
606 projection.input.as_deref(),
607 "ProjectionExec",
608 "input",
609 )?;
610 let input_schema = input.schema();
611 let exprs = projection
612 .expr
613 .iter()
614 .zip(projection.expr_name.iter())
615 .map(|(expr, name)| {
616 Ok(ProjectionExpr {
617 expr: ctx.decode_expr(expr, input_schema.as_ref())?,
618 alias: name.to_string(),
619 })
620 })
621 .collect::<Result<Vec<ProjectionExpr>>>()?;
622 Ok(Arc::new(ProjectionExec::try_new(exprs, input)?))
623 }
624}
625
626impl ProjectionStream {
627 fn new(
629 projector: Projector,
630 input: SendableRecordBatchStream,
631 baseline_metrics: BaselineMetrics,
632 ) -> Result<Self> {
633 Ok(Self {
634 projector,
635 input,
636 baseline_metrics,
637 })
638 }
639
640 fn batch_project(&self, batch: &RecordBatch) -> Result<RecordBatch> {
641 let _timer = self.baseline_metrics.elapsed_compute().timer();
643 self.projector.project_batch(batch)
644 }
645}
646
647struct ProjectionStream {
649 projector: Projector,
650 input: SendableRecordBatchStream,
651 baseline_metrics: BaselineMetrics,
652}
653
654impl Stream for ProjectionStream {
655 type Item = Result<RecordBatch>;
656
657 fn poll_next(
658 mut self: Pin<&mut Self>,
659 cx: &mut Context<'_>,
660 ) -> Poll<Option<Self::Item>> {
661 let poll = self.input.poll_next_unpin(cx).map(|x| match x {
662 Some(Ok(batch)) => Some(self.batch_project(&batch)),
663 other => other,
664 });
665
666 self.baseline_metrics.record_poll(poll)
667 }
668
669 fn size_hint(&self) -> (usize, Option<usize>) {
670 self.input.size_hint()
672 }
673}
674
675impl RecordBatchStream for ProjectionStream {
676 fn schema(&self) -> SchemaRef {
678 Arc::clone(self.projector.output_schema())
679 }
680}
681
682pub trait EmbeddedProjection: ExecutionPlan + Sized {
692 fn with_projection(&self, projection: Option<Vec<usize>>) -> Result<Self>;
693}
694
695pub fn try_embed_projection<Exec: EmbeddedProjection + 'static>(
698 projection: &ProjectionExec,
699 execution_plan: &Exec,
700) -> Result<Option<Arc<dyn ExecutionPlan>>> {
701 if projection.expr().is_empty() {
706 let new_execution_plan = Arc::new(execution_plan.with_projection(Some(vec![]))?);
707 return Ok(Some(new_execution_plan));
708 }
709
710 let projection_index = collect_column_indices(projection.expr());
712
713 if projection_index.is_empty() {
714 return Ok(None);
715 };
716
717 let columns_reduced = projection_index.len() < execution_plan.schema().fields().len();
718
719 let new_execution_plan =
720 Arc::new(execution_plan.with_projection(Some(projection_index.to_vec()))?);
721
722 let embed_project_exprs = projection_index
724 .iter()
725 .zip(new_execution_plan.schema().fields())
726 .map(|(index, field)| ProjectionExpr {
727 expr: Arc::new(Column::new(field.name(), *index)) as Arc<dyn PhysicalExpr>,
728 alias: field.name().to_owned(),
729 })
730 .collect::<Vec<_>>();
731
732 let mut new_projection_exprs = Vec::with_capacity(projection.expr().len());
733
734 for proj_expr in projection.expr() {
735 let Some(expr) =
737 update_expr(&proj_expr.expr, embed_project_exprs.as_slice(), false)?
738 else {
739 return Ok(None);
740 };
741 new_projection_exprs.push(ProjectionExpr {
742 expr,
743 alias: proj_expr.alias.clone(),
744 });
745 }
746 let new_projection = Arc::new(ProjectionExec::try_new(
748 new_projection_exprs,
749 Arc::clone(&new_execution_plan) as _,
750 )?);
751 if is_projection_removable(&new_projection) {
752 Ok(Some(new_execution_plan))
754 } else if columns_reduced {
755 Ok(Some(new_projection))
758 } else {
759 Ok(None)
762 }
763}
764
765pub struct JoinData {
766 pub projected_left_child: ProjectionExec,
767 pub projected_right_child: ProjectionExec,
768 pub join_filter: Option<JoinFilter>,
769 pub join_on: JoinOn,
770}
771
772#[deprecated(
773 since = "55.0.0",
774 note = "Use try_pushdown_through_join_with_column_indices instead"
775)]
776pub fn try_pushdown_through_join(
777 projection: &ProjectionExec,
778 join_left: &Arc<dyn ExecutionPlan>,
779 join_right: &Arc<dyn ExecutionPlan>,
780 join_on: JoinOnRef,
781 schema: &SchemaRef,
782 filter: Option<&JoinFilter>,
783) -> Result<Option<JoinData>> {
784 let left_field_count = join_left.schema().fields().len();
785 let column_indices = schema
786 .fields()
787 .iter()
788 .enumerate()
789 .map(|(index, _)| {
790 if index < left_field_count {
791 ColumnIndex {
792 index,
793 side: JoinSide::Left,
794 }
795 } else {
796 ColumnIndex {
797 index: index - left_field_count,
798 side: JoinSide::Right,
799 }
800 }
801 })
802 .collect::<Vec<_>>();
803
804 try_pushdown_through_join_with_column_indices(
805 projection,
806 join_left,
807 join_right,
808 join_on,
809 schema,
810 filter,
811 &column_indices,
812 )
813}
814
815pub fn try_pushdown_through_join_with_column_indices(
834 projection: &ProjectionExec,
835 join_left: &Arc<dyn ExecutionPlan>,
836 join_right: &Arc<dyn ExecutionPlan>,
837 join_on: JoinOnRef,
838 schema: &SchemaRef,
839 filter: Option<&JoinFilter>,
840 column_indices: &[ColumnIndex],
841) -> Result<Option<JoinData>> {
842 if column_indices.len() != schema.fields().len() {
843 return plan_err!(
844 "Column index mapping has {} entries but join schema has {} fields",
845 column_indices.len(),
846 schema.fields().len()
847 );
848 }
849 for (output_index, column_index) in column_indices.iter().enumerate() {
852 let (side, child_field_count) = match column_index.side {
853 JoinSide::Left => ("left", join_left.schema().fields().len()),
854 JoinSide::Right => ("right", join_right.schema().fields().len()),
855 JoinSide::None => continue,
856 };
857 if column_index.index >= child_field_count {
858 return plan_err!(
859 "Join output column {output_index} maps to {side} child column {}, but the child has {child_field_count} fields",
860 column_index.index
861 );
862 }
863 }
864
865 let Some(projection_as_columns) = physical_to_column_exprs(projection.expr()) else {
867 return Ok(None);
868 };
869
870 if projection_as_columns.len() >= schema.fields().len() {
871 return Ok(None);
872 }
873 let mut left_proj: Vec<(Column, String)> = Vec::new();
874 let mut right_proj: Vec<(Column, String)> = Vec::new();
875 let mut seen_right = false;
876 for (col, alias) in &projection_as_columns {
877 let Some(origin) = column_indices.get(col.index()) else {
878 return plan_err!(
879 "Projection column {} is outside the {}-entry column index mapping",
880 col.index(),
881 column_indices.len()
882 );
883 };
884 match origin.side {
885 JoinSide::Left => {
888 if seen_right {
889 return Ok(None);
890 }
891 left_proj.push((Column::new(col.name(), origin.index), alias.clone()));
892 }
893 JoinSide::Right => {
894 seen_right = true;
895 right_proj.push((Column::new(col.name(), origin.index), alias.clone()));
896 }
897 JoinSide::None => return Ok(None),
900 }
901 }
902
903 if left_proj.is_empty() || right_proj.is_empty() {
905 return Ok(None);
906 }
907
908 let new_filter = if let Some(filter) = filter {
913 match update_join_filter(&left_proj, &right_proj, filter, 0) {
914 Some(updated) => Some(updated),
915 None => return Ok(None),
916 }
917 } else {
918 None
919 };
920
921 let Some(new_on) = update_join_on(&left_proj, &right_proj, join_on, 0) else {
922 return Ok(None);
923 };
924
925 let (new_left, new_right) =
926 new_join_children_from_groups(&left_proj, &right_proj, join_left, join_right)?;
927
928 Ok(Some(JoinData {
929 projected_left_child: new_left,
930 projected_right_child: new_right,
931 join_filter: new_filter,
932 join_on: new_on,
933 }))
934}
935
936pub fn remove_unnecessary_projections(
941 plan: Arc<dyn ExecutionPlan>,
942) -> Result<Transformed<Arc<dyn ExecutionPlan>>> {
943 let maybe_modified = if let Some(projection) = plan.downcast_ref::<ProjectionExec>() {
944 if is_projection_removable(projection) {
947 return Ok(Transformed::yes(Arc::clone(projection.input())));
948 }
949 projection
951 .input()
952 .try_swapping_with_projection(projection)?
953 } else {
954 return Ok(Transformed::no(plan));
955 };
956 Ok(maybe_modified.map_or_else(|| Transformed::no(plan), Transformed::yes))
957}
958
959fn is_projection_removable(projection: &ProjectionExec) -> bool {
964 let exprs = projection.expr();
965 exprs.iter().enumerate().all(|(idx, proj_expr)| {
966 let Some(col) = proj_expr.expr.downcast_ref::<Column>() else {
967 return false;
968 };
969 col.name() == proj_expr.alias && col.index() == idx
970 }) && exprs.len() == projection.input().schema().fields().len()
971}
972
973pub fn all_alias_free_columns(exprs: &[ProjectionExpr]) -> bool {
976 exprs.iter().all(|proj_expr| {
977 proj_expr
978 .expr
979 .downcast_ref::<Column>()
980 .map(|column| column.name() == proj_expr.alias)
981 .unwrap_or(false)
982 })
983}
984
985pub fn new_projections_for_columns(
989 projection: &[ProjectionExpr],
990 source: &[usize],
991) -> Vec<usize> {
992 projection
993 .iter()
994 .filter_map(|proj_expr| {
995 proj_expr
996 .expr
997 .downcast_ref::<Column>()
998 .map(|expr| source[expr.index()])
999 })
1000 .collect()
1001}
1002
1003pub fn make_with_child(
1006 projection: &ProjectionExec,
1007 child: &Arc<dyn ExecutionPlan>,
1008) -> Result<Arc<dyn ExecutionPlan>> {
1009 ProjectionExec::try_new(projection.expr().to_vec(), Arc::clone(child))
1010 .map(|e| Arc::new(e) as _)
1011}
1012
1013pub fn all_columns(exprs: &[ProjectionExpr]) -> bool {
1015 exprs.iter().all(|proj_expr| proj_expr.expr.is::<Column>())
1016}
1017
1018pub fn update_ordering(
1021 ordering: LexOrdering,
1022 projected_exprs: &[ProjectionExpr],
1023) -> Result<Option<LexOrdering>> {
1024 let mut updated_exprs = vec![];
1025 for mut sort_expr in ordering.into_iter() {
1026 let Some(updated_expr) = update_expr(&sort_expr.expr, projected_exprs, false)?
1027 else {
1028 return Ok(None);
1029 };
1030 sort_expr.expr = updated_expr;
1031 updated_exprs.push(sort_expr);
1032 }
1033 Ok(LexOrdering::new(updated_exprs))
1034}
1035
1036pub fn update_ordering_requirement(
1039 reqs: LexRequirement,
1040 projected_exprs: &[ProjectionExpr],
1041) -> Result<Option<LexRequirement>> {
1042 let mut updated_exprs = vec![];
1043 for mut sort_expr in reqs.into_iter() {
1044 let Some(updated_expr) = update_expr(&sort_expr.expr, projected_exprs, false)?
1045 else {
1046 return Ok(None);
1047 };
1048 sort_expr.expr = updated_expr;
1049 updated_exprs.push(sort_expr);
1050 }
1051 Ok(LexRequirement::new(updated_exprs))
1052}
1053
1054pub fn physical_to_column_exprs(
1057 exprs: &[ProjectionExpr],
1058) -> Option<Vec<(Column, String)>> {
1059 exprs
1060 .iter()
1061 .map(|proj_expr| {
1062 proj_expr
1063 .expr
1064 .downcast_ref::<Column>()
1065 .map(|col| (col.clone(), proj_expr.alias.clone()))
1066 })
1067 .collect()
1068}
1069
1070pub fn new_join_children(
1074 projection_as_columns: &[(Column, String)],
1075 far_right_left_col_ind: i32,
1076 far_left_right_col_ind: i32,
1077 left_child: &Arc<dyn ExecutionPlan>,
1078 right_child: &Arc<dyn ExecutionPlan>,
1079) -> Result<(ProjectionExec, ProjectionExec)> {
1080 let new_left = ProjectionExec::try_new(
1081 projection_as_columns[0..=far_right_left_col_ind as _]
1082 .iter()
1083 .map(|(col, alias)| ProjectionExpr {
1084 expr: Arc::new(Column::new(col.name(), col.index())) as _,
1085 alias: alias.clone(),
1086 }),
1087 Arc::clone(left_child),
1088 )?;
1089 let left_size = left_child.schema().fields().len() as i32;
1090 let new_right = ProjectionExec::try_new(
1091 projection_as_columns[far_left_right_col_ind as _..]
1092 .iter()
1093 .map(|(col, alias)| {
1094 ProjectionExpr {
1095 expr: Arc::new(Column::new(
1096 col.name(),
1097 (col.index() as i32 - left_size) as _,
1100 )) as _,
1101 alias: alias.clone(),
1102 }
1103 }),
1104 Arc::clone(right_child),
1105 )?;
1106
1107 Ok((new_left, new_right))
1108}
1109
1110fn new_join_children_from_groups(
1117 left_proj: &[(Column, String)],
1118 right_proj: &[(Column, String)],
1119 left_child: &Arc<dyn ExecutionPlan>,
1120 right_child: &Arc<dyn ExecutionPlan>,
1121) -> Result<(ProjectionExec, ProjectionExec)> {
1122 let build = |cols: &[(Column, String)], child: &Arc<dyn ExecutionPlan>| {
1123 ProjectionExec::try_new(
1124 cols.iter().map(|(col, alias)| ProjectionExpr {
1125 expr: Arc::new(Column::new(col.name(), col.index())) as _,
1126 alias: alias.clone(),
1127 }),
1128 Arc::clone(child),
1129 )
1130 };
1131
1132 Ok((
1133 build(left_proj, left_child)?,
1134 build(right_proj, right_child)?,
1135 ))
1136}
1137
1138pub fn join_allows_pushdown(
1144 projection_as_columns: &[(Column, String)],
1145 join_schema: &SchemaRef,
1146 far_right_left_col_ind: i32,
1147 far_left_right_col_ind: i32,
1148) -> bool {
1149 projection_as_columns.len() < join_schema.fields().len()
1151 && (far_right_left_col_ind + 1 == far_left_right_col_ind)
1153 && far_right_left_col_ind >= 0
1155 && far_left_right_col_ind < projection_as_columns.len() as i32
1156}
1157
1158pub fn join_table_borders(
1164 left_table_column_count: usize,
1165 projection_as_columns: &[(Column, String)],
1166) -> (i32, i32) {
1167 let far_right_left_col_ind = projection_as_columns
1168 .iter()
1169 .enumerate()
1170 .take_while(|(_, (projection_column, _))| {
1171 projection_column.index() < left_table_column_count
1172 })
1173 .last()
1174 .map(|(index, _)| index as i32)
1175 .unwrap_or(-1);
1176
1177 let far_left_right_col_ind = projection_as_columns
1178 .iter()
1179 .enumerate()
1180 .rev()
1181 .take_while(|(_, (projection_column, _))| {
1182 projection_column.index() >= left_table_column_count
1183 })
1184 .last()
1185 .map(|(index, _)| index as i32)
1186 .unwrap_or(projection_as_columns.len() as i32);
1187
1188 (far_right_left_col_ind, far_left_right_col_ind)
1189}
1190
1191pub fn update_join_on(
1194 proj_left_exprs: &[(Column, String)],
1195 proj_right_exprs: &[(Column, String)],
1196 hash_join_on: &[(PhysicalExprRef, PhysicalExprRef)],
1197 left_field_size: usize,
1198) -> Option<Vec<(PhysicalExprRef, PhysicalExprRef)>> {
1199 let (left_idx, right_idx): (Vec<_>, Vec<_>) = hash_join_on
1200 .iter()
1201 .map(|(left, right)| (left, right))
1202 .unzip();
1203
1204 let new_left = new_columns_for_join_on(&left_idx, proj_left_exprs, 0)?;
1205 let new_right =
1206 new_columns_for_join_on(&right_idx, proj_right_exprs, left_field_size)?;
1207 Some(new_left.into_iter().zip(new_right).collect())
1208}
1209
1210pub fn update_join_filter(
1213 projection_left_exprs: &[(Column, String)],
1214 projection_right_exprs: &[(Column, String)],
1215 join_filter: &JoinFilter,
1216 left_field_size: usize,
1217) -> Option<JoinFilter> {
1218 let mut new_left_indices = new_indices_for_join_filter(
1219 join_filter,
1220 JoinSide::Left,
1221 projection_left_exprs,
1222 0,
1223 )
1224 .into_iter();
1225 let mut new_right_indices = new_indices_for_join_filter(
1226 join_filter,
1227 JoinSide::Right,
1228 projection_right_exprs,
1229 left_field_size,
1230 )
1231 .into_iter();
1232
1233 (new_right_indices.len() + new_left_indices.len()
1235 == join_filter.column_indices().len())
1236 .then(|| {
1237 JoinFilter::new(
1238 Arc::clone(join_filter.expression()),
1239 join_filter
1240 .column_indices()
1241 .iter()
1242 .map(|col_idx| ColumnIndex {
1243 index: if col_idx.side == JoinSide::Left {
1244 new_left_indices.next().unwrap()
1245 } else {
1246 new_right_indices.next().unwrap()
1247 },
1248 side: col_idx.side,
1249 })
1250 .collect(),
1251 Arc::clone(join_filter.schema()),
1252 )
1253 })
1254}
1255
1256fn try_collapse_projection_chain(
1259 outer: &ProjectionExec,
1260) -> Result<Option<Arc<dyn ExecutionPlan>>> {
1261 let mut current_exprs: Vec<ProjectionExpr> = outer.expr().to_vec();
1262 let mut current_input: Arc<dyn ExecutionPlan> = Arc::clone(outer.input());
1263 let mut column_ref_map: HashMap<Column, usize> = HashMap::new();
1264 let mut collapsed_any = false;
1265
1266 'outer: while let Some(inner_proj) = current_input.downcast_ref::<ProjectionExec>() {
1267 column_ref_map.clear();
1269 for proj_expr in ¤t_exprs {
1270 proj_expr.expr.apply(|expr| {
1271 if let Some(column) = expr.downcast_ref::<Column>() {
1272 *column_ref_map.entry(column.clone()).or_default() += 1;
1273 }
1274 Ok(TreeNodeRecursion::Continue)
1275 })?;
1276 }
1277 let inner_exprs = inner_proj.expr();
1278 let blocked = column_ref_map.iter().any(|(column, count)| {
1283 *count > 1
1284 && !inner_exprs[column.index()]
1285 .expr
1286 .placement()
1287 .should_push_to_leaves()
1288 });
1289 if blocked {
1290 break;
1291 }
1292
1293 let mut new_phys: Vec<Arc<dyn PhysicalExpr>> =
1294 Vec::with_capacity(current_exprs.len());
1295 for proj_expr in ¤t_exprs {
1296 let Some(expr) = update_expr(&proj_expr.expr, inner_exprs, true)? else {
1300 break 'outer;
1301 };
1302 new_phys.push(expr);
1303 }
1304 for (proj_expr, expr) in current_exprs.iter_mut().zip(new_phys) {
1305 proj_expr.expr = expr;
1306 }
1307 current_input = Arc::clone(inner_proj.input());
1308 collapsed_any = true;
1309 }
1310
1311 if !collapsed_any {
1312 return Ok(None);
1313 }
1314
1315 let unified: Arc<dyn ExecutionPlan> =
1317 Arc::new(ProjectionExec::try_new(current_exprs, current_input)?);
1318 remove_unnecessary_projections(unified).data().map(Some)
1319}
1320
1321fn collect_column_indices(exprs: &[ProjectionExpr]) -> Vec<usize> {
1323 let mut seen = std::collections::HashSet::new();
1330 let mut indices = Vec::new();
1331 for proj_expr in exprs {
1332 if let Some(col) = proj_expr.expr.downcast_ref::<Column>() {
1333 if seen.insert(col.index()) {
1335 indices.push(col.index());
1336 }
1337 } else {
1338 proj_expr
1342 .expr
1343 .apply(|expr| {
1344 if let Some(col) = expr.downcast_ref::<Column>()
1345 && seen.insert(col.index())
1346 {
1347 indices.push(col.index());
1348 }
1349 Ok(TreeNodeRecursion::Continue)
1350 })
1351 .expect("closure always returns OK");
1352 }
1353 }
1354 indices
1355}
1356
1357fn new_indices_for_join_filter(
1365 join_filter: &JoinFilter,
1366 join_side: JoinSide,
1367 projection_exprs: &[(Column, String)],
1368 column_index_offset: usize,
1369) -> Vec<usize> {
1370 join_filter
1371 .column_indices()
1372 .iter()
1373 .filter(|col_idx| col_idx.side == join_side)
1374 .filter_map(|col_idx| {
1375 projection_exprs
1376 .iter()
1377 .position(|(col, _)| col_idx.index + column_index_offset == col.index())
1378 })
1379 .collect()
1380}
1381
1382fn new_columns_for_join_on(
1390 hash_join_on: &[&PhysicalExprRef],
1391 projection_exprs: &[(Column, String)],
1392 column_index_offset: usize,
1393) -> Option<Vec<PhysicalExprRef>> {
1394 let new_columns = hash_join_on
1395 .iter()
1396 .filter_map(|on| {
1397 Arc::clone(*on)
1399 .transform(|expr| {
1400 if let Some(column) = expr.downcast_ref::<Column>() {
1401 let new_column = projection_exprs
1403 .iter()
1404 .enumerate()
1405 .find(|(_, (proj_column, _))| {
1406 column.name() == proj_column.name()
1407 && column.index() + column_index_offset
1408 == proj_column.index()
1409 })
1410 .map(|(index, (_, alias))| Column::new(alias, index));
1411 if let Some(new_column) = new_column {
1412 Ok(Transformed::yes(Arc::new(new_column)))
1413 } else {
1414 internal_err!(
1418 "Column {:?} not found in projection expressions",
1419 column
1420 )
1421 }
1422 } else {
1423 Ok(Transformed::no(expr))
1424 }
1425 })
1426 .data()
1427 .ok()
1428 })
1429 .collect::<Vec<_>>();
1430 (new_columns.len() == hash_join_on.len()).then_some(new_columns)
1431}
1432
1433#[cfg(test)]
1434mod tests {
1435 use super::*;
1436
1437 use crate::common::collect;
1438 use crate::empty::EmptyExec;
1439
1440 use crate::filter_pushdown::PushedDown;
1441 use crate::statistics::{StatisticsArgs, StatisticsContext};
1442 use crate::test;
1443 use crate::test::exec::StatisticsExec;
1444
1445 use arrow::datatypes::{DataType, Field, Schema};
1446 use datafusion_common::ScalarValue;
1447 use datafusion_common::stats::{ColumnStatistics, Precision, Statistics};
1448
1449 use datafusion_expr::Operator;
1450 use datafusion_physical_expr::expressions::{
1451 BinaryExpr, Column, DynamicFilterPhysicalExpr, Literal, binary, col, lit,
1452 };
1453
1454 #[test]
1455 fn test_try_new_with_schema_metadata_only_replaces_metadata() -> Result<()> {
1456 let input_schema = Arc::new(Schema::new(vec![Field::new(
1457 "input",
1458 DataType::Int32,
1459 false,
1460 )]));
1461 let input: Arc<dyn ExecutionPlan> = Arc::new(EmptyExec::new(input_schema));
1462 let field_metadata =
1463 HashMap::from([("field-key".to_string(), "field-value".to_string())]);
1464 let schema_metadata =
1465 HashMap::from([("schema-key".to_string(), "schema-value".to_string())]);
1466 let metadata_schema = Schema::new_with_metadata(
1467 vec![
1468 Field::new("ignored", DataType::Utf8, true)
1469 .with_metadata(field_metadata.clone()),
1470 ],
1471 schema_metadata.clone(),
1472 );
1473
1474 let projection = ProjectionExec::try_new_with_schema_metadata(
1475 [ProjectionExpr {
1476 expr: Arc::new(Column::new("input", 0)),
1477 alias: "output".to_string(),
1478 }],
1479 input,
1480 &metadata_schema,
1481 )?;
1482
1483 let expected_schema = Arc::new(Schema::new_with_metadata(
1484 vec![
1485 Field::new("output", DataType::Int32, false)
1486 .with_metadata(field_metadata),
1487 ],
1488 schema_metadata,
1489 ));
1490 assert_eq!(projection.schema(), expected_schema);
1491 Ok(())
1492 }
1493
1494 #[test]
1495 fn test_collect_column_indices() -> Result<()> {
1496 let expr = Arc::new(BinaryExpr::new(
1497 Arc::new(Column::new("b", 7)),
1498 Operator::Minus,
1499 Arc::new(BinaryExpr::new(
1500 Arc::new(Literal::new(ScalarValue::Int32(Some(1)))),
1501 Operator::Plus,
1502 Arc::new(Column::new("a", 1)),
1503 )),
1504 ));
1505 let column_indices = collect_column_indices(&[ProjectionExpr {
1506 expr,
1507 alias: "b-(1+a)".to_string(),
1508 }]);
1509 assert_eq!(column_indices, vec![7, 1]);
1511 Ok(())
1512 }
1513
1514 #[test]
1515 fn test_try_pushdown_through_join_validates_column_indices() -> Result<()> {
1516 let child_schema =
1517 Arc::new(Schema::new(vec![Field::new("i", DataType::Int32, false)]));
1518 let left: Arc<dyn ExecutionPlan> =
1519 Arc::new(EmptyExec::new(Arc::clone(&child_schema)));
1520 let right: Arc<dyn ExecutionPlan> = Arc::new(EmptyExec::new(child_schema));
1521 let join_schema = Arc::new(Schema::new(vec![
1522 Field::new("left_i", DataType::Int32, false),
1523 Field::new("right_i", DataType::Int32, false),
1524 ]));
1525 let join: Arc<dyn ExecutionPlan> =
1526 Arc::new(EmptyExec::new(Arc::clone(&join_schema)));
1527 let projection = ProjectionExec::try_new(
1528 vec![ProjectionExpr {
1529 expr: Arc::new(Column::new("left_i", 0)),
1530 alias: "left_i".to_string(),
1531 }],
1532 join,
1533 )?;
1534
1535 let Err(error) = try_pushdown_through_join_with_column_indices(
1536 &projection,
1537 &left,
1538 &right,
1539 &[],
1540 &join_schema,
1541 None,
1542 &[],
1543 ) else {
1544 panic!("expected a mismatched mapping length to return an error");
1545 };
1546 assert!(
1547 error.to_string().contains(
1548 "Column index mapping has 0 entries but join schema has 2 fields"
1549 )
1550 );
1551
1552 let invalid_child_index = [
1553 ColumnIndex {
1554 index: 1,
1555 side: JoinSide::Left,
1556 },
1557 ColumnIndex {
1558 index: 0,
1559 side: JoinSide::Right,
1560 },
1561 ];
1562 let Err(error) = try_pushdown_through_join_with_column_indices(
1563 &projection,
1564 &left,
1565 &right,
1566 &[],
1567 &join_schema,
1568 None,
1569 &invalid_child_index,
1570 ) else {
1571 panic!("expected an invalid child index to return an error");
1572 };
1573 assert!(error.to_string().contains(
1574 "Join output column 0 maps to left child column 1, but the child has 1 fields"
1575 ));
1576
1577 let wider_join_schema = Arc::new(Schema::new(vec![
1578 Field::new("left_i", DataType::Int32, false),
1579 Field::new("right_i", DataType::Int32, false),
1580 Field::new("extra", DataType::Int32, false),
1581 ]));
1582 let wider_join: Arc<dyn ExecutionPlan> =
1583 Arc::new(EmptyExec::new(wider_join_schema));
1584 let out_of_mapping_projection = ProjectionExec::try_new(
1585 vec![ProjectionExpr {
1586 expr: Arc::new(Column::new("extra", 2)),
1587 alias: "extra".to_string(),
1588 }],
1589 wider_join,
1590 )?;
1591 let valid_child_indices = [
1592 ColumnIndex {
1593 index: 0,
1594 side: JoinSide::Left,
1595 },
1596 ColumnIndex {
1597 index: 0,
1598 side: JoinSide::Right,
1599 },
1600 ];
1601 let Err(error) = try_pushdown_through_join_with_column_indices(
1602 &out_of_mapping_projection,
1603 &left,
1604 &right,
1605 &[],
1606 &join_schema,
1607 None,
1608 &valid_child_indices,
1609 ) else {
1610 panic!("expected an out-of-mapping projection to return an error");
1611 };
1612 assert!(
1613 error.to_string().contains(
1614 "Projection column 2 is outside the 2-entry column index mapping"
1615 )
1616 );
1617
1618 Ok(())
1619 }
1620
1621 #[test]
1622 fn test_join_table_borders() -> Result<()> {
1623 let projections = vec![
1624 (Column::new("b", 1), "b".to_owned()),
1625 (Column::new("c", 2), "c".to_owned()),
1626 (Column::new("e", 4), "e".to_owned()),
1627 (Column::new("d", 3), "d".to_owned()),
1628 (Column::new("c", 2), "c".to_owned()),
1629 (Column::new("f", 5), "f".to_owned()),
1630 (Column::new("h", 7), "h".to_owned()),
1631 (Column::new("g", 6), "g".to_owned()),
1632 ];
1633 let left_table_column_count = 5;
1634 assert_eq!(
1635 join_table_borders(left_table_column_count, &projections),
1636 (4, 5)
1637 );
1638
1639 let left_table_column_count = 8;
1640 assert_eq!(
1641 join_table_borders(left_table_column_count, &projections),
1642 (7, 8)
1643 );
1644
1645 let left_table_column_count = 1;
1646 assert_eq!(
1647 join_table_borders(left_table_column_count, &projections),
1648 (-1, 0)
1649 );
1650
1651 let projections = vec![
1652 (Column::new("a", 0), "a".to_owned()),
1653 (Column::new("b", 1), "b".to_owned()),
1654 (Column::new("d", 3), "d".to_owned()),
1655 (Column::new("g", 6), "g".to_owned()),
1656 (Column::new("e", 4), "e".to_owned()),
1657 (Column::new("f", 5), "f".to_owned()),
1658 (Column::new("e", 4), "e".to_owned()),
1659 (Column::new("h", 7), "h".to_owned()),
1660 ];
1661 let left_table_column_count = 5;
1662 assert_eq!(
1663 join_table_borders(left_table_column_count, &projections),
1664 (2, 7)
1665 );
1666
1667 let left_table_column_count = 7;
1668 assert_eq!(
1669 join_table_borders(left_table_column_count, &projections),
1670 (6, 7)
1671 );
1672
1673 Ok(())
1674 }
1675
1676 #[tokio::test]
1677 async fn project_no_column() -> Result<()> {
1678 let task_ctx = Arc::new(TaskContext::default());
1679
1680 let exec = test::scan_partitioned(1);
1681 let expected = collect(exec.execute(0, Arc::clone(&task_ctx))?).await?;
1682
1683 let projection = ProjectionExec::try_new(vec![] as Vec<ProjectionExpr>, exec)?;
1684 let stream = projection.execute(0, Arc::clone(&task_ctx))?;
1685 let output = collect(stream).await?;
1686 assert_eq!(output.len(), expected.len());
1687
1688 Ok(())
1689 }
1690
1691 #[tokio::test]
1692 async fn project_old_syntax() {
1693 let exec = test::scan_partitioned(1);
1694 let schema = exec.schema();
1695 let expr = col("i", &schema).unwrap();
1696 ProjectionExec::try_new(
1697 vec![
1698 (expr, "c".to_string()),
1701 ],
1702 exec,
1703 )
1704 .unwrap();
1706 }
1707
1708 #[test]
1709 fn test_projection_statistics_uses_input_schema() {
1710 let input_schema = Schema::new(vec![
1711 Field::new("a", DataType::Int32, false),
1712 Field::new("b", DataType::Int32, false),
1713 Field::new("c", DataType::Int32, false),
1714 Field::new("d", DataType::Int32, false),
1715 Field::new("e", DataType::Int32, false),
1716 Field::new("f", DataType::Int32, false),
1717 ]);
1718
1719 let input_statistics = Statistics {
1720 num_rows: Precision::Exact(10),
1721 column_statistics: vec![
1722 ColumnStatistics {
1723 min_value: Precision::Exact(ScalarValue::Int32(Some(1))),
1724 max_value: Precision::Exact(ScalarValue::Int32(Some(100))),
1725 ..Default::default()
1726 },
1727 ColumnStatistics {
1728 min_value: Precision::Exact(ScalarValue::Int32(Some(5))),
1729 max_value: Precision::Exact(ScalarValue::Int32(Some(50))),
1730 ..Default::default()
1731 },
1732 ColumnStatistics {
1733 min_value: Precision::Exact(ScalarValue::Int32(Some(10))),
1734 max_value: Precision::Exact(ScalarValue::Int32(Some(40))),
1735 ..Default::default()
1736 },
1737 ColumnStatistics {
1738 min_value: Precision::Exact(ScalarValue::Int32(Some(20))),
1739 max_value: Precision::Exact(ScalarValue::Int32(Some(30))),
1740 ..Default::default()
1741 },
1742 ColumnStatistics {
1743 min_value: Precision::Exact(ScalarValue::Int32(Some(21))),
1744 max_value: Precision::Exact(ScalarValue::Int32(Some(29))),
1745 ..Default::default()
1746 },
1747 ColumnStatistics {
1748 min_value: Precision::Exact(ScalarValue::Int32(Some(24))),
1749 max_value: Precision::Exact(ScalarValue::Int32(Some(26))),
1750 ..Default::default()
1751 },
1752 ],
1753 ..Default::default()
1754 };
1755
1756 let input = Arc::new(StatisticsExec::new(input_statistics, input_schema));
1757
1758 let exprs: Vec<ProjectionExpr> = vec![
1763 ProjectionExpr {
1764 expr: Arc::new(Column::new("c", 2)) as Arc<dyn PhysicalExpr>,
1765 alias: "c_renamed".to_string(),
1766 },
1767 ProjectionExpr {
1768 expr: Arc::new(BinaryExpr::new(
1769 Arc::new(Column::new("e", 4)),
1770 Operator::Plus,
1771 Arc::new(Column::new("f", 5)),
1772 )) as Arc<dyn PhysicalExpr>,
1773 alias: "e_plus_f".to_string(),
1774 },
1775 ];
1776
1777 let projection = ProjectionExec::try_new(exprs, input).unwrap();
1778
1779 let stats = StatisticsContext::new()
1780 .compute(&projection, &StatisticsArgs::new())
1781 .unwrap();
1782
1783 assert_eq!(stats.num_rows, Precision::Exact(10));
1784 assert_eq!(
1785 stats.column_statistics.len(),
1786 2,
1787 "Expected 2 columns in projection statistics"
1788 );
1789 assert!(stats.total_byte_size.is_exact().unwrap_or(false));
1790 }
1791
1792 #[test]
1793 fn test_filter_pushdown_with_alias() -> Result<()> {
1794 let input_schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
1795 let input = Arc::new(StatisticsExec::new(
1796 Statistics::new_unknown(&input_schema),
1797 input_schema.clone(),
1798 ));
1799
1800 let projection = ProjectionExec::try_new(
1802 vec![ProjectionExpr {
1803 expr: Arc::new(Column::new("a", 0)),
1804 alias: "b".to_string(),
1805 }],
1806 input,
1807 )?;
1808
1809 let filter = Arc::new(BinaryExpr::new(
1811 Arc::new(Column::new("b", 0)),
1812 Operator::Gt,
1813 Arc::new(Literal::new(ScalarValue::Int32(Some(5)))),
1814 )) as Arc<dyn PhysicalExpr>;
1815
1816 let description = projection.gather_filters_for_pushdown(
1817 FilterPushdownPhase::Post,
1818 vec![filter],
1819 &ConfigOptions::default(),
1820 )?;
1821
1822 let expected_filter = Arc::new(BinaryExpr::new(
1825 Arc::new(Column::new("a", 0)),
1826 Operator::Gt,
1827 Arc::new(Literal::new(ScalarValue::Int32(Some(5)))),
1828 )) as Arc<dyn PhysicalExpr>;
1829
1830 assert_eq!(description.self_filters(), vec![vec![]]);
1831 let pushed_filters = &description.parent_filters()[0];
1832 assert_eq!(
1833 format!("{}", pushed_filters[0].predicate),
1834 format!("{}", expected_filter)
1835 );
1836 assert!(matches!(pushed_filters[0].discriminant, PushedDown::Yes));
1838
1839 Ok(())
1840 }
1841
1842 #[test]
1843 fn test_filter_pushdown_with_multiple_aliases() -> Result<()> {
1844 let input_schema = Schema::new(vec![
1845 Field::new("a", DataType::Int32, false),
1846 Field::new("b", DataType::Int32, false),
1847 ]);
1848 let input = Arc::new(StatisticsExec::new(
1849 Statistics {
1850 column_statistics: vec![Default::default(); input_schema.fields().len()],
1851 ..Default::default()
1852 },
1853 input_schema.clone(),
1854 ));
1855
1856 let projection = ProjectionExec::try_new(
1858 vec![
1859 ProjectionExpr {
1860 expr: Arc::new(Column::new("a", 0)),
1861 alias: "x".to_string(),
1862 },
1863 ProjectionExpr {
1864 expr: Arc::new(Column::new("b", 1)),
1865 alias: "y".to_string(),
1866 },
1867 ],
1868 input,
1869 )?;
1870
1871 let filter1 = Arc::new(BinaryExpr::new(
1873 Arc::new(Column::new("x", 0)),
1874 Operator::Gt,
1875 Arc::new(Literal::new(ScalarValue::Int32(Some(5)))),
1876 )) as Arc<dyn PhysicalExpr>;
1877
1878 let filter2 = Arc::new(BinaryExpr::new(
1880 Arc::new(Column::new("y", 1)),
1881 Operator::Lt,
1882 Arc::new(Literal::new(ScalarValue::Int32(Some(10)))),
1883 )) as Arc<dyn PhysicalExpr>;
1884
1885 let description = projection.gather_filters_for_pushdown(
1886 FilterPushdownPhase::Post,
1887 vec![filter1, filter2],
1888 &ConfigOptions::default(),
1889 )?;
1890
1891 let expected_filter1 = Arc::new(BinaryExpr::new(
1893 Arc::new(Column::new("a", 0)),
1894 Operator::Gt,
1895 Arc::new(Literal::new(ScalarValue::Int32(Some(5)))),
1896 )) as Arc<dyn PhysicalExpr>;
1897
1898 let expected_filter2 = Arc::new(BinaryExpr::new(
1899 Arc::new(Column::new("b", 1)),
1900 Operator::Lt,
1901 Arc::new(Literal::new(ScalarValue::Int32(Some(10)))),
1902 )) as Arc<dyn PhysicalExpr>;
1903
1904 let pushed_filters = &description.parent_filters()[0];
1905 assert_eq!(pushed_filters.len(), 2);
1906 assert_eq!(
1908 format!("{}", pushed_filters[0].predicate),
1909 format!("{}", expected_filter1)
1910 );
1911 assert_eq!(
1912 format!("{}", pushed_filters[1].predicate),
1913 format!("{}", expected_filter2)
1914 );
1915 assert!(matches!(pushed_filters[0].discriminant, PushedDown::Yes));
1917 assert!(matches!(pushed_filters[1].discriminant, PushedDown::Yes));
1918
1919 Ok(())
1920 }
1921
1922 #[test]
1923 fn test_filter_pushdown_with_swapped_aliases() -> Result<()> {
1924 let input_schema = Schema::new(vec![
1925 Field::new("a", DataType::Int32, false),
1926 Field::new("b", DataType::Int32, false),
1927 ]);
1928 let input = Arc::new(StatisticsExec::new(
1929 Statistics {
1930 column_statistics: vec![Default::default(); input_schema.fields().len()],
1931 ..Default::default()
1932 },
1933 input_schema.clone(),
1934 ));
1935
1936 let projection = ProjectionExec::try_new(
1938 vec![
1939 ProjectionExpr {
1940 expr: Arc::new(Column::new("a", 0)),
1941 alias: "b".to_string(),
1942 },
1943 ProjectionExpr {
1944 expr: Arc::new(Column::new("b", 1)),
1945 alias: "a".to_string(),
1946 },
1947 ],
1948 input,
1949 )?;
1950
1951 let filter1 = Arc::new(BinaryExpr::new(
1953 Arc::new(Column::new("b", 0)),
1954 Operator::Gt,
1955 Arc::new(Literal::new(ScalarValue::Int32(Some(5)))),
1956 )) as Arc<dyn PhysicalExpr>;
1957
1958 let filter2 = Arc::new(BinaryExpr::new(
1960 Arc::new(Column::new("a", 1)),
1961 Operator::Lt,
1962 Arc::new(Literal::new(ScalarValue::Int32(Some(10)))),
1963 )) as Arc<dyn PhysicalExpr>;
1964
1965 let description = projection.gather_filters_for_pushdown(
1966 FilterPushdownPhase::Post,
1967 vec![filter1, filter2],
1968 &ConfigOptions::default(),
1969 )?;
1970
1971 let pushed_filters = &description.parent_filters()[0];
1972 assert_eq!(pushed_filters.len(), 2);
1973
1974 let expected_filter1 = "a@0 > 5";
1976 let expected_filter2 = "b@1 < 10";
1978
1979 assert_eq!(format!("{}", pushed_filters[0].predicate), expected_filter1);
1980 assert_eq!(format!("{}", pushed_filters[1].predicate), expected_filter2);
1981 assert!(matches!(pushed_filters[0].discriminant, PushedDown::Yes));
1983 assert!(matches!(pushed_filters[1].discriminant, PushedDown::Yes));
1984
1985 Ok(())
1986 }
1987
1988 #[test]
1989 fn test_filter_pushdown_with_mixed_columns() -> Result<()> {
1990 let input_schema = Schema::new(vec![
1991 Field::new("a", DataType::Int32, false),
1992 Field::new("b", DataType::Int32, false),
1993 ]);
1994 let input = Arc::new(StatisticsExec::new(
1995 Statistics {
1996 column_statistics: vec![Default::default(); input_schema.fields().len()],
1997 ..Default::default()
1998 },
1999 input_schema.clone(),
2000 ));
2001
2002 let projection = ProjectionExec::try_new(
2004 vec![
2005 ProjectionExpr {
2006 expr: Arc::new(Column::new("a", 0)),
2007 alias: "x".to_string(),
2008 },
2009 ProjectionExpr {
2010 expr: Arc::new(Column::new("b", 1)),
2011 alias: "b".to_string(),
2012 },
2013 ],
2014 input,
2015 )?;
2016
2017 let filter1 = Arc::new(BinaryExpr::new(
2019 Arc::new(Column::new("x", 0)),
2020 Operator::Gt,
2021 Arc::new(Literal::new(ScalarValue::Int32(Some(5)))),
2022 )) as Arc<dyn PhysicalExpr>;
2023
2024 let filter2 = Arc::new(BinaryExpr::new(
2026 Arc::new(Column::new("b", 1)),
2027 Operator::Lt,
2028 Arc::new(Literal::new(ScalarValue::Int32(Some(10)))),
2029 )) as Arc<dyn PhysicalExpr>;
2030
2031 let description = projection.gather_filters_for_pushdown(
2032 FilterPushdownPhase::Post,
2033 vec![filter1, filter2],
2034 &ConfigOptions::default(),
2035 )?;
2036
2037 let pushed_filters = &description.parent_filters()[0];
2038 assert_eq!(pushed_filters.len(), 2);
2039 let expected_filter1 = "a@0 > 5";
2041 let expected_filter2 = "b@1 < 10";
2043
2044 assert_eq!(format!("{}", pushed_filters[0].predicate), expected_filter1);
2045 assert_eq!(format!("{}", pushed_filters[1].predicate), expected_filter2);
2046 assert!(matches!(pushed_filters[0].discriminant, PushedDown::Yes));
2048 assert!(matches!(pushed_filters[1].discriminant, PushedDown::Yes));
2049
2050 Ok(())
2051 }
2052
2053 #[test]
2054 fn test_filter_pushdown_with_complex_expression() -> Result<()> {
2055 let input_schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
2056 let input = Arc::new(StatisticsExec::new(
2057 Statistics {
2058 column_statistics: vec![Default::default(); input_schema.fields().len()],
2059 ..Default::default()
2060 },
2061 input_schema.clone(),
2062 ));
2063
2064 let projection = ProjectionExec::try_new(
2066 vec![ProjectionExpr {
2067 expr: Arc::new(BinaryExpr::new(
2068 Arc::new(Column::new("a", 0)),
2069 Operator::Plus,
2070 Arc::new(Literal::new(ScalarValue::Int32(Some(1)))),
2071 )),
2072 alias: "z".to_string(),
2073 }],
2074 input,
2075 )?;
2076
2077 let filter = Arc::new(BinaryExpr::new(
2079 Arc::new(Column::new("z", 0)),
2080 Operator::Gt,
2081 Arc::new(Literal::new(ScalarValue::Int32(Some(10)))),
2082 )) as Arc<dyn PhysicalExpr>;
2083
2084 let description = projection.gather_filters_for_pushdown(
2085 FilterPushdownPhase::Post,
2086 vec![filter],
2087 &ConfigOptions::default(),
2088 )?;
2089
2090 let pushed_filters = &description.parent_filters()[0];
2092 assert!(matches!(pushed_filters[0].discriminant, PushedDown::Yes));
2093 assert_eq!(format!("{}", pushed_filters[0].predicate), "a@0 + 1 > 10");
2094
2095 Ok(())
2096 }
2097
2098 #[test]
2099 fn test_filter_pushdown_with_unknown_column() -> Result<()> {
2100 let input_schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
2101 let input = Arc::new(StatisticsExec::new(
2102 Statistics {
2103 column_statistics: vec![Default::default(); input_schema.fields().len()],
2104 ..Default::default()
2105 },
2106 input_schema.clone(),
2107 ));
2108
2109 let projection = ProjectionExec::try_new(
2111 vec![ProjectionExpr {
2112 expr: Arc::new(Column::new("a", 0)),
2113 alias: "a".to_string(),
2114 }],
2115 input,
2116 )?;
2117
2118 let filter = Arc::new(BinaryExpr::new(
2121 Arc::new(Column::new("unknown_col", 1)),
2122 Operator::Gt,
2123 Arc::new(Literal::new(ScalarValue::Int32(Some(5)))),
2124 )) as Arc<dyn PhysicalExpr>;
2125
2126 let description = projection.gather_filters_for_pushdown(
2127 FilterPushdownPhase::Post,
2128 vec![filter],
2129 &ConfigOptions::default(),
2130 )?;
2131
2132 let pushed_filters = &description.parent_filters()[0];
2133 assert!(matches!(pushed_filters[0].discriminant, PushedDown::No));
2134 assert_eq!(
2136 format!("{}", pushed_filters[0].predicate),
2137 "unknown_col@1 > 5"
2138 );
2139
2140 Ok(())
2141 }
2142
2143 #[test]
2147 fn test_basic_dyn_filter_projection_pushdown_update_child() -> Result<()> {
2148 let input_schema =
2149 Arc::new(Schema::new(vec![Field::new("b", DataType::Int32, false)]));
2150
2151 let input = Arc::new(StatisticsExec::new(
2152 Statistics {
2153 column_statistics: vec![Default::default(); input_schema.fields().len()],
2154 ..Default::default()
2155 },
2156 input_schema.as_ref().clone(),
2157 ));
2158
2159 let projection = ProjectionExec::try_new(
2161 vec![ProjectionExpr {
2162 expr: binary(
2163 Arc::new(Column::new("b", 0)),
2164 Operator::Minus,
2165 lit(1),
2166 &input_schema,
2167 )
2168 .unwrap(),
2169 alias: "a".to_string(),
2170 }],
2171 input,
2172 )?;
2173
2174 let projected_schema = projection.schema();
2176 let col_a = col("a", &projected_schema)?;
2177 let dynamic_filter = Arc::new(DynamicFilterPhysicalExpr::new(
2178 vec![Arc::clone(&col_a)],
2179 lit(true),
2180 ));
2181 let current = dynamic_filter.current()?;
2183 assert_eq!(format!("{current}"), "true");
2184
2185 let dyn_phy_expr: Arc<dyn PhysicalExpr> = Arc::clone(&dynamic_filter) as _;
2186
2187 let description = projection.gather_filters_for_pushdown(
2188 FilterPushdownPhase::Post,
2189 vec![dyn_phy_expr],
2190 &ConfigOptions::default(),
2191 )?;
2192
2193 let pushed_filters = &description.parent_filters()[0][0];
2194
2195 assert_eq!(
2197 format!("{}", pushed_filters.predicate),
2198 "DynamicFilter [ empty ]"
2199 );
2200
2201 let new_expr =
2203 Arc::new(BinaryExpr::new(Arc::clone(&col_a), Operator::Gt, lit(5i32)));
2204 dynamic_filter.update(new_expr)?;
2205
2206 let current = dynamic_filter.current()?;
2208 assert_eq!(format!("{current}"), "a@0 > 5");
2209
2210 assert_eq!(
2212 format!("{}", pushed_filters.predicate),
2213 "DynamicFilter [ b@0 - 1 > 5 ]"
2214 );
2215
2216 Ok(())
2217 }
2218}