use crate::physical_optimizer::utils::{add_sort_above, is_limit, is_union, is_window};
use crate::physical_plan::filter::FilterExec;
use crate::physical_plan::joins::utils::JoinSide;
use crate::physical_plan::joins::SortMergeJoinExec;
use crate::physical_plan::projection::ProjectionExec;
use crate::physical_plan::repartition::RepartitionExec;
use crate::physical_plan::sorts::sort::SortExec;
use crate::physical_plan::{with_new_children_if_necessary, ExecutionPlan};
use datafusion_common::tree_node::{Transformed, TreeNode, VisitRecursion};
use datafusion_common::{DataFusionError, Result};
use datafusion_expr::JoinType;
use datafusion_physical_expr::expressions::Column;
use datafusion_physical_expr::utils::{
ordering_satisfy_requirement, requirements_compatible,
};
use datafusion_physical_expr::{PhysicalSortExpr, PhysicalSortRequirement};
use itertools::izip;
use std::ops::Deref;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub(crate) struct SortPushDown {
pub plan: Arc<dyn ExecutionPlan>,
required_ordering: Option<Vec<PhysicalSortRequirement>>,
adjusted_request_ordering: Vec<Option<Vec<PhysicalSortRequirement>>>,
}
impl SortPushDown {
pub fn init(plan: Arc<dyn ExecutionPlan>) -> Self {
let request_ordering = plan.required_input_ordering();
SortPushDown {
plan,
required_ordering: None,
adjusted_request_ordering: request_ordering,
}
}
pub fn children(&self) -> Vec<SortPushDown> {
izip!(
self.plan.children().into_iter(),
self.adjusted_request_ordering.clone().into_iter(),
)
.map(|(child, from_parent)| {
let child_request_ordering = child.required_input_ordering();
SortPushDown {
plan: child,
required_ordering: from_parent,
adjusted_request_ordering: child_request_ordering,
}
})
.collect()
}
}
impl TreeNode for SortPushDown {
fn apply_children<F>(&self, op: &mut F) -> Result<VisitRecursion>
where
F: FnMut(&Self) -> Result<VisitRecursion>,
{
let children = self.children();
for child in children {
match op(&child)? {
VisitRecursion::Continue => {}
VisitRecursion::Skip => return Ok(VisitRecursion::Continue),
VisitRecursion::Stop => return Ok(VisitRecursion::Stop),
}
}
Ok(VisitRecursion::Continue)
}
fn map_children<F>(mut self, transform: F) -> Result<Self>
where
F: FnMut(Self) -> Result<Self>,
{
let children = self.children();
if !children.is_empty() {
let children_plans = children
.into_iter()
.map(transform)
.map(|r| r.map(|s| s.plan))
.collect::<Result<Vec<_>>>()?;
match with_new_children_if_necessary(self.plan, children_plans)? {
Transformed::Yes(plan) | Transformed::No(plan) => {
self.plan = plan;
}
}
};
Ok(self)
}
}
pub(crate) fn pushdown_sorts(
requirements: SortPushDown,
) -> Result<Transformed<SortPushDown>> {
let plan = &requirements.plan;
let parent_required = requirements.required_ordering.as_deref();
const ERR_MSG: &str = "Expects parent requirement to contain something";
let err = || DataFusionError::Plan(ERR_MSG.to_string());
if let Some(sort_exec) = plan.as_any().downcast_ref::<SortExec>() {
let mut new_plan = plan.clone();
if !ordering_satisfy_requirement(
plan.output_ordering(),
parent_required,
|| plan.equivalence_properties(),
|| plan.ordering_equivalence_properties(),
) {
let parent_required_expr = PhysicalSortRequirement::to_sort_exprs(
parent_required.ok_or_else(err)?.iter().cloned(),
);
new_plan = sort_exec.input.clone();
add_sort_above(&mut new_plan, parent_required_expr)?;
};
let required_ordering = new_plan
.output_ordering()
.map(PhysicalSortRequirement::from_sort_exprs);
let child = &new_plan.children()[0];
if let Some(adjusted) =
pushdown_requirement_to_children(child, required_ordering.as_deref())?
{
Ok(Transformed::Yes(SortPushDown {
plan: child.clone(),
required_ordering: None,
adjusted_request_ordering: adjusted,
}))
} else {
Ok(Transformed::Yes(SortPushDown::init(new_plan)))
}
} else {
if ordering_satisfy_requirement(
plan.output_ordering(),
parent_required,
|| plan.equivalence_properties(),
|| plan.ordering_equivalence_properties(),
) {
return Ok(Transformed::Yes(SortPushDown {
required_ordering: None,
..requirements
}));
}
if let Some(adjusted) = pushdown_requirement_to_children(plan, parent_required)? {
Ok(Transformed::Yes(SortPushDown {
plan: plan.clone(),
required_ordering: None,
adjusted_request_ordering: adjusted,
}))
} else {
let parent_required_expr = PhysicalSortRequirement::to_sort_exprs(
parent_required.ok_or_else(err)?.iter().cloned(),
);
let mut new_plan = plan.clone();
add_sort_above(&mut new_plan, parent_required_expr)?;
Ok(Transformed::Yes(SortPushDown::init(new_plan)))
}
}
}
fn pushdown_requirement_to_children(
plan: &Arc<dyn ExecutionPlan>,
parent_required: Option<&[PhysicalSortRequirement]>,
) -> Result<Option<Vec<Option<Vec<PhysicalSortRequirement>>>>> {
const ERR_MSG: &str = "Expects parent requirement to contain something";
let err = || DataFusionError::Plan(ERR_MSG.to_string());
let maintains_input_order = plan.maintains_input_order();
if is_window(plan) {
let required_input_ordering = plan.required_input_ordering();
let request_child = required_input_ordering[0].as_deref();
let child_plan = plan.children()[0].clone();
match determine_children_requirement(parent_required, request_child, child_plan) {
RequirementsCompatibility::Satisfy => {
Ok(Some(vec![request_child.map(|r| r.to_vec())]))
}
RequirementsCompatibility::Compatible(adjusted) => Ok(Some(vec![adjusted])),
RequirementsCompatibility::NonCompatible => Ok(None),
}
} else if is_union(plan) {
Ok(Some(vec![
parent_required.map(|elem| elem.to_vec());
plan.children().len()
]))
} else if let Some(smj) = plan.as_any().downcast_ref::<SortMergeJoinExec>() {
let left_columns_len = smj.left.schema().fields().len();
let parent_required_expr = PhysicalSortRequirement::to_sort_exprs(
parent_required.ok_or_else(err)?.iter().cloned(),
);
let expr_source_side =
expr_source_sides(&parent_required_expr, smj.join_type, left_columns_len);
match expr_source_side {
Some(JoinSide::Left) if maintains_input_order[0] => {
try_pushdown_requirements_to_join(
plan,
parent_required,
parent_required_expr,
JoinSide::Left,
)
}
Some(JoinSide::Right) if maintains_input_order[1] => {
let new_right_required = match smj.join_type {
JoinType::Inner | JoinType::Right => shift_right_required(
parent_required.ok_or_else(err)?,
left_columns_len,
)?,
JoinType::RightSemi | JoinType::RightAnti => {
parent_required.ok_or_else(err)?.to_vec()
}
_ => Err(DataFusionError::Plan(
"Unexpected SortMergeJoin type here".to_string(),
))?,
};
try_pushdown_requirements_to_join(
plan,
Some(new_right_required.deref()),
parent_required_expr,
JoinSide::Right,
)
}
_ => {
Ok(None)
}
}
} else if maintains_input_order.is_empty()
|| !maintains_input_order.iter().any(|o| *o)
|| plan.as_any().is::<RepartitionExec>()
|| plan.as_any().is::<FilterExec>()
|| plan.as_any().is::<ProjectionExec>()
|| is_limit(plan)
{
Ok(None)
} else {
Ok(Some(vec![
parent_required.map(|elem| elem.to_vec());
plan.children().len()
]))
}
}
fn determine_children_requirement(
parent_required: Option<&[PhysicalSortRequirement]>,
request_child: Option<&[PhysicalSortRequirement]>,
child_plan: Arc<dyn ExecutionPlan>,
) -> RequirementsCompatibility {
if requirements_compatible(
request_child,
parent_required,
|| child_plan.ordering_equivalence_properties(),
|| child_plan.equivalence_properties(),
) {
RequirementsCompatibility::Satisfy
} else if requirements_compatible(
parent_required,
request_child,
|| child_plan.ordering_equivalence_properties(),
|| child_plan.equivalence_properties(),
) {
let adjusted = parent_required.map(|r| r.to_vec());
RequirementsCompatibility::Compatible(adjusted)
} else {
RequirementsCompatibility::NonCompatible
}
}
fn try_pushdown_requirements_to_join(
plan: &Arc<dyn ExecutionPlan>,
parent_required: Option<&[PhysicalSortRequirement]>,
sort_expr: Vec<PhysicalSortExpr>,
push_side: JoinSide,
) -> Result<Option<Vec<Option<Vec<PhysicalSortRequirement>>>>> {
let child_idx = match push_side {
JoinSide::Left => 0,
JoinSide::Right => 1,
};
let required_input_ordering = plan.required_input_ordering();
let request_child = required_input_ordering[child_idx].as_deref();
let child_plan = plan.children()[child_idx].clone();
match determine_children_requirement(parent_required, request_child, child_plan) {
RequirementsCompatibility::Satisfy => Ok(None),
RequirementsCompatibility::Compatible(adjusted) => {
let new_adjusted = match push_side {
JoinSide::Left => {
vec![adjusted, required_input_ordering[1].clone()]
}
JoinSide::Right => {
vec![required_input_ordering[0].clone(), adjusted]
}
};
Ok(Some(new_adjusted))
}
RequirementsCompatibility::NonCompatible => {
add_sort_above(&mut plan.clone(), sort_expr)?;
Ok(None)
}
}
}
fn expr_source_sides(
required_exprs: &[PhysicalSortExpr],
join_type: JoinType,
left_columns_len: usize,
) -> Option<JoinSide> {
match join_type {
JoinType::Inner | JoinType::Left | JoinType::Right | JoinType::Full => {
let all_column_sides = required_exprs
.iter()
.filter_map(|r| {
r.expr.as_any().downcast_ref::<Column>().map(|col| {
if col.index() < left_columns_len {
JoinSide::Left
} else {
JoinSide::Right
}
})
})
.collect::<Vec<_>>();
if all_column_sides.len() != required_exprs.len() {
None
} else if all_column_sides
.iter()
.all(|side| matches!(side, JoinSide::Left))
{
Some(JoinSide::Left)
} else if all_column_sides
.iter()
.all(|side| matches!(side, JoinSide::Right))
{
Some(JoinSide::Right)
} else {
None
}
}
JoinType::LeftSemi | JoinType::LeftAnti => required_exprs
.iter()
.all(|e| e.expr.as_any().downcast_ref::<Column>().is_some())
.then_some(JoinSide::Left),
JoinType::RightSemi | JoinType::RightAnti => required_exprs
.iter()
.all(|e| e.expr.as_any().downcast_ref::<Column>().is_some())
.then_some(JoinSide::Right),
}
}
fn shift_right_required(
parent_required: &[PhysicalSortRequirement],
left_columns_len: usize,
) -> Result<Vec<PhysicalSortRequirement>> {
let new_right_required: Vec<PhysicalSortRequirement> = parent_required
.iter()
.filter_map(|r| {
let Some(col) = r.expr.as_any().downcast_ref::<Column>() else {
return None;
};
if col.index() < left_columns_len {
return None;
}
let new_col =
Arc::new(Column::new(col.name(), col.index() - left_columns_len));
Some(r.clone().with_expr(new_col))
})
.collect::<Vec<_>>();
if new_right_required.len() == parent_required.len() {
Ok(new_right_required)
} else {
Err(DataFusionError::Plan(
"Expect to shift all the parent required column indexes for SortMergeJoin"
.to_string(),
))
}
}
#[derive(Debug)]
enum RequirementsCompatibility {
Satisfy,
Compatible(Option<Vec<PhysicalSortRequirement>>),
NonCompatible,
}