diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/commands/typecmds.c | 22 | ||||
-rw-r--r-- | src/backend/optimizer/plan/planner.c | 59 | ||||
-rw-r--r-- | src/backend/optimizer/plan/setrefs.c | 64 | ||||
-rw-r--r-- | src/backend/optimizer/util/clauses.c | 66 | ||||
-rw-r--r-- | src/backend/utils/cache/plancache.c | 228 | ||||
-rw-r--r-- | src/backend/utils/cache/typcache.c | 11 |
6 files changed, 391 insertions, 59 deletions
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 1ffc8231d46..544f423dc40 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -62,6 +62,7 @@ #include "parser/parse_type.h" #include "utils/builtins.h" #include "utils/fmgroids.h" +#include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/rel.h" @@ -2297,7 +2298,7 @@ AlterDomainDefault(List *names, Node *defaultRaw) ObjectAddressSet(address, TypeRelationId, domainoid); /* Clean up */ - heap_close(rel, NoLock); + heap_close(rel, RowExclusiveLock); heap_freetuple(newtuple); return address; @@ -2494,8 +2495,6 @@ AlterDomainDropConstraint(List *names, const char *constrName, systable_endscan(conscan); heap_close(conrel, RowExclusiveLock); - heap_close(rel, NoLock); - if (!found) { if (!missing_ok) @@ -2509,8 +2508,18 @@ AlterDomainDropConstraint(List *names, const char *constrName, constrName, TypeNameToString(typename)))); } + /* + * We must send out an sinval message for the domain, to ensure that any + * dependent plans get rebuilt. Since this command doesn't change the + * domain's pg_type row, that won't happen automatically; do it manually. + */ + CacheInvalidateHeapTuple(rel, tup, NULL); + ObjectAddressSet(address, TypeRelationId, domainoid); + /* Clean up */ + heap_close(rel, RowExclusiveLock); + return address; } @@ -2615,6 +2624,13 @@ AlterDomainAddConstraint(List *names, Node *newConstraint, if (!constr->skip_validation) validateDomainConstraint(domainoid, ccbin); + /* + * We must send out an sinval message for the domain, to ensure that any + * dependent plans get rebuilt. Since this command doesn't change the + * domain's pg_type row, that won't happen automatically; do it manually. + */ + CacheInvalidateHeapTuple(typrel, tup, NULL); + ObjectAddressSet(address, TypeRelationId, domainoid); /* Clean up */ diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index c729a99f8b6..b6456485599 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -5923,10 +5923,16 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel, * side-effect that is useful when the expression will get evaluated more than * once. Also, we must fix operator function IDs. * + * This does not return any information about dependencies of the expression. + * Hence callers should use the results only for the duration of the current + * query. Callers that would like to cache the results for longer should use + * expression_planner_with_deps, probably via the plancache. + * * Note: this must not make any damaging changes to the passed-in expression * tree. (It would actually be okay to apply fix_opfuncids to it, but since * we first do an expression_tree_mutator-based walk, what is returned will - * be a new node tree.) + * be a new node tree.) The result is constructed in the current memory + * context; beware that this can leak a lot of additional stuff there, too. */ Expr * expression_planner(Expr *expr) @@ -5945,6 +5951,57 @@ expression_planner(Expr *expr) return (Expr *) result; } +/* + * expression_planner_with_deps + * Perform planner's transformations on a standalone expression, + * returning expression dependency information along with the result. + * + * This is identical to expression_planner() except that it also returns + * information about possible dependencies of the expression, ie identities of + * objects whose definitions affect the result. As in a PlannedStmt, these + * are expressed as a list of relation Oids and a list of PlanInvalItems. + */ +Expr * +expression_planner_with_deps(Expr *expr, + List **relationOids, + List **invalItems) +{ + Node *result; + PlannerGlobal glob; + PlannerInfo root; + + /* Make up dummy planner state so we can use setrefs machinery */ + MemSet(&glob, 0, sizeof(glob)); + glob.type = T_PlannerGlobal; + glob.relationOids = NIL; + glob.invalItems = NIL; + + MemSet(&root, 0, sizeof(root)); + root.type = T_PlannerInfo; + root.glob = &glob; + + /* + * Convert named-argument function calls, insert default arguments and + * simplify constant subexprs. Collect identities of inlined functions + * and elided domains, too. + */ + result = eval_const_expressions(&root, (Node *) expr); + + /* Fill in opfuncid values if missing */ + fix_opfuncids(result); + + /* + * Now walk the finished expression to find anything else we ought to + * record as an expression dependency. + */ + (void) extract_query_dependencies_walker(result, &root); + + *relationOids = glob.relationOids; + *invalItems = glob.invalItems; + + return (Expr *) result; +} + /* * plan_cluster_use_sort diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index a181aad8309..920a99d0d95 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -138,8 +138,7 @@ static List *set_returning_clause_references(PlannerInfo *root, Plan *topplan, Index resultRelation, int rtoffset); -static bool extract_query_dependencies_walker(Node *node, - PlannerInfo *context); + /***************************************************************************** * @@ -175,8 +174,8 @@ static bool extract_query_dependencies_walker(Node *node, * This will be used by plancache.c to drive invalidation of cached plans. * Relation dependencies are represented by OIDs, and everything else by * PlanInvalItems (this distinction is motivated by the shared-inval APIs). - * Currently, relations and user-defined functions are the only types of - * objects that are explicitly tracked this way. + * Currently, relations, user-defined functions, and domains are the only + * types of objects that are explicitly tracked this way. * * 8. We assign every plan node in the tree a unique ID. * @@ -2578,6 +2577,42 @@ record_plan_function_dependency(PlannerInfo *root, Oid funcid) } /* + * record_plan_type_dependency + * Mark the current plan as depending on a particular type. + * + * This is exported so that eval_const_expressions can record a + * dependency on a domain that it's removed a CoerceToDomain node for. + * + * We don't currently need to record dependencies on domains that the + * plan contains CoerceToDomain nodes for, though that might change in + * future. Hence, this isn't actually called in this module, though + * someday fix_expr_common might call it. + */ +void +record_plan_type_dependency(PlannerInfo *root, Oid typeid) +{ + /* + * As in record_plan_function_dependency, ignore the possibility that + * someone would change a built-in domain. + */ + if (typeid >= (Oid) FirstBootstrapObjectId) + { + PlanInvalItem *inval_item = makeNode(PlanInvalItem); + + /* + * It would work to use any syscache on pg_type, but the easiest is + * TYPEOID since we already have the type's OID at hand. Note that + * plancache.c knows we use TYPEOID. + */ + inval_item->cacheId = TYPEOID; + inval_item->hashValue = GetSysCacheHashValue1(TYPEOID, + ObjectIdGetDatum(typeid)); + + root->glob->invalItems = lappend(root->glob->invalItems, inval_item); + } +} + +/* * extract_query_dependencies * Given a rewritten, but not yet planned, query or queries * (i.e. a Query node or list of Query nodes), extract dependencies @@ -2586,6 +2621,13 @@ record_plan_function_dependency(PlannerInfo *root, Oid funcid) * * This is needed by plancache.c to handle invalidation of cached unplanned * queries. + * + * Note: this does not go through eval_const_expressions, and hence doesn't + * reflect its additions of inlined functions and elided CoerceToDomain nodes + * to the invalItems list. This is obviously OK for functions, since we'll + * see them in the original query tree anyway. For domains, it's OK because + * we don't care about domains unless they get elided. That is, a plan might + * have domain dependencies that the query tree doesn't. */ void extract_query_dependencies(Node *query, @@ -2615,14 +2657,20 @@ extract_query_dependencies(Node *query, *hasRowSecurity = glob.dependsOnRole; } -static bool +/* + * Tree walker for extract_query_dependencies. + * + * This is exported so that expression_planner_with_deps can call it on + * simple expressions (post-planning, not before planning, in that case). + * In that usage, glob.dependsOnRole isn't meaningful, but the relationOids + * and invalItems lists are added to as needed. + */ +bool extract_query_dependencies_walker(Node *node, PlannerInfo *context) { if (node == NULL) return false; Assert(!IsA(node, PlaceHolderVar)); - /* Extract function dependencies and check for regclass Consts */ - fix_expr_common(context, node); if (IsA(node, Query)) { Query *query = (Query *) node; @@ -2662,6 +2710,8 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context) return query_tree_walker(query, extract_query_dependencies_walker, (void *) context, 0); } + /* Extract function dependencies and check for regclass Consts */ + fix_expr_common(context, node); return expression_tree_walker(node, extract_query_dependencies_walker, (void *) context); } diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 8df369315bf..f4446169f5e 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -3699,6 +3699,70 @@ eval_const_expressions_mutator(Node *node, newbtest->location = btest->location; return (Node *) newbtest; } + case T_CoerceToDomain: + { + /* + * If the domain currently has no constraints, we replace the + * CoerceToDomain node with a simple RelabelType, which is + * both far faster to execute and more amenable to later + * optimization. We must then mark the plan as needing to be + * rebuilt if the domain's constraints change. + * + * Also, in estimation mode, always replace CoerceToDomain + * nodes, effectively assuming that the coercion will succeed. + */ + CoerceToDomain *cdomain = (CoerceToDomain *) node; + CoerceToDomain *newcdomain; + Node *arg; + + arg = eval_const_expressions_mutator((Node *) cdomain->arg, + context); + if (context->estimate || + !DomainHasConstraints(cdomain->resulttype)) + { + /* Record dependency, if this isn't estimation mode */ + if (context->root && !context->estimate) + record_plan_type_dependency(context->root, + cdomain->resulttype); + + /* Generate RelabelType to substitute for CoerceToDomain */ + /* This should match the RelabelType logic above */ + + while (arg && IsA(arg, RelabelType)) + arg = (Node *) ((RelabelType *) arg)->arg; + + if (arg && IsA(arg, Const)) + { + Const *con = (Const *) arg; + + con->consttype = cdomain->resulttype; + con->consttypmod = cdomain->resulttypmod; + con->constcollid = cdomain->resultcollid; + return (Node *) con; + } + else + { + RelabelType *newrelabel = makeNode(RelabelType); + + newrelabel->arg = (Expr *) arg; + newrelabel->resulttype = cdomain->resulttype; + newrelabel->resulttypmod = cdomain->resulttypmod; + newrelabel->resultcollid = cdomain->resultcollid; + newrelabel->relabelformat = cdomain->coercionformat; + newrelabel->location = cdomain->location; + return (Node *) newrelabel; + } + } + + newcdomain = makeNode(CoerceToDomain); + newcdomain->arg = (Expr *) arg; + newcdomain->resulttype = cdomain->resulttype; + newcdomain->resulttypmod = cdomain->resulttypmod; + newcdomain->resultcollid = cdomain->resultcollid; + newcdomain->coercionformat = cdomain->coercionformat; + newcdomain->location = cdomain->location; + return (Node *) newcdomain; + } case T_PlaceHolderVar: /* @@ -3770,7 +3834,7 @@ eval_const_expressions_mutator(Node *node, * For any node type not handled above, copy the node unchanged but * const-simplify its subexpressions. This is the correct thing for node * types whose behavior might change between planning and execution, such - * as CoerceToDomain. It's also a safe default for new node types not + * as CurrentOfExpr. It's also a safe default for new node types not * known to this routine. */ return ece_generic_processing(node); diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index 9ec81c5f367..2201752ffee 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -27,15 +27,21 @@ * query to change output tupdesc on replan --- if so, it's up to the * caller to notice changes and cope with them. * - * Currently, we track exactly the dependencies of plans on relations and - * user-defined functions. On relcache invalidation events or pg_proc - * syscache invalidation events, we invalidate just those plans that depend - * on the particular object being modified. (Note: this scheme assumes - * that any table modification that requires replanning will generate a - * relcache inval event.) We also watch for inval events on certain other - * system catalogs, such as pg_namespace; but for them, our response is - * just to invalidate all plans. We expect updates on those catalogs to - * be infrequent enough that more-detailed tracking is not worth the effort. + * Currently, we track exactly the dependencies of plans on relations, + * user-defined functions, and domains. On relcache invalidation events or + * pg_proc or pg_type syscache invalidation events, we invalidate just those + * plans that depend on the particular object being modified. (Note: this + * scheme assumes that any table modification that requires replanning will + * generate a relcache inval event.) We also watch for inval events on + * certain other system catalogs, such as pg_namespace; but for them, our + * response is just to invalidate all plans. We expect updates on those + * catalogs to be infrequent enough that more-detailed tracking is not worth + * the effort. + * + * In addition to full-fledged query plans, we provide a facility for + * detecting invalidations of simple scalar expressions. This is fairly + * bare-bones; it's the caller's responsibility to build a new expression + * if the old one gets invalidated. * * * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group @@ -57,6 +63,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/cost.h" #include "optimizer/planmain.h" +#include "optimizer/planner.h" #include "optimizer/prep.h" #include "parser/analyze.h" #include "parser/parsetree.h" @@ -82,10 +89,15 @@ /* * This is the head of the backend's list of "saved" CachedPlanSources (i.e., * those that are in long-lived storage and are examined for sinval events). - * We thread the structs manually instead of using List cells so that we can - * guarantee to save a CachedPlanSource without error. + * We use a dlist instead of separate List cells so that we can guarantee + * to save a CachedPlanSource without error. + */ +static dlist_head saved_plan_list = DLIST_STATIC_INIT(saved_plan_list); + +/* + * This is the head of the backend's list of CachedExpressions. */ -static CachedPlanSource *first_saved_plan = NULL; +static dlist_head cached_expression_list = DLIST_STATIC_INIT(cached_expression_list); static void ReleaseGenericPlan(CachedPlanSource *plansource); static List *RevalidateCachedQuery(CachedPlanSource *plansource, @@ -103,7 +115,7 @@ static void ScanQueryForLocks(Query *parsetree, bool acquire); static bool ScanQueryWalker(Node *node, bool *acquire); static TupleDesc PlanCacheComputeResultDesc(List *stmt_list); static void PlanCacheRelCallback(Datum arg, Oid relid); -static void PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue); +static void PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue); static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue); /* GUC parameter */ @@ -118,7 +130,8 @@ void InitPlanCache(void) { CacheRegisterRelcacheCallback(PlanCacheRelCallback, (Datum) 0); - CacheRegisterSyscacheCallback(PROCOID, PlanCacheFuncCallback, (Datum) 0); + CacheRegisterSyscacheCallback(PROCOID, PlanCacheObjectCallback, (Datum) 0); + CacheRegisterSyscacheCallback(TYPEOID, PlanCacheObjectCallback, (Datum) 0); CacheRegisterSyscacheCallback(NAMESPACEOID, PlanCacheSysCallback, (Datum) 0); CacheRegisterSyscacheCallback(OPEROID, PlanCacheSysCallback, (Datum) 0); CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0); @@ -206,7 +219,6 @@ CreateCachedPlan(RawStmt *raw_parse_tree, plansource->is_saved = false; plansource->is_valid = false; plansource->generation = 0; - plansource->next_saved = NULL; plansource->generic_cost = -1; plansource->total_custom_cost = 0; plansource->num_custom_plans = 0; @@ -274,7 +286,6 @@ CreateOneShotCachedPlan(RawStmt *raw_parse_tree, plansource->is_saved = false; plansource->is_valid = false; plansource->generation = 0; - plansource->next_saved = NULL; plansource->generic_cost = -1; plansource->total_custom_cost = 0; plansource->num_custom_plans = 0; @@ -471,8 +482,7 @@ SaveCachedPlan(CachedPlanSource *plansource) /* * Add the entry to the global list of cached plans. */ - plansource->next_saved = first_saved_plan; - first_saved_plan = plansource; + dlist_push_tail(&saved_plan_list, &plansource->node); plansource->is_saved = true; } @@ -493,21 +503,7 @@ DropCachedPlan(CachedPlanSource *plansource) /* If it's been saved, remove it from the list */ if (plansource->is_saved) { - if (first_saved_plan == plansource) - first_saved_plan = plansource->next_saved; - else - { - CachedPlanSource *psrc; - - for (psrc = first_saved_plan; psrc; psrc = psrc->next_saved) - { - if (psrc->next_saved == plansource) - { - psrc->next_saved = plansource->next_saved; - break; - } - } - } + dlist_delete(&plansource->node); plansource->is_saved = false; } @@ -1399,7 +1395,6 @@ CopyCachedPlan(CachedPlanSource *plansource) newsource->is_saved = false; newsource->is_valid = plansource->is_valid; newsource->generation = plansource->generation; - newsource->next_saved = NULL; /* We may as well copy any acquired cost knowledge */ newsource->generic_cost = plansource->generic_cost; @@ -1459,6 +1454,85 @@ CachedPlanGetTargetList(CachedPlanSource *plansource, } /* + * GetCachedExpression: construct a CachedExpression for an expression. + * + * This performs the same transformations on the expression as + * expression_planner(), ie, convert an expression as emitted by parse + * analysis to be ready to pass to the executor. + * + * The result is stashed in a private, long-lived memory context. + * (Note that this might leak a good deal of memory in the caller's + * context before that.) The passed-in expr tree is not modified. + */ +CachedExpression * +GetCachedExpression(Node *expr) +{ + CachedExpression *cexpr; + List *relationOids; + List *invalItems; + MemoryContext cexpr_context; + MemoryContext oldcxt; + + /* + * Pass the expression through the planner, and collect dependencies. + * Everything built here is leaked in the caller's context; that's + * intentional to minimize the size of the permanent data structure. + */ + expr = (Node *) expression_planner_with_deps((Expr *) expr, + &relationOids, + &invalItems); + + /* + * Make a private memory context, and copy what we need into that. To + * avoid leaking a long-lived context if we fail while copying data, we + * initially make the context under the caller's context. + */ + cexpr_context = AllocSetContextCreate(CurrentMemoryContext, + "CachedExpression", + ALLOCSET_SMALL_SIZES); + + oldcxt = MemoryContextSwitchTo(cexpr_context); + + cexpr = (CachedExpression *) palloc(sizeof(CachedExpression)); + cexpr->magic = CACHEDEXPR_MAGIC; + cexpr->expr = copyObject(expr); + cexpr->is_valid = true; + cexpr->relationOids = copyObject(relationOids); + cexpr->invalItems = copyObject(invalItems); + cexpr->context = cexpr_context; + + MemoryContextSwitchTo(oldcxt); + + /* + * Reparent the expr's memory context under CacheMemoryContext so that it + * will live indefinitely. + */ + MemoryContextSetParent(cexpr_context, CacheMemoryContext); + + /* + * Add the entry to the global list of cached expressions. + */ + dlist_push_tail(&cached_expression_list, &cexpr->node); + + return cexpr; +} + +/* + * FreeCachedExpression + * Delete a CachedExpression. + */ +void +FreeCachedExpression(CachedExpression *cexpr) +{ + /* Sanity check */ + Assert(cexpr->magic == CACHEDEXPR_MAGIC); + /* Unlink from global list */ + dlist_delete(&cexpr->node); + /* Free all storage associated with CachedExpression */ + MemoryContextDelete(cexpr->context); +} + +/* * QueryListGetPrimaryStmt * Get the "primary" stmt within a list, ie, the one marked canSetTag. * @@ -1692,10 +1766,13 @@ PlanCacheComputeResultDesc(List *stmt_list) static void PlanCacheRelCallback(Datum arg, Oid relid) { - CachedPlanSource *plansource; + dlist_iter iter; - for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved) + dlist_foreach(iter, &saved_plan_list) { + CachedPlanSource *plansource = dlist_container(CachedPlanSource, + node, iter.cur); + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); /* No work if it's already invalidated */ @@ -1742,25 +1819,43 @@ PlanCacheRelCallback(Datum arg, Oid relid) } } } + + /* Likewise check cached expressions */ + dlist_foreach(iter, &cached_expression_list) + { + CachedExpression *cexpr = dlist_container(CachedExpression, + node, iter.cur); + + Assert(cexpr->magic == CACHEDEXPR_MAGIC); + + /* No work if it's already invalidated */ + if (!cexpr->is_valid) + continue; + + if ((relid == InvalidOid) ? cexpr->relationOids != NIL : + list_member_oid(cexpr->relationOids, relid)) + { + cexpr->is_valid = false; + } + } } /* - * PlanCacheFuncCallback - * Syscache inval callback function for PROCOID cache + * PlanCacheObjectCallback + * Syscache inval callback function for PROCOID and TYPEOID caches * * Invalidate all plans mentioning the object with the specified hash value, * or all plans mentioning any member of this cache if hashvalue == 0. - * - * Note that the coding would support use for multiple caches, but right - * now only user-defined functions are tracked this way. */ static void -PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue) +PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue) { - CachedPlanSource *plansource; + dlist_iter iter; - for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved) + dlist_foreach(iter, &saved_plan_list) { + CachedPlanSource *plansource = dlist_container(CachedPlanSource, + node, iter.cur); ListCell *lc; Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); @@ -1825,6 +1920,34 @@ PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue) } } } + + /* Likewise check cached expressions */ + dlist_foreach(iter, &cached_expression_list) + { + CachedExpression *cexpr = dlist_container(CachedExpression, + node, iter.cur); + ListCell *lc; + + Assert(cexpr->magic == CACHEDEXPR_MAGIC); + + /* No work if it's already invalidated */ + if (!cexpr->is_valid) + continue; + + foreach(lc, cexpr->invalItems) + { + PlanInvalItem *item = (PlanInvalItem *) lfirst(lc); + + if (item->cacheId != cacheid) + continue; + if (hashvalue == 0 || + item->hashValue == hashvalue) + { + cexpr->is_valid = false; + break; + } + } + } } /* @@ -1845,10 +1968,12 @@ PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue) void ResetPlanCache(void) { - CachedPlanSource *plansource; + dlist_iter iter; - for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved) + dlist_foreach(iter, &saved_plan_list) { + CachedPlanSource *plansource = dlist_container(CachedPlanSource, + node, iter.cur); ListCell *lc; Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); @@ -1888,4 +2013,15 @@ ResetPlanCache(void) } } } + + /* Likewise invalidate cached expressions */ + dlist_foreach(iter, &cached_expression_list) + { + CachedExpression *cexpr = dlist_container(CachedExpression, + node, iter.cur); + + Assert(cexpr->magic == CACHEDEXPR_MAGIC); + + cexpr->is_valid = false; + } } diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c index 09f9d5fdcbd..1a96cc9b98f 100644 --- a/src/backend/utils/cache/typcache.c +++ b/src/backend/utils/cache/typcache.c @@ -992,7 +992,16 @@ load_domaintype_info(TypeCacheEntry *typentry) check_expr = (Expr *) stringToNode(constring); - /* ExecInitExpr will assume we've planned the expression */ + /* + * Plan the expression, since ExecInitExpr will expect that. + * + * Note: caching the result of expression_planner() is not very + * good practice. Ideally we'd use a CachedExpression here so + * that we would react promptly to, eg, changes in inlined + * functions. However, because we don't support mutable domain + * CHECK constraints, it's not really clear that it's worth the + * extra overhead to do that. + */ check_expr = expression_planner(check_expr); r = makeNode(DomainConstraintState); |