mirror of
https://github.com/postgres/postgres.git
synced 2025-04-22 23:02:54 +03:00
Support multi-argument UNNEST(), and TABLE() syntax for multiple functions.
This patch adds the ability to write TABLE( function1(), function2(), ...) as a single FROM-clause entry. The result is the concatenation of the first row from each function, followed by the second row from each function, etc; with NULLs inserted if any function produces fewer rows than others. This is believed to be a much more useful behavior than what Postgres currently does with multiple SRFs in a SELECT list. This syntax also provides a reasonable way to combine use of column definition lists with WITH ORDINALITY: put the column definition list inside TABLE(), where it's clear that it doesn't control the ordinality column as well. Also implement SQL-compliant multiple-argument UNNEST(), by turning UNNEST(a,b,c) into TABLE(unnest(a), unnest(b), unnest(c)). The SQL standard specifies TABLE() with only a single function, not multiple functions, and it seems to require an implicit UNNEST() which is not what this patch does. There may be something wrong with that reading of the spec, though, because if it's right then the spec's TABLE() is just a pointless alternative spelling of UNNEST(). After further review of that, we might choose to adopt a different syntax for what this patch does, but in any case this functionality seems clearly worthwhile. Andrew Gierth, reviewed by Zoltán Böszörményi and Heikki Linnakangas, and significantly revised by me
This commit is contained in:
parent
38f4328981
commit
784e762e88
@ -1456,7 +1456,7 @@ JumbleRangeTable(pgssJumbleState *jstate, List *rtable)
|
|||||||
APP_JUMB(rte->jointype);
|
APP_JUMB(rte->jointype);
|
||||||
break;
|
break;
|
||||||
case RTE_FUNCTION:
|
case RTE_FUNCTION:
|
||||||
JumbleExpr(jstate, rte->funcexpr);
|
JumbleExpr(jstate, (Node *) rte->functions);
|
||||||
break;
|
break;
|
||||||
case RTE_VALUES:
|
case RTE_VALUES:
|
||||||
JumbleExpr(jstate, (Node *) rte->values_lists);
|
JumbleExpr(jstate, (Node *) rte->values_lists);
|
||||||
@ -1866,6 +1866,13 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
|
|||||||
JumbleExpr(jstate, setop->rarg);
|
JumbleExpr(jstate, setop->rarg);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case T_RangeTblFunction:
|
||||||
|
{
|
||||||
|
RangeTblFunction *rtfunc = (RangeTblFunction *) node;
|
||||||
|
|
||||||
|
JumbleExpr(jstate, rtfunc->funcexpr);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
/* Only a warning, since we can stumble along anyway */
|
/* Only a warning, since we can stumble along anyway */
|
||||||
elog(WARNING, "unrecognized node type: %d",
|
elog(WARNING, "unrecognized node type: %d",
|
||||||
|
@ -11185,6 +11185,21 @@ SELECT NULLIF(value, '(none)') ...
|
|||||||
<entry><literallayout class="monospaced">1
|
<entry><literallayout class="monospaced">1
|
||||||
2</literallayout>(2 rows)</entry>
|
2</literallayout>(2 rows)</entry>
|
||||||
</row>
|
</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>
|
</tbody>
|
||||||
</tgroup>
|
</tgroup>
|
||||||
</table>
|
</table>
|
||||||
@ -13295,6 +13310,8 @@ AND
|
|||||||
functions, as detailed in <xref linkend="functions-srf-series"> and
|
functions, as detailed in <xref linkend="functions-srf-series"> and
|
||||||
<xref linkend="functions-srf-subscripts">. Other, more specialized
|
<xref linkend="functions-srf-subscripts">. Other, more specialized
|
||||||
set-returning functions are described elsewhere in this manual.
|
set-returning functions are described elsewhere in this manual.
|
||||||
|
See <xref linkend="queries-tablefunctions"> for ways to combine multiple
|
||||||
|
set-returning functions.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<table id="functions-srf-series">
|
<table id="functions-srf-series">
|
||||||
@ -13499,14 +13516,11 @@ SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
|
|||||||
</indexterm>
|
</indexterm>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
When a function in the <literal>FROM</literal> clause is suffixed by
|
When a function in the <literal>FROM</literal> clause is suffixed
|
||||||
<literal>WITH ORDINALITY</literal>, a <type>bigint</type> column is appended
|
by <literal>WITH ORDINALITY</literal>, a <type>bigint</type> column is
|
||||||
to the output which starts from 1 and increments by 1 for each row of the
|
appended to the output which starts from 1 and increments by 1 for each row
|
||||||
function's output. This is most useful in the case of set returning functions
|
of the function's output. This is most useful in the case of set returning
|
||||||
such as UNNEST(). This functionality is available for functions returning
|
functions such as <function>unnest()</>.
|
||||||
composite types or using <literal>OUT</literal> parameters, but not when using
|
|
||||||
a function returning <literal>RECORD</literal> with an explicit column
|
|
||||||
definition list.
|
|
||||||
|
|
||||||
<programlisting>
|
<programlisting>
|
||||||
-- set returning function WITH ORDINALITY
|
-- set returning function WITH ORDINALITY
|
||||||
|
@ -643,21 +643,55 @@ FROM (VALUES ('anne', 'smith'), ('bob', 'jones'), ('joe', 'blow'))
|
|||||||
the <literal>FROM</> clause of a query. Columns returned by table
|
the <literal>FROM</> clause of a query. Columns returned by table
|
||||||
functions can be included in <literal>SELECT</>,
|
functions can be included in <literal>SELECT</>,
|
||||||
<literal>JOIN</>, or <literal>WHERE</> clauses in the same manner
|
<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>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
If a table function returns a base data type, the single result
|
Table functions may also be combined using the <literal>TABLE</literal>
|
||||||
column name matches the function name. If the function returns a
|
syntax, with the results returned in parallel columns; the number of
|
||||||
composite type, the result columns get the same names as the
|
result rows in this case is that of the largest function result, with
|
||||||
individual attributes of the type.
|
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>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
A table function can be aliased in the <literal>FROM</> clause,
|
The special table function <literal>UNNEST</literal> may be called with
|
||||||
but it also can be left unaliased. If a function is used in the
|
any number of array parameters, and it returns a corresponding number of
|
||||||
<literal>FROM</> clause with no alias, the function name is used
|
columns, as if <literal>UNNEST</literal>
|
||||||
as the resulting table name.
|
(<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>
|
||||||
|
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>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@ -691,7 +725,30 @@ SELECT * FROM vw_getfoo;
|
|||||||
the pseudotype <type>record</>. When such a function is used in
|
the pseudotype <type>record</>. When such a function is used in
|
||||||
a query, the expected row structure must be specified in the
|
a query, the expected row structure must be specified in the
|
||||||
query itself, so that the system can know how to parse and plan
|
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>
|
<programlisting>
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM dblink('dbname=mydb', 'SELECT proname, prosrc FROM pg_proc')
|
FROM dblink('dbname=mydb', 'SELECT proname, prosrc FROM pg_proc')
|
||||||
|
@ -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> [, ...] ) ] ]
|
[ 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> [, ...] ) ]
|
[ 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> [, ...] ) ] ]
|
<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">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 ] <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> [, ...] ) ]
|
<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>
|
<phrase>and <replaceable class="parameter">with_query</replaceable> is:</phrase>
|
||||||
@ -368,31 +371,33 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
|
|||||||
Function calls can appear in the <literal>FROM</literal>
|
Function calls can appear in the <literal>FROM</literal>
|
||||||
clause. (This is especially useful for functions that return
|
clause. (This is especially useful for functions that return
|
||||||
result sets, but any function can be used.) This acts as
|
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.
|
duration of this single <command>SELECT</command> command.
|
||||||
When the optional <command>WITH ORDINALITY</command> is
|
When the optional <command>WITH ORDINALITY</command> clause is
|
||||||
appended to the function call, a new column is appended after
|
added to the function call, a new column is appended after
|
||||||
all the function call's columns with numbering for each row.
|
all the function's output columns with numbering for each row.
|
||||||
For example:
|
</para>
|
||||||
<programlisting>
|
|
||||||
SELECT * FROM unnest(ARRAY['a','b','c','d','e','f']) WITH ORDINALITY;
|
<para>
|
||||||
unnest | ordinality
|
An alias can be provided in the same way as for a table.
|
||||||
--------+----------
|
If an alias is written, a column
|
||||||
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
|
|
||||||
alias list can also be written to provide substitute names for
|
alias list can also be written to provide substitute names for
|
||||||
one or more attributes of the function's composite return
|
one or more attributes of the function's composite return
|
||||||
type, including the column added by <literal>ORDINALITY</literal>
|
type, including the column added by <literal>ORDINALITY</literal>
|
||||||
if present.
|
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>
|
<para>
|
||||||
If the function has been defined as returning the
|
If the function has been defined as returning the
|
||||||
<type>record</> data type, then an alias or the key word
|
<type>record</> data type, then an alias or the key word
|
||||||
@ -402,7 +407,21 @@ SELECT * FROM unnest(ARRAY['a','b','c','d','e','f']) WITH ORDINALITY;
|
|||||||
class="parameter">data_type</replaceable> <optional>, ...
|
class="parameter">data_type</replaceable> <optional>, ...
|
||||||
</>)</literal>. The column definition list must match the
|
</>)</literal>. The column definition list must match the
|
||||||
actual number and types of columns returned by the function.
|
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>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -1598,6 +1617,23 @@ SELECT * FROM distributors_2(111) AS (f1 int, f2 text);
|
|||||||
</programlisting>
|
</programlisting>
|
||||||
</para>
|
</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>
|
<para>
|
||||||
This example shows how to use a simple <literal>WITH</> clause:
|
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
|
<productname>PostgreSQL</productname> treats <literal>UNNEST()</> the
|
||||||
same as other set-returning functions.
|
same as other set-returning functions.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Placing multiple function calls inside <literal>TABLE( ... )</> syntax is
|
||||||
|
also an extension of the SQL standard.
|
||||||
|
</para>
|
||||||
</refsect2>
|
</refsect2>
|
||||||
|
|
||||||
<refsect2>
|
<refsect2>
|
||||||
|
@ -157,40 +157,6 @@ CreateTupleDescCopy(TupleDesc tupdesc)
|
|||||||
return desc;
|
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
|
* CreateTupleDescCopyConstr
|
||||||
* This function creates a new TupleDesc by copying from an existing
|
* This function creates a new TupleDesc by copying from an existing
|
||||||
@ -250,6 +216,47 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
|
|||||||
return desc;
|
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
|
* Free a TupleDesc including all substructure
|
||||||
*/
|
*/
|
||||||
|
@ -1757,8 +1757,7 @@ find_expr_references_walker(Node *node,
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Add whole-relation refs for each plain relation mentioned in the
|
* Add whole-relation refs for each plain relation mentioned in the
|
||||||
* subquery's rtable, as well as refs for any datatypes and collations
|
* subquery's rtable.
|
||||||
* used in a RECORD function's output.
|
|
||||||
*
|
*
|
||||||
* Note: query_tree_walker takes care of recursing into RTE_FUNCTION
|
* Note: query_tree_walker takes care of recursing into RTE_FUNCTION
|
||||||
* RTEs, subqueries, etc, so no need to do that here. But keep it
|
* 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
|
* Note: we don't need to worry about collations mentioned in
|
||||||
* RTE_VALUES or RTE_CTE RTEs, because those must just duplicate
|
* 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)
|
foreach(lc, query->rtable)
|
||||||
{
|
{
|
||||||
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
|
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
|
||||||
ListCell *ct;
|
|
||||||
|
|
||||||
switch (rte->rtekind)
|
switch (rte->rtekind)
|
||||||
{
|
{
|
||||||
@ -1779,22 +1779,6 @@ find_expr_references_walker(Node *node,
|
|||||||
add_object_address(OCLASS_CLASS, rte->relid, 0,
|
add_object_address(OCLASS_CLASS, rte->relid, 0,
|
||||||
context->addrs);
|
context->addrs);
|
||||||
break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1863,6 +1847,30 @@ find_expr_references_walker(Node *node,
|
|||||||
find_expr_references_walker((Node *) setop->groupClauses, context);
|
find_expr_references_walker((Node *) setop->groupClauses, context);
|
||||||
/* fall through to examine child nodes */
|
/* 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,
|
return expression_tree_walker(node, find_expr_references_walker,
|
||||||
(void *) context);
|
(void *) context);
|
||||||
|
@ -319,6 +319,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
|
|||||||
col->collOid = attribute->attcollation;
|
col->collOid = attribute->attcollation;
|
||||||
col->constraints = NIL;
|
col->constraints = NIL;
|
||||||
col->fdwoptions = NIL;
|
col->fdwoptions = NIL;
|
||||||
|
col->location = -1;
|
||||||
|
|
||||||
coltype->names = NIL;
|
coltype->names = NIL;
|
||||||
coltype->typeOid = attribute->atttypid;
|
coltype->typeOid = attribute->atttypid;
|
||||||
|
@ -1259,9 +1259,21 @@ ExplainNode(PlanState *planstate, List *ancestors,
|
|||||||
break;
|
break;
|
||||||
case T_FunctionScan:
|
case T_FunctionScan:
|
||||||
if (es->verbose)
|
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,
|
"Function Call", planstate, ancestors,
|
||||||
es->verbose, es);
|
es->verbose, es);
|
||||||
|
}
|
||||||
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
|
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
|
||||||
if (plan->qual)
|
if (plan->qual)
|
||||||
show_instrumentation_count("Rows Removed by Filter", 1,
|
show_instrumentation_count("Rows Removed by Filter", 1,
|
||||||
@ -1984,27 +1996,32 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
|
|||||||
break;
|
break;
|
||||||
case T_FunctionScan:
|
case T_FunctionScan:
|
||||||
{
|
{
|
||||||
Node *funcexpr;
|
FunctionScan *fscan = (FunctionScan *) plan;
|
||||||
|
|
||||||
/* Assert it's on a RangeFunction */
|
/* Assert it's on a RangeFunction */
|
||||||
Assert(rte->rtekind == RTE_FUNCTION);
|
Assert(rte->rtekind == RTE_FUNCTION);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the expression is still a function call, we can get the
|
* If the expression is still a function call of a single
|
||||||
* real name of the function. Otherwise, punt (this can
|
* function, we can get the real name of the function.
|
||||||
* happen if the optimizer simplified away the function call,
|
* Otherwise, punt. (Even if it was a single function call
|
||||||
* for example).
|
* originally, the optimizer could have simplified it away.)
|
||||||
*/
|
*/
|
||||||
funcexpr = ((FunctionScan *) plan)->funcexpr;
|
if (list_length(fscan->functions) == 1)
|
||||||
if (funcexpr && IsA(funcexpr, FuncExpr))
|
|
||||||
{
|
{
|
||||||
Oid funcid = ((FuncExpr *) funcexpr)->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);
|
objectname = get_func_name(funcid);
|
||||||
if (es->verbose)
|
if (es->verbose)
|
||||||
namespace =
|
namespace =
|
||||||
get_namespace_name(get_func_namespace(funcid));
|
get_namespace_name(get_func_namespace(funcid));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
objecttag = "Function Name";
|
objecttag = "Function Name";
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -143,6 +143,7 @@ DefineSequence(CreateSeqStmt *seq)
|
|||||||
coldef->collClause = NULL;
|
coldef->collClause = NULL;
|
||||||
coldef->collOid = InvalidOid;
|
coldef->collOid = InvalidOid;
|
||||||
coldef->constraints = NIL;
|
coldef->constraints = NIL;
|
||||||
|
coldef->location = -1;
|
||||||
|
|
||||||
null[i - 1] = false;
|
null[i - 1] = false;
|
||||||
|
|
||||||
|
@ -1605,6 +1605,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
|
|||||||
def->collClause = NULL;
|
def->collClause = NULL;
|
||||||
def->collOid = attribute->attcollation;
|
def->collOid = attribute->attcollation;
|
||||||
def->constraints = NIL;
|
def->constraints = NIL;
|
||||||
|
def->location = -1;
|
||||||
inhSchema = lappend(inhSchema, def);
|
inhSchema = lappend(inhSchema, def);
|
||||||
newattno[parent_attno - 1] = ++child_attno;
|
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_local = true;
|
||||||
cdef->is_not_null = true;
|
cdef->is_not_null = true;
|
||||||
cdef->storage = 0;
|
cdef->storage = 0;
|
||||||
|
cdef->location = -1;
|
||||||
cmd->def = (Node *) cdef;
|
cmd->def = (Node *) cdef;
|
||||||
}
|
}
|
||||||
ATPrepAddColumn(wqueue, rel, recurse, false, cmd, lockmode);
|
ATPrepAddColumn(wqueue, rel, recurse, false, cmd, lockmode);
|
||||||
|
@ -100,6 +100,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
|
|||||||
def->cooked_default = NULL;
|
def->cooked_default = NULL;
|
||||||
def->collClause = NULL;
|
def->collClause = NULL;
|
||||||
def->collOid = exprCollation((Node *) tle->expr);
|
def->collOid = exprCollation((Node *) tle->expr);
|
||||||
|
def->location = -1;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* It's possible that the column is of a collatable type but the
|
* It's possible that the column is of a collatable type but the
|
||||||
|
@ -22,13 +22,30 @@
|
|||||||
*/
|
*/
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include "catalog/pg_type.h"
|
||||||
#include "executor/nodeFunctionscan.h"
|
#include "executor/nodeFunctionscan.h"
|
||||||
#include "funcapi.h"
|
#include "funcapi.h"
|
||||||
#include "nodes/nodeFuncs.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);
|
static TupleTableSlot *FunctionNext(FunctionScanState *node);
|
||||||
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------
|
/* ----------------------------------------------------------------
|
||||||
* Scan Support
|
* Scan Support
|
||||||
* ----------------------------------------------------------------
|
* ----------------------------------------------------------------
|
||||||
@ -44,107 +61,182 @@ FunctionNext(FunctionScanState *node)
|
|||||||
{
|
{
|
||||||
EState *estate;
|
EState *estate;
|
||||||
ScanDirection direction;
|
ScanDirection direction;
|
||||||
Tuplestorestate *tuplestorestate;
|
|
||||||
TupleTableSlot *scanslot;
|
TupleTableSlot *scanslot;
|
||||||
TupleTableSlot *funcslot;
|
bool alldone;
|
||||||
|
int64 oldpos;
|
||||||
if (node->func_slot)
|
int funcno;
|
||||||
{
|
int att;
|
||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* get information from the estate and scan state
|
* get information from the estate and scan state
|
||||||
*/
|
*/
|
||||||
estate = node->ss.ps.state;
|
estate = node->ss.ps.state;
|
||||||
direction = estate->es_direction;
|
direction = estate->es_direction;
|
||||||
|
scanslot = node->ss.ss_ScanTupleSlot;
|
||||||
|
|
||||||
tuplestorestate = node->tuplestorestate;
|
if (node->simple)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Fast path for the trivial case: the function return type and scan
|
||||||
|
* result type are the same, so we fetch the function result straight
|
||||||
|
* into the scan result slot. No need to update ordinality or
|
||||||
|
* rowcounts either.
|
||||||
|
*/
|
||||||
|
Tuplestorestate *tstore = node->funcstates[0].tstore;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If first time through, read all tuples from function and put them in a
|
* If first time through, read all tuples from function and put them
|
||||||
* tuplestore. Subsequent calls just fetch tuples from tuplestore.
|
* in a tuplestore. Subsequent calls just fetch tuples from
|
||||||
|
* tuplestore.
|
||||||
*/
|
*/
|
||||||
if (tuplestorestate == NULL)
|
if (tstore == NULL)
|
||||||
{
|
{
|
||||||
node->tuplestorestate = tuplestorestate =
|
node->funcstates[0].tstore = tstore =
|
||||||
ExecMakeTableFunctionResult(node->funcexpr,
|
ExecMakeTableFunctionResult(node->funcstates[0].funcexpr,
|
||||||
node->ss.ps.ps_ExprContext,
|
node->ss.ps.ps_ExprContext,
|
||||||
node->func_tupdesc,
|
node->funcstates[0].tupdesc,
|
||||||
node->eflags & EXEC_FLAG_BACKWARD);
|
node->eflags & EXEC_FLAG_BACKWARD);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* paranoia - cope if the function, which may have constructed the
|
||||||
|
* tuplestore itself, didn't leave it pointing at the start. This
|
||||||
|
* call is fast, so the overhead shouldn't be an issue.
|
||||||
|
*/
|
||||||
|
tuplestore_rescan(tstore);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the next tuple from tuplestore. Return NULL if no more tuples.
|
* Get the next tuple from tuplestore.
|
||||||
*/
|
*/
|
||||||
(void) tuplestore_gettupleslot(tuplestorestate,
|
(void) tuplestore_gettupleslot(tstore,
|
||||||
ScanDirectionIsForward(direction),
|
ScanDirectionIsForward(direction),
|
||||||
false,
|
false,
|
||||||
funcslot);
|
scanslot);
|
||||||
|
return scanslot;
|
||||||
if (!scanslot)
|
}
|
||||||
return funcslot;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* we're doing ordinality, so we copy the values from the function return
|
* Increment or decrement ordinal counter before checking for end-of-data,
|
||||||
* slot to the (distinct) scan slot. We can do this because the lifetimes
|
* so that we can move off either end of the result by 1 (and no more than
|
||||||
* of the values in each slot are the same; until we reset the scan or
|
* 1) without losing correct count. See PortalRunSelect for why we can
|
||||||
* fetch the next tuple, both will be valid.
|
* assume that we won't be called repeatedly in the end-of-data state.
|
||||||
*/
|
*/
|
||||||
|
oldpos = node->ordinal;
|
||||||
ExecClearTuple(scanslot);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* increment or decrement before checking for end-of-data, so that we can
|
|
||||||
* move off either end of the result by 1 (and no more than 1) without
|
|
||||||
* losing correct count. See PortalRunSelect for why we assume that we
|
|
||||||
* won't be called repeatedly in the end-of-data state.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (ScanDirectionIsForward(direction))
|
if (ScanDirectionIsForward(direction))
|
||||||
node->ordinal++;
|
node->ordinal++;
|
||||||
else
|
else
|
||||||
node->ordinal--;
|
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;
|
FunctionScanPerFuncState *fs = &node->funcstates[funcno];
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
slot_getallattrs(funcslot);
|
/*
|
||||||
|
* If first time through, read all tuples from function and put them
|
||||||
for (i = 0; i < natts; ++i)
|
* in a tuplestore. Subsequent calls just fetch tuples from
|
||||||
|
* tuplestore.
|
||||||
|
*/
|
||||||
|
if (fs->tstore == NULL)
|
||||||
{
|
{
|
||||||
scanslot->tts_values[i] = funcslot->tts_values[i];
|
fs->tstore =
|
||||||
scanslot->tts_isnull[i] = funcslot->tts_isnull[i];
|
ExecMakeTableFunctionResult(fs->funcexpr,
|
||||||
|
node->ss.ps.ps_ExprContext,
|
||||||
|
fs->tupdesc,
|
||||||
|
node->eflags & EXEC_FLAG_BACKWARD);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* paranoia - cope if the function, which may have constructed the
|
||||||
|
* tuplestore itself, didn't leave it pointing at the start. This
|
||||||
|
* call is fast, so the overhead shouldn't be an issue.
|
||||||
|
*/
|
||||||
|
tuplestore_rescan(fs->tstore);
|
||||||
}
|
}
|
||||||
|
|
||||||
scanslot->tts_values[natts] = Int64GetDatumFast(node->ordinal);
|
/*
|
||||||
scanslot->tts_isnull[natts] = false;
|
* Get the next tuple from tuplestore.
|
||||||
|
*
|
||||||
|
* If we have a rowcount for the function, and we know the previous
|
||||||
|
* read position was out of bounds, don't try the read. This allows
|
||||||
|
* backward scan to work when there are mixed row counts present.
|
||||||
|
*/
|
||||||
|
if (fs->rowcount != -1 && fs->rowcount < oldpos)
|
||||||
|
ExecClearTuple(fs->func_slot);
|
||||||
|
else
|
||||||
|
(void) tuplestore_gettupleslot(fs->tstore,
|
||||||
|
ScanDirectionIsForward(direction),
|
||||||
|
false,
|
||||||
|
fs->func_slot);
|
||||||
|
|
||||||
|
if (TupIsNull(fs->func_slot))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* If we ran out of data for this function in the forward
|
||||||
|
* direction then we now know how many rows it returned. We need
|
||||||
|
* to know this in order to handle backwards scans. The row count
|
||||||
|
* we store is actually 1+ the actual number, because we have to
|
||||||
|
* position the tuplestore 1 off its end sometimes.
|
||||||
|
*/
|
||||||
|
if (ScanDirectionIsForward(direction) && fs->rowcount == -1)
|
||||||
|
fs->rowcount = node->ordinal;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* populate the result cols with nulls
|
||||||
|
*/
|
||||||
|
for (i = 0; i < fs->colcount; i++)
|
||||||
|
{
|
||||||
|
scanslot->tts_values[att] = (Datum) 0;
|
||||||
|
scanslot->tts_isnull[att] = true;
|
||||||
|
att++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* we have a result, so just copy it to the result cols.
|
||||||
|
*/
|
||||||
|
slot_getallattrs(fs->func_slot);
|
||||||
|
|
||||||
|
for (i = 0; i < fs->colcount; i++)
|
||||||
|
{
|
||||||
|
scanslot->tts_values[att] = fs->func_slot->tts_values[i];
|
||||||
|
scanslot->tts_isnull[att] = fs->func_slot->tts_isnull[i];
|
||||||
|
att++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We're not done until every function result is exhausted; we pad
|
||||||
|
* the shorter results with nulls until then.
|
||||||
|
*/
|
||||||
|
alldone = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ordinal col is always last, per spec.
|
||||||
|
*/
|
||||||
|
if (node->ordinality)
|
||||||
|
{
|
||||||
|
scanslot->tts_values[att] = Int64GetDatumFast(node->ordinal);
|
||||||
|
scanslot->tts_isnull[att] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If alldone, we just return the previously-cleared scanslot. Otherwise,
|
||||||
|
* finish creating the virtual tuple.
|
||||||
|
*/
|
||||||
|
if (!alldone)
|
||||||
ExecStoreVirtualTuple(scanslot);
|
ExecStoreVirtualTuple(scanslot);
|
||||||
}
|
|
||||||
|
|
||||||
return scanslot;
|
return scanslot;
|
||||||
}
|
}
|
||||||
@ -184,10 +276,13 @@ FunctionScanState *
|
|||||||
ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
|
ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
|
||||||
{
|
{
|
||||||
FunctionScanState *scanstate;
|
FunctionScanState *scanstate;
|
||||||
Oid funcrettype;
|
RangeTblEntry *rte = rt_fetch(node->scan.scanrelid,
|
||||||
TypeFuncClass functypclass;
|
estate->es_range_table);
|
||||||
TupleDesc func_tupdesc = NULL;
|
int nfuncs = list_length(node->functions);
|
||||||
TupleDesc scan_tupdesc = NULL;
|
TupleDesc scan_tupdesc;
|
||||||
|
int i,
|
||||||
|
natts;
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
/* check for unsupported flags */
|
/* check for unsupported flags */
|
||||||
Assert(!(eflags & EXEC_FLAG_MARK));
|
Assert(!(eflags & EXEC_FLAG_MARK));
|
||||||
@ -206,6 +301,29 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
|
|||||||
scanstate->ss.ps.state = estate;
|
scanstate->ss.ps.state = estate;
|
||||||
scanstate->eflags = eflags;
|
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
|
* Miscellaneous initialization
|
||||||
*
|
*
|
||||||
@ -213,22 +331,14 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
|
|||||||
*/
|
*/
|
||||||
ExecAssignExprContext(estate, &scanstate->ss.ps);
|
ExecAssignExprContext(estate, &scanstate->ss.ps);
|
||||||
|
|
||||||
|
scanstate->ss.ps.ps_TupFromTlist = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* tuple table initialization
|
* tuple table initialization
|
||||||
*/
|
*/
|
||||||
ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
|
ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
|
||||||
ExecInitScanTupleSlot(estate, &scanstate->ss);
|
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
|
* initialize child expressions
|
||||||
*/
|
*/
|
||||||
@ -239,56 +349,75 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
|
|||||||
ExecInitExpr((Expr *) node->scan.plan.qual,
|
ExecInitExpr((Expr *) node->scan.plan.qual,
|
||||||
(PlanState *) scanstate);
|
(PlanState *) scanstate);
|
||||||
|
|
||||||
|
scanstate->funcstates = palloc(nfuncs * sizeof(FunctionScanPerFuncState));
|
||||||
|
|
||||||
|
natts = 0;
|
||||||
|
i = 0;
|
||||||
|
foreach(lc, node->functions)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Now determine if the function returns a simple or composite
|
* Don't allocate the tuplestores; the actual calls to the functions
|
||||||
* type, and build an appropriate tupdesc. This tupdesc
|
* do that. NULL means that we have not called the function yet (or
|
||||||
* (func_tupdesc) is the one that matches the shape of the
|
* need to call it again after a rescan).
|
||||||
* function result, no extra columns.
|
|
||||||
*/
|
*/
|
||||||
functypclass = get_expr_result_type(node->funcexpr,
|
fs->tstore = NULL;
|
||||||
|
fs->rowcount = -1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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,
|
&funcrettype,
|
||||||
&func_tupdesc);
|
&tupdesc);
|
||||||
|
|
||||||
if (functypclass == TYPEFUNC_COMPOSITE)
|
if (functypclass == TYPEFUNC_COMPOSITE)
|
||||||
{
|
{
|
||||||
/* Composite data type, e.g. a table's row type */
|
/* Composite data type, e.g. a table's row type */
|
||||||
Assert(func_tupdesc);
|
Assert(tupdesc);
|
||||||
|
Assert(tupdesc->natts >= colcount);
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Must copy it out of typcache for safety */
|
/* Must copy it out of typcache for safety */
|
||||||
func_tupdesc = CreateTupleDescCopy(func_tupdesc);
|
tupdesc = CreateTupleDescCopy(tupdesc);
|
||||||
}
|
}
|
||||||
else if (functypclass == TYPEFUNC_SCALAR)
|
else if (functypclass == TYPEFUNC_SCALAR)
|
||||||
{
|
{
|
||||||
/* Base data type, i.e. scalar */
|
/* Base data type, i.e. scalar */
|
||||||
char *attname = strVal(linitial(node->funccolnames));
|
tupdesc = CreateTemplateTupleDesc(1, false);
|
||||||
|
TupleDescInitEntry(tupdesc,
|
||||||
func_tupdesc = CreateTemplateTupleDesc(1, false);
|
|
||||||
TupleDescInitEntry(func_tupdesc,
|
|
||||||
(AttrNumber) 1,
|
(AttrNumber) 1,
|
||||||
attname,
|
NULL, /* don't care about the name here */
|
||||||
funcrettype,
|
funcrettype,
|
||||||
-1,
|
-1,
|
||||||
0);
|
0);
|
||||||
TupleDescInitEntryCollation(func_tupdesc,
|
TupleDescInitEntryCollation(tupdesc,
|
||||||
(AttrNumber) 1,
|
(AttrNumber) 1,
|
||||||
exprCollation(node->funcexpr));
|
exprCollation(funcexpr));
|
||||||
}
|
}
|
||||||
else if (functypclass == TYPEFUNC_RECORD)
|
else if (functypclass == TYPEFUNC_RECORD)
|
||||||
{
|
{
|
||||||
func_tupdesc = BuildDescFromLists(node->funccolnames,
|
tupdesc = BuildDescFromLists(rtfunc->funccolnames,
|
||||||
node->funccoltypes,
|
rtfunc->funccoltypes,
|
||||||
node->funccoltypmods,
|
rtfunc->funccoltypmods,
|
||||||
node->funccolcollations);
|
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
|
else
|
||||||
{
|
{
|
||||||
@ -296,56 +425,89 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
|
|||||||
elog(ERROR, "function in FROM has unsupported return type");
|
elog(ERROR, "function in FROM has unsupported return type");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
fs->tupdesc = tupdesc;
|
||||||
* For RECORD results, make sure a typmod has been assigned. (The
|
fs->colcount = colcount;
|
||||||
* function should do this for itself, but let's cover things in case it
|
|
||||||
* doesn't.)
|
|
||||||
*/
|
|
||||||
BlessTupleDesc(func_tupdesc);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If doing ordinality, we need a new tupdesc with one additional column
|
* We only need separate slots for the function results if we are
|
||||||
* tacked on, always of type "bigint". The name to use has already been
|
* doing ordinality or multiple functions; otherwise, we'll fetch
|
||||||
* recorded by the parser as the last element of funccolnames.
|
* function results directly into the scan slot.
|
||||||
*
|
|
||||||
* Without ordinality, the scan result tupdesc is the same as the
|
|
||||||
* function result tupdesc. (No need to make a copy.)
|
|
||||||
*/
|
*/
|
||||||
|
if (!scanstate->simple)
|
||||||
|
{
|
||||||
|
fs->func_slot = ExecInitExtraTupleSlot(estate);
|
||||||
|
ExecSetSlotDescriptor(fs->func_slot, fs->tupdesc);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
fs->func_slot = NULL;
|
||||||
|
|
||||||
|
natts += colcount;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the combined TupleDesc
|
||||||
|
*
|
||||||
|
* 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 (scanstate->simple)
|
||||||
|
{
|
||||||
|
scan_tupdesc = CreateTupleDescCopy(scanstate->funcstates[0].tupdesc);
|
||||||
|
scan_tupdesc->tdtypeid = RECORDOID;
|
||||||
|
scan_tupdesc->tdtypmod = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AttrNumber attno = 0;
|
||||||
|
|
||||||
|
if (node->funcordinality)
|
||||||
|
natts++;
|
||||||
|
|
||||||
|
scan_tupdesc = CreateTemplateTupleDesc(natts, false);
|
||||||
|
|
||||||
|
for (i = 0; i < nfuncs; i++)
|
||||||
|
{
|
||||||
|
TupleDesc tupdesc = scanstate->funcstates[i].tupdesc;
|
||||||
|
int colcount = scanstate->funcstates[i].colcount;
|
||||||
|
int j;
|
||||||
|
|
||||||
|
for (j = 1; j <= colcount; j++)
|
||||||
|
TupleDescCopyEntry(scan_tupdesc, ++attno, tupdesc, j);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If doing ordinality, add a column of type "bigint" at the end */
|
||||||
if (node->funcordinality)
|
if (node->funcordinality)
|
||||||
{
|
{
|
||||||
int natts = func_tupdesc->natts;
|
|
||||||
|
|
||||||
scan_tupdesc = CreateTupleDescCopyExtend(func_tupdesc, 1);
|
|
||||||
|
|
||||||
TupleDescInitEntry(scan_tupdesc,
|
TupleDescInitEntry(scan_tupdesc,
|
||||||
natts + 1,
|
++attno,
|
||||||
strVal(llast(node->funccolnames)),
|
NULL, /* don't care about the name here */
|
||||||
INT8OID,
|
INT8OID,
|
||||||
-1,
|
-1,
|
||||||
0);
|
0);
|
||||||
|
|
||||||
BlessTupleDesc(scan_tupdesc);
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
scan_tupdesc = func_tupdesc;
|
|
||||||
|
|
||||||
scanstate->scan_tupdesc = scan_tupdesc;
|
Assert(attno == natts);
|
||||||
scanstate->func_tupdesc = func_tupdesc;
|
}
|
||||||
ExecAssignScanType(&scanstate->ss, scan_tupdesc);
|
|
||||||
|
|
||||||
if (scanstate->func_slot)
|
|
||||||
ExecSetSlotDescriptor(scanstate->func_slot, func_tupdesc);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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;
|
i = 0;
|
||||||
scanstate->tuplestorestate = NULL;
|
foreach(lc, rte->eref->colnames)
|
||||||
|
{
|
||||||
|
char *attname = strVal(lfirst(lc));
|
||||||
|
|
||||||
scanstate->funcexpr = ExecInitExpr((Expr *) node->funcexpr,
|
if (i >= scan_tupdesc->natts)
|
||||||
(PlanState *) scanstate);
|
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.
|
* Initialize result tuple type and projection info.
|
||||||
@ -365,6 +527,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
|
|||||||
void
|
void
|
||||||
ExecEndFunctionScan(FunctionScanState *node)
|
ExecEndFunctionScan(FunctionScanState *node)
|
||||||
{
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Free the exprcontext
|
* Free the exprcontext
|
||||||
*/
|
*/
|
||||||
@ -375,15 +539,23 @@ ExecEndFunctionScan(FunctionScanState *node)
|
|||||||
*/
|
*/
|
||||||
ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
|
ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
|
||||||
ExecClearTuple(node->ss.ss_ScanTupleSlot);
|
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)
|
for (i = 0; i < node->nfuncs; i++)
|
||||||
tuplestore_end(node->tuplestorestate);
|
{
|
||||||
node->tuplestorestate = NULL;
|
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
|
void
|
||||||
ExecReScanFunctionScan(FunctionScanState *node)
|
ExecReScanFunctionScan(FunctionScanState *node)
|
||||||
{
|
{
|
||||||
|
FunctionScan *scan = (FunctionScan *) node->ss.ps.plan;
|
||||||
|
int i;
|
||||||
|
Bitmapset *chgparam = node->ss.ps.chgParam;
|
||||||
|
|
||||||
ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
|
ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
|
||||||
if (node->func_slot)
|
for (i = 0; i < node->nfuncs; i++)
|
||||||
ExecClearTuple(node->func_slot);
|
{
|
||||||
|
FunctionScanPerFuncState *fs = &node->funcstates[i];
|
||||||
|
|
||||||
|
if (fs->func_slot)
|
||||||
|
ExecClearTuple(fs->func_slot);
|
||||||
|
}
|
||||||
|
|
||||||
ExecScanReScan(&node->ss);
|
ExecScanReScan(&node->ss);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Here we have a choice whether to drop the tuplestores (and recompute
|
||||||
|
* the function outputs) or just rescan them. We must recompute if an
|
||||||
|
* expression contains changed parameters, else we rescan.
|
||||||
|
*
|
||||||
|
* XXX maybe we should recompute if the function is volatile? But in
|
||||||
|
* general the executor doesn't conditionalize its actions on that.
|
||||||
|
*/
|
||||||
|
if (chgparam)
|
||||||
|
{
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
foreach(lc, scan->functions)
|
||||||
|
{
|
||||||
|
RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
|
||||||
|
|
||||||
|
if (bms_overlap(chgparam, rtfunc->funcparams))
|
||||||
|
{
|
||||||
|
if (node->funcstates[i].tstore != NULL)
|
||||||
|
{
|
||||||
|
tuplestore_end(node->funcstates[i].tstore);
|
||||||
|
node->funcstates[i].tstore = NULL;
|
||||||
|
}
|
||||||
|
node->funcstates[i].rowcount = -1;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset ordinality counter */
|
||||||
node->ordinal = 0;
|
node->ordinal = 0;
|
||||||
|
|
||||||
/*
|
/* Make sure we rewind any remaining tuplestores */
|
||||||
* If we haven't materialized yet, just return.
|
for (i = 0; i < node->nfuncs; i++)
|
||||||
*/
|
|
||||||
if (!node->tuplestorestate)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Here we have a choice whether to drop the tuplestore (and recompute the
|
|
||||||
* function outputs) or just rescan it. We must recompute if the
|
|
||||||
* expression contains parameters, else we rescan. XXX maybe we should
|
|
||||||
* recompute if the function is volatile?
|
|
||||||
*/
|
|
||||||
if (node->ss.ps.chgParam != NULL)
|
|
||||||
{
|
{
|
||||||
tuplestore_end(node->tuplestorestate);
|
if (node->funcstates[i].tstore != NULL)
|
||||||
node->tuplestorestate = NULL;
|
tuplestore_rescan(node->funcstates[i].tstore);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
tuplestore_rescan(node->tuplestorestate);
|
|
||||||
}
|
}
|
||||||
|
@ -504,11 +504,7 @@ _copyFunctionScan(const FunctionScan *from)
|
|||||||
/*
|
/*
|
||||||
* copy remainder of node
|
* copy remainder of node
|
||||||
*/
|
*/
|
||||||
COPY_NODE_FIELD(funcexpr);
|
COPY_NODE_FIELD(functions);
|
||||||
COPY_NODE_FIELD(funccolnames);
|
|
||||||
COPY_NODE_FIELD(funccoltypes);
|
|
||||||
COPY_NODE_FIELD(funccoltypmods);
|
|
||||||
COPY_NODE_FIELD(funccolcollations);
|
|
||||||
COPY_SCALAR_FIELD(funcordinality);
|
COPY_SCALAR_FIELD(funcordinality);
|
||||||
|
|
||||||
return newnode;
|
return newnode;
|
||||||
@ -1981,10 +1977,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
|
|||||||
COPY_SCALAR_FIELD(security_barrier);
|
COPY_SCALAR_FIELD(security_barrier);
|
||||||
COPY_SCALAR_FIELD(jointype);
|
COPY_SCALAR_FIELD(jointype);
|
||||||
COPY_NODE_FIELD(joinaliasvars);
|
COPY_NODE_FIELD(joinaliasvars);
|
||||||
COPY_NODE_FIELD(funcexpr);
|
COPY_NODE_FIELD(functions);
|
||||||
COPY_NODE_FIELD(funccoltypes);
|
|
||||||
COPY_NODE_FIELD(funccoltypmods);
|
|
||||||
COPY_NODE_FIELD(funccolcollations);
|
|
||||||
COPY_SCALAR_FIELD(funcordinality);
|
COPY_SCALAR_FIELD(funcordinality);
|
||||||
COPY_NODE_FIELD(values_lists);
|
COPY_NODE_FIELD(values_lists);
|
||||||
COPY_NODE_FIELD(values_collations);
|
COPY_NODE_FIELD(values_collations);
|
||||||
@ -2007,6 +2000,22 @@ _copyRangeTblEntry(const RangeTblEntry *from)
|
|||||||
return newnode;
|
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 *
|
static WithCheckOption *
|
||||||
_copyWithCheckOption(const WithCheckOption *from)
|
_copyWithCheckOption(const WithCheckOption *from)
|
||||||
{
|
{
|
||||||
@ -2299,9 +2308,10 @@ _copyRangeFunction(const RangeFunction *from)
|
|||||||
{
|
{
|
||||||
RangeFunction *newnode = makeNode(RangeFunction);
|
RangeFunction *newnode = makeNode(RangeFunction);
|
||||||
|
|
||||||
COPY_SCALAR_FIELD(ordinality);
|
|
||||||
COPY_SCALAR_FIELD(lateral);
|
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(alias);
|
||||||
COPY_NODE_FIELD(coldeflist);
|
COPY_NODE_FIELD(coldeflist);
|
||||||
|
|
||||||
@ -2366,6 +2376,7 @@ _copyColumnDef(const ColumnDef *from)
|
|||||||
COPY_SCALAR_FIELD(collOid);
|
COPY_SCALAR_FIELD(collOid);
|
||||||
COPY_NODE_FIELD(constraints);
|
COPY_NODE_FIELD(constraints);
|
||||||
COPY_NODE_FIELD(fdwoptions);
|
COPY_NODE_FIELD(fdwoptions);
|
||||||
|
COPY_LOCATION_FIELD(location);
|
||||||
|
|
||||||
return newnode;
|
return newnode;
|
||||||
}
|
}
|
||||||
@ -4550,6 +4561,9 @@ copyObject(const void *from)
|
|||||||
case T_RangeTblEntry:
|
case T_RangeTblEntry:
|
||||||
retval = _copyRangeTblEntry(from);
|
retval = _copyRangeTblEntry(from);
|
||||||
break;
|
break;
|
||||||
|
case T_RangeTblFunction:
|
||||||
|
retval = _copyRangeTblFunction(from);
|
||||||
|
break;
|
||||||
case T_WithCheckOption:
|
case T_WithCheckOption:
|
||||||
retval = _copyWithCheckOption(from);
|
retval = _copyWithCheckOption(from);
|
||||||
break;
|
break;
|
||||||
|
@ -2140,9 +2140,10 @@ _equalRangeSubselect(const RangeSubselect *a, const RangeSubselect *b)
|
|||||||
static bool
|
static bool
|
||||||
_equalRangeFunction(const RangeFunction *a, const RangeFunction *b)
|
_equalRangeFunction(const RangeFunction *a, const RangeFunction *b)
|
||||||
{
|
{
|
||||||
COMPARE_SCALAR_FIELD(ordinality);
|
|
||||||
COMPARE_SCALAR_FIELD(lateral);
|
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(alias);
|
||||||
COMPARE_NODE_FIELD(coldeflist);
|
COMPARE_NODE_FIELD(coldeflist);
|
||||||
|
|
||||||
@ -2179,6 +2180,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
|
|||||||
COMPARE_SCALAR_FIELD(collOid);
|
COMPARE_SCALAR_FIELD(collOid);
|
||||||
COMPARE_NODE_FIELD(constraints);
|
COMPARE_NODE_FIELD(constraints);
|
||||||
COMPARE_NODE_FIELD(fdwoptions);
|
COMPARE_NODE_FIELD(fdwoptions);
|
||||||
|
COMPARE_LOCATION_FIELD(location);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -2245,10 +2247,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
|
|||||||
COMPARE_SCALAR_FIELD(security_barrier);
|
COMPARE_SCALAR_FIELD(security_barrier);
|
||||||
COMPARE_SCALAR_FIELD(jointype);
|
COMPARE_SCALAR_FIELD(jointype);
|
||||||
COMPARE_NODE_FIELD(joinaliasvars);
|
COMPARE_NODE_FIELD(joinaliasvars);
|
||||||
COMPARE_NODE_FIELD(funcexpr);
|
COMPARE_NODE_FIELD(functions);
|
||||||
COMPARE_NODE_FIELD(funccoltypes);
|
|
||||||
COMPARE_NODE_FIELD(funccoltypmods);
|
|
||||||
COMPARE_NODE_FIELD(funccolcollations);
|
|
||||||
COMPARE_SCALAR_FIELD(funcordinality);
|
COMPARE_SCALAR_FIELD(funcordinality);
|
||||||
COMPARE_NODE_FIELD(values_lists);
|
COMPARE_NODE_FIELD(values_lists);
|
||||||
COMPARE_NODE_FIELD(values_collations);
|
COMPARE_NODE_FIELD(values_collations);
|
||||||
@ -2271,6 +2270,20 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
|
|||||||
return true;
|
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
|
static bool
|
||||||
_equalWithCheckOption(const WithCheckOption *a, const WithCheckOption *b)
|
_equalWithCheckOption(const WithCheckOption *a, const WithCheckOption *b)
|
||||||
{
|
{
|
||||||
@ -3018,6 +3031,9 @@ equal(const void *a, const void *b)
|
|||||||
case T_RangeTblEntry:
|
case T_RangeTblEntry:
|
||||||
retval = _equalRangeTblEntry(a, b);
|
retval = _equalRangeTblEntry(a, b);
|
||||||
break;
|
break;
|
||||||
|
case T_RangeTblFunction:
|
||||||
|
retval = _equalRangeTblFunction(a, b);
|
||||||
|
break;
|
||||||
case T_WithCheckOption:
|
case T_WithCheckOption:
|
||||||
retval = _equalWithCheckOption(a, b);
|
retval = _equalWithCheckOption(a, b);
|
||||||
break;
|
break;
|
||||||
|
@ -122,14 +122,10 @@ makeVarFromTargetEntry(Index varno,
|
|||||||
* a rowtype; either a named composite type, or RECORD. This function
|
* a rowtype; either a named composite type, or RECORD. This function
|
||||||
* encapsulates the logic for determining the correct rowtype OID to use.
|
* 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
|
* returning a non-composite result type, we produce a normal Var referencing
|
||||||
* the function's result directly, instead of the single-column composite
|
* the function's result directly, instead of the single-column composite
|
||||||
* value that the whole-row notation might otherwise suggest.
|
* 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 *
|
Var *
|
||||||
makeWholeRowVar(RangeTblEntry *rte,
|
makeWholeRowVar(RangeTblEntry *rte,
|
||||||
@ -139,6 +135,7 @@ makeWholeRowVar(RangeTblEntry *rte,
|
|||||||
{
|
{
|
||||||
Var *result;
|
Var *result;
|
||||||
Oid toid;
|
Oid toid;
|
||||||
|
Node *fexpr;
|
||||||
|
|
||||||
switch (rte->rtekind)
|
switch (rte->rtekind)
|
||||||
{
|
{
|
||||||
@ -157,31 +154,27 @@ makeWholeRowVar(RangeTblEntry *rte,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case RTE_FUNCTION:
|
case RTE_FUNCTION:
|
||||||
/*
|
|
||||||
* RTE is a function with or without ordinality. We map the
|
|
||||||
* cases as follows:
|
|
||||||
*
|
|
||||||
* If ordinality is set, we return a composite var even if
|
|
||||||
* the function is a scalar. This var is always of RECORD type.
|
|
||||||
*
|
|
||||||
* If ordinality is not set but the function returns a row,
|
|
||||||
* we keep the function's return type.
|
|
||||||
*
|
|
||||||
* If the function is a scalar, we do what allowScalar requests.
|
|
||||||
*/
|
|
||||||
toid = exprType(rte->funcexpr);
|
|
||||||
|
|
||||||
if (rte->funcordinality)
|
/*
|
||||||
|
* If there's more than one function, or ordinality is requested,
|
||||||
|
* force a RECORD result, since there's certainly more than one
|
||||||
|
* column involved and it can't be a known named type.
|
||||||
|
*/
|
||||||
|
if (rte->funcordinality || list_length(rte->functions) != 1)
|
||||||
{
|
{
|
||||||
/* ORDINALITY always produces an anonymous RECORD result */
|
/* always produces an anonymous RECORD result */
|
||||||
result = makeVar(varno,
|
result = makeVar(varno,
|
||||||
InvalidAttrNumber,
|
InvalidAttrNumber,
|
||||||
RECORDOID,
|
RECORDOID,
|
||||||
-1,
|
-1,
|
||||||
InvalidOid,
|
InvalidOid,
|
||||||
varlevelsup);
|
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 */
|
/* func returns composite; same as relation case */
|
||||||
result = makeVar(varno,
|
result = makeVar(varno,
|
||||||
@ -198,7 +191,7 @@ makeWholeRowVar(RangeTblEntry *rte,
|
|||||||
1,
|
1,
|
||||||
toid,
|
toid,
|
||||||
-1,
|
-1,
|
||||||
exprCollation(rte->funcexpr),
|
exprCollation(fexpr),
|
||||||
varlevelsup);
|
varlevelsup);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -214,6 +207,7 @@ makeWholeRowVar(RangeTblEntry *rte,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* RTE is a join, subselect, or VALUES. We represent this as a
|
* 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
|
* 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 -
|
* makeFuncCall -
|
||||||
*
|
*
|
||||||
* Initialize a FuncCall struct with the information every caller must
|
* Initialize a FuncCall struct with the information every caller must
|
||||||
* supply. Any non-default parameters have to be handled by the
|
* supply. Any non-default parameters have to be inserted by the caller.
|
||||||
* caller.
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
FuncCall *
|
FuncCall *
|
||||||
makeFuncCall(List *name, List *args, int location)
|
makeFuncCall(List *name, List *args, int location)
|
||||||
{
|
{
|
||||||
FuncCall *n = makeNode(FuncCall);
|
FuncCall *n = makeNode(FuncCall);
|
||||||
|
|
||||||
n->funcname = name;
|
n->funcname = name;
|
||||||
n->args = args;
|
n->args = args;
|
||||||
n->location = location;
|
|
||||||
n->agg_order = NIL;
|
n->agg_order = NIL;
|
||||||
n->agg_filter = NULL;
|
n->agg_filter = NULL;
|
||||||
n->agg_star = FALSE;
|
n->agg_star = FALSE;
|
||||||
n->agg_distinct = FALSE;
|
n->agg_distinct = FALSE;
|
||||||
n->func_variadic = FALSE;
|
n->func_variadic = FALSE;
|
||||||
n->over = NULL;
|
n->over = NULL;
|
||||||
|
n->location = location;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
@ -1447,6 +1447,9 @@ exprLocation(const Node *expr)
|
|||||||
case T_TypeName:
|
case T_TypeName:
|
||||||
loc = ((const TypeName *) expr)->location;
|
loc = ((const TypeName *) expr)->location;
|
||||||
break;
|
break;
|
||||||
|
case T_ColumnDef:
|
||||||
|
loc = ((const ColumnDef *) expr)->location;
|
||||||
|
break;
|
||||||
case T_Constraint:
|
case T_Constraint:
|
||||||
loc = ((const Constraint *) expr)->location;
|
loc = ((const Constraint *) expr)->location;
|
||||||
break;
|
break;
|
||||||
@ -1901,6 +1904,8 @@ expression_tree_walker(Node *node,
|
|||||||
break;
|
break;
|
||||||
case T_PlaceHolderInfo:
|
case T_PlaceHolderInfo:
|
||||||
return walker(((PlaceHolderInfo *) node)->ph_var, context);
|
return walker(((PlaceHolderInfo *) node)->ph_var, context);
|
||||||
|
case T_RangeTblFunction:
|
||||||
|
return walker(((RangeTblFunction *) node)->funcexpr, context);
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "unrecognized node type: %d",
|
elog(ERROR, "unrecognized node type: %d",
|
||||||
(int) nodeTag(node));
|
(int) nodeTag(node));
|
||||||
@ -2000,7 +2005,7 @@ range_table_walker(List *rtable,
|
|||||||
return true;
|
return true;
|
||||||
break;
|
break;
|
||||||
case RTE_FUNCTION:
|
case RTE_FUNCTION:
|
||||||
if (walker(rte->funcexpr, context))
|
if (walker(rte->functions, context))
|
||||||
return true;
|
return true;
|
||||||
break;
|
break;
|
||||||
case RTE_VALUES:
|
case RTE_VALUES:
|
||||||
@ -2615,6 +2620,17 @@ expression_tree_mutator(Node *node,
|
|||||||
return (Node *) newnode;
|
return (Node *) newnode;
|
||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
elog(ERROR, "unrecognized node type: %d",
|
elog(ERROR, "unrecognized node type: %d",
|
||||||
(int) nodeTag(node));
|
(int) nodeTag(node));
|
||||||
@ -2725,7 +2741,7 @@ range_table_mutator(List *rtable,
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case RTE_FUNCTION:
|
case RTE_FUNCTION:
|
||||||
MUTATE(newrte->funcexpr, rte->funcexpr, Node *);
|
MUTATE(newrte->functions, rte->functions, List *);
|
||||||
break;
|
break;
|
||||||
case RTE_VALUES:
|
case RTE_VALUES:
|
||||||
MUTATE(newrte->values_lists, rte->values_lists, List *);
|
MUTATE(newrte->values_lists, rte->values_lists, List *);
|
||||||
@ -3113,10 +3129,12 @@ raw_expression_tree_walker(Node *node,
|
|||||||
{
|
{
|
||||||
RangeFunction *rf = (RangeFunction *) node;
|
RangeFunction *rf = (RangeFunction *) node;
|
||||||
|
|
||||||
if (walker(rf->funccallnode, context))
|
if (walker(rf->functions, context))
|
||||||
return true;
|
return true;
|
||||||
if (walker(rf->alias, context))
|
if (walker(rf->alias, context))
|
||||||
return true;
|
return true;
|
||||||
|
if (walker(rf->coldeflist, context))
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case T_TypeName:
|
case T_TypeName:
|
||||||
|
@ -516,11 +516,7 @@ _outFunctionScan(StringInfo str, const FunctionScan *node)
|
|||||||
|
|
||||||
_outScanInfo(str, (const Scan *) node);
|
_outScanInfo(str, (const Scan *) node);
|
||||||
|
|
||||||
WRITE_NODE_FIELD(funcexpr);
|
WRITE_NODE_FIELD(functions);
|
||||||
WRITE_NODE_FIELD(funccolnames);
|
|
||||||
WRITE_NODE_FIELD(funccoltypes);
|
|
||||||
WRITE_NODE_FIELD(funccoltypmods);
|
|
||||||
WRITE_NODE_FIELD(funccolcollations);
|
|
||||||
WRITE_BOOL_FIELD(funcordinality);
|
WRITE_BOOL_FIELD(funcordinality);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2154,6 +2150,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
|
|||||||
WRITE_OID_FIELD(collOid);
|
WRITE_OID_FIELD(collOid);
|
||||||
WRITE_NODE_FIELD(constraints);
|
WRITE_NODE_FIELD(constraints);
|
||||||
WRITE_NODE_FIELD(fdwoptions);
|
WRITE_NODE_FIELD(fdwoptions);
|
||||||
|
WRITE_LOCATION_FIELD(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -2382,10 +2379,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
|
|||||||
WRITE_NODE_FIELD(joinaliasvars);
|
WRITE_NODE_FIELD(joinaliasvars);
|
||||||
break;
|
break;
|
||||||
case RTE_FUNCTION:
|
case RTE_FUNCTION:
|
||||||
WRITE_NODE_FIELD(funcexpr);
|
WRITE_NODE_FIELD(functions);
|
||||||
WRITE_NODE_FIELD(funccoltypes);
|
|
||||||
WRITE_NODE_FIELD(funccoltypmods);
|
|
||||||
WRITE_NODE_FIELD(funccolcollations);
|
|
||||||
WRITE_BOOL_FIELD(funcordinality);
|
WRITE_BOOL_FIELD(funcordinality);
|
||||||
break;
|
break;
|
||||||
case RTE_VALUES:
|
case RTE_VALUES:
|
||||||
@ -2414,6 +2408,20 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
|
|||||||
WRITE_BITMAPSET_FIELD(modifiedCols);
|
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
|
static void
|
||||||
_outAExpr(StringInfo str, const A_Expr *node)
|
_outAExpr(StringInfo str, const A_Expr *node)
|
||||||
{
|
{
|
||||||
@ -2619,9 +2627,10 @@ _outRangeFunction(StringInfo str, const RangeFunction *node)
|
|||||||
{
|
{
|
||||||
WRITE_NODE_TYPE("RANGEFUNCTION");
|
WRITE_NODE_TYPE("RANGEFUNCTION");
|
||||||
|
|
||||||
WRITE_BOOL_FIELD(ordinality);
|
|
||||||
WRITE_BOOL_FIELD(lateral);
|
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(alias);
|
||||||
WRITE_NODE_FIELD(coldeflist);
|
WRITE_NODE_FIELD(coldeflist);
|
||||||
}
|
}
|
||||||
@ -3156,6 +3165,9 @@ _outNode(StringInfo str, const void *obj)
|
|||||||
case T_RangeTblEntry:
|
case T_RangeTblEntry:
|
||||||
_outRangeTblEntry(str, obj);
|
_outRangeTblEntry(str, obj);
|
||||||
break;
|
break;
|
||||||
|
case T_RangeTblFunction:
|
||||||
|
_outRangeTblFunction(str, obj);
|
||||||
|
break;
|
||||||
case T_A_Expr:
|
case T_A_Expr:
|
||||||
_outAExpr(str, obj);
|
_outAExpr(str, obj);
|
||||||
break;
|
break;
|
||||||
|
@ -1220,10 +1220,7 @@ _readRangeTblEntry(void)
|
|||||||
READ_NODE_FIELD(joinaliasvars);
|
READ_NODE_FIELD(joinaliasvars);
|
||||||
break;
|
break;
|
||||||
case RTE_FUNCTION:
|
case RTE_FUNCTION:
|
||||||
READ_NODE_FIELD(funcexpr);
|
READ_NODE_FIELD(functions);
|
||||||
READ_NODE_FIELD(funccoltypes);
|
|
||||||
READ_NODE_FIELD(funccoltypmods);
|
|
||||||
READ_NODE_FIELD(funccolcollations);
|
|
||||||
READ_BOOL_FIELD(funcordinality);
|
READ_BOOL_FIELD(funcordinality);
|
||||||
break;
|
break;
|
||||||
case RTE_VALUES:
|
case RTE_VALUES:
|
||||||
@ -1255,6 +1252,25 @@ _readRangeTblEntry(void)
|
|||||||
READ_DONE();
|
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
|
* parseNodeString
|
||||||
@ -1378,6 +1394,8 @@ parseNodeString(void)
|
|||||||
return_value = _readFromExpr();
|
return_value = _readFromExpr();
|
||||||
else if (MATCH("RTE", 3))
|
else if (MATCH("RTE", 3))
|
||||||
return_value = _readRangeTblEntry();
|
return_value = _readRangeTblEntry();
|
||||||
|
else if (MATCH("RANGETBLFUNCTION", 16))
|
||||||
|
return_value = _readRangeTblFunction();
|
||||||
else if (MATCH("NOTIFY", 6))
|
else if (MATCH("NOTIFY", 6))
|
||||||
return_value = _readNotifyStmt();
|
return_value = _readNotifyStmt();
|
||||||
else if (MATCH("DECLARECURSOR", 13))
|
else if (MATCH("DECLARECURSOR", 13))
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include "catalog/pg_class.h"
|
#include "catalog/pg_class.h"
|
||||||
|
#include "catalog/pg_operator.h"
|
||||||
#include "foreign/fdwapi.h"
|
#include "foreign/fdwapi.h"
|
||||||
#include "nodes/nodeFuncs.h"
|
#include "nodes/nodeFuncs.h"
|
||||||
#ifdef OPTIMIZER_DEBUG
|
#ifdef OPTIMIZER_DEBUG
|
||||||
@ -1258,6 +1259,7 @@ static void
|
|||||||
set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
|
set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
|
||||||
{
|
{
|
||||||
Relids required_outer;
|
Relids required_outer;
|
||||||
|
List *pathkeys = NIL;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We don't support pushing join clauses into the quals of a function
|
* 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;
|
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 */
|
/* 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...) */
|
/* Select cheapest path (pretty easy in this case...) */
|
||||||
set_cheapest(rel);
|
set_cheapest(rel);
|
||||||
|
@ -1076,9 +1076,9 @@ cost_functionscan(Path *path, PlannerInfo *root,
|
|||||||
path->rows = baserel->rows;
|
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
|
* completion before returning any rows, and caches the results in a
|
||||||
* tuplestore. So the function eval cost is all startup cost, and per-row
|
* tuplestore. So the function eval cost is all startup cost, and per-row
|
||||||
* costs are minimal.
|
* 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
|
* estimates for functions tend to be, there's not a lot of point in that
|
||||||
* refinement right now.
|
* 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;
|
startup_cost += exprcost.startup + exprcost.per_tuple;
|
||||||
|
|
||||||
@ -3845,14 +3845,26 @@ void
|
|||||||
set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel)
|
set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel)
|
||||||
{
|
{
|
||||||
RangeTblEntry *rte;
|
RangeTblEntry *rte;
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
/* Should only be applied to base relations that are functions */
|
/* Should only be applied to base relations that are functions */
|
||||||
Assert(rel->relid > 0);
|
Assert(rel->relid > 0);
|
||||||
rte = planner_rt_fetch(rel->relid, root);
|
rte = planner_rt_fetch(rel->relid, root);
|
||||||
Assert(rte->rtekind == RTE_FUNCTION);
|
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 */
|
/* Now estimate number of output rows, etc */
|
||||||
set_baserel_size_estimates(root, rel);
|
set_baserel_size_estimates(root, rel);
|
||||||
|
@ -501,6 +501,57 @@ build_index_pathkeys(PlannerInfo *root,
|
|||||||
return retval;
|
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
|
* convert_subquery_pathkeys
|
||||||
* Build a pathkeys list that describes the ordering of a subquery's
|
* Build a pathkeys list that describes the ordering of a subquery's
|
||||||
|
@ -115,9 +115,7 @@ static BitmapHeapScan *make_bitmap_heapscan(List *qptlist,
|
|||||||
static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
|
static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
|
||||||
List *tidquals);
|
List *tidquals);
|
||||||
static FunctionScan *make_functionscan(List *qptlist, List *qpqual,
|
static FunctionScan *make_functionscan(List *qptlist, List *qpqual,
|
||||||
Index scanrelid, Node *funcexpr, bool ordinality,
|
Index scanrelid, List *functions, bool funcordinality);
|
||||||
List *funccolnames, List *funccoltypes, List *funccoltypmods,
|
|
||||||
List *funccolcollations);
|
|
||||||
static ValuesScan *make_valuesscan(List *qptlist, List *qpqual,
|
static ValuesScan *make_valuesscan(List *qptlist, List *qpqual,
|
||||||
Index scanrelid, List *values_lists);
|
Index scanrelid, List *values_lists);
|
||||||
static CteScan *make_ctescan(List *qptlist, List *qpqual,
|
static CteScan *make_ctescan(List *qptlist, List *qpqual,
|
||||||
@ -1709,13 +1707,13 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
|
|||||||
FunctionScan *scan_plan;
|
FunctionScan *scan_plan;
|
||||||
Index scan_relid = best_path->parent->relid;
|
Index scan_relid = best_path->parent->relid;
|
||||||
RangeTblEntry *rte;
|
RangeTblEntry *rte;
|
||||||
Node *funcexpr;
|
List *functions;
|
||||||
|
|
||||||
/* it should be a function base rel... */
|
/* it should be a function base rel... */
|
||||||
Assert(scan_relid > 0);
|
Assert(scan_relid > 0);
|
||||||
rte = planner_rt_fetch(scan_relid, root);
|
rte = planner_rt_fetch(scan_relid, root);
|
||||||
Assert(rte->rtekind == RTE_FUNCTION);
|
Assert(rte->rtekind == RTE_FUNCTION);
|
||||||
funcexpr = rte->funcexpr;
|
functions = rte->functions;
|
||||||
|
|
||||||
/* Sort clauses into best execution order */
|
/* Sort clauses into best execution order */
|
||||||
scan_clauses = order_qual_clauses(root, scan_clauses);
|
scan_clauses = order_qual_clauses(root, scan_clauses);
|
||||||
@ -1728,17 +1726,12 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
|
|||||||
{
|
{
|
||||||
scan_clauses = (List *)
|
scan_clauses = (List *)
|
||||||
replace_nestloop_params(root, (Node *) scan_clauses);
|
replace_nestloop_params(root, (Node *) scan_clauses);
|
||||||
/* The func expression itself could contain nestloop params, too */
|
/* The function expressions could contain nestloop params, too */
|
||||||
funcexpr = replace_nestloop_params(root, funcexpr);
|
functions = (List *) replace_nestloop_params(root, (Node *) functions);
|
||||||
}
|
}
|
||||||
|
|
||||||
scan_plan = make_functionscan(tlist, scan_clauses, scan_relid,
|
scan_plan = make_functionscan(tlist, scan_clauses, scan_relid,
|
||||||
funcexpr,
|
functions, rte->funcordinality);
|
||||||
rte->funcordinality,
|
|
||||||
rte->eref->colnames,
|
|
||||||
rte->funccoltypes,
|
|
||||||
rte->funccoltypmods,
|
|
||||||
rte->funccolcollations);
|
|
||||||
|
|
||||||
copy_path_costsize(&scan_plan->scan.plan, best_path);
|
copy_path_costsize(&scan_plan->scan.plan, best_path);
|
||||||
|
|
||||||
@ -3388,12 +3381,8 @@ static FunctionScan *
|
|||||||
make_functionscan(List *qptlist,
|
make_functionscan(List *qptlist,
|
||||||
List *qpqual,
|
List *qpqual,
|
||||||
Index scanrelid,
|
Index scanrelid,
|
||||||
Node *funcexpr,
|
List *functions,
|
||||||
bool ordinality,
|
bool funcordinality)
|
||||||
List *funccolnames,
|
|
||||||
List *funccoltypes,
|
|
||||||
List *funccoltypmods,
|
|
||||||
List *funccolcollations)
|
|
||||||
{
|
{
|
||||||
FunctionScan *node = makeNode(FunctionScan);
|
FunctionScan *node = makeNode(FunctionScan);
|
||||||
Plan *plan = &node->scan.plan;
|
Plan *plan = &node->scan.plan;
|
||||||
@ -3404,12 +3393,8 @@ make_functionscan(List *qptlist,
|
|||||||
plan->lefttree = NULL;
|
plan->lefttree = NULL;
|
||||||
plan->righttree = NULL;
|
plan->righttree = NULL;
|
||||||
node->scan.scanrelid = scanrelid;
|
node->scan.scanrelid = scanrelid;
|
||||||
node->funcexpr = funcexpr;
|
node->functions = functions;
|
||||||
node->funcordinality = ordinality;
|
node->funcordinality = funcordinality;
|
||||||
node->funccolnames = funccolnames;
|
|
||||||
node->funccoltypes = funccoltypes;
|
|
||||||
node->funccoltypmods = funccoltypmods;
|
|
||||||
node->funccolcollations = funccolcollations;
|
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
@ -307,7 +307,7 @@ extract_lateral_references(PlannerInfo *root, RelOptInfo *brel, Index rtindex)
|
|||||||
if (rte->rtekind == RTE_SUBQUERY)
|
if (rte->rtekind == RTE_SUBQUERY)
|
||||||
vars = pull_vars_of_level((Node *) rte->subquery, 1);
|
vars = pull_vars_of_level((Node *) rte->subquery, 1);
|
||||||
else if (rte->rtekind == RTE_FUNCTION)
|
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)
|
else if (rte->rtekind == RTE_VALUES)
|
||||||
vars = pull_vars_of_level((Node *) rte->values_lists, 0);
|
vars = pull_vars_of_level((Node *) rte->values_lists, 0);
|
||||||
else
|
else
|
||||||
|
@ -485,9 +485,9 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
|
|||||||
}
|
}
|
||||||
else if (rte->rtekind == RTE_FUNCTION)
|
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;
|
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)
|
else if (rte->rtekind == RTE_VALUES)
|
||||||
{
|
{
|
||||||
|
@ -381,10 +381,7 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
|
|||||||
/* zap unneeded sub-structure */
|
/* zap unneeded sub-structure */
|
||||||
newrte->subquery = NULL;
|
newrte->subquery = NULL;
|
||||||
newrte->joinaliasvars = NIL;
|
newrte->joinaliasvars = NIL;
|
||||||
newrte->funcexpr = NULL;
|
newrte->functions = NIL;
|
||||||
newrte->funccoltypes = NIL;
|
|
||||||
newrte->funccoltypmods = NIL;
|
|
||||||
newrte->funccolcollations = NIL;
|
|
||||||
newrte->values_lists = NIL;
|
newrte->values_lists = NIL;
|
||||||
newrte->values_collations = NIL;
|
newrte->values_collations = NIL;
|
||||||
newrte->ctecoltypes = 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);
|
fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
|
||||||
splan->scan.plan.qual =
|
splan->scan.plan.qual =
|
||||||
fix_scan_list(root, splan->scan.plan.qual, rtoffset);
|
fix_scan_list(root, splan->scan.plan.qual, rtoffset);
|
||||||
splan->funcexpr =
|
splan->functions =
|
||||||
fix_scan_expr(root, splan->funcexpr, rtoffset);
|
fix_scan_list(root, splan->functions, rtoffset);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case T_ValuesScan:
|
case T_ValuesScan:
|
||||||
|
@ -2135,9 +2135,37 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case T_FunctionScan:
|
case T_FunctionScan:
|
||||||
finalize_primnode(((FunctionScan *) plan)->funcexpr,
|
{
|
||||||
&context);
|
FunctionScan *fscan = (FunctionScan *) plan;
|
||||||
context.paramids = bms_add_members(context.paramids, scan_params);
|
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;
|
break;
|
||||||
|
|
||||||
case T_ValuesScan:
|
case T_ValuesScan:
|
||||||
|
@ -580,10 +580,7 @@ inline_set_returning_functions(PlannerInfo *root)
|
|||||||
/* Successful expansion, replace the rtable entry */
|
/* Successful expansion, replace the rtable entry */
|
||||||
rte->rtekind = RTE_SUBQUERY;
|
rte->rtekind = RTE_SUBQUERY;
|
||||||
rte->subquery = funcquery;
|
rte->subquery = funcquery;
|
||||||
rte->funcexpr = NULL;
|
rte->functions = NIL;
|
||||||
rte->funccoltypes = NIL;
|
|
||||||
rte->funccoltypmods = NIL;
|
|
||||||
rte->funccolcollations = NIL;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1623,8 +1620,8 @@ replace_vars_in_jointree(Node *jtnode,
|
|||||||
context);
|
context);
|
||||||
break;
|
break;
|
||||||
case RTE_FUNCTION:
|
case RTE_FUNCTION:
|
||||||
rte->funcexpr =
|
rte->functions = (List *)
|
||||||
pullup_replace_vars(rte->funcexpr,
|
pullup_replace_vars((Node *) rte->functions,
|
||||||
context);
|
context);
|
||||||
break;
|
break;
|
||||||
case RTE_VALUES:
|
case RTE_VALUES:
|
||||||
|
@ -4509,6 +4509,7 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
|
|||||||
Query *
|
Query *
|
||||||
inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
|
inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
|
||||||
{
|
{
|
||||||
|
RangeTblFunction *rtfunc;
|
||||||
FuncExpr *fexpr;
|
FuncExpr *fexpr;
|
||||||
Oid func_oid;
|
Oid func_oid;
|
||||||
HeapTuple func_tuple;
|
HeapTuple func_tuple;
|
||||||
@ -4537,14 +4538,18 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
|
|||||||
*/
|
*/
|
||||||
check_stack_depth();
|
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)
|
if (rte->funcordinality)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/* Fail if FROM item isn't a simple FuncExpr */
|
/* Fail if RTE isn't a single, simple FuncExpr */
|
||||||
fexpr = (FuncExpr *) rte->funcexpr;
|
if (list_length(rte->functions) != 1)
|
||||||
if (fexpr == NULL || !IsA(fexpr, FuncExpr))
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
rtfunc = (RangeTblFunction *) linitial(rte->functions);
|
||||||
|
|
||||||
|
if (!IsA(rtfunc->funcexpr, FuncExpr))
|
||||||
|
return NULL;
|
||||||
|
fexpr = (FuncExpr *) rtfunc->funcexpr;
|
||||||
|
|
||||||
func_oid = fexpr->funcid;
|
func_oid = fexpr->funcid;
|
||||||
|
|
||||||
@ -4734,7 +4739,8 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
|
|||||||
*/
|
*/
|
||||||
if (fexpr->funcresulttype == RECORDOID &&
|
if (fexpr->funcresulttype == RECORDOID &&
|
||||||
get_func_result_type(func_oid, NULL, NULL) == TYPEFUNC_RECORD &&
|
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;
|
goto fail;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1623,7 +1623,7 @@ create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
*/
|
*/
|
||||||
Path *
|
Path *
|
||||||
create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
|
create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
|
||||||
Relids required_outer)
|
List *pathkeys, Relids required_outer)
|
||||||
{
|
{
|
||||||
Path *pathnode = makeNode(Path);
|
Path *pathnode = makeNode(Path);
|
||||||
|
|
||||||
@ -1631,7 +1631,7 @@ create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
pathnode->parent = rel;
|
pathnode->parent = rel;
|
||||||
pathnode->param_info = get_baserel_parampathinfo(root, rel,
|
pathnode->param_info = get_baserel_parampathinfo(root, rel,
|
||||||
required_outer);
|
required_outer);
|
||||||
pathnode->pathkeys = NIL; /* for now, assume unordered result */
|
pathnode->pathkeys = pathkeys;
|
||||||
|
|
||||||
cost_functionscan(pathnode, root, rel, pathnode->param_info);
|
cost_functionscan(pathnode, root, rel, pathnode->param_info);
|
||||||
|
|
||||||
|
@ -406,6 +406,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
|
|||||||
a_expr b_expr c_expr AexprConst indirection_el
|
a_expr b_expr c_expr AexprConst indirection_el
|
||||||
columnref in_expr having_clause func_table array_expr
|
columnref in_expr having_clause func_table array_expr
|
||||||
ExclusionWhereClause
|
ExclusionWhereClause
|
||||||
|
%type <list> func_table_item func_table_list opt_col_def_list
|
||||||
|
%type <boolean> opt_ordinality
|
||||||
%type <list> ExclusionConstraintList ExclusionConstraintElem
|
%type <list> ExclusionConstraintList ExclusionConstraintElem
|
||||||
%type <list> func_arg_list
|
%type <list> func_arg_list
|
||||||
%type <node> func_arg_expr
|
%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
|
%token NULLS_FIRST NULLS_LAST WITH_ORDINALITY WITH_TIME
|
||||||
|
|
||||||
|
|
||||||
/* Precedence: lowest to highest */
|
/* Precedence: lowest to highest */
|
||||||
%nonassoc SET /* see relation_expr_opt_alias */
|
%nonassoc SET /* see relation_expr_opt_alias */
|
||||||
%left UNION EXCEPT
|
%left UNION EXCEPT
|
||||||
@ -1926,10 +1929,11 @@ alter_table_cmd:
|
|||||||
n->subtype = AT_AlterColumnType;
|
n->subtype = AT_AlterColumnType;
|
||||||
n->name = $3;
|
n->name = $3;
|
||||||
n->def = (Node *) def;
|
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->typeName = $6;
|
||||||
def->collClause = (CollateClause *) $7;
|
def->collClause = (CollateClause *) $7;
|
||||||
def->raw_default = $8;
|
def->raw_default = $8;
|
||||||
|
def->location = @3;
|
||||||
$$ = (Node *)n;
|
$$ = (Node *)n;
|
||||||
}
|
}
|
||||||
/* ALTER FOREIGN TABLE <name> ALTER [COLUMN] <colname> OPTIONS */
|
/* ALTER FOREIGN TABLE <name> ALTER [COLUMN] <colname> OPTIONS */
|
||||||
@ -2354,10 +2358,11 @@ alter_type_cmd:
|
|||||||
n->name = $3;
|
n->name = $3;
|
||||||
n->def = (Node *) def;
|
n->def = (Node *) def;
|
||||||
n->behavior = $8;
|
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->typeName = $6;
|
||||||
def->collClause = (CollateClause *) $7;
|
def->collClause = (CollateClause *) $7;
|
||||||
def->raw_default = NULL;
|
def->raw_default = NULL;
|
||||||
|
def->location = @3;
|
||||||
$$ = (Node *)n;
|
$$ = (Node *)n;
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
@ -2782,6 +2787,7 @@ columnDef: ColId Typename create_generic_options ColQualList
|
|||||||
n->fdwoptions = $3;
|
n->fdwoptions = $3;
|
||||||
SplitColQualList($4, &n->constraints, &n->collClause,
|
SplitColQualList($4, &n->constraints, &n->collClause,
|
||||||
yyscanner);
|
yyscanner);
|
||||||
|
n->location = @1;
|
||||||
$$ = (Node *)n;
|
$$ = (Node *)n;
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
@ -2801,6 +2807,7 @@ columnOptions: ColId WITH OPTIONS ColQualList
|
|||||||
n->collOid = InvalidOid;
|
n->collOid = InvalidOid;
|
||||||
SplitColQualList($4, &n->constraints, &n->collClause,
|
SplitColQualList($4, &n->constraints, &n->collClause,
|
||||||
yyscanner);
|
yyscanner);
|
||||||
|
n->location = @1;
|
||||||
$$ = (Node *)n;
|
$$ = (Node *)n;
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
@ -9648,44 +9655,19 @@ table_ref: relation_expr opt_alias_clause
|
|||||||
}
|
}
|
||||||
| func_table func_alias_clause
|
| func_table func_alias_clause
|
||||||
{
|
{
|
||||||
RangeFunction *n = makeNode(RangeFunction);
|
RangeFunction *n = (RangeFunction *) $1;
|
||||||
n->lateral = false;
|
|
||||||
n->ordinality = false;
|
|
||||||
n->funccallnode = $1;
|
|
||||||
n->alias = linitial($2);
|
n->alias = linitial($2);
|
||||||
n->coldeflist = lsecond($2);
|
n->coldeflist = lsecond($2);
|
||||||
$$ = (Node *) n;
|
$$ = (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
|
| LATERAL_P func_table func_alias_clause
|
||||||
{
|
{
|
||||||
RangeFunction *n = makeNode(RangeFunction);
|
RangeFunction *n = (RangeFunction *) $2;
|
||||||
n->lateral = true;
|
n->lateral = true;
|
||||||
n->ordinality = false;
|
|
||||||
n->funccallnode = $2;
|
|
||||||
n->alias = linitial($3);
|
n->alias = linitial($3);
|
||||||
n->coldeflist = lsecond($3);
|
n->coldeflist = lsecond($3);
|
||||||
$$ = (Node *) n;
|
$$ = (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
|
| select_with_parens opt_alias_clause
|
||||||
{
|
{
|
||||||
RangeSubselect *n = makeNode(RangeSubselect);
|
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->collClause = (CollateClause *) $3;
|
||||||
n->collOid = InvalidOid;
|
n->collOid = InvalidOid;
|
||||||
n->constraints = NIL;
|
n->constraints = NIL;
|
||||||
|
n->location = @1;
|
||||||
$$ = (Node *)n;
|
$$ = (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
|
* 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
|
* looks like one". This isn't very important, but it saves us having to
|
||||||
* which variants are legal in the backwards-compatible functional-index syntax
|
* document which variants are legal in places like "FROM function()" or the
|
||||||
* for CREATE INDEX.
|
* backwards-compatible functional-index syntax for CREATE INDEX.
|
||||||
* (Note that many of the special SQL functions wouldn't actually make any
|
* (Note that many of the special SQL functions wouldn't actually make any
|
||||||
* sense as functional index entries, but we ignore that consideration here.)
|
* sense as functional index entries, but we ignore that consideration here.)
|
||||||
*/
|
*/
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include "optimizer/tlist.h"
|
#include "optimizer/tlist.h"
|
||||||
#include "parser/analyze.h"
|
#include "parser/analyze.h"
|
||||||
#include "parser/parsetree.h"
|
#include "parser/parsetree.h"
|
||||||
|
#include "parser/parser.h"
|
||||||
#include "parser/parse_clause.h"
|
#include "parser/parse_clause.h"
|
||||||
#include "parser/parse_coerce.h"
|
#include "parser/parse_coerce.h"
|
||||||
#include "parser/parse_collate.h"
|
#include "parser/parse_collate.h"
|
||||||
@ -515,24 +516,18 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
|
|||||||
static RangeTblEntry *
|
static RangeTblEntry *
|
||||||
transformRangeFunction(ParseState *pstate, RangeFunction *r)
|
transformRangeFunction(ParseState *pstate, RangeFunction *r)
|
||||||
{
|
{
|
||||||
Node *funcexpr;
|
List *funcexprs = NIL;
|
||||||
char *funcname;
|
List *funcnames = NIL;
|
||||||
|
List *coldeflists = NIL;
|
||||||
bool is_lateral;
|
bool is_lateral;
|
||||||
RangeTblEntry *rte;
|
RangeTblEntry *rte;
|
||||||
|
ListCell *lc;
|
||||||
/*
|
|
||||||
* 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);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We make lateral_only names of this level visible, whether or not the
|
* We make lateral_only names of this level visible, whether or not the
|
||||||
* function is explicitly marked LATERAL. This is needed for SQL spec
|
* RangeFunction is explicitly marked LATERAL. This is needed for SQL
|
||||||
* compliance in the case of UNNEST(), and seems useful on convenience
|
* spec compliance in the case of UNNEST(), and seems useful on
|
||||||
* grounds for all functions in FROM.
|
* convenience grounds for all functions in FROM.
|
||||||
*
|
*
|
||||||
* (LATERAL can't nest within a single pstate level, so we don't need
|
* (LATERAL can't nest within a single pstate level, so we don't need
|
||||||
* save/restore logic here.)
|
* save/restore logic here.)
|
||||||
@ -541,46 +536,171 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
|
|||||||
pstate->p_lateral_active = true;
|
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;
|
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
|
* Mark the RTE as LATERAL if the user said LATERAL explicitly, or if
|
||||||
* there are any lateral cross-references in it.
|
* 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.
|
* OK, build an RTE for the function.
|
||||||
*/
|
*/
|
||||||
rte = addRangeTableEntryForFunction(pstate, funcname, funcexpr,
|
rte = addRangeTableEntryForFunction(pstate,
|
||||||
|
funcnames, funcexprs, coldeflists,
|
||||||
r, is_lateral, true);
|
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;
|
return rte;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ static void expandRelation(Oid relid, Alias *eref,
|
|||||||
int location, bool include_dropped,
|
int location, bool include_dropped,
|
||||||
List **colnames, List **colvars);
|
List **colnames, List **colvars);
|
||||||
static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
|
static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
|
||||||
|
int count, int offset,
|
||||||
int rtindex, int sublevels_up,
|
int rtindex, int sublevels_up,
|
||||||
int location, bool include_dropped,
|
int location, bool include_dropped,
|
||||||
List **colnames, List **colvars);
|
List **colnames, List **colvars);
|
||||||
@ -807,25 +808,20 @@ markVarForSelectPriv(ParseState *pstate, Var *var, RangeTblEntry *rte)
|
|||||||
/*
|
/*
|
||||||
* buildRelationAliases
|
* buildRelationAliases
|
||||||
* Construct the eref column name list for a relation RTE.
|
* Construct the eref column name list for a relation RTE.
|
||||||
* This code is also used for the case of a function RTE returning
|
* This code is also used for function RTEs.
|
||||||
* a named composite type or a registered RECORD type.
|
|
||||||
*
|
*
|
||||||
* tupdesc: the physical column information
|
* tupdesc: the physical column information
|
||||||
* alias: the user-supplied alias, or NULL if none
|
* alias: the user-supplied alias, or NULL if none
|
||||||
* eref: the eref Alias to store column names in
|
* 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
|
* 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
|
* empty strings for any dropped columns, so that it will be one-to-one with
|
||||||
* physical column numbers.
|
* 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.
|
* It is an error for there to be more aliases present than required.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref, bool ordinality)
|
buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
|
||||||
{
|
{
|
||||||
int maxattrs = tupdesc->natts;
|
int maxattrs = tupdesc->natts;
|
||||||
ListCell *aliaslc;
|
ListCell *aliaslc;
|
||||||
@ -877,98 +873,56 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref, bool ordinali
|
|||||||
eref->colnames = lappend(eref->colnames, attrname);
|
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? */
|
/* Too many user-supplied aliases? */
|
||||||
if (aliaslc)
|
if (aliaslc)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
|
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
|
||||||
errmsg("table \"%s\" has %d columns available but %d columns specified",
|
errmsg("table \"%s\" has %d columns available but %d columns specified",
|
||||||
eref->aliasname,
|
eref->aliasname, maxattrs - numdropped, numaliases)));
|
||||||
maxattrs - numdropped + (ordinality ? 1 : 0),
|
|
||||||
numaliases)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* buildScalarFunctionAlias
|
* chooseScalarFunctionAlias
|
||||||
* Construct the eref column name list for a function RTE,
|
* Select the column alias for a function in a function RTE,
|
||||||
* when the function returns a scalar type (not composite or RECORD).
|
* when the function returns a scalar type (not composite or RECORD).
|
||||||
*
|
*
|
||||||
* funcexpr: transformed expression tree for the function call
|
* funcexpr: transformed expression tree for the function call
|
||||||
* funcname: function name (used only for error message)
|
* funcname: function name (as determined by FigureColname)
|
||||||
* alias: the user-supplied alias, or NULL if none
|
* alias: the user-supplied alias for the RTE, or NULL if none
|
||||||
* eref: the eref Alias to store column names in
|
* nfuncs: the number of functions appearing in the function RTE
|
||||||
* ordinality: whether to add an ordinality column
|
|
||||||
*
|
*
|
||||||
* eref->colnames is filled in.
|
* 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.
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
static void
|
static char *
|
||||||
buildScalarFunctionAlias(Node *funcexpr, char *funcname,
|
chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
|
||||||
Alias *alias, Alias *eref, bool ordinality)
|
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
|
|
||||||
{
|
|
||||||
char *pname = NULL;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the expression is a simple function call, and the function has a
|
* If the expression is a simple function call, and the function has a
|
||||||
* single OUT parameter that is named, use the parameter's name.
|
* single OUT parameter that is named, use the parameter's name.
|
||||||
*/
|
*/
|
||||||
if (funcexpr && IsA(funcexpr, FuncExpr))
|
if (funcexpr && IsA(funcexpr, FuncExpr))
|
||||||
|
{
|
||||||
pname = get_func_result_name(((FuncExpr *) funcexpr)->funcid);
|
pname = get_func_result_name(((FuncExpr *) funcexpr)->funcid);
|
||||||
|
if (pname)
|
||||||
/*
|
return pname;
|
||||||
* 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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If we don't have a name for the ordinality column yet, supply a default. */
|
/*
|
||||||
if (ordinality && list_length(eref->colnames) < 2)
|
* If there's just one function in the RTE, and the user gave an RTE alias
|
||||||
eref->colnames = lappend(eref->colnames, makeString(pstrdup("ordinality")));
|
* 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.
|
* and/or actual column names.
|
||||||
*/
|
*/
|
||||||
rte->eref = makeAlias(refname, NIL);
|
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
|
* 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.
|
* and/or actual column names.
|
||||||
*/
|
*/
|
||||||
rte->eref = makeAlias(refname, NIL);
|
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.
|
* Set flags and access permissions.
|
||||||
@ -1230,38 +1184,78 @@ 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.
|
* This is just like addRangeTableEntry() except that it makes a function RTE.
|
||||||
*/
|
*/
|
||||||
RangeTblEntry *
|
RangeTblEntry *
|
||||||
addRangeTableEntryForFunction(ParseState *pstate,
|
addRangeTableEntryForFunction(ParseState *pstate,
|
||||||
char *funcname,
|
List *funcnames,
|
||||||
Node *funcexpr,
|
List *funcexprs,
|
||||||
|
List *coldeflists,
|
||||||
RangeFunction *rangefunc,
|
RangeFunction *rangefunc,
|
||||||
bool lateral,
|
bool lateral,
|
||||||
bool inFromCl)
|
bool inFromCl)
|
||||||
{
|
{
|
||||||
RangeTblEntry *rte = makeNode(RangeTblEntry);
|
RangeTblEntry *rte = makeNode(RangeTblEntry);
|
||||||
TypeFuncClass functypclass;
|
|
||||||
Oid funcrettype;
|
|
||||||
TupleDesc tupdesc;
|
|
||||||
Alias *alias = rangefunc->alias;
|
Alias *alias = rangefunc->alias;
|
||||||
List *coldeflist = rangefunc->coldeflist;
|
|
||||||
Alias *eref;
|
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->rtekind = RTE_FUNCTION;
|
||||||
rte->relid = InvalidOid;
|
rte->relid = InvalidOid;
|
||||||
rte->subquery = NULL;
|
rte->subquery = NULL;
|
||||||
rte->funcexpr = funcexpr;
|
rte->functions = NIL; /* we'll fill this list below */
|
||||||
rte->funccoltypes = NIL;
|
rte->funcordinality = rangefunc->ordinality;
|
||||||
rte->funccoltypmods = NIL;
|
|
||||||
rte->funccolcollations = NIL;
|
|
||||||
rte->alias = alias;
|
rte->alias = alias;
|
||||||
|
|
||||||
eref = makeAlias(alias ? alias->aliasname : funcname, NIL);
|
/*
|
||||||
|
* Choose the RTE alias name. We default to using the first function's
|
||||||
|
* name even when there's more than one; which is maybe arguable but beats
|
||||||
|
* using something constant like "table".
|
||||||
|
*/
|
||||||
|
if (alias)
|
||||||
|
aliasname = alias->aliasname;
|
||||||
|
else
|
||||||
|
aliasname = linitial(funcnames);
|
||||||
|
|
||||||
|
eref = makeAlias(aliasname, NIL);
|
||||||
rte->eref = eref;
|
rte->eref = eref;
|
||||||
|
|
||||||
|
/* Process each function ... */
|
||||||
|
functupdescs = (TupleDesc *) palloc(nfuncs * sizeof(TupleDesc));
|
||||||
|
|
||||||
|
totalatts = 0;
|
||||||
|
funcno = 0;
|
||||||
|
forthree(lc1, funcexprs, lc2, funcnames, lc3, coldeflists)
|
||||||
|
{
|
||||||
|
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.
|
* Now determine if the function returns a simple or composite type.
|
||||||
*/
|
*/
|
||||||
@ -1270,8 +1264,8 @@ addRangeTableEntryForFunction(ParseState *pstate,
|
|||||||
&tupdesc);
|
&tupdesc);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A coldeflist is required if the function returns RECORD and hasn't got
|
* A coldeflist is required if the function returns RECORD and hasn't
|
||||||
* a predetermined record type, and is prohibited otherwise.
|
* got a predetermined record type, and is prohibited otherwise.
|
||||||
*/
|
*/
|
||||||
if (coldeflist != NIL)
|
if (coldeflist != NIL)
|
||||||
{
|
{
|
||||||
@ -1279,7 +1273,8 @@ addRangeTableEntryForFunction(ParseState *pstate,
|
|||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
errmsg("a column definition list is only allowed for functions returning \"record\""),
|
errmsg("a column definition list is only allowed for functions returning \"record\""),
|
||||||
parser_errposition(pstate, exprLocation(funcexpr))));
|
parser_errposition(pstate,
|
||||||
|
exprLocation((Node *) coldeflist))));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1294,28 +1289,29 @@ addRangeTableEntryForFunction(ParseState *pstate,
|
|||||||
{
|
{
|
||||||
/* Composite data type, e.g. a table's row type */
|
/* Composite data type, e.g. a table's row type */
|
||||||
Assert(tupdesc);
|
Assert(tupdesc);
|
||||||
/* Build the column alias list */
|
|
||||||
buildRelationAliases(tupdesc, alias, eref, rangefunc->ordinality);
|
|
||||||
}
|
}
|
||||||
else if (functypclass == TYPEFUNC_SCALAR)
|
else if (functypclass == TYPEFUNC_SCALAR)
|
||||||
{
|
{
|
||||||
/* Base data type, i.e. scalar */
|
/* Base data type, i.e. scalar */
|
||||||
buildScalarFunctionAlias(funcexpr, funcname, alias, eref, rangefunc->ordinality);
|
tupdesc = CreateTemplateTupleDesc(1, false);
|
||||||
|
TupleDescInitEntry(tupdesc,
|
||||||
|
(AttrNumber) 1,
|
||||||
|
chooseScalarFunctionAlias(funcexpr, funcname,
|
||||||
|
alias, nfuncs),
|
||||||
|
funcrettype,
|
||||||
|
-1,
|
||||||
|
0);
|
||||||
}
|
}
|
||||||
else if (functypclass == TYPEFUNC_RECORD)
|
else if (functypclass == TYPEFUNC_RECORD)
|
||||||
{
|
{
|
||||||
ListCell *col;
|
ListCell *col;
|
||||||
|
|
||||||
if (rangefunc->ordinality)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
||||||
errmsg("WITH ORDINALITY is not supported for functions returning \"record\""),
|
|
||||||
parser_errposition(pstate, exprLocation(funcexpr))));
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Use the column definition list to form the alias list and
|
* Use the column definition list to construct a tupdesc and fill
|
||||||
* funccoltypes/funccoltypmods/funccolcollations lists.
|
* in the RangeTblFunction's lists.
|
||||||
*/
|
*/
|
||||||
|
tupdesc = CreateTemplateTupleDesc(list_length(coldeflist), false);
|
||||||
|
i = 1;
|
||||||
foreach(col, coldeflist)
|
foreach(col, coldeflist)
|
||||||
{
|
{
|
||||||
ColumnDef *n = (ColumnDef *) lfirst(col);
|
ColumnDef *n = (ColumnDef *) lfirst(col);
|
||||||
@ -1324,21 +1320,42 @@ addRangeTableEntryForFunction(ParseState *pstate,
|
|||||||
int32 attrtypmod;
|
int32 attrtypmod;
|
||||||
Oid attrcollation;
|
Oid attrcollation;
|
||||||
|
|
||||||
attrname = pstrdup(n->colname);
|
attrname = n->colname;
|
||||||
if (n->typeName->setof)
|
if (n->typeName->setof)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
|
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
|
||||||
errmsg("column \"%s\" cannot be declared SETOF",
|
errmsg("column \"%s\" cannot be declared SETOF",
|
||||||
attrname),
|
attrname),
|
||||||
parser_errposition(pstate, n->typeName->location)));
|
parser_errposition(pstate, n->location)));
|
||||||
typenameTypeIdAndMod(pstate, n->typeName, &attrtype, &attrtypmod);
|
typenameTypeIdAndMod(pstate, n->typeName,
|
||||||
|
&attrtype, &attrtypmod);
|
||||||
attrcollation = GetColumnDefCollation(pstate, n, attrtype);
|
attrcollation = GetColumnDefCollation(pstate, n, attrtype);
|
||||||
eref->colnames = lappend(eref->colnames, makeString(attrname));
|
TupleDescInitEntry(tupdesc,
|
||||||
rte->funccoltypes = lappend_oid(rte->funccoltypes, attrtype);
|
(AttrNumber) i,
|
||||||
rte->funccoltypmods = lappend_int(rte->funccoltypmods, attrtypmod);
|
attrname,
|
||||||
rte->funccolcollations = lappend_oid(rte->funccolcollations,
|
attrtype,
|
||||||
|
attrtypmod,
|
||||||
|
0);
|
||||||
|
TupleDescInitEntryCollation(tupdesc,
|
||||||
|
(AttrNumber) i,
|
||||||
attrcollation);
|
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
|
else
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
@ -1347,6 +1364,54 @@ addRangeTableEntryForFunction(ParseState *pstate,
|
|||||||
funcname, format_type_be(funcrettype)),
|
funcname, format_type_be(funcrettype)),
|
||||||
parser_errposition(pstate, exprLocation(funcexpr))));
|
parser_errposition(pstate, exprLocation(funcexpr))));
|
||||||
|
|
||||||
|
/* Finish off the RangeTblFunction and add it to the RTE's list */
|
||||||
|
rtfunc->funccolcount = tupdesc->natts;
|
||||||
|
rte->functions = lappend(rte->functions, rtfunc);
|
||||||
|
|
||||||
|
/* Save the tupdesc for use below */
|
||||||
|
functupdescs[funcno] = tupdesc;
|
||||||
|
totalatts += tupdesc->natts;
|
||||||
|
funcno++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If there's more than one function, or we want an ordinality column, we
|
||||||
|
* have to produce a merged tupdesc.
|
||||||
|
*/
|
||||||
|
if (nfuncs > 1 || rangefunc->ordinality)
|
||||||
|
{
|
||||||
|
if (rangefunc->ordinality)
|
||||||
|
totalatts++;
|
||||||
|
|
||||||
|
/* Merge the tuple descs of each function into a composite one */
|
||||||
|
tupdesc = CreateTemplateTupleDesc(totalatts, false);
|
||||||
|
natts = 0;
|
||||||
|
for (i = 0; i < nfuncs; i++)
|
||||||
|
{
|
||||||
|
for (j = 1; j <= functupdescs[i]->natts; j++)
|
||||||
|
TupleDescCopyEntry(tupdesc, ++natts, functupdescs[i], j);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add the ordinality column if needed */
|
||||||
|
if (rangefunc->ordinality)
|
||||||
|
TupleDescInitEntry(tupdesc,
|
||||||
|
(AttrNumber) ++natts,
|
||||||
|
"ordinality",
|
||||||
|
INT8OID,
|
||||||
|
-1,
|
||||||
|
0);
|
||||||
|
|
||||||
|
Assert(natts == totalatts);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* We can just use the single function's tupdesc as-is */
|
||||||
|
tupdesc = functupdescs[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Use the tupdesc while assigning column aliases for the RTE */
|
||||||
|
buildRelationAliases(tupdesc, alias, eref);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set flags and access permissions.
|
* Set flags and access permissions.
|
||||||
*
|
*
|
||||||
@ -1354,7 +1419,6 @@ addRangeTableEntryForFunction(ParseState *pstate,
|
|||||||
* permissions mechanism).
|
* permissions mechanism).
|
||||||
*/
|
*/
|
||||||
rte->lateral = lateral;
|
rte->lateral = lateral;
|
||||||
rte->funcordinality = rangefunc->ordinality;
|
|
||||||
rte->inh = false; /* never true for functions */
|
rte->inh = false; /* never true for functions */
|
||||||
rte->inFromCl = inFromCl;
|
rte->inFromCl = inFromCl;
|
||||||
|
|
||||||
@ -1710,11 +1774,6 @@ addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
|
|||||||
* The output lists go into *colnames and *colvars.
|
* The output lists go into *colnames and *colvars.
|
||||||
* If only one of the two kinds of output list is needed, pass NULL for the
|
* If only one of the two kinds of output list is needed, pass NULL for the
|
||||||
* output pointer for the unwanted one.
|
* 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
|
void
|
||||||
expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
|
expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
|
||||||
@ -1780,65 +1839,73 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
|
|||||||
case RTE_FUNCTION:
|
case RTE_FUNCTION:
|
||||||
{
|
{
|
||||||
/* Function RTE */
|
/* Function RTE */
|
||||||
|
int atts_done = 0;
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
|
foreach(lc, rte->functions)
|
||||||
|
{
|
||||||
|
RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
|
||||||
TypeFuncClass functypclass;
|
TypeFuncClass functypclass;
|
||||||
Oid funcrettype;
|
Oid funcrettype;
|
||||||
TupleDesc tupdesc;
|
TupleDesc tupdesc;
|
||||||
int ordinality_attno = 0;
|
|
||||||
|
|
||||||
functypclass = get_expr_result_type(rte->funcexpr,
|
functypclass = get_expr_result_type(rtfunc->funcexpr,
|
||||||
&funcrettype,
|
&funcrettype,
|
||||||
&tupdesc);
|
&tupdesc);
|
||||||
if (functypclass == TYPEFUNC_COMPOSITE)
|
if (functypclass == TYPEFUNC_COMPOSITE)
|
||||||
{
|
{
|
||||||
/* Composite data type, e.g. a table's row type */
|
/* Composite data type, e.g. a table's row type */
|
||||||
Assert(tupdesc);
|
Assert(tupdesc);
|
||||||
|
|
||||||
/*
|
|
||||||
* we rely here on the fact that expandTupleDesc doesn't
|
|
||||||
* care about being passed more aliases than it needs.
|
|
||||||
*/
|
|
||||||
expandTupleDesc(tupdesc, rte->eref,
|
expandTupleDesc(tupdesc, rte->eref,
|
||||||
|
rtfunc->funccolcount, atts_done,
|
||||||
rtindex, sublevels_up, location,
|
rtindex, sublevels_up, location,
|
||||||
include_dropped, colnames, colvars);
|
include_dropped, colnames, colvars);
|
||||||
|
|
||||||
ordinality_attno = tupdesc->natts + 1;
|
|
||||||
}
|
}
|
||||||
else if (functypclass == TYPEFUNC_SCALAR)
|
else if (functypclass == TYPEFUNC_SCALAR)
|
||||||
{
|
{
|
||||||
/* Base data type, i.e. scalar */
|
/* Base data type, i.e. scalar */
|
||||||
if (colnames)
|
if (colnames)
|
||||||
*colnames = lappend(*colnames,
|
*colnames = lappend(*colnames,
|
||||||
linitial(rte->eref->colnames));
|
list_nth(rte->eref->colnames,
|
||||||
|
atts_done));
|
||||||
|
|
||||||
if (colvars)
|
if (colvars)
|
||||||
{
|
{
|
||||||
Var *varnode;
|
Var *varnode;
|
||||||
|
|
||||||
varnode = makeVar(rtindex, 1,
|
varnode = makeVar(rtindex, atts_done + 1,
|
||||||
funcrettype, -1,
|
funcrettype, -1,
|
||||||
exprCollation(rte->funcexpr),
|
exprCollation(rtfunc->funcexpr),
|
||||||
sublevels_up);
|
sublevels_up);
|
||||||
varnode->location = location;
|
varnode->location = location;
|
||||||
|
|
||||||
*colvars = lappend(*colvars, varnode);
|
*colvars = lappend(*colvars, varnode);
|
||||||
}
|
}
|
||||||
|
|
||||||
ordinality_attno = 2;
|
|
||||||
}
|
}
|
||||||
else if (functypclass == TYPEFUNC_RECORD)
|
else if (functypclass == TYPEFUNC_RECORD)
|
||||||
{
|
{
|
||||||
if (colnames)
|
if (colnames)
|
||||||
*colnames = copyObject(rte->eref->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);
|
||||||
|
}
|
||||||
|
|
||||||
if (colvars)
|
if (colvars)
|
||||||
{
|
{
|
||||||
ListCell *l1;
|
ListCell *l1;
|
||||||
ListCell *l2;
|
ListCell *l2;
|
||||||
ListCell *l3;
|
ListCell *l3;
|
||||||
int attnum = 0;
|
int attnum = atts_done;
|
||||||
|
|
||||||
forthree(l1, rte->funccoltypes,
|
forthree(l1, rtfunc->funccoltypes,
|
||||||
l2, rte->funccoltypmods,
|
l2, rtfunc->funccoltypmods,
|
||||||
l3, rte->funccolcollations)
|
l3, rtfunc->funccolcollations)
|
||||||
{
|
{
|
||||||
Oid attrtype = lfirst_oid(l1);
|
Oid attrtype = lfirst_oid(l1);
|
||||||
int32 attrtypmod = lfirst_int(l2);
|
int32 attrtypmod = lfirst_int(l2);
|
||||||
@ -1856,31 +1923,31 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
|
|||||||
*colvars = lappend(*colvars, varnode);
|
*colvars = lappend(*colvars, varnode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* note, ordinality is not allowed in this case */
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* addRangeTableEntryForFunction should've caught this */
|
/* addRangeTableEntryForFunction should've caught this */
|
||||||
elog(ERROR, "function in FROM has unsupported return type");
|
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)
|
if (rte->funcordinality)
|
||||||
{
|
{
|
||||||
Assert(ordinality_attno > 0);
|
|
||||||
|
|
||||||
if (colnames)
|
if (colnames)
|
||||||
*colnames = lappend(*colnames, llast(rte->eref->colnames));
|
*colnames = lappend(*colnames,
|
||||||
|
llast(rte->eref->colnames));
|
||||||
|
|
||||||
if (colvars)
|
if (colvars)
|
||||||
{
|
{
|
||||||
Var *varnode = makeVar(rtindex,
|
Var *varnode = makeVar(rtindex,
|
||||||
ordinality_attno,
|
atts_done + 1,
|
||||||
INT8OID,
|
INT8OID,
|
||||||
-1,
|
-1,
|
||||||
InvalidOid,
|
InvalidOid,
|
||||||
sublevels_up);
|
sublevels_up);
|
||||||
|
|
||||||
*colvars = lappend(*colvars, varnode);
|
*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 */
|
/* Get the tupledesc and turn it over to expandTupleDesc */
|
||||||
rel = relation_open(relid, AccessShareLock);
|
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,
|
location, include_dropped,
|
||||||
colnames, colvars);
|
colnames, colvars);
|
||||||
relation_close(rel, AccessShareLock);
|
relation_close(rel, AccessShareLock);
|
||||||
@ -2060,20 +2128,34 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
|
|||||||
/*
|
/*
|
||||||
* expandTupleDesc -- expandRTE subroutine
|
* expandTupleDesc -- expandRTE subroutine
|
||||||
*
|
*
|
||||||
* Only the required number of column names are used from the Alias;
|
* Generate names and/or Vars for the first "count" attributes of the tupdesc,
|
||||||
* it is not an error to supply too many. (ordinality depends on this)
|
* 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
|
static void
|
||||||
expandTupleDesc(TupleDesc tupdesc, Alias *eref,
|
expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
|
||||||
int rtindex, int sublevels_up,
|
int rtindex, int sublevels_up,
|
||||||
int location, bool include_dropped,
|
int location, bool include_dropped,
|
||||||
List **colnames, List **colvars)
|
List **colnames, List **colvars)
|
||||||
{
|
{
|
||||||
int maxattrs = tupdesc->natts;
|
ListCell *aliascell = list_head(eref->colnames);
|
||||||
int numaliases = list_length(eref->colnames);
|
|
||||||
int varattno;
|
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];
|
Form_pg_attribute attr = tupdesc->attrs[varattno];
|
||||||
|
|
||||||
@ -2093,6 +2175,8 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref,
|
|||||||
makeNullConst(INT4OID, -1, InvalidOid));
|
makeNullConst(INT4OID, -1, InvalidOid));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (aliascell)
|
||||||
|
aliascell = lnext(aliascell);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2100,10 +2184,16 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref,
|
|||||||
{
|
{
|
||||||
char *label;
|
char *label;
|
||||||
|
|
||||||
if (varattno < numaliases)
|
if (aliascell)
|
||||||
label = strVal(list_nth(eref->colnames, varattno));
|
{
|
||||||
|
label = strVal(lfirst(aliascell));
|
||||||
|
aliascell = lnext(aliascell);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
/* If we run out of aliases, use the underlying name */
|
||||||
label = NameStr(attr->attname);
|
label = NameStr(attr->attname);
|
||||||
|
}
|
||||||
*colnames = lappend(*colnames, makeString(pstrdup(label)));
|
*colnames = lappend(*colnames, makeString(pstrdup(label)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2111,7 +2201,7 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref,
|
|||||||
{
|
{
|
||||||
Var *varnode;
|
Var *varnode;
|
||||||
|
|
||||||
varnode = makeVar(rtindex, attr->attnum,
|
varnode = makeVar(rtindex, varattno + offset + 1,
|
||||||
attr->atttypid, attr->atttypmod,
|
attr->atttypid, attr->atttypmod,
|
||||||
attr->attcollation,
|
attr->attcollation,
|
||||||
sublevels_up);
|
sublevels_up);
|
||||||
@ -2221,9 +2311,6 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
|
|||||||
/*
|
/*
|
||||||
* get_rte_attribute_type
|
* get_rte_attribute_type
|
||||||
* Get attribute type/typmod/collation information from a RangeTblEntry
|
* 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
|
void
|
||||||
get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
|
get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
|
||||||
@ -2278,25 +2365,23 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
|
|||||||
case RTE_FUNCTION:
|
case RTE_FUNCTION:
|
||||||
{
|
{
|
||||||
/* Function RTE */
|
/* Function RTE */
|
||||||
|
ListCell *lc;
|
||||||
|
int atts_done = 0;
|
||||||
|
|
||||||
|
/* Identify which function covers the requested column */
|
||||||
|
foreach(lc, rte->functions)
|
||||||
|
{
|
||||||
|
RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
|
||||||
|
|
||||||
|
if (attnum > atts_done &&
|
||||||
|
attnum <= atts_done + rtfunc->funccolcount)
|
||||||
|
{
|
||||||
TypeFuncClass functypclass;
|
TypeFuncClass functypclass;
|
||||||
Oid funcrettype;
|
Oid funcrettype;
|
||||||
TupleDesc tupdesc;
|
TupleDesc tupdesc;
|
||||||
|
|
||||||
/*
|
attnum -= atts_done; /* now relative to this func */
|
||||||
* if ordinality, then a reference to the last column
|
functypclass = get_expr_result_type(rtfunc->funcexpr,
|
||||||
* 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,
|
&funcrettype,
|
||||||
&tupdesc);
|
&tupdesc);
|
||||||
|
|
||||||
@ -2306,20 +2391,12 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
|
|||||||
Form_pg_attribute att_tup;
|
Form_pg_attribute att_tup;
|
||||||
|
|
||||||
Assert(tupdesc);
|
Assert(tupdesc);
|
||||||
|
Assert(attnum <= tupdesc->natts);
|
||||||
/* this is probably a can't-happen case */
|
|
||||||
if (attnum < 1 || attnum > tupdesc->natts)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
|
||||||
errmsg("column %d of relation \"%s\" does not exist",
|
|
||||||
attnum,
|
|
||||||
rte->eref->aliasname)));
|
|
||||||
|
|
||||||
att_tup = tupdesc->attrs[attnum - 1];
|
att_tup = tupdesc->attrs[attnum - 1];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If dropped column, pretend it ain't there. See notes
|
* If dropped column, pretend it ain't there. See
|
||||||
* in scanRTEForColumn.
|
* notes in scanRTEForColumn.
|
||||||
*/
|
*/
|
||||||
if (att_tup->attisdropped)
|
if (att_tup->attisdropped)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
@ -2333,24 +2410,48 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
|
|||||||
}
|
}
|
||||||
else if (functypclass == TYPEFUNC_SCALAR)
|
else if (functypclass == TYPEFUNC_SCALAR)
|
||||||
{
|
{
|
||||||
Assert(attnum == 1);
|
|
||||||
|
|
||||||
/* Base data type, i.e. scalar */
|
/* Base data type, i.e. scalar */
|
||||||
*vartype = funcrettype;
|
*vartype = funcrettype;
|
||||||
*vartypmod = -1;
|
*vartypmod = -1;
|
||||||
*varcollid = exprCollation(rte->funcexpr);
|
*varcollid = exprCollation(rtfunc->funcexpr);
|
||||||
}
|
}
|
||||||
else if (functypclass == TYPEFUNC_RECORD)
|
else if (functypclass == TYPEFUNC_RECORD)
|
||||||
{
|
{
|
||||||
*vartype = list_nth_oid(rte->funccoltypes, attnum - 1);
|
*vartype = list_nth_oid(rtfunc->funccoltypes,
|
||||||
*vartypmod = list_nth_int(rte->funccoltypmods, attnum - 1);
|
attnum - 1);
|
||||||
*varcollid = list_nth_oid(rte->funccolcollations, attnum - 1);
|
*vartypmod = list_nth_int(rtfunc->funccoltypmods,
|
||||||
|
attnum - 1);
|
||||||
|
*varcollid = list_nth_oid(rtfunc->funccolcollations,
|
||||||
|
attnum - 1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* addRangeTableEntryForFunction should've caught this */
|
/*
|
||||||
|
* addRangeTableEntryForFunction should've caught
|
||||||
|
* this
|
||||||
|
*/
|
||||||
elog(ERROR, "function in FROM has unsupported return type");
|
elog(ERROR, "function in FROM has unsupported return type");
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
atts_done += rtfunc->funccolcount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we get here, must be looking for the ordinality column */
|
||||||
|
if (rte->funcordinality && attnum == atts_done + 1)
|
||||||
|
{
|
||||||
|
*vartype = INT8OID;
|
||||||
|
*vartypmod = -1;
|
||||||
|
*varcollid = InvalidOid;
|
||||||
|
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;
|
break;
|
||||||
case RTE_VALUES:
|
case RTE_VALUES:
|
||||||
@ -2456,46 +2557,57 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
|
|||||||
case RTE_FUNCTION:
|
case RTE_FUNCTION:
|
||||||
{
|
{
|
||||||
/* Function RTE */
|
/* Function RTE */
|
||||||
Oid funcrettype = exprType(rte->funcexpr);
|
ListCell *lc;
|
||||||
Oid funcrelid = typeidTypeRelid(funcrettype);
|
int atts_done = 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* if ordinality, then a reference to the last column
|
* Dropped attributes are only possible with functions that
|
||||||
* in the name list must be referring to the
|
* return named composite types. In such a case we have to
|
||||||
* ordinality column, which is not dropped
|
* 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
|
foreach(lc, rte->functions)
|
||||||
&& attnum == list_length(rte->eref->colnames))
|
|
||||||
{
|
{
|
||||||
result = false;
|
RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
|
||||||
}
|
|
||||||
else if (OidIsValid(funcrelid))
|
if (attnum > atts_done &&
|
||||||
|
attnum <= atts_done + rtfunc->funccolcount)
|
||||||
{
|
{
|
||||||
/*
|
TypeFuncClass functypclass;
|
||||||
* Composite data type, i.e. a table's row type
|
Oid funcrettype;
|
||||||
*
|
TupleDesc tupdesc;
|
||||||
* Same as ordinary relation RTE
|
|
||||||
*/
|
functypclass = get_expr_result_type(rtfunc->funcexpr,
|
||||||
HeapTuple tp;
|
&funcrettype,
|
||||||
|
&tupdesc);
|
||||||
|
if (functypclass == TYPEFUNC_COMPOSITE)
|
||||||
|
{
|
||||||
|
/* Composite data type, e.g. a table's row type */
|
||||||
Form_pg_attribute att_tup;
|
Form_pg_attribute att_tup;
|
||||||
|
|
||||||
tp = SearchSysCache2(ATTNUM,
|
Assert(tupdesc);
|
||||||
ObjectIdGetDatum(funcrelid),
|
Assert(attnum - atts_done <= tupdesc->natts);
|
||||||
Int16GetDatum(attnum));
|
att_tup = tupdesc->attrs[attnum - atts_done - 1];
|
||||||
if (!HeapTupleIsValid(tp)) /* shouldn't happen */
|
return att_tup->attisdropped;
|
||||||
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
|
/* Otherwise, it can't have any dropped columns */
|
||||||
{
|
return false;
|
||||||
/*
|
|
||||||
* Must be a base data type, i.e. scalar
|
|
||||||
*/
|
|
||||||
result = 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;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -472,7 +472,7 @@ GetColumnDefCollation(ParseState *pstate, ColumnDef *coldef, Oid typeOid)
|
|||||||
{
|
{
|
||||||
Oid result;
|
Oid result;
|
||||||
Oid typcollation = get_typcollation(typeOid);
|
Oid typcollation = get_typcollation(typeOid);
|
||||||
int location = -1;
|
int location = coldef->location;
|
||||||
|
|
||||||
if (coldef->collClause)
|
if (coldef->collClause)
|
||||||
{
|
{
|
||||||
|
@ -754,6 +754,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
|
|||||||
def->collClause = NULL;
|
def->collClause = NULL;
|
||||||
def->collOid = attribute->attcollation;
|
def->collOid = attribute->attcollation;
|
||||||
def->constraints = NIL;
|
def->constraints = NIL;
|
||||||
|
def->location = -1;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add to column list
|
* Add to column list
|
||||||
@ -969,6 +970,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
|
|||||||
n->collClause = NULL;
|
n->collClause = NULL;
|
||||||
n->collOid = attr->attcollation;
|
n->collOid = attr->attcollation;
|
||||||
n->constraints = NIL;
|
n->constraints = NIL;
|
||||||
|
n->location = -1;
|
||||||
cxt->columns = lappend(cxt->columns, n);
|
cxt->columns = lappend(cxt->columns, n);
|
||||||
}
|
}
|
||||||
DecrTupleDescRefCount(tupdesc);
|
DecrTupleDescRefCount(tupdesc);
|
||||||
|
@ -390,7 +390,7 @@ rewriteRuleAction(Query *parsetree,
|
|||||||
{
|
{
|
||||||
case RTE_FUNCTION:
|
case RTE_FUNCTION:
|
||||||
sub_action->hasSubLinks =
|
sub_action->hasSubLinks =
|
||||||
checkExprHasSubLink(rte->funcexpr);
|
checkExprHasSubLink((Node *) rte->functions);
|
||||||
break;
|
break;
|
||||||
case RTE_VALUES:
|
case RTE_VALUES:
|
||||||
sub_action->hasSubLinks =
|
sub_action->hasSubLinks =
|
||||||
|
@ -387,8 +387,8 @@ static void get_from_clause_item(Node *jtnode, Query *query,
|
|||||||
deparse_context *context);
|
deparse_context *context);
|
||||||
static void get_column_alias_list(deparse_columns *colinfo,
|
static void get_column_alias_list(deparse_columns *colinfo,
|
||||||
deparse_context *context);
|
deparse_context *context);
|
||||||
static void get_from_clause_coldeflist(deparse_columns *colinfo,
|
static void get_from_clause_coldeflist(RangeTblFunction *rtfunc,
|
||||||
List *types, List *typmods, List *collations,
|
deparse_columns *colinfo,
|
||||||
deparse_context *context);
|
deparse_context *context);
|
||||||
static void get_opclass_name(Oid opclass, Oid actual_datatype,
|
static void get_opclass_name(Oid opclass, Oid actual_datatype,
|
||||||
StringInfo buf);
|
StringInfo buf);
|
||||||
@ -8012,6 +8012,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
|
|||||||
RangeTblEntry *rte = rt_fetch(varno, query->rtable);
|
RangeTblEntry *rte = rt_fetch(varno, query->rtable);
|
||||||
char *refname = get_rtable_name(varno, context);
|
char *refname = get_rtable_name(varno, context);
|
||||||
deparse_columns *colinfo = deparse_columns_fetch(varno, dpns);
|
deparse_columns *colinfo = deparse_columns_fetch(varno, dpns);
|
||||||
|
RangeTblFunction *rtfunc1 = NULL;
|
||||||
bool printalias;
|
bool printalias;
|
||||||
|
|
||||||
if (rte->lateral)
|
if (rte->lateral)
|
||||||
@ -8037,7 +8038,96 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
|
|||||||
break;
|
break;
|
||||||
case RTE_FUNCTION:
|
case RTE_FUNCTION:
|
||||||
/* Function RTE */
|
/* 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)
|
if (rte->funcordinality)
|
||||||
appendStringInfoString(buf, " WITH ORDINALITY");
|
appendStringInfoString(buf, " WITH ORDINALITY");
|
||||||
break;
|
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
|
* For a function RTE, always print alias. This covers possible
|
||||||
* renaming of the function and/or instability of the
|
* renaming of the function and/or instability of the
|
||||||
* FigureColname rules for things that aren't simple functions.
|
* 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;
|
printalias = true;
|
||||||
}
|
}
|
||||||
@ -8099,14 +8189,10 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
|
|||||||
appendStringInfo(buf, " %s", quote_identifier(refname));
|
appendStringInfo(buf, " %s", quote_identifier(refname));
|
||||||
|
|
||||||
/* Print the column definitions or aliases, if needed */
|
/* 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 */
|
/* Reconstruct the columndef list, which is also the aliases */
|
||||||
get_from_clause_coldeflist(colinfo,
|
get_from_clause_coldeflist(rtfunc1, colinfo, context);
|
||||||
rte->funccoltypes,
|
|
||||||
rte->funccoltypmods,
|
|
||||||
rte->funccolcollations,
|
|
||||||
context);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -8250,29 +8336,45 @@ get_column_alias_list(deparse_columns *colinfo, deparse_context *context)
|
|||||||
/*
|
/*
|
||||||
* get_from_clause_coldeflist - reproduce FROM clause coldeflist
|
* 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
|
* The coldeflist is appended immediately (no space) to buf. Caller is
|
||||||
* responsible for ensuring that an alias or AS is present before it.
|
* responsible for ensuring that an alias or AS is present before it.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
get_from_clause_coldeflist(deparse_columns *colinfo,
|
get_from_clause_coldeflist(RangeTblFunction *rtfunc,
|
||||||
List *types, List *typmods, List *collations,
|
deparse_columns *colinfo,
|
||||||
deparse_context *context)
|
deparse_context *context)
|
||||||
{
|
{
|
||||||
StringInfo buf = context->buf;
|
StringInfo buf = context->buf;
|
||||||
ListCell *l1;
|
ListCell *l1;
|
||||||
ListCell *l2;
|
ListCell *l2;
|
||||||
ListCell *l3;
|
ListCell *l3;
|
||||||
|
ListCell *l4;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
appendStringInfoChar(buf, '(');
|
appendStringInfoChar(buf, '(');
|
||||||
|
|
||||||
|
/* there's no forfour(), so must chase one list the hard way */
|
||||||
i = 0;
|
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);
|
Oid atttypid = lfirst_oid(l1);
|
||||||
int32 atttypmod = lfirst_int(l2);
|
int32 atttypmod = lfirst_int(l2);
|
||||||
Oid attcollation = lfirst_oid(l3);
|
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 */
|
Assert(attname); /* shouldn't be any dropped columns here */
|
||||||
|
|
||||||
@ -8285,6 +8387,8 @@ get_from_clause_coldeflist(deparse_columns *colinfo,
|
|||||||
attcollation != get_typcollation(atttypid))
|
attcollation != get_typcollation(atttypid))
|
||||||
appendStringInfo(buf, " COLLATE %s",
|
appendStringInfo(buf, " COLLATE %s",
|
||||||
generate_collation_name(attcollation));
|
generate_collation_name(attcollation));
|
||||||
|
|
||||||
|
l4 = lnext(l4);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,10 +87,12 @@ extern TupleDesc CreateTupleDesc(int natts, bool hasoid,
|
|||||||
Form_pg_attribute *attrs);
|
Form_pg_attribute *attrs);
|
||||||
|
|
||||||
extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc);
|
extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc);
|
||||||
extern TupleDesc CreateTupleDescCopyExtend(TupleDesc tupdesc, int moreatts);
|
|
||||||
|
|
||||||
extern TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc);
|
extern TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc);
|
||||||
|
|
||||||
|
extern void TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno,
|
||||||
|
TupleDesc src, AttrNumber srcAttno);
|
||||||
|
|
||||||
extern void FreeTupleDesc(TupleDesc tupdesc);
|
extern void FreeTupleDesc(TupleDesc tupdesc);
|
||||||
|
|
||||||
extern void IncrTupleDescRefCount(TupleDesc tupdesc);
|
extern void IncrTupleDescRefCount(TupleDesc tupdesc);
|
||||||
|
@ -53,6 +53,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* yyyymmddN */
|
/* yyyymmddN */
|
||||||
#define CATALOG_VERSION_NO 201311171
|
#define CATALOG_VERSION_NO 201311211
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -175,6 +175,7 @@ DATA(insert OID = 411 ( "<>" PGNSP PGUID b f f 20 20 16 411 410 int8ne neqsel
|
|||||||
DESCR("not equal");
|
DESCR("not equal");
|
||||||
DATA(insert OID = 412 ( "<" PGNSP PGUID b f f 20 20 16 413 415 int8lt scalarltsel scalarltjoinsel ));
|
DATA(insert OID = 412 ( "<" PGNSP PGUID b f f 20 20 16 413 415 int8lt scalarltsel scalarltjoinsel ));
|
||||||
DESCR("less than");
|
DESCR("less than");
|
||||||
|
#define Int8LessOperator 412
|
||||||
DATA(insert OID = 413 ( ">" PGNSP PGUID b f f 20 20 16 412 414 int8gt scalargtsel scalargtjoinsel ));
|
DATA(insert OID = 413 ( ">" PGNSP PGUID b f f 20 20 16 412 414 int8gt scalargtsel scalargtjoinsel ));
|
||||||
DESCR("greater than");
|
DESCR("greater than");
|
||||||
DATA(insert OID = 414 ( "<=" PGNSP PGUID b f f 20 20 16 415 413 int8le scalarltsel scalarltjoinsel ));
|
DATA(insert OID = 414 ( "<=" PGNSP PGUID b f f 20 20 16 415 413 int8le scalarltsel scalarltjoinsel ));
|
||||||
|
@ -1395,24 +1395,26 @@ typedef struct SubqueryScanState
|
|||||||
* function appearing in FROM (typically a function returning set).
|
* function appearing in FROM (typically a function returning set).
|
||||||
*
|
*
|
||||||
* eflags node's capability flags
|
* eflags node's capability flags
|
||||||
* ordinal column value for WITH ORDINALITY
|
* ordinality is this scan WITH ORDINALITY?
|
||||||
* scan_tupdesc scan tuple descriptor
|
* simple true if we have 1 function and no ordinality
|
||||||
* func_tupdesc function tuple descriptor
|
* ordinal current ordinal column value
|
||||||
* func_slot function result slot, or null
|
* nfuncs number of functions being executed
|
||||||
* tuplestorestate private state of tuplestore.c
|
* funcstates per-function execution states (private in
|
||||||
* funcexpr state for function expression being evaluated
|
* nodeFunctionscan.c)
|
||||||
* ----------------
|
* ----------------
|
||||||
*/
|
*/
|
||||||
|
struct FunctionScanPerFuncState;
|
||||||
|
|
||||||
typedef struct FunctionScanState
|
typedef struct FunctionScanState
|
||||||
{
|
{
|
||||||
ScanState ss; /* its first field is NodeTag */
|
ScanState ss; /* its first field is NodeTag */
|
||||||
int eflags;
|
int eflags;
|
||||||
|
bool ordinality;
|
||||||
|
bool simple;
|
||||||
int64 ordinal;
|
int64 ordinal;
|
||||||
TupleDesc scan_tupdesc;
|
int nfuncs;
|
||||||
TupleDesc func_tupdesc;
|
struct FunctionScanPerFuncState *funcstates; /* array of length
|
||||||
TupleTableSlot *func_slot;
|
* nfuncs */
|
||||||
Tuplestorestate *tuplestorestate;
|
|
||||||
ExprState *funcexpr;
|
|
||||||
} FunctionScanState;
|
} FunctionScanState;
|
||||||
|
|
||||||
/* ----------------
|
/* ----------------
|
||||||
|
@ -389,6 +389,7 @@ typedef enum NodeTag
|
|||||||
T_Constraint,
|
T_Constraint,
|
||||||
T_DefElem,
|
T_DefElem,
|
||||||
T_RangeTblEntry,
|
T_RangeTblEntry,
|
||||||
|
T_RangeTblFunction,
|
||||||
T_WithCheckOption,
|
T_WithCheckOption,
|
||||||
T_SortGroupClause,
|
T_SortGroupClause,
|
||||||
T_WindowClause,
|
T_WindowClause,
|
||||||
|
@ -288,10 +288,8 @@ typedef struct CollateClause
|
|||||||
* aggregate or some other kind of function. However, if FILTER or OVER is
|
* aggregate or some other kind of function. However, if FILTER or OVER is
|
||||||
* present it had better be an aggregate or window function.
|
* present it had better be an aggregate or window function.
|
||||||
*
|
*
|
||||||
* Normally, you'd initialize this via makeFuncCall() and then only
|
* Normally, you'd initialize this via makeFuncCall() and then only change the
|
||||||
* change the parts of the struct its defaults don't match afterwards
|
* parts of the struct its defaults don't match afterwards, as needed.
|
||||||
* if needed.
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
typedef struct FuncCall
|
typedef struct FuncCall
|
||||||
{
|
{
|
||||||
@ -466,13 +464,25 @@ typedef struct RangeSubselect
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* RangeFunction - function call appearing in a FROM clause
|
* 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
|
typedef struct RangeFunction
|
||||||
{
|
{
|
||||||
NodeTag type;
|
NodeTag type;
|
||||||
bool lateral; /* does it have LATERAL prefix? */
|
bool lateral; /* does it have LATERAL prefix? */
|
||||||
bool ordinality; /* does it have WITH ORDINALITY suffix? */
|
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 */
|
Alias *alias; /* table alias & optional column aliases */
|
||||||
List *coldeflist; /* list of ColumnDef nodes to describe result
|
List *coldeflist; /* list of ColumnDef nodes to describe result
|
||||||
* of function returning RECORD */
|
* of function returning RECORD */
|
||||||
@ -512,6 +522,7 @@ typedef struct ColumnDef
|
|||||||
Oid collOid; /* collation OID (InvalidOid if not set) */
|
Oid collOid; /* collation OID (InvalidOid if not set) */
|
||||||
List *constraints; /* other constraints on column */
|
List *constraints; /* other constraints on column */
|
||||||
List *fdwoptions; /* per-column FDW options */
|
List *fdwoptions; /* per-column FDW options */
|
||||||
|
int location; /* parse location, or -1 if none/unknown */
|
||||||
} ColumnDef;
|
} ColumnDef;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -652,13 +663,8 @@ typedef struct XmlSerialize
|
|||||||
* dropped columns. Note however that a stored rule may have nonempty
|
* dropped columns. Note however that a stored rule may have nonempty
|
||||||
* colnames for columns dropped since the rule was created (and for that
|
* colnames for columns dropped since the rule was created (and for that
|
||||||
* matter the colnames might be out of date due to column renamings).
|
* matter the colnames might be out of date due to column renamings).
|
||||||
*
|
* The same comments apply to FUNCTION RTEs when a function's return type
|
||||||
* The same comments apply to FUNCTION RTEs when the function's return type
|
* is a named composite 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.
|
|
||||||
*
|
*
|
||||||
* In JOIN RTEs, the colnames in both alias and eref are one-to-one with
|
* 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
|
* joinaliasvars entries. A JOIN RTE will omit columns of its inputs when
|
||||||
@ -755,22 +761,14 @@ typedef struct RangeTblEntry
|
|||||||
List *joinaliasvars; /* list of alias-var expansions */
|
List *joinaliasvars; /* list of alias-var expansions */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fields valid for a function RTE (else NULL):
|
* Fields valid for a function RTE (else NIL/zero):
|
||||||
*
|
*
|
||||||
* If the function returns an otherwise-unspecified RECORD, funccoltypes
|
* When funcordinality is true, the eref->colnames list includes an alias
|
||||||
* lists the column types declared in the RTE's column type specification,
|
* for the ordinality column. The ordinality column is otherwise
|
||||||
* funccoltypmods lists their declared typmods, funccolcollations their
|
* implicit, and must be accounted for "by hand" in places such as
|
||||||
* collations. Note that in this case, ORDINALITY is not permitted, so
|
* expandRTE().
|
||||||
* there is no extra ordinal column to be allowed for.
|
|
||||||
*
|
|
||||||
* Otherwise, those fields are NIL, and the result column types must be
|
|
||||||
* derived from the funcexpr while treating the ordinal column, if
|
|
||||||
* present, as a special case. (see get_rte_attribute_*)
|
|
||||||
*/
|
*/
|
||||||
Node *funcexpr; /* expression tree for func call */
|
List *functions; /* list of RangeTblFunction nodes */
|
||||||
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? */
|
bool funcordinality; /* is this called WITH ORDINALITY? */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -803,6 +801,37 @@ typedef struct RangeTblEntry
|
|||||||
Bitmapset *modifiedCols; /* columns needing INSERT/UPDATE permission */
|
Bitmapset *modifiedCols; /* columns needing INSERT/UPDATE permission */
|
||||||
} RangeTblEntry;
|
} 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 -
|
* WithCheckOption -
|
||||||
* representation of WITH CHECK OPTION checks to be applied to new tuples
|
* representation of WITH CHECK OPTION checks to be applied to new tuples
|
||||||
|
@ -424,12 +424,8 @@ typedef struct SubqueryScan
|
|||||||
typedef struct FunctionScan
|
typedef struct FunctionScan
|
||||||
{
|
{
|
||||||
Scan scan;
|
Scan scan;
|
||||||
Node *funcexpr; /* expression tree for func call */
|
List *functions; /* list of RangeTblFunction nodes */
|
||||||
bool funcordinality; /* WITH ORDINALITY */
|
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 */
|
|
||||||
} FunctionScan;
|
} FunctionScan;
|
||||||
|
|
||||||
/* ----------------
|
/* ----------------
|
||||||
|
@ -70,7 +70,7 @@ extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
extern Path *create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel,
|
extern Path *create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel,
|
||||||
List *pathkeys, Relids required_outer);
|
List *pathkeys, Relids required_outer);
|
||||||
extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
|
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,
|
extern Path *create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel,
|
||||||
Relids required_outer);
|
Relids required_outer);
|
||||||
extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel,
|
extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel,
|
||||||
|
@ -166,6 +166,9 @@ extern Path *get_cheapest_fractional_path_for_pathkeys(List *paths,
|
|||||||
double fraction);
|
double fraction);
|
||||||
extern List *build_index_pathkeys(PlannerInfo *root, IndexOptInfo *index,
|
extern List *build_index_pathkeys(PlannerInfo *root, IndexOptInfo *index,
|
||||||
ScanDirection scandir);
|
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,
|
extern List *convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
|
||||||
List *subquery_pathkeys);
|
List *subquery_pathkeys);
|
||||||
extern List *build_join_pathkeys(PlannerInfo *root,
|
extern List *build_join_pathkeys(PlannerInfo *root,
|
||||||
|
@ -58,8 +58,9 @@ extern RangeTblEntry *addRangeTableEntryForSubquery(ParseState *pstate,
|
|||||||
bool lateral,
|
bool lateral,
|
||||||
bool inFromCl);
|
bool inFromCl);
|
||||||
extern RangeTblEntry *addRangeTableEntryForFunction(ParseState *pstate,
|
extern RangeTblEntry *addRangeTableEntryForFunction(ParseState *pstate,
|
||||||
char *funcname,
|
List *funcnames,
|
||||||
Node *funcexpr,
|
List *funcexprs,
|
||||||
|
List *coldeflists,
|
||||||
RangeFunction *rangefunc,
|
RangeFunction *rangefunc,
|
||||||
bool lateral,
|
bool lateral,
|
||||||
bool inFromCl);
|
bool inFromCl);
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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 * 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 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 * 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
|
-- 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);
|
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 * from vw_ord;
|
||||||
select definition from pg_views where viewname='vw_ord';
|
select definition from pg_views where viewname='vw_ord';
|
||||||
drop view 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;
|
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 all from foo;
|
||||||
fetch backward all from foo;
|
fetch backward all from foo;
|
||||||
fetch all from foo;
|
fetch all from foo;
|
||||||
@ -31,6 +58,12 @@ fetch next from foo;
|
|||||||
fetch next from foo;
|
fetch next from foo;
|
||||||
fetch prior from foo;
|
fetch prior from foo;
|
||||||
fetch absolute 1 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;
|
commit;
|
||||||
|
|
||||||
-- function with implicit LATERAL
|
-- function with implicit LATERAL
|
||||||
@ -57,133 +90,169 @@ INSERT INTO foo VALUES(1,2,'Ed');
|
|||||||
INSERT INTO foo VALUES(2,1,'Mary');
|
INSERT INTO foo VALUES(2,1,'Mary');
|
||||||
|
|
||||||
-- sql, proretset = f, prorettype = b
|
-- sql, proretset = f, prorettype = b
|
||||||
CREATE FUNCTION getfoo(int) RETURNS int AS 'SELECT $1;' LANGUAGE SQL;
|
CREATE FUNCTION getfoo1(int) RETURNS int AS 'SELECT $1;' LANGUAGE SQL;
|
||||||
SELECT * FROM getfoo(1) AS t1;
|
SELECT * FROM getfoo1(1) AS t1;
|
||||||
SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
|
SELECT * FROM getfoo1(1) WITH ORDINALITY AS t1(v,o);
|
||||||
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo1(1);
|
||||||
SELECT * FROM vw_getfoo;
|
SELECT * FROM vw_getfoo;
|
||||||
DROP VIEW 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;
|
SELECT * FROM vw_getfoo;
|
||||||
|
DROP VIEW vw_getfoo;
|
||||||
|
|
||||||
-- sql, proretset = t, prorettype = b
|
-- sql, proretset = t, prorettype = b
|
||||||
DROP VIEW vw_getfoo;
|
CREATE FUNCTION getfoo2(int) RETURNS setof int AS 'SELECT fooid FROM foo WHERE fooid = $1;' LANGUAGE SQL;
|
||||||
DROP FUNCTION getfoo(int);
|
SELECT * FROM getfoo2(1) AS t1;
|
||||||
CREATE FUNCTION getfoo(int) RETURNS setof int AS 'SELECT fooid FROM foo WHERE fooid = $1;' LANGUAGE SQL;
|
SELECT * FROM getfoo2(1) WITH ORDINALITY AS t1(v,o);
|
||||||
SELECT * FROM getfoo(1) AS t1;
|
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo2(1);
|
||||||
SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
|
|
||||||
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
|
||||||
SELECT * FROM vw_getfoo;
|
SELECT * FROM vw_getfoo;
|
||||||
DROP VIEW 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;
|
SELECT * FROM vw_getfoo;
|
||||||
|
DROP VIEW vw_getfoo;
|
||||||
|
|
||||||
-- sql, proretset = t, prorettype = b
|
-- sql, proretset = t, prorettype = b
|
||||||
DROP VIEW vw_getfoo;
|
CREATE FUNCTION getfoo3(int) RETURNS setof text AS 'SELECT fooname FROM foo WHERE fooid = $1;' LANGUAGE SQL;
|
||||||
DROP FUNCTION getfoo(int);
|
SELECT * FROM getfoo3(1) AS t1;
|
||||||
CREATE FUNCTION getfoo(int) RETURNS setof text AS 'SELECT fooname FROM foo WHERE fooid = $1;' LANGUAGE SQL;
|
SELECT * FROM getfoo3(1) WITH ORDINALITY AS t1(v,o);
|
||||||
SELECT * FROM getfoo(1) AS t1;
|
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo3(1);
|
||||||
SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
|
|
||||||
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
|
||||||
SELECT * FROM vw_getfoo;
|
SELECT * FROM vw_getfoo;
|
||||||
DROP VIEW 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;
|
SELECT * FROM vw_getfoo;
|
||||||
|
DROP VIEW vw_getfoo;
|
||||||
|
|
||||||
-- sql, proretset = f, prorettype = c
|
-- sql, proretset = f, prorettype = c
|
||||||
DROP VIEW vw_getfoo;
|
CREATE FUNCTION getfoo4(int) RETURNS foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
|
||||||
DROP FUNCTION getfoo(int);
|
SELECT * FROM getfoo4(1) AS t1;
|
||||||
CREATE FUNCTION getfoo(int) RETURNS foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
|
SELECT * FROM getfoo4(1) WITH ORDINALITY AS t1(a,b,c,o);
|
||||||
SELECT * FROM getfoo(1) AS t1;
|
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo4(1);
|
||||||
SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
|
|
||||||
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
|
||||||
SELECT * FROM vw_getfoo;
|
SELECT * FROM vw_getfoo;
|
||||||
DROP VIEW 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;
|
SELECT * FROM vw_getfoo;
|
||||||
|
DROP VIEW vw_getfoo;
|
||||||
|
|
||||||
-- sql, proretset = t, prorettype = c
|
-- sql, proretset = t, prorettype = c
|
||||||
DROP VIEW vw_getfoo;
|
CREATE FUNCTION getfoo5(int) RETURNS setof foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
|
||||||
DROP FUNCTION getfoo(int);
|
SELECT * FROM getfoo5(1) AS t1;
|
||||||
CREATE FUNCTION getfoo(int) RETURNS setof foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
|
SELECT * FROM getfoo5(1) WITH ORDINALITY AS t1(a,b,c,o);
|
||||||
SELECT * FROM getfoo(1) AS t1;
|
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo5(1);
|
||||||
SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
|
|
||||||
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
|
||||||
SELECT * FROM vw_getfoo;
|
SELECT * FROM vw_getfoo;
|
||||||
DROP VIEW 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;
|
SELECT * FROM vw_getfoo;
|
||||||
|
DROP VIEW vw_getfoo;
|
||||||
|
|
||||||
-- ordinality not supported for returns record yet
|
|
||||||
-- sql, proretset = f, prorettype = record
|
-- sql, proretset = f, prorettype = record
|
||||||
DROP VIEW vw_getfoo;
|
CREATE FUNCTION getfoo6(int) RETURNS RECORD AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
|
||||||
DROP FUNCTION getfoo(int);
|
SELECT * FROM getfoo6(1) AS t1(fooid int, foosubid int, fooname text);
|
||||||
CREATE FUNCTION getfoo(int) RETURNS RECORD AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
|
SELECT * FROM TABLE( getfoo6(1) AS (fooid int, foosubid int, fooname text) ) WITH ORDINALITY;
|
||||||
SELECT * FROM getfoo(1) AS t1(fooid int, foosubid int, fooname text);
|
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo6(1) AS
|
||||||
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) AS
|
|
||||||
(fooid int, foosubid int, fooname text);
|
(fooid int, foosubid int, fooname text);
|
||||||
SELECT * FROM vw_getfoo;
|
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
|
-- sql, proretset = t, prorettype = record
|
||||||
DROP VIEW vw_getfoo;
|
CREATE FUNCTION getfoo7(int) RETURNS setof record AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
|
||||||
DROP FUNCTION getfoo(int);
|
SELECT * FROM getfoo7(1) AS t1(fooid int, foosubid int, fooname text);
|
||||||
CREATE FUNCTION getfoo(int) RETURNS setof record AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
|
SELECT * FROM TABLE( getfoo7(1) AS (fooid int, foosubid int, fooname text) ) WITH ORDINALITY;
|
||||||
SELECT * FROM getfoo(1) AS t1(fooid int, foosubid int, fooname text);
|
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo7(1) AS
|
||||||
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) AS
|
|
||||||
(fooid int, foosubid int, fooname text);
|
(fooid int, foosubid int, fooname text);
|
||||||
SELECT * FROM vw_getfoo;
|
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
|
-- plpgsql, proretset = f, prorettype = b
|
||||||
DROP VIEW vw_getfoo;
|
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;
|
||||||
DROP FUNCTION getfoo(int);
|
SELECT * FROM getfoo8(1) AS t1;
|
||||||
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 getfoo8(1) WITH ORDINALITY AS t1(v,o);
|
||||||
SELECT * FROM getfoo(1) AS t1;
|
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo8(1);
|
||||||
SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
|
|
||||||
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
|
||||||
SELECT * FROM vw_getfoo;
|
SELECT * FROM vw_getfoo;
|
||||||
DROP VIEW 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;
|
SELECT * FROM vw_getfoo;
|
||||||
|
DROP VIEW vw_getfoo;
|
||||||
|
|
||||||
-- plpgsql, proretset = f, prorettype = c
|
-- plpgsql, proretset = f, prorettype = c
|
||||||
DROP VIEW vw_getfoo;
|
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;
|
||||||
DROP FUNCTION getfoo(int);
|
SELECT * FROM getfoo9(1) AS t1;
|
||||||
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 getfoo9(1) WITH ORDINALITY AS t1(a,b,c,o);
|
||||||
SELECT * FROM getfoo(1) AS t1;
|
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo9(1);
|
||||||
SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
|
|
||||||
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
|
||||||
SELECT * FROM vw_getfoo;
|
SELECT * FROM vw_getfoo;
|
||||||
DROP VIEW 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;
|
SELECT * FROM vw_getfoo;
|
||||||
|
DROP VIEW vw_getfoo;
|
||||||
|
|
||||||
DROP VIEW vw_getfoo;
|
-- mix 'n match kinds, to exercise expandRTE and related logic
|
||||||
DROP FUNCTION getfoo(int);
|
|
||||||
|
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 FUNCTION foot(int);
|
||||||
DROP TABLE foo2;
|
DROP TABLE foo2;
|
||||||
DROP TABLE foo;
|
DROP TABLE foo;
|
||||||
|
|
||||||
-- Rescan tests --
|
-- 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 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
|
-- 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
|
--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
|
-- 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
|
-- 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 * 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 * 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 * 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 * 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) 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;
|
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)
|
--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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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);
|
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) f(i);
|
||||||
SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) WITH ORDINALITY AS f(i,o);
|
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_sql(int,int);
|
||||||
DROP FUNCTION foo_mat(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
|
-- Test cases involving OUT parameters
|
||||||
@ -444,12 +526,12 @@ select * from testfoo(); -- fail
|
|||||||
drop function 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);
|
create temp table users (userid text, seq int, email text, todrop bool, moredrop int, enabled bool);
|
||||||
insert into users values ('id','email',true,true);
|
insert into users values ('id',1,'email',true,11,true);
|
||||||
insert into users values ('id2','email2',true,true);
|
insert into users values ('id2',2,'email2',true,12,true);
|
||||||
alter table users drop column todrop;
|
alter table users drop column todrop;
|
||||||
|
|
||||||
create or replace function get_first_user() returns users as
|
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();
|
||||||
SELECT * FROM get_users() WITH ORDINALITY; -- make sure ordinality copes
|
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_first_user();
|
||||||
drop function get_users();
|
drop function get_users();
|
||||||
drop table users;
|
drop table users;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user