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
