Ensure plpgsql result tuples have the right composite type marking.

A function that is declared to return a named composite type must
return tuple datums that are physically marked as having that type.
The plpgsql code path that allowed directly returning an expanded-record
datum forgot to check that, so that an expanded record marked as type
RECORDOID could be returned if it had a physically-compatible tupdesc.
This'd be harmless, I think, if the record value never escaped the
current session --- but it's possible for it to get stored into a table,
and then subsequent sessions can't interpret the anonymous record type.

Fix by flattening the record into a tuple datum and overwriting its
type/typmod fields, if its declared type doesn't match the function's
declared type.  (In principle it might be possible to just change the
expanded record's stored type ID info, but there are enough tricky
consequences that I didn't want to mess with that, especially not in
a back-patched bug fix.)

Per bug report from Steve Rogerson.  Back-patch to v11 where the bug
was introduced.

Discussion: https://postgr.es/m/cbaecae6-7b87-584e-45f6-4d047b92ca2a@yewtc.demon.co.uk
This commit is contained in:
Tom Lane 2019-07-03 18:08:53 -04:00
parent 03e7b302b1
commit 5683b34956
3 changed files with 48 additions and 0 deletions

View File

@ -654,3 +654,16 @@ NOTICE: processing row 2
NOTICE: processing row 3
ERROR: value for domain ordered_texts violates check constraint "ordered_texts_check"
CONTEXT: PL/pgSQL function inline_code_block line 8 at assignment
-- check coercion of a record result to named-composite function output type
create function compresult(int8) returns two_int8s language plpgsql as
$$ declare r record; begin r := row($1,$1); return r; end $$;
create table two_int8s_tab (f1 two_int8s);
insert into two_int8s_tab values (compresult(42));
-- reconnect so we lose any local knowledge of anonymous record types
\c -
table two_int8s_tab;
f1
---------
(42,42)
(1 row)

View File

@ -808,6 +808,31 @@ coerce_function_result_tuple(PLpgSQL_execstate *estate, TupleDesc tupdesc)
estate->retval = PointerGetDatum(SPI_returntuple(rettup, tupdesc));
/* no need to free map, we're about to return anyway */
}
else if (!(tupdesc->tdtypeid == erh->er_decltypeid ||
(tupdesc->tdtypeid == RECORDOID &&
!ExpandedRecordIsDomain(erh))))
{
/*
* The expanded record has the right physical tupdesc, but the
* wrong type ID. (Typically, the expanded record is RECORDOID
* but the function is declared to return a named composite type.
* As in exec_move_row_from_datum, we don't allow returning a
* composite-domain record from a function declared to return
* RECORD.) So we must flatten the record to a tuple datum and
* overwrite its type fields with the right thing. spi.c doesn't
* provide any easy way to deal with this case, so we end up
* duplicating the guts of datumCopy() :-(
*/
Size resultsize;
HeapTupleHeader tuphdr;
resultsize = EOH_get_flat_size(&erh->hdr);
tuphdr = (HeapTupleHeader) SPI_palloc(resultsize);
EOH_flatten_into(&erh->hdr, (void *) tuphdr, resultsize);
HeapTupleHeaderSetTypeId(tuphdr, tupdesc->tdtypeid);
HeapTupleHeaderSetTypMod(tuphdr, tupdesc->tdtypmod);
estate->retval = PointerGetDatum(tuphdr);
}
else
{
/*

View File

@ -441,3 +441,13 @@ begin
d.f2 := r.b;
end loop;
end$$;
-- check coercion of a record result to named-composite function output type
create function compresult(int8) returns two_int8s language plpgsql as
$$ declare r record; begin r := row($1,$1); return r; end $$;
create table two_int8s_tab (f1 two_int8s);
insert into two_int8s_tab values (compresult(42));
-- reconnect so we lose any local knowledge of anonymous record types
\c -
table two_int8s_tab;