Commit 8b6da83d authored by Etsuro Fujita's avatar Etsuro Fujita

postgres_fdw: Account for triggers in non-direct remote UPDATE planning.

Previously, in postgresPlanForeignModify, we planned an UPDATE operation
on a foreign table so that we transmit only columns that were explicitly
targets of the UPDATE, so as to avoid unnecessary data transmission, but
if there were BEFORE ROW UPDATE triggers on the foreign table, those
triggers might change values for non-target columns, in which case we
would miss sending changed values for those columns.  Prevent optimizing
away transmitting all columns if there are BEFORE ROW UPDATE triggers on
the foreign table.

This is an oversight in commit 7cbe57c3 which added triggers on foreign
tables, so apply the patch all the way back to 9.4 where that came in.

Author: Shohei Mochizuki
Reviewed-by: Amit Langote
Discussion: https://postgr.es/m/201905270152.x4R1q3qi014550@toshiba.co.jp
parent 7dc6ae37
...@@ -6558,6 +6558,25 @@ SELECT * from loc1; ...@@ -6558,6 +6558,25 @@ SELECT * from loc1;
2 | skidoo triggered ! 2 | skidoo triggered !
(2 rows) (2 rows)
EXPLAIN (verbose, costs off)
UPDATE rem1 set f1 = 10; -- all columns should be transmitted
QUERY PLAN
-----------------------------------------------------------------------
Update on public.rem1
Remote SQL: UPDATE public.loc1 SET f1 = $2, f2 = $3 WHERE ctid = $1
-> Foreign Scan on public.rem1
Output: 10, f2, ctid, rem1.*
Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
(5 rows)
UPDATE rem1 set f1 = 10;
SELECT * from loc1;
f1 | f2
----+--------------------------------
10 | skidoo triggered ! triggered !
10 | skidoo triggered ! triggered !
(2 rows)
DELETE FROM rem1; DELETE FROM rem1;
-- Add a second trigger, to check that the changes are propagated correctly -- Add a second trigger, to check that the changes are propagated correctly
-- from trigger to trigger -- from trigger to trigger
...@@ -6670,7 +6689,7 @@ NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem1 ...@@ -6670,7 +6689,7 @@ NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem1
NOTICE: NEW: (13,"test triggered !") NOTICE: NEW: (13,"test triggered !")
ctid ctid
-------- --------
(0,27) (0,29)
(1 row) (1 row)
-- cleanup -- cleanup
...@@ -6775,9 +6794,9 @@ FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); ...@@ -6775,9 +6794,9 @@ FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
EXPLAIN (verbose, costs off) EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = ''; -- can't be pushed down UPDATE rem1 set f2 = ''; -- can't be pushed down
QUERY PLAN QUERY PLAN
--------------------------------------------------------------------- -----------------------------------------------------------------------
Update on public.rem1 Update on public.rem1
Remote SQL: UPDATE public.loc1 SET f2 = $2 WHERE ctid = $1 Remote SQL: UPDATE public.loc1 SET f1 = $2, f2 = $3 WHERE ctid = $1
-> Foreign Scan on public.rem1 -> Foreign Scan on public.rem1
Output: f1, ''::text, ctid, rem1.* Output: f1, ''::text, ctid, rem1.*
Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
...@@ -7405,11 +7424,11 @@ FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); ...@@ -7405,11 +7424,11 @@ FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
explain (verbose, costs off) explain (verbose, costs off)
update bar set f2 = f2 + 100; update bar set f2 = f2 + 100;
QUERY PLAN QUERY PLAN
-------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------
Update on public.bar Update on public.bar
Update on public.bar Update on public.bar
Foreign Update on public.bar2 Foreign Update on public.bar2
Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1 RETURNING f1, f2, f3 Remote SQL: UPDATE public.loct2 SET f1 = $2, f2 = $3, f3 = $4 WHERE ctid = $1 RETURNING f1, f2, f3
-> Seq Scan on public.bar -> Seq Scan on public.bar
Output: bar.f1, (bar.f2 + 100), bar.ctid Output: bar.f1, (bar.f2 + 100), bar.ctid
-> Foreign Scan on public.bar2 -> Foreign Scan on public.bar2
......
...@@ -1686,12 +1686,19 @@ postgresPlanForeignModify(PlannerInfo *root, ...@@ -1686,12 +1686,19 @@ postgresPlanForeignModify(PlannerInfo *root,
/* /*
* In an INSERT, we transmit all columns that are defined in the foreign * In an INSERT, we transmit all columns that are defined in the foreign
* table. In an UPDATE, we transmit only columns that were explicitly * table. In an UPDATE, if there are BEFORE ROW UPDATE triggers on the
* targets of the UPDATE, so as to avoid unnecessary data transmission. * foreign table, we transmit all columns like INSERT; else we transmit
* (We can't do that for INSERT since we would miss sending default values * only columns that were explicitly targets of the UPDATE, so as to avoid
* for columns not listed in the source statement.) * unnecessary data transmission. (We can't do that for INSERT since we
*/ * would miss sending default values for columns not listed in the source
if (operation == CMD_INSERT) * statement, and for UPDATE if there are BEFORE ROW UPDATE triggers since
* those triggers might change values for non-target columns, in which
* case we would miss sending changed values for those columns.)
*/
if (operation == CMD_INSERT ||
(operation == CMD_UPDATE &&
rel->trigdesc &&
rel->trigdesc->trig_update_before_row))
{ {
TupleDesc tupdesc = RelationGetDescr(rel); TupleDesc tupdesc = RelationGetDescr(rel);
int attnum; int attnum;
......
...@@ -1553,6 +1553,11 @@ SELECT * from loc1; ...@@ -1553,6 +1553,11 @@ SELECT * from loc1;
UPDATE rem1 set f2 = 'skidoo' RETURNING f2; UPDATE rem1 set f2 = 'skidoo' RETURNING f2;
SELECT * from loc1; SELECT * from loc1;
EXPLAIN (verbose, costs off)
UPDATE rem1 set f1 = 10; -- all columns should be transmitted
UPDATE rem1 set f1 = 10;
SELECT * from loc1;
DELETE FROM rem1; DELETE FROM rem1;
-- Add a second trigger, to check that the changes are propagated correctly -- Add a second trigger, to check that the changes are propagated correctly
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment