Fix check_sql_fn_retval to allow the case where a SQL function declared to
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 2 Apr 2007 18:49:49 +0000 (18:49 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 2 Apr 2007 18:49:49 +0000 (18:49 +0000)
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

index 5203671f8044faf2a439ecaa40e1d97ad7454701..06509f8065fe391e10489a29b59984b722492819 100644 (file)
@@ -848,9 +848,9 @@ ShutdownSQLFunction(Datum arg)
  * 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
@@ -863,7 +863,6 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
                                        JunkFilter **junkFilter)
 {
        Query      *parse;
-       int                     cmd;
        List       *tlist;
        ListCell   *tlistitem;
        int                     tlistlen;
@@ -892,46 +891,45 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
        /* find the final query */
        parse = (Query *) lfirst(list_tail(queryTreeList));
 
-       cmd = parse->commandType;
-       tlist = parse->targetList;
-
        /*
-        * 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.
         */
-       if (rettype == VOIDOID)
+       if (!(parse->commandType == CMD_SELECT && parse->into == NULL))
        {
-               if (cmd == CMD_SELECT)
+               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 (cmd != CMD_SELECT)
-               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.
         */
+       tlist = parse->targetList;
        tlistlen = ExecCleanTargetListLength(tlist);
 
        typerelid = typeidTypeRelid(rettype);
 
-       if (fn_typtype == 'b' || fn_typtype == 'd')
+       if (fn_typtype == 'b' || fn_typtype == 'd' ||
+               rettype == VOIDOID)
        {
                /* Shouldn't have a typerelid */
                Assert(typerelid == InvalidOid);
 
                /*
-                * 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.)
                 */