Skip to content

Commit e94fc1a

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 ed2bb0c commit e94fc1a

File tree

6 files changed

+287
-2
lines changed

6 files changed

+287
-2
lines changed

contrib/postgres_fdw/expected/postgres_fdw.out

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

74297429
DROP TRIGGER trig_row_after_delete ON rem1;
7430+
-- We are allowed to create transition-table triggers on both kinds of
7431+
-- inheritance even if they contain foreign tables as children, but currently
7432+
-- collecting transition tuples from such foreign tables is not supported.
7433+
CREATE TABLE local_tbl (a text, b int);
7434+
CREATE FOREIGN TABLE foreign_tbl (a text, b int)
7435+
SERVER loopback OPTIONS (table_name 'local_tbl');
7436+
INSERT INTO foreign_tbl VALUES ('AAA', 42);
7437+
-- Test case for partition hierarchy
7438+
CREATE TABLE parent_tbl (a text, b int) PARTITION BY LIST (a);
7439+
ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES IN ('AAA');
7440+
CREATE TRIGGER parent_tbl_insert_trig
7441+
AFTER INSERT ON parent_tbl REFERENCING NEW TABLE AS new_table
7442+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
7443+
CREATE TRIGGER parent_tbl_update_trig
7444+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
7445+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
7446+
CREATE TRIGGER parent_tbl_delete_trig
7447+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
7448+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
7449+
INSERT INTO parent_tbl VALUES ('AAA', 42);
7450+
ERROR: cannot collect transition tuples from child foreign tables
7451+
COPY parent_tbl (a, b) FROM stdin;
7452+
ERROR: cannot collect transition tuples from child foreign tables
7453+
CONTEXT: COPY parent_tbl, line 1: "AAA 42"
7454+
ALTER SERVER loopback OPTIONS (ADD batch_size '10');
7455+
INSERT INTO parent_tbl VALUES ('AAA', 42);
7456+
ERROR: cannot collect transition tuples from child foreign tables
7457+
COPY parent_tbl (a, b) FROM stdin;
7458+
ERROR: cannot collect transition tuples from child foreign tables
7459+
CONTEXT: COPY parent_tbl, line 1: "AAA 42"
7460+
ALTER SERVER loopback OPTIONS (DROP batch_size);
7461+
EXPLAIN (VERBOSE, COSTS OFF)
7462+
UPDATE parent_tbl SET b = b + 1;
7463+
QUERY PLAN
7464+
------------------------------------------------------------------------------------------------
7465+
Update on public.parent_tbl
7466+
Foreign Update on public.foreign_tbl parent_tbl_1
7467+
Remote SQL: UPDATE public.local_tbl SET b = $2 WHERE ctid = $1
7468+
-> Foreign Scan on public.foreign_tbl parent_tbl_1
7469+
Output: (parent_tbl_1.b + 1), parent_tbl_1.tableoid, parent_tbl_1.ctid, parent_tbl_1.*
7470+
Remote SQL: SELECT a, b, ctid FROM public.local_tbl FOR UPDATE
7471+
(6 rows)
7472+
7473+
UPDATE parent_tbl SET b = b + 1;
7474+
ERROR: cannot collect transition tuples from child foreign tables
7475+
EXPLAIN (VERBOSE, COSTS OFF)
7476+
DELETE FROM parent_tbl;
7477+
QUERY PLAN
7478+
------------------------------------------------------------------
7479+
Delete on public.parent_tbl
7480+
Foreign Delete on public.foreign_tbl parent_tbl_1
7481+
Remote SQL: DELETE FROM public.local_tbl WHERE ctid = $1
7482+
-> Foreign Scan on public.foreign_tbl parent_tbl_1
7483+
Output: parent_tbl_1.tableoid, parent_tbl_1.ctid
7484+
Remote SQL: SELECT ctid FROM public.local_tbl FOR UPDATE
7485+
(6 rows)
7486+
7487+
DELETE FROM parent_tbl;
7488+
ERROR: cannot collect transition tuples from child foreign tables
7489+
ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl;
7490+
DROP TABLE parent_tbl;
7491+
-- Test case for non-partition hierarchy
7492+
CREATE TABLE parent_tbl (a text, b int);
7493+
ALTER FOREIGN TABLE foreign_tbl INHERIT parent_tbl;
7494+
CREATE TRIGGER parent_tbl_update_trig
7495+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
7496+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
7497+
CREATE TRIGGER parent_tbl_delete_trig
7498+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
7499+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
7500+
EXPLAIN (VERBOSE, COSTS OFF)
7501+
UPDATE parent_tbl SET b = b + 1;
7502+
QUERY PLAN
7503+
------------------------------------------------------------------------------------------------------
7504+
Update on public.parent_tbl
7505+
Update on public.parent_tbl parent_tbl_1
7506+
Foreign Update on public.foreign_tbl parent_tbl_2
7507+
Remote SQL: UPDATE public.local_tbl SET b = $2 WHERE ctid = $1
7508+
-> Result
7509+
Output: (parent_tbl.b + 1), parent_tbl.tableoid, parent_tbl.ctid, (NULL::record)
7510+
-> Append
7511+
-> Seq Scan on public.parent_tbl parent_tbl_1
7512+
Output: parent_tbl_1.b, parent_tbl_1.tableoid, parent_tbl_1.ctid, NULL::record
7513+
-> Foreign Scan on public.foreign_tbl parent_tbl_2
7514+
Output: parent_tbl_2.b, parent_tbl_2.tableoid, parent_tbl_2.ctid, parent_tbl_2.*
7515+
Remote SQL: SELECT a, b, ctid FROM public.local_tbl FOR UPDATE
7516+
(12 rows)
7517+
7518+
UPDATE parent_tbl SET b = b + 1;
7519+
ERROR: cannot collect transition tuples from child foreign tables
7520+
EXPLAIN (VERBOSE, COSTS OFF)
7521+
DELETE FROM parent_tbl;
7522+
QUERY PLAN
7523+
------------------------------------------------------------------------
7524+
Delete on public.parent_tbl
7525+
Delete on public.parent_tbl parent_tbl_1
7526+
Foreign Delete on public.foreign_tbl parent_tbl_2
7527+
Remote SQL: DELETE FROM public.local_tbl WHERE ctid = $1
7528+
-> Append
7529+
-> Seq Scan on public.parent_tbl parent_tbl_1
7530+
Output: parent_tbl_1.tableoid, parent_tbl_1.ctid
7531+
-> Foreign Scan on public.foreign_tbl parent_tbl_2
7532+
Output: parent_tbl_2.tableoid, parent_tbl_2.ctid
7533+
Remote SQL: SELECT ctid FROM public.local_tbl FOR UPDATE
7534+
(10 rows)
7535+
7536+
DELETE FROM parent_tbl;
7537+
ERROR: cannot collect transition tuples from child foreign tables
7538+
ALTER FOREIGN TABLE foreign_tbl NO INHERIT parent_tbl;
7539+
DROP TABLE parent_tbl;
7540+
-- Cleanup
7541+
DROP FOREIGN TABLE foreign_tbl;
7542+
DROP TABLE local_tbl;
74307543
-- ===================================================================
74317544
-- test inheritance features
74327545
-- ===================================================================

contrib/postgres_fdw/sql/postgres_fdw.sql

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

1952+
1953+
-- We are allowed to create transition-table triggers on both kinds of
1954+
-- inheritance even if they contain foreign tables as children, but currently
1955+
-- collecting transition tuples from such foreign tables is not supported.
1956+
1957+
CREATE TABLE local_tbl (a text, b int);
1958+
CREATE FOREIGN TABLE foreign_tbl (a text, b int)
1959+
SERVER loopback OPTIONS (table_name 'local_tbl');
1960+
1961+
INSERT INTO foreign_tbl VALUES ('AAA', 42);
1962+
1963+
-- Test case for partition hierarchy
1964+
CREATE TABLE parent_tbl (a text, b int) PARTITION BY LIST (a);
1965+
ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES IN ('AAA');
1966+
1967+
CREATE TRIGGER parent_tbl_insert_trig
1968+
AFTER INSERT ON parent_tbl REFERENCING NEW TABLE AS new_table
1969+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
1970+
CREATE TRIGGER parent_tbl_update_trig
1971+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
1972+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
1973+
CREATE TRIGGER parent_tbl_delete_trig
1974+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
1975+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
1976+
1977+
INSERT INTO parent_tbl VALUES ('AAA', 42);
1978+
1979+
COPY parent_tbl (a, b) FROM stdin;
1980+
AAA 42
1981+
\.
1982+
1983+
ALTER SERVER loopback OPTIONS (ADD batch_size '10');
1984+
1985+
INSERT INTO parent_tbl VALUES ('AAA', 42);
1986+
1987+
COPY parent_tbl (a, b) FROM stdin;
1988+
AAA 42
1989+
\.
1990+
1991+
ALTER SERVER loopback OPTIONS (DROP batch_size);
1992+
1993+
EXPLAIN (VERBOSE, COSTS OFF)
1994+
UPDATE parent_tbl SET b = b + 1;
1995+
UPDATE parent_tbl SET b = b + 1;
1996+
1997+
EXPLAIN (VERBOSE, COSTS OFF)
1998+
DELETE FROM parent_tbl;
1999+
DELETE FROM parent_tbl;
2000+
2001+
ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl;
2002+
DROP TABLE parent_tbl;
2003+
2004+
-- Test case for non-partition hierarchy
2005+
CREATE TABLE parent_tbl (a text, b int);
2006+
ALTER FOREIGN TABLE foreign_tbl INHERIT parent_tbl;
2007+
2008+
CREATE TRIGGER parent_tbl_update_trig
2009+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
2010+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
2011+
CREATE TRIGGER parent_tbl_delete_trig
2012+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
2013+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
2014+
2015+
EXPLAIN (VERBOSE, COSTS OFF)
2016+
UPDATE parent_tbl SET b = b + 1;
2017+
UPDATE parent_tbl SET b = b + 1;
2018+
2019+
EXPLAIN (VERBOSE, COSTS OFF)
2020+
DELETE FROM parent_tbl;
2021+
DELETE FROM parent_tbl;
2022+
2023+
ALTER FOREIGN TABLE foreign_tbl NO INHERIT parent_tbl;
2024+
DROP TABLE parent_tbl;
2025+
2026+
-- Cleanup
2027+
DROP FOREIGN TABLE foreign_tbl;
2028+
DROP TABLE local_tbl;
2029+
19522030
-- ===================================================================
19532031
-- test inheritance features
19542032
-- ===================================================================

src/backend/commands/trigger.c

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

2379+
if (relinfo->ri_FdwRoutine && transition_capture &&
2380+
transition_capture->tcs_insert_new_table)
2381+
{
2382+
Assert(relinfo->ri_RootResultRelInfo);
2383+
ereport(ERROR,
2384+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2385+
errmsg("cannot collect transition tuples from child foreign tables")));
2386+
}
2387+
23792388
if ((trigdesc && trigdesc->trig_insert_after_row) ||
23802389
(transition_capture && transition_capture->tcs_insert_new_table))
23812390
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
@@ -2607,6 +2616,15 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
26072616
{
26082617
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
26092618

2619+
if (relinfo->ri_FdwRoutine && transition_capture &&
2620+
transition_capture->tcs_delete_old_table)
2621+
{
2622+
Assert(relinfo->ri_RootResultRelInfo);
2623+
ereport(ERROR,
2624+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2625+
errmsg("cannot collect transition tuples from child foreign tables")));
2626+
}
2627+
26102628
if ((trigdesc && trigdesc->trig_delete_after_row) ||
26112629
(transition_capture && transition_capture->tcs_delete_old_table))
26122630
{
@@ -2911,6 +2929,16 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
29112929
{
29122930
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
29132931

2932+
if (relinfo->ri_FdwRoutine && transition_capture &&
2933+
(transition_capture->tcs_update_old_table ||
2934+
transition_capture->tcs_update_new_table))
2935+
{
2936+
Assert(relinfo->ri_RootResultRelInfo);
2937+
ereport(ERROR,
2938+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2939+
errmsg("cannot collect transition tuples from child foreign tables")));
2940+
}
2941+
29142942
if ((trigdesc && trigdesc->trig_update_after_row) ||
29152943
(transition_capture &&
29162944
(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
@@ -6935,6 +6935,8 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
69356935
List *rowMarks, OnConflictExpr *onconflict, int epqParam)
69366936
{
69376937
ModifyTable *node = makeNode(ModifyTable);
6938+
bool transition_tables = false;
6939+
bool transition_tables_valid = false;
69386940
List *fdw_private_list;
69396941
Bitmapset *direct_modify_plans;
69406942
ListCell *lc;
@@ -7058,7 +7060,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
70587060
* callback functions needed for that and (2) there are no local
70597061
* structures that need to be run for each modified row: row-level
70607062
* triggers on the foreign table, stored generated columns, WITH CHECK
7061-
* OPTIONs from parent views.
7063+
* OPTIONs from parent views, transition tables on the named relation.
70627064
*/
70637065
direct_modify = false;
70647066
if (fdwroutine != NULL &&
@@ -7069,7 +7071,19 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
70697071
withCheckOptionLists == NIL &&
70707072
!has_row_triggers(root, rti, operation) &&
70717073
!has_stored_generated_columns(root, rti))
7072-
direct_modify = fdwroutine->PlanDirectModify(root, node, rti, i);
7074+
{
7075+
/* transition_tables is the same for all result relations */
7076+
if (!transition_tables_valid)
7077+
{
7078+
transition_tables = has_transition_tables(root,
7079+
nominalRelation,
7080+
operation);
7081+
transition_tables_valid = true;
7082+
}
7083+
if (!transition_tables)
7084+
direct_modify = fdwroutine->PlanDirectModify(root, node,
7085+
rti, i);
7086+
}
70737087
if (direct_modify)
70747088
direct_modify_plans = bms_add_member(direct_modify_plans, i);
70757089

src/backend/optimizer/util/plancat.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2190,6 +2190,56 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
21902190
return result;
21912191
}
21922192

2193+
/*
2194+
* has_transition_tables
2195+
*
2196+
* Detect whether the specified relation has any transition tables for event.
2197+
*/
2198+
bool
2199+
has_transition_tables(PlannerInfo *root, Index rti, CmdType event)
2200+
{
2201+
RangeTblEntry *rte = planner_rt_fetch(rti, root);
2202+
Relation relation;
2203+
TriggerDesc *trigDesc;
2204+
bool result = false;
2205+
2206+
Assert(rte->rtekind == RTE_RELATION);
2207+
2208+
/* Currently foreign tables cannot have transition tables */
2209+
if (rte->relkind == RELKIND_FOREIGN_TABLE)
2210+
return result;
2211+
2212+
/* Assume we already have adequate lock */
2213+
relation = table_open(rte->relid, NoLock);
2214+
2215+
trigDesc = relation->trigdesc;
2216+
switch (event)
2217+
{
2218+
case CMD_INSERT:
2219+
if (trigDesc &&
2220+
trigDesc->trig_insert_new_table)
2221+
result = true;
2222+
break;
2223+
case CMD_UPDATE:
2224+
if (trigDesc &&
2225+
(trigDesc->trig_update_old_table ||
2226+
trigDesc->trig_update_new_table))
2227+
result = true;
2228+
break;
2229+
case CMD_DELETE:
2230+
if (trigDesc &&
2231+
trigDesc->trig_delete_old_table)
2232+
result = true;
2233+
break;
2234+
default:
2235+
elog(ERROR, "unrecognized CmdType: %d", (int) event);
2236+
break;
2237+
}
2238+
2239+
table_close(relation, NoLock);
2240+
return result;
2241+
}
2242+
21932243
/*
21942244
* has_stored_generated_columns
21952245
*

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