Re: [PATCH] refint: Avoid reusing cascade UPDATE plans.

From: Ayush Tiwari <ayushtiwari(dot)slg01(at)gmail(dot)com>
To: Nathan Bossart <nathandbossart(at)gmail(dot)com>
Cc: PostgreSQL Hackers <pgsql-hackers(at)postgresql(dot)org>
Subject: Re: [PATCH] refint: Avoid reusing cascade UPDATE plans.
Date: 2026-05-15 06:43:21
Message-ID: CAJTYsWWEvcVsnJWXgijh5Sb8+odYs7ZS4pNPN2Va1Un5w5jATg@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

On Fri, 15 May 2026 at 02:44, Nathan Bossart <nathandbossart(at)gmail(dot)com>
wrote:

>
> That being said, the plan cache still has problems. There's no
> invalidation mechanism, so you could still end up with the wrong plan.
> Here's a quick example:
>
> CREATE EXTENSION refint;
>
> CREATE TABLE p AS SELECT 1 AS a;
> CREATE TABLE f1 AS SELECT 1 AS a;
> CREATE TABLE f2 AS SELECT 1 AS a;
> CREATE TRIGGER t
> AFTER DELETE OR UPDATE ON p
> FOR EACH ROW EXECUTE PROCEDURE
> check_foreign_key(2, 'c', 'a', 'f1', 'a', 'f2', 'a');
> UPDATE p SET a = 2;
>
> DROP TRIGGER t ON p;
> CREATE TRIGGER t
> AFTER DELETE OR UPDATE ON p
> FOR EACH ROW EXECUTE PROCEDURE
> check_foreign_key(1, 'c', 'a', 'f1', 'a');
> UPDATE p SET a = 3;
>
> The last UPDATE fails with:
>
> ERROR: t: check_foreign_key: # of plans changed in meantime
>
> A simple way to fix this could be to use the trigger OID instead of the
> trigger name in the plan cache key. That's not perfect because the OID
> could be reused, but IMHO it's better than what's there today. An even
> better approach would involve more sophisticated invalidation or removing
> the cache altogether.
>
>
Thanks for the review and the drop/recreate example.

After looking at it again, the root issue is that check_foreign_key()'s
private plan cache is not robust enough for the SQL it stores: the
cascade UPDATE query text embeds the current NEW key values in its SET
clause, and the cache itself has no invalidation mechanism, so the
trigger-name-keyed entries survive a drop and recreate. Rather than
keep teaching the cache more special cases, v2 just removes the cache
from check_foreign_key() entirely. Plans are prepared on each trigger
invocation and released by SPI_finish(). refint is example code, so I
think the simplicity is worth more than the per-call SPI_prepare.

check_primary_key() still uses the existing per-trigger cache; its
generated SELECT does not embed row values and the same invalidation
concern is far less interesting there, so I left it alone for now.

v2 keeps the cascade-UPDATE regression test from v1 and adds a second
test that drops and recreates a cascade trigger with a different number
of references, which reproduces your example.

Regards,
Ayush

Attachment Content-Type Size
v2-0001-refint-Remove-plan-cache-from-check_foreign_key.patch application/octet-stream 10.6 KB

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Michael Paquier 2026-05-15 06:54:59 Re: Re-add recently-removed tests for ltree and intarray
Previous Message shveta malik 2026-05-15 06:36:19 Re: Bound memory usage during manual slot sync retries