2600 lines
71 KiB
C
2600 lines
71 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* pl_comp.c - Compiler part of the PL/pgSQL
|
|
* procedural language
|
|
*
|
|
* Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/pl/plpgsql/src/pl_comp.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include <ctype.h>
|
|
|
|
#include "access/htup_details.h"
|
|
#include "catalog/namespace.h"
|
|
#include "catalog/pg_proc.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "funcapi.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "parser/parse_type.h"
|
|
#include "plpgsql.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/guc.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/regproc.h"
|
|
#include "utils/rel.h"
|
|
#include "utils/syscache.h"
|
|
#include "utils/typcache.h"
|
|
|
|
/* ----------
|
|
* Our own local and global variables
|
|
* ----------
|
|
*/
|
|
PLpgSQL_stmt_block *plpgsql_parse_result;
|
|
|
|
static int datums_alloc;
|
|
int plpgsql_nDatums;
|
|
PLpgSQL_datum **plpgsql_Datums;
|
|
static int datums_last;
|
|
|
|
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 plpgsql_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" /* pgrminclude ignore */
|
|
{NULL, 0}
|
|
};
|
|
|
|
|
|
/* ----------
|
|
* static prototypes
|
|
* ----------
|
|
*/
|
|
static PLpgSQL_function *do_compile(FunctionCallInfo fcinfo,
|
|
HeapTuple procTup,
|
|
PLpgSQL_function *function,
|
|
PLpgSQL_func_hashkey *hashkey,
|
|
bool forValidator);
|
|
static void plpgsql_compile_error_callback(void *arg);
|
|
static void add_parameter_name(PLpgSQL_nsitem_type itemtype, int itemno, const char *name);
|
|
static void add_dummy_return(PLpgSQL_function *function);
|
|
static Node *plpgsql_pre_column_ref(ParseState *pstate, ColumnRef *cref);
|
|
static Node *plpgsql_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var);
|
|
static Node *plpgsql_param_ref(ParseState *pstate, ParamRef *pref);
|
|
static Node *resolve_column_ref(ParseState *pstate, PLpgSQL_expr *expr,
|
|
ColumnRef *cref, bool error_if_no_field);
|
|
static Node *make_datum_param(PLpgSQL_expr *expr, int dno, int location);
|
|
static PLpgSQL_row *build_row_from_vars(PLpgSQL_variable **vars, int numvars);
|
|
static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod,
|
|
Oid collation, TypeName *origtypname);
|
|
static void plpgsql_start_datums(void);
|
|
static void plpgsql_finish_datums(PLpgSQL_function *function);
|
|
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 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
|
|
*/
|
|
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);
|
|
}
|
|
|
|
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().
|
|
*
|
|
* The passed-in "function" pointer is either NULL or an already-allocated
|
|
* function struct to overwrite.
|
|
*
|
|
* 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 plpgsql_compile_tmp_cxt.
|
|
*
|
|
* NB: this code is not re-entrant. We assume that nothing we do here could
|
|
* result in the invocation of another plpgsql function.
|
|
*/
|
|
static PLpgSQL_function *
|
|
do_compile(FunctionCallInfo fcinfo,
|
|
HeapTuple procTup,
|
|
PLpgSQL_function *function,
|
|
PLpgSQL_func_hashkey *hashkey,
|
|
bool forValidator)
|
|
{
|
|
Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
|
|
bool is_dml_trigger = CALLED_AS_TRIGGER(fcinfo);
|
|
bool is_event_trigger = CALLED_AS_EVENT_TRIGGER(fcinfo);
|
|
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 = TextDatumGetCString(prosrcdatum);
|
|
plpgsql_scanner_init(proc_source);
|
|
|
|
plpgsql_error_funcname = pstrdup(NameStr(procStruct->proname));
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/*
|
|
* 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 struct, if not done already. The function
|
|
* structs are never thrown away, so keep them in TopMemoryContext.
|
|
*/
|
|
if (function == NULL)
|
|
{
|
|
function = (PLpgSQL_function *)
|
|
MemoryContextAllocZero(TopMemoryContext, sizeof(PLpgSQL_function));
|
|
}
|
|
else
|
|
{
|
|
/* re-using a previously existing struct, so clear it out */
|
|
memset(function, 0, sizeof(PLpgSQL_function));
|
|
}
|
|
plpgsql_curr_compile = function;
|
|
|
|
/*
|
|
* All the permanent output of compilation (e.g. parse tree) is kept in a
|
|
* per-function memory context, so it can be reclaimed easily.
|
|
*/
|
|
func_cxt = AllocSetContextCreate(TopMemoryContext,
|
|
"PL/pgSQL function",
|
|
ALLOCSET_DEFAULT_SIZES);
|
|
plpgsql_compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);
|
|
|
|
function->fn_signature = format_procedure(fcinfo->flinfo->fn_oid);
|
|
MemoryContextSetIdentifier(func_cxt, function->fn_signature);
|
|
function->fn_oid = fcinfo->flinfo->fn_oid;
|
|
function->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
|
|
function->fn_tid = procTup->t_self;
|
|
function->fn_input_collation = fcinfo->fncollation;
|
|
function->fn_cxt = func_cxt;
|
|
function->out_param_varno = -1; /* set up for no OUT param */
|
|
function->resolve_option = plpgsql_variable_conflict;
|
|
function->print_strict_params = plpgsql_print_strict_params;
|
|
/* only promote extra warnings and errors at CREATE FUNCTION time */
|
|
function->extra_warnings = forValidator ? plpgsql_extra_warnings : 0;
|
|
function->extra_errors = forValidator ? plpgsql_extra_errors : 0;
|
|
|
|
if (is_dml_trigger)
|
|
function->fn_is_trigger = PLPGSQL_DML_TRIGGER;
|
|
else if (is_event_trigger)
|
|
function->fn_is_trigger = PLPGSQL_EVENT_TRIGGER;
|
|
else
|
|
function->fn_is_trigger = PLPGSQL_NOT_TRIGGER;
|
|
|
|
function->fn_prokind = procStruct->prokind;
|
|
|
|
function->nstatements = 0;
|
|
|
|
/*
|
|
* Initialize the compiler, particularly the namespace stack. The
|
|
* outermost namespace contains function parameters and other special
|
|
* variables (such as FOUND), and is named after the function itself.
|
|
*/
|
|
plpgsql_ns_init();
|
|
plpgsql_ns_push(NameStr(procStruct->proname), PLPGSQL_LABEL_BLOCK);
|
|
plpgsql_DumpExecTree = false;
|
|
plpgsql_start_datums();
|
|
|
|
switch (function->fn_is_trigger)
|
|
{
|
|
case PLPGSQL_NOT_TRIGGER:
|
|
|
|
/*
|
|
* 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(plpgsql_compile_tmp_cxt);
|
|
|
|
numargs = get_func_arg_info(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;
|
|
PLpgSQL_nsitem_type argitemtype;
|
|
|
|
/* Create $n name for variable */
|
|
snprintf(buf, sizeof(buf), "$%d", i + 1);
|
|
|
|
/* Create datatype info */
|
|
argdtype = plpgsql_build_datatype(argtypeid,
|
|
-1,
|
|
function->fn_input_collation,
|
|
NULL);
|
|
|
|
/* Disallow pseudotype argument */
|
|
/* (note we already replaced polymorphic types) */
|
|
/* (build_variable would do this, but wrong message) */
|
|
if (argdtype->ttype == PLPGSQL_TTYPE_PSEUDO)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("PL/pgSQL functions cannot accept type %s",
|
|
format_type_be(argtypeid))));
|
|
|
|
/*
|
|
* Build variable and add to datum list. If there's a name
|
|
* for the argument, use that as refname, else use $n name.
|
|
*/
|
|
argvariable = plpgsql_build_variable((argnames &&
|
|
argnames[i][0] != '\0') ?
|
|
argnames[i] : buf,
|
|
0, argdtype, false);
|
|
|
|
if (argvariable->dtype == PLPGSQL_DTYPE_VAR)
|
|
{
|
|
argitemtype = PLPGSQL_NSTYPE_VAR;
|
|
}
|
|
else
|
|
{
|
|
Assert(argvariable->dtype == PLPGSQL_DTYPE_REC);
|
|
argitemtype = PLPGSQL_NSTYPE_REC;
|
|
}
|
|
|
|
/* Remember arguments in appropriate arrays */
|
|
if (argmode == PROARGMODE_IN ||
|
|
argmode == PROARGMODE_INOUT ||
|
|
argmode == PROARGMODE_VARIADIC)
|
|
in_arg_varnos[num_in_args++] = argvariable->dno;
|
|
if (argmode == PROARGMODE_OUT ||
|
|
argmode == PROARGMODE_INOUT ||
|
|
argmode == PROARGMODE_TABLE)
|
|
out_arg_variables[num_out_args++] = argvariable;
|
|
|
|
/* Add to namespace under the $n name */
|
|
add_parameter_name(argitemtype, argvariable->dno, buf);
|
|
|
|
/* If there's a name for the argument, make an alias */
|
|
if (argnames && argnames[i][0] != '\0')
|
|
add_parameter_name(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. Procedures return a row even for one OUT
|
|
* parameter.
|
|
*/
|
|
if (num_out_args > 1 ||
|
|
(num_out_args == 1 && function->fn_prokind == PROKIND_PROCEDURE))
|
|
{
|
|
PLpgSQL_row *row = build_row_from_vars(out_arg_variables,
|
|
num_out_args);
|
|
|
|
plpgsql_adddatum((PLpgSQL_datum *) row);
|
|
function->out_param_varno = row->dno;
|
|
}
|
|
else if (num_out_args == 1)
|
|
function->out_param_varno = out_arg_variables[0]->dno;
|
|
|
|
/*
|
|
* 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 (IsPolymorphicType(rettypeid))
|
|
{
|
|
if (forValidator)
|
|
{
|
|
if (rettypeid == ANYARRAYOID)
|
|
rettypeid = INT4ARRAYOID;
|
|
else if (rettypeid == ANYRANGEOID)
|
|
rettypeid = INT4RANGEOID;
|
|
else /* ANYELEMENT or ANYNONARRAY */
|
|
rettypeid = INT4OID;
|
|
/* XXX what could we use for ANYENUM? */
|
|
}
|
|
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 = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rettypeid));
|
|
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 polymorphic types) */
|
|
if (typeStruct->typtype == TYPTYPE_PSEUDO)
|
|
{
|
|
if (rettypeid == VOIDOID ||
|
|
rettypeid == RECORDOID)
|
|
/* okay */ ;
|
|
else if (rettypeid == TRIGGEROID || rettypeid == EVTTRIGGEROID)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("trigger functions can only be called as triggers")));
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("PL/pgSQL functions cannot return type %s",
|
|
format_type_be(rettypeid))));
|
|
}
|
|
|
|
function->fn_retistuple = type_is_rowtype(rettypeid);
|
|
function->fn_retisdomain = (typeStruct->typtype == TYPTYPE_DOMAIN);
|
|
function->fn_retbyval = typeStruct->typbyval;
|
|
function->fn_rettyplen = typeStruct->typlen;
|
|
|
|
/*
|
|
* install $0 reference, but only for polymorphic return types,
|
|
* and not when the return is specified through an output
|
|
* parameter.
|
|
*/
|
|
if (IsPolymorphicType(procStruct->prorettype) &&
|
|
num_out_args == 0)
|
|
{
|
|
(void) plpgsql_build_variable("$0", 0,
|
|
build_datatype(typeTup,
|
|
-1,
|
|
function->fn_input_collation,
|
|
NULL),
|
|
true);
|
|
}
|
|
|
|
ReleaseSysCache(typeTup);
|
|
break;
|
|
|
|
case PLPGSQL_DML_TRIGGER:
|
|
/* Trigger procedure's return type is unknown yet */
|
|
function->fn_rettype = InvalidOid;
|
|
function->fn_retbyval = false;
|
|
function->fn_retistuple = true;
|
|
function->fn_retisdomain = false;
|
|
function->fn_retset = false;
|
|
|
|
/* shouldn't be any declared arguments */
|
|
if (procStruct->pronargs != 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
|
errmsg("trigger functions cannot have declared arguments"),
|
|
errhint("The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV instead.")));
|
|
|
|
/* Add the record for referencing NEW ROW */
|
|
rec = plpgsql_build_record("new", 0, NULL, RECORDOID, true);
|
|
function->new_varno = rec->dno;
|
|
|
|
/* Add the record for referencing OLD ROW */
|
|
rec = plpgsql_build_record("old", 0, NULL, RECORDOID, true);
|
|
function->old_varno = rec->dno;
|
|
|
|
/* Add the variable tg_name */
|
|
var = plpgsql_build_variable("tg_name", 0,
|
|
plpgsql_build_datatype(NAMEOID,
|
|
-1,
|
|
function->fn_input_collation,
|
|
NULL),
|
|
true);
|
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
|
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_NAME;
|
|
|
|
/* Add the variable tg_when */
|
|
var = plpgsql_build_variable("tg_when", 0,
|
|
plpgsql_build_datatype(TEXTOID,
|
|
-1,
|
|
function->fn_input_collation,
|
|
NULL),
|
|
true);
|
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
|
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_WHEN;
|
|
|
|
/* Add the variable tg_level */
|
|
var = plpgsql_build_variable("tg_level", 0,
|
|
plpgsql_build_datatype(TEXTOID,
|
|
-1,
|
|
function->fn_input_collation,
|
|
NULL),
|
|
true);
|
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
|
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_LEVEL;
|
|
|
|
/* Add the variable tg_op */
|
|
var = plpgsql_build_variable("tg_op", 0,
|
|
plpgsql_build_datatype(TEXTOID,
|
|
-1,
|
|
function->fn_input_collation,
|
|
NULL),
|
|
true);
|
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
|
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_OP;
|
|
|
|
/* Add the variable tg_relid */
|
|
var = plpgsql_build_variable("tg_relid", 0,
|
|
plpgsql_build_datatype(OIDOID,
|
|
-1,
|
|
InvalidOid,
|
|
NULL),
|
|
true);
|
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
|
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_RELID;
|
|
|
|
/* Add the variable tg_relname */
|
|
var = plpgsql_build_variable("tg_relname", 0,
|
|
plpgsql_build_datatype(NAMEOID,
|
|
-1,
|
|
function->fn_input_collation,
|
|
NULL),
|
|
true);
|
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
|
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TABLE_NAME;
|
|
|
|
/* tg_table_name is now preferred to tg_relname */
|
|
var = plpgsql_build_variable("tg_table_name", 0,
|
|
plpgsql_build_datatype(NAMEOID,
|
|
-1,
|
|
function->fn_input_collation,
|
|
NULL),
|
|
true);
|
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
|
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TABLE_NAME;
|
|
|
|
/* add the variable tg_table_schema */
|
|
var = plpgsql_build_variable("tg_table_schema", 0,
|
|
plpgsql_build_datatype(NAMEOID,
|
|
-1,
|
|
function->fn_input_collation,
|
|
NULL),
|
|
true);
|
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
|
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TABLE_SCHEMA;
|
|
|
|
/* Add the variable tg_nargs */
|
|
var = plpgsql_build_variable("tg_nargs", 0,
|
|
plpgsql_build_datatype(INT4OID,
|
|
-1,
|
|
InvalidOid,
|
|
NULL),
|
|
true);
|
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
|
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_NARGS;
|
|
|
|
/* Add the variable tg_argv */
|
|
var = plpgsql_build_variable("tg_argv", 0,
|
|
plpgsql_build_datatype(TEXTARRAYOID,
|
|
-1,
|
|
function->fn_input_collation,
|
|
NULL),
|
|
true);
|
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
|
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_ARGV;
|
|
|
|
break;
|
|
|
|
case PLPGSQL_EVENT_TRIGGER:
|
|
function->fn_rettype = VOIDOID;
|
|
function->fn_retbyval = false;
|
|
function->fn_retistuple = true;
|
|
function->fn_retisdomain = false;
|
|
function->fn_retset = false;
|
|
|
|
/* shouldn't be any declared arguments */
|
|
if (procStruct->pronargs != 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
|
errmsg("event trigger functions cannot have declared arguments")));
|
|
|
|
/* Add the variable tg_event */
|
|
var = plpgsql_build_variable("tg_event", 0,
|
|
plpgsql_build_datatype(TEXTOID,
|
|
-1,
|
|
function->fn_input_collation,
|
|
NULL),
|
|
true);
|
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
|
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_EVENT;
|
|
|
|
/* Add the variable tg_tag */
|
|
var = plpgsql_build_variable("tg_tag", 0,
|
|
plpgsql_build_datatype(TEXTOID,
|
|
-1,
|
|
function->fn_input_collation,
|
|
NULL),
|
|
true);
|
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
|
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TAG;
|
|
|
|
break;
|
|
|
|
default:
|
|
elog(ERROR, "unrecognized function typecode: %d",
|
|
(int) function->fn_is_trigger);
|
|
break;
|
|
}
|
|
|
|
/* Remember if function is STABLE/IMMUTABLE */
|
|
function->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
|
|
|
|
/*
|
|
* Create the magic FOUND variable.
|
|
*/
|
|
var = plpgsql_build_variable("found", 0,
|
|
plpgsql_build_datatype(BOOLOID,
|
|
-1,
|
|
InvalidOid,
|
|
NULL),
|
|
true);
|
|
function->found_varno = var->dno;
|
|
|
|
/*
|
|
* Now parse the function's text
|
|
*/
|
|
parse_rc = plpgsql_yyparse();
|
|
if (parse_rc != 0)
|
|
elog(ERROR, "plpgsql parser returned %d", parse_rc);
|
|
function->action = plpgsql_parse_result;
|
|
|
|
plpgsql_scanner_finish();
|
|
pfree(proc_source);
|
|
|
|
/*
|
|
* If it has OUT parameters or returns VOID or returns a set, we allow
|
|
* control to fall off the end without an explicit RETURN statement. The
|
|
* easiest way to implement this is to add a RETURN statement to the end
|
|
* of the statement list during parsing.
|
|
*/
|
|
if (num_out_args > 0 || function->fn_rettype == VOIDOID ||
|
|
function->fn_retset)
|
|
add_dummy_return(function);
|
|
|
|
/*
|
|
* Complete the function's info
|
|
*/
|
|
function->fn_nargs = procStruct->pronargs;
|
|
for (i = 0; i < function->fn_nargs; i++)
|
|
function->fn_argvarnos[i] = in_arg_varnos[i];
|
|
|
|
plpgsql_finish_datums(function);
|
|
|
|
/* Debug dump for completed functions */
|
|
if (plpgsql_DumpExecTree)
|
|
plpgsql_dumptree(function);
|
|
|
|
/*
|
|
* add it to the hash table
|
|
*/
|
|
plpgsql_HashTableInsert(function, hashkey);
|
|
|
|
/*
|
|
* Pop the error context stack
|
|
*/
|
|
error_context_stack = plerrcontext.previous;
|
|
plpgsql_error_funcname = NULL;
|
|
|
|
plpgsql_check_syntax = false;
|
|
|
|
MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
|
|
plpgsql_compile_tmp_cxt = NULL;
|
|
return function;
|
|
}
|
|
|
|
/* ----------
|
|
* plpgsql_compile_inline Make an execution tree for an anonymous code block.
|
|
*
|
|
* Note: this is generally parallel to do_compile(); is it worth trying to
|
|
* merge the two?
|
|
*
|
|
* Note: we assume the block will be thrown away so there is no need to build
|
|
* persistent data structures.
|
|
* ----------
|
|
*/
|
|
PLpgSQL_function *
|
|
plpgsql_compile_inline(char *proc_source)
|
|
{
|
|
char *func_name = "inline_code_block";
|
|
PLpgSQL_function *function;
|
|
ErrorContextCallback plerrcontext;
|
|
PLpgSQL_variable *var;
|
|
int parse_rc;
|
|
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.
|
|
*/
|
|
plpgsql_scanner_init(proc_source);
|
|
|
|
plpgsql_error_funcname = func_name;
|
|
|
|
/*
|
|
* Setup error traceback support for ereport()
|
|
*/
|
|
plerrcontext.callback = plpgsql_compile_error_callback;
|
|
plerrcontext.arg = proc_source;
|
|
plerrcontext.previous = error_context_stack;
|
|
error_context_stack = &plerrcontext;
|
|
|
|
/* Do extra syntax checking if check_function_bodies is on */
|
|
plpgsql_check_syntax = check_function_bodies;
|
|
|
|
/* Function struct does not live past current statement */
|
|
function = (PLpgSQL_function *) palloc0(sizeof(PLpgSQL_function));
|
|
|
|
plpgsql_curr_compile = function;
|
|
|
|
/*
|
|
* All the rest of the compile-time storage (e.g. parse tree) is kept in
|
|
* its own memory context, so it can be reclaimed easily.
|
|
*/
|
|
func_cxt = AllocSetContextCreate(CurrentMemoryContext,
|
|
"PL/pgSQL inline code context",
|
|
ALLOCSET_DEFAULT_SIZES);
|
|
plpgsql_compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);
|
|
|
|
function->fn_signature = pstrdup(func_name);
|
|
function->fn_is_trigger = PLPGSQL_NOT_TRIGGER;
|
|
function->fn_input_collation = InvalidOid;
|
|
function->fn_cxt = func_cxt;
|
|
function->out_param_varno = -1; /* set up for no OUT param */
|
|
function->resolve_option = plpgsql_variable_conflict;
|
|
function->print_strict_params = plpgsql_print_strict_params;
|
|
|
|
/*
|
|
* don't do extra validation for inline code as we don't want to add spam
|
|
* at runtime
|
|
*/
|
|
function->extra_warnings = 0;
|
|
function->extra_errors = 0;
|
|
|
|
function->nstatements = 0;
|
|
|
|
plpgsql_ns_init();
|
|
plpgsql_ns_push(func_name, PLPGSQL_LABEL_BLOCK);
|
|
plpgsql_DumpExecTree = false;
|
|
plpgsql_start_datums();
|
|
|
|
/* Set up as though in a function returning VOID */
|
|
function->fn_rettype = VOIDOID;
|
|
function->fn_retset = false;
|
|
function->fn_retistuple = false;
|
|
function->fn_retisdomain = false;
|
|
function->fn_prokind = PROKIND_FUNCTION;
|
|
/* a bit of hardwired knowledge about type VOID here */
|
|
function->fn_retbyval = true;
|
|
function->fn_rettyplen = sizeof(int32);
|
|
|
|
/*
|
|
* Remember if function is STABLE/IMMUTABLE. XXX would it be better to
|
|
* set this true inside a read-only transaction? Not clear.
|
|
*/
|
|
function->fn_readonly = false;
|
|
|
|
/*
|
|
* Create the magic FOUND variable.
|
|
*/
|
|
var = plpgsql_build_variable("found", 0,
|
|
plpgsql_build_datatype(BOOLOID,
|
|
-1,
|
|
InvalidOid,
|
|
NULL),
|
|
true);
|
|
function->found_varno = var->dno;
|
|
|
|
/*
|
|
* Now parse the function's text
|
|
*/
|
|
parse_rc = plpgsql_yyparse();
|
|
if (parse_rc != 0)
|
|
elog(ERROR, "plpgsql parser returned %d", parse_rc);
|
|
function->action = plpgsql_parse_result;
|
|
|
|
plpgsql_scanner_finish();
|
|
|
|
/*
|
|
* If it returns VOID (always true at the moment), we allow control to
|
|
* fall off the end without an explicit RETURN statement.
|
|
*/
|
|
if (function->fn_rettype == VOIDOID)
|
|
add_dummy_return(function);
|
|
|
|
/*
|
|
* Complete the function's info
|
|
*/
|
|
function->fn_nargs = 0;
|
|
|
|
plpgsql_finish_datums(function);
|
|
|
|
/*
|
|
* Pop the error context stack
|
|
*/
|
|
error_context_stack = plerrcontext.previous;
|
|
plpgsql_error_funcname = NULL;
|
|
|
|
plpgsql_check_syntax = false;
|
|
|
|
MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
|
|
plpgsql_compile_tmp_cxt = NULL;
|
|
return function;
|
|
}
|
|
|
|
|
|
/*
|
|
* error context callback to let us supply a call-stack traceback.
|
|
* If we are validating or executing an anonymous code block, the function
|
|
* source text is passed as an argument.
|
|
*/
|
|
static void
|
|
plpgsql_compile_error_callback(void *arg)
|
|
{
|
|
if (arg)
|
|
{
|
|
/*
|
|
* Try to convert syntax error position to reference text of original
|
|
* CREATE FUNCTION or DO command.
|
|
*/
|
|
if (function_parse_error_transpose((const char *) arg))
|
|
return;
|
|
|
|
/*
|
|
* Done if a syntax error position was reported; otherwise we have to
|
|
* fall back to a "near line N" report.
|
|
*/
|
|
}
|
|
|
|
if (plpgsql_error_funcname)
|
|
errcontext("compilation of PL/pgSQL function \"%s\" near line %d",
|
|
plpgsql_error_funcname, plpgsql_latest_lineno());
|
|
}
|
|
|
|
|
|
/*
|
|
* Add a name for a function parameter to the function's namespace
|
|
*/
|
|
static void
|
|
add_parameter_name(PLpgSQL_nsitem_type itemtype, int itemno, const char *name)
|
|
{
|
|
/*
|
|
* Before adding the name, check for duplicates. We need this even though
|
|
* functioncmds.c has a similar check, because that code explicitly
|
|
* doesn't complain about conflicting IN and OUT parameter names. In
|
|
* plpgsql, such names are in the same namespace, so there is no way to
|
|
* disambiguate.
|
|
*/
|
|
if (plpgsql_ns_lookup(plpgsql_ns_top(), true,
|
|
name, NULL, NULL,
|
|
NULL) != NULL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
|
errmsg("parameter name \"%s\" used more than once",
|
|
name)));
|
|
|
|
/* OK, add the name */
|
|
plpgsql_ns_additem(itemtype, itemno, name);
|
|
}
|
|
|
|
/*
|
|
* Add a dummy RETURN statement to the given function's body
|
|
*/
|
|
static void
|
|
add_dummy_return(PLpgSQL_function *function)
|
|
{
|
|
/*
|
|
* If the outer block has an EXCEPTION clause, we need to make a new outer
|
|
* block, since the added RETURN shouldn't act like it is inside the
|
|
* EXCEPTION clause.
|
|
*/
|
|
if (function->action->exceptions != NULL)
|
|
{
|
|
PLpgSQL_stmt_block *new;
|
|
|
|
new = palloc0(sizeof(PLpgSQL_stmt_block));
|
|
new->cmd_type = PLPGSQL_STMT_BLOCK;
|
|
new->stmtid = ++function->nstatements;
|
|
new->body = list_make1(function->action);
|
|
|
|
function->action = new;
|
|
}
|
|
if (function->action->body == NIL ||
|
|
((PLpgSQL_stmt *) llast(function->action->body))->cmd_type != PLPGSQL_STMT_RETURN)
|
|
{
|
|
PLpgSQL_stmt_return *new;
|
|
|
|
new = palloc0(sizeof(PLpgSQL_stmt_return));
|
|
new->cmd_type = PLPGSQL_STMT_RETURN;
|
|
new->stmtid = ++function->nstatements;
|
|
new->expr = NULL;
|
|
new->retvarno = function->out_param_varno;
|
|
|
|
function->action->body = lappend(function->action->body, new);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* plpgsql_parser_setup set up parser hooks for dynamic parameters
|
|
*
|
|
* Note: this routine, and the hook functions it prepares for, are logically
|
|
* part of plpgsql parsing. But they actually run during function execution,
|
|
* when we are ready to evaluate a SQL query or expression that has not
|
|
* previously been parsed and planned.
|
|
*/
|
|
void
|
|
plpgsql_parser_setup(struct ParseState *pstate, PLpgSQL_expr *expr)
|
|
{
|
|
pstate->p_pre_columnref_hook = plpgsql_pre_column_ref;
|
|
pstate->p_post_columnref_hook = plpgsql_post_column_ref;
|
|
pstate->p_paramref_hook = plpgsql_param_ref;
|
|
/* no need to use p_coerce_param_hook */
|
|
pstate->p_ref_hook_state = (void *) expr;
|
|
}
|
|
|
|
/*
|
|
* plpgsql_pre_column_ref parser callback before parsing a ColumnRef
|
|
*/
|
|
static Node *
|
|
plpgsql_pre_column_ref(ParseState *pstate, ColumnRef *cref)
|
|
{
|
|
PLpgSQL_expr *expr = (PLpgSQL_expr *) pstate->p_ref_hook_state;
|
|
|
|
if (expr->func->resolve_option == PLPGSQL_RESOLVE_VARIABLE)
|
|
return resolve_column_ref(pstate, expr, cref, false);
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* plpgsql_post_column_ref parser callback after parsing a ColumnRef
|
|
*/
|
|
static Node *
|
|
plpgsql_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
|
|
{
|
|
PLpgSQL_expr *expr = (PLpgSQL_expr *) pstate->p_ref_hook_state;
|
|
Node *myvar;
|
|
|
|
if (expr->func->resolve_option == PLPGSQL_RESOLVE_VARIABLE)
|
|
return NULL; /* we already found there's no match */
|
|
|
|
if (expr->func->resolve_option == PLPGSQL_RESOLVE_COLUMN && var != NULL)
|
|
return NULL; /* there's a table column, prefer that */
|
|
|
|
/*
|
|
* If we find a record/row variable but can't match a field name, throw
|
|
* error if there was no core resolution for the ColumnRef either. In
|
|
* that situation, the reference is inevitably going to fail, and
|
|
* complaining about the record/row variable is likely to be more on-point
|
|
* than the core parser's error message. (It's too bad we don't have
|
|
* access to transformColumnRef's internal crerr state here, as in case of
|
|
* a conflict with a table name this could still be less than the most
|
|
* helpful error message possible.)
|
|
*/
|
|
myvar = resolve_column_ref(pstate, expr, cref, (var == NULL));
|
|
|
|
if (myvar != NULL && var != NULL)
|
|
{
|
|
/*
|
|
* We could leave it to the core parser to throw this error, but we
|
|
* can add a more useful detail message than the core could.
|
|
*/
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_AMBIGUOUS_COLUMN),
|
|
errmsg("column reference \"%s\" is ambiguous",
|
|
NameListToString(cref->fields)),
|
|
errdetail("It could refer to either a PL/pgSQL variable or a table column."),
|
|
parser_errposition(pstate, cref->location)));
|
|
}
|
|
|
|
return myvar;
|
|
}
|
|
|
|
/*
|
|
* plpgsql_param_ref parser callback for ParamRefs ($n symbols)
|
|
*/
|
|
static Node *
|
|
plpgsql_param_ref(ParseState *pstate, ParamRef *pref)
|
|
{
|
|
PLpgSQL_expr *expr = (PLpgSQL_expr *) pstate->p_ref_hook_state;
|
|
char pname[32];
|
|
PLpgSQL_nsitem *nse;
|
|
|
|
snprintf(pname, sizeof(pname), "$%d", pref->number);
|
|
|
|
nse = plpgsql_ns_lookup(expr->ns, false,
|
|
pname, NULL, NULL,
|
|
NULL);
|
|
|
|
if (nse == NULL)
|
|
return NULL; /* name not known to plpgsql */
|
|
|
|
return make_datum_param(expr, nse->itemno, pref->location);
|
|
}
|
|
|
|
/*
|
|
* resolve_column_ref attempt to resolve a ColumnRef as a plpgsql var
|
|
*
|
|
* Returns the translated node structure, or NULL if name not found
|
|
*
|
|
* error_if_no_field tells whether to throw error or quietly return NULL if
|
|
* we are able to match a record/row name but don't find a field name match.
|
|
*/
|
|
static Node *
|
|
resolve_column_ref(ParseState *pstate, PLpgSQL_expr *expr,
|
|
ColumnRef *cref, bool error_if_no_field)
|
|
{
|
|
PLpgSQL_execstate *estate;
|
|
PLpgSQL_nsitem *nse;
|
|
const char *name1;
|
|
const char *name2 = NULL;
|
|
const char *name3 = NULL;
|
|
const char *colname = NULL;
|
|
int nnames;
|
|
int nnames_scalar = 0;
|
|
int nnames_wholerow = 0;
|
|
int nnames_field = 0;
|
|
|
|
/*
|
|
* We use the function's current estate to resolve parameter data types.
|
|
* This is really pretty bogus because there is no provision for updating
|
|
* plans when those types change ...
|
|
*/
|
|
estate = expr->func->cur_estate;
|
|
|
|
/*----------
|
|
* The allowed syntaxes are:
|
|
*
|
|
* A Scalar variable reference, or whole-row record reference.
|
|
* A.B Qualified scalar or whole-row reference, or field reference.
|
|
* A.B.C Qualified record field reference.
|
|
* A.* Whole-row record reference.
|
|
* A.B.* Qualified whole-row record reference.
|
|
*----------
|
|
*/
|
|
switch (list_length(cref->fields))
|
|
{
|
|
case 1:
|
|
{
|
|
Node *field1 = (Node *) linitial(cref->fields);
|
|
|
|
Assert(IsA(field1, String));
|
|
name1 = strVal(field1);
|
|
nnames_scalar = 1;
|
|
nnames_wholerow = 1;
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
Node *field1 = (Node *) linitial(cref->fields);
|
|
Node *field2 = (Node *) lsecond(cref->fields);
|
|
|
|
Assert(IsA(field1, String));
|
|
name1 = strVal(field1);
|
|
|
|
/* Whole-row reference? */
|
|
if (IsA(field2, A_Star))
|
|
{
|
|
/* Set name2 to prevent matches to scalar variables */
|
|
name2 = "*";
|
|
nnames_wholerow = 1;
|
|
break;
|
|
}
|
|
|
|
Assert(IsA(field2, String));
|
|
name2 = strVal(field2);
|
|
colname = name2;
|
|
nnames_scalar = 2;
|
|
nnames_wholerow = 2;
|
|
nnames_field = 1;
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
Node *field1 = (Node *) linitial(cref->fields);
|
|
Node *field2 = (Node *) lsecond(cref->fields);
|
|
Node *field3 = (Node *) lthird(cref->fields);
|
|
|
|
Assert(IsA(field1, String));
|
|
name1 = strVal(field1);
|
|
Assert(IsA(field2, String));
|
|
name2 = strVal(field2);
|
|
|
|
/* Whole-row reference? */
|
|
if (IsA(field3, A_Star))
|
|
{
|
|
/* Set name3 to prevent matches to scalar variables */
|
|
name3 = "*";
|
|
nnames_wholerow = 2;
|
|
break;
|
|
}
|
|
|
|
Assert(IsA(field3, String));
|
|
name3 = strVal(field3);
|
|
colname = name3;
|
|
nnames_field = 2;
|
|
break;
|
|
}
|
|
default:
|
|
/* too many names, ignore */
|
|
return NULL;
|
|
}
|
|
|
|
nse = plpgsql_ns_lookup(expr->ns, false,
|
|
name1, name2, name3,
|
|
&nnames);
|
|
|
|
if (nse == NULL)
|
|
return NULL; /* name not known to plpgsql */
|
|
|
|
switch (nse->itemtype)
|
|
{
|
|
case PLPGSQL_NSTYPE_VAR:
|
|
if (nnames == nnames_scalar)
|
|
return make_datum_param(expr, nse->itemno, cref->location);
|
|
break;
|
|
case PLPGSQL_NSTYPE_REC:
|
|
if (nnames == nnames_wholerow)
|
|
return make_datum_param(expr, nse->itemno, cref->location);
|
|
if (nnames == nnames_field)
|
|
{
|
|
/* colname could be a field in this record */
|
|
PLpgSQL_rec *rec = (PLpgSQL_rec *) estate->datums[nse->itemno];
|
|
int i;
|
|
|
|
/* search for a datum referencing this field */
|
|
i = rec->firstfield;
|
|
while (i >= 0)
|
|
{
|
|
PLpgSQL_recfield *fld = (PLpgSQL_recfield *) estate->datums[i];
|
|
|
|
Assert(fld->dtype == PLPGSQL_DTYPE_RECFIELD &&
|
|
fld->recparentno == nse->itemno);
|
|
if (strcmp(fld->fieldname, colname) == 0)
|
|
{
|
|
return make_datum_param(expr, i, cref->location);
|
|
}
|
|
i = fld->nextfield;
|
|
}
|
|
|
|
/*
|
|
* We should not get here, because a RECFIELD datum should
|
|
* have been built at parse time for every possible qualified
|
|
* reference to fields of this record. But if we do, handle
|
|
* it like field-not-found: throw error or return NULL.
|
|
*/
|
|
if (error_if_no_field)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
|
errmsg("record \"%s\" has no field \"%s\"",
|
|
(nnames_field == 1) ? name1 : name2,
|
|
colname),
|
|
parser_errposition(pstate, cref->location)));
|
|
}
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized plpgsql itemtype: %d", nse->itemtype);
|
|
}
|
|
|
|
/* Name format doesn't match the plpgsql variable type */
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Helper for columnref parsing: build a Param referencing a plpgsql datum,
|
|
* and make sure that that datum is listed in the expression's paramnos.
|
|
*/
|
|
static Node *
|
|
make_datum_param(PLpgSQL_expr *expr, int dno, int location)
|
|
{
|
|
PLpgSQL_execstate *estate;
|
|
PLpgSQL_datum *datum;
|
|
Param *param;
|
|
MemoryContext oldcontext;
|
|
|
|
/* see comment in resolve_column_ref */
|
|
estate = expr->func->cur_estate;
|
|
Assert(dno >= 0 && dno < estate->ndatums);
|
|
datum = estate->datums[dno];
|
|
|
|
/*
|
|
* Bitmapset must be allocated in function's permanent memory context
|
|
*/
|
|
oldcontext = MemoryContextSwitchTo(expr->func->fn_cxt);
|
|
expr->paramnos = bms_add_member(expr->paramnos, dno);
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
param = makeNode(Param);
|
|
param->paramkind = PARAM_EXTERN;
|
|
param->paramid = dno + 1;
|
|
plpgsql_exec_get_datum_type_info(estate,
|
|
datum,
|
|
¶m->paramtype,
|
|
¶m->paramtypmod,
|
|
¶m->paramcollid);
|
|
param->location = location;
|
|
|
|
return (Node *) param;
|
|
}
|
|
|
|
|
|
/* ----------
|
|
* plpgsql_parse_word The scanner calls this to postparse
|
|
* any single word that is not a reserved keyword.
|
|
*
|
|
* word1 is the downcased/dequoted identifier; it must be palloc'd in the
|
|
* function's long-term memory context.
|
|
*
|
|
* yytxt is the original token text; we need this to check for quoting,
|
|
* so that later checks for unreserved keywords work properly.
|
|
*
|
|
* We attempt to recognize the token as a variable only if lookup is true
|
|
* and the plpgsql_IdentifierLookup context permits it.
|
|
*
|
|
* If recognized as a variable, fill in *wdatum and return true;
|
|
* if not recognized, fill in *word and return false.
|
|
* (Note: those two pointers actually point to members of the same union,
|
|
* but for notational reasons we pass them separately.)
|
|
* ----------
|
|
*/
|
|
bool
|
|
plpgsql_parse_word(char *word1, const char *yytxt, bool lookup,
|
|
PLwdatum *wdatum, PLword *word)
|
|
{
|
|
PLpgSQL_nsitem *ns;
|
|
|
|
/*
|
|
* We should not lookup variables in DECLARE sections. In SQL
|
|
* expressions, there's no need to do so either --- lookup will happen
|
|
* when the expression is compiled.
|
|
*/
|
|
if (lookup && plpgsql_IdentifierLookup == IDENTIFIER_LOOKUP_NORMAL)
|
|
{
|
|
/*
|
|
* Do a lookup in the current namespace stack
|
|
*/
|
|
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
|
|
word1, NULL, NULL,
|
|
NULL);
|
|
|
|
if (ns != NULL)
|
|
{
|
|
switch (ns->itemtype)
|
|
{
|
|
case PLPGSQL_NSTYPE_VAR:
|
|
case PLPGSQL_NSTYPE_REC:
|
|
wdatum->datum = plpgsql_Datums[ns->itemno];
|
|
wdatum->ident = word1;
|
|
wdatum->quoted = (yytxt[0] == '"');
|
|
wdatum->idents = NIL;
|
|
return true;
|
|
|
|
default:
|
|
/* plpgsql_ns_lookup should never return anything else */
|
|
elog(ERROR, "unrecognized plpgsql itemtype: %d",
|
|
ns->itemtype);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Nothing found - up to now it's a word without any special meaning for
|
|
* us.
|
|
*/
|
|
word->ident = word1;
|
|
word->quoted = (yytxt[0] == '"');
|
|
return false;
|
|
}
|
|
|
|
|
|
/* ----------
|
|
* plpgsql_parse_dblword Same lookup for two words
|
|
* separated by a dot.
|
|
* ----------
|
|
*/
|
|
bool
|
|
plpgsql_parse_dblword(char *word1, char *word2,
|
|
PLwdatum *wdatum, PLcword *cword)
|
|
{
|
|
PLpgSQL_nsitem *ns;
|
|
List *idents;
|
|
int nnames;
|
|
|
|
idents = list_make2(makeString(word1),
|
|
makeString(word2));
|
|
|
|
/*
|
|
* We should do nothing in DECLARE sections. In SQL expressions, we
|
|
* really only need to make sure that RECFIELD datums are created when
|
|
* needed.
|
|
*/
|
|
if (plpgsql_IdentifierLookup != IDENTIFIER_LOOKUP_DECLARE)
|
|
{
|
|
/*
|
|
* Do a lookup in the current namespace stack
|
|
*/
|
|
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
|
|
word1, word2, NULL,
|
|
&nnames);
|
|
if (ns != NULL)
|
|
{
|
|
switch (ns->itemtype)
|
|
{
|
|
case PLPGSQL_NSTYPE_VAR:
|
|
/* Block-qualified reference to scalar variable. */
|
|
wdatum->datum = plpgsql_Datums[ns->itemno];
|
|
wdatum->ident = NULL;
|
|
wdatum->quoted = false; /* not used */
|
|
wdatum->idents = idents;
|
|
return true;
|
|
|
|
case PLPGSQL_NSTYPE_REC:
|
|
if (nnames == 1)
|
|
{
|
|
/*
|
|
* First word is a record name, so second word could
|
|
* be a field in this record. We build a RECFIELD
|
|
* datum whether it is or not --- any error will be
|
|
* detected later.
|
|
*/
|
|
PLpgSQL_rec *rec;
|
|
PLpgSQL_recfield *new;
|
|
|
|
rec = (PLpgSQL_rec *) (plpgsql_Datums[ns->itemno]);
|
|
new = plpgsql_build_recfield(rec, word2);
|
|
|
|
wdatum->datum = (PLpgSQL_datum *) new;
|
|
}
|
|
else
|
|
{
|
|
/* Block-qualified reference to record variable. */
|
|
wdatum->datum = plpgsql_Datums[ns->itemno];
|
|
}
|
|
wdatum->ident = NULL;
|
|
wdatum->quoted = false; /* not used */
|
|
wdatum->idents = idents;
|
|
return true;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Nothing found */
|
|
cword->idents = idents;
|
|
return false;
|
|
}
|
|
|
|
|
|
/* ----------
|
|
* plpgsql_parse_tripword Same lookup for three words
|
|
* separated by dots.
|
|
* ----------
|
|
*/
|
|
bool
|
|
plpgsql_parse_tripword(char *word1, char *word2, char *word3,
|
|
PLwdatum *wdatum, PLcword *cword)
|
|
{
|
|
PLpgSQL_nsitem *ns;
|
|
List *idents;
|
|
int nnames;
|
|
|
|
idents = list_make3(makeString(word1),
|
|
makeString(word2),
|
|
makeString(word3));
|
|
|
|
/*
|
|
* We should do nothing in DECLARE sections. In SQL expressions, we
|
|
* really only need to make sure that RECFIELD datums are created when
|
|
* needed.
|
|
*/
|
|
if (plpgsql_IdentifierLookup != IDENTIFIER_LOOKUP_DECLARE)
|
|
{
|
|
/*
|
|
* Do a lookup in the current namespace stack. Must find a qualified
|
|
* reference, else ignore.
|
|
*/
|
|
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
|
|
word1, word2, word3,
|
|
&nnames);
|
|
if (ns != NULL && nnames == 2)
|
|
{
|
|
switch (ns->itemtype)
|
|
{
|
|
case PLPGSQL_NSTYPE_REC:
|
|
{
|
|
/*
|
|
* words 1/2 are a record name, so third word could be
|
|
* a field in this record.
|
|
*/
|
|
PLpgSQL_rec *rec;
|
|
PLpgSQL_recfield *new;
|
|
|
|
rec = (PLpgSQL_rec *) (plpgsql_Datums[ns->itemno]);
|
|
new = plpgsql_build_recfield(rec, word3);
|
|
|
|
wdatum->datum = (PLpgSQL_datum *) new;
|
|
wdatum->ident = NULL;
|
|
wdatum->quoted = false; /* not used */
|
|
wdatum->idents = idents;
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Nothing found */
|
|
cword->idents = idents;
|
|
return false;
|
|
}
|
|
|
|
|
|
/* ----------
|
|
* plpgsql_parse_wordtype The scanner found word%TYPE. word can be
|
|
* a variable name or a basetype.
|
|
*
|
|
* Returns datatype struct, or NULL if no match found for word.
|
|
* ----------
|
|
*/
|
|
PLpgSQL_type *
|
|
plpgsql_parse_wordtype(char *ident)
|
|
{
|
|
PLpgSQL_type *dtype;
|
|
PLpgSQL_nsitem *nse;
|
|
TypeName *typeName;
|
|
HeapTuple typeTup;
|
|
|
|
/*
|
|
* Do a lookup in the current namespace stack
|
|
*/
|
|
nse = plpgsql_ns_lookup(plpgsql_ns_top(), false,
|
|
ident, NULL, NULL,
|
|
NULL);
|
|
|
|
if (nse != NULL)
|
|
{
|
|
switch (nse->itemtype)
|
|
{
|
|
case PLPGSQL_NSTYPE_VAR:
|
|
return ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype;
|
|
|
|
/* XXX perhaps allow REC/ROW here? */
|
|
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Word wasn't found in the namespace stack. Try to find a data type with
|
|
* that name, but ignore shell types and complex types.
|
|
*/
|
|
typeName = makeTypeName(ident);
|
|
typeTup = LookupTypeName(NULL, typeName, NULL, false);
|
|
if (typeTup)
|
|
{
|
|
Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
|
|
|
|
if (!typeStruct->typisdefined ||
|
|
typeStruct->typrelid != InvalidOid)
|
|
{
|
|
ReleaseSysCache(typeTup);
|
|
return NULL;
|
|
}
|
|
|
|
dtype = build_datatype(typeTup, -1,
|
|
plpgsql_curr_compile->fn_input_collation,
|
|
typeName);
|
|
|
|
ReleaseSysCache(typeTup);
|
|
return dtype;
|
|
}
|
|
|
|
/*
|
|
* Nothing found - up to now it's a word without any special meaning for
|
|
* us.
|
|
*/
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* ----------
|
|
* plpgsql_parse_cwordtype Same lookup for compositeword%TYPE
|
|
* ----------
|
|
*/
|
|
PLpgSQL_type *
|
|
plpgsql_parse_cwordtype(List *idents)
|
|
{
|
|
PLpgSQL_type *dtype = NULL;
|
|
PLpgSQL_nsitem *nse;
|
|
const char *fldname;
|
|
Oid classOid;
|
|
HeapTuple classtup = NULL;
|
|
HeapTuple attrtup = NULL;
|
|
HeapTuple typetup = NULL;
|
|
Form_pg_class classStruct;
|
|
Form_pg_attribute attrStruct;
|
|
MemoryContext oldCxt;
|
|
|
|
/* Avoid memory leaks in the long-term function context */
|
|
oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
|
|
|
|
if (list_length(idents) == 2)
|
|
{
|
|
/*
|
|
* Do a lookup in the current namespace stack. We don't need to check
|
|
* number of names matched, because we will only consider scalar
|
|
* variables.
|
|
*/
|
|
nse = plpgsql_ns_lookup(plpgsql_ns_top(), false,
|
|
strVal(linitial(idents)),
|
|
strVal(lsecond(idents)),
|
|
NULL,
|
|
NULL);
|
|
|
|
if (nse != NULL && nse->itemtype == PLPGSQL_NSTYPE_VAR)
|
|
{
|
|
dtype = ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype;
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* First word could also be a table name
|
|
*/
|
|
classOid = RelnameGetRelid(strVal(linitial(idents)));
|
|
if (!OidIsValid(classOid))
|
|
goto done;
|
|
fldname = strVal(lsecond(idents));
|
|
}
|
|
else if (list_length(idents) == 3)
|
|
{
|
|
RangeVar *relvar;
|
|
|
|
relvar = makeRangeVar(strVal(linitial(idents)),
|
|
strVal(lsecond(idents)),
|
|
-1);
|
|
/* Can't lock relation - we might not have privileges. */
|
|
classOid = RangeVarGetRelid(relvar, NoLock, true);
|
|
if (!OidIsValid(classOid))
|
|
goto done;
|
|
fldname = strVal(lthird(idents));
|
|
}
|
|
else
|
|
goto done;
|
|
|
|
classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(classOid));
|
|
if (!HeapTupleIsValid(classtup))
|
|
goto done;
|
|
classStruct = (Form_pg_class) GETSTRUCT(classtup);
|
|
|
|
/*
|
|
* It must be a relation, sequence, view, materialized view, composite
|
|
* type, or foreign table
|
|
*/
|
|
if (classStruct->relkind != RELKIND_RELATION &&
|
|
classStruct->relkind != RELKIND_SEQUENCE &&
|
|
classStruct->relkind != RELKIND_VIEW &&
|
|
classStruct->relkind != RELKIND_MATVIEW &&
|
|
classStruct->relkind != RELKIND_COMPOSITE_TYPE &&
|
|
classStruct->relkind != RELKIND_FOREIGN_TABLE &&
|
|
classStruct->relkind != RELKIND_PARTITIONED_TABLE)
|
|
goto done;
|
|
|
|
/*
|
|
* Fetch the named table field and its type
|
|
*/
|
|
attrtup = SearchSysCacheAttName(classOid, fldname);
|
|
if (!HeapTupleIsValid(attrtup))
|
|
goto done;
|
|
attrStruct = (Form_pg_attribute) GETSTRUCT(attrtup);
|
|
|
|
typetup = SearchSysCache1(TYPEOID,
|
|
ObjectIdGetDatum(attrStruct->atttypid));
|
|
if (!HeapTupleIsValid(typetup))
|
|
elog(ERROR, "cache lookup failed for type %u", attrStruct->atttypid);
|
|
|
|
/*
|
|
* Found that - build a compiler type struct in the caller's cxt and
|
|
* return it. Note that we treat the type as being found-by-OID; no
|
|
* attempt to re-look-up the type name will happen during invalidations.
|
|
*/
|
|
MemoryContextSwitchTo(oldCxt);
|
|
dtype = build_datatype(typetup,
|
|
attrStruct->atttypmod,
|
|
attrStruct->attcollation,
|
|
NULL);
|
|
MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
|
|
|
|
done:
|
|
if (HeapTupleIsValid(classtup))
|
|
ReleaseSysCache(classtup);
|
|
if (HeapTupleIsValid(attrtup))
|
|
ReleaseSysCache(attrtup);
|
|
if (HeapTupleIsValid(typetup))
|
|
ReleaseSysCache(typetup);
|
|
|
|
MemoryContextSwitchTo(oldCxt);
|
|
return dtype;
|
|
}
|
|
|
|
/* ----------
|
|
* plpgsql_parse_wordrowtype Scanner found word%ROWTYPE.
|
|
* So word must be a table name.
|
|
* ----------
|
|
*/
|
|
PLpgSQL_type *
|
|
plpgsql_parse_wordrowtype(char *ident)
|
|
{
|
|
Oid classOid;
|
|
|
|
/*
|
|
* Look up the relation. Note that because relation rowtypes have the
|
|
* same names as their relations, this could be handled as a type lookup
|
|
* equally well; we use the relation lookup code path only because the
|
|
* errors thrown here have traditionally referred to relations not types.
|
|
* But we'll make a TypeName in case we have to do re-look-up of the type.
|
|
*/
|
|
classOid = RelnameGetRelid(ident);
|
|
if (!OidIsValid(classOid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_TABLE),
|
|
errmsg("relation \"%s\" does not exist", ident)));
|
|
|
|
/* Build and return the row type struct */
|
|
return plpgsql_build_datatype(get_rel_type_id(classOid), -1, InvalidOid,
|
|
makeTypeName(ident));
|
|
}
|
|
|
|
/* ----------
|
|
* plpgsql_parse_cwordrowtype Scanner found compositeword%ROWTYPE.
|
|
* So word must be a namespace qualified table name.
|
|
* ----------
|
|
*/
|
|
PLpgSQL_type *
|
|
plpgsql_parse_cwordrowtype(List *idents)
|
|
{
|
|
Oid classOid;
|
|
RangeVar *relvar;
|
|
MemoryContext oldCxt;
|
|
|
|
/*
|
|
* As above, this is a relation lookup but could be a type lookup if we
|
|
* weren't being backwards-compatible about error wording.
|
|
*/
|
|
if (list_length(idents) != 2)
|
|
return NULL;
|
|
|
|
/* Avoid memory leaks in long-term function context */
|
|
oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
|
|
|
|
/* Look up relation name. Can't lock it - we might not have privileges. */
|
|
relvar = makeRangeVar(strVal(linitial(idents)),
|
|
strVal(lsecond(idents)),
|
|
-1);
|
|
classOid = RangeVarGetRelid(relvar, NoLock, false);
|
|
|
|
MemoryContextSwitchTo(oldCxt);
|
|
|
|
/* Build and return the row type struct */
|
|
return plpgsql_build_datatype(get_rel_type_id(classOid), -1, InvalidOid,
|
|
makeTypeNameFromNameList(idents));
|
|
}
|
|
|
|
/*
|
|
* plpgsql_build_variable - build a datum-array entry of a given
|
|
* datatype
|
|
*
|
|
* The returned struct may be a PLpgSQL_var or PLpgSQL_rec
|
|
* depending on the given datatype, and is allocated via
|
|
* palloc. The struct is automatically added to the current datum
|
|
* array, and optionally to the current namespace.
|
|
*/
|
|
PLpgSQL_variable *
|
|
plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype,
|
|
bool add2namespace)
|
|
{
|
|
PLpgSQL_variable *result;
|
|
|
|
switch (dtype->ttype)
|
|
{
|
|
case PLPGSQL_TTYPE_SCALAR:
|
|
{
|
|
/* Ordinary scalar datatype */
|
|
PLpgSQL_var *var;
|
|
|
|
var = palloc0(sizeof(PLpgSQL_var));
|
|
var->dtype = PLPGSQL_DTYPE_VAR;
|
|
var->refname = pstrdup(refname);
|
|
var->lineno = lineno;
|
|
var->datatype = dtype;
|
|
/* other fields are left as 0, might be changed by caller */
|
|
|
|
/* preset to NULL */
|
|
var->value = 0;
|
|
var->isnull = true;
|
|
var->freeval = false;
|
|
|
|
plpgsql_adddatum((PLpgSQL_datum *) var);
|
|
if (add2namespace)
|
|
plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR,
|
|
var->dno,
|
|
refname);
|
|
result = (PLpgSQL_variable *) var;
|
|
break;
|
|
}
|
|
case PLPGSQL_TTYPE_REC:
|
|
{
|
|
/* Composite type -- build a record variable */
|
|
PLpgSQL_rec *rec;
|
|
|
|
rec = plpgsql_build_record(refname, lineno,
|
|
dtype, dtype->typoid,
|
|
add2namespace);
|
|
result = (PLpgSQL_variable *) rec;
|
|
break;
|
|
}
|
|
case PLPGSQL_TTYPE_PSEUDO:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("variable \"%s\" has pseudo-type %s",
|
|
refname, format_type_be(dtype->typoid))));
|
|
result = NULL; /* keep compiler quiet */
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized ttype: %d", dtype->ttype);
|
|
result = NULL; /* keep compiler quiet */
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Build empty named record variable, and optionally add it to namespace
|
|
*/
|
|
PLpgSQL_rec *
|
|
plpgsql_build_record(const char *refname, int lineno,
|
|
PLpgSQL_type *dtype, Oid rectypeid,
|
|
bool add2namespace)
|
|
{
|
|
PLpgSQL_rec *rec;
|
|
|
|
rec = palloc0(sizeof(PLpgSQL_rec));
|
|
rec->dtype = PLPGSQL_DTYPE_REC;
|
|
rec->refname = pstrdup(refname);
|
|
rec->lineno = lineno;
|
|
/* other fields are left as 0, might be changed by caller */
|
|
rec->datatype = dtype;
|
|
rec->rectypeid = rectypeid;
|
|
rec->firstfield = -1;
|
|
rec->erh = NULL;
|
|
plpgsql_adddatum((PLpgSQL_datum *) rec);
|
|
if (add2namespace)
|
|
plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->dno, rec->refname);
|
|
|
|
return rec;
|
|
}
|
|
|
|
/*
|
|
* Build a row-variable data structure given the component variables.
|
|
* Include a rowtupdesc, since we will need to materialize the row result.
|
|
*/
|
|
static PLpgSQL_row *
|
|
build_row_from_vars(PLpgSQL_variable **vars, int numvars)
|
|
{
|
|
PLpgSQL_row *row;
|
|
int i;
|
|
|
|
row = palloc0(sizeof(PLpgSQL_row));
|
|
row->dtype = PLPGSQL_DTYPE_ROW;
|
|
row->refname = "(unnamed row)";
|
|
row->lineno = -1;
|
|
row->rowtupdesc = CreateTemplateTupleDesc(numvars);
|
|
row->nfields = numvars;
|
|
row->fieldnames = palloc(numvars * sizeof(char *));
|
|
row->varnos = palloc(numvars * sizeof(int));
|
|
|
|
for (i = 0; i < numvars; i++)
|
|
{
|
|
PLpgSQL_variable *var = vars[i];
|
|
Oid typoid;
|
|
int32 typmod;
|
|
Oid typcoll;
|
|
|
|
/* Member vars of a row should never be const */
|
|
Assert(!var->isconst);
|
|
|
|
switch (var->dtype)
|
|
{
|
|
case PLPGSQL_DTYPE_VAR:
|
|
case PLPGSQL_DTYPE_PROMISE:
|
|
typoid = ((PLpgSQL_var *) var)->datatype->typoid;
|
|
typmod = ((PLpgSQL_var *) var)->datatype->atttypmod;
|
|
typcoll = ((PLpgSQL_var *) var)->datatype->collation;
|
|
break;
|
|
|
|
case PLPGSQL_DTYPE_REC:
|
|
/* shouldn't need to revalidate rectypeid already... */
|
|
typoid = ((PLpgSQL_rec *) var)->rectypeid;
|
|
typmod = -1; /* don't know typmod, if it's used at all */
|
|
typcoll = InvalidOid; /* composite types have no collation */
|
|
break;
|
|
|
|
default:
|
|
elog(ERROR, "unrecognized dtype: %d", var->dtype);
|
|
typoid = InvalidOid; /* keep compiler quiet */
|
|
typmod = 0;
|
|
typcoll = InvalidOid;
|
|
break;
|
|
}
|
|
|
|
row->fieldnames[i] = var->refname;
|
|
row->varnos[i] = var->dno;
|
|
|
|
TupleDescInitEntry(row->rowtupdesc, i + 1,
|
|
var->refname,
|
|
typoid, typmod,
|
|
0);
|
|
TupleDescInitEntryCollation(row->rowtupdesc, i + 1, typcoll);
|
|
}
|
|
|
|
return row;
|
|
}
|
|
|
|
/*
|
|
* Build a RECFIELD datum for the named field of the specified record variable
|
|
*
|
|
* If there's already such a datum, just return it; we don't need duplicates.
|
|
*/
|
|
PLpgSQL_recfield *
|
|
plpgsql_build_recfield(PLpgSQL_rec *rec, const char *fldname)
|
|
{
|
|
PLpgSQL_recfield *recfield;
|
|
int i;
|
|
|
|
/* search for an existing datum referencing this field */
|
|
i = rec->firstfield;
|
|
while (i >= 0)
|
|
{
|
|
PLpgSQL_recfield *fld = (PLpgSQL_recfield *) plpgsql_Datums[i];
|
|
|
|
Assert(fld->dtype == PLPGSQL_DTYPE_RECFIELD &&
|
|
fld->recparentno == rec->dno);
|
|
if (strcmp(fld->fieldname, fldname) == 0)
|
|
return fld;
|
|
i = fld->nextfield;
|
|
}
|
|
|
|
/* nope, so make a new one */
|
|
recfield = palloc0(sizeof(PLpgSQL_recfield));
|
|
recfield->dtype = PLPGSQL_DTYPE_RECFIELD;
|
|
recfield->fieldname = pstrdup(fldname);
|
|
recfield->recparentno = rec->dno;
|
|
recfield->rectupledescid = INVALID_TUPLEDESC_IDENTIFIER;
|
|
|
|
plpgsql_adddatum((PLpgSQL_datum *) recfield);
|
|
|
|
/* now we can link it into the parent's chain */
|
|
recfield->nextfield = rec->firstfield;
|
|
rec->firstfield = recfield->dno;
|
|
|
|
return recfield;
|
|
}
|
|
|
|
/*
|
|
* plpgsql_build_datatype
|
|
* Build PLpgSQL_type struct given type OID, typmod, collation,
|
|
* and type's parsed name.
|
|
*
|
|
* If collation is not InvalidOid then it overrides the type's default
|
|
* collation. But collation is ignored if the datatype is non-collatable.
|
|
*
|
|
* origtypname is the parsed form of what the user wrote as the type name.
|
|
* It can be NULL if the type could not be a composite type, or if it was
|
|
* identified by OID to begin with (e.g., it's a function argument type).
|
|
*/
|
|
PLpgSQL_type *
|
|
plpgsql_build_datatype(Oid typeOid, int32 typmod,
|
|
Oid collation, TypeName *origtypname)
|
|
{
|
|
HeapTuple typeTup;
|
|
PLpgSQL_type *typ;
|
|
|
|
typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeOid));
|
|
if (!HeapTupleIsValid(typeTup))
|
|
elog(ERROR, "cache lookup failed for type %u", typeOid);
|
|
|
|
typ = build_datatype(typeTup, typmod, collation, origtypname);
|
|
|
|
ReleaseSysCache(typeTup);
|
|
|
|
return typ;
|
|
}
|
|
|
|
/*
|
|
* Utility subroutine to make a PLpgSQL_type struct given a pg_type entry
|
|
* and additional details (see comments for plpgsql_build_datatype).
|
|
*/
|
|
static PLpgSQL_type *
|
|
build_datatype(HeapTuple typeTup, int32 typmod,
|
|
Oid collation, TypeName *origtypname)
|
|
{
|
|
Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
|
|
PLpgSQL_type *typ;
|
|
|
|
if (!typeStruct->typisdefined)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("type \"%s\" is only a shell",
|
|
NameStr(typeStruct->typname))));
|
|
|
|
typ = (PLpgSQL_type *) palloc(sizeof(PLpgSQL_type));
|
|
|
|
typ->typname = pstrdup(NameStr(typeStruct->typname));
|
|
typ->typoid = typeStruct->oid;
|
|
switch (typeStruct->typtype)
|
|
{
|
|
case TYPTYPE_BASE:
|
|
case TYPTYPE_ENUM:
|
|
case TYPTYPE_RANGE:
|
|
typ->ttype = PLPGSQL_TTYPE_SCALAR;
|
|
break;
|
|
case TYPTYPE_COMPOSITE:
|
|
typ->ttype = PLPGSQL_TTYPE_REC;
|
|
break;
|
|
case TYPTYPE_DOMAIN:
|
|
if (type_is_rowtype(typeStruct->typbasetype))
|
|
typ->ttype = PLPGSQL_TTYPE_REC;
|
|
else
|
|
typ->ttype = PLPGSQL_TTYPE_SCALAR;
|
|
break;
|
|
case TYPTYPE_PSEUDO:
|
|
if (typ->typoid == RECORDOID)
|
|
typ->ttype = PLPGSQL_TTYPE_REC;
|
|
else
|
|
typ->ttype = PLPGSQL_TTYPE_PSEUDO;
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized typtype: %d",
|
|
(int) typeStruct->typtype);
|
|
break;
|
|
}
|
|
typ->typlen = typeStruct->typlen;
|
|
typ->typbyval = typeStruct->typbyval;
|
|
typ->typtype = typeStruct->typtype;
|
|
typ->collation = typeStruct->typcollation;
|
|
if (OidIsValid(collation) && OidIsValid(typ->collation))
|
|
typ->collation = collation;
|
|
/* Detect if type is true array, or domain thereof */
|
|
/* NB: this is only used to decide whether to apply expand_array */
|
|
if (typeStruct->typtype == TYPTYPE_BASE)
|
|
{
|
|
/*
|
|
* This test should include what get_element_type() checks. We also
|
|
* disallow non-toastable array types (i.e. oidvector and int2vector).
|
|
*/
|
|
typ->typisarray = (typeStruct->typlen == -1 &&
|
|
OidIsValid(typeStruct->typelem) &&
|
|
typeStruct->typstorage != 'p');
|
|
}
|
|
else if (typeStruct->typtype == TYPTYPE_DOMAIN)
|
|
{
|
|
/* we can short-circuit looking up base types if it's not varlena */
|
|
typ->typisarray = (typeStruct->typlen == -1 &&
|
|
typeStruct->typstorage != 'p' &&
|
|
OidIsValid(get_base_element_type(typeStruct->typbasetype)));
|
|
}
|
|
else
|
|
typ->typisarray = false;
|
|
typ->atttypmod = typmod;
|
|
|
|
/*
|
|
* If it's a named composite type (or domain over one), find the typcache
|
|
* entry and record the current tupdesc ID, so we can detect changes
|
|
* (including drops). We don't currently support on-the-fly replacement
|
|
* of non-composite types, else we might want to do this for them too.
|
|
*/
|
|
if (typ->ttype == PLPGSQL_TTYPE_REC && typ->typoid != RECORDOID)
|
|
{
|
|
TypeCacheEntry *typentry;
|
|
|
|
typentry = lookup_type_cache(typ->typoid,
|
|
TYPECACHE_TUPDESC |
|
|
TYPECACHE_DOMAIN_BASE_INFO);
|
|
if (typentry->typtype == TYPTYPE_DOMAIN)
|
|
typentry = lookup_type_cache(typentry->domainBaseType,
|
|
TYPECACHE_TUPDESC);
|
|
if (typentry->tupDesc == NULL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
errmsg("type %s is not composite",
|
|
format_type_be(typ->typoid))));
|
|
|
|
typ->origtypname = origtypname;
|
|
typ->tcache = typentry;
|
|
typ->tupdesc_id = typentry->tupDesc_identifier;
|
|
}
|
|
else
|
|
{
|
|
typ->origtypname = NULL;
|
|
typ->tcache = NULL;
|
|
typ->tupdesc_id = 0;
|
|
}
|
|
|
|
return typ;
|
|
}
|
|
|
|
/*
|
|
* plpgsql_recognize_err_condition
|
|
* Check condition name and translate it to SQLSTATE.
|
|
*
|
|
* Note: there are some cases where the same condition name has multiple
|
|
* entries in the table. We arbitrarily return the first match.
|
|
*/
|
|
int
|
|
plpgsql_recognize_err_condition(const char *condname, bool allow_sqlstate)
|
|
{
|
|
int i;
|
|
|
|
if (allow_sqlstate)
|
|
{
|
|
if (strlen(condname) == 5 &&
|
|
strspn(condname, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 5)
|
|
return MAKE_SQLSTATE(condname[0],
|
|
condname[1],
|
|
condname[2],
|
|
condname[3],
|
|
condname[4]);
|
|
}
|
|
|
|
for (i = 0; exception_label_map[i].label != NULL; i++)
|
|
{
|
|
if (strcmp(condname, exception_label_map[i].label) == 0)
|
|
return exception_label_map[i].sqlerrstate;
|
|
}
|
|
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("unrecognized exception condition \"%s\"",
|
|
condname)));
|
|
return 0; /* keep compiler quiet */
|
|
}
|
|
|
|
/*
|
|
* plpgsql_parse_err_condition
|
|
* Generate PLpgSQL_condition entry(s) for an exception condition name
|
|
*
|
|
* This has to be able to return a list because there are some duplicate
|
|
* names in the table of error code names.
|
|
*/
|
|
PLpgSQL_condition *
|
|
plpgsql_parse_err_condition(char *condname)
|
|
{
|
|
int i;
|
|
PLpgSQL_condition *new;
|
|
PLpgSQL_condition *prev;
|
|
|
|
/*
|
|
* XXX Eventually we will want to look for user-defined exception names
|
|
* here.
|
|
*/
|
|
|
|
/*
|
|
* OTHERS is represented as code 0 (which would map to '00000', but we
|
|
* have no need to represent that as an exception condition).
|
|
*/
|
|
if (strcmp(condname, "others") == 0)
|
|
{
|
|
new = palloc(sizeof(PLpgSQL_condition));
|
|
new->sqlerrstate = 0;
|
|
new->condname = condname;
|
|
new->next = NULL;
|
|
return new;
|
|
}
|
|
|
|
prev = NULL;
|
|
for (i = 0; exception_label_map[i].label != NULL; i++)
|
|
{
|
|
if (strcmp(condname, exception_label_map[i].label) == 0)
|
|
{
|
|
new = palloc(sizeof(PLpgSQL_condition));
|
|
new->sqlerrstate = exception_label_map[i].sqlerrstate;
|
|
new->condname = condname;
|
|
new->next = prev;
|
|
prev = new;
|
|
}
|
|
}
|
|
|
|
if (!prev)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("unrecognized exception condition \"%s\"",
|
|
condname)));
|
|
|
|
return prev;
|
|
}
|
|
|
|
/* ----------
|
|
* plpgsql_start_datums Initialize datum list at compile startup.
|
|
* ----------
|
|
*/
|
|
static void
|
|
plpgsql_start_datums(void)
|
|
{
|
|
datums_alloc = 128;
|
|
plpgsql_nDatums = 0;
|
|
/* This is short-lived, so needn't allocate in function's cxt */
|
|
plpgsql_Datums = MemoryContextAlloc(plpgsql_compile_tmp_cxt,
|
|
sizeof(PLpgSQL_datum *) * datums_alloc);
|
|
/* datums_last tracks what's been seen by plpgsql_add_initdatums() */
|
|
datums_last = 0;
|
|
}
|
|
|
|
/* ----------
|
|
* plpgsql_adddatum Add a variable, record or row
|
|
* to the compiler's datum list.
|
|
* ----------
|
|
*/
|
|
void
|
|
plpgsql_adddatum(PLpgSQL_datum *newdatum)
|
|
{
|
|
if (plpgsql_nDatums == datums_alloc)
|
|
{
|
|
datums_alloc *= 2;
|
|
plpgsql_Datums = repalloc(plpgsql_Datums, sizeof(PLpgSQL_datum *) * datums_alloc);
|
|
}
|
|
|
|
newdatum->dno = plpgsql_nDatums;
|
|
plpgsql_Datums[plpgsql_nDatums++] = newdatum;
|
|
}
|
|
|
|
/* ----------
|
|
* plpgsql_finish_datums Copy completed datum info into function struct.
|
|
* ----------
|
|
*/
|
|
static void
|
|
plpgsql_finish_datums(PLpgSQL_function *function)
|
|
{
|
|
Size copiable_size = 0;
|
|
int i;
|
|
|
|
function->ndatums = plpgsql_nDatums;
|
|
function->datums = palloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums);
|
|
for (i = 0; i < plpgsql_nDatums; i++)
|
|
{
|
|
function->datums[i] = plpgsql_Datums[i];
|
|
|
|
/* This must agree with copy_plpgsql_datums on what is copiable */
|
|
switch (function->datums[i]->dtype)
|
|
{
|
|
case PLPGSQL_DTYPE_VAR:
|
|
case PLPGSQL_DTYPE_PROMISE:
|
|
copiable_size += MAXALIGN(sizeof(PLpgSQL_var));
|
|
break;
|
|
case PLPGSQL_DTYPE_REC:
|
|
copiable_size += MAXALIGN(sizeof(PLpgSQL_rec));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
function->copiable_size = copiable_size;
|
|
}
|
|
|
|
|
|
/* ----------
|
|
* plpgsql_add_initdatums Make an array of the datum numbers of
|
|
* all the initializable datums created since the last call
|
|
* to this function.
|
|
*
|
|
* If varnos is NULL, we just forget any datum entries created since the
|
|
* last call.
|
|
*
|
|
* This is used around a DECLARE section to create a list of the datums
|
|
* that have to be initialized at block entry. Note that datums can also
|
|
* be created elsewhere than DECLARE, eg by a FOR-loop, but it is then
|
|
* the responsibility of special-purpose code to initialize them.
|
|
* ----------
|
|
*/
|
|
int
|
|
plpgsql_add_initdatums(int **varnos)
|
|
{
|
|
int i;
|
|
int n = 0;
|
|
|
|
/*
|
|
* The set of dtypes recognized here must match what exec_stmt_block()
|
|
* cares about (re)initializing at block entry.
|
|
*/
|
|
for (i = datums_last; i < plpgsql_nDatums; i++)
|
|
{
|
|
switch (plpgsql_Datums[i]->dtype)
|
|
{
|
|
case PLPGSQL_DTYPE_VAR:
|
|
case PLPGSQL_DTYPE_REC:
|
|
n++;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (varnos != NULL)
|
|
{
|
|
if (n > 0)
|
|
{
|
|
*varnos = (int *) palloc(sizeof(int) * n);
|
|
|
|
n = 0;
|
|
for (i = datums_last; i < plpgsql_nDatums; i++)
|
|
{
|
|
switch (plpgsql_Datums[i]->dtype)
|
|
{
|
|
case PLPGSQL_DTYPE_VAR:
|
|
case PLPGSQL_DTYPE_REC:
|
|
(*varnos)[n++] = plpgsql_Datums[i]->dno;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
*varnos = NULL;
|
|
}
|
|
|
|
datums_last = plpgsql_nDatums;
|
|
return n;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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);
|
|
|
|
/*
|
|
* if trigger, get its OID. 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 really be
|
|
* used.
|
|
*/
|
|
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,
|
|
* but with a special case for validation: assume that polymorphic arguments
|
|
* are integer, integer-array or integer-range. Also, we go ahead and report
|
|
* the error if we can't resolve the types.
|
|
*/
|
|
static void
|
|
plpgsql_resolve_polymorphic_argtypes(int numargs,
|
|
Oid *argtypes, char *argmodes,
|
|
Node *call_expr, bool forValidator,
|
|
const char *proname)
|
|
{
|
|
int i;
|
|
|
|
if (!forValidator)
|
|
{
|
|
/* 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)));
|
|
}
|
|
else
|
|
{
|
|
/* special validation case */
|
|
for (i = 0; i < numargs; i++)
|
|
{
|
|
switch (argtypes[i])
|
|
{
|
|
case ANYELEMENTOID:
|
|
case ANYNONARRAYOID:
|
|
case ANYENUMOID: /* XXX dubious */
|
|
argtypes[i] = INT4OID;
|
|
break;
|
|
case ANYARRAYOID:
|
|
argtypes[i] = INT4ARRAYOID;
|
|
break;
|
|
case ANYRANGEOID:
|
|
argtypes[i] = INT4RANGEOID;
|
|
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);
|
|
|
|
memset(&ctl, 0, sizeof(ctl));
|
|
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,
|
|
(void *) 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,
|
|
(void *) 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,
|
|
(void *) 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;
|
|
}
|