Fix handling of polymorphic output arguments for procedures.
authorTom Lane <[email protected]>
Wed, 15 May 2024 00:19:20 +0000 (20:19 -0400)
committerTom Lane <[email protected]>
Wed, 15 May 2024 00:19:20 +0000 (20:19 -0400)
Most of the infrastructure for procedure arguments was already
okay with polymorphic output arguments, but it turns out that
CallStmtResultDesc() was a few bricks shy of a load here.  It thought
all it needed to do was call build_function_result_tupdesc_t, but
that function specifically disclaims responsibility for resolving
polymorphic arguments.  Failing to handle that doesn't seem to be
a problem for CALL in plpgsql, but CALL from plain SQL would get
errors like "cannot display a value of type anyelement", or even
crash outright.

In v14 and later we can simply examine the exposed types of the
CallStmt.outargs nodes to get the right type OIDs.  But it's a lot
more complicated to fix in v12/v13, because those versions don't
have CallStmt.outargs, nor do they do expand_function_arguments
until ExecuteCallStmt runs.  We have to duplicatively run
expand_function_arguments, and then re-determine which elements
of the args list are output arguments.

Per bug #18463 from Drew Kimball.  Back-patch to all supported
versions, since it's busted in all of them.

Discussion: https://postgr.es/m/18463-f8cd77e12564d8a2@postgresql.org

src/backend/commands/functioncmds.c
src/pl/plpgsql/src/expected/plpgsql_call.out
src/pl/plpgsql/src/sql/plpgsql_call.sql
src/test/regress/expected/create_procedure.out
src/test/regress/sql/create_procedure.sql

index 9cf3fe8275e2025c2f1bc58163960db8148fcd59..6593fd7d811521a236cdf831886610738bd8aa98 100644 (file)
@@ -52,6 +52,7 @@
 #include "executor/functions.h"
 #include "funcapi.h"
 #include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
 #include "parser/analyze.h"
 #include "parser/parse_coerce.h"
@@ -2364,5 +2365,32 @@ CallStmtResultDesc(CallStmt *stmt)
 
    ReleaseSysCache(tuple);
 
+   /*
+    * The result of build_function_result_tupdesc_t has the right column
+    * names, but it just has the declared output argument types, which is the
+    * wrong thing in polymorphic cases.  Get the correct types by examining
+    * stmt->outargs.  We intentionally keep the atttypmod as -1 and the
+    * attcollation as the type's default, since that's always the appropriate
+    * thing for function outputs; there's no point in considering any
+    * additional info available from outargs.  Note that tupdesc is null if
+    * there are no outargs.
+    */
+   if (tupdesc)
+   {
+       Assert(tupdesc->natts == list_length(stmt->outargs));
+       for (int i = 0; i < tupdesc->natts; i++)
+       {
+           Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+           Node       *outarg = (Node *) list_nth(stmt->outargs, i);
+
+           TupleDescInitEntry(tupdesc,
+                              i + 1,
+                              NameStr(att->attname),
+                              exprType(outarg),
+                              -1,
+                              0);
+       }
+   }
+
    return tupdesc;
 }
index ab16416c1e237ed313f4375c7394e480bdc6a437..17235fca9120195f42212f3376eec5455c3af254 100644 (file)
@@ -409,6 +409,40 @@ END
 $$;
 NOTICE:  a: <NULL>, b: {30,7}
 NOTICE:  _a: 37, _b: 30, _c: 7
+-- polymorphic OUT arguments
+CREATE PROCEDURE test_proc12(a anyelement, OUT b anyelement, OUT c anyarray)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  RAISE NOTICE 'a: %', a;
+  b := a;
+  c := array[a];
+END;
+$$;
+DO $$
+DECLARE _a int; _b int; _c int[];
+BEGIN
+  _a := 10;
+  CALL test_proc12(_a, _b, _c);
+  RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+END
+$$;
+NOTICE:  a: 10
+NOTICE:  _a: 10, _b: 10, _c: {10}
+DO $$
+DECLARE _a int; _b int; _c text[];
+BEGIN
+  _a := 10;
+  CALL test_proc12(_a, _b, _c);  -- error
+  RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+END
+$$;
+ERROR:  procedure test_proc12(integer, integer, text[]) does not exist
+LINE 1: CALL test_proc12(_a, _b, _c)
+             ^
+HINT:  No procedure matches the given name and argument types. You might need to add explicit type casts.
+QUERY:  CALL test_proc12(_a, _b, _c)
+CONTEXT:  PL/pgSQL function inline_code_block line 5 at CALL
 -- transition variable assignment
 TRUNCATE test1;
 CREATE FUNCTION triggerfunc1() RETURNS trigger
index 8efc8e823ac3a2c8778648c147730f5ca36a99f3..869d021a0754816d1d1877e920a71431df1b5a65 100644 (file)
@@ -375,6 +375,36 @@ BEGIN
 END
 $$;
 
+-- polymorphic OUT arguments
+
+CREATE PROCEDURE test_proc12(a anyelement, OUT b anyelement, OUT c anyarray)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  RAISE NOTICE 'a: %', a;
+  b := a;
+  c := array[a];
+END;
+$$;
+
+DO $$
+DECLARE _a int; _b int; _c int[];
+BEGIN
+  _a := 10;
+  CALL test_proc12(_a, _b, _c);
+  RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+END
+$$;
+
+DO $$
+DECLARE _a int; _b int; _c text[];
+BEGIN
+  _a := 10;
+  CALL test_proc12(_a, _b, _c);  -- error
+  RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+END
+$$;
+
 
 -- transition variable assignment
 
index 6ab09d7ec89dfaa8285cb00b4785a660397324bf..8a32fd960c69ab206c5ff6b3f3230caeeaffd4f5 100644 (file)
@@ -193,6 +193,40 @@ AS $$
 SELECT NULL::int;
 $$;
 CALL ptest6(1, 2);
+CREATE PROCEDURE ptest6a(inout a anyelement, out b anyelement)
+LANGUAGE SQL
+AS $$
+SELECT $1, $1;
+$$;
+CALL ptest6a(1, null);
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+CALL ptest6a(1.1, null);
+  a  |  b  
+-----+-----
+ 1.1 | 1.1
+(1 row)
+
+CREATE PROCEDURE ptest6b(a anyelement, out b anyelement, out c anyarray)
+LANGUAGE SQL
+AS $$
+SELECT $1, array[$1];
+$$;
+CALL ptest6b(1, null, null);
+ b |  c  
+---+-----
+ 1 | {1}
+(1 row)
+
+CALL ptest6b(1.1, null, null);
+  b  |   c   
+-----+-------
+ 1.1 | {1.1}
+(1 row)
+
 -- collation assignment
 CREATE PROCEDURE ptest7(a text, b text)
 LANGUAGE SQL
index 012cdf36283facdb52c8d4d55ae83c755ba51520..b10cf71ca485e9785f231f41c3fe6de13b874422 100644 (file)
@@ -131,6 +131,24 @@ $$;
 
 CALL ptest6(1, 2);
 
+CREATE PROCEDURE ptest6a(inout a anyelement, out b anyelement)
+LANGUAGE SQL
+AS $$
+SELECT $1, $1;
+$$;
+
+CALL ptest6a(1, null);
+CALL ptest6a(1.1, null);
+
+CREATE PROCEDURE ptest6b(a anyelement, out b anyelement, out c anyarray)
+LANGUAGE SQL
+AS $$
+SELECT $1, array[$1];
+$$;
+
+CALL ptest6b(1, null, null);
+CALL ptest6b(1.1, null, null);
+
 
 -- collation assignment