From 9a527f18488cb336b39ce86520dd25e438a59144 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 2 Apr 2007 18:49:29 +0000
Subject: [PATCH] Fix check_sql_fn_retval to allow the case where a SQL
 function declared to return void ends with a SELECT, if that SELECT has a
 single result that is also of type void.  Without this, it's hard to write a
 void function that calls another void function.  Per gripe from Peter.

Back-patch as far as 8.0.
---
 src/backend/executor/functions.c | 46 +++++++++++++++-----------------
 1 file changed, 22 insertions(+), 24 deletions(-)

diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 4781bd903ab..1e1d450be57 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.113 2007/04/02 03:49:38 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.114 2007/04/02 18:49:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -849,9 +849,9 @@ ShutdownSQLFunction(Datum arg)
  * ANYELEMENT as rettype.  (This means we can't check the type during function
  * definition of a polymorphic function.)
  *
- * The return value is true if the function returns the entire tuple result
- * of its final SELECT, and false otherwise.  Note that because we allow
- * "SELECT rowtype_expression", this may be false even when the declared
+ * This function returns true if the sql function returns the entire tuple
+ * result of its final SELECT, and false otherwise.  Note that because we
+ * allow "SELECT rowtype_expression", this may be false even when the declared
  * function return type is a rowtype.
  *
  * If junkFilter isn't NULL, then *junkFilter is set to a JunkFilter defined
@@ -864,7 +864,6 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
 					JunkFilter **junkFilter)
 {
 	Query	   *parse;
-	bool		isSelect;
 	List	   *tlist;
 	ListCell   *tlistitem;
 	int			tlistlen;
@@ -890,32 +889,30 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
 	parse = (Query *) lfirst(list_tail(queryTreeList));
 
 	/*
-	 * Note: eventually replace this with QueryReturnsTuples?  We'd need a
-	 * more general method of determining the output type, though.
-	 */
-	isSelect = (parse->commandType == CMD_SELECT && parse->into == NULL);
-
-	/*
-	 * The last query must be a SELECT if and only if return type isn't VOID.
+	 * If the last query isn't a SELECT, the return type must be VOID.
+	 *
+	 * Note: eventually replace this test with QueryReturnsTuples?  We'd need
+	 * a more general method of determining the output type, though.
 	 */
-	if (rettype == VOIDOID)
+	if (!(parse->commandType == CMD_SELECT && parse->into == NULL))
 	{
-		if (isSelect)
+		if (rettype != VOIDOID)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
 			 errmsg("return type mismatch in function declared to return %s",
 					format_type_be(rettype)),
-			 errdetail("Function's final statement must not be a SELECT.")));
+				 errdetail("Function's final statement must be a SELECT.")));
 		return false;
 	}
 
-	/* by here, the function is declared to return some type */
-	if (!isSelect)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
-			 errmsg("return type mismatch in function declared to return %s",
-					format_type_be(rettype)),
-				 errdetail("Function's final statement must be a SELECT.")));
+	/*
+	 * OK, it's a SELECT, so it must return something matching the declared
+	 * type.  (We used to insist that the declared type not be VOID in this
+	 * case, but that makes it hard to write a void function that exits
+	 * after calling another void function.  Instead, we insist that the
+	 * SELECT return void ... so void is treated as if it were a scalar type
+	 * below.)
+	 */
 
 	/*
 	 * Count the non-junk entries in the result targetlist.
@@ -927,10 +924,11 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
 
 	if (fn_typtype == TYPTYPE_BASE ||
 		fn_typtype == TYPTYPE_DOMAIN ||
-		fn_typtype == TYPTYPE_ENUM)
+		fn_typtype == TYPTYPE_ENUM ||
+		rettype == VOIDOID)
 	{
 		/*
-		 * For base-type returns, the target list should have exactly one
+		 * For scalar-type returns, the target list should have exactly one
 		 * entry, and its type should agree with what the user declared. (As
 		 * of Postgres 7.2, we accept binary-compatible types too.)
 		 */
-- 
GitLab