plpgsql_check_function-20121006.patch

application/octet-stream

Filename: plpgsql_check_function-20121006.patch
Type: application/octet-stream
Part: 0
Message: Re: plpgsql_check_function - rebase for 9.3

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
File+
doc/src/sgml/plpgsql.sgml 0 0
src/pl/plpgsql/src/Makefile 0 0
src/pl/plpgsql/src/pl_check.c 0 0
src/pl/plpgsql/src/pl_comp.c 0 0
src/pl/plpgsql/src/pl_exec.c 0 0
src/pl/plpgsql/src/pl_handler.c 0 0
src/pl/plpgsql/src/plpgsql--1.0--1.1.sql 0 0
src/pl/plpgsql/src/plpgsql--1.1.sql 0 0
src/pl/plpgsql/src/plpgsql.control 0 0
src/pl/plpgsql/src/plpgsql.h 0 0
src/test/regress/expected/plpgsql.out 0 0
src/test/regress/sql/plpgsql.sql 0 0
src/tools/pgindent/typedefs.list 0 0
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
new file mode 100644
index 07fba57..9972bf7
*** a/doc/src/sgml/plpgsql.sgml
--- b/doc/src/sgml/plpgsql.sgml
*************** $$ LANGUAGE plpgsql;
*** 4364,4369 ****
--- 4364,4387 ----
      making it easier to recreate and debug functions.
     </para>
  
+   <sect2 id="plpgsql-check-function">
+    <title>Checking of embedded SQL</title>
+    <para>
+     The SQL statements inside <application>PL/pgSQL</> functions are
+     checked by validator for semantic errors. These errors
+     can be found by <function>plpgsql_check_function</function>:
+ <programlisting>
+ postgres=# select plpgsql_check_function('fx(int)');
+              plpgsql_check_function
+ ------------------------------------------------
+  error:42703:3:RETURN:column "b" does not exist
+  Query: SELECT (select a from t1 where b < _a)
+  --                                    ^
+                 (3 rows)
+ </programlisting>
+    </para>
+   </sect2>
+ 
    <sect2 id="plpgsql-quote-tips">
     <title>Handling of Quotation Marks</title>
  
diff --git a/src/pl/plpgsql/src/Makefile b/src/pl/plpgsql/src/Makefile
new file mode 100644
index 0db0dc5..904cd63
*** a/src/pl/plpgsql/src/Makefile
--- b/src/pl/plpgsql/src/Makefile
*************** override CPPFLAGS := -I. -I$(srcdir) $(C
*** 17,25 ****
  SHLIB_LINK = $(filter -lintl, $(LIBS))
  rpath =
  
! OBJS = pl_gram.o pl_handler.o pl_comp.o pl_exec.o pl_funcs.o pl_scanner.o
  
- DATA = plpgsql.control plpgsql--1.0.sql plpgsql--unpackaged--1.0.sql
  
  all: all-lib
  
--- 17,26 ----
  SHLIB_LINK = $(filter -lintl, $(LIBS))
  rpath =
  
! OBJS = pl_gram.o pl_handler.o pl_comp.o pl_exec.o pl_funcs.o pl_scanner.o pl_check.o
! 
! DATA = plpgsql.control plpgsql--1.0.sql plpgsql--1.1.sql plpgsql--unpackaged--1.0.sql plpgsql--1.0--1.1.sql
  
  
  all: all-lib
  
*************** uninstall-headers:
*** 52,58 ****
  
  
  # Force these dependencies to be known even without dependency info built:
! pl_gram.o pl_handler.o pl_comp.o pl_exec.o pl_funcs.o pl_scanner.o: plpgsql.h pl_gram.h plerrcodes.h
  
  # See notes in src/backend/parser/Makefile about the following two rules
  
--- 53,59 ----
  
  
  # Force these dependencies to be known even without dependency info built:
! pl_gram.o pl_handler.o pl_comp.o pl_exec.o pl_funcs.o pl_scanner.o pl_check.o : plpgsql.h pl_gram.h plerrcodes.h
  
  # See notes in src/backend/parser/Makefile about the following two rules
  
diff --git a/src/pl/plpgsql/src/pl_check.c b/src/pl/plpgsql/src/pl_check.c
new file mode 100644
index ...80d0303
*** a/src/pl/plpgsql/src/pl_check.c
--- b/src/pl/plpgsql/src/pl_check.c
***************
*** 0 ****
--- 1,1703 ----
+ /*-------------------------------------------------------------------------
+  *
+  * pl_checker.c		- Checker for the PL/pgSQL
+  *			  procedural language
+  *
+  * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  *
+  * IDENTIFICATION
+  *	  src/pl/plpgsql/src/pl_check.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #include "plpgsql.h"
+ 
+ #include "funcapi.h"
+ #include "miscadmin.h"
+ 
+ #include "access/htup_details.h"
+ #include "catalog/pg_proc.h"
+ #include "catalog/pg_type.h"
+ #include "executor/spi_priv.h"
+ #include "mb/pg_wchar.h"
+ #include "utils/builtins.h"
+ #include "utils/guc.h"
+ #include "utils/lsyscache.h"
+ #include "utils/syscache.h"
+ #include "utils/typcache.h"
+ #include "utils/rel.h"
+ #include "utils/xml.h"
+ 
+ static void     check_row_or_rec(PLpgSQL_checkstate * cstate, PLpgSQL_row * row, PLpgSQL_rec * rec);
+ static void     check_expr(PLpgSQL_checkstate * cstate, PLpgSQL_expr * expr);
+ static void     assign_tupdesc_row_or_rec(PLpgSQL_checkstate * cstate,
+ 			  PLpgSQL_row * row, PLpgSQL_rec * rec,
+ 			  TupleDesc tupdesc);
+ static void     assign_tupdesc_dno(PLpgSQL_checkstate * cstate, int varno, TupleDesc tupdesc);
+ static TupleDesc expr_get_desc(PLpgSQL_checkstate * cstate,
+ 	      PLpgSQL_expr * query,
+ 	      bool use_element_type,
+ 	      bool expand_record,
+ 	      bool is_expression);
+ static void     init_datum(PLpgSQL_checkstate * cstate, int varno);
+ static void     check_stmts(PLpgSQL_checkstate * cstate, List * stmts);
+ static void     check_stmt(PLpgSQL_checkstate * cstate, PLpgSQL_stmt * stmt);
+ static void     prepare_expr(PLpgSQL_checkstate * cstate,
+ 	     PLpgSQL_expr * expr, int cursorOptions);
+ static void     check_assignment(PLpgSQL_checkstate * cstate, PLpgSQL_expr * expr,
+ 		 PLpgSQL_rec * targetrec, PLpgSQL_row * targetrow,
+ 		 int targetdno);
+ static void     check_element_assignment(PLpgSQL_checkstate * cstate, PLpgSQL_expr * expr,
+ 			 PLpgSQL_rec * targetrec, PLpgSQL_row * targetrow,
+ 			 int targetdno);
+ static void     check_assignment_guts(PLpgSQL_checkstate * cstate, PLpgSQL_expr * expr,
+ 		      PLpgSQL_rec * targetrec, PLpgSQL_row * targetrow,
+ 		  int targetdno, bool use_element_type, bool is_expression);
+ static void     checker_error_edata(PLpgSQL_checkstate * cstate, ErrorData * edata);
+ 
+ static void     checker_error(PLpgSQL_checkstate * cstate,
+ 	      int sqlerrcode,
+ 	      const char *message, const char *detail,
+ 	      const char *hint, const char *level,
+ 	      int position, const char *query,
+ 	      const char *context);
+ 
+ static void     cstate_setup(PLpgSQL_checkstate * cstate,
+ 	     TupleDesc tupdesc, Tuplestorestate * tupstore,
+ 	     bool fatal_errors, int format);
+ static void     cstate_flush(PLpgSQL_checkstate * cstate);
+ static void     destroy_cstate(PLpgSQL_checkstate * cstate);
+ static void     function_check(PLpgSQL_function * func, FunctionCallInfo fcinfo,
+ 	       PLpgSQL_execstate * estate, PLpgSQL_checkstate * cstate);
+ static void     trigger_check(PLpgSQL_function * func,
+ 	      TriggerData * trigdata,
+ 	      PLpgSQL_execstate * estate, PLpgSQL_checkstate * cstate);
+ static int      load_configuration(HeapTuple procTuple, bool * reload_config);
+ 
+ /*
+  * Top checker function
+  *
+  */
+ void
+ plpgsql_function_check(HeapTuple procTuple, Oid relid,
+ 		       TupleDesc tupdesc,
+ 		       Tuplestorestate * tupstore,
+ 		       bool fatal_errors, int format)
+ {
+ 	PLpgSQL_checkstate cstate;
+ 	PLpgSQL_function *volatile function = NULL;
+ 	int             save_nestlevel = 0;
+ 	bool            reload_config;
+ 	Oid             funcoid;
+ 	FunctionCallInfoData fake_fcinfo;
+ 	FmgrInfo        flinfo;
+ 	TriggerData     trigdata;
+ 	int             rc;
+ 	ResourceOwner   oldowner;
+ 	MemoryContext   oldCxt;
+ 	PLpgSQL_execstate *cur_estate = NULL;
+ 	int             numargs;
+ 	Oid            *argtypes;
+ 	char          **argnames;
+ 	char           *argmodes;
+ 	int             i;
+ 
+ 	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));
+ 
+ 	plpgsql_setup_fake_fcinfo(&flinfo, &fake_fcinfo, &trigdata, NULL, funcoid, OidIsValid(relid), false);
+ 	cstate_setup(&cstate, tupdesc, tupstore, fatal_errors, format);
+ 
+ 	if (OidIsValid(relid))
+ 		trigdata.tg_relation = relation_open(relid, AccessShareLock);
+ 
+ 	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]);
+ 		}
+ 	}
+ 	oldCxt = CurrentMemoryContext;
+ 	oldowner = CurrentResourceOwner;
+ 
+ 	PG_TRY();
+ 	{
+ 		PLpgSQL_execstate estate;
+ 
+ 		BeginInternalSubTransaction(NULL);
+ 		MemoryContextSwitchTo(oldCxt);
+ 
+ 		save_nestlevel = load_configuration(procTuple, &reload_config);
+ 
+ 		/* Get a compiled function */
+ 		function = plpgsql_compile(&fake_fcinfo, false);
+ 
+ 		plpgsql_estate_setup(&estate, function, (ReturnSetInfo *) fake_fcinfo.resultinfo);
+ 		cstate.estate = &estate;
+ 
+ 		/* Must save and restore prior value of cur_estate */
+ 		cur_estate = function->cur_estate;
+ 
+ 		/*
+ 		 * Mark the function as busy, so it can't be deleted from
+ 		 * under us
+ 		 */
+ 		function->use_count++;
+ 
+ 		/* Create a fake runtime environment and process check */
+ 		if (!OidIsValid(relid))
+ 			function_check(function, &fake_fcinfo, &estate, &cstate);
+ 		else
+ 			trigger_check(function, &trigdata, &estate, &cstate);
+ 
+ 		/*
+ 		 * reload back a GUC. XXX: isn't this done automatically by
+ 		 * subxact rollback?
+ 		 */
+ 		if (reload_config)
+ 			AtEOXact_GUC(true, save_nestlevel);
+ 
+ 		plpgsql_destroy_econtext(&estate);
+ 
+ 		RollbackAndReleaseCurrentSubTransaction();
+ 		MemoryContextSwitchTo(oldCxt);
+ 		CurrentResourceOwner = oldowner;
+ 
+ 		SPI_restore_connection();
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		ErrorData      *edata;
+ 
+ 		MemoryContextSwitchTo(oldCxt);
+ 		edata = CopyErrorData();
+ 		FlushErrorState();
+ 
+ 		RollbackAndReleaseCurrentSubTransaction();
+ 		MemoryContextSwitchTo(oldCxt);
+ 		CurrentResourceOwner = oldowner;
+ 
+ 		checker_error_edata(&cstate, edata);
+ 		MemoryContextSwitchTo(oldCxt);
+ 		/* reconnect spi */
+ 		SPI_restore_connection();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	if (function) {
+ 		function->cur_estate = cur_estate;
+ 		function->use_count--;
+ 
+ 		/*
+ 		 * We cannot to preserve instance of this function, because
+ 		 * expressions are not consistent - a tests on simple
+ 		 * expression was be processed newer.
+ 		 */
+ 		plpgsql_delete_function(function);
+ 	}
+ 	if (OidIsValid(relid))
+ 		relation_close(trigdata.tg_relation, AccessShareLock);
+ 
+ 	cstate_flush(&cstate);
+ 
+ 	/* Cleanup temporary memory */
+ 	destroy_cstate(&cstate);
+ 
+ 	/*
+ 	 * 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;
+ 
+ 	/*
+ 	 * 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(cstate, func->fn_argvarnos[i]);
+ 	}
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(cstate, (PLpgSQL_stmt *) func->action);
+ }
+ 
+ /*
+  * Check trigger - prepare fake environments for testing trigger
+  *
+  */
+ static void
+ trigger_check(PLpgSQL_function * func, TriggerData * trigdata,
+ 	      PLpgSQL_execstate * estate, PLpgSQL_checkstate * cstate)
+ {
+ 	PLpgSQL_rec    *rec_new, *rec_old;
+ 	int             i;
+ 
+ 	/*
+ 	 * 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]);
+ 
+ 	/*
+ 	 * 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);
+ 
+ 	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);
+ 
+ 	/*
+ 	 * Assign the special tg_ variables
+ 	 */
+ 	init_datum(cstate, func->tg_op_varno);
+ 	init_datum(cstate, func->tg_name_varno);
+ 	init_datum(cstate, func->tg_when_varno);
+ 	init_datum(cstate, func->tg_level_varno);
+ 	init_datum(cstate, func->tg_relid_varno);
+ 	init_datum(cstate, func->tg_relname_varno);
+ 	init_datum(cstate, func->tg_table_name_varno);
+ 	init_datum(cstate, func->tg_table_schema_varno);
+ 	init_datum(cstate, func->tg_nargs_varno);
+ 	init_datum(cstate, func->tg_argv_varno);
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(cstate, (PLpgSQL_stmt *) func->action);
+ }
+ 
+ /*
+  * Verify lvalue It doesn't repeat a checks that are done. Checks a subscript
+  * expressions, verify a validity of record's fields, Returns true, when
+  * target is valid
+  */
+ static void
+ check_target(PLpgSQL_checkstate * cstate, int varno)
+ {
+ 	PLpgSQL_datum  *target = cstate->estate->datums[varno];
+ 
+ 	switch (target->dtype) {
+ 	case PLPGSQL_DTYPE_VAR:
+ 	case PLPGSQL_DTYPE_REC:
+ 		/* nothing to check */
+ 		break;
+ 
+ 	case PLPGSQL_DTYPE_ROW:
+ 		check_row_or_rec(cstate, (PLpgSQL_row *) target, 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)));
+ 		}
+ 		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
+ 			 */
+ 			arraytypeid = exec_get_datum_type(cstate->estate, target);
+ 			arraytypeid = getBaseType(arraytypeid);
+ 
+ 			arrayelemtypeid = get_element_type(arraytypeid);
+ 
+ 			if (!OidIsValid(arrayelemtypeid))
+ 				ereport(ERROR,
+ 					(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 					 errmsg("subscripted object is not an array")));
+ 		}
+ 		break;
+ 	}
+ }
+ 
+ /*
+  * 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;
+ 
+ 	/* there are nothing to check on rec now */
+ 	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]);
+ 		}
+ 	}
+ }
+ 
+ /*
+  * 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)
+ 		return;		/* already checked */
+ 
+ 	/*
+ 	 * 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));
+ 		}
+ 	}
+ 	expr->plan = SPI_saveplan(plan);
+ 	SPI_freeplan(plan);
+ }
+ 
+ /*
+  * Verify a expression
+  */
+ static void
+ check_expr(PLpgSQL_checkstate * cstate, PLpgSQL_expr * expr)
+ {
+ 	if (expr)
+ 		check_assignment_guts(cstate, expr, NULL, NULL, -1, false, false);
+ }
+ 
+ /*
+  * 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_assignment_guts(cstate, expr, targetrec, targetrow, targetdno, false,
+ 			      is_expression);
+ }
+ 
+ static void
+ check_element_assignment(PLpgSQL_checkstate * cstate, PLpgSQL_expr * expr,
+ 			 PLpgSQL_rec * targetrec, PLpgSQL_row * targetrow,
+ 			 int targetdno)
+ {
+ 	bool            is_expression = (targetrec == NULL && targetrow == NULL);
+ 
+ 	check_assignment_guts(cstate, expr, targetrec, targetrow, targetdno, true,
+ 			      is_expression);
+ }
+ 
+ static void
+ check_assignment_guts(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;
+ 
+ 	oldowner = CurrentResourceOwner;
+ 	BeginInternalSubTransaction(NULL);
+ 	MemoryContextSwitchTo(oldCxt);
+ 
+ 	PG_TRY();
+ 	{
+ 		prepare_expr(cstate, expr, 0);
+ 		tupdesc = expr_get_desc(cstate, expr, use_element_type, true, is_expression);
+ 		if (tupdesc) {
+ 			if (targetrow != NULL || targetrec != NULL)
+ 				assign_tupdesc_row_or_rec(cstate, targetrow, targetrec, tupdesc);
+ 			if (targetdno != -1)
+ 				assign_tupdesc_dno(cstate, targetdno, tupdesc);
+ 
+ 			if (targetrow) {
+ 				if (targetrow->nfields > tupdesc->natts)
+ 					checker_error(cstate,
+ 						      0,
+ 						      "too few attributies for target variables",
+ 						      "There are more target variables than output columns in query.",
+ 						      "Check target variables in SELECT INTO statement.",
+ 						      "warning",
+ 						      0, NULL, NULL);
+ 				else if (targetrow->nfields < tupdesc->natts)
+ 					checker_error(cstate,
+ 						      0,
+ 						      "too many attributies for target variables",
+ 						      "There are less target variables than output columns in query.",
+ 						      "Check target variables in SELECT INTO statement",
+ 						      "warning",
+ 						      0, NULL, 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
+ 			checker_error_edata(cstate, edata);
+ 		MemoryContextSwitchTo(oldCxt);
+ 
+ 		/* reconnect spi */
+ 		SPI_restore_connection();
+ 	}
+ 	PG_END_TRY();
+ }
+ 
+ /*
+  * 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           *nulls;
+ 	HeapTuple       tup;
+ 
+ 	if (tupdesc == NULL) {
+ 		checker_error(cstate,
+ 			      0,
+ 			      "tuple descriptor is empty", NULL, NULL,
+ 			      "warning",
+ 			      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");
+ 	}
+ }
+ 
+ /*
+  * Assign a tuple descriptor to variable specified by dno
+  */
+ static void
+ assign_tupdesc_dno(PLpgSQL_checkstate * cstate, int varno, TupleDesc tupdesc)
+ {
+ 	PLpgSQL_datum  *target = cstate->estate->datums[varno];
+ 
+ 	if (target->dtype == PLPGSQL_DTYPE_REC)
+ 		assign_tupdesc_row_or_rec(cstate, NULL, (PLpgSQL_rec *) target, tupdesc);
+ }
+ 
+ /*
+  * 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)
+ {
+ 	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);
+ 
+ 	/*
+ 	 * 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(tupdesc->attrs[0]->atttypid);
+ 		if (!OidIsValid(elemtype)) {
+ 			ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("FOREACH expression must yield an array, not type %s",
+ 			     format_type_be(tupdesc->attrs[0]->atttypid))));
+ 			FreeTupleDesc(tupdesc);
+ 		}
+ 		/* we can't know typmod now */
+ 		elemtupdesc = lookup_rowtype_tupdesc_noerror(elemtype, -1, true);
+ 		if (elemtupdesc != NULL) {
+ 			FreeTupleDesc(tupdesc);
+ 			tupdesc = CreateTupleDescCopy(elemtupdesc);
+ 			ReleaseTupleDesc(elemtupdesc);
+ 		} else
+ 			/* XXX: should be a warning? */
+ 			ereport(ERROR,
+ 				(errmsg("cannot to identify real type for record type variable")));
+ 	}
+ 	if (is_expression && tupdesc->natts != 1)
+ 		ereport(ERROR,
+ 			(errcode(ERRCODE_SYNTAX_ERROR),
+ 			 errmsg("query \"%s\" returned %d columns",
+ 				query->query,
+ 				tupdesc->natts)));
+ 
+ 	/*
+ 	 * 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(tupdesc->attrs[0]->atttypid,
+ 					       tupdesc->attrs[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 &&
+ 	    tupdesc->attrs[0]->atttypid == RECORDOID &&
+ 	    tupdesc->attrs[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.
+ 		 */
+ 		cplan = GetCachedPlan(plansource, NULL, true);
+ 		_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_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;
+ 				}
+ 			}
+ 		}
+ 		ReleaseCachedPlan(cplan, true);
+ 	}
+ 	return tupdesc;
+ }
+ 
+ /*
+  * Ensure check for all statements in list
+  */
+ void
+ check_stmts(PLpgSQL_checkstate * cstate, List * stmts)
+ {
+ 	ListCell       *lc;
+ 
+ 	foreach(lc, stmts) {
+ 		check_stmt(cstate, (PLpgSQL_stmt *) lfirst(lc));
+ 	}
+ }
+ 
+ /*
+  * 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;
+ 	}
+ }
+ 
+ /*
+  * walk over all statements
+  */
+ void
+ check_stmt(PLpgSQL_checkstate * cstate, PLpgSQL_stmt * stmt)
+ {
+ 	TupleDesc       tupdesc = NULL;
+ 	PLpgSQL_function *func;
+ 	ListCell       *l;
+ 	ResourceOwner   oldowner;
+ 	MemoryContext   oldCxt = CurrentMemoryContext;
+ 
+ 	if (stmt == NULL)
+ 		return;
+ 
+ 	cstate->estate->err_stmt = stmt;
+ 	func = cstate->estate->func;
+ 
+ 	oldowner = CurrentResourceOwner;
+ 	BeginInternalSubTransaction(NULL);
+ 	MemoryContextSwitchTo(oldCxt);
+ 
+ 	PG_TRY();
+ 	{
+ 		switch ((enum 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);
+ 
+ 								checker_error(cstate,
+ 									  0,
+ 								   str.data,
+ 									      "Local variable overlap function parameter.",
+ 								       NULL,
+ 								  "warning",
+ 									      0, NULL, NULL);
+ 								pfree(str.data);
+ 							}
+ 						}
+ 					}
+ 				}
+ 
+ 				check_stmts(cstate, stmt_block->body);
+ 
+ 				if (stmt_block->exceptions) {
+ 					foreach(l, stmt_block->exceptions->exc_list) {
+ 						check_stmts(cstate, ((PLpgSQL_exception *) lfirst(l))->action);
+ 					}
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_ASSIGN:
+ 			{
+ 				PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt;
+ 
+ 				check_target(cstate, stmt_assign->varno);
+ 
+ 				/* prepare plan if desn't exist yet */
+ 				check_assignment(cstate, stmt_assign->expr, NULL, NULL,
+ 						 stmt_assign->varno);
+ 				/*
+ 				 * XXX: i thínk I lost some args to
+ 				 * prepare_expr here
+ 				 */
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_IF:
+ 			{
+ 				PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt;
+ 				ListCell       *l;
+ 
+ 				check_expr(cstate, stmt_if->cond);
+ 				check_stmts(cstate, stmt_if->then_body);
+ 				foreach(l, stmt_if->elsif_list) {
+ 					PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l);
+ 
+ 					check_expr(cstate, elif->cond);
+ 					check_stmts(cstate, elif->stmts);
+ 				}
+ 
+ 				check_stmts(cstate, stmt_if->else_body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_CASE:
+ 			{
+ 				PLpgSQL_stmt_case *stmt_case = (PLpgSQL_stmt_case *) stmt;
+ 				Oid             result_oid;
+ 
+ 				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 */
+ 					result_oid = tupdesc->attrs[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);
+ 				}
+ 
+ 				check_stmts(cstate, stmt_case->else_stmts);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_LOOP:
+ 			check_stmts(cstate, ((PLpgSQL_stmt_loop *) stmt)->body);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_WHILE:
+ 			{
+ 				PLpgSQL_stmt_while *stmt_while = (PLpgSQL_stmt_while *) stmt;
+ 
+ 				check_expr(cstate, stmt_while->cond);
+ 				check_stmts(cstate, stmt_while->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORI:
+ 			{
+ 				PLpgSQL_stmt_fori *stmt_fori = (PLpgSQL_stmt_fori *) stmt;
+ 
+ 				check_expr(cstate, stmt_fori->lower);
+ 				check_expr(cstate, stmt_fori->upper);
+ 				check_expr(cstate, stmt_fori->step);
+ 				check_stmts(cstate, stmt_fori->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORS:
+ 			{
+ 				PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt;
+ 
+ 				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);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORC:
+ 			{
+ 				PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt;
+ 				PLpgSQL_var    *var = (PLpgSQL_var *) func->datums[stmt_forc->curvar];
+ 
+ 				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);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNFORS:
+ 			{
+ 				PLpgSQL_stmt_dynfors *stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt;
+ 
+ 				if (stmt_dynfors->rec != NULL) {
+ 					checker_error(cstate,
+ 						      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.",
+ 						      "warning",
+ 						      0, NULL, NULL);
+ 
+ 					/*
+ 					 * don't continue in checking. Behave
+ 					 * should be indeterministic.
+ 					 */
+ 					break;
+ 				}
+ 				check_expr(cstate, stmt_dynfors->query);
+ 
+ 				foreach(l, stmt_dynfors->params) {
+ 					check_expr(cstate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				check_stmts(cstate, stmt_dynfors->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FOREACH_A:
+ 			{
+ 				PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt;
+ 
+ 				check_target(cstate, stmt_foreach_a->varno);
+ 
+ 				check_element_assignment(cstate, stmt_foreach_a->expr, NULL, NULL, stmt_foreach_a->varno);
+ 
+ 				check_stmts(cstate, stmt_foreach_a->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_EXIT:
+ 			check_expr(cstate, ((PLpgSQL_stmt_exit *) stmt)->cond);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_PERFORM:
+ 			check_expr(cstate, ((PLpgSQL_stmt_perform *) stmt)->expr);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN:
+ 			check_expr(cstate, ((PLpgSQL_stmt_return *) stmt)->expr);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN_NEXT:
+ 			check_expr(cstate, ((PLpgSQL_stmt_return_next *) stmt)->expr);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN_QUERY:
+ 			{
+ 				PLpgSQL_stmt_return_query *stmt_rq = (PLpgSQL_stmt_return_query *) stmt;
+ 
+ 				check_expr(cstate, stmt_rq->dynquery);
+ 
+ 				check_expr(cstate, stmt_rq->query);
+ 
+ 				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;
+ 
+ 				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);
+ 				}
+ 
+ 				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")));
+ 			}
+ 			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(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) {
+ 						checker_error(cstate,
+ 							      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.",
+ 							      "warning",
+ 							      0, NULL, NULL);
+ 
+ 						/*
+ 						 * don't continue in
+ 						 * checking. Behave should be
+ 						 * indeterministic.
+ 						 */
+ 						break;
+ 					}
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_OPEN:
+ 			{
+ 				PLpgSQL_stmt_open *stmt_open = (PLpgSQL_stmt_open *) stmt;
+ 				PLpgSQL_var    *var = (PLpgSQL_var *) func->datums[stmt_open->curvar];
+ 
+ 				if (var->cursor_explicit_expr)
+ 					check_expr(cstate, var->cursor_explicit_expr);
+ 
+ 				check_expr(cstate, 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));
+ 				}
+ 			}
+ 			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);
+ 				}
+ 			}
+ 			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);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_CLOSE:
+ 			break;
+ 
+ 		default:
+ 			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
+ 		}
+ 
+ 		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
+ 			checker_error_edata(cstate, edata);
+ 		MemoryContextSwitchTo(oldCxt);
+ 
+ 		/* reconnect spi */
+ 		SPI_restore_connection();
+ 	}
+ 	PG_END_TRY();
+ }
+ 
+ /*
+  * 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(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(cstate, row->varnos[fnum]);
+ 			}
+ 		}
+ 		break;
+ 
+ 	default:
+ 		elog(ERROR, "unexpected dtype: %d", cstate->estate->datums[dno]->dtype);
+ 	}
+ }
+ 
+ /*
+  * forward edata out from checker
+  */
+ static void
+ checker_error_edata(PLpgSQL_checkstate * cstate,
+ 		    ErrorData * edata)
+ {
+ 	checker_error(cstate,
+ 		      edata->sqlerrcode,
+ 		      edata->message,
+ 		      edata->detail,
+ 		      edata->hint,
+ 		      "error",
+ 		      edata->internalpos,
+ 		      edata->internalquery,
+ 		      edata->context);
+ }
+ 
+ /*
+  * Append text line (StringInfo) to tuple store.
+  */
+ static void
+ checker_store_string(PLpgSQL_checkstate * cstate, StringInfo str)
+ {
+ 	Datum           value;
+ 	bool            isnull = false;
+ 	HeapTuple       tuple;
+ 
+ 	value = PointerGetDatum(cstring_to_text_with_len(str->data, str->len));
+ 	tuple = heap_form_tuple(cstate->tupdesc, &value, &isnull);
+ 
+ 	tuplestore_puttuple(cstate->tuple_store, tuple);
+ 
+ 	resetStringInfo(str);
+ }
+ 
+ /*
+  * prepare PLpgSQL_checkstate structure
+  */
+ static void
+ cstate_setup(PLpgSQL_checkstate * cstate,
+ 	     TupleDesc tupdesc,
+ 	     Tuplestorestate * tupstore,
+ 	     bool fatal_errors,
+ 	     int format)
+ {
+ 	cstate->estate = NULL;
+ 	cstate->tupdesc = tupdesc;
+ 	cstate->tuple_store = tupstore;
+ 	cstate->fatal_errors = fatal_errors;
+ 	cstate->format = format;
+ 	cstate->argnames = NIL;
+ 
+ 	if (format != PLPGSQL_CHECK_FORMAT_PLAIN)
+ 		cstate->sinfo = makeStringInfo();
+ 	else
+ 		cstate->sinfo = NULL;
+ 
+ 	/* put initial tag */
+ 	if (cstate->format == PLPGSQL_CHECK_FORMAT_XML)
+ 		appendStringInfoString(cstate->sinfo, "<Function>\n");
+ }
+ 
+ /*
+  * finishig a result stored in cstate
+  */
+ static void
+ cstate_flush(PLpgSQL_checkstate * cstate)
+ {
+ 	if (cstate->format == PLPGSQL_CHECK_FORMAT_XML)
+ 		appendStringInfoString(cstate->sinfo, "</Function>");
+ 
+ 	if (cstate->format != PLPGSQL_CHECK_FORMAT_PLAIN)
+ 		checker_store_string(cstate, cstate->sinfo);
+ }
+ 
+ /*
+  * release check state
+  */
+ static void
+ destroy_cstate(PLpgSQL_checkstate * cstate)
+ {
+ 	if (cstate->sinfo != NULL) {
+ 		if (cstate->sinfo->data != NULL)
+ 			pfree(cstate->sinfo->data);
+ 		pfree(cstate->sinfo);
+ 
+ 		cstate->sinfo = NULL;
+ 	}
+ }
+ 
+ /*
+  * collects errors and warnings in plain text format
+  */
+ static void
+ checker_error_plain(PLpgSQL_checkstate * cstate,
+ 		    int sqlerrcode,
+ 		    const char *message,
+ 		    const char *detail,
+ 		    const char *hint,
+ 		    const char *level,
+ 		    int position,
+ 		    const char *query,
+ 		    const char *context)
+ {
+ 	StringInfoData  sinfo;
+ 
+ 	initStringInfo(&sinfo);
+ 
+ 	Assert(message != NULL);
+ 	Assert(level != NULL);
+ 
+ 	if (cstate->estate && cstate->estate->err_stmt != NULL)
+ 		appendStringInfo(&sinfo, "%s:%s:%d:%s:%s",
+ 				 level,
+ 				 unpack_sql_state(sqlerrcode),
+ 				 cstate->estate->err_stmt->lineno,
+ 			    plpgsql_stmt_typename(cstate->estate->err_stmt),
+ 				 message);
+ 	else
+ 		appendStringInfo(&sinfo, "%s:%s:%s",
+ 				 level,
+ 				 unpack_sql_state(sqlerrcode),
+ 				 message);
+ 
+ 	checker_store_string(cstate, &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);
+ 
+ 				checker_store_string(cstate, &sinfo);
+ 
+ 				if (line_caret_pos > 0 && position == 0) {
+ 					appendStringInfo(&sinfo, "--     %*s",
+ 						       line_caret_pos, "^");
+ 					checker_store_string(cstate, &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);
+ 
+ 			checker_store_string(cstate, &sinfo);
+ 
+ 			if (line_caret_pos > 0 && position == 0) {
+ 				appendStringInfo(&sinfo, "--     %*s",
+ 						 line_caret_pos, "^");
+ 				checker_store_string(cstate, &sinfo);
+ 			}
+ 		}
+ 		pfree(_query);
+ 	}
+ 	if (detail != NULL) {
+ 		appendStringInfo(&sinfo, "Detail: %s", detail);
+ 		checker_store_string(cstate, &sinfo);
+ 	}
+ 	if (hint != NULL) {
+ 		appendStringInfo(&sinfo, "Hint: %s", hint);
+ 		checker_store_string(cstate, &sinfo);
+ 	}
+ 	if (context != NULL) {
+ 		appendStringInfo(&sinfo, "Context: %s", context);
+ 		checker_store_string(cstate, &sinfo);
+ 	}
+ 	pfree(sinfo.data);
+ }
+ 
+ /*
+  * checker_error_xml formats and collects a identifided issues
+  */
+ static void
+ checker_error_xml(PLpgSQL_checkstate * cstate,
+ 		  int sqlerrcode,
+ 		  const char *message,
+ 		  const char *detail,
+ 		  const char *hint,
+ 		  const char *level,
+ 		  int position,
+ 		  const char *query,
+ 		  const char *context)
+ {
+ 	Assert(message != NULL);
+ 	Assert(level != NULL);
+ 
+ 	/* there have to be prepared StringInfo for result */
+ 	Assert(cstate->sinfo != NULL);
+ 
+ 	/* flush tag */
+ 	appendStringInfoString(cstate->sinfo, "  <Issue>\n");
+ 
+ 	appendStringInfo(cstate->sinfo, "    <Level>%s</level>\n", level);
+ 	appendStringInfo(cstate->sinfo, "    <Sqlstate>%s</Sqlstate>\n",
+ 			 unpack_sql_state(sqlerrcode));
+ 	appendStringInfo(cstate->sinfo, "    <Message>%s</Message>\n",
+ 			 escape_xml(message));
+ 	if (cstate->estate->err_stmt != NULL)
+ 		appendStringInfo(cstate->sinfo, "    <Stmt lineno=\"%d\">%s</Stmt>\n",
+ 				 cstate->estate->err_stmt->lineno,
+ 			   plpgsql_stmt_typename(cstate->estate->err_stmt));
+ 	if (hint != NULL)
+ 		appendStringInfo(cstate->sinfo, "    <Hint>%s</Hint>\n",
+ 				 escape_xml(hint));
+ 	if (detail != NULL)
+ 		appendStringInfo(cstate->sinfo, "    <Detail>%s</Detail>\n",
+ 				 escape_xml(detail));
+ 	if (query != NULL)
+ 		appendStringInfo(cstate->sinfo, "    <Query position=\"%d\">%s</Query>\n",
+ 				 position, escape_xml(query));
+ 	if (context != NULL)
+ 		appendStringInfo(cstate->sinfo, "    <Context>%s</Context>\n",
+ 				 escape_xml(context));
+ 
+ 	/* flush closing tag */
+ 	appendStringInfoString(cstate->sinfo, "  </Issue>\n");
+ }
+ 
+ /*
+  * checker_error formats and collects a identifided issues
+  */
+ static void
+ checker_error(PLpgSQL_checkstate * cstate,
+ 	      int sqlerrcode,
+ 	      const char *message,
+ 	      const char *detail,
+ 	      const char *hint,
+ 	      const char *level,
+ 	      int position,
+ 	      const char *query,
+ 	      const char *context)
+ {
+ 	if (cstate->format == PLPGSQL_CHECK_FORMAT_PLAIN)
+ 		checker_error_plain(cstate, sqlerrcode,
+ 				    message, detail, hint, level,
+ 				    position, query,
+ 				    context);
+ 	else if (cstate->format == PLPGSQL_CHECK_FORMAT_XML)
+ 		checker_error_xml(cstate, sqlerrcode,
+ 				  message, detail, hint, level,
+ 				  position, query,
+ 				  context);
+ }
+ 
+ /*
+  * 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;
+ }
+ 
+ /*
+  * Set up a fake fcinfo with just enough info to satisfy plpgsql_compile().
+  *
+  * There should be a different real argtypes for polymorphic params.
+  */
+ void
+ plpgsql_setup_fake_fcinfo(FmgrInfo * flinfo,
+ 			  FunctionCallInfoData * fcinfo,
+ 			  TriggerData * trigdata,
+ 			  EventTriggerData * etrigdata,
+ 			  Oid funcoid,
+ 			  bool is_dml_trigger,
+ 			  bool is_event_trigger)
+ {
+ 	/* clean structures */
+ 	MemSet(fcinfo, 0, sizeof(FunctionCallInfoData));
+ 	MemSet(flinfo, 0, sizeof(FmgrInfo));
+ 
+ 	fcinfo->flinfo = flinfo;
+ 	flinfo->fn_oid = funcoid;
+ 	flinfo->fn_mcxt = CurrentMemoryContext;
+ 
+ 	if (is_dml_trigger) {
+ 		Assert(trigdata != NULL);
+ 
+ 		MemSet(trigdata, 0, sizeof(trigdata));
+ 		trigdata->type = T_TriggerData;
+ 		fcinfo->context = (Node *) trigdata;
+ 	} else if (is_event_trigger) {
+ 		MemSet(etrigdata, 0, sizeof(etrigdata));
+ 		etrigdata->type = T_EventTriggerData;
+ 		fcinfo->context = (Node *) etrigdata;
+ 	}
+ }
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
new file mode 100644
index 35f5721..58382b2
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
*************** static PLpgSQL_function *plpgsql_HashTab
*** 116,122 ****
  static void plpgsql_HashTableInsert(PLpgSQL_function *function,
  						PLpgSQL_func_hashkey *func_key);
  static void plpgsql_HashTableDelete(PLpgSQL_function *function);
- static void delete_function(PLpgSQL_function *func);
  
  /* ----------
   * plpgsql_compile		Make an execution tree for a PL/pgSQL function.
--- 116,121 ----
*************** recheck:
*** 176,182 ****
  			 * Nope, so remove it from hashtable and try to drop associated
  			 * storage (if not done already).
  			 */
! 			delete_function(function);
  
  			/*
  			 * If the function isn't in active use then we can overwrite the
--- 175,181 ----
  			 * Nope, so remove it from hashtable and try to drop associated
  			 * storage (if not done already).
  			 */
! 			plpgsql_delete_function(function);
  
  			/*
  			 * If the function isn't in active use then we can overwrite the
*************** plpgsql_resolve_polymorphic_argtypes(int
*** 2465,2471 ****
  }
  
  /*
!  * delete_function - clean up as much as possible of a stale function cache
   *
   * We can't release the PLpgSQL_function struct itself, because of the
   * possibility that there are fn_extra pointers to it.	We can release
--- 2464,2470 ----
  }
  
  /*
!  * plpgsql_delete_function - clean up as much as possible of a stale function cache
   *
   * We can't release the PLpgSQL_function struct itself, because of the
   * possibility that there are fn_extra pointers to it.	We can release
*************** plpgsql_resolve_polymorphic_argtypes(int
*** 2478,2485 ****
   * pointers to the same function cache.  Hence be careful not to do things
   * twice.
   */
! static void
! delete_function(PLpgSQL_function *func)
  {
  	/* remove function from hash table (might be done already) */
  	plpgsql_HashTableDelete(func);
--- 2477,2484 ----
   * pointers to the same function cache.  Hence be careful not to do things
   * twice.
   */
! void
! plpgsql_delete_function(PLpgSQL_function *func)
  {
  	/* remove function from hash table (might be done already) */
  	plpgsql_HashTableDelete(func);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
new file mode 100644
index 3b5b3bb..6eac4f7
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
*************** static SimpleEcontextStackEntry *simple_
*** 81,87 ****
   * Local function forward declarations
   ************************************************************/
  static void plpgsql_exec_error_callback(void *arg);
- static PLpgSQL_datum *copy_plpgsql_datum(PLpgSQL_datum *datum);
  
  static int exec_stmt_block(PLpgSQL_execstate *estate,
  				PLpgSQL_stmt_block *block);
--- 81,86 ----
*************** static int exec_stmt_dynexecute(PLpgSQL_
*** 134,142 ****
  static int exec_stmt_dynfors(PLpgSQL_execstate *estate,
  				  PLpgSQL_stmt_dynfors *stmt);
  
- static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
- 					 PLpgSQL_function *func,
- 					 ReturnSetInfo *rsi);
  static void exec_eval_cleanup(PLpgSQL_execstate *estate);
  
  static void exec_prepare_plan(PLpgSQL_execstate *estate,
--- 133,138 ----
*************** static Datum exec_simple_cast_value(PLpg
*** 205,211 ****
  static void exec_init_tuple_store(PLpgSQL_execstate *estate);
  static void exec_set_found(PLpgSQL_execstate *estate, bool state);
  static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
- static void plpgsql_destroy_econtext(PLpgSQL_execstate *estate);
  static void free_var(PLpgSQL_var *var);
  static void assign_text_var(PLpgSQL_var *var, const char *str);
  static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate,
--- 201,206 ----
*************** plpgsql_exec_error_callback(void *arg)
*** 933,939 ****
   * Support function for initializing local execution variables
   * ----------
   */
! static PLpgSQL_datum *
  copy_plpgsql_datum(PLpgSQL_datum *datum)
  {
  	PLpgSQL_datum *result;
--- 928,934 ----
   * Support function for initializing local execution variables
   * ----------
   */
! PLpgSQL_datum *
  copy_plpgsql_datum(PLpgSQL_datum *datum)
  {
  	PLpgSQL_datum *result;
*************** exec_stmt_raise(PLpgSQL_execstate *estat
*** 2950,2956 ****
   * Initialize a mostly empty execution state
   * ----------
   */
! static void
  plpgsql_estate_setup(PLpgSQL_execstate *estate,
  					 PLpgSQL_function *func,
  					 ReturnSetInfo *rsi)
--- 2945,2951 ----
   * Initialize a mostly empty execution state
   * ----------
   */
! void
  plpgsql_estate_setup(PLpgSQL_execstate *estate,
  					 PLpgSQL_function *func,
  					 ReturnSetInfo *rsi)
*************** plpgsql_create_econtext(PLpgSQL_execstat
*** 6016,6022 ****
   * We check that it matches the top stack entry, and destroy the stack
   * entry along with the context.
   */
! static void
  plpgsql_destroy_econtext(PLpgSQL_execstate *estate)
  {
  	SimpleEcontextStackEntry *next;
--- 6011,6017 ----
   * We check that it matches the top stack entry, and destroy the stack
   * entry along with the context.
   */
! void
  plpgsql_destroy_econtext(PLpgSQL_execstate *estate)
  {
  	SimpleEcontextStackEntry *next;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
new file mode 100644
index fa74e7d..d4b677c
*** a/src/pl/plpgsql/src/pl_handler.c
--- b/src/pl/plpgsql/src/pl_handler.c
***************
*** 15,20 ****
--- 15,23 ----
  
  #include "plpgsql.h"
  
+ #include "catalog/pg_enum.h"
+ #include "catalog/pg_language.h"
+ 
  #include "access/htup_details.h"
  #include "catalog/pg_proc.h"
  #include "catalog/pg_type.h"
*************** int			plpgsql_variable_conflict = PLPGSQ
*** 40,45 ****
--- 43,50 ----
  /* Hook for plugins */
  PLpgSQL_plugin **plugin_ptr = NULL;
  
+ static void precheck_function(HeapTuple procTuple, bool has_trigger_relation);
+ static int check_function_output_format(Oid format);
  
  /*
   * _PG_init()			- library load-time initialization
*************** plpgsql_inline_handler(PG_FUNCTION_ARGS)
*** 187,197 ****
  	 * plpgsql_exec_function().  In particular note that this sets things up
  	 * with no arguments passed.
  	 */
! 	MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo));
! 	MemSet(&flinfo, 0, sizeof(flinfo));
! 	fake_fcinfo.flinfo = &flinfo;
! 	flinfo.fn_oid = InvalidOid;
! 	flinfo.fn_mcxt = CurrentMemoryContext;
  
  	retval = plpgsql_exec_function(func, &fake_fcinfo);
  
--- 192,198 ----
  	 * plpgsql_exec_function().  In particular note that this sets things up
  	 * with no arguments passed.
  	 */
! 	plpgsql_setup_fake_fcinfo(&flinfo, &fake_fcinfo, NULL, NULL, InvalidOid, false, false);
  
  	retval = plpgsql_exec_function(func, &fake_fcinfo);
  
*************** plpgsql_validator(PG_FUNCTION_ARGS)
*** 297,319 ****
  		 * Set up a fake fcinfo with just enough info to satisfy
  		 * plpgsql_compile().
  		 */
! 		MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo));
! 		MemSet(&flinfo, 0, sizeof(flinfo));
! 		fake_fcinfo.flinfo = &flinfo;
! 		flinfo.fn_oid = funcoid;
! 		flinfo.fn_mcxt = CurrentMemoryContext;
! 		if (is_dml_trigger)
! 		{
! 			MemSet(&trigdata, 0, sizeof(trigdata));
! 			trigdata.type = T_TriggerData;
! 			fake_fcinfo.context = (Node *) &trigdata;
! 		}
! 		else if (is_event_trigger)
! 		{
! 			MemSet(&etrigdata, 0, sizeof(etrigdata));
! 			etrigdata.type = T_EventTriggerData;
! 			fake_fcinfo.context = (Node *) &etrigdata;
! 		}
  
  		/* Test-compile the function */
  		plpgsql_compile(&fake_fcinfo, true);
--- 298,304 ----
  		 * Set up a fake fcinfo with just enough info to satisfy
  		 * plpgsql_compile().
  		 */
! 		plpgsql_setup_fake_fcinfo(&flinfo, &fake_fcinfo, &trigdata, &etrigdata, funcoid, is_dml_trigger, is_event_trigger);
  
  		/* Test-compile the function */
  		plpgsql_compile(&fake_fcinfo, true);
*************** plpgsql_validator(PG_FUNCTION_ARGS)
*** 329,331 ****
--- 314,465 ----
  
  	PG_RETURN_VOID();
  }
+ 
+ /*
+  * ----------
+  * plpgsql_check_function
+  *
+  * It ensure a detailed validation
+  * ----------
+  */
+ PG_FUNCTION_INFO_V1(plpgsql_check_function);
+ 
+ Datum
+ plpgsql_check_function(PG_FUNCTION_ARGS)
+ {
+ 	Oid			funcoid = PG_GETARG_OID(0);
+ 	Oid			relid = PG_GETARG_OID(1);
+ 	bool			fatal_errors = PG_GETARG_BOOL(2);
+ 	Oid			format_oid = PG_GETARG_OID(3);
+ 	TupleDesc       tupdesc;
+ 	HeapTuple	procTuple;
+ 	Tuplestorestate *tupstore;
+ 	MemoryContext per_query_ctx;
+ 	MemoryContext oldcontext;
+ 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ 	int		format = PLPGSQL_CHECK_FORMAT_PLAIN;
+ 
+ 	/* 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);
+ 
+ 	precheck_function(procTuple, OidIsValid(relid));
+ 	format = check_function_output_format(format_oid);
+ 
+ 	/* 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);
+ 
+ 	plpgsql_function_check(procTuple, relid,
+ 						tupdesc, tupstore, fatal_errors, format);
+ 
+ 	ReleaseSysCache(procTuple);
+ 
+ 	/* clean up and return the tuplestore */
+ 	tuplestore_donestoring(tupstore);
+ 
+ 	rsinfo->returnMode = SFRM_Materialize;
+ 	rsinfo->setResult = tupstore;
+ 	rsinfo->setDesc = tupdesc;
+ 
+ 	return (Datum) 0;
+ }
+ 
+ /*
+  * Process necessary checking before code checking
+  */
+ static void
+ precheck_function(HeapTuple procTuple, bool has_trigger_relation)
+ {
+ 	Form_pg_proc		proc;
+ 	Form_pg_language	languageStruct;
+ 	HeapTuple	languageTuple;
+ 	char	   functyptype;
+ 	char	   *funcname;
+ 	bool		is_trigger = false;
+ 
+ 	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);
+ 
+ 	functyptype = get_typtype(proc->prorettype);
+ 
+ 	if (functyptype == TYPTYPE_PSEUDO)
+ 	{
+ 		/* we assume OPAQUE with no arguments means a trigger */
+ 		if (proc->prorettype == TRIGGEROID ||
+ 			(proc->prorettype == OPAQUEOID && proc->pronargs == 0))
+ 		{
+ 			is_trigger = true;
+ 			if (!has_trigger_relation)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 						 errmsg("missing trigger relation"),
+ 						 errhint("Trigger relation oid must be valid")));
+ 		}
+ 	}
+ 
+ 	if (has_trigger_relation && !is_trigger)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("function is not trigger"),
+ 				 errhint("Trigger relation oid must not be valid for non trigger function.")));
+ 
+ 	pfree(funcname);
+ }
+ 
+ /*
+  * Returns output format for plpgsql_check_function
+  */
+ static int
+ check_function_output_format(Oid format)
+ {
+ 	HeapTuple	tuple;
+ 	char		*label;
+ 	int	result = -1;
+ 
+ 	tuple = SearchSysCache1(ENUMOID, ObjectIdGetDatum(format));
+ 	if (!HeapTupleIsValid(tuple))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+ 				 errmsg("invalid internal value for enum: %u",
+ 						format)));
+ 
+ 	label = NameStr(((Form_pg_enum) GETSTRUCT(tuple))->enumlabel);
+ 
+ 	if (strcmp(label, "XML") == 0)
+ 		result = PLPGSQL_CHECK_FORMAT_XML;
+ 	else if (strcmp(label, "PLAIN_TEXT") == 0)
+ 		result = PLPGSQL_CHECK_FORMAT_PLAIN;
+ 
+ 	ReleaseSysCache(tuple);
+ 
+ 	return result;
+ }
diff --git a/src/pl/plpgsql/src/plpgsql--1.0--1.1.sql b/src/pl/plpgsql/src/plpgsql--1.0--1.1.sql
new file mode 100644
index ...62cd8c2
*** a/src/pl/plpgsql/src/plpgsql--1.0--1.1.sql
--- b/src/pl/plpgsql/src/plpgsql--1.0--1.1.sql
***************
*** 0 ****
--- 1,17 ----
+ /* src/pl/plpgsql/src/plpgsql--1.1.sql */
+ 
+ /*
+  * Currently, all the interesting stuff is done by CREATE LANGUAGE.
+  * Later we will probably "dumb down" that command and put more of the
+  * knowledge into this script.
+  */
+ 
+ CREATE TYPE plpgsql_check_function_format AS ENUM ('XML', 'PLAIN_TEXT');
+ 
+ CREATE FUNCTION plpgsql_check_function(funcoid regprocedure,
+ relid regclass = 0,
+ fatal_errors boolean = true,
+ format plpgsql_check_function_format = 'PLAIN_TEXT')
+ RETURNS SETOF text AS 'MODULE_PATHNAME'
+ LANGUAGE C
+ RETURNS NULL ON NULL INPUT;
diff --git a/src/pl/plpgsql/src/plpgsql--1.1.sql b/src/pl/plpgsql/src/plpgsql--1.1.sql
new file mode 100644
index ...291fdc5
*** a/src/pl/plpgsql/src/plpgsql--1.1.sql
--- b/src/pl/plpgsql/src/plpgsql--1.1.sql
***************
*** 0 ****
--- 1,21 ----
+ /* src/pl/plpgsql/src/plpgsql--1.1.sql */
+ 
+ /*
+  * Currently, all the interesting stuff is done by CREATE LANGUAGE.
+  * Later we will probably "dumb down" that command and put more of the
+  * knowledge into this script.
+  */
+ 
+ CREATE PROCEDURAL LANGUAGE plpgsql;
+ 
+ COMMENT ON PROCEDURAL LANGUAGE plpgsql IS 'PL/pgSQL procedural language';
+ 
+ CREATE TYPE plpgsql_check_function_format AS ENUM ('XML', 'PLAIN_TEXT');
+ 
+ CREATE FUNCTION plpgsql_check_function(funcoid regprocedure,
+ relid regclass = 0,
+ fatal_errors boolean = true,
+ format plpgsql_check_function_format = 'PLAIN_TEXT')
+ RETURNS SETOF text AS 'MODULE_PATHNAME'
+ LANGUAGE C
+ RETURNS NULL ON NULL INPUT;
diff --git a/src/pl/plpgsql/src/plpgsql.control b/src/pl/plpgsql/src/plpgsql.control
new file mode 100644
index b320227..4c75c93
*** a/src/pl/plpgsql/src/plpgsql.control
--- b/src/pl/plpgsql/src/plpgsql.control
***************
*** 1,6 ****
  # plpgsql extension
  comment = 'PL/pgSQL procedural language'
! default_version = '1.0'
  module_pathname = '$libdir/plpgsql'
  relocatable = false
  schema = pg_catalog
--- 1,6 ----
  # plpgsql extension
  comment = 'PL/pgSQL procedural language'
! default_version = '1.1'
  module_pathname = '$libdir/plpgsql'
  relocatable = false
  schema = pg_catalog
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
new file mode 100644
index 7ea6960..729a599
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
*************** typedef struct PLpgSQL_execstate
*** 778,783 ****
--- 778,798 ----
  	void	   *plugin_info;	/* reserved for use by optional plugin */
  } PLpgSQL_execstate;
  
+ enum {
+ 	PLPGSQL_CHECK_FORMAT_PLAIN,
+ 	PLPGSQL_CHECK_FORMAT_XML
+ };
+ 
+ typedef struct PLpgSQL_checkstate
+ {
+ 	PLpgSQL_execstate   *estate;			/* check state is estate extension */
+ 	Tuplestorestate *tuple_store;
+ 	TupleDesc	tupdesc;
+ 	StringInfo	sinfo;
+ 	bool			fatal_errors;		/* stop on first error */
+ 	int			format;
+ 	List		*argnames;
+ } PLpgSQL_checkstate;
  
  /*
   * A PLpgSQL_plugin structure represents an instrumentation plugin.
*************** extern PLpgSQL_condition *plpgsql_parse_
*** 913,918 ****
--- 928,934 ----
  extern void plpgsql_adddatum(PLpgSQL_datum *new);
  extern int	plpgsql_add_initdatums(int **varnos);
  extern void plpgsql_HashTableInit(void);
+ extern void plpgsql_delete_function(PLpgSQL_function *func);
  
  /* ----------
   * Functions in pl_handler.c
*************** extern void _PG_init(void);
*** 922,927 ****
--- 938,944 ----
  extern Datum plpgsql_call_handler(PG_FUNCTION_ARGS);
  extern Datum plpgsql_inline_handler(PG_FUNCTION_ARGS);
  extern Datum plpgsql_validator(PG_FUNCTION_ARGS);
+ extern Datum plpgsql_check_function(PG_FUNCTION_ARGS);
  
  /* ----------
   * Functions in pl_exec.c
*************** extern Oid exec_get_datum_type(PLpgSQL_e
*** 941,946 ****
--- 958,980 ----
  extern void exec_get_datum_type_info(PLpgSQL_execstate *estate,
  						 PLpgSQL_datum *datum,
  						 Oid *typeid, int32 *typmod, Oid *collation);
+ extern PLpgSQL_datum *copy_plpgsql_datum(PLpgSQL_datum *datum);
+ extern void plpgsql_estate_setup(PLpgSQL_execstate *estate,
+ 					 PLpgSQL_function *func,
+ 					 ReturnSetInfo *rsi);
+ extern void plpgsql_destroy_econtext(PLpgSQL_execstate *estate);
+ 
+ /* ----------
+  * Functions for namespace handling in pl_check.c
+  * ----------
+  */
+ extern void plpgsql_function_check(HeapTuple procTuple, Oid relid,
+ 					    TupleDesc tupdesc,
+ 					    Tuplestorestate *tupstore,
+ 						bool fatal_errors, int format);
+ extern void plpgsql_setup_fake_fcinfo(FmgrInfo *flinfo, FunctionCallInfoData *fcinfo,
+ 					TriggerData *trigdata, EventTriggerData *etrigdata,
+ 					Oid funcoid, bool is_dml_trigger, bool is_event_trigger);
  
  /* ----------
   * Functions for namespace handling in pl_funcs.c
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
new file mode 100644
index 3005336..77cc924
*** a/src/test/regress/expected/plpgsql.out
--- b/src/test/regress/expected/plpgsql.out
*************** end;
*** 302,307 ****
--- 302,313 ----
  ' language plpgsql;
  create trigger tg_hslot_biu before insert or update
      on HSlot for each row execute procedure tg_hslot_biu();
+ -- check trigger should not fail
+ select plpgsql_check_function('tg_hslot_biu()', 'HSlot');
+  plpgsql_check_function 
+ ------------------------
+ (0 rows)
+ 
  -- ************************************************************
  -- * BEFORE DELETE on HSlot
  -- *	- prevent from manual manipulation
*************** begin
*** 635,640 ****
--- 641,652 ----
      raise exception ''illegal backlink beginning with %'', mytype;
  end;
  ' language plpgsql;
+ -- check function should not fail
+ select plpgsql_check_function('tg_backlink_set(bpchar, bpchar)', 0);
+  plpgsql_check_function 
+ ------------------------
+ (0 rows)
+ 
  -- ************************************************************
  -- * Support function to clear out the backlink field if
  -- * it still points to specific slot
*************** NOTICE:  4 bb cc
*** 2947,2952 ****
--- 2959,2996 ----
   
  (1 row)
  
+ -- check function should not fail
+ select plpgsql_check_function('for_vect()');
+  plpgsql_check_function 
+ ------------------------
+ (0 rows)
+ 
+ -- recheck after check function
+ select for_vect();
+ NOTICE:  1
+ NOTICE:  2
+ NOTICE:  3
+ NOTICE:  1 BB CC
+ NOTICE:  2 BB CC
+ NOTICE:  3 BB CC
+ NOTICE:  4 BB CC
+ NOTICE:  1
+ NOTICE:  2
+ NOTICE:  3
+ NOTICE:  4
+ NOTICE:  1 BB CC
+ NOTICE:  2 BB CC
+ NOTICE:  3 BB CC
+ NOTICE:  4 BB CC
+ NOTICE:  1 bb cc
+ NOTICE:  2 bb cc
+ NOTICE:  3 bb cc
+ NOTICE:  4 bb cc
+  for_vect 
+ ----------
+  
+ (1 row)
+ 
  -- regression test: verify that multiple uses of same plpgsql datum within
  -- a SQL command all get mapped to the same $n parameter.  The return value
  -- of the SELECT is not important, we only care that it doesn't fail with
*************** begin
*** 3428,3433 ****
--- 3472,3483 ----
    return;
  end;
  $$ language plpgsql;
+ -- check function should not fail
+ select plpgsql_check_function('forc01()');
+  plpgsql_check_function 
+ ------------------------
+ (0 rows)
+ 
  select forc01();
  NOTICE:  5 from c
  NOTICE:  6 from c
*************** begin
*** 3861,3866 ****
--- 3911,3922 ----
    end case;
  end;
  $$ language plpgsql immutable;
+ -- check function should not fail
+ select plpgsql_check_function('case_test(bigint)');
+  plpgsql_check_function 
+ ------------------------
+ (0 rows)
+ 
  select case_test(1);
   case_test 
  -----------
*************** ERROR:  value for domain orderedarray vi
*** 4716,4718 ****
--- 4772,5299 ----
  CONTEXT:  PL/pgSQL function testoa(integer,integer,integer) line 5 at assignment
  drop function arrayassign1();
  drop function testoa(x1 int, x2 int, x3 int);
+ --
+ -- check function statement tests
+ --
+ --should fail - is not plpgsql
+ select plpgsql_check_function('session_user()');
+ ERROR:  "session_user"() is not a plpgsql function
+ create table t1(a int, b int);
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+   if false then
+     raise notice '% %', r.c;
+   end if;
+ end;
+ $$ language plpgsql;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ select plpgsql_check_function('f1()', fatal_errors := true);
+                          plpgsql_check_function                         
+ ------------------------------------------------------------------------
+  error:42703:4:SQL statement:column "c" of relation "t1" does not exist
+  Query: update t1 set c = 30
+  --                   ^
+ (3 rows)
+ 
+ select plpgsql_check_function('f1()', fatal_errors := false);
+                          plpgsql_check_function                         
+ ------------------------------------------------------------------------
+  error:42703:4:SQL statement:column "c" of relation "t1" does not exist
+  Query: update t1 set c = 30
+  --                   ^
+  error:42P01:7:RAISE:missing FROM-clause entry for table "r"
+  Query: SELECT r.c
+  --            ^
+  error:42601:7:RAISE:too few parameters specified for RAISE
+ (7 rows)
+ 
+ select plpgsql_check_function('f1()');
+                          plpgsql_check_function                         
+ ------------------------------------------------------------------------
+  error:42703:4:SQL statement:column "c" of relation "t1" does not exist
+  Query: update t1 set c = 30
+  --                   ^
+ (3 rows)
+ 
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ drop function f1();
+ create function g1(out a int, out b int)
+ as $$
+   select 10,20;
+ $$ language sql;
+ create function f1()
+ returns void as $$
+ declare r record;
+ begin
+   r := g1();
+   if false then
+     raise notice '%', r.c;
+   end if;
+ end;
+ $$ language plpgsql;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ select plpgsql_check_function('f1()');
+              plpgsql_check_function              
+ -------------------------------------------------
+  error:42703:6:RAISE:record "r" has no field "c"
+  Context: SQL statement "SELECT r.c"
+ (2 rows)
+ 
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ drop function f1();
+ drop function g1();
+ create function g1(out a int, out b int)
+ returns setof record as $$
+ select * from t1;
+ $$ language sql;
+ create function f1()
+ returns void as $$
+ declare r record;
+ begin
+   for r in select * from g1()
+   loop
+     raise notice '%', r.c;
+   end loop;
+ end;
+ $$ language plpgsql;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ select plpgsql_check_function('f1()');
+              plpgsql_check_function              
+ -------------------------------------------------
+  error:42703:6:RAISE:record "r" has no field "c"
+  Context: SQL statement "SELECT r.c"
+ (2 rows)
+ 
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ create or replace function f1()
+ returns void as $$
+ declare r record;
+ begin
+   for r in select * from g1()
+   loop
+     r.c := 20;
+   end loop;
+ end;
+ $$ language plpgsql;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ select plpgsql_check_function('f1()');
+                 plpgsql_check_function                
+ ------------------------------------------------------
+  error:42703:6:assignment:record "r" has no field "c"
+ (1 row)
+ 
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ drop function f1();
+ drop function g1();
+ create function f1()
+ returns int as $$
+ declare r int;
+ begin
+   if false then
+     r := a + b;
+   end if;
+   return r;
+ end;
+ $$ language plpgsql;
+ select f1();
+  f1 
+ ----
+    
+ (1 row)
+ 
+ select plpgsql_check_function('f1()');
+                plpgsql_check_function               
+ ----------------------------------------------------
+  error:42703:5:assignment:column "a" does not exist
+  Query: SELECT a + b
+  --            ^
+ (3 rows)
+ 
+ select f1();
+  f1 
+ ----
+    
+ (1 row)
+ 
+ drop function f1();
+ create or replace function f1()
+ returns void as $$
+ begin
+   if false then
+     raise notice '%', 1, 2;
+   end if;
+ end;
+ $$ language plpgsql;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ select plpgsql_check_function('f1()');
+                    plpgsql_check_function                    
+ -------------------------------------------------------------
+  error:42601:4:RAISE:too many parameters specified for RAISE
+ (1 row)
+ 
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ drop function f1();
+ create or replace function f1()
+ returns void as $$
+ begin
+   if false then
+     raise notice '% %';
+   end if;
+ end;
+ $$ language plpgsql;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ select plpgsql_check_function('f1()');
+                    plpgsql_check_function                   
+ ------------------------------------------------------------
+  error:42601:4:RAISE:too few parameters specified for RAISE
+ (1 row)
+ 
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ drop function f1();
+ create or replace function f1()
+ returns void as $$
+ declare r int[];
+ begin
+   if false then
+     r[c+10] := 20;
+   end if;
+ end;
+ $$ language plpgsql;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ select plpgsql_check_function('f1()');
+                plpgsql_check_function               
+ ----------------------------------------------------
+  error:42703:5:assignment:column "c" does not exist
+  Query: SELECT c+10
+  --            ^
+ (3 rows)
+ 
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ drop function f1();
+ create or replace function f1()
+ returns void as $$
+ declare r int;
+ begin
+   if false then
+     r[10] := 20;
+   end if;
+ end;
+ $$ language plpgsql set search_path = public;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ select plpgsql_check_function('f1()');
+                    plpgsql_check_function                    
+ -------------------------------------------------------------
+  error:42804:5:assignment:subscripted object is not an array
+ (1 row)
+ 
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ drop function f1();
+ create type _exception_type as (
+   state text,
+   message text,
+   detail text);
+ create or replace function f1()
+ returns void as $$
+ declare
+   _exception record;
+ begin
+   _exception := NULL::_exception_type;
+ exception when others then
+   get stacked diagnostics
+         _exception.state = RETURNED_SQLSTATE,
+         _exception.message = MESSAGE_TEXT,
+         _exception.detail = PG_EXCEPTION_DETAIL,
+         _exception.hint = PG_EXCEPTION_HINT;
+ end;
+ $$ language plpgsql;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ select plpgsql_check_function('f1()');
+                         plpgsql_check_function                         
+ -----------------------------------------------------------------------
+  error:42703:7:GET DIAGNOSTICS:record "_exception" has no field "hint"
+ (1 row)
+ 
+ drop function f1();
+ create or replace function f1_trg()
+ returns trigger as $$
+ begin
+   if new.a > 10 then
+     raise notice '%', new.b;
+     raise notice '%', new.c;
+   end if;
+   return new;
+ end;
+ $$ language plpgsql;
+ create trigger t1_f1 before insert on t1
+   for each row
+   execute procedure f1_trg();
+ insert into t1 values(6,30);
+ select plpgsql_check_function('f1_trg()','t1');
+               plpgsql_check_function               
+ ---------------------------------------------------
+  error:42703:5:RAISE:record "new" has no field "c"
+  Context: SQL statement "SELECT new.c"
+ (2 rows)
+ 
+ insert into t1 values(6,30);
+ create or replace function f1_trg()
+ returns trigger as $$
+ begin
+   new.a := new.a + 10;
+   new.b := new.b + 10;
+   new.c := 30;
+   return new;
+ end;
+ $$ language plpgsql;
+ -- should to fail
+ select plpgsql_check_function('f1_trg()','t1');
+                  plpgsql_check_function                 
+ --------------------------------------------------------
+  error:42703:5:assignment:record "new" has no field "c"
+ (1 row)
+ 
+ -- should to fail but not crash
+ insert into t1 values(6,30);
+ ERROR:  record "new" has no field "c"
+ CONTEXT:  PL/pgSQL function f1_trg() line 5 at assignment
+ create or replace function f1_trg()
+ returns trigger as $$
+ begin
+   new.a := new.a + 10;
+   new.b := new.b + 10;
+   return new;
+ end;
+ $$ language plpgsql;
+ -- ok
+ select plpgsql_check_function('f1_trg()', 't1');
+  plpgsql_check_function 
+ ------------------------
+ (0 rows)
+ 
+ -- ok
+ insert into t1 values(6,30);
+ select * from t1;
+  a  | b  
+ ----+----
+   6 | 30
+   6 | 30
+  16 | 40
+ (3 rows)
+ 
+ drop trigger t1_f1 on t1;
+ drop function f1_trg();
+ -- test of showing caret on correct place for multiline queries
+ create or replace function f1()
+ returns void as $$
+ begin
+   select
+   var
+   from
+   foo;
+ end;
+ $$ language plpgsql;
+ select plpgsql_check_function('f1()');
+                  plpgsql_check_function                  
+ ---------------------------------------------------------
+  error:42703:3:SQL statement:column "var" does not exist
+  Query: select
+           var
+  --       ^
+           from
+           foo
+ (6 rows)
+ 
+ drop function f1();
+ create or replace function f1()
+ returns int as $$
+ begin
+   return (select a
+              from t1
+             where hh = 20);
+ end;
+ $$ language plpgsql;
+ select plpgsql_check_function('f1()');
+              plpgsql_check_function              
+ -------------------------------------------------
+  error:42703:3:RETURN:column "hh" does not exist
+  Query: SELECT (select a
+                      from t1
+                     where hh = 20)
+  --                       ^
+ (5 rows)
+ 
+ create or replace function f1()
+ returns int as $$
+ begin
+   return (select a
+              from txxxxxxx
+             where hh = 20);
+ end;
+ $$ language plpgsql;
+ select plpgsql_check_function('f1()');
+                  plpgsql_check_function                  
+ ---------------------------------------------------------
+  error:42P01:3:RETURN:relation "txxxxxxx" does not exist
+  Query: SELECT (select a
+                      from txxxxxxx
+  --                       ^
+                     where hh = 20)
+ (5 rows)
+ 
+ drop function f1();
+ drop table t1;
+ drop type _exception_type;
+ -- raise warnings when target row has different number of attributies in
+ -- SELECT INTO statement
+ create or replace function f1()
+ returns void as $$
+ declare a1 int; a2 int;
+ begin
+   select 10,20 into a1,a2;
+ end;
+ $$ language plpgsql;
+ -- should be ok
+ select plpgsql_check_function('f1()');
+  plpgsql_check_function 
+ ------------------------
+ (0 rows)
+ 
+ create or replace function f1()
+ returns void as $$
+ declare a1 int; a2 int;
+ begin
+   select 10,20 into a1;
+ end;
+ $$ language plpgsql;
+ -- raise warning
+ select plpgsql_check_function('f1()');
+                          plpgsql_check_function                          
+ -------------------------------------------------------------------------
+  warning:00000:4:SQL statement:too many attributies for target variables
+  Detail: There are less target variables than output columns in query.
+  Hint: Check target variables in SELECT INTO statement
+ (3 rows)
+ 
+ create or replace function f1()
+ returns void as $$
+ declare a1 int; a2 int;
+ begin
+   select 10 into a1,a2;
+ end;
+ $$ language plpgsql;
+ -- raise warning
+ select plpgsql_check_function('f1()');
+                          plpgsql_check_function                         
+ ------------------------------------------------------------------------
+  warning:00000:4:SQL statement:too few attributies for target variables
+  Detail: There are more target variables than output columns in query.
+  Hint: Check target variables in SELECT INTO statement.
+ (3 rows)
+ 
+ -- bogus code
+ set check_function_bodies to off;
+ create or replace function f1()
+ returns void as $$
+ adasdfsadf
+ $$ language plpgsql;
+ select plpgsql_check_function('f1()');
+                    plpgsql_check_function                   
+ ------------------------------------------------------------
+  error:42601:syntax error at or near "adasdfsadf"
+  Query: 
+         adasdfsadf
+  --     ^
+         
+  Context: compilation of PL/pgSQL function "f1" near line 1
+ (6 rows)
+ 
+ drop function f1();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
new file mode 100644
index 2b60b67..206edbf
*** a/src/test/regress/sql/plpgsql.sql
--- b/src/test/regress/sql/plpgsql.sql
*************** end;
*** 366,371 ****
--- 366,373 ----
  create trigger tg_hslot_biu before insert or update
      on HSlot for each row execute procedure tg_hslot_biu();
  
+ -- check trigger should not fail
+ select plpgsql_check_function('tg_hslot_biu()', 'HSlot');
  
  -- ************************************************************
  -- * BEFORE DELETE on HSlot
*************** begin
*** 747,752 ****
--- 749,757 ----
  end;
  ' language plpgsql;
  
+ -- check function should not fail
+ select plpgsql_check_function('tg_backlink_set(bpchar, bpchar)', 0);
+ 
  
  -- ************************************************************
  -- * Support function to clear out the backlink field if
*************** $proc$ language plpgsql;
*** 2458,2463 ****
--- 2463,2475 ----
  
  select for_vect();
  
+ -- check function should not fail
+ select plpgsql_check_function('for_vect()');
+ 
+ -- recheck after check function
+ select for_vect();
+ 
+ 
  -- regression test: verify that multiple uses of same plpgsql datum within
  -- a SQL command all get mapped to the same $n parameter.  The return value
  -- of the SELECT is not important, we only care that it doesn't fail with
*************** begin
*** 2837,2842 ****
--- 2849,2857 ----
  end;
  $$ language plpgsql;
  
+ -- check function should not fail
+ select plpgsql_check_function('forc01()');
+ 
  select forc01();
  
  -- try updating the cursor's current row
*************** begin
*** 3171,3176 ****
--- 3186,3195 ----
  end;
  $$ language plpgsql immutable;
  
+ -- check function should not fail
+ select plpgsql_check_function('case_test(bigint)');
+ 
+ 
  select case_test(1);
  select case_test(2);
  select case_test(3);
*************** select testoa(1,2,1); -- fail at update
*** 3723,3725 ****
--- 3742,4105 ----
  
  drop function arrayassign1();
  drop function testoa(x1 int, x2 int, x3 int);
+ 
+ --
+ -- check function statement tests
+ --
+ 
+ --should fail - is not plpgsql
+ select plpgsql_check_function('session_user()');
+ 
+ create table t1(a int, b int);
+ 
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+   if false then
+     raise notice '% %', r.c;
+   end if;
+ end;
+ $$ language plpgsql;
+ 
+ select f1();
+ select plpgsql_check_function('f1()', fatal_errors := true);
+ select plpgsql_check_function('f1()', fatal_errors := false);
+ 
+ select plpgsql_check_function('f1()');
+ 
+ select f1();
+ 
+ drop function f1();
+ 
+ create function g1(out a int, out b int)
+ as $$
+   select 10,20;
+ $$ language sql;
+ 
+ create function f1()
+ returns void as $$
+ declare r record;
+ begin
+   r := g1();
+   if false then
+     raise notice '%', r.c;
+   end if;
+ end;
+ $$ language plpgsql;
+ 
+ select f1();
+ select plpgsql_check_function('f1()');
+ 
+ select f1();
+ 
+ drop function f1();
+ drop function g1();
+ 
+ create function g1(out a int, out b int)
+ returns setof record as $$
+ select * from t1;
+ $$ language sql;
+ 
+ create function f1()
+ returns void as $$
+ declare r record;
+ begin
+   for r in select * from g1()
+   loop
+     raise notice '%', r.c;
+   end loop;
+ end;
+ $$ language plpgsql;
+ 
+ select f1();
+ 
+ select plpgsql_check_function('f1()');
+ 
+ select f1();
+ 
+ create or replace function f1()
+ returns void as $$
+ declare r record;
+ begin
+   for r in select * from g1()
+   loop
+     r.c := 20;
+   end loop;
+ end;
+ $$ language plpgsql;
+ 
+ select f1();
+ 
+ select plpgsql_check_function('f1()');
+ 
+ select f1();
+ 
+ drop function f1();
+ drop function g1();
+ 
+ create function f1()
+ returns int as $$
+ declare r int;
+ begin
+   if false then
+     r := a + b;
+   end if;
+   return r;
+ end;
+ $$ language plpgsql;
+ 
+ select f1();
+ 
+ select plpgsql_check_function('f1()');
+ 
+ select f1();
+ 
+ drop function f1();
+ 
+ create or replace function f1()
+ returns void as $$
+ begin
+   if false then
+     raise notice '%', 1, 2;
+   end if;
+ end;
+ $$ language plpgsql;
+ 
+ select f1();
+ 
+ select plpgsql_check_function('f1()');
+ 
+ select f1();
+ 
+ drop function f1();
+ 
+ create or replace function f1()
+ returns void as $$
+ begin
+   if false then
+     raise notice '% %';
+   end if;
+ end;
+ $$ language plpgsql;
+ 
+ select f1();
+ 
+ select plpgsql_check_function('f1()');
+ 
+ select f1();
+ 
+ drop function f1();
+ 
+ create or replace function f1()
+ returns void as $$
+ declare r int[];
+ begin
+   if false then
+     r[c+10] := 20;
+   end if;
+ end;
+ $$ language plpgsql;
+ 
+ select f1();
+ 
+ select plpgsql_check_function('f1()');
+ 
+ select f1();
+ 
+ drop function f1();
+ 
+ create or replace function f1()
+ returns void as $$
+ declare r int;
+ begin
+   if false then
+     r[10] := 20;
+   end if;
+ end;
+ $$ language plpgsql set search_path = public;
+ 
+ select f1();
+ 
+ select plpgsql_check_function('f1()');
+ 
+ select f1();
+ 
+ drop function f1();
+ 
+ create type _exception_type as (
+   state text,
+   message text,
+   detail text);
+ 
+ create or replace function f1()
+ returns void as $$
+ declare
+   _exception record;
+ begin
+   _exception := NULL::_exception_type;
+ exception when others then
+   get stacked diagnostics
+         _exception.state = RETURNED_SQLSTATE,
+         _exception.message = MESSAGE_TEXT,
+         _exception.detail = PG_EXCEPTION_DETAIL,
+         _exception.hint = PG_EXCEPTION_HINT;
+ end;
+ $$ language plpgsql;
+ 
+ select f1();
+ 
+ select plpgsql_check_function('f1()');
+ 
+ drop function f1();
+ 
+ create or replace function f1_trg()
+ returns trigger as $$
+ begin
+   if new.a > 10 then
+     raise notice '%', new.b;
+     raise notice '%', new.c;
+   end if;
+   return new;
+ end;
+ $$ language plpgsql;
+ 
+ create trigger t1_f1 before insert on t1
+   for each row
+   execute procedure f1_trg();
+ 
+ insert into t1 values(6,30);
+ 
+ select plpgsql_check_function('f1_trg()','t1');
+ 
+ insert into t1 values(6,30);
+ 
+ create or replace function f1_trg()
+ returns trigger as $$
+ begin
+   new.a := new.a + 10;
+   new.b := new.b + 10;
+   new.c := 30;
+   return new;
+ end;
+ $$ language plpgsql;
+ 
+ -- should to fail
+ 
+ select plpgsql_check_function('f1_trg()','t1');
+ 
+ -- should to fail but not crash
+ insert into t1 values(6,30);
+ 
+ create or replace function f1_trg()
+ returns trigger as $$
+ begin
+   new.a := new.a + 10;
+   new.b := new.b + 10;
+   return new;
+ end;
+ $$ language plpgsql;
+ 
+ -- ok
+ select plpgsql_check_function('f1_trg()', 't1');
+ 
+ -- ok
+ insert into t1 values(6,30);
+ 
+ select * from t1;
+ 
+ drop trigger t1_f1 on t1;
+ 
+ drop function f1_trg();
+ 
+ -- test of showing caret on correct place for multiline queries
+ create or replace function f1()
+ returns void as $$
+ begin
+   select
+   var
+   from
+   foo;
+ end;
+ $$ language plpgsql;
+ 
+ select plpgsql_check_function('f1()');
+ 
+ drop function f1();
+ 
+ create or replace function f1()
+ returns int as $$
+ begin
+   return (select a
+              from t1
+             where hh = 20);
+ end;
+ $$ language plpgsql;
+ 
+ select plpgsql_check_function('f1()');
+ 
+ create or replace function f1()
+ returns int as $$
+ begin
+   return (select a
+              from txxxxxxx
+             where hh = 20);
+ end;
+ $$ language plpgsql;
+ 
+ select plpgsql_check_function('f1()');
+ 
+ drop function f1();
+ 
+ drop table t1;
+ drop type _exception_type;
+ 
+ -- raise warnings when target row has different number of attributies in
+ -- SELECT INTO statement
+ 
+ create or replace function f1()
+ returns void as $$
+ declare a1 int; a2 int;
+ begin
+   select 10,20 into a1,a2;
+ end;
+ $$ language plpgsql;
+ 
+ -- should be ok
+ select plpgsql_check_function('f1()');
+ 
+ create or replace function f1()
+ returns void as $$
+ declare a1 int; a2 int;
+ begin
+   select 10,20 into a1;
+ end;
+ $$ language plpgsql;
+ 
+ -- raise warning
+ select plpgsql_check_function('f1()');
+ 
+ create or replace function f1()
+ returns void as $$
+ declare a1 int; a2 int;
+ begin
+   select 10 into a1,a2;
+ end;
+ $$ language plpgsql;
+ 
+ -- raise warning
+ select plpgsql_check_function('f1()');
+ 
+ -- bogus code
+ set check_function_bodies to off;
+ 
+ create or replace function f1()
+ returns void as $$
+ adasdfsadf
+ $$ language plpgsql;
+ 
+ select plpgsql_check_function('f1()');
+ 
+ drop function f1();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
new file mode 100644
index a831a1e..b91d97f
*** a/src/tools/pgindent/typedefs.list
--- b/src/tools/pgindent/typedefs.list
*************** AlterDatabaseStmt
*** 57,62 ****
--- 57,63 ----
  AlterDefaultPrivilegesStmt
  AlterDomainStmt
  AlterEnumStmt
+ AlterEventTrigStmt
  AlterExtensionContentsStmt
  AlterExtensionStmt
  AlterFdwStmt
*************** CreateCastStmt
*** 319,324 ****
--- 320,326 ----
  CreateConversionStmt
  CreateDomainStmt
  CreateEnumStmt
+ CreateEventTrigStmt
  CreateExtensionStmt
  CreateFdwStmt
  CreateForeignServerStmt
*************** EquivalenceClass
*** 428,433 ****
--- 430,441 ----
  EquivalenceMember
  ErrorContextCallback
  ErrorData
+ EventTriggerCacheEntry
+ EventTriggerCacheItem
+ EventTriggerCacheStateType
+ EventTriggerData
+ EventTriggerEvent
+ EventTriggerInfo
  ExceptionLabelMap
  ExceptionMap
  ExecAuxRowMark
*************** FormData_pg_database
*** 513,518 ****
--- 521,527 ----
  FormData_pg_default_acl
  FormData_pg_depend
  FormData_pg_enum
+ FormData_pg_event_trigger
  FormData_pg_extension
  FormData_pg_foreign_data_wrapper
  FormData_pg_foreign_server
*************** Form_pg_database
*** 559,564 ****
--- 568,574 ----
  Form_pg_default_acl
  Form_pg_depend
  Form_pg_enum
+ Form_pg_event_trigger
  Form_pg_extension
  Form_pg_foreign_data_wrapper
  Form_pg_foreign_server
*************** IOFuncSelector
*** 750,755 ****
--- 760,766 ----
  IPCompareMethod
  ITEM
  IV
+ IdentLine
  IdentifierLookup
  IdentifySystemCmd
  IncrementVarSublevelsUp_context
*************** LWLockPadded
*** 860,865 ****
--- 871,877 ----
  LabelProvider
  LargeObjectDesc
  Latch
+ LateralJoinInfo
  LexDescr
  LexemeEntry
  LexemeHashKey
*************** PLpgSQL_stmt_return
*** 1104,1109 ****
--- 1116,1122 ----
  PLpgSQL_stmt_return_next
  PLpgSQL_stmt_return_query
  PLpgSQL_stmt_while
+ PLpgSQL_trigtype
  PLpgSQL_type
  PLpgSQL_var
  PLpgSQL_variable
*************** PQconninfoOption
*** 1142,1148 ****
  PQnoticeProcessor
  PQnoticeReceiver
  PQprintOpt
- PQrowProcessor
  PREDICATELOCK
  PREDICATELOCKTAG
  PREDICATELOCKTARGET
--- 1155,1160 ----
*************** Page
*** 1171,1176 ****
--- 1183,1189 ----
  PageHeader
  PageHeaderData
  PageSplitRecord
+ PageXLogRecPtr
  PagetableEntry
  Pairs
  ParallelSlot
*************** ParamPathInfo
*** 1186,1191 ****
--- 1199,1206 ----
  ParamRef
  ParentMapEntry
  ParseCallbackState
+ ParseExprKind
+ ParseNamespaceItem
  ParseParamRefHook
  ParseState
  ParsedLex
*************** PathKeysComparison
*** 1201,1207 ****
  Pattern_Prefix_Status
  Pattern_Type
  PendingOperationEntry
- PendingOperationTag
  PendingRelDelete
  PendingUnlinkEntry
  PerlInterpreter
--- 1216,1221 ----
*************** ProcLangInfo
*** 1295,1300 ****
--- 1309,1315 ----
  ProcSignalReason
  ProcSignalSlot
  ProcState
+ ProcessUtilityContext
  ProcessUtility_hook_type
  ProcessingMode
  ProjectionInfo
*************** TimeOffset
*** 1680,1685 ****
--- 1695,1701 ----
  TimeStamp
  TimeTzADT
  TimeZoneAbbrevTable
+ TimeoutId
  Timestamp
  TimestampTz
  TmFromChar
*************** WalSnd
*** 1793,1798 ****
--- 1809,1815 ----
  WalSndCtlData
  WalSndState
  WalSndrMessage
+ WholeRowVarExprState
  WindowAgg
  WindowAggState
  WindowClause
*************** X509_NAME
*** 1824,1830 ****
  X509_NAME_ENTRY
  X509_STORE
  X509_STORE_CTX
- XLogContRecord
  XLogCtlData
  XLogCtlInsert
  XLogCtlWrite
--- 1841,1846 ----
*************** XLogPageHeaderData
*** 1835,1840 ****
--- 1851,1857 ----
  XLogRecData
  XLogRecPtr
  XLogRecord
+ XLogSegNo
  XLogwrtResult
  XLogwrtRqst
  XPVIV
*************** cached_re_str
*** 1882,1887 ****
--- 1899,1905 ----
  cashKEY
  celt
  cfp
+ check_agg_arguments_context
  check_network_data
  check_object_relabel_type
  check_password_hook_type
*************** ean13
*** 1920,1932 ****
  eary
  emit_log_hook_type
  eval_const_expressions_context
  execution_state
  explain_get_index_name_hook_type
  f_smgr
  fd_set
  finalize_primnode_context
  find_expr_references_context
- find_minimum_var_level_context
  fix_join_expr_context
  fix_scan_expr_context
  fix_upper_expr_context
--- 1938,1951 ----
  eary
  emit_log_hook_type
  eval_const_expressions_context
+ event_trigger_command_tag_check_result
+ event_trigger_support_data
  execution_state
  explain_get_index_name_hook_type
  f_smgr
  fd_set
  finalize_primnode_context
  find_expr_references_context
  fix_join_expr_context
  fix_scan_expr_context
  fix_upper_expr_context
*************** ino_t
*** 1986,1997 ****
  instr_time
  int16
  int16KEY
- int2
  int2vector
  int32
  int32KEY
  int32_t
- int4
  int64
  int64KEY
  int8
--- 2005,2014 ----
*************** line_t
*** 2018,2024 ****
  locale_t
  locate_agg_of_level_context
  locate_var_of_level_context
- locate_var_of_relation_context
  locate_windowfunc_context
  logstreamer_param
  lquery
--- 2035,2040 ----
*************** ltxtquery
*** 2031,2036 ****
--- 2047,2053 ----
  mXactCacheEnt
  macKEY
  macaddr
+ map_variable_attnos_context
  mb2wchar_with_len_converter
  mbcharacter_incrementer
  mbdisplaylen_converter
*************** pthread_t
*** 2139,2144 ****
--- 2156,2162 ----
  pull_var_clause_context
  pull_varattnos_context
  pull_varnos_context
+ pull_vars_context
  pullup_replace_vars_context
  qsort_arg_comparator
  radius_attribute
*************** rb_comparator
*** 2151,2157 ****
  rb_freefunc
  reduce_outer_joins_state
  regex_t
- regexp
  regexp_matches_ctx
  regmatch_t
  regoff_t
--- 2169,2174 ----
*************** temp_tablespaces_extra
*** 2232,2237 ****
--- 2249,2256 ----
  text
  timeKEY
  time_t
+ timeout_handler
+ timeout_params
  timerCA
  timezone_extra
  tlist_vinfo
*************** walrcv_connect_type
*** 2269,2274 ****
--- 2288,2294 ----
  walrcv_disconnect_type
  walrcv_receive_type
  walrcv_send_type
+ wchar2mb_with_len_converter
  wchar_t
  win32_deadchild_waitinfo
  win32_pthread
*************** xmlBufferPtr
*** 2323,2328 ****
--- 2343,2349 ----
  xmlChar
  xmlDocPtr
  xmlErrorPtr
+ xmlExternalEntityLoader
  xmlGenericErrorFunc
  xmlNodePtr
  xmlNodeSetPtr
*************** xmlXPathContextPtr
*** 2336,2342 ****
--- 2357,2365 ----
  xmlXPathObjectPtr
  xmltype
  xpath_workspace
+ xsltSecurityPrefsPtr
  xsltStylesheetPtr
+ xsltTransformContextPtr
  yy_parser
  yy_size_t
  yyscan_t