Skip to content

Commit b6641f7

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 de7fd83 commit b6641f7

File tree

6 files changed

+260
-2
lines changed

6 files changed

+260
-2
lines changed

contrib/postgres_fdw/expected/postgres_fdw.out

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7241,6 +7241,108 @@ DELETE FROM rem1; -- can't be pushed down
72417241
(5 rows)
72427242

72437243
DROP TRIGGER trig_row_after_delete ON rem1;
7244+
-- We are allowed to create transition-table triggers on both kinds of
7245+
-- inheritance even if they contain foreign tables as children, but currently
7246+
-- collecting transition tuples from such foreign tables is not supported.
7247+
CREATE TABLE local_tbl (a text, b int);
7248+
CREATE FOREIGN TABLE foreign_tbl (a text, b int)
7249+
SERVER loopback OPTIONS (table_name 'local_tbl');
7250+
INSERT INTO foreign_tbl VALUES ('AAA', 42);
7251+
-- Test case for partition hierarchy
7252+
CREATE TABLE parent_tbl (a text, b int) PARTITION BY LIST (a);
7253+
ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES IN ('AAA');
7254+
CREATE TRIGGER parent_tbl_insert_trig
7255+
AFTER INSERT ON parent_tbl REFERENCING NEW TABLE AS new_table
7256+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
7257+
CREATE TRIGGER parent_tbl_update_trig
7258+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
7259+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
7260+
CREATE TRIGGER parent_tbl_delete_trig
7261+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
7262+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
7263+
INSERT INTO parent_tbl VALUES ('AAA', 42);
7264+
ERROR: cannot collect transition tuples from child foreign tables
7265+
COPY parent_tbl (a, b) FROM stdin;
7266+
ERROR: cannot collect transition tuples from child foreign tables
7267+
CONTEXT: COPY parent_tbl, line 1: "AAA 42"
7268+
EXPLAIN (VERBOSE, COSTS OFF)
7269+
UPDATE parent_tbl SET b = b + 1;
7270+
QUERY PLAN
7271+
-------------------------------------------------------------------------
7272+
Update on public.parent_tbl
7273+
Foreign Update on public.foreign_tbl parent_tbl_1
7274+
Remote SQL: UPDATE public.local_tbl SET b = $2 WHERE ctid = $1
7275+
-> Foreign Scan on public.foreign_tbl parent_tbl_1
7276+
Output: parent_tbl_1.a, (parent_tbl_1.b + 1), parent_tbl_1.ctid
7277+
Remote SQL: SELECT a, b, ctid FROM public.local_tbl FOR UPDATE
7278+
(6 rows)
7279+
7280+
UPDATE parent_tbl SET b = b + 1;
7281+
ERROR: cannot collect transition tuples from child foreign tables
7282+
EXPLAIN (VERBOSE, COSTS OFF)
7283+
DELETE FROM parent_tbl;
7284+
QUERY PLAN
7285+
------------------------------------------------------------------
7286+
Delete on public.parent_tbl
7287+
Foreign Delete on public.foreign_tbl parent_tbl_1
7288+
Remote SQL: DELETE FROM public.local_tbl WHERE ctid = $1
7289+
-> Foreign Scan on public.foreign_tbl parent_tbl_1
7290+
Output: parent_tbl_1.ctid
7291+
Remote SQL: SELECT ctid FROM public.local_tbl FOR UPDATE
7292+
(6 rows)
7293+
7294+
DELETE FROM parent_tbl;
7295+
ERROR: cannot collect transition tuples from child foreign tables
7296+
ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl;
7297+
DROP TABLE parent_tbl;
7298+
-- Test case for non-partition hierarchy
7299+
CREATE TABLE parent_tbl (a text, b int);
7300+
ALTER FOREIGN TABLE foreign_tbl INHERIT parent_tbl;
7301+
CREATE TRIGGER parent_tbl_update_trig
7302+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
7303+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
7304+
CREATE TRIGGER parent_tbl_delete_trig
7305+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
7306+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
7307+
EXPLAIN (VERBOSE, COSTS OFF)
7308+
UPDATE parent_tbl SET b = b + 1;
7309+
QUERY PLAN
7310+
-------------------------------------------------------------------------
7311+
Update on public.parent_tbl
7312+
Update on public.parent_tbl
7313+
Foreign Update on public.foreign_tbl parent_tbl_1
7314+
Remote SQL: UPDATE public.local_tbl SET b = $2 WHERE ctid = $1
7315+
-> Seq Scan on public.parent_tbl
7316+
Output: parent_tbl.a, (parent_tbl.b + 1), parent_tbl.ctid
7317+
-> Foreign Scan on public.foreign_tbl parent_tbl_1
7318+
Output: parent_tbl_1.a, (parent_tbl_1.b + 1), parent_tbl_1.ctid
7319+
Remote SQL: SELECT a, b, ctid FROM public.local_tbl FOR UPDATE
7320+
(9 rows)
7321+
7322+
UPDATE parent_tbl SET b = b + 1;
7323+
ERROR: cannot collect transition tuples from child foreign tables
7324+
EXPLAIN (VERBOSE, COSTS OFF)
7325+
DELETE FROM parent_tbl;
7326+
QUERY PLAN
7327+
------------------------------------------------------------------
7328+
Delete on public.parent_tbl
7329+
Delete on public.parent_tbl
7330+
Foreign Delete on public.foreign_tbl parent_tbl_1
7331+
Remote SQL: DELETE FROM public.local_tbl WHERE ctid = $1
7332+
-> Seq Scan on public.parent_tbl
7333+
Output: parent_tbl.ctid
7334+
-> Foreign Scan on public.foreign_tbl parent_tbl_1
7335+
Output: parent_tbl_1.ctid
7336+
Remote SQL: SELECT ctid FROM public.local_tbl FOR UPDATE
7337+
(9 rows)
7338+
7339+
DELETE FROM parent_tbl;
7340+
ERROR: cannot collect transition tuples from child foreign tables
7341+
ALTER FOREIGN TABLE foreign_tbl NO INHERIT parent_tbl;
7342+
DROP TABLE parent_tbl;
7343+
-- Cleanup
7344+
DROP FOREIGN TABLE foreign_tbl;
7345+
DROP TABLE local_tbl;
72447346
-- ===================================================================
72457347
-- test inheritance features
72467348
-- ===================================================================

contrib/postgres_fdw/sql/postgres_fdw.sql

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1857,6 +1857,74 @@ EXPLAIN (verbose, costs off)
18571857
DELETE FROM rem1; -- can't be pushed down
18581858
DROP TRIGGER trig_row_after_delete ON rem1;
18591859

1860+
1861+
-- We are allowed to create transition-table triggers on both kinds of
1862+
-- inheritance even if they contain foreign tables as children, but currently
1863+
-- collecting transition tuples from such foreign tables is not supported.
1864+
1865+
CREATE TABLE local_tbl (a text, b int);
1866+
CREATE FOREIGN TABLE foreign_tbl (a text, b int)
1867+
SERVER loopback OPTIONS (table_name 'local_tbl');
1868+
1869+
INSERT INTO foreign_tbl VALUES ('AAA', 42);
1870+
1871+
-- Test case for partition hierarchy
1872+
CREATE TABLE parent_tbl (a text, b int) PARTITION BY LIST (a);
1873+
ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES IN ('AAA');
1874+
1875+
CREATE TRIGGER parent_tbl_insert_trig
1876+
AFTER INSERT ON parent_tbl REFERENCING NEW TABLE AS new_table
1877+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
1878+
CREATE TRIGGER parent_tbl_update_trig
1879+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
1880+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
1881+
CREATE TRIGGER parent_tbl_delete_trig
1882+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
1883+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
1884+
1885+
INSERT INTO parent_tbl VALUES ('AAA', 42);
1886+
1887+
COPY parent_tbl (a, b) FROM stdin;
1888+
AAA 42
1889+
\.
1890+
1891+
EXPLAIN (VERBOSE, COSTS OFF)
1892+
UPDATE parent_tbl SET b = b + 1;
1893+
UPDATE parent_tbl SET b = b + 1;
1894+
1895+
EXPLAIN (VERBOSE, COSTS OFF)
1896+
DELETE FROM parent_tbl;
1897+
DELETE FROM parent_tbl;
1898+
1899+
ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl;
1900+
DROP TABLE parent_tbl;
1901+
1902+
-- Test case for non-partition hierarchy
1903+
CREATE TABLE parent_tbl (a text, b int);
1904+
ALTER FOREIGN TABLE foreign_tbl INHERIT parent_tbl;
1905+
1906+
CREATE TRIGGER parent_tbl_update_trig
1907+
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
1908+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
1909+
CREATE TRIGGER parent_tbl_delete_trig
1910+
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
1911+
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
1912+
1913+
EXPLAIN (VERBOSE, COSTS OFF)
1914+
UPDATE parent_tbl SET b = b + 1;
1915+
UPDATE parent_tbl SET b = b + 1;
1916+
1917+
EXPLAIN (VERBOSE, COSTS OFF)
1918+
DELETE FROM parent_tbl;
1919+
DELETE FROM parent_tbl;
1920+
1921+
ALTER FOREIGN TABLE foreign_tbl NO INHERIT parent_tbl;
1922+
DROP TABLE parent_tbl;
1923+
1924+
-- Cleanup
1925+
DROP FOREIGN TABLE foreign_tbl;
1926+
DROP TABLE local_tbl;
1927+
18601928
-- ===================================================================
18611929
-- test inheritance features
18621930
-- ===================================================================

src/backend/commands/trigger.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2301,6 +2301,15 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
23012301
{
23022302
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
23032303

2304+
if (relinfo->ri_FdwRoutine && transition_capture &&
2305+
transition_capture->tcs_insert_new_table)
2306+
{
2307+
Assert(relinfo->ri_RootResultRelInfo);
2308+
ereport(ERROR,
2309+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2310+
errmsg("cannot collect transition tuples from child foreign tables")));
2311+
}
2312+
23042313
if ((trigdesc && trigdesc->trig_insert_after_row) ||
23052314
(transition_capture && transition_capture->tcs_insert_new_table))
23062315
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
@@ -2533,6 +2542,12 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
25332542
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
25342543
TupleTableSlot *slot = ExecGetTriggerOldSlot(estate, relinfo);
25352544

2545+
if (relinfo->ri_FdwRoutine && transition_capture &&
2546+
transition_capture->tcs_delete_old_table)
2547+
ereport(ERROR,
2548+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2549+
errmsg("cannot collect transition tuples from child foreign tables")));
2550+
25362551
if ((trigdesc && trigdesc->trig_delete_after_row) ||
25372552
(transition_capture && transition_capture->tcs_delete_old_table))
25382553
{
@@ -2816,6 +2831,13 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
28162831
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
28172832
TupleTableSlot *oldslot = ExecGetTriggerOldSlot(estate, relinfo);
28182833

2834+
if (relinfo->ri_FdwRoutine && transition_capture &&
2835+
(transition_capture->tcs_update_old_table ||
2836+
transition_capture->tcs_update_new_table))
2837+
ereport(ERROR,
2838+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2839+
errmsg("cannot collect transition tuples from child foreign tables")));
2840+
28192841
ExecClearTuple(oldslot);
28202842

28212843
if ((trigdesc && trigdesc->trig_update_after_row) ||

src/backend/optimizer/plan/createplan.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6726,6 +6726,8 @@ make_modifytable(PlannerInfo *root,
67266726
List *rowMarks, OnConflictExpr *onconflict, int epqParam)
67276727
{
67286728
ModifyTable *node = makeNode(ModifyTable);
6729+
bool transition_tables = false;
6730+
bool transition_tables_valid = false;
67296731
List *fdw_private_list;
67306732
Bitmapset *direct_modify_plans;
67316733
ListCell *lc;
@@ -6842,7 +6844,7 @@ make_modifytable(PlannerInfo *root,
68426844
* callback functions needed for that and (2) there are no local
68436845
* structures that need to be run for each modified row: row-level
68446846
* triggers on the foreign table, stored generated columns, WITH CHECK
6845-
* OPTIONs from parent views.
6847+
* OPTIONs from parent views, transition tables on the named relation.
68466848
*/
68476849
direct_modify = false;
68486850
if (fdwroutine != NULL &&
@@ -6853,7 +6855,19 @@ make_modifytable(PlannerInfo *root,
68536855
withCheckOptionLists == NIL &&
68546856
!has_row_triggers(subroot, rti, operation) &&
68556857
!has_stored_generated_columns(subroot, rti))
6856-
direct_modify = fdwroutine->PlanDirectModify(subroot, node, rti, i);
6858+
{
6859+
/* transition_tables is the same for all result relations */
6860+
if (!transition_tables_valid)
6861+
{
6862+
transition_tables = has_transition_tables(root,
6863+
nominalRelation,
6864+
operation);
6865+
transition_tables_valid = true;
6866+
}
6867+
if (!transition_tables)
6868+
direct_modify = fdwroutine->PlanDirectModify(subroot, node,
6869+
rti, i);
6870+
}
68576871
if (direct_modify)
68586872
direct_modify_plans = bms_add_member(direct_modify_plans, i);
68596873

src/backend/optimizer/util/plancat.c

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

2137+
/*
2138+
* has_transition_tables
2139+
*
2140+
* Detect whether the specified relation has any transition tables for event.
2141+
*/
2142+
bool
2143+
has_transition_tables(PlannerInfo *root, Index rti, CmdType event)
2144+
{
2145+
RangeTblEntry *rte = planner_rt_fetch(rti, root);
2146+
Relation relation;
2147+
TriggerDesc *trigDesc;
2148+
bool result = false;
2149+
2150+
Assert(rte->rtekind == RTE_RELATION);
2151+
2152+
/* Currently foreign tables cannot have transition tables */
2153+
if (rte->relkind == RELKIND_FOREIGN_TABLE)
2154+
return result;
2155+
2156+
/* Assume we already have adequate lock */
2157+
relation = table_open(rte->relid, NoLock);
2158+
2159+
trigDesc = relation->trigdesc;
2160+
switch (event)
2161+
{
2162+
case CMD_INSERT:
2163+
if (trigDesc &&
2164+
trigDesc->trig_insert_new_table)
2165+
result = true;
2166+
break;
2167+
case CMD_UPDATE:
2168+
if (trigDesc &&
2169+
(trigDesc->trig_update_old_table ||
2170+
trigDesc->trig_update_new_table))
2171+
result = true;
2172+
break;
2173+
case CMD_DELETE:
2174+
if (trigDesc &&
2175+
trigDesc->trig_delete_old_table)
2176+
result = true;
2177+
break;
2178+
default:
2179+
elog(ERROR, "unrecognized CmdType: %d", (int) event);
2180+
break;
2181+
}
2182+
2183+
table_close(relation, NoLock);
2184+
return result;
2185+
}
2186+
21372187
/*
21382188
* has_stored_generated_columns
21392189
*

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