📄 pl_comp.c
字号:
/********************************************************************** * pl_comp.c - Compiler part of the PL/pgSQL * procedural language * * IDENTIFICATION * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.94.2.2 2005/12/09 17:09:00 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * * The author hereby grants permission to use, copy, modify, * distribute, and license this software and its documentation * for any purpose, provided that existing copyright notices are * retained in all copies and that this notice is included * verbatim in any distributions. No written agreement, license, * or royalty fee is required for any of the authorized uses. * Modifications to this software may be copyrighted by their * author and need not follow the licensing terms described * here, provided that the new terms are clearly indicated on * the first page of each file where they apply. * * IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR * CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS * SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN * IF THE AUTHOR HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON * AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAVE NO * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, * ENHANCEMENTS, OR MODIFICATIONS. * **********************************************************************/#include "plpgsql.h"#include <ctype.h>#include "pl.tab.h"#include "access/heapam.h"#include "catalog/namespace.h"#include "catalog/pg_attribute.h"#include "catalog/pg_attrdef.h"#include "catalog/pg_class.h"#include "catalog/pg_proc.h"#include "catalog/pg_type.h"#include "funcapi.h"#include "nodes/makefuncs.h"#include "parser/gramparse.h"#include "parser/parse_type.h"#include "tcop/tcopprot.h"#include "utils/array.h"#include "utils/builtins.h"#include "utils/lsyscache.h"#include "utils/memutils.h"#include "utils/syscache.h"/* ---------- * Variables in the parser that shouldn't go into plpgsql.h * ---------- */extern PLPGSQL_YYSTYPE plpgsql_yylval;/* ---------- * Our own local and global variables * ---------- */static int datums_alloc;int plpgsql_nDatums;PLpgSQL_datum **plpgsql_Datums;static int datums_last = 0;int plpgsql_error_lineno;char *plpgsql_error_funcname;bool plpgsql_DumpExecTree = false;bool plpgsql_check_syntax = false;PLpgSQL_function *plpgsql_curr_compile;/* A context appropriate for short-term allocs during compilation */MemoryContext compile_tmp_cxt;/* ---------- * Hash table for compiled functions * ---------- */static HTAB *plpgsql_HashTable = NULL;typedef struct plpgsql_hashent{ PLpgSQL_func_hashkey key; PLpgSQL_function *function;} plpgsql_HashEnt;#define FUNCS_PER_USER 128 /* initial table size *//* ---------- * Lookup table for EXCEPTION condition names * ---------- */typedef struct{ const char *label; int sqlerrstate;} ExceptionLabelMap;static const ExceptionLabelMap exception_label_map[] = {#include "plerrcodes.h" {NULL, 0}};/* ---------- * static prototypes * ---------- */static PLpgSQL_function *do_compile(FunctionCallInfo fcinfo, HeapTuple procTup, PLpgSQL_func_hashkey *hashkey, bool forValidator);static int fetchArgInfo(HeapTuple procTup, Oid **p_argtypes, char ***p_argnames, char **p_argmodes);static PLpgSQL_row *build_row_from_class(Oid classOid);static PLpgSQL_row *build_row_from_vars(PLpgSQL_variable **vars, int numvars);static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod);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);/* ---------- * plpgsql_compile Make an execution tree for a PL/pgSQL function. * * 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. * ---------- */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 hashkey_valid = false; /* * 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, "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; 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 == HeapTupleHeaderGetXmin(procTup->t_data) && function->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data))) { /* Nope, drop the function and associated storage */ delete_function(function); function = NULL; } } /* * If the function wasn't found or was out-of-date, we have to compile it */ if (!function) { /* * 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, &hashkey, forValidator); } ReleaseSysCache(procTup); /* * Save pointer in FmgrInfo to avoid search on subsequent calls */ fcinfo->flinfo->fn_extra = (void *) function; /* * Finally return the compiled function */ return function;}/* * This is the slow part of plpgsql_compile(). * * While compiling a function, the CurrentMemoryContext is the * per-function memory context of the function we are compiling. That * means a palloc() will allocate storage with the same lifetime as * the function itself. * * Because palloc()'d storage will not be immediately freed, temporary * allocations should either be performed in a short-lived memory * context or explicitly pfree'd. Since not all backend functions are * careful about pfree'ing their allocations, it is also wise to * switch into a short-term context before calling into the * backend. An appropriate context for performing short-term * allocations is the compile_tmp_cxt. */static PLpgSQL_function *do_compile(FunctionCallInfo fcinfo, HeapTuple procTup, PLpgSQL_func_hashkey *hashkey, bool forValidator){ Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup); int functype = CALLED_AS_TRIGGER(fcinfo) ? T_TRIGGER : T_FUNCTION; PLpgSQL_function *function; Datum prosrcdatum; bool isnull; char *proc_source; HeapTuple typeTup; Form_pg_type typeStruct; PLpgSQL_variable *var; PLpgSQL_rec *rec; int i; ErrorContextCallback plerrcontext; int parse_rc; Oid rettypeid; int numargs; int num_in_args = 0; int num_out_args = 0; Oid *argtypes; char **argnames; char *argmodes; int *in_arg_varnos = NULL; PLpgSQL_variable **out_arg_variables; MemoryContext func_cxt; /* * 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. */ prosrcdatum = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_prosrc, &isnull); if (isnull) elog(ERROR, "null prosrc"); proc_source = DatumGetCString(DirectFunctionCall1(textout, prosrcdatum)); plpgsql_scanner_init(proc_source, functype); 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 = forValidator ? proc_source : NULL; plerrcontext.previous = error_context_stack; error_context_stack = &plerrcontext; /* * Initialize the compiler */ plpgsql_ns_init(); plpgsql_ns_push(NULL); plpgsql_DumpExecTree = false; datums_alloc = 128; plpgsql_nDatums = 0; /* This is short-lived, so needn't allocate in function's cxt */ plpgsql_Datums = palloc(sizeof(PLpgSQL_datum *) * datums_alloc); datums_last = 0; /* * Do extra syntax checks when validating the function definition. We skip * this when actually compiling functions for execution, for performance * reasons. */ plpgsql_check_syntax = forValidator; /* * Create the new function node. We allocate the function and all of its * compile-time storage (e.g. parse tree) in its own memory context. This * allows us to reclaim the function's storage cleanly. */ func_cxt = AllocSetContextCreate(TopMemoryContext, "PL/PgSQL function context", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); compile_tmp_cxt = MemoryContextSwitchTo(func_cxt); function = palloc0(sizeof(*function)); plpgsql_curr_compile = function; function->fn_name = pstrdup(NameStr(procStruct->proname)); function->fn_oid = fcinfo->flinfo->fn_oid; function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data); function->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data); function->fn_functype = functype; function->fn_cxt = func_cxt; function->out_param_varno = -1; /* set up for no OUT param */ switch (functype) { case T_FUNCTION: /* * Fetch info about the procedure's parameters. Allocations aren't * needed permanently, so make them in tmp cxt. * * We also need to resolve any polymorphic input or output * argument types. In validation mode we won't be able to, so we * arbitrarily assume we are dealing with integers. */ MemoryContextSwitchTo(compile_tmp_cxt); numargs = fetchArgInfo(procTup, &argtypes, &argnames, &argmodes); plpgsql_resolve_polymorphic_argtypes(numargs, argtypes, argmodes, fcinfo->flinfo->fn_expr, forValidator, plpgsql_error_funcname); in_arg_varnos = (int *) palloc(numargs * sizeof(int)); out_arg_variables = (PLpgSQL_variable **) palloc(numargs * sizeof(PLpgSQL_variable *)); MemoryContextSwitchTo(func_cxt); /* * Create the variables for the procedure's parameters. */ for (i = 0; i < numargs; i++) { char buf[32]; Oid argtypeid = argtypes[i]; char argmode = argmodes ? argmodes[i] : PROARGMODE_IN; PLpgSQL_type *argdtype; PLpgSQL_variable *argvariable; int argitemtype; /* Create $n name for variable */ snprintf(buf, sizeof(buf), "$%d", i + 1); /* Create datatype info */ argdtype = plpgsql_build_datatype(argtypeid, -1); /* Disallow pseudotype argument */ /* (note we already replaced ANYARRAY/ANYELEMENT) */ /* (build_variable would do this, but wrong message) */ if (argdtype->ttype != PLPGSQL_TTYPE_SCALAR && argdtype->ttype != PLPGSQL_TTYPE_ROW) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("plpgsql functions cannot take type %s", format_type_be(argtypeid)))); /* Build variable and add to datum list */ argvariable = plpgsql_build_variable(buf, 0, argdtype, false); if (argvariable->dtype == PLPGSQL_DTYPE_VAR) { argitemtype = PLPGSQL_NSTYPE_VAR; /* input argument vars are forced to be CONSTANT */ if (argmode == PROARGMODE_IN) ((PLpgSQL_var *) argvariable)->isconst = true; } else { Assert(argvariable->dtype == PLPGSQL_DTYPE_ROW); argitemtype = PLPGSQL_NSTYPE_ROW; } /* Remember arguments in appropriate arrays */ if (argmode == PROARGMODE_IN || argmode == PROARGMODE_INOUT) in_arg_varnos[num_in_args++] = argvariable->dno; if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_INOUT) out_arg_variables[num_out_args++] = argvariable; /* Add to namespace under the $n name */ plpgsql_ns_additem(argitemtype, argvariable->dno, buf); /* If there's a name for the argument, make an alias */ if (argnames && argnames[i][0] != '\0') plpgsql_ns_additem(argitemtype, argvariable->dno, argnames[i]); } /* * If there's just one OUT parameter, out_param_varno points * directly to it. If there's more than one, build a row that * holds all of them. */ if (num_out_args == 1) function->out_param_varno = out_arg_variables[0]->dno; else if (num_out_args > 1) { PLpgSQL_row *row = build_row_from_vars(out_arg_variables, num_out_args); plpgsql_adddatum((PLpgSQL_datum *) row); function->out_param_varno = row->rowno; } /* * Check for a polymorphic returntype. If found, use the actual * returntype type from the caller's FuncExpr node, if we have * one. (In validation mode we arbitrarily assume we are dealing * with integers.) * * Note: errcode is FEATURE_NOT_SUPPORTED because it should always * work; if it doesn't we're in some context that fails to make * the info available. */ rettypeid = procStruct->prorettype; if (rettypeid == ANYARRAYOID || rettypeid == ANYELEMENTOID) { if (forValidator) { if (rettypeid == ANYARRAYOID) rettypeid = INT4ARRAYOID; else rettypeid = INT4OID; } else { rettypeid = get_fn_expr_rettype(fcinfo->flinfo); if (!OidIsValid(rettypeid)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("could not determine actual return type " "for polymorphic function \"%s\"", plpgsql_error_funcname))); } } /* * Normal function has a defined returntype */ function->fn_rettype = rettypeid; function->fn_retset = procStruct->proretset; /* * Lookup the function's return type */ typeTup = SearchSysCache(TYPEOID, ObjectIdGetDatum(rettypeid), 0, 0, 0); if (!HeapTupleIsValid(typeTup)) elog(ERROR, "cache lookup failed for type %u", rettypeid); typeStruct = (Form_pg_type) GETSTRUCT(typeTup); /* Disallow pseudotype result, except VOID or RECORD */ /* (note we already replaced ANYARRAY/ANYELEMENT) */ if (typeStruct->typtype == 'p') { if (rettypeid == VOIDOID || rettypeid == RECORDOID) /* okay */ ; else if (rettypeid == TRIGGEROID) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("trigger functions may only be called as triggers"))); else ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("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_rettypioparam = getTypeIOParam(typeTup); fmgr_info(typeStruct->typinput, &(function->fn_retinput)); /* * install $0 reference, but only for polymorphic return * types, and not when the return is specified through an * output parameter. */ if ((procStruct->prorettype == ANYARRAYOID || procStruct->prorettype == ANYELEMENTOID) && num_out_args == 0) { (void) plpgsql_build_variable("$0", 0, build_datatype(typeTup, -1), true); } } ReleaseSysCache(typeTup); break; case T_TRIGGER: /* Trigger procedure's return type is unknown yet */ function->fn_rettype = InvalidOid; function->fn_retbyval = false; function->fn_retistuple = true;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -