diff --git a/src/pl/plpgsql/src/expected/plpgsql_record.out b/src/pl/plpgsql/src/expected/plpgsql_record.out index 4c5d95c79e..afb922df29 100644 --- a/src/pl/plpgsql/src/expected/plpgsql_record.out +++ b/src/pl/plpgsql/src/expected/plpgsql_record.out @@ -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); create type nested_int8s as (c1 two_int8s, c2 two_int8s); -- base-case return of a composite type @@ -426,6 +427,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) + -- the context stack is different when debug_discard_caches -- is set, so suppress context output \set SHOW_CONTEXT never @@ -438,6 +451,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: $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 do $$ declare r two_int8s; diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index b791c23f06..65f259a90d 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -2504,9 +2504,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, @@ -2518,6 +2524,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)) @@ -2526,10 +2534,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]) diff --git a/src/pl/plpgsql/src/sql/plpgsql_record.sql b/src/pl/plpgsql/src/sql/plpgsql_record.sql index 535a3407a4..db655335b1 100644 --- a/src/pl/plpgsql/src/sql/plpgsql_record.sql +++ b/src/pl/plpgsql/src/sql/plpgsql_record.sql @@ -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); 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 $$; select getf1(1); 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 -- is set, so suppress context output \set SHOW_CONTEXT never @@ -264,6 +267,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;