From 5e22013d511aa07d3d2b2f70f7842d37e70ea02e Mon Sep 17 00:00:00 2001 From: Andrey Borodin Date: Tue, 7 Apr 2026 15:28:06 +0500 Subject: [PATCH 2/4] Track RTE_FUNCTION composite rowtype dependencies Record typrelid dependencies for named composite outputs of RTE_FUNCTION nodes, so relcache invalidation from ALTER TYPE marks cached plans stale. This keeps prepared and SPI plans in sync with composite rowshape changes. --- doc/src/sgml/ref/alter_type.sgml | 9 +++++++ src/backend/optimizer/plan/setrefs.c | 36 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/doc/src/sgml/ref/alter_type.sgml b/doc/src/sgml/ref/alter_type.sgml index 025a3ee48f5..72b046afd98 100644 --- a/doc/src/sgml/ref/alter_type.sgml +++ b/doc/src/sgml/ref/alter_type.sgml @@ -242,6 +242,15 @@ ALTER TYPE name SET ( + + Changing a composite type's attributes also invalidates cached plans for + queries that scan SETOF functions returning that + composite type (including through a domain over the composite), even when + no CREATE OR REPLACE FUNCTION is run. This matches the + relcache invalidation already sent for the composite type's cataloged + rowtype. + + You must own the type to use ALTER TYPE. To change the schema of a type, you must also have diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index ff0e875f2a2..b7e387b7a2a 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -26,6 +26,7 @@ #include "optimizer/subselect.h" #include "optimizer/tlist.h" #include "parser/parse_relation.h" +#include "parser/parse_type.h" #include "rewrite/rewriteManip.h" #include "tcop/utility.h" #include "utils/syscache.h" @@ -500,6 +501,34 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) } } +/* + * Record relcache dependencies for RTE_FUNCTION entries whose declared result + * is a named composite type (or a domain over one). ALTER TYPE {ADD | DROP | + * ALTER} ATTRIBUTE updates the composite type's pg_class row without changing + * OIDs of dependent functions, so plans must be rebuilt when the composite + * rowtype changes. (Compare extract_query_dependencies_walker, which must + * stay in sync.) + */ +static void +add_function_rte_relation_deps(PlannerGlobal *glob, const RangeTblEntry *rte) +{ + ListCell *lc; + + Assert(rte->rtekind == RTE_FUNCTION); + + foreach(lc, rte->functions) + { + RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); + Oid typid; + Oid typrelid; + + typid = exprType(rtfunc->funcexpr); + typrelid = typeOrDomainTypeRelid(typid); + if (OidIsValid(typrelid)) + glob->relationOids = lappend_oid(glob->relationOids, typrelid); + } +} + /* * Extract RangeTblEntries from a subquery that was never planned at all */ @@ -529,6 +558,8 @@ flatten_rtes_walker(Node *node, flatten_rtes_walker_context *cxt) if (rte->rtekind == RTE_RELATION || (rte->rtekind == RTE_SUBQUERY && OidIsValid(rte->relid))) add_rte_to_flat_rtable(cxt->glob, cxt->query->rteperminfos, rte); + else if (rte->rtekind == RTE_FUNCTION) + add_function_rte_relation_deps(cxt->glob, rte); return false; } if (IsA(node, Query)) @@ -567,6 +598,9 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, List *rteperminfos, { RangeTblEntry *newrte; + if (rte->rtekind == RTE_FUNCTION) + add_function_rte_relation_deps(glob, rte); + /* flat copy to duplicate all the scalar fields */ newrte = palloc_object(RangeTblEntry); memcpy(newrte, rte, sizeof(RangeTblEntry)); @@ -3800,6 +3834,8 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context) (rte->rtekind == RTE_NAMEDTUPLESTORE && OidIsValid(rte->relid))) context->glob->relationOids = lappend_oid(context->glob->relationOids, rte->relid); + else if (rte->rtekind == RTE_FUNCTION) + add_function_rte_relation_deps(context->glob, rte); } /* And recurse into the query's subexpressions */ -- 2.50.1 (Apple Git-155)