Skip to content

Commit b3b88d7

Browse files
committed
Improve plpgsql's ability to handle arguments declared as RECORD.
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
1 parent 87fd3c9 commit b3b88d7

File tree

3 files changed

+76
-4
lines changed

3 files changed

+76
-4
lines changed

src/pl/plpgsql/src/expected/plpgsql_record.out

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
-- Tests for PL/pgSQL handling of composite (record) variables
33
--
44
create type two_int4s as (f1 int4, f2 int4);
5+
create type more_int4s as (f0 text, f1 int4, f2 int4);
56
create type two_int8s as (q1 int8, q2 int8);
67
-- base-case return of a composite type
78
create function retc(int) returns two_int8s language plpgsql as
@@ -343,6 +344,18 @@ select getf1(row(1,2));
343344
1
344345
(1 row)
345346

347+
select getf1(row(1,2)::two_int4s);
348+
getf1
349+
-------
350+
1
351+
(1 row)
352+
353+
select getf1(row('foo',123,456)::more_int4s);
354+
getf1
355+
-------
356+
123
357+
(1 row)
358+
346359
-- a CLOBBER_CACHE_ALWAYS build will report this error with a different
347360
-- context stack than other builds, so suppress context output
348361
\set SHOW_CONTEXT never
@@ -355,6 +368,28 @@ select getf1(row(1,2));
355368
1
356369
(1 row)
357370

371+
-- this seemingly-equivalent case behaves a bit differently,
372+
-- because the core parser's handling of $N symbols is simplistic
373+
create function getf2(record) returns int language plpgsql as
374+
$$ begin return $1.f2; end $$;
375+
select getf2(row(1,2)); -- ideally would work, but does not
376+
ERROR: could not identify column "f2" in record data type
377+
LINE 1: SELECT $1.f2
378+
^
379+
QUERY: SELECT $1.f2
380+
CONTEXT: PL/pgSQL function getf2(record) line 1 at RETURN
381+
select getf2(row(1,2)::two_int4s);
382+
getf2
383+
-------
384+
2
385+
(1 row)
386+
387+
select getf2(row('foo',123,456)::more_int4s);
388+
getf2
389+
-------
390+
456
391+
(1 row)
392+
358393
-- check behavior when assignment to FOR-loop variable requires coercion
359394
do $$
360395
declare r two_int8s;

src/pl/plpgsql/src/pl_comp.c

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2464,9 +2464,15 @@ compute_function_hashkey(FunctionCallInfo fcinfo,
24642464

24652465
/*
24662466
* This is the same as the standard resolve_polymorphic_argtypes() function,
2467-
* but with a special case for validation: assume that polymorphic arguments
2468-
* are integer, integer-array or integer-range. Also, we go ahead and report
2469-
* the error if we can't resolve the types.
2467+
* except that:
2468+
* 1. We go ahead and report the error if we can't resolve the types.
2469+
* 2. We treat RECORD-type input arguments (not output arguments) as if
2470+
* they were polymorphic, replacing their types with the actual input
2471+
* types if we can determine those. This allows us to create a separate
2472+
* function cache entry for each named composite type passed to such an
2473+
* argument.
2474+
* 3. In validation mode, we have no inputs to look at, so assume that
2475+
* polymorphic arguments are integer, integer-array or integer-range.
24702476
*/
24712477
static void
24722478
plpgsql_resolve_polymorphic_argtypes(int numargs,
@@ -2478,6 +2484,8 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
24782484

24792485
if (!forValidator)
24802486
{
2487+
int inargno;
2488+
24812489
/* normal case, pass to standard routine */
24822490
if (!resolve_polymorphic_argtypes(numargs, argtypes, argmodes,
24832491
call_expr))
@@ -2486,10 +2494,28 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
24862494
errmsg("could not determine actual argument "
24872495
"type for polymorphic function \"%s\"",
24882496
proname)));
2497+
/* also, treat RECORD inputs (but not outputs) as polymorphic */
2498+
inargno = 0;
2499+
for (i = 0; i < numargs; i++)
2500+
{
2501+
char argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
2502+
2503+
if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
2504+
continue;
2505+
if (argtypes[i] == RECORDOID || argtypes[i] == RECORDARRAYOID)
2506+
{
2507+
Oid resolvedtype = get_call_expr_argtype(call_expr,
2508+
inargno);
2509+
2510+
if (OidIsValid(resolvedtype))
2511+
argtypes[i] = resolvedtype;
2512+
}
2513+
inargno++;
2514+
}
24892515
}
24902516
else
24912517
{
2492-
/* special validation case */
2518+
/* special validation case (no need to do anything for RECORD) */
24932519
for (i = 0; i < numargs; i++)
24942520
{
24952521
switch (argtypes[i])

src/pl/plpgsql/src/sql/plpgsql_record.sql

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
--
44

55
create type two_int4s as (f1 int4, f2 int4);
6+
create type more_int4s as (f0 text, f1 int4, f2 int4);
67
create type two_int8s as (q1 int8, q2 int8);
78

89
-- base-case return of a composite type
@@ -215,13 +216,23 @@ create function getf1(x record) returns int language plpgsql as
215216
$$ begin return x.f1; end $$;
216217
select getf1(1);
217218
select getf1(row(1,2));
219+
select getf1(row(1,2)::two_int4s);
220+
select getf1(row('foo',123,456)::more_int4s);
218221
-- a CLOBBER_CACHE_ALWAYS build will report this error with a different
219222
-- context stack than other builds, so suppress context output
220223
\set SHOW_CONTEXT never
221224
select getf1(row(1,2)::two_int8s);
222225
\set SHOW_CONTEXT errors
223226
select getf1(row(1,2));
224227

228+
-- this seemingly-equivalent case behaves a bit differently,
229+
-- because the core parser's handling of $N symbols is simplistic
230+
create function getf2(record) returns int language plpgsql as
231+
$$ begin return $1.f2; end $$;
232+
select getf2(row(1,2)); -- ideally would work, but does not
233+
select getf2(row(1,2)::two_int4s);
234+
select getf2(row('foo',123,456)::more_int4s);
235+
225236
-- check behavior when assignment to FOR-loop variable requires coercion
226237
do $$
227238
declare r two_int8s;

0 commit comments

Comments
 (0)