Commit 31a89185 authored by Tom Lane's avatar Tom Lane

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
parent da07a1e8
...@@ -1571,11 +1571,11 @@ RETURN <replaceable>expression</replaceable>; ...@@ -1571,11 +1571,11 @@ RETURN <replaceable>expression</replaceable>;
</para> </para>
<para> <para>
When returning a scalar type, any expression can be used. The In a function that returns a scalar type, the expression's result will
expression's result will be automatically cast into the automatically be cast into the function's return type as described for
function's return type as described for assignments. To return a assignments. But to return a composite (row) value, you must write an
composite (row) value, you must write a record or row variable expression delivering exactly the requested column set. This may
as the <replaceable>expression</replaceable>. require use of explicit casting.
</para> </para>
<para> <para>
...@@ -1600,6 +1600,20 @@ RETURN <replaceable>expression</replaceable>; ...@@ -1600,6 +1600,20 @@ RETURN <replaceable>expression</replaceable>;
however. In those cases a <command>RETURN</command> statement is however. In those cases a <command>RETURN</command> statement is
automatically executed if the top-level block finishes. automatically executed if the top-level block finishes.
</para> </para>
<para>
Some examples:
<programlisting>
-- functions returning a scalar type
RETURN 1 + 2;
RETURN scalar_var;
-- functions returning a composite type
RETURN composite_type_var;
RETURN (1, 2, 'three'::text); -- must cast columns to correct types
</programlisting>
</para>
</sect3> </sect3>
<sect3> <sect3>
......
...@@ -189,6 +189,12 @@ static void exec_move_row(PLpgSQL_execstate *estate, ...@@ -189,6 +189,12 @@ static void exec_move_row(PLpgSQL_execstate *estate,
static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate, static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate,
PLpgSQL_row *row, PLpgSQL_row *row,
TupleDesc tupdesc); TupleDesc tupdesc);
static HeapTuple get_tuple_from_datum(Datum value);
static TupleDesc get_tupdesc_from_datum(Datum value);
static void exec_move_row_from_datum(PLpgSQL_execstate *estate,
PLpgSQL_rec *rec,
PLpgSQL_row *row,
Datum value);
static char *convert_value_to_string(PLpgSQL_execstate *estate, static char *convert_value_to_string(PLpgSQL_execstate *estate,
Datum value, Oid valtype); Datum value, Oid valtype);
static Datum exec_cast_value(PLpgSQL_execstate *estate, static Datum exec_cast_value(PLpgSQL_execstate *estate,
...@@ -275,24 +281,9 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo) ...@@ -275,24 +281,9 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
if (!fcinfo->argnull[i]) if (!fcinfo->argnull[i])
{ {
HeapTupleHeader td; /* Assign row value from composite datum */
Oid tupType; exec_move_row_from_datum(&estate, NULL, row,
int32 tupTypmod; fcinfo->arg[i]);
TupleDesc tupdesc;
HeapTupleData tmptup;
td = DatumGetHeapTupleHeader(fcinfo->arg[i]);
/* Extract rowtype info and find a tupdesc */
tupType = HeapTupleHeaderGetTypeId(td);
tupTypmod = HeapTupleHeaderGetTypMod(td);
tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
/* Build a temporary HeapTuple control structure */
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
ItemPointerSetInvalid(&(tmptup.t_self));
tmptup.t_tableOid = InvalidOid;
tmptup.t_data = td;
exec_move_row(&estate, NULL, row, &tmptup, tupdesc);
ReleaseTupleDesc(tupdesc);
} }
else else
{ {
...@@ -2396,6 +2387,10 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt) ...@@ -2396,6 +2387,10 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
estate->rettupdesc = NULL; estate->rettupdesc = NULL;
estate->retisnull = true; estate->retisnull = true;
/*
* This special-case path covers record/row variables in fn_retistuple
* functions, as well as functions with one or more OUT parameters.
*/
if (stmt->retvarno >= 0) if (stmt->retvarno >= 0)
{ {
PLpgSQL_datum *retvar = estate->datums[stmt->retvarno]; PLpgSQL_datum *retvar = estate->datums[stmt->retvarno];
...@@ -2449,22 +2444,26 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt) ...@@ -2449,22 +2444,26 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
if (stmt->expr != NULL) if (stmt->expr != NULL)
{ {
if (estate->retistuple)
{
exec_run_select(estate, stmt->expr, 1, NULL);
if (estate->eval_processed > 0)
{
estate->retval = PointerGetDatum(estate->eval_tuptable->vals[0]);
estate->rettupdesc = estate->eval_tuptable->tupdesc;
estate->retisnull = false;
}
}
else
{
/* Normal case for scalar results */
estate->retval = exec_eval_expr(estate, stmt->expr, estate->retval = exec_eval_expr(estate, stmt->expr,
&(estate->retisnull), &(estate->retisnull),
&(estate->rettype)); &(estate->rettype));
if (estate->retistuple && !estate->retisnull)
{
/* Convert composite datum to a HeapTuple and TupleDesc */
HeapTuple tuple;
TupleDesc tupdesc;
/* Source must be of RECORD or composite type */
if (!type_is_rowtype(estate->rettype))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot return non-composite value from function returning composite type")));
tuple = get_tuple_from_datum(estate->retval);
tupdesc = get_tupdesc_from_datum(estate->retval);
estate->retval = PointerGetDatum(tuple);
estate->rettupdesc = CreateTupleDescCopy(tupdesc);
ReleaseTupleDesc(tupdesc);
} }
return PLPGSQL_RC_RETURN; return PLPGSQL_RC_RETURN;
...@@ -2473,8 +2472,7 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt) ...@@ -2473,8 +2472,7 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
/* /*
* Special hack for function returning VOID: instead of NULL, return a * Special hack for function returning VOID: instead of NULL, return a
* non-null VOID value. This is of dubious importance but is kept for * non-null VOID value. This is of dubious importance but is kept for
* backwards compatibility. Note that the only other way to get here is * backwards compatibility.
* to have written "RETURN NULL" in a function returning tuple.
*/ */
if (estate->fn_rettype == VOIDOID) if (estate->fn_rettype == VOIDOID)
{ {
...@@ -2513,6 +2511,10 @@ exec_stmt_return_next(PLpgSQL_execstate *estate, ...@@ -2513,6 +2511,10 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
tupdesc = estate->rettupdesc; tupdesc = estate->rettupdesc;
natts = tupdesc->natts; natts = tupdesc->natts;
/*
* This special-case path covers record/row variables in fn_retistuple
* functions, as well as functions with one or more OUT parameters.
*/
if (stmt->retvarno >= 0) if (stmt->retvarno >= 0)
{ {
PLpgSQL_datum *retvar = estate->datums[stmt->retvarno]; PLpgSQL_datum *retvar = estate->datums[stmt->retvarno];
...@@ -2593,16 +2595,66 @@ exec_stmt_return_next(PLpgSQL_execstate *estate, ...@@ -2593,16 +2595,66 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
bool isNull; bool isNull;
Oid rettype; Oid rettype;
if (natts != 1)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("wrong result type supplied in RETURN NEXT")));
retval = exec_eval_expr(estate, retval = exec_eval_expr(estate,
stmt->expr, stmt->expr,
&isNull, &isNull,
&rettype); &rettype);
if (estate->retistuple)
{
/* Expression should be of RECORD or composite type */
if (!isNull)
{
TupleDesc retvaldesc;
TupleConversionMap *tupmap;
if (!type_is_rowtype(rettype))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot return non-composite value from function returning composite type")));
tuple = get_tuple_from_datum(retval);
free_tuple = true; /* tuple is always freshly palloc'd */
/* it might need conversion */
retvaldesc = get_tupdesc_from_datum(retval);
tupmap = convert_tuples_by_position(retvaldesc, tupdesc,
gettext_noop("returned record type does not match expected record type"));
if (tupmap)
{
HeapTuple newtuple;
newtuple = do_convert_tuple(tuple, tupmap);
free_conversion_map(tupmap);
heap_freetuple(tuple);
tuple = newtuple;
}
ReleaseTupleDesc(retvaldesc);
/* tuple will be stored into tuplestore below */
}
else
{
/* Composite NULL --- store a row of nulls */
Datum *nulldatums;
bool *nullflags;
nulldatums = (Datum *) palloc0(natts * sizeof(Datum));
nullflags = (bool *) palloc(natts * sizeof(bool));
memset(nullflags, true, natts * sizeof(bool));
tuplestore_putvalues(estate->tuple_store, tupdesc,
nulldatums, nullflags);
pfree(nulldatums);
pfree(nullflags);
}
}
else
{
/* Simple scalar result */
if (natts != 1)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("wrong result type supplied in RETURN NEXT")));
/* coerce type if needed */ /* coerce type if needed */
retval = exec_simple_cast_value(estate, retval = exec_simple_cast_value(estate,
retval, retval,
...@@ -2614,6 +2666,7 @@ exec_stmt_return_next(PLpgSQL_execstate *estate, ...@@ -2614,6 +2666,7 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
tuplestore_putvalues(estate->tuple_store, tupdesc, tuplestore_putvalues(estate->tuple_store, tupdesc,
&retval, &isNull); &retval, &isNull);
} }
}
else else
{ {
ereport(ERROR, ereport(ERROR,
...@@ -3901,30 +3954,12 @@ exec_assign_value(PLpgSQL_execstate *estate, ...@@ -3901,30 +3954,12 @@ exec_assign_value(PLpgSQL_execstate *estate,
} }
else else
{ {
HeapTupleHeader td;
Oid tupType;
int32 tupTypmod;
TupleDesc tupdesc;
HeapTupleData tmptup;
/* Source must be of RECORD or composite type */ /* Source must be of RECORD or composite type */
if (!type_is_rowtype(valtype)) if (!type_is_rowtype(valtype))
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH), (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot assign non-composite value to a row variable"))); errmsg("cannot assign non-composite value to a row variable")));
/* Source is a tuple Datum, so safe to do this: */ exec_move_row_from_datum(estate, NULL, row, value);
td = DatumGetHeapTupleHeader(value);
/* Extract rowtype info and find a tupdesc */
tupType = HeapTupleHeaderGetTypeId(td);
tupTypmod = HeapTupleHeaderGetTypMod(td);
tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
/* Build a temporary HeapTuple control structure */
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
ItemPointerSetInvalid(&(tmptup.t_self));
tmptup.t_tableOid = InvalidOid;
tmptup.t_data = td;
exec_move_row(estate, NULL, row, &tmptup, tupdesc);
ReleaseTupleDesc(tupdesc);
} }
break; break;
} }
...@@ -3943,31 +3978,12 @@ exec_assign_value(PLpgSQL_execstate *estate, ...@@ -3943,31 +3978,12 @@ exec_assign_value(PLpgSQL_execstate *estate,
} }
else else
{ {
HeapTupleHeader td;
Oid tupType;
int32 tupTypmod;
TupleDesc tupdesc;
HeapTupleData tmptup;
/* Source must be of RECORD or composite type */ /* Source must be of RECORD or composite type */
if (!type_is_rowtype(valtype)) if (!type_is_rowtype(valtype))
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH), (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot assign non-composite value to a record variable"))); errmsg("cannot assign non-composite value to a record variable")));
exec_move_row_from_datum(estate, rec, NULL, value);
/* Source is a tuple Datum, so safe to do this: */
td = DatumGetHeapTupleHeader(value);
/* Extract rowtype info and find a tupdesc */
tupType = HeapTupleHeaderGetTypeId(td);
tupTypmod = HeapTupleHeaderGetTypMod(td);
tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
/* Build a temporary HeapTuple control structure */
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
ItemPointerSetInvalid(&(tmptup.t_self));
tmptup.t_tableOid = InvalidOid;
tmptup.t_data = td;
exec_move_row(estate, rec, NULL, &tmptup, tupdesc);
ReleaseTupleDesc(tupdesc);
} }
break; break;
} }
...@@ -5416,6 +5432,89 @@ make_tuple_from_row(PLpgSQL_execstate *estate, ...@@ -5416,6 +5432,89 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
return tuple; return tuple;
} }
/* ----------
* get_tuple_from_datum extract a tuple from a composite Datum
*
* Returns a freshly palloc'd HeapTuple.
*
* Note: it's caller's responsibility to be sure value is of composite type.
* ----------
*/
static HeapTuple
get_tuple_from_datum(Datum value)
{
HeapTupleHeader td = DatumGetHeapTupleHeader(value);
HeapTupleData tmptup;
/* Build a temporary HeapTuple control structure */
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
ItemPointerSetInvalid(&(tmptup.t_self));
tmptup.t_tableOid = InvalidOid;
tmptup.t_data = td;
/* Build a copy and return it */
return heap_copytuple(&tmptup);
}
/* ----------
* get_tupdesc_from_datum get a tuple descriptor for a composite Datum
*
* Returns a pointer to the TupleDesc of the tuple's rowtype.
* Caller is responsible for calling ReleaseTupleDesc when done with it.
*
* Note: it's caller's responsibility to be sure value is of composite type.
* ----------
*/
static TupleDesc
get_tupdesc_from_datum(Datum value)
{
HeapTupleHeader td = DatumGetHeapTupleHeader(value);
Oid tupType;
int32 tupTypmod;
/* Extract rowtype info and find a tupdesc */
tupType = HeapTupleHeaderGetTypeId(td);
tupTypmod = HeapTupleHeaderGetTypMod(td);
return lookup_rowtype_tupdesc(tupType, tupTypmod);
}
/* ----------
* exec_move_row_from_datum Move a composite Datum into a record or row
*
* This is equivalent to get_tuple_from_datum() followed by exec_move_row(),
* but we avoid constructing an intermediate physical copy of the tuple.
* ----------
*/
static void
exec_move_row_from_datum(PLpgSQL_execstate *estate,
PLpgSQL_rec *rec,
PLpgSQL_row *row,
Datum value)
{
HeapTupleHeader td = DatumGetHeapTupleHeader(value);
Oid tupType;
int32 tupTypmod;
TupleDesc tupdesc;
HeapTupleData tmptup;
/* Extract rowtype info and find a tupdesc */
tupType = HeapTupleHeaderGetTypeId(td);
tupTypmod = HeapTupleHeaderGetTypMod(td);
tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
/* Build a temporary HeapTuple control structure */
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
ItemPointerSetInvalid(&(tmptup.t_self));
tmptup.t_tableOid = InvalidOid;
tmptup.t_data = td;
/* Do the move */
exec_move_row(estate, rec, row, &tmptup, tupdesc);
/* Release tupdesc usage count */
ReleaseTupleDesc(tupdesc);
}
/* ---------- /* ----------
* convert_value_to_string Convert a non-null Datum to C string * convert_value_to_string Convert a non-null Datum to C string
* *
......
...@@ -2926,32 +2926,27 @@ make_return_stmt(int location) ...@@ -2926,32 +2926,27 @@ make_return_stmt(int location)
} }
else if (plpgsql_curr_compile->fn_retistuple) else if (plpgsql_curr_compile->fn_retistuple)
{ {
switch (yylex()) /*
{ * We want to special-case simple row or record references for
case K_NULL: * efficiency. So peek ahead to see if that's what we have.
/* we allow this to support RETURN NULL in triggers */ */
break; int tok = yylex();
case T_DATUM: if (tok == T_DATUM && plpgsql_peek() == ';' &&
if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW || (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC) yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC))
{
new->retvarno = yylval.wdatum.datum->dno; new->retvarno = yylval.wdatum.datum->dno;
/* eat the semicolon token that we only peeked at above */
tok = yylex();
Assert(tok == ';');
}
else else
ereport(ERROR, {
(errcode(ERRCODE_DATATYPE_MISMATCH), /* Not (just) a row/record name, so treat as expression */
errmsg("RETURN must specify a record or row variable in function returning row"), plpgsql_push_back_token(tok);
parser_errposition(yylloc))); new->expr = read_sql_expression(';', ";");
break;
default:
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("RETURN must specify a record or row variable in function returning row"),
parser_errposition(yylloc)));
break;
} }
if (yylex() != ';')
yyerror("syntax error");
} }
else else
{ {
...@@ -2994,28 +2989,27 @@ make_return_next_stmt(int location) ...@@ -2994,28 +2989,27 @@ make_return_next_stmt(int location)
} }
else if (plpgsql_curr_compile->fn_retistuple) else if (plpgsql_curr_compile->fn_retistuple)
{ {
switch (yylex()) /*
* We want to special-case simple row or record references for
* efficiency. So peek ahead to see if that's what we have.
*/
int tok = yylex();
if (tok == T_DATUM && plpgsql_peek() == ';' &&
(yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC))
{ {
case T_DATUM:
if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC)
new->retvarno = yylval.wdatum.datum->dno; new->retvarno = yylval.wdatum.datum->dno;
/* eat the semicolon token that we only peeked at above */
tok = yylex();
Assert(tok == ';');
}
else else
ereport(ERROR, {
(errcode(ERRCODE_DATATYPE_MISMATCH), /* Not (just) a row/record name, so treat as expression */
errmsg("RETURN NEXT must specify a record or row variable in function returning row"), plpgsql_push_back_token(tok);
parser_errposition(yylloc))); new->expr = read_sql_expression(';', ";");
break;
default:
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("RETURN NEXT must specify a record or row variable in function returning row"),
parser_errposition(yylloc)));
break;
} }
if (yylex() != ';')
yyerror("syntax error");
} }
else else
new->expr = read_sql_expression(';', ";"); new->expr = read_sql_expression(';', ";");
......
...@@ -442,9 +442,27 @@ plpgsql_append_source_text(StringInfo buf, ...@@ -442,9 +442,27 @@ plpgsql_append_source_text(StringInfo buf,
endlocation - startlocation); endlocation - startlocation);
} }
/*
* Peek one token ahead in the input stream. Only the token code is
* made available, not any of the auxiliary info such as location.
*
* NB: no variable or unreserved keyword lookup is performed here, they will
* be returned as IDENT. Reserved keywords are resolved as usual.
*/
int
plpgsql_peek(void)
{
int tok1;
TokenAuxData aux1;
tok1 = internal_yylex(&aux1);
push_back_token(tok1, &aux1);
return tok1;
}
/* /*
* Peek two tokens ahead in the input stream. The first token and its * Peek two tokens ahead in the input stream. The first token and its
* location the query are returned in *tok1_p and *tok1_loc, second token * location in the query are returned in *tok1_p and *tok1_loc, second token
* and its location in *tok2_p and *tok2_loc. * and its location in *tok2_p and *tok2_loc.
* *
* NB: no variable or unreserved keyword lookup is performed here, they will * NB: no variable or unreserved keyword lookup is performed here, they will
......
...@@ -976,6 +976,7 @@ extern void plpgsql_push_back_token(int token); ...@@ -976,6 +976,7 @@ extern void plpgsql_push_back_token(int token);
extern bool plpgsql_token_is_unreserved_keyword(int token); extern bool plpgsql_token_is_unreserved_keyword(int token);
extern void plpgsql_append_source_text(StringInfo buf, extern void plpgsql_append_source_text(StringInfo buf,
int startlocation, int endlocation); int startlocation, int endlocation);
extern int plpgsql_peek(void);
extern void plpgsql_peek2(int *tok1_p, int *tok2_p, int *tok1_loc, extern void plpgsql_peek2(int *tok1_p, int *tok2_p, int *tok1_loc,
int *tok2_loc); int *tok2_loc);
extern int plpgsql_scanner_errposition(int location); extern int plpgsql_scanner_errposition(int location);
......
...@@ -3624,7 +3624,139 @@ select * from returnqueryf(); ...@@ -3624,7 +3624,139 @@ select * from returnqueryf();
drop function returnqueryf(); drop function returnqueryf();
drop table tabwithcols; drop table tabwithcols;
--
-- Tests for composite-type results
--
create type footype as (x int, y varchar);
-- test: use of variable of composite type in return statement
create or replace function foo() returns footype as $$
declare
v footype;
begin
v := (1, 'hello');
return v;
end;
$$ language plpgsql;
select foo();
foo
-----------
(1,hello)
(1 row)
-- test: use of variable of record type in return statement
create or replace function foo() returns footype as $$
declare
v record;
begin
v := (1, 'hello'::varchar);
return v;
end;
$$ language plpgsql;
select foo();
foo
-----------
(1,hello)
(1 row)
-- test: use of row expr in return statement
create or replace function foo() returns footype as $$
begin
return (1, 'hello'::varchar);
end;
$$ language plpgsql;
select foo();
foo
-----------
(1,hello)
(1 row)
-- this does not work currently (no implicit casting)
create or replace function foo() returns footype as $$
begin
return (1, 'hello');
end;
$$ language plpgsql;
select foo();
ERROR: returned record type does not match expected record type
DETAIL: Returned type unknown does not match expected type character varying in column 2.
CONTEXT: PL/pgSQL function foo() while casting return value to function's return type
-- ... but this does
create or replace function foo() returns footype as $$
begin
return (1, 'hello')::footype;
end;
$$ language plpgsql;
select foo();
foo
-----------
(1,hello)
(1 row)
drop function foo();
-- test: return a row expr as record.
create or replace function foorec() returns record as $$
declare
v record;
begin
v := (1, 'hello');
return v;
end;
$$ language plpgsql;
select foorec();
foorec
-----------
(1,hello)
(1 row)
-- test: return row expr in return statement.
create or replace function foorec() returns record as $$
begin
return (1, 'hello');
end;
$$ language plpgsql;
select foorec();
foorec
-----------
(1,hello)
(1 row)
drop function foorec();
-- test: row expr in RETURN NEXT statement.
create or replace function foo() returns setof footype as $$
begin
for i in 1..3
loop
return next (1, 'hello'::varchar);
end loop;
return next null::footype;
return next (2, 'goodbye')::footype;
end;
$$ language plpgsql;
select * from foo();
x | y
---+---------
1 | hello
1 | hello
1 | hello
|
2 | goodbye
(5 rows)
drop function foo();
-- test: use invalid expr in return statement.
create or replace function foo() returns footype as $$
begin
return 1 + 1;
end;
$$ language plpgsql;
select foo();
ERROR: cannot return non-composite value from function returning composite type
CONTEXT: PL/pgSQL function foo() line 3 at RETURN
drop function foo();
drop type footype;
--
-- Tests for 8.4's new RAISE features -- Tests for 8.4's new RAISE features
--
create or replace function raise_test() returns void as $$ create or replace function raise_test() returns void as $$
begin begin
raise notice '% % %', 1, 2, 3 raise notice '% % %', 1, 2, 3
......
...@@ -2937,7 +2937,119 @@ select * from returnqueryf(); ...@@ -2937,7 +2937,119 @@ select * from returnqueryf();
drop function returnqueryf(); drop function returnqueryf();
drop table tabwithcols; drop table tabwithcols;
--
-- Tests for composite-type results
--
create type footype as (x int, y varchar);
-- test: use of variable of composite type in return statement
create or replace function foo() returns footype as $$
declare
v footype;
begin
v := (1, 'hello');
return v;
end;
$$ language plpgsql;
select foo();
-- test: use of variable of record type in return statement
create or replace function foo() returns footype as $$
declare
v record;
begin
v := (1, 'hello'::varchar);
return v;
end;
$$ language plpgsql;
select foo();
-- test: use of row expr in return statement
create or replace function foo() returns footype as $$
begin
return (1, 'hello'::varchar);
end;
$$ language plpgsql;
select foo();
-- this does not work currently (no implicit casting)
create or replace function foo() returns footype as $$
begin
return (1, 'hello');
end;
$$ language plpgsql;
select foo();
-- ... but this does
create or replace function foo() returns footype as $$
begin
return (1, 'hello')::footype;
end;
$$ language plpgsql;
select foo();
drop function foo();
-- test: return a row expr as record.
create or replace function foorec() returns record as $$
declare
v record;
begin
v := (1, 'hello');
return v;
end;
$$ language plpgsql;
select foorec();
-- test: return row expr in return statement.
create or replace function foorec() returns record as $$
begin
return (1, 'hello');
end;
$$ language plpgsql;
select foorec();
drop function foorec();
-- test: row expr in RETURN NEXT statement.
create or replace function foo() returns setof footype as $$
begin
for i in 1..3
loop
return next (1, 'hello'::varchar);
end loop;
return next null::footype;
return next (2, 'goodbye')::footype;
end;
$$ language plpgsql;
select * from foo();
drop function foo();
-- test: use invalid expr in return statement.
create or replace function foo() returns footype as $$
begin
return 1 + 1;
end;
$$ language plpgsql;
select foo();
drop function foo();
drop type footype;
--
-- Tests for 8.4's new RAISE features -- Tests for 8.4's new RAISE features
--
create or replace function raise_test() returns void as $$ create or replace function raise_test() returns void as $$
begin begin
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment