| From: | PG Bug reporting form <noreply(at)postgresql(dot)org> |
|---|---|
| To: | pgsql-bugs(at)lists(dot)postgresql(dot)org |
| Cc: | imchifan8(at)gmail(dot)com |
| Subject: | BUG #19875: Foreign-key self-update rejects an accepted implicit I/O cast |
| Date: | 2026-11-11 11:57:42 |
| Message-ID: | 19875-73e3c3c7534b9855@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: 19875
Logged by: Noah
Email address: imchifan8(at)gmail(dot)com
PostgreSQL version: 18.6
Operating system: Linux/amd64
Description:
An UPDATE that explicitly assigns a valid foreign-key column to itself fails
when the column's custom type uses an implicit WITH INOUT cast to the
referenced type. PostgreSQL accepts the cast when creating the constraint
and inserting the row, but the unchanged-key UPDATE reports `no conversion
function from integer_io to integer`.
The key value is unchanged and still maps to an existing referenced row, so
the UPDATE should succeed and set payload to 11.
How to repeat:
Run the following as a superuser in a disposable database with `psql -X -f
case.sql`:
```sql
\set ON_ERROR_STOP off
-- Recover cleanly if an earlier interrupted run left the shell type behind.
DROP TYPE IF EXISTS integer_io CASCADE;
CREATE TYPE integer_io;
CREATE FUNCTION integer_io_in(cstring)
RETURNS integer_io
AS 'int4in' LANGUAGE internal IMMUTABLE STRICT;
CREATE FUNCTION integer_io_out(integer_io)
RETURNS cstring
AS 'int4out' LANGUAGE internal IMMUTABLE STRICT;
CREATE TYPE integer_io
(INPUT = integer_io_in,
OUTPUT = integer_io_out,
LIKE = int4);
CREATE CAST (integer_io AS int4)
WITH INOUT AS IMPLICIT;
CREATE TABLE parent (id int4 PRIMARY KEY);
CREATE TABLE child
(a integer_io
REFERENCES parent(id),
payload int);
INSERT INTO parent VALUES (1);
INSERT INTO child VALUES ('1', 10);
SELECT conpfeqop[1]::regoperator AS pk_fk_operator,
conffeqop[1]::regoperator AS fk_fk_operator
FROM pg_constraint
WHERE conrelid = 'child'::regclass;
-- Assigning the FK column to itself should succeed because its value is
-- unchanged and still maps to the existing key. Naming the FK column in SET
-- makes the UPDATE trigger compare the old and new key values.
BEGIN;
SAVEPOINT before_update;
UPDATE child SET a = a, payload = 11;
ROLLBACK TO SAVEPOINT before_update;
SELECT a::text, payload FROM child;
ROLLBACK;
DROP TABLE child;
DROP TABLE parent;
DROP CAST (integer_io AS int4);
DROP TYPE integer_io CASCADE;
```
The constraint uses integer equality operators:
```
pk_fk_operator | fk_fk_operator
--------------------+--------------------
=(integer,integer) | =(integer,integer)
(1 row)
```
The UPDATE then fails with:
```
ERROR: no conversion function from integer_io to integer
```
After rolling back to the savepoint, the row remains unchanged:
```
a | payload
---+---------
1 | 10
(1 row)
```
The corresponding server log records:
```
ERROR: no conversion function from integer_io to integer
STATEMENT: UPDATE child SET a = a, payload = 11;
```
The evidence indicates that the foreign-key unchanged-key comparison path
expects the accepted coercion to be representable as a directly callable
function or relabel operation and does not handle this WITH INOUT coercion.
The issue was reproduced on PostgreSQL 18.6 on Linux/amd64. This occurs when
a foreign key uses a custom type with an implicit WITH INOUT cast and an
UPDATE explicitly assigns the key column to itself.
| From | Date | Subject | |
|---|---|---|---|
| Next Message | PG Bug reporting form | 2026-11-11 12:16:24 | BUG #19876: rhel-9.9-x86_64 yum repository for Postgres 16 is missing repodata |
| Previous Message | Kirill Reshke | 2026-11-11 11:03:10 | Re: BUG #19861: Self-join elimination drops a VOLATILE predicate and changes the query result |