Skip to content

Commit 5ef34a8

Browse files
committed
Fix the issue that SJE mistakenly omits qual clauses
When the SJE code handles the transfer of qual clauses from the removed relation to the remaining one, it replaces the Vars of the removed relation with the Vars of the remaining relation for each clause, and then reintegrates these clauses into the appropriate restriction or join clause lists, while attempting to avoid duplicates. However, the code compares RestrictInfo->clause to determine if two clauses are duplicates. This is just flat wrong. Two RestrictInfos with the same clause can have different required_relids, incompatible_relids, is_pushed_down, and so on. This can cause qual clauses to be mistakenly omitted, leading to wrong results. This patch fixes it by comparing the entire RestrictInfos not just their clauses ignoring 'rinfo_serial' field (otherwise almost all RestrictInfos will be unique). Making 'rinfo_serial' equal_ignore would break other code. This is why this commit implements our own comparison function for checking the equality of RestrictInfos. Reported-by: Zuming Jiang Bug: #18261 Discussion: https://postgr.es/m/18261-2a75d748c928609b%40postgresql.org Author: Richard Guo
1 parent 43b46aa commit 5ef34a8

File tree

3 files changed

+65
-4
lines changed

3 files changed

+65
-4
lines changed

src/backend/optimizer/plan/analyzejoins.c

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1657,6 +1657,28 @@ update_eclasses(EquivalenceClass *ec, int from, int to)
16571657
ec->ec_relids = replace_relid(ec->ec_relids, from, to);
16581658
}
16591659

1660+
/*
1661+
* "Logically" compares two RestrictInfo's ignoring the 'rinfo_serial' field,
1662+
* which makes almost every RestrictInfo unique. This type of comparison is
1663+
* useful when removing duplicates while moving RestrictInfo's from removed
1664+
* relation to remaining relation during self-join elimination.
1665+
*
1666+
* XXX: In the future, we might remove the 'rinfo_serial' field completely and
1667+
* get rid of this function.
1668+
*/
1669+
static bool
1670+
restrict_infos_logically_equal(RestrictInfo *a, RestrictInfo *b)
1671+
{
1672+
int saved_rinfo_serial = a->rinfo_serial;
1673+
bool result;
1674+
1675+
a->rinfo_serial = b->rinfo_serial;
1676+
result = equal(a, b);
1677+
a->rinfo_serial = saved_rinfo_serial;
1678+
1679+
return result;
1680+
}
1681+
16601682
/*
16611683
* Remove a relation after we have proven that it participates only in an
16621684
* unneeded unique self join.
@@ -1760,7 +1782,7 @@ remove_self_join_rel(PlannerInfo *root, PlanRowMark *kmark, PlanRowMark *rmark,
17601782
if (src == rinfo ||
17611783
(rinfo->parent_ec != NULL
17621784
&& src->parent_ec == rinfo->parent_ec)
1763-
|| equal(rinfo->clause, src->clause))
1785+
|| restrict_infos_logically_equal(rinfo, src))
17641786
{
17651787
is_redundant = true;
17661788
break;
@@ -1788,7 +1810,7 @@ remove_self_join_rel(PlannerInfo *root, PlanRowMark *kmark, PlanRowMark *rmark,
17881810
if (src == rinfo ||
17891811
(rinfo->parent_ec != NULL
17901812
&& src->parent_ec == rinfo->parent_ec)
1791-
|| equal(rinfo->clause, src->clause))
1813+
|| restrict_infos_logically_equal(rinfo, src))
17921814
{
17931815
is_redundant = true;
17941816
break;

src/test/regress/expected/join.out

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6742,7 +6742,7 @@ explain (costs off) select 1 from
67426742
reset join_collapse_limit;
67436743
reset enable_seqscan;
67446744
-- Check that clauses from the join filter list is not lost on the self-join removal
6745-
CREATE TABLE emp1 ( id SERIAL PRIMARY KEY NOT NULL, code int);
6745+
CREATE TABLE emp1 (id SERIAL PRIMARY KEY NOT NULL, code int);
67466746
explain (verbose, costs off)
67476747
SELECT * FROM emp1 e1, emp1 e2 WHERE e1.id = e2.id AND e2.code <> e1.code;
67486748
QUERY PLAN
@@ -6880,6 +6880,30 @@ WHERE t1.id = emp1.id RETURNING emp1.id, emp1.code;
68806880
Filter: (id IS NOT NULL)
68816881
(3 rows)
68826882

6883+
-- Check that SJE does not mistakenly omit qual clauses (bug #18187)
6884+
explain (costs off)
6885+
select 1 from emp1 full join
6886+
(select * from emp1 t1 join
6887+
emp1 t2 join emp1 t3 on t2.id = t3.id
6888+
on true
6889+
where false) s on true
6890+
where false;
6891+
QUERY PLAN
6892+
--------------------------
6893+
Result
6894+
One-Time Filter: false
6895+
(2 rows)
6896+
6897+
select 1 from emp1 full join
6898+
(select * from emp1 t1 join
6899+
emp1 t2 join emp1 t3 on t2.id = t3.id
6900+
on true
6901+
where false) s on true
6902+
where false;
6903+
?column?
6904+
----------
6905+
(0 rows)
6906+
68836907
-- We can remove the join even if we find the join can't duplicate rows and
68846908
-- the base quals of each side are different. In the following case we end up
68856909
-- moving quals over to s1 to make it so it can't match any rows.

src/test/regress/sql/join.sql

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2568,7 +2568,7 @@ reset join_collapse_limit;
25682568
reset enable_seqscan;
25692569

25702570
-- Check that clauses from the join filter list is not lost on the self-join removal
2571-
CREATE TABLE emp1 ( id SERIAL PRIMARY KEY NOT NULL, code int);
2571+
CREATE TABLE emp1 (id SERIAL PRIMARY KEY NOT NULL, code int);
25722572
explain (verbose, costs off)
25732573
SELECT * FROM emp1 e1, emp1 e2 WHERE e1.id = e2.id AND e2.code <> e1.code;
25742574

@@ -2622,6 +2622,21 @@ WITH t1 AS (SELECT * FROM emp1)
26222622
UPDATE emp1 SET code = t1.code + 1 FROM t1
26232623
WHERE t1.id = emp1.id RETURNING emp1.id, emp1.code;
26242624

2625+
-- Check that SJE does not mistakenly omit qual clauses (bug #18187)
2626+
explain (costs off)
2627+
select 1 from emp1 full join
2628+
(select * from emp1 t1 join
2629+
emp1 t2 join emp1 t3 on t2.id = t3.id
2630+
on true
2631+
where false) s on true
2632+
where false;
2633+
select 1 from emp1 full join
2634+
(select * from emp1 t1 join
2635+
emp1 t2 join emp1 t3 on t2.id = t3.id
2636+
on true
2637+
where false) s on true
2638+
where false;
2639+
26252640
-- We can remove the join even if we find the join can't duplicate rows and
26262641
-- the base quals of each side are different. In the following case we end up
26272642
-- moving quals over to s1 to make it so it can't match any rows.

0 commit comments

Comments
 (0)