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
/*
* 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
{
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);
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);