patch: CHECK FUNCTION statement

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

Hello

I am sending a version with regress tests and basic documentation

Regards

Pavel Stehule

Attachments:

check_function.difftext/x-patch; charset=US-ASCII; name=check_function.diffDownload
*** ./doc/src/sgml/ref/allfiles.sgml.orig	2011-10-04 13:56:40.000000000 +0200
--- ./doc/src/sgml/ref/allfiles.sgml	2011-10-05 11:44:22.667571959 +0200
***************
*** 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-10-04 13:58:46.580303751 +0200
--- ./doc/src/sgml/ref/create_language.sgml	2011-10-04 13:59:14.301344571 +0200
***************
*** 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>
  
***************
*** 218,223 ****
--- 218,237 ----
        </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-10-04 13:56:40.000000000 +0200
--- ./doc/src/sgml/reference.sgml	2011-10-05 14:18:39.597956163 +0200
***************
*** 68,73 ****
--- 68,74 ----
     &alterView;
     &analyze;
     &begin;
+    &checkFunction;
     &checkpoint;
     &close;
     &cluster;
*** ./doc/src/sgml/ref/check_function.sgml.orig	2011-10-05 11:42:53.365345351 +0200
--- ./doc/src/sgml/ref/check_function.sgml	2011-10-06 13:33:07.781530754 +0200
***************
*** 0 ****
--- 1,76 ----
+ <!--
+ 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>
+ <para>
+ <programlisting>
+ postgres=# \d foo
+       Table "public.foo"
+  Column |  Type   | Modifiers 
+ --------+---------+-----------
+  id     | integer | not null
+  b      | integer | 
+  c      | integer | 
+  d      | integer | 
+ Indexes:
+     "foo_pkey" PRIMARY KEY, btree (id)
+     "foo_b_c_key" UNIQUE CONSTRAINT, btree (b, c)
+     "foo_b_key" UNIQUE CONSTRAINT, btree (b)
+     "foo_d_idx" btree (d)
+ Referenced by:
+     TABLE "omega" CONSTRAINT "omega_foo_id_fkey" FOREIGN KEY (foo_id) REFERENCES foo(id)
+ 
+ postgres=# \sf fx
+ CREATE OR REPLACE FUNCTION public.fx(b integer)
+  RETURNS integer
+  LANGUAGE plpgsql
+ AS $function$
+ DECLARE r record;
+    s int := 0;
+ BEGIN
+   FOR r IN SELECT * FROM foo WHERE foo.b = fx.b
+   LOOP
+     s := s + r.x;
+   END LOOP;
+   RETURN s;
+ END;
+ $function$
+ postgres=# CHECK FUNCTION fx(int);
+ ERROR:  record "r" has no field "x"
+ CONTEXT:  SQL statement "SELECT s + r.x"
+ PL/pgSQL function "fx" line 7 at assignment
+ </programlisting>
+ </para>
+  </refsect1>
+ 
+ </refentry>
*** ./src/backend/catalog/pg_proc.c.orig	2011-10-04 13:56:40.742123353 +0200
--- ./src/backend/catalog/pg_proc.c	2011-10-04 13:59:14.302344572 +0200
***************
*** 1075,1077 ****
--- 1075,1078 ----
  	*newcursorpos = newcp;
  	return false;
  }
+ 
*** ./src/backend/commands/functioncmds.c.orig	2011-10-04 13:58:46.587303763 +0200
--- ./src/backend/commands/functioncmds.c	2011-10-06 13:13:53.211900081 +0200
***************
*** 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"
***************
*** 1079,1084 ****
--- 1081,1209 ----
  	}
  }
  
+ /*
+  * 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;
+ 
+ 		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 */
+ 		}
+ 
+ 		OidFunctionCall2(languageChecker, ObjectIdGetDatum(funcOid), 
+ 							    ObjectIdGetDatum(relid));
+ 
+ 		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-10-04 13:58:46.590303767 +0200
--- ./src/backend/commands/proclang.c	2011-10-05 07:25:52.104765099 +0200
***************
*** 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);
***************
*** 68,76 ****
  	PLTemplate *pltemplate;
  	Oid			handlerOid,
  				inlineOid,
! 				valOid;
  	Oid			funcrettype;
! 	Oid			funcargtypes[1];
  
  	/*
  	 * Translate the language name to lower case
--- 69,78 ----
  	PLTemplate *pltemplate;
  	Oid			handlerOid,
  				inlineOid,
! 				valOid,
! 				checkerOid;
  	Oid			funcrettype;
! 	Oid			funcargtypes[2];
  
  	/*
  	 * Translate the language name to lower case
***************
*** 225,234 ****
  		else
  			valOid = InvalidOid;
  
  		/* ok, create it */
  		create_proc_lang(languageName, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, pltemplate->tmpltrusted);
  	}
  	else
  	{
--- 227,275 ----
  		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(languageName, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, checkerOid, pltemplate->tmpltrusted);
  	}
  	else
  	{
***************
*** 300,309 ****
  		else
  			valOid = InvalidOid;
  
  		/* ok, create it */
  		create_proc_lang(languageName, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, stmt->pltrusted);
  	}
  }
  
--- 341,362 ----
  		else
  			valOid = InvalidOid;
  
+ 		/* validate the validator 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(languageName, stmt->replace, GetUserId(),
  						 handlerOid, inlineOid,
! 						 valOid, checkerOid, stmt->pltrusted);
  	}
  }
  
***************
*** 313,319 ****
  static void
  create_proc_lang(const char *languageName, bool replace,
  				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
! 				 Oid valOid, bool trusted)
  {
  	Relation	rel;
  	TupleDesc	tupDesc;
--- 366,372 ----
  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;
***************
*** 343,348 ****
--- 396,402 ----
  	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 */
***************
*** 429,434 ****
--- 483,497 ----
  		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);
***************
*** 484,489 ****
--- 547,557 ----
  		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-10-04 13:56:40.000000000 +0200
--- ./src/backend/nodes/copyfuncs.c	2011-10-06 13:06:19.787276391 +0200
***************
*** 2867,2872 ****
--- 2867,2885 ----
  	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)
  {
***************
*** 4230,4235 ****
--- 4243,4251 ----
  		case T_RemoveFuncStmt:
  			retval = _copyRemoveFuncStmt(from);
  			break;
+ 		case T_CheckFunctionStmt:
+ 			retval = _copyCheckFunctionStmt(from);
+ 			break;
  		case T_DoStmt:
  			retval = _copyDoStmt(from);
  			break;
*** ./src/backend/nodes/equalfuncs.c.orig	2011-10-04 13:56:40.000000000 +0200
--- ./src/backend/nodes/equalfuncs.c	2011-10-06 13:08:28.417452554 +0200
***************
*** 1302,1307 ****
--- 1302,1318 ----
  }
  
  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);
***************
*** 2787,2792 ****
--- 2798,2806 ----
  		case T_RemoveFuncStmt:
  			retval = _equalRemoveFuncStmt(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-10-04 13:58:46.594303771 +0200
--- ./src/backend/parser/gram.y	2011-10-04 13:59:14.314344592 +0200
***************
*** 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
***************
*** 3197,3207 ****
  				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;
--- 3199,3210 ----
  				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;
***************
*** 3209,3214 ****
--- 3212,3218 ----
  				n->plhandler = $8;
  				n->plinline = $9;
  				n->plvalidator = $10;
+ 				n->plchecker = $11;
  				n->pltrusted = $3;
  				$$ = (Node *)n;
  			}
***************
*** 3243,3248 ****
--- 3247,3257 ----
  			| /*EMPTY*/								{ $$ = NIL; }
  		;
  
+ opt_checker:
+ 			CHECK handler_name					{ $$ = $2; }
+ 			| /*EMPTY*/								{ $$ = NIL; }
+ 		;
+ 
  DropPLangStmt:
  			DROP opt_procedural LANGUAGE ColId_or_Sconst opt_drop_behavior
  				{
***************
*** 6249,6254 ****
--- 6258,6293 ----
  
  /*****************************************************************************
   *
+  *		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-10-04 13:58:46.609303793 +0200
--- ./src/backend/tcop/utility.c	2011-10-05 08:11:46.129451749 +0200
***************
*** 936,941 ****
--- 936,945 ----
  			AlterFunction((AlterFunctionStmt *) parsetree);
  			break;
  
+ 		case T_CheckFunctionStmt:
+ 			CheckFunction((CheckFunctionStmt *) parsetree);
+ 			break;
+ 
  		case T_IndexStmt:		/* CREATE INDEX */
  			{
  				IndexStmt  *stmt = (IndexStmt *) parsetree;
***************
*** 2260,2265 ****
--- 2264,2276 ----
  			}
  			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));
***************
*** 2722,2727 ****
--- 2733,2742 ----
  			}
  			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-10-04 13:56:40.000000000 +0200
--- ./src/bin/psql/tab-complete.c	2011-10-05 08:51:23.603634388 +0200
***************
*** 1,4 ****
--- 1,5 ----
  /*
+  *
   * psql - the PostgreSQL interactive terminal
   *
   * Copyright (c) 2000-2011, PostgreSQL Global Development Group
***************
*** 719,725 ****
  			   *prev6_wd;
  
  	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",
--- 720,726 ----
  			   *prev6_wd;
  
  	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",
***************
*** 1521,1526 ****
--- 1522,1549 ----
  
  		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-10-04 13:58:46.610303794 +0200
--- ./src/include/catalog/pg_language.h	2011-10-04 13:59:14.317344596 +0200
***************
*** 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-10-04 13:58:46.612303798 +0200
--- ./src/include/catalog/pg_pltemplate.h	2011-10-04 13:59:14.318344598 +0200
***************
*** 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-10-04 13:58:46.614303802 +0200
--- ./src/include/commands/defrem.h	2011-10-04 13:59:14.318344598 +0200
***************
*** 61,66 ****
--- 61,67 ----
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
  extern void RemoveFunction(RemoveFuncStmt *stmt);
  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-10-04 13:58:46.616303805 +0200
--- ./src/include/nodes/nodes.h	2011-10-04 13:59:14.319344599 +0200
***************
*** 290,295 ****
--- 290,296 ----
  	T_CreateFunctionStmt,
  	T_AlterFunctionStmt,
  	T_RemoveFuncStmt,
+ 	T_CheckFunctionStmt,
  	T_DoStmt,
  	T_RenameStmt,
  	T_RuleStmt,
*** ./src/include/nodes/parsenodes.h.orig	2011-10-04 13:56:40.994123707 +0200
--- ./src/include/nodes/parsenodes.h	2011-10-04 13:59:14.320344601 +0200
***************
*** 1750,1755 ****
--- 1750,1756 ----
  	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;
  
***************
*** 2132,2137 ****
--- 2133,2151 ----
  } RemoveFuncStmt;
  
  /* ----------------------
+  *		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_exec.c.orig	2011-10-04 13:56:41.051123786 +0200
--- ./src/pl/plpgsql/src/pl_exec.c	2011-10-05 07:45:35.615349404 +0200
***************
*** 211,216 ****
--- 211,220 ----
  						  PLpgSQL_expr *dynquery, List *params,
  						  const char *portalname, int cursorOptions);
  
+ static void
+ set_rectype_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt,
+ 					    PLpgSQL_rec *rec,PLpgSQL_expr *query,
+ 							    bool use_element_type);
  
  /* ----------
   * plpgsql_exec_function	Called by the call handler for
***************
*** 6175,6177 ****
--- 6179,6837 ----
  
  	return portal;
  }
+ 
+ /*
+  * Sometime we must initialize a unknown record variable with NULL
+  * of type that is derived from some plan. This is necessary for later
+  * using a rec variable. Last parameter 'use_element_type' is true, when
+  * we would to assign a element type of result array.
+  *
+  */
+ static void
+ set_rectype_for_query(PLpgSQL_execstate *estate,
+ 				    PLpgSQL_stmt *stmt,
+ 					    PLpgSQL_rec *rec,
+ 						    PLpgSQL_expr *query,
+ 							    bool use_element_type)
+ {
+ 	bool	   *nulls;
+ 	HeapTuple  tup;
+ 	CachedPlanSource *plansource = NULL;
+ 	const char *err_text = estate->err_text;
+ 
+ 	estate->err_text = NULL;
+ 	estate->err_stmt = stmt;
+ 
+ 	if (rec->freetup)
+ 		heap_freetuple(rec->tup);
+ 
+ 	if (rec->freetupdesc)
+ 		FreeTupleDesc(rec->tupdesc);
+ 
+ 	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);
+ 
+ 		rec->tupdesc = CreateTupleDescCopy(plansource->resultDesc);
+ 		rec->freetupdesc = true;
+ 	}
+ 	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 tupdesc;
+ 
+ 		/* result should be a array */
+ 		if (rec->tupdesc->natts != 1)
+ 			ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 				 errmsg_plural("query \"%s\" returned %d column",
+ 							   "query \"%s\" returned %d columns",
+ 							   rec->tupdesc->natts,
+ 							   query->query,
+ 							   rec->tupdesc->natts)));
+ 
+ 		/* check the type of the expression - must be an array */
+ 		elemtype = get_element_type(rec->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(rec->tupdesc->attrs[0]->atttypid))));
+ 
+ 		/* we can't know typmod now */
+ 		tupdesc = lookup_rowtype_tupdesc_noerror(elemtype, -1, true);
+ 		if (tupdesc != NULL)
+ 		{
+ 			if (rec->freetupdesc)
+ 				FreeTupleDesc(rec->tupdesc);
+ 			rec->tupdesc = CreateTupleDescCopy(tupdesc);
+ 			rec->freetupdesc = true;
+ 			ReleaseTupleDesc(tupdesc);
+ 		}
+ 		else
+ 			elog(ERROR, "cannot to identify real type for record type variable");
+ 	}
+ 
+ 	/*
+ 	 * When record is assigned to composite type, then
+ 	 * we should to unpack composite type.
+ 	 */
+ 	if (rec->tupdesc->tdtypeid == RECORDOID &&
+ 			rec->tupdesc->tdtypmod == -1 &&
+ 			rec->tupdesc->natts == 1 &&
+ 			stmt->cmd_type == PLPGSQL_STMT_ASSIGN)
+ 	{
+ 		TupleDesc tupdesc;
+ 
+ 		tupdesc = lookup_rowtype_tupdesc_noerror(rec->tupdesc->attrs[0]->atttypid,
+ 								rec->tupdesc->attrs[0]->atttypmod,
+ 												    true);
+ 		if (tupdesc != NULL)
+ 		{
+ 			if (rec->freetupdesc)
+ 				FreeTupleDesc(rec->tupdesc);
+ 			rec->tupdesc = CreateTupleDescCopy(tupdesc);
+ 			rec->freetupdesc = true;
+ 			ReleaseTupleDesc(tupdesc);
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * 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 (rec->tupdesc->tdtypeid == RECORDOID &&
+ 			rec->tupdesc->tdtypmod == -1 &&
+ 			rec->tupdesc->natts == 1 &&
+ 			rec->tupdesc->attrs[0]->atttypid == RECORDOID &&
+ 			rec->tupdesc->attrs[0]->atttypmod == -1)
+ 	{
+ 		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(rec->tupdesc);
+ 					BlessTupleDesc(rd);
+ 
+ 					rec->tupdesc = rd;
+ 				}
+ 			}
+ 		}
+ 
+ 		ReleaseCachedPlan(cplan, true);
+ 	}
+ 
+ 	/* last recheck */
+ 	if (rec->tupdesc->tdtypeid == RECORDOID &&
+ 			rec->tupdesc->tdtypmod == -1 &&
+ 			rec->tupdesc->natts == 1 &&
+ 			rec->tupdesc->attrs[0]->atttypid == RECORDOID &&
+ 			rec->tupdesc->attrs[0]->atttypmod == -1)
+ 		elog(ERROR, "cannot to identify real type for record type variable");
+ 
+ 	/* initialize rec by NULLs - variable should be valid */
+ 	nulls = (bool *) palloc(rec->tupdesc->natts * sizeof(bool));
+ 	memset(nulls, true, rec->tupdesc->natts * sizeof(bool));
+ 
+ 	tup = heap_form_tuple(rec->tupdesc, NULL, nulls);
+ 	if (HeapTupleIsValid(tup))
+ 	{
+ 		rec->tup = tup;
+ 		rec->freetup = true;
+ 	}
+ 	else
+ 	{
+ 		rec->tup = NULL;
+ 		rec->freetup = false;
+ 	}
+ 
+ 	estate->err_text = err_text;
+ }
+ 
+ /*
+  * Prepare plans walker - this can be used for checking
+  *
+  */
+ bool
+ plpgsql_expr_prepare_plan(PLpgSQL_stmt *stmt, PLpgSQL_expr *expr, void *context)
+ {
+ 	PLpgSQL_execstate *estate = (PLpgSQL_execstate *) context;
+ 	int cursorOptions = 0;
+ 	const char *err_text = estate->err_text;
+ 
+ 	/* overwrite a estate variables */
+ 	estate->err_text = NULL;
+ 	estate->err_stmt = stmt;
+ 
+ 	if (expr == NULL)
+ 		return false;
+ 
+ 	switch (stmt->cmd_type)
+ 	{
+ 		case PLPGSQL_STMT_OPEN:
+ 			{
+ 				PLpgSQL_stmt_open *stmt_open = (PLpgSQL_stmt_open *) stmt;
+ 				PLpgSQL_var *curvar = (PLpgSQL_var *) estate->datums[stmt_open->curvar];
+ 
+ 				cursorOptions = curvar->cursor_options;
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORC:
+ 			{
+ 				PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt;
+ 				PLpgSQL_var *curvar = (PLpgSQL_var *) estate->datums[stmt_forc->curvar];
+ 
+ 				/*
+ 				 * change a cursorOption only whenn this call is related to
+ 				 * curvar->cursor_explicit_expr
+ 				 */
+ 				if (curvar->cursor_explicit_expr == expr)
+ 					cursorOptions = curvar->cursor_options;
+ 			}
+ 			break;
+ 	}
+ 
+ 	/*
+ 	 * If first time through, create a plan for this expression.
+ 	 */
+ 	if (expr->plan == NULL)
+ 	{
+ 		exec_prepare_plan(estate, expr, cursorOptions);
+ 	}
+ 
+ 	/*
+ 	 * very common practic in PLpgSQL is  is using a record type. But any using of
+ 	 * untyped record breaks a check. A solution is an prediction of record type based
+ 	 * on plans - a following switch covers all PLpgSQL statements where a record
+ 	 * variable can be assigned.
+ 	 *
+ 	 * when record is target of dynamic SQL statement, then raise exception
+ 	 *
+ 	 */
+ 	switch (stmt->cmd_type)
+ 	{
+ 		case PLPGSQL_STMT_ASSIGN:
+ 			{
+ 				PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt;
+ 				PLpgSQL_datum *target = (estate->datums[stmt_assign->varno]);
+ 
+ 				if (target->dtype == PLPGSQL_DTYPE_REC)
+ 				{
+ 					set_rectype_for_query(estate, stmt,
+ 								(PLpgSQL_rec *) target,
+ 									    stmt_assign->expr,
+ 												false);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_EXECSQL:
+ 			{
+ 				PLpgSQL_stmt_execsql *stmt_execsql = (PLpgSQL_stmt_execsql *) stmt;
+ 				ListCell *l;
+ 
+ 				stmt_execsql->mod_stmt = false;
+ 				foreach(l, expr->plan->plancache_list)
+ 				{
+ 					CachedPlanSource *plansource = (CachedPlanSource *) lfirst(l);
+ 					CachedPlan *cplan = GetCachedPlan(plansource, NULL, true);
+ 					ListCell   *l2;
+ 
+ 					foreach(l2, cplan->stmt_list)
+ 					{
+ 						PlannedStmt *p = (PlannedStmt *) lfirst(l2);
+ 
+ 						if (IsA(p, PlannedStmt) &&
+ 								p->canSetTag)
+ 						{
+ 							if (p->commandType == CMD_INSERT ||
+ 								p->commandType == CMD_UPDATE ||
+ 								p->commandType == CMD_DELETE)
+ 								stmt_execsql->mod_stmt = true;
+ 						}
+ 					}
+ 
+ 					ReleaseCachedPlan(cplan, true);
+ 				}
+ 
+ 				if (stmt_execsql->rec != NULL)
+ 				{
+ 					set_rectype_for_query(estate, stmt,
+ 								(PLpgSQL_rec *) (estate->datums[stmt_execsql->rec->dno]),
+ 									stmt_execsql->sqlstmt,
+ 												false);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FETCH:
+ 			{
+ 				PLpgSQL_stmt_fetch *stmt_fetch = (PLpgSQL_stmt_fetch *) stmt;
+ 
+ 				/* fetch can not determinate a record datatype for refcursors */
+ 				if (stmt_fetch->rec != NULL)
+ 				{
+ 					PLpgSQL_var *curvar = (PLpgSQL_var *)( estate->datums[stmt_fetch->curvar]);
+ 					PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt_fetch->rec->dno]);
+ 
+ 					if (curvar->cursor_explicit_expr == NULL)
+ 						elog(ERROR, "cannot to determinate record type for refcursor");
+ 
+ 					set_rectype_for_query(estate, stmt,
+ 								rec,
+ 									curvar->cursor_explicit_expr,
+ 											    false);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORS:
+ 			{
+ 				PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt;
+ 
+ 				if (stmt_fors->rec != NULL)
+ 				{
+ 					set_rectype_for_query(estate, stmt,
+ 								(PLpgSQL_rec *) (estate->datums[stmt_fors->rec->dno]),
+ 									stmt_fors->query,
+ 											    false);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FORC:
+ 			{
+ 				PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt;
+ 				PLpgSQL_var *curvar = (PLpgSQL_var *) (estate->datums[stmt_forc->curvar]);
+ 
+ 				if (stmt_forc->rec != NULL && curvar->cursor_explicit_expr == expr)
+ 				{
+ 					PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt_forc->rec->dno]);
+ 
+ 					set_rectype_for_query(estate, stmt,
+ 									rec,
+ 									curvar->cursor_explicit_expr,
+ 											    false);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_FOREACH_A:
+ 			{
+ 				PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt;
+ 				PLpgSQL_datum *loop_var = estate->datums[stmt_foreach_a->varno];
+ 
+ 				if (loop_var->dtype == PLPGSQL_DTYPE_REC)
+ 				{
+ 					set_rectype_for_query(estate, stmt,
+ 								(PLpgSQL_rec *) loop_var,
+ 									stmt_foreach_a->expr,
+ 											    true);
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_CASE:
+ 			{
+ 				PLpgSQL_stmt_case *stmt_case = (PLpgSQL_stmt_case *) stmt;
+ 				TupleDesc tupdesc;
+ 				Oid result_oid;
+ 
+ 				/*
+ 				 * this is special case - a result type of expression should to
+ 				 * overwrite a expected int datatype.
+ 				 */
+ 				if (stmt_case->t_expr == expr)
+ 				{
+ 					CachedPlanSource *plansource = NULL;
+ 					const char *err_text = estate->err_text;
+ 
+ 					estate->err_text = NULL;
+ 					estate->err_stmt = stmt;
+ 
+ 
+ 					if (expr->plan != NULL)
+ 					{
+ 						SPIPlanPtr plan = expr->plan;
+ 						PLpgSQL_var *t_var = (PLpgSQL_var *) estate->datums[stmt_case->t_varno];
+ 
+ 						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);
+ 
+ 						if (tupdesc->natts != 1)
+ 							ereport(ERROR,
+ 								    (errcode(ERRCODE_SYNTAX_ERROR),
+ 								     errmsg_plural("query \"%s\" returned %d column",
+ 									   "query \"%s\" returned %d columns",
+ 										    tupdesc->natts,
+ 										    expr->query,
+ 										    tupdesc->natts)));
+ 
+ 						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);
+ 						FreeTupleDesc(tupdesc);
+ 					}
+ 					else
+ 						elog(ERROR, "there are no plan for query: \"%s\"",
+ 											expr->query);
+ 
+ 					estate->err_text = err_text;
+ 				}
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNEXECUTE:
+ 			{
+ 				PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt;
+ 
+ 				if (stmt_dynexecute->into && stmt_dynexecute->rec != NULL)
+ 					elog(ERROR, "cannot to determine a result of dynamic SQL");
+ 			}
+ 			break;
+ 
+ 		case PLPGSQL_STMT_DYNFORS:
+ 			{
+ 				PLpgSQL_stmt_dynfors *stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt;
+ 
+ 				if (stmt_dynfors->rec != NULL)
+ 					elog(ERROR, "cannot to determinate a result of dynamic SQL");
+ 			}
+ 			break;
+ 	}
+ 
+ 	estate->err_text = err_text;
+ 
+ 	return false;
+ }
+ 
+ 
+ /*
+  * 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;
+ }
+ 
+ /*
+  * 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 the execution state
+ 	 */
+ 	plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo);
+ 
+ 	/*
+ 	 * Setup error traceback support for ereport()
+ 	 */
+ 	plerrcontext.callback = plpgsql_exec_error_callback;
+ 	plerrcontext.arg = &estate;
+ 	plerrcontext.previous = error_context_stack;
+ 	error_context_stack = &plerrcontext;
+ 
+ 	/*
+ 	 * 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 call the toplevel block of statements
+ 	 */
+ 	estate.err_text = NULL;
+ 	estate.err_stmt = (PLpgSQL_stmt *) (func->action);
+ 
+ 	plpgsql_expr_walker(func, (PLpgSQL_stmt *) func->action,
+ 						    plpgsql_expr_prepare_plan,
+ 									&estate);
+ 
+ 	estate.err_stmt = NULL;
+ 	estate.err_text = gettext_noop("while casting return value to function's return type");
+ 
+ 	/* Clean up any leftover temporary memory */
+ 	plpgsql_destroy_econtext(&estate);
+ 	exec_eval_cleanup(&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;
+ 	int			i;
+ 	PLpgSQL_rec *rec_new,
+ 			   *rec_old;
+ 
+ 	/*
+ 	 * Setup the execution state
+ 	 */
+ 	plpgsql_estate_setup(&estate, func, NULL);
+ 
+ 	/*
+ 	 * Setup error traceback support for ereport()
+ 	 */
+ 	plerrcontext.callback = plpgsql_exec_error_callback;
+ 	plerrcontext.arg = &estate;
+ 	plerrcontext.previous = error_context_stack;
+ 	error_context_stack = &plerrcontext;
+ 
+ 	/*
+ 	 * Make local execution copies of all the datums
+ 	 */
+ 	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
+ 	 *
+ 	 * 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->tupdesc = trigdata->tg_relation->rd_att;
+ 	rec_new->freetupdesc = false;
+ 	rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
+ 	rec_old->freetup = false;
+ 	rec_old->tupdesc = trigdata->tg_relation->rd_att;
+ 	rec_old->freetupdesc = false;
+ 
+ 	/*
+ 	 * 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 call the toplevel block of statements
+ 	 */
+ 	estate.err_text = NULL;
+ 	estate.err_stmt = (PLpgSQL_stmt *) (func->action);
+ 
+ 	plpgsql_expr_walker(func, (PLpgSQL_stmt *) func->action,
+ 						    plpgsql_expr_prepare_plan,
+ 									&estate);
+ 
+ 	estate.err_stmt = NULL;
+ 	estate.err_text = gettext_noop("while casting return value to function's return type");
+ 
+ 	/* Clean up any leftover temporary memory */
+ 	plpgsql_destroy_econtext(&estate);
+ 	exec_eval_cleanup(&estate);
+ 
+ 	/*
+ 	 * Pop the error context stack
+ 	 */
+ 	error_context_stack = plerrcontext.previous;
+ }
*** ./src/pl/plpgsql/src/pl_funcs.c.orig	2011-10-04 13:58:46.624303816 +0200
--- ./src/pl/plpgsql/src/pl_funcs.c	2011-10-04 13:59:14.325344608 +0200
***************
*** 293,681 ****
  }
  
  
! /**********************************************************************
!  * Release memory when a PL/pgSQL function is no longer needed
   *
!  * The code for recursing through the function tree is really only
!  * needed to locate PLpgSQL_expr nodes, which may contain references
!  * to saved SPI Plans that must be freed.  The function tree itself,
!  * along with subsidiary data, is freed in one swoop by freeing the
!  * function's permanent memory context.
!  **********************************************************************/
! static void free_stmt(PLpgSQL_stmt *stmt);
! static void free_block(PLpgSQL_stmt_block *block);
! static void free_assign(PLpgSQL_stmt_assign *stmt);
! static void free_if(PLpgSQL_stmt_if *stmt);
! static void free_case(PLpgSQL_stmt_case *stmt);
! static void free_loop(PLpgSQL_stmt_loop *stmt);
! static void free_while(PLpgSQL_stmt_while *stmt);
! static void free_fori(PLpgSQL_stmt_fori *stmt);
! static void free_fors(PLpgSQL_stmt_fors *stmt);
! static void free_forc(PLpgSQL_stmt_forc *stmt);
! static void free_foreach_a(PLpgSQL_stmt_foreach_a *stmt);
! static void free_exit(PLpgSQL_stmt_exit *stmt);
! static void free_return(PLpgSQL_stmt_return *stmt);
! static void free_return_next(PLpgSQL_stmt_return_next *stmt);
! static void free_return_query(PLpgSQL_stmt_return_query *stmt);
! static void free_raise(PLpgSQL_stmt_raise *stmt);
! static void free_execsql(PLpgSQL_stmt_execsql *stmt);
! static void free_dynexecute(PLpgSQL_stmt_dynexecute *stmt);
! static void free_dynfors(PLpgSQL_stmt_dynfors *stmt);
! static void free_getdiag(PLpgSQL_stmt_getdiag *stmt);
! static void free_open(PLpgSQL_stmt_open *stmt);
! static void free_fetch(PLpgSQL_stmt_fetch *stmt);
! static void free_close(PLpgSQL_stmt_close *stmt);
! static void free_perform(PLpgSQL_stmt_perform *stmt);
! static void free_expr(PLpgSQL_expr *expr);
  
  
! static void
! free_stmt(PLpgSQL_stmt *stmt)
  {
  	switch ((enum PLpgSQL_stmt_types) stmt->cmd_type)
  	{
  		case PLPGSQL_STMT_BLOCK:
! 			free_block((PLpgSQL_stmt_block *) stmt);
! 			break;
  		case PLPGSQL_STMT_ASSIGN:
! 			free_assign((PLpgSQL_stmt_assign *) stmt);
! 			break;
  		case PLPGSQL_STMT_IF:
! 			free_if((PLpgSQL_stmt_if *) stmt);
! 			break;
  		case PLPGSQL_STMT_CASE:
! 			free_case((PLpgSQL_stmt_case *) stmt);
! 			break;
  		case PLPGSQL_STMT_LOOP:
! 			free_loop((PLpgSQL_stmt_loop *) stmt);
! 			break;
  		case PLPGSQL_STMT_WHILE:
! 			free_while((PLpgSQL_stmt_while *) stmt);
! 			break;
  		case PLPGSQL_STMT_FORI:
! 			free_fori((PLpgSQL_stmt_fori *) stmt);
! 			break;
  		case PLPGSQL_STMT_FORS:
! 			free_fors((PLpgSQL_stmt_fors *) stmt);
! 			break;
  		case PLPGSQL_STMT_FORC:
! 			free_forc((PLpgSQL_stmt_forc *) stmt);
! 			break;
! 		case PLPGSQL_STMT_FOREACH_A:
! 			free_foreach_a((PLpgSQL_stmt_foreach_a *) stmt);
! 			break;
! 		case PLPGSQL_STMT_EXIT:
! 			free_exit((PLpgSQL_stmt_exit *) stmt);
! 			break;
! 		case PLPGSQL_STMT_RETURN:
! 			free_return((PLpgSQL_stmt_return *) stmt);
! 			break;
! 		case PLPGSQL_STMT_RETURN_NEXT:
! 			free_return_next((PLpgSQL_stmt_return_next *) stmt);
! 			break;
! 		case PLPGSQL_STMT_RETURN_QUERY:
! 			free_return_query((PLpgSQL_stmt_return_query *) stmt);
! 			break;
! 		case PLPGSQL_STMT_RAISE:
! 			free_raise((PLpgSQL_stmt_raise *) stmt);
! 			break;
! 		case PLPGSQL_STMT_EXECSQL:
! 			free_execsql((PLpgSQL_stmt_execsql *) stmt);
! 			break;
! 		case PLPGSQL_STMT_DYNEXECUTE:
! 			free_dynexecute((PLpgSQL_stmt_dynexecute *) stmt);
! 			break;
  		case PLPGSQL_STMT_DYNFORS:
! 			free_dynfors((PLpgSQL_stmt_dynfors *) stmt);
! 			break;
! 		case PLPGSQL_STMT_GETDIAG:
! 			free_getdiag((PLpgSQL_stmt_getdiag *) stmt);
! 			break;
! 		case PLPGSQL_STMT_OPEN:
! 			free_open((PLpgSQL_stmt_open *) stmt);
! 			break;
! 		case PLPGSQL_STMT_FETCH:
! 			free_fetch((PLpgSQL_stmt_fetch *) stmt);
! 			break;
! 		case PLPGSQL_STMT_CLOSE:
! 			free_close((PLpgSQL_stmt_close *) stmt);
! 			break;
! 		case PLPGSQL_STMT_PERFORM:
! 			free_perform((PLpgSQL_stmt_perform *) stmt);
! 			break;
! 		default:
! 			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
! 			break;
! 	}
! }
  
! static void
! free_stmts(List *stmts)
! {
! 	ListCell   *s;
  
! 	foreach(s, stmts)
! 	{
! 		free_stmt((PLpgSQL_stmt *) lfirst(s));
! 	}
! }
  
! static void
! free_block(PLpgSQL_stmt_block *block)
! {
! 	free_stmts(block->body);
! 	if (block->exceptions)
! 	{
! 		ListCell   *e;
  
! 		foreach(e, block->exceptions->exc_list)
! 		{
! 			PLpgSQL_exception *exc = (PLpgSQL_exception *) lfirst(e);
  
! 			free_stmts(exc->action);
! 		}
! 	}
! }
  
! static void
! free_assign(PLpgSQL_stmt_assign *stmt)
! {
! 	free_expr(stmt->expr);
! }
  
! static void
! free_if(PLpgSQL_stmt_if *stmt)
! {
! 	free_expr(stmt->cond);
! 	free_stmts(stmt->true_body);
! 	free_stmts(stmt->false_body);
! }
  
! static void
! free_case(PLpgSQL_stmt_case *stmt)
! {
! 	ListCell   *l;
  
! 	free_expr(stmt->t_expr);
! 	foreach(l, stmt->case_when_list)
! 	{
! 		PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
  
! 		free_expr(cwt->expr);
! 		free_stmts(cwt->stmts);
! 	}
! 	free_stmts(stmt->else_stmts);
! }
  
! static void
! free_loop(PLpgSQL_stmt_loop *stmt)
! {
! 	free_stmts(stmt->body);
! }
  
! static void
! free_while(PLpgSQL_stmt_while *stmt)
! {
! 	free_expr(stmt->cond);
! 	free_stmts(stmt->body);
! }
  
! static void
! free_fori(PLpgSQL_stmt_fori *stmt)
! {
! 	free_expr(stmt->lower);
! 	free_expr(stmt->upper);
! 	free_expr(stmt->step);
! 	free_stmts(stmt->body);
! }
  
! static void
! free_fors(PLpgSQL_stmt_fors *stmt)
! {
! 	free_stmts(stmt->body);
! 	free_expr(stmt->query);
! }
  
! static void
! free_forc(PLpgSQL_stmt_forc *stmt)
! {
! 	free_stmts(stmt->body);
! 	free_expr(stmt->argquery);
! }
  
! static void
! free_foreach_a(PLpgSQL_stmt_foreach_a *stmt)
! {
! 	free_expr(stmt->expr);
! 	free_stmts(stmt->body);
! }
  
! static void
! free_open(PLpgSQL_stmt_open *stmt)
! {
! 	ListCell   *lc;
  
! 	free_expr(stmt->argquery);
! 	free_expr(stmt->query);
! 	free_expr(stmt->dynquery);
! 	foreach(lc, stmt->params)
! 	{
! 		free_expr((PLpgSQL_expr *) lfirst(lc));
! 	}
! }
  
! static void
! free_fetch(PLpgSQL_stmt_fetch *stmt)
! {
! 	free_expr(stmt->expr);
! }
  
! static void
! free_close(PLpgSQL_stmt_close *stmt)
! {
! }
  
! static void
! free_perform(PLpgSQL_stmt_perform *stmt)
! {
! 	free_expr(stmt->expr);
! }
  
! static void
! free_exit(PLpgSQL_stmt_exit *stmt)
! {
! 	free_expr(stmt->cond);
! }
  
! static void
! free_return(PLpgSQL_stmt_return *stmt)
! {
! 	free_expr(stmt->expr);
! }
  
! static void
! free_return_next(PLpgSQL_stmt_return_next *stmt)
! {
! 	free_expr(stmt->expr);
! }
  
! static void
! free_return_query(PLpgSQL_stmt_return_query *stmt)
! {
! 	ListCell   *lc;
  
! 	free_expr(stmt->query);
! 	free_expr(stmt->dynquery);
! 	foreach(lc, stmt->params)
! 	{
! 		free_expr((PLpgSQL_expr *) lfirst(lc));
! 	}
! }
  
! static void
! free_raise(PLpgSQL_stmt_raise *stmt)
! {
! 	ListCell   *lc;
  
! 	foreach(lc, stmt->params)
! 	{
! 		free_expr((PLpgSQL_expr *) lfirst(lc));
! 	}
! 	foreach(lc, stmt->options)
! 	{
! 		PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc);
  
! 		free_expr(opt->expr);
! 	}
! }
  
! static void
! free_execsql(PLpgSQL_stmt_execsql *stmt)
! {
! 	free_expr(stmt->sqlstmt);
! }
  
! static void
! free_dynexecute(PLpgSQL_stmt_dynexecute *stmt)
! {
! 	ListCell   *lc;
  
! 	free_expr(stmt->query);
! 	foreach(lc, stmt->params)
! 	{
! 		free_expr((PLpgSQL_expr *) lfirst(lc));
! 	}
! }
  
! static void
! free_dynfors(PLpgSQL_stmt_dynfors *stmt)
! {
! 	ListCell   *lc;
  
! 	free_stmts(stmt->body);
! 	free_expr(stmt->query);
! 	foreach(lc, stmt->params)
! 	{
! 		free_expr((PLpgSQL_expr *) lfirst(lc));
  	}
  }
  
! static void
! free_getdiag(PLpgSQL_stmt_getdiag *stmt)
! {
! }
  
! static void
! free_expr(PLpgSQL_expr *expr)
  {
  	if (expr && expr->plan)
  	{
  		SPI_freeplan(expr->plan);
  		expr->plan = NULL;
  	}
  }
  
  void
  plpgsql_free_function_memory(PLpgSQL_function *func)
  {
- 	int			i;
- 
  	/* Better not call this on an in-use function */
  	Assert(func->use_count == 0);
  
- 	/* Release plans associated with variable declarations */
- 	for (i = 0; i < func->ndatums; i++)
- 	{
- 		PLpgSQL_datum *d = func->datums[i];
- 
- 		switch (d->dtype)
- 		{
- 			case PLPGSQL_DTYPE_VAR:
- 				{
- 					PLpgSQL_var *var = (PLpgSQL_var *) d;
- 
- 					free_expr(var->default_val);
- 					free_expr(var->cursor_explicit_expr);
- 				}
- 				break;
- 			case PLPGSQL_DTYPE_ROW:
- 				break;
- 			case PLPGSQL_DTYPE_REC:
- 				break;
- 			case PLPGSQL_DTYPE_RECFIELD:
- 				break;
- 			case PLPGSQL_DTYPE_ARRAYELEM:
- 				free_expr(((PLpgSQL_arrayelem *) d)->subscript);
- 				break;
- 			default:
- 				elog(ERROR, "unrecognized data type: %d", d->dtype);
- 		}
- 	}
- 	func->ndatums = 0;
- 
  	/* Release plans in statement tree */
! 	if (func->action)
! 		free_block(func->action);
  	func->action = NULL;
  
  	/*
--- 293,653 ----
  }
  
  
! /*
!  * call a plpgsql_expr_walker for any statement in list
   *
!  */
! static bool
! plpgsql_expr_walker_list(PLpgSQL_function *func, List *stmts,
! 					bool (*expr_walker)(),
! 								void *context)
! {
! 	ListCell *lc;
  
+ 	foreach(lc, stmts)
+ 	{
+ 		PLpgSQL_stmt *stmt = (PLpgSQL_stmt *) lfirst(lc);
  
! 		if (plpgsql_expr_walker(func, stmt, expr_walker, context))
! 			return true;
! 	}
! 	return false;
! }
! 
! /*
!  * walk over all expressions inside statements tree
!  *
!  * stmt_walker is function called for every stmt and should be NULL
!  *
!  */
! bool
! plpgsql_expr_walker(PLpgSQL_function *func,
! 				PLpgSQL_stmt *stmt,
! 					bool (*expr_walker)(),
! 								void *context)
  {
+ 	ListCell *l;
+ 
+ 	if (stmt == NULL)
+ 		return false;
+ 
  	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]];
! 
! 					switch(d->dtype)
! 					{
! 						case PLPGSQL_DTYPE_VAR:
! 							{
! 								PLpgSQL_var *var = (PLpgSQL_var *) d;
! 
! 								if (expr_walker(stmt, var->default_val, context))
! 									return true;
! 
! 								/*
! 								 * theoretically there is place for walk over var->cursor_explicit_expr,
! 								 * but we would not to call process too early. In this moment a some 
! 								 * record parameters should be unknown. So we will wait on better moment
! 								 *
! 								 * if (expr_walker(stmt, var->cursor_explicit_expr, context))
! 								 * 	return true;
! 								 */
! 							}
! 							break;
! 						case PLPGSQL_DTYPE_ROW:
! 						case PLPGSQL_DTYPE_REC:
! 						case PLPGSQL_DTYPE_RECFIELD:
! 							break;
! 						case PLPGSQL_DTYPE_ARRAYELEM:
! 							if (expr_walker(stmt, ((PLpgSQL_arrayelem *) d)->subscript, context))
! 								return true;
! 						default:
! 							elog(ERROR, "unrecognized data type: %d", d->dtype);
! 					}
! 				}
! 
! 				if (plpgsql_expr_walker_list(func, stmt_block->body, expr_walker, context))
! 					return true;
! 
! 				if (stmt_block->exceptions)
! 				{
! 					foreach(l, stmt_block->exceptions->exc_list)
! 					{
! 						if (plpgsql_expr_walker_list(func, ((PLpgSQL_exception *) lfirst(l))->action,
! 														expr_walker,
! 															    context))
! 							return true;
! 					}
! 				}
! 
! 				return false;
! 			}
! 
  		case PLPGSQL_STMT_ASSIGN:
! 			return expr_walker(stmt, ((PLpgSQL_stmt_assign *) stmt)->expr, context);
! 
  		case PLPGSQL_STMT_IF:
! 			{
! 				PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt;
! 
! 				if (expr_walker(stmt, stmt_if->cond, context))
! 					return true;
! 
! 				if (plpgsql_expr_walker_list(func, stmt_if->true_body, expr_walker, context))
! 					return true;
! 
! 				return plpgsql_expr_walker_list(func, stmt_if->false_body, expr_walker, context);
! 			}
! 
  		case PLPGSQL_STMT_CASE:
! 			{
! 				PLpgSQL_stmt_case *stmt_case = (PLpgSQL_stmt_case *) stmt;
! 
! 				if (expr_walker(stmt, stmt_case->t_expr, context))
! 					return true;
! 
! 				foreach(l, stmt_case->case_when_list)
! 				{
! 					PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
! 
! 					if (expr_walker(stmt, cwt->expr, context))
! 						return true;
! 
! 					if (plpgsql_expr_walker_list(func, cwt->stmts, expr_walker, context))
! 						return true;
! 				}
! 
! 				return plpgsql_expr_walker_list(func, stmt_case->else_stmts, expr_walker, context);
! 			}
! 
  		case PLPGSQL_STMT_LOOP:
! 			return plpgsql_expr_walker_list(func, ((PLpgSQL_stmt_loop *) stmt)->body, expr_walker, context);
! 
  		case PLPGSQL_STMT_WHILE:
! 			{
! 				PLpgSQL_stmt_while *stmt_while = (PLpgSQL_stmt_while *) stmt;
! 
! 				if (expr_walker(stmt, stmt_while->cond, context))
! 					return true;
! 
! 				return plpgsql_expr_walker_list(func, stmt_while->body, expr_walker, context);
! 			}
! 
  		case PLPGSQL_STMT_FORI:
! 			{
! 				PLpgSQL_stmt_fori *stmt_fori = (PLpgSQL_stmt_fori *) stmt;
! 
! 				if (expr_walker(stmt, stmt_fori->lower, context))
! 					return true;
! 
! 				if (expr_walker(stmt, stmt_fori->upper, context))
! 					return true;
! 
! 				if (expr_walker(stmt, stmt_fori->step, context))
! 					return true;
! 
! 				return plpgsql_expr_walker_list(func, stmt_fori->body, expr_walker, context);
! 			}
! 
  		case PLPGSQL_STMT_FORS:
! 			{
! 				PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt;
! 
! 				if (expr_walker(stmt, stmt_fors->query, context))
! 					return true;
! 
! 				return plpgsql_expr_walker_list(func, stmt_fors->body, expr_walker, context);
! 			}
! 
  		case PLPGSQL_STMT_FORC:
! 			{
! 				PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt;
! 				PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_forc->curvar];
! 
! 				if (expr_walker(stmt, stmt_forc->argquery, context))
! 					return true;
! 
! 				if (expr_walker(stmt, var->cursor_explicit_expr, context))
! 					return true;
! 
! 				return plpgsql_expr_walker_list(func, stmt_forc->body, expr_walker, context);
! 			}
! 
  		case PLPGSQL_STMT_DYNFORS:
! 			{
! 				PLpgSQL_stmt_dynfors * stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt;
  
! 				if (expr_walker(stmt, stmt_dynfors->query, context))
! 					return true;
  
! 				foreach(l, stmt_dynfors->params)
! 				{
! 					if (expr_walker(stmt, (PLpgSQL_expr *) lfirst(l), context))
! 						return true;
! 				}
  
! 				return plpgsql_expr_walker_list(func, stmt_dynfors->body, expr_walker, context);
! 			}
  
! 		case PLPGSQL_STMT_FOREACH_A:
! 			{
! 				PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt;
  
! 				if (expr_walker(stmt, stmt_foreach_a->expr, context))
! 					return true;
  
! 				return plpgsql_expr_walker_list(func, stmt_foreach_a->body, expr_walker, context);
! 			}
  
! 		case PLPGSQL_STMT_EXIT:
! 			return expr_walker(stmt, ((PLpgSQL_stmt_exit *) stmt)->cond, context);
  
! 		case PLPGSQL_STMT_PERFORM:
! 			return expr_walker(stmt, ((PLpgSQL_stmt_perform *) stmt)->expr, context);
  
! 		case PLPGSQL_STMT_RETURN:
! 			return expr_walker(stmt, ((PLpgSQL_stmt_return *) stmt)->expr, context);
  
! 		case PLPGSQL_STMT_RETURN_NEXT:
! 			return expr_walker(stmt, ((PLpgSQL_stmt_return_next *) stmt)->expr, context);
  
! 		case PLPGSQL_STMT_RETURN_QUERY:
! 			{
! 				PLpgSQL_stmt_return_query *stmt_rq = (PLpgSQL_stmt_return_query *) stmt;
  
! 				if (expr_walker(stmt, stmt_rq->query, context))
! 					return true;
  
! 				if (expr_walker(stmt, stmt_rq->dynquery, context))
! 					return true;
  
! 				foreach(l, stmt_rq->params)
! 				{
! 					if (expr_walker(stmt, (PLpgSQL_expr *) lfirst(l), context))
! 						return true;
! 				}
  
! 				return false;
! 			}
  
! 		case PLPGSQL_STMT_RAISE:
! 			{
! 				PLpgSQL_stmt_raise *stmt_raise = (PLpgSQL_stmt_raise *) stmt;
  
! 				foreach(l, stmt_raise->params)
! 				{
! 					if (expr_walker(stmt, (PLpgSQL_expr *) lfirst(l), context))
! 						return true;
! 				}
! 				foreach(l, stmt_raise->options)
! 				{
! 					if (expr_walker(stmt, ((PLpgSQL_raise_option *) lfirst(l))->expr, context))
! 						return true;
! 				}
  
! 				return false;
! 			}
  
! 		case PLPGSQL_STMT_EXECSQL:
! 			return expr_walker(stmt, ((PLpgSQL_stmt_execsql *) stmt)->sqlstmt, context);
  
! 		case PLPGSQL_STMT_DYNEXECUTE:
! 			{
! 				PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt;
  
! 				if (expr_walker(stmt, stmt_dynexecute->query, context))
! 					return true;
  
! 				foreach(l, stmt_dynexecute->params)
! 				{
! 					if (expr_walker(stmt, (PLpgSQL_expr *) lfirst(l), context))
! 						return true;
! 				}
  
! 				return false;
! 			}
! 			break;
  
! 		case PLPGSQL_STMT_GETDIAG:
! 			return false;
  
! 		case PLPGSQL_STMT_OPEN:
! 			{
! 				PLpgSQL_stmt_open *stmt_open = (PLpgSQL_stmt_open *) stmt;
  
! 				PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_open->curvar];
  
! 				if (expr_walker(stmt, var->cursor_explicit_expr, context))
! 					return true;
  
! 				if (expr_walker(stmt, stmt_open->query, context))
! 					return true;
  
! 				if (expr_walker(stmt, stmt_open->dynquery, context))
! 					return true;
  
! 				if (expr_walker(stmt, stmt_open->argquery, context))
! 					return true;
  
! 				foreach(l, stmt_open->params)
! 				{
! 					if (expr_walker(stmt, (PLpgSQL_expr *) lfirst(l), context))
! 						return true;
! 				}
  
! 				return false;
! 			}
  
! 		case PLPGSQL_STMT_FETCH:
! 		case PLPGSQL_STMT_CLOSE:
! 			return false;
  
! 		default:
! 			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
! 			return false; /* be compiler quite */
  	}
  }
  
! /**********************************************************************
!  * Release memory when a PL/pgSQL function is no longer needed
!  *
!  * The code for recursing through the function tree is really only
!  * needed to locate PLpgSQL_expr nodes, which may contain references
!  * to saved SPI Plans that must be freed.  The function tree itself,
!  * along with subsidiary data, is freed in one swoop by freeing the
!  * function's permanent memory context.
!  **********************************************************************/
  
! static bool
! free_expression(PLpgSQL_stmt *stmt, PLpgSQL_expr *expr, void *context)
  {
  	if (expr && expr->plan)
  	{
  		SPI_freeplan(expr->plan);
  		expr->plan = NULL;
  	}
+ 	return false;
  }
  
  void
  plpgsql_free_function_memory(PLpgSQL_function *func)
  {
  	/* Better not call this on an in-use function */
  	Assert(func->use_count == 0);
  
  	/* Release plans in statement tree */
! 	plpgsql_expr_walker(func, (PLpgSQL_stmt *) func->action,
! 						    free_expression,
! 								    NULL);
! 	func->ndatums = 0;
  	func->action = NULL;
  
  	/*
*** ./src/pl/plpgsql/src/pl_handler.c.orig	2011-10-04 13:58:46.626303819 +0200
--- ./src/pl/plpgsql/src/pl_handler.c	2011-10-06 13:11:21.515690600 +0200
***************
*** 312,314 ****
--- 312,428 ----
  
  	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;
+ 
+ 	Form_pg_proc proc;
+ 	char		functyptype;
+ 	bool	   istrigger = false;
+ 	PLpgSQL_execstate *save_cur_estate;
+ 
+ 	/* 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, true);
+ 
+ 	/* Must save and restore prior value of cur_estate */
+ 	save_cur_estate = function->cur_estate;
+ 
+ 	/* 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 = save_cur_estate;
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	if (istrigger)
+ 		relation_close(trigdata.tg_relation, AccessShareLock);
+ 	function->cur_estate = save_cur_estate;
+ 
+ 	/*
+ 	 * 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-10-04 13:56:41.052123789 +0200
--- ./src/pl/plpgsql/src/plpgsql.h	2011-10-05 07:43:53.217212322 +0200
***************
*** 903,908 ****
--- 903,909 ----
  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
***************
*** 920,925 ****
--- 921,929 ----
  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_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
***************
*** 944,949 ****
--- 948,956 ----
  extern const char *plpgsql_getdiag_kindname(int kind);
  extern void plpgsql_free_function_memory(PLpgSQL_function *func);
  extern void plpgsql_dumptree(PLpgSQL_function *func);
+ extern bool plpgsql_expr_walker(PLpgSQL_function *func,
+ 				PLpgSQL_stmt *stmt,
+ 				bool (*expr_walker)(), void *context);
  
  /* ----------
   * Scanner functions in pl_scanner.c
*** ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql.orig	2011-10-04 13:58:46.628303823 +0200
--- ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql	2011-10-04 13:59:14.326344609 +0200
***************
*** 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-10-04 13:56:41.000000000 +0200
--- ./src/test/regress/expected/plpgsql.out	2011-10-06 13:47:47.000000000 +0200
***************
*** 4571,4573 ****
--- 4571,4626 ----
  CONTEXT:  PL/pgSQL function "testoa" line 5 at assignment
  drop function arrayassign1();
  drop function testoa(x1 int, x2 int, x3 int);
+ --
+ -- check function test
+ --
+ create table plpgsql_foo(a int, b int, c int);
+ create or replace function fx()
+ returns void as $$
+ declare r record;
+   s int := 0;
+ begin
+   for r in select a from plpgsql_foo where a = 10
+   loop
+     s := s + r.a;
+   end loop;
+ end;
+ $$ language plpgsql;
+ -- should be ok
+ check function fx();
+ create or replace function fx()
+ returns void as $$
+ declare r record;
+   s int := 0;
+ begin
+   for r in select a from plpgsql_foo where x = 10
+   loop
+     s := s + r.a;
+   end loop;
+ end;
+ $$ language plpgsql;
+ -- should fail
+ check function fx();
+ ERROR:  column "x" does not exist
+ LINE 1: select a from plpgsql_foo where x = 10
+                                         ^
+ QUERY:  select a from plpgsql_foo where x = 10
+ CONTEXT:  PL/pgSQL function "fx" line 5 at FOR over SELECT rows
+ create or replace function fx()
+ returns void as $$
+ declare r record;
+   s int := 0;
+ begin
+   for r in select a from plpgsql_foo where a = 10
+   loop
+     s := s + r.x;
+   end loop;
+ end;
+ $$ language plpgsql;
+ -- should fail
+ check function fx();
+ ERROR:  record "r" has no field "x"
+ CONTEXT:  SQL statement "SELECT s + r.x"
+ PL/pgSQL function "fx" line 7 at assignment
+ drop function fx();
+ drop table plpgsql_foo;
*** ./src/test/regress/sql/plpgsql.sql.orig	2011-10-04 13:56:41.000000000 +0200
--- ./src/test/regress/sql/plpgsql.sql	2011-10-06 13:47:13.444820293 +0200
***************
*** 3600,3602 ****
--- 3600,3655 ----
  
  drop function arrayassign1();
  drop function testoa(x1 int, x2 int, x3 int);
+ 
+ --
+ -- check function test
+ --
+ create table plpgsql_foo(a int, b int, c int);
+ 
+ create or replace function fx()
+ returns void as $$
+ declare r record;
+   s int := 0;
+ begin
+   for r in select a from plpgsql_foo where a = 10
+   loop
+     s := s + r.a;
+   end loop;
+ end;
+ $$ language plpgsql;
+ 
+ -- should be ok
+ check function fx();
+ 
+ create or replace function fx()
+ returns void as $$
+ declare r record;
+   s int := 0;
+ begin
+   for r in select a from plpgsql_foo where x = 10
+   loop
+     s := s + r.a;
+   end loop;
+ end;
+ $$ language plpgsql;
+ 
+ -- should fail
+ check function fx();
+ 
+ create or replace function fx()
+ returns void as $$
+ declare r record;
+   s int := 0;
+ begin
+   for r in select a from plpgsql_foo where a = 10
+   loop
+     s := s + r.x;
+   end loop;
+ end;
+ $$ language plpgsql;
+ 
+ -- should fail
+ check function fx();
+ 
+ drop function fx();
+ drop table plpgsql_foo;
#2Thom Brown
thom@linux.com
In reply to: Pavel Stehule (#1)
Re: patch: CHECK FUNCTION statement

On 6 October 2011 12:52, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Hello

I am sending a version with regress tests and basic documentation

Hi Pavel,

I think this sentence needs rewriting:

"checkfunction 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 CHECK FUNCTION or CHECK TRIGGER."

to something like:

"checkfunction is the name of an existing function that will be called
whenever a CHECK FUNCTION or CHECK TRIGGER is requested on a function
written in the language."

And shouldn't this apply to ALTER LANGUAGE too?

And there seem to be copy/paste symptoms in
doc/src/sgml/ref/check_function.sgml where it shows the definition of
CREATE FUNCTION and CREATE TRIGGER instead of CHECK FUNCTION and CHECK
TRIGGER.

In src/include/nodes/parsenodes.h there's the error message "there are
no plan for query:". This should probably read "there is no plan for
query:". This appears more than once.

And "cannot to identify real type for record type variable" doesn't
sound right. Firstly "to" shouldn't be in there, and referring to a
"real" type is ambiguous as there is a data type called "real". This
appears at least twice.

In src/pl/plpgsql/src/pl_exec.c:

"cannot to determine a result of dynamic SQL" should be "cannot
determine result of dynamic SQL".

Also, I recommend rebasing this patch as it doesn't apply cleanly. In
particular, the following fail:

src/pl/plpgsql/src/pl_funcs.c
src/test/regress/expected/plpgsql.out
src/test/regress/sql/plpgsql.sql

I haven't tried actually testing the patch itsel, but I will probably
give it a go if a rebased version appears. :)

--
Thom Brown
Twitter: @darkixion
IRC (freenode): dark_ixion
Registered Linux user: #516935

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#3Pavel Stehule
pavel.stehule@gmail.com
In reply to: Thom Brown (#2)
Re: patch: CHECK FUNCTION statement

Hello

2011/11/14 Thom Brown <thom@linux.com>:

On 6 October 2011 12:52, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Hello

I am sending a version with regress tests and basic documentation

Hi Pavel,

I think this sentence needs rewriting:

"checkfunction 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 CHECK FUNCTION or CHECK TRIGGER."

to something like:

"checkfunction is the name of an existing function that will be called
whenever a CHECK FUNCTION or CHECK TRIGGER is requested on a function
written in the language."

And shouldn't this apply to ALTER LANGUAGE too?

And there seem to be copy/paste symptoms in
doc/src/sgml/ref/check_function.sgml where it shows the definition of
CREATE FUNCTION and CREATE TRIGGER instead of CHECK FUNCTION and CHECK
TRIGGER.

In src/include/nodes/parsenodes.h there's the error message "there are
no plan for query:".  This should probably read "there is no plan for
query:".  This appears more than once.

And "cannot to identify real type for record type variable" doesn't
sound right.  Firstly "to" shouldn't be in there, and referring to a
"real" type is ambiguous as there is a data type called "real".  This
appears at least twice.

I am not native speaker, so please, fix documentation as you like.

In src/pl/plpgsql/src/pl_exec.c:

"cannot to determine a result of dynamic SQL" should be "cannot
determine result of dynamic SQL".

Also, I recommend rebasing this patch as it doesn't apply cleanly.  In
particular, the following fail:

src/pl/plpgsql/src/pl_funcs.c
src/test/regress/expected/plpgsql.out
src/test/regress/sql/plpgsql.sql

I haven't tried actually testing the patch itsel, but I will probably
give it a go if a rebased version appears. :)

There will be more work, I found one area, that was not checked - expr targets.

this new code is on github https://github.com/okbob/plpgsql_lint

this week I plan to redesign this contrib module to CHECK FUNCTION
implementation for 9.2.

Regards

Pavel

Show quoted text

--
Thom Brown
Twitter: @darkixion
IRC (freenode): dark_ixion
Registered Linux user: #516935

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#4Thom Brown
thom@linux.com
In reply to: Pavel Stehule (#3)
Re: patch: CHECK FUNCTION statement

On 14 November 2011 20:54, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Hello

2011/11/14 Thom Brown <thom@linux.com>:

On 6 October 2011 12:52, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Hello

I am sending a version with regress tests and basic documentation

Hi Pavel,

I think this sentence needs rewriting:

"checkfunction 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 CHECK FUNCTION or CHECK TRIGGER."

to something like:

"checkfunction is the name of an existing function that will be called
whenever a CHECK FUNCTION or CHECK TRIGGER is requested on a function
written in the language."

And shouldn't this apply to ALTER LANGUAGE too?

And there seem to be copy/paste symptoms in
doc/src/sgml/ref/check_function.sgml where it shows the definition of
CREATE FUNCTION and CREATE TRIGGER instead of CHECK FUNCTION and CHECK
TRIGGER.

In src/include/nodes/parsenodes.h there's the error message "there are
no plan for query:".  This should probably read "there is no plan for
query:".  This appears more than once.

And "cannot to identify real type for record type variable" doesn't
sound right.  Firstly "to" shouldn't be in there, and referring to a
"real" type is ambiguous as there is a data type called "real".  This
appears at least twice.

I am not native speaker, so please, fix documentation as you like.

Well I wasn't entirely confident my interpretations were correct. I'd
prefer to have a rebased patch I can fully apply first, and then I can
provide a corrective patch as I'd like to test it too.

--
Thom Brown
Twitter: @darkixion
IRC (freenode): dark_ixion
Registered Linux user: #516935

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company