@@ -189,6 +189,12 @@ static void exec_move_row(PLpgSQL_execstate *estate,
189
189
static HeapTuple make_tuple_from_row (PLpgSQL_execstate * estate ,
190
190
PLpgSQL_row * row ,
191
191
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 );
192
198
static char * convert_value_to_string (PLpgSQL_execstate * estate ,
193
199
Datum value , Oid valtype );
194
200
static Datum exec_cast_value (PLpgSQL_execstate * estate ,
@@ -275,24 +281,9 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
275
281
276
282
if (!fcinfo -> argnull [i ])
277
283
{
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 ]);
296
287
}
297
288
else
298
289
{
@@ -2396,6 +2387,10 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
2396
2387
estate -> rettupdesc = NULL ;
2397
2388
estate -> retisnull = true;
2398
2389
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
+ */
2399
2394
if (stmt -> retvarno >= 0 )
2400
2395
{
2401
2396
PLpgSQL_datum * retvar = estate -> datums [stmt -> retvarno ];
@@ -2449,22 +2444,26 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
2449
2444
2450
2445
if (stmt -> expr != NULL )
2451
2446
{
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 )
2463
2452
{
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 );
2468
2467
}
2469
2468
2470
2469
return PLPGSQL_RC_RETURN ;
@@ -2473,8 +2472,7 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
2473
2472
/*
2474
2473
* Special hack for function returning VOID: instead of NULL, return a
2475
2474
* 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.
2478
2476
*/
2479
2477
if (estate -> fn_rettype == VOIDOID )
2480
2478
{
@@ -2513,6 +2511,10 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
2513
2511
tupdesc = estate -> rettupdesc ;
2514
2512
natts = tupdesc -> natts ;
2515
2513
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
+ */
2516
2518
if (stmt -> retvarno >= 0 )
2517
2519
{
2518
2520
PLpgSQL_datum * retvar = estate -> datums [stmt -> retvarno ];
@@ -2593,26 +2595,77 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
2593
2595
bool isNull ;
2594
2596
Oid rettype ;
2595
2597
2596
- if (natts != 1 )
2597
- ereport (ERROR ,
2598
- (errcode (ERRCODE_DATATYPE_MISMATCH ),
2599
- errmsg ("wrong result type supplied in RETURN NEXT" )));
2600
-
2601
2598
retval = exec_eval_expr (estate ,
2602
2599
stmt -> expr ,
2603
2600
& isNull ,
2604
2601
& rettype );
2605
2602
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" )));
2613
2615
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
+ }
2616
2669
}
2617
2670
else
2618
2671
{
@@ -3901,30 +3954,12 @@ exec_assign_value(PLpgSQL_execstate *estate,
3901
3954
}
3902
3955
else
3903
3956
{
3904
- HeapTupleHeader td ;
3905
- Oid tupType ;
3906
- int32 tupTypmod ;
3907
- TupleDesc tupdesc ;
3908
- HeapTupleData tmptup ;
3909
-
3910
3957
/* Source must be of RECORD or composite type */
3911
3958
if (!type_is_rowtype (valtype ))
3912
3959
ereport (ERROR ,
3913
3960
(errcode (ERRCODE_DATATYPE_MISMATCH ),
3914
3961
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 );
3928
3963
}
3929
3964
break ;
3930
3965
}
@@ -3943,31 +3978,12 @@ exec_assign_value(PLpgSQL_execstate *estate,
3943
3978
}
3944
3979
else
3945
3980
{
3946
- HeapTupleHeader td ;
3947
- Oid tupType ;
3948
- int32 tupTypmod ;
3949
- TupleDesc tupdesc ;
3950
- HeapTupleData tmptup ;
3951
-
3952
3981
/* Source must be of RECORD or composite type */
3953
3982
if (!type_is_rowtype (valtype ))
3954
3983
ereport (ERROR ,
3955
3984
(errcode (ERRCODE_DATATYPE_MISMATCH ),
3956
3985
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 );
3971
3987
}
3972
3988
break ;
3973
3989
}
@@ -5416,6 +5432,89 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
5416
5432
return tuple ;
5417
5433
}
5418
5434
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
+
5419
5518
/* ----------
5420
5519
* convert_value_to_string Convert a non-null Datum to C string
5421
5520
*
0 commit comments