Skip to content

Commit 5509608

Browse files
Add unit tests for branches codecov flagged as uncovered
- A correlated NOT IN whose value equality is NOT NULL but whose correlation equality is nullable, checked second, exercises the loop continuing past the first conjunct. - An operand referencing columns from both schemas exercises the conservative fallback in operand_may_be_null directly. Verified each fails when the corresponding code path is broken.
1 parent b0220d8 commit 5509608

1 file changed

Lines changed: 59 additions & 0 deletions

File tree

‎datafusion/optimizer/src/decorrelate_predicate_subquery.rs‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,17 @@ mod tests {
838838
table_scan(Some(name), &schema, None)?.build()
839839
}
840840

841+
/// A scan with one `NOT NULL` column (`a`) and one nullable column
842+
/// (`grp`), for tests that need the two conjuncts of a correlated
843+
/// `NOT IN` join filter to disagree on nullability.
844+
fn mixed_nullability_scan(name: &str) -> Result<LogicalPlan> {
845+
let schema = Schema::new(vec![
846+
Field::new("a", DataType::Int32, false),
847+
Field::new("grp", DataType::Int32, true),
848+
]);
849+
table_scan(Some(name), &schema, None)?.build()
850+
}
851+
841852
fn has_null_aware_left_anti_join(plan: &LogicalPlan) -> bool {
842853
if let LogicalPlan::Join(join) = plan
843854
&& join.join_type == JoinType::LeftAnti
@@ -1639,6 +1650,54 @@ mod tests {
16391650
Ok(())
16401651
}
16411652

1653+
/// `join_keys_may_be_null` walks every equality conjunct rather than
1654+
/// stopping at the first: a correlated `NOT IN` whose value equality is
1655+
/// `NOT NULL` must still come out nullable when the correlation equality,
1656+
/// checked second, is not.
1657+
#[test]
1658+
fn correlated_not_in_is_null_aware_when_only_the_correlation_key_is_nullable()
1659+
-> Result<()> {
1660+
let outer_scan = mixed_nullability_scan("outer_t")?;
1661+
let inner_scan = mixed_nullability_scan("inner_t")?;
1662+
1663+
let subquery = Arc::new(
1664+
LogicalPlanBuilder::from(inner_scan)
1665+
.filter(
1666+
out_ref_col(DataType::Int32, "outer_t.grp").eq(col("inner_t.grp")),
1667+
)?
1668+
.project(vec![col("inner_t.a")])?
1669+
.build()?,
1670+
);
1671+
1672+
let plan = LogicalPlanBuilder::from(outer_scan)
1673+
.filter(not_in_subquery(col("outer_t.a"), subquery))?
1674+
.build()?;
1675+
1676+
let optimized = optimize_with_decorrelate(plan)?;
1677+
assert!(
1678+
has_null_aware_left_anti_join(&optimized),
1679+
"{}",
1680+
optimized.display_indent_schema()
1681+
);
1682+
1683+
Ok(())
1684+
}
1685+
1686+
/// An operand that mixes columns from both sides of the join can't be
1687+
/// attributed to either schema, so `operand_may_be_null` must fall back
1688+
/// to the conservative answer rather than guessing or erroring.
1689+
#[test]
1690+
fn operand_referencing_both_sides_is_treated_as_nullable() -> Result<()> {
1691+
let left_schema = Arc::clone(test_table_scan_with_name("left_t")?.schema());
1692+
let right_schema = Arc::clone(test_table_scan_with_name("right_t")?.schema());
1693+
1694+
let mixed = col("left_t.a").add(col("right_t.a"));
1695+
1696+
assert!(operand_may_be_null(&mixed, &left_schema, &right_schema)?);
1697+
1698+
Ok(())
1699+
}
1700+
16421701
/// The same rewrite must not fire for a correlated subquery: the
16431702
/// correlation predicate is a second equi-join key, and null-aware hash
16441703
/// joins accept only one.

0 commit comments

Comments
 (0)