Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wSEdu-0033Cl-0G for pgsql-bugs@arkaria.postgresql.org; Wed, 27 May 2026 13:49:54 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wSEds-008Oes-0G for pgsql-bugs@arkaria.postgresql.org; Wed, 27 May 2026 13:49:53 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wSCn2-0087ux-25 for pgsql-bugs@lists.postgresql.org; Wed, 27 May 2026 11:51:13 +0000 Received: from mahout.postgresql.org ([2001:4800:3e1:1::227]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1wSCmy-00000001gY5-2ixe for pgsql-bugs@lists.postgresql.org; Wed, 27 May 2026 11:51:13 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=postgresql.org; s=20171124; h=Message-ID:Date:Reply-To:Cc:From:To:Subject: Content-Transfer-Encoding:MIME-Version:Content-Type:Sender:Content-ID: Content-Description:In-Reply-To:References; bh=Umk1Z6be4stBHMbnkQNzSDfTR7NBDW2dlQCM5dJkOGA=; b=dK5EFt7iO3eK/eQjhu9C/hW2PF lljr744DIdTegnGWKiE9xfKjnLZgRWYVBJZun/YXCxOs2P6uuYwN/LpIqfZP2RxtiSRKfvo+Py7Lg +f8mHPbbllFzS8Hdb3Q1FtUd6+eR/yM6/WKZ7086AstgS+GFOFxTX8ooeSNnGHV6wb3DI+i4YLA8q SXwcTkdyvnUqLottcKYkgl7ngBe2ROE4UM9YnbVVUA+56DifXj4TLMKQS8S4r3snh8i1ZMav1NNlI rIOOrB90YPkzTf6Mo6RQ8mQOMWBbC2+tw7+JQs7Q81zUnukggkuXW7QkIDSTXwpUq8Z76MfZhGQt1 y9kJEMhA==; Received: from wrigleys.postgresql.org ([2a02:16a8:dc51::60]) by mahout.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wSCmv-003BLe-09 for pgsql-bugs@lists.postgresql.org; Wed, 27 May 2026 11:51:07 +0000 Received: from localhost ([127.0.0.1] helo=wrigleys.postgresql.org) by wrigleys.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wSCmt-00A12f-1i for pgsql-bugs@lists.postgresql.org; Wed, 27 May 2026 11:51:03 +0000 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Subject: BUG #19498: Anonymous ROW() field expansion fails after scalar subquery relay To: pgsql-bugs@lists.postgresql.org From: PG Bug reporting form Cc: yankairong@ruc.edu.cn Reply-To: yankairong@ruc.edu.cn, pgsql-bugs@lists.postgresql.org Date: Wed, 27 May 2026 11:50:12 +0000 Message-ID: <19498-e49069f1ed6487cd@postgresql.org> X-Auto-Response-Suppress: All Auto-Submitted: auto-generated List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk The following bug has been logged on the website: Bug reference: 19498 Logged by: muyehu Email address: yankairong@ruc.edu.cn PostgreSQL version: 18.4 Operating system: ubuntu22.04 Description: =20 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: