Hi,

On Sat, Sep 19, 2026 at 3:59 PM Amit Langote <[email protected]> wrote:
>
> Invalidate RI fast-path metadata on operator family changes
>
> The RI fast path checks a foreign key by probing the referenced
> unique index directly, using the equality operator recorded for
> the constraint.  Whether the fast path can be used is decided once
> and cached in RI_ConstraintInfo, and that cache is invalidated on
> pg_constraint changes but not on pg_amop.  So after an ALTER
> OPERATOR FAMILY drops the recorded operator and adds another in its
> place, the cached decision is stale, and the next fast-path check
> probes the index with an operator no longer in the opfamily and
> errors out with "operator XXX is not a member of opfamily XXX".
> The SPI path is unaffected, because the planner just stops matching
> the index.
>
> To fix, register an AMOPOPID syscache callback to flush the RI
> cache on pg_amop changes, and have ri_check_fastpath_index()
> recheck that the recorded operator is still the equality member of
> the index's opfamily, falling back to SPI when it is not.
>
> Add regression test coverage.
>
> Reported-by: Nikolay Samokhvalov <[email protected]>
> Author: Nikolay Samokhvalov <[email protected]>
> Discussion: 
> https://www.postgr.es/m/CAM527d9PzFzagr67N0%3DEx2ng1p5HzrcAszy3j5OoZKHXQMARXA%40mail.gmail.com
> Backpatch-through: 19
>
> Branch
> ------
> master
>
> Details
> -------
> https://git.postgresql.org/pg/commitdiff/c62b330912e2095dc8dee2f749adf7e5d94ca611
>
> Modified Files
> --------------
> src/backend/utils/adt/ri_triggers.c       | 45 ++++++++++++++++++++-
> src/test/regress/expected/foreign_key.out | 67 +++++++++++++++++++++++++++++++
> src/test/regress/sql/foreign_key.sql      | 56 ++++++++++++++++++++++++++
> 3 files changed, 166 insertions(+), 2 deletions(-)

I noticed a few BF failures, after pushing the above changes:

sifaka / master:

# diff -U3 
/Users/buildfarm/bf-data/HEAD/pgsql.build/src/test/regress/expected/window.out
/Users/buildfarm/bf-data/HEAD/pgsql.build/src/test/regress/results/window.out
# --- 
/Users/buildfarm/bf-data/HEAD/pgsql.build/src/test/regress/expected/window.out
2026-09-19 02:53:30
# +++ 
/Users/buildfarm/bf-data/HEAD/pgsql.build/src/test/regress/results/window.out
2026-09-19 03:22:01
# @@ -5658,16 +5658,19 @@
#  SELECT COUNT(*) OVER (ORDER BY t1.unique1)
#  FROM tenk1 t1 INNER JOIN tenk1 t2 ON t1.unique1 = t2.tenthous
#  LIMIT 1;
# -                                QUERY PLAN
# ---------------------------------------------------------------------------
# +                                      QUERY PLAN
# 
+--------------------------------------------------------------------------------------
#   Limit
#     ->  WindowAgg
#           Window: w1 AS (ORDER BY t1.unique1)
# -         ->  Nested Loop
# -               ->  Index Only Scan using tenk1_unique1 on tenk1 t1
# -               ->  Index Only Scan using tenk1_thous_tenthous on tenk1 t2
# -                     Index Cond: (tenthous = t1.unique1)
# -(7 rows)
# +         ->  Sort
# +               Sort Key: t1.unique1
# +               ->  Hash Join
# +                     Hash Cond: (t1.unique1 = t2.tenthous)
# +                     ->  Index Only Scan using tenk1_unique1 on tenk1 t1
# +                     ->  Hash
# +                           ->  Index Only Scan using
tenk1_thous_tenthous on tenk1 t2
# +(10 rows)
#
#  -- Ensure we get a cheap total plan.  Lack of ORDER BY in the WindowClause
#  -- means that all rows must be read from the join, so a cheap startup plan

sifaka / REL_19_STABLE:

# diff -U3 
/Users/buildfarm/bf-data/REL_19_STABLE/pgsql.build/src/test/regress/expected/window.out
/Users/buildfarm/bf-data/REL_19_STABLE/pgsql.build/src/test/modules/test_plan_advice/tmp_check/results/window.out
# --- 
/Users/buildfarm/bf-data/REL_19_STABLE/pgsql.build/src/test/regress/expected/window.out
2026-09-19 02:53:07
# +++ 
/Users/buildfarm/bf-data/REL_19_STABLE/pgsql.build/src/test/modules/test_plan_advice/tmp_check/results/window.out
2026-09-19 03:09:56
# @@ -5658,16 +5658,19 @@
#  SELECT COUNT(*) OVER (ORDER BY t1.unique1)
#  FROM tenk1 t1 INNER JOIN tenk1 t2 ON t1.unique1 = t2.tenthous
#  LIMIT 1;
# -                                QUERY PLAN
# ---------------------------------------------------------------------------
# +                                      QUERY PLAN
# 
+--------------------------------------------------------------------------------------
#   Limit
#     ->  WindowAgg
#           Window: w1 AS (ORDER BY t1.unique1)
# -         ->  Nested Loop
# -               ->  Index Only Scan using tenk1_unique1 on tenk1 t1
# -               ->  Index Only Scan using tenk1_thous_tenthous on tenk1 t2
# -                     Index Cond: (tenthous = t1.unique1)
# -(7 rows)
# +         ->  Sort
# +               Sort Key: t1.unique1
# +               ->  Hash Join
# +                     Hash Cond: (t1.unique1 = t2.tenthous)
# +                     ->  Index Only Scan using tenk1_unique1 on tenk1 t1
# +                     ->  Hash
# +                           ->  Index Only Scan using
tenk1_thous_tenthous on tenk1 t2
# +(10 rows)
#
#  -- Ensure we get a cheap total plan.  Lack of ORDER BY in the WindowClause
#  -- means that all rows must be read from the join, so a cheap startup plan

prion / REL_19_STABLE:

diff -U3 
/home/ec2-user/bf/root/REL_19_STABLE/pgsql/src/test/regress/expected/window.out
/home/ec2-user/bf/root/REL_19_STABLE/pgsql.build/src/test/modules/test_plan_advice/tmp_check/results/window.out
--- 
/home/ec2-user/bf/root/REL_19_STABLE/pgsql/src/test/regress/expected/window.out
2026-09-19 07:23:03.537952198 +0000
+++ 
/home/ec2-user/bf/root/REL_19_STABLE/pgsql.build/src/test/modules/test_plan_advice/tmp_check/results/window.out
2026-09-19 08:16:48.579365765 +0000
@@ -3634,7 +3634,7 @@
  WindowAgg
    Window: w1 AS (PARTITION BY f1 ORDER BY f2 RANGE BETWEEN
'1'::bigint PRECEDING AND '1'::bigint FOLLOWING)
    ->  Sort
-         Sort Key: f1
+         Sort Key: f1, f2
          ->  Seq Scan on t1
                Filter: (f1 = f2)
 (6 rows)
@@ -3681,7 +3681,7 @@
  WindowAgg
    Window: w1 AS (PARTITION BY f1 ORDER BY f2 GROUPS BETWEEN
'1'::bigint PRECEDING AND '1'::bigint FOLLOWING)
    ->  Sort
-         Sort Key: f1
+         Sort Key: f1, f2
          ->  Seq Scan on t1
                Filter: (f1 = f2)
 (6 rows)

turaca / master:

# # diff -U3 
/mnt/data/buildfarm/buildroot/HEAD/pgsql.build/src/test/regress/expected/window.out
/mnt/data/buildfarm/buildroot/HEAD/pgsql.build/src/bin/pg_upgrade/tmp_check/results/window.out
# # --- 
/mnt/data/buildfarm/buildroot/HEAD/pgsql.build/src/test/regress/expected/window.out
2026-09-19 07:53:30.000000000 +0100
# # +++ 
/mnt/data/buildfarm/buildroot/HEAD/pgsql.build/src/bin/pg_upgrade/tmp_check/results/window.out
2026-09-19 09:00:04.647674224 +0100
# # @@ -3634,7 +3634,7 @@
# #   WindowAgg
# #     Window: w1 AS (PARTITION BY f1 ORDER BY f2 RANGE BETWEEN
'1'::bigint PRECEDING AND '1'::bigint FOLLOWING)
# #     ->  Sort
# # -         Sort Key: f1
# # +         Sort Key: f1, f1
# #           ->  Seq Scan on t1
# #                 Filter: (f1 = f2)
# #  (6 rows)
# # 1 of 239 tests failed.
# # The differences that caused some tests to fail can be viewed in
the file 
"/mnt/data/buildfarm/buildroot/HEAD/pgsql.build/src/bin/pg_upgrade/tmp_check/regression.diffs".
# # A copy of the test summary that you see above is saved in the file
"/mnt/data/buildfarm/buildroot/HEAD/pgsql.build/src/bin/pg_upgrade/tmp_check/regression.out".

I don't see any relation between these failures and my commit. window
runs concurrently with foreign_key, but this commit's additions to the
latter are self-contained (a private operator family in its own
schema), so I don't see why window's plans would be affected.

Does anyone see it differently?

-- 
Thanks, Amit Langote


Reply via email to