diff --git a/src/sql/src/plan/lowering/variadic_left.rs b/src/sql/src/plan/lowering/variadic_left.rs index 72e3df39c3507..a36d14967605f 100644 --- a/src/sql/src/plan/lowering/variadic_left.rs +++ b/src/sql/src/plan/lowering/variadic_left.rs @@ -196,8 +196,9 @@ pub(crate) fn attempt_left_join_magic( inc_metrics("voj_5"); return Ok(None); } - // Only columns not from the outer scope introduce bindings. - if left >= oa { + // Only columns not from the outer scope introduce bindings (`oa <= left`) + // And `left` needs to be a column in the left relation (`left < oa + ba`) + if oa <= left && left < oa + ba { if let Some(bound) = bound_input { // If left references come from different inputs, bail out. if bound_to[left] != bound { diff --git a/test/sqllogictest/variadic_outer_join.slt b/test/sqllogictest/variadic_outer_join.slt new file mode 100644 index 0000000000000..ad89cedc5f36b --- /dev/null +++ b/test/sqllogictest/variadic_outer_join.slt @@ -0,0 +1,200 @@ +# Copyright Materialize, Inc. and contributors. All rights reserved. +# +# Use of this software is governed by the Business Source License +# included in the LICENSE file at the root of this repository. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0. + +mode cockroach + +statement ok +CREATE TABLE a (k int) + +statement ok +CREATE TABLE b (x int, y int, k int) + +statement ok +CREATE TABLE c (k int) + +statement ok +INSERT INTO a VALUES (1) + +# Only (5, 5, 100) satisfies the b.x = b.y filter. +statement ok +INSERT INTO b VALUES (5, 5, 100), (3, 7, 200) + +statement ok +INSERT INTO c VALUES (100) + +# Baseline: with the variadic LEFT JOIN lowering disabled, the generic +# outer-join lowering classifies `b.x = b.y` as a right-local filter and +# produces the correct single-row result. +simple conn=mz_system,user=mz_system +ALTER SYSTEM SET enable_variadic_left_join_lowering TO false +---- +COMPLETE 0 + +query T multiline +EXPLAIN OPTIMIZED PLAN FOR +SELECT a.k, b.x, b.y, b.k, c.k +FROM (a LEFT JOIN b ON b.x = b.y) LEFT JOIN c ON b.k = c.k +---- +Explained Query: + With + cte l0 = + CrossJoin type=differential + ArrangeBy keys=[[]] + ReadStorage materialize.public.a + ArrangeBy keys=[[]] + Filter (#0 = #1) + ReadStorage materialize.public.b + cte l1 = + Union + Get l0 + Project (#0, #2..=#4) + Map (null, null, null) + Join on=(#0 = #1) type=differential + ArrangeBy keys=[[#0]] + Union + Negate + Distinct project=[#0] + Project (#0) + Get l0 + Distinct project=[#0] + ReadStorage materialize.public.a + ArrangeBy keys=[[#0]] + ReadStorage materialize.public.a + cte l2 = + ArrangeBy keys=[[#3{k}]] + Filter (#3{k}) IS NOT NULL + Get l1 + cte l3 = + Project (#0..=#3) + Join on=(#3{k} = #4{k}) type=differential + Get l2 + ArrangeBy keys=[[#0{k}]] + Filter (#0{k}) IS NOT NULL + ReadStorage materialize.public.c + Return + Union + Map (null) + Union + Negate + Project (#0..=#3) + Join on=(#3{k} = #4) type=differential + Get l2 + ArrangeBy keys=[[#0]] + Distinct project=[#0] + Project (#3) + Get l3 + Get l1 + Project (#0..=#3, #3) + Get l3 + +Source materialize.public.a +Source materialize.public.b + filter=((#0 = #1)) +Source materialize.public.c + filter=((#0{k}) IS NOT NULL) + +Target cluster: quickstart + +EOF + +query IIIII rowsort +SELECT a.k, b.x, b.y, b.k, c.k +FROM (a LEFT JOIN b ON b.x = b.y) LEFT JOIN c ON b.k = c.k +---- +1 5 5 100 100 + +# Re-enable the variadic lowering (the system default) and re-run the +# same query. The expected result is unchanged. +# But we have observed a buggy extra NULL-enriched row (in comments below) + +simple conn=mz_system,user=mz_system +ALTER SYSTEM SET enable_variadic_left_join_lowering TO true +---- +COMPLETE 0 + +query T multiline +EXPLAIN OPTIMIZED PLAN FOR +SELECT a.k, b.x, b.y, b.k, c.k +FROM (a LEFT JOIN b ON b.x = b.y) LEFT JOIN c ON b.k = c.k +---- +Explained Query: + With + cte l0 = + CrossJoin type=differential + ArrangeBy keys=[[]] + ReadStorage materialize.public.a + ArrangeBy keys=[[]] + Filter (#0 = #1) + ReadStorage materialize.public.b + cte l1 = + Union + Get l0 + Project (#0, #2..=#4) + Map (null, null, null) + Join on=(#0 = #1) type=differential + ArrangeBy keys=[[#0]] + Union + Negate + Distinct project=[#0] + Project (#0) + Get l0 + Distinct project=[#0] + ReadStorage materialize.public.a + ArrangeBy keys=[[#0]] + ReadStorage materialize.public.a + cte l2 = + ArrangeBy keys=[[#3{k}]] + Filter (#3{k}) IS NOT NULL + Get l1 + cte l3 = + Project (#0..=#3) + Join on=(#3{k} = #4{k}) type=differential + Get l2 + ArrangeBy keys=[[#0{k}]] + Filter (#0{k}) IS NOT NULL + ReadStorage materialize.public.c + Return + Union + Map (null) + Union + Negate + Project (#0..=#3) + Join on=(#3{k} = #4) type=differential + Get l2 + ArrangeBy keys=[[#0]] + Distinct project=[#0] + Project (#3) + Get l3 + Get l1 + Project (#0..=#3, #3) + Get l3 + +Source materialize.public.a +Source materialize.public.b + filter=((#0 = #1)) +Source materialize.public.c + filter=((#0{k}) IS NOT NULL) + +Target cluster: quickstart + +EOF + +query IIIII rowsort +SELECT a.k, b.x, b.y, b.k, c.k +FROM (a LEFT JOIN b ON b.x = b.y) LEFT JOIN c ON b.k = c.k +---- +1 5 5 100 100 + +# 1 NULL NULL NULL NULL + +query IIII rowsort +SELECT a.k, b.k, b.x, c.k +FROM (a LEFT JOIN b ON a.k = b.k AND b.x = b.y) LEFT JOIN c ON a.k = c.k +---- +1 NULL NULL NULL