Skip to content

Commit 001b10c

Browse files
Support co-partitioned range inner equi joins
1 parent 6a0e76e commit 001b10c

16 files changed

Lines changed: 1134 additions & 433 deletions

File tree

datafusion/core/tests/physical_optimizer/sanity_checker.rs

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ use insta::assert_snapshot;
1919
use std::sync::Arc;
2020

2121
use crate::physical_optimizer::test_utils::{
22-
bounded_window_exec, global_limit_exec, local_limit_exec, memory_exec,
23-
projection_exec, repartition_exec, sort_exec, sort_expr, sort_expr_options,
24-
sort_merge_join_exec, sort_preserving_merge_exec, union_exec,
22+
bounded_window_exec, global_limit_exec, hash_join_exec, local_limit_exec,
23+
memory_exec, projection_exec, repartition_exec, sort_exec, sort_expr,
24+
sort_expr_options, sort_merge_join_exec, sort_preserving_merge_exec, union_exec,
2525
};
2626

2727
use arrow::compute::SortOptions;
@@ -30,8 +30,8 @@ use datafusion::datasource::stream::{FileStreamProvider, StreamConfig, StreamTab
3030
use datafusion::prelude::{CsvReadOptions, SessionContext};
3131
use datafusion_common::config::ConfigOptions;
3232
use datafusion_common::{JoinType, Result, ScalarValue};
33-
use datafusion_physical_expr::Partitioning;
3433
use datafusion_physical_expr::expressions::{Literal, col};
34+
use datafusion_physical_expr::{Partitioning, RangePartitioning, SplitPoint};
3535
use datafusion_physical_expr_common::sort_expr::LexOrdering;
3636
use datafusion_physical_optimizer::PhysicalOptimizerRule;
3737
use datafusion_physical_optimizer::sanity_checker::SanityCheckPlan;
@@ -400,6 +400,50 @@ fn assert_sanity_check(plan: &Arc<dyn ExecutionPlan>, is_sane: bool) {
400400
);
401401
}
402402

403+
fn range_partitioned_exec(
404+
schema: &SchemaRef,
405+
key: &str,
406+
split_points: impl IntoIterator<Item = i32>,
407+
) -> Result<Arc<dyn ExecutionPlan>> {
408+
let split_points = split_points
409+
.into_iter()
410+
.map(|value| SplitPoint::new(vec![ScalarValue::Int32(Some(value))]))
411+
.collect();
412+
let partitioning = Partitioning::Range(RangePartitioning::try_new(
413+
[sort_expr(key, schema)].into(),
414+
split_points,
415+
)?);
416+
RepartitionExec::try_new(memory_exec(schema), partitioning)
417+
.map(|exec| Arc::new(exec) as Arc<dyn ExecutionPlan>)
418+
}
419+
420+
#[test]
421+
fn test_partitioned_hash_join_requires_co_partitioned_children() -> Result<()> {
422+
let schema = create_test_schema2();
423+
let join_on = vec![(col("a", &schema)?, col("b", &schema)?)];
424+
let right = range_partitioned_exec(&schema, "b", [10])?;
425+
426+
let valid_join = hash_join_exec(
427+
range_partitioned_exec(&schema, "a", [10])?,
428+
Arc::clone(&right),
429+
join_on.clone(),
430+
None,
431+
&JoinType::Inner,
432+
)?;
433+
assert_sanity_check(&valid_join, true);
434+
435+
let invalid_join = hash_join_exec(
436+
range_partitioned_exec(&schema, "a", [20])?,
437+
right,
438+
join_on,
439+
None,
440+
&JoinType::Inner,
441+
)?;
442+
assert_sanity_check(&invalid_join, false);
443+
444+
Ok(())
445+
}
446+
403447
#[tokio::test]
404448
/// Tests that plan is valid when the sort requirements are satisfied.
405449
async fn test_bounded_window_agg_sort_requirement() -> Result<()> {

datafusion/physical-expr/src/partitioning.rs

Lines changed: 0 additions & 231 deletions
Original file line numberDiff line numberDiff line change
@@ -244,50 +244,6 @@ impl RangePartitioning {
244244
self.split_points.len() + 1
245245
}
246246

247-
/// Returns true when `self` and `other` describe the same range partition
248-
/// map.
249-
///
250-
/// Single-partition range partitionings are always compatible. Otherwise,
251-
/// the two partitionings must have identical split points and equivalent
252-
/// ordering expressions with the same sort options.
253-
pub fn compatible_with(
254-
&self,
255-
other: &Self,
256-
eq_properties: &EquivalenceProperties,
257-
) -> bool {
258-
if self.partition_count() == 1 && other.partition_count() == 1 {
259-
return true;
260-
}
261-
262-
if self.split_points != other.split_points
263-
|| self.ordering.len() != other.ordering.len()
264-
{
265-
return false;
266-
}
267-
268-
if !self
269-
.ordering
270-
.iter()
271-
.zip(other.ordering.iter())
272-
.all(|(left, right)| left.options == right.options)
273-
{
274-
return false;
275-
}
276-
277-
let left_exprs = self
278-
.ordering
279-
.iter()
280-
.map(|sort_expr| Arc::clone(&sort_expr.expr))
281-
.collect::<Vec<_>>();
282-
let right_exprs = other
283-
.ordering
284-
.iter()
285-
.map(|sort_expr| Arc::clone(&sort_expr.expr))
286-
.collect::<Vec<_>>();
287-
288-
equivalent_exprs(&left_exprs, &right_exprs, eq_properties)
289-
}
290-
291247
/// Calculates the range partitioning after applying the given projection.
292248
///
293249
/// Returns `None` if any range key cannot be projected or if projection
@@ -406,42 +362,6 @@ impl Partitioning {
406362
}
407363
}
408364

409-
/// Returns true when `self` and `other` describe compatible partition maps.
410-
///
411-
/// Compatible partition maps can be used for partition-local behavior: if
412-
/// this returns true, partition `i` from both partitionings can be treated
413-
/// as covering the same partition domain. This is stricter than
414-
/// [`Self::satisfaction`], which only answers whether this partitioning can
415-
/// satisfy a required distribution.
416-
pub fn compatible_with(
417-
&self,
418-
other: &Self,
419-
eq_properties: &EquivalenceProperties,
420-
) -> bool {
421-
if self.partition_count() == 1 && other.partition_count() == 1 {
422-
return true;
423-
}
424-
425-
match (self, other) {
426-
(
427-
Partitioning::Hash(left_exprs, left_count),
428-
Partitioning::Hash(right_exprs, right_count),
429-
) => {
430-
if left_count != right_count {
431-
return false;
432-
}
433-
if left_exprs.is_empty() || right_exprs.is_empty() {
434-
return false;
435-
}
436-
equivalent_exprs(left_exprs, right_exprs, eq_properties)
437-
}
438-
(Partitioning::Range(left), Partitioning::Range(right)) => {
439-
left.compatible_with(right, eq_properties)
440-
}
441-
_ => false,
442-
}
443-
}
444-
445365
/// Returns true if `subset_exprs` is a subset of `exprs`.
446366
/// For example: Hash(a, b) is subset of Hash(a) since a partition with all occurrences of
447367
/// a distinct (a) must also contain all occurrences of a distinct (a, b) with the same (a).
@@ -1304,157 +1224,6 @@ mod tests {
13041224
Ok(())
13051225
}
13061226

1307-
#[test]
1308-
fn test_range_partitioning_compatible_with() -> Result<()> {
1309-
let fixture = PartitioningTestFixture::int64(&["a", "b"])?;
1310-
let mut eq_properties = fixture.eq_properties.clone();
1311-
eq_properties.add_equal_conditions(fixture.col(0), fixture.col(1))?;
1312-
1313-
let split_points = vec![int_split_point([10]), int_split_point([20])];
1314-
let range_a = fixture.range([0], split_points.clone());
1315-
let range_a_same = fixture.range([0], split_points.clone());
1316-
let range_b_equivalent = fixture.range([1], split_points.clone());
1317-
let range_b_different_split = fixture.range([1], vec![int_split_point([30])]);
1318-
let range_a_desc = RangePartitioning::try_new(
1319-
[fixture.range_sort_expr(0, SortOptions::new(true, false))].into(),
1320-
vec![int_split_point([10])],
1321-
)?;
1322-
let single_partition_range_a = fixture.range([0], vec![]);
1323-
let single_partition_range_b = fixture.range([1], vec![]);
1324-
1325-
assert!(range_a.compatible_with(&range_a_same, &fixture.eq_properties));
1326-
assert!(range_a.compatible_with(&range_b_equivalent, &eq_properties));
1327-
assert!(!range_a.compatible_with(&range_b_equivalent, &fixture.eq_properties));
1328-
assert!(!range_a.compatible_with(&range_b_different_split, &eq_properties));
1329-
assert!(!range_a.compatible_with(&range_a_desc, &eq_properties));
1330-
assert!(
1331-
single_partition_range_a
1332-
.compatible_with(&single_partition_range_b, &fixture.eq_properties)
1333-
);
1334-
1335-
assert!(
1336-
fixture
1337-
.range_partitioning([0], vec![int_split_point([10])])
1338-
.compatible_with(
1339-
&fixture.range_partitioning([1], vec![int_split_point([10])]),
1340-
&eq_properties
1341-
)
1342-
);
1343-
assert!(
1344-
!fixture
1345-
.range_partitioning([0], vec![int_split_point([10])])
1346-
.compatible_with(
1347-
&fixture.range_partitioning([0], vec![int_split_point([20])]),
1348-
&fixture.eq_properties
1349-
)
1350-
);
1351-
assert!(
1352-
!fixture
1353-
.range_partitioning([0], vec![int_split_point([10])])
1354-
.compatible_with(
1355-
&fixture.hash_partitioning([0], 2),
1356-
&fixture.eq_properties
1357-
)
1358-
);
1359-
1360-
Ok(())
1361-
}
1362-
1363-
#[test]
1364-
fn test_hash_partitioning_compatible_with() -> Result<()> {
1365-
let fixture = PartitioningTestFixture::int64(&["a", "b"])?;
1366-
let mut eq_properties = fixture.eq_properties.clone();
1367-
eq_properties.add_equal_conditions(fixture.col(0), fixture.col(1))?;
1368-
1369-
assert!(
1370-
fixture.hash_partitioning([0], 2).compatible_with(
1371-
&fixture.hash_partitioning([0], 2),
1372-
&fixture.eq_properties
1373-
)
1374-
);
1375-
assert!(
1376-
fixture
1377-
.hash_partitioning([0], 2)
1378-
.compatible_with(&fixture.hash_partitioning([1], 2), &eq_properties)
1379-
);
1380-
assert!(
1381-
!fixture.hash_partitioning([0], 2).compatible_with(
1382-
&fixture.hash_partitioning([1], 2),
1383-
&fixture.eq_properties
1384-
)
1385-
);
1386-
assert!(
1387-
!fixture.hash_partitioning([0], 2).compatible_with(
1388-
&fixture.hash_partitioning([0], 3),
1389-
&fixture.eq_properties
1390-
)
1391-
);
1392-
assert!(!fixture.hash_partitioning([0], 2).compatible_with(
1393-
&fixture.hash_partitioning([0, 1], 2),
1394-
&fixture.eq_properties
1395-
));
1396-
assert!(
1397-
!Partitioning::Hash(vec![], 2)
1398-
.compatible_with(&Partitioning::Hash(vec![], 2), &fixture.eq_properties)
1399-
);
1400-
assert!(!fixture.hash_partitioning([0], 2).compatible_with(
1401-
&fixture.range_partitioning([0], vec![int_split_point([10])]),
1402-
&fixture.eq_properties
1403-
));
1404-
assert!(
1405-
fixture.hash_partitioning([0], 1).compatible_with(
1406-
&Partitioning::RoundRobinBatch(1),
1407-
&fixture.eq_properties
1408-
)
1409-
);
1410-
1411-
Ok(())
1412-
}
1413-
1414-
#[test]
1415-
fn test_round_robin_partitioning_compatible_with() {
1416-
let eq_properties = EquivalenceProperties::new(Arc::new(Schema::empty()));
1417-
1418-
assert!(
1419-
Partitioning::RoundRobinBatch(1)
1420-
.compatible_with(&Partitioning::RoundRobinBatch(1), &eq_properties)
1421-
);
1422-
assert!(
1423-
!Partitioning::RoundRobinBatch(2)
1424-
.compatible_with(&Partitioning::RoundRobinBatch(2), &eq_properties)
1425-
);
1426-
assert!(
1427-
Partitioning::RoundRobinBatch(1)
1428-
.compatible_with(&Partitioning::UnknownPartitioning(1), &eq_properties)
1429-
);
1430-
assert!(
1431-
!Partitioning::RoundRobinBatch(2)
1432-
.compatible_with(&Partitioning::UnknownPartitioning(2), &eq_properties)
1433-
);
1434-
}
1435-
1436-
#[test]
1437-
fn test_unknown_partitioning_compatible_with() {
1438-
let eq_properties = EquivalenceProperties::new(Arc::new(Schema::empty()));
1439-
1440-
assert!(
1441-
Partitioning::UnknownPartitioning(1)
1442-
.compatible_with(&Partitioning::UnknownPartitioning(1), &eq_properties)
1443-
);
1444-
assert!(
1445-
!Partitioning::UnknownPartitioning(2)
1446-
.compatible_with(&Partitioning::UnknownPartitioning(2), &eq_properties)
1447-
);
1448-
assert!(
1449-
Partitioning::UnknownPartitioning(1)
1450-
.compatible_with(&Partitioning::RoundRobinBatch(1), &eq_properties)
1451-
);
1452-
assert!(
1453-
!Partitioning::UnknownPartitioning(2)
1454-
.compatible_with(&Partitioning::RoundRobinBatch(2), &eq_properties)
1455-
);
1456-
}
1457-
14581227
#[test]
14591228
fn test_multi_partition_range_does_not_satisfy_hash_distribution() -> Result<()> {
14601229
let fixture = PartitioningTestFixture::int64(&["a", "b"])?;

0 commit comments

Comments
 (0)