type coercion of record types
Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.
You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:
docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t253245psql -h localhost -U postgresBuilt from patchset v3 (message #3), August 23, 2026 at 08:19 AM.
Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:
git clone --branch t253245_3 https://github.com/hackorum-dev/postgres.gitIn a checkout you already have, add the fork once:
git remote add hackorum https://github.com/hackorum-dev/postgres.gitthen, for this patchset and every later one:
git fetch hackorum t253245_3 && git checkout t253245_3Patchset v3 (message #3) is on t253245_3
Hi all,
I was migrating some code to test my custom postgres branch and stumbled
into this upstream issue about type coercion.
Here a simple reproducer
CREATE TABLE tab (a int, b int);
CREATE FUNCTION f() RETURNS tab LANGUAGE sql
AS $$ SELECT j FROM (SELECT 1 AS a, 2 AS b) j $$;
Since SIGSEGV is fairly annoying, even on a test database, I did some
digging. It seems, that the changes in the coercion mechanics caused
this. Apparently this is around since the typemod function rework
shortly discussed here
(/messages/by-id/18929.1574895430@sss.pgh.pa.us).
Looking at this, I convinced myself, that a new case in
coerce_fn_result_column (only for the RECORDOID) might be a way to
address this. But I am not sure how to do this without passing the
entire parse back to it additionally.
Side note maybe a fallback in coerce_record_to_complex on pstate == NULL
could make this an error instead of a segfault, which would have been
good enough for my use case, since the rewrite is almost trivial. Maybe
that's the better route.
Regards
Arne
Arne Roland <arne.roland@malkut.net> writes:
I was migrating some code to test my custom postgres branch and stumbled
into this upstream issue about type coercion.
Here a simple reproducer
CREATE TABLE tab (a int, b int);
CREATE FUNCTION f() RETURNS tab LANGUAGE sql
AS $$ SELECT j FROM (SELECT 1 AS a, 2 AS b) j $$;
Hmm, interesting. "git bisect" agrees that this started to crash at
913bbd88dc6b859c70ebb48107b38d693c4c6673 is the first bad commit
commit 913bbd88dc6b859c70ebb48107b38d693c4c6673 (HEAD)
Author: Tom Lane <tgl@sss.pgh.pa.us>
Date: Wed Jan 8 11:07:53 2020 -0500
Improve the handling of result type coercions in SQL functions.
Before that it failed with
ERROR: return type mismatch in function declared to return tab
DETAIL: Final statement returns record instead of integer at column 1.
CONTEXT: SQL function "f"
so it's not like things were great beforehand.
The proximate cause of the problem is that coerce_fn_result_column
thinks it can pass pstate = NULL to coerce_to_target_type, which
is specifically allowed by that function's header comment; but
coerce_to_target_type can call coerce_record_to_complex, and that
function's code path for a whole-row Var absolutely requires a
valid pstate. This inconsistency seems to go back decades (at
least to 2004).
I don't see any easy way to make this work -- by the time we get
to coerce_fn_result_column, we don't have a pstate anymore. But
since this case has never actually worked AFAICT, maybe it's okay
to just treat it as unsupported. I agree with fixing
coerce_record_to_complex to do something less drastic than
crashing, but probably we also need something in functions.c
to provide the user-visible error report.
regards, tom lane
Hi Tom,
thanks for your quick answer.
On 2026-07-30 4:12 AM, Tom Lane wrote:
[...]
so it's not like things were great beforehand.
The proximate cause of the problem is that coerce_fn_result_column
thinks it can pass pstate = NULL to coerce_to_target_type, which
is specifically allowed by that function's header comment; but
coerce_to_target_type can call coerce_record_to_complex, and that
function's code path for a whole-row Var absolutely requires a
valid pstate. This inconsistency seems to go back decades (at
least to 2004).I don't see any easy way to make this work -- by the time we get
to coerce_fn_result_column, we don't have a pstate anymore. But
since this case has never actually worked AFAICT, maybe it's okay
to just treat it as unsupported. I agree with fixing
coerce_record_to_complex to do something less drastic than
crashing, but probably we also need something in functions.c
to provide the user-visible error report.
Is there a particular reason we can't just error right here? Why can't
we just do something like the attached patch? That seems genuinely
simple to me.
Regards
Arne
Arne Roland <arne.roland@malkut.net> writes:
Is there a particular reason we can't just error right here? Why can't
we just do something like the attached patch? That seems genuinely
simple to me.
The problem I've got with that is that coerce_record_to_complex
has no principled basis for giving that errhint: for all it knows,
the context could be something else entirely. If we could throw that
error within coerce_fn_result_column, it'd be okay.
It does seem like a good idea to put something like
if (pstate == NULL)
elog(ERROR, "cannot handle whole-row Var without a pstate");
into coerce_record_to_complex, and we should certainly also update
the relevant function header comments so that they are not lies.
But that's just defense-in-depth; I don't want it to be the
user-visible behavior.
regards, tom lane