1
0
mirror of https://github.com/postgres/postgres.git synced 2025-08-08 06:02:22 +03:00

Disallow collecting transition tuples from child foreign tables.

Commit 9e6104c66 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 3d956d956, 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
This commit is contained in:
Etsuro Fujita
2025-08-08 10:50:04 +09:00
parent ed2bb0cdda
commit e94fc1a8ad
6 changed files with 287 additions and 2 deletions

View File

@@ -7427,6 +7427,119 @@ DELETE FROM rem1; -- can't be pushed down
(5 rows)
DROP TRIGGER trig_row_after_delete ON rem1;
-- We are allowed to create transition-table triggers on both kinds of
-- inheritance even if they contain foreign tables as children, but currently
-- collecting transition tuples from such foreign tables is not supported.
CREATE TABLE local_tbl (a text, b int);
CREATE FOREIGN TABLE foreign_tbl (a text, b int)
SERVER loopback OPTIONS (table_name 'local_tbl');
INSERT INTO foreign_tbl VALUES ('AAA', 42);
-- Test case for partition hierarchy
CREATE TABLE parent_tbl (a text, b int) PARTITION BY LIST (a);
ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES IN ('AAA');
CREATE TRIGGER parent_tbl_insert_trig
AFTER INSERT ON parent_tbl REFERENCING NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER parent_tbl_update_trig
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER parent_tbl_delete_trig
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
INSERT INTO parent_tbl VALUES ('AAA', 42);
ERROR: cannot collect transition tuples from child foreign tables
COPY parent_tbl (a, b) FROM stdin;
ERROR: cannot collect transition tuples from child foreign tables
CONTEXT: COPY parent_tbl, line 1: "AAA 42"
ALTER SERVER loopback OPTIONS (ADD batch_size '10');
INSERT INTO parent_tbl VALUES ('AAA', 42);
ERROR: cannot collect transition tuples from child foreign tables
COPY parent_tbl (a, b) FROM stdin;
ERROR: cannot collect transition tuples from child foreign tables
CONTEXT: COPY parent_tbl, line 1: "AAA 42"
ALTER SERVER loopback OPTIONS (DROP batch_size);
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE parent_tbl SET b = b + 1;
QUERY PLAN
------------------------------------------------------------------------------------------------
Update on public.parent_tbl
Foreign Update on public.foreign_tbl parent_tbl_1
Remote SQL: UPDATE public.local_tbl SET b = $2 WHERE ctid = $1
-> Foreign Scan on public.foreign_tbl parent_tbl_1
Output: (parent_tbl_1.b + 1), parent_tbl_1.tableoid, parent_tbl_1.ctid, parent_tbl_1.*
Remote SQL: SELECT a, b, ctid FROM public.local_tbl FOR UPDATE
(6 rows)
UPDATE parent_tbl SET b = b + 1;
ERROR: cannot collect transition tuples from child foreign tables
EXPLAIN (VERBOSE, COSTS OFF)
DELETE FROM parent_tbl;
QUERY PLAN
------------------------------------------------------------------
Delete on public.parent_tbl
Foreign Delete on public.foreign_tbl parent_tbl_1
Remote SQL: DELETE FROM public.local_tbl WHERE ctid = $1
-> Foreign Scan on public.foreign_tbl parent_tbl_1
Output: parent_tbl_1.tableoid, parent_tbl_1.ctid
Remote SQL: SELECT ctid FROM public.local_tbl FOR UPDATE
(6 rows)
DELETE FROM parent_tbl;
ERROR: cannot collect transition tuples from child foreign tables
ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl;
DROP TABLE parent_tbl;
-- Test case for non-partition hierarchy
CREATE TABLE parent_tbl (a text, b int);
ALTER FOREIGN TABLE foreign_tbl INHERIT parent_tbl;
CREATE TRIGGER parent_tbl_update_trig
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER parent_tbl_delete_trig
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE parent_tbl SET b = b + 1;
QUERY PLAN
------------------------------------------------------------------------------------------------------
Update on public.parent_tbl
Update on public.parent_tbl parent_tbl_1
Foreign Update on public.foreign_tbl parent_tbl_2
Remote SQL: UPDATE public.local_tbl SET b = $2 WHERE ctid = $1
-> Result
Output: (parent_tbl.b + 1), parent_tbl.tableoid, parent_tbl.ctid, (NULL::record)
-> Append
-> Seq Scan on public.parent_tbl parent_tbl_1
Output: parent_tbl_1.b, parent_tbl_1.tableoid, parent_tbl_1.ctid, NULL::record
-> Foreign Scan on public.foreign_tbl parent_tbl_2
Output: parent_tbl_2.b, parent_tbl_2.tableoid, parent_tbl_2.ctid, parent_tbl_2.*
Remote SQL: SELECT a, b, ctid FROM public.local_tbl FOR UPDATE
(12 rows)
UPDATE parent_tbl SET b = b + 1;
ERROR: cannot collect transition tuples from child foreign tables
EXPLAIN (VERBOSE, COSTS OFF)
DELETE FROM parent_tbl;
QUERY PLAN
------------------------------------------------------------------------
Delete on public.parent_tbl
Delete on public.parent_tbl parent_tbl_1
Foreign Delete on public.foreign_tbl parent_tbl_2
Remote SQL: DELETE FROM public.local_tbl WHERE ctid = $1
-> Append
-> Seq Scan on public.parent_tbl parent_tbl_1
Output: parent_tbl_1.tableoid, parent_tbl_1.ctid
-> Foreign Scan on public.foreign_tbl parent_tbl_2
Output: parent_tbl_2.tableoid, parent_tbl_2.ctid
Remote SQL: SELECT ctid FROM public.local_tbl FOR UPDATE
(10 rows)
DELETE FROM parent_tbl;
ERROR: cannot collect transition tuples from child foreign tables
ALTER FOREIGN TABLE foreign_tbl NO INHERIT parent_tbl;
DROP TABLE parent_tbl;
-- Cleanup
DROP FOREIGN TABLE foreign_tbl;
DROP TABLE local_tbl;
-- ===================================================================
-- test inheritance features
-- ===================================================================

View File

@@ -1949,6 +1949,84 @@ EXPLAIN (verbose, costs off)
DELETE FROM rem1; -- can't be pushed down
DROP TRIGGER trig_row_after_delete ON rem1;
-- We are allowed to create transition-table triggers on both kinds of
-- inheritance even if they contain foreign tables as children, but currently
-- collecting transition tuples from such foreign tables is not supported.
CREATE TABLE local_tbl (a text, b int);
CREATE FOREIGN TABLE foreign_tbl (a text, b int)
SERVER loopback OPTIONS (table_name 'local_tbl');
INSERT INTO foreign_tbl VALUES ('AAA', 42);
-- Test case for partition hierarchy
CREATE TABLE parent_tbl (a text, b int) PARTITION BY LIST (a);
ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES IN ('AAA');
CREATE TRIGGER parent_tbl_insert_trig
AFTER INSERT ON parent_tbl REFERENCING NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER parent_tbl_update_trig
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER parent_tbl_delete_trig
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
INSERT INTO parent_tbl VALUES ('AAA', 42);
COPY parent_tbl (a, b) FROM stdin;
AAA 42
\.
ALTER SERVER loopback OPTIONS (ADD batch_size '10');
INSERT INTO parent_tbl VALUES ('AAA', 42);
COPY parent_tbl (a, b) FROM stdin;
AAA 42
\.
ALTER SERVER loopback OPTIONS (DROP batch_size);
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE parent_tbl SET b = b + 1;
UPDATE parent_tbl SET b = b + 1;
EXPLAIN (VERBOSE, COSTS OFF)
DELETE FROM parent_tbl;
DELETE FROM parent_tbl;
ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl;
DROP TABLE parent_tbl;
-- Test case for non-partition hierarchy
CREATE TABLE parent_tbl (a text, b int);
ALTER FOREIGN TABLE foreign_tbl INHERIT parent_tbl;
CREATE TRIGGER parent_tbl_update_trig
AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER parent_tbl_delete_trig
AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE parent_tbl SET b = b + 1;
UPDATE parent_tbl SET b = b + 1;
EXPLAIN (VERBOSE, COSTS OFF)
DELETE FROM parent_tbl;
DELETE FROM parent_tbl;
ALTER FOREIGN TABLE foreign_tbl NO INHERIT parent_tbl;
DROP TABLE parent_tbl;
-- Cleanup
DROP FOREIGN TABLE foreign_tbl;
DROP TABLE local_tbl;
-- ===================================================================
-- test inheritance features
-- ===================================================================