Commit 1e836e6d authored by craig[bot]'s avatar craig[bot]

Merge #49788

49788: opt: add rules to eliminate a do-nothing join under a Project r=DrewKimball a=DrewKimball

Previously, the optimizer couldn't eliminate a join in the input of a
Project when the removal would have no effect on the output of the
Project operator.

This patch adds rules to replace a join with one of its input relations
when the following conditions are met:

1. The Project doesn't use any columns from the "discarded" input.
2. The Join doesn't eliminate or duplicate rows from the "preserved" input.

Fixes #49149

Release note (sql change): The optimizer can now remove an unnecessary
join from the input of a Project operator.
Co-authored-by: default avatarDrew Kimball <[email protected]>
parents dfb6e1be 6bf047df
......@@ -922,36 +922,36 @@ NULL NULL NULL 2 2 2
# Regression test for #20858.
statement ok
CREATE TABLE l (a INT PRIMARY KEY)
CREATE TABLE l (a INT PRIMARY KEY, b1 INT)
statement ok
CREATE TABLE r (a INT PRIMARY KEY)
CREATE TABLE r (a INT PRIMARY KEY, b2 INT)
statement ok
INSERT INTO l VALUES (1), (2), (3)
INSERT INTO l VALUES (1, 1), (2, 1), (3, 1)
statement ok
INSERT INTO r VALUES (2), (3), (4)
INSERT INTO r VALUES (2, 1), (3, 1), (4, 1)
query I
query III
SELECT * FROM l LEFT OUTER JOIN r USING(a) WHERE a = 1
----
1
1 1 NULL
query I
query III
SELECT * FROM l LEFT OUTER JOIN r USING(a) WHERE a = 2
----
2
2 1 1
query I
query III
SELECT * FROM l RIGHT OUTER JOIN r USING(a) WHERE a = 3
----
3
3 1 1
query I
query III
SELECT * FROM l RIGHT OUTER JOIN r USING(a) WHERE a = 4
----
4
4 NULL 1
# Regression tests for mixed-type equality columns (#22514).
......
......@@ -126,7 +126,9 @@ ALTER TABLE data INJECT STATISTICS '[
# Ensure lookup join performs properly on input that has more than 100 rows.
query I
SELECT count(*) FROM (SELECT * FROM data WHERE c = 1) AS l NATURAL JOIN data AS r
SELECT count(*)
FROM (SELECT * FROM data WHERE c = 1) AS l
NATURAL JOIN (SELECT * FROM data WHERE c > 0) AS r
----
1000
......
......@@ -6,7 +6,7 @@ statement ok
SET vectorize='201auto'; SET vectorize_row_count_threshold=0
statement ok
CREATE TABLE t (k INT PRIMARY KEY)
CREATE TABLE t (k INT NOT NULL)
statement ok
INSERT INTO t VALUES (1), (2), (3)
......@@ -14,7 +14,7 @@ INSERT INTO t VALUES (1), (2), (3)
query T
SELECT url FROM [EXPLAIN ANALYZE SELECT t1.k FROM t AS t1, t AS t2 WHERE t1.k = t2.k]
----
https://cockroachdb.github.io/distsqlplan/decode.html#eJzMkl9r2zAUxd_3KS73qWVaajtvgkLC5rGMNOniwP4UP6j2XSqqWJ50NVqCv_uwFVhTupVsL32Tjs5PvudYO_Q_DEos8nn-dg3BGXi_Wl7AVf7lcj6dLWC6mM6_fsvh5N2sWBef5qewt3I6uo1ehmkBnIr9IoPPH_JVHg3nwNnotkSBja1pobbkUV5hiqXA1tmKvLeul3aDYVbfoUwE6qYN3MulwMo6QrlD1mwIJa7VtaEVqZrcWYICa2KlzXBt6_RWufsJo8CiVY2X8AYFXiuubsiDDdwGltBTHFrzQBqjQE-GKtY_Nd9LSEZJb_OsjAHWW5KQeCw7gRHZD-dZbQhl2ol_C5C-kADZHwP8njs01tXkqD6YuezJ5yxPtHBBbkMfrW7InWWHLRj6zieT9PXpudObm7hEgcs-5yT9vz7ojqrA2jbPdzI-5qeuyLe28fS4mydvTvpCqN5QLNjb4Cq6dLYaPhO3y4EbhJo8x9MsbmbNcDS8uodwegScPYazv8LjAzjpyu7VrwAAAP__8eFe4Q==
https://cockroachdb.github.io/distsqlplan/decode.html#eJzEkl9v0zAUxd_5FFf3aZPMFqdvlia1QNAKpR1NJf5MefDqS2ctiYN9I1ZV_e4oztBotoHGC2_28e8451xnh-F7iQrzbJa9XkHrS3i7XHyAy-zzxWwyncNkPpl9-ZrB0Ztpvso_zo7hDmV5ctOzDJMcWIq7RQqfzrNl1gNnwOnJTYECa2dorisKqC5RYiGw8W5NITjfSbsITM0tqkSgrZuWO7kQuHaeUO2QLZeEClf6qqQlaUP-NEGBhljbMl7beFtpvx0zCswbXQcFL1HgomUFY4kCvfsRwJM2CkYoMLAuS2BbkYIkoMCrLdMvQEoJr7DYC3Qt38cJrDeESu7Fv0WW_y1y-mTk-6Rt7bwhT-YgZdE5_4Y80vtch-t3ztbkT9PD2iV946OxPD7zdnMdVwelu2MYNI_ag_rRP0R78QEb2HkyEKwhBZFBgZW-hYoq57fQBjIK0gTe26enOHrOwy8pNK4ONJzmozcn3QjJbKh_kuBav6YL79bxM_12EX1RMBS4P037zbSOR_HP_N0sn2FOh-b0j-bRgTnZF_sXPwMAAP__4NthfA==
statement ok
RESET vectorize; RESET vectorize_row_count_threshold
......@@ -1205,89 +1205,95 @@ render · · (x, y, u, v) ·
# join.
statement ok
CREATE TABLE l (a INT PRIMARY KEY)
CREATE TABLE l (a INT PRIMARY KEY, b1 INT, FAMILY (a))
statement ok
CREATE TABLE r (a INT PRIMARY KEY)
CREATE TABLE r (a INT PRIMARY KEY, b2 INT, FAMILY (a))
query TTTTT
EXPLAIN (VERBOSE) SELECT * FROM l LEFT OUTER JOIN r USING(a) WHERE a = 3;
----
· distributed false · ·
· vectorized true · ·
render · · (a) ·
│ render 0 a · ·
└── merge-join · · (a, a) ·
│ type left outer · ·
│ equality (a) = (a) · ·
│ left cols are key · · ·
│ right cols are key · · ·
│ mergeJoinOrder +"(a=a)" · ·
├── scan · · (a) ·
│ table [email protected] · ·
│ spans /3-/3/# · ·
└── scan · · (a) ·
· table [email protected] · ·
· spans /3-/3/# · ·
· distributed false · ·
· vectorized true · ·
render · · (a, b1, b2) ·
│ render 0 a · ·
│ render 1 b1 · ·
│ render 2 b2 · ·
└── merge-join · · (a, b1, a, b2) ·
│ type left outer · ·
│ equality (a) = (a) · ·
│ left cols are key · · ·
│ right cols are key · · ·
│ mergeJoinOrder +"(a=a)" · ·
├── scan · · (a, b1) ·
│ table [email protected] · ·
│ spans /3-/3/# · ·
└── scan · · (a, b2) ·
· table [email protected] · ·
· spans /3-/3/# · ·
query TTTTT
EXPLAIN (VERBOSE) SELECT * FROM l LEFT OUTER JOIN r ON l.a = r.a WHERE l.a = 3;
----
· distributed false · ·
· vectorized true · ·
merge-join · · (a, a) ·
│ type left outer · ·
│ equality (a) = (a) · ·
│ left cols are key · · ·
│ right cols are key · · ·
│ mergeJoinOrder +"(a=a)" · ·
├── scan · · (a) ·
│ table [email protected] · ·
│ spans /3-/3/# · ·
└── scan · · (a) ·
· table [email protected] · ·
· spans /3-/3/# · ·
· distributed false · ·
· vectorized true · ·
merge-join · · (a, b1, a, b2) ·
│ type left outer · ·
│ equality (a) = (a) · ·
│ left cols are key · · ·
│ right cols are key · · ·
│ mergeJoinOrder +"(a=a)" · ·
├── scan · · (a, b1) ·
│ table [email protected] · ·
│ spans /3-/3/# · ·
└── scan · · (a, b2) ·
· table [email protected] · ·
· spans /3-/3/# · ·
query TTTTT
EXPLAIN (VERBOSE) SELECT * FROM l RIGHT OUTER JOIN r USING(a) WHERE a = 3;
----
· distributed false · ·
· vectorized true · ·
render · · (a) ·
│ render 0 a · ·
└── merge-join · · (a, a) ·
│ type left outer · ·
│ equality (a) = (a) · ·
│ left cols are key · · ·
│ right cols are key · · ·
│ mergeJoinOrder +"(a=a)" · ·
├── scan · · (a) ·
│ table [email protected] · ·
│ spans /3-/3/# · ·
└── scan · · (a) ·
· table [email protected] · ·
· spans /3-/3/# · ·
· distributed false · ·
· vectorized true · ·
render · · (a, b1, b2) ·
│ render 0 a · ·
│ render 1 b1 · ·
│ render 2 b2 · ·
└── merge-join · · (a, b2, a, b1) ·
│ type left outer · ·
│ equality (a) = (a) · ·
│ left cols are key · · ·
│ right cols are key · · ·
│ mergeJoinOrder +"(a=a)" · ·
├── scan · · (a, b2) ·
│ table [email protected] · ·
│ spans /3-/3/# · ·
└── scan · · (a, b1) ·
· table [email protected] · ·
· spans /3-/3/# · ·
query TTTTT
EXPLAIN (VERBOSE) SELECT * FROM l RIGHT OUTER JOIN r ON l.a = r.a WHERE r.a = 3;
----
· distributed false · ·
· vectorized true · ·
render · · (a, a) ·
│ render 0 a · ·
│ render 1 a · ·
└── merge-join · · (a, a) ·
│ type left outer · ·
│ equality (a) = (a) · ·
│ left cols are key · · ·
│ right cols are key · · ·
│ mergeJoinOrder +"(a=a)" · ·
├── scan · · (a) ·
│ table [email protected] · ·
│ spans /3-/3/# · ·
└── scan · · (a) ·
· table [email protected] · ·
· spans /3-/3/# · ·
· distributed false · ·
· vectorized true · ·
render · · (a, b1, a, b2) ·
│ render 0 a · ·
│ render 1 b1 · ·
│ render 2 a · ·
│ render 3 b2 · ·
└── merge-join · · (a, b2, a, b1) ·
│ type left outer · ·
│ equality (a) = (a) · ·
│ left cols are key · · ·
│ right cols are key · · ·
│ mergeJoinOrder +"(a=a)" · ·
├── scan · · (a, b2) ·
│ table [email protected] · ·
│ spans /3-/3/# · ·
└── scan · · (a, b1) ·
· table [email protected] · ·
· spans /3-/3/# · ·
# Regression tests for #21243
statement ok
......
......@@ -158,7 +158,10 @@ NULL /1 {1} 1
/9 NULL {5} 5
query TTTTT
EXPLAIN (VERBOSE) SELECT * FROM (SELECT * FROM data WHERE c = 1) AS l NATURAL JOIN data AS r
EXPLAIN (VERBOSE)
SELECT *
FROM (SELECT * FROM data WHERE c = 1) AS l
NATURAL JOIN (SELECT * FROM data WHERE c > 0) AS r
----
· distributed true · ·
· vectorized true · ·
......@@ -173,15 +176,19 @@ render · · (a, b, c
│ equality (a, b, c, d) = (a, b, c, d) · ·
│ equality cols are key · · ·
│ parallel · · ·
│ pred @7 > 0 · ·
└── scan · · (a, b, c, d) ·
· table [email protected] · ·
· spans FULL SCAN · ·
· filter c = 1 · ·
query T
SELECT url FROM [EXPLAIN (DISTSQL) SELECT * FROM (SELECT * FROM data WHERE c = 1) AS l NATURAL JOIN data AS r]
SELECT url FROM [EXPLAIN (DISTSQL)
SELECT *
FROM (SELECT * FROM data WHERE c = 1) AS l
NATURAL JOIN (SELECT * FROM data WHERE c > 0) AS r]
----
https://cockroachdb.github.io/distsqlplan/decode.html#eJzElNFq2zAUhu_3FIdz1Q4ZR7bTpoKBw5Yyl8zp4owNii-8-NB5cy1PlmEj5N2H40KTkMqGhOxSyvn5v3zHaIXV7xwFRpPp5P0CapXD7Xz2CR4m3-6n4yCEiw9BtIg-Ty_heeRtO3Cxe0wTncDXj5P5BJbwDvgljCPIIRwvvszHU7ibBWE7M45AxciwkCmFyRNVKB6QI0MHGbrI0EOGQ4wZlkouqaqkakZWm0CQ_kExYJgVZa2b65jhUipCsUKd6ZxQ4CL5ntOckpSUPUCGKekkyzc1pcqeEvXXb0CQYVQmRSXAsjkkRQocpP5BChneZrkmJcB3m78ihAjCxQjjNUNZ65fmSiePhIKvWX-6O5kVz3BDI9xUyl91CT9lVoAsBPic-Q7zXeY3gma13r16Dc55Fe6FqS6kSklRugMUrw_gh9KSpc0He5OHu92dbt5_bbzP2mxu2c4JF9fBt7W4q_Mvzukvz-klz7Fs94TyOvi25F2fX57bX57bS55r2d4J5XXwbckbnV-e11-e10ueZ9nDE8rr4NuSd_N_39sDcHOqSllU1Os1HTTvMaWP1D7elazVku6VXG5q2uNsk9tcpFTp9lfeHoKi_akB3A5zY9jZCfP9sGNu7qh2jWnPHPaO4R4aw1fm5qtjmq-N4ZG5eXRM8415V4OOz8T8ke13x-s3_wIAAP__KNhJ9Q==
https://cockroachdb.github.io/distsqlplan/decode.html#eJzMldFvmzAQxt_3V5zuqZ2MwEDa1NIkoi3VUmWkSzJtUscDC6eOjWJmjLQpyv8-Ad2aRK1BSh7yePg-vs-_s-w1lr8yFLgYT8dvl1CpDK7nsw9wN_5yOx1NQjh7N1ksFx-n5_DY8rptONstk1jH8Pn9eD6GFbwBfg6jBWQQjpaf5qMp3MzqXxkkXyvH8QicRqciZJjLhML4gUoUd8iRoYsMPWToI8MBRgwLJVdUllLVLetGMEl-o3AYpnlR6fpzxHAlFaFYo051RihwGX_LaE5xQsp2kGFCOk6zxqZQ6UOs_gR1NGS4KOK8FGDZHOI8AQ5SfyeFDK_TTJMSEHj1XoUQk3A5xGjDUFb6ybnU8T2h4BvWP92NTPPHcANjuKmUP6sCfsg0B5kLCDgLXBZ4LKgBzUIILv9T_ZeQ4azSu60vhXZfDP2UtcqlSkhRshM02jyzrVBasrC5s9f5vLe34837j5P3GafNLds94kA78m0N9OJ0Bur2h-r2gupatndEqB35tqBeng5Urz9UrxdUz7L9I0LtyLcFdXg6UP3-UP1eUH3LHhwRake-LahXpwO14xGaU1nIvKRet7VT3_eU3FP7OJSyUiu6VXLV2LTlrNE1HxIqdbvK22KSt0t1wG0xN4rdHTHfF7tm5w5rz6j2zWL_kNwDo_jC7HxxiPOlUTw0Ow8Pcb4yz8rpOCbmQ7bvHW1e_Q0AAP__Xs1ugQ==
statement ok
CREATE TABLE books (title STRING, edition INT, shelf INT, PRIMARY KEY (title, edition))
......
......@@ -1144,7 +1144,7 @@ CREATE TABLE table1 (
----
# Regression test for #38091.
norm
norm disable=EliminateJoinUnderProjectLeft
SELECT (
SELECT 1
FROM table1
......
......@@ -290,7 +290,7 @@ func (c *CustomFuncs) HasOuterCols(input opt.Expr) bool {
}
// IsCorrelated returns true if any variable in the source expression references
// a column from the destination expression. For example:
// a column from the given set of output columns. For example:
// (InnerJoin
// (Scan a)
// (Scan b)
......@@ -300,8 +300,8 @@ func (c *CustomFuncs) HasOuterCols(input opt.Expr) bool {
// The $item expression is correlated with the (Scan a) expression because it
// references one of its columns. But the $item expression is not correlated
// with the (Scan b) expression.
func (c *CustomFuncs) IsCorrelated(src, dst memo.RelExpr) bool {
return src.Relational().OuterCols.Intersects(dst.Relational().OutputCols)
func (c *CustomFuncs) IsCorrelated(src memo.RelExpr, cols opt.ColSet) bool {
return src.Relational().OuterCols.Intersects(cols)
}
// IsBoundBy returns true if all outer references in the source expression are
......
......@@ -38,7 +38,7 @@
[DecorrelateJoin, Normalize]
(JoinApply
$left:*
$right:* & ^(IsCorrelated $right $left)
$right:* & ^(IsCorrelated $right (OutputCols $left))
$on:*
$private:*
)
......
......@@ -2,6 +2,46 @@
# project.opt contains normalization rules for the Project operator.
# =============================================================================
# EliminateJoinUnderProjectLeft replaces an InnerJoin or LeftJoin with its left
# input when:
# 1. The project doesn't use columns from the join's right input.
# 2. The join does not duplicate any left rows.
# 3. The join does not filter any left rows.
#
# Note: EliminateJoinUnderProjectLeft should stay above EliminateProject so that
# it has a chance to fire before the Project can be removed.
[EliminateJoinUnderProjectLeft, Normalize]
(Project
$join:(InnerJoin | LeftJoin $left:* $right:*) &
(JoinDoesNotDuplicateLeftRows $join) &
(JoinPreservesLeftRows $join)
$projections:* &
^(AreProjectionsCorrelated
$projections
$rightCols:(OutputCols $right)
)
$passthrough:* & ^(ColsIntersect $passthrough $rightCols)
)
=>
(Project $left $projections $passthrough)
# EliminateJoinUnderProjectRight mirrors EliminateJoinUnderProjectLeft, except
# that it only matches InnerJoins.
[EliminateJoinUnderProjectRight, Normalize]
(Project
$join:(InnerJoin $left:* $right:*) &
(JoinDoesNotDuplicateRightRows $join) &
(JoinPreservesRightRows $join)
$projections:* &
^(AreProjectionsCorrelated
$projections
$leftCols:(OutputCols $left)
)
$passthrough:* & ^(ColsIntersect $passthrough $leftCols)
)
=>
(Project $right $projections $passthrough)
# EliminateProject discards a Project operator which is not adding or removing
# columns.
[EliminateProject, Normalize]
......
......@@ -1520,7 +1520,7 @@ WHERE EXISTS(
)=i
)
----
semi-join-apply
select
├── columns: v1:1!null
├── cardinality: [0 - 2]
├── values
......@@ -1528,45 +1528,31 @@ semi-join-apply
│ ├── cardinality: [2 - 2]
│ ├── (1,)
│ └── (2,)
├── left-join (hash)
│ ├── columns: k:2!null i:3!null x:7!null y:8!null v1:11
│ ├── outer: (1)
│ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
│ ├── key: (7)
│ ├── fd: (2)-->(3), (7)-->(8,11), (2)==(7), (7)==(2), (3)==(8), (8)==(3)
│ ├── inner-join (hash)
│ │ ├── columns: k:2!null i:3!null x:7!null y:8!null
│ │ ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
│ │ ├── key: (7)
│ │ ├── fd: (2)-->(3), (7)-->(8), (2)==(7), (7)==(2), (3)==(8), (8)==(3)
│ │ ├── scan a
│ │ │ ├── columns: k:2!null i:3
│ │ │ ├── key: (2)
│ │ │ └── fd: (2)-->(3)
│ │ ├── scan xy
│ │ │ ├── columns: x:7!null y:8
│ │ │ ├── key: (7)
│ │ │ └── fd: (7)-->(8)
│ │ └── filters
│ │ ├── x:7 = k:2 [outer=(2,7), constraints=(/2: (/NULL - ]; /7: (/NULL - ]), fd=(2)==(7), (7)==(2)]
│ │ └── i:3 = y:8 [outer=(3,8), constraints=(/3: (/NULL - ]; /8: (/NULL - ]), fd=(3)==(8), (8)==(3)]
│ ├── project
│ │ ├── columns: v1:11
│ │ ├── outer: (1)
│ │ ├── cardinality: [0 - 1]
│ │ ├── key: ()
│ │ ├── fd: ()-->(11)
│ │ ├── limit
│ │ │ ├── cardinality: [0 - 1]
│ │ │ ├── key: ()
│ │ │ ├── scan uv
│ │ │ │ └── limit hint: 1.00
│ │ │ └── 1
│ │ └── projections
│ │ └── column1:1 [as=v1:11, outer=(1)]
│ └── filters
│ └── x:7 = v1:11 [outer=(7,11), constraints=(/7: (/NULL - ]; /11: (/NULL - ]), fd=(7)==(11), (11)==(7)]
└── filters (true)
└── filters
└── exists [subquery]
└── limit
├── columns: k:2!null i:3!null x:7!null y:8!null
├── cardinality: [0 - 1]
├── key: ()
├── fd: ()-->(2,3,7,8)
├── inner-join (hash)
│ ├── columns: k:2!null i:3!null x:7!null y:8!null
│ ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
│ ├── key: (7)
│ ├── fd: (2)-->(3), (7)-->(8), (2)==(7), (7)==(2), (3)==(8), (8)==(3)
│ ├── limit hint: 1.00
│ ├── scan a
│ │ ├── columns: k:2!null i:3
│ │ ├── key: (2)
│ │ └── fd: (2)-->(3)
│ ├── scan xy
│ │ ├── columns: x:7!null y:8
│ │ ├── key: (7)
│ │ └── fd: (7)-->(8)
│ └── filters
│ ├── x:7 = k:2 [outer=(2,7), constraints=(/2: (/NULL - ]; /7: (/NULL - ]), fd=(2)==(7), (7)==(2)]
│ └── i:3 = y:8 [outer=(3,8), constraints=(/3: (/NULL - ]; /8: (/NULL - ]), fd=(3)==(8), (8)==(3)]
└── 1
norm expect=TryDecorrelateInnerLeftJoin
SELECT *
......
......@@ -113,9 +113,9 @@ group-by
# EliminateJoinUnderGroupByLeft
# --------------------------------------------------
# Simple DistinctOn case with a LeftJoin on an equality between primary keys.
# Simple DistinctOn case with a LeftJoin.
norm expect=EliminateJoinUnderGroupByLeft
SELECT DISTINCT ON (x) x, y FROM xy LEFT JOIN fks ON x=k
SELECT DISTINCT ON (x) x, y FROM xy LEFT JOIN fks ON x=v
----
scan xy
├── columns: x:1!null y:2
......@@ -125,7 +125,7 @@ scan xy
# RightJoin case. The RightJoin is turned into a LeftJoin, so
# EliminateJoinUnderGroupByLeft matches it.
norm expect=EliminateJoinUnderGroupByLeft
SELECT DISTINCT ON (x) x, y FROM fks RIGHT JOIN xy ON x=k
SELECT DISTINCT ON (x) x, y FROM fks RIGHT JOIN xy ON x=v
----
scan xy
├── columns: x:5!null y:6
......@@ -154,7 +154,7 @@ group-by
# Case with ScalarGroupBy with a sum aggregate that doesn't ignore duplicates.
# The join can be eliminated because r1 is a foreign key referencing x, which
# implies that the rows of fks are not being duplicated by the join.
norm expect=EliminateJoinUnderGroupByLeft
norm expect=EliminateJoinUnderGroupByLeft disable=EliminateJoinUnderProjectLeft
SELECT sum(k) FROM fks LEFT JOIN xy ON x=r1
----
scalar-group-by
......@@ -188,7 +188,7 @@ group-by
└── y:2
# LeftJoin case with a not-null foreign key equality filter and a sum aggregate.
norm expect=EliminateJoinUnderGroupByLeft
norm expect=EliminateJoinUnderGroupByLeft disable=EliminateJoinUnderProjectLeft
SELECT k, sum(r1) FROM fks LEFT JOIN xy ON x=r1 GROUP BY k
----
group-by
......@@ -207,7 +207,7 @@ group-by
# The LeftJoin guarantees that all left rows will be included in the output, and
# since k is a key column, no rows from xy will be duplicated. Therefore the sum
# aggregate will not be affected by join removal.
norm expect=EliminateJoinUnderGroupByLeft
norm expect=EliminateJoinUnderGroupByLeft disable=EliminateJoinUnderProjectLeft
SELECT x, sum(y) FROM xy LEFT JOIN fks ON x=k GROUP BY x
----
group-by
......@@ -227,7 +227,7 @@ group-by
# since r2 is a foreign key referencing x, it is guaranteed that no left rows
# will be matched more than once. Therefore, the sum aggregate will be
# unaffected by join removal.
norm expect=EliminateJoinUnderGroupByLeft
norm expect=EliminateJoinUnderGroupByLeft disable=EliminateJoinUnderProjectLeft
SELECT k, sum(r1) FROM fks LEFT JOIN xy ON x=r2 GROUP BY k
----
group-by
......@@ -245,7 +245,7 @@ group-by
# InnerJoin case. Because r1 is a non-null foreign key that references x, the
# join output is guaranteed to include every left row exactly once.
norm expect=EliminateJoinUnderGroupByLeft
norm expect=EliminateJoinUnderGroupByLeft disable=EliminateJoinUnderProjectLeft
SELECT k, sum(r1) FROM fks INNER JOIN xy ON x=r1 GROUP BY k
----
group-by
......@@ -263,7 +263,7 @@ group-by
# Case with an ordering on left columns.
norm expect=EliminateJoinUnderGroupByLeft
SELECT max(y) FROM xy LEFT JOIN fks ON x = k GROUP BY x ORDER BY x
SELECT max(y) FROM xy LEFT JOIN fks ON True GROUP BY x ORDER BY x
----
group-by
├── columns: max:7 [hidden: x:1!null]
......@@ -443,7 +443,7 @@ group-by
# --------------------------------------------------
# InnerJoin case.
norm expect=EliminateJoinUnderGroupByRight
norm expect=EliminateJoinUnderGroupByRight disable=EliminateJoinUnderProjectRight
SELECT k, sum(r1) FROM xy INNER JOIN fks ON x = r1 GROUP BY k
----
group-by
......
......@@ -2300,29 +2300,39 @@ values
# EliminateJoinNoColsLeft
# --------------------------------------------------
norm expect=EliminateJoinNoColsLeft
SELECT s FROM (VALUES (1, 2)) INNER JOIN a ON s='foo'
SELECT unnest(ARRAY[[1,2,3],[4,5]])
----
select
├── columns: s:6!null
├── fd: ()-->(6)
├── scan a
│ └── columns: s:6
└── filters
└── s:6 = 'foo' [outer=(6), constraints=(/6: [/'foo' - /'foo']; tight), fd=()-->(6)]
values
├── columns: unnest:1!null
├── cardinality: [2 - 2]
├── (ARRAY[1,2,3],)
└── (ARRAY[4,5],)
# --------------------------------------------------
# EliminateJoinNoColsRight
# --------------------------------------------------
norm expect=EliminateJoinNoColsRight
SELECT s FROM a INNER JOIN (SELECT count(*) FROM b) ON s='foo'
SELECT * FROM xy WHERE EXISTS(SELECT generate_series(x, 10))
----
select
├── columns: s:4!null
├── fd: ()-->(4)
├── scan a
│ └── columns: s:4
└── filters
└── s:4 = 'foo' [outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]
group-by
├── columns: x:1!null y:2
├── grouping columns: x:1!null
├── immutable, side-effects
├── key: (1)
├── fd: (1)-->(2)
├── project-set
│ ├── columns: x:1!null y:2 generate_series:3
│ ├── immutable, side-effects
│ ├── fd: (1)-->(2)
│ ├── scan xy
│ │ ├── columns: x:1!null y:2
│ │ ├── key: (1)
│ │ └── fd: (1)-->(2)
│ └── zip
│ └── generate_series(x:1, 10) [outer=(1), immutable, side-effects]
└── aggregations
└── const-agg [as=y:2, outer=(2)]
└── y:2
# --------------------------------------------------
# HoistJoinProjectRight
......
......@@ -6,6 +6,250 @@ exec-ddl
CREATE TABLE b (x INT PRIMARY KEY, z INT)
----
exec-ddl
CREATE TABLE xy (x INT, y INT, PRIMARY KEY (x, y))
----
exec-ddl
CREATE TABLE fks
(
k INT PRIMARY KEY,
v INT,
v2 INT,
r1 INT NOT NULL REFERENCES a (x),
r2 INT REFERENCES a (x),
UNIQUE INDEX (v, v2)
)
----
# --------------------------------------------------
# EliminateJoinUnderProjectLeft
# --------------------------------------------------
# LeftJoin case with equality of primary keys.
norm expect=EliminateJoinUnderProjectLeft
SELECT b.x, b.z FROM b LEFT JOIN a ON b.x = a.x
----
scan b
├── columns: x:1!null z:2
├── key: (1)
└── fd: (1)-->(2)
# LeftJoin case with not-null foreign key.
norm expect=EliminateJoinUnderProjectLeft
SELECT k, v FROM fks LEFT JOIN a ON r1 = x
----
scan fks
├── columns: k:1!null v:2
├── key: (1)
└── fd: (1)-->(2)
# LeftJoin case with nullable foreign key.
norm expect=EliminateJoinUnderProjectLeft
SELECT k, v FROM fks LEFT JOIN a ON r2 = x
----
scan fks
├── columns: k:1!null v:2
├── key: (1)
└── fd: (1)-->(2)
# InnerJoin case with not-null foreign key (nullable is no-op below).
norm expect=EliminateJoinUnderProjectLeft
SELECT k, v FROM fks INNER JOIN a ON r1 = x
----
scan fks
├── columns: k:1!null v:2
├── key: (1)
└── fd: (1)-->(2)
# InnerJoin case with self join.
norm expect=EliminateJoinUnderProjectLeft
SELECT b.x, b.z FROM b INNER JOIN b AS b1 ON b.x = b1.x
----
scan b
├── columns: x:1!null z:2
├── key: (1)
└── fd: (1)-->(2)
# Case with equality between multicolumn keys.
norm expect=EliminateJoinUnderProjectLeft
SELECT k, v FROM fks LEFT JOIN xy ON v2 = x AND v = y
----
scan fks
├── columns: k:1!null v:2
├── key: (1)
└── fd: (1)-->(2)
# Case with no passthrough columns.
norm expect=EliminateJoinUnderProjectLeft
SELECT 1+b.x FROM b LEFT JOIN a ON b.x = a.x
----
project
├── columns: "?column?":7!null
├── scan b
│ ├── columns: b.x:1!null
│ └── key: (1)
└── projections
└── b.x:1 + 1 [as="?column?":7, outer=(1)]
# Case with no references to the left side.
norm expect=EliminateJoinUnderProjectLeft
SELECT 1 FROM b LEFT JOIN a ON b.x = a.x
----
project
├── columns: "?column?":7!null
├── fd: ()-->(7)
├── scan b
└── projections
└── 1 [as="?column?":7]
# No-op case because the cross join may duplicate left rows.
norm expect-not=EliminateJoinUnderProjectLeft
SELECT 1 FROM b LEFT JOIN a ON True
----
project
├── columns: "?column?":7!null
├── fd: ()-->(7)
├── left-join (cross)
│ ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-more)
│ ├── scan b
│ ├── scan a
│ └── filters (true)
└── projections
└── 1 [as="?column?":7]
# No-op case with a projection that references the right input.
norm expect-not=EliminateJoinUnderProjectLeft
SELECT b.x, b.z, 1+a.x FROM b LEFT JOIN a ON b.x = a.x
----
project
├── columns: x:1!null z:2 "?column?":7
├── key: (1)
├── fd: (1)-->(2,7)
├── left-join (hash)
│ ├── columns: b.x:1!null z:2 a.x:3
│ ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
│ ├── key: (1)
│ ├── fd: (1)-->(2,3)
│ ├── scan b
│ │ ├── columns: b.x:1!null z:2
│ │ ├── key: (1)
│ │ └── fd: (1)-->(2)
│ ├── scan a
│ │ ├── columns: a.x:3!null
│ │ └── key: (3)
│ └── filters
│ └── b.x:1 = a.x:3 [outer=(1,3), constraints=(/1: (/NULL - ]; /3: (/NULL - ]), fd=(1)==(3), (3)==(1)]
└── projections
└── a.x:3 + 1 [as="?column?":7, outer=(3)]
# No-op case because r2 is nullable, and therefore rows may not match despite
# the fact that it is a foreign key.
norm expect-not=EliminateJoinUnderProjectLeft
SELECT k, v FROM fks INNER JOIN a ON r2 = x
----
project
├── columns: k:1!null v:2
├── key: (1)
├── fd: (1)-->(2)
└── inner-join (hash)