Skip to content

Commit a546964

Browse files
committed
Fix incorrect logic for caching ResultRelInfos for triggers
When dealing with ResultRelInfos for partitions, there are cases where there are mixed requirements for the ri_RootResultRelInfo. There are cases when the partition itself requires a NULL ri_RootResultRelInfo and in the same query, the same partition may require a ResultRelInfo with its parent set in ri_RootResultRelInfo. This could cause the column mapping between the partitioned table and the partition not to be done which could result in crashes if the column attnums didn't match exactly. The fix is simple. We now check that the ri_RootResultRelInfo matches what the caller passed to ExecGetTriggerResultRel() and only return a cached ResultRelInfo when the ri_RootResultRelInfo matches what the caller wants, otherwise we'll make a new one. Author: David Rowley <dgrowleyml@gmail.com> Author: Amit Langote <amitlangote09@gmail.com> Reported-by: Dmitry Fomin <fomin.list@gmail.com> Discussion: https://postgr.es/m/7DCE78D7-0520-4207-822B-92F60AEA14B4@gmail.com Backpatch-through: 15
1 parent a978882 commit a546964

File tree

3 files changed

+116
-7
lines changed

3 files changed

+116
-7
lines changed

src/backend/executor/execMain.c

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,10 +1343,9 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
13431343
* Get a ResultRelInfo for a trigger target relation.
13441344
*
13451345
* Most of the time, triggers are fired on one of the result relations of the
1346-
* query, and so we can just return a member of the es_result_relations array,
1347-
* or the es_tuple_routing_result_relations list (if any). (Note: in self-join
1348-
* situations there might be multiple members with the same OID; if so it
1349-
* doesn't matter which one we pick.)
1346+
* query, and so we can just return a suitable one we already made and stored
1347+
* in the es_opened_result_relations or es_tuple_routing_result_relations
1348+
* Lists.
13501349
*
13511350
* However, it is sometimes necessary to fire triggers on other relations;
13521351
* this happens mainly when an RI update trigger queues additional triggers
@@ -1366,11 +1365,20 @@ ExecGetTriggerResultRel(EState *estate, Oid relid,
13661365
Relation rel;
13671366
MemoryContext oldcontext;
13681367

1368+
/*
1369+
* Before creating a new ResultRelInfo, check if we've already made and
1370+
* cached one for this relation. We must ensure that the given
1371+
* 'rootRelInfo' matches the one stored in the cached ResultRelInfo as
1372+
* trigger handling for partitions can result in mixed requirements for
1373+
* what ri_RootResultRelInfo is set to.
1374+
*/
1375+
13691376
/* Search through the query result relations */
13701377
foreach(l, estate->es_opened_result_relations)
13711378
{
13721379
rInfo = lfirst(l);
1373-
if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
1380+
if (RelationGetRelid(rInfo->ri_RelationDesc) == relid &&
1381+
rInfo->ri_RootResultRelInfo == rootRelInfo)
13741382
return rInfo;
13751383
}
13761384

@@ -1381,15 +1389,17 @@ ExecGetTriggerResultRel(EState *estate, Oid relid,
13811389
foreach(l, estate->es_tuple_routing_result_relations)
13821390
{
13831391
rInfo = (ResultRelInfo *) lfirst(l);
1384-
if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
1392+
if (RelationGetRelid(rInfo->ri_RelationDesc) == relid &&
1393+
rInfo->ri_RootResultRelInfo == rootRelInfo)
13851394
return rInfo;
13861395
}
13871396

13881397
/* Nope, but maybe we already made an extra ResultRelInfo for it */
13891398
foreach(l, estate->es_trig_target_relations)
13901399
{
13911400
rInfo = (ResultRelInfo *) lfirst(l);
1392-
if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
1401+
if (RelationGetRelid(rInfo->ri_RelationDesc) == relid &&
1402+
rInfo->ri_RootResultRelInfo == rootRelInfo)
13931403
return rInfo;
13941404
}
13951405
/* Nope, so we need a new one */

src/test/regress/expected/foreign_key.out

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3077,3 +3077,54 @@ SET client_min_messages TO warning;
30773077
DROP SCHEMA fkpart12 CASCADE;
30783078
RESET client_min_messages;
30793079
RESET search_path;
3080+
-- Exercise the column mapping code with foreign keys. In this test we'll
3081+
-- create a partitioned table which has a partition with a dropped column and
3082+
-- check to ensure that an UPDATE cascades the changes correctly to the
3083+
-- partitioned table.
3084+
CREATE SCHEMA fkpart13;
3085+
SET search_path TO fkpart13;
3086+
CREATE TABLE fkpart13_t1 (a int PRIMARY KEY);
3087+
CREATE TABLE fkpart13_t2 (
3088+
part_id int PRIMARY KEY,
3089+
column_to_drop int,
3090+
FOREIGN KEY (part_id) REFERENCES fkpart13_t1 ON UPDATE CASCADE ON DELETE CASCADE
3091+
) PARTITION BY LIST (part_id);
3092+
CREATE TABLE fkpart13_t2_p1 PARTITION OF fkpart13_t2 FOR VALUES IN (1);
3093+
-- drop the column
3094+
ALTER TABLE fkpart13_t2 DROP COLUMN column_to_drop;
3095+
-- create a new partition without the dropped column
3096+
CREATE TABLE fkpart13_t2_p2 PARTITION OF fkpart13_t2 FOR VALUES IN (2);
3097+
CREATE TABLE fkpart13_t3 (
3098+
a int NOT NULL,
3099+
FOREIGN KEY (a)
3100+
REFERENCES fkpart13_t2
3101+
ON UPDATE CASCADE ON DELETE CASCADE
3102+
);
3103+
INSERT INTO fkpart13_t1 (a) VALUES (1);
3104+
INSERT INTO fkpart13_t2 (part_id) VALUES (1);
3105+
INSERT INTO fkpart13_t3 (a) VALUES (1);
3106+
-- Test a cascading update works correctly with with the dropped column
3107+
UPDATE fkpart13_t1 SET a = 2 WHERE a = 1;
3108+
SELECT tableoid::regclass,* FROM fkpart13_t2;
3109+
tableoid | part_id
3110+
----------------+---------
3111+
fkpart13_t2_p2 | 2
3112+
(1 row)
3113+
3114+
SELECT tableoid::regclass,* FROM fkpart13_t3;
3115+
tableoid | a
3116+
-------------+---
3117+
fkpart13_t3 | 2
3118+
(1 row)
3119+
3120+
-- Exercise code in ExecGetTriggerResultRel() as there's been previous issues
3121+
-- with ResultRelInfos being returned with the incorrect ri_RootResultRelInfo
3122+
WITH cte AS (
3123+
UPDATE fkpart13_t2_p1 SET part_id = part_id
3124+
) UPDATE fkpart13_t1 SET a = 2 WHERE a = 1;
3125+
DROP SCHEMA fkpart13 CASCADE;
3126+
NOTICE: drop cascades to 3 other objects
3127+
DETAIL: drop cascades to table fkpart13_t1
3128+
drop cascades to table fkpart13_t2
3129+
drop cascades to table fkpart13_t3
3130+
RESET search_path;

src/test/regress/sql/foreign_key.sql

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2181,3 +2181,51 @@ SET client_min_messages TO warning;
21812181
DROP SCHEMA fkpart12 CASCADE;
21822182
RESET client_min_messages;
21832183
RESET search_path;
2184+
2185+
-- Exercise the column mapping code with foreign keys. In this test we'll
2186+
-- create a partitioned table which has a partition with a dropped column and
2187+
-- check to ensure that an UPDATE cascades the changes correctly to the
2188+
-- partitioned table.
2189+
CREATE SCHEMA fkpart13;
2190+
SET search_path TO fkpart13;
2191+
2192+
CREATE TABLE fkpart13_t1 (a int PRIMARY KEY);
2193+
2194+
CREATE TABLE fkpart13_t2 (
2195+
part_id int PRIMARY KEY,
2196+
column_to_drop int,
2197+
FOREIGN KEY (part_id) REFERENCES fkpart13_t1 ON UPDATE CASCADE ON DELETE CASCADE
2198+
) PARTITION BY LIST (part_id);
2199+
2200+
CREATE TABLE fkpart13_t2_p1 PARTITION OF fkpart13_t2 FOR VALUES IN (1);
2201+
2202+
-- drop the column
2203+
ALTER TABLE fkpart13_t2 DROP COLUMN column_to_drop;
2204+
2205+
-- create a new partition without the dropped column
2206+
CREATE TABLE fkpart13_t2_p2 PARTITION OF fkpart13_t2 FOR VALUES IN (2);
2207+
2208+
CREATE TABLE fkpart13_t3 (
2209+
a int NOT NULL,
2210+
FOREIGN KEY (a)
2211+
REFERENCES fkpart13_t2
2212+
ON UPDATE CASCADE ON DELETE CASCADE
2213+
);
2214+
2215+
INSERT INTO fkpart13_t1 (a) VALUES (1);
2216+
INSERT INTO fkpart13_t2 (part_id) VALUES (1);
2217+
INSERT INTO fkpart13_t3 (a) VALUES (1);
2218+
2219+
-- Test a cascading update works correctly with with the dropped column
2220+
UPDATE fkpart13_t1 SET a = 2 WHERE a = 1;
2221+
SELECT tableoid::regclass,* FROM fkpart13_t2;
2222+
SELECT tableoid::regclass,* FROM fkpart13_t3;
2223+
2224+
-- Exercise code in ExecGetTriggerResultRel() as there's been previous issues
2225+
-- with ResultRelInfos being returned with the incorrect ri_RootResultRelInfo
2226+
WITH cte AS (
2227+
UPDATE fkpart13_t2_p1 SET part_id = part_id
2228+
) UPDATE fkpart13_t1 SET a = 2 WHERE a = 1;
2229+
2230+
DROP SCHEMA fkpart13 CASCADE;
2231+
RESET search_path;

0 commit comments

Comments
 (0)