Skip to content

Commit d642d23

Browse files
author
Etsuro Fujita
committed
Disallow collecting transition tuples from child foreign tables.
Commit 9e6104c disallowed transition tables on foreign tables, but failed to account for cases where a foreign table is a child table of a partitioned/inherited table on which transition tables exist, leading to incorrect transition tuples collected from such foreign tables for queries on the parent table triggering transition capture. This occurred not only for inherited UPDATE/DELETE but for partitioned INSERT later supported by commit 3d956d9, which should have handled it at least for the INSERT case, but didn't. To fix, modify ExecAR*Triggers to throw an error if the given relation is a foreign table requesting transition capture. Also, this commit fixes make_modifytable so that in case of an inherited UPDATE/DELETE triggering transition capture, FDWs choose normal operations to modify child foreign tables, not DirectModify; which is needed because they would otherwise skip the calls to ExecAR*Triggers at execution, causing unexpected behavior. Author: Etsuro Fujita <etsuro.fujita@gmail.com> Reviewed-by: Amit Langote <amitlangote09@gmail.com> Discussion: https://postgr.es/m/CAPmGK14QJYikKzBDCe3jMbpGENnQ7popFmbEgm-XTNuk55oyHg%40mail.gmail.com Backpatch-through: 13
1 parent 769be67 commit d642d23

File tree

6 files changed

+291
-2
lines changed

6 files changed

+291
-2
lines changed

contrib/postgres_fdw/expected/postgres_fdw.out

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7615,6 +7615,119 @@ DELETE FROM rem1; -- can't be pushed down
76157615
(5 rows)
76167616

76177617
DROP TRIGGER trig_row_after_delete ON rem1;
7618+
-- We are allowed to create transition-table triggers on both kinds of
7619+
-- inheritance even if they contain foreign tables as children, but currently
7620+
-- collecting transition tuples from such foreign tables is not supported.
7621+
CREATE TABLE local_tbl (a text, b int);
7622+
CREATE FOREIGN TABLE foreign_tbl (a text, b int)
7623+
SERVER loopback OPTIONS (table_name 'local_tbl');
7624+
INSERT INTO foreign_tbl VALUES ('AAA', 42);
7625+
-- Test case for partition hierarchy
7626+
CREATE TABLE parent_tbl (a text, b int) PARTITION BY LIST (a);
7627+
ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES IN ('AAA');
7628+
CREATE TRIGGER parent_tbl_insert_trig
7629+
AFTER INSERT ON parent_tbl REFERENCING NEW TABLE AS new_table
7630+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
7631+
CREATE TRIGGER parent_tbl_update_trig
7632+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
7633+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
7634+
CREATE TRIGGER parent_tbl_delete_trig
7635+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
7636+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
7637+
INSERT INTO parent_tbl VALUES ('AAA', 42);
7638+
ERROR: cannot collect transition tuples from child foreign tables
7639+
COPY parent_tbl (a, b) FROM stdin;
7640+
ERROR: cannot collect transition tuples from child foreign tables
7641+
CONTEXT: COPY parent_tbl, line 1: "AAA 42"
7642+
ALTER SERVER loopback OPTIONS (ADD batch_size '10');
7643+
INSERT INTO parent_tbl VALUES ('AAA', 42);
7644+
ERROR: cannot collect transition tuples from child foreign tables
7645+
COPY parent_tbl (a, b) FROM stdin;
7646+
ERROR: cannot collect transition tuples from child foreign tables
7647+
CONTEXT: COPY parent_tbl, line 1: "AAA 42"
7648+
ALTER SERVER loopback OPTIONS (DROP batch_size);
7649+
EXPLAIN (VERBOSE, COSTS OFF)
7650+
UPDATE parent_tbl SET b = b + 1;
7651+
QUERY PLAN
7652+
------------------------------------------------------------------------------------------------
7653+
Update on public.parent_tbl
7654+
Foreign Update on public.foreign_tbl parent_tbl_1
7655+
Remote SQL: UPDATE public.local_tbl SET b = $2 WHERE ctid = $1
7656+
-> Foreign Scan on public.foreign_tbl parent_tbl_1
7657+
Output: (parent_tbl_1.b + 1), parent_tbl_1.tableoid, parent_tbl_1.ctid, parent_tbl_1.*
7658+
Remote SQL: SELECT a, b, ctid FROM public.local_tbl FOR UPDATE
7659+
(6 rows)
7660+
7661+
UPDATE parent_tbl SET b = b + 1;
7662+
ERROR: cannot collect transition tuples from child foreign tables
7663+
EXPLAIN (VERBOSE, COSTS OFF)
7664+
DELETE FROM parent_tbl;
7665+
QUERY PLAN
7666+
------------------------------------------------------------------
7667+
Delete on public.parent_tbl
7668+
Foreign Delete on public.foreign_tbl parent_tbl_1
7669+
Remote SQL: DELETE FROM public.local_tbl WHERE ctid = $1
7670+
-> Foreign Scan on public.foreign_tbl parent_tbl_1
7671+
Output: parent_tbl_1.tableoid, parent_tbl_1.ctid
7672+
Remote SQL: SELECT ctid FROM public.local_tbl FOR UPDATE
7673+
(6 rows)
7674+
7675+
DELETE FROM parent_tbl;
7676+
ERROR: cannot collect transition tuples from child foreign tables
7677+
ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl;
7678+
DROP TABLE parent_tbl;
7679+
-- Test case for non-partition hierarchy
7680+
CREATE TABLE parent_tbl (a text, b int);
7681+
ALTER FOREIGN TABLE foreign_tbl INHERIT parent_tbl;
7682+
CREATE TRIGGER parent_tbl_update_trig
7683+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
7684+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
7685+
CREATE TRIGGER parent_tbl_delete_trig
7686+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
7687+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
7688+
EXPLAIN (VERBOSE, COSTS OFF)
7689+
UPDATE parent_tbl SET b = b + 1;
7690+
QUERY PLAN
7691+
------------------------------------------------------------------------------------------------------
7692+
Update on public.parent_tbl
7693+
Update on public.parent_tbl parent_tbl_1
7694+
Foreign Update on public.foreign_tbl parent_tbl_2
7695+
Remote SQL: UPDATE public.local_tbl SET b = $2 WHERE ctid = $1
7696+
-> Result
7697+
Output: (parent_tbl.b + 1), parent_tbl.tableoid, parent_tbl.ctid, (NULL::record)
7698+
-> Append
7699+
-> Seq Scan on public.parent_tbl parent_tbl_1
7700+
Output: parent_tbl_1.b, parent_tbl_1.tableoid, parent_tbl_1.ctid, NULL::record
7701+
-> Foreign Scan on public.foreign_tbl parent_tbl_2
7702+
Output: parent_tbl_2.b, parent_tbl_2.tableoid, parent_tbl_2.ctid, parent_tbl_2.*
7703+
Remote SQL: SELECT a, b, ctid FROM public.local_tbl FOR UPDATE
7704+
(12 rows)
7705+
7706+
UPDATE parent_tbl SET b = b + 1;
7707+
ERROR: cannot collect transition tuples from child foreign tables
7708+
EXPLAIN (VERBOSE, COSTS OFF)
7709+
DELETE FROM parent_tbl;
7710+
QUERY PLAN
7711+
------------------------------------------------------------------------
7712+
Delete on public.parent_tbl
7713+
Delete on public.parent_tbl parent_tbl_1
7714+
Foreign Delete on public.foreign_tbl parent_tbl_2
7715+
Remote SQL: DELETE FROM public.local_tbl WHERE ctid = $1
7716+
-> Append
7717+
-> Seq Scan on public.parent_tbl parent_tbl_1
7718+
Output: parent_tbl_1.tableoid, parent_tbl_1.ctid
7719+
-> Foreign Scan on public.foreign_tbl parent_tbl_2
7720+
Output: parent_tbl_2.tableoid, parent_tbl_2.ctid
7721+
Remote SQL: SELECT ctid FROM public.local_tbl FOR UPDATE
7722+
(10 rows)
7723+
7724+
DELETE FROM parent_tbl;
7725+
ERROR: cannot collect transition tuples from child foreign tables
7726+
ALTER FOREIGN TABLE foreign_tbl NO INHERIT parent_tbl;
7727+
DROP TABLE parent_tbl;
7728+
-- Cleanup
7729+
DROP FOREIGN TABLE foreign_tbl;
7730+
DROP TABLE local_tbl;
76187731
-- ===================================================================
76197732
-- test inheritance features
76207733
-- ===================================================================

contrib/postgres_fdw/sql/postgres_fdw.sql

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2026,6 +2026,84 @@ EXPLAIN (verbose, costs off)
20262026
DELETE FROM rem1; -- can't be pushed down
20272027
DROP TRIGGER trig_row_after_delete ON rem1;
20282028

2029+
2030+
-- We are allowed to create transition-table triggers on both kinds of
2031+
-- inheritance even if they contain foreign tables as children, but currently
2032+
-- collecting transition tuples from such foreign tables is not supported.
2033+
2034+
CREATE TABLE local_tbl (a text, b int);
2035+
CREATE FOREIGN TABLE foreign_tbl (a text, b int)
2036+
SERVER loopback OPTIONS (table_name 'local_tbl');
2037+
2038+
INSERT INTO foreign_tbl VALUES ('AAA', 42);
2039+
2040+
-- Test case for partition hierarchy
2041+
CREATE TABLE parent_tbl (a text, b int) PARTITION BY LIST (a);
2042+
ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES IN ('AAA');
2043+
2044+
CREATE TRIGGER parent_tbl_insert_trig
2045+
AFTER INSERT ON parent_tbl REFERENCING NEW TABLE AS new_table
2046+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
2047+
CREATE TRIGGER parent_tbl_update_trig
2048+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
2049+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
2050+
CREATE TRIGGER parent_tbl_delete_trig
2051+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
2052+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
2053+
2054+
INSERT INTO parent_tbl VALUES ('AAA', 42);
2055+
2056+
COPY parent_tbl (a, b) FROM stdin;
2057+
AAA 42
2058+
\.
2059+
2060+
ALTER SERVER loopback OPTIONS (ADD batch_size '10');
2061+
2062+
INSERT INTO parent_tbl VALUES ('AAA', 42);
2063+
2064+
COPY parent_tbl (a, b) FROM stdin;
2065+
AAA 42
2066+
\.
2067+
2068+
ALTER SERVER loopback OPTIONS (DROP batch_size);
2069+
2070+
EXPLAIN (VERBOSE, COSTS OFF)
2071+
UPDATE parent_tbl SET b = b + 1;
2072+
UPDATE parent_tbl SET b = b + 1;
2073+
2074+
EXPLAIN (VERBOSE, COSTS OFF)
2075+
DELETE FROM parent_tbl;
2076+
DELETE FROM parent_tbl;
2077+
2078+
ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl;
2079+
DROP TABLE parent_tbl;
2080+
2081+
-- Test case for non-partition hierarchy
2082+
CREATE TABLE parent_tbl (a text, b int);
2083+
ALTER FOREIGN TABLE foreign_tbl INHERIT parent_tbl;
2084+
2085+
CREATE TRIGGER parent_tbl_update_trig
2086+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
2087+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
2088+
CREATE TRIGGER parent_tbl_delete_trig
2089+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
2090+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
2091+
2092+
EXPLAIN (VERBOSE, COSTS OFF)
2093+
UPDATE parent_tbl SET b = b + 1;
2094+
UPDATE parent_tbl SET b = b + 1;
2095+
2096+
EXPLAIN (VERBOSE, COSTS OFF)
2097+
DELETE FROM parent_tbl;
2098+
DELETE FROM parent_tbl;
2099+
2100+
ALTER FOREIGN TABLE foreign_tbl NO INHERIT parent_tbl;
2101+
DROP TABLE parent_tbl;
2102+
2103+
-- Cleanup
2104+
DROP FOREIGN TABLE foreign_tbl;
2105+
DROP TABLE local_tbl;
2106+
20292107
-- ===================================================================
20302108
-- test inheritance features
20312109
-- ===================================================================

src/backend/commands/trigger.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2606,6 +2606,15 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
26062606
{
26072607
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
26082608

2609+
if (relinfo->ri_FdwRoutine && transition_capture &&
2610+
transition_capture->tcs_insert_new_table)
2611+
{
2612+
Assert(relinfo->ri_RootResultRelInfo);
2613+
ereport(ERROR,
2614+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2615+
errmsg("cannot collect transition tuples from child foreign tables")));
2616+
}
2617+
26092618
if ((trigdesc && trigdesc->trig_insert_after_row) ||
26102619
(transition_capture && transition_capture->tcs_insert_new_table))
26112620
AfterTriggerSaveEvent(estate, relinfo, NULL, NULL,
@@ -2864,6 +2873,15 @@ ExecARDeleteTriggers(EState *estate,
28642873
{
28652874
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
28662875

2876+
if (relinfo->ri_FdwRoutine && transition_capture &&
2877+
transition_capture->tcs_delete_old_table)
2878+
{
2879+
Assert(relinfo->ri_RootResultRelInfo);
2880+
ereport(ERROR,
2881+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2882+
errmsg("cannot collect transition tuples from child foreign tables")));
2883+
}
2884+
28672885
if ((trigdesc && trigdesc->trig_delete_after_row) ||
28682886
(transition_capture && transition_capture->tcs_delete_old_table))
28692887
{
@@ -3206,6 +3224,16 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
32063224
{
32073225
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
32083226

3227+
if (relinfo->ri_FdwRoutine && transition_capture &&
3228+
(transition_capture->tcs_update_old_table ||
3229+
transition_capture->tcs_update_new_table))
3230+
{
3231+
Assert(relinfo->ri_RootResultRelInfo);
3232+
ereport(ERROR,
3233+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3234+
errmsg("cannot collect transition tuples from child foreign tables")));
3235+
}
3236+
32093237
if ((trigdesc && trigdesc->trig_update_after_row) ||
32103238
(transition_capture &&
32113239
(transition_capture->tcs_update_old_table ||

src/backend/optimizer/plan/createplan.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6989,6 +6989,8 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
69896989
List *mergeActionLists, int epqParam)
69906990
{
69916991
ModifyTable *node = makeNode(ModifyTable);
6992+
bool transition_tables = false;
6993+
bool transition_tables_valid = false;
69926994
List *fdw_private_list;
69936995
Bitmapset *direct_modify_plans;
69946996
ListCell *lc;
@@ -7134,7 +7136,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
71347136
* callback functions needed for that and (2) there are no local
71357137
* structures that need to be run for each modified row: row-level
71367138
* triggers on the foreign table, stored generated columns, WITH CHECK
7137-
* OPTIONs from parent views.
7139+
* OPTIONs from parent views, transition tables on the named relation.
71387140
*/
71397141
direct_modify = false;
71407142
if (fdwroutine != NULL &&
@@ -7145,7 +7147,19 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
71457147
withCheckOptionLists == NIL &&
71467148
!has_row_triggers(root, rti, operation) &&
71477149
!has_stored_generated_columns(root, rti))
7148-
direct_modify = fdwroutine->PlanDirectModify(root, node, rti, i);
7150+
{
7151+
/* transition_tables is the same for all result relations */
7152+
if (!transition_tables_valid)
7153+
{
7154+
transition_tables = has_transition_tables(root,
7155+
nominalRelation,
7156+
operation);
7157+
transition_tables_valid = true;
7158+
}
7159+
if (!transition_tables)
7160+
direct_modify = fdwroutine->PlanDirectModify(root, node,
7161+
rti, i);
7162+
}
71497163
if (direct_modify)
71507164
direct_modify_plans = bms_add_member(direct_modify_plans, i);
71517165

src/backend/optimizer/util/plancat.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2217,6 +2217,60 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
22172217
return result;
22182218
}
22192219

2220+
/*
2221+
* has_transition_tables
2222+
*
2223+
* Detect whether the specified relation has any transition tables for event.
2224+
*/
2225+
bool
2226+
has_transition_tables(PlannerInfo *root, Index rti, CmdType event)
2227+
{
2228+
RangeTblEntry *rte = planner_rt_fetch(rti, root);
2229+
Relation relation;
2230+
TriggerDesc *trigDesc;
2231+
bool result = false;
2232+
2233+
Assert(rte->rtekind == RTE_RELATION);
2234+
2235+
/* Currently foreign tables cannot have transition tables */
2236+
if (rte->relkind == RELKIND_FOREIGN_TABLE)
2237+
return result;
2238+
2239+
/* Assume we already have adequate lock */
2240+
relation = table_open(rte->relid, NoLock);
2241+
2242+
trigDesc = relation->trigdesc;
2243+
switch (event)
2244+
{
2245+
case CMD_INSERT:
2246+
if (trigDesc &&
2247+
trigDesc->trig_insert_new_table)
2248+
result = true;
2249+
break;
2250+
case CMD_UPDATE:
2251+
if (trigDesc &&
2252+
(trigDesc->trig_update_old_table ||
2253+
trigDesc->trig_update_new_table))
2254+
result = true;
2255+
break;
2256+
case CMD_DELETE:
2257+
if (trigDesc &&
2258+
trigDesc->trig_delete_old_table)
2259+
result = true;
2260+
break;
2261+
/* There is no separate event for MERGE, only INSERT/UPDATE/DELETE */
2262+
case CMD_MERGE:
2263+
result = false;
2264+
break;
2265+
default:
2266+
elog(ERROR, "unrecognized CmdType: %d", (int) event);
2267+
break;
2268+
}
2269+
2270+
table_close(relation, NoLock);
2271+
return result;
2272+
}
2273+
22202274
/*
22212275
* has_stored_generated_columns
22222276
*

src/include/optimizer/plancat.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node);
7272

7373
extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
7474

75+
extern bool has_transition_tables(PlannerInfo *root, Index rti, CmdType event);
76+
7577
extern bool has_stored_generated_columns(PlannerInfo *root, Index rti);
7678

7779
extern Bitmapset *get_dependent_generated_columns(PlannerInfo *root, Index rti,

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