From d60f10b0e74173653d17c09750a791afe6f56404 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Wed, 22 May 2002 17:21:02 +0000
Subject: [PATCH] Add optional "validator" function to languages that can
 validate the function body (and other properties) as a function in the
 language is created.  This generalizes ad hoc code that already existed for
 the built-in languages.

The validation now happens after the pg_proc tuple of the new function
is created, so it is possible to define recursive SQL functions.

Add some regression test cases that cover bogus function definition
attempts.
---
 doc/src/sgml/ref/create_language.sgml         |  30 ++-
 doc/src/sgml/release.sgml                     |   8 +-
 src/backend/catalog/pg_aggregate.c            |   3 +-
 src/backend/catalog/pg_proc.c                 | 200 ++++++++++++------
 src/backend/commands/functioncmds.c           |  28 ++-
 src/backend/commands/proclang.c               |  20 +-
 src/backend/nodes/copyfuncs.c                 |   3 +-
 src/backend/nodes/equalfuncs.c                |   4 +-
 src/backend/parser/gram.y                     |  16 +-
 src/backend/parser/keywords.c                 |   3 +-
 src/backend/utils/adt/sets.c                  |   3 +-
 src/bin/pg_dump/pg_dump.c                     |  40 +++-
 src/include/catalog/catversion.h              |   4 +-
 src/include/catalog/pg_language.h             |  16 +-
 src/include/catalog/pg_proc.h                 |  10 +-
 src/include/nodes/parsenodes.h                |   3 +-
 .../regress/input/create_function_1.source    |  25 +++
 .../regress/output/create_function_1.source   |  25 +++
 18 files changed, 339 insertions(+), 102 deletions(-)

diff --git a/doc/src/sgml/ref/create_language.sgml b/doc/src/sgml/ref/create_language.sgml
index e5f47be9ffe..42d7c9b4877 100644
--- a/doc/src/sgml/ref/create_language.sgml
+++ b/doc/src/sgml/ref/create_language.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/create_language.sgml,v 1.23 2002/05/18 15:44:47 petere Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/create_language.sgml,v 1.24 2002/05/22 17:20:58 petere Exp $
 PostgreSQL documentation
 -->
 
@@ -17,7 +17,7 @@ PostgreSQL documentation
  <refsynopsisdiv>
 <synopsis>
 CREATE [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">langname</replaceable>
-    HANDLER <replaceable class="parameter">call_handler</replaceable>
+    HANDLER <replaceable class="parameter">call_handler</replaceable> [ VALIDATOR <replaceable>valfunction</replaceable> ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -113,6 +113,32 @@ CREATE [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">langna
       </para>
      </listitem>
     </varlistentry>
+
+    <varlistentry>
+     <term><literal>VALIDATOR</literal> <replaceable class="parameter">valfunction</replaceable></term>
+
+     <listitem>
+      <para>
+       <replaceable class="parameter">valfunction</replaceable> is the
+       name of a previously registered function that will be called
+       when a new function in the language is created, to validate the
+       new function.  The validator function must take one argument of
+       type <type>oid</type>, which will be the OID of the
+       to-be-created function, and can have any return type.  If no
+       validator function is specified, then a new function will not
+       be checked when it is created.
+      </para>
+
+      <para>
+       A validator function would typically inspect the function body
+       for syntactical correctness, but it can also look at other
+       properties of the function, for example if the language cannot
+       handle certain argument types.  To signal an error, the
+       validator function should use the <function>elog()</function>
+       function.  The return value of the function is ignored.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/release.sgml b/doc/src/sgml/release.sgml
index b25b7ef5e38..0bdb095804c 100644
--- a/doc/src/sgml/release.sgml
+++ b/doc/src/sgml/release.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/release.sgml,v 1.137 2002/05/18 13:47:59 petere Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/release.sgml,v 1.138 2002/05/22 17:20:58 petere Exp $
 -->
 
 <appendix id="release">
@@ -24,6 +24,8 @@ CDATA means the content is "SGML-free", so you can write without
 worries about funny characters.
 -->
 <literallayout><![CDATA[
+Recursive SQL functions can be defined
+User-defined procedural languages can register a validator function to check new functions as they are created
 Functions can be executed with the privileges of the owner
 Syntax of CREATE FUNCTION has been extended to resemble SQL99
 Effects of SET within a transaction block now roll back if transaction aborts
@@ -72,7 +74,7 @@ Database and user-specific session defaults for run-time configuration variables
     <title>Changes</title>
 
     <para>
-     <programlisting>
+<literallayout>
 Ensure that sequence counters do not go backwards after a crash (Tom)
 Fix pgaccess kanji-coversion key binding (Tatsuo)
 Optimizer improvements (Tom)
@@ -90,7 +92,7 @@ contrib/tsearch dictionary improvements, see README.tsearch for
   an additional installation step (Thomas T. Thai, Teodor Sigaev)
 Fix for array subscripts handling (Tom)
 Allow EXECUTE of "CREATE TABLE AS ... SELECT" in PL/PgSQL (Tom)
-     </programlisting>
+</literallayout>
     </para>
    </sect2>
   </sect1>
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 1853acda73c..3a3d749e19d 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/catalog/pg_aggregate.c,v 1.47 2002/05/21 22:05:54 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/catalog/pg_aggregate.c,v 1.48 2002/05/22 17:20:58 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -136,6 +136,7 @@ AggregateCreate(const char *aggName,
 							  false,		/* doesn't return a set */
 							  finaltype,	/* returnType */
 							  INTERNALlanguageId,	/* languageObjectId */
+							  0,
 							  "aggregate_dummy",	/* placeholder proc */
 							  "-",			/* probin */
 							  true,			/* isAgg */
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 4d656d98ba2..e7421ef2077 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/catalog/pg_proc.c,v 1.73 2002/05/21 22:05:54 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/catalog/pg_proc.c,v 1.74 2002/05/22 17:20:58 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,6 +20,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
+#include "fmgr.h"
 #include "miscadmin.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
@@ -32,6 +33,9 @@
 
 
 static void checkretval(Oid rettype, List *queryTreeList);
+Datum fmgr_internal_validator(PG_FUNCTION_ARGS);
+Datum fmgr_c_validator(PG_FUNCTION_ARGS);
+Datum fmgr_sql_validator(PG_FUNCTION_ARGS);
 
 
 /* ----------------------------------------------------------------
@@ -45,6 +49,7 @@ ProcedureCreate(const char *procedureName,
 				bool returnsSet,
 				Oid returnType,
 				Oid languageObjectId,
+				Oid languageValidator,
 				const char *prosrc,
 				const char *probin,
 				bool isAgg,
@@ -66,7 +71,6 @@ ProcedureCreate(const char *procedureName,
 	char		nulls[Natts_pg_proc];
 	Datum		values[Natts_pg_proc];
 	char		replaces[Natts_pg_proc];
-	List	   *querytree_list;
 	Oid			typev[FUNC_MAX_ARGS];
 	Oid			relid;
 	NameData	procname;
@@ -126,12 +130,6 @@ ProcedureCreate(const char *procedureName,
 		}
 	}
 
-	if (!OidIsValid(returnType))
-	{
-		if (languageObjectId == SQLlanguageId)
-			elog(ERROR, "SQL functions cannot return type \"opaque\"");
-	}
-
 	/*
 	 * don't allow functions of complex types that have the same name as
 	 * existing attributes of the type
@@ -142,65 +140,6 @@ ProcedureCreate(const char *procedureName,
 		elog(ERROR, "method %s already an attribute of type %s",
 			 procedureName, format_type_be(typev[0]));
 
-	/*
-	 * If this is a postquel procedure, we parse it here in order to be
-	 * sure that it contains no syntax errors.	We should store the plan
-	 * in an Inversion file for use later, but for now, we just store the
-	 * procedure's text in the prosrc attribute.
-	 */
-
-	if (languageObjectId == SQLlanguageId)
-	{
-		querytree_list = pg_parse_and_rewrite((char *) prosrc,
-											  typev,
-											  parameterCount);
-		/* typecheck return value */
-		checkretval(returnType, querytree_list);
-	}
-
-	/*
-	 * If this is an internal procedure, check that the given internal
-	 * function name (the 'prosrc' value) is a known builtin function.
-	 *
-	 * NOTE: in Postgres versions before 6.5, the SQL name of the created
-	 * function could not be different from the internal name, and
-	 * 'prosrc' wasn't used.  So there is code out there that does CREATE
-	 * FUNCTION xyz AS '' LANGUAGE 'internal'.	To preserve some modicum
-	 * of backwards compatibility, accept an empty 'prosrc' value as
-	 * meaning the supplied SQL function name.
-	 */
-	if (languageObjectId == INTERNALlanguageId)
-	{
-		if (strlen(prosrc) == 0)
-			prosrc = procedureName;
-		if (fmgr_internal_function((char *) prosrc) == InvalidOid)
-			elog(ERROR,
-				 "there is no built-in function named \"%s\"",
-				 prosrc);
-	}
-
-	/*
-	 * If this is a dynamically loadable procedure, make sure that the
-	 * library file exists, is loadable, and contains the specified link
-	 * symbol.	Also check for a valid function information record.
-	 *
-	 * We used to perform these checks only when the function was first
-	 * called, but it seems friendlier to verify the library's validity at
-	 * CREATE FUNCTION time.
-	 */
-	if (languageObjectId == ClanguageId)
-	{
-		void	   *libraryhandle;
-
-		/* If link symbol is specified as "-", substitute procedure name */
-		if (strcmp(prosrc, "-") == 0)
-			prosrc = procedureName;
-		(void) load_external_function((char *) probin,
-									  (char *) prosrc,
-									  true,
-									  &libraryhandle);
-		(void) fetch_finfo_record(libraryhandle, (char *) prosrc);
-	}
 
 	/*
 	 * All seems OK; prepare the data to be inserted into pg_proc.
@@ -316,6 +255,14 @@ ProcedureCreate(const char *procedureName,
 
 	heap_close(rel, RowExclusiveLock);
 
+	/* Verify function body */
+	if (OidIsValid(languageValidator))
+	{
+		/* Advance command counter so recursive functions can be defined */
+		CommandCounterIncrement();
+		OidFunctionCall1(languageValidator, retval);
+	}
+
 	return retval;
 }
 
@@ -454,3 +401,122 @@ checkretval(Oid rettype, List *queryTreeList)
 
 	heap_close(reln, AccessShareLock);
 }
+
+
+
+/*
+ * Validator for internal functions
+ *
+ * Check that the given internal function name (the "prosrc" value) is
+ * a known builtin function.
+ */
+Datum
+fmgr_internal_validator(PG_FUNCTION_ARGS)
+{
+	Oid			funcoid = PG_GETARG_OID(0);
+	HeapTuple	tuple;
+	Form_pg_proc proc;
+	bool		isnull;
+	Datum		tmp;
+	char	   *prosrc;
+
+	tuple = SearchSysCache(PROCOID, funcoid, 0, 0, 0);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup of function %u failed", funcoid);
+	proc = (Form_pg_proc) GETSTRUCT(tuple);
+
+	tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
+	if (isnull)
+		elog(ERROR, "null prosrc");
+	prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
+
+	if (fmgr_internal_function(prosrc) == InvalidOid)
+		elog(ERROR, "there is no built-in function named \"%s\"", prosrc);
+
+	ReleaseSysCache(tuple);
+	PG_RETURN_BOOL(true);
+}
+
+
+
+/*
+ * Validator for C language functions
+ *
+ * Make sure that the library file exists, is loadable, and contains
+ * the specified link symbol. Also check for a valid function
+ * information record.
+ */
+Datum
+fmgr_c_validator(PG_FUNCTION_ARGS)
+{
+	Oid			funcoid = PG_GETARG_OID(0);
+	void	   *libraryhandle;
+	HeapTuple	tuple;
+	Form_pg_proc proc;
+	bool		isnull;
+	Datum		tmp;
+	char	   *prosrc;
+	char	   *probin;
+
+	tuple = SearchSysCache(PROCOID, funcoid, 0, 0, 0);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup of function %u failed", funcoid);
+	proc = (Form_pg_proc) GETSTRUCT(tuple);
+
+	tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
+	if (isnull)
+		elog(ERROR, "null prosrc");
+	prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
+
+	tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_probin, &isnull);
+	if (isnull)
+		elog(ERROR, "null probin");
+	probin = DatumGetCString(DirectFunctionCall1(textout, tmp));
+	
+	(void) load_external_function(probin, prosrc, true, &libraryhandle);
+	(void) fetch_finfo_record(libraryhandle, prosrc);
+
+	ReleaseSysCache(tuple);
+	PG_RETURN_BOOL(true);
+}
+
+
+
+/*
+ * Validator for SQL language functions
+ *
+ * Parse it here in order to be sure that it contains no syntax
+ * errors.
+ */
+Datum
+fmgr_sql_validator(PG_FUNCTION_ARGS)
+{
+	Oid			funcoid = PG_GETARG_OID(0);
+	HeapTuple	tuple;
+	Form_pg_proc proc;
+	List	   *querytree_list;
+	bool		isnull;
+	Datum		tmp;
+	char	   *prosrc;
+
+	tuple = SearchSysCache(PROCOID, funcoid, 0, 0, 0);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup of function %u failed", funcoid);
+
+	proc = (Form_pg_proc) GETSTRUCT(tuple);
+
+	if (!OidIsValid(proc->prorettype))
+			elog(ERROR, "SQL functions cannot return type \"opaque\"");
+
+	tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
+	if (isnull)
+		elog(ERROR, "null prosrc");
+
+	prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
+
+	querytree_list = pg_parse_and_rewrite(prosrc, proc->proargtypes, proc->pronargs);
+	checkretval(proc->prorettype, querytree_list);
+
+	ReleaseSysCache(tuple);
+	PG_RETURN_BOOL(true);
+}
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 06870b0d3d2..a619a71b435 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/functioncmds.c,v 1.5 2002/05/18 13:47:59 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/functioncmds.c,v 1.6 2002/05/22 17:20:58 petere Exp $
  *
  * DESCRIPTION
  *	  These routines take the parse tree and pick out the
@@ -388,6 +388,7 @@ CreateFunction(CreateFunctionStmt *stmt)
 	char	   *language;
 	char		languageName[NAMEDATALEN];
 	Oid			languageOid;
+	Oid			languageValidator;
 	char	   *funcname;
 	Oid			namespaceId;
 	AclResult	aclresult;
@@ -457,6 +458,8 @@ CreateFunction(CreateFunctionStmt *stmt)
 			aclcheck_error(ACLCHECK_NO_PRIV, NameStr(languageStruct->lanname));
 	}
 
+	languageValidator = languageStruct->lanvalidator;
+
 	ReleaseSysCache(languageTuple);
 
 	/*
@@ -477,6 +480,28 @@ CreateFunction(CreateFunctionStmt *stmt)
 	interpret_AS_clause(languageOid, languageName, as_clause,
 						&prosrc_str, &probin_str);
 
+	if (languageOid == INTERNALlanguageId)
+	{
+		/*
+		 * In PostgreSQL versions before 6.5, the SQL name of the
+		 * created function could not be different from the internal
+		 * name, and "prosrc" wasn't used.  So there is code out there
+		 * that does CREATE FUNCTION xyz AS '' LANGUAGE 'internal'.
+		 * To preserve some modicum of backwards compatibility, accept
+		 * an empty "prosrc" value as meaning the supplied SQL
+		 * function name.
+		 */
+		if (strlen(prosrc_str) == 0)
+			prosrc_str = funcname;
+	}
+
+	if (languageOid == ClanguageId)
+	{
+		/* If link symbol is specified as "-", substitute procedure name */
+		if (strcmp(prosrc_str, "-") == 0)
+			prosrc_str = funcname;
+	}
+
 	/*
 	 * And now that we have all the parameters, and know we're permitted
 	 * to do so, go ahead and create the function.
@@ -487,6 +512,7 @@ CreateFunction(CreateFunctionStmt *stmt)
 					returnsSet,
 					prorettype,
 					languageOid,
+					languageValidator,
 					prosrc_str, /* converted to text later */
 					probin_str, /* converted to text later */
 					false,		/* not an aggregate */
diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c
index 6cabbf0ec13..626c05f59cd 100644
--- a/src/backend/commands/proclang.c
+++ b/src/backend/commands/proclang.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/proclang.c,v 1.32 2002/05/21 22:05:54 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/proclang.c,v 1.33 2002/05/22 17:20:58 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -21,6 +21,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
 #include "commands/proclang.h"
 #include "commands/defrem.h"
 #include "fmgr.h"
@@ -39,7 +40,7 @@ void
 CreateProceduralLanguage(CreatePLangStmt *stmt)
 {
 	char		languageName[NAMEDATALEN];
-	Oid			procOid;
+	Oid			procOid, valProcOid;
 	Oid			typev[FUNC_MAX_ARGS];
 	char		nulls[Natts_pg_language];
 	Datum		values[Natts_pg_language];
@@ -76,9 +77,21 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
 		elog(ERROR, "PL handler function %s() doesn't exist",
 			 NameListToString(stmt->plhandler));
 	if (get_func_rettype(procOid) != InvalidOid)
-		elog(ERROR, "PL handler function %s() isn't of return type Opaque",
+		elog(ERROR, "PL handler function %s() does not return type \"opaque\"",
 			 NameListToString(stmt->plhandler));
 
+	/* validate the validator function */
+	if (stmt->plvalidator)
+	{
+		typev[0] = OIDOID;
+		valProcOid = LookupFuncName(stmt->plvalidator, 1, typev);
+		if (!OidIsValid(valProcOid))
+			elog(ERROR, "PL validator function %s(oid) doesn't exist",
+				 NameListToString(stmt->plvalidator));
+	}
+	else
+		valProcOid = 0;
+
 	/*
 	 * Insert the new language into pg_language
 	 */
@@ -93,6 +106,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
 	values[i++] = BoolGetDatum(true);	/* lanispl */
 	values[i++] = BoolGetDatum(stmt->pltrusted);
 	values[i++] = ObjectIdGetDatum(procOid);
+	values[i++] = ObjectIdGetDatum(valProcOid);
 	values[i++] = DirectFunctionCall1(textin,
 									  CStringGetDatum(stmt->plcompiler));
 	nulls[i] = 'n';				/* lanacl */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1f0cc11934f..5bc16dc9b82 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.187 2002/05/17 18:32:52 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.188 2002/05/22 17:20:58 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2418,6 +2418,7 @@ _copyCreatePLangStmt(CreatePLangStmt *from)
 	if (from->plname)
 		newnode->plname = pstrdup(from->plname);
 	Node_Copy(from, newnode, plhandler);
+	Node_Copy(from, newnode, plvalidator);
 	if (from->plcompiler)
 		newnode->plcompiler = pstrdup(from->plcompiler);
 	newnode->pltrusted = from->pltrusted;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index f48d6d033f4..b4f576fc881 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -20,7 +20,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.134 2002/05/17 18:32:52 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.135 2002/05/22 17:20:59 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1252,6 +1252,8 @@ _equalCreatePLangStmt(CreatePLangStmt *a, CreatePLangStmt *b)
 		return false;
 	if (!equal(a->plhandler, b->plhandler))
 		return false;
+	if (!equal(a->plvalidator, b->plvalidator))
+		return false;
 	if (!equalstr(a->plcompiler, b->plcompiler))
 		return false;
 	if (a->pltrusted != b->pltrusted)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 999910161d0..d1109b58a7e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.318 2002/05/19 15:16:55 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.319 2002/05/22 17:20:59 petere Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -182,7 +182,7 @@ static void doNegateFloat(Value *v);
 		index_name, name, function_name, file_name
 
 %type <list>	func_name, handler_name, qual_Op, qual_all_Op, OptUseOp,
-		opt_class
+		opt_class, opt_validator
 
 %type <range>	qualified_name, OptConstrFromTable
 
@@ -375,7 +375,7 @@ static void doNegateFloat(Value *v);
 	UNENCRYPTED, UNION, UNIQUE, UNKNOWN, UNLISTEN, UNTIL, UPDATE, USAGE,
 	USER, USING,
 
-	VACUUM, VALID, VALUES, VARCHAR, VARYING, VERBOSE, VERSION, VIEW, VOLATILE,
+	VACUUM, VALID, VALIDATOR, VALUES, VARCHAR, VARYING, VERBOSE, VERSION, VIEW, VOLATILE,
 	WHEN, WHERE, WITH, WITHOUT, WORK,
 	YEAR_P,
 	ZONE
@@ -1835,12 +1835,13 @@ IntegerOnly:  Iconst
  *****************************************************************************/
 
 CreatePLangStmt:  CREATE opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
-			HANDLER handler_name opt_lancompiler
+			HANDLER handler_name opt_validator opt_lancompiler
 			{
 				CreatePLangStmt *n = makeNode(CreatePLangStmt);
 				n->plname = $5;
 				n->plhandler = $7;
-				n->plcompiler = $8;
+				n->plvalidator = $8;
+				n->plcompiler = $9;
 				n->pltrusted = $2;
 				$$ = (Node *)n;
 			}
@@ -1864,6 +1865,10 @@ opt_lancompiler: LANCOMPILER Sconst { $$ = $2; }
 			| /*EMPTY*/			{ $$ = ""; }
 		;
 
+opt_validator: VALIDATOR handler_name { $$ = $2; }
+			| /*EMPTY*/ { $$ = NULL; }
+		;
+
 DropPLangStmt:  DROP opt_procedural LANGUAGE ColId_or_Sconst
 			{
 				DropPLangStmt *n = makeNode(DropPLangStmt);
@@ -6357,6 +6362,7 @@ unreserved_keyword:
 		| USAGE
 		| VACUUM
 		| VALID
+		| VALIDATOR
 		| VALUES
 		| VARYING
 		| VERSION
diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c
index 36900127ea8..2cf91572a00 100644
--- a/src/backend/parser/keywords.c
+++ b/src/backend/parser/keywords.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.110 2002/05/17 18:32:52 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.111 2002/05/22 17:20:59 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -292,6 +292,7 @@ static const ScanKeyword ScanKeywords[] = {
 	{"using", USING},
 	{"vacuum", VACUUM},
 	{"valid", VALID},
+	{"validator", VALIDATOR},
 	{"values", VALUES},
 	{"varchar", VARCHAR},
 	{"varying", VARYING},
diff --git a/src/backend/utils/adt/sets.c b/src/backend/utils/adt/sets.c
index 298bdbdec82..e93ed50ff0a 100644
--- a/src/backend/utils/adt/sets.c
+++ b/src/backend/utils/adt/sets.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/Attic/sets.c,v 1.44 2002/05/18 13:47:59 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/Attic/sets.c,v 1.45 2002/05/22 17:21:00 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -58,6 +58,7 @@ SetDefine(char *querystr, Oid elemType)
 							 true,		/* returnsSet */
 							 elemType,	/* returnType */
 							 SQLlanguageId,	/* language */
+							 SQLvalidatorId,
 							 querystr,	/* prosrc */
 							 fileName,	/* probin */
 							 false,		/* not aggregate */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5e1d3ecd0dc..2e0295fc844 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -22,7 +22,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/bin/pg_dump/pg_dump.c,v 1.263 2002/05/19 10:08:25 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/bin/pg_dump/pg_dump.c,v 1.264 2002/05/22 17:21:00 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -3108,6 +3108,7 @@ dumpProcLangs(Archive *fout, FuncInfo finfo[], int numFuncs)
 	int			i_lanname;
 	int			i_lanpltrusted;
 	int			i_lanplcallfoid;
+	int			i_lanvalidator = -1;
 	int			i_lancompiler;
 	int			i_lanacl = -1;
 	char	   *lanoid;
@@ -3115,10 +3116,12 @@ dumpProcLangs(Archive *fout, FuncInfo finfo[], int numFuncs)
 	char	   *lancompiler;
 	char	   *lanacl;
 	const char *lanplcallfoid;
+	const char *lanvalidator;
 	const char *((*deps)[]);
 	int			depIdx;
 	int			i,
-				fidx;
+				fidx,
+				vidx = -1;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema("pg_catalog");
@@ -3142,7 +3145,10 @@ dumpProcLangs(Archive *fout, FuncInfo finfo[], int numFuncs)
 	i_lancompiler = PQfnumber(res, "lancompiler");
 	i_oid = PQfnumber(res, "oid");
 	if (fout->remoteVersion >= 70300)
+	{
+		i_lanvalidator = PQfnumber(res, "lanvalidator");
 		i_lanacl = PQfnumber(res, "lanacl");
+	}
 
 	for (i = 0; i < ntups; i++)
 	{
@@ -3151,9 +3157,15 @@ dumpProcLangs(Archive *fout, FuncInfo finfo[], int numFuncs)
 		lanname = PQgetvalue(res, i, i_lanname);
 		lancompiler = PQgetvalue(res, i, i_lancompiler);
 		if (fout->remoteVersion >= 70300)
+		{
+			lanvalidator = PQgetvalue(res, i, i_lanvalidator);
 			lanacl = PQgetvalue(res, i, i_lanacl);
+		}
 		else
-			lanacl = "{=U}";				
+		{
+			lanvalidator = "0";
+			lanacl = "{=U}";
+		}
 
 		fidx = findFuncByOid(finfo, numFuncs, lanplcallfoid);
 		if (fidx < 0)
@@ -3163,6 +3175,17 @@ dumpProcLangs(Archive *fout, FuncInfo finfo[], int numFuncs)
 			exit_nicely();
 		}
 
+		if (strcmp(lanvalidator, "0") != 0)
+		{
+			vidx = findFuncByOid(finfo, numFuncs, lanvalidator);
+			if (vidx < 0)
+			{
+				write_msg(NULL, "validator procedure for procedural language %s not found\n",
+						  lanname);
+				exit_nicely();
+			}
+		}
+
 		/*
 		 * Current theory is to dump PLs iff their underlying functions
 		 * will be dumped (are in a dumpable namespace, or have a non-system
@@ -3178,7 +3201,7 @@ dumpProcLangs(Archive *fout, FuncInfo finfo[], int numFuncs)
 		resetPQExpBuffer(delqry);
 
 		/* Make a dependency to ensure function is dumped first */
-		deps = malloc(sizeof(char *) * 2);
+		deps = malloc(sizeof(char *) * (2 + (strcmp(lanvalidator, "0")!=0) ? 1 : 0));
 		depIdx = 0;
 
 		(*deps)[depIdx++] = strdup(lanplcallfoid);
@@ -3189,8 +3212,15 @@ dumpProcLangs(Archive *fout, FuncInfo finfo[], int numFuncs)
 						  (PQgetvalue(res, i, i_lanpltrusted)[0] == 't') ?
 						  "TRUSTED " : "",
 						  fmtId(lanname, force_quotes));
-		appendPQExpBuffer(defqry, " HANDLER %s;\n",
+		appendPQExpBuffer(defqry, " HANDLER %s",
 						  fmtId(finfo[fidx].proname, force_quotes));
+		if (strcmp(lanvalidator, "0")!=0)
+		{
+			appendPQExpBuffer(defqry, " VALIDATOR %s",
+							  fmtId(finfo[vidx].proname, force_quotes));
+			(*deps)[depIdx++] = strdup(lanvalidator);
+		}
+		appendPQExpBuffer(defqry, ";\n");
 
 		(*deps)[depIdx++] = NULL;		/* End of List */
 
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 4c9b2fd83a3..bd861396210 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: catversion.h,v 1.132 2002/05/18 13:48:00 petere Exp $
+ * $Id: catversion.h,v 1.133 2002/05/22 17:21:01 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200205181
+#define CATALOG_VERSION_NO	200205221
 
 #endif
diff --git a/src/include/catalog/pg_language.h b/src/include/catalog/pg_language.h
index d7f48628e93..e9eec5f4845 100644
--- a/src/include/catalog/pg_language.h
+++ b/src/include/catalog/pg_language.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pg_language.h,v 1.16 2002/02/18 23:11:35 petere Exp $
+ * $Id: pg_language.h,v 1.17 2002/05/22 17:21:01 petere Exp $
  *
  * NOTES
  *	  the genbki.sh script reads this file and generates .bki
@@ -37,6 +37,7 @@ CATALOG(pg_language)
 	bool		lanispl;		/* Is a procedural language */
 	bool		lanpltrusted;	/* PL is trusted */
 	Oid			lanplcallfoid;	/* Call handler for PL */
+	Oid			lanvalidator;	/* optional validation function */
 	text		lancompiler;	/* VARIABLE LENGTH FIELD */
     aclitem		lanacl[1];		/* Access privileges */
 } FormData_pg_language;
@@ -52,26 +53,27 @@ typedef FormData_pg_language *Form_pg_language;
  *		compiler constants for pg_language
  * ----------------
  */
-#define Natts_pg_language				6
+#define Natts_pg_language				7
 #define Anum_pg_language_lanname		1
 #define Anum_pg_language_lanispl		2
 #define Anum_pg_language_lanpltrusted		3
 #define Anum_pg_language_lanplcallfoid		4
-#define Anum_pg_language_lancompiler		5
-#define Anum_pg_language_lanacl			6
+#define Anum_pg_language_lanvalidator		5
+#define Anum_pg_language_lancompiler		6
+#define Anum_pg_language_lanacl			7
 
 /* ----------------
  *		initial contents of pg_language
  * ----------------
  */
 
-DATA(insert OID = 12 ( "internal" f f 0 "n/a" _null_ ));
+DATA(insert OID = 12 ( "internal" f f 0 2246 "n/a" _null_ ));
 DESCR("Built-in functions");
 #define INTERNALlanguageId 12
-DATA(insert OID = 13 ( "c" f f 0 "/bin/cc" _null_ ));
+DATA(insert OID = 13 ( "c" f f 0 2247 "/bin/cc" _null_ ));
 DESCR("Dynamically-loaded C functions");
 #define ClanguageId 13
-DATA(insert OID = 14 ( "sql" f t 0 "postgres" _null_ ));
+DATA(insert OID = 14 ( "sql" f t 0 2248 "postgres" _null_ ));
 DESCR("SQL-language functions");
 #define SQLlanguageId 14
 
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index c7f829c3153..2cc1f8aac1f 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pg_proc.h,v 1.238 2002/05/20 23:51:43 tgl Exp $
+ * $Id: pg_proc.h,v 1.239 2002/05/22 17:21:01 petere Exp $
  *
  * NOTES
  *	  The script catalog/genbki.sh reads this file and generates .bki
@@ -2962,6 +2962,13 @@ DESCR("(internal)");
 DATA(insert OID = 2221 (  regtypeout		PGNSP PGUID 12 f f f t f s 1   23 "0" 100 0 0 100	regtypeout - _null_ ));
 DESCR("(internal)");
 
+DATA(insert OID = 2246 ( fmgr_internal_validator PGNSP PGUID 12 f f f t f s 1 26 "23" 100 0 0 100 fmgr_internal_validator - _null_ ));
+DESCR("(internal)");
+DATA(insert OID = 2247 ( fmgr_c_validator	PGNSP PGUID 12 f f f t f s 1   26 "23" 100 0 0 100	fmgr_c_validator - _null_ ));
+DESCR("(internal)");
+DATA(insert OID = 2248 ( fmgr_sql_validator	PGNSP PGUID 12 f f f t f s 1   26 "23" 100 0 0 100	fmgr_sql_validator - _null_ ));
+DESCR("(internal)");
+#define SQLvalidatorId 2248
 
 /*
  * Symbolic values for provolatile column: these indicate whether the result
@@ -2985,6 +2992,7 @@ extern Oid ProcedureCreate(const char *procedureName,
 				bool returnsSet,
 				Oid returnType,
 				Oid languageObjectId,
+				Oid languageValidator,
 				const char *prosrc,
 				const char *probin,
 				bool isAgg,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3466e125982..f7ea60ac802 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parsenodes.h,v 1.178 2002/05/17 18:32:52 petere Exp $
+ * $Id: parsenodes.h,v 1.179 2002/05/22 17:21:01 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -990,6 +990,7 @@ typedef struct CreatePLangStmt
 	NodeTag		type;
 	char	   *plname;			/* PL name */
 	List	   *plhandler;		/* PL call handler function (qual. name) */
+	List	   *plvalidator;	/* optional validator function (qual. name) */
 	char	   *plcompiler;		/* lancompiler text */
 	bool		pltrusted;		/* PL is trusted */
 } CreatePLangStmt;
diff --git a/src/test/regress/input/create_function_1.source b/src/test/regress/input/create_function_1.source
index 6d91674cd56..14ae6ff2bbb 100644
--- a/src/test/regress/input/create_function_1.source
+++ b/src/test/regress/input/create_function_1.source
@@ -42,3 +42,28 @@ CREATE FUNCTION set_ttdummy (int4)
         AS '@abs_builddir@/regress@DLSUFFIX@'
         LANGUAGE 'C';
 
+-- Things that shouldn't work:
+
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE sql
+    AS 'SELECT ''not an integer'';';
+
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE sql
+    AS 'not even SQL';
+
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE sql
+    AS 'SELECT 1, 2, 3;';
+
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE sql
+    AS 'SELECT $2;';
+
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE sql
+    AS 'a', 'b';
+
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE c
+    AS 'nosuchfile';
+
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE c
+    AS '@abs_builddir@/regress@DLSUFFIX@', 'nosuchsymbol';
+
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE internal
+    AS 'nosuch';
diff --git a/src/test/regress/output/create_function_1.source b/src/test/regress/output/create_function_1.source
index ded580ee7c4..2f59a693319 100644
--- a/src/test/regress/output/create_function_1.source
+++ b/src/test/regress/output/create_function_1.source
@@ -34,3 +34,28 @@ CREATE FUNCTION set_ttdummy (int4)
         RETURNS int4
         AS '@abs_builddir@/regress@DLSUFFIX@'
         LANGUAGE 'C';
+-- Things that shouldn't work:
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE sql
+    AS 'SELECT ''not an integer'';';
+ERROR:  return type mismatch in function: declared to return integer, returns "unknown"
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE sql
+    AS 'not even SQL';
+ERROR:  parser: parse error at or near "not"
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE sql
+    AS 'SELECT 1, 2, 3;';
+ERROR:  function declared to return integer returns multiple columns in final SELECT
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE sql
+    AS 'SELECT $2;';
+ERROR:  Parameter '$2' is out of range
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE sql
+    AS 'a', 'b';
+ERROR:  CREATE FUNCTION: only one AS item needed for sql language
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE c
+    AS 'nosuchfile';
+ERROR:  stat failed on file 'nosuchfile': No such file or directory
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE c
+    AS '@abs_builddir@/regress@DLSUFFIX@', 'nosuchsymbol';
+ERROR:  Can't find function nosuchsymbol in file @abs_builddir@/regress@DLSUFFIX@
+CREATE FUNCTION test1 (int) RETURNS int LANGUAGE internal
+    AS 'nosuch';
+ERROR:  there is no built-in function named "nosuch"
-- 
GitLab