/*-------------------------------------------------------------------------
 *
 * plpgsql_check.c
 *
 *			  enhanced checks for plpgsql functions
 *
 * by Pavel Stehule 2013-2016
 *
 *-------------------------------------------------------------------------
 *
 * Notes:
 *
 * 1) Secondary hash table for function signature is necessary due holding is_checked
 *    attribute - this protection against unwanted repeated check.
 *
 * 2) Reusing some plpgsql_xxx functions requires full run-time environment. It is
 *    emulated by fake expression context and fake fceinfo (these are created when
 *    active checking is used) - see: setup_fake_fcinfo, setup_cstate.
 *
 * 3) The environment is referenced by stored execution plans. The actual plan should
 *    not be linked with fake environment. All expressions created in checking time
 *    should be relased by release_exprs(cstate.exprs) function.
 *
 */
#include "postgres.h"
#include "plpgsql.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "plpgsql_check_builtins.h"
#if PG_VERSION_NUM >= 100000
#define PLPGSQL_STMT_TYPES
#else
#define PLPGSQL_STMT_TYPES		(enum PLpgSQL_stmt_types)
#endif
#if PG_VERSION_NUM >= 100000
#include "utils/regproc.h"
#endif
#if PG_VERSION_NUM >= 90300
#include "access/htup_details.h"
#else
/* Older version doesn't support event triggers */
#ifdef _MSC_VER
typedef struct {char nothing[0];}  EventTriggerData;
#else
typedef struct {}  EventTriggerData;
#endif
typedef enum PLpgSQL_trigtype
{
	PLPGSQL_DML_TRIGGER,
	PLPGSQL_EVENT_TRIGGER,
	PLPGSQL_NOT_TRIGGER
} PLpgSQL_trigtype;
#endif
#include "access/tupconvert.h"
#include "access/tupdesc.h"
#ifndef TupleDescAttr
#define TupleDescAttr(tupdesc, i) ((tupdesc)->attrs[(i)])
#endif
#include "catalog/pg_language.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "executor/spi_priv.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_coerce.h"
#include "tcop/utility.h"
#include "tsearch/ts_locale.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
#include "utils/rel.h"
#include "utils/json.h"
#include "utils/reltrigger.h"
#include "utils/xml.h"
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
/*
 * columns of plpgsql_check_function_table result
 *
 */
#define Natts_result					11
#define Anum_result_functionid			0
#define Anum_result_lineno			1
#define Anum_result_statement			2
#define Anum_result_sqlstate			3
#define Anum_result_message			4
#define Anum_result_detail			5
#define Anum_result_hint			6
#define Anum_result_level			7
#define Anum_result_position			8
#define Anum_result_query			9
#define Anum_result_context			10
enum
{
	PLPGSQL_CHECK_ERROR,
	PLPGSQL_CHECK_WARNING_OTHERS,
	PLPGSQL_CHECK_WARNING_EXTRA,					/* check shadowed variables */
	PLPGSQL_CHECK_WARNING_PERFORMANCE
};
enum
{
	PLPGSQL_CHECK_FORMAT_ELOG,
	PLPGSQL_CHECK_FORMAT_TEXT,
	PLPGSQL_CHECK_FORMAT_TABULAR,
	PLPGSQL_CHECK_FORMAT_XML,
	PLPGSQL_CHECK_FORMAT_JSON
};
enum
{
	PLPGSQL_CHECK_CLOSED,
	PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS,
	PLPGSQL_CHECK_POSSIBLY_CLOSED,
	PLPGSQL_CHECK_UNCLOSED,
	PLPGSQL_CHECK_UNKNOWN
};
enum
{
	PLPGSQL_CHECK_MODE_DISABLED,		/* all functionality is disabled */
	PLPGSQL_CHECK_MODE_BY_FUNCTION,		/* checking is allowed via CHECK function only (default) */
	PLPGSQL_CHECK_MODE_FRESH_START,		/* check only when function is called first time */
	PLPGSQL_CHECK_MODE_EVERY_START		/* check on every start */
};
typedef struct PLpgSQL_stmt_stack_item
{
	PLpgSQL_stmt			*stmt;
	char				*label;
	struct PLpgSQL_stmt_stack_item	*outer;
} PLpgSQL_stmt_stack_item;
typedef struct PLpgSQL_checkstate
{
	Oid			fn_oid;						/* oid of checked function */
	List	    *argnames;					/* function arg names */
	PLpgSQL_execstate	   *estate;			/* check state is estate extension */
	Tuplestorestate		   *tuple_store;	/* result target */
	TupleDesc	tupdesc;					/* result description */
	bool		fatal_errors;				/* stop on first error */
	bool		performance_warnings;		/* show performace warnings */
	bool		other_warnings;				/* show other warnings */
	bool		extra_warnings;				/* show extra warnings */
	int			format;						/* output format */
	StringInfo	sinfo;						/* aux. stringInfo used for result string concat */
	MemoryContext			check_cxt;
	List	   *exprs;						/* list of all expression created by checker */
	bool		is_active_mode;				/* true, when checking is started by plpgsql_check_function */
	Bitmapset  *used_variables;				/* track which variables have been used; bit per varno */
	Bitmapset  *modif_variables;			/* track which variables had been changed; bit per varno */
	PLpgSQL_stmt_stack_item *top_stmt_stack;	/* list of known labels + related command */
	bool		found_return_query;			/* true, when code contains RETURN query */
} PLpgSQL_checkstate;
static void assign_tupdesc_dno(PLpgSQL_checkstate *cstate, int varno, TupleDesc tupdesc, bool isnull);
static void assign_tupdesc_row_or_rec(PLpgSQL_checkstate *cstate,
						  PLpgSQL_row *row, PLpgSQL_rec *rec,
						  TupleDesc tupdesc, bool isnull);
static void check_assignment(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr,
				 PLpgSQL_rec *targetrec, PLpgSQL_row *targetrow,
				 int targetdno);
static void check_assignment_with_possible_slices(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr,
						 PLpgSQL_rec *targetrec, PLpgSQL_row *targetrow,
						 int targetdno, bool use_element_type);
static void check_expr(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr);
static void check_expr_with_expected_scalar_type(PLpgSQL_checkstate *cstate,
								 PLpgSQL_expr *expr,
								 Oid expected_typoid,
								 bool required);
static void check_expr_as_rvalue(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr,
					  PLpgSQL_rec *targetrec, PLpgSQL_row *targetrow,
				   int targetdno, bool use_element_type, bool is_expression);
static void check_returned_expr(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, bool is_expression);
static void check_expr_as_sqlstmt_nodata(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr);
static void check_assign_to_target_type(PLpgSQL_checkstate *cstate,
							 Oid target_typoid, int32 target_typmod,
							 Oid value_typoid,
									    bool isnull);
static void check_function_epilog(PLpgSQL_checkstate *cstate);
static void check_function_prolog(PLpgSQL_checkstate *cstate);
static void check_on_func_beg(PLpgSQL_execstate * estate, PLpgSQL_function * func);
static void check_plpgsql_function(HeapTuple procTuple, Oid relid, PLpgSQL_trigtype trigtype,
					   TupleDesc tupdesc,
					   Tuplestorestate *tupstore,
							    int format,
									  bool fatal_errors,
									  bool other_warnings,
									  bool performance_warnings,
									  bool extra_warnings);
static void check_row_or_rec(PLpgSQL_checkstate *cstate, PLpgSQL_row *row, PLpgSQL_rec *rec);
static void check_stmt(PLpgSQL_checkstate *cstate, PLpgSQL_stmt *stmt, int *closing, List **exceptions);
static void check_stmts(PLpgSQL_checkstate *cstate, List *stmts, int *closing, List **exceptions);
static void check_target(PLpgSQL_checkstate *cstate, int varno, Oid *expected_typoid, int *expected_typmod);
static PLpgSQL_datum *copy_plpgsql_datum(PLpgSQL_datum *datum);
static char *datum_get_refname(PLpgSQL_datum *d);
static TupleDesc expr_get_desc(PLpgSQL_checkstate *cstate,
							  PLpgSQL_expr *query,
										  bool use_element_type,
										  bool expand_record,
										  bool is_expression,
										  Oid *first_level_typoid);
static void format_error_xml(StringInfo str,
						  PLpgSQL_execstate *estate,
								 int sqlerrcode, int lineno,
								 const char *message, const char *detail, const char *hint,
								 int level, int position,
								 const char *query,
								 const char *context);
static void format_error_json(StringInfo str,
	PLpgSQL_execstate *estate,
	int sqlerrcode, int lineno,
	const char *message, const char *detail, const char *hint,
	int level, int position,
	const char *query,
	const char *context);
static void function_check(PLpgSQL_function *func, FunctionCallInfo fcinfo,
								   PLpgSQL_execstate *estate, PLpgSQL_checkstate *cstate);
static PLpgSQL_trigtype get_trigtype(HeapTuple procTuple);
static void init_datum_dno(PLpgSQL_checkstate *cstate, int varno);
static bool is_checked(PLpgSQL_function *func);
static int load_configuration(HeapTuple procTuple, bool *reload_config);
static void mark_as_checked(PLpgSQL_function *func);
static void plpgsql_check_HashTableInit(void);
static void prohibit_write_plan(PLpgSQL_checkstate *cstate, PLpgSQL_expr *query);
static void put_error(PLpgSQL_checkstate *cstate,
					  int sqlerrcode, int lineno,
					  const char *message, const char *detail, const char *hint,
					  int level, int position,
						 const char *query, const char *context);
static void put_error_edata(PLpgSQL_checkstate *cstate, ErrorData *edata);
static void precheck_conditions(HeapTuple procTuple, PLpgSQL_trigtype trigtype, Oid relid);
static void prepare_expr(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, int cursorOptions);
static void release_exprs(List *exprs);
static void setup_cstate(PLpgSQL_checkstate *cstate,
							 Oid fn_oid, TupleDesc tupdesc, Tuplestorestate *tupstore,
							 bool fatal_errors,
								 bool other_warnings, bool perform_warnings, bool extra_warnings,
												    int format,
												    bool is_active_mode);
static void setup_fake_fcinfo(HeapTuple procTuple,
						 FmgrInfo *flinfo,
						 FunctionCallInfoData *fcinfo,
						 ReturnSetInfo *rsinfo,
										 TriggerData *trigdata,
										 Oid relid,
										 EventTriggerData *etrigdata,
										 Oid funcoid,
										 PLpgSQL_trigtype trigtype,
										 Trigger *tg_trigger);
static void setup_plpgsql_estate(PLpgSQL_execstate *estate,
								 PLpgSQL_function *func, ReturnSetInfo *rsi);
static void trigger_check(PLpgSQL_function *func,
							  Node *trigdata,
								  PLpgSQL_execstate *estate, PLpgSQL_checkstate *cstate);
static void tuplestore_put_error_text(Tuplestorestate *tuple_store, TupleDesc tupdesc,
						  PLpgSQL_execstate *estate, Oid fn_oid,
								 int sqlerrcode, int lineno,
								 const char *message, const char *detail, const char *hint,
								 int level, int position,
									 const char *query, const char *context);
static void tuplestore_put_error_tabular(Tuplestorestate *tuple_store, TupleDesc tupdesc,
						  PLpgSQL_execstate *estate, Oid fn_oid,
								 int sqlerrcode, int lineno,
								 const char *message, const char *detail, const char *hint,
								 int level, int position,
									 const char *query, const char *context);
static void tuplestore_put_text_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
								    const char *message, int len);
static void report_unused_variables(PLpgSQL_checkstate *cstate);
static void record_variable_usage(PLpgSQL_checkstate *cstate, int dno, bool write);
static bool datum_is_used(PLpgSQL_checkstate *cstate, int dno, bool write);
static bool is_const_null_expr(PLpgSQL_expr *query);
static void prohibit_transaction_stmt(PLpgSQL_checkstate *cstate, PLpgSQL_expr *query);
static int merge_closing(int c, int c_local, List **exceptions, List *exceptions_local, int err_code);
static int possibly_closed(int c);
static char *ExprGetString(PLpgSQL_expr *query, bool *IsConst);
static bool exception_matches_conditions(int err_code, PLpgSQL_condition *cond);
static bool plpgsql_check_other_warnings = false;
static bool plpgsql_check_extra_warnings = false;
static bool plpgsql_check_performance_warnings = false;
static bool plpgsql_check_fatal_errors = true;
static int plpgsql_check_mode = PLPGSQL_CHECK_MODE_BY_FUNCTION;
static PLpgSQL_plugin plugin_funcs = { NULL, check_on_func_beg, NULL, NULL, NULL};
static const struct config_enum_entry plpgsql_check_mode_options[] = {
	{"disabled", PLPGSQL_CHECK_MODE_DISABLED, false},
	{"by_function", PLPGSQL_CHECK_MODE_BY_FUNCTION, false},
	{"fresh_start", PLPGSQL_CHECK_MODE_FRESH_START, false},
	{"every_start", PLPGSQL_CHECK_MODE_EVERY_START, false},
	{NULL, 0, false}
};
/* ----------
 * Hash table for checked functions
 * ----------
 */
static HTAB *plpgsql_check_HashTable = NULL;
typedef struct plpgsql_hashent
{
	PLpgSQL_func_hashkey key;
	TransactionId fn_xmin;
	ItemPointerData fn_tid;
	bool is_checked;
} plpgsql_check_HashEnt;
#define FUNCS_PER_USER		128 /* initial table size */
PG_FUNCTION_INFO_V1(plpgsql_check_function);
PG_FUNCTION_INFO_V1(plpgsql_check_function_tb);
/*
 * Module initialization
 *
 * join to PLpgSQL executor
 *
 */
void 
_PG_init(void)
{
	PLpgSQL_plugin ** var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable( "PLpgSQL_plugin" );
	/* Be sure we do initialization only once (should be redundant now) */
	static bool inited = false;
	if (inited)
		return;
	*var_ptr = &plugin_funcs;
	DefineCustomEnumVariable("plpgsql_check.mode",
					    "choose a mode for enhanced checking",
					    NULL,
					    &plpgsql_check_mode,
					    PLPGSQL_CHECK_MODE_BY_FUNCTION,
					    plpgsql_check_mode_options,
					    PGC_SUSET, 0,
					    NULL, NULL, NULL);
	DefineCustomBoolVariable("plpgsql_check.show_nonperformance_extra_warnings",
					    "when is true, then extra warning (except performance warnings) are showed",
					    NULL,
					    &plpgsql_check_extra_warnings,
					    false,
					    PGC_SUSET, 0,
					    NULL, NULL, NULL);
	DefineCustomBoolVariable("plpgsql_check.show_nonperformance_warnings",
					    "when is true, then warning (except performance warnings) are showed",
					    NULL,
					    &plpgsql_check_other_warnings,
					    false,
					    PGC_SUSET, 0,
					    NULL, NULL, NULL);
	DefineCustomBoolVariable("plpgsql_check.show_performance_warnings",
					    "when is true, then performance warnings are showed",
					    NULL,
					    &plpgsql_check_performance_warnings,
					    false,
					    PGC_SUSET, 0,
					    NULL, NULL, NULL);
	DefineCustomBoolVariable("plpgsql_check.fatal_errors",
					    "when is true, then plpgsql check stops execution on detected error",
					    NULL,
					    &plpgsql_check_fatal_errors,
					    true,
					    PGC_SUSET, 0,
					    NULL, NULL, NULL);
	plpgsql_check_HashTableInit();
	inited = true;
}
/*
 * plpgsql_check_func_beg 
 *
 *      callback function - called by plgsql executor, when function is started
 *      and local variables are initialized.
 *
 */
static void
check_on_func_beg(PLpgSQL_execstate * estate, PLpgSQL_function * func)
{
	const char *err_text = estate->err_text;
	int closing;
	List		*exceptions;
	if (plpgsql_check_mode == PLPGSQL_CHECK_MODE_FRESH_START ||
		   plpgsql_check_mode == PLPGSQL_CHECK_MODE_EVERY_START)
	{
		int i;
		PLpgSQL_rec *saved_records;
		PLpgSQL_var *saved_vars;
		MemoryContext oldcontext,
					 old_cxt;
		ResourceOwner oldowner;
		PLpgSQL_checkstate cstate;
		/*
		 * don't allow repeated execution on checked function
		 * when it is not requsted. 
		 */
		if (plpgsql_check_mode == PLPGSQL_CHECK_MODE_FRESH_START &&
			is_checked(func))
		{
			elog(NOTICE, "function \"%s\" was checked already",
							    func->fn_signature);
			return;
		}
		mark_as_checked(func);
		setup_cstate(&cstate, func->fn_oid, NULL, NULL,
							    plpgsql_check_fatal_errors,
							    plpgsql_check_other_warnings,
							    plpgsql_check_performance_warnings,
							    plpgsql_check_extra_warnings,
							    PLPGSQL_CHECK_FORMAT_ELOG,
							    false);
		/* use real estate */
		cstate.estate = estate;
		old_cxt = MemoryContextSwitchTo(cstate.check_cxt);
		/*
		 * During the check stage a rec and vars variables are modified, so we should
		 * to save their content
		 */
		saved_records = palloc(sizeof(PLpgSQL_rec) * estate->ndatums);
		saved_vars = palloc(sizeof(PLpgSQL_var) * estate->ndatums);
		for (i = 0; i < estate->ndatums; i++)
		{
			if (estate->datums[i]->dtype == PLPGSQL_DTYPE_REC)
			{
				PLpgSQL_rec *rec = (PLpgSQL_rec *) estate->datums[i];
				saved_records[i].tup = rec->tup;
				saved_records[i].tupdesc = rec->tupdesc;
				saved_records[i].freetup = rec->freetup;
				saved_records[i].freetupdesc = rec->freetupdesc;
				/* don't release a original tupdesc and original tup */
				rec->freetup = false;
				rec->freetupdesc = false;
			}
			else if (estate->datums[i]->dtype == PLPGSQL_DTYPE_VAR)
			{
				PLpgSQL_var *var = (PLpgSQL_var *) estate->datums[i];
				saved_vars[i].value = var->value;
				saved_vars[i].isnull = var->isnull;
				saved_vars[i].freeval = var->freeval;
				var->freeval = false;
			}
		}
		estate->err_text = NULL;
		/*
		 * Raised exception should be trapped in outer functtion. Protection
		 * against outer trap is QUERY_CANCELED exception. 
		 */
		oldcontext = CurrentMemoryContext;
		oldowner = CurrentResourceOwner;
		PG_TRY();
		{
			/*
			 * Now check the toplevel block of statements
			 */
			check_stmt(&cstate, (PLpgSQL_stmt *) func->action, &closing, &exceptions);
			estate->err_stmt = NULL;
			if (closing != PLPGSQL_CHECK_CLOSED && closing != PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS)
				put_error(&cstate,
								  ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT, 0,
								  "control reached end of function without RETURN",
								  NULL,
								  NULL,
								  closing == PLPGSQL_CHECK_UNCLOSED ?
										PLPGSQL_CHECK_ERROR : PLPGSQL_CHECK_WARNING_EXTRA,
								  0, NULL, NULL);
			report_unused_variables(&cstate);
		}
		PG_CATCH();
		{
			ErrorData  *edata;
			/* Save error info */
			MemoryContextSwitchTo(oldcontext);
			edata = CopyErrorData();
			FlushErrorState();
			CurrentResourceOwner = oldowner;
			release_exprs(cstate.exprs);
			edata->sqlerrcode = ERRCODE_QUERY_CANCELED;
			ReThrowError(edata);
		}
		PG_END_TRY();
		estate->err_text = err_text;
		estate->err_stmt = NULL;
		/* return back a original rec variables */
		for (i = 0; i < estate->ndatums; i++)
		{
			if (estate->datums[i]->dtype == PLPGSQL_DTYPE_REC)
			{
				PLpgSQL_rec *rec = (PLpgSQL_rec *) estate->datums[i];
				if (rec->freetupdesc)
					FreeTupleDesc(rec->tupdesc);
				rec->tup = saved_records[i].tup;
				rec->tupdesc = saved_records[i].tupdesc;
				rec->freetup = saved_records[i].freetup;
				rec->freetupdesc = saved_records[i].freetupdesc;
			}
			else if (estate->datums[i]->dtype == PLPGSQL_DTYPE_VAR)
			{
				PLpgSQL_var *var = (PLpgSQL_var *) estate->datums[i];
				var->value = saved_vars[i].value;
				var->isnull = saved_vars[i].isnull;
				var->freeval = saved_vars[i].freeval;
			}
		}
		MemoryContextSwitchTo(old_cxt);
		MemoryContextDelete(cstate.check_cxt);
	}
}
/*
 * plpgsql_check_function
 *
 * Extended check with formatted text output
 *
 */
Datum
plpgsql_check_function(PG_FUNCTION_ARGS)
{
	Oid			funcoid = PG_GETARG_OID(0);
	Oid			relid = PG_GETARG_OID(1);
	char			*format_str = text_to_cstring(PG_GETARG_TEXT_PP(2));
	bool			fatal_errors = PG_GETARG_BOOL(3);
	bool			other_warnings = PG_GETARG_BOOL(4);
	bool			performance_warnings = PG_GETARG_BOOL(5);
	bool			extra_warnings;
	TupleDesc	tupdesc;
	HeapTuple	procTuple;
	Tuplestorestate *tupstore;
	MemoryContext per_query_ctx;
	MemoryContext oldcontext;
	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
	PLpgSQL_trigtype trigtype;
	char *format_lower_str;
	int format = PLPGSQL_CHECK_FORMAT_TEXT;
	ErrorContextCallback *prev_errorcontext;
	if (PG_NARGS() != 7)
		elog(ERROR, "unexpected number of parameters, you should to update extension");
	extra_warnings = PG_GETARG_BOOL(6);
	format_lower_str = lowerstr(format_str);
	if (strcmp(format_lower_str, "text") == 0)
		format = PLPGSQL_CHECK_FORMAT_TEXT;
	else if (strcmp(format_lower_str, "xml") == 0)
		format = PLPGSQL_CHECK_FORMAT_XML;
	else if (strcmp(format_lower_str, "json") == 0)
		format = PLPGSQL_CHECK_FORMAT_JSON;
	else
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("unrecognize format: \"%s\"",
									 format_lower_str),
			errhint("Only \"text\", \"xml\" and \"json\" formats are supported.")));
	/* check to see if caller supports us returning a tuplestore */
	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("set-valued function called in context that cannot accept a set")));
	if (!(rsinfo->allowedModes & SFRM_Materialize))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("materialize mode required, but it is not allowed in this context")));
	procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
	if (!HeapTupleIsValid(procTuple))
		elog(ERROR, "cache lookup failed for function %u", funcoid);
	trigtype = get_trigtype(procTuple);
	precheck_conditions(procTuple, trigtype, relid);
	/* need to build tuplestore in query context */
	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
	oldcontext = MemoryContextSwitchTo(per_query_ctx);
	tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc);
	tupstore = tuplestore_begin_heap(false, false, work_mem);
	MemoryContextSwitchTo(oldcontext);
	prev_errorcontext = error_context_stack;
	error_context_stack = NULL;
	check_plpgsql_function(procTuple, relid, trigtype,
							   tupdesc, tupstore,
							   format,
								   fatal_errors,
								   other_warnings, performance_warnings, extra_warnings);
	error_context_stack = prev_errorcontext;
	ReleaseSysCache(procTuple);
	/* clean up and return the tuplestore */
	tuplestore_donestoring(tupstore);
	rsinfo->returnMode = SFRM_Materialize;
	rsinfo->setResult = tupstore;
	rsinfo->setDesc = tupdesc;
	return (Datum) 0;
}
/*
 * plpgsql_check_function_tb
 *
 * It ensure a detailed validation and returns result as multicolumn table
 *
 */
Datum
plpgsql_check_function_tb(PG_FUNCTION_ARGS)
{
	Oid			funcoid = PG_GETARG_OID(0);
	Oid			relid = PG_GETARG_OID(1);
	bool			fatal_errors = PG_GETARG_BOOL(2);
	bool			other_warnings = PG_GETARG_BOOL(3);
	bool			performance_warnings = PG_GETARG_BOOL(4);
	bool			extra_warnings;
	TupleDesc	tupdesc;
	HeapTuple	procTuple;
	Tuplestorestate *tupstore;
	MemoryContext per_query_ctx;
	MemoryContext oldcontext;
	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
	PLpgSQL_trigtype trigtype;
	ErrorContextCallback *prev_errorcontext;
	if (PG_NARGS() != 6)
		elog(ERROR, "unexpected number of parameters, you should to update extension");
	extra_warnings = PG_GETARG_BOOL(5);
	/* check to see if caller supports us returning a tuplestore */
	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("set-valued function called in context that cannot accept a set")));
	if (!(rsinfo->allowedModes & SFRM_Materialize))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("materialize mode required, but it is not allowed in this context")));
	procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
	if (!HeapTupleIsValid(procTuple))
		elog(ERROR, "cache lookup failed for function %u", funcoid);
	trigtype = get_trigtype(procTuple);
	precheck_conditions(procTuple, trigtype, relid);
	/* need to build tuplestore in query context */
	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
	oldcontext = MemoryContextSwitchTo(per_query_ctx);
	tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc);
	tupstore = tuplestore_begin_heap(false, false, work_mem);
	MemoryContextSwitchTo(oldcontext);
	prev_errorcontext = error_context_stack;
	/* Envelope outer plpgsql function is not interesting */
	error_context_stack = NULL;
	check_plpgsql_function(procTuple, relid, trigtype,
							   tupdesc, tupstore,
							   PLPGSQL_CHECK_FORMAT_TABULAR,
								   fatal_errors,
								   other_warnings, performance_warnings, extra_warnings);
	error_context_stack = prev_errorcontext;
	ReleaseSysCache(procTuple);
	/* clean up and return the tuplestore */
	tuplestore_donestoring(tupstore);
	rsinfo->returnMode = SFRM_Materialize;
	rsinfo->setResult = tupstore;
	rsinfo->setDesc = tupdesc;
	return (Datum) 0;
}
/*
 * Add label to stack of labels
 */
static PLpgSQL_stmt_stack_item *
push_stmt_to_stmt_stack(PLpgSQL_checkstate *cstate)
{
	PLpgSQL_stmt *stmt = cstate->estate->err_stmt;
	PLpgSQL_stmt_stack_item *stmt_stack_item;
	PLpgSQL_stmt_stack_item *current = cstate->top_stmt_stack;
	stmt_stack_item = (PLpgSQL_stmt_stack_item *) palloc(sizeof(PLpgSQL_stmt_stack_item));
	stmt_stack_item->stmt = stmt;
	switch (PLPGSQL_STMT_TYPES stmt->cmd_type)
	{
		case PLPGSQL_STMT_BLOCK:
			stmt_stack_item->label = ((PLpgSQL_stmt_block *) stmt)->label;
			break;
		case PLPGSQL_STMT_EXIT:
			stmt_stack_item->label = ((PLpgSQL_stmt_exit *) stmt)->label;
			break;
		case PLPGSQL_STMT_LOOP:
			stmt_stack_item->label = ((PLpgSQL_stmt_loop *) stmt)->label;
			break;
		case PLPGSQL_STMT_WHILE:
			stmt_stack_item->label = ((PLpgSQL_stmt_while *) stmt)->label;
			break;
		case PLPGSQL_STMT_FORI:
			stmt_stack_item->label = ((PLpgSQL_stmt_fori *) stmt)->label;
			break;
		case PLPGSQL_STMT_FORS:
			stmt_stack_item->label = ((PLpgSQL_stmt_fors *) stmt)->label;
			break;
		case PLPGSQL_STMT_FORC:
			stmt_stack_item->label = ((PLpgSQL_stmt_forc *) stmt)->label;
			break;
		case PLPGSQL_STMT_DYNFORS:
			stmt_stack_item->label = ((PLpgSQL_stmt_dynfors *) stmt)->label;
			break;
		case PLPGSQL_STMT_FOREACH_A:
			stmt_stack_item->label = ((PLpgSQL_stmt_foreach_a *) stmt)->label;
			break;
		default:
			stmt_stack_item->label = NULL;
	}
	stmt_stack_item->outer = current;
	cstate->top_stmt_stack = stmt_stack_item;
	return current;
}
static void
pop_stmt_from_stmt_stack(PLpgSQL_checkstate *cstate)
{
	PLpgSQL_stmt_stack_item *current = cstate->top_stmt_stack;
	Assert(cstate->top_stmt_stack != NULL);
	cstate->top_stmt_stack = current->outer;
	pfree(current);
}
/*
 * Returns true, when stmt is any loop statement
 */
static bool
is_any_loop_stmt(PLpgSQL_stmt *stmt)
{
	switch (PLPGSQL_STMT_TYPES stmt->cmd_type)
	{
		case PLPGSQL_STMT_LOOP:
		case PLPGSQL_STMT_WHILE:
		case PLPGSQL_STMT_FORI:
		case PLPGSQL_STMT_FORS:
		case PLPGSQL_STMT_FORC:
		case PLPGSQL_STMT_DYNFORS:
		case PLPGSQL_STMT_FOREACH_A:
			return true;
		default:
			return false;
	}
}
/*
 * Searching a any statement related to CONTINUE/EXIT statement.
 * label cannot be NULL.
 */
static PLpgSQL_stmt *
find_stmt_with_label(char *label, PLpgSQL_stmt_stack_item *current)
{
	while (current != NULL)
	{
		if (current->label != NULL
				&& strcmp(current->label, label) == 0)
			return current->stmt;
		current = current->outer;
	}
	return NULL;
}
static PLpgSQL_stmt *
find_nearest_loop(PLpgSQL_stmt_stack_item *current)
{
	while (current != NULL)
	{
		if (is_any_loop_stmt(current->stmt))
			return current->stmt;
		current = current->outer;
	}
	return NULL;
}
/*
 * returns false, when a variable doesn't shadows any other variable
 */
static bool
found_shadowed_variable(char *varname, PLpgSQL_stmt_stack_item *current, PLpgSQL_checkstate *cstate)
{
	while (current != NULL)
	{
		if (current->stmt->cmd_type == PLPGSQL_STMT_BLOCK)
		{
			PLpgSQL_stmt_block *stmt_block = (PLpgSQL_stmt_block *) current->stmt;
			int			i;
			PLpgSQL_datum *d;
			for (i = 0; i < stmt_block->n_initvars; i++)
			{
				char	   *refname;
				d = cstate->estate->func->datums[stmt_block->initvarnos[i]];
				refname = datum_get_refname(d);
				if (refname != NULL && strcmp(refname, varname) == 0)
					return true;
			}
		}
		current = current->outer;
	}
	return false;
}
/*
 * Returns PLpgSQL_trigtype based on prorettype
 */
static PLpgSQL_trigtype
get_trigtype(HeapTuple procTuple)
{
	Form_pg_proc proc;
	char		functyptype;
	proc = (Form_pg_proc) GETSTRUCT(procTuple);
	functyptype = get_typtype(proc->prorettype);
	/*
	 * Disallow pseudotype result  except for TRIGGER, RECORD, VOID, or
	 * polymorphic
	 */
	if (functyptype == TYPTYPE_PSEUDO)
	{
		/* we assume OPAQUE with no arguments means a trigger */
		if (proc->prorettype == TRIGGEROID ||
			(proc->prorettype == OPAQUEOID && proc->pronargs == 0))
			return PLPGSQL_DML_TRIGGER;
#if PG_VERSION_NUM >= 90300
		else if (proc->prorettype == EVTTRIGGEROID)
			return PLPGSQL_EVENT_TRIGGER;
#endif
		else if (proc->prorettype != RECORDOID &&
				 proc->prorettype != VOIDOID &&
				 !IsPolymorphicType(proc->prorettype))
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("PL/pgSQL functions cannot return type %s",
							format_type_be(proc->prorettype))));
	}
	return PLPGSQL_NOT_TRIGGER;
}
/*
 * Process necessary checking before code checking
 *     a) disallow other than plpgsql check function,
 *     b) when function is trigger function, then reloid must be defined
 */
static void
precheck_conditions(HeapTuple procTuple, PLpgSQL_trigtype trigtype, Oid relid)
{
	Form_pg_proc proc;
	Form_pg_language languageStruct;
	HeapTuple	languageTuple;
	char	   *funcname;
	proc = (Form_pg_proc) GETSTRUCT(procTuple);
	funcname = format_procedure(HeapTupleGetOid(procTuple));
	/* used language must be plpgsql */
	languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
	Assert(HeapTupleIsValid(languageTuple));
	languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
	if (strcmp(NameStr(languageStruct->lanname), "plpgsql") != 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("%s is not a plpgsql function", funcname)));
	ReleaseSysCache(languageTuple);
	/* dml trigger needs valid relid, others not */
	if (trigtype == PLPGSQL_DML_TRIGGER)
	{
		if (!OidIsValid(relid))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("missing trigger relation"),
					 errhint("Trigger relation oid must be valid")));
	}
	else
	{
		if (OidIsValid(relid))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("function is not trigger"),
					 errhint("Trigger relation oid must not be valid for non dml trigger function.")));
	}
	pfree(funcname);
}
/*
 * own implementation
 *
 */
static void
check_plpgsql_function(HeapTuple procTuple, Oid relid, PLpgSQL_trigtype trigtype,
					   TupleDesc tupdesc,
					   Tuplestorestate *tupstore,
							    int format,
									  bool fatal_errors,
									  bool other_warnings, 
									  bool performance_warnings,
									  bool extra_warnings)
{
	PLpgSQL_checkstate cstate;
	PLpgSQL_function *volatile function = NULL;
	int			save_nestlevel = 0;
	bool		reload_config;
	Oid			funcoid;
	FunctionCallInfoData fake_fcinfo;
	FmgrInfo	flinfo;
	TriggerData trigdata;
	EventTriggerData etrigdata;
	Trigger tg_trigger;
	int			rc;
	ResourceOwner oldowner;
	PLpgSQL_execstate *cur_estate = NULL;
	MemoryContext old_cxt;
	PLpgSQL_execstate estate;
	ReturnSetInfo rsinfo;
	funcoid = HeapTupleGetOid(procTuple);
	/*
	 * Connect to SPI manager
	 */
	if ((rc = SPI_connect()) != SPI_OK_CONNECT)
		elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
	setup_fake_fcinfo(procTuple, &flinfo, &fake_fcinfo, &rsinfo, &trigdata, relid, &etrigdata,
										  funcoid, trigtype, &tg_trigger);
	setup_cstate(&cstate, funcoid, tupdesc, tupstore,
							    fatal_errors,
							    other_warnings, performance_warnings, extra_warnings,
										    format,
										    true);
	old_cxt = MemoryContextSwitchTo(cstate.check_cxt);
	check_function_prolog(&cstate);
	/*
	 * Copy argument names for later check, only when other warnings are required.
	 * Argument names are used for check parameter versus local variable collision.
	 */
	if (other_warnings)
	{
		int		numargs;
		Oid		   *argtypes;
		char	  **argnames;
		char	   *argmodes;
		int			i;
		numargs = get_func_arg_info(procTuple,
							&argtypes, &argnames, &argmodes);
		if (argnames != NULL)
		{
			for (i = 0; i < numargs; i++)
			{
				if (argnames[i][0] != '\0')
					cstate.argnames = lappend(cstate.argnames, argnames[i]);
			}
		}
	}
	oldowner = CurrentResourceOwner;
	PG_TRY();
	{
		BeginInternalSubTransaction(NULL);
		MemoryContextSwitchTo(cstate.check_cxt);
		save_nestlevel = load_configuration(procTuple, &reload_config);
		/* have to wait for this decision to loaded configuration */
		if (plpgsql_check_mode != PLPGSQL_CHECK_MODE_DISABLED)
		{
			/* Get a compiled function */
			function = plpgsql_compile(&fake_fcinfo, false);
			/* Must save and restore prior value of cur_estate */
			cur_estate = function->cur_estate;
			/* recheck trigtype */
#if PG_VERSION_NUM >= 90300
			Assert(function->fn_is_trigger == trigtype);
#else
#ifdef USE_ASSERT_CHECKING
			if (function->fn_is_trigger)
				Assert(trigtype == PLPGSQL_DML_TRIGGER);
			else
				Assert(trigtype == PLPGSQL_NOT_TRIGGER);
#endif
#endif
			setup_plpgsql_estate(&estate, function, (ReturnSetInfo *) fake_fcinfo.resultinfo);
			cstate.estate = &estate;
			/*
			 * Mark the function as busy, ensure higher than zero usage. There is no
			 * reason for protection function against delete, but I afraid of asserts.
			 */
			function->use_count++;
			/* Create a fake runtime environment and process check */
			switch (trigtype)
			{
				case PLPGSQL_DML_TRIGGER:
					trigger_check(function, (Node *) &trigdata, &estate, &cstate);
					break;
				case PLPGSQL_EVENT_TRIGGER:
					trigger_check(function, (Node *) &etrigdata, &estate, &cstate);
					break;
				case PLPGSQL_NOT_TRIGGER:
					function_check(function, &fake_fcinfo, &estate, &cstate);
					break;
			}
			function->cur_estate = cur_estate;
			function->use_count--;
		}
		else
			elog(NOTICE, "plpgsql_check is disabled");
		/*
		 * reload back a GUC. XXX: isn't this done automatically by subxact
		 * rollback?
		 */
		if (reload_config)
			AtEOXact_GUC(true, save_nestlevel);
		RollbackAndReleaseCurrentSubTransaction();
		MemoryContextSwitchTo(cstate.check_cxt);
		CurrentResourceOwner = oldowner;
		if (OidIsValid(relid))
			relation_close(trigdata.tg_relation, AccessShareLock);
		release_exprs(cstate.exprs);
		SPI_restore_connection();
	}
	PG_CATCH();
	{
		ErrorData  *edata;
		MemoryContextSwitchTo(cstate.check_cxt);
		edata = CopyErrorData();
		FlushErrorState();
		RollbackAndReleaseCurrentSubTransaction();
		MemoryContextSwitchTo(cstate.check_cxt);
		CurrentResourceOwner = oldowner;
		if (OidIsValid(relid))
			relation_close(trigdata.tg_relation, AccessShareLock);
		if (function)
		{
			function->cur_estate = cur_estate;
			function->use_count--;
			release_exprs(cstate.exprs);
		}
		put_error_edata(&cstate, edata);
		/* reconnect spi */
		SPI_restore_connection();
	}
	PG_END_TRY();
	check_function_epilog(&cstate);
	MemoryContextSwitchTo(old_cxt);
	MemoryContextDelete(cstate.check_cxt);
	/*
	 * Disconnect from SPI manager
	 */
	if ((rc = SPI_finish()) != SPI_OK_FINISH)
		elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
}
/*
 * Check function - it prepare variables and starts a prepare plan walker
 *
 */
static void
function_check(PLpgSQL_function *func, FunctionCallInfo fcinfo,
			   PLpgSQL_execstate *estate, PLpgSQL_checkstate *cstate)
{
	int			i;
	int closing = PLPGSQL_CHECK_UNCLOSED;
	List	   *exceptions;
	/*
	 * Make local execution copies of all the datums
	 */
	for (i = 0; i < cstate->estate->ndatums; i++)
		cstate->estate->datums[i] = copy_plpgsql_datum(func->datums[i]);
	/*
	 * Store the actual call argument values (fake) into the appropriate
	 * variables
	 */
	for (i = 0; i < func->fn_nargs; i++)
	{
		init_datum_dno(cstate, func->fn_argvarnos[i]);
	}
	/*
	 * Now check the toplevel block of statements
	 */
	check_stmt(cstate, (PLpgSQL_stmt *) func->action, &closing, &exceptions);
	/* clean state values - next errors are not related to any command */
	cstate->estate->err_stmt = NULL;
	if (closing != PLPGSQL_CHECK_CLOSED && closing != PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS)
		put_error(cstate,
						  ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT, 0,
						  "control reached end of function without RETURN",
						  NULL,
						  NULL,
						  closing == PLPGSQL_CHECK_UNCLOSED ? PLPGSQL_CHECK_ERROR : PLPGSQL_CHECK_WARNING_EXTRA,
						  0, NULL, NULL);
	report_unused_variables(cstate);
}
/*
 * Check trigger - prepare fake environments for testing trigger
 *
 */
static void
trigger_check(PLpgSQL_function *func, Node *tdata,
			  PLpgSQL_execstate *estate, PLpgSQL_checkstate *cstate)
{
	PLpgSQL_rec *rec_new,
			   *rec_old;
	int			i;
	int closing = PLPGSQL_CHECK_UNCLOSED;
	List	   *exceptions;
	/*
	 * Make local execution copies of all the datums
	 */
	for (i = 0; i < cstate->estate->ndatums; i++)
		cstate->estate->datums[i] = copy_plpgsql_datum(func->datums[i]);
	if (IsA(tdata, TriggerData))
	{
		TriggerData *trigdata = (TriggerData *) tdata;
		/*
		 * Put the OLD and NEW tuples into record variables
		 *
		 * We make the tupdescs available in both records even though only one
		 * may have a value.  This allows parsing of record references to
		 * succeed in functions that are used for multiple trigger types. For
		 * example, we might have a test like "if (TG_OP = 'INSERT' and
		 * NEW.foo = 'xyz')", which should parse regardless of the current
		 * trigger type.
		 */
		rec_new = (PLpgSQL_rec *) (cstate->estate->datums[func->new_varno]);
		rec_new->freetup = false;
		rec_new->freetupdesc = false;
		assign_tupdesc_row_or_rec(cstate, NULL, rec_new, trigdata->tg_relation->rd_att, false);
		rec_old = (PLpgSQL_rec *) (cstate->estate->datums[func->old_varno]);
		rec_old->freetup = false;
		rec_old->freetupdesc = false;
		assign_tupdesc_row_or_rec(cstate, NULL, rec_old, trigdata->tg_relation->rd_att, false);
		/*
		 * Assign the special tg_ variables
		 */
		init_datum_dno(cstate, func->tg_op_varno);
		init_datum_dno(cstate, func->tg_name_varno);
		init_datum_dno(cstate, func->tg_when_varno);
		init_datum_dno(cstate, func->tg_level_varno);
		init_datum_dno(cstate, func->tg_relid_varno);
		init_datum_dno(cstate, func->tg_relname_varno);
		init_datum_dno(cstate, func->tg_table_name_varno);
		init_datum_dno(cstate, func->tg_table_schema_varno);
		init_datum_dno(cstate, func->tg_nargs_varno);
		init_datum_dno(cstate, func->tg_argv_varno);
	}
#if PG_VERSION_NUM >= 90300
	else if (IsA(tdata, EventTriggerData))
	{
		init_datum_dno(cstate, func->tg_event_varno);
		init_datum_dno(cstate, func->tg_tag_varno);
	}
#endif
	else
		elog(ERROR, "unexpected environment");
	/*
	 * Now check the toplevel block of statements
	 */
	check_stmt(cstate, (PLpgSQL_stmt *) func->action, &closing, &exceptions);
	/* clean state values - next errors are not related to any command */
	cstate->estate->err_stmt = NULL;
	if (closing != PLPGSQL_CHECK_CLOSED && closing != PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS)
		put_error(cstate,
						  ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT, 0,
						  "control reached end of function without RETURN",
						  NULL,
						  NULL,
						  closing == PLPGSQL_CHECK_UNCLOSED ? PLPGSQL_CHECK_ERROR : PLPGSQL_CHECK_WARNING_EXTRA,
						  0, NULL, NULL);
	report_unused_variables(cstate);
}
/*
 * Loads function's configuration
 *
 * Before checking function we have to load configuration related to
 * function. This is function manager job, but we don't use it for checking.
 */
static int
load_configuration(HeapTuple procTuple, bool *reload_config)
{
	Datum		datum;
	bool		isnull;
	int			new_nest_level;
	*reload_config = false;
	new_nest_level = 0;
	datum = SysCacheGetAttr(PROCOID, procTuple, Anum_pg_proc_proconfig, &isnull);
	if (!isnull)
	{
		ArrayType  *set_items;
		/* Set per-function configuration parameters */
		set_items = DatumGetArrayTypeP(datum);
		if (set_items != NULL)
		{						/* Need a new GUC nesting level */
			new_nest_level = NewGUCNestLevel();
			*reload_config = true;
			ProcessGUCArray(set_items,
							(superuser() ? PGC_SUSET : PGC_USERSET),
							PGC_S_SESSION,
							GUC_ACTION_SAVE);
		}
	}
	return new_nest_level;
}
/*
 * Release all plans created in check time
 *
 */
static void
release_exprs(List *exprs)
{
	ListCell *l;
	foreach(l, exprs)
	{
		PLpgSQL_expr *expr = (PLpgSQL_expr *) lfirst(l);
		SPI_freeplan(expr->plan);
		expr->plan = NULL;
	}
}
/****************************************************************************************
 * Prepare environment
 *
 ****************************************************************************************
 *
 */
static bool
is_polymorphic_tupdesc(TupleDesc tupdesc)
{
	int	i;
	for (i = 0; i < tupdesc->natts; i++)
		if (IsPolymorphicType(TupleDescAttr(tupdesc, i)->atttypid))
			return true;
	return false;
}
/*
 * Set up a fake fcinfo with just enough info to satisfy plpgsql_compile().
 *
 * There should be a different real argtypes for polymorphic params.
 *
 */
static void
setup_fake_fcinfo(HeapTuple procTuple,
						  FmgrInfo *flinfo,
						  FunctionCallInfoData *fcinfo,
						  ReturnSetInfo *rsinfo,
						  TriggerData *trigdata,
						  Oid relid,
						  EventTriggerData *etrigdata,
						  Oid funcoid,
						  PLpgSQL_trigtype trigtype,
						  Trigger *tg_trigger)
{
	Form_pg_proc procform;
	Oid		rettype;
	TupleDesc resultTupleDesc;
	procform = (Form_pg_proc) GETSTRUCT(procTuple);
	rettype = procform->prorettype;
	/* clean structures */
	MemSet(fcinfo, 0, sizeof(FunctionCallInfoData));
	MemSet(flinfo, 0, sizeof(FmgrInfo));
	MemSet(rsinfo, 0, sizeof(ReturnSetInfo));
	fcinfo->flinfo = flinfo;
	flinfo->fn_oid = funcoid;
	flinfo->fn_mcxt = CurrentMemoryContext;
	if (trigtype == PLPGSQL_DML_TRIGGER)
	{
		Assert(trigdata != NULL);
		MemSet(trigdata, 0, sizeof(TriggerData));
		MemSet(tg_trigger, 0, sizeof(Trigger));
		trigdata->type = T_TriggerData;
		trigdata->tg_trigger = tg_trigger;
		fcinfo->context = (Node *) trigdata;
		if (OidIsValid(relid))
			trigdata->tg_relation = relation_open(relid, AccessShareLock);
	}
#if PG_VERSION_NUM >= 90300
	else if (trigtype == PLPGSQL_EVENT_TRIGGER)
	{
		MemSet(etrigdata, 0, sizeof(etrigdata));
		etrigdata->type = T_EventTriggerData;
		fcinfo->context = (Node *) etrigdata;
	}
#endif
	/* 
	 * prepare ReturnSetInfo
	 *
	 * necessary for RETURN NEXT and RETURN QUERY
	 *
	 */
	resultTupleDesc = build_function_result_tupdesc_t(procTuple);
	if (resultTupleDesc)
	{
		/* we cannot to solve polymorphic params now */
		if (is_polymorphic_tupdesc(resultTupleDesc))
		{
			FreeTupleDesc(resultTupleDesc);
			resultTupleDesc = NULL;
		}
	}
	else if (rettype == TRIGGEROID || rettype == OPAQUEOID)
	{
		/* trigger - return value should be ROW or RECORD based on relid */
		if (trigdata->tg_relation)
			resultTupleDesc = CreateTupleDescCopy(trigdata->tg_relation->rd_att);
	}
	else if (!IsPolymorphicType(rettype))
	{
		if (get_typtype(rettype) == TYPTYPE_COMPOSITE)
			resultTupleDesc = lookup_rowtype_tupdesc_copy(rettype, -1);
		else
		{
			resultTupleDesc = CreateTemplateTupleDesc(1, false);
			TupleDescInitEntry(resultTupleDesc,
							    (AttrNumber) 1, "__result__",
							    rettype, -1, 0);
			resultTupleDesc = BlessTupleDesc(resultTupleDesc);
		}
	}
	if (resultTupleDesc)
	{
		fcinfo->resultinfo = (Node *) rsinfo;
		rsinfo->type = T_ReturnSetInfo;
		rsinfo->expectedDesc = resultTupleDesc;
		rsinfo->allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize);
		rsinfo->returnMode = SFRM_ValuePerCall;
		/*
		 * ExprContext is created inside CurrentMemoryContext,
		 * without any additional source allocation. It is released
		 * on end of transaction.
		 */
		rsinfo->econtext = CreateStandaloneExprContext();
	}
}
/*
 * prepare PLpgSQL_checkstate structure
 *
 */
static void
setup_cstate(PLpgSQL_checkstate *cstate,
			 Oid fn_oid,
			 TupleDesc tupdesc,
			 Tuplestorestate *tupstore,
			 bool fatal_errors,
			 bool other_warnings,
			 bool performance_warnings,
			 bool extra_warnings,
			 int format,
			 bool is_active_mode)
{
	cstate->fn_oid = fn_oid;
	cstate->estate = NULL;
	cstate->tupdesc = tupdesc;
	cstate->tuple_store = tupstore;
	cstate->fatal_errors = fatal_errors;
	cstate->other_warnings = other_warnings;
	cstate->performance_warnings = performance_warnings;
	cstate->extra_warnings = extra_warnings;
	cstate->argnames = NIL;
	cstate->exprs = NIL;
	cstate->used_variables = NULL;
	cstate->modif_variables = NULL;
	cstate->top_stmt_stack = NULL;
	cstate->format = format;
	cstate->is_active_mode = is_active_mode;
	cstate->sinfo = NULL;
	cstate->check_cxt = AllocSetContextCreate(CurrentMemoryContext,
										 "plpgsql_check temporary cxt",
										   ALLOCSET_DEFAULT_MINSIZE,
										   ALLOCSET_DEFAULT_INITSIZE,
										   ALLOCSET_DEFAULT_MAXSIZE);
	cstate->found_return_query = false;
}
/* ----------
 * Initialize a plpgsql fake execution state
 * ----------
 */
static void
setup_plpgsql_estate(PLpgSQL_execstate *estate,
					 PLpgSQL_function *func,
					 ReturnSetInfo *rsi)
{
	/* this link will be restored at exit from plpgsql_call_handler */
	func->cur_estate = estate;
	estate->func = func;
	estate->retval = (Datum) 0;
	estate->retisnull = true;
	estate->rettype = InvalidOid;
	estate->fn_rettype = func->fn_rettype;
	estate->retistuple = func->fn_retistuple;
	estate->retisset = func->fn_retset;
	estate->readonly_func = func->fn_readonly;
	estate->rettupdesc = NULL;
	estate->exitlabel = NULL;
	estate->cur_error = NULL;
	estate->tuple_store = NULL;
	if (rsi)
	{
		estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory;
		estate->tuple_store_owner = CurrentResourceOwner;
		if (estate->retisset)
			estate->rettupdesc = rsi->expectedDesc;
	}
	else
	{
		estate->tuple_store_cxt = NULL;
		estate->tuple_store_owner = NULL;
	}
	estate->rsi = rsi;
	estate->found_varno = func->found_varno;
	estate->ndatums = func->ndatums;
	estate->datums = palloc(sizeof(PLpgSQL_datum *) * estate->ndatums);
	/* caller is expected to fill the datums array */
	estate->eval_tuptable = NULL;
	estate->eval_processed = 0;
	estate->eval_lastoid = InvalidOid;
	estate->eval_econtext = NULL;
#if PG_VERSION_NUM < 90500
	estate->cur_expr = NULL;
#endif
	estate->err_stmt = NULL;
	estate->err_text = NULL;
	estate->plugin_info = NULL;
}
/*
 * Initialize plpgsql datum to NULL. This routine is used only for function
 * and trigger parameters so it should not support all dtypes.
 *
 */
static void
init_datum_dno(PLpgSQL_checkstate *cstate, int dno)
{
	switch (cstate->estate->datums[dno]->dtype)
	{
		case PLPGSQL_DTYPE_VAR:
			{
				PLpgSQL_var *var = (PLpgSQL_var *) cstate->estate->datums[dno];
				var->value = (Datum) 0;
				var->isnull = true;
				var->freeval = false;
			}
			break;
		case PLPGSQL_DTYPE_ROW:
			{
				PLpgSQL_row *row = (PLpgSQL_row *) cstate->estate->datums[dno];
				int			fnum;
				for (fnum = 0; fnum < row->nfields; fnum++)
				{
					if (row->varnos[fnum] < 0)
						continue;		/* skip dropped column in row struct */
					init_datum_dno(cstate, row->varnos[fnum]);
				}
			}
			break;
		default:
			elog(ERROR, "unexpected dtype: %d", cstate->estate->datums[dno]->dtype);
	}
}
/*
 * initializing local execution variables
 *
 */
PLpgSQL_datum *
copy_plpgsql_datum(PLpgSQL_datum *datum)
{
	PLpgSQL_datum *result;
	switch (datum->dtype)
	{
		case PLPGSQL_DTYPE_VAR:
			{
				PLpgSQL_var *new = palloc(sizeof(PLpgSQL_var));
				memcpy(new, datum, sizeof(PLpgSQL_var));
				/* Ensure the value is null (possibly not needed?) */
				new->value = 0;
				new->isnull = true;
				new->freeval = false;
				result = (PLpgSQL_datum *) new;
			}
			break;
		case PLPGSQL_DTYPE_REC:
			{
				PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec));
				memcpy(new, datum, sizeof(PLpgSQL_rec));
				/* Ensure the value is null (possibly not needed?) */
				new->tup = NULL;
				new->tupdesc = NULL;
				new->freetup = false;
				new->freetupdesc = false;
				result = (PLpgSQL_datum *) new;
			}
			break;
		case PLPGSQL_DTYPE_ROW:
		case PLPGSQL_DTYPE_RECFIELD:
		case PLPGSQL_DTYPE_ARRAYELEM:
			/*
			 * These datum records are read-only at runtime, so no need to
			 * copy them (well, ARRAYELEM contains some cached type data, but
			 * we'd just as soon centralize the caching anyway)
			 */
			result = datum;
			break;
		default:
			elog(ERROR, "unrecognized dtype: %d", datum->dtype);
			result = NULL;		/* keep compiler quiet */
			break;
	}
	return result;
}
/****************************************************************************************
 * Extended check walker
 *
 ****************************************************************************************
 *
 */
/*
 * walk over all plpgsql statements - search and check expressions
 *
 */
static void
check_stmt(PLpgSQL_checkstate *cstate, PLpgSQL_stmt *stmt, int *closing, List **exceptions)
{
	TupleDesc	tupdesc = NULL;
	PLpgSQL_function *func;
	ListCell   *l;
	ResourceOwner oldowner;
	MemoryContext oldCxt = CurrentMemoryContext;
	PLpgSQL_stmt_stack_item *outer_stmt;
	if (stmt == NULL)
		return;
	cstate->estate->err_stmt = stmt;
	func = cstate->estate->func;
	/*
	 * Attention - returns NULL, when there are not any outer level
	 */
	outer_stmt = push_stmt_to_stmt_stack(cstate);
	oldowner = CurrentResourceOwner;
	BeginInternalSubTransaction(NULL);
	MemoryContextSwitchTo(oldCxt);
	PG_TRY();
	{
		switch (PLPGSQL_STMT_TYPES stmt->cmd_type)
		{
			case PLPGSQL_STMT_BLOCK:
				{
					PLpgSQL_stmt_block *stmt_block = (PLpgSQL_stmt_block *) stmt;
					int			i;
					PLpgSQL_datum *d;
					for (i = 0; i < stmt_block->n_initvars; i++)
					{
						char	   *refname;
						d = func->datums[stmt_block->initvarnos[i]];
						if (d->dtype == PLPGSQL_DTYPE_VAR)
						{
							PLpgSQL_var *var = (PLpgSQL_var *) d;
							check_expr(cstate, var->default_val);
						}
						refname = datum_get_refname(d);
						if (refname != NULL)
						{
							ListCell   *l;
							foreach(l, cstate->argnames)
							{
								char	   *argname = (char *) lfirst(l);
								if (strcmp(argname, refname) == 0)
								{
									StringInfoData str;
									initStringInfo(&str);
									appendStringInfo(&str, "parameter \"%s\" is overlapped",
													 refname);
									put_error(cstate,
												  0, 0,
												  str.data,
												  "Local variable overlap function parameter.",
												  NULL,
												  PLPGSQL_CHECK_WARNING_OTHERS,
												  0, NULL, NULL);
									pfree(str.data);
								}
							}
							if (found_shadowed_variable(refname, outer_stmt, cstate))
							{
								StringInfoData str;
								initStringInfo(&str);
								appendStringInfo(&str, "variable \"%s\" shadows a previously defined variable",
													 refname);
								put_error(cstate,
												  0, 0,
												  str.data,
												  NULL,
												  "SET plpgsql.extra_warnings TO 'shadowed_variables'",
												  PLPGSQL_CHECK_WARNING_EXTRA,
												  0, NULL, NULL);
								pfree(str.data);
							}
						}
					}
					check_stmts(cstate, stmt_block->body, closing, exceptions);
					if (stmt_block->exceptions)
					{
						int closing_local;
						List   *exceptions_local = NIL;
						int		closing_handlers = PLPGSQL_CHECK_UNKNOWN;
						List   *exceptions_transformed = NIL;
						if (*closing == PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS)
						{
							ListCell   *lc;
							int			i = 0;
							int    *err_codes = NULL;
							int		nerr_codes = 0;
							/* copy errcodes to a array */
							nerr_codes = list_length(*exceptions);
							err_codes = palloc(sizeof(int) * nerr_codes);
							foreach(lc, *exceptions)
							{
								err_codes[i++] = lfirst_int(lc);
							}
							foreach(l, stmt_block->exceptions->exc_list)
							{
								PLpgSQL_exception *exception = (PLpgSQL_exception *) lfirst(l);
								/* RETURN in exception handler ~ is possible closing */
								check_stmts(cstate, exception->action,
												&closing_local, &exceptions_local);
								if (*exceptions != NIL)
								{
									int		i;
									for (i = 0; i < nerr_codes; i++)
									{
										int		err_code = err_codes[i];
										if (err_code != -1 &&
											exception_matches_conditions(err_code, exception->conditions))
										{
											closing_handlers = merge_closing(closing_handlers, closing_local,
																			 &exceptions_transformed, exceptions_local,
																			 err_code);
											*exceptions = list_delete_int(*exceptions, err_code);
											err_codes[i] = -1;
										}
									}
								}
							}
							Assert(err_codes != NULL);
							pfree(err_codes);
							if (closing_handlers != PLPGSQL_CHECK_UNKNOWN)
							{
								*closing = closing_handlers;
								if (closing_handlers == PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS)
									*exceptions = list_concat_unique_int(*exceptions, exceptions_transformed);
								else
									*exceptions = NIL;
							}
						}
						else
						{
							foreach(l, stmt_block->exceptions->exc_list)
							{
								PLpgSQL_exception *exception = (PLpgSQL_exception *) lfirst(l);
								/* RETURN in exception handler ~ it is possible closing only */
								check_stmts(cstate, exception->action,
												&closing_local, &exceptions_local);
								closing_handlers = merge_closing(closing_handlers, closing_local,
																 &exceptions_transformed, exceptions_local,
																 -1);
							}
							if (closing_handlers != *closing)
								*closing = PLPGSQL_CHECK_POSSIBLY_CLOSED;
						}
						/*
						 * Mark the hidden variables SQLSTATE and SQLERRM used
						 * even if they actually weren't.  Not using them
						 * should practically never be a sign of a problem, so
						 * there's no point in annoying the user.
						 */
						record_variable_usage(cstate, stmt_block->exceptions->sqlstate_varno, false);
						record_variable_usage(cstate, stmt_block->exceptions->sqlerrm_varno, false);
					}
				}
				break;
#if PG_VERSION_NUM >= 90500
			case PLPGSQL_STMT_ASSERT:
				{
					PLpgSQL_stmt_assert *stmt_assert = (PLpgSQL_stmt_assert *) stmt;
					/*
					 * Should or should not to depends on plpgsql_check_asserts?
					 * I am thinking, so any code (active or inactive) should be valid,
					 * so I ignore plpgsql_check_asserts option.
					 */
					check_expr_with_expected_scalar_type(cstate,
									 stmt_assert->cond, BOOLOID, true);
					if (stmt_assert->message != NULL)
						check_expr(cstate, stmt_assert->message);
				}
				break;
#endif
			case PLPGSQL_STMT_ASSIGN:
				{
					PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt;
					check_assignment(cstate, stmt_assign->expr, NULL, NULL,
									 stmt_assign->varno);
				}
				break;
			case PLPGSQL_STMT_IF:
				{
					PLpgSQL_stmt_if	*stmt_if = (PLpgSQL_stmt_if *) stmt;
					ListCell    *l;
					int		closing_local;
					int		closing_all_paths = PLPGSQL_CHECK_UNKNOWN;
					List   *exceptions_local;
					check_expr_with_expected_scalar_type(cstate,
									     stmt_if->cond, BOOLOID, true);
					check_stmts(cstate, stmt_if->then_body, &closing_local,
								&exceptions_local);
					closing_all_paths = merge_closing(closing_all_paths,
													  closing_local,
													  exceptions,
													  exceptions_local,
													  -1);
					foreach(l, stmt_if->elsif_list)
					{
						PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l);
						check_expr_with_expected_scalar_type(cstate,
										     elif->cond, BOOLOID, true);
						check_stmts(cstate, elif->stmts, &closing_local,
									&exceptions_local);
						closing_all_paths = merge_closing(closing_all_paths,
														  closing_local,
														  exceptions,
														  exceptions_local,
														  -1);
					}
					check_stmts(cstate, stmt_if->else_body, &closing_local,
								&exceptions_local);
					closing_all_paths = merge_closing(closing_all_paths,
													  closing_local,
													  exceptions,
													  exceptions_local,
													  -1);
					if (stmt_if->else_body != NULL)
						*closing = closing_all_paths;
					else if (closing_all_paths == PLPGSQL_CHECK_UNCLOSED)
						*closing = PLPGSQL_CHECK_UNCLOSED;
					else
						*closing = PLPGSQL_CHECK_POSSIBLY_CLOSED;
				}
				break;
			case PLPGSQL_STMT_CASE:
				{
					PLpgSQL_stmt_case *stmt_case = (PLpgSQL_stmt_case *) stmt;
					Oid			result_oid;
					int		closing_local;
					List	*exceptions_local;
					int		closing_all_paths = PLPGSQL_CHECK_UNKNOWN;
					if (stmt_case->t_expr != NULL)
					{
						PLpgSQL_var *t_var = (PLpgSQL_var *) cstate->estate->datums[stmt_case->t_varno];
						/*
						 * we need to set hidden variable type
						 */
						prepare_expr(cstate, stmt_case->t_expr, 0);
						tupdesc = expr_get_desc(cstate,
												stmt_case->t_expr,
												false,	/* no element type */
												true,	/* expand record */
												true,	/* is expression */
												NULL);
						result_oid = TupleDescAttr(tupdesc, 0)->atttypid;
						/*
						 * When expected datatype is different from real,
						 * change it. Note that what we're modifying here is
						 * an execution copy of the datum, so this doesn't
						 * affect the originally stored function parse tree.
						 */
						if (t_var->datatype->typoid != result_oid)
							t_var->datatype = plpgsql_build_datatype(result_oid,
																	 -1,
								   cstate->estate->func->fn_input_collation);
						ReleaseTupleDesc(tupdesc);
					}
					foreach(l, stmt_case->case_when_list)
					{
						PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
						check_expr(cstate, cwt->expr);
						check_stmts(cstate, cwt->stmts, &closing_local, &exceptions_local);
						closing_all_paths = merge_closing(closing_all_paths,
														  closing_local,
														  exceptions,
														  exceptions_local,
														  -1);
					}
					if (stmt_case->else_stmts)
					{
						check_stmts(cstate, stmt_case->else_stmts, &closing_local, &exceptions_local);
						*closing = merge_closing(closing_all_paths,
														  closing_local,
														  exceptions,
														  exceptions_local,
														  -1);
					}
					else
						/* is not ensured all path evaluation */
						*closing = possibly_closed(closing_all_paths);
				}
				break;
			case PLPGSQL_STMT_LOOP:
				check_stmts(cstate, ((PLpgSQL_stmt_loop *) stmt)->body, closing, exceptions);
				break;
			case PLPGSQL_STMT_WHILE:
				{
					PLpgSQL_stmt_while *stmt_while = (PLpgSQL_stmt_while *) stmt;
					int		closing_local;
					List   *exceptions_local;
					check_expr_with_expected_scalar_type(cstate,
										     stmt_while->cond,
										     BOOLOID,
										     true);
					check_expr(cstate, stmt_while->cond);
					/*
					 * When is not guaranteed execution (possible zero loops),
					 * then ignore closing info from body.
					 */
					check_stmts(cstate, stmt_while->body, &closing_local, &exceptions_local);
					*closing = possibly_closed(closing_local);
				}
				break;
			case PLPGSQL_STMT_FORI:
				{
					PLpgSQL_stmt_fori *stmt_fori = (PLpgSQL_stmt_fori *) stmt;
					int			dno = stmt_fori->var->dno;
					int		closing_local;
					List   *exceptions_local;
					/* prepare plan if desn't exist yet */
					check_assignment(cstate, stmt_fori->lower, NULL, NULL, dno);
					check_assignment(cstate, stmt_fori->upper, NULL, NULL, dno);
					if (stmt_fori->step)
						check_assignment(cstate, stmt_fori->step, NULL, NULL, dno);
					check_stmts(cstate, stmt_fori->body, &closing_local, &exceptions_local);
					*closing = possibly_closed(closing_local);
				}
				break;
			case PLPGSQL_STMT_FORS:
				{
					PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt;
					int		closing_local;
					List   *exceptions_local;
					check_row_or_rec(cstate, stmt_fors->row, stmt_fors->rec);
					/* we need to set hidden variable type */
					check_assignment(cstate, stmt_fors->query,
									 stmt_fors->rec, stmt_fors->row, -1);
					check_stmts(cstate, stmt_fors->body, &closing_local, &exceptions_local);
					*closing = possibly_closed(closing_local);
				}
				break;
			case PLPGSQL_STMT_FORC:
				{
					PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt;
					PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_forc->curvar];
					int		closing_local;
					List   *exceptions_local;
					check_row_or_rec(cstate, stmt_forc->row, stmt_forc->rec);
					check_expr(cstate, stmt_forc->argquery);
					if (var->cursor_explicit_expr != NULL)
						check_assignment(cstate, var->cursor_explicit_expr,
										 stmt_forc->rec, stmt_forc->row, -1);
					check_stmts(cstate, stmt_forc->body, &closing_local, &exceptions_local);
					*closing = possibly_closed(closing_local);
					cstate->used_variables = bms_add_member(cstate->used_variables,
										 stmt_forc->curvar);
				}
				break;
			case PLPGSQL_STMT_DYNFORS:
				{
					PLpgSQL_stmt_dynfors *stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt;
					int		closing_local;
					List   *exceptions_local;
					check_row_or_rec(cstate, stmt_dynfors->row, stmt_dynfors->rec);
					check_expr(cstate, stmt_dynfors->query);
					foreach(l, stmt_dynfors->params)
					{
						check_expr(cstate, (PLpgSQL_expr *) lfirst(l));
					}
					if (stmt_dynfors->rec != NULL)
					{
						put_error(cstate,
									  0, 0,
								"cannot determinate a result of dynamic SQL",
									  "Cannot to contine in check.",
					  "Don't use dynamic SQL and record type together, when you would check function.",
									  PLPGSQL_CHECK_WARNING_OTHERS,
									  0, NULL, NULL);
					}
					check_stmts(cstate, stmt_dynfors->body, &closing_local, &exceptions_local);
					*closing = possibly_closed(closing_local);
				}
				break;
			case PLPGSQL_STMT_FOREACH_A:
				{
					PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt;
					bool use_element_type;
					int		closing_local;
					List   *exceptions_local;
					check_target(cstate, stmt_foreach_a->varno, NULL, NULL);
					/*
					 * When slice > 0, then result and target are a array.
					 * We shoudl to disable a array element refencing.
					 */
					use_element_type = stmt_foreach_a->slice == 0;
					check_assignment_with_possible_slices(cstate,
											 stmt_foreach_a->expr, 
											 NULL, NULL,
											 stmt_foreach_a->varno,
											 use_element_type);
					check_stmts(cstate, stmt_foreach_a->body, &closing_local, &exceptions_local);
					*closing = possibly_closed(closing_local);
				}
				break;
			case PLPGSQL_STMT_EXIT:
				{
					PLpgSQL_stmt_exit *stmt_exit = (PLpgSQL_stmt_exit *) stmt;
					check_expr(cstate, stmt_exit->cond);
					if (stmt_exit->label != NULL)
					{
						PLpgSQL_stmt *labeled_stmt = find_stmt_with_label(stmt_exit->label,
												    outer_stmt);
						if (labeled_stmt == NULL)
							ereport(ERROR,
								(errcode(ERRCODE_SYNTAX_ERROR),
								 errmsg("label \"%s\" does not exist", stmt_exit->label)));
						/* CONTINUE only allows loop labels */
						if (!is_any_loop_stmt(labeled_stmt) && !stmt_exit->is_exit)
							ereport(ERROR,
									(errcode(ERRCODE_SYNTAX_ERROR),
									 errmsg("block label \"%s\" cannot be used in CONTINUE",
									 stmt_exit->label)));
					}
					else
					{
						if (find_nearest_loop(outer_stmt) == NULL)
							ereport(ERROR,
								(errcode(ERRCODE_SYNTAX_ERROR),
								 errmsg("%s cannot be used outside a loop",
								 plpgsql_stmt_typename((PLpgSQL_stmt *) stmt_exit))));
					}
				}
				break;
			case PLPGSQL_STMT_PERFORM:
				check_expr(cstate, ((PLpgSQL_stmt_perform *) stmt)->expr);
				break;
			case PLPGSQL_STMT_RETURN:
				{
					PLpgSQL_stmt_return *stmt_rt = (PLpgSQL_stmt_return *) stmt;
					if (stmt_rt->retvarno >= 0)
					{
						PLpgSQL_datum *retvar = cstate->estate->datums[stmt_rt->retvarno];
						PLpgSQL_execstate *estate = cstate->estate;
						cstate->used_variables = bms_add_member(cstate->used_variables, stmt_rt->retvarno);
						switch (retvar->dtype)
						{
							case PLPGSQL_DTYPE_VAR:
								{
									PLpgSQL_var *var = (PLpgSQL_var *) retvar;
									check_assign_to_target_type(cstate,
										 cstate->estate->func->fn_rettype, -1,
										 var->datatype->typoid, false);
								}
								break;
							case PLPGSQL_DTYPE_REC:
								{
									PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar;
									if (rec->tupdesc && estate->rsi && IsA(estate->rsi, ReturnSetInfo))
									{
										TupleDesc	rettupdesc = estate->rsi->expectedDesc;
										TupleConversionMap *tupmap ;
										tupmap = convert_tuples_by_position(rec->tupdesc, rettupdesc,
											 gettext_noop("returned record type does not match expected record type"));
										if (tupmap)
											free_conversion_map(tupmap);
									}
								}
								break;
							case PLPGSQL_DTYPE_ROW:
								{
									PLpgSQL_row *row = (PLpgSQL_row *) retvar;
									if (row->rowtupdesc && estate->rsi && IsA(estate->rsi, ReturnSetInfo))
									{
										TupleDesc	rettupdesc = estate->rsi->expectedDesc;
										TupleConversionMap *tupmap ;
										tupmap = convert_tuples_by_position(row->rowtupdesc, rettupdesc,
											 gettext_noop("returned record type does not match expected record type"));
										if (tupmap)
											free_conversion_map(tupmap);
									}
								}
								break;
							default:
								;		/* nope */
						}
					}
					*closing = PLPGSQL_CHECK_CLOSED;
					if (stmt_rt->expr)
						check_returned_expr(cstate, stmt_rt->expr, true);
				}
				break;
			case PLPGSQL_STMT_RETURN_NEXT:
				{
					PLpgSQL_stmt_return_next *stmt_rn = (PLpgSQL_stmt_return_next *) stmt;
					if (stmt_rn->retvarno >= 0)
					{
						PLpgSQL_datum *retvar = cstate->estate->datums[stmt_rn->retvarno];
						PLpgSQL_execstate *estate = cstate->estate;
						TupleDesc	tupdesc;
						int		natts;
						cstate->used_variables = bms_add_member(cstate->used_variables, stmt_rn->retvarno);
						if (!estate->retisset)
							ereport(ERROR,
									(errcode(ERRCODE_SYNTAX_ERROR),
									 errmsg("cannot use RETURN NEXT in a non-SETOF function")));
						tupdesc = estate->rettupdesc;
						natts = tupdesc ? tupdesc->natts : 0;
	
						switch (retvar->dtype)
						{
							case PLPGSQL_DTYPE_VAR:
								{
									PLpgSQL_var *var = (PLpgSQL_var *) retvar;
									if (natts > 1)
										ereport(ERROR,
												(errcode(ERRCODE_DATATYPE_MISMATCH),
												 errmsg("wrong result type supplied in RETURN NEXT")));
									check_assign_to_target_type(cstate,
										 cstate->estate->func->fn_rettype, -1,
										 var->datatype->typoid, false);
								}
								break;
							case PLPGSQL_DTYPE_REC:
								{
									PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar;
									TupleConversionMap *tupmap;
									if (!HeapTupleIsValid(rec->tup))
										ereport(ERROR,
												  (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
												   errmsg("record \"%s\" is not assigned yet",
												   rec->refname),
										errdetail("The tuple structure of a not-yet-assigned"
															  " record is indeterminate.")));
									if (tupdesc)
									{
										tupmap = convert_tuples_by_position(rec->tupdesc,
																tupdesc,
											gettext_noop("wrong record type supplied in RETURN NEXT"));
										if (tupmap)
											free_conversion_map(tupmap);
									}
								}
								break;
							case PLPGSQL_DTYPE_ROW:
								{
									PLpgSQL_row *row = (PLpgSQL_row *) retvar;
									bool	row_is_valid_result;
									row_is_valid_result = true;
									if (tupdesc)
									{
										if (row->nfields == natts)
										{
											int		i;
											for (i = 0; i < natts; i++)
											{
												PLpgSQL_var *var;
												if (TupleDescAttr(tupdesc, i)->attisdropped)
													continue;
												if (row->varnos[i] < 0)
													elog(ERROR, "dropped rowtype entry for non-dropped column");
												var = (PLpgSQL_var *) (cstate->estate->datums[row->varnos[i]]);
												if (var->datatype->typoid != TupleDescAttr(tupdesc, i)->atttypid)
												{
													row_is_valid_result = false;
													break;
												}
											}
										}
										else
											row_is_valid_result = false;
										if (!row_is_valid_result)
											ereport(ERROR,
													(errcode(ERRCODE_DATATYPE_MISMATCH),
											errmsg("wrong record type supplied in RETURN NEXT")));
									}
								}
								break;
							default:
								;		/* nope */
						}
					}
					if (stmt_rn->expr)
						check_returned_expr(cstate, stmt_rn->expr, true);
				}
				break;
			case PLPGSQL_STMT_RETURN_QUERY:
				{
					PLpgSQL_stmt_return_query *stmt_rq = (PLpgSQL_stmt_return_query *) stmt;
					check_expr(cstate, stmt_rq->dynquery);
					if (stmt_rq->query)
					{
						check_returned_expr(cstate, stmt_rq->query, false);
						cstate->found_return_query = true;
					}
					foreach(l, stmt_rq->params)
					{
						check_expr(cstate, (PLpgSQL_expr *) lfirst(l));
					}
				}
				break;
			case PLPGSQL_STMT_RAISE:
				{
					PLpgSQL_stmt_raise *stmt_raise = (PLpgSQL_stmt_raise *) stmt;
					ListCell   *current_param;
					char	   *cp;
					int			err_code = 0;
					if (stmt_raise->condname != NULL)
						err_code = plpgsql_recognize_err_condition(stmt_raise->condname, true);
					foreach(l, stmt_raise->params)
					{
						check_expr(cstate, (PLpgSQL_expr *) lfirst(l));
					}
					foreach(l, stmt_raise->options)
					{
						PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(l);
						check_expr(cstate, opt->expr);
						if (opt->opt_type == PLPGSQL_RAISEOPTION_ERRCODE)
						{
							bool		IsConst;
							char	   *value = ExprGetString(opt->expr, &IsConst);
							if (IsConst && value != NULL)
								err_code = plpgsql_recognize_err_condition(value, true);
							else
								err_code = -1;		/* cannot be calculated now */
						}
					}
					current_param = list_head(stmt_raise->params);
					/* ensure any single % has a own parameter */
					if (stmt_raise->message != NULL)
					{
						for (cp = stmt_raise->message; *cp; cp++)
						{
							if (cp[0] == '%')
							{
								if (cp[1] == '%')
								{
									cp++;
									continue;
								}
								if (current_param == NULL)
									ereport(ERROR,
											(errcode(ERRCODE_SYNTAX_ERROR),
											 errmsg("too few parameters specified for RAISE")));
								current_param = lnext(current_param);
							}
						}
					}
					if (current_param != NULL)
						ereport(ERROR,
								(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("too many parameters specified for RAISE")));
					if (stmt_raise->elog_level >= ERROR)
					{
						*closing = PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS;
						if (err_code == 0)
							err_code = ERRCODE_RAISE_EXCEPTION;
						else if (err_code == -1)
							err_code = 0; /* cannot be calculated */
						*exceptions = list_make1_int(err_code);
					}
					/* without any parameters it is reRAISE */
					if (stmt_raise->condname == NULL && stmt_raise->message == NULL &&
						stmt_raise->options == NIL)
					{
						*closing = PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS;
						/* should be enhanced in future */
						*exceptions = list_make1_int(-2); /* reRAISE */
					}
				}
				break;
			case PLPGSQL_STMT_EXECSQL:
				{
					PLpgSQL_stmt_execsql *stmt_execsql = (PLpgSQL_stmt_execsql *) stmt;
					if (stmt_execsql->into)
					{
						check_row_or_rec(cstate, stmt_execsql->row, stmt_execsql->rec);
						check_assignment(cstate, stmt_execsql->sqlstmt,
								   stmt_execsql->rec, stmt_execsql->row, -1);
					}
					else
						/* only statement */
						check_expr_as_sqlstmt_nodata(cstate, stmt_execsql->sqlstmt);
				}
				break;
			case PLPGSQL_STMT_DYNEXECUTE:
				{
					PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt;
					check_expr(cstate, stmt_dynexecute->query);
					foreach(l, stmt_dynexecute->params)
					{
						check_expr(cstate, (PLpgSQL_expr *) lfirst(l));
					}
					if (stmt_dynexecute->into)
					{
						check_row_or_rec(cstate, stmt_dynexecute->row, stmt_dynexecute->rec);
						if (stmt_dynexecute->rec != NULL)
						{
							put_error(cstate,
										  0, 0,
								"cannot determinate a result of dynamic SQL",
										  "Cannot to contine in check.",
						  "Don't use dynamic SQL and record type together, when you would check function.",
										  PLPGSQL_CHECK_WARNING_OTHERS,
										  0, NULL, NULL);
						}
					}
				}
				break;
			case PLPGSQL_STMT_OPEN:
				{
					PLpgSQL_stmt_open *stmt_open = (PLpgSQL_stmt_open *) stmt;
					PLpgSQL_var *var = (PLpgSQL_var *) (cstate->estate->datums[stmt_open->curvar]);
					if (var->cursor_explicit_expr)
						check_expr(cstate, var->cursor_explicit_expr);
					check_expr(cstate, stmt_open->query);
					if (var != NULL && stmt_open->query != NULL)
						var->cursor_explicit_expr = stmt_open->query;
					check_expr(cstate, stmt_open->argquery);
					check_expr(cstate, stmt_open->dynquery);
					foreach(l, stmt_open->params)
					{
						check_expr(cstate, (PLpgSQL_expr *) lfirst(l));
					}
					cstate->used_variables = bms_add_member(cstate->used_variables,
									 stmt_open->curvar);
				}
				break;
			case PLPGSQL_STMT_GETDIAG:
				{
					PLpgSQL_stmt_getdiag *stmt_getdiag = (PLpgSQL_stmt_getdiag *) stmt;
					ListCell   *lc;
					foreach(lc, stmt_getdiag->diag_items)
					{
						PLpgSQL_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc);
						check_target(cstate, diag_item->target, NULL, NULL);
					}
				}
				break;
			case PLPGSQL_STMT_FETCH:
				{
					PLpgSQL_stmt_fetch *stmt_fetch = (PLpgSQL_stmt_fetch *) stmt;
					PLpgSQL_var *var = (PLpgSQL_var *) (cstate->estate->datums[stmt_fetch->curvar]);
					check_row_or_rec(cstate, stmt_fetch->row, stmt_fetch->rec);
					if (var != NULL && var->cursor_explicit_expr != NULL)
						check_assignment(cstate, var->cursor_explicit_expr,
									   stmt_fetch->rec, stmt_fetch->row, -1);
					cstate->used_variables = bms_add_member(cstate->used_variables, stmt_fetch->curvar);
				}
				break;
			case PLPGSQL_STMT_CLOSE:
				cstate->used_variables = bms_add_member(cstate->used_variables,
								 ((PLpgSQL_stmt_close *) stmt)->curvar);
				break;
			default:
				elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
		}
		pop_stmt_from_stmt_stack(cstate);
		RollbackAndReleaseCurrentSubTransaction();
		MemoryContextSwitchTo(oldCxt);
		CurrentResourceOwner = oldowner;
		SPI_restore_connection();
	}
	PG_CATCH();
	{
		ErrorData  *edata;
		MemoryContextSwitchTo(oldCxt);
		edata = CopyErrorData();
		FlushErrorState();
		RollbackAndReleaseCurrentSubTransaction();
		MemoryContextSwitchTo(oldCxt);
		CurrentResourceOwner = oldowner;
		pop_stmt_from_stmt_stack(cstate);
		/*
		 * If fatal_errors is true, we just propagate the error up to the
		 * highest level. Otherwise the error is appended to our current list
		 * of errors, and we continue checking.
		 */
		if (cstate->fatal_errors)
			ReThrowError(edata);
		else
			put_error_edata(cstate, edata);
		MemoryContextSwitchTo(oldCxt);
		/* reconnect spi */
		SPI_restore_connection();
	}
	PG_END_TRY();
}
/*
 * Ensure check for all statements in list
 *
 */
static void
check_stmts(PLpgSQL_checkstate *cstate, List *stmts, int *closing, List **exceptions)
{
	ListCell   *lc;
	int			closing_local;
	List	   *exceptions_local;
	bool		dead_code_alert = false;
	*closing = PLPGSQL_CHECK_UNCLOSED;
	*exceptions = NIL;
	foreach(lc, stmts)
	{
		PLpgSQL_stmt	   *stmt = (PLpgSQL_stmt *) lfirst(lc);
		closing_local = PLPGSQL_CHECK_UNCLOSED;
		exceptions_local = NIL;
		check_stmt(cstate, stmt, &closing_local, &exceptions_local);
		if (dead_code_alert)
		{
			put_error(cstate,
						  0, stmt->lineno,
						  "unreachable code",
						  NULL,
						  NULL,
						  PLPGSQL_CHECK_WARNING_EXTRA,
						  0, NULL, NULL);
			/* don't raise this warning every line */
			dead_code_alert = false;
		}
		if (closing_local == PLPGSQL_CHECK_CLOSED)
		{
			dead_code_alert = true;
			*closing = PLPGSQL_CHECK_CLOSED;
			*exceptions = NIL;
		}
		else if (closing_local == PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS)
		{
			dead_code_alert = true;
			if (*closing == PLPGSQL_CHECK_UNCLOSED ||
				*closing == PLPGSQL_CHECK_POSSIBLY_CLOSED ||
				*closing == PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS)
			{
				*closing = PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS;
				*exceptions = exceptions_local;
			}
		}
		else if (closing_local == PLPGSQL_CHECK_POSSIBLY_CLOSED)
		{
			if (*closing == PLPGSQL_CHECK_UNCLOSED)
			{
				*closing = PLPGSQL_CHECK_POSSIBLY_CLOSED;
				*exceptions = NIL;
			}
		}
	}
}
static int
possibly_closed(int c)
{
	switch (c)
	{
		case PLPGSQL_CHECK_CLOSED:
		case PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS:
		case PLPGSQL_CHECK_POSSIBLY_CLOSED:
			return PLPGSQL_CHECK_POSSIBLY_CLOSED;
		default:
			return PLPGSQL_CHECK_UNCLOSED;
	}
}
static int
merge_closing(int c, int c_local, List **exceptions, List *exceptions_local, int err_code)
{
	*exceptions = NIL;
	if (c == PLPGSQL_CHECK_UNKNOWN)
	{
		if (c_local == PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS)
			*exceptions = exceptions_local;
		return c_local;
	}
	if (c_local == PLPGSQL_CHECK_UNKNOWN)
		return c;
	if (c == c_local)
	{
		if (c == PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS)
		{
			if (err_code != -1)
			{
				ListCell *lc;
				/* replace reRAISE symbol (-2) by real err_code */
				foreach(lc, exceptions_local)
				{
					int		t_err_code = lfirst_int(lc);
					*exceptions = list_append_unique_int(*exceptions,
														t_err_code != -2 ? t_err_code : err_code);
				}
			}
			else
				*exceptions = list_concat_unique_int(*exceptions, exceptions_local);
		}
		return c_local;
	}
	if (c == PLPGSQL_CHECK_CLOSED || c_local == PLPGSQL_CHECK_CLOSED)
	{
		if (c == PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS ||
			c_local == PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS)
		return PLPGSQL_CHECK_CLOSED;
	}
	return PLPGSQL_CHECK_POSSIBLY_CLOSED;
}
static bool
exception_matches_conditions(int sqlerrstate, PLpgSQL_condition *cond)
{
	for (; cond != NULL; cond = cond->next)
	{
		int			_sqlerrstate = cond->sqlerrstate;
		/*
		 * OTHERS matches everything *except* query-canceled and
		 * assert-failure.  If you're foolish enough, you can match those
		 * explicitly.
		 */
		if (_sqlerrstate == 0)
		{
			if (sqlerrstate != ERRCODE_QUERY_CANCELED
#if PG_VERSION_NUM >= 90500
				 && sqlerrstate != ERRCODE_ASSERT_FAILURE
#endif
				)
				return true;
		}
		/* Exact match? */
		else if (sqlerrstate == _sqlerrstate)
			return true;
		/* Category match? */
		else if (ERRCODE_IS_CATEGORY(_sqlerrstate) &&
				 ERRCODE_TO_CATEGORY(sqlerrstate) == _sqlerrstate)
			return true;
	}
	return false;
}
/*
 * Verify a expression
 *
 */
static void
check_expr(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr)
{
	if (expr)
		check_expr_as_rvalue(cstate, expr, NULL, NULL, -1, false, false);
}
static void
record_variable_usage(PLpgSQL_checkstate *cstate, int dno, bool write)
{
	if (dno >= 0)
	{
		cstate->used_variables = bms_add_member(cstate->used_variables, dno);
		if (write)
			cstate->modif_variables = bms_add_member(cstate->modif_variables, dno);
	}
}
/*
 * Returns true if dno is explicitly declared. It should not be used
 * for arguments.
 */
static bool
datum_is_explicit(PLpgSQL_checkstate *cstate, int dno)
{
	PLpgSQL_execstate *estate = cstate->estate;
	switch (estate->datums[dno]->dtype)
	{
		case PLPGSQL_DTYPE_VAR:
		case PLPGSQL_DTYPE_ROW:
		case PLPGSQL_DTYPE_REC:
			return ((PLpgSQL_variable *) estate->datums[dno])->lineno > 0;
		default:
			return false;
	}
}
/*
 * returns true, when datum or some child is used
 */
static bool
datum_is_used(PLpgSQL_checkstate *cstate, int dno, bool write)
{
	PLpgSQL_execstate *estate = cstate->estate;
	switch (estate->datums[dno]->dtype)
	{
		case PLPGSQL_DTYPE_VAR:
			{
				return bms_is_member(dno,
						write ? cstate->modif_variables : cstate->used_variables);
			}
			break;
		case PLPGSQL_DTYPE_ROW:
			{
				PLpgSQL_row *row = (PLpgSQL_row *) estate->datums[dno];
				int	     i;
				if (bms_is_member(dno,
						  write ? cstate->modif_variables : cstate->used_variables))
					return true;
				for (i = 0; i < row->nfields; i++)
				{
					if (row->varnos[i] < 0)
						continue;
					if (datum_is_used(cstate, row->varnos[i], write))
						return true;
				}
				return false;
			}
			break;
		case PLPGSQL_DTYPE_REC:
			{
				PLpgSQL_rec *rec = (PLpgSQL_rec *) estate->datums[dno];
				int	     i;
				if (bms_is_member(dno,
						  write ? cstate->modif_variables : cstate->used_variables))
					return true;
				/* search any used recfield with related recparentno */
				for (i = 0; i < estate->ndatums; i++)
				{
					if (estate->datums[i]->dtype == PLPGSQL_DTYPE_RECFIELD)
					{
						PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) estate->datums[i];
						if (recfield->recparentno == rec->dno
								    && datum_is_used(cstate, i, write))
							return true;
					}
				}
			}
			break;
		case PLPGSQL_DTYPE_RECFIELD:
			return bms_is_member(dno,
					write ? cstate->modif_variables : cstate->used_variables);
		default:
			return false;
	}
	return false;
}
#define UNUSED_VARIABLE_TEXT			"unused variable \"%s\""
#define UNUSED_VARIABLE_TEXT_CHECK_LENGTH	15
#define UNUSED_PARAMETER_TEXT			"unused parameter \"%s\""
#define UNMODIFIED_VARIABLE_TEXT		"unmodified OUT variable \"%s\""
#define OUT_COMPOSITE_IS_NOT_SINGE_TEXT	"composite OUT variable \"%s\" is not single argument"
/*
 * Reports all unused variables explicitly DECLAREd by the user.  Ignores
 * special variables created by PL/PgSQL.
 */
static void
report_unused_variables(PLpgSQL_checkstate *cstate)
{
	int i;
	PLpgSQL_execstate *estate = cstate->estate;
	/* now, there are no active plpgsql statement */
	estate->err_stmt = NULL;
	for (i = 0; i < estate->ndatums; i++)
		if (datum_is_explicit(cstate, i) && !datum_is_used(cstate, i, false))
		{
			PLpgSQL_variable *var = (PLpgSQL_variable *) estate->datums[i];
			StringInfoData message;
			initStringInfo(&message);
			appendStringInfo(&message, UNUSED_VARIABLE_TEXT, var->refname);
			put_error(cstate,
					  0, var->lineno,
					  message.data,
					  NULL,
					  NULL,
					  PLPGSQL_CHECK_WARNING_OTHERS,
					  0, NULL, NULL);
			pfree(message.data);
			message.data = NULL;
		}
	if (cstate->extra_warnings)
	{
		PLpgSQL_function *func = estate->func;
	
		/* check IN parameters */
		for (i = 0; i < func->fn_nargs; i++)
		{
			int		varno = func->fn_argvarnos[i];
			if (!datum_is_used(cstate, varno, false))
			{
				PLpgSQL_variable *var = (PLpgSQL_variable *) estate->datums[varno];
				StringInfoData message;
				initStringInfo(&message);
				appendStringInfo(&message, UNUSED_PARAMETER_TEXT, var->refname);
				put_error(cstate,
						  0, 0,
						  message.data,
						  NULL,
						  NULL,
						  PLPGSQL_CHECK_WARNING_EXTRA,
						  0, NULL, NULL);
				pfree(message.data); message.data = NULL;
			}
		}
		/* are there some OUT parameters (expect modification)? */
		if (func->out_param_varno != -1 && !cstate->found_return_query)
		{
			int		varno = func->out_param_varno;
			PLpgSQL_variable *var = (PLpgSQL_variable *) estate->datums[varno];
			if (var->dtype == PLPGSQL_DTYPE_ROW && var->refname == NULL)
			{
				/* this function has more OUT parameters */
				PLpgSQL_row *row = (PLpgSQL_row*) var;
				int		fnum;
				for (fnum = 0; fnum < row->nfields; fnum++)
				{
					int		varno2 = row->varnos[fnum];
					PLpgSQL_variable *var = (PLpgSQL_variable *) estate->datums[varno2];
					StringInfoData message;
					if (var->dtype == PLPGSQL_DTYPE_ROW ||
						  var->dtype == PLPGSQL_DTYPE_REC)
					{
						initStringInfo(&message);
						appendStringInfo(&message,
									  OUT_COMPOSITE_IS_NOT_SINGE_TEXT, var->refname);
						put_error(cstate,
								  0, 0,
								  message.data,
								  NULL,
								  NULL,
								  PLPGSQL_CHECK_WARNING_EXTRA,
								  0, NULL, NULL);
						pfree(message.data);
						message.data = NULL;
					}
					if (!datum_is_used(cstate, varno2, true))
					{
						initStringInfo(&message);
						appendStringInfo(&message, UNMODIFIED_VARIABLE_TEXT, var->refname);
						put_error(cstate,
								  0, 0,
								  message.data,
								  NULL,
								  NULL,
								  PLPGSQL_CHECK_WARNING_EXTRA,
								  0, NULL, NULL);
						pfree(message.data);
						message.data = NULL;
					}
				}
			}
			else
			{
				if (!datum_is_used(cstate, varno, true))
				{
					PLpgSQL_variable *var = (PLpgSQL_variable *) estate->datums[varno];
					StringInfoData message;
					initStringInfo(&message);
					appendStringInfo(&message, UNMODIFIED_VARIABLE_TEXT, var->refname);
					put_error(cstate,
							  0, 0,
							  message.data,
							  NULL,
							  NULL,
							  PLPGSQL_CHECK_WARNING_EXTRA,
							  0, NULL, NULL);
					pfree(message.data);
					message.data = NULL;
				}
			}
		}
	}
}
/*
 * Verify an assignment of 'expr' to 'target'
 *
 */
static void
check_assignment(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr,
				 PLpgSQL_rec *targetrec, PLpgSQL_row *targetrow,
				 int targetdno)
{
	bool		is_expression = (targetrec == NULL && targetrow == NULL);
	check_expr_as_rvalue(cstate, expr, targetrec, targetrow, targetdno, false,
						  is_expression);
}
/*
 * Verify an assignment of 'expr' to 'target' with possible slices
 *
 * it is used in FOREACH ARRAY where SLICE change a target type
 *
 */
static void
check_assignment_with_possible_slices(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr,
				 PLpgSQL_rec *targetrec, PLpgSQL_row *targetrow,
				 int targetdno, bool use_element_type)
{
	bool		is_expression = (targetrec == NULL && targetrow == NULL);
	check_expr_as_rvalue(cstate, expr, targetrec, targetrow, targetdno, use_element_type,
						  is_expression);
}
/*
 * Verify to possible cast to bool, integer, ..
 *
 */
static void
check_expr_with_expected_scalar_type(PLpgSQL_checkstate *cstate,
								 PLpgSQL_expr *expr,
								 Oid expected_typoid,
								 bool required)
{
	ResourceOwner oldowner;
	MemoryContext oldCxt = CurrentMemoryContext;
	oldowner = CurrentResourceOwner;
	BeginInternalSubTransaction(NULL);
	MemoryContextSwitchTo(oldCxt);
	if (!expr)
	{
		if (required)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("required expression is empty")));
	}
	else
	{
		PG_TRY();
		{
			TupleDesc	tupdesc;
			bool		is_immutable_null;
			prepare_expr(cstate, expr, 0);
			/* record all variables used by the query */
			cstate->used_variables = bms_add_members(cstate->used_variables, expr->paramnos);
			tupdesc = expr_get_desc(cstate, expr, false, true, true, NULL);
			is_immutable_null = is_const_null_expr(expr);
			if (tupdesc)
			{
				/* when we know value or type */
				if (!is_immutable_null)
					check_assign_to_target_type(cstate,
									    expected_typoid, -1,
									    TupleDescAttr(tupdesc, 0)->atttypid,
									    is_immutable_null);
			}
			ReleaseTupleDesc(tupdesc);
			RollbackAndReleaseCurrentSubTransaction();
			MemoryContextSwitchTo(oldCxt);
			CurrentResourceOwner = oldowner;
			SPI_restore_connection();
		}
		PG_CATCH();
		{
			ErrorData  *edata;
			MemoryContextSwitchTo(oldCxt);
			edata = CopyErrorData();
			FlushErrorState();
			RollbackAndReleaseCurrentSubTransaction();
			MemoryContextSwitchTo(oldCxt);
			CurrentResourceOwner = oldowner;
			/*
			 * If fatal_errors is true, we just propagate the error up to the
			 * highest level. Otherwise the error is appended to our current list
			 * of errors, and we continue checking.
			 */
			if (cstate->fatal_errors)
				ReThrowError(edata);
			else
				put_error_edata(cstate, edata);
			MemoryContextSwitchTo(oldCxt);
			/* reconnect spi */
			SPI_restore_connection();
		}
		PG_END_TRY();
	}
}
/*
 * Checks used for RETURN QUERY
 *
 */
static void
check_returned_expr(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, bool is_expression)
{
	PLpgSQL_execstate *estate = cstate->estate;
	PLpgSQL_function *func = estate->func;
	bool		is_return_query = !is_expression;
	ResourceOwner oldowner;
	MemoryContext oldCxt = CurrentMemoryContext;
	oldowner = CurrentResourceOwner;
	BeginInternalSubTransaction(NULL);
	MemoryContextSwitchTo(oldCxt);
	PG_TRY();
	{
		TupleDesc	tupdesc;
		bool		is_immutable_null;
		Oid			first_level_typ = InvalidOid;
		prepare_expr(cstate, expr, 0);
		/* record all variables used by the query */
		cstate->used_variables = bms_add_members(cstate->used_variables, expr->paramnos);
		tupdesc = expr_get_desc(cstate, expr, false, true, is_expression, &first_level_typ);
		is_immutable_null = is_const_null_expr(expr);
		if (tupdesc)
		{
			/* enforce check for trigger function - result must be composit */
			if (func->fn_retistuple && is_expression 
						  && !(type_is_rowtype(TupleDescAttr(tupdesc, 0)->atttypid) ||
							   type_is_rowtype(first_level_typ) || tupdesc->natts > 1))
			{
				/* but we should to allow NULL */
				if (!is_immutable_null)
					put_error(cstate,
								ERRCODE_DATATYPE_MISMATCH, 0,
					"cannot return non-composite value from function returning composite type",
												NULL,
												NULL,
												PLPGSQL_CHECK_ERROR,
												0, NULL, NULL);
			}
			/* tupmap is used when function returns tuple or RETURN QUERY was used */
			else if (func->fn_retistuple || is_return_query)
			{
				/* should to know expected result */
				if (estate->rsi && IsA(estate->rsi, ReturnSetInfo))
				{
					TupleDesc	rettupdesc = estate->rsi->expectedDesc;
					TupleConversionMap *tupmap ;
					tupmap = convert_tuples_by_position(tupdesc, rettupdesc,
			    !is_expression ? gettext_noop("structure of query does not match function result type")
			                   : gettext_noop("returned record type does not match expected record type"));
					if (tupmap)
						free_conversion_map(tupmap);
				}
			}
			else
			{
				/* returns scalar */
				if (!IsPolymorphicType(func->fn_rettype))
				{
					check_assign_to_target_type(cstate,
									    func->fn_rettype, -1,
									    TupleDescAttr(tupdesc, 0)->atttypid,
									    is_immutable_null);
				}
			}
			ReleaseTupleDesc(tupdesc);
		}
		RollbackAndReleaseCurrentSubTransaction();
		MemoryContextSwitchTo(oldCxt);
		CurrentResourceOwner = oldowner;
		SPI_restore_connection();
	}
	PG_CATCH();
	{
		ErrorData  *edata;
		MemoryContextSwitchTo(oldCxt);
		edata = CopyErrorData();
		FlushErrorState();
		RollbackAndReleaseCurrentSubTransaction();
		MemoryContextSwitchTo(oldCxt);
		CurrentResourceOwner = oldowner;
		/*
		 * If fatal_errors is true, we just propagate the error up to the
		 * highest level. Otherwise the error is appended to our current list
		 * of errors, and we continue checking.
		 */
		if (cstate->fatal_errors)
			ReThrowError(edata);
		else
			put_error_edata(cstate, edata);
		MemoryContextSwitchTo(oldCxt);
		/* reconnect spi */
		SPI_restore_connection();
	}
	PG_END_TRY();
}
/*
 * Check expression as rvalue - on right in assign statement. It is used for
 * only expression check too - when target is unknown.
 *
 */
static void
check_expr_as_rvalue(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr,
					  PLpgSQL_rec *targetrec, PLpgSQL_row *targetrow,
					int targetdno, bool use_element_type, bool is_expression)
{
	ResourceOwner oldowner;
	MemoryContext oldCxt = CurrentMemoryContext;
	TupleDesc	tupdesc;
	bool is_immutable_null;
	bool			expand = true;
	Oid			first_level_typoid;
	Oid expected_typoid = InvalidOid;
	int expected_typmod = InvalidOid;
	if (targetdno != -1)
	{
		check_target(cstate, targetdno, &expected_typoid, &expected_typmod);
		/*
		 * When target variable is not compossite, then we should not
		 * to expand result tupdesc.
		 */
		if (!type_is_rowtype(expected_typoid))
			expand = false;
	}
	oldowner = CurrentResourceOwner;
	BeginInternalSubTransaction(NULL);
	MemoryContextSwitchTo(oldCxt);
	PG_TRY();
	{
		prepare_expr(cstate, expr, 0);
		/* record all variables used by the query */
		cstate->used_variables = bms_add_members(cstate->used_variables, expr->paramnos);
		tupdesc = expr_get_desc(cstate, expr, use_element_type, expand, is_expression, &first_level_typoid);
		is_immutable_null = is_const_null_expr(expr);
		if (expected_typoid != InvalidOid && type_is_rowtype(expected_typoid) && first_level_typoid != InvalidOid)
		{
			/* simple error, scalar source to composite target */
			if (!type_is_rowtype(first_level_typoid) && !is_immutable_null)
			{
				put_error(cstate,
						  ERRCODE_DATATYPE_MISMATCH, 0,
							  "cannot assign scalar variable to composite target",
							  NULL,
							  NULL,
							  PLPGSQL_CHECK_ERROR,
							  0, NULL, NULL);
				goto no_other_check;
			}
			/* simple ok, target and source composite types are same */
			if (type_is_rowtype(first_level_typoid)
				    && first_level_typoid != RECORDOID && first_level_typoid == expected_typoid)
				goto no_other_check;
		}
		if (tupdesc)
		{
			if (targetrow != NULL || targetrec != NULL)
				assign_tupdesc_row_or_rec(cstate, targetrow, targetrec, tupdesc, is_immutable_null);
			if (targetdno != -1)
				assign_tupdesc_dno(cstate, targetdno, tupdesc, is_immutable_null);
			if (targetrow)
			{
				if (targetrow->nfields > tupdesc->natts)
					put_error(cstate,
								  0, 0,
								  "too few attributies for target variables",
								  "There are more target variables than output columns in query.",
						  "Check target variables in SELECT INTO statement.",
								  PLPGSQL_CHECK_WARNING_OTHERS,
								  0, NULL, NULL);
				else if (targetrow->nfields < tupdesc->natts)
					put_error(cstate,
								  0, 0,
								  "too many attributies for target variables",
								  "There are less target variables than output columns in query.",
						   "Check target variables in SELECT INTO statement",
								  PLPGSQL_CHECK_WARNING_OTHERS,
								  0, NULL, NULL);
			}
		}
no_other_check:
		if (tupdesc)
			ReleaseTupleDesc(tupdesc);
		RollbackAndReleaseCurrentSubTransaction();
		MemoryContextSwitchTo(oldCxt);
		CurrentResourceOwner = oldowner;
		SPI_restore_connection();
	}
	PG_CATCH();
	{
		ErrorData  *edata;
		MemoryContextSwitchTo(oldCxt);
		edata = CopyErrorData();
		FlushErrorState();
		RollbackAndReleaseCurrentSubTransaction();
		MemoryContextSwitchTo(oldCxt);
		CurrentResourceOwner = oldowner;
		/*
		 * If fatal_errors is true, we just propagate the error up to the
		 * highest level. Otherwise the error is appended to our current list
		 * of errors, and we continue checking.
		 */
		if (cstate->fatal_errors)
			ReThrowError(edata);
		else
			put_error_edata(cstate, edata);
		MemoryContextSwitchTo(oldCxt);
		/* reconnect spi */
		SPI_restore_connection();
	}
	PG_END_TRY();
}
/*
 * Check a SQL statement, should not to return data
 *
 */
static void
check_expr_as_sqlstmt_nodata(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr)
{
	ResourceOwner oldowner;
	MemoryContext oldCxt = CurrentMemoryContext;
	oldowner = CurrentResourceOwner;
	BeginInternalSubTransaction(NULL);
	MemoryContextSwitchTo(oldCxt);
	PG_TRY();
	{
		prepare_expr(cstate, expr, 0);
		/* record all variables used by the query */
		cstate->used_variables = bms_add_members(cstate->used_variables, expr->paramnos);
		if (expr_get_desc(cstate, expr, false, false, false, NULL))
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("query has no destination for result data")));
		RollbackAndReleaseCurrentSubTransaction();
		MemoryContextSwitchTo(oldCxt);
		CurrentResourceOwner = oldowner;
		SPI_restore_connection();
	}
	PG_CATCH();
	{
		ErrorData  *edata;
		MemoryContextSwitchTo(oldCxt);
		edata = CopyErrorData();
		FlushErrorState();
		RollbackAndReleaseCurrentSubTransaction();
		MemoryContextSwitchTo(oldCxt);
		CurrentResourceOwner = oldowner;
		/*
		 * If fatal_errors is true, we just propagate the error up to the
		 * highest level. Otherwise the error is appended to our current list
		 * of errors, and we continue checking.
		 */
		if (cstate->fatal_errors)
			ReThrowError(edata);
		else
			put_error_edata(cstate, edata);
		MemoryContextSwitchTo(oldCxt);
		/* reconnect spi */
		SPI_restore_connection();
	}
	PG_END_TRY();
}
/*
 * Check composed lvalue There is nothing to check on rec variables
 */
static void
check_row_or_rec(PLpgSQL_checkstate *cstate, PLpgSQL_row *row, PLpgSQL_rec *rec)
{
	int			fnum;
	if (row != NULL)
	{
		for (fnum = 0; fnum < row->nfields; fnum++)
		{
			/* skip dropped columns */
			if (row->varnos[fnum] < 0)
				continue;
			check_target(cstate, row->varnos[fnum], NULL, NULL);
		}
		record_variable_usage(cstate, row->dno, true);
	}
	else if (rec != NULL)
	{
		/*
		 * There are no checks done on records currently; just record that the
		 * variable is not unused.
		 */
		record_variable_usage(cstate, rec->dno, true);
	}
}
/*
 * Verify lvalue It doesn't repeat a checks that are done. Checks a subscript
 * expressions, verify a validity of record's fields.
 */
static void
check_target(PLpgSQL_checkstate *cstate, int varno, Oid *expected_typoid, int *expected_typmod)
{
	PLpgSQL_datum *target = cstate->estate->datums[varno];
	record_variable_usage(cstate, varno, true);
	switch (target->dtype)
	{
		case PLPGSQL_DTYPE_VAR:
			{
				PLpgSQL_var *var = (PLpgSQL_var *) target;
				PLpgSQL_type *tp = var->datatype;
				if (expected_typoid != NULL)
					*expected_typoid = tp->typoid;
				if (expected_typmod != NULL)
					*expected_typmod = tp->atttypmod;
			}
			break;
		case PLPGSQL_DTYPE_REC:
			{
				PLpgSQL_rec *rec = (PLpgSQL_rec *) target;
				if (rec->tupdesc != NULL)
				{
					if (expected_typoid != NULL)
						*expected_typoid = rec->tupdesc->tdtypeid;
					if (expected_typmod != NULL)
						*expected_typmod = rec->tupdesc->tdtypmod;
				}
				else
				{
					if (expected_typoid != NULL)
						*expected_typoid = RECORDOID;
					if (expected_typmod != NULL)
						*expected_typmod = -1;
				}
			}
			break;
		case PLPGSQL_DTYPE_ROW:
			{
				PLpgSQL_row *row = (PLpgSQL_row *) target;
				if (row->rowtupdesc != NULL)
				{
					if (expected_typoid != NULL)
						*expected_typoid = row->rowtupdesc->tdtypeid;
					if (expected_typmod != NULL)
						*expected_typmod = row->rowtupdesc->tdtypmod;
				}
				else
				{
					if (expected_typoid != NULL)
						*expected_typoid = RECORDOID;
					if (expected_typmod != NULL)
						*expected_typmod = -1;
				}
				check_row_or_rec(cstate, row, NULL);
			}
			break;
		case PLPGSQL_DTYPE_RECFIELD:
			{
				PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target;
				PLpgSQL_rec *rec;
				int			fno;
				rec = (PLpgSQL_rec *) (cstate->estate->datums[recfield->recparentno]);
				/*
				 * Check that there is already a tuple in the record. We need
				 * that because records don't have any predefined field
				 * structure.
				 */
				if (!HeapTupleIsValid(rec->tup))
					ereport(ERROR,
						  (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					errmsg("record \"%s\" is not assigned to tuple structure",
						   rec->refname)));
				/*
				 * Get the number of the records field to change and the
				 * number of attributes in the tuple.  Note: disallow system
				 * column names because the code below won't cope.
				 */
				fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
				if (fno <= 0)
					ereport(ERROR,
							(errcode(ERRCODE_UNDEFINED_COLUMN),
							 errmsg("record \"%s\" has no field \"%s\"",
									rec->refname, recfield->fieldname)));
				if (expected_typoid)
					*expected_typoid = SPI_gettypeid(rec->tupdesc, fno);
				if (expected_typmod)
					*expected_typmod = TupleDescAttr(rec->tupdesc, fno - 1)->atttypmod;
			}
			break;
		case PLPGSQL_DTYPE_ARRAYELEM:
			{
				/*
				 * Target is an element of an array
				 */
				int			nsubscripts;
				Oid			arrayelemtypeid;
				Oid			arraytypeid;
				/*
				 * To handle constructs like x[1][2] := something, we have to
				 * be prepared to deal with a chain of arrayelem datums. Chase
				 * back to find the base array datum, and save the subscript
				 * expressions as we go.  (We are scanning right to left here,
				 * but want to evaluate the subscripts left-to-right to
				 * minimize surprises.)
				 */
				nsubscripts = 0;
				do
				{
					PLpgSQL_arrayelem *arrayelem = (PLpgSQL_arrayelem *) target;
					if (nsubscripts++ >= MAXDIM)
						ereport(ERROR,
								(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
								 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
										nsubscripts + 1, MAXDIM)));
					/* Validate expression. */
					/* XXX is_expression */
					check_expr(cstate, arrayelem->subscript);
					target = cstate->estate->datums[arrayelem->arrayparentno];
				} while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM);
				/*
				 * If target is domain over array, reduce to base type
				 */
#if PG_VERSION_NUM >= 90600
				arraytypeid = plpgsql_exec_get_datum_type(cstate->estate, target);
#else
				arraytypeid = exec_get_datum_type(cstate->estate, target);
#endif
				arraytypeid = getBaseType(arraytypeid);
				arrayelemtypeid = get_element_type(arraytypeid);
				if (!OidIsValid(arrayelemtypeid))
					ereport(ERROR,
							(errcode(ERRCODE_DATATYPE_MISMATCH),
							 errmsg("subscripted object is not an array")));
				if (expected_typoid)
					*expected_typoid = arrayelemtypeid;
				if (expected_typmod)
					*expected_typmod = ((PLpgSQL_var *) target)->datatype->atttypmod;
				record_variable_usage(cstate, target->dno, true);
			}
			break;
		default:
			;		/* nope */
	}
}
/*
 * Generate a prepared plan - this is simplified copy from pl_exec.c Is not
 * necessary to check simple plan, returns true, when expression is
 * succesfully prepared.
 */
static void
prepare_expr(PLpgSQL_checkstate *cstate,
			 PLpgSQL_expr *expr, int cursorOptions)
{
	SPIPlanPtr	plan;
	if (expr->plan == NULL)
	{
		/*
		 * The grammar can't conveniently set expr->func while building the parse
		 * tree, so make sure it's set before parser hooks need it.
		 */
		expr->func = cstate->estate->func;
		/*
		 * Generate and save the plan
		 */
		plan = SPI_prepare_params(expr->query,
								  (ParserSetupHook) plpgsql_parser_setup,
								  (void *) expr,
								  cursorOptions);
		if (plan == NULL)
		{
			/* Some SPI errors deserve specific error messages */
			switch (SPI_result)
			{
				case SPI_ERROR_COPY:
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("cannot COPY to/from client in PL/pgSQL")));
					break;
				case SPI_ERROR_TRANSACTION:
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("cannot begin/end transactions in PL/pgSQL"),
							 errhint("Use a BEGIN block with an EXCEPTION clause instead.")));
					break;
				default:
					elog(ERROR, "SPI_prepare_params failed for \"%s\": %s",
						 expr->query, SPI_result_code_string(SPI_result));
			}
		}
		/*
		 * We would to check all plans, but when plan exists, then don't 
		 * overwrite existing plan.
		 */
		if (expr->plan == NULL)
		{
			expr->plan = SPI_saveplan(plan);
			cstate->exprs = lappend(cstate->exprs, expr);
		}
		SPI_freeplan(plan);
	}
	/* Don't allow write plan when function is read only */
	if (cstate->estate->readonly_func)
		prohibit_write_plan(cstate, expr);
	prohibit_transaction_stmt(cstate, expr);
}
/*
 * Check so target can accept typoid value
 *
 */
static void
check_assign_to_target_type(PLpgSQL_checkstate *cstate,
							 Oid target_typoid, int32 target_typmod,
												 Oid value_typoid,
												 bool isnull)
{
#if PG_VERSION_NUM < 90500
	/* any used typmod enforces IO cast - performance warning for older than 9.5*/
	if (target_typmod != -1)
		put_error(cstate,
					  ERRCODE_DATATYPE_MISMATCH, 0,
					  "target type has type modificator",
					  NULL,
					  "Usage of type modificator enforces slower IO casting.",
					  PLPGSQL_CHECK_WARNING_PERFORMANCE,
					  0, NULL, NULL);
#endif
	if (type_is_rowtype(value_typoid))
		put_error(cstate,
					  ERRCODE_DATATYPE_MISMATCH, 0,
					  "cannot cast composite value to a scalar type",
					  NULL,
					  NULL,
					  PLPGSQL_CHECK_ERROR,
					  0, NULL, NULL);
	else if (target_typoid != value_typoid)
	{
		StringInfoData	str;
		initStringInfo(&str);
		appendStringInfo(&str, "cast \"%s\" value to \"%s\" type",
									format_type_be(value_typoid),
									format_type_be(target_typoid));
		/* accent warning when cast is without supported explicit casting */
		if (!can_coerce_type(1, &value_typoid, &target_typoid, COERCION_EXPLICIT))
			put_error(cstate,
						  ERRCODE_DATATYPE_MISMATCH, 0,
						  "target type is different type than source type",
						  str.data,
						  "There are no possible explicit coercion between those types, possibly bug!",
						  PLPGSQL_CHECK_WARNING_OTHERS,
						  0, NULL, NULL);
		else if (!can_coerce_type(1, &value_typoid, &target_typoid, COERCION_ASSIGNMENT))
			put_error(cstate,
						  ERRCODE_DATATYPE_MISMATCH, 0,
						  "target type is different type than source type",
						  str.data,
						  "The input expression type does not have an assignment cast to the target type.",
						  PLPGSQL_CHECK_WARNING_OTHERS,
						  0, NULL, NULL);
		else
		{
			/* highly probably only performance issue */
			if (!isnull)
				put_error(cstate,
							  ERRCODE_DATATYPE_MISMATCH, 0,
							  "target type is different type than source type",
							  str.data,
							  "Hidden casting can be a performance issue.",
							  PLPGSQL_CHECK_WARNING_PERFORMANCE,
							  0, NULL, NULL);
		}
		pfree(str.data);
	}
}
/*
 * Assign a tuple descriptor to variable specified by dno
 */
static void
assign_tupdesc_dno(PLpgSQL_checkstate *cstate, int varno, TupleDesc tupdesc, bool isnull)
{
	PLpgSQL_datum *target = cstate->estate->datums[varno];
	switch (target->dtype)
	{
		case PLPGSQL_DTYPE_VAR:
			{
				PLpgSQL_var *var = (PLpgSQL_var *) target;
				check_assign_to_target_type(cstate,
									 var->datatype->typoid, var->datatype->atttypmod,
									 TupleDescAttr(tupdesc, 0)->atttypid,
									 isnull);
			}
			break;
		case PLPGSQL_DTYPE_ROW:
			assign_tupdesc_row_or_rec(cstate, (PLpgSQL_row *) target, NULL, tupdesc, isnull);
			break;
		case PLPGSQL_DTYPE_REC:
			assign_tupdesc_row_or_rec(cstate, NULL, (PLpgSQL_rec *) target, tupdesc, isnull);
			break;
		case PLPGSQL_DTYPE_ARRAYELEM:
			{
				Oid expected_typoid;
				int expected_typmod;
				check_target(cstate, varno, &expected_typoid, &expected_typmod);
				/* When target is composite type, then source is expanded already */
				if (type_is_rowtype(expected_typoid))
				{
					PLpgSQL_rec rec;
					rec.tup = NULL;
					rec.freetup = false;
					rec.freetupdesc = false;
					PG_TRY();
					{
						rec.tupdesc = lookup_rowtype_tupdesc_noerror(expected_typoid, expected_typmod, true);
						assign_tupdesc_row_or_rec(cstate, NULL, &rec, tupdesc, isnull);
						if (rec.tupdesc)
							ReleaseTupleDesc(rec.tupdesc);
					}
					PG_CATCH();
					{
						if (rec.tupdesc)
							ReleaseTupleDesc(rec.tupdesc);
						PG_RE_THROW();
					}
					PG_END_TRY();
				}
				else
					check_assign_to_target_type(cstate,
									    expected_typoid, expected_typmod,
									    TupleDescAttr(tupdesc, 0)->atttypid,
									    isnull);
			}
			break;
		default:
			;		/* nope */
	}
}
/*
 * We have to assign TupleDesc to all used record variables step by step. We
 * would to use a exec routines for query preprocessing, so we must to create
 * a typed NULL value, and this value is assigned to record variable.
 */
static void
assign_tupdesc_row_or_rec(PLpgSQL_checkstate *cstate,
						  PLpgSQL_row *row, PLpgSQL_rec *rec,
						  TupleDesc tupdesc, bool isnull)
{
	bool	   *nulls;
	HeapTuple	tup;
	if (tupdesc == NULL)
	{
		put_error(cstate,
					  0, 0,
					  "tuple descriptor is empty", NULL, NULL,
					  PLPGSQL_CHECK_WARNING_OTHERS,
					  0, NULL, NULL);
		return;
	}
	/*
	 * row variable has assigned TupleDesc already, so don't be processed here
	 */
	if (rec != NULL)
	{
		PLpgSQL_rec *target = (PLpgSQL_rec *) (cstate->estate->datums[rec->dno]);
		if (target->freetup)
			heap_freetuple(target->tup);
		if (rec->freetupdesc)
			FreeTupleDesc(target->tupdesc);
		/* initialize rec by NULLs */
		nulls = (bool *) palloc(tupdesc->natts * sizeof(bool));
		memset(nulls, true, tupdesc->natts * sizeof(bool));
		target->tupdesc = CreateTupleDescCopy(tupdesc);
		target->freetupdesc = true;
		tup = heap_form_tuple(tupdesc, NULL, nulls);
		if (HeapTupleIsValid(tup))
		{
			target->tup = tup;
			target->freetup = true;
		}
		else
			elog(ERROR, "cannot to build valid composite value");
	}
	else if (row != NULL && tupdesc != NULL)
	{
		int			td_natts = tupdesc->natts;
		int			fnum;
		int			anum;
		anum = 0;
		for (fnum = 0; fnum < row->nfields; fnum++)
		{
			if (row->varnos[fnum] < 0)
				continue;		/* skip dropped column in row struct */
			while (anum < td_natts && TupleDescAttr(tupdesc, anum)->attisdropped)
				anum++;			/* skip dropped column in tuple */
			if (anum < td_natts)
			{
				Oid	valtype = SPI_gettypeid(tupdesc, anum + 1);
				PLpgSQL_datum *target = cstate->estate->datums[row->varnos[fnum]];
				switch (target->dtype)
				{
					case PLPGSQL_DTYPE_VAR:
						{
							PLpgSQL_var *var = (PLpgSQL_var *) target;
							check_assign_to_target_type(cstate,
												 var->datatype->typoid,
												 var->datatype->atttypmod,
														 valtype,
														 isnull);
						}
						break;
					case PLPGSQL_DTYPE_RECFIELD:
						{
							Oid	expected_typoid;
							int	expected_typmod;
							check_target(cstate, target->dno, &expected_typoid, &expected_typmod);
							check_assign_to_target_type(cstate,
												 expected_typoid,
												 expected_typmod,
														valtype,
														isnull);
						}
						break;
					default:
						;		/* nope */
				}
				anum++;
			}
		}
	}
}
/*
 * Common part of some expression based analyzes.
 *
 */
static CachedPlan *
ExprGetPlan(PLpgSQL_expr *query)
{
	CachedPlanSource *plansource = NULL;
	CachedPlan *cplan;
	if (query->plan != NULL)
	{
		SPIPlanPtr	plan = query->plan;
		if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
			elog(ERROR, "cached plan is not valid plan");
		if (list_length(plan->plancache_list) != 1)
			elog(ERROR, "plan is not single execution plan");
		plansource = (CachedPlanSource *) linitial(plan->plancache_list);
		if (!plansource->resultDesc)
			elog(ERROR, "query returns no result");
	}
	else
		elog(ERROR, "there are no plan for query: \"%s\"",
			 query->query);
	/*
	 * When tupdesc is related to unpined record, we will try to check
	 * plan if it is just function call and if it is then we can try to
	 * derive a tupledes from function's description.
	 */
#if PG_VERSION_NUM >= 100000
	cplan = GetCachedPlan(plansource, NULL, true, NULL);
#else
	cplan = GetCachedPlan(plansource, NULL, true);
#endif
	return cplan;
}
/*
 * Returns Const Value from expression if it is possible.
 *
 */
static Const *
ExprGetConst(PLpgSQL_expr *query, bool *IsConst)
{
	PlannedStmt *_stmt;
	Plan	   *_plan;
	TargetEntry *tle;
	CachedPlan *cplan;
	Const	   *result = NULL;
	cplan = ExprGetPlan(query);
	_stmt = (PlannedStmt *) linitial(cplan->stmt_list);
	if (IsA(_stmt, PlannedStmt) &&_stmt->commandType == CMD_SELECT)
	{
		_plan = _stmt->planTree;
		if (IsA(_plan, Result) &&list_length(_plan->targetlist) == 1)
		{
			tle = (TargetEntry *) linitial(_plan->targetlist);
			if (((Node *) tle->expr)->type == T_Const)
				result = (Const *) tle->expr;
		}
	}
	*IsConst = result != NULL;
	ReleaseCachedPlan(cplan, true);
	return result;
}
/*
 * Returns true for entered NULL constant
 *
 */
static bool
is_const_null_expr(PLpgSQL_expr *query)
{
	Const	   *c;
	bool		IsConst;
	c = ExprGetConst(query, &IsConst);
	return IsConst ? c->constisnull : false;
}
/*
 * Returns string for any not null constant.
 * When constant is NULL, then returns NULL.
 *
 */
static char *
ExprGetString(PLpgSQL_expr *query, bool *IsConst)
{
	Const	   *c;
	char	   *result = NULL;
	c = ExprGetConst(query, IsConst);
	if (*IsConst && !c->constisnull)
	{
		Oid		typoutput;
		bool	typisvarlena;
		getTypeOutputInfo(c->consttype, &typoutput, &typisvarlena);
		result = OidOutputFunctionCall(typoutput, c->constvalue);
	}
	return result;
}
/*
 * Returns a tuple descriptor based on existing plan, When error is detected
 * returns null.
 */
static TupleDesc
expr_get_desc(PLpgSQL_checkstate *cstate,
			  PLpgSQL_expr *query,
			  bool use_element_type,
			  bool expand_record,
			  bool is_expression,
			  Oid *first_level_typoid)
{
	TupleDesc	tupdesc = NULL;
	CachedPlanSource *plansource = NULL;
	if (query->plan != NULL)
	{
		SPIPlanPtr	plan = query->plan;
		if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
			elog(ERROR, "cached plan is not valid plan");
		if (list_length(plan->plancache_list) != 1)
			elog(ERROR, "plan is not single execution plan");
		plansource = (CachedPlanSource *) linitial(plan->plancache_list);
		if (!plansource->resultDesc)
		{
			if (is_expression)
				elog(ERROR, "query returns no result");
			else
				return NULL;
		}
		tupdesc = CreateTupleDescCopy(plansource->resultDesc);
	}
	else
		elog(ERROR, "there are no plan for query: \"%s\"",
			 query->query);
	if (is_expression && tupdesc->natts != 1)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("query \"%s\" returned %d columns",
						query->query,
						tupdesc->natts)));
	/*
	 * try to get a element type, when result is a array (used with FOREACH
	 * ARRAY stmt)
	 */
	if (use_element_type)
	{
		Oid			elemtype;
		TupleDesc	elemtupdesc;
		/* result should be a array */
		if (is_expression && tupdesc->natts != 1)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("query \"%s\" returned %d columns",
							query->query,
							tupdesc->natts)));
		/* check the type of the expression - must be an array */
		elemtype = get_element_type(TupleDescAttr(tupdesc, 0)->atttypid);
		if (!OidIsValid(elemtype))
		{
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
				errmsg("FOREACH expression must yield an array, not type %s",
					   format_type_be(TupleDescAttr(tupdesc, 0)->atttypid))));
			FreeTupleDesc(tupdesc);
		}
		if (is_expression && first_level_typoid != NULL)
			*first_level_typoid = elemtype;
		/* when elemtype is not composity, prepare single field tupdesc */
		if (!type_is_rowtype(elemtype))
		{
			TupleDesc rettupdesc;
			rettupdesc = CreateTemplateTupleDesc(1, false);
			TupleDescInitEntry(rettupdesc, 1, "__array_element__", elemtype, -1, 0);
			FreeTupleDesc(tupdesc);
			BlessTupleDesc(rettupdesc);
			tupdesc = rettupdesc;
		}
		else
		{
			elemtupdesc = lookup_rowtype_tupdesc_noerror(elemtype, -1, true);
			if (elemtupdesc != NULL)
			{
				FreeTupleDesc(tupdesc);
				tupdesc = CreateTupleDescCopy(elemtupdesc);
				ReleaseTupleDesc(elemtupdesc);
			}
		}
	}
	else
	{
		if (is_expression && first_level_typoid != NULL)
			*first_level_typoid = TupleDescAttr(tupdesc, 0)->atttypid;
	}
	/*
	 * One spacial case is when record is assigned to composite type, then we
	 * should to unpack composite type.
	 */
	if (tupdesc->tdtypeid == RECORDOID &&
		tupdesc->tdtypmod == -1 &&
		tupdesc->natts == 1 && expand_record)
	{
		TupleDesc	unpack_tupdesc;
		unpack_tupdesc = lookup_rowtype_tupdesc_noerror(TupleDescAttr(tupdesc, 0)->atttypid,
												TupleDescAttr(tupdesc, 0)->atttypmod,
														true);
		if (unpack_tupdesc != NULL)
		{
			FreeTupleDesc(tupdesc);
			tupdesc = CreateTupleDescCopy(unpack_tupdesc);
			ReleaseTupleDesc(unpack_tupdesc);
		}
	}
	/*
	 * There is special case, when returned tupdesc contains only unpined
	 * record: rec := func_with_out_parameters(). IN this case we must to dig
	 * more deep - we have to find oid of function and get their parameters,
	 *
	 * This is support for assign statement recvar :=
	 * func_with_out_parameters(..)
	 *
	 * XXX: Why don't we always do that?
	 */
	if (tupdesc->tdtypeid == RECORDOID &&
		tupdesc->tdtypmod == -1 &&
		tupdesc->natts == 1 &&
		TupleDescAttr(tupdesc, 0)->atttypid == RECORDOID &&
		TupleDescAttr(tupdesc, 0)->atttypmod == -1 &&
		expand_record)
	{
		PlannedStmt *_stmt;
		Plan	   *_plan;
		TargetEntry *tle;
		CachedPlan *cplan;
		/*
		 * When tupdesc is related to unpined record, we will try to check
		 * plan if it is just function call and if it is then we can try to
		 * derive a tupledes from function's description.
		 */
#if PG_VERSION_NUM >= 100000
	cplan = GetCachedPlan(plansource, NULL, true, NULL);
#else
	cplan = GetCachedPlan(plansource, NULL, true);
#endif
		_stmt = (PlannedStmt *) linitial(cplan->stmt_list);
		if (IsA(_stmt, PlannedStmt) &&_stmt->commandType == CMD_SELECT)
		{
			_plan = _stmt->planTree;
			if (IsA(_plan, Result) &&list_length(_plan->targetlist) == 1)
			{
				tle = (TargetEntry *) linitial(_plan->targetlist);
				switch (((Node *) tle->expr)->type)
				{
					case T_FuncExpr:
						{
							FuncExpr   *fn = (FuncExpr *) tle->expr;
							FmgrInfo	flinfo;
							FunctionCallInfoData fcinfo;
							TupleDesc	rd;
							Oid			rt;
							fmgr_info(fn->funcid, &flinfo);
							flinfo.fn_expr = (Node *) fn;
							fcinfo.flinfo = &flinfo;
							get_call_result_type(&fcinfo, &rt, &rd);
							if (rd == NULL)
								ereport(ERROR,
										(errcode(ERRCODE_DATATYPE_MISMATCH),
								 errmsg("function does not return composite type, is not possible to identify composite type")));
							FreeTupleDesc(tupdesc);
							BlessTupleDesc(rd);
							tupdesc = rd;
						}
						break;
					case T_RowExpr:
						{
							RowExpr		*row = (RowExpr *) tle->expr;
							ListCell *lc_colname;
							ListCell *lc_arg;
							TupleDesc rettupdesc;
							int			i = 1;
							rettupdesc = CreateTemplateTupleDesc(list_length(row->args), false);
							forboth (lc_colname, row->colnames, lc_arg, row->args)
							{
								Node	*arg = lfirst(lc_arg);
								char	*name = strVal(lfirst(lc_colname));
								TupleDescInitEntry(rettupdesc, i,
												    name,
												    exprType(arg),
												    exprTypmod(arg),
												    0);
								i++;
							}
							FreeTupleDesc(tupdesc);
							BlessTupleDesc(rettupdesc);
							tupdesc = rettupdesc;
						}
						break;
					default:
							/* cannot to take tupdesc */
							tupdesc = NULL;
				}
			}
		}
		ReleaseCachedPlan(cplan, true);
	}
	return tupdesc;
}
/*
 * Raise a error when plan is not read only
 */
static void
prohibit_write_plan(PLpgSQL_checkstate *cstate, PLpgSQL_expr *query)
{
	CachedPlanSource *plansource = NULL;
	SPIPlanPtr	 plan = query->plan;
	CachedPlan	*cplan;
	List		*stmt_list;
	ListCell	*lc;
	if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
		elog(ERROR, "cached plan is not valid plan");
	if (list_length(plan->plancache_list) != 1)
		elog(ERROR, "plan is not single execution plan");
	plansource = (CachedPlanSource *) linitial(plan->plancache_list);
#if PG_VERSION_NUM >= 100000
	cplan = GetCachedPlan(plansource, NULL, true, NULL);
#else
	cplan = GetCachedPlan(plansource, NULL, true);
#endif
	stmt_list = cplan->stmt_list;
	foreach(lc, stmt_list)
	{
#if PG_VERSION_NUM >= 100000
		PlannedStmt *pstmt = (PlannedStmt *) lfirst(lc);
		Assert(IsA(pstmt, PlannedStmt));
#else
		Node *pstmt = (Node *) lfirst(lc);
#endif
		if (!CommandIsReadOnly(pstmt))
		{
			StringInfoData message;
			initStringInfo(&message);
			appendStringInfo(&message,
					"%s is not allowed in a non volatile function",
							CreateCommandTag((Node *) pstmt));
			put_error(cstate,
					  ERRCODE_FEATURE_NOT_SUPPORTED, 0,
					  message.data,
					  NULL,
					  NULL,
					  PLPGSQL_CHECK_ERROR,
					  0, query->query, NULL);
			pfree(message.data);
			message.data = NULL;
		}
	}
	ReleaseCachedPlan(cplan, true);
}
/*
 * Raise a error when plan is a transactional statement
 */
static void
prohibit_transaction_stmt(PLpgSQL_checkstate *cstate, PLpgSQL_expr *query)
{
	CachedPlanSource *plansource = NULL;
	SPIPlanPtr	 plan = query->plan;
	CachedPlan	*cplan;
	List		*stmt_list;
	ListCell	*lc;
	if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
		elog(ERROR, "cached plan is not valid plan");
	if (list_length(plan->plancache_list) != 1)
		elog(ERROR, "plan is not single execution plan");
	plansource = (CachedPlanSource *) linitial(plan->plancache_list);
#if PG_VERSION_NUM >= 100000
	cplan = GetCachedPlan(plansource, NULL, true, NULL);
#else
	cplan = GetCachedPlan(plansource, NULL, true);
#endif
	stmt_list = cplan->stmt_list;
	foreach(lc, stmt_list)
	{
		Node *pstmt = (Node *) lfirst(lc);
		if (IsA(pstmt, TransactionStmt))
		{
			put_error(cstate,
					  ERRCODE_FEATURE_NOT_SUPPORTED, 0,
					  "cannot begin/end transactions in PL/pgSQL",
					  NULL,
					  "Use a BEGIN block with an EXCEPTION clause instead.",
					  PLPGSQL_CHECK_ERROR,
					  0, query->query, NULL);
		}
	}
	ReleaseCachedPlan(cplan, true);
}
/*
 * returns refname of PLpgSQL_datum
 */
static char *
datum_get_refname(PLpgSQL_datum *d)
{
	switch (d->dtype)
	{
		case PLPGSQL_DTYPE_VAR:
			return ((PLpgSQL_var *) d)->refname;
		case PLPGSQL_DTYPE_ROW:
			return ((PLpgSQL_row *) d)->refname;
		case PLPGSQL_DTYPE_REC:
			return ((PLpgSQL_rec *) d)->refname;
		default:
			return NULL;
	}
}
/****************************************************************************************
 * Output routines
 *
 ****************************************************************************************
 *
 */
#define SET_RESULT_NULL(anum) \
	do { \
		values[(anum)] = (Datum) 0; \
		nulls[(anum)] = true; \
	} while (0)
#define SET_RESULT(anum, value) \
	do { \
		values[(anum)] = (value); \
		nulls[(anum)] = false; \
	} while(0)
#define SET_RESULT_TEXT(anum, str) \
	do { \
		if (str != NULL) \
		{ \
			SET_RESULT((anum), CStringGetTextDatum((str))); \
		} \
		else \
		{ \
			SET_RESULT_NULL(anum); \
		} \
	} while (0)
#define SET_RESULT_INT32(anum, ival)	SET_RESULT((anum), Int32GetDatum((ival)))
#define SET_RESULT_OID(anum, oid)	SET_RESULT((anum), ObjectIdGetDatum((oid)))
/*
 * error processing switch - ignore warnings when it is necessary,
 * store fields to result tuplestore or raise exception to out
 *
 */
static void
put_error(PLpgSQL_checkstate *cstate,
					  int sqlerrcode,
					  int lineno,
					  const char *message,
					  const char *detail,
					  const char *hint,
					  int level,
					  int position,
					  const char *query,
					  const char *context)
{
	/* ignore warnings when is not requested */
	if ((level == PLPGSQL_CHECK_WARNING_PERFORMANCE && !cstate->performance_warnings) ||
			    (level == PLPGSQL_CHECK_WARNING_OTHERS && !cstate->other_warnings) ||
			    (level == PLPGSQL_CHECK_WARNING_EXTRA && !cstate->extra_warnings))
		return;
	if (cstate->tuple_store != NULL)
	{
		switch (cstate->format)
		{
			case PLPGSQL_CHECK_FORMAT_TABULAR:
				tuplestore_put_error_tabular(cstate->tuple_store, cstate->tupdesc,
									 cstate->estate, cstate->fn_oid,
									 sqlerrcode, lineno, message, detail,
									 hint, level, position, query, context);
				break;
			case PLPGSQL_CHECK_FORMAT_TEXT:
				tuplestore_put_error_text(cstate->tuple_store, cstate->tupdesc,
									 cstate->estate, cstate->fn_oid,
									 sqlerrcode, lineno, message, detail,
									 hint, level, position, query, context);
				break;
			case PLPGSQL_CHECK_FORMAT_XML:
				format_error_xml(cstate->sinfo, cstate->estate,
									 sqlerrcode, lineno, message, detail,
									 hint, level, position, query, context);
				break;
			case PLPGSQL_CHECK_FORMAT_JSON:
				format_error_json(cstate->sinfo, cstate->estate,
									 sqlerrcode, lineno, message, detail,
									 hint, level, position, query, context);
			break;
		}
	}
	else
	{
		int elevel;
		/*
		 * when a passive mode is active and fatal_errors is false, then
		 * raise warning everytime.
		 */
		if (!cstate->is_active_mode && !cstate->fatal_errors)
			elevel = WARNING;
		else
			elevel = level == PLPGSQL_CHECK_ERROR ? ERROR : WARNING;
		/* use error fields as parameters of postgres exception */
		ereport(elevel,
				(sqlerrcode ? errcode(sqlerrcode) : 0,
				 errmsg_internal("%s", message),
				 (detail != NULL) ? errdetail_internal("%s", detail) : 0,
				 (hint != NULL) ? errhint("%s", hint) : 0,
				 (query != NULL) ? internalerrquery(query) : 0,
				 (position != 0) ? internalerrposition(position) : 0,
				 (context != NULL) ? errcontext("%s", context) : 0));
	}
}
static const char *
error_level_str(int level)
{
	switch (level)
	{
		case PLPGSQL_CHECK_ERROR:
			return "error";
		case PLPGSQL_CHECK_WARNING_OTHERS:
			return "warning";
		case PLPGSQL_CHECK_WARNING_EXTRA:
			return "warning extra";
		case PLPGSQL_CHECK_WARNING_PERFORMANCE:
			return "performance";
		default:
			return "???";
	}
}
/*
 * store error fields to result tuplestore
 *
 */
static void
tuplestore_put_error_tabular(Tuplestorestate *tuple_store, TupleDesc tupdesc,
						  PLpgSQL_execstate *estate,
								 Oid fn_oid,
								 int sqlerrcode,
								 int lineno,
								 const char *message,
								 const char *detail,
								 const char *hint,
								 int level,
								 int position,
								 const char *query,
								 const char *context)
{
	Datum	values[Natts_result];
	bool	nulls[Natts_result];
	Assert(message != NULL);
	SET_RESULT_OID(Anum_result_functionid, fn_oid);
	/* lineno should be valid */
	if (estate != NULL && estate->err_stmt != NULL && estate->err_stmt->lineno > 0)
	{
		/* use lineno based on err_stmt */
		SET_RESULT_INT32(Anum_result_lineno, estate->err_stmt->lineno);
		SET_RESULT_TEXT(Anum_result_statement, plpgsql_stmt_typename(estate->err_stmt));
	}
	else if (strncmp(message, UNUSED_VARIABLE_TEXT, UNUSED_VARIABLE_TEXT_CHECK_LENGTH) == 0)
	{
		SET_RESULT_INT32(Anum_result_lineno, lineno);
		SET_RESULT_TEXT(Anum_result_statement, "DECLARE");
	}
	else
	{
		SET_RESULT_NULL(Anum_result_lineno);
		SET_RESULT_NULL(Anum_result_statement);
	} 
	SET_RESULT_TEXT(Anum_result_sqlstate, unpack_sql_state(sqlerrcode));
	SET_RESULT_TEXT(Anum_result_message, message);
	SET_RESULT_TEXT(Anum_result_detail, detail);
	SET_RESULT_TEXT(Anum_result_hint, hint);
	SET_RESULT_TEXT(Anum_result_level, error_level_str(level));
	if (position != 0)
		SET_RESULT_INT32(Anum_result_position, position);
	else
		SET_RESULT_NULL(Anum_result_position);
	SET_RESULT_TEXT(Anum_result_query, query);
	SET_RESULT_TEXT(Anum_result_context, context);
	tuplestore_putvalues(tuple_store, tupdesc, values, nulls);
}
/*
 * collects errors and warnings in plain text format
 */
static void
tuplestore_put_error_text(Tuplestorestate *tuple_store, TupleDesc tupdesc,
						  PLpgSQL_execstate *estate,
								 Oid fn_oid,
								 int sqlerrcode,
								 int lineno,
								 const char *message,
								 const char *detail,
								 const char *hint,
								 int level,
								 int position,
								 const char *query,
								 const char *context)
{
	StringInfoData  sinfo;
	const char *level_str = error_level_str(level);
	Assert(message != NULL);
	initStringInfo(&sinfo);
	/* lineno should be valid for actual statements */
	if (estate != NULL && estate->err_stmt != NULL && estate->err_stmt->lineno > 0)
		appendStringInfo(&sinfo, "%s:%s:%d:%s:%s",
				 level_str,
				 unpack_sql_state(sqlerrcode),
				 estate->err_stmt->lineno,
			    plpgsql_stmt_typename(estate->err_stmt),
				 message);
	else if (strncmp(message, UNUSED_VARIABLE_TEXT, UNUSED_VARIABLE_TEXT_CHECK_LENGTH) == 0)
	{
		appendStringInfo(&sinfo, "%s:%s:%d:%s:%s",
				 level_str,
				 unpack_sql_state(sqlerrcode),
				 lineno,
				 "DECLARE",
				 message);
	}
	else
	{
		appendStringInfo(&sinfo, "%s:%s:%s",
				 level_str,
				 unpack_sql_state(sqlerrcode),
				 message);
	}
	tuplestore_put_text_line(tuple_store, tupdesc, sinfo.data, sinfo.len);
	resetStringInfo(&sinfo);
	if (query != NULL) 
	{
		char           *query_line;	/* pointer to beginning of current line */
		int             line_caret_pos;
		bool            is_first_line = true;
		char           *_query = pstrdup(query);
		char           *ptr;
		ptr = _query;
		query_line = ptr;
		line_caret_pos = position;
		while (*ptr != '\0')
		{
			/* search end of lines and replace '\n' by '\0' */
			if (*ptr == '\n')
			{
				*ptr = '\0';
				if (is_first_line)
				{
					appendStringInfo(&sinfo, "Query: %s", query_line);
					is_first_line = false;
				} else
					appendStringInfo(&sinfo, "       %s", query_line);
				tuplestore_put_text_line(tuple_store, tupdesc, sinfo.data, sinfo.len);
				resetStringInfo(&sinfo);
				if (line_caret_pos > 0 && position == 0)
				{
					appendStringInfo(&sinfo, "--     %*s",
						       line_caret_pos, "^");
					tuplestore_put_text_line(tuple_store, tupdesc, sinfo.data, sinfo.len);
					resetStringInfo(&sinfo);
					line_caret_pos = 0;
				}
				/* store caret position offset for next line */
				if (position > 1)
					line_caret_pos = position - 1;
				/* go to next line */
				query_line = ptr + 1;
			}
			ptr += pg_mblen(ptr);
			if (position > 0)
				position--;
		}
		/* flush last line */
		if (query_line != NULL)
		{
			if (is_first_line)
				appendStringInfo(&sinfo, "Query: %s", query_line);
			else
				appendStringInfo(&sinfo, "       %s", query_line);
			tuplestore_put_text_line(tuple_store, tupdesc, sinfo.data, sinfo.len);
			resetStringInfo(&sinfo);
			if (line_caret_pos > 0 && position == 0)
			{
				appendStringInfo(&sinfo, "--     %*s",
						 line_caret_pos, "^");
				tuplestore_put_text_line(tuple_store, tupdesc, sinfo.data, sinfo.len);
				resetStringInfo(&sinfo);
			}
		}
		pfree(_query);
	}
	if (detail != NULL)
	{
		appendStringInfo(&sinfo, "Detail: %s", detail);
		tuplestore_put_text_line(tuple_store, tupdesc, sinfo.data, sinfo.len);
		resetStringInfo(&sinfo);
	}
	if (hint != NULL)
	{
		appendStringInfo(&sinfo, "Hint: %s", hint);
		tuplestore_put_text_line(tuple_store, tupdesc, sinfo.data, sinfo.len);
		resetStringInfo(&sinfo);
	}
	if (context != NULL) 
	{
		appendStringInfo(&sinfo, "Context: %s", context);
		tuplestore_put_text_line(tuple_store, tupdesc, sinfo.data, sinfo.len);
		resetStringInfo(&sinfo);
	}
	pfree(sinfo.data);
}
/*
 * format_error_xml formats and collects a identifided issues
 */
static void
format_error_xml(StringInfo str,
						  PLpgSQL_execstate *estate,
								 int sqlerrcode,
								 int lineno,
								 const char *message,
								 const char *detail,
								 const char *hint,
								 int level,
								 int position,
								 const char *query,
								 const char *context)
{
	const char *level_str = error_level_str(level);
	Assert(message != NULL);
	/* flush tag */
	appendStringInfoString(str, "  \n");
	appendStringInfo(str, "    %s\n", level_str);
	appendStringInfo(str, "    %s\n",
						 unpack_sql_state(sqlerrcode));
	appendStringInfo(str, "    %s\n",
							 escape_xml(message));
	if (estate != NULL && estate->err_stmt != NULL)
		appendStringInfo(str, "    %s\n",
				 estate->err_stmt->lineno,
			   plpgsql_stmt_typename(estate->err_stmt));
	else if (strcmp(message, "unused declared variable") == 0)
		appendStringInfo(str, "    DECLARE\n",
				 lineno);
	if (hint != NULL)
		appendStringInfo(str, "    %s\n",
								 escape_xml(hint));
	if (detail != NULL)
		appendStringInfo(str, "    %s\n",
								 escape_xml(detail));
	if (query != NULL)
		appendStringInfo(str, "    %s\n",
							 position, escape_xml(query));
	if (context != NULL)
		appendStringInfo(str, "    %s\n",
							 escape_xml(context));
	/* flush closing tag */
	appendStringInfoString(str, "  \n");
}
/*
* format_error_json formats and collects a identifided issues
*/
static void
format_error_json(StringInfo str,
	PLpgSQL_execstate *estate,
	int sqlerrcode,
	int lineno,
	const char *message,
	const char *detail,
	const char *hint,
	int level,
	int position,
	const char *query,
	const char *context)
{
	const char *level_str = error_level_str(level);
	StringInfoData sinfo; /*Holds escaped json*/
	Assert(message != NULL);
	initStringInfo(&sinfo);
	/* flush tag */
	appendStringInfoString(str, "  {\n");
	appendStringInfo(str, "    \"level\":\"%s\",\n", level_str);
		
	escape_json(&sinfo, message);
	appendStringInfo(str, "    \"message\":%s,\n", sinfo.data);
	if (estate != NULL && estate->err_stmt != NULL)
		appendStringInfo(str, "    \"statement\":{\n\"lineNumber\":\"%d\",\n\"text\":\"%s\"\n},\n",
			estate->err_stmt->lineno,
			plpgsql_stmt_typename(estate->err_stmt));
	else if (strcmp(message, "unused declared variable") == 0)
		appendStringInfo(str, "    \"statement\":{\n\"lineNumber\":\"%d\",\n\"text\":\"DECLARE\"\n},",
			lineno);
	if (hint != NULL) {
		resetStringInfo(&sinfo);
		escape_json(&sinfo, hint);
		appendStringInfo(str, "    \"hint\":%s,\n", sinfo.data);
	}
	if (detail != NULL) {
		resetStringInfo(&sinfo);
		escape_json(&sinfo, detail);
		appendStringInfo(str, "    \"detail\":%s,\n", sinfo.data);
	}
	if (query != NULL) {
		resetStringInfo(&sinfo);
		escape_json(&sinfo, query);
		appendStringInfo(str, "    \"query\":{\n\"position\":\"%d\",\n\"text\":%s\n},\n", position, sinfo.data);
	}
	if (context != NULL) {
		resetStringInfo(&sinfo);
		escape_json(&sinfo, context);
		appendStringInfo(str, "    \"context\":%s,\n", sinfo.data);
	}
	/* placing this property last as to avoid a trailing comma*/
	appendStringInfo(str, "    \"sqlState\":\"%s\"\n",	unpack_sql_state(sqlerrcode));
	/* flush closing tag. Needs comman jus in case there is more than one issue. Comma removed in epilog */
	appendStringInfoString(str, "  },");
}
/*
 * store edata
 */
static void
put_error_edata(PLpgSQL_checkstate *cstate,
					ErrorData *edata)
{
	put_error(cstate,
				  edata->sqlerrcode,
				  edata->lineno,
				  edata->message,
				  edata->detail,
				  edata->hint,
				  PLPGSQL_CHECK_ERROR,
				  edata->internalpos,
				  edata->internalquery,
				  edata->context);
}
/*
 * Append text line (StringInfo) to one column tuple store
 *
 */
static void
tuplestore_put_text_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
								    const char *message, int len)
{
	Datum           value;
	bool            isnull = false;
	HeapTuple       tuple;
	if (len >= 0)
		value = PointerGetDatum(cstring_to_text_with_len(message, len));
	else
		value = PointerGetDatum(cstring_to_text(message));
	tuple = heap_form_tuple(tupdesc, &value, &isnull);
	tuplestore_puttuple(tuple_store, tuple);
}
/*
 * routines for beginning and finishing function checking
 *
* it is used primary for XML & JSON format - create almost left and almost right tag per function
 *
 */
static void
check_function_prolog(PLpgSQL_checkstate *cstate)
{
	/* XML format requires StringInfo buffer */
	if (cstate->format == PLPGSQL_CHECK_FORMAT_XML)
	{
		if (cstate->sinfo != NULL)
			resetStringInfo(cstate->sinfo);
		else
			cstate->sinfo = makeStringInfo();
		/* create a initial tag */
		appendStringInfo(cstate->sinfo, "\n", cstate->fn_oid);
	}
	else if (cstate->format == PLPGSQL_CHECK_FORMAT_JSON) {
		if (cstate->sinfo != NULL)
			resetStringInfo(cstate->sinfo);
		else
			cstate->sinfo = makeStringInfo();
		/* create a initial tag */
		appendStringInfo(cstate->sinfo, "{ \"function\":\"%d\",\n\"issues\":[\n", cstate->fn_oid);
	}
}
static void
check_function_epilog(PLpgSQL_checkstate *cstate)
{
	if (cstate->format == PLPGSQL_CHECK_FORMAT_XML)
	{
		appendStringInfoString(cstate->sinfo, "");
		tuplestore_put_text_line(cstate->tuple_store, cstate->tupdesc,
					    cstate->sinfo->data, cstate->sinfo->len);
	}
	else if (cstate->format == PLPGSQL_CHECK_FORMAT_JSON)
	{
		if (cstate->sinfo->len > 1 && cstate->sinfo->data[cstate->sinfo->len -1] == ',') {
			cstate->sinfo->data[cstate->sinfo->len - 1] = '\n';
		}
		appendStringInfoString(cstate->sinfo, "\n]\n}");
		tuplestore_put_text_line(cstate->tuple_store, cstate->tupdesc,
			cstate->sinfo->data, cstate->sinfo->len);
	}
}
/****************************************************************************************
 * A maintaining of hash table of checked functions
 *
 ****************************************************************************************
 *
 */
/*
 * We cannot to attach to DELETE event - so we don't need implement delete here.
 */
/* exported so we can call it from plpgsql_check_init() */
static void
plpgsql_check_HashTableInit(void)
{
	HASHCTL		ctl;
	/* don't allow double-initialization */
	Assert(plpgsql_check_HashTable == NULL);
	memset(&ctl, 0, sizeof(ctl));
	ctl.keysize = sizeof(PLpgSQL_func_hashkey);
	ctl.entrysize = sizeof(plpgsql_check_HashEnt);
	ctl.hash = tag_hash;
	plpgsql_check_HashTable = hash_create("plpgsql_check function cache",
									FUNCS_PER_USER,
									&ctl,
									HASH_ELEM | HASH_FUNCTION);
}
static bool
is_checked(PLpgSQL_function *func)
{
	plpgsql_check_HashEnt *hentry;
	hentry = (plpgsql_check_HashEnt *) hash_search(plpgsql_check_HashTable,
											 (void *) func->fn_hashkey,
											 HASH_FIND,
											 NULL);
	if (hentry != NULL && hentry->fn_xmin == func->fn_xmin &&
			  ItemPointerEquals(&hentry->fn_tid, &func->fn_tid))
		return hentry->is_checked;
	return false;
}
static void
mark_as_checked(PLpgSQL_function *func)
{
	plpgsql_check_HashEnt *hentry;
	bool		found;
	/* don't try to mark anonymous code blocks */
	if (func->fn_oid != InvalidOid)
	{
		hentry = (plpgsql_check_HashEnt *) hash_search(plpgsql_check_HashTable,
												 (void *) func->fn_hashkey,
												 HASH_ENTER,
												 &found);
		hentry->fn_xmin = func->fn_xmin;
		hentry->fn_tid = func->fn_tid;
		hentry->is_checked = true;
	}
}