From 66c68f8432ac6db65244b3df76969f2d5bdfbe40 Mon Sep 17 00:00:00 2001 From: Amit Langote 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 to " 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 Author: Ewan Young Reviewed-by: Amit Langote 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