text: /*-------------------------------------------------------------------------
   *
   * pl_comp.c		- Compiler part of the PL/pgSQL
   *			  procedural language
   *
   * Portions Copyright (c) 1996-2020, 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,
  									 &param->paramtype,
  									 &param->paramtypmod,
  									 &param->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;
  }
  