From 5683b34956b4e8da9dccadc2e3a53b86104ebb33 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 3 Jul 2019 18:08:53 -0400 Subject: [PATCH] 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 --- .../plpgsql/src/expected/plpgsql_record.out | 13 ++++++++++ src/pl/plpgsql/src/pl_exec.c | 25 +++++++++++++++++++ src/pl/plpgsql/src/sql/plpgsql_record.sql | 10 ++++++++ 3 files changed, 48 insertions(+) diff --git a/src/pl/plpgsql/src/expected/plpgsql_record.out b/src/pl/plpgsql/src/expected/plpgsql_record.out index cc36231aef..b9d76b5c6b 100644 --- a/src/pl/plpgsql/src/expected/plpgsql_record.out +++ b/src/pl/plpgsql/src/expected/plpgsql_record.out @@ -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) + diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 90a2257894..08961e2af9 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -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 { /* diff --git a/src/pl/plpgsql/src/sql/plpgsql_record.sql b/src/pl/plpgsql/src/sql/plpgsql_record.sql index 88333d45e1..46c6178c73 100644 --- a/src/pl/plpgsql/src/sql/plpgsql_record.sql +++ b/src/pl/plpgsql/src/sql/plpgsql_record.sql @@ -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; -- 2.40.0