*** ./doc/src/sgml/plpgsql.sgml.orig 2007-04-29 03:21:08.000000000 +0200 --- ./doc/src/sgml/plpgsql.sgml 2007-05-06 10:00:58.000000000 +0200 *************** *** 1576,1584 **** <command>RETURN</> ! ! RETURN expression; ! RETURN with an expression terminates the --- 1576,1589 ---- <command>RETURN</> ! ! ! RETURN expression; ! ! ! RETURN TABLE ( query ); ! ! RETURN with an expression terminates the *************** *** 1603,1608 **** --- 1608,1622 ---- + If your function returns a set, you can use table expression. + An query is evaluated and result set + is returned to the caller. You cannot mix RETURN TABLE + and RETURN NEXT statements in one function. + Performance might be poor, if result set is very large. The reason + is described below. + + + If you declared the function to return void, a RETURN statement can be used to exit the function early; but do not write an expression following *** ./src/pl/plpgsql/src/gram.y.orig 2007-04-29 03:21:09.000000000 +0200 --- ./src/pl/plpgsql/src/gram.y 2007-05-06 10:16:56.000000000 +0200 *************** *** 30,36 **** static PLpgSQL_type *read_datatype(int tok); static PLpgSQL_stmt *make_execsql_stmt(const char *sqlstart, int lineno); static PLpgSQL_stmt_fetch *read_fetch_direction(void); ! static PLpgSQL_stmt *make_return_stmt(int lineno); static PLpgSQL_stmt *make_return_next_stmt(int lineno); static void check_assignable(PLpgSQL_datum *datum); static void read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, --- 30,36 ---- static PLpgSQL_type *read_datatype(int tok); static PLpgSQL_stmt *make_execsql_stmt(const char *sqlstart, int lineno); static PLpgSQL_stmt_fetch *read_fetch_direction(void); ! static PLpgSQL_stmt *make_return_stmt(int lineno, bool is_tblexpr); static PLpgSQL_stmt *make_return_next_stmt(int lineno); static void check_assignable(PLpgSQL_datum *datum); static void read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, *************** *** 196,201 **** --- 196,202 ---- %token K_REVERSE %token K_SCROLL %token K_STRICT + %token K_TABLE %token K_THEN %token K_TO %token K_TYPE *************** *** 1185,1194 **** { $$ = make_return_next_stmt($2); } else { plpgsql_push_back_token(tok); ! $$ = make_return_stmt($2); } } ; --- 1186,1200 ---- { $$ = make_return_next_stmt($2); } + else if (tok == K_TABLE) + { + $$ = make_return_stmt($2, true); + + } else { plpgsql_push_back_token(tok); ! $$ = make_return_stmt($2, false); } } ; *************** *** 2105,2111 **** static PLpgSQL_stmt * ! make_return_stmt(int lineno) { PLpgSQL_stmt_return *new; --- 2111,2117 ---- static PLpgSQL_stmt * ! make_return_stmt(int lineno, bool is_tblexpr) { PLpgSQL_stmt_return *new; *************** *** 2114,2121 **** new->lineno = lineno; new->expr = NULL; new->retvarno = -1; ! if (plpgsql_curr_compile->fn_retset) { if (yylex() != ';') yyerror("RETURN cannot have a parameter in function returning set; use RETURN NEXT"); --- 2120,2141 ---- new->lineno = lineno; new->expr = NULL; new->retvarno = -1; + new->is_tblexpr = is_tblexpr; + + if (is_tblexpr) + { + if (!plpgsql_curr_compile->fn_retset) + yyerror("table expression can be used only if function is SETOF or TABLE function"); ! if (yylex() != '(') ! yyerror("expected '('"); ! ! new->expr = read_sql_construct(')', 0, ")", "", false, true, NULL); ! ! if (yylex() != ';') ! yyerror("expected ';'"); ! } ! else if (plpgsql_curr_compile->fn_retset) { if (yylex() != ';') yyerror("RETURN cannot have a parameter in function returning set; use RETURN NEXT"); *** ./src/pl/plpgsql/src/pl_comp.c.orig 2007-05-06 09:56:17.000000000 +0200 --- ./src/pl/plpgsql/src/pl_comp.c 2007-05-06 10:00:59.000000000 +0200 *************** *** 399,404 **** --- 399,408 ---- PLpgSQL_variable *argvariable; int argitemtype; + /* skip table function columns */ + if (argmode == PROARGMODE_TABLE) + continue; + /* Create $n name for variable */ snprintf(buf, sizeof(buf), "$%d", i + 1); *** ./src/pl/plpgsql/src/pl_exec.c.orig 2007-04-29 03:21:09.000000000 +0200 --- ./src/pl/plpgsql/src/pl_exec.c 2007-05-06 10:00:59.000000000 +0200 *************** *** 175,180 **** --- 175,181 ---- static void exec_set_found(PLpgSQL_execstate *estate, bool state); static void plpgsql_create_econtext(PLpgSQL_execstate *estate); static void free_var(PLpgSQL_var *var); + static void materialize_portal(PLpgSQL_execstate *estate, Portal portal); /* ---------- *************** *** 1871,1877 **** --- 1872,1888 ---- * the work will be done at the top level. */ if (estate->retisset) + { + if (stmt->is_tblexpr) + { + Portal portal; + + exec_run_select(estate, stmt->expr, 0, &portal); + materialize_portal(estate, portal); + } + return PLPGSQL_RC_RETURN; + } /* initialize for null result (possibly a tuple) */ estate->retval = (Datum) 0; *************** *** 4984,4986 **** --- 4995,5059 ---- var->freeval = false; } } + + + /* + * Fetch all rows from portal and store its in list of tuples returned + * by the current SRF. + */ + static void + materialize_portal(PLpgSQL_execstate *estate, Portal portal) + { + SPITupleTable *tuptab; + int n; + int i; + TupleDesc tupdesc; + int natts; + HeapTuple tuple; + + if (estate->tuple_store) + elog(ERROR, "cannot mix return table and return next statement"); + + exec_init_tuple_store(estate); + + tupdesc = estate->rettupdesc; + natts = tupdesc->natts; + + SPI_cursor_fetch(portal, true, 10); + n = SPI_processed; + tuptab = SPI_tuptable; + + if (!compatible_tupdesc(tupdesc, tuptab->tupdesc)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("incompatible TABLE expression in RETURN statement"))); + + while (n > 0) + { + for (i = 0; i < n; i++) + { + tuple = tuptab->vals[i]; + + if (HeapTupleIsValid(tuple)) + { + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt); + tuplestore_puttuple(estate->tuple_store, tuple); + MemoryContextSwitchTo(oldcxt); + } + } + + SPI_freetuptable(tuptab); + + /* + * Fetch the next 50 tuples + */ + SPI_cursor_fetch(portal, true, 50); + n = SPI_processed; + tuptab = SPI_tuptable; + } + + SPI_freetuptable(tuptab); + SPI_cursor_close(portal); + } *** ./src/pl/plpgsql/src/pl_funcs.c.orig 2007-04-29 03:21:09.000000000 +0200 --- ./src/pl/plpgsql/src/pl_funcs.c 2007-05-06 10:00:59.000000000 +0200 *************** *** 857,862 **** --- 857,864 ---- { dump_ind(); printf("RETURN "); + if (stmt->is_tblexpr) + printf("TABLE "); if (stmt->retvarno >= 0) printf("variable %d", stmt->retvarno); else if (stmt->expr != NULL) *** ./src/pl/plpgsql/src/plpgsql.h.orig 2007-04-29 03:21:09.000000000 +0200 --- ./src/pl/plpgsql/src/plpgsql.h 2007-05-06 10:00:59.000000000 +0200 *************** *** 483,488 **** --- 483,489 ---- int lineno; PLpgSQL_expr *expr; int retvarno; + bool is_tblexpr; } PLpgSQL_stmt_return; typedef struct *** ./src/pl/plpgsql/src/scan.l.orig 2007-04-29 03:21:09.000000000 +0200 --- ./src/pl/plpgsql/src/scan.l 2007-05-06 10:00:59.000000000 +0200 *************** *** 159,164 **** --- 159,165 ---- row_count { return K_ROW_COUNT; } scroll { return K_SCROLL; } strict { return K_STRICT; } + table { return K_TABLE; } then { return K_THEN; } to { return K_TO; } type { return K_TYPE; } *** ./src/test/regress/expected/plpgsql.out.orig 2007-04-29 03:21:09.000000000 +0200 --- ./src/test/regress/expected/plpgsql.out 2007-05-06 10:47:19.000000000 +0200 *************** *** 3051,3053 **** --- 3051,3124 ---- (3 rows) drop function sc_test(); + + -- table functions test + create table tbl_test( + a int, + b int + ); + insert into tbl_test select a, b from generate_series(1,5) a(a), generate_series(1,5) b(b); + --should fail, non unique column names + create function tbl_fce_test(a int) + returns table(a int, a int) as $$ + begin + return table(select * from tbl_test t where t.a < a); + end; + $$ language plpgsql; + ERROR: column "a" duplicated + -- should runtime error + create function tbl_fce_test(a int) + returns table(a int, b int) as $$ + begin + -- nonexisting column + raise notice '%', b; + return table(select * from tbl_test t where t.a < a); + end; + $$ language plpgsql; + select * from tbl_fce_test(3) where a > 2; + ERROR: column "b" does not exist + LINE 1: SELECT b + ^ + QUERY: SELECT b + CONTEXT: PL/pgSQL function "tbl_fce_test" line 3 at raise + -- should work + create or replace function tbl_fce_test(a int) + returns table(a int, b int) as $$ + begin + return table(select * from tbl_test t where t.a < a); + end; + $$ language plpgsql; + select * from tbl_fce_test(3) where a >= 2; + a | b + ---+--- + 2 | 1 + 2 | 2 + 2 | 3 + 2 | 4 + 2 | 5 + (5 rows) + + create function tbl_fce_test2() + returns table (o int) as $$ + begin + return table(select a from tbl_test t where t.a < 3); + end; + $$ language plpgsql; + select * from tbl_fce_test2(); + o + --- + 1 + 1 + 1 + 1 + 1 + 2 + 2 + 2 + 2 + 2 + (10 rows) + + drop function tbl_fce_test2(); + drop function tbl_fce_test(int); + drop table tbl_test; *** ./src/test/regress/sql/plpgsql.sql.orig 2007-04-29 03:21:09.000000000 +0200 --- ./src/test/regress/sql/plpgsql.sql 2007-05-06 10:36:29.000000000 +0200 *************** *** 2535,2537 **** --- 2535,2586 ---- drop function sc_test(); + + + -- table functions test + create table tbl_test( + a int, + b int + ); + + insert into tbl_test select a, b from generate_series(1,5) a(a), generate_series(1,5) b(b); + + --should fail, non unique column names + create function tbl_fce_test(a int) + returns table(a int, a int) as $$ + begin + return table(select * from tbl_test t where t.a < a); + end; + $$ language plpgsql; + + -- should runtime error + create function tbl_fce_test(a int) + returns table(a int, b int) as $$ + begin + -- nonexisting column + raise notice '%', b; + return table(select * from tbl_test t where t.a < a); + end; + $$ language plpgsql; + select * from tbl_fce_test(3) where a > 2; + + -- should work + create or replace function tbl_fce_test(a int) + returns table(a int, b int) as $$ + begin + return table(select * from tbl_test t where t.a < a); + end; + $$ language plpgsql; + select * from tbl_fce_test(3) where a >= 2; + + create function tbl_fce_test2() + returns table (o int) as $$ + begin + return table(select a from tbl_test t where t.a < 3); + end; + $$ language plpgsql; + select * from tbl_fce_test2(); + + drop function tbl_fce_test2(); + drop function tbl_fce_test(int); + drop table tbl_test;