From 66c68f8432ac6db65244b3df76969f2d5bdfbe40 Mon Sep 17 00:00:00 2001
From: Amit Langote <amitlan@postgresql.org>
Date: Tue, 16 Jun 2026 18:06:58 +0900
Subject: [PATCH v2] Fix RI fast-path for domain-typed FK columns

The RI fast path is the first caller to pass a cross-type pf_eq_oprs
operator to ri_HashCompareOp().  Its test for whether a cast can be
skipped, "typeid == righttype", failed when the FK column was a domain,
since typeid is then the domain OID rather than its base type.  The code
concluded no usable conversion existed and threw "no conversion function
from <domain> to <type>" for every valid row.

Look through the domain to its base type.  When pfeqop comes directly
from the index opfamily its right-hand input is getBaseType(fktype), so
getBaseType(typeid) == righttype is the correct test; the PK = PK
fallback (right-hand input opcintype) still fails that test and falls
through to the existing cast lookup unchanged.

Oversight in commit 2da86c1.

Reported-by: Ewan Young <kdbase.hack@gmail.com>
Author: Ewan Young <kdbase.hack@gmail.com>
Reviewed-by: Amit Langote <amitlangote09@gmail.com>
Discussion: https://postgr.es/m/CAON2xHNDFC4cX2atvTpMuC=cK9y7q4J+n3+15w4148AohXEc1w@mail.gmail.com
---
 src/backend/utils/adt/ri_triggers.c       |  7 ++++---
 src/test/regress/expected/foreign_key.out | 15 +++++++++++++++
 src/test/regress/sql/foreign_key.sql      | 14 ++++++++++++++
 3 files changed, 33 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 06e7728c45d..44129a35c08 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -4113,10 +4113,11 @@ ri_HashCompareOp(Oid eq_opr, Oid typeid)
 		/*
 		 * pf_eq_oprs (used by the fast path) can be cross-type when the FK
 		 * and PK columns differ in type, e.g. int48eq for int4 PK / int8 FK.
-		 * If the FK column's type already matches what the operator expects
-		 * as its right-hand input, no cast is needed.
+		 * If the FK column's type, or the base type of a domain over it,
+		 * already matches what the operator expects as its right-hand input,
+		 * no cast is needed.
 		 */
-		if (typeid == righttype)
+		if (getBaseType(typeid) == righttype)
 			castfunc = InvalidOid;	/* simplest case */
 		else
 		{
diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out
index e1563144d4c..c334cce752c 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -3705,6 +3705,21 @@ INSERT INTO fp_fk_cross VALUES (999);
 ERROR:  insert or update on table "fp_fk_cross" violates foreign key constraint "fp_fk_cross_a_fkey"
 DETAIL:  Key (a)=(999) is not present in table "fp_pk_cross".
 DROP TABLE fp_fk_cross, fp_pk_cross;
+-- Domain-typed FK column whose base type differs from the PK type: the
+-- fast path must look through the domain to its base type when deciding
+-- whether the cross-type comparison needs a cast.  Otherwise valid rows
+-- were wrongly rejected with "no conversion function from ... to ...".
+CREATE DOMAIN fp_int8dom AS int8;
+CREATE TABLE fp_pk_dom (a int4 PRIMARY KEY);
+INSERT INTO fp_pk_dom SELECT generate_series(1, 200);
+CREATE TABLE fp_fk_dom (a fp_int8dom REFERENCES fp_pk_dom);
+INSERT INTO fp_fk_dom SELECT generate_series(1, 200);
+INSERT INTO fp_fk_dom VALUES (999);
+ERROR:  insert or update on table "fp_fk_dom" violates foreign key constraint "fp_fk_dom_a_fkey"
+DETAIL:  Key (a)=(999) is not present in table "fp_pk_dom".
+INSERT INTO fp_fk_dom VALUES (NULL);
+DROP TABLE fp_fk_dom, fp_pk_dom;
+DROP DOMAIN fp_int8dom;
 -- Duplicate FK values: when using the batched SAOP path, every
 -- row must be recognized as satisfied, not just the first match
 CREATE TABLE fp_pk_dup (a int PRIMARY KEY);
diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql
index abeb85965b9..17eadc4bb5a 100644
--- a/src/test/regress/sql/foreign_key.sql
+++ b/src/test/regress/sql/foreign_key.sql
@@ -2673,6 +2673,20 @@ INSERT INTO fp_fk_cross SELECT generate_series(1, 200);
 INSERT INTO fp_fk_cross VALUES (999);
 DROP TABLE fp_fk_cross, fp_pk_cross;
 
+-- Domain-typed FK column whose base type differs from the PK type: the
+-- fast path must look through the domain to its base type when deciding
+-- whether the cross-type comparison needs a cast.  Otherwise valid rows
+-- were wrongly rejected with "no conversion function from ... to ...".
+CREATE DOMAIN fp_int8dom AS int8;
+CREATE TABLE fp_pk_dom (a int4 PRIMARY KEY);
+INSERT INTO fp_pk_dom SELECT generate_series(1, 200);
+CREATE TABLE fp_fk_dom (a fp_int8dom REFERENCES fp_pk_dom);
+INSERT INTO fp_fk_dom SELECT generate_series(1, 200);
+INSERT INTO fp_fk_dom VALUES (999);
+INSERT INTO fp_fk_dom VALUES (NULL);
+DROP TABLE fp_fk_dom, fp_pk_dom;
+DROP DOMAIN fp_int8dom;
+
 -- Duplicate FK values: when using the batched SAOP path, every
 -- row must be recognized as satisfied, not just the first match
 CREATE TABLE fp_pk_dup (a int PRIMARY KEY);
-- 
2.47.3

