*** ./doc/src/sgml/plpgsql.sgml.orig 2007-02-11 12:01:48.000000000 +0100 --- ./doc/src/sgml/plpgsql.sgml 2007-02-11 18:54:16.000000000 +0100 *************** *** 1567,1575 **** <command>RETURN</> ! ! RETURN expression; ! RETURN with an expression terminates the --- 1567,1580 ---- <command>RETURN</> ! ! ! RETURN expression; ! ! ! RETURN TABLE ( query ); ! ! RETURN with an expression terminates the *************** *** 1594,1599 **** --- 1599,1613 ---- + 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 *** ./doc/src/sgml/ref/create_function.sgml.orig 2007-02-11 09:55:06.000000000 +0100 --- ./doc/src/sgml/ref/create_function.sgml 2007-02-11 10:28:08.000000000 +0100 *************** *** 21,27 **** CREATE [ OR REPLACE ] FUNCTION name ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ! [ RETURNS rettype ] { LANGUAGE langname | IMMUTABLE | STABLE | VOLATILE | CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT --- 21,28 ---- CREATE [ OR REPLACE ] FUNCTION name ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ! [ RETURNS rettype ! | RETURNS TABLE ( colname coltype [, ...] ) ] { LANGUAGE langname | IMMUTABLE | STABLE | VOLATILE | CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT *************** *** 387,392 **** --- 388,427 ---- + + colname + + + + The name of an output table column. + + + + + + coltype + + + + The data type(s) of output table column. + + + Depending on the implementation language it might also be allowed + to specify pseudotypes such as cstring. + Pseudotypes indicate that the actual argument type is either + incompletely specified, or outside the set of ordinary SQL data types. + + + The type of a column is referenced by writing + tablename.columnname%TYPE. + Using this feature can sometimes help make a function independent of + changes to the definition of a table. + + + + *************** *** 516,522 **** A CREATE FUNCTION command is defined in SQL:1999 and later. The PostgreSQL version is similar but not fully compatible. The attributes are not portable, neither are the ! different available languages. --- 551,557 ---- A CREATE FUNCTION command is defined in SQL:1999 and later. The PostgreSQL version is similar but not fully compatible. The attributes are not portable, neither are the ! different available languages. TABLE functions are defined in SQL:2003. *** ./doc/src/sgml/xfunc.sgml.orig 2007-02-11 09:18:54.000000000 +0100 --- ./doc/src/sgml/xfunc.sgml 2007-02-11 09:45:11.000000000 +0100 *************** *** 102,107 **** --- 102,115 ---- + TABLEfunctionAn SQL function can + declared to return table specified by function's retun table as + TABLE(somecolumns). In this case + all rows of the last query's result are returned. Furteher details + appear bellow. + + + The body of an SQL function must be a list of SQL statements separated by semicolons. A semicolon after the last statement is optional. Unless the function is declared to return *************** *** 713,718 **** --- 721,784 ---- + <acronym>SQL</acronym> Table Functions + + + When an SQL function is declared as returning + TABLE(somecolumns), the function's final + SELECT query is executed to completion, and each row it + outputs is returned as an element of the result set. + + + + This feature is normally used when calling the function in the FROM + clause. In this case each row returned by the function becomes + a row of the table seen by the query. For example, assume that + table foo has the same contents as above, and we say: + + + CREATE FUNCTION getfoo(int) + RETURNS TABLE(id integer, subid integer, name varchar) AS $$ + SELECT * FROM foo WHERE fooid = $1; + $$ LANGUAGE SQL; + + SELECT * FROM getfoo(1) AS t1; + + + Then we would get: + + id | subid | name + -----+-------+------ + 1 | 1 | Joe + 1 | 2 | Ed + (2 rows) + + + + + Returned table can have one or more columns; + + + CREATE FUNCTION listchildren(text) RETURNS TABLE(name text) AS $$ + SELECT name FROM nodes WHERE parent = $1 + $$ LANGUAGE SQL; + + SELECT * FROM listchildren('Top'); + + + Then we would get: + + name + -------- + Child1 + Child2 + Child3 + (3 rows) + + + + + Polymorphic <acronym>SQL</acronym> Functions *** ./src/backend/commands/functioncmds.c.orig 2007-02-09 23:16:48.000000000 +0100 --- ./src/backend/commands/functioncmds.c 2007-02-09 23:39:51.000000000 +0100 *************** *** 203,218 **** (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("functions cannot accept set arguments"))); ! if (fp->mode != FUNC_PARAM_OUT) ! inTypes[inCount++] = toid; ! ! if (fp->mode != FUNC_PARAM_IN) { ! if (outCount == 0) /* save first OUT param's type */ ! *requiredResultType = toid; ! outCount++; } allTypes[i] = ObjectIdGetDatum(toid); paramModes[i] = CharGetDatum(fp->mode); --- 203,224 ---- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("functions cannot accept set arguments"))); ! switch (fp->mode) { ! case FUNC_PARAM_IN: ! inTypes[inCount++] = toid; ! break; ! case FUNC_PARAM_INOUT: ! inTypes[inCount++] = toid; ! case FUNC_PARAM_OUT: ! case FUNC_PARAM_TABLE: ! /* save first OUT param's type */ ! if (outCount == 0) ! *requiredResultType = toid; ! outCount++; } + allTypes[i] = ObjectIdGetDatum(toid); paramModes[i] = CharGetDatum(fp->mode); *** ./src/backend/parser/gram.y.orig 2007-02-09 22:44:53.000000000 +0100 --- ./src/backend/parser/gram.y 2007-02-10 17:18:53.000000000 +0100 *************** *** 109,114 **** --- 109,117 ---- static void doNegateFloat(Value *v); static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args); + static List *mergeTblFuncParameters(List *func_args, List *columns); + static TypeName *TblFuncTypeName(List *colums); + %} %name-prefix="base_yy" *************** *** 247,252 **** --- 250,256 ---- TableFuncElementList opt_type_modifiers prep_type_clause execute_param_clause using_clause returning_clause + tbl_func_column_list %type into_clause OptTempTableName *************** *** 255,260 **** --- 259,266 ---- %type arg_class %type func_return func_type + %type tbl_func_column + %type TriggerForType OptTemp %type OnCommitOption *************** *** 3906,3911 **** --- 3912,3930 ---- n->withClause = $7; $$ = (Node *)n; } + | CREATE opt_or_replace FUNCTION func_name func_args + RETURNS TABLE '(' tbl_func_column_list ')' createfunc_opt_list opt_definition + { + CreateFunctionStmt *n = makeNode(CreateFunctionStmt); + n->replace = $2; + n->funcname = $4; + n->parameters = mergeTblFuncParameters($5, $9); + n->returnType = TblFuncTypeName($9); + n->returnType->location = @8; + n->options = $11; + n->withClause = $12; + $$ = (Node *)n; + } ; opt_or_replace: *************** *** 4108,4113 **** --- 4127,4156 ---- | /*EMPTY*/ { $$ = NIL; } ; + /* + * Culumn list for table function definition + */ + tbl_func_column_list: + tbl_func_column + { + $$ = list_make1($1); + } + | tbl_func_column_list ',' tbl_func_column + { + $$ = lappend($1, $3); + } + ; + + tbl_func_column: param_name func_type + { + FunctionParameter *n = makeNode(FunctionParameter); + n->name = $1; + n->argType = $2; + n->mode = FUNC_PARAM_TABLE; + $$ = n; + } + ; + /***************************************************************************** * ALTER FUNCTION * *************** *** 9280,9286 **** { FunctionParameter *p = (FunctionParameter *) lfirst(i); ! if (p->mode != FUNC_PARAM_OUT) /* keep if IN or INOUT */ result = lappend(result, p->argType); } return result; --- 9323,9330 ---- { FunctionParameter *p = (FunctionParameter *) lfirst(i); ! /* keep if IN or INOUT */ ! if (p->mode != FUNC_PARAM_OUT && p->mode != FUNC_PARAM_TABLE) result = lappend(result, p->argType); } return result; *************** *** 9471,9476 **** --- 9515,9581 ---- return (Node *) x; } + /* mergeTblFuncParameters check only FUNC_PARAM_IN params in func_args list. + * Next check duplicate column names. Returns joined list. + */ + static List * + mergeTblFuncParameters(List *func_args, List *columns) + { + ListCell *i; + + foreach(i, func_args) + { + FunctionParameter *p = (FunctionParameter *) lfirst(i); + + if (p->mode != FUNC_PARAM_IN) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("OUT or INOUT arguments aren't allowed in TABLE function"))); + } + + /* Check for duplicate names in the explicit list of columns. + */ + foreach(i, columns) + { + FunctionParameter *p = (FunctionParameter *) lfirst(i); + ListCell *rest; + + for_each_cell(rest, lnext(i)) + { + FunctionParameter *rp = (FunctionParameter *) lfirst(rest); + + if (strcmp(p->name, rp->name) == 0) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_COLUMN), + errmsg("column \"%s\" duplicated", + p->name))); + } + } + + return list_concat(func_args, columns); + } + + /* Returns correct return type for TABLE function. For list of length one returns + * correct type, For longer list returns record + */ + static TypeName * + TblFuncTypeName(List *columns) + { + TypeName *result; + + if (list_length(columns) > 1) + result = makeTypeName("record"); + else + { + FunctionParameter *p = (FunctionParameter *) linitial(columns); + result = (TypeName *) copyObject(p->argType); + } + + result->setof = true; + + return result; + } + /* * Must undefine base_yylex before including scan.c, since we want it * to create the function base_yylex not filtered_base_yylex. *** ./src/backend/utils/fmgr/funcapi.c.orig 2007-02-10 14:36:42.000000000 +0100 --- ./src/backend/utils/fmgr/funcapi.c 2007-02-10 14:41:31.000000000 +0100 *************** *** 517,523 **** switch (argtypes[i]) { case ANYELEMENTOID: ! if (argmode == PROARGMODE_OUT) have_anyelement_result = true; else { --- 517,523 ---- switch (argtypes[i]) { case ANYELEMENTOID: ! if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE) have_anyelement_result = true; else { *************** *** 532,538 **** } break; case ANYARRAYOID: ! if (argmode == PROARGMODE_OUT) have_anyarray_result = true; else { --- 532,538 ---- } break; case ANYARRAYOID: ! if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE) have_anyarray_result = true; else { *************** *** 549,555 **** default: break; } ! if (argmode != PROARGMODE_OUT) inargno++; } --- 549,555 ---- default: break; } ! if (argmode != PROARGMODE_OUT && argmode != PROARGMODE_TABLE) inargno++; } *************** *** 810,816 **** if (argmodes[i] == PROARGMODE_IN) continue; Assert(argmodes[i] == PROARGMODE_OUT || ! argmodes[i] == PROARGMODE_INOUT); if (++numoutargs > 1) { /* multiple out args, so forget it */ --- 810,817 ---- if (argmodes[i] == PROARGMODE_IN) continue; Assert(argmodes[i] == PROARGMODE_OUT || ! argmodes[i] == PROARGMODE_INOUT || ! argmodes[i] == PROARGMODE_TABLE); if (++numoutargs > 1) { /* multiple out args, so forget it */ *************** *** 961,967 **** if (argmodes[i] == PROARGMODE_IN) continue; Assert(argmodes[i] == PROARGMODE_OUT || ! argmodes[i] == PROARGMODE_INOUT); outargtypes[numoutargs] = argtypes[i]; if (argnames) pname = DatumGetCString(DirectFunctionCall1(textout, argnames[i])); --- 962,969 ---- if (argmodes[i] == PROARGMODE_IN) continue; Assert(argmodes[i] == PROARGMODE_OUT || ! argmodes[i] == PROARGMODE_INOUT || ! argmodes[i] == PROARGMODE_TABLE); outargtypes[numoutargs] = argtypes[i]; if (argnames) pname = DatumGetCString(DirectFunctionCall1(textout, argnames[i])); *** ./src/include/catalog/pg_proc.h.orig 2007-02-10 14:34:55.000000000 +0100 --- ./src/include/catalog/pg_proc.h 2007-02-10 14:35:36.000000000 +0100 *************** *** 4100,4105 **** --- 4100,4106 ---- #define PROARGMODE_IN 'i' #define PROARGMODE_OUT 'o' #define PROARGMODE_INOUT 'b' + #define PROARGMODE_TABLE 't' /* *** ./src/include/nodes/parsenodes.h.orig 2007-02-09 23:14:56.000000000 +0100 --- ./src/include/nodes/parsenodes.h 2007-02-09 23:15:59.000000000 +0100 *************** *** 1554,1560 **** /* the assigned enum values appear in pg_proc, don't change 'em! */ FUNC_PARAM_IN = 'i', /* input only */ FUNC_PARAM_OUT = 'o', /* output only */ ! FUNC_PARAM_INOUT = 'b' /* both */ } FunctionParameterMode; typedef struct FunctionParameter --- 1554,1561 ---- /* the assigned enum values appear in pg_proc, don't change 'em! */ FUNC_PARAM_IN = 'i', /* input only */ FUNC_PARAM_OUT = 'o', /* output only */ ! FUNC_PARAM_INOUT = 'b', /* both */ ! FUNC_PARAM_TABLE = 't' /* table function column */ } FunctionParameterMode; typedef struct FunctionParameter *** ./src/pl/plpgsql/src/gram.y.orig 2007-02-10 18:14:34.000000000 +0100 --- ./src/pl/plpgsql/src/gram.y 2007-02-10 18:33:23.000000000 +0100 *************** *** 30,36 **** static PLpgSQL_type *read_datatype(int tok); static PLpgSQL_stmt *make_execsql_stmt(const char *sqlstart, int lineno); static PLpgSQL_stmt *make_fetch_stmt(int lineno, int curvar); ! 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 *make_fetch_stmt(int lineno, int curvar); ! 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, *************** *** 189,194 **** --- 189,195 ---- %token K_RETURN %token K_REVERSE %token K_STRICT + %token K_TABLE %token K_THEN %token K_TO %token K_TYPE *************** *** 1161,1170 **** { $$ = make_return_next_stmt($2); } else { plpgsql_push_back_token(tok); ! $$ = make_return_stmt($2); } } ; --- 1162,1176 ---- { $$ = 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); } } ; *************** *** 1997,2003 **** static PLpgSQL_stmt * ! make_return_stmt(int lineno) { PLpgSQL_stmt_return *new; --- 2003,2009 ---- static PLpgSQL_stmt * ! make_return_stmt(int lineno, bool is_tblexpr) { PLpgSQL_stmt_return *new; *************** *** 2006,2013 **** 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"); --- 2012,2033 ---- 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-02-10 14:42:30.000000000 +0100 --- ./src/pl/plpgsql/src/pl_comp.c 2007-02-10 14:57:41.000000000 +0100 *************** *** 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-02-10 18:14:43.000000000 +0100 --- ./src/pl/plpgsql/src/pl_exec.c 2007-02-10 20:11:34.000000000 +0100 *************** *** 174,179 **** --- 174,180 ---- 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); /* ---------- *************** *** 1851,1857 **** --- 1852,1868 ---- * 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; *************** *** 4884,4886 **** --- 4895,4959 ---- 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-02-10 21:44:56.000000000 +0100 --- ./src/pl/plpgsql/src/pl_funcs.c 2007-02-10 21:46:31.000000000 +0100 *************** *** 813,818 **** --- 813,820 ---- { 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-02-10 18:26:33.000000000 +0100 --- ./src/pl/plpgsql/src/plpgsql.h 2007-02-10 18:27:09.000000000 +0100 *************** *** 476,481 **** --- 476,482 ---- int lineno; PLpgSQL_expr *expr; int retvarno; + bool is_tblexpr; } PLpgSQL_stmt_return; typedef struct *** ./src/pl/plpgsql/src/scan.l.orig 2007-02-10 18:29:28.000000000 +0100 --- ./src/pl/plpgsql/src/scan.l 2007-02-10 18:30:16.000000000 +0100 *************** *** 156,161 **** --- 156,162 ---- reverse { return K_REVERSE; } row_count { return K_ROW_COUNT; } 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-02-10 22:01:18.000000000 +0100 --- ./src/test/regress/expected/plpgsql.out 2007-02-10 22:26:23.000000000 +0100 *************** *** 2934,2936 **** --- 2934,3006 ---- ERROR: query returned more than one row CONTEXT: PL/pgSQL function "footest" line 4 at execute statement drop function footest(); + -- 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/expected/rangefuncs.out.orig 2007-02-10 22:41:43.000000000 +0100 --- ./src/test/regress/expected/rangefuncs.out 2007-02-10 22:40:36.000000000 +0100 *************** *** 528,530 **** --- 528,583 ---- AS 'select $1, array[$1,$1]' LANGUAGE sql; ERROR: cannot determine result data type DETAIL: A function returning "anyarray" or "anyelement" must have at least one argument of either type. + -- + -- table functions + -- + CREATE OR REPLACE FUNCTION foo() + RETURNS TABLE(a int) + AS $$ SELECT a FROM generate_series(1,5) a(a) $$ LANGUAGE sql; + SELECT * FROM foo(); + a + --- + 1 + 2 + 3 + 4 + 5 + (5 rows) + + DROP FUNCTION foo(); + CREATE OR REPLACE FUNCTION foo() + RETURNS TABLE(a int, b int) + AS $$ SELECT a, b + FROM generate_series(1,5) a(a), + generate_series(1,5) b(b) $$ LANGUAGE sql; + SELECT * FROM foo(); + a | b + ---+--- + 1 | 1 + 1 | 2 + 1 | 3 + 1 | 4 + 1 | 5 + 2 | 1 + 2 | 2 + 2 | 3 + 2 | 4 + 2 | 5 + 3 | 1 + 3 | 2 + 3 | 3 + 3 | 4 + 3 | 5 + 4 | 1 + 4 | 2 + 4 | 3 + 4 | 4 + 4 | 5 + 5 | 1 + 5 | 2 + 5 | 3 + 5 | 4 + 5 | 5 + (25 rows) + + DROP FUNCTION foo(); *** ./src/test/regress/sql/plpgsql.sql.orig 2007-02-10 21:52:59.000000000 +0100 --- ./src/test/regress/sql/plpgsql.sql 2007-02-10 22:25:20.000000000 +0100 *************** *** 2440,2442 **** --- 2440,2492 ---- select footest(); drop function footest(); + + + -- 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; + *** ./src/test/regress/sql/rangefuncs.sql.orig 2007-02-10 22:35:04.000000000 +0100 --- ./src/test/regress/sql/rangefuncs.sql 2007-02-10 22:39:29.000000000 +0100 *************** *** 261,263 **** --- 261,281 ---- -- fails, no way to deduce outputs CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray) AS 'select $1, array[$1,$1]' LANGUAGE sql; + + -- + -- table functions + -- + CREATE OR REPLACE FUNCTION foo() + RETURNS TABLE(a int) + AS $$ SELECT a FROM generate_series(1,5) a(a) $$ LANGUAGE sql; + SELECT * FROM foo(); + DROP FUNCTION foo(); + + CREATE OR REPLACE FUNCTION foo() + RETURNS TABLE(a int, b int) + AS $$ SELECT a, b + FROM generate_series(1,5) a(a), + generate_series(1,5) b(b) $$ LANGUAGE sql; + SELECT * FROM foo(); + DROP FUNCTION foo(); +