public inbox for [email protected]  
help / color / mirror / Atom feed
From: PG Bug reporting form <[email protected]>
To: [email protected]
Cc: [email protected]
Subject: BUG #19498: Anonymous ROW() field expansion fails after scalar subquery relay
Date: Wed, 27 May 2026 11:50:12 +0000
Message-ID: <[email protected]> (raw)

The following bug has been logged on the website:

Bug reference:      19498
Logged by:          muyehu
Email address:      [email protected]
PostgreSQL version: 18.4
Operating system:   ubuntu22.04
Description:        

PostgreSQL version: 18.4
Platform: x86_64-pc-linux-gnu, gcc 11.4.0
Component: parser / rowtypes / expression analysis

Hi,

I found an inconsistency in field expansion of anonymous ROW() values.

A direct relay of an anonymous composite value allows field extraction:

SELECT (c).f1
FROM (SELECT row(1, 2) AS c) s;

This returns:

 f1
----
  1

However, if the same anonymous composite value is first relayed through a
scalar subquery and then exposed as an output column, later field expansion
fails with:

ERROR:  record type has not been registered

The failure is triggered by operations that need field-level tuple
descriptor information, such as:

- (c).f1
- f1(c)
- (c).*
- INSERT ... SELECT (c).*

At the same time, the relayed value itself still appears to be preserved:
operations such as row_to_json(c), to_jsonb(c), and c::text work on the same
query shape. This suggests that the issue is not that the anonymous record
value is lost, but that its tuple descriptor cannot be recovered later
during field expansion.

Minimal reproduction:

BEGIN;

-- Direct relay works.
SELECT (c).f1
FROM (SELECT row(1, 2) AS c) s;

-- Scalar subquery relay fails.
SELECT (c).f1
FROM (
  SELECT (SELECT z.c FROM (SELECT row(1, 2) AS c) z) AS c
) s;

-- Star expansion fails in the same way.
SELECT (c).*
FROM (
  SELECT (SELECT z.c FROM (SELECT row(1, 2) AS c) z) AS c
) s;

ROLLBACK;

Actual result for the second and third queries:

ERROR:  record type has not been registered

Expected behavior:

Since the direct relay of the same anonymous ROW() value allows field
expansion, I expected the scalar-subquery relay to preserve enough row
descriptor information for the same field expansion, or at least to behave
consistently with the direct relay case.

The same behavior can also be reproduced with a CTE relay:

WITH cte(c) AS MATERIALIZED (
  SELECT row(1, 2)
),
pass1(c) AS (
  SELECT (SELECT z.c FROM (SELECT cte.c) z)
  FROM cte
)
SELECT (c).f1
FROM pass1;

This also fails with:

ERROR:  record type has not been registered

The same relay shape also fails with functional field access:

SELECT f1(c)
FROM (
  SELECT (SELECT z.c FROM (SELECT row(1, 2) AS c) z) AS c
) s;

and with DML projection:

CREATE TEMP TABLE t(f1 int, f2 int);

INSERT INTO t
SELECT (c).*
FROM (
  SELECT (SELECT z.c FROM (SELECT row(1, 2) AS c) z) AS c
) s;

Both fail with:

ERROR:  record type has not been registered

Adjacent operations that work:

On the same scalar-subquery relay shape, the following operations succeed:

SELECT row_to_json(c)
FROM (
  SELECT (SELECT z.c FROM (SELECT row(1, 2) AS c) z) AS c
) s;

SELECT to_jsonb(c)
FROM (
  SELECT (SELECT z.c FROM (SELECT row(1, 2) AS c) z) AS c
) s;

SELECT c::text
FROM (
  SELECT (SELECT z.c FROM (SELECT row(1, 2) AS c) z) AS c
) s;

For example, c::text returns:

(1,2)

This suggests that the scalar subquery does relay the anonymous record
value, but later field expansion cannot recover its descriptor.

Possible relation to existing rowtype tests:

This looks possibly related to previous rowtype /
indirect-composite-reference issues, including bug #18077.

The existing regression tests in src/test/regress/sql/rowtypes.sql cover
several indirect composite reference paths. For example, this shape works:

WITH cte(c) AS MATERIALIZED (SELECT row(1, 2)),
     cte2(c) AS (SELECT * FROM cte)
SELECT (c).f1
FROM cte2;

But replacing the relay column with a scalar subquery returning the same
anonymous composite value fails:

WITH cte(c) AS MATERIALIZED (SELECT row(1, 2)),
     cte2(c) AS (
       SELECT (SELECT c FROM cte) AS c
     )
SELECT (c).f1
FROM cte2;

So the remaining uncovered shape seems to be:

anonymous ROW()
  -> scalar subquery returning record
  -> exposed as an upper query output column
  -> later field expansion using (c).f1 / (c).*

Code inspection note:



reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected]
  Subject: Re: BUG #19498: Anonymous ROW() field expansion fails after scalar subquery relay
  In-Reply-To: <[email protected]>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox