From 5c8867d4230beeb1a3d12cf45d699ea9ad027180 Mon Sep 17 00:00:00 2001 From: spoondla Date: Fri, 23 Jan 2026 17:28:54 -0800 Subject: [PATCH v4] Fix (bug #19382) server crash when ALTER TYPE is used mid-transaction in PL/pgSQL When ALTER TYPE changes a composite type's column types within a transaction, PL/pgSQL record variables that were populated before the ALTER still hold data in the old format. Returning such records causes a crash because the output functions expect data matching the new type definition, not the old one. The crash manifested as a segmentation fault in record_out() when it attempted to interpret integer data as a text pointer, due to the mismatch between the stored data and the current type definition. --- src/pl/plpgsql/src/pl_exec.c | 69 +++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 65b0fd0790f..6250c4a748b 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -470,6 +470,7 @@ static char *format_preparedparamsdata(PLpgSQL_execstate *estate, static PLpgSQL_variable *make_callstmt_target(PLpgSQL_execstate *estate, PLpgSQL_expr *expr); +static void check_record_type_not_altered(PLpgSQL_rec *rec); /* ---------- * plpgsql_exec_function Called by the call handler for @@ -3287,8 +3288,30 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt) } break; - case PLPGSQL_DTYPE_ROW: case PLPGSQL_DTYPE_REC: + { + PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar; + int32 rettypmod; + + /* + * Check if the record's composite type was altered since + * the record was populated. If so, raise an error to + * prevent crashes when outputting the record. + */ + if (rec->rectypeid != RECORDOID && rec->erh != NULL && + !ExpandedRecordIsEmpty(rec->erh)) + check_record_type_not_altered(rec); + + exec_eval_datum(estate, + retvar, + &estate->rettype, + &rettypmod, + &estate->retval, + &estate->retisnull); + } + break; + + case PLPGSQL_DTYPE_ROW: { /* exec_eval_datum can handle these cases */ int32 rettypmod; @@ -3434,6 +3457,14 @@ exec_stmt_return_next(PLpgSQL_execstate *estate, TupleDesc rec_tupdesc; TupleConversionMap *tupmap; + /* + * Check if the record's composite type was altered since + * the record was populated. If so, raise an error to + * prevent crashes when storing to the tuplestore. + */ + if (rec->rectypeid != RECORDOID && rec->erh != NULL) + check_record_type_not_altered(rec); + /* If rec is null, try to convert it to a row of nulls */ if (rec->erh == NULL) instantiate_empty_record_variable(estate, rec); @@ -9216,3 +9247,39 @@ format_preparedparamsdata(PLpgSQL_execstate *estate, return paramstr.data; } + +/* + * check_record_type_not_altered + * + * Check if a record's composite type has been altered since the record + * was populated. If so, raise an error to prevent crashes that would + * occur when outputting data that no longer matches the current type + * definition. + */ +static void +check_record_type_not_altered(PLpgSQL_rec *rec) +{ + ExpandedRecordHeader *erh = rec->erh; + TypeCacheEntry *typentry; + + /* Nothing to do for anonymous RECORD type */ + if (rec->rectypeid == RECORDOID) + return; + + /* Get current type definition from typcache */ + typentry = lookup_type_cache(rec->rectypeid, + TYPECACHE_TUPDESC | + TYPECACHE_DOMAIN_BASE_INFO); + if (typentry->typtype == TYPTYPE_DOMAIN) + typentry = lookup_type_cache(typentry->domainBaseType, + TYPECACHE_TUPDESC); + + /* If type has changed since the record was populated, raise an error */ + if (erh->er_tupdesc_id != typentry->tupDesc_identifier) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot return record variable \"%s\" after its composite type was altered", + rec->refname), + errdetail("ALTER TYPE changed the definition of type \"%s\" after the record was populated.", + format_type_be(rec->rectypeid)))); +} -- 2.39.5 (Apple Git-154)