diff options
Diffstat (limited to 'src/backend/utils/cache')
-rw-r--r-- | src/backend/utils/cache/Makefile | 1 | ||||
-rw-r--r-- | src/backend/utils/cache/funccache.c | 612 | ||||
-rw-r--r-- | src/backend/utils/cache/meson.build | 1 | ||||
-rw-r--r-- | src/backend/utils/cache/plancache.c | 191 |
4 files changed, 767 insertions, 38 deletions
diff --git a/src/backend/utils/cache/Makefile b/src/backend/utils/cache/Makefile index 5105018cb79..77b3e1a037b 100644 --- a/src/backend/utils/cache/Makefile +++ b/src/backend/utils/cache/Makefile @@ -16,6 +16,7 @@ OBJS = \ attoptcache.o \ catcache.o \ evtcache.o \ + funccache.o \ inval.o \ lsyscache.o \ partcache.o \ diff --git a/src/backend/utils/cache/funccache.c b/src/backend/utils/cache/funccache.c new file mode 100644 index 00000000000..150c502a612 --- /dev/null +++ b/src/backend/utils/cache/funccache.c @@ -0,0 +1,612 @@ +/*------------------------------------------------------------------------- + * + * funccache.c + * Function cache management. + * + * funccache.c manages a cache of function execution data. The cache + * is used by SQL-language and PL/pgSQL functions, and could be used by + * other function languages. Each cache entry is specific to the execution + * of a particular function (identified by OID) with specific input data + * types; so a polymorphic function could have many associated cache entries. + * Trigger functions similarly have a cache entry per trigger. These rules + * allow the cached data to be specific to the particular data types the + * function call will be dealing with. + * + * + * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/cache/funccache.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/pg_proc.h" +#include "commands/event_trigger.h" +#include "commands/trigger.h" +#include "common/hashfn.h" +#include "funcapi.h" +#include "utils/funccache.h" +#include "utils/hsearch.h" +#include "utils/syscache.h" + + +/* + * Hash table for cached functions + */ +static HTAB *cfunc_hashtable = NULL; + +typedef struct CachedFunctionHashEntry +{ + CachedFunctionHashKey key; /* hash key, must be first */ + CachedFunction *function; /* points to data of language-specific size */ +} CachedFunctionHashEntry; + +#define FUNCS_PER_USER 128 /* initial table size */ + +static uint32 cfunc_hash(const void *key, Size keysize); +static int cfunc_match(const void *key1, const void *key2, Size keysize); + + +/* + * Initialize the hash table on first use. + * + * The hash table will be in TopMemoryContext regardless of caller's context. + */ +static void +cfunc_hashtable_init(void) +{ + HASHCTL ctl; + + /* don't allow double-initialization */ + Assert(cfunc_hashtable == NULL); + + ctl.keysize = sizeof(CachedFunctionHashKey); + ctl.entrysize = sizeof(CachedFunctionHashEntry); + ctl.hash = cfunc_hash; + ctl.match = cfunc_match; + cfunc_hashtable = hash_create("Cached function hash", + FUNCS_PER_USER, + &ctl, + HASH_ELEM | HASH_FUNCTION | HASH_COMPARE); +} + +/* + * cfunc_hash: hash function for cfunc hash table + * + * We need special hash and match functions to deal with the optional + * presence of a TupleDesc in the hash keys. As long as we have to do + * that, we might as well also be smart about not comparing unused + * elements of the argtypes arrays. + */ +static uint32 +cfunc_hash(const void *key, Size keysize) +{ + const CachedFunctionHashKey *k = (const CachedFunctionHashKey *) key; + uint32 h; + + Assert(keysize == sizeof(CachedFunctionHashKey)); + /* Hash all the fixed fields except callResultType */ + h = DatumGetUInt32(hash_any((const unsigned char *) k, + offsetof(CachedFunctionHashKey, callResultType))); + /* Incorporate input argument types */ + if (k->nargs > 0) + h = hash_combine(h, + DatumGetUInt32(hash_any((const unsigned char *) k->argtypes, + k->nargs * sizeof(Oid)))); + /* Incorporate callResultType if present */ + if (k->callResultType) + h = hash_combine(h, hashRowType(k->callResultType)); + return h; +} + +/* + * cfunc_match: match function to use with cfunc_hash + */ +static int +cfunc_match(const void *key1, const void *key2, Size keysize) +{ + const CachedFunctionHashKey *k1 = (const CachedFunctionHashKey *) key1; + const CachedFunctionHashKey *k2 = (const CachedFunctionHashKey *) key2; + + Assert(keysize == sizeof(CachedFunctionHashKey)); + /* Compare all the fixed fields except callResultType */ + if (memcmp(k1, k2, offsetof(CachedFunctionHashKey, callResultType)) != 0) + return 1; /* not equal */ + /* Compare input argument types (we just verified that nargs matches) */ + if (k1->nargs > 0 && + memcmp(k1->argtypes, k2->argtypes, k1->nargs * sizeof(Oid)) != 0) + return 1; /* not equal */ + /* Compare callResultType */ + if (k1->callResultType) + { + if (k2->callResultType) + { + if (!equalRowTypes(k1->callResultType, k2->callResultType)) + return 1; /* not equal */ + } + else + return 1; /* not equal */ + } + else + { + if (k2->callResultType) + return 1; /* not equal */ + } + return 0; /* equal */ +} + +/* + * Look up the CachedFunction for the given hash key. + * Returns NULL if not present. + */ +static CachedFunction * +cfunc_hashtable_lookup(CachedFunctionHashKey *func_key) +{ + CachedFunctionHashEntry *hentry; + + if (cfunc_hashtable == NULL) + return NULL; + + hentry = (CachedFunctionHashEntry *) hash_search(cfunc_hashtable, + func_key, + HASH_FIND, + NULL); + if (hentry) + return hentry->function; + else + return NULL; +} + +/* + * Insert a hash table entry. + */ +static void +cfunc_hashtable_insert(CachedFunction *function, + CachedFunctionHashKey *func_key) +{ + CachedFunctionHashEntry *hentry; + bool found; + + if (cfunc_hashtable == NULL) + cfunc_hashtable_init(); + + hentry = (CachedFunctionHashEntry *) hash_search(cfunc_hashtable, + func_key, + HASH_ENTER, + &found); + if (found) + elog(WARNING, "trying to insert a function that already exists"); + + /* + * If there's a callResultType, copy it into TopMemoryContext. If we're + * unlucky enough for that to fail, leave the entry with null + * callResultType, which will probably never match anything. + */ + if (func_key->callResultType) + { + MemoryContext oldcontext = MemoryContextSwitchTo(TopMemoryContext); + + hentry->key.callResultType = NULL; + hentry->key.callResultType = CreateTupleDescCopy(func_key->callResultType); + MemoryContextSwitchTo(oldcontext); + } + + hentry->function = function; + + /* Set back-link from function to hashtable key */ + function->fn_hashkey = &hentry->key; +} + +/* + * Delete a hash table entry. + */ +static void +cfunc_hashtable_delete(CachedFunction *function) +{ + CachedFunctionHashEntry *hentry; + TupleDesc tupdesc; + + /* do nothing if not in table */ + if (function->fn_hashkey == NULL) + return; + + /* + * We need to free the callResultType if present, which is slightly tricky + * because it has to be valid during the hashtable search. Fortunately, + * because we have the hashkey back-link, we can grab that pointer before + * deleting the hashtable entry. + */ + tupdesc = function->fn_hashkey->callResultType; + + hentry = (CachedFunctionHashEntry *) hash_search(cfunc_hashtable, + function->fn_hashkey, + HASH_REMOVE, + NULL); + if (hentry == NULL) + elog(WARNING, "trying to delete function that does not exist"); + + /* Remove back link, which no longer points to allocated storage */ + function->fn_hashkey = NULL; + + /* Release the callResultType if present */ + if (tupdesc) + FreeTupleDesc(tupdesc); +} + +/* + * Compute the hashkey for a given function invocation + * + * The hashkey is returned into the caller-provided storage at *hashkey. + * Note however that if a callResultType is incorporated, we've not done + * anything about copying that. + */ +static void +compute_function_hashkey(FunctionCallInfo fcinfo, + Form_pg_proc procStruct, + CachedFunctionHashKey *hashkey, + Size cacheEntrySize, + bool includeResultType, + bool forValidator) +{ + /* Make sure pad bytes within fixed part of the struct are zero */ + memset(hashkey, 0, offsetof(CachedFunctionHashKey, argtypes)); + + /* get function OID */ + hashkey->funcOid = fcinfo->flinfo->fn_oid; + + /* get call context */ + hashkey->isTrigger = CALLED_AS_TRIGGER(fcinfo); + hashkey->isEventTrigger = CALLED_AS_EVENT_TRIGGER(fcinfo); + + /* record cacheEntrySize so multiple languages can share hash table */ + hashkey->cacheEntrySize = cacheEntrySize; + + /* + * If DML trigger, include trigger's OID in the hash, so that each trigger + * usage gets a different hash entry, allowing for e.g. different relation + * rowtypes or transition table names. In validation mode we do not know + * what relation or transition table names are intended to be used, so we + * leave trigOid zero; the hash entry built in this case will never be + * used for any actual calls. + * + * We don't currently need to distinguish different event trigger usages + * in the same way, since the special parameter variables don't vary in + * type in that case. + */ + if (hashkey->isTrigger && !forValidator) + { + TriggerData *trigdata = (TriggerData *) fcinfo->context; + + hashkey->trigOid = trigdata->tg_trigger->tgoid; + } + + /* get input collation, if known */ + hashkey->inputCollation = fcinfo->fncollation; + + /* + * We include only input arguments in the hash key, since output argument + * types can be deduced from those, and it would require extra cycles to + * include the output arguments. But we have to resolve any polymorphic + * argument types to the real types for the call. + */ + if (procStruct->pronargs > 0) + { + hashkey->nargs = procStruct->pronargs; + memcpy(hashkey->argtypes, procStruct->proargtypes.values, + procStruct->pronargs * sizeof(Oid)); + cfunc_resolve_polymorphic_argtypes(procStruct->pronargs, + hashkey->argtypes, + NULL, /* all args are inputs */ + fcinfo->flinfo->fn_expr, + forValidator, + NameStr(procStruct->proname)); + } + + /* + * While regular OUT arguments are sufficiently represented by the + * resolved input arguments, a function returning composite has additional + * variability: ALTER TABLE/ALTER TYPE could affect what it returns. Also, + * a function returning RECORD may depend on a column definition list to + * determine its output rowtype. If the caller needs the exact result + * type to be part of the hash lookup key, we must run + * get_call_result_type() to find that out. + */ + if (includeResultType) + { + Oid resultTypeId; + TupleDesc tupdesc; + + switch (get_call_result_type(fcinfo, &resultTypeId, &tupdesc)) + { + case TYPEFUNC_COMPOSITE: + case TYPEFUNC_COMPOSITE_DOMAIN: + hashkey->callResultType = tupdesc; + break; + default: + /* scalar result, or indeterminate rowtype */ + break; + } + } +} + +/* + * This is the same as the standard resolve_polymorphic_argtypes() function, + * except that: + * 1. We go ahead and report the error if we can't resolve the types. + * 2. We treat RECORD-type input arguments (not output arguments) as if + * they were polymorphic, replacing their types with the actual input + * types if we can determine those. This allows us to create a separate + * function cache entry for each named composite type passed to such an + * argument. + * 3. In validation mode, we have no inputs to look at, so assume that + * polymorphic arguments are integer, integer-array or integer-range. + */ +void +cfunc_resolve_polymorphic_argtypes(int numargs, + Oid *argtypes, char *argmodes, + Node *call_expr, bool forValidator, + const char *proname) +{ + int i; + + if (!forValidator) + { + int inargno; + + /* normal case, pass to standard routine */ + if (!resolve_polymorphic_argtypes(numargs, argtypes, argmodes, + call_expr)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine actual argument " + "type for polymorphic function \"%s\"", + proname))); + /* also, treat RECORD inputs (but not outputs) as polymorphic */ + inargno = 0; + for (i = 0; i < numargs; i++) + { + char argmode = argmodes ? argmodes[i] : PROARGMODE_IN; + + if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE) + continue; + if (argtypes[i] == RECORDOID || argtypes[i] == RECORDARRAYOID) + { + Oid resolvedtype = get_call_expr_argtype(call_expr, + inargno); + + if (OidIsValid(resolvedtype)) + argtypes[i] = resolvedtype; + } + inargno++; + } + } + else + { + /* special validation case (no need to do anything for RECORD) */ + for (i = 0; i < numargs; i++) + { + switch (argtypes[i]) + { + case ANYELEMENTOID: + case ANYNONARRAYOID: + case ANYENUMOID: /* XXX dubious */ + case ANYCOMPATIBLEOID: + case ANYCOMPATIBLENONARRAYOID: + argtypes[i] = INT4OID; + break; + case ANYARRAYOID: + case ANYCOMPATIBLEARRAYOID: + argtypes[i] = INT4ARRAYOID; + break; + case ANYRANGEOID: + case ANYCOMPATIBLERANGEOID: + argtypes[i] = INT4RANGEOID; + break; + case ANYMULTIRANGEOID: + argtypes[i] = INT4MULTIRANGEOID; + break; + default: + break; + } + } + } +} + +/* + * delete_function - clean up as much as possible of a stale function cache + * + * We can't release the CachedFunction struct itself, because of the + * possibility that there are fn_extra pointers to it. We can release + * the subsidiary storage, but only if there are no active evaluations + * in progress. Otherwise we'll just leak that storage. Since the + * case would only occur if a pg_proc update is detected during a nested + * recursive call on the function, a leak seems acceptable. + * + * Note that this can be called more than once if there are multiple fn_extra + * pointers to the same function cache. Hence be careful not to do things + * twice. + */ +static void +delete_function(CachedFunction *func) +{ + /* remove function from hash table (might be done already) */ + cfunc_hashtable_delete(func); + + /* release the function's storage if safe and not done already */ + if (func->use_count == 0 && + func->dcallback != NULL) + { + func->dcallback(func); + func->dcallback = NULL; + } +} + +/* + * Compile a cached function, if no existing cache entry is suitable. + * + * fcinfo is the current call information. + * + * function should be NULL or the result of a previous call of + * cached_function_compile() for the same fcinfo. The caller will + * typically save the result in fcinfo->flinfo->fn_extra, or in a + * field of a struct pointed to by fn_extra, to re-use in later + * calls within the same query. + * + * ccallback and dcallback are function-language-specific callbacks to + * compile and delete a cached function entry. dcallback can be NULL + * if there's nothing for it to do. + * + * cacheEntrySize is the function-language-specific size of the cache entry + * (which embeds a CachedFunction struct and typically has many more fields + * after that). + * + * If includeResultType is true and the function returns composite, + * include the actual result descriptor in the cache lookup key. + * + * If forValidator is true, we're only compiling for validation purposes, + * and so some checks are skipped. + * + * Note: it's important for this to fall through quickly if the function + * has already been compiled. + * + * Note: this function leaves the "use_count" field as zero. The caller + * is expected to increment the use_count and decrement it when done with + * the cache entry. + */ +CachedFunction * +cached_function_compile(FunctionCallInfo fcinfo, + CachedFunction *function, + CachedFunctionCompileCallback ccallback, + CachedFunctionDeleteCallback dcallback, + Size cacheEntrySize, + bool includeResultType, + bool forValidator) +{ + Oid funcOid = fcinfo->flinfo->fn_oid; + HeapTuple procTup; + Form_pg_proc procStruct; + CachedFunctionHashKey hashkey; + bool function_valid = false; + bool hashkey_valid = false; + + /* + * Lookup the pg_proc tuple by Oid; we'll need it in any case + */ + procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid)); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failed for function %u", funcOid); + procStruct = (Form_pg_proc) GETSTRUCT(procTup); + + /* + * Do we already have a cache entry for the current FmgrInfo? If not, try + * to find one in the hash table. + */ +recheck: + if (!function) + { + /* Compute hashkey using function signature and actual arg types */ + compute_function_hashkey(fcinfo, procStruct, &hashkey, + cacheEntrySize, includeResultType, + forValidator); + hashkey_valid = true; + + /* And do the lookup */ + function = cfunc_hashtable_lookup(&hashkey); + } + + if (function) + { + /* We have a compiled function, but is it still valid? */ + if (function->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) && + ItemPointerEquals(&function->fn_tid, &procTup->t_self)) + function_valid = true; + else + { + /* + * Nope, so remove it from hashtable and try to drop associated + * storage (if not done already). + */ + delete_function(function); + + /* + * If the function isn't in active use then we can overwrite the + * func struct with new data, allowing any other existing fn_extra + * pointers to make use of the new definition on their next use. + * If it is in use then just leave it alone and make a new one. + * (The active invocations will run to completion using the + * previous definition, and then the cache entry will just be + * leaked; doesn't seem worth adding code to clean it up, given + * what a corner case this is.) + * + * If we found the function struct via fn_extra then it's possible + * a replacement has already been made, so go back and recheck the + * hashtable. + */ + if (function->use_count != 0) + { + function = NULL; + if (!hashkey_valid) + goto recheck; + } + } + } + + /* + * If the function wasn't found or was out-of-date, we have to compile it. + */ + if (!function_valid) + { + /* + * Calculate hashkey if we didn't already; we'll need it to store the + * completed function. + */ + if (!hashkey_valid) + compute_function_hashkey(fcinfo, procStruct, &hashkey, + cacheEntrySize, includeResultType, + forValidator); + + /* + * Create the new function struct, if not done already. The function + * structs are never thrown away, so keep them in TopMemoryContext. + */ + Assert(cacheEntrySize >= sizeof(CachedFunction)); + if (function == NULL) + { + function = (CachedFunction *) + MemoryContextAllocZero(TopMemoryContext, cacheEntrySize); + } + else + { + /* re-using a previously existing struct, so clear it out */ + memset(function, 0, cacheEntrySize); + } + + /* + * Fill in the CachedFunction part. fn_hashkey and use_count remain + * zeroes for now. + */ + function->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data); + function->fn_tid = procTup->t_self; + function->dcallback = dcallback; + + /* + * Do the hard, language-specific part. + */ + ccallback(fcinfo, procTup, &hashkey, function, forValidator); + + /* + * Add the completed struct to the hash table. + */ + cfunc_hashtable_insert(function, &hashkey); + } + + ReleaseSysCache(procTup); + + /* + * Finally return the compiled function + */ + return function; +} diff --git a/src/backend/utils/cache/meson.build b/src/backend/utils/cache/meson.build index 104b28737d7..a1784dce585 100644 --- a/src/backend/utils/cache/meson.build +++ b/src/backend/utils/cache/meson.build @@ -4,6 +4,7 @@ backend_sources += files( 'attoptcache.c', 'catcache.c', 'evtcache.c', + 'funccache.c', 'inval.c', 'lsyscache.c', 'partcache.c', diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index 6c2979d5c82..3b681647060 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -14,7 +14,7 @@ * Cache invalidation is driven off sinval events. Any CachedPlanSource * that matches the event is marked invalid, as is its generic CachedPlan * if it has one. When (and if) the next demand for a cached plan occurs, - * parse analysis and rewrite is repeated to build a new valid query tree, + * parse analysis and/or rewrite is repeated to build a new valid query tree, * and then planning is performed as normal. We also force re-analysis and * re-planning if the active search_path is different from the previous time * or, if RLS is involved, if the user changes or the RLS environment changes. @@ -63,6 +63,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/optimizer.h" #include "parser/analyze.h" +#include "rewrite/rewriteHandler.h" #include "storage/lmgr.h" #include "tcop/pquery.h" #include "tcop/utility.h" @@ -75,18 +76,6 @@ /* - * We must skip "overhead" operations that involve database access when the - * cached plan's subject statement is a transaction control command or one - * that requires a snapshot not to be set yet (such as SET or LOCK). More - * generally, statements that do not require parse analysis/rewrite/plan - * activity never need to be revalidated, so we can treat them all like that. - * For the convenience of postgres.c, treat empty statements that way too. - */ -#define StmtPlanRequiresRevalidation(plansource) \ - ((plansource)->raw_parse_tree != NULL && \ - stmt_requires_parse_analysis((plansource)->raw_parse_tree)) - -/* * 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 use a dlist instead of separate List cells so that we can guarantee @@ -100,6 +89,8 @@ static dlist_head saved_plan_list = DLIST_STATIC_INIT(saved_plan_list); static dlist_head cached_expression_list = DLIST_STATIC_INIT(cached_expression_list); static void ReleaseGenericPlan(CachedPlanSource *plansource); +static bool StmtPlanRequiresRevalidation(CachedPlanSource *plansource); +static bool BuildingPlanRequiresSnapshot(CachedPlanSource *plansource); static List *RevalidateCachedQuery(CachedPlanSource *plansource, QueryEnvironment *queryEnv, bool release_generic); @@ -166,7 +157,7 @@ InitPlanCache(void) } /* - * CreateCachedPlan: initially create a plan cache entry. + * CreateCachedPlan: initially create a plan cache entry for a raw parse tree. * * Creation of a cached plan is divided into two steps, CreateCachedPlan and * CompleteCachedPlan. CreateCachedPlan should be called after running the @@ -220,6 +211,7 @@ CreateCachedPlan(RawStmt *raw_parse_tree, plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); plansource->magic = CACHEDPLANSOURCE_MAGIC; plansource->raw_parse_tree = copyObject(raw_parse_tree); + plansource->analyzed_parse_tree = NULL; plansource->query_string = pstrdup(query_string); MemoryContextSetIdentifier(source_context, plansource->query_string); plansource->commandTag = commandTag; @@ -227,6 +219,8 @@ CreateCachedPlan(RawStmt *raw_parse_tree, plansource->num_params = 0; plansource->parserSetup = NULL; plansource->parserSetupArg = NULL; + plansource->postRewrite = NULL; + plansource->postRewriteArg = NULL; plansource->cursor_options = 0; plansource->fixed_result = false; plansource->resultDesc = NULL; @@ -256,6 +250,34 @@ CreateCachedPlan(RawStmt *raw_parse_tree, } /* + * CreateCachedPlanForQuery: initially create a plan cache entry for a Query. + * + * This is used in the same way as CreateCachedPlan, except that the source + * query has already been through parse analysis, and the plancache will never + * try to re-do that step. + * + * Currently this is used only for new-style SQL functions, where we have a + * Query from the function's prosqlbody, but no source text. The query_string + * is typically empty, but is required anyway. + */ +CachedPlanSource * +CreateCachedPlanForQuery(Query *analyzed_parse_tree, + const char *query_string, + CommandTag commandTag) +{ + CachedPlanSource *plansource; + MemoryContext oldcxt; + + /* Rather than duplicating CreateCachedPlan, just do this: */ + plansource = CreateCachedPlan(NULL, query_string, commandTag); + oldcxt = MemoryContextSwitchTo(plansource->context); + plansource->analyzed_parse_tree = copyObject(analyzed_parse_tree); + MemoryContextSwitchTo(oldcxt); + + return plansource; +} + +/* * CreateOneShotCachedPlan: initially create a one-shot plan cache entry. * * This variant of CreateCachedPlan creates a plan cache entry that is meant @@ -289,12 +311,15 @@ CreateOneShotCachedPlan(RawStmt *raw_parse_tree, plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); plansource->magic = CACHEDPLANSOURCE_MAGIC; plansource->raw_parse_tree = raw_parse_tree; + plansource->analyzed_parse_tree = NULL; plansource->query_string = query_string; plansource->commandTag = commandTag; plansource->param_types = NULL; plansource->num_params = 0; plansource->parserSetup = NULL; plansource->parserSetupArg = NULL; + plansource->postRewrite = NULL; + plansource->postRewriteArg = NULL; plansource->cursor_options = 0; plansource->fixed_result = false; plansource->resultDesc = NULL; @@ -465,6 +490,29 @@ CompleteCachedPlan(CachedPlanSource *plansource, } /* + * SetPostRewriteHook: set a hook to modify post-rewrite query trees + * + * Some callers have a need to modify the query trees between rewriting and + * planning. In the initial call to CompleteCachedPlan, it's assumed such + * work was already done on the querytree_list. However, if we're forced + * to replan, it will need to be done over. The caller can set this hook + * to provide code to make that happen. + * + * postRewriteArg is just passed verbatim to the hook. As with parserSetupArg, + * it is caller's responsibility that the referenced data remains + * valid for as long as the CachedPlanSource exists. + */ +void +SetPostRewriteHook(CachedPlanSource *plansource, + PostRewriteHook postRewrite, + void *postRewriteArg) +{ + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + plansource->postRewrite = postRewrite; + plansource->postRewriteArg = postRewriteArg; +} + +/* * SaveCachedPlan: save a cached plan permanently * * This function moves the cached plan underneath CacheMemoryContext (making @@ -567,6 +615,42 @@ ReleaseGenericPlan(CachedPlanSource *plansource) } /* + * We must skip "overhead" operations that involve database access when the + * cached plan's subject statement is a transaction control command or one + * that requires a snapshot not to be set yet (such as SET or LOCK). More + * generally, statements that do not require parse analysis/rewrite/plan + * activity never need to be revalidated, so we can treat them all like that. + * For the convenience of postgres.c, treat empty statements that way too. + */ +static bool +StmtPlanRequiresRevalidation(CachedPlanSource *plansource) +{ + if (plansource->raw_parse_tree != NULL) + return stmt_requires_parse_analysis(plansource->raw_parse_tree); + else if (plansource->analyzed_parse_tree != NULL) + return query_requires_rewrite_plan(plansource->analyzed_parse_tree); + /* empty query never needs revalidation */ + return false; +} + +/* + * Determine if creating a plan for this CachedPlanSource requires a snapshot. + * In fact this function matches StmtPlanRequiresRevalidation(), but we want + * to preserve the distinction between stmt_requires_parse_analysis() and + * analyze_requires_snapshot(). + */ +static bool +BuildingPlanRequiresSnapshot(CachedPlanSource *plansource) +{ + if (plansource->raw_parse_tree != NULL) + return analyze_requires_snapshot(plansource->raw_parse_tree); + else if (plansource->analyzed_parse_tree != NULL) + return query_requires_rewrite_plan(plansource->analyzed_parse_tree); + /* empty query never needs a snapshot */ + return false; +} + +/* * RevalidateCachedQuery: ensure validity of analyzed-and-rewritten query tree. * * What we do here is re-acquire locks and redo parse analysis if necessary. @@ -592,7 +676,6 @@ RevalidateCachedQuery(CachedPlanSource *plansource, bool release_generic) { bool snapshot_set; - RawStmt *rawtree; List *tlist; /* transient query-tree list */ List *qlist; /* permanent query-tree list */ TupleDesc resultDesc; @@ -615,7 +698,10 @@ RevalidateCachedQuery(CachedPlanSource *plansource, /* * If the query is currently valid, we should have a saved search_path --- * check to see if that matches the current environment. If not, we want - * to force replan. + * to force replan. (We could almost ignore this consideration when + * working from an analyzed parse tree; but there are scenarios where + * planning can have search_path-dependent results, for example if it + * inlines an old-style SQL function.) */ if (plansource->is_valid) { @@ -662,9 +748,9 @@ RevalidateCachedQuery(CachedPlanSource *plansource, } /* - * Discard the no-longer-useful query tree. (Note: we don't want to do - * this any earlier, else we'd not have been able to release locks - * correctly in the race condition case.) + * Discard the no-longer-useful rewritten query tree. (Note: we don't + * want to do this any earlier, else we'd not have been able to release + * locks correctly in the race condition case.) */ plansource->is_valid = false; plansource->query_list = NIL; @@ -711,25 +797,52 @@ RevalidateCachedQuery(CachedPlanSource *plansource, } /* - * Run parse analysis and rule rewriting. The parser tends to scribble on - * its input, so we must copy the raw parse tree to prevent corruption of - * the cache. + * Run parse analysis (if needed) and rule rewriting. */ - rawtree = copyObject(plansource->raw_parse_tree); - if (rawtree == NULL) - tlist = NIL; - else if (plansource->parserSetup != NULL) - tlist = pg_analyze_and_rewrite_withcb(rawtree, - plansource->query_string, - plansource->parserSetup, - plansource->parserSetupArg, - queryEnv); + if (plansource->raw_parse_tree != NULL) + { + /* Source is raw parse tree */ + RawStmt *rawtree; + + /* + * The parser tends to scribble on its input, so we must copy the raw + * parse tree to prevent corruption of the cache. + */ + rawtree = copyObject(plansource->raw_parse_tree); + if (plansource->parserSetup != NULL) + tlist = pg_analyze_and_rewrite_withcb(rawtree, + plansource->query_string, + plansource->parserSetup, + plansource->parserSetupArg, + queryEnv); + else + tlist = pg_analyze_and_rewrite_fixedparams(rawtree, + plansource->query_string, + plansource->param_types, + plansource->num_params, + queryEnv); + } + else if (plansource->analyzed_parse_tree != NULL) + { + /* Source is pre-analyzed query, so we only need to rewrite */ + Query *analyzed_tree; + + /* The rewriter scribbles on its input, too, so copy */ + analyzed_tree = copyObject(plansource->analyzed_parse_tree); + /* Acquire locks needed before rewriting ... */ + AcquireRewriteLocks(analyzed_tree, true, false); + /* ... and do it */ + tlist = pg_rewrite_query(analyzed_tree); + } else - tlist = pg_analyze_and_rewrite_fixedparams(rawtree, - plansource->query_string, - plansource->param_types, - plansource->num_params, - queryEnv); + { + /* Empty query, nothing to do */ + tlist = NIL; + } + + /* Apply post-rewrite callback if there is one */ + if (plansource->postRewrite != NULL) + plansource->postRewrite(tlist, plansource->postRewriteArg); /* Release snapshot if we got one */ if (snapshot_set) @@ -963,8 +1076,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist, */ snapshot_set = false; if (!ActiveSnapshotSet() && - plansource->raw_parse_tree && - analyze_requires_snapshot(plansource->raw_parse_tree)) + BuildingPlanRequiresSnapshot(plansource)) { PushActiveSnapshot(GetTransactionSnapshot()); snapshot_set = true; @@ -1703,6 +1815,7 @@ CopyCachedPlan(CachedPlanSource *plansource) newsource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); newsource->magic = CACHEDPLANSOURCE_MAGIC; newsource->raw_parse_tree = copyObject(plansource->raw_parse_tree); + newsource->analyzed_parse_tree = copyObject(plansource->analyzed_parse_tree); newsource->query_string = pstrdup(plansource->query_string); MemoryContextSetIdentifier(source_context, newsource->query_string); newsource->commandTag = plansource->commandTag; @@ -1718,6 +1831,8 @@ CopyCachedPlan(CachedPlanSource *plansource) newsource->num_params = plansource->num_params; newsource->parserSetup = plansource->parserSetup; newsource->parserSetupArg = plansource->parserSetupArg; + newsource->postRewrite = plansource->postRewrite; + newsource->postRewriteArg = plansource->postRewriteArg; newsource->cursor_options = plansource->cursor_options; newsource->fixed_result = plansource->fixed_result; if (plansource->resultDesc) |