Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions datafusion/core/tests/physical_optimizer/enforce_distribution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,12 @@ impl ExecutionPlan for SinglePartitionMaintainsOrderExec {
vec![&self.input]
}

fn required_input_distribution(&self) -> Vec<Distribution> {
vec![Distribution::SinglePartition]
fn input_distribution_requirements(
&self,
) -> datafusion_physical_plan::InputDistributionRequirements {
datafusion_physical_plan::InputDistributionRequirements::new(vec![
Distribution::SinglePartition,
])
}

fn maintains_input_order(&self) -> Vec<bool> {
Expand Down Expand Up @@ -823,6 +827,49 @@ fn range_grouping_set_aggregate_rehashes_with_grouping_id() -> Result<()> {
Ok(())
}

#[test]
fn range_inner_hash_join_rehashes_incompatible_range_partitioning() -> Result<()> {
let left = parquet_exec_with_output_partitioning(range_partitioning(
"a",
[10, 20, 30],
SortOptions::default(),
)?);
let right = projection_exec_with_alias(
parquet_exec_with_output_partitioning(range_partitioning(
"a",
[10, 30, 40],
SortOptions::default(),
)?),
vec![
("a".to_string(), "a1".to_string()),
("b".to_string(), "b1".to_string()),
],
);
let join_on = vec![(
Arc::new(Column::new_with_schema("a", &left.schema())?) as _,
Arc::new(Column::new_with_schema("a1", &right.schema())?) as _,
)];
let join = hash_join_exec(left, right, &join_on, &JoinType::Inner);

let plan = TestConfig::default()
.with_query_execution_partitions(4)
.to_plan(join, &DISTRIB_DISTRIB_SORT);

assert_plan!(
plan,
@r"
HashJoinExec: mode=Partitioned, join_type=Inner, on=[(a@0, a1@0)]
RepartitionExec: partitioning=Hash([a@0], 4), input_partitions=4
DataSourceExec: file_groups={4 groups: [[p0], [p1], [p2], [p3]]}, projection=[a, b, c, d, e], output_partitioning=Range([a@0 ASC], [(10), (20), (30)], 4), file_type=parquet
RepartitionExec: partitioning=Hash([a1@0], 4), input_partitions=4
ProjectionExec: expr=[a@0 as a1, b@1 as b1]
DataSourceExec: file_groups={4 groups: [[p0], [p1], [p2], [p3]]}, projection=[a, b, c, d, e], output_partitioning=Range([a@0 ASC], [(10), (30), (40)], 4), file_type=parquet
"
);

Ok(())
}

#[test]
fn multi_hash_joins() -> Result<()> {
let left = parquet_exec();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -974,8 +974,12 @@ impl ExecutionPlan for MockReqExec {
fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
vec![&self.input]
}
fn required_input_distribution(&self) -> Vec<Distribution> {
vec![self.dist.clone()]
fn input_distribution_requirements(
&self,
) -> datafusion_physical_plan::InputDistributionRequirements {
datafusion_physical_plan::InputDistributionRequirements::new(vec![
self.dist.clone(),
])
}
fn required_input_ordering(&self) -> Vec<Option<OrderingRequirements>> {
vec![
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -800,7 +800,9 @@ fn test_output_req_after_projection() -> Result<()> {
if let Distribution::KeyPartitioned(vec) = after_optimize
.downcast_ref::<OutputRequirementExec>()
.unwrap()
.required_input_distribution()[0]
.input_distribution_requirements()
.child_distribution(0)
.unwrap()
.clone()
{
assert!(
Expand Down
51 changes: 47 additions & 4 deletions datafusion/core/tests/physical_optimizer/sanity_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ use insta::assert_snapshot;
use std::sync::Arc;

use crate::physical_optimizer::test_utils::{
bounded_window_exec, global_limit_exec, local_limit_exec, memory_exec,
projection_exec, repartition_exec, sort_exec, sort_expr, sort_expr_options,
sort_merge_join_exec, sort_preserving_merge_exec, union_exec,
bounded_window_exec, global_limit_exec, hash_join_exec, local_limit_exec,
memory_exec, projection_exec, repartition_exec, sort_exec, sort_expr,
sort_expr_options, sort_merge_join_exec, sort_preserving_merge_exec, union_exec,
};

use arrow::compute::SortOptions;
Expand All @@ -30,8 +30,8 @@ use datafusion::datasource::stream::{FileStreamProvider, StreamConfig, StreamTab
use datafusion::prelude::{CsvReadOptions, SessionContext};
use datafusion_common::config::ConfigOptions;
use datafusion_common::{JoinType, Result, ScalarValue};
use datafusion_physical_expr::Partitioning;
use datafusion_physical_expr::expressions::{Literal, col};
use datafusion_physical_expr::{Partitioning, RangePartitioning, SplitPoint};
use datafusion_physical_expr_common::sort_expr::LexOrdering;
use datafusion_physical_optimizer::PhysicalOptimizerRule;
use datafusion_physical_optimizer::sanity_checker::SanityCheckPlan;
Expand Down Expand Up @@ -400,6 +400,49 @@ fn assert_sanity_check(plan: &Arc<dyn ExecutionPlan>, is_sane: bool) {
);
}

fn range_partitioned_exec(
schema: &SchemaRef,
key: &str,
split_points: impl IntoIterator<Item = i32>,
) -> Result<Arc<dyn ExecutionPlan>> {
let split_points = split_points
.into_iter()
.map(|value| SplitPoint::new(vec![ScalarValue::Int32(Some(value))]))
.collect();
let partitioning = Partitioning::Range(RangePartitioning::try_new(
[sort_expr(key, schema)].into(),
split_points,
)?);
RepartitionExec::try_new(memory_exec(schema), partitioning)
.map(|exec| Arc::new(exec) as Arc<dyn ExecutionPlan>)
}

#[test]
fn test_partitioned_hash_join_requires_co_partitioned_children() -> Result<()> {
let schema = create_test_schema2();
let join_on = vec![(col("a", &schema)?, col("a", &schema)?)];

let compatible_join = hash_join_exec(
range_partitioned_exec(&schema, "a", [10])?,
range_partitioned_exec(&schema, "a", [10])?,
join_on.clone(),
None,
&JoinType::Inner,
)?;
assert_sanity_check(&compatible_join, true);

let incompatible_join = hash_join_exec(
range_partitioned_exec(&schema, "a", [10])?,
range_partitioned_exec(&schema, "a", [20])?,
join_on,
None,
&JoinType::Inner,
)?;
assert_sanity_check(&incompatible_join, false);

Ok(())
}

#[tokio::test]
/// Tests that plan is valid when the sort requirements are satisfied.
async fn test_bounded_window_agg_sort_requirement() -> Result<()> {
Expand Down
8 changes: 6 additions & 2 deletions datafusion/core/tests/user_defined/user_defined_plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -708,8 +708,12 @@ impl ExecutionPlan for TopKExec {
&self.cache
}

fn required_input_distribution(&self) -> Vec<Distribution> {
vec![Distribution::SinglePartition]
fn input_distribution_requirements(
&self,
) -> datafusion_physical_plan::InputDistributionRequirements {
datafusion_physical_plan::InputDistributionRequirements::new(vec![
Distribution::SinglePartition,
])
}

fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
Expand Down
14 changes: 11 additions & 3 deletions datafusion/datasource/src/sink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ use datafusion_physical_expr_common::sort_expr::{LexRequirement, OrderingRequire
use datafusion_physical_plan::metrics::MetricsSet;
use datafusion_physical_plan::stream::RecordBatchStreamAdapter;
use datafusion_physical_plan::{
DisplayAs, DisplayFormatType, ExecutionPlan, ExecutionPlanProperties, Partitioning,
PlanProperties, SendableRecordBatchStream, execute_input_stream,
DisplayAs, DisplayFormatType, ExecutionPlan, ExecutionPlanProperties,
InputDistributionRequirements, Partitioning, PlanProperties,
SendableRecordBatchStream, execute_input_stream,
};

use async_trait::async_trait;
Expand Down Expand Up @@ -189,9 +190,16 @@ impl ExecutionPlan for DataSinkExec {
}

fn required_input_distribution(&self) -> Vec<Distribution> {
self.input_distribution_requirements().into_per_child()
}

fn input_distribution_requirements(&self) -> InputDistributionRequirements {
// DataSink is responsible for dynamically partitioning its
// own input at execution time, and so requires a single input partition.
vec![Distribution::SinglePartition; self.children().len()]
InputDistributionRequirements::new(vec![
Distribution::SinglePartition;
self.children().len()
])
}

fn required_input_ordering(&self) -> Vec<Option<OrderingRequirements>> {
Expand Down
4 changes: 3 additions & 1 deletion datafusion/physical-expr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ pub use equivalence::{
AcrossPartitions, ConstExpr, EquivalenceProperties, calculate_union,
};
pub use expressions::{DynamicFilterTracker, DynamicFilterTracking};
pub use partitioning::{Distribution, Partitioning, RangePartitioning};
pub use partitioning::{
Distribution, Partitioning, PartitioningSatisfaction, RangePartitioning,
};
pub use physical_expr::{
add_offset_to_expr, add_offset_to_physical_sort_exprs, create_lex_ordering,
create_ordering, create_physical_partitioning, create_physical_sort_expr,
Expand Down
Loading
Loading