diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index ba2c57b..a2157b7 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -4296,6 +4296,24 @@ $$ LANGUAGE plpgsql;
     provide convenient features such as escaping single quotes and
     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
index e3fef84..cc9b479 100644
--- a/src/pl/plpgsql/src/Makefile
+++ b/src/pl/plpgsql/src/Makefile
@@ -17,9 +17,10 @@ override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
 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
+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
 
-DATA = plpgsql.control plpgsql--1.0.sql plpgsql--unpackaged--1.0.sql
 
 all: all-lib
 
@@ -52,7 +53,7 @@ uninstall-headers:
 
 
 # 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
+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 0000000..46bdeb4
--- /dev/null
+++ b/src/pl/plpgsql/src/pl_check.c
@@ -0,0 +1,1772 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "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, funcoid, OidIsValid(relid));
+	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,
+								    Oid funcoid, bool istrigger)
+{
+	/* clean structures */
+	MemSet(fcinfo, 0, sizeof(FunctionCallInfoData));
+	MemSet(flinfo, 0, sizeof(FmgrInfo));
+
+	fcinfo->flinfo = flinfo;
+	flinfo->fn_oid = funcoid;
+	flinfo->fn_mcxt = CurrentMemoryContext;
+
+	if (istrigger)
+	{
+		Assert(trigdata != NULL);
+
+		MemSet(trigdata, 0, sizeof(TriggerData));
+		trigdata->type = T_TriggerData;
+		fcinfo->context = (Node *) trigdata;
+	}
+}
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 5d2f818..245b7e9 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -115,7 +115,6 @@ static PLpgSQL_function *plpgsql_HashTableLookup(PLpgSQL_func_hashkey *func_key)
 static void plpgsql_HashTableInsert(PLpgSQL_function *function,
 						PLpgSQL_func_hashkey *func_key);
 static void plpgsql_HashTableDelete(PLpgSQL_function *function);
-static void delete_function(PLpgSQL_function *func);
 
 /* ----------
  * plpgsql_compile		Make an execution tree for a PL/pgSQL function.
@@ -175,7 +174,7 @@ recheck:
 			 * Nope, so remove it from hashtable and try to drop associated
 			 * storage (if not done already).
 			 */
-			delete_function(function);
+			plpgsql_delete_function(function);
 
 			/*
 			 * If the function isn't in active use then we can overwrite the
@@ -2426,7 +2425,7 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
 }
 
 /*
- * delete_function - clean up as much as possible of a stale function cache
+ * 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
@@ -2439,8 +2438,8 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
  * pointers to the same function cache.  Hence be careful not to do things
  * twice.
  */
-static void
-delete_function(PLpgSQL_function *func)
+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
index 8ca791c..bd84ab5 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -80,7 +80,6 @@ static SimpleEcontextStackEntry *simple_econtext_stack = NULL;
  * 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);
@@ -133,9 +132,6 @@ static int exec_stmt_dynexecute(PLpgSQL_execstate *estate,
 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,
@@ -204,7 +200,6 @@ static Datum exec_simple_cast_value(PLpgSQL_execstate *estate,
 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,
@@ -839,7 +834,7 @@ plpgsql_exec_error_callback(void *arg)
  * Support function for initializing local execution variables
  * ----------
  */
-static PLpgSQL_datum *
+PLpgSQL_datum *
 copy_plpgsql_datum(PLpgSQL_datum *datum)
 {
 	PLpgSQL_datum *result;
@@ -2858,7 +2853,7 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
  * Initialize a mostly empty execution state
  * ----------
  */
-static void
+void
 plpgsql_estate_setup(PLpgSQL_execstate *estate,
 					 PLpgSQL_function *func,
 					 ReturnSetInfo *rsi)
@@ -5924,7 +5919,7 @@ plpgsql_create_econtext(PLpgSQL_execstate *estate)
  * We check that it matches the top stack entry, and destroy the stack
  * entry along with the context.
  */
-static void
+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
index 022ec3f..4d3364c 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -15,6 +15,8 @@
 
 #include "plpgsql.h"
 
+#include "catalog/pg_enum.h"
+#include "catalog/pg_language.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
@@ -39,6 +41,8 @@ int			plpgsql_variable_conflict = PLPGSQL_RESOLVE_ERROR;
 /* 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
@@ -180,11 +184,7 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
 	 * 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;
+	plpgsql_setup_fake_fcinfo(&flinfo, &fake_fcinfo, NULL, InvalidOid, false);
 
 	retval = plpgsql_exec_function(func, &fake_fcinfo);
 
@@ -286,17 +286,7 @@ plpgsql_validator(PG_FUNCTION_ARGS)
 		 * 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 (istrigger)
-		{
-			MemSet(&trigdata, 0, sizeof(trigdata));
-			trigdata.type = T_TriggerData;
-			fake_fcinfo.context = (Node *) &trigdata;
-		}
+		plpgsql_setup_fake_fcinfo(&flinfo, &fake_fcinfo, &trigdata, funcoid, istrigger);
 
 		/* Test-compile the function */
 		plpgsql_compile(&fake_fcinfo, true);
@@ -312,3 +302,153 @@ plpgsql_validator(PG_FUNCTION_ARGS)
 
 	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 0000000..62cd8c2
--- /dev/null
+++ b/src/pl/plpgsql/src/plpgsql--1.0--1.1.sql
@@ -0,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 0000000..291fdc5
--- /dev/null
+++ b/src/pl/plpgsql/src/plpgsql--1.1.sql
@@ -0,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
index b320227..4c75c93 100644
--- a/src/pl/plpgsql/src/plpgsql.control
+++ b/src/pl/plpgsql/src/plpgsql.control
@@ -1,6 +1,6 @@
 # plpgsql extension
 comment = 'PL/pgSQL procedural language'
-default_version = '1.0'
+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
index b63f336..c2a4cc9 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -767,6 +767,21 @@ typedef struct PLpgSQL_execstate
 	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.
@@ -902,6 +917,7 @@ extern PLpgSQL_condition *plpgsql_parse_err_condition(char *condname);
 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
@@ -911,6 +927,7 @@ extern void _PG_init(void);
 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
@@ -928,6 +945,22 @@ extern Oid exec_get_datum_type(PLpgSQL_execstate *estate,
 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, Oid funcoid, bool istrigger);
 
 /* ----------
  * Functions for namespace handling in pl_funcs.c
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 1e45919..77d575d 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -302,6 +302,12 @@ end;
 ' 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
@@ -635,6 +641,12 @@ begin
     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
@@ -2950,6 +2962,38 @@ NOTICE:  4 bb cc
  
 (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
@@ -3431,6 +3475,12 @@ begin
   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
@@ -3864,6 +3914,12 @@ begin
   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 
 -----------
@@ -4719,3 +4775,528 @@ ERROR:  value for domain orderedarray violates check constraint "sorted"
 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
index 2b60b67..87a15d3 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -366,6 +366,8 @@ end;
 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
@@ -747,6 +749,9 @@ begin
 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
@@ -2458,6 +2463,13 @@ $proc$ language plpgsql;
 
 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
@@ -2837,6 +2849,9 @@ begin
 end;
 $$ language plpgsql;
 
+-- check function should not fail
+select plpgsql_check_function('forc01()');
+
 select forc01();
 
 -- try updating the cursor's current row
@@ -3171,6 +3186,10 @@ begin
 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);
@@ -3723,3 +3742,365 @@ select testoa(1,2,1); -- fail at update
 
 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();
+
