diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index ea930af796d74f93d1c52d4629e418718ca56b66..12322b87b805a47b215f9f2726c1c8d6297256fa 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 a5c808effae322ea17e8537055ec870fe84bc4f6..a411e3a0cc94e4b75923caae8ba199ec027cb610 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -11185,6 +11185,21 @@ SELECT NULLIF(value, '(none)') ... <entry><literallayout class="monospaced">1 2</literallayout>(2 rows)</entry> </row> + <row> + <entry> + <literal> + <function>unnest</function>(<type>anyarray</type>, <type>anyarray</type> [, ...]) + </literal> + </entry> + <entry><type>setof anyelement, anyelement [, ...]</type></entry> + <entry>expand multiple arrays (possibly of different types) to a set + of rows. This is only allowed in the FROM clause; see + <xref linkend="queries-tablefunctions"></entry> + <entry><literal>unnest(ARRAY[1,2],ARRAY['foo','bar','baz'])</literal></entry> + <entry><literallayout class="monospaced">1 foo +2 bar +NULL baz</literallayout>(3 rows)</entry> + </row> </tbody> </tgroup> </table> @@ -13295,6 +13310,8 @@ AND functions, as detailed in <xref linkend="functions-srf-series"> and <xref linkend="functions-srf-subscripts">. Other, more specialized set-returning functions are described elsewhere in this manual. + See <xref linkend="queries-tablefunctions"> for ways to combine multiple + set-returning functions. </para> <table id="functions-srf-series"> @@ -13499,14 +13516,11 @@ SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]); </indexterm> <para> - When a function in the <literal>FROM</literal> clause is suffixed by - <literal>WITH ORDINALITY</literal>, a <type>bigint</type> 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 <literal>OUT</literal> parameters, but not when using - a function returning <literal>RECORD</literal> with an explicit column - definition list. + When a function in the <literal>FROM</literal> clause is suffixed + by <literal>WITH ORDINALITY</literal>, a <type>bigint</type> 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 <function>unnest()</>. <programlisting> -- set returning function WITH ORDINALITY diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml index c32c85765188265d24aa620e0709d4b3d39bf0b1..b33de6820057d5940f15c26393eb972204c18a63 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 <literal>FROM</> clause of a query. Columns returned by table functions can be included in <literal>SELECT</>, <literal>JOIN</>, or <literal>WHERE</> clauses in the same manner - as a table, view, or subquery column. + as columns of a table, view, or subquery. </para> <para> - 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 <literal>TABLE</literal> + 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. </para> +<synopsis> +<replaceable>function_call</replaceable> <optional>WITH ORDINALITY</optional> <optional><optional>AS</optional> <replaceable>table_alias</replaceable> <optional>(<replaceable>column_alias</replaceable> <optional>, ... </optional>)</optional></optional> +TABLE( <replaceable>function_call</replaceable> <optional>, ... </optional> ) <optional>WITH ORDINALITY</optional> <optional><optional>AS</optional> <replaceable>table_alias</replaceable> <optional>(<replaceable>column_alias</replaceable> <optional>, ... </optional>)</optional></optional> +</synopsis> + + <para> + If the <literal>WITH ORDINALITY</literal> clause is specified, an + additional column of type <type>bigint</type> 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 <literal>UNNEST ... WITH ORDINALITY</literal>.) + By default, the ordinal column is called <literal>ordinality</>, but + a different column name can be assigned to it using + an <literal>AS</literal> clause. + </para> + + <para> + The special table function <literal>UNNEST</literal> may be called with + any number of array parameters, and it returns a corresponding number of + columns, as if <literal>UNNEST</literal> + (<xref linkend="functions-array">) had been called on each parameter + separately and combined using the <literal>TABLE</literal> construct. + </para> + +<synopsis> +UNNEST( <replaceable>array_expression</replaceable> <optional>, ... </optional> ) <optional>WITH ORDINALITY</optional> <optional><optional>AS</optional> <replaceable>table_alias</replaceable> <optional>(<replaceable>column_alias</replaceable> <optional>, ... </optional>)</optional></optional> +</synopsis> + <para> - A table function can be aliased in the <literal>FROM</> clause, - but it also can be left unaliased. If a function is used in the - <literal>FROM</> clause with no alias, the function name is used - as the resulting table name. + If no <replaceable>table_alias</replaceable> is specified, the function + name is used as the table name; in the case of a <literal>TABLE()</> + construct, the first function's name is used. + </para> + + <para> + 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. </para> <para> @@ -691,7 +725,30 @@ SELECT * FROM vw_getfoo; the pseudotype <type>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: + </para> + +<synopsis> +<replaceable>function_call</replaceable> <optional>AS</optional> <replaceable>alias</replaceable> (<replaceable>column_definition</replaceable> <optional>, ... </optional>) +<replaceable>function_call</replaceable> AS <optional><replaceable>alias</replaceable></optional> (<replaceable>column_definition</replaceable> <optional>, ... </optional>) +TABLE( ... <replaceable>function_call</replaceable> AS (<replaceable>column_definition</replaceable> <optional>, ... </optional>) <optional>, ... </optional> ) +</synopsis> + + <para> + When not using the <literal>TABLE()</> syntax, + the <replaceable>column_definition</replaceable> list replaces the column + alias list that could otherwise be attached to the <literal>FROM</> + item; the names in the column definitions serve as column aliases. + When using the <literal>TABLE()</> syntax, + a <replaceable>column_definition</replaceable> list can be attached to + each member function separately; or if there is only one member function + and no <literal>WITH ORDINALITY</> clause, + a <replaceable>column_definition</replaceable> list can be written in + place of a column alias list following <literal>TABLE()</>. + </para> + + <para> + Consider this example: <programlisting> 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 e603b7644eab2cb0882a72e5ce27541b2c3cd6de..88ebd73d49ca84f669eb4a44df71f303ef338f50 100644 --- a/doc/src/sgml/ref/select.sgml +++ b/doc/src/sgml/ref/select.sgml @@ -52,9 +52,12 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ] [ LATERAL ] ( <replaceable class="parameter">select</replaceable> ) [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] <replaceable class="parameter">with_query_name</replaceable> [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ] - [ LATERAL ] <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ] + [ LATERAL ] <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) + [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ] [ LATERAL ] <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ] <replaceable class="parameter">alias</replaceable> ( <replaceable class="parameter">column_definition</replaceable> [, ...] ) [ LATERAL ] <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] ) + [ LATERAL ] TABLE( <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] ) ] [, ...] ) + [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ] <replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) ] <phrase>and <replaceable class="parameter">with_query</replaceable> is:</phrase> @@ -368,30 +371,32 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] Function calls can appear in the <literal>FROM</literal> 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 <command>SELECT</command> command. - When the optional <command>WITH ORDINALITY</command> 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: -<programlisting> -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) -</programlisting> - An alias can also be used. If an alias is written, a column + When the optional <command>WITH ORDINALITY</command> clause is + added to the function call, a new column is appended after + all the function's output columns with numbering for each row. + </para> + + <para> + 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 <literal>ORDINALITY</literal> if present. - </para> + </para> + + <para> + Multiple function calls can be combined into a + single <literal>FROM</>-clause item by surrounding them + with <literal>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. + </para> <para> 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</replaceable> <optional>, ... </>)</literal>. The column definition list must match the actual number and types of columns returned by the function. - <literal>ORDINALITY</literal> does not work in this case. + </para> + + <para> + When using the <literal>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 + <literal>TABLE( ... )</>. A column definition list can be placed + after the <literal>TABLE( ... )</> construct only if there's just a + single function and no <literal>WITH ORDINALITY</> clause. + </para> + + <para> + To use <literal>ORDINALITY</literal> together with a column definition + list, you must use the <literal>TABLE( ... )</> syntax and put the + column definition list inside <literal>TABLE( ... )</>. </para> </listitem> </varlistentry> @@ -1598,6 +1617,23 @@ SELECT * FROM distributors_2(111) AS (f1 int, f2 text); </programlisting> </para> + <para> + Here is an example of a function with an ordinality column added: + +<programlisting> +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) +</programlisting> + </para> + <para> This example shows how to use a simple <literal>WITH</> clause: @@ -1773,6 +1809,11 @@ SELECT distributors.* WHERE distributors.name = 'Westward'; <productname>PostgreSQL</productname> treats <literal>UNNEST()</> the same as other set-returning functions. </para> + + <para> + Placing multiple function calls inside <literal>TABLE( ... )</> syntax is + also an extension of the SQL standard. + </para> </refsect2> <refsect2> diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index bf47640092abe02c6420c5ec9659f01f85ebfa4a..d766ae728731ccfdabbbb1c4d0e9b3690f61b7e9 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 fe17c96f1247e3cfdfaf9804458df7be6070bd79..908126ce0168635a902c8dc8f3f19d747bfbff50 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 a3509d8c2a3242efa7896fb0a777e5928b239d6e..9c1fd2cd593ebd6dc8df6f42099315a99b237c35 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 4e93df26cc82191bd13b89fcc598743545527f61..bd5428de97b74ab8f4767d14a26626de237d4598 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; - - objectname = get_func_name(funcid); - if (es->verbose) - namespace = - get_namespace_name(get_func_namespace(funcid)); + RangeTblFunction *rtfunc = (RangeTblFunction *) linitial(fscan->functions); + + 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 67b8a5dfaba40dbc3a3ab63fad7d20a12483b92d..b6fb2e31c5954bbfc1043c624962e6f2482983e0 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 0b31f55af28996b1e7ba9cabc06f04adf86ac479..3483107e595b11411b663bc7208ed4e98792dcd0 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 aca40e7a7634884c88283e565cd823016585d676..0703c05cff8a6298d76d2311b51f2dad351b19ac 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 423e02f35411b0b6e0d38c90939bbc12dbe42df9..3e386fd3811b1b699eb92dd87e24cdb73c915db6 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,107 +61,182 @@ 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); - } - - /* - * Get the next tuple from tuplestore. Return NULL if no more tuples. - */ - (void) tuplestore_gettupleslot(tuplestorestate, - ScanDirectionIsForward(direction), - false, - funcslot); - - if (!scanslot) - return funcslot; + /* + * 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; - /* - * 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. - */ + /* + * 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); + } - ExecClearTuple(scanslot); + /* + * Get the next tuple from tuplestore. + */ + (void) tuplestore_gettupleslot(tstore, + ScanDirectionIsForward(direction), + false, + scanslot); + return 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. + * 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. */ - + 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); + /* + * 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) + { + 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); + } - for (i = 0; i < natts; ++i) + /* + * 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); + + if (TupIsNull(fs->func_slot)) { - scanslot->tts_values[i] = funcslot->tts_values[i]; - scanslot->tts_isnull[i] = funcslot->tts_isnull[i]; + /* + * 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; + } + } - scanslot->tts_values[natts] = Int64GetDatumFast(node->ordinal); - scanslot->tts_isnull[natts] = 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)); - - 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"); - } + /* + * 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); - /* - * 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 (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++; + } /* - * 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 = CreateTupleDescCopy(scanstate->funcstates[0].tupdesc); + scan_tupdesc->tdtypeid = RECORDOID; + scan_tupdesc->tdtypmod = -1; + } + else + { + AttrNumber attno = 0; - scan_tupdesc = CreateTupleDescCopyExtend(func_tupdesc, 1); + if (node->funcordinality) + natts++; - TupleDescInitEntry(scan_tupdesc, - natts + 1, - strVal(llast(node->funccolnames)), - INT8OID, - -1, - 0); + scan_tupdesc = CreateTemplateTupleDesc(natts, false); - BlessTupleDesc(scan_tupdesc); - } - else - scan_tupdesc = func_tupdesc; + for (i = 0; i < nfuncs; i++) + { + TupleDesc tupdesc = scanstate->funcstates[i].tupdesc; + int colcount = scanstate->funcstates[i].colcount; + int j; - scanstate->scan_tupdesc = scan_tupdesc; - scanstate->func_tupdesc = func_tupdesc; - ExecAssignScanType(&scanstate->ss, scan_tupdesc); + 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); + } - if (scanstate->func_slot) - ExecSetSlotDescriptor(scanstate->func_slot, func_tupdesc); + 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]; - ExecScanReScan(&node->ss); + if (fs->func_slot) + ExecClearTuple(fs->func_slot); + } - node->ordinal = 0; + ExecScanReScan(&node->ss); /* - * If we haven't materialized yet, just return. + * 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 (!node->tuplestorestate) - return; + if (chgparam) + { + ListCell *lc; - /* - * 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) + 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; + + /* 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 1733da633a316247be2fb891bc66cab42fce27b4..e3edcf6f74fb84b474c4100ad6dd28541df8f9b5 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 7b29812b696dcb588fd9ee676e2db056fed697f0..1f9b5d70f554f1e50b682d2e249d65fc7c99c602 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 4a7e793ae0a476cf5f4ee128c31e170630d846cf..d3ed4fe98b9e7137cb71cdfa338fc16bfb5180eb 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. + * 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. */ - toid = exprType(rte->funcexpr); - - if (rte->funcordinality) + 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 908f397d501ab5b5453d54755cf0c8a398d4b3e9..d7db67dc9ea8bf8ba55864b4c8220885946b783e 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 b39927e02550fa3c9144e143ee39f8a785031bd2..4c7505e3341498aa3968a45313c048eeec353712 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 d325bb321295e8306394e5b131cc780d754f974d..2e2cfa7af6ab8070b87cb46d8b635bb0dd67e3b0 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 bfd3809a007a38b8252ebde2813adfe143e865f6..96fe50f0b271b300e0fdcc2a88acbd762e866a3f 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -18,6 +18,7 @@ #include <math.h> #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 e7f8cec0fed13bd20dcd1758f602b7d33ce1984c..50f08521bfe4b269f32d6fc0241e94ad098badf8 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 032b2cdc1330e5b65508e84d6b48026e7e0d0897..9c8ede658f4b999df901da563385ecee61c097f6 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 5947e5b136f4d66ba6da083d705ec6c73ba1986f..f2c122d295951b60618131e10926b9ec544ffb72 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 04a399ee13cd549a1b3dec3a44f0f09bbefdc99c..59606643925cdc91aad0f289da9c60bdf531c1db 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 d8aa35dee79ac4a54fb352b34e503335cf8ed3cb..66707944a040717c6f0a22710af8efd8a9ab6880 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 b78d72701b3d6a5890850c41b8de6609c4a7c066..5c9f3d64ce77154761846b67beb258b8835d5ed2 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 0df70c4443b171e10bddcc3f1e0cee6b7ed688c7..d8cabbd5bfedcc792f3df5627eb2752afd49a3ce 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 c742cc9542b9add71588353cd9b71df99f431c68..485ac31bd37d2623248ac912201b098f5bf77a98 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 7ce8a9d8180df60be2d36c0b2e260c9a9f6716f9..a7fdd52c2942dd8bb72f72b61c4b26f0a3d1625e 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 64b1705191329a2886325b16aa4da7cbf2c5c576..a7169efd85601b03ae717580083aedf5a8bece58 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 11f629118b00f6b2291beded5fb51a2d7d0955ad..19220971da6a7c1cf080dd87bc23be68f5de8f00 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 <list> func_table_item func_table_list opt_col_def_list +%type <boolean> opt_ordinality %type <list> ExclusionConstraintList ExclusionConstraintElem %type <list> func_arg_list %type <node> 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 <name> ALTER [COLUMN] <colname> 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 7a1261d0fd2e213adaf35ccfd45f1042e5e84948..8b4c0ae0d3b85b89eee585dd8ccb31a15c2014a4 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 0052d21ad62fa63515e32982deaa64be919aea24..cd8d75e23d9b6517d68c866760b4cd2d1f23f423 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 - * - * eref->colnames is filled in. + * 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 * - * 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 (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 + /* + * 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)) { - 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); - rte->eref = eref; - - /* - * Now determine if the function returns a simple or composite type. - */ - functypclass = get_expr_result_type(funcexpr, - &funcrettype, - &tupdesc); - /* - * A coldeflist is required if the function returns RECORD and hasn't got - * a predetermined record type, and is prohibited otherwise. + * 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 (coldeflist != NIL) - { - 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)))); - } + if (alias) + aliasname = alias->aliasname; else + aliasname = linitial(funcnames); + + eref = makeAlias(aliasname, NIL); + rte->eref = eref; + + /* Process each function ... */ + functupdescs = (TupleDesc *) palloc(nfuncs * sizeof(TupleDesc)); + + totalatts = 0; + funcno = 0; + forthree(lc1, funcexprs, lc2, funcnames, lc3, coldeflists) { - if (functypclass == TYPEFUNC_RECORD) + Node *funcexpr = (Node *) lfirst(lc1); + char *funcname = (char *) lfirst(lc2); + List *coldeflist = (List *) lfirst(lc3); + RangeTblFunction *rtfunc = makeNode(RangeTblFunction); + TypeFuncClass functypclass; + Oid funcrettype; + + /* 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 */ + + /* + * Now determine if the function returns a simple or composite type. + */ + functypclass = get_expr_result_type(funcexpr, + &funcrettype, + &tupdesc); + + /* + * 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_SYNTAX_ERROR), + errmsg("a column definition list is only allowed for functions returning \"record\""), + parser_errposition(pstate, + exprLocation((Node *) coldeflist)))); + } + 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_SYNTAX_ERROR), - errmsg("a column definition list is required for functions returning \"record\""), + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("function \"%s\" in FROM has unsupported return type %s", + funcname, format_type_be(funcrettype)), parser_errposition(pstate, exprLocation(funcexpr)))); - } - 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); + /* 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++; } - else if (functypclass == TYPEFUNC_RECORD) - { - ListCell *col; + /* + * 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) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("WITH ORDINALITY is not supported for functions returning \"record\""), - parser_errposition(pstate, exprLocation(funcexpr)))); + totalatts++; - /* - * Use the column definition list to form the alias list and - * funccoltypes/funccoltypmods/funccolcollations lists. - */ - foreach(col, coldeflist) + /* Merge the tuple descs of each function into a composite one */ + tupdesc = CreateTemplateTupleDesc(totalatts, false); + natts = 0; + for (i = 0; i < nfuncs; i++) { - ColumnDef *n = (ColumnDef *) lfirst(col); - char *attrname; - Oid attrtype; - int32 attrtypmod; - Oid attrcollation; - - attrname = pstrdup(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->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); + 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 - 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)))); + { + /* 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; - - functypclass = get_expr_result_type(rte->funcexpr, - &funcrettype, - &tupdesc); - if (functypclass == TYPEFUNC_COMPOSITE) - { - /* Composite data type, e.g. a table's row type */ - Assert(tupdesc); + int atts_done = 0; + ListCell *lc; - /* - * 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) + foreach(lc, rte->functions) { - /* Base data type, i.e. scalar */ - if (colnames) - *colnames = lappend(*colnames, - linitial(rte->eref->colnames)); - - if (colvars) + RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); + TypeFuncClass functypclass; + Oid funcrettype; + TupleDesc tupdesc; + + 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; - - forthree(l1, rte->funccoltypes, - l2, rte->funccoltypmods, - l3, rte->funccolcollations) + /* Base data type, i.e. scalar */ + if (colnames) + *colnames = lappend(*colnames, + list_nth(rte->eref->colnames, + atts_done)); + + 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; + + /* 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); + } - /* note, ordinality is not allowed in this case */ - } - else - { - /* addRangeTableEntryForFunction should've caught this */ - elog(ERROR, "function in FROM has unsupported return type"); + 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; - - /* - * 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)) - { - *vartype = INT8OID; - *vartypmod = -1; - *varcollid = InvalidOid; - break; - } - - functypclass = get_expr_result_type(rte->funcexpr, - &funcrettype, - &tupdesc); + ListCell *lc; + int atts_done = 0; - if (functypclass == TYPEFUNC_COMPOSITE) + /* Identify which function covers the requested column */ + foreach(lc, rte->functions) { - /* Composite data type, e.g. a table's row type */ - Form_pg_attribute att_tup; + RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); - 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))); + if (attnum > atts_done && + attnum <= atts_done + rtfunc->funccolcount) + { + TypeFuncClass functypclass; + Oid funcrettype; + TupleDesc tupdesc; - att_tup = tupdesc->attrs[attnum - 1]; + attnum -= atts_done; /* now relative to this func */ + functypclass = get_expr_result_type(rtfunc->funcexpr, + &funcrettype, + &tupdesc); - /* - * 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; + 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; } - 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) + /* If we get here, must be looking for the ordinality column */ + if (rte->funcordinality && attnum == atts_done + 1) { - *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"); + *vartype = INT8OID; + *vartypmod = -1; + *varcollid = InvalidOid; + return; } + + /* 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; - - 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; + RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); + + 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 07fce8a0112dabbe4e92a586bc61808359d89589..ee6802a65582dd4880b643a502216d786f3468e6 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 19d19e5f396a8adb212c0ba23d4f43db539a1557..ae2206a12331613716979863d32e001acb57769c 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 c52a3743de972ff5e66704cbf057d690a56d79a1..50cb75392b35a9088554d161df89107c33cd0a9f 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 5ffce68c6f4116db27381f5bc8097ce5ab96273e..74b573bd5e62d452444f3fdf654f1e8d5d5a0410 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 49226b70e6b2da73c6ff5a7a41ea12b1c77f853d..4aee94b0e6efb0c16d44338081d083fd322ecbe0 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 2d80cc30b7eccbbfefe2fd64081c0418796e88be..c783b2628174bdd459f739061a68d1ae3e52203b 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 0350ef668715fd0ef021946bcafefefdb02046f8..78efaa5f23e434f109a6d80723a70451f26ba0dc 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 bedcf040a7ac261340437fb63ed4fcbbb162146c..5a4034729cfccd50652cb2c2f6868d0a09a153d5 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 fc6b1d7dbd1a2865e3f75c7e8fec22fec7f2d40d..ff9af7691c91af86275a7a651a4201c5fed377ec 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 55524b468abc32c1a71a4ad30e50771c885bd4b1..6a5555f918d82694c0353d408e262406d7c5df9f 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): - * - * 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. + * Fields valid for a function RTE (else NIL/zero): * - * 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 44ea0b775294aaa248ea92d46b8b84881c89d548..101e22cee1d55116bc5927c6c7049a352b42c18c 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 9686229134e228be1187d6c727034abb5d186ffe..0033a3c5297a8550e7cf273b37af11bb1d27d4eb 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 96ffdb195fe62e03e0f5a33ce854855392d5886b..999adaaf8cd6d6a561ffe3c4fb4283976f5e98a0 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 83c9131e78167b6e8610712adacf09cd71656f32..f20c113a806394fff387583d50860c46f01df189 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 37391dcfce72b3d541756866db7cd7cea31c774b..418f92c4dbee3c67f5fff5b09b346b313de72cbd 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) --- 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; +-- sql, proretset = t, prorettype = b +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) --- 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; +-- sql, proretset = f, prorettype = c +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) --- 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; +-- sql, proretset = t, prorettype = c +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 --- 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); +-- sql, proretset = f, prorettype = record +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) --- 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 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 +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) --- 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; +-- plpgsql, proretset = f, prorettype = c +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 e82a1d5571226dc5087c84ea442d078f7ab640ab..7ba8cbb3042a4063ef54edfed73249a3068e4b51 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 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;