Skip to content

Commit 30b4955

Browse files
committed
Fix misuse of RelOptInfo.unique_for_rels cache by SJE
When SJE uses RelOptInfo.unique_for_rels cache, it passes filtered quals to innerrel_is_unique_ext(). That might lead to an invalid match to cache entries made by previous non self-join checking calls. Add UniqueRelInfo.self_join flag to prevent such cases. Also, fix that SJE should require a strict match of outerrelids to make sure UniqueRelInfo.extra_clauses are valid. Reported-by: Alexander Lakhin Discussion: https://postgr.es/m/4788f781-31bd-9796-d7d6-588a751c8787%40gmail.com
1 parent d3c5f37 commit 30b4955

File tree

4 files changed

+59
-9
lines changed

4 files changed

+59
-9
lines changed

src/backend/optimizer/plan/analyzejoins.c

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,8 +1247,10 @@ innerrel_is_unique(PlannerInfo *root,
12471247

12481248
/*
12491249
* innerrel_is_unique_ext
1250-
* Do the same as innerrel_is_unique(), but also return additional clauses
1251-
* from a baserestrictinfo list that were used to prove uniqueness.
1250+
* Do the same as innerrel_is_unique(), but also set to '*extra_clauses'
1251+
* additional clauses from a baserestrictinfo list that were used to prove
1252+
* uniqueness. A non NULL 'extra_clauses' indicates that we're checking
1253+
* for self-join and correspondingly dealing with filtered clauses.
12521254
*/
12531255
bool
12541256
innerrel_is_unique_ext(PlannerInfo *root,
@@ -1264,6 +1266,7 @@ innerrel_is_unique_ext(PlannerInfo *root,
12641266
ListCell *lc;
12651267
UniqueRelInfo *uniqueRelInfo;
12661268
List *outer_exprs = NIL;
1269+
bool self_join = (extra_clauses != NULL);
12671270

12681271
/* Certainly can't prove uniqueness when there are no joinclauses */
12691272
if (restrictlist == NIL)
@@ -1278,16 +1281,23 @@ innerrel_is_unique_ext(PlannerInfo *root,
12781281

12791282
/*
12801283
* Query the cache to see if we've managed to prove that innerrel is
1281-
* unique for any subset of this outerrel. We don't need an exact match,
1282-
* as extra outerrels can't make the innerrel any less unique (or more
1283-
* formally, the restrictlist for a join to a superset outerrel must be a
1284-
* superset of the conditions we successfully used before).
1284+
* unique for any subset of this outerrel. For non self-join search, we
1285+
* don't need an exact match, as extra outerrels can't make the innerrel
1286+
* any less unique (or more formally, the restrictlist for a join to a
1287+
* superset outerrel must be a superset of the conditions we successfully
1288+
* used before). For self-join search, we require an exact match of
1289+
* outerrels, because we need extra clauses to be valid for our case.
1290+
* Also, for self-join checking we've filtered the clauses list. Thus,
1291+
* for a self-join search, we can match only the result cached for another
1292+
* self-join check.
12851293
*/
12861294
foreach(lc, innerrel->unique_for_rels)
12871295
{
12881296
uniqueRelInfo = (UniqueRelInfo *) lfirst(lc);
12891297

1290-
if (bms_is_subset(uniqueRelInfo->outerrelids, outerrelids))
1298+
if ((!self_join && bms_is_subset(uniqueRelInfo->outerrelids, outerrelids)) ||
1299+
(self_join && bms_equal(uniqueRelInfo->outerrelids, outerrelids) &&
1300+
uniqueRelInfo->self_join))
12911301
{
12921302
if (extra_clauses)
12931303
*extra_clauses = uniqueRelInfo->extra_clauses;
@@ -1309,7 +1319,8 @@ innerrel_is_unique_ext(PlannerInfo *root,
13091319

13101320
/* No cached information, so try to make the proof. */
13111321
if (is_innerrel_unique_for(root, joinrelids, outerrelids, innerrel,
1312-
jointype, restrictlist, &outer_exprs))
1322+
jointype, restrictlist,
1323+
self_join ? &outer_exprs : NULL))
13131324
{
13141325
/*
13151326
* Cache the positive result for future probes, being sure to keep it
@@ -1323,8 +1334,9 @@ innerrel_is_unique_ext(PlannerInfo *root,
13231334
*/
13241335
old_context = MemoryContextSwitchTo(root->planner_cxt);
13251336
uniqueRelInfo = makeNode(UniqueRelInfo);
1326-
uniqueRelInfo->extra_clauses = outer_exprs;
13271337
uniqueRelInfo->outerrelids = bms_copy(outerrelids);
1338+
uniqueRelInfo->self_join = self_join;
1339+
uniqueRelInfo->extra_clauses = outer_exprs;
13281340
innerrel->unique_for_rels = lappend(innerrel->unique_for_rels,
13291341
uniqueRelInfo);
13301342
MemoryContextSwitchTo(old_context);

src/include/nodes/pathnodes.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3407,6 +3407,12 @@ typedef struct UniqueRelInfo
34073407
*/
34083408
Relids outerrelids;
34093409

3410+
/*
3411+
* The relation in consideration is unique when considering only clauses
3412+
* suitable for self-join (passed split_selfjoin_quals()).
3413+
*/
3414+
bool self_join;
3415+
34103416
/*
34113417
* Additional clauses from a baserestrictinfo list that were used to prove
34123418
* the uniqueness. We cache it for the self-join checking procedure: a

src/test/regress/expected/join.out

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6905,6 +6905,29 @@ where false;
69056905
----------
69066906
(0 rows)
69076907

6908+
-- Check that SJE does not mistakenly re-use knowledge of relation uniqueness
6909+
-- made with different set of quals
6910+
insert into emp1 values (2, 1);
6911+
explain (costs off)
6912+
select * from emp1 t1 where exists (select * from emp1 t2
6913+
where t2.id = t1.code and t2.code > 0);
6914+
QUERY PLAN
6915+
---------------------------------------------
6916+
Nested Loop
6917+
-> Seq Scan on emp1 t1
6918+
-> Index Scan using emp1_pkey on emp1 t2
6919+
Index Cond: (id = t1.code)
6920+
Filter: (code > 0)
6921+
(5 rows)
6922+
6923+
select * from emp1 t1 where exists (select * from emp1 t2
6924+
where t2.id = t1.code and t2.code > 0);
6925+
id | code
6926+
----+------
6927+
1 | 1
6928+
2 | 1
6929+
(2 rows)
6930+
69086931
-- We can remove the join even if we find the join can't duplicate rows and
69096932
-- the base quals of each side are different. In the following case we end up
69106933
-- moving quals over to s1 to make it so it can't match any rows.

src/test/regress/sql/join.sql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2638,6 +2638,15 @@ select 1 from emp1 full join
26382638
where false) s on true
26392639
where false;
26402640

2641+
-- Check that SJE does not mistakenly re-use knowledge of relation uniqueness
2642+
-- made with different set of quals
2643+
insert into emp1 values (2, 1);
2644+
explain (costs off)
2645+
select * from emp1 t1 where exists (select * from emp1 t2
2646+
where t2.id = t1.code and t2.code > 0);
2647+
select * from emp1 t1 where exists (select * from emp1 t2
2648+
where t2.id = t1.code and t2.code > 0);
2649+
26412650
-- We can remove the join even if we find the join can't duplicate rows and
26422651
-- the base quals of each side are different. In the following case we end up
26432652
-- moving quals over to s1 to make it so it can't match any rows.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy