diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index ea930af796..12322b87b8 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -1456,7 +1456,7 @@ JumbleRangeTable(pgssJumbleState *jstate, List *rtable)
APP_JUMB(rte->jointype);
break;
case RTE_FUNCTION:
- JumbleExpr(jstate, rte->funcexpr);
+ JumbleExpr(jstate, (Node *) rte->functions);
break;
case RTE_VALUES:
JumbleExpr(jstate, (Node *) rte->values_lists);
@@ -1866,6 +1866,13 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
JumbleExpr(jstate, setop->rarg);
}
break;
+ case T_RangeTblFunction:
+ {
+ RangeTblFunction *rtfunc = (RangeTblFunction *) node;
+
+ JumbleExpr(jstate, rtfunc->funcexpr);
+ }
+ break;
default:
/* Only a warning, since we can stumble along anyway */
elog(WARNING, "unrecognized node type: %d",
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index a5c808effa..a411e3a0cc 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11185,6 +11185,21 @@ SELECT NULLIF(value, '(none)') ...
1
2(2 rows)
+
+
+
+ unnest(anyarray, anyarray [, ...])
+
+
+ setof anyelement, anyelement [, ...]
+ expand multiple arrays (possibly of different types) to a set
+ of rows. This is only allowed in the FROM clause; see
+
+ unnest(ARRAY[1,2],ARRAY['foo','bar','baz'])
+ 1 foo
+2 bar
+NULL baz(3 rows)
+
@@ -13295,6 +13310,8 @@ AND
functions, as detailed in and
. Other, more specialized
set-returning functions are described elsewhere in this manual.
+ See for ways to combine multiple
+ set-returning functions.
@@ -13499,14 +13516,11 @@ SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
- When a function in the FROM clause is suffixed by
- WITH ORDINALITY, a bigint column is appended
- to the output which starts from 1 and increments by 1 for each row of the
- function's output. This is most useful in the case of set returning functions
- such as UNNEST(). This functionality is available for functions returning
- composite types or using OUT parameters, but not when using
- a function returning RECORD with an explicit column
- definition list.
+ When a function in the FROM clause is suffixed
+ by WITH ORDINALITY, a bigint column is
+ appended to the output which starts from 1 and increments by 1 for each row
+ of the function's output. This is most useful in the case of set returning
+ functions such as unnest()>.
-- set returning function WITH ORDINALITY
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index c32c857651..b33de68200 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -643,21 +643,55 @@ FROM (VALUES ('anne', 'smith'), ('bob', 'jones'), ('joe', 'blow'))
the FROM> clause of a query. Columns returned by table
functions can be included in SELECT>,
JOIN>, or WHERE> clauses in the same manner
- as a table, view, or subquery column.
+ as columns of a table, view, or subquery.
- If a table function returns a base data type, the single result
- column name matches the function name. If the function returns a
- composite type, the result columns get the same names as the
- individual attributes of the type.
+ Table functions may also be combined using the TABLE
+ syntax, with the results returned in parallel columns; the number of
+ result rows in this case is that of the largest function result, with
+ smaller results padded with NULLs to match.
+
+
+
+function_call WITH ORDINALITY AS table_alias (column_alias , ... )
+TABLE( function_call , ... ) WITH ORDINALITY AS table_alias (column_alias , ... )
+
+
+
+ If the WITH ORDINALITY clause is specified, an
+ additional column of type bigint will be added to the
+ function result columns. This column numbers the rows of the function
+ result set, starting from 1. (This is a generalization of the
+ SQL-standard syntax for UNNEST ... WITH ORDINALITY.)
+ By default, the ordinal column is called ordinality>, but
+ a different column name can be assigned to it using
+ an AS clause.
- A table function can be aliased in the FROM> clause,
- but it also can be left unaliased. If a function is used in the
- FROM> clause with no alias, the function name is used
- as the resulting table name.
+ The special table function UNNEST may be called with
+ any number of array parameters, and it returns a corresponding number of
+ columns, as if UNNEST
+ () had been called on each parameter
+ separately and combined using the TABLE construct.
+
+
+
+UNNEST( array_expression , ... ) WITH ORDINALITY AS table_alias (column_alias , ... )
+
+
+
+ If no table_alias is specified, the function
+ name is used as the table name; in the case of a TABLE()>
+ construct, the first function's name is used.
+
+
+
+ If column aliases are not supplied, then for a function returning a base
+ data type, the column name is also the same as the function name. For a
+ function returning a composite type, the result columns get the names
+ of the individual attributes of the type.
@@ -691,7 +725,30 @@ SELECT * FROM vw_getfoo;
the pseudotype record>. When such a function is used in
a query, the expected row structure must be specified in the
query itself, so that the system can know how to parse and plan
- the query. Consider this example:
+ the query. This syntax looks like:
+
+
+
+function_call AS alias (column_definition , ... )
+function_call AS alias (column_definition , ... )
+TABLE( ... function_call AS (column_definition , ... ) , ... )
+
+
+
+ When not using the TABLE()> syntax,
+ the column_definition list replaces the column
+ alias list that could otherwise be attached to the FROM>
+ item; the names in the column definitions serve as column aliases.
+ When using the TABLE()> syntax,
+ a column_definition list can be attached to
+ each member function separately; or if there is only one member function
+ and no WITH ORDINALITY> clause,
+ a column_definition list can be written in
+ place of a column alias list following TABLE()>.
+
+
+
+ Consider this example:
SELECT *
FROM dblink('dbname=mydb', 'SELECT proname, prosrc FROM pg_proc')
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index e603b7644e..88ebd73d49 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -52,9 +52,12 @@ SELECT [ ALL | DISTINCT [ ON ( expressiontable_name [ * ] [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
[ LATERAL ] ( select ) [ AS ] alias [ ( column_alias [, ...] ) ]
with_query_name [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
- [ LATERAL ] function_name ( [ argument [, ...] ] ) [ WITH ORDINALITY ] [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
+ [ LATERAL ] function_name ( [ argument [, ...] ] )
+ [ WITH ORDINALITY ] [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
[ LATERAL ] function_name ( [ argument [, ...] ] ) [ AS ] alias ( column_definition [, ...] )
[ LATERAL ] function_name ( [ argument [, ...] ] ) AS ( column_definition [, ...] )
+ [ LATERAL ] TABLE( function_name ( [ argument [, ...] ] ) [ AS ( column_definition [, ...] ) ] [, ...] )
+ [ WITH ORDINALITY ] [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
from_item [ NATURAL ] join_type from_item [ ON join_condition | USING ( join_column [, ...] ) ]
and with_query is:
@@ -368,30 +371,32 @@ TABLE [ ONLY ] table_name [ * ]
Function calls can appear in the FROM
clause. (This is especially useful for functions that return
result sets, but any function can be used.) This acts as
- though its output were created as a temporary table for the
+ though the function's output were created as a temporary table for the
duration of this single SELECT command.
- When the optional WITH ORDINALITY is
- appended to the function call, a new column is appended after
- all the function call's columns with numbering for each row.
- For example:
-
-SELECT * FROM unnest(ARRAY['a','b','c','d','e','f']) WITH ORDINALITY;
- unnest | ordinality
---------+----------
- a | 1
- b | 2
- c | 3
- d | 4
- e | 5
- f | 6
-(6 rows)
-
- An alias can also be used. If an alias is written, a column
+ When the optional WITH ORDINALITY clause is
+ added to the function call, a new column is appended after
+ all the function's output columns with numbering for each row.
+
+
+
+ An alias can be provided in the same way as for a table.
+ If an alias is written, a column
alias list can also be written to provide substitute names for
one or more attributes of the function's composite return
type, including the column added by ORDINALITY
if present.
-
+
+
+
+ Multiple function calls can be combined into a
+ single FROM>-clause item by surrounding them
+ with TABLE( ... )>. The output of such an item is the
+ concatenation of the first row from each function, then the second
+ row from each function, etc. If some of the functions produce fewer
+ rows than others, NULLs are substituted for the missing data, so
+ that the total number of rows returned is always the same as for the
+ function that produced the most rows.
+
If the function has been defined as returning the
@@ -402,7 +407,21 @@ SELECT * FROM unnest(ARRAY['a','b','c','d','e','f']) WITH ORDINALITY;
class="parameter">data_type , ...
>). The column definition list must match the
actual number and types of columns returned by the function.
- ORDINALITY does not work in this case.
+
+
+
+ When using the TABLE( ... )> syntax, if one of the
+ functions requires a column definition list, it's preferred to put
+ the column definition list after the function call inside
+ TABLE( ... )>. A column definition list can be placed
+ after the TABLE( ... )> construct only if there's just a
+ single function and no WITH ORDINALITY> clause.
+
+
+
+ To use ORDINALITY together with a column definition
+ list, you must use the TABLE( ... )> syntax and put the
+ column definition list inside TABLE( ... )>.
@@ -1598,6 +1617,23 @@ SELECT * FROM distributors_2(111) AS (f1 int, f2 text);
+
+ Here is an example of a function with an ordinality column added:
+
+
+SELECT * FROM unnest(ARRAY['a','b','c','d','e','f']) WITH ORDINALITY;
+ unnest | ordinality
+--------+----------
+ a | 1
+ b | 2
+ c | 3
+ d | 4
+ e | 5
+ f | 6
+(6 rows)
+
+
+
This example shows how to use a simple WITH> clause:
@@ -1773,6 +1809,11 @@ SELECT distributors.* WHERE distributors.name = 'Westward';
PostgreSQL treats UNNEST()> the
same as other set-returning functions.
+
+
+ Placing multiple function calls inside TABLE( ... )> syntax is
+ also an extension of the SQL standard.
+
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index bf47640092..d766ae7287 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -157,40 +157,6 @@ CreateTupleDescCopy(TupleDesc tupdesc)
return desc;
}
-/*
- * CreateTupleDescCopyExtend
- * This function creates a new TupleDesc by copying from an existing
- * TupleDesc, but adding space for more columns. The new tupdesc is
- * not regarded as the same record type as the old one (and therefore
- * does not inherit its typeid/typmod, which instead are left as an
- * anonymous record type).
- *
- * The additional column slots are not initialized in any way;
- * callers must do their own TupleDescInitEntry on each.
- *
- * !!! Constraints and defaults are not copied !!!
- */
-TupleDesc
-CreateTupleDescCopyExtend(TupleDesc tupdesc, int moreatts)
-{
- TupleDesc desc;
- int i;
- int src_natts = tupdesc->natts;
-
- Assert(moreatts >= 0);
-
- desc = CreateTemplateTupleDesc(src_natts + moreatts, tupdesc->tdhasoid);
-
- for (i = 0; i < src_natts; i++)
- {
- memcpy(desc->attrs[i], tupdesc->attrs[i], ATTRIBUTE_FIXED_PART_SIZE);
- desc->attrs[i]->attnotnull = false;
- desc->attrs[i]->atthasdef = false;
- }
-
- return desc;
-}
-
/*
* CreateTupleDescCopyConstr
* This function creates a new TupleDesc by copying from an existing
@@ -250,6 +216,47 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
return desc;
}
+/*
+ * TupleDescCopyEntry
+ * This function copies a single attribute structure from one tuple
+ * descriptor to another.
+ *
+ * !!! Constraints and defaults are not copied !!!
+ */
+void
+TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno,
+ TupleDesc src, AttrNumber srcAttno)
+{
+ /*
+ * sanity checks
+ */
+ AssertArg(PointerIsValid(src));
+ AssertArg(PointerIsValid(dst));
+ AssertArg(srcAttno >= 1);
+ AssertArg(srcAttno <= src->natts);
+ AssertArg(dstAttno >= 1);
+ AssertArg(dstAttno <= dst->natts);
+
+ memcpy(dst->attrs[dstAttno - 1], src->attrs[srcAttno - 1],
+ ATTRIBUTE_FIXED_PART_SIZE);
+
+ /*
+ * Aside from updating the attno, we'd better reset attcacheoff.
+ *
+ * XXX Actually, to be entirely safe we'd need to reset the attcacheoff of
+ * all following columns in dst as well. Current usage scenarios don't
+ * require that though, because all following columns will get initialized
+ * by other uses of this function or TupleDescInitEntry. So we cheat a
+ * bit to avoid a useless O(N^2) penalty.
+ */
+ dst->attrs[dstAttno - 1]->attnum = dstAttno;
+ dst->attrs[dstAttno - 1]->attcacheoff = -1;
+
+ /* since we're not copying constraints or defaults, clear these */
+ dst->attrs[dstAttno - 1]->attnotnull = false;
+ dst->attrs[dstAttno - 1]->atthasdef = false;
+}
+
/*
* Free a TupleDesc including all substructure
*/
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index fe17c96f12..908126ce01 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1757,8 +1757,7 @@ find_expr_references_walker(Node *node,
/*
* Add whole-relation refs for each plain relation mentioned in the
- * subquery's rtable, as well as refs for any datatypes and collations
- * used in a RECORD function's output.
+ * subquery's rtable.
*
* Note: query_tree_walker takes care of recursing into RTE_FUNCTION
* RTEs, subqueries, etc, so no need to do that here. But keep it
@@ -1766,12 +1765,13 @@ find_expr_references_walker(Node *node,
*
* Note: we don't need to worry about collations mentioned in
* RTE_VALUES or RTE_CTE RTEs, because those must just duplicate
- * collations referenced in other parts of the Query.
+ * collations referenced in other parts of the Query. We do have to
+ * worry about collations mentioned in RTE_FUNCTION, but we take care
+ * of those when we recurse to the RangeTblFunction node(s).
*/
foreach(lc, query->rtable)
{
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
- ListCell *ct;
switch (rte->rtekind)
{
@@ -1779,22 +1779,6 @@ find_expr_references_walker(Node *node,
add_object_address(OCLASS_CLASS, rte->relid, 0,
context->addrs);
break;
- case RTE_FUNCTION:
- foreach(ct, rte->funccoltypes)
- {
- add_object_address(OCLASS_TYPE, lfirst_oid(ct), 0,
- context->addrs);
- }
- foreach(ct, rte->funccolcollations)
- {
- Oid collid = lfirst_oid(ct);
-
- if (OidIsValid(collid) &&
- collid != DEFAULT_COLLATION_OID)
- add_object_address(OCLASS_COLLATION, collid, 0,
- context->addrs);
- }
- break;
default:
break;
}
@@ -1863,6 +1847,30 @@ find_expr_references_walker(Node *node,
find_expr_references_walker((Node *) setop->groupClauses, context);
/* fall through to examine child nodes */
}
+ else if (IsA(node, RangeTblFunction))
+ {
+ RangeTblFunction *rtfunc = (RangeTblFunction *) node;
+ ListCell *ct;
+
+ /*
+ * Add refs for any datatypes and collations used in a column
+ * definition list for a RECORD function. (For other cases, it should
+ * be enough to depend on the function itself.)
+ */
+ foreach(ct, rtfunc->funccoltypes)
+ {
+ add_object_address(OCLASS_TYPE, lfirst_oid(ct), 0,
+ context->addrs);
+ }
+ foreach(ct, rtfunc->funccolcollations)
+ {
+ Oid collid = lfirst_oid(ct);
+
+ if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID)
+ add_object_address(OCLASS_COLLATION, collid, 0,
+ context->addrs);
+ }
+ }
return expression_tree_walker(node, find_expr_references_walker,
(void *) context);
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index a3509d8c2a..9c1fd2cd59 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -319,6 +319,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
col->collOid = attribute->attcollation;
col->constraints = NIL;
col->fdwoptions = NIL;
+ col->location = -1;
coltype->names = NIL;
coltype->typeOid = attribute->atttypid;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 4e93df26cc..bd5428de97 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1259,9 +1259,21 @@ ExplainNode(PlanState *planstate, List *ancestors,
break;
case T_FunctionScan:
if (es->verbose)
- show_expression(((FunctionScan *) plan)->funcexpr,
+ {
+ List *fexprs = NIL;
+ ListCell *lc;
+
+ foreach(lc, ((FunctionScan *) plan)->functions)
+ {
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
+
+ fexprs = lappend(fexprs, rtfunc->funcexpr);
+ }
+ /* We rely on show_expression to insert commas as needed */
+ show_expression((Node *) fexprs,
"Function Call", planstate, ancestors,
es->verbose, es);
+ }
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
if (plan->qual)
show_instrumentation_count("Rows Removed by Filter", 1,
@@ -1984,26 +1996,31 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
break;
case T_FunctionScan:
{
- Node *funcexpr;
+ FunctionScan *fscan = (FunctionScan *) plan;
/* Assert it's on a RangeFunction */
Assert(rte->rtekind == RTE_FUNCTION);
/*
- * If the expression is still a function call, we can get the
- * real name of the function. Otherwise, punt (this can
- * happen if the optimizer simplified away the function call,
- * for example).
+ * If the expression is still a function call of a single
+ * function, we can get the real name of the function.
+ * Otherwise, punt. (Even if it was a single function call
+ * originally, the optimizer could have simplified it away.)
*/
- funcexpr = ((FunctionScan *) plan)->funcexpr;
- if (funcexpr && IsA(funcexpr, FuncExpr))
+ if (list_length(fscan->functions) == 1)
{
- Oid funcid = ((FuncExpr *) funcexpr)->funcid;
+ RangeTblFunction *rtfunc = (RangeTblFunction *) linitial(fscan->functions);
- objectname = get_func_name(funcid);
- if (es->verbose)
- namespace =
- get_namespace_name(get_func_namespace(funcid));
+ if (IsA(rtfunc->funcexpr, FuncExpr))
+ {
+ FuncExpr *funcexpr = (FuncExpr *) rtfunc->funcexpr;
+ Oid funcid = funcexpr->funcid;
+
+ objectname = get_func_name(funcid);
+ if (es->verbose)
+ namespace =
+ get_namespace_name(get_func_namespace(funcid));
+ }
}
objecttag = "Function Name";
}
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 67b8a5dfab..b6fb2e31c5 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -143,6 +143,7 @@ DefineSequence(CreateSeqStmt *seq)
coldef->collClause = NULL;
coldef->collOid = InvalidOid;
coldef->constraints = NIL;
+ coldef->location = -1;
null[i - 1] = false;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0b31f55af2..3483107e59 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1605,6 +1605,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
def->collClause = NULL;
def->collOid = attribute->attcollation;
def->constraints = NIL;
+ def->location = -1;
inhSchema = lappend(inhSchema, def);
newattno[parent_attno - 1] = ++child_attno;
}
@@ -4823,6 +4824,7 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
cdef->is_local = true;
cdef->is_not_null = true;
cdef->storage = 0;
+ cdef->location = -1;
cmd->def = (Node *) cdef;
}
ATPrepAddColumn(wqueue, rel, recurse, false, cmd, lockmode);
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index aca40e7a76..0703c05cff 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -100,6 +100,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
def->cooked_default = NULL;
def->collClause = NULL;
def->collOid = exprCollation((Node *) tle->expr);
+ def->location = -1;
/*
* It's possible that the column is of a collatable type but the
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index 423e02f354..3e386fd381 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -22,13 +22,30 @@
*/
#include "postgres.h"
+#include "catalog/pg_type.h"
#include "executor/nodeFunctionscan.h"
#include "funcapi.h"
#include "nodes/nodeFuncs.h"
-#include "catalog/pg_type.h"
+#include "parser/parsetree.h"
+#include "utils/builtins.h"
+
+
+/*
+ * Runtime data for each function being scanned.
+ */
+typedef struct FunctionScanPerFuncState
+{
+ ExprState *funcexpr; /* state of the expression being evaluated */
+ TupleDesc tupdesc; /* desc of the function result type */
+ int colcount; /* expected number of result columns */
+ Tuplestorestate *tstore; /* holds the function result set */
+ int64 rowcount; /* # of rows in result set, -1 if not known */
+ TupleTableSlot *func_slot; /* function result slot (or NULL) */
+} FunctionScanPerFuncState;
static TupleTableSlot *FunctionNext(FunctionScanState *node);
+
/* ----------------------------------------------------------------
* Scan Support
* ----------------------------------------------------------------
@@ -44,108 +61,183 @@ FunctionNext(FunctionScanState *node)
{
EState *estate;
ScanDirection direction;
- Tuplestorestate *tuplestorestate;
TupleTableSlot *scanslot;
- TupleTableSlot *funcslot;
-
- if (node->func_slot)
- {
- /*
- * ORDINALITY case:
- *
- * We fetch the function result into FUNCSLOT (which matches the
- * function return type), and then copy the values to SCANSLOT
- * (which matches the scan result type), setting the ordinal
- * column in the process.
- */
-
- funcslot = node->func_slot;
- scanslot = node->ss.ss_ScanTupleSlot;
- }
- else
- {
- /*
- * non-ORDINALITY case: the function return type and scan result
- * type are the same, so we fetch the function result straight
- * into the scan result slot.
- */
-
- funcslot = node->ss.ss_ScanTupleSlot;
- scanslot = NULL;
- }
+ bool alldone;
+ int64 oldpos;
+ int funcno;
+ int att;
/*
* get information from the estate and scan state
*/
estate = node->ss.ps.state;
direction = estate->es_direction;
+ scanslot = node->ss.ss_ScanTupleSlot;
- tuplestorestate = node->tuplestorestate;
-
- /*
- * If first time through, read all tuples from function and put them in a
- * tuplestore. Subsequent calls just fetch tuples from tuplestore.
- */
- if (tuplestorestate == NULL)
+ if (node->simple)
{
- node->tuplestorestate = tuplestorestate =
- ExecMakeTableFunctionResult(node->funcexpr,
- node->ss.ps.ps_ExprContext,
- node->func_tupdesc,
- node->eflags & EXEC_FLAG_BACKWARD);
+ /*
+ * Fast path for the trivial case: the function return type and scan
+ * result type are the same, so we fetch the function result straight
+ * into the scan result slot. No need to update ordinality or
+ * rowcounts either.
+ */
+ Tuplestorestate *tstore = node->funcstates[0].tstore;
+
+ /*
+ * If first time through, read all tuples from function and put them
+ * in a tuplestore. Subsequent calls just fetch tuples from
+ * tuplestore.
+ */
+ if (tstore == NULL)
+ {
+ node->funcstates[0].tstore = tstore =
+ ExecMakeTableFunctionResult(node->funcstates[0].funcexpr,
+ node->ss.ps.ps_ExprContext,
+ node->funcstates[0].tupdesc,
+ node->eflags & EXEC_FLAG_BACKWARD);
+
+ /*
+ * paranoia - cope if the function, which may have constructed the
+ * tuplestore itself, didn't leave it pointing at the start. This
+ * call is fast, so the overhead shouldn't be an issue.
+ */
+ tuplestore_rescan(tstore);
+ }
+
+ /*
+ * Get the next tuple from tuplestore.
+ */
+ (void) tuplestore_gettupleslot(tstore,
+ ScanDirectionIsForward(direction),
+ false,
+ scanslot);
+ return scanslot;
}
/*
- * Get the next tuple from tuplestore. Return NULL if no more tuples.
+ * Increment or decrement ordinal counter before checking for end-of-data,
+ * so that we can move off either end of the result by 1 (and no more than
+ * 1) without losing correct count. See PortalRunSelect for why we can
+ * assume that we won't be called repeatedly in the end-of-data state.
*/
- (void) tuplestore_gettupleslot(tuplestorestate,
- ScanDirectionIsForward(direction),
- false,
- funcslot);
-
- if (!scanslot)
- return funcslot;
-
- /*
- * we're doing ordinality, so we copy the values from the function return
- * slot to the (distinct) scan slot. We can do this because the lifetimes
- * of the values in each slot are the same; until we reset the scan or
- * fetch the next tuple, both will be valid.
- */
-
- ExecClearTuple(scanslot);
-
- /*
- * increment or decrement before checking for end-of-data, so that we can
- * move off either end of the result by 1 (and no more than 1) without
- * losing correct count. See PortalRunSelect for why we assume that we
- * won't be called repeatedly in the end-of-data state.
- */
-
+ oldpos = node->ordinal;
if (ScanDirectionIsForward(direction))
node->ordinal++;
else
node->ordinal--;
- if (!TupIsNull(funcslot))
+ /*
+ * Main loop over functions.
+ *
+ * We fetch the function results into func_slots (which match the function
+ * return types), and then copy the values to scanslot (which matches the
+ * scan result type), setting the ordinal column (if any) as well.
+ */
+ ExecClearTuple(scanslot);
+ att = 0;
+ alldone = true;
+ for (funcno = 0; funcno < node->nfuncs; funcno++)
{
- int natts = funcslot->tts_tupleDescriptor->natts;
- int i;
+ FunctionScanPerFuncState *fs = &node->funcstates[funcno];
+ int i;
- slot_getallattrs(funcslot);
-
- for (i = 0; i < natts; ++i)
+ /*
+ * If first time through, read all tuples from function and put them
+ * in a tuplestore. Subsequent calls just fetch tuples from
+ * tuplestore.
+ */
+ if (fs->tstore == NULL)
{
- scanslot->tts_values[i] = funcslot->tts_values[i];
- scanslot->tts_isnull[i] = funcslot->tts_isnull[i];
+ fs->tstore =
+ ExecMakeTableFunctionResult(fs->funcexpr,
+ node->ss.ps.ps_ExprContext,
+ fs->tupdesc,
+ node->eflags & EXEC_FLAG_BACKWARD);
+
+ /*
+ * paranoia - cope if the function, which may have constructed the
+ * tuplestore itself, didn't leave it pointing at the start. This
+ * call is fast, so the overhead shouldn't be an issue.
+ */
+ tuplestore_rescan(fs->tstore);
}
- scanslot->tts_values[natts] = Int64GetDatumFast(node->ordinal);
- scanslot->tts_isnull[natts] = false;
+ /*
+ * Get the next tuple from tuplestore.
+ *
+ * If we have a rowcount for the function, and we know the previous
+ * read position was out of bounds, don't try the read. This allows
+ * backward scan to work when there are mixed row counts present.
+ */
+ if (fs->rowcount != -1 && fs->rowcount < oldpos)
+ ExecClearTuple(fs->func_slot);
+ else
+ (void) tuplestore_gettupleslot(fs->tstore,
+ ScanDirectionIsForward(direction),
+ false,
+ fs->func_slot);
- ExecStoreVirtualTuple(scanslot);
+ if (TupIsNull(fs->func_slot))
+ {
+ /*
+ * If we ran out of data for this function in the forward
+ * direction then we now know how many rows it returned. We need
+ * to know this in order to handle backwards scans. The row count
+ * we store is actually 1+ the actual number, because we have to
+ * position the tuplestore 1 off its end sometimes.
+ */
+ if (ScanDirectionIsForward(direction) && fs->rowcount == -1)
+ fs->rowcount = node->ordinal;
+
+ /*
+ * populate the result cols with nulls
+ */
+ for (i = 0; i < fs->colcount; i++)
+ {
+ scanslot->tts_values[att] = (Datum) 0;
+ scanslot->tts_isnull[att] = true;
+ att++;
+ }
+ }
+ else
+ {
+ /*
+ * we have a result, so just copy it to the result cols.
+ */
+ slot_getallattrs(fs->func_slot);
+
+ for (i = 0; i < fs->colcount; i++)
+ {
+ scanslot->tts_values[att] = fs->func_slot->tts_values[i];
+ scanslot->tts_isnull[att] = fs->func_slot->tts_isnull[i];
+ att++;
+ }
+
+ /*
+ * We're not done until every function result is exhausted; we pad
+ * the shorter results with nulls until then.
+ */
+ alldone = false;
+ }
}
+ /*
+ * ordinal col is always last, per spec.
+ */
+ if (node->ordinality)
+ {
+ scanslot->tts_values[att] = Int64GetDatumFast(node->ordinal);
+ scanslot->tts_isnull[att] = false;
+ }
+
+ /*
+ * If alldone, we just return the previously-cleared scanslot. Otherwise,
+ * finish creating the virtual tuple.
+ */
+ if (!alldone)
+ ExecStoreVirtualTuple(scanslot);
+
return scanslot;
}
@@ -184,10 +276,13 @@ FunctionScanState *
ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
{
FunctionScanState *scanstate;
- Oid funcrettype;
- TypeFuncClass functypclass;
- TupleDesc func_tupdesc = NULL;
- TupleDesc scan_tupdesc = NULL;
+ RangeTblEntry *rte = rt_fetch(node->scan.scanrelid,
+ estate->es_range_table);
+ int nfuncs = list_length(node->functions);
+ TupleDesc scan_tupdesc;
+ int i,
+ natts;
+ ListCell *lc;
/* check for unsupported flags */
Assert(!(eflags & EXEC_FLAG_MARK));
@@ -206,6 +301,29 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
scanstate->ss.ps.state = estate;
scanstate->eflags = eflags;
+ /*
+ * are we adding an ordinality column?
+ */
+ scanstate->ordinality = node->funcordinality;
+
+ scanstate->nfuncs = nfuncs;
+ if (nfuncs == 1 && !node->funcordinality)
+ scanstate->simple = true;
+ else
+ scanstate->simple = false;
+
+ /*
+ * Ordinal 0 represents the "before the first row" position.
+ *
+ * We need to track ordinal position even when not adding an ordinality
+ * column to the result, in order to handle backwards scanning properly
+ * with multiple functions with different result sizes. (We can't position
+ * any individual function's tuplestore any more than 1 place beyond its
+ * end, so when scanning backwards, we need to know when to start
+ * including the function in the scan again.)
+ */
+ scanstate->ordinal = 0;
+
/*
* Miscellaneous initialization
*
@@ -213,22 +331,14 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
*/
ExecAssignExprContext(estate, &scanstate->ss.ps);
+ scanstate->ss.ps.ps_TupFromTlist = false;
+
/*
* tuple table initialization
*/
ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
ExecInitScanTupleSlot(estate, &scanstate->ss);
- /*
- * We only need a separate slot for the function result if we are doing
- * ordinality; otherwise, we fetch function results directly into the
- * scan slot.
- */
- if (node->funcordinality)
- scanstate->func_slot = ExecInitExtraTupleSlot(estate);
- else
- scanstate->func_slot = NULL;
-
/*
* initialize child expressions
*/
@@ -239,113 +349,165 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
ExecInitExpr((Expr *) node->scan.plan.qual,
(PlanState *) scanstate);
- /*
- * Now determine if the function returns a simple or composite
- * type, and build an appropriate tupdesc. This tupdesc
- * (func_tupdesc) is the one that matches the shape of the
- * function result, no extra columns.
- */
- functypclass = get_expr_result_type(node->funcexpr,
- &funcrettype,
- &func_tupdesc);
+ scanstate->funcstates = palloc(nfuncs * sizeof(FunctionScanPerFuncState));
- if (functypclass == TYPEFUNC_COMPOSITE)
+ natts = 0;
+ i = 0;
+ foreach(lc, node->functions)
{
- /* Composite data type, e.g. a table's row type */
- Assert(func_tupdesc);
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
+ Node *funcexpr = rtfunc->funcexpr;
+ int colcount = rtfunc->funccolcount;
+ FunctionScanPerFuncState *fs = &scanstate->funcstates[i];
+ TypeFuncClass functypclass;
+ Oid funcrettype;
+ TupleDesc tupdesc;
+
+ fs->funcexpr = ExecInitExpr((Expr *) funcexpr, (PlanState *) scanstate);
/*
- * XXX
- * Existing behaviour is a bit inconsistent with regard to aliases and
- * whole-row Vars of the function result. If the function returns a
- * composite type, then the whole-row Var will refer to this tupdesc,
- * which has the type's own column names rather than the alias column
- * names given in the query. This affects the output of constructs like
- * row_to_json which read the column names from the passed-in values.
+ * Don't allocate the tuplestores; the actual calls to the functions
+ * do that. NULL means that we have not called the function yet (or
+ * need to call it again after a rescan).
*/
+ fs->tstore = NULL;
+ fs->rowcount = -1;
- /* Must copy it out of typcache for safety */
- func_tupdesc = CreateTupleDescCopy(func_tupdesc);
- }
- else if (functypclass == TYPEFUNC_SCALAR)
- {
- /* Base data type, i.e. scalar */
- char *attname = strVal(linitial(node->funccolnames));
+ /*
+ * Now determine if the function returns a simple or composite type,
+ * and build an appropriate tupdesc. Note that in the composite case,
+ * the function may now return more columns than it did when the plan
+ * was made; we have to ignore any columns beyond "colcount".
+ */
+ functypclass = get_expr_result_type(funcexpr,
+ &funcrettype,
+ &tupdesc);
- func_tupdesc = CreateTemplateTupleDesc(1, false);
- TupleDescInitEntry(func_tupdesc,
- (AttrNumber) 1,
- attname,
- funcrettype,
- -1,
- 0);
- TupleDescInitEntryCollation(func_tupdesc,
- (AttrNumber) 1,
- exprCollation(node->funcexpr));
- }
- else if (functypclass == TYPEFUNC_RECORD)
- {
- func_tupdesc = BuildDescFromLists(node->funccolnames,
- node->funccoltypes,
- node->funccoltypmods,
- node->funccolcollations);
- }
- else
- {
- /* crummy error message, but parser should have caught this */
- elog(ERROR, "function in FROM has unsupported return type");
+ if (functypclass == TYPEFUNC_COMPOSITE)
+ {
+ /* Composite data type, e.g. a table's row type */
+ Assert(tupdesc);
+ Assert(tupdesc->natts >= colcount);
+ /* Must copy it out of typcache for safety */
+ tupdesc = CreateTupleDescCopy(tupdesc);
+ }
+ else if (functypclass == TYPEFUNC_SCALAR)
+ {
+ /* Base data type, i.e. scalar */
+ tupdesc = CreateTemplateTupleDesc(1, false);
+ TupleDescInitEntry(tupdesc,
+ (AttrNumber) 1,
+ NULL, /* don't care about the name here */
+ funcrettype,
+ -1,
+ 0);
+ TupleDescInitEntryCollation(tupdesc,
+ (AttrNumber) 1,
+ exprCollation(funcexpr));
+ }
+ else if (functypclass == TYPEFUNC_RECORD)
+ {
+ tupdesc = BuildDescFromLists(rtfunc->funccolnames,
+ rtfunc->funccoltypes,
+ rtfunc->funccoltypmods,
+ rtfunc->funccolcollations);
+
+ /*
+ * For RECORD results, make sure a typmod has been assigned. (The
+ * function should do this for itself, but let's cover things in
+ * case it doesn't.)
+ */
+ BlessTupleDesc(tupdesc);
+ }
+ else
+ {
+ /* crummy error message, but parser should have caught this */
+ elog(ERROR, "function in FROM has unsupported return type");
+ }
+
+ fs->tupdesc = tupdesc;
+ fs->colcount = colcount;
+
+ /*
+ * We only need separate slots for the function results if we are
+ * doing ordinality or multiple functions; otherwise, we'll fetch
+ * function results directly into the scan slot.
+ */
+ if (!scanstate->simple)
+ {
+ fs->func_slot = ExecInitExtraTupleSlot(estate);
+ ExecSetSlotDescriptor(fs->func_slot, fs->tupdesc);
+ }
+ else
+ fs->func_slot = NULL;
+
+ natts += colcount;
+ i++;
}
/*
- * For RECORD results, make sure a typmod has been assigned. (The
- * function should do this for itself, but let's cover things in case it
- * doesn't.)
- */
- BlessTupleDesc(func_tupdesc);
-
- /*
- * If doing ordinality, we need a new tupdesc with one additional column
- * tacked on, always of type "bigint". The name to use has already been
- * recorded by the parser as the last element of funccolnames.
+ * Create the combined TupleDesc
*
- * Without ordinality, the scan result tupdesc is the same as the
- * function result tupdesc. (No need to make a copy.)
+ * If there is just one function without ordinality, the scan result
+ * tupdesc is the same as the function result tupdesc --- except that
+ * we may stuff new names into it below, so drop any rowtype label.
*/
- if (node->funcordinality)
+ if (scanstate->simple)
{
- int natts = func_tupdesc->natts;
-
- scan_tupdesc = CreateTupleDescCopyExtend(func_tupdesc, 1);
-
- TupleDescInitEntry(scan_tupdesc,
- natts + 1,
- strVal(llast(node->funccolnames)),
- INT8OID,
- -1,
- 0);
-
- BlessTupleDesc(scan_tupdesc);
+ scan_tupdesc = CreateTupleDescCopy(scanstate->funcstates[0].tupdesc);
+ scan_tupdesc->tdtypeid = RECORDOID;
+ scan_tupdesc->tdtypmod = -1;
}
else
- scan_tupdesc = func_tupdesc;
+ {
+ AttrNumber attno = 0;
- scanstate->scan_tupdesc = scan_tupdesc;
- scanstate->func_tupdesc = func_tupdesc;
- ExecAssignScanType(&scanstate->ss, scan_tupdesc);
+ if (node->funcordinality)
+ natts++;
- if (scanstate->func_slot)
- ExecSetSlotDescriptor(scanstate->func_slot, func_tupdesc);
+ scan_tupdesc = CreateTemplateTupleDesc(natts, false);
+
+ for (i = 0; i < nfuncs; i++)
+ {
+ TupleDesc tupdesc = scanstate->funcstates[i].tupdesc;
+ int colcount = scanstate->funcstates[i].colcount;
+ int j;
+
+ for (j = 1; j <= colcount; j++)
+ TupleDescCopyEntry(scan_tupdesc, ++attno, tupdesc, j);
+ }
+
+ /* If doing ordinality, add a column of type "bigint" at the end */
+ if (node->funcordinality)
+ {
+ TupleDescInitEntry(scan_tupdesc,
+ ++attno,
+ NULL, /* don't care about the name here */
+ INT8OID,
+ -1,
+ 0);
+ }
+
+ Assert(attno == natts);
+ }
/*
- * Other node-specific setup
+ * Make sure the scan result tupdesc has the column names the query
+ * expects. This affects the output of constructs like row_to_json which
+ * read the column names from the passed-in tupdesc.
*/
- scanstate->ordinal = 0;
- scanstate->tuplestorestate = NULL;
+ i = 0;
+ foreach(lc, rte->eref->colnames)
+ {
+ char *attname = strVal(lfirst(lc));
- scanstate->funcexpr = ExecInitExpr((Expr *) node->funcexpr,
- (PlanState *) scanstate);
+ if (i >= scan_tupdesc->natts)
+ break; /* shouldn't happen, but just in case */
+ namestrcpy(&(scan_tupdesc->attrs[i]->attname), attname);
+ i++;
+ }
- scanstate->ss.ps.ps_TupFromTlist = false;
+ ExecAssignScanType(&scanstate->ss, scan_tupdesc);
/*
* Initialize result tuple type and projection info.
@@ -365,6 +527,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
void
ExecEndFunctionScan(FunctionScanState *node)
{
+ int i;
+
/*
* Free the exprcontext
*/
@@ -375,15 +539,23 @@ ExecEndFunctionScan(FunctionScanState *node)
*/
ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
ExecClearTuple(node->ss.ss_ScanTupleSlot);
- if (node->func_slot)
- ExecClearTuple(node->func_slot);
/*
- * Release tuplestore resources
+ * Release slots and tuplestore resources
*/
- if (node->tuplestorestate != NULL)
- tuplestore_end(node->tuplestorestate);
- node->tuplestorestate = NULL;
+ for (i = 0; i < node->nfuncs; i++)
+ {
+ FunctionScanPerFuncState *fs = &node->funcstates[i];
+
+ if (fs->func_slot)
+ ExecClearTuple(fs->func_slot);
+
+ if (fs->tstore != NULL)
+ {
+ tuplestore_end(node->funcstates[i].tstore);
+ fs->tstore = NULL;
+ }
+ }
}
/* ----------------------------------------------------------------
@@ -395,31 +567,58 @@ ExecEndFunctionScan(FunctionScanState *node)
void
ExecReScanFunctionScan(FunctionScanState *node)
{
+ FunctionScan *scan = (FunctionScan *) node->ss.ps.plan;
+ int i;
+ Bitmapset *chgparam = node->ss.ps.chgParam;
+
ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
- if (node->func_slot)
- ExecClearTuple(node->func_slot);
+ for (i = 0; i < node->nfuncs; i++)
+ {
+ FunctionScanPerFuncState *fs = &node->funcstates[i];
+
+ if (fs->func_slot)
+ ExecClearTuple(fs->func_slot);
+ }
ExecScanReScan(&node->ss);
+ /*
+ * Here we have a choice whether to drop the tuplestores (and recompute
+ * the function outputs) or just rescan them. We must recompute if an
+ * expression contains changed parameters, else we rescan.
+ *
+ * XXX maybe we should recompute if the function is volatile? But in
+ * general the executor doesn't conditionalize its actions on that.
+ */
+ if (chgparam)
+ {
+ ListCell *lc;
+
+ i = 0;
+ foreach(lc, scan->functions)
+ {
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
+
+ if (bms_overlap(chgparam, rtfunc->funcparams))
+ {
+ if (node->funcstates[i].tstore != NULL)
+ {
+ tuplestore_end(node->funcstates[i].tstore);
+ node->funcstates[i].tstore = NULL;
+ }
+ node->funcstates[i].rowcount = -1;
+ }
+ i++;
+ }
+ }
+
+ /* Reset ordinality counter */
node->ordinal = 0;
- /*
- * If we haven't materialized yet, just return.
- */
- if (!node->tuplestorestate)
- return;
-
- /*
- * Here we have a choice whether to drop the tuplestore (and recompute the
- * function outputs) or just rescan it. We must recompute if the
- * expression contains parameters, else we rescan. XXX maybe we should
- * recompute if the function is volatile?
- */
- if (node->ss.ps.chgParam != NULL)
+ /* Make sure we rewind any remaining tuplestores */
+ for (i = 0; i < node->nfuncs; i++)
{
- tuplestore_end(node->tuplestorestate);
- node->tuplestorestate = NULL;
+ if (node->funcstates[i].tstore != NULL)
+ tuplestore_rescan(node->funcstates[i].tstore);
}
- else
- tuplestore_rescan(node->tuplestorestate);
}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1733da633a..e3edcf6f74 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -504,11 +504,7 @@ _copyFunctionScan(const FunctionScan *from)
/*
* copy remainder of node
*/
- COPY_NODE_FIELD(funcexpr);
- COPY_NODE_FIELD(funccolnames);
- COPY_NODE_FIELD(funccoltypes);
- COPY_NODE_FIELD(funccoltypmods);
- COPY_NODE_FIELD(funccolcollations);
+ COPY_NODE_FIELD(functions);
COPY_SCALAR_FIELD(funcordinality);
return newnode;
@@ -1981,10 +1977,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
COPY_SCALAR_FIELD(security_barrier);
COPY_SCALAR_FIELD(jointype);
COPY_NODE_FIELD(joinaliasvars);
- COPY_NODE_FIELD(funcexpr);
- COPY_NODE_FIELD(funccoltypes);
- COPY_NODE_FIELD(funccoltypmods);
- COPY_NODE_FIELD(funccolcollations);
+ COPY_NODE_FIELD(functions);
COPY_SCALAR_FIELD(funcordinality);
COPY_NODE_FIELD(values_lists);
COPY_NODE_FIELD(values_collations);
@@ -2007,6 +2000,22 @@ _copyRangeTblEntry(const RangeTblEntry *from)
return newnode;
}
+static RangeTblFunction *
+_copyRangeTblFunction(const RangeTblFunction *from)
+{
+ RangeTblFunction *newnode = makeNode(RangeTblFunction);
+
+ COPY_NODE_FIELD(funcexpr);
+ COPY_SCALAR_FIELD(funccolcount);
+ COPY_NODE_FIELD(funccolnames);
+ COPY_NODE_FIELD(funccoltypes);
+ COPY_NODE_FIELD(funccoltypmods);
+ COPY_NODE_FIELD(funccolcollations);
+ COPY_BITMAPSET_FIELD(funcparams);
+
+ return newnode;
+}
+
static WithCheckOption *
_copyWithCheckOption(const WithCheckOption *from)
{
@@ -2299,9 +2308,10 @@ _copyRangeFunction(const RangeFunction *from)
{
RangeFunction *newnode = makeNode(RangeFunction);
- COPY_SCALAR_FIELD(ordinality);
COPY_SCALAR_FIELD(lateral);
- COPY_NODE_FIELD(funccallnode);
+ COPY_SCALAR_FIELD(ordinality);
+ COPY_SCALAR_FIELD(is_table);
+ COPY_NODE_FIELD(functions);
COPY_NODE_FIELD(alias);
COPY_NODE_FIELD(coldeflist);
@@ -2366,6 +2376,7 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(collOid);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(fdwoptions);
+ COPY_LOCATION_FIELD(location);
return newnode;
}
@@ -4550,6 +4561,9 @@ copyObject(const void *from)
case T_RangeTblEntry:
retval = _copyRangeTblEntry(from);
break;
+ case T_RangeTblFunction:
+ retval = _copyRangeTblFunction(from);
+ break;
case T_WithCheckOption:
retval = _copyWithCheckOption(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 7b29812b69..1f9b5d70f5 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2140,9 +2140,10 @@ _equalRangeSubselect(const RangeSubselect *a, const RangeSubselect *b)
static bool
_equalRangeFunction(const RangeFunction *a, const RangeFunction *b)
{
- COMPARE_SCALAR_FIELD(ordinality);
COMPARE_SCALAR_FIELD(lateral);
- COMPARE_NODE_FIELD(funccallnode);
+ COMPARE_SCALAR_FIELD(ordinality);
+ COMPARE_SCALAR_FIELD(is_table);
+ COMPARE_NODE_FIELD(functions);
COMPARE_NODE_FIELD(alias);
COMPARE_NODE_FIELD(coldeflist);
@@ -2179,6 +2180,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
COMPARE_SCALAR_FIELD(collOid);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(fdwoptions);
+ COMPARE_LOCATION_FIELD(location);
return true;
}
@@ -2245,10 +2247,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
COMPARE_SCALAR_FIELD(security_barrier);
COMPARE_SCALAR_FIELD(jointype);
COMPARE_NODE_FIELD(joinaliasvars);
- COMPARE_NODE_FIELD(funcexpr);
- COMPARE_NODE_FIELD(funccoltypes);
- COMPARE_NODE_FIELD(funccoltypmods);
- COMPARE_NODE_FIELD(funccolcollations);
+ COMPARE_NODE_FIELD(functions);
COMPARE_SCALAR_FIELD(funcordinality);
COMPARE_NODE_FIELD(values_lists);
COMPARE_NODE_FIELD(values_collations);
@@ -2271,6 +2270,20 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
return true;
}
+static bool
+_equalRangeTblFunction(const RangeTblFunction *a, const RangeTblFunction *b)
+{
+ COMPARE_NODE_FIELD(funcexpr);
+ COMPARE_SCALAR_FIELD(funccolcount);
+ COMPARE_NODE_FIELD(funccolnames);
+ COMPARE_NODE_FIELD(funccoltypes);
+ COMPARE_NODE_FIELD(funccoltypmods);
+ COMPARE_NODE_FIELD(funccolcollations);
+ COMPARE_BITMAPSET_FIELD(funcparams);
+
+ return true;
+}
+
static bool
_equalWithCheckOption(const WithCheckOption *a, const WithCheckOption *b)
{
@@ -3018,6 +3031,9 @@ equal(const void *a, const void *b)
case T_RangeTblEntry:
retval = _equalRangeTblEntry(a, b);
break;
+ case T_RangeTblFunction:
+ retval = _equalRangeTblFunction(a, b);
+ break;
case T_WithCheckOption:
retval = _equalWithCheckOption(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 4a7e793ae0..d3ed4fe98b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -122,14 +122,10 @@ makeVarFromTargetEntry(Index varno,
* a rowtype; either a named composite type, or RECORD. This function
* encapsulates the logic for determining the correct rowtype OID to use.
*
- * If allowScalar is true, then for the case where the RTE is a function
+ * If allowScalar is true, then for the case where the RTE is a single function
* returning a non-composite result type, we produce a normal Var referencing
* the function's result directly, instead of the single-column composite
* value that the whole-row notation might otherwise suggest.
- *
- * We also handle the specific case of function RTEs with ordinality,
- * where the additional column has to be added. This forces the result
- * to be composite and RECORD type.
*/
Var *
makeWholeRowVar(RangeTblEntry *rte,
@@ -139,6 +135,7 @@ makeWholeRowVar(RangeTblEntry *rte,
{
Var *result;
Oid toid;
+ Node *fexpr;
switch (rte->rtekind)
{
@@ -157,31 +154,27 @@ makeWholeRowVar(RangeTblEntry *rte,
break;
case RTE_FUNCTION:
- /*
- * RTE is a function with or without ordinality. We map the
- * cases as follows:
- *
- * If ordinality is set, we return a composite var even if
- * the function is a scalar. This var is always of RECORD type.
- *
- * If ordinality is not set but the function returns a row,
- * we keep the function's return type.
- *
- * If the function is a scalar, we do what allowScalar requests.
- */
- toid = exprType(rte->funcexpr);
- if (rte->funcordinality)
+ /*
+ * If there's more than one function, or ordinality is requested,
+ * force a RECORD result, since there's certainly more than one
+ * column involved and it can't be a known named type.
+ */
+ if (rte->funcordinality || list_length(rte->functions) != 1)
{
- /* ORDINALITY always produces an anonymous RECORD result */
+ /* always produces an anonymous RECORD result */
result = makeVar(varno,
InvalidAttrNumber,
RECORDOID,
-1,
InvalidOid,
varlevelsup);
+ break;
}
- else if (type_is_rowtype(toid))
+
+ fexpr = ((RangeTblFunction *) linitial(rte->functions))->funcexpr;
+ toid = exprType(fexpr);
+ if (type_is_rowtype(toid))
{
/* func returns composite; same as relation case */
result = makeVar(varno,
@@ -198,7 +191,7 @@ makeWholeRowVar(RangeTblEntry *rte,
1,
toid,
-1,
- exprCollation(rte->funcexpr),
+ exprCollation(fexpr),
varlevelsup);
}
else
@@ -214,6 +207,7 @@ makeWholeRowVar(RangeTblEntry *rte,
break;
default:
+
/*
* RTE is a join, subselect, or VALUES. We represent this as a
* whole-row Var of RECORD type. (Note that in most cases the Var
@@ -541,23 +535,21 @@ makeDefElemExtended(char *nameSpace, char *name, Node *arg,
* makeFuncCall -
*
* Initialize a FuncCall struct with the information every caller must
- * supply. Any non-default parameters have to be handled by the
- * caller.
- *
+ * supply. Any non-default parameters have to be inserted by the caller.
*/
-
FuncCall *
makeFuncCall(List *name, List *args, int location)
{
- FuncCall *n = makeNode(FuncCall);
+ FuncCall *n = makeNode(FuncCall);
+
n->funcname = name;
n->args = args;
- n->location = location;
n->agg_order = NIL;
n->agg_filter = NULL;
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
n->over = NULL;
+ n->location = location;
return n;
}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 908f397d50..d7db67dc9e 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1447,6 +1447,9 @@ exprLocation(const Node *expr)
case T_TypeName:
loc = ((const TypeName *) expr)->location;
break;
+ case T_ColumnDef:
+ loc = ((const ColumnDef *) expr)->location;
+ break;
case T_Constraint:
loc = ((const Constraint *) expr)->location;
break;
@@ -1901,6 +1904,8 @@ expression_tree_walker(Node *node,
break;
case T_PlaceHolderInfo:
return walker(((PlaceHolderInfo *) node)->ph_var, context);
+ case T_RangeTblFunction:
+ return walker(((RangeTblFunction *) node)->funcexpr, context);
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -2000,7 +2005,7 @@ range_table_walker(List *rtable,
return true;
break;
case RTE_FUNCTION:
- if (walker(rte->funcexpr, context))
+ if (walker(rte->functions, context))
return true;
break;
case RTE_VALUES:
@@ -2615,6 +2620,17 @@ expression_tree_mutator(Node *node,
return (Node *) newnode;
}
break;
+ case T_RangeTblFunction:
+ {
+ RangeTblFunction *rtfunc = (RangeTblFunction *) node;
+ RangeTblFunction *newnode;
+
+ FLATCOPY(newnode, rtfunc, RangeTblFunction);
+ MUTATE(newnode->funcexpr, rtfunc->funcexpr, Node *);
+ /* Assume we need not copy the coldef info lists */
+ return (Node *) newnode;
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -2725,7 +2741,7 @@ range_table_mutator(List *rtable,
}
break;
case RTE_FUNCTION:
- MUTATE(newrte->funcexpr, rte->funcexpr, Node *);
+ MUTATE(newrte->functions, rte->functions, List *);
break;
case RTE_VALUES:
MUTATE(newrte->values_lists, rte->values_lists, List *);
@@ -3113,10 +3129,12 @@ raw_expression_tree_walker(Node *node,
{
RangeFunction *rf = (RangeFunction *) node;
- if (walker(rf->funccallnode, context))
+ if (walker(rf->functions, context))
return true;
if (walker(rf->alias, context))
return true;
+ if (walker(rf->coldeflist, context))
+ return true;
}
break;
case T_TypeName:
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b39927e025..4c7505e334 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -516,11 +516,7 @@ _outFunctionScan(StringInfo str, const FunctionScan *node)
_outScanInfo(str, (const Scan *) node);
- WRITE_NODE_FIELD(funcexpr);
- WRITE_NODE_FIELD(funccolnames);
- WRITE_NODE_FIELD(funccoltypes);
- WRITE_NODE_FIELD(funccoltypmods);
- WRITE_NODE_FIELD(funccolcollations);
+ WRITE_NODE_FIELD(functions);
WRITE_BOOL_FIELD(funcordinality);
}
@@ -2154,6 +2150,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
WRITE_OID_FIELD(collOid);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(fdwoptions);
+ WRITE_LOCATION_FIELD(location);
}
static void
@@ -2382,10 +2379,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
WRITE_NODE_FIELD(joinaliasvars);
break;
case RTE_FUNCTION:
- WRITE_NODE_FIELD(funcexpr);
- WRITE_NODE_FIELD(funccoltypes);
- WRITE_NODE_FIELD(funccoltypmods);
- WRITE_NODE_FIELD(funccolcollations);
+ WRITE_NODE_FIELD(functions);
WRITE_BOOL_FIELD(funcordinality);
break;
case RTE_VALUES:
@@ -2414,6 +2408,20 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
WRITE_BITMAPSET_FIELD(modifiedCols);
}
+static void
+_outRangeTblFunction(StringInfo str, const RangeTblFunction *node)
+{
+ WRITE_NODE_TYPE("RANGETBLFUNCTION");
+
+ WRITE_NODE_FIELD(funcexpr);
+ WRITE_INT_FIELD(funccolcount);
+ WRITE_NODE_FIELD(funccolnames);
+ WRITE_NODE_FIELD(funccoltypes);
+ WRITE_NODE_FIELD(funccoltypmods);
+ WRITE_NODE_FIELD(funccolcollations);
+ WRITE_BITMAPSET_FIELD(funcparams);
+}
+
static void
_outAExpr(StringInfo str, const A_Expr *node)
{
@@ -2619,9 +2627,10 @@ _outRangeFunction(StringInfo str, const RangeFunction *node)
{
WRITE_NODE_TYPE("RANGEFUNCTION");
- WRITE_BOOL_FIELD(ordinality);
WRITE_BOOL_FIELD(lateral);
- WRITE_NODE_FIELD(funccallnode);
+ WRITE_BOOL_FIELD(ordinality);
+ WRITE_BOOL_FIELD(is_table);
+ WRITE_NODE_FIELD(functions);
WRITE_NODE_FIELD(alias);
WRITE_NODE_FIELD(coldeflist);
}
@@ -3156,6 +3165,9 @@ _outNode(StringInfo str, const void *obj)
case T_RangeTblEntry:
_outRangeTblEntry(str, obj);
break;
+ case T_RangeTblFunction:
+ _outRangeTblFunction(str, obj);
+ break;
case T_A_Expr:
_outAExpr(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index d325bb3212..2e2cfa7af6 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1220,10 +1220,7 @@ _readRangeTblEntry(void)
READ_NODE_FIELD(joinaliasvars);
break;
case RTE_FUNCTION:
- READ_NODE_FIELD(funcexpr);
- READ_NODE_FIELD(funccoltypes);
- READ_NODE_FIELD(funccoltypmods);
- READ_NODE_FIELD(funccolcollations);
+ READ_NODE_FIELD(functions);
READ_BOOL_FIELD(funcordinality);
break;
case RTE_VALUES:
@@ -1255,6 +1252,25 @@ _readRangeTblEntry(void)
READ_DONE();
}
+/*
+ * _readRangeTblFunction
+ */
+static RangeTblFunction *
+_readRangeTblFunction(void)
+{
+ READ_LOCALS(RangeTblFunction);
+
+ READ_NODE_FIELD(funcexpr);
+ READ_INT_FIELD(funccolcount);
+ READ_NODE_FIELD(funccolnames);
+ READ_NODE_FIELD(funccoltypes);
+ READ_NODE_FIELD(funccoltypmods);
+ READ_NODE_FIELD(funccolcollations);
+ READ_BITMAPSET_FIELD(funcparams);
+
+ READ_DONE();
+}
+
/*
* parseNodeString
@@ -1378,6 +1394,8 @@ parseNodeString(void)
return_value = _readFromExpr();
else if (MATCH("RTE", 3))
return_value = _readRangeTblEntry();
+ else if (MATCH("RANGETBLFUNCTION", 16))
+ return_value = _readRangeTblFunction();
else if (MATCH("NOTIFY", 6))
return_value = _readNotifyStmt();
else if (MATCH("DECLARECURSOR", 13))
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index bfd3809a00..96fe50f0b2 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -18,6 +18,7 @@
#include
#include "catalog/pg_class.h"
+#include "catalog/pg_operator.h"
#include "foreign/fdwapi.h"
#include "nodes/nodeFuncs.h"
#ifdef OPTIMIZER_DEBUG
@@ -1258,6 +1259,7 @@ static void
set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
Relids required_outer;
+ List *pathkeys = NIL;
/*
* We don't support pushing join clauses into the quals of a function
@@ -1266,8 +1268,55 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
required_outer = rel->lateral_relids;
+ /*
+ * The result is considered unordered unless ORDINALITY was used, in which
+ * case it is ordered by the ordinal column (the last one). See if we
+ * care, by checking for uses of that Var in equivalence classes.
+ */
+ if (rte->funcordinality)
+ {
+ AttrNumber ordattno = rel->max_attr;
+ Var *var = NULL;
+ ListCell *lc;
+
+ /*
+ * Is there a Var for it in reltargetlist? If not, the query did not
+ * reference the ordinality column, or at least not in any way that
+ * would be interesting for sorting.
+ */
+ foreach(lc, rel->reltargetlist)
+ {
+ Var *node = (Var *) lfirst(lc);
+
+ /* checking varno/varlevelsup is just paranoia */
+ if (IsA(node, Var) &&
+ node->varattno == ordattno &&
+ node->varno == rel->relid &&
+ node->varlevelsup == 0)
+ {
+ var = node;
+ break;
+ }
+ }
+
+ /*
+ * Try to build pathkeys for this Var with int8 sorting. We tell
+ * build_expression_pathkey not to build any new equivalence class; if
+ * the Var isn't already mentioned in some EC, it means that nothing
+ * cares about the ordering.
+ */
+ if (var)
+ pathkeys = build_expression_pathkey(root,
+ (Expr *) var,
+ NULL, /* below outer joins */
+ Int8LessOperator,
+ rel->relids,
+ false);
+ }
+
/* Generate appropriate path */
- add_path(rel, create_functionscan_path(root, rel, required_outer));
+ add_path(rel, create_functionscan_path(root, rel,
+ pathkeys, required_outer));
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(rel);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index e7f8cec0fe..50f08521bf 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -1076,9 +1076,9 @@ cost_functionscan(Path *path, PlannerInfo *root,
path->rows = baserel->rows;
/*
- * Estimate costs of executing the function expression.
+ * Estimate costs of executing the function expression(s).
*
- * Currently, nodeFunctionscan.c always executes the function to
+ * Currently, nodeFunctionscan.c always executes the functions to
* completion before returning any rows, and caches the results in a
* tuplestore. So the function eval cost is all startup cost, and per-row
* costs are minimal.
@@ -1088,7 +1088,7 @@ cost_functionscan(Path *path, PlannerInfo *root,
* estimates for functions tend to be, there's not a lot of point in that
* refinement right now.
*/
- cost_qual_eval_node(&exprcost, rte->funcexpr, root);
+ cost_qual_eval_node(&exprcost, (Node *) rte->functions, root);
startup_cost += exprcost.startup + exprcost.per_tuple;
@@ -3845,14 +3845,26 @@ void
set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel)
{
RangeTblEntry *rte;
+ ListCell *lc;
/* Should only be applied to base relations that are functions */
Assert(rel->relid > 0);
rte = planner_rt_fetch(rel->relid, root);
Assert(rte->rtekind == RTE_FUNCTION);
- /* Estimate number of rows the function itself will return */
- rel->tuples = expression_returns_set_rows(rte->funcexpr);
+ /*
+ * Estimate number of rows the functions will return. The rowcount of the
+ * node is that of the largest function result.
+ */
+ rel->tuples = 0;
+ foreach(lc, rte->functions)
+ {
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
+ double ntup = expression_returns_set_rows(rtfunc->funcexpr);
+
+ if (ntup > rel->tuples)
+ rel->tuples = ntup;
+ }
/* Now estimate number of output rows, etc */
set_baserel_size_estimates(root, rel);
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 032b2cdc13..9c8ede658f 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -501,6 +501,57 @@ build_index_pathkeys(PlannerInfo *root,
return retval;
}
+/*
+ * build_expression_pathkey
+ * Build a pathkeys list that describes an ordering by a single expression
+ * using the given sort operator.
+ *
+ * expr, nullable_relids, and rel are as for make_pathkey_from_sortinfo.
+ * We induce the other arguments assuming default sort order for the operator.
+ *
+ * Similarly to make_pathkey_from_sortinfo, the result is NIL if create_it
+ * is false and the expression isn't already in some EquivalenceClass.
+ */
+List *
+build_expression_pathkey(PlannerInfo *root,
+ Expr *expr,
+ Relids nullable_relids,
+ Oid opno,
+ Relids rel,
+ bool create_it)
+{
+ List *pathkeys;
+ Oid opfamily,
+ opcintype;
+ int16 strategy;
+ PathKey *cpathkey;
+
+ /* Find the operator in pg_amop --- failure shouldn't happen */
+ if (!get_ordering_op_properties(opno,
+ &opfamily, &opcintype, &strategy))
+ elog(ERROR, "operator %u is not a valid ordering operator",
+ opno);
+
+ cpathkey = make_pathkey_from_sortinfo(root,
+ expr,
+ nullable_relids,
+ opfamily,
+ opcintype,
+ exprCollation((Node *) expr),
+ (strategy == BTGreaterStrategyNumber),
+ (strategy == BTGreaterStrategyNumber),
+ 0,
+ rel,
+ create_it);
+
+ if (cpathkey)
+ pathkeys = list_make1(cpathkey);
+ else
+ pathkeys = NIL;
+
+ return pathkeys;
+}
+
/*
* convert_subquery_pathkeys
* Build a pathkeys list that describes the ordering of a subquery's
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 5947e5b136..f2c122d295 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -115,9 +115,7 @@ static BitmapHeapScan *make_bitmap_heapscan(List *qptlist,
static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
List *tidquals);
static FunctionScan *make_functionscan(List *qptlist, List *qpqual,
- Index scanrelid, Node *funcexpr, bool ordinality,
- List *funccolnames, List *funccoltypes, List *funccoltypmods,
- List *funccolcollations);
+ Index scanrelid, List *functions, bool funcordinality);
static ValuesScan *make_valuesscan(List *qptlist, List *qpqual,
Index scanrelid, List *values_lists);
static CteScan *make_ctescan(List *qptlist, List *qpqual,
@@ -1709,13 +1707,13 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
FunctionScan *scan_plan;
Index scan_relid = best_path->parent->relid;
RangeTblEntry *rte;
- Node *funcexpr;
+ List *functions;
/* it should be a function base rel... */
Assert(scan_relid > 0);
rte = planner_rt_fetch(scan_relid, root);
Assert(rte->rtekind == RTE_FUNCTION);
- funcexpr = rte->funcexpr;
+ functions = rte->functions;
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
@@ -1728,17 +1726,12 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
{
scan_clauses = (List *)
replace_nestloop_params(root, (Node *) scan_clauses);
- /* The func expression itself could contain nestloop params, too */
- funcexpr = replace_nestloop_params(root, funcexpr);
+ /* The function expressions could contain nestloop params, too */
+ functions = (List *) replace_nestloop_params(root, (Node *) functions);
}
scan_plan = make_functionscan(tlist, scan_clauses, scan_relid,
- funcexpr,
- rte->funcordinality,
- rte->eref->colnames,
- rte->funccoltypes,
- rte->funccoltypmods,
- rte->funccolcollations);
+ functions, rte->funcordinality);
copy_path_costsize(&scan_plan->scan.plan, best_path);
@@ -3388,12 +3381,8 @@ static FunctionScan *
make_functionscan(List *qptlist,
List *qpqual,
Index scanrelid,
- Node *funcexpr,
- bool ordinality,
- List *funccolnames,
- List *funccoltypes,
- List *funccoltypmods,
- List *funccolcollations)
+ List *functions,
+ bool funcordinality)
{
FunctionScan *node = makeNode(FunctionScan);
Plan *plan = &node->scan.plan;
@@ -3404,12 +3393,8 @@ make_functionscan(List *qptlist,
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
- node->funcexpr = funcexpr;
- node->funcordinality = ordinality;
- node->funccolnames = funccolnames;
- node->funccoltypes = funccoltypes;
- node->funccoltypmods = funccoltypmods;
- node->funccolcollations = funccolcollations;
+ node->functions = functions;
+ node->funcordinality = funcordinality;
return node;
}
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 04a399ee13..5960664392 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -307,7 +307,7 @@ extract_lateral_references(PlannerInfo *root, RelOptInfo *brel, Index rtindex)
if (rte->rtekind == RTE_SUBQUERY)
vars = pull_vars_of_level((Node *) rte->subquery, 1);
else if (rte->rtekind == RTE_FUNCTION)
- vars = pull_vars_of_level(rte->funcexpr, 0);
+ vars = pull_vars_of_level((Node *) rte->functions, 0);
else if (rte->rtekind == RTE_VALUES)
vars = pull_vars_of_level((Node *) rte->values_lists, 0);
else
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d8aa35dee7..66707944a0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -485,9 +485,9 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
}
else if (rte->rtekind == RTE_FUNCTION)
{
- /* Preprocess the function expression fully */
+ /* Preprocess the function expression(s) fully */
kind = rte->lateral ? EXPRKIND_RTFUNC_LATERAL : EXPRKIND_RTFUNC;
- rte->funcexpr = preprocess_expression(root, rte->funcexpr, kind);
+ rte->functions = (List *) preprocess_expression(root, (Node *) rte->functions, kind);
}
else if (rte->rtekind == RTE_VALUES)
{
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index b78d72701b..5c9f3d64ce 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -381,10 +381,7 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
/* zap unneeded sub-structure */
newrte->subquery = NULL;
newrte->joinaliasvars = NIL;
- newrte->funcexpr = NULL;
- newrte->funccoltypes = NIL;
- newrte->funccoltypmods = NIL;
- newrte->funccolcollations = NIL;
+ newrte->functions = NIL;
newrte->values_lists = NIL;
newrte->values_collations = NIL;
newrte->ctecoltypes = NIL;
@@ -525,8 +522,8 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
splan->scan.plan.qual =
fix_scan_list(root, splan->scan.plan.qual, rtoffset);
- splan->funcexpr =
- fix_scan_expr(root, splan->funcexpr, rtoffset);
+ splan->functions =
+ fix_scan_list(root, splan->functions, rtoffset);
}
break;
case T_ValuesScan:
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 0df70c4443..d8cabbd5bf 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2135,9 +2135,37 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
break;
case T_FunctionScan:
- finalize_primnode(((FunctionScan *) plan)->funcexpr,
- &context);
- context.paramids = bms_add_members(context.paramids, scan_params);
+ {
+ FunctionScan *fscan = (FunctionScan *) plan;
+ ListCell *lc;
+
+ /*
+ * Call finalize_primnode independently on each function
+ * expression, so that we can record which params are
+ * referenced in each, in order to decide which need
+ * re-evaluating during rescan.
+ */
+ foreach(lc, fscan->functions)
+ {
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
+ finalize_primnode_context funccontext;
+
+ funccontext = context;
+ funccontext.paramids = NULL;
+
+ finalize_primnode(rtfunc->funcexpr, &funccontext);
+
+ /* remember results for execution */
+ rtfunc->funcparams = funccontext.paramids;
+
+ /* add the function's params to the overall set */
+ context.paramids = bms_add_members(context.paramids,
+ funccontext.paramids);
+ }
+
+ context.paramids = bms_add_members(context.paramids,
+ scan_params);
+ }
break;
case T_ValuesScan:
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index c742cc9542..485ac31bd3 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -580,10 +580,7 @@ inline_set_returning_functions(PlannerInfo *root)
/* Successful expansion, replace the rtable entry */
rte->rtekind = RTE_SUBQUERY;
rte->subquery = funcquery;
- rte->funcexpr = NULL;
- rte->funccoltypes = NIL;
- rte->funccoltypmods = NIL;
- rte->funccolcollations = NIL;
+ rte->functions = NIL;
}
}
}
@@ -1623,8 +1620,8 @@ replace_vars_in_jointree(Node *jtnode,
context);
break;
case RTE_FUNCTION:
- rte->funcexpr =
- pullup_replace_vars(rte->funcexpr,
+ rte->functions = (List *)
+ pullup_replace_vars((Node *) rte->functions,
context);
break;
case RTE_VALUES:
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 7ce8a9d818..a7fdd52c29 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4509,6 +4509,7 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
Query *
inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
{
+ RangeTblFunction *rtfunc;
FuncExpr *fexpr;
Oid func_oid;
HeapTuple func_tuple;
@@ -4537,14 +4538,18 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
*/
check_stack_depth();
- /* Fail if the caller wanted ORDINALITY - we don't implement that here. */
+ /* Fail if the RTE has ORDINALITY - we don't implement that here. */
if (rte->funcordinality)
return NULL;
- /* Fail if FROM item isn't a simple FuncExpr */
- fexpr = (FuncExpr *) rte->funcexpr;
- if (fexpr == NULL || !IsA(fexpr, FuncExpr))
+ /* Fail if RTE isn't a single, simple FuncExpr */
+ if (list_length(rte->functions) != 1)
return NULL;
+ rtfunc = (RangeTblFunction *) linitial(rte->functions);
+
+ if (!IsA(rtfunc->funcexpr, FuncExpr))
+ return NULL;
+ fexpr = (FuncExpr *) rtfunc->funcexpr;
func_oid = fexpr->funcid;
@@ -4734,7 +4739,8 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
*/
if (fexpr->funcresulttype == RECORDOID &&
get_func_result_type(func_oid, NULL, NULL) == TYPEFUNC_RECORD &&
- !tlist_matches_coltypelist(querytree->targetList, rte->funccoltypes))
+ !tlist_matches_coltypelist(querytree->targetList,
+ rtfunc->funccoltypes))
goto fail;
/*
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 64b1705191..a7169efd85 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1623,7 +1623,7 @@ create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel,
*/
Path *
create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
- Relids required_outer)
+ List *pathkeys, Relids required_outer)
{
Path *pathnode = makeNode(Path);
@@ -1631,7 +1631,7 @@ create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
pathnode->parent = rel;
pathnode->param_info = get_baserel_parampathinfo(root, rel,
required_outer);
- pathnode->pathkeys = NIL; /* for now, assume unordered result */
+ pathnode->pathkeys = pathkeys;
cost_functionscan(pathnode, root, rel, pathnode->param_info);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 11f629118b..19220971da 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -406,6 +406,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
a_expr b_expr c_expr AexprConst indirection_el
columnref in_expr having_clause func_table array_expr
ExclusionWhereClause
+%type func_table_item func_table_list opt_col_def_list
+%type opt_ordinality
%type ExclusionConstraintList ExclusionConstraintElem
%type func_arg_list
%type func_arg_expr
@@ -613,6 +615,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
*/
%token NULLS_FIRST NULLS_LAST WITH_ORDINALITY WITH_TIME
+
/* Precedence: lowest to highest */
%nonassoc SET /* see relation_expr_opt_alias */
%left UNION EXCEPT
@@ -1926,10 +1929,11 @@ alter_table_cmd:
n->subtype = AT_AlterColumnType;
n->name = $3;
n->def = (Node *) def;
- /* We only use these three fields of the ColumnDef node */
+ /* We only use these fields of the ColumnDef node */
def->typeName = $6;
def->collClause = (CollateClause *) $7;
def->raw_default = $8;
+ def->location = @3;
$$ = (Node *)n;
}
/* ALTER FOREIGN TABLE ALTER [COLUMN] OPTIONS */
@@ -2354,10 +2358,11 @@ alter_type_cmd:
n->name = $3;
n->def = (Node *) def;
n->behavior = $8;
- /* We only use these three fields of the ColumnDef node */
+ /* We only use these fields of the ColumnDef node */
def->typeName = $6;
def->collClause = (CollateClause *) $7;
def->raw_default = NULL;
+ def->location = @3;
$$ = (Node *)n;
}
;
@@ -2782,6 +2787,7 @@ columnDef: ColId Typename create_generic_options ColQualList
n->fdwoptions = $3;
SplitColQualList($4, &n->constraints, &n->collClause,
yyscanner);
+ n->location = @1;
$$ = (Node *)n;
}
;
@@ -2801,6 +2807,7 @@ columnOptions: ColId WITH OPTIONS ColQualList
n->collOid = InvalidOid;
SplitColQualList($4, &n->constraints, &n->collClause,
yyscanner);
+ n->location = @1;
$$ = (Node *)n;
}
;
@@ -9648,44 +9655,19 @@ table_ref: relation_expr opt_alias_clause
}
| func_table func_alias_clause
{
- RangeFunction *n = makeNode(RangeFunction);
- n->lateral = false;
- n->ordinality = false;
- n->funccallnode = $1;
+ RangeFunction *n = (RangeFunction *) $1;
n->alias = linitial($2);
n->coldeflist = lsecond($2);
$$ = (Node *) n;
}
- | func_table WITH_ORDINALITY func_alias_clause
- {
- RangeFunction *n = makeNode(RangeFunction);
- n->lateral = false;
- n->ordinality = true;
- n->funccallnode = $1;
- n->alias = linitial($3);
- n->coldeflist = lsecond($3);
- $$ = (Node *) n;
- }
| LATERAL_P func_table func_alias_clause
{
- RangeFunction *n = makeNode(RangeFunction);
+ RangeFunction *n = (RangeFunction *) $2;
n->lateral = true;
- n->ordinality = false;
- n->funccallnode = $2;
n->alias = linitial($3);
n->coldeflist = lsecond($3);
$$ = (Node *) n;
}
- | LATERAL_P func_table WITH_ORDINALITY func_alias_clause
- {
- RangeFunction *n = makeNode(RangeFunction);
- n->lateral = true;
- n->ordinality = true;
- n->funccallnode = $2;
- n->alias = linitial($4);
- n->coldeflist = lsecond($4);
- $$ = (Node *) n;
- }
| select_with_parens opt_alias_clause
{
RangeSubselect *n = makeNode(RangeSubselect);
@@ -9996,7 +9978,54 @@ relation_expr_opt_alias: relation_expr %prec UMINUS
}
;
-func_table: func_expr_windowless { $$ = $1; }
+/*
+ * func_table represents a function invocation in a FROM list. It can be
+ * a plain function call, like "foo(...)", or a TABLE expression with
+ * one or more function calls, "TABLE (foo(...), bar(...))",
+ * optionally with WITH ORDINALITY attached.
+ * In the TABLE syntax, a column definition list can be given for each
+ * function, for example:
+ * TABLE (foo() AS (foo_res_a text, foo_res_b text),
+ * bar() AS (bar_res_a text, bar_res_b text))
+ * It's also possible to attach a column definition list to the RangeFunction
+ * as a whole, but that's handled by the table_ref production.
+ */
+func_table: func_expr_windowless opt_ordinality
+ {
+ RangeFunction *n = makeNode(RangeFunction);
+ n->lateral = false;
+ n->ordinality = $2;
+ n->is_table = false;
+ n->functions = list_make1(list_make2($1, NIL));
+ /* alias and coldeflist are set by table_ref production */
+ $$ = (Node *) n;
+ }
+ | TABLE '(' func_table_list ')' opt_ordinality
+ {
+ RangeFunction *n = makeNode(RangeFunction);
+ n->lateral = false;
+ n->ordinality = $5;
+ n->is_table = true;
+ n->functions = $3;
+ /* alias and coldeflist are set by table_ref production */
+ $$ = (Node *) n;
+ }
+ ;
+
+func_table_item: func_expr_windowless opt_col_def_list
+ { $$ = list_make2($1, $2); }
+ ;
+
+func_table_list: func_table_item { $$ = list_make1($1); }
+ | func_table_list ',' func_table_item { $$ = lappend($1, $3); }
+ ;
+
+opt_col_def_list: AS '(' TableFuncElementList ')' { $$ = $3; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+opt_ordinality: WITH_ORDINALITY { $$ = true; }
+ | /*EMPTY*/ { $$ = false; }
;
@@ -10051,6 +10080,7 @@ TableFuncElement: ColId Typename opt_collate_clause
n->collClause = (CollateClause *) $3;
n->collOid = InvalidOid;
n->constraints = NIL;
+ n->location = @1;
$$ = (Node *)n;
}
;
@@ -11172,11 +11202,11 @@ func_application: func_name '(' ')'
/*
- * func_expr and its cousin func_expr_windowless is split out from c_expr just
+ * func_expr and its cousin func_expr_windowless are split out from c_expr just
* so that we have classifications for "everything that is a function call or
- * looks like one". This isn't very important, but it saves us having to document
- * which variants are legal in the backwards-compatible functional-index syntax
- * for CREATE INDEX.
+ * looks like one". This isn't very important, but it saves us having to
+ * document which variants are legal in places like "FROM function()" or the
+ * backwards-compatible functional-index syntax for CREATE INDEX.
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 7a1261d0fd..8b4c0ae0d3 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -24,6 +24,7 @@
#include "optimizer/tlist.h"
#include "parser/analyze.h"
#include "parser/parsetree.h"
+#include "parser/parser.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
@@ -515,24 +516,18 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
static RangeTblEntry *
transformRangeFunction(ParseState *pstate, RangeFunction *r)
{
- Node *funcexpr;
- char *funcname;
+ List *funcexprs = NIL;
+ List *funcnames = NIL;
+ List *coldeflists = NIL;
bool is_lateral;
RangeTblEntry *rte;
-
- /*
- * Get function name for possible use as alias. We use the same
- * transformation rules as for a SELECT output expression. For a FuncCall
- * node, the result will be the function name, but it is possible for the
- * grammar to hand back other node types.
- */
- funcname = FigureColname(r->funccallnode);
+ ListCell *lc;
/*
* We make lateral_only names of this level visible, whether or not the
- * function is explicitly marked LATERAL. This is needed for SQL spec
- * compliance in the case of UNNEST(), and seems useful on convenience
- * grounds for all functions in FROM.
+ * RangeFunction is explicitly marked LATERAL. This is needed for SQL
+ * spec compliance in the case of UNNEST(), and seems useful on
+ * convenience grounds for all functions in FROM.
*
* (LATERAL can't nest within a single pstate level, so we don't need
* save/restore logic here.)
@@ -541,46 +536,171 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
pstate->p_lateral_active = true;
/*
- * Transform the raw expression.
+ * Transform the raw expressions.
+ *
+ * While transforming, also save function names for possible use as alias
+ * and column names. We use the same transformation rules as for a SELECT
+ * output expression. For a FuncCall node, the result will be the
+ * function name, but it is possible for the grammar to hand back other
+ * node types.
+ *
+ * We have to get this info now, because FigureColname only works on raw
+ * parsetrees. Actually deciding what to do with the names is left up to
+ * addRangeTableEntryForFunction.
+ *
+ * Likewise, collect column definition lists if there were any. But
+ * complain if we find one here and the RangeFunction has one too.
*/
- funcexpr = transformExpr(pstate, r->funccallnode, EXPR_KIND_FROM_FUNCTION);
+ foreach(lc, r->functions)
+ {
+ List *pair = (List *) lfirst(lc);
+ Node *fexpr;
+ List *coldeflist;
+
+ /* Disassemble the function-call/column-def-list pairs */
+ Assert(list_length(pair) == 2);
+ fexpr = (Node *) linitial(pair);
+ coldeflist = (List *) lsecond(pair);
+
+ /*
+ * If we find a function call unnest() with more than one argument and
+ * no special decoration, transform it into separate unnest() calls on
+ * each argument. This is a kluge, for sure, but it's less nasty than
+ * other ways of implementing the SQL-standard UNNEST() syntax.
+ *
+ * If there is any decoration (including a coldeflist), we don't
+ * transform, which probably means a no-such-function error later. We
+ * could alternatively throw an error right now, but that doesn't seem
+ * tremendously helpful. If someone is using any such decoration,
+ * then they're not using the SQL-standard syntax, and they're more
+ * likely expecting an un-tweaked function call.
+ *
+ * Note: the transformation changes a non-schema-qualified unnest()
+ * function name into schema-qualified pg_catalog.unnest(). This
+ * choice is also a bit debatable, but it seems reasonable to force
+ * use of built-in unnest() when we make this transformation.
+ */
+ if (IsA(fexpr, FuncCall))
+ {
+ FuncCall *fc = (FuncCall *) fexpr;
+
+ if (list_length(fc->funcname) == 1 &&
+ strcmp(strVal(linitial(fc->funcname)), "unnest") == 0 &&
+ list_length(fc->args) > 1 &&
+ fc->agg_order == NIL &&
+ fc->agg_filter == NULL &&
+ !fc->agg_star &&
+ !fc->agg_distinct &&
+ !fc->func_variadic &&
+ fc->over == NULL &&
+ coldeflist == NIL)
+ {
+ ListCell *lc;
+
+ foreach(lc, fc->args)
+ {
+ Node *arg = (Node *) lfirst(lc);
+ FuncCall *newfc;
+
+ newfc = makeFuncCall(SystemFuncName("unnest"),
+ list_make1(arg),
+ fc->location);
+
+ funcexprs = lappend(funcexprs,
+ transformExpr(pstate, (Node *) newfc,
+ EXPR_KIND_FROM_FUNCTION));
+
+ funcnames = lappend(funcnames,
+ FigureColname((Node *) newfc));
+
+ /* coldeflist is empty, so no error is possible */
+
+ coldeflists = lappend(coldeflists, coldeflist);
+ }
+ continue; /* done with this function item */
+ }
+ }
+
+ /* normal case ... */
+ funcexprs = lappend(funcexprs,
+ transformExpr(pstate, fexpr,
+ EXPR_KIND_FROM_FUNCTION));
+
+ funcnames = lappend(funcnames,
+ FigureColname(fexpr));
+
+ if (coldeflist && r->coldeflist)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("multiple column definition lists are not allowed for the same function"),
+ parser_errposition(pstate,
+ exprLocation((Node *) r->coldeflist))));
+
+ coldeflists = lappend(coldeflists, coldeflist);
+ }
pstate->p_lateral_active = false;
/*
- * We must assign collations now so that we can fill funccolcollations.
+ * We must assign collations now so that the RTE exposes correct collation
+ * info for Vars created from it.
*/
- assign_expr_collations(pstate, funcexpr);
+ assign_list_collations(pstate, funcexprs);
+
+ /*
+ * Install the top-level coldeflist if there was one (we already checked
+ * that there was no conflicting per-function coldeflist).
+ *
+ * We only allow this when there's a single function (even after UNNEST
+ * expansion) and no WITH ORDINALITY. The reason for the latter
+ * restriction is that it's not real clear whether the ordinality column
+ * should be in the coldeflist, and users are too likely to make mistakes
+ * in one direction or the other. Putting the coldeflist inside TABLE()
+ * is much clearer in this case.
+ */
+ if (r->coldeflist)
+ {
+ if (list_length(funcexprs) != 1)
+ {
+ if (r->is_table)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("TABLE() with multiple functions cannot have a column definition list"),
+ errhint("Put a separate column definition list for each function inside TABLE()."),
+ parser_errposition(pstate,
+ exprLocation((Node *) r->coldeflist))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("UNNEST() with multiple arguments cannot have a column definition list"),
+ errhint("Use separate UNNEST() calls inside TABLE(), and attach a column definition list to each one."),
+ parser_errposition(pstate,
+ exprLocation((Node *) r->coldeflist))));
+ }
+ if (r->ordinality)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("WITH ORDINALITY cannot be used with a column definition list"),
+ errhint("Put the column definition list inside TABLE()."),
+ parser_errposition(pstate,
+ exprLocation((Node *) r->coldeflist))));
+
+ coldeflists = list_make1(r->coldeflist);
+ }
/*
* Mark the RTE as LATERAL if the user said LATERAL explicitly, or if
* there are any lateral cross-references in it.
*/
- is_lateral = r->lateral || contain_vars_of_level(funcexpr, 0);
+ is_lateral = r->lateral || contain_vars_of_level((Node *) funcexprs, 0);
/*
* OK, build an RTE for the function.
*/
- rte = addRangeTableEntryForFunction(pstate, funcname, funcexpr,
+ rte = addRangeTableEntryForFunction(pstate,
+ funcnames, funcexprs, coldeflists,
r, is_lateral, true);
- /*
- * If a coldeflist was supplied, ensure it defines a legal set of names
- * (no duplicates) and datatypes (no pseudo-types, for instance).
- * addRangeTableEntryForFunction looked up the type names but didn't check
- * them further than that.
- */
- if (r->coldeflist)
- {
- TupleDesc tupdesc;
-
- tupdesc = BuildDescFromLists(rte->eref->colnames,
- rte->funccoltypes,
- rte->funccoltypmods,
- rte->funccolcollations);
- CheckAttributeNamesTypes(tupdesc, RELKIND_COMPOSITE_TYPE, false);
- }
-
return rte;
}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 0052d21ad6..cd8d75e23d 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -44,6 +44,7 @@ static void expandRelation(Oid relid, Alias *eref,
int location, bool include_dropped,
List **colnames, List **colvars);
static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
+ int count, int offset,
int rtindex, int sublevels_up,
int location, bool include_dropped,
List **colnames, List **colvars);
@@ -807,25 +808,20 @@ markVarForSelectPriv(ParseState *pstate, Var *var, RangeTblEntry *rte)
/*
* buildRelationAliases
* Construct the eref column name list for a relation RTE.
- * This code is also used for the case of a function RTE returning
- * a named composite type or a registered RECORD type.
+ * This code is also used for function RTEs.
*
* tupdesc: the physical column information
* alias: the user-supplied alias, or NULL if none
* eref: the eref Alias to store column names in
- * ordinality: true if an ordinality column is to be added
*
* eref->colnames is filled in. Also, alias->colnames is rebuilt to insert
* empty strings for any dropped columns, so that it will be one-to-one with
* physical column numbers.
*
- * If we add an ordinality column, its colname comes from the alias if there
- * is one, otherwise we default it. (We don't add it to alias->colnames.)
- *
* It is an error for there to be more aliases present than required.
*/
static void
-buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref, bool ordinality)
+buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
{
int maxattrs = tupdesc->natts;
ListCell *aliaslc;
@@ -877,98 +873,56 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref, bool ordinali
eref->colnames = lappend(eref->colnames, attrname);
}
- /* tack on the ordinality column at the end */
- if (ordinality)
- {
- Value *attrname;
-
- if (aliaslc)
- {
- attrname = (Value *) lfirst(aliaslc);
- aliaslc = lnext(aliaslc);
- alias->colnames = lappend(alias->colnames, attrname);
- }
- else
- {
- attrname = makeString(pstrdup("ordinality"));
- }
-
- eref->colnames = lappend(eref->colnames, attrname);
- }
-
/* Too many user-supplied aliases? */
if (aliaslc)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("table \"%s\" has %d columns available but %d columns specified",
- eref->aliasname,
- maxattrs - numdropped + (ordinality ? 1 : 0),
- numaliases)));
+ eref->aliasname, maxattrs - numdropped, numaliases)));
}
/*
- * buildScalarFunctionAlias
- * Construct the eref column name list for a function RTE,
+ * chooseScalarFunctionAlias
+ * Select the column alias for a function in a function RTE,
* when the function returns a scalar type (not composite or RECORD).
*
* funcexpr: transformed expression tree for the function call
- * funcname: function name (used only for error message)
- * alias: the user-supplied alias, or NULL if none
- * eref: the eref Alias to store column names in
- * ordinality: whether to add an ordinality column
+ * funcname: function name (as determined by FigureColname)
+ * alias: the user-supplied alias for the RTE, or NULL if none
+ * nfuncs: the number of functions appearing in the function RTE
*
- * eref->colnames is filled in.
- *
- * The caller must have previously filled in eref->aliasname, which will
- * be used as the result column name if no alias is given.
- *
- * A user-supplied Alias can contain up to two column alias names; one for
- * the function result, and one for the ordinality column; it is an error
- * to specify more aliases than required.
+ * Note that the name we choose might be overridden later, if the user-given
+ * alias includes column alias names. That's of no concern here.
*/
-static void
-buildScalarFunctionAlias(Node *funcexpr, char *funcname,
- Alias *alias, Alias *eref, bool ordinality)
+static char *
+chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
+ Alias *alias, int nfuncs)
{
- Assert(eref->colnames == NIL);
+ char *pname;
- /* Use user-specified column alias if there is one. */
- if (alias && alias->colnames != NIL)
+ /*
+ * If the expression is a simple function call, and the function has a
+ * single OUT parameter that is named, use the parameter's name.
+ */
+ if (funcexpr && IsA(funcexpr, FuncExpr))
{
- if (list_length(alias->colnames) > (ordinality ? 2 : 1))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
- errmsg("too many column aliases specified for function %s",
- funcname)));
-
- eref->colnames = copyObject(alias->colnames);
- }
- else
- {
- char *pname = NULL;
-
- /*
- * If the expression is a simple function call, and the function has a
- * single OUT parameter that is named, use the parameter's name.
- */
- if (funcexpr && IsA(funcexpr, FuncExpr))
- pname = get_func_result_name(((FuncExpr *) funcexpr)->funcid);
-
- /*
- * Otherwise, use the previously-determined alias name provided by the
- * caller (which is not necessarily the function name!)
- */
- if (!pname)
- pname = eref->aliasname;
-
- eref->colnames = list_make1(makeString(pname));
+ pname = get_func_result_name(((FuncExpr *) funcexpr)->funcid);
+ if (pname)
+ return pname;
}
- /* If we don't have a name for the ordinality column yet, supply a default. */
- if (ordinality && list_length(eref->colnames) < 2)
- eref->colnames = lappend(eref->colnames, makeString(pstrdup("ordinality")));
+ /*
+ * If there's just one function in the RTE, and the user gave an RTE alias
+ * name, use that name. (This makes FROM func() AS foo use "foo" as the
+ * column name as well as the table alias.)
+ */
+ if (nfuncs == 1 && alias)
+ return alias->aliasname;
- return;
+ /*
+ * Otherwise use the function name.
+ */
+ return funcname;
}
/*
@@ -1064,7 +1018,7 @@ addRangeTableEntry(ParseState *pstate,
* and/or actual column names.
*/
rte->eref = makeAlias(refname, NIL);
- buildRelationAliases(rel->rd_att, alias, rte->eref, false);
+ buildRelationAliases(rel->rd_att, alias, rte->eref);
/*
* Drop the rel refcount, but keep the access lock till end of transaction
@@ -1124,7 +1078,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
* and/or actual column names.
*/
rte->eref = makeAlias(refname, NIL);
- buildRelationAliases(rel->rd_att, alias, rte->eref, false);
+ buildRelationAliases(rel->rd_att, alias, rte->eref);
/*
* Set flags and access permissions.
@@ -1230,122 +1184,233 @@ addRangeTableEntryForSubquery(ParseState *pstate,
}
/*
- * Add an entry for a function to the pstate's range table (p_rtable).
+ * Add an entry for a function (or functions) to the pstate's range table
+ * (p_rtable).
*
* This is just like addRangeTableEntry() except that it makes a function RTE.
*/
RangeTblEntry *
addRangeTableEntryForFunction(ParseState *pstate,
- char *funcname,
- Node *funcexpr,
+ List *funcnames,
+ List *funcexprs,
+ List *coldeflists,
RangeFunction *rangefunc,
bool lateral,
bool inFromCl)
{
RangeTblEntry *rte = makeNode(RangeTblEntry);
- TypeFuncClass functypclass;
- Oid funcrettype;
- TupleDesc tupdesc;
Alias *alias = rangefunc->alias;
- List *coldeflist = rangefunc->coldeflist;
Alias *eref;
+ char *aliasname;
+ int nfuncs = list_length(funcexprs);
+ TupleDesc *functupdescs;
+ TupleDesc tupdesc;
+ ListCell *lc1,
+ *lc2,
+ *lc3;
+ int i;
+ int j;
+ int funcno;
+ int natts,
+ totalatts;
rte->rtekind = RTE_FUNCTION;
rte->relid = InvalidOid;
rte->subquery = NULL;
- rte->funcexpr = funcexpr;
- rte->funccoltypes = NIL;
- rte->funccoltypmods = NIL;
- rte->funccolcollations = NIL;
+ rte->functions = NIL; /* we'll fill this list below */
+ rte->funcordinality = rangefunc->ordinality;
rte->alias = alias;
- eref = makeAlias(alias ? alias->aliasname : funcname, NIL);
+ /*
+ * Choose the RTE alias name. We default to using the first function's
+ * name even when there's more than one; which is maybe arguable but beats
+ * using something constant like "table".
+ */
+ if (alias)
+ aliasname = alias->aliasname;
+ else
+ aliasname = linitial(funcnames);
+
+ eref = makeAlias(aliasname, NIL);
rte->eref = eref;
- /*
- * Now determine if the function returns a simple or composite type.
- */
- functypclass = get_expr_result_type(funcexpr,
- &funcrettype,
- &tupdesc);
+ /* Process each function ... */
+ functupdescs = (TupleDesc *) palloc(nfuncs * sizeof(TupleDesc));
- /*
- * A coldeflist is required if the function returns RECORD and hasn't got
- * a predetermined record type, and is prohibited otherwise.
- */
- if (coldeflist != NIL)
+ totalatts = 0;
+ funcno = 0;
+ forthree(lc1, funcexprs, lc2, funcnames, lc3, coldeflists)
{
- if (functypclass != TYPEFUNC_RECORD)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("a column definition list is only allowed for functions returning \"record\""),
- parser_errposition(pstate, exprLocation(funcexpr))));
- }
- else
- {
- if (functypclass == TYPEFUNC_RECORD)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("a column definition list is required for functions returning \"record\""),
- parser_errposition(pstate, exprLocation(funcexpr))));
- }
+ Node *funcexpr = (Node *) lfirst(lc1);
+ char *funcname = (char *) lfirst(lc2);
+ List *coldeflist = (List *) lfirst(lc3);
+ RangeTblFunction *rtfunc = makeNode(RangeTblFunction);
+ TypeFuncClass functypclass;
+ Oid funcrettype;
- if (functypclass == TYPEFUNC_COMPOSITE)
- {
- /* Composite data type, e.g. a table's row type */
- Assert(tupdesc);
- /* Build the column alias list */
- buildRelationAliases(tupdesc, alias, eref, rangefunc->ordinality);
- }
- else if (functypclass == TYPEFUNC_SCALAR)
- {
- /* Base data type, i.e. scalar */
- buildScalarFunctionAlias(funcexpr, funcname, alias, eref, rangefunc->ordinality);
- }
- else if (functypclass == TYPEFUNC_RECORD)
- {
- ListCell *col;
-
- if (rangefunc->ordinality)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("WITH ORDINALITY is not supported for functions returning \"record\""),
- parser_errposition(pstate, exprLocation(funcexpr))));
+ /* Initialize RangeTblFunction node */
+ rtfunc->funcexpr = funcexpr;
+ rtfunc->funccolnames = NIL;
+ rtfunc->funccoltypes = NIL;
+ rtfunc->funccoltypmods = NIL;
+ rtfunc->funccolcollations = NIL;
+ rtfunc->funcparams = NULL; /* not set until planning */
/*
- * Use the column definition list to form the alias list and
- * funccoltypes/funccoltypmods/funccolcollations lists.
+ * Now determine if the function returns a simple or composite type.
*/
- foreach(col, coldeflist)
- {
- ColumnDef *n = (ColumnDef *) lfirst(col);
- char *attrname;
- Oid attrtype;
- int32 attrtypmod;
- Oid attrcollation;
+ functypclass = get_expr_result_type(funcexpr,
+ &funcrettype,
+ &tupdesc);
- attrname = pstrdup(n->colname);
- if (n->typeName->setof)
+ /*
+ * A coldeflist is required if the function returns RECORD and hasn't
+ * got a predetermined record type, and is prohibited otherwise.
+ */
+ if (coldeflist != NIL)
+ {
+ if (functypclass != TYPEFUNC_RECORD)
ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("column \"%s\" cannot be declared SETOF",
- attrname),
- parser_errposition(pstate, n->typeName->location)));
- typenameTypeIdAndMod(pstate, n->typeName, &attrtype, &attrtypmod);
- attrcollation = GetColumnDefCollation(pstate, n, attrtype);
- eref->colnames = lappend(eref->colnames, makeString(attrname));
- rte->funccoltypes = lappend_oid(rte->funccoltypes, attrtype);
- rte->funccoltypmods = lappend_int(rte->funccoltypmods, attrtypmod);
- rte->funccolcollations = lappend_oid(rte->funccolcollations,
- attrcollation);
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("a column definition list is only allowed for functions returning \"record\""),
+ parser_errposition(pstate,
+ exprLocation((Node *) coldeflist))));
}
- }
- else
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
+ else
+ {
+ if (functypclass == TYPEFUNC_RECORD)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("a column definition list is required for functions returning \"record\""),
+ parser_errposition(pstate, exprLocation(funcexpr))));
+ }
+
+ if (functypclass == TYPEFUNC_COMPOSITE)
+ {
+ /* Composite data type, e.g. a table's row type */
+ Assert(tupdesc);
+ }
+ else if (functypclass == TYPEFUNC_SCALAR)
+ {
+ /* Base data type, i.e. scalar */
+ tupdesc = CreateTemplateTupleDesc(1, false);
+ TupleDescInitEntry(tupdesc,
+ (AttrNumber) 1,
+ chooseScalarFunctionAlias(funcexpr, funcname,
+ alias, nfuncs),
+ funcrettype,
+ -1,
+ 0);
+ }
+ else if (functypclass == TYPEFUNC_RECORD)
+ {
+ ListCell *col;
+
+ /*
+ * Use the column definition list to construct a tupdesc and fill
+ * in the RangeTblFunction's lists.
+ */
+ tupdesc = CreateTemplateTupleDesc(list_length(coldeflist), false);
+ i = 1;
+ foreach(col, coldeflist)
+ {
+ ColumnDef *n = (ColumnDef *) lfirst(col);
+ char *attrname;
+ Oid attrtype;
+ int32 attrtypmod;
+ Oid attrcollation;
+
+ attrname = n->colname;
+ if (n->typeName->setof)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" cannot be declared SETOF",
+ attrname),
+ parser_errposition(pstate, n->location)));
+ typenameTypeIdAndMod(pstate, n->typeName,
+ &attrtype, &attrtypmod);
+ attrcollation = GetColumnDefCollation(pstate, n, attrtype);
+ TupleDescInitEntry(tupdesc,
+ (AttrNumber) i,
+ attrname,
+ attrtype,
+ attrtypmod,
+ 0);
+ TupleDescInitEntryCollation(tupdesc,
+ (AttrNumber) i,
+ attrcollation);
+ rtfunc->funccolnames = lappend(rtfunc->funccolnames,
+ makeString(pstrdup(attrname)));
+ rtfunc->funccoltypes = lappend_oid(rtfunc->funccoltypes,
+ attrtype);
+ rtfunc->funccoltypmods = lappend_int(rtfunc->funccoltypmods,
+ attrtypmod);
+ rtfunc->funccolcollations = lappend_oid(rtfunc->funccolcollations,
+ attrcollation);
+
+ i++;
+ }
+
+ /*
+ * Ensure that the coldeflist defines a legal set of names (no
+ * duplicates) and datatypes (no pseudo-types, for instance).
+ */
+ CheckAttributeNamesTypes(tupdesc, RELKIND_COMPOSITE_TYPE, false);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("function \"%s\" in FROM has unsupported return type %s",
funcname, format_type_be(funcrettype)),
- parser_errposition(pstate, exprLocation(funcexpr))));
+ parser_errposition(pstate, exprLocation(funcexpr))));
+
+ /* Finish off the RangeTblFunction and add it to the RTE's list */
+ rtfunc->funccolcount = tupdesc->natts;
+ rte->functions = lappend(rte->functions, rtfunc);
+
+ /* Save the tupdesc for use below */
+ functupdescs[funcno] = tupdesc;
+ totalatts += tupdesc->natts;
+ funcno++;
+ }
+
+ /*
+ * If there's more than one function, or we want an ordinality column, we
+ * have to produce a merged tupdesc.
+ */
+ if (nfuncs > 1 || rangefunc->ordinality)
+ {
+ if (rangefunc->ordinality)
+ totalatts++;
+
+ /* Merge the tuple descs of each function into a composite one */
+ tupdesc = CreateTemplateTupleDesc(totalatts, false);
+ natts = 0;
+ for (i = 0; i < nfuncs; i++)
+ {
+ for (j = 1; j <= functupdescs[i]->natts; j++)
+ TupleDescCopyEntry(tupdesc, ++natts, functupdescs[i], j);
+ }
+
+ /* Add the ordinality column if needed */
+ if (rangefunc->ordinality)
+ TupleDescInitEntry(tupdesc,
+ (AttrNumber) ++natts,
+ "ordinality",
+ INT8OID,
+ -1,
+ 0);
+
+ Assert(natts == totalatts);
+ }
+ else
+ {
+ /* We can just use the single function's tupdesc as-is */
+ tupdesc = functupdescs[0];
+ }
+
+ /* Use the tupdesc while assigning column aliases for the RTE */
+ buildRelationAliases(tupdesc, alias, eref);
/*
* Set flags and access permissions.
@@ -1354,7 +1419,6 @@ addRangeTableEntryForFunction(ParseState *pstate,
* permissions mechanism).
*/
rte->lateral = lateral;
- rte->funcordinality = rangefunc->ordinality;
rte->inh = false; /* never true for functions */
rte->inFromCl = inFromCl;
@@ -1710,11 +1774,6 @@ addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
* The output lists go into *colnames and *colvars.
* If only one of the two kinds of output list is needed, pass NULL for the
* output pointer for the unwanted one.
- *
- * For function RTEs with ORDINALITY, this expansion includes the
- * ordinal column, whose type (bigint) had better match the type assumed in the
- * executor. The colname for the ordinality column must have been set up already
- * in the RTE; it is always last.
*/
void
expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
@@ -1780,107 +1839,115 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
case RTE_FUNCTION:
{
/* Function RTE */
- TypeFuncClass functypclass;
- Oid funcrettype;
- TupleDesc tupdesc;
- int ordinality_attno = 0;
+ int atts_done = 0;
+ ListCell *lc;
- functypclass = get_expr_result_type(rte->funcexpr,
- &funcrettype,
- &tupdesc);
- if (functypclass == TYPEFUNC_COMPOSITE)
+ foreach(lc, rte->functions)
{
- /* Composite data type, e.g. a table's row type */
- Assert(tupdesc);
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
+ TypeFuncClass functypclass;
+ Oid funcrettype;
+ TupleDesc tupdesc;
- /*
- * we rely here on the fact that expandTupleDesc doesn't
- * care about being passed more aliases than it needs.
- */
- expandTupleDesc(tupdesc, rte->eref,
- rtindex, sublevels_up, location,
- include_dropped, colnames, colvars);
-
- ordinality_attno = tupdesc->natts + 1;
- }
- else if (functypclass == TYPEFUNC_SCALAR)
- {
- /* Base data type, i.e. scalar */
- if (colnames)
- *colnames = lappend(*colnames,
- linitial(rte->eref->colnames));
-
- if (colvars)
+ functypclass = get_expr_result_type(rtfunc->funcexpr,
+ &funcrettype,
+ &tupdesc);
+ if (functypclass == TYPEFUNC_COMPOSITE)
{
- Var *varnode;
-
- varnode = makeVar(rtindex, 1,
- funcrettype, -1,
- exprCollation(rte->funcexpr),
- sublevels_up);
- varnode->location = location;
-
- *colvars = lappend(*colvars, varnode);
+ /* Composite data type, e.g. a table's row type */
+ Assert(tupdesc);
+ expandTupleDesc(tupdesc, rte->eref,
+ rtfunc->funccolcount, atts_done,
+ rtindex, sublevels_up, location,
+ include_dropped, colnames, colvars);
}
-
- ordinality_attno = 2;
- }
- else if (functypclass == TYPEFUNC_RECORD)
- {
- if (colnames)
- *colnames = copyObject(rte->eref->colnames);
- if (colvars)
+ else if (functypclass == TYPEFUNC_SCALAR)
{
- ListCell *l1;
- ListCell *l2;
- ListCell *l3;
- int attnum = 0;
+ /* Base data type, i.e. scalar */
+ if (colnames)
+ *colnames = lappend(*colnames,
+ list_nth(rte->eref->colnames,
+ atts_done));
- forthree(l1, rte->funccoltypes,
- l2, rte->funccoltypmods,
- l3, rte->funccolcollations)
+ if (colvars)
{
- Oid attrtype = lfirst_oid(l1);
- int32 attrtypmod = lfirst_int(l2);
- Oid attrcollation = lfirst_oid(l3);
Var *varnode;
- attnum++;
- varnode = makeVar(rtindex,
- attnum,
- attrtype,
- attrtypmod,
- attrcollation,
+ varnode = makeVar(rtindex, atts_done + 1,
+ funcrettype, -1,
+ exprCollation(rtfunc->funcexpr),
sublevels_up);
varnode->location = location;
+
*colvars = lappend(*colvars, varnode);
}
}
+ else if (functypclass == TYPEFUNC_RECORD)
+ {
+ if (colnames)
+ {
+ List *namelist;
- /* note, ordinality is not allowed in this case */
- }
- else
- {
- /* addRangeTableEntryForFunction should've caught this */
- elog(ERROR, "function in FROM has unsupported return type");
+ /* extract appropriate subset of column list */
+ namelist = list_copy_tail(rte->eref->colnames,
+ atts_done);
+ namelist = list_truncate(namelist,
+ rtfunc->funccolcount);
+ *colnames = list_concat(*colnames, namelist);
+ }
+
+ if (colvars)
+ {
+ ListCell *l1;
+ ListCell *l2;
+ ListCell *l3;
+ int attnum = atts_done;
+
+ forthree(l1, rtfunc->funccoltypes,
+ l2, rtfunc->funccoltypmods,
+ l3, rtfunc->funccolcollations)
+ {
+ Oid attrtype = lfirst_oid(l1);
+ int32 attrtypmod = lfirst_int(l2);
+ Oid attrcollation = lfirst_oid(l3);
+ Var *varnode;
+
+ attnum++;
+ varnode = makeVar(rtindex,
+ attnum,
+ attrtype,
+ attrtypmod,
+ attrcollation,
+ sublevels_up);
+ varnode->location = location;
+ *colvars = lappend(*colvars, varnode);
+ }
+ }
+ }
+ else
+ {
+ /* addRangeTableEntryForFunction should've caught this */
+ elog(ERROR, "function in FROM has unsupported return type");
+ }
+ atts_done += rtfunc->funccolcount;
}
- /* tack on the extra ordinality column if present */
+ /* Append the ordinality column if any */
if (rte->funcordinality)
{
- Assert(ordinality_attno > 0);
-
if (colnames)
- *colnames = lappend(*colnames, llast(rte->eref->colnames));
+ *colnames = lappend(*colnames,
+ llast(rte->eref->colnames));
if (colvars)
{
- Var *varnode = makeVar(rtindex,
- ordinality_attno,
- INT8OID,
- -1,
- InvalidOid,
- sublevels_up);
+ Var *varnode = makeVar(rtindex,
+ atts_done + 1,
+ INT8OID,
+ -1,
+ InvalidOid,
+ sublevels_up);
+
*colvars = lappend(*colvars, varnode);
}
}
@@ -2051,7 +2118,8 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
/* Get the tupledesc and turn it over to expandTupleDesc */
rel = relation_open(relid, AccessShareLock);
- expandTupleDesc(rel->rd_att, eref, rtindex, sublevels_up,
+ expandTupleDesc(rel->rd_att, eref, rel->rd_att->natts, 0,
+ rtindex, sublevels_up,
location, include_dropped,
colnames, colvars);
relation_close(rel, AccessShareLock);
@@ -2060,20 +2128,34 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
/*
* expandTupleDesc -- expandRTE subroutine
*
- * Only the required number of column names are used from the Alias;
- * it is not an error to supply too many. (ordinality depends on this)
+ * Generate names and/or Vars for the first "count" attributes of the tupdesc,
+ * and append them to colnames/colvars. "offset" is added to the varattno
+ * that each Var would otherwise have, and we also skip the first "offset"
+ * entries in eref->colnames. (These provisions allow use of this code for
+ * an individual composite-returning function in an RTE_FUNCTION RTE.)
*/
static void
-expandTupleDesc(TupleDesc tupdesc, Alias *eref,
+expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
int rtindex, int sublevels_up,
int location, bool include_dropped,
List **colnames, List **colvars)
{
- int maxattrs = tupdesc->natts;
- int numaliases = list_length(eref->colnames);
+ ListCell *aliascell = list_head(eref->colnames);
int varattno;
- for (varattno = 0; varattno < maxattrs; varattno++)
+ if (colnames)
+ {
+ int i;
+
+ for (i = 0; i < offset; i++)
+ {
+ if (aliascell)
+ aliascell = lnext(aliascell);
+ }
+ }
+
+ Assert(count <= tupdesc->natts);
+ for (varattno = 0; varattno < count; varattno++)
{
Form_pg_attribute attr = tupdesc->attrs[varattno];
@@ -2093,6 +2175,8 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref,
makeNullConst(INT4OID, -1, InvalidOid));
}
}
+ if (aliascell)
+ aliascell = lnext(aliascell);
continue;
}
@@ -2100,10 +2184,16 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref,
{
char *label;
- if (varattno < numaliases)
- label = strVal(list_nth(eref->colnames, varattno));
+ if (aliascell)
+ {
+ label = strVal(lfirst(aliascell));
+ aliascell = lnext(aliascell);
+ }
else
+ {
+ /* If we run out of aliases, use the underlying name */
label = NameStr(attr->attname);
+ }
*colnames = lappend(*colnames, makeString(pstrdup(label)));
}
@@ -2111,7 +2201,7 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref,
{
Var *varnode;
- varnode = makeVar(rtindex, attr->attnum,
+ varnode = makeVar(rtindex, varattno + offset + 1,
attr->atttypid, attr->atttypmod,
attr->attcollation,
sublevels_up);
@@ -2221,9 +2311,6 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
/*
* get_rte_attribute_type
* Get attribute type/typmod/collation information from a RangeTblEntry
- *
- * Once again, for function RTEs we may have to synthesize the
- * ordinality column with the correct type.
*/
void
get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
@@ -2278,79 +2365,93 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
case RTE_FUNCTION:
{
/* Function RTE */
- TypeFuncClass functypclass;
- Oid funcrettype;
- TupleDesc tupdesc;
+ ListCell *lc;
+ int atts_done = 0;
- /*
- * if ordinality, then a reference to the last column
- * in the name list must be referring to the
- * ordinality column
- */
- if (rte->funcordinality
- && attnum == list_length(rte->eref->colnames))
+ /* Identify which function covers the requested column */
+ foreach(lc, rte->functions)
+ {
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
+
+ if (attnum > atts_done &&
+ attnum <= atts_done + rtfunc->funccolcount)
+ {
+ TypeFuncClass functypclass;
+ Oid funcrettype;
+ TupleDesc tupdesc;
+
+ attnum -= atts_done; /* now relative to this func */
+ functypclass = get_expr_result_type(rtfunc->funcexpr,
+ &funcrettype,
+ &tupdesc);
+
+ if (functypclass == TYPEFUNC_COMPOSITE)
+ {
+ /* Composite data type, e.g. a table's row type */
+ Form_pg_attribute att_tup;
+
+ Assert(tupdesc);
+ Assert(attnum <= tupdesc->natts);
+ att_tup = tupdesc->attrs[attnum - 1];
+
+ /*
+ * If dropped column, pretend it ain't there. See
+ * notes in scanRTEForColumn.
+ */
+ if (att_tup->attisdropped)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ NameStr(att_tup->attname),
+ rte->eref->aliasname)));
+ *vartype = att_tup->atttypid;
+ *vartypmod = att_tup->atttypmod;
+ *varcollid = att_tup->attcollation;
+ }
+ else if (functypclass == TYPEFUNC_SCALAR)
+ {
+ /* Base data type, i.e. scalar */
+ *vartype = funcrettype;
+ *vartypmod = -1;
+ *varcollid = exprCollation(rtfunc->funcexpr);
+ }
+ else if (functypclass == TYPEFUNC_RECORD)
+ {
+ *vartype = list_nth_oid(rtfunc->funccoltypes,
+ attnum - 1);
+ *vartypmod = list_nth_int(rtfunc->funccoltypmods,
+ attnum - 1);
+ *varcollid = list_nth_oid(rtfunc->funccolcollations,
+ attnum - 1);
+ }
+ else
+ {
+ /*
+ * addRangeTableEntryForFunction should've caught
+ * this
+ */
+ elog(ERROR, "function in FROM has unsupported return type");
+ }
+ return;
+ }
+ atts_done += rtfunc->funccolcount;
+ }
+
+ /* If we get here, must be looking for the ordinality column */
+ if (rte->funcordinality && attnum == atts_done + 1)
{
*vartype = INT8OID;
*vartypmod = -1;
*varcollid = InvalidOid;
- break;
+ return;
}
- functypclass = get_expr_result_type(rte->funcexpr,
- &funcrettype,
- &tupdesc);
-
- if (functypclass == TYPEFUNC_COMPOSITE)
- {
- /* Composite data type, e.g. a table's row type */
- Form_pg_attribute att_tup;
-
- Assert(tupdesc);
-
- /* this is probably a can't-happen case */
- if (attnum < 1 || attnum > tupdesc->natts)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column %d of relation \"%s\" does not exist",
- attnum,
- rte->eref->aliasname)));
-
- att_tup = tupdesc->attrs[attnum - 1];
-
- /*
- * If dropped column, pretend it ain't there. See notes
- * in scanRTEForColumn.
- */
- if (att_tup->attisdropped)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
- NameStr(att_tup->attname),
- rte->eref->aliasname)));
- *vartype = att_tup->atttypid;
- *vartypmod = att_tup->atttypmod;
- *varcollid = att_tup->attcollation;
- }
- else if (functypclass == TYPEFUNC_SCALAR)
- {
- Assert(attnum == 1);
-
- /* Base data type, i.e. scalar */
- *vartype = funcrettype;
- *vartypmod = -1;
- *varcollid = exprCollation(rte->funcexpr);
- }
- else if (functypclass == TYPEFUNC_RECORD)
- {
- *vartype = list_nth_oid(rte->funccoltypes, attnum - 1);
- *vartypmod = list_nth_int(rte->funccoltypmods, attnum - 1);
- *varcollid = list_nth_oid(rte->funccolcollations, attnum - 1);
- }
- else
- {
- /* addRangeTableEntryForFunction should've caught this */
- elog(ERROR, "function in FROM has unsupported return type");
- }
+ /* this probably can't happen ... */
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column %d of relation \"%s\" does not exist",
+ attnum,
+ rte->eref->aliasname)));
}
break;
case RTE_VALUES:
@@ -2456,46 +2557,57 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
case RTE_FUNCTION:
{
/* Function RTE */
- Oid funcrettype = exprType(rte->funcexpr);
- Oid funcrelid = typeidTypeRelid(funcrettype);
+ ListCell *lc;
+ int atts_done = 0;
/*
- * if ordinality, then a reference to the last column
- * in the name list must be referring to the
- * ordinality column, which is not dropped
+ * Dropped attributes are only possible with functions that
+ * return named composite types. In such a case we have to
+ * look up the result type to see if it currently has this
+ * column dropped. So first, loop over the funcs until we
+ * find the one that covers the requested column.
*/
- if (rte->funcordinality
- && attnum == list_length(rte->eref->colnames))
+ foreach(lc, rte->functions)
{
- result = false;
- }
- else if (OidIsValid(funcrelid))
- {
- /*
- * Composite data type, i.e. a table's row type
- *
- * Same as ordinary relation RTE
- */
- HeapTuple tp;
- Form_pg_attribute att_tup;
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
- tp = SearchSysCache2(ATTNUM,
- ObjectIdGetDatum(funcrelid),
- Int16GetDatum(attnum));
- if (!HeapTupleIsValid(tp)) /* shouldn't happen */
- elog(ERROR, "cache lookup failed for attribute %d of relation %u",
- attnum, funcrelid);
- att_tup = (Form_pg_attribute) GETSTRUCT(tp);
- result = att_tup->attisdropped;
- ReleaseSysCache(tp);
- }
- else
- {
- /*
- * Must be a base data type, i.e. scalar
- */
- result = false;
+ if (attnum > atts_done &&
+ attnum <= atts_done + rtfunc->funccolcount)
+ {
+ TypeFuncClass functypclass;
+ Oid funcrettype;
+ TupleDesc tupdesc;
+
+ functypclass = get_expr_result_type(rtfunc->funcexpr,
+ &funcrettype,
+ &tupdesc);
+ if (functypclass == TYPEFUNC_COMPOSITE)
+ {
+ /* Composite data type, e.g. a table's row type */
+ Form_pg_attribute att_tup;
+
+ Assert(tupdesc);
+ Assert(attnum - atts_done <= tupdesc->natts);
+ att_tup = tupdesc->attrs[attnum - atts_done - 1];
+ return att_tup->attisdropped;
+ }
+ /* Otherwise, it can't have any dropped columns */
+ return false;
+ }
+ atts_done += rtfunc->funccolcount;
}
+
+ /* If we get here, must be looking for the ordinality column */
+ if (rte->funcordinality && attnum == atts_done + 1)
+ return false;
+
+ /* this probably can't happen ... */
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column %d of relation \"%s\" does not exist",
+ attnum,
+ rte->eref->aliasname)));
+ result = false; /* keep compiler quiet */
}
break;
default:
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 07fce8a011..ee6802a655 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -472,7 +472,7 @@ GetColumnDefCollation(ParseState *pstate, ColumnDef *coldef, Oid typeOid)
{
Oid result;
Oid typcollation = get_typcollation(typeOid);
- int location = -1;
+ int location = coldef->location;
if (coldef->collClause)
{
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 19d19e5f39..ae2206a123 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -754,6 +754,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
def->collClause = NULL;
def->collOid = attribute->attcollation;
def->constraints = NIL;
+ def->location = -1;
/*
* Add to column list
@@ -969,6 +970,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
n->collClause = NULL;
n->collOid = attr->attcollation;
n->constraints = NIL;
+ n->location = -1;
cxt->columns = lappend(cxt->columns, n);
}
DecrTupleDescRefCount(tupdesc);
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index c52a3743de..50cb75392b 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -390,7 +390,7 @@ rewriteRuleAction(Query *parsetree,
{
case RTE_FUNCTION:
sub_action->hasSubLinks =
- checkExprHasSubLink(rte->funcexpr);
+ checkExprHasSubLink((Node *) rte->functions);
break;
case RTE_VALUES:
sub_action->hasSubLinks =
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 5ffce68c6f..74b573bd5e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -387,8 +387,8 @@ static void get_from_clause_item(Node *jtnode, Query *query,
deparse_context *context);
static void get_column_alias_list(deparse_columns *colinfo,
deparse_context *context);
-static void get_from_clause_coldeflist(deparse_columns *colinfo,
- List *types, List *typmods, List *collations,
+static void get_from_clause_coldeflist(RangeTblFunction *rtfunc,
+ deparse_columns *colinfo,
deparse_context *context);
static void get_opclass_name(Oid opclass, Oid actual_datatype,
StringInfo buf);
@@ -8012,6 +8012,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
RangeTblEntry *rte = rt_fetch(varno, query->rtable);
char *refname = get_rtable_name(varno, context);
deparse_columns *colinfo = deparse_columns_fetch(varno, dpns);
+ RangeTblFunction *rtfunc1 = NULL;
bool printalias;
if (rte->lateral)
@@ -8037,7 +8038,96 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
break;
case RTE_FUNCTION:
/* Function RTE */
- get_rule_expr(rte->funcexpr, context, true);
+ rtfunc1 = (RangeTblFunction *) linitial(rte->functions);
+
+ /*
+ * Omit TABLE() syntax if there's just one function, unless it
+ * has both a coldeflist and WITH ORDINALITY. If it has both,
+ * we must use TABLE() syntax to avoid ambiguity about whether
+ * the coldeflist includes the ordinality column.
+ */
+ if (list_length(rte->functions) == 1 &&
+ (rtfunc1->funccolnames == NIL || !rte->funcordinality))
+ {
+ get_rule_expr(rtfunc1->funcexpr, context, true);
+ /* we'll print the coldeflist below, if it has one */
+ }
+ else
+ {
+ bool all_unnest;
+ ListCell *lc;
+
+ /*
+ * If all the function calls in the list are to unnest,
+ * and none need a coldeflist, then collapse the list back
+ * down to UNNEST(args). (If we had more than one
+ * built-in unnest function, this would get more
+ * difficult.)
+ *
+ * XXX This is pretty ugly, since it makes not-terribly-
+ * future-proof assumptions about what the parser would do
+ * with the output; but the alternative is to emit our
+ * nonstandard extended TABLE() notation for what might
+ * have been a perfectly spec-compliant multi-argument
+ * UNNEST().
+ */
+ all_unnest = true;
+ foreach(lc, rte->functions)
+ {
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
+
+ if (!IsA(rtfunc->funcexpr, FuncExpr) ||
+ ((FuncExpr *) rtfunc->funcexpr)->funcid != F_ARRAY_UNNEST ||
+ rtfunc->funccolnames != NIL)
+ {
+ all_unnest = false;
+ break;
+ }
+ }
+
+ if (all_unnest)
+ {
+ List *allargs = NIL;
+
+ foreach(lc, rte->functions)
+ {
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
+ List *args = ((FuncExpr *) rtfunc->funcexpr)->args;
+
+ allargs = list_concat(allargs, list_copy(args));
+ }
+
+ appendStringInfoString(buf, "UNNEST(");
+ get_rule_expr((Node *) allargs, context, true);
+ appendStringInfoChar(buf, ')');
+ }
+ else
+ {
+ int funcno = 0;
+
+ appendStringInfoString(buf, "TABLE(");
+ foreach(lc, rte->functions)
+ {
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
+
+ if (funcno > 0)
+ appendStringInfoString(buf, ", ");
+ get_rule_expr(rtfunc->funcexpr, context, true);
+ if (rtfunc->funccolnames != NIL)
+ {
+ /* Reconstruct the column definition list */
+ appendStringInfoString(buf, " AS ");
+ get_from_clause_coldeflist(rtfunc,
+ NULL,
+ context);
+ }
+ funcno++;
+ }
+ appendStringInfoChar(buf, ')');
+ }
+ /* prevent printing duplicate coldeflist below */
+ rtfunc1 = NULL;
+ }
if (rte->funcordinality)
appendStringInfoString(buf, " WITH ORDINALITY");
break;
@@ -8081,7 +8171,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
* For a function RTE, always print alias. This covers possible
* renaming of the function and/or instability of the
* FigureColname rules for things that aren't simple functions.
- * Also note we'd need to force it anyway for the RECORD case.
+ * Note we'd need to force it anyway for the columndef list case.
*/
printalias = true;
}
@@ -8099,14 +8189,10 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
appendStringInfo(buf, " %s", quote_identifier(refname));
/* Print the column definitions or aliases, if needed */
- if (rte->rtekind == RTE_FUNCTION && rte->funccoltypes != NIL)
+ if (rtfunc1 && rtfunc1->funccolnames != NIL)
{
- /* Function returning RECORD, reconstruct the columndefs */
- get_from_clause_coldeflist(colinfo,
- rte->funccoltypes,
- rte->funccoltypmods,
- rte->funccolcollations,
- context);
+ /* Reconstruct the columndef list, which is also the aliases */
+ get_from_clause_coldeflist(rtfunc1, colinfo, context);
}
else
{
@@ -8250,29 +8336,45 @@ get_column_alias_list(deparse_columns *colinfo, deparse_context *context)
/*
* get_from_clause_coldeflist - reproduce FROM clause coldeflist
*
+ * When printing a top-level coldeflist (which is syntactically also the
+ * relation's column alias list), use column names from colinfo. But when
+ * printing a coldeflist embedded inside TABLE(), we prefer to use the
+ * original coldeflist's names, which are available in rtfunc->funccolnames.
+ * Pass NULL for colinfo to select the latter behavior.
+ *
* The coldeflist is appended immediately (no space) to buf. Caller is
* responsible for ensuring that an alias or AS is present before it.
*/
static void
-get_from_clause_coldeflist(deparse_columns *colinfo,
- List *types, List *typmods, List *collations,
+get_from_clause_coldeflist(RangeTblFunction *rtfunc,
+ deparse_columns *colinfo,
deparse_context *context)
{
StringInfo buf = context->buf;
ListCell *l1;
ListCell *l2;
ListCell *l3;
+ ListCell *l4;
int i;
appendStringInfoChar(buf, '(');
+ /* there's no forfour(), so must chase one list the hard way */
i = 0;
- forthree(l1, types, l2, typmods, l3, collations)
+ l4 = list_head(rtfunc->funccolnames);
+ forthree(l1, rtfunc->funccoltypes,
+ l2, rtfunc->funccoltypmods,
+ l3, rtfunc->funccolcollations)
{
- char *attname = colinfo->colnames[i];
Oid atttypid = lfirst_oid(l1);
int32 atttypmod = lfirst_int(l2);
Oid attcollation = lfirst_oid(l3);
+ char *attname;
+
+ if (colinfo)
+ attname = colinfo->colnames[i];
+ else
+ attname = strVal(lfirst(l4));
Assert(attname); /* shouldn't be any dropped columns here */
@@ -8285,6 +8387,8 @@ get_from_clause_coldeflist(deparse_columns *colinfo,
attcollation != get_typcollation(atttypid))
appendStringInfo(buf, " COLLATE %s",
generate_collation_name(attcollation));
+
+ l4 = lnext(l4);
i++;
}
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index 49226b70e6..4aee94b0e6 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -87,10 +87,12 @@ extern TupleDesc CreateTupleDesc(int natts, bool hasoid,
Form_pg_attribute *attrs);
extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc);
-extern TupleDesc CreateTupleDescCopyExtend(TupleDesc tupdesc, int moreatts);
extern TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc);
+extern void TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno,
+ TupleDesc src, AttrNumber srcAttno);
+
extern void FreeTupleDesc(TupleDesc tupdesc);
extern void IncrTupleDescRefCount(TupleDesc tupdesc);
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 2d80cc30b7..c783b26281 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201311171
+#define CATALOG_VERSION_NO 201311211
#endif
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 0350ef6687..78efaa5f23 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -175,6 +175,7 @@ DATA(insert OID = 411 ( "<>" PGNSP PGUID b f f 20 20 16 411 410 int8ne neqsel
DESCR("not equal");
DATA(insert OID = 412 ( "<" PGNSP PGUID b f f 20 20 16 413 415 int8lt scalarltsel scalarltjoinsel ));
DESCR("less than");
+#define Int8LessOperator 412
DATA(insert OID = 413 ( ">" PGNSP PGUID b f f 20 20 16 412 414 int8gt scalargtsel scalargtjoinsel ));
DESCR("greater than");
DATA(insert OID = 414 ( "<=" PGNSP PGUID b f f 20 20 16 415 413 int8le scalarltsel scalarltjoinsel ));
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index bedcf040a7..5a4034729c 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1395,24 +1395,26 @@ typedef struct SubqueryScanState
* function appearing in FROM (typically a function returning set).
*
* eflags node's capability flags
- * ordinal column value for WITH ORDINALITY
- * scan_tupdesc scan tuple descriptor
- * func_tupdesc function tuple descriptor
- * func_slot function result slot, or null
- * tuplestorestate private state of tuplestore.c
- * funcexpr state for function expression being evaluated
+ * ordinality is this scan WITH ORDINALITY?
+ * simple true if we have 1 function and no ordinality
+ * ordinal current ordinal column value
+ * nfuncs number of functions being executed
+ * funcstates per-function execution states (private in
+ * nodeFunctionscan.c)
* ----------------
*/
+struct FunctionScanPerFuncState;
+
typedef struct FunctionScanState
{
ScanState ss; /* its first field is NodeTag */
int eflags;
- int64 ordinal;
- TupleDesc scan_tupdesc;
- TupleDesc func_tupdesc;
- TupleTableSlot *func_slot;
- Tuplestorestate *tuplestorestate;
- ExprState *funcexpr;
+ bool ordinality;
+ bool simple;
+ int64 ordinal;
+ int nfuncs;
+ struct FunctionScanPerFuncState *funcstates; /* array of length
+ * nfuncs */
} FunctionScanState;
/* ----------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index fc6b1d7dbd..ff9af7691c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -389,6 +389,7 @@ typedef enum NodeTag
T_Constraint,
T_DefElem,
T_RangeTblEntry,
+ T_RangeTblFunction,
T_WithCheckOption,
T_SortGroupClause,
T_WindowClause,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 55524b468a..6a5555f918 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -288,10 +288,8 @@ typedef struct CollateClause
* aggregate or some other kind of function. However, if FILTER or OVER is
* present it had better be an aggregate or window function.
*
- * Normally, you'd initialize this via makeFuncCall() and then only
- * change the parts of the struct its defaults don't match afterwards
- * if needed.
- *
+ * Normally, you'd initialize this via makeFuncCall() and then only change the
+ * parts of the struct its defaults don't match afterwards, as needed.
*/
typedef struct FuncCall
{
@@ -466,13 +464,25 @@ typedef struct RangeSubselect
/*
* RangeFunction - function call appearing in a FROM clause
+ *
+ * functions is a List because we use this to represent the construct
+ * TABLE(func1(...), func2(...), ...). Each element of this list is a
+ * two-element sublist, the first element being the untransformed function
+ * call tree, and the second element being a possibly-empty list of ColumnDef
+ * nodes representing any columndef list attached to that function within the
+ * TABLE() syntax.
+ *
+ * alias and coldeflist represent any alias and/or columndef list attached
+ * at the top level. (We disallow coldeflist appearing both here and
+ * per-function, but that's checked in parse analysis, not by the grammar.)
*/
typedef struct RangeFunction
{
NodeTag type;
bool lateral; /* does it have LATERAL prefix? */
bool ordinality; /* does it have WITH ORDINALITY suffix? */
- Node *funccallnode; /* untransformed function call tree */
+ bool is_table; /* is result of TABLE() syntax? */
+ List *functions; /* per-function information, see above */
Alias *alias; /* table alias & optional column aliases */
List *coldeflist; /* list of ColumnDef nodes to describe result
* of function returning RECORD */
@@ -512,6 +522,7 @@ typedef struct ColumnDef
Oid collOid; /* collation OID (InvalidOid if not set) */
List *constraints; /* other constraints on column */
List *fdwoptions; /* per-column FDW options */
+ int location; /* parse location, or -1 if none/unknown */
} ColumnDef;
/*
@@ -652,13 +663,8 @@ typedef struct XmlSerialize
* dropped columns. Note however that a stored rule may have nonempty
* colnames for columns dropped since the rule was created (and for that
* matter the colnames might be out of date due to column renamings).
- *
- * The same comments apply to FUNCTION RTEs when the function's return type
- * is a named composite type. In addition, for all return types, FUNCTION
- * RTEs with ORDINALITY must always have the last colname entry being the
- * one for the ordinal column; this is enforced when constructing the RTE.
- * Thus when ORDINALITY is used, there will be exactly one more colname
- * than would have been present otherwise.
+ * The same comments apply to FUNCTION RTEs when a function's return type
+ * is a named composite type.
*
* In JOIN RTEs, the colnames in both alias and eref are one-to-one with
* joinaliasvars entries. A JOIN RTE will omit columns of its inputs when
@@ -755,23 +761,15 @@ typedef struct RangeTblEntry
List *joinaliasvars; /* list of alias-var expansions */
/*
- * Fields valid for a function RTE (else NULL):
+ * Fields valid for a function RTE (else NIL/zero):
*
- * If the function returns an otherwise-unspecified RECORD, funccoltypes
- * lists the column types declared in the RTE's column type specification,
- * funccoltypmods lists their declared typmods, funccolcollations their
- * collations. Note that in this case, ORDINALITY is not permitted, so
- * there is no extra ordinal column to be allowed for.
- *
- * Otherwise, those fields are NIL, and the result column types must be
- * derived from the funcexpr while treating the ordinal column, if
- * present, as a special case. (see get_rte_attribute_*)
+ * When funcordinality is true, the eref->colnames list includes an alias
+ * for the ordinality column. The ordinality column is otherwise
+ * implicit, and must be accounted for "by hand" in places such as
+ * expandRTE().
*/
- Node *funcexpr; /* expression tree for func call */
- List *funccoltypes; /* OID list of column type OIDs */
- List *funccoltypmods; /* integer list of column typmods */
- List *funccolcollations; /* OID list of column collation OIDs */
- bool funcordinality; /* is this called WITH ORDINALITY? */
+ List *functions; /* list of RangeTblFunction nodes */
+ bool funcordinality; /* is this called WITH ORDINALITY? */
/*
* Fields valid for a values RTE (else NIL):
@@ -803,6 +801,37 @@ typedef struct RangeTblEntry
Bitmapset *modifiedCols; /* columns needing INSERT/UPDATE permission */
} RangeTblEntry;
+/*
+ * RangeTblFunction -
+ * RangeTblEntry subsidiary data for one function in a FUNCTION RTE.
+ *
+ * If the function had a column definition list (required for an
+ * otherwise-unspecified RECORD result), funccolnames lists the names given
+ * in the definition list, funccoltypes lists their declared column types,
+ * funccoltypmods lists their typmods, funccolcollations their collations.
+ * Otherwise, those fields are NIL.
+ *
+ * Notice we don't attempt to store info about the results of functions
+ * returning named composite types, because those can change from time to
+ * time. We do however remember how many columns we thought the type had
+ * (including dropped columns!), so that we can successfully ignore any
+ * columns added after the query was parsed.
+ */
+typedef struct RangeTblFunction
+{
+ NodeTag type;
+
+ Node *funcexpr; /* expression tree for func call */
+ int funccolcount; /* number of columns it contributes to RTE */
+ /* These fields record the contents of a column definition list, if any: */
+ List *funccolnames; /* column names (list of String) */
+ List *funccoltypes; /* OID list of column type OIDs */
+ List *funccoltypmods; /* integer list of column typmods */
+ List *funccolcollations; /* OID list of column collation OIDs */
+ /* This is set during planning for use by the executor: */
+ Bitmapset *funcparams; /* PARAM_EXEC Param IDs affecting this func */
+} RangeTblFunction;
+
/*
* WithCheckOption -
* representation of WITH CHECK OPTION checks to be applied to new tuples
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 44ea0b7752..101e22cee1 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -424,12 +424,8 @@ typedef struct SubqueryScan
typedef struct FunctionScan
{
Scan scan;
- Node *funcexpr; /* expression tree for func call */
- bool funcordinality; /* WITH ORDINALITY */
- List *funccolnames; /* output column names (string Value nodes) */
- List *funccoltypes; /* OID list of column type OIDs */
- List *funccoltypmods; /* integer list of column typmods */
- List *funccolcollations; /* OID list of column collation OIDs */
+ List *functions; /* list of RangeTblFunction nodes */
+ bool funcordinality; /* WITH ORDINALITY */
} FunctionScan;
/* ----------------
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 9686229134..0033a3c529 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -70,7 +70,7 @@ extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel,
extern Path *create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel,
List *pathkeys, Relids required_outer);
extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
- Relids required_outer);
+ List *pathkeys, Relids required_outer);
extern Path *create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer);
extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 96ffdb195f..999adaaf8c 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -166,6 +166,9 @@ extern Path *get_cheapest_fractional_path_for_pathkeys(List *paths,
double fraction);
extern List *build_index_pathkeys(PlannerInfo *root, IndexOptInfo *index,
ScanDirection scandir);
+extern List *build_expression_pathkey(PlannerInfo *root, Expr *expr,
+ Relids nullable_relids, Oid opno,
+ Relids rel, bool create_it);
extern List *convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
List *subquery_pathkeys);
extern List *build_join_pathkeys(PlannerInfo *root,
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 83c9131e78..f20c113a80 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -58,8 +58,9 @@ extern RangeTblEntry *addRangeTableEntryForSubquery(ParseState *pstate,
bool lateral,
bool inFromCl);
extern RangeTblEntry *addRangeTableEntryForFunction(ParseState *pstate,
- char *funcname,
- Node *funcexpr,
+ List *funcnames,
+ List *funcexprs,
+ List *coldeflists,
RangeFunction *rangefunc,
bool lateral,
bool inFromCl);
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 37391dcfce..418f92c4db 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -67,6 +67,15 @@ select * from unnest(array[1.0::float8]) with ordinality as z(a,ord);
1 | 1
(1 row)
+select row_to_json(s.*) from generate_series(11,14) with ordinality s;
+ row_to_json
+-------------------------
+ {"s":11,"ordinality":1}
+ {"s":12,"ordinality":2}
+ {"s":13,"ordinality":3}
+ {"s":14,"ordinality":4}
+(4 rows)
+
-- ordinality vs. views
create temporary view vw_ord as select * from (values (1)) v(n) join foot(1) with ordinality as z(a,b,ord) on (n=ord);
select * from vw_ord;
@@ -87,59 +96,207 @@ select definition from pg_views where viewname='vw_ord';
(1 row)
drop view vw_ord;
--- ordinality vs. rewind and reverse scan
+-- multiple functions
+select * from table(foot(1),foot(2)) with ordinality as z(a,b,c,d,ord);
+ a | b | c | d | ord
+---+-----+---+----+-----
+ 1 | 11 | 2 | 22 | 1
+ 1 | 111 | | | 2
+(2 rows)
+
+create temporary view vw_ord as select * from (values (1)) v(n) join table(foot(1),foot(2)) with ordinality as z(a,b,c,d,ord) on (n=ord);
+select * from vw_ord;
+ n | a | b | c | d | ord
+---+---+----+---+----+-----
+ 1 | 1 | 11 | 2 | 22 | 1
+(1 row)
+
+select definition from pg_views where viewname='vw_ord';
+ definition
+-----------------------------------------------------------------------------------------
+ SELECT v.n, +
+ z.a, +
+ z.b, +
+ z.c, +
+ z.d, +
+ z.ord +
+ FROM (( VALUES (1)) v(n) +
+ JOIN TABLE(foot(1), foot(2)) WITH ORDINALITY z(a, b, c, d, ord) ON ((v.n = z.ord)));
+(1 row)
+
+drop view vw_ord;
+-- expansions of unnest()
+select * from unnest(array[10,20],array['foo','bar'],array[1.0]);
+ unnest | unnest | unnest
+--------+--------+--------
+ 10 | foo | 1.0
+ 20 | bar |
+(2 rows)
+
+select * from unnest(array[10,20],array['foo','bar'],array[1.0]) with ordinality as z(a,b,c,ord);
+ a | b | c | ord
+----+-----+-----+-----
+ 10 | foo | 1.0 | 1
+ 20 | bar | | 2
+(2 rows)
+
+select * from table(unnest(array[10,20],array['foo','bar'],array[1.0])) with ordinality as z(a,b,c,ord);
+ a | b | c | ord
+----+-----+-----+-----
+ 10 | foo | 1.0 | 1
+ 20 | bar | | 2
+(2 rows)
+
+select * from table(unnest(array[10,20],array['foo','bar']), generate_series(101,102)) with ordinality as z(a,b,c,ord);
+ a | b | c | ord
+----+-----+-----+-----
+ 10 | foo | 101 | 1
+ 20 | bar | 102 | 2
+(2 rows)
+
+create temporary view vw_ord as select * from unnest(array[10,20],array['foo','bar'],array[1.0]) as z(a,b,c);
+select * from vw_ord;
+ a | b | c
+----+-----+-----
+ 10 | foo | 1.0
+ 20 | bar |
+(2 rows)
+
+select definition from pg_views where viewname='vw_ord';
+ definition
+----------------------------------------------------------------------------------------
+ SELECT z.a, +
+ z.b, +
+ z.c +
+ FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
+(1 row)
+
+drop view vw_ord;
+create temporary view vw_ord as select * from table(unnest(array[10,20],array['foo','bar'],array[1.0])) as z(a,b,c);
+select * from vw_ord;
+ a | b | c
+----+-----+-----
+ 10 | foo | 1.0
+ 20 | bar |
+(2 rows)
+
+select definition from pg_views where viewname='vw_ord';
+ definition
+----------------------------------------------------------------------------------------
+ SELECT z.a, +
+ z.b, +
+ z.c +
+ FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
+(1 row)
+
+drop view vw_ord;
+create temporary view vw_ord as select * from table(unnest(array[10,20],array['foo','bar']), generate_series(1,2)) as z(a,b,c);
+select * from vw_ord;
+ a | b | c
+----+-----+---
+ 10 | foo | 1
+ 20 | bar | 2
+(2 rows)
+
+select definition from pg_views where viewname='vw_ord';
+ definition
+------------------------------------------------------------------------------------------------------------------
+ SELECT z.a, +
+ z.b, +
+ z.c +
+ FROM TABLE(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
+(1 row)
+
+drop view vw_ord;
+-- ordinality and multiple functions vs. rewind and reverse scan
begin;
-declare foo scroll cursor for select * from generate_series(1,5) with ordinality as g(i,o);
+declare foo scroll cursor for select * from table(generate_series(1,5),generate_series(1,2)) with ordinality as g(i,j,o);
fetch all from foo;
- i | o
----+---
- 1 | 1
- 2 | 2
- 3 | 3
- 4 | 4
- 5 | 5
+ i | j | o
+---+---+---
+ 1 | 1 | 1
+ 2 | 2 | 2
+ 3 | | 3
+ 4 | | 4
+ 5 | | 5
(5 rows)
fetch backward all from foo;
- i | o
----+---
- 5 | 5
- 4 | 4
- 3 | 3
- 2 | 2
- 1 | 1
+ i | j | o
+---+---+---
+ 5 | | 5
+ 4 | | 4
+ 3 | | 3
+ 2 | 2 | 2
+ 1 | 1 | 1
(5 rows)
fetch all from foo;
- i | o
----+---
- 1 | 1
- 2 | 2
- 3 | 3
- 4 | 4
- 5 | 5
+ i | j | o
+---+---+---
+ 1 | 1 | 1
+ 2 | 2 | 2
+ 3 | | 3
+ 4 | | 4
+ 5 | | 5
(5 rows)
fetch next from foo;
- i | o
----+---
+ i | j | o
+---+---+---
(0 rows)
fetch next from foo;
- i | o
----+---
+ i | j | o
+---+---+---
(0 rows)
fetch prior from foo;
- i | o
----+---
- 5 | 5
+ i | j | o
+---+---+---
+ 5 | | 5
(1 row)
fetch absolute 1 from foo;
- i | o
----+---
- 1 | 1
+ i | j | o
+---+---+---
+ 1 | 1 | 1
+(1 row)
+
+fetch next from foo;
+ i | j | o
+---+---+---
+ 2 | 2 | 2
+(1 row)
+
+fetch next from foo;
+ i | j | o
+---+---+---
+ 3 | | 3
+(1 row)
+
+fetch next from foo;
+ i | j | o
+---+---+---
+ 4 | | 4
+(1 row)
+
+fetch prior from foo;
+ i | j | o
+---+---+---
+ 3 | | 3
+(1 row)
+
+fetch prior from foo;
+ i | j | o
+---+---+---
+ 2 | 2 | 2
+(1 row)
+
+fetch prior from foo;
+ i | j | o
+---+---+---
+ 1 | 1 | 1
(1 row)
commit;
@@ -199,62 +356,61 @@ INSERT INTO foo VALUES(1,1,'Joe');
INSERT INTO foo VALUES(1,2,'Ed');
INSERT INTO foo VALUES(2,1,'Mary');
-- sql, proretset = f, prorettype = b
-CREATE FUNCTION getfoo(int) RETURNS int AS 'SELECT $1;' LANGUAGE SQL;
-SELECT * FROM getfoo(1) AS t1;
+CREATE FUNCTION getfoo1(int) RETURNS int AS 'SELECT $1;' LANGUAGE SQL;
+SELECT * FROM getfoo1(1) AS t1;
t1
----
1
(1 row)
-SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+SELECT * FROM getfoo1(1) WITH ORDINALITY AS t1(v,o);
v | o
---+---
1 | 1
(1 row)
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo1(1);
SELECT * FROM vw_getfoo;
- getfoo
---------
- 1
+ getfoo1
+---------
+ 1
(1 row)
DROP VIEW vw_getfoo;
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY as t1(v,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo1(1) WITH ORDINALITY as t1(v,o);
SELECT * FROM vw_getfoo;
v | o
---+---
1 | 1
(1 row)
+DROP VIEW vw_getfoo;
-- sql, proretset = t, prorettype = b
-DROP VIEW vw_getfoo;
-DROP FUNCTION getfoo(int);
-CREATE FUNCTION getfoo(int) RETURNS setof int AS 'SELECT fooid FROM foo WHERE fooid = $1;' LANGUAGE SQL;
-SELECT * FROM getfoo(1) AS t1;
+CREATE FUNCTION getfoo2(int) RETURNS setof int AS 'SELECT fooid FROM foo WHERE fooid = $1;' LANGUAGE SQL;
+SELECT * FROM getfoo2(1) AS t1;
t1
----
1
1
(2 rows)
-SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+SELECT * FROM getfoo2(1) WITH ORDINALITY AS t1(v,o);
v | o
---+---
1 | 1
1 | 2
(2 rows)
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo2(1);
SELECT * FROM vw_getfoo;
- getfoo
---------
- 1
- 1
+ getfoo2
+---------
+ 1
+ 1
(2 rows)
DROP VIEW vw_getfoo;
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo2(1) WITH ORDINALITY AS t1(v,o);
SELECT * FROM vw_getfoo;
v | o
---+---
@@ -262,34 +418,33 @@ SELECT * FROM vw_getfoo;
1 | 2
(2 rows)
--- sql, proretset = t, prorettype = b
DROP VIEW vw_getfoo;
-DROP FUNCTION getfoo(int);
-CREATE FUNCTION getfoo(int) RETURNS setof text AS 'SELECT fooname FROM foo WHERE fooid = $1;' LANGUAGE SQL;
-SELECT * FROM getfoo(1) AS t1;
+-- sql, proretset = t, prorettype = b
+CREATE FUNCTION getfoo3(int) RETURNS setof text AS 'SELECT fooname FROM foo WHERE fooid = $1;' LANGUAGE SQL;
+SELECT * FROM getfoo3(1) AS t1;
t1
-----
Joe
Ed
(2 rows)
-SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+SELECT * FROM getfoo3(1) WITH ORDINALITY AS t1(v,o);
v | o
-----+---
Joe | 1
Ed | 2
(2 rows)
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo3(1);
SELECT * FROM vw_getfoo;
- getfoo
---------
+ getfoo3
+---------
Joe
Ed
(2 rows)
DROP VIEW vw_getfoo;
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo3(1) WITH ORDINALITY AS t1(v,o);
SELECT * FROM vw_getfoo;
v | o
-----+---
@@ -297,23 +452,22 @@ SELECT * FROM vw_getfoo;
Ed | 2
(2 rows)
+DROP VIEW vw_getfoo;
-- sql, proretset = f, prorettype = c
-DROP VIEW vw_getfoo;
-DROP FUNCTION getfoo(int);
-CREATE FUNCTION getfoo(int) RETURNS foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
-SELECT * FROM getfoo(1) AS t1;
+CREATE FUNCTION getfoo4(int) RETURNS foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
+SELECT * FROM getfoo4(1) AS t1;
fooid | foosubid | fooname
-------+----------+---------
1 | 1 | Joe
(1 row)
-SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+SELECT * FROM getfoo4(1) WITH ORDINALITY AS t1(a,b,c,o);
a | b | c | o
---+---+-----+---
1 | 1 | Joe | 1
(1 row)
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo4(1);
SELECT * FROM vw_getfoo;
fooid | foosubid | fooname
-------+----------+---------
@@ -321,32 +475,31 @@ SELECT * FROM vw_getfoo;
(1 row)
DROP VIEW vw_getfoo;
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo4(1) WITH ORDINALITY AS t1(a,b,c,o);
SELECT * FROM vw_getfoo;
a | b | c | o
---+---+-----+---
1 | 1 | Joe | 1
(1 row)
+DROP VIEW vw_getfoo;
-- sql, proretset = t, prorettype = c
-DROP VIEW vw_getfoo;
-DROP FUNCTION getfoo(int);
-CREATE FUNCTION getfoo(int) RETURNS setof foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
-SELECT * FROM getfoo(1) AS t1;
+CREATE FUNCTION getfoo5(int) RETURNS setof foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
+SELECT * FROM getfoo5(1) AS t1;
fooid | foosubid | fooname
-------+----------+---------
1 | 1 | Joe
1 | 2 | Ed
(2 rows)
-SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+SELECT * FROM getfoo5(1) WITH ORDINALITY AS t1(a,b,c,o);
a | b | c | o
---+---+-----+---
1 | 1 | Joe | 1
1 | 2 | Ed | 2
(2 rows)
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo5(1);
SELECT * FROM vw_getfoo;
fooid | foosubid | fooname
-------+----------+---------
@@ -355,7 +508,7 @@ SELECT * FROM vw_getfoo;
(2 rows)
DROP VIEW vw_getfoo;
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo5(1) WITH ORDINALITY AS t1(a,b,c,o);
SELECT * FROM vw_getfoo;
a | b | c | o
---+---+-----+---
@@ -363,18 +516,22 @@ SELECT * FROM vw_getfoo;
1 | 2 | Ed | 2
(2 rows)
--- ordinality not supported for returns record yet
+DROP VIEW vw_getfoo;
-- sql, proretset = f, prorettype = record
-DROP VIEW vw_getfoo;
-DROP FUNCTION getfoo(int);
-CREATE FUNCTION getfoo(int) RETURNS RECORD AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
-SELECT * FROM getfoo(1) AS t1(fooid int, foosubid int, fooname text);
+CREATE FUNCTION getfoo6(int) RETURNS RECORD AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
+SELECT * FROM getfoo6(1) AS t1(fooid int, foosubid int, fooname text);
fooid | foosubid | fooname
-------+----------+---------
1 | 1 | Joe
(1 row)
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) AS
+SELECT * FROM TABLE( getfoo6(1) AS (fooid int, foosubid int, fooname text) ) WITH ORDINALITY;
+ fooid | foosubid | fooname | ordinality
+-------+----------+---------+------------
+ 1 | 1 | Joe | 1
+(1 row)
+
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo6(1) AS
(fooid int, foosubid int, fooname text);
SELECT * FROM vw_getfoo;
fooid | foosubid | fooname
@@ -382,18 +539,34 @@ SELECT * FROM vw_getfoo;
1 | 1 | Joe
(1 row)
+DROP VIEW vw_getfoo;
+CREATE VIEW vw_getfoo AS
+ SELECT * FROM TABLE( getfoo6(1) AS (fooid int, foosubid int, fooname text) )
+ WITH ORDINALITY;
+SELECT * FROM vw_getfoo;
+ fooid | foosubid | fooname | ordinality
+-------+----------+---------+------------
+ 1 | 1 | Joe | 1
+(1 row)
+
+DROP VIEW vw_getfoo;
-- sql, proretset = t, prorettype = record
-DROP VIEW vw_getfoo;
-DROP FUNCTION getfoo(int);
-CREATE FUNCTION getfoo(int) RETURNS setof record AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
-SELECT * FROM getfoo(1) AS t1(fooid int, foosubid int, fooname text);
+CREATE FUNCTION getfoo7(int) RETURNS setof record AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
+SELECT * FROM getfoo7(1) AS t1(fooid int, foosubid int, fooname text);
fooid | foosubid | fooname
-------+----------+---------
1 | 1 | Joe
1 | 2 | Ed
(2 rows)
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) AS
+SELECT * FROM TABLE( getfoo7(1) AS (fooid int, foosubid int, fooname text) ) WITH ORDINALITY;
+ fooid | foosubid | fooname | ordinality
+-------+----------+---------+------------
+ 1 | 1 | Joe | 1
+ 1 | 2 | Ed | 2
+(2 rows)
+
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo7(1) AS
(fooid int, foosubid int, fooname text);
SELECT * FROM vw_getfoo;
fooid | foosubid | fooname
@@ -402,54 +575,63 @@ SELECT * FROM vw_getfoo;
1 | 2 | Ed
(2 rows)
--- plpgsql, proretset = f, prorettype = b
DROP VIEW vw_getfoo;
-DROP FUNCTION getfoo(int);
-CREATE FUNCTION getfoo(int) RETURNS int AS 'DECLARE fooint int; BEGIN SELECT fooid into fooint FROM foo WHERE fooid = $1; RETURN fooint; END;' LANGUAGE plpgsql;
-SELECT * FROM getfoo(1) AS t1;
+CREATE VIEW vw_getfoo AS
+ SELECT * FROM TABLE( getfoo7(1) AS (fooid int, foosubid int, fooname text) )
+ WITH ORDINALITY;
+SELECT * FROM vw_getfoo;
+ fooid | foosubid | fooname | ordinality
+-------+----------+---------+------------
+ 1 | 1 | Joe | 1
+ 1 | 2 | Ed | 2
+(2 rows)
+
+DROP VIEW vw_getfoo;
+-- plpgsql, proretset = f, prorettype = b
+CREATE FUNCTION getfoo8(int) RETURNS int AS 'DECLARE fooint int; BEGIN SELECT fooid into fooint FROM foo WHERE fooid = $1; RETURN fooint; END;' LANGUAGE plpgsql;
+SELECT * FROM getfoo8(1) AS t1;
t1
----
1
(1 row)
-SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+SELECT * FROM getfoo8(1) WITH ORDINALITY AS t1(v,o);
v | o
---+---
1 | 1
(1 row)
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo8(1);
SELECT * FROM vw_getfoo;
- getfoo
---------
- 1
+ getfoo8
+---------
+ 1
(1 row)
DROP VIEW vw_getfoo;
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo8(1) WITH ORDINALITY AS t1(v,o);
SELECT * FROM vw_getfoo;
v | o
---+---
1 | 1
(1 row)
+DROP VIEW vw_getfoo;
-- plpgsql, proretset = f, prorettype = c
-DROP VIEW vw_getfoo;
-DROP FUNCTION getfoo(int);
-CREATE FUNCTION getfoo(int) RETURNS foo AS 'DECLARE footup foo%ROWTYPE; BEGIN SELECT * into footup FROM foo WHERE fooid = $1; RETURN footup; END;' LANGUAGE plpgsql;
-SELECT * FROM getfoo(1) AS t1;
+CREATE FUNCTION getfoo9(int) RETURNS foo AS 'DECLARE footup foo%ROWTYPE; BEGIN SELECT * into footup FROM foo WHERE fooid = $1; RETURN footup; END;' LANGUAGE plpgsql;
+SELECT * FROM getfoo9(1) AS t1;
fooid | foosubid | fooname
-------+----------+---------
1 | 1 | Joe
(1 row)
-SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+SELECT * FROM getfoo9(1) WITH ORDINALITY AS t1(a,b,c,o);
a | b | c | o
---+---+-----+---
1 | 1 | Joe | 1
(1 row)
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo9(1);
SELECT * FROM vw_getfoo;
fooid | foosubid | fooname
-------+----------+---------
@@ -457,7 +639,7 @@ SELECT * FROM vw_getfoo;
(1 row)
DROP VIEW vw_getfoo;
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo9(1) WITH ORDINALITY AS t1(a,b,c,o);
SELECT * FROM vw_getfoo;
a | b | c | o
---+---+-----+---
@@ -465,23 +647,82 @@ SELECT * FROM vw_getfoo;
(1 row)
DROP VIEW vw_getfoo;
-DROP FUNCTION getfoo(int);
+-- mix 'n match kinds, to exercise expandRTE and related logic
+select * from table(getfoo1(1),getfoo2(1),getfoo3(1),getfoo4(1),getfoo5(1),
+ getfoo6(1) AS (fooid int, foosubid int, fooname text),
+ getfoo7(1) AS (fooid int, foosubid int, fooname text),
+ getfoo8(1),getfoo9(1))
+ with ordinality as t1(a,b,c,d,e,f,g,h,i,j,k,l,m,o,p,q,r,s,t,u);
+ a | b | c | d | e | f | g | h | i | j | k | l | m | o | p | q | r | s | t | u
+---+---+-----+---+---+-----+---+---+-----+---+---+-----+---+---+-----+---+---+---+-----+---
+ 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | 1 | Joe | 1
+ | 1 | Ed | | | | 1 | 2 | Ed | | | | 1 | 2 | Ed | | | | | 2
+(2 rows)
+
+select * from table(getfoo9(1),getfoo8(1),
+ getfoo7(1) AS (fooid int, foosubid int, fooname text),
+ getfoo6(1) AS (fooid int, foosubid int, fooname text),
+ getfoo5(1),getfoo4(1),getfoo3(1),getfoo2(1),getfoo1(1))
+ with ordinality as t1(a,b,c,d,e,f,g,h,i,j,k,l,m,o,p,q,r,s,t,u);
+ a | b | c | d | e | f | g | h | i | j | k | l | m | o | p | q | r | s | t | u
+---+---+-----+---+---+---+-----+---+---+-----+---+---+-----+---+---+-----+-----+---+---+---
+ 1 | 1 | Joe | 1 | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | Joe | 1 | 1 | 1
+ | | | | 1 | 2 | Ed | | | | 1 | 2 | Ed | | | | Ed | 1 | | 2
+(2 rows)
+
+create temporary view vw_foo as
+ select * from table(getfoo9(1),
+ getfoo7(1) AS (fooid int, foosubid int, fooname text),
+ getfoo1(1))
+ with ordinality as t1(a,b,c,d,e,f,g,n);
+select * from vw_foo;
+ a | b | c | d | e | f | g | n
+---+---+-----+---+---+-----+---+---
+ 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1
+ | | | 1 | 2 | Ed | | 2
+(2 rows)
+
+select pg_get_viewdef('vw_foo');
+ pg_get_viewdef
+--------------------------------------------------------------------------------------------------------------------------------------------------
+ SELECT t1.a, +
+ t1.b, +
+ t1.c, +
+ t1.d, +
+ t1.e, +
+ t1.f, +
+ t1.g, +
+ t1.n +
+ FROM TABLE(getfoo9(1), getfoo7(1) AS (fooid integer, foosubid integer, fooname text), getfoo1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
+(1 row)
+
+drop view vw_foo;
+DROP FUNCTION getfoo1(int);
+DROP FUNCTION getfoo2(int);
+DROP FUNCTION getfoo3(int);
+DROP FUNCTION getfoo4(int);
+DROP FUNCTION getfoo5(int);
+DROP FUNCTION getfoo6(int);
+DROP FUNCTION getfoo7(int);
+DROP FUNCTION getfoo8(int);
+DROP FUNCTION getfoo9(int);
DROP FUNCTION foot(int);
DROP TABLE foo2;
DROP TABLE foo;
-- Rescan tests --
-CREATE TEMPORARY SEQUENCE foo_rescan_seq;
+CREATE TEMPORARY SEQUENCE foo_rescan_seq1;
+CREATE TEMPORARY SEQUENCE foo_rescan_seq2;
CREATE TYPE foo_rescan_t AS (i integer, s bigint);
-CREATE FUNCTION foo_sql(int,int) RETURNS setof foo_rescan_t AS 'SELECT i, nextval(''foo_rescan_seq'') FROM generate_series($1,$2) i;' LANGUAGE SQL;
+CREATE FUNCTION foo_sql(int,int) RETURNS setof foo_rescan_t AS 'SELECT i, nextval(''foo_rescan_seq1'') FROM generate_series($1,$2) i;' LANGUAGE SQL;
-- plpgsql functions use materialize mode
-CREATE FUNCTION foo_mat(int,int) RETURNS setof foo_rescan_t AS 'begin for i in $1..$2 loop return next (i, nextval(''foo_rescan_seq'')); end loop; end;' LANGUAGE plpgsql;
+CREATE FUNCTION foo_mat(int,int) RETURNS setof foo_rescan_t AS 'begin for i in $1..$2 loop return next (i, nextval(''foo_rescan_seq2'')); end loop; end;' LANGUAGE plpgsql;
--invokes ExecReScanFunctionScan - all these cases should materialize the function only once
-- LEFT JOIN on a condition that the planner can't prove to be true is used to ensure the function
-- is on the inner path of a nestloop join
-SELECT setval('foo_rescan_seq',1,false);
- setval
---------
- 1
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
(1 row)
SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) ON (r+i)<100;
@@ -498,10 +739,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) ON (r+i)<100;
3 | 13 | 3
(9 rows)
-SELECT setval('foo_rescan_seq',1,false);
- setval
---------
- 1
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
(1 row)
SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100;
@@ -518,10 +759,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) WITH ORDINALITY
3 | 13 | 3 | 3
(9 rows)
-SELECT setval('foo_rescan_seq',1,false);
- setval
---------
- 1
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
(1 row)
SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) ON (r+i)<100;
@@ -538,10 +779,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) ON (r+i)<100;
3 | 13 | 3
(9 rows)
-SELECT setval('foo_rescan_seq',1,false);
- setval
---------
- 1
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
(1 row)
SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100;
@@ -558,6 +799,26 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) WITH ORDINALITY
3 | 13 | 3 | 3
(9 rows)
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN TABLE( foo_sql(11,13), foo_mat(11,13) ) WITH ORDINALITY AS f(i1,s1,i2,s2,o) ON (r+i1+i2)<100;
+ r | i1 | s1 | i2 | s2 | o
+---+----+----+----+----+---
+ 1 | 11 | 1 | 11 | 1 | 1
+ 1 | 12 | 2 | 12 | 2 | 2
+ 1 | 13 | 3 | 13 | 3 | 3
+ 2 | 11 | 1 | 11 | 1 | 1
+ 2 | 12 | 2 | 12 | 2 | 2
+ 2 | 13 | 3 | 13 | 3 | 3
+ 3 | 11 | 1 | 11 | 1 | 1
+ 3 | 12 | 2 | 12 | 2 | 2
+ 3 | 13 | 3 | 13 | 3 | 3
+(9 rows)
+
SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) f(i) ON (r+i)<100;
r | i
---+----
@@ -615,10 +876,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) WITH O
(9 rows)
--invokes ExecReScanFunctionScan with chgParam != NULL (using implied LATERAL)
-SELECT setval('foo_rescan_seq',1,false);
- setval
---------
- 1
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
(1 row)
SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13);
@@ -632,10 +893,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13);
3 | 13 | 6
(6 rows)
-SELECT setval('foo_rescan_seq',1,false);
- setval
---------
- 1
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
(1 row)
SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13) WITH ORDINALITY AS f(i,s,o);
@@ -649,10 +910,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13) WITH ORDINALITY AS f(i
3 | 13 | 6 | 1
(6 rows)
-SELECT setval('foo_rescan_seq',1,false);
- setval
---------
- 1
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
(1 row)
SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r);
@@ -666,10 +927,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r);
3 | 13 | 6
(6 rows)
-SELECT setval('foo_rescan_seq',1,false);
- setval
---------
- 1
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
(1 row)
SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r) WITH ORDINALITY AS f(i,s,o);
@@ -683,10 +944,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r) WITH ORDINALITY AS f(i
3 | 13 | 6 | 3
(6 rows)
-SELECT setval('foo_rescan_seq',1,false);
- setval
---------
- 1
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
(1 row)
SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2);
@@ -704,10 +965,10 @@ SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2);
16 | 20 | 20 | 10
(10 rows)
-SELECT setval('foo_rescan_seq',1,false);
- setval
---------
- 1
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
(1 row)
SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2) WITH ORDINALITY AS f(i,s,o);
@@ -725,10 +986,10 @@ SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2) WITH ORD
16 | 20 | 20 | 10 | 5
(10 rows)
-SELECT setval('foo_rescan_seq',1,false);
- setval
---------
- 1
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
(1 row)
SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13);
@@ -742,10 +1003,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13);
3 | 13 | 6
(6 rows)
-SELECT setval('foo_rescan_seq',1,false);
- setval
---------
- 1
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
(1 row)
SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13) WITH ORDINALITY AS f(i,s,o);
@@ -759,10 +1020,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13) WITH ORDINALITY AS f(i
3 | 13 | 6 | 1
(6 rows)
-SELECT setval('foo_rescan_seq',1,false);
- setval
---------
- 1
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
(1 row)
SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r);
@@ -776,10 +1037,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r);
3 | 13 | 6
(6 rows)
-SELECT setval('foo_rescan_seq',1,false);
- setval
---------
- 1
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
(1 row)
SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r) WITH ORDINALITY AS f(i,s,o);
@@ -793,10 +1054,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r) WITH ORDINALITY AS f(i
3 | 13 | 6 | 3
(6 rows)
-SELECT setval('foo_rescan_seq',1,false);
- setval
---------
- 1
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
(1 row)
SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2);
@@ -814,10 +1075,10 @@ SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2);
16 | 20 | 20 | 10
(10 rows)
-SELECT setval('foo_rescan_seq',1,false);
- setval
---------
- 1
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
(1 row)
SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2) WITH ORDINALITY AS f(i,s,o);
@@ -835,6 +1096,82 @@ SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2) WITH ORD
16 | 20 | 20 | 10 | 5
(10 rows)
+-- selective rescan of multiple functions:
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), TABLE( foo_sql(11,11), foo_mat(10+r,13) );
+ r | i | s | i | s
+---+----+---+----+---
+ 1 | 11 | 1 | 11 | 1
+ 1 | | | 12 | 2
+ 1 | | | 13 | 3
+ 2 | 11 | 1 | 12 | 4
+ 2 | | | 13 | 5
+ 3 | 11 | 1 | 13 | 6
+(6 rows)
+
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), TABLE( foo_sql(10+r,13), foo_mat(11,11) );
+ r | i | s | i | s
+---+----+---+----+---
+ 1 | 11 | 1 | 11 | 1
+ 1 | 12 | 2 | |
+ 1 | 13 | 3 | |
+ 2 | 12 | 4 | 11 | 1
+ 2 | 13 | 5 | |
+ 3 | 13 | 6 | 11 | 1
+(6 rows)
+
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM (VALUES (1),(2),(3)) v(r), TABLE( foo_sql(10+r,13), foo_mat(10+r,13) );
+ r | i | s | i | s
+---+----+---+----+---
+ 1 | 11 | 1 | 11 | 1
+ 1 | 12 | 2 | 12 | 2
+ 1 | 13 | 3 | 13 | 3
+ 2 | 12 | 4 | 12 | 4
+ 2 | 13 | 5 | 13 | 5
+ 3 | 13 | 6 | 13 | 6
+(6 rows)
+
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+ setval | setval
+--------+--------
+ 1 | 1
+(1 row)
+
+SELECT * FROM generate_series(1,2) r1, generate_series(r1,3) r2, TABLE( foo_sql(10+r1,13), foo_mat(10+r2,13) );
+ r1 | r2 | i | s | i | s
+----+----+----+----+----+---
+ 1 | 1 | 11 | 1 | 11 | 1
+ 1 | 1 | 12 | 2 | 12 | 2
+ 1 | 1 | 13 | 3 | 13 | 3
+ 1 | 2 | 11 | 4 | 12 | 4
+ 1 | 2 | 12 | 5 | 13 | 5
+ 1 | 2 | 13 | 6 | |
+ 1 | 3 | 11 | 7 | 13 | 6
+ 1 | 3 | 12 | 8 | |
+ 1 | 3 | 13 | 9 | |
+ 2 | 2 | 12 | 10 | 12 | 7
+ 2 | 2 | 13 | 11 | 13 | 8
+ 2 | 3 | 12 | 12 | 13 | 9
+ 2 | 3 | 13 | 13 | |
+(13 rows)
+
SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) f(i);
r | i
---+----
@@ -1072,7 +1409,8 @@ SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
DROP FUNCTION foo_sql(int,int);
DROP FUNCTION foo_mat(int,int);
-DROP SEQUENCE foo_rescan_seq;
+DROP SEQUENCE foo_rescan_seq1;
+DROP SEQUENCE foo_rescan_seq2;
--
-- Test cases involving OUT parameters
--
@@ -1521,51 +1859,97 @@ LINE 1: select * from testfoo();
^
drop function testfoo();
--
--- Check some cases involving dropped columns in a rowtype result
+-- Check some cases involving added/dropped columns in a rowtype result
--
-create temp table users (userid text, email text, todrop bool, enabled bool);
-insert into users values ('id','email',true,true);
-insert into users values ('id2','email2',true,true);
+create temp table users (userid text, seq int, email text, todrop bool, moredrop int, enabled bool);
+insert into users values ('id',1,'email',true,11,true);
+insert into users values ('id2',2,'email2',true,12,true);
alter table users drop column todrop;
create or replace function get_first_user() returns users as
$$ SELECT * FROM users ORDER BY userid LIMIT 1; $$
language sql stable;
SELECT get_first_user();
- get_first_user
-----------------
- (id,email,t)
+ get_first_user
+-------------------
+ (id,1,email,11,t)
(1 row)
SELECT * FROM get_first_user();
- userid | email | enabled
---------+-------+---------
- id | email | t
+ userid | seq | email | moredrop | enabled
+--------+-----+-------+----------+---------
+ id | 1 | email | 11 | t
(1 row)
create or replace function get_users() returns setof users as
$$ SELECT * FROM users ORDER BY userid; $$
language sql stable;
SELECT get_users();
- get_users
-----------------
- (id,email,t)
- (id2,email2,t)
+ get_users
+---------------------
+ (id,1,email,11,t)
+ (id2,2,email2,12,t)
(2 rows)
SELECT * FROM get_users();
- userid | email | enabled
---------+--------+---------
- id | email | t
- id2 | email2 | t
+ userid | seq | email | moredrop | enabled
+--------+-----+--------+----------+---------
+ id | 1 | email | 11 | t
+ id2 | 2 | email2 | 12 | t
(2 rows)
SELECT * FROM get_users() WITH ORDINALITY; -- make sure ordinality copes
- userid | email | enabled | ordinality
---------+--------+---------+------------
- id | email | t | 1
- id2 | email2 | t | 2
+ userid | seq | email | moredrop | enabled | ordinality
+--------+-----+--------+----------+---------+------------
+ id | 1 | email | 11 | t | 1
+ id2 | 2 | email2 | 12 | t | 2
(2 rows)
+-- multiple functions vs. dropped columns
+SELECT * FROM TABLE(generate_series(10,11), get_users()) WITH ORDINALITY;
+ generate_series | userid | seq | email | moredrop | enabled | ordinality
+-----------------+--------+-----+--------+----------+---------+------------
+ 10 | id | 1 | email | 11 | t | 1
+ 11 | id2 | 2 | email2 | 12 | t | 2
+(2 rows)
+
+SELECT * FROM TABLE(get_users(), generate_series(10,11)) WITH ORDINALITY;
+ userid | seq | email | moredrop | enabled | generate_series | ordinality
+--------+-----+--------+----------+---------+-----------------+------------
+ id | 1 | email | 11 | t | 10 | 1
+ id2 | 2 | email2 | 12 | t | 11 | 2
+(2 rows)
+
+-- check that we can cope with post-parsing changes in rowtypes
+create temp view usersview as
+SELECT * FROM TABLE(get_users(), generate_series(10,11)) WITH ORDINALITY;
+select * from usersview;
+ userid | seq | email | moredrop | enabled | generate_series | ordinality
+--------+-----+--------+----------+---------+-----------------+------------
+ id | 1 | email | 11 | t | 10 | 1
+ id2 | 2 | email2 | 12 | t | 11 | 2
+(2 rows)
+
+alter table users drop column moredrop;
+select * from usersview;
+ userid | seq | email | moredrop | enabled | generate_series | ordinality
+--------+-----+--------+----------+---------+-----------------+------------
+ id | 1 | email | | t | 10 | 1
+ id2 | 2 | email2 | | t | 11 | 2
+(2 rows)
+
+alter table users add column junk text;
+select * from usersview;
+ userid | seq | email | moredrop | enabled | generate_series | ordinality
+--------+-----+--------+----------+---------+-----------------+------------
+ id | 1 | email | | t | 10 | 1
+ id2 | 2 | email2 | | t | 11 | 2
+(2 rows)
+
+alter table users alter column seq type numeric;
+select * from usersview; -- expect clean failure
+ERROR: attribute 2 has wrong type
+DETAIL: Table has type numeric, but query expects integer.
+drop view usersview;
drop function get_first_user();
drop function get_users();
drop table users;
diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql
index e82a1d5571..7ba8cbb304 100644
--- a/src/test/regress/sql/rangefuncs.sql
+++ b/src/test/regress/sql/rangefuncs.sql
@@ -16,14 +16,41 @@ select a,ord from unnest(array['a','b']) with ordinality as z(a,ord);
select * from unnest(array['a','b']) with ordinality as z(a,ord);
select a,ord from unnest(array[1.0::float8]) with ordinality as z(a,ord);
select * from unnest(array[1.0::float8]) with ordinality as z(a,ord);
+select row_to_json(s.*) from generate_series(11,14) with ordinality s;
-- ordinality vs. views
create temporary view vw_ord as select * from (values (1)) v(n) join foot(1) with ordinality as z(a,b,ord) on (n=ord);
select * from vw_ord;
select definition from pg_views where viewname='vw_ord';
drop view vw_ord;
--- ordinality vs. rewind and reverse scan
+
+-- multiple functions
+select * from table(foot(1),foot(2)) with ordinality as z(a,b,c,d,ord);
+create temporary view vw_ord as select * from (values (1)) v(n) join table(foot(1),foot(2)) with ordinality as z(a,b,c,d,ord) on (n=ord);
+select * from vw_ord;
+select definition from pg_views where viewname='vw_ord';
+drop view vw_ord;
+
+-- expansions of unnest()
+select * from unnest(array[10,20],array['foo','bar'],array[1.0]);
+select * from unnest(array[10,20],array['foo','bar'],array[1.0]) with ordinality as z(a,b,c,ord);
+select * from table(unnest(array[10,20],array['foo','bar'],array[1.0])) with ordinality as z(a,b,c,ord);
+select * from table(unnest(array[10,20],array['foo','bar']), generate_series(101,102)) with ordinality as z(a,b,c,ord);
+create temporary view vw_ord as select * from unnest(array[10,20],array['foo','bar'],array[1.0]) as z(a,b,c);
+select * from vw_ord;
+select definition from pg_views where viewname='vw_ord';
+drop view vw_ord;
+create temporary view vw_ord as select * from table(unnest(array[10,20],array['foo','bar'],array[1.0])) as z(a,b,c);
+select * from vw_ord;
+select definition from pg_views where viewname='vw_ord';
+drop view vw_ord;
+create temporary view vw_ord as select * from table(unnest(array[10,20],array['foo','bar']), generate_series(1,2)) as z(a,b,c);
+select * from vw_ord;
+select definition from pg_views where viewname='vw_ord';
+drop view vw_ord;
+
+-- ordinality and multiple functions vs. rewind and reverse scan
begin;
-declare foo scroll cursor for select * from generate_series(1,5) with ordinality as g(i,o);
+declare foo scroll cursor for select * from table(generate_series(1,5),generate_series(1,2)) with ordinality as g(i,j,o);
fetch all from foo;
fetch backward all from foo;
fetch all from foo;
@@ -31,6 +58,12 @@ fetch next from foo;
fetch next from foo;
fetch prior from foo;
fetch absolute 1 from foo;
+fetch next from foo;
+fetch next from foo;
+fetch next from foo;
+fetch prior from foo;
+fetch prior from foo;
+fetch prior from foo;
commit;
-- function with implicit LATERAL
@@ -57,133 +90,169 @@ INSERT INTO foo VALUES(1,2,'Ed');
INSERT INTO foo VALUES(2,1,'Mary');
-- sql, proretset = f, prorettype = b
-CREATE FUNCTION getfoo(int) RETURNS int AS 'SELECT $1;' LANGUAGE SQL;
-SELECT * FROM getfoo(1) AS t1;
-SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
+CREATE FUNCTION getfoo1(int) RETURNS int AS 'SELECT $1;' LANGUAGE SQL;
+SELECT * FROM getfoo1(1) AS t1;
+SELECT * FROM getfoo1(1) WITH ORDINALITY AS t1(v,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo1(1);
SELECT * FROM vw_getfoo;
DROP VIEW vw_getfoo;
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY as t1(v,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo1(1) WITH ORDINALITY as t1(v,o);
SELECT * FROM vw_getfoo;
+DROP VIEW vw_getfoo;
-- sql, proretset = t, prorettype = b
-DROP VIEW vw_getfoo;
-DROP FUNCTION getfoo(int);
-CREATE FUNCTION getfoo(int) RETURNS setof int AS 'SELECT fooid FROM foo WHERE fooid = $1;' LANGUAGE SQL;
-SELECT * FROM getfoo(1) AS t1;
-SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
+CREATE FUNCTION getfoo2(int) RETURNS setof int AS 'SELECT fooid FROM foo WHERE fooid = $1;' LANGUAGE SQL;
+SELECT * FROM getfoo2(1) AS t1;
+SELECT * FROM getfoo2(1) WITH ORDINALITY AS t1(v,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo2(1);
SELECT * FROM vw_getfoo;
DROP VIEW vw_getfoo;
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo2(1) WITH ORDINALITY AS t1(v,o);
SELECT * FROM vw_getfoo;
+DROP VIEW vw_getfoo;
-- sql, proretset = t, prorettype = b
-DROP VIEW vw_getfoo;
-DROP FUNCTION getfoo(int);
-CREATE FUNCTION getfoo(int) RETURNS setof text AS 'SELECT fooname FROM foo WHERE fooid = $1;' LANGUAGE SQL;
-SELECT * FROM getfoo(1) AS t1;
-SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
+CREATE FUNCTION getfoo3(int) RETURNS setof text AS 'SELECT fooname FROM foo WHERE fooid = $1;' LANGUAGE SQL;
+SELECT * FROM getfoo3(1) AS t1;
+SELECT * FROM getfoo3(1) WITH ORDINALITY AS t1(v,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo3(1);
SELECT * FROM vw_getfoo;
DROP VIEW vw_getfoo;
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo3(1) WITH ORDINALITY AS t1(v,o);
SELECT * FROM vw_getfoo;
+DROP VIEW vw_getfoo;
-- sql, proretset = f, prorettype = c
-DROP VIEW vw_getfoo;
-DROP FUNCTION getfoo(int);
-CREATE FUNCTION getfoo(int) RETURNS foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
-SELECT * FROM getfoo(1) AS t1;
-SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
+CREATE FUNCTION getfoo4(int) RETURNS foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
+SELECT * FROM getfoo4(1) AS t1;
+SELECT * FROM getfoo4(1) WITH ORDINALITY AS t1(a,b,c,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo4(1);
SELECT * FROM vw_getfoo;
DROP VIEW vw_getfoo;
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo4(1) WITH ORDINALITY AS t1(a,b,c,o);
SELECT * FROM vw_getfoo;
+DROP VIEW vw_getfoo;
-- sql, proretset = t, prorettype = c
-DROP VIEW vw_getfoo;
-DROP FUNCTION getfoo(int);
-CREATE FUNCTION getfoo(int) RETURNS setof foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
-SELECT * FROM getfoo(1) AS t1;
-SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
+CREATE FUNCTION getfoo5(int) RETURNS setof foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
+SELECT * FROM getfoo5(1) AS t1;
+SELECT * FROM getfoo5(1) WITH ORDINALITY AS t1(a,b,c,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo5(1);
SELECT * FROM vw_getfoo;
DROP VIEW vw_getfoo;
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo5(1) WITH ORDINALITY AS t1(a,b,c,o);
SELECT * FROM vw_getfoo;
+DROP VIEW vw_getfoo;
--- ordinality not supported for returns record yet
-- sql, proretset = f, prorettype = record
-DROP VIEW vw_getfoo;
-DROP FUNCTION getfoo(int);
-CREATE FUNCTION getfoo(int) RETURNS RECORD AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
-SELECT * FROM getfoo(1) AS t1(fooid int, foosubid int, fooname text);
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) AS
+CREATE FUNCTION getfoo6(int) RETURNS RECORD AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
+SELECT * FROM getfoo6(1) AS t1(fooid int, foosubid int, fooname text);
+SELECT * FROM TABLE( getfoo6(1) AS (fooid int, foosubid int, fooname text) ) WITH ORDINALITY;
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo6(1) AS
(fooid int, foosubid int, fooname text);
SELECT * FROM vw_getfoo;
+DROP VIEW vw_getfoo;
+CREATE VIEW vw_getfoo AS
+ SELECT * FROM TABLE( getfoo6(1) AS (fooid int, foosubid int, fooname text) )
+ WITH ORDINALITY;
+SELECT * FROM vw_getfoo;
+DROP VIEW vw_getfoo;
-- sql, proretset = t, prorettype = record
-DROP VIEW vw_getfoo;
-DROP FUNCTION getfoo(int);
-CREATE FUNCTION getfoo(int) RETURNS setof record AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
-SELECT * FROM getfoo(1) AS t1(fooid int, foosubid int, fooname text);
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) AS
+CREATE FUNCTION getfoo7(int) RETURNS setof record AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
+SELECT * FROM getfoo7(1) AS t1(fooid int, foosubid int, fooname text);
+SELECT * FROM TABLE( getfoo7(1) AS (fooid int, foosubid int, fooname text) ) WITH ORDINALITY;
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo7(1) AS
(fooid int, foosubid int, fooname text);
SELECT * FROM vw_getfoo;
+DROP VIEW vw_getfoo;
+CREATE VIEW vw_getfoo AS
+ SELECT * FROM TABLE( getfoo7(1) AS (fooid int, foosubid int, fooname text) )
+ WITH ORDINALITY;
+SELECT * FROM vw_getfoo;
+DROP VIEW vw_getfoo;
-- plpgsql, proretset = f, prorettype = b
-DROP VIEW vw_getfoo;
-DROP FUNCTION getfoo(int);
-CREATE FUNCTION getfoo(int) RETURNS int AS 'DECLARE fooint int; BEGIN SELECT fooid into fooint FROM foo WHERE fooid = $1; RETURN fooint; END;' LANGUAGE plpgsql;
-SELECT * FROM getfoo(1) AS t1;
-SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
+CREATE FUNCTION getfoo8(int) RETURNS int AS 'DECLARE fooint int; BEGIN SELECT fooid into fooint FROM foo WHERE fooid = $1; RETURN fooint; END;' LANGUAGE plpgsql;
+SELECT * FROM getfoo8(1) AS t1;
+SELECT * FROM getfoo8(1) WITH ORDINALITY AS t1(v,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo8(1);
SELECT * FROM vw_getfoo;
DROP VIEW vw_getfoo;
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo8(1) WITH ORDINALITY AS t1(v,o);
SELECT * FROM vw_getfoo;
+DROP VIEW vw_getfoo;
-- plpgsql, proretset = f, prorettype = c
-DROP VIEW vw_getfoo;
-DROP FUNCTION getfoo(int);
-CREATE FUNCTION getfoo(int) RETURNS foo AS 'DECLARE footup foo%ROWTYPE; BEGIN SELECT * into footup FROM foo WHERE fooid = $1; RETURN footup; END;' LANGUAGE plpgsql;
-SELECT * FROM getfoo(1) AS t1;
-SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
+CREATE FUNCTION getfoo9(int) RETURNS foo AS 'DECLARE footup foo%ROWTYPE; BEGIN SELECT * into footup FROM foo WHERE fooid = $1; RETURN footup; END;' LANGUAGE plpgsql;
+SELECT * FROM getfoo9(1) AS t1;
+SELECT * FROM getfoo9(1) WITH ORDINALITY AS t1(a,b,c,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo9(1);
SELECT * FROM vw_getfoo;
DROP VIEW vw_getfoo;
-CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+CREATE VIEW vw_getfoo AS SELECT * FROM getfoo9(1) WITH ORDINALITY AS t1(a,b,c,o);
SELECT * FROM vw_getfoo;
+DROP VIEW vw_getfoo;
-DROP VIEW vw_getfoo;
-DROP FUNCTION getfoo(int);
+-- mix 'n match kinds, to exercise expandRTE and related logic
+
+select * from table(getfoo1(1),getfoo2(1),getfoo3(1),getfoo4(1),getfoo5(1),
+ getfoo6(1) AS (fooid int, foosubid int, fooname text),
+ getfoo7(1) AS (fooid int, foosubid int, fooname text),
+ getfoo8(1),getfoo9(1))
+ with ordinality as t1(a,b,c,d,e,f,g,h,i,j,k,l,m,o,p,q,r,s,t,u);
+select * from table(getfoo9(1),getfoo8(1),
+ getfoo7(1) AS (fooid int, foosubid int, fooname text),
+ getfoo6(1) AS (fooid int, foosubid int, fooname text),
+ getfoo5(1),getfoo4(1),getfoo3(1),getfoo2(1),getfoo1(1))
+ with ordinality as t1(a,b,c,d,e,f,g,h,i,j,k,l,m,o,p,q,r,s,t,u);
+
+create temporary view vw_foo as
+ select * from table(getfoo9(1),
+ getfoo7(1) AS (fooid int, foosubid int, fooname text),
+ getfoo1(1))
+ with ordinality as t1(a,b,c,d,e,f,g,n);
+select * from vw_foo;
+select pg_get_viewdef('vw_foo');
+drop view vw_foo;
+
+DROP FUNCTION getfoo1(int);
+DROP FUNCTION getfoo2(int);
+DROP FUNCTION getfoo3(int);
+DROP FUNCTION getfoo4(int);
+DROP FUNCTION getfoo5(int);
+DROP FUNCTION getfoo6(int);
+DROP FUNCTION getfoo7(int);
+DROP FUNCTION getfoo8(int);
+DROP FUNCTION getfoo9(int);
DROP FUNCTION foot(int);
DROP TABLE foo2;
DROP TABLE foo;
-- Rescan tests --
-CREATE TEMPORARY SEQUENCE foo_rescan_seq;
+CREATE TEMPORARY SEQUENCE foo_rescan_seq1;
+CREATE TEMPORARY SEQUENCE foo_rescan_seq2;
CREATE TYPE foo_rescan_t AS (i integer, s bigint);
-CREATE FUNCTION foo_sql(int,int) RETURNS setof foo_rescan_t AS 'SELECT i, nextval(''foo_rescan_seq'') FROM generate_series($1,$2) i;' LANGUAGE SQL;
+CREATE FUNCTION foo_sql(int,int) RETURNS setof foo_rescan_t AS 'SELECT i, nextval(''foo_rescan_seq1'') FROM generate_series($1,$2) i;' LANGUAGE SQL;
-- plpgsql functions use materialize mode
-CREATE FUNCTION foo_mat(int,int) RETURNS setof foo_rescan_t AS 'begin for i in $1..$2 loop return next (i, nextval(''foo_rescan_seq'')); end loop; end;' LANGUAGE plpgsql;
+CREATE FUNCTION foo_mat(int,int) RETURNS setof foo_rescan_t AS 'begin for i in $1..$2 loop return next (i, nextval(''foo_rescan_seq2'')); end loop; end;' LANGUAGE plpgsql;
--invokes ExecReScanFunctionScan - all these cases should materialize the function only once
-- LEFT JOIN on a condition that the planner can't prove to be true is used to ensure the function
-- is on the inner path of a nestloop join
-SELECT setval('foo_rescan_seq',1,false);
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) ON (r+i)<100;
-SELECT setval('foo_rescan_seq',1,false);
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100;
-SELECT setval('foo_rescan_seq',1,false);
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) ON (r+i)<100;
-SELECT setval('foo_rescan_seq',1,false);
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100;
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN TABLE( foo_sql(11,13), foo_mat(11,13) ) WITH ORDINALITY AS f(i1,s1,i2,s2,o) ON (r+i1+i2)<100;
SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) f(i) ON (r+i)<100;
SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) WITH ORDINALITY AS f(i,o) ON (r+i)<100;
@@ -193,32 +262,44 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) WITH O
--invokes ExecReScanFunctionScan with chgParam != NULL (using implied LATERAL)
-SELECT setval('foo_rescan_seq',1,false);
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13);
-SELECT setval('foo_rescan_seq',1,false);
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13) WITH ORDINALITY AS f(i,s,o);
-SELECT setval('foo_rescan_seq',1,false);
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r);
-SELECT setval('foo_rescan_seq',1,false);
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r) WITH ORDINALITY AS f(i,s,o);
-SELECT setval('foo_rescan_seq',1,false);
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2);
-SELECT setval('foo_rescan_seq',1,false);
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2) WITH ORDINALITY AS f(i,s,o);
-SELECT setval('foo_rescan_seq',1,false);
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13);
-SELECT setval('foo_rescan_seq',1,false);
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13) WITH ORDINALITY AS f(i,s,o);
-SELECT setval('foo_rescan_seq',1,false);
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r);
-SELECT setval('foo_rescan_seq',1,false);
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r) WITH ORDINALITY AS f(i,s,o);
-SELECT setval('foo_rescan_seq',1,false);
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2);
-SELECT setval('foo_rescan_seq',1,false);
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2) WITH ORDINALITY AS f(i,s,o);
+-- selective rescan of multiple functions:
+
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r), TABLE( foo_sql(11,11), foo_mat(10+r,13) );
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r), TABLE( foo_sql(10+r,13), foo_mat(11,11) );
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+SELECT * FROM (VALUES (1),(2),(3)) v(r), TABLE( foo_sql(10+r,13), foo_mat(10+r,13) );
+
+SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false);
+SELECT * FROM generate_series(1,2) r1, generate_series(r1,3) r2, TABLE( foo_sql(10+r1,13), foo_mat(10+r2,13) );
+
SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) f(i);
SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) WITH ORDINALITY AS f(i,o);
@@ -242,7 +323,8 @@ SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
DROP FUNCTION foo_sql(int,int);
DROP FUNCTION foo_mat(int,int);
-DROP SEQUENCE foo_rescan_seq;
+DROP SEQUENCE foo_rescan_seq1;
+DROP SEQUENCE foo_rescan_seq2;
--
-- Test cases involving OUT parameters
@@ -444,12 +526,12 @@ select * from testfoo(); -- fail
drop function testfoo();
--
--- Check some cases involving dropped columns in a rowtype result
+-- Check some cases involving added/dropped columns in a rowtype result
--
-create temp table users (userid text, email text, todrop bool, enabled bool);
-insert into users values ('id','email',true,true);
-insert into users values ('id2','email2',true,true);
+create temp table users (userid text, seq int, email text, todrop bool, moredrop int, enabled bool);
+insert into users values ('id',1,'email',true,11,true);
+insert into users values ('id2',2,'email2',true,12,true);
alter table users drop column todrop;
create or replace function get_first_user() returns users as
@@ -467,6 +549,23 @@ SELECT get_users();
SELECT * FROM get_users();
SELECT * FROM get_users() WITH ORDINALITY; -- make sure ordinality copes
+-- multiple functions vs. dropped columns
+SELECT * FROM TABLE(generate_series(10,11), get_users()) WITH ORDINALITY;
+SELECT * FROM TABLE(get_users(), generate_series(10,11)) WITH ORDINALITY;
+
+-- check that we can cope with post-parsing changes in rowtypes
+create temp view usersview as
+SELECT * FROM TABLE(get_users(), generate_series(10,11)) WITH ORDINALITY;
+
+select * from usersview;
+alter table users drop column moredrop;
+select * from usersview;
+alter table users add column junk text;
+select * from usersview;
+alter table users alter column seq type numeric;
+select * from usersview; -- expect clean failure
+
+drop view usersview;
drop function get_first_user();
drop function get_users();
drop table users;