Fix confusion about the return rowtype of SQL-language procedures.
authorTom Lane <[email protected]>
Tue, 12 Mar 2024 22:16:10 +0000 (18:16 -0400)
committerTom Lane <[email protected]>
Tue, 12 Mar 2024 22:16:10 +0000 (18:16 -0400)
There is a very ancient hack in check_sql_fn_retval that allows a
single SELECT targetlist entry of composite type to be taken as
supplying all the output columns of a function returning composite.
(This is grotty and fundamentally ambiguous, but it's really hard
to do nested composite-returning functions without it.)

As far as I know, that doesn't cause any problems in ordinary
functions.  It's disastrous for procedures however.  All procedures
that have any output parameters are labeled with prorettype RECORD,
and the CALL code expects it will get back a record with one column
per output parameter, regardless of whether any of those parameters
is composite.  Doing something else leads to an assertion failure
or core dump.

This is simple enough to fix: we just need to not apply that rule
when considering procedures.  However, that requires adding another
argument to check_sql_fn_retval, which at least in principle might be
getting called by external callers.  Therefore, in the back branches
convert check_sql_fn_retval into an ABI-preserving wrapper around a
new function check_sql_fn_retval_ext.

Per report from Yahor Yuzefovich.  This has been broken since we
implemented procedures, so back-patch to all supported branches.

Discussion: https://postgr.es/m/CABz5gWHSjj2df6uG0NRiDhZ_Uz=Y8t0FJP-_SVSsRsnrQT76Gg@mail.gmail.com

src/backend/catalog/pg_proc.c
src/backend/executor/functions.c
src/backend/optimizer/util/clauses.c
src/include/executor/functions.h
src/test/regress/expected/create_procedure.out
src/test/regress/sql/create_procedure.sql

index 98c47a5db6b82eb63df32869930df09f8942d014..821c2bef444085f8a19c826849cad747f80e6583 100644 (file)
@@ -967,9 +967,10 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
 
            (void) get_func_result_type(funcoid, &rettype, &rettupdesc);
 
-           (void) check_sql_fn_retval(querytree_list,
-                                      rettype, rettupdesc,
-                                      false, NULL);
+           (void) check_sql_fn_retval_ext(querytree_list,
+                                          rettype, rettupdesc,
+                                          proc->prokind,
+                                          false, NULL);
        }
 
        error_context_stack = sqlerrcontext.previous;
index e8ab5be0bca867b6eebb1809f78a6870c5859838..2ec5c14ae6eab03de046c3829a35577ca566db32 100644 (file)
@@ -750,11 +750,12 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
     * the rowtype column into multiple columns, since we have no way to
     * notify the caller that it should do that.)
     */
-   fcache->returnsTuple = check_sql_fn_retval(queryTree_list,
-                                              rettype,
-                                              rettupdesc,
-                                              false,
-                                              &resulttlist);
+   fcache->returnsTuple = check_sql_fn_retval_ext(queryTree_list,
+                                                  rettype,
+                                                  rettupdesc,
+                                                  procedureStruct->prokind,
+                                                  false,
+                                                  &resulttlist);
 
    /*
     * Construct a JunkFilter we can use to coerce the returned rowtype to the
@@ -1615,6 +1616,21 @@ check_sql_fn_retval(List *queryTreeLists,
                    Oid rettype, TupleDesc rettupdesc,
                    bool insertDroppedCols,
                    List **resultTargetList)
+{
+   /* Wrapper function to preserve ABI compatibility in released branches */
+   return check_sql_fn_retval_ext(queryTreeLists,
+                                  rettype, rettupdesc,
+                                  PROKIND_FUNCTION,
+                                  insertDroppedCols,
+                                  resultTargetList);
+}
+
+bool
+check_sql_fn_retval_ext(List *queryTreeLists,
+                       Oid rettype, TupleDesc rettupdesc,
+                       char prokind,
+                       bool insertDroppedCols,
+                       List **resultTargetList)
 {
    bool        is_tuple_result = false;
    Query      *parse;
@@ -1632,7 +1648,7 @@ check_sql_fn_retval(List *queryTreeLists,
 
    /*
     * If it's declared to return VOID, we don't care what's in the function.
-    * (This takes care of the procedure case, as well.)
+    * (This takes care of procedures with no output parameters, as well.)
     */
    if (rettype == VOIDOID)
        return false;
@@ -1787,8 +1803,13 @@ check_sql_fn_retval(List *queryTreeLists,
         * or not the record type really matches.  For the moment we rely on
         * runtime type checking to catch any discrepancy, but it'd be nice to
         * do better at parse time.
+        *
+        * We must *not* do this for a procedure, however.  Procedures with
+        * output parameter(s) have rettype RECORD, and the CALL code expects
+        * to get results corresponding to the list of output parameters, even
+        * when there's just one parameter that's composite.
         */
-       if (tlistlen == 1)
+       if (tlistlen == 1 && prokind != PROKIND_PROCEDURE)
        {
            TargetEntry *tle = (TargetEntry *) linitial(tlist);
 
index bd332ab052e86aef75665d23c0e1058ccfe17302..a076fa9646f8b5c44186398d2dd976a9e0577fe9 100644 (file)
@@ -4574,9 +4574,10 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
     * needed; that's probably not important, but let's be careful.
     */
    querytree_list = list_make1(querytree);
-   if (check_sql_fn_retval(list_make1(querytree_list),
-                           result_type, rettupdesc,
-                           false, NULL))
+   if (check_sql_fn_retval_ext(list_make1(querytree_list),
+                               result_type, rettupdesc,
+                               funcform->prokind,
+                               false, NULL))
        goto fail;              /* reject whole-tuple-result cases */
 
    /*
@@ -5125,9 +5126,10 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
     * shows it's returning a whole tuple result; otherwise what it's
     * returning is a single composite column which is not what we need.
     */
-   if (!check_sql_fn_retval(list_make1(querytree_list),
-                            fexpr->funcresulttype, rettupdesc,
-                            true, NULL) &&
+   if (!check_sql_fn_retval_ext(list_make1(querytree_list),
+                                fexpr->funcresulttype, rettupdesc,
+                                funcform->prokind,
+                                true, NULL) &&
        (functypclass == TYPEFUNC_COMPOSITE ||
         functypclass == TYPEFUNC_COMPOSITE_DOMAIN ||
         functypclass == TYPEFUNC_RECORD))
index a9e14e2fbed24007ea71b741eb0c0d2364f01e95..226f146d9d27bfe67d7a11c32d0ce66ee618a9aa 100644 (file)
@@ -50,6 +50,12 @@ extern bool check_sql_fn_retval(List *queryTreeLists,
                                bool insertDroppedCols,
                                List **resultTargetList);
 
+extern bool check_sql_fn_retval_ext(List *queryTreeLists,
+                                   Oid rettype, TupleDesc rettupdesc,
+                                   char prokind,
+                                   bool insertDroppedCols,
+                                   List **resultTargetList);
+
 extern DestReceiver *CreateSQLFunctionDestReceiver(void);
 
 #endif                         /* FUNCTIONS_H */
index 46c827f9791c6b8e0803f15a2403fd76cdb2fe6d..f3eabc0a6a312f425b35f267ee3d9dde67b3b613 100644 (file)
@@ -148,7 +148,19 @@ CALL ptest4a(a, b);  -- error, not supported
 $$;
 ERROR:  calling procedures with output arguments is not supported in SQL functions
 CONTEXT:  SQL function "ptest4b"
-DROP PROCEDURE ptest4a;
+-- we used to get confused by a single output argument that is composite
+CREATE PROCEDURE ptest4c(INOUT comp int8_tbl)
+LANGUAGE SQL
+AS $$
+SELECT ROW(1, 2);
+$$;
+CALL ptest4c(NULL);
+ comp  
+-------
+ (1,2)
+(1 row)
+
+DROP PROCEDURE ptest4a, ptest4c;
 -- named and default parameters
 CREATE OR REPLACE PROCEDURE ptest5(a int, b text, c int default 100)
 LANGUAGE SQL
index 75cc0fcf2a6ce43bb7d6aff88f3e41b9a603d803..50a4d881f9441e3a8aab6596e7661da45d2de250 100644 (file)
@@ -90,7 +90,16 @@ AS $$
 CALL ptest4a(a, b);  -- error, not supported
 $$;
 
-DROP PROCEDURE ptest4a;
+-- we used to get confused by a single output argument that is composite
+CREATE PROCEDURE ptest4c(INOUT comp int8_tbl)
+LANGUAGE SQL
+AS $$
+SELECT ROW(1, 2);
+$$;
+
+CALL ptest4c(NULL);
+
+DROP PROCEDURE ptest4a, ptest4c;
 
 
 -- named and default parameters