mirror of
https://github.com/postgres/postgres.git
synced 2025-05-02 11:44:50 +03:00
Prevent join removal from removing the query's result relation.
This was not something that required consideration before MERGE was invented; but MERGE builds a join tree that left-joins to the result relation, meaning that remove_useless_joins will consider removing it. That should generally be stopped by the query's use of output variables from the result relation. However, if the result relation is inherited (e.g. a partitioned table) then we don't add any row identity variables to the query until expand_inherited_rtentry, which happens after join removal. This was exposed as of commit 3c569049b, which made it possible to deduce that a partitioned table could contain at most one row matching a join key, enabling removal of the not-yet-expanded result relation. Ooops. To fix, let's just teach join_is_removable that the query result rel is never removable. It's a cheap enough test in any case, and it'll save some cycles that we'd otherwise expend in proving that it's not removable, even in the cases we got right. Back-patch to v15 where MERGE was added. Although I think the case cannot be reached in v15, this seems like cheap insurance. Per investigation of a report from Alexander Lakhin. Discussion: https://postgr.es/m/36bee393-b351-16ac-93b2-d46d83637e45@gmail.com
This commit is contained in:
parent
69e8c7cf1d
commit
f6db76c555
@ -183,6 +183,14 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo)
|
|||||||
if (!bms_get_singleton_member(sjinfo->min_righthand, &innerrelid))
|
if (!bms_get_singleton_member(sjinfo->min_righthand, &innerrelid))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Never try to eliminate a left join to the query result rel. Although
|
||||||
|
* the case is syntactically impossible in standard SQL, MERGE will build
|
||||||
|
* a join tree that looks exactly like that.
|
||||||
|
*/
|
||||||
|
if (innerrelid == root->parse->resultRelation)
|
||||||
|
return false;
|
||||||
|
|
||||||
innerrel = find_base_rel(root, innerrelid);
|
innerrel = find_base_rel(root, innerrelid);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1771,6 +1771,41 @@ SELECT * FROM pa_target ORDER BY tid;
|
|||||||
(9 rows)
|
(9 rows)
|
||||||
|
|
||||||
ROLLBACK;
|
ROLLBACK;
|
||||||
|
DROP TABLE pa_source;
|
||||||
|
DROP TABLE pa_target CASCADE;
|
||||||
|
-- Partitioned table with primary key
|
||||||
|
CREATE TABLE pa_target (tid integer PRIMARY KEY) PARTITION BY LIST (tid);
|
||||||
|
CREATE TABLE pa_targetp PARTITION OF pa_target DEFAULT;
|
||||||
|
CREATE TABLE pa_source (sid integer);
|
||||||
|
INSERT INTO pa_source VALUES (1), (2);
|
||||||
|
EXPLAIN (VERBOSE, COSTS OFF)
|
||||||
|
MERGE INTO pa_target t USING pa_source s ON t.tid = s.sid
|
||||||
|
WHEN NOT MATCHED THEN INSERT VALUES (s.sid);
|
||||||
|
QUERY PLAN
|
||||||
|
-------------------------------------------------------------
|
||||||
|
Merge on public.pa_target t
|
||||||
|
Merge on public.pa_targetp t_1
|
||||||
|
-> Hash Left Join
|
||||||
|
Output: s.sid, t_1.tableoid, t_1.ctid
|
||||||
|
Inner Unique: true
|
||||||
|
Hash Cond: (s.sid = t_1.tid)
|
||||||
|
-> Seq Scan on public.pa_source s
|
||||||
|
Output: s.sid
|
||||||
|
-> Hash
|
||||||
|
Output: t_1.tid, t_1.tableoid, t_1.ctid
|
||||||
|
-> Seq Scan on public.pa_targetp t_1
|
||||||
|
Output: t_1.tid, t_1.tableoid, t_1.ctid
|
||||||
|
(12 rows)
|
||||||
|
|
||||||
|
MERGE INTO pa_target t USING pa_source s ON t.tid = s.sid
|
||||||
|
WHEN NOT MATCHED THEN INSERT VALUES (s.sid);
|
||||||
|
TABLE pa_target;
|
||||||
|
tid
|
||||||
|
-----
|
||||||
|
1
|
||||||
|
2
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
DROP TABLE pa_source;
|
DROP TABLE pa_source;
|
||||||
DROP TABLE pa_target CASCADE;
|
DROP TABLE pa_target CASCADE;
|
||||||
-- some complex joins on the source side
|
-- some complex joins on the source side
|
||||||
|
@ -1116,6 +1116,26 @@ ROLLBACK;
|
|||||||
DROP TABLE pa_source;
|
DROP TABLE pa_source;
|
||||||
DROP TABLE pa_target CASCADE;
|
DROP TABLE pa_target CASCADE;
|
||||||
|
|
||||||
|
-- Partitioned table with primary key
|
||||||
|
|
||||||
|
CREATE TABLE pa_target (tid integer PRIMARY KEY) PARTITION BY LIST (tid);
|
||||||
|
CREATE TABLE pa_targetp PARTITION OF pa_target DEFAULT;
|
||||||
|
CREATE TABLE pa_source (sid integer);
|
||||||
|
|
||||||
|
INSERT INTO pa_source VALUES (1), (2);
|
||||||
|
|
||||||
|
EXPLAIN (VERBOSE, COSTS OFF)
|
||||||
|
MERGE INTO pa_target t USING pa_source s ON t.tid = s.sid
|
||||||
|
WHEN NOT MATCHED THEN INSERT VALUES (s.sid);
|
||||||
|
|
||||||
|
MERGE INTO pa_target t USING pa_source s ON t.tid = s.sid
|
||||||
|
WHEN NOT MATCHED THEN INSERT VALUES (s.sid);
|
||||||
|
|
||||||
|
TABLE pa_target;
|
||||||
|
|
||||||
|
DROP TABLE pa_source;
|
||||||
|
DROP TABLE pa_target CASCADE;
|
||||||
|
|
||||||
-- some complex joins on the source side
|
-- some complex joins on the source side
|
||||||
|
|
||||||
CREATE TABLE cj_target (tid integer, balance float, val text)
|
CREATE TABLE cj_target (tid integer, balance float, val text)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user