*** ./doc/src/sgml/plpgsql.sgml.orig 2006-08-23 14:29:07.000000000 +0200 --- ./doc/src/sgml/plpgsql.sgml 2006-08-23 14:31:43.000000000 +0200 *************** *** 1586,1599 **** - When returning a scalar type, any expression can be used. The - expression's result will be automatically cast into the - function's return type as described for assignments. To return a - composite (row) value, you must write a record or row variable - as the expression. - - - If you declared the function with output parameters, write just RETURN with no expression. The current values of the output parameter variables will be returned. --- 1586,1591 ---- *** ./src/pl/plpgsql/src/gram.y.orig 2006-08-23 10:39:35.000000000 +0200 --- ./src/pl/plpgsql/src/gram.y 2006-08-24 13:49:51.000000000 +0200 *************** *** 2001,2010 **** --- 2001,2013 ---- make_return_stmt(int lineno) { PLpgSQL_stmt_return *new; + int tok; new = palloc0(sizeof(PLpgSQL_stmt_return)); new->cmd_type = PLPGSQL_STMT_RETURN; new->lineno = lineno; + new->compat_tupdesc = PLPGSQL_TUPLE_DESC_UNKNOWN; + new->pcache = NULL; new->expr = NULL; new->retvarno = -1; *************** *** 2026,2032 **** } else if (plpgsql_curr_compile->fn_retistuple) { ! switch (yylex()) { case K_NULL: /* we allow this to support RETURN NULL in triggers */ --- 2029,2036 ---- } else if (plpgsql_curr_compile->fn_retistuple) { ! tok = yylex(); ! switch (tok) { case K_NULL: /* we allow this to support RETURN NULL in triggers */ *************** *** 2040,2051 **** new->retvarno = yylval.rec->recno; break; default: ! yyerror("RETURN must specify a record or row variable in function returning tuple"); break; } ! if (yylex() != ';') ! yyerror("RETURN must specify a record or row variable in function returning tuple"); } else { --- 2044,2061 ---- new->retvarno = yylval.rec->recno; break; + case T_WORD: + case '(': + plpgsql_push_back_token(tok); + new->expr = plpgsql_read_expression(';',";"); + break; + default: ! yyerror("RETURN must specify a value in function returning tuple"); break; } ! if (!new->expr && yylex() != ';') ! yyerror("RETURN must specify a value in function returning tuple"); } else { *************** *** 2065,2070 **** --- 2075,2081 ---- make_return_next_stmt(int lineno) { PLpgSQL_stmt_return_next *new; + int tok; if (!plpgsql_curr_compile->fn_retset) yyerror("cannot use RETURN NEXT in a non-SETOF function"); *************** *** 2072,2077 **** --- 2083,2090 ---- new = palloc0(sizeof(PLpgSQL_stmt_return_next)); new->cmd_type = PLPGSQL_STMT_RETURN_NEXT; new->lineno = lineno; + new->compat_tupdesc = PLPGSQL_TUPLE_DESC_UNKNOWN; + new->pcache = NULL; new->expr = NULL; new->retvarno = -1; *************** *** 2083,2089 **** } else if (plpgsql_curr_compile->fn_retistuple) { ! switch (yylex()) { case T_ROW: new->retvarno = yylval.row->rowno; --- 2096,2103 ---- } else if (plpgsql_curr_compile->fn_retistuple) { ! tok = yylex(); ! switch (tok) { case T_ROW: new->retvarno = yylval.row->rowno; *************** *** 2093,2104 **** new->retvarno = yylval.rec->recno; break; default: ! yyerror("RETURN NEXT must specify a record or row variable in function returning tuple"); break; } ! if (yylex() != ';') ! yyerror("RETURN NEXT must specify a record or row variable in function returning tuple"); } else new->expr = plpgsql_read_expression(';', ";"); --- 2107,2124 ---- new->retvarno = yylval.rec->recno; break; + case T_WORD: + case '(': + plpgsql_push_back_token(tok); + new->expr = plpgsql_read_expression(';',";"); + break; + default: ! yyerror("RETURN NEXT must a value in function returning tuple"); break; } ! if (!new->expr && yylex() != ';') ! yyerror("RETURN NEXT must specify a value in function returning tuple"); } else new->expr = plpgsql_read_expression(';', ";"); *** ./src/pl/plpgsql/src/pl_exec.c.orig 2006-08-23 11:30:40.000000000 +0200 --- ./src/pl/plpgsql/src/pl_exec.c 2006-08-24 16:39:05.000000000 +0200 *************** *** 155,160 **** --- 155,162 ---- static void exec_set_found(PLpgSQL_execstate *estate, bool state); static void free_var(PLpgSQL_var *var); + static void exec_return_coerce_tuple(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt); + /* ---------- * plpgsql_exec_function Called by the call handler for *************** *** 1805,1817 **** { if (estate->retistuple) { ! exec_run_select(estate, stmt->expr, 1, NULL); ! if (estate->eval_processed > 0) ! { ! estate->retval = (Datum) estate->eval_tuptable->vals[0]; ! estate->rettupdesc = estate->eval_tuptable->tupdesc; ! estate->retisnull = false; ! } } else { --- 1807,1822 ---- { if (estate->retistuple) { ! estate->retval = exec_eval_expr(estate, stmt->expr, &(estate->retisnull), &(estate->rettype)); ! ! if (!(estate->rettype == RECORDOID || get_typtype(estate->rettype) == 'c')) ! ereport(ERROR, ! (errcode(ERRCODE_DATATYPE_MISMATCH), ! errmsg("wrong result type supplied in RETURN"))); ! ! ! if (!estate->retisnull) ! exec_return_coerce_tuple(estate, stmt); } else { *************** *** 1863,1868 **** --- 1868,1874 ---- if (estate->tuple_store == NULL) exec_init_tuple_store(estate); + /* rettupdesc will be filled by exec_init_tuple_store */ tupdesc = estate->rettupdesc; natts = tupdesc->natts; *************** *** 1940,1965 **** bool isNull; Oid rettype; ! if (natts != 1) ! ereport(ERROR, ! (errcode(ERRCODE_DATATYPE_MISMATCH), ! errmsg("wrong result type supplied in RETURN NEXT"))); ! ! retval = exec_eval_expr(estate, ! stmt->expr, ! &isNull, ! &rettype); ! ! /* coerce type if needed */ ! retval = exec_simple_cast_value(retval, ! rettype, ! tupdesc->attrs[0]->atttypid, ! tupdesc->attrs[0]->atttypmod, ! isNull); ! ! tuple = heap_form_tuple(tupdesc, &retval, &isNull); ! ! free_tuple = true; exec_eval_cleanup(estate); } --- 1946,1993 ---- bool isNull; Oid rettype; ! if (estate->retistuple) ! { ! estate->retval = exec_eval_expr(estate, stmt->expr, &(estate->retisnull), &(estate->rettype)); ! ! if (!(estate->rettype == RECORDOID || get_typtype(estate->rettype) == 'c')) ! ereport(ERROR, ! (errcode(ERRCODE_DATATYPE_MISMATCH), ! errmsg("wrong result type supplied in RETURN"))); ! ! tuple = NULL; ! ! if (!estate->retisnull) ! { ! /* coerce type if needed */ ! exec_return_coerce_tuple(estate, (PLpgSQL_stmt_return *) stmt); ! tuple = (HeapTuple) estate->retval; ! free_tuple = true; ! } ! } ! else ! { ! if (natts != 1) ! ereport(ERROR, ! (errcode(ERRCODE_DATATYPE_MISMATCH), ! errmsg("wrong result type supplied in RETURN NEXT"))); ! ! retval = exec_eval_expr(estate, ! stmt->expr, ! &isNull, ! &rettype); ! ! /* coerce type if needed */ ! retval = exec_simple_cast_value(retval, ! rettype, ! tupdesc->attrs[0]->atttypid, ! tupdesc->attrs[0]->atttypmod, ! isNull); ! ! tuple = heap_form_tuple(tupdesc, &retval, &isNull); ! ! free_tuple = true; ! } exec_eval_cleanup(estate); } *************** *** 4683,4685 **** --- 4711,4863 ---- var->freeval = false; } } + + + /* + * Cast tuple for desired RSI tuple descriptor + * + */ + + static HeapTuple + coerce_to_tuple(PLpgSQL_execstate * estate, + HeapTuple tuple, + TupleDesc tupdesc, + TupleDesc reqtupdesc, + PLpgSQL_stmt_return *stmt) + { + int rnatts = reqtupdesc->natts; + int natts = tupdesc->natts; + PLpgSQL_cast_tuple_cache *cache = stmt->pcache; + int i; + + if (!cache) + { + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(estate->err_func->fn_cxt); + + cache = palloc(sizeof(PLpgSQL_cast_tuple_cache)); + cache->values = (Datum *) palloc0(natts * sizeof(Datum)); + cache->rvalues = (Datum *) palloc0(rnatts * sizeof(Datum)); + cache->nulls = (bool *) palloc(natts * sizeof(bool)); + cache->rnulls = (bool *) palloc(rnatts * sizeof(bool)); + + MemoryContextSwitchTo(oldcontext); + + stmt->pcache = cache; + } + else + { + memset(cache->values, 0, natts * sizeof(Datum)); + memset(cache->rvalues, 0, rnatts * sizeof(bool)); + } + + heap_deform_tuple(tuple, tupdesc, cache->values, cache->nulls); + + for (i = 0; i < rnatts; i++) + { + if (i < natts) + { + cache->rvalues[i] = exec_simple_cast_value(cache->values[i], + tupdesc->attrs[i]->atttypid, + reqtupdesc->attrs[i]->atttypid, + reqtupdesc->attrs[i]->atttypmod, + cache->nulls[i]); + cache->rnulls[i] = cache->nulls[i]; + } + else + { + /* missing values */ + cache->rnulls[i] = true; + } + } + + tuple = heap_form_tuple(reqtupdesc, cache->rvalues, cache->rnulls); + + return tuple; + } + + static void + exec_return_coerce_tuple(PLpgSQL_execstate *estate, + PLpgSQL_stmt_return *stmt) + { + int tupType; + int tupTypmod; + TupleDesc in_tupdesc = NULL; + HeapTupleData td; + + td.t_len = HeapTupleHeaderGetDatumLength((HeapTupleHeader) estate->retval); + ItemPointerSetInvalid(&(td.t_self)); + td.t_tableOid = InvalidOid; + td.t_data = (HeapTupleHeader) estate->retval; + + /* + * if not neccessery I don't create new in_tupdesc + */ + + switch (stmt->compat_tupdesc) + { + case PLPGSQL_TUPLE_DESC_UNKNOWN: + case PLPGSQL_TUPLE_DESC_NOTRSI: + case PLPGSQL_TUPLE_DESC_INCOMPATIBLE: + { + tupType = HeapTupleHeaderGetTypeId((HeapTupleHeader) estate->retval); + tupTypmod = HeapTupleHeaderGetTypMod((HeapTupleHeader) estate->retval); + in_tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + + if (in_tupdesc == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("wrong result type supplied in RETURN"))); + + if (stmt->compat_tupdesc == PLPGSQL_TUPLE_DESC_UNKNOWN) + { + if (!estate->rsi) + { + stmt->compat_tupdesc = PLPGSQL_TUPLE_DESC_NOTRSI; + } + else + { + stmt->compat_tupdesc = (IsA(estate->rsi, ReturnSetInfo) + && estate->rsi->expectedDesc != NULL + && compatible_tupdesc(estate->rsi->expectedDesc, in_tupdesc)) ? + PLPGSQL_TUPLE_DESC_COMPATIBLE : PLPGSQL_TUPLE_DESC_INCOMPATIBLE; + } + } + + if (stmt->compat_tupdesc == PLPGSQL_TUPLE_DESC_INCOMPATIBLE) + { + estate->retval = (Datum) coerce_to_tuple(estate, + &td, + in_tupdesc, + estate->rsi->expectedDesc, + stmt); + estate->rettupdesc = estate->rsi->expectedDesc; + + break; + } + else if (stmt->compat_tupdesc == PLPGSQL_TUPLE_DESC_NOTRSI) + { + estate->retval = (Datum) heap_copytuple(&td); + estate->rettupdesc = in_tupdesc; + + break; + } + } + case PLPGSQL_TUPLE_DESC_COMPATIBLE: + { + estate->retval = (Datum) heap_copytuple(&td); + estate->rettupdesc = estate->rsi->expectedDesc; + } + } + if ((HeapTuple) estate->retval == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("wrong record type supplied in RETURN NEXT"))); + if (in_tupdesc) + ReleaseTupleDesc(in_tupdesc); + + exec_eval_cleanup(estate); + } + + *** ./src/pl/plpgsql/src/plpgsql.h.orig 2006-08-15 21:01:17.000000000 +0200 --- ./src/pl/plpgsql/src/plpgsql.h 2006-08-24 16:10:57.000000000 +0200 *************** *** 117,122 **** --- 117,135 ---- PLPGSQL_GETDIAG_RESULT_OID }; + /* --------- + * Used for output's coercing of result tuple + * --------- + */ + + enum + { + PLPGSQL_TUPLE_DESC_COMPATIBLE, + PLPGSQL_TUPLE_DESC_INCOMPATIBLE, + PLPGSQL_TUPLE_DESC_NOTRSI, + PLPGSQL_TUPLE_DESC_UNKNOWN + }; + /********************************************************************** * Node and structure definitions *************** *** 466,476 **** --- 479,498 ---- PLpgSQL_expr *cond; } PLpgSQL_stmt_exit; + typedef struct + { + Datum *values; + Datum *rvalues; + bool *nulls; + bool *rnulls; + } PLpgSQL_cast_tuple_cache; typedef struct { /* RETURN statement */ int cmd_type; int lineno; + int compat_tupdesc; + PLpgSQL_cast_tuple_cache *pcache; /* cache for deform_... */ PLpgSQL_expr *expr; int retvarno; } PLpgSQL_stmt_return; *************** *** 479,487 **** --- 501,512 ---- { /* RETURN NEXT statement */ int cmd_type; int lineno; + int compat_tupdesc; + PLpgSQL_cast_tuple_cache *pcache; /* cache for heap_deform_tuple */ PLpgSQL_expr *expr; int retvarno; } PLpgSQL_stmt_return_next; + /* offset for compat_tupdesc and pcache have to be equal with PLpgSQL_stmt_return */ typedef struct { /* RAISE statement */ *** ./src/test/regress/sql/plpgsql.sql.orig 2006-08-23 14:20:53.000000000 +0200 --- ./src/test/regress/sql/plpgsql.sql 2006-08-24 16:43:34.000000000 +0200 *************** *** 2440,2442 **** --- 2440,2627 ---- select footest(); drop function footest(); + + -- test return can contain any valid row expression + + + create type __trt as (x integer, y integer, z text); + + create or replace function return_row1() returns __trt as $$ + declare r __trt; + begin r := row(1,2,3); + return r; + end; + $$ language plpgsql; + select return_row1(); + + -- should fail, record variable isn't cast to __trt type + create or replace function return_row2() returns __trt as $$ + declare r record; + begin r := row(1,2,3); + return r; + end; + $$ language plpgsql; + select return_row2(); + + -- should fail, missing rsi for neccessery conversion + create or replace function return_row3() returns __trt as $$ + begin + return row(1,2,3); + end; + $$ language plpgsql; + select return_row3(); + + create or replace function return_row4() returns __trt as $$ + begin + return (1,2,'3'::text); + end; + $$ language plpgsql; + select return_row4(); + + create or replace function return_row5() returns record as $$ + begin + return row(1,2,3); + end; + $$ language plpgsql; + select return_row5(); + + create or replace function return_row6() returns setof __trt as $$ + begin + return next row(1,2,3::text); + return next row(4,5,6::text); + return; + end; + $$ language plpgsql; + select * from return_row6(); + + create or replace function return_row7() returns setof __trt as $$ + declare r __trt; + begin + r := row(1,2,3); + return next r; + r := row(4,5,6); + return next r; + return; + end; + $$ language plpgsql; + select * from return_row7(); + + create or replace function return_row8() returns setof __trt as $$ + declare r record; + begin + r := row(1,2,3::text); + return next r; + r := row(4,5,6::text); + return next r; + return; + end; + $$ language plpgsql; + select * from return_row8(); + + create or replace function return_row9() returns setof record as $$ + declare r record; + begin + r := row(1,2,3::text); + return next r; + r := row(4,5,6::text); + return next r; + return; + end; + $$ language plpgsql; + select * from return_row9() as (x integer, y integer, z text); + + create or replace function return_row10() returns setof record as $$ + begin + return next row(1,2,3::text); + return next row(4,5,6::text); + return; + end; + $$ language plpgsql; + select * from return_row10() as (x integer, y integer, z text); + + create or replace function return_row11() returns setof float as $$ + declare a float = 1.3; + begin + return next sin(1.1); + return next sin(1.2); + return next sin(a); + return; + end; + $$ language plpgsql; + select * from return_row11(); + + create or replace function return_row12() returns float as $$ + declare a float = 1.2; + begin + return sin(a); + end; + $$ language plpgsql; + select return_row12(); + + -- should fail: only row function is allowed + create or replace function return_row13() returns setof record as $$ + begin + return next sin(1); + return next sin(2); + return; + end; + $$ language plpgsql; + select * from return_row13() as (x integer, y integer, z text); + + create or replace function return_row14() returns setof record as $$ + begin + return next (1,2,3); + return next (1,2,3::text); + return next row(1,2,3::text); + return next return_row5(); + return next return_row1(); + return; + end; + $$ language plpgsql; + select * from return_row14() as (x integer, y integer, z text); + + create or replace function return_row15() returns record as $$ + begin + return return_row4(); + end; + $$ language plpgsql; + select return_row15(); + select * from return_row15() as (a integer, b integer, c text); + + create or replace function return_row16() returns record as $$ + begin + return return_row4(); + end; + $$ language plpgsql; + select return_row16(); + select * from return_row16() as (a integer, b integer, c text); + + create or replace function return_row17() returns setof record as $$ + begin + return next (1,2,3, NULL, NULL); + return next (1,2,3::text, NULL, NULL); + return next row(1,2,3::text,4,5); + return; + end; + $$ language plpgsql; + select * from return_row17() as (x integer, y integer, z integer, i integer); + + drop function return_row1(); + drop function return_row2(); + drop function return_row3(); + drop function return_row4(); + drop function return_row5(); + drop function return_row6(); + drop function return_row7(); + drop function return_row8(); + drop function return_row9 (); + drop function return_row10(); + drop function return_row11(); + drop function return_row12(); + drop function return_row13(); + drop function return_row14(); + drop function return_row15(); + drop function return_row16(); + drop function return_row17(); + + drop type __trt;