BUG #19819: Foreign key DDL can fail with internal errors (XX000) from elog(): "key columns are not both collata

From: PG Bug reporting form <noreply(at)postgresql(dot)org>
To: pgsql-bugs(at)lists(dot)postgresql(dot)org
Cc: 303677365(at)qq(dot)com
Subject: BUG #19819: Foreign key DDL can fail with internal errors (XX000) from elog(): "key columns are not both collata
Date: 2026-10-30 06:55:38
Message-ID: 19819-8aec9277898c3c06@postgresql.org
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

The following bug has been logged on the website:

Bug reference: 19819
Logged by: chunling qin
Email address: 303677365(at)qq(dot)com
PostgreSQL version: 18.6
Operating system: Linux (TencentOS 4.4, x86_64)
Description:

Hello,

ATAddForeignKeyConstraint() (src/backend/commands/tablecmds.c) contains
two elog(ERROR, ...) calls that are reachable from ordinary user DDL.
elog() produces SQLSTATE XX000 (internal error), which per convention
should mean "this should never happen" -- but both sites can be reached
by plain CREATE TABLE ... REFERENCES / ALTER TABLE ... ADD CONSTRAINT,
and one of them rejects a combination that ought to work.

1) "key columns are not both collatable"

The referencing and referenced columns have an assignable implicit cast
(pg_cast: castcontext='i'), but the two types differ in collatability.
Reproducer:

CREATE TABLE q1p (a text PRIMARY KEY);
CREATE TABLE q1f (a "char" REFERENCES q1p(a));
-- ERROR: XX000: key columns are not both collatable

The same happens for "char" -> varchar (implicit function cast), and for
binary casts of the stats types:

CREATE TABLE q2p (a bytea PRIMARY KEY);
CREATE TABLE q2f (a pg_mcv_list REFERENCES q2p(a));
-- ERROR: XX000: key columns are not both collatable

For comparison, the reverse combinations (no implicit cast exists) are
rejected with a proper user-level error, so the normal rejection path
does exist:

CREATE TABLE q4p (a "char" PRIMARY KEY);
CREATE TABLE q4f (a text REFERENCES q4p(a));
-- ERROR: 42804: ... incompatible types: text and "char"

Expected for case 1: since "char" -> text is an implicit function cast
(pg_cast: castmethod='f', castcontext='i'), this foreign key combination
should either be accepted, or be rejected with a normal user-level
SQLSTATE (42804 "datatype mismatch" / "foreign key constraint cannot be
implemented"), not an XX000 internal error.

2) "missing operator %d(%u,%u) in opfamily %u"

CREATE UNIQUE INDEX accepts an operator class that lacks an equality
operator (strategy 3), and the failure only surfaces later, as an
internal error, when the foreign key is added:

CREATE OPERATOR CLASS cbh_noeq_ops FOR TYPE int4 USING btree AS
OPERATOR 1 pg_catalog.<(int4,int4), OPERATOR 2
pg_catalog.<=(int4,int4),
OPERATOR 4 pg_catalog.>=(int4,int4), OPERATOR 5
pg_catalog.>(int4,int4),
FUNCTION 1 pg_catalog.btint4cmp(int4,int4); -- deliberately no
strategy 3
CREATE TABLE n1p (a int4);
CREATE UNIQUE INDEX n1i ON n1p (a cbh_noeq_ops); -- accepted
CREATE TABLE n1f (a int4 REFERENCES n1p(a));
-- ERROR: XX000: missing operator 3(23,23) in opfamily 61711

Expected for case 2: a user-level error such as "operator class
cbh_noeq_ops has no equality operator", or rejecting the index at
CREATE UNIQUE INDEX time.

Both elog() sites are in ATAddForeignKeyConstraint()
(src/backend/commands/tablecmds.c): the collatability check and the
equality-operator lookup. Turning them into ereport() with proper
SQLSTATEs (and ideally allowing case 1, since the implicit cast exists)
would match how the surrounding code already reports user-reachable
conditions.

Reproduced on:
- PostgreSQL 20devel (snapshot of 2026-10-27, x86_64-pc-linux-gnu)
- PostgreSQL 18.6 (REL_18_STABLE, self-built)

Both DDL paths (CREATE TABLE ... REFERENCES and ALTER TABLE ... ADD
CONSTRAINT) were checked; re-running after DROP and from fresh sessions
gives identical results.

Regards,
Chunling Qin

Browse pgsql-bugs by date

  From Date Subject
Next Message PG Bug reporting form 2026-10-30 06:58:56 BUG #19820: ANALYZE can store empty (0-dimensional) stavalues that the statistics import functions refuse to res
Previous Message Gaurav Singh 2026-10-30 06:50:13 Re: postgres_fdw returns wrong rows for age(timestamptz, timestamptz) filters