BUG #19470: PostgreSQL backend aborts (assert failure) when a prepared statement returns a composite type cast t
The following bug has been logged on the website:
Bug reference: 19470
Logged by: HaoGang Mao
Email address: haogangmao@gmail.com
PostgreSQL version: 18.3
Operating system: Linux
Description:
Reproduction steps (minimal):
BEGIN;
CREATE TYPE foo AS (a int, b text);
PREPARE p AS SELECT CAST(ROW(1, 'hello') AS foo)::text;
EXECUTE p;
ALTER TYPE foo ALTER ATTRIBUTE a TYPE VARCHAR(100);
EXECUTE p;
COMMIT;
Expected: Error message (type modified while a prepared plan / expression is
active)
Actual: Server connection dropped; backend aborts with SIGABRT due to
assertion failure
Server log (trimmed):
TRAP: failed Assert("false"), File: "heaptuple.c", Line: 1417, PID: <pid>
... heap_deform_tuple()
Hi,
On Sat, May 2, 2026 at 1:27 AM PG Bug reporting form <noreply@postgresql.org>
wrote:
The following bug has been logged on the website:
Bug reference: 19470
Logged by: HaoGang Mao
Email address: haogangmao@gmail.com
PostgreSQL version: 18.3
Operating system: Linux
Description:Reproduction steps (minimal):
BEGIN;
CREATE TYPE foo AS (a int, b text);
PREPARE p AS SELECT CAST(ROW(1, 'hello') AS foo)::text;
EXECUTE p;
ALTER TYPE foo ALTER ATTRIBUTE a TYPE VARCHAR(100);
EXECUTE p;
COMMIT;Expected: Error message (type modified while a prepared plan / expression
is
active)
Actual: Server connection dropped; backend aborts with SIGABRT due to
assertion failureServer log (trimmed):
TRAP: failed Assert("false"), File: "heaptuple.c", Line: 1417, PID: <pid>
... heap_deform_tuple()
Thanks for reporting and the repro, I was able to reproduce this.
The cause of this crash is a cache invalidation failure. When ALTER TYPE is
executed,
the cached plan for the prepared statement is not properly invalidated. So
the executor uses
a stale memory layout during the second execution. This causes tuple
deformation to see type
confusion, reading a 4-byte INT as a Varlena header. The system interprets
the lowest byte
of the integer as a TOAST pointer, reads the adjacent garbage memory as a
TOAST tag,
and triggers the Assert(false) in VARTAG_SIZE, replacing the assertion with
an elog(ERROR)
would prevent the hard crash, it only masks the main issue. I think the
correct fix is that
ALTER TYPE on a composite attribute correctly triggers a plan
re-validation, thoughts?
--
Thanks,
Srinath Reddy Sadipiralla
EDB: https://www.enterprisedb.com/
On Wed, Apr 29, 2026, at 8:27 AM, PG Bug reporting form wrote:
Reproduction steps (minimal):
BEGIN;
CREATE TYPE foo AS (a int, b text);
PREPARE p AS SELECT CAST(ROW(1, 'hello') AS foo)::text;
EXECUTE p;
ALTER TYPE foo ALTER ATTRIBUTE a TYPE VARCHAR(100);
EXECUTE p;
COMMIT;
Thanks for your report! The attached patch fixes this case. I included a
similar test case but I'm fine if the test is not included with this fix. I
decided to create a separate function instead of adding the new conditions to
record_plan_type_dependency() because it keeps the fix simple and isolated.
The crash is reproduced back to v11 which means this fix should be backpatched
to all supported versions.
--
Euler Taveira
EDB https://www.enterprisedb.com/
Attachments:
v1-0001-Fix-a-crash-with-cached-plans-after-changing-comp.patchtext/x-patch; name="=?UTF-8?Q?v1-0001-Fix-a-crash-with-cached-plans-after-changing-comp.patc?= =?UTF-8?Q?h?="Download+81-2
Hi,
On Tue, May 5, 2026 at 11:37 AM Euler Taveira <euler@eulerto.com> wrote:
On Wed, Apr 29, 2026, at 8:27 AM, PG Bug reporting form wrote:
Reproduction steps (minimal):
BEGIN;
CREATE TYPE foo AS (a int, b text);
PREPARE p AS SELECT CAST(ROW(1, 'hello') AS foo)::text;
EXECUTE p;
ALTER TYPE foo ALTER ATTRIBUTE a TYPE VARCHAR(100);
EXECUTE p;
COMMIT;Thanks for your report! The attached patch fixes this case. I included a
similar test case but I'm fine if the test is not included with this fix. I
decided to create a separate function instead of adding the new conditions to
record_plan_type_dependency() because it keeps the fix simple and isolated.The crash is reproduced back to v11 which means this fix should be backpatched
to all supported versions.
Thanks for working on this.
I think this is on the right track. Using relationOids is the right
mechanism, since the DDL doesn't touch the composite's pg_type row and
a TYPEOID inval item wouldn't work here. Doing it in fix_expr_common
covers extract_query_dependencies too.
What I'm less easy about is whether Const and RowExpr are the only
carriers. As written, the TYPTYPE_COMPOSITE test won't look through an
array over a composite or a domain over one, and nodes like
FieldSelect and ConvertRowtypeExpr also carry composite type OIDs.
Could you check whether any of those reproduce a similar crash? If so
we'd want to look through to the element/base type and/or cover more
node types before this goes in.
Modulo that, I'll prepare to push to all supported branches.
--
Thanks, Amit Langote