From 96c408c5029ea274a5a9599ffd9a97ef498e994e Mon Sep 17 00:00:00 2001 From: Euler Taveira Date: Mon, 4 May 2026 13:13:04 -0300 Subject: [PATCH v1] Fix a crash with cached plans after changing composite types If an existing prepared statement contains a composite type and a subsequent ALTER TYPE command changes one of the attribute types, the plan cache was never invalidated, resulting in a crash. Add the composite type relation of any named composite type that appears in Const and RowExpr nodes. The ALTER TYPE ... ALTER ATTRIBUTE command already has the mechanism to send a relcache invalidation. It requires that the composite type relation is added to relationOids list so the PlanCacheRelCallback can find and invalidate the affected plans. Bug: #19470 Reported-by: HaoGang Mao Author: Euler Taveira Discussion: https://postgr.es/m/19470-0a344a5b356fc1a8@postgresql.org --- src/backend/optimizer/plan/setrefs.c | 39 ++++++++++++++++++++++++- src/test/regress/expected/plancache.out | 25 ++++++++++++++++ src/test/regress/sql/plancache.sql | 18 ++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index ff0e875f2a2..fb73de0bd27 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -29,6 +29,7 @@ #include "rewrite/rewriteManip.h" #include "tcop/utility.h" #include "utils/syscache.h" +#include "utils/typcache.h" typedef enum @@ -210,7 +211,8 @@ static List *set_returning_clause_references(PlannerInfo *root, static List *set_windowagg_runcondition_references(PlannerInfo *root, List *runcondition, Plan *plan); - +static void record_plan_composite_type_dependency(PlannerInfo *root, + Oid typid); static void record_elided_node(PlannerGlobal *glob, int plan_node_id, NodeTag elided_type, Bitmapset *relids); @@ -2157,6 +2159,13 @@ fix_expr_common(PlannerInfo *root, Node *node) root->glob->relationOids = lappend_oid(root->glob->relationOids, DatumGetObjectId(con->constvalue)); + + record_plan_composite_type_dependency(root, con->consttype); + } + else if (IsA(node, RowExpr)) + { + record_plan_composite_type_dependency(root, + ((RowExpr *) node)->row_typeid); } else if (IsA(node, GroupingFunc)) { @@ -3692,6 +3701,34 @@ record_plan_type_dependency(PlannerInfo *root, Oid typid) } } +/* + * record_plan_composite_type_dependency + * Mark the current plan as depending on a particular composite type. + * + * Add composite type relation to the list of the relations the plan depends + * on. This ensures that when ALTER TYPE ... ALTER ATTRIBUTE is executed, any + * plans that use this composite type will be invalidated. + */ +static void +record_plan_composite_type_dependency(PlannerInfo *root, Oid typid) +{ + /* + * As in record_plan_function_dependency, ignore the possibility that + * someone would change a built-in composite type. Anonymous record types + * are not considered. + */ + if (typid >= (Oid) FirstUnpinnedObjectId) + { + TypeCacheEntry *typentry; + + typentry = lookup_type_cache(typid, 0); + if (typentry->typtype == TYPTYPE_COMPOSITE && + OidIsValid(typentry->typrelid)) + root->glob->relationOids = + lappend_oid(root->glob->relationOids, typentry->typrelid); + } +} + /* * extract_query_dependencies * Given a rewritten, but not yet planned, query or queries diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out index d58534ca1cd..766c430c862 100644 --- a/src/test/regress/expected/plancache.out +++ b/src/test/regress/expected/plancache.out @@ -402,3 +402,28 @@ select name, generic_plans, custom_plans from pg_prepared_statements (1 row) drop table test_mode; +-- bug #19470: ALTER TYPE ... ALTER ATTRIBUTE should invalidate plans. +-- Test compatible and incompatible attribute type changes. +CREATE TYPE bug_19470 AS (a int, b text); +PREPARE stmt_19470 AS SELECT CAST(ROW(1, 'hello') AS bug_19470)::text; +EXECUTE stmt_19470; + row +----------- + (1,hello) +(1 row) + +-- should be ok +ALTER TYPE bug_19470 ALTER ATTRIBUTE a TYPE varchar(100); +EXECUTE stmt_19470; + row +----------- + (1,hello) +(1 row) + +-- should fail +ALTER TYPE bug_19470 ALTER ATTRIBUTE a TYPE timestamp without time zone; +EXECUTE stmt_19470; +ERROR: cannot cast type record to bug_19470 +DETAIL: Cannot cast type integer to timestamp without time zone in column 1. +DEALLOCATE stmt_19470; +DROP TYPE bug_19470; diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql index aed388d03a1..842a2137a37 100644 --- a/src/test/regress/sql/plancache.sql +++ b/src/test/regress/sql/plancache.sql @@ -228,3 +228,21 @@ select name, generic_plans, custom_plans from pg_prepared_statements where name = 'test_mode_pp'; drop table test_mode; + +-- bug #19470: ALTER TYPE ... ALTER ATTRIBUTE should invalidate plans. +-- Test compatible and incompatible attribute type changes. +CREATE TYPE bug_19470 AS (a int, b text); + +PREPARE stmt_19470 AS SELECT CAST(ROW(1, 'hello') AS bug_19470)::text; +EXECUTE stmt_19470; + +-- should be ok +ALTER TYPE bug_19470 ALTER ATTRIBUTE a TYPE varchar(100); +EXECUTE stmt_19470; + +-- should fail +ALTER TYPE bug_19470 ALTER ATTRIBUTE a TYPE timestamp without time zone; +EXECUTE stmt_19470; + +DEALLOCATE stmt_19470; +DROP TYPE bug_19470; -- 2.39.5