]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Improve plpgsql's ability to handle arguments declared as RECORD.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 16 Sep 2022 17:23:01 +0000 (13:23 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 16 Sep 2022 17:23:01 +0000 (13:23 -0400)
Treat arguments declared as RECORD as if that were a polymorphic type
(which it is, sort of), in that we substitute the actual argument type
while forming the function cache lookup key.  This allows the specific
composite type to be known in some cases where it was not before,
at the cost of making a separate function cache entry for each named
composite type that's passed to the function during a session.  The
particular symptom discussed in bug #17610 could be solved in other
more-efficient ways, but only at the cost of considerable development
work, and there are other cases where we'd still fail without this.

Per bug #17610 from Martin JurĨa.  Back-patch to v11 where we first
allowed plpgsql functions to be declared as taking type RECORD.

Discussion: https://postgr.es/m/17610-fb1eef75bf6c2364@postgresql.org

src/pl/plpgsql/src/expected/plpgsql_record.out
src/pl/plpgsql/src/pl_comp.c
src/pl/plpgsql/src/sql/plpgsql_record.sql

index cf6089cbb219262a44be0a5f2ffe623e60d29b4f..cccac60e4a6658ed0b2ea6e99472f6069d29cb7b 100644 (file)
@@ -2,6 +2,7 @@
 -- Tests for PL/pgSQL handling of composite (record) variables
 --
 create type two_int4s as (f1 int4, f2 int4);
+create type more_int4s as (f0 text, f1 int4, f2 int4);
 create type two_int8s as (q1 int8, q2 int8);
 -- base-case return of a composite type
 create function retc(int) returns two_int8s language plpgsql as
@@ -343,6 +344,18 @@ select getf1(row(1,2));
      1
 (1 row)
 
+select getf1(row(1,2)::two_int4s);
+ getf1 
+-------
+     1
+(1 row)
+
+select getf1(row('foo',123,456)::more_int4s);
+ getf1 
+-------
+   123
+(1 row)
+
 -- a CLOBBER_CACHE_ALWAYS build will report this error with a different
 -- context stack than other builds, so suppress context output
 \set SHOW_CONTEXT never
@@ -355,6 +368,28 @@ select getf1(row(1,2));
      1
 (1 row)
 
+-- this seemingly-equivalent case behaves a bit differently,
+-- because the core parser's handling of $N symbols is simplistic
+create function getf2(record) returns int language plpgsql as
+$$ begin return $1.f2; end $$;
+select getf2(row(1,2));  -- ideally would work, but does not
+ERROR:  could not identify column "f2" in record data type
+LINE 1: SELECT $1.f2
+               ^
+QUERY:  SELECT $1.f2
+CONTEXT:  PL/pgSQL function getf2(record) line 1 at RETURN
+select getf2(row(1,2)::two_int4s);
+ getf2 
+-------
+     2
+(1 row)
+
+select getf2(row('foo',123,456)::more_int4s);
+ getf2 
+-------
+   456
+(1 row)
+
 -- check behavior when assignment to FOR-loop variable requires coercion
 do $$
 declare r two_int8s;
index 7e49d59a2d0dbd41951c1625d44d760c45c746ef..755889bb622d837ad05edc34ce6f20ba4120a897 100644 (file)
@@ -2455,9 +2455,15 @@ compute_function_hashkey(FunctionCallInfo fcinfo,
 
 /*
  * This is the same as the standard resolve_polymorphic_argtypes() function,
- * but with a special case for validation: assume that polymorphic arguments
- * are integer, integer-array or integer-range.  Also, we go ahead and report
- * the error if we can't resolve the types.
+ * except that:
+ * 1. We go ahead and report the error if we can't resolve the types.
+ * 2. We treat RECORD-type input arguments (not output arguments) as if
+ *    they were polymorphic, replacing their types with the actual input
+ *    types if we can determine those.  This allows us to create a separate
+ *    function cache entry for each named composite type passed to such an
+ *    argument.
+ * 3. In validation mode, we have no inputs to look at, so assume that
+ *    polymorphic arguments are integer, integer-array or integer-range.
  */
 static void
 plpgsql_resolve_polymorphic_argtypes(int numargs,
@@ -2469,6 +2475,8 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
 
        if (!forValidator)
        {
+               int                     inargno;
+
                /* normal case, pass to standard routine */
                if (!resolve_polymorphic_argtypes(numargs, argtypes, argmodes,
                                                                                  call_expr))
@@ -2477,10 +2485,28 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
                                         errmsg("could not determine actual argument "
                                                        "type for polymorphic function \"%s\"",
                                                        proname)));
+               /* also, treat RECORD inputs (but not outputs) as polymorphic */
+               inargno = 0;
+               for (i = 0; i < numargs; i++)
+               {
+                       char            argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
+
+                       if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+                               continue;
+                       if (argtypes[i] == RECORDOID || argtypes[i] == RECORDARRAYOID)
+                       {
+                               Oid                     resolvedtype = get_call_expr_argtype(call_expr,
+                                                                                                                                inargno);
+
+                               if (OidIsValid(resolvedtype))
+                                       argtypes[i] = resolvedtype;
+                       }
+                       inargno++;
+               }
        }
        else
        {
-               /* special validation case */
+               /* special validation case (no need to do anything for RECORD) */
                for (i = 0; i < numargs; i++)
                {
                        switch (argtypes[i])
index 128846e6108231a8a22116da60e03a41e188bf86..0bc8e63c088c57b517b01a0fb087d3eb7ba9675c 100644 (file)
@@ -3,6 +3,7 @@
 --
 
 create type two_int4s as (f1 int4, f2 int4);
+create type more_int4s as (f0 text, f1 int4, f2 int4);
 create type two_int8s as (q1 int8, q2 int8);
 
 -- base-case return of a composite type
@@ -215,6 +216,8 @@ create function getf1(x record) returns int language plpgsql as
 $$ begin return x.f1; end $$;
 select getf1(1);
 select getf1(row(1,2));
+select getf1(row(1,2)::two_int4s);
+select getf1(row('foo',123,456)::more_int4s);
 -- a CLOBBER_CACHE_ALWAYS build will report this error with a different
 -- context stack than other builds, so suppress context output
 \set SHOW_CONTEXT never
@@ -222,6 +225,14 @@ select getf1(row(1,2)::two_int8s);
 \set SHOW_CONTEXT errors
 select getf1(row(1,2));
 
+-- this seemingly-equivalent case behaves a bit differently,
+-- because the core parser's handling of $N symbols is simplistic
+create function getf2(record) returns int language plpgsql as
+$$ begin return $1.f2; end $$;
+select getf2(row(1,2));  -- ideally would work, but does not
+select getf2(row(1,2)::two_int4s);
+select getf2(row('foo',123,456)::more_int4s);
+
 -- check behavior when assignment to FOR-loop variable requires coercion
 do $$
 declare r two_int8s;