]> granicus.if.org Git - postgresql/commitdiff
Ensure plpgsql result tuples have the right composite type marking.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 3 Jul 2019 22:08:53 +0000 (18:08 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 3 Jul 2019 22:08:53 +0000 (18:08 -0400)
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

src/pl/plpgsql/src/expected/plpgsql_record.out
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/sql/plpgsql_record.sql

index cc36231aef5b5a94391f433c8fe8cb00c69821f9..b9d76b5c6b7c93cf73845b6dd2cecb6a13dae524 100644 (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)
+
index 90a22578946a094a577d41e97356317e27a560ce..08961e2af9cc4d10a19938dcb9783565a493036b 100644 (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
                {
                        /*
index 88333d45e14ac7301c35529e018f4fc8ed4f8723..46c6178c73f2b0246429997953438135f26963e5 100644 (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;