mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-09-03 23:07:19 +02:00
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
This commit is contained in:
parent
c946425176
commit
b759bb6714
@ -2,6 +2,7 @@
|
|||||||
-- Tests for PL/pgSQL handling of composite (record) variables
|
-- Tests for PL/pgSQL handling of composite (record) variables
|
||||||
--
|
--
|
||||||
create type two_int4s as (f1 int4, f2 int4);
|
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);
|
create type two_int8s as (q1 int8, q2 int8);
|
||||||
create type nested_int8s as (c1 two_int8s, c2 two_int8s);
|
create type nested_int8s as (c1 two_int8s, c2 two_int8s);
|
||||||
-- base-case return of a composite type
|
-- base-case return of a composite type
|
||||||
@ -426,6 +427,18 @@ select getf1(row(1,2));
|
|||||||
1
|
1
|
||||||
(1 row)
|
(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)
|
||||||
|
|
||||||
-- the context stack is different when debug_discard_caches
|
-- the context stack is different when debug_discard_caches
|
||||||
-- is set, so suppress context output
|
-- is set, so suppress context output
|
||||||
\set SHOW_CONTEXT never
|
\set SHOW_CONTEXT never
|
||||||
@ -438,6 +451,28 @@ select getf1(row(1,2));
|
|||||||
1
|
1
|
||||||
(1 row)
|
(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: $1.f2
|
||||||
|
^
|
||||||
|
QUERY: $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
|
-- check behavior when assignment to FOR-loop variable requires coercion
|
||||||
do $$
|
do $$
|
||||||
declare r two_int8s;
|
declare r two_int8s;
|
||||||
|
@ -2504,9 +2504,15 @@ compute_function_hashkey(FunctionCallInfo fcinfo,
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* This is the same as the standard resolve_polymorphic_argtypes() function,
|
* This is the same as the standard resolve_polymorphic_argtypes() function,
|
||||||
* but with a special case for validation: assume that polymorphic arguments
|
* except that:
|
||||||
* are integer, integer-array or integer-range. Also, we go ahead and report
|
* 1. We go ahead and report the error if we can't resolve the types.
|
||||||
* 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
|
static void
|
||||||
plpgsql_resolve_polymorphic_argtypes(int numargs,
|
plpgsql_resolve_polymorphic_argtypes(int numargs,
|
||||||
@ -2518,6 +2524,8 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
|
|||||||
|
|
||||||
if (!forValidator)
|
if (!forValidator)
|
||||||
{
|
{
|
||||||
|
int inargno;
|
||||||
|
|
||||||
/* normal case, pass to standard routine */
|
/* normal case, pass to standard routine */
|
||||||
if (!resolve_polymorphic_argtypes(numargs, argtypes, argmodes,
|
if (!resolve_polymorphic_argtypes(numargs, argtypes, argmodes,
|
||||||
call_expr))
|
call_expr))
|
||||||
@ -2526,10 +2534,28 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
|
|||||||
errmsg("could not determine actual argument "
|
errmsg("could not determine actual argument "
|
||||||
"type for polymorphic function \"%s\"",
|
"type for polymorphic function \"%s\"",
|
||||||
proname)));
|
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
|
else
|
||||||
{
|
{
|
||||||
/* special validation case */
|
/* special validation case (no need to do anything for RECORD) */
|
||||||
for (i = 0; i < numargs; i++)
|
for (i = 0; i < numargs; i++)
|
||||||
{
|
{
|
||||||
switch (argtypes[i])
|
switch (argtypes[i])
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
--
|
--
|
||||||
|
|
||||||
create type two_int4s as (f1 int4, f2 int4);
|
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);
|
create type two_int8s as (q1 int8, q2 int8);
|
||||||
create type nested_int8s as (c1 two_int8s, c2 two_int8s);
|
create type nested_int8s as (c1 two_int8s, c2 two_int8s);
|
||||||
|
|
||||||
@ -257,6 +258,8 @@ create function getf1(x record) returns int language plpgsql as
|
|||||||
$$ begin return x.f1; end $$;
|
$$ begin return x.f1; end $$;
|
||||||
select getf1(1);
|
select getf1(1);
|
||||||
select getf1(row(1,2));
|
select getf1(row(1,2));
|
||||||
|
select getf1(row(1,2)::two_int4s);
|
||||||
|
select getf1(row('foo',123,456)::more_int4s);
|
||||||
-- the context stack is different when debug_discard_caches
|
-- the context stack is different when debug_discard_caches
|
||||||
-- is set, so suppress context output
|
-- is set, so suppress context output
|
||||||
\set SHOW_CONTEXT never
|
\set SHOW_CONTEXT never
|
||||||
@ -264,6 +267,14 @@ select getf1(row(1,2)::two_int8s);
|
|||||||
\set SHOW_CONTEXT errors
|
\set SHOW_CONTEXT errors
|
||||||
select getf1(row(1,2));
|
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
|
-- check behavior when assignment to FOR-loop variable requires coercion
|
||||||
do $$
|
do $$
|
||||||
declare r two_int8s;
|
declare r two_int8s;
|
||||||
|
Loading…
Reference in New Issue
Block a user