Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 5683b34

Browse files
committed
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
1 parent 03e7b30 commit 5683b34

File tree

3 files changed

+48
-0
lines changed

3 files changed

+48
-0
lines changed

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

+13
Original file line numberDiff line numberDiff line change
@@ -654,3 +654,16 @@ NOTICE: processing row 2
654654
NOTICE: processing row 3
655655
ERROR: value for domain ordered_texts violates check constraint "ordered_texts_check"
656656
CONTEXT: PL/pgSQL function inline_code_block line 8 at assignment
657+
-- check coercion of a record result to named-composite function output type
658+
create function compresult(int8) returns two_int8s language plpgsql as
659+
$$ declare r record; begin r := row($1,$1); return r; end $$;
660+
create table two_int8s_tab (f1 two_int8s);
661+
insert into two_int8s_tab values (compresult(42));
662+
-- reconnect so we lose any local knowledge of anonymous record types
663+
\c -
664+
table two_int8s_tab;
665+
f1
666+
---------
667+
(42,42)
668+
(1 row)
669+

src/pl/plpgsql/src/pl_exec.c

+25
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,31 @@ coerce_function_result_tuple(PLpgSQL_execstate *estate, TupleDesc tupdesc)
808808
estate->retval = PointerGetDatum(SPI_returntuple(rettup, tupdesc));
809809
/* no need to free map, we're about to return anyway */
810810
}
811+
else if (!(tupdesc->tdtypeid == erh->er_decltypeid ||
812+
(tupdesc->tdtypeid == RECORDOID &&
813+
!ExpandedRecordIsDomain(erh))))
814+
{
815+
/*
816+
* The expanded record has the right physical tupdesc, but the
817+
* wrong type ID. (Typically, the expanded record is RECORDOID
818+
* but the function is declared to return a named composite type.
819+
* As in exec_move_row_from_datum, we don't allow returning a
820+
* composite-domain record from a function declared to return
821+
* RECORD.) So we must flatten the record to a tuple datum and
822+
* overwrite its type fields with the right thing. spi.c doesn't
823+
* provide any easy way to deal with this case, so we end up
824+
* duplicating the guts of datumCopy() :-(
825+
*/
826+
Size resultsize;
827+
HeapTupleHeader tuphdr;
828+
829+
resultsize = EOH_get_flat_size(&erh->hdr);
830+
tuphdr = (HeapTupleHeader) SPI_palloc(resultsize);
831+
EOH_flatten_into(&erh->hdr, (void *) tuphdr, resultsize);
832+
HeapTupleHeaderSetTypeId(tuphdr, tupdesc->tdtypeid);
833+
HeapTupleHeaderSetTypMod(tuphdr, tupdesc->tdtypmod);
834+
estate->retval = PointerGetDatum(tuphdr);
835+
}
811836
else
812837
{
813838
/*

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

+10
Original file line numberDiff line numberDiff line change
@@ -441,3 +441,13 @@ begin
441441
d.f2 := r.b;
442442
end loop;
443443
end$$;
444+
445+
-- check coercion of a record result to named-composite function output type
446+
create function compresult(int8) returns two_int8s language plpgsql as
447+
$$ declare r record; begin r := row($1,$1); return r; end $$;
448+
449+
create table two_int8s_tab (f1 two_int8s);
450+
insert into two_int8s_tab values (compresult(42));
451+
-- reconnect so we lose any local knowledge of anonymous record types
452+
\c -
453+
table two_int8s_tab;

0 commit comments

Comments
 (0)