patch: CHECK FUNCTION statement

Started by Pavel Stehuleabout 14 years ago99 messages
#1Pavel Stehule
pavel.stehule@gmail.com
1 attachment(s)

Hello all,

I am sending updated patch, that implements a CHECK FUNCTION and CHECK
TRIGGER statements.

This patch is significantly redesigned to previous version (PL/pgSQL
part) - it is more readable, more accurate. There are new regress
tests.

Please, can some English native speaker fix doc and comments?

Usage is very simply

postgres=# \d t1
Table "public.t1"
Column | Type | Modifiers
--------+---------+-----------
a | integer |
b | integer |
Triggers:
t1_f1 BEFORE INSERT ON t1 FOR EACH ROW EXECUTE PROCEDURE f1_trg()

postgres=# \sf+ f1_trg
CREATE OR REPLACE FUNCTION public.f1_trg()
RETURNS trigger
LANGUAGE plpgsql
1 AS $function$
2 begin
3 new.a := new.a + 10;
4 new.b := new.b + 10;
5 new.c := 30;
6 return new;
7 end;
8 $function$
postgres=# check trigger t1_f1 on t1;
NOTICE: checking function "f1_trg()"
ERROR: record "new" has no field "c"
CONTEXT: checking of PL/pgSQL function "f1_trg" line 5 at assignment

Checker handler should be called explicitly

postgres=# select plpgsql_checker('f1'::regproc, 0);
ERROR: column "c" of relation "t1" does not exist
LINE 1: update t1 set c = 30
^
QUERY: update t1 set c = 30
CONTEXT: checking of PL/pgSQL function "f1" line 4 at SQL statement

or (check or plpgsql custom functions)

DO $$
DECLARE r regprocedure;
BEGIN
FOR r IN SELECT p.oid
FROM pg_catalog.pg_proc p
LEFT JOIN pg_catalog.pg_namespace n ON
n.oid = p.pronamespace
LEFT JOIN pg_catalog.pg_language l ON
l.oid = p.prolang
WHERE l.lanname = 'plpgsql'
AND n.nspname <> 'pg_catalog'
AND n.nspname <> 'information_schema'
AND p.prorettype <>
'pg_catalog.trigger'::pg_catalog.regtype
LOOP
RAISE NOTICE 'check %', r;
PERFORM plpgsql_checker(r, 0);
END LOOP;
END;
$$;

ToDo:

CHECK FUNCTION search function according to function signature - it
should be changes for using a actual types - it can be solution for
polymorphic types and useful tool for work with overloaded functions -
when is not clean, that function was executed.

check function foo(int, int);
NOTICE: checking function foo(variadic anyarray)
...

and maybe some support for named parameters
check function foo(name text, surname text);
NOTICE: checking function foo(text, text, text, text)
...

Regards

Pavel Stehule

Attachments:

check_pl-2011-11-26.difftext/x-patch; charset=US-ASCII; name=check_pl-2011-11-26.diffDownload
*** ./doc/src/sgml/ref/allfiles.sgml.orig	2011-11-25 12:04:21.509588672 +0100
--- ./doc/src/sgml/ref/allfiles.sgml	2011-11-25 12:05:31.844784492 +0100
***************
*** 40,45 ****
--- 40,46 ----
  <!ENTITY alterView          SYSTEM "alter_view.sgml">
  <!ENTITY analyze            SYSTEM "analyze.sgml">
  <!ENTITY begin              SYSTEM "begin.sgml">
+ <!ENTITY checkFunction      SYSTEM "check_function.sgml">
  <!ENTITY checkpoint         SYSTEM "checkpoint.sgml">
  <!ENTITY close              SYSTEM "close.sgml">
  <!ENTITY cluster            SYSTEM "cluster.sgml">
*** ./doc/src/sgml/ref/create_language.sgml.orig	2011-11-24 12:51:44.663317228 +0100
--- ./doc/src/sgml/ref/create_language.sgml	2011-11-25 12:05:31.845784481 +0100
***************
*** 23,29 ****
  <synopsis>
  CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
  CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ]
  </synopsis>
   </refsynopsisdiv>
  
--- 23,29 ----
  <synopsis>
  CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
  CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ] [ CHECK <replaceable>checkfunction</replaceable> ]
  </synopsis>
   </refsynopsisdiv>
  
***************
*** 217,222 ****
--- 217,236 ----
        </para>
       </listitem>
      </varlistentry>
+ 
+     <varlistentry>
+      <term><literal>CHECK</literal> <replaceable class="parameter">checkfunction</replaceable></term>
+ 
+      <listitem>
+       <para><replaceable class="parameter">checkfunction</replaceable> is the
+        name of a previously registered function that will be called
+        when a new function in the language is created, to check the
+        function by statemnt <command>CHECK FUNCTION</command> or 
+        <command>CHECK TRIGGER</command>.
+       </para>
+      </listitem>
+     </varlistentry>
+ 
     </variablelist>
  
    <para>
*** ./doc/src/sgml/reference.sgml.orig	2011-11-25 12:04:21.530588430 +0100
--- ./doc/src/sgml/reference.sgml	2011-11-25 12:05:31.849784436 +0100
***************
*** 68,73 ****
--- 68,74 ----
     &alterView;
     &analyze;
     &begin;
+    &checkFunction;
     &checkpoint;
     &close;
     &cluster;
*** ./doc/src/sgml/ref/check_function.sgml.orig	2011-11-25 12:05:11.416017756 +0100
--- ./doc/src/sgml/ref/check_function.sgml	2011-11-25 12:05:31.849784436 +0100
***************
*** 0 ****
--- 1,37 ----
+ <!--
+ doc/src/sgml/ref/check_function.sgml
+ -->
+ 
+ <refentry id="SQL-CHECKFUNCTION">
+  <refmeta>
+   <refentrytitle>CHECK FUNCTION</refentrytitle>
+   <manvolnum>7</manvolnum>
+   <refmiscinfo>SQL - Language Statements</refmiscinfo>
+  </refmeta>
+ 
+  <refnamediv>
+   <refname>CHECK FUNCTION</refname>
+   <refpurpose>ensure a deep checking of existing function</refpurpose>
+  </refnamediv>
+ 
+  <indexterm zone="sql-checkfunction">
+   <primary>CHECK FUNCTION</primary>
+  </indexterm>
+ 
+  <refsynopsisdiv>
+ <synopsis>
+ CREATE FUNCTION <replaceable class="parameter">name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] )
+  | CREATE TRIGGER <replaceable class="parameter">name</replaceable> ON <replaceable class="parameter">tablename</replaceable>
+ </synopsis>
+  </refsynopsisdiv>
+ 
+  <refsect1 id="sql-checkfunction-description">
+   <title>Description</title>
+ 
+   <para>
+    <command>CHECK FUNCTION</command> check a existing function.
+    <command>CHECK TRIGGER</command> check a trigger function.
+   </para>
+  </refsect1>
+ 
+ </refentry>
*** ./src/backend/catalog/pg_proc.c.orig	2011-11-24 12:51:44.745316295 +0100
--- ./src/backend/catalog/pg_proc.c	2011-11-25 12:05:31.850784424 +0100
***************
*** 1101,1103 ****
--- 1101,1104 ----
  	*newcursorpos = newcp;
  	return false;
  }
+ 
*** ./src/backend/commands/functioncmds.c.orig	2011-11-24 12:51:44.754316192 +0100
--- ./src/backend/commands/functioncmds.c	2011-11-25 20:01:45.521469608 +0100
***************
*** 44,53 ****
--- 44,55 ----
  #include "catalog/pg_namespace.h"
  #include "catalog/pg_proc.h"
  #include "catalog/pg_proc_fn.h"
+ #include "catalog/pg_trigger.h"
  #include "catalog/pg_type.h"
  #include "catalog/pg_type_fn.h"
  #include "commands/defrem.h"
  #include "commands/proclang.h"
+ #include "commands/trigger.h"
  #include "miscadmin.h"
  #include "optimizer/var.h"
  #include "parser/parse_coerce.h"
***************
*** 60,65 ****
--- 62,68 ----
  #include "utils/fmgroids.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
+ #include "utils/memutils.h"
  #include "utils/rel.h"
  #include "utils/syscache.h"
  #include "utils/tqual.h"
***************
*** 1009,1014 ****
--- 1012,1152 ----
  	}
  }
  
+ /*
+  * CheckFunction
+  *			call a PL checker function when this function exists.
+  */
+ void
+ CheckFunction(CheckFunctionStmt *stmt)
+ {
+ 	List	   *functionName = stmt->funcname;
+ 	List	   *argTypes = stmt->args;	/* list of TypeName nodes */
+ 	Oid			funcOid;
+ 
+ 	HeapTuple	tup;
+ 	Form_pg_proc proc;
+ 
+ 	HeapTuple	languageTuple;
+ 	Form_pg_language languageStruct;
+ 	Oid		languageChecker;
+ 	Oid trgOid = InvalidOid;
+ 	Oid	relid = InvalidOid;
+ 
+ 	/* when we should to check trigger, then we should to find a trigger handler */
+ 	if (functionName == NULL)
+ 	{
+ 		HeapTuple	ht_trig;
+ 		Form_pg_trigger trigrec;
+ 		ScanKeyData skey[1];
+ 		Relation	tgrel;
+ 		SysScanDesc tgscan;
+ 		char *fname;
+ 
+ 		relid = RangeVarGetRelid(stmt->relation, ShareLock, false, false);
+ 		trgOid = get_trigger_oid(relid, stmt->trgname, false);
+ 
+ 		/*
+ 		 * Fetch the pg_trigger tuple by the Oid of the trigger
+ 		 */
+ 		tgrel = heap_open(TriggerRelationId, AccessShareLock);
+ 
+ 		ScanKeyInit(&skey[0],
+ 					ObjectIdAttributeNumber,
+ 					BTEqualStrategyNumber, F_OIDEQ,
+ 					ObjectIdGetDatum(trgOid));
+ 
+ 		tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true,
+ 									SnapshotNow, 1, skey);
+ 
+ 		ht_trig = systable_getnext(tgscan);
+ 
+ 		if (!HeapTupleIsValid(ht_trig))
+ 			elog(ERROR, "could not find tuple for trigger %u", trgOid);
+ 
+ 		trigrec = (Form_pg_trigger) GETSTRUCT(ht_trig);
+ 
+ 		/* we need to know trigger function to get PL checker function */
+ 		funcOid = trigrec->tgfoid;
+ 		fname = format_procedure(funcOid);
+ 		/* Clean up */
+ 		systable_endscan(tgscan);
+ 
+ 		elog(NOTICE, "checking function \"%s\"", fname);
+ 		pfree(fname);
+ 
+ 		heap_close(tgrel, AccessShareLock);
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * Find the function, 
+ 		 */
+ 		funcOid = LookupFuncNameTypeNames(functionName, argTypes, false);
+ 	}
+ 
+ 	tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
+ 	if (!HeapTupleIsValid(tup)) /* should not happen */
+ 		elog(ERROR, "cache lookup failed for function %u", funcOid);
+ 
+ 	proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 	languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
+ 	Assert(HeapTupleIsValid(languageTuple));
+ 
+ 	languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
+ 	languageChecker = languageStruct->lanchecker;
+ 
+ 	/* Check a function body */
+ 	if (OidIsValid(languageChecker))
+ 	{
+ 		ArrayType  *set_items = NULL;
+ 		int			save_nestlevel;
+ 		Datum	datum;
+ 		bool		isnull;
+ 		MemoryContext oldCxt;
+ 		MemoryContext checkCxt;
+ 
+ 		datum = SysCacheGetAttr(PROCOID, tup, Anum_pg_proc_proconfig, &isnull);
+ 
+ 		if (!isnull)
+ 		{
+ 			/* Set per-function configuration parameters */
+ 			set_items = (ArrayType *) DatumGetPointer(datum);
+ 			if (set_items)			/* Need a new GUC nesting level */
+ 			{
+ 				save_nestlevel = NewGUCNestLevel();
+ 				ProcessGUCArray(set_items,
+ 							(superuser() ? PGC_SUSET : PGC_USERSET),
+ 							PGC_S_SESSION,
+ 							GUC_ACTION_SAVE);
+ 			}
+ 			else
+ 				save_nestlevel = 0; /* keep compiler quiet */
+ 		}
+ 
+ 		checkCxt = AllocSetContextCreate(CurrentMemoryContext,
+ 									"Check temporary context",
+ 									ALLOCSET_DEFAULT_MINSIZE,
+ 									ALLOCSET_DEFAULT_INITSIZE,
+ 									ALLOCSET_DEFAULT_MAXSIZE);
+ 
+ 		oldCxt = MemoryContextSwitchTo(checkCxt);
+ 
+ 		OidFunctionCall2(languageChecker, ObjectIdGetDatum(funcOid), 
+ 							    ObjectIdGetDatum(relid));
+ 
+ 		MemoryContextSwitchTo(oldCxt);
+ 
+ 		if (set_items)
+ 			AtEOXact_GUC(true, save_nestlevel);
+ 	}
+ 	else
+ 		elog(WARNING, "language \"%s\" has no defined checker function",
+ 			    NameStr(languageStruct->lanname));
+ 
+ 	ReleaseSysCache(languageTuple);
+ 	ReleaseSysCache(tup);
+ }
  
  /*
   * Rename function
*** ./src/backend/commands/proclang.c.orig	2011-11-24 12:51:44.756316170 +0100
--- ./src/backend/commands/proclang.c	2011-11-25 12:17:46.031559228 +0100
***************
*** 46,57 ****
  	char	   *tmplhandler;	/* name of handler function */
  	char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
  	char	   *tmplvalidator;	/* name of validator function, or NULL */
  	char	   *tmpllibrary;	/* path of shared library */
  } PLTemplate;
  
  static void create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, bool trusted);
  static PLTemplate *find_language_template(const char *languageName);
  static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
  							Oid newOwnerId);
--- 46,58 ----
  	char	   *tmplhandler;	/* name of handler function */
  	char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
  	char	   *tmplvalidator;	/* name of validator function, or NULL */
+ 	char	   *tmplchecker;	/* name of checker function, or NULL */
  	char	   *tmpllibrary;	/* path of shared library */
  } PLTemplate;
  
  static void create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, Oid checkerOid, bool trusted);
  static PLTemplate *find_language_template(const char *languageName);
  static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
  							Oid newOwnerId);
***************
*** 67,75 ****
  	PLTemplate *pltemplate;
  	Oid			handlerOid,
  				inlineOid,
! 				valOid;
  	Oid			funcrettype;
! 	Oid			funcargtypes[1];
  
  	/*
  	 * If we have template information for the language, ignore the supplied
--- 68,77 ----
  	PLTemplate *pltemplate;
  	Oid			handlerOid,
  				inlineOid,
! 				valOid,
! 				checkerOid;
  	Oid			funcrettype;
! 	Oid			funcargtypes[2];
  
  	/*
  	 * If we have template information for the language, ignore the supplied
***************
*** 219,228 ****
  		else
  			valOid = InvalidOid;
  
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, pltemplate->tmpltrusted);
  	}
  	else
  	{
--- 221,269 ----
  		else
  			valOid = InvalidOid;
  
+ 		/*
+ 		 * Likewise for the checker, if required; but we don't care about
+ 		 * its return type.
+ 		 */
+ 		if (pltemplate->tmplchecker)
+ 		{
+ 			funcname = SystemFuncName(pltemplate->tmplchecker);
+ 			funcargtypes[0] = OIDOID;
+ 			funcargtypes[1] = REGCLASSOID;
+ 			checkerOid = LookupFuncName(funcname, 2, funcargtypes, true);
+ 			if (!OidIsValid(checkerOid))
+ 			{
+ 				checkerOid = ProcedureCreate(pltemplate->tmplchecker,
+ 										 PG_CATALOG_NAMESPACE,
+ 										 false, /* replace */
+ 										 false, /* returnsSet */
+ 										 VOIDOID,
+ 										 ClanguageId,
+ 										 F_FMGR_C_VALIDATOR,
+ 										 pltemplate->tmplchecker,
+ 										 pltemplate->tmpllibrary,
+ 										 false, /* isAgg */
+ 										 false, /* isWindowFunc */
+ 										 false, /* security_definer */
+ 										 true,	/* isStrict */
+ 										 PROVOLATILE_VOLATILE,
+ 										 buildoidvector(funcargtypes, 2),
+ 										 PointerGetDatum(NULL),
+ 										 PointerGetDatum(NULL),
+ 										 PointerGetDatum(NULL),
+ 										 NIL,
+ 										 PointerGetDatum(NULL),
+ 										 1,
+ 										 0);
+ 			}
+ 		}
+ 		else
+ 			checkerOid = InvalidOid;
+ 
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, checkerOid, pltemplate->tmpltrusted);
  	}
  	else
  	{
***************
*** 294,303 ****
  		else
  			valOid = InvalidOid;
  
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, stmt->pltrusted);
  	}
  }
  
--- 335,355 ----
  		else
  			valOid = InvalidOid;
  
+ 		/* validate the checker function */
+ 		if (stmt->plchecker)
+ 		{
+ 			funcargtypes[0] = OIDOID;
+ 			funcargtypes[1] = REGCLASSOID;
+ 			checkerOid = LookupFuncName(stmt->plchecker, 2, funcargtypes, false);
+ 			/* return value is ignored, so we don't check the type */
+ 		}
+ 		else
+ 			checkerOid = InvalidOid;
+ 
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, checkerOid, stmt->pltrusted);
  	}
  }
  
***************
*** 307,313 ****
  static void
  create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, bool trusted)
  {
  	Relation	rel;
  	TupleDesc	tupDesc;
--- 359,365 ----
  static void
  create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, Oid checkerOid, bool trusted)
  {
  	Relation	rel;
  	TupleDesc	tupDesc;
***************
*** 337,342 ****
--- 389,395 ----
  	values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid);
  	values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid);
  	values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid);
+ 	values[Anum_pg_language_lanchecker - 1] = ObjectIdGetDatum(checkerOid);
  	nulls[Anum_pg_language_lanacl - 1] = true;
  
  	/* Check for pre-existing definition */
***************
*** 423,428 ****
--- 476,490 ----
  		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  	}
  
+ 	/* dependency on the checker function, if any */
+ 	if (OidIsValid(checkerOid))
+ 	{
+ 		referenced.classId = ProcedureRelationId;
+ 		referenced.objectId = checkerOid;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	/* Post creation hook for new procedural language */
  	InvokeObjectAccessHook(OAT_POST_CREATE,
  						   LanguageRelationId, myself.objectId, 0);
***************
*** 478,483 ****
--- 540,550 ----
  		if (!isnull)
  			result->tmplvalidator = TextDatumGetCString(datum);
  
+ 		datum = heap_getattr(tup, Anum_pg_pltemplate_tmplchecker,
+ 							 RelationGetDescr(rel), &isnull);
+ 		if (!isnull)
+ 			result->tmplchecker = TextDatumGetCString(datum);
+ 
  		datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary,
  							 RelationGetDescr(rel), &isnull);
  		if (!isnull)
*** ./src/backend/parser/gram.y.orig	2011-11-24 12:51:44.810315553 +0100
--- ./src/backend/parser/gram.y	2011-11-25 12:05:31.859784321 +0100
***************
*** 227,232 ****
--- 227,233 ----
  		DeallocateStmt PrepareStmt ExecuteStmt
  		DropOwnedStmt ReassignOwnedStmt
  		AlterTSConfigurationStmt AlterTSDictionaryStmt
+ 		CheckFunctionStmt
  
  %type <node>	select_no_parens select_with_parens select_clause
  				simple_select values_clause
***************
*** 276,282 ****
  
  %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
  				opt_class opt_inline_handler opt_validator validator_clause
! 				opt_collate
  
  %type <range>	qualified_name OptConstrFromTable
  
--- 277,283 ----
  
  %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
  				opt_class opt_inline_handler opt_validator validator_clause
! 				opt_collate opt_checker
  
  %type <range>	qualified_name OptConstrFromTable
  
***************
*** 701,706 ****
--- 702,708 ----
  			| AlterUserSetStmt
  			| AlterUserStmt
  			| AnalyzeStmt
+ 			| CheckFunctionStmt
  			| CheckPointStmt
  			| ClosePortalStmt
  			| ClusterStmt
***************
*** 3206,3216 ****
  				n->plhandler = NIL;
  				n->plinline = NIL;
  				n->plvalidator = NIL;
  				n->pltrusted = false;
  				$$ = (Node *)n;
  			}
  			| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
! 			  HANDLER handler_name opt_inline_handler opt_validator
  			{
  				CreatePLangStmt *n = makeNode(CreatePLangStmt);
  				n->replace = $2;
--- 3208,3219 ----
  				n->plhandler = NIL;
  				n->plinline = NIL;
  				n->plvalidator = NIL;
+ 				n->plchecker = NIL;
  				n->pltrusted = false;
  				$$ = (Node *)n;
  			}
  			| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
! 			  HANDLER handler_name opt_inline_handler opt_validator opt_checker
  			{
  				CreatePLangStmt *n = makeNode(CreatePLangStmt);
  				n->replace = $2;
***************
*** 3218,3223 ****
--- 3221,3227 ----
  				n->plhandler = $8;
  				n->plinline = $9;
  				n->plvalidator = $10;
+ 				n->plchecker = $11;
  				n->pltrusted = $3;
  				$$ = (Node *)n;
  			}
***************
*** 3252,3257 ****
--- 3256,3266 ----
  			| /*EMPTY*/								{ $$ = NIL; }
  		;
  
+ opt_checker:
+ 			CHECK handler_name					{ $$ = $2; }
+ 			| /*EMPTY*/								{ $$ = NIL; }
+ 		;
+ 
  DropPLangStmt:
  			DROP opt_procedural LANGUAGE ColId_or_Sconst opt_drop_behavior
  				{
***************
*** 6282,6287 ****
--- 6291,6326 ----
  
  /*****************************************************************************
   *
+  *		CHECK FUNCTION funcname(args)
+  *		CHECK TRIGGER triggername ON table
+  *
+  *
+  *****************************************************************************/
+ 
+ 
+ CheckFunctionStmt:
+ 			CHECK FUNCTION func_name func_args
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = $3;
+ 					n->args = extractArgTypes($4);
+ 					n->trgname = NULL;
+ 					n->relation = NULL;
+ 					$$ = (Node *) n;
+ 				}
+ 			| CHECK TRIGGER name ON qualified_name
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = NULL;
+ 					n->args = NIL;
+ 					n->trgname = $3;
+ 					n->relation = $5;
+ 					$$ = (Node *) n;
+ 				}
+ 		;
+ 
+ /*****************************************************************************
+  *
   *		DO <anonymous code block> [ LANGUAGE language ]
   *
   * We use a DefElem list for future extensibility, and to allow flexibility
*** ./src/backend/tcop/utility.c.orig	2011-11-24 12:51:44.929314198 +0100
--- ./src/backend/tcop/utility.c	2011-11-25 12:05:31.880784082 +0100
***************
*** 882,887 ****
--- 882,891 ----
  			AlterFunction((AlterFunctionStmt *) parsetree);
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			CheckFunction((CheckFunctionStmt *) parsetree);
+ 			break;
+ 
  		case T_IndexStmt:		/* CREATE INDEX */
  			{
  				IndexStmt  *stmt = (IndexStmt *) parsetree;
***************
*** 2125,2130 ****
--- 2129,2141 ----
  			}
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			if (((CheckFunctionStmt *) parsetree)->funcname != NULL)
+ 				tag = "CHECK FUNCTION";
+ 			else
+ 				tag = "CHECK TRIGGER";
+ 			break;
+ 
  		default:
  			elog(WARNING, "unrecognized node type: %d",
  				 (int) nodeTag(parsetree));
***************
*** 2565,2570 ****
--- 2576,2585 ----
  			}
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			lev = LOGSTMT_ALL;
+ 			break;
+ 
  		default:
  			elog(WARNING, "unrecognized node type: %d",
  				 (int) nodeTag(parsetree));
*** ./src/bin/psql/tab-complete.c.orig	2011-11-24 12:51:45.147311716 +0100
--- ./src/bin/psql/tab-complete.c	2011-11-25 12:05:31.882784060 +0100
***************
*** 1,4 ****
--- 1,5 ----
  /*
+  *
   * psql - the PostgreSQL interactive terminal
   *
   * Copyright (c) 2000-2011, PostgreSQL Global Development Group
***************
*** 727,733 ****
  #define prev6_wd  (previous_words[5])
  
  	static const char *const sql_commands[] = {
! 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
  		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
--- 728,734 ----
  #define prev6_wd  (previous_words[5])
  
  	static const char *const sql_commands[] = {
! 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECK", "CHECKPOINT", "CLOSE", "CLUSTER",
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
  		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
***************
*** 1524,1529 ****
--- 1525,1552 ----
  
  		COMPLETE_WITH_LIST(list_TRANS);
  	}
+ 
+ /* CHECK */
+ 	else if (pg_strcasecmp(prev_wd, "CHECK") == 0)
+ 	{
+ 		static const char *const list_CHECK[] =
+ 		{"FUNCTION", "TRIGGER", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_CHECK);
+ 	}
+ 	else if (pg_strcasecmp(prev3_wd, "CHECK") == 0 &&
+ 			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+ 	{
+ 		COMPLETE_WITH_CONST("ON");
+ 	}
+ 	else if (pg_strcasecmp(prev4_wd, "CHECK") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "ON") == 0)
+ 	{
+ 		completion_info_charp = prev2_wd;
+ 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+ 	}
+ 
  /* CLUSTER */
  
  	/*
*** ./src/include/catalog/pg_language.h.orig	2011-11-25 12:04:21.610587514 +0100
--- ./src/include/catalog/pg_language.h	2011-11-25 12:05:31.883784048 +0100
***************
*** 37,42 ****
--- 37,43 ----
  	Oid			lanplcallfoid;	/* Call handler for PL */
  	Oid			laninline;		/* Optional anonymous-block handler function */
  	Oid			lanvalidator;	/* Optional validation function */
+ 	Oid			lanchecker;	/* Optional checker function */
  	aclitem		lanacl[1];		/* Access privileges */
  } FormData_pg_language;
  
***************
*** 51,57 ****
   *		compiler constants for pg_language
   * ----------------
   */
! #define Natts_pg_language				8
  #define Anum_pg_language_lanname		1
  #define Anum_pg_language_lanowner		2
  #define Anum_pg_language_lanispl		3
--- 52,58 ----
   *		compiler constants for pg_language
   * ----------------
   */
! #define Natts_pg_language				9
  #define Anum_pg_language_lanname		1
  #define Anum_pg_language_lanowner		2
  #define Anum_pg_language_lanispl		3
***************
*** 59,78 ****
  #define Anum_pg_language_lanplcallfoid	5
  #define Anum_pg_language_laninline		6
  #define Anum_pg_language_lanvalidator	7
! #define Anum_pg_language_lanacl			8
  
  /* ----------------
   *		initial contents of pg_language
   * ----------------
   */
  
! DATA(insert OID = 12 ( "internal"	PGUID f f 0 0 2246 _null_ ));
  DESCR("built-in functions");
  #define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c"			PGUID f f 0 0 2247 _null_ ));
  DESCR("dynamically-loaded C functions");
  #define ClanguageId 13
! DATA(insert OID = 14 ( "sql"		PGUID f t 0 0 2248 _null_ ));
  DESCR("SQL-language functions");
  #define SQLlanguageId 14
  
--- 60,80 ----
  #define Anum_pg_language_lanplcallfoid	5
  #define Anum_pg_language_laninline		6
  #define Anum_pg_language_lanvalidator	7
! #define Anum_pg_language_lanchecker	8
! #define Anum_pg_language_lanacl			9
  
  /* ----------------
   *		initial contents of pg_language
   * ----------------
   */
  
! DATA(insert OID = 12 ( "internal"	PGUID f f 0 0 2246 0 _null_ ));
  DESCR("built-in functions");
  #define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c"			PGUID f f 0 0 2247 0 _null_ ));
  DESCR("dynamically-loaded C functions");
  #define ClanguageId 13
! DATA(insert OID = 14 ( "sql"		PGUID f t 0 0 2248 0 _null_ ));
  DESCR("SQL-language functions");
  #define SQLlanguageId 14
  
*** ./src/include/catalog/pg_pltemplate.h.orig	2011-11-25 12:04:21.612587492 +0100
--- ./src/include/catalog/pg_pltemplate.h	2011-11-25 12:05:31.884784036 +0100
***************
*** 36,41 ****
--- 36,42 ----
  	text		tmplhandler;	/* name of call handler function */
  	text		tmplinline;		/* name of anonymous-block handler, or NULL */
  	text		tmplvalidator;	/* name of validator function, or NULL */
+ 	text		tmplchecker;	/* name of checker function, or NULL */
  	text		tmpllibrary;	/* path of shared library */
  	aclitem		tmplacl[1];		/* access privileges for template */
  } FormData_pg_pltemplate;
***************
*** 51,65 ****
   *		compiler constants for pg_pltemplate
   * ----------------
   */
! #define Natts_pg_pltemplate					8
  #define Anum_pg_pltemplate_tmplname			1
  #define Anum_pg_pltemplate_tmpltrusted		2
  #define Anum_pg_pltemplate_tmpldbacreate	3
  #define Anum_pg_pltemplate_tmplhandler		4
  #define Anum_pg_pltemplate_tmplinline		5
  #define Anum_pg_pltemplate_tmplvalidator	6
! #define Anum_pg_pltemplate_tmpllibrary		7
! #define Anum_pg_pltemplate_tmplacl			8
  
  
  /* ----------------
--- 52,67 ----
   *		compiler constants for pg_pltemplate
   * ----------------
   */
! #define Natts_pg_pltemplate					9
  #define Anum_pg_pltemplate_tmplname			1
  #define Anum_pg_pltemplate_tmpltrusted		2
  #define Anum_pg_pltemplate_tmpldbacreate	3
  #define Anum_pg_pltemplate_tmplhandler		4
  #define Anum_pg_pltemplate_tmplinline		5
  #define Anum_pg_pltemplate_tmplvalidator	6
! #define Anum_pg_pltemplate_tmplchecker		7
! #define Anum_pg_pltemplate_tmpllibrary		8
! #define Anum_pg_pltemplate_tmplacl			9
  
  
  /* ----------------
***************
*** 67,79 ****
   * ----------------
   */
  
! DATA(insert ( "plpgsql"		t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl"		t t "pltcl_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu"		f f "pltclu_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl"		t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu"		f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu"	f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u"	f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u"	f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" "$libdir/plpython3" _null_ ));
  
  #endif   /* PG_PLTEMPLATE_H */
--- 69,81 ----
   * ----------------
   */
  
! DATA(insert ( "plpgsql"		t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "plpgsql_checker" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl"		t t "pltcl_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu"		f f "pltclu_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl"		t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu"		f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu"	f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u"	f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u"	f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" _null_ "$libdir/plpython3" _null_ ));
  
  #endif   /* PG_PLTEMPLATE_H */
*** ./src/include/commands/defrem.h.orig	2011-11-24 12:51:45.172311430 +0100
--- ./src/include/commands/defrem.h	2011-11-25 12:05:31.885784024 +0100
***************
*** 62,67 ****
--- 62,68 ----
  /* commands/functioncmds.c */
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
  extern void RemoveFunctionById(Oid funcOid);
+ extern void CheckFunction(CheckFunctionStmt *stmt);
  extern void SetFunctionReturnType(Oid funcOid, Oid newRetType);
  extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType);
  extern void RenameFunction(List *name, List *argtypes, const char *newname);
*** ./src/include/nodes/nodes.h.orig	2011-11-24 12:51:45.000000000 +0100
--- ./src/include/nodes/nodes.h	2011-11-25 12:22:37.306374249 +0100
***************
*** 291,296 ****
--- 291,297 ----
  	T_IndexStmt,
  	T_CreateFunctionStmt,
  	T_AlterFunctionStmt,
+ 	T_CheckFunctionStmt,
  	T_DoStmt,
  	T_RenameStmt,
  	T_RuleStmt,
*** ./src/include/nodes/parsenodes.h.orig	2011-11-24 12:51:45.194311178 +0100
--- ./src/include/nodes/parsenodes.h	2011-11-25 12:05:31.888783991 +0100
***************
*** 1734,1739 ****
--- 1734,1740 ----
  	List	   *plhandler;		/* PL call handler function (qual. name) */
  	List	   *plinline;		/* optional inline function (qual. name) */
  	List	   *plvalidator;	/* optional validator function (qual. name) */
+ 	List	   *plchecker;		/* optional checker function (qual. name) */
  	bool		pltrusted;		/* PL is trusted */
  } CreatePLangStmt;
  
***************
*** 2077,2082 ****
--- 2078,2096 ----
  } AlterFunctionStmt;
  
  /* ----------------------
+  *		Check {Function|Trigger} Statement
+  * ----------------------
+  */
+ typedef struct CheckFunctionStmt
+ {
+ 	NodeTag		type;
+ 	List	   *funcname;			/* qualified name of checked object */
+ 	List	   *args;			/* types of the arguments */
+ 	char	   *trgname;			/* trigger's name */
+ 	RangeVar	*relation;		/* trigger's relation */
+ } CheckFunctionStmt;
+ 
+ /* ----------------------
   *		DO Statement
   *
   * DoStmt is the raw parser output, InlineCodeBlock is the execution-time API
*** ./src/pl/plpgsql/src/Makefile.orig	2011-11-24 12:51:45.000000000 +0100
--- ./src/pl/plpgsql/src/Makefile	2011-11-25 13:23:57.605922840 +0100
***************
*** 17,23 ****
  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
  
--- 17,23 ----
  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--unpackaged--1.0.sql
  
***************
*** 44,50 ****
  
  
  # 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
  
--- 44,50 ----
  
  
  # 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
  
*** ./src/pl/plpgsql/src/pl_exec.c.orig	2011-11-24 12:51:45.000000000 +0100
--- ./src/pl/plpgsql/src/pl_exec.c	2011-11-25 22:19:41.398342164 +0100
***************
*** 80,86 ****
   * 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);
--- 80,85 ----
***************
*** 133,141 ****
  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,
--- 132,137 ----
***************
*** 181,190 ****
  static ParamListInfo setup_param_list(PLpgSQL_execstate *estate,
  				 PLpgSQL_expr *expr);
  static void plpgsql_param_fetch(ParamListInfo params, int paramid);
- static void exec_move_row(PLpgSQL_execstate *estate,
- 			  PLpgSQL_rec *rec,
- 			  PLpgSQL_row *row,
- 			  HeapTuple tup, TupleDesc tupdesc);
  static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate,
  					PLpgSQL_row *row,
  					TupleDesc tupdesc);
--- 177,182 ----
***************
*** 201,207 ****
  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,
--- 193,198 ----
***************
*** 229,235 ****
  	 * Setup the execution state
  	 */
  	plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo);
- 
  	/*
  	 * Setup error traceback support for ereport()
  	 */
--- 220,225 ----
***************
*** 243,249 ****
  	 */
  	estate.err_text = gettext_noop("during initialization of execution state");
  	for (i = 0; i < estate.ndatums; i++)
! 		estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
  
  	/*
  	 * Store the actual call argument values into the appropriate variables
--- 233,239 ----
  	 */
  	estate.err_text = gettext_noop("during initialization of execution state");
  	for (i = 0; i < estate.ndatums; i++)
! 		estate.datums[i] = plpgsql_copy_datum(func->datums[i]);
  
  	/*
  	 * Store the actual call argument values into the appropriate variables
***************
*** 287,299 ****
  						ItemPointerSetInvalid(&(tmptup.t_self));
  						tmptup.t_tableOid = InvalidOid;
  						tmptup.t_data = td;
! 						exec_move_row(&estate, NULL, row, &tmptup, tupdesc);
  						ReleaseTupleDesc(tupdesc);
  					}
  					else
  					{
  						/* If arg is null, treat it as an empty row */
! 						exec_move_row(&estate, NULL, row, NULL, NULL);
  					}
  				}
  				break;
--- 277,289 ----
  						ItemPointerSetInvalid(&(tmptup.t_self));
  						tmptup.t_tableOid = InvalidOid;
  						tmptup.t_data = td;
! 						plpgsql_exec_move_row(&estate, NULL, row, &tmptup, tupdesc);
  						ReleaseTupleDesc(tupdesc);
  					}
  					else
  					{
  						/* If arg is null, treat it as an empty row */
! 						plpgsql_exec_move_row(&estate, NULL, row, NULL, NULL);
  					}
  				}
  				break;
***************
*** 514,520 ****
  	 */
  	estate.err_text = gettext_noop("during initialization of execution state");
  	for (i = 0; i < estate.ndatums; i++)
! 		estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
  
  	/*
  	 * Put the OLD and NEW tuples into record variables
--- 504,510 ----
  	 */
  	estate.err_text = gettext_noop("during initialization of execution state");
  	for (i = 0; i < estate.ndatums; i++)
! 		estate.datums[i] = plpgsql_copy_datum(func->datums[i]);
  
  	/*
  	 * Put the OLD and NEW tuples into record variables
***************
*** 832,839 ****
   * Support function for initializing local execution variables
   * ----------
   */
! static PLpgSQL_datum *
! copy_plpgsql_datum(PLpgSQL_datum *datum)
  {
  	PLpgSQL_datum *result;
  
--- 822,829 ----
   * Support function for initializing local execution variables
   * ----------
   */
! PLpgSQL_datum *
! plpgsql_copy_datum(PLpgSQL_datum *datum)
  {
  	PLpgSQL_datum *result;
  
***************
*** 2854,2860 ****
   * Initialize a mostly empty execution state
   * ----------
   */
! static void
  plpgsql_estate_setup(PLpgSQL_execstate *estate,
  					 PLpgSQL_function *func,
  					 ReturnSetInfo *rsi)
--- 2844,2850 ----
   * Initialize a mostly empty execution state
   * ----------
   */
! void
  plpgsql_estate_setup(PLpgSQL_execstate *estate,
  					 PLpgSQL_function *func,
  					 ReturnSetInfo *rsi)
***************
*** 3170,3176 ****
  						(errcode(ERRCODE_NO_DATA_FOUND),
  						 errmsg("query returned no rows")));
  			/* set the target to NULL(s) */
! 			exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
  		}
  		else
  		{
--- 3160,3166 ----
  						(errcode(ERRCODE_NO_DATA_FOUND),
  						 errmsg("query returned no rows")));
  			/* set the target to NULL(s) */
! 			plpgsql_exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
  		}
  		else
  		{
***************
*** 3179,3185 ****
  						(errcode(ERRCODE_TOO_MANY_ROWS),
  						 errmsg("query returned more than one row")));
  			/* Put the first result row into the target */
! 			exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
  		}
  
  		/* Clean up */
--- 3169,3175 ----
  						(errcode(ERRCODE_TOO_MANY_ROWS),
  						 errmsg("query returned more than one row")));
  			/* Put the first result row into the target */
! 			plpgsql_exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
  		}
  
  		/* Clean up */
***************
*** 3350,3356 ****
  						(errcode(ERRCODE_NO_DATA_FOUND),
  						 errmsg("query returned no rows")));
  			/* set the target to NULL(s) */
! 			exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
  		}
  		else
  		{
--- 3340,3346 ----
  						(errcode(ERRCODE_NO_DATA_FOUND),
  						 errmsg("query returned no rows")));
  			/* set the target to NULL(s) */
! 			plpgsql_exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
  		}
  		else
  		{
***************
*** 3359,3365 ****
  						(errcode(ERRCODE_TOO_MANY_ROWS),
  						 errmsg("query returned more than one row")));
  			/* Put the first result row into the target */
! 			exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
  		}
  	}
  	else
--- 3349,3355 ----
  						(errcode(ERRCODE_TOO_MANY_ROWS),
  						 errmsg("query returned more than one row")));
  			/* Put the first result row into the target */
! 			plpgsql_exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
  		}
  	}
  	else
***************
*** 3630,3638 ****
  		 * ----------
  		 */
  		if (n == 0)
! 			exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
  		else
! 			exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
  
  		SPI_freetuptable(tuptab);
  	}
--- 3620,3628 ----
  		 * ----------
  		 */
  		if (n == 0)
! 			plpgsql_exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
  		else
! 			plpgsql_exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
  
  		SPI_freetuptable(tuptab);
  	}
***************
*** 3806,3812 ****
  				if (*isNull)
  				{
  					/* If source is null, just assign nulls to the row */
! 					exec_move_row(estate, NULL, row, NULL, NULL);
  				}
  				else
  				{
--- 3796,3802 ----
  				if (*isNull)
  				{
  					/* If source is null, just assign nulls to the row */
! 					plpgsql_exec_move_row(estate, NULL, row, NULL, NULL);
  				}
  				else
  				{
***************
*** 3832,3838 ****
  					ItemPointerSetInvalid(&(tmptup.t_self));
  					tmptup.t_tableOid = InvalidOid;
  					tmptup.t_data = td;
! 					exec_move_row(estate, NULL, row, &tmptup, tupdesc);
  					ReleaseTupleDesc(tupdesc);
  				}
  				break;
--- 3822,3828 ----
  					ItemPointerSetInvalid(&(tmptup.t_self));
  					tmptup.t_tableOid = InvalidOid;
  					tmptup.t_data = td;
! 					plpgsql_exec_move_row(estate, NULL, row, &tmptup, tupdesc);
  					ReleaseTupleDesc(tupdesc);
  				}
  				break;
***************
*** 3848,3854 ****
  				if (*isNull)
  				{
  					/* If source is null, just assign nulls to the record */
! 					exec_move_row(estate, rec, NULL, NULL, NULL);
  				}
  				else
  				{
--- 3838,3844 ----
  				if (*isNull)
  				{
  					/* If source is null, just assign nulls to the record */
! 					plpgsql_exec_move_row(estate, rec, NULL, NULL, NULL);
  				}
  				else
  				{
***************
*** 3875,3881 ****
  					ItemPointerSetInvalid(&(tmptup.t_self));
  					tmptup.t_tableOid = InvalidOid;
  					tmptup.t_data = td;
! 					exec_move_row(estate, rec, NULL, &tmptup, tupdesc);
  					ReleaseTupleDesc(tupdesc);
  				}
  				break;
--- 3865,3871 ----
  					ItemPointerSetInvalid(&(tmptup.t_self));
  					tmptup.t_tableOid = InvalidOid;
  					tmptup.t_data = td;
! 					plpgsql_exec_move_row(estate, rec, NULL, &tmptup, tupdesc);
  					ReleaseTupleDesc(tupdesc);
  				}
  				break;
***************
*** 4730,4736 ****
  	 * through with found = false.
  	 */
  	if (n <= 0)
! 		exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
  	else
  		found = true;			/* processed at least one tuple */
  
--- 4720,4726 ----
  	 * through with found = false.
  	 */
  	if (n <= 0)
! 		plpgsql_exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
  	else
  		found = true;			/* processed at least one tuple */
  
***************
*** 4746,4752 ****
  			/*
  			 * Assign the tuple to the target
  			 */
! 			exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);
  
  			/*
  			 * Execute the statements
--- 4736,4742 ----
  			/*
  			 * Assign the tuple to the target
  			 */
! 			plpgsql_exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);
  
  			/*
  			 * Execute the statements
***************
*** 5137,5147 ****
  
  
  /* ----------
!  * exec_move_row			Move one tuple's values into a record or row
   * ----------
   */
! static void
! exec_move_row(PLpgSQL_execstate *estate,
  			  PLpgSQL_rec *rec,
  			  PLpgSQL_row *row,
  			  HeapTuple tup, TupleDesc tupdesc)
--- 5127,5137 ----
  
  
  /* ----------
!  * plpgsql_exec_move_row			Move one tuple's values into a record or row
   * ----------
   */
! void
! plpgsql_exec_move_row(PLpgSQL_execstate *estate,
  			  PLpgSQL_rec *rec,
  			  PLpgSQL_row *row,
  			  HeapTuple tup, TupleDesc tupdesc)
***************
*** 5916,5922 ****
   * 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;
--- 5906,5912 ----
   * 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;
*** ./src/pl/plpgsql/src/pl_handler.c.orig	2011-11-25 12:04:21.661586925 +0100
--- ./src/pl/plpgsql/src/pl_handler.c	2011-11-26 08:54:10.153356791 +0100
***************
*** 312,314 ****
--- 312,452 ----
  
  	PG_RETURN_VOID();
  }
+ 
+ /* ----------
+  * plpgsql_checker
+  *
+  * This function attempts to check a embeded SQL inside a PL/pgSQL function at
+  * CHECK FUNCTION time. It should to have one or two parameters. Second
+  * parameter is a relation (used when function is trigger).
+  * ----------
+  */
+ PG_FUNCTION_INFO_V1(plpgsql_checker);
+ 
+ Datum
+ plpgsql_checker(PG_FUNCTION_ARGS)
+ {
+ 	Oid			funcoid = PG_GETARG_OID(0);
+ 	Oid			relid = PG_GETARG_OID(1);
+ 	HeapTuple	tuple;
+ 	FunctionCallInfoData fake_fcinfo;
+ 	FmgrInfo	flinfo;
+ 	TriggerData trigdata;
+ 	int			rc;
+ 	PLpgSQL_function *function;
+ 	PLpgSQL_execstate *cur_estate;
+ 
+ 	Form_pg_proc proc;
+ 	char		functyptype;
+ 	bool	   istrigger = false;
+ 
+ 	/* we don't need to repair a check done by validator */
+ 
+ 	tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
+ 	if (!HeapTupleIsValid(tuple))
+ 		elog(ERROR, "cache lookup failed for function %u", funcoid);
+ 	proc = (Form_pg_proc) GETSTRUCT(tuple);
+ 
+ 	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))
+ 		{
+ 			istrigger = true;
+ 			if (!OidIsValid(relid))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("PL/pgSQL trigger functions cannot be checked directly"),
+ 						 errhint("use CHECK TRIGGER statement instead")));
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Connect to SPI manager
+ 	 */
+ 	if ((rc = SPI_connect()) != SPI_OK_CONNECT)
+ 			elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
+ 
+ 	/*
+ 	 * Set up a fake fcinfo with just enough info to satisfy
+ 	 * plpgsql_compile().
+ 	 *
+ 	 * there should be a different real argtypes for polymorphic params
+ 	 */
+ 	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;
+ 		trigdata.tg_relation = relation_open(relid, AccessShareLock);
+ 		fake_fcinfo.context = (Node *) &trigdata;
+ 	}
+ 
+ 	/* Get a compiled function */
+ 	function = plpgsql_compile(&fake_fcinfo, false);
+ 
+ 	/* 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 prepare plans */
+ 	PG_TRY();
+ 	{
+ 		if (!istrigger)
+ 			plpgsql_check_function(function, &fake_fcinfo);
+ 		else
+ 			plpgsql_check_trigger(function, &trigdata);
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		if (istrigger)
+ 			relation_close(trigdata.tg_relation, AccessShareLock);
+ 
+ 		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);
+ 
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	if (istrigger)
+ 		relation_close(trigdata.tg_relation, AccessShareLock);
+ 
+ 	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);
+ 
+ 	/*
+ 	 * Disconnect from SPI manager
+ 	 */
+ 	if ((rc = SPI_finish()) != SPI_OK_FINISH)
+ 			elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
+ 
+ 	ReleaseSysCache(tuple);
+ 
+ 	PG_RETURN_VOID();
+ }
*** ./src/pl/plpgsql/src/pl_check.c.orig	2011-11-25 13:07:08.000000000 +0100
--- ./src/pl/plpgsql/src/pl_check.c	2011-11-26 09:15:17.010967364 +0100
***************
*** 0 ****
--- 1,1091 ----
+ /*-------------------------------------------------------------------------
+  *
+  * pl_check.c		- Checker part of the PL/pgSQL
+  *			  procedural language
+  *
+  * Portions Copyright (c) 1996-2011, 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 "catalog/pg_type.h"
+ #include "executor/spi_priv.h"
+ #include "utils/builtins.h"
+ #include "utils/lsyscache.h"
+ #include "utils/rel.h"
+ #include "utils/typcache.h"
+ 
+ static void check_target(PLpgSQL_execstate *estate, int varno);
+ static void check_row_or_rec(PLpgSQL_execstate *estate, PLpgSQL_row *row, PLpgSQL_rec *rec);
+ static void prepare_expr(PLpgSQL_execstate *estate, PLpgSQL_expr *expr, int cursorOptions);
+ static void check_expr(PLpgSQL_execstate *estate, PLpgSQL_expr *expr);
+ static void assign_tupdesc_row_or_rec(PLpgSQL_execstate *estate,
+ 					    PLpgSQL_row *row, PLpgSQL_rec *rec,
+ 								    TupleDesc tupdesc);
+ static void assign_tupdesc_dno(PLpgSQL_execstate *estate, int varno, TupleDesc tupdesc);
+ static TupleDesc 
+ expr_get_desc(PLpgSQL_execstate *estate, PLpgSQL_expr *query,
+ 							bool use_element_type,
+ 							bool expand_record,
+ 							bool is_expression);
+ 
+ static void check_stmts(PLpgSQL_execstate *estate, List *stmts);
+ static void check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt);
+ 
+ static void var_init_to_null(PLpgSQL_execstate *estate, int varno);
+ 
+ 
+ /*
+  * append a CONTEXT to error message
+  */
+ static void
+ check_error_callback(void *arg)
+ {
+ 	PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;
+ 
+ 	if (estate->err_stmt != NULL)
+ 	{
+ 		/* translator: last %s is a plpgsql statement type name */
+ 		errcontext("checking of PL/pgSQL function \"%s\" line %d at %s",
+ 				   estate->func->fn_name,
+ 				   estate->err_stmt->lineno,
+ 				   plpgsql_stmt_typename(estate->err_stmt));
+ 	}
+ 	else
+ 		errcontext("checking of PL/pgSQL function \"%s\"",
+ 				   estate->func->fn_name);
+ }
+ 
+ /*
+  * Check function - it prepare variables and starts a prepare plan walker
+  *		called by function checker
+  */
+ void
+ plpgsql_check_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
+ {
+ 	PLpgSQL_execstate estate;
+ 	ErrorContextCallback plerrcontext;
+ 	int			i;
+ 
+ 	/* Setup error callback for ereport */
+ 	plerrcontext.callback = check_error_callback;
+ 	plerrcontext.arg = &estate;
+ 	plerrcontext.previous = error_context_stack;
+ 	error_context_stack = &plerrcontext;
+ 
+ 	/*
+ 	 * Setup the execution state - we would to reuse some exec routines
+ 	 * so we need a estate
+ 	 */
+ 	plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo);
+ 
+ 	/*
+ 	 * Make local execution copies of all the datums
+ 	 */
+ 	for (i = 0; i < estate.ndatums; i++)
+ 		estate.datums[i] = plpgsql_copy_datum(func->datums[i]);
+ 
+ 	/*
+ 	 * Store the actual call argument values into the appropriate variables
+ 	 */
+ 	for (i = 0; i < func->fn_nargs; i++)
+ 	{
+ 		int			n = func->fn_argvarnos[i];
+ 
+ 		switch (estate.datums[n]->dtype)
+ 		{
+ 			case PLPGSQL_DTYPE_VAR:
+ 				{
+ 					var_init_to_null(&estate, n);
+ 				}
+ 				break;
+ 
+ 			case PLPGSQL_DTYPE_ROW:
+ 				{
+ 					PLpgSQL_row *row = (PLpgSQL_row *) estate.datums[n];
+ 
+ 					plpgsql_exec_move_row(&estate, NULL, row, NULL, NULL);
+ 				}
+ 				break;
+ 
+ 			default:
+ 				elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(&estate, (PLpgSQL_stmt *) func->action);
+ 
+ 	/* Cleanup temporary memory */
+ 	plpgsql_destroy_econtext(&estate);
+ 
+ 	/* Pop the error context stack */
+ 	error_context_stack = plerrcontext.previous;
+ }
+ 
+ /*
+  * Check trigger - prepare fake environments for testing trigger
+  *
+  */
+ void
+ plpgsql_check_trigger(PLpgSQL_function *func,
+ 					 TriggerData *trigdata)
+ {
+ 	PLpgSQL_execstate estate;
+ 	ErrorContextCallback plerrcontext;
+ 	PLpgSQL_rec *rec_new,
+ 			   *rec_old;
+ 	int			i;
+ 
+ 	/* Setup error callback for ereport */
+ 	plerrcontext.callback = check_error_callback;
+ 	plerrcontext.arg = &estate;
+ 	plerrcontext.previous = error_context_stack;
+ 	error_context_stack = &plerrcontext;
+ 
+ 	/*
+ 	 * Setup the execution state - we would to reuse some exec routines
+ 	 * so we need a estate
+ 	 */
+ 	plpgsql_estate_setup(&estate, func, NULL);
+ 
+ 	/*
+ 	 * Make local execution copies of all the datums
+ 	 */
+ 	for (i = 0; i < estate.ndatums; i++)
+ 		estate.datums[i] = plpgsql_copy_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 *) (estate.datums[func->new_varno]);
+ 	rec_new->freetup = false;
+ 	rec_new->freetupdesc = false;
+ 	assign_tupdesc_row_or_rec(&estate, NULL, rec_new, trigdata->tg_relation->rd_att);
+ 
+ 	rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
+ 	rec_old->freetup = false;
+ 	rec_old->freetupdesc = false;
+ 	assign_tupdesc_row_or_rec(&estate, NULL, rec_old, trigdata->tg_relation->rd_att);
+ 
+ 	/*
+ 	 * Assign the special tg_ variables
+ 	 */
+ 	var_init_to_null(&estate, func->tg_op_varno);
+ 	var_init_to_null(&estate, func->tg_name_varno);
+ 	var_init_to_null(&estate, func->tg_when_varno);
+ 	var_init_to_null(&estate, func->tg_level_varno);
+ 	var_init_to_null(&estate, func->tg_relid_varno);
+ 	var_init_to_null(&estate, func->tg_relname_varno);
+ 	var_init_to_null(&estate, func->tg_table_name_varno);
+ 	var_init_to_null(&estate, func->tg_table_schema_varno);
+ 	var_init_to_null(&estate, func->tg_nargs_varno);
+ 	var_init_to_null(&estate, func->tg_argv_varno);
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(&estate, (PLpgSQL_stmt *) func->action);
+ 
+ 	/* Cleanup temporary memory */
+ 	plpgsql_destroy_econtext(&estate);
+ 
+ 	/* Pop the error context stack */
+ 	error_context_stack = plerrcontext.previous;
+ }
+ 
+ /*
+  * Verify lvalue
+  *    It doesn't repeat a checks that are done.
+  *  Checks a subscript expressions, verify a validity of record's fields
+  */
+ static void
+ check_target(PLpgSQL_execstate *estate, int varno)
+ {
+ 	PLpgSQL_datum *target = estate->datums[varno];
+ 
+ 	switch (target->dtype)
+ 	{
+ 		case PLPGSQL_DTYPE_VAR:
+ 		case PLPGSQL_DTYPE_REC:
+ 			break;
+ 
+ 		case PLPGSQL_DTYPE_ROW:
+ 			check_row_or_rec(estate, (PLpgSQL_row *) target, NULL);
+ 			break;
+ 
+ 		case PLPGSQL_DTYPE_RECFIELD:
+ 			{
+ 				PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target;
+ 				PLpgSQL_rec *rec;
+ 				int			fno;
+ 
+ 				rec = (PLpgSQL_rec *) (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)));
+ 
+ 					check_expr(estate, arrayelem->subscript);
+ 
+ 					target = 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(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_execstate *estate, 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(estate, row->varnos[fnum]);
+ 		}
+ 	}
+ }
+ 
+ /*
+  * Generate a prepared plan - this is simplyfied copy from pl_exec.c
+  *   Is not necessary to check simple plan
+  */
+ static void
+ prepare_expr(PLpgSQL_execstate *estate,
+ 				  PLpgSQL_expr *expr, int cursorOptions)
+ {
+ 	SPIPlanPtr	plan;
+ 
+ 	/* leave when there are not expression */
+ 	if (expr == NULL)
+ 		return;
+ 
+ 	/* leave when plan is created */
+ 	if (expr->plan != NULL)
+ 		return;
+ 
+ 	/*
+ 	 * 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 = 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")));
+ 			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.")));
+ 			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_execstate *estate, PLpgSQL_expr *expr)
+ {
+ 	TupleDesc tupdesc;
+ 
+ 	if (expr != NULL)
+ 	{
+ 		prepare_expr(estate, expr, 0);
+ 		tupdesc = expr_get_desc(estate, expr, false, false, true);
+ 		ReleaseTupleDesc(tupdesc);
+ 	}
+ }
+ 
+ /*
+  * 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_execstate *estate,
+ 					    PLpgSQL_row *row, PLpgSQL_rec *rec,
+ 								    TupleDesc tupdesc)
+ {
+ 	bool	   *nulls;
+ 	HeapTuple  tup;
+ 
+ 	if (tupdesc == NULL)
+ 		elog(ERROR, "tuple descriptor is empty");
+ 
+ 	/*
+ 	 * row variable has assigned TupleDesc already, so don't be processed
+ 	 * here
+ 	 */
+ 	if (rec != NULL)
+ 	{
+ 		PLpgSQL_rec *target = (PLpgSQL_rec *)(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_execstate *estate, int varno, TupleDesc tupdesc)
+ {
+ 	PLpgSQL_datum *target = estate->datums[varno];
+ 
+ 	if (target->dtype == PLPGSQL_DTYPE_REC)
+ 		assign_tupdesc_row_or_rec(estate, NULL, (PLpgSQL_rec *) target, tupdesc);
+ }
+ 
+ /*
+  * Returns a tuple descriptor based on existing plan
+  */
+ static TupleDesc 
+ expr_get_desc(PLpgSQL_execstate *estate,
+ 						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);
+ 
+ 		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 (tupdesc->natts != 1)
+ 			ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 				 errmsg_plural("query \"%s\" returned %d column",
+ 							   "query \"%s\" returned %d columns",
+ 							   tupdesc->natts,
+ 							   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))));
+ 
+ 		/* 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
+ 			elog(ERROR, "cannot to identify real type for record type variable");
+ 	}
+ 
+ 	if (is_expression && tupdesc->natts != 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 			  errmsg_plural("query \"%s\" returned %d column",
+ 				   "query \"%s\" returned %d columns",
+ 				   tupdesc->natts,
+ 				   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(..)
+ 	 */
+ 	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)
+ 						elog(ERROR, "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
+  */
+ static void
+ check_stmts(PLpgSQL_execstate *estate, List *stmts)
+ {
+ 	ListCell *lc;
+ 
+ 	foreach(lc, stmts)
+ 	{
+ 		check_stmt(estate, (PLpgSQL_stmt *) lfirst(lc));
+ 	}
+ }
+ 
+ /*
+  * walk over all statements
+  */
+ static void
+ check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
+ {
+ 	TupleDesc	tupdesc = NULL;
+ 	PLpgSQL_function *func;
+ 	ListCell *l;
+ 
+ 	if (stmt == NULL)
+ 		return;
+ 
+ 	estate->err_stmt = stmt;
+ 	func = estate->func;
+ 
+ 	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++)
+ 				{
+ 					d = func->datums[stmt_block->initvarnos[i]];
+ 
+ 					if (d->dtype == PLPGSQL_DTYPE_VAR)
+ 					{
+ 						PLpgSQL_var *var = (PLpgSQL_var *) d;
+ 
+ 						check_expr(estate, var->default_val);
+ 					}
+ 				}
+ 
+ 				check_stmts(estate, stmt_block->body);
+ 				
+ 				if (stmt_block->exceptions)
+ 				{
+ 					foreach(l, stmt_block->exceptions->exc_list)
+ 					{
+ 						check_stmts(estate, ((PLpgSQL_exception *) lfirst(l))->action);
+ 					}
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_ASSIGN:
+ 			{
+ 				PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt;
+ 
+ 				/* prepare plan if desn't exist yet */
+ 				prepare_expr(estate, stmt_assign->expr, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_assign->expr,
+ 										false,		/* no element type */
+ 										true,		/* expand record */
+ 										true);		/* is expression */
+ 
+ 				/* check target, ensure target can get a result */
+ 				check_target(estate, stmt_assign->varno);
+ 
+ 				/* assign a tupdesc to record variable */
+ 				assign_tupdesc_dno(estate, stmt_assign->varno, tupdesc);
+ 				ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_IF:
+ 			{
+ 				PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt;
+ 				ListCell *l;
+ 
+ 				check_expr(estate, stmt_if->cond);
+ 
+ 				check_stmts(estate, stmt_if->then_body);
+ 
+ 				foreach(l, stmt_if->elsif_list)
+ 				{
+ 					PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l);
+ 
+ 					check_expr(estate, elif->cond);
+ 					check_stmts(estate, elif->stmts);
+ 				}
+ 
+ 				check_stmts(estate, 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 *) estate->datums[stmt_case->t_varno];
+ 
+ 					/* we need to set hidden variable type */
+ 					prepare_expr(estate, stmt_case->t_expr, 0);
+ 
+ 					tupdesc = expr_get_desc(estate,
+ 									stmt_case->t_expr,
+ 										false,		/* no element type */
+ 										false,		/* 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,
+ 												   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(estate, cwt->expr);
+ 					check_stmts(estate, cwt->stmts);
+ 				}
+ 
+ 				check_stmts(estate, stmt_case->else_stmts);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_LOOP:
+ 			check_stmts(estate, ((PLpgSQL_stmt_loop *) stmt)->body);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_WHILE:
+ 			{
+ 				PLpgSQL_stmt_while *stmt_while = (PLpgSQL_stmt_while *) stmt;
+ 
+ 				check_expr(estate, stmt_while->cond);
+ 				check_stmts(estate, stmt_while->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORI:
+ 			{
+ 				PLpgSQL_stmt_fori *stmt_fori = (PLpgSQL_stmt_fori *) stmt;
+ 
+ 				check_expr(estate, stmt_fori->lower);
+ 				check_expr(estate, stmt_fori->upper);
+ 				check_expr(estate, stmt_fori->step);
+ 
+ 				check_stmts(estate, stmt_fori->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORS:
+ 			{
+ 				PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt;
+ 
+ 				/* we need to set hidden variable type */
+ 				prepare_expr(estate, stmt_fors->query, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_fors->query,
+ 									false,		/* no element type */
+ 									false,		/* expand record */
+ 									false);		/* is expression */
+ 
+ 				check_row_or_rec(estate, stmt_fors->row, stmt_fors->rec);
+ 				assign_tupdesc_row_or_rec(estate, stmt_fors->row, stmt_fors->rec, tupdesc);
+ 
+ 				check_stmts(estate, stmt_fors->body);
+ 				ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORC:
+ 			{
+ 				PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt;
+ 				PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_forc->curvar];
+ 
+ 				prepare_expr(estate, stmt_forc->argquery, 0);
+ 
+ 				if (var->cursor_explicit_expr != NULL)
+ 				{
+ 					prepare_expr(estate, var->cursor_explicit_expr,
+ 								    var->cursor_options);
+ 
+ 					tupdesc = expr_get_desc(estate,
+ 									var->cursor_explicit_expr,
+ 										false,		/* no element type */
+ 										false,		/* expand record */
+ 										false);		/* is expression */
+ 
+ 					check_row_or_rec(estate, stmt_forc->row, stmt_forc->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_forc->row, stmt_forc->rec, tupdesc);
+ 				}
+ 
+ 				check_stmts(estate, stmt_forc->body);
+ 				if (tupdesc != NULL)
+ 					ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNFORS:
+ 			{
+ 				PLpgSQL_stmt_dynfors * stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt;
+ 
+ 				if (stmt_dynfors->rec != NULL)
+ 					elog(ERROR, "cannot determinate a result of dynamic SQL");
+ 
+ 				check_expr(estate, stmt_dynfors->query);
+ 
+ 				foreach(l, stmt_dynfors->params)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				check_stmts(estate, stmt_dynfors->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FOREACH_A:
+ 			{
+ 				PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt;
+ 
+ 				prepare_expr(estate, stmt_foreach_a->expr, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_foreach_a->expr,
+ 									true,		/* no element type */
+ 									false,		/* expand record */
+ 									true);		/* is expression */
+ 
+ 				check_target(estate, stmt_foreach_a->varno);
+ 				assign_tupdesc_dno(estate, stmt_foreach_a->varno, tupdesc);
+ 				ReleaseTupleDesc(tupdesc);
+ 
+ 				check_stmts(estate, stmt_foreach_a->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_EXIT:
+ 			check_expr(estate, ((PLpgSQL_stmt_exit *) stmt)->cond);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_PERFORM:
+ 			prepare_expr(estate, ((PLpgSQL_stmt_perform *) stmt)->expr, 0);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN:
+ 			check_expr(estate, ((PLpgSQL_stmt_return *) stmt)->expr);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN_NEXT:
+ 			check_expr(estate, ((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(estate, stmt_rq->dynquery);
+ 				prepare_expr(estate, stmt_rq->query, 0);
+ 
+ 				foreach(l, stmt_rq->params)
+ 				{
+ 					check_expr(estate, (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(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				foreach(l, stmt_raise->options)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				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;
+ 
+ 				prepare_expr(estate, stmt_execsql->sqlstmt, 0);
+ 				if (stmt_execsql->into)
+ 				{
+ 					tupdesc = expr_get_desc(estate,
+ 								stmt_execsql->sqlstmt,
+ 											false,		/* no element type */
+ 											false,		/* expand record */
+ 											false);		/* is expression */
+ 
+ 					/* check target, ensure target can get a result */
+ 					check_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec, tupdesc);
+ 					ReleaseTupleDesc(tupdesc);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNEXECUTE:
+ 			{
+ 				PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt;
+ 
+ 				check_expr(estate, stmt_dynexecute->query);
+ 
+ 				foreach(l, stmt_dynexecute->params)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				if (stmt_dynexecute->into)
+ 				{
+ 					if (stmt_dynexecute->rec != NULL)
+ 						elog(ERROR, "cannot determinate a result of dynamic SQL");
+ 
+ 					check_row_or_rec(estate, stmt_dynexecute->row, stmt_dynexecute->rec);
+ 				}
+ 			}
+ 			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)
+ 					prepare_expr(estate, var->cursor_explicit_expr,
+ 								   var->cursor_options);
+ 
+ 				prepare_expr(estate, stmt_open->query, 0);
+ 				prepare_expr(estate, stmt_open->argquery, 0);
+ 				check_expr(estate, stmt_open->dynquery);
+ 
+ 				foreach(l, stmt_open->params)
+ 				{
+ 					check_expr(estate, (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(estate, diag_item->target);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FETCH:
+ 			{
+ 				PLpgSQL_stmt_fetch *stmt_fetch = (PLpgSQL_stmt_fetch *) stmt;
+ 				PLpgSQL_var *var = (PLpgSQL_var *)(estate->datums[stmt_fetch->curvar]);
+ 
+ 				if (var != NULL && var->cursor_explicit_expr != NULL)
+ 				{
+ 					prepare_expr(estate, var->cursor_explicit_expr, 
+ 									    var->cursor_options);
+ 					tupdesc = expr_get_desc(estate,
+ 								    var->cursor_explicit_expr,
+ 											false,		/* no element type */
+ 											false,		/* expand record */
+ 											false);		/* is expression */
+ 					check_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec, tupdesc);
+ 					ReleaseTupleDesc(tupdesc);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_CLOSE:
+ 			break;
+ 
+ 		default:
+ 			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
+ 			return; /* be compiler quite */
+ 	}
+ }
+ 
+ /*
+  * Initialize variable to NULL
+  */
+ static void
+ var_init_to_null(PLpgSQL_execstate *estate, int varno)
+ {
+ 	PLpgSQL_var *var = (PLpgSQL_var *) estate->datums[varno];
+ 	var->value = (Datum) 0;
+ 	var->isnull = true;
+ 	var->freeval = false;
+ }
*** ./src/pl/plpgsql/src/plpgsql.h.orig	2011-11-24 12:51:45.000000000 +0100
--- ./src/pl/plpgsql/src/plpgsql.h	2011-11-25 22:45:14.167881352 +0100
***************
*** 901,906 ****
--- 901,907 ----
  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_delete_function(PLpgSQL_function *func);
  extern void plpgsql_HashTableInit(void);
  
  /* ----------
***************
*** 911,921 ****
--- 912,931 ----
  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_checker(PG_FUNCTION_ARGS);
+ 
+ /* ----------
+  * Functions in pl_check.c
+  * ----------
+  */
+ extern void plpgsql_check_function(PLpgSQL_function *func, FunctionCallInfo fcinfo);
+ extern void plpgsql_check_trigger(PLpgSQL_function *func, TriggerData *trigdata);
  
  /* ----------
   * Functions in pl_exec.c
   * ----------
   */
+ extern PLpgSQL_datum *plpgsql_copy_datum(PLpgSQL_datum *datum);
  extern Datum plpgsql_exec_function(PLpgSQL_function *func,
  					  FunctionCallInfo fcinfo);
  extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func,
***************
*** 928,933 ****
--- 938,952 ----
  extern void exec_get_datum_type_info(PLpgSQL_execstate *estate,
  						 PLpgSQL_datum *datum,
  						 Oid *typeid, int32 *typmod, Oid *collation);
+ extern bool plpgsql_expr_prepare_plan(PLpgSQL_stmt *stmt, PLpgSQL_expr *expr, void *context);
+ extern void plpgsql_estate_setup(PLpgSQL_execstate *estate,
+ 						 PLpgSQL_function *func,
+ 						 ReturnSetInfo *rsi);
+ extern void plpgsql_exec_move_row(PLpgSQL_execstate *estate,
+ 							  PLpgSQL_rec *rec,
+ 							  PLpgSQL_row *row,
+ 							  HeapTuple tup, TupleDesc tupdesc);
+ extern void plpgsql_destroy_econtext(PLpgSQL_execstate *estate);
  
  /* ----------
   * Functions for namespace handling in pl_funcs.c
*** ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql.orig	2011-11-25 12:04:21.675586770 +0100
--- ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql	2011-11-25 12:05:31.895783910 +0100
***************
*** 5,7 ****
--- 5,8 ----
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_call_handler();
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_inline_handler(internal);
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_validator(oid);
+ ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_checker(oid, regclass);
*** ./src/test/regress/expected/plpgsql.out.orig	2011-11-24 12:51:45.000000000 +0100
--- ./src/test/regress/expected/plpgsql.out	2011-11-26 09:15:55.000000000 +0100
***************
*** 302,307 ****
--- 302,310 ----
  ' 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
+ check trigger tg_hslot_biu on HSlot;
+ NOTICE:  checking function "tg_hslot_biu()"
  -- ************************************************************
  -- * BEFORE DELETE on HSlot
  -- *	- prevent from manual manipulation
***************
*** 635,640 ****
--- 638,645 ----
      raise exception ''illegal backlink beginning with %'', mytype;
  end;
  ' language plpgsql;
+ -- check function should not fail
+ check function tg_backlink_set(bpchar, bpchar);
  -- ************************************************************
  -- * Support function to clear out the backlink field if
  -- * it still points to specific slot
***************
*** 2802,2807 ****
--- 2807,2840 ----
   
  (1 row)
  
+ -- check function should not fail
+ check function for_vect();
+ -- 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
***************
*** 3283,3288 ****
--- 3316,3323 ----
    return;
  end;
  $$ language plpgsql;
+ -- check function should not fail
+ check function forc01();
  select forc01();
  NOTICE:  5 from c
  NOTICE:  6 from c
***************
*** 3716,3721 ****
--- 3751,3758 ----
    end case;
  end;
  $$ language plpgsql immutable;
+ -- check function should not fail
+ check function case_test(bigint);
  select case_test(1);
   case_test 
  -----------
***************
*** 4571,4573 ****
--- 4608,4942 ----
  CONTEXT:  PL/pgSQL function "testoa" line 5 at assignment
  drop function arrayassign1();
  drop function testoa(x1 int, x2 int, x3 int);
+ --
+ -- check function statement tests
+ --
+ create table t1(a int, b int);
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+ end;
+ $$ language plpgsql;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ check function f1();
+ ERROR:  column "c" of relation "t1" does not exist
+ LINE 1: update t1 set c = 30
+                       ^
+ QUERY:  update t1 set c = 30
+ CONTEXT:  checking of PL/pgSQL function "f1" line 4 at SQL statement
+ 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)
+ 
+ check function f1();
+ ERROR:  record "r" has no field "c"
+ CONTEXT:  SQL statement "SELECT r.c"
+ checking of PL/pgSQL function "f1" line 6 at RAISE
+ 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)
+ 
+ check function f1();
+ ERROR:  record "r" has no field "c"
+ CONTEXT:  SQL statement "SELECT r.c"
+ checking of PL/pgSQL function "f1" line 6 at RAISE
+ 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)
+ 
+ check function f1();
+ ERROR:  record "r" has no field "c"
+ CONTEXT:  checking of PL/pgSQL function "f1" line 6 at assignment
+ 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)
+ 
+ check function f1();
+ ERROR:  column "a" does not exist
+ LINE 1: SELECT a + b
+                ^
+ QUERY:  SELECT a + b
+ CONTEXT:  checking of PL/pgSQL function "f1" line 5 at assignment
+ 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)
+ 
+ check function f1();
+ ERROR:  too many parameters specified for RAISE
+ CONTEXT:  checking of PL/pgSQL function "f1" line 4 at RAISE
+ 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)
+ 
+ check function f1();
+ ERROR:  too few parameters specified for RAISE
+ CONTEXT:  checking of PL/pgSQL function "f1" line 4 at RAISE
+ 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)
+ 
+ check function f1();
+ ERROR:  column "c" does not exist
+ LINE 1: SELECT c+10
+                ^
+ QUERY:  SELECT c+10
+ CONTEXT:  checking of PL/pgSQL function "f1" line 5 at assignment
+ 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;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ check function f1();
+ ERROR:  subscripted object is not an array
+ CONTEXT:  checking of PL/pgSQL function "f1" line 5 at assignment
+ 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)
+ 
+ check function f1();
+ ERROR:  record "_exception" has no field "hint"
+ CONTEXT:  checking of PL/pgSQL function "f1" line 7 at GET DIAGNOSTICS
+ 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);
+ check trigger t1_f1 on t1;
+ NOTICE:  checking function "f1_trg()"
+ ERROR:  record "new" has no field "c"
+ CONTEXT:  SQL statement "SELECT new.c"
+ checking of PL/pgSQL function "f1_trg" line 5 at RAISE
+ 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
+ check trigger t1_f1 on t1;
+ NOTICE:  checking function "f1_trg()"
+ ERROR:  record "new" has no field "c"
+ CONTEXT:  checking of PL/pgSQL function "f1_trg" line 5 at assignment
+ -- 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
+ check trigger t1_f1 on t1;
+ NOTICE:  checking function "f1_trg()"
+ -- ok
+ insert into t1 values(6,30);
+ drop table t1;
+ drop type _exception_type;
+ drop function f1_trg();
*** ./src/test/regress/sql/plpgsql.sql.orig	2011-11-24 12:51:45.000000000 +0100
--- ./src/test/regress/sql/plpgsql.sql	2011-11-26 09:01:01.346078538 +0100
***************
*** 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
+ check trigger tg_hslot_biu on HSlot;
  
  -- ************************************************************
  -- * BEFORE DELETE on HSlot
***************
*** 747,752 ****
--- 749,757 ----
  end;
  ' language plpgsql;
  
+ -- check function should not fail
+ check function tg_backlink_set(bpchar, bpchar);
+ 
  
  -- ************************************************************
  -- * Support function to clear out the backlink field if
***************
*** 2335,2340 ****
--- 2340,2352 ----
  
  select for_vect();
  
+ -- check function should not fail
+ 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
***************
*** 2714,2719 ****
--- 2726,2734 ----
  end;
  $$ language plpgsql;
  
+ -- check function should not fail
+ check function forc01();
+ 
  select forc01();
  
  -- try updating the cursor's current row
***************
*** 3048,3053 ****
--- 3063,3071 ----
  end;
  $$ language plpgsql immutable;
  
+ -- check function should not fail
+ check function case_test(bigint);
+ 
  select case_test(1);
  select case_test(2);
  select case_test(3);
***************
*** 3600,3602 ****
--- 3618,3862 ----
  
  drop function arrayassign1();
  drop function testoa(x1 int, x2 int, x3 int);
+ 
+ --
+ -- check function statement tests
+ --
+ 
+ create table t1(a int, b int);
+ 
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+ end;
+ $$ language plpgsql;
+ 
+ select f1();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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;
+ 
+ select f1();
+ 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();
+ 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);
+ check trigger t1_f1 on 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
+ check trigger t1_f1 on 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
+ check trigger t1_f1 on t1;
+ 
+ -- ok
+ insert into t1 values(6,30);
+ 
+ drop table t1;
+ drop type _exception_type;
+ 
+ drop function f1_trg();
+ 
#2Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Pavel Stehule (#1)
Re: review: CHECK FUNCTION statement

Pavel Stehule wrote:

I am sending updated patch, that implements a CHECK FUNCTION and CHECK
TRIGGER statements.

This patch is significantly redesigned to previous version (PL/pgSQL
part) - it is more readable, more accurate. There are new regress
tests.

Please, can some English native speaker fix doc and comments?

ToDo:

CHECK FUNCTION search function according to function signature - it
should be changes for using a actual types - it can be solution for
polymorphic types and useful tool for work with overloaded functions -
when is not clean, that function was executed.

check function foo(int, int);
NOTICE: checking function foo(variadic anyarray)
...

and maybe some support for named parameters
check function foo(name text, surname text);
NOTICE: checking function foo(text, text, text, text)
...

I think that CHECK FUNCTION should work exactly like DROP FUNCTION
in these respects.

Submission review:
------------------

The patch is context diff, applies with some offsets, contains
regression tests and documentation.

The documentation should be expanded, the doc for CHECK FUNCTION
is only a stub. It should describe the procedure and what is checked.
That would also make reviewing easier.
I think that some documentation should be added to plhandler.sgml.
There is a spelling error (statemnt) in the docs.

Usability review:
-----------------

If I understand right, the goal of CHECK FUNCTION is to find errors in
the function definition without actually having to execute it.
The patch tries to provide this for PL/pgSQL.

There hasn't been any discussion on the list, the patch was just posted,
so I can't say that we want that. Tom added it to the commitfest page,
so there's one important voice against dismissing it right away :^)

I don't understand the functional difference between a "validator function"
and a "check function" as proposed by this patch. I am probably missing
something, but why couldn't these checks be added to function validation
when check_function_bodies is set?
A new "CHECK FUNCTION" statement could simply call the validator function.

I don't see any pg_dump support in this patch, and PL/pgSQL probably doesn't
need that, but I think pg_dump support for CREATE LANGUAGE would have to
be added for other PLs.

I can't test if the functionality is complete because I can't get it to
run (see below).

Feature test:
-------------

I can't really test the patch because initdb fails:

$ initdb -E UTF8 --locale=de_DE.UTF-8 --lc-messages=en_US.UTF-8 -U postgres /postgres/cvs/dbhome
The files belonging to this database system will be owned by user "laurenz".
This user must also own the server process.

The database cluster will be initialized with locales
COLLATE: de_DE.UTF-8
CTYPE: de_DE.UTF-8
MESSAGES: en_US.UTF-8
MONETARY: de_DE.UTF-8
NUMERIC: de_DE.UTF-8
TIME: de_DE.UTF-8
The default text search configuration will be set to "german".

creating directory /postgres/cvs/dbhome ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 32MB
creating configuration files ... ok
creating template1 database in /postgres/cvs/dbhome/base/1 ... ok
initializing pg_authid ... ok
initializing dependencies ... ok
creating system views ... ok
loading system objects' descriptions ... ok
creating collations ... ok
creating conversions ... ok
creating dictionaries ... ok
setting privileges on built-in objects ... ok
creating information schema ... ok
loading PL/pgSQL server-side language ... FATAL: could not load library "/postgres/cvs/pg92/lib/plpgsql.so": /postgres/cvs/pg92/lib/plpgsql.so: undefined symbol: plpgsql_delete_function
STATEMENT: CREATE EXTENSION plpgsql;

child process exited with exit code 1
initdb: removing data directory "/postgres/cvs/dbhome"

Coding review:
--------------

The patch compiles without warnings.
The comments in the code should be revised, they are bad English.
I can't say if there should be more of them -- I don't know this part of
the code well enough to have a well-founded opinion.

I don't think there are any portability issues, but I could not test it.

There are a lot of small changes to pl/plpgsql/src/pl_exec.c, are they all
necessary? For example, why was copy_plpgsql_datum renamed to
plpgsql_copy_datum?

I'll mark the patch as "Waiting on Author".

Yours,
Laurenz Albe

#3Pavel Stehule
pavel.stehule@gmail.com
In reply to: Albe Laurenz (#2)
Re: review: CHECK FUNCTION statement

Hello

2011/11/29 Albe Laurenz <laurenz.albe@wien.gv.at>:

Pavel Stehule wrote:

I am sending updated patch, that implements a CHECK FUNCTION and CHECK
TRIGGER statements.

This patch is significantly redesigned to previous version (PL/pgSQL
part) - it is more readable, more accurate. There are new regress
tests.

Please, can some English native speaker fix doc and comments?

ToDo:

CHECK FUNCTION search function according to function signature - it
should be changes for using a actual types - it can be solution for
polymorphic types and useful tool for work with overloaded functions -
when is not clean, that function was executed.

check function foo(int, int);
NOTICE: checking function foo(variadic anyarray)
...

and maybe some support for named parameters
check function foo(name text, surname text);
NOTICE: checking function foo(text, text, text, text)
...

I think that CHECK FUNCTION should work exactly like DROP FUNCTION
in these respects.

Submission review:
------------------

The patch is context diff, applies with some offsets, contains
regression tests and documentation.

The documentation should be expanded, the doc for CHECK FUNCTION
is only a stub. It should describe the procedure and what is checked.
That would also make reviewing easier.
I think that some documentation should be added to plhandler.sgml.
There is a spelling error (statemnt) in the docs.

Usability review:
-----------------

If I understand right, the goal of CHECK FUNCTION is to find errors in
the function definition without actually having to execute it.
The patch tries to provide this for PL/pgSQL.

There hasn't been any discussion on the list, the patch was just posted,
so I can't say that we want that. Tom added it to the commitfest page,
so there's one important voice against dismissing it right away :^)

This feature was transformed from plpgsql_lint contrib module. So
there was a voises so this functionality should be in contrib module
as minimum

http://archives.postgresql.org/pgsql-hackers/2011-07/msg00900.php
http://archives.postgresql.org/pgsql-hackers/2011-07/msg01035.php

Contrib module has one disadvantage - it cannot be used in combination
with other plpgsql extensions like edb debugger or edb profiler. So I
rewrote it as core plpgsql patch. It was a plpgsql.prepare_plans
feature. This idea was rejected and replaced by CHECK FUNCTION
statement

Tom propossed a syntax

http://archives.postgresql.org/pgsql-hackers/2011-09/msg00549.php
http://archives.postgresql.org/pgsql-hackers/2011-09/msg00563.php

I don't understand the functional difference between a "validator function"
and a "check function" as proposed by this patch. I am probably missing
something, but why couldn't these checks be added to function validation
when check_function_bodies is set?
A new "CHECK FUNCTION" statement could simply call the validator function.

A validation function is not simple now - and check feature increase a
complexity. Other problem with validator function is their polymorphic
interface.

I don't see any pg_dump support in this patch, and PL/pgSQL probably doesn't
need that, but I think pg_dump support for CREATE LANGUAGE would have to
be added for other PLs.

I have to recheck it

I can't test if the functionality is complete because I can't get it to
run (see below).

sorry - I'll look on it

Feature test:
-------------

I can't really test the patch because initdb fails:

$ initdb -E UTF8 --locale=de_DE.UTF-8 --lc-messages=en_US.UTF-8 -U postgres /postgres/cvs/dbhome
The files belonging to this database system will be owned by user "laurenz".
This user must also own the server process.

The database cluster will be initialized with locales
 COLLATE:  de_DE.UTF-8
 CTYPE:    de_DE.UTF-8
 MESSAGES: en_US.UTF-8
 MONETARY: de_DE.UTF-8
 NUMERIC:  de_DE.UTF-8
 TIME:     de_DE.UTF-8
The default text search configuration will be set to "german".

creating directory /postgres/cvs/dbhome ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 32MB
creating configuration files ... ok
creating template1 database in /postgres/cvs/dbhome/base/1 ... ok
initializing pg_authid ... ok
initializing dependencies ... ok
creating system views ... ok
loading system objects' descriptions ... ok
creating collations ... ok
creating conversions ... ok
creating dictionaries ... ok
setting privileges on built-in objects ... ok
creating information schema ... ok
loading PL/pgSQL server-side language ... FATAL:  could not load library "/postgres/cvs/pg92/lib/plpgsql.so": /postgres/cvs/pg92/lib/plpgsql.so: undefined symbol: plpgsql_delete_function
STATEMENT:  CREATE EXTENSION plpgsql;

child process exited with exit code 1
initdb: removing data directory "/postgres/cvs/dbhome"

Coding review:
--------------

The patch compiles without warnings.
The comments in the code should be revised, they are bad English.
I can't say if there should be more of them -- I don't know this part of
the code well enough to have a well-founded opinion.

I don't think there are any portability issues, but I could not test it.

There are a lot of small changes to pl/plpgsql/src/pl_exec.c, are they all
necessary? For example, why was copy_plpgsql_datum renamed to
plpgsql_copy_datum?

yes, it's necessary - a implementation is in new file and there is
necessary call a functions from pg_compile and pg_exec files -
checking is between compilation and execution - so some functions
should not be static now. All plpgsql public functions should start
with plpgsql_ prefix. It is reason for renaming.

I'll mark the patch as "Waiting on Author".

I'll look on it this night

Regards

Pavel

Show quoted text

Yours,
Laurenz Albe

#4Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#3)
Re: review: CHECK FUNCTION statement

Pavel Stehule <pavel.stehule@gmail.com> writes:

2011/11/29 Albe Laurenz <laurenz.albe@wien.gv.at>:

There are a lot of small changes to pl/plpgsql/src/pl_exec.c, are they all
necessary? For example, why was copy_plpgsql_datum renamed to
plpgsql_copy_datum?

yes, it's necessary - a implementation is in new file and there is
necessary call a functions from pg_compile and pg_exec files -
checking is between compilation and execution - so some functions
should not be static now. All plpgsql public functions should start
with plpgsql_ prefix. It is reason for renaming.

I don't think renaming is necessary. plpgsql is a standalone shared
library and so its symbols don't matter to anybody but itself.

Possibly a larger question, though, is whether you really need a new
source file. If that results in having to export functions that
otherwise could stay static, maybe it's not the best choice.

regards, tom lane

#5Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#4)
Re: review: CHECK FUNCTION statement

2011/11/29 Tom Lane <tgl@sss.pgh.pa.us>:

Pavel Stehule <pavel.stehule@gmail.com> writes:

2011/11/29 Albe Laurenz <laurenz.albe@wien.gv.at>:

There are a lot of small changes to pl/plpgsql/src/pl_exec.c, are they all
necessary? For example, why was copy_plpgsql_datum renamed to
plpgsql_copy_datum?

yes, it's necessary - a implementation is in new file and there is
necessary call a functions from pg_compile and pg_exec files -
checking is between compilation and execution - so some functions
should not be static now. All plpgsql public functions should start
with plpgsql_ prefix. It is reason for renaming.

I don't think renaming is necessary.  plpgsql is a standalone shared
library and so its symbols don't matter to anybody but itself.

Possibly a larger question, though, is whether you really need a new
source file.  If that results in having to export functions that
otherwise could stay static, maybe it's not the best choice.

This patch was originally in pl_exec.c but this file has a 6170 lines
and checking adds 1092 lines - so I moved it to new file

It has little bit different semantics, but it is true, so checking
hardly depends on routines from pl_exec - routines for variable's
management.

I have no problem to move it back. I reduces original patch little bit.

Some refactoring of pl_exec should be nice - a management of row,
record variables and array fields is part that can be shared with
SQL/PSM interpret. But I have not idea how it realize.

Regards

Pavel

Show quoted text

                       regards, tom lane

#6Alvaro Herrera
alvherre@commandprompt.com
In reply to: Pavel Stehule (#5)
Re: review: CHECK FUNCTION statement

Excerpts from Pavel Stehule's message of mar nov 29 14:37:24 -0300 2011:

2011/11/29 Tom Lane <tgl@sss.pgh.pa.us>:

I don't think renaming is necessary.  plpgsql is a standalone shared
library and so its symbols don't matter to anybody but itself.

Possibly a larger question, though, is whether you really need a new
source file.  If that results in having to export functions that
otherwise could stay static, maybe it's not the best choice.

Some refactoring of pl_exec should be nice - a management of row,
record variables and array fields is part that can be shared with
SQL/PSM interpret. But I have not idea how it realize.

I proposed at the PL summit that perhaps we should have some sort of PL
lib that would be shared by plpgsql and plpsm, to reduce code
duplication.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#7Pavel Stehule
pavel.stehule@gmail.com
In reply to: Albe Laurenz (#2)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Hello

updated patch:

* recheck compilation and initdb
* working routines moved to pl_exec.c
* add entry to catalog.sgml about lanchecker field
* add node's utils

Regards

Pavel Stehule

2011/11/29 Albe Laurenz <laurenz.albe@wien.gv.at>:

Show quoted text

Pavel Stehule wrote:

I am sending updated patch, that implements a CHECK FUNCTION and CHECK
TRIGGER statements.

This patch is significantly redesigned to previous version (PL/pgSQL
part) - it is more readable, more accurate. There are new regress
tests.

Please, can some English native speaker fix doc and comments?

ToDo:

CHECK FUNCTION search function according to function signature - it
should be changes for using a actual types - it can be solution for
polymorphic types and useful tool for work with overloaded functions -
when is not clean, that function was executed.

check function foo(int, int);
NOTICE: checking function foo(variadic anyarray)
...

and maybe some support for named parameters
check function foo(name text, surname text);
NOTICE: checking function foo(text, text, text, text)
...

I think that CHECK FUNCTION should work exactly like DROP FUNCTION
in these respects.

Submission review:
------------------

The patch is context diff, applies with some offsets, contains
regression tests and documentation.

The documentation should be expanded, the doc for CHECK FUNCTION
is only a stub. It should describe the procedure and what is checked.
That would also make reviewing easier.
I think that some documentation should be added to plhandler.sgml.
There is a spelling error (statemnt) in the docs.

Usability review:
-----------------

If I understand right, the goal of CHECK FUNCTION is to find errors in
the function definition without actually having to execute it.
The patch tries to provide this for PL/pgSQL.

There hasn't been any discussion on the list, the patch was just posted,
so I can't say that we want that. Tom added it to the commitfest page,
so there's one important voice against dismissing it right away :^)

I don't understand the functional difference between a "validator function"
and a "check function" as proposed by this patch. I am probably missing
something, but why couldn't these checks be added to function validation
when check_function_bodies is set?
A new "CHECK FUNCTION" statement could simply call the validator function.

I don't see any pg_dump support in this patch, and PL/pgSQL probably doesn't
need that, but I think pg_dump support for CREATE LANGUAGE would have to
be added for other PLs.

I can't test if the functionality is complete because I can't get it to
run (see below).

Feature test:
-------------

I can't really test the patch because initdb fails:

$ initdb -E UTF8 --locale=de_DE.UTF-8 --lc-messages=en_US.UTF-8 -U postgres /postgres/cvs/dbhome
The files belonging to this database system will be owned by user "laurenz".
This user must also own the server process.

The database cluster will be initialized with locales
 COLLATE:  de_DE.UTF-8
 CTYPE:    de_DE.UTF-8
 MESSAGES: en_US.UTF-8
 MONETARY: de_DE.UTF-8
 NUMERIC:  de_DE.UTF-8
 TIME:     de_DE.UTF-8
The default text search configuration will be set to "german".

creating directory /postgres/cvs/dbhome ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 32MB
creating configuration files ... ok
creating template1 database in /postgres/cvs/dbhome/base/1 ... ok
initializing pg_authid ... ok
initializing dependencies ... ok
creating system views ... ok
loading system objects' descriptions ... ok
creating collations ... ok
creating conversions ... ok
creating dictionaries ... ok
setting privileges on built-in objects ... ok
creating information schema ... ok
loading PL/pgSQL server-side language ... FATAL:  could not load library "/postgres/cvs/pg92/lib/plpgsql.so": /postgres/cvs/pg92/lib/plpgsql.so: undefined symbol: plpgsql_delete_function
STATEMENT:  CREATE EXTENSION plpgsql;

child process exited with exit code 1
initdb: removing data directory "/postgres/cvs/dbhome"

Coding review:
--------------

The patch compiles without warnings.
The comments in the code should be revised, they are bad English.
I can't say if there should be more of them -- I don't know this part of
the code well enough to have a well-founded opinion.

I don't think there are any portability issues, but I could not test it.

There are a lot of small changes to pl/plpgsql/src/pl_exec.c, are they all
necessary? For example, why was copy_plpgsql_datum renamed to
plpgsql_copy_datum?

I'll mark the patch as "Waiting on Author".

Yours,
Laurenz Albe

Attachments:

check_pl-2011-11-29.difftext/x-patch; charset=US-ASCII; name=check_pl-2011-11-29.diffDownload
*** ./doc/src/sgml/catalogs.sgml.orig	2011-11-29 19:09:02.000000000 +0100
--- ./doc/src/sgml/catalogs.sgml	2011-11-29 20:28:00.571246006 +0100
***************
*** 3652,3657 ****
--- 3652,3668 ----
       </row>
  
       <row>
+       <entry><structfield>lanchecker</structfield></entry>
+       <entry><type>oid</type></entry>
+       <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+       <entry>
+        This references a language checker function that is responsible
+        for checking a embedded SQL and can provide detailed checking.
+        Zero if no checker is provided.
+       </entry>
+      </row>
+ 
+      <row>
        <entry><structfield>lanacl</structfield></entry>
        <entry><type>aclitem[]</type></entry>
        <entry></entry>
*** ./doc/src/sgml/ref/allfiles.sgml.orig	2011-11-29 19:20:59.468117093 +0100
--- ./doc/src/sgml/ref/allfiles.sgml	2011-11-29 19:21:24.487804955 +0100
***************
*** 40,45 ****
--- 40,46 ----
  <!ENTITY alterView          SYSTEM "alter_view.sgml">
  <!ENTITY analyze            SYSTEM "analyze.sgml">
  <!ENTITY begin              SYSTEM "begin.sgml">
+ <!ENTITY checkFunction      SYSTEM "check_function.sgml">
  <!ENTITY checkpoint         SYSTEM "checkpoint.sgml">
  <!ENTITY close              SYSTEM "close.sgml">
  <!ENTITY cluster            SYSTEM "cluster.sgml">
*** ./doc/src/sgml/ref/create_language.sgml.orig	2011-11-29 19:20:59.470117069 +0100
--- ./doc/src/sgml/ref/create_language.sgml	2011-11-29 19:21:24.488804943 +0100
***************
*** 23,29 ****
  <synopsis>
  CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
  CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ]
  </synopsis>
   </refsynopsisdiv>
  
--- 23,29 ----
  <synopsis>
  CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
  CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ] [ CHECK <replaceable>checkfunction</replaceable> ]
  </synopsis>
   </refsynopsisdiv>
  
***************
*** 217,222 ****
--- 217,236 ----
        </para>
       </listitem>
      </varlistentry>
+ 
+     <varlistentry>
+      <term><literal>CHECK</literal> <replaceable class="parameter">checkfunction</replaceable></term>
+ 
+      <listitem>
+       <para><replaceable class="parameter">checkfunction</replaceable> is the
+        name of a previously registered function that will be called
+        when a new function in the language is created, to check the
+        function by statemnt <command>CHECK FUNCTION</command> or 
+        <command>CHECK TRIGGER</command>.
+       </para>
+      </listitem>
+     </varlistentry>
+ 
     </variablelist>
  
    <para>
*** ./doc/src/sgml/reference.sgml.orig	2011-11-29 19:20:59.471117057 +0100
--- ./doc/src/sgml/reference.sgml	2011-11-29 19:21:24.492804895 +0100
***************
*** 68,73 ****
--- 68,74 ----
     &alterView;
     &analyze;
     &begin;
+    &checkFunction;
     &checkpoint;
     &close;
     &cluster;
*** ./src/backend/catalog/pg_proc.c.orig	2011-11-29 19:20:59.474117021 +0100
--- ./src/backend/catalog/pg_proc.c	2011-11-29 19:21:24.494804869 +0100
***************
*** 1101,1103 ****
--- 1101,1104 ----
  	*newcursorpos = newcp;
  	return false;
  }
+ 
*** ./src/backend/commands/functioncmds.c.orig	2011-11-29 19:20:59.475117009 +0100
--- ./src/backend/commands/functioncmds.c	2011-11-29 19:21:24.496804843 +0100
***************
*** 44,53 ****
--- 44,55 ----
  #include "catalog/pg_namespace.h"
  #include "catalog/pg_proc.h"
  #include "catalog/pg_proc_fn.h"
+ #include "catalog/pg_trigger.h"
  #include "catalog/pg_type.h"
  #include "catalog/pg_type_fn.h"
  #include "commands/defrem.h"
  #include "commands/proclang.h"
+ #include "commands/trigger.h"
  #include "miscadmin.h"
  #include "optimizer/var.h"
  #include "parser/parse_coerce.h"
***************
*** 60,65 ****
--- 62,68 ----
  #include "utils/fmgroids.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
+ #include "utils/memutils.h"
  #include "utils/rel.h"
  #include "utils/syscache.h"
  #include "utils/tqual.h"
***************
*** 1009,1014 ****
--- 1012,1152 ----
  	}
  }
  
+ /*
+  * CheckFunction
+  *			call a PL checker function when this function exists.
+  */
+ void
+ CheckFunction(CheckFunctionStmt *stmt)
+ {
+ 	List	   *functionName = stmt->funcname;
+ 	List	   *argTypes = stmt->args;	/* list of TypeName nodes */
+ 	Oid			funcOid;
+ 
+ 	HeapTuple	tup;
+ 	Form_pg_proc proc;
+ 
+ 	HeapTuple	languageTuple;
+ 	Form_pg_language languageStruct;
+ 	Oid		languageChecker;
+ 	Oid trgOid = InvalidOid;
+ 	Oid	relid = InvalidOid;
+ 
+ 	/* when we should to check trigger, then we should to find a trigger handler */
+ 	if (functionName == NULL)
+ 	{
+ 		HeapTuple	ht_trig;
+ 		Form_pg_trigger trigrec;
+ 		ScanKeyData skey[1];
+ 		Relation	tgrel;
+ 		SysScanDesc tgscan;
+ 		char *fname;
+ 
+ 		relid = RangeVarGetRelid(stmt->relation, ShareLock, false, false);
+ 		trgOid = get_trigger_oid(relid, stmt->trgname, false);
+ 
+ 		/*
+ 		 * Fetch the pg_trigger tuple by the Oid of the trigger
+ 		 */
+ 		tgrel = heap_open(TriggerRelationId, AccessShareLock);
+ 
+ 		ScanKeyInit(&skey[0],
+ 					ObjectIdAttributeNumber,
+ 					BTEqualStrategyNumber, F_OIDEQ,
+ 					ObjectIdGetDatum(trgOid));
+ 
+ 		tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true,
+ 									SnapshotNow, 1, skey);
+ 
+ 		ht_trig = systable_getnext(tgscan);
+ 
+ 		if (!HeapTupleIsValid(ht_trig))
+ 			elog(ERROR, "could not find tuple for trigger %u", trgOid);
+ 
+ 		trigrec = (Form_pg_trigger) GETSTRUCT(ht_trig);
+ 
+ 		/* we need to know trigger function to get PL checker function */
+ 		funcOid = trigrec->tgfoid;
+ 		fname = format_procedure(funcOid);
+ 		/* Clean up */
+ 		systable_endscan(tgscan);
+ 
+ 		elog(NOTICE, "checking function \"%s\"", fname);
+ 		pfree(fname);
+ 
+ 		heap_close(tgrel, AccessShareLock);
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * Find the function, 
+ 		 */
+ 		funcOid = LookupFuncNameTypeNames(functionName, argTypes, false);
+ 	}
+ 
+ 	tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
+ 	if (!HeapTupleIsValid(tup)) /* should not happen */
+ 		elog(ERROR, "cache lookup failed for function %u", funcOid);
+ 
+ 	proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 	languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
+ 	Assert(HeapTupleIsValid(languageTuple));
+ 
+ 	languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
+ 	languageChecker = languageStruct->lanchecker;
+ 
+ 	/* Check a function body */
+ 	if (OidIsValid(languageChecker))
+ 	{
+ 		ArrayType  *set_items = NULL;
+ 		int			save_nestlevel;
+ 		Datum	datum;
+ 		bool		isnull;
+ 		MemoryContext oldCxt;
+ 		MemoryContext checkCxt;
+ 
+ 		datum = SysCacheGetAttr(PROCOID, tup, Anum_pg_proc_proconfig, &isnull);
+ 
+ 		if (!isnull)
+ 		{
+ 			/* Set per-function configuration parameters */
+ 			set_items = (ArrayType *) DatumGetPointer(datum);
+ 			if (set_items)			/* Need a new GUC nesting level */
+ 			{
+ 				save_nestlevel = NewGUCNestLevel();
+ 				ProcessGUCArray(set_items,
+ 							(superuser() ? PGC_SUSET : PGC_USERSET),
+ 							PGC_S_SESSION,
+ 							GUC_ACTION_SAVE);
+ 			}
+ 			else
+ 				save_nestlevel = 0; /* keep compiler quiet */
+ 		}
+ 
+ 		checkCxt = AllocSetContextCreate(CurrentMemoryContext,
+ 									"Check temporary context",
+ 									ALLOCSET_DEFAULT_MINSIZE,
+ 									ALLOCSET_DEFAULT_INITSIZE,
+ 									ALLOCSET_DEFAULT_MAXSIZE);
+ 
+ 		oldCxt = MemoryContextSwitchTo(checkCxt);
+ 
+ 		OidFunctionCall2(languageChecker, ObjectIdGetDatum(funcOid), 
+ 							    ObjectIdGetDatum(relid));
+ 
+ 		MemoryContextSwitchTo(oldCxt);
+ 
+ 		if (set_items)
+ 			AtEOXact_GUC(true, save_nestlevel);
+ 	}
+ 	else
+ 		elog(WARNING, "language \"%s\" has no defined checker function",
+ 			    NameStr(languageStruct->lanname));
+ 
+ 	ReleaseSysCache(languageTuple);
+ 	ReleaseSysCache(tup);
+ }
  
  /*
   * Rename function
*** ./src/backend/commands/proclang.c.orig	2011-11-29 19:20:59.477116983 +0100
--- ./src/backend/commands/proclang.c	2011-11-29 19:21:24.497804830 +0100
***************
*** 46,57 ****
  	char	   *tmplhandler;	/* name of handler function */
  	char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
  	char	   *tmplvalidator;	/* name of validator function, or NULL */
  	char	   *tmpllibrary;	/* path of shared library */
  } PLTemplate;
  
  static void create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, bool trusted);
  static PLTemplate *find_language_template(const char *languageName);
  static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
  							Oid newOwnerId);
--- 46,58 ----
  	char	   *tmplhandler;	/* name of handler function */
  	char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
  	char	   *tmplvalidator;	/* name of validator function, or NULL */
+ 	char	   *tmplchecker;	/* name of checker function, or NULL */
  	char	   *tmpllibrary;	/* path of shared library */
  } PLTemplate;
  
  static void create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, Oid checkerOid, bool trusted);
  static PLTemplate *find_language_template(const char *languageName);
  static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
  							Oid newOwnerId);
***************
*** 67,75 ****
  	PLTemplate *pltemplate;
  	Oid			handlerOid,
  				inlineOid,
! 				valOid;
  	Oid			funcrettype;
! 	Oid			funcargtypes[1];
  
  	/*
  	 * If we have template information for the language, ignore the supplied
--- 68,77 ----
  	PLTemplate *pltemplate;
  	Oid			handlerOid,
  				inlineOid,
! 				valOid,
! 				checkerOid;
  	Oid			funcrettype;
! 	Oid			funcargtypes[2];
  
  	/*
  	 * If we have template information for the language, ignore the supplied
***************
*** 219,228 ****
  		else
  			valOid = InvalidOid;
  
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, pltemplate->tmpltrusted);
  	}
  	else
  	{
--- 221,269 ----
  		else
  			valOid = InvalidOid;
  
+ 		/*
+ 		 * Likewise for the checker, if required; but we don't care about
+ 		 * its return type.
+ 		 */
+ 		if (pltemplate->tmplchecker)
+ 		{
+ 			funcname = SystemFuncName(pltemplate->tmplchecker);
+ 			funcargtypes[0] = OIDOID;
+ 			funcargtypes[1] = REGCLASSOID;
+ 			checkerOid = LookupFuncName(funcname, 2, funcargtypes, true);
+ 			if (!OidIsValid(checkerOid))
+ 			{
+ 				checkerOid = ProcedureCreate(pltemplate->tmplchecker,
+ 										 PG_CATALOG_NAMESPACE,
+ 										 false, /* replace */
+ 										 false, /* returnsSet */
+ 										 VOIDOID,
+ 										 ClanguageId,
+ 										 F_FMGR_C_VALIDATOR,
+ 										 pltemplate->tmplchecker,
+ 										 pltemplate->tmpllibrary,
+ 										 false, /* isAgg */
+ 										 false, /* isWindowFunc */
+ 										 false, /* security_definer */
+ 										 true,	/* isStrict */
+ 										 PROVOLATILE_VOLATILE,
+ 										 buildoidvector(funcargtypes, 2),
+ 										 PointerGetDatum(NULL),
+ 										 PointerGetDatum(NULL),
+ 										 PointerGetDatum(NULL),
+ 										 NIL,
+ 										 PointerGetDatum(NULL),
+ 										 1,
+ 										 0);
+ 			}
+ 		}
+ 		else
+ 			checkerOid = InvalidOid;
+ 
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, checkerOid, pltemplate->tmpltrusted);
  	}
  	else
  	{
***************
*** 294,303 ****
  		else
  			valOid = InvalidOid;
  
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, stmt->pltrusted);
  	}
  }
  
--- 335,355 ----
  		else
  			valOid = InvalidOid;
  
+ 		/* validate the checker function */
+ 		if (stmt->plchecker)
+ 		{
+ 			funcargtypes[0] = OIDOID;
+ 			funcargtypes[1] = REGCLASSOID;
+ 			checkerOid = LookupFuncName(stmt->plchecker, 2, funcargtypes, false);
+ 			/* return value is ignored, so we don't check the type */
+ 		}
+ 		else
+ 			checkerOid = InvalidOid;
+ 
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, checkerOid, stmt->pltrusted);
  	}
  }
  
***************
*** 307,313 ****
  static void
  create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, bool trusted)
  {
  	Relation	rel;
  	TupleDesc	tupDesc;
--- 359,365 ----
  static void
  create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, Oid checkerOid, bool trusted)
  {
  	Relation	rel;
  	TupleDesc	tupDesc;
***************
*** 337,342 ****
--- 389,395 ----
  	values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid);
  	values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid);
  	values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid);
+ 	values[Anum_pg_language_lanchecker - 1] = ObjectIdGetDatum(checkerOid);
  	nulls[Anum_pg_language_lanacl - 1] = true;
  
  	/* Check for pre-existing definition */
***************
*** 423,428 ****
--- 476,490 ----
  		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  	}
  
+ 	/* dependency on the checker function, if any */
+ 	if (OidIsValid(checkerOid))
+ 	{
+ 		referenced.classId = ProcedureRelationId;
+ 		referenced.objectId = checkerOid;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	/* Post creation hook for new procedural language */
  	InvokeObjectAccessHook(OAT_POST_CREATE,
  						   LanguageRelationId, myself.objectId, 0);
***************
*** 478,483 ****
--- 540,550 ----
  		if (!isnull)
  			result->tmplvalidator = TextDatumGetCString(datum);
  
+ 		datum = heap_getattr(tup, Anum_pg_pltemplate_tmplchecker,
+ 							 RelationGetDescr(rel), &isnull);
+ 		if (!isnull)
+ 			result->tmplchecker = TextDatumGetCString(datum);
+ 
  		datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary,
  							 RelationGetDescr(rel), &isnull);
  		if (!isnull)
*** ./src/backend/nodes/copyfuncs.c.orig	2011-11-29 19:09:02.000000000 +0100
--- ./src/backend/nodes/copyfuncs.c	2011-11-29 20:17:01.339172458 +0100
***************
*** 2880,2885 ****
--- 2880,2898 ----
  	return newnode;
  }
  
+ static CheckFunctionStmt *
+ _copyCheckFunctionStmt(CheckFunctionStmt *from)
+ {
+ 	CheckFunctionStmt *newnode = makeNode(CheckFunctionStmt);
+ 
+ 	COPY_NODE_FIELD(funcname);
+ 	COPY_NODE_FIELD(args);
+ 	COPY_STRING_FIELD(trgname);
+ 	COPY_NODE_FIELD(relation);
+ 
+ 	return newnode;
+ }
+ 
  static DoStmt *
  _copyDoStmt(DoStmt *from)
  {
***************
*** 4165,4170 ****
--- 4178,4186 ----
  		case T_AlterFunctionStmt:
  			retval = _copyAlterFunctionStmt(from);
  			break;
+ 		case T_CheckFunctionStmt:
+ 			retval = _copyCheckFunctionStmt(from);
+ 			break;
  		case T_DoStmt:
  			retval = _copyDoStmt(from);
  			break;
*** ./src/backend/nodes/equalfuncs.c.orig	2011-11-29 20:19:55.045587471 +0100
--- ./src/backend/nodes/equalfuncs.c	2011-11-29 20:19:21.850082357 +0100
***************
*** 1292,1297 ****
--- 1292,1308 ----
  }
  
  static bool
+ _equalCheckFunctionStmt(CheckFunctionStmt *a, CheckFunctionStmt *b)
+ {
+ 	COMPARE_NODE_FIELD(funcname);
+ 	COMPARE_NODE_FIELD(args);
+ 	COMPARE_STRING_FIELD(trgname);
+ 	COMPARE_NODE_FIELD(relation);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalDoStmt(DoStmt *a, DoStmt *b)
  {
  	COMPARE_NODE_FIELD(args);
***************
*** 2708,2713 ****
--- 2719,2727 ----
  		case T_AlterFunctionStmt:
  			retval = _equalAlterFunctionStmt(a, b);
  			break;
+ 		case T_CheckFunctionStmt:
+ 			retval = _equalCheckFunctionStmt(a, b);
+ 			break;
  		case T_DoStmt:
  			retval = _equalDoStmt(a, b);
  			break;
*** ./src/backend/parser/gram.y.orig	2011-11-29 19:09:02.876463248 +0100
--- ./src/backend/parser/gram.y	2011-11-29 19:21:24.502804769 +0100
***************
*** 227,232 ****
--- 227,233 ----
  		DeallocateStmt PrepareStmt ExecuteStmt
  		DropOwnedStmt ReassignOwnedStmt
  		AlterTSConfigurationStmt AlterTSDictionaryStmt
+ 		CheckFunctionStmt
  
  %type <node>	select_no_parens select_with_parens select_clause
  				simple_select values_clause
***************
*** 276,282 ****
  
  %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
  				opt_class opt_inline_handler opt_validator validator_clause
! 				opt_collate
  
  %type <range>	qualified_name OptConstrFromTable
  
--- 277,283 ----
  
  %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
  				opt_class opt_inline_handler opt_validator validator_clause
! 				opt_collate opt_checker
  
  %type <range>	qualified_name OptConstrFromTable
  
***************
*** 700,705 ****
--- 701,707 ----
  			| AlterUserSetStmt
  			| AlterUserStmt
  			| AnalyzeStmt
+ 			| CheckFunctionStmt
  			| CheckPointStmt
  			| ClosePortalStmt
  			| ClusterStmt
***************
*** 3174,3184 ****
  				n->plhandler = NIL;
  				n->plinline = NIL;
  				n->plvalidator = NIL;
  				n->pltrusted = false;
  				$$ = (Node *)n;
  			}
  			| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
! 			  HANDLER handler_name opt_inline_handler opt_validator
  			{
  				CreatePLangStmt *n = makeNode(CreatePLangStmt);
  				n->replace = $2;
--- 3176,3187 ----
  				n->plhandler = NIL;
  				n->plinline = NIL;
  				n->plvalidator = NIL;
+ 				n->plchecker = NIL;
  				n->pltrusted = false;
  				$$ = (Node *)n;
  			}
  			| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
! 			  HANDLER handler_name opt_inline_handler opt_validator opt_checker
  			{
  				CreatePLangStmt *n = makeNode(CreatePLangStmt);
  				n->replace = $2;
***************
*** 3186,3191 ****
--- 3189,3195 ----
  				n->plhandler = $8;
  				n->plinline = $9;
  				n->plvalidator = $10;
+ 				n->plchecker = $11;
  				n->pltrusted = $3;
  				$$ = (Node *)n;
  			}
***************
*** 3220,3225 ****
--- 3224,3234 ----
  			| /*EMPTY*/								{ $$ = NIL; }
  		;
  
+ opt_checker:
+ 			CHECK handler_name					{ $$ = $2; }
+ 			| /*EMPTY*/								{ $$ = NIL; }
+ 		;
+ 
  DropPLangStmt:
  			DROP opt_procedural LANGUAGE ColId_or_Sconst opt_drop_behavior
  				{
***************
*** 6250,6255 ****
--- 6259,6294 ----
  
  /*****************************************************************************
   *
+  *		CHECK FUNCTION funcname(args)
+  *		CHECK TRIGGER triggername ON table
+  *
+  *
+  *****************************************************************************/
+ 
+ 
+ CheckFunctionStmt:
+ 			CHECK FUNCTION func_name func_args
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = $3;
+ 					n->args = extractArgTypes($4);
+ 					n->trgname = NULL;
+ 					n->relation = NULL;
+ 					$$ = (Node *) n;
+ 				}
+ 			| CHECK TRIGGER name ON qualified_name
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = NULL;
+ 					n->args = NIL;
+ 					n->trgname = $3;
+ 					n->relation = $5;
+ 					$$ = (Node *) n;
+ 				}
+ 		;
+ 
+ /*****************************************************************************
+  *
   *		DO <anonymous code block> [ LANGUAGE language ]
   *
   * We use a DefElem list for future extensibility, and to allow flexibility
*** ./src/backend/tcop/utility.c.orig	2011-11-29 19:20:59.480116945 +0100
--- ./src/backend/tcop/utility.c	2011-11-29 19:21:24.513804628 +0100
***************
*** 882,887 ****
--- 882,891 ----
  			AlterFunction((AlterFunctionStmt *) parsetree);
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			CheckFunction((CheckFunctionStmt *) parsetree);
+ 			break;
+ 
  		case T_IndexStmt:		/* CREATE INDEX */
  			{
  				IndexStmt  *stmt = (IndexStmt *) parsetree;
***************
*** 2125,2130 ****
--- 2129,2141 ----
  			}
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			if (((CheckFunctionStmt *) parsetree)->funcname != NULL)
+ 				tag = "CHECK FUNCTION";
+ 			else
+ 				tag = "CHECK TRIGGER";
+ 			break;
+ 
  		default:
  			elog(WARNING, "unrecognized node type: %d",
  				 (int) nodeTag(parsetree));
***************
*** 2565,2570 ****
--- 2576,2585 ----
  			}
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			lev = LOGSTMT_ALL;
+ 			break;
+ 
  		default:
  			elog(WARNING, "unrecognized node type: %d",
  				 (int) nodeTag(parsetree));
*** ./src/bin/pg_dump/pg_dump.c.orig	2011-11-29 19:09:03.000000000 +0100
--- ./src/bin/pg_dump/pg_dump.c	2011-11-29 20:04:31.094156626 +0100
***************
*** 5326,5338 ****
  	int			i_lanplcallfoid;
  	int			i_laninline;
  	int			i_lanvalidator;
  	int			i_lanacl;
  	int			i_lanowner;
  
  	/* Make sure we are in proper schema */
  	selectSourceSchema("pg_catalog");
  
! 	if (g_fout->remoteVersion >= 90000)
  	{
  		/* pg_language has a laninline column */
  		appendPQExpBuffer(query, "SELECT tableoid, oid, "
--- 5326,5351 ----
  	int			i_lanplcallfoid;
  	int			i_laninline;
  	int			i_lanvalidator;
+ 	int			i_lanchecker;
  	int			i_lanacl;
  	int			i_lanowner;
  
  	/* Make sure we are in proper schema */
  	selectSourceSchema("pg_catalog");
  
! 	if (g_fout->remoteVersion >= 90200)
! 	{
! 		/* pg_language has a lanchecker column */
! 		appendPQExpBuffer(query, "SELECT tableoid, oid, "
! 						  "lanname, lanpltrusted, lanplcallfoid, "
! 						  "laninline, lanvalidator, lanchecker, lanacl, "
! 						  "(%s lanowner) AS lanowner "
! 						  "FROM pg_language "
! 						  "WHERE lanispl "
! 						  "ORDER BY oid",
! 						  username_subquery);
! 	}
! 	else if (g_fout->remoteVersion >= 90000)
  	{
  		/* pg_language has a laninline column */
  		appendPQExpBuffer(query, "SELECT tableoid, oid, "
***************
*** 5409,5414 ****
--- 5422,5428 ----
  	/* these may fail and return -1: */
  	i_laninline = PQfnumber(res, "laninline");
  	i_lanvalidator = PQfnumber(res, "lanvalidator");
+ 	i_lanchecker = PQfnumber(res, "lanchecker");
  	i_lanacl = PQfnumber(res, "lanacl");
  	i_lanowner = PQfnumber(res, "lanowner");
  
***************
*** 5422,5427 ****
--- 5436,5445 ----
  		planginfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_lanname));
  		planginfo[i].lanpltrusted = *(PQgetvalue(res, i, i_lanpltrusted)) == 't';
  		planginfo[i].lanplcallfoid = atooid(PQgetvalue(res, i, i_lanplcallfoid));
+ 		if (i_lanchecker >= 0)
+ 			planginfo[i].lanchecker = atooid(PQgetvalue(res, i, i_lanchecker));
+ 		else
+ 			planginfo[i].lanchecker = InvalidOid;
  		if (i_laninline >= 0)
  			planginfo[i].laninline = atooid(PQgetvalue(res, i, i_laninline));
  		else
***************
*** 8597,8602 ****
--- 8615,8621 ----
  	char	   *qlanname;
  	char	   *lanschema;
  	FuncInfo   *funcInfo;
+ 	FuncInfo   *checkerInfo = NULL;
  	FuncInfo   *inlineInfo = NULL;
  	FuncInfo   *validatorInfo = NULL;
  
***************
*** 8616,8621 ****
--- 8635,8647 ----
  	if (funcInfo != NULL && !funcInfo->dobj.dump)
  		funcInfo = NULL;		/* treat not-dumped same as not-found */
  
+ 	if (OidIsValid(plang->lanchecker))
+ 	{
+ 		checkerInfo = findFuncByOid(plang->lanchecker);
+ 		if (checkerInfo != NULL && !checkerInfo->dobj.dump)
+ 			checkerInfo = NULL;
+ 	}
+ 
  	if (OidIsValid(plang->laninline))
  	{
  		inlineInfo = findFuncByOid(plang->laninline);
***************
*** 8642,8647 ****
--- 8668,8674 ----
  	 * don't, this might not work terribly nicely.
  	 */
  	useParams = (funcInfo != NULL &&
+ 				 (checkerInfo != NULL || !OidIsValid(plang->lanchecker)) &&
  				 (inlineInfo != NULL || !OidIsValid(plang->laninline)) &&
  				 (validatorInfo != NULL || !OidIsValid(plang->lanvalidator)));
  
***************
*** 8697,8702 ****
--- 8724,8739 ----
  			appendPQExpBuffer(defqry, "%s",
  							  fmtId(validatorInfo->dobj.name));
  		}
+ 		if (OidIsValid(plang->lanchecker))
+ 		{
+ 			appendPQExpBuffer(defqry, " CHECK ");
+ 			/* Cope with possibility that checker is in different schema */
+ 			if (checkerInfo->dobj.namespace != funcInfo->dobj.namespace)
+ 				appendPQExpBuffer(defqry, "%s.",
+ 							   fmtId(checkerInfo->dobj.namespace->dobj.name));
+ 			appendPQExpBuffer(defqry, "%s",
+ 							  fmtId(checkerInfo->dobj.name));
+ 		}
  	}
  	else
  	{
*** ./src/bin/pg_dump/pg_dump.h.orig	2011-11-29 20:05:48.255044631 +0100
--- ./src/bin/pg_dump/pg_dump.h	2011-11-29 20:05:08.766614345 +0100
***************
*** 387,392 ****
--- 387,393 ----
  	Oid			lanplcallfoid;
  	Oid			laninline;
  	Oid			lanvalidator;
+ 	Oid			lanchecker;
  	char	   *lanacl;
  	char	   *lanowner;		/* name of owner, or empty string */
  } ProcLangInfo;
*** ./src/bin/psql/tab-complete.c.orig	2011-11-29 19:20:59.482116921 +0100
--- ./src/bin/psql/tab-complete.c	2011-11-29 19:21:24.516804592 +0100
***************
*** 1,4 ****
--- 1,5 ----
  /*
+  *
   * psql - the PostgreSQL interactive terminal
   *
   * Copyright (c) 2000-2011, PostgreSQL Global Development Group
***************
*** 727,733 ****
  #define prev6_wd  (previous_words[5])
  
  	static const char *const sql_commands[] = {
! 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
  		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
--- 728,734 ----
  #define prev6_wd  (previous_words[5])
  
  	static const char *const sql_commands[] = {
! 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECK", "CHECKPOINT", "CLOSE", "CLUSTER",
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
  		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
***************
*** 1524,1529 ****
--- 1525,1552 ----
  
  		COMPLETE_WITH_LIST(list_TRANS);
  	}
+ 
+ /* CHECK */
+ 	else if (pg_strcasecmp(prev_wd, "CHECK") == 0)
+ 	{
+ 		static const char *const list_CHECK[] =
+ 		{"FUNCTION", "TRIGGER", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_CHECK);
+ 	}
+ 	else if (pg_strcasecmp(prev3_wd, "CHECK") == 0 &&
+ 			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+ 	{
+ 		COMPLETE_WITH_CONST("ON");
+ 	}
+ 	else if (pg_strcasecmp(prev4_wd, "CHECK") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "ON") == 0)
+ 	{
+ 		completion_info_charp = prev2_wd;
+ 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+ 	}
+ 
  /* CLUSTER */
  
  	/*
*** ./src/include/catalog/pg_language.h.orig	2011-11-29 19:20:59.483116909 +0100
--- ./src/include/catalog/pg_language.h	2011-11-29 19:21:24.518804568 +0100
***************
*** 37,42 ****
--- 37,43 ----
  	Oid			lanplcallfoid;	/* Call handler for PL */
  	Oid			laninline;		/* Optional anonymous-block handler function */
  	Oid			lanvalidator;	/* Optional validation function */
+ 	Oid			lanchecker;	/* Optional checker function */
  	aclitem		lanacl[1];		/* Access privileges */
  } FormData_pg_language;
  
***************
*** 51,57 ****
   *		compiler constants for pg_language
   * ----------------
   */
! #define Natts_pg_language				8
  #define Anum_pg_language_lanname		1
  #define Anum_pg_language_lanowner		2
  #define Anum_pg_language_lanispl		3
--- 52,58 ----
   *		compiler constants for pg_language
   * ----------------
   */
! #define Natts_pg_language				9
  #define Anum_pg_language_lanname		1
  #define Anum_pg_language_lanowner		2
  #define Anum_pg_language_lanispl		3
***************
*** 59,78 ****
  #define Anum_pg_language_lanplcallfoid	5
  #define Anum_pg_language_laninline		6
  #define Anum_pg_language_lanvalidator	7
! #define Anum_pg_language_lanacl			8
  
  /* ----------------
   *		initial contents of pg_language
   * ----------------
   */
  
! DATA(insert OID = 12 ( "internal"	PGUID f f 0 0 2246 _null_ ));
  DESCR("built-in functions");
  #define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c"			PGUID f f 0 0 2247 _null_ ));
  DESCR("dynamically-loaded C functions");
  #define ClanguageId 13
! DATA(insert OID = 14 ( "sql"		PGUID f t 0 0 2248 _null_ ));
  DESCR("SQL-language functions");
  #define SQLlanguageId 14
  
--- 60,80 ----
  #define Anum_pg_language_lanplcallfoid	5
  #define Anum_pg_language_laninline		6
  #define Anum_pg_language_lanvalidator	7
! #define Anum_pg_language_lanchecker	8
! #define Anum_pg_language_lanacl			9
  
  /* ----------------
   *		initial contents of pg_language
   * ----------------
   */
  
! DATA(insert OID = 12 ( "internal"	PGUID f f 0 0 2246 0 _null_ ));
  DESCR("built-in functions");
  #define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c"			PGUID f f 0 0 2247 0 _null_ ));
  DESCR("dynamically-loaded C functions");
  #define ClanguageId 13
! DATA(insert OID = 14 ( "sql"		PGUID f t 0 0 2248 0 _null_ ));
  DESCR("SQL-language functions");
  #define SQLlanguageId 14
  
*** ./src/include/catalog/pg_pltemplate.h.orig	2011-11-29 19:20:59.484116897 +0100
--- ./src/include/catalog/pg_pltemplate.h	2011-11-29 19:21:24.518804568 +0100
***************
*** 36,41 ****
--- 36,42 ----
  	text		tmplhandler;	/* name of call handler function */
  	text		tmplinline;		/* name of anonymous-block handler, or NULL */
  	text		tmplvalidator;	/* name of validator function, or NULL */
+ 	text		tmplchecker;	/* name of checker function, or NULL */
  	text		tmpllibrary;	/* path of shared library */
  	aclitem		tmplacl[1];		/* access privileges for template */
  } FormData_pg_pltemplate;
***************
*** 51,65 ****
   *		compiler constants for pg_pltemplate
   * ----------------
   */
! #define Natts_pg_pltemplate					8
  #define Anum_pg_pltemplate_tmplname			1
  #define Anum_pg_pltemplate_tmpltrusted		2
  #define Anum_pg_pltemplate_tmpldbacreate	3
  #define Anum_pg_pltemplate_tmplhandler		4
  #define Anum_pg_pltemplate_tmplinline		5
  #define Anum_pg_pltemplate_tmplvalidator	6
! #define Anum_pg_pltemplate_tmpllibrary		7
! #define Anum_pg_pltemplate_tmplacl			8
  
  
  /* ----------------
--- 52,67 ----
   *		compiler constants for pg_pltemplate
   * ----------------
   */
! #define Natts_pg_pltemplate					9
  #define Anum_pg_pltemplate_tmplname			1
  #define Anum_pg_pltemplate_tmpltrusted		2
  #define Anum_pg_pltemplate_tmpldbacreate	3
  #define Anum_pg_pltemplate_tmplhandler		4
  #define Anum_pg_pltemplate_tmplinline		5
  #define Anum_pg_pltemplate_tmplvalidator	6
! #define Anum_pg_pltemplate_tmplchecker		7
! #define Anum_pg_pltemplate_tmpllibrary		8
! #define Anum_pg_pltemplate_tmplacl			9
  
  
  /* ----------------
***************
*** 67,79 ****
   * ----------------
   */
  
! DATA(insert ( "plpgsql"		t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl"		t t "pltcl_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu"		f f "pltclu_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl"		t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu"		f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu"	f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u"	f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u"	f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" "$libdir/plpython3" _null_ ));
  
  #endif   /* PG_PLTEMPLATE_H */
--- 69,81 ----
   * ----------------
   */
  
! DATA(insert ( "plpgsql"		t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "plpgsql_checker" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl"		t t "pltcl_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu"		f f "pltclu_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl"		t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu"		f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu"	f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u"	f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u"	f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" _null_ "$libdir/plpython3" _null_ ));
  
  #endif   /* PG_PLTEMPLATE_H */
*** ./src/include/commands/defrem.h.orig	2011-11-29 19:20:59.486116871 +0100
--- ./src/include/commands/defrem.h	2011-11-29 19:21:24.519804556 +0100
***************
*** 62,67 ****
--- 62,68 ----
  /* commands/functioncmds.c */
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
  extern void RemoveFunctionById(Oid funcOid);
+ extern void CheckFunction(CheckFunctionStmt *stmt);
  extern void SetFunctionReturnType(Oid funcOid, Oid newRetType);
  extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType);
  extern void RenameFunction(List *name, List *argtypes, const char *newname);
*** ./src/include/nodes/nodes.h.orig	2011-11-29 19:20:59.487116858 +0100
--- ./src/include/nodes/nodes.h	2011-11-29 19:21:24.521804532 +0100
***************
*** 291,296 ****
--- 291,297 ----
  	T_IndexStmt,
  	T_CreateFunctionStmt,
  	T_AlterFunctionStmt,
+ 	T_CheckFunctionStmt,
  	T_DoStmt,
  	T_RenameStmt,
  	T_RuleStmt,
*** ./src/include/nodes/parsenodes.h.orig	2011-11-29 19:20:59.489116833 +0100
--- ./src/include/nodes/parsenodes.h	2011-11-29 19:21:24.523804506 +0100
***************
*** 1734,1739 ****
--- 1734,1740 ----
  	List	   *plhandler;		/* PL call handler function (qual. name) */
  	List	   *plinline;		/* optional inline function (qual. name) */
  	List	   *plvalidator;	/* optional validator function (qual. name) */
+ 	List	   *plchecker;		/* optional checker function (qual. name) */
  	bool		pltrusted;		/* PL is trusted */
  } CreatePLangStmt;
  
***************
*** 2077,2082 ****
--- 2078,2096 ----
  } AlterFunctionStmt;
  
  /* ----------------------
+  *		Check {Function|Trigger} Statement
+  * ----------------------
+  */
+ typedef struct CheckFunctionStmt
+ {
+ 	NodeTag		type;
+ 	List	   *funcname;			/* qualified name of checked object */
+ 	List	   *args;			/* types of the arguments */
+ 	char	   *trgname;			/* trigger's name */
+ 	RangeVar	*relation;		/* trigger's relation */
+ } CheckFunctionStmt;
+ 
+ /* ----------------------
   *		DO Statement
   *
   * DoStmt is the raw parser output, InlineCodeBlock is the execution-time API
*** ./src/pl/plpgsql/src/pl_comp.c.orig	2011-11-29 19:09:03.000000000 +0100
--- ./src/pl/plpgsql/src/pl_comp.c	2011-11-29 19:42:43.058753779 +0100
***************
*** 115,121 ****
  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.
--- 115,120 ----
***************
*** 175,181 ****
  			 * 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
--- 174,180 ----
  			 * 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
***************
*** 2426,2432 ****
  }
  
  /*
!  * 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
--- 2425,2431 ----
  }
  
  /*
!  * 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,2446 ****
   * 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);
--- 2438,2445 ----
   * 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);
*** ./src/pl/plpgsql/src/pl_exec.c.orig	2011-11-29 19:09:03.316459122 +0100
--- ./src/pl/plpgsql/src/pl_exec.c	2011-11-29 19:37:19.000000000 +0100
***************
*** 210,216 ****
  static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
  						  PLpgSQL_expr *dynquery, List *params,
  						  const char *portalname, int cursorOptions);
! 
  
  /* ----------
   * plpgsql_exec_function	Called by the call handler for
--- 210,228 ----
  static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
  						  PLpgSQL_expr *dynquery, List *params,
  						  const char *portalname, int cursorOptions);
! static void check_row_or_rec(PLpgSQL_execstate *estate, PLpgSQL_row *row, PLpgSQL_rec *rec);
! static void check_expr(PLpgSQL_execstate *estate, PLpgSQL_expr *expr);
! static void assign_tupdesc_row_or_rec(PLpgSQL_execstate *estate,
! 					    PLpgSQL_row *row, PLpgSQL_rec *rec,
! 								    TupleDesc tupdesc);
! static TupleDesc expr_get_desc(PLpgSQL_execstate *estate,
! 						PLpgSQL_expr *query,
! 							bool use_element_type,
! 							bool expand_record,
! 								bool is_expression);
! static void var_init_to_null(PLpgSQL_execstate *estate, int varno);
! static void check_stmts(PLpgSQL_execstate *estate, List *stmts);
! static void check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt);
  
  /* ----------
   * plpgsql_exec_function	Called by the call handler for
***************
*** 6176,6178 ****
--- 6188,7242 ----
  
  	return portal;
  }
+ 
+ /*
+  * Following code ensures a CHECK FUNCTION and CHECK TRIGGER statements for PL/pgSQL
+  *
+  */
+ 
+ /*
+  * append a CONTEXT to error message
+  */
+ static void
+ check_error_callback(void *arg)
+ {
+ 	PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;
+ 
+ 	if (estate->err_stmt != NULL)
+ 	{
+ 		/* translator: last %s is a plpgsql statement type name */
+ 		errcontext("checking of PL/pgSQL function \"%s\" line %d at %s",
+ 				   estate->func->fn_name,
+ 				   estate->err_stmt->lineno,
+ 				   plpgsql_stmt_typename(estate->err_stmt));
+ 	}
+ 	else
+ 		errcontext("checking of PL/pgSQL function \"%s\"",
+ 				   estate->func->fn_name);
+ }
+ 
+ /*
+  * Check function - it prepare variables and starts a prepare plan walker
+  *		called by function checker
+  */
+ void
+ plpgsql_check_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
+ {
+ 	PLpgSQL_execstate estate;
+ 	ErrorContextCallback plerrcontext;
+ 	int			i;
+ 
+ 	/* Setup error callback for ereport */
+ 	plerrcontext.callback = check_error_callback;
+ 	plerrcontext.arg = &estate;
+ 	plerrcontext.previous = error_context_stack;
+ 	error_context_stack = &plerrcontext;
+ 
+ 	/*
+ 	 * Setup the execution state - we would to reuse some exec routines
+ 	 * so we need a estate
+ 	 */
+ 	plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo);
+ 
+ 	/*
+ 	 * Make local execution copies of all the datums
+ 	 */
+ 	for (i = 0; i < estate.ndatums; i++)
+ 		estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
+ 
+ 	/*
+ 	 * Store the actual call argument values into the appropriate variables
+ 	 */
+ 	for (i = 0; i < func->fn_nargs; i++)
+ 	{
+ 		int			n = func->fn_argvarnos[i];
+ 
+ 		switch (estate.datums[n]->dtype)
+ 		{
+ 			case PLPGSQL_DTYPE_VAR:
+ 				{
+ 					var_init_to_null(&estate, n);
+ 				}
+ 				break;
+ 
+ 			case PLPGSQL_DTYPE_ROW:
+ 				{
+ 					PLpgSQL_row *row = (PLpgSQL_row *) estate.datums[n];
+ 
+ 					exec_move_row(&estate, NULL, row, NULL, NULL);
+ 				}
+ 				break;
+ 
+ 			default:
+ 				elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(&estate, (PLpgSQL_stmt *) func->action);
+ 
+ 	/* Cleanup temporary memory */
+ 	plpgsql_destroy_econtext(&estate);
+ 
+ 	/* Pop the error context stack */
+ 	error_context_stack = plerrcontext.previous;
+ }
+ 
+ /*
+  * Check trigger - prepare fake environments for testing trigger
+  *
+  */
+ void
+ plpgsql_check_trigger(PLpgSQL_function *func,
+ 					 TriggerData *trigdata)
+ {
+ 	PLpgSQL_execstate estate;
+ 	ErrorContextCallback plerrcontext;
+ 	PLpgSQL_rec *rec_new,
+ 			   *rec_old;
+ 	int			i;
+ 
+ 	/* Setup error callback for ereport */
+ 	plerrcontext.callback = check_error_callback;
+ 	plerrcontext.arg = &estate;
+ 	plerrcontext.previous = error_context_stack;
+ 	error_context_stack = &plerrcontext;
+ 
+ 	/*
+ 	 * Setup the execution state - we would to reuse some exec routines
+ 	 * so we need a estate
+ 	 */
+ 	plpgsql_estate_setup(&estate, func, NULL);
+ 
+ 	/*
+ 	 * Make local execution copies of all the datums
+ 	 */
+ 	for (i = 0; i < estate.ndatums; i++)
+ 		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 *) (estate.datums[func->new_varno]);
+ 	rec_new->freetup = false;
+ 	rec_new->freetupdesc = false;
+ 	assign_tupdesc_row_or_rec(&estate, NULL, rec_new, trigdata->tg_relation->rd_att);
+ 
+ 	rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
+ 	rec_old->freetup = false;
+ 	rec_old->freetupdesc = false;
+ 	assign_tupdesc_row_or_rec(&estate, NULL, rec_old, trigdata->tg_relation->rd_att);
+ 
+ 	/*
+ 	 * Assign the special tg_ variables
+ 	 */
+ 	var_init_to_null(&estate, func->tg_op_varno);
+ 	var_init_to_null(&estate, func->tg_name_varno);
+ 	var_init_to_null(&estate, func->tg_when_varno);
+ 	var_init_to_null(&estate, func->tg_level_varno);
+ 	var_init_to_null(&estate, func->tg_relid_varno);
+ 	var_init_to_null(&estate, func->tg_relname_varno);
+ 	var_init_to_null(&estate, func->tg_table_name_varno);
+ 	var_init_to_null(&estate, func->tg_table_schema_varno);
+ 	var_init_to_null(&estate, func->tg_nargs_varno);
+ 	var_init_to_null(&estate, func->tg_argv_varno);
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(&estate, (PLpgSQL_stmt *) func->action);
+ 
+ 	/* Cleanup temporary memory */
+ 	plpgsql_destroy_econtext(&estate);
+ 
+ 	/* Pop the error context stack */
+ 	error_context_stack = plerrcontext.previous;
+ }
+ 
+ /*
+  * Verify lvalue
+  *    It doesn't repeat a checks that are done.
+  *  Checks a subscript expressions, verify a validity of record's fields
+  */
+ static void
+ check_target(PLpgSQL_execstate *estate, int varno)
+ {
+ 	PLpgSQL_datum *target = estate->datums[varno];
+ 
+ 	switch (target->dtype)
+ 	{
+ 		case PLPGSQL_DTYPE_VAR:
+ 		case PLPGSQL_DTYPE_REC:
+ 			break;
+ 
+ 		case PLPGSQL_DTYPE_ROW:
+ 			check_row_or_rec(estate, (PLpgSQL_row *) target, NULL);
+ 			break;
+ 
+ 		case PLPGSQL_DTYPE_RECFIELD:
+ 			{
+ 				PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target;
+ 				PLpgSQL_rec *rec;
+ 				int			fno;
+ 
+ 				rec = (PLpgSQL_rec *) (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)));
+ 
+ 					check_expr(estate, arrayelem->subscript);
+ 
+ 					target = 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(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_execstate *estate, 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(estate, row->varnos[fnum]);
+ 		}
+ 	}
+ }
+ 
+ /*
+  * Generate a prepared plan - this is simplyfied copy from pl_exec.c
+  *   Is not necessary to check simple plan
+  */
+ static void
+ prepare_expr(PLpgSQL_execstate *estate,
+ 				  PLpgSQL_expr *expr, int cursorOptions)
+ {
+ 	SPIPlanPtr	plan;
+ 
+ 	/* leave when there are not expression */
+ 	if (expr == NULL)
+ 		return;
+ 
+ 	/* leave when plan is created */
+ 	if (expr->plan != NULL)
+ 		return;
+ 
+ 	/*
+ 	 * 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 = 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")));
+ 			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.")));
+ 			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_execstate *estate, PLpgSQL_expr *expr)
+ {
+ 	TupleDesc tupdesc;
+ 
+ 	if (expr != NULL)
+ 	{
+ 		prepare_expr(estate, expr, 0);
+ 		tupdesc = expr_get_desc(estate, expr, false, false, true);
+ 		ReleaseTupleDesc(tupdesc);
+ 	}
+ }
+ 
+ /*
+  * 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_execstate *estate,
+ 					    PLpgSQL_row *row, PLpgSQL_rec *rec,
+ 								    TupleDesc tupdesc)
+ {
+ 	bool	   *nulls;
+ 	HeapTuple  tup;
+ 
+ 	if (tupdesc == NULL)
+ 		elog(ERROR, "tuple descriptor is empty");
+ 
+ 	/*
+ 	 * row variable has assigned TupleDesc already, so don't be processed
+ 	 * here
+ 	 */
+ 	if (rec != NULL)
+ 	{
+ 		PLpgSQL_rec *target = (PLpgSQL_rec *)(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_execstate *estate, int varno, TupleDesc tupdesc)
+ {
+ 	PLpgSQL_datum *target = estate->datums[varno];
+ 
+ 	if (target->dtype == PLPGSQL_DTYPE_REC)
+ 		assign_tupdesc_row_or_rec(estate, NULL, (PLpgSQL_rec *) target, tupdesc);
+ }
+ 
+ /*
+  * Returns a tuple descriptor based on existing plan
+  */
+ static TupleDesc 
+ expr_get_desc(PLpgSQL_execstate *estate,
+ 						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);
+ 
+ 		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 (tupdesc->natts != 1)
+ 			ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 				 errmsg_plural("query \"%s\" returned %d column",
+ 							   "query \"%s\" returned %d columns",
+ 							   tupdesc->natts,
+ 							   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))));
+ 
+ 		/* 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
+ 			elog(ERROR, "cannot to identify real type for record type variable");
+ 	}
+ 
+ 	if (is_expression && tupdesc->natts != 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 			  errmsg_plural("query \"%s\" returned %d column",
+ 				   "query \"%s\" returned %d columns",
+ 				   tupdesc->natts,
+ 				   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(..)
+ 	 */
+ 	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)
+ 						elog(ERROR, "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
+  */
+ static void
+ check_stmts(PLpgSQL_execstate *estate, List *stmts)
+ {
+ 	ListCell *lc;
+ 
+ 	foreach(lc, stmts)
+ 	{
+ 		check_stmt(estate, (PLpgSQL_stmt *) lfirst(lc));
+ 	}
+ }
+ 
+ /*
+  * walk over all statements
+  */
+ static void
+ check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
+ {
+ 	TupleDesc	tupdesc = NULL;
+ 	PLpgSQL_function *func;
+ 	ListCell *l;
+ 
+ 	if (stmt == NULL)
+ 		return;
+ 
+ 	estate->err_stmt = stmt;
+ 	func = estate->func;
+ 
+ 	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++)
+ 				{
+ 					d = func->datums[stmt_block->initvarnos[i]];
+ 
+ 					if (d->dtype == PLPGSQL_DTYPE_VAR)
+ 					{
+ 						PLpgSQL_var *var = (PLpgSQL_var *) d;
+ 
+ 						check_expr(estate, var->default_val);
+ 					}
+ 				}
+ 
+ 				check_stmts(estate, stmt_block->body);
+ 				
+ 				if (stmt_block->exceptions)
+ 				{
+ 					foreach(l, stmt_block->exceptions->exc_list)
+ 					{
+ 						check_stmts(estate, ((PLpgSQL_exception *) lfirst(l))->action);
+ 					}
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_ASSIGN:
+ 			{
+ 				PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt;
+ 
+ 				/* prepare plan if desn't exist yet */
+ 				prepare_expr(estate, stmt_assign->expr, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_assign->expr,
+ 										false,		/* no element type */
+ 										true,		/* expand record */
+ 										true);		/* is expression */
+ 
+ 				/* check target, ensure target can get a result */
+ 				check_target(estate, stmt_assign->varno);
+ 
+ 				/* assign a tupdesc to record variable */
+ 				assign_tupdesc_dno(estate, stmt_assign->varno, tupdesc);
+ 				ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_IF:
+ 			{
+ 				PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt;
+ 				ListCell *l;
+ 
+ 				check_expr(estate, stmt_if->cond);
+ 
+ 				check_stmts(estate, stmt_if->then_body);
+ 
+ 				foreach(l, stmt_if->elsif_list)
+ 				{
+ 					PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l);
+ 
+ 					check_expr(estate, elif->cond);
+ 					check_stmts(estate, elif->stmts);
+ 				}
+ 
+ 				check_stmts(estate, 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 *) estate->datums[stmt_case->t_varno];
+ 
+ 					/* we need to set hidden variable type */
+ 					prepare_expr(estate, stmt_case->t_expr, 0);
+ 
+ 					tupdesc = expr_get_desc(estate,
+ 									stmt_case->t_expr,
+ 										false,		/* no element type */
+ 										false,		/* 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,
+ 												   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(estate, cwt->expr);
+ 					check_stmts(estate, cwt->stmts);
+ 				}
+ 
+ 				check_stmts(estate, stmt_case->else_stmts);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_LOOP:
+ 			check_stmts(estate, ((PLpgSQL_stmt_loop *) stmt)->body);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_WHILE:
+ 			{
+ 				PLpgSQL_stmt_while *stmt_while = (PLpgSQL_stmt_while *) stmt;
+ 
+ 				check_expr(estate, stmt_while->cond);
+ 				check_stmts(estate, stmt_while->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORI:
+ 			{
+ 				PLpgSQL_stmt_fori *stmt_fori = (PLpgSQL_stmt_fori *) stmt;
+ 
+ 				check_expr(estate, stmt_fori->lower);
+ 				check_expr(estate, stmt_fori->upper);
+ 				check_expr(estate, stmt_fori->step);
+ 
+ 				check_stmts(estate, stmt_fori->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORS:
+ 			{
+ 				PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt;
+ 
+ 				/* we need to set hidden variable type */
+ 				prepare_expr(estate, stmt_fors->query, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_fors->query,
+ 									false,		/* no element type */
+ 									false,		/* expand record */
+ 									false);		/* is expression */
+ 
+ 				check_row_or_rec(estate, stmt_fors->row, stmt_fors->rec);
+ 				assign_tupdesc_row_or_rec(estate, stmt_fors->row, stmt_fors->rec, tupdesc);
+ 
+ 				check_stmts(estate, stmt_fors->body);
+ 				ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORC:
+ 			{
+ 				PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt;
+ 				PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_forc->curvar];
+ 
+ 				prepare_expr(estate, stmt_forc->argquery, 0);
+ 
+ 				if (var->cursor_explicit_expr != NULL)
+ 				{
+ 					prepare_expr(estate, var->cursor_explicit_expr,
+ 								    var->cursor_options);
+ 
+ 					tupdesc = expr_get_desc(estate,
+ 									var->cursor_explicit_expr,
+ 										false,		/* no element type */
+ 										false,		/* expand record */
+ 										false);		/* is expression */
+ 
+ 					check_row_or_rec(estate, stmt_forc->row, stmt_forc->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_forc->row, stmt_forc->rec, tupdesc);
+ 				}
+ 
+ 				check_stmts(estate, stmt_forc->body);
+ 				if (tupdesc != NULL)
+ 					ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNFORS:
+ 			{
+ 				PLpgSQL_stmt_dynfors * stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt;
+ 
+ 				if (stmt_dynfors->rec != NULL)
+ 					elog(ERROR, "cannot determinate a result of dynamic SQL");
+ 
+ 				check_expr(estate, stmt_dynfors->query);
+ 
+ 				foreach(l, stmt_dynfors->params)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				check_stmts(estate, stmt_dynfors->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FOREACH_A:
+ 			{
+ 				PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt;
+ 
+ 				prepare_expr(estate, stmt_foreach_a->expr, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_foreach_a->expr,
+ 									true,		/* no element type */
+ 									false,		/* expand record */
+ 									true);		/* is expression */
+ 
+ 				check_target(estate, stmt_foreach_a->varno);
+ 				assign_tupdesc_dno(estate, stmt_foreach_a->varno, tupdesc);
+ 				ReleaseTupleDesc(tupdesc);
+ 
+ 				check_stmts(estate, stmt_foreach_a->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_EXIT:
+ 			check_expr(estate, ((PLpgSQL_stmt_exit *) stmt)->cond);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_PERFORM:
+ 			prepare_expr(estate, ((PLpgSQL_stmt_perform *) stmt)->expr, 0);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN:
+ 			check_expr(estate, ((PLpgSQL_stmt_return *) stmt)->expr);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN_NEXT:
+ 			check_expr(estate, ((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(estate, stmt_rq->dynquery);
+ 				prepare_expr(estate, stmt_rq->query, 0);
+ 
+ 				foreach(l, stmt_rq->params)
+ 				{
+ 					check_expr(estate, (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(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				foreach(l, stmt_raise->options)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				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;
+ 
+ 				prepare_expr(estate, stmt_execsql->sqlstmt, 0);
+ 				if (stmt_execsql->into)
+ 				{
+ 					tupdesc = expr_get_desc(estate,
+ 								stmt_execsql->sqlstmt,
+ 											false,		/* no element type */
+ 											false,		/* expand record */
+ 											false);		/* is expression */
+ 
+ 					/* check target, ensure target can get a result */
+ 					check_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec, tupdesc);
+ 					ReleaseTupleDesc(tupdesc);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNEXECUTE:
+ 			{
+ 				PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt;
+ 
+ 				check_expr(estate, stmt_dynexecute->query);
+ 
+ 				foreach(l, stmt_dynexecute->params)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				if (stmt_dynexecute->into)
+ 				{
+ 					if (stmt_dynexecute->rec != NULL)
+ 						elog(ERROR, "cannot determinate a result of dynamic SQL");
+ 
+ 					check_row_or_rec(estate, stmt_dynexecute->row, stmt_dynexecute->rec);
+ 				}
+ 			}
+ 			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)
+ 					prepare_expr(estate, var->cursor_explicit_expr,
+ 								   var->cursor_options);
+ 
+ 				prepare_expr(estate, stmt_open->query, 0);
+ 				prepare_expr(estate, stmt_open->argquery, 0);
+ 				check_expr(estate, stmt_open->dynquery);
+ 
+ 				foreach(l, stmt_open->params)
+ 				{
+ 					check_expr(estate, (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(estate, diag_item->target);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FETCH:
+ 			{
+ 				PLpgSQL_stmt_fetch *stmt_fetch = (PLpgSQL_stmt_fetch *) stmt;
+ 				PLpgSQL_var *var = (PLpgSQL_var *)(estate->datums[stmt_fetch->curvar]);
+ 
+ 				if (var != NULL && var->cursor_explicit_expr != NULL)
+ 				{
+ 					prepare_expr(estate, var->cursor_explicit_expr, 
+ 									    var->cursor_options);
+ 					tupdesc = expr_get_desc(estate,
+ 								    var->cursor_explicit_expr,
+ 											false,		/* no element type */
+ 											false,		/* expand record */
+ 											false);		/* is expression */
+ 					check_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec, tupdesc);
+ 					ReleaseTupleDesc(tupdesc);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_CLOSE:
+ 			break;
+ 
+ 		default:
+ 			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
+ 			return; /* be compiler quite */
+ 	}
+ }
+ 
+ /*
+  * Initialize variable to NULL
+  */
+ static void
+ var_init_to_null(PLpgSQL_execstate *estate, int varno)
+ {
+ 	PLpgSQL_var *var = (PLpgSQL_var *) estate->datums[varno];
+ 	var->value = (Datum) 0;
+ 	var->isnull = true;
+ 	var->freeval = false;
+ }
*** ./src/pl/plpgsql/src/pl_handler.c.orig	2011-11-29 19:20:59.494116771 +0100
--- ./src/pl/plpgsql/src/pl_handler.c	2011-11-29 19:21:24.529804431 +0100
***************
*** 312,314 ****
--- 312,452 ----
  
  	PG_RETURN_VOID();
  }
+ 
+ /* ----------
+  * plpgsql_checker
+  *
+  * This function attempts to check a embeded SQL inside a PL/pgSQL function at
+  * CHECK FUNCTION time. It should to have one or two parameters. Second
+  * parameter is a relation (used when function is trigger).
+  * ----------
+  */
+ PG_FUNCTION_INFO_V1(plpgsql_checker);
+ 
+ Datum
+ plpgsql_checker(PG_FUNCTION_ARGS)
+ {
+ 	Oid			funcoid = PG_GETARG_OID(0);
+ 	Oid			relid = PG_GETARG_OID(1);
+ 	HeapTuple	tuple;
+ 	FunctionCallInfoData fake_fcinfo;
+ 	FmgrInfo	flinfo;
+ 	TriggerData trigdata;
+ 	int			rc;
+ 	PLpgSQL_function *function;
+ 	PLpgSQL_execstate *cur_estate;
+ 
+ 	Form_pg_proc proc;
+ 	char		functyptype;
+ 	bool	   istrigger = false;
+ 
+ 	/* we don't need to repair a check done by validator */
+ 
+ 	tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
+ 	if (!HeapTupleIsValid(tuple))
+ 		elog(ERROR, "cache lookup failed for function %u", funcoid);
+ 	proc = (Form_pg_proc) GETSTRUCT(tuple);
+ 
+ 	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))
+ 		{
+ 			istrigger = true;
+ 			if (!OidIsValid(relid))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("PL/pgSQL trigger functions cannot be checked directly"),
+ 						 errhint("use CHECK TRIGGER statement instead")));
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Connect to SPI manager
+ 	 */
+ 	if ((rc = SPI_connect()) != SPI_OK_CONNECT)
+ 			elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
+ 
+ 	/*
+ 	 * Set up a fake fcinfo with just enough info to satisfy
+ 	 * plpgsql_compile().
+ 	 *
+ 	 * there should be a different real argtypes for polymorphic params
+ 	 */
+ 	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;
+ 		trigdata.tg_relation = relation_open(relid, AccessShareLock);
+ 		fake_fcinfo.context = (Node *) &trigdata;
+ 	}
+ 
+ 	/* Get a compiled function */
+ 	function = plpgsql_compile(&fake_fcinfo, false);
+ 
+ 	/* 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 prepare plans */
+ 	PG_TRY();
+ 	{
+ 		if (!istrigger)
+ 			plpgsql_check_function(function, &fake_fcinfo);
+ 		else
+ 			plpgsql_check_trigger(function, &trigdata);
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		if (istrigger)
+ 			relation_close(trigdata.tg_relation, AccessShareLock);
+ 
+ 		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);
+ 
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	if (istrigger)
+ 		relation_close(trigdata.tg_relation, AccessShareLock);
+ 
+ 	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);
+ 
+ 	/*
+ 	 * Disconnect from SPI manager
+ 	 */
+ 	if ((rc = SPI_finish()) != SPI_OK_FINISH)
+ 			elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
+ 
+ 	ReleaseSysCache(tuple);
+ 
+ 	PG_RETURN_VOID();
+ }
*** ./src/pl/plpgsql/src/plpgsql.h.orig	2011-11-29 19:20:59.500116698 +0100
--- ./src/pl/plpgsql/src/plpgsql.h	2011-11-29 20:22:19.423516596 +0100
***************
*** 902,907 ****
--- 902,908 ----
  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,916 ****
--- 912,918 ----
  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_checker(PG_FUNCTION_ARGS);
  
  /* ----------
   * Functions in pl_exec.c
***************
*** 928,933 ****
--- 930,939 ----
  extern void exec_get_datum_type_info(PLpgSQL_execstate *estate,
  						 PLpgSQL_datum *datum,
  						 Oid *typeid, int32 *typmod, Oid *collation);
+ extern void plpgsql_check_function(PLpgSQL_function *func,
+ 					 FunctionCallInfo fcinfo);
+ extern void plpgsql_check_trigger(PLpgSQL_function *func,
+ 					 TriggerData *trigdata);
  
  /* ----------
   * Functions for namespace handling in pl_funcs.c
*** ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql.orig	2011-11-29 19:20:59.502116672 +0100
--- ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql	2011-11-29 19:21:24.533804381 +0100
***************
*** 5,7 ****
--- 5,8 ----
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_call_handler();
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_inline_handler(internal);
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_validator(oid);
+ ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_checker(oid, regclass);
*** ./src/test/regress/expected/plpgsql.out.orig	2011-11-29 19:20:59.505116634 +0100
--- ./src/test/regress/expected/plpgsql.out	2011-11-29 19:21:24.536804342 +0100
***************
*** 302,307 ****
--- 302,310 ----
  ' 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
+ check trigger tg_hslot_biu on HSlot;
+ NOTICE:  checking function "tg_hslot_biu()"
  -- ************************************************************
  -- * BEFORE DELETE on HSlot
  -- *	- prevent from manual manipulation
***************
*** 635,640 ****
--- 638,645 ----
      raise exception ''illegal backlink beginning with %'', mytype;
  end;
  ' language plpgsql;
+ -- check function should not fail
+ check function tg_backlink_set(bpchar, bpchar);
  -- ************************************************************
  -- * Support function to clear out the backlink field if
  -- * it still points to specific slot
***************
*** 2802,2807 ****
--- 2807,2840 ----
   
  (1 row)
  
+ -- check function should not fail
+ check function for_vect();
+ -- 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
***************
*** 3283,3288 ****
--- 3316,3323 ----
    return;
  end;
  $$ language plpgsql;
+ -- check function should not fail
+ check function forc01();
  select forc01();
  NOTICE:  5 from c
  NOTICE:  6 from c
***************
*** 3716,3721 ****
--- 3751,3758 ----
    end case;
  end;
  $$ language plpgsql immutable;
+ -- check function should not fail
+ check function case_test(bigint);
  select case_test(1);
   case_test 
  -----------
***************
*** 4571,4573 ****
--- 4608,4942 ----
  CONTEXT:  PL/pgSQL function "testoa" line 5 at assignment
  drop function arrayassign1();
  drop function testoa(x1 int, x2 int, x3 int);
+ --
+ -- check function statement tests
+ --
+ create table t1(a int, b int);
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+ end;
+ $$ language plpgsql;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ check function f1();
+ ERROR:  column "c" of relation "t1" does not exist
+ LINE 1: update t1 set c = 30
+                       ^
+ QUERY:  update t1 set c = 30
+ CONTEXT:  checking of PL/pgSQL function "f1" line 4 at SQL statement
+ 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)
+ 
+ check function f1();
+ ERROR:  record "r" has no field "c"
+ CONTEXT:  SQL statement "SELECT r.c"
+ checking of PL/pgSQL function "f1" line 6 at RAISE
+ 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)
+ 
+ check function f1();
+ ERROR:  record "r" has no field "c"
+ CONTEXT:  SQL statement "SELECT r.c"
+ checking of PL/pgSQL function "f1" line 6 at RAISE
+ 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)
+ 
+ check function f1();
+ ERROR:  record "r" has no field "c"
+ CONTEXT:  checking of PL/pgSQL function "f1" line 6 at assignment
+ 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)
+ 
+ check function f1();
+ ERROR:  column "a" does not exist
+ LINE 1: SELECT a + b
+                ^
+ QUERY:  SELECT a + b
+ CONTEXT:  checking of PL/pgSQL function "f1" line 5 at assignment
+ 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)
+ 
+ check function f1();
+ ERROR:  too many parameters specified for RAISE
+ CONTEXT:  checking of PL/pgSQL function "f1" line 4 at RAISE
+ 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)
+ 
+ check function f1();
+ ERROR:  too few parameters specified for RAISE
+ CONTEXT:  checking of PL/pgSQL function "f1" line 4 at RAISE
+ 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)
+ 
+ check function f1();
+ ERROR:  column "c" does not exist
+ LINE 1: SELECT c+10
+                ^
+ QUERY:  SELECT c+10
+ CONTEXT:  checking of PL/pgSQL function "f1" line 5 at assignment
+ 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;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ check function f1();
+ ERROR:  subscripted object is not an array
+ CONTEXT:  checking of PL/pgSQL function "f1" line 5 at assignment
+ 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)
+ 
+ check function f1();
+ ERROR:  record "_exception" has no field "hint"
+ CONTEXT:  checking of PL/pgSQL function "f1" line 7 at GET DIAGNOSTICS
+ 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);
+ check trigger t1_f1 on t1;
+ NOTICE:  checking function "f1_trg()"
+ ERROR:  record "new" has no field "c"
+ CONTEXT:  SQL statement "SELECT new.c"
+ checking of PL/pgSQL function "f1_trg" line 5 at RAISE
+ 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
+ check trigger t1_f1 on t1;
+ NOTICE:  checking function "f1_trg()"
+ ERROR:  record "new" has no field "c"
+ CONTEXT:  checking of PL/pgSQL function "f1_trg" line 5 at assignment
+ -- 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
+ check trigger t1_f1 on t1;
+ NOTICE:  checking function "f1_trg()"
+ -- ok
+ insert into t1 values(6,30);
+ drop table t1;
+ drop type _exception_type;
+ drop function f1_trg();
*** ./src/test/regress/sql/plpgsql.sql.orig	2011-11-29 19:20:59.508116598 +0100
--- ./src/test/regress/sql/plpgsql.sql	2011-11-29 19:21:24.538804318 +0100
***************
*** 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
+ check trigger tg_hslot_biu on HSlot;
  
  -- ************************************************************
  -- * BEFORE DELETE on HSlot
***************
*** 747,752 ****
--- 749,757 ----
  end;
  ' language plpgsql;
  
+ -- check function should not fail
+ check function tg_backlink_set(bpchar, bpchar);
+ 
  
  -- ************************************************************
  -- * Support function to clear out the backlink field if
***************
*** 2335,2340 ****
--- 2340,2352 ----
  
  select for_vect();
  
+ -- check function should not fail
+ 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
***************
*** 2714,2719 ****
--- 2726,2734 ----
  end;
  $$ language plpgsql;
  
+ -- check function should not fail
+ check function forc01();
+ 
  select forc01();
  
  -- try updating the cursor's current row
***************
*** 3048,3053 ****
--- 3063,3071 ----
  end;
  $$ language plpgsql immutable;
  
+ -- check function should not fail
+ check function case_test(bigint);
+ 
  select case_test(1);
  select case_test(2);
  select case_test(3);
***************
*** 3600,3602 ****
--- 3618,3862 ----
  
  drop function arrayassign1();
  drop function testoa(x1 int, x2 int, x3 int);
+ 
+ --
+ -- check function statement tests
+ --
+ 
+ create table t1(a int, b int);
+ 
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+ end;
+ $$ language plpgsql;
+ 
+ select f1();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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;
+ 
+ select f1();
+ 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();
+ 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);
+ check trigger t1_f1 on 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
+ check trigger t1_f1 on 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
+ check trigger t1_f1 on t1;
+ 
+ -- ok
+ insert into t1 values(6,30);
+ 
+ drop table t1;
+ drop type _exception_type;
+ 
+ drop function f1_trg();
+ 
#8Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#7)
Re: review: CHECK FUNCTION statement

2011/11/29 Pavel Stehule <pavel.stehule@gmail.com>:

Hello

updated patch:

* recheck compilation and initdb
* working routines moved to pl_exec.c
* add entry to catalog.sgml about lanchecker field
* add node's utils

+ pg_dump support

Pavel

Show quoted text

Regards

Pavel Stehule

2011/11/29 Albe Laurenz <laurenz.albe@wien.gv.at>:

Pavel Stehule wrote:

I am sending updated patch, that implements a CHECK FUNCTION and CHECK
TRIGGER statements.

This patch is significantly redesigned to previous version (PL/pgSQL
part) - it is more readable, more accurate. There are new regress
tests.

Please, can some English native speaker fix doc and comments?

ToDo:

CHECK FUNCTION search function according to function signature - it
should be changes for using a actual types - it can be solution for
polymorphic types and useful tool for work with overloaded functions -
when is not clean, that function was executed.

check function foo(int, int);
NOTICE: checking function foo(variadic anyarray)
...

and maybe some support for named parameters
check function foo(name text, surname text);
NOTICE: checking function foo(text, text, text, text)
...

I think that CHECK FUNCTION should work exactly like DROP FUNCTION
in these respects.

Submission review:
------------------

The patch is context diff, applies with some offsets, contains
regression tests and documentation.

The documentation should be expanded, the doc for CHECK FUNCTION
is only a stub. It should describe the procedure and what is checked.
That would also make reviewing easier.
I think that some documentation should be added to plhandler.sgml.
There is a spelling error (statemnt) in the docs.

Usability review:
-----------------

If I understand right, the goal of CHECK FUNCTION is to find errors in
the function definition without actually having to execute it.
The patch tries to provide this for PL/pgSQL.

There hasn't been any discussion on the list, the patch was just posted,
so I can't say that we want that. Tom added it to the commitfest page,
so there's one important voice against dismissing it right away :^)

I don't understand the functional difference between a "validator function"
and a "check function" as proposed by this patch. I am probably missing
something, but why couldn't these checks be added to function validation
when check_function_bodies is set?
A new "CHECK FUNCTION" statement could simply call the validator function.

I don't see any pg_dump support in this patch, and PL/pgSQL probably doesn't
need that, but I think pg_dump support for CREATE LANGUAGE would have to
be added for other PLs.

I can't test if the functionality is complete because I can't get it to
run (see below).

Feature test:
-------------

I can't really test the patch because initdb fails:

$ initdb -E UTF8 --locale=de_DE.UTF-8 --lc-messages=en_US.UTF-8 -U postgres /postgres/cvs/dbhome
The files belonging to this database system will be owned by user "laurenz".
This user must also own the server process.

The database cluster will be initialized with locales
 COLLATE:  de_DE.UTF-8
 CTYPE:    de_DE.UTF-8
 MESSAGES: en_US.UTF-8
 MONETARY: de_DE.UTF-8
 NUMERIC:  de_DE.UTF-8
 TIME:     de_DE.UTF-8
The default text search configuration will be set to "german".

creating directory /postgres/cvs/dbhome ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 32MB
creating configuration files ... ok
creating template1 database in /postgres/cvs/dbhome/base/1 ... ok
initializing pg_authid ... ok
initializing dependencies ... ok
creating system views ... ok
loading system objects' descriptions ... ok
creating collations ... ok
creating conversions ... ok
creating dictionaries ... ok
setting privileges on built-in objects ... ok
creating information schema ... ok
loading PL/pgSQL server-side language ... FATAL:  could not load library "/postgres/cvs/pg92/lib/plpgsql.so": /postgres/cvs/pg92/lib/plpgsql.so: undefined symbol: plpgsql_delete_function
STATEMENT:  CREATE EXTENSION plpgsql;

child process exited with exit code 1
initdb: removing data directory "/postgres/cvs/dbhome"

Coding review:
--------------

The patch compiles without warnings.
The comments in the code should be revised, they are bad English.
I can't say if there should be more of them -- I don't know this part of
the code well enough to have a well-founded opinion.

I don't think there are any portability issues, but I could not test it.

There are a lot of small changes to pl/plpgsql/src/pl_exec.c, are they all
necessary? For example, why was copy_plpgsql_datum renamed to
plpgsql_copy_datum?

I'll mark the patch as "Waiting on Author".

Yours,
Laurenz Albe

#9Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Pavel Stehule (#7)
Re: review: CHECK FUNCTION statement

Pavel Stehule wrote:

updated patch:

* recheck compilation and initdb
* working routines moved to pl_exec.c
* add entry to catalog.sgml about lanchecker field
* add node's utils

Documentation:
--------------

This patch has no documentation for CHECK FUNCTION or CHECK TRIGGER.
The last patch had at least something.
"\h check function" in psql does not show anything.

The patch should also add documentation about the handler function
to plhandler.sgml. In particular, the difference between the
validator function and the check function should be pointed out.

Usability:
----------

Do I understand right that the reason why the check function is
different from the validator function is that it would be more difficult
to add the checks to the validator function?

Is that a good enough argument? From a user's perspective it is
difficult to see why some checks are performed at function creation
time, while others have to be explicitly checked with CHECK FUNCTION.
I think it would be much more intuitive if CHECK FUNCTION does
the same as function validation with check_function_bodies on.

Submission, Compilation, Regression tests:
------------------------------------------

The patch applies and compiles fine and passes regression tests.
The tests cover the functionality.
"initdb" succeeds.

pg_dump:
--------

pg_dump support does not work right.
If I create a language like this:

CREATE LANGUAGE mylang HANDLER plpgsql_call_handler
INLINE plpgsql_inline_handler
VALIDATOR plpgsql_validator
CHECK plpgsql_checker;
the dump will contain:
CREATE OR REPLACE PROCEDURAL LANGUAGE mylang;

This is not a problem of this patch though (same in 9.1);
it seems that pg_dump support for languages without definition
in pg_pltemplate is broken in general.

Feature test:
-------------

CHECK FUNCTION and CHECK TRIGGER work, I couldn't crash it.

Error messages could be better:
CHECK TRIGGER atrigger;
ERROR: syntax error at or near ";"
LINE 1: CHECK TRIGGER atrigger;
^
Something like "expected keyword 'ON'" might be nice.

There are a lot of things that CHECK FUNCTION does not check, some
examples:

1)

CREATE OR REPLACE FUNCTION t(i integer) RETURNS integer
LANGUAGE plpgsql STRICT AS
$$DECLARE j integer;
BEGIN
IF i=1 THEN
FOR I IN 1..4 BY -1 LOOP
RAISE NOTICE '%', i;
END LOOP;
RETURN -1;
ELSE
RETURN 2*i;
END IF;
END;$$;

CHECK FUNCTION t(integer); -- no error

SELECT t(1);
ERROR: BY value of FOR loop must be greater than zero
CONTEXT: PL/pgSQL function "t" line 4 at FOR with integer loop variable

2)

CREATE OR REPLACE FUNCTION t(i integer) RETURNS integer
LANGUAGE plpgsql STRICT AS
$$DECLARE j integer;
BEGIN
IF i=1 THEN
j=9999999999999999999;
RETURN j;
ELSE
RETURN 2*i;
END IF;
END;$$;

CHECK FUNCTION t(integer); -- no error

SELECT t(1);
ERROR: value "9999999999999999999" is out of range for type integer
CONTEXT: PL/pgSQL function "t" line 4 at assignment

3)

CREATE OR REPLACE FUNCTION t(i integer) RETURNS integer
LANGUAGE plpgsql STRICT AS
$$DECLARE j atable;
BEGIN IF i=1 THEN
j=12;
RETURN j;
ELSE
RETURN 2*i;
END IF;
END;$$;

CHECK FUNCTION t(integer); -- no error

SELECT t(1);
ERROR: cannot assign non-composite value to a row variable
CONTEXT: PL/pgSQL function "t" line 3 at assignment

4)

CREATE TABLE atable(
id integer PRIMARY KEY,
val text NOT NULL
);

INSERT INTO atable VALUES (1, 'eins');

CREATE OR REPLACE FUNCTION atrigger() RETURNS trigger
LANGUAGE plpgsql STRICT AS
$$BEGIN
NEW.id=22;
RETURN NEW;
END;$$;

CREATE TRIGGER atrigger AFTER DELETE ON atable FOR EACH ROW
EXECUTE PROCEDURE atrigger();

CHECK TRIGGER atrigger ON atable; -- no error
NOTICE: checking function "atrigger()"

DELETE FROM atable;
ERROR: record "new" is not assigned yet
DETAIL: The tuple structure of a not-yet-assigned record is indeterminate.
CONTEXT: PL/pgSQL function "atrigger" line 2 at assignment

I can try and come up with more if desired.

Maybe case 2) and 4) cannot reasonably be covered.

It is probably very hard to check everything possible, but the
usefulness of CHECK FUNCTION is proportional to the number of
cases covered.

I'll mark the patch as "Waiting on Author" until there is more
documentation, I understand the answers to the questions
raised in "usability" above, and until it is agreed that the things
checked are sufficient.

Yours,
Laurenz Albe

#10Tom Lane
tgl@sss.pgh.pa.us
In reply to: Albe Laurenz (#9)
Re: review: CHECK FUNCTION statement

"Albe Laurenz" <laurenz.albe@wien.gv.at> writes:

Do I understand right that the reason why the check function is
different from the validator function is that it would be more difficult
to add the checks to the validator function?

Is that a good enough argument? From a user's perspective it is
difficult to see why some checks are performed at function creation
time, while others have to be explicitly checked with CHECK FUNCTION.
I think it would be much more intuitive if CHECK FUNCTION does
the same as function validation with check_function_bodies on.

I think the important point here is that we need to support more than
one level of validation, and that the higher levels can't really be
applied by default in CREATE FUNCTION because they may fail on perfectly
valid code.

The real reason why we need a separate check function is that the API
for validators doesn't include any parameter about check level.

It's conceivable that instead of adding a check-function entry point,
we could generalizee check_function_bodies into a more-than-2-level
setting, and expect validators to pay attention to the GUC's value
when deciding how aggressively to validate. However, it's far from
clear to me that that's a more usable definition than having a separate
CHECK FUNCTION command. An example of where a separate CHECK command
could come in handy is: you just did some ALTER TABLE commands, and now
you would like to check if your functions all still work with the
modified table schemas.

[ snip examples of some additional checks that could be made ]
It is probably very hard to check everything possible, but the
usefulness of CHECK FUNCTION is proportional to the number of
cases covered.

I don't think that the initial patch needs to check everything that
could conceivably be checked. We can always add more checking later.
The initial patch obviously has to create the infrastructure for
optional checking, and the specific check that Pavel wants to add
is to run parse analysis on each SQL statement in a plpgsql function.
That seems to me to be a well-defined and useful check, so I think the
scope of the patch is entirely adequate for now.

A bigger issue is that once you think about more than one kind of check,
it becomes apparent that we might need some user-specifiable options to
control which checks are applied. And I see no provision for that here.
This is not something we can add later, at least not without breaking
the API for the check function --- and if we're willing to break API,
why not just add some more parameters to the validator and avoid having
a second function?

On the whole, it might not be a bad idea to have two allowed signatures
for the validator function, rather than inventing an additional column
in pg_language. But the fundamental point IMHO is that there needs to
be a provision to pass language-dependent validation options to the
function, whether it's the existing validator or a separate checker
entry point.

regards, tom lane

#11Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#10)
Re: review: CHECK FUNCTION statement

On Wed, Nov 30, 2011 at 10:53 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

On the whole, it might not be a bad idea to have two allowed signatures
for the validator function, rather than inventing an additional column
in pg_language.  But the fundamental point IMHO is that there needs to
be a provision to pass language-dependent validation options to the
function, whether it's the existing validator or a separate checker
entry point.

Something like:

CHECK FUNCTION proname(proargs) WITH (...fdw-style elastic options...)

?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#12Pavel Stehule
pavel.stehule@gmail.com
In reply to: Albe Laurenz (#9)
Re: review: CHECK FUNCTION statement

Hello

CREATE OR REPLACE FUNCTION t(i integer) RETURNS integer
 LANGUAGE plpgsql STRICT AS
 $$DECLARE j integer;
   BEGIN
     IF i=1 THEN
       FOR I IN 1..4 BY -1 LOOP
          RAISE NOTICE '%', i;
       END LOOP;
       RETURN -1;
     ELSE
       RETURN 2*i;
     END IF;
   END;$$;

CHECK FUNCTION t(integer); -- no error

SELECT t(1);
ERROR:  BY value of FOR loop must be greater than zero
CONTEXT:  PL/pgSQL function "t" line 4 at FOR with integer loop variable

2)

CREATE OR REPLACE FUNCTION t(i integer) RETURNS integer
 LANGUAGE plpgsql STRICT AS
 $$DECLARE j integer;
   BEGIN
     IF i=1 THEN
       j=9999999999999999999;
       RETURN j;
     ELSE
       RETURN 2*i;
     END IF;
   END;$$;

CHECK FUNCTION t(integer); -- no error

SELECT t(1);
ERROR:  value "9999999999999999999" is out of range for type integer
CONTEXT:  PL/pgSQL function "t" line 4 at assignment

This kind of check are little bit difficult. It is solveable but I
would to have a skelet in core, and then this skelet can be enhanced
step by step.

Where is problem? PL/pgSQL usually don't work with numeric constant.
Almost all numbers are expressions - and checking function ensure only
semantic validity of expression, but don't try to evaluate expression.
So isn't possible to check runtime errors now.

Regards

Pavel

#13Alvaro Herrera
alvherre@commandprompt.com
In reply to: Tom Lane (#10)
Re: review: CHECK FUNCTION statement

Excerpts from Tom Lane's message of mié nov 30 12:53:42 -0300 2011:

A bigger issue is that once you think about more than one kind of check,
it becomes apparent that we might need some user-specifiable options to
control which checks are applied. And I see no provision for that here.
This is not something we can add later, at least not without breaking
the API for the check function --- and if we're willing to break API,
why not just add some more parameters to the validator and avoid having
a second function?

How about

CHECK (parse, names=off) FUNCTION foobar(a, b, c)

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#14Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#11)
Re: review: CHECK FUNCTION statement

Robert Haas <robertmhaas@gmail.com> writes:

On Wed, Nov 30, 2011 at 10:53 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

On the whole, it might not be a bad idea to have two allowed signatures
for the validator function, rather than inventing an additional column
in pg_language. �But the fundamental point IMHO is that there needs to
be a provision to pass language-dependent validation options to the
function, whether it's the existing validator or a separate checker
entry point.

Something like:
CHECK FUNCTION proname(proargs) WITH (...fdw-style elastic options...)

Great minds think alike ... that was pretty much exactly the syntax that
was in the back of my mind.

regards, tom lane

#15Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#13)
Re: review: CHECK FUNCTION statement

2011/11/30 Alvaro Herrera <alvherre@commandprompt.com>:

Excerpts from Tom Lane's message of mié nov 30 12:53:42 -0300 2011:

A bigger issue is that once you think about more than one kind of check,
it becomes apparent that we might need some user-specifiable options to
control which checks are applied.  And I see no provision for that here.
This is not something we can add later, at least not without breaking
the API for the check function --- and if we're willing to break API,
why not just add some more parameters to the validator and avoid having
a second function?

How about

CHECK (parse, names=off) FUNCTION foobar(a, b, c)

this syntax is relative consistent with EXPLAIN, is it ok for all?

Pavel

Show quoted text

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#16Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#15)
Re: review: CHECK FUNCTION statement

Pavel Stehule <pavel.stehule@gmail.com> writes:

2011/11/30 Alvaro Herrera <alvherre@commandprompt.com>:

How about
CHECK (parse, names=off) FUNCTION foobar(a, b, c)

this syntax is relative consistent with EXPLAIN, is it ok for all?

It seems pretty awkward to me, particularly putting the options before
the second keyword of the command --- that could bite us if we ever want
some other flavors of CHECK command. I prefer Robert's suggestion of a
WITH clause at the end.

regards, tom lane

#17Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#16)
Re: review: CHECK FUNCTION statement

2011/11/30 Tom Lane <tgl@sss.pgh.pa.us>:

Pavel Stehule <pavel.stehule@gmail.com> writes:

2011/11/30 Alvaro Herrera <alvherre@commandprompt.com>:

How about
CHECK (parse, names=off) FUNCTION foobar(a, b, c)

this syntax is relative consistent with EXPLAIN, is it ok for all?

It seems pretty awkward to me, particularly putting the options before
the second keyword of the command --- that could bite us if we ever want
some other flavors of CHECK command.  I prefer Robert's suggestion of a
WITH clause at the end.

we can provide both versions - as can be fine for people. Is is simple
in parser. I like both variants and I am thinking so much more
important is a API of checker function and behave of CHECK FUNCTION
statement.

Just idea - don't kill me :). Because CHECK FUNCTION is not
destructive , then complete signature is not necessary, and when
function name is unique, then parameters should be optional - it can
be comfortable for manual work, so just CHECK FUNCTION name; can work.
I see a usage for option - a entering parameter's values instead
signature. When I started with overloaded functions, sometimes I had a
problem with identification of function that was executed - CHECK
FUNCTION can help

CHECK FUNCTION name(10,20) WITH (values);
Notice: checking function name(int, int, int default 20)

I would to design API of checker function be friendly to direct call.
There was some ideas to design CHECK FUNCTION for possibility to check
all functions in schema or language. It should be, but we have a
inline statement and system catalog, so anybody can write own scripts
per your requests. And It was one point to decision for separate
checker function from validate function.

Regards

Pavel

Show quoted text

                       regards, tom lane

#18Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#17)
Re: review: CHECK FUNCTION statement

Pavel Stehule <pavel.stehule@gmail.com> writes:

2011/11/30 Tom Lane <tgl@sss.pgh.pa.us>:

It seems pretty awkward to me, particularly putting the options before
the second keyword of the command --- that could bite us if we ever want
some other flavors of CHECK command. I prefer Robert's suggestion of a
WITH clause at the end.

we can provide both versions - as can be fine for people. Is is simple
in parser. I like both variants and I am thinking so much more
important is a API of checker function and behave of CHECK FUNCTION
statement.

I think you missed my point: I don't want the options list at the front
because I'm afraid it will prevent us from making good extensions in the
future. Offering both syntaxes does not fix that.

Just idea - don't kill me :). Because CHECK FUNCTION is not
destructive , then complete signature is not necessary, and when
function name is unique, then parameters should be optional - it can
be comfortable for manual work, so just CHECK FUNCTION name; can work.

Well, there was some discussion of having a "bulk check" or wildcard
capability in the CHECK command, but this seems like an awfully
constricted version of that.

The thing I'd prefer to see in the first cut is some notation for "check
all functions owned by me that are in language FOO". The reason for the
language restriction is that if we think the options are
language-specific, there's no reason to believe that different
validators would accept the same options.

regards, tom lane

#19Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#18)
Re: review: CHECK FUNCTION statement

Hello

I rechecked a possibility to use a validator function together with
checker function.

The main issue is a different interface of both functions. Validator
needs just function oid and uses global variable
check_function_bodies. Checker function needs function oid and
relation oid (possible some other params). When we mix these two
functions together we need a

validator(oid) or validator(oid, oid, variadic "any")

one parameter function is old validator, three parameters function is checker.

Question:

What is a correct signature for this function? We cannot use a
overloading, because we can have only one validator function per
language. We can change a validator to support both forms, and we have
to be carefully and correct if we will work with our validators(3 and
more params) or foreign validators (1 param). We should to support
both (compatibility reasons). We are not careful about validators now
- there are some places, where one parameter is hardly expected - this
should be changed. So using validator for checking doesn't mean
smaller patch, but is true, so these functions has similar semantic -
validator is usually "low level" checker.

What is your opinion?

Regards

Pavel

#20Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#19)
Re: review: CHECK FUNCTION statement

Pavel Stehule <pavel.stehule@gmail.com> writes:

I rechecked a possibility to use a validator function together with
checker function.

The main issue is a different interface of both functions. Validator
needs just function oid and uses global variable
check_function_bodies. Checker function needs function oid and
relation oid (possible some other params). When we mix these two
functions together we need a

validator(oid) or validator(oid, oid, variadic "any")

Right, although if you want it to be callable from SQL I think that
variadic "any" is too loose.

What is a correct signature for this function? We cannot use a
overloading, because we can have only one validator function per
language.

So? You have one validator function, it has either signature;
if it has the old signature then CHECK isn't supported by the language.
We have plenty of examples of this sort of thing already.

One issue that would need to be considered is how the validator tells
the difference between a CREATE FUNCTION call and a CHECK call with
default parameters (no WITH clause). Those shouldn't do exactly the
same thing, presumably. Maybe that's a sufficient reason to have two
entry points.

regards, tom lane

#21Pavel Stehule
pavel.stehule@gmail.com
In reply to: Albe Laurenz (#9)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Hello

updated patch with documentation

Regards

Pavel

Attachments:

check_pl-2011-11-30.difftext/x-patch; charset=US-ASCII; name=check_pl-2011-11-30.diffDownload
*** ./doc/src/sgml/catalogs.sgml.orig	2011-11-29 19:09:02.000000000 +0100
--- ./doc/src/sgml/catalogs.sgml	2011-11-29 20:28:00.571246006 +0100
***************
*** 3652,3657 ****
--- 3652,3668 ----
       </row>
  
       <row>
+       <entry><structfield>lanchecker</structfield></entry>
+       <entry><type>oid</type></entry>
+       <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+       <entry>
+        This references a language checker function that is responsible
+        for checking a embedded SQL and can provide detailed checking.
+        Zero if no checker is provided.
+       </entry>
+      </row>
+ 
+      <row>
        <entry><structfield>lanacl</structfield></entry>
        <entry><type>aclitem[]</type></entry>
        <entry></entry>
*** ./doc/src/sgml/ref/allfiles.sgml.orig	2011-11-29 19:20:59.468117093 +0100
--- ./doc/src/sgml/ref/allfiles.sgml	2011-11-29 19:21:24.487804955 +0100
***************
*** 40,45 ****
--- 40,46 ----
  <!ENTITY alterView          SYSTEM "alter_view.sgml">
  <!ENTITY analyze            SYSTEM "analyze.sgml">
  <!ENTITY begin              SYSTEM "begin.sgml">
+ <!ENTITY checkFunction      SYSTEM "check_function.sgml">
  <!ENTITY checkpoint         SYSTEM "checkpoint.sgml">
  <!ENTITY close              SYSTEM "close.sgml">
  <!ENTITY cluster            SYSTEM "cluster.sgml">
*** ./doc/src/sgml/ref/create_language.sgml.orig	2011-11-29 19:20:59.470117069 +0100
--- ./doc/src/sgml/ref/create_language.sgml	2011-11-29 19:21:24.488804943 +0100
***************
*** 23,29 ****
  <synopsis>
  CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
  CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ]
  </synopsis>
   </refsynopsisdiv>
  
--- 23,29 ----
  <synopsis>
  CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
  CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ] [ CHECK <replaceable>checkfunction</replaceable> ]
  </synopsis>
   </refsynopsisdiv>
  
***************
*** 217,222 ****
--- 217,236 ----
        </para>
       </listitem>
      </varlistentry>
+ 
+     <varlistentry>
+      <term><literal>CHECK</literal> <replaceable class="parameter">checkfunction</replaceable></term>
+ 
+      <listitem>
+       <para><replaceable class="parameter">checkfunction</replaceable> is the
+        name of a previously registered function that will be called
+        when a new function in the language is created, to check the
+        function by statemnt <command>CHECK FUNCTION</command> or 
+        <command>CHECK TRIGGER</command>.
+       </para>
+      </listitem>
+     </varlistentry>
+ 
     </variablelist>
  
    <para>
*** ./doc/src/sgml/reference.sgml.orig	2011-11-29 19:20:59.471117057 +0100
--- ./doc/src/sgml/reference.sgml	2011-11-29 19:21:24.492804895 +0100
***************
*** 68,73 ****
--- 68,74 ----
     &alterView;
     &analyze;
     &begin;
+    &checkFunction;
     &checkpoint;
     &close;
     &cluster;
*** ./doc/src/sgml/ref/check_function.sgml.orig	2011-11-30 19:31:09.021947003 +0100
--- ./doc/src/sgml/ref/check_function.sgml	2011-11-29 19:21:24.493804882 +0100
***************
*** 0 ****
--- 1,37 ----
+ <!--
+ doc/src/sgml/ref/check_function.sgml
+ -->
+ 
+ <refentry id="SQL-CHECKFUNCTION">
+  <refmeta>
+   <refentrytitle>CHECK FUNCTION</refentrytitle>
+   <manvolnum>7</manvolnum>
+   <refmiscinfo>SQL - Language Statements</refmiscinfo>
+  </refmeta>
+ 
+  <refnamediv>
+   <refname>CHECK FUNCTION</refname>
+   <refpurpose>ensure a deep checking of existing function</refpurpose>
+  </refnamediv>
+ 
+  <indexterm zone="sql-checkfunction">
+   <primary>CHECK FUNCTION</primary>
+  </indexterm>
+ 
+  <refsynopsisdiv>
+ <synopsis>
+ CREATE FUNCTION <replaceable class="parameter">name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] )
+  | CREATE TRIGGER <replaceable class="parameter">name</replaceable> ON <replaceable class="parameter">tablename</replaceable>
+ </synopsis>
+  </refsynopsisdiv>
+ 
+  <refsect1 id="sql-checkfunction-description">
+   <title>Description</title>
+ 
+   <para>
+    <command>CHECK FUNCTION</command> check a existing function.
+    <command>CHECK TRIGGER</command> check a trigger function.
+   </para>
+  </refsect1>
+ 
+ </refentry>
*** ./src/backend/catalog/pg_proc.c.orig	2011-11-29 19:20:59.474117021 +0100
--- ./src/backend/catalog/pg_proc.c	2011-11-29 19:21:24.494804869 +0100
***************
*** 1101,1103 ****
--- 1101,1104 ----
  	*newcursorpos = newcp;
  	return false;
  }
+ 
*** ./src/backend/commands/functioncmds.c.orig	2011-11-29 19:20:59.475117009 +0100
--- ./src/backend/commands/functioncmds.c	2011-11-29 19:21:24.496804843 +0100
***************
*** 44,53 ****
--- 44,55 ----
  #include "catalog/pg_namespace.h"
  #include "catalog/pg_proc.h"
  #include "catalog/pg_proc_fn.h"
+ #include "catalog/pg_trigger.h"
  #include "catalog/pg_type.h"
  #include "catalog/pg_type_fn.h"
  #include "commands/defrem.h"
  #include "commands/proclang.h"
+ #include "commands/trigger.h"
  #include "miscadmin.h"
  #include "optimizer/var.h"
  #include "parser/parse_coerce.h"
***************
*** 60,65 ****
--- 62,68 ----
  #include "utils/fmgroids.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
+ #include "utils/memutils.h"
  #include "utils/rel.h"
  #include "utils/syscache.h"
  #include "utils/tqual.h"
***************
*** 1009,1014 ****
--- 1012,1152 ----
  	}
  }
  
+ /*
+  * CheckFunction
+  *			call a PL checker function when this function exists.
+  */
+ void
+ CheckFunction(CheckFunctionStmt *stmt)
+ {
+ 	List	   *functionName = stmt->funcname;
+ 	List	   *argTypes = stmt->args;	/* list of TypeName nodes */
+ 	Oid			funcOid;
+ 
+ 	HeapTuple	tup;
+ 	Form_pg_proc proc;
+ 
+ 	HeapTuple	languageTuple;
+ 	Form_pg_language languageStruct;
+ 	Oid		languageChecker;
+ 	Oid trgOid = InvalidOid;
+ 	Oid	relid = InvalidOid;
+ 
+ 	/* when we should to check trigger, then we should to find a trigger handler */
+ 	if (functionName == NULL)
+ 	{
+ 		HeapTuple	ht_trig;
+ 		Form_pg_trigger trigrec;
+ 		ScanKeyData skey[1];
+ 		Relation	tgrel;
+ 		SysScanDesc tgscan;
+ 		char *fname;
+ 
+ 		relid = RangeVarGetRelid(stmt->relation, ShareLock, false, false);
+ 		trgOid = get_trigger_oid(relid, stmt->trgname, false);
+ 
+ 		/*
+ 		 * Fetch the pg_trigger tuple by the Oid of the trigger
+ 		 */
+ 		tgrel = heap_open(TriggerRelationId, AccessShareLock);
+ 
+ 		ScanKeyInit(&skey[0],
+ 					ObjectIdAttributeNumber,
+ 					BTEqualStrategyNumber, F_OIDEQ,
+ 					ObjectIdGetDatum(trgOid));
+ 
+ 		tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true,
+ 									SnapshotNow, 1, skey);
+ 
+ 		ht_trig = systable_getnext(tgscan);
+ 
+ 		if (!HeapTupleIsValid(ht_trig))
+ 			elog(ERROR, "could not find tuple for trigger %u", trgOid);
+ 
+ 		trigrec = (Form_pg_trigger) GETSTRUCT(ht_trig);
+ 
+ 		/* we need to know trigger function to get PL checker function */
+ 		funcOid = trigrec->tgfoid;
+ 		fname = format_procedure(funcOid);
+ 		/* Clean up */
+ 		systable_endscan(tgscan);
+ 
+ 		elog(NOTICE, "checking function \"%s\"", fname);
+ 		pfree(fname);
+ 
+ 		heap_close(tgrel, AccessShareLock);
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * Find the function, 
+ 		 */
+ 		funcOid = LookupFuncNameTypeNames(functionName, argTypes, false);
+ 	}
+ 
+ 	tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
+ 	if (!HeapTupleIsValid(tup)) /* should not happen */
+ 		elog(ERROR, "cache lookup failed for function %u", funcOid);
+ 
+ 	proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 	languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
+ 	Assert(HeapTupleIsValid(languageTuple));
+ 
+ 	languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
+ 	languageChecker = languageStruct->lanchecker;
+ 
+ 	/* Check a function body */
+ 	if (OidIsValid(languageChecker))
+ 	{
+ 		ArrayType  *set_items = NULL;
+ 		int			save_nestlevel;
+ 		Datum	datum;
+ 		bool		isnull;
+ 		MemoryContext oldCxt;
+ 		MemoryContext checkCxt;
+ 
+ 		datum = SysCacheGetAttr(PROCOID, tup, Anum_pg_proc_proconfig, &isnull);
+ 
+ 		if (!isnull)
+ 		{
+ 			/* Set per-function configuration parameters */
+ 			set_items = (ArrayType *) DatumGetPointer(datum);
+ 			if (set_items)			/* Need a new GUC nesting level */
+ 			{
+ 				save_nestlevel = NewGUCNestLevel();
+ 				ProcessGUCArray(set_items,
+ 							(superuser() ? PGC_SUSET : PGC_USERSET),
+ 							PGC_S_SESSION,
+ 							GUC_ACTION_SAVE);
+ 			}
+ 			else
+ 				save_nestlevel = 0; /* keep compiler quiet */
+ 		}
+ 
+ 		checkCxt = AllocSetContextCreate(CurrentMemoryContext,
+ 									"Check temporary context",
+ 									ALLOCSET_DEFAULT_MINSIZE,
+ 									ALLOCSET_DEFAULT_INITSIZE,
+ 									ALLOCSET_DEFAULT_MAXSIZE);
+ 
+ 		oldCxt = MemoryContextSwitchTo(checkCxt);
+ 
+ 		OidFunctionCall2(languageChecker, ObjectIdGetDatum(funcOid), 
+ 							    ObjectIdGetDatum(relid));
+ 
+ 		MemoryContextSwitchTo(oldCxt);
+ 
+ 		if (set_items)
+ 			AtEOXact_GUC(true, save_nestlevel);
+ 	}
+ 	else
+ 		elog(WARNING, "language \"%s\" has no defined checker function",
+ 			    NameStr(languageStruct->lanname));
+ 
+ 	ReleaseSysCache(languageTuple);
+ 	ReleaseSysCache(tup);
+ }
  
  /*
   * Rename function
*** ./src/backend/commands/proclang.c.orig	2011-11-29 19:20:59.477116983 +0100
--- ./src/backend/commands/proclang.c	2011-11-29 19:21:24.497804830 +0100
***************
*** 46,57 ****
  	char	   *tmplhandler;	/* name of handler function */
  	char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
  	char	   *tmplvalidator;	/* name of validator function, or NULL */
  	char	   *tmpllibrary;	/* path of shared library */
  } PLTemplate;
  
  static void create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, bool trusted);
  static PLTemplate *find_language_template(const char *languageName);
  static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
  							Oid newOwnerId);
--- 46,58 ----
  	char	   *tmplhandler;	/* name of handler function */
  	char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
  	char	   *tmplvalidator;	/* name of validator function, or NULL */
+ 	char	   *tmplchecker;	/* name of checker function, or NULL */
  	char	   *tmpllibrary;	/* path of shared library */
  } PLTemplate;
  
  static void create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, Oid checkerOid, bool trusted);
  static PLTemplate *find_language_template(const char *languageName);
  static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
  							Oid newOwnerId);
***************
*** 67,75 ****
  	PLTemplate *pltemplate;
  	Oid			handlerOid,
  				inlineOid,
! 				valOid;
  	Oid			funcrettype;
! 	Oid			funcargtypes[1];
  
  	/*
  	 * If we have template information for the language, ignore the supplied
--- 68,77 ----
  	PLTemplate *pltemplate;
  	Oid			handlerOid,
  				inlineOid,
! 				valOid,
! 				checkerOid;
  	Oid			funcrettype;
! 	Oid			funcargtypes[2];
  
  	/*
  	 * If we have template information for the language, ignore the supplied
***************
*** 219,228 ****
  		else
  			valOid = InvalidOid;
  
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, pltemplate->tmpltrusted);
  	}
  	else
  	{
--- 221,269 ----
  		else
  			valOid = InvalidOid;
  
+ 		/*
+ 		 * Likewise for the checker, if required; but we don't care about
+ 		 * its return type.
+ 		 */
+ 		if (pltemplate->tmplchecker)
+ 		{
+ 			funcname = SystemFuncName(pltemplate->tmplchecker);
+ 			funcargtypes[0] = OIDOID;
+ 			funcargtypes[1] = REGCLASSOID;
+ 			checkerOid = LookupFuncName(funcname, 2, funcargtypes, true);
+ 			if (!OidIsValid(checkerOid))
+ 			{
+ 				checkerOid = ProcedureCreate(pltemplate->tmplchecker,
+ 										 PG_CATALOG_NAMESPACE,
+ 										 false, /* replace */
+ 										 false, /* returnsSet */
+ 										 VOIDOID,
+ 										 ClanguageId,
+ 										 F_FMGR_C_VALIDATOR,
+ 										 pltemplate->tmplchecker,
+ 										 pltemplate->tmpllibrary,
+ 										 false, /* isAgg */
+ 										 false, /* isWindowFunc */
+ 										 false, /* security_definer */
+ 										 true,	/* isStrict */
+ 										 PROVOLATILE_VOLATILE,
+ 										 buildoidvector(funcargtypes, 2),
+ 										 PointerGetDatum(NULL),
+ 										 PointerGetDatum(NULL),
+ 										 PointerGetDatum(NULL),
+ 										 NIL,
+ 										 PointerGetDatum(NULL),
+ 										 1,
+ 										 0);
+ 			}
+ 		}
+ 		else
+ 			checkerOid = InvalidOid;
+ 
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, checkerOid, pltemplate->tmpltrusted);
  	}
  	else
  	{
***************
*** 294,303 ****
  		else
  			valOid = InvalidOid;
  
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, stmt->pltrusted);
  	}
  }
  
--- 335,355 ----
  		else
  			valOid = InvalidOid;
  
+ 		/* validate the checker function */
+ 		if (stmt->plchecker)
+ 		{
+ 			funcargtypes[0] = OIDOID;
+ 			funcargtypes[1] = REGCLASSOID;
+ 			checkerOid = LookupFuncName(stmt->plchecker, 2, funcargtypes, false);
+ 			/* return value is ignored, so we don't check the type */
+ 		}
+ 		else
+ 			checkerOid = InvalidOid;
+ 
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, checkerOid, stmt->pltrusted);
  	}
  }
  
***************
*** 307,313 ****
  static void
  create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, bool trusted)
  {
  	Relation	rel;
  	TupleDesc	tupDesc;
--- 359,365 ----
  static void
  create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, Oid checkerOid, bool trusted)
  {
  	Relation	rel;
  	TupleDesc	tupDesc;
***************
*** 337,342 ****
--- 389,395 ----
  	values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid);
  	values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid);
  	values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid);
+ 	values[Anum_pg_language_lanchecker - 1] = ObjectIdGetDatum(checkerOid);
  	nulls[Anum_pg_language_lanacl - 1] = true;
  
  	/* Check for pre-existing definition */
***************
*** 423,428 ****
--- 476,490 ----
  		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  	}
  
+ 	/* dependency on the checker function, if any */
+ 	if (OidIsValid(checkerOid))
+ 	{
+ 		referenced.classId = ProcedureRelationId;
+ 		referenced.objectId = checkerOid;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	/* Post creation hook for new procedural language */
  	InvokeObjectAccessHook(OAT_POST_CREATE,
  						   LanguageRelationId, myself.objectId, 0);
***************
*** 478,483 ****
--- 540,550 ----
  		if (!isnull)
  			result->tmplvalidator = TextDatumGetCString(datum);
  
+ 		datum = heap_getattr(tup, Anum_pg_pltemplate_tmplchecker,
+ 							 RelationGetDescr(rel), &isnull);
+ 		if (!isnull)
+ 			result->tmplchecker = TextDatumGetCString(datum);
+ 
  		datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary,
  							 RelationGetDescr(rel), &isnull);
  		if (!isnull)
*** ./src/backend/nodes/copyfuncs.c.orig	2011-11-29 19:09:02.000000000 +0100
--- ./src/backend/nodes/copyfuncs.c	2011-11-29 20:17:01.339172458 +0100
***************
*** 2880,2885 ****
--- 2880,2898 ----
  	return newnode;
  }
  
+ static CheckFunctionStmt *
+ _copyCheckFunctionStmt(CheckFunctionStmt *from)
+ {
+ 	CheckFunctionStmt *newnode = makeNode(CheckFunctionStmt);
+ 
+ 	COPY_NODE_FIELD(funcname);
+ 	COPY_NODE_FIELD(args);
+ 	COPY_STRING_FIELD(trgname);
+ 	COPY_NODE_FIELD(relation);
+ 
+ 	return newnode;
+ }
+ 
  static DoStmt *
  _copyDoStmt(DoStmt *from)
  {
***************
*** 4165,4170 ****
--- 4178,4186 ----
  		case T_AlterFunctionStmt:
  			retval = _copyAlterFunctionStmt(from);
  			break;
+ 		case T_CheckFunctionStmt:
+ 			retval = _copyCheckFunctionStmt(from);
+ 			break;
  		case T_DoStmt:
  			retval = _copyDoStmt(from);
  			break;
*** ./src/backend/nodes/equalfuncs.c.orig	2011-11-29 20:19:55.045587471 +0100
--- ./src/backend/nodes/equalfuncs.c	2011-11-29 20:19:21.850082357 +0100
***************
*** 1292,1297 ****
--- 1292,1308 ----
  }
  
  static bool
+ _equalCheckFunctionStmt(CheckFunctionStmt *a, CheckFunctionStmt *b)
+ {
+ 	COMPARE_NODE_FIELD(funcname);
+ 	COMPARE_NODE_FIELD(args);
+ 	COMPARE_STRING_FIELD(trgname);
+ 	COMPARE_NODE_FIELD(relation);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalDoStmt(DoStmt *a, DoStmt *b)
  {
  	COMPARE_NODE_FIELD(args);
***************
*** 2708,2713 ****
--- 2719,2727 ----
  		case T_AlterFunctionStmt:
  			retval = _equalAlterFunctionStmt(a, b);
  			break;
+ 		case T_CheckFunctionStmt:
+ 			retval = _equalCheckFunctionStmt(a, b);
+ 			break;
  		case T_DoStmt:
  			retval = _equalDoStmt(a, b);
  			break;
*** ./src/backend/parser/gram.y.orig	2011-11-29 19:09:02.876463248 +0100
--- ./src/backend/parser/gram.y	2011-11-29 19:21:24.502804769 +0100
***************
*** 227,232 ****
--- 227,233 ----
  		DeallocateStmt PrepareStmt ExecuteStmt
  		DropOwnedStmt ReassignOwnedStmt
  		AlterTSConfigurationStmt AlterTSDictionaryStmt
+ 		CheckFunctionStmt
  
  %type <node>	select_no_parens select_with_parens select_clause
  				simple_select values_clause
***************
*** 276,282 ****
  
  %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
  				opt_class opt_inline_handler opt_validator validator_clause
! 				opt_collate
  
  %type <range>	qualified_name OptConstrFromTable
  
--- 277,283 ----
  
  %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
  				opt_class opt_inline_handler opt_validator validator_clause
! 				opt_collate opt_checker
  
  %type <range>	qualified_name OptConstrFromTable
  
***************
*** 700,705 ****
--- 701,707 ----
  			| AlterUserSetStmt
  			| AlterUserStmt
  			| AnalyzeStmt
+ 			| CheckFunctionStmt
  			| CheckPointStmt
  			| ClosePortalStmt
  			| ClusterStmt
***************
*** 3174,3184 ****
  				n->plhandler = NIL;
  				n->plinline = NIL;
  				n->plvalidator = NIL;
  				n->pltrusted = false;
  				$$ = (Node *)n;
  			}
  			| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
! 			  HANDLER handler_name opt_inline_handler opt_validator
  			{
  				CreatePLangStmt *n = makeNode(CreatePLangStmt);
  				n->replace = $2;
--- 3176,3187 ----
  				n->plhandler = NIL;
  				n->plinline = NIL;
  				n->plvalidator = NIL;
+ 				n->plchecker = NIL;
  				n->pltrusted = false;
  				$$ = (Node *)n;
  			}
  			| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
! 			  HANDLER handler_name opt_inline_handler opt_validator opt_checker
  			{
  				CreatePLangStmt *n = makeNode(CreatePLangStmt);
  				n->replace = $2;
***************
*** 3186,3191 ****
--- 3189,3195 ----
  				n->plhandler = $8;
  				n->plinline = $9;
  				n->plvalidator = $10;
+ 				n->plchecker = $11;
  				n->pltrusted = $3;
  				$$ = (Node *)n;
  			}
***************
*** 3220,3225 ****
--- 3224,3234 ----
  			| /*EMPTY*/								{ $$ = NIL; }
  		;
  
+ opt_checker:
+ 			CHECK handler_name					{ $$ = $2; }
+ 			| /*EMPTY*/								{ $$ = NIL; }
+ 		;
+ 
  DropPLangStmt:
  			DROP opt_procedural LANGUAGE ColId_or_Sconst opt_drop_behavior
  				{
***************
*** 6250,6255 ****
--- 6259,6294 ----
  
  /*****************************************************************************
   *
+  *		CHECK FUNCTION funcname(args)
+  *		CHECK TRIGGER triggername ON table
+  *
+  *
+  *****************************************************************************/
+ 
+ 
+ CheckFunctionStmt:
+ 			CHECK FUNCTION func_name func_args
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = $3;
+ 					n->args = extractArgTypes($4);
+ 					n->trgname = NULL;
+ 					n->relation = NULL;
+ 					$$ = (Node *) n;
+ 				}
+ 			| CHECK TRIGGER name ON qualified_name
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = NULL;
+ 					n->args = NIL;
+ 					n->trgname = $3;
+ 					n->relation = $5;
+ 					$$ = (Node *) n;
+ 				}
+ 		;
+ 
+ /*****************************************************************************
+  *
   *		DO <anonymous code block> [ LANGUAGE language ]
   *
   * We use a DefElem list for future extensibility, and to allow flexibility
*** ./src/backend/tcop/utility.c.orig	2011-11-29 19:20:59.480116945 +0100
--- ./src/backend/tcop/utility.c	2011-11-29 19:21:24.513804628 +0100
***************
*** 882,887 ****
--- 882,891 ----
  			AlterFunction((AlterFunctionStmt *) parsetree);
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			CheckFunction((CheckFunctionStmt *) parsetree);
+ 			break;
+ 
  		case T_IndexStmt:		/* CREATE INDEX */
  			{
  				IndexStmt  *stmt = (IndexStmt *) parsetree;
***************
*** 2125,2130 ****
--- 2129,2141 ----
  			}
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			if (((CheckFunctionStmt *) parsetree)->funcname != NULL)
+ 				tag = "CHECK FUNCTION";
+ 			else
+ 				tag = "CHECK TRIGGER";
+ 			break;
+ 
  		default:
  			elog(WARNING, "unrecognized node type: %d",
  				 (int) nodeTag(parsetree));
***************
*** 2565,2570 ****
--- 2576,2585 ----
  			}
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			lev = LOGSTMT_ALL;
+ 			break;
+ 
  		default:
  			elog(WARNING, "unrecognized node type: %d",
  				 (int) nodeTag(parsetree));
*** ./src/bin/pg_dump/pg_dump.c.orig	2011-11-29 19:09:03.000000000 +0100
--- ./src/bin/pg_dump/pg_dump.c	2011-11-29 20:04:31.094156626 +0100
***************
*** 5326,5338 ****
  	int			i_lanplcallfoid;
  	int			i_laninline;
  	int			i_lanvalidator;
  	int			i_lanacl;
  	int			i_lanowner;
  
  	/* Make sure we are in proper schema */
  	selectSourceSchema("pg_catalog");
  
! 	if (g_fout->remoteVersion >= 90000)
  	{
  		/* pg_language has a laninline column */
  		appendPQExpBuffer(query, "SELECT tableoid, oid, "
--- 5326,5351 ----
  	int			i_lanplcallfoid;
  	int			i_laninline;
  	int			i_lanvalidator;
+ 	int			i_lanchecker;
  	int			i_lanacl;
  	int			i_lanowner;
  
  	/* Make sure we are in proper schema */
  	selectSourceSchema("pg_catalog");
  
! 	if (g_fout->remoteVersion >= 90200)
! 	{
! 		/* pg_language has a lanchecker column */
! 		appendPQExpBuffer(query, "SELECT tableoid, oid, "
! 						  "lanname, lanpltrusted, lanplcallfoid, "
! 						  "laninline, lanvalidator, lanchecker, lanacl, "
! 						  "(%s lanowner) AS lanowner "
! 						  "FROM pg_language "
! 						  "WHERE lanispl "
! 						  "ORDER BY oid",
! 						  username_subquery);
! 	}
! 	else if (g_fout->remoteVersion >= 90000)
  	{
  		/* pg_language has a laninline column */
  		appendPQExpBuffer(query, "SELECT tableoid, oid, "
***************
*** 5409,5414 ****
--- 5422,5428 ----
  	/* these may fail and return -1: */
  	i_laninline = PQfnumber(res, "laninline");
  	i_lanvalidator = PQfnumber(res, "lanvalidator");
+ 	i_lanchecker = PQfnumber(res, "lanchecker");
  	i_lanacl = PQfnumber(res, "lanacl");
  	i_lanowner = PQfnumber(res, "lanowner");
  
***************
*** 5422,5427 ****
--- 5436,5445 ----
  		planginfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_lanname));
  		planginfo[i].lanpltrusted = *(PQgetvalue(res, i, i_lanpltrusted)) == 't';
  		planginfo[i].lanplcallfoid = atooid(PQgetvalue(res, i, i_lanplcallfoid));
+ 		if (i_lanchecker >= 0)
+ 			planginfo[i].lanchecker = atooid(PQgetvalue(res, i, i_lanchecker));
+ 		else
+ 			planginfo[i].lanchecker = InvalidOid;
  		if (i_laninline >= 0)
  			planginfo[i].laninline = atooid(PQgetvalue(res, i, i_laninline));
  		else
***************
*** 8597,8602 ****
--- 8615,8621 ----
  	char	   *qlanname;
  	char	   *lanschema;
  	FuncInfo   *funcInfo;
+ 	FuncInfo   *checkerInfo = NULL;
  	FuncInfo   *inlineInfo = NULL;
  	FuncInfo   *validatorInfo = NULL;
  
***************
*** 8616,8621 ****
--- 8635,8647 ----
  	if (funcInfo != NULL && !funcInfo->dobj.dump)
  		funcInfo = NULL;		/* treat not-dumped same as not-found */
  
+ 	if (OidIsValid(plang->lanchecker))
+ 	{
+ 		checkerInfo = findFuncByOid(plang->lanchecker);
+ 		if (checkerInfo != NULL && !checkerInfo->dobj.dump)
+ 			checkerInfo = NULL;
+ 	}
+ 
  	if (OidIsValid(plang->laninline))
  	{
  		inlineInfo = findFuncByOid(plang->laninline);
***************
*** 8642,8647 ****
--- 8668,8674 ----
  	 * don't, this might not work terribly nicely.
  	 */
  	useParams = (funcInfo != NULL &&
+ 				 (checkerInfo != NULL || !OidIsValid(plang->lanchecker)) &&
  				 (inlineInfo != NULL || !OidIsValid(plang->laninline)) &&
  				 (validatorInfo != NULL || !OidIsValid(plang->lanvalidator)));
  
***************
*** 8697,8702 ****
--- 8724,8739 ----
  			appendPQExpBuffer(defqry, "%s",
  							  fmtId(validatorInfo->dobj.name));
  		}
+ 		if (OidIsValid(plang->lanchecker))
+ 		{
+ 			appendPQExpBuffer(defqry, " CHECK ");
+ 			/* Cope with possibility that checker is in different schema */
+ 			if (checkerInfo->dobj.namespace != funcInfo->dobj.namespace)
+ 				appendPQExpBuffer(defqry, "%s.",
+ 							   fmtId(checkerInfo->dobj.namespace->dobj.name));
+ 			appendPQExpBuffer(defqry, "%s",
+ 							  fmtId(checkerInfo->dobj.name));
+ 		}
  	}
  	else
  	{
*** ./src/bin/pg_dump/pg_dump.h.orig	2011-11-29 20:05:48.255044631 +0100
--- ./src/bin/pg_dump/pg_dump.h	2011-11-29 20:05:08.766614345 +0100
***************
*** 387,392 ****
--- 387,393 ----
  	Oid			lanplcallfoid;
  	Oid			laninline;
  	Oid			lanvalidator;
+ 	Oid			lanchecker;
  	char	   *lanacl;
  	char	   *lanowner;		/* name of owner, or empty string */
  } ProcLangInfo;
*** ./src/bin/psql/tab-complete.c.orig	2011-11-29 19:20:59.482116921 +0100
--- ./src/bin/psql/tab-complete.c	2011-11-29 19:21:24.516804592 +0100
***************
*** 1,4 ****
--- 1,5 ----
  /*
+  *
   * psql - the PostgreSQL interactive terminal
   *
   * Copyright (c) 2000-2011, PostgreSQL Global Development Group
***************
*** 727,733 ****
  #define prev6_wd  (previous_words[5])
  
  	static const char *const sql_commands[] = {
! 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
  		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
--- 728,734 ----
  #define prev6_wd  (previous_words[5])
  
  	static const char *const sql_commands[] = {
! 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECK", "CHECKPOINT", "CLOSE", "CLUSTER",
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
  		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
***************
*** 1524,1529 ****
--- 1525,1552 ----
  
  		COMPLETE_WITH_LIST(list_TRANS);
  	}
+ 
+ /* CHECK */
+ 	else if (pg_strcasecmp(prev_wd, "CHECK") == 0)
+ 	{
+ 		static const char *const list_CHECK[] =
+ 		{"FUNCTION", "TRIGGER", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_CHECK);
+ 	}
+ 	else if (pg_strcasecmp(prev3_wd, "CHECK") == 0 &&
+ 			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+ 	{
+ 		COMPLETE_WITH_CONST("ON");
+ 	}
+ 	else if (pg_strcasecmp(prev4_wd, "CHECK") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "ON") == 0)
+ 	{
+ 		completion_info_charp = prev2_wd;
+ 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+ 	}
+ 
  /* CLUSTER */
  
  	/*
*** ./src/include/catalog/pg_language.h.orig	2011-11-29 19:20:59.483116909 +0100
--- ./src/include/catalog/pg_language.h	2011-11-29 19:21:24.518804568 +0100
***************
*** 37,42 ****
--- 37,43 ----
  	Oid			lanplcallfoid;	/* Call handler for PL */
  	Oid			laninline;		/* Optional anonymous-block handler function */
  	Oid			lanvalidator;	/* Optional validation function */
+ 	Oid			lanchecker;	/* Optional checker function */
  	aclitem		lanacl[1];		/* Access privileges */
  } FormData_pg_language;
  
***************
*** 51,57 ****
   *		compiler constants for pg_language
   * ----------------
   */
! #define Natts_pg_language				8
  #define Anum_pg_language_lanname		1
  #define Anum_pg_language_lanowner		2
  #define Anum_pg_language_lanispl		3
--- 52,58 ----
   *		compiler constants for pg_language
   * ----------------
   */
! #define Natts_pg_language				9
  #define Anum_pg_language_lanname		1
  #define Anum_pg_language_lanowner		2
  #define Anum_pg_language_lanispl		3
***************
*** 59,78 ****
  #define Anum_pg_language_lanplcallfoid	5
  #define Anum_pg_language_laninline		6
  #define Anum_pg_language_lanvalidator	7
! #define Anum_pg_language_lanacl			8
  
  /* ----------------
   *		initial contents of pg_language
   * ----------------
   */
  
! DATA(insert OID = 12 ( "internal"	PGUID f f 0 0 2246 _null_ ));
  DESCR("built-in functions");
  #define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c"			PGUID f f 0 0 2247 _null_ ));
  DESCR("dynamically-loaded C functions");
  #define ClanguageId 13
! DATA(insert OID = 14 ( "sql"		PGUID f t 0 0 2248 _null_ ));
  DESCR("SQL-language functions");
  #define SQLlanguageId 14
  
--- 60,80 ----
  #define Anum_pg_language_lanplcallfoid	5
  #define Anum_pg_language_laninline		6
  #define Anum_pg_language_lanvalidator	7
! #define Anum_pg_language_lanchecker	8
! #define Anum_pg_language_lanacl			9
  
  /* ----------------
   *		initial contents of pg_language
   * ----------------
   */
  
! DATA(insert OID = 12 ( "internal"	PGUID f f 0 0 2246 0 _null_ ));
  DESCR("built-in functions");
  #define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c"			PGUID f f 0 0 2247 0 _null_ ));
  DESCR("dynamically-loaded C functions");
  #define ClanguageId 13
! DATA(insert OID = 14 ( "sql"		PGUID f t 0 0 2248 0 _null_ ));
  DESCR("SQL-language functions");
  #define SQLlanguageId 14
  
*** ./src/include/catalog/pg_pltemplate.h.orig	2011-11-29 19:20:59.484116897 +0100
--- ./src/include/catalog/pg_pltemplate.h	2011-11-29 19:21:24.518804568 +0100
***************
*** 36,41 ****
--- 36,42 ----
  	text		tmplhandler;	/* name of call handler function */
  	text		tmplinline;		/* name of anonymous-block handler, or NULL */
  	text		tmplvalidator;	/* name of validator function, or NULL */
+ 	text		tmplchecker;	/* name of checker function, or NULL */
  	text		tmpllibrary;	/* path of shared library */
  	aclitem		tmplacl[1];		/* access privileges for template */
  } FormData_pg_pltemplate;
***************
*** 51,65 ****
   *		compiler constants for pg_pltemplate
   * ----------------
   */
! #define Natts_pg_pltemplate					8
  #define Anum_pg_pltemplate_tmplname			1
  #define Anum_pg_pltemplate_tmpltrusted		2
  #define Anum_pg_pltemplate_tmpldbacreate	3
  #define Anum_pg_pltemplate_tmplhandler		4
  #define Anum_pg_pltemplate_tmplinline		5
  #define Anum_pg_pltemplate_tmplvalidator	6
! #define Anum_pg_pltemplate_tmpllibrary		7
! #define Anum_pg_pltemplate_tmplacl			8
  
  
  /* ----------------
--- 52,67 ----
   *		compiler constants for pg_pltemplate
   * ----------------
   */
! #define Natts_pg_pltemplate					9
  #define Anum_pg_pltemplate_tmplname			1
  #define Anum_pg_pltemplate_tmpltrusted		2
  #define Anum_pg_pltemplate_tmpldbacreate	3
  #define Anum_pg_pltemplate_tmplhandler		4
  #define Anum_pg_pltemplate_tmplinline		5
  #define Anum_pg_pltemplate_tmplvalidator	6
! #define Anum_pg_pltemplate_tmplchecker		7
! #define Anum_pg_pltemplate_tmpllibrary		8
! #define Anum_pg_pltemplate_tmplacl			9
  
  
  /* ----------------
***************
*** 67,79 ****
   * ----------------
   */
  
! DATA(insert ( "plpgsql"		t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl"		t t "pltcl_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu"		f f "pltclu_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl"		t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu"		f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu"	f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u"	f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u"	f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" "$libdir/plpython3" _null_ ));
  
  #endif   /* PG_PLTEMPLATE_H */
--- 69,81 ----
   * ----------------
   */
  
! DATA(insert ( "plpgsql"		t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "plpgsql_checker" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl"		t t "pltcl_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu"		f f "pltclu_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl"		t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu"		f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu"	f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u"	f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u"	f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" _null_ "$libdir/plpython3" _null_ ));
  
  #endif   /* PG_PLTEMPLATE_H */
*** ./src/include/commands/defrem.h.orig	2011-11-29 19:20:59.486116871 +0100
--- ./src/include/commands/defrem.h	2011-11-29 19:21:24.519804556 +0100
***************
*** 62,67 ****
--- 62,68 ----
  /* commands/functioncmds.c */
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
  extern void RemoveFunctionById(Oid funcOid);
+ extern void CheckFunction(CheckFunctionStmt *stmt);
  extern void SetFunctionReturnType(Oid funcOid, Oid newRetType);
  extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType);
  extern void RenameFunction(List *name, List *argtypes, const char *newname);
*** ./src/include/nodes/nodes.h.orig	2011-11-29 19:20:59.487116858 +0100
--- ./src/include/nodes/nodes.h	2011-11-29 19:21:24.521804532 +0100
***************
*** 291,296 ****
--- 291,297 ----
  	T_IndexStmt,
  	T_CreateFunctionStmt,
  	T_AlterFunctionStmt,
+ 	T_CheckFunctionStmt,
  	T_DoStmt,
  	T_RenameStmt,
  	T_RuleStmt,
*** ./src/include/nodes/parsenodes.h.orig	2011-11-29 19:20:59.489116833 +0100
--- ./src/include/nodes/parsenodes.h	2011-11-29 19:21:24.523804506 +0100
***************
*** 1734,1739 ****
--- 1734,1740 ----
  	List	   *plhandler;		/* PL call handler function (qual. name) */
  	List	   *plinline;		/* optional inline function (qual. name) */
  	List	   *plvalidator;	/* optional validator function (qual. name) */
+ 	List	   *plchecker;		/* optional checker function (qual. name) */
  	bool		pltrusted;		/* PL is trusted */
  } CreatePLangStmt;
  
***************
*** 2077,2082 ****
--- 2078,2096 ----
  } AlterFunctionStmt;
  
  /* ----------------------
+  *		Check {Function|Trigger} Statement
+  * ----------------------
+  */
+ typedef struct CheckFunctionStmt
+ {
+ 	NodeTag		type;
+ 	List	   *funcname;			/* qualified name of checked object */
+ 	List	   *args;			/* types of the arguments */
+ 	char	   *trgname;			/* trigger's name */
+ 	RangeVar	*relation;		/* trigger's relation */
+ } CheckFunctionStmt;
+ 
+ /* ----------------------
   *		DO Statement
   *
   * DoStmt is the raw parser output, InlineCodeBlock is the execution-time API
*** ./src/pl/plpgsql/src/pl_comp.c.orig	2011-11-29 19:09:03.000000000 +0100
--- ./src/pl/plpgsql/src/pl_comp.c	2011-11-29 19:42:43.058753779 +0100
***************
*** 115,121 ****
  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.
--- 115,120 ----
***************
*** 175,181 ****
  			 * 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
--- 174,180 ----
  			 * 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
***************
*** 2426,2432 ****
  }
  
  /*
!  * 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
--- 2425,2431 ----
  }
  
  /*
!  * 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,2446 ****
   * 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);
--- 2438,2445 ----
   * 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);
*** ./src/pl/plpgsql/src/pl_exec.c.orig	2011-11-29 19:09:03.316459122 +0100
--- ./src/pl/plpgsql/src/pl_exec.c	2011-11-29 19:37:19.000000000 +0100
***************
*** 210,216 ****
  static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
  						  PLpgSQL_expr *dynquery, List *params,
  						  const char *portalname, int cursorOptions);
! 
  
  /* ----------
   * plpgsql_exec_function	Called by the call handler for
--- 210,228 ----
  static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
  						  PLpgSQL_expr *dynquery, List *params,
  						  const char *portalname, int cursorOptions);
! static void check_row_or_rec(PLpgSQL_execstate *estate, PLpgSQL_row *row, PLpgSQL_rec *rec);
! static void check_expr(PLpgSQL_execstate *estate, PLpgSQL_expr *expr);
! static void assign_tupdesc_row_or_rec(PLpgSQL_execstate *estate,
! 					    PLpgSQL_row *row, PLpgSQL_rec *rec,
! 								    TupleDesc tupdesc);
! static TupleDesc expr_get_desc(PLpgSQL_execstate *estate,
! 						PLpgSQL_expr *query,
! 							bool use_element_type,
! 							bool expand_record,
! 								bool is_expression);
! static void var_init_to_null(PLpgSQL_execstate *estate, int varno);
! static void check_stmts(PLpgSQL_execstate *estate, List *stmts);
! static void check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt);
  
  /* ----------
   * plpgsql_exec_function	Called by the call handler for
***************
*** 6176,6178 ****
--- 6188,7242 ----
  
  	return portal;
  }
+ 
+ /*
+  * Following code ensures a CHECK FUNCTION and CHECK TRIGGER statements for PL/pgSQL
+  *
+  */
+ 
+ /*
+  * append a CONTEXT to error message
+  */
+ static void
+ check_error_callback(void *arg)
+ {
+ 	PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;
+ 
+ 	if (estate->err_stmt != NULL)
+ 	{
+ 		/* translator: last %s is a plpgsql statement type name */
+ 		errcontext("checking of PL/pgSQL function \"%s\" line %d at %s",
+ 				   estate->func->fn_name,
+ 				   estate->err_stmt->lineno,
+ 				   plpgsql_stmt_typename(estate->err_stmt));
+ 	}
+ 	else
+ 		errcontext("checking of PL/pgSQL function \"%s\"",
+ 				   estate->func->fn_name);
+ }
+ 
+ /*
+  * Check function - it prepare variables and starts a prepare plan walker
+  *		called by function checker
+  */
+ void
+ plpgsql_check_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
+ {
+ 	PLpgSQL_execstate estate;
+ 	ErrorContextCallback plerrcontext;
+ 	int			i;
+ 
+ 	/* Setup error callback for ereport */
+ 	plerrcontext.callback = check_error_callback;
+ 	plerrcontext.arg = &estate;
+ 	plerrcontext.previous = error_context_stack;
+ 	error_context_stack = &plerrcontext;
+ 
+ 	/*
+ 	 * Setup the execution state - we would to reuse some exec routines
+ 	 * so we need a estate
+ 	 */
+ 	plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo);
+ 
+ 	/*
+ 	 * Make local execution copies of all the datums
+ 	 */
+ 	for (i = 0; i < estate.ndatums; i++)
+ 		estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
+ 
+ 	/*
+ 	 * Store the actual call argument values into the appropriate variables
+ 	 */
+ 	for (i = 0; i < func->fn_nargs; i++)
+ 	{
+ 		int			n = func->fn_argvarnos[i];
+ 
+ 		switch (estate.datums[n]->dtype)
+ 		{
+ 			case PLPGSQL_DTYPE_VAR:
+ 				{
+ 					var_init_to_null(&estate, n);
+ 				}
+ 				break;
+ 
+ 			case PLPGSQL_DTYPE_ROW:
+ 				{
+ 					PLpgSQL_row *row = (PLpgSQL_row *) estate.datums[n];
+ 
+ 					exec_move_row(&estate, NULL, row, NULL, NULL);
+ 				}
+ 				break;
+ 
+ 			default:
+ 				elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(&estate, (PLpgSQL_stmt *) func->action);
+ 
+ 	/* Cleanup temporary memory */
+ 	plpgsql_destroy_econtext(&estate);
+ 
+ 	/* Pop the error context stack */
+ 	error_context_stack = plerrcontext.previous;
+ }
+ 
+ /*
+  * Check trigger - prepare fake environments for testing trigger
+  *
+  */
+ void
+ plpgsql_check_trigger(PLpgSQL_function *func,
+ 					 TriggerData *trigdata)
+ {
+ 	PLpgSQL_execstate estate;
+ 	ErrorContextCallback plerrcontext;
+ 	PLpgSQL_rec *rec_new,
+ 			   *rec_old;
+ 	int			i;
+ 
+ 	/* Setup error callback for ereport */
+ 	plerrcontext.callback = check_error_callback;
+ 	plerrcontext.arg = &estate;
+ 	plerrcontext.previous = error_context_stack;
+ 	error_context_stack = &plerrcontext;
+ 
+ 	/*
+ 	 * Setup the execution state - we would to reuse some exec routines
+ 	 * so we need a estate
+ 	 */
+ 	plpgsql_estate_setup(&estate, func, NULL);
+ 
+ 	/*
+ 	 * Make local execution copies of all the datums
+ 	 */
+ 	for (i = 0; i < estate.ndatums; i++)
+ 		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 *) (estate.datums[func->new_varno]);
+ 	rec_new->freetup = false;
+ 	rec_new->freetupdesc = false;
+ 	assign_tupdesc_row_or_rec(&estate, NULL, rec_new, trigdata->tg_relation->rd_att);
+ 
+ 	rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
+ 	rec_old->freetup = false;
+ 	rec_old->freetupdesc = false;
+ 	assign_tupdesc_row_or_rec(&estate, NULL, rec_old, trigdata->tg_relation->rd_att);
+ 
+ 	/*
+ 	 * Assign the special tg_ variables
+ 	 */
+ 	var_init_to_null(&estate, func->tg_op_varno);
+ 	var_init_to_null(&estate, func->tg_name_varno);
+ 	var_init_to_null(&estate, func->tg_when_varno);
+ 	var_init_to_null(&estate, func->tg_level_varno);
+ 	var_init_to_null(&estate, func->tg_relid_varno);
+ 	var_init_to_null(&estate, func->tg_relname_varno);
+ 	var_init_to_null(&estate, func->tg_table_name_varno);
+ 	var_init_to_null(&estate, func->tg_table_schema_varno);
+ 	var_init_to_null(&estate, func->tg_nargs_varno);
+ 	var_init_to_null(&estate, func->tg_argv_varno);
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(&estate, (PLpgSQL_stmt *) func->action);
+ 
+ 	/* Cleanup temporary memory */
+ 	plpgsql_destroy_econtext(&estate);
+ 
+ 	/* Pop the error context stack */
+ 	error_context_stack = plerrcontext.previous;
+ }
+ 
+ /*
+  * Verify lvalue
+  *    It doesn't repeat a checks that are done.
+  *  Checks a subscript expressions, verify a validity of record's fields
+  */
+ static void
+ check_target(PLpgSQL_execstate *estate, int varno)
+ {
+ 	PLpgSQL_datum *target = estate->datums[varno];
+ 
+ 	switch (target->dtype)
+ 	{
+ 		case PLPGSQL_DTYPE_VAR:
+ 		case PLPGSQL_DTYPE_REC:
+ 			break;
+ 
+ 		case PLPGSQL_DTYPE_ROW:
+ 			check_row_or_rec(estate, (PLpgSQL_row *) target, NULL);
+ 			break;
+ 
+ 		case PLPGSQL_DTYPE_RECFIELD:
+ 			{
+ 				PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target;
+ 				PLpgSQL_rec *rec;
+ 				int			fno;
+ 
+ 				rec = (PLpgSQL_rec *) (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)));
+ 
+ 					check_expr(estate, arrayelem->subscript);
+ 
+ 					target = 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(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_execstate *estate, 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(estate, row->varnos[fnum]);
+ 		}
+ 	}
+ }
+ 
+ /*
+  * Generate a prepared plan - this is simplyfied copy from pl_exec.c
+  *   Is not necessary to check simple plan
+  */
+ static void
+ prepare_expr(PLpgSQL_execstate *estate,
+ 				  PLpgSQL_expr *expr, int cursorOptions)
+ {
+ 	SPIPlanPtr	plan;
+ 
+ 	/* leave when there are not expression */
+ 	if (expr == NULL)
+ 		return;
+ 
+ 	/* leave when plan is created */
+ 	if (expr->plan != NULL)
+ 		return;
+ 
+ 	/*
+ 	 * 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 = 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")));
+ 			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.")));
+ 			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_execstate *estate, PLpgSQL_expr *expr)
+ {
+ 	TupleDesc tupdesc;
+ 
+ 	if (expr != NULL)
+ 	{
+ 		prepare_expr(estate, expr, 0);
+ 		tupdesc = expr_get_desc(estate, expr, false, false, true);
+ 		ReleaseTupleDesc(tupdesc);
+ 	}
+ }
+ 
+ /*
+  * 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_execstate *estate,
+ 					    PLpgSQL_row *row, PLpgSQL_rec *rec,
+ 								    TupleDesc tupdesc)
+ {
+ 	bool	   *nulls;
+ 	HeapTuple  tup;
+ 
+ 	if (tupdesc == NULL)
+ 		elog(ERROR, "tuple descriptor is empty");
+ 
+ 	/*
+ 	 * row variable has assigned TupleDesc already, so don't be processed
+ 	 * here
+ 	 */
+ 	if (rec != NULL)
+ 	{
+ 		PLpgSQL_rec *target = (PLpgSQL_rec *)(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_execstate *estate, int varno, TupleDesc tupdesc)
+ {
+ 	PLpgSQL_datum *target = estate->datums[varno];
+ 
+ 	if (target->dtype == PLPGSQL_DTYPE_REC)
+ 		assign_tupdesc_row_or_rec(estate, NULL, (PLpgSQL_rec *) target, tupdesc);
+ }
+ 
+ /*
+  * Returns a tuple descriptor based on existing plan
+  */
+ static TupleDesc 
+ expr_get_desc(PLpgSQL_execstate *estate,
+ 						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);
+ 
+ 		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 (tupdesc->natts != 1)
+ 			ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 				 errmsg_plural("query \"%s\" returned %d column",
+ 							   "query \"%s\" returned %d columns",
+ 							   tupdesc->natts,
+ 							   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))));
+ 
+ 		/* 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
+ 			elog(ERROR, "cannot to identify real type for record type variable");
+ 	}
+ 
+ 	if (is_expression && tupdesc->natts != 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 			  errmsg_plural("query \"%s\" returned %d column",
+ 				   "query \"%s\" returned %d columns",
+ 				   tupdesc->natts,
+ 				   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(..)
+ 	 */
+ 	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)
+ 						elog(ERROR, "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
+  */
+ static void
+ check_stmts(PLpgSQL_execstate *estate, List *stmts)
+ {
+ 	ListCell *lc;
+ 
+ 	foreach(lc, stmts)
+ 	{
+ 		check_stmt(estate, (PLpgSQL_stmt *) lfirst(lc));
+ 	}
+ }
+ 
+ /*
+  * walk over all statements
+  */
+ static void
+ check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
+ {
+ 	TupleDesc	tupdesc = NULL;
+ 	PLpgSQL_function *func;
+ 	ListCell *l;
+ 
+ 	if (stmt == NULL)
+ 		return;
+ 
+ 	estate->err_stmt = stmt;
+ 	func = estate->func;
+ 
+ 	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++)
+ 				{
+ 					d = func->datums[stmt_block->initvarnos[i]];
+ 
+ 					if (d->dtype == PLPGSQL_DTYPE_VAR)
+ 					{
+ 						PLpgSQL_var *var = (PLpgSQL_var *) d;
+ 
+ 						check_expr(estate, var->default_val);
+ 					}
+ 				}
+ 
+ 				check_stmts(estate, stmt_block->body);
+ 				
+ 				if (stmt_block->exceptions)
+ 				{
+ 					foreach(l, stmt_block->exceptions->exc_list)
+ 					{
+ 						check_stmts(estate, ((PLpgSQL_exception *) lfirst(l))->action);
+ 					}
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_ASSIGN:
+ 			{
+ 				PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt;
+ 
+ 				/* prepare plan if desn't exist yet */
+ 				prepare_expr(estate, stmt_assign->expr, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_assign->expr,
+ 										false,		/* no element type */
+ 										true,		/* expand record */
+ 										true);		/* is expression */
+ 
+ 				/* check target, ensure target can get a result */
+ 				check_target(estate, stmt_assign->varno);
+ 
+ 				/* assign a tupdesc to record variable */
+ 				assign_tupdesc_dno(estate, stmt_assign->varno, tupdesc);
+ 				ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_IF:
+ 			{
+ 				PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt;
+ 				ListCell *l;
+ 
+ 				check_expr(estate, stmt_if->cond);
+ 
+ 				check_stmts(estate, stmt_if->then_body);
+ 
+ 				foreach(l, stmt_if->elsif_list)
+ 				{
+ 					PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l);
+ 
+ 					check_expr(estate, elif->cond);
+ 					check_stmts(estate, elif->stmts);
+ 				}
+ 
+ 				check_stmts(estate, 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 *) estate->datums[stmt_case->t_varno];
+ 
+ 					/* we need to set hidden variable type */
+ 					prepare_expr(estate, stmt_case->t_expr, 0);
+ 
+ 					tupdesc = expr_get_desc(estate,
+ 									stmt_case->t_expr,
+ 										false,		/* no element type */
+ 										false,		/* 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,
+ 												   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(estate, cwt->expr);
+ 					check_stmts(estate, cwt->stmts);
+ 				}
+ 
+ 				check_stmts(estate, stmt_case->else_stmts);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_LOOP:
+ 			check_stmts(estate, ((PLpgSQL_stmt_loop *) stmt)->body);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_WHILE:
+ 			{
+ 				PLpgSQL_stmt_while *stmt_while = (PLpgSQL_stmt_while *) stmt;
+ 
+ 				check_expr(estate, stmt_while->cond);
+ 				check_stmts(estate, stmt_while->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORI:
+ 			{
+ 				PLpgSQL_stmt_fori *stmt_fori = (PLpgSQL_stmt_fori *) stmt;
+ 
+ 				check_expr(estate, stmt_fori->lower);
+ 				check_expr(estate, stmt_fori->upper);
+ 				check_expr(estate, stmt_fori->step);
+ 
+ 				check_stmts(estate, stmt_fori->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORS:
+ 			{
+ 				PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt;
+ 
+ 				/* we need to set hidden variable type */
+ 				prepare_expr(estate, stmt_fors->query, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_fors->query,
+ 									false,		/* no element type */
+ 									false,		/* expand record */
+ 									false);		/* is expression */
+ 
+ 				check_row_or_rec(estate, stmt_fors->row, stmt_fors->rec);
+ 				assign_tupdesc_row_or_rec(estate, stmt_fors->row, stmt_fors->rec, tupdesc);
+ 
+ 				check_stmts(estate, stmt_fors->body);
+ 				ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORC:
+ 			{
+ 				PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt;
+ 				PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_forc->curvar];
+ 
+ 				prepare_expr(estate, stmt_forc->argquery, 0);
+ 
+ 				if (var->cursor_explicit_expr != NULL)
+ 				{
+ 					prepare_expr(estate, var->cursor_explicit_expr,
+ 								    var->cursor_options);
+ 
+ 					tupdesc = expr_get_desc(estate,
+ 									var->cursor_explicit_expr,
+ 										false,		/* no element type */
+ 										false,		/* expand record */
+ 										false);		/* is expression */
+ 
+ 					check_row_or_rec(estate, stmt_forc->row, stmt_forc->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_forc->row, stmt_forc->rec, tupdesc);
+ 				}
+ 
+ 				check_stmts(estate, stmt_forc->body);
+ 				if (tupdesc != NULL)
+ 					ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNFORS:
+ 			{
+ 				PLpgSQL_stmt_dynfors * stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt;
+ 
+ 				if (stmt_dynfors->rec != NULL)
+ 					elog(ERROR, "cannot determinate a result of dynamic SQL");
+ 
+ 				check_expr(estate, stmt_dynfors->query);
+ 
+ 				foreach(l, stmt_dynfors->params)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				check_stmts(estate, stmt_dynfors->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FOREACH_A:
+ 			{
+ 				PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt;
+ 
+ 				prepare_expr(estate, stmt_foreach_a->expr, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_foreach_a->expr,
+ 									true,		/* no element type */
+ 									false,		/* expand record */
+ 									true);		/* is expression */
+ 
+ 				check_target(estate, stmt_foreach_a->varno);
+ 				assign_tupdesc_dno(estate, stmt_foreach_a->varno, tupdesc);
+ 				ReleaseTupleDesc(tupdesc);
+ 
+ 				check_stmts(estate, stmt_foreach_a->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_EXIT:
+ 			check_expr(estate, ((PLpgSQL_stmt_exit *) stmt)->cond);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_PERFORM:
+ 			prepare_expr(estate, ((PLpgSQL_stmt_perform *) stmt)->expr, 0);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN:
+ 			check_expr(estate, ((PLpgSQL_stmt_return *) stmt)->expr);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN_NEXT:
+ 			check_expr(estate, ((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(estate, stmt_rq->dynquery);
+ 				prepare_expr(estate, stmt_rq->query, 0);
+ 
+ 				foreach(l, stmt_rq->params)
+ 				{
+ 					check_expr(estate, (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(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				foreach(l, stmt_raise->options)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				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;
+ 
+ 				prepare_expr(estate, stmt_execsql->sqlstmt, 0);
+ 				if (stmt_execsql->into)
+ 				{
+ 					tupdesc = expr_get_desc(estate,
+ 								stmt_execsql->sqlstmt,
+ 											false,		/* no element type */
+ 											false,		/* expand record */
+ 											false);		/* is expression */
+ 
+ 					/* check target, ensure target can get a result */
+ 					check_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec, tupdesc);
+ 					ReleaseTupleDesc(tupdesc);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNEXECUTE:
+ 			{
+ 				PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt;
+ 
+ 				check_expr(estate, stmt_dynexecute->query);
+ 
+ 				foreach(l, stmt_dynexecute->params)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				if (stmt_dynexecute->into)
+ 				{
+ 					if (stmt_dynexecute->rec != NULL)
+ 						elog(ERROR, "cannot determinate a result of dynamic SQL");
+ 
+ 					check_row_or_rec(estate, stmt_dynexecute->row, stmt_dynexecute->rec);
+ 				}
+ 			}
+ 			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)
+ 					prepare_expr(estate, var->cursor_explicit_expr,
+ 								   var->cursor_options);
+ 
+ 				prepare_expr(estate, stmt_open->query, 0);
+ 				prepare_expr(estate, stmt_open->argquery, 0);
+ 				check_expr(estate, stmt_open->dynquery);
+ 
+ 				foreach(l, stmt_open->params)
+ 				{
+ 					check_expr(estate, (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(estate, diag_item->target);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FETCH:
+ 			{
+ 				PLpgSQL_stmt_fetch *stmt_fetch = (PLpgSQL_stmt_fetch *) stmt;
+ 				PLpgSQL_var *var = (PLpgSQL_var *)(estate->datums[stmt_fetch->curvar]);
+ 
+ 				if (var != NULL && var->cursor_explicit_expr != NULL)
+ 				{
+ 					prepare_expr(estate, var->cursor_explicit_expr, 
+ 									    var->cursor_options);
+ 					tupdesc = expr_get_desc(estate,
+ 								    var->cursor_explicit_expr,
+ 											false,		/* no element type */
+ 											false,		/* expand record */
+ 											false);		/* is expression */
+ 					check_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec, tupdesc);
+ 					ReleaseTupleDesc(tupdesc);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_CLOSE:
+ 			break;
+ 
+ 		default:
+ 			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
+ 			return; /* be compiler quite */
+ 	}
+ }
+ 
+ /*
+  * Initialize variable to NULL
+  */
+ static void
+ var_init_to_null(PLpgSQL_execstate *estate, int varno)
+ {
+ 	PLpgSQL_var *var = (PLpgSQL_var *) estate->datums[varno];
+ 	var->value = (Datum) 0;
+ 	var->isnull = true;
+ 	var->freeval = false;
+ }
*** ./src/pl/plpgsql/src/pl_handler.c.orig	2011-11-29 19:20:59.494116771 +0100
--- ./src/pl/plpgsql/src/pl_handler.c	2011-11-29 19:21:24.529804431 +0100
***************
*** 312,314 ****
--- 312,452 ----
  
  	PG_RETURN_VOID();
  }
+ 
+ /* ----------
+  * plpgsql_checker
+  *
+  * This function attempts to check a embeded SQL inside a PL/pgSQL function at
+  * CHECK FUNCTION time. It should to have one or two parameters. Second
+  * parameter is a relation (used when function is trigger).
+  * ----------
+  */
+ PG_FUNCTION_INFO_V1(plpgsql_checker);
+ 
+ Datum
+ plpgsql_checker(PG_FUNCTION_ARGS)
+ {
+ 	Oid			funcoid = PG_GETARG_OID(0);
+ 	Oid			relid = PG_GETARG_OID(1);
+ 	HeapTuple	tuple;
+ 	FunctionCallInfoData fake_fcinfo;
+ 	FmgrInfo	flinfo;
+ 	TriggerData trigdata;
+ 	int			rc;
+ 	PLpgSQL_function *function;
+ 	PLpgSQL_execstate *cur_estate;
+ 
+ 	Form_pg_proc proc;
+ 	char		functyptype;
+ 	bool	   istrigger = false;
+ 
+ 	/* we don't need to repair a check done by validator */
+ 
+ 	tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
+ 	if (!HeapTupleIsValid(tuple))
+ 		elog(ERROR, "cache lookup failed for function %u", funcoid);
+ 	proc = (Form_pg_proc) GETSTRUCT(tuple);
+ 
+ 	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))
+ 		{
+ 			istrigger = true;
+ 			if (!OidIsValid(relid))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("PL/pgSQL trigger functions cannot be checked directly"),
+ 						 errhint("use CHECK TRIGGER statement instead")));
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Connect to SPI manager
+ 	 */
+ 	if ((rc = SPI_connect()) != SPI_OK_CONNECT)
+ 			elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
+ 
+ 	/*
+ 	 * Set up a fake fcinfo with just enough info to satisfy
+ 	 * plpgsql_compile().
+ 	 *
+ 	 * there should be a different real argtypes for polymorphic params
+ 	 */
+ 	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;
+ 		trigdata.tg_relation = relation_open(relid, AccessShareLock);
+ 		fake_fcinfo.context = (Node *) &trigdata;
+ 	}
+ 
+ 	/* Get a compiled function */
+ 	function = plpgsql_compile(&fake_fcinfo, false);
+ 
+ 	/* 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 prepare plans */
+ 	PG_TRY();
+ 	{
+ 		if (!istrigger)
+ 			plpgsql_check_function(function, &fake_fcinfo);
+ 		else
+ 			plpgsql_check_trigger(function, &trigdata);
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		if (istrigger)
+ 			relation_close(trigdata.tg_relation, AccessShareLock);
+ 
+ 		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);
+ 
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	if (istrigger)
+ 		relation_close(trigdata.tg_relation, AccessShareLock);
+ 
+ 	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);
+ 
+ 	/*
+ 	 * Disconnect from SPI manager
+ 	 */
+ 	if ((rc = SPI_finish()) != SPI_OK_FINISH)
+ 			elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
+ 
+ 	ReleaseSysCache(tuple);
+ 
+ 	PG_RETURN_VOID();
+ }
*** ./src/pl/plpgsql/src/plpgsql.h.orig	2011-11-29 19:20:59.500116698 +0100
--- ./src/pl/plpgsql/src/plpgsql.h	2011-11-29 20:22:19.423516596 +0100
***************
*** 902,907 ****
--- 902,908 ----
  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,916 ****
--- 912,918 ----
  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_checker(PG_FUNCTION_ARGS);
  
  /* ----------
   * Functions in pl_exec.c
***************
*** 928,933 ****
--- 930,939 ----
  extern void exec_get_datum_type_info(PLpgSQL_execstate *estate,
  						 PLpgSQL_datum *datum,
  						 Oid *typeid, int32 *typmod, Oid *collation);
+ extern void plpgsql_check_function(PLpgSQL_function *func,
+ 					 FunctionCallInfo fcinfo);
+ extern void plpgsql_check_trigger(PLpgSQL_function *func,
+ 					 TriggerData *trigdata);
  
  /* ----------
   * Functions for namespace handling in pl_funcs.c
*** ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql.orig	2011-11-29 19:20:59.502116672 +0100
--- ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql	2011-11-29 19:21:24.533804381 +0100
***************
*** 5,7 ****
--- 5,8 ----
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_call_handler();
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_inline_handler(internal);
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_validator(oid);
+ ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_checker(oid, regclass);
*** ./src/test/regress/expected/plpgsql.out.orig	2011-11-29 19:20:59.505116634 +0100
--- ./src/test/regress/expected/plpgsql.out	2011-11-29 19:21:24.536804342 +0100
***************
*** 302,307 ****
--- 302,310 ----
  ' 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
+ check trigger tg_hslot_biu on HSlot;
+ NOTICE:  checking function "tg_hslot_biu()"
  -- ************************************************************
  -- * BEFORE DELETE on HSlot
  -- *	- prevent from manual manipulation
***************
*** 635,640 ****
--- 638,645 ----
      raise exception ''illegal backlink beginning with %'', mytype;
  end;
  ' language plpgsql;
+ -- check function should not fail
+ check function tg_backlink_set(bpchar, bpchar);
  -- ************************************************************
  -- * Support function to clear out the backlink field if
  -- * it still points to specific slot
***************
*** 2802,2807 ****
--- 2807,2840 ----
   
  (1 row)
  
+ -- check function should not fail
+ check function for_vect();
+ -- 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
***************
*** 3283,3288 ****
--- 3316,3323 ----
    return;
  end;
  $$ language plpgsql;
+ -- check function should not fail
+ check function forc01();
  select forc01();
  NOTICE:  5 from c
  NOTICE:  6 from c
***************
*** 3716,3721 ****
--- 3751,3758 ----
    end case;
  end;
  $$ language plpgsql immutable;
+ -- check function should not fail
+ check function case_test(bigint);
  select case_test(1);
   case_test 
  -----------
***************
*** 4571,4573 ****
--- 4608,4942 ----
  CONTEXT:  PL/pgSQL function "testoa" line 5 at assignment
  drop function arrayassign1();
  drop function testoa(x1 int, x2 int, x3 int);
+ --
+ -- check function statement tests
+ --
+ create table t1(a int, b int);
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+ end;
+ $$ language plpgsql;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ check function f1();
+ ERROR:  column "c" of relation "t1" does not exist
+ LINE 1: update t1 set c = 30
+                       ^
+ QUERY:  update t1 set c = 30
+ CONTEXT:  checking of PL/pgSQL function "f1" line 4 at SQL statement
+ 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)
+ 
+ check function f1();
+ ERROR:  record "r" has no field "c"
+ CONTEXT:  SQL statement "SELECT r.c"
+ checking of PL/pgSQL function "f1" line 6 at RAISE
+ 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)
+ 
+ check function f1();
+ ERROR:  record "r" has no field "c"
+ CONTEXT:  SQL statement "SELECT r.c"
+ checking of PL/pgSQL function "f1" line 6 at RAISE
+ 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)
+ 
+ check function f1();
+ ERROR:  record "r" has no field "c"
+ CONTEXT:  checking of PL/pgSQL function "f1" line 6 at assignment
+ 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)
+ 
+ check function f1();
+ ERROR:  column "a" does not exist
+ LINE 1: SELECT a + b
+                ^
+ QUERY:  SELECT a + b
+ CONTEXT:  checking of PL/pgSQL function "f1" line 5 at assignment
+ 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)
+ 
+ check function f1();
+ ERROR:  too many parameters specified for RAISE
+ CONTEXT:  checking of PL/pgSQL function "f1" line 4 at RAISE
+ 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)
+ 
+ check function f1();
+ ERROR:  too few parameters specified for RAISE
+ CONTEXT:  checking of PL/pgSQL function "f1" line 4 at RAISE
+ 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)
+ 
+ check function f1();
+ ERROR:  column "c" does not exist
+ LINE 1: SELECT c+10
+                ^
+ QUERY:  SELECT c+10
+ CONTEXT:  checking of PL/pgSQL function "f1" line 5 at assignment
+ 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;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ check function f1();
+ ERROR:  subscripted object is not an array
+ CONTEXT:  checking of PL/pgSQL function "f1" line 5 at assignment
+ 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)
+ 
+ check function f1();
+ ERROR:  record "_exception" has no field "hint"
+ CONTEXT:  checking of PL/pgSQL function "f1" line 7 at GET DIAGNOSTICS
+ 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);
+ check trigger t1_f1 on t1;
+ NOTICE:  checking function "f1_trg()"
+ ERROR:  record "new" has no field "c"
+ CONTEXT:  SQL statement "SELECT new.c"
+ checking of PL/pgSQL function "f1_trg" line 5 at RAISE
+ 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
+ check trigger t1_f1 on t1;
+ NOTICE:  checking function "f1_trg()"
+ ERROR:  record "new" has no field "c"
+ CONTEXT:  checking of PL/pgSQL function "f1_trg" line 5 at assignment
+ -- 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
+ check trigger t1_f1 on t1;
+ NOTICE:  checking function "f1_trg()"
+ -- ok
+ insert into t1 values(6,30);
+ drop table t1;
+ drop type _exception_type;
+ drop function f1_trg();
*** ./src/test/regress/sql/plpgsql.sql.orig	2011-11-29 19:20:59.508116598 +0100
--- ./src/test/regress/sql/plpgsql.sql	2011-11-29 19:21:24.538804318 +0100
***************
*** 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
+ check trigger tg_hslot_biu on HSlot;
  
  -- ************************************************************
  -- * BEFORE DELETE on HSlot
***************
*** 747,752 ****
--- 749,757 ----
  end;
  ' language plpgsql;
  
+ -- check function should not fail
+ check function tg_backlink_set(bpchar, bpchar);
+ 
  
  -- ************************************************************
  -- * Support function to clear out the backlink field if
***************
*** 2335,2340 ****
--- 2340,2352 ----
  
  select for_vect();
  
+ -- check function should not fail
+ 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
***************
*** 2714,2719 ****
--- 2726,2734 ----
  end;
  $$ language plpgsql;
  
+ -- check function should not fail
+ check function forc01();
+ 
  select forc01();
  
  -- try updating the cursor's current row
***************
*** 3048,3053 ****
--- 3063,3071 ----
  end;
  $$ language plpgsql immutable;
  
+ -- check function should not fail
+ check function case_test(bigint);
+ 
  select case_test(1);
  select case_test(2);
  select case_test(3);
***************
*** 3600,3602 ****
--- 3618,3862 ----
  
  drop function arrayassign1();
  drop function testoa(x1 int, x2 int, x3 int);
+ 
+ --
+ -- check function statement tests
+ --
+ 
+ create table t1(a int, b int);
+ 
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+ end;
+ $$ language plpgsql;
+ 
+ select f1();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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;
+ 
+ select f1();
+ 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();
+ 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);
+ check trigger t1_f1 on 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
+ check trigger t1_f1 on 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
+ check trigger t1_f1 on t1;
+ 
+ -- ok
+ insert into t1 values(6,30);
+ 
+ drop table t1;
+ drop type _exception_type;
+ 
+ drop function f1_trg();
+ 
#22Peter Eisentraut
peter_e@gmx.net
In reply to: Tom Lane (#10)
Re: review: CHECK FUNCTION statement

On ons, 2011-11-30 at 10:53 -0500, Tom Lane wrote:

I think the important point here is that we need to support more than
one level of validation, and that the higher levels can't really be
applied by default in CREATE FUNCTION because they may fail on perfectly
valid code.

How would this work with anything other than PL/pgSQL in practice?

Here is an additional use case: There are a bunch of syntax and style
checkers for Python: pylint, pyflakes, pep8, pychecker, and maybe more.
I would like to have a way to use these for PL/Python. Right now I use
a tool I wrote called plpylint (https://github.com/petere/plpylint),
which pulls the source code out of the database and runs pylint on the
client, which works well enough, but what is being discussed here could
lead to a better solution.

So what I'd like to have is some way to say

check all plpythonu functions [in this schema or whatever] using
checker "pylint"

where "pylint" was previously defined as a checker associated with the
plpythonu language that actually invokes some user-defined function.

Also, what kind of report does this generate?

#23Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Tom Lane (#10)
Re: review: CHECK FUNCTION statement

Tom Lane wrote:

Do I understand right that the reason why the check function is
different from the validator function is that it would be more

difficult

to add the checks to the validator function?

Is that a good enough argument? From a user's perspective it is
difficult to see why some checks are performed at function creation
time, while others have to be explicitly checked with CHECK FUNCTION.
I think it would be much more intuitive if CHECK FUNCTION does
the same as function validation with check_function_bodies on.

I think the important point here is that we need to support more than
one level of validation, and that the higher levels can't really be
applied by default in CREATE FUNCTION because they may fail on

perfectly

valid code.

I understand now.

There are three levels of checking:
1) Validation with check_function_bodies = off (checks nothing).
2) Validation with check_function_bodies = on (checks syntax).
3) CHECK FUNCTION (checks RAISE and objects referenced in the function).

As long as 3) implies 2) (which I think it does), that makes sense.

I guess I was led astray by the documentation in plhandler.sgml:

Validator functions should typically honor the check_function_bodies
parameter: [...] this parameter is turned off by pg_dump so that it
can load procedural language functions without worrying about possible
dependencies of the function bodies on other database objects.

"Dependencyies on other database objects" seems more like a description
of CHECK FUNCTION.
But I guess that this documentation should be changed anyway to describe
the check function.

A bigger issue is that once you think about more than one kind of

check,

it becomes apparent that we might need some user-specifiable options

to

control which checks are applied. And I see no provision for that

here.

My attempt at a syntax that could also cover Peter's wish for multiple
checker functions:

CHECK FUNCTION { func(args) | ALL [IN SCHEMA schema] [FOR ROLE user] }
[ USING check_function ] OPTIONS (optname optarg [, ...])

Yours,
Laurenz Albe

#24Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#22)
Re: review: CHECK FUNCTION statement

Peter Eisentraut <peter_e@gmx.net> writes:

On ons, 2011-11-30 at 10:53 -0500, Tom Lane wrote:

I think the important point here is that we need to support more than
one level of validation, and that the higher levels can't really be
applied by default in CREATE FUNCTION because they may fail on perfectly
valid code.

How would this work with anything other than PL/pgSQL in practice?

Well, that's TBD by the individual PL authors, but it hardly seems
implausible that there might be lint-like checks applicable in many
PLs. As long as we have the functionality pushed out to a PL-specific
checker function, the details can be worked out later.

So what I'd like to have is some way to say

check all plpythonu functions [in this schema or whatever] using
checker "pylint"

where "pylint" was previously defined as a checker associated with the
plpythonu language that actually invokes some user-defined function.

That sounds like a language-specific option to me.

Also, what kind of report does this generate?

Good question. I suspect what Pavel has now will raise errors, but that
doesn't scale very nicely to checking more than one function, or even to
finding more than one bug in a single function.

My first instinct is to say that it should work like plain EXPLAIN, ie,
deliver a textual report that we send as if it were a query result.

regards, tom lane

#25Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#24)
Re: review: CHECK FUNCTION statement

Hello

Also, what kind of report does this generate?

Good question.  I suspect what Pavel has now will raise errors, but that
doesn't scale very nicely to checking more than one function, or even to
finding more than one bug in a single function.

I stop on first error now. Reason is reuse of functionality that can
to mark a critical point (bug) of embedded query in console.

Continuous checking is possible (plpgsql), but there are a few
critical bugs, that does stop. For example - any buggy assign to
record var causes uninitialized variable and any later checks are
affected. This is possible when ASSIGN, FOR SELECT, SELECT INTO
statements are used. It is small part of possible bugs but relative
often pattern. So I didn't worry about it yet.

My first instinct is to say that it should work like plain EXPLAIN, ie,
deliver a textual report that we send as if it were a query result.

can be - but EXPLAIN raises exception too, when there some error.
There should be a some possibility to simply identify result. I am not
sure if checking on empty result is good way. A raising exception
should be option. When we return text, then we have to think about
some structured form result - line, position, message. A advance of
exception on first issue is fact, so these questions are solved.

so CHECK can returns lines - like EXPLAIN, but can be nice and helpful
for this moment a GUC - some like check_raises_exception = on|off.
Default should be "off". And I have to think about some FORMAT option.

is it good plan?

Pavel

Show quoted text

                       regards, tom lane

#26Pavel Stehule
pavel.stehule@gmail.com
In reply to: Albe Laurenz (#23)
Re: review: CHECK FUNCTION statement

Hello

My attempt at a syntax that could also cover Peter's wish for multiple
checker functions:

CHECK FUNCTION { func(args) | ALL [IN SCHEMA schema] [FOR ROLE user] }
 [ USING check_function ] OPTIONS (optname optarg [, ...])

check_function should be related to one language, so you have to
specify language if you would to specify check_function (if we would
to have more check functions for one language).

Regards

Pavel Stehule

#27Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#26)
Re: review: CHECK FUNCTION statement

2011/12/2 Pavel Stehule <pavel.stehule@gmail.com>:

Hello

My attempt at a syntax that could also cover Peter's wish for multiple
checker functions:

CHECK FUNCTION { func(args) | ALL [IN SCHEMA schema] [FOR ROLE user] }
 [ USING check_function ] OPTIONS (optname optarg [, ...])

some other idea about other using CHECK FUNCTION

CHECK FUNCTION func(args)
RETURNS ... AS $$

$$ LANGUAGE xxx

This should to do check of function body without affect on registered
function. This is addition to previous defined syntax.

Nice a day

Pavel

#28Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Pavel Stehule (#1)
Re: review: CHECK FUNCTION statement

Pavel Stehule wrote:

My attempt at a syntax that could also cover Peter's wish for multiple
checker functions:

CHECK FUNCTION { func(args) | ALL [IN SCHEMA schema] [FOR ROLE user] }
 [ USING check_function ] OPTIONS (optname optarg [, ...])

check_function should be related to one language, so you have to
specify language if you would to specify check_function (if we would
to have more check functions for one language).

Right, I forgot LANGUAGE:

CHECK FUNCTION { func(args) | ALL IN LANGUAGE pl [IN SCHEMA schema] [FOR ROLE user] }
[ USING check_function ] OPTIONS (optname optarg [, ...])

If func(args) is given, the language can be inferred.

Yours,
Laurenz Albe

#29Pavel Stehule
pavel.stehule@gmail.com
In reply to: Albe Laurenz (#28)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Hello

there is a updated patch.

it support multi check, options and custom check functions are not
supported yet. I don't plan to implement custom check functions in
this round - I has not any example of usage - but we have agreement on
syntax and behave, so this should not be problem. I changed reporting
- from exception to warnings.

postgres=# check function all in schema public;
NOTICE: skip check function "hello()", it use C or internal language
NOTICE: skip check function "tri()", it is trigger function
NOTICE: skip check function "vloz_do_foo(integer)", language "sql"
hasn't checker function
NOTICE: skip check function "mojefunc(integer)", language "sql"
hasn't checker function
NOTICE: skip check function "myleft(text,integer)", language "sql"
hasn't checker function
NOTICE: checked function "array_random(integer,integer)"
NOTICE: skip check function "array_random1(integer,integer)",
language "sql" hasn't checker function
NOTICE: checked function "vrattab(integer)"
NOTICE: checked function "yyy()"
NOTICE: checked function "xxx()"
WARNING: error in function "zpracuj(integer)"
LINE 1: select h from hodnoty
^
DETAIL: column "h" does not exist
QUERY: select h from hodnoty
CONTEXT: line 4 at FOR over SELECT rows
WARNING: error in function "ii(integer)"
LINE 1: SELECT (select aa from i)
^
DETAIL: column "aa" does not exist
QUERY: SELECT (select aa from i)
CONTEXT: line 3 at RETURN
CHECK FUNCTION

postgres=# check function all in schema public in language plpgsql;
NOTICE: skip check function "tri()", it is trigger function
NOTICE: checked function "array_random(integer,integer)"
NOTICE: checked function "vrattab(integer)"
NOTICE: checked function "yyy()"
NOTICE: checked function "xxx()"
WARNING: error in function "zpracuj(integer)"
LINE 1: select h from hodnoty
^
DETAIL: column "h" does not exist
QUERY: select h from hodnoty
CONTEXT: line 4 at FOR over SELECT rows
WARNING: error in function "ii(integer)"
LINE 1: SELECT (select aa from i)
^
DETAIL: column "aa" does not exist
QUERY: SELECT (select aa from i)
CONTEXT: line 3 at RETURN
CHECK FUNCTION

postgres=# check function all in schema public in language plpgsql for role www;
NOTICE: nothing to check
CHECK FUNCTION

please, try it

Regards

Pavel Stehule

2011/12/3 Albe Laurenz <laurenz.albe@wien.gv.at>:

Show quoted text

Pavel Stehule wrote:

My attempt at a syntax that could also cover Peter's wish for multiple
checker functions:

CHECK FUNCTION { func(args) | ALL [IN SCHEMA schema] [FOR ROLE user] }
 [ USING check_function ] OPTIONS (optname optarg [, ...])

check_function should be related to one language, so you have to
specify language if you would to specify check_function (if we would
to have more check functions for one language).

Right, I forgot LANGUAGE:

CHECK FUNCTION { func(args) | ALL IN LANGUAGE pl [IN SCHEMA schema] [FOR ROLE user] }
 [ USING check_function ] OPTIONS (optname optarg [, ...])

If func(args) is given, the language can be inferred.

Yours,
Laurenz Albe

Attachments:

check_function-2011-12-07-4.difftext/x-patch; charset=US-ASCII; name=check_function-2011-12-07-4.diffDownload
*** ./doc/src/sgml/catalogs.sgml.orig	2011-12-07 05:59:37.123674706 +0100
--- ./doc/src/sgml/catalogs.sgml	2011-12-07 06:00:07.253332903 +0100
***************
*** 3652,3657 ****
--- 3652,3668 ----
       </row>
  
       <row>
+       <entry><structfield>lanchecker</structfield></entry>
+       <entry><type>oid</type></entry>
+       <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+       <entry>
+        This references a language checker function that is responsible
+        for checking a embedded SQL and can provide detailed checking.
+        Zero if no checker is provided.
+       </entry>
+      </row>
+ 
+      <row>
        <entry><structfield>lanacl</structfield></entry>
        <entry><type>aclitem[]</type></entry>
        <entry></entry>
*** ./doc/src/sgml/ref/allfiles.sgml.orig	2011-12-07 05:59:37.125674684 +0100
--- ./doc/src/sgml/ref/allfiles.sgml	2011-12-07 06:00:07.254332891 +0100
***************
*** 40,45 ****
--- 40,46 ----
  <!ENTITY alterView          SYSTEM "alter_view.sgml">
  <!ENTITY analyze            SYSTEM "analyze.sgml">
  <!ENTITY begin              SYSTEM "begin.sgml">
+ <!ENTITY checkFunction      SYSTEM "check_function.sgml">
  <!ENTITY checkpoint         SYSTEM "checkpoint.sgml">
  <!ENTITY close              SYSTEM "close.sgml">
  <!ENTITY cluster            SYSTEM "cluster.sgml">
*** ./doc/src/sgml/ref/create_language.sgml.orig	2011-12-07 05:59:37.127674661 +0100
--- ./doc/src/sgml/ref/create_language.sgml	2011-12-07 06:00:07.256332868 +0100
***************
*** 23,29 ****
  <synopsis>
  CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
  CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ]
  </synopsis>
   </refsynopsisdiv>
  
--- 23,29 ----
  <synopsis>
  CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
  CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ] [ CHECK <replaceable>checkfunction</replaceable> ]
  </synopsis>
   </refsynopsisdiv>
  
***************
*** 217,222 ****
--- 217,236 ----
        </para>
       </listitem>
      </varlistentry>
+ 
+     <varlistentry>
+      <term><literal>CHECK</literal> <replaceable class="parameter">checkfunction</replaceable></term>
+ 
+      <listitem>
+       <para><replaceable class="parameter">checkfunction</replaceable> is the
+        name of a previously registered function that will be called
+        when a new function in the language is created, to check the
+        function by statemnt <command>CHECK FUNCTION</command> or 
+        <command>CHECK TRIGGER</command>.
+       </para>
+      </listitem>
+     </varlistentry>
+ 
     </variablelist>
  
    <para>
*** ./doc/src/sgml/reference.sgml.orig	2011-12-07 05:59:37.128674649 +0100
--- ./doc/src/sgml/reference.sgml	2011-12-07 06:00:07.257332857 +0100
***************
*** 68,73 ****
--- 68,74 ----
     &alterView;
     &analyze;
     &begin;
+    &checkFunction;
     &checkpoint;
     &close;
     &cluster;
*** ./doc/src/sgml/ref/check_function.sgml.orig	2011-12-07 05:59:58.965426923 +0100
--- ./doc/src/sgml/ref/check_function.sgml	2011-12-06 17:54:28.000000000 +0100
***************
*** 0 ****
--- 1,38 ----
+ <!--
+ doc/src/sgml/ref/check_function.sgml
+ -->
+ 
+ <refentry id="SQL-CHECKFUNCTION">
+  <refmeta>
+   <refentrytitle>CHECK FUNCTION</refentrytitle>
+   <manvolnum>7</manvolnum>
+   <refmiscinfo>SQL - Language Statements</refmiscinfo>
+  </refmeta>
+ 
+  <refnamediv>
+   <refname>CHECK FUNCTION</refname>
+   <refpurpose>ensure a deep checking of existing function</refpurpose>
+  </refnamediv>
+ 
+  <indexterm zone="sql-checkfunction">
+   <primary>CHECK FUNCTION</primary>
+  </indexterm>
+ 
+  <refsynopsisdiv>
+ <synopsis>
+ CHECK FUNCTION <replaceable class="parameter">name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] )
+  | CHECK FUNCTION ALL [ IN LANGUAGE <replaceable class="parameter">langname</replaceable> ] [ IN SCHEMA <replaceable class="parameter">schemaname</replaceable> ] [ FOR ROLE <replaceable class="parameter">ownername</replaceable> ]
+  | CHECK TRIGGER <replaceable class="parameter">name</replaceable> ON <replaceable class="parameter">tablename</replaceable>
+ </synopsis>
+  </refsynopsisdiv>
+ 
+  <refsect1 id="sql-checkfunction-description">
+   <title>Description</title>
+ 
+   <para>
+    <command>CHECK FUNCTION</command> check a existing function.
+    <command>CHECK TRIGGER</command> check a trigger function.
+   </para>
+  </refsect1>
+ 
+ </refentry>
*** ./src/backend/catalog/pg_proc.c.orig	2011-12-07 05:59:37.131674614 +0100
--- ./src/backend/catalog/pg_proc.c	2011-12-07 06:00:07.260332824 +0100
***************
*** 1101,1103 ****
--- 1101,1104 ----
  	*newcursorpos = newcp;
  	return false;
  }
+ 
*** ./src/backend/commands/functioncmds.c.orig	2011-12-07 05:59:37.132674603 +0100
--- ./src/backend/commands/functioncmds.c	2011-12-07 07:58:31.520772351 +0100
***************
*** 35,53 ****
--- 35,58 ----
  #include "access/genam.h"
  #include "access/heapam.h"
  #include "access/sysattr.h"
+ #include "access/xact.h"
  #include "catalog/dependency.h"
  #include "catalog/indexing.h"
  #include "catalog/objectaccess.h"
  #include "catalog/pg_aggregate.h"
  #include "catalog/pg_cast.h"
  #include "catalog/pg_language.h"
+ #include "catalog/namespace.h"
  #include "catalog/pg_namespace.h"
  #include "catalog/pg_proc.h"
  #include "catalog/pg_proc_fn.h"
+ #include "catalog/pg_trigger.h"
  #include "catalog/pg_type.h"
  #include "catalog/pg_type_fn.h"
  #include "commands/defrem.h"
  #include "commands/proclang.h"
+ #include "commands/trigger.h"
+ #include "executor/spi_priv.h"
  #include "miscadmin.h"
  #include "optimizer/var.h"
  #include "parser/parse_coerce.h"
***************
*** 60,66 ****
--- 65,73 ----
  #include "utils/fmgroids.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
+ #include "utils/memutils.h"
  #include "utils/rel.h"
+ #include "utils/resowner.h"
  #include "utils/syscache.h"
  #include "utils/tqual.h"
  
***************
*** 1009,1014 ****
--- 1016,1365 ----
  	}
  }
  
+ /*
+  * Search and execute related checker function
+  */
+ static void
+ CheckFunctionById(Oid funcOid, Oid relid)
+ {
+ 	HeapTuple	tup;
+ 	Form_pg_proc proc;
+ 	HeapTuple	languageTuple;
+ 	Form_pg_language languageStruct;
+ 	Oid		languageChecker;
+ 	bool	found_bug;
+ 	char *funcname;
+ 
+ 	tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
+ 	if (!HeapTupleIsValid(tup)) /* should not happen */
+ 		elog(ERROR, "cache lookup failed for function %u", funcOid);
+ 
+ 	proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 	languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
+ 	Assert(HeapTupleIsValid(languageTuple));
+ 
+ 	languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
+ 	languageChecker = languageStruct->lanchecker;
+ 
+ 	funcname = format_procedure(funcOid);
+ 
+ 	/* Check a function body */
+ 	if (OidIsValid(languageChecker))
+ 	{
+ 		ArrayType  *set_items = NULL;
+ 		int			save_nestlevel = 0;
+ 		Datum	datum;
+ 		bool		isnull;
+ 		MemoryContext oldCxt;
+ 		MemoryContext checkCxt;
+ 		ResourceOwner		oldowner;
+ 
+ 		datum = SysCacheGetAttr(PROCOID, tup, Anum_pg_proc_proconfig, &isnull);
+ 
+ 		if (!isnull)
+ 		{
+ 			/* Set per-function configuration parameters */
+ 			set_items = (ArrayType *) DatumGetPointer(datum);
+ 			if (set_items)			/* Need a new GUC nesting level */
+ 			{
+ 				save_nestlevel = NewGUCNestLevel();
+ 				ProcessGUCArray(set_items,
+ 							(superuser() ? PGC_SUSET : PGC_USERSET),
+ 							PGC_S_SESSION,
+ 							GUC_ACTION_SAVE);
+ 			}
+ 			else
+ 				save_nestlevel = 0; /* keep compiler quiet */
+ 		}
+ 
+ 		checkCxt = AllocSetContextCreate(CurrentMemoryContext,
+ 									"Check temporary context",
+ 									ALLOCSET_DEFAULT_MINSIZE,
+ 									ALLOCSET_DEFAULT_INITSIZE,
+ 									ALLOCSET_DEFAULT_MAXSIZE);
+ 
+ 		oldCxt = MemoryContextSwitchTo(checkCxt);
+ 
+ 		oldowner = CurrentResourceOwner;
+ 		BeginInternalSubTransaction(NULL);
+ 		MemoryContextSwitchTo(checkCxt);
+ 
+ 		/*
+ 		 * forward exception to warning
+ 		 */
+ 		PG_TRY();
+ 		{
+ 			OidFunctionCall2(languageChecker, ObjectIdGetDatum(funcOid), 
+ 								    ObjectIdGetDatum(relid));
+ 
+ 			RollbackAndReleaseCurrentSubTransaction();
+ 			MemoryContextSwitchTo(checkCxt);
+ 			CurrentResourceOwner = oldowner;
+ 
+ 			found_bug = false;
+ 		}
+ 		PG_CATCH();
+ 		{
+ 			ErrorData *edata;
+ 
+ 			MemoryContextSwitchTo(checkCxt);
+ 			edata = CopyErrorData();
+ 			FlushErrorState();
+ 
+ 			RollbackAndReleaseCurrentSubTransaction();
+ 			MemoryContextSwitchTo(checkCxt);
+ 			CurrentResourceOwner = oldowner;
+ 
+ 			edata->elevel = WARNING;
+ 
+ 			ereport(WARNING,
+ 					(errmsg("error in function \"%s\"", funcname),
+ 					 edata->message != NULL ? errdetail_internal("%s", edata->message) : 0,
+ 					 edata->context != NULL ? errcontext("%s",edata->context) : 0,
+ 					 edata->internalquery != NULL ? internalerrquery(edata->internalquery) : 0,
+ 					 internalerrposition(edata->internalpos)));
+ 
+ 			FreeErrorData(edata);
+ 
+ 			found_bug = true;
+ 		}
+ 		PG_END_TRY();
+ 
+ 		MemoryContextSwitchTo(oldCxt);
+ 
+ 		if (set_items)
+ 			AtEOXact_GUC(true, save_nestlevel);
+ 
+ 		if (!found_bug)
+ 			elog(NOTICE, "checked function \"%s\"",
+ 							funcname);
+ 	}
+ 	else
+ 		elog(NOTICE, "skip check function \"%s\", language \"%s\" hasn't checker function",
+ 					funcname,
+ 							    NameStr(languageStruct->lanname));
+ 
+ 	pfree(funcname);
+ 
+ 	ReleaseSysCache(languageTuple);
+ 	ReleaseSysCache(tup);
+ }
+ 
+ /*
+  * CheckFunction
+  *			call a PL checker function when this function exists.
+  */
+ void
+ CheckFunction(CheckFunctionStmt *stmt)
+ {
+ 	List	   *functionName = stmt->funcname;
+ 	List	   *argTypes = stmt->args;	/* list of TypeName nodes */
+ 	Oid			funcOid = InvalidOid;
+ 	HeapTuple	tup;
+ 	Oid trgOid = InvalidOid;
+ 	Oid	relid = InvalidOid;
+ 	Oid languageId;
+ 	int nkeys = 0;
+ 	List	*objects = NIL;
+ 
+ 	/*
+ 	 * when Check stmt is multiple statement, then
+ 	 * prepare list of processed functions.
+ 	 */
+ 	if (stmt->options != NIL)
+ 	{
+ 		ScanKeyData key[3];
+ 		Relation rel;
+ 		HeapScanDesc scan;
+ 		bool	immutable_language = false;
+ 		ListCell *option;
+ 		bool	language_opt =  false;
+ 		bool	schema_opt = false;
+ 		bool	owner_opt = false;
+ 
+ 		foreach(option, stmt->options)
+ 		{
+ 			DefElem    *defel = (DefElem *) lfirst(option);
+ 
+ 			if (strcmp(defel->defname, "language") == 0)
+ 			{
+ 				HeapTuple	languageTuple;
+ 				Form_pg_language languageStruct;
+ 				Oid		languageChecker;
+ 				char *language = defGetString(defel);
+ 
+ 				if (language_opt)
+ 					elog(ERROR, "multiple usage of IN LANGUAGE option");
+ 				language_opt = true;
+ 
+ 				languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language));
+ 				if (!HeapTupleIsValid(languageTuple))
+ 					ereport(ERROR,
+ 						(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 						 errmsg("language \"%s\" does not exist", language),
+ 						 (PLTemplateExists(language) ?
+ 						  errhint("Use CREATE LANGUAGE to load the language into the database.") : 0)));
+ 
+ 				languageId = HeapTupleGetOid(languageTuple);
+ 				if (languageId == INTERNALlanguageId || languageId == ClanguageId)
+ 					elog(ERROR, "cannot to check functions in C or internal languages");
+ 
+ 				languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
+ 				languageChecker = languageStruct->lanchecker;
+ 				if (!OidIsValid(languageChecker))
+ 					elog(ERROR, "language \"%s\" has no defined checker function",
+ 							    language);
+ 
+ 				ScanKeyInit(&key[nkeys++],
+ 							Anum_pg_proc_prolang,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(languageId));
+ 
+ 				ReleaseSysCache(languageTuple);
+ 
+ 				immutable_language = true;
+ 			}
+ 			else if (strcmp(defel->defname, "schema") == 0)
+ 			{
+ 				Oid namespaceId = LookupExplicitNamespace(defGetString(defel));
+ 
+ 				if (schema_opt)
+ 					elog(ERROR, "multiple usage of IN SCHEMA option");
+ 				schema_opt = true;
+ 
+ 				ScanKeyInit(&key[nkeys++],
+ 							Anum_pg_proc_pronamespace,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(namespaceId));
+ 
+ 			}
+ 			else if (strcmp(defel->defname, "owner") == 0)
+ 			{
+ 				Oid ownerId = get_role_oid(defGetString(defel), false);
+ 
+ 				if (owner_opt)
+ 					elog(ERROR, "multiple usage of FOR ROLE option");
+ 				owner_opt = true;
+ 
+ 				ScanKeyInit(&key[nkeys++],
+ 							Anum_pg_proc_proowner,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(ownerId));
+ 
+ 			}
+ 			else
+ 				elog(ERROR, "option \"%s\" not recognized",
+ 					 defel->defname);
+ 		}
+ 
+ 		rel = heap_open(ProcedureRelationId,AccessShareLock);
+ 		scan = heap_beginscan(rel, SnapshotNow, nkeys, key);
+ 
+ 		while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+ 		{
+ 			Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 			funcOid = HeapTupleGetOid(tup);
+ 
+ 			if (!immutable_language)
+ 			{
+ 				Oid prolang = proc->prolang;
+ 
+ 				/* skip C or internal */
+ 				if (prolang == INTERNALlanguageId || prolang == ClanguageId)
+ 				{
+ 					elog(NOTICE, "skip check function \"%s\", it use C or internal language",
+ 								format_procedure(funcOid));
+ 					continue;
+ 				}
+ 			}
+ 
+ 			if (proc->prorettype == TRIGGEROID)
+ 			{
+ 				elog(NOTICE, "skip check function \"%s\", it is trigger function",
+ 							format_procedure(funcOid));
+ 				continue;
+ 			}
+ 
+ 			objects = lappend_oid(objects, funcOid);
+ 		}
+ 
+ 		heap_endscan(scan);
+ 		heap_close(rel, AccessShareLock);
+ 	}
+ 
+ 	/* when we should to check trigger, then we should to find a trigger handler */
+ 	else if (stmt->trgname != NULL)
+ 	{
+ 		HeapTuple	ht_trig;
+ 		Form_pg_trigger trigrec;
+ 		ScanKeyData skey[1];
+ 		Relation	tgrel;
+ 		SysScanDesc tgscan;
+ 
+ 		relid = RangeVarGetRelid(stmt->relation, ShareLock, false);
+ 		trgOid = get_trigger_oid(relid, stmt->trgname, false);
+ 
+ 		/*
+ 		 * Fetch the pg_trigger tuple by the Oid of the trigger
+ 		 */
+ 		tgrel = heap_open(TriggerRelationId, AccessShareLock);
+ 
+ 		ScanKeyInit(&skey[0],
+ 					ObjectIdAttributeNumber,
+ 					BTEqualStrategyNumber, F_OIDEQ,
+ 					ObjectIdGetDatum(trgOid));
+ 
+ 		tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true,
+ 									SnapshotNow, 1, skey);
+ 
+ 		ht_trig = systable_getnext(tgscan);
+ 
+ 		if (!HeapTupleIsValid(ht_trig))
+ 			elog(ERROR, "could not find tuple for trigger %u", trgOid);
+ 
+ 		trigrec = (Form_pg_trigger) GETSTRUCT(ht_trig);
+ 
+ 		/* we need to know trigger function to get PL checker function */
+ 		funcOid = trigrec->tgfoid;
+ 
+ 		/* Clean up */
+ 		systable_endscan(tgscan);
+ 
+ 		heap_close(tgrel, AccessShareLock);
+ 	}
+ 	else if (stmt->funcname != NULL)
+ 	{
+ 		/*
+ 		 * Find the function, 
+ 		 */
+ 		funcOid = LookupFuncNameTypeNames(functionName, argTypes, false);
+ 	}
+ 
+ 	if (funcOid == InvalidOid && objects == NIL)
+ 	{
+ 		elog(NOTICE, "nothing to check");
+ 		return;
+ 	}
+ 
+ 	if (objects != NIL)
+ 	{
+ 		ListCell *object;
+ 
+ 		foreach(object, objects)
+ 		{
+ 			CHECK_FOR_INTERRUPTS();
+ 
+ 			funcOid = lfirst_oid(object);
+ 			CheckFunctionById(funcOid, InvalidOid);
+ 		}
+ 	}
+ 	else
+ 	{
+ 		CheckFunctionById(funcOid, relid);
+ 	}
+ }
  
  /*
   * Rename function
*** ./src/backend/commands/proclang.c.orig	2011-12-07 05:59:37.134674581 +0100
--- ./src/backend/commands/proclang.c	2011-12-07 06:00:07.264332779 +0100
***************
*** 46,57 ****
  	char	   *tmplhandler;	/* name of handler function */
  	char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
  	char	   *tmplvalidator;	/* name of validator function, or NULL */
  	char	   *tmpllibrary;	/* path of shared library */
  } PLTemplate;
  
  static void create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, bool trusted);
  static PLTemplate *find_language_template(const char *languageName);
  static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
  							Oid newOwnerId);
--- 46,58 ----
  	char	   *tmplhandler;	/* name of handler function */
  	char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
  	char	   *tmplvalidator;	/* name of validator function, or NULL */
+ 	char	   *tmplchecker;	/* name of checker function, or NULL */
  	char	   *tmpllibrary;	/* path of shared library */
  } PLTemplate;
  
  static void create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, Oid checkerOid, bool trusted);
  static PLTemplate *find_language_template(const char *languageName);
  static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
  							Oid newOwnerId);
***************
*** 67,75 ****
  	PLTemplate *pltemplate;
  	Oid			handlerOid,
  				inlineOid,
! 				valOid;
  	Oid			funcrettype;
! 	Oid			funcargtypes[1];
  
  	/*
  	 * If we have template information for the language, ignore the supplied
--- 68,77 ----
  	PLTemplate *pltemplate;
  	Oid			handlerOid,
  				inlineOid,
! 				valOid,
! 				checkerOid;
  	Oid			funcrettype;
! 	Oid			funcargtypes[2];
  
  	/*
  	 * If we have template information for the language, ignore the supplied
***************
*** 219,228 ****
  		else
  			valOid = InvalidOid;
  
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, pltemplate->tmpltrusted);
  	}
  	else
  	{
--- 221,269 ----
  		else
  			valOid = InvalidOid;
  
+ 		/*
+ 		 * Likewise for the checker, if required; but we don't care about
+ 		 * its return type.
+ 		 */
+ 		if (pltemplate->tmplchecker)
+ 		{
+ 			funcname = SystemFuncName(pltemplate->tmplchecker);
+ 			funcargtypes[0] = OIDOID;
+ 			funcargtypes[1] = REGCLASSOID;
+ 			checkerOid = LookupFuncName(funcname, 2, funcargtypes, true);
+ 			if (!OidIsValid(checkerOid))
+ 			{
+ 				checkerOid = ProcedureCreate(pltemplate->tmplchecker,
+ 										 PG_CATALOG_NAMESPACE,
+ 										 false, /* replace */
+ 										 false, /* returnsSet */
+ 										 VOIDOID,
+ 										 ClanguageId,
+ 										 F_FMGR_C_VALIDATOR,
+ 										 pltemplate->tmplchecker,
+ 										 pltemplate->tmpllibrary,
+ 										 false, /* isAgg */
+ 										 false, /* isWindowFunc */
+ 										 false, /* security_definer */
+ 										 true,	/* isStrict */
+ 										 PROVOLATILE_VOLATILE,
+ 										 buildoidvector(funcargtypes, 2),
+ 										 PointerGetDatum(NULL),
+ 										 PointerGetDatum(NULL),
+ 										 PointerGetDatum(NULL),
+ 										 NIL,
+ 										 PointerGetDatum(NULL),
+ 										 1,
+ 										 0);
+ 			}
+ 		}
+ 		else
+ 			checkerOid = InvalidOid;
+ 
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, checkerOid, pltemplate->tmpltrusted);
  	}
  	else
  	{
***************
*** 294,303 ****
  		else
  			valOid = InvalidOid;
  
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, stmt->pltrusted);
  	}
  }
  
--- 335,355 ----
  		else
  			valOid = InvalidOid;
  
+ 		/* validate the checker function */
+ 		if (stmt->plchecker)
+ 		{
+ 			funcargtypes[0] = OIDOID;
+ 			funcargtypes[1] = REGCLASSOID;
+ 			checkerOid = LookupFuncName(stmt->plchecker, 2, funcargtypes, false);
+ 			/* return value is ignored, so we don't check the type */
+ 		}
+ 		else
+ 			checkerOid = InvalidOid;
+ 
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, checkerOid, stmt->pltrusted);
  	}
  }
  
***************
*** 307,313 ****
  static void
  create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, bool trusted)
  {
  	Relation	rel;
  	TupleDesc	tupDesc;
--- 359,365 ----
  static void
  create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, Oid checkerOid, bool trusted)
  {
  	Relation	rel;
  	TupleDesc	tupDesc;
***************
*** 337,342 ****
--- 389,395 ----
  	values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid);
  	values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid);
  	values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid);
+ 	values[Anum_pg_language_lanchecker - 1] = ObjectIdGetDatum(checkerOid);
  	nulls[Anum_pg_language_lanacl - 1] = true;
  
  	/* Check for pre-existing definition */
***************
*** 423,428 ****
--- 476,490 ----
  		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  	}
  
+ 	/* dependency on the checker function, if any */
+ 	if (OidIsValid(checkerOid))
+ 	{
+ 		referenced.classId = ProcedureRelationId;
+ 		referenced.objectId = checkerOid;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	/* Post creation hook for new procedural language */
  	InvokeObjectAccessHook(OAT_POST_CREATE,
  						   LanguageRelationId, myself.objectId, 0);
***************
*** 478,483 ****
--- 540,550 ----
  		if (!isnull)
  			result->tmplvalidator = TextDatumGetCString(datum);
  
+ 		datum = heap_getattr(tup, Anum_pg_pltemplate_tmplchecker,
+ 							 RelationGetDescr(rel), &isnull);
+ 		if (!isnull)
+ 			result->tmplchecker = TextDatumGetCString(datum);
+ 
  		datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary,
  							 RelationGetDescr(rel), &isnull);
  		if (!isnull)
*** ./src/backend/nodes/copyfuncs.c.orig	2011-12-07 05:59:37.135674570 +0100
--- ./src/backend/nodes/copyfuncs.c	2011-12-07 06:38:24.420227380 +0100
***************
*** 2880,2885 ****
--- 2880,2900 ----
  	return newnode;
  }
  
+ static CheckFunctionStmt *
+ _copyCheckFunctionStmt(CheckFunctionStmt *from)
+ {
+ 	CheckFunctionStmt *newnode = makeNode(CheckFunctionStmt);
+ 
+ 	COPY_NODE_FIELD(funcname);
+ 	COPY_NODE_FIELD(args);
+ 	COPY_STRING_FIELD(trgname);
+ 	COPY_NODE_FIELD(relation);
+ 	COPY_SCALAR_FIELD(is_function);
+ 	COPY_NODE_FIELD(options);
+ 
+ 	return newnode;
+ }
+ 
  static DoStmt *
  _copyDoStmt(DoStmt *from)
  {
***************
*** 4165,4170 ****
--- 4180,4188 ----
  		case T_AlterFunctionStmt:
  			retval = _copyAlterFunctionStmt(from);
  			break;
+ 		case T_CheckFunctionStmt:
+ 			retval = _copyCheckFunctionStmt(from);
+ 			break;
  		case T_DoStmt:
  			retval = _copyDoStmt(from);
  			break;
*** ./src/backend/nodes/equalfuncs.c.orig	2011-12-07 05:59:37.137674548 +0100
--- ./src/backend/nodes/equalfuncs.c	2011-12-07 06:38:56.187881989 +0100
***************
*** 1292,1297 ****
--- 1292,1310 ----
  }
  
  static bool
+ _equalCheckFunctionStmt(CheckFunctionStmt *a, CheckFunctionStmt *b)
+ {
+ 	COMPARE_NODE_FIELD(funcname);
+ 	COMPARE_NODE_FIELD(args);
+ 	COMPARE_STRING_FIELD(trgname);
+ 	COMPARE_NODE_FIELD(relation);
+ 	COMPARE_SCALAR_FIELD(is_function);
+ 	COMPARE_NODE_FIELD(options);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalDoStmt(DoStmt *a, DoStmt *b)
  {
  	COMPARE_NODE_FIELD(args);
***************
*** 2708,2713 ****
--- 2721,2729 ----
  		case T_AlterFunctionStmt:
  			retval = _equalAlterFunctionStmt(a, b);
  			break;
+ 		case T_CheckFunctionStmt:
+ 			retval = _equalCheckFunctionStmt(a, b);
+ 			break;
  		case T_DoStmt:
  			retval = _equalDoStmt(a, b);
  			break;
*** ./src/backend/parser/gram.y.orig	2011-12-07 05:59:37.138674536 +0100
--- ./src/backend/parser/gram.y	2011-12-07 06:33:34.230177891 +0100
***************
*** 227,232 ****
--- 227,233 ----
  		DeallocateStmt PrepareStmt ExecuteStmt
  		DropOwnedStmt ReassignOwnedStmt
  		AlterTSConfigurationStmt AlterTSDictionaryStmt
+ 		CheckFunctionStmt
  
  %type <node>	select_no_parens select_with_parens select_clause
  				simple_select values_clause
***************
*** 276,282 ****
  
  %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
  				opt_class opt_inline_handler opt_validator validator_clause
! 				opt_collate
  
  %type <range>	qualified_name OptConstrFromTable
  
--- 277,283 ----
  
  %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
  				opt_class opt_inline_handler opt_validator validator_clause
! 				opt_collate opt_checker
  
  %type <range>	qualified_name OptConstrFromTable
  
***************
*** 463,469 ****
  %type <windef>	window_definition over_clause window_specification
  				opt_frame_clause frame_extent frame_bound
  %type <str>		opt_existing_window_name
! 
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
--- 464,471 ----
  %type <windef>	window_definition over_clause window_specification
  				opt_frame_clause frame_extent frame_bound
  %type <str>		opt_existing_window_name
! %type <defelt>	CheckFunctionOptElem
! %type <list>	CheckFunctionOpts
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
***************
*** 700,705 ****
--- 702,708 ----
  			| AlterUserSetStmt
  			| AlterUserStmt
  			| AnalyzeStmt
+ 			| CheckFunctionStmt
  			| CheckPointStmt
  			| ClosePortalStmt
  			| ClusterStmt
***************
*** 3174,3184 ****
  				n->plhandler = NIL;
  				n->plinline = NIL;
  				n->plvalidator = NIL;
  				n->pltrusted = false;
  				$$ = (Node *)n;
  			}
  			| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
! 			  HANDLER handler_name opt_inline_handler opt_validator
  			{
  				CreatePLangStmt *n = makeNode(CreatePLangStmt);
  				n->replace = $2;
--- 3177,3188 ----
  				n->plhandler = NIL;
  				n->plinline = NIL;
  				n->plvalidator = NIL;
+ 				n->plchecker = NIL;
  				n->pltrusted = false;
  				$$ = (Node *)n;
  			}
  			| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
! 			  HANDLER handler_name opt_inline_handler opt_validator opt_checker
  			{
  				CreatePLangStmt *n = makeNode(CreatePLangStmt);
  				n->replace = $2;
***************
*** 3186,3191 ****
--- 3190,3196 ----
  				n->plhandler = $8;
  				n->plinline = $9;
  				n->plvalidator = $10;
+ 				n->plchecker = $11;
  				n->pltrusted = $3;
  				$$ = (Node *)n;
  			}
***************
*** 3220,3225 ****
--- 3225,3235 ----
  			| /*EMPTY*/								{ $$ = NIL; }
  		;
  
+ opt_checker:
+ 			CHECK handler_name					{ $$ = $2; }
+ 			| /*EMPTY*/								{ $$ = NIL; }
+ 		;
+ 
  DropPLangStmt:
  			DROP opt_procedural LANGUAGE ColId_or_Sconst opt_drop_behavior
  				{
***************
*** 6250,6255 ****
--- 6260,6333 ----
  
  /*****************************************************************************
   *
+  *		CHECK FUNCTION funcname(args)
+  *		CHECK TRIGGER triggername ON table
+  *
+  *
+  *****************************************************************************/
+ 
+ 
+ CheckFunctionStmt:
+ 			CHECK FUNCTION func_name func_args
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = $3;
+ 					n->args = extractArgTypes($4);
+ 					n->trgname = NULL;
+ 					n->relation = NULL;
+ 					n->is_function = true;
+ 					n->options = NIL;
+ 					$$ = (Node *) n;
+ 				}
+ 			| CHECK TRIGGER name ON qualified_name
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = NULL;
+ 					n->args = NIL;
+ 					n->trgname = $3;
+ 					n->relation = $5;
+ 					n->is_function = false;
+ 					n->options = NIL;
+ 					$$ = (Node *) n;
+ 				}
+ 			| CHECK FUNCTION ALL CheckFunctionOpts
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = NULL;
+ 					n->args = NIL;
+ 					n->trgname = NULL;
+ 					n->relation = NULL;
+ 					n->is_function = true;
+ 					n->options = $4;
+ 					$$ = (Node *) n;
+ 				}
+ 		;
+ 
+ CheckFunctionOpts:
+ 			CheckFunctionOptElem					{ $$ = list_make1($1); }
+ 			| CheckFunctionOpts CheckFunctionOptElem	{ $$ = lappend($1, $2); }
+ 		;
+ 
+ CheckFunctionOptElem:
+ 			IN_P LANGUAGE ColId
+ 				{
+ 					$$ = makeDefElem("language",
+ 								    (Node *) makeString($3));
+ 				}
+ 			| IN_P SCHEMA ColId
+ 				{
+ 					$$ = makeDefElem("schema",
+ 								    (Node *) makeString($3));
+ 				}
+ 			| FOR ROLE ColId
+ 				{
+ 					$$ = makeDefElem("owner",
+ 								    (Node *) makeString($3));
+ 				}
+ 		;
+ 
+ /*****************************************************************************
+  *
   *		DO <anonymous code block> [ LANGUAGE language ]
   *
   * We use a DefElem list for future extensibility, and to allow flexibility
*** ./src/backend/tcop/utility.c.orig	2011-12-07 05:59:37.140674512 +0100
--- ./src/backend/tcop/utility.c	2011-12-07 06:35:57.836744377 +0100
***************
*** 882,887 ****
--- 882,891 ----
  			AlterFunction((AlterFunctionStmt *) parsetree);
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			CheckFunction((CheckFunctionStmt *) parsetree);
+ 			break;
+ 
  		case T_IndexStmt:		/* CREATE INDEX */
  			{
  				IndexStmt  *stmt = (IndexStmt *) parsetree;
***************
*** 2125,2130 ****
--- 2129,2141 ----
  			}
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			if (((CheckFunctionStmt *) parsetree)->is_function)
+ 				tag = "CHECK FUNCTION";
+ 			else
+ 				tag = "CHECK TRIGGER";
+ 			break;
+ 
  		default:
  			elog(WARNING, "unrecognized node type: %d",
  				 (int) nodeTag(parsetree));
***************
*** 2565,2570 ****
--- 2576,2585 ----
  			}
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			lev = LOGSTMT_ALL;
+ 			break;
+ 
  		default:
  			elog(WARNING, "unrecognized node type: %d",
  				 (int) nodeTag(parsetree));
*** ./src/bin/pg_dump/pg_dump.c.orig	2011-12-07 05:59:37.142674489 +0100
--- ./src/bin/pg_dump/pg_dump.c	2011-12-07 06:00:07.312332234 +0100
***************
*** 5326,5338 ****
  	int			i_lanplcallfoid;
  	int			i_laninline;
  	int			i_lanvalidator;
  	int			i_lanacl;
  	int			i_lanowner;
  
  	/* Make sure we are in proper schema */
  	selectSourceSchema("pg_catalog");
  
! 	if (g_fout->remoteVersion >= 90000)
  	{
  		/* pg_language has a laninline column */
  		appendPQExpBuffer(query, "SELECT tableoid, oid, "
--- 5326,5351 ----
  	int			i_lanplcallfoid;
  	int			i_laninline;
  	int			i_lanvalidator;
+ 	int			i_lanchecker;
  	int			i_lanacl;
  	int			i_lanowner;
  
  	/* Make sure we are in proper schema */
  	selectSourceSchema("pg_catalog");
  
! 	if (g_fout->remoteVersion >= 90200)
! 	{
! 		/* pg_language has a lanchecker column */
! 		appendPQExpBuffer(query, "SELECT tableoid, oid, "
! 						  "lanname, lanpltrusted, lanplcallfoid, "
! 						  "laninline, lanvalidator, lanchecker, lanacl, "
! 						  "(%s lanowner) AS lanowner "
! 						  "FROM pg_language "
! 						  "WHERE lanispl "
! 						  "ORDER BY oid",
! 						  username_subquery);
! 	}
! 	else if (g_fout->remoteVersion >= 90000)
  	{
  		/* pg_language has a laninline column */
  		appendPQExpBuffer(query, "SELECT tableoid, oid, "
***************
*** 5409,5414 ****
--- 5422,5428 ----
  	/* these may fail and return -1: */
  	i_laninline = PQfnumber(res, "laninline");
  	i_lanvalidator = PQfnumber(res, "lanvalidator");
+ 	i_lanchecker = PQfnumber(res, "lanchecker");
  	i_lanacl = PQfnumber(res, "lanacl");
  	i_lanowner = PQfnumber(res, "lanowner");
  
***************
*** 5422,5427 ****
--- 5436,5445 ----
  		planginfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_lanname));
  		planginfo[i].lanpltrusted = *(PQgetvalue(res, i, i_lanpltrusted)) == 't';
  		planginfo[i].lanplcallfoid = atooid(PQgetvalue(res, i, i_lanplcallfoid));
+ 		if (i_lanchecker >= 0)
+ 			planginfo[i].lanchecker = atooid(PQgetvalue(res, i, i_lanchecker));
+ 		else
+ 			planginfo[i].lanchecker = InvalidOid;
  		if (i_laninline >= 0)
  			planginfo[i].laninline = atooid(PQgetvalue(res, i, i_laninline));
  		else
***************
*** 8597,8602 ****
--- 8615,8621 ----
  	char	   *qlanname;
  	char	   *lanschema;
  	FuncInfo   *funcInfo;
+ 	FuncInfo   *checkerInfo = NULL;
  	FuncInfo   *inlineInfo = NULL;
  	FuncInfo   *validatorInfo = NULL;
  
***************
*** 8616,8621 ****
--- 8635,8647 ----
  	if (funcInfo != NULL && !funcInfo->dobj.dump)
  		funcInfo = NULL;		/* treat not-dumped same as not-found */
  
+ 	if (OidIsValid(plang->lanchecker))
+ 	{
+ 		checkerInfo = findFuncByOid(plang->lanchecker);
+ 		if (checkerInfo != NULL && !checkerInfo->dobj.dump)
+ 			checkerInfo = NULL;
+ 	}
+ 
  	if (OidIsValid(plang->laninline))
  	{
  		inlineInfo = findFuncByOid(plang->laninline);
***************
*** 8642,8647 ****
--- 8668,8674 ----
  	 * don't, this might not work terribly nicely.
  	 */
  	useParams = (funcInfo != NULL &&
+ 				 (checkerInfo != NULL || !OidIsValid(plang->lanchecker)) &&
  				 (inlineInfo != NULL || !OidIsValid(plang->laninline)) &&
  				 (validatorInfo != NULL || !OidIsValid(plang->lanvalidator)));
  
***************
*** 8697,8702 ****
--- 8724,8739 ----
  			appendPQExpBuffer(defqry, "%s",
  							  fmtId(validatorInfo->dobj.name));
  		}
+ 		if (OidIsValid(plang->lanchecker))
+ 		{
+ 			appendPQExpBuffer(defqry, " CHECK ");
+ 			/* Cope with possibility that checker is in different schema */
+ 			if (checkerInfo->dobj.namespace != funcInfo->dobj.namespace)
+ 				appendPQExpBuffer(defqry, "%s.",
+ 							   fmtId(checkerInfo->dobj.namespace->dobj.name));
+ 			appendPQExpBuffer(defqry, "%s",
+ 							  fmtId(checkerInfo->dobj.name));
+ 		}
  	}
  	else
  	{
*** ./src/bin/pg_dump/pg_dump.h.orig	2011-12-07 05:59:37.144674467 +0100
--- ./src/bin/pg_dump/pg_dump.h	2011-12-07 06:00:07.348331825 +0100
***************
*** 387,392 ****
--- 387,393 ----
  	Oid			lanplcallfoid;
  	Oid			laninline;
  	Oid			lanvalidator;
+ 	Oid			lanchecker;
  	char	   *lanacl;
  	char	   *lanowner;		/* name of owner, or empty string */
  } ProcLangInfo;
*** ./src/bin/psql/tab-complete.c.orig	2011-12-07 05:59:37.146674445 +0100
--- ./src/bin/psql/tab-complete.c	2011-12-07 06:00:07.350331803 +0100
***************
*** 1,4 ****
--- 1,5 ----
  /*
+  *
   * psql - the PostgreSQL interactive terminal
   *
   * Copyright (c) 2000-2011, PostgreSQL Global Development Group
***************
*** 727,733 ****
  #define prev6_wd  (previous_words[5])
  
  	static const char *const sql_commands[] = {
! 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
  		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
--- 728,734 ----
  #define prev6_wd  (previous_words[5])
  
  	static const char *const sql_commands[] = {
! 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECK", "CHECKPOINT", "CLOSE", "CLUSTER",
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
  		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
***************
*** 1524,1529 ****
--- 1525,1552 ----
  
  		COMPLETE_WITH_LIST(list_TRANS);
  	}
+ 
+ /* CHECK */
+ 	else if (pg_strcasecmp(prev_wd, "CHECK") == 0)
+ 	{
+ 		static const char *const list_CHECK[] =
+ 		{"FUNCTION", "TRIGGER", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_CHECK);
+ 	}
+ 	else if (pg_strcasecmp(prev3_wd, "CHECK") == 0 &&
+ 			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+ 	{
+ 		COMPLETE_WITH_CONST("ON");
+ 	}
+ 	else if (pg_strcasecmp(prev4_wd, "CHECK") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "ON") == 0)
+ 	{
+ 		completion_info_charp = prev2_wd;
+ 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+ 	}
+ 
  /* CLUSTER */
  
  	/*
*** ./src/include/catalog/pg_language.h.orig	2011-12-07 05:59:37.147674434 +0100
--- ./src/include/catalog/pg_language.h	2011-12-07 06:00:07.351331792 +0100
***************
*** 37,42 ****
--- 37,43 ----
  	Oid			lanplcallfoid;	/* Call handler for PL */
  	Oid			laninline;		/* Optional anonymous-block handler function */
  	Oid			lanvalidator;	/* Optional validation function */
+ 	Oid			lanchecker;	/* Optional checker function */
  	aclitem		lanacl[1];		/* Access privileges */
  } FormData_pg_language;
  
***************
*** 51,57 ****
   *		compiler constants for pg_language
   * ----------------
   */
! #define Natts_pg_language				8
  #define Anum_pg_language_lanname		1
  #define Anum_pg_language_lanowner		2
  #define Anum_pg_language_lanispl		3
--- 52,58 ----
   *		compiler constants for pg_language
   * ----------------
   */
! #define Natts_pg_language				9
  #define Anum_pg_language_lanname		1
  #define Anum_pg_language_lanowner		2
  #define Anum_pg_language_lanispl		3
***************
*** 59,78 ****
  #define Anum_pg_language_lanplcallfoid	5
  #define Anum_pg_language_laninline		6
  #define Anum_pg_language_lanvalidator	7
! #define Anum_pg_language_lanacl			8
  
  /* ----------------
   *		initial contents of pg_language
   * ----------------
   */
  
! DATA(insert OID = 12 ( "internal"	PGUID f f 0 0 2246 _null_ ));
  DESCR("built-in functions");
  #define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c"			PGUID f f 0 0 2247 _null_ ));
  DESCR("dynamically-loaded C functions");
  #define ClanguageId 13
! DATA(insert OID = 14 ( "sql"		PGUID f t 0 0 2248 _null_ ));
  DESCR("SQL-language functions");
  #define SQLlanguageId 14
  
--- 60,80 ----
  #define Anum_pg_language_lanplcallfoid	5
  #define Anum_pg_language_laninline		6
  #define Anum_pg_language_lanvalidator	7
! #define Anum_pg_language_lanchecker	8
! #define Anum_pg_language_lanacl			9
  
  /* ----------------
   *		initial contents of pg_language
   * ----------------
   */
  
! DATA(insert OID = 12 ( "internal"	PGUID f f 0 0 2246 0 _null_ ));
  DESCR("built-in functions");
  #define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c"			PGUID f f 0 0 2247 0 _null_ ));
  DESCR("dynamically-loaded C functions");
  #define ClanguageId 13
! DATA(insert OID = 14 ( "sql"		PGUID f t 0 0 2248 0 _null_ ));
  DESCR("SQL-language functions");
  #define SQLlanguageId 14
  
*** ./src/include/catalog/pg_pltemplate.h.orig	2011-12-07 05:59:37.149674412 +0100
--- ./src/include/catalog/pg_pltemplate.h	2011-12-07 06:00:07.352331780 +0100
***************
*** 36,41 ****
--- 36,42 ----
  	text		tmplhandler;	/* name of call handler function */
  	text		tmplinline;		/* name of anonymous-block handler, or NULL */
  	text		tmplvalidator;	/* name of validator function, or NULL */
+ 	text		tmplchecker;	/* name of checker function, or NULL */
  	text		tmpllibrary;	/* path of shared library */
  	aclitem		tmplacl[1];		/* access privileges for template */
  } FormData_pg_pltemplate;
***************
*** 51,65 ****
   *		compiler constants for pg_pltemplate
   * ----------------
   */
! #define Natts_pg_pltemplate					8
  #define Anum_pg_pltemplate_tmplname			1
  #define Anum_pg_pltemplate_tmpltrusted		2
  #define Anum_pg_pltemplate_tmpldbacreate	3
  #define Anum_pg_pltemplate_tmplhandler		4
  #define Anum_pg_pltemplate_tmplinline		5
  #define Anum_pg_pltemplate_tmplvalidator	6
! #define Anum_pg_pltemplate_tmpllibrary		7
! #define Anum_pg_pltemplate_tmplacl			8
  
  
  /* ----------------
--- 52,67 ----
   *		compiler constants for pg_pltemplate
   * ----------------
   */
! #define Natts_pg_pltemplate					9
  #define Anum_pg_pltemplate_tmplname			1
  #define Anum_pg_pltemplate_tmpltrusted		2
  #define Anum_pg_pltemplate_tmpldbacreate	3
  #define Anum_pg_pltemplate_tmplhandler		4
  #define Anum_pg_pltemplate_tmplinline		5
  #define Anum_pg_pltemplate_tmplvalidator	6
! #define Anum_pg_pltemplate_tmplchecker		7
! #define Anum_pg_pltemplate_tmpllibrary		8
! #define Anum_pg_pltemplate_tmplacl			9
  
  
  /* ----------------
***************
*** 67,79 ****
   * ----------------
   */
  
! DATA(insert ( "plpgsql"		t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl"		t t "pltcl_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu"		f f "pltclu_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl"		t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu"		f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu"	f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u"	f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u"	f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" "$libdir/plpython3" _null_ ));
  
  #endif   /* PG_PLTEMPLATE_H */
--- 69,81 ----
   * ----------------
   */
  
! DATA(insert ( "plpgsql"		t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "plpgsql_checker" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl"		t t "pltcl_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu"		f f "pltclu_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl"		t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu"		f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu"	f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u"	f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u"	f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" _null_ "$libdir/plpython3" _null_ ));
  
  #endif   /* PG_PLTEMPLATE_H */
*** ./src/include/commands/defrem.h.orig	2011-12-07 05:59:37.150674400 +0100
--- ./src/include/commands/defrem.h	2011-12-07 06:00:07.352331780 +0100
***************
*** 62,67 ****
--- 62,68 ----
  /* commands/functioncmds.c */
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
  extern void RemoveFunctionById(Oid funcOid);
+ extern void CheckFunction(CheckFunctionStmt *stmt);
  extern void SetFunctionReturnType(Oid funcOid, Oid newRetType);
  extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType);
  extern void RenameFunction(List *name, List *argtypes, const char *newname);
*** ./src/include/nodes/nodes.h.orig	2011-12-07 05:59:37.151674388 +0100
--- ./src/include/nodes/nodes.h	2011-12-07 06:00:07.353331768 +0100
***************
*** 291,296 ****
--- 291,297 ----
  	T_IndexStmt,
  	T_CreateFunctionStmt,
  	T_AlterFunctionStmt,
+ 	T_CheckFunctionStmt,
  	T_DoStmt,
  	T_RenameStmt,
  	T_RuleStmt,
*** ./src/include/nodes/parsenodes.h.orig	2011-12-07 05:59:37.153674364 +0100
--- ./src/include/nodes/parsenodes.h	2011-12-07 06:16:44.502121440 +0100
***************
*** 1734,1739 ****
--- 1734,1740 ----
  	List	   *plhandler;		/* PL call handler function (qual. name) */
  	List	   *plinline;		/* optional inline function (qual. name) */
  	List	   *plvalidator;	/* optional validator function (qual. name) */
+ 	List	   *plchecker;		/* optional checker function (qual. name) */
  	bool		pltrusted;		/* PL is trusted */
  } CreatePLangStmt;
  
***************
*** 2077,2082 ****
--- 2078,2098 ----
  } AlterFunctionStmt;
  
  /* ----------------------
+  *		Check {Function|Trigger} Statement
+  * ----------------------
+  */
+ typedef struct CheckFunctionStmt
+ {
+ 	NodeTag		type;
+ 	List	   *funcname;			/* qualified name of checked object */
+ 	List	   *args;			/* types of the arguments */
+ 	char	   *trgname;			/* trigger's name */
+ 	RangeVar	*relation;		/* trigger's relation */
+ 	bool	   is_function;		/* true for CHECK FUNCTION statement */
+ 	List	   *options;			/* other DefElem options */
+ } CheckFunctionStmt;
+ 
+ /* ----------------------
   *		DO Statement
   *
   * DoStmt is the raw parser output, InlineCodeBlock is the execution-time API
*** ./src/pl/plpgsql/src/pl_comp.c.orig	2011-12-07 05:59:37.155674342 +0100
--- ./src/pl/plpgsql/src/pl_comp.c	2011-12-07 06:00:07.360331689 +0100
***************
*** 115,121 ****
  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.
--- 115,120 ----
***************
*** 175,181 ****
  			 * 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
--- 174,180 ----
  			 * 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
***************
*** 2426,2432 ****
  }
  
  /*
!  * 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
--- 2425,2431 ----
  }
  
  /*
!  * 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,2446 ****
   * 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);
--- 2438,2445 ----
   * 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);
*** ./src/pl/plpgsql/src/pl_exec.c.orig	2011-12-07 05:59:37.157674320 +0100
--- ./src/pl/plpgsql/src/pl_exec.c	2011-12-07 07:36:23.112366630 +0100
***************
*** 210,216 ****
  static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
  						  PLpgSQL_expr *dynquery, List *params,
  						  const char *portalname, int cursorOptions);
! 
  
  /* ----------
   * plpgsql_exec_function	Called by the call handler for
--- 210,228 ----
  static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
  						  PLpgSQL_expr *dynquery, List *params,
  						  const char *portalname, int cursorOptions);
! static void check_row_or_rec(PLpgSQL_execstate *estate, PLpgSQL_row *row, PLpgSQL_rec *rec);
! static void check_expr(PLpgSQL_execstate *estate, PLpgSQL_expr *expr);
! static void assign_tupdesc_row_or_rec(PLpgSQL_execstate *estate,
! 					    PLpgSQL_row *row, PLpgSQL_rec *rec,
! 								    TupleDesc tupdesc);
! static TupleDesc expr_get_desc(PLpgSQL_execstate *estate,
! 						PLpgSQL_expr *query,
! 							bool use_element_type,
! 							bool expand_record,
! 								bool is_expression);
! static void var_init_to_null(PLpgSQL_execstate *estate, int varno);
! static void check_stmts(PLpgSQL_execstate *estate, List *stmts);
! static void check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt);
  
  /* ----------
   * plpgsql_exec_function	Called by the call handler for
***************
*** 6176,6178 ****
--- 6188,7240 ----
  
  	return portal;
  }
+ 
+ /*
+  * Following code ensures a CHECK FUNCTION and CHECK TRIGGER statements for PL/pgSQL
+  *
+  */
+ 
+ /*
+  * append a CONTEXT to error message
+  */
+ static void
+ check_error_callback(void *arg)
+ {
+ 	PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;
+ 
+ 	if (estate->err_stmt != NULL)
+ 	{
+ 		/* translator: last %s is a plpgsql statement type name */
+ 		errcontext("line %d at %s",
+ 				   estate->err_stmt->lineno,
+ 				   plpgsql_stmt_typename(estate->err_stmt));
+ 	}
+ }
+ 
+ /*
+  * Check function - it prepare variables and starts a prepare plan walker
+  *		called by function checker
+  */
+ void
+ plpgsql_check_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
+ {
+ 	PLpgSQL_execstate estate;
+ 	ErrorContextCallback plerrcontext;
+ 	int			i;
+ 
+ 	/* Setup error callback for ereport */
+ 	plerrcontext.callback = check_error_callback;
+ 	plerrcontext.arg = &estate;
+ 	plerrcontext.previous = error_context_stack;
+ 	error_context_stack = &plerrcontext;
+ 
+ 	/*
+ 	 * Setup the execution state - we would to reuse some exec routines
+ 	 * so we need a estate
+ 	 */
+ 	plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo);
+ 
+ 	/*
+ 	 * Make local execution copies of all the datums
+ 	 */
+ 	for (i = 0; i < estate.ndatums; i++)
+ 		estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
+ 
+ 	/*
+ 	 * Store the actual call argument values into the appropriate variables
+ 	 */
+ 	for (i = 0; i < func->fn_nargs; i++)
+ 	{
+ 		int			n = func->fn_argvarnos[i];
+ 
+ 		switch (estate.datums[n]->dtype)
+ 		{
+ 			case PLPGSQL_DTYPE_VAR:
+ 				{
+ 					var_init_to_null(&estate, n);
+ 				}
+ 				break;
+ 
+ 			case PLPGSQL_DTYPE_ROW:
+ 				{
+ 					PLpgSQL_row *row = (PLpgSQL_row *) estate.datums[n];
+ 
+ 					exec_move_row(&estate, NULL, row, NULL, NULL);
+ 				}
+ 				break;
+ 
+ 			default:
+ 				elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(&estate, (PLpgSQL_stmt *) func->action);
+ 
+ 	/* Cleanup temporary memory */
+ 	plpgsql_destroy_econtext(&estate);
+ 
+ 	/* Pop the error context stack */
+ 	error_context_stack = plerrcontext.previous;
+ }
+ 
+ /*
+  * Check trigger - prepare fake environments for testing trigger
+  *
+  */
+ void
+ plpgsql_check_trigger(PLpgSQL_function *func,
+ 					 TriggerData *trigdata)
+ {
+ 	PLpgSQL_execstate estate;
+ 	ErrorContextCallback plerrcontext;
+ 	PLpgSQL_rec *rec_new,
+ 			   *rec_old;
+ 	int			i;
+ 
+ 	/* Setup error callback for ereport */
+ 	plerrcontext.callback = check_error_callback;
+ 	plerrcontext.arg = &estate;
+ 	plerrcontext.previous = error_context_stack;
+ 	error_context_stack = &plerrcontext;
+ 
+ 	/*
+ 	 * Setup the execution state - we would to reuse some exec routines
+ 	 * so we need a estate
+ 	 */
+ 	plpgsql_estate_setup(&estate, func, NULL);
+ 
+ 	/*
+ 	 * Make local execution copies of all the datums
+ 	 */
+ 	for (i = 0; i < estate.ndatums; i++)
+ 		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 *) (estate.datums[func->new_varno]);
+ 	rec_new->freetup = false;
+ 	rec_new->freetupdesc = false;
+ 	assign_tupdesc_row_or_rec(&estate, NULL, rec_new, trigdata->tg_relation->rd_att);
+ 
+ 	rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
+ 	rec_old->freetup = false;
+ 	rec_old->freetupdesc = false;
+ 	assign_tupdesc_row_or_rec(&estate, NULL, rec_old, trigdata->tg_relation->rd_att);
+ 
+ 	/*
+ 	 * Assign the special tg_ variables
+ 	 */
+ 	var_init_to_null(&estate, func->tg_op_varno);
+ 	var_init_to_null(&estate, func->tg_name_varno);
+ 	var_init_to_null(&estate, func->tg_when_varno);
+ 	var_init_to_null(&estate, func->tg_level_varno);
+ 	var_init_to_null(&estate, func->tg_relid_varno);
+ 	var_init_to_null(&estate, func->tg_relname_varno);
+ 	var_init_to_null(&estate, func->tg_table_name_varno);
+ 	var_init_to_null(&estate, func->tg_table_schema_varno);
+ 	var_init_to_null(&estate, func->tg_nargs_varno);
+ 	var_init_to_null(&estate, func->tg_argv_varno);
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(&estate, (PLpgSQL_stmt *) func->action);
+ 
+ 	/* Cleanup temporary memory */
+ 	plpgsql_destroy_econtext(&estate);
+ 
+ 	/* Pop the error context stack */
+ 	error_context_stack = plerrcontext.previous;
+ }
+ 
+ /*
+  * Verify lvalue
+  *    It doesn't repeat a checks that are done.
+  *  Checks a subscript expressions, verify a validity of record's fields
+  */
+ static void
+ check_target(PLpgSQL_execstate *estate, int varno)
+ {
+ 	PLpgSQL_datum *target = estate->datums[varno];
+ 
+ 	switch (target->dtype)
+ 	{
+ 		case PLPGSQL_DTYPE_VAR:
+ 		case PLPGSQL_DTYPE_REC:
+ 			break;
+ 
+ 		case PLPGSQL_DTYPE_ROW:
+ 			check_row_or_rec(estate, (PLpgSQL_row *) target, NULL);
+ 			break;
+ 
+ 		case PLPGSQL_DTYPE_RECFIELD:
+ 			{
+ 				PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target;
+ 				PLpgSQL_rec *rec;
+ 				int			fno;
+ 
+ 				rec = (PLpgSQL_rec *) (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)));
+ 
+ 					check_expr(estate, arrayelem->subscript);
+ 
+ 					target = 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(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_execstate *estate, 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(estate, row->varnos[fnum]);
+ 		}
+ 	}
+ }
+ 
+ /*
+  * Generate a prepared plan - this is simplyfied copy from pl_exec.c
+  *   Is not necessary to check simple plan
+  */
+ static void
+ prepare_expr(PLpgSQL_execstate *estate,
+ 				  PLpgSQL_expr *expr, int cursorOptions)
+ {
+ 	SPIPlanPtr	plan;
+ 
+ 	/* leave when there are not expression */
+ 	if (expr == NULL)
+ 		return;
+ 
+ 	/* leave when plan is created */
+ 	if (expr->plan != NULL)
+ 		return;
+ 
+ 	/*
+ 	 * 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 = 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")));
+ 			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.")));
+ 			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_execstate *estate, PLpgSQL_expr *expr)
+ {
+ 	TupleDesc tupdesc;
+ 
+ 	if (expr != NULL)
+ 	{
+ 		prepare_expr(estate, expr, 0);
+ 		tupdesc = expr_get_desc(estate, expr, false, false, true);
+ 		ReleaseTupleDesc(tupdesc);
+ 	}
+ }
+ 
+ /*
+  * 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_execstate *estate,
+ 					    PLpgSQL_row *row, PLpgSQL_rec *rec,
+ 								    TupleDesc tupdesc)
+ {
+ 	bool	   *nulls;
+ 	HeapTuple  tup;
+ 
+ 	if (tupdesc == NULL)
+ 		elog(ERROR, "tuple descriptor is empty");
+ 
+ 	/*
+ 	 * row variable has assigned TupleDesc already, so don't be processed
+ 	 * here
+ 	 */
+ 	if (rec != NULL)
+ 	{
+ 		PLpgSQL_rec *target = (PLpgSQL_rec *)(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_execstate *estate, int varno, TupleDesc tupdesc)
+ {
+ 	PLpgSQL_datum *target = estate->datums[varno];
+ 
+ 	if (target->dtype == PLPGSQL_DTYPE_REC)
+ 		assign_tupdesc_row_or_rec(estate, NULL, (PLpgSQL_rec *) target, tupdesc);
+ }
+ 
+ /*
+  * Returns a tuple descriptor based on existing plan
+  */
+ static TupleDesc 
+ expr_get_desc(PLpgSQL_execstate *estate,
+ 						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);
+ 
+ 		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 (tupdesc->natts != 1)
+ 			ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 				 errmsg_plural("query \"%s\" returned %d column",
+ 							   "query \"%s\" returned %d columns",
+ 							   tupdesc->natts,
+ 							   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))));
+ 
+ 		/* 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
+ 			elog(ERROR, "cannot to identify real type for record type variable");
+ 	}
+ 
+ 	if (is_expression && tupdesc->natts != 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 			  errmsg_plural("query \"%s\" returned %d column",
+ 				   "query \"%s\" returned %d columns",
+ 				   tupdesc->natts,
+ 				   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(..)
+ 	 */
+ 	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)
+ 						elog(ERROR, "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
+  */
+ static void
+ check_stmts(PLpgSQL_execstate *estate, List *stmts)
+ {
+ 	ListCell *lc;
+ 
+ 	foreach(lc, stmts)
+ 	{
+ 		check_stmt(estate, (PLpgSQL_stmt *) lfirst(lc));
+ 	}
+ }
+ 
+ /*
+  * walk over all statements
+  */
+ static void
+ check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
+ {
+ 	TupleDesc	tupdesc = NULL;
+ 	PLpgSQL_function *func;
+ 	ListCell *l;
+ 
+ 	if (stmt == NULL)
+ 		return;
+ 
+ 	estate->err_stmt = stmt;
+ 	func = estate->func;
+ 
+ 	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++)
+ 				{
+ 					d = func->datums[stmt_block->initvarnos[i]];
+ 
+ 					if (d->dtype == PLPGSQL_DTYPE_VAR)
+ 					{
+ 						PLpgSQL_var *var = (PLpgSQL_var *) d;
+ 
+ 						check_expr(estate, var->default_val);
+ 					}
+ 				}
+ 
+ 				check_stmts(estate, stmt_block->body);
+ 				
+ 				if (stmt_block->exceptions)
+ 				{
+ 					foreach(l, stmt_block->exceptions->exc_list)
+ 					{
+ 						check_stmts(estate, ((PLpgSQL_exception *) lfirst(l))->action);
+ 					}
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_ASSIGN:
+ 			{
+ 				PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt;
+ 
+ 				/* prepare plan if desn't exist yet */
+ 				prepare_expr(estate, stmt_assign->expr, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_assign->expr,
+ 										false,		/* no element type */
+ 										true,		/* expand record */
+ 										true);		/* is expression */
+ 
+ 				/* check target, ensure target can get a result */
+ 				check_target(estate, stmt_assign->varno);
+ 
+ 				/* assign a tupdesc to record variable */
+ 				assign_tupdesc_dno(estate, stmt_assign->varno, tupdesc);
+ 				ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_IF:
+ 			{
+ 				PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt;
+ 				ListCell *l;
+ 
+ 				check_expr(estate, stmt_if->cond);
+ 
+ 				check_stmts(estate, stmt_if->then_body);
+ 
+ 				foreach(l, stmt_if->elsif_list)
+ 				{
+ 					PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l);
+ 
+ 					check_expr(estate, elif->cond);
+ 					check_stmts(estate, elif->stmts);
+ 				}
+ 
+ 				check_stmts(estate, 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 *) estate->datums[stmt_case->t_varno];
+ 
+ 					/* we need to set hidden variable type */
+ 					prepare_expr(estate, stmt_case->t_expr, 0);
+ 
+ 					tupdesc = expr_get_desc(estate,
+ 									stmt_case->t_expr,
+ 										false,		/* no element type */
+ 										false,		/* 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,
+ 												   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(estate, cwt->expr);
+ 					check_stmts(estate, cwt->stmts);
+ 				}
+ 
+ 				check_stmts(estate, stmt_case->else_stmts);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_LOOP:
+ 			check_stmts(estate, ((PLpgSQL_stmt_loop *) stmt)->body);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_WHILE:
+ 			{
+ 				PLpgSQL_stmt_while *stmt_while = (PLpgSQL_stmt_while *) stmt;
+ 
+ 				check_expr(estate, stmt_while->cond);
+ 				check_stmts(estate, stmt_while->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORI:
+ 			{
+ 				PLpgSQL_stmt_fori *stmt_fori = (PLpgSQL_stmt_fori *) stmt;
+ 
+ 				check_expr(estate, stmt_fori->lower);
+ 				check_expr(estate, stmt_fori->upper);
+ 				check_expr(estate, stmt_fori->step);
+ 
+ 				check_stmts(estate, stmt_fori->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORS:
+ 			{
+ 				PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt;
+ 
+ 				/* we need to set hidden variable type */
+ 				prepare_expr(estate, stmt_fors->query, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_fors->query,
+ 									false,		/* no element type */
+ 									false,		/* expand record */
+ 									false);		/* is expression */
+ 
+ 				check_row_or_rec(estate, stmt_fors->row, stmt_fors->rec);
+ 				assign_tupdesc_row_or_rec(estate, stmt_fors->row, stmt_fors->rec, tupdesc);
+ 
+ 				check_stmts(estate, stmt_fors->body);
+ 				ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORC:
+ 			{
+ 				PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt;
+ 				PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_forc->curvar];
+ 
+ 				prepare_expr(estate, stmt_forc->argquery, 0);
+ 
+ 				if (var->cursor_explicit_expr != NULL)
+ 				{
+ 					prepare_expr(estate, var->cursor_explicit_expr,
+ 								    var->cursor_options);
+ 
+ 					tupdesc = expr_get_desc(estate,
+ 									var->cursor_explicit_expr,
+ 										false,		/* no element type */
+ 										false,		/* expand record */
+ 										false);		/* is expression */
+ 
+ 					check_row_or_rec(estate, stmt_forc->row, stmt_forc->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_forc->row, stmt_forc->rec, tupdesc);
+ 				}
+ 
+ 				check_stmts(estate, stmt_forc->body);
+ 				if (tupdesc != NULL)
+ 					ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNFORS:
+ 			{
+ 				PLpgSQL_stmt_dynfors * stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt;
+ 
+ 				if (stmt_dynfors->rec != NULL)
+ 					elog(ERROR, "cannot determinate a result of dynamic SQL");
+ 
+ 				check_expr(estate, stmt_dynfors->query);
+ 
+ 				foreach(l, stmt_dynfors->params)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				check_stmts(estate, stmt_dynfors->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FOREACH_A:
+ 			{
+ 				PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt;
+ 
+ 				prepare_expr(estate, stmt_foreach_a->expr, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_foreach_a->expr,
+ 									true,		/* no element type */
+ 									false,		/* expand record */
+ 									true);		/* is expression */
+ 
+ 				check_target(estate, stmt_foreach_a->varno);
+ 				assign_tupdesc_dno(estate, stmt_foreach_a->varno, tupdesc);
+ 				ReleaseTupleDesc(tupdesc);
+ 
+ 				check_stmts(estate, stmt_foreach_a->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_EXIT:
+ 			check_expr(estate, ((PLpgSQL_stmt_exit *) stmt)->cond);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_PERFORM:
+ 			prepare_expr(estate, ((PLpgSQL_stmt_perform *) stmt)->expr, 0);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN:
+ 			check_expr(estate, ((PLpgSQL_stmt_return *) stmt)->expr);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN_NEXT:
+ 			check_expr(estate, ((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(estate, stmt_rq->dynquery);
+ 				prepare_expr(estate, stmt_rq->query, 0);
+ 
+ 				foreach(l, stmt_rq->params)
+ 				{
+ 					check_expr(estate, (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(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				foreach(l, stmt_raise->options)
+ 				{
+ 					PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(l);
+ 
+ 					check_expr(estate, 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;
+ 
+ 				prepare_expr(estate, stmt_execsql->sqlstmt, 0);
+ 				if (stmt_execsql->into)
+ 				{
+ 					tupdesc = expr_get_desc(estate,
+ 								stmt_execsql->sqlstmt,
+ 											false,		/* no element type */
+ 											false,		/* expand record */
+ 											false);		/* is expression */
+ 
+ 					/* check target, ensure target can get a result */
+ 					check_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec, tupdesc);
+ 					ReleaseTupleDesc(tupdesc);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNEXECUTE:
+ 			{
+ 				PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt;
+ 
+ 				check_expr(estate, stmt_dynexecute->query);
+ 
+ 				foreach(l, stmt_dynexecute->params)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				if (stmt_dynexecute->into)
+ 				{
+ 					if (stmt_dynexecute->rec != NULL)
+ 						elog(ERROR, "cannot determinate a result of dynamic SQL");
+ 
+ 					check_row_or_rec(estate, stmt_dynexecute->row, stmt_dynexecute->rec);
+ 				}
+ 			}
+ 			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)
+ 					prepare_expr(estate, var->cursor_explicit_expr,
+ 								   var->cursor_options);
+ 
+ 				prepare_expr(estate, stmt_open->query, 0);
+ 				prepare_expr(estate, stmt_open->argquery, 0);
+ 				check_expr(estate, stmt_open->dynquery);
+ 
+ 				foreach(l, stmt_open->params)
+ 				{
+ 					check_expr(estate, (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(estate, diag_item->target);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FETCH:
+ 			{
+ 				PLpgSQL_stmt_fetch *stmt_fetch = (PLpgSQL_stmt_fetch *) stmt;
+ 				PLpgSQL_var *var = (PLpgSQL_var *)(estate->datums[stmt_fetch->curvar]);
+ 
+ 				if (var != NULL && var->cursor_explicit_expr != NULL)
+ 				{
+ 					prepare_expr(estate, var->cursor_explicit_expr, 
+ 									    var->cursor_options);
+ 					tupdesc = expr_get_desc(estate,
+ 								    var->cursor_explicit_expr,
+ 											false,		/* no element type */
+ 											false,		/* expand record */
+ 											false);		/* is expression */
+ 					check_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec, tupdesc);
+ 					ReleaseTupleDesc(tupdesc);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_CLOSE:
+ 			break;
+ 
+ 		default:
+ 			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
+ 			return; /* be compiler quite */
+ 	}
+ }
+ 
+ /*
+  * Initialize variable to NULL
+  */
+ static void
+ var_init_to_null(PLpgSQL_execstate *estate, int varno)
+ {
+ 	PLpgSQL_var *var = (PLpgSQL_var *) estate->datums[varno];
+ 	var->value = (Datum) 0;
+ 	var->isnull = true;
+ 	var->freeval = false;
+ }
*** ./src/pl/plpgsql/src/pl_handler.c.orig	2011-12-07 05:59:37.158674309 +0100
--- ./src/pl/plpgsql/src/pl_handler.c	2011-12-07 06:00:07.365331632 +0100
***************
*** 312,314 ****
--- 312,452 ----
  
  	PG_RETURN_VOID();
  }
+ 
+ /* ----------
+  * plpgsql_checker
+  *
+  * This function attempts to check a embeded SQL inside a PL/pgSQL function at
+  * CHECK FUNCTION time. It should to have one or two parameters. Second
+  * parameter is a relation (used when function is trigger).
+  * ----------
+  */
+ PG_FUNCTION_INFO_V1(plpgsql_checker);
+ 
+ Datum
+ plpgsql_checker(PG_FUNCTION_ARGS)
+ {
+ 	Oid			funcoid = PG_GETARG_OID(0);
+ 	Oid			relid = PG_GETARG_OID(1);
+ 	HeapTuple	tuple;
+ 	FunctionCallInfoData fake_fcinfo;
+ 	FmgrInfo	flinfo;
+ 	TriggerData trigdata;
+ 	int			rc;
+ 	PLpgSQL_function *function;
+ 	PLpgSQL_execstate *cur_estate;
+ 
+ 	Form_pg_proc proc;
+ 	char		functyptype;
+ 	bool	   istrigger = false;
+ 
+ 	/* we don't need to repair a check done by validator */
+ 
+ 	tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
+ 	if (!HeapTupleIsValid(tuple))
+ 		elog(ERROR, "cache lookup failed for function %u", funcoid);
+ 	proc = (Form_pg_proc) GETSTRUCT(tuple);
+ 
+ 	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))
+ 		{
+ 			istrigger = true;
+ 			if (!OidIsValid(relid))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("PL/pgSQL trigger functions cannot be checked directly"),
+ 						 errhint("use CHECK TRIGGER statement instead")));
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Connect to SPI manager
+ 	 */
+ 	if ((rc = SPI_connect()) != SPI_OK_CONNECT)
+ 			elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
+ 
+ 	/*
+ 	 * Set up a fake fcinfo with just enough info to satisfy
+ 	 * plpgsql_compile().
+ 	 *
+ 	 * there should be a different real argtypes for polymorphic params
+ 	 */
+ 	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;
+ 		trigdata.tg_relation = relation_open(relid, AccessShareLock);
+ 		fake_fcinfo.context = (Node *) &trigdata;
+ 	}
+ 
+ 	/* Get a compiled function */
+ 	function = plpgsql_compile(&fake_fcinfo, false);
+ 
+ 	/* 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 prepare plans */
+ 	PG_TRY();
+ 	{
+ 		if (!istrigger)
+ 			plpgsql_check_function(function, &fake_fcinfo);
+ 		else
+ 			plpgsql_check_trigger(function, &trigdata);
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		if (istrigger)
+ 			relation_close(trigdata.tg_relation, AccessShareLock);
+ 
+ 		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);
+ 
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	if (istrigger)
+ 		relation_close(trigdata.tg_relation, AccessShareLock);
+ 
+ 	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);
+ 
+ 	/*
+ 	 * Disconnect from SPI manager
+ 	 */
+ 	if ((rc = SPI_finish()) != SPI_OK_FINISH)
+ 			elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
+ 
+ 	ReleaseSysCache(tuple);
+ 
+ 	PG_RETURN_VOID();
+ }
*** ./src/pl/plpgsql/src/plpgsql.h.orig	2011-12-07 05:59:37.160674287 +0100
--- ./src/pl/plpgsql/src/plpgsql.h	2011-12-07 06:00:07.366331620 +0100
***************
*** 902,907 ****
--- 902,908 ----
  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,916 ****
--- 912,918 ----
  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_checker(PG_FUNCTION_ARGS);
  
  /* ----------
   * Functions in pl_exec.c
***************
*** 928,933 ****
--- 930,939 ----
  extern void exec_get_datum_type_info(PLpgSQL_execstate *estate,
  						 PLpgSQL_datum *datum,
  						 Oid *typeid, int32 *typmod, Oid *collation);
+ extern void plpgsql_check_function(PLpgSQL_function *func,
+ 					 FunctionCallInfo fcinfo);
+ extern void plpgsql_check_trigger(PLpgSQL_function *func,
+ 					 TriggerData *trigdata);
  
  /* ----------
   * Functions for namespace handling in pl_funcs.c
*** ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql.orig	2011-12-07 05:59:37.162674263 +0100
--- ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql	2011-12-07 06:00:07.366331620 +0100
***************
*** 5,7 ****
--- 5,8 ----
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_call_handler();
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_inline_handler(internal);
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_validator(oid);
+ ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_checker(oid, regclass);
*** ./src/test/regress/expected/plpgsql.out.orig	2011-12-07 05:59:37.164674240 +0100
--- ./src/test/regress/expected/plpgsql.out	2011-12-07 08:00:04.000000000 +0100
***************
*** 302,307 ****
--- 302,310 ----
  ' 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
+ check trigger tg_hslot_biu on HSlot;
+ NOTICE:  checked function "tg_hslot_biu()"
  -- ************************************************************
  -- * BEFORE DELETE on HSlot
  -- *	- prevent from manual manipulation
***************
*** 635,640 ****
--- 638,646 ----
      raise exception ''illegal backlink beginning with %'', mytype;
  end;
  ' language plpgsql;
+ -- check function should not fail
+ check function tg_backlink_set(bpchar, bpchar);
+ NOTICE:  checked function "tg_backlink_set(character,character)"
  -- ************************************************************
  -- * Support function to clear out the backlink field if
  -- * it still points to specific slot
***************
*** 2802,2807 ****
--- 2808,2842 ----
   
  (1 row)
  
+ -- check function should not fail
+ check function for_vect();
+ NOTICE:  checked function "for_vect()"
+ -- 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
***************
*** 3283,3288 ****
--- 3318,3326 ----
    return;
  end;
  $$ language plpgsql;
+ -- check function should not fail
+ check function forc01();
+ NOTICE:  checked function "forc01()"
  select forc01();
  NOTICE:  5 from c
  NOTICE:  6 from c
***************
*** 3716,3721 ****
--- 3754,3762 ----
    end case;
  end;
  $$ language plpgsql immutable;
+ -- check function should not fail
+ check function case_test(bigint);
+ NOTICE:  checked function "case_test(bigint)"
  select case_test(1);
   case_test 
  -----------
***************
*** 4571,4573 ****
--- 4612,4956 ----
  CONTEXT:  PL/pgSQL function "testoa" line 5 at assignment
  drop function arrayassign1();
  drop function testoa(x1 int, x2 int, x3 int);
+ --
+ -- check function statement tests
+ --
+ create table t1(a int, b int);
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+ end;
+ $$ language plpgsql;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ LINE 1: update t1 set c = 30
+                       ^
+ DETAIL:  column "c" of relation "t1" does not exist
+ QUERY:  update t1 set c = 30
+ CONTEXT:  line 4 at SQL statement
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  record "r" has no field "c"
+ CONTEXT:  SQL statement "SELECT r.c"
+ line 6 at RAISE
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  record "r" has no field "c"
+ CONTEXT:  SQL statement "SELECT r.c"
+ line 6 at RAISE
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  record "r" has no field "c"
+ CONTEXT:  line 6 at assignment
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ LINE 1: SELECT a + b
+                ^
+ DETAIL:  column "a" does not exist
+ QUERY:  SELECT a + b
+ CONTEXT:  line 5 at assignment
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  too many parameters specified for RAISE
+ CONTEXT:  line 4 at RAISE
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  too few parameters specified for RAISE
+ CONTEXT:  line 4 at RAISE
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ LINE 1: SELECT c+10
+                ^
+ DETAIL:  column "c" does not exist
+ QUERY:  SELECT c+10
+ CONTEXT:  line 5 at assignment
+ 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;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  subscripted object is not an array
+ CONTEXT:  line 5 at assignment
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  record "_exception" has no field "hint"
+ CONTEXT:  line 7 at GET DIAGNOSTICS
+ 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);
+ check trigger t1_f1 on t1;
+ WARNING:  error in function "f1_trg()"
+ DETAIL:  record "new" has no field "c"
+ CONTEXT:  SQL statement "SELECT new.c"
+ line 5 at RAISE
+ 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
+ check trigger t1_f1 on t1;
+ WARNING:  error in function "f1_trg()"
+ DETAIL:  record "new" has no field "c"
+ CONTEXT:  line 5 at assignment
+ -- 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
+ check trigger t1_f1 on t1;
+ NOTICE:  checked function "f1_trg()"
+ -- ok
+ insert into t1 values(6,30);
+ drop table t1;
+ drop type _exception_type;
+ drop function f1_trg();
*** ./src/test/regress/sql/plpgsql.sql.orig	2011-12-07 05:59:37.166674218 +0100
--- ./src/test/regress/sql/plpgsql.sql	2011-12-07 06:00:07.371331565 +0100
***************
*** 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
+ check trigger tg_hslot_biu on HSlot;
  
  -- ************************************************************
  -- * BEFORE DELETE on HSlot
***************
*** 747,752 ****
--- 749,757 ----
  end;
  ' language plpgsql;
  
+ -- check function should not fail
+ check function tg_backlink_set(bpchar, bpchar);
+ 
  
  -- ************************************************************
  -- * Support function to clear out the backlink field if
***************
*** 2335,2340 ****
--- 2340,2352 ----
  
  select for_vect();
  
+ -- check function should not fail
+ 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
***************
*** 2714,2719 ****
--- 2726,2734 ----
  end;
  $$ language plpgsql;
  
+ -- check function should not fail
+ check function forc01();
+ 
  select forc01();
  
  -- try updating the cursor's current row
***************
*** 3048,3053 ****
--- 3063,3071 ----
  end;
  $$ language plpgsql immutable;
  
+ -- check function should not fail
+ check function case_test(bigint);
+ 
  select case_test(1);
  select case_test(2);
  select case_test(3);
***************
*** 3600,3602 ****
--- 3618,3862 ----
  
  drop function arrayassign1();
  drop function testoa(x1 int, x2 int, x3 int);
+ 
+ --
+ -- check function statement tests
+ --
+ 
+ create table t1(a int, b int);
+ 
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+ end;
+ $$ language plpgsql;
+ 
+ select f1();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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;
+ 
+ select f1();
+ 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();
+ 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);
+ check trigger t1_f1 on 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
+ check trigger t1_f1 on 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
+ check trigger t1_f1 on t1;
+ 
+ -- ok
+ insert into t1 values(6,30);
+ 
+ drop table t1;
+ drop type _exception_type;
+ 
+ drop function f1_trg();
+ 
#30Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Pavel Stehule (#29)
Re: review: CHECK FUNCTION statement

Pavel Stehule wrote:

there is a updated patch.

it support multi check, options and custom check functions are not
supported yet. I don't plan to implement custom check functions in
this round - I has not any example of usage - but we have agreement on
syntax and behave, so this should not be problem. I changed reporting
- from exception to warnings.

The patch applies and builds cleanly.

The syntax error messages are still inadequate; all I can get is
'syntax error at or near "%s"'. They should be more detailed.

Many other messages and code comments are in bad English.

It might be a good idea to add some regression tests for the
CHECK FUNCTION ALL variants.

Functionality:
--------------

I noticed an oddity:

postgres=# CHECK FUNCTION ALL;
ERROR: syntax error at or near ";"
LINE 1: CHECK FUNCTION ALL;
^
postgres=# CHECK FUNCTION ALL IN LANGUAGE plpgsql;
NOTICE: nothing to check
postgres=# CHECK FUNCTION ALL IN SCHEMA pg_catalog;
[prints lots of NOTICEs]

According to the syntax diagram and my intuition CHECK FUNCTION ALL
without additional clauses should work.

Regarding the syntax: I know I suggested it myself, but after several
times of typing "IN LANGUAGE plpgsql" I think that omitting the "IN"
would be better and more like other commands (e.g. CREATE FUNCTION).

It is a pity that the CHECK FUNCTION ALL variants will not check
trigger functions, but I understand the difficulty -- it would
require checking all trigger functions on all tables where they
occur in a trigger.

I think that the checker function should be shown in psql's
\dL+ output.

Barring these little gripes, the functionality seems "ready for
committer" from my point of view.

Code review:
------------

I do not feel competent for a thorough code review.

Documentation:
--------------

This is where I see the greatest shortcomings.

- The documentation for the system catalog pg_pltemplate should be
extended to include tmplchecker.
- The documentation patch for CREATE LANGUAGE is plain wrong and
contains a syntax error.
- CHECK FUNCTION and CHECK TRIGGER should be treated as different
SQL statements. It is misleading to have CHECK TRIGGER listed
under CHECK FUNCTION. If they have to be together, the statement
should be called "CHECK" and not "CHECK TRIGGER", but I think
they should be separate.
- There is still no documentation patch for plhandler.sgml.

I think that at least the documentation should be improved before
I am ready to set this as "ready for committer".

Yours,
Laurenz Albe

#31Pavel Stehule
pavel.stehule@gmail.com
In reply to: Albe Laurenz (#30)
Re: review: CHECK FUNCTION statement

2011/12/7 Albe Laurenz <laurenz.albe@wien.gv.at>:

Pavel Stehule wrote:

there is a updated patch.

it support multi check, options and custom check functions are not
supported yet. I don't plan to implement custom check functions in
this round - I has not any example of usage - but we have agreement on
syntax and behave, so this should not be problem. I changed reporting
- from exception to warnings.

The patch applies and builds cleanly.

The syntax error messages are still inadequate; all I can get is
'syntax error at or near "%s"'.  They should be more detailed.

this system is based on error messages that generates a plpgsql engine
or bison engine. I can correct only a few percent from these messages
:(

internally I didn't wrote a compiler or plpgsql checker - this is just
tool that can emit some plpgsql interpret subprocess - and when these
subprocesses raises exceptions, then takes their messages.

Many other messages and code comments are in bad English.

It might be a good idea to add some regression tests for the
CHECK FUNCTION ALL variants.

Functionality:
--------------

I noticed an oddity:

postgres=# CHECK FUNCTION ALL;
ERROR:  syntax error at or near ";"
LINE 1: CHECK FUNCTION ALL;
                         ^
postgres=# CHECK FUNCTION ALL IN LANGUAGE plpgsql;
NOTICE:  nothing to check
postgres=# CHECK FUNCTION ALL IN SCHEMA pg_catalog;
[prints lots of NOTICEs]

According to the syntax diagram and my intuition CHECK FUNCTION ALL
without additional clauses should work.

this is question - this will check all functions in postgres.It's 2421
function, so one criterium as minimum should be good idea.

We can remove buildin functions from list - so it will check all
function in database.

Regarding the syntax: I know I suggested it myself, but after several
times of typing "IN LANGUAGE plpgsql" I think that omitting the "IN"
would be better and more like other commands (e.g. CREATE FUNCTION).

IN should be syntactic sugar

It is a pity that the CHECK FUNCTION ALL variants will not check
trigger functions, but I understand the difficulty -- it would
require checking all trigger functions on all tables where they
occur in a trigger.

I think that the checker function should be shown in psql's
\dL+ output.

Barring these little gripes, the functionality seems "ready for
committer" from my point of view.

Code review:
------------

I do not feel competent for a thorough code review.

Documentation:
--------------

This is where I see the greatest shortcomings.

- The documentation for the system catalog pg_pltemplate should be
 extended to include tmplchecker.
- The documentation patch for CREATE LANGUAGE is plain wrong and
 contains a syntax error.
- CHECK FUNCTION and CHECK TRIGGER should be treated as different
 SQL statements.  It is misleading to have CHECK TRIGGER listed
 under CHECK FUNCTION.  If they have to be together, the statement
 should be called "CHECK" and not "CHECK TRIGGER", but I think
 they should be separate.
- There is still no documentation patch for plhandler.sgml.

I think that at least the documentation should be improved before
I am ready to set this as "ready for committer".

please, can you send a correction to documentation or error messages?

I am not able to write documentation

Regards

Pavel

Show quoted text

Yours,
Laurenz Albe

#32Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Pavel Stehule (#31)
Re: review: CHECK FUNCTION statement

Pavel Stehule wrote:

The syntax error messages are still inadequate; all I can get is
'syntax error at or near "%s"'.  They should be more detailed.

this system is based on error messages that generates a plpgsql engine
or bison engine. I can correct only a few percent from these messages
:(

internally I didn't wrote a compiler or plpgsql checker - this is just
tool that can emit some plpgsql interpret subprocess - and when these
subprocesses raises exceptions, then takes their messages.

I see.

I think that at least the documentation should be improved before
I am ready to set this as "ready for committer".

please, can you send a correction to documentation or error messages?

I am not able to write documentation

I'll give it a try.

Yours,
Laurenz Albe

#33Pavel Stehule
pavel.stehule@gmail.com
In reply to: Albe Laurenz (#32)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Hello

updated version

changes:

* CHECK FUNCTION ALL; is enabled - in this case functions from
pg_catalog schema are ignored

I looked on parser, and I didn't other changes there - IN SCHEMA, FOR
ROLE are used more time there, so our usage will be consistent

Regards

Pavel

2011/12/7 Albe Laurenz <laurenz.albe@wien.gv.at>:

Show quoted text

Pavel Stehule wrote:

The syntax error messages are still inadequate; all I can get is
'syntax error at or near "%s"'.  They should be more detailed.

this system is based on error messages that generates a plpgsql engine
or bison engine. I can correct only a few percent from these messages
:(

internally I didn't wrote a compiler or plpgsql checker - this is just
tool that can emit some plpgsql interpret subprocess - and when these
subprocesses raises exceptions, then takes their messages.

I see.

I think that at least the documentation should be improved before
I am ready to set this as "ready for committer".

please, can you send a correction to documentation or error messages?

I am not able to write documentation

I'll give it a try.

Yours,
Laurenz Albe

Attachments:

check_function-2011-12-08-1.difftext/x-patch; charset=US-ASCII; name=check_function-2011-12-08-1.diffDownload
*** ./doc/src/sgml/catalogs.sgml.orig	2011-12-07 05:59:37.123674706 +0100
--- ./doc/src/sgml/catalogs.sgml	2011-12-07 06:00:07.253332903 +0100
***************
*** 3652,3657 ****
--- 3652,3668 ----
       </row>
  
       <row>
+       <entry><structfield>lanchecker</structfield></entry>
+       <entry><type>oid</type></entry>
+       <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+       <entry>
+        This references a language checker function that is responsible
+        for checking a embedded SQL and can provide detailed checking.
+        Zero if no checker is provided.
+       </entry>
+      </row>
+ 
+      <row>
        <entry><structfield>lanacl</structfield></entry>
        <entry><type>aclitem[]</type></entry>
        <entry></entry>
*** ./doc/src/sgml/ref/allfiles.sgml.orig	2011-12-07 05:59:37.125674684 +0100
--- ./doc/src/sgml/ref/allfiles.sgml	2011-12-07 06:00:07.254332891 +0100
***************
*** 40,45 ****
--- 40,46 ----
  <!ENTITY alterView          SYSTEM "alter_view.sgml">
  <!ENTITY analyze            SYSTEM "analyze.sgml">
  <!ENTITY begin              SYSTEM "begin.sgml">
+ <!ENTITY checkFunction      SYSTEM "check_function.sgml">
  <!ENTITY checkpoint         SYSTEM "checkpoint.sgml">
  <!ENTITY close              SYSTEM "close.sgml">
  <!ENTITY cluster            SYSTEM "cluster.sgml">
*** ./doc/src/sgml/ref/create_language.sgml.orig	2011-12-07 05:59:37.127674661 +0100
--- ./doc/src/sgml/ref/create_language.sgml	2011-12-07 06:00:07.256332868 +0100
***************
*** 23,29 ****
  <synopsis>
  CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
  CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ]
  </synopsis>
   </refsynopsisdiv>
  
--- 23,29 ----
  <synopsis>
  CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
  CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ] [ CHECK <replaceable>checkfunction</replaceable> ]
  </synopsis>
   </refsynopsisdiv>
  
***************
*** 217,222 ****
--- 217,236 ----
        </para>
       </listitem>
      </varlistentry>
+ 
+     <varlistentry>
+      <term><literal>CHECK</literal> <replaceable class="parameter">checkfunction</replaceable></term>
+ 
+      <listitem>
+       <para><replaceable class="parameter">checkfunction</replaceable> is the
+        name of a previously registered function that will be called
+        when a new function in the language is created, to check the
+        function by statemnt <command>CHECK FUNCTION</command> or 
+        <command>CHECK TRIGGER</command>.
+       </para>
+      </listitem>
+     </varlistentry>
+ 
     </variablelist>
  
    <para>
*** ./doc/src/sgml/reference.sgml.orig	2011-12-07 05:59:37.128674649 +0100
--- ./doc/src/sgml/reference.sgml	2011-12-07 06:00:07.257332857 +0100
***************
*** 68,73 ****
--- 68,74 ----
     &alterView;
     &analyze;
     &begin;
+    &checkFunction;
     &checkpoint;
     &close;
     &cluster;
*** ./doc/src/sgml/ref/check_function.sgml.orig	2011-12-07 05:59:58.965426923 +0100
--- ./doc/src/sgml/ref/check_function.sgml	2011-12-06 17:54:28.000000000 +0100
***************
*** 0 ****
--- 1,38 ----
+ <!--
+ doc/src/sgml/ref/check_function.sgml
+ -->
+ 
+ <refentry id="SQL-CHECKFUNCTION">
+  <refmeta>
+   <refentrytitle>CHECK FUNCTION</refentrytitle>
+   <manvolnum>7</manvolnum>
+   <refmiscinfo>SQL - Language Statements</refmiscinfo>
+  </refmeta>
+ 
+  <refnamediv>
+   <refname>CHECK FUNCTION</refname>
+   <refpurpose>ensure a deep checking of existing function</refpurpose>
+  </refnamediv>
+ 
+  <indexterm zone="sql-checkfunction">
+   <primary>CHECK FUNCTION</primary>
+  </indexterm>
+ 
+  <refsynopsisdiv>
+ <synopsis>
+ CHECK FUNCTION <replaceable class="parameter">name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] )
+  | CHECK FUNCTION ALL [ IN LANGUAGE <replaceable class="parameter">langname</replaceable> ] [ IN SCHEMA <replaceable class="parameter">schemaname</replaceable> ] [ FOR ROLE <replaceable class="parameter">ownername</replaceable> ]
+  | CHECK TRIGGER <replaceable class="parameter">name</replaceable> ON <replaceable class="parameter">tablename</replaceable>
+ </synopsis>
+  </refsynopsisdiv>
+ 
+  <refsect1 id="sql-checkfunction-description">
+   <title>Description</title>
+ 
+   <para>
+    <command>CHECK FUNCTION</command> check a existing function.
+    <command>CHECK TRIGGER</command> check a trigger function.
+   </para>
+  </refsect1>
+ 
+ </refentry>
*** ./src/backend/catalog/pg_proc.c.orig	2011-12-07 05:59:37.131674614 +0100
--- ./src/backend/catalog/pg_proc.c	2011-12-07 06:00:07.260332824 +0100
***************
*** 1101,1103 ****
--- 1101,1104 ----
  	*newcursorpos = newcp;
  	return false;
  }
+ 
*** ./src/backend/commands/functioncmds.c.orig	2011-12-07 05:59:37.132674603 +0100
--- ./src/backend/commands/functioncmds.c	2011-12-08 14:57:35.755579256 +0100
***************
*** 35,53 ****
--- 35,58 ----
  #include "access/genam.h"
  #include "access/heapam.h"
  #include "access/sysattr.h"
+ #include "access/xact.h"
  #include "catalog/dependency.h"
  #include "catalog/indexing.h"
  #include "catalog/objectaccess.h"
  #include "catalog/pg_aggregate.h"
  #include "catalog/pg_cast.h"
  #include "catalog/pg_language.h"
+ #include "catalog/namespace.h"
  #include "catalog/pg_namespace.h"
  #include "catalog/pg_proc.h"
  #include "catalog/pg_proc_fn.h"
+ #include "catalog/pg_trigger.h"
  #include "catalog/pg_type.h"
  #include "catalog/pg_type_fn.h"
  #include "commands/defrem.h"
  #include "commands/proclang.h"
+ #include "commands/trigger.h"
+ #include "executor/spi_priv.h"
  #include "miscadmin.h"
  #include "optimizer/var.h"
  #include "parser/parse_coerce.h"
***************
*** 60,66 ****
--- 65,73 ----
  #include "utils/fmgroids.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
+ #include "utils/memutils.h"
  #include "utils/rel.h"
+ #include "utils/resowner.h"
  #include "utils/syscache.h"
  #include "utils/tqual.h"
  
***************
*** 1009,1014 ****
--- 1016,1374 ----
  	}
  }
  
+ /*
+  * Search and execute related checker function
+  */
+ static void
+ CheckFunctionById(Oid funcOid, Oid relid)
+ {
+ 	HeapTuple	tup;
+ 	Form_pg_proc proc;
+ 	HeapTuple	languageTuple;
+ 	Form_pg_language languageStruct;
+ 	Oid		languageChecker;
+ 	bool	found_bug;
+ 	char *funcname;
+ 
+ 	tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
+ 	if (!HeapTupleIsValid(tup)) /* should not happen */
+ 		elog(ERROR, "cache lookup failed for function %u", funcOid);
+ 
+ 	proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 	languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
+ 	Assert(HeapTupleIsValid(languageTuple));
+ 
+ 	languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
+ 	languageChecker = languageStruct->lanchecker;
+ 
+ 	funcname = format_procedure(funcOid);
+ 
+ 	/* Check a function body */
+ 	if (OidIsValid(languageChecker))
+ 	{
+ 		ArrayType  *set_items = NULL;
+ 		int			save_nestlevel = 0;
+ 		Datum	datum;
+ 		bool		isnull;
+ 		MemoryContext oldCxt;
+ 		MemoryContext checkCxt;
+ 		ResourceOwner		oldowner;
+ 
+ 		datum = SysCacheGetAttr(PROCOID, tup, Anum_pg_proc_proconfig, &isnull);
+ 
+ 		if (!isnull)
+ 		{
+ 			/* Set per-function configuration parameters */
+ 			set_items = (ArrayType *) DatumGetPointer(datum);
+ 			if (set_items)			/* Need a new GUC nesting level */
+ 			{
+ 				save_nestlevel = NewGUCNestLevel();
+ 				ProcessGUCArray(set_items,
+ 							(superuser() ? PGC_SUSET : PGC_USERSET),
+ 							PGC_S_SESSION,
+ 							GUC_ACTION_SAVE);
+ 			}
+ 			else
+ 				save_nestlevel = 0; /* keep compiler quiet */
+ 		}
+ 
+ 		checkCxt = AllocSetContextCreate(CurrentMemoryContext,
+ 									"Check temporary context",
+ 									ALLOCSET_DEFAULT_MINSIZE,
+ 									ALLOCSET_DEFAULT_INITSIZE,
+ 									ALLOCSET_DEFAULT_MAXSIZE);
+ 
+ 		oldCxt = MemoryContextSwitchTo(checkCxt);
+ 
+ 		oldowner = CurrentResourceOwner;
+ 		BeginInternalSubTransaction(NULL);
+ 		MemoryContextSwitchTo(checkCxt);
+ 
+ 		/*
+ 		 * forward exception to warning
+ 		 */
+ 		PG_TRY();
+ 		{
+ 			OidFunctionCall2(languageChecker, ObjectIdGetDatum(funcOid), 
+ 								    ObjectIdGetDatum(relid));
+ 
+ 			RollbackAndReleaseCurrentSubTransaction();
+ 			MemoryContextSwitchTo(checkCxt);
+ 			CurrentResourceOwner = oldowner;
+ 
+ 			found_bug = false;
+ 		}
+ 		PG_CATCH();
+ 		{
+ 			ErrorData *edata;
+ 
+ 			MemoryContextSwitchTo(checkCxt);
+ 			edata = CopyErrorData();
+ 			FlushErrorState();
+ 
+ 			RollbackAndReleaseCurrentSubTransaction();
+ 			MemoryContextSwitchTo(checkCxt);
+ 			CurrentResourceOwner = oldowner;
+ 
+ 			edata->elevel = WARNING;
+ 
+ 			ereport(WARNING,
+ 					(errmsg("error in function \"%s\"", funcname),
+ 					 edata->message != NULL ? errdetail_internal("%s", edata->message) : 0,
+ 					 edata->context != NULL ? errcontext("%s",edata->context) : 0,
+ 					 edata->internalquery != NULL ? internalerrquery(edata->internalquery) : 0,
+ 					 internalerrposition(edata->internalpos)));
+ 
+ 			FreeErrorData(edata);
+ 
+ 			found_bug = true;
+ 		}
+ 		PG_END_TRY();
+ 
+ 		MemoryContextSwitchTo(oldCxt);
+ 
+ 		if (set_items)
+ 			AtEOXact_GUC(true, save_nestlevel);
+ 
+ 		if (!found_bug)
+ 			elog(NOTICE, "checked function \"%s\"",
+ 							funcname);
+ 	}
+ 	else
+ 		elog(NOTICE, "skip check function \"%s\", language \"%s\" hasn't checker function",
+ 					funcname,
+ 							    NameStr(languageStruct->lanname));
+ 
+ 	pfree(funcname);
+ 
+ 	ReleaseSysCache(languageTuple);
+ 	ReleaseSysCache(tup);
+ }
+ 
+ /*
+  * CheckFunction
+  *			call a PL checker function when this function exists.
+  */
+ void
+ CheckFunction(CheckFunctionStmt *stmt)
+ {
+ 	List	   *functionName = stmt->funcname;
+ 	List	   *argTypes = stmt->args;	/* list of TypeName nodes */
+ 	Oid			funcOid = InvalidOid;
+ 	HeapTuple	tup;
+ 	Oid trgOid = InvalidOid;
+ 	Oid	relid = InvalidOid;
+ 	Oid languageId;
+ 	int nkeys = 0;
+ 	List	*objects = NIL;
+ 
+ 
+ 	if (stmt->funcname != NULL)
+ 	{
+ 		/*
+ 		 * Find the function, 
+ 		 */
+ 		funcOid = LookupFuncNameTypeNames(functionName, argTypes, false);
+ 	}
+ 	else if (stmt->trgname != NULL)
+ 	{
+ 		HeapTuple	ht_trig;
+ 		Form_pg_trigger trigrec;
+ 		ScanKeyData skey[1];
+ 		Relation	tgrel;
+ 		SysScanDesc tgscan;
+ 
+ 		/* find a trigger function */
+ 		relid = RangeVarGetRelid(stmt->relation, ShareLock, false);
+ 		trgOid = get_trigger_oid(relid, stmt->trgname, false);
+ 
+ 		/*
+ 		 * Fetch the pg_trigger tuple by the Oid of the trigger
+ 		 */
+ 		tgrel = heap_open(TriggerRelationId, AccessShareLock);
+ 
+ 		ScanKeyInit(&skey[0],
+ 					ObjectIdAttributeNumber,
+ 					BTEqualStrategyNumber, F_OIDEQ,
+ 					ObjectIdGetDatum(trgOid));
+ 
+ 		tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true,
+ 									SnapshotNow, 1, skey);
+ 
+ 		ht_trig = systable_getnext(tgscan);
+ 
+ 		if (!HeapTupleIsValid(ht_trig))
+ 			elog(ERROR, "could not find tuple for trigger %u", trgOid);
+ 
+ 		trigrec = (Form_pg_trigger) GETSTRUCT(ht_trig);
+ 
+ 		/* we need to know trigger function to get PL checker function */
+ 		funcOid = trigrec->tgfoid;
+ 
+ 		/* Clean up */
+ 		systable_endscan(tgscan);
+ 
+ 		heap_close(tgrel, AccessShareLock);
+ 	}
+ 	else
+ 	{
+ 		ScanKeyData	   key[4];
+ 		Relation	   rel;
+ 		HeapScanDesc 	   scan;
+ 		ListCell *option;
+ 		bool	language_opt =  false;
+ 		bool	schema_opt = false;
+ 		bool	owner_opt = false;
+ 
+ 		/*
+ 		 * when Check stmt is multiple statement, then
+ 		 * prepare list of processed functions.
+ 		 */
+ 		foreach(option, stmt->options)
+ 		{
+ 			DefElem    *defel = (DefElem *) lfirst(option);
+ 
+ 			if (strcmp(defel->defname, "language") == 0)
+ 			{
+ 				HeapTuple	languageTuple;
+ 				Form_pg_language languageStruct;
+ 				Oid		languageChecker;
+ 				char *language = defGetString(defel);
+ 
+ 				if (language_opt)
+ 					elog(ERROR, "multiple usage of IN LANGUAGE option");
+ 				language_opt = true;
+ 
+ 				languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language));
+ 				if (!HeapTupleIsValid(languageTuple))
+ 					ereport(ERROR,
+ 						(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 						 errmsg("language \"%s\" does not exist", language),
+ 						 (PLTemplateExists(language) ?
+ 						  errhint("Use CREATE LANGUAGE to load the language into the database.") : 0)));
+ 
+ 				languageId = HeapTupleGetOid(languageTuple);
+ 				if (languageId == INTERNALlanguageId || languageId == ClanguageId)
+ 					elog(ERROR, "cannot to check functions in C or internal languages");
+ 
+ 				languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
+ 				languageChecker = languageStruct->lanchecker;
+ 				if (!OidIsValid(languageChecker))
+ 					elog(ERROR, "language \"%s\" has no defined checker function",
+ 							    language);
+ 
+ 				ScanKeyInit(&key[nkeys++],
+ 							Anum_pg_proc_prolang,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(languageId));
+ 
+ 				ReleaseSysCache(languageTuple);
+ 			}
+ 			else if (strcmp(defel->defname, "schema") == 0)
+ 			{
+ 				Oid namespaceId = LookupExplicitNamespace(defGetString(defel));
+ 
+ 				if (schema_opt)
+ 					elog(ERROR, "multiple usage of IN SCHEMA option");
+ 				schema_opt = true;
+ 
+ 				ScanKeyInit(&key[nkeys++],
+ 							Anum_pg_proc_pronamespace,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(namespaceId));
+ 
+ 			}
+ 			else if (strcmp(defel->defname, "owner") == 0)
+ 			{
+ 				Oid ownerId = get_role_oid(defGetString(defel), false);
+ 
+ 				if (owner_opt)
+ 					elog(ERROR, "multiple usage of FOR ROLE option");
+ 				owner_opt = true;
+ 
+ 				ScanKeyInit(&key[nkeys++],
+ 							Anum_pg_proc_proowner,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(ownerId));
+ 			}
+ 			else
+ 				elog(ERROR, "option \"%s\" not recognized",
+ 					 defel->defname);
+ 		}
+ 
+ 		/*
+ 		 * We don't would to iterate over pg_catalog functions without
+ 		 * explicit request.
+ 		 */
+ 		if (!schema_opt)
+ 		{
+ 				ScanKeyInit(&key[nkeys++],
+ 							Anum_pg_proc_pronamespace,
+ 							BTEqualStrategyNumber, F_OIDNE,
+ 							ObjectIdGetDatum(PG_CATALOG_NAMESPACE));
+ 		}
+ 
+ 		rel = heap_open(ProcedureRelationId,AccessShareLock);
+ 		scan = heap_beginscan(rel, SnapshotNow, nkeys, key);
+ 
+ 		while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+ 		{
+ 			Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 			funcOid = HeapTupleGetOid(tup);
+ 
+ 			/* when language is not specified, then check checker */
+ 			if (!language_opt)
+ 			{
+ 				Oid prolang = proc->prolang;
+ 
+ 				/* skip C or internal */
+ 				if (prolang == INTERNALlanguageId || prolang == ClanguageId)
+ 				{
+ 					elog(NOTICE, "skip check function \"%s\", it use C or internal language",
+ 								format_procedure(funcOid));
+ 					continue;
+ 				}
+ 			}
+ 
+ 			if (proc->prorettype == TRIGGEROID)
+ 			{
+ 				elog(NOTICE, "skip check function \"%s\", it is trigger function",
+ 							format_procedure(funcOid));
+ 				continue;
+ 			}
+ 
+ 			objects = lappend_oid(objects, funcOid);
+ 		}
+ 
+ 		heap_endscan(scan);
+ 		heap_close(rel, AccessShareLock);
+ 	}
+ 
+ 	if (funcOid == InvalidOid && objects == NIL)
+ 	{
+ 		elog(NOTICE, "nothing to check");
+ 		return;
+ 	}
+ 
+ 	if (objects != NIL)
+ 	{
+ 		ListCell *object;
+ 
+ 		foreach(object, objects)
+ 		{
+ 			CHECK_FOR_INTERRUPTS();
+ 
+ 			funcOid = lfirst_oid(object);
+ 			CheckFunctionById(funcOid, InvalidOid);
+ 		}
+ 	}
+ 	else
+ 	{
+ 		CheckFunctionById(funcOid, relid);
+ 	}
+ }
  
  /*
   * Rename function
*** ./src/backend/commands/proclang.c.orig	2011-12-07 05:59:37.134674581 +0100
--- ./src/backend/commands/proclang.c	2011-12-07 06:00:07.264332779 +0100
***************
*** 46,57 ****
  	char	   *tmplhandler;	/* name of handler function */
  	char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
  	char	   *tmplvalidator;	/* name of validator function, or NULL */
  	char	   *tmpllibrary;	/* path of shared library */
  } PLTemplate;
  
  static void create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, bool trusted);
  static PLTemplate *find_language_template(const char *languageName);
  static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
  							Oid newOwnerId);
--- 46,58 ----
  	char	   *tmplhandler;	/* name of handler function */
  	char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
  	char	   *tmplvalidator;	/* name of validator function, or NULL */
+ 	char	   *tmplchecker;	/* name of checker function, or NULL */
  	char	   *tmpllibrary;	/* path of shared library */
  } PLTemplate;
  
  static void create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, Oid checkerOid, bool trusted);
  static PLTemplate *find_language_template(const char *languageName);
  static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
  							Oid newOwnerId);
***************
*** 67,75 ****
  	PLTemplate *pltemplate;
  	Oid			handlerOid,
  				inlineOid,
! 				valOid;
  	Oid			funcrettype;
! 	Oid			funcargtypes[1];
  
  	/*
  	 * If we have template information for the language, ignore the supplied
--- 68,77 ----
  	PLTemplate *pltemplate;
  	Oid			handlerOid,
  				inlineOid,
! 				valOid,
! 				checkerOid;
  	Oid			funcrettype;
! 	Oid			funcargtypes[2];
  
  	/*
  	 * If we have template information for the language, ignore the supplied
***************
*** 219,228 ****
  		else
  			valOid = InvalidOid;
  
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, pltemplate->tmpltrusted);
  	}
  	else
  	{
--- 221,269 ----
  		else
  			valOid = InvalidOid;
  
+ 		/*
+ 		 * Likewise for the checker, if required; but we don't care about
+ 		 * its return type.
+ 		 */
+ 		if (pltemplate->tmplchecker)
+ 		{
+ 			funcname = SystemFuncName(pltemplate->tmplchecker);
+ 			funcargtypes[0] = OIDOID;
+ 			funcargtypes[1] = REGCLASSOID;
+ 			checkerOid = LookupFuncName(funcname, 2, funcargtypes, true);
+ 			if (!OidIsValid(checkerOid))
+ 			{
+ 				checkerOid = ProcedureCreate(pltemplate->tmplchecker,
+ 										 PG_CATALOG_NAMESPACE,
+ 										 false, /* replace */
+ 										 false, /* returnsSet */
+ 										 VOIDOID,
+ 										 ClanguageId,
+ 										 F_FMGR_C_VALIDATOR,
+ 										 pltemplate->tmplchecker,
+ 										 pltemplate->tmpllibrary,
+ 										 false, /* isAgg */
+ 										 false, /* isWindowFunc */
+ 										 false, /* security_definer */
+ 										 true,	/* isStrict */
+ 										 PROVOLATILE_VOLATILE,
+ 										 buildoidvector(funcargtypes, 2),
+ 										 PointerGetDatum(NULL),
+ 										 PointerGetDatum(NULL),
+ 										 PointerGetDatum(NULL),
+ 										 NIL,
+ 										 PointerGetDatum(NULL),
+ 										 1,
+ 										 0);
+ 			}
+ 		}
+ 		else
+ 			checkerOid = InvalidOid;
+ 
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, checkerOid, pltemplate->tmpltrusted);
  	}
  	else
  	{
***************
*** 294,303 ****
  		else
  			valOid = InvalidOid;
  
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, stmt->pltrusted);
  	}
  }
  
--- 335,355 ----
  		else
  			valOid = InvalidOid;
  
+ 		/* validate the checker function */
+ 		if (stmt->plchecker)
+ 		{
+ 			funcargtypes[0] = OIDOID;
+ 			funcargtypes[1] = REGCLASSOID;
+ 			checkerOid = LookupFuncName(stmt->plchecker, 2, funcargtypes, false);
+ 			/* return value is ignored, so we don't check the type */
+ 		}
+ 		else
+ 			checkerOid = InvalidOid;
+ 
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, checkerOid, stmt->pltrusted);
  	}
  }
  
***************
*** 307,313 ****
  static void
  create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, bool trusted)
  {
  	Relation	rel;
  	TupleDesc	tupDesc;
--- 359,365 ----
  static void
  create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, Oid checkerOid, bool trusted)
  {
  	Relation	rel;
  	TupleDesc	tupDesc;
***************
*** 337,342 ****
--- 389,395 ----
  	values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid);
  	values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid);
  	values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid);
+ 	values[Anum_pg_language_lanchecker - 1] = ObjectIdGetDatum(checkerOid);
  	nulls[Anum_pg_language_lanacl - 1] = true;
  
  	/* Check for pre-existing definition */
***************
*** 423,428 ****
--- 476,490 ----
  		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  	}
  
+ 	/* dependency on the checker function, if any */
+ 	if (OidIsValid(checkerOid))
+ 	{
+ 		referenced.classId = ProcedureRelationId;
+ 		referenced.objectId = checkerOid;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	/* Post creation hook for new procedural language */
  	InvokeObjectAccessHook(OAT_POST_CREATE,
  						   LanguageRelationId, myself.objectId, 0);
***************
*** 478,483 ****
--- 540,550 ----
  		if (!isnull)
  			result->tmplvalidator = TextDatumGetCString(datum);
  
+ 		datum = heap_getattr(tup, Anum_pg_pltemplate_tmplchecker,
+ 							 RelationGetDescr(rel), &isnull);
+ 		if (!isnull)
+ 			result->tmplchecker = TextDatumGetCString(datum);
+ 
  		datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary,
  							 RelationGetDescr(rel), &isnull);
  		if (!isnull)
*** ./src/backend/nodes/copyfuncs.c.orig	2011-12-07 05:59:37.135674570 +0100
--- ./src/backend/nodes/copyfuncs.c	2011-12-07 06:38:24.420227380 +0100
***************
*** 2880,2885 ****
--- 2880,2900 ----
  	return newnode;
  }
  
+ static CheckFunctionStmt *
+ _copyCheckFunctionStmt(CheckFunctionStmt *from)
+ {
+ 	CheckFunctionStmt *newnode = makeNode(CheckFunctionStmt);
+ 
+ 	COPY_NODE_FIELD(funcname);
+ 	COPY_NODE_FIELD(args);
+ 	COPY_STRING_FIELD(trgname);
+ 	COPY_NODE_FIELD(relation);
+ 	COPY_SCALAR_FIELD(is_function);
+ 	COPY_NODE_FIELD(options);
+ 
+ 	return newnode;
+ }
+ 
  static DoStmt *
  _copyDoStmt(DoStmt *from)
  {
***************
*** 4165,4170 ****
--- 4180,4188 ----
  		case T_AlterFunctionStmt:
  			retval = _copyAlterFunctionStmt(from);
  			break;
+ 		case T_CheckFunctionStmt:
+ 			retval = _copyCheckFunctionStmt(from);
+ 			break;
  		case T_DoStmt:
  			retval = _copyDoStmt(from);
  			break;
*** ./src/backend/nodes/equalfuncs.c.orig	2011-12-07 05:59:37.137674548 +0100
--- ./src/backend/nodes/equalfuncs.c	2011-12-07 06:38:56.187881989 +0100
***************
*** 1292,1297 ****
--- 1292,1310 ----
  }
  
  static bool
+ _equalCheckFunctionStmt(CheckFunctionStmt *a, CheckFunctionStmt *b)
+ {
+ 	COMPARE_NODE_FIELD(funcname);
+ 	COMPARE_NODE_FIELD(args);
+ 	COMPARE_STRING_FIELD(trgname);
+ 	COMPARE_NODE_FIELD(relation);
+ 	COMPARE_SCALAR_FIELD(is_function);
+ 	COMPARE_NODE_FIELD(options);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalDoStmt(DoStmt *a, DoStmt *b)
  {
  	COMPARE_NODE_FIELD(args);
***************
*** 2708,2713 ****
--- 2721,2729 ----
  		case T_AlterFunctionStmt:
  			retval = _equalAlterFunctionStmt(a, b);
  			break;
+ 		case T_CheckFunctionStmt:
+ 			retval = _equalCheckFunctionStmt(a, b);
+ 			break;
  		case T_DoStmt:
  			retval = _equalDoStmt(a, b);
  			break;
*** ./src/backend/parser/gram.y.orig	2011-12-07 05:59:37.138674536 +0100
--- ./src/backend/parser/gram.y	2011-12-08 14:48:01.858729545 +0100
***************
*** 227,232 ****
--- 227,233 ----
  		DeallocateStmt PrepareStmt ExecuteStmt
  		DropOwnedStmt ReassignOwnedStmt
  		AlterTSConfigurationStmt AlterTSDictionaryStmt
+ 		CheckFunctionStmt
  
  %type <node>	select_no_parens select_with_parens select_clause
  				simple_select values_clause
***************
*** 276,282 ****
  
  %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
  				opt_class opt_inline_handler opt_validator validator_clause
! 				opt_collate
  
  %type <range>	qualified_name OptConstrFromTable
  
--- 277,283 ----
  
  %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
  				opt_class opt_inline_handler opt_validator validator_clause
! 				opt_collate opt_checker
  
  %type <range>	qualified_name OptConstrFromTable
  
***************
*** 463,469 ****
  %type <windef>	window_definition over_clause window_specification
  				opt_frame_clause frame_extent frame_bound
  %type <str>		opt_existing_window_name
! 
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
--- 464,471 ----
  %type <windef>	window_definition over_clause window_specification
  				opt_frame_clause frame_extent frame_bound
  %type <str>		opt_existing_window_name
! %type <defelt>	CheckFunctionOptElem
! %type <list>	CheckFunctionOpts
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
***************
*** 700,705 ****
--- 702,708 ----
  			| AlterUserSetStmt
  			| AlterUserStmt
  			| AnalyzeStmt
+ 			| CheckFunctionStmt
  			| CheckPointStmt
  			| ClosePortalStmt
  			| ClusterStmt
***************
*** 3174,3184 ****
  				n->plhandler = NIL;
  				n->plinline = NIL;
  				n->plvalidator = NIL;
  				n->pltrusted = false;
  				$$ = (Node *)n;
  			}
  			| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
! 			  HANDLER handler_name opt_inline_handler opt_validator
  			{
  				CreatePLangStmt *n = makeNode(CreatePLangStmt);
  				n->replace = $2;
--- 3177,3188 ----
  				n->plhandler = NIL;
  				n->plinline = NIL;
  				n->plvalidator = NIL;
+ 				n->plchecker = NIL;
  				n->pltrusted = false;
  				$$ = (Node *)n;
  			}
  			| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
! 			  HANDLER handler_name opt_inline_handler opt_validator opt_checker
  			{
  				CreatePLangStmt *n = makeNode(CreatePLangStmt);
  				n->replace = $2;
***************
*** 3186,3191 ****
--- 3190,3196 ----
  				n->plhandler = $8;
  				n->plinline = $9;
  				n->plvalidator = $10;
+ 				n->plchecker = $11;
  				n->pltrusted = $3;
  				$$ = (Node *)n;
  			}
***************
*** 3220,3225 ****
--- 3225,3235 ----
  			| /*EMPTY*/								{ $$ = NIL; }
  		;
  
+ opt_checker:
+ 			CHECK handler_name					{ $$ = $2; }
+ 			| /*EMPTY*/								{ $$ = NIL; }
+ 		;
+ 
  DropPLangStmt:
  			DROP opt_procedural LANGUAGE ColId_or_Sconst opt_drop_behavior
  				{
***************
*** 6250,6255 ****
--- 6260,6344 ----
  
  /*****************************************************************************
   *
+  *		CHECK FUNCTION funcname(args)
+  *		CHECK TRIGGER triggername ON table
+  *
+  *
+  *****************************************************************************/
+ 
+ 
+ CheckFunctionStmt:
+ 			CHECK FUNCTION func_name func_args
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = $3;
+ 					n->args = extractArgTypes($4);
+ 					n->trgname = NULL;
+ 					n->relation = NULL;
+ 					n->is_function = true;
+ 					n->options = NIL;
+ 					$$ = (Node *) n;
+ 				}
+ 			| CHECK TRIGGER name ON qualified_name
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = NULL;
+ 					n->args = NIL;
+ 					n->trgname = $3;
+ 					n->relation = $5;
+ 					n->is_function = false;
+ 					n->options = NIL;
+ 					$$ = (Node *) n;
+ 				}
+ 			| CHECK FUNCTION ALL
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = NULL;
+ 					n->args = NIL;
+ 					n->trgname = NULL;
+ 					n->relation = NULL;
+ 					n->is_function = true;
+ 					n->options = NIL;
+ 					$$ = (Node *) n;
+ 				}
+ 			| CHECK FUNCTION ALL CheckFunctionOpts
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = NULL;
+ 					n->args = NIL;
+ 					n->trgname = NULL;
+ 					n->relation = NULL;
+ 					n->is_function = true;
+ 					n->options = $4;
+ 					$$ = (Node *) n;
+ 				}
+ 		;
+ 
+ CheckFunctionOpts:
+ 			CheckFunctionOptElem					{ $$ = list_make1($1); }
+ 			| CheckFunctionOpts CheckFunctionOptElem	{ $$ = lappend($1, $2); }
+ 		;
+ 
+ CheckFunctionOptElem:
+ 			IN_P LANGUAGE ColId
+ 				{
+ 					$$ = makeDefElem("language",
+ 								    (Node *) makeString($2));
+ 				}
+ 			| IN_P SCHEMA ColId
+ 				{
+ 					$$ = makeDefElem("schema",
+ 								    (Node *) makeString($2));
+ 				}
+ 			| FOR ROLE ColId
+ 				{
+ 					$$ = makeDefElem("owner",
+ 								    (Node *) makeString($3));
+ 				}
+ 		;
+ 
+ /*****************************************************************************
+  *
   *		DO <anonymous code block> [ LANGUAGE language ]
   *
   * We use a DefElem list for future extensibility, and to allow flexibility
*** ./src/backend/tcop/utility.c.orig	2011-12-07 05:59:37.140674512 +0100
--- ./src/backend/tcop/utility.c	2011-12-07 06:35:57.836744377 +0100
***************
*** 882,887 ****
--- 882,891 ----
  			AlterFunction((AlterFunctionStmt *) parsetree);
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			CheckFunction((CheckFunctionStmt *) parsetree);
+ 			break;
+ 
  		case T_IndexStmt:		/* CREATE INDEX */
  			{
  				IndexStmt  *stmt = (IndexStmt *) parsetree;
***************
*** 2125,2130 ****
--- 2129,2141 ----
  			}
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			if (((CheckFunctionStmt *) parsetree)->is_function)
+ 				tag = "CHECK FUNCTION";
+ 			else
+ 				tag = "CHECK TRIGGER";
+ 			break;
+ 
  		default:
  			elog(WARNING, "unrecognized node type: %d",
  				 (int) nodeTag(parsetree));
***************
*** 2565,2570 ****
--- 2576,2585 ----
  			}
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			lev = LOGSTMT_ALL;
+ 			break;
+ 
  		default:
  			elog(WARNING, "unrecognized node type: %d",
  				 (int) nodeTag(parsetree));
*** ./src/bin/pg_dump/pg_dump.c.orig	2011-12-07 05:59:37.142674489 +0100
--- ./src/bin/pg_dump/pg_dump.c	2011-12-07 06:00:07.312332234 +0100
***************
*** 5326,5338 ****
  	int			i_lanplcallfoid;
  	int			i_laninline;
  	int			i_lanvalidator;
  	int			i_lanacl;
  	int			i_lanowner;
  
  	/* Make sure we are in proper schema */
  	selectSourceSchema("pg_catalog");
  
! 	if (g_fout->remoteVersion >= 90000)
  	{
  		/* pg_language has a laninline column */
  		appendPQExpBuffer(query, "SELECT tableoid, oid, "
--- 5326,5351 ----
  	int			i_lanplcallfoid;
  	int			i_laninline;
  	int			i_lanvalidator;
+ 	int			i_lanchecker;
  	int			i_lanacl;
  	int			i_lanowner;
  
  	/* Make sure we are in proper schema */
  	selectSourceSchema("pg_catalog");
  
! 	if (g_fout->remoteVersion >= 90200)
! 	{
! 		/* pg_language has a lanchecker column */
! 		appendPQExpBuffer(query, "SELECT tableoid, oid, "
! 						  "lanname, lanpltrusted, lanplcallfoid, "
! 						  "laninline, lanvalidator, lanchecker, lanacl, "
! 						  "(%s lanowner) AS lanowner "
! 						  "FROM pg_language "
! 						  "WHERE lanispl "
! 						  "ORDER BY oid",
! 						  username_subquery);
! 	}
! 	else if (g_fout->remoteVersion >= 90000)
  	{
  		/* pg_language has a laninline column */
  		appendPQExpBuffer(query, "SELECT tableoid, oid, "
***************
*** 5409,5414 ****
--- 5422,5428 ----
  	/* these may fail and return -1: */
  	i_laninline = PQfnumber(res, "laninline");
  	i_lanvalidator = PQfnumber(res, "lanvalidator");
+ 	i_lanchecker = PQfnumber(res, "lanchecker");
  	i_lanacl = PQfnumber(res, "lanacl");
  	i_lanowner = PQfnumber(res, "lanowner");
  
***************
*** 5422,5427 ****
--- 5436,5445 ----
  		planginfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_lanname));
  		planginfo[i].lanpltrusted = *(PQgetvalue(res, i, i_lanpltrusted)) == 't';
  		planginfo[i].lanplcallfoid = atooid(PQgetvalue(res, i, i_lanplcallfoid));
+ 		if (i_lanchecker >= 0)
+ 			planginfo[i].lanchecker = atooid(PQgetvalue(res, i, i_lanchecker));
+ 		else
+ 			planginfo[i].lanchecker = InvalidOid;
  		if (i_laninline >= 0)
  			planginfo[i].laninline = atooid(PQgetvalue(res, i, i_laninline));
  		else
***************
*** 8597,8602 ****
--- 8615,8621 ----
  	char	   *qlanname;
  	char	   *lanschema;
  	FuncInfo   *funcInfo;
+ 	FuncInfo   *checkerInfo = NULL;
  	FuncInfo   *inlineInfo = NULL;
  	FuncInfo   *validatorInfo = NULL;
  
***************
*** 8616,8621 ****
--- 8635,8647 ----
  	if (funcInfo != NULL && !funcInfo->dobj.dump)
  		funcInfo = NULL;		/* treat not-dumped same as not-found */
  
+ 	if (OidIsValid(plang->lanchecker))
+ 	{
+ 		checkerInfo = findFuncByOid(plang->lanchecker);
+ 		if (checkerInfo != NULL && !checkerInfo->dobj.dump)
+ 			checkerInfo = NULL;
+ 	}
+ 
  	if (OidIsValid(plang->laninline))
  	{
  		inlineInfo = findFuncByOid(plang->laninline);
***************
*** 8642,8647 ****
--- 8668,8674 ----
  	 * don't, this might not work terribly nicely.
  	 */
  	useParams = (funcInfo != NULL &&
+ 				 (checkerInfo != NULL || !OidIsValid(plang->lanchecker)) &&
  				 (inlineInfo != NULL || !OidIsValid(plang->laninline)) &&
  				 (validatorInfo != NULL || !OidIsValid(plang->lanvalidator)));
  
***************
*** 8697,8702 ****
--- 8724,8739 ----
  			appendPQExpBuffer(defqry, "%s",
  							  fmtId(validatorInfo->dobj.name));
  		}
+ 		if (OidIsValid(plang->lanchecker))
+ 		{
+ 			appendPQExpBuffer(defqry, " CHECK ");
+ 			/* Cope with possibility that checker is in different schema */
+ 			if (checkerInfo->dobj.namespace != funcInfo->dobj.namespace)
+ 				appendPQExpBuffer(defqry, "%s.",
+ 							   fmtId(checkerInfo->dobj.namespace->dobj.name));
+ 			appendPQExpBuffer(defqry, "%s",
+ 							  fmtId(checkerInfo->dobj.name));
+ 		}
  	}
  	else
  	{
*** ./src/bin/pg_dump/pg_dump.h.orig	2011-12-07 05:59:37.144674467 +0100
--- ./src/bin/pg_dump/pg_dump.h	2011-12-07 06:00:07.348331825 +0100
***************
*** 387,392 ****
--- 387,393 ----
  	Oid			lanplcallfoid;
  	Oid			laninline;
  	Oid			lanvalidator;
+ 	Oid			lanchecker;
  	char	   *lanacl;
  	char	   *lanowner;		/* name of owner, or empty string */
  } ProcLangInfo;
*** ./src/bin/psql/tab-complete.c.orig	2011-12-07 05:59:37.146674445 +0100
--- ./src/bin/psql/tab-complete.c	2011-12-07 06:00:07.350331803 +0100
***************
*** 1,4 ****
--- 1,5 ----
  /*
+  *
   * psql - the PostgreSQL interactive terminal
   *
   * Copyright (c) 2000-2011, PostgreSQL Global Development Group
***************
*** 727,733 ****
  #define prev6_wd  (previous_words[5])
  
  	static const char *const sql_commands[] = {
! 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
  		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
--- 728,734 ----
  #define prev6_wd  (previous_words[5])
  
  	static const char *const sql_commands[] = {
! 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECK", "CHECKPOINT", "CLOSE", "CLUSTER",
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
  		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
***************
*** 1524,1529 ****
--- 1525,1552 ----
  
  		COMPLETE_WITH_LIST(list_TRANS);
  	}
+ 
+ /* CHECK */
+ 	else if (pg_strcasecmp(prev_wd, "CHECK") == 0)
+ 	{
+ 		static const char *const list_CHECK[] =
+ 		{"FUNCTION", "TRIGGER", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_CHECK);
+ 	}
+ 	else if (pg_strcasecmp(prev3_wd, "CHECK") == 0 &&
+ 			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+ 	{
+ 		COMPLETE_WITH_CONST("ON");
+ 	}
+ 	else if (pg_strcasecmp(prev4_wd, "CHECK") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "ON") == 0)
+ 	{
+ 		completion_info_charp = prev2_wd;
+ 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+ 	}
+ 
  /* CLUSTER */
  
  	/*
*** ./src/include/catalog/pg_language.h.orig	2011-12-07 05:59:37.147674434 +0100
--- ./src/include/catalog/pg_language.h	2011-12-07 06:00:07.351331792 +0100
***************
*** 37,42 ****
--- 37,43 ----
  	Oid			lanplcallfoid;	/* Call handler for PL */
  	Oid			laninline;		/* Optional anonymous-block handler function */
  	Oid			lanvalidator;	/* Optional validation function */
+ 	Oid			lanchecker;	/* Optional checker function */
  	aclitem		lanacl[1];		/* Access privileges */
  } FormData_pg_language;
  
***************
*** 51,57 ****
   *		compiler constants for pg_language
   * ----------------
   */
! #define Natts_pg_language				8
  #define Anum_pg_language_lanname		1
  #define Anum_pg_language_lanowner		2
  #define Anum_pg_language_lanispl		3
--- 52,58 ----
   *		compiler constants for pg_language
   * ----------------
   */
! #define Natts_pg_language				9
  #define Anum_pg_language_lanname		1
  #define Anum_pg_language_lanowner		2
  #define Anum_pg_language_lanispl		3
***************
*** 59,78 ****
  #define Anum_pg_language_lanplcallfoid	5
  #define Anum_pg_language_laninline		6
  #define Anum_pg_language_lanvalidator	7
! #define Anum_pg_language_lanacl			8
  
  /* ----------------
   *		initial contents of pg_language
   * ----------------
   */
  
! DATA(insert OID = 12 ( "internal"	PGUID f f 0 0 2246 _null_ ));
  DESCR("built-in functions");
  #define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c"			PGUID f f 0 0 2247 _null_ ));
  DESCR("dynamically-loaded C functions");
  #define ClanguageId 13
! DATA(insert OID = 14 ( "sql"		PGUID f t 0 0 2248 _null_ ));
  DESCR("SQL-language functions");
  #define SQLlanguageId 14
  
--- 60,80 ----
  #define Anum_pg_language_lanplcallfoid	5
  #define Anum_pg_language_laninline		6
  #define Anum_pg_language_lanvalidator	7
! #define Anum_pg_language_lanchecker	8
! #define Anum_pg_language_lanacl			9
  
  /* ----------------
   *		initial contents of pg_language
   * ----------------
   */
  
! DATA(insert OID = 12 ( "internal"	PGUID f f 0 0 2246 0 _null_ ));
  DESCR("built-in functions");
  #define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c"			PGUID f f 0 0 2247 0 _null_ ));
  DESCR("dynamically-loaded C functions");
  #define ClanguageId 13
! DATA(insert OID = 14 ( "sql"		PGUID f t 0 0 2248 0 _null_ ));
  DESCR("SQL-language functions");
  #define SQLlanguageId 14
  
*** ./src/include/catalog/pg_pltemplate.h.orig	2011-12-07 05:59:37.149674412 +0100
--- ./src/include/catalog/pg_pltemplate.h	2011-12-07 06:00:07.352331780 +0100
***************
*** 36,41 ****
--- 36,42 ----
  	text		tmplhandler;	/* name of call handler function */
  	text		tmplinline;		/* name of anonymous-block handler, or NULL */
  	text		tmplvalidator;	/* name of validator function, or NULL */
+ 	text		tmplchecker;	/* name of checker function, or NULL */
  	text		tmpllibrary;	/* path of shared library */
  	aclitem		tmplacl[1];		/* access privileges for template */
  } FormData_pg_pltemplate;
***************
*** 51,65 ****
   *		compiler constants for pg_pltemplate
   * ----------------
   */
! #define Natts_pg_pltemplate					8
  #define Anum_pg_pltemplate_tmplname			1
  #define Anum_pg_pltemplate_tmpltrusted		2
  #define Anum_pg_pltemplate_tmpldbacreate	3
  #define Anum_pg_pltemplate_tmplhandler		4
  #define Anum_pg_pltemplate_tmplinline		5
  #define Anum_pg_pltemplate_tmplvalidator	6
! #define Anum_pg_pltemplate_tmpllibrary		7
! #define Anum_pg_pltemplate_tmplacl			8
  
  
  /* ----------------
--- 52,67 ----
   *		compiler constants for pg_pltemplate
   * ----------------
   */
! #define Natts_pg_pltemplate					9
  #define Anum_pg_pltemplate_tmplname			1
  #define Anum_pg_pltemplate_tmpltrusted		2
  #define Anum_pg_pltemplate_tmpldbacreate	3
  #define Anum_pg_pltemplate_tmplhandler		4
  #define Anum_pg_pltemplate_tmplinline		5
  #define Anum_pg_pltemplate_tmplvalidator	6
! #define Anum_pg_pltemplate_tmplchecker		7
! #define Anum_pg_pltemplate_tmpllibrary		8
! #define Anum_pg_pltemplate_tmplacl			9
  
  
  /* ----------------
***************
*** 67,79 ****
   * ----------------
   */
  
! DATA(insert ( "plpgsql"		t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl"		t t "pltcl_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu"		f f "pltclu_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl"		t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu"		f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu"	f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u"	f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u"	f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" "$libdir/plpython3" _null_ ));
  
  #endif   /* PG_PLTEMPLATE_H */
--- 69,81 ----
   * ----------------
   */
  
! DATA(insert ( "plpgsql"		t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "plpgsql_checker" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl"		t t "pltcl_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu"		f f "pltclu_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl"		t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu"		f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu"	f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u"	f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u"	f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" _null_ "$libdir/plpython3" _null_ ));
  
  #endif   /* PG_PLTEMPLATE_H */
*** ./src/include/commands/defrem.h.orig	2011-12-07 05:59:37.150674400 +0100
--- ./src/include/commands/defrem.h	2011-12-07 06:00:07.352331780 +0100
***************
*** 62,67 ****
--- 62,68 ----
  /* commands/functioncmds.c */
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
  extern void RemoveFunctionById(Oid funcOid);
+ extern void CheckFunction(CheckFunctionStmt *stmt);
  extern void SetFunctionReturnType(Oid funcOid, Oid newRetType);
  extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType);
  extern void RenameFunction(List *name, List *argtypes, const char *newname);
*** ./src/include/nodes/nodes.h.orig	2011-12-07 05:59:37.151674388 +0100
--- ./src/include/nodes/nodes.h	2011-12-07 06:00:07.353331768 +0100
***************
*** 291,296 ****
--- 291,297 ----
  	T_IndexStmt,
  	T_CreateFunctionStmt,
  	T_AlterFunctionStmt,
+ 	T_CheckFunctionStmt,
  	T_DoStmt,
  	T_RenameStmt,
  	T_RuleStmt,
*** ./src/include/nodes/parsenodes.h.orig	2011-12-07 05:59:37.153674364 +0100
--- ./src/include/nodes/parsenodes.h	2011-12-07 06:16:44.502121440 +0100
***************
*** 1734,1739 ****
--- 1734,1740 ----
  	List	   *plhandler;		/* PL call handler function (qual. name) */
  	List	   *plinline;		/* optional inline function (qual. name) */
  	List	   *plvalidator;	/* optional validator function (qual. name) */
+ 	List	   *plchecker;		/* optional checker function (qual. name) */
  	bool		pltrusted;		/* PL is trusted */
  } CreatePLangStmt;
  
***************
*** 2077,2082 ****
--- 2078,2098 ----
  } AlterFunctionStmt;
  
  /* ----------------------
+  *		Check {Function|Trigger} Statement
+  * ----------------------
+  */
+ typedef struct CheckFunctionStmt
+ {
+ 	NodeTag		type;
+ 	List	   *funcname;			/* qualified name of checked object */
+ 	List	   *args;			/* types of the arguments */
+ 	char	   *trgname;			/* trigger's name */
+ 	RangeVar	*relation;		/* trigger's relation */
+ 	bool	   is_function;		/* true for CHECK FUNCTION statement */
+ 	List	   *options;			/* other DefElem options */
+ } CheckFunctionStmt;
+ 
+ /* ----------------------
   *		DO Statement
   *
   * DoStmt is the raw parser output, InlineCodeBlock is the execution-time API
*** ./src/pl/plpgsql/src/pl_comp.c.orig	2011-12-07 05:59:37.155674342 +0100
--- ./src/pl/plpgsql/src/pl_comp.c	2011-12-07 06:00:07.360331689 +0100
***************
*** 115,121 ****
  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.
--- 115,120 ----
***************
*** 175,181 ****
  			 * 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
--- 174,180 ----
  			 * 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
***************
*** 2426,2432 ****
  }
  
  /*
!  * 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
--- 2425,2431 ----
  }
  
  /*
!  * 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,2446 ****
   * 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);
--- 2438,2445 ----
   * 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);
*** ./src/pl/plpgsql/src/pl_exec.c.orig	2011-12-07 05:59:37.157674320 +0100
--- ./src/pl/plpgsql/src/pl_exec.c	2011-12-07 07:36:23.112366630 +0100
***************
*** 210,216 ****
  static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
  						  PLpgSQL_expr *dynquery, List *params,
  						  const char *portalname, int cursorOptions);
! 
  
  /* ----------
   * plpgsql_exec_function	Called by the call handler for
--- 210,228 ----
  static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
  						  PLpgSQL_expr *dynquery, List *params,
  						  const char *portalname, int cursorOptions);
! static void check_row_or_rec(PLpgSQL_execstate *estate, PLpgSQL_row *row, PLpgSQL_rec *rec);
! static void check_expr(PLpgSQL_execstate *estate, PLpgSQL_expr *expr);
! static void assign_tupdesc_row_or_rec(PLpgSQL_execstate *estate,
! 					    PLpgSQL_row *row, PLpgSQL_rec *rec,
! 								    TupleDesc tupdesc);
! static TupleDesc expr_get_desc(PLpgSQL_execstate *estate,
! 						PLpgSQL_expr *query,
! 							bool use_element_type,
! 							bool expand_record,
! 								bool is_expression);
! static void var_init_to_null(PLpgSQL_execstate *estate, int varno);
! static void check_stmts(PLpgSQL_execstate *estate, List *stmts);
! static void check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt);
  
  /* ----------
   * plpgsql_exec_function	Called by the call handler for
***************
*** 6176,6178 ****
--- 6188,7240 ----
  
  	return portal;
  }
+ 
+ /*
+  * Following code ensures a CHECK FUNCTION and CHECK TRIGGER statements for PL/pgSQL
+  *
+  */
+ 
+ /*
+  * append a CONTEXT to error message
+  */
+ static void
+ check_error_callback(void *arg)
+ {
+ 	PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;
+ 
+ 	if (estate->err_stmt != NULL)
+ 	{
+ 		/* translator: last %s is a plpgsql statement type name */
+ 		errcontext("line %d at %s",
+ 				   estate->err_stmt->lineno,
+ 				   plpgsql_stmt_typename(estate->err_stmt));
+ 	}
+ }
+ 
+ /*
+  * Check function - it prepare variables and starts a prepare plan walker
+  *		called by function checker
+  */
+ void
+ plpgsql_check_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
+ {
+ 	PLpgSQL_execstate estate;
+ 	ErrorContextCallback plerrcontext;
+ 	int			i;
+ 
+ 	/* Setup error callback for ereport */
+ 	plerrcontext.callback = check_error_callback;
+ 	plerrcontext.arg = &estate;
+ 	plerrcontext.previous = error_context_stack;
+ 	error_context_stack = &plerrcontext;
+ 
+ 	/*
+ 	 * Setup the execution state - we would to reuse some exec routines
+ 	 * so we need a estate
+ 	 */
+ 	plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo);
+ 
+ 	/*
+ 	 * Make local execution copies of all the datums
+ 	 */
+ 	for (i = 0; i < estate.ndatums; i++)
+ 		estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
+ 
+ 	/*
+ 	 * Store the actual call argument values into the appropriate variables
+ 	 */
+ 	for (i = 0; i < func->fn_nargs; i++)
+ 	{
+ 		int			n = func->fn_argvarnos[i];
+ 
+ 		switch (estate.datums[n]->dtype)
+ 		{
+ 			case PLPGSQL_DTYPE_VAR:
+ 				{
+ 					var_init_to_null(&estate, n);
+ 				}
+ 				break;
+ 
+ 			case PLPGSQL_DTYPE_ROW:
+ 				{
+ 					PLpgSQL_row *row = (PLpgSQL_row *) estate.datums[n];
+ 
+ 					exec_move_row(&estate, NULL, row, NULL, NULL);
+ 				}
+ 				break;
+ 
+ 			default:
+ 				elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(&estate, (PLpgSQL_stmt *) func->action);
+ 
+ 	/* Cleanup temporary memory */
+ 	plpgsql_destroy_econtext(&estate);
+ 
+ 	/* Pop the error context stack */
+ 	error_context_stack = plerrcontext.previous;
+ }
+ 
+ /*
+  * Check trigger - prepare fake environments for testing trigger
+  *
+  */
+ void
+ plpgsql_check_trigger(PLpgSQL_function *func,
+ 					 TriggerData *trigdata)
+ {
+ 	PLpgSQL_execstate estate;
+ 	ErrorContextCallback plerrcontext;
+ 	PLpgSQL_rec *rec_new,
+ 			   *rec_old;
+ 	int			i;
+ 
+ 	/* Setup error callback for ereport */
+ 	plerrcontext.callback = check_error_callback;
+ 	plerrcontext.arg = &estate;
+ 	plerrcontext.previous = error_context_stack;
+ 	error_context_stack = &plerrcontext;
+ 
+ 	/*
+ 	 * Setup the execution state - we would to reuse some exec routines
+ 	 * so we need a estate
+ 	 */
+ 	plpgsql_estate_setup(&estate, func, NULL);
+ 
+ 	/*
+ 	 * Make local execution copies of all the datums
+ 	 */
+ 	for (i = 0; i < estate.ndatums; i++)
+ 		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 *) (estate.datums[func->new_varno]);
+ 	rec_new->freetup = false;
+ 	rec_new->freetupdesc = false;
+ 	assign_tupdesc_row_or_rec(&estate, NULL, rec_new, trigdata->tg_relation->rd_att);
+ 
+ 	rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
+ 	rec_old->freetup = false;
+ 	rec_old->freetupdesc = false;
+ 	assign_tupdesc_row_or_rec(&estate, NULL, rec_old, trigdata->tg_relation->rd_att);
+ 
+ 	/*
+ 	 * Assign the special tg_ variables
+ 	 */
+ 	var_init_to_null(&estate, func->tg_op_varno);
+ 	var_init_to_null(&estate, func->tg_name_varno);
+ 	var_init_to_null(&estate, func->tg_when_varno);
+ 	var_init_to_null(&estate, func->tg_level_varno);
+ 	var_init_to_null(&estate, func->tg_relid_varno);
+ 	var_init_to_null(&estate, func->tg_relname_varno);
+ 	var_init_to_null(&estate, func->tg_table_name_varno);
+ 	var_init_to_null(&estate, func->tg_table_schema_varno);
+ 	var_init_to_null(&estate, func->tg_nargs_varno);
+ 	var_init_to_null(&estate, func->tg_argv_varno);
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(&estate, (PLpgSQL_stmt *) func->action);
+ 
+ 	/* Cleanup temporary memory */
+ 	plpgsql_destroy_econtext(&estate);
+ 
+ 	/* Pop the error context stack */
+ 	error_context_stack = plerrcontext.previous;
+ }
+ 
+ /*
+  * Verify lvalue
+  *    It doesn't repeat a checks that are done.
+  *  Checks a subscript expressions, verify a validity of record's fields
+  */
+ static void
+ check_target(PLpgSQL_execstate *estate, int varno)
+ {
+ 	PLpgSQL_datum *target = estate->datums[varno];
+ 
+ 	switch (target->dtype)
+ 	{
+ 		case PLPGSQL_DTYPE_VAR:
+ 		case PLPGSQL_DTYPE_REC:
+ 			break;
+ 
+ 		case PLPGSQL_DTYPE_ROW:
+ 			check_row_or_rec(estate, (PLpgSQL_row *) target, NULL);
+ 			break;
+ 
+ 		case PLPGSQL_DTYPE_RECFIELD:
+ 			{
+ 				PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target;
+ 				PLpgSQL_rec *rec;
+ 				int			fno;
+ 
+ 				rec = (PLpgSQL_rec *) (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)));
+ 
+ 					check_expr(estate, arrayelem->subscript);
+ 
+ 					target = 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(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_execstate *estate, 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(estate, row->varnos[fnum]);
+ 		}
+ 	}
+ }
+ 
+ /*
+  * Generate a prepared plan - this is simplyfied copy from pl_exec.c
+  *   Is not necessary to check simple plan
+  */
+ static void
+ prepare_expr(PLpgSQL_execstate *estate,
+ 				  PLpgSQL_expr *expr, int cursorOptions)
+ {
+ 	SPIPlanPtr	plan;
+ 
+ 	/* leave when there are not expression */
+ 	if (expr == NULL)
+ 		return;
+ 
+ 	/* leave when plan is created */
+ 	if (expr->plan != NULL)
+ 		return;
+ 
+ 	/*
+ 	 * 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 = 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")));
+ 			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.")));
+ 			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_execstate *estate, PLpgSQL_expr *expr)
+ {
+ 	TupleDesc tupdesc;
+ 
+ 	if (expr != NULL)
+ 	{
+ 		prepare_expr(estate, expr, 0);
+ 		tupdesc = expr_get_desc(estate, expr, false, false, true);
+ 		ReleaseTupleDesc(tupdesc);
+ 	}
+ }
+ 
+ /*
+  * 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_execstate *estate,
+ 					    PLpgSQL_row *row, PLpgSQL_rec *rec,
+ 								    TupleDesc tupdesc)
+ {
+ 	bool	   *nulls;
+ 	HeapTuple  tup;
+ 
+ 	if (tupdesc == NULL)
+ 		elog(ERROR, "tuple descriptor is empty");
+ 
+ 	/*
+ 	 * row variable has assigned TupleDesc already, so don't be processed
+ 	 * here
+ 	 */
+ 	if (rec != NULL)
+ 	{
+ 		PLpgSQL_rec *target = (PLpgSQL_rec *)(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_execstate *estate, int varno, TupleDesc tupdesc)
+ {
+ 	PLpgSQL_datum *target = estate->datums[varno];
+ 
+ 	if (target->dtype == PLPGSQL_DTYPE_REC)
+ 		assign_tupdesc_row_or_rec(estate, NULL, (PLpgSQL_rec *) target, tupdesc);
+ }
+ 
+ /*
+  * Returns a tuple descriptor based on existing plan
+  */
+ static TupleDesc 
+ expr_get_desc(PLpgSQL_execstate *estate,
+ 						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);
+ 
+ 		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 (tupdesc->natts != 1)
+ 			ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 				 errmsg_plural("query \"%s\" returned %d column",
+ 							   "query \"%s\" returned %d columns",
+ 							   tupdesc->natts,
+ 							   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))));
+ 
+ 		/* 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
+ 			elog(ERROR, "cannot to identify real type for record type variable");
+ 	}
+ 
+ 	if (is_expression && tupdesc->natts != 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 			  errmsg_plural("query \"%s\" returned %d column",
+ 				   "query \"%s\" returned %d columns",
+ 				   tupdesc->natts,
+ 				   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(..)
+ 	 */
+ 	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)
+ 						elog(ERROR, "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
+  */
+ static void
+ check_stmts(PLpgSQL_execstate *estate, List *stmts)
+ {
+ 	ListCell *lc;
+ 
+ 	foreach(lc, stmts)
+ 	{
+ 		check_stmt(estate, (PLpgSQL_stmt *) lfirst(lc));
+ 	}
+ }
+ 
+ /*
+  * walk over all statements
+  */
+ static void
+ check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
+ {
+ 	TupleDesc	tupdesc = NULL;
+ 	PLpgSQL_function *func;
+ 	ListCell *l;
+ 
+ 	if (stmt == NULL)
+ 		return;
+ 
+ 	estate->err_stmt = stmt;
+ 	func = estate->func;
+ 
+ 	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++)
+ 				{
+ 					d = func->datums[stmt_block->initvarnos[i]];
+ 
+ 					if (d->dtype == PLPGSQL_DTYPE_VAR)
+ 					{
+ 						PLpgSQL_var *var = (PLpgSQL_var *) d;
+ 
+ 						check_expr(estate, var->default_val);
+ 					}
+ 				}
+ 
+ 				check_stmts(estate, stmt_block->body);
+ 				
+ 				if (stmt_block->exceptions)
+ 				{
+ 					foreach(l, stmt_block->exceptions->exc_list)
+ 					{
+ 						check_stmts(estate, ((PLpgSQL_exception *) lfirst(l))->action);
+ 					}
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_ASSIGN:
+ 			{
+ 				PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt;
+ 
+ 				/* prepare plan if desn't exist yet */
+ 				prepare_expr(estate, stmt_assign->expr, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_assign->expr,
+ 										false,		/* no element type */
+ 										true,		/* expand record */
+ 										true);		/* is expression */
+ 
+ 				/* check target, ensure target can get a result */
+ 				check_target(estate, stmt_assign->varno);
+ 
+ 				/* assign a tupdesc to record variable */
+ 				assign_tupdesc_dno(estate, stmt_assign->varno, tupdesc);
+ 				ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_IF:
+ 			{
+ 				PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt;
+ 				ListCell *l;
+ 
+ 				check_expr(estate, stmt_if->cond);
+ 
+ 				check_stmts(estate, stmt_if->then_body);
+ 
+ 				foreach(l, stmt_if->elsif_list)
+ 				{
+ 					PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l);
+ 
+ 					check_expr(estate, elif->cond);
+ 					check_stmts(estate, elif->stmts);
+ 				}
+ 
+ 				check_stmts(estate, 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 *) estate->datums[stmt_case->t_varno];
+ 
+ 					/* we need to set hidden variable type */
+ 					prepare_expr(estate, stmt_case->t_expr, 0);
+ 
+ 					tupdesc = expr_get_desc(estate,
+ 									stmt_case->t_expr,
+ 										false,		/* no element type */
+ 										false,		/* 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,
+ 												   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(estate, cwt->expr);
+ 					check_stmts(estate, cwt->stmts);
+ 				}
+ 
+ 				check_stmts(estate, stmt_case->else_stmts);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_LOOP:
+ 			check_stmts(estate, ((PLpgSQL_stmt_loop *) stmt)->body);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_WHILE:
+ 			{
+ 				PLpgSQL_stmt_while *stmt_while = (PLpgSQL_stmt_while *) stmt;
+ 
+ 				check_expr(estate, stmt_while->cond);
+ 				check_stmts(estate, stmt_while->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORI:
+ 			{
+ 				PLpgSQL_stmt_fori *stmt_fori = (PLpgSQL_stmt_fori *) stmt;
+ 
+ 				check_expr(estate, stmt_fori->lower);
+ 				check_expr(estate, stmt_fori->upper);
+ 				check_expr(estate, stmt_fori->step);
+ 
+ 				check_stmts(estate, stmt_fori->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORS:
+ 			{
+ 				PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt;
+ 
+ 				/* we need to set hidden variable type */
+ 				prepare_expr(estate, stmt_fors->query, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_fors->query,
+ 									false,		/* no element type */
+ 									false,		/* expand record */
+ 									false);		/* is expression */
+ 
+ 				check_row_or_rec(estate, stmt_fors->row, stmt_fors->rec);
+ 				assign_tupdesc_row_or_rec(estate, stmt_fors->row, stmt_fors->rec, tupdesc);
+ 
+ 				check_stmts(estate, stmt_fors->body);
+ 				ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORC:
+ 			{
+ 				PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt;
+ 				PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_forc->curvar];
+ 
+ 				prepare_expr(estate, stmt_forc->argquery, 0);
+ 
+ 				if (var->cursor_explicit_expr != NULL)
+ 				{
+ 					prepare_expr(estate, var->cursor_explicit_expr,
+ 								    var->cursor_options);
+ 
+ 					tupdesc = expr_get_desc(estate,
+ 									var->cursor_explicit_expr,
+ 										false,		/* no element type */
+ 										false,		/* expand record */
+ 										false);		/* is expression */
+ 
+ 					check_row_or_rec(estate, stmt_forc->row, stmt_forc->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_forc->row, stmt_forc->rec, tupdesc);
+ 				}
+ 
+ 				check_stmts(estate, stmt_forc->body);
+ 				if (tupdesc != NULL)
+ 					ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNFORS:
+ 			{
+ 				PLpgSQL_stmt_dynfors * stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt;
+ 
+ 				if (stmt_dynfors->rec != NULL)
+ 					elog(ERROR, "cannot determinate a result of dynamic SQL");
+ 
+ 				check_expr(estate, stmt_dynfors->query);
+ 
+ 				foreach(l, stmt_dynfors->params)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				check_stmts(estate, stmt_dynfors->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FOREACH_A:
+ 			{
+ 				PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt;
+ 
+ 				prepare_expr(estate, stmt_foreach_a->expr, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_foreach_a->expr,
+ 									true,		/* no element type */
+ 									false,		/* expand record */
+ 									true);		/* is expression */
+ 
+ 				check_target(estate, stmt_foreach_a->varno);
+ 				assign_tupdesc_dno(estate, stmt_foreach_a->varno, tupdesc);
+ 				ReleaseTupleDesc(tupdesc);
+ 
+ 				check_stmts(estate, stmt_foreach_a->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_EXIT:
+ 			check_expr(estate, ((PLpgSQL_stmt_exit *) stmt)->cond);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_PERFORM:
+ 			prepare_expr(estate, ((PLpgSQL_stmt_perform *) stmt)->expr, 0);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN:
+ 			check_expr(estate, ((PLpgSQL_stmt_return *) stmt)->expr);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN_NEXT:
+ 			check_expr(estate, ((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(estate, stmt_rq->dynquery);
+ 				prepare_expr(estate, stmt_rq->query, 0);
+ 
+ 				foreach(l, stmt_rq->params)
+ 				{
+ 					check_expr(estate, (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(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				foreach(l, stmt_raise->options)
+ 				{
+ 					PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(l);
+ 
+ 					check_expr(estate, 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;
+ 
+ 				prepare_expr(estate, stmt_execsql->sqlstmt, 0);
+ 				if (stmt_execsql->into)
+ 				{
+ 					tupdesc = expr_get_desc(estate,
+ 								stmt_execsql->sqlstmt,
+ 											false,		/* no element type */
+ 											false,		/* expand record */
+ 											false);		/* is expression */
+ 
+ 					/* check target, ensure target can get a result */
+ 					check_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec, tupdesc);
+ 					ReleaseTupleDesc(tupdesc);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNEXECUTE:
+ 			{
+ 				PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt;
+ 
+ 				check_expr(estate, stmt_dynexecute->query);
+ 
+ 				foreach(l, stmt_dynexecute->params)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				if (stmt_dynexecute->into)
+ 				{
+ 					if (stmt_dynexecute->rec != NULL)
+ 						elog(ERROR, "cannot determinate a result of dynamic SQL");
+ 
+ 					check_row_or_rec(estate, stmt_dynexecute->row, stmt_dynexecute->rec);
+ 				}
+ 			}
+ 			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)
+ 					prepare_expr(estate, var->cursor_explicit_expr,
+ 								   var->cursor_options);
+ 
+ 				prepare_expr(estate, stmt_open->query, 0);
+ 				prepare_expr(estate, stmt_open->argquery, 0);
+ 				check_expr(estate, stmt_open->dynquery);
+ 
+ 				foreach(l, stmt_open->params)
+ 				{
+ 					check_expr(estate, (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(estate, diag_item->target);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FETCH:
+ 			{
+ 				PLpgSQL_stmt_fetch *stmt_fetch = (PLpgSQL_stmt_fetch *) stmt;
+ 				PLpgSQL_var *var = (PLpgSQL_var *)(estate->datums[stmt_fetch->curvar]);
+ 
+ 				if (var != NULL && var->cursor_explicit_expr != NULL)
+ 				{
+ 					prepare_expr(estate, var->cursor_explicit_expr, 
+ 									    var->cursor_options);
+ 					tupdesc = expr_get_desc(estate,
+ 								    var->cursor_explicit_expr,
+ 											false,		/* no element type */
+ 											false,		/* expand record */
+ 											false);		/* is expression */
+ 					check_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec, tupdesc);
+ 					ReleaseTupleDesc(tupdesc);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_CLOSE:
+ 			break;
+ 
+ 		default:
+ 			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
+ 			return; /* be compiler quite */
+ 	}
+ }
+ 
+ /*
+  * Initialize variable to NULL
+  */
+ static void
+ var_init_to_null(PLpgSQL_execstate *estate, int varno)
+ {
+ 	PLpgSQL_var *var = (PLpgSQL_var *) estate->datums[varno];
+ 	var->value = (Datum) 0;
+ 	var->isnull = true;
+ 	var->freeval = false;
+ }
*** ./src/pl/plpgsql/src/pl_handler.c.orig	2011-12-07 05:59:37.158674309 +0100
--- ./src/pl/plpgsql/src/pl_handler.c	2011-12-07 06:00:07.365331632 +0100
***************
*** 312,314 ****
--- 312,452 ----
  
  	PG_RETURN_VOID();
  }
+ 
+ /* ----------
+  * plpgsql_checker
+  *
+  * This function attempts to check a embeded SQL inside a PL/pgSQL function at
+  * CHECK FUNCTION time. It should to have one or two parameters. Second
+  * parameter is a relation (used when function is trigger).
+  * ----------
+  */
+ PG_FUNCTION_INFO_V1(plpgsql_checker);
+ 
+ Datum
+ plpgsql_checker(PG_FUNCTION_ARGS)
+ {
+ 	Oid			funcoid = PG_GETARG_OID(0);
+ 	Oid			relid = PG_GETARG_OID(1);
+ 	HeapTuple	tuple;
+ 	FunctionCallInfoData fake_fcinfo;
+ 	FmgrInfo	flinfo;
+ 	TriggerData trigdata;
+ 	int			rc;
+ 	PLpgSQL_function *function;
+ 	PLpgSQL_execstate *cur_estate;
+ 
+ 	Form_pg_proc proc;
+ 	char		functyptype;
+ 	bool	   istrigger = false;
+ 
+ 	/* we don't need to repair a check done by validator */
+ 
+ 	tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
+ 	if (!HeapTupleIsValid(tuple))
+ 		elog(ERROR, "cache lookup failed for function %u", funcoid);
+ 	proc = (Form_pg_proc) GETSTRUCT(tuple);
+ 
+ 	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))
+ 		{
+ 			istrigger = true;
+ 			if (!OidIsValid(relid))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("PL/pgSQL trigger functions cannot be checked directly"),
+ 						 errhint("use CHECK TRIGGER statement instead")));
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Connect to SPI manager
+ 	 */
+ 	if ((rc = SPI_connect()) != SPI_OK_CONNECT)
+ 			elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
+ 
+ 	/*
+ 	 * Set up a fake fcinfo with just enough info to satisfy
+ 	 * plpgsql_compile().
+ 	 *
+ 	 * there should be a different real argtypes for polymorphic params
+ 	 */
+ 	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;
+ 		trigdata.tg_relation = relation_open(relid, AccessShareLock);
+ 		fake_fcinfo.context = (Node *) &trigdata;
+ 	}
+ 
+ 	/* Get a compiled function */
+ 	function = plpgsql_compile(&fake_fcinfo, false);
+ 
+ 	/* 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 prepare plans */
+ 	PG_TRY();
+ 	{
+ 		if (!istrigger)
+ 			plpgsql_check_function(function, &fake_fcinfo);
+ 		else
+ 			plpgsql_check_trigger(function, &trigdata);
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		if (istrigger)
+ 			relation_close(trigdata.tg_relation, AccessShareLock);
+ 
+ 		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);
+ 
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	if (istrigger)
+ 		relation_close(trigdata.tg_relation, AccessShareLock);
+ 
+ 	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);
+ 
+ 	/*
+ 	 * Disconnect from SPI manager
+ 	 */
+ 	if ((rc = SPI_finish()) != SPI_OK_FINISH)
+ 			elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
+ 
+ 	ReleaseSysCache(tuple);
+ 
+ 	PG_RETURN_VOID();
+ }
*** ./src/pl/plpgsql/src/plpgsql.h.orig	2011-12-07 05:59:37.160674287 +0100
--- ./src/pl/plpgsql/src/plpgsql.h	2011-12-07 06:00:07.366331620 +0100
***************
*** 902,907 ****
--- 902,908 ----
  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,916 ****
--- 912,918 ----
  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_checker(PG_FUNCTION_ARGS);
  
  /* ----------
   * Functions in pl_exec.c
***************
*** 928,933 ****
--- 930,939 ----
  extern void exec_get_datum_type_info(PLpgSQL_execstate *estate,
  						 PLpgSQL_datum *datum,
  						 Oid *typeid, int32 *typmod, Oid *collation);
+ extern void plpgsql_check_function(PLpgSQL_function *func,
+ 					 FunctionCallInfo fcinfo);
+ extern void plpgsql_check_trigger(PLpgSQL_function *func,
+ 					 TriggerData *trigdata);
  
  /* ----------
   * Functions for namespace handling in pl_funcs.c
*** ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql.orig	2011-12-07 05:59:37.162674263 +0100
--- ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql	2011-12-07 06:00:07.366331620 +0100
***************
*** 5,7 ****
--- 5,8 ----
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_call_handler();
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_inline_handler(internal);
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_validator(oid);
+ ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_checker(oid, regclass);
*** ./src/test/regress/expected/plpgsql.out.orig	2011-12-07 05:59:37.164674240 +0100
--- ./src/test/regress/expected/plpgsql.out	2011-12-07 08:00:04.000000000 +0100
***************
*** 302,307 ****
--- 302,310 ----
  ' 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
+ check trigger tg_hslot_biu on HSlot;
+ NOTICE:  checked function "tg_hslot_biu()"
  -- ************************************************************
  -- * BEFORE DELETE on HSlot
  -- *	- prevent from manual manipulation
***************
*** 635,640 ****
--- 638,646 ----
      raise exception ''illegal backlink beginning with %'', mytype;
  end;
  ' language plpgsql;
+ -- check function should not fail
+ check function tg_backlink_set(bpchar, bpchar);
+ NOTICE:  checked function "tg_backlink_set(character,character)"
  -- ************************************************************
  -- * Support function to clear out the backlink field if
  -- * it still points to specific slot
***************
*** 2802,2807 ****
--- 2808,2842 ----
   
  (1 row)
  
+ -- check function should not fail
+ check function for_vect();
+ NOTICE:  checked function "for_vect()"
+ -- 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
***************
*** 3283,3288 ****
--- 3318,3326 ----
    return;
  end;
  $$ language plpgsql;
+ -- check function should not fail
+ check function forc01();
+ NOTICE:  checked function "forc01()"
  select forc01();
  NOTICE:  5 from c
  NOTICE:  6 from c
***************
*** 3716,3721 ****
--- 3754,3762 ----
    end case;
  end;
  $$ language plpgsql immutable;
+ -- check function should not fail
+ check function case_test(bigint);
+ NOTICE:  checked function "case_test(bigint)"
  select case_test(1);
   case_test 
  -----------
***************
*** 4571,4573 ****
--- 4612,4956 ----
  CONTEXT:  PL/pgSQL function "testoa" line 5 at assignment
  drop function arrayassign1();
  drop function testoa(x1 int, x2 int, x3 int);
+ --
+ -- check function statement tests
+ --
+ create table t1(a int, b int);
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+ end;
+ $$ language plpgsql;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ LINE 1: update t1 set c = 30
+                       ^
+ DETAIL:  column "c" of relation "t1" does not exist
+ QUERY:  update t1 set c = 30
+ CONTEXT:  line 4 at SQL statement
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  record "r" has no field "c"
+ CONTEXT:  SQL statement "SELECT r.c"
+ line 6 at RAISE
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  record "r" has no field "c"
+ CONTEXT:  SQL statement "SELECT r.c"
+ line 6 at RAISE
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  record "r" has no field "c"
+ CONTEXT:  line 6 at assignment
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ LINE 1: SELECT a + b
+                ^
+ DETAIL:  column "a" does not exist
+ QUERY:  SELECT a + b
+ CONTEXT:  line 5 at assignment
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  too many parameters specified for RAISE
+ CONTEXT:  line 4 at RAISE
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  too few parameters specified for RAISE
+ CONTEXT:  line 4 at RAISE
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ LINE 1: SELECT c+10
+                ^
+ DETAIL:  column "c" does not exist
+ QUERY:  SELECT c+10
+ CONTEXT:  line 5 at assignment
+ 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;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  subscripted object is not an array
+ CONTEXT:  line 5 at assignment
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  record "_exception" has no field "hint"
+ CONTEXT:  line 7 at GET DIAGNOSTICS
+ 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);
+ check trigger t1_f1 on t1;
+ WARNING:  error in function "f1_trg()"
+ DETAIL:  record "new" has no field "c"
+ CONTEXT:  SQL statement "SELECT new.c"
+ line 5 at RAISE
+ 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
+ check trigger t1_f1 on t1;
+ WARNING:  error in function "f1_trg()"
+ DETAIL:  record "new" has no field "c"
+ CONTEXT:  line 5 at assignment
+ -- 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
+ check trigger t1_f1 on t1;
+ NOTICE:  checked function "f1_trg()"
+ -- ok
+ insert into t1 values(6,30);
+ drop table t1;
+ drop type _exception_type;
+ drop function f1_trg();
*** ./src/test/regress/sql/plpgsql.sql.orig	2011-12-07 05:59:37.166674218 +0100
--- ./src/test/regress/sql/plpgsql.sql	2011-12-07 06:00:07.371331565 +0100
***************
*** 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
+ check trigger tg_hslot_biu on HSlot;
  
  -- ************************************************************
  -- * BEFORE DELETE on HSlot
***************
*** 747,752 ****
--- 749,757 ----
  end;
  ' language plpgsql;
  
+ -- check function should not fail
+ check function tg_backlink_set(bpchar, bpchar);
+ 
  
  -- ************************************************************
  -- * Support function to clear out the backlink field if
***************
*** 2335,2340 ****
--- 2340,2352 ----
  
  select for_vect();
  
+ -- check function should not fail
+ 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
***************
*** 2714,2719 ****
--- 2726,2734 ----
  end;
  $$ language plpgsql;
  
+ -- check function should not fail
+ check function forc01();
+ 
  select forc01();
  
  -- try updating the cursor's current row
***************
*** 3048,3053 ****
--- 3063,3071 ----
  end;
  $$ language plpgsql immutable;
  
+ -- check function should not fail
+ check function case_test(bigint);
+ 
  select case_test(1);
  select case_test(2);
  select case_test(3);
***************
*** 3600,3602 ****
--- 3618,3862 ----
  
  drop function arrayassign1();
  drop function testoa(x1 int, x2 int, x3 int);
+ 
+ --
+ -- check function statement tests
+ --
+ 
+ create table t1(a int, b int);
+ 
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+ end;
+ $$ language plpgsql;
+ 
+ select f1();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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;
+ 
+ select f1();
+ 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();
+ 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);
+ check trigger t1_f1 on 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
+ check trigger t1_f1 on 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
+ check trigger t1_f1 on t1;
+ 
+ -- ok
+ insert into t1 values(6,30);
+ 
+ drop table t1;
+ drop type _exception_type;
+ 
+ drop function f1_trg();
+ 
#34Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#33)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Hello

a small addition

* don't check SQL functions - are checked well now
* don't check functions from information_schema too

Regards

Pavel

2011/12/8 Pavel Stehule <pavel.stehule@gmail.com>:

Show quoted text

Hello

updated version

changes:

* CHECK FUNCTION ALL; is enabled - in this case functions from
pg_catalog schema are ignored

I looked on parser, and I didn't other changes there - IN SCHEMA, FOR
ROLE are used more time there, so our usage will be consistent

Regards

Pavel

2011/12/7 Albe Laurenz <laurenz.albe@wien.gv.at>:

Pavel Stehule wrote:

The syntax error messages are still inadequate; all I can get is
'syntax error at or near "%s"'.  They should be more detailed.

this system is based on error messages that generates a plpgsql engine
or bison engine. I can correct only a few percent from these messages
:(

internally I didn't wrote a compiler or plpgsql checker - this is just
tool that can emit some plpgsql interpret subprocess - and when these
subprocesses raises exceptions, then takes their messages.

I see.

I think that at least the documentation should be improved before
I am ready to set this as "ready for committer".

please, can you send a correction to documentation or error messages?

I am not able to write documentation

I'll give it a try.

Yours,
Laurenz Albe

Attachments:

check_function-2011-12-08-2.difftext/x-patch; charset=US-ASCII; name=check_function-2011-12-08-2.diffDownload
*** ./doc/src/sgml/catalogs.sgml.orig	2011-12-07 05:59:37.123674706 +0100
--- ./doc/src/sgml/catalogs.sgml	2011-12-07 06:00:07.253332903 +0100
***************
*** 3652,3657 ****
--- 3652,3668 ----
       </row>
  
       <row>
+       <entry><structfield>lanchecker</structfield></entry>
+       <entry><type>oid</type></entry>
+       <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+       <entry>
+        This references a language checker function that is responsible
+        for checking a embedded SQL and can provide detailed checking.
+        Zero if no checker is provided.
+       </entry>
+      </row>
+ 
+      <row>
        <entry><structfield>lanacl</structfield></entry>
        <entry><type>aclitem[]</type></entry>
        <entry></entry>
*** ./doc/src/sgml/ref/allfiles.sgml.orig	2011-12-07 05:59:37.125674684 +0100
--- ./doc/src/sgml/ref/allfiles.sgml	2011-12-07 06:00:07.254332891 +0100
***************
*** 40,45 ****
--- 40,46 ----
  <!ENTITY alterView          SYSTEM "alter_view.sgml">
  <!ENTITY analyze            SYSTEM "analyze.sgml">
  <!ENTITY begin              SYSTEM "begin.sgml">
+ <!ENTITY checkFunction      SYSTEM "check_function.sgml">
  <!ENTITY checkpoint         SYSTEM "checkpoint.sgml">
  <!ENTITY close              SYSTEM "close.sgml">
  <!ENTITY cluster            SYSTEM "cluster.sgml">
*** ./doc/src/sgml/ref/create_language.sgml.orig	2011-12-07 05:59:37.127674661 +0100
--- ./doc/src/sgml/ref/create_language.sgml	2011-12-07 06:00:07.256332868 +0100
***************
*** 23,29 ****
  <synopsis>
  CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
  CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ]
  </synopsis>
   </refsynopsisdiv>
  
--- 23,29 ----
  <synopsis>
  CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
  CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ] [ CHECK <replaceable>checkfunction</replaceable> ]
  </synopsis>
   </refsynopsisdiv>
  
***************
*** 217,222 ****
--- 217,236 ----
        </para>
       </listitem>
      </varlistentry>
+ 
+     <varlistentry>
+      <term><literal>CHECK</literal> <replaceable class="parameter">checkfunction</replaceable></term>
+ 
+      <listitem>
+       <para><replaceable class="parameter">checkfunction</replaceable> is the
+        name of a previously registered function that will be called
+        when a new function in the language is created, to check the
+        function by statemnt <command>CHECK FUNCTION</command> or 
+        <command>CHECK TRIGGER</command>.
+       </para>
+      </listitem>
+     </varlistentry>
+ 
     </variablelist>
  
    <para>
*** ./doc/src/sgml/reference.sgml.orig	2011-12-07 05:59:37.128674649 +0100
--- ./doc/src/sgml/reference.sgml	2011-12-07 06:00:07.257332857 +0100
***************
*** 68,73 ****
--- 68,74 ----
     &alterView;
     &analyze;
     &begin;
+    &checkFunction;
     &checkpoint;
     &close;
     &cluster;
*** ./doc/src/sgml/ref/check_function.sgml.orig	2011-12-07 05:59:58.965426923 +0100
--- ./doc/src/sgml/ref/check_function.sgml	2011-12-06 17:54:28.000000000 +0100
***************
*** 0 ****
--- 1,38 ----
+ <!--
+ doc/src/sgml/ref/check_function.sgml
+ -->
+ 
+ <refentry id="SQL-CHECKFUNCTION">
+  <refmeta>
+   <refentrytitle>CHECK FUNCTION</refentrytitle>
+   <manvolnum>7</manvolnum>
+   <refmiscinfo>SQL - Language Statements</refmiscinfo>
+  </refmeta>
+ 
+  <refnamediv>
+   <refname>CHECK FUNCTION</refname>
+   <refpurpose>ensure a deep checking of existing function</refpurpose>
+  </refnamediv>
+ 
+  <indexterm zone="sql-checkfunction">
+   <primary>CHECK FUNCTION</primary>
+  </indexterm>
+ 
+  <refsynopsisdiv>
+ <synopsis>
+ CHECK FUNCTION <replaceable class="parameter">name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] )
+  | CHECK FUNCTION ALL [ IN LANGUAGE <replaceable class="parameter">langname</replaceable> ] [ IN SCHEMA <replaceable class="parameter">schemaname</replaceable> ] [ FOR ROLE <replaceable class="parameter">ownername</replaceable> ]
+  | CHECK TRIGGER <replaceable class="parameter">name</replaceable> ON <replaceable class="parameter">tablename</replaceable>
+ </synopsis>
+  </refsynopsisdiv>
+ 
+  <refsect1 id="sql-checkfunction-description">
+   <title>Description</title>
+ 
+   <para>
+    <command>CHECK FUNCTION</command> check a existing function.
+    <command>CHECK TRIGGER</command> check a trigger function.
+   </para>
+  </refsect1>
+ 
+ </refentry>
*** ./src/backend/catalog/pg_proc.c.orig	2011-12-07 05:59:37.131674614 +0100
--- ./src/backend/catalog/pg_proc.c	2011-12-07 06:00:07.260332824 +0100
***************
*** 1101,1103 ****
--- 1101,1104 ----
  	*newcursorpos = newcp;
  	return false;
  }
+ 
*** ./src/backend/commands/functioncmds.c.orig	2011-12-07 05:59:37.132674603 +0100
--- ./src/backend/commands/functioncmds.c	2011-12-08 16:31:50.775499183 +0100
***************
*** 35,53 ****
--- 35,58 ----
  #include "access/genam.h"
  #include "access/heapam.h"
  #include "access/sysattr.h"
+ #include "access/xact.h"
  #include "catalog/dependency.h"
  #include "catalog/indexing.h"
  #include "catalog/objectaccess.h"
  #include "catalog/pg_aggregate.h"
  #include "catalog/pg_cast.h"
  #include "catalog/pg_language.h"
+ #include "catalog/namespace.h"
  #include "catalog/pg_namespace.h"
  #include "catalog/pg_proc.h"
  #include "catalog/pg_proc_fn.h"
+ #include "catalog/pg_trigger.h"
  #include "catalog/pg_type.h"
  #include "catalog/pg_type_fn.h"
  #include "commands/defrem.h"
  #include "commands/proclang.h"
+ #include "commands/trigger.h"
+ #include "executor/spi_priv.h"
  #include "miscadmin.h"
  #include "optimizer/var.h"
  #include "parser/parse_coerce.h"
***************
*** 60,66 ****
--- 65,73 ----
  #include "utils/fmgroids.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
+ #include "utils/memutils.h"
  #include "utils/rel.h"
+ #include "utils/resowner.h"
  #include "utils/syscache.h"
  #include "utils/tqual.h"
  
***************
*** 1009,1014 ****
--- 1016,1393 ----
  	}
  }
  
+ /*
+  * Search and execute related checker function
+  */
+ static void
+ CheckFunctionById(Oid funcOid, Oid relid)
+ {
+ 	HeapTuple	tup;
+ 	Form_pg_proc proc;
+ 	HeapTuple	languageTuple;
+ 	Form_pg_language languageStruct;
+ 	Oid		languageChecker;
+ 	bool	found_bug;
+ 	char *funcname;
+ 
+ 	tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
+ 	if (!HeapTupleIsValid(tup)) /* should not happen */
+ 		elog(ERROR, "cache lookup failed for function %u", funcOid);
+ 
+ 	proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 	languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
+ 	Assert(HeapTupleIsValid(languageTuple));
+ 
+ 	languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
+ 	languageChecker = languageStruct->lanchecker;
+ 
+ 	funcname = format_procedure(funcOid);
+ 
+ 	/* Check a function body */
+ 	if (OidIsValid(languageChecker))
+ 	{
+ 		ArrayType  *set_items = NULL;
+ 		int			save_nestlevel = 0;
+ 		Datum	datum;
+ 		bool		isnull;
+ 		MemoryContext oldCxt;
+ 		MemoryContext checkCxt;
+ 		ResourceOwner		oldowner;
+ 
+ 		datum = SysCacheGetAttr(PROCOID, tup, Anum_pg_proc_proconfig, &isnull);
+ 
+ 		if (!isnull)
+ 		{
+ 			/* Set per-function configuration parameters */
+ 			set_items = (ArrayType *) DatumGetPointer(datum);
+ 			if (set_items)			/* Need a new GUC nesting level */
+ 			{
+ 				save_nestlevel = NewGUCNestLevel();
+ 				ProcessGUCArray(set_items,
+ 							(superuser() ? PGC_SUSET : PGC_USERSET),
+ 							PGC_S_SESSION,
+ 							GUC_ACTION_SAVE);
+ 			}
+ 			else
+ 				save_nestlevel = 0; /* keep compiler quiet */
+ 		}
+ 
+ 		checkCxt = AllocSetContextCreate(CurrentMemoryContext,
+ 									"Check temporary context",
+ 									ALLOCSET_DEFAULT_MINSIZE,
+ 									ALLOCSET_DEFAULT_INITSIZE,
+ 									ALLOCSET_DEFAULT_MAXSIZE);
+ 
+ 		oldCxt = MemoryContextSwitchTo(checkCxt);
+ 
+ 		oldowner = CurrentResourceOwner;
+ 		BeginInternalSubTransaction(NULL);
+ 		MemoryContextSwitchTo(checkCxt);
+ 
+ 		/*
+ 		 * forward exception to warning
+ 		 */
+ 		PG_TRY();
+ 		{
+ 			OidFunctionCall2(languageChecker, ObjectIdGetDatum(funcOid), 
+ 								    ObjectIdGetDatum(relid));
+ 
+ 			RollbackAndReleaseCurrentSubTransaction();
+ 			MemoryContextSwitchTo(checkCxt);
+ 			CurrentResourceOwner = oldowner;
+ 
+ 			found_bug = false;
+ 		}
+ 		PG_CATCH();
+ 		{
+ 			ErrorData *edata;
+ 
+ 			MemoryContextSwitchTo(checkCxt);
+ 			edata = CopyErrorData();
+ 			FlushErrorState();
+ 
+ 			RollbackAndReleaseCurrentSubTransaction();
+ 			MemoryContextSwitchTo(checkCxt);
+ 			CurrentResourceOwner = oldowner;
+ 
+ 			edata->elevel = WARNING;
+ 
+ 			ereport(WARNING,
+ 					(errmsg("error in function \"%s\"", funcname),
+ 					 edata->message != NULL ? errdetail_internal("%s", edata->message) : 0,
+ 					 edata->context != NULL ? errcontext("%s",edata->context) : 0,
+ 					 edata->internalquery != NULL ? internalerrquery(edata->internalquery) : 0,
+ 					 internalerrposition(edata->internalpos)));
+ 
+ 			FreeErrorData(edata);
+ 
+ 			found_bug = true;
+ 		}
+ 		PG_END_TRY();
+ 
+ 		MemoryContextSwitchTo(oldCxt);
+ 
+ 		if (set_items)
+ 			AtEOXact_GUC(true, save_nestlevel);
+ 
+ 		if (!found_bug)
+ 			elog(NOTICE, "checked function \"%s\"",
+ 							funcname);
+ 	}
+ 	else
+ 		elog(NOTICE, "skip check function \"%s\", language \"%s\" hasn't checker function",
+ 					funcname,
+ 							    NameStr(languageStruct->lanname));
+ 
+ 	pfree(funcname);
+ 
+ 	ReleaseSysCache(languageTuple);
+ 	ReleaseSysCache(tup);
+ }
+ 
+ /*
+  * CheckFunction
+  *			call a PL checker function when this function exists.
+  */
+ void
+ CheckFunction(CheckFunctionStmt *stmt)
+ {
+ 	List	   *functionName = stmt->funcname;
+ 	List	   *argTypes = stmt->args;	/* list of TypeName nodes */
+ 	Oid			funcOid = InvalidOid;
+ 	HeapTuple	tup;
+ 	Oid trgOid = InvalidOid;
+ 	Oid	relid = InvalidOid;
+ 	Oid languageId;
+ 	int nkeys = 0;
+ 	List	*objects = NIL;
+ 
+ 
+ 	if (stmt->funcname != NULL)
+ 	{
+ 		/*
+ 		 * Find the function, 
+ 		 */
+ 		funcOid = LookupFuncNameTypeNames(functionName, argTypes, false);
+ 	}
+ 	else if (stmt->trgname != NULL)
+ 	{
+ 		HeapTuple	ht_trig;
+ 		Form_pg_trigger trigrec;
+ 		ScanKeyData skey[1];
+ 		Relation	tgrel;
+ 		SysScanDesc tgscan;
+ 
+ 		/* find a trigger function */
+ 		relid = RangeVarGetRelid(stmt->relation, ShareLock, false);
+ 		trgOid = get_trigger_oid(relid, stmt->trgname, false);
+ 
+ 		/*
+ 		 * Fetch the pg_trigger tuple by the Oid of the trigger
+ 		 */
+ 		tgrel = heap_open(TriggerRelationId, AccessShareLock);
+ 
+ 		ScanKeyInit(&skey[0],
+ 					ObjectIdAttributeNumber,
+ 					BTEqualStrategyNumber, F_OIDEQ,
+ 					ObjectIdGetDatum(trgOid));
+ 
+ 		tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true,
+ 									SnapshotNow, 1, skey);
+ 
+ 		ht_trig = systable_getnext(tgscan);
+ 
+ 		if (!HeapTupleIsValid(ht_trig))
+ 			elog(ERROR, "could not find tuple for trigger %u", trgOid);
+ 
+ 		trigrec = (Form_pg_trigger) GETSTRUCT(ht_trig);
+ 
+ 		/* we need to know trigger function to get PL checker function */
+ 		funcOid = trigrec->tgfoid;
+ 
+ 		/* Clean up */
+ 		systable_endscan(tgscan);
+ 
+ 		heap_close(tgrel, AccessShareLock);
+ 	}
+ 	else
+ 	{
+ 		ScanKeyData	   key[5];
+ 		Relation	   rel;
+ 		HeapScanDesc 	   scan;
+ 		ListCell *option;
+ 		bool	language_opt =  false;
+ 		bool	schema_opt = false;
+ 		bool	owner_opt = false;
+ 
+ 		/*
+ 		 * when Check stmt is multiple statement, then
+ 		 * prepare list of processed functions.
+ 		 */
+ 		foreach(option, stmt->options)
+ 		{
+ 			DefElem    *defel = (DefElem *) lfirst(option);
+ 
+ 			if (strcmp(defel->defname, "language") == 0)
+ 			{
+ 				HeapTuple	languageTuple;
+ 				Form_pg_language languageStruct;
+ 				Oid		languageChecker;
+ 				char *language = defGetString(defel);
+ 
+ 				if (language_opt)
+ 					elog(ERROR, "multiple usage of IN LANGUAGE option");
+ 				language_opt = true;
+ 
+ 				languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language));
+ 				if (!HeapTupleIsValid(languageTuple))
+ 					ereport(ERROR,
+ 						(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 						 errmsg("language \"%s\" does not exist", language),
+ 						 (PLTemplateExists(language) ?
+ 						  errhint("Use CREATE LANGUAGE to load the language into the database.") : 0)));
+ 
+ 				languageId = HeapTupleGetOid(languageTuple);
+ 				if (languageId == INTERNALlanguageId || languageId == ClanguageId)
+ 					elog(ERROR, "cannot to check functions in C or internal languages");
+ 
+ 				languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
+ 				languageChecker = languageStruct->lanchecker;
+ 				if (!OidIsValid(languageChecker))
+ 					elog(ERROR, "language \"%s\" has no defined checker function",
+ 							    language);
+ 
+ 				ScanKeyInit(&key[nkeys++],
+ 							Anum_pg_proc_prolang,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(languageId));
+ 
+ 				ReleaseSysCache(languageTuple);
+ 			}
+ 			else if (strcmp(defel->defname, "schema") == 0)
+ 			{
+ 				Oid namespaceId = LookupExplicitNamespace(defGetString(defel));
+ 
+ 				if (schema_opt)
+ 					elog(ERROR, "multiple usage of IN SCHEMA option");
+ 				schema_opt = true;
+ 
+ 				ScanKeyInit(&key[nkeys++],
+ 							Anum_pg_proc_pronamespace,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(namespaceId));
+ 
+ 			}
+ 			else if (strcmp(defel->defname, "owner") == 0)
+ 			{
+ 				Oid ownerId = get_role_oid(defGetString(defel), false);
+ 
+ 				if (owner_opt)
+ 					elog(ERROR, "multiple usage of FOR ROLE option");
+ 				owner_opt = true;
+ 
+ 				ScanKeyInit(&key[nkeys++],
+ 							Anum_pg_proc_proowner,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(ownerId));
+ 			}
+ 			else
+ 				elog(ERROR, "option \"%s\" not recognized",
+ 					 defel->defname);
+ 		}
+ 
+ 		/*
+ 		 * We don't would to iterate over pg_catalog functions without
+ 		 * explicit request and information_schema.
+ 		 */
+ 		if (!schema_opt)
+ 		{
+ 			Oid information_schemaId = LookupExplicitNamespace("information_schema");
+ 		
+ 			ScanKeyInit(&key[nkeys++],
+ 						Anum_pg_proc_pronamespace,
+ 						BTEqualStrategyNumber, F_OIDNE,
+ 						ObjectIdGetDatum(PG_CATALOG_NAMESPACE));
+ 
+ 			ScanKeyInit(&key[nkeys++],
+ 						Anum_pg_proc_pronamespace,
+ 						BTEqualStrategyNumber, F_OIDNE,
+ 						ObjectIdGetDatum(information_schemaId));
+ 		}
+ 
+ 		rel = heap_open(ProcedureRelationId,AccessShareLock);
+ 		scan = heap_beginscan(rel, SnapshotNow, nkeys, key);
+ 
+ 		while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+ 		{
+ 			Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 			funcOid = HeapTupleGetOid(tup);
+ 
+ 			/* when language is not specified, then check checker */
+ 			if (!language_opt)
+ 			{
+ 				Oid prolang = proc->prolang;
+ 
+ 				/* skip C, internal or SQL */
+ 				if (prolang == INTERNALlanguageId)
+ 				{
+ 					elog(NOTICE, "skip check function \"%s\", it uses internal language",
+ 								format_procedure(funcOid));
+ 					continue;
+ 				}
+ 				else if (prolang == ClanguageId)
+ 				{
+ 					elog(NOTICE, "skip check function \"%s\", uses C language",
+ 								format_procedure(funcOid));
+ 					continue;
+ 				}
+ 				else if (prolang == SQLlanguageId)
+ 				{
+ 					elog(NOTICE, "skip check function \"%s\", uses SQL language",
+ 								format_procedure(funcOid));
+ 					continue;
+ 				}
+ 			}
+ 
+ 			if (proc->prorettype == TRIGGEROID)
+ 			{
+ 				elog(NOTICE, "skip check function \"%s\", it is trigger function",
+ 							format_procedure(funcOid));
+ 				continue;
+ 			}
+ 
+ 			objects = lappend_oid(objects, funcOid);
+ 		}
+ 
+ 		heap_endscan(scan);
+ 		heap_close(rel, AccessShareLock);
+ 	}
+ 
+ 	if (funcOid == InvalidOid && objects == NIL)
+ 	{
+ 		elog(NOTICE, "nothing to check");
+ 		return;
+ 	}
+ 
+ 	if (objects != NIL)
+ 	{
+ 		ListCell *object;
+ 
+ 		foreach(object, objects)
+ 		{
+ 			CHECK_FOR_INTERRUPTS();
+ 
+ 			funcOid = lfirst_oid(object);
+ 			CheckFunctionById(funcOid, InvalidOid);
+ 		}
+ 	}
+ 	else
+ 	{
+ 		CheckFunctionById(funcOid, relid);
+ 	}
+ }
  
  /*
   * Rename function
*** ./src/backend/commands/proclang.c.orig	2011-12-07 05:59:37.134674581 +0100
--- ./src/backend/commands/proclang.c	2011-12-07 06:00:07.264332779 +0100
***************
*** 46,57 ****
  	char	   *tmplhandler;	/* name of handler function */
  	char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
  	char	   *tmplvalidator;	/* name of validator function, or NULL */
  	char	   *tmpllibrary;	/* path of shared library */
  } PLTemplate;
  
  static void create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, bool trusted);
  static PLTemplate *find_language_template(const char *languageName);
  static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
  							Oid newOwnerId);
--- 46,58 ----
  	char	   *tmplhandler;	/* name of handler function */
  	char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
  	char	   *tmplvalidator;	/* name of validator function, or NULL */
+ 	char	   *tmplchecker;	/* name of checker function, or NULL */
  	char	   *tmpllibrary;	/* path of shared library */
  } PLTemplate;
  
  static void create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, Oid checkerOid, bool trusted);
  static PLTemplate *find_language_template(const char *languageName);
  static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
  							Oid newOwnerId);
***************
*** 67,75 ****
  	PLTemplate *pltemplate;
  	Oid			handlerOid,
  				inlineOid,
! 				valOid;
  	Oid			funcrettype;
! 	Oid			funcargtypes[1];
  
  	/*
  	 * If we have template information for the language, ignore the supplied
--- 68,77 ----
  	PLTemplate *pltemplate;
  	Oid			handlerOid,
  				inlineOid,
! 				valOid,
! 				checkerOid;
  	Oid			funcrettype;
! 	Oid			funcargtypes[2];
  
  	/*
  	 * If we have template information for the language, ignore the supplied
***************
*** 219,228 ****
  		else
  			valOid = InvalidOid;
  
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, pltemplate->tmpltrusted);
  	}
  	else
  	{
--- 221,269 ----
  		else
  			valOid = InvalidOid;
  
+ 		/*
+ 		 * Likewise for the checker, if required; but we don't care about
+ 		 * its return type.
+ 		 */
+ 		if (pltemplate->tmplchecker)
+ 		{
+ 			funcname = SystemFuncName(pltemplate->tmplchecker);
+ 			funcargtypes[0] = OIDOID;
+ 			funcargtypes[1] = REGCLASSOID;
+ 			checkerOid = LookupFuncName(funcname, 2, funcargtypes, true);
+ 			if (!OidIsValid(checkerOid))
+ 			{
+ 				checkerOid = ProcedureCreate(pltemplate->tmplchecker,
+ 										 PG_CATALOG_NAMESPACE,
+ 										 false, /* replace */
+ 										 false, /* returnsSet */
+ 										 VOIDOID,
+ 										 ClanguageId,
+ 										 F_FMGR_C_VALIDATOR,
+ 										 pltemplate->tmplchecker,
+ 										 pltemplate->tmpllibrary,
+ 										 false, /* isAgg */
+ 										 false, /* isWindowFunc */
+ 										 false, /* security_definer */
+ 										 true,	/* isStrict */
+ 										 PROVOLATILE_VOLATILE,
+ 										 buildoidvector(funcargtypes, 2),
+ 										 PointerGetDatum(NULL),
+ 										 PointerGetDatum(NULL),
+ 										 PointerGetDatum(NULL),
+ 										 NIL,
+ 										 PointerGetDatum(NULL),
+ 										 1,
+ 										 0);
+ 			}
+ 		}
+ 		else
+ 			checkerOid = InvalidOid;
+ 
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, checkerOid, pltemplate->tmpltrusted);
  	}
  	else
  	{
***************
*** 294,303 ****
  		else
  			valOid = InvalidOid;
  
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, stmt->pltrusted);
  	}
  }
  
--- 335,355 ----
  		else
  			valOid = InvalidOid;
  
+ 		/* validate the checker function */
+ 		if (stmt->plchecker)
+ 		{
+ 			funcargtypes[0] = OIDOID;
+ 			funcargtypes[1] = REGCLASSOID;
+ 			checkerOid = LookupFuncName(stmt->plchecker, 2, funcargtypes, false);
+ 			/* return value is ignored, so we don't check the type */
+ 		}
+ 		else
+ 			checkerOid = InvalidOid;
+ 
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, checkerOid, stmt->pltrusted);
  	}
  }
  
***************
*** 307,313 ****
  static void
  create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, bool trusted)
  {
  	Relation	rel;
  	TupleDesc	tupDesc;
--- 359,365 ----
  static void
  create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, Oid checkerOid, bool trusted)
  {
  	Relation	rel;
  	TupleDesc	tupDesc;
***************
*** 337,342 ****
--- 389,395 ----
  	values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid);
  	values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid);
  	values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid);
+ 	values[Anum_pg_language_lanchecker - 1] = ObjectIdGetDatum(checkerOid);
  	nulls[Anum_pg_language_lanacl - 1] = true;
  
  	/* Check for pre-existing definition */
***************
*** 423,428 ****
--- 476,490 ----
  		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  	}
  
+ 	/* dependency on the checker function, if any */
+ 	if (OidIsValid(checkerOid))
+ 	{
+ 		referenced.classId = ProcedureRelationId;
+ 		referenced.objectId = checkerOid;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	/* Post creation hook for new procedural language */
  	InvokeObjectAccessHook(OAT_POST_CREATE,
  						   LanguageRelationId, myself.objectId, 0);
***************
*** 478,483 ****
--- 540,550 ----
  		if (!isnull)
  			result->tmplvalidator = TextDatumGetCString(datum);
  
+ 		datum = heap_getattr(tup, Anum_pg_pltemplate_tmplchecker,
+ 							 RelationGetDescr(rel), &isnull);
+ 		if (!isnull)
+ 			result->tmplchecker = TextDatumGetCString(datum);
+ 
  		datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary,
  							 RelationGetDescr(rel), &isnull);
  		if (!isnull)
*** ./src/backend/nodes/copyfuncs.c.orig	2011-12-07 05:59:37.135674570 +0100
--- ./src/backend/nodes/copyfuncs.c	2011-12-07 06:38:24.420227380 +0100
***************
*** 2880,2885 ****
--- 2880,2900 ----
  	return newnode;
  }
  
+ static CheckFunctionStmt *
+ _copyCheckFunctionStmt(CheckFunctionStmt *from)
+ {
+ 	CheckFunctionStmt *newnode = makeNode(CheckFunctionStmt);
+ 
+ 	COPY_NODE_FIELD(funcname);
+ 	COPY_NODE_FIELD(args);
+ 	COPY_STRING_FIELD(trgname);
+ 	COPY_NODE_FIELD(relation);
+ 	COPY_SCALAR_FIELD(is_function);
+ 	COPY_NODE_FIELD(options);
+ 
+ 	return newnode;
+ }
+ 
  static DoStmt *
  _copyDoStmt(DoStmt *from)
  {
***************
*** 4165,4170 ****
--- 4180,4188 ----
  		case T_AlterFunctionStmt:
  			retval = _copyAlterFunctionStmt(from);
  			break;
+ 		case T_CheckFunctionStmt:
+ 			retval = _copyCheckFunctionStmt(from);
+ 			break;
  		case T_DoStmt:
  			retval = _copyDoStmt(from);
  			break;
*** ./src/backend/nodes/equalfuncs.c.orig	2011-12-07 05:59:37.137674548 +0100
--- ./src/backend/nodes/equalfuncs.c	2011-12-07 06:38:56.187881989 +0100
***************
*** 1292,1297 ****
--- 1292,1310 ----
  }
  
  static bool
+ _equalCheckFunctionStmt(CheckFunctionStmt *a, CheckFunctionStmt *b)
+ {
+ 	COMPARE_NODE_FIELD(funcname);
+ 	COMPARE_NODE_FIELD(args);
+ 	COMPARE_STRING_FIELD(trgname);
+ 	COMPARE_NODE_FIELD(relation);
+ 	COMPARE_SCALAR_FIELD(is_function);
+ 	COMPARE_NODE_FIELD(options);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalDoStmt(DoStmt *a, DoStmt *b)
  {
  	COMPARE_NODE_FIELD(args);
***************
*** 2708,2713 ****
--- 2721,2729 ----
  		case T_AlterFunctionStmt:
  			retval = _equalAlterFunctionStmt(a, b);
  			break;
+ 		case T_CheckFunctionStmt:
+ 			retval = _equalCheckFunctionStmt(a, b);
+ 			break;
  		case T_DoStmt:
  			retval = _equalDoStmt(a, b);
  			break;
*** ./src/backend/parser/gram.y.orig	2011-12-07 05:59:37.138674536 +0100
--- ./src/backend/parser/gram.y	2011-12-08 14:48:01.858729545 +0100
***************
*** 227,232 ****
--- 227,233 ----
  		DeallocateStmt PrepareStmt ExecuteStmt
  		DropOwnedStmt ReassignOwnedStmt
  		AlterTSConfigurationStmt AlterTSDictionaryStmt
+ 		CheckFunctionStmt
  
  %type <node>	select_no_parens select_with_parens select_clause
  				simple_select values_clause
***************
*** 276,282 ****
  
  %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
  				opt_class opt_inline_handler opt_validator validator_clause
! 				opt_collate
  
  %type <range>	qualified_name OptConstrFromTable
  
--- 277,283 ----
  
  %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
  				opt_class opt_inline_handler opt_validator validator_clause
! 				opt_collate opt_checker
  
  %type <range>	qualified_name OptConstrFromTable
  
***************
*** 463,469 ****
  %type <windef>	window_definition over_clause window_specification
  				opt_frame_clause frame_extent frame_bound
  %type <str>		opt_existing_window_name
! 
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
--- 464,471 ----
  %type <windef>	window_definition over_clause window_specification
  				opt_frame_clause frame_extent frame_bound
  %type <str>		opt_existing_window_name
! %type <defelt>	CheckFunctionOptElem
! %type <list>	CheckFunctionOpts
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
***************
*** 700,705 ****
--- 702,708 ----
  			| AlterUserSetStmt
  			| AlterUserStmt
  			| AnalyzeStmt
+ 			| CheckFunctionStmt
  			| CheckPointStmt
  			| ClosePortalStmt
  			| ClusterStmt
***************
*** 3174,3184 ****
  				n->plhandler = NIL;
  				n->plinline = NIL;
  				n->plvalidator = NIL;
  				n->pltrusted = false;
  				$$ = (Node *)n;
  			}
  			| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
! 			  HANDLER handler_name opt_inline_handler opt_validator
  			{
  				CreatePLangStmt *n = makeNode(CreatePLangStmt);
  				n->replace = $2;
--- 3177,3188 ----
  				n->plhandler = NIL;
  				n->plinline = NIL;
  				n->plvalidator = NIL;
+ 				n->plchecker = NIL;
  				n->pltrusted = false;
  				$$ = (Node *)n;
  			}
  			| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
! 			  HANDLER handler_name opt_inline_handler opt_validator opt_checker
  			{
  				CreatePLangStmt *n = makeNode(CreatePLangStmt);
  				n->replace = $2;
***************
*** 3186,3191 ****
--- 3190,3196 ----
  				n->plhandler = $8;
  				n->plinline = $9;
  				n->plvalidator = $10;
+ 				n->plchecker = $11;
  				n->pltrusted = $3;
  				$$ = (Node *)n;
  			}
***************
*** 3220,3225 ****
--- 3225,3235 ----
  			| /*EMPTY*/								{ $$ = NIL; }
  		;
  
+ opt_checker:
+ 			CHECK handler_name					{ $$ = $2; }
+ 			| /*EMPTY*/								{ $$ = NIL; }
+ 		;
+ 
  DropPLangStmt:
  			DROP opt_procedural LANGUAGE ColId_or_Sconst opt_drop_behavior
  				{
***************
*** 6250,6255 ****
--- 6260,6344 ----
  
  /*****************************************************************************
   *
+  *		CHECK FUNCTION funcname(args)
+  *		CHECK TRIGGER triggername ON table
+  *
+  *
+  *****************************************************************************/
+ 
+ 
+ CheckFunctionStmt:
+ 			CHECK FUNCTION func_name func_args
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = $3;
+ 					n->args = extractArgTypes($4);
+ 					n->trgname = NULL;
+ 					n->relation = NULL;
+ 					n->is_function = true;
+ 					n->options = NIL;
+ 					$$ = (Node *) n;
+ 				}
+ 			| CHECK TRIGGER name ON qualified_name
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = NULL;
+ 					n->args = NIL;
+ 					n->trgname = $3;
+ 					n->relation = $5;
+ 					n->is_function = false;
+ 					n->options = NIL;
+ 					$$ = (Node *) n;
+ 				}
+ 			| CHECK FUNCTION ALL
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = NULL;
+ 					n->args = NIL;
+ 					n->trgname = NULL;
+ 					n->relation = NULL;
+ 					n->is_function = true;
+ 					n->options = NIL;
+ 					$$ = (Node *) n;
+ 				}
+ 			| CHECK FUNCTION ALL CheckFunctionOpts
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = NULL;
+ 					n->args = NIL;
+ 					n->trgname = NULL;
+ 					n->relation = NULL;
+ 					n->is_function = true;
+ 					n->options = $4;
+ 					$$ = (Node *) n;
+ 				}
+ 		;
+ 
+ CheckFunctionOpts:
+ 			CheckFunctionOptElem					{ $$ = list_make1($1); }
+ 			| CheckFunctionOpts CheckFunctionOptElem	{ $$ = lappend($1, $2); }
+ 		;
+ 
+ CheckFunctionOptElem:
+ 			IN_P LANGUAGE ColId
+ 				{
+ 					$$ = makeDefElem("language",
+ 								    (Node *) makeString($2));
+ 				}
+ 			| IN_P SCHEMA ColId
+ 				{
+ 					$$ = makeDefElem("schema",
+ 								    (Node *) makeString($2));
+ 				}
+ 			| FOR ROLE ColId
+ 				{
+ 					$$ = makeDefElem("owner",
+ 								    (Node *) makeString($3));
+ 				}
+ 		;
+ 
+ /*****************************************************************************
+  *
   *		DO <anonymous code block> [ LANGUAGE language ]
   *
   * We use a DefElem list for future extensibility, and to allow flexibility
*** ./src/backend/tcop/utility.c.orig	2011-12-07 05:59:37.140674512 +0100
--- ./src/backend/tcop/utility.c	2011-12-07 06:35:57.836744377 +0100
***************
*** 882,887 ****
--- 882,891 ----
  			AlterFunction((AlterFunctionStmt *) parsetree);
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			CheckFunction((CheckFunctionStmt *) parsetree);
+ 			break;
+ 
  		case T_IndexStmt:		/* CREATE INDEX */
  			{
  				IndexStmt  *stmt = (IndexStmt *) parsetree;
***************
*** 2125,2130 ****
--- 2129,2141 ----
  			}
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			if (((CheckFunctionStmt *) parsetree)->is_function)
+ 				tag = "CHECK FUNCTION";
+ 			else
+ 				tag = "CHECK TRIGGER";
+ 			break;
+ 
  		default:
  			elog(WARNING, "unrecognized node type: %d",
  				 (int) nodeTag(parsetree));
***************
*** 2565,2570 ****
--- 2576,2585 ----
  			}
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			lev = LOGSTMT_ALL;
+ 			break;
+ 
  		default:
  			elog(WARNING, "unrecognized node type: %d",
  				 (int) nodeTag(parsetree));
*** ./src/bin/pg_dump/pg_dump.c.orig	2011-12-07 05:59:37.142674489 +0100
--- ./src/bin/pg_dump/pg_dump.c	2011-12-07 06:00:07.312332234 +0100
***************
*** 5326,5338 ****
  	int			i_lanplcallfoid;
  	int			i_laninline;
  	int			i_lanvalidator;
  	int			i_lanacl;
  	int			i_lanowner;
  
  	/* Make sure we are in proper schema */
  	selectSourceSchema("pg_catalog");
  
! 	if (g_fout->remoteVersion >= 90000)
  	{
  		/* pg_language has a laninline column */
  		appendPQExpBuffer(query, "SELECT tableoid, oid, "
--- 5326,5351 ----
  	int			i_lanplcallfoid;
  	int			i_laninline;
  	int			i_lanvalidator;
+ 	int			i_lanchecker;
  	int			i_lanacl;
  	int			i_lanowner;
  
  	/* Make sure we are in proper schema */
  	selectSourceSchema("pg_catalog");
  
! 	if (g_fout->remoteVersion >= 90200)
! 	{
! 		/* pg_language has a lanchecker column */
! 		appendPQExpBuffer(query, "SELECT tableoid, oid, "
! 						  "lanname, lanpltrusted, lanplcallfoid, "
! 						  "laninline, lanvalidator, lanchecker, lanacl, "
! 						  "(%s lanowner) AS lanowner "
! 						  "FROM pg_language "
! 						  "WHERE lanispl "
! 						  "ORDER BY oid",
! 						  username_subquery);
! 	}
! 	else if (g_fout->remoteVersion >= 90000)
  	{
  		/* pg_language has a laninline column */
  		appendPQExpBuffer(query, "SELECT tableoid, oid, "
***************
*** 5409,5414 ****
--- 5422,5428 ----
  	/* these may fail and return -1: */
  	i_laninline = PQfnumber(res, "laninline");
  	i_lanvalidator = PQfnumber(res, "lanvalidator");
+ 	i_lanchecker = PQfnumber(res, "lanchecker");
  	i_lanacl = PQfnumber(res, "lanacl");
  	i_lanowner = PQfnumber(res, "lanowner");
  
***************
*** 5422,5427 ****
--- 5436,5445 ----
  		planginfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_lanname));
  		planginfo[i].lanpltrusted = *(PQgetvalue(res, i, i_lanpltrusted)) == 't';
  		planginfo[i].lanplcallfoid = atooid(PQgetvalue(res, i, i_lanplcallfoid));
+ 		if (i_lanchecker >= 0)
+ 			planginfo[i].lanchecker = atooid(PQgetvalue(res, i, i_lanchecker));
+ 		else
+ 			planginfo[i].lanchecker = InvalidOid;
  		if (i_laninline >= 0)
  			planginfo[i].laninline = atooid(PQgetvalue(res, i, i_laninline));
  		else
***************
*** 8597,8602 ****
--- 8615,8621 ----
  	char	   *qlanname;
  	char	   *lanschema;
  	FuncInfo   *funcInfo;
+ 	FuncInfo   *checkerInfo = NULL;
  	FuncInfo   *inlineInfo = NULL;
  	FuncInfo   *validatorInfo = NULL;
  
***************
*** 8616,8621 ****
--- 8635,8647 ----
  	if (funcInfo != NULL && !funcInfo->dobj.dump)
  		funcInfo = NULL;		/* treat not-dumped same as not-found */
  
+ 	if (OidIsValid(plang->lanchecker))
+ 	{
+ 		checkerInfo = findFuncByOid(plang->lanchecker);
+ 		if (checkerInfo != NULL && !checkerInfo->dobj.dump)
+ 			checkerInfo = NULL;
+ 	}
+ 
  	if (OidIsValid(plang->laninline))
  	{
  		inlineInfo = findFuncByOid(plang->laninline);
***************
*** 8642,8647 ****
--- 8668,8674 ----
  	 * don't, this might not work terribly nicely.
  	 */
  	useParams = (funcInfo != NULL &&
+ 				 (checkerInfo != NULL || !OidIsValid(plang->lanchecker)) &&
  				 (inlineInfo != NULL || !OidIsValid(plang->laninline)) &&
  				 (validatorInfo != NULL || !OidIsValid(plang->lanvalidator)));
  
***************
*** 8697,8702 ****
--- 8724,8739 ----
  			appendPQExpBuffer(defqry, "%s",
  							  fmtId(validatorInfo->dobj.name));
  		}
+ 		if (OidIsValid(plang->lanchecker))
+ 		{
+ 			appendPQExpBuffer(defqry, " CHECK ");
+ 			/* Cope with possibility that checker is in different schema */
+ 			if (checkerInfo->dobj.namespace != funcInfo->dobj.namespace)
+ 				appendPQExpBuffer(defqry, "%s.",
+ 							   fmtId(checkerInfo->dobj.namespace->dobj.name));
+ 			appendPQExpBuffer(defqry, "%s",
+ 							  fmtId(checkerInfo->dobj.name));
+ 		}
  	}
  	else
  	{
*** ./src/bin/pg_dump/pg_dump.h.orig	2011-12-07 05:59:37.144674467 +0100
--- ./src/bin/pg_dump/pg_dump.h	2011-12-07 06:00:07.348331825 +0100
***************
*** 387,392 ****
--- 387,393 ----
  	Oid			lanplcallfoid;
  	Oid			laninline;
  	Oid			lanvalidator;
+ 	Oid			lanchecker;
  	char	   *lanacl;
  	char	   *lanowner;		/* name of owner, or empty string */
  } ProcLangInfo;
*** ./src/bin/psql/tab-complete.c.orig	2011-12-07 05:59:37.146674445 +0100
--- ./src/bin/psql/tab-complete.c	2011-12-07 06:00:07.350331803 +0100
***************
*** 1,4 ****
--- 1,5 ----
  /*
+  *
   * psql - the PostgreSQL interactive terminal
   *
   * Copyright (c) 2000-2011, PostgreSQL Global Development Group
***************
*** 727,733 ****
  #define prev6_wd  (previous_words[5])
  
  	static const char *const sql_commands[] = {
! 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
  		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
--- 728,734 ----
  #define prev6_wd  (previous_words[5])
  
  	static const char *const sql_commands[] = {
! 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECK", "CHECKPOINT", "CLOSE", "CLUSTER",
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
  		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
***************
*** 1524,1529 ****
--- 1525,1552 ----
  
  		COMPLETE_WITH_LIST(list_TRANS);
  	}
+ 
+ /* CHECK */
+ 	else if (pg_strcasecmp(prev_wd, "CHECK") == 0)
+ 	{
+ 		static const char *const list_CHECK[] =
+ 		{"FUNCTION", "TRIGGER", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_CHECK);
+ 	}
+ 	else if (pg_strcasecmp(prev3_wd, "CHECK") == 0 &&
+ 			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+ 	{
+ 		COMPLETE_WITH_CONST("ON");
+ 	}
+ 	else if (pg_strcasecmp(prev4_wd, "CHECK") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "ON") == 0)
+ 	{
+ 		completion_info_charp = prev2_wd;
+ 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+ 	}
+ 
  /* CLUSTER */
  
  	/*
*** ./src/include/catalog/pg_language.h.orig	2011-12-07 05:59:37.147674434 +0100
--- ./src/include/catalog/pg_language.h	2011-12-07 06:00:07.351331792 +0100
***************
*** 37,42 ****
--- 37,43 ----
  	Oid			lanplcallfoid;	/* Call handler for PL */
  	Oid			laninline;		/* Optional anonymous-block handler function */
  	Oid			lanvalidator;	/* Optional validation function */
+ 	Oid			lanchecker;	/* Optional checker function */
  	aclitem		lanacl[1];		/* Access privileges */
  } FormData_pg_language;
  
***************
*** 51,57 ****
   *		compiler constants for pg_language
   * ----------------
   */
! #define Natts_pg_language				8
  #define Anum_pg_language_lanname		1
  #define Anum_pg_language_lanowner		2
  #define Anum_pg_language_lanispl		3
--- 52,58 ----
   *		compiler constants for pg_language
   * ----------------
   */
! #define Natts_pg_language				9
  #define Anum_pg_language_lanname		1
  #define Anum_pg_language_lanowner		2
  #define Anum_pg_language_lanispl		3
***************
*** 59,78 ****
  #define Anum_pg_language_lanplcallfoid	5
  #define Anum_pg_language_laninline		6
  #define Anum_pg_language_lanvalidator	7
! #define Anum_pg_language_lanacl			8
  
  /* ----------------
   *		initial contents of pg_language
   * ----------------
   */
  
! DATA(insert OID = 12 ( "internal"	PGUID f f 0 0 2246 _null_ ));
  DESCR("built-in functions");
  #define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c"			PGUID f f 0 0 2247 _null_ ));
  DESCR("dynamically-loaded C functions");
  #define ClanguageId 13
! DATA(insert OID = 14 ( "sql"		PGUID f t 0 0 2248 _null_ ));
  DESCR("SQL-language functions");
  #define SQLlanguageId 14
  
--- 60,80 ----
  #define Anum_pg_language_lanplcallfoid	5
  #define Anum_pg_language_laninline		6
  #define Anum_pg_language_lanvalidator	7
! #define Anum_pg_language_lanchecker	8
! #define Anum_pg_language_lanacl			9
  
  /* ----------------
   *		initial contents of pg_language
   * ----------------
   */
  
! DATA(insert OID = 12 ( "internal"	PGUID f f 0 0 2246 0 _null_ ));
  DESCR("built-in functions");
  #define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c"			PGUID f f 0 0 2247 0 _null_ ));
  DESCR("dynamically-loaded C functions");
  #define ClanguageId 13
! DATA(insert OID = 14 ( "sql"		PGUID f t 0 0 2248 0 _null_ ));
  DESCR("SQL-language functions");
  #define SQLlanguageId 14
  
*** ./src/include/catalog/pg_pltemplate.h.orig	2011-12-07 05:59:37.149674412 +0100
--- ./src/include/catalog/pg_pltemplate.h	2011-12-07 06:00:07.352331780 +0100
***************
*** 36,41 ****
--- 36,42 ----
  	text		tmplhandler;	/* name of call handler function */
  	text		tmplinline;		/* name of anonymous-block handler, or NULL */
  	text		tmplvalidator;	/* name of validator function, or NULL */
+ 	text		tmplchecker;	/* name of checker function, or NULL */
  	text		tmpllibrary;	/* path of shared library */
  	aclitem		tmplacl[1];		/* access privileges for template */
  } FormData_pg_pltemplate;
***************
*** 51,65 ****
   *		compiler constants for pg_pltemplate
   * ----------------
   */
! #define Natts_pg_pltemplate					8
  #define Anum_pg_pltemplate_tmplname			1
  #define Anum_pg_pltemplate_tmpltrusted		2
  #define Anum_pg_pltemplate_tmpldbacreate	3
  #define Anum_pg_pltemplate_tmplhandler		4
  #define Anum_pg_pltemplate_tmplinline		5
  #define Anum_pg_pltemplate_tmplvalidator	6
! #define Anum_pg_pltemplate_tmpllibrary		7
! #define Anum_pg_pltemplate_tmplacl			8
  
  
  /* ----------------
--- 52,67 ----
   *		compiler constants for pg_pltemplate
   * ----------------
   */
! #define Natts_pg_pltemplate					9
  #define Anum_pg_pltemplate_tmplname			1
  #define Anum_pg_pltemplate_tmpltrusted		2
  #define Anum_pg_pltemplate_tmpldbacreate	3
  #define Anum_pg_pltemplate_tmplhandler		4
  #define Anum_pg_pltemplate_tmplinline		5
  #define Anum_pg_pltemplate_tmplvalidator	6
! #define Anum_pg_pltemplate_tmplchecker		7
! #define Anum_pg_pltemplate_tmpllibrary		8
! #define Anum_pg_pltemplate_tmplacl			9
  
  
  /* ----------------
***************
*** 67,79 ****
   * ----------------
   */
  
! DATA(insert ( "plpgsql"		t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl"		t t "pltcl_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu"		f f "pltclu_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl"		t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu"		f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu"	f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u"	f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u"	f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" "$libdir/plpython3" _null_ ));
  
  #endif   /* PG_PLTEMPLATE_H */
--- 69,81 ----
   * ----------------
   */
  
! DATA(insert ( "plpgsql"		t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "plpgsql_checker" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl"		t t "pltcl_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu"		f f "pltclu_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl"		t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu"		f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu"	f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u"	f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u"	f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" _null_ "$libdir/plpython3" _null_ ));
  
  #endif   /* PG_PLTEMPLATE_H */
*** ./src/include/commands/defrem.h.orig	2011-12-07 05:59:37.150674400 +0100
--- ./src/include/commands/defrem.h	2011-12-07 06:00:07.352331780 +0100
***************
*** 62,67 ****
--- 62,68 ----
  /* commands/functioncmds.c */
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
  extern void RemoveFunctionById(Oid funcOid);
+ extern void CheckFunction(CheckFunctionStmt *stmt);
  extern void SetFunctionReturnType(Oid funcOid, Oid newRetType);
  extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType);
  extern void RenameFunction(List *name, List *argtypes, const char *newname);
*** ./src/include/nodes/nodes.h.orig	2011-12-07 05:59:37.151674388 +0100
--- ./src/include/nodes/nodes.h	2011-12-07 06:00:07.353331768 +0100
***************
*** 291,296 ****
--- 291,297 ----
  	T_IndexStmt,
  	T_CreateFunctionStmt,
  	T_AlterFunctionStmt,
+ 	T_CheckFunctionStmt,
  	T_DoStmt,
  	T_RenameStmt,
  	T_RuleStmt,
*** ./src/include/nodes/parsenodes.h.orig	2011-12-07 05:59:37.153674364 +0100
--- ./src/include/nodes/parsenodes.h	2011-12-07 06:16:44.502121440 +0100
***************
*** 1734,1739 ****
--- 1734,1740 ----
  	List	   *plhandler;		/* PL call handler function (qual. name) */
  	List	   *plinline;		/* optional inline function (qual. name) */
  	List	   *plvalidator;	/* optional validator function (qual. name) */
+ 	List	   *plchecker;		/* optional checker function (qual. name) */
  	bool		pltrusted;		/* PL is trusted */
  } CreatePLangStmt;
  
***************
*** 2077,2082 ****
--- 2078,2098 ----
  } AlterFunctionStmt;
  
  /* ----------------------
+  *		Check {Function|Trigger} Statement
+  * ----------------------
+  */
+ typedef struct CheckFunctionStmt
+ {
+ 	NodeTag		type;
+ 	List	   *funcname;			/* qualified name of checked object */
+ 	List	   *args;			/* types of the arguments */
+ 	char	   *trgname;			/* trigger's name */
+ 	RangeVar	*relation;		/* trigger's relation */
+ 	bool	   is_function;		/* true for CHECK FUNCTION statement */
+ 	List	   *options;			/* other DefElem options */
+ } CheckFunctionStmt;
+ 
+ /* ----------------------
   *		DO Statement
   *
   * DoStmt is the raw parser output, InlineCodeBlock is the execution-time API
*** ./src/pl/plpgsql/src/pl_comp.c.orig	2011-12-07 05:59:37.155674342 +0100
--- ./src/pl/plpgsql/src/pl_comp.c	2011-12-07 06:00:07.360331689 +0100
***************
*** 115,121 ****
  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.
--- 115,120 ----
***************
*** 175,181 ****
  			 * 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
--- 174,180 ----
  			 * 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
***************
*** 2426,2432 ****
  }
  
  /*
!  * 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
--- 2425,2431 ----
  }
  
  /*
!  * 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,2446 ****
   * 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);
--- 2438,2445 ----
   * 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);
*** ./src/pl/plpgsql/src/pl_exec.c.orig	2011-12-07 05:59:37.157674320 +0100
--- ./src/pl/plpgsql/src/pl_exec.c	2011-12-07 07:36:23.112366630 +0100
***************
*** 210,216 ****
  static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
  						  PLpgSQL_expr *dynquery, List *params,
  						  const char *portalname, int cursorOptions);
! 
  
  /* ----------
   * plpgsql_exec_function	Called by the call handler for
--- 210,228 ----
  static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
  						  PLpgSQL_expr *dynquery, List *params,
  						  const char *portalname, int cursorOptions);
! static void check_row_or_rec(PLpgSQL_execstate *estate, PLpgSQL_row *row, PLpgSQL_rec *rec);
! static void check_expr(PLpgSQL_execstate *estate, PLpgSQL_expr *expr);
! static void assign_tupdesc_row_or_rec(PLpgSQL_execstate *estate,
! 					    PLpgSQL_row *row, PLpgSQL_rec *rec,
! 								    TupleDesc tupdesc);
! static TupleDesc expr_get_desc(PLpgSQL_execstate *estate,
! 						PLpgSQL_expr *query,
! 							bool use_element_type,
! 							bool expand_record,
! 								bool is_expression);
! static void var_init_to_null(PLpgSQL_execstate *estate, int varno);
! static void check_stmts(PLpgSQL_execstate *estate, List *stmts);
! static void check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt);
  
  /* ----------
   * plpgsql_exec_function	Called by the call handler for
***************
*** 6176,6178 ****
--- 6188,7240 ----
  
  	return portal;
  }
+ 
+ /*
+  * Following code ensures a CHECK FUNCTION and CHECK TRIGGER statements for PL/pgSQL
+  *
+  */
+ 
+ /*
+  * append a CONTEXT to error message
+  */
+ static void
+ check_error_callback(void *arg)
+ {
+ 	PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;
+ 
+ 	if (estate->err_stmt != NULL)
+ 	{
+ 		/* translator: last %s is a plpgsql statement type name */
+ 		errcontext("line %d at %s",
+ 				   estate->err_stmt->lineno,
+ 				   plpgsql_stmt_typename(estate->err_stmt));
+ 	}
+ }
+ 
+ /*
+  * Check function - it prepare variables and starts a prepare plan walker
+  *		called by function checker
+  */
+ void
+ plpgsql_check_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
+ {
+ 	PLpgSQL_execstate estate;
+ 	ErrorContextCallback plerrcontext;
+ 	int			i;
+ 
+ 	/* Setup error callback for ereport */
+ 	plerrcontext.callback = check_error_callback;
+ 	plerrcontext.arg = &estate;
+ 	plerrcontext.previous = error_context_stack;
+ 	error_context_stack = &plerrcontext;
+ 
+ 	/*
+ 	 * Setup the execution state - we would to reuse some exec routines
+ 	 * so we need a estate
+ 	 */
+ 	plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo);
+ 
+ 	/*
+ 	 * Make local execution copies of all the datums
+ 	 */
+ 	for (i = 0; i < estate.ndatums; i++)
+ 		estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
+ 
+ 	/*
+ 	 * Store the actual call argument values into the appropriate variables
+ 	 */
+ 	for (i = 0; i < func->fn_nargs; i++)
+ 	{
+ 		int			n = func->fn_argvarnos[i];
+ 
+ 		switch (estate.datums[n]->dtype)
+ 		{
+ 			case PLPGSQL_DTYPE_VAR:
+ 				{
+ 					var_init_to_null(&estate, n);
+ 				}
+ 				break;
+ 
+ 			case PLPGSQL_DTYPE_ROW:
+ 				{
+ 					PLpgSQL_row *row = (PLpgSQL_row *) estate.datums[n];
+ 
+ 					exec_move_row(&estate, NULL, row, NULL, NULL);
+ 				}
+ 				break;
+ 
+ 			default:
+ 				elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(&estate, (PLpgSQL_stmt *) func->action);
+ 
+ 	/* Cleanup temporary memory */
+ 	plpgsql_destroy_econtext(&estate);
+ 
+ 	/* Pop the error context stack */
+ 	error_context_stack = plerrcontext.previous;
+ }
+ 
+ /*
+  * Check trigger - prepare fake environments for testing trigger
+  *
+  */
+ void
+ plpgsql_check_trigger(PLpgSQL_function *func,
+ 					 TriggerData *trigdata)
+ {
+ 	PLpgSQL_execstate estate;
+ 	ErrorContextCallback plerrcontext;
+ 	PLpgSQL_rec *rec_new,
+ 			   *rec_old;
+ 	int			i;
+ 
+ 	/* Setup error callback for ereport */
+ 	plerrcontext.callback = check_error_callback;
+ 	plerrcontext.arg = &estate;
+ 	plerrcontext.previous = error_context_stack;
+ 	error_context_stack = &plerrcontext;
+ 
+ 	/*
+ 	 * Setup the execution state - we would to reuse some exec routines
+ 	 * so we need a estate
+ 	 */
+ 	plpgsql_estate_setup(&estate, func, NULL);
+ 
+ 	/*
+ 	 * Make local execution copies of all the datums
+ 	 */
+ 	for (i = 0; i < estate.ndatums; i++)
+ 		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 *) (estate.datums[func->new_varno]);
+ 	rec_new->freetup = false;
+ 	rec_new->freetupdesc = false;
+ 	assign_tupdesc_row_or_rec(&estate, NULL, rec_new, trigdata->tg_relation->rd_att);
+ 
+ 	rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
+ 	rec_old->freetup = false;
+ 	rec_old->freetupdesc = false;
+ 	assign_tupdesc_row_or_rec(&estate, NULL, rec_old, trigdata->tg_relation->rd_att);
+ 
+ 	/*
+ 	 * Assign the special tg_ variables
+ 	 */
+ 	var_init_to_null(&estate, func->tg_op_varno);
+ 	var_init_to_null(&estate, func->tg_name_varno);
+ 	var_init_to_null(&estate, func->tg_when_varno);
+ 	var_init_to_null(&estate, func->tg_level_varno);
+ 	var_init_to_null(&estate, func->tg_relid_varno);
+ 	var_init_to_null(&estate, func->tg_relname_varno);
+ 	var_init_to_null(&estate, func->tg_table_name_varno);
+ 	var_init_to_null(&estate, func->tg_table_schema_varno);
+ 	var_init_to_null(&estate, func->tg_nargs_varno);
+ 	var_init_to_null(&estate, func->tg_argv_varno);
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(&estate, (PLpgSQL_stmt *) func->action);
+ 
+ 	/* Cleanup temporary memory */
+ 	plpgsql_destroy_econtext(&estate);
+ 
+ 	/* Pop the error context stack */
+ 	error_context_stack = plerrcontext.previous;
+ }
+ 
+ /*
+  * Verify lvalue
+  *    It doesn't repeat a checks that are done.
+  *  Checks a subscript expressions, verify a validity of record's fields
+  */
+ static void
+ check_target(PLpgSQL_execstate *estate, int varno)
+ {
+ 	PLpgSQL_datum *target = estate->datums[varno];
+ 
+ 	switch (target->dtype)
+ 	{
+ 		case PLPGSQL_DTYPE_VAR:
+ 		case PLPGSQL_DTYPE_REC:
+ 			break;
+ 
+ 		case PLPGSQL_DTYPE_ROW:
+ 			check_row_or_rec(estate, (PLpgSQL_row *) target, NULL);
+ 			break;
+ 
+ 		case PLPGSQL_DTYPE_RECFIELD:
+ 			{
+ 				PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target;
+ 				PLpgSQL_rec *rec;
+ 				int			fno;
+ 
+ 				rec = (PLpgSQL_rec *) (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)));
+ 
+ 					check_expr(estate, arrayelem->subscript);
+ 
+ 					target = 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(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_execstate *estate, 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(estate, row->varnos[fnum]);
+ 		}
+ 	}
+ }
+ 
+ /*
+  * Generate a prepared plan - this is simplyfied copy from pl_exec.c
+  *   Is not necessary to check simple plan
+  */
+ static void
+ prepare_expr(PLpgSQL_execstate *estate,
+ 				  PLpgSQL_expr *expr, int cursorOptions)
+ {
+ 	SPIPlanPtr	plan;
+ 
+ 	/* leave when there are not expression */
+ 	if (expr == NULL)
+ 		return;
+ 
+ 	/* leave when plan is created */
+ 	if (expr->plan != NULL)
+ 		return;
+ 
+ 	/*
+ 	 * 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 = 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")));
+ 			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.")));
+ 			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_execstate *estate, PLpgSQL_expr *expr)
+ {
+ 	TupleDesc tupdesc;
+ 
+ 	if (expr != NULL)
+ 	{
+ 		prepare_expr(estate, expr, 0);
+ 		tupdesc = expr_get_desc(estate, expr, false, false, true);
+ 		ReleaseTupleDesc(tupdesc);
+ 	}
+ }
+ 
+ /*
+  * 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_execstate *estate,
+ 					    PLpgSQL_row *row, PLpgSQL_rec *rec,
+ 								    TupleDesc tupdesc)
+ {
+ 	bool	   *nulls;
+ 	HeapTuple  tup;
+ 
+ 	if (tupdesc == NULL)
+ 		elog(ERROR, "tuple descriptor is empty");
+ 
+ 	/*
+ 	 * row variable has assigned TupleDesc already, so don't be processed
+ 	 * here
+ 	 */
+ 	if (rec != NULL)
+ 	{
+ 		PLpgSQL_rec *target = (PLpgSQL_rec *)(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_execstate *estate, int varno, TupleDesc tupdesc)
+ {
+ 	PLpgSQL_datum *target = estate->datums[varno];
+ 
+ 	if (target->dtype == PLPGSQL_DTYPE_REC)
+ 		assign_tupdesc_row_or_rec(estate, NULL, (PLpgSQL_rec *) target, tupdesc);
+ }
+ 
+ /*
+  * Returns a tuple descriptor based on existing plan
+  */
+ static TupleDesc 
+ expr_get_desc(PLpgSQL_execstate *estate,
+ 						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);
+ 
+ 		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 (tupdesc->natts != 1)
+ 			ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 				 errmsg_plural("query \"%s\" returned %d column",
+ 							   "query \"%s\" returned %d columns",
+ 							   tupdesc->natts,
+ 							   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))));
+ 
+ 		/* 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
+ 			elog(ERROR, "cannot to identify real type for record type variable");
+ 	}
+ 
+ 	if (is_expression && tupdesc->natts != 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 			  errmsg_plural("query \"%s\" returned %d column",
+ 				   "query \"%s\" returned %d columns",
+ 				   tupdesc->natts,
+ 				   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(..)
+ 	 */
+ 	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)
+ 						elog(ERROR, "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
+  */
+ static void
+ check_stmts(PLpgSQL_execstate *estate, List *stmts)
+ {
+ 	ListCell *lc;
+ 
+ 	foreach(lc, stmts)
+ 	{
+ 		check_stmt(estate, (PLpgSQL_stmt *) lfirst(lc));
+ 	}
+ }
+ 
+ /*
+  * walk over all statements
+  */
+ static void
+ check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
+ {
+ 	TupleDesc	tupdesc = NULL;
+ 	PLpgSQL_function *func;
+ 	ListCell *l;
+ 
+ 	if (stmt == NULL)
+ 		return;
+ 
+ 	estate->err_stmt = stmt;
+ 	func = estate->func;
+ 
+ 	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++)
+ 				{
+ 					d = func->datums[stmt_block->initvarnos[i]];
+ 
+ 					if (d->dtype == PLPGSQL_DTYPE_VAR)
+ 					{
+ 						PLpgSQL_var *var = (PLpgSQL_var *) d;
+ 
+ 						check_expr(estate, var->default_val);
+ 					}
+ 				}
+ 
+ 				check_stmts(estate, stmt_block->body);
+ 				
+ 				if (stmt_block->exceptions)
+ 				{
+ 					foreach(l, stmt_block->exceptions->exc_list)
+ 					{
+ 						check_stmts(estate, ((PLpgSQL_exception *) lfirst(l))->action);
+ 					}
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_ASSIGN:
+ 			{
+ 				PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt;
+ 
+ 				/* prepare plan if desn't exist yet */
+ 				prepare_expr(estate, stmt_assign->expr, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_assign->expr,
+ 										false,		/* no element type */
+ 										true,		/* expand record */
+ 										true);		/* is expression */
+ 
+ 				/* check target, ensure target can get a result */
+ 				check_target(estate, stmt_assign->varno);
+ 
+ 				/* assign a tupdesc to record variable */
+ 				assign_tupdesc_dno(estate, stmt_assign->varno, tupdesc);
+ 				ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_IF:
+ 			{
+ 				PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt;
+ 				ListCell *l;
+ 
+ 				check_expr(estate, stmt_if->cond);
+ 
+ 				check_stmts(estate, stmt_if->then_body);
+ 
+ 				foreach(l, stmt_if->elsif_list)
+ 				{
+ 					PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l);
+ 
+ 					check_expr(estate, elif->cond);
+ 					check_stmts(estate, elif->stmts);
+ 				}
+ 
+ 				check_stmts(estate, 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 *) estate->datums[stmt_case->t_varno];
+ 
+ 					/* we need to set hidden variable type */
+ 					prepare_expr(estate, stmt_case->t_expr, 0);
+ 
+ 					tupdesc = expr_get_desc(estate,
+ 									stmt_case->t_expr,
+ 										false,		/* no element type */
+ 										false,		/* 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,
+ 												   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(estate, cwt->expr);
+ 					check_stmts(estate, cwt->stmts);
+ 				}
+ 
+ 				check_stmts(estate, stmt_case->else_stmts);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_LOOP:
+ 			check_stmts(estate, ((PLpgSQL_stmt_loop *) stmt)->body);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_WHILE:
+ 			{
+ 				PLpgSQL_stmt_while *stmt_while = (PLpgSQL_stmt_while *) stmt;
+ 
+ 				check_expr(estate, stmt_while->cond);
+ 				check_stmts(estate, stmt_while->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORI:
+ 			{
+ 				PLpgSQL_stmt_fori *stmt_fori = (PLpgSQL_stmt_fori *) stmt;
+ 
+ 				check_expr(estate, stmt_fori->lower);
+ 				check_expr(estate, stmt_fori->upper);
+ 				check_expr(estate, stmt_fori->step);
+ 
+ 				check_stmts(estate, stmt_fori->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORS:
+ 			{
+ 				PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt;
+ 
+ 				/* we need to set hidden variable type */
+ 				prepare_expr(estate, stmt_fors->query, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_fors->query,
+ 									false,		/* no element type */
+ 									false,		/* expand record */
+ 									false);		/* is expression */
+ 
+ 				check_row_or_rec(estate, stmt_fors->row, stmt_fors->rec);
+ 				assign_tupdesc_row_or_rec(estate, stmt_fors->row, stmt_fors->rec, tupdesc);
+ 
+ 				check_stmts(estate, stmt_fors->body);
+ 				ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORC:
+ 			{
+ 				PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt;
+ 				PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_forc->curvar];
+ 
+ 				prepare_expr(estate, stmt_forc->argquery, 0);
+ 
+ 				if (var->cursor_explicit_expr != NULL)
+ 				{
+ 					prepare_expr(estate, var->cursor_explicit_expr,
+ 								    var->cursor_options);
+ 
+ 					tupdesc = expr_get_desc(estate,
+ 									var->cursor_explicit_expr,
+ 										false,		/* no element type */
+ 										false,		/* expand record */
+ 										false);		/* is expression */
+ 
+ 					check_row_or_rec(estate, stmt_forc->row, stmt_forc->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_forc->row, stmt_forc->rec, tupdesc);
+ 				}
+ 
+ 				check_stmts(estate, stmt_forc->body);
+ 				if (tupdesc != NULL)
+ 					ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNFORS:
+ 			{
+ 				PLpgSQL_stmt_dynfors * stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt;
+ 
+ 				if (stmt_dynfors->rec != NULL)
+ 					elog(ERROR, "cannot determinate a result of dynamic SQL");
+ 
+ 				check_expr(estate, stmt_dynfors->query);
+ 
+ 				foreach(l, stmt_dynfors->params)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				check_stmts(estate, stmt_dynfors->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FOREACH_A:
+ 			{
+ 				PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt;
+ 
+ 				prepare_expr(estate, stmt_foreach_a->expr, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_foreach_a->expr,
+ 									true,		/* no element type */
+ 									false,		/* expand record */
+ 									true);		/* is expression */
+ 
+ 				check_target(estate, stmt_foreach_a->varno);
+ 				assign_tupdesc_dno(estate, stmt_foreach_a->varno, tupdesc);
+ 				ReleaseTupleDesc(tupdesc);
+ 
+ 				check_stmts(estate, stmt_foreach_a->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_EXIT:
+ 			check_expr(estate, ((PLpgSQL_stmt_exit *) stmt)->cond);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_PERFORM:
+ 			prepare_expr(estate, ((PLpgSQL_stmt_perform *) stmt)->expr, 0);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN:
+ 			check_expr(estate, ((PLpgSQL_stmt_return *) stmt)->expr);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN_NEXT:
+ 			check_expr(estate, ((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(estate, stmt_rq->dynquery);
+ 				prepare_expr(estate, stmt_rq->query, 0);
+ 
+ 				foreach(l, stmt_rq->params)
+ 				{
+ 					check_expr(estate, (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(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				foreach(l, stmt_raise->options)
+ 				{
+ 					PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(l);
+ 
+ 					check_expr(estate, 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;
+ 
+ 				prepare_expr(estate, stmt_execsql->sqlstmt, 0);
+ 				if (stmt_execsql->into)
+ 				{
+ 					tupdesc = expr_get_desc(estate,
+ 								stmt_execsql->sqlstmt,
+ 											false,		/* no element type */
+ 											false,		/* expand record */
+ 											false);		/* is expression */
+ 
+ 					/* check target, ensure target can get a result */
+ 					check_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec, tupdesc);
+ 					ReleaseTupleDesc(tupdesc);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNEXECUTE:
+ 			{
+ 				PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt;
+ 
+ 				check_expr(estate, stmt_dynexecute->query);
+ 
+ 				foreach(l, stmt_dynexecute->params)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				if (stmt_dynexecute->into)
+ 				{
+ 					if (stmt_dynexecute->rec != NULL)
+ 						elog(ERROR, "cannot determinate a result of dynamic SQL");
+ 
+ 					check_row_or_rec(estate, stmt_dynexecute->row, stmt_dynexecute->rec);
+ 				}
+ 			}
+ 			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)
+ 					prepare_expr(estate, var->cursor_explicit_expr,
+ 								   var->cursor_options);
+ 
+ 				prepare_expr(estate, stmt_open->query, 0);
+ 				prepare_expr(estate, stmt_open->argquery, 0);
+ 				check_expr(estate, stmt_open->dynquery);
+ 
+ 				foreach(l, stmt_open->params)
+ 				{
+ 					check_expr(estate, (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(estate, diag_item->target);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FETCH:
+ 			{
+ 				PLpgSQL_stmt_fetch *stmt_fetch = (PLpgSQL_stmt_fetch *) stmt;
+ 				PLpgSQL_var *var = (PLpgSQL_var *)(estate->datums[stmt_fetch->curvar]);
+ 
+ 				if (var != NULL && var->cursor_explicit_expr != NULL)
+ 				{
+ 					prepare_expr(estate, var->cursor_explicit_expr, 
+ 									    var->cursor_options);
+ 					tupdesc = expr_get_desc(estate,
+ 								    var->cursor_explicit_expr,
+ 											false,		/* no element type */
+ 											false,		/* expand record */
+ 											false);		/* is expression */
+ 					check_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec, tupdesc);
+ 					ReleaseTupleDesc(tupdesc);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_CLOSE:
+ 			break;
+ 
+ 		default:
+ 			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
+ 			return; /* be compiler quite */
+ 	}
+ }
+ 
+ /*
+  * Initialize variable to NULL
+  */
+ static void
+ var_init_to_null(PLpgSQL_execstate *estate, int varno)
+ {
+ 	PLpgSQL_var *var = (PLpgSQL_var *) estate->datums[varno];
+ 	var->value = (Datum) 0;
+ 	var->isnull = true;
+ 	var->freeval = false;
+ }
*** ./src/pl/plpgsql/src/pl_handler.c.orig	2011-12-07 05:59:37.158674309 +0100
--- ./src/pl/plpgsql/src/pl_handler.c	2011-12-07 06:00:07.365331632 +0100
***************
*** 312,314 ****
--- 312,452 ----
  
  	PG_RETURN_VOID();
  }
+ 
+ /* ----------
+  * plpgsql_checker
+  *
+  * This function attempts to check a embeded SQL inside a PL/pgSQL function at
+  * CHECK FUNCTION time. It should to have one or two parameters. Second
+  * parameter is a relation (used when function is trigger).
+  * ----------
+  */
+ PG_FUNCTION_INFO_V1(plpgsql_checker);
+ 
+ Datum
+ plpgsql_checker(PG_FUNCTION_ARGS)
+ {
+ 	Oid			funcoid = PG_GETARG_OID(0);
+ 	Oid			relid = PG_GETARG_OID(1);
+ 	HeapTuple	tuple;
+ 	FunctionCallInfoData fake_fcinfo;
+ 	FmgrInfo	flinfo;
+ 	TriggerData trigdata;
+ 	int			rc;
+ 	PLpgSQL_function *function;
+ 	PLpgSQL_execstate *cur_estate;
+ 
+ 	Form_pg_proc proc;
+ 	char		functyptype;
+ 	bool	   istrigger = false;
+ 
+ 	/* we don't need to repair a check done by validator */
+ 
+ 	tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
+ 	if (!HeapTupleIsValid(tuple))
+ 		elog(ERROR, "cache lookup failed for function %u", funcoid);
+ 	proc = (Form_pg_proc) GETSTRUCT(tuple);
+ 
+ 	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))
+ 		{
+ 			istrigger = true;
+ 			if (!OidIsValid(relid))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("PL/pgSQL trigger functions cannot be checked directly"),
+ 						 errhint("use CHECK TRIGGER statement instead")));
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Connect to SPI manager
+ 	 */
+ 	if ((rc = SPI_connect()) != SPI_OK_CONNECT)
+ 			elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
+ 
+ 	/*
+ 	 * Set up a fake fcinfo with just enough info to satisfy
+ 	 * plpgsql_compile().
+ 	 *
+ 	 * there should be a different real argtypes for polymorphic params
+ 	 */
+ 	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;
+ 		trigdata.tg_relation = relation_open(relid, AccessShareLock);
+ 		fake_fcinfo.context = (Node *) &trigdata;
+ 	}
+ 
+ 	/* Get a compiled function */
+ 	function = plpgsql_compile(&fake_fcinfo, false);
+ 
+ 	/* 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 prepare plans */
+ 	PG_TRY();
+ 	{
+ 		if (!istrigger)
+ 			plpgsql_check_function(function, &fake_fcinfo);
+ 		else
+ 			plpgsql_check_trigger(function, &trigdata);
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		if (istrigger)
+ 			relation_close(trigdata.tg_relation, AccessShareLock);
+ 
+ 		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);
+ 
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	if (istrigger)
+ 		relation_close(trigdata.tg_relation, AccessShareLock);
+ 
+ 	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);
+ 
+ 	/*
+ 	 * Disconnect from SPI manager
+ 	 */
+ 	if ((rc = SPI_finish()) != SPI_OK_FINISH)
+ 			elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
+ 
+ 	ReleaseSysCache(tuple);
+ 
+ 	PG_RETURN_VOID();
+ }
*** ./src/pl/plpgsql/src/plpgsql.h.orig	2011-12-07 05:59:37.160674287 +0100
--- ./src/pl/plpgsql/src/plpgsql.h	2011-12-07 06:00:07.366331620 +0100
***************
*** 902,907 ****
--- 902,908 ----
  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,916 ****
--- 912,918 ----
  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_checker(PG_FUNCTION_ARGS);
  
  /* ----------
   * Functions in pl_exec.c
***************
*** 928,933 ****
--- 930,939 ----
  extern void exec_get_datum_type_info(PLpgSQL_execstate *estate,
  						 PLpgSQL_datum *datum,
  						 Oid *typeid, int32 *typmod, Oid *collation);
+ extern void plpgsql_check_function(PLpgSQL_function *func,
+ 					 FunctionCallInfo fcinfo);
+ extern void plpgsql_check_trigger(PLpgSQL_function *func,
+ 					 TriggerData *trigdata);
  
  /* ----------
   * Functions for namespace handling in pl_funcs.c
*** ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql.orig	2011-12-07 05:59:37.162674263 +0100
--- ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql	2011-12-07 06:00:07.366331620 +0100
***************
*** 5,7 ****
--- 5,8 ----
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_call_handler();
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_inline_handler(internal);
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_validator(oid);
+ ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_checker(oid, regclass);
*** ./src/test/regress/expected/plpgsql.out.orig	2011-12-07 05:59:37.164674240 +0100
--- ./src/test/regress/expected/plpgsql.out	2011-12-07 08:00:04.000000000 +0100
***************
*** 302,307 ****
--- 302,310 ----
  ' 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
+ check trigger tg_hslot_biu on HSlot;
+ NOTICE:  checked function "tg_hslot_biu()"
  -- ************************************************************
  -- * BEFORE DELETE on HSlot
  -- *	- prevent from manual manipulation
***************
*** 635,640 ****
--- 638,646 ----
      raise exception ''illegal backlink beginning with %'', mytype;
  end;
  ' language plpgsql;
+ -- check function should not fail
+ check function tg_backlink_set(bpchar, bpchar);
+ NOTICE:  checked function "tg_backlink_set(character,character)"
  -- ************************************************************
  -- * Support function to clear out the backlink field if
  -- * it still points to specific slot
***************
*** 2802,2807 ****
--- 2808,2842 ----
   
  (1 row)
  
+ -- check function should not fail
+ check function for_vect();
+ NOTICE:  checked function "for_vect()"
+ -- 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
***************
*** 3283,3288 ****
--- 3318,3326 ----
    return;
  end;
  $$ language plpgsql;
+ -- check function should not fail
+ check function forc01();
+ NOTICE:  checked function "forc01()"
  select forc01();
  NOTICE:  5 from c
  NOTICE:  6 from c
***************
*** 3716,3721 ****
--- 3754,3762 ----
    end case;
  end;
  $$ language plpgsql immutable;
+ -- check function should not fail
+ check function case_test(bigint);
+ NOTICE:  checked function "case_test(bigint)"
  select case_test(1);
   case_test 
  -----------
***************
*** 4571,4573 ****
--- 4612,4956 ----
  CONTEXT:  PL/pgSQL function "testoa" line 5 at assignment
  drop function arrayassign1();
  drop function testoa(x1 int, x2 int, x3 int);
+ --
+ -- check function statement tests
+ --
+ create table t1(a int, b int);
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+ end;
+ $$ language plpgsql;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ LINE 1: update t1 set c = 30
+                       ^
+ DETAIL:  column "c" of relation "t1" does not exist
+ QUERY:  update t1 set c = 30
+ CONTEXT:  line 4 at SQL statement
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  record "r" has no field "c"
+ CONTEXT:  SQL statement "SELECT r.c"
+ line 6 at RAISE
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  record "r" has no field "c"
+ CONTEXT:  SQL statement "SELECT r.c"
+ line 6 at RAISE
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  record "r" has no field "c"
+ CONTEXT:  line 6 at assignment
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ LINE 1: SELECT a + b
+                ^
+ DETAIL:  column "a" does not exist
+ QUERY:  SELECT a + b
+ CONTEXT:  line 5 at assignment
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  too many parameters specified for RAISE
+ CONTEXT:  line 4 at RAISE
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  too few parameters specified for RAISE
+ CONTEXT:  line 4 at RAISE
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ LINE 1: SELECT c+10
+                ^
+ DETAIL:  column "c" does not exist
+ QUERY:  SELECT c+10
+ CONTEXT:  line 5 at assignment
+ 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;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  subscripted object is not an array
+ CONTEXT:  line 5 at assignment
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  record "_exception" has no field "hint"
+ CONTEXT:  line 7 at GET DIAGNOSTICS
+ 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);
+ check trigger t1_f1 on t1;
+ WARNING:  error in function "f1_trg()"
+ DETAIL:  record "new" has no field "c"
+ CONTEXT:  SQL statement "SELECT new.c"
+ line 5 at RAISE
+ 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
+ check trigger t1_f1 on t1;
+ WARNING:  error in function "f1_trg()"
+ DETAIL:  record "new" has no field "c"
+ CONTEXT:  line 5 at assignment
+ -- 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
+ check trigger t1_f1 on t1;
+ NOTICE:  checked function "f1_trg()"
+ -- ok
+ insert into t1 values(6,30);
+ drop table t1;
+ drop type _exception_type;
+ drop function f1_trg();
*** ./src/test/regress/sql/plpgsql.sql.orig	2011-12-07 05:59:37.166674218 +0100
--- ./src/test/regress/sql/plpgsql.sql	2011-12-07 06:00:07.371331565 +0100
***************
*** 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
+ check trigger tg_hslot_biu on HSlot;
  
  -- ************************************************************
  -- * BEFORE DELETE on HSlot
***************
*** 747,752 ****
--- 749,757 ----
  end;
  ' language plpgsql;
  
+ -- check function should not fail
+ check function tg_backlink_set(bpchar, bpchar);
+ 
  
  -- ************************************************************
  -- * Support function to clear out the backlink field if
***************
*** 2335,2340 ****
--- 2340,2352 ----
  
  select for_vect();
  
+ -- check function should not fail
+ 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
***************
*** 2714,2719 ****
--- 2726,2734 ----
  end;
  $$ language plpgsql;
  
+ -- check function should not fail
+ check function forc01();
+ 
  select forc01();
  
  -- try updating the cursor's current row
***************
*** 3048,3053 ****
--- 3063,3071 ----
  end;
  $$ language plpgsql immutable;
  
+ -- check function should not fail
+ check function case_test(bigint);
+ 
  select case_test(1);
  select case_test(2);
  select case_test(3);
***************
*** 3600,3602 ****
--- 3618,3862 ----
  
  drop function arrayassign1();
  drop function testoa(x1 int, x2 int, x3 int);
+ 
+ --
+ -- check function statement tests
+ --
+ 
+ create table t1(a int, b int);
+ 
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+ end;
+ $$ language plpgsql;
+ 
+ select f1();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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;
+ 
+ select f1();
+ 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();
+ 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);
+ check trigger t1_f1 on 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
+ check trigger t1_f1 on 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
+ check trigger t1_f1 on t1;
+ 
+ -- ok
+ insert into t1 values(6,30);
+ 
+ drop table t1;
+ drop type _exception_type;
+ 
+ drop function f1_trg();
+ 
#35Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Pavel Stehule (#34)
Re: review: CHECK FUNCTION statement

Pavel Stehule wrote:

updated version

changes:

* CHECK FUNCTION ALL; is enabled - in this case functions from
pg_catalog schema are ignored

I looked on parser, and I didn't other changes there - IN SCHEMA, FOR
ROLE are used more time there, so our usage will be consistent

a small addition

* don't check SQL functions - are checked well now
* don't check functions from information_schema too

One hunk in the patch fails due to conflict with
commit d5f23af6bfbc454e86dd16e5c7a0bfc0cf6189d0
(Peter Eisentraut's const patch).

There are also compiler warnings about discarded const
qualifiers in backend/nodes/copyfuncs.c,
backend/nodes/equalfuncs.c and backend/parser/gram.y.

There is a bug when ALL IN SCHEMA or ALL IN LANGUAGE
is used:

test=> CHECK FUNCTION ALL IN LANGUAGE plpgsql;
ERROR: language "language" does not exist
test=> CHECK FUNCTION ALL IN SCHEMA laurenz;
ERROR: schema "schema" does not exist

Something gets mixed up here.

I like the idea that CHECK FUNCTION ALL without additional
clauses works and ignores pg_catalog and information_schema!

I'm working on some documentation, but it won't be final before
the functionality is agreed upon.

Yours,
Laurenz Albe

#36Pavel Stehule
pavel.stehule@gmail.com
In reply to: Albe Laurenz (#35)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Hello

there is fixed version

Regards

Pavel

2011/12/9 Albe Laurenz <laurenz.albe@wien.gv.at>:

Show quoted text

Pavel Stehule wrote:

updated version

changes:

* CHECK FUNCTION ALL; is enabled - in this case functions from
pg_catalog schema are ignored

I looked on parser, and I didn't other changes there - IN SCHEMA, FOR
ROLE are used more time there, so our usage will be consistent

a small addition

* don't check SQL functions - are checked well now
* don't check functions from information_schema too

One hunk in the patch fails due to conflict with
commit d5f23af6bfbc454e86dd16e5c7a0bfc0cf6189d0
(Peter Eisentraut's const patch).

There are also compiler warnings about discarded const
qualifiers in backend/nodes/copyfuncs.c,
backend/nodes/equalfuncs.c and backend/parser/gram.y.

There is a bug when ALL IN SCHEMA or ALL IN LANGUAGE
is used:

test=> CHECK FUNCTION ALL IN LANGUAGE plpgsql;
ERROR:  language "language" does not exist
test=> CHECK FUNCTION ALL IN SCHEMA laurenz;
ERROR:  schema "schema" does not exist

Something gets mixed up here.

I like the idea that CHECK FUNCTION ALL without additional
clauses works and ignores pg_catalog and information_schema!

I'm working on some documentation, but it won't be final before
the functionality is agreed upon.

Yours,
Laurenz Albe

Attachments:

check_function-2011-12-09-2.difftext/x-patch; charset=US-ASCII; name=check_function-2011-12-09-2.diffDownload
*** ./doc/src/sgml/catalogs.sgml.orig	2011-12-09 15:15:09.164913469 +0100
--- ./doc/src/sgml/catalogs.sgml	2011-12-09 15:15:43.219637443 +0100
***************
*** 3652,3657 ****
--- 3652,3668 ----
       </row>
  
       <row>
+       <entry><structfield>lanchecker</structfield></entry>
+       <entry><type>oid</type></entry>
+       <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+       <entry>
+        This references a language checker function that is responsible
+        for checking a embedded SQL and can provide detailed checking.
+        Zero if no checker is provided.
+       </entry>
+      </row>
+ 
+      <row>
        <entry><structfield>lanacl</structfield></entry>
        <entry><type>aclitem[]</type></entry>
        <entry></entry>
*** ./doc/src/sgml/ref/allfiles.sgml.orig	2011-12-09 15:15:09.165913461 +0100
--- ./doc/src/sgml/ref/allfiles.sgml	2011-12-09 15:15:43.219637443 +0100
***************
*** 40,45 ****
--- 40,46 ----
  <!ENTITY alterView          SYSTEM "alter_view.sgml">
  <!ENTITY analyze            SYSTEM "analyze.sgml">
  <!ENTITY begin              SYSTEM "begin.sgml">
+ <!ENTITY checkFunction      SYSTEM "check_function.sgml">
  <!ENTITY checkpoint         SYSTEM "checkpoint.sgml">
  <!ENTITY close              SYSTEM "close.sgml">
  <!ENTITY cluster            SYSTEM "cluster.sgml">
*** ./doc/src/sgml/ref/create_language.sgml.orig	2011-12-09 15:15:09.167913445 +0100
--- ./doc/src/sgml/ref/create_language.sgml	2011-12-09 15:15:43.264637074 +0100
***************
*** 23,29 ****
  <synopsis>
  CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
  CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ]
  </synopsis>
   </refsynopsisdiv>
  
--- 23,29 ----
  <synopsis>
  CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
  CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ] [ CHECK <replaceable>checkfunction</replaceable> ]
  </synopsis>
   </refsynopsisdiv>
  
***************
*** 217,222 ****
--- 217,236 ----
        </para>
       </listitem>
      </varlistentry>
+ 
+     <varlistentry>
+      <term><literal>CHECK</literal> <replaceable class="parameter">checkfunction</replaceable></term>
+ 
+      <listitem>
+       <para><replaceable class="parameter">checkfunction</replaceable> is the
+        name of a previously registered function that will be called
+        when a new function in the language is created, to check the
+        function by statemnt <command>CHECK FUNCTION</command> or 
+        <command>CHECK TRIGGER</command>.
+       </para>
+      </listitem>
+     </varlistentry>
+ 
     </variablelist>
  
    <para>
*** ./doc/src/sgml/reference.sgml.orig	2011-12-09 15:15:09.168913437 +0100
--- ./doc/src/sgml/reference.sgml	2011-12-09 15:15:43.264637074 +0100
***************
*** 68,73 ****
--- 68,74 ----
     &alterView;
     &analyze;
     &begin;
+    &checkFunction;
     &checkpoint;
     &close;
     &cluster;
*** ./doc/src/sgml/ref/check_function.sgml.orig	2011-12-09 15:15:36.767690075 +0100
--- ./doc/src/sgml/ref/check_function.sgml	2011-12-09 15:15:43.265637066 +0100
***************
*** 0 ****
--- 1,38 ----
+ <!--
+ doc/src/sgml/ref/check_function.sgml
+ -->
+ 
+ <refentry id="SQL-CHECKFUNCTION">
+  <refmeta>
+   <refentrytitle>CHECK FUNCTION</refentrytitle>
+   <manvolnum>7</manvolnum>
+   <refmiscinfo>SQL - Language Statements</refmiscinfo>
+  </refmeta>
+ 
+  <refnamediv>
+   <refname>CHECK FUNCTION</refname>
+   <refpurpose>ensure a deep checking of existing function</refpurpose>
+  </refnamediv>
+ 
+  <indexterm zone="sql-checkfunction">
+   <primary>CHECK FUNCTION</primary>
+  </indexterm>
+ 
+  <refsynopsisdiv>
+ <synopsis>
+ CHECK FUNCTION <replaceable class="parameter">name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] )
+  | CHECK FUNCTION ALL [ IN LANGUAGE <replaceable class="parameter">langname</replaceable> ] [ IN SCHEMA <replaceable class="parameter">schemaname</replaceable> ] [ FOR ROLE <replaceable class="parameter">ownername</replaceable> ]
+  | CHECK TRIGGER <replaceable class="parameter">name</replaceable> ON <replaceable class="parameter">tablename</replaceable>
+ </synopsis>
+  </refsynopsisdiv>
+ 
+  <refsect1 id="sql-checkfunction-description">
+   <title>Description</title>
+ 
+   <para>
+    <command>CHECK FUNCTION</command> check a existing function.
+    <command>CHECK TRIGGER</command> check a trigger function.
+   </para>
+  </refsect1>
+ 
+ </refentry>
*** ./src/backend/catalog/pg_proc.c.orig	2011-12-09 15:15:09.171913413 +0100
--- ./src/backend/catalog/pg_proc.c	2011-12-09 15:15:43.266637058 +0100
***************
*** 1101,1103 ****
--- 1101,1104 ----
  	*newcursorpos = newcp;
  	return false;
  }
+ 
*** ./src/backend/commands/functioncmds.c.orig	2011-12-09 15:15:09.173913397 +0100
--- ./src/backend/commands/functioncmds.c	2011-12-09 15:15:43.267637050 +0100
***************
*** 35,53 ****
--- 35,58 ----
  #include "access/genam.h"
  #include "access/heapam.h"
  #include "access/sysattr.h"
+ #include "access/xact.h"
  #include "catalog/dependency.h"
  #include "catalog/indexing.h"
  #include "catalog/objectaccess.h"
  #include "catalog/pg_aggregate.h"
  #include "catalog/pg_cast.h"
  #include "catalog/pg_language.h"
+ #include "catalog/namespace.h"
  #include "catalog/pg_namespace.h"
  #include "catalog/pg_proc.h"
  #include "catalog/pg_proc_fn.h"
+ #include "catalog/pg_trigger.h"
  #include "catalog/pg_type.h"
  #include "catalog/pg_type_fn.h"
  #include "commands/defrem.h"
  #include "commands/proclang.h"
+ #include "commands/trigger.h"
+ #include "executor/spi_priv.h"
  #include "miscadmin.h"
  #include "optimizer/var.h"
  #include "parser/parse_coerce.h"
***************
*** 60,66 ****
--- 65,73 ----
  #include "utils/fmgroids.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
+ #include "utils/memutils.h"
  #include "utils/rel.h"
+ #include "utils/resowner.h"
  #include "utils/syscache.h"
  #include "utils/tqual.h"
  
***************
*** 1009,1014 ****
--- 1016,1393 ----
  	}
  }
  
+ /*
+  * Search and execute related checker function
+  */
+ static void
+ CheckFunctionById(Oid funcOid, Oid relid)
+ {
+ 	HeapTuple	tup;
+ 	Form_pg_proc proc;
+ 	HeapTuple	languageTuple;
+ 	Form_pg_language languageStruct;
+ 	Oid		languageChecker;
+ 	bool	found_bug;
+ 	char *funcname;
+ 
+ 	tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
+ 	if (!HeapTupleIsValid(tup)) /* should not happen */
+ 		elog(ERROR, "cache lookup failed for function %u", funcOid);
+ 
+ 	proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 	languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
+ 	Assert(HeapTupleIsValid(languageTuple));
+ 
+ 	languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
+ 	languageChecker = languageStruct->lanchecker;
+ 
+ 	funcname = format_procedure(funcOid);
+ 
+ 	/* Check a function body */
+ 	if (OidIsValid(languageChecker))
+ 	{
+ 		ArrayType  *set_items = NULL;
+ 		int			save_nestlevel = 0;
+ 		Datum	datum;
+ 		bool		isnull;
+ 		MemoryContext oldCxt;
+ 		MemoryContext checkCxt;
+ 		ResourceOwner		oldowner;
+ 
+ 		datum = SysCacheGetAttr(PROCOID, tup, Anum_pg_proc_proconfig, &isnull);
+ 
+ 		if (!isnull)
+ 		{
+ 			/* Set per-function configuration parameters */
+ 			set_items = (ArrayType *) DatumGetPointer(datum);
+ 			if (set_items)			/* Need a new GUC nesting level */
+ 			{
+ 				save_nestlevel = NewGUCNestLevel();
+ 				ProcessGUCArray(set_items,
+ 							(superuser() ? PGC_SUSET : PGC_USERSET),
+ 							PGC_S_SESSION,
+ 							GUC_ACTION_SAVE);
+ 			}
+ 			else
+ 				save_nestlevel = 0; /* keep compiler quiet */
+ 		}
+ 
+ 		checkCxt = AllocSetContextCreate(CurrentMemoryContext,
+ 									"Check temporary context",
+ 									ALLOCSET_DEFAULT_MINSIZE,
+ 									ALLOCSET_DEFAULT_INITSIZE,
+ 									ALLOCSET_DEFAULT_MAXSIZE);
+ 
+ 		oldCxt = MemoryContextSwitchTo(checkCxt);
+ 
+ 		oldowner = CurrentResourceOwner;
+ 		BeginInternalSubTransaction(NULL);
+ 		MemoryContextSwitchTo(checkCxt);
+ 
+ 		/*
+ 		 * forward exception to warning
+ 		 */
+ 		PG_TRY();
+ 		{
+ 			OidFunctionCall2(languageChecker, ObjectIdGetDatum(funcOid), 
+ 								    ObjectIdGetDatum(relid));
+ 
+ 			RollbackAndReleaseCurrentSubTransaction();
+ 			MemoryContextSwitchTo(checkCxt);
+ 			CurrentResourceOwner = oldowner;
+ 
+ 			found_bug = false;
+ 		}
+ 		PG_CATCH();
+ 		{
+ 			ErrorData *edata;
+ 
+ 			MemoryContextSwitchTo(checkCxt);
+ 			edata = CopyErrorData();
+ 			FlushErrorState();
+ 
+ 			RollbackAndReleaseCurrentSubTransaction();
+ 			MemoryContextSwitchTo(checkCxt);
+ 			CurrentResourceOwner = oldowner;
+ 
+ 			edata->elevel = WARNING;
+ 
+ 			ereport(WARNING,
+ 					(errmsg("error in function \"%s\"", funcname),
+ 					 edata->message != NULL ? errdetail_internal("%s", edata->message) : 0,
+ 					 edata->context != NULL ? errcontext("%s",edata->context) : 0,
+ 					 edata->internalquery != NULL ? internalerrquery(edata->internalquery) : 0,
+ 					 internalerrposition(edata->internalpos)));
+ 
+ 			FreeErrorData(edata);
+ 
+ 			found_bug = true;
+ 		}
+ 		PG_END_TRY();
+ 
+ 		MemoryContextSwitchTo(oldCxt);
+ 
+ 		if (set_items)
+ 			AtEOXact_GUC(true, save_nestlevel);
+ 
+ 		if (!found_bug)
+ 			elog(NOTICE, "checked function \"%s\"",
+ 							funcname);
+ 	}
+ 	else
+ 		elog(NOTICE, "skip check function \"%s\", language \"%s\" hasn't checker function",
+ 					funcname,
+ 							    NameStr(languageStruct->lanname));
+ 
+ 	pfree(funcname);
+ 
+ 	ReleaseSysCache(languageTuple);
+ 	ReleaseSysCache(tup);
+ }
+ 
+ /*
+  * CheckFunction
+  *			call a PL checker function when this function exists.
+  */
+ void
+ CheckFunction(CheckFunctionStmt *stmt)
+ {
+ 	List	   *functionName = stmt->funcname;
+ 	List	   *argTypes = stmt->args;	/* list of TypeName nodes */
+ 	Oid			funcOid = InvalidOid;
+ 	HeapTuple	tup;
+ 	Oid trgOid = InvalidOid;
+ 	Oid	relid = InvalidOid;
+ 	Oid languageId;
+ 	int nkeys = 0;
+ 	List	*objects = NIL;
+ 
+ 
+ 	if (stmt->funcname != NULL)
+ 	{
+ 		/*
+ 		 * Find the function, 
+ 		 */
+ 		funcOid = LookupFuncNameTypeNames(functionName, argTypes, false);
+ 	}
+ 	else if (stmt->trgname != NULL)
+ 	{
+ 		HeapTuple	ht_trig;
+ 		Form_pg_trigger trigrec;
+ 		ScanKeyData skey[1];
+ 		Relation	tgrel;
+ 		SysScanDesc tgscan;
+ 
+ 		/* find a trigger function */
+ 		relid = RangeVarGetRelid(stmt->relation, ShareLock, false);
+ 		trgOid = get_trigger_oid(relid, stmt->trgname, false);
+ 
+ 		/*
+ 		 * Fetch the pg_trigger tuple by the Oid of the trigger
+ 		 */
+ 		tgrel = heap_open(TriggerRelationId, AccessShareLock);
+ 
+ 		ScanKeyInit(&skey[0],
+ 					ObjectIdAttributeNumber,
+ 					BTEqualStrategyNumber, F_OIDEQ,
+ 					ObjectIdGetDatum(trgOid));
+ 
+ 		tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true,
+ 									SnapshotNow, 1, skey);
+ 
+ 		ht_trig = systable_getnext(tgscan);
+ 
+ 		if (!HeapTupleIsValid(ht_trig))
+ 			elog(ERROR, "could not find tuple for trigger %u", trgOid);
+ 
+ 		trigrec = (Form_pg_trigger) GETSTRUCT(ht_trig);
+ 
+ 		/* we need to know trigger function to get PL checker function */
+ 		funcOid = trigrec->tgfoid;
+ 
+ 		/* Clean up */
+ 		systable_endscan(tgscan);
+ 
+ 		heap_close(tgrel, AccessShareLock);
+ 	}
+ 	else
+ 	{
+ 		ScanKeyData	   key[5];
+ 		Relation	   rel;
+ 		HeapScanDesc 	   scan;
+ 		ListCell *option;
+ 		bool	language_opt =  false;
+ 		bool	schema_opt = false;
+ 		bool	owner_opt = false;
+ 
+ 		/*
+ 		 * when Check stmt is multiple statement, then
+ 		 * prepare list of processed functions.
+ 		 */
+ 		foreach(option, stmt->options)
+ 		{
+ 			DefElem    *defel = (DefElem *) lfirst(option);
+ 
+ 			if (strcmp(defel->defname, "language") == 0)
+ 			{
+ 				HeapTuple	languageTuple;
+ 				Form_pg_language languageStruct;
+ 				Oid		languageChecker;
+ 				char *language = defGetString(defel);
+ 
+ 				if (language_opt)
+ 					elog(ERROR, "multiple usage of IN LANGUAGE option");
+ 				language_opt = true;
+ 
+ 				languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language));
+ 				if (!HeapTupleIsValid(languageTuple))
+ 					ereport(ERROR,
+ 						(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 						 errmsg("language \"%s\" does not exist", language),
+ 						 (PLTemplateExists(language) ?
+ 						  errhint("Use CREATE LANGUAGE to load the language into the database.") : 0)));
+ 
+ 				languageId = HeapTupleGetOid(languageTuple);
+ 				if (languageId == INTERNALlanguageId || languageId == ClanguageId)
+ 					elog(ERROR, "cannot to check functions in C or internal languages");
+ 
+ 				languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
+ 				languageChecker = languageStruct->lanchecker;
+ 				if (!OidIsValid(languageChecker))
+ 					elog(ERROR, "language \"%s\" has no defined checker function",
+ 							    language);
+ 
+ 				ScanKeyInit(&key[nkeys++],
+ 							Anum_pg_proc_prolang,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(languageId));
+ 
+ 				ReleaseSysCache(languageTuple);
+ 			}
+ 			else if (strcmp(defel->defname, "schema") == 0)
+ 			{
+ 				Oid namespaceId = LookupExplicitNamespace(defGetString(defel));
+ 
+ 				if (schema_opt)
+ 					elog(ERROR, "multiple usage of IN SCHEMA option");
+ 				schema_opt = true;
+ 
+ 				ScanKeyInit(&key[nkeys++],
+ 							Anum_pg_proc_pronamespace,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(namespaceId));
+ 
+ 			}
+ 			else if (strcmp(defel->defname, "owner") == 0)
+ 			{
+ 				Oid ownerId = get_role_oid(defGetString(defel), false);
+ 
+ 				if (owner_opt)
+ 					elog(ERROR, "multiple usage of FOR ROLE option");
+ 				owner_opt = true;
+ 
+ 				ScanKeyInit(&key[nkeys++],
+ 							Anum_pg_proc_proowner,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(ownerId));
+ 			}
+ 			else
+ 				elog(ERROR, "option \"%s\" not recognized",
+ 					 defel->defname);
+ 		}
+ 
+ 		/*
+ 		 * We don't would to iterate over pg_catalog functions without
+ 		 * explicit request and information_schema.
+ 		 */
+ 		if (!schema_opt)
+ 		{
+ 			Oid information_schemaId = LookupExplicitNamespace("information_schema");
+ 		
+ 			ScanKeyInit(&key[nkeys++],
+ 						Anum_pg_proc_pronamespace,
+ 						BTEqualStrategyNumber, F_OIDNE,
+ 						ObjectIdGetDatum(PG_CATALOG_NAMESPACE));
+ 
+ 			ScanKeyInit(&key[nkeys++],
+ 						Anum_pg_proc_pronamespace,
+ 						BTEqualStrategyNumber, F_OIDNE,
+ 						ObjectIdGetDatum(information_schemaId));
+ 		}
+ 
+ 		rel = heap_open(ProcedureRelationId,AccessShareLock);
+ 		scan = heap_beginscan(rel, SnapshotNow, nkeys, key);
+ 
+ 		while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+ 		{
+ 			Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 			funcOid = HeapTupleGetOid(tup);
+ 
+ 			/* when language is not specified, then check checker */
+ 			if (!language_opt)
+ 			{
+ 				Oid prolang = proc->prolang;
+ 
+ 				/* skip C, internal or SQL */
+ 				if (prolang == INTERNALlanguageId)
+ 				{
+ 					elog(NOTICE, "skip check function \"%s\", it uses internal language",
+ 								format_procedure(funcOid));
+ 					continue;
+ 				}
+ 				else if (prolang == ClanguageId)
+ 				{
+ 					elog(NOTICE, "skip check function \"%s\", uses C language",
+ 								format_procedure(funcOid));
+ 					continue;
+ 				}
+ 				else if (prolang == SQLlanguageId)
+ 				{
+ 					elog(NOTICE, "skip check function \"%s\", uses SQL language",
+ 								format_procedure(funcOid));
+ 					continue;
+ 				}
+ 			}
+ 
+ 			if (proc->prorettype == TRIGGEROID)
+ 			{
+ 				elog(NOTICE, "skip check function \"%s\", it is trigger function",
+ 							format_procedure(funcOid));
+ 				continue;
+ 			}
+ 
+ 			objects = lappend_oid(objects, funcOid);
+ 		}
+ 
+ 		heap_endscan(scan);
+ 		heap_close(rel, AccessShareLock);
+ 	}
+ 
+ 	if (funcOid == InvalidOid && objects == NIL)
+ 	{
+ 		elog(NOTICE, "nothing to check");
+ 		return;
+ 	}
+ 
+ 	if (objects != NIL)
+ 	{
+ 		ListCell *object;
+ 
+ 		foreach(object, objects)
+ 		{
+ 			CHECK_FOR_INTERRUPTS();
+ 
+ 			funcOid = lfirst_oid(object);
+ 			CheckFunctionById(funcOid, InvalidOid);
+ 		}
+ 	}
+ 	else
+ 	{
+ 		CheckFunctionById(funcOid, relid);
+ 	}
+ }
  
  /*
   * Rename function
*** ./src/backend/commands/proclang.c.orig	2011-12-09 15:15:09.174913389 +0100
--- ./src/backend/commands/proclang.c	2011-12-09 15:15:43.269637034 +0100
***************
*** 46,57 ****
  	char	   *tmplhandler;	/* name of handler function */
  	char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
  	char	   *tmplvalidator;	/* name of validator function, or NULL */
  	char	   *tmpllibrary;	/* path of shared library */
  } PLTemplate;
  
  static void create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, bool trusted);
  static PLTemplate *find_language_template(const char *languageName);
  static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
  							Oid newOwnerId);
--- 46,58 ----
  	char	   *tmplhandler;	/* name of handler function */
  	char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
  	char	   *tmplvalidator;	/* name of validator function, or NULL */
+ 	char	   *tmplchecker;	/* name of checker function, or NULL */
  	char	   *tmpllibrary;	/* path of shared library */
  } PLTemplate;
  
  static void create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, Oid checkerOid, bool trusted);
  static PLTemplate *find_language_template(const char *languageName);
  static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
  							Oid newOwnerId);
***************
*** 67,75 ****
  	PLTemplate *pltemplate;
  	Oid			handlerOid,
  				inlineOid,
! 				valOid;
  	Oid			funcrettype;
! 	Oid			funcargtypes[1];
  
  	/*
  	 * If we have template information for the language, ignore the supplied
--- 68,77 ----
  	PLTemplate *pltemplate;
  	Oid			handlerOid,
  				inlineOid,
! 				valOid,
! 				checkerOid;
  	Oid			funcrettype;
! 	Oid			funcargtypes[2];
  
  	/*
  	 * If we have template information for the language, ignore the supplied
***************
*** 219,228 ****
  		else
  			valOid = InvalidOid;
  
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, pltemplate->tmpltrusted);
  	}
  	else
  	{
--- 221,269 ----
  		else
  			valOid = InvalidOid;
  
+ 		/*
+ 		 * Likewise for the checker, if required; but we don't care about
+ 		 * its return type.
+ 		 */
+ 		if (pltemplate->tmplchecker)
+ 		{
+ 			funcname = SystemFuncName(pltemplate->tmplchecker);
+ 			funcargtypes[0] = OIDOID;
+ 			funcargtypes[1] = REGCLASSOID;
+ 			checkerOid = LookupFuncName(funcname, 2, funcargtypes, true);
+ 			if (!OidIsValid(checkerOid))
+ 			{
+ 				checkerOid = ProcedureCreate(pltemplate->tmplchecker,
+ 										 PG_CATALOG_NAMESPACE,
+ 										 false, /* replace */
+ 										 false, /* returnsSet */
+ 										 VOIDOID,
+ 										 ClanguageId,
+ 										 F_FMGR_C_VALIDATOR,
+ 										 pltemplate->tmplchecker,
+ 										 pltemplate->tmpllibrary,
+ 										 false, /* isAgg */
+ 										 false, /* isWindowFunc */
+ 										 false, /* security_definer */
+ 										 true,	/* isStrict */
+ 										 PROVOLATILE_VOLATILE,
+ 										 buildoidvector(funcargtypes, 2),
+ 										 PointerGetDatum(NULL),
+ 										 PointerGetDatum(NULL),
+ 										 PointerGetDatum(NULL),
+ 										 NIL,
+ 										 PointerGetDatum(NULL),
+ 										 1,
+ 										 0);
+ 			}
+ 		}
+ 		else
+ 			checkerOid = InvalidOid;
+ 
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, checkerOid, pltemplate->tmpltrusted);
  	}
  	else
  	{
***************
*** 294,303 ****
  		else
  			valOid = InvalidOid;
  
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, stmt->pltrusted);
  	}
  }
  
--- 335,355 ----
  		else
  			valOid = InvalidOid;
  
+ 		/* validate the checker function */
+ 		if (stmt->plchecker)
+ 		{
+ 			funcargtypes[0] = OIDOID;
+ 			funcargtypes[1] = REGCLASSOID;
+ 			checkerOid = LookupFuncName(stmt->plchecker, 2, funcargtypes, false);
+ 			/* return value is ignored, so we don't check the type */
+ 		}
+ 		else
+ 			checkerOid = InvalidOid;
+ 
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, checkerOid, stmt->pltrusted);
  	}
  }
  
***************
*** 307,313 ****
  static void
  create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, bool trusted)
  {
  	Relation	rel;
  	TupleDesc	tupDesc;
--- 359,365 ----
  static void
  create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, Oid checkerOid, bool trusted)
  {
  	Relation	rel;
  	TupleDesc	tupDesc;
***************
*** 337,342 ****
--- 389,395 ----
  	values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid);
  	values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid);
  	values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid);
+ 	values[Anum_pg_language_lanchecker - 1] = ObjectIdGetDatum(checkerOid);
  	nulls[Anum_pg_language_lanacl - 1] = true;
  
  	/* Check for pre-existing definition */
***************
*** 423,428 ****
--- 476,490 ----
  		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  	}
  
+ 	/* dependency on the checker function, if any */
+ 	if (OidIsValid(checkerOid))
+ 	{
+ 		referenced.classId = ProcedureRelationId;
+ 		referenced.objectId = checkerOid;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	/* Post creation hook for new procedural language */
  	InvokeObjectAccessHook(OAT_POST_CREATE,
  						   LanguageRelationId, myself.objectId, 0);
***************
*** 478,483 ****
--- 540,550 ----
  		if (!isnull)
  			result->tmplvalidator = TextDatumGetCString(datum);
  
+ 		datum = heap_getattr(tup, Anum_pg_pltemplate_tmplchecker,
+ 							 RelationGetDescr(rel), &isnull);
+ 		if (!isnull)
+ 			result->tmplchecker = TextDatumGetCString(datum);
+ 
  		datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary,
  							 RelationGetDescr(rel), &isnull);
  		if (!isnull)
*** ./src/backend/nodes/copyfuncs.c.orig	2011-12-09 14:29:07.861472089 +0100
--- ./src/backend/nodes/copyfuncs.c	2011-12-09 15:15:43.271637018 +0100
***************
*** 2880,2885 ****
--- 2880,2900 ----
  	return newnode;
  }
  
+ static CheckFunctionStmt *
+ _copyCheckFunctionStmt(CheckFunctionStmt *from)
+ {
+ 	CheckFunctionStmt *newnode = makeNode(CheckFunctionStmt);
+ 
+ 	COPY_NODE_FIELD(funcname);
+ 	COPY_NODE_FIELD(args);
+ 	COPY_STRING_FIELD(trgname);
+ 	COPY_NODE_FIELD(relation);
+ 	COPY_SCALAR_FIELD(is_function);
+ 	COPY_NODE_FIELD(options);
+ 
+ 	return newnode;
+ }
+ 
  static DoStmt *
  _copyDoStmt(const DoStmt *from)
  {
***************
*** 4165,4170 ****
--- 4180,4188 ----
  		case T_AlterFunctionStmt:
  			retval = _copyAlterFunctionStmt(from);
  			break;
+ 		case T_CheckFunctionStmt:
+ 			retval = _copyCheckFunctionStmt(from);
+ 			break;
  		case T_DoStmt:
  			retval = _copyDoStmt(from);
  			break;
*** ./src/backend/nodes/equalfuncs.c.orig	2011-12-09 14:29:07.861472089 +0100
--- ./src/backend/nodes/equalfuncs.c	2011-12-09 15:18:32.018205894 +0100
***************
*** 1292,1297 ****
--- 1292,1310 ----
  }
  
  static bool
+ _equalCheckFunctionStmt(CheckFunctionStmt *a, CheckFunctionStmt *b)
+ {
+ 	COMPARE_NODE_FIELD(funcname);
+ 	COMPARE_NODE_FIELD(args);
+ 	COMPARE_STRING_FIELD(trgname);
+ 	COMPARE_NODE_FIELD(relation);
+ 	COMPARE_SCALAR_FIELD(is_function);
+ 	COMPARE_NODE_FIELD(options);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalDoStmt(const DoStmt *a, const DoStmt *b)
  {
  	COMPARE_NODE_FIELD(args);
***************
*** 2708,2713 ****
--- 2721,2729 ----
  		case T_AlterFunctionStmt:
  			retval = _equalAlterFunctionStmt(a, b);
  			break;
+ 		case T_CheckFunctionStmt:
+ 			retval = _equalCheckFunctionStmt(a, b);
+ 			break;
  		case T_DoStmt:
  			retval = _equalDoStmt(a, b);
  			break;
*** ./src/backend/parser/gram.y.orig	2011-12-09 15:15:09.179913349 +0100
--- ./src/backend/parser/gram.y	2011-12-09 15:15:43.280636943 +0100
***************
*** 227,232 ****
--- 227,233 ----
  		DeallocateStmt PrepareStmt ExecuteStmt
  		DropOwnedStmt ReassignOwnedStmt
  		AlterTSConfigurationStmt AlterTSDictionaryStmt
+ 		CheckFunctionStmt
  
  %type <node>	select_no_parens select_with_parens select_clause
  				simple_select values_clause
***************
*** 276,282 ****
  
  %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
  				opt_class opt_inline_handler opt_validator validator_clause
! 				opt_collate
  
  %type <range>	qualified_name OptConstrFromTable
  
--- 277,283 ----
  
  %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
  				opt_class opt_inline_handler opt_validator validator_clause
! 				opt_collate opt_checker
  
  %type <range>	qualified_name OptConstrFromTable
  
***************
*** 463,469 ****
  %type <windef>	window_definition over_clause window_specification
  				opt_frame_clause frame_extent frame_bound
  %type <str>		opt_existing_window_name
! 
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
--- 464,471 ----
  %type <windef>	window_definition over_clause window_specification
  				opt_frame_clause frame_extent frame_bound
  %type <str>		opt_existing_window_name
! %type <defelt>	CheckFunctionOptElem
! %type <list>	CheckFunctionOpts
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
***************
*** 700,705 ****
--- 702,708 ----
  			| AlterUserSetStmt
  			| AlterUserStmt
  			| AnalyzeStmt
+ 			| CheckFunctionStmt
  			| CheckPointStmt
  			| ClosePortalStmt
  			| ClusterStmt
***************
*** 3174,3184 ****
  				n->plhandler = NIL;
  				n->plinline = NIL;
  				n->plvalidator = NIL;
  				n->pltrusted = false;
  				$$ = (Node *)n;
  			}
  			| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
! 			  HANDLER handler_name opt_inline_handler opt_validator
  			{
  				CreatePLangStmt *n = makeNode(CreatePLangStmt);
  				n->replace = $2;
--- 3177,3188 ----
  				n->plhandler = NIL;
  				n->plinline = NIL;
  				n->plvalidator = NIL;
+ 				n->plchecker = NIL;
  				n->pltrusted = false;
  				$$ = (Node *)n;
  			}
  			| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
! 			  HANDLER handler_name opt_inline_handler opt_validator opt_checker
  			{
  				CreatePLangStmt *n = makeNode(CreatePLangStmt);
  				n->replace = $2;
***************
*** 3186,3191 ****
--- 3190,3196 ----
  				n->plhandler = $8;
  				n->plinline = $9;
  				n->plvalidator = $10;
+ 				n->plchecker = $11;
  				n->pltrusted = $3;
  				$$ = (Node *)n;
  			}
***************
*** 3220,3225 ****
--- 3225,3235 ----
  			| /*EMPTY*/								{ $$ = NIL; }
  		;
  
+ opt_checker:
+ 			CHECK handler_name					{ $$ = $2; }
+ 			| /*EMPTY*/								{ $$ = NIL; }
+ 		;
+ 
  DropPLangStmt:
  			DROP opt_procedural LANGUAGE ColId_or_Sconst opt_drop_behavior
  				{
***************
*** 6250,6255 ****
--- 6260,6344 ----
  
  /*****************************************************************************
   *
+  *		CHECK FUNCTION funcname(args)
+  *		CHECK TRIGGER triggername ON table
+  *
+  *
+  *****************************************************************************/
+ 
+ 
+ CheckFunctionStmt:
+ 			CHECK FUNCTION func_name func_args
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = $3;
+ 					n->args = extractArgTypes($4);
+ 					n->trgname = NULL;
+ 					n->relation = NULL;
+ 					n->is_function = true;
+ 					n->options = NIL;
+ 					$$ = (Node *) n;
+ 				}
+ 			| CHECK TRIGGER name ON qualified_name
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = NULL;
+ 					n->args = NIL;
+ 					n->trgname = $3;
+ 					n->relation = $5;
+ 					n->is_function = false;
+ 					n->options = NIL;
+ 					$$ = (Node *) n;
+ 				}
+ 			| CHECK FUNCTION ALL
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = NULL;
+ 					n->args = NIL;
+ 					n->trgname = NULL;
+ 					n->relation = NULL;
+ 					n->is_function = true;
+ 					n->options = NIL;
+ 					$$ = (Node *) n;
+ 				}
+ 			| CHECK FUNCTION ALL CheckFunctionOpts
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = NULL;
+ 					n->args = NIL;
+ 					n->trgname = NULL;
+ 					n->relation = NULL;
+ 					n->is_function = true;
+ 					n->options = $4;
+ 					$$ = (Node *) n;
+ 				}
+ 		;
+ 
+ CheckFunctionOpts:
+ 			CheckFunctionOptElem					{ $$ = list_make1($1); }
+ 			| CheckFunctionOpts CheckFunctionOptElem	{ $$ = lappend($1, $2); }
+ 		;
+ 
+ CheckFunctionOptElem:
+ 			IN_P LANGUAGE ColId
+ 				{
+ 					$$ = makeDefElem("language",
+ 								    (Node *) makeString($3));
+ 				}
+ 			| IN_P SCHEMA ColId
+ 				{
+ 					$$ = makeDefElem("schema",
+ 								    (Node *) makeString($3));
+ 				}
+ 			| FOR ROLE ColId
+ 				{
+ 					$$ = makeDefElem("owner",
+ 								    (Node *) makeString($3));
+ 				}
+ 		;
+ 
+ /*****************************************************************************
+  *
   *		DO <anonymous code block> [ LANGUAGE language ]
   *
   * We use a DefElem list for future extensibility, and to allow flexibility
*** ./src/backend/tcop/utility.c.orig	2011-12-09 15:15:09.180913341 +0100
--- ./src/backend/tcop/utility.c	2011-12-09 15:15:43.314636666 +0100
***************
*** 882,887 ****
--- 882,891 ----
  			AlterFunction((AlterFunctionStmt *) parsetree);
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			CheckFunction((CheckFunctionStmt *) parsetree);
+ 			break;
+ 
  		case T_IndexStmt:		/* CREATE INDEX */
  			{
  				IndexStmt  *stmt = (IndexStmt *) parsetree;
***************
*** 2125,2130 ****
--- 2129,2141 ----
  			}
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			if (((CheckFunctionStmt *) parsetree)->is_function)
+ 				tag = "CHECK FUNCTION";
+ 			else
+ 				tag = "CHECK TRIGGER";
+ 			break;
+ 
  		default:
  			elog(WARNING, "unrecognized node type: %d",
  				 (int) nodeTag(parsetree));
***************
*** 2565,2570 ****
--- 2576,2585 ----
  			}
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			lev = LOGSTMT_ALL;
+ 			break;
+ 
  		default:
  			elog(WARNING, "unrecognized node type: %d",
  				 (int) nodeTag(parsetree));
*** ./src/bin/pg_dump/pg_dump.c.orig	2011-12-09 15:15:09.182913325 +0100
--- ./src/bin/pg_dump/pg_dump.c	2011-12-09 15:15:43.320636618 +0100
***************
*** 5326,5338 ****
  	int			i_lanplcallfoid;
  	int			i_laninline;
  	int			i_lanvalidator;
  	int			i_lanacl;
  	int			i_lanowner;
  
  	/* Make sure we are in proper schema */
  	selectSourceSchema("pg_catalog");
  
! 	if (g_fout->remoteVersion >= 90000)
  	{
  		/* pg_language has a laninline column */
  		appendPQExpBuffer(query, "SELECT tableoid, oid, "
--- 5326,5351 ----
  	int			i_lanplcallfoid;
  	int			i_laninline;
  	int			i_lanvalidator;
+ 	int			i_lanchecker;
  	int			i_lanacl;
  	int			i_lanowner;
  
  	/* Make sure we are in proper schema */
  	selectSourceSchema("pg_catalog");
  
! 	if (g_fout->remoteVersion >= 90200)
! 	{
! 		/* pg_language has a lanchecker column */
! 		appendPQExpBuffer(query, "SELECT tableoid, oid, "
! 						  "lanname, lanpltrusted, lanplcallfoid, "
! 						  "laninline, lanvalidator, lanchecker, lanacl, "
! 						  "(%s lanowner) AS lanowner "
! 						  "FROM pg_language "
! 						  "WHERE lanispl "
! 						  "ORDER BY oid",
! 						  username_subquery);
! 	}
! 	else if (g_fout->remoteVersion >= 90000)
  	{
  		/* pg_language has a laninline column */
  		appendPQExpBuffer(query, "SELECT tableoid, oid, "
***************
*** 5409,5414 ****
--- 5422,5428 ----
  	/* these may fail and return -1: */
  	i_laninline = PQfnumber(res, "laninline");
  	i_lanvalidator = PQfnumber(res, "lanvalidator");
+ 	i_lanchecker = PQfnumber(res, "lanchecker");
  	i_lanacl = PQfnumber(res, "lanacl");
  	i_lanowner = PQfnumber(res, "lanowner");
  
***************
*** 5422,5427 ****
--- 5436,5445 ----
  		planginfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_lanname));
  		planginfo[i].lanpltrusted = *(PQgetvalue(res, i, i_lanpltrusted)) == 't';
  		planginfo[i].lanplcallfoid = atooid(PQgetvalue(res, i, i_lanplcallfoid));
+ 		if (i_lanchecker >= 0)
+ 			planginfo[i].lanchecker = atooid(PQgetvalue(res, i, i_lanchecker));
+ 		else
+ 			planginfo[i].lanchecker = InvalidOid;
  		if (i_laninline >= 0)
  			planginfo[i].laninline = atooid(PQgetvalue(res, i, i_laninline));
  		else
***************
*** 8597,8602 ****
--- 8615,8621 ----
  	char	   *qlanname;
  	char	   *lanschema;
  	FuncInfo   *funcInfo;
+ 	FuncInfo   *checkerInfo = NULL;
  	FuncInfo   *inlineInfo = NULL;
  	FuncInfo   *validatorInfo = NULL;
  
***************
*** 8616,8621 ****
--- 8635,8647 ----
  	if (funcInfo != NULL && !funcInfo->dobj.dump)
  		funcInfo = NULL;		/* treat not-dumped same as not-found */
  
+ 	if (OidIsValid(plang->lanchecker))
+ 	{
+ 		checkerInfo = findFuncByOid(plang->lanchecker);
+ 		if (checkerInfo != NULL && !checkerInfo->dobj.dump)
+ 			checkerInfo = NULL;
+ 	}
+ 
  	if (OidIsValid(plang->laninline))
  	{
  		inlineInfo = findFuncByOid(plang->laninline);
***************
*** 8642,8647 ****
--- 8668,8674 ----
  	 * don't, this might not work terribly nicely.
  	 */
  	useParams = (funcInfo != NULL &&
+ 				 (checkerInfo != NULL || !OidIsValid(plang->lanchecker)) &&
  				 (inlineInfo != NULL || !OidIsValid(plang->laninline)) &&
  				 (validatorInfo != NULL || !OidIsValid(plang->lanvalidator)));
  
***************
*** 8697,8702 ****
--- 8724,8739 ----
  			appendPQExpBuffer(defqry, "%s",
  							  fmtId(validatorInfo->dobj.name));
  		}
+ 		if (OidIsValid(plang->lanchecker))
+ 		{
+ 			appendPQExpBuffer(defqry, " CHECK ");
+ 			/* Cope with possibility that checker is in different schema */
+ 			if (checkerInfo->dobj.namespace != funcInfo->dobj.namespace)
+ 				appendPQExpBuffer(defqry, "%s.",
+ 							   fmtId(checkerInfo->dobj.namespace->dobj.name));
+ 			appendPQExpBuffer(defqry, "%s",
+ 							  fmtId(checkerInfo->dobj.name));
+ 		}
  	}
  	else
  	{
*** ./src/bin/pg_dump/pg_dump.h.orig	2011-12-09 15:15:09.184913309 +0100
--- ./src/bin/pg_dump/pg_dump.h	2011-12-09 15:15:43.350636372 +0100
***************
*** 387,392 ****
--- 387,393 ----
  	Oid			lanplcallfoid;
  	Oid			laninline;
  	Oid			lanvalidator;
+ 	Oid			lanchecker;
  	char	   *lanacl;
  	char	   *lanowner;		/* name of owner, or empty string */
  } ProcLangInfo;
*** ./src/bin/psql/tab-complete.c.orig	2011-12-09 15:15:09.185913301 +0100
--- ./src/bin/psql/tab-complete.c	2011-12-09 15:15:43.352636356 +0100
***************
*** 1,4 ****
--- 1,5 ----
  /*
+  *
   * psql - the PostgreSQL interactive terminal
   *
   * Copyright (c) 2000-2011, PostgreSQL Global Development Group
***************
*** 727,733 ****
  #define prev6_wd  (previous_words[5])
  
  	static const char *const sql_commands[] = {
! 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
  		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
--- 728,734 ----
  #define prev6_wd  (previous_words[5])
  
  	static const char *const sql_commands[] = {
! 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECK", "CHECKPOINT", "CLOSE", "CLUSTER",
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
  		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
***************
*** 1524,1529 ****
--- 1525,1552 ----
  
  		COMPLETE_WITH_LIST(list_TRANS);
  	}
+ 
+ /* CHECK */
+ 	else if (pg_strcasecmp(prev_wd, "CHECK") == 0)
+ 	{
+ 		static const char *const list_CHECK[] =
+ 		{"FUNCTION", "TRIGGER", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_CHECK);
+ 	}
+ 	else if (pg_strcasecmp(prev3_wd, "CHECK") == 0 &&
+ 			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+ 	{
+ 		COMPLETE_WITH_CONST("ON");
+ 	}
+ 	else if (pg_strcasecmp(prev4_wd, "CHECK") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "ON") == 0)
+ 	{
+ 		completion_info_charp = prev2_wd;
+ 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+ 	}
+ 
  /* CLUSTER */
  
  	/*
*** ./src/include/catalog/pg_language.h.orig	2011-12-09 15:15:09.186913293 +0100
--- ./src/include/catalog/pg_language.h	2011-12-09 15:15:43.353636348 +0100
***************
*** 37,42 ****
--- 37,43 ----
  	Oid			lanplcallfoid;	/* Call handler for PL */
  	Oid			laninline;		/* Optional anonymous-block handler function */
  	Oid			lanvalidator;	/* Optional validation function */
+ 	Oid			lanchecker;	/* Optional checker function */
  	aclitem		lanacl[1];		/* Access privileges */
  } FormData_pg_language;
  
***************
*** 51,57 ****
   *		compiler constants for pg_language
   * ----------------
   */
! #define Natts_pg_language				8
  #define Anum_pg_language_lanname		1
  #define Anum_pg_language_lanowner		2
  #define Anum_pg_language_lanispl		3
--- 52,58 ----
   *		compiler constants for pg_language
   * ----------------
   */
! #define Natts_pg_language				9
  #define Anum_pg_language_lanname		1
  #define Anum_pg_language_lanowner		2
  #define Anum_pg_language_lanispl		3
***************
*** 59,78 ****
  #define Anum_pg_language_lanplcallfoid	5
  #define Anum_pg_language_laninline		6
  #define Anum_pg_language_lanvalidator	7
! #define Anum_pg_language_lanacl			8
  
  /* ----------------
   *		initial contents of pg_language
   * ----------------
   */
  
! DATA(insert OID = 12 ( "internal"	PGUID f f 0 0 2246 _null_ ));
  DESCR("built-in functions");
  #define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c"			PGUID f f 0 0 2247 _null_ ));
  DESCR("dynamically-loaded C functions");
  #define ClanguageId 13
! DATA(insert OID = 14 ( "sql"		PGUID f t 0 0 2248 _null_ ));
  DESCR("SQL-language functions");
  #define SQLlanguageId 14
  
--- 60,80 ----
  #define Anum_pg_language_lanplcallfoid	5
  #define Anum_pg_language_laninline		6
  #define Anum_pg_language_lanvalidator	7
! #define Anum_pg_language_lanchecker	8
! #define Anum_pg_language_lanacl			9
  
  /* ----------------
   *		initial contents of pg_language
   * ----------------
   */
  
! DATA(insert OID = 12 ( "internal"	PGUID f f 0 0 2246 0 _null_ ));
  DESCR("built-in functions");
  #define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c"			PGUID f f 0 0 2247 0 _null_ ));
  DESCR("dynamically-loaded C functions");
  #define ClanguageId 13
! DATA(insert OID = 14 ( "sql"		PGUID f t 0 0 2248 0 _null_ ));
  DESCR("SQL-language functions");
  #define SQLlanguageId 14
  
*** ./src/include/catalog/pg_pltemplate.h.orig	2011-12-09 15:15:09.188913277 +0100
--- ./src/include/catalog/pg_pltemplate.h	2011-12-09 15:15:43.354636340 +0100
***************
*** 36,41 ****
--- 36,42 ----
  	text		tmplhandler;	/* name of call handler function */
  	text		tmplinline;		/* name of anonymous-block handler, or NULL */
  	text		tmplvalidator;	/* name of validator function, or NULL */
+ 	text		tmplchecker;	/* name of checker function, or NULL */
  	text		tmpllibrary;	/* path of shared library */
  	aclitem		tmplacl[1];		/* access privileges for template */
  } FormData_pg_pltemplate;
***************
*** 51,65 ****
   *		compiler constants for pg_pltemplate
   * ----------------
   */
! #define Natts_pg_pltemplate					8
  #define Anum_pg_pltemplate_tmplname			1
  #define Anum_pg_pltemplate_tmpltrusted		2
  #define Anum_pg_pltemplate_tmpldbacreate	3
  #define Anum_pg_pltemplate_tmplhandler		4
  #define Anum_pg_pltemplate_tmplinline		5
  #define Anum_pg_pltemplate_tmplvalidator	6
! #define Anum_pg_pltemplate_tmpllibrary		7
! #define Anum_pg_pltemplate_tmplacl			8
  
  
  /* ----------------
--- 52,67 ----
   *		compiler constants for pg_pltemplate
   * ----------------
   */
! #define Natts_pg_pltemplate					9
  #define Anum_pg_pltemplate_tmplname			1
  #define Anum_pg_pltemplate_tmpltrusted		2
  #define Anum_pg_pltemplate_tmpldbacreate	3
  #define Anum_pg_pltemplate_tmplhandler		4
  #define Anum_pg_pltemplate_tmplinline		5
  #define Anum_pg_pltemplate_tmplvalidator	6
! #define Anum_pg_pltemplate_tmplchecker		7
! #define Anum_pg_pltemplate_tmpllibrary		8
! #define Anum_pg_pltemplate_tmplacl			9
  
  
  /* ----------------
***************
*** 67,79 ****
   * ----------------
   */
  
! DATA(insert ( "plpgsql"		t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl"		t t "pltcl_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu"		f f "pltclu_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl"		t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu"		f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu"	f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u"	f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u"	f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" "$libdir/plpython3" _null_ ));
  
  #endif   /* PG_PLTEMPLATE_H */
--- 69,81 ----
   * ----------------
   */
  
! DATA(insert ( "plpgsql"		t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "plpgsql_checker" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl"		t t "pltcl_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu"		f f "pltclu_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl"		t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu"		f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu"	f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u"	f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u"	f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" _null_ "$libdir/plpython3" _null_ ));
  
  #endif   /* PG_PLTEMPLATE_H */
*** ./src/include/commands/defrem.h.orig	2011-12-09 15:15:09.189913269 +0100
--- ./src/include/commands/defrem.h	2011-12-09 15:15:43.355636332 +0100
***************
*** 62,67 ****
--- 62,68 ----
  /* commands/functioncmds.c */
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
  extern void RemoveFunctionById(Oid funcOid);
+ extern void CheckFunction(CheckFunctionStmt *stmt);
  extern void SetFunctionReturnType(Oid funcOid, Oid newRetType);
  extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType);
  extern void RenameFunction(List *name, List *argtypes, const char *newname);
*** ./src/include/nodes/nodes.h.orig	2011-12-09 15:15:09.190913261 +0100
--- ./src/include/nodes/nodes.h	2011-12-09 15:15:43.356636323 +0100
***************
*** 291,296 ****
--- 291,297 ----
  	T_IndexStmt,
  	T_CreateFunctionStmt,
  	T_AlterFunctionStmt,
+ 	T_CheckFunctionStmt,
  	T_DoStmt,
  	T_RenameStmt,
  	T_RuleStmt,
*** ./src/include/nodes/parsenodes.h.orig	2011-12-09 15:15:09.192913245 +0100
--- ./src/include/nodes/parsenodes.h	2011-12-09 15:15:43.357636314 +0100
***************
*** 1734,1739 ****
--- 1734,1740 ----
  	List	   *plhandler;		/* PL call handler function (qual. name) */
  	List	   *plinline;		/* optional inline function (qual. name) */
  	List	   *plvalidator;	/* optional validator function (qual. name) */
+ 	List	   *plchecker;		/* optional checker function (qual. name) */
  	bool		pltrusted;		/* PL is trusted */
  } CreatePLangStmt;
  
***************
*** 2077,2082 ****
--- 2078,2098 ----
  } AlterFunctionStmt;
  
  /* ----------------------
+  *		Check {Function|Trigger} Statement
+  * ----------------------
+  */
+ typedef struct CheckFunctionStmt
+ {
+ 	NodeTag		type;
+ 	List	   *funcname;			/* qualified name of checked object */
+ 	List	   *args;			/* types of the arguments */
+ 	char	   *trgname;			/* trigger's name */
+ 	RangeVar	*relation;		/* trigger's relation */
+ 	bool	   is_function;		/* true for CHECK FUNCTION statement */
+ 	List	   *options;			/* other DefElem options */
+ } CheckFunctionStmt;
+ 
+ /* ----------------------
   *		DO Statement
   *
   * DoStmt is the raw parser output, InlineCodeBlock is the execution-time API
*** ./src/pl/plpgsql/src/pl_comp.c.orig	2011-12-09 15:15:09.193913237 +0100
--- ./src/pl/plpgsql/src/pl_comp.c	2011-12-09 15:15:43.359636297 +0100
***************
*** 115,121 ****
  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.
--- 115,120 ----
***************
*** 175,181 ****
  			 * 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
--- 174,180 ----
  			 * 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
***************
*** 2426,2432 ****
  }
  
  /*
!  * 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
--- 2425,2431 ----
  }
  
  /*
!  * 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,2446 ****
   * 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);
--- 2438,2445 ----
   * 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);
*** ./src/pl/plpgsql/src/pl_exec.c.orig	2011-12-09 15:15:09.197913205 +0100
--- ./src/pl/plpgsql/src/pl_exec.c	2011-12-09 15:15:43.363636265 +0100
***************
*** 210,216 ****
  static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
  						  PLpgSQL_expr *dynquery, List *params,
  						  const char *portalname, int cursorOptions);
! 
  
  /* ----------
   * plpgsql_exec_function	Called by the call handler for
--- 210,228 ----
  static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
  						  PLpgSQL_expr *dynquery, List *params,
  						  const char *portalname, int cursorOptions);
! static void check_row_or_rec(PLpgSQL_execstate *estate, PLpgSQL_row *row, PLpgSQL_rec *rec);
! static void check_expr(PLpgSQL_execstate *estate, PLpgSQL_expr *expr);
! static void assign_tupdesc_row_or_rec(PLpgSQL_execstate *estate,
! 					    PLpgSQL_row *row, PLpgSQL_rec *rec,
! 								    TupleDesc tupdesc);
! static TupleDesc expr_get_desc(PLpgSQL_execstate *estate,
! 						PLpgSQL_expr *query,
! 							bool use_element_type,
! 							bool expand_record,
! 								bool is_expression);
! static void var_init_to_null(PLpgSQL_execstate *estate, int varno);
! static void check_stmts(PLpgSQL_execstate *estate, List *stmts);
! static void check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt);
  
  /* ----------
   * plpgsql_exec_function	Called by the call handler for
***************
*** 6176,6178 ****
--- 6188,7240 ----
  
  	return portal;
  }
+ 
+ /*
+  * Following code ensures a CHECK FUNCTION and CHECK TRIGGER statements for PL/pgSQL
+  *
+  */
+ 
+ /*
+  * append a CONTEXT to error message
+  */
+ static void
+ check_error_callback(void *arg)
+ {
+ 	PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;
+ 
+ 	if (estate->err_stmt != NULL)
+ 	{
+ 		/* translator: last %s is a plpgsql statement type name */
+ 		errcontext("line %d at %s",
+ 				   estate->err_stmt->lineno,
+ 				   plpgsql_stmt_typename(estate->err_stmt));
+ 	}
+ }
+ 
+ /*
+  * Check function - it prepare variables and starts a prepare plan walker
+  *		called by function checker
+  */
+ void
+ plpgsql_check_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
+ {
+ 	PLpgSQL_execstate estate;
+ 	ErrorContextCallback plerrcontext;
+ 	int			i;
+ 
+ 	/* Setup error callback for ereport */
+ 	plerrcontext.callback = check_error_callback;
+ 	plerrcontext.arg = &estate;
+ 	plerrcontext.previous = error_context_stack;
+ 	error_context_stack = &plerrcontext;
+ 
+ 	/*
+ 	 * Setup the execution state - we would to reuse some exec routines
+ 	 * so we need a estate
+ 	 */
+ 	plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo);
+ 
+ 	/*
+ 	 * Make local execution copies of all the datums
+ 	 */
+ 	for (i = 0; i < estate.ndatums; i++)
+ 		estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
+ 
+ 	/*
+ 	 * Store the actual call argument values into the appropriate variables
+ 	 */
+ 	for (i = 0; i < func->fn_nargs; i++)
+ 	{
+ 		int			n = func->fn_argvarnos[i];
+ 
+ 		switch (estate.datums[n]->dtype)
+ 		{
+ 			case PLPGSQL_DTYPE_VAR:
+ 				{
+ 					var_init_to_null(&estate, n);
+ 				}
+ 				break;
+ 
+ 			case PLPGSQL_DTYPE_ROW:
+ 				{
+ 					PLpgSQL_row *row = (PLpgSQL_row *) estate.datums[n];
+ 
+ 					exec_move_row(&estate, NULL, row, NULL, NULL);
+ 				}
+ 				break;
+ 
+ 			default:
+ 				elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(&estate, (PLpgSQL_stmt *) func->action);
+ 
+ 	/* Cleanup temporary memory */
+ 	plpgsql_destroy_econtext(&estate);
+ 
+ 	/* Pop the error context stack */
+ 	error_context_stack = plerrcontext.previous;
+ }
+ 
+ /*
+  * Check trigger - prepare fake environments for testing trigger
+  *
+  */
+ void
+ plpgsql_check_trigger(PLpgSQL_function *func,
+ 					 TriggerData *trigdata)
+ {
+ 	PLpgSQL_execstate estate;
+ 	ErrorContextCallback plerrcontext;
+ 	PLpgSQL_rec *rec_new,
+ 			   *rec_old;
+ 	int			i;
+ 
+ 	/* Setup error callback for ereport */
+ 	plerrcontext.callback = check_error_callback;
+ 	plerrcontext.arg = &estate;
+ 	plerrcontext.previous = error_context_stack;
+ 	error_context_stack = &plerrcontext;
+ 
+ 	/*
+ 	 * Setup the execution state - we would to reuse some exec routines
+ 	 * so we need a estate
+ 	 */
+ 	plpgsql_estate_setup(&estate, func, NULL);
+ 
+ 	/*
+ 	 * Make local execution copies of all the datums
+ 	 */
+ 	for (i = 0; i < estate.ndatums; i++)
+ 		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 *) (estate.datums[func->new_varno]);
+ 	rec_new->freetup = false;
+ 	rec_new->freetupdesc = false;
+ 	assign_tupdesc_row_or_rec(&estate, NULL, rec_new, trigdata->tg_relation->rd_att);
+ 
+ 	rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
+ 	rec_old->freetup = false;
+ 	rec_old->freetupdesc = false;
+ 	assign_tupdesc_row_or_rec(&estate, NULL, rec_old, trigdata->tg_relation->rd_att);
+ 
+ 	/*
+ 	 * Assign the special tg_ variables
+ 	 */
+ 	var_init_to_null(&estate, func->tg_op_varno);
+ 	var_init_to_null(&estate, func->tg_name_varno);
+ 	var_init_to_null(&estate, func->tg_when_varno);
+ 	var_init_to_null(&estate, func->tg_level_varno);
+ 	var_init_to_null(&estate, func->tg_relid_varno);
+ 	var_init_to_null(&estate, func->tg_relname_varno);
+ 	var_init_to_null(&estate, func->tg_table_name_varno);
+ 	var_init_to_null(&estate, func->tg_table_schema_varno);
+ 	var_init_to_null(&estate, func->tg_nargs_varno);
+ 	var_init_to_null(&estate, func->tg_argv_varno);
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(&estate, (PLpgSQL_stmt *) func->action);
+ 
+ 	/* Cleanup temporary memory */
+ 	plpgsql_destroy_econtext(&estate);
+ 
+ 	/* Pop the error context stack */
+ 	error_context_stack = plerrcontext.previous;
+ }
+ 
+ /*
+  * Verify lvalue
+  *    It doesn't repeat a checks that are done.
+  *  Checks a subscript expressions, verify a validity of record's fields
+  */
+ static void
+ check_target(PLpgSQL_execstate *estate, int varno)
+ {
+ 	PLpgSQL_datum *target = estate->datums[varno];
+ 
+ 	switch (target->dtype)
+ 	{
+ 		case PLPGSQL_DTYPE_VAR:
+ 		case PLPGSQL_DTYPE_REC:
+ 			break;
+ 
+ 		case PLPGSQL_DTYPE_ROW:
+ 			check_row_or_rec(estate, (PLpgSQL_row *) target, NULL);
+ 			break;
+ 
+ 		case PLPGSQL_DTYPE_RECFIELD:
+ 			{
+ 				PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target;
+ 				PLpgSQL_rec *rec;
+ 				int			fno;
+ 
+ 				rec = (PLpgSQL_rec *) (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)));
+ 
+ 					check_expr(estate, arrayelem->subscript);
+ 
+ 					target = 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(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_execstate *estate, 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(estate, row->varnos[fnum]);
+ 		}
+ 	}
+ }
+ 
+ /*
+  * Generate a prepared plan - this is simplyfied copy from pl_exec.c
+  *   Is not necessary to check simple plan
+  */
+ static void
+ prepare_expr(PLpgSQL_execstate *estate,
+ 				  PLpgSQL_expr *expr, int cursorOptions)
+ {
+ 	SPIPlanPtr	plan;
+ 
+ 	/* leave when there are not expression */
+ 	if (expr == NULL)
+ 		return;
+ 
+ 	/* leave when plan is created */
+ 	if (expr->plan != NULL)
+ 		return;
+ 
+ 	/*
+ 	 * 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 = 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")));
+ 			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.")));
+ 			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_execstate *estate, PLpgSQL_expr *expr)
+ {
+ 	TupleDesc tupdesc;
+ 
+ 	if (expr != NULL)
+ 	{
+ 		prepare_expr(estate, expr, 0);
+ 		tupdesc = expr_get_desc(estate, expr, false, false, true);
+ 		ReleaseTupleDesc(tupdesc);
+ 	}
+ }
+ 
+ /*
+  * 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_execstate *estate,
+ 					    PLpgSQL_row *row, PLpgSQL_rec *rec,
+ 								    TupleDesc tupdesc)
+ {
+ 	bool	   *nulls;
+ 	HeapTuple  tup;
+ 
+ 	if (tupdesc == NULL)
+ 		elog(ERROR, "tuple descriptor is empty");
+ 
+ 	/*
+ 	 * row variable has assigned TupleDesc already, so don't be processed
+ 	 * here
+ 	 */
+ 	if (rec != NULL)
+ 	{
+ 		PLpgSQL_rec *target = (PLpgSQL_rec *)(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_execstate *estate, int varno, TupleDesc tupdesc)
+ {
+ 	PLpgSQL_datum *target = estate->datums[varno];
+ 
+ 	if (target->dtype == PLPGSQL_DTYPE_REC)
+ 		assign_tupdesc_row_or_rec(estate, NULL, (PLpgSQL_rec *) target, tupdesc);
+ }
+ 
+ /*
+  * Returns a tuple descriptor based on existing plan
+  */
+ static TupleDesc 
+ expr_get_desc(PLpgSQL_execstate *estate,
+ 						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);
+ 
+ 		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 (tupdesc->natts != 1)
+ 			ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 				 errmsg_plural("query \"%s\" returned %d column",
+ 							   "query \"%s\" returned %d columns",
+ 							   tupdesc->natts,
+ 							   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))));
+ 
+ 		/* 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
+ 			elog(ERROR, "cannot to identify real type for record type variable");
+ 	}
+ 
+ 	if (is_expression && tupdesc->natts != 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 			  errmsg_plural("query \"%s\" returned %d column",
+ 				   "query \"%s\" returned %d columns",
+ 				   tupdesc->natts,
+ 				   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(..)
+ 	 */
+ 	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)
+ 						elog(ERROR, "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
+  */
+ static void
+ check_stmts(PLpgSQL_execstate *estate, List *stmts)
+ {
+ 	ListCell *lc;
+ 
+ 	foreach(lc, stmts)
+ 	{
+ 		check_stmt(estate, (PLpgSQL_stmt *) lfirst(lc));
+ 	}
+ }
+ 
+ /*
+  * walk over all statements
+  */
+ static void
+ check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
+ {
+ 	TupleDesc	tupdesc = NULL;
+ 	PLpgSQL_function *func;
+ 	ListCell *l;
+ 
+ 	if (stmt == NULL)
+ 		return;
+ 
+ 	estate->err_stmt = stmt;
+ 	func = estate->func;
+ 
+ 	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++)
+ 				{
+ 					d = func->datums[stmt_block->initvarnos[i]];
+ 
+ 					if (d->dtype == PLPGSQL_DTYPE_VAR)
+ 					{
+ 						PLpgSQL_var *var = (PLpgSQL_var *) d;
+ 
+ 						check_expr(estate, var->default_val);
+ 					}
+ 				}
+ 
+ 				check_stmts(estate, stmt_block->body);
+ 				
+ 				if (stmt_block->exceptions)
+ 				{
+ 					foreach(l, stmt_block->exceptions->exc_list)
+ 					{
+ 						check_stmts(estate, ((PLpgSQL_exception *) lfirst(l))->action);
+ 					}
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_ASSIGN:
+ 			{
+ 				PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt;
+ 
+ 				/* prepare plan if desn't exist yet */
+ 				prepare_expr(estate, stmt_assign->expr, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_assign->expr,
+ 										false,		/* no element type */
+ 										true,		/* expand record */
+ 										true);		/* is expression */
+ 
+ 				/* check target, ensure target can get a result */
+ 				check_target(estate, stmt_assign->varno);
+ 
+ 				/* assign a tupdesc to record variable */
+ 				assign_tupdesc_dno(estate, stmt_assign->varno, tupdesc);
+ 				ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_IF:
+ 			{
+ 				PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt;
+ 				ListCell *l;
+ 
+ 				check_expr(estate, stmt_if->cond);
+ 
+ 				check_stmts(estate, stmt_if->then_body);
+ 
+ 				foreach(l, stmt_if->elsif_list)
+ 				{
+ 					PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l);
+ 
+ 					check_expr(estate, elif->cond);
+ 					check_stmts(estate, elif->stmts);
+ 				}
+ 
+ 				check_stmts(estate, 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 *) estate->datums[stmt_case->t_varno];
+ 
+ 					/* we need to set hidden variable type */
+ 					prepare_expr(estate, stmt_case->t_expr, 0);
+ 
+ 					tupdesc = expr_get_desc(estate,
+ 									stmt_case->t_expr,
+ 										false,		/* no element type */
+ 										false,		/* 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,
+ 												   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(estate, cwt->expr);
+ 					check_stmts(estate, cwt->stmts);
+ 				}
+ 
+ 				check_stmts(estate, stmt_case->else_stmts);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_LOOP:
+ 			check_stmts(estate, ((PLpgSQL_stmt_loop *) stmt)->body);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_WHILE:
+ 			{
+ 				PLpgSQL_stmt_while *stmt_while = (PLpgSQL_stmt_while *) stmt;
+ 
+ 				check_expr(estate, stmt_while->cond);
+ 				check_stmts(estate, stmt_while->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORI:
+ 			{
+ 				PLpgSQL_stmt_fori *stmt_fori = (PLpgSQL_stmt_fori *) stmt;
+ 
+ 				check_expr(estate, stmt_fori->lower);
+ 				check_expr(estate, stmt_fori->upper);
+ 				check_expr(estate, stmt_fori->step);
+ 
+ 				check_stmts(estate, stmt_fori->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORS:
+ 			{
+ 				PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt;
+ 
+ 				/* we need to set hidden variable type */
+ 				prepare_expr(estate, stmt_fors->query, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_fors->query,
+ 									false,		/* no element type */
+ 									false,		/* expand record */
+ 									false);		/* is expression */
+ 
+ 				check_row_or_rec(estate, stmt_fors->row, stmt_fors->rec);
+ 				assign_tupdesc_row_or_rec(estate, stmt_fors->row, stmt_fors->rec, tupdesc);
+ 
+ 				check_stmts(estate, stmt_fors->body);
+ 				ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORC:
+ 			{
+ 				PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt;
+ 				PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_forc->curvar];
+ 
+ 				prepare_expr(estate, stmt_forc->argquery, 0);
+ 
+ 				if (var->cursor_explicit_expr != NULL)
+ 				{
+ 					prepare_expr(estate, var->cursor_explicit_expr,
+ 								    var->cursor_options);
+ 
+ 					tupdesc = expr_get_desc(estate,
+ 									var->cursor_explicit_expr,
+ 										false,		/* no element type */
+ 										false,		/* expand record */
+ 										false);		/* is expression */
+ 
+ 					check_row_or_rec(estate, stmt_forc->row, stmt_forc->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_forc->row, stmt_forc->rec, tupdesc);
+ 				}
+ 
+ 				check_stmts(estate, stmt_forc->body);
+ 				if (tupdesc != NULL)
+ 					ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNFORS:
+ 			{
+ 				PLpgSQL_stmt_dynfors * stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt;
+ 
+ 				if (stmt_dynfors->rec != NULL)
+ 					elog(ERROR, "cannot determinate a result of dynamic SQL");
+ 
+ 				check_expr(estate, stmt_dynfors->query);
+ 
+ 				foreach(l, stmt_dynfors->params)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				check_stmts(estate, stmt_dynfors->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FOREACH_A:
+ 			{
+ 				PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt;
+ 
+ 				prepare_expr(estate, stmt_foreach_a->expr, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_foreach_a->expr,
+ 									true,		/* no element type */
+ 									false,		/* expand record */
+ 									true);		/* is expression */
+ 
+ 				check_target(estate, stmt_foreach_a->varno);
+ 				assign_tupdesc_dno(estate, stmt_foreach_a->varno, tupdesc);
+ 				ReleaseTupleDesc(tupdesc);
+ 
+ 				check_stmts(estate, stmt_foreach_a->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_EXIT:
+ 			check_expr(estate, ((PLpgSQL_stmt_exit *) stmt)->cond);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_PERFORM:
+ 			prepare_expr(estate, ((PLpgSQL_stmt_perform *) stmt)->expr, 0);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN:
+ 			check_expr(estate, ((PLpgSQL_stmt_return *) stmt)->expr);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN_NEXT:
+ 			check_expr(estate, ((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(estate, stmt_rq->dynquery);
+ 				prepare_expr(estate, stmt_rq->query, 0);
+ 
+ 				foreach(l, stmt_rq->params)
+ 				{
+ 					check_expr(estate, (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(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				foreach(l, stmt_raise->options)
+ 				{
+ 					PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(l);
+ 
+ 					check_expr(estate, 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;
+ 
+ 				prepare_expr(estate, stmt_execsql->sqlstmt, 0);
+ 				if (stmt_execsql->into)
+ 				{
+ 					tupdesc = expr_get_desc(estate,
+ 								stmt_execsql->sqlstmt,
+ 											false,		/* no element type */
+ 											false,		/* expand record */
+ 											false);		/* is expression */
+ 
+ 					/* check target, ensure target can get a result */
+ 					check_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec, tupdesc);
+ 					ReleaseTupleDesc(tupdesc);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNEXECUTE:
+ 			{
+ 				PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt;
+ 
+ 				check_expr(estate, stmt_dynexecute->query);
+ 
+ 				foreach(l, stmt_dynexecute->params)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				if (stmt_dynexecute->into)
+ 				{
+ 					if (stmt_dynexecute->rec != NULL)
+ 						elog(ERROR, "cannot determinate a result of dynamic SQL");
+ 
+ 					check_row_or_rec(estate, stmt_dynexecute->row, stmt_dynexecute->rec);
+ 				}
+ 			}
+ 			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)
+ 					prepare_expr(estate, var->cursor_explicit_expr,
+ 								   var->cursor_options);
+ 
+ 				prepare_expr(estate, stmt_open->query, 0);
+ 				prepare_expr(estate, stmt_open->argquery, 0);
+ 				check_expr(estate, stmt_open->dynquery);
+ 
+ 				foreach(l, stmt_open->params)
+ 				{
+ 					check_expr(estate, (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(estate, diag_item->target);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FETCH:
+ 			{
+ 				PLpgSQL_stmt_fetch *stmt_fetch = (PLpgSQL_stmt_fetch *) stmt;
+ 				PLpgSQL_var *var = (PLpgSQL_var *)(estate->datums[stmt_fetch->curvar]);
+ 
+ 				if (var != NULL && var->cursor_explicit_expr != NULL)
+ 				{
+ 					prepare_expr(estate, var->cursor_explicit_expr, 
+ 									    var->cursor_options);
+ 					tupdesc = expr_get_desc(estate,
+ 								    var->cursor_explicit_expr,
+ 											false,		/* no element type */
+ 											false,		/* expand record */
+ 											false);		/* is expression */
+ 					check_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec, tupdesc);
+ 					ReleaseTupleDesc(tupdesc);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_CLOSE:
+ 			break;
+ 
+ 		default:
+ 			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
+ 			return; /* be compiler quite */
+ 	}
+ }
+ 
+ /*
+  * Initialize variable to NULL
+  */
+ static void
+ var_init_to_null(PLpgSQL_execstate *estate, int varno)
+ {
+ 	PLpgSQL_var *var = (PLpgSQL_var *) estate->datums[varno];
+ 	var->value = (Datum) 0;
+ 	var->isnull = true;
+ 	var->freeval = false;
+ }
*** ./src/pl/plpgsql/src/pl_handler.c.orig	2011-12-09 15:15:09.198913197 +0100
--- ./src/pl/plpgsql/src/pl_handler.c	2011-12-09 15:15:43.364636257 +0100
***************
*** 312,314 ****
--- 312,452 ----
  
  	PG_RETURN_VOID();
  }
+ 
+ /* ----------
+  * plpgsql_checker
+  *
+  * This function attempts to check a embeded SQL inside a PL/pgSQL function at
+  * CHECK FUNCTION time. It should to have one or two parameters. Second
+  * parameter is a relation (used when function is trigger).
+  * ----------
+  */
+ PG_FUNCTION_INFO_V1(plpgsql_checker);
+ 
+ Datum
+ plpgsql_checker(PG_FUNCTION_ARGS)
+ {
+ 	Oid			funcoid = PG_GETARG_OID(0);
+ 	Oid			relid = PG_GETARG_OID(1);
+ 	HeapTuple	tuple;
+ 	FunctionCallInfoData fake_fcinfo;
+ 	FmgrInfo	flinfo;
+ 	TriggerData trigdata;
+ 	int			rc;
+ 	PLpgSQL_function *function;
+ 	PLpgSQL_execstate *cur_estate;
+ 
+ 	Form_pg_proc proc;
+ 	char		functyptype;
+ 	bool	   istrigger = false;
+ 
+ 	/* we don't need to repair a check done by validator */
+ 
+ 	tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
+ 	if (!HeapTupleIsValid(tuple))
+ 		elog(ERROR, "cache lookup failed for function %u", funcoid);
+ 	proc = (Form_pg_proc) GETSTRUCT(tuple);
+ 
+ 	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))
+ 		{
+ 			istrigger = true;
+ 			if (!OidIsValid(relid))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("PL/pgSQL trigger functions cannot be checked directly"),
+ 						 errhint("use CHECK TRIGGER statement instead")));
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Connect to SPI manager
+ 	 */
+ 	if ((rc = SPI_connect()) != SPI_OK_CONNECT)
+ 			elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
+ 
+ 	/*
+ 	 * Set up a fake fcinfo with just enough info to satisfy
+ 	 * plpgsql_compile().
+ 	 *
+ 	 * there should be a different real argtypes for polymorphic params
+ 	 */
+ 	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;
+ 		trigdata.tg_relation = relation_open(relid, AccessShareLock);
+ 		fake_fcinfo.context = (Node *) &trigdata;
+ 	}
+ 
+ 	/* Get a compiled function */
+ 	function = plpgsql_compile(&fake_fcinfo, false);
+ 
+ 	/* 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 prepare plans */
+ 	PG_TRY();
+ 	{
+ 		if (!istrigger)
+ 			plpgsql_check_function(function, &fake_fcinfo);
+ 		else
+ 			plpgsql_check_trigger(function, &trigdata);
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		if (istrigger)
+ 			relation_close(trigdata.tg_relation, AccessShareLock);
+ 
+ 		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);
+ 
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	if (istrigger)
+ 		relation_close(trigdata.tg_relation, AccessShareLock);
+ 
+ 	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);
+ 
+ 	/*
+ 	 * Disconnect from SPI manager
+ 	 */
+ 	if ((rc = SPI_finish()) != SPI_OK_FINISH)
+ 			elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
+ 
+ 	ReleaseSysCache(tuple);
+ 
+ 	PG_RETURN_VOID();
+ }
*** ./src/pl/plpgsql/src/plpgsql.h.orig	2011-12-09 15:15:09.199913189 +0100
--- ./src/pl/plpgsql/src/plpgsql.h	2011-12-09 15:15:43.365636249 +0100
***************
*** 902,907 ****
--- 902,908 ----
  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,916 ****
--- 912,918 ----
  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_checker(PG_FUNCTION_ARGS);
  
  /* ----------
   * Functions in pl_exec.c
***************
*** 928,933 ****
--- 930,939 ----
  extern void exec_get_datum_type_info(PLpgSQL_execstate *estate,
  						 PLpgSQL_datum *datum,
  						 Oid *typeid, int32 *typmod, Oid *collation);
+ extern void plpgsql_check_function(PLpgSQL_function *func,
+ 					 FunctionCallInfo fcinfo);
+ extern void plpgsql_check_trigger(PLpgSQL_function *func,
+ 					 TriggerData *trigdata);
  
  /* ----------
   * Functions for namespace handling in pl_funcs.c
*** ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql.orig	2011-12-09 15:15:09.201913173 +0100
--- ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql	2011-12-09 15:15:43.366636241 +0100
***************
*** 5,7 ****
--- 5,8 ----
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_call_handler();
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_inline_handler(internal);
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_validator(oid);
+ ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_checker(oid, regclass);
*** ./src/test/regress/expected/plpgsql.out.orig	2011-12-09 15:15:09.202913165 +0100
--- ./src/test/regress/expected/plpgsql.out	2011-12-09 15:15:43.368636225 +0100
***************
*** 302,307 ****
--- 302,310 ----
  ' 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
+ check trigger tg_hslot_biu on HSlot;
+ NOTICE:  checked function "tg_hslot_biu()"
  -- ************************************************************
  -- * BEFORE DELETE on HSlot
  -- *	- prevent from manual manipulation
***************
*** 635,640 ****
--- 638,646 ----
      raise exception ''illegal backlink beginning with %'', mytype;
  end;
  ' language plpgsql;
+ -- check function should not fail
+ check function tg_backlink_set(bpchar, bpchar);
+ NOTICE:  checked function "tg_backlink_set(character,character)"
  -- ************************************************************
  -- * Support function to clear out the backlink field if
  -- * it still points to specific slot
***************
*** 2802,2807 ****
--- 2808,2842 ----
   
  (1 row)
  
+ -- check function should not fail
+ check function for_vect();
+ NOTICE:  checked function "for_vect()"
+ -- 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
***************
*** 3283,3288 ****
--- 3318,3326 ----
    return;
  end;
  $$ language plpgsql;
+ -- check function should not fail
+ check function forc01();
+ NOTICE:  checked function "forc01()"
  select forc01();
  NOTICE:  5 from c
  NOTICE:  6 from c
***************
*** 3716,3721 ****
--- 3754,3762 ----
    end case;
  end;
  $$ language plpgsql immutable;
+ -- check function should not fail
+ check function case_test(bigint);
+ NOTICE:  checked function "case_test(bigint)"
  select case_test(1);
   case_test 
  -----------
***************
*** 4571,4573 ****
--- 4612,4956 ----
  CONTEXT:  PL/pgSQL function "testoa" line 5 at assignment
  drop function arrayassign1();
  drop function testoa(x1 int, x2 int, x3 int);
+ --
+ -- check function statement tests
+ --
+ create table t1(a int, b int);
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+ end;
+ $$ language plpgsql;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ LINE 1: update t1 set c = 30
+                       ^
+ DETAIL:  column "c" of relation "t1" does not exist
+ QUERY:  update t1 set c = 30
+ CONTEXT:  line 4 at SQL statement
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  record "r" has no field "c"
+ CONTEXT:  SQL statement "SELECT r.c"
+ line 6 at RAISE
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  record "r" has no field "c"
+ CONTEXT:  SQL statement "SELECT r.c"
+ line 6 at RAISE
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  record "r" has no field "c"
+ CONTEXT:  line 6 at assignment
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ LINE 1: SELECT a + b
+                ^
+ DETAIL:  column "a" does not exist
+ QUERY:  SELECT a + b
+ CONTEXT:  line 5 at assignment
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  too many parameters specified for RAISE
+ CONTEXT:  line 4 at RAISE
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  too few parameters specified for RAISE
+ CONTEXT:  line 4 at RAISE
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ LINE 1: SELECT c+10
+                ^
+ DETAIL:  column "c" does not exist
+ QUERY:  SELECT c+10
+ CONTEXT:  line 5 at assignment
+ 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;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  subscripted object is not an array
+ CONTEXT:  line 5 at assignment
+ 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)
+ 
+ check function f1();
+ WARNING:  error in function "f1()"
+ DETAIL:  record "_exception" has no field "hint"
+ CONTEXT:  line 7 at GET DIAGNOSTICS
+ 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);
+ check trigger t1_f1 on t1;
+ WARNING:  error in function "f1_trg()"
+ DETAIL:  record "new" has no field "c"
+ CONTEXT:  SQL statement "SELECT new.c"
+ line 5 at RAISE
+ 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
+ check trigger t1_f1 on t1;
+ WARNING:  error in function "f1_trg()"
+ DETAIL:  record "new" has no field "c"
+ CONTEXT:  line 5 at assignment
+ -- 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
+ check trigger t1_f1 on t1;
+ NOTICE:  checked function "f1_trg()"
+ -- ok
+ insert into t1 values(6,30);
+ drop table t1;
+ drop type _exception_type;
+ drop function f1_trg();
*** ./src/test/regress/sql/plpgsql.sql.orig	2011-12-09 15:15:09.204913149 +0100
--- ./src/test/regress/sql/plpgsql.sql	2011-12-09 15:15:43.371636201 +0100
***************
*** 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
+ check trigger tg_hslot_biu on HSlot;
  
  -- ************************************************************
  -- * BEFORE DELETE on HSlot
***************
*** 747,752 ****
--- 749,757 ----
  end;
  ' language plpgsql;
  
+ -- check function should not fail
+ check function tg_backlink_set(bpchar, bpchar);
+ 
  
  -- ************************************************************
  -- * Support function to clear out the backlink field if
***************
*** 2335,2340 ****
--- 2340,2352 ----
  
  select for_vect();
  
+ -- check function should not fail
+ 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
***************
*** 2714,2719 ****
--- 2726,2734 ----
  end;
  $$ language plpgsql;
  
+ -- check function should not fail
+ check function forc01();
+ 
  select forc01();
  
  -- try updating the cursor's current row
***************
*** 3048,3053 ****
--- 3063,3071 ----
  end;
  $$ language plpgsql immutable;
  
+ -- check function should not fail
+ check function case_test(bigint);
+ 
  select case_test(1);
  select case_test(2);
  select case_test(3);
***************
*** 3600,3602 ****
--- 3618,3862 ----
  
  drop function arrayassign1();
  drop function testoa(x1 int, x2 int, x3 int);
+ 
+ --
+ -- check function statement tests
+ --
+ 
+ create table t1(a int, b int);
+ 
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+ end;
+ $$ language plpgsql;
+ 
+ select f1();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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;
+ 
+ select f1();
+ 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();
+ 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);
+ check trigger t1_f1 on 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
+ check trigger t1_f1 on 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
+ check trigger t1_f1 on t1;
+ 
+ -- ok
+ insert into t1 values(6,30);
+ 
+ drop table t1;
+ drop type _exception_type;
+ 
+ drop function f1_trg();
+ 
#37Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Pavel Stehule (#36)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Pavel Stehule wrote:

there is fixed version

Here is my attempt at a doc patch.

Could you add it to your patch so that all is in a single patch?

Yours,
Laurenz Albe

Attachments:

check_function_docs.patchapplication/octet-stream; name=check_function_docs.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
new file mode 100644
index b8cc16f..a436aeb
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 3652,3657 ****
--- 3652,3669 ----
       </row>
  
       <row>
+       <entry><structfield>lanchecker</structfield></entry>
+       <entry><type>oid</type></entry>
+       <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+       <entry>
+        This references a correctness check function that is used by
+        <command>CHECK FUNCTION</> and <command>CHECK TRIGGER</> for in-depth
+        checks of function bodies.
+        Zero if no such function is provided.
+       </entry>
+      </row>
+ 
+      <row>
        <entry><structfield>lanacl</structfield></entry>
        <entry><type>aclitem[]</type></entry>
        <entry></entry>
***************
*** 4262,4267 ****
--- 4274,4285 ----
       </row>
  
       <row>
+       <entry><structfield>tmplchecker</structfield></entry>
+       <entry><type>text</type></entry>
+       <entry>Name of correctness check function, or null if none</entry>
+      </row>
+ 
+      <row>
        <entry><structfield>tmpllibrary</structfield></entry>
        <entry><type>text</type></entry>
        <entry>Path of shared library that implements language</entry>
diff --git a/doc/src/sgml/plhandler.sgml b/doc/src/sgml/plhandler.sgml
new file mode 100644
index 54b88ef..21d043b
*** a/doc/src/sgml/plhandler.sgml
--- b/doc/src/sgml/plhandler.sgml
*************** CREATE LANGUAGE plsample
*** 161,170 ****
      Although providing a call handler is sufficient to create a minimal
      procedural language, there are two other functions that can optionally
      be provided to make the language more convenient to use.  These
!     are a <firstterm>validator</firstterm> and an
!     <firstterm>inline handler</firstterm>.  A validator can be provided
!     to allow language-specific checking to be done during
!     <xref linkend="sql-createfunction">.
      An inline handler can be provided to allow the language to support
      anonymous code blocks executed via the <xref linkend="sql-do"> command.
     </para>
--- 161,173 ----
      Although providing a call handler is sufficient to create a minimal
      procedural language, there are two other functions that can optionally
      be provided to make the language more convenient to use.  These
!     are a <firstterm>validator</firstterm>, a <firstterm>checker</firstterm>
!     and an <firstterm>inline handler</firstterm>.
!     A validator can be provided to allow language-specific checking to be
!     done during <xref linkend="sql-createfunction">.
!     A checker performs in-depth checks of function bodies via the
!     commands <xref linkend="sql-checkfunction"> and
!     <xref linkend="sql-checktrigger">.
      An inline handler can be provided to allow the language to support
      anonymous code blocks executed via the <xref linkend="sql-do"> command.
     </para>
*************** CREATE LANGUAGE plsample
*** 203,208 ****
--- 206,232 ----
     </para>
  
     <para>
+     If a checker is provided by a procedural language, it must be declared
+     as a function taking two arguments, one of type <type>oid</> and one of
+     type <type>regclass</>.  The checker's result is ignored, so it is customarily
+     declared to return <type>void</>.
+     The checker will be called whenever a function is checked with
+     <command>CHECK FUNCTION</> or <command>CHECK TRIGGER</>.
+     The passed-in OID is the OID of the function's <classname>pg_proc</>
+     row.  The passed-in <type>regclass</> is the OID of the
+     <classname>pg_class</> row of the table on which the trigger is defined,
+     or <symbol>InvalidOid</symbol> in the case of <command>CHECK FUNCTION</>.
+     The checker must fetch these rows in the usual way, and do
+     whatever checking is appropriate.
+     Typical checks include verifying that all referenced database objects
+     actually exist or style checks like verifying that all declared
+     variables are actually used.  If the checker finds the function to be okay,
+     it should just return.  If it finds a problem, it should report a
+     <literal>WARNING</> via the normal <function>ereport()</> error reporting
+     mechanism.
+    </para>
+ 
+    <para>
      If an inline handler is provided by a procedural language, it
      must be declared as a function taking a single parameter of type
      <type>internal</>.  The inline handler's result is ignored, so it is
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
new file mode 100644
index 382d297..ef75780
*** a/doc/src/sgml/ref/allfiles.sgml
--- b/doc/src/sgml/ref/allfiles.sgml
*************** Complete list of usable sgml source file
*** 40,45 ****
--- 40,47 ----
  <!ENTITY alterView          SYSTEM "alter_view.sgml">
  <!ENTITY analyze            SYSTEM "analyze.sgml">
  <!ENTITY begin              SYSTEM "begin.sgml">
+ <!ENTITY checkFunction      SYSTEM "check_function.sgml">
+ <!ENTITY checkTrigger       SYSTEM "check_trigger.sgml">
  <!ENTITY checkpoint         SYSTEM "checkpoint.sgml">
  <!ENTITY close              SYSTEM "close.sgml">
  <!ENTITY cluster            SYSTEM "cluster.sgml">
diff --git a/doc/src/sgml/ref/check_function.sgml b/doc/src/sgml/ref/check_function.sgml
new file mode 100644
index ...6549846
*** a/doc/src/sgml/ref/check_function.sgml
--- b/doc/src/sgml/ref/check_function.sgml
***************
*** 0 ****
--- 1,138 ----
+ <!--
+ doc/src/sgml/ref/check_function.sgml
+ -->
+ 
+ <refentry id="SQL-CHECKFUNCTION">
+  <refmeta>
+   <refentrytitle>CHECK FUNCTION</refentrytitle>
+   <manvolnum>7</manvolnum>
+   <refmiscinfo>SQL - Language Statements</refmiscinfo>
+  </refmeta>
+ 
+  <refnamediv>
+   <refname>CHECK FUNCTION</refname>
+   <refpurpose>perform an in-depth check of an existing function</refpurpose>
+  </refnamediv>
+ 
+  <indexterm zone="sql-checkfunction">
+   <primary>CHECK FUNCTION</primary>
+  </indexterm>
+ 
+  <refsynopsisdiv>
+ <synopsis>
+ CHECK FUNCTION <replaceable class="parameter">name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] )
+  | CHECK FUNCTION ALL [ IN LANGUAGE <replaceable class="parameter">lang_name</replaceable> ] [ IN SCHEMA <replaceable class="parameter">schema_name</replaceable> ] [ FOR ROLE <replaceable class="parameter">owner_name</replaceable> ]
+ </synopsis>
+  </refsynopsisdiv>
+ 
+  <refsect1 id="sql-checkfunction-description">
+   <title>Description</title>
+ 
+   <para>
+    <command>CHECK FUNCTION</command> performs an in-depth check of existing
+    functions if the procedural language supports it (currently only
+    <link linkend="plpgsql">PL/pgSQL</link> is defined with a check function).
+    The checks performed depend on the language, but typically include checks
+    for the existence of database objects referenced by the function or style
+    checks like defined but unused variables.
+   </para>
+ 
+   <para>
+    If a problem is found with a function, a <literal>WARNING</> will be raised.
+   </para>
+ 
+   <para>The form <command>CHECK FUNCTION ALL</command> will check all functions
+    matching the specified criteria.  If no criteria are specified, functions in
+    the schemas <literal>pg_catalog</literal> and <literal>information_schema</literal>
+    will be excluded from the check.
+   </para>
+  </refsect1>
+ 
+  <refsect1>
+   <title>Parameters</title>
+ 
+   <variablelist>
+    <varlistentry>
+     <term><replaceable class="parameter">name</replaceable></term>
+ 
+     <listitem>
+      <para>
+       The name (optionally schema-qualified) of an existing function.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><replaceable class="parameter">argmode</replaceable></term>
+ 
+     <listitem>
+      <para>
+       The mode of an argument: <literal>IN</>, <literal>OUT</>,
+       <literal>INOUT</>, or <literal>VARIADIC</>.
+       If omitted, the default is <literal>IN</>.
+       Note that <command>CHECK FUNCTION</command> does not actually pay
+       any attention to <literal>OUT</> arguments, since only the input
+       arguments are needed to determine the function's identity.
+       So it is sufficient to list the <literal>IN</>, <literal>INOUT</>,
+       and <literal>VARIADIC</> arguments.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><replaceable class="parameter">argname</replaceable></term>
+ 
+     <listitem>
+      <para>
+       The name of an argument.
+       Note that <command>CHECK FUNCTION</command> does not actually pay
+       any attention to argument names, since only the argument data
+       types are needed to determine the function's identity.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><replaceable class="parameter">lang_name</replaceable></term>
+ 
+     <listitem>
+      <para>
+       The name of a procedural language.
+       Specifies that only functions written in this language should
+       be checked.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><replaceable class="parameter">schema_name</replaceable></term>
+ 
+     <listitem>
+      <para>
+       Specifies that only functions in this schema should be checked.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><replaceable class="parameter">owner_name</replaceable></term>
+ 
+     <listitem>
+      <para>
+       Specifies that only functions owned by this role should be checked.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+  </refsect1>
+ 
+  <refsect1 id="sql-checkfunction-compat">
+   <title>Compatibility</title>
+ 
+   <para>
+    The <command>CHECK FUNCTION</command> statement is a
+    <productname>PostgreSQL</productname> extension.
+   </para>
+  </refsect1>
+ 
+ </refentry>
diff --git a/doc/src/sgml/ref/check_trigger.sgml b/doc/src/sgml/ref/check_trigger.sgml
new file mode 100644
index ...d97b396
*** a/doc/src/sgml/ref/check_trigger.sgml
--- b/doc/src/sgml/ref/check_trigger.sgml
***************
*** 0 ****
--- 1,79 ----
+ <!--
+ doc/src/sgml/ref/check_trigger.sgml
+ -->
+ 
+ <refentry id="SQL-CHECKTRIGGER">
+  <refmeta>
+   <refentrytitle>CHECK TRIGGER</refentrytitle>
+   <manvolnum>7</manvolnum>
+   <refmiscinfo>SQL - Language Statements</refmiscinfo>
+  </refmeta>
+ 
+  <refnamediv>
+   <refname>CHECK TRIGGER</refname>
+   <refpurpose>perform an in-depth check of an existing trigger function</refpurpose>
+  </refnamediv>
+ 
+  <indexterm zone="sql-checktrigger">
+   <primary>CHECK TRIGGER</primary>
+  </indexterm>
+ 
+  <refsynopsisdiv>
+ <synopsis>
+ CHECK TRIGGER <replaceable class="parameter">name</replaceable> ON <replaceable class="parameter">tablename</replaceable>
+ </synopsis>
+  </refsynopsisdiv>
+ 
+  <refsect1 id="sql-checktrigger-description">
+   <title>Description</title>
+ 
+   <para>
+    <command>CHECK FUNCTION</command> performs an in-depth check of a trigger
+    function if the procedural language supports it (currently only
+    <link linkend="plpgsql">PL/pgSQL</link> is defined with a check function).
+    The checks performed depend on the language, but typically include checks
+    for the existence of database objects referenced by the function or style
+    checks like defined but unused variables.
+   </para>
+ 
+   <para>
+    If a problem is found with the function, a <literal>WARNING</> will be raised.
+   </para>
+  </refsect1>
+ 
+  <refsect1>
+   <title>Parameters</title>
+ 
+   <variablelist>
+    <varlistentry>
+     <term><replaceable class="parameter">name</replaceable></term>
+     <listitem>
+      <para>
+       The name of a trigger.  The trigger function for this trigger will
+       be checked.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><replaceable class="parameter">table</replaceable></term>
+     <listitem>
+      <para>
+       The name (optionally schema-qualified) of the table or view the trigger
+       is for.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+  </refsect1>
+ 
+  <refsect1 id="sql-checktrigger-compat">
+   <title>Compatibility</title>
+ 
+   <para>
+    The <command>CHECK TRIGGER</command> statement is a
+    <productname>PostgreSQL</productname> extension.
+   </para>
+  </refsect1>
+ 
+ </refentry>
diff --git a/doc/src/sgml/ref/create_language.sgml b/doc/src/sgml/ref/create_language.sgml
new file mode 100644
index 0995b13..34fc36e
*** a/doc/src/sgml/ref/create_language.sgml
--- b/doc/src/sgml/ref/create_language.sgml
*************** PostgreSQL documentation
*** 23,29 ****
  <synopsis>
  CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
  CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ]
  </synopsis>
   </refsynopsisdiv>
  
--- 23,29 ----
  <synopsis>
  CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
  CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ] [ CHECK <replaceable>checkfunction</replaceable> ]
  </synopsis>
   </refsynopsisdiv>
  
*************** CREATE [ OR REPLACE ] [ TRUSTED ] [ PROC
*** 217,222 ****
--- 217,249 ----
        </para>
       </listitem>
      </varlistentry>
+ 
+     <varlistentry>
+      <term><literal>CHECK</literal> <replaceable class="parameter">checkfunction</replaceable></term>
+ 
+      <listitem>
+       <para><replaceable class="parameter">checkfunction</replaceable> is the
+        name of a previously registered function that will be called
+        by <command>CHECK FUNCTION</command> and <command>CHECK TRIGGER</command>
+        to check the bodies of functions defined in the language.
+        If no check function is specified, this kind of check is not available.
+        The check function must take two arguments, one of type <type>oid</type>
+        (the OID of the function to be checked), and one of type <type>regclass</type>
+        (the table with the trigger for <command>CHECK TRIGGER</command>).
+        The check function will typically return <type>void</>.
+       </para>
+ 
+       <para>
+        A check function would typically perform an in-depth check of the function
+        body, for example that all referenced database objects exist, but it could
+        also check for stylistic problems like defined but unreferenced variables.
+        To signal a problem found during the check, the function should use the
+        <function>ereport()</function> to report a <literal>WARNING</>.
+        The return value of the function is ignored.
+       </para>
+      </listitem>
+     </varlistentry>
+ 
     </variablelist>
  
    <para>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
new file mode 100644
index 7326519..460794e
*** a/doc/src/sgml/reference.sgml
--- b/doc/src/sgml/reference.sgml
***************
*** 68,73 ****
--- 68,75 ----
     &alterView;
     &analyze;
     &begin;
+    &checkFunction;
+    &checkTrigger;
     &checkpoint;
     &close;
     &cluster;
#38Pavel Stehule
pavel.stehule@gmail.com
In reply to: Albe Laurenz (#37)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Hello

2011/12/9 Albe Laurenz <laurenz.albe@wien.gv.at>:

Pavel Stehule wrote:

there is fixed version

Here is my attempt at a doc patch.

Could you add it to your patch so that all is in a single patch?

there is merged patch

Thank you

Regards

Pavel

Show quoted text

Yours,
Laurenz Albe

Attachments:

check_function-2011-12-09-3.diff.gzapplication/x-gzip; name=check_function-2011-12-09-3.diff.gzDownload
#39Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Pavel Stehule (#38)
Re: review: CHECK FUNCTION statement

Pavel Stehule wrote:

there is merged patch

Works fine, except that there are still missing const qualifiers
in copyfuncs.c and equalfuncs.c that lead to compiler warnings.

One thing I forgot to mention:
I thought there was a consensus to add a WITH() or OPTIONS() clause
to pass options to the checker function:
http://archives.postgresql.org/message-id/12568.1322669638@sss.pgh.pa.us

I think this should be there so that the API does not have to be
changed in the future.

Yours,
Laurenz Albe

#40Pavel Stehule
pavel.stehule@gmail.com
In reply to: Albe Laurenz (#39)
Re: review: CHECK FUNCTION statement

hello

2011/12/12 Albe Laurenz <laurenz.albe@wien.gv.at>:

Pavel Stehule wrote:

there is merged patch

Works fine, except that there are still missing const qualifiers
in copyfuncs.c and equalfuncs.c that lead to compiler warnings.

One thing I forgot to mention:
I thought there was a consensus to add a WITH() or OPTIONS() clause
to pass options to the checker function:
http://archives.postgresql.org/message-id/12568.1322669638@sss.pgh.pa.us

I think this should be there so that the API does not have to be
changed in the future.

there is just one question - how propagate options to check functions

I am thinking about third parameter - probably text array

??
Regards

Pavel

Show quoted text

Yours,
Laurenz Albe

#41Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Pavel Stehule (#40)
Re: review: CHECK FUNCTION statement

Pavel Stehule wrote:

One thing I forgot to mention:
I thought there was a consensus to add a WITH() or OPTIONS() clause
to pass options to the checker function:
http://archives.postgresql.org/message-id/12568.1322669638@sss.pgh.pa.us

I think this should be there so that the API does not have to be
changed in the future.

there is just one question - how propagate options to check functions

I am thinking about third parameter - probably text array

Either that, or couldn't you pass an option List as data type "internal"?

I don't know what is most natural or convenient.

Yours,
Laurenz Albe

#42Pavel Stehule
pavel.stehule@gmail.com
In reply to: Albe Laurenz (#41)
Re: review: CHECK FUNCTION statement

2011/12/13 Albe Laurenz <laurenz.albe@wien.gv.at>:

Pavel Stehule wrote:

One thing I forgot to mention:
I thought there was a consensus to add a WITH() or OPTIONS() clause
to pass options to the checker function:
http://archives.postgresql.org/message-id/12568.1322669638@sss.pgh.pa.us

I think this should be there so that the API does not have to be
changed in the future.

there is just one question - how propagate options to check functions

I am thinking about third parameter - probably text array

Either that, or couldn't you pass an option List as data type "internal"?

this is question - internal is most simply solution, but then we
cannot to call check function directly

Regards

Pavel

Show quoted text

I don't know what is most natural or convenient.

Yours,
Laurenz Albe

#43Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#42)
Re: review: CHECK FUNCTION statement

Pavel Stehule <pavel.stehule@gmail.com> writes:

2011/12/13 Albe Laurenz <laurenz.albe@wien.gv.at>:

Either that, or couldn't you pass an option List as data type "internal"?

this is question - internal is most simply solution, but then we
cannot to call check function directly

Yeah, one of the proposals for allowing people to specify complicated
conditions about what to check was to tell them to do
select checker(oid) from pg_proc where any-random-condition;
If the checker isn't user-callable then we lose that escape hatch, and
the only selection conditions that will ever be possible are the ones
we take the trouble to shoehorn into the CHECK FUNCTION statement.
Doesn't seem like a good thing to me.

regards, tom lane

#44Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#43)
Re: review: CHECK FUNCTION statement

2011/12/13 Tom Lane <tgl@sss.pgh.pa.us>:

Pavel Stehule <pavel.stehule@gmail.com> writes:

2011/12/13 Albe Laurenz <laurenz.albe@wien.gv.at>:

Either that, or couldn't you pass an option List as data type "internal"?

this is question - internal is most simply solution, but then we
cannot to call check function directly

Yeah, one of the proposals for allowing people to specify complicated
conditions about what to check was to tell them to do
       select checker(oid) from pg_proc where any-random-condition;

If the checker isn't user-callable then we lose that escape hatch, and
the only selection conditions that will ever be possible are the ones
we take the trouble to shoehorn into the CHECK FUNCTION statement.
Doesn't seem like a good thing to me.

yes, it is reason why I thinking just about string array.

I have not idea about other PL, but options for plpgsql can be one
word and checker function can simply parse two or more words options.

Now I would to implement flags "quite" - ignore NOTIFY messages and
"fatal_errors" to stop on first error.

Regards

Pavel

Show quoted text

                       regards, tom lane

#45Pavel Stehule
pavel.stehule@gmail.com
In reply to: Albe Laurenz (#39)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Hello

2011/12/12 Albe Laurenz <laurenz.albe@wien.gv.at>:

Pavel Stehule wrote:

there is merged patch

Works fine, except that there are still missing const qualifiers
in copyfuncs.c and equalfuncs.c that lead to compiler warnings.

One thing I forgot to mention:
I thought there was a consensus to add a WITH() or OPTIONS() clause
to pass options to the checker function:
http://archives.postgresql.org/message-id/12568.1322669638@sss.pgh.pa.us

I think this should be there so that the API does not have to be
changed in the future.

changes:

* fixed warnings
* support for options - actually only two options are supported -
quite and fatal_errors

these options are +/- useful - main reason for their existence is
testing of support of options - processing on CHECK ... stmt side and
processing on checker function side.

options are send as 2d text array - some like
'{{quite,on},{fatal_errors,on}} - so direct call of checker function
is possible

* regress test for multi check

Regards

Pavel

Show quoted text

Yours,
Laurenz Albe

Attachments:

check_function-2011-12-14-1.diff.gzapplication/x-gzip; name=check_function-2011-12-14-1.diff.gzDownload
#46Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Pavel Stehule (#45)
Re: review: CHECK FUNCTION statement

Pavel Stehule wrote:

changes:

* fixed warnings
* support for options - actually only two options are supported -
quite and fatal_errors

these options are +/- useful - main reason for their existence is
testing of support of options - processing on CHECK ... stmt side and
processing on checker function side.

options are send as 2d text array - some like
'{{quite,on},{fatal_errors,on}} - so direct call of checker function
is possible

* regress test for multi check

First of all: It should be "quiet" and not "quite".

The patch applies and builds fine.

It fails one of ist own regression tests, here is the diff:

*** /postgres/cvs/postgresql/src/test/regress/expected/plpgsql.out      2011-12-14 11:50:44.000000000 +0100
--- /postgres/cvs/postgresql/src/test/regress/results/plpgsql.out       2011-12-14 16:19:45.000000000 +0100
***************
*** 4975,4991 ****
  end;
  $$ language plpgsql;
  check function all in schema plpgsql_check;
- NOTICE:  checked function "plpgsql_check.fce1()"
  WARNING:  error in function "plpgsql_check.fce2()"
  DETAIL:  too few parameters specified for RAISE
  CONTEXT:  line 3 at RAISE
  NOTICE:  checked function "plpgsql_check.fce3()"
  check function all in schema plpgsql_check with (quite);
  WARNING:  error in function "plpgsql_check.fce2()"
  DETAIL:  too few parameters specified for RAISE
  CONTEXT:  line 3 at RAISE
  check function all in schema plpgsql_check with (fatal_errors);
- NOTICE:  checked function "plpgsql_check.fce1()"
  ERROR:  too few parameters specified for RAISE
  CONTEXT:  PL/pgSQL function "fce2" line 3 at RAISE
  check function all in schema plpgsql_check with (quite, fatal_errors on);
--- 4975,4990 ----
  end;
  $$ language plpgsql;
  check function all in schema plpgsql_check;
  WARNING:  error in function "plpgsql_check.fce2()"
  DETAIL:  too few parameters specified for RAISE
  CONTEXT:  line 3 at RAISE
  NOTICE:  checked function "plpgsql_check.fce3()"
+ NOTICE:  checked function "plpgsql_check.fce1()"
  check function all in schema plpgsql_check with (quite);
  WARNING:  error in function "plpgsql_check.fce2()"
  DETAIL:  too few parameters specified for RAISE
  CONTEXT:  line 3 at RAISE
  check function all in schema plpgsql_check with (fatal_errors);
  ERROR:  too few parameters specified for RAISE
  CONTEXT:  PL/pgSQL function "fce2" line 3 at RAISE
  check function all in schema plpgsql_check with (quite, fatal_errors on);

======================================================================

The "quiet" option is not very intuitive:

test=> CHECK FUNCTION ALL WITH (quite 'off');
NOTICE: skip check function "atrig()", it is trigger function
NOTICE: skip check function "perl_max(integer,integer)", language "plperl" hasn't checker function
NOTICE: checked function "ok()"
NOTICE: checked function "newstyle(integer)"
CHECK FUNCTION

test=> CHECK FUNCTION ALL WITH (quite 'on');
NOTICE: skip check function "atrig()", it is trigger function
CHECK FUNCTION

I understand that "quiet" cannot silence this message, nor
"skip ..., uses C language" and "skip ..., it uses internal language",
but that means that it is not very useful as it is.

If all we need is a sample option, I think that "fatal_errors" is
enough, and I think that is an option that can be useful.

Yours,
Laurenz Albe

#47Pavel Stehule
pavel.stehule@gmail.com
In reply to: Albe Laurenz (#46)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Hello

so removed "quite" option
and removed multiple check regression tests also - there is missing
explicit order of function checking, so regress tests can fail :(

Regards

Pavel

2011/12/14 Albe Laurenz <laurenz.albe@wien.gv.at>:

Show quoted text

Pavel Stehule wrote:

changes:

* fixed warnings
* support for options - actually only two options are supported -
quite and fatal_errors

these options are +/- useful - main reason for their existence is
testing of  support of options - processing on CHECK ... stmt side and
processing on checker function side.

options are send as 2d text array - some like
'{{quite,on},{fatal_errors,on}} - so direct call of checker function
is possible

* regress test for multi check

First of all: It should be "quiet" and not "quite".

The patch applies and builds fine.

It fails one of ist own regression tests, here is the diff:

*** /postgres/cvs/postgresql/src/test/regress/expected/plpgsql.out      2011-12-14 11:50:44.000000000 +0100
--- /postgres/cvs/postgresql/src/test/regress/results/plpgsql.out       2011-12-14 16:19:45.000000000 +0100
***************
*** 4975,4991 ****
 end;
 $$ language plpgsql;
 check function all in schema plpgsql_check;
- NOTICE:  checked function "plpgsql_check.fce1()"
 WARNING:  error in function "plpgsql_check.fce2()"
 DETAIL:  too few parameters specified for RAISE
 CONTEXT:  line 3 at RAISE
 NOTICE:  checked function "plpgsql_check.fce3()"
 check function all in schema plpgsql_check with (quite);
 WARNING:  error in function "plpgsql_check.fce2()"
 DETAIL:  too few parameters specified for RAISE
 CONTEXT:  line 3 at RAISE
 check function all in schema plpgsql_check with (fatal_errors);
- NOTICE:  checked function "plpgsql_check.fce1()"
 ERROR:  too few parameters specified for RAISE
 CONTEXT:  PL/pgSQL function "fce2" line 3 at RAISE
 check function all in schema plpgsql_check with (quite, fatal_errors on);
--- 4975,4990 ----
 end;
 $$ language plpgsql;
 check function all in schema plpgsql_check;
 WARNING:  error in function "plpgsql_check.fce2()"
 DETAIL:  too few parameters specified for RAISE
 CONTEXT:  line 3 at RAISE
 NOTICE:  checked function "plpgsql_check.fce3()"
+ NOTICE:  checked function "plpgsql_check.fce1()"
 check function all in schema plpgsql_check with (quite);
 WARNING:  error in function "plpgsql_check.fce2()"
 DETAIL:  too few parameters specified for RAISE
 CONTEXT:  line 3 at RAISE
 check function all in schema plpgsql_check with (fatal_errors);
 ERROR:  too few parameters specified for RAISE
 CONTEXT:  PL/pgSQL function "fce2" line 3 at RAISE
 check function all in schema plpgsql_check with (quite, fatal_errors on);

======================================================================

The "quiet" option is not very intuitive:

test=> CHECK FUNCTION ALL WITH (quite 'off');
NOTICE:  skip check function "atrig()", it is trigger function
NOTICE:  skip check function "perl_max(integer,integer)", language "plperl" hasn't checker function
NOTICE:  checked function "ok()"
NOTICE:  checked function "newstyle(integer)"
CHECK FUNCTION

test=> CHECK FUNCTION ALL WITH (quite 'on');
NOTICE:  skip check function "atrig()", it is trigger function
CHECK FUNCTION

I understand that "quiet" cannot silence this message, nor
"skip ..., uses C language" and "skip ..., it uses internal language",
but that means that it is not very useful as it is.

If all we need is a sample option, I think that "fatal_errors" is
enough, and I think that is an option that can be useful.

Yours,
Laurenz Albe

Attachments:

check_function-2011-12-14-3.diff.gzapplication/x-gzip; name=check_function-2011-12-14-3.diff.gzDownload
�X�Ncheck_function-2011-12-14-3.diff�\{s�F��[�]mB�$��DI�y�P��:E�It����Ck`0�d&��~�=@�R��[�-�������u���q�E�����|�N��\4����|��l���v�y�Z������I����f��|V��wR�9��u����^�u��)"�|f���^�s|�fT����^����������8z���)������I����$^��������]p�=�����C�x�s�Y�~�{�����]�'<v��g�����}%��
������	�%���S,��P��!��7hHC�dT��&_���x�C��07�cD����f��M�(d��I�_���MJ����K'����F��a��^
'�����>��|��f����kgQ�����U�H����E�t�����h�m�����3FL��E��������yA(
x�L������A���;��CsX�����:�o������i����V7U�n����N��*u�\���	��<��W���~��Tc���:�"��3,�'��b'���u*�O��'Y�|���A�� jC��|	����9�W4o�V��B��r�yt��>�lfI*�k���N��'�V��n����;�f��Uk��R���A������J~8G���j�q���l��>L�%sc�$�-��_:�����{k�FF:5���d�C�"|4�!�T]'d�
`��"v����!��{��e������<�|��j����?�����;���H�N.��N�{N�.H�r9��c��C��\����!FS�8V��%�Q�`���w}���!(kh=�D%��`�S'!~	�R�Zx���^A��F����d$��=]��8Q����e��
y���f�������(`����L�~�������>)�T��m��R�~-�����H�#�|�b�>;���z�hY��4��,�)����/��~_k���tJ��4�aQL;66N��y���u�,x�����')Dh7�k����tw��������3�.���{�O�r-�S�
�-*b��3:Rs1��7_���@@��,��g��F��N��Z�|#	�qe��c�E�cg�*NW�m,��^�s�1�X�f�y���1������EK'��q�q�c���P�t/�.�=�`�@<h���������V����7x��i��i��H�{u?d����G9|�� �I���B� �F���Rl�7��)��@T��8w�A���S���H��3��^M������.
�����5�^�����#H���X�V��3�H&�0)4��X��?8�i�)�0hE�}���p�Pe��|Pm���
�`��?������	�`�h.w�-��r�5z&�">�2�&��v��/$������(z!����[�A� �d�����(|��(��q���c�BQ�="iA�$A�F]���1G���3:t�qps5�z�
��$lSp���}.;W����1HC� b����#���ZmuXq���6�[����P���bs�0�W1���6����e~5�DPk��f��m�����CX���_�t!t�4�G��N�w�*@����nN1w�:��t������cO{���FW������ �|������o'���>UM������g��7�r�
����Nw|��u�*��y��v�+���.T5�jP�w��_Q_e8��U���iZU�/�/�!Uv�6��P��T�{N��)R�#�n�uZg�����eD�5��
�������6@�S��	���Jax3LF�gv}�nFo.�{�on�����7�Kx�\�~;x=b�`���������7�}��n���j�k���[��gM���W�����#��������.�W�� -W	������`r�e�$�m��p��������'�K��VU��V�^U�$1[�C&��DE(���^������{���Z��z}arW���B����&_��;����|�����V.��C�#���)�&IK���A�O���J�"8��>D��&!D�y#�� d��
��R���w�'���$&�(���Q�����14�7.$%1�3��I��dvW��(��<JySK(#������,�&�H�hL(��=!J�O)�JIt�p�
1�2|�&��rEte���i&>��Euqu��I���������R����(�HGP'	���S^p�NF�}8Y���D���5����:A(�")M'F�T�[����
��j"�uh�f�7[�Ds�i��������jYQa�~�-]e�2���[Q��[x��`��|KI���h�����2{���t�����M��?J��GC'g�v��<n�4OwCQ��'���Z���#|<����&
z�d�):y!��u&$��XC�J#S�1@�@��E)���o�eyt�:���q�����=��if��:H�u�89j�n��C��T��Z���N1��?O������Z��1�{����u2��_QhG���*56]@����\�V�#z�z����������>0���R'�o���]���:���\��&��)S���E*7�V�x:��iW���!!N���+�*��? �a���*L�KVV��t�
���rC��y">�#��,K���qV(��c���/#/����:���	�����~�5�w4����w��#tdz��L�*
���b\^Rt��(1��p�D��?x���Y:ed^adv}�(+�C��2"�Gl���1����X~
I_���T�h�n#�M�Q�#O��=�Da���0k�Pj�����`B��+{�K�x�VZ��RY�1<F���B��
���F��.`}�'�U�u@ ���)w�k��0����������e|H���P��P=�|�	�g�	DS�O dw5I@����� ���k-z���8}!���Y K�s9���R�g��HP�$�^&*D�&��_hxY8
�\X���������e�H�(�dy�$�Bcn&2P\`�w&�Ttc�����H���<�LM��2�'t�1p��\vO[J�Z@�iD�q�Lc����������3���6bk�f���E9�"��Nw+������������?�������V�ZeL��
Y�"������8|������N>��j4�S�Z�|�v�i��6TUtV�K���a&,�-�$����:����v��}?n�����>�\9�}U'�0'A��$InjvF�d���~�Z')!���p�����
-��.w��{8h�Igu�/d�5����!e�`�mM�+�z������u�f����$z0b`[3L5:MC���k��}�J��\�"�d����
<h�b��L���\�LV-�s�y�+
2~��vEC�sx_��"��-GaP|�!
����vq7�)	c����$������Pov�0���aB�z����l7O��&�<�`��s!�0f�K�S���[w�R���k�z'��^�����m����N��>�������S�'��%����=��S�ON����/6�gs�����$���g�?��gI�)j���{<eHGk����rI4��T�OO%9Z��erI����T�=��%���R#���7��y{����������~�<�(����/��W'=R���B��@~n��u������m�~��P���pK����V�u��tb��t'�m:]����J�G���[�f����~]d�X�;�x!Zb/>��\~O]��9�<����(��z]�\�p���-��`u��I���Z'�;���i���99��zu��vd	O��K��}�u��s�L��~Q��;��J�N��X�|����&��z��3o�)kA��nY�t{r��6�P�6�_)o�:��Q�6��4uJ_������6��������h�]J9
��=R������	h!�[�`��C������]/_���C��a��}~�o�oq�~c���c���J�mj����x,�_xE�Y;>�����hS���`�.�q�{[J'k���5�l�O����^�%}(����[�e����Cn�&�ceU�1m6Ok`=�oJ���Z��kc���$C�qx�����r'v�=W�f6
|C����!���.".�o��D��Y
p�N��\��x_�/��"�[�Sz[I"���	��C����w^��0�����f�U�}y���>��q�/@N�^��z���S?wQ1��4~�v�����W�K�+�*��
"������������2=L,�?���1P�x�tJ	Sr�G�|z����S�
�����4Z���#��J���FQwZ�J=����{�yr�$�eE	�Z%��U�2�����
��VA7t:��
��\��=��2������5r1:�����������[���jU5�$���b��^�&�����	1��f�\0]�FS<]�Z��o$�&=`c����3�T���g��kl��D^��:�,����:zP���t~=�Q2��%	5����w�7j�p��y~���*��)�^�y�����7������4�=?L`	��OC.��4e-�|������y��'��{����0
�u�(�������j��K�Vx8&�t�����(����?���%FdR�U!H	�,oy�)����$���A�����a��U��@UFV�D�c�f!�B�`�V��W_�D|�^�22����x���u����
J.���G�{��#T{���u��5L{
{�Re����No���&��>��x�Z=���vt{;����a���RD���#��G���A+�@�����������/����aE��?��
�A��jJw��&\e(�F��}����4 �UD�d��F��V����!�{z1z5x{9�~?���c����j<y������Q��r?��2\�>����D=�����?���_�G����+y>Lf���+�\��t��1}#�}���N����65f	c�����m��fj��V���J}T�H)������5�,*�-#4S�VZ�����[�C>Vo���&�
��_A�����k�G`'v�
�py�$������o^O'77[Y����O]���C5���C�A����;�+�Dhl�����V
p{uK���? ��`2�nK�#�x���8�j-�O����VE������U�*�3���%6b��������B��jR����X�ye_~��F�����W���UL
	q�@�����|�����S����l�*���6=e���T���mXBF����wH��r JU���y�V�U$|Z�\?(�f�����T��u�D�1z�R����m;��,V�|�)�'�� ]��8_�1k,��s��X�������x8��7��W#���v�h
M�����oi$b(�����Dk���,���b�||kF�C[�?��~�+��L� gKX�L���X�(h�7������LdLE�'}[���\nMNb:�0EtD%���(��d�n�%2�M�|	4P>���^�Q�o�iS'�O�j�n�b����[c8
#�k�M���rf�7�"�\h�Cxl�����H��]a����,������01M�@�����Hi;a�9��*Mc���q_D���x�;@zt�`Gg�y�R��~��5k��1�	�tE��Z��cO��b�_����#By
DM�r;�k���f1��1T�Q8�"�~UC~��**s�(�`U�����53�#��QQ���Q�xH�R�u���^a
�d����\}# n�pA}]��He^_Nb����{V�,u1;��heM���7�(���LPH��Dq�2O��E��b(��������6������%Z/?u!���"���"YW�u��=y�D5����LPE�p^�Et\w�$=��/������!�K�� '� 6��Jl 5><��d����|yQ�g�H����\�����a]�2�_e/a;W�`�x?����oA�	+$7�����/����f��qBq�l�ohK��Y��x������+�9i���3�jbO�x�
����E&V~��5����k[��(�Y��	'�X7��~��������z�4��IK#��W��O]�R��3;^�,�,!������n]���B�F�(5��u�9��5��_%����e�~dT� 1H���pT���T
���g�O9�o�h\�h\Y����%ZFk(g�OE��P�7	�
���S.{��JO}<��a���_��1�����Evt3�����32��:i=�������5���~o����k)����~�gh����������Fay�w�fW)|�*�f�;�����3`�{(���#5�����cd��1��(��*���t��*+��Y�_8�/�0!����n���q��+K�q��N�xTV�5���B�Y3;��Q2+O��f0\kP'�\�g����"����;m4]}�]ru��Fo����2�
�NHE�zHf�Rr/��]|X��U"5)�y�A'E��$���$������U�l'
e/�v#�O���TkR�1�cS!��$�!��d.Q��f���U^Zt��o��2[��0^����f2$A
���r����g��h>�����S������",���^H�{%	��-��s��q}���7�(�F���f�$�9��9_j���Kv���y[��.�7�nG��N����1[��h�V�BV�q�d6A�����0�v�e�5��T���TZ�dZ*�[M
_k������	NE���&�V5k4'��������L��E�����0����+-�13Z�,Ht<��������,�Nl�����v���A���q��{����;��N��|��>FJ�d����G�_���M�;�������k�yy��
*`�6��n�i(=�����v�����F�B<��@
�{���z`����������x����[f���P����E���#R��.�������`[t)����
�-2"g�����o����y+���LI�aB���:����D]�����K�"fD,���_���
kyt���0�#�x�Z���d�����~�+����g�=h�
4�S>�|���CV�L�Q�M�LBa���=A���f����������XvL�$'��@�=�
(�6���LY�
�@�	,��j��W<�D�S[�F�|�k#�6�;�NCB^�M0�L7���<�5���.CFD��f���_��W����s�K���h���CCf����wt���|����~[���yx!h��H������JH�A!���R�j%R�jY�&]�q��:���uHc�>jR���*���H�I0���k�e��������,���)���c�:QA�=�:���~
�;,�������8~|����"���+�u6
�����'�R����Ww}�"��y�Y���"�u�3�����dd���D��I� �g>`�4��/2`X��2��#�j�������B����T��hz/Tw�BL��	x�����!9��	QO]�[Q�H�V�����)���_�!��}������5��p�q��+�����{2��V��[�U*�������C2��V��.������r�.��D�X/P�Q�8�[�T�u��H���<s��+�Li����\������}FY���3��m���2WI
oU5�V����[U�9?�_�y}{�Z�o���
��6[��m���4D�w�)U�v�6�<u�����q��%Y������V��E.C�vL2g63��"2��������3R��"��y��+�W��q�t!��dv�\��z���>Z�� JIG	�/�
����|c+����BK:��L���G��/7���L=��l�&��Q���H�{�au��\�{���DS2�oO�<�����M��h��kD��d�[A���)~W��W�����m�������d8��<��TG�1�DD�U��2��
������DgJ���h���}�#=)<�`"S����	��x`���J�8E��b��&��\��s��[��w�J3[A���x�p<0����+:�h2S;����lx�Y����#�$?����5��������gWi�2��G��\���L��J]\}\b����(�=�M��������Ee�����V����P�B����A��:oFM��K�����J����F;f��~a+L#�4c�����>k��?�;?�)CN����wv���-d�?�;b\`+Q����r�y!���`�\��.�3��Qwr���E!��W����N�B`�U��N��e%S�G�����7o�o��=��wM� ��r�K��6w�I�wuU0�$�	N��-�jA�4���������i�(yc���sr���8=;���h�sx���/��/�N�� ~��e�k�Z{6Ur@�Le@$}@�����*���N.����_
��|�}(w���mV:H�������L�/����hU[��?�4+��J=��������7��9!]�J����:^�O{��W���H��7S1F��T�+���>2�B�uV� -�[7�\��H�A7.���Yo�j�F�l��pz�r�}�\��nS$�k�<v-A�M����kF�/��0}-���������1��5��7�%��
YE^[Y&
i����`1iL�Sn���V���%6���"�do�����$�5��F�YI���;��n��h��L���Q����4^V��m��Jt�>m��������;�T!�7�M#:�aV��\R��`����R��1lR��C�!�v�~��Z	�sbs�����
V��O��%��a�5��L�p(�:b�P\J&�pD����6�*�O�:����N�����LxZ�j�C6���D��l�Y�lm	4q�YP����-���\�W>`�k��"rcmh�3+��57�
�F�����!i�
�����Zcg���}�1a(Z~�H�g�!tS�Y<��M1�|^�}^���i����j�1$��c
�>oV��v6[;��v��S�[���S����/��v�5�E�����_�0Q�<
\��]_��:dU0���7x�����[6����)e�r�'�����7�����m�-^0o�;g��o�;u�"\Q�����������$5�
��~������j�����9�%�x	����~��F�"HWj��J��-b�7k;5x��kiKC�v������\��X.E�T���{�t��w������>��@��E/��S��+O?8@����6s�G���ek�vn�>��n�0X���,
�Y��W�c[�y�G����9�7q�R�����J����n����{g����) 7#�,������TM,���f���y_�7����M�������U?�0"@>��d}��S�/X��v���l`qv�p/>e���j�~[X50��V!%������"��]�Lh�o_�!/
Cu�y��n����0�Q]�7d�uz���u�0��)��|���96#~����%��=���N�F�	������� fT\�8HhE��*������F2���+0��������z����#�Z�UJ�2��W��Iu�0���o�k��E{&�_cWB*�@$��L�/��������X`WwIfAG�.��]m��G�o6��X��-���T:���U�W%�Gu2�e�l�����
��6 ��A��w�TAGTx����H$����[�J��tU��b���W�[2Dt�����j��z�Q�����O��e{�~\`��'�W��}Wu�s����?:���}m?�FCk�z��~����y���w���S��L#wj�#�������u���BK�]�V��B���m�x��d����s��h��\>������
�/���<Eg���t����1���G��4j��J����v�Ri��U�ATH�F���/����S�>{E��}�-�e�����������.0lIm����9�.�����xx8�J��g�>��w{�G�3��,�
<������J\s5����������R��@m�v%�<���wV���hY<J��K�/;-^2���W����;�E�v7w
��Us���Z-g
�m,X����u��z]�3���;E{��~��e��R��Q7�S#�����y.\����-���4�[��S������uT��������G��H�"��}L�V�����[�
|@��pj��U��<[�+�6��h�k��#��NX����9�t$+&|9}<��g,�<��^8S�k<{��q�����!��beYI��E������_b��i�?�SA]��6��:��
����<��r�����p7�n�7�w��u�w4��s���-���1�_�>@���Z��+# +@����tO���	X�<��~���x�m�1�V��B������O�Zf���Gt�X?+���3�e��<�(�.e��mmMnYD	6����L�F��5���k��wO�c/8o��{bc[8w����Y0,��O�6��,3;�V]�_�Su���]���K�������{fk+�S��'�'����0�6
U3��Q���l����
���$(���H��4�Rh�D�2����{ga������Kr?I�{���z����v<�B����(�c����y[4��1�^�����fbj(�ln��pS�D[��l�C 2����L+q�1�'���;����&5�����.]��	C>�Q/����8��l�qO93g��PHL�>�o��a��z���������3�(�������TI)^�U��-��5j�V��
��;;�����f�G3��%������T��i{V����`�</�B�=H���BP=P�9j�#���������v4��)qHe�k�Lv�c���*�ZC�p��.<jJ�������B88l���=����U�[\U a?�r:{t����7j1�n�0���|d�P�h��|}��B�_9���e�wU�����m��[�4�Z�hg��:�?��������Nw���_t�z'#�"7��L��b�PG�P���PCazP�"=����j�[��F�^R��C�����{��MzO�%H�E�?������=�,Q���6�0�	��O���z��3���)��9�(���2��[�
��]u/�sr����)��^��v������8�uI�g�"�d��7��h�����L^�//�i��@�,����~��c����H������q��ya��}���#��A�S��������xS�4�'�+����![�����W��;@��Y�.�4����s��-�������O��g��$�d��:9;h�E��y�
Wv�gH��V�5��~��!_����fu��������:<�[e1�kF!�ozw��X3�2�Q{��$6z��p9�XF�)^v�����m�p�Z���{/\C��}��s�,���t�@I1�E��W�l[B�t��g�Wo�����9_��M���E9`0��O�"��y��'�_wh3qd���ZZ�m�����oOfO�Z3��/#=��oS���5���H�l�9�'����7|T���o��[$������1�`T\N/

*(l�nWvZU� ���m��z-b�j�_8O�!�
��C�NN��	2����z@�.O����~^��D[��JL��m�k:�5�}�}��~���6r[�>������M~�k����(�l�R�0I"�����r�������F9@p��;	V�(/��Y�����.�[����?w������d��U��p�T������G�6gy�r�U8��MruM�����vp:M.�w�(����M�A����d��I�_�Na8��g�M��aVD0�X��������&L�����a��$`�!��&<jHs�@��t�c����]���X~���/����_+�
�L%&:'��o����d�j�
`G�f�J(� ��;,3���<�KL�D\�4�wZ4/�����!W���E@��y��L�}�1
�.�;��X
@U
�y�
�[(�6��lcg����7���u��  Y!�<"�y�
X����)��+��,'��y�>�I���&$�t�G'>7=���>�yC'B*�l���pS9��#��������^1<�3�����h������i����>�Z��+��SZY�,&v�!/��"��8��l��p|�F�o<������x>	u��]�n�{���C&�5����� �h���d<O��lO��mMI���\^f��0����+%�"lu���Ygv��Q�}F_���~�_m��������:=9<���G'�m����j�\�?y������C����K�N�v���������Y[�q�W���Io������	}������1=l�����
�>=����iw�����������<��C6�<:�;��������H�a�74����)����;�4�<�Z���k��(����Q��]����[��^��a�5�Y��]@�l����9�LC�$�3��7%)U�5Q��,���B.JP�T	���U�2��Z�B�����;j��s-q#;d�_F��u.����;���c�*Lb��4�7�Fh0E�<��:r����m�#/�9������_�?����xJV����Kj����<RNAl�]�D.��c5���A�Li.�	
W1_������6�s�6��m)��'��,�m�����&�!��:\���rB���0/��A1�&9���<�������z��*�pW����w\G����<��d_q�R��0�6�,�q�ud5�
�������@6z�Y�l[��lx��)j��v���R9d�w�	�}�.�����q���j�K�+��"x������1����V�w|��8>GikQ�yJ���rf���rB^(�`�:jT�Y�;���a��%��2{��r2J��c����r���\��N�~�����*��^o��.���F,�������l�!�����������h����R���`��;��W�ns��h?o�r���8�W�fz;����a�!9=;����u�U��T}f��CHE�%�A���q�+��{����C���{�a?����������d�9T�8�j��4�5�2T������R^���G�\���	1�m#
 m[�,����m�eCG[nkIv��a�(��	����d(�����kf��/�bk���3/���U�]����f`�����O>faa�N�RsqY}6����=Z���S
%^Y���9(�j��}�q��^���SK�������B�����k������������g	���j��C���~S$�g�iy����E����[UVD�Xoq�sh�����S	6��>

����]�k11��|����S���]�G0�?����F�or�@/�������7�z�@��1��?�F�H�#i����r�H��� ���V����u��O��:��;��$g�Vvj�,����iC9�}^z�%���N=��;��G�hI`$/I��=>e���	L@�������7�20���������H�[(�6��#Z�Q��X3}d:�b�f�n��0��'��N���\�q�e�TSqo����������f���@Y�0t�L�����
\�Iu�
Y�<��Rg��w�e?/z��k|W�����VOF��7���D�1U20g�$�,0�^������a�`����/�v��>Qn�J(��		6��n-D�z�(��n�R�m��<��zZ�;[��E%�"s��<��}TyV�~0����P��"��Y�x3������o-~���l�Tww��b��F�RC<P��i�"����PbQ���V�3l��hMiuDR�5��9�}�d�2k���Cy��cTZN33a`0x�}��<nB�A�+])��0=�v�zC�UwdD���<�y��28�"W�U\�
�B����k��a��_��F��MT��82��l��]QaD���0b�+KB]G|x���&���S�����aTU�(�@�/x>�!�����F�:<�')w�e�0���P~]��x�6�%��>4..L�s�0���H5������jtc���\��'�*��}�����D�]���J���yKnn=[��s*�)b;�~���{��h<�M�3L���y��5��U���^�6f	���PP��P�*�'zX�b�m �ld��ym�<�p��)R@)k[��v�w��i��]/������j�O�&W�?��#�FM����e�.����;.�����:�����+hb�ib@5L���k!��ko$MV��Jt��7����x��&>��
Fn�)C�TgL�d�;5��>ObJ3%��`�g�1vM�:|w~z���1 V�K�q?�FR8 �.U���c8xzC ����M]���|h	�*4����R�!�P&)��IF�rD�0�I�6�0�<����&��G��p�����������f�U�7&l��j�
��7�h#�c��h� �n��k��N�C6��f�A�{@1i������x�Qf��0�q����O1H�0R��������^�����}9��Kf�%�\w�L@�4~8p
������0v4v4�����f(���z�3���w�V�J)/�C��y�H�)76�M������lD�,KHWT<pJtE��0����Fe�Zt��S��y�_�k���dK�%?'@$�����"���b�b9�jV
�k��`��Z�@�����n��
��<0��j�^�"->h�5�y�)&.�����cC� ����@K����W����& ��v�z�[����`B#`u�%��i:�����N�gbS��[���/�����C<���$��4��X�a�.~f�P���|�t���Ork��� ]&X�:�c�/q����E�L��,/�����a��T��wQ|��CU`%���I������m�i��D�{��:�����,ZD(?��+*��0Z�++hb�H]��D,s;�6M{I
�f�G?����-�2t'���b��C{@�z)�������:�,
�1��=�a ����l(q|8���<�u��x:������6m���G�C��&���{�L�WY��]�s-��7�����y��A2��m�*�!"��j�[��z�����'01��DO%���1��[�[��� �<�Y^7:��S������	�����7b�'����;�Vt(�^���<����L���	�J%��6p���0��JNU������7^�;Fhu����J���(���y4���.e�S]	E�J�R	W�Y^%m�w<lbUD(���h,����0���~M{oT^:����l������j�2i��OF�����w��^�k�'��B������C�@�s��K�JiB��H��{��mo���Y��7��r�*���_jc96�Eld%��,$Vtm����~
6 �����]�����61���}�w%`�9���"z���i�����)�2H;^d*!�����:��uE���-�X[�\�9��O�'�������c|��v<R`�i��]:����teGq������8F��S�7
0�����,�e^�2��`<��@ye1�f�U���P�og(��OV��s����.^9���Q��������O�L����o}b��(
)o_S(;�s�.,J���9�phZ�2� ����R��d����
�/������aAMQ�u9(F����ILA��_k���<�
f�T���%����-n���/����{g*F��h������@�"�^^g'?ez�Yqy���������Y �G�����8{�J�����������N����/�PG����2��X�O�����?�C�LJ.��'(#d3�������l�5E�Q��t"�z�~�7 �O���
B�l:����HT=��N���05U�6+uI7H����E�1��8@��u��"}L����g1'�U�]�1xB��y�9���
����\�����%��(�U�#��O����O���D3$�_��:���6:9: �����.���bu�:��?�����;�_��K�,�@x�]�6@��H2�x~u�GC�pM��Tk���I���(���M��S�@��������L��>.a2Rm��I��F�@9������dl�,��}�G������@��RcC:l>LyE�����)�	�Ix�!�y9��UO����'k,@K��	��5a&�TaW�)����u��S�)$��)�CQ��C�������P�K/����4�
$SR�/q=���4�DUt*�q�����-���
���M��������I���I���6)hi�I�-�G����I�zA�/����C���k�k�T�X������{V!���ut���u0!�8<\�|�������������e���iry
���G��M�q�f�	�W�1����'��e�5��"�O��,�J��}�^z���Os =F3���t�H�8��U�V{���D���&��R�E� j�k�KK�NES)��BY{_�;�,T(�e�DT����������3-(�����8d���.����E�>U�������T�er�,�0�/�p�)��L�\����g��kH'&�L�� g�|�f�m"���m�jk[�f�(���cFw(/�������6�����9�����wqoB�,~z�5�����%%](�X?y�L�y��}L�}����;���N�����?|8<?�`���N{M�F�q�^�W���	�HU����~�����K�$0�[��1s�8Y�g��>��f�y�\����L�4��������Y���,~
�����w��$�A#��8�f=�I�%1���e�?�\�������]���
9bhf���]-5���8w]K�e�p|�~sx�>���}x�&J�W��B�*��3g�Jr��c7�-�Z��������>j����D��[���H�W��>S^�M�j8s������`3H��A��}�X���x����<�����~��=I�.9v��;~6�m\�ZYB�;���x�d����Q*u�R/�M
���"�J^ t�W�T7)u�����mB����C���(���Ci�7������a|	p�"���moD�c<�{3����m���![������(y�d�&����z��$�\=�v2*J�u�U���<}t3��D�O�bd��{?�71w'f���������������n���v��}��PD�#���Ln0y�P�����������Hn�!!����
�@�iT��Y8$�$�=4�` ���4���c�DE�j�[��E)���c��P�t 7HC��/����,
�}�T����
&��MI<A�$;��C�����54N���9R��p%�;$�&�"�����3>��xC�`�����^g�]�00�lo�s]��|��o�DG�1*~<���y+���4�c%\�70rR:����J3����a-D'�O�7��KH����H8�'�+b��Z�o�����W#\��V/Z�y�{2!�cJ�,IjpZu��J������a�3����8)��$�A�ic��m<����=��<��N;�_��L�wt���h\�4&2��b@�c7k.�g�w��y��%��r���|�)�}:�R>� ����<�r�#U���R�D��N�A"@����v0�7�����VW��������Ty*|�G	P��]��<\��PT�k
��2iHUcx����oL��d"7�r_��U]R��A�'}~2��S��K�#3r�[��Qx`-��b�5(_M��w����FHv�w0�5cq���u����Gk)�M�?���G���h	B��50pv���G����v����z�'���a|G�@��h�|�o�{�gm�?����u�����c�I��v��P��N9���GQ�����s��"�JF�b����=e�p����>+�~�T�JZ���1E����o\Hb`��l�����xJI��E�����������!�r|J��������	�r
Oa{�������p���|�5��x�J�=*�=\��@������b-���[���z���g|U��,��C�OF�S���L	H� ���������i��S9��cI%"<�q:����O6���S���g+�+�����O�X3��=n�7I�B���v�h7s��k�����������uF�ai�_K��ld�la!d�����2B�f-8|��L��4<�p�n���U���5d�"Zf�P����>Dv#8`2B��!
(H���%X(	M��Cy
���G���NmjT��T�7�Dl"[�l&}`�����a�������V�F���la�/����q�N���TL������25���-��d�1����9a��8�<@�z��m�����������l����QE��+�@�{d\/eF$F�e;���~u��x��"�a����P��\��,�Z�.v�hv�b����r9"���T
h��L=r��h3����.���5u�6
m�������:�pQ2@a5�#a?��Pe������ ��-��g�xo�B�<����w[N/"
: a�f��`�*Q�!}R�4_�{��M�*!��E���h���~���B����,�[�w>Yu	-�T����u�{�Oj��-�a��P/�y�������\9�5t�'���t��v���&��%�PR8C��D��d��������{�����,K�<6�����2qs$.�99k����H������w�@3�B��}L���5|�2�|����.b3&qI�;�$J�d��v��qg�g��������d8����2��I���|��V�,*�eA�Nh��\��ZR��l]�s �V&F��� ��`:r���(��5}��1���}�WJ���������(��]#k\s����OO��W�q:N�9�Ez<��E��<�������r���BOZ�����'xRW�x�I)b�S�6j�TF"�h)t���8�l5M�
90[fI*��P=E ��������C�)�sb^��
���=t�ks����m]��
�>!�#_?RC�V��������e4�@�)�dEYQ�Mz����K-�Ckgm�yxrv�q�u�%��(�Q�����������a�	d�?)���w]�n��Yn1��k��s�����y�����`���c�����]�)PX����^<9:[��/r���{N��s�1���kc�,Q^���Y-��}�����@r��~�����'r@
6l�� �j��l�id�S��f���3��m
;�2��vTE��HV�t67}^����]�t�i������1�3��$O��y����Rl\���#�9�gC��Y;Z�;"��GA� Y��%*�Z�	�@3rD�M����N�6�6�.�"*4��D�?h����"����O����5@�$�t[OL]-�R�*~�\A_IVo����
�x,�;���K\�#}�G���+� ��{�.?
`��H�VQV;J���=o��;�����J��tKQ�>���N{�DJo])��,���|�vs��:X#��mDz���=�4
C����w(�����������D�%�"Z��i��o��S����]_�I^�"o��-�j��slj���=k���\B���|9bC"�t��{4�.��M
��v��@�rd�����Ov��2��XYa����+B��~l�������
�m�^����.���t`J��nQ!�/::K��x����:�(�h��?�2'$}Q���}Ft�OuY5?��xj�6������e��dD{f���t�'+J����c�l}hx&4l����~%2������u�RT	�������������	�3����0�kf���/<�H�S�|w�Lp���9|�������m�t-R��f�+P7����N�5&��qS�&���.��t�R�X��/)G����"�����.���B�&�+t'�&y�PP���O�����������8�\���{g�$�����[�z��Y]����p�n������mAnW�����xpg��,cb������jk�w�
��
�,v�jD���5�Of��e\�	g����!�����mu���%L&��P|O�L�]<�n�A��h�mj�lJ���@�>�-����d��mcGkG����G�)��~����E����e����������)r��V�W�G�����{����	��@�x���5k�����a�bW��1��MvB��/>V�;���0uQ��+9�J.%��(�t
-�4�!X0���C9�3��T
��;7�FI.��6�dr���!����jRqWR�%x��]��Tc�~�������
�@��K��c-X�n��'�t(�����3�:��\�	u��{�:�m��Gr�����G�AK��HE�O'�%�|�R��Yq���i���QW�����3H.)��
���������6��]����]���	���14p��msqo���&uE&��H�����`$}��qL��|�8���jG8�:�n��$�H��e�HM��m� �����t��T����_�meO�ZB3��(-M��N8���.:$$��))�,~�E�Bzj�E��[�YI�Y��BJ�QHO����)���SyI�ztrr����c.YY5O4�\sy�E������v������SoU"�9��T�;�r��
?�PzsrvX41��D�������K�
�o��VSoVye����e��s)&��?\���Zp�!p�a��^�e�Y�'�O dC��-}L.yJR���d��{1V�k��cK]��7�r�K�W�	��$$��/�����~��>���"�/���:��PB�}���|��*��x��1��@��0�'��`g���.���X\<yk�T����\n�,�>}�������	�����a�����S�����~9^D�w#������m�X���L! e&�t����g�t��%��CC����]�%Nb����W����$�����@B��&-�������vR��Vwo���v{�f����Kf���q��P���IB`Uu�r(/%��k��HE��,�h����T�-AOt������G�pQ�����d&�
��/���}��Cx���8d�,��w�vy��|8;^zr���vw��������&w�Q��O�����_�(������(L���n�����?��RiLF�S,b�|���6/�/������B��g��%���*�$�=YQy���0���;�r��X��,�8NJ���(s^.�����F�~���N2�qoP��K��#l�+
����h|;�~nnH-��������)�?Qvl��Z��?�v\aP����_���=��wO������j�B�T	�x!�0���n�����R�f���=����e|+���.�#��U/Mh��������1����Ygx�e����7�����nK��t����?�H���c������-S�,����
>��8���1�0dqf���o�����=�����e����f�V�� �.h/��.��_cA$E���)<g��`��>l���hj�.���J������:��m:��`����(B��t�`P��Q���]��2����������AC�
�4���>����[���'�<f��^����,�C\��r6����������"l�<HzW
��/�L�\/�~�ITW��g7��xoA]$Z�_�x�{�N����^��������vg�]�J*FG�u�=������~�
���������[����g-T�S��� �����-qb�u�u��!J���>?'�tb�nY7�S~"���������r��$��'����
v��g�|�m�kxj�����Gt��Qp��F�`�G�j�~MEs�WI���H�1�(�Z2c�_���U.���;��Fmw{���9�������moVw�Z���*�A\�<�6q'�4���G2���9�Yp����c�z�y��Jo���o�����/���Y2L���n��Bg�Y2J�o�����az�r^kx�m�V�4jb��Z���h�4��o�~������Z S)E��h��o��~�b
I�X�aA�L|
��x�����s)�����iC��d���5�!k��w:�+NR����f.[��������V��Wa�������`y)�N��r�St�����coU�6�I}�F�������h�x�����Q�kr&P�3)s-�x�Jw����������?��F�(���eA�D�M�t2�!���8d������Q�����=��{��$Ge�*�Z0�,3?��e�N~CWm�%�������f�"����b��d��]6jO�z�R����xr���tMS@a�����[mF��LDE{�ZS�w����'��gz���9iM��@�
�_��{x����c��AF�ps����lb������tS����*��t��(y�Q��L�!J��>K�u��9E)���9����:�3A�Jt����=�~������3Yw&er;���^p��)_�
���o�kq�_����`�18x0���3d�!	�	G���%���%6m�anbe��
��lC� >���Mi|�E{��sxGW�U�x|vUlUe���1��n�^������8���q����Td�G���������e��G��b�������~/���3�K����G��E��H@����?Q8�D�Y���*��K��S��g�-�t���a��4$���h��
�����R/�^�|8>�^�u7h
'�/O�Z��S"p�L����7s�����P��\��G|��A�F�������4���K)y���4(�n��m1l�����01����2�T�F��J�o���[�9��^LB���������
0<�>�M��HXD�x�D���\
�S��s"�~8��K8�{����]J��k����}�]���b�`L�I�H���3Q�!F�XzF&6d�����;�1����H��X��t<��}����4>�cDE�R��Xo�|eDb=���/�|���?���oc�E7�<9���C�c���&�s
���������QPog�o���00��2��q?$2(�c��I��i�nf"�sb�=<z�l�7�����&��J%�Dr
�6������l*��[�:J��y�����G#�n3��7�Q���C�L��<����kt�����&���D���Vurs������~ ��y<�`s0���V0�Q��xDiU�)������p���)kB����2~��	E�����;���xx�fr
�.k����70������3������jEL�a��P�a2D�
��������@im�M���l��	����B���z���C5y�������t��������B}%ko�
0���N��h��]u"A��F^�{�a�(Oy�zZ�'.������������������)���[�3J�0MgX�E��p�:A�}����4�BB�=��-�
�it1O9zu2S!�.0�0FF�47���<�f��
�`�����}YT/�:B9m�)�Nn���4?y}]��Y���2��?
m��E'�_�[����`�O^��R��<����F��8�}���?4���8��!,
�#�d`�0/�+��V`cCp������x�h��H�{��
�)��#O������H��i0i\�
�����l/N~���8&B�G��6��q�l&TD���Z����ywv�S�]������0!���Ynq
��F��2��0����O�`U������H9�a.�Q�^��������wy���7'��~��{nV���PA�7����h���]l�PmIE������Q�l����nu�^`��i�*;�5���Ri�[<�P<���*�3k��t'��� ��.�nQ�l�Q��`��H������3"~�^=��g+~���h�Z�x-�v���n��a��A��4<z�������"c���z"Q�,wlX+�qn}�C��*N7�z�u��h(�\F�����
��'Z5��u`O���,�Z
`�@���>��c\����*�_�UKF�~��H!�Iz�y�3�<����S���`��Tv
� _���@zC�W�?�����.���0������:������
*B8��
��M
���#���7���E�K=��`�/
d�x���������I�KYH�768-�s����fu�y����`����l����^����V}s���5�v[E�����u[��{{G��?w���hpR�G{�
���{U��4`  ��gCv�k�x�i���18@���S'��2����w�]xd��A	���i=�9���Y�r�q�[[��^��d`��;��`����En�����=�������9^�P�!�Un)�R��:�g��d���Oghq�O��
F>��9����t�;I{��9 Ok�$�X06���zUjdi��[y��wF�����O:�����Q{j��v�~��2,C�?�B����>���Q��6�QoK��4�p�F��I&s&���k5�*�f�.^��Zz�(T3�`�A��<I���
������~�$z���\�=yR�n��G�h�"�����) �=c"���� ��������:=�
������v��a����>H�6�J�������K]5A�"Fb����M
HD����w`������k5�fU�����5f�����I�����*�4����%�	��B����,����Q�?N����h�)�}��>jf-�e���]\D���V�Q#��ih�bK/��F��h���'������iE7 �&(���8E�A�v>}�1�D qc=rBQ��)p-�{��Q�b4�bxl�[q�n��>����ZS1�8���u��mm��(v9�������&����il�����4*��#N2�+�F]�C��P�o�},��!!��e�Ce�����[|����~�v�Uil��l���j����8m�.PX������"z!��/0��lT2�x����>`���!���v�B�j�j�Jsw�����1�2<�NV������q�cX�I�f����Zr4��{���{n��G
e�J�G]�mDH��7�6R����c��Z���]��T��0,x<U)�H�������$*��GF��OO"��A'
ur
��e�J.I>�(gSitW��hz��d���j��wv|x��y��('#��j|L�������t���xs���!�R�@�U$Z�l�:����Aj���0���E/��&���L�q?��H�+�k}U+#��p�2^�<5Kn6Y�Z�W��S�vb}�~u����W�1�J��E�H-�����'�+:����f�E��X���%��X���@8����f��=Z�t����}zud�
HM{�f�\�p���Y�������p{��#tm�,�JX����;^1JT6���/�&�K�W�-�]�C|�]����O�U�D�{�i���������a
�6��g�� �+8��������O������\��zY�D����]2Gvq��}�~�U��{�/�"���|����_[���Z���X_��xG�$��/���O$_nU�Y��~E�B��N�����_\k�t��/�6��iS��Q�L�dc4�*�[���O��}������|B��x���<�M�M��]��Goj���0;�L���S����g��M���D��.0����N�.���2z�>?�{���z�V��l���������S���
��;_%�k����g�q:�{��m�a���������HMw6�rpO[��(���E��Z����r���{�����.^-�z[��zyf�<^�"���r�[�����c�#T���?-�*
���Yh�g�k	�N�H�~��L�4���d?M����(y
������/.�$k��}�~cC\[�����C�1F�{�N{����!�{�$������������r�_��E�*�����':�
�A���-,!Y������zY�9�DW�f����=��f�����n�����������������/�/�:|��L}vG�T���������vsl'�|����E,�����x�IT�Fc�RoH_ �������8���� Yy�o����~��Z��b���-x�hzx����|�]!���S���D��=���������K|ahf�js����-l5����xf���sz!�;d���|>e'��V+�Q������jb>����}����7�r?���v���92�W*W��������Wa1t���Q���c��u1����V�_�z��a��f��4��3��N�����h�_����N�3��>�������\�����X�F�o�Uc�������i&�uqQ��!a���P�����V��@���+Kk�W��\������o��
#48Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Pavel Stehule (#47)
Re: review: CHECK FUNCTION statement

Pavel Stehule wrote:

so removed "quite" option
and removed multiple check regression tests also - there is missing
explicit order of function checking, so regress tests can fail :(

There seems to be a problem with the SET clause of CREATE FUNCTION:

ftest=# CREATE OR REPLACE FUNCTION a(integer) RETURNS integer
LANGUAGE plpgsql AS 'BEGIN RETURN 2*$1; END';
CREATE FUNCTION
ftest=# CHECK FUNCTION a(integer);
NOTICE: checked function "a(integer)"
CHECK FUNCTION
ftest=# CREATE OR REPLACE FUNCTION a(integer) RETURNS integer
LANGUAGE plpgsql SET search_path=public AS 'BEGIN RETURN 2*$1; END';
CREATE FUNCTION
ftest=# CHECK FUNCTION a(integer);
The connection to the server was lost. Attempting reset: Failed.
!>

Yours,
Laurenz Albe

#49Pavel Stehule
pavel.stehule@gmail.com
In reply to: Albe Laurenz (#48)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Hello

2011/12/15 Albe Laurenz <laurenz.albe@wien.gv.at>:

Pavel Stehule wrote:

so removed "quite" option
and removed multiple check regression tests also - there is missing
explicit order of function checking, so regress tests can fail :(

There seems to be a problem with the SET clause of CREATE FUNCTION:

ftest=# CREATE OR REPLACE FUNCTION a(integer) RETURNS integer
       LANGUAGE plpgsql AS 'BEGIN RETURN 2*$1; END';
CREATE FUNCTION
ftest=# CHECK FUNCTION a(integer);
NOTICE:  checked function "a(integer)"
CHECK FUNCTION
ftest=# CREATE OR REPLACE FUNCTION a(integer) RETURNS integer
       LANGUAGE plpgsql SET search_path=public AS 'BEGIN RETURN 2*$1; END';
CREATE FUNCTION
ftest=# CHECK FUNCTION a(integer);
The connection to the server was lost. Attempting reset: Failed.
!>

There was bug - missing detoast call

fixed

Regards

Pavel

Show quoted text

Yours,
Laurenz Albe

Attachments:

check_function-2011-12-15-1.diff.gzapplication/x-gzip; name=check_function-2011-12-15-1.diff.gzDownload
#50Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#49)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Hello

one small update - better emulation of environment for security
definer functions

Regards

Pavel

Attachments:

check_function-2011-12-15-2.diff.gzapplication/x-gzip; name=check_function-2011-12-15-2.diff.gzDownload
����Ncheck_function-2011-12-15-2.diff�\{s�F����]mB����Vx�P��:E�IT����C
k`0�df����{�(��m�Uq%�0������_�����Z�^�����/�]�I� ��>�������Ng���i���I��d�������v��~�����R��~�d��:�?<�������?|f����f�������E��=O;/��w�G�}x2��R?�0���S��+7��<������}���]��tW6.tN�K��|�t���l�	����3��C��-%��%�G���&t���O�PsBe������!
��Q�3�����|�c�\0��Qs ��`4e6[�n�G!K���A����[�TN�h�pB�?|;�{}{1��//Nw�J������7�k��E1���/���
+X4K���<��V��o<��?ca����O[ ���^�</�/�)P�Q7�X�|GZsh����]^��uy�R������^g/U��^�E���U�d�>A��!yB�/@%q�����`��U�E�gX"�O���N��-�T6�\�+'����{'�W��
���td�r��^���[��W������s�f������n�d��d����t���n�`���t����T09�A��G����J~8G���j�q���l��>L�%sc�$�-��_8�����{+�FF:M���d�c�"|4�!�T]'d�`��"v����!�{��e�����<�|��j����������;���H�N.�N�{N�.H�r9��c��C��\����!FS�8V��%�Q�h��K�� I�CP��:z�+�J4�T�NB��H�k�m�[z�f���2��`��t���Dr`s��
6p�qvDh�������;D��5/��3e�[��K'v���R�z��S���lc��"M�������l������ei���R�����<��T�}�e���)Q:.�H�E1m�<�8<����)���v��m���>hv{����5>8�����g����X
@T��%?a��H�Nq7@���9�"S��H���9n���<%x��z]f��>6�u���"�sI�+�{?��_��@<v��N�q�����Q �E=�o��U@h����}�&�
J\�X�pb?�WO�8��*K���1��v���z>���?@�����5T�o@�0�J0m��)qo�����$��'���� NI���B� �V�`q)
��<l��9�l$�]j�p|��T$w#��6��T�p�b�����8$;v��WE�e���R�j!�����'�	�L
Mr%V�����IZ�E��#8ZQc�ig	"\�>T���K�@�7?t������rs��A/g1�;���?A���d��	��,�LD�����K	����6�^H��i��i�5H,��"x"�5
�D�A$�"l�;��?P�r�HZP%I�%�QM�y��`2G��]\_�/���h;	�<x<�F������q��%��$�w���b�C�AV�j���`��?�*y�Xn&�W��*��S��P������j�T�,s��XY��k=�a�+�=z�����^���S��U�n�1w�9�����{�V����{�~5���'?�&�8~��#3�n~����g[T5}�:�c���"\��+�'�J:��9�V���t��i�1��Rg�P�T�AU��2~e}�a,����(K�����_	^>C�*��m�u�*��B�$,���S�����=�uN:������k�A4�{x�A���u,'���(���h0����5�]��#���/������9<�.�����)���q9�8r~�n����G�x�k��4����|�YG�������|t�T�0SX�t�������I��� ��ap>>L.��`���M�.���B�4�3]���w	��j����~��
�$f�t��u�����s��v�#*�;��V��I=��0���NwSa�Ibj@�/W��rvh�i���u������Xi��U����|� 
����P�{�"^@�"��eO��QJ�U%j���tqJD�vF��r	Vz������C�$�P2;��+�C�u���%����PfAw��4&�Vz��%�_)�zEt�p�
12|)�Lc�2�2F��4��6�����y����H_���"pg�3�%k�0�T�I�|;������aN�p0���`MFgMv�J���GJ�	�Q(��uux��8��HdZ����o��le���2Eg"�fVET�_vK�FY�L�Ad�V��^--�/(i���v������i9I+M�����J���ku4tt���������f(j��dzp�<����-o����^���G�N^I9}�	I2�*����H��`�>�U����MQ��'�_��uxpxp�n�?��43g\=$�9n���^g�P��@;���::���~=glh����)�5j��o�n��|�L��W�Q#p�J�M�c��y���=��>DA�Z�Ow�Cn��gQ`;�\'�o���]�������\��&��)S���E*7���x	:��hW���!!N���+�*��? �a���*M�KV���p�
���rC��y"��#��,K���qV(��S���/"/����:���������~��Z�w4����[��	:2�^A��X������]<;�AL6-�)�����""@qN���]�?�J��������/���D��N,?��/N`H*V4b���������'/Da/3Q���(��y�:d>�L�pci�qi��
@��T*+�"���s�Xj������h����$�O������$9�P�!�.x-��&���:�]����\����N�jPm���A ���0~&��@4���BvW�$�}h�bP\k���u���I�>��Y:��A����r<�-�F�Z%��2Q)
5�f��B����P���*4���=DW.FFA$�'q�5�6a"�}�ebJU@�0V8i���D�Y0�����-A+s@��~B�7��e���$��?�F@�G�4V�KLmh�������W�T��6��s�5�r$*E�[�n!R���F\{J`���J�;����V�QeL��
Y
KU����^
>m�HGMP'NR5��i3}���`A��
U��������x�	aD?Id��l�������].�DE�O�w/�*��6�Nz_�	��IP�*I�����>�B0���.WIJH��-r�.�x�B<�������Y��K�kM�j�rH�*a[��J�����*�g��Ye��(��(j��F�i� ��t���/S���U���<���GM]�c�V"�	Z_X��>�UK����*���.��B������ ���sK�Q_`��9�P�#n�>aX���d#�!��w>���w��6L�T�=R������B�"��cN�]�0
�`6��4;U}�_��P�J�������Q��������"�b���ku�{��{���f��z�K��#�x:��N?���3�%_l����w&�����I�+�/J������S�>!��t����J�/��~o.I���Jr�������L%}Q�${�OK&�G�F>�ko(���\�9�Q5(�/
����<y2Qd�m�^J��Nz����R���� �p������m�~�|W���r+�����a�����i�����t��(m�o:��t��&����u�yc���G�"B���e���s���V1syP�EQ2y������.<�Y=��j��L�>��N�m|��=��6����o�[B��#-���>f�q].��1S�~����;��J�N��X��X��q�|G�����ZW� x	P��^�=9TUP(ga�/�T�qQ�(T�Kn��	���`7�xN�����YX���.��f���VCdZh����2�j��U`Q7����|Q�+�>L����a��-�g�7��<F����&��~M���R��W�������~3}�6%�J�v�bG�WP:Y;_�PV�Y\<E~��z��Gy����b.(CT���X����U����>n�����O���qjL?��d�1v���l����=e�����M��4�@ihH=d���������~9K�.�������O�����B$z+wJo+IB�V2!��?�_�.�+���7��[����/o���&�`��������}D~k��]eL�-�b��`:'�e�k�j�W�j�!���L���l;mi�)���2�����k��PJ�*������j�N96�r���m���2��.��Vr�n�b�����U�����L��{��3'Y-�Jh��������X�����f�����x>p�,������8X������������(��/3�����e��������@�eu[�
�f4��\�'��n�r�t�M�t��N~"	5��������(.��fY��lg���	�u
Y�;��u��z�Q��z��d^�Kj(��3����b�C��,7
��)-�<l[�d�������9����	l#�<�i� �@���%��<�)H�k��o�����"���(�����~H�jH����
�n�{3T
 ���B��i�������'sm,\�^��1X'.��{(��(~�o-�����S�\�������+�QxA"�����[�o=�G���Z��������A��pW����%�&�:?��
��Qz������5��)��2?��
o������������ft=>����}���TmP��0gj�`o��Wf���(��s�����)UHR[Q�aN���1�+��b����Y*�lk�^��"sZ����z:4�m�4��_i����2�����A��x,����.��K�j�j�+��B@%��k�I]�`����ov�f8��E�����Vj2����//�rf:�4��f��Hs�Q���t�&�~���=�K�:����Y��j����&�N� �\X-�uCz[�>��!��m
�mI�RXF��!��[�V�sPG���l�zp{>�~?���m����b<y�����b�T%���)e��y��~����!���JzDx�=m�#��iD�N0�H���Xq-W��r�b����tr��Q*��`@5|:A��{�
@��,�`�������s��*�7�M+�)������d���r����R������h:y{}�c�8�����G�j��+��� �D�H+O62D��6(U�tQ�~�/#��k�7��I�������k��=W��kCT@��CP���������su�i�P��� @7
����d��1���6+[)X���V���Uw��&��m�9��3y�n�U�rm��3}��=��ag��1����a����z�J���Q��������o���o�E�}K�-�bj��O�����<$��x1�TG�t�J�Gh�HO�=U(�dV��C�����Rb��RU��}���i	��2�����:�<U#j���s��c-�����������g�di��.�
z<SG�Sr��L���
�����p�T�������Z-�s$�v�|abCQ���^m�G��kJ���,ft=���?.	�c�$�$>SF��r
P�L��}�0�,v�7�Q����G�
���:�]�&'a���":)i�,�S�<�$��~���9�@�lk�2��F;};w�6u���n(�fP ^!l���?^��Py\����+�����r"�I�fI</m���U�V��"��I����	�i2��X�X��t�i>��:�4M=�����#�����#����-;I@a��/���wP��v�u?�5����4Y�c�����
�Cg��W� z�xR-o#�)P����W�70��1T�9�"�~�%�C���f����7�y���(�jDE���F	�!��O��C|��5�@@/���\}# 4����=�"�IG$�������������W��&c���sS�x�����Fe�<�'�e�S�	���^W�����(���F�F��h�0E��,�������cY�,~������m���l�\NN^=B`N����L�+���.}���	��}�W��f�Z]]]��"\R�iv����c*Q��Wv�RY�T"���*�S���~~CxzFq���FP�h�%0��~vA$�e��1�a[�^��s����+f���D���%�qx���k�K��k.+��"�k�C��	n�O��/Cjn`9B3_��������U�6�<�
�y�=e��@E5���I�0���_��-�{��25��u�����7Q���sE�(Q>j����r�ojz��U��� 0��^Ab��q�X@���(�
$m���q$�.��.������sK���Z����Z���o�^)}��+�\�d="���x��8-�3��c\�]�S���f|
#08{f_��2��������Nc6������w$��p&�Z��_���FWB���6
���]4�J���a�@�ed���3��C���iMU��t�1��E��W�L��LVY�����������	Yf�v�=����]Y���D�Ge�A��
y�%`�"�	��(A�8B����p�@usr1�������~�i�����;�6z�Dn��i��'vB���C2{���S�BN��2���I���"pR$�HBq>�M����x|�P�0�v��P�k7B�������EK5�&�c�3�=�;^@raSKJV;5q;o�}iS��L�+����� �����h/`vAq!C�0]�.��(�~��L��]��8=�<.�
)��^����>��Q�@\��w?<7����|�)��i4��lLR�s����~���['�;o����*u���|������5fku��jYh�V
!.��&�v�}|�1�N�,#`�a��*2��JKY�K�|���kM�Us�y���U_�����f�����U��#>�I��Hs9��"S����v%��5�n����gF73R:?��!�e��
b|�r��~8>h�9<ntO^���~�Q�)���C1�I� �,�r����z��I�`g��vr
3/�~�A�-4��k����
z��|�W9xo?]u�pV���xO2@{@����>;�;O��3r����a|�����!��R�~D�W�$�����T�Q�l��^��2d���a��G��y+���LI�aB���:����D]������K�"fD,���_���
kyt���0�#�8GZ���d�����~�+����g�=h��w�S>�|���CV�L�Q�M�LBa���=A���f��2(��������XvL� $'�����<|�|�pvp��T��j ��s|5J�+X"���|�pC�����;�N�D��N0�v���w��2��f#��M�)�A���/�����z���%�x��V����g#��l��;:y�E>��to�-���<���|����S��'$���RRR)U�)I5��I��D��y{�����E��)5��Olf��$�$����5�2����|Ji�g��r��\c�1������y}�k��\�Vux�HwRA��Wm~��:�7�uG�3�[
qr�����aTL��0��m��T�'�!�%4�&#��$�Ob>�C�q���r<��qmy�V�xqN�E*���e�vu��^�"�(������o�m-�C����>�����1�G3S����2C2�5��{s���k@��d�Z��"�����z3��V��[�U*�������C2�
a��.������r�.��D�X/P����+a�R��MG#r0�����3�Y���rUnO��_��%c\��(D��M]p���h*�����};��/�����������j����[+f�lU��u6
���f�T9��@�3�Iw�����)^�d=�im����v(9�1����yN���FT&PjbNJ�����������2��/��)r5jl��{<�s��(%%�;�/)0��B:�����S��2-�,F2eFhG����(:�3�<w�����>�#��i��ay���N�UL�(�=Q����F���S���Md.������]M�_o�;�1��&�r��L,���pSe �@u]W����*2tJf\Z��A(}^����1��8��lb~��h��C��'�����goR{=��/���t�sM:����n�^�1+�l��g�����p�������L��Rf[��i2d��6��^������Ogf@������A	������f.��|:�Z��]5+��T]��Q�{|���w_;��� �/($��V3�)���P%r�������������aO����;���v�����V4�F.i ����5|}�~��w~�S���	e��l�[�"�w���V�K#�5��B(���6���]NgFM��7��u�B
+�{���+��V�(�����X���p�q!�{o�t��{����V^��&��R�m��t���`�I��d�[\��b���LQ��(Q�����)qzv����^�����_�a_�/,��A�O�.N5����l�������H��Z5�w�U�{�\�w=��j-���P������4t���CK7���_:A`���4���LiV>v��z�;�/�d��o|�sB����Uy����,6	�H�'.��+o�b�
�8�nW5�}d�����A
Z�'�n�������n\x����h�*���@_��
���L7��
X���#��x�Z�B�$�U���,
_`�ae�37"�����)cZAk*Et^KF�:����L
�D1��b����<�R�?���u]Kl��E��4�0�g��I4%j(��JS�b�K���4w��$@��tp`B�����������}o�XV���i���}��K��������
!�o��5�Z���b�����?��^�a��:�����K��J��������T���P�-����1��e��C��cL���2��#b�{�����wP�|������w���&�|d�$KWk�D�x� � V��u[�zekK���������l������;`_�����FD[�1n]�
b��nX�0�(�g�
Ikn��@�g�;�����{��C��c�@z=c�������n�`����������N����W�!��SH�y����������k�������j>D�{~�[�,R�>v��F+S�i�(����2��!��Y~����{�=�%��9F�L)c��?9��}���9l�Wm3o���xs�9;<~������������w�w��'���n@^�����WC��5���X-A�K����K6�A�RkmU��m��Y�����]K[�9��%-�\��������r�(2��4�;� ����W�f���&��,z�V��V�Xy��*0F���ct8z��,[��sc��Vu��Z��fQ���n��"� =j��v���� �������n�U��wv��?�;k��L��e�~�T���jb���4�m�������,�o�u�@�������$����
����ux��>`���;�{�);8�T������qe���lz5��l�y&��gB3�:
y�@�;���Fu�@�U�
��J�!D5~���u�0f�)��|���9D(~����%��=���N�F�	��������lT\�8HhE��*�����mG2���+0������al?z����#�Z�UJ�2��W��Iu�0���o�k��'�_cWB*�@$��L�/��������X`WwIfAG�.��]m��G�o6��X��-���T:���U�W%��Gu2�0l�l�����
��6 ��A��w�TAGTx����H$����[�J�FV��b���W�[2Dt�����j��z�Q�����P���}�~\`��'�W��}Wu�s����?:���}m?�FCk�z��~����y���w���S��L#wj�#�������u���BK�]�V��B���m�x��d����s��h��\>�$����
�/���<Eg���t����1e��G��4j��J����v�Ri��U�ATH�F���/����S�>{E��}�-�e�����������.0lIm����9�.�����xx8�J��g�>��w{�G�3��,�
<������J\s5����������R��@m�v%�<���wV���hY<J��K�/;-^2���W����;�E�v7w
��Us���Z-g
�m,X����u��z]�3���;E{��~��e��R��Q7�S#�����y.\����-���4�[��S������uT��������G��H�"��}L�V�����[�
|@��pj��U��<[�+��y��h�bk��#��������9��8+&�>}<��g,�<��^8S�k<{��q�����!��beYI��E������_b��i�?�SA]��6��:���M����<��r�����p7�n�7�w��u�w4��s�������1�_�>@���Z��+# +@����tO���	X��>��~���x�m�1�V��B������O�Zf���Gt�X?+���3�e��<�(�.e��mmMnYD	6����L�F��5���k��wO�c/8o��{bc[8w����Y0,��O�6��,3;�V]�_�Su���]���K�������{fk+�S��'�'����0�6
U3��Q���l����
���$(���H��4�Rh�D�2����{ga������Kr?I�{���z����v<�B����(�c����y[4��1�^�����fbj(�ln��pS�D[��l�C 2����L+q�1�'���;������5�����.]��	C>�Q/����8��l�qO9A!'J�PH��8B��mt9��PoV�3:�Y<y����u�u��*)�k�Jq��`.�F��j��_�V|g�^���,z�h�D���R ��~6�c�*��,���P�c�i�P^��;G-sd4V����FZ7e#��xm��.sL@��V�^k.<��GM	��tuX�M�w�g=����r��
d"��SN'1Bv_��C-���a�>Z��l��M�����0\���+'�������v�y����uK���W�l}"X��G�=y{�y������NX��d������[L�H�[Y�j(L�U�����[�z���h�KJ�+q=_xo��I���������#�R�x����%J�����:����I<U�w�~f{�9eh8��UXf�}��a�����xN��7�Y�c<�������n����.)��a��^d�,����F_-1�q�C������e<-����}�����cL���c���[�T<��:/L4���u��7�o
��u�v�y�o
��>�v��T��"d��2P1��!}({5������Z�wn~������w����]��8�$��W'g����/8�U��N��p�]m�\�������l� ]jVw+[M��s�Y����U��f���wGi�5S.#��jHb#����#�eT��e�����W0�U����5�K�z9��YN�_
�T�^��z5��%@��Mq�Npe�����o������TXt��I���+r���y�u�6G���������

��d�$�5�=�2�cH��6uY-Y#�;������{����}�G�a�����Eb\
=yXQ���F�����������ve�U2;��<���!��������@��;����$�� #_(P�O�����J���
O�Uk���D8�����C�P�:}���G����hc ���cJ���J������6� `>���`��
J��$�h���O�-#�J�j�����$X����'g%�;��o��ZW��qk������y�V���,R�WJ,^km�l��J?X�|;7��5-�8�u�\��Q���w�\�8%O1&*�C��d�0��3��x��+"P,l@R�wQ~a���Z����B�����z5��9{����:�1?��u��.of�w�
��A�������n�P�����Rd�h2N����g�P%
x�\���`r�D��%&L"��G�;-��m�����+����"���<�W&��
��n���E,���<C
��-`�����]i��y���yr���e��<s,�X�W����|��X���b���JV�g:�����������!���eb�����[E��ZE�����)�	�l�Ft
��]Mc,H��1Q#�]��$����d1-��y����X��AVdo����0:�tx�	���N��Ip�����v����2�~luoF#�?&�y�Ee{���okJ:V���2+��uu�_)��`�{�O�:����:�3�r�w���j��������������1�?:9o���T�h������mU��o���_�w���6��5���������:��Lz{x��wv@_O��������a����n������M���N���l��tx|�������������O����Gj���q���Oy|��X���1����4�^#�F����|����U��G��$
�Y���h����d������YeR'9��6�)I�������
�f�D�pfrQ���J��\��94���b�L���)P[��k���!�2��sq=�������Va�
�y��4B�)���q8������G�l��<yY��E`v?|h��R��\�/�S�
w��]R#��H���r
b�R%r��H���?��gJs�Lh�����u`0������#��Gl�HA���8��g1oC�/�%5���8��}����2��y���6�iF=��in��'���U�������T�c�:6_�I>&�����"'���1e��+��#����hHF$M��u���R8`�be��GOQC����l��1 +�#N�P�vY�-(5�|���Pc\*5X�W�3?�<w��y�w�����}��9J[����S*��3���-���B!�� �Q����)��W�U-�`d�9�����QOg{ho���U�r�t��<���U��z�u�~7b���}�V^��`�
��<e=��d �O�@�_-��}o���$�Zw��(D�y���h�����jG0�#�	���
c��������+h�je��3�_B*Z(���~��[�[Q7���E�������
�Q�?� �����$������AU�8�V�)������&�-��"H�>��Jl�O�!myhi��d���mk/:�rcXK�c�;F�\M��,o&C��f���X3��}�[����ym������T��0��\�8~�1+t*������!t�xE���
��p\�R�(��:�M�A��T\��x�S���D�Zj�
v�� v�bG�>`�]K�����'W|h����?�H��-�U���*^XG�h�[�� �>�M��7���/z�^����#�z���C����_���$�J�����ih�<f��]����p����������z<����w`�<4~�3zY_nu��q��R��4��4�F��I�p$�U��E���eD���m����~z��iw���'9k��S�g��LL�����/A6�v����AD8��@K#�{I�'��)K���O`������4�)��1V�����m,G��B��Q��"��H��s���#����7�t����=a�ot��h�����/�����cx#�_�/Fp�
��7��������e=���P��L�cl�Z��L�:#�`�k/��x���^����U}�z2�E��]$2����9�$af�I��uvG��6�#F��}Y��X��Y�r�UB��OH��tk!��EQ�vk��nK�����R�����,*���g������s�:��A-���g������9�f�fN~k�����fs���So;�l7������GMvQ�����".�(���a��DkJ�#�����6�)��%�p�Yc�.8.�S���r���	�����C��qJ�^�J!�������:��##
V�w�����W����"��BU8
l�O]�������g0:�m��E� ��x����e�~ ���
#z�����\Y�:��v�4�<<��@e��
�B��D���~���
i���w0B��y?I�;.{�!�~���:�����-�h��qqaR��s�)/�F���OG��W��L�����<�Uq��K�e�'�p��eW����[rs��b�u�S�N����-�#�F��l2�a�T�����I?�J����1K�{���ZN�ZVQ?�����nie#���k+�����O�JY��������O�^�z�5�F<$V�|z4�:����Y7j���,�w�^��q�.g_��A�.�]AN�aJ�T]�^{#i���T��}��]\�����4��o0r�O�:c�'���p~���xSZ�)1w���?���k������;�?���^���	 ��4���v�2����?������m�b|��CK�U9��D%�����2I1uM2��#*��L��q�i�1���4��?����wG���{����KN?x(6��J��0acmV�o��EQ#LDsAt3�_�_v���7�P���)H�O467����#�2�D���sU\�}�A����'��$�|��r���\P���).\2�,q��kf������k�,�7P�����i���^6C	4e�k��f���"0TJ�x9������@�L���m��7��Gf#�fYB����S�+������|g5*�������������\3�4v [B-�9� �G-�f�qxVs��YT�RX^[VkF��66�w��m�_��Y�U+��i�A����CL1q����FZ�����X���h=��2}�-4�_�����,/��P-��O���DD�w�<�:�rl�~�����t��I��'q?�y��2
�t�3��Z������
X�|��[����2��"���}���8],��`J�dyam���3��n������+���Lr���l;N�D%b�����l�	4<&eg�� �@��8g]Q�V��
_�XA��F�j�H&bY��1�i�KR�7�?���}]n���;�0�����Z��K����]t���dQX������_�`�C�����i�����)�t�
��i����<��7��G��d����bn����k	�ahw�]�s���Vm�U���U���l�[��
>��	�&z*��f�q������G4A�������A���*�t�<VL��gnO|�[>9�$�x���@q������4�e�<�N�T*�����/�P��/�Pr����(7��
�1B���W�O�F�Ud��a��w)���J(*(��P��J��B��*i���a�� �C�x�eGcY�N������k�{�����8�f�N���5T��IS�8|2bo��W�K��j�pX>9������]�J�N_b�WJ;��@�5���n{Cf�����%��T����R��a/b#+�Vg!��k��}��S�4�6��}^�}��av
e�#�+C��|M�����
4H�6��6M��A��"S��^�y��+b�gn�_������E�<)����5�; H�����Nc����
��c(;�S�g��1�V��Q�Q�
����g�{}(�������e��+���0� �rf����|;C1��x����0��L��p��	�����?��7G\�=}�dB����&x���u��CiHy��B�q�uaQ�������C��k�I9$������'ST���W0|A>PUn��Bh�2��A1b�p��Nb
�E�ZN=��o03�*��(y�G�oq�t~9mw�;S1�lD�hV}�j��z8;�)���������E�|l����<�������D���W:8�������}vvr����x��:��v����J�=�
�1rdRr�6<�@� �Y�]~xM
�g��)��Z%��
�;���!~z��oZf��]7�G��I6u:V����:��X�K�A2$�/��1e�"���K���c2�,�8�9��*����J��;�����m������
������/qvG����||2�}��m'�!��'���4����1}���Pu1�����IU�)���L�X����^2d)x���Z���G������h<b�k���Z��}�H�tHnFQ�o
����/@N
�$�&�g:��q	��j�\Lb�4R��7�f�
� c�f�
��?zx���*$��`�a��(zt�vON�O8L�
���1��z���=YcAZ��N��M�	3��
���MA�N���?�RL!9�M��8�R�5�<����]�x�uu`�1m ����������'��S����fW]mi^o���l�`E����L
��OJ���IAK�N�l�=�����H�2|�|���']�][����_T~��
�&���K����	���!������p��u�_U��.��O���hH�?��h
�S6sN0�b�A(�4>)�.3���H�i�Lf�U����#��co4�x��	0�I<�+E��,p\����s��$<&5���R�(��Q�\�x\ZJv*�J�\����J�qe�B�-c%� ��x�������>e^V�iAQ���!�7�tQou�/����~����h,�J,�{d���}���OIwg��R��g?%�XC:1AdE�93���5�o�0�_mT[��5�Ea.3�Cy�/�
omm�����v(��\��{�d������dMG/,)�BI��)�+e�����c����������t�����}������a��u�k�6R������x@�vM(F�2����3��L�\:4`` 9�a�J���3����?��d�A�4[w�K�:��m5`
���|F,�������g��h���$�Kg&A
�886�q4�IL"e(���--+p���l�<?=�����T��C�0���L�j����`���Z�,���������Aw������5P2����V���9�V���	n���4d��l���Q��GD$t���6E�����������nV��#$� �FT�A�������`��V��{D����?~���k����K�u�������m�"����A�(�;%K%`(NN�R	��@�z��nR����P��K��
��I�sd|54l�dE�%���D�'vJ���e>�}
�K `��1_o{#z����y�Toc6��
��7���$�E�&S4o�e�S�$����@��QQZ�������������'�|�#{����������;1�O�N������?�t�?�����5�"�)h�dr���p���
��>�����Gr	�.@%,�nXrO�ZE��!�%�����W�	�N$*�Ws��.J��|�0Fk.�2���A�0��~����di0����65n0I�oJ�	�$������g�}b�V�7��q�O$���zT�+9�!y5��������Y�z������:�����Yg{;@��������|;':2�Q��I�[A��+�
�A����y>?�h.P��G�k!:Y�����]B2\=�E���>�^[f�"}��|l�����z������	ycPzfIR�����W���M�
�e�d�I9�X�$!
�L��o�Q<E ��aF��
v�����f2���'�e�C���1���lc��Y3p�>�sp@���SM�(�����F��N������9���U��$�C�����'��t�
z]d�������7E����vXt5�������P�c<J�
��4���O���]k@�IC��K�%cR
%3�����������x<����T���^:���[����k��s��@�jrG��06�4B�[��Q����n�<Z#H9o����F<���GK@`�����+�.?*��%���e6��#�<�@�5�;:����E��C�xx���|8k�x��������'��@~p��M���s���b�v���78��Gf�{��U2z����(�)S�3L�6��Yy����P�J����(B���|��C`���`S���.��SJz�,",n=w��$Gv����S�E��-Ho�M���k�x
��}V���+<��;��n��W��Q�������Ukyp}��d�0��TU?��fpe�^: 2��R�H�dJ@�1����'���7M[����NK*��	���1�$~�q�f�*�W<[�\AU^7�xr������q�I�*(��G���_����'}�tU��0�KK�2X"Xg#�f!�<�d$�b5k1���d����i��t��V�"$��!��2�R�}�� ����<
i@A
��(�BIh�n��k�]W=��pjS�������'b��f3�SEE�v�����87r��d� �}	\5��Cu*�0��b�F��7p���	���hq=&#��a|��	�_����R��nm�d4e���7]\�(d�'�Wd��(��X�b��#�z)3"1-���h����kd��������r��Ee��zt���E�SK�~����(��]�R@+d��{%G�q���u�'�����ih�D,���������
��	�a��*���_��\nA�=��x�8�)��v���rzi�	�7�#�P����B�����nzW	��.r\�G{���{ob��ndA�r�����Kh)��0]����~R�oa
���z��#�0��\^O��Y����=�� $�+ ����|�0Y�.�����j�%z�e ��O'X�Nnu�w�gYJ����V����#q���Y{o�]D:���x����a"��c�,n��s�)���4��t�1�K��&Q"$�e$��_�;{?�$gD'%vu'��~�yPeOb���;����`Qq/�wB���j����g�����21
-�1���T�F!���-�������R����W��v�E����X�������|zR����q��!/��Iu/���A�V_o���P�f<z��@e��p<�}>�������hLJ�J�Qs�2	EK��G��pf��hZW����2K2P����)	�H��<H�5G$*MA�����n��������^�;wg�l��=m��������
(�"�^O��,��PM&+����h�3V
]^j9Z;h���X�������/��p@��Z�=�(��w.�${L� ��I�>^��
v�d�r���\s$��������m=w�������$E�2L��r=(�������Z5}����s�m���!�80��XCcd���ftx�j�~����V�������8����V<�sj��`3�'yT#W�`�L#����6�U����n+P�!�����*��`.@�
�����B�u�x������N{��mGd�a�9�%yz�0�K����`��l�!��>*�����z�1�?
����-Q��bM���#�m�w�w��1��p�Q����x%b���9@#�����tt�x���>��*&�F��zb�j��"V�3�
�J�z����P��cAD��u\�B��<B�v]Q��0�+w9��h�7G�����Q������y����q�TW�\U�_�[�����yDu��c'Rz��HY�g�������`N��1�l#���\����Q��eT�Ca5�N����6�$:-���%N�l��^��7W���r��L��y��nm��SS��cS����YS�M���L���1������t�oR($��F�#[����,�|���E�����
C���\����c`�%��U$�|n�l3��&\Epp-��S�}t�
�}��Y*���/���qDG�]����9!��:P���3��}����	��Sc�)&������p(�O$#�3+E��{E8YQ�$|�Cg�C�3�a&U�+�)�=��g�c���J@��\�(^�WL�DN���WV|��X_�0_�|��G������#d�C���YX������d�th��k�"�7_�����w�w��1Qn��:5���u��������|I9�&/�IG'�t!�/�6�_�;)�7������}r�}��UU�`�\����*��;3$��d��L�:~0���H�*�:��W��c�vKm�m��lr����.��;��8�d3(���l_�U[��3W��Fhpe��T#r���9�x2�.-�M8�w~~���i�.�h��X�U-a�0T�:�{:d"����u�Z�E�|�kS�fS�������oI��E$[<'h;�X;B�G�<*�L����8�/�n=�- �T�����O��e��R��>��u���e�WO��E��n�Y�W~G����2,���o��j]�����4p������\���Vr)	FY�kh���q���0M.z`�����Rp����7� Hr��!&�Kw��
��,U�����.���{���%��������]h�n(z�\�0Tk��u3�>���Ci�
U���i����M���}v����xl��<��O�<�x�X���@�(J~:a,��c��<��\�Mk-�����
���e�ArIATfT�W���d�I���r�h�����O�6����;4o��{#7���6��+2�X�D:6}]�#���3�c��0�+�y�V;�A�9v�6$�E
�.�Dj�$n#��-,����;���J.]��l+{j��I�G�h�h2Gw�!_�w�t�!!��OIg��/��SS,Z��:�J����Pj�BzJ��ONyfDP��KR����S�nns����x�����/���w�G�������w�z��1�P���Q�GU���������.&j^����_zVX|�^��z��+;�L�.�.�K11\�a�:_���+
�+
��:,��J��89�!��o�cr�S��-<%s�����_��[���O��!��_�RO�~$!�o��-��C���y�%�Y}	6�i>���+�L�k�W!��s��i����I?Y��;�m�wq�����X����,�r�g���{X�w��=�O�����{���}$��=�x�=v���"2?�1%����n3�2��g�)3������7�?���-.��$����,q��]��R��4%��MfzZ=6iY��}��������{�%N���4S�N]2���4�k=�J�oL��{�Cy)�5_3'F*�Ee�HF��W���m	z��F�>�8����.��$3!lH6~Q?��3@^�#�\^g�!�g���������v������SFG�����=n��<4����"�J�?|h��RDTO�m�Da��5pK--�L�L�Jcb0:�z`���h,���y�}q�wX���d��=O.���V1$��������'E��f?�����8�zf��`1�qRRE�F��ry=
���4����CGEt���{�rL�X��4aK]i��o�E����ssCj�V��\�L����c{�^�G����
�"v���Z�
��O�{bu~��.T���J���[�yP�@u�.\}����6��P����l<�.�[�](�v!i'�z�hB�����e���5d`��:��.F%o����
�v[j��{��}�QD���CE��/�l�R�`	U��W���)m��)�!�3[��|c�G�!�.-�.+�>�(�@�53���}��vA{Y.u9u��")"��N�9�[g�a+DSSvi��VZNl4�?�	,�h�Yp�K�d�G���3�R���*'��B7��$�N.��G~�Bl(�!,T��e�$���^>q�1;<�2|�f������������mw��a��A��R��8eJ�z��CpPN��:@
>��,��x�"���*�#�Kw�������v L��;��
UR1:j���U�����T���.
h�5}D������<k���
,��M���vh�C�����Q�����9����,p������ie��g ������4&��<���o��_?=�;~hC�X���P{%�>��<�+^x��s57R��8�T;��k*�+�JR�EB�1D	��#�*�>�ri' ���5j��K$ 6������uhl{��������VA�����;���?�?�Q8����z�C�&W�����k�Wz��|����8=|�T����a��.w�u�:��Q~{5��_����Z���l�z�Q�m���FC�)=}���?����J1(��E��`|���SHb�bg���S�E��\���K���OM���F #���	Ys��_q��R���7s��<�ge��J��
��=��6�Kiwz�[����S6Tm{�R�IM�;7"�O6U'�cF��+w�T��\�3����I�k����U��V��&�K�G�����!v6zPE��//Z&
n���	G����!�L�LG��B��O���I��3�� 9*�'�P��"��e����,�t��jk/��0��,��4{������&����P{*����w�������^f�k��
�/����j3:�d"*����R��pG�<A=�;�h��Ik*x��P��z\���7'�ke2���+O}��e{go������t��=T� �P���F�;\���d���Q
��Y*�+V�)J�]�.�)��,���	�U�[��,|���{�w�����3)����G��c�O�jV��~s^�#���H�C����a�(��!{I8�L8N/Iu�,��h�s+�Em�`���D?��hJ��,�����;���Z�����b�*s������tpC����U�^��ND���#lF��`�r S=Jt�G|d��/k�?z�(��\ ���T�sx�~�I\�`.>���g(���G�N=���)0'���MMT��X�����0>;n�3��
�(��!�v5E��l&p^v(�zy����������Ah8���0~y�r����8f���������v,F���"�<��&J4r���8��y�G�XJ�+���A	���p��l�a�/}��G��Ao\�!��P4��P�|��L�����an��b��<^����`<>W�����o"�Eb�"��%b���R(���]�����pY��1�^�9������R�\]+�������D'��p+p`2M�En'�)�Z
1�����020�!{��U���aE�F��Z��y���~G���A#*b��U�z���(#�)8��~9�#������
�}�-�9�������m46��SX�HT'������z;;|��}��A����'X����!�)@����M*Nu3������e�Q�&�41�U*y$�S���]8h6F$ffS��b�Q���3Ug<�u�1�H���z�%X�e����}.^^����'0a��&*G6������Cm�E���!���Y��7��q�"��#J�JO�'X����{e�NY3�5�/����M(��D��iON��; 7�kvY�.�>������%�1�V<V+b*3e�����!�l0��h�����Jk�l�Ods�M0���"���|������L��T�����_���P�+Y3xV���h��p��G����	�4��"�;�Ey�k��:>q�L��?_f��]T�����G4L9����QZ�i8�2/r����	*���f,�y���7�h�V�L��y�����
�w����12z����@`��7��W�?��f���z���i�M�ur��������e��:g��y}�ih�7.:Y��u�z<e����}�B����)�
6�����c���������i\ai%��y�]q/����?w��+De�@���U�L����y��5�&E
�M�I�2n���Mg{q��O�1=�����e3�"�eW�B�T������������,��	����r�S�6�0���y��)^�L~��b�u����F��3p����������c���9��k^�cp�:��
��y]d��E+���b+�jK�(��������f�^o�v����N�U���Y�=?�J{8��������W	�Y���;A�x��P)fw� Gp��g���o<DZ��O�����Q�=[�{��G��z�ka�[mVvkU
;�$����@f��p�CT��*d�c�Z�s���GVq�y�K�;hDC!�2"���hX�H=��I��{��%�fa��j��Z
�V����s�b�5�nV�"�Z2�3~@E
�N�{����1��������[}���h�5�����r���`���<��p	�5��X�h�d'n��'F|�wnP��y�W�lR_�	�,�y��-j]�����x�P ����^?����5���NR^�B����h��ll�6��H�sx�����`���hD�����;�F����*2goU����h��;�������>>G���<�;8�V��V&@�����(��=�[\��MC�v����:)f�������#;�J �L����=����������,��F�"oE���F�y��Y��
��ZCj��jO"���xQBu��W���J	���t8�u/���,j<���q>��+8��w�P��z�Q�$��K<��<�E��c���O��U�Q��Eo������[:>����GF�e����-�E@���y�T��6����G�N��F�-m k����NM�'����������U�x��<h���P�t���{�$�+h3K������Qh�r}��I%��cC 5���D�b@��*��X�����f��B�/�S��`7L+��c.����mF?�� L#�t*��"�SO.u�-��y2NFl�7) ��+Z���bC��x���U���k�o���"���~$��x�����4Vn
��'�6%R`/����k�G]�h8������}�p�Q#���}�d����tq��n[�G����yd���-�,��H����N�xl<�>rJG�������?O��)�����L �d����	EE����x���G)��t�	���o���M���#s:hjM�����:�q���Yo4���d�������H�BD��7N�������8�@��4uAu�ZC������������OTi��+�fdkn�I���Z�Y��V��]�}c{�	�Z�r��)�@1`Mn�3�'����W���j�EP�����j"�����X�[��
|Q����+��-��'����<
8Y�bG��*�]�aU'���2"D��[h������������5��*�u��i ��@��H]�j>��k��wa�S,�����T��#I���Cb����(�?=��P(��5���q*�$�lDf��M��]m���Y��e�������������J�������1utx��j��cV�������A��wx�(J�k1W�h�����jC�S]��w�� ������C�c2���`�"�����U��L��	��x���,��d�j�^��N��}p��q@����^Q��+=G�"�0��x��|��t���"��u�bu�J��Gc�9��{��NB�!#�h!z���GB�����E/4 5���^p5�u>�f���C�:W�������e�*a�#S�Lx�(Q�d�:�/�x��.�^�o�`vM�%w��rh[�.<uW-�)�EP���������V��)P����e�\@�������g�?������s��f�e�����w�p��i>�Q�%V%������,���.�C�m	j�k�i���`}]��@���������>��}�U]fM��$�������fxG~2�&��i'�����i���\��tF�/�h��is���W�L f+5Z�*�[���O�����p��~B<�x���	<�M�m������oj�	��0;�e���S ������M�1�~F��.p����N�.���2z�>?�{���z�V�+n���������S���
��;_u�0�v�>W��	0����o��c���w��F������{�$<�G��f/z�����S�v�����0��v�j�f�Z������������{�q5$�mUc ����i�Ui��(�tK=S_K wD����h���$_�h"���@q��S@����~q�� �Y+f�����p�����$���1�����u�K��
���k$��-� �w��=�{���%.2c��m->�Qm���@V�a	�z����;���&��4���E���5�����N���]�5�|�-4�I�=0��_�����D_�(��2!�n7�+�[u���.<�6���.+u�X�k�Q��������V���NB�m��c�6n��Z��`��������f>v��m�[�����<H�?a��C�G������ ���C�=I#��������
[
�t�j�bm���o�C�tO��8�<��7�|�N�U�V��.��M�i��|lW���z	 ��`�~����S�1sdH�T�\��c���	�������
�����0H?�b|i��'����Y������h�i��gO�W����t_�D����k���a���xl��'Cy�����kyY����U�bW|C����:�}�E����Bu����[!v-V��,�r_���=@4b�_���Ku�
#51Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Pavel Stehule (#50)
Re: review: CHECK FUNCTION statement

Pavel Stehule wrote:

one small update - better emulation of environment for security
definer functions

Patch applies and compiles fine, core functionality works fine.

I found a little bug:

In backend/commands/functioncmds.c,
function CheckFunction(CheckFunctionStmt *stmt),
while you perform the table scan for CHECK FUNCTION ALL,
you use the variable funcOid to hold the OID of the current
function in the loop.

If no appropriate function is found in the loop, the
check immediately after the table scan will not succeed
because funcOid holds the OID of the last function scanned
in the loop.
As a consequence, CheckFunctionById is called for this
function.

Here is a demonstration:
test=> CHECK FUNCTION ALL IN SCHEMA pg_catalog;
[...]
NOTICE: skip check function "plpgsql_checker(oid,regclass,text[])", uses C language
NOTICE: skip check function "plperl_call_handler()", uses C language
NOTICE: skip check function "plperl_inline_handler(internal)", uses C language
NOTICE: skip check function "plperl_validator(oid)", uses C language
NOTICE: skip check function "plperl_validator(oid)", language "c" hasn't checker function
CHECK FUNCTION

when it should be:
test=> CHECK FUNCTION ALL IN SCHEMA pg_catalog;
[...]
NOTICE: skip check function "plpgsql_checker(oid,regclass,text[])", uses C language
NOTICE: skip check function "plperl_call_handler()", uses C language
NOTICE: skip check function "plperl_inline_handler(internal)", uses C language
NOTICE: skip check function "plperl_validator(oid)", uses C language
NOTICE: nothing to check
CHECK FUNCTION

Another thing struck me as odd:

You have the option "fatal_errors" for the checker function, but you
special case it in CheckFunction(CheckFunctionStmt *stmt) and turn
errors to warnings if it is not set.

Wouldn't it be better to have the checker function ereport a WARNING
or an ERROR depending on the setting? Options should be handled by the
checker function.

Yours,
Laurenz Albe

#52Greg Smith
greg@2ndQuadrant.com
In reply to: Albe Laurenz (#51)
Re: review: CHECK FUNCTION statement

I just poked at this a bit myself to see how the patch looked. There's
just over 4000 lines in the diff. Even though 1/4 of that is tests,
which is itself encouraging, that's still a good sized feature. The
rate at which code here has still been changing regularly here has me
nervous about considering this a commit candidate right now though. It
seems like it still needs a bit more time to have problems squeezed out
still.

Two ideas I was thinking about here:

-If you take a step back and look at where the problem parts of the code
have been recently, are there any new tests or assertions you might add
to try and detect problems like that in the future? I haven't been
following this closely enough to have any suggestions where, and there
is a lot of error checking aimed at logging already; maybe there's
nothing new to chase there.

-Can we find some larger functions you haven't tested this against yet
to throw at it? It seems able to consume all the cases you've
constructed for it; it would be nice to find some brand new ones it's
never seen before to check.

This has made a lot of progress and seems it will be a good commit
candidate for the next CF. I think it justs a bit more time than we
have left in this CommitFest for it right now, particularly given the
size of the patch. I'm turning this one into "returned with feedback",
but as a mediocre pl/pgsql author I'm hoping to see more updates still.

--
Greg Smith 2ndQuadrant US greg@2ndQuadrant.com Baltimore, MD
PostgreSQL Training, Services, and 24x7 Support www.2ndQuadrant.us

#53Pavel Stehule
pavel.stehule@gmail.com
In reply to: Albe Laurenz (#51)
Re: review: CHECK FUNCTION statement

2011/12/16 Albe Laurenz <laurenz.albe@wien.gv.at>:

Pavel Stehule wrote:

one small update - better emulation of environment for security
definer functions

Patch applies and compiles fine, core functionality works fine.

I found a little bug:

In backend/commands/functioncmds.c,
function CheckFunction(CheckFunctionStmt *stmt),
while you perform the table scan for CHECK FUNCTION ALL,
you use the variable funcOid to hold the OID of the current
function in the loop.

If no appropriate function is found in the loop, the
check immediately after the table scan will not succeed
because funcOid holds the OID of the last function scanned
in the loop.
As a consequence, CheckFunctionById is called for this
function.

Here is a demonstration:
test=> CHECK FUNCTION ALL IN SCHEMA pg_catalog;
[...]
NOTICE:  skip check function "plpgsql_checker(oid,regclass,text[])", uses C language
NOTICE:  skip check function "plperl_call_handler()", uses C language
NOTICE:  skip check function "plperl_inline_handler(internal)", uses C language
NOTICE:  skip check function "plperl_validator(oid)", uses C language
NOTICE:  skip check function "plperl_validator(oid)", language "c" hasn't checker function
CHECK FUNCTION

when it should be:
test=> CHECK FUNCTION ALL IN SCHEMA pg_catalog;
[...]
NOTICE:  skip check function "plpgsql_checker(oid,regclass,text[])", uses C language
NOTICE:  skip check function "plperl_call_handler()", uses C language
NOTICE:  skip check function "plperl_inline_handler(internal)", uses C language
NOTICE:  skip check function "plperl_validator(oid)", uses C language
NOTICE:  nothing to check
CHECK FUNCTION

I'll fix it

Another thing struck me as odd:

You have the option "fatal_errors" for the checker function, but you
special case it in CheckFunction(CheckFunctionStmt *stmt) and turn
errors to warnings if it is not set.

Wouldn't it be better to have the checker function ereport a WARNING
or an ERROR depending on the setting? Options should be handled by the
checker function.

The behave that I use, is more rubust and there is only a few lines of
code more.

a) It ensure expectable behave for third party checker function -
exception in checker function doesn't break a multi statement check
function
b) It can ensure same format of error message - because it is
transformed on top level

Regards

Pavel

Show quoted text

Yours,
Laurenz Albe

#54Pavel Stehule
pavel.stehule@gmail.com
In reply to: Greg Smith (#52)
Re: review: CHECK FUNCTION statement

2011/12/16 Greg Smith <greg@2ndquadrant.com>:

I just poked at this a bit myself to see how the patch looked.  There's just
over 4000 lines in the diff.  Even though 1/4 of that is tests, which is
itself encouraging, that's still a good sized feature.  The rate at which
code here has still been changing regularly here has me nervous about
considering this a commit candidate right now though.  It seems like it
still needs a bit more time to have problems squeezed out still.

Two ideas I was thinking about here:

-If you take a step back and look at where the problem parts of the code
have been recently, are there any new tests or assertions you might add to
try and detect problems like that in the future?  I haven't been following
this closely enough to have any suggestions where, and there is a lot of
error checking aimed at logging already; maybe there's nothing new to chase
there.

last bug was based on wrong untoasting of function parameters - and it
was hang on assertion - so it was ok

I can recheck code where I can add asserts

There is known issue - I cannot to check multi check statement in
regress tests, because results (notices, errors) can be in random
order. We don't sort functions by oid in check_functions_lookup.

-Can we find some larger functions you haven't tested this against yet to
throw at it?  It seems able to consume all the cases you've constructed for
it; it would be nice to find some brand new ones it's never seen before to
check.

I use it for checking of my most large plpgsql project - it is about
300KB plpgsql procedures - but this code is not free - and this module
helps to find lot of bugs.

I have plan to check a more functions from regress tests - but these
tests are no bugs - I checked almost all four months ago - dynamic
sql based and temp table based cannot check.

This has made a lot of progress and seems it will be a good commit candidate
for the next CF.  I think it justs a bit more time than we have left in this
CommitFest for it right now, particularly given the size of the patch.  I'm
turning this one into "returned with feedback", but as a mediocre pl/pgsql
author I'm hoping to see more updates still.

I'll send update early

Regards

Pavel

Show quoted text

--
Greg Smith   2ndQuadrant US    greg@2ndQuadrant.com   Baltimore, MD
PostgreSQL Training, Services, and 24x7 Support  www.2ndQuadrant.us

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#55Pavel Stehule
pavel.stehule@gmail.com
In reply to: Albe Laurenz (#51)
Re: review: CHECK FUNCTION statement

Hello

You have the option "fatal_errors" for the checker function, but you
special case it in CheckFunction(CheckFunctionStmt *stmt) and turn
errors to warnings if it is not set.

Wouldn't it be better to have the checker function ereport a WARNING
or an ERROR depending on the setting? Options should be handled by the
checker function.

A would to process fatal_errors out of checker function - just it is
more robust. This flag has not too sense in plpgsql - but can have a
more sense in other languages.

But I'll think again about flags

note about warnings and errors. Warnings are useless on checker
function level, because they are just shown, but they cannot be
trapped.

maybe result based on tuplestore can be better - I have to look on it.

Regards

Pavel

Show quoted text

Yours,
Laurenz Albe

#56Greg Smith
greg@2ndQuadrant.com
In reply to: Pavel Stehule (#54)
Re: review: CHECK FUNCTION statement

On 12/17/2011 04:00 PM, Pavel Stehule wrote:

I use it for checking of my most large plpgsql project - it is about
300KB plpgsql procedures - but this code is not free - and this module
helps to find lot of bugs.

Great. If you continue to check against that regularly, that makes me
feel better. I was guessing you had a large body of such source code
around, and knowing it executed correctly against all of it improves my
confidence here.

--
Greg Smith 2ndQuadrant US greg@2ndQuadrant.com Baltimore, MD
PostgreSQL Training, Services, and 24x7 Support www.2ndQuadrant.us

#57Pavel Stehule
pavel.stehule@gmail.com
In reply to: Greg Smith (#56)
Re: review: CHECK FUNCTION statement

2011/12/19 Greg Smith <greg@2ndquadrant.com>:

On 12/17/2011 04:00 PM, Pavel Stehule wrote:

I use it for checking of my most large plpgsql project - it is about
300KB plpgsql procedures - but this code is not free - and this module
helps to find lot of bugs.

Great.  If you continue to check against that regularly, that makes me feel
better.  I was guessing you had a large body of such source code around, and
knowing it executed correctly against all of it improves my confidence here.

I am not alone

a subset is used in plpgsql_lint and I know about some commercial
subjects that use it too.

https://github.com/okbob/plpgsql_lint

but code in check function is little newer. It can interesting test
some code that is wroted by person with background from other db,
because they use a different patterns

I don't use a explicit cursors for example - on other hand, I use
exception intensively in my last project. We can ask people from
LedgerSMB about testing if somebody has contact

Regards

Pavel

Show quoted text

--
Greg Smith   2ndQuadrant US    greg@2ndQuadrant.com   Baltimore, MD
PostgreSQL Training, Services, and 24x7 Support  www.2ndQuadrant.us

#58Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#57)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Hello all

here is new version of CHECK FUNCTION patch

I changed implementation of interface:

* checked functions returns table instead raising exceptions - it
necessary for describing more issues inside one function - and it
allow to use better structured data then ExceptionData

postgres=# select lineno, statement, sqlstate, message, detail, hint,
level, "position", query from plpgsql_checker('f1()', 0, '{}', false);
lineno | statement | sqlstate | message
| detail | hint | level | position | query
--------+---------------+----------+--------------------------------------------+--------+--------+-------+----------+----------------------
4 | SQL statement | 42703 | column "c" of relation "t1" does
not exist | [null] | [null] | error | 15 | update t1 set c = 30
7 | RAISE | 42P01 | missing FROM-clause entry for
table "r" | [null] | [null] | error | 8 | SELECT r.c
7 | RAISE | 42601 | too few parameters specified for
RAISE | [null] | [null] | error | [null] | [null]
(3 rows)

* result of CHECK FUNCTION is simple table (like EXPLAIN - based on
Tom proposition)

postgres=# check function f1();
CHECK FUNCTION
------------------------------------------------------------------------
In function: 'f1()'
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
(8 rows)

This change allow a more playing with output

postgres=# check function all in schema public;
CHECK FUNCTION
────────────────────────────────────────────────────────────────────────
In function: 'bubu(integer)'
error:42703:2:assignment:column "v" does not exist
query:SELECT a + v
^
error:42601:3:RETURN:query "SELECT 1,1" returned 2 columns
query:SELECT 1,1

In function: 'f1()'
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

Function is valid: 'ff(integer)'
Function is valid: 'fff(integer)'
(18 rows)

Regards

Pavel Stehule

Attachments:

check_function-2012-01-01-1.diff.gzapplication/x-gzip; name=check_function-2012-01-01-1.diff.gzDownload
#59Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Pavel Stehule (#58)
Re: review: CHECK FUNCTION statement

Pavel Stehule wrote:

here is new version of CHECK FUNCTION patch

I won't be able to review that one because I'll be in
California from Jan 6 to Jan 29.

Yours,
Laurenz Albe

#60Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Pavel Stehule (#58)
Re: review: CHECK FUNCTION statement

Pavel Stehule wrote:

here is new version of CHECK FUNCTION patch

I changed implementation of interface:

* checked functions returns table instead raising exceptions - it
necessary for describing more issues inside one function - and it
allow to use better structured data then ExceptionData

[...]

* result of CHECK FUNCTION is simple table (like EXPLAIN - based on
Tom proposition)

I don't have the time for a complete review, but I tried the patch
and found:

It is in context diff and applies to current master (there is fuzz 1
in one hunk). It contains documentation and regression tests.
Compiles without warnings and passes regression tests.

The two or three CHECK FUNCTIONs I ran worked ok.

The documentation (that I wrote) will need to get updated: currently
it states in two places that the checker function should throw a
warning if it encounters a problem.

Yours,
Laurenz Albe

#61Petr Jelínek
pjmodos@pjmodos.net
In reply to: Pavel Stehule (#58)
1 attachment(s)
Re: review: CHECK FUNCTION statement

On 01/01/2012 01:01 PM, Pavel Stehule wrote:

Hello all

here is new version of CHECK FUNCTION patch

Hi,

I took a shot at reviewing this. The attached version is made against
yesterdays HEAD (which means it applies cleanly) with some updates to
documentation.

I changed implementation of interface:

* checked functions returns table instead raising exceptions - it
necessary for describing more issues inside one function - and it
allow to use better structured data then ExceptionDat

The new interface makes sense to me the way it is, should be usable by
other languages too and for external tools it should enable sufficient
filtering options for what to care about and what not to care about.

Anyway on to full review:

Submission:
Patch has enough documentation (mostly written by Albe Laurenz with some
adjustments from me). It has quite nice set of regression tests which it
passes on both my machines.

Usability:
We certainly want (IMHO it's something we should have had long time ago)
this feature and patch implements it in a way that seems to be useful.
It has pg_dump support.

Feature test:
Works as advertised, passes all regression tests, I tested many real
world functions written by various people and was unable to crash it or
make it misbehave. I can imagine it not working properly with some
EXECUTE statements but I don't believe that is avoidable due to the
nature of PL/pgSQL.

Coding review:
I have no complaints about the code itself, neither did my compiler.
There is no interaction with system so it should not cause any
portability issues.

From my point of view this seems to be ready for committer and barring
any objections I will mark it as such.

Regards
Petr Jelinek (PJMODOS)

Attachments:

checkfunction20120128.difftext/x-patch; name=checkfunction20120128.diffDownload
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 3663,3668 ****
--- 3663,3680 ----
       </row>
  
       <row>
+       <entry><structfield>lanchecker</structfield></entry>
+       <entry><type>oid</type></entry>
+       <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+       <entry>
+        This references a correctness check function that is used by
+        <command>CHECK FUNCTION</> and <command>CHECK TRIGGER</> for in-depth
+        checks of function bodies.
+        Zero if no such function is provided.
+       </entry>
+      </row>
+ 
+      <row>
        <entry><structfield>lanacl</structfield></entry>
        <entry><type>aclitem[]</type></entry>
        <entry></entry>
***************
*** 4273,4278 ****
--- 4285,4296 ----
       </row>
  
       <row>
+       <entry><structfield>tmplchecker</structfield></entry>
+       <entry><type>text</type></entry>
+       <entry>Name of correctness check function, or null if none</entry>
+      </row>
+ 
+      <row>
        <entry><structfield>tmpllibrary</structfield></entry>
        <entry><type>text</type></entry>
        <entry>Path of shared library that implements language</entry>
*** a/doc/src/sgml/plhandler.sgml
--- b/doc/src/sgml/plhandler.sgml
***************
*** 159,170 **** CREATE LANGUAGE plsample
  
     <para>
      Although providing a call handler is sufficient to create a minimal
!     procedural language, there are two other functions that can optionally
      be provided to make the language more convenient to use.  These
!     are a <firstterm>validator</firstterm> and an
!     <firstterm>inline handler</firstterm>.  A validator can be provided
!     to allow language-specific checking to be done during
!     <xref linkend="sql-createfunction">.
      An inline handler can be provided to allow the language to support
      anonymous code blocks executed via the <xref linkend="sql-do"> command.
     </para>
--- 159,173 ----
  
     <para>
      Although providing a call handler is sufficient to create a minimal
!     procedural language, there are three other functions that can optionally
      be provided to make the language more convenient to use.  These
!     are a <firstterm>validator</firstterm>, a <firstterm>checker</firstterm>
!     and an <firstterm>inline handler</firstterm>.
!     A validator can be provided to allow language-specific checking to be
!     done during <xref linkend="sql-createfunction">.
!     A checker performs in-depth checks of function bodies via the
!     commands <xref linkend="sql-checkfunction"> and
!     <xref linkend="sql-checktrigger">.
      An inline handler can be provided to allow the language to support
      anonymous code blocks executed via the <xref linkend="sql-do"> command.
     </para>
***************
*** 203,208 **** CREATE LANGUAGE plsample
--- 206,244 ----
     </para>
  
     <para>
+     If a checker is provided by a procedural language, it must be declared
+     as a function taking four arguments, one of type <type>oid</> second of
+     type <type>regclass</> third is text array (used for options) and fourth
+     of type <type>boolean</>.
+     The checker's result is a table which consists of 
+     functionid of type <type>oid</> (oid of a checked function), lineno 
+     of type <type>integer</> (representing line number of the function 
+     body source), statement of type <type>text</> (statement type), sqlstate 
+     of type <type>text</>, message of type <type>text</> (error message), 
+     detail of type <type>text</> (detail of the error mesaage if any), 
+     hint of type <type>text</> (hint for the error message if any), 
+     level of type <type>text</> (error level), position of type 
+     <type>integer</> (possition of the error in a query if there is a query)
+     and query of type <type>text</> (showing SQL statement in which 
+     the error occured).
+     The checker will be called whenever a function is checked with
+     <command>CHECK FUNCTION</> or <command>CHECK TRIGGER</>.
+     The passed-in OID is the OID of the function's <classname>pg_proc</>
+     row.  The passed-in <type>regclass</> is the OID of the
+     <classname>pg_class</> row of the table on which the trigger is defined,
+     or <symbol>InvalidOid</symbol> in the case of <command>CHECK FUNCTION</>.
+     The last parameter passed as <type>boolean</> defines if checker should
+     stop checking on first error.
+     The checker must fetch these rows in the usual way, and do
+     whatever checking is appropriate.
+     Typical checks include verifying that all referenced database objects
+     actually exist or style checks like verifying that all declared
+     variables are actually used.  If the checker finds the function to be okay,
+     it should just return. If it finds a problem, it should return a row 
+     describing error.
+    </para>
+ 
+    <para>
      If an inline handler is provided by a procedural language, it
      must be declared as a function taking a single parameter of type
      <type>internal</>.  The inline handler's result is ignored, so it is
*** a/doc/src/sgml/ref/allfiles.sgml
--- b/doc/src/sgml/ref/allfiles.sgml
***************
*** 40,45 **** Complete list of usable sgml source files in this directory.
--- 40,47 ----
  <!ENTITY alterView          SYSTEM "alter_view.sgml">
  <!ENTITY analyze            SYSTEM "analyze.sgml">
  <!ENTITY begin              SYSTEM "begin.sgml">
+ <!ENTITY checkFunction      SYSTEM "check_function.sgml">
+ <!ENTITY checkTrigger       SYSTEM "check_trigger.sgml">
  <!ENTITY checkpoint         SYSTEM "checkpoint.sgml">
  <!ENTITY close              SYSTEM "close.sgml">
  <!ENTITY cluster            SYSTEM "cluster.sgml">
*** a/doc/src/sgml/ref/create_language.sgml
--- b/doc/src/sgml/ref/create_language.sgml
***************
*** 23,29 **** PostgreSQL documentation
  <synopsis>
  CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
  CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ]
  </synopsis>
   </refsynopsisdiv>
  
--- 23,29 ----
  <synopsis>
  CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
  CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ] [ CHECK <replaceable>checkfunction</replaceable> ]
  </synopsis>
   </refsynopsisdiv>
  
***************
*** 217,222 **** CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
--- 217,261 ----
        </para>
       </listitem>
      </varlistentry>
+ 
+     <varlistentry>
+      <term><literal>CHECK</literal> <replaceable class="parameter">checkfunction</replaceable></term>
+ 
+      <listitem>
+       <para><replaceable class="parameter">checkfunction</replaceable> is the
+        name of a previously registered function that will be called
+        by <command>CHECK FUNCTION</command> and <command>CHECK TRIGGER</command>
+        to check the bodies of functions defined in the language.
+        If no check function is specified, this kind of check is not available.
+        The check function must take four arguments, first of type <type>oid</type>
+        (the OID of the function to be checked), second of type <type>regclass</type>
+        (the table with the trigger for <command>CHECK TRIGGER</command>),
+        third is text array (options) and fourth is boolean (defines if check
+        function should stop on first error found).
+        The check function will typically return set of rows with ten fields:
+        functionid of type <type>oid</> (oid of a checked function), lineno 
+        of type <type>integer</> (representing line number of the function 
+        body source), statement of type <type>text</> (statement type), sqlstate 
+        of type <type>text</>, message of type <type>text</> (error message), 
+        detail of type <type>text</> (detail of the error mesaage if any), 
+        hint of type <type>text</> (hint for the error message if any), 
+        level of type <type>text</> (error level), position of type 
+        <type>integer</> (possition of the error in a query if there is a query)
+        and query of type <type>text</> (showing SQL statement in which 
+        the error occured).
+       </para>
+ 
+       <para>
+        A check function would typically perform an in-depth check of the function
+        body, for example that all referenced database objects exist, but it could
+        also check for stylistic problems like defined but unreferenced variables.
+        To signal a problem found during the check, the function should return row
+        for each error found, if fourth input argument is True, it should return
+        immediately  after first error.
+       </para>
+      </listitem>
+     </varlistentry>
+ 
     </variablelist>
  
    <para>
*** a/doc/src/sgml/reference.sgml
--- b/doc/src/sgml/reference.sgml
***************
*** 68,73 ****
--- 68,75 ----
     &alterView;
     &analyze;
     &begin;
+    &checkFunction;
+    &checkTrigger;
     &checkpoint;
     &close;
     &cluster;
*** a/src/backend/catalog/pg_proc.c
--- b/src/backend/catalog/pg_proc.c
***************
*** 1101,1103 **** fail:
--- 1101,1104 ----
  	*newcursorpos = newcp;
  	return false;
  }
+ 
*** a/src/backend/commands/functioncmds.c
--- b/src/backend/commands/functioncmds.c
***************
*** 35,53 ****
--- 35,59 ----
  #include "access/genam.h"
  #include "access/heapam.h"
  #include "access/sysattr.h"
+ #include "access/xact.h"
  #include "catalog/dependency.h"
  #include "catalog/indexing.h"
  #include "catalog/objectaccess.h"
  #include "catalog/pg_aggregate.h"
  #include "catalog/pg_cast.h"
  #include "catalog/pg_language.h"
+ #include "catalog/namespace.h"
  #include "catalog/pg_namespace.h"
  #include "catalog/pg_proc.h"
  #include "catalog/pg_proc_fn.h"
+ #include "catalog/pg_trigger.h"
  #include "catalog/pg_type.h"
  #include "catalog/pg_type_fn.h"
  #include "commands/defrem.h"
  #include "commands/proclang.h"
+ #include "commands/trigger.h"
+ #include "executor/executor.h"
+ #include "executor/spi_priv.h"
  #include "miscadmin.h"
  #include "optimizer/var.h"
  #include "parser/parse_coerce.h"
***************
*** 60,66 ****
--- 66,74 ----
  #include "utils/fmgroids.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
+ #include "utils/memutils.h"
  #include "utils/rel.h"
+ #include "utils/resowner.h"
  #include "utils/syscache.h"
  #include "utils/tqual.h"
  
***************
*** 779,785 **** interpret_AS_clause(Oid languageOid, const char *languageName,
  }
  
  
- 
  /*
   * CreateFunction
   *	 Execute a CREATE FUNCTION utility statement.
--- 787,792 ----
***************
*** 1022,1027 **** RemoveFunctionById(Oid funcOid)
--- 1029,1523 ----
  	}
  }
  
+ /*
+  * Search and execute related checker function,
+  *
+  *   returns true, when checked function is valid
+  *
+  */
+ static bool
+ CheckFunctionById(Oid funcOid, Oid relid, ArrayType *options,
+ 								bool fatal_errors,
+ 										TupOutputState *tstate)
+ {
+ 	HeapTuple	tup;
+ 	Form_pg_proc proc;
+ 	HeapTuple	languageTuple;
+ 	Form_pg_language languageStruct;
+ 	Oid		languageChecker;
+ 	char *funcname;
+ 	StringInfoData  sinfo;
+ 	Form_pg_proc		checker_fce;
+ 	HeapTuple		checker_fce_tup;
+ 	bool		result = true;
+ 
+ 	tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
+ 	if (!HeapTupleIsValid(tup)) /* should not happen */
+ 		elog(ERROR, "cache lookup failed for function %u", funcOid);
+ 
+ 	proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 	languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
+ 	Assert(HeapTupleIsValid(languageTuple));
+ 
+ 	languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
+ 	languageChecker = languageStruct->lanchecker;
+ 
+ 	funcname = format_procedure(funcOid);
+ 
+ 	/* Check a function body */
+ 	if (OidIsValid(languageChecker))
+ 	{
+ 		Oid	argtypes[4] = { REGPROCEDUREOID, REGCLASSOID, TEXTARRAYOID, BOOLOID };
+ 		bool	nulls[4] = { false, false, false, false };
+ 		Datum	values[4];
+ 		int i;
+ 
+ 		/* call a checker function and read result */
+ 		checker_fce_tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(languageChecker));
+ 		if (!HeapTupleIsValid(checker_fce_tup)) /* should not happen */
+ 			elog(ERROR, "cache lookup failed for function %u", funcOid);
+ 		checker_fce = (Form_pg_proc) GETSTRUCT(checker_fce_tup);
+ 
+ 		initStringInfo(&sinfo);
+ 		appendStringInfo(&sinfo, "SELECT * FROM %s($1, $2, $3, $4)",
+ 						quote_identifier(NameStr(checker_fce->proname)));
+ 		ReleaseSysCache(checker_fce_tup);
+ 
+ 		/*
+ 		 * Connect to SPI manager
+ 		 */
+ 		if (SPI_connect() != SPI_OK_CONNECT)
+ 			elog(ERROR, "SPI_connect failed");
+ 
+ 		values[0] = ObjectIdGetDatum(funcOid);
+ 		values[1] = ObjectIdGetDatum(relid);
+ 		values[2] = PointerGetDatum(options);
+ 		values[3] = BoolGetDatum(fatal_errors);
+ 
+ 		SPI_execute_with_args(sinfo.data,
+ 						4, argtypes,
+ 						values, nulls, 
+ 							    true, 0);
+ 
+ 		result = SPI_processed == 0;
+ 
+ 		if (result)
+ 		{
+ 			resetStringInfo(&sinfo);
+ 			appendStringInfo(&sinfo, "Function is valid: '%s'", funcname);
+ 			do_text_output_oneline(tstate, sinfo.data);
+ 		}
+ 		else
+ 		{
+ 			resetStringInfo(&sinfo);
+ 			appendStringInfo(&sinfo, "In function: '%s'", funcname);
+ 			do_text_output_oneline(tstate, sinfo.data);
+ 			
+ 			for (i = 0; i < SPI_processed; i++)
+ 			{
+ 				char		*query;
+ 
+ 				resetStringInfo(&sinfo);
+ 				appendStringInfo(&sinfo, "%s:%s:%s:%s:%s",
+ 					SPI_getvalue(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 8),
+ 					SPI_getvalue(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 4),
+ 					SPI_getvalue(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 2),
+ 					SPI_getvalue(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 3),
+ 					SPI_getvalue(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 5));
+ 
+ 				do_text_output_oneline(tstate, sinfo.data);
+ 				resetStringInfo(&sinfo);
+ 
+ 				query = SPI_getvalue(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 10);
+ 				if (query != NULL)
+ 				{
+ 					int	position;
+ 					Datum	value;
+ 					bool	isnull;
+ 
+ 					appendStringInfo(&sinfo, "query:%s", query);
+ 					do_text_output_oneline(tstate, sinfo.data);
+ 
+ 					value = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 9, &isnull);
+ 
+ 					if (!isnull)
+ 					{
+ 						position = DatumGetInt32( value);
+ 						if (position > 0)
+ 						{
+ 							resetStringInfo(&sinfo);
+ 
+ 							appendStringInfo(&sinfo, "      %*s",
+ 								position, "^");
+ 							do_text_output_oneline(tstate, sinfo.data);
+ 						}
+ 					}
+ 				}
+ 			}
+ 		}
+ 
+ 		/*
+ 		 * Disconnect from SPI manager
+ 		 */
+ 		if (SPI_finish() != SPI_OK_FINISH)
+ 			elog(ERROR, "SPI_finish failed");
+ 	}
+ 
+ 	pfree(funcname);
+ 
+ 	ReleaseSysCache(languageTuple);
+ 	ReleaseSysCache(tup);
+ 
+ 	return result;
+ }
+ 
+ TupleDesc
+ CheckFunctionResultDesc(CheckFunctionStmt *stmt)
+ {
+ 	TupleDesc	tupdesc;
+ 
+ 	tupdesc = CreateTemplateTupleDesc(1, false);
+ 
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 1,
+ 					    stmt->is_function ? "CHECK FUNCTION" : "CHECK TRIGGER",
+ 					    TEXTOID, -1, 0);
+ 
+ 	return tupdesc;
+ }
+ 
+ /*
+  * CheckFunction
+  *			call a PL checker function when this function exists.
+  */
+ void
+ CheckFunction(CheckFunctionStmt *stmt,
+ 					DestReceiver *dest)
+ {
+ 	List	   *functionName = stmt->funcname;
+ 	List	   *argTypes = stmt->args;	/* list of TypeName nodes */
+ 	Oid			funcOid = InvalidOid;
+ 	HeapTuple	tup;
+ 	Oid trgOid = InvalidOid;
+ 	Oid	relid = InvalidOid;
+ 	Oid languageId;
+ 	int nkeys = 0;
+ 	List	*objects = NIL;
+ 	ArrayType *options;
+ 	ListCell *option;
+ 	bool	stop_on_first_error = false;
+ 	ArrayBuildState *astate = NULL;
+ 	int		dims[2] = {0, 2};
+ 	int		lbs[2] = {1, 1};
+ 	TupOutputState *tstate;
+ 
+ 	tstate = begin_tup_output_tupdesc(dest, CheckFunctionResultDesc(stmt));
+ 
+ 	/*
+ 	 * iterate over defelem check_options and serialize it to two dimensional
+ 	 * text array. These option will be send to checker_function. When options
+ 	 * are not entered, then prepare empty array - it's necessary - checker
+ 	 * function is strict.
+ 	 */
+ 	if (stmt->check_options != NIL)
+ 	{
+ 
+ 		/* complete dimensions */
+ 		dims[0] = list_length(stmt->check_options);
+ 
+ 		foreach(option, stmt->check_options)
+ 		{
+ 			DefElem    *defel = (DefElem *) lfirst(option);
+ 			char *option_name = defel->defname;
+ 			Datum		value;
+ 			bool		isnull;
+ 
+ 			astate = accumArrayResult(astate,
+ 							 CStringGetTextDatum(option_name), false,
+ 												 TEXTOID,
+ 												 CurrentMemoryContext);
+ 
+ 			if (strcmp(option_name, "fatal_errors") == 0)
+ 			{
+ 				stop_on_first_error = defGetBoolean(defel);
+ 				value = stop_on_first_error ? CStringGetTextDatum("on") : CStringGetTextDatum("off");
+ 				isnull = false;
+ 			}
+ 			else if (defel->arg != NULL)
+ 			{
+ 				value = CStringGetTextDatum(defGetString(defel));
+ 				isnull = false;
+ 			}
+ 			else
+ 			{
+ 				value = (Datum) 0;
+ 				isnull = true;
+ 			}
+ 
+ 			astate = accumArrayResult(astate,
+ 							value, isnull,
+ 									 TEXTOID,  CurrentMemoryContext);
+ 		}
+ 	}
+ 
+ 	if (astate != NULL)
+ 		options = DatumGetArrayTypeP(makeMdArrayResult(astate, 2, dims, lbs,
+ 								 CurrentMemoryContext, true));
+ 	else
+ 		options = construct_empty_array(TEXTOID);
+ 
+ 	if (stmt->funcname != NULL)
+ 	{
+ 		/*
+ 		 * Find the function, 
+ 		 */
+ 		funcOid = LookupFuncNameTypeNames(functionName, argTypes, false);
+ 	}
+ 	else if (stmt->trgname != NULL)
+ 	{
+ 		HeapTuple	ht_trig;
+ 		Form_pg_trigger trigrec;
+ 		ScanKeyData skey[1];
+ 		Relation	tgrel;
+ 		SysScanDesc tgscan;
+ 
+ 		/* find a trigger function */
+ 		relid = RangeVarGetRelid(stmt->relation, ShareLock, false);
+ 		trgOid = get_trigger_oid(relid, stmt->trgname, false);
+ 
+ 		/*
+ 		 * Fetch the pg_trigger tuple by the Oid of the trigger
+ 		 */
+ 		tgrel = heap_open(TriggerRelationId, AccessShareLock);
+ 
+ 		ScanKeyInit(&skey[0],
+ 					ObjectIdAttributeNumber,
+ 					BTEqualStrategyNumber, F_OIDEQ,
+ 					ObjectIdGetDatum(trgOid));
+ 
+ 		tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true,
+ 									SnapshotNow, 1, skey);
+ 
+ 		ht_trig = systable_getnext(tgscan);
+ 
+ 		if (!HeapTupleIsValid(ht_trig))
+ 			elog(ERROR, "could not find tuple for trigger %u", trgOid);
+ 
+ 		trigrec = (Form_pg_trigger) GETSTRUCT(ht_trig);
+ 
+ 		/* we need to know trigger function to get PL checker function */
+ 		funcOid = trigrec->tgfoid;
+ 
+ 		/* Clean up */
+ 		systable_endscan(tgscan);
+ 
+ 		heap_close(tgrel, AccessShareLock);
+ 	}
+ 	else
+ 	{
+ 		ScanKeyData	   key[5];
+ 		Relation	   rel;
+ 		HeapScanDesc 	   scan;
+ 		bool	language_opt =  false;
+ 		bool	schema_opt = false;
+ 		bool	owner_opt = false;
+ 
+ 		/*
+ 		 * when Check stmt is multiple statement, then
+ 		 * prepare list of processed functions.
+ 		 */
+ 		foreach(option, stmt->options)
+ 		{
+ 			DefElem    *defel = (DefElem *) lfirst(option);
+ 
+ 			if (strcmp(defel->defname, "language") == 0)
+ 			{
+ 				HeapTuple	languageTuple;
+ 				Form_pg_language languageStruct;
+ 				Oid		languageChecker;
+ 				char *language = defGetString(defel);
+ 
+ 				if (language_opt)
+ 					elog(ERROR, "multiple usage of IN LANGUAGE option");
+ 				language_opt = true;
+ 
+ 				languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language));
+ 				if (!HeapTupleIsValid(languageTuple))
+ 					ereport(ERROR,
+ 						(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 						 errmsg("language \"%s\" does not exist", language),
+ 						 (PLTemplateExists(language) ?
+ 						  errhint("Use CREATE LANGUAGE to load the language into the database.") : 0)));
+ 
+ 				languageId = HeapTupleGetOid(languageTuple);
+ 				if (languageId == INTERNALlanguageId || languageId == ClanguageId)
+ 					elog(ERROR, "cannot to check functions in C or internal languages");
+ 
+ 				languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
+ 				languageChecker = languageStruct->lanchecker;
+ 				if (!OidIsValid(languageChecker))
+ 					elog(ERROR, "language \"%s\" has no defined checker function",
+ 							    language);
+ 
+ 				ScanKeyInit(&key[nkeys++],
+ 							Anum_pg_proc_prolang,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(languageId));
+ 
+ 				ReleaseSysCache(languageTuple);
+ 			}
+ 			else if (strcmp(defel->defname, "schema") == 0)
+ 			{
+ 				Oid namespaceId = LookupExplicitNamespace(defGetString(defel));
+ 
+ 				if (schema_opt)
+ 					elog(ERROR, "multiple usage of IN SCHEMA option");
+ 				schema_opt = true;
+ 
+ 				ScanKeyInit(&key[nkeys++],
+ 							Anum_pg_proc_pronamespace,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(namespaceId));
+ 
+ 			}
+ 			else if (strcmp(defel->defname, "owner") == 0)
+ 			{
+ 				Oid ownerId = get_role_oid(defGetString(defel), false);
+ 
+ 				if (owner_opt)
+ 					elog(ERROR, "multiple usage of FOR ROLE option");
+ 				owner_opt = true;
+ 
+ 				ScanKeyInit(&key[nkeys++],
+ 							Anum_pg_proc_proowner,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(ownerId));
+ 			}
+ 			else
+ 				elog(ERROR, "option \"%s\" not recognized",
+ 					 defel->defname);
+ 		}
+ 
+ 		/*
+ 		 * We don't would to iterate over pg_catalog functions without
+ 		 * explicit request and information_schema.
+ 		 */
+ 		if (!schema_opt)
+ 		{
+ 			Oid information_schemaId = LookupExplicitNamespace("information_schema");
+ 		
+ 			ScanKeyInit(&key[nkeys++],
+ 						Anum_pg_proc_pronamespace,
+ 						BTEqualStrategyNumber, F_OIDNE,
+ 						ObjectIdGetDatum(PG_CATALOG_NAMESPACE));
+ 
+ 			ScanKeyInit(&key[nkeys++],
+ 						Anum_pg_proc_pronamespace,
+ 						BTEqualStrategyNumber, F_OIDNE,
+ 						ObjectIdGetDatum(information_schemaId));
+ 		}
+ 
+ 		rel = heap_open(ProcedureRelationId,AccessShareLock);
+ 		scan = heap_beginscan(rel, SnapshotNow, nkeys, key);
+ 
+ 		while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+ 		{
+ 			Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 			funcOid = HeapTupleGetOid(tup);
+ 
+ 			/* when language is not specified, then check checker */
+ 			if (!language_opt)
+ 			{
+ 				Oid prolang = proc->prolang;
+ 
+ 				/* skip C, internal or SQL */
+ 				if (prolang == INTERNALlanguageId)
+ 				{
+ 					elog(NOTICE, "skip check function \"%s\", it uses internal language",
+ 								format_procedure(funcOid));
+ 					continue;
+ 				}
+ 				else if (prolang == ClanguageId)
+ 				{
+ 					elog(NOTICE, "skip check function \"%s\", uses C language",
+ 								format_procedure(funcOid));
+ 					continue;
+ 				}
+ 				else if (prolang == SQLlanguageId)
+ 				{
+ 					elog(NOTICE, "skip check function \"%s\", uses SQL language",
+ 								format_procedure(funcOid));
+ 					continue;
+ 				}
+ 			}
+ 
+ 			if (proc->prorettype == TRIGGEROID)
+ 			{
+ 				elog(NOTICE, "skip check function \"%s\", it is trigger function",
+ 							format_procedure(funcOid));
+ 				continue;
+ 			}
+ 
+ 			objects = lappend_oid(objects, funcOid);
+ 		}
+ 
+ 		heap_endscan(scan);
+ 		heap_close(rel, AccessShareLock);
+ 	}
+ 
+ 	if (funcOid == InvalidOid && objects == NIL)
+ 	{
+ 		elog(NOTICE, "nothing to check");
+ 		return;
+ 	}
+ 
+ 	if (objects != NIL)
+ 	{
+ 		ListCell *object;
+ 		bool	isfirst = true;
+ 		bool		prev_error = false;
+ 
+ 		foreach(object, objects)
+ 		{
+ 			CHECK_FOR_INTERRUPTS();
+ 
+ 			/*
+ 			 * append empty line when prev. was error,
+ 			 * for better readability.
+ 			 */
+ 			if (!isfirst && prev_error)
+ 				do_text_output_oneline(tstate, "");
+ 
+ 			funcOid = lfirst_oid(object);
+ 			if (!CheckFunctionById(funcOid, InvalidOid, options,
+ 									stop_on_first_error,
+ 											    tstate))
+ 			{
+ 				prev_error = true;
+ 				if (stop_on_first_error)
+ 					break;
+ 			}
+ 			else
+ 				prev_error = false;
+ 
+ 			isfirst = false;
+ 		}
+ 	}
+ 	else
+ 	{
+ 		CheckFunctionById(funcOid, relid, options,
+ 							    stop_on_first_error,
+ 										    tstate);
+ 	}
+ 
+ 	end_tup_output(tstate);
+ }
+ 
+ /*
+  * CheckFunctionResultDesc -
+  *	  construct the result tupledesc for an CHECK
+  */
  
  /*
   * Rename function
*** a/src/backend/commands/proclang.c
--- b/src/backend/commands/proclang.c
***************
*** 46,57 **** typedef struct
  	char	   *tmplhandler;	/* name of handler function */
  	char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
  	char	   *tmplvalidator;	/* name of validator function, or NULL */
  	char	   *tmpllibrary;	/* path of shared library */
  } PLTemplate;
  
  static void create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, bool trusted);
  static PLTemplate *find_language_template(const char *languageName);
  static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
  							Oid newOwnerId);
--- 46,58 ----
  	char	   *tmplhandler;	/* name of handler function */
  	char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
  	char	   *tmplvalidator;	/* name of validator function, or NULL */
+ 	char	   *tmplchecker;	/* name of checker function, or NULL */
  	char	   *tmpllibrary;	/* path of shared library */
  } PLTemplate;
  
  static void create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, Oid checkerOid, bool trusted);
  static PLTemplate *find_language_template(const char *languageName);
  static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
  							Oid newOwnerId);
***************
*** 67,75 **** CreateProceduralLanguage(CreatePLangStmt *stmt)
  	PLTemplate *pltemplate;
  	Oid			handlerOid,
  				inlineOid,
! 				valOid;
  	Oid			funcrettype;
! 	Oid			funcargtypes[1];
  
  	/*
  	 * If we have template information for the language, ignore the supplied
--- 68,77 ----
  	PLTemplate *pltemplate;
  	Oid			handlerOid,
  				inlineOid,
! 				valOid,
! 				checkerOid;
  	Oid			funcrettype;
! 	Oid			funcargtypes[4];
  
  	/*
  	 * If we have template information for the language, ignore the supplied
***************
*** 219,228 **** CreateProceduralLanguage(CreatePLangStmt *stmt)
  		else
  			valOid = InvalidOid;
  
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, pltemplate->tmpltrusted);
  	}
  	else
  	{
--- 221,309 ----
  		else
  			valOid = InvalidOid;
  
+ 		/*
+ 		 * Likewise for the checker, if required; but we don't care about
+ 		 * its return type.
+ 		 */
+ 		if (pltemplate->tmplchecker)
+ 		{
+ 			funcname = SystemFuncName(pltemplate->tmplchecker);
+ 			funcargtypes[0] = REGPROCEDUREOID;
+ 			funcargtypes[1] = REGCLASSOID;
+ 			funcargtypes[2] = TEXTARRAYOID;
+ 			funcargtypes[3] = BOOLOID;
+ 
+ 			checkerOid = LookupFuncName(funcname, 4, funcargtypes, true);
+ 			if (!OidIsValid(checkerOid))
+ 			{
+ 				Datum	   allTypes[14];
+ 				Datum	   paramModes[14];
+ 				Datum	   paramNames[14];
+ 				ArrayType	*allParameterTypes;
+ 				ArrayType	*parameterModes;
+ 				ArrayType	*parameterNames;
+ 
+ 				Oid	   Types[14] = { REGPROCEDUREOID, REGCLASSOID, TEXTARRAYOID, BOOLOID,
+ 						    OIDOID, INT4OID, TEXTOID, TEXTOID,
+ 						    TEXTOID, TEXTOID, TEXTOID, TEXTOID,
+ 						    INT4OID, TEXTOID };
+ 
+ 				char	   Modes[14] = { FUNC_PARAM_IN, FUNC_PARAM_IN, FUNC_PARAM_IN, FUNC_PARAM_IN,
+ 						       FUNC_PARAM_OUT, FUNC_PARAM_OUT, FUNC_PARAM_OUT, FUNC_PARAM_OUT,
+ 						       FUNC_PARAM_OUT, FUNC_PARAM_OUT, FUNC_PARAM_OUT, FUNC_PARAM_OUT,
+ 						       FUNC_PARAM_OUT, FUNC_PARAM_OUT };
+ 
+ 				char	   *Names[14] = { "functionid", "tableid", "options", "fatal_errors",
+ 						       "functionid", "lineno", "statement", "sqlstate",
+ 						       "message", "detail", "hint", "level",
+ 						       "position", "query" };
+ 				int	i;
+ 
+ 				for (i = 0; i < 14; i++)
+ 				{
+ 					allTypes[i] = ObjectIdGetDatum(Types[i]);
+ 					paramModes[i] = CharGetDatum(Modes[i]);
+ 					paramNames[i] = CStringGetTextDatum(Names[i]);
+ 				}
+ 
+ 				allParameterTypes = construct_array(allTypes, 14, OIDOID,
+ 											 sizeof(Oid), true, 'i');
+ 				parameterModes = construct_array(paramModes, 14, CHAROID,
+ 											 1, true, 'c');
+ 				parameterNames = construct_array(paramNames, 14, TEXTOID,
+ 											 -1, false, 'i');
+ 
+ 				checkerOid = ProcedureCreate(pltemplate->tmplchecker,
+ 										 PG_CATALOG_NAMESPACE,
+ 										 false, /* replace */
+ 										 true, /* returnsSet */
+ 										 RECORDOID,
+ 										 ClanguageId,
+ 										 F_FMGR_C_VALIDATOR,
+ 										 pltemplate->tmplchecker,
+ 										 pltemplate->tmpllibrary,
+ 										 false, /* isAgg */
+ 										 false, /* isWindowFunc */
+ 										 false, /* security_definer */
+ 										 true,	/* isStrict */
+ 										 PROVOLATILE_VOLATILE,
+ 										 buildoidvector(funcargtypes, 4), /* parameterTypes */
+ 										 PointerGetDatum(allParameterTypes),
+ 										 PointerGetDatum(parameterModes),
+ 										 PointerGetDatum(parameterNames),
+ 										 NIL,
+ 										 PointerGetDatum(NULL),
+ 										 1,
+ 										 100);
+ 			}
+ 		}
+ 		else
+ 			checkerOid = InvalidOid;
+ 
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, checkerOid, pltemplate->tmpltrusted);
  	}
  	else
  	{
***************
*** 294,303 **** CreateProceduralLanguage(CreatePLangStmt *stmt)
  		else
  			valOid = InvalidOid;
  
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, stmt->pltrusted);
  	}
  }
  
--- 375,396 ----
  		else
  			valOid = InvalidOid;
  
+ 		/* validate the checker function */
+ 		if (stmt->plchecker)
+ 		{
+ 			funcargtypes[0] = OIDOID;
+ 			funcargtypes[1] = REGCLASSOID;
+ 			funcargtypes[2] = TEXTARRAYOID;
+ 			checkerOid = LookupFuncName(stmt->plchecker, 3, funcargtypes, false);
+ 			/* return value is ignored, so we don't check the type */
+ 		}
+ 		else
+ 			checkerOid = InvalidOid;
+ 
  		/* ok, create it */
  		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, checkerOid, stmt->pltrusted);
  	}
  }
  
***************
*** 307,313 **** CreateProceduralLanguage(CreatePLangStmt *stmt)
  static void
  create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, bool trusted)
  {
  	Relation	rel;
  	TupleDesc	tupDesc;
--- 400,406 ----
  static void
  create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, Oid checkerOid, bool trusted)
  {
  	Relation	rel;
  	TupleDesc	tupDesc;
***************
*** 337,342 **** create_proc_lang(const char *languageName, bool replace,
--- 430,436 ----
  	values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid);
  	values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid);
  	values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid);
+ 	values[Anum_pg_language_lanchecker - 1] = ObjectIdGetDatum(checkerOid);
  	nulls[Anum_pg_language_lanacl - 1] = true;
  
  	/* Check for pre-existing definition */
***************
*** 423,428 **** create_proc_lang(const char *languageName, bool replace,
--- 517,531 ----
  		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
  	}
  
+ 	/* dependency on the checker function, if any */
+ 	if (OidIsValid(checkerOid))
+ 	{
+ 		referenced.classId = ProcedureRelationId;
+ 		referenced.objectId = checkerOid;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
  	/* Post creation hook for new procedural language */
  	InvokeObjectAccessHook(OAT_POST_CREATE,
  						   LanguageRelationId, myself.objectId, 0);
***************
*** 478,483 **** find_language_template(const char *languageName)
--- 581,591 ----
  		if (!isnull)
  			result->tmplvalidator = TextDatumGetCString(datum);
  
+ 		datum = heap_getattr(tup, Anum_pg_pltemplate_tmplchecker,
+ 							 RelationGetDescr(rel), &isnull);
+ 		if (!isnull)
+ 			result->tmplchecker = TextDatumGetCString(datum);
+ 
  		datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary,
  							 RelationGetDescr(rel), &isnull);
  		if (!isnull)
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2883,2888 **** _copyAlterFunctionStmt(const AlterFunctionStmt *from)
--- 2883,2904 ----
  	return newnode;
  }
  
+ static CheckFunctionStmt *
+ _copyCheckFunctionStmt(const CheckFunctionStmt *from)
+ {
+ 	CheckFunctionStmt *newnode = makeNode(CheckFunctionStmt);
+ 
+ 	COPY_NODE_FIELD(funcname);
+ 	COPY_NODE_FIELD(args);
+ 	COPY_STRING_FIELD(trgname);
+ 	COPY_NODE_FIELD(relation);
+ 	COPY_SCALAR_FIELD(is_function);
+ 	COPY_NODE_FIELD(options);
+ 	COPY_NODE_FIELD(check_options);
+ 
+ 	return newnode;
+ }
+ 
  static DoStmt *
  _copyDoStmt(const DoStmt *from)
  {
***************
*** 4171,4176 **** copyObject(const void *from)
--- 4187,4195 ----
  		case T_AlterFunctionStmt:
  			retval = _copyAlterFunctionStmt(from);
  			break;
+ 		case T_CheckFunctionStmt:
+ 			retval = _copyCheckFunctionStmt(from);
+ 			break;
  		case T_DoStmt:
  			retval = _copyDoStmt(from);
  			break;
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 1294,1299 **** _equalAlterFunctionStmt(const AlterFunctionStmt *a, const AlterFunctionStmt *b)
--- 1294,1313 ----
  }
  
  static bool
+ _equalCheckFunctionStmt(const CheckFunctionStmt *a, const CheckFunctionStmt *b)
+ {
+ 	COMPARE_NODE_FIELD(funcname);
+ 	COMPARE_NODE_FIELD(args);
+ 	COMPARE_STRING_FIELD(trgname);
+ 	COMPARE_NODE_FIELD(relation);
+ 	COMPARE_SCALAR_FIELD(is_function);
+ 	COMPARE_NODE_FIELD(options);
+ 	COMPARE_NODE_FIELD(check_options);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalDoStmt(const DoStmt *a, const DoStmt *b)
  {
  	COMPARE_NODE_FIELD(args);
***************
*** 2714,2719 **** equal(const void *a, const void *b)
--- 2728,2736 ----
  		case T_AlterFunctionStmt:
  			retval = _equalAlterFunctionStmt(a, b);
  			break;
+ 		case T_CheckFunctionStmt:
+ 			retval = _equalCheckFunctionStmt(a, b);
+ 			break;
  		case T_DoStmt:
  			retval = _equalDoStmt(a, b);
  			break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 227,232 **** static void processCASbits(int cas_bits, int location, const char *constrType,
--- 227,233 ----
  		DeallocateStmt PrepareStmt ExecuteStmt
  		DropOwnedStmt ReassignOwnedStmt
  		AlterTSConfigurationStmt AlterTSDictionaryStmt
+ 		CheckFunctionStmt
  
  %type <node>	select_no_parens select_with_parens select_clause
  				simple_select values_clause
***************
*** 276,282 **** static void processCASbits(int cas_bits, int location, const char *constrType,
  
  %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
  				opt_class opt_inline_handler opt_validator validator_clause
! 				opt_collate
  
  %type <range>	qualified_name OptConstrFromTable
  
--- 277,283 ----
  
  %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
  				opt_class opt_inline_handler opt_validator validator_clause
! 				opt_collate opt_checker
  
  %type <range>	qualified_name OptConstrFromTable
  
***************
*** 463,468 **** static void processCASbits(int cas_bits, int location, const char *constrType,
--- 464,473 ----
  %type <windef>	window_definition over_clause window_specification
  				opt_frame_clause frame_extent frame_bound
  %type <str>		opt_existing_window_name
+ %type <defelt>	CheckFunctionOptElem check_option_elem
+ %type <list>	CheckFunctionOpts OptCheckFunctionOpts check_option_list opt_check_options
+ %type <str>	check_option_name
+ %type <node>	check_option_arg
  
  
  /*
***************
*** 700,705 **** stmt :
--- 705,711 ----
  			| AlterUserSetStmt
  			| AlterUserStmt
  			| AnalyzeStmt
+ 			| CheckFunctionStmt
  			| CheckPointStmt
  			| ClosePortalStmt
  			| ClusterStmt
***************
*** 3216,3226 **** CreatePLangStmt:
  				n->plhandler = NIL;
  				n->plinline = NIL;
  				n->plvalidator = NIL;
  				n->pltrusted = false;
  				$$ = (Node *)n;
  			}
  			| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
! 			  HANDLER handler_name opt_inline_handler opt_validator
  			{
  				CreatePLangStmt *n = makeNode(CreatePLangStmt);
  				n->replace = $2;
--- 3222,3233 ----
  				n->plhandler = NIL;
  				n->plinline = NIL;
  				n->plvalidator = NIL;
+ 				n->plchecker = NIL;
  				n->pltrusted = false;
  				$$ = (Node *)n;
  			}
  			| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
! 			  HANDLER handler_name opt_inline_handler opt_validator opt_checker
  			{
  				CreatePLangStmt *n = makeNode(CreatePLangStmt);
  				n->replace = $2;
***************
*** 3228,3233 **** CreatePLangStmt:
--- 3235,3241 ----
  				n->plhandler = $8;
  				n->plinline = $9;
  				n->plvalidator = $10;
+ 				n->plchecker = $11;
  				n->pltrusted = $3;
  				$$ = (Node *)n;
  			}
***************
*** 3262,3267 **** opt_validator:
--- 3270,3280 ----
  			| /*EMPTY*/								{ $$ = NIL; }
  		;
  
+ opt_checker:
+ 			CHECK handler_name					{ $$ = $2; }
+ 			| /*EMPTY*/								{ $$ = NIL; }
+ 		;
+ 
  DropPLangStmt:
  			DROP opt_procedural LANGUAGE ColId_or_Sconst opt_drop_behavior
  				{
***************
*** 6319,6324 **** any_operator:
--- 6332,6459 ----
  
  /*****************************************************************************
   *
+  *		CHECK FUNCTION funcname(args)
+  *		CHECK TRIGGER triggername ON table
+  *
+  *
+  *****************************************************************************/
+ 
+ 
+ CheckFunctionStmt:
+ 			CHECK FUNCTION func_name func_args opt_check_options
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = $3;
+ 					n->args = extractArgTypes($4);
+ 					n->trgname = NULL;
+ 					n->relation = NULL;
+ 					n->is_function = true;
+ 					n->options = NIL;
+ 					n->check_options = $5;
+ 					$$ = (Node *) n;
+ 				}
+ 			| CHECK TRIGGER name ON qualified_name opt_check_options
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = NULL;
+ 					n->args = NIL;
+ 					n->trgname = $3;
+ 					n->relation = $5;
+ 					n->is_function = false;
+ 					n->options = NIL;
+ 					n->check_options = $6;
+ 					$$ = (Node *) n;
+ 				}
+ 			| CHECK FUNCTION ALL OptCheckFunctionOpts opt_check_options
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = NULL;
+ 					n->args = NIL;
+ 					n->trgname = NULL;
+ 					n->relation = NULL;
+ 					n->is_function = true;
+ 					n->options = $4;
+ 					n->check_options = $5;
+ 					$$ = (Node *) n;
+ 				}
+ 		;
+ 
+ OptCheckFunctionOpts:
+ 			CheckFunctionOpts
+ 				{
+ 					$$ = $1;
+ 				}
+ 			| /* EMPTY */
+ 				{
+ 					$$ = NIL;
+ 				}
+ 		;
+ 
+ CheckFunctionOpts:
+ 			CheckFunctionOptElem					{ $$ = list_make1($1); }
+ 			| CheckFunctionOpts CheckFunctionOptElem	{ $$ = lappend($1, $2); }
+ 		;
+ 
+ CheckFunctionOptElem:
+ 			IN_P LANGUAGE ColId
+ 				{
+ 					$$ = makeDefElem("language",
+ 								    (Node *) makeString($3));
+ 				}
+ 			| IN_P SCHEMA ColId
+ 				{
+ 					$$ = makeDefElem("schema",
+ 								    (Node *) makeString($3));
+ 				}
+ 			| FOR ROLE ColId
+ 				{
+ 					$$ = makeDefElem("owner",
+ 								    (Node *) makeString($3));
+ 				}
+ 		;
+ 
+ opt_check_options:
+ 			WITH '(' check_option_list ')'
+ 				{
+ 					$$ = $3;
+ 				}
+ 			| /* EMPTY */
+ 				{
+ 					$$ = NIL;
+ 				}
+ 		;
+ 
+ check_option_list:
+ 			check_option_elem
+ 				{
+ 					$$ = list_make1($1);
+ 				}
+ 			| check_option_list ',' check_option_elem
+ 				{
+ 					$$ = lappend($1, $3);
+ 				}
+ 		;
+ 
+ check_option_elem:
+ 			check_option_name check_option_arg
+ 				{
+ 					$$ = makeDefElem($1, $2);
+ 				}
+ 		;
+ 
+ check_option_name:
+ 			ColId					{ $$ = $1; }
+ 			| analyze_keyword		{ $$ = "analyze"; }
+ 			| VERBOSE				{ $$ = "verbose"; }
+ 		;
+ 
+ check_option_arg:
+ 			opt_boolean_or_string	{ $$ = (Node *) makeString($1); }
+ 			| /* EMPTY */			{ $$ = NULL; }
+ 		;
+ 
+ /*****************************************************************************
+  *
   *		DO <anonymous code block> [ LANGUAGE language ]
   *
   * We use a DefElem list for future extensibility, and to allow flexibility
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 901,906 **** standard_ProcessUtility(Node *parsetree,
--- 901,910 ----
  			AlterFunction((AlterFunctionStmt *) parsetree);
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			CheckFunction((CheckFunctionStmt *) parsetree, dest);
+ 			break;
+ 
  		case T_IndexStmt:		/* CREATE INDEX */
  			{
  				IndexStmt  *stmt = (IndexStmt *) parsetree;
***************
*** 1243,1248 **** UtilityReturnsTuples(Node *parsetree)
--- 1247,1255 ----
  		case T_ExplainStmt:
  			return true;
  
+ 		case T_CheckFunctionStmt:
+ 			return true;
+ 
  		case T_VariableShowStmt:
  			return true;
  
***************
*** 1293,1298 **** UtilityTupleDescriptor(Node *parsetree)
--- 1300,1308 ----
  		case T_ExplainStmt:
  			return ExplainResultDesc((ExplainStmt *) parsetree);
  
+ 		case T_CheckFunctionStmt:
+ 			return CheckFunctionResultDesc((CheckFunctionStmt *) parsetree);
+ 
  		case T_VariableShowStmt:
  			{
  				VariableShowStmt *n = (VariableShowStmt *) parsetree;
***************
*** 2144,2149 **** CreateCommandTag(Node *parsetree)
--- 2154,2166 ----
  			}
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			if (((CheckFunctionStmt *) parsetree)->is_function)
+ 				tag = "CHECK FUNCTION";
+ 			else
+ 				tag = "CHECK TRIGGER";
+ 			break;
+ 
  		default:
  			elog(WARNING, "unrecognized node type: %d",
  				 (int) nodeTag(parsetree));
***************
*** 2584,2589 **** GetCommandLogLevel(Node *parsetree)
--- 2601,2610 ----
  			}
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			lev = LOGSTMT_ALL;
+ 			break;
+ 
  		default:
  			elog(WARNING, "unrecognized node type: %d",
  				 (int) nodeTag(parsetree));
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 5398,5410 **** getProcLangs(int *numProcLangs)
  	int			i_lanplcallfoid;
  	int			i_laninline;
  	int			i_lanvalidator;
  	int			i_lanacl;
  	int			i_lanowner;
  
  	/* Make sure we are in proper schema */
  	selectSourceSchema("pg_catalog");
  
! 	if (g_fout->remoteVersion >= 90000)
  	{
  		/* pg_language has a laninline column */
  		appendPQExpBuffer(query, "SELECT tableoid, oid, "
--- 5398,5423 ----
  	int			i_lanplcallfoid;
  	int			i_laninline;
  	int			i_lanvalidator;
+ 	int			i_lanchecker;
  	int			i_lanacl;
  	int			i_lanowner;
  
  	/* Make sure we are in proper schema */
  	selectSourceSchema("pg_catalog");
  
! 	if (g_fout->remoteVersion >= 90200)
! 	{
! 		/* pg_language has a lanchecker column */
! 		appendPQExpBuffer(query, "SELECT tableoid, oid, "
! 						  "lanname, lanpltrusted, lanplcallfoid, "
! 						  "laninline, lanvalidator, lanchecker, lanacl, "
! 						  "(%s lanowner) AS lanowner "
! 						  "FROM pg_language "
! 						  "WHERE lanispl "
! 						  "ORDER BY oid",
! 						  username_subquery);
! 	}
! 	else if (g_fout->remoteVersion >= 90000)
  	{
  		/* pg_language has a laninline column */
  		appendPQExpBuffer(query, "SELECT tableoid, oid, "
***************
*** 5481,5486 **** getProcLangs(int *numProcLangs)
--- 5494,5500 ----
  	/* these may fail and return -1: */
  	i_laninline = PQfnumber(res, "laninline");
  	i_lanvalidator = PQfnumber(res, "lanvalidator");
+ 	i_lanchecker = PQfnumber(res, "lanchecker");
  	i_lanacl = PQfnumber(res, "lanacl");
  	i_lanowner = PQfnumber(res, "lanowner");
  
***************
*** 5494,5499 **** getProcLangs(int *numProcLangs)
--- 5508,5517 ----
  		planginfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_lanname));
  		planginfo[i].lanpltrusted = *(PQgetvalue(res, i, i_lanpltrusted)) == 't';
  		planginfo[i].lanplcallfoid = atooid(PQgetvalue(res, i, i_lanplcallfoid));
+ 		if (i_lanchecker >= 0)
+ 			planginfo[i].lanchecker = atooid(PQgetvalue(res, i, i_lanchecker));
+ 		else
+ 			planginfo[i].lanchecker = InvalidOid;
  		if (i_laninline >= 0)
  			planginfo[i].laninline = atooid(PQgetvalue(res, i, i_laninline));
  		else
***************
*** 8709,8714 **** dumpProcLang(Archive *fout, ProcLangInfo *plang)
--- 8727,8733 ----
  	char	   *qlanname;
  	char	   *lanschema;
  	FuncInfo   *funcInfo;
+ 	FuncInfo   *checkerInfo = NULL;
  	FuncInfo   *inlineInfo = NULL;
  	FuncInfo   *validatorInfo = NULL;
  
***************
*** 8728,8733 **** dumpProcLang(Archive *fout, ProcLangInfo *plang)
--- 8747,8759 ----
  	if (funcInfo != NULL && !funcInfo->dobj.dump)
  		funcInfo = NULL;		/* treat not-dumped same as not-found */
  
+ 	if (OidIsValid(plang->lanchecker))
+ 	{
+ 		checkerInfo = findFuncByOid(plang->lanchecker);
+ 		if (checkerInfo != NULL && !checkerInfo->dobj.dump)
+ 			checkerInfo = NULL;
+ 	}
+ 
  	if (OidIsValid(plang->laninline))
  	{
  		inlineInfo = findFuncByOid(plang->laninline);
***************
*** 8754,8759 **** dumpProcLang(Archive *fout, ProcLangInfo *plang)
--- 8780,8786 ----
  	 * don't, this might not work terribly nicely.
  	 */
  	useParams = (funcInfo != NULL &&
+ 				 (checkerInfo != NULL || !OidIsValid(plang->lanchecker)) &&
  				 (inlineInfo != NULL || !OidIsValid(plang->laninline)) &&
  				 (validatorInfo != NULL || !OidIsValid(plang->lanvalidator)));
  
***************
*** 8809,8814 **** dumpProcLang(Archive *fout, ProcLangInfo *plang)
--- 8836,8851 ----
  			appendPQExpBuffer(defqry, "%s",
  							  fmtId(validatorInfo->dobj.name));
  		}
+ 		if (OidIsValid(plang->lanchecker))
+ 		{
+ 			appendPQExpBuffer(defqry, " CHECK ");
+ 			/* Cope with possibility that checker is in different schema */
+ 			if (checkerInfo->dobj.namespace != funcInfo->dobj.namespace)
+ 				appendPQExpBuffer(defqry, "%s.",
+ 							   fmtId(checkerInfo->dobj.namespace->dobj.name));
+ 			appendPQExpBuffer(defqry, "%s",
+ 							  fmtId(checkerInfo->dobj.name));
+ 		}
  	}
  	else
  	{
*** a/src/bin/pg_dump/pg_dump.h
--- b/src/bin/pg_dump/pg_dump.h
***************
*** 390,395 **** typedef struct _procLangInfo
--- 390,396 ----
  	Oid			lanplcallfoid;
  	Oid			laninline;
  	Oid			lanvalidator;
+ 	Oid			lanchecker;
  	char	   *lanacl;
  	char	   *lanowner;		/* name of owner, or empty string */
  } ProcLangInfo;
*** a/src/bin/psql/tab-complete.c
--- b/src/bin/psql/tab-complete.c
***************
*** 1,4 ****
--- 1,5 ----
  /*
+  *
   * psql - the PostgreSQL interactive terminal
   *
   * Copyright (c) 2000-2012, PostgreSQL Global Development Group
***************
*** 727,733 **** psql_completion(char *text, int start, int end)
  #define prev6_wd  (previous_words[5])
  
  	static const char *const sql_commands[] = {
! 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
  		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
--- 728,734 ----
  #define prev6_wd  (previous_words[5])
  
  	static const char *const sql_commands[] = {
! 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECK", "CHECKPOINT", "CLOSE", "CLUSTER",
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
  		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
***************
*** 1524,1529 **** psql_completion(char *text, int start, int end)
--- 1525,1552 ----
  
  		COMPLETE_WITH_LIST(list_TRANS);
  	}
+ 
+ /* CHECK */
+ 	else if (pg_strcasecmp(prev_wd, "CHECK") == 0)
+ 	{
+ 		static const char *const list_CHECK[] =
+ 		{"FUNCTION", "TRIGGER", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_CHECK);
+ 	}
+ 	else if (pg_strcasecmp(prev3_wd, "CHECK") == 0 &&
+ 			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+ 	{
+ 		COMPLETE_WITH_CONST("ON");
+ 	}
+ 	else if (pg_strcasecmp(prev4_wd, "CHECK") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "ON") == 0)
+ 	{
+ 		completion_info_charp = prev2_wd;
+ 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+ 	}
+ 
  /* CLUSTER */
  
  	/*
*** a/src/include/catalog/pg_language.h
--- b/src/include/catalog/pg_language.h
***************
*** 37,42 **** CATALOG(pg_language,2612)
--- 37,43 ----
  	Oid			lanplcallfoid;	/* Call handler for PL */
  	Oid			laninline;		/* Optional anonymous-block handler function */
  	Oid			lanvalidator;	/* Optional validation function */
+ 	Oid			lanchecker;	    /* Optional checker function */
  #ifdef CATALOG_VARLEN			/* variable-length fields start here */
  	aclitem		lanacl[1];		/* Access privileges */
  #endif
***************
*** 53,59 **** typedef FormData_pg_language *Form_pg_language;
   *		compiler constants for pg_language
   * ----------------
   */
! #define Natts_pg_language				8
  #define Anum_pg_language_lanname		1
  #define Anum_pg_language_lanowner		2
  #define Anum_pg_language_lanispl		3
--- 54,60 ----
   *		compiler constants for pg_language
   * ----------------
   */
! #define Natts_pg_language				9
  #define Anum_pg_language_lanname		1
  #define Anum_pg_language_lanowner		2
  #define Anum_pg_language_lanispl		3
***************
*** 61,80 **** typedef FormData_pg_language *Form_pg_language;
  #define Anum_pg_language_lanplcallfoid	5
  #define Anum_pg_language_laninline		6
  #define Anum_pg_language_lanvalidator	7
! #define Anum_pg_language_lanacl			8
  
  /* ----------------
   *		initial contents of pg_language
   * ----------------
   */
  
! DATA(insert OID = 12 ( "internal"	PGUID f f 0 0 2246 _null_ ));
  DESCR("built-in functions");
  #define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c"			PGUID f f 0 0 2247 _null_ ));
  DESCR("dynamically-loaded C functions");
  #define ClanguageId 13
! DATA(insert OID = 14 ( "sql"		PGUID f t 0 0 2248 _null_ ));
  DESCR("SQL-language functions");
  #define SQLlanguageId 14
  
--- 62,82 ----
  #define Anum_pg_language_lanplcallfoid	5
  #define Anum_pg_language_laninline		6
  #define Anum_pg_language_lanvalidator	7
! #define Anum_pg_language_lanchecker	    8
! #define Anum_pg_language_lanacl			9
  
  /* ----------------
   *		initial contents of pg_language
   * ----------------
   */
  
! DATA(insert OID = 12 ( "internal"	PGUID f f 0 0 2246 0 _null_ ));
  DESCR("built-in functions");
  #define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c"			PGUID f f 0 0 2247 0 _null_ ));
  DESCR("dynamically-loaded C functions");
  #define ClanguageId 13
! DATA(insert OID = 14 ( "sql"		PGUID f t 0 0 2248 0 _null_ ));
  DESCR("SQL-language functions");
  #define SQLlanguageId 14
  
*** a/src/include/catalog/pg_pltemplate.h
--- b/src/include/catalog/pg_pltemplate.h
***************
*** 37,42 **** CATALOG(pg_pltemplate,1136) BKI_SHARED_RELATION BKI_WITHOUT_OIDS
--- 37,43 ----
  	text		tmplhandler;	/* name of call handler function */
  	text		tmplinline;		/* name of anonymous-block handler, or NULL */
  	text		tmplvalidator;	/* name of validator function, or NULL */
+ 	text		tmplchecker;	/* name of checker function, or NULL */
  	text		tmpllibrary;	/* path of shared library */
  	aclitem		tmplacl[1];		/* access privileges for template */
  #endif
***************
*** 53,67 **** typedef FormData_pg_pltemplate *Form_pg_pltemplate;
   *		compiler constants for pg_pltemplate
   * ----------------
   */
! #define Natts_pg_pltemplate					8
  #define Anum_pg_pltemplate_tmplname			1
  #define Anum_pg_pltemplate_tmpltrusted		2
  #define Anum_pg_pltemplate_tmpldbacreate	3
  #define Anum_pg_pltemplate_tmplhandler		4
  #define Anum_pg_pltemplate_tmplinline		5
  #define Anum_pg_pltemplate_tmplvalidator	6
! #define Anum_pg_pltemplate_tmpllibrary		7
! #define Anum_pg_pltemplate_tmplacl			8
  
  
  /* ----------------
--- 54,69 ----
   *		compiler constants for pg_pltemplate
   * ----------------
   */
! #define Natts_pg_pltemplate					9
  #define Anum_pg_pltemplate_tmplname			1
  #define Anum_pg_pltemplate_tmpltrusted		2
  #define Anum_pg_pltemplate_tmpldbacreate	3
  #define Anum_pg_pltemplate_tmplhandler		4
  #define Anum_pg_pltemplate_tmplinline		5
  #define Anum_pg_pltemplate_tmplvalidator	6
! #define Anum_pg_pltemplate_tmplchecker		7
! #define Anum_pg_pltemplate_tmpllibrary		8
! #define Anum_pg_pltemplate_tmplacl			9
  
  
  /* ----------------
***************
*** 69,81 **** typedef FormData_pg_pltemplate *Form_pg_pltemplate;
   * ----------------
   */
  
! DATA(insert ( "plpgsql"		t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl"		t t "pltcl_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu"		f f "pltclu_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl"		t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu"		f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu"	f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u"	f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u"	f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" "$libdir/plpython3" _null_ ));
  
  #endif   /* PG_PLTEMPLATE_H */
--- 71,83 ----
   * ----------------
   */
  
! DATA(insert ( "plpgsql"		t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "plpgsql_checker" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl"		t t "pltcl_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu"		f f "pltclu_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl"		t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu"		f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu"	f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u"	f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u"	f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" _null_ "$libdir/plpython3" _null_ ));
  
  #endif   /* PG_PLTEMPLATE_H */
*** a/src/include/commands/defrem.h
--- b/src/include/commands/defrem.h
***************
*** 15,20 ****
--- 15,21 ----
  #define DEFREM_H
  
  #include "nodes/parsenodes.h"
+ #include "tcop/dest.h"
  
  /* commands/dropcmds.c */
  extern void RemoveObjects(DropStmt *stmt);
***************
*** 62,67 **** extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
--- 63,70 ----
  /* commands/functioncmds.c */
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
  extern void RemoveFunctionById(Oid funcOid);
+ extern void CheckFunction(CheckFunctionStmt *stmt, DestReceiver *dest);
+ extern TupleDesc CheckFunctionResultDesc(CheckFunctionStmt *stmt); 
  extern void SetFunctionReturnType(Oid funcOid, Oid newRetType);
  extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType);
  extern void RenameFunction(List *name, List *argtypes, const char *newname);
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 290,295 **** typedef enum NodeTag
--- 290,296 ----
  	T_IndexStmt,
  	T_CreateFunctionStmt,
  	T_AlterFunctionStmt,
+ 	T_CheckFunctionStmt,
  	T_DoStmt,
  	T_RenameStmt,
  	T_RuleStmt,
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1740,1745 **** typedef struct CreatePLangStmt
--- 1740,1746 ----
  	List	   *plhandler;		/* PL call handler function (qual. name) */
  	List	   *plinline;		/* optional inline function (qual. name) */
  	List	   *plvalidator;	/* optional validator function (qual. name) */
+ 	List	   *plchecker;		/* optional checker function (qual. name) */
  	bool		pltrusted;		/* PL is trusted */
  } CreatePLangStmt;
  
***************
*** 2084,2089 **** typedef struct AlterFunctionStmt
--- 2085,2106 ----
  } AlterFunctionStmt;
  
  /* ----------------------
+  *		Check {Function|Trigger} Statement
+  * ----------------------
+  */
+ typedef struct CheckFunctionStmt
+ {
+ 	NodeTag		type;
+ 	List	   *funcname;			/* qualified name of checked object */
+ 	List	   *args;			/* types of the arguments */
+ 	char	   *trgname;			/* trigger's name */
+ 	RangeVar	*relation;		/* trigger's relation */
+ 	bool	   is_function;		/* true for CHECK FUNCTION statement */
+ 	List	   *options;			/* other DefElem options */
+ 	List	   *check_options;		/* options for check function */
+ } CheckFunctionStmt;
+ 
+ /* ----------------------
   *		DO Statement
   *
   * DoStmt is the raw parser output, InlineCodeBlock is the execution-time API
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
***************
*** 115,121 **** 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.
--- 115,120 ----
***************
*** 175,181 **** recheck:
  			 * 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
--- 174,180 ----
  			 * 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
***************
*** 2426,2432 **** plpgsql_resolve_polymorphic_argtypes(int numargs,
  }
  
  /*
!  * 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
--- 2425,2431 ----
  }
  
  /*
!  * 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,2446 **** 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)
  {
  	/* remove function from hash table (might be done already) */
  	plpgsql_HashTableDelete(func);
--- 2438,2445 ----
   * 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);
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
***************
*** 210,216 **** static void free_params_data(PreparedParamsData *ppd);
--- 210,237 ----
  static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
  						  PLpgSQL_expr *dynquery, List *params,
  						  const char *portalname, int cursorOptions);
+ static bool check_row_or_rec(PLpgSQL_checkstate *cstate, PLpgSQL_row *row, PLpgSQL_rec *rec);
+ static bool 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 TupleDesc expr_get_desc(PLpgSQL_checkstate *cstate,
+ 						PLpgSQL_expr *query,
+ 							bool use_element_type,
+ 							bool expand_record,
+ 								bool is_expression);
+ static void var_init_to_null(PLpgSQL_checkstate *cstate, int varno);
+ static bool check_stmts(PLpgSQL_checkstate *cstate, List *stmts);
+ static bool check_stmt(PLpgSQL_checkstate *cstate, PLpgSQL_stmt *stmt);
+ static bool prepare_expr(PLpgSQL_checkstate *cstate,
+ 				  PLpgSQL_expr *expr, int cursorOptions);
  
+ /*
+  * check_function raises exception, when this variable is true.
+  * A reason why global variable is used is a change of context
+  * log - see check_error_callback.
+  */
+ bool plpgsql_stop_on_first_error;
  
  /* ----------
   * plpgsql_exec_function	Called by the call handler for
***************
*** 6176,6178 **** exec_dynquery_with_params(PLpgSQL_execstate *estate,
--- 6197,7744 ----
  
  	return portal;
  }
+ 
+ /*
+  * Following code ensures a CHECK FUNCTION and CHECK TRIGGER statements for PL/pgSQL
+  *
+  */
+ 
+ /*
+  * Store error record to tuplestore
+  */
+ static void
+ report_error(PLpgSQL_checkstate *cstate,
+ 					PLpgSQL_stmt *stmt,
+ 							    int sqlerrcode,
+ 							    char *message,
+ 							    char *detail,
+ 							    char *hint,
+ 									char *level,
+ 										    int position,
+ 										    char *query)
+ {
+ 	Datum	values[10];
+ 	bool	nulls[10];
+ 
+ 	values[0] = ObjectIdGetDatum(cstate->estate.func->fn_oid);
+ 	nulls[0] = false;
+ 
+ 	if (stmt != NULL)
+ 	{
+ 		values[1] = Int32GetDatum(stmt->lineno);
+ 		values[2] = CStringGetTextDatum(plpgsql_stmt_typename(stmt));
+ 		nulls[1] = false;
+ 		nulls[2] = false;
+ 	}
+ 	else
+ 	{
+ 		nulls[1] = true;
+ 		nulls[2] = true;
+ 	}
+ 
+ 	values[3] = CStringGetTextDatum(unpack_sql_state(sqlerrcode));
+ 	nulls[3] = false;
+ 
+ 	if (message != NULL)
+ 	{
+ 		values[4] = CStringGetTextDatum(message);
+ 		nulls[4] = false;
+ 	}
+ 	else
+ 		nulls[4] = true;
+ 
+ 	if (detail != NULL)
+ 	{
+ 		values[5] = CStringGetTextDatum(detail);
+ 		nulls[5] = false;
+ 	}
+ 	else
+ 		nulls[5] = true;
+ 
+ 	if (hint != NULL)
+ 	{
+ 		values[6] = CStringGetTextDatum(hint);
+ 		nulls[6] = false;
+ 	}
+ 	else
+ 		nulls[6] = true;
+ 
+ 	if (level != NULL)
+ 	{
+ 		values[7] = CStringGetTextDatum(level);
+ 		nulls[7] = false;
+ 	}
+ 	else
+ 		nulls[7] = true;
+ 
+ 	if (query != NULL)
+ 	{
+ 		values[8] = Int32GetDatum(position);
+ 		values[9] = CStringGetTextDatum(query);
+ 		nulls[8] = false;
+ 		nulls[9] = false;
+ 	}
+ 	else
+ 	{
+ 		nulls[8] = true;
+ 		nulls[9] = true;
+ 	}
+ 
+ 	tuplestore_putvalues(cstate->tupstore, cstate->tupdesc, values, nulls);
+ }
+ 
+ static void
+ report_error_edata(PLpgSQL_checkstate *cstate,
+ 					ErrorData *edata)
+ {
+ 	report_error(cstate, cstate->estate.err_stmt,
+ 							edata->sqlerrcode,
+ 								    edata->message,
+ 								    edata->detail,
+ 								    edata->hint,
+ 									    "error",
+ 										    edata->internalpos,
+ 										    edata->internalquery);
+ }
+ 
+ /*
+  * Check function - it prepare variables and starts a prepare plan walker
+  *		called by function checker
+  */
+ void
+ plpgsql_check_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
+ 									TupleDesc tupdesc,
+ 									Tuplestorestate *tupstore,
+ 										bool fatal_errors)
+ {
+ 	PLpgSQL_checkstate cstate;
+ 	int			i;
+ 
+ 	/*
+ 	 * Setup the execution state - we would to reuse some exec routines
+ 	 * so we need a estate
+ 	 */
+ 	plpgsql_estate_setup(&cstate.estate, func, (ReturnSetInfo *) fcinfo->resultinfo);
+ 	cstate.tupdesc = tupdesc;
+ 	cstate.tupstore = tupstore;
+ 	cstate.fatal_errors = fatal_errors;
+ 
+ 	/*
+ 	 * 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 into the appropriate variables
+ 	 */
+ 	for (i = 0; i < func->fn_nargs; i++)
+ 	{
+ 		int			n = func->fn_argvarnos[i];
+ 
+ 		switch (cstate.estate.datums[n]->dtype)
+ 		{
+ 			case PLPGSQL_DTYPE_VAR:
+ 				{
+ 					var_init_to_null(&cstate, n);
+ 				}
+ 				break;
+ 
+ 			case PLPGSQL_DTYPE_ROW:
+ 				{
+ 					PLpgSQL_row *row = (PLpgSQL_row *) cstate.estate.datums[n];
+ 
+ 					exec_move_row(&cstate.estate, NULL, row, NULL, NULL);
+ 				}
+ 				break;
+ 
+ 			default:
+ 				elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(&cstate, (PLpgSQL_stmt *) func->action);
+ 
+ 	/* Cleanup temporary memory */
+ 	plpgsql_destroy_econtext(&cstate.estate);
+ }
+ 
+ /*
+  * Check trigger - prepare fake environments for testing trigger
+  *
+  */
+ void
+ plpgsql_check_trigger(PLpgSQL_function *func,
+ 					 TriggerData *trigdata,
+ 								TupleDesc tupdesc,
+ 								Tuplestorestate *tupstore,
+ 									bool fatal_errors)
+ {
+ 	PLpgSQL_checkstate cstate;
+ 	PLpgSQL_rec *rec_new,
+ 			   *rec_old;
+ 	int			i;
+ 
+ 	/*
+ 	 * Setup the execution state - we would to reuse some exec routines
+ 	 * so we need a estate
+ 	 */
+ 	plpgsql_estate_setup(&cstate.estate, func, NULL);
+ 	cstate.tupdesc = tupdesc;
+ 	cstate.tupstore = tupstore;
+ 	cstate.fatal_errors = fatal_errors;
+ 
+ 	/*
+ 	 * 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
+ 	 */
+ 	var_init_to_null(&cstate, func->tg_op_varno);
+ 	var_init_to_null(&cstate, func->tg_name_varno);
+ 	var_init_to_null(&cstate, func->tg_when_varno);
+ 	var_init_to_null(&cstate, func->tg_level_varno);
+ 	var_init_to_null(&cstate, func->tg_relid_varno);
+ 	var_init_to_null(&cstate, func->tg_relname_varno);
+ 	var_init_to_null(&cstate, func->tg_table_name_varno);
+ 	var_init_to_null(&cstate, func->tg_table_schema_varno);
+ 	var_init_to_null(&cstate, func->tg_nargs_varno);
+ 	var_init_to_null(&cstate, func->tg_argv_varno);
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(&cstate, (PLpgSQL_stmt *) func->action);
+ 
+ 	/* Cleanup temporary memory */
+ 	plpgsql_destroy_econtext(&cstate.estate);
+ }
+ 
+ /*
+  * 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 bool
+ check_target(PLpgSQL_checkstate *cstate, int varno)
+ {
+ 	PLpgSQL_datum *target = cstate->estate.datums[varno];
+ 	StringInfoData		msg;
+ 
+ 	switch (target->dtype)
+ 	{
+ 		case PLPGSQL_DTYPE_VAR:
+ 		case PLPGSQL_DTYPE_REC:
+ 			return true;
+ 
+ 		case PLPGSQL_DTYPE_ROW:
+ 			return check_row_or_rec(cstate, (PLpgSQL_row *) target, NULL);
+ 
+ 		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))
+ 				{
+ 					initStringInfo(&msg);
+ 					appendStringInfo(&msg, "record \"%s\" is not assigned to tuple structure",
+ 									rec->refname);
+ 
+ 					report_error(cstate,
+ 							cstate->estate.err_stmt,
+ 								ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE,
+ 										msg.data,
+ 										NULL, NULL,
+ 											"error",
+ 												0, NULL);
+ 					pfree(msg.data);
+ 
+ 					return false;
+ 				}
+ 
+ 				/*
+ 				 * 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)
+ 				{
+ 					initStringInfo(&msg);
+ 					appendStringInfo(&msg, "record \"%s\" has no field \"%s\"",
+ 								        rec->refname, recfield->fieldname);
+ 					report_error(cstate,
+ 							cstate->estate.err_stmt,
+ 								ERRCODE_UNDEFINED_COLUMN,
+ 										msg.data,
+ 										NULL, NULL,
+ 											"error",
+ 												0, NULL);
+ 					pfree(msg.data);
+ 
+ 					return false;
+ 				}
+ 			}
+ 			return true;
+ 
+ 		case PLPGSQL_DTYPE_ARRAYELEM:
+ 			{
+ 				/*
+ 				 * Target is an element of an array
+ 				 */
+ 				int			nsubscripts;
+ 				Oid		arrayelemtypeid;
+ 				Oid		arraytypeid;
+ 				bool	result = true;
+ 				bool	overlimit_detected = false;
+ 
+ 				/*
+ 				 * 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;
+ 					bool	subscript_is_valid;
+ 
+ 					if (nsubscripts++ >= MAXDIM)
+ 					{
+ 						/* don't repeet message */
+ 						if (!overlimit_detected)
+ 						{
+ 						
+ 							initStringInfo(&msg);
+ 							appendStringInfo(&msg, "number of array dimensions (%d) exceeds the maximum allowed (%d)",
+ 												nsubscripts + 1, MAXDIM);
+ 
+ 							report_error(cstate,
+ 									cstate->estate.err_stmt,
+ 										ERRCODE_PROGRAM_LIMIT_EXCEEDED,
+ 												msg.data,
+ 												NULL, NULL,
+ 													"error",
+ 														0, NULL);
+ 							pfree(msg.data);
+ 
+ 							overlimit_detected = true;
+ 							result = false;
+ 
+ 							if (cstate->fatal_errors)
+ 							{
+ 								return false;
+ 							}
+ 						}
+ 					}
+ 
+ 					/*
+ 					 * validate expression, cannot to use check expression, because we
+ 					 * we have to know if expression is valid or not. Result of check_expr
+ 					 * is based on fatal_errors flag.
+ 					 */
+ 					subscript_is_valid = false;
+ 					if (prepare_expr(cstate, arrayelem->subscript, 0))
+ 					{
+ 						TupleDesc tupdesc = expr_get_desc(cstate, arrayelem->subscript, false, false, true);
+ 
+ 						if (tupdesc != NULL)
+ 						{
+ 							ReleaseTupleDesc(tupdesc);
+ 							subscript_is_valid = true;
+ 						}
+ 					}
+ 
+ 					if (!subscript_is_valid)
+ 					{
+ 						result = false;
+ 						if (cstate->fatal_errors)
+ 						{
+ 							return false;
+ 						}
+ 					}
+ 
+ 					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))
+ 				{
+ 					report_error(cstate,
+ 							cstate->estate.err_stmt,
+ 								ERRCODE_DATATYPE_MISMATCH,
+ 										"subscripted object is not an array",
+ 										NULL, NULL,
+ 											"error",
+ 												0, NULL);
+ 
+ 					result = false;
+ 					if (cstate->fatal_errors)
+ 						return false;
+ 				}
+ 
+ 				return result;
+ 			}
+ 			break;
+ 	}
+ 
+ 	return true;
+ }
+ 
+ /*
+  * Check composed lvalue
+  *    There is nothing to check on rec variables
+  */
+ static bool
+ check_row_or_rec(PLpgSQL_checkstate *cstate, PLpgSQL_row *row, PLpgSQL_rec *rec)
+ {
+ 	int fnum;
+ 	bool	result = true;
+ 
+ 	/* 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;
+ 
+ 			if (!check_target(cstate, row->varnos[fnum]))
+ 			{
+ 				result = false;
+ 
+ 				if (cstate->fatal_errors)
+ 					break;
+ 			}
+ 		}
+ 	}
+ 
+ 	return result;
+ }
+ 
+ /*
+  * Generate a prepared plan - this is simplyfied copy from pl_exec.c
+  *   Is not necessary to check simple plan,
+  * returns true, when expression is succesfully prepared.
+  */
+ static bool
+ prepare_expr(PLpgSQL_checkstate *cstate,
+ 				  PLpgSQL_expr *expr, int cursorOptions)
+ {
+ 	ResourceOwner		oldowner;
+ 	MemoryContext		oldCxt = CurrentMemoryContext;
+ 	SPIPlanPtr	plan = NULL;
+ 	bool			reported_bug;
+ 
+ 	/* leave when there are not expression */
+ 	if (expr == NULL)
+ 		return true;
+ 
+ 	/* leave when plan is created */
+ 	if (expr->plan != NULL)
+ 		return true;
+ 
+ 	/*
+ 	 * 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;
+ 
+ 	oldowner = CurrentResourceOwner;
+ 	BeginInternalSubTransaction(NULL);
+ 	MemoryContextSwitchTo(oldCxt);
+ 
+ 	PG_TRY();
+ 	{
+ 
+ 		/*
+ 		 * Generate and save the plan
+ 		 */
+ 		plan = SPI_prepare_params(expr->query,
+ 								  (ParserSetupHook) plpgsql_parser_setup,
+ 								  (void *) expr,
+ 								  cursorOptions);
+ 
+ 		RollbackAndReleaseCurrentSubTransaction();
+ 		MemoryContextSwitchTo(oldCxt);
+ 		CurrentResourceOwner = oldowner;
+ 
+ 		SPI_restore_connection();
+ 		reported_bug = false;
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		ErrorData	*edata;
+ 
+ 		MemoryContextSwitchTo(oldCxt);
+ 		edata = CopyErrorData();
+ 		FlushErrorState();
+ 
+ 		RollbackAndReleaseCurrentSubTransaction();
+ 		MemoryContextSwitchTo(oldCxt);
+ 		CurrentResourceOwner = oldowner;
+ 
+ 		report_error_edata(cstate, edata);
+ 		MemoryContextSwitchTo(oldCxt);
+ 
+ 		/* reconnect spi */
+ 		SPI_restore_connection();
+ 
+ 		reported_bug = true;
+ 	}
+ 	PG_END_TRY();
+ 
+ 	if (plan == NULL && !reported_bug)
+ 	{
+ 		/* Some SPI errors deserve specific error messages */
+ 		switch (SPI_result)
+ 		{
+ 			case SPI_ERROR_COPY:
+ 				report_error(cstate,
+ 						cstate->estate.err_stmt,
+ 							ERRCODE_FEATURE_NOT_SUPPORTED,
+ 							"cannot COPY to/from client in PL/pgSQL", NULL, NULL,
+ 										"error",
+ 											0, expr->query);
+ 				break;
+ 
+ 			case SPI_ERROR_TRANSACTION:
+ 				report_error(cstate,
+ 						cstate->estate.err_stmt,
+ 							ERRCODE_FEATURE_NOT_SUPPORTED,
+ 							"cannot begin/end transactions in PL/pgSQL",
+ 							NULL,
+ 							"Use a BEGIN block with an EXCEPTION clause instead.",
+ 										"error",
+ 											0, expr->query);
+ 				break;
+ 
+ 			default:
+ 				elog(ERROR, "SPI_prepare_params failed for \"%s\": %s",
+ 					 expr->query, SPI_result_code_string(SPI_result));
+ 		}
+ 	}
+ 
+ 	if (plan != NULL)
+ 	{
+ 		expr->plan = SPI_saveplan(plan);
+ 		SPI_freeplan(plan);
+ 	}
+ 
+ 	return plan != NULL;
+ }
+ 
+ /*
+  * Verify a expression
+  */
+ static bool
+ check_expr(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr)
+ {
+ 	TupleDesc tupdesc;
+ 
+ 	if (expr != NULL)
+ 	{
+ 		if (prepare_expr(cstate, expr, 0))
+ 		{
+ 			tupdesc = expr_get_desc(cstate, expr, false, false, true);
+ 			if (tupdesc != NULL)
+ 			{
+ 				ReleaseTupleDesc(tupdesc);
+ 			}
+ 			else
+ 				return cstate->fatal_errors;
+ 		}
+ 		else
+ 			return cstate->fatal_errors;
+ 	}
+ 
+ 	return false;
+ }
+ 
+ /*
+  * 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)
+ 	{
+ 		report_error(cstate,
+ 				cstate->estate.err_stmt, 0,
+ 					    "tuple descriptor is empty", NULL, NULL,
+ 									"warning",
+ 										    0, 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;
+ 	StringInfoData		msg;
+ 
+ 	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);
+ 
+ 		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 (tupdesc->natts != 1)
+ 		{
+ 			initStringInfo(&msg);
+ 			appendStringInfo(&msg, "query \"%s\" returned %d columns",
+ 										    query->query,
+ 										    tupdesc->natts);
+ 			report_error(cstate,
+ 						cstate->estate.err_stmt,
+ 							ERRCODE_SYNTAX_ERROR,
+ 							msg.data, NULL, NULL,
+ 										"error",
+ 											0, query->query);
+ 
+ 			pfree(msg.data);
+ 			FreeTupleDesc(tupdesc);
+ 
+ 			return NULL;
+ 		}
+ 
+ 		/* check the type of the expression - must be an array */
+ 		elemtype = get_element_type(tupdesc->attrs[0]->atttypid);
+ 		if (!OidIsValid(elemtype))
+ 		{
+ 			initStringInfo(&msg);
+ 			appendStringInfo(&msg, "FOREACH expression must yield an array, not type %s",
+ 						format_type_be(tupdesc->attrs[0]->atttypid));
+ 			report_error(cstate,
+ 						cstate->estate.err_stmt,
+ 							ERRCODE_DATATYPE_MISMATCH,
+ 							msg.data, NULL, NULL,
+ 										"error",
+ 											0, query->query);
+ 
+ 			pfree(msg.data);
+ 			FreeTupleDesc(tupdesc);
+ 
+ 			return NULL;
+ 		}
+ 
+ 		/* 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
+ 			report_error(cstate,
+ 						cstate->estate.err_stmt,
+ 							0,
+ 							"cannot to identify real type for record type variable", NULL, NULL,
+ 										"warning",
+ 											0, query->query);
+ 
+ 			FreeTupleDesc(tupdesc);
+ 
+ 			return NULL;
+ 	}
+ 
+ 	if (is_expression && tupdesc->natts != 1)
+ 	{
+ 		initStringInfo(&msg);
+ 		appendStringInfo(&msg, "query \"%s\" returned %d columns",
+ 									    query->query,
+ 									    tupdesc->natts);
+ 		report_error(cstate,
+ 					cstate->estate.err_stmt,
+ 						ERRCODE_SYNTAX_ERROR,
+ 						msg.data, NULL, NULL,
+ 									"error",
+ 										0, query->query);
+ 
+ 		pfree(msg.data);
+ 		FreeTupleDesc(tupdesc);
+ 
+ 		return NULL;
+ 	}
+ 
+ 	/*
+ 	 * 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(..)
+ 	 */
+ 	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)
+ 					{
+ 						report_error(cstate,
+ 									cstate->estate.err_stmt,
+ 										ERRCODE_DATATYPE_MISMATCH,
+ 				"function does not return composite type, is not possible to identify composite type",
+ 										NULL, NULL,
+ 											"error",
+ 												0, query->query);
+ 
+ 						FreeTupleDesc(tupdesc);
+ 						ReleaseCachedPlan(cplan, true);
+ 						return NULL;
+ 					}
+ 
+ 					FreeTupleDesc(tupdesc);
+ 					BlessTupleDesc(rd);
+ 
+ 					tupdesc = rd;
+ 				}
+ 			}
+ 		}
+ 
+ 		ReleaseCachedPlan(cplan, true);
+ 	}
+ 
+ 	return tupdesc;
+ }
+ 
+ /*
+  * Ensure check for all statements in list
+  */
+ bool
+ check_stmts(PLpgSQL_checkstate *cstate, List *stmts)
+ {
+ 	ListCell *lc;
+ 
+ 	foreach(lc, stmts)
+ 	{
+ 		if (check_stmt(cstate, (PLpgSQL_stmt *) lfirst(lc)) && cstate->fatal_errors)
+ 			return true;
+ 	}
+ 
+ 	return false;
+ }
+ 
+ /*
+  * walk over all statements
+  */
+ bool
+ check_stmt(PLpgSQL_checkstate *cstate, PLpgSQL_stmt *stmt)
+ {
+ 	TupleDesc	tupdesc = NULL;
+ 	PLpgSQL_function *func;
+ 	ListCell *l;
+ 
+ 	if (stmt == NULL)
+ 		return false;
+ 
+ 	cstate->estate.err_stmt = stmt;
+ 	func = cstate->estate.func;
+ 
+ 	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++)
+ 				{
+ 					d = func->datums[stmt_block->initvarnos[i]];
+ 
+ 					if (d->dtype == PLPGSQL_DTYPE_VAR)
+ 					{
+ 						PLpgSQL_var *var = (PLpgSQL_var *) d;
+ 
+ 						if (check_expr(cstate, var->default_val))
+ 							return true;
+ 					}
+ 				}
+ 
+ 				if (check_stmts(cstate, stmt_block->body))
+ 					return true;;
+ 
+ 				if (stmt_block->exceptions)
+ 				{
+ 					foreach(l, stmt_block->exceptions->exc_list)
+ 					{
+ 						if (check_stmts(cstate, ((PLpgSQL_exception *) lfirst(l))->action))
+ 							return true;
+ 					}
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_ASSIGN:
+ 			{
+ 				PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt;
+ 				bool	is_valid = false;
+ 				bool	target_is_valid;
+ 
+ 				target_is_valid = check_target(cstate, stmt_assign->varno);
+ 				if (!target_is_valid && cstate->fatal_errors)
+ 					return true;
+ 
+ 				/* prepare plan if desn't exist yet */
+ 				if (prepare_expr(cstate, stmt_assign->expr, 0))
+ 				{
+ 					tupdesc = expr_get_desc(cstate,
+ 								stmt_assign->expr,
+ 										false,		/* no element type */
+ 										true,		/* expand record */
+ 										true);		/* is expression */
+ 
+ 					if (tupdesc != NULL)
+ 					{
+ 						/* assign a tupdesc to record variable */
+ 						if (target_is_valid)
+ 							assign_tupdesc_dno(cstate, stmt_assign->varno, tupdesc);
+ 						is_valid = true;
+ 						ReleaseTupleDesc(tupdesc);
+ 					}
+ 				}
+ 
+ 				if (!is_valid && cstate->fatal_errors)
+ 					return true;
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_IF:
+ 			{
+ 				PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt;
+ 				ListCell *l;
+ 
+ 				if (check_expr(cstate, stmt_if->cond))
+ 					return true;
+ 
+ 				if (check_stmts(cstate, stmt_if->then_body))
+ 					return true;
+ 
+ 				foreach(l, stmt_if->elsif_list)
+ 				{
+ 					PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l);
+ 
+ 					if (check_expr(cstate, elif->cond))
+ 						return true;
+ 
+ 					if (check_stmts(cstate, elif->stmts))
+ 						return true;
+ 				}
+ 
+ 				if (check_stmts(cstate, stmt_if->else_body))
+ 					return true;
+ 			}
+ 			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];
+ 					bool	is_valid = false;
+ 
+ 					/* we need to set hidden variable type */
+ 					if (prepare_expr(cstate, stmt_case->t_expr, 0))
+ 					{
+ 						tupdesc = expr_get_desc(cstate,
+ 										stmt_case->t_expr,
+ 											false,		/* no element type */
+ 											false,		/* expand record */
+ 											true);		/* is expression */
+ 						if (tupdesc != NULL)
+ 						{
+ 							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);
+ 							is_valid = true;
+ 						}
+ 					}
+ 
+ 					if (!is_valid && cstate->fatal_errors)
+ 						return true;
+ 				}
+ 
+ 				foreach(l, stmt_case->case_when_list)
+ 				{
+ 					PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
+ 
+ 					if (check_expr(cstate, cwt->expr))
+ 						return true;
+ 
+ 					if (check_stmts(cstate, cwt->stmts))
+ 						return true;
+ 				}
+ 
+ 				if (check_stmts(cstate, stmt_case->else_stmts))
+ 					return true;
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_LOOP:
+ 			if (check_stmts(cstate, ((PLpgSQL_stmt_loop *) stmt)->body))
+ 				return true;
+ 			break;
+ 
+ 		case PLPGSQL_STMT_WHILE:
+ 			{
+ 				PLpgSQL_stmt_while *stmt_while = (PLpgSQL_stmt_while *) stmt;
+ 
+ 				if (check_expr(cstate, stmt_while->cond))
+ 					return true;
+ 
+ 				if (check_stmts(cstate, stmt_while->body))
+ 					return true;
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORI:
+ 			{
+ 				PLpgSQL_stmt_fori *stmt_fori = (PLpgSQL_stmt_fori *) stmt;
+ 
+ 				if (check_expr(cstate, stmt_fori->lower))
+ 					return true;
+ 
+ 				if (check_expr(cstate, stmt_fori->upper))
+ 					return true;
+ 
+ 				if (check_expr(cstate, stmt_fori->step))
+ 					return true;
+ 
+ 				if (check_stmts(cstate, stmt_fori->body))
+ 					return true;
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORS:
+ 			{
+ 				PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt;
+ 				bool is_valid = false;
+ 				bool target_is_valid;
+ 
+ 				target_is_valid = check_row_or_rec(cstate, stmt_fors->row, stmt_fors->rec);
+ 				if (!target_is_valid && cstate->fatal_errors)
+ 					return true;
+ 
+ 				/* we need to set hidden variable type */
+ 				if (prepare_expr(cstate, stmt_fors->query, 0))
+ 				{
+ 					tupdesc = expr_get_desc(cstate,
+ 									stmt_fors->query,
+ 										false,		/* no element type */
+ 										false,		/* expand record */
+ 										false);		/* is expression */
+ 					if (tupdesc != NULL)
+ 					{
+ 						if (target_is_valid)
+ 							assign_tupdesc_row_or_rec(cstate, stmt_fors->row, stmt_fors->rec, tupdesc);
+ 
+ 						is_valid = true;
+ 						ReleaseTupleDesc(tupdesc);
+ 					}
+ 				}
+ 
+ 				if (!is_valid && cstate->fatal_errors)
+ 					return true;
+ 
+ 				if (check_stmts(cstate, stmt_fors->body))
+ 					return true;
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORC:
+ 			{
+ 				PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt;
+ 				PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_forc->curvar];
+ 				bool is_valid = false;
+ 				bool target_is_valid;
+ 
+ 				target_is_valid = check_row_or_rec(cstate, stmt_forc->row, stmt_forc->rec);
+ 				if (!target_is_valid && cstate->fatal_errors)
+ 					return true;
+ 
+ 				if (!prepare_expr(cstate, stmt_forc->argquery, 0) && cstate->fatal_errors)
+ 					return true;
+ 
+ 				if (var->cursor_explicit_expr != NULL)
+ 				{
+ 					if (prepare_expr(cstate, var->cursor_explicit_expr,
+ 								    var->cursor_options))
+ 					{
+ 						tupdesc = expr_get_desc(cstate,
+ 										var->cursor_explicit_expr,
+ 											false,		/* no element type */
+ 											false,		/* expand record */
+ 											false);		/* is expression */
+ 
+ 						if (target_is_valid)
+ 							assign_tupdesc_row_or_rec(cstate, stmt_forc->row, stmt_forc->rec, tupdesc);
+ 
+ 						is_valid = true;
+ 						ReleaseTupleDesc(tupdesc);
+ 					}
+ 					if (!is_valid && cstate->fatal_errors)
+ 						return true;
+ 				}
+ 
+ 				if (check_stmts(cstate, stmt_forc->body))
+ 					return true;
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNFORS:
+ 			{
+ 				PLpgSQL_stmt_dynfors * stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt;
+ 
+ 				if (stmt_dynfors->rec != NULL)
+ 				{
+ 					report_error(cstate,
+ 						cstate->estate.err_stmt,
+ 							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);
+ 
+ 					/*
+ 					 * don't continue in checking. Behave should be indeterministic.
+ 					 */
+ 					return true;
+ 				}
+ 
+ 				if (check_expr(cstate, stmt_dynfors->query))
+ 					return true;
+ 
+ 				foreach(l, stmt_dynfors->params)
+ 				{
+ 					if (check_expr(cstate, (PLpgSQL_expr *) lfirst(l)))
+ 						return true;
+ 				}
+ 
+ 				if (check_stmts(cstate, stmt_dynfors->body))
+ 					return true;
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FOREACH_A:
+ 			{
+ 				PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt;
+ 				bool is_valid = false;
+ 				bool target_is_valid;
+ 
+ 				target_is_valid = check_target(cstate, stmt_foreach_a->varno);
+ 				if (!target_is_valid && cstate->fatal_errors)
+ 					return true;
+ 
+ 				if (prepare_expr(cstate, stmt_foreach_a->expr, 0))
+ 				{
+ 					tupdesc = expr_get_desc(cstate,
+ 									stmt_foreach_a->expr,
+ 										true,		/* no element type */
+ 										false,		/* expand record */
+ 										true);		/* is expression */
+ 					if (tupdesc != NULL)
+ 					{
+ 						if (target_is_valid)
+ 							assign_tupdesc_dno(cstate, stmt_foreach_a->varno, tupdesc);
+ 						is_valid = true;
+ 						ReleaseTupleDesc(tupdesc);
+ 					}
+ 				}
+ 				if (!is_valid && cstate->fatal_errors)
+ 					return true;
+ 
+ 				if (check_stmts(cstate, stmt_foreach_a->body))
+ 					return true;
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_EXIT:
+ 			if (check_expr(cstate, ((PLpgSQL_stmt_exit *) stmt)->cond))
+ 				return true;
+ 			break;
+ 
+ 		case PLPGSQL_STMT_PERFORM:
+ 			if (prepare_expr(cstate, ((PLpgSQL_stmt_perform *) stmt)->expr, 0))
+ 				return true;
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN:
+ 			if (check_expr(cstate, ((PLpgSQL_stmt_return *) stmt)->expr))
+ 				return true;
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN_NEXT:
+ 			if (check_expr(cstate, ((PLpgSQL_stmt_return_next *) stmt)->expr))
+ 				return true;
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN_QUERY:
+ 			{
+ 				PLpgSQL_stmt_return_query *stmt_rq = (PLpgSQL_stmt_return_query *) stmt;
+ 
+ 				if (check_expr(cstate, stmt_rq->dynquery))
+ 					return true;
+ 
+ 				if (prepare_expr(cstate, stmt_rq->query, 0) && cstate->fatal_errors)
+ 					return true;
+ 
+ 				foreach(l, stmt_rq->params)
+ 				{
+ 					if (check_expr(cstate, (PLpgSQL_expr *) lfirst(l)))
+ 						return true;
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RAISE:
+ 			{
+ 				PLpgSQL_stmt_raise *stmt_raise = (PLpgSQL_stmt_raise *) stmt;
+ 				ListCell *current_param;
+ 				char *cp;
+ 
+ 				foreach(l, stmt_raise->params)
+ 				{
+ 					if (check_expr(cstate, (PLpgSQL_expr *) lfirst(l)))
+ 						return true;
+ 				}
+ 
+ 				foreach(l, stmt_raise->options)
+ 				{
+ 					PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(l);
+ 
+ 					if (check_expr(cstate, opt->expr))
+ 						return true;
+ 				}
+ 
+ 				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)
+ 							{
+ 								report_error(cstate,
+ 										cstate->estate.err_stmt,
+ 										ERRCODE_SYNTAX_ERROR,
+ 										"too few parameters specified for RAISE",
+ 										NULL, NULL,
+ 											"error",
+ 												0, NULL);
+ 								if (cstate->fatal_errors)
+ 									return true;
+ 								break;
+ 							}
+ 
+ 							current_param = lnext(current_param);
+ 						}
+ 					}
+ 				}
+ 
+ 				if (current_param != NULL)
+ 				{
+ 					report_error(cstate,
+ 							cstate->estate.err_stmt,
+ 								ERRCODE_SYNTAX_ERROR,
+ 										"too many parameters specified for RAISE",
+ 										NULL, NULL,
+ 											"error",
+ 												0, NULL);
+ 					if (cstate->fatal_errors)
+ 						return true;
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_EXECSQL:
+ 			{
+ 				PLpgSQL_stmt_execsql *stmt_execsql = (PLpgSQL_stmt_execsql *) stmt;
+ 
+ 				if (stmt_execsql->into)
+ 				{
+ 					bool is_valid = false;
+ 					bool target_is_valid;
+ 
+ 					target_is_valid = check_row_or_rec(cstate, stmt_execsql->row, stmt_execsql->rec);
+ 					if (!target_is_valid && cstate->fatal_errors)
+ 						return true;
+ 
+ 					if (prepare_expr(cstate, stmt_execsql->sqlstmt, 0))
+ 					{
+ 						tupdesc = expr_get_desc(cstate,
+ 									stmt_execsql->sqlstmt,
+ 												false,		/* no element type */
+ 												false,		/* expand record */
+ 												false);		/* is expression */
+ 						if (tupdesc != NULL)
+ 						{
+ 							if (target_is_valid)
+ 								assign_tupdesc_row_or_rec(cstate, stmt_execsql->row, stmt_execsql->rec, tupdesc);
+ 							ReleaseTupleDesc(tupdesc);
+ 							is_valid = true;
+ 						}
+ 					}
+ 
+ 					if (!is_valid && cstate->fatal_errors)
+ 						return true;
+ 				}
+ 				else
+ 				{
+ 					/* only statement */
+ 					if (!prepare_expr(cstate, stmt_execsql->sqlstmt, 0) && cstate->fatal_errors)
+ 						return true;
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNEXECUTE:
+ 			{
+ 				PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt;
+ 
+ 				if (check_expr(cstate, stmt_dynexecute->query))
+ 					return true;
+ 
+ 				foreach(l, stmt_dynexecute->params)
+ 				{
+ 					if (check_expr(cstate, (PLpgSQL_expr *) lfirst(l)))
+ 						return true;
+ 				}
+ 
+ 				if (stmt_dynexecute->into)
+ 				{
+ 					if (!check_row_or_rec(cstate, stmt_dynexecute->row, stmt_dynexecute->rec)
+ 							&& cstate->fatal_errors)
+ 						return true;
+ 
+ 					if (stmt_dynexecute->rec != NULL)
+ 					{
+ 						report_error(cstate,
+ 							cstate->estate.err_stmt,
+ 								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);
+ 
+ 						/*
+ 						 * don't continue in checking. Behave should be indeterministic.
+ 						 */
+ 						return true;
+ 					}
+ 				}
+ 			}
+ 			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)
+ 				{
+ 					if (!prepare_expr(cstate, var->cursor_explicit_expr,
+ 								   var->cursor_options) && cstate->fatal_errors)
+ 						return true;
+ 				}
+ 
+ 				if (!prepare_expr(cstate, stmt_open->query, 0) && cstate->fatal_errors)
+ 					return true;
+ 
+ 				if (!prepare_expr(cstate, stmt_open->argquery, 0) && cstate->fatal_errors)
+ 					return true;
+ 
+ 				if (check_expr(cstate, stmt_open->dynquery))
+ 					return true;
+ 
+ 				foreach(l, stmt_open->params)
+ 				{
+ 					if (check_expr(cstate, (PLpgSQL_expr *) lfirst(l)))
+ 						return true;
+ 				}
+ 			}
+ 			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);
+ 
+ 					if (!check_target(cstate, diag_item->target) && cstate->fatal_errors)
+ 						return true;
+ 				}
+ 			}
+ 			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]);
+ 				bool is_valid = false;
+ 				bool target_is_valid;
+ 
+ 				target_is_valid = check_row_or_rec(cstate, stmt_fetch->row, stmt_fetch->rec);
+ 				if (!target_is_valid && cstate->fatal_errors)
+ 					return true;
+ 
+ 				if (var != NULL && var->cursor_explicit_expr != NULL)
+ 				{
+ 					if (prepare_expr(cstate, var->cursor_explicit_expr,
+ 									    var->cursor_options))
+ 					{
+ 						tupdesc = expr_get_desc(cstate,
+ 									    var->cursor_explicit_expr,
+ 												false,		/* no element type */
+ 												false,		/* expand record */
+ 												false);		/* is expression */
+ 						if (tupdesc)
+ 						{
+ 							if (target_is_valid)
+ 								assign_tupdesc_row_or_rec(cstate, stmt_fetch->row, stmt_fetch->rec, tupdesc);
+ 							ReleaseTupleDesc(tupdesc);
+ 							is_valid = true;
+ 						}
+ 					}
+ 
+ 					if (!is_valid && cstate->fatal_errors)
+ 						return true;
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_CLOSE:
+ 			break;
+ 
+ 		default:
+ 			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
+ 			return false; /* be compiler quite */
+ 	}
+ 
+ 	return false;
+ }
+ 
+ /*
+  * Initialize variable to NULL
+  */
+ static void
+ var_init_to_null(PLpgSQL_checkstate *cstate, int varno)
+ {
+ 	PLpgSQL_var *var = (PLpgSQL_var *) cstate->estate.datums[varno];
+ 	var->value = (Datum) 0;
+ 	var->isnull = true;
+ 	var->freeval = false;
+ }
*** a/src/pl/plpgsql/src/pl_handler.c
--- b/src/pl/plpgsql/src/pl_handler.c
***************
*** 15,24 ****
--- 15,26 ----
  
  #include "plpgsql.h"
  
+ #include "catalog/pg_language.h"
  #include "catalog/pg_proc.h"
  #include "catalog/pg_type.h"
  #include "funcapi.h"
  #include "miscadmin.h"
+ #include "utils/array.h"
  #include "utils/builtins.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
***************
*** 312,314 **** plpgsql_validator(PG_FUNCTION_ARGS)
--- 314,582 ----
  
  	PG_RETURN_VOID();
  }
+ 
+ /* ----------
+  * plpgsql_checker
+  *
+  * This function attempts to check a embeded SQL inside a PL/pgSQL function at
+  * CHECK FUNCTION time. It should to have one or two parameters. Second
+  * parameter is a relation (used when function is trigger).
+  * ----------
+  */
+ PG_FUNCTION_INFO_V1(plpgsql_checker);
+ 
+ Datum
+ plpgsql_checker(PG_FUNCTION_ARGS)
+ {
+ 	Oid			funcoid = PG_GETARG_OID(0);
+ 	Oid			relid = PG_GETARG_OID(1);
+ 	ArrayType		*options = PG_GETARG_ARRAYTYPE_P_COPY(2);
+ 	bool			fatal_errors = PG_GETARG_BOOL(3);
+ 	HeapTuple	tuple;
+ 	FunctionCallInfoData fake_fcinfo;
+ 	FmgrInfo	flinfo;
+ 	TriggerData trigdata;
+ 	int			rc;
+ 	PLpgSQL_function *function;
+ 	PLpgSQL_execstate *cur_estate;
+ 	Form_pg_proc proc;
+ 	char		functyptype;
+ 	bool	   istrigger = false;
+ 	HeapTuple		languageTuple;
+ 	Form_pg_language	languageStruct;
+ 	TupleDesc       tupdesc;
+ 	Tuplestorestate *tupstore;
+ 	MemoryContext per_query_ctx;
+ 	MemoryContext oldcontext;
+ 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ 	ArrayType	*set_items = NULL;
+ 	int		save_nestlevel = 0;
+ 	Datum		datum;
+ 	bool		isnull;
+ 	char			*funcname;
+ 
+ 	/* 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")));
+ 
+ 	/* 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(rsinfo->allowedModes & SFRM_Materialize_Random,
+ 								  false, work_mem);
+ 	MemoryContextSwitchTo(oldcontext);
+ 
+ 	/*
+ 	 * Options should be two dimensional text array. Actually just
+ 	 * fatal_errors option is supported and this option modify
+ 	 * a format of context string (add a function name).
+ 	 */
+ 	if (ARR_NDIM(options) > 0)
+ 	{
+ 		int	*dims;
+ 		int	*lbs;
+ 		ArrayIterator array_iterator;
+ 		bool		isnull;
+ 		Datum		value;
+ 
+ 		if (ARR_NDIM(options) != 2)
+ 			elog(ERROR, "only two diminsional array is allowed as options");
+ 
+ 		if (ARR_ELEMTYPE(options) != TEXTOID)
+ 			elog(ERROR, "expected text array");
+ 
+ 		dims = ARR_DIMS(options);
+ 		lbs = ARR_LBOUND(options);
+ 		if (dims[1] - lbs[1] + 1 != 2)
+ 			elog(ERROR, "second dimension must be equal to two");
+ 
+ 		/* iterate over array of options */
+ 		array_iterator = array_create_iterator(options, 0);
+ 
+ 		while(array_iterate(array_iterator, &value, &isnull))
+ 		{
+ 			char *option_name;
+ 		
+ 			if (isnull)
+ 				elog(ERROR, "option name is NULL");
+ 
+ 			option_name = TextDatumGetCString(value);
+ 
+ 			/* no option supported yet */
+ 			elog(ERROR, "unknown option \"%s\"", option_name);
+ 		}
+ 
+ 		/* Release temporary memory */
+ 		array_free_iterator(array_iterator);
+ 		pfree(options);
+ 	}
+ 
+ 	tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
+ 	if (!HeapTupleIsValid(tuple))
+ 		elog(ERROR, "cache lookup failed for function %u", funcoid);
+ 	proc = (Form_pg_proc) GETSTRUCT(tuple);
+ 
+ 	funcname = format_procedure(funcoid);
+ 
+ 	/* 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)
+ 		elog(ERROR, "\"%s\" is not 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))
+ 		{
+ 			istrigger = true;
+ 			if (!OidIsValid(relid))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("PL/pgSQL trigger functions cannot be checked directly"),
+ 						 errhint("use CHECK TRIGGER statement instead")));
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Must to set a per-function configuration parameter here, because we
+ 	 * would to allow direct usage of checker function.
+ 	 */
+ 	datum = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_proconfig, &isnull);
+ 	if (!isnull)
+ 	{
+ 		/* Set per-function configuration parameters */
+ 		set_items = DatumGetArrayTypeP(datum);
+ 
+ 		if (set_items)			/* Need a new GUC nesting level */
+ 		{
+ 			save_nestlevel = NewGUCNestLevel();
+ 			ProcessGUCArray(set_items,
+ 						(superuser() ? PGC_SUSET : PGC_USERSET),
+ 								PGC_S_SESSION,
+ 								GUC_ACTION_SAVE);
+ 		}
+ 		else
+ 			save_nestlevel = 0; /* keep compiler quiet */
+ 	}
+ 
+ 	/*
+ 	 * Connect to SPI manager
+ 	 */
+ 	if ((rc = SPI_connect()) != SPI_OK_CONNECT)
+ 			elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
+ 
+ 	/*
+ 	 * Set up a fake fcinfo with just enough info to satisfy
+ 	 * plpgsql_compile().
+ 	 *
+ 	 * there should be a different real argtypes for polymorphic params
+ 	 */
+ 	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;
+ 		trigdata.tg_relation = relation_open(relid, AccessShareLock);
+ 		fake_fcinfo.context = (Node *) &trigdata;
+ 	}
+ 
+ 	/* Get a compiled function */
+ 	function = plpgsql_compile(&fake_fcinfo, false);
+ 
+ 	/* 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 */
+ 	PG_TRY();
+ 	{
+ 		if (!istrigger)
+ 			plpgsql_check_function(function, &fake_fcinfo,
+ 									tupdesc,
+ 									tupstore,
+ 									fatal_errors);
+ 		else
+ 			plpgsql_check_trigger(function, &trigdata,
+ 									tupdesc,
+ 									tupstore,
+ 									fatal_errors);
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		if (istrigger)
+ 			relation_close(trigdata.tg_relation, AccessShareLock);
+ 
+ 		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);
+ 
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	if (istrigger)
+ 		relation_close(trigdata.tg_relation, AccessShareLock);
+ 
+ 	function->cur_estate = cur_estate;
+ 	function->use_count--;
+ 
+ 	/* reload back a GUC */
+ 	if (set_items)
+ 		AtEOXact_GUC(true, save_nestlevel);
+ 
+ 	/*
+ 	 * 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);
+ 
+ 	/*
+ 	 * Disconnect from SPI manager
+ 	 */
+ 	if ((rc = SPI_finish()) != SPI_OK_FINISH)
+ 			elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
+ 
+ 	ReleaseSysCache(tuple);
+ 
+ 	/* clean up and return the tuplestore */
+ 	tuplestore_donestoring(tupstore);
+ 
+ 	rsinfo->returnMode = SFRM_Materialize;
+ 	rsinfo->setResult = tupstore;
+ 	rsinfo->setDesc = tupdesc;
+ 
+ 	return (Datum) 0;
+ }
*** a/src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql
--- b/src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql
***************
*** 5,7 **** ALTER EXTENSION plpgsql ADD PROCEDURAL LANGUAGE plpgsql;
--- 5,8 ----
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_call_handler();
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_inline_handler(internal);
  ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_validator(oid);
+ ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_checker(oid, regclass, text[]);
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
***************
*** 767,772 **** typedef struct PLpgSQL_execstate
--- 767,779 ----
  	void	   *plugin_info;	/* reserved for use by optional plugin */
  } PLpgSQL_execstate;
  
+ typedef struct PLpgSQL_checkstate
+ {
+ 	PLpgSQL_execstate   estate;			/* check state is estate extension */
+ 	TupleDesc		tupdesc;
+ 	Tuplestorestate		*tupstore;
+ 	bool			fatal_errors;		/* stop on first error */
+ } PLpgSQL_checkstate;
  
  /*
   * A PLpgSQL_plugin structure represents an instrumentation plugin.
***************
*** 866,871 **** extern MemoryContext compile_tmp_cxt;
--- 873,880 ----
  
  extern PLpgSQL_plugin **plugin_ptr;
  
+ extern bool plpgsql_stop_on_first_error;
+ 
  /**********************************************************************
   * Function declarations
   **********************************************************************/
***************
*** 902,907 **** extern PLpgSQL_condition *plpgsql_parse_err_condition(char *condname);
--- 911,917 ----
  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,916 **** extern void _PG_init(void);
--- 921,927 ----
  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_checker(PG_FUNCTION_ARGS);
  
  /* ----------
   * Functions in pl_exec.c
***************
*** 928,933 **** extern Oid exec_get_datum_type(PLpgSQL_execstate *estate,
--- 939,954 ----
  extern void exec_get_datum_type_info(PLpgSQL_execstate *estate,
  						 PLpgSQL_datum *datum,
  						 Oid *typeid, int32 *typmod, Oid *collation);
+ extern void plpgsql_check_function(PLpgSQL_function *func,
+ 					 FunctionCallInfo fcinfo,
+ 					 TupleDesc tupdesc,
+ 					 Tuplestorestate *tupstore,
+ 					 bool fatal_errors);
+ extern void plpgsql_check_trigger(PLpgSQL_function *func,
+ 					 TriggerData *trigdata,
+ 					 TupleDesc tupdesc,
+ 					 Tuplestorestate *tupstore,
+ 					 bool fatal_errors);
  
  /* ----------
   * Functions for namespace handling in pl_funcs.c
*** a/src/test/regress/expected/plpgsql.out
--- b/src/test/regress/expected/plpgsql.out
***************
*** 302,307 **** end;
--- 302,314 ----
  ' 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 count(*) from plpgsql_checker('tg_hslot_biu()', 'HSlot', '{}', true);
+  count 
+ -------
+      0
+ (1 row)
+ 
  -- ************************************************************
  -- * BEFORE DELETE on HSlot
  -- *	- prevent from manual manipulation
***************
*** 635,640 **** begin
--- 642,654 ----
      raise exception ''illegal backlink beginning with %'', mytype;
  end;
  ' language plpgsql;
+ -- check function should not fail
+ select count(*) from plpgsql_checker('tg_backlink_set(bpchar, bpchar)', 0, '{}', true);
+  count 
+ -------
+      0
+ (1 row)
+ 
  -- ************************************************************
  -- * Support function to clear out the backlink field if
  -- * it still points to specific slot
***************
*** 2931,2936 **** NOTICE:  4 bb cc
--- 2945,2983 ----
   
  (1 row)
  
+ -- check function should not fail
+ select count(*) from plpgsql_checker('for_vect()', 0, '{}', true);
+  count 
+ -------
+      0
+ (1 row)
+ 
+ -- 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
***************
*** 3412,3417 **** begin
--- 3459,3471 ----
    return;
  end;
  $$ language plpgsql;
+ -- check function should not fail
+ select count(*) from plpgsql_checker('forc01()', 0, '{}', true);
+  count 
+ -------
+      0
+ (1 row)
+ 
  select forc01();
  NOTICE:  5 from c
  NOTICE:  6 from c
***************
*** 3845,3850 **** begin
--- 3899,3911 ----
    end case;
  end;
  $$ language plpgsql immutable;
+ -- check function should not fail
+ select count(*) from plpgsql_checker('case_test(bigint)', 0, '{}', true);
+  count 
+ -------
+      0
+ (1 row)
+ 
  select case_test(1);
   case_test 
  -----------
***************
*** 4700,4702 **** ERROR:  value for domain orderedarray violates check constraint "sorted"
--- 4761,5237 ----
  CONTEXT:  PL/pgSQL function "testoa" 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('session_user()', 0, '{}', true);
+ ERROR:  ""session_user"()" is not 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+  lineno |   statement   | sqlstate |                  message                   | detail | hint | level | position |        query         
+ --------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------
+       4 | SQL statement | 42703    | column "c" of relation "t1" does not exist |        |      | error |       15 | update t1 set c = 30
+ (1 row)
+ 
+ select lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', false);
+  lineno |   statement   | sqlstate |                  message                   | detail | hint | level | position |        query         
+ --------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------
+       4 | SQL statement | 42703    | column "c" of relation "t1" does not exist |        |      | error |       15 | update t1 set c = 30
+       7 | RAISE         | 42P01    | missing FROM-clause entry for table "r"    |        |      | error |        8 | SELECT r.c
+       7 | RAISE         | 42601    | too few parameters specified for RAISE     |        |      | error |          | 
+ (3 rows)
+ 
+ check function f1();
+                              CHECK FUNCTION                             
+ ------------------------------------------------------------------------
+  In function: 'f1()'
+  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
+ (8 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+  lineno | statement | sqlstate |           message           | detail | hint | level | position | query 
+ --------+-----------+----------+-----------------------------+--------+------+-------+----------+-------
+       6 | RAISE     | 42703    | record "r" has no field "c" |        |      | error |          | 
+ (1 row)
+ 
+ check function f1();
+                  CHECK FUNCTION                  
+ -------------------------------------------------
+  In function: 'f1()'
+  error:42703:6:RAISE:record "r" has no field "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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+  lineno | statement | sqlstate |           message           | detail | hint | level | position | query 
+ --------+-----------+----------+-----------------------------+--------+------+-------+----------+-------
+       6 | RAISE     | 42703    | record "r" has no field "c" |        |      | error |          | 
+ (1 row)
+ 
+ check function f1();
+                  CHECK FUNCTION                  
+ -------------------------------------------------
+  In function: 'f1()'
+  error:42703:6:RAISE:record "r" has no field "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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+  lineno | statement  | sqlstate |           message           | detail | hint | level | position | query 
+ --------+------------+----------+-----------------------------+--------+------+-------+----------+-------
+       6 | assignment | 42703    | record "r" has no field "c" |        |      | error |          | 
+ (1 row)
+ 
+ check function f1();
+                     CHECK FUNCTION                    
+ ------------------------------------------------------
+  In function: 'f1()'
+  error:42703:6:assignment:record "r" has no field "c"
+ (2 rows)
+ 
+ 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+  lineno | statement  | sqlstate |          message          | detail | hint | level | position |    query     
+ --------+------------+----------+---------------------------+--------+------+-------+----------+--------------
+       5 | assignment | 42703    | column "a" does not exist |        |      | error |        8 | SELECT a + b
+ (1 row)
+ 
+ check function f1();
+                    CHECK FUNCTION                   
+ ----------------------------------------------------
+  In function: 'f1()'
+  error:42703:5:assignment:column "a" does not exist
+  query:SELECT a + b
+               ^
+ (4 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+  lineno | statement | sqlstate |                 message                 | detail | hint | level | position | query 
+ --------+-----------+----------+-----------------------------------------+--------+------+-------+----------+-------
+       4 | RAISE     | 42601    | too many parameters specified for RAISE |        |      | error |          | 
+ (1 row)
+ 
+ check function f1();
+                        CHECK FUNCTION                        
+ -------------------------------------------------------------
+  In function: 'f1()'
+  error:42601:4:RAISE:too many parameters specified for RAISE
+ (2 rows)
+ 
+ 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+  lineno | statement | sqlstate |                message                 | detail | hint | level | position | query 
+ --------+-----------+----------+----------------------------------------+--------+------+-------+----------+-------
+       4 | RAISE     | 42601    | too few parameters specified for RAISE |        |      | error |          | 
+ (1 row)
+ 
+ check function f1();
+                        CHECK FUNCTION                       
+ ------------------------------------------------------------
+  In function: 'f1()'
+  error:42601:4:RAISE:too few parameters specified for RAISE
+ (2 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[c+10] := 20;
+   end if;
+ end;
+ $$ language plpgsql;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ select lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+  lineno | statement  | sqlstate |          message          | detail | hint | level | position |    query    
+ --------+------------+----------+---------------------------+--------+------+-------+----------+-------------
+       5 | assignment | 42703    | column "c" does not exist |        |      | error |        8 | SELECT c+10
+ (1 row)
+ 
+ check function f1();
+                    CHECK FUNCTION                   
+ ----------------------------------------------------
+  In function: 'f1()'
+  error:42703:5:assignment:column "c" does not exist
+  query:SELECT c+10
+               ^
+ (4 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+  lineno | statement  | sqlstate |              message               | detail | hint | level | position | query 
+ --------+------------+----------+------------------------------------+--------+------+-------+----------+-------
+       5 | assignment | 42804    | subscripted object is not an array |        |      | error |          | 
+ (1 row)
+ 
+ check function f1();
+                        CHECK FUNCTION                        
+ -------------------------------------------------------------
+  In function: 'f1()'
+  error:42804:5:assignment:subscripted object is not an array
+ (2 rows)
+ 
+ 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+  lineno |    statement    | sqlstate |                 message                 | detail | hint | level | position | query 
+ --------+-----------------+----------+-----------------------------------------+--------+------+-------+----------+-------
+       7 | GET DIAGNOSTICS | 42703    | record "_exception" has no field "hint" |        |      | error |          | 
+ (1 row)
+ 
+ check function f1();
+                             CHECK FUNCTION                             
+ -----------------------------------------------------------------------
+  In function: 'f1()'
+  error:42703:7:GET DIAGNOSTICS:record "_exception" has no field "hint"
+ (2 rows)
+ 
+ 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1_trg()', 't1', '{}', true);
+  lineno | statement | sqlstate |            message            | detail | hint | level | position | query 
+ --------+-----------+----------+-------------------------------+--------+------+-------+----------+-------
+       5 | RAISE     | 42703    | record "new" has no field "c" |        |      | error |          | 
+ (1 row)
+ 
+ check trigger t1_f1 on t1;
+                    CHECK TRIGGER                   
+ ---------------------------------------------------
+  In function: 'f1_trg()'
+  error:42703:5:RAISE:record "new" has no field "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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1_trg()', 't1', '{}', true);
+  lineno | statement  | sqlstate |            message            | detail | hint | level | position | query 
+ --------+------------+----------+-------------------------------+--------+------+-------+----------+-------
+       5 | assignment | 42703    | record "new" has no field "c" |        |      | error |          | 
+ (1 row)
+ 
+ check trigger t1_f1 on t1;
+                      CHECK TRIGGER                      
+ --------------------------------------------------------
+  In function: 'f1_trg()'
+  error:42703:5:assignment:record "new" has no field "c"
+ (2 rows)
+ 
+ -- 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 count(*) from plpgsql_checker('f1_trg()', 't1', '{}', true);
+  count 
+ -------
+      0
+ (1 row)
+ 
+ -- ok
+ insert into t1 values(6,30);
+ select * from t1;
+  a  | b  
+ ----+----
+   6 | 30
+   6 | 30
+  16 | 40
+ (3 rows)
+ 
+ drop table t1;
+ drop type _exception_type;
+ drop function f1_trg();
*** a/src/test/regress/sql/plpgsql.sql
--- b/src/test/regress/sql/plpgsql.sql
***************
*** 366,371 **** end;
--- 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 count(*) from plpgsql_checker('tg_hslot_biu()', 'HSlot', '{}', true);
  
  -- ************************************************************
  -- * BEFORE DELETE on HSlot
***************
*** 747,752 **** begin
--- 749,757 ----
  end;
  ' language plpgsql;
  
+ -- check function should not fail
+ select count(*) from plpgsql_checker('tg_backlink_set(bpchar, bpchar)', 0, '{}', true);
+ 
  
  -- ************************************************************
  -- * Support function to clear out the backlink field if
***************
*** 2443,2448 **** $proc$ language plpgsql;
--- 2448,2460 ----
  
  select for_vect();
  
+ -- check function should not fail
+ select count(*) from plpgsql_checker('for_vect()', 0, '{}', true);
+ 
+ -- 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
***************
*** 2822,2827 **** begin
--- 2834,2842 ----
  end;
  $$ language plpgsql;
  
+ -- check function should not fail
+ select count(*) from plpgsql_checker('forc01()', 0, '{}', true);
+ 
  select forc01();
  
  -- try updating the cursor's current row
***************
*** 3156,3161 **** begin
--- 3171,3180 ----
  end;
  $$ language plpgsql immutable;
  
+ -- check function should not fail
+ select count(*) from plpgsql_checker('case_test(bigint)', 0, '{}', true);
+ 
+ 
  select case_test(1);
  select case_test(2);
  select case_test(3);
***************
*** 3708,3710 **** select testoa(1,2,1); -- fail at update
--- 3727,4013 ----
  
  drop function arrayassign1();
  drop function testoa(x1 int, x2 int, x3 int);
+ 
+ --
+ -- check function statement tests
+ --
+ 
+ --should fail - is not plpgsql
+ select lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('session_user()', 0, '{}', true);
+ 
+ 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+ select lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', false);
+ 
+ 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+ 
+ 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+ 
+ 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+ 
+ 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+ 
+ 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+ 
+ 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+ 
+ 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+ 
+ 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+ 
+ 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1()', 0, '{}', true);
+ 
+ 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1_trg()', 't1', '{}', true);
+ 
+ check trigger t1_f1 on 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 lineno, statement, sqlstate, message, detail, hint, level, "position", query from plpgsql_checker('f1_trg()', 't1', '{}', true);
+ 
+ check trigger t1_f1 on 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 count(*) from plpgsql_checker('f1_trg()', 't1', '{}', true);
+ 
+ -- ok
+ insert into t1 values(6,30);
+ 
+ select * from t1;
+ 
+ drop table t1;
+ drop type _exception_type;
+ 
+ drop function f1_trg();
#62Alvaro Herrera
alvherre@commandprompt.com
In reply to: Petr Jelínek (#61)
Re: review: CHECK FUNCTION statement

I have a few comments about this patch:

I didn't like the fact that the checker calling infrastructure uses
SPI instead of just a FunctionCallN to call the checker function. I
think this should be easily avoidable.

Second, I see that functioncmds.c gets a lot into trigger internals just
to be able to figure out the function starting from a trigger name. I
think it'd be saner to have a new function in trigger.c that returns the
required function OID.

I think CheckFunction would be clearer if the code to check multiple
objects is split out into a separate subroutine.

After CheckFunction there is a leftover function comment without any
following function. There are other spurious hunks that add or remove
single lines too (once in an otherwise untouched file).

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#63Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#62)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Hello

Dne 28. února 2012 17:48 Alvaro Herrera <alvherre@commandprompt.com> napsal(a):

I have a few comments about this patch:

I didn't like the fact that the checker calling infrastructure uses
SPI instead of just a FunctionCallN to call the checker function.  I
think this should be easily avoidable.

It is not possible - or it has not simple solution (I don't how to do
it). PLpgSQL_checker is SRF function. SPI is used for processing
returned resultset. I looked to pg source code, and I didn't find any
other pattern than using SPI for SRF function call. It is probably
possible, but it means some code duplication too. I invite any ideas.

Second, I see that functioncmds.c gets a lot into trigger internals just
to be able to figure out the function starting from a trigger name.  I
think it'd be saner to have a new function in trigger.c that returns the
required function OID.

done

I think CheckFunction would be clearer if the code to check multiple
objects is split out into a separate subroutine.

done

After CheckFunction there is a leftover function comment without any
following function.  There are other spurious hunks that add or remove
single lines too (once in an otherwise untouched file).

fixed

I refreshed patch for current git repository.

Regards

Pavel

Show quoted text

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Attachments:

check_function-2012-02-28-2.diff.gzapplication/x-gzip; name=check_function-2012-02-28-2.diff.gzDownload
#64Alvaro Herrera
alvherre@commandprompt.com
In reply to: Pavel Stehule (#63)
Re: review: CHECK FUNCTION statement

Excerpts from Pavel Stehule's message of mar feb 28 16:30:58 -0300 2012:

I refreshed patch for current git repository.

Thanks, I'll have a look.

Oh, another thing -- you shouldn't patch the 1.0 version of the plpgsql
extension. Rather I think you should produce a 1.1 version.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#65Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#64)
1 attachment(s)
Re: review: CHECK FUNCTION statement

2012/2/28 Alvaro Herrera <alvherre@commandprompt.com>:

Excerpts from Pavel Stehule's message of mar feb 28 16:30:58 -0300 2012:

I refreshed patch for current git repository.

Thanks, I'll have a look.

Oh, another thing -- you shouldn't patch the 1.0 version of the plpgsql
extension.  Rather I think you should produce a 1.1 version.

there is patch with updated tag. I am not sure if there are some
changes in pg_upgrade are necessary??

Regards and thank you

Pavel

Show quoted text

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Attachments:

check_function-2012-02-28-3.diff.gzapplication/x-gzip; name=check_function-2012-02-28-3.diff.gzDownload
#66Alvaro Herrera
alvherre@commandprompt.com
In reply to: Alvaro Herrera (#64)
Re: review: CHECK FUNCTION statement

In gram.y we have a new check_option_list nonterminal. This is mostly
identical to explain_option_list, except that the option args do not
take a NumericOnly (only opt_boolean_or_string and empty). I wonder if
it's really worthwhile having a bunch of separate productions for this;
how about we just use the existing explain_option_list instead and get
rid of those extra productions?

elog() is used in many user-facing messages (errors and notices). Full
ereport() calls should be used there, so that messages are marked for
translations and so on.

Does the patched pg_dump work with older servers?

I don't like CheckFunction being declared in defrem.h. It seems
completely out of place there. I don't see any better place though, so
I'm thinking maybe we should have a new header file for it (say
commands/functions.h; but we already have executor/functions.h so
perhaps it's better to find another name). This addition means that
there's a distressingly large number of .c files that are now getting
dest.h, which was previously pretty confined.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#67Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#66)
Re: review: CHECK FUNCTION statement

2012/2/28 Alvaro Herrera <alvherre@commandprompt.com>:

In gram.y we have a new check_option_list nonterminal.  This is mostly
identical to explain_option_list, except that the option args do not
take a NumericOnly (only opt_boolean_or_string and empty).  I wonder if
it's really worthwhile having a bunch of separate productions for this;
how about we just use the existing explain_option_list instead and get
rid of those extra productions?

I have to look on it

elog() is used in many user-facing messages (errors and notices).  Full
ereport() calls should be used there, so that messages are marked for
translations and so on.

yes, I'll fix it

Does the patched pg_dump work with older servers?

yes, It should to do

I don't like CheckFunction being declared in defrem.h.  It seems
completely out of place there.  I don't see any better place though, so
I'm thinking maybe we should have a new header file for it (say
commands/functions.h; but we already have executor/functions.h so
perhaps it's better to find another name).  This addition means that
there's a distressingly large number of .c files that are now getting
dest.h, which was previously pretty confined.

you have much better knowledge about headers then me, so, please,
propose solution.

Regards

Pavel

Show quoted text

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#68Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#66)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Hello

2012/2/28 Alvaro Herrera <alvherre@commandprompt.com>:

In gram.y we have a new check_option_list nonterminal.  This is mostly
identical to explain_option_list, except that the option args do not
take a NumericOnly (only opt_boolean_or_string and empty).  I wonder if
it's really worthwhile having a bunch of separate productions for this;
how about we just use the existing explain_option_list instead and get
rid of those extra productions?

elog() is used in many user-facing messages (errors and notices).  Full
ereport() calls should be used there, so that messages are marked for
translations and so on.

I replaced elog by ereport for all not internal errors

Does the patched pg_dump work with older servers?

it should to do

I don't like CheckFunction being declared in defrem.h.  It seems
completely out of place there.  I don't see any better place though, so
I'm thinking maybe we should have a new header file for it (say
commands/functions.h; but we already have executor/functions.h so
perhaps it's better to find another name).  This addition means that
there's a distressingly large number of .c files that are now getting
dest.h, which was previously pretty confined.

please, fix it like you wish

Regards

Pavel

Show quoted text

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Attachments:

check_function-2012-02-29-1.diff.gzapplication/x-gzip; name=check_function-2012-02-29-1.diff.gzDownload
�$&NOcheck_function-2012-02-29-1.diff�<�{G�?�����$�@�e����;\�E8{���o�i`���$������~������?�������zu�]o:e{{3/fv�
�����l��;��p&���&:����Y�t���h�l>���������O������6����������������u�v��}���~����V�_l1�HO���q��8q�r���}���g��<:o���[8?6^/�E���-�U�{1�l~��}UW�[��(t����^���l���j;o���&�h��O��h�	�)�x�p�l��Q�%��r�t8�,��1���.��
�s'\,�����������tG���y��Ak�{4��}�b�4����|�
6�T�p�L<	]����o���� db��@ �r���5�9(���G��n;~��s�H�
�_��>/��nF=:xN:ztpzl��\G����|%���x��@�P$��a1f��})��%�q%�7��h��/[I�����1�#Pc5��m��/N0�le��U���n��A�}��l��x`�NO���t�N�ON7X������@}j�����1��{�Q�]uo�u����6�V�����l������l�����,� Y5n3��N=���8dN��������o�!"4Q�]�2����`k����!�����bp���Kl�I��y�<��Q�h�n����? 
nP�EH�0���^#X�&L.�\�k����������=��C�zI#�@;�CR�^��k��G�,fp�)z%"�V�����;�DnK��8�"�A���#�������=)>�������[���1�'iR���d���
���D�b�gp���r��G�����j?��<�I�P�O���(�I�ErPly���>�U��e�X���z��8��0�%�z����.g?�����Y��D-��,7��%Q�-iK���[;�?��6�����I"~��h��(RN��(�
`�+�����^�!F9ILc��L�U�>[��_��CG�R����}Y
 �3�F���^�"���od�Y��&y�5��%pf�dg������)m����Fmb�S<��'>g�s�!�Ox"&����b=�|M�P�h�f�����g��1���_A�<�&id�ZL@Z8�6��`�����;�1l>�|n��{��8�7��J�R�,��0��+�"����R�����1�^X��`�4���� �{��>��Q���w��9�@`�2�XC+cR� 
^*���C8�M�s%������K�*Y��{T���]���z�}c&
g{s��������/����s�J�������{Oo�
�LX���(X�>��}���}��3���
�i�g�2e� �m��mE�^D������C�Xj��q�|
�����u��b�����5�}������-h�T30�#�(fh�<FIKD���^��J�EJ���Aq�L|6����%ZAV}�c�d X"�
Vb^��^[��n(��C H�c�A�^����]z���e����z�5Pb$lRf��	1n�OH���&N��x�(c����>��}�C)������:����F���IN1N�x�����`��!�S��g�D.F<^EA1A�D@��\X)h	}�~�L
'�&H}JR���4/�U�/D+�u�E����r�	��yJ7���XsAF�:-�+KZ��z���N�L���l4�R�z �����r������y��������o�	�(J��"U����o�R8b�n�'%��j�Q!��{f�@�/4+��a��`����`�������������v�����k|}DC�"=����3V2Lv�L�Haec�K��5#h��J�A]c�@CG���
U��H&u/C��y2���a~(x����ll
C]nu�1�UV&@c��*5�N)������}�lNN�'���T��DU�����aZ�!��Ox�	E<�8����yE���/��T�+��a��������x3���.�
;W�hR�sm}���1����R�@}�J�l�`4|��|��(���3���
������i4@Pp�<H���Hp]?w�����u�,�}�$����%� *ed�����_F'�>������0
&o?������'S.Sm��5�#c![��� �6]�Uaj�U�X,���
bMr&�����v�R%=QB��W�"�|����@�14�w^��A��K�R�����A�MeH��7��f�k�T~�xO��R�(����N��O�'�,��*F;��!������� rI0�+�gB��k1�V����#�����H�T`�2?,��LyA����@��I��D��m%b(����wD%DXB��C�Y���)1��C�/0�r9�I	c����R�.81��%�l�����
��R���X���^T���JF�	�F��
G�)jG���G��*HlC)���l5���~j���Kv�����5]���hFE-
�H5�GUd}�b�U�����E�S�0&ZU��9:EWum��*HMf�){Bz<��*I��M��#��`e�b�f$1�d��P���}�r��*1�V�X^0����X�90{Sq#_J�X=�b�b�bZ�)����rl��$O���T�������������gG|s2�_��!0.<9��>���oM�R=�$Z=Qz��x�m&'N��dW
IRU��I��M���,����������Ru������Xw���{�4�������q�u�1H�m������u�v����)X�[���{�&"����W����R�)�J��'�����S���?��E�C)68����>h6]��}vr��
xJ�QI��������c�������8�dZ3�ps^/��s{Y�'����;w�m'�
��4���+�O`�*�����T��R��D�X�qlQE#���:�>
��XB~Q�� ��{�������J��7�*�i�L��"���E��Gy�t��T�<|���Q�/�����M���c�/O4F��w�#����A_c'�����}�3��Xg����=��bAl�W3�9[�E'�}�8�����_�����KAX�A���t�����M8e����.�t���@�r���q�O�V�7�!���-:z�fnGlGw��1�%x���-������T��.�j�'�@d�j:KfH���h�)���y�����utJf|���A�z�w�N�w��
v���f(�Jn�A��Q�����o��s������T���2x6Y��0���6=����+�����n�
�I�>)�����c��^-5���[��:�)�W��V��U!�-%@;11��?�0���Z�'�[{A�XY:�y���R��4��Z����=�e����d)�K=�������L�KX
���i���VSbO��.�3VB��j�,���%��`x���]�.��v+g��K`>9�����@�j����X!�m|c&���Q�
@��
��Z,���%x1�}����6z����B��`e&?%}��!F}����Q\I4��k�����F������(��I�,���E�{��(�r;�b�(,83�vnf�ijuO��,����JUA�^�&��Q��b�f�c}��Y��lm���j$�*��W���=S�MC
�zE���{��6���U���W��[z��k�;������+,k}��J
�����V��B���mY���O��I6��	#;�����R1s��6F�M�����g�n����Y��M�'J1��8�H�o�I�D�[�*o{W��<����O��h��m����C�w�]7���U���bl�����P���m�����:�>��b�"][��n6�Loo�.T$;[ZL��6��7�t|���{=�b���H�P�����R��&W���)�v)$�������0]kM"�k�T��)����%(?�z�6�h��X�1r:�����M�d�}w�7�k�*E����x�����y���k=�bE���dap���
��&z�`�=���A�$Q���m��B�qp��6dp`�����^�5�����'��Fh
C�2��g�
M��R�%�|�j;T�Trx`I��L�H�5[I�����6�P:w���D����b�f���j,v��H����S 9|
$�:��� Z�/�r�~!}�}=�h�������T=�{��k�����]�n���hd���f$�Sw������!	?&��L��Y�[I~
1��U5i�����0�� ><h0"�,���0����zP�Y)��v��������������i�-�>)C�q���0�6
x�)�,b�q�o����>\��]�"`9�����]��|�Q��IH��
��AMC/AWr�������i���
�T��APSJgR;|5�e�_,1�7�����u��1_�F��8�t>���ZP?���O�d����=����I�#��0-��z����U���W�Z�
}�A���f���1G���P����-@\��?@Y�O���Jr�����ss��_�z�^���Y��.�������<�|\k�&)����@��V}�.*n�{��.�tG���&m����C~�����X�@<��90��+�6�p��<2�D�c��M�0�6�):8��e��xwW��L@#�&A�6���TPk�����]�i����������F7�l3�N��p��L�8<r�����/��`4�������
��cj>���O��������VK"��F����=��Q�l���x���/as���7��*���3�����cx~�3�H
Ob��)����\����$,��IM�������
��)��I��\�����np�w��_��OL��|0R����37���SCda�L�6n����!�H���j���w�w�^3����}I
�Cj�g�M����I��p`��������}�S��^:W����ea������`��q�J���2�^!o��D��_Zt+����nZC�����+��F5� �%�$��;1f��)�����-z5r�����N�2������������`��2T���1x���Xe���&�<b%�8�d��-v�������U�z���x�@�5J�r�,'^��j�o!���S0���A�/�a�W�7�f���	���r�Q�
��1h"8!�z%��F�Rf&|����
^S��*:�t(����/2����R��.)xe'�m|�jG�	g��;�P
?��4�Y�P���j�w��y�oq$�����:����+�:%d�W;f�t�K��c�� >�["	/�\gw�>�(��6���2{D|
��-�I����*H�����u����1����t{f���QU����BD����
7��JgU}����[�/K�B����J��]��us���R��x�[2��0[S���C����y�����m	����(a�atoG.�s����iX����2?P�JB��$���l�$,�e|������M(�TI�)d�mU���j��'U�%�ZI,q�RU�UT��H�B�L	Z������e�/�R�,wMU�;��Z^.(L�*�B�	�9!�������u�;�VR��`	D�O&��g��r2�?%������t;��jG`hR*[�������_z(�/"�E}R8�ei�B��=��#��Y.��>j�������-S%S�u�,�������4�#�����8�z��4Q�K7�qw��x�'���6��x	Ny���?�q�I�����E=�@�]�����iG3��#�V�D��+K�Mx����H�[CU�2Yw�C�A���H��4]f#l�W����D�|�Bq�[��zuX����>����kS{��$U������������hYje%��S��\��1S&���/�2��(�<��%��q�`��Ko�A����������+��p�K`4�$�xF��3fGmSJdX#�2�����}m\�����S�0I,@�ha3�~.��p�wn��ii�o��QK&�$����l�I�8s��XR��OU��:���L1�=oG�{q/Z�1=�3/D���e��n����N|�����W���]N��v
7��
��}����'7Y��3;���*j���Z��V)��@,�|���%�{���a�%���v�;�%�g�*�K���\��\8�6	4-�yyPM�����1@���v8�?
m�27�N ���
���O�l�f��5;�Y�+V�wY�O���Q�S/r�]]��2^h���No�7 p���o�����QY��8��2���wnV����C���Km��o��r���}�BI��"i�����8+c�������z��h ���2GR���������0j�Y����M@�R!c���A��	�(�'q���Y���U<	���a\vOY����4�I��3\0�\�8@�,<�0�����VxW�1_��YB��LvU����U��9F5����
��H���	"�f��uq�M�d���&{3�U��S.�^5��R�xY�g������<33R�IE{r
�p�+*��2Q�^l.��]�S��7G'5V���G�z9xsz��y�����l
����	�+v��n����XUw�X�K���bHF2mw�
���?��<�o�Uv6�,c�������8>���������}&b_	�o��6��9��1L���"����<E�@M��J�(��A`.o$$&1^Z��	�j��.[�"�
q�8�F����o��'��������V{�Y�Vw�66�W�9���6
��Ri���G�FP��A�����@yPD,�BH��!t+��&�q����[���\�(��
���?�����F�B0�6��6����B���`'����C(c�?���C�s�%J0+Xq+���B��$.G��H�������^t���n��bQj�8��=MO��]�%�����%H����?����
��>z���
�V��h����FOY��\��w�r���j��]r�mP�Zz�Zn�uey��]�*Urg8�O���eG�YvIr�yE�HAoX�e��6�0(
�=�o?���b��|��ix�)<�����q�����(�M�'��w����=��S�����������)�2k��P8�5���8�%�����Q������'�13:�����.����+9b��O^���h��f�����q������<un�=S��
)N1�jRL��d!���@�Q�m���F��R���l��&+&��?�7����c�l�_���)�yG����r.�-�+�[#��V��3���H1�������a0��������YY�ozi�T����S/��'%�(MZ��2�L�AkL�����O�_�s^�~��,��Ol-c�V
��*-��?����57.z9�lJ� ��o�6�~H,�tu�d��8b~C�1j
#���h��em�,>rlfY�"� Rk:^!��7e�%�w����j�qp��&}��_�W�Kg(���+-��	��-�i�����*=�F�����+�s\���������'"=�	s������L��t�4����%7��%%Siq��q'�
��7W|.�\�V!�~�/!�,����O���{�E8I9o������b�_�	��{&,��~��%	��7�(���N�}����;<N�K�aw:�&����K���c��a�@��ZP.sA��pL}z�yt�
�Z��N�#�-��}s��:�+�G�T7��+���>R�[�0!Q�����6��"���
����E����S������R��^�	����\�J�Q�9,�jdaN�P?�(��W��	qIA^k�,	�	I�)��p��(���c�w�x���n�^��<���C?�����:�<N�����?)�]�G'�VQ�$;�h�K�.�?W2��iLhK2����K�nx>M�6QJ(0S���47��xa�t�
K_�fQ%���
�b\���H��`��7EK'���t���x�k3A���/�i�9�*�����nAc�>�!���������x�	6���)�G(�q ������n�5���*d�G�p��nPSM\C�I!g�������$��c@mv��Cb�tP���>�W����XQ�����a�����������F�%
'b���� ��W$2gf���J�����Jl�<�����%
ek��vTlY�.�*�!��|�@;���ySp�n���m�wY��(��2T���;�W>���N/.v�X)M��X��5+���3;5���X�0jk�c�g�n��MX<��SKq���������-��+�n��f%����sp�=���i��d�X�g�p�]x�F�Ps7�����t���[%S@GRl��z�]�^�z�����J���;%[���6w0�$}n���F�(�,��k��`�����f���u����p���{	�y��V$���mt��������J�4'Mb1e��ui-�B��7���Dn����F1�W	���1���8��p��P���'�|�����k�G1�s ������O��71�>_�}���WZc=g����XH���+��='j���Q���}
�3��mtIU�9���@��=���~�Qg:	��������:�y��y$�'Z|����s
hN%��Q
'B��-=��Z�vS�Pp;���c�vy���r�CB��Qd��DW'�qJ���@�uQ������R��&���)�2�l�IsxS��-f�nc� #Z|�������OG\�,�6���1���)4����G>y%�a�m�{,�V�z�_��f��u���_���k��4mM�!�|g�IY0��������k�-�x��Z��+6�/�$�����	)��{i�zJ���2jpOD3�J�h�w�nv�R�{�g�g��7G��C/G�%���G'o���feV�`N�����sy�����F%�e�'W����|z8�%��������l#�j���:�w��
���;�GW�f�K���2H��a�0�lMpQ3��8�<�dj_��6��"ir�6iG�s��,Mz<3�;��������z{{7���67W�Y��5���^!
�Z�%��:1OZ���o�x�Y��^V�b�6]d��h�?}��*���{y�]��*'PX(��T#>"�^��X���.��_&�e�:���
��M:����������i��Y�s ��<�s@L�\���L�(�%����}[�O ��N�{���x��4w��
�s�v���lP������q����9���D����z`9�A��?����+��%"����yb�k(`��
}�����J��#T`���y��11�y�E./����z:n\���'�Tc��>���|�IN���*�!��a�yMPJ���fQ:��'�7�>�uF�-J����V����f;��6�������?1S|������<�"���s?=��MOu�40������5�����h�����"/a��hr@�|�%���qonQ���V��d>�j�����^���65p4���&sV�V�����z�Wx[�����0�
��`:�#���z�@��YZ�b�;���3��`�B	�"1�j���}�P<x4-�h�f~m{���������lxs��=���c�g<��O2�>��S�!JTg���i�)%:�'t�P�o�2q�Qol�����7/<��/u4d��}!���sW��]��U�%������W_�r.�jue�����L�/�^��������5�����V� �&���=z0�ij��cz	�Di��K��'k�oW�"�%x�����Q�A��f��y+���w2w����M������_�j���u�p�y�,����������|��������Z����F�����f���;���%T
��a
{!���������T�m����12P���V�A��V�	�"����=����H���]&����i�3c�/�\�#��S�����{�D^�9\l���B�3�gO�����(	�;����9u�R�97nw'���W���V�>u�=� �X�����<}������2>�]�a������JG��h��������V7����<�zz{����yj-���sk��4��|</���S������'	�Y(d!��_E>j�-z���0�u^q�6vs��L�{�PT��Z����=������n��%%��sR����xhG'�Y����+�M�`�n�i4l6���n����g���%&�\�I����f������{@O��)T�%�����zV~F����@�����,Pl<��7��S^�O��@�A��b6sG�`�gb*G�Cy��t`�>�C�L1d��q���j2���N]�A��7�j�Iw8z.y�R�b��(�6;;��F�Zml_mlo��'��5G����n�!~�K���^{��X ����H�4�b��������2U|�������e�rZ�(
�������R�d�(r���a�G1c���)�����}���':�f�u��mV����������t�-�9O;iu�s���3vZ�^��^��r����Dcc����G#��~�4��D��M�����Q4RV�Y"���Kf\���)����|�Z�l�34��l�r���\K�$��%x�?s��#���6�$#�3Y'z������^x�����#k���O�N�V�2&O��<)���P�`\O6E}�
��u���7�rQ�e��V�J�����i��~D{�����/�}�(~�����h��B{������F�;}������n���;����C#��sfd����ll�{�}�S��p�������{}D��t�����������e��7���_�����z�/���Mm�t��O<!����nI�x���-,����P���������^��2�m��]&��N��/Q��N���1��R�^��
�C�+,S�2�<���Cr��c=�������Y�������^]�c��c����cH<��e�B*�Y�&\�!��_z{R5x������3<�[�W,+�+j����JQ�7w����������Q�{sz~b���p��W#�u��	��I:fP��)>�6D��v?�C���k����M�u�B.��B����)��'�Ss������Bw5 #7L�Vq���:�{���k�K��evy������E��S��%����|
�"�fmSN����4���X��~��������@!��{�Q��;������A��|�V\4��Vs��+����YNc�W���=@���&uY'���[�L��d?v?gt�����O����r9c�!��&�����!��l�	@v���nx���O��~�y�0��r]rb��S�|�}��0���qPm	Tz�s�:�eQ���V�5y..�ed�������+���L$S�(�0N�W���+Bl�6���1� O����Xp!F�����+�d%b�����q��3����|h��+���4�����;��7%����������o3��+��d��x)���6P��_`�#��F�7��n8�E�$?�:�{5��a��J������tQ��|�f/�������0l�Y�����:�}���)N�Q��6l7w*���s#"�?������C]�N�z������
�3�A4p���_GE�p��A���Vu��'�;��9(����a��@����"A"�;�L����>�^����7c=�����Z7)-��s	q7b��q����U��V{���f/K��Y�X���{lv����t�6EW���t4���f[Q�<�5�����Q�4�j^�7�H���8�bLzn!�q�$�����u�����oe#���;�N�Z���7aSr6"�~b+2J����Tk�
:_������W���>�����Sd�vwQ$��m������~L���]Q Jn��7j����������CT�G?F��#�"6hm�����cd����%
Y��r<i��+����q\
�W�����?F�i��������
��b��2�R�%y������5������KH�|������O�_��n�=:�/D��N�N�����E��|���y"ij�6�*/������H���D��o�o���������x��%M�\x��$�����`���������g��:���[����g��<�7���w����}���E�g||c���������|�=���.�����[g<:��q��]��<:pTh�����)����r�{!n�t9$��+6
�\��������g6����/��C��FG����Bk&�(�
:�S�����F!=�Fz��eR��u.�����?����2�~�q4G#k��8�����7������������O�����j8���`x��$�G���p�8��,;P���
��i/|.����Z����e�p;�v���V���U;�p(n�����&k��#KT��S�R���Wrl��0Y���
'���%M�t���%p��^5�5���Z��-�s�K�t��V�r�S���g�����??n��$���s��~/fz�n�m��M���yt������}���5�.R]��O����=��#��zi\W�)#����!��w����ax8v��t��'�����u}���'��m����������
)�f���B���J��R��-�l-XC���z�^{��ud���9cH���Vq1��m��98�%/�Ap=�(��������*�h!Z`��L�<����C����P�<�T��*�ew�t��<���6��z���#(z�����:l]���1��d=�� &��^��,��z��w.�������v�P$B��_��aO�L��{#s�M��e;������W�=����G�%��Nl�9@m���#�-%���F���l��������s�^;���W�� �%+�ZckE���Qp�������q�NO�r'�.1�$���JyA�������������6��6��������������c��:v)�����:�k�t�n.�<��iO/�v�Il�.t�j���8Q$�DN��_��S9QP��2��D�^�������UV@�Tj�,�O���8Q��[�C6;N)�<N�=���a�����g����G81Ct���]KdJXx�;����G�|LL���?�L�h�>�=����W-
k����u��Y��~�u:�����|8mc��-O�i<�����=
������%��Y+��s����3-����4���#z�������~g�����orF@/�s
��E=o��q��` ��@yi��Q8��r.��ip��!��X��BX=3;X���S�x���e�)Q�a���1�|���#�������7�(���N��mpv|�z�H+x��}�l�c�k1o�RY���c��F����f�]��w��T�X�_'�e�`�S<l�9o��������m�X��Vo��3����9s]���p���al����C%���yx;�(Q2�2��:~%�O�\9b�TF}%E^$C����]�@��:�n�6����p��3$67^�O'�w�Av����������n84�]������\d�s�9�mC�c �@?���=���"���h�����y����w��;
�;�_����k��0mI��B�f�3�U����68��5����FR�2KMA���F��i�j�����hj�(�S[���7���?�$@��������{r�����1���"q���d�o-+G��!5�n�%�r�5�x2�r�X*��Vb����N�Z�v�W��%�_;��{��O&{���8� ���%XR%���B?�����\��^<�)�Ksx$��x��������t�w�|�������Vc��n}w�]�M���W�N��&Y��g�9L"@����w~��8G�WF�*��VX�4������K�Z�k�W�
7n���������J��-=����Of1�XG�3��RjS���� ����P��=HA,��z���s��������j�K�?����D�����EKH*������9K��������'H(]{��6�Q��.5����s�������*�)^W��b�{����L��U�'��(k���<�74����
S��`rU%�"^�2�!�bl�o�{�_��^v!�������_�#�?�~������B�6���z�����{��'y�<������Ie/%C
���O"X����4����M?�9�*��Eou���Z{w�W�nn���j[iJ�W����D#�,�&6�cR��Dd;}�/���PD�!~��?P���p
�����w�e����QT�zsg��^_�U7�hgL�v���u�3/Y~+n�V�
���!����x�u�X/=�n�r���G������j�v�w2�;s��2[������IZ6��������c�bUk����
���h�nI�������iW�a�����F�H�.��3I��yF?{3::����#T��-���)�Ss%}��Y�����Q�D^����qtk`*���~N��T���f[LH�!.v*�NN������I��l;�=�n������1��
D�a7z��m�p��o�^4��
��h�1���JUJ=�Hc������S.(F�}�-�$�b���%�b�A
���b��	�I��I��|w���61O��I0�b��)��2�q��`4��������&�YH��+�3�"�iaT�����^���������{��l����W��3�n�����%yx�N
XX+8q�6�Rg���&�O�v)T!3�I��	�=���� �(OP�l6b�hR-q��g���new���J��
�vE)�����U��aJ�
�����	n���s1�5���aUn_�4L�sb��_����jM��mR��5��S�����8��r��3�1Ci�1���T������jugw�y��3�1#
3R�C�1~�Sq=���|/@i�%vl���(C��h�e�P�DM�������@�P��� �R�������eF���f���r��j�����C:��������cfv3�8�c��^��x�3((�V����<	��$�f7����a�-��4D��qx��trow��'��z""�UJ����;*f���C)�3�M5����k'}T�5T��3��L���%�3�p�r��K���9�#huH���A��
�a����3.l����71P��N�kh��8���/��1D��:<TC'��(q5������Yr@�&��3����>��vU�n��5{��%a�{��F��dg����������85*a�e����HU�y5�.�4
���*��0�$R�'YBk�$��&,��Vac�mQ����\.<��-��L�Y��18�IQ�p��.p��9r�~�D#��b(�|������MhWJ1b�)O�����wO�5=//���f�
��M�	k<�y����K��W�;�
��v�J*3t�����t���K��Fp�zIX��M�V�\�d�$��NH�������0��
���d ����)�����k<4�h~&��I�n:�d��{��)[���m���	�@�6�I��;6���=��,z�NE�������._#o�,�<R�Yg�h�Y�F�
D��b3�K���B3o��{��{g���|3�s����9���t��y=o�����~�f�����/��������������=������M<\���
I##�����9�b'+v�Xais0�r���xI�*�y�ZER$T5�v��	{2�?����4TEh�wHh�&A��e���������h��O������K
�j������RE�FX�*b`�=�|��:���{3�PL�8�8��_chu��c@w����������i_6�Y�����v���p�E'?w�)� ���N�@��^��]�e?E,��a&N�0Ff��������g��.Tw�o��7����[.��C(;cn��kS����/����;��A����
�
0��������#Vd�0�M�w"R[������h��%}u��kG���������t��,Pw8���	�Z\<�e�9#�X���U�����:���jm�b=����?"$W��+0�"����B+���9�-Xw2�[K����<���#��6F}�E��i�j@Woz�Dw�(�.�H��a��p�|�F�3g<���,�������E�>��������D�M���j"�5���0�i����D�I���x�VT�dt7@�Q�AU�H�>�E��Jgk��M�Ez�%��/�l�E�����C���N`b|�H�H�#�8���Y�Pd`������}qeEF�6:�%���Px���������O?��j<�B�k��}T�T����B���p`��I�y�M�U#�e�R*��]�iM�<�:x�9t�� ��x�1�TX����ke|0�����A��y���t�����/:i� 	>DO��~��CHa���zC��A�:
Tg8��bUX��������B/rc7��Q����R������1Y ����hLVx�&���.BJ4����&�� R����L�k�+�5L�D�~�1�B@���	R��`��(�\�
N�`��q��gz��WC�O�����y�B|.4tw�����!L��=����O�R��1��rGV5"���.��+9g%�:����m����;	O9)�N�����r.���)&�]�j��=�Yf��,8-���<�����Q��������}j�/�0�P���I�s(<c�5FS>Oy���P
T2/V������A��U�'�~h=�������X��m�-��p]��>�tzGh^�|o?���m��C��.�o5�IC)=�v8���OqE}�>�l��W���xK4�
7%i8q������d�F�}�;��~�
�y���XD��;��^&�2B���!����[dK�����IKF��#q��|1(K�idfD�)�e)���MA��=<T�������r'"W9�m#q���?+���m/U�j�cfY�C#{a���'�M�;�7t)]�D� n���Dv="^X��g���	���C"�H��*�S���&���X�\�"�dp�R;��Me6|����(�J&�2/_��#� t`Lh���++�t����d�[G-yJ|��[�1�I���o��Z�y�q
������#���4��t�iy���4�fj:K �`R�����up��^����u�������%&���lyJ4�M5!��JV�������/��F���u�����Led@����u��9G
�
��r�K�o7��mk�2�:�	q����#�}2��/T/�9[O|O0��� ��)j�Mb^)yP�U�BAI������Q`��w��E/�0i�j�����O�7HY���%G�����}*h�pr�zst�:N�?�?�����;�)�~��S����#�l_��mD�2���W0&9�&�'� ��Q�4��m�!�R���t��
d?��@��~t���$�zyn�z9#6�B
����?j?������� 3=5��O���N�uQ����
4L����&z~�H��y��-���V�0������58)WL]�p�W����Yh�	��y(����d��B9GQ��������QL'��=��!���I�����O���S���6��;2�=Lu%��������>}�G�]�U��9���	f��tD1{'[A���fm
����������m>������zVrD��N�d�2�\D���s0dD���
���Mo����;f���n���c $���k�V�+`�ii��8Z�xv~��|�}p|���2h�x�j�c�$���2�T��e�,e
7ef������uH(oK	�$�%s+`�f�+�1�{l��e�X���B��^�w�m��1��x���b,�MW;9�-2���Aq$WP|�)Z�U�}]5�4���2������6|Z`1(����R#��)M�z���,n��c>\p���v���V�������@d�Rx`��"6���%H����������Q�Y`�j�0����m�=A#���#��F���
o��E�}�����KpN'*5�!��,<�.���]��M��+�M����x�;�U�7v�	6��xV���h����v�B����oFK��������;��.���Zvp�ix]3�,��	�E������Y_�JQ�aN�J���)��<���X���
}F�������TYOg��z.�{�4���d���$��8����
�$��6iDFwd�_�����k��W���C�#c����_��~a�`c7	� vX�ghU�����F�a��t�z�R���!#���mn��H��5����p������7��	9�/�nG�{r��+C��q\Z���!��l��Ug�)V����U��K��j����AU�����3��A�	O9(����<�k����>`�6�:����o�����=;:���M��i�Ik�>�B4�^��^4��$1�w0�],�4��?����i�����7���^U��]>�Q��|������p�B���!�����q�!�{��P��8�-N:���rHF�g15�	���]<�o��_b���&����)��S��J�c�^�������X^L;��� �������}� ������ g�������2���yj�\�W��xA=�vq`��&�u��g�Dd[��i��T����_�|qVWh��7)?-|y>dO��AO�LY��B1�2k�J��M�9;��p	�b_ ����E��A/��rf����J��J�n��Fdo����^�����\&���	�:�7XR���x	d�G��8���!��C�Bx��$qnn������Hz ����ri~g;���D��JL�
a�0���[�f^��8�����jf�Mk���y��.>����_���e���[86�����#E���ZvM�|v8��&�!ZhH�,�iS��}�]�f�A��<$|�������Pa�(��\{�t�P�sF�X�>i"�A<	������`��d�dS�X�-����I��vVQN�/	81��I{K�DIf�9��$��Rqn�.Z���{���v��}�aM����x�2K���X�Ae����0���&�����*���R�ZFx�
��l�x}{����]��g��vQ����`5r|��z������}
Pi�N�Y�M��)a�g�I��~?�1�!Jt��V����}LQz �/�10�$�V���U�S�.w��y���j5�]���f����V�����+,j��@i��	u�V�T����F���RRF��Sg�X������f��`��\4�tL9���U���.�}@[�pl�}ID�)B����&9��8�_��
���� ��
lZ O��)������c`�u���8���H�#Yj��G
���L���������!6���+->����s�6���L-TD4�Y
+&���1!��
��>�K��6��W�l����A�eA��?����uM��!�v�l���=���F�Y�p��$�����"r�d����V�<�Y��q��A�e���o���3:������i�T�C?o�n�S]��6if�
�Hr�U��e,�� 
��3�����~"����N�����]=������a������n��dIN��r�S,���x��5F�	W)�/%��S��������Tx��m�:"Z wzv���{�`��G���j!n2J��e��R�C�`W�n�_�O�W��ZFn����]��o
��5^Q}!��#�D����N�C�������7W��ke��i[&<�e����'
)D������Q��G�M��D�d������N�����!�DQ�X_>�v���cW.`��B�):��O�O9��|l�[K�YJ0{+�bLG`�����4���r��?<��I���N.�d���5F�j2`%��!���L�T4��7f�W]#w<b���Q���A�<��Y�L���Y`4t���
^I���
�nl�Q`�Q�>
��,��+�
���QG����:�SxJ�)������NG�#sX��a�^!�����0�����j6e0���{_����tNC������f�z�])��&��$���
G�f����J�1��^9�����f��2d.T,V)�qE�Z����3�C�'!�E�>��l���+���06asv&]7+s�4gq:@�Mp��BX
����J]���[����Q�(Q��z�����d^*\N�17����K!1�R���k�.x��|����e_$h����g��������R0	&="��S�9��<R�U*t���hWJ$��
�����F;i]��>�B�2�]/H����Qp��t�j�#��W����u������&YfG��/�{a8b�v�r%�k��~�8��p�������=��D�Hc:B2��Y�`�����%U.�I����p�����2���4�P���9D.���Ri5�0��
��9��>KV���b��>�4\E�?���)2�����|/�����W���o�!'�/�`���ue'���a�#�GG�����uS�tU�+�H�k���+2��pb�������]GQ��.�#�vY���u����v��E7�T_A�p�<I�s)������E��up���@-U��%P${KC��HL��W-� Y��:��F' �98OH�y����CA��I,H����/�����2��O-��*�K��Z��	��o��=]��z�!���h�i��(E�D8.�.bO��vzag��DsZ @��*_
��}��#c�Y���h������L��PQ(���@y�r��m(�J$,��x���L��b�y�P%�2��\
���e����L����Kp*�M2��]��/�Hk�Lnz�Ym�^���8��g)-y�\��.^cP�z��;��D��Lz�=D���pW���E\u�<@��Z�y�A@v�����b�B����j_�<�l�	�����%�
������������
Q�|3Q��o��0��voMr�b���	������"���h��|�
S��>530��P��)U�;-�N�������k/=W�����������g:�Ssl���|�o�.�b_w�h�o�nm����h�@B�=��(��N$!KF{&2���U�"�js�Q��������S�ZY�U���<=[Q�_��,������d���*�c'�
��X�c`lZug���M�n�{n+n
�;N��!~�<}g�)�\yC-;����f��� s�L)���
��GoO��[��U�G�u��^"����1����L<G���wz;}�}�*�J!���x&��K��q���T�����i��
�3i�@1���r
��\.�
�h���w����Rh8@�X��:�t��3�v&�=�C.r��C��s������R�[6�}�3.��!�����s��f:�e���^s����|���W��$:�;S�p)�K(�
z��r.��-���R]�|�h)��qt��O
Kz��������/�}��N�x��5���r�`s'�
0[��B
x
o�oN:��h��_�_����=P��$�
�m�N�[$M=36���I��2d[�x]�K��w��k�a"_0��&��g��O'���;�L���)��t�AO2����%��z1]/����_�p`�ofi��:�[E���y�Z�&�6>�E���oxU�Qp�I���P<%��;��t>��v-��������#9������m���:�Y���8=�qt
���F^=���0��UuZz�L��5`VW�J*%\AXf����+%�+��1 ��@�(���Hx�|��'��
FS4g�sD/
�����{�������r������37u�,r�@+�,|�AB����u���o�AG����%�EV�#}z���Wj�]������c�U�{� ��$��Q��rA�����7��G����d��59{~������+3�s�ymLG�G��~�n�I���pb�q����*�����q����{��>����'�����Y,��Y=\b.�{��<'oE��y�y$�E��w����$�x>d���
��k������2��M�b�8b�c(���@�	��~�@��9

�k�����d���~�Q7*�Cs�OnC	O1��P�����w�5�DXL���*eC�'�JO'b��J���J�?�s��3����O�X�����A��6FE���-H��,:6��`(�����u�V;06���b+��N����p�>�NC��tB����&�i��p*��]/�X��<��5w�
���X�e�����!Y�Y7LT���.O�t$�� 2MS���-�"�WS���3HfF���[��[�G#�����h����A����;�	�%?-[�u�e:�D7_3Ys��c/�J�
��f����C���|j=uC���OsIe��S2�z�����G�I��Oc|��&�r���,�\:k�axo;��D��p��=���� �[��OY����zNZ?.��K0�<�8���:�)�FK����dz��}�K-����4IF>S��O$��G�4���,���{�Tt�H������q���jI��F���s�wG�k�M~V�%g�L�!�,��DdT������^.r�u����{�N+h\}�{���tu|!bb�q���c�����:@xz>�����%��
&7C�D�=�}�c��a��R��T��y�qa)Ej�"n�ml|��i��{�%-���.�C{�5/d������P]�w�+����0��B��G�C�g��������M�(5�z�18�^;�-Z\�] &r���R����1��>~����u?���n����C@�J�T��B
P��ab����bqka����^�GVm� ,��8��0��?���C�n���5�s���2���P(�WA9c�3d�/�(?l�:�>�����x�o��>�<�9��H[>\�����5��1��PSvA�V{�"������T����{��
n��a�b��I\V����G���f����yu��V9����PO;6wO��v4�O��pz�*�@�B�@_����������r/����1W�Y7����*��<�G_����)nw��4�0��&I�����Sy��<<���@*zQ�Z�A�Jb�)���M��x5�����o����0�����y��>��?;������?-���uy���R(DG�U�{�2�K,H�rb��
52a�?�uw�\���Oc�����*�Y-9>�)G��k�/A��T�T�E��|l�O�"�-�E��"<N�m���0Y���P���+I�d����SFQ=�C���h_g:�����&sH(��6���!��x�!k� ���C�C|��YA�M��P���
�8���%�&Q����D������Q���Q�������]��Ub	��_�F�vW����f�}��j[�������^�������?�S��6+�
��u���������A�?����jVo����}�E(^?]���z���Lf	�����"p�o�yn�����(��{ ,�sg,�I���S��D
~��, ���/������~|S8V|�����+
���5+��]X(�(#I1A`<{��pB�3����+����Dn�?=:�t'J�`F���	��1>�������L0*{lC�Ux�	{��(�T���:g�[����k�]���ItV�����F��p�Ijr7tt�Uu��`8���������(�(p��#L�]_��N�O���|G'oN��k���0{G����x���D)(p
�s`W2(�0p;6�rF���G���C����^I�bKQ�(wL�N�$��������������&xy�B5���P=�_�����Q~J6��%�3��5��F�{L�3�cj�7�-)��O�����+�d��p��/Py��ut^�� �e�]����������t�_��&�&j���
�����N���'��R�Q8����;�5�z��um7�1~N(���8��E���	��#3���p�#������s�x?��u��C�A��->����h1�����!s�g"�(I	�A���jKp"�g�@a�TNB��
���������9��Uie�����yl��Y�V�0�c������J�tH���g��m�dg�WSg"nw1���.��$�����_IZ��C�����xs�>xp2f����pk[F�Q��|�8�U(�yd2�.��b��r����@�
�d�����A��Tb��D���cX%Y�PJ��_�I��. |Y<�z�I���e���M���k�kte�_�T@	���`4�N���o=E�������'��9'"�K:<G����I���pq��+Q�����z��r�`�� �6�c���m�%�s������[���C�r����U���N��
N�����'0%�)�*L����~��:�`���d8FF?�@�$i�E��g���G�w}%-�����l�W��z#�!����{a��L������e��K8�3�3��v�L��&��*���H�%�w��O?��o)�TG��ue�����M;&���	idAk�kb�R���H����Oa���/-?�K��8*S�}�3-���_�qE}+Y����w���Y7��T*�+��L?�����������S���"X�8���L#2�Y�!MZ,s��$�h>�tb�tk����~A!w>c�[������h��_En��(�������z|�����������g���uJ9��z��eay9�;Y�q���K���@�n�:CV���b��}b1"���(�r/.�?\JO��CAN���SB��t��6��#QB�~�����X��%:�?y��D���+����������Ry���gI���7��M�]����O`A������+���V1��2����IrRR�H��s��7@f!��,=N�3��%����kL��*���J��?���t��pxjNv�m��@��l��-��_O9�m���>"E���PW�Go���1f���3��U�{����@�����
0V��LA2�V�f0����.iz=���2	4���p'�_v'}8���n�D//��1kd��
��)\fV`���:��/���[�|8�������Y"���n���w:8��7�I.C�GLG��{{Im��N�. �?�1���ME����?4<{L�hO!
� ��55�\����Qd���2���NL�>�N��o?��~8P(�!?�" w�p��O�;�qO���$.�q�<xE��Z@�s0����+�����~�K�����s�����T$�h]\��8�����������Od��k�c���f} � w ��V0��m{02���2�Rc�(IE\^!B�ON�����0��,�:�1�u��5/������V��d���L�(nw8N�o=E��5+o�X�e���%'kr��E�sE��y4���1�����Y�n`��o�f�����+Z���T��-�T�
���~f�������.	������Abh��P=B��r���	�?�rx]��,Q�:0J��F�IW�L���t�.��
���_�w%��lB]�j�4`cLu�Q�xG�A;f~�L����r�%�L�������8��$�F���9�_�q���U�I��1g_�:�Xu�1'��&�e��I�!r�`k:�vOcn��0�Ww8L�(��8�9_	���� �����tB��$�(�����&������:���Q�gY����Z
���G���3��o���~�2*�g�O�mN��2�������a92���>V����g����^�_�l3��2%tGf�=�J�/��0R�^B������b�,��:r�0$��d�v�w��Iw�NvY<����Ao!w|����Op�������9���x�f��[A����6�4m�TB6����]�a�OZ�?�����9Q��'�Gl��m���^t����9�b�1 }���\�������9:9�x���p���$�J�xo��tzSN��P�;��p@��Mx�A����P�sM�_���C�n����:�y��b/�������������U�sn��RK����n���kw�������U�n�_�m|�e���?>GA�o�+��Z��l���:l�<���Y��K��3����C��V�x�Ev�'SX$�491���9H�-����o[Uj�7�d���;�{����v���`YnW8�
���	�2� ��*�T�iCFh�)C���e(��?����h.g�����:�T�'EE���zfdb�B�X�/��-&����J-���"�jg����m���/0�H�� ���Fp �6DjD�M��Ad��@dm�����,��A���A���T%��,
������"&��,j�=7���)#���e�\�~�l��
��������ipe,j����X8�>M3��"��fK����~3��r^��� ���>!���_�k&�����<T*��\k�������XY�9�kg��5K&�����a���j���b�$An�9�Y�|f���o��.������0�C��b���";��F��]�6��������~3�`�d���`�E��3�����%:�P�x9�0YZ79f�q���g@@�-�e��=[B|���`���������Q���`�
fh3���-R�,��y�b���u �so�M������j\5����������q3{nh����q����@�JT��b�=^��,��T	���Bfu���=�%�;
�|W�
���M*�F���H7���9}[{d���Z�)%"v�IR���@���b!�f�*����J��Z�9!��C������
��G�$p�
�CX@�o�����1�8$A��0;�� �����lm��l7���DA`���D{LnGAu�0)������dL�"��ES'�p04a^
�a^O�G���/U/��?1�y����v�u\���gv���
����z��gl_�%t
��k]���Y�v�G�P�����V�7p����S�L��vO"�<'����];��DB����r�d�L�Z�MRW\]{aws(5_������/+����I�����_�>�S��y�G���
����%�g�<��!���U���j�Vj[�ra:,��H�;���0�K�[����1����N�(��0oqd����|Cu�������a"����U�f��%��/��DN(�U�!�)At-�����8�1b�|5y�����qx#e����Tv����$�xT�>�5���i�����03�i�]ss�����N�[�n����-Y�fvY�a������,�3k�d���x��cr����$�DS�	������-���!���w%i���V��wJG�0�U^�$����:\���f����yi-"���{:������I��o�YE=������xf3�r;
;������V���W���A=�����dU����e���,��%�G����=@�J��FS&�y[�M�����<�?�5��r6���gQ�^C3x}���&�z������=;F������=� '�����O���?q�.B794�C�d�Q,7+y�}�v.5#4w&U�0�'����*C���n�x��ns�� ������������Q�.�����RO���� ��	�C��2���
�����HlW���`��^w�7�2��kup��L=i��4SO����)��Mt�I>i����_3+g7��"	�B�	B���P �cznRy��o�8����;F!^��yA�G�����=9�0�G@xF#v� e���cbVU��&�vd�A�I�� q���3B��I{0������xiJ����/gS��S���$��~
����OJ�0��F����,^P;0b����g�y���*�4��7vvS��W��]����S��<��P�0��r��M�4�A�mT��ZrX�uZ����:�6��j��6	StM�B�=���m������; |��p���D/�'ph� �c2�_^R�'��m��Uq@�6i?�3�$>W
Z���/uK�7��$	�fP�_K��P���_����O��2�������.&�K�����6{������
������]��V0h�RQ74��@��a�%��ve�K��*`��,`�����].���/�i����k�6�UG/��7�t����#f9��,�1J|FW��D*6`�e��'�b4h�}?�_t�����sc0���g��UQ�j��Jx��M�4� 0���&��8>�6^�A�[��������J�������e���	l�������x��=7�4�;���/��Z�yn{B��x���X�2��f}{��S�����Z�.��il=�'�e:	�9���v������ym�f��/�0���������Y�f�l��_o�#b�7�����}����"@`�"���eg�CQ;8k�)\H�������>Di ���)�CVS���D ���������td�Z�P�x��f��`�E��S/�#h���E�$����;����>N�1]�!"��-���@6z�n��G���I�$�q]+��@��	G��&���\om�R�H��|�����E�bk��f���K���L�������M����x�Qe�'s�%�<
��,
)0�+����
q~^rh7qNj8�>���E���&
��?���������!F��V�'�OH ��������S�m=��N`�S�S����B��*��^���g��g}}�N~F���Xi��_Z���^�DP� _�y�#1�R
���^�>����ZS��QnS�G[�F������
��a����<�(������W#B��0d���C��y�E���H
�2Y� ���s!��_f_��.���\����rI:���'�����������Hq�I�yp��n�fb<j���n��y��S�����������G�9.�\~*/��s���TD�8��<��Gw���sBd���O�wf�{���(���_��N-D����������X=��h���)2'9;����������������������?�3~g���������b�j�����}��'��aA|0{a�����v�qD /+D�2�}T�fjx4u����
l�#C���|��^�H���t!�2����<h-
s�����3G�Nl�U��������/.�/[���zm^��������MS����Q&����3��;l]�g� DO�wtr�/�)���O�W�����h9��u�0��������E��������i/���a653��"��/�\6��.����K��;\��b�U����m*$�Xg��m����H��D!�'X�.$�X_jj�@�/��g
Ke�Tc����UiP.��IcxH�?8�����d����<y �5�p@�	�(}8"�)��t��'"�A6?%i������bx~������taO?����9Y�_��H����g����L����Pr������������W`^���"�ES`M�(������a��4��"?3=�l��?�J,B����Z9��tK���\�J�FH�hX4��h��v��[
�57|������6!�}S�%��)0���t�c����������u�jm���\�3�J$��(�,F��h	?vl,��6����<�#�'�
A������[�u<�u����nQ��",4�
���l�����5Y�J���&`�������
K�{B�w����hf`@�����������O�G_&"i���+������Y�_8O
����y��gm����������3���)�_?�����
���7v(u�v}�R�!~),���J�-�zH�'��xj���\��ZN��]���N�I}���u����4�����K~����S��R���L��������W�+�_�
�������/���_n��@����������~F#�/��m����Oi8�o��/�t���!-��Oh�����)���5������m�1k/y/����?�������������/��C	(��

#69Alvaro Herrera
alvherre@commandprompt.com
In reply to: Pavel Stehule (#68)
Re: review: CHECK FUNCTION statement

I think the way we're passing down the options to the checker is a bit
of a mess. The way it is formulated, it seems to me that we'd need to
add support code in the core CheckFunction for each option we might want
to accept in the PL-specific checkers -- including what type of value
the option receives. As an example, right now we have all but one
option taking a string argument (judging from usage of defGetString());
however, option fatal_errors takes a boolean value, and it converts to
string "on" or "off" which is supposed to be passed down to the checker.

This doesn't seem very future-proof.

(Also, the patch seems to be passing the fatal_errors value twice: first
in the options array, where it is ignored by the plpgsql checker, and a
second time as a separate boolean option. This needs a cleanup).

I don't see any good way to pass down generic options in a generic way.
Maybe we can just state that all option values are going to be passed as
strings -- is that good enough? The other option would be to pass them
using something like pg_node_tree, but then it wouldn't be possible to
call the checker directly instead of through CHECK FUNCTION, which I
think was a requirement. And it'd be a stronger argument against usage
of SPI to call the checker function from CHECK FUNCTION, but that's an
unsolved problem.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#70Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#69)
Re: review: CHECK FUNCTION statement

Hello

2012/2/29 Alvaro Herrera <alvherre@commandprompt.com>:

I think the way we're passing down the options to the checker is a bit
of a mess.  The way it is formulated, it seems to me that we'd need to
add support code in the core CheckFunction for each option we might want
to accept in the PL-specific checkers -- including what type of value
the option receives.  As an example, right now we have all but one
option taking a string argument (judging from usage of defGetString());
however, option fatal_errors takes a boolean value, and it converts to
string "on" or "off" which is supposed to be passed down to the checker.
This doesn't seem very future-proof.

I don't agree - we has not any datatype that can hold "key/value" list
- and can be used via SQL interface. So two dimensional text array is
simple and generic data type. Theoretically we can use JSON type now,
but I think so array is more generic and better checked then JSON now
(and it require less code - and JSON is still string too). We don't
plan to modify parser to better support of JSON, so text array is more
user friendly. I think so pl checker function can be simply called
with used concept. But I am not against to your proposals. Actually
"concept" of generic options was required in initial discuss and then
there is implemented, but it not used. But cannot be removed, because
probably don't would to change API in next version.

(Also, the patch seems to be passing the fatal_errors value twice: first
in the options array, where it is ignored by the plpgsql checker, and a
second time as a separate boolean option.  This needs a cleanup).

This is by design. One request for checker function (and check
function statement) was generic support for some optional data. This
can has sense for plperl or plpython, and it are not used now.
Fatal_errors is only proof concept and can be removed. I plan to use
these options in 9.3 for checking of "inline blocks".

I don't see any good way to pass down generic options in a generic way.
Maybe we can just state that all option values are going to be passed as
strings -- is that good enough?  The other option would be to pass them
using something like pg_node_tree, but then it wouldn't be possible to
call the checker directly instead of through CHECK FUNCTION, which I
think was a requirement.  And it'd be a stronger argument against usage
of SPI to call the checker function from CHECK FUNCTION, but that's an
unsolved problem.

Using just string needs more complex parsing, and I don't like some
like pg_node_tree too, because it cannot be simple created by hands
for direct call of checker function. Please, accept fact, so we would
to call directly PL checker function - and there all params of this
function should be simple created - and using two dimensional array is
really simple: ARRAY [['source',$$....$$]].

I don't understand why you don't like pass generic options by generic way. Why?

Regards

Pavel Stehule

Show quoted text

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#71Alvaro Herrera
alvherre@commandprompt.com
In reply to: Pavel Stehule (#70)
Re: review: CHECK FUNCTION statement

Why does CollectCheckedFunctions skip trigger functions? My only guess
is that at one point the checker was not supposed to know how to check
them, and a later version learned about it and this bit wasn't updated;
but maybe there's another reason?

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#72Alvaro Herrera
alvherre@commandprompt.com
In reply to: Alvaro Herrera (#71)
1 attachment(s)
Re: review: CHECK FUNCTION statement

I've cleaned up the backend code a bit -- see attached. More yet to go
through; I'm mainly sending it out for you (and everyone, really) to
give your opinion on my changes so far.

(I split out the plpgsql checker for the time being into a separate
branch; I'll get on it after I get this part committed.)

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Attachments:

check_function-core-2012-03-01-1.diffapplication/octet-stream; name=check_function-core-2012-03-01-1.diffDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 180554b..7ec5b59 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3663,6 +3663,18 @@
      </row>
 
      <row>
+      <entry><structfield>lanchecker</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+      <entry>
+       This references a correctness check function that is used by
+       <command>CHECK FUNCTION</> and <command>CHECK TRIGGER</> for in-depth
+       checks of function bodies.
+       Zero if no such function is provided.
+      </entry>
+     </row>
+
+     <row>
       <entry><structfield>lanacl</structfield></entry>
       <entry><type>aclitem[]</type></entry>
       <entry></entry>
@@ -4273,6 +4285,12 @@
      </row>
 
      <row>
+      <entry><structfield>tmplchecker</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry>Name of correctness check function, or null if none</entry>
+     </row>
+
+     <row>
       <entry><structfield>tmpllibrary</structfield></entry>
       <entry><type>text</type></entry>
       <entry>Path of shared library that implements language</entry>
diff --git a/doc/src/sgml/plhandler.sgml b/doc/src/sgml/plhandler.sgml
index 54b88ef..d460683 100644
--- a/doc/src/sgml/plhandler.sgml
+++ b/doc/src/sgml/plhandler.sgml
@@ -159,12 +159,15 @@ CREATE LANGUAGE plsample
 
    <para>
     Although providing a call handler is sufficient to create a minimal
-    procedural language, there are two other functions that can optionally
+    procedural language, there are three other functions that can optionally
     be provided to make the language more convenient to use.  These
-    are a <firstterm>validator</firstterm> and an
-    <firstterm>inline handler</firstterm>.  A validator can be provided
-    to allow language-specific checking to be done during
-    <xref linkend="sql-createfunction">.
+    are a <firstterm>validator</firstterm>, a <firstterm>checker</firstterm>
+    and an <firstterm>inline handler</firstterm>.
+    A validator can be provided to allow language-specific checking to be
+    done during <xref linkend="sql-createfunction">.
+    A checker performs in-depth checks of function bodies via the
+    commands <xref linkend="sql-checkfunction"> and
+    <xref linkend="sql-checktrigger">.
     An inline handler can be provided to allow the language to support
     anonymous code blocks executed via the <xref linkend="sql-do"> command.
    </para>
@@ -203,6 +206,39 @@ CREATE LANGUAGE plsample
    </para>
 
    <para>
+    If a checker is provided by a procedural language, it must be declared
+    as a function taking four arguments, one of type <type>oid</> second of
+    type <type>regclass</> third is text array (used for options) and fourth
+    of type <type>boolean</>.
+    The checker's result is a table which consists of 
+    functionid of type <type>oid</> (oid of a checked function), lineno 
+    of type <type>integer</> (representing line number of the function 
+    body source), statement of type <type>text</> (statement type), sqlstate 
+    of type <type>text</>, message of type <type>text</> (error message), 
+    detail of type <type>text</> (detail of the error mesaage if any), 
+    hint of type <type>text</> (hint for the error message if any), 
+    level of type <type>text</> (error level), position of type 
+    <type>integer</> (possition of the error in a query if there is a query)
+    and query of type <type>text</> (showing SQL statement in which 
+    the error occured).
+    The checker will be called whenever a function is checked with
+    <command>CHECK FUNCTION</> or <command>CHECK TRIGGER</>.
+    The passed-in OID is the OID of the function's <classname>pg_proc</>
+    row.  The passed-in <type>regclass</> is the OID of the
+    <classname>pg_class</> row of the table on which the trigger is defined,
+    or <symbol>InvalidOid</symbol> in the case of <command>CHECK FUNCTION</>.
+    The last parameter passed as <type>boolean</> defines if checker should
+    stop checking on first error.
+    The checker must fetch these rows in the usual way, and do
+    whatever checking is appropriate.
+    Typical checks include verifying that all referenced database objects
+    actually exist or style checks like verifying that all declared
+    variables are actually used.  If the checker finds the function to be okay,
+    it should just return. If it finds a problem, it should return a row 
+    describing error.
+   </para>
+
+   <para>
     If an inline handler is provided by a procedural language, it
     must be declared as a function taking a single parameter of type
     <type>internal</>.  The inline handler's result is ignored, so it is
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 382d297..ef75780 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -40,6 +40,8 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY alterView          SYSTEM "alter_view.sgml">
 <!ENTITY analyze            SYSTEM "analyze.sgml">
 <!ENTITY begin              SYSTEM "begin.sgml">
+<!ENTITY checkFunction      SYSTEM "check_function.sgml">
+<!ENTITY checkTrigger       SYSTEM "check_trigger.sgml">
 <!ENTITY checkpoint         SYSTEM "checkpoint.sgml">
 <!ENTITY close              SYSTEM "close.sgml">
 <!ENTITY cluster            SYSTEM "cluster.sgml">
diff --git a/doc/src/sgml/ref/create_language.sgml b/doc/src/sgml/ref/create_language.sgml
index 0995b13..b6f6dfa3 100644
--- a/doc/src/sgml/ref/create_language.sgml
+++ b/doc/src/sgml/ref/create_language.sgml
@@ -23,7 +23,7 @@ PostgreSQL documentation
 <synopsis>
 CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
 CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
-    HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ]
+    HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ] [ CHECK <replaceable>checkfunction</replaceable> ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -217,6 +217,45 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
       </para>
      </listitem>
     </varlistentry>
+
+    <varlistentry>
+     <term><literal>CHECK</literal> <replaceable class="parameter">checkfunction</replaceable></term>
+
+     <listitem>
+      <para><replaceable class="parameter">checkfunction</replaceable> is the
+       name of a previously registered function that will be called
+       by <command>CHECK FUNCTION</command> and <command>CHECK TRIGGER</command>
+       to check the bodies of functions defined in the language.
+       If no check function is specified, this kind of check is not available.
+       The check function must take four arguments, first of type <type>oid</type>
+       (the OID of the function to be checked), second of type <type>regclass</type>
+       (the table with the trigger for <command>CHECK TRIGGER</command>),
+       third is text array (options) and fourth is boolean (defines if check
+       function should stop on first error found).
+       The check function will typically return set of rows with ten fields:
+       functionid of type <type>oid</> (oid of a checked function), lineno 
+       of type <type>integer</> (representing line number of the function 
+       body source), statement of type <type>text</> (statement type), sqlstate 
+       of type <type>text</>, message of type <type>text</> (error message), 
+       detail of type <type>text</> (detail of the error mesaage if any), 
+       hint of type <type>text</> (hint for the error message if any), 
+       level of type <type>text</> (error level), position of type 
+       <type>integer</> (possition of the error in a query if there is a query)
+       and query of type <type>text</> (showing SQL statement in which 
+       the error occured).
+      </para>
+
+      <para>
+       A check function would typically perform an in-depth check of the function
+       body, for example that all referenced database objects exist, but it could
+       also check for stylistic problems like defined but unreferenced variables.
+       To signal a problem found during the check, the function should return row
+       for each error found, if fourth input argument is True, it should return
+       immediately  after first error.
+      </para>
+     </listitem>
+    </varlistentry>
+
    </variablelist>
 
   <para>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 7326519..460794e 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -68,6 +68,8 @@
    &alterView;
    &analyze;
    &begin;
+   &checkFunction;
+   &checkTrigger;
    &checkpoint;
    &close;
    &cluster;
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index ce866a2..5140ead 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -48,6 +48,9 @@
 #include "catalog/pg_type_fn.h"
 #include "commands/defrem.h"
 #include "commands/proclang.h"
+#include "commands/trigger.h"
+#include "executor/executor.h"
+#include "executor/spi.h"
 #include "miscadmin.h"
 #include "optimizer/var.h"
 #include "parser/parse_coerce.h"
@@ -791,7 +794,19 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
 	}
 }
 
-
+/*
+ * Emit an appropriate error message that a language was searched for and
+ * not found.
+ */
+static void
+report_missing_language(const char *language)
+{
+	ereport(ERROR,
+			(errcode(ERRCODE_UNDEFINED_OBJECT),
+			 errmsg("language \"%s\" does not exist", language),
+			 (PLTemplateExists(language) ?
+			  errhint("Use CREATE LANGUAGE to load the language into the database.") : 0)));
+}
 
 /*
  * CreateFunction
@@ -858,11 +873,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
 	/* Look up the language and validate permissions */
 	languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language));
 	if (!HeapTupleIsValid(languageTuple))
-		ereport(ERROR,
-				(errcode(ERRCODE_UNDEFINED_OBJECT),
-				 errmsg("language \"%s\" does not exist", language),
-				 (PLTemplateExists(language) ?
-				  errhint("Use CREATE LANGUAGE to load the language into the database.") : 0)));
+		report_missing_language(language);
 
 	languageOid = HeapTupleGetOid(languageTuple);
 	languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
@@ -1048,6 +1059,533 @@ RemoveFunctionById(Oid funcOid)
 	}
 }
 
+/*
+ * Search and execute the checker function.
+ *
+ *   returns true, when checked function is valid
+ */
+static bool
+CheckFunctionById(Oid funcOid, Oid relid, ArrayType *options,
+				  bool fatal_errors, TupOutputState *tstate)
+{
+	HeapTuple		tup;
+	Form_pg_proc	proc;
+	HeapTuple		languageTuple;
+	Form_pg_language lanForm;
+	Oid				languageChecker;
+	char		   *funcname;
+	StringInfoData  sinfo;
+	Form_pg_proc	checker_fce;
+	HeapTuple		checker_fce_tup;
+	bool			result = true;
+
+	tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
+	if (!HeapTupleIsValid(tup)) /* should not happen */
+		elog(ERROR, "cache lookup failed for function %u", funcOid);
+
+	proc = (Form_pg_proc) GETSTRUCT(tup);
+
+	languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
+	Assert(HeapTupleIsValid(languageTuple));
+
+	lanForm = (Form_pg_language) GETSTRUCT(languageTuple);
+	languageChecker = lanForm->lanchecker;
+
+	funcname = format_procedure(funcOid);
+
+	/* Check a function body */
+	if (OidIsValid(languageChecker))
+	{
+		Oid		argtypes[4] =
+		{ REGPROCEDUREOID, REGCLASSOID, TEXTARRAYOID, BOOLOID };
+		bool	nulls[4] = { false, false, false, false };
+		Datum	values[4];
+		int		i;
+
+		/* call a checker function and read result */
+		checker_fce_tup = SearchSysCache1(PROCOID,
+										  ObjectIdGetDatum(languageChecker));
+		if (!HeapTupleIsValid(checker_fce_tup)) /* should not happen */
+			elog(ERROR, "cache lookup failed for function %u", funcOid);
+		checker_fce = (Form_pg_proc) GETSTRUCT(checker_fce_tup);
+
+		initStringInfo(&sinfo);
+		appendStringInfo(&sinfo, "SELECT * FROM %s($1, $2, $3, $4)",
+						 quote_identifier(NameStr(checker_fce->proname)));
+		ReleaseSysCache(checker_fce_tup);
+
+		/*
+		 * Connect to SPI manager
+		 */
+		if (SPI_connect() != SPI_OK_CONNECT)
+			elog(ERROR, "SPI_connect failed");
+
+		values[0] = ObjectIdGetDatum(funcOid);
+		values[1] = ObjectIdGetDatum(relid);
+		values[2] = PointerGetDatum(options);
+		values[3] = BoolGetDatum(fatal_errors);
+
+		SPI_execute_with_args(sinfo.data,
+							  4, argtypes,
+							  values, nulls, 
+							  true, 0);
+
+		result = SPI_processed == 0;
+
+		if (result)
+		{
+			resetStringInfo(&sinfo);
+			appendStringInfo(&sinfo, "function is valid: %s", funcname);
+			do_text_output_oneline(tstate, sinfo.data);
+		}
+		else
+		{
+			resetStringInfo(&sinfo);
+			appendStringInfo(&sinfo, "in function: %s", funcname);
+			do_text_output_oneline(tstate, sinfo.data);
+
+			for (i = 0; i < SPI_processed; i++)
+			{
+				char		*query;
+
+				resetStringInfo(&sinfo);
+				appendStringInfo(&sinfo, "%s:%s:%s:%s:%s",
+								 SPI_getvalue(SPI_tuptable->vals[i],
+											  SPI_tuptable->tupdesc, 8),
+								 SPI_getvalue(SPI_tuptable->vals[i],
+											  SPI_tuptable->tupdesc, 4),
+								 SPI_getvalue(SPI_tuptable->vals[i],
+											  SPI_tuptable->tupdesc, 2),
+								 SPI_getvalue(SPI_tuptable->vals[i],
+											  SPI_tuptable->tupdesc, 3),
+								 SPI_getvalue(SPI_tuptable->vals[i],
+											  SPI_tuptable->tupdesc, 5));
+
+				do_text_output_oneline(tstate, sinfo.data);
+				resetStringInfo(&sinfo);
+
+				query = SPI_getvalue(SPI_tuptable->vals[i],
+									 SPI_tuptable->tupdesc, 10);
+				if (query != NULL)
+				{
+					int		position;
+					Datum	value;
+					bool	isnull;
+
+					appendStringInfo(&sinfo, "query:%s", query);
+					do_text_output_oneline(tstate, sinfo.data);
+
+					value = SPI_getbinval(SPI_tuptable->vals[i],
+										  SPI_tuptable->tupdesc, 9, &isnull);
+
+					if (!isnull)
+					{
+						position = DatumGetInt32( value);
+						if (position > 0)
+						{
+							resetStringInfo(&sinfo);
+
+							appendStringInfo(&sinfo, "      %*s",
+											 position, "^");
+							do_text_output_oneline(tstate, sinfo.data);
+						}
+					}
+				}
+			}
+		}
+
+		/*
+		 * Disconnect from SPI manager
+		 */
+		if (SPI_finish() != SPI_OK_FINISH)
+			elog(ERROR, "SPI_finish failed");
+	}
+
+	pfree(funcname);
+
+	ReleaseSysCache(languageTuple);
+	ReleaseSysCache(tup);
+
+	return result;
+}
+
+TupleDesc
+CheckFunctionResultDesc(CheckFunctionStmt *stmt)
+{
+	TupleDesc	tupdesc;
+
+	tupdesc = CreateTemplateTupleDesc(1, false);
+
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1,
+					   stmt->is_function ? "CHECK FUNCTION" : "CHECK TRIGGER",
+					   TEXTOID, -1, 0);
+
+	return tupdesc;
+}
+
+/*
+ * CollectCheckedFunctions
+ * 		Collect functions to check according to options
+ */
+static List *
+CollectCheckedFunctions(List *options)
+{
+	ScanKeyData	key[5];
+	Relation	rel;
+	HeapScanDesc scan;
+	bool		language_opt = false;
+	bool		schema_opt = false;
+	bool		owner_opt = false;
+	ListCell   *option;
+	Oid			infoschema_oid = InvalidOid;
+	int			nkeys = 0;
+	HeapTuple	tup;
+	List	   *objects = NIL;
+
+	foreach(option, options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(option);
+
+		if (strcmp(defel->defname, "language") == 0)
+		{
+			char	   *language;
+			HeapTuple	langtup;
+			Form_pg_language lanForm;
+			Oid			languageId;
+			Oid			lanchecker;
+
+			if (language_opt)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("multiple %s clauses not allowed",
+								"IN LANGUAGE")));
+			language_opt = true;
+
+			language = defGetString(defel);
+			langtup = SearchSysCache1(LANGNAME, PointerGetDatum(language));
+
+			if (!HeapTupleIsValid(langtup))
+				report_missing_language(language);
+
+			languageId = HeapTupleGetOid(langtup);
+
+			if (languageId == INTERNALlanguageId)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("cannot check functions in language \"%s\"",
+								"internal")));
+			if (languageId == ClanguageId)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("cannot check functions in language \"%s\"",
+								"C")));
+			if (languageId == SQLlanguageId)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("cannot check functions in language \"%s\"",
+								"SQL")));
+
+			lanForm = (Form_pg_language) GETSTRUCT(langtup);
+			lanchecker = lanForm->lanchecker;
+			if (!OidIsValid(lanchecker))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("language \"%s\" does not have a checker function",
+								language)));
+
+			ScanKeyInit(&key[nkeys++],
+						Anum_pg_proc_prolang,
+						BTEqualStrategyNumber, F_OIDEQ,
+						ObjectIdGetDatum(languageId));
+
+			ReleaseSysCache(langtup);
+		}
+		else if (strcmp(defel->defname, "schema") == 0)
+		{
+			Oid namespaceId;
+
+			if (schema_opt)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("multiple %s clauses not allowed",
+								"IN SCHEMA")));
+			schema_opt = true;
+			namespaceId = LookupExplicitNamespace(defGetString(defel));
+			ScanKeyInit(&key[nkeys++],
+						Anum_pg_proc_pronamespace,
+						BTEqualStrategyNumber, F_OIDEQ,
+						ObjectIdGetDatum(namespaceId));
+
+		}
+		else if (strcmp(defel->defname, "owner") == 0)
+		{
+			Oid ownerId;
+
+			if (owner_opt)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("multiple %s clauses not allowed",
+								"FOR ROLE")));
+			owner_opt = true;
+
+			ownerId = get_role_oid(defGetString(defel), false);
+			ScanKeyInit(&key[nkeys++],
+						Anum_pg_proc_proowner,
+						BTEqualStrategyNumber, F_OIDEQ,
+						ObjectIdGetDatum(ownerId));
+		}
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("option \"%s\" not recognized", defel->defname)));
+	}
+
+	if (!schema_opt)
+		infoschema_oid = LookupExplicitNamespace("information_schema");
+
+	rel = heap_open(ProcedureRelationId, AccessShareLock);
+	scan = heap_beginscan(rel, SnapshotNow, nkeys, key);
+
+	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	{
+		Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(tup);
+		Oid		funcOid;
+		char   *procname;
+
+		/*
+		 * ignore pg_catalog and information_schema, unless explicitely
+		 * requested.
+		 */
+		if (schema_opt)
+		{
+			if (proc->pronamespace != namespaceId)
+				continue;
+		}
+		else if (proc->pronamespace == PG_CATALOG_NAMESPACE ||
+				 proc->pronamespace == infoschema_oid)
+			continue;
+
+		funcOid = HeapTupleGetOid(tup);
+		procname = format_procedure(funcOid);
+
+		/* some languages are not supported */
+		if (!language_opt)
+		{
+			Oid		prolang = proc->prolang;
+			HeapTuple	langtup;
+
+			/* skip C, internal or SQL */
+			if (prolang == INTERNALlanguageId)
+			{
+				ereport(DEBUG1,
+						(errmsg("skipping function %s -- uses language %s",
+								procname, "internal")));
+				pfree(procname);
+				continue;
+			}
+			else if (prolang == ClanguageId)
+			{
+				ereport(DEBUG1,
+						(errmsg("skipping function %s -- uses language %s",
+								procname, "C")));
+				pfree(procname);
+				continue;
+			}
+			else if (prolang == SQLlanguageId)
+			{
+				ereport(DEBUG1,
+						(errmsg("skipping function %s -- uses language %s",
+								procname, "SQL")));
+				pfree(procname);
+				continue;
+			}
+
+			langtup = SearchSysCache1(LANGOID, ObjectIdGetDatum(prolang));
+			lanForm = (Form_pg_language) GETSTRUCT(langtup);
+			lanchecker = lanForm->lanchecker;
+			if (!OidIsValid(lanchecker))
+			{
+				ereport(DEBUG1,
+						(errmsg("skipping function %s -- language %s does not have a checker",
+								procname, language)));
+				pfree(procname);
+				continue;
+			}
+		}
+
+
+		if (proc->prorettype == TRIGGEROID)
+		{
+			/* XXX why? */
+			ereport(NOTICE,
+					(errmsg("skipping function %s", procname),
+					 errdetail("This function is a trigger function.")));
+			pfree(procname);
+			continue;
+		}
+	
+		pfree(procname);
+		objects = lappend_oid(objects, funcOid);
+	}
+
+	heap_endscan(scan);
+	heap_close(rel, AccessShareLock);
+
+	return objects;
+}
+
+/*
+ * Transform a CHECK FUNCTION option list into a 2-D text array, to pass to the
+ * checker function.  If there are no options, return an empty array; this is
+ * necessary because the checker function might be strict.
+ *
+ * stop_on_first_error corresponds to the "fatal_errors" option; that one
+ * is passed as a separate argument to the checker function instead of
+ * being part of the options array.
+ */
+static ArrayType *
+process_options(List *options, bool *stop_on_first_error)
+{
+	ArrayType  *optarray;
+	ArrayBuildState *astate = NULL;
+	int			dims[2] = {0, 2};
+	int			lbs[2] = {1, 1};
+
+	if (options != NIL)
+	{
+		ListCell   *option;
+
+		/* complete dimensions */
+		dims[0] = list_length(options);
+
+		foreach(option, options)
+		{
+			DefElem    *defel = (DefElem *) lfirst(option);
+			char	   *option_name = defel->defname;
+			Datum		value;
+			bool		isnull;
+
+			/* this option is treated specially */
+			if (strcmp(option_name, "fatal_errors") == 0)
+			{
+				*stop_on_first_error = defGetBoolean(defel);
+				continue;
+			}
+
+			astate = accumArrayResult(astate,
+									  CStringGetTextDatum(option_name), false,
+									  TEXTOID,
+									  CurrentMemoryContext);
+
+			if (defel->arg != NULL)
+			{
+				value = CStringGetTextDatum(defGetString(defel));
+				isnull = false;
+			}
+			else
+			{
+				value = (Datum) 0;
+				isnull = true;
+			}
+
+			astate = accumArrayResult(astate,
+									  value, isnull,
+									  TEXTOID,  CurrentMemoryContext);
+		}
+	}
+
+	if (astate != NULL)
+		optarray = DatumGetArrayTypeP(makeMdArrayResult(astate, 2, dims, lbs,
+														CurrentMemoryContext,
+														true));
+	else
+		optarray = construct_empty_array(TEXTOID);
+
+	return optarray;
+}
+
+/*
+ * CheckFunction
+ *			Call a PL checker function, if it exists.
+ */
+void
+CheckFunction(CheckFunctionStmt *stmt, DestReceiver *dest)
+{
+	List	   *functionName = stmt->funcname;
+	List	   *argTypes = stmt->args;	/* list of TypeName nodes */
+	ArrayType  *options;
+	bool		stop_on_first_error = false;
+	TupOutputState *tstate;
+
+	tstate = begin_tup_output_tupdesc(dest, CheckFunctionResultDesc(stmt));
+	options = process_options(stmt->check_options, &stop_on_first_error);
+
+	if (stmt->funcname != NULL)
+	{
+		Oid	funcOid;
+
+		/* find a function */
+		funcOid = LookupFuncNameTypeNames(functionName, argTypes, false);
+
+		CheckFunctionById(funcOid, InvalidOid, options,
+						  stop_on_first_error, tstate);
+	}
+	else if (stmt->trgname != NULL)
+	{
+		Oid		relid;
+		Oid		funcOid;
+
+		/* find a trigger function */
+		relid = RangeVarGetRelid(stmt->relation, AccessShareLock, false);
+		funcOid = get_trigger_funcid(relid, stmt->trgname, false);
+
+		CheckFunctionById(funcOid, relid, options,
+						  stop_on_first_error, tstate);
+	}
+	else
+	{
+		List   *objects;
+		Oid		funcOid;
+
+		objects = CollectCheckedFunctions(stmt->options);
+		if (objects != NIL)
+		{
+			ListCell *object;
+			bool	isfirst = true;
+			bool	prev_error = false;
+
+			foreach(object, objects)
+			{
+				CHECK_FOR_INTERRUPTS();
+
+				/*
+				 * append an empty line when the previous one was an error, for
+				 * better readability.
+				 */
+				if (!isfirst && prev_error)
+					do_text_output_oneline(tstate, "");
+
+				funcOid = lfirst_oid(object);
+				if (!CheckFunctionById(funcOid, InvalidOid, options,
+									   stop_on_first_error, tstate))
+				{
+					prev_error = true;
+					if (stop_on_first_error)
+						break;
+				}
+				else
+					prev_error = false;
+
+				isfirst = false;
+			}
+		}
+		else
+			ereport(NOTICE,
+					(errmsg("nothing to check")));
+	}
+
+	end_tup_output(tstate);
+}
 
 /*
  * Rename function
@@ -1955,11 +2493,7 @@ ExecuteDoStmt(DoStmt *stmt)
 	/* Look up the language and validate permissions */
 	languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language));
 	if (!HeapTupleIsValid(languageTuple))
-		ereport(ERROR,
-				(errcode(ERRCODE_UNDEFINED_OBJECT),
-				 errmsg("language \"%s\" does not exist", language),
-				 (PLTemplateExists(language) ?
-				  errhint("Use CREATE LANGUAGE to load the language into the database.") : 0)));
+		report_missing_language(language);
 
 	codeblock->langOid = HeapTupleGetOid(languageTuple);
 	languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c
index 8d6a041..86054f1 100644
--- a/src/backend/commands/proclang.c
+++ b/src/backend/commands/proclang.c
@@ -46,12 +46,13 @@ typedef struct
 	char	   *tmplhandler;	/* name of handler function */
 	char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
 	char	   *tmplvalidator;	/* name of validator function, or NULL */
+	char	   *tmplchecker;	/* name of checker function, or NULL */
 	char	   *tmpllibrary;	/* path of shared library */
 } PLTemplate;
 
 static void create_proc_lang(const char *languageName, bool replace,
 				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
-				 Oid valOid, bool trusted);
+				 Oid valOid, Oid checkerOid, bool trusted);
 static PLTemplate *find_language_template(const char *languageName);
 static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
 							Oid newOwnerId);
@@ -67,9 +68,10 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
 	PLTemplate *pltemplate;
 	Oid			handlerOid,
 				inlineOid,
-				valOid;
+				valOid,
+				checkerOid;
 	Oid			funcrettype;
-	Oid			funcargtypes[1];
+	Oid			funcargtypes[4];
 
 	/*
 	 * If we have template information for the language, ignore the supplied
@@ -222,10 +224,90 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
 		else
 			valOid = InvalidOid;
 
+		/*
+		 * Likewise for the checker, if required; but we don't care about
+		 * its return type.
+		 */
+		if (pltemplate->tmplchecker)
+		{
+			funcname = SystemFuncName(pltemplate->tmplchecker);
+			funcargtypes[0] = REGPROCEDUREOID;
+			funcargtypes[1] = REGCLASSOID;
+			funcargtypes[2] = TEXTARRAYOID;
+			funcargtypes[3] = BOOLOID;
+
+			checkerOid = LookupFuncName(funcname, 4, funcargtypes, true);
+			if (!OidIsValid(checkerOid))
+			{
+				Datum	   allTypes[14];
+				Datum	   paramModes[14];
+				Datum	   paramNames[14];
+				ArrayType	*allParameterTypes;
+				ArrayType	*parameterModes;
+				ArrayType	*parameterNames;
+
+				Oid	   Types[14] = { REGPROCEDUREOID, REGCLASSOID, TEXTARRAYOID, BOOLOID,
+						    OIDOID, INT4OID, TEXTOID, TEXTOID,
+						    TEXTOID, TEXTOID, TEXTOID, TEXTOID,
+						    INT4OID, TEXTOID };
+
+				char	   Modes[14] = { FUNC_PARAM_IN, FUNC_PARAM_IN, FUNC_PARAM_IN, FUNC_PARAM_IN,
+						       FUNC_PARAM_OUT, FUNC_PARAM_OUT, FUNC_PARAM_OUT, FUNC_PARAM_OUT,
+						       FUNC_PARAM_OUT, FUNC_PARAM_OUT, FUNC_PARAM_OUT, FUNC_PARAM_OUT,
+						       FUNC_PARAM_OUT, FUNC_PARAM_OUT };
+
+				char	   *Names[14] = { "functionid", "tableid", "options", "fatal_errors",
+						       "functionid", "lineno", "statement", "sqlstate",
+						       "message", "detail", "hint", "level",
+						       "position", "query" };
+				int	i;
+
+				for (i = 0; i < 14; i++)
+				{
+					allTypes[i] = ObjectIdGetDatum(Types[i]);
+					paramModes[i] = CharGetDatum(Modes[i]);
+					paramNames[i] = CStringGetTextDatum(Names[i]);
+				}
+
+				allParameterTypes = construct_array(allTypes, 14, OIDOID,
+											 sizeof(Oid), true, 'i');
+				parameterModes = construct_array(paramModes, 14, CHAROID,
+											 1, true, 'c');
+				parameterNames = construct_array(paramNames, 14, TEXTOID,
+											 -1, false, 'i');
+
+				checkerOid = ProcedureCreate(pltemplate->tmplchecker,
+										 PG_CATALOG_NAMESPACE,
+										 false, /* replace */
+										 true, /* returnsSet */
+										 RECORDOID,
+										 ClanguageId,
+										 F_FMGR_C_VALIDATOR,
+										 pltemplate->tmplchecker,
+										 pltemplate->tmpllibrary,
+										 false, /* isAgg */
+										 false, /* isWindowFunc */
+										 false, /* security_definer */
+										 false,	/* isLeakProof */
+										 true,	/* isStrict */
+										 PROVOLATILE_VOLATILE,
+										 buildoidvector(funcargtypes, 4), /* parameterTypes */
+										 PointerGetDatum(allParameterTypes),
+										 PointerGetDatum(parameterModes),
+										 PointerGetDatum(parameterNames),
+										 NIL,
+										 PointerGetDatum(NULL),
+										 1,
+										 100);
+			}
+		}
+		else
+			checkerOid = InvalidOid;
+
 		/* ok, create it */
 		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
 						 handlerOid, inlineOid,
-						 valOid, pltemplate->tmpltrusted);
+						 valOid, checkerOid, pltemplate->tmpltrusted);
 	}
 	else
 	{
@@ -297,10 +379,22 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
 		else
 			valOid = InvalidOid;
 
+		/* validate the checker function */
+		if (stmt->plchecker)
+		{
+			funcargtypes[0] = OIDOID;
+			funcargtypes[1] = REGCLASSOID;
+			funcargtypes[2] = TEXTARRAYOID;
+			checkerOid = LookupFuncName(stmt->plchecker, 3, funcargtypes, false);
+			/* return value is ignored, so we don't check the type */
+		}
+		else
+			checkerOid = InvalidOid;
+
 		/* ok, create it */
 		create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
 						 handlerOid, inlineOid,
-						 valOid, stmt->pltrusted);
+						 valOid, checkerOid, stmt->pltrusted);
 	}
 }
 
@@ -310,7 +404,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
 static void
 create_proc_lang(const char *languageName, bool replace,
 				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
-				 Oid valOid, bool trusted)
+				 Oid valOid, Oid checkerOid, bool trusted)
 {
 	Relation	rel;
 	TupleDesc	tupDesc;
@@ -340,6 +434,7 @@ create_proc_lang(const char *languageName, bool replace,
 	values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid);
 	values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid);
 	values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid);
+	values[Anum_pg_language_lanchecker - 1] = ObjectIdGetDatum(checkerOid);
 	nulls[Anum_pg_language_lanacl - 1] = true;
 
 	/* Check for pre-existing definition */
@@ -426,6 +521,15 @@ create_proc_lang(const char *languageName, bool replace,
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
+	/* dependency on the checker function, if any */
+	if (OidIsValid(checkerOid))
+	{
+		referenced.classId = ProcedureRelationId;
+		referenced.objectId = checkerOid;
+		referenced.objectSubId = 0;
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
 	/* Post creation hook for new procedural language */
 	InvokeObjectAccessHook(OAT_POST_CREATE,
 						   LanguageRelationId, myself.objectId, 0);
@@ -481,6 +585,11 @@ find_language_template(const char *languageName)
 		if (!isnull)
 			result->tmplvalidator = TextDatumGetCString(datum);
 
+		datum = heap_getattr(tup, Anum_pg_pltemplate_tmplchecker,
+							 RelationGetDescr(rel), &isnull);
+		if (!isnull)
+			result->tmplchecker = TextDatumGetCString(datum);
+
 		datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary,
 							 RelationGetDescr(rel), &isnull);
 		if (!isnull)
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index caae2da..fdd4539 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -4658,3 +4658,50 @@ pg_trigger_depth(PG_FUNCTION_ARGS)
 {
 	PG_RETURN_INT32(MyTriggerDepth);
 }
+
+/*
+ * get_trigger_procoid - returns a oid of trigger functions
+ */
+Oid 
+get_trigger_funcid(Oid relid, const char *tgname, bool missing_ok)
+{
+	Oid	funcOid;
+	HeapTuple	ht_trig;
+	Form_pg_trigger trigrec;
+	ScanKeyData skey[1];
+	Relation	tgrel;
+	SysScanDesc tgscan;
+	Oid	trgOid;
+
+	trgOid = get_trigger_oid(relid, tgname, missing_ok);
+
+	/*
+	 * Fetch the pg_trigger tuple by the Oid of the trigger
+	 */
+	tgrel = heap_open(TriggerRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey[0],
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(trgOid));
+
+	tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true,
+								SnapshotNow, 1, skey);
+
+	ht_trig = systable_getnext(tgscan);
+
+	if (!HeapTupleIsValid(ht_trig))
+		elog(ERROR, "could not find tuple for trigger %u", trgOid);
+
+	trigrec = (Form_pg_trigger) GETSTRUCT(ht_trig);
+
+	/* we need to know trigger function to get PL checker function */
+	funcOid = trigrec->tgfoid;
+
+	/* Clean up */
+	systable_endscan(tgscan);
+
+	heap_close(tgrel, AccessShareLock);
+
+	return funcOid;
+}
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index e755e7c..c1caf2e 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -1135,7 +1135,7 @@ BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
  * Functions for sending tuples to the frontend (or other specified destination)
  * as though it is a SELECT result. These are used by utility commands that
  * need to project directly to the destination and don't need or want full
- * table function capability. Currently used by EXPLAIN and SHOW ALL.
+ * table function capability. Currently used by EXPLAIN, SHOW ALL, CHECK FUNCTION.
  */
 TupOutputState *
 begin_tup_output_tupdesc(DestReceiver *dest, TupleDesc tupdesc)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7fec4db..f71b2fd 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2884,6 +2884,22 @@ _copyAlterFunctionStmt(const AlterFunctionStmt *from)
 	return newnode;
 }
 
+static CheckFunctionStmt *
+_copyCheckFunctionStmt(const CheckFunctionStmt *from)
+{
+	CheckFunctionStmt *newnode = makeNode(CheckFunctionStmt);
+
+	COPY_NODE_FIELD(funcname);
+	COPY_NODE_FIELD(args);
+	COPY_STRING_FIELD(trgname);
+	COPY_NODE_FIELD(relation);
+	COPY_SCALAR_FIELD(is_function);
+	COPY_NODE_FIELD(options);
+	COPY_NODE_FIELD(check_options);
+
+	return newnode;
+}
+
 static DoStmt *
 _copyDoStmt(const DoStmt *from)
 {
@@ -4172,6 +4188,9 @@ copyObject(const void *from)
 		case T_AlterFunctionStmt:
 			retval = _copyAlterFunctionStmt(from);
 			break;
+		case T_CheckFunctionStmt:
+			retval = _copyCheckFunctionStmt(from);
+			break;
 		case T_DoStmt:
 			retval = _copyDoStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index d2a79eb..11553f3 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1294,6 +1294,20 @@ _equalAlterFunctionStmt(const AlterFunctionStmt *a, const AlterFunctionStmt *b)
 }
 
 static bool
+_equalCheckFunctionStmt(const CheckFunctionStmt *a, const CheckFunctionStmt *b)
+{
+	COMPARE_NODE_FIELD(funcname);
+	COMPARE_NODE_FIELD(args);
+	COMPARE_STRING_FIELD(trgname);
+	COMPARE_NODE_FIELD(relation);
+	COMPARE_SCALAR_FIELD(is_function);
+	COMPARE_NODE_FIELD(options);
+	COMPARE_NODE_FIELD(check_options);
+
+	return true;
+}
+
+static bool
 _equalDoStmt(const DoStmt *a, const DoStmt *b)
 {
 	COMPARE_NODE_FIELD(args);
@@ -2715,6 +2729,9 @@ equal(const void *a, const void *b)
 		case T_AlterFunctionStmt:
 			retval = _equalAlterFunctionStmt(a, b);
 			break;
+		case T_CheckFunctionStmt:
+			retval = _equalCheckFunctionStmt(a, b);
+			break;
 		case T_DoStmt:
 			retval = _equalDoStmt(a, b);
 			break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d1ce2ab..f84876d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -227,6 +227,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
 		DeallocateStmt PrepareStmt ExecuteStmt
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
+		CheckFunctionStmt
 
 %type <node>	select_no_parens select_with_parens select_clause
 				simple_select values_clause
@@ -276,7 +277,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
 
 %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
 				opt_class opt_inline_handler opt_validator validator_clause
-				opt_collate
+				opt_collate opt_checker
 
 %type <range>	qualified_name OptConstrFromTable
 
@@ -463,6 +464,8 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
 %type <windef>	window_definition over_clause window_specification
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
+%type <defelt>	CheckFunctionOptElem
+%type <list>	CheckFunctionOpts OptCheckFunctionOpts opt_check_options
 
 
 /*
@@ -700,6 +703,7 @@ stmt :
 			| AlterUserSetStmt
 			| AlterUserStmt
 			| AnalyzeStmt
+			| CheckFunctionStmt
 			| CheckPointStmt
 			| ClosePortalStmt
 			| ClusterStmt
@@ -3226,11 +3230,12 @@ CreatePLangStmt:
 				n->plhandler = NIL;
 				n->plinline = NIL;
 				n->plvalidator = NIL;
+				n->plchecker = NIL;
 				n->pltrusted = false;
 				$$ = (Node *)n;
 			}
 			| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
-			  HANDLER handler_name opt_inline_handler opt_validator
+			  HANDLER handler_name opt_inline_handler opt_validator opt_checker
 			{
 				CreatePLangStmt *n = makeNode(CreatePLangStmt);
 				n->replace = $2;
@@ -3238,6 +3243,7 @@ CreatePLangStmt:
 				n->plhandler = $8;
 				n->plinline = $9;
 				n->plvalidator = $10;
+				n->plchecker = $11;
 				n->pltrusted = $3;
 				$$ = (Node *)n;
 			}
@@ -3272,6 +3278,11 @@ opt_validator:
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+opt_checker:
+			CHECK handler_name					{ $$ = $2; }
+			| /*EMPTY*/								{ $$ = NIL; }
+		;
+
 DropPLangStmt:
 			DROP opt_procedural LANGUAGE ColId_or_Sconst opt_drop_behavior
 				{
@@ -6337,6 +6348,99 @@ any_operator:
 
 /*****************************************************************************
  *
+ *		CHECK FUNCTION funcname(args)
+ *		CHECK TRIGGER triggername ON table
+ *
+ *
+ *****************************************************************************/
+
+
+CheckFunctionStmt:
+			CHECK FUNCTION func_name func_args opt_check_options
+				{
+					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+					n->funcname = $3;
+					n->args = extractArgTypes($4);
+					n->trgname = NULL;
+					n->relation = NULL;
+					n->is_function = true;
+					n->options = NIL;
+					n->check_options = $5;
+					$$ = (Node *) n;
+				}
+			| CHECK TRIGGER name ON qualified_name opt_check_options
+				{
+					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+					n->funcname = NULL;
+					n->args = NIL;
+					n->trgname = $3;
+					n->relation = $5;
+					n->is_function = false;
+					n->options = NIL;
+					n->check_options = $6;
+					$$ = (Node *) n;
+				}
+			| CHECK FUNCTION ALL OptCheckFunctionOpts opt_check_options
+				{
+					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+					n->funcname = NULL;
+					n->args = NIL;
+					n->trgname = NULL;
+					n->relation = NULL;
+					n->is_function = true;
+					n->options = $4;
+					n->check_options = $5;
+					$$ = (Node *) n;
+				}
+		;
+
+OptCheckFunctionOpts:
+			CheckFunctionOpts
+				{
+					$$ = $1;
+				}
+			| /* EMPTY */
+				{
+					$$ = NIL;
+				}
+		;
+
+CheckFunctionOpts:
+			CheckFunctionOptElem					{ $$ = list_make1($1); }
+			| CheckFunctionOpts CheckFunctionOptElem	{ $$ = lappend($1, $2); }
+		;
+
+CheckFunctionOptElem:
+			IN_P LANGUAGE ColId
+				{
+					$$ = makeDefElem("language",
+								    (Node *) makeString($3));
+				}
+			| IN_P SCHEMA ColId
+				{
+					$$ = makeDefElem("schema",
+								    (Node *) makeString($3));
+				}
+			| FOR ROLE ColId
+				{
+					$$ = makeDefElem("owner",
+								    (Node *) makeString($3));
+				}
+		;
+
+opt_check_options:
+			WITH '(' explain_option_list ')'
+				{
+					$$ = $3;
+				}
+			| /* EMPTY */
+				{
+					$$ = NIL;
+				}
+		;
+
+/*****************************************************************************
+ *
  *		DO <anonymous code block> [ LANGUAGE language ]
  *
  * We use a DefElem list for future extensibility, and to allow flexibility
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 5b81c0b..37f0709 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -901,6 +901,10 @@ standard_ProcessUtility(Node *parsetree,
 			AlterFunction((AlterFunctionStmt *) parsetree);
 			break;
 
+		case T_CheckFunctionStmt:
+			CheckFunction((CheckFunctionStmt *) parsetree, dest);
+			break;
+
 		case T_IndexStmt:		/* CREATE INDEX */
 			{
 				IndexStmt  *stmt = (IndexStmt *) parsetree;
@@ -1243,6 +1247,9 @@ UtilityReturnsTuples(Node *parsetree)
 		case T_ExplainStmt:
 			return true;
 
+		case T_CheckFunctionStmt:
+			return true;
+
 		case T_VariableShowStmt:
 			return true;
 
@@ -1293,6 +1300,9 @@ UtilityTupleDescriptor(Node *parsetree)
 		case T_ExplainStmt:
 			return ExplainResultDesc((ExplainStmt *) parsetree);
 
+		case T_CheckFunctionStmt:
+			return CheckFunctionResultDesc((CheckFunctionStmt *) parsetree);
+
 		case T_VariableShowStmt:
 			{
 				VariableShowStmt *n = (VariableShowStmt *) parsetree;
@@ -2144,6 +2154,13 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_CheckFunctionStmt:
+			if (((CheckFunctionStmt *) parsetree)->is_function)
+				tag = "CHECK FUNCTION";
+			else
+				tag = "CHECK TRIGGER";
+			break;
+
 		default:
 			elog(WARNING, "unrecognized node type: %d",
 				 (int) nodeTag(parsetree));
@@ -2584,6 +2601,10 @@ GetCommandLogLevel(Node *parsetree)
 			}
 			break;
 
+		case T_CheckFunctionStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 		default:
 			elog(WARNING, "unrecognized node type: %d",
 				 (int) nodeTag(parsetree));
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d845c90..3e2b7bf 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5306,13 +5306,26 @@ getProcLangs(Archive *fout, int *numProcLangs)
 	int			i_lanplcallfoid;
 	int			i_laninline;
 	int			i_lanvalidator;
+	int			i_lanchecker;
 	int			i_lanacl;
 	int			i_lanowner;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
 
-	if (fout->remoteVersion >= 90000)
+	if (fout->remoteVersion >= 90200)
+	{
+		/* pg_language has a lanchecker column */
+		appendPQExpBuffer(query, "SELECT tableoid, oid, "
+						  "lanname, lanpltrusted, lanplcallfoid, "
+						  "laninline, lanvalidator, lanchecker, lanacl, "
+						  "(%s lanowner) AS lanowner "
+						  "FROM pg_language "
+						  "WHERE lanispl "
+						  "ORDER BY oid",
+						  username_subquery);
+	}
+	else if (fout->remoteVersion >= 90000)
 	{
 		/* pg_language has a laninline column */
 		appendPQExpBuffer(query, "SELECT tableoid, oid, "
@@ -5388,6 +5401,7 @@ getProcLangs(Archive *fout, int *numProcLangs)
 	/* these may fail and return -1: */
 	i_laninline = PQfnumber(res, "laninline");
 	i_lanvalidator = PQfnumber(res, "lanvalidator");
+	i_lanchecker = PQfnumber(res, "lanchecker");
 	i_lanacl = PQfnumber(res, "lanacl");
 	i_lanowner = PQfnumber(res, "lanowner");
 
@@ -5401,6 +5415,10 @@ getProcLangs(Archive *fout, int *numProcLangs)
 		planginfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_lanname));
 		planginfo[i].lanpltrusted = *(PQgetvalue(res, i, i_lanpltrusted)) == 't';
 		planginfo[i].lanplcallfoid = atooid(PQgetvalue(res, i, i_lanplcallfoid));
+		if (i_lanchecker >= 0)
+			planginfo[i].lanchecker = atooid(PQgetvalue(res, i, i_lanchecker));
+		else
+			planginfo[i].lanchecker = InvalidOid;
 		if (i_laninline >= 0)
 			planginfo[i].laninline = atooid(PQgetvalue(res, i, i_laninline));
 		else
@@ -8621,6 +8639,7 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
 	char	   *qlanname;
 	char	   *lanschema;
 	FuncInfo   *funcInfo;
+	FuncInfo   *checkerInfo = NULL;
 	FuncInfo   *inlineInfo = NULL;
 	FuncInfo   *validatorInfo = NULL;
 
@@ -8640,6 +8659,13 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
 	if (funcInfo != NULL && !funcInfo->dobj.dump)
 		funcInfo = NULL;		/* treat not-dumped same as not-found */
 
+	if (OidIsValid(plang->lanchecker))
+	{
+		checkerInfo = findFuncByOid(plang->lanchecker);
+		if (checkerInfo != NULL && !checkerInfo->dobj.dump)
+			checkerInfo = NULL;
+	}
+
 	if (OidIsValid(plang->laninline))
 	{
 		inlineInfo = findFuncByOid(plang->laninline);
@@ -8666,6 +8692,7 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
 	 * don't, this might not work terribly nicely.
 	 */
 	useParams = (funcInfo != NULL &&
+				 (checkerInfo != NULL || !OidIsValid(plang->lanchecker)) &&
 				 (inlineInfo != NULL || !OidIsValid(plang->laninline)) &&
 				 (validatorInfo != NULL || !OidIsValid(plang->lanvalidator)));
 
@@ -8721,6 +8748,16 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
 			appendPQExpBuffer(defqry, "%s",
 							  fmtId(validatorInfo->dobj.name));
 		}
+		if (OidIsValid(plang->lanchecker))
+		{
+			appendPQExpBuffer(defqry, " CHECK ");
+			/* Cope with possibility that checker is in different schema */
+			if (checkerInfo->dobj.namespace != funcInfo->dobj.namespace)
+				appendPQExpBuffer(defqry, "%s.",
+							   fmtId(checkerInfo->dobj.namespace->dobj.name));
+			appendPQExpBuffer(defqry, "%s",
+							  fmtId(checkerInfo->dobj.name));
+		}
 	}
 	else
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 28a7fe4..d95c4d9 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -381,6 +381,7 @@ typedef struct _procLangInfo
 	Oid			lanplcallfoid;
 	Oid			laninline;
 	Oid			lanvalidator;
+	Oid			lanchecker;
 	char	   *lanacl;
 	char	   *lanowner;		/* name of owner, or empty string */
 } ProcLangInfo;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 6f481bb..c2758a7 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1,4 +1,5 @@
 /*
+ *
  * psql - the PostgreSQL interactive terminal
  *
  * Copyright (c) 2000-2012, PostgreSQL Global Development Group
@@ -739,7 +740,7 @@ psql_completion(char *text, int start, int end)
 #define prev6_wd  (previous_words[5])
 
 	static const char *const sql_commands[] = {
-		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
+		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECK", "CHECKPOINT", "CLOSE", "CLUSTER",
 		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
 		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
 		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
@@ -1536,6 +1537,28 @@ psql_completion(char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TRANS);
 	}
+
+/* CHECK */
+	else if (pg_strcasecmp(prev_wd, "CHECK") == 0)
+	{
+		static const char *const list_CHECK[] =
+		{"FUNCTION", "TRIGGER", NULL};
+
+		COMPLETE_WITH_LIST(list_CHECK);
+	}
+	else if (pg_strcasecmp(prev3_wd, "CHECK") == 0 &&
+			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+	{
+		COMPLETE_WITH_CONST("ON");
+	}
+	else if (pg_strcasecmp(prev4_wd, "CHECK") == 0 &&
+			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
+			 pg_strcasecmp(prev_wd, "ON") == 0)
+	{
+		completion_info_charp = prev2_wd;
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+	}
+
 /* CLUSTER */
 
 	/*
diff --git a/src/include/catalog/pg_language.h b/src/include/catalog/pg_language.h
index eb4ae5a..ac2faa7 100644
--- a/src/include/catalog/pg_language.h
+++ b/src/include/catalog/pg_language.h
@@ -37,6 +37,7 @@ CATALOG(pg_language,2612)
 	Oid			lanplcallfoid;	/* Call handler for PL */
 	Oid			laninline;		/* Optional anonymous-block handler function */
 	Oid			lanvalidator;	/* Optional validation function */
+	Oid			lanchecker;	    /* Optional checker function */
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	aclitem		lanacl[1];		/* Access privileges */
 #endif
@@ -53,7 +54,7 @@ typedef FormData_pg_language *Form_pg_language;
  *		compiler constants for pg_language
  * ----------------
  */
-#define Natts_pg_language				8
+#define Natts_pg_language				9
 #define Anum_pg_language_lanname		1
 #define Anum_pg_language_lanowner		2
 #define Anum_pg_language_lanispl		3
@@ -61,20 +62,21 @@ typedef FormData_pg_language *Form_pg_language;
 #define Anum_pg_language_lanplcallfoid	5
 #define Anum_pg_language_laninline		6
 #define Anum_pg_language_lanvalidator	7
-#define Anum_pg_language_lanacl			8
+#define Anum_pg_language_lanchecker	    8
+#define Anum_pg_language_lanacl			9
 
 /* ----------------
  *		initial contents of pg_language
  * ----------------
  */
 
-DATA(insert OID = 12 ( "internal"	PGUID f f 0 0 2246 _null_ ));
+DATA(insert OID = 12 ( "internal"	PGUID f f 0 0 2246 0 _null_ ));
 DESCR("built-in functions");
 #define INTERNALlanguageId 12
-DATA(insert OID = 13 ( "c"			PGUID f f 0 0 2247 _null_ ));
+DATA(insert OID = 13 ( "c"			PGUID f f 0 0 2247 0 _null_ ));
 DESCR("dynamically-loaded C functions");
 #define ClanguageId 13
-DATA(insert OID = 14 ( "sql"		PGUID f t 0 0 2248 _null_ ));
+DATA(insert OID = 14 ( "sql"		PGUID f t 0 0 2248 0 _null_ ));
 DESCR("SQL-language functions");
 #define SQLlanguageId 14
 
diff --git a/src/include/catalog/pg_pltemplate.h b/src/include/catalog/pg_pltemplate.h
index 00abd53..62ce2de 100644
--- a/src/include/catalog/pg_pltemplate.h
+++ b/src/include/catalog/pg_pltemplate.h
@@ -37,6 +37,7 @@ CATALOG(pg_pltemplate,1136) BKI_SHARED_RELATION BKI_WITHOUT_OIDS
 	text		tmplhandler;	/* name of call handler function */
 	text		tmplinline;		/* name of anonymous-block handler, or NULL */
 	text		tmplvalidator;	/* name of validator function, or NULL */
+	text		tmplchecker;	/* name of checker function, or NULL */
 	text		tmpllibrary;	/* path of shared library */
 	aclitem		tmplacl[1];		/* access privileges for template */
 #endif
@@ -53,15 +54,16 @@ typedef FormData_pg_pltemplate *Form_pg_pltemplate;
  *		compiler constants for pg_pltemplate
  * ----------------
  */
-#define Natts_pg_pltemplate					8
+#define Natts_pg_pltemplate					9
 #define Anum_pg_pltemplate_tmplname			1
 #define Anum_pg_pltemplate_tmpltrusted		2
 #define Anum_pg_pltemplate_tmpldbacreate	3
 #define Anum_pg_pltemplate_tmplhandler		4
 #define Anum_pg_pltemplate_tmplinline		5
 #define Anum_pg_pltemplate_tmplvalidator	6
-#define Anum_pg_pltemplate_tmpllibrary		7
-#define Anum_pg_pltemplate_tmplacl			8
+#define Anum_pg_pltemplate_tmplchecker		7
+#define Anum_pg_pltemplate_tmpllibrary		8
+#define Anum_pg_pltemplate_tmplacl			9
 
 
 /* ----------------
@@ -69,13 +71,13 @@ typedef FormData_pg_pltemplate *Form_pg_pltemplate;
  * ----------------
  */
 
-DATA(insert ( "plpgsql"		t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "$libdir/plpgsql" _null_ ));
-DATA(insert ( "pltcl"		t t "pltcl_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
-DATA(insert ( "pltclu"		f f "pltclu_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
-DATA(insert ( "plperl"		t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" "$libdir/plperl" _null_ ));
-DATA(insert ( "plperlu"		f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" "$libdir/plperl" _null_ ));
-DATA(insert ( "plpythonu"	f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" "$libdir/plpython2" _null_ ));
-DATA(insert ( "plpython2u"	f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" "$libdir/plpython2" _null_ ));
-DATA(insert ( "plpython3u"	f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" "$libdir/plpython3" _null_ ));
+DATA(insert ( "plpgsql"		t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" _null_ "$libdir/plpgsql" _null_ ));
+DATA(insert ( "pltcl"		t t "pltcl_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
+DATA(insert ( "pltclu"		f f "pltclu_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
+DATA(insert ( "plperl"		t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" _null_ "$libdir/plperl" _null_ ));
+DATA(insert ( "plperlu"		f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" _null_ "$libdir/plperl" _null_ ));
+DATA(insert ( "plpythonu"	f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" _null_ "$libdir/plpython2" _null_ ));
+DATA(insert ( "plpython2u"	f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" _null_ "$libdir/plpython2" _null_ ));
+DATA(insert ( "plpython3u"	f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" _null_ "$libdir/plpython3" _null_ ));
 
 #endif   /* PG_PLTEMPLATE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 163b2ea..4ba192c 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -15,6 +15,7 @@
 #define DEFREM_H
 
 #include "nodes/parsenodes.h"
+#include "tcop/dest.h"
 
 /* commands/dropcmds.c */
 extern void RemoveObjects(DropStmt *stmt);
@@ -62,6 +63,8 @@ extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
 /* commands/functioncmds.c */
 extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
 extern void RemoveFunctionById(Oid funcOid);
+extern void CheckFunction(CheckFunctionStmt *stmt, DestReceiver *dest);
+extern TupleDesc CheckFunctionResultDesc(CheckFunctionStmt *stmt); 
 extern void SetFunctionReturnType(Oid funcOid, Oid newRetType);
 extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType);
 extern void RenameFunction(List *name, List *argtypes, const char *newname);
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 9303341..10d89d0 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -207,4 +207,6 @@ extern int	RI_FKey_trigger_type(Oid tgfoid);
 
 extern Datum pg_trigger_depth(PG_FUNCTION_ARGS);
 
+extern Oid get_trigger_funcid(Oid relid, const char *tgname, bool missing_ok);
+
 #endif   /* TRIGGER_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 0e7d184..cbafc8e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -290,6 +290,7 @@ typedef enum NodeTag
 	T_IndexStmt,
 	T_CreateFunctionStmt,
 	T_AlterFunctionStmt,
+	T_CheckFunctionStmt,
 	T_DoStmt,
 	T_RenameStmt,
 	T_RuleStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ab55639..4c295a0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1741,6 +1741,7 @@ typedef struct CreatePLangStmt
 	List	   *plhandler;		/* PL call handler function (qual. name) */
 	List	   *plinline;		/* optional inline function (qual. name) */
 	List	   *plvalidator;	/* optional validator function (qual. name) */
+	List	   *plchecker;		/* optional checker function (qual. name) */
 	bool		pltrusted;		/* PL is trusted */
 } CreatePLangStmt;
 
@@ -2085,6 +2086,22 @@ typedef struct AlterFunctionStmt
 } AlterFunctionStmt;
 
 /* ----------------------
+ *		Check {Function|Trigger} Statement
+ * ----------------------
+ */
+typedef struct CheckFunctionStmt
+{
+	NodeTag		type;
+	List	   *funcname;			/* qualified name of checked object */
+	List	   *args;			/* types of the arguments */
+	char	   *trgname;			/* trigger's name */
+	RangeVar	*relation;		/* trigger's relation */
+	bool	   is_function;		/* true for CHECK FUNCTION statement */
+	List	   *options;			/* other DefElem options */
+	List	   *check_options;		/* options for check function */
+} CheckFunctionStmt;
+
+/* ----------------------
  *		DO Statement
  *
  * DoStmt is the raw parser output, InlineCodeBlock is the execution-time API
#73Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#71)
Re: review: CHECK FUNCTION statement

Hello

2012/3/1 Alvaro Herrera <alvherre@commandprompt.com>:

Why does CollectCheckedFunctions skip trigger functions?  My only guess
is that at one point the checker was not supposed to know how to check
them, and a later version learned about it and this bit wasn't updated;
but maybe there's another reason?

you cannot to check trigger function without assigned relation -
TupleDescription should be assigned to NEW and OLD variables.

Regards

Pavel

Show quoted text

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#74Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#72)
Re: review: CHECK FUNCTION statement

2012/3/2 Alvaro Herrera <alvherre@commandprompt.com>:

I've cleaned up the backend code a bit -- see attached.  More yet to go
through; I'm mainly sending it out for you (and everyone, really) to
give your opinion on my changes so far.

(I split out the plpgsql checker for the time being into a separate
branch; I'll get on it after I get this part committed.)

it looks well

Regards

Pavel Stěhule

Show quoted text

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#75Alvaro Herrera
alvherre@commandprompt.com
In reply to: Pavel Stehule (#73)
Re: review: CHECK FUNCTION statement

Excerpts from Pavel Stehule's message of vie mar 02 05:29:26 -0300 2012:

you cannot to check trigger function without assigned relation -
TupleDescription should be assigned to NEW and OLD variables.

Oh, I see, that makes sense.

After mulling over this a bit, I'm dubious about having two separate
commands, one which checks triggers and another that checks non-trigger
functions. Wouldn't it make more sense to have some options into CHECK
FUNCTION so that it receives the trigger and corresponding relation name
to check? For example "check function foo() trigger on tab" or
something like that?

I also wonder if it would make sense to have grammar for "check all
triggers on table xyz" or some such, and even "check all triggers on all
functions".

Another thing is that "CHECK FUNCTION ALL FOR ROLE foo" seems a bit
strange to me. What about "CHECK FUNCTION ALL OWNED BY foo" instead?
("CHECK FUNCTION ALL" seems strange as a whole, but I'm not sure that we
can improve that ... still, if anyone has ideas I'm sure we can discuss)

As a reminder: we also have
CHECK FUNCTION ALL IN SCHEMA f
and
CHECK FUNCTION ALL IN LANGUAGE f
(and combinations thereof)

Thoughts?

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#76Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#75)
Re: review: CHECK FUNCTION statement

Hello

2012/3/2 Alvaro Herrera <alvherre@commandprompt.com>:

Excerpts from Pavel Stehule's message of vie mar 02 05:29:26 -0300 2012:

you cannot to check trigger function without assigned relation -
TupleDescription should be assigned to NEW and OLD variables.

Oh, I see, that makes sense.

After mulling over this a bit, I'm dubious about having two separate
commands, one which checks triggers and another that checks non-trigger
functions.  Wouldn't it make more sense to have some options into CHECK
FUNCTION so that it receives the trigger and corresponding relation name
to check?  For example "check function foo() trigger on tab" or
something like that?

I don't like it - "check trigger" is simple - and consistent

I also wonder if it would make sense to have grammar for "check all
triggers on table xyz" or some such, and even "check all triggers on all
functions".

this is good idea - and I like it

CHECK TRIGGER ALL ON TABLE X

there are possible some combination like CHECK TRIGGER ALL IN SCHEMA ...;

and similar. But I am not sure, if this is necessary - for some more
complex usage developer can use a direct PL check function.

Another thing is that "CHECK FUNCTION ALL FOR ROLE foo" seems a bit
strange to me.  What about "CHECK FUNCTION ALL OWNED BY foo" instead?
("CHECK FUNCTION ALL" seems strange as a whole, but I'm not sure that we
can improve that ... still, if anyone has ideas I'm sure we can discuss)

this should not be nice from language view, but it doesn't need new keywords

pattern FOR ROLE, IN SCHEMA are used

ALTER DEFAULT PRIVILEGES FOR ROLE

but OWNED BY is used too (SeqOptElem:)

I agree so OWNED BY sounds better (can be changed)

As a reminder: we also have
CHECK FUNCTION ALL IN SCHEMA f

it is ok

and
CHECK FUNCTION ALL IN LANGUAGE f
(and combinations thereof)

it is ok too

Regards

Pavel

Show quoted text

Thoughts?

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#77Alvaro Herrera
alvherre@commandprompt.com
In reply to: Pavel Stehule (#63)
Re: review: CHECK FUNCTION statement

Excerpts from Pavel Stehule's message of mar feb 28 16:30:58 -0300 2012:

Hello

Dne 28. února 2012 17:48 Alvaro Herrera <alvherre@commandprompt.com> napsal(a):

I have a few comments about this patch:

I didn't like the fact that the checker calling infrastructure uses
SPI instead of just a FunctionCallN to call the checker function.  I
think this should be easily avoidable.

It is not possible - or it has not simple solution (I don't how to do
it). PLpgSQL_checker is SRF function. SPI is used for processing
returned resultset. I looked to pg source code, and I didn't find any
other pattern than using SPI for SRF function call. It is probably
possible, but it means some code duplication too. I invite any ideas.

It wasn't all that difficult -- see below. While at this, I have a
question: how attached you are to the current return format for CHECK
FUNCTION?

check function f1();
CHECK FUNCTION
-------------------------------------------------------------
In function: 'f1()'
error:42804:5:assignment:subscripted object is not an array
(2 rows)

It seems to me that it'd be trivial to make it look like this instead:

check function f1();
function | lineno | statement | sqlstate | message | detail | hint | level | position | query
---------+--------+------------+----------+------------------------------------+--------+------+-------+----------+-------
f1() | 5 | assignment | 42804 | subscripted object is not an array | | | error | |
(1 row)

This looks much nicer to me.

One thing we lose is the caret marking the position of the error -- but
I'm wondering if that really works well. I didn't test it but from the
code it looks to me like it'd misbehave if you had a multiline statement.

Opinions?

/*
* Search and execute the checker function.
*
* returns true, when checked function is valid
*/
static bool
CheckFunctionById(Oid funcOid, Oid relid, ArrayType *options,
bool fatal_errors, TupOutputState *tstate)
{
HeapTuple tup;
Form_pg_proc proc;
HeapTuple languageTuple;
Form_pg_language lanForm;
Oid languageChecker;
char *funcname;
int result;

tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
if (!HeapTupleIsValid(tup)) /* should not happen */
elog(ERROR, "cache lookup failed for function %u", funcOid);

proc = (Form_pg_proc) GETSTRUCT(tup);

languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
Assert(HeapTupleIsValid(languageTuple));

lanForm = (Form_pg_language) GETSTRUCT(languageTuple);
languageChecker = lanForm->lanchecker;

funcname = format_procedure(funcOid);

/* We're all set to call the checker */
if (OidIsValid(languageChecker))
{
TupleDesc tupdesc;
Datum checkret;
FmgrInfo flinfo;
ReturnSetInfo rsinfo;
FunctionCallInfoData fcinfo;

/* create the tuple descriptor that the checker is supposed to return */
tupdesc = CreateTemplateTupleDesc(10, false);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "functionid", REGPROCOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "lineno", INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 3, "statement", TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 4, "sqlstate", TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 5, "message", TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 6, "detail", TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 7, "hint", TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 8, "level", TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 9, "position", INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 10, "query", TEXTOID, -1, 0);

fmgr_info(languageChecker, &flinfo);

rsinfo.type = T_ReturnSetInfo;
rsinfo.econtext = CreateStandaloneExprContext();
rsinfo.expectedDesc = tupdesc;
rsinfo.allowedModes = (int) SFRM_Materialize;
/* returnMode is set by the checker, hopefully ... */
/* isDone is not relevant, since not using ValuePerCall */
rsinfo.setResult = NULL;
rsinfo.setDesc = NULL;

InitFunctionCallInfoData(fcinfo, &flinfo, 4, InvalidOid, NULL, (Node *) &rsinfo);
fcinfo.arg[0] = ObjectIdGetDatum(funcOid);
fcinfo.arg[1] = ObjectIdGetDatum(relid);
fcinfo.arg[2] = PointerGetDatum(options);
fcinfo.arg[3] = BoolGetDatum(fatal_errors);
fcinfo.argnull[0] = false;
fcinfo.argnull[1] = false;
fcinfo.argnull[2] = false;
fcinfo.argnull[3] = false;

checkret = FunctionCallInvoke(&fcinfo);

if (rsinfo.returnMode != SFRM_Materialize)
elog(ERROR, "checker function didn't return a proper return set");
/* XXX we have to do some checking on rsinfo.isDone and checkret here */

if (rsinfo.setResult != NULL)
{
bool isnull;
StringInfoData str;
TupleTableSlot *slot = MakeSingleTupleTableSlot(tupdesc);

initStringInfo(&str);

while (tuplestore_gettupleslot(rsinfo.setResult, true, false, slot))
{
text *message = (text *) DatumGetPointer(slot_getattr(slot, 5, &isnull));

resetStringInfo(&str);

appendStringInfo(&str, "got a message: %s", text_to_cstring(message));
do_text_output_oneline(tstate, str.data);
}

pfree(str.data);
ExecDropSingleTupleTableSlot(slot);
}
}

pfree(funcname);

ReleaseSysCache(languageTuple);
ReleaseSysCache(tup);

return result;
}

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#78Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#77)
Re: review: CHECK FUNCTION statement

Hello

It wasn't all that difficult -- see below.  While at this, I have a
question: how attached you are to the current return format for CHECK
FUNCTION?

TupleDescr is created by language creator. This ensure exactly
expected format, because there are no possible registry check function
with other output tuple descriptor.

check function f1();
                      CHECK FUNCTION
-------------------------------------------------------------
 In function: 'f1()'
 error:42804:5:assignment:subscripted object is not an array
(2 rows)

It seems to me that it'd be trivial to make it look like this instead:

check function f1();
function | lineno | statement  | sqlstate |              message               | detail | hint | level | position | query
---------+--------+------------+----------+------------------------------------+--------+------+-------+----------+-------
f1()     |      5 | assignment | 42804    | subscripted object is not an array |        |      | error |          |
(1 row)

This looks much nicer to me.

I am strongly disagree.

1. This format is not consistent with other commands,
2. This format is difficult for copy/paste
3. THE ARE NOT CARET - this is really important
5. This form is bad for terminals - there are long rows, and with \x
outout, there are lot of "garbage" for multicommand
4. When you would to this form, you can to directly call SRF PL check
functions.

One thing we lose is the caret marking the position of the error -- but
I'm wondering if that really works well.  I didn't test it but from the
code it looks to me like it'd misbehave if you had a multiline statement.

Opinions?

-1

/*
 * Search and execute the checker function.
 *
 *   returns true, when checked function is valid
 */
static bool
CheckFunctionById(Oid funcOid, Oid relid, ArrayType *options,
                                 bool fatal_errors, TupOutputState *tstate)
{
       HeapTuple               tup;
       Form_pg_proc    proc;
       HeapTuple               languageTuple;
       Form_pg_language lanForm;
       Oid                             languageChecker;
       char               *funcname;
       int                             result;

       tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
       if (!HeapTupleIsValid(tup)) /* should not happen */
               elog(ERROR, "cache lookup failed for function %u", funcOid);

       proc = (Form_pg_proc) GETSTRUCT(tup);

       languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
       Assert(HeapTupleIsValid(languageTuple));

       lanForm = (Form_pg_language) GETSTRUCT(languageTuple);
       languageChecker = lanForm->lanchecker;

       funcname = format_procedure(funcOid);

       /* We're all set to call the checker */
       if (OidIsValid(languageChecker))
       {
               TupleDesc               tupdesc;
               Datum                   checkret;
               FmgrInfo                flinfo;
               ReturnSetInfo   rsinfo;
               FunctionCallInfoData fcinfo;

               /* create the tuple descriptor that the checker is supposed to return */
               tupdesc = CreateTemplateTupleDesc(10, false);
               TupleDescInitEntry(tupdesc, (AttrNumber) 1, "functionid", REGPROCOID, -1, 0);
               TupleDescInitEntry(tupdesc, (AttrNumber) 2, "lineno", INT4OID, -1, 0);
               TupleDescInitEntry(tupdesc, (AttrNumber) 3, "statement", TEXTOID, -1, 0);
               TupleDescInitEntry(tupdesc, (AttrNumber) 4, "sqlstate", TEXTOID, -1, 0);
               TupleDescInitEntry(tupdesc, (AttrNumber) 5, "message", TEXTOID, -1, 0);
               TupleDescInitEntry(tupdesc, (AttrNumber) 6, "detail", TEXTOID, -1, 0);
               TupleDescInitEntry(tupdesc, (AttrNumber) 7, "hint", TEXTOID, -1, 0);
               TupleDescInitEntry(tupdesc, (AttrNumber) 8, "level", TEXTOID, -1, 0);
               TupleDescInitEntry(tupdesc, (AttrNumber) 9, "position", INT4OID, -1, 0);
               TupleDescInitEntry(tupdesc, (AttrNumber) 10, "query", TEXTOID, -1, 0);

               fmgr_info(languageChecker, &flinfo);

               rsinfo.type = T_ReturnSetInfo;
               rsinfo.econtext = CreateStandaloneExprContext();
               rsinfo.expectedDesc = tupdesc;
               rsinfo.allowedModes = (int) SFRM_Materialize;
               /* returnMode is set by the checker, hopefully ... */
               /* isDone is not relevant, since not using ValuePerCall */
               rsinfo.setResult = NULL;
               rsinfo.setDesc = NULL;

               InitFunctionCallInfoData(fcinfo, &flinfo, 4, InvalidOid, NULL, (Node *) &rsinfo);
               fcinfo.arg[0] = ObjectIdGetDatum(funcOid);
               fcinfo.arg[1] = ObjectIdGetDatum(relid);
               fcinfo.arg[2] = PointerGetDatum(options);
               fcinfo.arg[3] = BoolGetDatum(fatal_errors);
               fcinfo.argnull[0] = false;
               fcinfo.argnull[1] = false;
               fcinfo.argnull[2] = false;
               fcinfo.argnull[3] = false;

               checkret = FunctionCallInvoke(&fcinfo);

               if (rsinfo.returnMode != SFRM_Materialize)
                       elog(ERROR, "checker function didn't return a proper return set");
               /* XXX we have to do some checking on rsinfo.isDone and checkret here */

               if (rsinfo.setResult != NULL)
               {
                       bool    isnull;
                       StringInfoData  str;
                       TupleTableSlot  *slot = MakeSingleTupleTableSlot(tupdesc);

                       initStringInfo(&str);

                       while (tuplestore_gettupleslot(rsinfo.setResult, true, false, slot))
                       {
                               text *message = (text *) DatumGetPointer(slot_getattr(slot, 5, &isnull));

                               resetStringInfo(&str);

                               appendStringInfo(&str, "got a message: %s", text_to_cstring(message));
                               do_text_output_oneline(tstate, str.data);
                       }

                       pfree(str.data);
                       ExecDropSingleTupleTableSlot(slot);
               }
       }

       pfree(funcname);

       ReleaseSysCache(languageTuple);
       ReleaseSysCache(tup);

       return result;
}

Without correct registration you cannot to call PL check function
directly simply. I don't thing so this is good price for removing a
few SPI lines. I don't understand why you don't like SPI. It is used
more times in code for similar purpose.

so from me -1

Regards

Pavel Stehule

Show quoted text

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#79Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#78)
Re: review: CHECK FUNCTION statement

2012/3/3 Pavel Stehule <pavel.stehule@gmail.com>:

Hello

It wasn't all that difficult -- see below.  While at this, I have a
question: how attached you are to the current return format for CHECK
FUNCTION?

TupleDescr is created by language creator. This ensure exactly
expected format, because there are no possible registry check function
with other output tuple descriptor.

check function f1();
                      CHECK FUNCTION
-------------------------------------------------------------
 In function: 'f1()'
 error:42804:5:assignment:subscripted object is not an array
(2 rows)

It seems to me that it'd be trivial to make it look like this instead:

check function f1();
function | lineno | statement  | sqlstate |              message               | detail | hint | level | position | query
---------+--------+------------+----------+------------------------------------+--------+------+-------+----------+-------
f1()     |      5 | assignment | 42804    | subscripted object is not an array |        |      | error |          |
(1 row)

This looks much nicer to me.

This was similar to first design - it is near to result of direct PL
check function call. But Tom and Albe had different opinion - check a
thread three months ago, and I had to agree with them - they proposed
better behave for using in psql console - and result is more similar
to usual output when exception is raised.

I am strongly disagree.

1. This format is not consistent with other commands,
2. This format is difficult for copy/paste
3. THE ARE NOT CARET - this is really important
5. This form is bad for terminals - there are long rows, and with \x
outout, there are lot of "garbage" for multicommand
4. When you would to this form, you can to directly call SRF PL check
functions.

One thing we lose is the caret marking the position of the error -- but
I'm wondering if that really works well.  I didn't test it but from the
code it looks to me like it'd misbehave if you had a multiline statement.

Opinions?

/*
 * Search and execute the checker function.
 *
 *   returns true, when checked function is valid
 */
static bool
CheckFunctionById(Oid funcOid, Oid relid, ArrayType *options,
                                 bool fatal_errors, TupOutputState *tstate)
{
       HeapTuple               tup;
       Form_pg_proc    proc;
       HeapTuple               languageTuple;
       Form_pg_language lanForm;
       Oid                             languageChecker;
       char               *funcname;
       int                             result;

       tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
       if (!HeapTupleIsValid(tup)) /* should not happen */
               elog(ERROR, "cache lookup failed for function %u", funcOid);

       proc = (Form_pg_proc) GETSTRUCT(tup);

       languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
       Assert(HeapTupleIsValid(languageTuple));

       lanForm = (Form_pg_language) GETSTRUCT(languageTuple);
       languageChecker = lanForm->lanchecker;

       funcname = format_procedure(funcOid);

       /* We're all set to call the checker */
       if (OidIsValid(languageChecker))
       {
               TupleDesc               tupdesc;
               Datum                   checkret;
               FmgrInfo                flinfo;
               ReturnSetInfo   rsinfo;
               FunctionCallInfoData fcinfo;

               /* create the tuple descriptor that the checker is supposed to return */
               tupdesc = CreateTemplateTupleDesc(10, false);
               TupleDescInitEntry(tupdesc, (AttrNumber) 1, "functionid", REGPROCOID, -1, 0);
               TupleDescInitEntry(tupdesc, (AttrNumber) 2, "lineno", INT4OID, -1, 0);
               TupleDescInitEntry(tupdesc, (AttrNumber) 3, "statement", TEXTOID, -1, 0);
               TupleDescInitEntry(tupdesc, (AttrNumber) 4, "sqlstate", TEXTOID, -1, 0);
               TupleDescInitEntry(tupdesc, (AttrNumber) 5, "message", TEXTOID, -1, 0);
               TupleDescInitEntry(tupdesc, (AttrNumber) 6, "detail", TEXTOID, -1, 0);
               TupleDescInitEntry(tupdesc, (AttrNumber) 7, "hint", TEXTOID, -1, 0);
               TupleDescInitEntry(tupdesc, (AttrNumber) 8, "level", TEXTOID, -1, 0);
               TupleDescInitEntry(tupdesc, (AttrNumber) 9, "position", INT4OID, -1, 0);
               TupleDescInitEntry(tupdesc, (AttrNumber) 10, "query", TEXTOID, -1, 0);

               fmgr_info(languageChecker, &flinfo);

               rsinfo.type = T_ReturnSetInfo;
               rsinfo.econtext = CreateStandaloneExprContext();
               rsinfo.expectedDesc = tupdesc;
               rsinfo.allowedModes = (int) SFRM_Materialize;
               /* returnMode is set by the checker, hopefully ... */
               /* isDone is not relevant, since not using ValuePerCall */
               rsinfo.setResult = NULL;
               rsinfo.setDesc = NULL;

               InitFunctionCallInfoData(fcinfo, &flinfo, 4, InvalidOid, NULL, (Node *) &rsinfo);
               fcinfo.arg[0] = ObjectIdGetDatum(funcOid);
               fcinfo.arg[1] = ObjectIdGetDatum(relid);
               fcinfo.arg[2] = PointerGetDatum(options);
               fcinfo.arg[3] = BoolGetDatum(fatal_errors);
               fcinfo.argnull[0] = false;
               fcinfo.argnull[1] = false;
               fcinfo.argnull[2] = false;
               fcinfo.argnull[3] = false;

               checkret = FunctionCallInvoke(&fcinfo);

               if (rsinfo.returnMode != SFRM_Materialize)
                       elog(ERROR, "checker function didn't return a proper return set");
               /* XXX we have to do some checking on rsinfo.isDone and checkret here */

               if (rsinfo.setResult != NULL)
               {
                       bool    isnull;
                       StringInfoData  str;
                       TupleTableSlot  *slot = MakeSingleTupleTableSlot(tupdesc);

                       initStringInfo(&str);

                       while (tuplestore_gettupleslot(rsinfo.setResult, true, false, slot))
                       {
                               text *message = (text *) DatumGetPointer(slot_getattr(slot, 5, &isnull));

                               resetStringInfo(&str);

                               appendStringInfo(&str, "got a message: %s", text_to_cstring(message));
                               do_text_output_oneline(tstate, str.data);
                       }

                       pfree(str.data);
                       ExecDropSingleTupleTableSlot(slot);
               }
       }

       pfree(funcname);

       ReleaseSysCache(languageTuple);
       ReleaseSysCache(tup);

       return result;
}

Without correct registration you cannot to call PL check function
directly simply. I don't thing so this is good price for removing a
few SPI lines. I don't understand why you don't like SPI. It is used
more times in code for similar purpose.

so from me -1

this disallow direct PL check function call - so any more complex
situation cannot be solved by SQL, but must be solved by PL/pgSQL with
dynamic SQL

so I don't like it. We can talk about format for check_options - but I
don't would to lost a possibility to simple call check function.

Regards

Pavel

Show quoted text

Regards

Pavel Stehule

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#80Alvaro Herrera
alvherre@commandprompt.com
In reply to: Pavel Stehule (#79)
Re: review: CHECK FUNCTION statement

Excerpts from Pavel Stehule's message of sáb mar 03 02:45:06 -0300 2012:

Without correct registration you cannot to call PL check function
directly simply. I don't thing so this is good price for removing a
few SPI lines. I don't understand why you don't like SPI.

I don't dislike SPI in general. I just dislike using it internally in
the backend. Other than RI, it's not used anywhere.

It is used more times in code for similar purpose.

this disallow direct PL check function call - so any more complex
situation cannot be solved by SQL, but must be solved by PL/pgSQL with
dynamic SQL

Nonsense. Where did you get this idea? I did not touch the plpgsql
code at all, it'd still work exactly as in your original patch.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#81Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#80)
Re: review: CHECK FUNCTION statement

2012/3/3 Alvaro Herrera <alvherre@commandprompt.com>:

Excerpts from Pavel Stehule's message of sáb mar 03 02:45:06 -0300 2012:

Without correct registration you cannot to call PL check function
directly simply. I don't thing so this is good price for removing a
few SPI lines. I don't understand why you don't like SPI.

I don't dislike SPI in general.  I just dislike using it internally in
the backend.  Other than RI, it's not used anywhere.

It is used more times in code for similar purpose.

this disallow direct PL check function call - so any more complex
situation cannot be solved by SQL, but must be solved by PL/pgSQL with
dynamic SQL

Nonsense.  Where did you get this idea?  I did not touch the plpgsql
code at all, it'd still work exactly as in your original patch.

ok

I am sorry

Pavel

Show quoted text

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#82Petr Jelínek
pjmodos@pjmodos.net
In reply to: Alvaro Herrera (#77)
Re: review: CHECK FUNCTION statement

On 03/03/2012 02:24 AM, Alvaro Herrera wrote:

question: how attached you are to the current return format for CHECK
FUNCTION?

check function f1();
CHECK FUNCTION
-------------------------------------------------------------
In function: 'f1()'
error:42804:5:assignment:subscripted object is not an array
(2 rows)

It seems to me that it'd be trivial to make it look like this instead:

check function f1();
function | lineno | statement | sqlstate | message | detail | hint | level | position | query
---------+--------+------------+----------+------------------------------------+--------+------+-------+----------+-------
f1() | 5 | assignment | 42804 | subscripted object is not an array | | | error | |
(1 row)

This looks much nicer to me.

One thing we lose is the caret marking the position of the error -- but
I'm wondering if that really works well. I didn't test it but from the
code it looks to me like it'd misbehave if you had a multiline statement.

Opinions?

Well, if you want nicely formated table you can always call the checker
function directly, I think the statement returning something that is
more human and less machine is more consistent approach with the rest of
the utility commands. In other words I don't really see the point of it.

Petr

#83Alvaro Herrera
alvherre@commandprompt.com
In reply to: Petr Jelínek (#82)
Re: review: CHECK FUNCTION statement

Excerpts from Petr Jelínek's message of sáb mar 03 10:26:04 -0300 2012:

On 03/03/2012 02:24 AM, Alvaro Herrera wrote:

question: how attached you are to the current return format for CHECK
FUNCTION?

check function f1();
CHECK FUNCTION
-------------------------------------------------------------
In function: 'f1()'
error:42804:5:assignment:subscripted object is not an array
(2 rows)

Well, if you want nicely formated table you can always call the checker
function directly, I think the statement returning something that is
more human and less machine is more consistent approach with the rest of
the utility commands. In other words I don't really see the point of it.

I am not against having some more human readable output than plain
tabular. In particular the idea that we need to have all fields is of
course open to discussion. But is the output as proposed above really
all that human friendly? I disagree that it cannot be improved.

BTW one thing that's missing in this feature so far is some
translatability of the returned output.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#84Alvaro Herrera
alvherre@commandprompt.com
In reply to: Pavel Stehule (#78)
Re: review: CHECK FUNCTION statement

Excerpts from Pavel Stehule's message of sáb mar 03 02:25:52 -0300 2012:

Hello

It wasn't all that difficult -- see below.  While at this, I have a
question: how attached you are to the current return format for CHECK
FUNCTION?

TupleDescr is created by language creator. This ensure exactly
expected format, because there are no possible registry check function
with other output tuple descriptor.

I'm not sure what you're saying. What TupDesc are you talking about?
The tupdesc returned by the checker is certainly hardcoded by the core
support; the language creator cannot deviate from it.

check function f1();
function | lineno | statement  | sqlstate |              message               | detail | hint | level | position | query
---------+--------+------------+----------+------------------------------------+--------+------+-------+----------+-------
f1()     |      5 | assignment | 42804    | subscripted object is not an array |        |      | error |          |
(1 row)

This looks much nicer to me.

I am strongly disagree.

1. This format is not consistent with other commands,
2. This format is difficult for copy/paste
3. THE ARE NOT CARET - this is really important
5. This form is bad for terminals - there are long rows, and with \x
outout, there are lot of "garbage" for multicommand
4. When you would to this form, you can to directly call SRF PL check
functions.

I am not sure that consistency is the most important thing here; I think
what we care about is that it's usable. So yeah, it might be hard to
cut and paste, and also too wide. Maybe we can run some of the fields
together, and omit others.

I am not sure about the caret thingy -- mainly because I don't think it
works all that well. I don't know how psql does it, but I notice that
it shows a single line in a multiline query -- so it's not just a matter
of adding some number of spaces.

Given the negative feedback, I'm going to leave this output format
unchanged; we can tweak it later.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#85Alvaro Herrera
alvherre@commandprompt.com
In reply to: Alvaro Herrera (#84)
Re: review: CHECK FUNCTION statement

Excerpts from Alvaro Herrera's message of sáb mar 03 16:54:19 -0300 2012:

Excerpts from Pavel Stehule's message of sáb mar 03 02:25:52 -0300 2012:

3. THE ARE NOT CARET - this is really important

I am not sure about the caret thingy -- mainly because I don't think it
works all that well. I don't know how psql does it, but I notice that
it shows a single line in a multiline query -- so it's not just a matter
of adding some number of spaces.

I checked how this works in psql. It is upwards of 200 lines of code --
see reportErrorPosition in libpq/fe-protocol3.c. I'm not sure this can
be made to work sensibly here.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#86Alvaro Herrera
alvherre@commandprompt.com
In reply to: Alvaro Herrera (#85)
Re: review: CHECK FUNCTION statement

Excerpts from Alvaro Herrera's message of sáb mar 03 17:56:23 -0300 2012:

Excerpts from Alvaro Herrera's message of sáb mar 03 16:54:19 -0300 2012:

Excerpts from Pavel Stehule's message of sáb mar 03 02:25:52 -0300 2012:

3. THE ARE NOT CARET - this is really important

I am not sure about the caret thingy -- mainly because I don't think it
works all that well.

It doesn't work correctly with your patch; see sample below. Note the
caret is pointing to an entirely nonsensical position. I'm not sure
about duplicating the libpq line-counting logic in the backend.

Also note i18n seems to be working well, except for the "In function"
header, "query", and the error level. That seems easily fixable.

I remain unconvinced that this is the best possible output.

alvherre=# create function f() returns int language plpgsql as $$
begin select
var
from
foo; end; $$;
CREATE FUNCTION
alvherre=# check function f();
CHECK FUNCTION
---------------------------------------------------------
In function: 'f()'
error:42P01:2:sentencia SQL:no existe la relación «foo»
query:select +
var +
from +
foo
^
(4 filas)

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#87Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#86)
Re: review: CHECK FUNCTION statement

Hello

2012/3/4 Alvaro Herrera <alvherre@commandprompt.com>:

Excerpts from Alvaro Herrera's message of sáb mar 03 17:56:23 -0300 2012:

Excerpts from Alvaro Herrera's message of sáb mar 03 16:54:19 -0300 2012:

Excerpts from Pavel Stehule's message of sáb mar 03 02:25:52 -0300 2012:

3. THE ARE NOT CARET - this is really important

I am not sure about the caret thingy -- mainly because I don't think it
works all that well.

It doesn't work correctly with your patch; see sample below.  Note the
caret is pointing to an entirely nonsensical position.  I'm not sure
about duplicating the libpq line-counting logic in the backend.

Also note i18n seems to be working well, except for the "In function"
header, "query", and the error level.  That seems easily fixable.

I remain unconvinced that this is the best possible output.

alvherre=# create function f() returns int language plpgsql as $$
begin select
var
from
foo; end; $$;
CREATE FUNCTION
alvherre=# check function f();
                    CHECK FUNCTION
---------------------------------------------------------
 In function: 'f()'
 error:42P01:2:sentencia SQL:no existe la relación «foo»
 query:select                                           +
 var                                                    +
 from                                                   +
 foo
                      ^
(4 filas)

this should be fixed. I checked expressions, that works (I expect)
correctly. Caret helps - (really). Sometimes man is blind :).

I'll look on this topic tomorrow and I hope this will be solvable simply.

Regards

Pavel

Show quoted text

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#88Alvaro Herrera
alvherre@commandprompt.com
In reply to: Pavel Stehule (#87)
Re: review: CHECK FUNCTION statement

Excerpts from Pavel Stehule's message of dom mar 04 16:33:08 -0300 2012:

Hello

2012/3/4 Alvaro Herrera <alvherre@commandprompt.com>:

                    CHECK FUNCTION
---------------------------------------------------------
 In function: 'f()'
 error:42P01:2:sentencia SQL:no existe la relación «foo»
 query:select                                           +
 var                                                    +
 from                                                   +
 foo
                      ^
(4 filas)

this should be fixed. I checked expressions, that works (I expect)
correctly. Caret helps - (really). Sometimes man is blind :).

Agreed.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#89Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#88)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Hello

2012/3/5 Alvaro Herrera <alvherre@commandprompt.com>:

Excerpts from Pavel Stehule's message of dom mar 04 16:33:08 -0300 2012:

Hello

2012/3/4 Alvaro Herrera <alvherre@commandprompt.com>:

                    CHECK FUNCTION
---------------------------------------------------------
 In function: 'f()'
 error:42P01:2:sentencia SQL:no existe la relación «foo»
 query:select                                           +
 var                                                    +
 from                                                   +
 foo
                      ^
(4 filas)

this should be fixed. I checked expressions, that works (I expect)
correctly. Caret helps - (really). Sometimes man is blind :).

Agreed.

I don't have your last version, so I am sending just part of
CheckFunctionById function - this fragment ensures a output

or please, send me your last patch and I'll do merge

now result is better

postgres=> create function f() returns int language plpgsql as $$
postgres$> begin select
postgres$> var
postgres$> from
postgres$> foo; end; $$;
CREATE FUNCTION
postgres=> check function f();
CHECK FUNCTION
-----------------------------------------------------------
In function: f()
error:42P01:2:SQL statement:relation "foo" does not exist
query:select
var
from
foo
^
(7 rows)

and some utf8 fce

postgres=> check function fx(int);
CHECK FUNCTION
--------------------------------------------------
In function: fx(integer)
error:42703:3:RETURN:column "ýšý" does not exist
query:SELECT (select žlutý
from jj
/* ýšý */
where /*ýšýšý8*/ ýšý = 10)
^
(7 rows)

postgres=> check function fx(int);
CHECK FUNCTION
-------------------------------------------------
In function: fx(integer)
error:42703:3:RETURN:column "xx" does not exist
query:SELECT (select t.a
from t
/* ýšý */
where /*ýšýšý8*/ xx = 10)
^
(7 rows)

caret is ok

regards

Pavel

Show quoted text

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Attachments:

patch.ctext/x-csrc; charset=US-ASCII; name=patch.cDownload
#90Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#89)
1 attachment(s)
Re: review: CHECK FUNCTION statement

small fix of CheckFunctionById function

Regards

p.s. Alvaro, please, send your patch and I'll merge it

Attachments:

patch.ctext/x-csrc; charset=US-ASCII; name=patch.cDownload
#91Alvaro Herrera
alvherre@commandprompt.com
In reply to: Pavel Stehule (#90)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Excerpts from Pavel Stehule's message of lun mar 05 13:02:50 -0300 2012:

small fix of CheckFunctionById function

Regards

p.s. Alvaro, please, send your patch and I'll merge it

Here it is, with your changes already merged. I also added back the
new reference doc files which were dropped after the 2012-01-01 version.
Note I haven't touched or read the plpgsql checker code at all (only
some automatic indentation changes IIRC). I haven't verified the
regression tests either.

FWIW I'm not going to participate in the other thread; neither I am
going to work any more on this patch until the other thread sees some
reasonable conclusion.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Attachments:

check_function-2012-03-05-1.patch.gzapplication/x-gzip; name=check_function-2012-03-05-1.patch.gzDownload
#92Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#91)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Hello

* I refreshed regress tests and appended tests for multi lines query
* There are enhanced checking of SELECT INTO statement
* I fixed showing details and hints

Regards

Pavel Stehule

2012/3/5 Alvaro Herrera <alvherre@commandprompt.com>:

Show quoted text

Excerpts from Pavel Stehule's message of lun mar 05 13:02:50 -0300 2012:

small fix of CheckFunctionById function

Regards

p.s. Alvaro, please, send your patch and I'll merge it

Here it is, with your changes already merged.  I also added back the
new reference doc files which were dropped after the 2012-01-01 version.
Note I haven't touched or read the plpgsql checker code at all (only
some automatic indentation changes IIRC).  I haven't verified the
regression tests either.

FWIW I'm not going to participate in the other thread; neither I am
going to work any more on this patch until the other thread sees some
reasonable conclusion.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Attachments:

check_function-2012-03-06-1.patch.gzapplication/x-gzip; name=check_function-2012-03-06-1.patch.gzDownload
�
�UOcheck_function-2012-03-06-1.patch�<iw�6���_�h��Hm�o�O[���[��������HH�4E2<�Vv��oU� H�����d����m�B�P79�l�Z���0��v'�������Vby�<n�'6���������O��~��>>��l�Yw���h��jm��������������a���������]F�.:Q�x���G��'?p?�V�q�v2s��\z�o/���G���#��c�U�/����_U`�������#��w�����B��5E�o-�e8�`����.:8��M3j�����l�pc���o��Y���J����f�o'n��da%���;l��H.�`��|�������������vx��d�Z�����G�;"��-���Bc�Ic���������A�����3�,N�E�_\�;����{������n�^����>40��O�}/�f����S������f���e4Y���i�?%��t�[R-M����'6����;��s���>��e+��}���$^X���B�6���3��yj��U8��.� ��J
^�WZ����������������W_b�
(O��s�=�}���F����nz�w��w}z�����uZ�%Y���E��R�\�f���������l��.0�%�#n%����.-o����Dq';���F��a�{�<,��Z�b�
��� ��T���p-"��`�%N��H����(�F�� ����j�`��h0y���p^�]��(N��./,�u�$���Z�b�>�k�Xj��YzL�"�
z"�V<j�[q�m�D�%n��8T���E��	�}�g�����>�������-�����'k���[�@����9 Pl���\y�jkU����k�4	�����"�lRd�T�D�|�#�O�*������gQ"�X`�W� �8�M���?q;M�\PYNt�5
�.:���:�'�v��<<�l���9�D�����0�<D!�Qj�N/�8!����^�!F9YLc����4y��d��������0#��d1��@�@fD|��1B%7r�P�G�7�V�Na�<��
R	�YE?��A�q�|R��(&���8�(����g��!�/v��$S�S�u��5���X���&�6�8��@�O�PD<��y�M�H?]Na�p��.���XK�9�'�|�������q�����%�5���
�<�`7$ ���'��U�1zaU���@�c�+�h�V/��PrX�,������ v��
Z���@RS�� ?��!w&�+��523.`��j<��������[���=i`�)�fc]���1�/� ����J#S}�X�����hC>V�#!�1wZ@��������4�^����2i� �m����5�j&^

8B��Xj�q:|Z�4������rx����-��l�������T3��P�0��K����%�u-Z/IJ�B���$��A�� �|6���!%RAV}��d X��q
^��Z5IP�@��@�DGO�r�n��]j�U���)������;���lE�	����D}J���R�X����o�c������j��X�7��,�69��B���MN11�<v���`��!�S�g�D.F<I#����G  w
s.���>?e&c;r�H��S���4��*���E+��r�����]��b��qC6��X2sAF�2-�+OZ���s��N�Lq�
� HFvi����&�k2'<<;p�O�m>;=>=�����(I�����Ou����c_�F=���[�gF�~�Yq1��{_�����o ���\����������Y��&�G4�.�a���/���a�k}����V6����==��������(�:�6�l����dRw��/��u���������F�Rh�
#�K��,�%�(��]���fG�n�L���������k�0w���JQ���M�����g{�f��
d�M����V��Z-�Ih����\�_ Bi��S7�!�
�z���x|��{i�����������}P��nl��,�����nTFv�"���j0$��Q$����q4V
J��v�~N�TV�c3�����"�CB�F+�2���Sw��N��-��viE�5ZU;a����+?!�3^���w��es2c5}S���v�T#�V�#���q��t������9�b�������n�D��e0�h�H5+�4����X�����b0��'�Ag=)](����{O!�Ab�V���#6��y�����S��e�F�$\D�]>R��%��k|�E��M<rT�R�;�����Cs��5K���.��5En��/4�ZX�������Pz]��L,T{Vg+�N�4���+����Q��b����iB��@���\8u�pK�]~���s���T���(�T���R"�_���j%�!��c�����l�R=S Ieb�X�*��1�@�
3���	�J;��z�� 	�O�)�l�Jr����T��2e��fIvd1}����h8��DP%�������d�!9�
iA�dH!����
"wiA�(2,�Q ����q�%�(?�
�hi��)c>!$<d��l��������U}�O��# �Y� 	�b
�DR��Y,��.�+�����5��2qA�Nc�/��<������<�g�S�K{n�_����s@��+�q�zvD#����]��FU`��x3������������H��M��X�*;���`H�K���~�
�P6��=T�Q�?�F����*����K7IP�Q��X2����G������V�	 E��$�Q���`���'�iY�(,���C��V�3Q��a�h<
����\�o8�1K���9��9��^��L�������F�5��59������d��2/���T��h�uQ�������X~�������7e�N&����#pbb��#4���d��Q��CrY[�-8V�|�6sBq@L����������\7�!�E��x���.�r�Ee�R�a�	�$�\\Q�;u���*�T����q(�C���Q�>q2�8�f;������2��B���"bu9�����v��ggG�Sr��aT���.���>Qo�gn[���	����f���b�:�{����&RRs�?��&}A���2m��=�S�������~c)1�p�S��]�o������oQ���0[�5�^��r����A�>"h2�/��'K3�e���Q�yE�#Y�~��RY���)��=��t�u�3����NE������a�==��83k���JTUqf,�=l��=�	3~1@Ay?�Nw�����?2<���]��������}?����mO��mF�������������{����c*t8}m�X�7�����.Hp]?�n���m��K�5���/	�
�e�k�ffL�Jb2&�{JW�����"���K�sQ��Y����2'S�e��Qq��8�xj���V�e��"i��|y�R�������1x���q��q�Y�K�7O5����7=yS�#���8=�|>`�(��V7|fo���U�����C,�O>�tm]B��0�`��+��#���K��L�����Yr����8��g�E#��nu���b}	Vy�]��:>�p�W57��6���/��� �b+^�_k����dM�.����������$�Y~!�`��N�^��9b���F��o�����^���u����^��?{��%���~�^�%���x��s�@�P��Lk6��L��h�����I[��9��	@dn�)06�&Z����l���&��d��^������2#���|���4�����
f$3�d6��PMe��06;��G)_�&��K�V���>0k��%����B.���,��H�������iU^d��l������{�n����m�r�����������B�������N�F~���'����x�*w��l��������j����o�&�&�6$yj�XUS3�(!��N��aq[@I6�����:h����Y7��m�vnI������&a��Y����FA��G��19Z��������~��](H��U�n��P����`���!������(��7��U�`X�l����I�`�{�4��w^0p��?I-�EA>����F�|�H����>���+����Z���(�<'n���t������	Z���o/j9�C`�"�����/F���W��jp�n�<5�:����8t3����,�"��-�_x�����%��5�!B���J���hy1��0�`��L��F=&��o���Gaa�)r��^Hw���;y�����C��[:LV�]�#�Q,��d�s+�������v��[@��G:poh���]�����f�^�N��I����1��H����Mz�
�e�U~�N��]��m��u^����-�o��*DF�g���G1�"��xY+P������agw�`�����|)��Q�2j!�����.�������`^�Z��n�����u��`����~�����!�p
�x^�i��Q�C��Zv=�B�ZS�K
��s�@�}����I� r%��{�E����nS`9����+`2�i��k��h�V�����0EWT�S�Dbpv|��v��������a���w�2a�c�����S 	���&>�4�S�V~�@�6
�(�Q`B��aw$w��
u�[G.{��h0IX����J�e�EX0�����V�A��X�ao4v[;Eh�l)�ER Fn��r��S�%z�7(*;���&h>�o��������t�!���O�l�{���G�W�����&��!u�O���c|�l<h��n5p��������(c#�F��"e��XF��i\��1EfE���?��G��{Wf�6�G��Q��g�"-�F�	R"�o0
���sB��l��.�/�r ��LL�M�D�%�&���$R����;(N)i]��~H���jyN�@Zj9N���_����&�W�J�"�s=����)��O�������W�������s�����jR�r@��[�K�
�{(���l�O��F}�6�ZV��1���Bq�������&j>�k0})�C@��|��.��:B\���LT��JF��t�D	���N��g�9������x��*=SP�k��Ki*��N�l�����w�������<#�K"�����H��A���s�s4~�h��a]x�����6,�#T���M(d�\Xv�&a��e3�:!Y��G���L~�� ��/�F�y���=:���.��8���k\~`+H�+���<��������mD���	u@N(��A�<� �Cl�n�)	��}��r�����r�$f���F�h@h��M3- W����@
<����6;VBT9����?�������~�Zg�n$��I5�l-����� ��/��u	?q{7qGa� u[&h�!&�g�$S�����\(l�5aA���u�*�����u3��d-�6�b�����{0�s������q4����������qt/�n|��~����V!T���2�a�X4��m���Zt@��L �3��|	6�@�&�>eh�WB%��$=lS��6����M�ht����>�cy��C]��z��)�����V�'��N�;�ab�0*
v�v�~���7t��Y`���J�pF���#�t�k��-y,	��?�h�G ��b�~�k�x�FH�TnZI����7�.�4�]���Z]4��&"�7�4	�������
�JLF�8�V4�q�'���������|��Z�k��e�a���o8�4^TSdZ����y�����\�(�G^%�H�GC!7�����Y��WT9�%;���kL������'���+!<8�o�����0q������G~G�>���EQ$���$o'N"j~�l�����,C�S�%.��(�~<�S��n�a���ra��@�m�ra(�W�vmn�L�)��Alj����V����^����������x�~0�����`8o���WLA��d�=���u�d��AN�;M6����_�"6 KO�8�O
�&`�0e���O���o����8�������p�X�����������\����
�z"$�F
a�|��n}������.�����VWWWU���z�:��.X����(�X2e]'#��#
c^E�ZZb����Hh�������r~��g����yxCC4:l����
��B������.U�E��?1���!N���q��o���^����|���o�W|Ctf���D<)�D��/����/f���-�H��/<A����Mvw
�V�n��������?��a���d
���D��owu!7�%��Y��@���T$g*K�x���r���Hx��Cm5��P�*uR<H�8������+�(%�pd��N�G����R*-+�(`���YL"�B�[s�5o�����7����i0o��z%MxN�[d����@���������@z�����@)���3����<w�Ef����3�:�lV����=��d�hR�%����~l�������B���]����\7k�-$Q�`�/R'e��d�����f���%��S;tt���G0���Mf�^�5����4�bm����$��E�����E�sO�pn�:��k��8E��=� �
�{( e0
�f��R���nM�UU��:��#�	5��ij�jg�/��������$:+��]I�puU�5���(Z'�|Vb��.�#��Fo�n3w�<x���`S���;��i�/�7�����X�C��X1�r8o:g�T���P�jNP���U�����T�2�-gRT������Q\hB�����	4g8G�1��S?Q6(uj�=����Xx
�8H0*�>�m`��~W��w��]��+l=�U��Biu���&��'�?N`O
��n��!�&q�h-�����*����������n�j����DSzW*�[7���*�[���%�������#�q�tQ��Z�����������������d�dC3���	,I:���C���]������w#�
����sJ���t��[(h� n%�h�`5v���e'_��Fz0O�Y��kI������h��@M�peA��� �j��.K�W	���~����t�Ju}*!��l��Tg�U�����wq�KL��`��0a����F_�Fz#�H%��~w��<U�>D�u]���~M����������R��g����i��]t�X.j��,f�����K4�~�^�:kJfud��<�����94���a�6Z(��/}�g�{G���Z< �V��>��q�h�%���v������?i���|���a-.-�h�f��@�`e�z��{�eA�����>8v	h7�9�&��5�tv���d�����~8��U���E-��?r}���&:9<h~c�����1C	0$v1��G�LM:M�d��%f��Q��Pt(lw��K�2��Ex��A�������r��K������������lkI�S�-@K�>u���a�>���A������{L�L�){�0C�.�����C��zz�r�x�2���A>7�
�����|���_�:�Dh+_����/2�=�7m��|��E#� �{L	4�������!�W�ZI��c^��~�g��&���A���{� �����wE�XQ���(��w�\�x���26	����6I2����%����>j�����'�m?=�wXC���1������F�
(L�py�V����$��,C�����a��q���BF�|"�����������1�{;=��>)
4���m��w�wt�6B���c��;���u|t������4���k�N1�b���k
�%�(��=�� ~�oq-Q��u��Q�C�/z����3P���`����_������9��|�~�|��m�#�D=���e����"�]�4jp�����E�����6����'=�c�i���d���f
���c3�����TqF��a���N��@�$ wd��'Y�����9V�oV���9Q��4�AS>C�����_j�@�Z���i�b[�T��ql`��J������dC�U({)Y7�������s����y�O��6l���8�a���5^��}|rq��t���;ZZ�!����"%Zw������v�7{��9(h��]��c,O=�gi��!(G��C��)9�3�`��:[Z���/�q/��Y/*�Q)�:y����z��_�eFQ���^VY?�O�@����b,�z*�����]^���{	N	�_&-L�����;
�
W�5���%���E���Lc�A�������ko�(#����yfh5m2(:�=&�������$����0JDL�<�������hXX�=q<_��_T��a90q�3�-R%�~�z����"�8.�Z����m�V���UT���{�_�*����X�� m��������t�;���������E���	�����b]������%Gm���e%�#�qQ:qK%���'|mb��4�H�.{��T��g��CQ|��u��{%���JA�!T0���*�S�p�Vk|K���e~�����]\�m�c�1{I���F�[
�zZr?���51-�FT�W�����,��Lm.����v=����^b�`�Q[��t�BJS�����2�o��n��+
6~���4�V��2q
Tg,���.�=/��3���c�m���P��%���(G�)�NG����0$�Z*��������PHVR�+l�%ug�L:����3%�/(y�.�]��T��������uG����)�A��?Z���*|��g�u���F�Ic3����������8f���
��1�������	��t]Ge��Vs�J4�a�a����(�J�Cw)��h�]_�I7tb�g�c����z�=��2�Y��oi�|(��&34����@�H�����I�C�
�XOO�kA�z`�����!q��f2;,������"����l��"������T��������^��$�6�U�hV�;���C�>zsr�������y����H�$�XV�,
�����s@N��)��c���L#� v��N��9fsU�sL�<���C��E;d�K��9��kY���_I"+#O���[4{�kv-��s7������:�A\�%�7�p�?��#"���m������q���Q���"|�llP���fm���4����b�?t���D��KG���_v�����������%+tgKH����f��^�V�7�6�g
r��1!��S��mVju������o�|���E�jQr�.����a���w"g�qx���[-��{��@�Wi�t3�~��@3���~����;m��mh���<]<�n�r��� �`�����WT�e��n6��D��Ka\�`�0��HG"��%���B���2=�Ht�)����K��{�<�
�w�K�����%H��f�D#y^89�l����
6���S^�����6�*;��fc��;�����hc���?��#��rO���G��;Ct��������9�A���B}�P�x�?��t6�b�'&�x"�sx�Jl�#��v�M�`�(}eL�i���I������z}���@82#�2V���|��~��9�����	e$�Jw`��Rt[�b��zz��P�e�|��H�z"������	�������Jk�����gV��V	��7O�jRl�h��<\�4��U�;;��!\��ON���(5���Ec�N�	���Y������_n�.��w/xZ�?�f�Q�}
�P���W������e�����1Q���	�������`cz���.\�]���
vG�S����8HF�U!*7�Y��x�Q����>��i6xe�6? gU��������y�����l����GD[���U����2;;�L&"����I��nC��a:����������Y4S>�F���L���;���:�o���++y���N0��~g���]M5�ob+���~i�:�����%+|��	�T��|�����*zzJ]�v����0B����������{D$���0���n�,�I�4��7M3,j��Sc���hz�fB9���\�u��J������
C�V~	����o���R��	���'�l�����Yn]\�������g�~dr���g�Z����E���{�����������iq�4i����}�A������(��5�"P.sNW��pL}{r�wqx���Z�xe
��/�������/�X������]�v�RebyS�Z�vB��������`���2?�t������D�w�������3b9����X�:�2��U�,�)��%����a<!.+H�k-�e�8#�9%]������o���l����������j��g��I��s�L������Gg�V���s
�
]�S�Ng���M�D�1���J�!��BT��l�/m�-S"���__[w����n���N3�fA�C�]h���������(�BC@�����?j����/��p$\�6gU�C�Y���Nh�!W����v�~��)7���v4a*j��qDhn(nuu#|�J�C&��`���l��5t4)$�m}s�^��6��%���(/q�k����_���I�������*��y�<>h������;��s��.�M:�q�l!������*��X��{�R��C�������}Yd�[���������G����h%1�
/
��
^'q���`bj"�����/��A����Etzr~�5�%����w_x�*���g��6������I��A�@8�sv�����8���j��������L<
9��q�2�J����-���
���_e���p�W��n���K����5r��y�Y�N�V��l
o�L�9)��z;�V��������J��	wJ�_)mlWx���.��>bNAy�� bj��h�����f���y�������{�>u��H���k������;V}�Y����)<]V�N�nw;����H�����,�S�����s���C}��G��@s�� C��KX�#d`�&��q~���k${
��M�X������3�]	A�l)d#�T8��+��{"�u�s�~��y��2N���p`�J��v��'.���\�G�8L����)s�6�I[h1<���>%���(Hs�(��\��p"$�[��s&�Uh5�}A��9�];�by+�
�m����/]�������U#��{�-���n���
Mz	'�������c��R�jQ"U���k�M��u<��P�����X �3�����?����� ��.XF�l����l����Z����D6�J�����uK����V��]<�y3���a�b^���{8���B-����������ef|����X�q�I�p������, [s����M�iN�}���b������A�c��y�����EU�Y����8��,��s��ty��@�i���Y�;@T�����j�ei�c��1[*���kOr��0�@�i;ehW������!��9w��&��5S1mTB��'���.[���y+����RK�3[;��?k�fN�]�5�
[�V��Z	�h�
�
jC�^����K�9L����_�u��U$i+����i�"K�16A�g��
0���E����#��j���MJ�����h���%����"R�m<�,r��?�6,/��q�P�B��]Re�[b���
p7����o����h���Rwj��~G�3���5J���
y�9/��.�*1V��\�!����*��Vc}�j���L2�vpp�[ O.s��������V�Z�l��[;���-��d��1��H�A���������,���=W���(��)���v%�(���+�S�����=��J�pv����������hz���6���s��h�o_��x�V^��s��6�v������k'�W��k��}����g��>m	���V���b��p��dY��^�� �Xm�F�H�c�P��U�Y�RS4_D9�x!�0����)@.j��<��V��-��U�&�X$M��&�(y���	h������nN��n���z���\V�5�v��1m{Mmp�h�J�J�u�7#*4����xw�O7/�w1��6]�]�M��t��9a���;���M�����-,
L���F���{=a/�:�m.pe�����Q?��A1l����~�o�wx�S�V7M�����}�A����(&m���]p�GT��%O��0���g6��N��N���V�������	��_;����l��E�$��������w~���FX�4��Du�-��q:lw����;H0�T�b��q����~�����|���,���s�3O�a���~�w��c����A��3����d��[g$9_�������*�	��D�~��a$z��[��b�c�A�����c����D���� kf����H��r�?C���B��t|���O<�"]�CL�W@�6@�GV	m������]�����y@
����]�^�p\$��pO��&�b6�rI��� �bs�Rx?94eTw���^���%r.�@��sV�V$sn�B�

,tQ��F���M��Cx����T$�#@V�9'�2{��BwuS�1![$%���e��.���M|�������^�dL�~�������<�;�{�<����&z7���������}�J���p�*H�)��r7t
[�o�7R��XC�{��/�y����k�����������w0N�����:r�l�����_�jPR����zV�����K[��O�~wn����~�{��J����'�z�w|p�<����
NS{`]�����&���y���v�Q�����.���%�n������e�r;��_�-�����"Y��������o�y�[�r[��O�������?,?����5��Y���638��rW��
 T����
�B ���������T�
mD��M�K��H!�P��q�������q��5��EO�G�
��t�efl�"=I����:f���1�����+�_zd�3#�@��{&�!
�T�GIP���@�7����
�9����S|��u��vT�e�F���7���`�3n�Q����q����9�N�����z�3�'�n_���Z8��3���K�407��A����Y����O��_�?=I�P�B��E>j�%z���0��^q;�{G��=W(P.Y��em��Cyl��[� �DN�%�����������	�+�Mb?Y�m7�������b��e����9��l�I �����1����~@O��Vewx�N=+?�H�q�'o#
S�l�Y�F�'�
V�8��7�D���]�'�+���#cc�������|T�t1�>'4aZF��_`V����a�mu������F����������KQm\n�Zk��jc�jmkm�aR�z^s�)������{���z��NY ����H�4&b��������xI�*�bMMW����B���+J_�>V���P�d�-r���A�{�����)��8��}���+:������X}*0���)[%d�*P���<����)_g���m<��}�M���y�(�I4���I\�����<wB?���9�y�E���`��, \��d������zm�n^���u��b�>m\��AX�&a�]�7�S��19bN?���j���y�06^)a�����\����wv|x����=�������&%`�No��.v���bc���i���2$���G���4��&����������f?�\��F��|p����iO��;}������Y�C#�_n]^�`m������h�QD��R����NFx^��!-�
[7�_0�E<b��ro|k
��p���������(�C�da�}nR�x��V7���'�W��$\�c�NO��g����8��������yn�6��"��U���%���(�6b0$����Y��%��),S�2�<��������:�~w|�����>������U2�����-�H����Y���f!�9c�WZQ��/�<��@T�,K�}�%�+������Kj����J�9;y���}����1p�t������������li~"}o��7y�	E"�H��!���k����@����("-
�����U95Wk/x|�C��o�zd\��]�E�������g����
:8����E��S��%XP��h���H��m��178K\o�c��j�����bTHG��xP>��$C��t��	'��k��f��ra����?=+h����T�h�^��.�d]A��ze�g���9���z0�Dq{���3&�P�
4aPx����,
d{�N�������G�^�,~���+ca��'�?d�w����|���8���������U~M���f/�ed�������#���L$S�(aK1�����+�*�4-�),������y�*������b����)��X-���h�\th��C������`5��j�|�����C+���
�Q�)�b
W*��
nn�
����j���+������%�$?�\v�U��J��U��������!�37��W_L^Cl���,S�k�:��0�S��*�F�����]�m>�!FD�'@Ye�#�������9�c�n��M�_T�:3:jF��e��A?��.����u�{���^�#�U�i~�xI�2$���t"����Si�M�/�)����Ln����D��I��� f��W������j����Zo�4�������f�W�����Tt5�qyA�L���f[Q�<���\&c�|9�}����S��
8NI���J:#.���t����S��t��,����v���Zm��6`Q
"P?���|����Z��������Z%t��&�������Q��o;��k4��{��]��ZR J����j�����n�2��T�������?���A+[�k�8�H��jX��$�
���Q<����������x���][q�h=���N��i��o1���s)��|�~��ch
������cx�]4�������_��n�=<�/D�NO������y��|8�Z�g�ij�6f!>y��)%���|;��>I����{GG'������YS�8����P������{g����=;9���1=l~�������~}���'
�=���7y�G�0f*xt�w��<��'�R[��
����y��#4�h��[�����G���8�/�"O�./`��9�.��p�s��|#��x�F��l���b��)t��uHU���\4:PhMk:+�6��xE#�����P��j�I�K���Kfn�`�O�a4�0�Y��>�8�qL(��q(��-�D(�E� ��N�u7?�o>4�~(C��W�!�GF���T')=�����F������}��^�;n'�E}��Q���yb��z�'q���Wq�p���="&��S���b�,���N�J}�V_*8�����)&,5����9{���{B��q�("u&�u���Z��`��|��t��V
yL���\!���}�wv�<.I�,��Y��R V%�v��HQ"31���90���:�Jbv~�t�k����}�DK������=��8�)��.g���%5��N���=�u�O�S�N���;���c�8F�K�2��+���c:���k�3V*�'�Bm��`k�� �l�+����6�#����)C"/�6'38\���.�Y��|B�$��)E����.?�:�n�4G��G-��~�:M`+`h���VWe'�l���x~����������9R$�<��5�	u�<�?+/b���j��r6����='���[��\�[��>�np��{�"D��UL����~����7�c_����Z��lO�������j�Yx�^2]��f��F��t����][�/�
��.�/wv�3�^;�Q��������j��%������;`���&�=9�g���|��*�p� �[*��hy��wf���a�x`��r@��3&���.Vr��8w�R��d w�6�f�\�<��iO/U������,��JE�q�H�D���_����)(�E��9S�}s|<8����+����'l� �����f��
�
+���L��#99
��V��>�U�������V-�\fN�����#b�\�11�B���2iG�E���r^�;^��%���>�M�gM��Q��~d����������<�����_O�� �0�_�����y����=;������E����������u��;�??
����^�gD=;�z�0����?�@��4��(Hc�@������%��z��l����{;?�����m�`�-��.�r����m���r�?la��uEcy�����<=(�O��}Y3v�6:=�h�?����C�XB��H��&�y�,�^���f����j�*^�iO��r-��\�T�+��,�x�|s�|��iKj�����,}��,7f^���s��l����u��=��#��G��������N��0�DF�Tiuvs��(\������O�}�"�"3�(��m�q���hY�`Hl*n|��n�j�f������OQ7@�Tqh�����s���&��
�{Ef����������8��<����	�����*/I/����nB������xxM� �-)�:�l���x��l�����.#F���D�7��.\��M����h`���Z{{��6���ML������m��6~l:����7O�M���1�:eB'�9����d�o7e(E��q����%�r�5��z�H�Kna%de���[��;���N{gg�U�2�6�K*Ek�����A�?v�Y�lH�y�mj��@����y|��%{lp����$��@�:����Q{�m*��9�av!��i(af��wz��m�ol_U��kqc-�I���-�=����H�;>V%=X�c�u<p��*�3����+!�m�v�4����Lu������H����-  �/766�i�[���x9
4QW��H[�d�E��M�	����,��4�_8*PF�1DB�t�K��u�pU�}}%+v����+�������������J�r8?$h:.�Q�N����Y������1���7%Tjf
r�����Ey�aK(��o���W�u�S$R$��&hY��k����1A�����u��]�N7�32�m��42���&18:><��}e&�4m�Ux��R����a��o�������)k�;PyZoh�q{���=�����LE��eh�[{���~
����T�U��U��������E�^�#���~D�����{�4uH�vV�5])II����Q��wz�������Ot���`��Io5{�Q�w�����F}������E�-�,*A4��kr����%n�����m��EL��7�~�o����qU��!�{XV+��1E����}����Zu��F����7�[?��I��d���Vj�Z��85 �/���n�2�(d����
����>�����d�Z�������P�@���"n]"�o��`����9�AY��~�]����)��nIX������iW��%�$��FI�S�wg.:5�:���)$h�;�#T:�-���)XPs)��������f�D^�q��qt�a*��S��$U������aB�������� ��|CR-`��c�����cH�P���4��:@����~�A(����'�>�qN���RUJ=�HxY�x�w�`�)O(F����$)�)Y�)�F�� ��
��33�`�H�.O�'�dct�.&�0�I����D�~���?�t(�����b<#+67f\��^���N���v���Ov���}"��^�����<�Q'H
�`'n3��{V���S�]&-
����i��dDvy��}��(
�!�"J��UK���YV��S��UhlsX��
�v�3Ju@6��R�0��h�8��]�[�p���D���]��H�
wc/����ur�"�.�F��)�&mJ��lMI�Qfo����s�+�/�933t�3�Z��\���q�Z���Zk��P�
L:f��� cM�����^%�(�na���6n��%�Z���P�DM��}����@�P���HY�OX!`T!����D���An�)�*��?+�(N�x���'6��/���h���x�-38z!�k��B���jy�I����O�V�y��L
�l�_�!Z���H3X<��.t��3�B�(="�����"RnMJ��)/���@d0�
�\;i?3��"���[t�L/A����4����/�0B��h��{��+��z����&>��#*�+nb&�H=�������T�����w�2y�x��N�N
�j�k+����� �M�Eg�pz�=��q
U�n��5{V�%)��u��2,��h��+K���5�i���4b\n��1jWU�y���e`�Q�]u���+L��T�I����-Q*��G��w�m`��M
����`-�C��I�t+���M����iKz��������c{�=G�cu8��N'��hB�)��Ny
�����{R���x�Sq/�����������!��[�����>k'����[1�t�q�i��+N!r�jR�����5��H
N��OL���p_���[ElZ}�Z_�s[TW�%�}��8`_ =��(���Q7q�kTR0q*�a�M�(!��r�&L����-�Lg{:���j�kU�M�Q:=���N=��|�����0����%��U���^���s'�^4a���$��3Ru�Q�9Ws�����F�o����7�z�Jn��S����K����������������=��-�y;�_��w������a��HRg�]��;�]a�l4s\��xI�*�y��VE��=j�������(a�}*������S����J������������������y��x����Z�A,fJh�X��7f��u�W7���F�*���t��o���5��Pwq3��D�0V.�LnFB��p��Z���:���j�w����X��;�!Ym�R�qOW��������W��-��R��=Nf������%����P)�������e{I���}�,6V<����l�8J����-A@�K\�M\`LqdI �A��������<����GP����y��������4�������t�YMJC�T'5sF���Q�j^u���7�j�K���
����?v��`���L��1�b
-e���LWS���q���Jh2Y��p�JL�y
�aX�����	e����"��)��)0��U����'���<=:}�h}p��i�b9��	������N�S%���zY����w���b8	��-����nJ%�3P�5r���bE���_���0n��^yI�h�m�Se�FZ���Mc�1L���(�w���l���T�8"��~�"�d�Qt/�$�H��[8����6���o���	OG��}�����c���)�W�p�:����{V�Q�k����\�@J�N�|�t6c%<������������
)�%w�$��w�������EG���n ����C���t^o���7��>W�.�������G�\k�z�}�i!U@z:�7�F�m|��Q�����*�6��(�|J7���|2�aB!�Z�|<�Z�)�7e|{�~��G ��&a�{��E��7]��uT��!�-����W��w������)���y��W��c<�g�����1�����[��f�`
����E�O�iqZpodU�!�
����R�1��5":]i�u#p������}'��/�TJ.g��iH7���S.��#��X������i���i��5-hh�i���G���f���LT1s�3�Z�Ad4������s�@��|5����
�#�]e��������8���~w�*����Z�����su��D��	���C��=����<O�X��,�20��y:��\�^����E%^��C�Y*�o*���� 5����'d4F�}u8�tV�
�x��3Xm����^fu+B���!���
%[dK�����IE��#q��b	($�4��4
JkqGJ��Vs�&"��a��������T�i@�H�(��
y�{��U����Y��^��]X����.��U
-J�;�3���[";�,���3do��rX�!��"�k�)�xMTY[�h.�b�
O��G��]�����2����B�J%����/�%���H�'%���/�"��(Y��Q�O4��Ka��g���q�h,��lbo����N%�w:G��
�)m�)K*��>�?y������E�����g�o>�^`�����[�Su�1|f�o�����Gk��^ R�Mzs�m�f��#�Qo��9���5^�6��}+���$���\��s9����.2���Q�B�;)gvJ��f��$�?E�5hI�+%�J��a'!9��=����0���[���n-�0)�c���Lx�'D�
�-��g�$c������>!�~8>h�9<nD�'G�.�i�����;;���y�|��w�/�)���l!�A���|�"��
C���(�U�6�p��s���t�:c?"�@��n���60����C�o�����R����_�����/^�����P�d>��&.�~�}�N@X���dC[a��'
�*�3qjw9O`e�N{��jq
N��R�(�h(��

$a��} 
���D���Q�h��U7����;��H��=z� ��2��Tmu�_�������o;���4�!��T�2(�,>�L�i���}�[�-�������0��������� H�����`���{���A�� ���72����r[�J��<���lSf/O���93:������U{����6;H���vn���#0xvf=VT��!`�4�,����RI������g{������Q���f��y�S�@�_�F�_�N�_�XN ��0�pS��,��i�,��CFe[� @�4��E��b	C�p���Y���
Z�U/�,|�������,S?����)�*F���i�tU����"�5�Gr�{��U]u���)��K~_fSS`��N*f���2�T�������]���FNn��c>\t���v���R�������`d�����M�b9|�Ys0>���M��W#~i���h��	����q�6r�0|H�K�-�rS_����-�s:Q�i��gA���=p����uF�\�Hoz
�&��k\q������9\���
��5w�gd��
O����A��������w�j��:Vj�Qx�����fF!d����$4y�-�:������>����D��>R?_av����|�mF��������WOg��-�u���2�+��M[���G�"\�6k�E�b�_�����-���k�����1�B�����_an%\S7Q� fX�'hU�������H����j��Z�
�����I?
��"��E!�j.�Mz��3�RY2��7�/�����������������%�4f?6^3���Q�R�5���Os�����7�������3��A	,O8pw���\�+�����gm6����k�o�����==<��������I}�~�B0�vt9�6�No������.��&��tx�����H�Ps<�����+z��������BO��x(~n�~Iz,_��xe������M�*��&�����>_S����YJ�\&Wx�.��7���)��wF����)��S�^J�b�Z����x-�b(x>�����o?����������2c���o����T�7���O{;����\������4���5�18^>%�=�;��qk`����_��a��h��79_)|y�go��^[xLcP��L�R�Z������`(���b@h�N�
�5��5�G����c�Zb�V�b�P�4�
�A����Mw����s2W�s�0���O�����1���� ��AG0s�zV�1r�i��!��1�I$���f�=G��]��2M2�E.���l`�Q���[_���� ����t�0�}O`e�s���}����p�������'g��A-��]������V�(j����b%����X�a
�������5��#��?i�%�yB��f_���m�,>��RQ6*��f-hO������Z]REtz�(��^n��p��d�dSna�'���I��vVQO#�'�8y���Y�J�����s��I��
����]����?��G����}��&�R�C�_�%�iU,������q��[s�>N��p��^�4A%#<�eK~&���1��f�m�)��U�G��|�Ym_����}�=$C��M��]4@���������6�g�I��;�0�>Jt��������<Qz �/�18�$�V�����c�"w��y���j5����P3^��K����Kf���hn�4��(u!����,��:6���>�,����G-���
�fi���fT���e�q��\�z�,d�����=�~��l��3Z�^��Vn�{kat���b{��Wm�D�`/�A1��}��nb��{�*��r�7��!��&��H�"���Q�~��j��pg�����+C`V_�0�Zv@�+*&g�mr�@�Z��8>�V��b��f6��$.=mNm��������������x�1\s�(�E��'�g�z�����y���;��=��rE���y�-2�]�*���|��f������� �$J�f\V)�=�7��h�����O�QLi}[��9�6s0���6��\%�v������w�r")����M���2��L�'�EV����ep!/�-�����ArTM����z�s�9�6��N�B;B�k���&�(����Ni�zr���S��W��u�hx������q�~��!�Ov�D��$oE|���]�~�
��
�K�)3�vXt����I��J���`X��xIu�|�G`��Bj;������.��\�!���^/��J���ra�9�%.R������6Q4���M��0��������N���������Q�Z_2qv���cW`-�B�%.3>(�I�nf���(��(��`VVL��~���2w.�]�:��!I�$�������8�����{��X8�T	919�f���,���hD�}�|�5Y��DlE�*���=oU5���2
h�t�m�7x%18r�����G!�Fxg�4�{����p,4�W� ������xRl)�k�,Q�N��#��m�m/K���RLd<����fz�+�\��I��4�J��M}iQw���9A�HV����9hY�Nv�r�hFH�y�D���a
�"��a1>���V��q>�*.���*���'�a�$�����I�|���n��um��K\�+��P���g�4���@8�Q�X��7 ���`��Y��$��)no���}���-/� 'g�������f@��Z�-c��7>nP��#���H�����B�3.,,���YfXOI�����Z�T>H��_����K���]����qD7����y���� ���������cZ^���cV*�b}!-I2��\�[��n'��}������������m\��Eg�lo���'*V��3�
���%������T�K�f[xo�H;���z=�wJV��������~\*-G"i��$�A��r�������@Bw�Rs!Oj�R	��h��`���u�%x/�����W8���>NX\������N�d:v�7NU�x%��z��j�E�^1v-���&#;oG�����
���R����b����+��a�W��S}	ig��$�������D������Cy"�T�_@��
uC�1	�\1?�A���e��F���@���$�<�M����\�$�������SPVfuat�&�X�l�%�X-_�������.|{=��N]�I����~���H�'�P;���p���R�
���V����2���k����*��F�"e&~�3eZ\��B�����Jd��On2��UV���#��m�$Uy�3c*y�]f�+���'�c���5}��aWDId���,�_������Q���)G�=z����\\��2V������kDa_�a�W0��8�I���[���7)
�N��'�Y���������.z����d�'�	���V-�`�n��tI{�����w)8-4�D�������~����� ��I,�
@�Q;;k��)�h����0�!�7<tLa�9���i���L���i�J���Y�m_#��)�_���^���=��N������������|I\;�~{t\��"�����F$�Ut��XR�6���^��*9��g�B�^�|�w�#�z���.�?�����r|����k@I�I�6�kb�mr���K����V�_����U��]����x�f kH���-O�����h�e'`�4����%�cF����a��;??|{\��"�,�?����x�M��W��y���d�9���}������,��V&���2�����n����}��OF^����7L�$�"��$�O(���y��h����{�8�+)w��T��0��	[��]h.�[�;D��U��=������e��2���E���"/��jY/����N�<_<�f���o�7'����d7%��6d�(.Rci�^;H4g���J�Q!�uN9��b���v�\"�qIO�R��t}�7����������-�47��aA9��<���Qh��Y�DT~�~�"��P�3���w(�9@�
,}�5�,"��(�8��S�.lp$�+:t�5�(�G	��}�i�$g��O)'Suw�A�y�z)���~v���H�'���t7d�6|fH]�0������`l��w454��\Q@GI��wV��3�R`
��6w�����gpv����{�2����o��&w�#��M��X�%�a,-��v�;����A�mU��=��NK���X#�ze��aB�-<��W��W&[bD�j�~Q��s�4��Z�yQ:�N3;��]g�5��}N��YO�I�5{����9���S�����&u��s�@+�C|��B���Pa8������s��������R
k����>J�|�,7��]���h���.������&%��6���e������oN���������������
��0��p���1��"<|
��'Y���K��%HCK�Df5AdV���\���+�fp'�� B��pM��x��:�x!�m��2������h�"C�#������9�������v�����;�������A9�W�b;p���P��?�4�2���(������Q�^����N�ifw���iu&J��D�����[N��?R6����(��2�d����)�z�4=��1�4���4�p<��h����P��,�2���[�_�C�i���-S�Ggx"59�M88Q|�i)v�w+��V��CQ�i)�^���r2D_U�1�`=��Q��&��},~�-/aX���)l0��a�-�p9��34RU��8��
����>N�|���03O`�:���<�YS�]��3��s��&1�j�����IX����&��(����p���e�Cwc���t=6�Y��?��W�I����z"�}�n��Y��-Vv?�E�������y<~�6�?��*�|B��������9���9uN��3��m�A��t?H����� ���Y�����<��<������\k��D�����������	�t��L����i��:��?@n���S�Q���y���=���Or2��c��'�?R~y�<�=�B\��6[Rc�u���|��A1���O�����02.�XH�H��^t��<WPg���7r������7I�.@���8q=�"��8"���������s�d�.yVX0���f���������eL�����z����}�����Q���]��

*O�����2s����h=��4'a.�8���Ur�x�8Q*��3��l������"�D�R����A��9��x����cp. �v�k����)���sK�|f^�����5���N����}�Q|��|�����W�,0���R�R��36I��,x��G5���,��jS$��s�s����iVr��e>�B�d`1�je�p�P���S�<��!n&�u~<�:&��z�uOv8�h�����[<3g�N'���py���T��D5!_�l�����x4�L��N��g��%�Z�!���i�����:N�	��,r�y��)���b��|y�JQ��O�RO�P&+��G|nfM���( %K6���S��B���G6��r.&H� L�]���{Sv��V{��������\���IFPtF�-�c�{��,z0#j�PI�>�u{����wJ�R)|��Vr�h��b��Yz������� ��E�5�����m*����n������z�]@�,����(<����S{x
�"j�=��c��j��9�A�6/�����N|-�A���S��������,��R�7��P�x4�����y�:6��@81����/|\��M�b�����������K\.1'
*:l[4�����d&^��ch#>���������c���c���(����3k#Q���s��f��;:�WWnA/#��u��e��u����$���U{���7���B����c�t�	Fr��"��S�@�����O;*d�P��h��0�l!���x�X�)��yqd�|���8[/���8(�)���:q�c�����ug������|�}.~Q�3����jK]N+��\��j�^OZ��j�j�^�]�Ummms}}auuuz/+++3�����Vk���Z������^P��:�Vw�N0@1���,�����X��~>���q�z_'T&X�����z�[���A'������6�
���x����)�i��A�4F����qvh�����>�"��z��V��J}se<�$Y-���o�7�)�M�w��|	��i��V����LE�d���?�?�E��d���9�n��F#L���P`�Jn/�6lw��`��6��:��[�:�k��]���Q�6�*u8��`��}�q���;@M0�Z�.��^7�%�����wapx��$��V���Y4�o+�w�v��Rp�<�0�P B��%�2��(Q�{�$�TZ�c�+I��)��)%g*���d��r���^���T�d
(Q<5|����D6���U���.������\K�0�/?��6��7��}K�6��c��;���d�R�|�vi��:$�����@8,�9�s.ibpa&/]���9�����`b�gc/�+�l���;��/��bj��~&j�~���w�-�(���'#
�<L%�X9�bI���p�zm,-������"�%��GQN�E��?��:tY����B�D���omNE�\�L����9��S5N%tzK4?���D�ic�����qEysg�4a�X��.�c�����[K�B
����E��*��m?H!�U�)Y���i������@��%���.1�����}�~���������	y�9�������s4L�u9IF�����$�'O��yq�G��j��	Z>�1��@ �R��W���b����Q��^8����qrV��x��<z���L*��kh�3(�������q�K��&	��g��r�%����������E3g�`�FC=��ztw$�x����:�M�d�������'�%�7�r�s�I��W���8a��d�FzA?�@�hA�E���@X�������wtx�����o^4�"����d�	�����������0�Cz����H1�9��h/��_�a�Q�l�6��v��k(�#��Q�a��������[
G��kUAY���j.�]�+�=���3	�Z`��>B���@	z��Ym:w������70Z~�������
k�}�9��O�~k���;�����J�V��n$R�D�8D:U���� 6�X�O3�����V����r��2���g��4iv�����i��h�=���c���/������`P�����������(?��W��0�.��)28O�a���>�`������>���:����m#��,�2���P"��>(s�o$���O���_��l��H9�0�K
8���������n���,�y�=&e�M>*)��f�n��a�����
��]}�b��4M�r��:���gg���7.�&��O���� �L�or��9F|�q|���Z{37���*5<��^qYn���G�k@��'k-���N�_���ju������S����N�����q���NN�����T@@,�������&1����f����:;|��y�������P1���+S��R�r/��+4Y`'���V�����Vw]�&@D/j�LJ\�0��o�� �@�X} �r?$���R.e�f������q�ZA����\���\$6�h��u��\�
=F�u��"6D4i��F�w�����s@Y���`��I���zcC���=
-�g�F��gfrE;��>�hyZ����)��g�1
1�w���}��&J,pr��9Y�8������HN�������a;��
e8~�!,������:}��w��P/�;|=�_uJT":o������G�I����o���3	��?cbW?����%�80\���6��=��L0�$//e�''��O��u��|�e���G��\�My�Z�/�*p��"H�L�(�����o=E�(���Fc� (/�r
�cse ��R�����t���{�P 81v�> ���_;�*����t:���������][��
��D�g5�0����������	b����z�Z���Q?��V����m\��uD�p��T�+��3I���Mu�tr�o��]�����	~�������Y`Ee�U
����2�"�*���n��2������a�oG���!w��~i�A�V�(��C��f������z�3��Z����� [O1���6��q��m�f�k���9�p�:�3m�!����/8��h��Ir_��Q��o����������`�EGCmT�e�S8�5�dU�t�X�����G���dn?s�Nf����>& ��63Y���n?M�!4"4�t
���9��ru�H��/������v9����Z����1\�T�7����$��&����� 	���t���������d��>���x��
��.���|Wv��y|`�/D��3�����4��~�HL7L�`���2"8��Q����5��L��;�Gw��{��>��N\Z�����z>��0H���Wh�x�so���0\aN^#+��8�(��1!=���3cf�=n���o��&7���sC����f4��NA�!����bs���?`���L�^��rO�WWk��*
�W�~�s ��T�r�7�I������M����������/�Q��k�J��V�*kx�
�3�4`}V���*2�Z�]��� �%C�
���Dqd{	���?���P7�a�m�J��b��S�C.����Zl�o/��]oQni8�����p[j�g���4�>ohN E���e(�5>����hf�����<�P'�����s����Z���I<��Mj3�	�Z@��E3�U�B���42} � r������"�@��(��D(�2EVd�����(���R{V��1������d�1�	��6[3��Ly�]4�T����1
��{Sv5L�+cQ�^G�t����	���+�+lV���a#�\c�0��B�k���H���O��w�n������h7�
���b������sS�P����x������S���aLE��j��|�dQn�9�ytf����S��-~�iv&U��?gu!���F}�^��V�[[�����~3�p�d���h�I���e�����:�P�x9�0YX5���a���g���V�/j�p?��I4�G7r?���n�����B!�������H��'�Mr�����XW��(7��s�b�q�h���6��Z3/���%�!�omn!����@K����3����E��*�-1�Z��1Z��*��W,��o���[UX%.�DE��ov�]�����!���SJDl�����_��<3�B�MmW*�+�<��Y"��C������
��G`8U�!P����:�&$��}�����r�A����������[
��4Q�|s<QLF��A�B�-B
f����u0��H1rG��	'�{M�A�|!��I�8��S� ~|����'�{N��Y�#�vj��Y��U���V���S���}Y��S��.kw
hq����6]0Y*� [�����LN
��gCz<�`������wqzs��
]����*����M[�]]�cwq�M?g���D��}�R���[���~I����3Q.��9�g�7���B/3�"����KC��M�j�Tj��0�c�$�������X� ����'5 Z��r��G��9��Q�Q������yycB�������X	e-����W��Y+��7!�fS��ZV�_<d}K0e��1�r����;��<F����L5��>0"��S����J���y�&����3�C<cj9��[����I���Uo����f��d���e����;~0����g����+�m�����:�I��Qt�����m��RI!{3h7������$��oF���*����"�%0q�2�>�5��y^XI�H����Fe��Guw�=�~VQ�h<���?���������	HYX)�p�Kp���wtc�u�*���Q��i �/K�x	����u��P>:�1�
Z���.���^<�jf�sxH�6���N��\C3x}��3�&�|����@���,&��]���,z0Q����d*�?q����:�����M�����b��$E.R�+�	"�*����[���k)DCZ��N�x����F�A�[�'���J���K�j�J.(=/����}�Z�<�8�a"�^W�~�GfFb����k�������Q�_��}�d�I#�d=�d���?F���n;�'����+rv!x-��.4�0h�2|���U���#�7� n���NQ��:f^p�#��c�����PCN���;��2
[��1�*uq��;2���$���3�Z"������+��o��)�6�V!:����Z��9�����u��!Y_~�Qi��X�=|�(�/��Y�
���<���
��D��;9(���jO��vLZ���eD�+_b����AcDIe���[%��o������f��������_(1B����������0i�G�/�>�������0��������1z�@[y�UP?&�'p&����BAV����nbA����cw�#���
�k�����k]>���Qp��+��b����V�v��4k������
������]�W0��RQ74��@[�~��lv�K��*b����\t�.���M�V4s�.��r�������HW���)�1q
�fa���g��U�H�z����B�d3��
��������US\��1{��g_�UQ�j��J�v%L�4� 4���&[)s|�e�*"�����m1Q�o=z���Q����0��>���w�j����?{n:h�w����l�+����	����w:b-d~W����O��A���"����sqT[����?��������V��k�5���	���� �g�0\`�S`m���������^�9;y�������B�"a^.:#(���Y3�H�C&
`S`���j��#�(
�()���!�)���LT�Is���?�a��xF�{A��b���Q/fG"h��2/������:����z��/f����~Wn�N�!,��]�Y[�[��YF��VF}��#�$�daE�
����Z��������y�� �$R�������8��Y��&�����&��t����'������&E����
���4���Yo��~V�hqFz8�>���@�6e�O�2L��D[�x=/��)�_��g�2������!�y�}^Sc
=��P	,�:�:��M'dK��J]�U�d����s�����#?����{�*?�M9��|��2�������r#"�v0<���K$v<V+�2�A�����7��o����U�c�9����+m�����������q�!�/��Sw��6��x����p���2���_^�i����������:��*��o.Cl�y�������������gHI�Q��Y��#6�����Q���h�f���{C����g3f{�������N~�F�s��E�S���}���4����Jm�����/��?���Y�y8���8nD�	�;���S}0��)v��==�������s��#�+�/�����Vn�����(���f?�~{m����/9\J}��j,�����8�?2�
��	�t�<��Ma�"�~@�U���t�M8�n�����<4��C5�9;��^�y����x�7�;�B����A
SCw�F�$�J��^sG�Kl�U���4��o��/�.��`q
���}��|�m3B��pY�i~��<%�������Q�m�L�w����,�����$ME!y���Z��Ro�
3,��_���/,Jdu�8��{����c�X����h>M���F�k��i������Uc�J��&*"������[s�����o`-��q
��c}������#��=4,�9 S�m���f�A	>%��!�������C�.�Rm�@�k�����L�xD�3I������T��/}^���� ���dl~z��x��TaW?���/��d1 �3oy��&�9}������O�!�Y
�I;r��|����mo,f��9��LZ�u�7�v��\���$�2=����C#��	��gv����gtC�g�\#J#�_jL4W�h���[
���������D��3������������J�����%��%�]��3�$}��C���h0?����������?���cox7���5�8���dt������3��}@�����E�x_�.��f�O�(��
�4���`��;�����������.�D��A�S�;��t��>�"���gY�.��K��Bf��$�����E=�b������G���J��`)�.��)�9I=E�A��&S��o/�0��%��|
Z�_�X�������wU\����M|(��J\�Y��L�T/��R10�[Oe�=�����[���m����E[Xf��b��<�]�����G�
�ow	��H{�1F���.&M���"=�����|L�&w-��~k�B��4����{���1f���I1����N��?y�P��O�.U_&G����:[j�fO*"q��6����F��X[k�������6R".[��p�T�����>y87�����>r�7
-�����w*;N(�	����kO��DV��i��
����-���D�	QH�FZ2�>]�FH[����U��SL��vc=�����_a$�P�.�?' ����Dk<L��g�N�N�ZD;kL<k[�Jmm������r�3E�Z(��{Z>m�S����6��jl��*��u����V�J�-#<	%��9&�:B�\1��ZQ������G������%�A��"V?����P���Q����#�3���I�O��P����/Y��������E�_�����`�q�(��O���`��NG��+��>!��
���B�J��R��2���&���������W����-����#
o?w���5����5���h�:�z��l�����n�Y<�������1����1����f6���l�f�a����f�����<�[
#93Alvaro Herrera
alvherre@commandprompt.com
In reply to: Pavel Stehule (#92)
Re: review: CHECK FUNCTION statement

Excerpts from Pavel Stehule's message of mar mar 06 03:43:06 -0300 2012:

Hello

* I refreshed regress tests and appended tests for multi lines query
* There are enhanced checking of SELECT INTO statement
* I fixed showing details and hints

Oh, I forgot to remove the do_tup_output_slot() function which I added
in some previous version but is no longer necessary. Also, there are
two includes that I put separately in functioncmds.c that are only
necessary for the CHECK FUNCTION stuff; those should be merged in with
the other includes there. (I was toying with the idea of moving all
that code to a new file).

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#94Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#1)
1 attachment(s)
Re: review: CHECK FUNCTION statement

2012/3/6 Alvaro Herrera <alvherre@commandprompt.com>:

Excerpts from Pavel Stehule's message of mar mar 06 10:44:09 -0300 2012:

2012/3/6 Alvaro Herrera <alvherre@commandprompt.com>:

Excerpts from Pavel Stehule's message of mar mar 06 03:43:06 -0300 2012:

Hello

* I refreshed regress tests and appended tests for multi lines query
* There are enhanced checking of SELECT INTO statement
* I fixed showing details and hints

Oh, I forgot to remove the do_tup_output_slot() function which I added
in some previous version but is no longer necessary.  Also, there are
two includes that I put separately in functioncmds.c that are only
necessary for the CHECK FUNCTION stuff; those should be merged in with
the other includes there.  (I was toying with the idea of moving all
that code to a new file).

I am not sure, what you did do. Can you remove this useless code, please?

It's just a three line function that's not called anywhere.

ok

fixed patch

Pavel

Show quoted text

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Attachments:

check_function-2012-03-06-2.patch.gzapplication/x-gzip; name=check_function-2012-03-06-2.patch.gzDownload
��VOcheck_function-2012-03-06-2.patch�<iw�6���_�h��Hm�o�O[���[��������HH�4E2<�Vv��oU� H�����d����m�B�P79�l�Z���0��v'�������Vby�<n�'6���������O��~��>>��l�Yw���h��jm��������������a���������]F�.:Q�x���G��'?p?�V�q�v2s��\z�o/���G���#��c�U�/����_U`�������#��w�����B��5E�o-�e8�`����.:8��M3j�����l�pc���o��Y���J����f�o'n��da%���;l��H.�`��|�������������vx��d�Z�����G�;"��-���Bc�Ic���������A�����3�,N�E�_\�;����{������n�^����>40��O�}/�f����S������f���e4Y���i�?%��t�[R-M����'6����;��s���>��e+��}���$^X���B�6���3��yj��U8��.� ��J
^�WZ����������������W_b�
(O��s�=�}���F����nz�w��w}z�����uZ�%Y���E��R�\�f���������l��.0�%�#n%����.-o����Dq';���F��a�{�<,��Z�b�
��� ��T���p-"��`�%N��H����(�F�� ����j�`��h0y���p^�]��(N��./,�u�$���Z�b�>�k�Xj��YzL�"�
z"�V<j�[q�m�D�%n��8T���E��	�}�g�����>�������-�����'k���[�@����9 Pl���\y�jkU����k�4	�����"�lRd�T�D�|�#�O�*������gQ"�X`�W� �8�M���?q;M�\PYNt�5
�.:���:�'�v��<<�l���9�D�����0�<D!�Qj�N/�8!����^�!F9YLc����4y��d��������0#��d1��@�@fD|��1B%7r�P�G�7�V�Na�<��
R	�YE?��A�q�|R��(&���8�(����g��!�/v��$S�S�u��5���X���&�6�8��@�O�PD<��y�M�H?]Na�p��.���XK�9�'�|�������q�����%�5���
�<�`7$ ���'��U�1zaU���@�c�+�h�V/��PrX�,������ v��
Z���@RS�� ?��!w&�+��523.`��j<��������[���=i`�)�fc]���1�/� ����J#S}�X�����hC>V�#!�1wZ@��������4�^����2i� �m����5�j&^

8B��Xj�q:|Z�4������rx����-��l�������T3��P�0��K����%�u-Z/IJ�B���$��A�� �|6���!%RAV}��d X��q
^��Z5IP�@��@�DGO�r�n��]j�U���)������;���lE�	����D}J���R�X����o�c������j��X�7��,�69��B���MN11�<v���`��!�S�g�D.F<I#����G  w
s.���>?e&c;r�H��S���4��*���E+��r�����]��b��qC6��X2sAF�2-�+OZ���s��N�Lq�
� HFvi����&�k2'<<;p�O�m>;=>=�����(I�����Ou����c_�F=���[�gF�~�Yq1��{_�����o ���\����������Y��&�G4�.�a���/���a�k}����V6����==��������(�:�6�l����dRw��/��u���������F�Rh�
#�K��,�%�(��]���fG�n�L���������k�0w���JQ���M�����g{�f��
d�M����V��Z-�Ih����\�_ Bi��S7�!�
�z���x|��{i�����������}P��nl��,�����nTFv�"���j0$��Q$����q4V
J��v�~N�TV�c3�����"�CB�F+�2���Sw��N��-��viE�5ZU;a����+?!�3^���w��es2c5}S���v�T#�V�#���q��t������9�b�������n�D��e0�h�H5+�4����X�����b0��'�Ag=)](����{O!�Ab�V���#6��y�����S��e�F�$\D�]>R��%��k|�E��M<rT�R�;�����Cs��5K���.��5En��/4�ZX�������Pz]��L,T{Vg+�N�4���+����Q��b����iB��@���\8u�pK�]~���s���T���(�T���R"�_���j%�!��c�����l�R=S Ieb�X�*��1�@�
3���	�J;��z�� 	�O�)�l�Jr����T��2e��fIvd1}����h8��DP%�������d�!9�
iA�dH!����
"wiA�(2,�Q ����q�%�(?�
�hi��)c>!$<d��l��������U}�O��# �Y� 	�b
�DR��Y,��.�+�����5��2qA�Nc�/��<������<�g�S�K{n�_����s@��+�q�zvD#����]��FU`��x3������������H��M��X�*;���`H�K���~�
�P6��=T�Q�?�F����*����K7IP�Q��X2����G������V�	 E��$�Q���`���'�iY�(,���C��V�3Q��a�h<
����\�o8�1K���9��9��^��L�������F�5��59������d��2/���T��h�uQ�������X~�������7e�N&����#pbb��#4���d��Q��CrY[�-8V�|�6sBq@L����������\7�!�E��x���.�r�Ee�R�a�	�$�\\Q�;u���*�T����q(�C���Q�>q2�8�f;������2��B���"bu9�����v��ggG�Sr��aT���.���>Qo�gn[���	����f���b�:�{����&RRs�?��&}A���2m��=�S�������~c)1�p�S��]�o������oQ���0[�5�^��r����A�>"h2�/��'K3�e���Q�yE�#Y�~��RY���)��=��t�u�3����NE������a�==��83k���JTUqf,�=l��=�	3~1@Ay?�Nw�����?2<���]��������}?����mO��mF�������������{����c*t8}m�X�7�����.Hp]?�n���m��K�5���/	�
�e�k�ffL�Jb2&�{JW�����"���K�sQ��Y����2'S�e��Qq��8�xj���V�e��"i��|y�R�������1x���q��q�Y�K�7O5����7=yS�#���8=�|>`�(��V7|fo���U�����C,�O>�tm]B��0�`��+��#���K��L�����Yr����8��g�E#��nu���b}	Vy�]��:>�p�W57��6���/��� �b+^�_k����dM�.����������$�Y~!�`��N�^��9b���F��o�����^���u����^��?{��%���~�^�%���x��s�@�P��Lk6��L��h�����I[��9��	@dn�)06�&Z����l���&��d��^������2#���|���4�����
f$3�d6��PMe��06;��G)_�&��K�V���>0k��%����B.���,��H�������iU^d��l������{�n����m�r�����������B�������N�F~���'����x�*w��l��������j����o�&�&�6$yj�XUS3�(!��N��aq[@I6�����:h����Y7��m�vnI������&a��Y����FA��G��19Z��������~��](H��U�n��P����`���!������(��7��U�`X�l����I�`�{�4��w^0p��?I-�EA>����F�|�H����>���+����Z���(�<'n���t������	Z���o/j9�C`�"�����/F���W��jp�n�<5�:����8t3����,�"��-�_x�����%��5�!B���J���hy1��0�`��L��F=&��o���Gaa�)r��^Hw���;y�����C��[:LV�]�#�Q,��d�s+�������v��[@��G:poh���]�����f�^�N��I����1��H����Mz�
�e�U~�N��]��m��u^����-�o��*DF�g���G1�"��xY+P������agw�`�����|)��Q�2j!�����.�������`^�Z��n�����u��`����~�����!�p
�x^�i��Q�C��Zv=�B�ZS�K
��s�@�}����I� r%��{�E����nS`9����+`2�i��k��h�V�����0EWT�S�Dbpv|��v��������a���w�2a�c�����S 	���&>�4�S�V~�@�6
�(�Q`B��aw$w��
u�[G.{��h0IX����J�e�EX0�����V�A��X�ao4v[;Eh�l)�ER Fn��r��S�%z�7(*;���&h>�o��������t�!���O�l�{���G�W�����&��!u�O���c|�l<h��n5p��������(c#�F��"e��XF��i\��1EfE���?��G��{Wf�6�G��Q��g�"-�F�	R"�o0
���sB��l��.�/�r ��LL�M�D�%�&���$R����;(N)i]��~H���jyN�@Zj9N���_����&�W�J�"�s=����)��O�������W�������s�����jR�r@��[�K�
�{(���l�O��F}�6�ZV��1���Bq�������&j>�k0})�C@��|��.��:B\���LT��JF��t�D	���N��g�9������x��*=SP�k��Ki*��N�l�����w�������<#�K"�����H��A���s�s4~�h��a]x�����6,�#T���M(d�\Xv�&a��e3�:!Y��G���L~�� ��/�F�y���=:���.��8���k\~`+H�+���<��������mD���	u@N(��A�<� �Cl�n�)	��}��r�����r�$f���F�h@h��M3- W����@
<����6;VBT9����?�������~�Zg�n$��I5�l-����� ��/��u	?q{7qGa� u[&h�!&�g�$S�����\(l�5aA���u�*�����u3��d-�6�b�����{0�s������q4����������qt/�n|��~����V!T���2�a�X4��m���Zt@��L �3��|	6�@�&�>eh�WB%��$=lS��6����M�ht����>�cy��C]��z��)�����V�'��N�;�ab�0*
v�v�~���7t��Y`���J�pF���#�t�k��-y,	��?�h�G ��b�~�k�x�FH�TnZI����7�.�4�]���Z]4��&"�7�4	�������
�JLF�8�V4�q�'���������|��Z�k��e�a���o8�4^TSdZ����y�����\�(�G^%�H�GC!7�����Y��WT9�%;���kL������'���+!<8�o�����0q������G~G�>���EQ$���$o'N"j~�l�����,C�S�%.��(�~<�S��n�a���ra��@�m�ra(�W�vmn�L�)��Alj����V����^����������x�~0�����`8o���WLA��d�=���u�d��AN�;M6����_�"6 KO�8�O
�&`�0e���O���o����8�������p�X�����������\����
�z"$�F
a�|��n}������.�����VWWWU���z�:��.X����(�X2e]'#��#
c^E�ZZb����Hh�������r~��g����yxCC4:l����
��B������.U�E��?1���!N���q��o���^����|���o�W|Ctf���D<)�D��/����/f���-�H��/<A����Mvw
�V�n��������?��a���d
���D��owu!7�%��Y��@���T$g*K�x���r���Hx��Cm5��P�*uR<H�8������+�(%�pd��N�G����R*-+�(`���YL"�B�[s�5o�����7����i0o��z%MxN�[d����@���������@z�����@)���3����<w�Ef����3�:�lV����=��d�hR�%����~l�������B���]����\7k�-$Q�`�/R'e��d�����f���%��S;tt���G0���Mf�^�5����4�bm����$��E�����E�sO�pn�:��k��8E��=� �
�{( e0
�f��R���nM�UU��:��#�	5��ij�jg�/��������$:+��]I�puU�5���(Z'�|Vb��.�#��Fo�n3w�<x���`S���;��i�/�7�����X�C��X1�r8o:g�T���P�jNP���U�����T�2�-gRT������Q\hB�����	4g8G�1��S?Q6(uj�=����Xx
�8H0*�>�m`��~W��w��]��+l=�U��Biu���&��'�?N`O
��n��!�&q�h-�����*����������n�j����DSzW*�[7���*�[���%�������#�q�tQ��Z�����������������d�dC3���	,I:���C���]������w#�
����sJ���t��[(h� n%�h�`5v���e'_��Fz0O�Y��kI������h��@M�peA��� �j��.K�W	���~����t�Ju}*!��l��Tg�U�����wq�KL��`��0a����F_�Fz#�H%��~w��<U�>D�u]���~M����������R��g����i��]t�X.j��,f�����K4�~�^�:kJfud��<�����94���a�6Z(��/}�g�{G���Z< �V��>��q�h�%���v������?i���|���a-.-�h�f��@�`e�z��{�eA�����>8v	h7�9�&��5�tv���d�����~8��U���E-��?r}���&:9<h~c�����1C	0$v1��G�LM:M�d��%f��Q��Pt(lw��K�2��Ex��A�������r��K������������lkI�S�-@K�>u���a�>���A������{L�L�){�0C�.�����C��zz�r�x�2���A>7�
�����|���_�:�Dh+_����/2�=�7m��|��E#� �{L	4�������!�W�ZI��c^��~�g��&���A���{� �����wE�XQ���(��w�\�x���26	����6I2����%����>j�����'�m?=�wXC���1������F�
(L�py�V����$��,C�����a��q���BF�|"�����������1�{;=��>)
4���m��w�wt�6B���c��;���u|t������4���k�N1�b���k
�%�(��=�� ~�oq-Q��u��Q�C�/z����3P���`����_������9��|�~�|��m�#�D=���e����"�]�4jp�����E�����6����'=�c�i���d���f
���c3�����TqF��a���N��@�$ wd��'Y�����9V�oV���9Q��4�AS>C�����_j�@�Z���i�b[�T��ql`��J������dC�U({)Y7�������s����y�O��6l���8�a���5^��}|rq��t���;ZZ�!����"%Zw������v�7{��9(h��]��c,O=�gi��!(G��C��)9�3�`��:[Z���/�q/��Y/*�Q)�:y����z��_�eFQ���^VY?�O�@����b,�z*�����]^���{	N	�_&-L�����;
�
W�5���%���E���Lc�A�������ko�(#����yfh5m2(:�=&�������$����0JDL�<�������hXX�=q<_��_T��a90q�3�-R%�~�z����"�8.�Z����m�V���UT���{�_�*����X�� m��������t�;���������E���	�����b]������%Gm���e%�#�qQ:qK%���'|mb��4�H�.{��T��g��CQ|��u��{%���JA�!T0���*�S�p�Vk|K���e~�����]\�m�c�1{I���F�[
�zZr?���51-�FT�W�����,��Lm.����v=����^b�`�Q[��t�BJS�����2�o��n��+
6~���4�V��2q
Tg,���.�=/��3���c�m���P��%���(G�)�NG����0$�Z*��������PHVR�+l�%ug�L:����3%�/(y�.�]��T��������uG����)�A��?Z���*|��g�u���F�Ic3����������8f���
��1�������	��t]Ge��Vs�J4�a�a����(�J�Cw)��h�]_�I7tb�g�c����z�=��2�Y��oi�|(��&34����@�H�����I�C�
�XOO�kA�z`�����!q��f2;,������"����l��"������T��������^��$�6�U�hV�;���C�>zsr�������y����H�$�XV�,
�����s@N��)��c���L#� v��N��9fsU�sL�<���C��E;d�K��9��kY���_I"+#O���[4{�kv-��s7������:�A\�%�7�p�?��#"���m������q���Q���"|�llP���fm���4����b�?t���D��KG���_v�����������%+tgKH����f��^�V�7�6�g
r��1!��S��mVju������o�|���E�jQr�.����a���w"g�qx���[-��{��@�Wi�t3�~��@3���~����;m��mh���<]<�n�r��� �`�����WT�e��n6��D��Ka\�`�0��HG"��%���B���2=�Ht�)����K��{�<�
�w�K�����%H��f�D#y^89�l����
6���S^�����6�*;��fc��;�����hc���?��#��rO���G��;Ct��������9�A���B}�P�x�?��t6�b�'&�x"�sx�Jl�#��v�M�`�(}eL�i���I������z}���@82#�2V���|��~��9�����	e$�Jw`��Rt[�b��zz��P�e�|��H�z"������	�������Jk�����gV��V	��7O�jRl�h��<\�4��U�;;��!\��ON���(5���Ec�N�	���Y������_n�.��w/xZ�?�f�Q�}
�P���W������e�����1Q���	�������`cz���.\�]���
vG�S����8HF�U!*7�Y��x�Q����>��i6xe�6? gU��������y�����l����GD[���U����2;;�L&"����I��nC��a:����������Y4S>�F���L���;���:�o���++y���N0��~g���]M5�ob+���~i�:�����%+|��	�T��|�����*zzJ]�v����0B����������{D$���0���n�,�I�4��7M3,j��Sc���hz�fB9���\�u��J������
C�V~	����o���R��	���'�l�����Yn]\�������g�~dr���g�Z����E���{�����������iq�4i����}�A������(��5�"P.sNW��pL}{r�wqx���Z�xe
��/�������/�X������]�v�RebyS�Z�vB��������`���2?�t������D�w�������3b9����X�:�2��U�,�)��%����a<!.+H�k-�e�8#�9%]������o���l����������j��g��I��s�L������Gg�V���s
�
]�S�Ng���M�D�1���J�!��BT��l�/m�-S"���__[w����n���N3�fA�C�]h���������(�BC@�����?j����/��p$\�6gU�C�Y���Nh�!W����v�~��)7���v4a*j��qDhn(nuu#|�J�C&��`���l��5t4)$�m}s�^��6��%���(/q�k����_���I�������*��y�<>h������;��s��.�M:�q�l!������*��X��{�R��C�������}Yd�[���������G����h%1�
/
��
^'q���`bj"�����/��A����Etzr~�5�%����w_x�*���g��6������I��A�@8�sv�����8���j��������L<
9��q�2�J����-���
���_e���p�W��n���K����5r��y�Y�N�V��l
o�L�9)��z;�V��������J��	wJ�_)mlWx���.��>bNAy�� bj��h�����f���y�������{�>u��H���k������;V}�Y����)<]V�N�nw;����H�����,�S�����s���C}��G��@s�� C��KX�#d`�&��q~���k${
��M�X������3�]	A�l)d#�T8��+��{"�u�s�~��y��2N���p`�J��v��'.���\�G�8L����)s�6�I[h1<���>%���(Hs�(��\��p"$�[��s&�Uh5�}A��9�];�by+�
�m����/]�������U#��{�-���n���
Mz	'�������c��R�jQ"U���k�M��u<��P�����X �3�����?����� ��.XF�l����l���V�_��b"n%Og��������I+�	��.����o�0K1��a�=\h_������n�gEh�23�KdGc��8�$�s8{��i\
�R����U�m��	�4'�>�tn1�����
����r�<j�_H��*��M��b�rf�V�9�L�<�q��4�����	 �MHl�d���4������-NX��'9m�x�
���2�������h������;��	�������6*�~�f�\��P���e��H�	<���>��I�>�^��ln_]n����v���3�L%�B��eH��E�+}�**�2^v:����:�\-��"�*tq�jW�8�Z `,��B=��HO��q���1P?4V���ym!Y�'�(\4�7���/pX�%Z\�@����+1U
V�f�N�����3y��l
Vw�I��<k�
��	�V�h;�^\��c8�w����\�ml�N��U�t�
��	/����,~��Iv/�~�Y��� 5Cl�c)M���Hn�y,�&Wl�v�<���4��L��	�G���[@�w�o�$��j
���jL��^E�+D�X}��^�g�$��
���c���].�N����z��9���"��Ro����{g�	;7�����n���U�lai`�.�5�o���	{Y,�I*s�+�������H���c`�F���U���N]x[�4�?/���m^>x����9��w��Q���L�����z����;��;q�[m8��v6��������ou�=[�m�������������_vF)&�&*����P�~K<8\��o����4-	jQ�@��!���w1���Tr����M/��8���	!L�8����:��al���8��z��~��l�r��$�+Ng���U)M�9#��#X���tT�<��F�?J;�c��3Ju��(��>d�,(A:�p�R*�|�������K��|2���H��.
+�#mB����|�S]5
���ic��y@
����z�NA�J8.�Ox�'LZ�DA���5��f����uJ�����Q����zU�#��Q�����9+y+��m!y�W��(�H~��������a����\��y���#�RXf�Z�pl
1&d����C�,���!�
��F���ZCc/l2���f���st���=v�qn{��I`C��d1�>D5&:G%���R���H��7���Qo���������<�Ro25fKtW�Bn�r�]-:�5���:SGn����KT�HR�?�C��}2p]`{i������]�����w�X��p���R�����g>=���ij�������s7�=O���.Y j�T_����������z��n���_nW����E���^�/k��"����<��:~k[�{<����|��������Zy���F�!��8�f����������SzX�@��8�����Y�������2����)��e�� �c���]�!�1���bz(�@Z����a7�L�-0c���7Z��G�1k�t���S�=_X��
����#�`���3"
i@��>J����=b�yN�Tp�
��hO���_�������	b6������	�}��q���2>�&�#��/���t��-�5����P>t3p�����������y�^��y��90
�����|�|j��r���I~�B��}(�	P�,��eE���(��[����;2���B�vp�j�/kK��cC�%�
��C8����*7.��C;<�N3'T`�86��c�o��mh�oK�A��
�EO�z�����'�T���;[W9�=�s[�A����;����b������(����g!Tl<��7��\��p��f�`�.+��}�~�xd��~"�/���[2+���)�G�X�d���WP����h$Y]uA��7�j�Q�?x.7p9e��RG�����e����Z�Z�`Z����e
 ��F�K��~�)�"��S�?pq�/�=���kz:�r9� ^R���XS��}~��P���
�N���G�GF�6���vx|��^lw��cJ*6oErn��}���}��:��-V�
�8�s���Y�
�&�9O;iu�3@��;-;�i��W��\�$kk�$�]��3@��LC�;�{�N���<���@S0ei	.d_2�R�?�aE��N7/����D`1w���/�� ,E�0�.A���S��1��h/���x���l ������]�N�b�?�Lh��;;><~[Q���
mN�������
����7'(�w;n}C�����4t

�G��#��{pv�_�	�������hO3��f��c��{>���������F���]�^�h������/�.��F��sf��bl4�(��
}�o�j\'#</P��������/��?��v�7�5pa8\d��m��_Hp��
U���>7IK��q��yB���oR����L����3G�g�����VrN��<�E~�h�*�G|��m�|�1��z�R������e�\��'ts���P�Q�d����o�'����q}=��J��3�L�M�<���Y�,��DUl�H"*1���'W��J�e�8����~��W�bI���^�7g'����/�{�<#��������_��3t]y��#�O��M���&/��#�hADi�C=d=x{m�ztch���6�E������t�*��j����ch���U�,;�Ct�������|��`y��3e��2\A���=�G�(�p�1�J^��	��
99�gi���>~��Tm�/�_UT�
�h�����t�4��/2�ws���Z.l��lQ`�g�g���
�-����e��1��Z�L�l?v=�tbb�S��(n��_t�$�CR�&
O����!�lo�	A�7;����������]b�r��2��,�����a�O����	����\�B?�����s�P��E��L�}��77v�5}���d�%�$���B?\}E[���3�e4�yb4�^�r�wVL��V9�8���[�#�
�M|h���y}�fv�[�������sh���R��5"�3�[���J�]Y��M^�����V-�o�Psu4u��V �;��{�����}�*��')����3�|���������k�
6��ej}
R�����`�S`<�([B���+���9����(/�qeRW����?t�9� �Cc��� �	���_��F�p���a�O�����kb���(�����a��@��3^���0�D2�����Tb���o
<�<-�[7Y�2>�3	q7�������U�^��w6Z����/X{�w������[������5./(�K���c+��G��v��E���"���XP�"r�7�5��)��uX@g�E�t���s`�Wuj_�������Z��]^V����,J�B�g�"P��o�u�R���1���[ay�J���z=L0����[����vzq�h�a�����[K
D����Z�^qx��_�]u�J���W����"6hek}���xQ
+{����t�+�)�w���X(b�ft�V��Gp���7~Z"�[��r�\J�$��)tZ�,��>9��(L{G�3�r�w��������o������c*�tr��/����y���M�ydO��oJ	�z(�N�O�7������������w��&@.�h*�}�������}=��NN��yL��7�?p;���_�4/��I�o��xD���M���!��
���'�������~C�>=k���M7�u��V��� ��Q�52�3�K��S����������Nt���]$s�aP����Y,�����B��
"��F
�iMg����h��P^��j#?V�2�|�:��c������1�fF?�8��G#4�	��8��E����������������o����?������$�G���p��8�3Yv������ku�������;js:O,��/��d#�V�V�*�[�G��rtj��UC��%�X�)W�o��K6qw������Tb�f�r:xO�8����&��^+��B�z��9������!�W�_�+�YtH�o�����%�|�74������N�)JEE#��3B;�A�7����+ f��N7���]���a�J�txJn���:�����*r9�z�/�q7t���������B��t��f���qU���h���#����2���=�CqT���6�1c�R}r)T��J
�����f�R�=j�:�x^��2$B�Risr1���-���3>�'��!��R���a�������3��� _a����q��������7}�juUv���N�~��W���W��o�cgD���5��6����g�E��7Z�Xz���V�'�%z
N����p�����}b������E:�p��r?i���)��co����#�h0���6�����`������t��	��,���2�uK�!��_�7 a]n_���g:D�v&�^���-Y���K�����w�DgM�xrL��;9�p�A0�� A��T*����N]��������@���D���g�oO]���q���X&L|��m�!����yb���(^���'�-:�Yl�����L����	|$��S9SP���Gs�h�2�gpBO++�W*�O-�O��A�)i���!��T
�����Gr8�69���}z��>�������Z&=���;�)G��;��cb��!e��@����!���w�j�K�I�3|��v��|����9��t-u��i�MmyM��-����A2ta� ��!����a��{v�E?�}��:?����M�����w�~�)���4�zv��a���Q��4�i
�Q<����4�=gKdz�d;��!��v~Xs����0��<[�!]<�N����r�<��� >��$��������q�yz*P4�(���f��mtzt�|
2I3z��~����I�1Ln��Y��He���e=!��U���� ��Z�c�2|��W
$�Y~������>zG���"G�!�Y�Z�Yn��&�t���{�N��A���z�?BG��Sg�m�	���a��m-�����*�Q�2j-)�(���1���Df.�q����#����T���T��>�?�t���Y��v���n��a��$lb����x0-/Lul�1�����
�����E�q��y�'#��;��;U^�^r��������7Y����AL[R2t��)��2[��w��]F���q��oh�pG�:Mc���������N{m�-m����m!��_��+m��t6�a�Fo������#
bV��N�S$�B��a����$�sF�%K�����B����/��	������omn�T�W;����V�����/M����6�Rl-?���f)�������q3;��\��d�A	�)k�l�S6��~q�48�Vs������9i�o��u(QqQ��><�T&�n���Bl��P���{���_�X����V����Z��0\;�[�{%���$w|�Jz�T���x���U�g�d��9WB��8�pi�����:��]�5�.[��[@@_nll6`����;�rh��^!�����Z�>C�6@��YR9
i
�T��.c��*�|�XK�4�����JV��gk�WL�37��	Yq�1�i����p~(�_�d���T��3�(�)h}m�c��moJ����0[�=�-�	�����P���t��%2��\'�����M�����D1ec��KYQP���|�n�gd.���>�P�Aj�������-�Wf�s�]���,����F��/�8���&�������G]a��3�-L��T�K^�����w�������]L�[N=��?��_q�/Zd��n1BJ�G�k�0�c/������i������+%)�\��:���N��r��� ���sW�[?��fo6j��v�Z���o]�6�����%�E%8=����gH�O/q������hc��(bb�	��x�����c��W����Z�N��(�x�>�C~����kU4�4��A��9�N��%k&�R��
���!���v;�!F!/=�V����!]o\n'k�j��\nm���
��� �c}����zp}���
���������X4NVvK���7?'�L�"�
����5rH��r�;s��9�)��N�� Ac�	���m�ML���K������6��5K%�r�{v���
S����7$�*<����8
"\����	�T�j��c�d������cH��Q���4��:@����~�A(����'�>�qN���RUJ=�HxY�x�w�`�)O(F�����$I�)��)�F�� ��
��3sW`���a���1O���0:�z]r�a����%����m8��PX/:)�%p�xFV$z2�
c�gF�VUK�l�Su;n��'�et�>���D�AH��$���7�J�=+�Gg�I�.���dF��4�^2"�<����E���M%G���%�[�,����l�*4�9��V��c��<�: �AU�w��
fCI���]�[�p��	!���]��H�wc/����V��J^U�h���6�ym��$T(���e��9��������:H��~�M.k���Q���k���@���&3R�Cq��&~�sqg��F�5�0�GYb�����-m��s(S��Q���axu�a�cVK�,�'�0�e�PO"�h� 7�w�V��W'w<L��E����f4��q<������D]��Aq�<�4;�I��'I+�<Nb��y��������j�,�Zx:}���Y!e��}�C�TB)�������tL 2��B������PN��3�9c�� �`�I�������x�ah4�����A��
�~xEQ�Nl���71R����mh�{q*~��i��;D�Lg<TC'�q'q5������Yr��&��3H8A���8��w7���=����������dg������n���4I�V���t��Q��"�34�.�4�����3LG\a
'H�JO��>�0��If<��km3�mRP���k�B�Lr�[�Tso�<
��(L[��h,��|�����H��820���>w:9F�FH)�Xt�S`.|%���zP�����{\������'����6F�
�Y;�����������{N��HuX\q
q�V���\g`��Q�;R�p*U~�`r������tW_��V�V_�������aI�k�`"��H��'
�0j�MG���L�Jz�n��J)-���	�p�lw�-���N'���Z�Z�nSz�N�&.�SO?��_�hD,�G<(L	j�b�a#BA�"(�u)��	�M�}�#�q���E�ou����7������������������~7���&+�x��c����5���|��s�z������n��{e�h�6"����aWlv�N~WX*
��%5�^���r� �UQ�z�����p�$:J�{���5�4��TEh�G�5���y�2��������,y7/<o��$�<�D��i��������YE�|����������))����'�~�1%�]��Lt1�2����%S�����V2k��v@�:�����s�c����d�
J�2�=]�;�o*k@^q'��K���8���/g>{��;J:B�*;��-T�>��%)���M��X��t�W����(��_�/pe6q�1q��%�1C+�N�Zj�p��7�A�_�W��;"���4b����V��; d5)�$R�����rGa�p�QS�l�=.
�VV(@������A��I�"�3!��|�)��YO�3]M�Fc�J�!�k*�A�d�N(`p�!*1��)��a�zt��O$���b��RP�2�����d�nT98��O0/dl�H/�����-�����M�����&sNN�Z�;�N���6�eA�g'�e����$�{��
&��)�H�@����>��E>�cr��M�Nz�����u�m�N��
h	���6�E�01������3���>PFR1�����e_�X�Q�F��`��"Jn��B�������U+�&<
��Q"�f��cB'^5g�n���Kg��YYkD�`�mzj#r�N)Ut:�3������xs^�3:�*����I����m�5�
�G�s��g_?: v������L�_�>����%v�w?���_��c-P�ot#-�
�COgm����
U���=7v������j�����t����'�&������q�����qS����WLh2��a6s���E��7]��uT��!�-����W��w������)���y��W��c<�g�����1����M������`
����E�O�iqbgodU�!�
����R�1��5":]i�u#p������}'��/�TJ.g��ifd���
pi�(���BN����NN�}��iAC�MK��=j�/�0�P�`����1��"����<����*}��A'�|U�y�*�O�n������8���UA��������M���{�%�NO�������5�y:��f����3���%�bP�"(��_���-*�J��R�~S��$-�i���?!�1����9���rTp��%��j���t�2�[������T(�"CX*����LZ(���cLK@!����QPZ�;R:w���6yx��
+
%g�ND�rNZFbD��V�[��n�:�����^��F����Ov�tw�jhQz���A������da
<��!{d&��B��	_��O!�k�����DsQ�Vx��=
,�:�fl7��e<$�T*��D��|�.�$=�@��<)!��|���E�����Z��x��� ^
��?�_�����Fc>g{eG�&u*�@���9��UHOikM�XR~�1���������O."Lwu�<k~��������]4�:0��+��3+}�O}�%=Z�e��9��n��m+7���z�0����5�����W�[��&1�m-�R�����8|��w��>�����I9�Sz��0C�� A�)��AKb\)�T�U�;A���9�g�����]�r��vk��IQ/���f��?!z�P�m?�&�?-'�	1���A���q� �?9����s�L��L���������{��;x}aNa��d����C�D�h�TF������c��{�=��`��	�>v;���%-/r~s^����M����,-��c�'�6}���d.�"&��Q�4q�h���v��&
��=?Q T���S���x+c8u���� T�kp����F�DC����Wh 	���i(�&�d��8G��������Q�G�.�����x��.�j����n�T��=}��'���.���Ag�Ie�O�}�6�#��l�~u�|�}��`D0�E�����A���fe6���������l>�������zVr4��N�d�2{y�+���Y������~�����Kd���A�6��s,	��X�;�3���j
�id�4�,�J�.����=�{�?�����7�������4���t����r�,����Z�d	�OKd�52*�R���d.��Hb�{L��g�W��X�z�`g��}�����6d�����?L�V1�N�����������8�+(�����_WM9�]��2�����tZP1[~��i��En�����e�6rr�4������O��>d������Pt#����.�Dl�s ���������T�h������K3&�eEoO@�H���+��[��C��[:o����B��=n���JMkLt8B������fG��3��REz�k�7/^��cU��p���*�um����>#�NV�{:V]������]���8T����RK��.>��53
!�T\�$��;ny����m�����%$�-����
��<p���c_h3��&-W�\��z:3x�m���8�\��^X�dl��R4v<2h�
`�Y�/����j���
o�W_�X���^��N���
s+�����Z�1;��?A�Z�D�f�gD�4�P,��l��4fO�i�f�F��,
Ts��m�K�����"��i����i�v��'wf�&d������7^/���1����Qu6�b��0�Q���K�����T5������xJ`y��������\)�'%�>k������^�|��&����)��t4�llN�+����������pz�$���p�e6�����dl�E�������V_��/&7�@@gz
��C�s�}�K�� `����(;�����0�m��P��5�-NL���z���vF�Rj�2��Kv�����N���3���NO9v����R�����5�k9C�����0��|�Q6\�����f�����8}]��P����Ff~��y�d���Du_P���]�x�����)���)����[��
�*���D+����J���>{[����c
3�b&e�J��"�����C��B�t�n��).��>�f���J��J�o��Fdo�������������>~-~N�����q�:���#�"��3.H���l!<��N"A�7��9��@�J�<�i��/rQ~g;���D��L�
a����[�Y�^��x+;��5��������&]�8==9�pUjQ�h����t���HEQ���u+A.6��"�P-/�-��)����I��(Y�r�7�"��okeA�5���Q�}7kA{
U=�����*��KGI��r�=��%�$�r�=��PN=����x�=I���\���W�M�e��#�O<l�'�f���_���>�m���c�5)�*���,YN�b���N�P�������q������2�	*���(cX�3i��y��7�n�O)���>����j����5���!�em����7�gU7eL5��!=+M�}�����QZ��
����������x�����\ �"���&��sM���f�P�!�������ZG_Z&�\2s����Es��L�@��4�7]d�o��iT��)g)%�8j��WX6K�<m7��E=-+ ���/����f!#��|�A�[�(d��6������X��r��[�#�l�k��j;&{��A o�X�H�tS�;U�=������A�U5a�G�����NT36�383��>^�����94���_Q19;n�[��BE�����bf���f5�A��'q�isj{���OL� ����������3G1�(B��8�<{�{l�>���'���n�9H~�+
V�u��h�I'��W����;�6������=��f YPj6��J!�y����G�u�g�}Z�bJ���f���!��I.\�i^��B(����V���#�I	���7nZ/�H�90,�0`�<1.�J'G`/�p�yo!o����j���<m�C����)�Y�t��_��v�7� E����tJ;����~��
�����;D�#�DO�����{o}��'b4X�'y+��E��R�K�hX�n�_�O�1���Z��mdO��W����r�K�+�;<K�&R���U<�t�u�����V��z��WB�D��;��-q��2����p�������lP&���7'g���w�.�E��,��� ������g�2k)h�.q����A��O�t3���E�UD�E��b������;pA���y�I��$yO���}������,�����!�J�����0#d��gi|VF� ���������:$b��(�WI4�E�y��$t�i@���l���+�������-=
)4�;��������c���������g]N���bK�]�g�2w:��<n�m{1Xr(�F�`"�A`��H��0��^����XO��!W��m��K��������rE�b�c�}�Y@��v��{P�3D3B��+'
��kX��
��a���J.��AUqUgP��}<IS� A�|��O��wfvc�Ch[N\���h^����LL>k�a�@�������O�)�(�;\�rT&Q4�Lq{������oy�@99;������/�4
���o�����p�Z��PE>G�?�N�&��qaaY�P��G�2�zJ��7]D�B��A����D\2���J=4��; ��0��^�����iN^p�����X/�`��RU�R��i�H�Yu�Z��
t;I���u�d4�7���g}�n���/:Ce{c�^=Q���Fp�aVg=.)�@'��Z]�4��{�G������1��P��E��GD%w��Ri9Iv&�
�=�[�G]��p�#��yRh��H��DS�����.��{�l�]Xv���A���C�q��b���^Wv'��������q��:�+A��3]U�<(���h��6�y;��>/,m`WO���@v��� ��]����h���KH;#P'��.DO��� :o5�/����
��bdoX�R��I0�����G,��42���B�<=$q��oBn�p�2&� �T�l����2���35���d{,��j�
'X����vu���!Gv�bL�]�
7���F��=���Y��X�#�U�JW�|���W=6��4O_sg�8�V)�4�)3���)��2T�|m,P"�lr��F��J}=�?m�'����S��(��
0�]�l���8��N�$��;��"J"�e�����@�5d�Z�=O9�����o������J�u��.^c 
�z��;��AF�	Lz�>D�
�pX��IQPu�<?��ZOw4 �}���w����e%+>�O�����zh�(w[�K�h������K�i��%�h�f�����]�G'�L
`1o����Ys�M�(F�>�������c
[���O�L3��@eJ��N�(T�����m�I�M����E���d��Atj��P���/���%���K��������r�Y6��m5� �������	�&�:nU�����8c����������CG�u�q&O�����l�XJbH2�A_�m��
��X�����������7��m����5L��4YC
�ly���S\EC-;���f,-��3B��u5��������b�)e���Eo]��o�w(p��w�{��&3�q���������e����2�����I�����v�����}2�'���az&I)��%9|B��o�{�FK4�^���q\I�S���,�i�M��g�Bs�*�!�j��~������(cm��w�,����y1^xW��x�'�Tw����aH6�=|S�9��e�%�)���!sGq��K(���A�9��P��
i�s�yk&��s�Q�Kz����������q(��f��d�l���fZ�!�D��-�$���B�{��b$����k����J�9����C���U`����f`�EEa�!l��ua�#�_��#��F�<J04 ���N$9K|J9����:�C�K�&���Sv��D�>�����![d��3C�B��>�V/#`+������N��:J25����@6�Q��P8$��;�t�<����-��u�	���}#o7���(i�m���:.Yciq����s�����Fm��(���TuZz�L��1�+s%
B�l�i��R��2�#�U�������&��������Ytz�����r8C/���s���z�N��������q'�*���w�5��f�cZ��N��D�
������+G''�|�L�jX����Q���d�L���w�G�4vA\v�gO5)a�����x,K$�<�LsrvX<���L��fg����<VX}�Q^�3N����`��6���k�m<��O\��.AZ�4 2�	"��Wd����W_�7��;i}z�k2�������ns��ge���dFkz�x������IN�mG�O���'��������nev�������sf<�?�	������G!
�����������vBL3��c?�N�3Q2-$B�
e<��r������,=�G�f�'��'&Ma��(�����i����������F���,��~f��)`�������Ok�m��>:�[��!th������NK�k�[q���r\��LKQ�b=-��!��:�)�i�����7�P,���c�smy	�2m�La����{m	���������z�����o����qj�C����yk��
����������N��-6��W[��df@O��]�7�@pD�e�w�#�%?.��3����������n��Mz%�u����u�O���n��+�q.�N|L�\�������EV��&�vF���U���s:m�uxo;b��A2D_g��0� ������t��Q�G����Z�%�%���t�|h��PL��[v�`Z=�G�H�����
�r������*����������
~��y�{�w8������������������-��n
�a�M~R�`����q��B"D�e���[x/����:��$��{F4��I�v9JW����)�8X�AT��g=%<���d �w�����
�$0Sg�V_��,.c�5���/�����3�H-W������:?mhPyb/��E���T?��D+�����9	s��Q����;�s��R�@����}^`��nd���$r��n%/j�9\�3���s���_��%OY^�[��3��<<�����t
4�o����s���t�$���g�)U������Y�Ir�d�kn=���g�GV�� i������0��?��Oc��k�/���%��V+�3�������A-fq3����	�1Q����{���G��v���Z��9St:��D���C&�\����&�	�bf���OO����xd�wwz�?S
�(���
A%vOS��'�q"�H@�ga�;���_MY��}E��#W�r�~��z��2YQ<>�s3k���g@)Y������p�:<��/��p1Aa����8�<�c��s�������'�}���7N2��3�m�S���e����P��J����������f�S���J�[����cE�DK����8��6'���v.���m��oS���6vc���6�c�Bf��v�@�y�������S�hQk�a&k�Ts��I
��yqp���x/�hw�k��WvG�RE�=�<L�eY����Qx�"��)���Wg(�{���?��qLU~��2o���&\�&�%��|�]�r�9iPA�a���F&��'3���Cy�q��_��������d`�E�.�����X������<5�������rz!����.�u��OL�%�^�����l���@=�L�S��M0�+�u������}�Q� �P��D�~���g1�������M	-�[�#�C�|���z�M'�A9�@O1��q����VW�;#?O�����s����'_V[�rZ���Uk�z�j\U�W��z���jkk�������{YXYY�����o�Z�����
~�����ZP������v���f�f����-������u��{���:�2�U��-����"�:������AV���X��N7}N�N35�
z��0�_^��C����>��������+
����W��;(��&�jAn<}��pL�m�����KLLC$���`*"%�pF����-�'C|F�)t���6a�����Ur{��a���S��Q���l�����]s��JU�:�IU��������D��j��bv9����/	G.��v����7'���rf^���~[X�����;E���+��	�����t�(�&�@���C$��`����^I
MaFN)9S�N�${�������������&S@�����p<���$�A�t(��
�u1�\_'��(P�Z��I~�!��	��o�[R�	5�@��W� �r_�C�[`H���!�u����a����sI�3y�B�0%�)=M��?{�_�g����O|��S�d��3Qk�k�u��n�D���<Q���a*����K�L�C�kc�h9E�'-&?�z0p�-:�G�����5�%Z�^|ks*J�j�`"�>m������q*����X�����%2M�t��+��;s�	�����p�E�'&�Z�P�6�./�V��m�A
1�bO����N��0
��..�� �v��A��������ku���}��d�L��������$��a�����H�0��%}�'q>yzv���?��pVK�M���y�������o�����%�G�����y���e��#����[��	��}�eR%\C{�A��lu���c]��X7I��?�,����(!�����������(�9s6�q_���#1��{�P��	n
'Ct|p��l�f�8!/�4�����[N�E�����H��3p�%[7��I�@��.���G�j�E������������t�l�}��y���%�N0�.tFw��E����`��@�;6���mF�	�� �F{�����2f����3,\@I�����K%��^�|8>��R82���X�
���Us���_)����I(�C���&�J�#N�j������N��}����N]f���VX��K�y�~R�[���%�����nV�b�v#9�J%z�!��/��	�*�4x��I���`.�b��D���iD�<+/�I�C��WO�M�F���.u�|�&F���B��?���L��5D��.��� �Iwq�M�)��y[7��)���O�N�aWU�	��?l	�ea�y�tt�Yu�A�K~#���|����j��`��Db�Y��\R�!�_�}����xE5w�\$g���k��0);m�QI^4�hv��
[����h��mD���+��`@��i������,>;��_�qa�7�Wx����|�f�~���0�1�[����DM���������W��a����r[�'>�0^:??Yk�$$w�����U��$^%�?��Tv��ppb�]v���t�tr����&�b����6�9w�H.6KOv�������3%����-��qop\�=����{)?]���;��0$�j�p�������j4z zQSeR����H��~C7��a����c�!�n�r)c5��7/�`�s�
��U�z<�u�"��F�������P�1�����!2X�H��5���6\'�� �
$`o���O"P���E�����hh�=�42��83�+���hD��2��e�L�%>�Q��A��So?�+6Q�c���`������8�'G�Dr�����W4���T(���aA��%����}�����zA������S��y������>�>"N��}���	H����#��i����/�����$��qPd�1�e�����$�xy�(+>9�{�r|���s.�L�|8r���l���RFxAT���A�e`�E���^|}��)���@��d�03Ay��k8�+��e�j(���|����2���35�a�X��QTa&W���y���7.����RN�P8w'<����_��H������hOCt��#��4w���P.��*��E�h�2%�#������_���I:P�n�s����~�g������M���]��Fl��+*��Rh����9�WQ��t�L��0���X~;� |;�t�(�3��K3����Ei>r�5��N��8�����d����'��z��7��aw�Sn��0�_�?���������i3
�%_��~���LG�L���&��=}]�����7Me��PS/:j�j-�/�����!p%��������w�w=z�w$cp�1��v2K�\�1Af���nu�iR�y�	�h����9����F�9���������������}��������N%5%���0Y��U�aH��(������6�M���x�&C7��^/��<fh��Pt����������~!�����i-&,����c8Db�aB����a���'���Qe����?�3g�#�	�v����u����9���@�5���B����xsx|x�����
s�Y�����F���	�����3su��q��}�~4���F�B�<�5���u
qnV�k��+��tg2P��~�x���Z��UQ�-��K-�	uh���;x���H�$���V��o��|�e���N~y�"_�Wj5��VY�;�UX�Y�����V���:�
�����-�T���$�#�K0���H�M���{o�Uj�����r_v��b{|{	5�z�rK�i��`��M�C�R;�>T��1�yCs)���-@�y��l���E30�T�������:9�TT��gF���$`x�N���<hR�	M���_("�Q�
�����`���Q�c��Pdev�:�F��'B��)(�"��E���G�.���R�@�if��l%��y(L��������f�x���y���_4�Q�7p�;8���a\���:���,L��&]��X��`�"V'������r^����@
ML}�<��;w;�f�U�D�Y�T��L���������b��s���,���`�?�V��&c*��W���k'�rs�A��0�;0���n�\Pm��O�#0�:���8�	F^6�k��V�����h�4f0������� S�MD�M2��/�'_�.������)����I&�SV�<l~��|Q{���q�M�A<����g_v;���P
�`���t�E��89o�[�6����r�E�������F���������y!n�/�
ks����X��
��	]��,�T	m�Q��<�����T�,��`���x�
���*qa$*��|��h�Z��5N�
����R"b����M�
����A(�lj�R��[�����&JP�%�Eo��?���*`�j�����1��0!A��0
U��.��
*^�5����5��j���������b2���maR03�e������E��;��N8����h�
�a^O�G��f�����+��<��s���Za�SC�����Z�����W���|�����D���tY�k@�[��,n�����S��2�N�erj��>��I��~E�����$T�P�EN�P���%l��$�����Cn�9w�'�������N������K�/�����rQ���=s�	�z��O�0�^Z�m�Vc�R�t����;&a��g7`f�����@]N<��rgp�����8�e�9N�"�:���������n?�|�J(k��|����Z9��	4�D��
��!�[�)#v�����O>�)��1R��|d�aw�����&�xT�>�5���������0S�i����F�N����zD�<73�%��L/KN$L�������p=��MF^Ql�g,6F��M������X]&Wl+��J
��A��!���w%	��|�0���Ta��I,������)���aG���J
D�����7*�u<���$������zF��/�����\�vv�=H(@��J��c^Z�#���P��TY4��M!~YZ�K�_Lv����������l��m66p�6����U3���C�����u������@���4Y��5�W����`1������e����dT� �PQ�����'��yF�&��n�nm�
$)r��\IM���T��N���$<]K!���w��w�7*
��:>�8�o�Pj]]^�VVrA�y�'<�C������0��q��B��?23��l&Xs����
��z�Z���%sO�'��'����1��Et��>i����_1���k�Eu����@�_(��;W����E!�qq3E]w�B��!0����3�Mg����r�6�9��Q�����XU���D_���5&�%F��qv�y�F�_�]/Mi��q����F���z������,��~
�����J�0u�Z��G�}A�����op�-���~DP�^'�����A&��U{��c��<%D( b^�s?�
� �H*�[-8,�*�d}km
�`��6k���>PtM�B�����m�\�N�I�=~���(M��
^�����A.�d�����O��C��{����1i?�3�d>�
Z����<u�=��#	�fT�_K��P���_����O����h�_��@����������Yk5��7m�W�F�F����������E������e��X_R&V[l��E/��[t��Tl
����Sv���c�����7@��/�0�HY��S�4+��?;W��D*�c�e��'�a4h�}?o���n���z����; _<�
x��V[~U��+a����	�
5�J���.�U����0m��
~������j���]�	_p������+=P��W��s�9@����&�e\�>/lO����k!��Z�o�5x�R-���4f����"�������td'���`��~^���!|�L���|�a>{��[��k;�^������m'���������V�\$`��:	��p�A�P�6���F�2i�z��lW3
�Fi�FIi�dYM�'�e�ZL�[��&���{�8_�3�����/<�z1;AC���yQ�(������������~1#��N=���r��t��
)`���R�z�����2��2��!� �$+�o(����*�������_�0&��v~f�^���,����;0).]
�7��f��G>Qf>M�8>4)��h�wVH�4���p��z+���D��3��i4��o
�)���a2�'�����y����@H����e?����G��H�������S�y=��J`�������o:![�T2����'���������	��G��VY�l����E������t��[)�����d]"���ZQ�Y
����i�_x��v��
��y���_i�����
���xn���
!|y�V�����g���6.�CN�������L�]�.��wNO��9��VQ�s�`c�����Ov�p>��xfO�8CJ��zT�����������uG�6X�����Z}��?��3��O��g�F�v��6���x&-����=�������?�Vjk?e���=��x�O�r��q��q#��K0�90�n�����O�����yw4]_�����_q|����U�zp�����F9�-4�����k�<�t|���P�S\Uc1&�v������o�O����n
S����
]��n�It���O�������������a�������������I�|�}j���6�0'�U_���;2�Xb���W���y�st~�w���k��T����{o����
�(H����)��4/���5h�g��;<��d��o\�'i*
�����J��z��P�a�����p�<|�`Q"�[�i~���gK��Jm��@���@�i���P4^��M��T����W��6Q��.w'������x7�$d}kP��S`��L�H�����a���jl���7+
J(�))	}G������u��j�2]S�E��f��#��I2����D�"Gx��R�o<�O&c���&�����
����~~�?'��d�y���(.%0�a�����t?~�
9�R�O�����C����l{3`90��!5e�����8��������'������1M��<�+��=<�2v8��Q���Rc��2F�x0�j�m}�� ~.�'���ae`����^�7�;�|G�W��](m�,),/y� ��$�c��
���xH|@����4�nvn~6���{���f]����$��_�������zl=��/0(���eXp9��_0�zG�nP�Y���k���i6^���,���Tmv�&�,��R��C����&���W���K=�r?p�g\zt2K��%	�,@'-r�qK\D(�.�>��w@W��K�w1�PMY��I�)�26�Jo|{	��.�N&�[��P�X���������G^�������z�f�uh�C9�U����eB�z���2��a�z*��&?}��Rlo�'t7,��2��7�qh��\Te�?�n8|;�KP�~@���1�p��w1iJ�$F��_
F��c"5�k1~�;X�j���le(X�E�����1s��O�1�?u:���������t�z�29b����R4{R�w����n5����Z���5%N\���q�bp�#����
�������>M$8����Qh�u-��S�qB�L����^{:�<$��L[_oP����m��%bM�B*7���@����=0B���OL��v�bz����<��x�
#y��t��18�4�� $Z�a�>Ku�v��"�Y�`�Y��Ujk�������3�)B�Ba���z�i������1$Wc��Uil�������U�h�I(��1����������5=��_>�U����7(��%��Y`� >N���L������A��O~��������eP?f�}.��r��=v����D��~B�����v:��]���	��W�g��WB������A�G4����O�]�6���=��m�=m-ix���>�i��v�A��D��	��e���=�tS������^�y��>���gf87����d�6�
��dw5���T�
�RY
#95Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#94)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Hello

there is new version

* fixed small formatting issues related to drop SPI call
* long functions was divided
* CREATE TRIGGER ALL ON table support

Regards

Pavel

Attachments:

check_function-2012-03-06-3.patch.gzapplication/x-gzip; name=check_function-2012-03-06-3.patch.gzDownload
#96Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#95)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Hello

I found one small issue where Query was not forwarded when trigger
record was broken. I had to append "context" forwarding.

Regards

Pavel

2012/3/6 Pavel Stehule <pavel.stehule@gmail.com>:

Show quoted text

Hello

there is new version

* fixed small formatting issues related to drop SPI call
* long functions was divided
* CREATE TRIGGER ALL ON table support

Regards

Pavel

Attachments:

check_function-2012-03-07-1.patch.gzapplication/x-gzip; name=check_function-2012-03-07-1.patch.gzDownload
#97Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#95)
1 attachment(s)
Re: review: CHECK FUNCTION statement

Hello

multicheck for triggers are supported now

CHECK TRIGGER ALL;
CHECK TRIGGER ALL IN SCHEMA xxx FOR ROLE yyy;

Regards

Pavel Stehule

Attachments:

check_function-2012-03-07-2.patch.gzapplication/x-gzip; name=check_function-2012-03-07-2.patch.gzDownload
�B�WOcheck_function-2012-03-07-2.patch�\yw�F��[���i���X���h��2�%������&�1 (�����U� @��^�y���$�Q]]]]���n:�l����0��v+�����k�Vby�<n�'6�R�����.NN�y��l��-�s�i�O�����V�����-k���O�����o��g����W�L}�O���h}u'��Nf.��+����?���e�_�D�|�d���u.[�WY3�Mxdy������;o*r"��vE��[K~�'X�������j���h���#/��E|�#��<f��(�@��1������7�Y���W1w�t��\��ri��������do�������u��4_=����
�vD�����F��,��O��qS7�;�����xe/������u��[�D W�@}:�u�-�+Y�\?Zgh
�_��S~�s�U1���������I�s��:�,C���4����Z:
�%)��:���W�'����;��s���?C�E3���`%�I��"Pc9��m�/�]�`��+k���)�w����xTh������O�����t�O���G[,^�����@}��\���P'v=�u�=v���������-��T����,)���,��|!�����,X��r�f�j6sm����������.-o����DqgvH���[�@�,yX���bl�gA�0�43O�ZD�7v�FS�rm'����IpM�- j�����5�`���������Q���]^=X��XI[/-$h�������\s%R���e��o�+?0��Qs��Cn��$b[��A���Vd 8(�<|{����g�!�O��r%m�n�g�h��Ij$�E �l���1|��LN��<o����*�S���P�	����Q�D:(�Ht*k�D�|�#�OlUpy!��)��	:�0�D���`���
�z�p6�����UD����r�[�Q0u����a���a��~t��=3&�����	�Ka8y@!PQh�N/WqB[��{���rRLc����U�>_��_���CG� w�bV��:A�h�9��*Y�����?���fU�My�5�k�%pd�~�#N������6��QB�#��x�����g����qB�)����N�����Q"vt�ZW��*b��>��$"C <�&i��ZNa����.�NY��ns#N`���so��X�=~����/����SP�K��(�������<�\���Q��,����5��[>!�C]�P��x���r#zR��K2V��1�X,hh���>���+p��I�J�Ee����6ek�Q	F�uk,,��)��4��������G0�_������L#s�����GWm�-�X���������7�����i3l�K����LZ-���<�M[�A]M���[M�����,	��4>�]����y���4���>9�;���W��VL��\��������^�$M�k�zIVbT*���$+O�8	��g�|�(�
��3��)� �X�`��<Z�:)����Iu�0��!�V�P��Y�.(�r��o{+p�����	P �D$�Cf�)	n�O�b�M�d�x�(c���=�H{��B�YW�`w����b�$:�&9��������V ��#�C�($���R�x���&R�A��)����EC�C�Sf2�#w��+�\����]�Z���y^��-�/��)-�����"�i�_Y�2���8�v�o�A3Z�J3��0 �h c���C��������������p�DAX���2
m�3����_�F=R���[�gF��B��b�Dk{�_����o�� �\������F��{V�����+���g������3�s`���*��@����V)I�UM��tK�Z�U��M6�:����L�
�yA��gGUE=`Sh�r=�J�����(R��f�>�;*	����
�n��f�pvv��I�[ha��Lk�A�m����S�S�����?���A�A6��6�s�� �y8��p���!B�!q�o��l-u��x~��.[�El������+�G
v�"��BJ1Q����V���a��>h�
��r�(\E�N�PV�c3�����")C"�z+�R���Cw��N��-��viE�
^U9Q���	�k?!�#^���w�6=��d�5��hGP�B��}�+���D?
Q�s]��c��?��
�T���u�l6�q���?AG��%dj@�!����b�����@��q�YO
'JdF@�}�)"1h��*#��n��w�O�<��qJ��������P��#�I_B����TT^��#{M�I�����?0�2v���������������/46��~7i�eKD��u"�4�P�i���:)�D��<��V���Js����ihOB�L,�N�B/���+Wn[�l�<U3"*
4U�$�����X�#r���$\�MW����XR���.f�8m�6P�x��E��l���f�j.������6h& o� e�d��B�+_�$=��>��Kw8��a ���rcyBW0��T���u24�����Jk��� N��H�\AQ�9�Q��
�t���c>$:d��t�������U}�O��#`�����$�J4�i��fQ�L�������M��������A���j���}	E�XzbwI���)��=��_�c��s@#�+��`���F���3D���2`��xS����������7�B$���&�,��������P�R��c�>�M��J�����~����kH����$���U`,�fG�=A�Eb�i��"�A��(BK��|�����I��e��!tG+���t�p�h:�mR�sy����,1v���\�L�zJ#��N/)����2�k9�sr
��2����m��n�
Z����7UAW��SD#�:��+S����6Eh'Ut���8	1ue�_�tR�dde��\�����'���P��l�o;����%��cH��������eQ_����Hh%�������6w]^�y�,�R`;d�:
u��C'���7�&%�X��R4���vK��I��t��j������������2c����n��O�������6}B�;����=/����^"������4��M�IB_�q{:MGG��y��J�d� �]_����~_y��{9.�o~���,A&���)��E���K�\�����D���yG&�������#� {�bx2{e�������u9���W�����$��(�������D�������'��Q�9=��:3k��RReP��-]�=����	Sy1 A�J/�3�����mT�>�v�{�>��{7���-|���o�f�0�����������>
;��L�����n��'9�J���~���o���,3�KL�E'��S�Nb�g:d��Y3��L���w���8�:6���r��@��9��VYP�d
��r3
�h�C����\l�t��+���G�7O�Lj�?��*o�9N92.���R�r�&��i��U�*��y�P�������"�n|���_�1#f��s>�']��/[�2w�`��+��pJ��&&��(��@\f-�wOi�������Hx�]=@(~{P@U>P�U��-w�Uq��.C������D������������L;����_��k[�NJ����j�I�t/XL�#e�9�7<�Q{�w���Flp����H��z#�^��{����K�-�%�W�Y>FQo��MC;.�5[S���fT�NK�?����.��_����Y�����!=�;[��A�1X6��"`�;����E,/�����V�^z3�
��cX�:���}x^�U�J����I�	�KL����:0k��=����\,�]j���N*��I��,.2je4tvtxz��h6�O�g����!��82[ .<=GX?��7|�o�����}q��n��&�|���m��{��Kz+[�}k�7��~��<�l��)����K'n�0�ZI1�����:l6���m9��r:B����/��pRD�0�W,����=���*!7��/19Z������V�~��U�V�!���h���ro��M�#C�����8��7��:����{�X����q�������L����B����`>A�����|������>���k����Z�Y�q��f�����*������q�����4��������
T*���������@)X��/�j�9�y�T���Q-QK�QV�nnd<������Q����Ghsu`�b��_;��X23�3����b�`a409��2�t�����%_���-�(�G{!y�'��g
�2[wt���,U���T8����V8^�^��}�'���?R��S{MS<����?�8�w�=6���L�#|d��y{� 
����b���({�*�Ap��3��~c���
74�-�8����xX!=�x��s+������+
�����h��?d
�wJ���t��Z�- ���?���O�7�
!�1!6�7�Xv}w���nzo�������?���5�������f��?�������T�z^�c����HA��I\�-��D$�h�Z���#�7����������'QV�Rc��v�K}�K�Z�Z^S>Q�����y��a�g�"��mS�~%�����gV���R5���+v�*�r�>@~�G�I�6V���fB���)�h_�^�TQ
����tR�w<����2�"L���~��B?�G�f��j����4�vTj�EZ znW����`o�l���_����A��}�!��F�}1t���b�#����x4�_�7��W���	�u�����3���q�������}���]K��26Bk�qm �j,�%�Nsw�	��I���&`p�>���\�8rHOC���)O���8��p�9}�������M���P�@�Q���������8�*i�Y��$���
^��Aub�I����s8�'V�J��R�H�b��:����a���T�������E1^��'�RW��-H0h�����L��F"Jx��!d>���!����r~��.�d.��,��9(���;�eg��;=@���Z����K	!��Z�*�;�c�%�Q�$u�$�x&�S�$�T�$t��\?��9��'��stTn��:�i-����	7D>&�
I���<@�Z~Q�K�� ����`��5d#��;�0IXb�:g��N���o���Y�1��^�L�4��H�Y��jB�"[��*	W	�>d����i����q�,��r�XH5�����n�������!d#��
'��(���"�����r��������X6$y�xB�rm�-��*�e`�mU�������F��k��X�����a���O��b��	�j�U�
��".����WB@�m�T^���.�8Dk�V��F!bJ���x�5QW�x�hI5�~6�Q������
��4��'��6�(��w�V&Bv$�2�����Daq��
3�����R�����i�k��*'��m�����'�C�)���>��jj�n��?�)�_X��2��]��=��gxD�	0.&�Ir���e��Ta���W,�lB3���YE����nU��h�����$��_�
�4���a��|Q��Z�Vkf�O!�wn&*�gfs��9���5�Q?����������E�-=�2D a��)�1��D�J"$h��7��l�|L1�R��l�����~��'�Yr�������3Urj��������uZ�u�;��+���V�8�W5���%h4�h�c�'����w
�_�7=����Y����a���r��>,��	�Tv�Un�<2+7��C��W�]������hh�Wo6�����������z��,�O��+���J��P=�Kc<�y����
{o}�#���l�jVE/�,J���$���0Gk�Q��ehJ������ge
��MR������������m�ra(����qf�T�	hT ����0��{{{w=��'7�������}0����T���?�����_�����d{��&�bT���������qd��OQ�&�!#����\��3�,onn���~"$�Z
a�|��l��&	������������,�_Se���(������$�����������%�c�&h0��!CF|v��'��7t;�=�e�1<�����+DR�������x�w�����:D���Jw`/�����/J�Uf`�FaTG��.�Ep�3���W*�{Xx�^�
�x�AW�%��Lg&���@�:?=�$������YMm/�U|}�������+�!��9��t�P�����d���l�@f,D�I��� �pw��(#bv��>����n�������5`2�r@�"�����	
��L��b�<�q��zA*�9�%��C}5��5�d5��p5��D�L� u8'5�>��}W��Q�2�e�������W��T���k����Y,#�MZ���k���=�]od�,j��������RD��"/-Ueu.cd[hy��RM�W���@�0
i)���w���0�K�D��=�t��%A/�{�?k��7�2�x����)�%�DlU�m���#l}W?��9������o���
����_2mgv�A����%��S;p��LW��O���6��{����g��
�bm�[]�I,'�>�)�(����B�=�����P����@1��s�+�jP������4\7S��j,wk���������M^����V�vj��
:�n���Q��������WW�Y�*����2�`Za����<8�z9��=�������g^�rt�<9�M��)?6O�4`�
b#:q�th�T�#�ec(Z�jN@��~���E��Lj��Uz�)��1�M�}�t�vh��LIW|�Y��&��77O<�19��)�R����b��GR�q��������������j������h�;Z�\��0��p���~�`�&}YB�����	����y�Eai��B��Lk������po��!��{V[v�DW����-5����${�d�u_�
��G*Pm��V1���J~��g�$���>���Ut���)��Zz8�����V��.������{������:�P������<�0�	*��V27Q(6a1��ZXq*M�.����l�!S��M����;����n�*�i���
k�m���	�0�������[9G/��Y7����D���)m����h��|1az^�eo��x����t���A��o�yHU_v`GC7��z�0I"'6��M�+�YoQ[�Q�E#��J3� �R���������&}�	@��~��{�h/K���x�x��nt.+rrf17��;�or/�I3\���se�&��0w/�aa�3.��ZT��Cq�xe6��aA�)������6)C�Fc�������hDS�������D� u���PxSym��4E�Q�+�g=nG�$'G���et��D���	'�*Ln���(5��\�����Bx�kt�N���%��"��t
U������F9>����X)���o�Oty�}��%��	5����4Ot@%c@�g���&&c�@�H�I��W�0�1�an�1at�������������"��@���������D��5r��coxqi���/���x��u�����`�LDWmtX�������~���z$
Hu
x�����Tg�u�c�����aq�*��La���T��3���� ��.�Ws�T��>JX]�X8�����(��'��s ��A\�;�hhR����v#���-1��@��B����U�,:���mVv����yK������v}���,��q�6�.�r��(db?�.��M�A��|>�U�ass&�f\?����9��~������?�@����I�?������.���gCWI��4N���"�����4#�Y*���V��BQ�V����u��;��X��� ��9��h�������Z:���X�w��P������V������S�������;�����:��~�O����B��>[{&>!����CM��l�WA^T�������������#q�n\�R��7�&�S#6���'�_��{tO��������DJ~��u&��X�L��	|��,�&�F�MTnG�c����F�Q�zp����a�����!��wPT8���G���N2���>���$k�5�5_�X������6z���w}/���$��7�&�#����WF��'' �|X�I���(���LHl�y?&7�������;8i_6���/a���c-�e��v�I��]����O���DOV��P�RJ-n;�R�i��Ae����-:�^����Q����p�s�!��__
D�����Gf�08Zj��c���69�HH����@�{��n���eV�Dh��-������+|cPh�nU���q��z�������3	����[v*����������@��Q'��k�s{�����������2�R{a���?���A{�Uq�����:rO��?���N4%^4F|���Rp�������I�s<9X>x���C�3��E���#���9Q�l�g�(�d������8 �(���}P��aR���9��8i��A_��#��}��������k��&<�M3	^��1	Z"-2�f���������6�Z�mf�Njs�f^�M�
���\��I6��rj�|�j�
1�7�`��d�CwFW��ow���,�>��}��3~��+S�F�*���}�z��G����b�#�d�<�> ������d��&�9g��)2����p�G��Y-5����a8���ta3�^$�.fs�i��J�9��w
wP��������()�_MY^x*=�&5-�Va���p�%<B��Q��^�i2J��ANS������3�u�M�G�_v����U����(��u.�@����t�XW_�������������������������[\��4�����{�VLB�
q�J2)�$�H�B�Gi��F��L�
lS��[�����a�3u��`��Tf����jytha������o>�Q��a��1�0DDoi2d����=JQzf�<��l0g6;�H�����T$�q0E;��g�n6����r�A�s�2C�8��VF���G�8�9�J�����i�����F�L�JH�����JnZ`������L��F��n��:����,��w�\��/.9T(���@��Q��Q�O8�n�X����u��w���z���&��uQ��CS�#�P\s=L	0
������n��{nu�MXb�������e��������2B3��3�aV0`w��F���GNy�p�A@%*���>�(#��9	yfB{�������2���<��.9�A;��}�<�lY�*���eaE,\�J�\C�����`[�J����������/!������w�����[����M�R�h�T��t�� f� c��|C
�5����=t�yU�
�?��#h��=�q`��a���\�8&(N:06p�s�(�J�|�	�1����'h��F��"���/��v8y��sz��@�����y�`l6��������*���0��[��)����}������%p�9lQ&�����.�-���{���ps]9h0vH��
�P�Fe��+��a��*��d��L����kI����b���������R8n�I�V���}7g��
#P����>S�.T*�&��z9�QP�N���0��*sM�5��2V�&�(Fp�A�B�w7D��c��i�*�}��RQ��ij|�
�/#BIthE#���ip�C7��)	�|A�u)x���Z�I:�5����5�"Rx���h'����e�1Q������F��T>q�	x�@��N��X�b�������]������u�\RHK���9��E�j�[J�V�5K���^����fM�K�l�!�Lx67��
�u������\���sR��9f�y���inw���`���X���7��WJj?|�\v��L8}&�h�g/��pE7H��E���Gz}3�����,�$����"����2��^�����`�������$oN���<�pzq^5�X���@$�X��<�I������	9k����x�`l���h<�hFa7��~u��q������S���K���-[�f����4�`���.3����[-���v�R��37���M��<��H���c�Z�,W�2O��n.a.Z�|X�%�
�<7���;�ZU"9�K��R�%)U�*�8�g�8�&�� �;������P�m��y0@&��:����������/{@��
����������(L�bKH����f������6;���,�Z�6J��8�(��f��T+�����nU M)f�qI��<�]�4����d~���MR�8I�n�����[�%��a��z� �������y��N+~�2�m!+S��_���=����� _�U��*���9�Mg�����.���M(��)i�$��d�P^�R��#Y@A��LFWM=X,*@��'���$�~�u����cg	R@�90�X�N�i��#9r�e���H����<��Vm�R�4�l
��{�?IHq��}z��p��=��7g�y$�u��5
k�k��6�0(���P�>9&b���b�'F�H
�;�B�K����v5�&������Ub��^u9mj�I���������e� �s)x�_�Q�st'���Cr�c����m0E�v��fTx	�4�u:�j�K%��f3A��������@����%���zfWH�)��1�l��� �XC������!�,jR����~�/��UON����>5�e�b�"��-����������<��5��������;���x��oINw��HeZ6�0��l���:)yO��Bl���Q�����E5o3jz�1Wa�UzgB��_Za��\�_�����?��4�2N���"����6�Ow���<�pQ���g�Z��-@�U��i,�'�l1��O�(�������p�Y����6��N�3<��8	L��0�&�Tk\�� ���d)�b�7�������LiNy�*Z�3��s����Mh� ��/���s.���X�A�j���z�5�^M�TO�������
-#���������G49=�	s������4L��l�4����%7�s�M�6����;�y`�nm� �����-���/!�R�u�_�P�T�'m��'��8]���r�����&x���Y���������.$|}���d��:=p��w�����J��d���L�����GQ�3�y�e��2=]H��'G{�G�@��u������Gy0�����u��t)�F�$.�J���j��tR����k���/�H�\��{sZ�!�-�����k:KT<fY����X�9�1��,�)�������s<A/-l�k-���8%�9%]9���F�\�o,`�G�Z[;�f�c�V�V����	q�>W�(�I��2^;5�����s+c�U�2�x���eD�!�T��$S�9@�
����&g�n�a����u7���z�~v��y5�7�oF���v���n}
�%��d�1y��Gmj�B��H��al�\����nIc�r�
������!5��M�Y��FLE�8�3��B�
;=�_��bGB&!{>E����K�b�
I������h6j��G�f]�G����~���_}v{�D�+���t�|����A�x���������>����5-�h&':���b��o|1�{��C����Z���T��l
���.���������G�_g�Nbq����r���wjh��� !��ii��J�����]�'�_�X��Fw�3�����#��n�l��<�W����H���pxT�&�9���88����������:�Gl�k�
��r�FQ������a����\�n&w�6�K�p���=\#W����^��WO:%z���) O�0����^��v�7Z;3\<�&J��l!�v������	>7H���y��TA�������s�f���}���mZ���{1�8�Z�H�5�{[��������D����(����	�!w��q���P}[9�Y�P��d}� ��xtm�B�{��g`�~�\�i��{���w
��D�(�T��qby,�T{>�(F��(v0�/�I���$mmlD[�z����W��������������@�H>�y
P�7���������B��+�������ef|�����%02��$�b�q5B-3[St�#���A�~�f[�b�a
���A)(�y���!�����&J�e�p������LRu����4������$s�f&�w��aY�j�X��>�~�z��a�
�>�Fm�r��Cjz�w������|�	��*�A��L���O�	<a��w���6k�\SF���Kx
2X�>��Id>�^��ln_]n5���n����(�lY@��!M��	�������Q�k�-�k��Zh�E�U�0���.��OO���
��y#=�����#�y+�C�}���5]��r���A;xs�>:��d^�%�}���������j������w�w&�SAQ3�]��;�����f��5@+e���o�X��:�w����\oll�N���������K=�=1�_���c.�|�YS���5Cl�c7)Mf6��Hn�Y(�&Wl�v�<�����d�3�tG��o����n3���.���v���T-����"2��Y����&I�����Y�yw��']�'��.�8����w�����?�;k����{���������:��@�)�4�������F�vW�/��e�.E�+^"[
:�����w��;��i�y9��/�8h��������9������a8J����Qx[�OF�����0
��n�����l]v���_;{���li�E��[m�@P�����/�q�����J�A9LTo�W$��s�z$M�A��I;���t����]��;�
�������(1��o�`�����A�*���Bs���AL�����J%K��|EW7C����D�q�%#�c�rz�S�8�*����n~�wF�.�Gc����GZY3tt{M2;��6� �'C��������s��y*�E$~c%~�����U#�oz����A5m,s;��;�Q����u�Dl���1+5L%J�%�L��-�+6��<�j�����]����	A�������������+y�W��(��~��;������G�k��/idaE���-l���Z��M������j	�t���*MX����V�g���!����FR����;?�h�w>�x�y�~�|�����~���g�5<�9��9�Qq:�Q�<�K;������Mr]i5[
���������^����> ����K��sW/�~�+��N���E��C���uD(����>?�C���?p+�0��@�S����+�(�?�v��9
:�J��;>8j��he���=���0*hN@��\ �=���dQ/�+�eS.^��m�Vs���.��/�sw����M������_6���e�t�y�[M���\y��O��������_h�o�ES,t��,6�I��pw��
K���#8���dI$���=8;9��\�.����x�@!1��10��uX�
Z���
���"���CE�����h��d����#�Y&u�����W��O��ba%��oy�����/����^u����Q�jP*���?�yN�R@-Gag�'���/����y��Z1�\�T2oi+���7N�N����q���w6�N��OEjm���f��c�SK!K�O��-���O�������?�����YL;��Rz������,&Agf-s����~�[�Z`��y������a5?h}��/Y�G�~�k��O�c���22��H���eE|�1��������w���!��(��Y��ec��MYZ���n�#D@5����*3.��C;<NSU�\ql�����id���l�;�/[&�����	zf�OBA>�7��f��8�z���9��������i}�z."�kgF��k9p�/	�������z^}�iM{a���E�z��<&Z���'hP�P(�����8
�;(
�Z�h��1���$!����LT�tP@�>��OF�"X��{�.�1J&&�RW��WyS�3w�b~��)�_��|�r��Y���[[Wk[k%vu��Y�y���;kd������aR����6��"��x���w�Q����-)S��U K���?[^6���f��+��YTQ�3��K�����qA�gLI���H��3��]�`\o���+ue�$�����=j����f��+�_�9-�b��ow���-)O����M�z��!zv=`����Vu�f`d��E��)K�,��B�%�����T4�t��ll�K�V�����Ex���������=��)�E��h,�����|�l�&�����S���BX^<��n�����mM-N�6E/$����+L��/x��D�p]������6/���ShM�+y4�>B��Y�^�z`��=�x�iy���5K6����u������y��i���Fgg
�F������!�����7c��FAV�Ksw�:#�@�hR�un�_0��`2��������������������?�C�ga�}������N/���'���%� e�c����pC�8$��i_��&�NtN��<�E�|�p�*�G|����`}�0��z�J������2M.C�z�:	�o(��/��&�������o�~=���FU��Q�A$�i�Y�,���l�M"�|���'S�7�J�m�9����~��W�������������������k�'�������}���g��4������4���,�4�$�hC����z�~����K��u�u[:\bm^o�{J�F��P���K�s������>��WG�Qj�w�����n���k
����
�����r����KnA�q��ZEZ���P����2���H?�?�����WM�B2u'���7�dN3�x(1����}~+�1���������yAc�;���=@w��&uY'���[�M@�t?v?�t���SR������1	���TN������f��@�7� ���>nH�����C??��1�|�A��/��-�u�3&&�9�-���;.��\�~j��_��RR��E��L�v�77v�5}��	e�%�z�L}������M����2��<1z��b9��'+�`��WhaB�j!��I�ps�[�N��kW�����j�����8��)!8�����,�DMS��,�T������i>h�2{/�8��@?����g��(����~��z�u��d()��@�w0��_��W_��!6`�d�,S��%u��'`j�8E���%�ak}���|�:�1 B��8���E��S]������B������A4p%��=��
��/g!��l�)CiQ4���}��
:-�VY��3c���V�P�y'�i�z����+V�_j����W��|Y�&]*��LB�M��u#B\s;��������Fg������].����
�l��V6|�"�
�:�dFX3���fy��y��i.}9�}����������OH����:#.����^���3���t��l����v���^�4�6`S
6"�~j+rJ�����V5�@���VX^���z�_���o��"
;���s�n�~�3l8��#�����Qrm���h�����aO��d0�<FoG���-�
Z�Z_c��12^T���2&� n9�#�
g
��q$*
���ul���P�n���OK$��k��UI����#��C����O�.0���E���������������������1�?:9o���T��<M��&`�������R������������wtt�o~�������/�
e_zyx��wv@_O����S�l������������7���w����=���y�g|tc��G'{���|�-�������O���<:��q��Ukn?:pT������)��T���{.�?�rH��#����J<��Dq���f�l2$"}� FR0"��F
�iMg����h��PV��j+;V�2�l�&��cI�����1�fF?�8��G+o%��8�����7����f�������o���j0���`p��$�G���p�8|2Yv�����;�I7z!���Z�P��2�%�r=�6�z=�4��<�P��%���j����G q�N�Zs��\* ���aB$y(��+O�r"�'t	�����b���~�y��}��{@w�n�<��_��,:���{gG����}��U�bU��&��e&������y�A���3�%�E��P�z�_�������=a����FeR����|I��!������cv�w
E_M�����&���x���#����R�~���� Rl,�(/D�X��,/�
�J��6�
��\�l����ZYG�+S�D^�l�30\���]r�_��_�S�r{;l�����2��1
���xW3�ZX=���u�Q����T���jQG�_�������k��fs}S8(P$�<��5�	u�>�?�.b���jl�AB�[�<�t3=��.��;���Y���t�5m��us����##���bN����������;�u��E;�������W�=���������^�����@Ue$�-%Dtm-��n�@������i�DD�vJ��W������F�������ap��������oO��r''.0�9dy+��T'��z4�V|x���3���=���<c�Ku��Kv���@C&�F�76��n.��������QN�m��h��V)"��"99�M<�r�r������T��e���BO++�W��O-�)l!N��Dv3Kd���U��q�h!I���MN+jz���C��I81Ct����H�O�xg��@"���5��1�3@����c���ya�x����&�x�B7���l����9�Hu-u��i�MmyM#��_O��0�k�r����>/X0lvz����g~����'���~|3�C��{���??��)�l�4�fz��a4���|����i
�U<�V�@Z����s�%C�z��l�������\g{�1��`�#��.���g����<��� >���H��+��������@�x|d�4���������E��)�$���b	M��c�fe��{�����fD���p}�["�eZ���2e�R��H���A��Y�}���--�E�E&���~���yM�
����������m��!���"
�����/;�����_�6�9_bCqe�ZR�e2>�c�
���\������mw��e��!����}��9���
�.d��.;E�e
��I�R�v�w��"�������t�"�����mC�gE�q��y�Gc��;���;U��~t��]I�������ptM� �-)�]t���:R.�e����6��������M?�:8v��6t��Z���ak�����G�6Qr�m!��_��+m��t�a�o�����c�������0�3��&�~{(����v�,�]t+w_S�L�d6&�����Uskss�^�����l�DG+k#�5y�ho6���b;����k�".v�`���)'hl��`w��;��N��`!Q�e��md����7(n�G�����"�7o|N��[@e1e�/j�����J%&�o� ���
!L���N��������z}{-l��y����������p���>l�1��8~x5���l�<�J�v9N;\�c�w����IO~MYH�����[@�4����l��]�4w6�t��D��z��G�Z'k-��;���O0o$0dI�(�){�Q�2���3���]b-������+Y����_1=H�J��6c��^+����PI���(G�b���y$fd�������m���)q�S{��Xl-�p�@'(�;��D�z~��~�?��N�Iq����EKH&����	R/��v�Z����tX%u)�U�
&�Q�Cj��s9���-�W��s�]���<�����Y��/�p���&�������G]a��R��L"��T�K^�����w������]H�[�gbr�������m��r��
%�-�5,�(�c/��bBSS�tl�a���+%)�\��:�a��N��r��� ���sW1&�({����F�����76�[W��,�,�o�dQ	��d���3���Wx�N����hc�(\��	��x���p
�������e�R��Q���}���^]m���h�iL����sxu�K�,���(k�^�����V{�e��Z/=�N�r����t�u�����nt����.6P�e�g��,&���������#�gY�'���
�h�!����MMn~��w�e���n��t;�lw��Ss�S�����A���%���m�KL���KYb�f�Z�����
y��}{�����`���7(�.<����8�"B\����%��jso`�x�����cD�3P���$tb�W]�F�m� e��Iw�D�
��_�K��3�"^�q$�<��bdz�����!�$�� @<�_��Vc (�G�`L���s�F1ge�s�ah���$H��E���K����hxS4B����b��5	���!���U���j�D�N:7��n=�@�����6{P�$o�	��k7
y��gV�x��.��fF��4�^2&�<����<��Me����
��{�]h���qZ����
�v�3JtI�u��a�>�
<EW��-B8V�l8���.�D�i�=��3���*�R�E�(�=e��Ci^��)�d���u�g�y�"�%8g2C�����km�s�hn��z��mm�6f!3�@��"���5����]T{�0�t�I�������Nn�3my8�:H�06
��}�A���C�;X"ay>b��Q�([f�ziFk�!����R���� ��Q2��
N�f7�����(w������#�
�����3O�<�:���$fj�g��:
�>p�|�F�q����w����������#��p��G- ����T������i�
p��k'�g�5T�p�Pg���l8J�}���4
��7*�����(j���P�\q3E������|�������Y�	Q&�#���Q' �F�v�!���!@���\tg��\&P���^]�g�[�sQV28� ����V����\IN�H���W�Q��"��j^�i��U<J�\a
'H�JO��>�-�2��<��km�lnR(���
��?�-��X��Ry6�0x��Q�����Xp�R�9r�~LR#�'���|��A����MhWJ��"*O�����w)�
����w*����� /����p~����-c��p�u�q��G�V��=t�s�F*fq�)�`��D�hV��]��npBPR�pni~�`�V�����%X}��]G[}
��@2^s[TWGWO~Z'q�6�Az4?Q8�q�i:�����`�U��t�6
4���'�&L��=A��{)�9a�����v���tz6���z������F��}����V��%l�,��O�*�u)��	�M�}���q��E�ou����7�����ODQ��E=c%���)�nf���W��VQ�T��ykJ�[���4���=/%��n�����a�(IRg�S��s*v���B0:*DkE�x�Ek�����@,	���/�UM9O��)}�H-�H�$eY�D,ZLe�X�e�"���E�}J!_xX�SQT�u����1O]�cZ
TQ�4��T	��pW�����;g']�`�����nI8A�Z%��;2�5��PwaS��]J��:V���,L�}9�g���5G��)��UZy$#]��d	@�e���B��D !��px�6t��s`�����Y'X��H�Qz�1��P?�n��
�l?J��d`�3�����3r.�	vX}��h����U����,��a�54��t���,�[����U����y�����_R�bf���@��0�k+��)�/����9�j
���X�My����4�ZY��&�k~�c���nF�C��(Sh)��4g�M��'x��C�7k���/d5V8�x�#����S8
����
O���(CE������8Lq�O@.���j���?����r��P8������?������q�������Z,Q�m�����N�K���`\#���*����R!�UnX#�H�j����^��M�Qz��c��vmpQ�����F<
E�01��D ��y�2C�-#\Xq�z��U_�Y�Q�F7�`��#�n����mt;`w�Gy��G�� ��Z�|2����p�:���}+�)q��.�H�y@J���j�)6VB���.�3�����+�Gw�$���
Ra��HA)8����:9: v����(5����G�\���2����>���Xq���H���Z����F
��{����=7v��A�����Q[F1�2>�c�'�E5��MK2�tR�>7e���J�3i�p���fg�<���+o0 ��!���qC|�!#���z1�wE�����),�s���\/^�j�!���z���K��BCw71������l)`
����CWU1l�LF	�Y�@�C����\* ���P# �J���;���:O����X�n0��(AJ�2�y�mZ\Z7�:����9�5���G��r�=jZ��l���k�Z��@��
X*�0Q��	�j
��Q��R�B�U���� �?_��������K�,��p��.8���UA���������F����J�'�w�Q�W�C���<d���|1��
�����G({w���/�G�F�h`���D���)��A�}%���U�5��&~EX9*8�]��`u��\�z���Zcd�@��*�l�!�Tn�k�&-qK�D2H��'���s�~������\g�MD�e��G���}t���H�P�FbD��O+���m7S�j�cfY��#va�B���&��:�},4���[!� �,�M
�3d"��rX�!��%�k�)�xMTY�h.���
O��G�������2��LF�BT*�P	��/�E�����'����/�"�� Y}���N	��z��0i���U�?�8o�oa:�K3;B7U����/i�8O{ikM�cR~�O��������\����}�������E;8���h�u`>uW�gV�v��*Mz��'�W���Tu���p���������^��:�0��E1���D�����(������,|Y���8z��q�)���d�9���N@�q
p�J�"�rG0
�H����i;?=l������>4h���3q��Ho(Z�,?KgG�?8K�����������}��}x�y���w&J�wv��C����C�t_����l��1��3������
s���( U�6�P���+�1][��Xy9|��m���Mx�Q�KB�=���`�+�r	K�����	ox_�"�Y�����|<0M\FZE����@���,����(�@s���u����
�N�%n���{.��Q@��hZpX-���q^AT�cK�jG���CW���##��(&cu��u��`8Nu�P���`U�c*������I�u�#4��/�@��|R���������5[�_���O1�� N���DP�hVV0���������p��fB�L0�����2��;��m���2,X�����bvB���W�%2c����q��7K� x�;�+�Q�+`�ir�LG������g'o���G��/������A� 5�4����������Qf	����7Gx�����H)q+)0�E�s5`����,<i�D9g��f�b��D��Q��&�w�m�"3�sx���bD-��M���g0����AqDZP���\�U/���r��dOg:%3E2v
��b��k�J
�����������I��@@p����u�R[}���f(����K��]�d+f� �3�o���jv@S�fg����4{�zW4T�Df']�����vn�;�%����r�I����*5�!��/<�����<KI�K5�M�����w�:o��S�W�L�����;9@R�����ck��V��������w�j��z�j9R8����y���@z*D��l��[�u8e}K+E}�9{9�X���"�B+`Qn�Rr M����c.Rk=�E?��P�rN.��^X��r��S4v$t�p������aW|eF������������r_��]'?�C�$�4Q,�&n�jA���O���6Q���x�G�����"��7
��~��@�#
B�\ z��ngh%
�pZe�t�/�o��{����Cv�q<d����#���K�Ug�)V����V����j����A�������3��A�8O8������+�����g-7����k�o�����==<����G�X����!mF��rrm��p���7�9�b�C@3z�py�o��K��Y�~C������-�E��N���H\����cX��$���N�!���K�*�u'��9��_S��x�<�F.�+�|G�����������uz�0�����������/_c��C1+<�\^��~��"U��{�|N���A��Ah�������*��2s���s�l\y.@XG�5b�����r�������;X�%���k�~r�Y^����d������G��]�4eS����U�T�6���8,�K0;Z1@4N'�J[���#av�1���	�t1}�T���iD���7In��9Y�����c��O�����1����V\%�X �d?rv�1��
i��!��!������&+>G� �]���M4�E.���6���L4���c2l �i�@|Cp&Tz�iq	C;��5��������6]+�8==9�p�jQdi�H����bQ��k���Z./�ee��u1�����/��S��=�������~��������_[+�P��(�����^��P�sJ~^��%�~2������YbP�E��2Yl��>��r��]Z�
�%��t.(�M0��J3�yg���Rqn��_���=V�m;�Z?t��b	�!N���e�,�P���5;����,�in�J���R���~�z��@��������f�m�)��]�d�����~��d]��'��\��O�Y�~�uS��sB����$�g�#���%��`���I���(I���s�i��5��d�������d&5��Z����\��uP�er��-3w�
�Z07P���|]����"�|��M��e@�X)f.�Q�w ���Y���g����\.��L����������/��@��hd
�IP�:�����L;�-;V_wc���� ���@��"S�;U�=������Z�U5zHR3���NNT�:983�Z	^�4����	B�\Q1� ��-�Uj�&��jX15Vd����� ��#���i�k{A�������BT�0��8^�����Y�n������ck jt�>z����� �V�.X!�/`$�+���b��
}����!-��������lfh�B�@�4���u�g�}��b|�[��
����I��j#���5E��1�iX�����P$7�6_��iMY�l���h���H��\/zU�C^����7~��6�Q>�c��.j��I��|�,U:�
-��pV_������R�p:�\�I~��N�W_���1������������C��9����I>��d�E�����5V����S��%������62)=����[�jv��TO�w�,�+A����x������M�U"�Z�e��	y�.���n	���!4�8��_6%a��*��$��99k���St��(4�G���o 
#l��'B�N|�J���Q�c�ed�����4u3�����f��^�Z1c�����&�K�������W&�O��~������,�����!O������F�_�em�����hqD#}G�b�5qGr"���|�������L�n:���#��o�JB�dn=ucK�

���i��dU��_�po8��<hA(����xR�)�����N$#X��A�^Vl��H�����j��?����_S�
W�']���+��V>��4��+�3Z�L��"�1���l�es�.�!������`��	w��5�����J���b.��/�Bur�g�P�Q~<�����(�!r�~_��KOi����F�-�����<�IE����H
��0O)U����0R�Tn	N��xT5Q��Tq{G��qW���R ���`�[�:�B��V��O�
����0��H,&�S�NGH%�?c�XX&�-��������Ma��R�0��E{P",6T��i�1�t�a������={I�����
&c��G���TW���r����Z��d�_�[���F�����F�L�������m\��F<R�7v�{��1�VfE�q�������t&����9��,��v��^��'%.�TN�D�rW?�T��@�d�>��Gyfy�c��
m �;����'u��X�D�8�D��w1:�����1�a��
CA�����WZ�{]���_G��=���tQ�����������P��ca���m4��vLM5,n`gQ�������l)�+�"�&{U�9;��w�O�=]��h��Ap�>j�_8�'�AK�u����P7��b���$+�X�ed����yz@��3�����U�|A:�Rn����2���;��>�b{���j�
'X����vu������aT�]�
7t�� H���H#����/����*�+h>�Z��>��`n�g��1&��u���FH�L��o�t��"�����h��P�9�VB^�g�1��5}�
��<U<�R��Pg3D���Xt[&�M����Q� �.;����
�mW��W�q�S�%���r.7+Y����d�w�5���G����1�t����C��f
��j��VgIA*
��z����f'.����/d1�`��~=,���CK6�@���)]��R;~��n�=��
,^+6����_�b\;1c���`-����RJ;{��)?����V���(k����v�S����P�2���"<�,)�\�]__��8�_���>:��{�#;5�+���W���Kf�qs�w���~x���"d�v�"Q��D��k����[U*r�6'^Ep-��v�,�����o]�����%���9��3���c��q�C���dN�E1��'01��3�t�M�n��n+n
�;I��A~�<}g.*�\EC�:���g,-�3�L%����{���o���[d�e�G�u��&��e��1���L=�3�g���,v�.#�E��R����LI/|(���('t�����!�R��g�d�b����-T*����l�Dc��;�
�;E;*���V��[�dehR�[�;H�����=������&�,�R���M���s�^���4^�?7�	/�|�0 ����)>������>���;�R\)������"��0/���oP�{*�aT�����E���4�-�Q�Q�_����>���5���r�`'�
0sX�BxJ��oF��;o�_�_����=P���$�
�m(V���F�9�H,�8�g��W�.l�%�+":�����C���� �Y��c�r���2��x�^�6�����EKq{9r��%���l�����S:���T��9���MM

{�+
)y����C���:����Jnswf�.z��v-����	��f�#�8�����m���:�Yc�p���(���!���CE�*9$zF���^8��e
������	W�����y�TyeEd��U[;sY@#Ym�?/���1�ug����^(����tV����a�4=�s��r<��b
�L|���:o�!+��hFU��^"+~��+G''�LW�K5�W���,�"Yf�]�����������=M���!k��!��X�Hy<MsrvX<��X�O_����sL+���1�'_��d8|t����=�6�d�K� �[��mA�#2��Y�+2�X���W_����;�|z�����Q����B���e�Y-*W�h�"C�#������9�������N�~������;�����A9�U�b;@g&#(����:)��|�@��v� F���?�b��'�����T2-DB�
����rQ ?N6����0��2b9�zb��z5=��1
5u�5�p\�ot���R(�gI�b9��[����<>`���C�����Djr� r0��6�(v�w+�#W��C��i+�^���rADV�1�@=�����&���`"��/�X���qlIH*7:_W�r"3rh��������@`Jl^���d����Y4kv��E��������Es:un��<�^p=���=	#�V��^)7����%��9|�.�q���2��G�$��������+��W�^j=�>]C���{�.+���:����d��<@��^�UO>���`�k<v4O�gN��i���{�y.���F#�rF�:s��}���x���<������\{��}L������C���bD-������?2H�/5�fl���$��T�T1��f%���36�I(��'�l���r����gv�K�iK�-������|X�F��'e`
F0��32n�XHDI��^��<P��b�������f�7Q���,����p���W5D
���k�S�M��3�x�X0������������������z����[M)��q#tc���OPT���-i�_�������"z6��i��\dq<�����bq"Y�"��MEO/�s��t.�N���L���Z�U�����@,R����H����},�x�.��R~������Cf
�� �����?�i^�'��	U���t��*�hHJ9>Hm[�V.���Y5��.���W$��(s�s��`�iLX2��e>���b�b�K���`�5�S�<W����h�����1q������������Fj�o-���/:EU,��$R�	I�&T��DA!�t�����d<��M���~5��%�[�!��nk������R�7����kP@���_M���}�E����E�p?�~=�v�L+g��=i��i`CJ�lV�WZ�\�����l��
�>\�H� d����9�������@�����u���sX'�A�r[�$�{u,2z0S��l���)�B=����o�>��S��S���ci�D�N����8!���K\��H; ���a������v\�{�
�����cl�L{5]��y�������S�q�l�a&
las��II��}qp����,������+}"L�"�NvM��Y��%��8�"��)���Wg(�{���'��vLU����o���J�z#�$^v�g�x���8� l�m�`#�����q��1�<�8�`�_�4��d���f�b���c]�Y2�AW1�\b�R�L�uG'�b�-�e��b�N��m������K��T_uk��</<�zA��1�H��0,� �}�(��� ��:Qya��E���)��z��H]��M	N�G�3@�|�����M���P����8���t!S����X�/�Q����B|��g���G����Vb��_�Z�uZW���z����������������^VVVf����[�66j�5��M��������No��0�1���,�����P>�~1�za�z^GT&���/~K�����;��8��6N:ad|��e2�{������o�{
��$���$=4~�K�
��oq�Z�f��VZ��Zss�x�I\�O�o>S��`����.&�6K�oO0���C0�����	��>�������1�Bb���*����p�Q���]�8uj�*'�|�����CU��6�+u8�b��a��tc�&��-d�Dl��q������kpx��$��QM��Y4:o+�w9kH���PQPv�b��B��t�0�E��
*��@�A�*�e!�^I
>M�HN)�S�I�$#��M������Q�EEM��
E^�G����������A�*6h��(���(�`��Zr�I�����������nl:n��S��AA(e���h��+
'S��HbL3��r�C;��Ff���~aJ�S�{��	;�6>�"�W?������S�����k������A����9%�y4�@i��DB�US/�$B�������r�,J1N�"�"L��a�tZt����C��������~xk�4J�k�r"�>������I"A��Y���R9�Od�6�����7w�N#V�U���*�?&�����Z�$P�6��.�*�����1Wc_�����N v0L
��..��"�v��A��������:s�>xp2b&���pk[F�oL���(����d,i���>�KDP��������IB����fQh�<��hh����o����%�G�����s�y���c������d����eR�oCk�a���u7��c]��X7���?�n����(A����AJv�7W����7�x���=��`�m(J.��A�����kN�K2
��n|����W���8cV���G|A?�@hA�E������d#�s����������t�l�}��}���%�`N ��u�w��E����@��@�;4Ko!��S�#!�F{�����G���`���
�$�������J�}}����K!��:�i�*(�_VT�]~W�J�Lv���d�-0@�W�dA	z�)_m�x�$����70Z~�)����9
k�}�f����~k5�L��=c(t3��#�A�T��T�
��2�� VA�A�c&]q���\���c�Y�^������&�	u#d=14M��#�����P�3�%�`Q6R
����]3���G�a�Np�P���?4F�@�Q8����'��Q==;��SUS'�����H�Yv�J�3/VO�L��9���4���	,���'&��:L�����������;���"9��n�NFQ�i�I%���l�9M"7��zlg����_"�w�5��M�B�%I��Y*��Y|v���~��������;���1,4S��\}���jF_$l�V���
���JcX��l�}��	��Bg�'{-���]�_v��q��(Y�`�NY*;o881d����$�(���}�����Y���	Gn��B��%�����uuv��m�����;s�y��7M�L�>�J������dU�q*���Z)�����[�}5�=���2	vY�p$�E��`k1~c��,�c�!�s�2�g5��/�i�s�
��U|=�:��Xl����Q�2�b��U�n��,��
@6E�`o�
���7�
��q3��TM��'�?4:K
.��O���33���Q@#Z�Vi�.ce�/1M<F!"Y�N����P�D��N��!0#�GwP����3^�^�0l��R�
�7����K������y�������3�eA�B%��������}}�R:8������&G�F����B��Ok.���}�N&�
�"#���R�L%'���K�Y��������cb>��Yg?g������2�:K)�A(++�D����b�G����F�S����+�Ma4f��%W���n�d��Q��P����p��e�Cgb��1c�������:���xIo\��)����jy�������f�u�!YVr�{R�� ���G��i��3�\�g]��E�h�R%�������_���Q:`��s����A�g��]	+��0��\��l��;*��Rh���W�wQ��t����0���X~�b�A�j������]HZ��4�8[��B'�r�p��x,9�.17J/B����M�]8������k��L�cr��.�u&h:L#x	���_b �D�pf�X4i��������~�i,��:����PUkU����W��L�-������������dn?r�Nf����>�*H�65Y��� ��y`���9`����9o+WW�4��r�p�ao���/1a�#9��;�%Hu{#�HrK2IK`�
��
��0�`��a��(m�!�:I���F#7K��/��,d�u�������wUw;������c6c��(�
J���@DB�aB�N���0�������8�2U�����{��>���n�h[g�X=��81������<���������w�W���HKg��7
��OLH_g5�����G��������4�5����q�)���S`������\�8�X�%F������\�����F}��Bm���_j�$����_����~#��pc�^_�����T�x=����E���5je���w���=�L�g/�"��u�5���[	"[4� O`�(�l?����g =��F>��m���#�,��
N�ex��W����%���/�-
'db�:�����vP}�zMc���TS��[�X�������f`v�������ur\VT���F���$`x�N���<`��	L���_ "�Q�����/��H�� ���Bp �2;�X�G������Y�q�"X����!�J�Y�JN�if�mE��y0L^/y�fkfd�7S>�{G�3����}���Y����)������y��`c`9�0!|���ZcEb���X/�1r1�5&(�����A
�L}:<��7w;�f�U��Y�TN��0Vsm}��q97�����^�� �����`�[yJ�+�T���v1���N�����ga�w`�;�0�uA���?����
�aV��l5����z}�����i�`�73
fMA�����d
�_fQ`�|��@t
%�WS����I8�V�<h~������|����p|#�c������x��P0C�K��"m�P�����
����ySn���Fo�f���~7lnm�ouf����[pC���������X��
8�^�X�-�
���ey����	�Y~%���-��U�]���T��fw����ok��"[+;�D�f-=���Z��7P,d��w�b��J�3��1Kd�|(1D��|b��^���NU{���{Y�:��E$��}����<r�A����������[-��4Q�|s<QL��a�A�-B
�����u8��H1rG��	'�M���B������h3N�����N�o������Z�n����];�k�~7f���9����U�K���v�,-�fa�KL���.���7p��-�S�����)L���u�.Ln.Q�k@��<y%���|��%W����r�����z��6�e�����4)=8����$<c�\T+E�g�WbQnW/5�"����^��6�Vk���t��2a1vL��9#o��q$K�J���D�e
��;��H����-svrri5�'f��w^n�<�K�����X	�-����W��Y+��7y������x���`��]c���5���w
�!=F����L5��>0 ������J��y�&����3��`2cj9��[����������f�����ek��%'�����M���r=��MF^Ql�g,6���M����x�.�+�FK%����K�����;�����|�0���Wa��I,������)���aG���JH���{�W�:��i��o�yM=������xn�2s;
;�$,ea���1/-	�A=�tc��6�\V���E������%�/&�m�GCM���F�y��
���u�yd��k��#mj����^/��f����g.M��t���sX��{v�(��+�h?|[�`�$W/��(��>���;t�����'�G�r���H�WR0D2&U� ��[����(C���N�x����F�E�[�'����J���K���N.(=/���$=�.y��XF�8{]������
Z6l8�����[F}����wKf��2O�3Of�k�c�+����~��<�[�bV�n�E�����@����Y�}�>Bx��f����x�C`�O?�
$3�Mg���~�6�9��Q�����X]���H_���5&9%^��q�h�<����������4Z88Z���L#c2��`:�����u��AY_~�Qq��Xk<��({.����
���<���h��	���w2��W��]������<��P�@���%��?zil��#��o���p��$�[kk������m����5�K%�B�st�!0s�Q7E]�h�%)���
^���h� 2�_\P�'��!me=Wq@�������R�K
-�Q�j����
�����_3(����_(B���M�l�'�G��h�_��@�����������k5F�7m�W�F�d��q��������E���X���a,^FV[l����E��bu��|aE3�������V�z���Y�+X���	�1a�fa���g|��L�b}X�j�}�FS�������v3��cn��|��+��jjT��U�����/�5L�64�(���'���"$�}��a���#�W�?�:�����8���oWz��=���t�!����W��W���|��w"�ve~W����O�cC���"ROc��8n,e$��~�����������yc����g/���?�����p�Y����Z����	��Wo�N��vz�"�E��0���G��
���q��7R���l���]�4|��%���"�����Z��-�Ve0���?������� ���K�^�D��7xd^
p#���u�py���sc�1�S$���>6�>f
��m���[�[��iF��QE}��#�$�daE�
����Z����������Uc)k��gF��+���l������\z�%43U>E����A>4*��p�G+$v�`LG�g���Y�����4��7����_2e�!w�/Sv���E�<
1�������%$M�S/�Z�S0n|D$�(s�7#~����B���,��������Lp9�x��ao(?��w�gh�����Oxp?���������pz.�t��r���j�SR|�# eN0<�-�Kk�u��F������V���m�����������@��l�`-��s+y\�
����������<�1�p�q�:dT2�����>�aW�K�����S�a�5��7���<��I�i���y=�Y{z��~TR=��A7SN<j��M������ch�������,?�Q��J�s8��;�s���4��1�������Yi��������G��?1�=��y��@�/�pg�!�p�T�o�S=�L�{��*4�H��0�����^��W=���}�3�4��t��	w���^[��'�K�Di@1Z�����}T�#���>��,OO�)�]`\�Ot���	&Q�^���C��1X������;/_rJ��/S�&�)]�2`0@bb��[��$�ix�`��B�m��chq2��Ap������E��[\��+��}~����qk~YY6�)��~�}J�{������t�S��_��@�7T��4��m��p%�^�m_(��x|r~q���`A"�[�i~���g^��Jm�L��������C�xt�"7�N����Gw�P�V��RE$��-{��27�n
JH�6�
)�><�W������8��C�R��;5�? �n�Z���Sb��Y/�9�'9��S�M�tM1)y��I�r�d,E��PE-��g�,�� w	���K��C���
F���������~~�?'����5�8)n,0�I��<����~��;���O:����C��^���p��A�Av�y�){E����������X���g�������yp�����7���]�����	�$S�����2��������o�k>"'�/�L�YB���X�s=owhm����A�P�
�R�^�&@<G�c��x��/���5X
��������Y��G��3p���
�u���.���E2:��u���3������G�a@",�������Q���}6� <��9V����W1�ys3U�]�T�K��t����z�M�5��o������z��~�������d�����%x"�Z��y��P�B����6�st�L.X��)ok�:�q8B�$F�A��&p�On/�u	�@��o����%Y�����yq�/��
������r���������z	���e	B-�/N�P{)S|*����c�Zx���}�����P�L^R�����(r��]	���xnP�~@��K�t����a��tI�c�G��/��#1t1��;���j���te(Xph�k��
1s��O
1�Kv:���������p�{�R�����p�	�]VD��]mlmu;�z����m�mM�V�n� P]��������mC�}�htn�O�N}�`s�x����v��B%a��pO�(��F����[�m}}[�0}�P��!����A�t���m��'�Xs�I��������K��x�
�����c�Bi�����LF�`�<�i��:�pgc��gc�Qk��2?7J�S�t�8a�a�2O��O[��&������Z[��Zksg.�%�V���A����������5W���V����������F*�)�g����O/V�R���I��DJ��@�������I�[��\����/������9o��s��������Y���~�����~q�Y�?�]�_w����?�$��?�_	p�*�Z�����/c�_n�:m5���F{�^<��s7�}Zc�-as�7���%��e���%��f�t�
��so�<ft����33�����s�y����s���y��?��rx
#98Bruce Momjian
bruce@momjian.us
In reply to: Pavel Stehule (#7)
Re: review: CHECK FUNCTION statement

What happened to this feature patch? A TODO?

---------------------------------------------------------------------------

On Tue, Nov 29, 2011 at 08:37:15PM +0100, Pavel Stehule wrote:

Hello

updated patch:

* recheck compilation and initdb
* working routines moved to pl_exec.c
* add entry to catalog.sgml about lanchecker field
* add node's utils

Regards

Pavel Stehule

2011/11/29 Albe Laurenz <laurenz.albe@wien.gv.at>:

Pavel Stehule wrote:

I am sending updated patch, that implements a CHECK FUNCTION and CHECK
TRIGGER statements.

This patch is significantly redesigned to previous version (PL/pgSQL
part) - it is more readable, more accurate. There are new regress
tests.

Please, can some English native speaker fix doc and comments?

ToDo:

CHECK FUNCTION search function according to function signature - it
should be changes for using a actual types - it can be solution for
polymorphic types and useful tool for work with overloaded functions -
when is not clean, that function was executed.

check function foo(int, int);
NOTICE: checking function foo(variadic anyarray)
...

and maybe some support for named parameters
check function foo(name text, surname text);
NOTICE: checking function foo(text, text, text, text)
...

I think that CHECK FUNCTION should work exactly like DROP FUNCTION
in these respects.

Submission review:
------------------

The patch is context diff, applies with some offsets, contains
regression tests and documentation.

The documentation should be expanded, the doc for CHECK FUNCTION
is only a stub. It should describe the procedure and what is checked.
That would also make reviewing easier.
I think that some documentation should be added to plhandler.sgml.
There is a spelling error (statemnt) in the docs.

Usability review:
-----------------

If I understand right, the goal of CHECK FUNCTION is to find errors in
the function definition without actually having to execute it.
The patch tries to provide this for PL/pgSQL.

There hasn't been any discussion on the list, the patch was just posted,
so I can't say that we want that. Tom added it to the commitfest page,
so there's one important voice against dismissing it right away :^)

I don't understand the functional difference between a "validator function"
and a "check function" as proposed by this patch. I am probably missing
something, but why couldn't these checks be added to function validation
when check_function_bodies is set?
A new "CHECK FUNCTION" statement could simply call the validator function.

I don't see any pg_dump support in this patch, and PL/pgSQL probably doesn't
need that, but I think pg_dump support for CREATE LANGUAGE would have to
be added for other PLs.

I can't test if the functionality is complete because I can't get it to
run (see below).

Feature test:
-------------

I can't really test the patch because initdb fails:

$ initdb -E UTF8 --locale=de_DE.UTF-8 --lc-messages=en_US.UTF-8 -U postgres /postgres/cvs/dbhome
The files belonging to this database system will be owned by user "laurenz".
This user must also own the server process.

The database cluster will be initialized with locales
�COLLATE: �de_DE.UTF-8
�CTYPE: � �de_DE.UTF-8
�MESSAGES: en_US.UTF-8
�MONETARY: de_DE.UTF-8
�NUMERIC: �de_DE.UTF-8
�TIME: � � de_DE.UTF-8
The default text search configuration will be set to "german".

creating directory /postgres/cvs/dbhome ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 32MB
creating configuration files ... ok
creating template1 database in /postgres/cvs/dbhome/base/1 ... ok
initializing pg_authid ... ok
initializing dependencies ... ok
creating system views ... ok
loading system objects' descriptions ... ok
creating collations ... ok
creating conversions ... ok
creating dictionaries ... ok
setting privileges on built-in objects ... ok
creating information schema ... ok
loading PL/pgSQL server-side language ... FATAL: �could not load library "/postgres/cvs/pg92/lib/plpgsql.so": /postgres/cvs/pg92/lib/plpgsql.so: undefined symbol: plpgsql_delete_function
STATEMENT: �CREATE EXTENSION plpgsql;

child process exited with exit code 1
initdb: removing data directory "/postgres/cvs/dbhome"

Coding review:
--------------

The patch compiles without warnings.
The comments in the code should be revised, they are bad English.
I can't say if there should be more of them -- I don't know this part of
the code well enough to have a well-founded opinion.

I don't think there are any portability issues, but I could not test it.

There are a lot of small changes to pl/plpgsql/src/pl_exec.c, are they all
necessary? For example, why was copy_plpgsql_datum renamed to
plpgsql_copy_datum?

I'll mark the patch as "Waiting on Author".

Yours,
Laurenz Albe

*** ./doc/src/sgml/catalogs.sgml.orig	2011-11-29 19:09:02.000000000 +0100
--- ./doc/src/sgml/catalogs.sgml	2011-11-29 20:28:00.571246006 +0100
***************
*** 3652,3657 ****
--- 3652,3668 ----
</row>
<row>
+       <entry><structfield>lanchecker</structfield></entry>
+       <entry><type>oid</type></entry>
+       <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+       <entry>
+        This references a language checker function that is responsible
+        for checking a embedded SQL and can provide detailed checking.
+        Zero if no checker is provided.
+       </entry>
+      </row>
+ 
+      <row>
<entry><structfield>lanacl</structfield></entry>
<entry><type>aclitem[]</type></entry>
<entry></entry>
*** ./doc/src/sgml/ref/allfiles.sgml.orig	2011-11-29 19:20:59.468117093 +0100
--- ./doc/src/sgml/ref/allfiles.sgml	2011-11-29 19:21:24.487804955 +0100
***************
*** 40,45 ****
--- 40,46 ----
<!ENTITY alterView          SYSTEM "alter_view.sgml">
<!ENTITY analyze            SYSTEM "analyze.sgml">
<!ENTITY begin              SYSTEM "begin.sgml">
+ <!ENTITY checkFunction      SYSTEM "check_function.sgml">
<!ENTITY checkpoint         SYSTEM "checkpoint.sgml">
<!ENTITY close              SYSTEM "close.sgml">
<!ENTITY cluster            SYSTEM "cluster.sgml">
*** ./doc/src/sgml/ref/create_language.sgml.orig	2011-11-29 19:20:59.470117069 +0100
--- ./doc/src/sgml/ref/create_language.sgml	2011-11-29 19:21:24.488804943 +0100
***************
*** 23,29 ****
<synopsis>
CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ]
</synopsis>
</refsynopsisdiv>
--- 23,29 ----
<synopsis>
CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ] [ CHECK <replaceable>checkfunction</replaceable> ]
</synopsis>
</refsynopsisdiv>
***************
*** 217,222 ****
--- 217,236 ----
</para>
</listitem>
</varlistentry>
+ 
+     <varlistentry>
+      <term><literal>CHECK</literal> <replaceable class="parameter">checkfunction</replaceable></term>
+ 
+      <listitem>
+       <para><replaceable class="parameter">checkfunction</replaceable> is the
+        name of a previously registered function that will be called
+        when a new function in the language is created, to check the
+        function by statemnt <command>CHECK FUNCTION</command> or 
+        <command>CHECK TRIGGER</command>.
+       </para>
+      </listitem>
+     </varlistentry>
+ 
</variablelist>
<para>
*** ./doc/src/sgml/reference.sgml.orig	2011-11-29 19:20:59.471117057 +0100
--- ./doc/src/sgml/reference.sgml	2011-11-29 19:21:24.492804895 +0100
***************
*** 68,73 ****
--- 68,74 ----
&alterView;
&analyze;
&begin;
+    &checkFunction;
&checkpoint;
&close;
&cluster;
*** ./src/backend/catalog/pg_proc.c.orig	2011-11-29 19:20:59.474117021 +0100
--- ./src/backend/catalog/pg_proc.c	2011-11-29 19:21:24.494804869 +0100
***************
*** 1101,1103 ****
--- 1101,1104 ----
*newcursorpos = newcp;
return false;
}
+ 
*** ./src/backend/commands/functioncmds.c.orig	2011-11-29 19:20:59.475117009 +0100
--- ./src/backend/commands/functioncmds.c	2011-11-29 19:21:24.496804843 +0100
***************
*** 44,53 ****
--- 44,55 ----
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_proc_fn.h"
+ #include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "catalog/pg_type_fn.h"
#include "commands/defrem.h"
#include "commands/proclang.h"
+ #include "commands/trigger.h"
#include "miscadmin.h"
#include "optimizer/var.h"
#include "parser/parse_coerce.h"
***************
*** 60,65 ****
--- 62,68 ----
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
+ #include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/tqual.h"
***************
*** 1009,1014 ****
--- 1012,1152 ----
}
}
+ /*
+  * CheckFunction
+  *			call a PL checker function when this function exists.
+  */
+ void
+ CheckFunction(CheckFunctionStmt *stmt)
+ {
+ 	List	   *functionName = stmt->funcname;
+ 	List	   *argTypes = stmt->args;	/* list of TypeName nodes */
+ 	Oid			funcOid;
+ 
+ 	HeapTuple	tup;
+ 	Form_pg_proc proc;
+ 
+ 	HeapTuple	languageTuple;
+ 	Form_pg_language languageStruct;
+ 	Oid		languageChecker;
+ 	Oid trgOid = InvalidOid;
+ 	Oid	relid = InvalidOid;
+ 
+ 	/* when we should to check trigger, then we should to find a trigger handler */
+ 	if (functionName == NULL)
+ 	{
+ 		HeapTuple	ht_trig;
+ 		Form_pg_trigger trigrec;
+ 		ScanKeyData skey[1];
+ 		Relation	tgrel;
+ 		SysScanDesc tgscan;
+ 		char *fname;
+ 
+ 		relid = RangeVarGetRelid(stmt->relation, ShareLock, false, false);
+ 		trgOid = get_trigger_oid(relid, stmt->trgname, false);
+ 
+ 		/*
+ 		 * Fetch the pg_trigger tuple by the Oid of the trigger
+ 		 */
+ 		tgrel = heap_open(TriggerRelationId, AccessShareLock);
+ 
+ 		ScanKeyInit(&skey[0],
+ 					ObjectIdAttributeNumber,
+ 					BTEqualStrategyNumber, F_OIDEQ,
+ 					ObjectIdGetDatum(trgOid));
+ 
+ 		tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true,
+ 									SnapshotNow, 1, skey);
+ 
+ 		ht_trig = systable_getnext(tgscan);
+ 
+ 		if (!HeapTupleIsValid(ht_trig))
+ 			elog(ERROR, "could not find tuple for trigger %u", trgOid);
+ 
+ 		trigrec = (Form_pg_trigger) GETSTRUCT(ht_trig);
+ 
+ 		/* we need to know trigger function to get PL checker function */
+ 		funcOid = trigrec->tgfoid;
+ 		fname = format_procedure(funcOid);
+ 		/* Clean up */
+ 		systable_endscan(tgscan);
+ 
+ 		elog(NOTICE, "checking function \"%s\"", fname);
+ 		pfree(fname);
+ 
+ 		heap_close(tgrel, AccessShareLock);
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * Find the function, 
+ 		 */
+ 		funcOid = LookupFuncNameTypeNames(functionName, argTypes, false);
+ 	}
+ 
+ 	tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
+ 	if (!HeapTupleIsValid(tup)) /* should not happen */
+ 		elog(ERROR, "cache lookup failed for function %u", funcOid);
+ 
+ 	proc = (Form_pg_proc) GETSTRUCT(tup);
+ 
+ 	languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
+ 	Assert(HeapTupleIsValid(languageTuple));
+ 
+ 	languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
+ 	languageChecker = languageStruct->lanchecker;
+ 
+ 	/* Check a function body */
+ 	if (OidIsValid(languageChecker))
+ 	{
+ 		ArrayType  *set_items = NULL;
+ 		int			save_nestlevel;
+ 		Datum	datum;
+ 		bool		isnull;
+ 		MemoryContext oldCxt;
+ 		MemoryContext checkCxt;
+ 
+ 		datum = SysCacheGetAttr(PROCOID, tup, Anum_pg_proc_proconfig, &isnull);
+ 
+ 		if (!isnull)
+ 		{
+ 			/* Set per-function configuration parameters */
+ 			set_items = (ArrayType *) DatumGetPointer(datum);
+ 			if (set_items)			/* Need a new GUC nesting level */
+ 			{
+ 				save_nestlevel = NewGUCNestLevel();
+ 				ProcessGUCArray(set_items,
+ 							(superuser() ? PGC_SUSET : PGC_USERSET),
+ 							PGC_S_SESSION,
+ 							GUC_ACTION_SAVE);
+ 			}
+ 			else
+ 				save_nestlevel = 0; /* keep compiler quiet */
+ 		}
+ 
+ 		checkCxt = AllocSetContextCreate(CurrentMemoryContext,
+ 									"Check temporary context",
+ 									ALLOCSET_DEFAULT_MINSIZE,
+ 									ALLOCSET_DEFAULT_INITSIZE,
+ 									ALLOCSET_DEFAULT_MAXSIZE);
+ 
+ 		oldCxt = MemoryContextSwitchTo(checkCxt);
+ 
+ 		OidFunctionCall2(languageChecker, ObjectIdGetDatum(funcOid), 
+ 							    ObjectIdGetDatum(relid));
+ 
+ 		MemoryContextSwitchTo(oldCxt);
+ 
+ 		if (set_items)
+ 			AtEOXact_GUC(true, save_nestlevel);
+ 	}
+ 	else
+ 		elog(WARNING, "language \"%s\" has no defined checker function",
+ 			    NameStr(languageStruct->lanname));
+ 
+ 	ReleaseSysCache(languageTuple);
+ 	ReleaseSysCache(tup);
+ }
/*
* Rename function
*** ./src/backend/commands/proclang.c.orig	2011-11-29 19:20:59.477116983 +0100
--- ./src/backend/commands/proclang.c	2011-11-29 19:21:24.497804830 +0100
***************
*** 46,57 ****
char	   *tmplhandler;	/* name of handler function */
char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
char	   *tmplvalidator;	/* name of validator function, or NULL */
char	   *tmpllibrary;	/* path of shared library */
} PLTemplate;
static void create_proc_lang(const char *languageName, bool replace,
Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, bool trusted);
static PLTemplate *find_language_template(const char *languageName);
static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
Oid newOwnerId);
--- 46,58 ----
char	   *tmplhandler;	/* name of handler function */
char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
char	   *tmplvalidator;	/* name of validator function, or NULL */
+ 	char	   *tmplchecker;	/* name of checker function, or NULL */
char	   *tmpllibrary;	/* path of shared library */
} PLTemplate;

static void create_proc_lang(const char *languageName, bool replace,
Oid languageOwner, Oid handlerOid, Oid inlineOid,
! Oid valOid, Oid checkerOid, bool trusted);
static PLTemplate *find_language_template(const char *languageName);
static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
Oid newOwnerId);
***************
*** 67,75 ****
PLTemplate *pltemplate;
Oid handlerOid,
inlineOid,
! valOid;
Oid funcrettype;
! Oid funcargtypes[1];

/*
* If we have template information for the language, ignore the supplied
--- 68,77 ----
PLTemplate *pltemplate;
Oid			handlerOid,
inlineOid,
! 				valOid,
! 				checkerOid;
Oid			funcrettype;
! 	Oid			funcargtypes[2];

/*
* If we have template information for the language, ignore the supplied
***************
*** 219,228 ****
else
valOid = InvalidOid;

/* ok, create it */
create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
handlerOid, inlineOid,
! 						 valOid, pltemplate->tmpltrusted);
}
else
{
--- 221,269 ----
else
valOid = InvalidOid;
+ 		/*
+ 		 * Likewise for the checker, if required; but we don't care about
+ 		 * its return type.
+ 		 */
+ 		if (pltemplate->tmplchecker)
+ 		{
+ 			funcname = SystemFuncName(pltemplate->tmplchecker);
+ 			funcargtypes[0] = OIDOID;
+ 			funcargtypes[1] = REGCLASSOID;
+ 			checkerOid = LookupFuncName(funcname, 2, funcargtypes, true);
+ 			if (!OidIsValid(checkerOid))
+ 			{
+ 				checkerOid = ProcedureCreate(pltemplate->tmplchecker,
+ 										 PG_CATALOG_NAMESPACE,
+ 										 false, /* replace */
+ 										 false, /* returnsSet */
+ 										 VOIDOID,
+ 										 ClanguageId,
+ 										 F_FMGR_C_VALIDATOR,
+ 										 pltemplate->tmplchecker,
+ 										 pltemplate->tmpllibrary,
+ 										 false, /* isAgg */
+ 										 false, /* isWindowFunc */
+ 										 false, /* security_definer */
+ 										 true,	/* isStrict */
+ 										 PROVOLATILE_VOLATILE,
+ 										 buildoidvector(funcargtypes, 2),
+ 										 PointerGetDatum(NULL),
+ 										 PointerGetDatum(NULL),
+ 										 PointerGetDatum(NULL),
+ 										 NIL,
+ 										 PointerGetDatum(NULL),
+ 										 1,
+ 										 0);
+ 			}
+ 		}
+ 		else
+ 			checkerOid = InvalidOid;
+ 
/* ok, create it */
create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
handlerOid, inlineOid,
! 						 valOid, checkerOid, pltemplate->tmpltrusted);
}
else
{
***************
*** 294,303 ****
else
valOid = InvalidOid;

/* ok, create it */
create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
handlerOid, inlineOid,
! valOid, stmt->pltrusted);
}
}

--- 335,355 ----
else
valOid = InvalidOid;
+ 		/* validate the checker function */
+ 		if (stmt->plchecker)
+ 		{
+ 			funcargtypes[0] = OIDOID;
+ 			funcargtypes[1] = REGCLASSOID;
+ 			checkerOid = LookupFuncName(stmt->plchecker, 2, funcargtypes, false);
+ 			/* return value is ignored, so we don't check the type */
+ 		}
+ 		else
+ 			checkerOid = InvalidOid;
+ 
/* ok, create it */
create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
handlerOid, inlineOid,
! 						 valOid, checkerOid, stmt->pltrusted);
}
}
***************
*** 307,313 ****
static void
create_proc_lang(const char *languageName, bool replace,
Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, bool trusted)
{
Relation	rel;
TupleDesc	tupDesc;
--- 359,365 ----
static void
create_proc_lang(const char *languageName, bool replace,
Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, Oid checkerOid, bool trusted)
{
Relation	rel;
TupleDesc	tupDesc;
***************
*** 337,342 ****
--- 389,395 ----
values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid);
values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid);
values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid);
+ 	values[Anum_pg_language_lanchecker - 1] = ObjectIdGetDatum(checkerOid);
nulls[Anum_pg_language_lanacl - 1] = true;
/* Check for pre-existing definition */
***************
*** 423,428 ****
--- 476,490 ----
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
+ 	/* dependency on the checker function, if any */
+ 	if (OidIsValid(checkerOid))
+ 	{
+ 		referenced.classId = ProcedureRelationId;
+ 		referenced.objectId = checkerOid;
+ 		referenced.objectSubId = 0;
+ 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 	}
+ 
/* Post creation hook for new procedural language */
InvokeObjectAccessHook(OAT_POST_CREATE,
LanguageRelationId, myself.objectId, 0);
***************
*** 478,483 ****
--- 540,550 ----
if (!isnull)
result->tmplvalidator = TextDatumGetCString(datum);
+ 		datum = heap_getattr(tup, Anum_pg_pltemplate_tmplchecker,
+ 							 RelationGetDescr(rel), &isnull);
+ 		if (!isnull)
+ 			result->tmplchecker = TextDatumGetCString(datum);
+ 
datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary,
RelationGetDescr(rel), &isnull);
if (!isnull)
*** ./src/backend/nodes/copyfuncs.c.orig	2011-11-29 19:09:02.000000000 +0100
--- ./src/backend/nodes/copyfuncs.c	2011-11-29 20:17:01.339172458 +0100
***************
*** 2880,2885 ****
--- 2880,2898 ----
return newnode;
}
+ static CheckFunctionStmt *
+ _copyCheckFunctionStmt(CheckFunctionStmt *from)
+ {
+ 	CheckFunctionStmt *newnode = makeNode(CheckFunctionStmt);
+ 
+ 	COPY_NODE_FIELD(funcname);
+ 	COPY_NODE_FIELD(args);
+ 	COPY_STRING_FIELD(trgname);
+ 	COPY_NODE_FIELD(relation);
+ 
+ 	return newnode;
+ }
+ 
static DoStmt *
_copyDoStmt(DoStmt *from)
{
***************
*** 4165,4170 ****
--- 4178,4186 ----
case T_AlterFunctionStmt:
retval = _copyAlterFunctionStmt(from);
break;
+ 		case T_CheckFunctionStmt:
+ 			retval = _copyCheckFunctionStmt(from);
+ 			break;
case T_DoStmt:
retval = _copyDoStmt(from);
break;
*** ./src/backend/nodes/equalfuncs.c.orig	2011-11-29 20:19:55.045587471 +0100
--- ./src/backend/nodes/equalfuncs.c	2011-11-29 20:19:21.850082357 +0100
***************
*** 1292,1297 ****
--- 1292,1308 ----
}
static bool
+ _equalCheckFunctionStmt(CheckFunctionStmt *a, CheckFunctionStmt *b)
+ {
+ 	COMPARE_NODE_FIELD(funcname);
+ 	COMPARE_NODE_FIELD(args);
+ 	COMPARE_STRING_FIELD(trgname);
+ 	COMPARE_NODE_FIELD(relation);
+ 
+ 	return true;
+ }
+ 
+ static bool
_equalDoStmt(DoStmt *a, DoStmt *b)
{
COMPARE_NODE_FIELD(args);
***************
*** 2708,2713 ****
--- 2719,2727 ----
case T_AlterFunctionStmt:
retval = _equalAlterFunctionStmt(a, b);
break;
+ 		case T_CheckFunctionStmt:
+ 			retval = _equalCheckFunctionStmt(a, b);
+ 			break;
case T_DoStmt:
retval = _equalDoStmt(a, b);
break;
*** ./src/backend/parser/gram.y.orig	2011-11-29 19:09:02.876463248 +0100
--- ./src/backend/parser/gram.y	2011-11-29 19:21:24.502804769 +0100
***************
*** 227,232 ****
--- 227,233 ----
DeallocateStmt PrepareStmt ExecuteStmt
DropOwnedStmt ReassignOwnedStmt
AlterTSConfigurationStmt AlterTSDictionaryStmt
+ 		CheckFunctionStmt

%type <node> select_no_parens select_with_parens select_clause
simple_select values_clause
***************
*** 276,282 ****

%type <list> func_name handler_name qual_Op qual_all_Op subquery_Op
opt_class opt_inline_handler opt_validator validator_clause
! opt_collate

%type <range> qualified_name OptConstrFromTable

--- 277,283 ----

%type <list> func_name handler_name qual_Op qual_all_Op subquery_Op
opt_class opt_inline_handler opt_validator validator_clause
! opt_collate opt_checker

%type <range> qualified_name OptConstrFromTable

***************
*** 700,705 ****
--- 701,707 ----
| AlterUserSetStmt
| AlterUserStmt
| AnalyzeStmt
+ 			| CheckFunctionStmt
| CheckPointStmt
| ClosePortalStmt
| ClusterStmt
***************
*** 3174,3184 ****
n->plhandler = NIL;
n->plinline = NIL;
n->plvalidator = NIL;
n->pltrusted = false;
$$ = (Node *)n;
}
| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
! 			  HANDLER handler_name opt_inline_handler opt_validator
{
CreatePLangStmt *n = makeNode(CreatePLangStmt);
n->replace = $2;
--- 3176,3187 ----
n->plhandler = NIL;
n->plinline = NIL;
n->plvalidator = NIL;
+ 				n->plchecker = NIL;
n->pltrusted = false;
$$ = (Node *)n;
}
| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
! 			  HANDLER handler_name opt_inline_handler opt_validator opt_checker
{
CreatePLangStmt *n = makeNode(CreatePLangStmt);
n->replace = $2;
***************
*** 3186,3191 ****
--- 3189,3195 ----
n->plhandler = $8;
n->plinline = $9;
n->plvalidator = $10;
+ 				n->plchecker = $11;
n->pltrusted = $3;
$$ = (Node *)n;
}
***************
*** 3220,3225 ****
--- 3224,3234 ----
| /*EMPTY*/								{ $$ = NIL; }
;
+ opt_checker:
+ 			CHECK handler_name					{ $$ = $2; }
+ 			| /*EMPTY*/								{ $$ = NIL; }
+ 		;
+ 
DropPLangStmt:
DROP opt_procedural LANGUAGE ColId_or_Sconst opt_drop_behavior
{
***************
*** 6250,6255 ****
--- 6259,6294 ----
/*****************************************************************************
*
+  *		CHECK FUNCTION funcname(args)
+  *		CHECK TRIGGER triggername ON table
+  *
+  *
+  *****************************************************************************/
+ 
+ 
+ CheckFunctionStmt:
+ 			CHECK FUNCTION func_name func_args
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = $3;
+ 					n->args = extractArgTypes($4);
+ 					n->trgname = NULL;
+ 					n->relation = NULL;
+ 					$$ = (Node *) n;
+ 				}
+ 			| CHECK TRIGGER name ON qualified_name
+ 				{
+ 					CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+ 					n->funcname = NULL;
+ 					n->args = NIL;
+ 					n->trgname = $3;
+ 					n->relation = $5;
+ 					$$ = (Node *) n;
+ 				}
+ 		;
+ 
+ /*****************************************************************************
+  *
*		DO <anonymous code block> [ LANGUAGE language ]
*
* We use a DefElem list for future extensibility, and to allow flexibility
*** ./src/backend/tcop/utility.c.orig	2011-11-29 19:20:59.480116945 +0100
--- ./src/backend/tcop/utility.c	2011-11-29 19:21:24.513804628 +0100
***************
*** 882,887 ****
--- 882,891 ----
AlterFunction((AlterFunctionStmt *) parsetree);
break;
+ 		case T_CheckFunctionStmt:
+ 			CheckFunction((CheckFunctionStmt *) parsetree);
+ 			break;
+ 
case T_IndexStmt:		/* CREATE INDEX */
{
IndexStmt  *stmt = (IndexStmt *) parsetree;
***************
*** 2125,2130 ****
--- 2129,2141 ----
}
break;
+ 		case T_CheckFunctionStmt:
+ 			if (((CheckFunctionStmt *) parsetree)->funcname != NULL)
+ 				tag = "CHECK FUNCTION";
+ 			else
+ 				tag = "CHECK TRIGGER";
+ 			break;
+ 
default:
elog(WARNING, "unrecognized node type: %d",
(int) nodeTag(parsetree));
***************
*** 2565,2570 ****
--- 2576,2585 ----
}
break;
+ 		case T_CheckFunctionStmt:
+ 			lev = LOGSTMT_ALL;
+ 			break;
+ 
default:
elog(WARNING, "unrecognized node type: %d",
(int) nodeTag(parsetree));
*** ./src/bin/pg_dump/pg_dump.c.orig	2011-11-29 19:09:03.000000000 +0100
--- ./src/bin/pg_dump/pg_dump.c	2011-11-29 20:04:31.094156626 +0100
***************
*** 5326,5338 ****
int			i_lanplcallfoid;
int			i_laninline;
int			i_lanvalidator;
int			i_lanacl;
int			i_lanowner;

/* Make sure we are in proper schema */
selectSourceSchema("pg_catalog");

! 	if (g_fout->remoteVersion >= 90000)
{
/* pg_language has a laninline column */
appendPQExpBuffer(query, "SELECT tableoid, oid, "
--- 5326,5351 ----
int			i_lanplcallfoid;
int			i_laninline;
int			i_lanvalidator;
+ 	int			i_lanchecker;
int			i_lanacl;
int			i_lanowner;

/* Make sure we are in proper schema */
selectSourceSchema("pg_catalog");

! 	if (g_fout->remoteVersion >= 90200)
! 	{
! 		/* pg_language has a lanchecker column */
! 		appendPQExpBuffer(query, "SELECT tableoid, oid, "
! 						  "lanname, lanpltrusted, lanplcallfoid, "
! 						  "laninline, lanvalidator, lanchecker, lanacl, "
! 						  "(%s lanowner) AS lanowner "
! 						  "FROM pg_language "
! 						  "WHERE lanispl "
! 						  "ORDER BY oid",
! 						  username_subquery);
! 	}
! 	else if (g_fout->remoteVersion >= 90000)
{
/* pg_language has a laninline column */
appendPQExpBuffer(query, "SELECT tableoid, oid, "
***************
*** 5409,5414 ****
--- 5422,5428 ----
/* these may fail and return -1: */
i_laninline = PQfnumber(res, "laninline");
i_lanvalidator = PQfnumber(res, "lanvalidator");
+ 	i_lanchecker = PQfnumber(res, "lanchecker");
i_lanacl = PQfnumber(res, "lanacl");
i_lanowner = PQfnumber(res, "lanowner");
***************
*** 5422,5427 ****
--- 5436,5445 ----
planginfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_lanname));
planginfo[i].lanpltrusted = *(PQgetvalue(res, i, i_lanpltrusted)) == 't';
planginfo[i].lanplcallfoid = atooid(PQgetvalue(res, i, i_lanplcallfoid));
+ 		if (i_lanchecker >= 0)
+ 			planginfo[i].lanchecker = atooid(PQgetvalue(res, i, i_lanchecker));
+ 		else
+ 			planginfo[i].lanchecker = InvalidOid;
if (i_laninline >= 0)
planginfo[i].laninline = atooid(PQgetvalue(res, i, i_laninline));
else
***************
*** 8597,8602 ****
--- 8615,8621 ----
char	   *qlanname;
char	   *lanschema;
FuncInfo   *funcInfo;
+ 	FuncInfo   *checkerInfo = NULL;
FuncInfo   *inlineInfo = NULL;
FuncInfo   *validatorInfo = NULL;
***************
*** 8616,8621 ****
--- 8635,8647 ----
if (funcInfo != NULL && !funcInfo->dobj.dump)
funcInfo = NULL;		/* treat not-dumped same as not-found */
+ 	if (OidIsValid(plang->lanchecker))
+ 	{
+ 		checkerInfo = findFuncByOid(plang->lanchecker);
+ 		if (checkerInfo != NULL && !checkerInfo->dobj.dump)
+ 			checkerInfo = NULL;
+ 	}
+ 
if (OidIsValid(plang->laninline))
{
inlineInfo = findFuncByOid(plang->laninline);
***************
*** 8642,8647 ****
--- 8668,8674 ----
* don't, this might not work terribly nicely.
*/
useParams = (funcInfo != NULL &&
+ 				 (checkerInfo != NULL || !OidIsValid(plang->lanchecker)) &&
(inlineInfo != NULL || !OidIsValid(plang->laninline)) &&
(validatorInfo != NULL || !OidIsValid(plang->lanvalidator)));
***************
*** 8697,8702 ****
--- 8724,8739 ----
appendPQExpBuffer(defqry, "%s",
fmtId(validatorInfo->dobj.name));
}
+ 		if (OidIsValid(plang->lanchecker))
+ 		{
+ 			appendPQExpBuffer(defqry, " CHECK ");
+ 			/* Cope with possibility that checker is in different schema */
+ 			if (checkerInfo->dobj.namespace != funcInfo->dobj.namespace)
+ 				appendPQExpBuffer(defqry, "%s.",
+ 							   fmtId(checkerInfo->dobj.namespace->dobj.name));
+ 			appendPQExpBuffer(defqry, "%s",
+ 							  fmtId(checkerInfo->dobj.name));
+ 		}
}
else
{
*** ./src/bin/pg_dump/pg_dump.h.orig	2011-11-29 20:05:48.255044631 +0100
--- ./src/bin/pg_dump/pg_dump.h	2011-11-29 20:05:08.766614345 +0100
***************
*** 387,392 ****
--- 387,393 ----
Oid			lanplcallfoid;
Oid			laninline;
Oid			lanvalidator;
+ 	Oid			lanchecker;
char	   *lanacl;
char	   *lanowner;		/* name of owner, or empty string */
} ProcLangInfo;
*** ./src/bin/psql/tab-complete.c.orig	2011-11-29 19:20:59.482116921 +0100
--- ./src/bin/psql/tab-complete.c	2011-11-29 19:21:24.516804592 +0100
***************
*** 1,4 ****
--- 1,5 ----
/*
+  *
* psql - the PostgreSQL interactive terminal
*
* Copyright (c) 2000-2011, PostgreSQL Global Development Group
***************
*** 727,733 ****
#define prev6_wd  (previous_words[5])
static const char *const sql_commands[] = {
! 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
--- 728,734 ----
#define prev6_wd  (previous_words[5])
static const char *const sql_commands[] = {
! 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECK", "CHECKPOINT", "CLOSE", "CLUSTER",
"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
***************
*** 1524,1529 ****
--- 1525,1552 ----
COMPLETE_WITH_LIST(list_TRANS);
}
+ 
+ /* CHECK */
+ 	else if (pg_strcasecmp(prev_wd, "CHECK") == 0)
+ 	{
+ 		static const char *const list_CHECK[] =
+ 		{"FUNCTION", "TRIGGER", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_CHECK);
+ 	}
+ 	else if (pg_strcasecmp(prev3_wd, "CHECK") == 0 &&
+ 			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+ 	{
+ 		COMPLETE_WITH_CONST("ON");
+ 	}
+ 	else if (pg_strcasecmp(prev4_wd, "CHECK") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "ON") == 0)
+ 	{
+ 		completion_info_charp = prev2_wd;
+ 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+ 	}
+ 
/* CLUSTER */
/*
*** ./src/include/catalog/pg_language.h.orig	2011-11-29 19:20:59.483116909 +0100
--- ./src/include/catalog/pg_language.h	2011-11-29 19:21:24.518804568 +0100
***************
*** 37,42 ****
--- 37,43 ----
Oid			lanplcallfoid;	/* Call handler for PL */
Oid			laninline;		/* Optional anonymous-block handler function */
Oid			lanvalidator;	/* Optional validation function */
+ 	Oid			lanchecker;	/* Optional checker function */
aclitem		lanacl[1];		/* Access privileges */
} FormData_pg_language;
***************
*** 51,57 ****
*		compiler constants for pg_language
* ----------------
*/
! #define Natts_pg_language				8
#define Anum_pg_language_lanname		1
#define Anum_pg_language_lanowner		2
#define Anum_pg_language_lanispl		3
--- 52,58 ----
*		compiler constants for pg_language
* ----------------
*/
! #define Natts_pg_language				9
#define Anum_pg_language_lanname		1
#define Anum_pg_language_lanowner		2
#define Anum_pg_language_lanispl		3
***************
*** 59,78 ****
#define Anum_pg_language_lanplcallfoid	5
#define Anum_pg_language_laninline		6
#define Anum_pg_language_lanvalidator	7
! #define Anum_pg_language_lanacl			8

/* ----------------
* initial contents of pg_language
* ----------------
*/

! DATA(insert OID = 12 ( "internal" PGUID f f 0 0 2246 _null_ ));
DESCR("built-in functions");
#define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c" PGUID f f 0 0 2247 _null_ ));
DESCR("dynamically-loaded C functions");
#define ClanguageId 13
! DATA(insert OID = 14 ( "sql" PGUID f t 0 0 2248 _null_ ));
DESCR("SQL-language functions");
#define SQLlanguageId 14

--- 60,80 ----
#define Anum_pg_language_lanplcallfoid	5
#define Anum_pg_language_laninline		6
#define Anum_pg_language_lanvalidator	7
! #define Anum_pg_language_lanchecker	8
! #define Anum_pg_language_lanacl			9

/* ----------------
* initial contents of pg_language
* ----------------
*/

! DATA(insert OID = 12 ( "internal" PGUID f f 0 0 2246 0 _null_ ));
DESCR("built-in functions");
#define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c" PGUID f f 0 0 2247 0 _null_ ));
DESCR("dynamically-loaded C functions");
#define ClanguageId 13
! DATA(insert OID = 14 ( "sql" PGUID f t 0 0 2248 0 _null_ ));
DESCR("SQL-language functions");
#define SQLlanguageId 14

*** ./src/include/catalog/pg_pltemplate.h.orig	2011-11-29 19:20:59.484116897 +0100
--- ./src/include/catalog/pg_pltemplate.h	2011-11-29 19:21:24.518804568 +0100
***************
*** 36,41 ****
--- 36,42 ----
text		tmplhandler;	/* name of call handler function */
text		tmplinline;		/* name of anonymous-block handler, or NULL */
text		tmplvalidator;	/* name of validator function, or NULL */
+ 	text		tmplchecker;	/* name of checker function, or NULL */
text		tmpllibrary;	/* path of shared library */
aclitem		tmplacl[1];		/* access privileges for template */
} FormData_pg_pltemplate;
***************
*** 51,65 ****
*		compiler constants for pg_pltemplate
* ----------------
*/
! #define Natts_pg_pltemplate					8
#define Anum_pg_pltemplate_tmplname			1
#define Anum_pg_pltemplate_tmpltrusted		2
#define Anum_pg_pltemplate_tmpldbacreate	3
#define Anum_pg_pltemplate_tmplhandler		4
#define Anum_pg_pltemplate_tmplinline		5
#define Anum_pg_pltemplate_tmplvalidator	6
! #define Anum_pg_pltemplate_tmpllibrary		7
! #define Anum_pg_pltemplate_tmplacl			8
/* ----------------
--- 52,67 ----
*		compiler constants for pg_pltemplate
* ----------------
*/
! #define Natts_pg_pltemplate					9
#define Anum_pg_pltemplate_tmplname			1
#define Anum_pg_pltemplate_tmpltrusted		2
#define Anum_pg_pltemplate_tmpldbacreate	3
#define Anum_pg_pltemplate_tmplhandler		4
#define Anum_pg_pltemplate_tmplinline		5
#define Anum_pg_pltemplate_tmplvalidator	6
! #define Anum_pg_pltemplate_tmplchecker		7
! #define Anum_pg_pltemplate_tmpllibrary		8
! #define Anum_pg_pltemplate_tmplacl			9

/* ----------------
***************
*** 67,79 ****
* ----------------
*/

! DATA(insert ( "plpgsql" t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl" t t "pltcl_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu" f f "pltclu_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl" t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu" f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu" f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u" f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u" f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" "$libdir/plpython3" _null_ ));

#endif   /* PG_PLTEMPLATE_H */
--- 69,81 ----
* ----------------
*/

! DATA(insert ( "plpgsql" t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "plpgsql_checker" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl" t t "pltcl_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu" f f "pltclu_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl" t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu" f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu" f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u" f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u" f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" _null_ "$libdir/plpython3" _null_ ));

#endif   /* PG_PLTEMPLATE_H */
*** ./src/include/commands/defrem.h.orig	2011-11-29 19:20:59.486116871 +0100
--- ./src/include/commands/defrem.h	2011-11-29 19:21:24.519804556 +0100
***************
*** 62,67 ****
--- 62,68 ----
/* commands/functioncmds.c */
extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
extern void RemoveFunctionById(Oid funcOid);
+ extern void CheckFunction(CheckFunctionStmt *stmt);
extern void SetFunctionReturnType(Oid funcOid, Oid newRetType);
extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType);
extern void RenameFunction(List *name, List *argtypes, const char *newname);
*** ./src/include/nodes/nodes.h.orig	2011-11-29 19:20:59.487116858 +0100
--- ./src/include/nodes/nodes.h	2011-11-29 19:21:24.521804532 +0100
***************
*** 291,296 ****
--- 291,297 ----
T_IndexStmt,
T_CreateFunctionStmt,
T_AlterFunctionStmt,
+ 	T_CheckFunctionStmt,
T_DoStmt,
T_RenameStmt,
T_RuleStmt,
*** ./src/include/nodes/parsenodes.h.orig	2011-11-29 19:20:59.489116833 +0100
--- ./src/include/nodes/parsenodes.h	2011-11-29 19:21:24.523804506 +0100
***************
*** 1734,1739 ****
--- 1734,1740 ----
List	   *plhandler;		/* PL call handler function (qual. name) */
List	   *plinline;		/* optional inline function (qual. name) */
List	   *plvalidator;	/* optional validator function (qual. name) */
+ 	List	   *plchecker;		/* optional checker function (qual. name) */
bool		pltrusted;		/* PL is trusted */
} CreatePLangStmt;
***************
*** 2077,2082 ****
--- 2078,2096 ----
} AlterFunctionStmt;
/* ----------------------
+  *		Check {Function|Trigger} Statement
+  * ----------------------
+  */
+ typedef struct CheckFunctionStmt
+ {
+ 	NodeTag		type;
+ 	List	   *funcname;			/* qualified name of checked object */
+ 	List	   *args;			/* types of the arguments */
+ 	char	   *trgname;			/* trigger's name */
+ 	RangeVar	*relation;		/* trigger's relation */
+ } CheckFunctionStmt;
+ 
+ /* ----------------------
*		DO Statement
*
* DoStmt is the raw parser output, InlineCodeBlock is the execution-time API
*** ./src/pl/plpgsql/src/pl_comp.c.orig	2011-11-29 19:09:03.000000000 +0100
--- ./src/pl/plpgsql/src/pl_comp.c	2011-11-29 19:42:43.058753779 +0100
***************
*** 115,121 ****
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.
--- 115,120 ----
***************
*** 175,181 ****
* 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
--- 174,180 ----
* 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
***************
*** 2426,2432 ****
}

/*
!  * 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
--- 2425,2431 ----
}
/*
!  * 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,2446 ****
* 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);
--- 2438,2445 ----
* 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);
*** ./src/pl/plpgsql/src/pl_exec.c.orig	2011-11-29 19:09:03.316459122 +0100
--- ./src/pl/plpgsql/src/pl_exec.c	2011-11-29 19:37:19.000000000 +0100
***************
*** 210,216 ****
static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
PLpgSQL_expr *dynquery, List *params,
const char *portalname, int cursorOptions);
! 
/* ----------
* plpgsql_exec_function	Called by the call handler for
--- 210,228 ----
static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
PLpgSQL_expr *dynquery, List *params,
const char *portalname, int cursorOptions);
! static void check_row_or_rec(PLpgSQL_execstate *estate, PLpgSQL_row *row, PLpgSQL_rec *rec);
! static void check_expr(PLpgSQL_execstate *estate, PLpgSQL_expr *expr);
! static void assign_tupdesc_row_or_rec(PLpgSQL_execstate *estate,
! 					    PLpgSQL_row *row, PLpgSQL_rec *rec,
! 								    TupleDesc tupdesc);
! static TupleDesc expr_get_desc(PLpgSQL_execstate *estate,
! 						PLpgSQL_expr *query,
! 							bool use_element_type,
! 							bool expand_record,
! 								bool is_expression);
! static void var_init_to_null(PLpgSQL_execstate *estate, int varno);
! static void check_stmts(PLpgSQL_execstate *estate, List *stmts);
! static void check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt);
/* ----------
* plpgsql_exec_function	Called by the call handler for
***************
*** 6176,6178 ****
--- 6188,7242 ----
return portal;
}
+ 
+ /*
+  * Following code ensures a CHECK FUNCTION and CHECK TRIGGER statements for PL/pgSQL
+  *
+  */
+ 
+ /*
+  * append a CONTEXT to error message
+  */
+ static void
+ check_error_callback(void *arg)
+ {
+ 	PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;
+ 
+ 	if (estate->err_stmt != NULL)
+ 	{
+ 		/* translator: last %s is a plpgsql statement type name */
+ 		errcontext("checking of PL/pgSQL function \"%s\" line %d at %s",
+ 				   estate->func->fn_name,
+ 				   estate->err_stmt->lineno,
+ 				   plpgsql_stmt_typename(estate->err_stmt));
+ 	}
+ 	else
+ 		errcontext("checking of PL/pgSQL function \"%s\"",
+ 				   estate->func->fn_name);
+ }
+ 
+ /*
+  * Check function - it prepare variables and starts a prepare plan walker
+  *		called by function checker
+  */
+ void
+ plpgsql_check_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
+ {
+ 	PLpgSQL_execstate estate;
+ 	ErrorContextCallback plerrcontext;
+ 	int			i;
+ 
+ 	/* Setup error callback for ereport */
+ 	plerrcontext.callback = check_error_callback;
+ 	plerrcontext.arg = &estate;
+ 	plerrcontext.previous = error_context_stack;
+ 	error_context_stack = &plerrcontext;
+ 
+ 	/*
+ 	 * Setup the execution state - we would to reuse some exec routines
+ 	 * so we need a estate
+ 	 */
+ 	plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo);
+ 
+ 	/*
+ 	 * Make local execution copies of all the datums
+ 	 */
+ 	for (i = 0; i < estate.ndatums; i++)
+ 		estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
+ 
+ 	/*
+ 	 * Store the actual call argument values into the appropriate variables
+ 	 */
+ 	for (i = 0; i < func->fn_nargs; i++)
+ 	{
+ 		int			n = func->fn_argvarnos[i];
+ 
+ 		switch (estate.datums[n]->dtype)
+ 		{
+ 			case PLPGSQL_DTYPE_VAR:
+ 				{
+ 					var_init_to_null(&estate, n);
+ 				}
+ 				break;
+ 
+ 			case PLPGSQL_DTYPE_ROW:
+ 				{
+ 					PLpgSQL_row *row = (PLpgSQL_row *) estate.datums[n];
+ 
+ 					exec_move_row(&estate, NULL, row, NULL, NULL);
+ 				}
+ 				break;
+ 
+ 			default:
+ 				elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(&estate, (PLpgSQL_stmt *) func->action);
+ 
+ 	/* Cleanup temporary memory */
+ 	plpgsql_destroy_econtext(&estate);
+ 
+ 	/* Pop the error context stack */
+ 	error_context_stack = plerrcontext.previous;
+ }
+ 
+ /*
+  * Check trigger - prepare fake environments for testing trigger
+  *
+  */
+ void
+ plpgsql_check_trigger(PLpgSQL_function *func,
+ 					 TriggerData *trigdata)
+ {
+ 	PLpgSQL_execstate estate;
+ 	ErrorContextCallback plerrcontext;
+ 	PLpgSQL_rec *rec_new,
+ 			   *rec_old;
+ 	int			i;
+ 
+ 	/* Setup error callback for ereport */
+ 	plerrcontext.callback = check_error_callback;
+ 	plerrcontext.arg = &estate;
+ 	plerrcontext.previous = error_context_stack;
+ 	error_context_stack = &plerrcontext;
+ 
+ 	/*
+ 	 * Setup the execution state - we would to reuse some exec routines
+ 	 * so we need a estate
+ 	 */
+ 	plpgsql_estate_setup(&estate, func, NULL);
+ 
+ 	/*
+ 	 * Make local execution copies of all the datums
+ 	 */
+ 	for (i = 0; i < estate.ndatums; i++)
+ 		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 *) (estate.datums[func->new_varno]);
+ 	rec_new->freetup = false;
+ 	rec_new->freetupdesc = false;
+ 	assign_tupdesc_row_or_rec(&estate, NULL, rec_new, trigdata->tg_relation->rd_att);
+ 
+ 	rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
+ 	rec_old->freetup = false;
+ 	rec_old->freetupdesc = false;
+ 	assign_tupdesc_row_or_rec(&estate, NULL, rec_old, trigdata->tg_relation->rd_att);
+ 
+ 	/*
+ 	 * Assign the special tg_ variables
+ 	 */
+ 	var_init_to_null(&estate, func->tg_op_varno);
+ 	var_init_to_null(&estate, func->tg_name_varno);
+ 	var_init_to_null(&estate, func->tg_when_varno);
+ 	var_init_to_null(&estate, func->tg_level_varno);
+ 	var_init_to_null(&estate, func->tg_relid_varno);
+ 	var_init_to_null(&estate, func->tg_relname_varno);
+ 	var_init_to_null(&estate, func->tg_table_name_varno);
+ 	var_init_to_null(&estate, func->tg_table_schema_varno);
+ 	var_init_to_null(&estate, func->tg_nargs_varno);
+ 	var_init_to_null(&estate, func->tg_argv_varno);
+ 
+ 	/*
+ 	 * Now check the toplevel block of statements
+ 	 */
+ 	check_stmt(&estate, (PLpgSQL_stmt *) func->action);
+ 
+ 	/* Cleanup temporary memory */
+ 	plpgsql_destroy_econtext(&estate);
+ 
+ 	/* Pop the error context stack */
+ 	error_context_stack = plerrcontext.previous;
+ }
+ 
+ /*
+  * Verify lvalue
+  *    It doesn't repeat a checks that are done.
+  *  Checks a subscript expressions, verify a validity of record's fields
+  */
+ static void
+ check_target(PLpgSQL_execstate *estate, int varno)
+ {
+ 	PLpgSQL_datum *target = estate->datums[varno];
+ 
+ 	switch (target->dtype)
+ 	{
+ 		case PLPGSQL_DTYPE_VAR:
+ 		case PLPGSQL_DTYPE_REC:
+ 			break;
+ 
+ 		case PLPGSQL_DTYPE_ROW:
+ 			check_row_or_rec(estate, (PLpgSQL_row *) target, NULL);
+ 			break;
+ 
+ 		case PLPGSQL_DTYPE_RECFIELD:
+ 			{
+ 				PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target;
+ 				PLpgSQL_rec *rec;
+ 				int			fno;
+ 
+ 				rec = (PLpgSQL_rec *) (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)));
+ 
+ 					check_expr(estate, arrayelem->subscript);
+ 
+ 					target = 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(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_execstate *estate, 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(estate, row->varnos[fnum]);
+ 		}
+ 	}
+ }
+ 
+ /*
+  * Generate a prepared plan - this is simplyfied copy from pl_exec.c
+  *   Is not necessary to check simple plan
+  */
+ static void
+ prepare_expr(PLpgSQL_execstate *estate,
+ 				  PLpgSQL_expr *expr, int cursorOptions)
+ {
+ 	SPIPlanPtr	plan;
+ 
+ 	/* leave when there are not expression */
+ 	if (expr == NULL)
+ 		return;
+ 
+ 	/* leave when plan is created */
+ 	if (expr->plan != NULL)
+ 		return;
+ 
+ 	/*
+ 	 * 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 = 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")));
+ 			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.")));
+ 			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_execstate *estate, PLpgSQL_expr *expr)
+ {
+ 	TupleDesc tupdesc;
+ 
+ 	if (expr != NULL)
+ 	{
+ 		prepare_expr(estate, expr, 0);
+ 		tupdesc = expr_get_desc(estate, expr, false, false, true);
+ 		ReleaseTupleDesc(tupdesc);
+ 	}
+ }
+ 
+ /*
+  * 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_execstate *estate,
+ 					    PLpgSQL_row *row, PLpgSQL_rec *rec,
+ 								    TupleDesc tupdesc)
+ {
+ 	bool	   *nulls;
+ 	HeapTuple  tup;
+ 
+ 	if (tupdesc == NULL)
+ 		elog(ERROR, "tuple descriptor is empty");
+ 
+ 	/*
+ 	 * row variable has assigned TupleDesc already, so don't be processed
+ 	 * here
+ 	 */
+ 	if (rec != NULL)
+ 	{
+ 		PLpgSQL_rec *target = (PLpgSQL_rec *)(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_execstate *estate, int varno, TupleDesc tupdesc)
+ {
+ 	PLpgSQL_datum *target = estate->datums[varno];
+ 
+ 	if (target->dtype == PLPGSQL_DTYPE_REC)
+ 		assign_tupdesc_row_or_rec(estate, NULL, (PLpgSQL_rec *) target, tupdesc);
+ }
+ 
+ /*
+  * Returns a tuple descriptor based on existing plan
+  */
+ static TupleDesc 
+ expr_get_desc(PLpgSQL_execstate *estate,
+ 						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);
+ 
+ 		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 (tupdesc->natts != 1)
+ 			ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 				 errmsg_plural("query \"%s\" returned %d column",
+ 							   "query \"%s\" returned %d columns",
+ 							   tupdesc->natts,
+ 							   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))));
+ 
+ 		/* 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
+ 			elog(ERROR, "cannot to identify real type for record type variable");
+ 	}
+ 
+ 	if (is_expression && tupdesc->natts != 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 			  errmsg_plural("query \"%s\" returned %d column",
+ 				   "query \"%s\" returned %d columns",
+ 				   tupdesc->natts,
+ 				   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(..)
+ 	 */
+ 	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)
+ 						elog(ERROR, "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
+  */
+ static void
+ check_stmts(PLpgSQL_execstate *estate, List *stmts)
+ {
+ 	ListCell *lc;
+ 
+ 	foreach(lc, stmts)
+ 	{
+ 		check_stmt(estate, (PLpgSQL_stmt *) lfirst(lc));
+ 	}
+ }
+ 
+ /*
+  * walk over all statements
+  */
+ static void
+ check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
+ {
+ 	TupleDesc	tupdesc = NULL;
+ 	PLpgSQL_function *func;
+ 	ListCell *l;
+ 
+ 	if (stmt == NULL)
+ 		return;
+ 
+ 	estate->err_stmt = stmt;
+ 	func = estate->func;
+ 
+ 	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++)
+ 				{
+ 					d = func->datums[stmt_block->initvarnos[i]];
+ 
+ 					if (d->dtype == PLPGSQL_DTYPE_VAR)
+ 					{
+ 						PLpgSQL_var *var = (PLpgSQL_var *) d;
+ 
+ 						check_expr(estate, var->default_val);
+ 					}
+ 				}
+ 
+ 				check_stmts(estate, stmt_block->body);
+ 				
+ 				if (stmt_block->exceptions)
+ 				{
+ 					foreach(l, stmt_block->exceptions->exc_list)
+ 					{
+ 						check_stmts(estate, ((PLpgSQL_exception *) lfirst(l))->action);
+ 					}
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_ASSIGN:
+ 			{
+ 				PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt;
+ 
+ 				/* prepare plan if desn't exist yet */
+ 				prepare_expr(estate, stmt_assign->expr, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_assign->expr,
+ 										false,		/* no element type */
+ 										true,		/* expand record */
+ 										true);		/* is expression */
+ 
+ 				/* check target, ensure target can get a result */
+ 				check_target(estate, stmt_assign->varno);
+ 
+ 				/* assign a tupdesc to record variable */
+ 				assign_tupdesc_dno(estate, stmt_assign->varno, tupdesc);
+ 				ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_IF:
+ 			{
+ 				PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt;
+ 				ListCell *l;
+ 
+ 				check_expr(estate, stmt_if->cond);
+ 
+ 				check_stmts(estate, stmt_if->then_body);
+ 
+ 				foreach(l, stmt_if->elsif_list)
+ 				{
+ 					PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l);
+ 
+ 					check_expr(estate, elif->cond);
+ 					check_stmts(estate, elif->stmts);
+ 				}
+ 
+ 				check_stmts(estate, 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 *) estate->datums[stmt_case->t_varno];
+ 
+ 					/* we need to set hidden variable type */
+ 					prepare_expr(estate, stmt_case->t_expr, 0);
+ 
+ 					tupdesc = expr_get_desc(estate,
+ 									stmt_case->t_expr,
+ 										false,		/* no element type */
+ 										false,		/* 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,
+ 												   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(estate, cwt->expr);
+ 					check_stmts(estate, cwt->stmts);
+ 				}
+ 
+ 				check_stmts(estate, stmt_case->else_stmts);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_LOOP:
+ 			check_stmts(estate, ((PLpgSQL_stmt_loop *) stmt)->body);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_WHILE:
+ 			{
+ 				PLpgSQL_stmt_while *stmt_while = (PLpgSQL_stmt_while *) stmt;
+ 
+ 				check_expr(estate, stmt_while->cond);
+ 				check_stmts(estate, stmt_while->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORI:
+ 			{
+ 				PLpgSQL_stmt_fori *stmt_fori = (PLpgSQL_stmt_fori *) stmt;
+ 
+ 				check_expr(estate, stmt_fori->lower);
+ 				check_expr(estate, stmt_fori->upper);
+ 				check_expr(estate, stmt_fori->step);
+ 
+ 				check_stmts(estate, stmt_fori->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORS:
+ 			{
+ 				PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt;
+ 
+ 				/* we need to set hidden variable type */
+ 				prepare_expr(estate, stmt_fors->query, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_fors->query,
+ 									false,		/* no element type */
+ 									false,		/* expand record */
+ 									false);		/* is expression */
+ 
+ 				check_row_or_rec(estate, stmt_fors->row, stmt_fors->rec);
+ 				assign_tupdesc_row_or_rec(estate, stmt_fors->row, stmt_fors->rec, tupdesc);
+ 
+ 				check_stmts(estate, stmt_fors->body);
+ 				ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORC:
+ 			{
+ 				PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt;
+ 				PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_forc->curvar];
+ 
+ 				prepare_expr(estate, stmt_forc->argquery, 0);
+ 
+ 				if (var->cursor_explicit_expr != NULL)
+ 				{
+ 					prepare_expr(estate, var->cursor_explicit_expr,
+ 								    var->cursor_options);
+ 
+ 					tupdesc = expr_get_desc(estate,
+ 									var->cursor_explicit_expr,
+ 										false,		/* no element type */
+ 										false,		/* expand record */
+ 										false);		/* is expression */
+ 
+ 					check_row_or_rec(estate, stmt_forc->row, stmt_forc->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_forc->row, stmt_forc->rec, tupdesc);
+ 				}
+ 
+ 				check_stmts(estate, stmt_forc->body);
+ 				if (tupdesc != NULL)
+ 					ReleaseTupleDesc(tupdesc);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNFORS:
+ 			{
+ 				PLpgSQL_stmt_dynfors * stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt;
+ 
+ 				if (stmt_dynfors->rec != NULL)
+ 					elog(ERROR, "cannot determinate a result of dynamic SQL");
+ 
+ 				check_expr(estate, stmt_dynfors->query);
+ 
+ 				foreach(l, stmt_dynfors->params)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				check_stmts(estate, stmt_dynfors->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FOREACH_A:
+ 			{
+ 				PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt;
+ 
+ 				prepare_expr(estate, stmt_foreach_a->expr, 0);
+ 
+ 				tupdesc = expr_get_desc(estate,
+ 								stmt_foreach_a->expr,
+ 									true,		/* no element type */
+ 									false,		/* expand record */
+ 									true);		/* is expression */
+ 
+ 				check_target(estate, stmt_foreach_a->varno);
+ 				assign_tupdesc_dno(estate, stmt_foreach_a->varno, tupdesc);
+ 				ReleaseTupleDesc(tupdesc);
+ 
+ 				check_stmts(estate, stmt_foreach_a->body);
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_EXIT:
+ 			check_expr(estate, ((PLpgSQL_stmt_exit *) stmt)->cond);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_PERFORM:
+ 			prepare_expr(estate, ((PLpgSQL_stmt_perform *) stmt)->expr, 0);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN:
+ 			check_expr(estate, ((PLpgSQL_stmt_return *) stmt)->expr);
+ 			break;
+ 
+ 		case PLPGSQL_STMT_RETURN_NEXT:
+ 			check_expr(estate, ((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(estate, stmt_rq->dynquery);
+ 				prepare_expr(estate, stmt_rq->query, 0);
+ 
+ 				foreach(l, stmt_rq->params)
+ 				{
+ 					check_expr(estate, (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(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				foreach(l, stmt_raise->options)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				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;
+ 
+ 				prepare_expr(estate, stmt_execsql->sqlstmt, 0);
+ 				if (stmt_execsql->into)
+ 				{
+ 					tupdesc = expr_get_desc(estate,
+ 								stmt_execsql->sqlstmt,
+ 											false,		/* no element type */
+ 											false,		/* expand record */
+ 											false);		/* is expression */
+ 
+ 					/* check target, ensure target can get a result */
+ 					check_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec, tupdesc);
+ 					ReleaseTupleDesc(tupdesc);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNEXECUTE:
+ 			{
+ 				PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt;
+ 
+ 				check_expr(estate, stmt_dynexecute->query);
+ 
+ 				foreach(l, stmt_dynexecute->params)
+ 				{
+ 					check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+ 				}
+ 
+ 				if (stmt_dynexecute->into)
+ 				{
+ 					if (stmt_dynexecute->rec != NULL)
+ 						elog(ERROR, "cannot determinate a result of dynamic SQL");
+ 
+ 					check_row_or_rec(estate, stmt_dynexecute->row, stmt_dynexecute->rec);
+ 				}
+ 			}
+ 			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)
+ 					prepare_expr(estate, var->cursor_explicit_expr,
+ 								   var->cursor_options);
+ 
+ 				prepare_expr(estate, stmt_open->query, 0);
+ 				prepare_expr(estate, stmt_open->argquery, 0);
+ 				check_expr(estate, stmt_open->dynquery);
+ 
+ 				foreach(l, stmt_open->params)
+ 				{
+ 					check_expr(estate, (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(estate, diag_item->target);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FETCH:
+ 			{
+ 				PLpgSQL_stmt_fetch *stmt_fetch = (PLpgSQL_stmt_fetch *) stmt;
+ 				PLpgSQL_var *var = (PLpgSQL_var *)(estate->datums[stmt_fetch->curvar]);
+ 
+ 				if (var != NULL && var->cursor_explicit_expr != NULL)
+ 				{
+ 					prepare_expr(estate, var->cursor_explicit_expr, 
+ 									    var->cursor_options);
+ 					tupdesc = expr_get_desc(estate,
+ 								    var->cursor_explicit_expr,
+ 											false,		/* no element type */
+ 											false,		/* expand record */
+ 											false);		/* is expression */
+ 					check_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec);
+ 					assign_tupdesc_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec, tupdesc);
+ 					ReleaseTupleDesc(tupdesc);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_CLOSE:
+ 			break;
+ 
+ 		default:
+ 			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
+ 			return; /* be compiler quite */
+ 	}
+ }
+ 
+ /*
+  * Initialize variable to NULL
+  */
+ static void
+ var_init_to_null(PLpgSQL_execstate *estate, int varno)
+ {
+ 	PLpgSQL_var *var = (PLpgSQL_var *) estate->datums[varno];
+ 	var->value = (Datum) 0;
+ 	var->isnull = true;
+ 	var->freeval = false;
+ }
*** ./src/pl/plpgsql/src/pl_handler.c.orig	2011-11-29 19:20:59.494116771 +0100
--- ./src/pl/plpgsql/src/pl_handler.c	2011-11-29 19:21:24.529804431 +0100
***************
*** 312,314 ****
--- 312,452 ----
PG_RETURN_VOID();
}
+ 
+ /* ----------
+  * plpgsql_checker
+  *
+  * This function attempts to check a embeded SQL inside a PL/pgSQL function at
+  * CHECK FUNCTION time. It should to have one or two parameters. Second
+  * parameter is a relation (used when function is trigger).
+  * ----------
+  */
+ PG_FUNCTION_INFO_V1(plpgsql_checker);
+ 
+ Datum
+ plpgsql_checker(PG_FUNCTION_ARGS)
+ {
+ 	Oid			funcoid = PG_GETARG_OID(0);
+ 	Oid			relid = PG_GETARG_OID(1);
+ 	HeapTuple	tuple;
+ 	FunctionCallInfoData fake_fcinfo;
+ 	FmgrInfo	flinfo;
+ 	TriggerData trigdata;
+ 	int			rc;
+ 	PLpgSQL_function *function;
+ 	PLpgSQL_execstate *cur_estate;
+ 
+ 	Form_pg_proc proc;
+ 	char		functyptype;
+ 	bool	   istrigger = false;
+ 
+ 	/* we don't need to repair a check done by validator */
+ 
+ 	tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
+ 	if (!HeapTupleIsValid(tuple))
+ 		elog(ERROR, "cache lookup failed for function %u", funcoid);
+ 	proc = (Form_pg_proc) GETSTRUCT(tuple);
+ 
+ 	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))
+ 		{
+ 			istrigger = true;
+ 			if (!OidIsValid(relid))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("PL/pgSQL trigger functions cannot be checked directly"),
+ 						 errhint("use CHECK TRIGGER statement instead")));
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Connect to SPI manager
+ 	 */
+ 	if ((rc = SPI_connect()) != SPI_OK_CONNECT)
+ 			elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
+ 
+ 	/*
+ 	 * Set up a fake fcinfo with just enough info to satisfy
+ 	 * plpgsql_compile().
+ 	 *
+ 	 * there should be a different real argtypes for polymorphic params
+ 	 */
+ 	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;
+ 		trigdata.tg_relation = relation_open(relid, AccessShareLock);
+ 		fake_fcinfo.context = (Node *) &trigdata;
+ 	}
+ 
+ 	/* Get a compiled function */
+ 	function = plpgsql_compile(&fake_fcinfo, false);
+ 
+ 	/* 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 prepare plans */
+ 	PG_TRY();
+ 	{
+ 		if (!istrigger)
+ 			plpgsql_check_function(function, &fake_fcinfo);
+ 		else
+ 			plpgsql_check_trigger(function, &trigdata);
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		if (istrigger)
+ 			relation_close(trigdata.tg_relation, AccessShareLock);
+ 
+ 		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);
+ 
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	if (istrigger)
+ 		relation_close(trigdata.tg_relation, AccessShareLock);
+ 
+ 	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);
+ 
+ 	/*
+ 	 * Disconnect from SPI manager
+ 	 */
+ 	if ((rc = SPI_finish()) != SPI_OK_FINISH)
+ 			elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
+ 
+ 	ReleaseSysCache(tuple);
+ 
+ 	PG_RETURN_VOID();
+ }
*** ./src/pl/plpgsql/src/plpgsql.h.orig	2011-11-29 19:20:59.500116698 +0100
--- ./src/pl/plpgsql/src/plpgsql.h	2011-11-29 20:22:19.423516596 +0100
***************
*** 902,907 ****
--- 902,908 ----
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,916 ****
--- 912,918 ----
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_checker(PG_FUNCTION_ARGS);
/* ----------
* Functions in pl_exec.c
***************
*** 928,933 ****
--- 930,939 ----
extern void exec_get_datum_type_info(PLpgSQL_execstate *estate,
PLpgSQL_datum *datum,
Oid *typeid, int32 *typmod, Oid *collation);
+ extern void plpgsql_check_function(PLpgSQL_function *func,
+ 					 FunctionCallInfo fcinfo);
+ extern void plpgsql_check_trigger(PLpgSQL_function *func,
+ 					 TriggerData *trigdata);
/* ----------
* Functions for namespace handling in pl_funcs.c
*** ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql.orig	2011-11-29 19:20:59.502116672 +0100
--- ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql	2011-11-29 19:21:24.533804381 +0100
***************
*** 5,7 ****
--- 5,8 ----
ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_call_handler();
ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_inline_handler(internal);
ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_validator(oid);
+ ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_checker(oid, regclass);
*** ./src/test/regress/expected/plpgsql.out.orig	2011-11-29 19:20:59.505116634 +0100
--- ./src/test/regress/expected/plpgsql.out	2011-11-29 19:21:24.536804342 +0100
***************
*** 302,307 ****
--- 302,310 ----
' 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
+ check trigger tg_hslot_biu on HSlot;
+ NOTICE:  checking function "tg_hslot_biu()"
-- ************************************************************
-- * BEFORE DELETE on HSlot
-- *	- prevent from manual manipulation
***************
*** 635,640 ****
--- 638,645 ----
raise exception ''illegal backlink beginning with %'', mytype;
end;
' language plpgsql;
+ -- check function should not fail
+ check function tg_backlink_set(bpchar, bpchar);
-- ************************************************************
-- * Support function to clear out the backlink field if
-- * it still points to specific slot
***************
*** 2802,2807 ****
--- 2807,2840 ----

(1 row)

+ -- check function should not fail
+ check function for_vect();
+ -- 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
***************
*** 3283,3288 ****
--- 3316,3323 ----
return;
end;
$$ language plpgsql;
+ -- check function should not fail
+ check function forc01();
select forc01();
NOTICE:  5 from c
NOTICE:  6 from c
***************
*** 3716,3721 ****
--- 3751,3758 ----
end case;
end;
$$ language plpgsql immutable;
+ -- check function should not fail
+ check function case_test(bigint);
select case_test(1);
case_test 
-----------
***************
*** 4571,4573 ****
--- 4608,4942 ----
CONTEXT:  PL/pgSQL function "testoa" line 5 at assignment
drop function arrayassign1();
drop function testoa(x1 int, x2 int, x3 int);
+ --
+ -- check function statement tests
+ --
+ create table t1(a int, b int);
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+ end;
+ $$ language plpgsql;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ check function f1();
+ ERROR:  column "c" of relation "t1" does not exist
+ LINE 1: update t1 set c = 30
+                       ^
+ QUERY:  update t1 set c = 30
+ CONTEXT:  checking of PL/pgSQL function "f1" line 4 at SQL statement
+ 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)
+ 
+ check function f1();
+ ERROR:  record "r" has no field "c"
+ CONTEXT:  SQL statement "SELECT r.c"
+ checking of PL/pgSQL function "f1" line 6 at RAISE
+ 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)
+ 
+ check function f1();
+ ERROR:  record "r" has no field "c"
+ CONTEXT:  SQL statement "SELECT r.c"
+ checking of PL/pgSQL function "f1" line 6 at RAISE
+ 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)
+ 
+ check function f1();
+ ERROR:  record "r" has no field "c"
+ CONTEXT:  checking of PL/pgSQL function "f1" line 6 at assignment
+ 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)
+ 
+ check function f1();
+ ERROR:  column "a" does not exist
+ LINE 1: SELECT a + b
+                ^
+ QUERY:  SELECT a + b
+ CONTEXT:  checking of PL/pgSQL function "f1" line 5 at assignment
+ 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)
+ 
+ check function f1();
+ ERROR:  too many parameters specified for RAISE
+ CONTEXT:  checking of PL/pgSQL function "f1" line 4 at RAISE
+ 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)
+ 
+ check function f1();
+ ERROR:  too few parameters specified for RAISE
+ CONTEXT:  checking of PL/pgSQL function "f1" line 4 at RAISE
+ 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)
+ 
+ check function f1();
+ ERROR:  column "c" does not exist
+ LINE 1: SELECT c+10
+                ^
+ QUERY:  SELECT c+10
+ CONTEXT:  checking of PL/pgSQL function "f1" line 5 at assignment
+ 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;
+ select f1();
+  f1 
+ ----
+  
+ (1 row)
+ 
+ check function f1();
+ ERROR:  subscripted object is not an array
+ CONTEXT:  checking of PL/pgSQL function "f1" line 5 at assignment
+ 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)
+ 
+ check function f1();
+ ERROR:  record "_exception" has no field "hint"
+ CONTEXT:  checking of PL/pgSQL function "f1" line 7 at GET DIAGNOSTICS
+ 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);
+ check trigger t1_f1 on t1;
+ NOTICE:  checking function "f1_trg()"
+ ERROR:  record "new" has no field "c"
+ CONTEXT:  SQL statement "SELECT new.c"
+ checking of PL/pgSQL function "f1_trg" line 5 at RAISE
+ 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
+ check trigger t1_f1 on t1;
+ NOTICE:  checking function "f1_trg()"
+ ERROR:  record "new" has no field "c"
+ CONTEXT:  checking of PL/pgSQL function "f1_trg" line 5 at assignment
+ -- 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
+ check trigger t1_f1 on t1;
+ NOTICE:  checking function "f1_trg()"
+ -- ok
+ insert into t1 values(6,30);
+ drop table t1;
+ drop type _exception_type;
+ drop function f1_trg();
*** ./src/test/regress/sql/plpgsql.sql.orig	2011-11-29 19:20:59.508116598 +0100
--- ./src/test/regress/sql/plpgsql.sql	2011-11-29 19:21:24.538804318 +0100
***************
*** 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
+ check trigger tg_hslot_biu on HSlot;

-- ************************************************************
-- * BEFORE DELETE on HSlot
***************
*** 747,752 ****
--- 749,757 ----
end;
' language plpgsql;
+ -- check function should not fail
+ check function tg_backlink_set(bpchar, bpchar);
+ 
-- ************************************************************
-- * Support function to clear out the backlink field if
***************
*** 2335,2340 ****
--- 2340,2352 ----

select for_vect();

+ -- check function should not fail
+ 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
***************
*** 2714,2719 ****
--- 2726,2734 ----
end;
$$ language plpgsql;
+ -- check function should not fail
+ check function forc01();
+ 
select forc01();
-- try updating the cursor's current row
***************
*** 3048,3053 ****
--- 3063,3071 ----
end;
$$ language plpgsql immutable;
+ -- check function should not fail
+ check function case_test(bigint);
+ 
select case_test(1);
select case_test(2);
select case_test(3);
***************
*** 3600,3602 ****
--- 3618,3862 ----
drop function arrayassign1();
drop function testoa(x1 int, x2 int, x3 int);
+ 
+ --
+ -- check function statement tests
+ --
+ 
+ create table t1(a int, b int);
+ 
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+ end;
+ $$ language plpgsql;
+ 
+ select f1();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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;
+ 
+ select f1();
+ 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();
+ 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);
+ check trigger t1_f1 on 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
+ check trigger t1_f1 on 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
+ check trigger t1_f1 on t1;
+ 
+ -- ok
+ insert into t1 values(6,30);
+ 
+ drop table t1;
+ drop type _exception_type;
+ 
+ drop function f1_trg();
+ 

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ It's impossible for everything to be true. +

#99Pavel Stehule
pavel.stehule@gmail.com
In reply to: Bruce Momjian (#98)
Re: review: CHECK FUNCTION statement

Hello

it is in open commitfest

http://archives.postgresql.org/message-id/CAFj8pRAYVTQYCL8_NF_hDQjc0m+JBvbwR6E_ZJ0SJfkKQ9m2kA@mail.gmail.com

Regards

Pavel

2012/8/17 Bruce Momjian <bruce@momjian.us>:

Show quoted text

What happened to this feature patch? A TODO?

---------------------------------------------------------------------------

On Tue, Nov 29, 2011 at 08:37:15PM +0100, Pavel Stehule wrote:

Hello

updated patch:

* recheck compilation and initdb
* working routines moved to pl_exec.c
* add entry to catalog.sgml about lanchecker field
* add node's utils

Regards

Pavel Stehule

2011/11/29 Albe Laurenz <laurenz.albe@wien.gv.at>:

Pavel Stehule wrote:

I am sending updated patch, that implements a CHECK FUNCTION and CHECK
TRIGGER statements.

This patch is significantly redesigned to previous version (PL/pgSQL
part) - it is more readable, more accurate. There are new regress
tests.

Please, can some English native speaker fix doc and comments?

ToDo:

CHECK FUNCTION search function according to function signature - it
should be changes for using a actual types - it can be solution for
polymorphic types and useful tool for work with overloaded functions -
when is not clean, that function was executed.

check function foo(int, int);
NOTICE: checking function foo(variadic anyarray)
...

and maybe some support for named parameters
check function foo(name text, surname text);
NOTICE: checking function foo(text, text, text, text)
...

I think that CHECK FUNCTION should work exactly like DROP FUNCTION
in these respects.

Submission review:
------------------

The patch is context diff, applies with some offsets, contains
regression tests and documentation.

The documentation should be expanded, the doc for CHECK FUNCTION
is only a stub. It should describe the procedure and what is checked.
That would also make reviewing easier.
I think that some documentation should be added to plhandler.sgml.
There is a spelling error (statemnt) in the docs.

Usability review:
-----------------

If I understand right, the goal of CHECK FUNCTION is to find errors in
the function definition without actually having to execute it.
The patch tries to provide this for PL/pgSQL.

There hasn't been any discussion on the list, the patch was just posted,
so I can't say that we want that. Tom added it to the commitfest page,
so there's one important voice against dismissing it right away :^)

I don't understand the functional difference between a "validator function"
and a "check function" as proposed by this patch. I am probably missing
something, but why couldn't these checks be added to function validation
when check_function_bodies is set?
A new "CHECK FUNCTION" statement could simply call the validator function.

I don't see any pg_dump support in this patch, and PL/pgSQL probably doesn't
need that, but I think pg_dump support for CREATE LANGUAGE would have to
be added for other PLs.

I can't test if the functionality is complete because I can't get it to
run (see below).

Feature test:
-------------

I can't really test the patch because initdb fails:

$ initdb -E UTF8 --locale=de_DE.UTF-8 --lc-messages=en_US.UTF-8 -U postgres /postgres/cvs/dbhome
The files belonging to this database system will be owned by user "laurenz".
This user must also own the server process.

The database cluster will be initialized with locales
COLLATE: de_DE.UTF-8
CTYPE: de_DE.UTF-8
MESSAGES: en_US.UTF-8
MONETARY: de_DE.UTF-8
NUMERIC: de_DE.UTF-8
TIME: de_DE.UTF-8
The default text search configuration will be set to "german".

creating directory /postgres/cvs/dbhome ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 32MB
creating configuration files ... ok
creating template1 database in /postgres/cvs/dbhome/base/1 ... ok
initializing pg_authid ... ok
initializing dependencies ... ok
creating system views ... ok
loading system objects' descriptions ... ok
creating collations ... ok
creating conversions ... ok
creating dictionaries ... ok
setting privileges on built-in objects ... ok
creating information schema ... ok
loading PL/pgSQL server-side language ... FATAL: could not load library "/postgres/cvs/pg92/lib/plpgsql.so": /postgres/cvs/pg92/lib/plpgsql.so: undefined symbol: plpgsql_delete_function
STATEMENT: CREATE EXTENSION plpgsql;

child process exited with exit code 1
initdb: removing data directory "/postgres/cvs/dbhome"

Coding review:
--------------

The patch compiles without warnings.
The comments in the code should be revised, they are bad English.
I can't say if there should be more of them -- I don't know this part of
the code well enough to have a well-founded opinion.

I don't think there are any portability issues, but I could not test it.

There are a lot of small changes to pl/plpgsql/src/pl_exec.c, are they all
necessary? For example, why was copy_plpgsql_datum renamed to
plpgsql_copy_datum?

I'll mark the patch as "Waiting on Author".

Yours,
Laurenz Albe

*** ./doc/src/sgml/catalogs.sgml.orig 2011-11-29 19:09:02.000000000 +0100
--- ./doc/src/sgml/catalogs.sgml      2011-11-29 20:28:00.571246006 +0100
***************
*** 3652,3657 ****
--- 3652,3668 ----
</row>
<row>
+       <entry><structfield>lanchecker</structfield></entry>
+       <entry><type>oid</type></entry>
+       <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+       <entry>
+        This references a language checker function that is responsible
+        for checking a embedded SQL and can provide detailed checking.
+        Zero if no checker is provided.
+       </entry>
+      </row>
+
+      <row>
<entry><structfield>lanacl</structfield></entry>
<entry><type>aclitem[]</type></entry>
<entry></entry>
*** ./doc/src/sgml/ref/allfiles.sgml.orig     2011-11-29 19:20:59.468117093 +0100
--- ./doc/src/sgml/ref/allfiles.sgml  2011-11-29 19:21:24.487804955 +0100
***************
*** 40,45 ****
--- 40,46 ----
<!ENTITY alterView          SYSTEM "alter_view.sgml">
<!ENTITY analyze            SYSTEM "analyze.sgml">
<!ENTITY begin              SYSTEM "begin.sgml">
+ <!ENTITY checkFunction      SYSTEM "check_function.sgml">
<!ENTITY checkpoint         SYSTEM "checkpoint.sgml">
<!ENTITY close              SYSTEM "close.sgml">
<!ENTITY cluster            SYSTEM "cluster.sgml">
*** ./doc/src/sgml/ref/create_language.sgml.orig      2011-11-29 19:20:59.470117069 +0100
--- ./doc/src/sgml/ref/create_language.sgml   2011-11-29 19:21:24.488804943 +0100
***************
*** 23,29 ****
<synopsis>
CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ]
</synopsis>
</refsynopsisdiv>
--- 23,29 ----
<synopsis>
CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
!     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ] [ CHECK <replaceable>checkfunction</replaceable> ]
</synopsis>
</refsynopsisdiv>
***************
*** 217,222 ****
--- 217,236 ----
</para>
</listitem>
</varlistentry>
+
+     <varlistentry>
+      <term><literal>CHECK</literal> <replaceable class="parameter">checkfunction</replaceable></term>
+
+      <listitem>
+       <para><replaceable class="parameter">checkfunction</replaceable> is the
+        name of a previously registered function that will be called
+        when a new function in the language is created, to check the
+        function by statemnt <command>CHECK FUNCTION</command> or
+        <command>CHECK TRIGGER</command>.
+       </para>
+      </listitem>
+     </varlistentry>
+
</variablelist>
<para>
*** ./doc/src/sgml/reference.sgml.orig        2011-11-29 19:20:59.471117057 +0100
--- ./doc/src/sgml/reference.sgml     2011-11-29 19:21:24.492804895 +0100
***************
*** 68,73 ****
--- 68,74 ----
&alterView;
&analyze;
&begin;
+    &checkFunction;
&checkpoint;
&close;
&cluster;
*** ./src/backend/catalog/pg_proc.c.orig      2011-11-29 19:20:59.474117021 +0100
--- ./src/backend/catalog/pg_proc.c   2011-11-29 19:21:24.494804869 +0100
***************
*** 1101,1103 ****
--- 1101,1104 ----
*newcursorpos = newcp;
return false;
}
+
*** ./src/backend/commands/functioncmds.c.orig        2011-11-29 19:20:59.475117009 +0100
--- ./src/backend/commands/functioncmds.c     2011-11-29 19:21:24.496804843 +0100
***************
*** 44,53 ****
--- 44,55 ----
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_proc_fn.h"
+ #include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "catalog/pg_type_fn.h"
#include "commands/defrem.h"
#include "commands/proclang.h"
+ #include "commands/trigger.h"
#include "miscadmin.h"
#include "optimizer/var.h"
#include "parser/parse_coerce.h"
***************
*** 60,65 ****
--- 62,68 ----
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
+ #include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/tqual.h"
***************
*** 1009,1014 ****
--- 1012,1152 ----
}
}
+ /*
+  * CheckFunction
+  *                  call a PL checker function when this function exists.
+  */
+ void
+ CheckFunction(CheckFunctionStmt *stmt)
+ {
+     List       *functionName = stmt->funcname;
+     List       *argTypes = stmt->args;      /* list of TypeName nodes */
+     Oid                     funcOid;
+
+     HeapTuple       tup;
+     Form_pg_proc proc;
+
+     HeapTuple       languageTuple;
+     Form_pg_language languageStruct;
+     Oid             languageChecker;
+     Oid trgOid = InvalidOid;
+     Oid     relid = InvalidOid;
+
+     /* when we should to check trigger, then we should to find a trigger handler */
+     if (functionName == NULL)
+     {
+             HeapTuple       ht_trig;
+             Form_pg_trigger trigrec;
+             ScanKeyData skey[1];
+             Relation        tgrel;
+             SysScanDesc tgscan;
+             char *fname;
+
+             relid = RangeVarGetRelid(stmt->relation, ShareLock, false, false);
+             trgOid = get_trigger_oid(relid, stmt->trgname, false);
+
+             /*
+              * Fetch the pg_trigger tuple by the Oid of the trigger
+              */
+             tgrel = heap_open(TriggerRelationId, AccessShareLock);
+
+             ScanKeyInit(&skey[0],
+                                     ObjectIdAttributeNumber,
+                                     BTEqualStrategyNumber, F_OIDEQ,
+                                     ObjectIdGetDatum(trgOid));
+
+             tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true,
+                                                                     SnapshotNow, 1, skey);
+
+             ht_trig = systable_getnext(tgscan);
+
+             if (!HeapTupleIsValid(ht_trig))
+                     elog(ERROR, "could not find tuple for trigger %u", trgOid);
+
+             trigrec = (Form_pg_trigger) GETSTRUCT(ht_trig);
+
+             /* we need to know trigger function to get PL checker function */
+             funcOid = trigrec->tgfoid;
+             fname = format_procedure(funcOid);
+             /* Clean up */
+             systable_endscan(tgscan);
+
+             elog(NOTICE, "checking function \"%s\"", fname);
+             pfree(fname);
+
+             heap_close(tgrel, AccessShareLock);
+     }
+     else
+     {
+             /*
+              * Find the function,
+              */
+             funcOid = LookupFuncNameTypeNames(functionName, argTypes, false);
+     }
+
+     tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
+     if (!HeapTupleIsValid(tup)) /* should not happen */
+             elog(ERROR, "cache lookup failed for function %u", funcOid);
+
+     proc = (Form_pg_proc) GETSTRUCT(tup);
+
+     languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
+     Assert(HeapTupleIsValid(languageTuple));
+
+     languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
+     languageChecker = languageStruct->lanchecker;
+
+     /* Check a function body */
+     if (OidIsValid(languageChecker))
+     {
+             ArrayType  *set_items = NULL;
+             int                     save_nestlevel;
+             Datum   datum;
+             bool            isnull;
+             MemoryContext oldCxt;
+             MemoryContext checkCxt;
+
+             datum = SysCacheGetAttr(PROCOID, tup, Anum_pg_proc_proconfig, &isnull);
+
+             if (!isnull)
+             {
+                     /* Set per-function configuration parameters */
+                     set_items = (ArrayType *) DatumGetPointer(datum);
+                     if (set_items)                  /* Need a new GUC nesting level */
+                     {
+                             save_nestlevel = NewGUCNestLevel();
+                             ProcessGUCArray(set_items,
+                                                     (superuser() ? PGC_SUSET : PGC_USERSET),
+                                                     PGC_S_SESSION,
+                                                     GUC_ACTION_SAVE);
+                     }
+                     else
+                             save_nestlevel = 0; /* keep compiler quiet */
+             }
+
+             checkCxt = AllocSetContextCreate(CurrentMemoryContext,
+                                                                     "Check temporary context",
+                                                                     ALLOCSET_DEFAULT_MINSIZE,
+                                                                     ALLOCSET_DEFAULT_INITSIZE,
+                                                                     ALLOCSET_DEFAULT_MAXSIZE);
+
+             oldCxt = MemoryContextSwitchTo(checkCxt);
+
+             OidFunctionCall2(languageChecker, ObjectIdGetDatum(funcOid),
+                                                         ObjectIdGetDatum(relid));
+
+             MemoryContextSwitchTo(oldCxt);
+
+             if (set_items)
+                     AtEOXact_GUC(true, save_nestlevel);
+     }
+     else
+             elog(WARNING, "language \"%s\" has no defined checker function",
+                         NameStr(languageStruct->lanname));
+
+     ReleaseSysCache(languageTuple);
+     ReleaseSysCache(tup);
+ }
/*
* Rename function
*** ./src/backend/commands/proclang.c.orig    2011-11-29 19:20:59.477116983 +0100
--- ./src/backend/commands/proclang.c 2011-11-29 19:21:24.497804830 +0100
***************
*** 46,57 ****
char       *tmplhandler;        /* name of handler function */
char       *tmplinline;         /* name of anonymous-block handler, or NULL */
char       *tmplvalidator;      /* name of validator function, or NULL */
char       *tmpllibrary;        /* path of shared library */
} PLTemplate;
static void create_proc_lang(const char *languageName, bool replace,
Oid languageOwner, Oid handlerOid, Oid inlineOid,
!                              Oid valOid, bool trusted);
static PLTemplate *find_language_template(const char *languageName);
static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
Oid newOwnerId);
--- 46,58 ----
char       *tmplhandler;        /* name of handler function */
char       *tmplinline;         /* name of anonymous-block handler, or NULL */
char       *tmplvalidator;      /* name of validator function, or NULL */
+     char       *tmplchecker;        /* name of checker function, or NULL */
char       *tmpllibrary;        /* path of shared library */
} PLTemplate;

static void create_proc_lang(const char *languageName, bool replace,
Oid languageOwner, Oid handlerOid, Oid inlineOid,
! Oid valOid, Oid checkerOid, bool trusted);
static PLTemplate *find_language_template(const char *languageName);
static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel,
Oid newOwnerId);
***************
*** 67,75 ****
PLTemplate *pltemplate;
Oid handlerOid,
inlineOid,
! valOid;
Oid funcrettype;
! Oid funcargtypes[1];

/*
* If we have template information for the language, ignore the supplied
--- 68,77 ----
PLTemplate *pltemplate;
Oid                     handlerOid,
inlineOid,
!                             valOid,
!                             checkerOid;
Oid                     funcrettype;
!     Oid                     funcargtypes[2];

/*
* If we have template information for the language, ignore the supplied
***************
*** 219,228 ****
else
valOid = InvalidOid;

/* ok, create it */
create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
handlerOid, inlineOid,
!                                              valOid, pltemplate->tmpltrusted);
}
else
{
--- 221,269 ----
else
valOid = InvalidOid;
+             /*
+              * Likewise for the checker, if required; but we don't care about
+              * its return type.
+              */
+             if (pltemplate->tmplchecker)
+             {
+                     funcname = SystemFuncName(pltemplate->tmplchecker);
+                     funcargtypes[0] = OIDOID;
+                     funcargtypes[1] = REGCLASSOID;
+                     checkerOid = LookupFuncName(funcname, 2, funcargtypes, true);
+                     if (!OidIsValid(checkerOid))
+                     {
+                             checkerOid = ProcedureCreate(pltemplate->tmplchecker,
+                                                                              PG_CATALOG_NAMESPACE,
+                                                                              false, /* replace */
+                                                                              false, /* returnsSet */
+                                                                              VOIDOID,
+                                                                              ClanguageId,
+                                                                              F_FMGR_C_VALIDATOR,
+                                                                              pltemplate->tmplchecker,
+                                                                              pltemplate->tmpllibrary,
+                                                                              false, /* isAgg */
+                                                                              false, /* isWindowFunc */
+                                                                              false, /* security_definer */
+                                                                              true,  /* isStrict */
+                                                                              PROVOLATILE_VOLATILE,
+                                                                              buildoidvector(funcargtypes, 2),
+                                                                              PointerGetDatum(NULL),
+                                                                              PointerGetDatum(NULL),
+                                                                              PointerGetDatum(NULL),
+                                                                              NIL,
+                                                                              PointerGetDatum(NULL),
+                                                                              1,
+                                                                              0);
+                     }
+             }
+             else
+                     checkerOid = InvalidOid;
+
/* ok, create it */
create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
handlerOid, inlineOid,
!                                              valOid, checkerOid, pltemplate->tmpltrusted);
}
else
{
***************
*** 294,303 ****
else
valOid = InvalidOid;

/* ok, create it */
create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
handlerOid, inlineOid,
! valOid, stmt->pltrusted);
}
}

--- 335,355 ----
else
valOid = InvalidOid;
+             /* validate the checker function */
+             if (stmt->plchecker)
+             {
+                     funcargtypes[0] = OIDOID;
+                     funcargtypes[1] = REGCLASSOID;
+                     checkerOid = LookupFuncName(stmt->plchecker, 2, funcargtypes, false);
+                     /* return value is ignored, so we don't check the type */
+             }
+             else
+                     checkerOid = InvalidOid;
+
/* ok, create it */
create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
handlerOid, inlineOid,
!                                              valOid, checkerOid, stmt->pltrusted);
}
}
***************
*** 307,313 ****
static void
create_proc_lang(const char *languageName, bool replace,
Oid languageOwner, Oid handlerOid, Oid inlineOid,
!                              Oid valOid, bool trusted)
{
Relation        rel;
TupleDesc       tupDesc;
--- 359,365 ----
static void
create_proc_lang(const char *languageName, bool replace,
Oid languageOwner, Oid handlerOid, Oid inlineOid,
!                              Oid valOid, Oid checkerOid, bool trusted)
{
Relation        rel;
TupleDesc       tupDesc;
***************
*** 337,342 ****
--- 389,395 ----
values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid);
values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid);
values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid);
+     values[Anum_pg_language_lanchecker - 1] = ObjectIdGetDatum(checkerOid);
nulls[Anum_pg_language_lanacl - 1] = true;
/* Check for pre-existing definition */
***************
*** 423,428 ****
--- 476,490 ----
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
+     /* dependency on the checker function, if any */
+     if (OidIsValid(checkerOid))
+     {
+             referenced.classId = ProcedureRelationId;
+             referenced.objectId = checkerOid;
+             referenced.objectSubId = 0;
+             recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+     }
+
/* Post creation hook for new procedural language */
InvokeObjectAccessHook(OAT_POST_CREATE,
LanguageRelationId, myself.objectId, 0);
***************
*** 478,483 ****
--- 540,550 ----
if (!isnull)
result->tmplvalidator = TextDatumGetCString(datum);
+             datum = heap_getattr(tup, Anum_pg_pltemplate_tmplchecker,
+                                                      RelationGetDescr(rel), &isnull);
+             if (!isnull)
+                     result->tmplchecker = TextDatumGetCString(datum);
+
datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary,
RelationGetDescr(rel), &isnull);
if (!isnull)
*** ./src/backend/nodes/copyfuncs.c.orig      2011-11-29 19:09:02.000000000 +0100
--- ./src/backend/nodes/copyfuncs.c   2011-11-29 20:17:01.339172458 +0100
***************
*** 2880,2885 ****
--- 2880,2898 ----
return newnode;
}
+ static CheckFunctionStmt *
+ _copyCheckFunctionStmt(CheckFunctionStmt *from)
+ {
+     CheckFunctionStmt *newnode = makeNode(CheckFunctionStmt);
+
+     COPY_NODE_FIELD(funcname);
+     COPY_NODE_FIELD(args);
+     COPY_STRING_FIELD(trgname);
+     COPY_NODE_FIELD(relation);
+
+     return newnode;
+ }
+
static DoStmt *
_copyDoStmt(DoStmt *from)
{
***************
*** 4165,4170 ****
--- 4178,4186 ----
case T_AlterFunctionStmt:
retval = _copyAlterFunctionStmt(from);
break;
+             case T_CheckFunctionStmt:
+                     retval = _copyCheckFunctionStmt(from);
+                     break;
case T_DoStmt:
retval = _copyDoStmt(from);
break;
*** ./src/backend/nodes/equalfuncs.c.orig     2011-11-29 20:19:55.045587471 +0100
--- ./src/backend/nodes/equalfuncs.c  2011-11-29 20:19:21.850082357 +0100
***************
*** 1292,1297 ****
--- 1292,1308 ----
}
static bool
+ _equalCheckFunctionStmt(CheckFunctionStmt *a, CheckFunctionStmt *b)
+ {
+     COMPARE_NODE_FIELD(funcname);
+     COMPARE_NODE_FIELD(args);
+     COMPARE_STRING_FIELD(trgname);
+     COMPARE_NODE_FIELD(relation);
+
+     return true;
+ }
+
+ static bool
_equalDoStmt(DoStmt *a, DoStmt *b)
{
COMPARE_NODE_FIELD(args);
***************
*** 2708,2713 ****
--- 2719,2727 ----
case T_AlterFunctionStmt:
retval = _equalAlterFunctionStmt(a, b);
break;
+             case T_CheckFunctionStmt:
+                     retval = _equalCheckFunctionStmt(a, b);
+                     break;
case T_DoStmt:
retval = _equalDoStmt(a, b);
break;
*** ./src/backend/parser/gram.y.orig  2011-11-29 19:09:02.876463248 +0100
--- ./src/backend/parser/gram.y       2011-11-29 19:21:24.502804769 +0100
***************
*** 227,232 ****
--- 227,233 ----
DeallocateStmt PrepareStmt ExecuteStmt
DropOwnedStmt ReassignOwnedStmt
AlterTSConfigurationStmt AlterTSDictionaryStmt
+             CheckFunctionStmt

%type <node> select_no_parens select_with_parens select_clause
simple_select values_clause
***************
*** 276,282 ****

%type <list> func_name handler_name qual_Op qual_all_Op subquery_Op
opt_class opt_inline_handler opt_validator validator_clause
! opt_collate

%type <range> qualified_name OptConstrFromTable

--- 277,283 ----

%type <list> func_name handler_name qual_Op qual_all_Op subquery_Op
opt_class opt_inline_handler opt_validator validator_clause
! opt_collate opt_checker

%type <range> qualified_name OptConstrFromTable

***************
*** 700,705 ****
--- 701,707 ----
| AlterUserSetStmt
| AlterUserStmt
| AnalyzeStmt
+                     | CheckFunctionStmt
| CheckPointStmt
| ClosePortalStmt
| ClusterStmt
***************
*** 3174,3184 ****
n->plhandler = NIL;
n->plinline = NIL;
n->plvalidator = NIL;
n->pltrusted = false;
$$ = (Node *)n;
}
| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
!                       HANDLER handler_name opt_inline_handler opt_validator
{
CreatePLangStmt *n = makeNode(CreatePLangStmt);
n->replace = $2;
--- 3176,3187 ----
n->plhandler = NIL;
n->plinline = NIL;
n->plvalidator = NIL;
+                             n->plchecker = NIL;
n->pltrusted = false;
$$ = (Node *)n;
}
| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
!                       HANDLER handler_name opt_inline_handler opt_validator opt_checker
{
CreatePLangStmt *n = makeNode(CreatePLangStmt);
n->replace = $2;
***************
*** 3186,3191 ****
--- 3189,3195 ----
n->plhandler = $8;
n->plinline = $9;
n->plvalidator = $10;
+                             n->plchecker = $11;
n->pltrusted = $3;
$$ = (Node *)n;
}
***************
*** 3220,3225 ****
--- 3224,3234 ----
| /*EMPTY*/                                                             { $$ = NIL; }
;
+ opt_checker:
+                     CHECK handler_name                                      { $$ = $2; }
+                     | /*EMPTY*/                                                             { $$ = NIL; }
+             ;
+
DropPLangStmt:
DROP opt_procedural LANGUAGE ColId_or_Sconst opt_drop_behavior
{
***************
*** 6250,6255 ****
--- 6259,6294 ----
/*****************************************************************************
*
+  *          CHECK FUNCTION funcname(args)
+  *          CHECK TRIGGER triggername ON table
+  *
+  *
+  *****************************************************************************/
+
+
+ CheckFunctionStmt:
+                     CHECK FUNCTION func_name func_args
+                             {
+                                     CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+                                     n->funcname = $3;
+                                     n->args = extractArgTypes($4);
+                                     n->trgname = NULL;
+                                     n->relation = NULL;
+                                     $$ = (Node *) n;
+                             }
+                     | CHECK TRIGGER name ON qualified_name
+                             {
+                                     CheckFunctionStmt *n = makeNode(CheckFunctionStmt);
+                                     n->funcname = NULL;
+                                     n->args = NIL;
+                                     n->trgname = $3;
+                                     n->relation = $5;
+                                     $$ = (Node *) n;
+                             }
+             ;
+
+ /*****************************************************************************
+  *
*          DO <anonymous code block> [ LANGUAGE language ]
*
* We use a DefElem list for future extensibility, and to allow flexibility
*** ./src/backend/tcop/utility.c.orig 2011-11-29 19:20:59.480116945 +0100
--- ./src/backend/tcop/utility.c      2011-11-29 19:21:24.513804628 +0100
***************
*** 882,887 ****
--- 882,891 ----
AlterFunction((AlterFunctionStmt *) parsetree);
break;
+             case T_CheckFunctionStmt:
+                     CheckFunction((CheckFunctionStmt *) parsetree);
+                     break;
+
case T_IndexStmt:               /* CREATE INDEX */
{
IndexStmt  *stmt = (IndexStmt *) parsetree;
***************
*** 2125,2130 ****
--- 2129,2141 ----
}
break;
+             case T_CheckFunctionStmt:
+                     if (((CheckFunctionStmt *) parsetree)->funcname != NULL)
+                             tag = "CHECK FUNCTION";
+                     else
+                             tag = "CHECK TRIGGER";
+                     break;
+
default:
elog(WARNING, "unrecognized node type: %d",
(int) nodeTag(parsetree));
***************
*** 2565,2570 ****
--- 2576,2585 ----
}
break;
+             case T_CheckFunctionStmt:
+                     lev = LOGSTMT_ALL;
+                     break;
+
default:
elog(WARNING, "unrecognized node type: %d",
(int) nodeTag(parsetree));
*** ./src/bin/pg_dump/pg_dump.c.orig  2011-11-29 19:09:03.000000000 +0100
--- ./src/bin/pg_dump/pg_dump.c       2011-11-29 20:04:31.094156626 +0100
***************
*** 5326,5338 ****
int                     i_lanplcallfoid;
int                     i_laninline;
int                     i_lanvalidator;
int                     i_lanacl;
int                     i_lanowner;

/* Make sure we are in proper schema */
selectSourceSchema("pg_catalog");

!     if (g_fout->remoteVersion >= 90000)
{
/* pg_language has a laninline column */
appendPQExpBuffer(query, "SELECT tableoid, oid, "
--- 5326,5351 ----
int                     i_lanplcallfoid;
int                     i_laninline;
int                     i_lanvalidator;
+     int                     i_lanchecker;
int                     i_lanacl;
int                     i_lanowner;

/* Make sure we are in proper schema */
selectSourceSchema("pg_catalog");

!     if (g_fout->remoteVersion >= 90200)
!     {
!             /* pg_language has a lanchecker column */
!             appendPQExpBuffer(query, "SELECT tableoid, oid, "
!                                               "lanname, lanpltrusted, lanplcallfoid, "
!                                               "laninline, lanvalidator, lanchecker, lanacl, "
!                                               "(%s lanowner) AS lanowner "
!                                               "FROM pg_language "
!                                               "WHERE lanispl "
!                                               "ORDER BY oid",
!                                               username_subquery);
!     }
!     else if (g_fout->remoteVersion >= 90000)
{
/* pg_language has a laninline column */
appendPQExpBuffer(query, "SELECT tableoid, oid, "
***************
*** 5409,5414 ****
--- 5422,5428 ----
/* these may fail and return -1: */
i_laninline = PQfnumber(res, "laninline");
i_lanvalidator = PQfnumber(res, "lanvalidator");
+     i_lanchecker = PQfnumber(res, "lanchecker");
i_lanacl = PQfnumber(res, "lanacl");
i_lanowner = PQfnumber(res, "lanowner");
***************
*** 5422,5427 ****
--- 5436,5445 ----
planginfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_lanname));
planginfo[i].lanpltrusted = *(PQgetvalue(res, i, i_lanpltrusted)) == 't';
planginfo[i].lanplcallfoid = atooid(PQgetvalue(res, i, i_lanplcallfoid));
+             if (i_lanchecker >= 0)
+                     planginfo[i].lanchecker = atooid(PQgetvalue(res, i, i_lanchecker));
+             else
+                     planginfo[i].lanchecker = InvalidOid;
if (i_laninline >= 0)
planginfo[i].laninline = atooid(PQgetvalue(res, i, i_laninline));
else
***************
*** 8597,8602 ****
--- 8615,8621 ----
char       *qlanname;
char       *lanschema;
FuncInfo   *funcInfo;
+     FuncInfo   *checkerInfo = NULL;
FuncInfo   *inlineInfo = NULL;
FuncInfo   *validatorInfo = NULL;
***************
*** 8616,8621 ****
--- 8635,8647 ----
if (funcInfo != NULL && !funcInfo->dobj.dump)
funcInfo = NULL;                /* treat not-dumped same as not-found */
+     if (OidIsValid(plang->lanchecker))
+     {
+             checkerInfo = findFuncByOid(plang->lanchecker);
+             if (checkerInfo != NULL && !checkerInfo->dobj.dump)
+                     checkerInfo = NULL;
+     }
+
if (OidIsValid(plang->laninline))
{
inlineInfo = findFuncByOid(plang->laninline);
***************
*** 8642,8647 ****
--- 8668,8674 ----
* don't, this might not work terribly nicely.
*/
useParams = (funcInfo != NULL &&
+                              (checkerInfo != NULL || !OidIsValid(plang->lanchecker)) &&
(inlineInfo != NULL || !OidIsValid(plang->laninline)) &&
(validatorInfo != NULL || !OidIsValid(plang->lanvalidator)));
***************
*** 8697,8702 ****
--- 8724,8739 ----
appendPQExpBuffer(defqry, "%s",
fmtId(validatorInfo->dobj.name));
}
+             if (OidIsValid(plang->lanchecker))
+             {
+                     appendPQExpBuffer(defqry, " CHECK ");
+                     /* Cope with possibility that checker is in different schema */
+                     if (checkerInfo->dobj.namespace != funcInfo->dobj.namespace)
+                             appendPQExpBuffer(defqry, "%s.",
+                                                        fmtId(checkerInfo->dobj.namespace->dobj.name));
+                     appendPQExpBuffer(defqry, "%s",
+                                                       fmtId(checkerInfo->dobj.name));
+             }
}
else
{
*** ./src/bin/pg_dump/pg_dump.h.orig  2011-11-29 20:05:48.255044631 +0100
--- ./src/bin/pg_dump/pg_dump.h       2011-11-29 20:05:08.766614345 +0100
***************
*** 387,392 ****
--- 387,393 ----
Oid                     lanplcallfoid;
Oid                     laninline;
Oid                     lanvalidator;
+     Oid                     lanchecker;
char       *lanacl;
char       *lanowner;           /* name of owner, or empty string */
} ProcLangInfo;
*** ./src/bin/psql/tab-complete.c.orig        2011-11-29 19:20:59.482116921 +0100
--- ./src/bin/psql/tab-complete.c     2011-11-29 19:21:24.516804592 +0100
***************
*** 1,4 ****
--- 1,5 ----
/*
+  *
* psql - the PostgreSQL interactive terminal
*
* Copyright (c) 2000-2011, PostgreSQL Global Development Group
***************
*** 727,733 ****
#define prev6_wd  (previous_words[5])
static const char *const sql_commands[] = {
!             "ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
--- 728,734 ----
#define prev6_wd  (previous_words[5])
static const char *const sql_commands[] = {
!             "ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECK", "CHECKPOINT", "CLOSE", "CLUSTER",
"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
***************
*** 1524,1529 ****
--- 1525,1552 ----
COMPLETE_WITH_LIST(list_TRANS);
}
+
+ /* CHECK */
+     else if (pg_strcasecmp(prev_wd, "CHECK") == 0)
+     {
+             static const char *const list_CHECK[] =
+             {"FUNCTION", "TRIGGER", NULL};
+
+             COMPLETE_WITH_LIST(list_CHECK);
+     }
+     else if (pg_strcasecmp(prev3_wd, "CHECK") == 0 &&
+                      pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+     {
+             COMPLETE_WITH_CONST("ON");
+     }
+     else if (pg_strcasecmp(prev4_wd, "CHECK") == 0 &&
+                      pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
+                      pg_strcasecmp(prev_wd, "ON") == 0)
+     {
+             completion_info_charp = prev2_wd;
+             COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+     }
+
/* CLUSTER */
/*
*** ./src/include/catalog/pg_language.h.orig  2011-11-29 19:20:59.483116909 +0100
--- ./src/include/catalog/pg_language.h       2011-11-29 19:21:24.518804568 +0100
***************
*** 37,42 ****
--- 37,43 ----
Oid                     lanplcallfoid;  /* Call handler for PL */
Oid                     laninline;              /* Optional anonymous-block handler function */
Oid                     lanvalidator;   /* Optional validation function */
+     Oid                     lanchecker;     /* Optional checker function */
aclitem         lanacl[1];              /* Access privileges */
} FormData_pg_language;
***************
*** 51,57 ****
*          compiler constants for pg_language
* ----------------
*/
! #define Natts_pg_language                           8
#define Anum_pg_language_lanname            1
#define Anum_pg_language_lanowner           2
#define Anum_pg_language_lanispl            3
--- 52,58 ----
*          compiler constants for pg_language
* ----------------
*/
! #define Natts_pg_language                           9
#define Anum_pg_language_lanname            1
#define Anum_pg_language_lanowner           2
#define Anum_pg_language_lanispl            3
***************
*** 59,78 ****
#define Anum_pg_language_lanplcallfoid      5
#define Anum_pg_language_laninline          6
#define Anum_pg_language_lanvalidator       7
! #define Anum_pg_language_lanacl                     8

/* ----------------
* initial contents of pg_language
* ----------------
*/

! DATA(insert OID = 12 ( "internal" PGUID f f 0 0 2246 _null_ ));
DESCR("built-in functions");
#define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c" PGUID f f 0 0 2247 _null_ ));
DESCR("dynamically-loaded C functions");
#define ClanguageId 13
! DATA(insert OID = 14 ( "sql" PGUID f t 0 0 2248 _null_ ));
DESCR("SQL-language functions");
#define SQLlanguageId 14

--- 60,80 ----
#define Anum_pg_language_lanplcallfoid      5
#define Anum_pg_language_laninline          6
#define Anum_pg_language_lanvalidator       7
! #define Anum_pg_language_lanchecker 8
! #define Anum_pg_language_lanacl                     9

/* ----------------
* initial contents of pg_language
* ----------------
*/

! DATA(insert OID = 12 ( "internal" PGUID f f 0 0 2246 0 _null_ ));
DESCR("built-in functions");
#define INTERNALlanguageId 12
! DATA(insert OID = 13 ( "c" PGUID f f 0 0 2247 0 _null_ ));
DESCR("dynamically-loaded C functions");
#define ClanguageId 13
! DATA(insert OID = 14 ( "sql" PGUID f t 0 0 2248 0 _null_ ));
DESCR("SQL-language functions");
#define SQLlanguageId 14

*** ./src/include/catalog/pg_pltemplate.h.orig        2011-11-29 19:20:59.484116897 +0100
--- ./src/include/catalog/pg_pltemplate.h     2011-11-29 19:21:24.518804568 +0100
***************
*** 36,41 ****
--- 36,42 ----
text            tmplhandler;    /* name of call handler function */
text            tmplinline;             /* name of anonymous-block handler, or NULL */
text            tmplvalidator;  /* name of validator function, or NULL */
+     text            tmplchecker;    /* name of checker function, or NULL */
text            tmpllibrary;    /* path of shared library */
aclitem         tmplacl[1];             /* access privileges for template */
} FormData_pg_pltemplate;
***************
*** 51,65 ****
*          compiler constants for pg_pltemplate
* ----------------
*/
! #define Natts_pg_pltemplate                                 8
#define Anum_pg_pltemplate_tmplname                 1
#define Anum_pg_pltemplate_tmpltrusted              2
#define Anum_pg_pltemplate_tmpldbacreate    3
#define Anum_pg_pltemplate_tmplhandler              4
#define Anum_pg_pltemplate_tmplinline               5
#define Anum_pg_pltemplate_tmplvalidator    6
! #define Anum_pg_pltemplate_tmpllibrary              7
! #define Anum_pg_pltemplate_tmplacl                  8
/* ----------------
--- 52,67 ----
*          compiler constants for pg_pltemplate
* ----------------
*/
! #define Natts_pg_pltemplate                                 9
#define Anum_pg_pltemplate_tmplname                 1
#define Anum_pg_pltemplate_tmpltrusted              2
#define Anum_pg_pltemplate_tmpldbacreate    3
#define Anum_pg_pltemplate_tmplhandler              4
#define Anum_pg_pltemplate_tmplinline               5
#define Anum_pg_pltemplate_tmplvalidator    6
! #define Anum_pg_pltemplate_tmplchecker              7
! #define Anum_pg_pltemplate_tmpllibrary              8
! #define Anum_pg_pltemplate_tmplacl                  9

/* ----------------
***************
*** 67,79 ****
* ----------------
*/

! DATA(insert ( "plpgsql" t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl" t t "pltcl_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu" f f "pltclu_call_handler" _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl" t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu" f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu" f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u" f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u" f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" "$libdir/plpython3" _null_ ));

#endif   /* PG_PLTEMPLATE_H */
--- 69,81 ----
* ----------------
*/

! DATA(insert ( "plpgsql" t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "plpgsql_checker" "$libdir/plpgsql" _null_ ));
! DATA(insert ( "pltcl" t t "pltcl_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "pltclu" f f "pltclu_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ ));
! DATA(insert ( "plperl" t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plperlu" f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" _null_ "$libdir/plperl" _null_ ));
! DATA(insert ( "plpythonu" f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython2u" f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" _null_ "$libdir/plpython2" _null_ ));
! DATA(insert ( "plpython3u" f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" _null_ "$libdir/plpython3" _null_ ));

#endif   /* PG_PLTEMPLATE_H */
*** ./src/include/commands/defrem.h.orig      2011-11-29 19:20:59.486116871 +0100
--- ./src/include/commands/defrem.h   2011-11-29 19:21:24.519804556 +0100
***************
*** 62,67 ****
--- 62,68 ----
/* commands/functioncmds.c */
extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
extern void RemoveFunctionById(Oid funcOid);
+ extern void CheckFunction(CheckFunctionStmt *stmt);
extern void SetFunctionReturnType(Oid funcOid, Oid newRetType);
extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType);
extern void RenameFunction(List *name, List *argtypes, const char *newname);
*** ./src/include/nodes/nodes.h.orig  2011-11-29 19:20:59.487116858 +0100
--- ./src/include/nodes/nodes.h       2011-11-29 19:21:24.521804532 +0100
***************
*** 291,296 ****
--- 291,297 ----
T_IndexStmt,
T_CreateFunctionStmt,
T_AlterFunctionStmt,
+     T_CheckFunctionStmt,
T_DoStmt,
T_RenameStmt,
T_RuleStmt,
*** ./src/include/nodes/parsenodes.h.orig     2011-11-29 19:20:59.489116833 +0100
--- ./src/include/nodes/parsenodes.h  2011-11-29 19:21:24.523804506 +0100
***************
*** 1734,1739 ****
--- 1734,1740 ----
List       *plhandler;          /* PL call handler function (qual. name) */
List       *plinline;           /* optional inline function (qual. name) */
List       *plvalidator;        /* optional validator function (qual. name) */
+     List       *plchecker;          /* optional checker function (qual. name) */
bool            pltrusted;              /* PL is trusted */
} CreatePLangStmt;
***************
*** 2077,2082 ****
--- 2078,2096 ----
} AlterFunctionStmt;
/* ----------------------
+  *          Check {Function|Trigger} Statement
+  * ----------------------
+  */
+ typedef struct CheckFunctionStmt
+ {
+     NodeTag         type;
+     List       *funcname;                   /* qualified name of checked object */
+     List       *args;                       /* types of the arguments */
+     char       *trgname;                    /* trigger's name */
+     RangeVar        *relation;              /* trigger's relation */
+ } CheckFunctionStmt;
+
+ /* ----------------------
*          DO Statement
*
* DoStmt is the raw parser output, InlineCodeBlock is the execution-time API
*** ./src/pl/plpgsql/src/pl_comp.c.orig       2011-11-29 19:09:03.000000000 +0100
--- ./src/pl/plpgsql/src/pl_comp.c    2011-11-29 19:42:43.058753779 +0100
***************
*** 115,121 ****
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.
--- 115,120 ----
***************
*** 175,181 ****
* 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
--- 174,180 ----
* 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
***************
*** 2426,2432 ****
}

/*
!  * 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
--- 2425,2431 ----
}
/*
!  * 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,2446 ****
* 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);
--- 2438,2445 ----
* 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);
*** ./src/pl/plpgsql/src/pl_exec.c.orig       2011-11-29 19:09:03.316459122 +0100
--- ./src/pl/plpgsql/src/pl_exec.c    2011-11-29 19:37:19.000000000 +0100
***************
*** 210,216 ****
static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
PLpgSQL_expr *dynquery, List *params,
const char *portalname, int cursorOptions);
!
/* ----------
* plpgsql_exec_function    Called by the call handler for
--- 210,228 ----
static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
PLpgSQL_expr *dynquery, List *params,
const char *portalname, int cursorOptions);
! static void check_row_or_rec(PLpgSQL_execstate *estate, PLpgSQL_row *row, PLpgSQL_rec *rec);
! static void check_expr(PLpgSQL_execstate *estate, PLpgSQL_expr *expr);
! static void assign_tupdesc_row_or_rec(PLpgSQL_execstate *estate,
!                                         PLpgSQL_row *row, PLpgSQL_rec *rec,
!                                                                 TupleDesc tupdesc);
! static TupleDesc expr_get_desc(PLpgSQL_execstate *estate,
!                                             PLpgSQL_expr *query,
!                                                     bool use_element_type,
!                                                     bool expand_record,
!                                                             bool is_expression);
! static void var_init_to_null(PLpgSQL_execstate *estate, int varno);
! static void check_stmts(PLpgSQL_execstate *estate, List *stmts);
! static void check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt);
/* ----------
* plpgsql_exec_function    Called by the call handler for
***************
*** 6176,6178 ****
--- 6188,7242 ----
return portal;
}
+
+ /*
+  * Following code ensures a CHECK FUNCTION and CHECK TRIGGER statements for PL/pgSQL
+  *
+  */
+
+ /*
+  * append a CONTEXT to error message
+  */
+ static void
+ check_error_callback(void *arg)
+ {
+     PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;
+
+     if (estate->err_stmt != NULL)
+     {
+             /* translator: last %s is a plpgsql statement type name */
+             errcontext("checking of PL/pgSQL function \"%s\" line %d at %s",
+                                estate->func->fn_name,
+                                estate->err_stmt->lineno,
+                                plpgsql_stmt_typename(estate->err_stmt));
+     }
+     else
+             errcontext("checking of PL/pgSQL function \"%s\"",
+                                estate->func->fn_name);
+ }
+
+ /*
+  * Check function - it prepare variables and starts a prepare plan walker
+  *          called by function checker
+  */
+ void
+ plpgsql_check_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
+ {
+     PLpgSQL_execstate estate;
+     ErrorContextCallback plerrcontext;
+     int                     i;
+
+     /* Setup error callback for ereport */
+     plerrcontext.callback = check_error_callback;
+     plerrcontext.arg = &estate;
+     plerrcontext.previous = error_context_stack;
+     error_context_stack = &plerrcontext;
+
+     /*
+      * Setup the execution state - we would to reuse some exec routines
+      * so we need a estate
+      */
+     plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo);
+
+     /*
+      * Make local execution copies of all the datums
+      */
+     for (i = 0; i < estate.ndatums; i++)
+             estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
+
+     /*
+      * Store the actual call argument values into the appropriate variables
+      */
+     for (i = 0; i < func->fn_nargs; i++)
+     {
+             int                     n = func->fn_argvarnos[i];
+
+             switch (estate.datums[n]->dtype)
+             {
+                     case PLPGSQL_DTYPE_VAR:
+                             {
+                                     var_init_to_null(&estate, n);
+                             }
+                             break;
+
+                     case PLPGSQL_DTYPE_ROW:
+                             {
+                                     PLpgSQL_row *row = (PLpgSQL_row *) estate.datums[n];
+
+                                     exec_move_row(&estate, NULL, row, NULL, NULL);
+                             }
+                             break;
+
+                     default:
+                             elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
+             }
+     }
+
+     /*
+      * Now check the toplevel block of statements
+      */
+     check_stmt(&estate, (PLpgSQL_stmt *) func->action);
+
+     /* Cleanup temporary memory */
+     plpgsql_destroy_econtext(&estate);
+
+     /* Pop the error context stack */
+     error_context_stack = plerrcontext.previous;
+ }
+
+ /*
+  * Check trigger - prepare fake environments for testing trigger
+  *
+  */
+ void
+ plpgsql_check_trigger(PLpgSQL_function *func,
+                                      TriggerData *trigdata)
+ {
+     PLpgSQL_execstate estate;
+     ErrorContextCallback plerrcontext;
+     PLpgSQL_rec *rec_new,
+                        *rec_old;
+     int                     i;
+
+     /* Setup error callback for ereport */
+     plerrcontext.callback = check_error_callback;
+     plerrcontext.arg = &estate;
+     plerrcontext.previous = error_context_stack;
+     error_context_stack = &plerrcontext;
+
+     /*
+      * Setup the execution state - we would to reuse some exec routines
+      * so we need a estate
+      */
+     plpgsql_estate_setup(&estate, func, NULL);
+
+     /*
+      * Make local execution copies of all the datums
+      */
+     for (i = 0; i < estate.ndatums; i++)
+             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 *) (estate.datums[func->new_varno]);
+     rec_new->freetup = false;
+     rec_new->freetupdesc = false;
+     assign_tupdesc_row_or_rec(&estate, NULL, rec_new, trigdata->tg_relation->rd_att);
+
+     rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
+     rec_old->freetup = false;
+     rec_old->freetupdesc = false;
+     assign_tupdesc_row_or_rec(&estate, NULL, rec_old, trigdata->tg_relation->rd_att);
+
+     /*
+      * Assign the special tg_ variables
+      */
+     var_init_to_null(&estate, func->tg_op_varno);
+     var_init_to_null(&estate, func->tg_name_varno);
+     var_init_to_null(&estate, func->tg_when_varno);
+     var_init_to_null(&estate, func->tg_level_varno);
+     var_init_to_null(&estate, func->tg_relid_varno);
+     var_init_to_null(&estate, func->tg_relname_varno);
+     var_init_to_null(&estate, func->tg_table_name_varno);
+     var_init_to_null(&estate, func->tg_table_schema_varno);
+     var_init_to_null(&estate, func->tg_nargs_varno);
+     var_init_to_null(&estate, func->tg_argv_varno);
+
+     /*
+      * Now check the toplevel block of statements
+      */
+     check_stmt(&estate, (PLpgSQL_stmt *) func->action);
+
+     /* Cleanup temporary memory */
+     plpgsql_destroy_econtext(&estate);
+
+     /* Pop the error context stack */
+     error_context_stack = plerrcontext.previous;
+ }
+
+ /*
+  * Verify lvalue
+  *    It doesn't repeat a checks that are done.
+  *  Checks a subscript expressions, verify a validity of record's fields
+  */
+ static void
+ check_target(PLpgSQL_execstate *estate, int varno)
+ {
+     PLpgSQL_datum *target = estate->datums[varno];
+
+     switch (target->dtype)
+     {
+             case PLPGSQL_DTYPE_VAR:
+             case PLPGSQL_DTYPE_REC:
+                     break;
+
+             case PLPGSQL_DTYPE_ROW:
+                     check_row_or_rec(estate, (PLpgSQL_row *) target, NULL);
+                     break;
+
+             case PLPGSQL_DTYPE_RECFIELD:
+                     {
+                             PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target;
+                             PLpgSQL_rec *rec;
+                             int                     fno;
+
+                             rec = (PLpgSQL_rec *) (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)));
+
+                                     check_expr(estate, arrayelem->subscript);
+
+                                     target = 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(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_execstate *estate, 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(estate, row->varnos[fnum]);
+             }
+     }
+ }
+
+ /*
+  * Generate a prepared plan - this is simplyfied copy from pl_exec.c
+  *   Is not necessary to check simple plan
+  */
+ static void
+ prepare_expr(PLpgSQL_execstate *estate,
+                               PLpgSQL_expr *expr, int cursorOptions)
+ {
+     SPIPlanPtr      plan;
+
+     /* leave when there are not expression */
+     if (expr == NULL)
+             return;
+
+     /* leave when plan is created */
+     if (expr->plan != NULL)
+             return;
+
+     /*
+      * 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 = 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")));
+                     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.")));
+                     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_execstate *estate, PLpgSQL_expr *expr)
+ {
+     TupleDesc tupdesc;
+
+     if (expr != NULL)
+     {
+             prepare_expr(estate, expr, 0);
+             tupdesc = expr_get_desc(estate, expr, false, false, true);
+             ReleaseTupleDesc(tupdesc);
+     }
+ }
+
+ /*
+  * 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_execstate *estate,
+                                         PLpgSQL_row *row, PLpgSQL_rec *rec,
+                                                                 TupleDesc tupdesc)
+ {
+     bool       *nulls;
+     HeapTuple  tup;
+
+     if (tupdesc == NULL)
+             elog(ERROR, "tuple descriptor is empty");
+
+     /*
+      * row variable has assigned TupleDesc already, so don't be processed
+      * here
+      */
+     if (rec != NULL)
+     {
+             PLpgSQL_rec *target = (PLpgSQL_rec *)(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_execstate *estate, int varno, TupleDesc tupdesc)
+ {
+     PLpgSQL_datum *target = estate->datums[varno];
+
+     if (target->dtype == PLPGSQL_DTYPE_REC)
+             assign_tupdesc_row_or_rec(estate, NULL, (PLpgSQL_rec *) target, tupdesc);
+ }
+
+ /*
+  * Returns a tuple descriptor based on existing plan
+  */
+ static TupleDesc
+ expr_get_desc(PLpgSQL_execstate *estate,
+                                             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);
+
+             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 (tupdesc->natts != 1)
+                     ereport(ERROR,
+                             (errcode(ERRCODE_SYNTAX_ERROR),
+                              errmsg_plural("query \"%s\" returned %d column",
+                                                        "query \"%s\" returned %d columns",
+                                                        tupdesc->natts,
+                                                        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))));
+
+             /* 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
+                     elog(ERROR, "cannot to identify real type for record type variable");
+     }
+
+     if (is_expression && tupdesc->natts != 1)
+             ereport(ERROR,
+                             (errcode(ERRCODE_SYNTAX_ERROR),
+                       errmsg_plural("query \"%s\" returned %d column",
+                                "query \"%s\" returned %d columns",
+                                tupdesc->natts,
+                                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(..)
+      */
+     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)
+                                             elog(ERROR, "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
+  */
+ static void
+ check_stmts(PLpgSQL_execstate *estate, List *stmts)
+ {
+     ListCell *lc;
+
+     foreach(lc, stmts)
+     {
+             check_stmt(estate, (PLpgSQL_stmt *) lfirst(lc));
+     }
+ }
+
+ /*
+  * walk over all statements
+  */
+ static void
+ check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
+ {
+     TupleDesc       tupdesc = NULL;
+     PLpgSQL_function *func;
+     ListCell *l;
+
+     if (stmt == NULL)
+             return;
+
+     estate->err_stmt = stmt;
+     func = estate->func;
+
+     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++)
+                             {
+                                     d = func->datums[stmt_block->initvarnos[i]];
+
+                                     if (d->dtype == PLPGSQL_DTYPE_VAR)
+                                     {
+                                             PLpgSQL_var *var = (PLpgSQL_var *) d;
+
+                                             check_expr(estate, var->default_val);
+                                     }
+                             }
+
+                             check_stmts(estate, stmt_block->body);
+
+                             if (stmt_block->exceptions)
+                             {
+                                     foreach(l, stmt_block->exceptions->exc_list)
+                                     {
+                                             check_stmts(estate, ((PLpgSQL_exception *) lfirst(l))->action);
+                                     }
+                             }
+                     }
+                     break;
+
+             case PLPGSQL_STMT_ASSIGN:
+                     {
+                             PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt;
+
+                             /* prepare plan if desn't exist yet */
+                             prepare_expr(estate, stmt_assign->expr, 0);
+
+                             tupdesc = expr_get_desc(estate,
+                                                             stmt_assign->expr,
+                                                                             false,          /* no element type */
+                                                                             true,           /* expand record */
+                                                                             true);          /* is expression */
+
+                             /* check target, ensure target can get a result */
+                             check_target(estate, stmt_assign->varno);
+
+                             /* assign a tupdesc to record variable */
+                             assign_tupdesc_dno(estate, stmt_assign->varno, tupdesc);
+                             ReleaseTupleDesc(tupdesc);
+                     }
+                     break;
+
+             case PLPGSQL_STMT_IF:
+                     {
+                             PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt;
+                             ListCell *l;
+
+                             check_expr(estate, stmt_if->cond);
+
+                             check_stmts(estate, stmt_if->then_body);
+
+                             foreach(l, stmt_if->elsif_list)
+                             {
+                                     PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l);
+
+                                     check_expr(estate, elif->cond);
+                                     check_stmts(estate, elif->stmts);
+                             }
+
+                             check_stmts(estate, 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 *) estate->datums[stmt_case->t_varno];
+
+                                     /* we need to set hidden variable type */
+                                     prepare_expr(estate, stmt_case->t_expr, 0);
+
+                                     tupdesc = expr_get_desc(estate,
+                                                                     stmt_case->t_expr,
+                                                                             false,          /* no element type */
+                                                                             false,          /* 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,
+                                                                                                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(estate, cwt->expr);
+                                     check_stmts(estate, cwt->stmts);
+                             }
+
+                             check_stmts(estate, stmt_case->else_stmts);
+                     }
+                     break;
+
+             case PLPGSQL_STMT_LOOP:
+                     check_stmts(estate, ((PLpgSQL_stmt_loop *) stmt)->body);
+                     break;
+
+             case PLPGSQL_STMT_WHILE:
+                     {
+                             PLpgSQL_stmt_while *stmt_while = (PLpgSQL_stmt_while *) stmt;
+
+                             check_expr(estate, stmt_while->cond);
+                             check_stmts(estate, stmt_while->body);
+                     }
+                     break;
+
+             case PLPGSQL_STMT_FORI:
+                     {
+                             PLpgSQL_stmt_fori *stmt_fori = (PLpgSQL_stmt_fori *) stmt;
+
+                             check_expr(estate, stmt_fori->lower);
+                             check_expr(estate, stmt_fori->upper);
+                             check_expr(estate, stmt_fori->step);
+
+                             check_stmts(estate, stmt_fori->body);
+                     }
+                     break;
+
+             case PLPGSQL_STMT_FORS:
+                     {
+                             PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt;
+
+                             /* we need to set hidden variable type */
+                             prepare_expr(estate, stmt_fors->query, 0);
+
+                             tupdesc = expr_get_desc(estate,
+                                                             stmt_fors->query,
+                                                                     false,          /* no element type */
+                                                                     false,          /* expand record */
+                                                                     false);         /* is expression */
+
+                             check_row_or_rec(estate, stmt_fors->row, stmt_fors->rec);
+                             assign_tupdesc_row_or_rec(estate, stmt_fors->row, stmt_fors->rec, tupdesc);
+
+                             check_stmts(estate, stmt_fors->body);
+                             ReleaseTupleDesc(tupdesc);
+                     }
+                     break;
+
+             case PLPGSQL_STMT_FORC:
+                     {
+                             PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt;
+                             PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_forc->curvar];
+
+                             prepare_expr(estate, stmt_forc->argquery, 0);
+
+                             if (var->cursor_explicit_expr != NULL)
+                             {
+                                     prepare_expr(estate, var->cursor_explicit_expr,
+                                                                 var->cursor_options);
+
+                                     tupdesc = expr_get_desc(estate,
+                                                                     var->cursor_explicit_expr,
+                                                                             false,          /* no element type */
+                                                                             false,          /* expand record */
+                                                                             false);         /* is expression */
+
+                                     check_row_or_rec(estate, stmt_forc->row, stmt_forc->rec);
+                                     assign_tupdesc_row_or_rec(estate, stmt_forc->row, stmt_forc->rec, tupdesc);
+                             }
+
+                             check_stmts(estate, stmt_forc->body);
+                             if (tupdesc != NULL)
+                                     ReleaseTupleDesc(tupdesc);
+                     }
+                     break;
+
+             case PLPGSQL_STMT_DYNFORS:
+                     {
+                             PLpgSQL_stmt_dynfors * stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt;
+
+                             if (stmt_dynfors->rec != NULL)
+                                     elog(ERROR, "cannot determinate a result of dynamic SQL");
+
+                             check_expr(estate, stmt_dynfors->query);
+
+                             foreach(l, stmt_dynfors->params)
+                             {
+                                     check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+                             }
+
+                             check_stmts(estate, stmt_dynfors->body);
+                     }
+                     break;
+
+             case PLPGSQL_STMT_FOREACH_A:
+                     {
+                             PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt;
+
+                             prepare_expr(estate, stmt_foreach_a->expr, 0);
+
+                             tupdesc = expr_get_desc(estate,
+                                                             stmt_foreach_a->expr,
+                                                                     true,           /* no element type */
+                                                                     false,          /* expand record */
+                                                                     true);          /* is expression */
+
+                             check_target(estate, stmt_foreach_a->varno);
+                             assign_tupdesc_dno(estate, stmt_foreach_a->varno, tupdesc);
+                             ReleaseTupleDesc(tupdesc);
+
+                             check_stmts(estate, stmt_foreach_a->body);
+                     }
+                     break;
+
+             case PLPGSQL_STMT_EXIT:
+                     check_expr(estate, ((PLpgSQL_stmt_exit *) stmt)->cond);
+                     break;
+
+             case PLPGSQL_STMT_PERFORM:
+                     prepare_expr(estate, ((PLpgSQL_stmt_perform *) stmt)->expr, 0);
+                     break;
+
+             case PLPGSQL_STMT_RETURN:
+                     check_expr(estate, ((PLpgSQL_stmt_return *) stmt)->expr);
+                     break;
+
+             case PLPGSQL_STMT_RETURN_NEXT:
+                     check_expr(estate, ((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(estate, stmt_rq->dynquery);
+                             prepare_expr(estate, stmt_rq->query, 0);
+
+                             foreach(l, stmt_rq->params)
+                             {
+                                     check_expr(estate, (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(estate, (PLpgSQL_expr *) lfirst(l));
+                             }
+
+                             foreach(l, stmt_raise->options)
+                             {
+                                     check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+                             }
+
+                             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;
+
+                             prepare_expr(estate, stmt_execsql->sqlstmt, 0);
+                             if (stmt_execsql->into)
+                             {
+                                     tupdesc = expr_get_desc(estate,
+                                                             stmt_execsql->sqlstmt,
+                                                                                     false,          /* no element type */
+                                                                                     false,          /* expand record */
+                                                                                     false);         /* is expression */
+
+                                     /* check target, ensure target can get a result */
+                                     check_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec);
+                                     assign_tupdesc_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec, tupdesc);
+                                     ReleaseTupleDesc(tupdesc);
+                             }
+                     }
+                     break;
+
+             case PLPGSQL_STMT_DYNEXECUTE:
+                     {
+                             PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt;
+
+                             check_expr(estate, stmt_dynexecute->query);
+
+                             foreach(l, stmt_dynexecute->params)
+                             {
+                                     check_expr(estate, (PLpgSQL_expr *) lfirst(l));
+                             }
+
+                             if (stmt_dynexecute->into)
+                             {
+                                     if (stmt_dynexecute->rec != NULL)
+                                             elog(ERROR, "cannot determinate a result of dynamic SQL");
+
+                                     check_row_or_rec(estate, stmt_dynexecute->row, stmt_dynexecute->rec);
+                             }
+                     }
+                     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)
+                                     prepare_expr(estate, var->cursor_explicit_expr,
+                                                                var->cursor_options);
+
+                             prepare_expr(estate, stmt_open->query, 0);
+                             prepare_expr(estate, stmt_open->argquery, 0);
+                             check_expr(estate, stmt_open->dynquery);
+
+                             foreach(l, stmt_open->params)
+                             {
+                                     check_expr(estate, (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(estate, diag_item->target);
+                             }
+                     }
+                     break;
+
+             case PLPGSQL_STMT_FETCH:
+                     {
+                             PLpgSQL_stmt_fetch *stmt_fetch = (PLpgSQL_stmt_fetch *) stmt;
+                             PLpgSQL_var *var = (PLpgSQL_var *)(estate->datums[stmt_fetch->curvar]);
+
+                             if (var != NULL && var->cursor_explicit_expr != NULL)
+                             {
+                                     prepare_expr(estate, var->cursor_explicit_expr,
+                                                                         var->cursor_options);
+                                     tupdesc = expr_get_desc(estate,
+                                                                 var->cursor_explicit_expr,
+                                                                                     false,          /* no element type */
+                                                                                     false,          /* expand record */
+                                                                                     false);         /* is expression */
+                                     check_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec);
+                                     assign_tupdesc_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec, tupdesc);
+                                     ReleaseTupleDesc(tupdesc);
+                             }
+                     }
+                     break;
+
+             case PLPGSQL_STMT_CLOSE:
+                     break;
+
+             default:
+                     elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
+                     return; /* be compiler quite */
+     }
+ }
+
+ /*
+  * Initialize variable to NULL
+  */
+ static void
+ var_init_to_null(PLpgSQL_execstate *estate, int varno)
+ {
+     PLpgSQL_var *var = (PLpgSQL_var *) estate->datums[varno];
+     var->value = (Datum) 0;
+     var->isnull = true;
+     var->freeval = false;
+ }
*** ./src/pl/plpgsql/src/pl_handler.c.orig    2011-11-29 19:20:59.494116771 +0100
--- ./src/pl/plpgsql/src/pl_handler.c 2011-11-29 19:21:24.529804431 +0100
***************
*** 312,314 ****
--- 312,452 ----
PG_RETURN_VOID();
}
+
+ /* ----------
+  * plpgsql_checker
+  *
+  * This function attempts to check a embeded SQL inside a PL/pgSQL function at
+  * CHECK FUNCTION time. It should to have one or two parameters. Second
+  * parameter is a relation (used when function is trigger).
+  * ----------
+  */
+ PG_FUNCTION_INFO_V1(plpgsql_checker);
+
+ Datum
+ plpgsql_checker(PG_FUNCTION_ARGS)
+ {
+     Oid                     funcoid = PG_GETARG_OID(0);
+     Oid                     relid = PG_GETARG_OID(1);
+     HeapTuple       tuple;
+     FunctionCallInfoData fake_fcinfo;
+     FmgrInfo        flinfo;
+     TriggerData trigdata;
+     int                     rc;
+     PLpgSQL_function *function;
+     PLpgSQL_execstate *cur_estate;
+
+     Form_pg_proc proc;
+     char            functyptype;
+     bool       istrigger = false;
+
+     /* we don't need to repair a check done by validator */
+
+     tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
+     if (!HeapTupleIsValid(tuple))
+             elog(ERROR, "cache lookup failed for function %u", funcoid);
+     proc = (Form_pg_proc) GETSTRUCT(tuple);
+
+     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))
+             {
+                     istrigger = true;
+                     if (!OidIsValid(relid))
+                             ereport(ERROR,
+                                             (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                              errmsg("PL/pgSQL trigger functions cannot be checked directly"),
+                                              errhint("use CHECK TRIGGER statement instead")));
+             }
+     }
+
+     /*
+      * Connect to SPI manager
+      */
+     if ((rc = SPI_connect()) != SPI_OK_CONNECT)
+                     elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
+
+     /*
+      * Set up a fake fcinfo with just enough info to satisfy
+      * plpgsql_compile().
+      *
+      * there should be a different real argtypes for polymorphic params
+      */
+     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;
+             trigdata.tg_relation = relation_open(relid, AccessShareLock);
+             fake_fcinfo.context = (Node *) &trigdata;
+     }
+
+     /* Get a compiled function */
+     function = plpgsql_compile(&fake_fcinfo, false);
+
+     /* 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 prepare plans */
+     PG_TRY();
+     {
+             if (!istrigger)
+                     plpgsql_check_function(function, &fake_fcinfo);
+             else
+                     plpgsql_check_trigger(function, &trigdata);
+     }
+     PG_CATCH();
+     {
+             if (istrigger)
+                     relation_close(trigdata.tg_relation, AccessShareLock);
+
+             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);
+
+             PG_RE_THROW();
+     }
+     PG_END_TRY();
+
+     if (istrigger)
+             relation_close(trigdata.tg_relation, AccessShareLock);
+
+     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);
+
+     /*
+      * Disconnect from SPI manager
+      */
+     if ((rc = SPI_finish()) != SPI_OK_FINISH)
+                     elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
+
+     ReleaseSysCache(tuple);
+
+     PG_RETURN_VOID();
+ }
*** ./src/pl/plpgsql/src/plpgsql.h.orig       2011-11-29 19:20:59.500116698 +0100
--- ./src/pl/plpgsql/src/plpgsql.h    2011-11-29 20:22:19.423516596 +0100
***************
*** 902,907 ****
--- 902,908 ----
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,916 ****
--- 912,918 ----
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_checker(PG_FUNCTION_ARGS);
/* ----------
* Functions in pl_exec.c
***************
*** 928,933 ****
--- 930,939 ----
extern void exec_get_datum_type_info(PLpgSQL_execstate *estate,
PLpgSQL_datum *datum,
Oid *typeid, int32 *typmod, Oid *collation);
+ extern void plpgsql_check_function(PLpgSQL_function *func,
+                                      FunctionCallInfo fcinfo);
+ extern void plpgsql_check_trigger(PLpgSQL_function *func,
+                                      TriggerData *trigdata);
/* ----------
* Functions for namespace handling in pl_funcs.c
*** ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql.orig    2011-11-29 19:20:59.502116672 +0100
--- ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql 2011-11-29 19:21:24.533804381 +0100
***************
*** 5,7 ****
--- 5,8 ----
ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_call_handler();
ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_inline_handler(internal);
ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_validator(oid);
+ ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_checker(oid, regclass);
*** ./src/test/regress/expected/plpgsql.out.orig      2011-11-29 19:20:59.505116634 +0100
--- ./src/test/regress/expected/plpgsql.out   2011-11-29 19:21:24.536804342 +0100
***************
*** 302,307 ****
--- 302,310 ----
' 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
+ check trigger tg_hslot_biu on HSlot;
+ NOTICE:  checking function "tg_hslot_biu()"
-- ************************************************************
-- * BEFORE DELETE on HSlot
-- *        - prevent from manual manipulation
***************
*** 635,640 ****
--- 638,645 ----
raise exception ''illegal backlink beginning with %'', mytype;
end;
' language plpgsql;
+ -- check function should not fail
+ check function tg_backlink_set(bpchar, bpchar);
-- ************************************************************
-- * Support function to clear out the backlink field if
-- * it still points to specific slot
***************
*** 2802,2807 ****
--- 2807,2840 ----

(1 row)

+ -- check function should not fail
+ check function for_vect();
+ -- 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
***************
*** 3283,3288 ****
--- 3316,3323 ----
return;
end;
$$ language plpgsql;
+ -- check function should not fail
+ check function forc01();
select forc01();
NOTICE:  5 from c
NOTICE:  6 from c
***************
*** 3716,3721 ****
--- 3751,3758 ----
end case;
end;
$$ language plpgsql immutable;
+ -- check function should not fail
+ check function case_test(bigint);
select case_test(1);
case_test
-----------
***************
*** 4571,4573 ****
--- 4608,4942 ----
CONTEXT:  PL/pgSQL function "testoa" line 5 at assignment
drop function arrayassign1();
drop function testoa(x1 int, x2 int, x3 int);
+ --
+ -- check function statement tests
+ --
+ create table t1(a int, b int);
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+ end;
+ $$ language plpgsql;
+ select f1();
+  f1
+ ----
+
+ (1 row)
+
+ check function f1();
+ ERROR:  column "c" of relation "t1" does not exist
+ LINE 1: update t1 set c = 30
+                       ^
+ QUERY:  update t1 set c = 30
+ CONTEXT:  checking of PL/pgSQL function "f1" line 4 at SQL statement
+ 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)
+
+ check function f1();
+ ERROR:  record "r" has no field "c"
+ CONTEXT:  SQL statement "SELECT r.c"
+ checking of PL/pgSQL function "f1" line 6 at RAISE
+ 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)
+
+ check function f1();
+ ERROR:  record "r" has no field "c"
+ CONTEXT:  SQL statement "SELECT r.c"
+ checking of PL/pgSQL function "f1" line 6 at RAISE
+ 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)
+
+ check function f1();
+ ERROR:  record "r" has no field "c"
+ CONTEXT:  checking of PL/pgSQL function "f1" line 6 at assignment
+ 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)
+
+ check function f1();
+ ERROR:  column "a" does not exist
+ LINE 1: SELECT a + b
+                ^
+ QUERY:  SELECT a + b
+ CONTEXT:  checking of PL/pgSQL function "f1" line 5 at assignment
+ 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)
+
+ check function f1();
+ ERROR:  too many parameters specified for RAISE
+ CONTEXT:  checking of PL/pgSQL function "f1" line 4 at RAISE
+ 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)
+
+ check function f1();
+ ERROR:  too few parameters specified for RAISE
+ CONTEXT:  checking of PL/pgSQL function "f1" line 4 at RAISE
+ 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)
+
+ check function f1();
+ ERROR:  column "c" does not exist
+ LINE 1: SELECT c+10
+                ^
+ QUERY:  SELECT c+10
+ CONTEXT:  checking of PL/pgSQL function "f1" line 5 at assignment
+ 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;
+ select f1();
+  f1
+ ----
+
+ (1 row)
+
+ check function f1();
+ ERROR:  subscripted object is not an array
+ CONTEXT:  checking of PL/pgSQL function "f1" line 5 at assignment
+ 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)
+
+ check function f1();
+ ERROR:  record "_exception" has no field "hint"
+ CONTEXT:  checking of PL/pgSQL function "f1" line 7 at GET DIAGNOSTICS
+ 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);
+ check trigger t1_f1 on t1;
+ NOTICE:  checking function "f1_trg()"
+ ERROR:  record "new" has no field "c"
+ CONTEXT:  SQL statement "SELECT new.c"
+ checking of PL/pgSQL function "f1_trg" line 5 at RAISE
+ 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
+ check trigger t1_f1 on t1;
+ NOTICE:  checking function "f1_trg()"
+ ERROR:  record "new" has no field "c"
+ CONTEXT:  checking of PL/pgSQL function "f1_trg" line 5 at assignment
+ -- 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
+ check trigger t1_f1 on t1;
+ NOTICE:  checking function "f1_trg()"
+ -- ok
+ insert into t1 values(6,30);
+ drop table t1;
+ drop type _exception_type;
+ drop function f1_trg();
*** ./src/test/regress/sql/plpgsql.sql.orig   2011-11-29 19:20:59.508116598 +0100
--- ./src/test/regress/sql/plpgsql.sql        2011-11-29 19:21:24.538804318 +0100
***************
*** 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
+ check trigger tg_hslot_biu on HSlot;

-- ************************************************************
-- * BEFORE DELETE on HSlot
***************
*** 747,752 ****
--- 749,757 ----
end;
' language plpgsql;
+ -- check function should not fail
+ check function tg_backlink_set(bpchar, bpchar);
+
-- ************************************************************
-- * Support function to clear out the backlink field if
***************
*** 2335,2340 ****
--- 2340,2352 ----

select for_vect();

+ -- check function should not fail
+ 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
***************
*** 2714,2719 ****
--- 2726,2734 ----
end;
$$ language plpgsql;
+ -- check function should not fail
+ check function forc01();
+
select forc01();
-- try updating the cursor's current row
***************
*** 3048,3053 ****
--- 3063,3071 ----
end;
$$ language plpgsql immutable;
+ -- check function should not fail
+ check function case_test(bigint);
+
select case_test(1);
select case_test(2);
select case_test(3);
***************
*** 3600,3602 ****
--- 3618,3862 ----
drop function arrayassign1();
drop function testoa(x1 int, x2 int, x3 int);
+
+ --
+ -- check function statement tests
+ --
+
+ create table t1(a int, b int);
+
+ create function f1()
+ returns void as $$
+ begin
+   if false then
+     update t1 set c = 30;
+   end if;
+ end;
+ $$ language plpgsql;
+
+ select f1();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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;
+
+ select f1();
+ 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();
+ 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);
+ check trigger t1_f1 on 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
+ check trigger t1_f1 on 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
+ check trigger t1_f1 on t1;
+
+ -- ok
+ insert into t1 values(6,30);
+
+ drop table t1;
+ drop type _exception_type;
+
+ drop function f1_trg();
+

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ It's impossible for everything to be true. +