public inbox for [email protected]
help / color / mirror / Atom feedFrom: Andrey Borodin <[email protected]>
To: surya poondla <[email protected]>
Cc: songjinzhou <[email protected]>
Cc: dllggyx <[email protected]>
Cc: pgsql-bugs <[email protected]>
Subject: Re: BUG #19382: Server crash at __nss_database_lookup
Date: Sat, 4 Apr 2026 17:42:20 +0500
Message-ID: <[email protected]> (raw)
In-Reply-To: <CAOVWO5oRGPd7mA3d85jNYmjLNfeBAca5oDcHTfRFxbAwPLxs5g@mail.gmail.com>
References: <[email protected]>
<CAOVWO5rVBKsjG4YwO_PJQu2OBGp8qUdF1jineYY6Lm3zc6-KWQ@mail.gmail.com>
<CAOVWO5rbwKgHWLYJMvKuvGxW9eFSk7LADk=ZxDEvwA1uTefvAg@mail.gmail.com>
<CAOVWO5qN8UXdwMnDa+a7aVq=irGSfm2aeYvEc-uV6j4QHZiyrA@mail.gmail.com>
<CAOVWO5py4zLPYsnGK1EEzHnC4feTSmbsEZn-BiDgY_=J1P6Wmw@mail.gmail.com>
<CAOVWO5pbgCVx0zgTr1mxZug2hoGwxZOk+-Owvwg0jaQv9JE3Fw@mail.gmail.com>
<CAOVWO5r19cctAFKbW24jfMKsD-pkyV21w+z7L3pCPxM1CArtjQ@mail.gmail.com>
<CAOVWO5oSeBouPv0ueVByh+_6EgRCjWh0spSmnF6Cv-TF1twqKg@mail.gmail.com>
<[email protected]>
<CAOVWO5oH37CETZuxxXw3dhCMOHPMA0xFoJBWTpfJ06OV7sGzTQ@mail.gmail.com>
<[email protected]>
<CAOVWO5oRGPd7mA3d85jNYmjLNfeBAca5oDcHTfRFxbAwPLxs5g@mail.gmail.com>
> On 3 Apr 2026, at 04:14, surya poondla <[email protected]> wrote:
>
> <0004-Fix-bug-19382-server-crash-when-ALTER-TYPE-is-used-m.patch>
Hi Surya,
Thanks for the updated patch. I noticed it checks er_tupdesc_id of the
outermost record variable, but does not recurse into nested composite types.
The server still crashes when only an inner type is altered:
CREATE TYPE inner_t AS (x INT, y INT);
CREATE TYPE outer_t AS (a INT, b inner_t);
CREATE OR REPLACE FUNCTION test_nested() RETURNS record LANGUAGE plpgsql AS $$
DECLARE r1 outer_t; r2 outer_t;
BEGIN
r1 := ROW(1, ROW(10, power(2,30)::int4)::inner_t)::outer_t;
ALTER TYPE inner_t ALTER ATTRIBUTE y TYPE TEXT;
r2 := r1;
RETURN r2;
END; $$;
SELECT test_nested(); -- server crash
The same gap exists on the cursor side independently of your patch, and I
have a fix for that part that walks the type tree recursively. IMO the
PL/pgSQL assignment path will need a similar recursive check.
I'm definitely not a big fan of checking types on every FETCH, but I see no
other ways around.
Best regards, Andrey Borodin.
Attachments:
[application/octet-stream] v2026-04-04-0001-Fix-incorrect-results-when-composite-typ.patch (9.4K, 2-v2026-04-04-0001-Fix-incorrect-results-when-composite-typ.patch)
download | inline diff:
From bd2f4aff350aab947ab66cdad7afa910f1ede7a1 Mon Sep 17 00:00:00 2001
From: Andrey Borodin <[email protected]>
Date: Sat, 4 Apr 2026 10:26:44 +0500
Subject: [PATCH v2026-04-04] Fix incorrect results when composite type is
altered mid-cursor-scan
HeapTuples carry only the type OID, not a schema version. If ALTER TYPE
changes a composite type between FETCHes, the stored tuples are
interpreted with the wrong definition.
At cursor open, record the tupDesc_identifier of every composite type
reachable from the result columns (including nested ones). Reject the
FETCH with ERRCODE_INVALID_CURSOR_STATE if any identifier has changed.
Reported-by: Yuxiao Guo <[email protected]>
Discussion: https://www.postgresql.org/message-id/CAOVWO5oRGPd7mA3d85jNYmjLNfeBAca5oDcHTfRFxbAwPLxs5g@mail.gmail.com
---
src/backend/tcop/pquery.c | 130 +++++++++++++++++++++++++
src/include/utils/portal.h | 5 +
src/test/regress/expected/rowtypes.out | 54 ++++++++++
src/test/regress/sql/rowtypes.sql | 36 +++++++
4 files changed, 225 insertions(+)
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index d8fc75d0bb9..07cbf288936 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -25,8 +25,12 @@
#include "pg_trace.h"
#include "tcop/pquery.h"
#include "tcop/utility.h"
+#include "catalog/pg_type_d.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
+#include "utils/typcache.h"
/*
@@ -425,6 +429,103 @@ FetchStatementTargetList(Node *stmt)
* On return, portal is ready to accept PortalRun() calls, and the result
* tupdesc (if any) is known.
*/
+
+/*
+ * CollectCompositeTypeVersions
+ * Record typid's tupDesc_identifier, then recurse into its composite-type
+ * attributes. Duplicate OIDs are skipped. Arrays are repalloc'd as
+ * needed; n/alloc are updated in place.
+ */
+static void
+CollectCompositeTypeVersions(Oid typid,
+ Oid **oids, uint64 **versions,
+ int *n, int *alloc)
+{
+ TypeCacheEntry *typentry;
+ TupleDesc tupdesc;
+
+ for (int i = 0; i < *n; i++) /* skip if already recorded */
+ if ((*oids)[i] == typid)
+ return;
+
+ typentry = lookup_type_cache(typid, TYPECACHE_TUPDESC);
+
+ if (*n >= *alloc)
+ {
+ *alloc *= 2;
+ *oids = repalloc(*oids, *alloc * sizeof(Oid));
+ *versions = repalloc(*versions, *alloc * sizeof(uint64));
+ }
+
+ (*oids)[*n] = typid;
+ (*versions)[*n] = typentry->tupDesc_identifier;
+ (*n)++;
+
+ tupdesc = typentry->tupDesc;
+ if (tupdesc == NULL)
+ return;
+
+ for (int i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+ if (!attr->attisdropped &&
+ attr->atttypid != RECORDOID &&
+ get_typtype(attr->atttypid) == TYPTYPE_COMPOSITE)
+ CollectCompositeTypeVersions(attr->atttypid,
+ oids, versions, n, alloc);
+ }
+}
+
+/*
+ * InitPortalCompositeTypeVersions
+ * Snapshot tupDesc_identifier for every named composite type reachable
+ * from portal->tupDesc (including nested types). Called once at cursor
+ * open; checked at each FETCH to detect mid-scan ALTER TYPE.
+ */
+static void
+InitPortalCompositeTypeVersions(Portal portal)
+{
+ TupleDesc tupdesc = portal->tupDesc;
+ MemoryContext oldcxt;
+ int alloc = 8;
+ int n = 0;
+ Oid *oids;
+ uint64 *versions;
+
+ if (tupdesc == NULL)
+ return;
+
+ oldcxt = MemoryContextSwitchTo(portal->portalContext);
+ oids = palloc(alloc * sizeof(Oid));
+ versions = palloc(alloc * sizeof(uint64));
+
+ for (int i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+ if (!attr->attisdropped &&
+ attr->atttypid != RECORDOID &&
+ get_typtype(attr->atttypid) == TYPTYPE_COMPOSITE)
+ CollectCompositeTypeVersions(attr->atttypid,
+ &oids, &versions, &n, &alloc);
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+
+ if (n > 0)
+ {
+ portal->nCursorCompositeTypes = n;
+ portal->cursorCompositeTypeOids = oids;
+ portal->cursorCompositeTypeVersions = versions;
+ }
+ else
+ {
+ pfree(oids);
+ pfree(versions);
+ }
+}
+
void
PortalStart(Portal portal, ParamListInfo params,
int eflags, Snapshot snapshot)
@@ -522,6 +623,13 @@ PortalStart(Portal portal, ParamListInfo params,
*/
portal->tupDesc = queryDesc->tupDesc;
+ /*
+ * Record type-cache versions for any named composite-type
+ * result columns so that FETCH can detect mid-scan ALTER
+ * TYPE.
+ */
+ InitPortalCompositeTypeVersions(portal);
+
/*
* Reset cursor position data to "start of query"
*/
@@ -1383,6 +1491,28 @@ PortalRunFetch(Portal portal,
Assert(PortalIsValid(portal));
+ /*
+ * Reject the fetch if any composite type in the result has been altered
+ * since the cursor was opened; HeapTuples carry no type-version tag so
+ * the mismatch cannot be caught later.
+ */
+ if (portal->nCursorCompositeTypes > 0)
+ {
+ for (int i = 0; i < portal->nCursorCompositeTypes; i++)
+ {
+ Oid typid = portal->cursorCompositeTypeOids[i];
+ TypeCacheEntry *typentry =
+ lookup_type_cache(typid, TYPECACHE_TUPDESC);
+
+ if (typentry->tupDesc_identifier != portal->cursorCompositeTypeVersions[i])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_STATE),
+ errmsg("cursor scan cannot continue after composite type \"%s\" was altered",
+ format_type_be(typid)),
+ errhint("Close and reopen the cursor after ALTER TYPE.")));
+ }
+ }
+
/*
* Check for improper portal use, and mark portal active.
*/
diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h
index a7bedb12c18..a8d725a35ad 100644
--- a/src/include/utils/portal.h
+++ b/src/include/utils/portal.h
@@ -160,6 +160,11 @@ typedef struct PortalData
/* and these are the format codes to use for the columns: */
int16 *formats; /* a format code for each column */
+ /* tupDesc_identifier snapshots for composite types in the result columns */
+ int nCursorCompositeTypes; /* 0 if none */
+ Oid *cursorCompositeTypeOids;
+ uint64 *cursorCompositeTypeVersions;
+
/*
* Outermost ActiveSnapshot for execution of the portal's queries. For
* all but a few utility commands, we require such a snapshot to exist.
diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out
index 956bc2d02fc..7f4c0567fce 100644
--- a/src/test/regress/expected/rowtypes.out
+++ b/src/test/regress/expected/rowtypes.out
@@ -1408,3 +1408,57 @@ ERROR: column "oid" not found in data type compositetable
LINE 1: SELECT (NULL::compositetable).oid;
^
DROP TABLE compositetable;
+-- ALTER TYPE mid-cursor-scan must be detected and raise an error.
+CREATE TYPE mycomptype AS (a INT, b INT);
+BEGIN;
+DECLARE cur1 CURSOR FOR
+ SELECT (i, i * 100)::mycomptype FROM generate_series(1, 5) i;
+FETCH cur1;
+ row
+---------
+ (1,100)
+(1 row)
+
+ALTER TYPE mycomptype ALTER ATTRIBUTE b TYPE TEXT;
+FETCH cur1;
+ERROR: cursor scan cannot continue after composite type "mycomptype" was altered
+HINT: Close and reopen the cursor after ALTER TYPE.
+COMMIT;
+DROP TYPE mycomptype;
+-- Same check applies when a nested composite type is altered.
+CREATE TYPE myinnertype AS (x INT, y INT);
+CREATE TYPE myoutertype AS (a INT, b myinnertype);
+BEGIN;
+DECLARE cur2 CURSOR FOR
+ SELECT (i, (i * 10, i * 100)::myinnertype)::myoutertype
+ FROM generate_series(1, 5) i;
+FETCH cur2;
+ row
+----------------
+ (1,"(10,100)")
+(1 row)
+
+ALTER TYPE myinnertype ALTER ATTRIBUTE y TYPE TEXT;
+FETCH cur2;
+ERROR: cursor scan cannot continue after composite type "myinnertype" was altered
+HINT: Close and reopen the cursor after ALTER TYPE.
+COMMIT;
+DROP TYPE myoutertype;
+DROP TYPE myinnertype;
+-- MOVE goes through the same portal path and must also be rejected.
+CREATE TYPE mycomptype2 AS (a INT, b INT);
+BEGIN;
+DECLARE cur3 CURSOR FOR
+ SELECT (i, i)::mycomptype2 FROM generate_series(1, 10) i;
+FETCH cur3;
+ row
+-------
+ (1,1)
+(1 row)
+
+ALTER TYPE mycomptype2 ALTER ATTRIBUTE b TYPE TEXT;
+MOVE cur3;
+ERROR: cursor scan cannot continue after composite type "mycomptype2" was altered
+HINT: Close and reopen the cursor after ALTER TYPE.
+COMMIT;
+DROP TYPE mycomptype2;
diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql
index 174b062144a..4fad2bc1719 100644
--- a/src/test/regress/sql/rowtypes.sql
+++ b/src/test/regress/sql/rowtypes.sql
@@ -562,3 +562,39 @@ SELECT (NULL::compositetable).a;
SELECT (NULL::compositetable).oid;
DROP TABLE compositetable;
+
+-- ALTER TYPE mid-cursor-scan must be detected and raise an error.
+CREATE TYPE mycomptype AS (a INT, b INT);
+BEGIN;
+DECLARE cur1 CURSOR FOR
+ SELECT (i, i * 100)::mycomptype FROM generate_series(1, 5) i;
+FETCH cur1;
+ALTER TYPE mycomptype ALTER ATTRIBUTE b TYPE TEXT;
+FETCH cur1;
+COMMIT;
+DROP TYPE mycomptype;
+
+-- Same check applies when a nested composite type is altered.
+CREATE TYPE myinnertype AS (x INT, y INT);
+CREATE TYPE myoutertype AS (a INT, b myinnertype);
+BEGIN;
+DECLARE cur2 CURSOR FOR
+ SELECT (i, (i * 10, i * 100)::myinnertype)::myoutertype
+ FROM generate_series(1, 5) i;
+FETCH cur2;
+ALTER TYPE myinnertype ALTER ATTRIBUTE y TYPE TEXT;
+FETCH cur2;
+COMMIT;
+DROP TYPE myoutertype;
+DROP TYPE myinnertype;
+
+-- MOVE goes through the same portal path and must also be rejected.
+CREATE TYPE mycomptype2 AS (a INT, b INT);
+BEGIN;
+DECLARE cur3 CURSOR FOR
+ SELECT (i, i)::mycomptype2 FROM generate_series(1, 10) i;
+FETCH cur3;
+ALTER TYPE mycomptype2 ALTER ATTRIBUTE b TYPE TEXT;
+MOVE cur3;
+COMMIT;
+DROP TYPE mycomptype2;
--
2.51.2
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], [email protected], [email protected]
Subject: Re: BUG #19382: Server crash at __nss_database_lookup
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