=== Applying patches on top of PostgreSQL commit ID 9b7eb6f02e8d4affb225dd0aa239c8e7e0ff2cba === /etc/rc.d/jail: WARNING: Per-jail configuration via jail_* variables is obsolete. Please consider migrating to /etc/jail.conf. Tue Aug 19 01:45:27 UTC 2025 On branch cf/5974 nothing to commit, working tree clean === using 'git am' to apply patch ./00001_free_function_memory.patch === Patch format detection failed. === using patch(1) to apply patch ./00001_free_function_memory.patch === patching file src/backend/commands/functioncmds.c Hunk #1 succeeded at 1313 (offset 12 lines). Hunk #2 succeeded at 1324 (offset 12 lines). patching file src/include/utils/plancache.h Hunk #1 succeeded at 253 (offset 20 lines). patching file src/pl/plpgsql/src/pl_comp.c Hunk #1 FAILED at 67. Hunk #2 FAILED at 121. Hunk #3 FAILED at 223. Hunk #4 succeeded at 2346 with fuzz 2 (offset -324 lines). 3 out of 4 hunks FAILED -- saving rejects to file src/pl/plpgsql/src/pl_comp.c.rej patching file src/pl/plpgsql/src/pl_handler.c Hunk #1 succeeded at 149 (offset 3 lines). Hunk #2 succeeded at 206 (offset 2 lines). patching file src/pl/plpgsql/src/plpgsql.h Hunk #1 FAILED at 1263. 1 out of 1 hunk FAILED -- saving rejects to file src/pl/plpgsql/src/plpgsql.h.rej Unstaged changes after reset: M src/backend/commands/functioncmds.c M src/include/utils/plancache.h M src/pl/plpgsql/src/pl_comp.c M src/pl/plpgsql/src/pl_handler.c Removing src/pl/plpgsql/src/pl_comp.c.rej Removing src/pl/plpgsql/src/plpgsql.h.rej === using 'git apply' to apply patch ./00001_free_function_memory.patch === Applied patch to 'src/backend/commands/functioncmds.c' cleanly. Applied patch to 'src/include/utils/plancache.h' cleanly. Applied patch to 'src/pl/plpgsql/src/pl_comp.c' with conflicts. Applied patch to 'src/pl/plpgsql/src/pl_handler.c' cleanly. Applied patch to 'src/pl/plpgsql/src/plpgsql.h' with conflicts. U src/pl/plpgsql/src/pl_comp.c U src/pl/plpgsql/src/plpgsql.h diff --cc src/pl/plpgsql/src/pl_comp.c index f6976689a69,12adcc37ef9..00000000000 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@@ -53,6 -56,37 +53,40 @@@ PLpgSQL_function *plpgsql_curr_compile MemoryContext plpgsql_compile_tmp_cxt; /* ---------- ++<<<<<<< ours ++======= + * Hash table for compiled functions + * ---------- + */ + static HTAB *plpgsql_HashTable = NULL; + + typedef struct plpgsql_hashent + { + PLpgSQL_func_hashkey key; + PLpgSQL_function *function; + } plpgsql_HashEnt; + + /* ---------- + * Hash table for recording funcOid and functions + * ---------- + */ + static HTAB *plpgsql_funcOid_HashTable = NULL; + + typedef struct PLpgSQL_funcOid_hashkey + { + Oid funcOid; + } PLpgSQL_funcOid_hashkey; + + typedef struct plpgsql_function_hashent + { + PLpgSQL_funcOid_hashkey key; + PLpgSQL_function *function; + } plpgsql_function_hashent; + + #define FUNCS_PER_USER 128 /* initial table size */ + + /* ---------- ++>>>>>>> theirs * Lookup table for EXCEPTION condition names * ---------- */ @@@ -91,6 -125,22 +125,25 @@@ static PLpgSQL_type *build_datatype(Hea Oid collation, TypeName *origtypname); static void plpgsql_start_datums(void); static void plpgsql_finish_datums(PLpgSQL_function *function); ++<<<<<<< ours ++======= + static void compute_function_hashkey(FunctionCallInfo fcinfo, + Form_pg_proc procStruct, + PLpgSQL_func_hashkey *hashkey, + bool forValidator); + static void plpgsql_resolve_polymorphic_argtypes(int numargs, + Oid *argtypes, char *argmodes, + Node *call_expr, bool forValidator, + const char *proname); + static PLpgSQL_function *plpgsql_HashTableLookup(PLpgSQL_func_hashkey *func_key); + static void plpgsql_HashTableInsert(PLpgSQL_function *function, + PLpgSQL_func_hashkey *func_key); + static void plpgsql_HashTableDelete(PLpgSQL_function *function); + static void delete_function(PLpgSQL_function *func); + static PLpgSQL_function *plpgsql_funcOid_HashTableLookup(PLpgSQL_funcOid_hashkey *func_key); + static void plpgsql_funcOid_HashTableInsert(PLpgSQL_function *function); + static void plpgsql_funcOid_HashTableDelete(PLpgSQL_funcOid_hashkey *func_key); ++>>>>>>> theirs /* ---------- * plpgsql_compile Make an execution tree for a PL/pgSQL function. @@@ -105,24 -155,100 +158,48 @@@ PLpgSQL_function * plpgsql_compile(FunctionCallInfo fcinfo, bool forValidator) { - Oid funcOid = fcinfo->flinfo->fn_oid; - HeapTuple procTup; - Form_pg_proc procStruct; PLpgSQL_function *function; - PLpgSQL_func_hashkey 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); /* - * See if there's already a cache entry for the current FmgrInfo. If not, - * try to find one in the hash table. - */ - function = (PLpgSQL_function *) fcinfo->flinfo->fn_extra; - -recheck: - if (!function) - { - /* Compute hashkey using function signature and actual arg types */ - compute_function_hashkey(fcinfo, procStruct, &hashkey, forValidator); - hashkey_valid = true; - - /* And do the lookup */ - function = plpgsql_HashTableLookup(&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 + * funccache.c manages re-use of existing PLpgSQL_function caches. + * + * In PL/pgSQL we use fn_extra directly as the pointer to the long-lived + * function cache entry; we have no need for any query-lifespan cache. + * Also, we don't need to make the cache key depend on composite result + * type (at least for now). */ ++<<<<<<< ours + function = (PLpgSQL_function *) + cached_function_compile(fcinfo, + fcinfo->flinfo->fn_extra, + plpgsql_compile_callback, + plpgsql_delete_callback, + sizeof(PLpgSQL_function), + false, + forValidator); ++======= + 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, + forValidator); + + /* + * Do the hard part. + */ + function = do_compile(fcinfo, procTup, function, + &hashkey, forValidator); + + /* Record the relationship between funcOid and function */ + plpgsql_funcOid_HashTableInsert(function); + } + + ReleaseSysCache(procTup); ++>>>>>>> theirs /* * Save pointer in FmgrInfo to avoid search on subsequent calls @@@ -2346,3 -2454,326 +2423,329 @@@ plpgsql_add_initdatums(int **varnos datums_last = plpgsql_nDatums; return n; } ++<<<<<<< ours ++======= + + + /* + * Compute the hashkey for a given function invocation + * + * The hashkey is returned into the caller-provided storage at *hashkey. + */ + static void + compute_function_hashkey(FunctionCallInfo fcinfo, + Form_pg_proc procStruct, + PLpgSQL_func_hashkey *hashkey, + bool forValidator) + { + /* Make sure any unused bytes of the struct are zero */ + MemSet(hashkey, 0, sizeof(PLpgSQL_func_hashkey)); + + /* 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); + + /* + * 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; + + if (procStruct->pronargs > 0) + { + /* get the argument types */ + memcpy(hashkey->argtypes, procStruct->proargtypes.values, + procStruct->pronargs * sizeof(Oid)); + + /* resolve any polymorphic argument types */ + plpgsql_resolve_polymorphic_argtypes(procStruct->pronargs, + hashkey->argtypes, + NULL, + fcinfo->flinfo->fn_expr, + forValidator, + NameStr(procStruct->proname)); + } + } + + /* + * 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. + */ + static void + plpgsql_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 PLpgSQL_function 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(PLpgSQL_function *func) + { + /* remove function from hash table (might be done already) */ + plpgsql_HashTableDelete(func); + + /* release the function's storage if safe and not done already */ + if (func->use_count == 0) + plpgsql_free_function_memory(func); + } + + /* exported so we can call it from _PG_init() */ + void + plpgsql_HashTableInit(void) + { + HASHCTL ctl; + + /* don't allow double-initialization */ + Assert(plpgsql_HashTable == NULL); + + ctl.keysize = sizeof(PLpgSQL_func_hashkey); + ctl.entrysize = sizeof(plpgsql_HashEnt); + plpgsql_HashTable = hash_create("PLpgSQL function hash", + FUNCS_PER_USER, + &ctl, + HASH_ELEM | HASH_BLOBS); + } + + static PLpgSQL_function * + plpgsql_HashTableLookup(PLpgSQL_func_hashkey *func_key) + { + plpgsql_HashEnt *hentry; + + hentry = (plpgsql_HashEnt *) hash_search(plpgsql_HashTable, + func_key, + HASH_FIND, + NULL); + if (hentry) + return hentry->function; + else + return NULL; + } + + static void + plpgsql_HashTableInsert(PLpgSQL_function *function, + PLpgSQL_func_hashkey *func_key) + { + plpgsql_HashEnt *hentry; + bool found; + + hentry = (plpgsql_HashEnt *) hash_search(plpgsql_HashTable, + func_key, + HASH_ENTER, + &found); + if (found) + elog(WARNING, "trying to insert a function that already exists"); + + hentry->function = function; + /* prepare back link from function to hashtable key */ + function->fn_hashkey = &hentry->key; + } + + static void + plpgsql_HashTableDelete(PLpgSQL_function *function) + { + plpgsql_HashEnt *hentry; + + /* do nothing if not in table */ + if (function->fn_hashkey == NULL) + return; + + hentry = (plpgsql_HashEnt *) hash_search(plpgsql_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; + } + + /* Try to delete and release the plan cache from the hash table */ + void + RemoveFunctionPlanCacheById(Oid funcoid) + { + PLpgSQL_funcOid_hashkey func_key; + PLpgSQL_function *function = NULL; + + func_key.funcOid = funcoid; + function = plpgsql_funcOid_HashTableLookup(&func_key); + if (function) + { + plpgsql_funcOid_HashTableDelete(&func_key); + delete_function(function); + } + } + + void + plpgsql_funcOid_HashTableInit(void) + { + HASHCTL ctl; + + /* don't allow double-initialization */ + Assert(plpgsql_funcOid_HashTable == NULL); + + ctl.keysize = sizeof(PLpgSQL_funcOid_hashkey); + ctl.entrysize = sizeof(plpgsql_function_hashent); + plpgsql_funcOid_HashTable = hash_create("PLpgSQL function Oid hash", + FUNCS_PER_USER, + &ctl, + HASH_ELEM | HASH_BLOBS); + } + + static PLpgSQL_function * + plpgsql_funcOid_HashTableLookup(PLpgSQL_funcOid_hashkey *func_key) + { + plpgsql_function_hashent *hentry; + + hentry = (plpgsql_function_hashent *) hash_search(plpgsql_funcOid_HashTable, + func_key, + HASH_FIND, + NULL); + if (hentry) + return hentry->function; + else + return NULL; + } + + static void + plpgsql_funcOid_HashTableInsert(PLpgSQL_function *function) + { + bool found; + plpgsql_function_hashent *hentry; + PLpgSQL_funcOid_hashkey func_key; + func_key.funcOid = function->fn_oid; + + if (plpgsql_funcOid_HashTableLookup(&func_key)) + return; + + hentry = (plpgsql_function_hashent *) hash_search(plpgsql_funcOid_HashTable, + &func_key, + HASH_ENTER, + &found); + + hentry->function = function; + hentry->key.funcOid = func_key.funcOid; + } + + static void + plpgsql_funcOid_HashTableDelete(PLpgSQL_funcOid_hashkey *func_key) + { + plpgsql_function_hashent *hentry; + + hentry = (plpgsql_function_hashent *) hash_search(plpgsql_funcOid_HashTable, + func_key, + 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 */ + hentry->function = NULL; + hentry->key.funcOid = InvalidOid; -} ++} ++>>>>>>> theirs diff --cc src/pl/plpgsql/src/plpgsql.h index 41e52b8ce71,3ce25635009..00000000000 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@@ -1252,6 -1262,9 +1252,12 @@@ extern PGDLLEXPORT int plpgsql_recogniz extern PLpgSQL_condition *plpgsql_parse_err_condition(char *condname); extern void plpgsql_adddatum(PLpgSQL_datum *newdatum); extern int plpgsql_add_initdatums(int **varnos); ++<<<<<<< ours ++======= + extern void plpgsql_HashTableInit(void); + extern void plpgsql_funcOid_HashTableInit(void); + extern void RemoveFunctionPlanCacheById(Oid funcoid); ++>>>>>>> theirs /* * Functions in pl_exec.c