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

Commit 31a8918

Browse files
committed
Improve pl/pgsql to support composite-type expressions in RETURN.
For some reason lost in the mists of prehistory, RETURN was only coded to allow a simple reference to a composite variable when the function's return type is composite. Allow an expression instead, while preserving the efficiency of the original code path in the case where the expression is indeed just a composite variable's name. Likewise for RETURN NEXT. As is true in various other places, the supplied expression must yield exactly the number and data types of the required columns. There was some discussion of relaxing that for pl/pgsql, but no consensus yet, so this patch doesn't address that. Asif Rehman, reviewed by Pavel Stehule
1 parent da07a1e commit 31a8918

File tree

7 files changed

+508
-138
lines changed

7 files changed

+508
-138
lines changed

doc/src/sgml/plpgsql.sgml

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,11 +1571,11 @@ RETURN <replaceable>expression</replaceable>;
15711571
</para>
15721572

15731573
<para>
1574-
When returning a scalar type, any expression can be used. The
1575-
expression's result will be automatically cast into the
1576-
function's return type as described for assignments. To return a
1577-
composite (row) value, you must write a record or row variable
1578-
as the <replaceable>expression</replaceable>.
1574+
In a function that returns a scalar type, the expression's result will
1575+
automatically be cast into the function's return type as described for
1576+
assignments. But to return a composite (row) value, you must write an
1577+
expression delivering exactly the requested column set. This may
1578+
require use of explicit casting.
15791579
</para>
15801580

15811581
<para>
@@ -1600,6 +1600,20 @@ RETURN <replaceable>expression</replaceable>;
16001600
however. In those cases a <command>RETURN</command> statement is
16011601
automatically executed if the top-level block finishes.
16021602
</para>
1603+
1604+
<para>
1605+
Some examples:
1606+
1607+
<programlisting>
1608+
-- functions returning a scalar type
1609+
RETURN 1 + 2;
1610+
RETURN scalar_var;
1611+
1612+
-- functions returning a composite type
1613+
RETURN composite_type_var;
1614+
RETURN (1, 2, 'three'::text); -- must cast columns to correct types
1615+
</programlisting>
1616+
</para>
16031617
</sect3>
16041618

16051619
<sect3>

src/pl/plpgsql/src/pl_exec.c

Lines changed: 187 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,12 @@ static void exec_move_row(PLpgSQL_execstate *estate,
189189
static 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);
192198
static char *convert_value_to_string(PLpgSQL_execstate *estate,
193199
Datum value, Oid valtype);
194200
static 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

Comments
 (0)