Skip to content

Commit 9048a83

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 bdda6ba commit 9048a83

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
@@ -8012,6 +8012,119 @@ DELETE FROM rem1; -- can't be pushed down
80128012
(5 rows)
80138013

80148014
DROP TRIGGER trig_row_after_delete ON rem1;
8015+
-- We are allowed to create transition-table triggers on both kinds of
8016+
-- inheritance even if they contain foreign tables as children, but currently
8017+
-- collecting transition tuples from such foreign tables is not supported.
8018+
CREATE TABLE local_tbl (a text, b int);
8019+
CREATE FOREIGN TABLE foreign_tbl (a text, b int)
8020+
SERVER loopback OPTIONS (table_name 'local_tbl');
8021+
INSERT INTO foreign_tbl VALUES ('AAA', 42);
8022+
-- Test case for partition hierarchy
8023+
CREATE TABLE parent_tbl (a text, b int) PARTITION BY LIST (a);
8024+
ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES IN ('AAA');
8025+
CREATE TRIGGER parent_tbl_insert_trig
8026+
AFTER INSERT ON parent_tbl REFERENCING NEW TABLE AS new_table
8027+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
8028+
CREATE TRIGGER parent_tbl_update_trig
8029+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
8030+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
8031+
CREATE TRIGGER parent_tbl_delete_trig
8032+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
8033+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
8034+
INSERT INTO parent_tbl VALUES ('AAA', 42);
8035+
ERROR: cannot collect transition tuples from child foreign tables
8036+
COPY parent_tbl (a, b) FROM stdin;
8037+
ERROR: cannot collect transition tuples from child foreign tables
8038+
CONTEXT: COPY parent_tbl, line 1: "AAA 42"
8039+
ALTER SERVER loopback OPTIONS (ADD batch_size '10');
8040+
INSERT INTO parent_tbl VALUES ('AAA', 42);
8041+
ERROR: cannot collect transition tuples from child foreign tables
8042+
COPY parent_tbl (a, b) FROM stdin;
8043+
ERROR: cannot collect transition tuples from child foreign tables
8044+
CONTEXT: COPY parent_tbl, line 1: "AAA 42"
8045+
ALTER SERVER loopback OPTIONS (DROP batch_size);
8046+
EXPLAIN (VERBOSE, COSTS OFF)
8047+
UPDATE parent_tbl SET b = b + 1;
8048+
QUERY PLAN
8049+
------------------------------------------------------------------------------------------------
8050+
Update on public.parent_tbl
8051+
Foreign Update on public.foreign_tbl parent_tbl_1
8052+
Remote SQL: UPDATE public.local_tbl SET b = $2 WHERE ctid = $1
8053+
-> Foreign Scan on public.foreign_tbl parent_tbl_1
8054+
Output: (parent_tbl_1.b + 1), parent_tbl_1.tableoid, parent_tbl_1.ctid, parent_tbl_1.*
8055+
Remote SQL: SELECT a, b, ctid FROM public.local_tbl FOR UPDATE
8056+
(6 rows)
8057+
8058+
UPDATE parent_tbl SET b = b + 1;
8059+
ERROR: cannot collect transition tuples from child foreign tables
8060+
EXPLAIN (VERBOSE, COSTS OFF)
8061+
DELETE FROM parent_tbl;
8062+
QUERY PLAN
8063+
------------------------------------------------------------------
8064+
Delete on public.parent_tbl
8065+
Foreign Delete on public.foreign_tbl parent_tbl_1
8066+
Remote SQL: DELETE FROM public.local_tbl WHERE ctid = $1
8067+
-> Foreign Scan on public.foreign_tbl parent_tbl_1
8068+
Output: parent_tbl_1.tableoid, parent_tbl_1.ctid
8069+
Remote SQL: SELECT ctid FROM public.local_tbl FOR UPDATE
8070+
(6 rows)
8071+
8072+
DELETE FROM parent_tbl;
8073+
ERROR: cannot collect transition tuples from child foreign tables
8074+
ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl;
8075+
DROP TABLE parent_tbl;
8076+
-- Test case for non-partition hierarchy
8077+
CREATE TABLE parent_tbl (a text, b int);
8078+
ALTER FOREIGN TABLE foreign_tbl INHERIT parent_tbl;
8079+
CREATE TRIGGER parent_tbl_update_trig
8080+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
8081+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
8082+
CREATE TRIGGER parent_tbl_delete_trig
8083+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
8084+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
8085+
EXPLAIN (VERBOSE, COSTS OFF)
8086+
UPDATE parent_tbl SET b = b + 1;
8087+
QUERY PLAN
8088+
------------------------------------------------------------------------------------------------------
8089+
Update on public.parent_tbl
8090+
Update on public.parent_tbl parent_tbl_1
8091+
Foreign Update on public.foreign_tbl parent_tbl_2
8092+
Remote SQL: UPDATE public.local_tbl SET b = $2 WHERE ctid = $1
8093+
-> Result
8094+
Output: (parent_tbl.b + 1), parent_tbl.tableoid, parent_tbl.ctid, (NULL::record)
8095+
-> Append
8096+
-> Seq Scan on public.parent_tbl parent_tbl_1
8097+
Output: parent_tbl_1.b, parent_tbl_1.tableoid, parent_tbl_1.ctid, NULL::record
8098+
-> Foreign Scan on public.foreign_tbl parent_tbl_2
8099+
Output: parent_tbl_2.b, parent_tbl_2.tableoid, parent_tbl_2.ctid, parent_tbl_2.*
8100+
Remote SQL: SELECT a, b, ctid FROM public.local_tbl FOR UPDATE
8101+
(12 rows)
8102+
8103+
UPDATE parent_tbl SET b = b + 1;
8104+
ERROR: cannot collect transition tuples from child foreign tables
8105+
EXPLAIN (VERBOSE, COSTS OFF)
8106+
DELETE FROM parent_tbl;
8107+
QUERY PLAN
8108+
------------------------------------------------------------------------
8109+
Delete on public.parent_tbl
8110+
Delete on public.parent_tbl parent_tbl_1
8111+
Foreign Delete on public.foreign_tbl parent_tbl_2
8112+
Remote SQL: DELETE FROM public.local_tbl WHERE ctid = $1
8113+
-> Append
8114+
-> Seq Scan on public.parent_tbl parent_tbl_1
8115+
Output: parent_tbl_1.tableoid, parent_tbl_1.ctid
8116+
-> Foreign Scan on public.foreign_tbl parent_tbl_2
8117+
Output: parent_tbl_2.tableoid, parent_tbl_2.ctid
8118+
Remote SQL: SELECT ctid FROM public.local_tbl FOR UPDATE
8119+
(10 rows)
8120+
8121+
DELETE FROM parent_tbl;
8122+
ERROR: cannot collect transition tuples from child foreign tables
8123+
ALTER FOREIGN TABLE foreign_tbl NO INHERIT parent_tbl;
8124+
DROP TABLE parent_tbl;
8125+
-- Cleanup
8126+
DROP FOREIGN TABLE foreign_tbl;
8127+
DROP TABLE local_tbl;
80158128
-- ===================================================================
80168129
-- test inheritance features
80178130
-- ===================================================================

contrib/postgres_fdw/sql/postgres_fdw.sql

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

2236+
2237+
-- We are allowed to create transition-table triggers on both kinds of
2238+
-- inheritance even if they contain foreign tables as children, but currently
2239+
-- collecting transition tuples from such foreign tables is not supported.
2240+
2241+
CREATE TABLE local_tbl (a text, b int);
2242+
CREATE FOREIGN TABLE foreign_tbl (a text, b int)
2243+
SERVER loopback OPTIONS (table_name 'local_tbl');
2244+
2245+
INSERT INTO foreign_tbl VALUES ('AAA', 42);
2246+
2247+
-- Test case for partition hierarchy
2248+
CREATE TABLE parent_tbl (a text, b int) PARTITION BY LIST (a);
2249+
ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES IN ('AAA');
2250+
2251+
CREATE TRIGGER parent_tbl_insert_trig
2252+
AFTER INSERT ON parent_tbl REFERENCING NEW TABLE AS new_table
2253+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
2254+
CREATE TRIGGER parent_tbl_update_trig
2255+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
2256+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
2257+
CREATE TRIGGER parent_tbl_delete_trig
2258+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
2259+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
2260+
2261+
INSERT INTO parent_tbl VALUES ('AAA', 42);
2262+
2263+
COPY parent_tbl (a, b) FROM stdin;
2264+
AAA 42
2265+
\.
2266+
2267+
ALTER SERVER loopback OPTIONS (ADD batch_size '10');
2268+
2269+
INSERT INTO parent_tbl VALUES ('AAA', 42);
2270+
2271+
COPY parent_tbl (a, b) FROM stdin;
2272+
AAA 42
2273+
\.
2274+
2275+
ALTER SERVER loopback OPTIONS (DROP batch_size);
2276+
2277+
EXPLAIN (VERBOSE, COSTS OFF)
2278+
UPDATE parent_tbl SET b = b + 1;
2279+
UPDATE parent_tbl SET b = b + 1;
2280+
2281+
EXPLAIN (VERBOSE, COSTS OFF)
2282+
DELETE FROM parent_tbl;
2283+
DELETE FROM parent_tbl;
2284+
2285+
ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl;
2286+
DROP TABLE parent_tbl;
2287+
2288+
-- Test case for non-partition hierarchy
2289+
CREATE TABLE parent_tbl (a text, b int);
2290+
ALTER FOREIGN TABLE foreign_tbl INHERIT parent_tbl;
2291+
2292+
CREATE TRIGGER parent_tbl_update_trig
2293+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
2294+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
2295+
CREATE TRIGGER parent_tbl_delete_trig
2296+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
2297+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
2298+
2299+
EXPLAIN (VERBOSE, COSTS OFF)
2300+
UPDATE parent_tbl SET b = b + 1;
2301+
UPDATE parent_tbl SET b = b + 1;
2302+
2303+
EXPLAIN (VERBOSE, COSTS OFF)
2304+
DELETE FROM parent_tbl;
2305+
DELETE FROM parent_tbl;
2306+
2307+
ALTER FOREIGN TABLE foreign_tbl NO INHERIT parent_tbl;
2308+
DROP TABLE parent_tbl;
2309+
2310+
-- Cleanup
2311+
DROP FOREIGN TABLE foreign_tbl;
2312+
DROP TABLE local_tbl;
2313+
22362314
-- ===================================================================
22372315
-- test inheritance features
22382316
-- ===================================================================

src/backend/commands/trigger.c

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

2540+
if (relinfo->ri_FdwRoutine && transition_capture &&
2541+
transition_capture->tcs_insert_new_table)
2542+
{
2543+
Assert(relinfo->ri_RootResultRelInfo);
2544+
ereport(ERROR,
2545+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2546+
errmsg("cannot collect transition tuples from child foreign tables")));
2547+
}
2548+
25402549
if ((trigdesc && trigdesc->trig_insert_after_row) ||
25412550
(transition_capture && transition_capture->tcs_insert_new_table))
25422551
AfterTriggerSaveEvent(estate, relinfo, NULL, NULL,
@@ -2807,6 +2816,15 @@ ExecARDeleteTriggers(EState *estate,
28072816
{
28082817
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
28092818

2819+
if (relinfo->ri_FdwRoutine && transition_capture &&
2820+
transition_capture->tcs_delete_old_table)
2821+
{
2822+
Assert(relinfo->ri_RootResultRelInfo);
2823+
ereport(ERROR,
2824+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2825+
errmsg("cannot collect transition tuples from child foreign tables")));
2826+
}
2827+
28102828
if ((trigdesc && trigdesc->trig_delete_after_row) ||
28112829
(transition_capture && transition_capture->tcs_delete_old_table))
28122830
{
@@ -3160,6 +3178,16 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
31603178
{
31613179
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
31623180

3181+
if (relinfo->ri_FdwRoutine && transition_capture &&
3182+
(transition_capture->tcs_update_old_table ||
3183+
transition_capture->tcs_update_new_table))
3184+
{
3185+
Assert(relinfo->ri_RootResultRelInfo);
3186+
ereport(ERROR,
3187+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3188+
errmsg("cannot collect transition tuples from child foreign tables")));
3189+
}
3190+
31633191
if ((trigdesc && trigdesc->trig_update_after_row) ||
31643192
(transition_capture &&
31653193
(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
@@ -7038,6 +7038,8 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
70387038
int epqParam)
70397039
{
70407040
ModifyTable *node = makeNode(ModifyTable);
7041+
bool transition_tables = false;
7042+
bool transition_tables_valid = false;
70417043
List *fdw_private_list;
70427044
Bitmapset *direct_modify_plans;
70437045
ListCell *lc;
@@ -7182,7 +7184,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
71827184
* callback functions needed for that and (2) there are no local
71837185
* structures that need to be run for each modified row: row-level
71847186
* triggers on the foreign table, stored generated columns, WITH CHECK
7185-
* OPTIONs from parent views.
7187+
* OPTIONs from parent views, transition tables on the named relation.
71867188
*/
71877189
direct_modify = false;
71887190
if (fdwroutine != NULL &&
@@ -7193,7 +7195,19 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
71937195
withCheckOptionLists == NIL &&
71947196
!has_row_triggers(root, rti, operation) &&
71957197
!has_stored_generated_columns(root, rti))
7196-
direct_modify = fdwroutine->PlanDirectModify(root, node, rti, i);
7198+
{
7199+
/* transition_tables is the same for all result relations */
7200+
if (!transition_tables_valid)
7201+
{
7202+
transition_tables = has_transition_tables(root,
7203+
nominalRelation,
7204+
operation);
7205+
transition_tables_valid = true;
7206+
}
7207+
if (!transition_tables)
7208+
direct_modify = fdwroutine->PlanDirectModify(root, node,
7209+
rti, i);
7210+
}
71977211
if (direct_modify)
71987212
direct_modify_plans = bms_add_member(direct_modify_plans, i);
71997213

src/backend/optimizer/util/plancat.c

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

2284+
/*
2285+
* has_transition_tables
2286+
*
2287+
* Detect whether the specified relation has any transition tables for event.
2288+
*/
2289+
bool
2290+
has_transition_tables(PlannerInfo *root, Index rti, CmdType event)
2291+
{
2292+
RangeTblEntry *rte = planner_rt_fetch(rti, root);
2293+
Relation relation;
2294+
TriggerDesc *trigDesc;
2295+
bool result = false;
2296+
2297+
Assert(rte->rtekind == RTE_RELATION);
2298+
2299+
/* Currently foreign tables cannot have transition tables */
2300+
if (rte->relkind == RELKIND_FOREIGN_TABLE)
2301+
return result;
2302+
2303+
/* Assume we already have adequate lock */
2304+
relation = table_open(rte->relid, NoLock);
2305+
2306+
trigDesc = relation->trigdesc;
2307+
switch (event)
2308+
{
2309+
case CMD_INSERT:
2310+
if (trigDesc &&
2311+
trigDesc->trig_insert_new_table)
2312+
result = true;
2313+
break;
2314+
case CMD_UPDATE:
2315+
if (trigDesc &&
2316+
(trigDesc->trig_update_old_table ||
2317+
trigDesc->trig_update_new_table))
2318+
result = true;
2319+
break;
2320+
case CMD_DELETE:
2321+
if (trigDesc &&
2322+
trigDesc->trig_delete_old_table)
2323+
result = true;
2324+
break;
2325+
/* There is no separate event for MERGE, only INSERT/UPDATE/DELETE */
2326+
case CMD_MERGE:
2327+
result = false;
2328+
break;
2329+
default:
2330+
elog(ERROR, "unrecognized CmdType: %d", (int) event);
2331+
break;
2332+
}
2333+
2334+
table_close(relation, NoLock);
2335+
return result;
2336+
}
2337+
22842338
/*
22852339
* has_stored_generated_columns
22862340
*

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