Re: polymorphic arguments and return type for PL/pgSQL
От | Joe Conway |
---|---|
Тема | Re: polymorphic arguments and return type for PL/pgSQL |
Дата | |
Msg-id | 3F00C462.6090900@joeconway.com обсуждение исходный текст |
Ответ на | Re: polymorphic arguments and return type for PL/pgSQL (Tom Lane <tgl@sss.pgh.pa.us>) |
Ответы |
Re: polymorphic arguments and return type for PL/pgSQL
(Joe Conway <mail@joeconway.com>)
|
Список | pgsql-patches |
Tom Lane wrote: > Joe Conway wrote: >>2) create hash key using a new structure that includes function oid, >>return type, and argument types, and use that for direct lookup. > > The latter. By the time you pay the price of a hash lookup, a slightly > longer key is nearly free. (Maybe entirely free, since it might produce > better-distributed hash values.) > > dynahash only supports fixed-length keys, so don't forget to zero out > unused positions in the argument type vector. > > BTW, I can't see any need to include the return type in the hash key --- > wouldn't it be predetermined given the argument types? > The attached implements a compiled function hash in addition to the earlier changes to support PL/pgSQL polymorphism. It also includes the ealier requested change wrt generating an ERROR when faced with polymorphic arguments or return type and no FuncExpr node available. The compiled function hash uses the following key: typedef struct PLpgSQL_func_key { Oid funcOid; Oid argtypes[FUNC_MAX_ARGS]; } PLpgSQL_func_key; I did a simple test to check performance impact using the ealier sample function and table: CREATE OR REPLACE FUNCTION tst(anyelement) returns anyarray as ' begin if $1 is of (int2, int4, int8, float4, float8, numeric) then return array[$1 * 2]; elsif $1 is of (text) then return array[$1 || $1]; else return array[$1]; end if; end; ' language 'plpgsql'; create table plpgsql(f1 int, f2 float8, f3 text, f4 oid); insert into plpgsql values(1, 1.1, 'a', 1); insert into plpgsql values(2, 2.2, 'b', 2); --------------------------------------------------------- with original patch (linked list compiled function cache) --------------------------------------------------------- psql regression explain analyze SELECT tst(f1) from plpgsql; Total runtime: 3.73 msec explain analyze SELECT tst(f1) from plpgsql; Total runtime: 0.19 msec explain analyze SELECT tst(f2) from plpgsql; Total runtime: 1.89 msec explain analyze SELECT tst(f2) from plpgsql; Total runtime: 0.19 msec explain analyze SELECT tst(f3) from plpgsql; Total runtime: 1.36 msec explain analyze SELECT tst(f3) from plpgsql; Total runtime: 0.19 msec explain analyze SELECT tst(f4) from plpgsql; Total runtime: 0.70 msec explain analyze SELECT tst(f4) from plpgsql; Total runtime: 0.21 msec explain analyze SELECT tst(f1) from plpgsql; Total runtime: 0.18 msec explain analyze SELECT tst(f2) from plpgsql; Total runtime: 0.19 msec explain analyze SELECT tst(f3) from plpgsql; Total runtime: 0.18 msec explain analyze SELECT tst(f4) from plpgsql; Total runtime: 0.18 msec \q ---------------------------------------------------- with this patch (hash table compiled function cache) ---------------------------------------------------- psql regression explain analyze SELECT tst(f1) from plpgsql; Total runtime: 2.93 msec explain analyze SELECT tst(f1) from plpgsql; Total runtime: 0.19 msec explain analyze SELECT tst(f2) from plpgsql; Total runtime: 1.64 msec explain analyze SELECT tst(f2) from plpgsql; Total runtime: 0.18 msec explain analyze SELECT tst(f3) from plpgsql; Total runtime: 1.05 msec explain analyze SELECT tst(f3) from plpgsql; Total runtime: 0.19 msec explain analyze SELECT tst(f4) from plpgsql; Total runtime: 0.69 msec explain analyze SELECT tst(f4) from plpgsql; Total runtime: 0.19 msec explain analyze SELECT tst(f1) from plpgsql; Total runtime: 0.19 msec explain analyze SELECT tst(f2) from plpgsql; Total runtime: 0.18 msec explain analyze SELECT tst(f3) from plpgsql; Total runtime: 0.21 msec explain analyze SELECT tst(f4) from plpgsql; Total runtime: 0.22 msec \q No difference worth caring about. In more complex scenarios, the hash table cache should win hands down, I'd think. Compiles clean, and passes all regression tests. I'll look to update the docs and regression tests as part of my post freeze array/polymorphic function cleanup. If there are no objections, please apply. Thanks, Joe Index: src/pl/plpgsql/src/pl_comp.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/pl/plpgsql/src/pl_comp.c,v retrieving revision 1.58 diff -c -r1.58 pl_comp.c *** src/pl/plpgsql/src/pl_comp.c 5 May 2003 16:46:27 -0000 1.58 --- src/pl/plpgsql/src/pl_comp.c 30 Jun 2003 22:20:15 -0000 *************** *** 79,88 **** PLpgSQL_function *plpgsql_curr_compile; static void plpgsql_compile_error_callback(void *arg); static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod); ! /* * This routine is a crock, and so is everyplace that calls it. The problem --- 79,118 ---- PLpgSQL_function *plpgsql_curr_compile; + /* ---------- + * compiled function hash table related + * ---------- + */ + static HTAB *plpgsql_HashTable = (HTAB *) NULL; + + typedef struct PLpgSQL_func_key + { + Oid funcOid; + Oid argtypes[FUNC_MAX_ARGS]; + } PLpgSQL_func_key; + + typedef struct plpgsql_hashent + { + PLpgSQL_func_key func_key; + PLpgSQL_function *function; + } plpgsql_HashEnt; + #define FUNCS_PER_USER 128 + + + /* ---------- + * static prototypes + * ---------- + */ static void plpgsql_compile_error_callback(void *arg); static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod); ! static PLpgSQL_function *get_function_by_signature(FunctionCallInfo fcinfo, ! HeapTuple procTup); ! static void plpgsql_HashTableInit(void); ! static PLpgSQL_function *plpgsql_HashTableLookup(PLpgSQL_func_key *func_key); ! static void plpgsql_HashTableInsert(PLpgSQL_function *function); ! static void plpgsql_HashTableDelete(PLpgSQL_function *function); ! static PLpgSQL_func_key *function_get_func_key(PLpgSQL_function *function); /* * This routine is a crock, and so is everyplace that calls it. The problem *************** *** 108,114 **** * ---------- */ PLpgSQL_function * ! plpgsql_compile(Oid fn_oid, int functype) { int parse_rc; HeapTuple procTup; --- 138,144 ---- * ---------- */ PLpgSQL_function * ! plpgsql_compile(FunctionCallInfo fcinfo) { int parse_rc; HeapTuple procTup; *************** *** 123,530 **** int i; int arg_varnos[FUNC_MAX_ARGS]; ErrorContextCallback plerrcontext; /* ! * Lookup the pg_proc tuple by Oid */ procTup = SearchSysCache(PROCOID, ! ObjectIdGetDatum(fn_oid), 0, 0, 0); if (!HeapTupleIsValid(procTup)) ! elog(ERROR, "plpgsql: cache lookup for proc %u failed", fn_oid); ! ! /* ! * Setup the scanner input and error info. We assume that this function ! * cannot be invoked recursively, so there's no need to save and restore ! * the static variables used here. ! */ ! procStruct = (Form_pg_proc) GETSTRUCT(procTup); ! proc_source = DatumGetCString(DirectFunctionCall1(textout, ! PointerGetDatum(&procStruct->prosrc))); ! plpgsql_scanner_init(proc_source, functype); ! pfree(proc_source); ! ! plpgsql_error_funcname = pstrdup(NameStr(procStruct->proname)); ! plpgsql_error_lineno = 0; /* ! * Setup error traceback support for ereport() */ ! plerrcontext.callback = plpgsql_compile_error_callback; ! plerrcontext.arg = NULL; ! plerrcontext.previous = error_context_stack; ! error_context_stack = &plerrcontext; /* ! * Initialize the compiler */ ! plpgsql_ns_init(); ! plpgsql_ns_push(NULL); ! plpgsql_DumpExecTree = 0; ! ! datums_alloc = 128; ! plpgsql_nDatums = 0; ! plpgsql_Datums = palloc(sizeof(PLpgSQL_datum *) * datums_alloc); ! datums_last = 0; ! /* ! * Create the new function node ! */ ! function = malloc(sizeof(PLpgSQL_function)); ! memset(function, 0, sizeof(PLpgSQL_function)); ! plpgsql_curr_compile = function; ! function->fn_name = strdup(NameStr(procStruct->proname)); ! function->fn_oid = fn_oid; ! function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data); ! function->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data); ! function->fn_functype = functype; ! switch (functype) ! { ! case T_FUNCTION: ! /* ! * Normal function has a defined returntype ! */ ! function->fn_rettype = procStruct->prorettype; ! function->fn_retset = procStruct->proretset; ! /* ! * Lookup the functions return type ! */ ! typeTup = SearchSysCache(TYPEOID, ! ObjectIdGetDatum(procStruct->prorettype), ! 0, 0, 0); ! if (!HeapTupleIsValid(typeTup)) ! elog(ERROR, "cache lookup for return type %u failed", ! procStruct->prorettype); ! typeStruct = (Form_pg_type) GETSTRUCT(typeTup); ! /* Disallow pseudotype result, except VOID or RECORD */ ! if (typeStruct->typtype == 'p') ! { ! if (procStruct->prorettype == VOIDOID || ! procStruct->prorettype == RECORDOID) ! /* okay */ ; ! else if (procStruct->prorettype == TRIGGEROID) ! elog(ERROR, "plpgsql functions cannot return type %s" ! "\n\texcept when used as triggers", ! format_type_be(procStruct->prorettype)); else ! elog(ERROR, "plpgsql functions cannot return type %s", ! format_type_be(procStruct->prorettype)); ! } ! ! if (typeStruct->typrelid != InvalidOid || ! procStruct->prorettype == RECORDOID) ! function->fn_retistuple = true; ! else ! { ! function->fn_retbyval = typeStruct->typbyval; ! function->fn_rettyplen = typeStruct->typlen; ! function->fn_rettypelem = typeStruct->typelem; ! perm_fmgr_info(typeStruct->typinput, &(function->fn_retinput)); ! } ! ReleaseSysCache(typeTup); ! ! /* ! * Create the variables for the procedures parameters ! */ ! for (i = 0; i < procStruct->pronargs; i++) ! { ! char buf[32]; ! snprintf(buf, sizeof(buf), "$%d", i + 1); /* name for variable */ /* ! * Get the parameters type */ typeTup = SearchSysCache(TYPEOID, ! ObjectIdGetDatum(procStruct->proargtypes[i]), 0, 0, 0); if (!HeapTupleIsValid(typeTup)) ! elog(ERROR, "cache lookup for argument type %u failed", ! procStruct->proargtypes[i]); typeStruct = (Form_pg_type) GETSTRUCT(typeTup); ! /* Disallow pseudotype argument */ if (typeStruct->typtype == 'p') ! elog(ERROR, "plpgsql functions cannot take type %s", ! format_type_be(procStruct->proargtypes[i])); ! if (typeStruct->typrelid != InvalidOid) { /* ! * For tuple type parameters, we set up a record of ! * that type */ ! row = plpgsql_build_rowtype(typeStruct->typrelid); ! ! row->refname = strdup(buf); ! plpgsql_adddatum((PLpgSQL_datum *) row); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_ROW, row->rowno, ! row->refname); - arg_varnos[i] = row->rowno; - } - else - { /* ! * Normal parameters get a var node */ ! var = malloc(sizeof(PLpgSQL_var)); ! memset(var, 0, sizeof(PLpgSQL_var)); ! var->dtype = PLPGSQL_DTYPE_VAR; ! var->refname = strdup(buf); ! var->lineno = 0; ! var->datatype = build_datatype(typeTup, -1); ! var->isconst = true; ! var->notnull = false; ! var->default_val = NULL; ! ! plpgsql_adddatum((PLpgSQL_datum *) var); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, ! var->refname); ! arg_varnos[i] = var->varno; } ! ReleaseSysCache(typeTup); ! } ! break; ! case T_TRIGGER: ! /* ! * Trigger procedures return type is unknown yet ! */ ! function->fn_rettype = InvalidOid; ! function->fn_retbyval = false; ! function->fn_retistuple = true; ! function->fn_retset = false; ! /* ! * Add the record for referencing NEW ! */ ! rec = malloc(sizeof(PLpgSQL_rec)); ! memset(rec, 0, sizeof(PLpgSQL_rec)); ! rec->dtype = PLPGSQL_DTYPE_REC; ! rec->refname = strdup("new"); ! rec->tup = NULL; ! rec->tupdesc = NULL; ! rec->freetup = false; ! plpgsql_adddatum((PLpgSQL_datum *) rec); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->recno, rec->refname); ! function->new_varno = rec->recno; ! /* ! * Add the record for referencing OLD ! */ ! rec = malloc(sizeof(PLpgSQL_rec)); ! memset(rec, 0, sizeof(PLpgSQL_rec)); ! rec->dtype = PLPGSQL_DTYPE_REC; ! rec->refname = strdup("old"); ! rec->tup = NULL; ! rec->tupdesc = NULL; ! rec->freetup = false; ! plpgsql_adddatum((PLpgSQL_datum *) rec); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->recno, rec->refname); ! function->old_varno = rec->recno; ! /* ! * Add the variable tg_name ! */ ! var = malloc(sizeof(PLpgSQL_var)); ! memset(var, 0, sizeof(PLpgSQL_var)); ! var->dtype = PLPGSQL_DTYPE_VAR; ! var->refname = strdup("tg_name"); ! var->lineno = 0; ! var->datatype = plpgsql_parse_datatype("name"); ! var->isconst = false; ! var->notnull = false; ! var->default_val = NULL; ! ! plpgsql_adddatum((PLpgSQL_datum *) var); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname); ! function->tg_name_varno = var->varno; ! /* ! * Add the variable tg_when ! */ ! var = malloc(sizeof(PLpgSQL_var)); ! memset(var, 0, sizeof(PLpgSQL_var)); ! var->dtype = PLPGSQL_DTYPE_VAR; ! var->refname = strdup("tg_when"); ! var->lineno = 0; ! var->datatype = plpgsql_parse_datatype("text"); ! var->isconst = false; ! var->notnull = false; ! var->default_val = NULL; ! ! plpgsql_adddatum((PLpgSQL_datum *) var); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname); ! function->tg_when_varno = var->varno; ! /* ! * Add the variable tg_level ! */ ! var = malloc(sizeof(PLpgSQL_var)); ! memset(var, 0, sizeof(PLpgSQL_var)); ! var->dtype = PLPGSQL_DTYPE_VAR; ! var->refname = strdup("tg_level"); ! var->lineno = 0; ! var->datatype = plpgsql_parse_datatype("text"); ! var->isconst = false; ! var->notnull = false; ! var->default_val = NULL; ! ! plpgsql_adddatum((PLpgSQL_datum *) var); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname); ! function->tg_level_varno = var->varno; ! /* ! * Add the variable tg_op ! */ ! var = malloc(sizeof(PLpgSQL_var)); ! memset(var, 0, sizeof(PLpgSQL_var)); ! var->dtype = PLPGSQL_DTYPE_VAR; ! var->refname = strdup("tg_op"); ! var->lineno = 0; ! var->datatype = plpgsql_parse_datatype("text"); ! var->isconst = false; ! var->notnull = false; ! var->default_val = NULL; ! ! plpgsql_adddatum((PLpgSQL_datum *) var); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname); ! function->tg_op_varno = var->varno; ! /* ! * Add the variable tg_relid ! */ ! var = malloc(sizeof(PLpgSQL_var)); ! memset(var, 0, sizeof(PLpgSQL_var)); ! var->dtype = PLPGSQL_DTYPE_VAR; ! var->refname = strdup("tg_relid"); ! var->lineno = 0; ! var->datatype = plpgsql_parse_datatype("oid"); ! var->isconst = false; ! var->notnull = false; ! var->default_val = NULL; ! ! plpgsql_adddatum((PLpgSQL_datum *) var); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname); ! function->tg_relid_varno = var->varno; ! /* ! * Add the variable tg_relname ! */ ! var = malloc(sizeof(PLpgSQL_var)); ! memset(var, 0, sizeof(PLpgSQL_var)); ! var->dtype = PLPGSQL_DTYPE_VAR; ! var->refname = strdup("tg_relname"); ! var->lineno = 0; ! var->datatype = plpgsql_parse_datatype("name"); ! var->isconst = false; ! var->notnull = false; ! var->default_val = NULL; ! ! plpgsql_adddatum((PLpgSQL_datum *) var); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname); ! function->tg_relname_varno = var->varno; ! /* ! * Add the variable tg_nargs ! */ ! var = malloc(sizeof(PLpgSQL_var)); ! memset(var, 0, sizeof(PLpgSQL_var)); ! var->dtype = PLPGSQL_DTYPE_VAR; ! var->refname = strdup("tg_nargs"); ! var->lineno = 0; ! var->datatype = plpgsql_parse_datatype("int4"); ! var->isconst = false; ! var->notnull = false; ! var->default_val = NULL; ! ! plpgsql_adddatum((PLpgSQL_datum *) var); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname); ! function->tg_nargs_varno = var->varno; ! break; ! default: ! elog(ERROR, "unknown function type %u in plpgsql_compile()", ! functype); ! break; ! } ! /* ! * Create the magic FOUND variable. ! */ ! var = malloc(sizeof(PLpgSQL_var)); ! memset(var, 0, sizeof(PLpgSQL_var)); ! var->dtype = PLPGSQL_DTYPE_VAR; ! var->refname = strdup("found"); ! var->lineno = 0; ! var->datatype = plpgsql_parse_datatype("bool"); ! var->isconst = false; ! var->notnull = false; ! var->default_val = NULL; ! plpgsql_adddatum((PLpgSQL_datum *) var); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname); ! function->found_varno = var->varno; ! /* ! * Forget about the above created variables ! */ ! plpgsql_add_initdatums(NULL); ! /* ! * Now parse the functions text ! */ ! parse_rc = plpgsql_yyparse(); ! if (parse_rc != 0) ! elog(ERROR, "plpgsql: parser returned %d ???", parse_rc); ! plpgsql_scanner_finish(); ! /* ! * If that was successful, complete the functions info. ! */ ! function->fn_nargs = procStruct->pronargs; ! for (i = 0; i < function->fn_nargs; i++) ! function->fn_argvarnos[i] = arg_varnos[i]; ! function->ndatums = plpgsql_nDatums; ! function->datums = malloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums); ! for (i = 0; i < plpgsql_nDatums; i++) ! function->datums[i] = plpgsql_Datums[i]; ! function->action = plpgsql_yylval.program; ReleaseSysCache(procTup); /* ! * Pop the error context stack */ ! error_context_stack = plerrcontext.previous; ! plpgsql_error_funcname = NULL; ! plpgsql_error_lineno = 0; /* * Finally return the compiled function */ if (plpgsql_DumpExecTree) plpgsql_dumptree(function); return function; } --- 153,625 ---- int i; int arg_varnos[FUNC_MAX_ARGS]; ErrorContextCallback plerrcontext; + Oid rettypeid; + Oid funcOid = fcinfo->flinfo->fn_oid; + int functype = CALLED_AS_TRIGGER(fcinfo) ? T_TRIGGER : T_FUNCTION; /* ! * Lookup the pg_proc tuple by Oid; we'll need it in any case */ procTup = SearchSysCache(PROCOID, ! ObjectIdGetDatum(funcOid), 0, 0, 0); if (!HeapTupleIsValid(procTup)) ! elog(ERROR, "plpgsql: cache lookup for proc %u failed", funcOid); /* ! * Lookup the function in the hashtable based on funcOid and ! * argument type signature. Create the hash table if this ! * is the first time through. */ ! function = get_function_by_signature(fcinfo, procTup); /* ! * if the function wasn't found or was out-of-date, ! * we have to compile it */ ! if (!function) ! { ! /* ! * Setup the scanner input and error info. We assume that this function ! * cannot be invoked recursively, so there's no need to save and restore ! * the static variables used here. ! */ ! procStruct = (Form_pg_proc) GETSTRUCT(procTup); ! proc_source = DatumGetCString(DirectFunctionCall1(textout, ! PointerGetDatum(&procStruct->prosrc))); ! plpgsql_scanner_init(proc_source, functype); ! pfree(proc_source); ! plpgsql_error_funcname = pstrdup(NameStr(procStruct->proname)); ! plpgsql_error_lineno = 0; ! /* ! * Setup error traceback support for ereport() ! */ ! plerrcontext.callback = plpgsql_compile_error_callback; ! plerrcontext.arg = NULL; ! plerrcontext.previous = error_context_stack; ! error_context_stack = &plerrcontext; ! /* ! * Initialize the compiler ! */ ! plpgsql_ns_init(); ! plpgsql_ns_push(NULL); ! plpgsql_DumpExecTree = 0; ! ! datums_alloc = 128; ! plpgsql_nDatums = 0; ! plpgsql_Datums = palloc(sizeof(PLpgSQL_datum *) * datums_alloc); ! datums_last = 0; ! /* ! * Create the new function node ! */ ! function = malloc(sizeof(PLpgSQL_function)); ! memset(function, 0, sizeof(PLpgSQL_function)); ! plpgsql_curr_compile = function; ! ! function->fn_name = strdup(NameStr(procStruct->proname)); ! function->fn_oid = funcOid; ! function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data); ! function->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data); ! function->fn_functype = functype; ! switch (functype) ! { ! case T_FUNCTION: ! /* ! * Check for a polymorphic returntype. If found, use the actual ! * returntype type from the caller's FuncExpr node, if we ! * have one. ! */ ! if (procStruct->prorettype == ANYARRAYOID || ! procStruct->prorettype == ANYELEMENTOID) ! { ! rettypeid = get_fn_expr_rettype(fcinfo); ! if (!OidIsValid(rettypeid)) ! elog(ERROR, "could not determine actual return type " \ ! "for polymorphic function %s", ! plpgsql_error_funcname); ! } else ! rettypeid = procStruct->prorettype; ! /* ! * Normal function has a defined returntype ! */ ! function->fn_rettype = rettypeid; ! function->fn_retset = procStruct->proretset; /* ! * Lookup the functions return type */ typeTup = SearchSysCache(TYPEOID, ! ObjectIdGetDatum(rettypeid), 0, 0, 0); if (!HeapTupleIsValid(typeTup)) ! elog(ERROR, "cache lookup for return type %u failed", ! rettypeid); typeStruct = (Form_pg_type) GETSTRUCT(typeTup); ! /* Disallow pseudotype result, except VOID or RECORD */ if (typeStruct->typtype == 'p') ! { ! if (rettypeid == VOIDOID || ! rettypeid == RECORDOID) ! /* okay */ ; ! else if (rettypeid == TRIGGEROID) ! elog(ERROR, "plpgsql functions cannot return type %s" ! "\n\texcept when used as triggers", ! format_type_be(rettypeid)); ! else ! elog(ERROR, "plpgsql functions cannot return type %s", ! format_type_be(rettypeid)); ! } ! ! if (typeStruct->typrelid != InvalidOid || ! rettypeid == RECORDOID) ! function->fn_retistuple = true; ! else ! { ! function->fn_retbyval = typeStruct->typbyval; ! function->fn_rettyplen = typeStruct->typlen; ! function->fn_rettypelem = typeStruct->typelem; ! perm_fmgr_info(typeStruct->typinput, &(function->fn_retinput)); ! } ! ReleaseSysCache(typeTup); ! /* ! * Create the variables for the procedures parameters ! */ ! for (i = 0; i < procStruct->pronargs; i++) { + char buf[32]; + Oid argtypeid = InvalidOid; + + snprintf(buf, sizeof(buf), "$%d", i + 1); /* name for variable */ + /* ! * Check for polymorphic arguments. If found, use the actual ! * parameter type from the caller's FuncExpr node, if we ! * have one. */ ! if (procStruct->proargtypes[i] == ANYARRAYOID || ! procStruct->proargtypes[i] == ANYELEMENTOID) ! { ! argtypeid = get_fn_expr_argtype(fcinfo, i); ! if (!OidIsValid(argtypeid)) ! elog(ERROR, "could not determine actual argument " \ ! "type for polymorphic function %s", ! plpgsql_error_funcname); ! } ! else ! argtypeid = procStruct->proargtypes[i]; ! function->fn_argtypes[i] = argtypeid; /* ! * Get the parameters type */ ! typeTup = SearchSysCache(TYPEOID, ! ObjectIdGetDatum(argtypeid), ! 0, 0, 0); ! if (!HeapTupleIsValid(typeTup)) ! elog(ERROR, "cache lookup for argument type %u failed", ! argtypeid); ! typeStruct = (Form_pg_type) GETSTRUCT(typeTup); ! ! /* Disallow pseudotype argument */ ! if (typeStruct->typtype == 'p') ! elog(ERROR, "plpgsql functions cannot take type %s", ! format_type_be(argtypeid)); ! if (typeStruct->typrelid != InvalidOid) ! { ! /* ! * For tuple type parameters, we set up a record of ! * that type ! */ ! row = plpgsql_build_rowtype(typeStruct->typrelid); ! ! row->refname = strdup(buf); ! ! plpgsql_adddatum((PLpgSQL_datum *) row); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_ROW, row->rowno, ! row->refname); ! ! arg_varnos[i] = row->rowno; ! } ! else ! { ! /* ! * Normal parameters get a var node ! */ ! var = malloc(sizeof(PLpgSQL_var)); ! memset(var, 0, sizeof(PLpgSQL_var)); ! ! var->dtype = PLPGSQL_DTYPE_VAR; ! var->refname = strdup(buf); ! var->lineno = 0; ! var->datatype = build_datatype(typeTup, -1); ! var->isconst = true; ! var->notnull = false; ! var->default_val = NULL; ! ! plpgsql_adddatum((PLpgSQL_datum *) var); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, ! var->refname); ! arg_varnos[i] = var->varno; ! } ! ReleaseSysCache(typeTup); } ! break; ! case T_TRIGGER: ! /* ! * Trigger procedures return type is unknown yet ! */ ! function->fn_rettype = InvalidOid; ! function->fn_retbyval = false; ! function->fn_retistuple = true; ! function->fn_retset = false; ! /* ! * Add the record for referencing NEW ! */ ! rec = malloc(sizeof(PLpgSQL_rec)); ! memset(rec, 0, sizeof(PLpgSQL_rec)); ! rec->dtype = PLPGSQL_DTYPE_REC; ! rec->refname = strdup("new"); ! rec->tup = NULL; ! rec->tupdesc = NULL; ! rec->freetup = false; ! plpgsql_adddatum((PLpgSQL_datum *) rec); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->recno, rec->refname); ! function->new_varno = rec->recno; ! /* ! * Add the record for referencing OLD ! */ ! rec = malloc(sizeof(PLpgSQL_rec)); ! memset(rec, 0, sizeof(PLpgSQL_rec)); ! rec->dtype = PLPGSQL_DTYPE_REC; ! rec->refname = strdup("old"); ! rec->tup = NULL; ! rec->tupdesc = NULL; ! rec->freetup = false; ! plpgsql_adddatum((PLpgSQL_datum *) rec); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->recno, rec->refname); ! function->old_varno = rec->recno; ! /* ! * Add the variable tg_name ! */ ! var = malloc(sizeof(PLpgSQL_var)); ! memset(var, 0, sizeof(PLpgSQL_var)); ! var->dtype = PLPGSQL_DTYPE_VAR; ! var->refname = strdup("tg_name"); ! var->lineno = 0; ! var->datatype = plpgsql_parse_datatype("name"); ! var->isconst = false; ! var->notnull = false; ! var->default_val = NULL; ! ! plpgsql_adddatum((PLpgSQL_datum *) var); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname); ! function->tg_name_varno = var->varno; ! /* ! * Add the variable tg_when ! */ ! var = malloc(sizeof(PLpgSQL_var)); ! memset(var, 0, sizeof(PLpgSQL_var)); ! var->dtype = PLPGSQL_DTYPE_VAR; ! var->refname = strdup("tg_when"); ! var->lineno = 0; ! var->datatype = plpgsql_parse_datatype("text"); ! var->isconst = false; ! var->notnull = false; ! var->default_val = NULL; ! ! plpgsql_adddatum((PLpgSQL_datum *) var); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname); ! function->tg_when_varno = var->varno; ! /* ! * Add the variable tg_level ! */ ! var = malloc(sizeof(PLpgSQL_var)); ! memset(var, 0, sizeof(PLpgSQL_var)); ! var->dtype = PLPGSQL_DTYPE_VAR; ! var->refname = strdup("tg_level"); ! var->lineno = 0; ! var->datatype = plpgsql_parse_datatype("text"); ! var->isconst = false; ! var->notnull = false; ! var->default_val = NULL; ! ! plpgsql_adddatum((PLpgSQL_datum *) var); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname); ! function->tg_level_varno = var->varno; ! /* ! * Add the variable tg_op ! */ ! var = malloc(sizeof(PLpgSQL_var)); ! memset(var, 0, sizeof(PLpgSQL_var)); ! var->dtype = PLPGSQL_DTYPE_VAR; ! var->refname = strdup("tg_op"); ! var->lineno = 0; ! var->datatype = plpgsql_parse_datatype("text"); ! var->isconst = false; ! var->notnull = false; ! var->default_val = NULL; ! ! plpgsql_adddatum((PLpgSQL_datum *) var); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname); ! function->tg_op_varno = var->varno; ! /* ! * Add the variable tg_relid ! */ ! var = malloc(sizeof(PLpgSQL_var)); ! memset(var, 0, sizeof(PLpgSQL_var)); ! var->dtype = PLPGSQL_DTYPE_VAR; ! var->refname = strdup("tg_relid"); ! var->lineno = 0; ! var->datatype = plpgsql_parse_datatype("oid"); ! var->isconst = false; ! var->notnull = false; ! var->default_val = NULL; ! ! plpgsql_adddatum((PLpgSQL_datum *) var); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname); ! function->tg_relid_varno = var->varno; ! /* ! * Add the variable tg_relname ! */ ! var = malloc(sizeof(PLpgSQL_var)); ! memset(var, 0, sizeof(PLpgSQL_var)); ! var->dtype = PLPGSQL_DTYPE_VAR; ! var->refname = strdup("tg_relname"); ! var->lineno = 0; ! var->datatype = plpgsql_parse_datatype("name"); ! var->isconst = false; ! var->notnull = false; ! var->default_val = NULL; ! ! plpgsql_adddatum((PLpgSQL_datum *) var); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname); ! function->tg_relname_varno = var->varno; ! /* ! * Add the variable tg_nargs ! */ ! var = malloc(sizeof(PLpgSQL_var)); ! memset(var, 0, sizeof(PLpgSQL_var)); ! var->dtype = PLPGSQL_DTYPE_VAR; ! var->refname = strdup("tg_nargs"); ! var->lineno = 0; ! var->datatype = plpgsql_parse_datatype("int4"); ! var->isconst = false; ! var->notnull = false; ! var->default_val = NULL; ! ! plpgsql_adddatum((PLpgSQL_datum *) var); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname); ! function->tg_nargs_varno = var->varno; ! break; ! default: ! elog(ERROR, "unknown function type %u in plpgsql_compile()", ! functype); ! break; ! } ! /* ! * Create the magic FOUND variable. ! */ ! var = malloc(sizeof(PLpgSQL_var)); ! memset(var, 0, sizeof(PLpgSQL_var)); ! var->dtype = PLPGSQL_DTYPE_VAR; ! var->refname = strdup("found"); ! var->lineno = 0; ! var->datatype = plpgsql_parse_datatype("bool"); ! var->isconst = false; ! var->notnull = false; ! var->default_val = NULL; ! plpgsql_adddatum((PLpgSQL_datum *) var); ! plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname); ! function->found_varno = var->varno; ! /* ! * Forget about the above created variables ! */ ! plpgsql_add_initdatums(NULL); ! /* ! * Now parse the functions text ! */ ! parse_rc = plpgsql_yyparse(); ! if (parse_rc != 0) ! elog(ERROR, "plpgsql: parser returned %d ???", parse_rc); ! plpgsql_scanner_finish(); ! /* ! * If that was successful, complete the functions info. ! */ ! function->fn_nargs = procStruct->pronargs; ! for (i = 0; i < function->fn_nargs; i++) ! function->fn_argvarnos[i] = arg_varnos[i]; ! function->ndatums = plpgsql_nDatums; ! function->datums = malloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums); ! for (i = 0; i < plpgsql_nDatums; i++) ! function->datums[i] = plpgsql_Datums[i]; ! function->action = plpgsql_yylval.program; ! ! /* ! * add it to the hash table ! */ ! plpgsql_HashTableInsert(function); ! ! /* ! * Pop the error context stack ! */ ! error_context_stack = plerrcontext.previous; ! plpgsql_error_funcname = NULL; ! plpgsql_error_lineno = 0; ! } ReleaseSysCache(procTup); /* ! * Save pointer in FmgrInfo to avoid search on subsequent calls */ ! fcinfo->flinfo->fn_extra = (void *) function; /* * Finally return the compiled function */ if (plpgsql_DumpExecTree) plpgsql_dumptree(function); + return function; } *************** *** 1500,1502 **** --- 1595,1767 ---- plpgsql_error_lineno = plpgsql_scanner_lineno(); elog(ERROR, "%s at or near \"%s\"", s, plpgsql_yytext); } + + + /* + * get_function_by_signature + * + * Returns a function keyed by funcOid and argument types, or NULL + * if not found. Also checks to be sure what we found is still + * valid, clearing the hash table entry and returning NULL if it + * is out-of-date. This is needed because CREATE OR REPLACE FUNCTION + * can modify the function's pg_proc entry without changing its OID + * or arguments. + * + * Creates the hashtable if it doesn't already exist. + */ + static PLpgSQL_function * + get_function_by_signature(FunctionCallInfo fcinfo, HeapTuple procTup) + { + PLpgSQL_function *function = NULL; + PLpgSQL_func_key *func_key; + Oid funcOid = fcinfo->flinfo->fn_oid; + Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup); + int i; + bool entry_valid; + + /* + * first see if we've already been here during the current + * SQL function call + */ + function = (PLpgSQL_function *) fcinfo->flinfo->fn_extra; + + if (!function) + { + if (!plpgsql_HashTable) + plpgsql_HashTableInit(); + + func_key = (PLpgSQL_func_key *) palloc0(sizeof(PLpgSQL_func_key)); + func_key->funcOid = funcOid; + + /* get the argument types */ + for (i = 0; i < procStruct->pronargs; i++) + { + Oid argtypeid = InvalidOid; + + /* + * Check for polymorphic arguments. If found, use the actual + * parameter type from the caller's FuncExpr node, if we + * have one. + */ + if (procStruct->proargtypes[i] == ANYARRAYOID || + procStruct->proargtypes[i] == ANYELEMENTOID) + { + argtypeid = get_fn_expr_argtype(fcinfo, i); + if (!OidIsValid(argtypeid)) + elog(ERROR, "could not determine actual argument " \ + "type for polymorphic function %s", + plpgsql_error_funcname); + } + else + argtypeid = procStruct->proargtypes[i]; + + func_key->argtypes[i] = argtypeid; + } + + function = plpgsql_HashTableLookup(func_key); + } + + if (function) + { + /* we have a compiled function, but is it still valid */ + entry_valid = + (function->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) && + function->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data)); + + if (!entry_valid) + { + plpgsql_HashTableDelete(function); + + /* XXX: is it worth worrying about the leaked function struct? */ + function = NULL; + } + } + + return function; + } + + static void + plpgsql_HashTableInit(void) + { + HASHCTL ctl; + + memset(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(PLpgSQL_func_key); + ctl.entrysize = sizeof(plpgsql_HashEnt); + ctl.hash = tag_hash; + plpgsql_HashTable = hash_create("PLpgSQL func cache", FUNCS_PER_USER, + &ctl, HASH_ELEM | HASH_FUNCTION); + } + + static PLpgSQL_function * + plpgsql_HashTableLookup(PLpgSQL_func_key *func_key) + { + plpgsql_HashEnt *hentry; + + hentry = (plpgsql_HashEnt*) hash_search(plpgsql_HashTable, + (void *) func_key, + HASH_FIND, + NULL); + + if (hentry) + return hentry->function; + else + return (PLpgSQL_function *) NULL; + } + + static void + plpgsql_HashTableInsert(PLpgSQL_function *function) + { + plpgsql_HashEnt *hentry; + bool found; + PLpgSQL_func_key *func_key; + + func_key = function_get_func_key(function); + hentry = (plpgsql_HashEnt*) hash_search(plpgsql_HashTable, + (void *) func_key, + HASH_ENTER, + &found); + + if (hentry == NULL) + elog(ERROR, "out of memory in plpgsql_HashTable"); + + if (found) + elog(WARNING, "trying to insert a function that exists."); + + hentry->function = function; + } + + static void + plpgsql_HashTableDelete(PLpgSQL_function *function) + { + plpgsql_HashEnt *hentry; + PLpgSQL_func_key *func_key; + + func_key = function_get_func_key(function); + hentry = (plpgsql_HashEnt*) hash_search(plpgsql_HashTable, + (void *) func_key, + HASH_REMOVE, + NULL); + + if (hentry == NULL) + elog(WARNING, "trying to delete function that does not exist."); + } + + static PLpgSQL_func_key * + function_get_func_key(PLpgSQL_function *function) + { + PLpgSQL_func_key *func_key; + int i; + + Assert(function != NULL); + + func_key = (PLpgSQL_func_key *) palloc0(sizeof(PLpgSQL_func_key)); + func_key->funcOid = function->fn_oid; + + /* get the argument types */ + for (i = 0; i < function->fn_nargs; i++) + func_key->argtypes[i] = function->fn_argtypes[i]; + + return func_key; + } + Index: src/pl/plpgsql/src/pl_handler.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/pl/plpgsql/src/pl_handler.c,v retrieving revision 1.12 diff -c -r1.12 pl_handler.c *** src/pl/plpgsql/src/pl_handler.c 30 Aug 2002 00:28:41 -0000 1.12 --- src/pl/plpgsql/src/pl_handler.c 30 Jun 2003 21:35:11 -0000 *************** *** 44,59 **** #include "utils/builtins.h" #include "utils/syscache.h" - - /* - * Head of list of already-compiled functions - */ - static PLpgSQL_function *compiled_functions = NULL; - - - static bool func_up_to_date(PLpgSQL_function * func); - - /* ---------- * plpgsql_call_handler * --- 44,49 ---- *************** *** 67,76 **** Datum plpgsql_call_handler(PG_FUNCTION_ARGS) { - bool isTrigger = CALLED_AS_TRIGGER(fcinfo); - Oid funcOid = fcinfo->flinfo->fn_oid; PLpgSQL_function *func; Datum retval; /* * Connect to SPI manager --- 57,65 ---- Datum plpgsql_call_handler(PG_FUNCTION_ARGS) { PLpgSQL_function *func; Datum retval; + bool isTrigger = CALLED_AS_TRIGGER(fcinfo); /* * Connect to SPI manager *************** *** 78,126 **** if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "plpgsql: cannot connect to SPI manager"); ! /* ! * Check if we already compiled this function and saved the pointer ! * (ie, current FmgrInfo has been used before) ! */ ! func = (PLpgSQL_function *) fcinfo->flinfo->fn_extra; ! if (func != NULL) ! { ! Assert(func->fn_oid == funcOid); ! ! /* ! * But is the function still up to date? ! */ ! if (!func_up_to_date(func)) ! func = NULL; ! } ! ! if (func == NULL) ! { ! /* ! * Check if we already compiled this function for another caller ! */ ! for (func = compiled_functions; func != NULL; func = func->next) ! { ! if (funcOid == func->fn_oid && func_up_to_date(func)) ! break; ! } ! ! /* ! * If not, do so and add it to the compiled ones ! */ ! if (func == NULL) ! { ! func = plpgsql_compile(funcOid, ! isTrigger ? T_TRIGGER : T_FUNCTION); ! func->next = compiled_functions; ! compiled_functions = func; ! } ! ! /* ! * Save pointer in FmgrInfo to avoid search on subsequent calls ! */ ! fcinfo->flinfo->fn_extra = (void *) func; ! } /* * Determine if called as function or trigger and call appropriate --- 67,74 ---- if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "plpgsql: cannot connect to SPI manager"); ! /* Find or compile the function */ ! func = plpgsql_compile(fcinfo); /* * Determine if called as function or trigger and call appropriate *************** *** 139,169 **** elog(ERROR, "plpgsql: SPI_finish() failed"); return retval; - } - - - /* - * Check to see if a compiled function is still up-to-date. This - * is needed because CREATE OR REPLACE FUNCTION can modify the - * function's pg_proc entry without changing its OID. - */ - static bool - func_up_to_date(PLpgSQL_function * func) - { - HeapTuple procTup; - bool result; - - procTup = SearchSysCache(PROCOID, - ObjectIdGetDatum(func->fn_oid), - 0, 0, 0); - if (!HeapTupleIsValid(procTup)) - elog(ERROR, "plpgsql: cache lookup for proc %u failed", - func->fn_oid); - - result = (func->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) && - func->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data)); - - ReleaseSysCache(procTup); - - return result; } --- 87,90 ---- Index: src/pl/plpgsql/src/plpgsql.h =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/pl/plpgsql/src/plpgsql.h,v retrieving revision 1.36 diff -c -r1.36 plpgsql.h *** src/pl/plpgsql/src/plpgsql.h 5 May 2003 16:46:28 -0000 1.36 --- src/pl/plpgsql/src/plpgsql.h 30 Jun 2003 21:28:42 -0000 *************** *** 504,509 **** --- 504,510 ---- bool fn_retset; int fn_nargs; + Oid fn_argtypes[FUNC_MAX_ARGS]; int fn_argvarnos[FUNC_MAX_ARGS]; int found_varno; int new_varno; *************** *** 519,526 **** int ndatums; PLpgSQL_datum **datums; PLpgSQL_stmt_block *action; - - struct PLpgSQL_function *next; /* for chaining list of functions */ } PLpgSQL_function; --- 520,525 ---- *************** *** 588,594 **** * Functions in pl_comp.c * ---------- */ ! extern PLpgSQL_function *plpgsql_compile(Oid fn_oid, int functype); extern int plpgsql_parse_word(char *word); extern int plpgsql_parse_dblword(char *word); extern int plpgsql_parse_tripword(char *word); --- 587,593 ---- * Functions in pl_comp.c * ---------- */ ! extern PLpgSQL_function *plpgsql_compile(FunctionCallInfo fcinfo); extern int plpgsql_parse_word(char *word); extern int plpgsql_parse_dblword(char *word); extern int plpgsql_parse_tripword(char *word);
В списке pgsql-patches по дате отправления: