From 5311d44fed4871a0cb0bc2b23be027ea38eee142 Mon Sep 17 00:00:00 2001 From: Ayush Tiwari Date: Sat, 25 Apr 2026 09:49:51 +0000 Subject: [PATCH] Detect row type changes in EEOP_ROW expressions EEOP_ROW cached the target composite type's TupleDesc at executor startup and never re-checked it. If ALTER TYPE modified the type while a cursor was still open, the next FETCH would use the stale descriptor, which could crash the backend with SIGSEGV when attribute storage properties changed (e.g. int -> text). Other rowtype-aware steps (EEOP_FIELDSELECT, EEOP_FIELDSTORE_DEFORM, EEOP_NULLTEST_ROW*, EEOP_CONVERT_ROWTYPE) already guard against this via ExprEvalRowtypeCache. Add the same check to EEOP_ROW: stash the TypeCacheEntry pointer and tupDesc_identifier at init time, and compare at runtime in ExecEvalRow(), raising an error if the type has changed. Reported-by: HaoGang Mao --- src/backend/executor/execExpr.c | 15 ++++++++++++++- src/backend/executor/execExprInterp.c | 11 +++++++++++ src/include/executor/execExpr.h | 1 + src/test/regress/expected/rowtypes.out | 17 +++++++++++++++++ src/test/regress/sql/rowtypes.sql | 12 ++++++++++++ 5 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 77229141b38..8b83e5d71ea 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -2011,11 +2011,24 @@ ExecInitExprRec(Expr *node, ExprState *state, ExecTypeSetColNames(tupdesc, rowexpr->colnames); /* Bless the tupdesc so it can be looked up later */ BlessTupleDesc(tupdesc); + scratch.d.row.rowcache.cacheptr = NULL; + scratch.d.row.rowcache.tupdesc_id = 0; } else { + TypeCacheEntry *typentry; + /* it's been cast to a named type, use that */ - tupdesc = lookup_rowtype_tupdesc_copy(rowexpr->row_typeid, -1); + typentry = lookup_type_cache(rowexpr->row_typeid, + TYPECACHE_TUPDESC); + if (typentry->tupDesc == NULL) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("type %s is not composite", + format_type_be(rowexpr->row_typeid)))); + tupdesc = CreateTupleDescCopyConstr(typentry->tupDesc); + scratch.d.row.rowcache.cacheptr = typentry; + scratch.d.row.rowcache.tupdesc_id = typentry->tupDesc_identifier; } /* diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 0634af964a9..0ec06e87278 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -3667,6 +3667,17 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op) { HeapTuple tuple; + if (op->d.row.rowcache.tupdesc_id != 0) + { + TypeCacheEntry *typentry = (TypeCacheEntry *) op->d.row.rowcache.cacheptr; + + if (typentry->tupDesc_identifier != op->d.row.rowcache.tupdesc_id) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("row type %s has changed", + format_type_be(op->d.row.tupdesc->tdtypeid)))); + } + /* build tuple from evaluated field values */ tuple = heap_form_tuple(op->d.row.tupdesc, op->d.row.elemvalues, diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index c61b3d624d5..532e01b7b6c 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -499,6 +499,7 @@ typedef struct ExprEvalStep struct { TupleDesc tupdesc; /* descriptor for result tuples */ + ExprEvalRowtypeCache rowcache; /* workspace for the values constituting the row: */ Datum *elemvalues; bool *elemnulls; diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out index 956bc2d02fc..7ede45b320a 100644 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@ -1408,3 +1408,20 @@ ERROR: column "oid" not found in data type compositetable LINE 1: SELECT (NULL::compositetable).oid; ^ DROP TABLE compositetable; +-- A named ROW() result must not survive ALTER TYPE with the old layout. +CREATE TYPE cursor_rowtype AS (a int, b int); +BEGIN; +DECLARE c CURSOR FOR + SELECT (i, power(2, 30))::cursor_rowtype + FROM generate_series(1, 2) i; +FETCH c; + row +---------------- + (1,1073741824) +(1 row) + +ALTER TYPE cursor_rowtype ALTER ATTRIBUTE b TYPE text; +FETCH c; +ERROR: row type cursor_rowtype has changed +ROLLBACK; +DROP TYPE cursor_rowtype; diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql index 174b062144a..ec64f968be8 100644 --- a/src/test/regress/sql/rowtypes.sql +++ b/src/test/regress/sql/rowtypes.sql @@ -562,3 +562,15 @@ SELECT (NULL::compositetable).a; SELECT (NULL::compositetable).oid; DROP TABLE compositetable; + +-- A named ROW() result must not survive ALTER TYPE with the old layout. +CREATE TYPE cursor_rowtype AS (a int, b int); +BEGIN; +DECLARE c CURSOR FOR + SELECT (i, power(2, 30))::cursor_rowtype + FROM generate_series(1, 2) i; +FETCH c; +ALTER TYPE cursor_rowtype ALTER ATTRIBUTE b TYPE text; +FETCH c; +ROLLBACK; +DROP TYPE cursor_rowtype; -- 2.43.0