Re: BEFORE UPDATE trigger on postgres_fdw table not work

From: Amit Langote <Langote_Amit_f8(at)lab(dot)ntt(dot)co(dot)jp>
To: Shohei Mochizuki <shohei(dot)mochizuki(at)toshiba(dot)co(dot)jp>, pgsql-hackers(at)postgresql(dot)org
Subject: Re: BEFORE UPDATE trigger on postgres_fdw table not work
Date: 2019-05-27 08:04:33
Message-ID: bd33677c-5a0d-3743-3f9d-1eab500e20d0@lab.ntt.co.jp
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Mochizuki-san,

On 2019/05/27 10:52, Shohei Mochizuki wrote:
> Hi,
>
> I noticed returning a modified record in a row-level BEFORE UPDATE trigger
> on postgres_fdw foreign tables do not work. Attached patch fixes this issue.
>
> Without attached patch:
>
> postgres=# UPDATE rem1 set f1 = 10;
> postgres=# SELECT * FROM rem1;
>  f1 |         f2
> ----+--------------------
>  10 | update triggered !
> (1 row)
>
> f2 should be updated by trigger, but not.

Indeed. That seems like a bug to me.

> This is because current fdw code adds only columns to RemoteSQL that were
> explicitly targets of the UPDATE as follows.

Yeah. So, the trigger execution correctly modifies the existing tuple
fetched from the remote server, but those changes are then essentially
discarded by postgres_fdw, that is, postgresExecForeignModify().

> With attached patch, f2 is updated by a trigger and "f2 = $3" is added to
> remote SQL
> as follows.
>
> postgres=# UPDATE rem1 set f1 = 10;
> postgres=# select * from rem1;
>  f1 |               f2
> ----+--------------------------------
>  10 | update triggered ! triggered !
> (1 row)
>
> postgres=# EXPLAIN (verbose, costs off)
> postgres-# UPDATE rem1 set f1 = 10;
>                               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)
>
> My patch adds all columns to a target list of remote update query
> as in INSERT case if a before update trigger exists.

Thanks for the patch. It seems to fix the problem as far as I can see.

> I tried to add only columns modified in trigger to the target list of
> a remote update query, but I cannot find simple way to do that because
> update query is built during planning phase at postgresPlanForeignModify
> while it is difficult to decide which columns are modified by a trigger
> until query execution.

I think that the approach in your patch may be fine, but others may disagree.

We don't require row triggers' definition to declare which columns of the
input row it intends to modify. Without that information, the planner
can't determine the exact set of changed columns to transmit to the remote
server. So it's too early, for example, for PlanForeignModify() to
construct an optimal update query which transmits only the columns that
are changed, including those that may be modified by triggers. If the FDW
had delayed the construction of the exact update query to
ExecForeignUpdate(), we could build a more optimal update query, because
by then we will know *all* columns that have changed, including those that
are changed by BEFORE UPDATE row triggers if any. Maybe other FDWs beside
postgres_fdw do that already, so it's possible to rejigger postgres_fdw to
do that too. But considering that such rejiggering is only necessary for
efficiency, I'm not sure if others will agree to pursuing it, especially
if it requires too much code change. Also, in the worst case, we'll end
up generating new query for every row being changed, because the trigger
may change different columns for different rows based on some condition.

Thanks,
Amit

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Michael Paquier 2019-05-27 08:05:12 Re: Why does pg_checksums -r not have a long option?
Previous Message Michael Paquier 2019-05-27 08:02:36 Re: Inconsistent error message wording for REINDEX CONCURRENTLY