aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/cache/funccache.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/cache/funccache.c')
-rw-r--r--src/backend/utils/cache/funccache.c612
1 files changed, 612 insertions, 0 deletions
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;
+}