P
Use the source, Luke!
In ATAddForeignKeyConstraint in src/backend/commands/tablecmds.c, we find the truth about the requirements:
/*
* There had better be a primary equality operator for the index.
* We'll use it for PK = PK comparisons.
*/
ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
eqstrategy);
if (!OidIsValid(ppeqop))
elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
eqstrategy, opcintype, opcintype, opfamily);
So, the unique index on the target type has to support equality comparisons.
/*
* Are there equality operators that take exactly the FK type? Assume
* we should look through any domain here.
*/
fktyped = getBaseType(fktype);
pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
eqstrategy);
if (OidIsValid(pfeqop))
{
pfeqop_right = fktyped;
ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
eqstrategy);
}
else
{
/* keep compiler quiet */
pfeqop_right = InvalidOid;
ffeqop = InvalidOid;
}
If there is an equality operator between the data types of the referencing column and the referenced column that is supported by the target index, we are good.
if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
{
/*
* Otherwise, look for an implicit cast from the FK type to the
* opcintype, and if found, use the primary equality operator.
* This is a bit tricky because opcintype might be a polymorphic
* type such as ANYARRAY or ANYENUM; so what we have to test is
* whether the two actual column types can be concurrently cast to
* that type. (Otherwise, we'd fail to reject combinations such
* as int[] and point[].)
*/
Oid input_typeids[2];
Oid target_typeids[2];
input_typeids[0] = pktype;
input_typeids[1] = fktype;
target_typeids[0] = opcintype;
target_typeids[1] = opcintype;
if (can_coerce_type(2, input_typeids, target_typeids,
COERCION_IMPLICIT))
{
pfeqop = ffeqop = ppeqop;
pfeqop_right = opcintype;
}
}
Otherwise, there must be an implicit cast from the type of the referencing column to the referenced column.
if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("foreign key constraint \"%s\" cannot be implemented",
fkconstraint->conname),
errdetail("Key columns \"%s\" and \"%s\" "
"are of incompatible types: %s and %s.",
strVal(list_nth(fkconstraint->fk_attrs, i)),
strVal(list_nth(fkconstraint->pk_attrs, i)),
format_type_be(fktype),
format_type_be(pktype))));
If neither is true error out.
So you can have foreign keys from integer to smallint because there exists an equality operator between these types that belongs to the index's operator family:
\do =
List of operators
Schema │ Name │ Left arg type │ Right arg type │ Result type │ Description
════════════╪══════╪═════════════════════════════╪═════════════════════════════╪═════════════╪═══════════════
...
pg_catalog │ = │ integer │ smallint │ boolean │ equal
...
(63 rows)
However, there is no implicit cast between text and integer, so you cannot have foreign key references between these types.
\dC
List of casts
Source type │ Target type │ Function │ Implicit?
═════════════════════════════╪═════════════════════════════╪════════════════════╪═══════════════
...
integer │ bigint │ int8 │ yes
integer │ bit │ bit │ no
integer │ boolean │ bool │ no
integer │ "char" │ char │ no
integer │ double precision │ float8 │ yes
integer │ money │ money │ in assignment
integer │ numeric │ numeric │ yes
integer │ oid │ (binary coercible) │ yes
integer │ real │ float4 │ yes
integer │ regclass │ (binary coercible) │ yes
integer │ regcollation │ (binary coercible) │ yes
integer │ regconfig │ (binary coercible) │ yes
integer │ regdictionary │ (binary coercible) │ yes
integer │ regnamespace │ (binary coercible) │ yes
integer │ regoper │ (binary coercible) │ yes
integer │ regoperator │ (binary coercible) │ yes
integer │ regproc │ (binary coercible) │ yes
integer │ regprocedure │ (binary coercible) │ yes
integer │ regrole │ (binary coercible) │ yes
integer │ regtype │ (binary coercible) │ yes
integer │ smallint │ int2 │ in assignment
...