Skip to content

Commit 62a1211

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 84b32fd commit 62a1211

File tree

6 files changed

+295
-4
lines changed

6 files changed

+295
-4
lines changed

contrib/postgres_fdw/expected/postgres_fdw.out

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

82358235
DROP TRIGGER trig_row_after_delete ON rem1;
8236+
-- We are allowed to create transition-table triggers on both kinds of
8237+
-- inheritance even if they contain foreign tables as children, but currently
8238+
-- collecting transition tuples from such foreign tables is not supported.
8239+
CREATE TABLE local_tbl (a text, b int);
8240+
CREATE FOREIGN TABLE foreign_tbl (a text, b int)
8241+
SERVER loopback OPTIONS (table_name 'local_tbl');
8242+
INSERT INTO foreign_tbl VALUES ('AAA', 42);
8243+
-- Test case for partition hierarchy
8244+
CREATE TABLE parent_tbl (a text, b int) PARTITION BY LIST (a);
8245+
ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES IN ('AAA');
8246+
CREATE TRIGGER parent_tbl_insert_trig
8247+
AFTER INSERT ON parent_tbl REFERENCING NEW TABLE AS new_table
8248+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
8249+
CREATE TRIGGER parent_tbl_update_trig
8250+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
8251+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
8252+
CREATE TRIGGER parent_tbl_delete_trig
8253+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
8254+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
8255+
INSERT INTO parent_tbl VALUES ('AAA', 42);
8256+
ERROR: cannot collect transition tuples from child foreign tables
8257+
COPY parent_tbl (a, b) FROM stdin;
8258+
ERROR: cannot collect transition tuples from child foreign tables
8259+
CONTEXT: COPY parent_tbl, line 1: "AAA 42"
8260+
ALTER SERVER loopback OPTIONS (ADD batch_size '10');
8261+
INSERT INTO parent_tbl VALUES ('AAA', 42);
8262+
ERROR: cannot collect transition tuples from child foreign tables
8263+
COPY parent_tbl (a, b) FROM stdin;
8264+
ERROR: cannot collect transition tuples from child foreign tables
8265+
CONTEXT: COPY parent_tbl, line 1: "AAA 42"
8266+
ALTER SERVER loopback OPTIONS (DROP batch_size);
8267+
EXPLAIN (VERBOSE, COSTS OFF)
8268+
UPDATE parent_tbl SET b = b + 1;
8269+
QUERY PLAN
8270+
------------------------------------------------------------------------------------------------
8271+
Update on public.parent_tbl
8272+
Foreign Update on public.foreign_tbl parent_tbl_1
8273+
Remote SQL: UPDATE public.local_tbl SET b = $2 WHERE ctid = $1
8274+
-> Foreign Scan on public.foreign_tbl parent_tbl_1
8275+
Output: (parent_tbl_1.b + 1), parent_tbl_1.tableoid, parent_tbl_1.ctid, parent_tbl_1.*
8276+
Remote SQL: SELECT a, b, ctid FROM public.local_tbl FOR UPDATE
8277+
(6 rows)
8278+
8279+
UPDATE parent_tbl SET b = b + 1;
8280+
ERROR: cannot collect transition tuples from child foreign tables
8281+
EXPLAIN (VERBOSE, COSTS OFF)
8282+
DELETE FROM parent_tbl;
8283+
QUERY PLAN
8284+
------------------------------------------------------------------
8285+
Delete on public.parent_tbl
8286+
Foreign Delete on public.foreign_tbl parent_tbl_1
8287+
Remote SQL: DELETE FROM public.local_tbl WHERE ctid = $1
8288+
-> Foreign Scan on public.foreign_tbl parent_tbl_1
8289+
Output: parent_tbl_1.tableoid, parent_tbl_1.ctid
8290+
Remote SQL: SELECT ctid FROM public.local_tbl FOR UPDATE
8291+
(6 rows)
8292+
8293+
DELETE FROM parent_tbl;
8294+
ERROR: cannot collect transition tuples from child foreign tables
8295+
ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl;
8296+
DROP TABLE parent_tbl;
8297+
-- Test case for non-partition hierarchy
8298+
CREATE TABLE parent_tbl (a text, b int);
8299+
ALTER FOREIGN TABLE foreign_tbl INHERIT parent_tbl;
8300+
CREATE TRIGGER parent_tbl_update_trig
8301+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
8302+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
8303+
CREATE TRIGGER parent_tbl_delete_trig
8304+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
8305+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
8306+
EXPLAIN (VERBOSE, COSTS OFF)
8307+
UPDATE parent_tbl SET b = b + 1;
8308+
QUERY PLAN
8309+
------------------------------------------------------------------------------------------------------
8310+
Update on public.parent_tbl
8311+
Update on public.parent_tbl parent_tbl_1
8312+
Foreign Update on public.foreign_tbl parent_tbl_2
8313+
Remote SQL: UPDATE public.local_tbl SET b = $2 WHERE ctid = $1
8314+
-> Result
8315+
Output: (parent_tbl.b + 1), parent_tbl.tableoid, parent_tbl.ctid, (NULL::record)
8316+
-> Append
8317+
-> Seq Scan on public.parent_tbl parent_tbl_1
8318+
Output: parent_tbl_1.b, parent_tbl_1.tableoid, parent_tbl_1.ctid, NULL::record
8319+
-> Foreign Scan on public.foreign_tbl parent_tbl_2
8320+
Output: parent_tbl_2.b, parent_tbl_2.tableoid, parent_tbl_2.ctid, parent_tbl_2.*
8321+
Remote SQL: SELECT a, b, ctid FROM public.local_tbl FOR UPDATE
8322+
(12 rows)
8323+
8324+
UPDATE parent_tbl SET b = b + 1;
8325+
ERROR: cannot collect transition tuples from child foreign tables
8326+
EXPLAIN (VERBOSE, COSTS OFF)
8327+
DELETE FROM parent_tbl;
8328+
QUERY PLAN
8329+
------------------------------------------------------------------------
8330+
Delete on public.parent_tbl
8331+
Delete on public.parent_tbl parent_tbl_1
8332+
Foreign Delete on public.foreign_tbl parent_tbl_2
8333+
Remote SQL: DELETE FROM public.local_tbl WHERE ctid = $1
8334+
-> Append
8335+
-> Seq Scan on public.parent_tbl parent_tbl_1
8336+
Output: parent_tbl_1.tableoid, parent_tbl_1.ctid
8337+
-> Foreign Scan on public.foreign_tbl parent_tbl_2
8338+
Output: parent_tbl_2.tableoid, parent_tbl_2.ctid
8339+
Remote SQL: SELECT ctid FROM public.local_tbl FOR UPDATE
8340+
(10 rows)
8341+
8342+
DELETE FROM parent_tbl;
8343+
ERROR: cannot collect transition tuples from child foreign tables
8344+
ALTER FOREIGN TABLE foreign_tbl NO INHERIT parent_tbl;
8345+
DROP TABLE parent_tbl;
8346+
-- Cleanup
8347+
DROP FOREIGN TABLE foreign_tbl;
8348+
DROP TABLE local_tbl;
82368349
-- ===================================================================
82378350
-- test inheritance features
82388351
-- ===================================================================

contrib/postgres_fdw/sql/postgres_fdw.sql

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

2284+
2285+
-- We are allowed to create transition-table triggers on both kinds of
2286+
-- inheritance even if they contain foreign tables as children, but currently
2287+
-- collecting transition tuples from such foreign tables is not supported.
2288+
2289+
CREATE TABLE local_tbl (a text, b int);
2290+
CREATE FOREIGN TABLE foreign_tbl (a text, b int)
2291+
SERVER loopback OPTIONS (table_name 'local_tbl');
2292+
2293+
INSERT INTO foreign_tbl VALUES ('AAA', 42);
2294+
2295+
-- Test case for partition hierarchy
2296+
CREATE TABLE parent_tbl (a text, b int) PARTITION BY LIST (a);
2297+
ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES IN ('AAA');
2298+
2299+
CREATE TRIGGER parent_tbl_insert_trig
2300+
AFTER INSERT ON parent_tbl REFERENCING NEW TABLE AS new_table
2301+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
2302+
CREATE TRIGGER parent_tbl_update_trig
2303+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
2304+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
2305+
CREATE TRIGGER parent_tbl_delete_trig
2306+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
2307+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
2308+
2309+
INSERT INTO parent_tbl VALUES ('AAA', 42);
2310+
2311+
COPY parent_tbl (a, b) FROM stdin;
2312+
AAA 42
2313+
\.
2314+
2315+
ALTER SERVER loopback OPTIONS (ADD batch_size '10');
2316+
2317+
INSERT INTO parent_tbl VALUES ('AAA', 42);
2318+
2319+
COPY parent_tbl (a, b) FROM stdin;
2320+
AAA 42
2321+
\.
2322+
2323+
ALTER SERVER loopback OPTIONS (DROP batch_size);
2324+
2325+
EXPLAIN (VERBOSE, COSTS OFF)
2326+
UPDATE parent_tbl SET b = b + 1;
2327+
UPDATE parent_tbl SET b = b + 1;
2328+
2329+
EXPLAIN (VERBOSE, COSTS OFF)
2330+
DELETE FROM parent_tbl;
2331+
DELETE FROM parent_tbl;
2332+
2333+
ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl;
2334+
DROP TABLE parent_tbl;
2335+
2336+
-- Test case for non-partition hierarchy
2337+
CREATE TABLE parent_tbl (a text, b int);
2338+
ALTER FOREIGN TABLE foreign_tbl INHERIT parent_tbl;
2339+
2340+
CREATE TRIGGER parent_tbl_update_trig
2341+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
2342+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
2343+
CREATE TRIGGER parent_tbl_delete_trig
2344+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
2345+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
2346+
2347+
EXPLAIN (VERBOSE, COSTS OFF)
2348+
UPDATE parent_tbl SET b = b + 1;
2349+
UPDATE parent_tbl SET b = b + 1;
2350+
2351+
EXPLAIN (VERBOSE, COSTS OFF)
2352+
DELETE FROM parent_tbl;
2353+
DELETE FROM parent_tbl;
2354+
2355+
ALTER FOREIGN TABLE foreign_tbl NO INHERIT parent_tbl;
2356+
DROP TABLE parent_tbl;
2357+
2358+
-- Cleanup
2359+
DROP FOREIGN TABLE foreign_tbl;
2360+
DROP TABLE local_tbl;
2361+
22842362
-- ===================================================================
22852363
-- test inheritance features
22862364
-- ===================================================================

src/backend/commands/trigger.c

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

2548+
if (relinfo->ri_FdwRoutine && transition_capture &&
2549+
transition_capture->tcs_insert_new_table)
2550+
{
2551+
Assert(relinfo->ri_RootResultRelInfo);
2552+
ereport(ERROR,
2553+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2554+
errmsg("cannot collect transition tuples from child foreign tables")));
2555+
}
2556+
25482557
if ((trigdesc && trigdesc->trig_insert_after_row) ||
25492558
(transition_capture && transition_capture->tcs_insert_new_table))
25502559
AfterTriggerSaveEvent(estate, relinfo, NULL, NULL,
@@ -2797,6 +2806,15 @@ ExecARDeleteTriggers(EState *estate,
27972806
{
27982807
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
27992808

2809+
if (relinfo->ri_FdwRoutine && transition_capture &&
2810+
transition_capture->tcs_delete_old_table)
2811+
{
2812+
Assert(relinfo->ri_RootResultRelInfo);
2813+
ereport(ERROR,
2814+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2815+
errmsg("cannot collect transition tuples from child foreign tables")));
2816+
}
2817+
28002818
if ((trigdesc && trigdesc->trig_delete_after_row) ||
28012819
(transition_capture && transition_capture->tcs_delete_old_table))
28022820
{
@@ -3134,6 +3152,16 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
31343152
{
31353153
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
31363154

3155+
if (relinfo->ri_FdwRoutine && transition_capture &&
3156+
(transition_capture->tcs_update_old_table ||
3157+
transition_capture->tcs_update_new_table))
3158+
{
3159+
Assert(relinfo->ri_RootResultRelInfo);
3160+
ereport(ERROR,
3161+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3162+
errmsg("cannot collect transition tuples from child foreign tables")));
3163+
}
3164+
31373165
if ((trigdesc && trigdesc->trig_update_after_row) ||
31383166
(transition_capture &&
31393167
(transition_capture->tcs_update_old_table ||

src/backend/optimizer/plan/createplan.c

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7233,6 +7233,8 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
72337233
ModifyTable *node = makeNode(ModifyTable);
72347234
bool returning_old_or_new = false;
72357235
bool returning_old_or_new_valid = false;
7236+
bool transition_tables = false;
7237+
bool transition_tables_valid = false;
72367238
List *fdw_private_list;
72377239
Bitmapset *direct_modify_plans;
72387240
ListCell *lc;
@@ -7379,8 +7381,8 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
73797381
* callback functions needed for that and (2) there are no local
73807382
* structures that need to be run for each modified row: row-level
73817383
* triggers on the foreign table, stored generated columns, WITH CHECK
7382-
* OPTIONs from parent views, or Vars returning OLD/NEW in the
7383-
* RETURNING list.
7384+
* OPTIONs from parent views, Vars returning OLD/NEW in the RETURNING
7385+
* list, or transition tables on the named relation.
73847386
*/
73857387
direct_modify = false;
73867388
if (fdwroutine != NULL &&
@@ -7392,7 +7394,10 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
73927394
!has_row_triggers(root, rti, operation) &&
73937395
!has_stored_generated_columns(root, rti))
73947396
{
7395-
/* returning_old_or_new is the same for all result relations */
7397+
/*
7398+
* returning_old_or_new and transition_tables are the same for all
7399+
* result relations, respectively
7400+
*/
73967401
if (!returning_old_or_new_valid)
73977402
{
73987403
returning_old_or_new =
@@ -7401,7 +7406,18 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
74017406
returning_old_or_new_valid = true;
74027407
}
74037408
if (!returning_old_or_new)
7404-
direct_modify = fdwroutine->PlanDirectModify(root, node, rti, i);
7409+
{
7410+
if (!transition_tables_valid)
7411+
{
7412+
transition_tables = has_transition_tables(root,
7413+
nominalRelation,
7414+
operation);
7415+
transition_tables_valid = true;
7416+
}
7417+
if (!transition_tables)
7418+
direct_modify = fdwroutine->PlanDirectModify(root, node,
7419+
rti, i);
7420+
}
74057421
}
74067422
if (direct_modify)
74077423
direct_modify_plans = bms_add_member(direct_modify_plans, i);

src/backend/optimizer/util/plancat.c

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

2391+
/*
2392+
* has_transition_tables
2393+
*
2394+
* Detect whether the specified relation has any transition tables for event.
2395+
*/
2396+
bool
2397+
has_transition_tables(PlannerInfo *root, Index rti, CmdType event)
2398+
{
2399+
RangeTblEntry *rte = planner_rt_fetch(rti, root);
2400+
Relation relation;
2401+
TriggerDesc *trigDesc;
2402+
bool result = false;
2403+
2404+
Assert(rte->rtekind == RTE_RELATION);
2405+
2406+
/* Currently foreign tables cannot have transition tables */
2407+
if (rte->relkind == RELKIND_FOREIGN_TABLE)
2408+
return result;
2409+
2410+
/* Assume we already have adequate lock */
2411+
relation = table_open(rte->relid, NoLock);
2412+
2413+
trigDesc = relation->trigdesc;
2414+
switch (event)
2415+
{
2416+
case CMD_INSERT:
2417+
if (trigDesc &&
2418+
trigDesc->trig_insert_new_table)
2419+
result = true;
2420+
break;
2421+
case CMD_UPDATE:
2422+
if (trigDesc &&
2423+
(trigDesc->trig_update_old_table ||
2424+
trigDesc->trig_update_new_table))
2425+
result = true;
2426+
break;
2427+
case CMD_DELETE:
2428+
if (trigDesc &&
2429+
trigDesc->trig_delete_old_table)
2430+
result = true;
2431+
break;
2432+
/* There is no separate event for MERGE, only INSERT/UPDATE/DELETE */
2433+
case CMD_MERGE:
2434+
result = false;
2435+
break;
2436+
default:
2437+
elog(ERROR, "unrecognized CmdType: %d", (int) event);
2438+
break;
2439+
}
2440+
2441+
table_close(relation, NoLock);
2442+
return result;
2443+
}
2444+
23912445
/*
23922446
* has_stored_generated_columns
23932447
*

src/include/optimizer/plancat.h

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

7777
extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
7878

79+
extern bool has_transition_tables(PlannerInfo *root, Index rti, CmdType event);
80+
7981
extern bool has_stored_generated_columns(PlannerInfo *root, Index rti);
8082

8183
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