@@ -189,6 +189,12 @@ static void exec_move_row(PLpgSQL_execstate *estate,
189189static HeapTuple make_tuple_from_row (PLpgSQL_execstate * estate ,
190190 PLpgSQL_row * row ,
191191 TupleDesc tupdesc );
192+ static HeapTuple get_tuple_from_datum (Datum value );
193+ static TupleDesc get_tupdesc_from_datum (Datum value );
194+ static void exec_move_row_from_datum (PLpgSQL_execstate * estate ,
195+ PLpgSQL_rec * rec ,
196+ PLpgSQL_row * row ,
197+ Datum value );
192198static char * convert_value_to_string (PLpgSQL_execstate * estate ,
193199 Datum value , Oid valtype );
194200static Datum exec_cast_value (PLpgSQL_execstate * estate ,
@@ -275,24 +281,9 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
275281
276282 if (!fcinfo -> argnull [i ])
277283 {
278- HeapTupleHeader td ;
279- Oid tupType ;
280- int32 tupTypmod ;
281- TupleDesc tupdesc ;
282- HeapTupleData tmptup ;
283-
284- td = DatumGetHeapTupleHeader (fcinfo -> arg [i ]);
285- /* Extract rowtype info and find a tupdesc */
286- tupType = HeapTupleHeaderGetTypeId (td );
287- tupTypmod = HeapTupleHeaderGetTypMod (td );
288- tupdesc = lookup_rowtype_tupdesc (tupType , tupTypmod );
289- /* Build a temporary HeapTuple control structure */
290- tmptup .t_len = HeapTupleHeaderGetDatumLength (td );
291- ItemPointerSetInvalid (& (tmptup .t_self ));
292- tmptup .t_tableOid = InvalidOid ;
293- tmptup .t_data = td ;
294- exec_move_row (& estate , NULL , row , & tmptup , tupdesc );
295- ReleaseTupleDesc (tupdesc );
284+ /* Assign row value from composite datum */
285+ exec_move_row_from_datum (& estate , NULL , row ,
286+ fcinfo -> arg [i ]);
296287 }
297288 else
298289 {
@@ -2396,6 +2387,10 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
23962387 estate -> rettupdesc = NULL ;
23972388 estate -> retisnull = true;
23982389
2390+ /*
2391+ * This special-case path covers record/row variables in fn_retistuple
2392+ * functions, as well as functions with one or more OUT parameters.
2393+ */
23992394 if (stmt -> retvarno >= 0 )
24002395 {
24012396 PLpgSQL_datum * retvar = estate -> datums [stmt -> retvarno ];
@@ -2449,22 +2444,26 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
24492444
24502445 if (stmt -> expr != NULL )
24512446 {
2452- if (estate -> retistuple )
2453- {
2454- exec_run_select (estate , stmt -> expr , 1 , NULL );
2455- if (estate -> eval_processed > 0 )
2456- {
2457- estate -> retval = PointerGetDatum (estate -> eval_tuptable -> vals [0 ]);
2458- estate -> rettupdesc = estate -> eval_tuptable -> tupdesc ;
2459- estate -> retisnull = false;
2460- }
2461- }
2462- else
2447+ estate -> retval = exec_eval_expr (estate , stmt -> expr ,
2448+ & (estate -> retisnull ),
2449+ & (estate -> rettype ));
2450+
2451+ if (estate -> retistuple && !estate -> retisnull )
24632452 {
2464- /* Normal case for scalar results */
2465- estate -> retval = exec_eval_expr (estate , stmt -> expr ,
2466- & (estate -> retisnull ),
2467- & (estate -> rettype ));
2453+ /* Convert composite datum to a HeapTuple and TupleDesc */
2454+ HeapTuple tuple ;
2455+ TupleDesc tupdesc ;
2456+
2457+ /* Source must be of RECORD or composite type */
2458+ if (!type_is_rowtype (estate -> rettype ))
2459+ ereport (ERROR ,
2460+ (errcode (ERRCODE_DATATYPE_MISMATCH ),
2461+ errmsg ("cannot return non-composite value from function returning composite type" )));
2462+ tuple = get_tuple_from_datum (estate -> retval );
2463+ tupdesc = get_tupdesc_from_datum (estate -> retval );
2464+ estate -> retval = PointerGetDatum (tuple );
2465+ estate -> rettupdesc = CreateTupleDescCopy (tupdesc );
2466+ ReleaseTupleDesc (tupdesc );
24682467 }
24692468
24702469 return PLPGSQL_RC_RETURN ;
@@ -2473,8 +2472,7 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
24732472 /*
24742473 * Special hack for function returning VOID: instead of NULL, return a
24752474 * non-null VOID value. This is of dubious importance but is kept for
2476- * backwards compatibility. Note that the only other way to get here is
2477- * to have written "RETURN NULL" in a function returning tuple.
2475+ * backwards compatibility.
24782476 */
24792477 if (estate -> fn_rettype == VOIDOID )
24802478 {
@@ -2513,6 +2511,10 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
25132511 tupdesc = estate -> rettupdesc ;
25142512 natts = tupdesc -> natts ;
25152513
2514+ /*
2515+ * This special-case path covers record/row variables in fn_retistuple
2516+ * functions, as well as functions with one or more OUT parameters.
2517+ */
25162518 if (stmt -> retvarno >= 0 )
25172519 {
25182520 PLpgSQL_datum * retvar = estate -> datums [stmt -> retvarno ];
@@ -2593,26 +2595,77 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
25932595 bool isNull ;
25942596 Oid rettype ;
25952597
2596- if (natts != 1 )
2597- ereport (ERROR ,
2598- (errcode (ERRCODE_DATATYPE_MISMATCH ),
2599- errmsg ("wrong result type supplied in RETURN NEXT" )));
2600-
26012598 retval = exec_eval_expr (estate ,
26022599 stmt -> expr ,
26032600 & isNull ,
26042601 & rettype );
26052602
2606- /* coerce type if needed */
2607- retval = exec_simple_cast_value (estate ,
2608- retval ,
2609- rettype ,
2610- tupdesc -> attrs [0 ]-> atttypid ,
2611- tupdesc -> attrs [0 ]-> atttypmod ,
2612- isNull );
2603+ if (estate -> retistuple )
2604+ {
2605+ /* Expression should be of RECORD or composite type */
2606+ if (!isNull )
2607+ {
2608+ TupleDesc retvaldesc ;
2609+ TupleConversionMap * tupmap ;
2610+
2611+ if (!type_is_rowtype (rettype ))
2612+ ereport (ERROR ,
2613+ (errcode (ERRCODE_DATATYPE_MISMATCH ),
2614+ errmsg ("cannot return non-composite value from function returning composite type" )));
26132615
2614- tuplestore_putvalues (estate -> tuple_store , tupdesc ,
2615- & retval , & isNull );
2616+ tuple = get_tuple_from_datum (retval );
2617+ free_tuple = true; /* tuple is always freshly palloc'd */
2618+
2619+ /* it might need conversion */
2620+ retvaldesc = get_tupdesc_from_datum (retval );
2621+ tupmap = convert_tuples_by_position (retvaldesc , tupdesc ,
2622+ gettext_noop ("returned record type does not match expected record type" ));
2623+ if (tupmap )
2624+ {
2625+ HeapTuple newtuple ;
2626+
2627+ newtuple = do_convert_tuple (tuple , tupmap );
2628+ free_conversion_map (tupmap );
2629+ heap_freetuple (tuple );
2630+ tuple = newtuple ;
2631+ }
2632+ ReleaseTupleDesc (retvaldesc );
2633+ /* tuple will be stored into tuplestore below */
2634+ }
2635+ else
2636+ {
2637+ /* Composite NULL --- store a row of nulls */
2638+ Datum * nulldatums ;
2639+ bool * nullflags ;
2640+
2641+ nulldatums = (Datum * ) palloc0 (natts * sizeof (Datum ));
2642+ nullflags = (bool * ) palloc (natts * sizeof (bool ));
2643+ memset (nullflags , true, natts * sizeof (bool ));
2644+ tuplestore_putvalues (estate -> tuple_store , tupdesc ,
2645+ nulldatums , nullflags );
2646+ pfree (nulldatums );
2647+ pfree (nullflags );
2648+ }
2649+ }
2650+ else
2651+ {
2652+ /* Simple scalar result */
2653+ if (natts != 1 )
2654+ ereport (ERROR ,
2655+ (errcode (ERRCODE_DATATYPE_MISMATCH ),
2656+ errmsg ("wrong result type supplied in RETURN NEXT" )));
2657+
2658+ /* coerce type if needed */
2659+ retval = exec_simple_cast_value (estate ,
2660+ retval ,
2661+ rettype ,
2662+ tupdesc -> attrs [0 ]-> atttypid ,
2663+ tupdesc -> attrs [0 ]-> atttypmod ,
2664+ isNull );
2665+
2666+ tuplestore_putvalues (estate -> tuple_store , tupdesc ,
2667+ & retval , & isNull );
2668+ }
26162669 }
26172670 else
26182671 {
@@ -3901,30 +3954,12 @@ exec_assign_value(PLpgSQL_execstate *estate,
39013954 }
39023955 else
39033956 {
3904- HeapTupleHeader td ;
3905- Oid tupType ;
3906- int32 tupTypmod ;
3907- TupleDesc tupdesc ;
3908- HeapTupleData tmptup ;
3909-
39103957 /* Source must be of RECORD or composite type */
39113958 if (!type_is_rowtype (valtype ))
39123959 ereport (ERROR ,
39133960 (errcode (ERRCODE_DATATYPE_MISMATCH ),
39143961 errmsg ("cannot assign non-composite value to a row variable" )));
3915- /* Source is a tuple Datum, so safe to do this: */
3916- td = DatumGetHeapTupleHeader (value );
3917- /* Extract rowtype info and find a tupdesc */
3918- tupType = HeapTupleHeaderGetTypeId (td );
3919- tupTypmod = HeapTupleHeaderGetTypMod (td );
3920- tupdesc = lookup_rowtype_tupdesc (tupType , tupTypmod );
3921- /* Build a temporary HeapTuple control structure */
3922- tmptup .t_len = HeapTupleHeaderGetDatumLength (td );
3923- ItemPointerSetInvalid (& (tmptup .t_self ));
3924- tmptup .t_tableOid = InvalidOid ;
3925- tmptup .t_data = td ;
3926- exec_move_row (estate , NULL , row , & tmptup , tupdesc );
3927- ReleaseTupleDesc (tupdesc );
3962+ exec_move_row_from_datum (estate , NULL , row , value );
39283963 }
39293964 break ;
39303965 }
@@ -3943,31 +3978,12 @@ exec_assign_value(PLpgSQL_execstate *estate,
39433978 }
39443979 else
39453980 {
3946- HeapTupleHeader td ;
3947- Oid tupType ;
3948- int32 tupTypmod ;
3949- TupleDesc tupdesc ;
3950- HeapTupleData tmptup ;
3951-
39523981 /* Source must be of RECORD or composite type */
39533982 if (!type_is_rowtype (valtype ))
39543983 ereport (ERROR ,
39553984 (errcode (ERRCODE_DATATYPE_MISMATCH ),
39563985 errmsg ("cannot assign non-composite value to a record variable" )));
3957-
3958- /* Source is a tuple Datum, so safe to do this: */
3959- td = DatumGetHeapTupleHeader (value );
3960- /* Extract rowtype info and find a tupdesc */
3961- tupType = HeapTupleHeaderGetTypeId (td );
3962- tupTypmod = HeapTupleHeaderGetTypMod (td );
3963- tupdesc = lookup_rowtype_tupdesc (tupType , tupTypmod );
3964- /* Build a temporary HeapTuple control structure */
3965- tmptup .t_len = HeapTupleHeaderGetDatumLength (td );
3966- ItemPointerSetInvalid (& (tmptup .t_self ));
3967- tmptup .t_tableOid = InvalidOid ;
3968- tmptup .t_data = td ;
3969- exec_move_row (estate , rec , NULL , & tmptup , tupdesc );
3970- ReleaseTupleDesc (tupdesc );
3986+ exec_move_row_from_datum (estate , rec , NULL , value );
39713987 }
39723988 break ;
39733989 }
@@ -5416,6 +5432,89 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
54165432 return tuple ;
54175433}
54185434
5435+ /* ----------
5436+ * get_tuple_from_datum extract a tuple from a composite Datum
5437+ *
5438+ * Returns a freshly palloc'd HeapTuple.
5439+ *
5440+ * Note: it's caller's responsibility to be sure value is of composite type.
5441+ * ----------
5442+ */
5443+ static HeapTuple
5444+ get_tuple_from_datum (Datum value )
5445+ {
5446+ HeapTupleHeader td = DatumGetHeapTupleHeader (value );
5447+ HeapTupleData tmptup ;
5448+
5449+ /* Build a temporary HeapTuple control structure */
5450+ tmptup .t_len = HeapTupleHeaderGetDatumLength (td );
5451+ ItemPointerSetInvalid (& (tmptup .t_self ));
5452+ tmptup .t_tableOid = InvalidOid ;
5453+ tmptup .t_data = td ;
5454+
5455+ /* Build a copy and return it */
5456+ return heap_copytuple (& tmptup );
5457+ }
5458+
5459+ /* ----------
5460+ * get_tupdesc_from_datum get a tuple descriptor for a composite Datum
5461+ *
5462+ * Returns a pointer to the TupleDesc of the tuple's rowtype.
5463+ * Caller is responsible for calling ReleaseTupleDesc when done with it.
5464+ *
5465+ * Note: it's caller's responsibility to be sure value is of composite type.
5466+ * ----------
5467+ */
5468+ static TupleDesc
5469+ get_tupdesc_from_datum (Datum value )
5470+ {
5471+ HeapTupleHeader td = DatumGetHeapTupleHeader (value );
5472+ Oid tupType ;
5473+ int32 tupTypmod ;
5474+
5475+ /* Extract rowtype info and find a tupdesc */
5476+ tupType = HeapTupleHeaderGetTypeId (td );
5477+ tupTypmod = HeapTupleHeaderGetTypMod (td );
5478+ return lookup_rowtype_tupdesc (tupType , tupTypmod );
5479+ }
5480+
5481+ /* ----------
5482+ * exec_move_row_from_datum Move a composite Datum into a record or row
5483+ *
5484+ * This is equivalent to get_tuple_from_datum() followed by exec_move_row(),
5485+ * but we avoid constructing an intermediate physical copy of the tuple.
5486+ * ----------
5487+ */
5488+ static void
5489+ exec_move_row_from_datum (PLpgSQL_execstate * estate ,
5490+ PLpgSQL_rec * rec ,
5491+ PLpgSQL_row * row ,
5492+ Datum value )
5493+ {
5494+ HeapTupleHeader td = DatumGetHeapTupleHeader (value );
5495+ Oid tupType ;
5496+ int32 tupTypmod ;
5497+ TupleDesc tupdesc ;
5498+ HeapTupleData tmptup ;
5499+
5500+ /* Extract rowtype info and find a tupdesc */
5501+ tupType = HeapTupleHeaderGetTypeId (td );
5502+ tupTypmod = HeapTupleHeaderGetTypMod (td );
5503+ tupdesc = lookup_rowtype_tupdesc (tupType , tupTypmod );
5504+
5505+ /* Build a temporary HeapTuple control structure */
5506+ tmptup .t_len = HeapTupleHeaderGetDatumLength (td );
5507+ ItemPointerSetInvalid (& (tmptup .t_self ));
5508+ tmptup .t_tableOid = InvalidOid ;
5509+ tmptup .t_data = td ;
5510+
5511+ /* Do the move */
5512+ exec_move_row (estate , rec , row , & tmptup , tupdesc );
5513+
5514+ /* Release tupdesc usage count */
5515+ ReleaseTupleDesc (tupdesc );
5516+ }
5517+
54195518/* ----------
54205519 * convert_value_to_string Convert a non-null Datum to C string
54215520 *
0 commit comments