Support window functions a la SQL:2008.
Hitoshi Harada, with some kibitzing from Heikki and Tom.
This commit is contained in:
parent
38e9348282
commit
95b07bc7f5
@ -7,7 +7,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/contrib/tsearch2/tsearch2.c,v 1.6 2008/03/25 22:42:42 tgl Exp $
|
||||
* $PostgreSQL: pgsql/contrib/tsearch2/tsearch2.c,v 1.7 2008/12/28 18:53:53 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -410,7 +410,15 @@ tsa_rewrite_accum(PG_FUNCTION_ARGS)
|
||||
MemoryContext aggcontext;
|
||||
MemoryContext oldcontext;
|
||||
|
||||
aggcontext = ((AggState *) fcinfo->context)->aggcontext;
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
aggcontext = ((AggState *) fcinfo->context)->aggcontext;
|
||||
else if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
|
||||
aggcontext = ((WindowAggState *) fcinfo->context)->wincontext;
|
||||
else
|
||||
{
|
||||
elog(ERROR, "tsa_rewrite_accum called in non-aggregate context");
|
||||
aggcontext = NULL; /* keep compiler quiet */
|
||||
}
|
||||
|
||||
if (PG_ARGISNULL(0) || PG_GETARG_POINTER(0) == NULL)
|
||||
{
|
||||
|
@ -1,4 +1,4 @@
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/advanced.sgml,v 1.54 2007/02/01 00:28:16 momjian Exp $ -->
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/advanced.sgml,v 1.55 2008/12/28 18:53:53 tgl Exp $ -->
|
||||
|
||||
<chapter id="tutorial-advanced">
|
||||
<title>Advanced Features</title>
|
||||
@ -240,7 +240,7 @@ COMMIT;
|
||||
<para>
|
||||
<productname>PostgreSQL</> actually treats every SQL statement as being
|
||||
executed within a transaction. If you do not issue a <command>BEGIN</>
|
||||
command,
|
||||
command,
|
||||
then each individual statement has an implicit <command>BEGIN</> and
|
||||
(if successful) <command>COMMIT</> wrapped around it. A group of
|
||||
statements surrounded by <command>BEGIN</> and <command>COMMIT</>
|
||||
@ -265,7 +265,7 @@ COMMIT;
|
||||
with <command>ROLLBACK TO</>. All the transaction's database changes
|
||||
between defining the savepoint and rolling back to it are discarded, but
|
||||
changes earlier than the savepoint are kept.
|
||||
</para>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
After rolling back to a savepoint, it continues to be defined, so you can
|
||||
@ -274,7 +274,7 @@ COMMIT;
|
||||
system can free some resources. Keep in mind that either releasing or
|
||||
rolling back to a savepoint
|
||||
will automatically release all savepoints that were defined after it.
|
||||
</para>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
All this is happening within the transaction block, so none of it
|
||||
@ -282,7 +282,7 @@ COMMIT;
|
||||
transaction block, the committed actions become visible as a unit
|
||||
to other sessions, while the rolled-back actions never become visible
|
||||
at all.
|
||||
</para>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Remembering the bank database, suppose we debit $100.00 from Alice's
|
||||
@ -317,6 +317,242 @@ COMMIT;
|
||||
</sect1>
|
||||
|
||||
|
||||
<sect1 id="tutorial-window">
|
||||
<title id="tutorial-window-title">Window Functions</title>
|
||||
|
||||
<indexterm zone="tutorial-window">
|
||||
<primary>window function</primary>
|
||||
</indexterm>
|
||||
|
||||
<para>
|
||||
A <firstterm>window function</> performs a calculation across a set of
|
||||
table rows that are somehow related to the current row. This is comparable
|
||||
to the type of calculation that can be done with an aggregate function.
|
||||
But unlike regular aggregate functions, use of a window function does not
|
||||
cause rows to become grouped into a single output row — the
|
||||
rows retain their separate identities. Behind the scenes, the window
|
||||
function is able to access more than just the current row of the query
|
||||
result.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Here is an example that shows how to compare each employee's salary
|
||||
with the average salary in his or her department:
|
||||
|
||||
<programlisting>
|
||||
SELECT depname, empno, salary, avg(salary) OVER (PARTITION BY depname) FROM empsalary;
|
||||
</programlisting>
|
||||
|
||||
<screen>
|
||||
depname | empno | salary | avg
|
||||
-----------+-------+--------+-----------------------
|
||||
develop | 11 | 5200 | 5020.0000000000000000
|
||||
develop | 7 | 4200 | 5020.0000000000000000
|
||||
develop | 9 | 4500 | 5020.0000000000000000
|
||||
develop | 8 | 6000 | 5020.0000000000000000
|
||||
develop | 10 | 5200 | 5020.0000000000000000
|
||||
personnel | 5 | 3500 | 3700.0000000000000000
|
||||
personnel | 2 | 3900 | 3700.0000000000000000
|
||||
sales | 3 | 4800 | 4866.6666666666666667
|
||||
sales | 1 | 5000 | 4866.6666666666666667
|
||||
sales | 4 | 4800 | 4866.6666666666666667
|
||||
(10 rows)
|
||||
</screen>
|
||||
|
||||
The first three output columns come directly from the table
|
||||
<structname>empsalary</>, and there is one output row for each row in the
|
||||
table. The fourth column represents an average taken across all the table
|
||||
rows that have the same <structfield>depname</> value as the current row.
|
||||
(This actually is the same function as the regular <function>avg</>
|
||||
aggregate function, but the <literal>OVER</> clause causes it to be
|
||||
treated as a window function and computed across an appropriate set of
|
||||
rows.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
A window function call always contains an <literal>OVER</> clause
|
||||
following the window function's name and argument(s). This is what
|
||||
syntactically distinguishes it from a regular function or aggregate
|
||||
function. The <literal>OVER</> clause determines exactly how the
|
||||
rows of the query are split up for processing by the window function.
|
||||
The <literal>PARTITION BY</> list within <literal>OVER</> specifies
|
||||
dividing the rows into groups, or partitions, that share the same
|
||||
values of the <literal>PARTITION BY</> expression(s). For each row,
|
||||
the window function is computed across the rows that fall into the
|
||||
same partition as the current row.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Although <function>avg</> will produce the same result no matter
|
||||
what order it processes the partition's rows in, this is not true of all
|
||||
window functions. When needed, you can control that order using
|
||||
<literal>ORDER BY</> within <literal>OVER</>. Here is an example:
|
||||
|
||||
<programlisting>
|
||||
SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary DESC) FROM empsalary;
|
||||
</programlisting>
|
||||
|
||||
<screen>
|
||||
depname | empno | salary | rank
|
||||
-----------+-------+--------+------
|
||||
develop | 8 | 6000 | 1
|
||||
develop | 10 | 5200 | 2
|
||||
develop | 11 | 5200 | 2
|
||||
develop | 9 | 4500 | 4
|
||||
develop | 7 | 4200 | 5
|
||||
personnel | 2 | 3900 | 1
|
||||
personnel | 5 | 3500 | 2
|
||||
sales | 1 | 5000 | 1
|
||||
sales | 4 | 4800 | 2
|
||||
sales | 3 | 4800 | 2
|
||||
(10 rows)
|
||||
</screen>
|
||||
|
||||
As shown here, the <function>rank</> function produces a numerical rank
|
||||
within the current row's partition for each distinct <literal>ORDER BY</>
|
||||
value, in the order defined by the <literal>ORDER BY</> clause.
|
||||
<function>rank</> needs no explicit parameter, because its behavior
|
||||
is entirely determined by the <literal>OVER</> clause.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The rows considered by a window function are those of the <quote>virtual
|
||||
table</> produced by the query's <literal>FROM</> clause as filtered by its
|
||||
<literal>WHERE</>, <literal>GROUP BY</>, and <literal>HAVING</> clauses
|
||||
if any. For example, a row removed because it does not meet the
|
||||
<literal>WHERE</> condition is not seen by any window function.
|
||||
A query can contain multiple window functions that slice up the data
|
||||
in different ways by means of different <literal>OVER</> clauses, but
|
||||
they all act on the same collection of rows defined by this virtual table.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
We already saw that <literal>ORDER BY</> can be omitted if the ordering
|
||||
of rows is not important. It is also possible to omit <literal>PARTITION
|
||||
BY</>, in which case the window function is computed over all rows of the
|
||||
virtual table; that is, there is one partition containing all the rows.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
There is another important concept associated with window functions:
|
||||
for each row, there is a set of rows within its partition called its
|
||||
<firstterm>window frame</>. When <literal>ORDER BY</> is omitted the
|
||||
frame is always the same as the partition. If <literal>ORDER BY</> is
|
||||
supplied, the frame consists of all rows from the start of the partition
|
||||
up to the current row, plus any following rows that are equal to the
|
||||
current row according to the <literal>ORDER BY</> clause.
|
||||
<footnote>
|
||||
<para>
|
||||
The SQL standard includes options to define the window frame in
|
||||
other ways, but this definition is the only one currently supported
|
||||
by <productname>PostgreSQL</productname>.
|
||||
</para>
|
||||
</footnote>
|
||||
Many window functions act only on the rows of the window frame, rather
|
||||
than of the whole partition. Here is an example using <function>sum</>:
|
||||
</para>
|
||||
|
||||
<programlisting>
|
||||
SELECT salary, sum(salary) OVER () FROM empsalary;
|
||||
</programlisting>
|
||||
|
||||
<screen>
|
||||
salary | sum
|
||||
--------+-------
|
||||
5200 | 47100
|
||||
5000 | 47100
|
||||
3500 | 47100
|
||||
4800 | 47100
|
||||
3900 | 47100
|
||||
4200 | 47100
|
||||
4500 | 47100
|
||||
4800 | 47100
|
||||
6000 | 47100
|
||||
5200 | 47100
|
||||
(10 rows)
|
||||
</screen>
|
||||
|
||||
<para>
|
||||
Above, since there is no <literal>ORDER BY</> in the <literal>OVER</>
|
||||
clause, the window frame is the same as the partition, which for lack of
|
||||
<literal>PARTITION BY</> is the whole table; in other words each sum is
|
||||
taken over the whole table and so we get the same result for each output
|
||||
row. But if we add an <literal>ORDER BY</> clause, we get very different
|
||||
results:
|
||||
</para>
|
||||
|
||||
<programlisting>
|
||||
SELECT salary, sum(salary) OVER (ORDER BY salary) FROM empsalary;
|
||||
</programlisting>
|
||||
|
||||
<screen>
|
||||
salary | sum
|
||||
--------+-------
|
||||
3500 | 3500
|
||||
3900 | 7400
|
||||
4200 | 11600
|
||||
4500 | 16100
|
||||
4800 | 25700
|
||||
4800 | 25700
|
||||
5000 | 30700
|
||||
5200 | 41100
|
||||
5200 | 41100
|
||||
6000 | 47100
|
||||
(10 rows)
|
||||
</screen>
|
||||
|
||||
<para>
|
||||
Here the sum is taken from the first (lowest) salary up through the
|
||||
current one, including any duplicates of the current one (notice the
|
||||
results for the duplicated salaries).
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Window functions are permitted only in the <literal>SELECT</literal> list
|
||||
and the <literal>ORDER BY</> clause of the query. They are forbidden
|
||||
elsewhere, such as in <literal>GROUP BY</>, <literal>HAVING</>
|
||||
and <literal>WHERE</literal> clauses. This is because they logically
|
||||
execute after the processing of those clauses. Also, window functions
|
||||
execute after regular aggregate functions. This means it is valid to
|
||||
include an aggregate function call in the arguments of a window function,
|
||||
but not vice versa.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If there is a need to filter or group rows after the window calculations
|
||||
are performed, you can use a sub-select. For example:
|
||||
|
||||
<programlisting>
|
||||
SELECT depname, empno, salary, enroll_date
|
||||
FROM
|
||||
(SELECT depname, empno, salary, enroll_date,
|
||||
rank() OVER (PARTITION BY depname ORDER BY salary DESC, empno) AS pos
|
||||
FROM empsalary
|
||||
) AS ss
|
||||
WHERE pos < 3;
|
||||
</programlisting>
|
||||
|
||||
The above query only shows the rows from the inner query having
|
||||
<literal>rank</> less than <literal>3</>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
When a query involves multiple window functions, it is possible to write
|
||||
out each one with a separate <literal>OVER</> clause, but this is
|
||||
duplicative and error-prone if the same windowing behavior is wanted
|
||||
for several functions. Instead, each windowing behavior can be named
|
||||
in a <literal>WINDOW</> clause and then referenced in <literal>OVER</>.
|
||||
For example:
|
||||
|
||||
<programlisting>
|
||||
SELECT sum(salary) OVER w, avg(salary) OVER w
|
||||
FROM empsalary
|
||||
WINDOW w AS (PARTITION BY depname ORDER BY salary DESC);
|
||||
</programlisting>
|
||||
</para>
|
||||
</sect1>
|
||||
|
||||
|
||||
<sect1 id="tutorial-inheritance">
|
||||
<title>Inheritance</title>
|
||||
|
||||
@ -391,7 +627,7 @@ CREATE TABLE capitals (
|
||||
|
||||
<para>
|
||||
For example, the following query finds the names of all cities,
|
||||
including state capitals, that are located at an altitude
|
||||
including state capitals, that are located at an altitude
|
||||
over 500 feet:
|
||||
|
||||
<programlisting>
|
||||
@ -455,7 +691,7 @@ SELECT name, altitude
|
||||
|
||||
<sect1 id="tutorial-conclusion">
|
||||
<title>Conclusion</title>
|
||||
|
||||
|
||||
<para>
|
||||
<productname>PostgreSQL</productname> has many features not
|
||||
touched upon in this tutorial introduction, which has been
|
||||
|
@ -1,4 +1,4 @@
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/errcodes.sgml,v 1.25 2008/10/04 21:56:52 tgl Exp $ -->
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/errcodes.sgml,v 1.26 2008/12/28 18:53:53 tgl Exp $ -->
|
||||
|
||||
<appendix id="errcodes-appendix">
|
||||
<title><productname>PostgreSQL</productname> Error Codes</title>
|
||||
@ -378,6 +378,18 @@
|
||||
<entry>invalid_argument_for_logarithm</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><literal>22014</literal></entry>
|
||||
<entry>INVALID ARGUMENT FOR NTILE FUNCTION</entry>
|
||||
<entry>invalid_argument_for_ntile_function</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><literal>22016</literal></entry>
|
||||
<entry>INVALID ARGUMENT FOR NTH_VALUE FUNCTION</entry>
|
||||
<entry>invalid_argument_for_nth_value_function</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><literal>2201F</literal></entry>
|
||||
<entry>INVALID ARGUMENT FOR POWER FUNCTION</entry>
|
||||
@ -990,6 +1002,12 @@
|
||||
<entry>grouping_error</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><literal>42P20</literal></entry>
|
||||
<entry>WINDOWING ERROR</entry>
|
||||
<entry>windowing_error</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><literal>42P19</literal></entry>
|
||||
<entry>INVALID RECURSION</entry>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.463 2008/12/19 16:25:16 petere Exp $ -->
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.464 2008/12/28 18:53:53 tgl Exp $ -->
|
||||
|
||||
<chapter id="functions">
|
||||
<title>Functions and Operators</title>
|
||||
@ -10149,6 +10149,278 @@ SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab;
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="functions-window">
|
||||
<title>Window Functions</title>
|
||||
|
||||
<indexterm zone="functions-window">
|
||||
<primary>window function</primary>
|
||||
<secondary>built-in</secondary>
|
||||
</indexterm>
|
||||
|
||||
<para>
|
||||
<firstterm>Window functions</firstterm> provide the ability to perform
|
||||
calculations across sets of rows that are related to the current query
|
||||
row. For information about this feature see
|
||||
<xref linkend="tutorial-window"> and
|
||||
<xref linkend="syntax-window-functions">.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The built-in window functions are listed in
|
||||
<xref linkend="functions-window-table">. Note that these functions
|
||||
<emphasis>must</> be invoked using window function syntax; that is an
|
||||
<literal>OVER</> clause is required.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
In addition to these functions, any built-in or user-defined aggregate
|
||||
function can be used as a window function (see
|
||||
<xref linkend="functions-aggregate"> for a list of the built-in aggregates).
|
||||
Aggregate functions act as window functions only when an <literal>OVER</>
|
||||
clause follows the call; otherwise they act as regular aggregates.
|
||||
</para>
|
||||
|
||||
<table id="functions-window-table">
|
||||
<title>General-Purpose Window Functions</title>
|
||||
|
||||
<tgroup cols="3">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Function</entry>
|
||||
<entry>Return Type</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<indexterm>
|
||||
<primary>row_number</primary>
|
||||
</indexterm>
|
||||
<function>row_number()</function>
|
||||
</entry>
|
||||
<entry>
|
||||
<type>bigint</type>
|
||||
</entry>
|
||||
<entry>number of the current row within its partition, counting from 1</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry>
|
||||
<indexterm>
|
||||
<primary>rank</primary>
|
||||
</indexterm>
|
||||
<function>rank()</function>
|
||||
</entry>
|
||||
<entry>
|
||||
<type>bigint</type>
|
||||
</entry>
|
||||
<entry>rank of the current row with gaps; same as <function>row_number</> of its first peer</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry>
|
||||
<indexterm>
|
||||
<primary>dense_rank</primary>
|
||||
</indexterm>
|
||||
<function>dense_rank()</function>
|
||||
</entry>
|
||||
<entry>
|
||||
<type>bigint</type>
|
||||
</entry>
|
||||
<entry>rank of the current row without gaps; this function counts peer groups</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry>
|
||||
<indexterm>
|
||||
<primary>percent_rank</primary>
|
||||
</indexterm>
|
||||
<function>percent_rank()</function>
|
||||
</entry>
|
||||
<entry>
|
||||
<type>double precision</type>
|
||||
</entry>
|
||||
<entry>relative rank of the current row: (<function>rank</> - 1) / (total rows - 1)</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry>
|
||||
<indexterm>
|
||||
<primary>cume_dist</primary>
|
||||
</indexterm>
|
||||
<function>cume_dist()</function>
|
||||
</entry>
|
||||
<entry>
|
||||
<type>double precision</type>
|
||||
</entry>
|
||||
<entry>relative rank of the current row: (number of rows preceding or peer with current row) / (total rows)</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry>
|
||||
<indexterm>
|
||||
<primary>ntile</primary>
|
||||
</indexterm>
|
||||
<function>ntile(<replaceable class="parameter">num_buckets</replaceable> <type>integer</>)</function>
|
||||
</entry>
|
||||
<entry>
|
||||
<type>integer</type>
|
||||
</entry>
|
||||
<entry>integer ranging from 1 to the argument value, dividing the
|
||||
partition as equally as possible</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry>
|
||||
<indexterm>
|
||||
<primary>lag</primary>
|
||||
</indexterm>
|
||||
<function>
|
||||
lag(<replaceable class="parameter">value</replaceable> <type>any</>
|
||||
[, <replaceable class="parameter">offset</replaceable> <type>integer</>
|
||||
[, <replaceable class="parameter">default</replaceable> <type>any</> ]])
|
||||
</function>
|
||||
</entry>
|
||||
<entry>
|
||||
<type>same type as <replaceable class="parameter">value</replaceable></type>
|
||||
</entry>
|
||||
<entry>
|
||||
returns <replaceable class="parameter">value</replaceable> evaluated at
|
||||
the row that is <replaceable class="parameter">offset</replaceable>
|
||||
rows before the current row within the partition; if there is no such
|
||||
row, instead return <replaceable class="parameter">default</replaceable>.
|
||||
Both <replaceable class="parameter">offset</replaceable> and
|
||||
<replaceable class="parameter">default</replaceable> are evaluated
|
||||
with respect to the current row. If omitted,
|
||||
<replaceable class="parameter">offset</replaceable> defaults to 1 and
|
||||
<replaceable class="parameter">default</replaceable> to null
|
||||
</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry>
|
||||
<indexterm>
|
||||
<primary>lead</primary>
|
||||
</indexterm>
|
||||
<function>
|
||||
lead(<replaceable class="parameter">value</replaceable> <type>any</>
|
||||
[, <replaceable class="parameter">offset</replaceable> <type>integer</>
|
||||
[, <replaceable class="parameter">default</replaceable> <type>any</> ]])
|
||||
</function>
|
||||
</entry>
|
||||
<entry>
|
||||
<type>same type as <replaceable class="parameter">value</replaceable></type>
|
||||
</entry>
|
||||
<entry>
|
||||
returns <replaceable class="parameter">value</replaceable> evaluated at
|
||||
the row that is <replaceable class="parameter">offset</replaceable>
|
||||
rows after the current row within the partition; if there is no such
|
||||
row, instead return <replaceable class="parameter">default</replaceable>.
|
||||
Both <replaceable class="parameter">offset</replaceable> and
|
||||
<replaceable class="parameter">default</replaceable> are evaluated
|
||||
with respect to the current row. If omitted,
|
||||
<replaceable class="parameter">offset</replaceable> defaults to 1 and
|
||||
<replaceable class="parameter">default</replaceable> to null
|
||||
</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry>
|
||||
<indexterm>
|
||||
<primary>first_value</primary>
|
||||
</indexterm>
|
||||
<function>first_value(<replaceable class="parameter">value</replaceable> <type>any</>)</function>
|
||||
</entry>
|
||||
<entry>
|
||||
<type>same type as <replaceable class="parameter">value</replaceable></type>
|
||||
</entry>
|
||||
<entry>
|
||||
returns <replaceable class="parameter">value</replaceable> evaluated
|
||||
at the row that is the first row of the window frame
|
||||
</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry>
|
||||
<indexterm>
|
||||
<primary>last_value</primary>
|
||||
</indexterm>
|
||||
<function>last_value(<replaceable class="parameter">value</replaceable> <type>any</>)</function>
|
||||
</entry>
|
||||
<entry>
|
||||
<type>same type as <replaceable class="parameter">value</replaceable></type>
|
||||
</entry>
|
||||
<entry>
|
||||
returns <replaceable class="parameter">value</replaceable> evaluated
|
||||
at the row that is the last row of the window frame
|
||||
</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry>
|
||||
<indexterm>
|
||||
<primary>nth_value</primary>
|
||||
</indexterm>
|
||||
<function>
|
||||
nth_value(<replaceable class="parameter">value</replaceable> <type>any</>, <replaceable class="parameter">nth</replaceable> <type>integer</>)
|
||||
</function>
|
||||
</entry>
|
||||
<entry>
|
||||
<type>same type as <replaceable class="parameter">value</replaceable></type>
|
||||
</entry>
|
||||
<entry>
|
||||
returns <replaceable class="parameter">value</replaceable> evaluated
|
||||
at the row that is the <replaceable class="parameter">nth</replaceable>
|
||||
row of the window frame (counting from 1); null if no such row
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
|
||||
<para>
|
||||
All of the functions listed in
|
||||
<xref linkend="functions-window-table"> depend on the sort ordering
|
||||
specified by the <literal>ORDER BY</> clause of the associated window
|
||||
definition. Rows that are not distinct in the <literal>ORDER BY</>
|
||||
ordering are said to be <firstterm>peers</>; the four ranking functions
|
||||
are defined so that they give the same answer for any two peer rows.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Note that <function>first_value</>, <function>last_value</>, and
|
||||
<function>nth_value</> consider only the rows within the <quote>window
|
||||
frame</>, that is the rows from the start of the partition through the
|
||||
last peer of the current row. This is particularly likely to give
|
||||
unintuitive results for <function>last_value</>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
When an aggregate function is used as a window function, it aggregates
|
||||
over the rows within the current row's window frame. To obtain
|
||||
aggregation over the whole partition, be sure to omit <literal>ORDER BY</>
|
||||
from the window definition. An aggregate used with <literal>ORDER BY</>
|
||||
produces a <quote>running sum</> type of behavior, which may or may not
|
||||
be what's wanted.
|
||||
</para>
|
||||
|
||||
<note>
|
||||
<para>
|
||||
The SQL standard defines a <literal>RESPECT NULLS</> or
|
||||
<literal>IGNORE NULLS</> option for <function>lead</>, <function>lag</>,
|
||||
<function>first_value</>, <function>last_value</>, and
|
||||
<function>nth_value</>. This is not implemented in
|
||||
<productname>PostgreSQL</productname>: the behavior is always the
|
||||
same as the standard's default, namely <literal>RESPECT NULLS</>.
|
||||
Likewise, the standard's <literal>FROM FIRST</> or <literal>FROM LAST</>
|
||||
option for <function>nth_value</> is not implemented: only the
|
||||
default <literal>FROM FIRST</> behavior is supported.
|
||||
</para>
|
||||
</note>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="functions-subquery">
|
||||
<title>Subquery Expressions</title>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/queries.sgml,v 1.50 2008/10/14 00:41:34 tgl Exp $ -->
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/queries.sgml,v 1.51 2008/12/28 18:53:54 tgl Exp $ -->
|
||||
|
||||
<chapter id="queries">
|
||||
<title>Queries</title>
|
||||
@ -949,6 +949,57 @@ SELECT product_id, p.name, (sum(s.units) * (p.price - p.cost)) AS profit
|
||||
5000. Note that the aggregate expressions do not necessarily need
|
||||
to be the same in all parts of the query.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If a query contains aggregate function calls, but no <literal>GROUP BY</>
|
||||
clause, grouping still occurs: the result is a single group row (or
|
||||
perhaps no rows at all, if the single row is then eliminated by
|
||||
<literal>HAVING</>).
|
||||
The same is true if it contains a <literal>HAVING</> clause, even
|
||||
without any aggregate function calls or <literal>GROUP BY</> clause.
|
||||
</para>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="queries-window">
|
||||
<title>Window Function Processing</>
|
||||
|
||||
<indexterm zone="queries-window">
|
||||
<primary>window function</primary>
|
||||
<secondary>order of execution</>
|
||||
</indexterm>
|
||||
|
||||
<para>
|
||||
If the query contains any window functions (see
|
||||
<xref linkend="tutorial-window"> and
|
||||
<xref linkend="syntax-window-functions">), these functions are evaluated
|
||||
after any grouping, aggregation, and <literal>HAVING</> filtering is
|
||||
performed. That is, if the query uses any aggregates, <literal>GROUP
|
||||
BY</>, or <literal>HAVING</>, then the rows seen by the window functions
|
||||
are the group rows instead of the original table rows from
|
||||
<literal>FROM</>/<literal>WHERE</>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
When multiple window functions are used, all the window functions having
|
||||
syntactically equivalent <literal>PARTITION BY</> and <literal>ORDER BY</>
|
||||
clauses in their window definitions are guaranteed to be evaluated in a
|
||||
single pass over the data. Therefore they will see the same sort ordering,
|
||||
even if the <literal>ORDER BY</> does not uniquely determine an ordering.
|
||||
However, no guarantees are made about the evaluation of functions having
|
||||
different <literal>PARTITION BY</> or <literal>ORDER BY</> specifications.
|
||||
(In such cases a sort step is typically required between the passes of
|
||||
window function evaluations, and the sort is not guaranteed to preserve
|
||||
ordering of rows that its <literal>ORDER BY</> sees as equivalent.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Currently, use of window functions always forces sorting, and so the
|
||||
query output will be ordered according to one or another of the window
|
||||
functions' <literal>PARTITION BY</>/<literal>ORDER BY</> clauses.
|
||||
It is not recommendable to rely on this, however. Use an explicit
|
||||
top-level <literal>ORDER BY</> clause if you want to be sure the
|
||||
results are sorted in a particular way.
|
||||
</para>
|
||||
</sect2>
|
||||
</sect1>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/query.sgml,v 1.50 2007/02/01 00:28:17 momjian Exp $ -->
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/query.sgml,v 1.51 2008/12/28 18:53:54 tgl Exp $ -->
|
||||
|
||||
<chapter id="tutorial-sql">
|
||||
<title>The <acronym>SQL</acronym> Language</title>
|
||||
@ -621,7 +621,7 @@ SELECT W1.city, W1.temp_lo AS low, W1.temp_hi AS high,
|
||||
San Francisco | 43 | 57 | San Francisco | 46 | 50
|
||||
Hayward | 37 | 54 | San Francisco | 46 | 50
|
||||
(2 rows)
|
||||
</programlisting>
|
||||
</programlisting>
|
||||
|
||||
Here we have relabeled the weather table as <literal>W1</> and
|
||||
<literal>W2</> to be able to distinguish the left and right side
|
||||
@ -651,9 +651,9 @@ SELECT *
|
||||
<indexterm><primary>min</primary></indexterm>
|
||||
<indexterm><primary>sum</primary></indexterm>
|
||||
|
||||
Like most other relational database products,
|
||||
Like most other relational database products,
|
||||
<productname>PostgreSQL</productname> supports
|
||||
aggregate functions.
|
||||
<firstterm>aggregate functions</>.
|
||||
An aggregate function computes a single result from multiple input rows.
|
||||
For example, there are aggregates to compute the
|
||||
<function>count</function>, <function>sum</function>,
|
||||
@ -815,7 +815,7 @@ SELECT city, max(temp_lo)
|
||||
|
||||
<para>
|
||||
You can update existing rows using the
|
||||
<command>UPDATE</command> command.
|
||||
<command>UPDATE</command> command.
|
||||
Suppose you discover the temperature readings are
|
||||
all off by 2 degrees after November 28. You can correct the
|
||||
data as follows:
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
$PostgreSQL: pgsql/doc/src/sgml/ref/select.sgml,v 1.112 2008/12/01 09:38:08 petere Exp $
|
||||
$PostgreSQL: pgsql/doc/src/sgml/ref/select.sgml,v 1.113 2008/12/28 18:53:54 tgl Exp $
|
||||
PostgreSQL documentation
|
||||
-->
|
||||
|
||||
@ -39,6 +39,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
|
||||
[ WHERE <replaceable class="parameter">condition</replaceable> ]
|
||||
[ GROUP BY <replaceable class="parameter">expression</replaceable> [, ...] ]
|
||||
[ HAVING <replaceable class="parameter">condition</replaceable> [, ...] ]
|
||||
[ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceable class="parameter">window_definition</replaceable> ) [, ...] ]
|
||||
[ { UNION | INTERSECT | EXCEPT } [ ALL ] <replaceable class="parameter">select</replaceable> ]
|
||||
[ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
|
||||
[ LIMIT { <replaceable class="parameter">count</replaceable> | ALL } ]
|
||||
@ -566,6 +567,67 @@ HAVING <replaceable class="parameter">condition</replaceable>
|
||||
</para>
|
||||
</refsect2>
|
||||
|
||||
<refsect2 id="SQL-WINDOW">
|
||||
<title id="sql-window-title"><literal>WINDOW</literal> Clause</title>
|
||||
|
||||
<para>
|
||||
The optional <literal>WINDOW</literal> clause has the general form
|
||||
<synopsis>
|
||||
WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceable class="parameter">window_definition</replaceable> ) [, ...]
|
||||
</synopsis>
|
||||
where <replaceable class="parameter">window_name</replaceable> is
|
||||
a name that can be referenced from subsequent window definitions or
|
||||
<literal>OVER</> clauses, and
|
||||
<replaceable class="parameter">window_definition</replaceable> is
|
||||
<synopsis>
|
||||
[ <replaceable class="parameter">existing_window_name</replaceable> ]
|
||||
[ PARTITION BY <replaceable class="parameter">expression</replaceable> [, ...] ]
|
||||
[ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
|
||||
</synopsis>
|
||||
The elements of the <literal>PARTITION BY</> list are interpreted in
|
||||
the same fashion as elements of a
|
||||
<xref linkend="sql-groupby" endterm="sql-groupby-title">, and
|
||||
the elements of the <literal>ORDER BY</> list are interpreted in the
|
||||
same fashion as elements of an
|
||||
<xref linkend="sql-orderby" endterm="sql-orderby-title">.
|
||||
The only difference is that these expressions can contain aggregate
|
||||
function calls, which are not allowed in a regular <literal>GROUP BY</>
|
||||
clause. They are allowed here because windowing occurs after grouping
|
||||
and aggregation.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If an <replaceable class="parameter">existing_window_name</replaceable>
|
||||
is specified it must refer to an earlier entry in the <literal>WINDOW</>
|
||||
list; the new window copies its partitioning clause from that entry,
|
||||
as well as its ordering clause if any. In this case the new window cannot
|
||||
specify its own <literal>PARTITION BY</> clause, and it can specify
|
||||
<literal>ORDER BY</> only if the copied window does not have one.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The purpose of a <literal>WINDOW</literal> clause is to specify the
|
||||
behavior of <firstterm>window functions</> appearing in the query's
|
||||
<xref linkend="sql-select-list" endterm="sql-select-list-title"> or
|
||||
<xref linkend="sql-orderby" endterm="sql-orderby-title">. These functions
|
||||
can reference the <literal>WINDOW</literal> clause entries by name
|
||||
in their <literal>OVER</> clauses. A <literal>WINDOW</literal> clause
|
||||
entry does not have to be referenced anywhere, however; if it is not
|
||||
used in the query it is simply ignored. It is possible to use window
|
||||
functions without any <literal>WINDOW</literal> clause at all, since
|
||||
a window function call can specify its window definition directly in
|
||||
its <literal>OVER</> clause. However, the <literal>WINDOW</literal>
|
||||
clause saves typing when the same window definition is needed for more
|
||||
than one window function.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Window functions are described in detail in
|
||||
<xref linkend="tutorial-window"> and
|
||||
<xref linkend="syntax-window-functions">.
|
||||
</para>
|
||||
</refsect2>
|
||||
|
||||
<refsect2 id="sql-select-list">
|
||||
<title id="sql-select-list-title"><command>SELECT</command> List</title>
|
||||
|
||||
@ -922,7 +984,7 @@ FETCH { FIRST | NEXT } [ <replaceable class="parameter">count</replaceable> ] {
|
||||
constants for the offset or fetch count, parentheses will be
|
||||
necessary in most cases. If the fetch count is omitted, it
|
||||
defaults to 1.
|
||||
</para>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
When using <literal>LIMIT</>, it is a good idea to use an
|
||||
@ -1387,6 +1449,19 @@ SELECT distributors.* WHERE distributors.name = 'Westward';
|
||||
</para>
|
||||
</refsect2>
|
||||
|
||||
<refsect2>
|
||||
<title><literal>WINDOW</literal> Clause Restrictions</title>
|
||||
|
||||
<para>
|
||||
The SQL standard provides for an optional <quote>framing clause</>,
|
||||
introduced by the key word <literal>RANGE</> or <literal>ROWS</>,
|
||||
in window definitions. <productname>PostgreSQL</productname> does
|
||||
not yet implement framing clauses, and always follows the
|
||||
default framing behavior, which is equivalent to the framing clause
|
||||
<literal>ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW</>.
|
||||
</para>
|
||||
</refsect2>
|
||||
|
||||
<refsect2>
|
||||
<title><literal>LIMIT</literal> and <literal>OFFSET</literal></title>
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
$PostgreSQL: pgsql/doc/src/sgml/ref/select_into.sgml,v 1.43 2008/11/14 10:22:47 petere Exp $
|
||||
$PostgreSQL: pgsql/doc/src/sgml/ref/select_into.sgml,v 1.44 2008/12/28 18:53:54 tgl Exp $
|
||||
PostgreSQL documentation
|
||||
-->
|
||||
|
||||
@ -29,6 +29,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
|
||||
[ WHERE <replaceable class="parameter">condition</replaceable> ]
|
||||
[ GROUP BY <replaceable class="parameter">expression</replaceable> [, ...] ]
|
||||
[ HAVING <replaceable class="parameter">condition</replaceable> [, ...] ]
|
||||
[ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceable class="parameter">window_definition</replaceable> ) [, ...] ]
|
||||
[ { UNION | INTERSECT | EXCEPT } [ ALL ] <replaceable class="parameter">select</replaceable> ]
|
||||
[ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
|
||||
[ LIMIT { <replaceable class="parameter">count</replaceable> | ALL } ]
|
||||
|
@ -1,4 +1,4 @@
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/syntax.sgml,v 1.126 2008/12/09 20:52:03 tgl Exp $ -->
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/syntax.sgml,v 1.127 2008/12/28 18:53:54 tgl Exp $ -->
|
||||
|
||||
<chapter id="sql-syntax">
|
||||
<title>SQL Syntax</title>
|
||||
@ -1201,6 +1201,12 @@ SELECT 3 OPERATOR(pg_catalog.+) 4;
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
A window function call.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
A type cast.
|
||||
@ -1445,7 +1451,7 @@ $1.somecolumn
|
||||
enclosed in parentheses:
|
||||
|
||||
<synopsis>
|
||||
<replaceable>function</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional> )
|
||||
<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional> )
|
||||
</synopsis>
|
||||
</para>
|
||||
|
||||
@ -1480,7 +1486,7 @@ sqrt(2)
|
||||
<synopsis>
|
||||
<replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] )
|
||||
<replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] )
|
||||
<replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] )
|
||||
<replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable>)
|
||||
<replaceable>aggregate_name</replaceable> ( * )
|
||||
</synopsis>
|
||||
|
||||
@ -1488,7 +1494,7 @@ sqrt(2)
|
||||
defined aggregate (possibly qualified with a schema name), and
|
||||
<replaceable>expression</replaceable> is
|
||||
any value expression that does not itself contain an aggregate
|
||||
expression.
|
||||
expression or a window function call.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
@ -1550,6 +1556,89 @@ sqrt(2)
|
||||
</note>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="syntax-window-functions">
|
||||
<title>Window Function Calls</title>
|
||||
|
||||
<indexterm zone="syntax-window-functions">
|
||||
<primary>window function</primary>
|
||||
<secondary>invocation</secondary>
|
||||
</indexterm>
|
||||
|
||||
<indexterm zone="syntax-window-functions">
|
||||
<primary>OVER clause</primary>
|
||||
</indexterm>
|
||||
|
||||
<para>
|
||||
A <firstterm>window function call</firstterm> represents the application
|
||||
of an aggregate-like function over some portion of the rows selected
|
||||
by a query. Unlike regular aggregate function calls, this is not tied
|
||||
to grouping of the selected rows into a single output row — each
|
||||
row remains separate in the query output. However the window function
|
||||
is able to scan all the rows that would be part of the current row's
|
||||
group according to the grouping specification (<literal>PARTITION BY</>
|
||||
list) of the window function call.
|
||||
The syntax of a window function call is one of the following:
|
||||
|
||||
<synopsis>
|
||||
<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER ( <replaceable class="parameter">window_definition</replaceable> )
|
||||
<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER <replaceable>window_name</replaceable>
|
||||
<replaceable>function_name</replaceable> ( * ) OVER ( <replaceable class="parameter">window_definition</replaceable> )
|
||||
<replaceable>function_name</replaceable> ( * ) OVER <replaceable>window_name</replaceable>
|
||||
</synopsis>
|
||||
where <replaceable class="parameter">window_definition</replaceable>
|
||||
has the syntax
|
||||
<synopsis>
|
||||
[ <replaceable class="parameter">window_name</replaceable> ]
|
||||
[ PARTITION BY <replaceable class="parameter">expression</replaceable> [, ...] ]
|
||||
[ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
|
||||
</synopsis>
|
||||
|
||||
Here, <replaceable>expression</replaceable> represents any value
|
||||
expression that does not itself contain window function calls.
|
||||
The <literal>PARTITION BY</> and <literal>ORDER BY</> lists have
|
||||
essentially the same syntax and semantics as <literal>GROUP BY</>
|
||||
and <literal>ORDER BY</> clauses of the whole query.
|
||||
<replaceable>window_name</replaceable> is a reference to a named window
|
||||
specification defined in the query's <literal>WINDOW</literal> clause.
|
||||
Named window specifications are usually referenced with just
|
||||
<literal>OVER</> <replaceable>window_name</replaceable>, but it is
|
||||
also possible to write a window name inside the parentheses and then
|
||||
optionally override its ordering clause with <literal>ORDER BY</>.
|
||||
This latter syntax follows the same rules as modifying an existing
|
||||
window name within the <literal>WINDOW</literal> clause; see the
|
||||
<xref linkend="sql-select" endterm="sql-select-title"> reference
|
||||
page for details.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The built-in window functions are described in <xref
|
||||
linkend="functions-window-table">. Also, any built-in or
|
||||
user-defined aggregate function can be used as a window function.
|
||||
Currently, there is no provision for user-defined window functions
|
||||
other than aggregates.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The syntaxes using <literal>*</> are used for calling parameter-less
|
||||
aggregate functions as window functions, for example
|
||||
<literal>count(*) OVER (PARTITION BY x ORDER BY y)</>.
|
||||
<literal>*</> is customarily not used for non-aggregate window functions.
|
||||
Aggregate window functions, unlike normal aggregate functions, do not
|
||||
allow <literal>DISTINCT</> to be used within the function argument list.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Window function calls are permitted only in the <literal>SELECT</literal>
|
||||
list and the <literal>ORDER BY</> clause of the query.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
More information about window functions can be found in
|
||||
<xref linkend="tutorial-window"> and
|
||||
<xref linkend="queries-window">.
|
||||
</para>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="sql-syntax-type-casts">
|
||||
<title>Type Casts</title>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/xaggr.sgml,v 1.36 2008/11/20 21:10:44 tgl Exp $ -->
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/xaggr.sgml,v 1.37 2008/12/28 18:53:54 tgl Exp $ -->
|
||||
|
||||
<sect1 id="xaggr">
|
||||
<title>User-Defined Aggregates</title>
|
||||
@ -167,10 +167,13 @@ SELECT attrelid::regclass, array_accum(atttypid::regtype)
|
||||
<para>
|
||||
A function written in C can detect that it is being called as an
|
||||
aggregate transition or final function by seeing if it was passed
|
||||
an <structname>AggState</> node as the function call <quote>context</>,
|
||||
an <structname>AggState</> or <structname>WindowAggState</> node
|
||||
as the function call <quote>context</>,
|
||||
for example by:
|
||||
<programlisting>
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
if (fcinfo->context &&
|
||||
(IsA(fcinfo->context, AggState) ||
|
||||
IsA(fcinfo->context, WindowAggState)))
|
||||
</programlisting>
|
||||
One reason for checking this is that when it is true, the first input
|
||||
must be a temporary transition value and can therefore safely be modified
|
||||
|
@ -8,7 +8,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.83 2008/12/19 16:25:17 petere Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.84 2008/12/28 18:53:54 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -1480,6 +1480,14 @@ find_expr_references_walker(Node *node,
|
||||
context->addrs);
|
||||
/* fall through to examine arguments */
|
||||
}
|
||||
else if (IsA(node, WindowFunc))
|
||||
{
|
||||
WindowFunc *wfunc = (WindowFunc *) node;
|
||||
|
||||
add_object_address(OCLASS_PROC, wfunc->winfnoid, 0,
|
||||
context->addrs);
|
||||
/* fall through to examine arguments */
|
||||
}
|
||||
else if (IsA(node, SubPlan))
|
||||
{
|
||||
/* Extra work needed here if we ever need this case */
|
||||
@ -1602,6 +1610,7 @@ find_expr_references_walker(Node *node,
|
||||
/* query_tree_walker ignores ORDER BY etc, but we need those opers */
|
||||
find_expr_references_walker((Node *) query->sortClause, context);
|
||||
find_expr_references_walker((Node *) query->groupClause, context);
|
||||
find_expr_references_walker((Node *) query->windowClause, context);
|
||||
find_expr_references_walker((Node *) query->distinctClause, context);
|
||||
|
||||
/* Examine substructure of query */
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.347 2008/11/29 00:13:21 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.348 2008/12/28 18:53:54 tgl Exp $
|
||||
*
|
||||
*
|
||||
* INTERFACE ROUTINES
|
||||
@ -2138,6 +2138,10 @@ cookDefault(ParseState *pstate,
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_GROUPING_ERROR),
|
||||
errmsg("cannot use aggregate function in default expression")));
|
||||
if (pstate->p_hasWindowFuncs)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("cannot use window function in default expression")));
|
||||
|
||||
/*
|
||||
* Coerce the expression to the correct type and typmod, if given. This
|
||||
@ -2211,6 +2215,10 @@ cookConstraint(ParseState *pstate,
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_GROUPING_ERROR),
|
||||
errmsg("cannot use aggregate function in check constraint")));
|
||||
if (pstate->p_hasWindowFuncs)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("cannot use window function in check constraint")));
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.157 2008/12/19 18:25:19 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.158 2008/12/28 18:53:54 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -80,6 +80,8 @@ ProcedureCreate(const char *procedureName,
|
||||
float4 prorows)
|
||||
{
|
||||
Oid retval;
|
||||
/* XXX we don't currently have a way to make new window functions */
|
||||
bool isWindowFunc = false;
|
||||
int parameterCount;
|
||||
int allParamCount;
|
||||
Oid *allParams;
|
||||
@ -292,8 +294,7 @@ ProcedureCreate(const char *procedureName,
|
||||
values[Anum_pg_proc_prorows - 1] = Float4GetDatum(prorows);
|
||||
values[Anum_pg_proc_provariadic - 1] = ObjectIdGetDatum(variadicType);
|
||||
values[Anum_pg_proc_proisagg - 1] = BoolGetDatum(isAgg);
|
||||
/* XXX we don't currently have a way to make new window functions */
|
||||
values[Anum_pg_proc_proiswindow - 1] = BoolGetDatum(false);
|
||||
values[Anum_pg_proc_proiswindow - 1] = BoolGetDatum(isWindowFunc);
|
||||
values[Anum_pg_proc_prosecdef - 1] = BoolGetDatum(security_definer);
|
||||
values[Anum_pg_proc_proisstrict - 1] = BoolGetDatum(isStrict);
|
||||
values[Anum_pg_proc_proretset - 1] = BoolGetDatum(returnsSet);
|
||||
@ -440,18 +441,31 @@ ProcedureCreate(const char *procedureName,
|
||||
}
|
||||
}
|
||||
|
||||
/* Can't change aggregate status, either */
|
||||
/* Can't change aggregate or window-function status, either */
|
||||
if (oldproc->proisagg != isAgg)
|
||||
{
|
||||
if (oldproc->proisagg)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("function \"%s\" is an aggregate",
|
||||
errmsg("function \"%s\" is an aggregate function",
|
||||
procedureName)));
|
||||
else
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("function \"%s\" is not an aggregate",
|
||||
errmsg("function \"%s\" is not an aggregate function",
|
||||
procedureName)));
|
||||
}
|
||||
if (oldproc->proiswindow != isWindowFunc)
|
||||
{
|
||||
if (oldproc->proiswindow)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("function \"%s\" is a window function",
|
||||
procedureName)));
|
||||
else
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("function \"%s\" is not a window function",
|
||||
procedureName)));
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1994-5, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.181 2008/11/19 01:10:23 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.182 2008/12/28 18:53:55 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -590,6 +590,9 @@ explain_outNode(StringInfo str,
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case T_WindowAgg:
|
||||
pname = "WindowAgg";
|
||||
break;
|
||||
case T_Unique:
|
||||
pname = "Unique";
|
||||
break;
|
||||
|
@ -10,7 +10,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/functioncmds.c,v 1.103 2008/12/18 18:20:33 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/functioncmds.c,v 1.104 2008/12/28 18:53:55 tgl Exp $
|
||||
*
|
||||
* DESCRIPTION
|
||||
* These routines take the parse tree and pick out the
|
||||
@ -321,6 +321,10 @@ examine_parameter_list(List *parameters, Oid languageOid,
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_GROUPING_ERROR),
|
||||
errmsg("cannot use aggregate function in parameter default value")));
|
||||
if (pstate->p_hasWindowFuncs)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("cannot use window function in parameter default value")));
|
||||
|
||||
*parameterDefaults = lappend(*parameterDefaults, def);
|
||||
have_defaults = true;
|
||||
@ -1538,6 +1542,10 @@ CreateCast(CreateCastStmt *stmt)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("cast function must not be an aggregate function")));
|
||||
if (procstruct->proiswindow)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("cast function must not be a window function")));
|
||||
if (procstruct->proretset)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
|
@ -10,7 +10,7 @@
|
||||
* Copyright (c) 2002-2008, PostgreSQL Global Development Group
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.93 2008/12/13 02:29:21 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.94 2008/12/28 18:53:55 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -347,6 +347,10 @@ EvaluateParams(PreparedStatement *pstmt, List *params,
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_GROUPING_ERROR),
|
||||
errmsg("cannot use aggregate function in EXECUTE parameter")));
|
||||
if (pstate->p_hasWindowFuncs)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("cannot use window function in EXECUTE parameter")));
|
||||
|
||||
given_type_id = exprType(expr);
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.274 2008/12/15 21:35:31 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.275 2008/12/28 18:53:55 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -5506,6 +5506,10 @@ ATPrepAlterColumnType(List **wqueue,
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_GROUPING_ERROR),
|
||||
errmsg("cannot use aggregate function in transform expression")));
|
||||
if (pstate->p_hasWindowFuncs)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("cannot use window function in transform expression")));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.127 2008/11/30 19:01:29 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.128 2008/12/28 18:53:55 tgl Exp $
|
||||
*
|
||||
* DESCRIPTION
|
||||
* The "DefineFoo" routines take the parse tree and pick out the
|
||||
@ -2255,6 +2255,10 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_GROUPING_ERROR),
|
||||
errmsg("cannot use aggregate function in check constraint")));
|
||||
if (pstate->p_hasWindowFuncs)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("cannot use window function in check constraint")));
|
||||
|
||||
/*
|
||||
* Convert to string form for storage.
|
||||
|
@ -4,7 +4,7 @@
|
||||
# Makefile for executor
|
||||
#
|
||||
# IDENTIFICATION
|
||||
# $PostgreSQL: pgsql/src/backend/executor/Makefile,v 1.28 2008/10/04 21:56:52 tgl Exp $
|
||||
# $PostgreSQL: pgsql/src/backend/executor/Makefile,v 1.29 2008/12/28 18:53:55 tgl Exp $
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
@ -22,6 +22,6 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \
|
||||
nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
|
||||
nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \
|
||||
nodeLimit.o nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
|
||||
tstoreReceiver.o spi.o
|
||||
nodeWindowAgg.o tstoreReceiver.o spi.o
|
||||
|
||||
include $(top_srcdir)/src/backend/common.mk
|
||||
|
@ -6,7 +6,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/backend/executor/execAmi.c,v 1.101 2008/10/28 17:13:51 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/executor/execAmi.c,v 1.102 2008/12/28 18:53:55 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -20,6 +20,7 @@
|
||||
#include "executor/nodeBitmapHeapscan.h"
|
||||
#include "executor/nodeBitmapIndexscan.h"
|
||||
#include "executor/nodeBitmapOr.h"
|
||||
#include "executor/nodeCtescan.h"
|
||||
#include "executor/nodeFunctionscan.h"
|
||||
#include "executor/nodeGroup.h"
|
||||
#include "executor/nodeGroup.h"
|
||||
@ -40,7 +41,7 @@
|
||||
#include "executor/nodeTidscan.h"
|
||||
#include "executor/nodeUnique.h"
|
||||
#include "executor/nodeValuesscan.h"
|
||||
#include "executor/nodeCtescan.h"
|
||||
#include "executor/nodeWindowAgg.h"
|
||||
#include "executor/nodeWorktablescan.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#include "utils/syscache.h"
|
||||
@ -210,6 +211,10 @@ ExecReScan(PlanState *node, ExprContext *exprCtxt)
|
||||
ExecReScanAgg((AggState *) node, exprCtxt);
|
||||
break;
|
||||
|
||||
case T_WindowAggState:
|
||||
ExecReScanWindowAgg((WindowAggState *) node, exprCtxt);
|
||||
break;
|
||||
|
||||
case T_UniqueState:
|
||||
ExecReScanUnique((UniqueState *) node, exprCtxt);
|
||||
break;
|
||||
|
@ -12,7 +12,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/executor/execProcnode.c,v 1.63 2008/10/04 21:56:53 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/executor/execProcnode.c,v 1.64 2008/12/28 18:53:55 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -85,6 +85,7 @@
|
||||
#include "executor/nodeBitmapHeapscan.h"
|
||||
#include "executor/nodeBitmapIndexscan.h"
|
||||
#include "executor/nodeBitmapOr.h"
|
||||
#include "executor/nodeCtescan.h"
|
||||
#include "executor/nodeFunctionscan.h"
|
||||
#include "executor/nodeGroup.h"
|
||||
#include "executor/nodeHash.h"
|
||||
@ -104,7 +105,7 @@
|
||||
#include "executor/nodeTidscan.h"
|
||||
#include "executor/nodeUnique.h"
|
||||
#include "executor/nodeValuesscan.h"
|
||||
#include "executor/nodeCtescan.h"
|
||||
#include "executor/nodeWindowAgg.h"
|
||||
#include "executor/nodeWorktablescan.h"
|
||||
#include "miscadmin.h"
|
||||
|
||||
@ -260,6 +261,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
|
||||
estate, eflags);
|
||||
break;
|
||||
|
||||
case T_WindowAgg:
|
||||
result = (PlanState *) ExecInitWindowAgg((WindowAgg *) node,
|
||||
estate, eflags);
|
||||
break;
|
||||
|
||||
case T_Unique:
|
||||
result = (PlanState *) ExecInitUnique((Unique *) node,
|
||||
estate, eflags);
|
||||
@ -425,6 +431,10 @@ ExecProcNode(PlanState *node)
|
||||
result = ExecAgg((AggState *) node);
|
||||
break;
|
||||
|
||||
case T_WindowAggState:
|
||||
result = ExecWindowAgg((WindowAggState *) node);
|
||||
break;
|
||||
|
||||
case T_UniqueState:
|
||||
result = ExecUnique((UniqueState *) node);
|
||||
break;
|
||||
@ -601,6 +611,10 @@ ExecCountSlotsNode(Plan *node)
|
||||
case T_Agg:
|
||||
return ExecCountSlotsAgg((Agg *) node);
|
||||
|
||||
case T_WindowAgg:
|
||||
return ExecCountSlotsWindowAgg((WindowAgg *) node);
|
||||
break;
|
||||
|
||||
case T_Unique:
|
||||
return ExecCountSlotsUnique((Unique *) node);
|
||||
|
||||
@ -749,6 +763,10 @@ ExecEndNode(PlanState *node)
|
||||
ExecEndAgg((AggState *) node);
|
||||
break;
|
||||
|
||||
case T_WindowAggState:
|
||||
ExecEndWindowAgg((WindowAggState *) node);
|
||||
break;
|
||||
|
||||
case T_UniqueState:
|
||||
ExecEndUnique((UniqueState *) node);
|
||||
break;
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.238 2008/12/18 19:38:22 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.239 2008/12/28 18:53:55 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -62,6 +62,9 @@ static Datum ExecEvalArrayRef(ArrayRefExprState *astate,
|
||||
static Datum ExecEvalAggref(AggrefExprState *aggref,
|
||||
ExprContext *econtext,
|
||||
bool *isNull, ExprDoneCond *isDone);
|
||||
static Datum ExecEvalWindowFunc(WindowFuncExprState *wfunc,
|
||||
ExprContext *econtext,
|
||||
bool *isNull, ExprDoneCond *isDone);
|
||||
static Datum ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
|
||||
bool *isNull, ExprDoneCond *isDone);
|
||||
static Datum ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
|
||||
@ -443,6 +446,27 @@ ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext,
|
||||
return econtext->ecxt_aggvalues[aggref->aggno];
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* ExecEvalWindowFunc
|
||||
*
|
||||
* Returns a Datum whose value is the value of the precomputed
|
||||
* window function found in the given expression context.
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
static Datum
|
||||
ExecEvalWindowFunc(WindowFuncExprState *wfunc, ExprContext *econtext,
|
||||
bool *isNull, ExprDoneCond *isDone)
|
||||
{
|
||||
if (isDone)
|
||||
*isDone = ExprSingleResult;
|
||||
|
||||
if (econtext->ecxt_aggvalues == NULL) /* safety check */
|
||||
elog(ERROR, "no window functions in this expression context");
|
||||
|
||||
*isNull = econtext->ecxt_aggnulls[wfunc->wfuncno];
|
||||
return econtext->ecxt_aggvalues[wfunc->wfuncno];
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* ExecEvalVar
|
||||
*
|
||||
@ -4062,12 +4086,12 @@ ExecEvalExprSwitchContext(ExprState *expression,
|
||||
* executions of the expression are needed. Typically the context will be
|
||||
* the same as the per-query context of the associated ExprContext.
|
||||
*
|
||||
* Any Aggref and SubPlan nodes found in the tree are added to the lists
|
||||
* of such nodes held by the parent PlanState. Otherwise, we do very little
|
||||
* initialization here other than building the state-node tree. Any nontrivial
|
||||
* work associated with initializing runtime info for a node should happen
|
||||
* during the first actual evaluation of that node. (This policy lets us
|
||||
* avoid work if the node is never actually evaluated.)
|
||||
* Any Aggref, WindowFunc, or SubPlan nodes found in the tree are added to the
|
||||
* lists of such nodes held by the parent PlanState. Otherwise, we do very
|
||||
* little initialization here other than building the state-node tree. Any
|
||||
* nontrivial work associated with initializing runtime info for a node should
|
||||
* happen during the first actual evaluation of that node. (This policy lets
|
||||
* us avoid work if the node is never actually evaluated.)
|
||||
*
|
||||
* Note: there is no ExecEndExpr function; we assume that any resource
|
||||
* cleanup needed will be handled by just releasing the memory context
|
||||
@ -4145,11 +4169,49 @@ ExecInitExpr(Expr *node, PlanState *parent)
|
||||
else
|
||||
{
|
||||
/* planner messed up */
|
||||
elog(ERROR, "aggref found in non-Agg plan node");
|
||||
elog(ERROR, "Aggref found in non-Agg plan node");
|
||||
}
|
||||
state = (ExprState *) astate;
|
||||
}
|
||||
break;
|
||||
case T_WindowFunc:
|
||||
{
|
||||
WindowFunc *wfunc = (WindowFunc *) node;
|
||||
WindowFuncExprState *wfstate = makeNode(WindowFuncExprState);
|
||||
|
||||
wfstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWindowFunc;
|
||||
if (parent && IsA(parent, WindowAggState))
|
||||
{
|
||||
WindowAggState *winstate = (WindowAggState *) parent;
|
||||
int nfuncs;
|
||||
|
||||
winstate->funcs = lcons(wfstate, winstate->funcs);
|
||||
nfuncs = ++winstate->numfuncs;
|
||||
if (wfunc->winagg)
|
||||
winstate->numaggs++;
|
||||
|
||||
wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args,
|
||||
parent);
|
||||
|
||||
/*
|
||||
* Complain if the windowfunc's arguments contain any
|
||||
* windowfuncs; nested window functions are semantically
|
||||
* nonsensical. (This should have been caught earlier,
|
||||
* but we defend against it here anyway.)
|
||||
*/
|
||||
if (nfuncs != winstate->numfuncs)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("window function calls cannot be nested")));
|
||||
}
|
||||
else
|
||||
{
|
||||
/* planner messed up */
|
||||
elog(ERROR, "WindowFunc found in non-WindowAgg plan node");
|
||||
}
|
||||
state = (ExprState *) wfstate;
|
||||
}
|
||||
break;
|
||||
case T_ArrayRef:
|
||||
{
|
||||
ArrayRef *aref = (ArrayRef *) node;
|
||||
|
1854
src/backend/executor/nodeWindowAgg.c
Normal file
1854
src/backend/executor/nodeWindowAgg.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -15,7 +15,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.416 2008/12/19 16:25:17 petere Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.417 2008/12/28 18:53:55 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -668,6 +668,32 @@ _copyAgg(Agg *from)
|
||||
return newnode;
|
||||
}
|
||||
|
||||
/*
|
||||
* _copyWindowAgg
|
||||
*/
|
||||
static WindowAgg *
|
||||
_copyWindowAgg(WindowAgg *from)
|
||||
{
|
||||
WindowAgg *newnode = makeNode(WindowAgg);
|
||||
|
||||
CopyPlanFields((Plan *) from, (Plan *) newnode);
|
||||
|
||||
COPY_SCALAR_FIELD(partNumCols);
|
||||
if (from->partNumCols > 0)
|
||||
{
|
||||
COPY_POINTER_FIELD(partColIdx, from->partNumCols * sizeof(AttrNumber));
|
||||
COPY_POINTER_FIELD(partOperators, from->partNumCols * sizeof(Oid));
|
||||
}
|
||||
COPY_SCALAR_FIELD(ordNumCols);
|
||||
if (from->ordNumCols > 0)
|
||||
{
|
||||
COPY_POINTER_FIELD(ordColIdx, from->ordNumCols * sizeof(AttrNumber));
|
||||
COPY_POINTER_FIELD(ordOperators, from->ordNumCols * sizeof(Oid));
|
||||
}
|
||||
|
||||
return newnode;
|
||||
}
|
||||
|
||||
/*
|
||||
* _copyUnique
|
||||
*/
|
||||
@ -931,6 +957,25 @@ _copyAggref(Aggref *from)
|
||||
return newnode;
|
||||
}
|
||||
|
||||
/*
|
||||
* _copyWindowFunc
|
||||
*/
|
||||
static WindowFunc *
|
||||
_copyWindowFunc(WindowFunc *from)
|
||||
{
|
||||
WindowFunc *newnode = makeNode(WindowFunc);
|
||||
|
||||
COPY_SCALAR_FIELD(winfnoid);
|
||||
COPY_SCALAR_FIELD(wintype);
|
||||
COPY_NODE_FIELD(args);
|
||||
COPY_SCALAR_FIELD(winref);
|
||||
COPY_SCALAR_FIELD(winstar);
|
||||
COPY_SCALAR_FIELD(winagg);
|
||||
COPY_LOCATION_FIELD(location);
|
||||
|
||||
return newnode;
|
||||
}
|
||||
|
||||
/*
|
||||
* _copyArrayRef
|
||||
*/
|
||||
@ -1729,6 +1774,21 @@ _copySortGroupClause(SortGroupClause *from)
|
||||
return newnode;
|
||||
}
|
||||
|
||||
static WindowClause *
|
||||
_copyWindowClause(WindowClause *from)
|
||||
{
|
||||
WindowClause *newnode = makeNode(WindowClause);
|
||||
|
||||
COPY_STRING_FIELD(name);
|
||||
COPY_STRING_FIELD(refname);
|
||||
COPY_NODE_FIELD(partitionClause);
|
||||
COPY_NODE_FIELD(orderClause);
|
||||
COPY_SCALAR_FIELD(winref);
|
||||
COPY_SCALAR_FIELD(copiedOrder);
|
||||
|
||||
return newnode;
|
||||
}
|
||||
|
||||
static RowMarkClause *
|
||||
_copyRowMarkClause(RowMarkClause *from)
|
||||
{
|
||||
@ -1850,6 +1910,7 @@ _copyFuncCall(FuncCall *from)
|
||||
COPY_SCALAR_FIELD(agg_star);
|
||||
COPY_SCALAR_FIELD(agg_distinct);
|
||||
COPY_SCALAR_FIELD(func_variadic);
|
||||
COPY_NODE_FIELD(over);
|
||||
COPY_LOCATION_FIELD(location);
|
||||
|
||||
return newnode;
|
||||
@ -1940,6 +2001,20 @@ _copySortBy(SortBy *from)
|
||||
return newnode;
|
||||
}
|
||||
|
||||
static WindowDef *
|
||||
_copyWindowDef(WindowDef *from)
|
||||
{
|
||||
WindowDef *newnode = makeNode(WindowDef);
|
||||
|
||||
COPY_STRING_FIELD(name);
|
||||
COPY_STRING_FIELD(refname);
|
||||
COPY_NODE_FIELD(partitionClause);
|
||||
COPY_NODE_FIELD(orderClause);
|
||||
COPY_LOCATION_FIELD(location);
|
||||
|
||||
return newnode;
|
||||
}
|
||||
|
||||
static RangeSubselect *
|
||||
_copyRangeSubselect(RangeSubselect *from)
|
||||
{
|
||||
@ -2081,6 +2156,7 @@ _copyQuery(Query *from)
|
||||
COPY_SCALAR_FIELD(resultRelation);
|
||||
COPY_NODE_FIELD(intoClause);
|
||||
COPY_SCALAR_FIELD(hasAggs);
|
||||
COPY_SCALAR_FIELD(hasWindowFuncs);
|
||||
COPY_SCALAR_FIELD(hasSubLinks);
|
||||
COPY_SCALAR_FIELD(hasDistinctOn);
|
||||
COPY_SCALAR_FIELD(hasRecursive);
|
||||
@ -2091,6 +2167,7 @@ _copyQuery(Query *from)
|
||||
COPY_NODE_FIELD(returningList);
|
||||
COPY_NODE_FIELD(groupClause);
|
||||
COPY_NODE_FIELD(havingQual);
|
||||
COPY_NODE_FIELD(windowClause);
|
||||
COPY_NODE_FIELD(distinctClause);
|
||||
COPY_NODE_FIELD(sortClause);
|
||||
COPY_NODE_FIELD(limitOffset);
|
||||
@ -2153,6 +2230,7 @@ _copySelectStmt(SelectStmt *from)
|
||||
COPY_NODE_FIELD(whereClause);
|
||||
COPY_NODE_FIELD(groupClause);
|
||||
COPY_NODE_FIELD(havingClause);
|
||||
COPY_NODE_FIELD(windowClause);
|
||||
COPY_NODE_FIELD(withClause);
|
||||
COPY_NODE_FIELD(valuesLists);
|
||||
COPY_NODE_FIELD(sortClause);
|
||||
@ -3440,6 +3518,9 @@ copyObject(void *from)
|
||||
case T_Agg:
|
||||
retval = _copyAgg(from);
|
||||
break;
|
||||
case T_WindowAgg:
|
||||
retval = _copyWindowAgg(from);
|
||||
break;
|
||||
case T_Unique:
|
||||
retval = _copyUnique(from);
|
||||
break;
|
||||
@ -3480,6 +3561,9 @@ copyObject(void *from)
|
||||
case T_Aggref:
|
||||
retval = _copyAggref(from);
|
||||
break;
|
||||
case T_WindowFunc:
|
||||
retval = _copyWindowFunc(from);
|
||||
break;
|
||||
case T_ArrayRef:
|
||||
retval = _copyArrayRef(from);
|
||||
break;
|
||||
@ -3951,6 +4035,9 @@ copyObject(void *from)
|
||||
case T_SortBy:
|
||||
retval = _copySortBy(from);
|
||||
break;
|
||||
case T_WindowDef:
|
||||
retval = _copyWindowDef(from);
|
||||
break;
|
||||
case T_RangeSubselect:
|
||||
retval = _copyRangeSubselect(from);
|
||||
break;
|
||||
@ -3984,6 +4071,9 @@ copyObject(void *from)
|
||||
case T_SortGroupClause:
|
||||
retval = _copySortGroupClause(from);
|
||||
break;
|
||||
case T_WindowClause:
|
||||
retval = _copyWindowClause(from);
|
||||
break;
|
||||
case T_RowMarkClause:
|
||||
retval = _copyRowMarkClause(from);
|
||||
break;
|
||||
|
@ -22,7 +22,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.341 2008/12/19 16:25:17 petere Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.342 2008/12/28 18:53:56 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -191,6 +191,20 @@ _equalAggref(Aggref *a, Aggref *b)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
_equalWindowFunc(WindowFunc *a, WindowFunc *b)
|
||||
{
|
||||
COMPARE_SCALAR_FIELD(winfnoid);
|
||||
COMPARE_SCALAR_FIELD(wintype);
|
||||
COMPARE_NODE_FIELD(args);
|
||||
COMPARE_SCALAR_FIELD(winref);
|
||||
COMPARE_SCALAR_FIELD(winstar);
|
||||
COMPARE_SCALAR_FIELD(winagg);
|
||||
COMPARE_LOCATION_FIELD(location);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
_equalArrayRef(ArrayRef *a, ArrayRef *b)
|
||||
{
|
||||
@ -839,6 +853,7 @@ _equalQuery(Query *a, Query *b)
|
||||
COMPARE_SCALAR_FIELD(resultRelation);
|
||||
COMPARE_NODE_FIELD(intoClause);
|
||||
COMPARE_SCALAR_FIELD(hasAggs);
|
||||
COMPARE_SCALAR_FIELD(hasWindowFuncs);
|
||||
COMPARE_SCALAR_FIELD(hasSubLinks);
|
||||
COMPARE_SCALAR_FIELD(hasDistinctOn);
|
||||
COMPARE_SCALAR_FIELD(hasRecursive);
|
||||
@ -849,6 +864,7 @@ _equalQuery(Query *a, Query *b)
|
||||
COMPARE_NODE_FIELD(returningList);
|
||||
COMPARE_NODE_FIELD(groupClause);
|
||||
COMPARE_NODE_FIELD(havingQual);
|
||||
COMPARE_NODE_FIELD(windowClause);
|
||||
COMPARE_NODE_FIELD(distinctClause);
|
||||
COMPARE_NODE_FIELD(sortClause);
|
||||
COMPARE_NODE_FIELD(limitOffset);
|
||||
@ -903,6 +919,7 @@ _equalSelectStmt(SelectStmt *a, SelectStmt *b)
|
||||
COMPARE_NODE_FIELD(whereClause);
|
||||
COMPARE_NODE_FIELD(groupClause);
|
||||
COMPARE_NODE_FIELD(havingClause);
|
||||
COMPARE_NODE_FIELD(windowClause);
|
||||
COMPARE_NODE_FIELD(withClause);
|
||||
COMPARE_NODE_FIELD(valuesLists);
|
||||
COMPARE_NODE_FIELD(sortClause);
|
||||
@ -1894,6 +1911,7 @@ _equalFuncCall(FuncCall *a, FuncCall *b)
|
||||
COMPARE_SCALAR_FIELD(agg_star);
|
||||
COMPARE_SCALAR_FIELD(agg_distinct);
|
||||
COMPARE_SCALAR_FIELD(func_variadic);
|
||||
COMPARE_NODE_FIELD(over);
|
||||
COMPARE_LOCATION_FIELD(location);
|
||||
|
||||
return true;
|
||||
@ -1980,6 +1998,18 @@ _equalSortBy(SortBy *a, SortBy *b)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
_equalWindowDef(WindowDef *a, WindowDef *b)
|
||||
{
|
||||
COMPARE_STRING_FIELD(name);
|
||||
COMPARE_STRING_FIELD(refname);
|
||||
COMPARE_NODE_FIELD(partitionClause);
|
||||
COMPARE_NODE_FIELD(orderClause);
|
||||
COMPARE_LOCATION_FIELD(location);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
_equalRangeSubselect(RangeSubselect *a, RangeSubselect *b)
|
||||
{
|
||||
@ -2106,6 +2136,19 @@ _equalSortGroupClause(SortGroupClause *a, SortGroupClause *b)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
_equalWindowClause(WindowClause *a, WindowClause *b)
|
||||
{
|
||||
COMPARE_STRING_FIELD(name);
|
||||
COMPARE_STRING_FIELD(refname);
|
||||
COMPARE_NODE_FIELD(partitionClause);
|
||||
COMPARE_NODE_FIELD(orderClause);
|
||||
COMPARE_SCALAR_FIELD(winref);
|
||||
COMPARE_SCALAR_FIELD(copiedOrder);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
_equalRowMarkClause(RowMarkClause *a, RowMarkClause *b)
|
||||
{
|
||||
@ -2311,6 +2354,9 @@ equal(void *a, void *b)
|
||||
case T_Aggref:
|
||||
retval = _equalAggref(a, b);
|
||||
break;
|
||||
case T_WindowFunc:
|
||||
retval = _equalWindowFunc(a, b);
|
||||
break;
|
||||
case T_ArrayRef:
|
||||
retval = _equalArrayRef(a, b);
|
||||
break;
|
||||
@ -2769,6 +2815,9 @@ equal(void *a, void *b)
|
||||
case T_SortBy:
|
||||
retval = _equalSortBy(a, b);
|
||||
break;
|
||||
case T_WindowDef:
|
||||
retval = _equalWindowDef(a, b);
|
||||
break;
|
||||
case T_RangeSubselect:
|
||||
retval = _equalRangeSubselect(a, b);
|
||||
break;
|
||||
@ -2802,6 +2851,9 @@ equal(void *a, void *b)
|
||||
case T_SortGroupClause:
|
||||
retval = _equalSortGroupClause(a, b);
|
||||
break;
|
||||
case T_WindowClause:
|
||||
retval = _equalWindowClause(a, b);
|
||||
break;
|
||||
case T_RowMarkClause:
|
||||
retval = _equalRowMarkClause(a, b);
|
||||
break;
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/nodes/nodeFuncs.c,v 1.35 2008/10/21 20:42:52 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/nodes/nodeFuncs.c,v 1.36 2008/12/28 18:53:56 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -52,6 +52,9 @@ exprType(Node *expr)
|
||||
case T_Aggref:
|
||||
type = ((Aggref *) expr)->aggtype;
|
||||
break;
|
||||
case T_WindowFunc:
|
||||
type = ((WindowFunc *) expr)->wintype;
|
||||
break;
|
||||
case T_ArrayRef:
|
||||
{
|
||||
ArrayRef *arrayref = (ArrayRef *) expr;
|
||||
@ -548,6 +551,8 @@ expression_returns_set_walker(Node *node, void *context)
|
||||
/* Avoid recursion for some cases that can't return a set */
|
||||
if (IsA(node, Aggref))
|
||||
return false;
|
||||
if (IsA(node, WindowFunc))
|
||||
return false;
|
||||
if (IsA(node, DistinctExpr))
|
||||
return false;
|
||||
if (IsA(node, ScalarArrayOpExpr))
|
||||
@ -634,6 +639,10 @@ exprLocation(Node *expr)
|
||||
/* function name should always be the first thing */
|
||||
loc = ((Aggref *) expr)->location;
|
||||
break;
|
||||
case T_WindowFunc:
|
||||
/* function name should always be the first thing */
|
||||
loc = ((WindowFunc *) expr)->location;
|
||||
break;
|
||||
case T_ArrayRef:
|
||||
/* just use array argument's location */
|
||||
loc = exprLocation((Node *) ((ArrayRef *) expr)->refexpr);
|
||||
@ -868,6 +877,9 @@ exprLocation(Node *expr)
|
||||
/* just use argument's location (ignore operator, if any) */
|
||||
loc = exprLocation(((SortBy *) expr)->node);
|
||||
break;
|
||||
case T_WindowDef:
|
||||
loc = ((WindowDef *) expr)->location;
|
||||
break;
|
||||
case T_TypeName:
|
||||
loc = ((TypeName *) expr)->location;
|
||||
break;
|
||||
@ -1045,6 +1057,16 @@ expression_tree_walker(Node *node,
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case T_WindowFunc:
|
||||
{
|
||||
WindowFunc *expr = (WindowFunc *) node;
|
||||
|
||||
/* recurse directly on List */
|
||||
if (expression_tree_walker((Node *) expr->args,
|
||||
walker, context))
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case T_ArrayRef:
|
||||
{
|
||||
ArrayRef *aref = (ArrayRef *) node;
|
||||
@ -1221,6 +1243,16 @@ expression_tree_walker(Node *node,
|
||||
case T_Query:
|
||||
/* Do nothing with a sub-Query, per discussion above */
|
||||
break;
|
||||
case T_WindowClause:
|
||||
{
|
||||
WindowClause *wc = (WindowClause *) node;
|
||||
|
||||
if (walker(wc->partitionClause, context))
|
||||
return true;
|
||||
if (walker(wc->orderClause, context))
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case T_CommonTableExpr:
|
||||
{
|
||||
CommonTableExpr *cte = (CommonTableExpr *) node;
|
||||
@ -1539,6 +1571,16 @@ expression_tree_mutator(Node *node,
|
||||
return (Node *) newnode;
|
||||
}
|
||||
break;
|
||||
case T_WindowFunc:
|
||||
{
|
||||
WindowFunc *wfunc = (WindowFunc *) node;
|
||||
WindowFunc *newnode;
|
||||
|
||||
FLATCOPY(newnode, wfunc, WindowFunc);
|
||||
MUTATE(newnode->args, wfunc->args, List *);
|
||||
return (Node *) newnode;
|
||||
}
|
||||
break;
|
||||
case T_ArrayRef:
|
||||
{
|
||||
ArrayRef *arrayref = (ArrayRef *) node;
|
||||
@ -1848,6 +1890,17 @@ expression_tree_mutator(Node *node,
|
||||
case T_Query:
|
||||
/* Do nothing with a sub-Query, per discussion above */
|
||||
return node;
|
||||
case T_WindowClause:
|
||||
{
|
||||
WindowClause *wc = (WindowClause *) node;
|
||||
WindowClause *newnode;
|
||||
|
||||
FLATCOPY(newnode, wc, WindowClause);
|
||||
MUTATE(newnode->partitionClause, wc->partitionClause, List *);
|
||||
MUTATE(newnode->orderClause, wc->orderClause, List *);
|
||||
return (Node *) newnode;
|
||||
}
|
||||
break;
|
||||
case T_CommonTableExpr:
|
||||
{
|
||||
CommonTableExpr *cte = (CommonTableExpr *) node;
|
||||
@ -2280,6 +2333,8 @@ raw_expression_tree_walker(Node *node, bool (*walker) (), void *context)
|
||||
return true;
|
||||
if (walker(stmt->havingClause, context))
|
||||
return true;
|
||||
if (walker(stmt->windowClause, context))
|
||||
return true;
|
||||
if (walker(stmt->withClause, context))
|
||||
return true;
|
||||
if (walker(stmt->valuesLists, context))
|
||||
@ -2318,6 +2373,8 @@ raw_expression_tree_walker(Node *node, bool (*walker) (), void *context)
|
||||
|
||||
if (walker(fcall->args, context))
|
||||
return true;
|
||||
if (walker(fcall->over, context))
|
||||
return true;
|
||||
/* function name is deemed uninteresting */
|
||||
}
|
||||
break;
|
||||
@ -2365,6 +2422,16 @@ raw_expression_tree_walker(Node *node, bool (*walker) (), void *context)
|
||||
break;
|
||||
case T_SortBy:
|
||||
return walker(((SortBy *) node)->node, context);
|
||||
case T_WindowDef:
|
||||
{
|
||||
WindowDef *wd = (WindowDef *) node;
|
||||
|
||||
if (walker(wd->partitionClause, context))
|
||||
return true;
|
||||
if (walker(wd->orderClause, context))
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case T_RangeSubselect:
|
||||
{
|
||||
RangeSubselect *rs = (RangeSubselect *) node;
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.346 2008/12/01 21:06:12 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.347 2008/12/28 18:53:56 tgl Exp $
|
||||
*
|
||||
* NOTES
|
||||
* Every node type that can appear in stored rules' parsetrees *must*
|
||||
@ -566,6 +566,36 @@ _outAgg(StringInfo str, Agg *node)
|
||||
WRITE_LONG_FIELD(numGroups);
|
||||
}
|
||||
|
||||
static void
|
||||
_outWindowAgg(StringInfo str, WindowAgg *node)
|
||||
{
|
||||
int i;
|
||||
|
||||
WRITE_NODE_TYPE("WINDOWAGG");
|
||||
|
||||
_outPlanInfo(str, (Plan *) node);
|
||||
|
||||
WRITE_INT_FIELD(partNumCols);
|
||||
|
||||
appendStringInfo(str, " :partColIdx");
|
||||
for (i = 0; i < node->partNumCols; i++)
|
||||
appendStringInfo(str, " %d", node->partColIdx[i]);
|
||||
|
||||
appendStringInfo(str, " :partOperations");
|
||||
for (i = 0; i < node->partNumCols; i++)
|
||||
appendStringInfo(str, " %u", node->partOperators[i]);
|
||||
|
||||
WRITE_INT_FIELD(ordNumCols);
|
||||
|
||||
appendStringInfo(str, " :ordColIdx");
|
||||
for (i = 0; i< node->ordNumCols; i++)
|
||||
appendStringInfo(str, " %d", node->ordColIdx[i]);
|
||||
|
||||
appendStringInfo(str, " :ordOperations");
|
||||
for (i = 0; i < node->ordNumCols; i++)
|
||||
appendStringInfo(str, " %u", node->ordOperators[i]);
|
||||
}
|
||||
|
||||
static void
|
||||
_outGroup(StringInfo str, Group *node)
|
||||
{
|
||||
@ -798,6 +828,20 @@ _outAggref(StringInfo str, Aggref *node)
|
||||
WRITE_LOCATION_FIELD(location);
|
||||
}
|
||||
|
||||
static void
|
||||
_outWindowFunc(StringInfo str, WindowFunc *node)
|
||||
{
|
||||
WRITE_NODE_TYPE("WINDOWFUNC");
|
||||
|
||||
WRITE_OID_FIELD(winfnoid);
|
||||
WRITE_OID_FIELD(wintype);
|
||||
WRITE_NODE_FIELD(args);
|
||||
WRITE_UINT_FIELD(winref);
|
||||
WRITE_BOOL_FIELD(winstar);
|
||||
WRITE_BOOL_FIELD(winagg);
|
||||
WRITE_LOCATION_FIELD(location);
|
||||
}
|
||||
|
||||
static void
|
||||
_outArrayRef(StringInfo str, ArrayRef *node)
|
||||
{
|
||||
@ -1440,6 +1484,7 @@ _outPlannerInfo(StringInfo str, PlannerInfo *node)
|
||||
WRITE_NODE_FIELD(placeholder_list);
|
||||
WRITE_NODE_FIELD(query_pathkeys);
|
||||
WRITE_NODE_FIELD(group_pathkeys);
|
||||
WRITE_NODE_FIELD(window_pathkeys);
|
||||
WRITE_NODE_FIELD(distinct_pathkeys);
|
||||
WRITE_NODE_FIELD(sort_pathkeys);
|
||||
WRITE_FLOAT_FIELD(total_table_pages, "%.0f");
|
||||
@ -1722,6 +1767,7 @@ _outSelectStmt(StringInfo str, SelectStmt *node)
|
||||
WRITE_NODE_FIELD(whereClause);
|
||||
WRITE_NODE_FIELD(groupClause);
|
||||
WRITE_NODE_FIELD(havingClause);
|
||||
WRITE_NODE_FIELD(windowClause);
|
||||
WRITE_NODE_FIELD(withClause);
|
||||
WRITE_NODE_FIELD(valuesLists);
|
||||
WRITE_NODE_FIELD(sortClause);
|
||||
@ -1744,6 +1790,7 @@ _outFuncCall(StringInfo str, FuncCall *node)
|
||||
WRITE_BOOL_FIELD(agg_star);
|
||||
WRITE_BOOL_FIELD(agg_distinct);
|
||||
WRITE_BOOL_FIELD(func_variadic);
|
||||
WRITE_NODE_FIELD(over);
|
||||
WRITE_LOCATION_FIELD(location);
|
||||
}
|
||||
|
||||
@ -1866,6 +1913,7 @@ _outQuery(StringInfo str, Query *node)
|
||||
WRITE_INT_FIELD(resultRelation);
|
||||
WRITE_NODE_FIELD(intoClause);
|
||||
WRITE_BOOL_FIELD(hasAggs);
|
||||
WRITE_BOOL_FIELD(hasWindowFuncs);
|
||||
WRITE_BOOL_FIELD(hasSubLinks);
|
||||
WRITE_BOOL_FIELD(hasDistinctOn);
|
||||
WRITE_BOOL_FIELD(hasRecursive);
|
||||
@ -1876,6 +1924,7 @@ _outQuery(StringInfo str, Query *node)
|
||||
WRITE_NODE_FIELD(returningList);
|
||||
WRITE_NODE_FIELD(groupClause);
|
||||
WRITE_NODE_FIELD(havingQual);
|
||||
WRITE_NODE_FIELD(windowClause);
|
||||
WRITE_NODE_FIELD(distinctClause);
|
||||
WRITE_NODE_FIELD(sortClause);
|
||||
WRITE_NODE_FIELD(limitOffset);
|
||||
@ -1895,6 +1944,19 @@ _outSortGroupClause(StringInfo str, SortGroupClause *node)
|
||||
WRITE_BOOL_FIELD(nulls_first);
|
||||
}
|
||||
|
||||
static void
|
||||
_outWindowClause(StringInfo str, WindowClause *node)
|
||||
{
|
||||
WRITE_NODE_TYPE("WINDOWCLAUSE");
|
||||
|
||||
WRITE_STRING_FIELD(name);
|
||||
WRITE_STRING_FIELD(refname);
|
||||
WRITE_NODE_FIELD(partitionClause);
|
||||
WRITE_NODE_FIELD(orderClause);
|
||||
WRITE_UINT_FIELD(winref);
|
||||
WRITE_BOOL_FIELD(copiedOrder);
|
||||
}
|
||||
|
||||
static void
|
||||
_outRowMarkClause(StringInfo str, RowMarkClause *node)
|
||||
{
|
||||
@ -2171,6 +2233,18 @@ _outSortBy(StringInfo str, SortBy *node)
|
||||
WRITE_LOCATION_FIELD(location);
|
||||
}
|
||||
|
||||
static void
|
||||
_outWindowDef(StringInfo str, WindowDef *node)
|
||||
{
|
||||
WRITE_NODE_TYPE("WINDOWDEF");
|
||||
|
||||
WRITE_STRING_FIELD(name);
|
||||
WRITE_STRING_FIELD(refname);
|
||||
WRITE_NODE_FIELD(partitionClause);
|
||||
WRITE_NODE_FIELD(orderClause);
|
||||
WRITE_LOCATION_FIELD(location);
|
||||
}
|
||||
|
||||
static void
|
||||
_outRangeSubselect(StringInfo str, RangeSubselect *node)
|
||||
{
|
||||
@ -2347,6 +2421,9 @@ _outNode(StringInfo str, void *obj)
|
||||
case T_Agg:
|
||||
_outAgg(str, obj);
|
||||
break;
|
||||
case T_WindowAgg:
|
||||
_outWindowAgg(str, obj);
|
||||
break;
|
||||
case T_Group:
|
||||
_outGroup(str, obj);
|
||||
break;
|
||||
@ -2392,6 +2469,9 @@ _outNode(StringInfo str, void *obj)
|
||||
case T_Aggref:
|
||||
_outAggref(str, obj);
|
||||
break;
|
||||
case T_WindowFunc:
|
||||
_outWindowFunc(str, obj);
|
||||
break;
|
||||
case T_ArrayRef:
|
||||
_outArrayRef(str, obj);
|
||||
break;
|
||||
@ -2616,6 +2696,9 @@ _outNode(StringInfo str, void *obj)
|
||||
case T_SortGroupClause:
|
||||
_outSortGroupClause(str, obj);
|
||||
break;
|
||||
case T_WindowClause:
|
||||
_outWindowClause(str, obj);
|
||||
break;
|
||||
case T_RowMarkClause:
|
||||
_outRowMarkClause(str, obj);
|
||||
break;
|
||||
@ -2661,6 +2744,9 @@ _outNode(StringInfo str, void *obj)
|
||||
case T_SortBy:
|
||||
_outSortBy(str, obj);
|
||||
break;
|
||||
case T_WindowDef:
|
||||
_outWindowDef(str, obj);
|
||||
break;
|
||||
case T_RangeSubselect:
|
||||
_outRangeSubselect(str, obj);
|
||||
break;
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.217 2008/11/15 19:43:46 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.218 2008/12/28 18:53:56 tgl Exp $
|
||||
*
|
||||
* NOTES
|
||||
* Path and Plan nodes do not have any readfuncs support, because we
|
||||
@ -153,6 +153,7 @@ _readQuery(void)
|
||||
READ_INT_FIELD(resultRelation);
|
||||
READ_NODE_FIELD(intoClause);
|
||||
READ_BOOL_FIELD(hasAggs);
|
||||
READ_BOOL_FIELD(hasWindowFuncs);
|
||||
READ_BOOL_FIELD(hasSubLinks);
|
||||
READ_BOOL_FIELD(hasDistinctOn);
|
||||
READ_BOOL_FIELD(hasRecursive);
|
||||
@ -163,6 +164,7 @@ _readQuery(void)
|
||||
READ_NODE_FIELD(returningList);
|
||||
READ_NODE_FIELD(groupClause);
|
||||
READ_NODE_FIELD(havingQual);
|
||||
READ_NODE_FIELD(windowClause);
|
||||
READ_NODE_FIELD(distinctClause);
|
||||
READ_NODE_FIELD(sortClause);
|
||||
READ_NODE_FIELD(limitOffset);
|
||||
@ -217,6 +219,24 @@ _readSortGroupClause(void)
|
||||
READ_DONE();
|
||||
}
|
||||
|
||||
/*
|
||||
* _readWindowClause
|
||||
*/
|
||||
static WindowClause *
|
||||
_readWindowClause(void)
|
||||
{
|
||||
READ_LOCALS(WindowClause);
|
||||
|
||||
READ_STRING_FIELD(name);
|
||||
READ_STRING_FIELD(refname);
|
||||
READ_NODE_FIELD(partitionClause);
|
||||
READ_NODE_FIELD(orderClause);
|
||||
READ_UINT_FIELD(winref);
|
||||
READ_BOOL_FIELD(copiedOrder);
|
||||
|
||||
READ_DONE();
|
||||
}
|
||||
|
||||
/*
|
||||
* _readRowMarkClause
|
||||
*/
|
||||
@ -402,6 +422,25 @@ _readAggref(void)
|
||||
READ_DONE();
|
||||
}
|
||||
|
||||
/*
|
||||
* _readWindowFunc
|
||||
*/
|
||||
static WindowFunc *
|
||||
_readWindowFunc(void)
|
||||
{
|
||||
READ_LOCALS(WindowFunc);
|
||||
|
||||
READ_OID_FIELD(winfnoid);
|
||||
READ_OID_FIELD(wintype);
|
||||
READ_NODE_FIELD(args);
|
||||
READ_UINT_FIELD(winref);
|
||||
READ_BOOL_FIELD(winstar);
|
||||
READ_BOOL_FIELD(winagg);
|
||||
READ_LOCATION_FIELD(location);
|
||||
|
||||
READ_DONE();
|
||||
}
|
||||
|
||||
/*
|
||||
* _readArrayRef
|
||||
*/
|
||||
@ -1091,6 +1130,8 @@ parseNodeString(void)
|
||||
return_value = _readQuery();
|
||||
else if (MATCH("SORTGROUPCLAUSE", 15))
|
||||
return_value = _readSortGroupClause();
|
||||
else if (MATCH("WINDOWCLAUSE", 12))
|
||||
return_value = _readWindowClause();
|
||||
else if (MATCH("ROWMARKCLAUSE", 13))
|
||||
return_value = _readRowMarkClause();
|
||||
else if (MATCH("COMMONTABLEEXPR", 15))
|
||||
@ -1111,6 +1152,8 @@ parseNodeString(void)
|
||||
return_value = _readParam();
|
||||
else if (MATCH("AGGREF", 6))
|
||||
return_value = _readAggref();
|
||||
else if (MATCH("WINDOWFUNC", 10))
|
||||
return_value = _readWindowFunc();
|
||||
else if (MATCH("ARRAYREF", 8))
|
||||
return_value = _readArrayRef();
|
||||
else if (MATCH("FUNCEXPR", 8))
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.177 2008/11/15 19:43:46 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.178 2008/12/28 18:53:56 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -929,10 +929,13 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
|
||||
* 1. If the subquery has a LIMIT clause, we must not push down any quals,
|
||||
* since that could change the set of rows returned.
|
||||
*
|
||||
* 2. If the subquery contains EXCEPT or EXCEPT ALL set ops we cannot push
|
||||
* 2. If the subquery contains any window functions, we can't push quals
|
||||
* into it, because that would change the results.
|
||||
*
|
||||
* 3. If the subquery contains EXCEPT or EXCEPT ALL set ops we cannot push
|
||||
* quals into it, because that would change the results.
|
||||
*
|
||||
* 3. For subqueries using UNION/UNION ALL/INTERSECT/INTERSECT ALL, we can
|
||||
* 4. For subqueries using UNION/UNION ALL/INTERSECT/INTERSECT ALL, we can
|
||||
* push quals into each component query, but the quals can only reference
|
||||
* subquery columns that suffer no type coercions in the set operation.
|
||||
* Otherwise there are possible semantic gotchas. So, we check the
|
||||
@ -950,6 +953,10 @@ subquery_is_pushdown_safe(Query *subquery, Query *topquery,
|
||||
if (subquery->limitOffset != NULL || subquery->limitCount != NULL)
|
||||
return false;
|
||||
|
||||
/* Check point 2 */
|
||||
if (subquery->hasWindowFuncs)
|
||||
return false;
|
||||
|
||||
/* Are we at top level, or looking at a setop component? */
|
||||
if (subquery == topquery)
|
||||
{
|
||||
@ -1092,6 +1099,12 @@ qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual,
|
||||
if (contain_subplans(qual))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* It would be unsafe to push down window function calls, but at least
|
||||
* for the moment we could never see any in a qual anyhow.
|
||||
*/
|
||||
Assert(!contain_window_function(qual));
|
||||
|
||||
/*
|
||||
* Examine all Vars used in clause; since it's a restriction clause, all
|
||||
* such Vars must refer to subselect output columns.
|
||||
|
@ -54,7 +54,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.201 2008/11/22 22:47:05 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.202 2008/12/28 18:53:56 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -1283,6 +1283,40 @@ cost_agg(Path *path, PlannerInfo *root,
|
||||
path->total_cost = total_cost;
|
||||
}
|
||||
|
||||
/*
|
||||
* cost_windowagg
|
||||
* Determines and returns the cost of performing a WindowAgg plan node,
|
||||
* including the cost of its input.
|
||||
*
|
||||
* Input is assumed already properly sorted.
|
||||
*/
|
||||
void
|
||||
cost_windowagg(Path *path, PlannerInfo *root,
|
||||
int numWindowFuncs, int numPartCols, int numOrderCols,
|
||||
Cost input_startup_cost, Cost input_total_cost,
|
||||
double input_tuples)
|
||||
{
|
||||
Cost startup_cost;
|
||||
Cost total_cost;
|
||||
|
||||
startup_cost = input_startup_cost;
|
||||
total_cost = input_total_cost;
|
||||
|
||||
/*
|
||||
* We charge one cpu_operator_cost per window function per tuple (often a
|
||||
* drastic underestimate, but without a way to gauge how many tuples the
|
||||
* window function will fetch, it's hard to do better). We also charge
|
||||
* cpu_operator_cost per grouping column per tuple for grouping
|
||||
* comparisons, plus cpu_tuple_cost per tuple for general overhead.
|
||||
*/
|
||||
total_cost += cpu_operator_cost * input_tuples * numWindowFuncs;
|
||||
total_cost += cpu_operator_cost * input_tuples * (numPartCols + numOrderCols);
|
||||
total_cost += cpu_tuple_cost * input_tuples;
|
||||
|
||||
path->startup_cost = startup_cost;
|
||||
path->total_cost = total_cost;
|
||||
}
|
||||
|
||||
/*
|
||||
* cost_group
|
||||
* Determines and returns the cost of performing a Group plan node,
|
||||
@ -2155,6 +2189,11 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
|
||||
* Vars and Consts are charged zero, and so are boolean operators (AND,
|
||||
* OR, NOT). Simplistic, but a lot better than no model at all.
|
||||
*
|
||||
* Note that Aggref and WindowFunc nodes are (and should be) treated
|
||||
* like Vars --- whatever execution cost they have is absorbed into
|
||||
* plan-node-specific costing. As far as expression evaluation is
|
||||
* concerned they're just like Vars.
|
||||
*
|
||||
* Should we try to account for the possibility of short-circuit
|
||||
* evaluation of AND/OR? Probably *not*, because that would make the
|
||||
* results depend on the clause ordering, and we are not in any position
|
||||
|
@ -10,7 +10,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/path/equivclass.c,v 1.14 2008/12/01 21:06:13 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/path/equivclass.c,v 1.15 2008/12/28 18:53:56 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -438,14 +438,16 @@ get_eclass_for_sort_expr(PlannerInfo *root,
|
||||
|
||||
/*
|
||||
* add_eq_member doesn't check for volatile functions, set-returning
|
||||
* functions, or aggregates, but such could appear in sort expressions; so
|
||||
* we have to check whether its const-marking was correct.
|
||||
* functions, aggregates, or window functions, but such could appear
|
||||
* in sort expressions; so we have to check whether its const-marking
|
||||
* was correct.
|
||||
*/
|
||||
if (newec->ec_has_const)
|
||||
{
|
||||
if (newec->ec_has_volatile ||
|
||||
expression_returns_set((Node *) expr) ||
|
||||
contain_agg_clause((Node *) expr))
|
||||
contain_agg_clause((Node *) expr) ||
|
||||
contain_window_function((Node *) expr))
|
||||
{
|
||||
newec->ec_has_const = false;
|
||||
newem->em_is_const = false;
|
||||
|
@ -10,7 +10,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.252 2008/11/20 19:52:54 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.253 2008/12/28 18:53:56 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -3237,8 +3237,8 @@ make_agg(PlannerInfo *root, List *tlist, List *qual,
|
||||
* anything for Aggref nodes; this is okay since they are really
|
||||
* comparable to Vars.
|
||||
*
|
||||
* See notes in grouping_planner about why this routine and make_group are
|
||||
* the only ones in this file that worry about tlist eval cost.
|
||||
* See notes in grouping_planner about why only make_agg, make_windowagg
|
||||
* and make_group worry about tlist eval cost.
|
||||
*/
|
||||
if (qual)
|
||||
{
|
||||
@ -3260,6 +3260,53 @@ make_agg(PlannerInfo *root, List *tlist, List *qual,
|
||||
return node;
|
||||
}
|
||||
|
||||
WindowAgg *
|
||||
make_windowagg(PlannerInfo *root, List *tlist, int numWindowFuncs,
|
||||
int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
|
||||
int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators,
|
||||
Plan *lefttree)
|
||||
{
|
||||
WindowAgg *node = makeNode(WindowAgg);
|
||||
Plan *plan = &node->plan;
|
||||
Path windowagg_path; /* dummy for result of cost_windowagg */
|
||||
QualCost qual_cost;
|
||||
|
||||
node->partNumCols = partNumCols;
|
||||
node->partColIdx = partColIdx;
|
||||
node->partOperators = partOperators;
|
||||
node->ordNumCols = ordNumCols;
|
||||
node->ordColIdx = ordColIdx;
|
||||
node->ordOperators = ordOperators;
|
||||
|
||||
copy_plan_costsize(plan, lefttree); /* only care about copying size */
|
||||
cost_windowagg(&windowagg_path, root,
|
||||
numWindowFuncs, partNumCols, ordNumCols,
|
||||
lefttree->startup_cost,
|
||||
lefttree->total_cost,
|
||||
lefttree->plan_rows);
|
||||
plan->startup_cost = windowagg_path.startup_cost;
|
||||
plan->total_cost = windowagg_path.total_cost;
|
||||
|
||||
/*
|
||||
* We also need to account for the cost of evaluation of the tlist.
|
||||
*
|
||||
* See notes in grouping_planner about why only make_agg, make_windowagg
|
||||
* and make_group worry about tlist eval cost.
|
||||
*/
|
||||
cost_qual_eval(&qual_cost, tlist, root);
|
||||
plan->startup_cost += qual_cost.startup;
|
||||
plan->total_cost += qual_cost.startup;
|
||||
plan->total_cost += qual_cost.per_tuple * plan->plan_rows;
|
||||
|
||||
plan->targetlist = tlist;
|
||||
plan->lefttree = lefttree;
|
||||
plan->righttree = NULL;
|
||||
/* WindowAgg nodes never have a qual clause */
|
||||
plan->qual = NIL;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
Group *
|
||||
make_group(PlannerInfo *root,
|
||||
List *tlist,
|
||||
@ -3300,8 +3347,8 @@ make_group(PlannerInfo *root,
|
||||
* lower plan level and will only be copied by the Group node. Worth
|
||||
* fixing?
|
||||
*
|
||||
* See notes in grouping_planner about why this routine and make_agg are
|
||||
* the only ones in this file that worry about tlist eval cost.
|
||||
* See notes in grouping_planner about why only make_agg, make_windowagg
|
||||
* and make_group worry about tlist eval cost.
|
||||
*/
|
||||
if (qual)
|
||||
{
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.43 2008/08/25 22:42:33 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.44 2008/12/28 18:53:56 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -95,11 +95,11 @@ optimize_minmax_aggregates(PlannerInfo *root, List *tlist, Path *best_path)
|
||||
/*
|
||||
* Reject unoptimizable cases.
|
||||
*
|
||||
* We don't handle GROUP BY, because our current implementations of
|
||||
* grouping require looking at all the rows anyway, and so there's not
|
||||
* much point in optimizing MIN/MAX.
|
||||
* We don't handle GROUP BY or windowing, because our current
|
||||
* implementations of grouping require looking at all the rows anyway,
|
||||
* and so there's not much point in optimizing MIN/MAX.
|
||||
*/
|
||||
if (parse->groupClause)
|
||||
if (parse->groupClause || parse->hasWindowFuncs)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
|
@ -14,7 +14,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planmain.c,v 1.112 2008/10/22 20:17:51 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planmain.c,v 1.113 2008/12/28 18:53:56 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -67,9 +67,9 @@
|
||||
* PlannerInfo field and not a passed parameter is that the low-level routines
|
||||
* in indxpath.c need to see it.)
|
||||
*
|
||||
* Note: the PlannerInfo node also includes group_pathkeys, distinct_pathkeys,
|
||||
* and sort_pathkeys, which like query_pathkeys need to be canonicalized once
|
||||
* the info is available.
|
||||
* Note: the PlannerInfo node also includes group_pathkeys, window_pathkeys,
|
||||
* distinct_pathkeys, and sort_pathkeys, which like query_pathkeys need to be
|
||||
* canonicalized once the info is available.
|
||||
*
|
||||
* tuple_fraction is interpreted as follows:
|
||||
* 0: expect all tuples to be retrieved (normal case)
|
||||
@ -121,6 +121,8 @@ query_planner(PlannerInfo *root, List *tlist,
|
||||
root->query_pathkeys);
|
||||
root->group_pathkeys = canonicalize_pathkeys(root,
|
||||
root->group_pathkeys);
|
||||
root->window_pathkeys = canonicalize_pathkeys(root,
|
||||
root->window_pathkeys);
|
||||
root->distinct_pathkeys = canonicalize_pathkeys(root,
|
||||
root->distinct_pathkeys);
|
||||
root->sort_pathkeys = canonicalize_pathkeys(root,
|
||||
@ -228,11 +230,12 @@ query_planner(PlannerInfo *root, List *tlist,
|
||||
/*
|
||||
* We have completed merging equivalence sets, so it's now possible to
|
||||
* convert the requested query_pathkeys to canonical form. Also
|
||||
* canonicalize the groupClause, distinctClause and sortClause pathkeys
|
||||
* for use later.
|
||||
* canonicalize the groupClause, windowClause, distinctClause and
|
||||
* sortClause pathkeys for use later.
|
||||
*/
|
||||
root->query_pathkeys = canonicalize_pathkeys(root, root->query_pathkeys);
|
||||
root->group_pathkeys = canonicalize_pathkeys(root, root->group_pathkeys);
|
||||
root->window_pathkeys = canonicalize_pathkeys(root, root->window_pathkeys);
|
||||
root->distinct_pathkeys = canonicalize_pathkeys(root, root->distinct_pathkeys);
|
||||
root->sort_pathkeys = canonicalize_pathkeys(root, root->sort_pathkeys);
|
||||
|
||||
@ -287,10 +290,12 @@ query_planner(PlannerInfo *root, List *tlist,
|
||||
* If both GROUP BY and ORDER BY are specified, we will need two
|
||||
* levels of sort --- and, therefore, certainly need to read all the
|
||||
* tuples --- unless ORDER BY is a subset of GROUP BY. Likewise if
|
||||
* we have both DISTINCT and GROUP BY.
|
||||
* we have both DISTINCT and GROUP BY, or if we have a window
|
||||
* specification not compatible with the GROUP BY.
|
||||
*/
|
||||
if (!pathkeys_contained_in(root->sort_pathkeys, root->group_pathkeys) ||
|
||||
!pathkeys_contained_in(root->distinct_pathkeys, root->group_pathkeys))
|
||||
!pathkeys_contained_in(root->distinct_pathkeys, root->group_pathkeys) ||
|
||||
!pathkeys_contained_in(root->window_pathkeys, root->group_pathkeys))
|
||||
tuple_fraction = 0.0;
|
||||
}
|
||||
else if (parse->hasAggs || root->hasHavingQual)
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.247 2008/12/18 18:20:33 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.248 2008/12/28 18:53:56 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -82,6 +82,18 @@ static void locate_grouping_columns(PlannerInfo *root,
|
||||
List *sub_tlist,
|
||||
AttrNumber *groupColIdx);
|
||||
static List *postprocess_setop_tlist(List *new_tlist, List *orig_tlist);
|
||||
static List *select_active_windows(PlannerInfo *root, WindowFuncLists *wflists);
|
||||
static List *make_pathkeys_for_window(PlannerInfo *root, WindowClause *wc,
|
||||
List *tlist, bool canonicalize);
|
||||
static void get_column_info_for_window(PlannerInfo *root, WindowClause *wc,
|
||||
List *tlist,
|
||||
int numSortCols, AttrNumber *sortColIdx,
|
||||
int *partNumCols,
|
||||
AttrNumber **partColIdx,
|
||||
Oid **partOperators,
|
||||
int *ordNumCols,
|
||||
AttrNumber **ordColIdx,
|
||||
Oid **ordOperators);
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
@ -852,6 +864,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
|
||||
AggClauseCounts agg_counts;
|
||||
int numGroupCols;
|
||||
bool use_hashed_grouping = false;
|
||||
WindowFuncLists *wflists = NULL;
|
||||
List *activeWindows = NIL;
|
||||
|
||||
MemSet(&agg_counts, 0, sizeof(AggClauseCounts));
|
||||
|
||||
@ -866,6 +880,22 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
|
||||
/* Preprocess targetlist */
|
||||
tlist = preprocess_targetlist(root, tlist);
|
||||
|
||||
/*
|
||||
* Locate any window functions in the tlist. (We don't need to look
|
||||
* anywhere else, since expressions used in ORDER BY will be in there
|
||||
* too.) Note that they could all have been eliminated by constant
|
||||
* folding, in which case we don't need to do any more work.
|
||||
*/
|
||||
if (parse->hasWindowFuncs)
|
||||
{
|
||||
wflists = find_window_functions((Node *) tlist,
|
||||
list_length(parse->windowClause));
|
||||
if (wflists->numWindowFuncs > 0)
|
||||
activeWindows = select_active_windows(root, wflists);
|
||||
else
|
||||
parse->hasWindowFuncs = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate appropriate target list for subplan; may be different from
|
||||
* tlist if grouping or aggregation is needed.
|
||||
@ -890,6 +920,19 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
|
||||
else
|
||||
root->group_pathkeys = NIL;
|
||||
|
||||
/* We consider only the first (bottom) window in pathkeys logic */
|
||||
if (activeWindows != NIL)
|
||||
{
|
||||
WindowClause *wc = (WindowClause *) linitial(activeWindows);
|
||||
|
||||
root->window_pathkeys = make_pathkeys_for_window(root,
|
||||
wc,
|
||||
tlist,
|
||||
false);
|
||||
}
|
||||
else
|
||||
root->window_pathkeys = NIL;
|
||||
|
||||
if (parse->distinctClause &&
|
||||
grouping_is_sortable(parse->distinctClause))
|
||||
root->distinct_pathkeys =
|
||||
@ -927,11 +970,12 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
|
||||
* Figure out whether we want a sorted result from query_planner.
|
||||
*
|
||||
* If we have a sortable GROUP BY clause, then we want a result sorted
|
||||
* properly for grouping. Otherwise, if there's a sortable DISTINCT
|
||||
* clause that's more rigorous than the ORDER BY clause, we try to
|
||||
* produce output that's sufficiently well sorted for the DISTINCT.
|
||||
* Otherwise, if there is an ORDER BY clause, we want to sort by the
|
||||
* ORDER BY clause.
|
||||
* properly for grouping. Otherwise, if we have window functions to
|
||||
* evaluate, we try to sort for the first window. Otherwise, if
|
||||
* there's a sortable DISTINCT clause that's more rigorous than the
|
||||
* ORDER BY clause, we try to produce output that's sufficiently well
|
||||
* sorted for the DISTINCT. Otherwise, if there is an ORDER BY
|
||||
* clause, we want to sort by the ORDER BY clause.
|
||||
*
|
||||
* Note: if we have both ORDER BY and GROUP BY, and ORDER BY is a
|
||||
* superset of GROUP BY, it would be tempting to request sort by ORDER
|
||||
@ -942,6 +986,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
|
||||
*/
|
||||
if (root->group_pathkeys)
|
||||
root->query_pathkeys = root->group_pathkeys;
|
||||
else if (root->window_pathkeys)
|
||||
root->query_pathkeys = root->window_pathkeys;
|
||||
else if (list_length(root->distinct_pathkeys) >
|
||||
list_length(root->sort_pathkeys))
|
||||
root->query_pathkeys = root->distinct_pathkeys;
|
||||
@ -1092,10 +1138,10 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
|
||||
*
|
||||
* Below this point, any tlist eval cost for added-on nodes
|
||||
* should be accounted for as we create those nodes.
|
||||
* Presently, of the node types we can add on, only Agg and
|
||||
* Group project new tlists (the rest just copy their input
|
||||
* tuples) --- so make_agg() and make_group() are responsible
|
||||
* for computing the added cost.
|
||||
* Presently, of the node types we can add on, only Agg,
|
||||
* WindowAgg, and Group project new tlists (the rest just copy
|
||||
* their input tuples) --- so make_agg(), make_windowagg() and
|
||||
* make_group() are responsible for computing the added cost.
|
||||
*/
|
||||
cost_qual_eval(&tlist_cost, sub_tlist, root);
|
||||
result_plan->startup_cost += tlist_cost.startup;
|
||||
@ -1225,6 +1271,142 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
|
||||
NULL);
|
||||
}
|
||||
} /* end of non-minmax-aggregate case */
|
||||
|
||||
/*
|
||||
* Since each window function could require a different sort order,
|
||||
* we stack up a WindowAgg node for each window, with sort steps
|
||||
* between them as needed.
|
||||
*/
|
||||
if (activeWindows)
|
||||
{
|
||||
List *window_tlist;
|
||||
ListCell *l;
|
||||
|
||||
/*
|
||||
* If the top-level plan node is one that cannot do expression
|
||||
* evaluation, we must insert a Result node to project the
|
||||
* desired tlist. (In some cases this might not really be
|
||||
* required, but it's not worth trying to avoid it.) Note that
|
||||
* on second and subsequent passes through the following loop,
|
||||
* the top-level node will be a WindowAgg which we know can
|
||||
* project; so we only need to check once.
|
||||
*/
|
||||
if (!is_projection_capable_plan(result_plan))
|
||||
{
|
||||
result_plan = (Plan *) make_result(root,
|
||||
NIL,
|
||||
NULL,
|
||||
result_plan);
|
||||
}
|
||||
|
||||
/*
|
||||
* The "base" targetlist for all steps of the windowing process
|
||||
* is a flat tlist of all Vars and Aggs needed in the result.
|
||||
* (In some cases we wouldn't need to propagate all of these
|
||||
* all the way to the top, since they might only be needed as
|
||||
* inputs to WindowFuncs. It's probably not worth trying to
|
||||
* optimize that though.) As we climb up the stack, we add
|
||||
* outputs for the WindowFuncs computed at each level. Also,
|
||||
* each input tlist has to present all the columns needed to
|
||||
* sort the data for the next WindowAgg step. That's handled
|
||||
* internally by make_sort_from_pathkeys, but we need the
|
||||
* copyObject steps here to ensure that each plan node has
|
||||
* a separately modifiable tlist.
|
||||
*/
|
||||
window_tlist = flatten_tlist(tlist);
|
||||
if (parse->hasAggs)
|
||||
window_tlist = add_to_flat_tlist(window_tlist,
|
||||
pull_agg_clause((Node *) tlist));
|
||||
result_plan->targetlist = (List *) copyObject(window_tlist);
|
||||
|
||||
foreach(l, activeWindows)
|
||||
{
|
||||
WindowClause *wc = (WindowClause *) lfirst(l);
|
||||
List *window_pathkeys;
|
||||
int partNumCols;
|
||||
AttrNumber *partColIdx;
|
||||
Oid *partOperators;
|
||||
int ordNumCols;
|
||||
AttrNumber *ordColIdx;
|
||||
Oid *ordOperators;
|
||||
|
||||
window_pathkeys = make_pathkeys_for_window(root,
|
||||
wc,
|
||||
tlist,
|
||||
true);
|
||||
|
||||
/*
|
||||
* This is a bit tricky: we build a sort node even if we don't
|
||||
* really have to sort. Even when no explicit sort is needed,
|
||||
* we need to have suitable resjunk items added to the input
|
||||
* plan's tlist for any partitioning or ordering columns that
|
||||
* aren't plain Vars. Furthermore, this way we can use
|
||||
* existing infrastructure to identify which input columns are
|
||||
* the interesting ones.
|
||||
*/
|
||||
if (window_pathkeys)
|
||||
{
|
||||
Sort *sort_plan;
|
||||
|
||||
sort_plan = make_sort_from_pathkeys(root,
|
||||
result_plan,
|
||||
window_pathkeys,
|
||||
-1.0);
|
||||
if (!pathkeys_contained_in(window_pathkeys,
|
||||
current_pathkeys))
|
||||
{
|
||||
/* we do indeed need to sort */
|
||||
result_plan = (Plan *) sort_plan;
|
||||
current_pathkeys = window_pathkeys;
|
||||
}
|
||||
/* In either case, extract the per-column information */
|
||||
get_column_info_for_window(root, wc, tlist,
|
||||
sort_plan->numCols,
|
||||
sort_plan->sortColIdx,
|
||||
&partNumCols,
|
||||
&partColIdx,
|
||||
&partOperators,
|
||||
&ordNumCols,
|
||||
&ordColIdx,
|
||||
&ordOperators);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* empty window specification, nothing to sort */
|
||||
partNumCols = 0;
|
||||
partColIdx = NULL;
|
||||
partOperators = NULL;
|
||||
ordNumCols = 0;
|
||||
ordColIdx = NULL;
|
||||
ordOperators = NULL;
|
||||
}
|
||||
|
||||
if (lnext(l))
|
||||
{
|
||||
/* Add the current WindowFuncs to the running tlist */
|
||||
window_tlist = add_to_flat_tlist(window_tlist,
|
||||
wflists->windowFuncs[wc->winref]);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Install the original tlist in the topmost WindowAgg */
|
||||
window_tlist = tlist;
|
||||
}
|
||||
|
||||
/* ... and make the WindowAgg plan node */
|
||||
result_plan = (Plan *)
|
||||
make_windowagg(root,
|
||||
(List *) copyObject(window_tlist),
|
||||
list_length(wflists->windowFuncs[wc->winref]),
|
||||
partNumCols,
|
||||
partColIdx,
|
||||
partOperators,
|
||||
ordNumCols,
|
||||
ordColIdx,
|
||||
ordOperators,
|
||||
result_plan);
|
||||
}
|
||||
}
|
||||
} /* end of if (setOperations) */
|
||||
|
||||
/*
|
||||
@ -2030,7 +2212,8 @@ make_subplanTargetList(PlannerInfo *root,
|
||||
* If we're not grouping or aggregating, there's nothing to do here;
|
||||
* query_planner should receive the unmodified target list.
|
||||
*/
|
||||
if (!parse->hasAggs && !parse->groupClause && !root->hasHavingQual)
|
||||
if (!parse->hasAggs && !parse->groupClause && !root->hasHavingQual &&
|
||||
!parse->hasWindowFuncs)
|
||||
{
|
||||
*need_tlist_eval = true;
|
||||
return tlist;
|
||||
@ -2039,7 +2222,9 @@ make_subplanTargetList(PlannerInfo *root,
|
||||
/*
|
||||
* Otherwise, start with a "flattened" tlist (having just the vars
|
||||
* mentioned in the targetlist and HAVING qual --- but not upper-level
|
||||
* Vars; they will be replaced by Params later on).
|
||||
* Vars; they will be replaced by Params later on). Note this includes
|
||||
* vars used in resjunk items, so we are covering the needs of ORDER BY
|
||||
* and window specifications.
|
||||
*/
|
||||
sub_tlist = flatten_tlist(tlist);
|
||||
extravars = pull_var_clause(parse->havingQual, true);
|
||||
@ -2066,7 +2251,7 @@ make_subplanTargetList(PlannerInfo *root,
|
||||
{
|
||||
SortGroupClause *grpcl = (SortGroupClause *) lfirst(gl);
|
||||
Node *groupexpr = get_sortgroupclause_expr(grpcl, tlist);
|
||||
TargetEntry *te = NULL;
|
||||
TargetEntry *te;
|
||||
|
||||
/*
|
||||
* Find or make a matching sub_tlist entry. If the groupexpr
|
||||
@ -2074,20 +2259,10 @@ make_subplanTargetList(PlannerInfo *root,
|
||||
* won't make multiple groupClause entries for the same TLE.)
|
||||
*/
|
||||
if (groupexpr && IsA(groupexpr, Var))
|
||||
{
|
||||
ListCell *sl;
|
||||
te = tlist_member(groupexpr, sub_tlist);
|
||||
else
|
||||
te = NULL;
|
||||
|
||||
foreach(sl, sub_tlist)
|
||||
{
|
||||
TargetEntry *lte = (TargetEntry *) lfirst(sl);
|
||||
|
||||
if (equal(groupexpr, lte->expr))
|
||||
{
|
||||
te = lte;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!te)
|
||||
{
|
||||
te = makeTargetEntry((Expr *) groupexpr,
|
||||
@ -2112,7 +2287,7 @@ make_subplanTargetList(PlannerInfo *root,
|
||||
*
|
||||
* This is only needed if we don't use the sub_tlist chosen by
|
||||
* make_subplanTargetList. We have to forget the column indexes found
|
||||
* by that routine and re-locate the grouping vars in the real sub_tlist.
|
||||
* by that routine and re-locate the grouping exprs in the real sub_tlist.
|
||||
*/
|
||||
static void
|
||||
locate_grouping_columns(PlannerInfo *root,
|
||||
@ -2137,18 +2312,10 @@ locate_grouping_columns(PlannerInfo *root,
|
||||
{
|
||||
SortGroupClause *grpcl = (SortGroupClause *) lfirst(gl);
|
||||
Node *groupexpr = get_sortgroupclause_expr(grpcl, tlist);
|
||||
TargetEntry *te = NULL;
|
||||
ListCell *sl;
|
||||
TargetEntry *te = tlist_member(groupexpr, sub_tlist);
|
||||
|
||||
foreach(sl, sub_tlist)
|
||||
{
|
||||
te = (TargetEntry *) lfirst(sl);
|
||||
if (equal(groupexpr, te->expr))
|
||||
break;
|
||||
}
|
||||
if (!sl)
|
||||
if (!te)
|
||||
elog(ERROR, "failed to locate grouping columns");
|
||||
|
||||
groupColIdx[keyno++] = te->resno;
|
||||
}
|
||||
}
|
||||
@ -2190,3 +2357,219 @@ postprocess_setop_tlist(List *new_tlist, List *orig_tlist)
|
||||
elog(ERROR, "resjunk output columns are not implemented");
|
||||
return new_tlist;
|
||||
}
|
||||
|
||||
/*
|
||||
* select_active_windows
|
||||
* Create a list of the "active" window clauses (ie, those referenced
|
||||
* by non-deleted WindowFuncs) in the order they are to be executed.
|
||||
*/
|
||||
static List *
|
||||
select_active_windows(PlannerInfo *root, WindowFuncLists *wflists)
|
||||
{
|
||||
List *result;
|
||||
List *actives;
|
||||
ListCell *lc;
|
||||
|
||||
/* First, make a list of the active windows */
|
||||
actives = NIL;
|
||||
foreach(lc, root->parse->windowClause)
|
||||
{
|
||||
WindowClause *wc = (WindowClause *) lfirst(lc);
|
||||
|
||||
/* It's only active if wflists shows some related WindowFuncs */
|
||||
Assert(wc->winref <= wflists->maxWinRef);
|
||||
if (wflists->windowFuncs[wc->winref] != NIL)
|
||||
actives = lappend(actives, wc);
|
||||
}
|
||||
|
||||
/*
|
||||
* Now, ensure that windows with identical partitioning/ordering clauses
|
||||
* are adjacent in the list. This is required by the SQL standard, which
|
||||
* says that only one sort is to be used for such windows, even if they
|
||||
* are otherwise distinct (eg, different names or framing clauses).
|
||||
*
|
||||
* There is room to be much smarter here, for example detecting whether
|
||||
* one window's sort keys are a prefix of another's (so that sorting
|
||||
* for the latter would do for the former), or putting windows first
|
||||
* that match a sort order available for the underlying query. For the
|
||||
* moment we are content with meeting the spec.
|
||||
*/
|
||||
result = NIL;
|
||||
while (actives != NIL)
|
||||
{
|
||||
WindowClause *wc = (WindowClause *) linitial(actives);
|
||||
ListCell *prev;
|
||||
ListCell *next;
|
||||
|
||||
/* Move wc from actives to result */
|
||||
actives = list_delete_first(actives);
|
||||
result = lappend(result, wc);
|
||||
|
||||
/* Now move any matching windows from actives to result */
|
||||
prev = NULL;
|
||||
for (lc = list_head(actives); lc; lc = next)
|
||||
{
|
||||
WindowClause *wc2 = (WindowClause *) lfirst(lc);
|
||||
|
||||
next = lnext(lc);
|
||||
if (equal(wc->partitionClause, wc2->partitionClause) &&
|
||||
equal(wc->orderClause, wc2->orderClause))
|
||||
{
|
||||
actives = list_delete_cell(actives, lc, prev);
|
||||
result = lappend(result, wc2);
|
||||
}
|
||||
else
|
||||
prev = lc;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* make_pathkeys_for_window
|
||||
* Create a pathkeys list describing the required input ordering
|
||||
* for the given WindowClause.
|
||||
*
|
||||
* The required ordering is first the PARTITION keys, then the ORDER keys.
|
||||
* In the future we might try to implement windowing using hashing, in which
|
||||
* case the ordering could be relaxed, but for now we always sort.
|
||||
*/
|
||||
static List *
|
||||
make_pathkeys_for_window(PlannerInfo *root, WindowClause *wc,
|
||||
List *tlist, bool canonicalize)
|
||||
{
|
||||
List *window_pathkeys;
|
||||
List *window_sortclauses;
|
||||
|
||||
/* Throw error if can't sort */
|
||||
if (!grouping_is_sortable(wc->partitionClause))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("could not implement window PARTITION BY"),
|
||||
errdetail("Window partitioning columns must be of sortable datatypes.")));
|
||||
if (!grouping_is_sortable(wc->orderClause))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("could not implement window ORDER BY"),
|
||||
errdetail("Window ordering columns must be of sortable datatypes.")));
|
||||
|
||||
/* Okay, make the combined pathkeys */
|
||||
window_sortclauses = list_concat(list_copy(wc->partitionClause),
|
||||
list_copy(wc->orderClause));
|
||||
window_pathkeys = make_pathkeys_for_sortclauses(root,
|
||||
window_sortclauses,
|
||||
tlist,
|
||||
canonicalize);
|
||||
list_free(window_sortclauses);
|
||||
return window_pathkeys;
|
||||
}
|
||||
|
||||
/*----------
|
||||
* get_column_info_for_window
|
||||
* Get the partitioning/ordering column numbers and equality operators
|
||||
* for a WindowAgg node.
|
||||
*
|
||||
* This depends on the behavior of make_pathkeys_for_window()!
|
||||
*
|
||||
* We are given the target WindowClause and an array of the input column
|
||||
* numbers associated with the resulting pathkeys. In the easy case, there
|
||||
* are the same number of pathkey columns as partitioning + ordering columns
|
||||
* and we just have to copy some data around. However, it's possible that
|
||||
* some of the original partitioning + ordering columns were eliminated as
|
||||
* redundant during the transformation to pathkeys. (This can happen even
|
||||
* though the parser gets rid of obvious duplicates. A typical scenario is a
|
||||
* window specification "PARTITION BY x ORDER BY y" coupled with a clause
|
||||
* "WHERE x = y" that causes the two sort columns to be recognized as
|
||||
* redundant.) In that unusual case, we have to work a lot harder to
|
||||
* determine which keys are significant.
|
||||
*
|
||||
* The method used here is a bit brute-force: add the sort columns to a list
|
||||
* one at a time and note when the resulting pathkey list gets longer. But
|
||||
* it's a sufficiently uncommon case that a faster way doesn't seem worth
|
||||
* the amount of code refactoring that'd be needed.
|
||||
*----------
|
||||
*/
|
||||
static void
|
||||
get_column_info_for_window(PlannerInfo *root, WindowClause *wc, List *tlist,
|
||||
int numSortCols, AttrNumber *sortColIdx,
|
||||
int *partNumCols,
|
||||
AttrNumber **partColIdx,
|
||||
Oid **partOperators,
|
||||
int *ordNumCols,
|
||||
AttrNumber **ordColIdx,
|
||||
Oid **ordOperators)
|
||||
{
|
||||
int numPart = list_length(wc->partitionClause);
|
||||
int numOrder = list_length(wc->orderClause);
|
||||
|
||||
if (numSortCols == numPart + numOrder)
|
||||
{
|
||||
/* easy case */
|
||||
*partNumCols = numPart;
|
||||
*partColIdx = sortColIdx;
|
||||
*partOperators = extract_grouping_ops(wc->partitionClause);
|
||||
*ordNumCols = numOrder;
|
||||
*ordColIdx = sortColIdx + numPart;
|
||||
*ordOperators = extract_grouping_ops(wc->orderClause);
|
||||
}
|
||||
else
|
||||
{
|
||||
List *sortclauses;
|
||||
List *pathkeys;
|
||||
int scidx;
|
||||
ListCell *lc;
|
||||
|
||||
/* first, allocate what's certainly enough space for the arrays */
|
||||
*partNumCols = 0;
|
||||
*partColIdx = (AttrNumber *) palloc(numPart * sizeof(AttrNumber));
|
||||
*partOperators = (Oid *) palloc(numPart * sizeof(Oid));
|
||||
*ordNumCols = 0;
|
||||
*ordColIdx = (AttrNumber *) palloc(numOrder * sizeof(AttrNumber));
|
||||
*ordOperators = (Oid *) palloc(numOrder * sizeof(Oid));
|
||||
sortclauses = NIL;
|
||||
pathkeys = NIL;
|
||||
scidx = 0;
|
||||
foreach(lc, wc->partitionClause)
|
||||
{
|
||||
SortGroupClause *sgc = (SortGroupClause *) lfirst(lc);
|
||||
List *new_pathkeys;
|
||||
|
||||
sortclauses = lappend(sortclauses, sgc);
|
||||
new_pathkeys = make_pathkeys_for_sortclauses(root,
|
||||
sortclauses,
|
||||
tlist,
|
||||
true);
|
||||
if (list_length(new_pathkeys) > list_length(pathkeys))
|
||||
{
|
||||
/* this sort clause is actually significant */
|
||||
*partColIdx[*partNumCols] = sortColIdx[scidx++];
|
||||
*partOperators[*partNumCols] = sgc->eqop;
|
||||
(*partNumCols)++;
|
||||
pathkeys = new_pathkeys;
|
||||
}
|
||||
}
|
||||
foreach(lc, wc->orderClause)
|
||||
{
|
||||
SortGroupClause *sgc = (SortGroupClause *) lfirst(lc);
|
||||
List *new_pathkeys;
|
||||
|
||||
sortclauses = lappend(sortclauses, sgc);
|
||||
new_pathkeys = make_pathkeys_for_sortclauses(root,
|
||||
sortclauses,
|
||||
tlist,
|
||||
true);
|
||||
if (list_length(new_pathkeys) > list_length(pathkeys))
|
||||
{
|
||||
/* this sort clause is actually significant */
|
||||
*ordColIdx[*ordNumCols] = sortColIdx[scidx++];
|
||||
*ordOperators[*ordNumCols] = sgc->eqop;
|
||||
(*ordNumCols)++;
|
||||
pathkeys = new_pathkeys;
|
||||
}
|
||||
}
|
||||
/* complain if we didn't eat exactly the right number of sort cols */
|
||||
if (scidx != numSortCols)
|
||||
elog(ERROR, "failed to deconstruct sort operators into partitioning/ordering operators");
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.146 2008/10/21 20:42:53 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.147 2008/12/28 18:53:57 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -415,6 +415,7 @@ set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
|
||||
}
|
||||
break;
|
||||
case T_Agg:
|
||||
case T_WindowAgg:
|
||||
case T_Group:
|
||||
set_upper_references(glob, plan, rtoffset);
|
||||
break;
|
||||
@ -679,6 +680,11 @@ fix_expr_common(PlannerGlobal *glob, Node *node)
|
||||
record_plan_function_dependency(glob,
|
||||
((Aggref *) node)->aggfnoid);
|
||||
}
|
||||
else if (IsA(node, WindowFunc))
|
||||
{
|
||||
record_plan_function_dependency(glob,
|
||||
((WindowFunc *) node)->winfnoid);
|
||||
}
|
||||
else if (IsA(node, FuncExpr))
|
||||
{
|
||||
record_plan_function_dependency(glob,
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.143 2008/12/08 00:16:09 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.144 2008/12/28 18:53:57 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -1243,6 +1243,7 @@ simplify_EXISTS_query(Query *query)
|
||||
query->intoClause ||
|
||||
query->setOperations ||
|
||||
query->hasAggs ||
|
||||
query->hasWindowFuncs ||
|
||||
query->havingQual ||
|
||||
query->limitOffset ||
|
||||
query->limitCount ||
|
||||
@ -1258,13 +1259,14 @@ simplify_EXISTS_query(Query *query)
|
||||
|
||||
/*
|
||||
* Otherwise, we can throw away the targetlist, as well as any GROUP,
|
||||
* DISTINCT, and ORDER BY clauses; none of those clauses will change
|
||||
* a nonzero-rows result to zero rows or vice versa. (Furthermore,
|
||||
* WINDOW, DISTINCT, and ORDER BY clauses; none of those clauses will
|
||||
* change a nonzero-rows result to zero rows or vice versa. (Furthermore,
|
||||
* since our parsetree representation of these clauses depends on the
|
||||
* targetlist, we'd better throw them away if we drop the targetlist.)
|
||||
*/
|
||||
query->targetList = NIL;
|
||||
query->groupClause = NIL;
|
||||
query->windowClause = NIL;
|
||||
query->distinctClause = NIL;
|
||||
query->sortClause = NIL;
|
||||
query->hasDistinctOn = false;
|
||||
@ -1321,8 +1323,8 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect,
|
||||
* The rest of the sub-select must not refer to any Vars of the parent
|
||||
* query. (Vars of higher levels should be okay, though.)
|
||||
*
|
||||
* Note: we need not check for Aggs separately because we know the
|
||||
* sub-select is as yet unoptimized; any uplevel Agg must therefore
|
||||
* Note: we need not check for Aggrefs separately because we know the
|
||||
* sub-select is as yet unoptimized; any uplevel Aggref must therefore
|
||||
* contain an uplevel Var reference. This is not the case below ...
|
||||
*/
|
||||
if (contain_vars_of_level((Node *) subselect, 1))
|
||||
@ -1432,7 +1434,8 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect,
|
||||
/*
|
||||
* And there can't be any child Vars in the stuff we intend to pull up.
|
||||
* (Note: we'd need to check for child Aggs too, except we know the
|
||||
* child has no aggs at all because of simplify_EXISTS_query's check.)
|
||||
* child has no aggs at all because of simplify_EXISTS_query's check.
|
||||
* The same goes for window functions.)
|
||||
*/
|
||||
if (contain_vars_of_level((Node *) leftargs, 0))
|
||||
return NULL;
|
||||
@ -1955,6 +1958,7 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params)
|
||||
case T_RecursiveUnion:
|
||||
case T_Hash:
|
||||
case T_Agg:
|
||||
case T_WindowAgg:
|
||||
case T_SeqScan:
|
||||
case T_Material:
|
||||
case T_Sort:
|
||||
|
@ -16,7 +16,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.60 2008/11/11 19:05:21 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.61 2008/12/28 18:53:57 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -742,7 +742,10 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
|
||||
* Miscellaneous housekeeping.
|
||||
*/
|
||||
parse->hasSubLinks |= subquery->hasSubLinks;
|
||||
/* subquery won't be pulled up if it hasAggs, so no work there */
|
||||
/*
|
||||
* subquery won't be pulled up if it hasAggs or hasWindowFuncs, so no
|
||||
* work needed on those flags
|
||||
*/
|
||||
|
||||
/*
|
||||
* Return the adjusted subquery jointree to replace the RangeTblRef entry
|
||||
@ -931,6 +934,7 @@ is_simple_subquery(Query *subquery)
|
||||
* limiting, or WITH. (XXX WITH could possibly be allowed later)
|
||||
*/
|
||||
if (subquery->hasAggs ||
|
||||
subquery->hasWindowFuncs ||
|
||||
subquery->groupClause ||
|
||||
subquery->havingQual ||
|
||||
subquery->sortClause ||
|
||||
|
@ -22,7 +22,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.162 2008/11/15 19:43:46 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.163 2008/12/28 18:53:57 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -136,6 +136,7 @@ plan_set_operations(PlannerInfo *root, double tuple_fraction,
|
||||
Assert(parse->jointree->quals == NULL);
|
||||
Assert(parse->groupClause == NIL);
|
||||
Assert(parse->havingQual == NULL);
|
||||
Assert(parse->windowClause == NIL);
|
||||
Assert(parse->distinctClause == NIL);
|
||||
|
||||
/*
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.271 2008/12/18 18:20:34 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.272 2008/12/28 18:53:57 tgl Exp $
|
||||
*
|
||||
* HISTORY
|
||||
* AUTHOR DATE MAJOR EVENT
|
||||
@ -72,7 +72,9 @@ typedef struct
|
||||
} substitute_actual_srf_parameters_context;
|
||||
|
||||
static bool contain_agg_clause_walker(Node *node, void *context);
|
||||
static bool pull_agg_clause_walker(Node *node, List **context);
|
||||
static bool count_agg_clauses_walker(Node *node, AggClauseCounts *counts);
|
||||
static bool find_window_functions_walker(Node *node, WindowFuncLists *lists);
|
||||
static bool expression_returns_set_rows_walker(Node *node, double *count);
|
||||
static bool contain_subplans_walker(Node *node, void *context);
|
||||
static bool contain_mutable_functions_walker(Node *node, void *context);
|
||||
@ -388,6 +390,41 @@ contain_agg_clause_walker(Node *node, void *context)
|
||||
return expression_tree_walker(node, contain_agg_clause_walker, context);
|
||||
}
|
||||
|
||||
/*
|
||||
* pull_agg_clause
|
||||
* Recursively search for Aggref nodes within a clause.
|
||||
*
|
||||
* Returns a List of all Aggrefs found.
|
||||
*
|
||||
* This does not descend into subqueries, and so should be used only after
|
||||
* reduction of sublinks to subplans, or in contexts where it's known there
|
||||
* are no subqueries. There mustn't be outer-aggregate references either.
|
||||
*/
|
||||
List *
|
||||
pull_agg_clause(Node *clause)
|
||||
{
|
||||
List *result = NIL;
|
||||
|
||||
(void) pull_agg_clause_walker(clause, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool
|
||||
pull_agg_clause_walker(Node *node, List **context)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false;
|
||||
if (IsA(node, Aggref))
|
||||
{
|
||||
Assert(((Aggref *) node)->agglevelsup == 0);
|
||||
*context = lappend(*context, node);
|
||||
return false; /* no need to descend into arguments */
|
||||
}
|
||||
Assert(!IsA(node, SubLink));
|
||||
return expression_tree_walker(node, pull_agg_clause_walker,
|
||||
(void *) context);
|
||||
}
|
||||
|
||||
/*
|
||||
* count_agg_clauses
|
||||
* Recursively count the Aggref nodes in an expression tree.
|
||||
@ -519,6 +556,79 @@ count_agg_clauses_walker(Node *node, AggClauseCounts *counts)
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
* Window-function clause manipulation
|
||||
*****************************************************************************/
|
||||
|
||||
/*
|
||||
* contain_window_function
|
||||
* Recursively search for WindowFunc nodes within a clause.
|
||||
*
|
||||
* Since window functions don't have level fields, but are hard-wired to
|
||||
* be associated with the current query level, this is just the same as
|
||||
* rewriteManip.c's function.
|
||||
*/
|
||||
bool
|
||||
contain_window_function(Node *clause)
|
||||
{
|
||||
return checkExprHasWindowFuncs(clause);
|
||||
}
|
||||
|
||||
/*
|
||||
* find_window_functions
|
||||
* Locate all the WindowFunc nodes in an expression tree, and organize
|
||||
* them by winref ID number.
|
||||
*
|
||||
* Caller must provide an upper bound on the winref IDs expected in the tree.
|
||||
*/
|
||||
WindowFuncLists *
|
||||
find_window_functions(Node *clause, Index maxWinRef)
|
||||
{
|
||||
WindowFuncLists *lists = palloc(sizeof(WindowFuncLists));
|
||||
|
||||
lists->numWindowFuncs = 0;
|
||||
lists->maxWinRef = maxWinRef;
|
||||
lists->windowFuncs = (List **) palloc0((maxWinRef + 1) * sizeof(List *));
|
||||
(void) find_window_functions_walker(clause, lists);
|
||||
return lists;
|
||||
}
|
||||
|
||||
static bool
|
||||
find_window_functions_walker(Node *node, WindowFuncLists *lists)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false;
|
||||
if (IsA(node, WindowFunc))
|
||||
{
|
||||
WindowFunc *wfunc = (WindowFunc *) node;
|
||||
|
||||
/* winref is unsigned, so one-sided test is OK */
|
||||
if (wfunc->winref > lists->maxWinRef)
|
||||
elog(ERROR, "WindowFunc contains out-of-range winref %u",
|
||||
wfunc->winref);
|
||||
lists->windowFuncs[wfunc->winref] =
|
||||
lappend(lists->windowFuncs[wfunc->winref], wfunc);
|
||||
lists->numWindowFuncs++;
|
||||
|
||||
/*
|
||||
* Complain if the window function's arguments contain window functions
|
||||
*/
|
||||
if (contain_window_function((Node *) wfunc->args))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("window function calls cannot be nested")));
|
||||
|
||||
/*
|
||||
* Having checked that, we need not recurse into the argument.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
Assert(!IsA(node, SubLink));
|
||||
return expression_tree_walker(node, find_window_functions_walker,
|
||||
(void *) lists);
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
* Support for expressions returning sets
|
||||
*****************************************************************************/
|
||||
@ -567,6 +677,8 @@ expression_returns_set_rows_walker(Node *node, double *count)
|
||||
/* Avoid recursion for some cases that can't return a set */
|
||||
if (IsA(node, Aggref))
|
||||
return false;
|
||||
if (IsA(node, WindowFunc))
|
||||
return false;
|
||||
if (IsA(node, DistinctExpr))
|
||||
return false;
|
||||
if (IsA(node, ScalarArrayOpExpr))
|
||||
@ -897,6 +1009,11 @@ contain_nonstrict_functions_walker(Node *node, void *context)
|
||||
/* an aggregate could return non-null with null input */
|
||||
return true;
|
||||
}
|
||||
if (IsA(node, WindowFunc))
|
||||
{
|
||||
/* a window function could return non-null with null input */
|
||||
return true;
|
||||
}
|
||||
if (IsA(node, ArrayRef))
|
||||
{
|
||||
/* array assignment is nonstrict, but subscripting is strict */
|
||||
@ -1589,7 +1706,8 @@ is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK)
|
||||
* not-constant expressions, namely aggregates (Aggrefs). In current usage
|
||||
* this is only applied to WHERE clauses and so a check for Aggrefs would be
|
||||
* a waste of cycles; but be sure to also check contain_agg_clause() if you
|
||||
* want to know about pseudo-constness in other contexts.
|
||||
* want to know about pseudo-constness in other contexts. The same goes
|
||||
* for window functions (WindowFuncs).
|
||||
*/
|
||||
bool
|
||||
is_pseudo_constant_clause(Node *clause)
|
||||
@ -3472,6 +3590,7 @@ inline_function(Oid funcid, Oid result_type, List *args,
|
||||
querytree->utilityStmt ||
|
||||
querytree->intoClause ||
|
||||
querytree->hasAggs ||
|
||||
querytree->hasWindowFuncs ||
|
||||
querytree->hasSubLinks ||
|
||||
querytree->cteList ||
|
||||
querytree->rtable ||
|
||||
@ -3479,6 +3598,7 @@ inline_function(Oid funcid, Oid result_type, List *args,
|
||||
querytree->jointree->quals ||
|
||||
querytree->groupClause ||
|
||||
querytree->havingQual ||
|
||||
querytree->windowClause ||
|
||||
querytree->distinctClause ||
|
||||
querytree->sortClause ||
|
||||
querytree->limitOffset ||
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/util/tlist.c,v 1.83 2008/10/21 20:42:53 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/util/tlist.c,v 1.84 2008/12/28 18:53:57 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -101,28 +101,28 @@ flatten_tlist(List *tlist)
|
||||
|
||||
/*
|
||||
* add_to_flat_tlist
|
||||
* Add more vars to a flattened tlist (if they're not already in it)
|
||||
* Add more items to a flattened tlist (if they're not already in it)
|
||||
*
|
||||
* 'tlist' is the flattened tlist
|
||||
* 'vars' is a list of Var and/or PlaceHolderVar nodes
|
||||
* 'exprs' is a list of expressions (usually, but not necessarily, Vars)
|
||||
*
|
||||
* Returns the extended tlist.
|
||||
*/
|
||||
List *
|
||||
add_to_flat_tlist(List *tlist, List *vars)
|
||||
add_to_flat_tlist(List *tlist, List *exprs)
|
||||
{
|
||||
int next_resno = list_length(tlist) + 1;
|
||||
ListCell *v;
|
||||
ListCell *lc;
|
||||
|
||||
foreach(v, vars)
|
||||
foreach(lc, exprs)
|
||||
{
|
||||
Node *var = (Node *) lfirst(v);
|
||||
Node *expr = (Node *) lfirst(lc);
|
||||
|
||||
if (!tlist_member(var, tlist))
|
||||
if (!tlist_member(expr, tlist))
|
||||
{
|
||||
TargetEntry *tle;
|
||||
|
||||
tle = makeTargetEntry(copyObject(var), /* copy needed?? */
|
||||
tle = makeTargetEntry(copyObject(expr), /* copy needed?? */
|
||||
next_resno++,
|
||||
NULL,
|
||||
false);
|
||||
|
@ -17,7 +17,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.384 2008/12/13 02:00:19 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.385 2008/12/28 18:53:58 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -306,6 +306,9 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
|
||||
qry->hasAggs = pstate->p_hasAggs;
|
||||
if (pstate->p_hasAggs)
|
||||
parseCheckAggregates(pstate, qry);
|
||||
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
|
||||
if (pstate->p_hasWindowFuncs)
|
||||
parseCheckWindowFuncs(pstate, qry);
|
||||
|
||||
return qry;
|
||||
}
|
||||
@ -673,6 +676,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
|
||||
errmsg("cannot use aggregate function in VALUES"),
|
||||
parser_errposition(pstate,
|
||||
locate_agg_of_level((Node *) qry, 0))));
|
||||
if (pstate->p_hasWindowFuncs)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("cannot use window function in VALUES"),
|
||||
parser_errposition(pstate,
|
||||
locate_windowfunc((Node *) qry))));
|
||||
|
||||
return qry;
|
||||
}
|
||||
@ -764,6 +773,9 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
|
||||
/* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
|
||||
pstate->p_locking_clause = stmt->lockingClause;
|
||||
|
||||
/* make WINDOW info available for window functions, too */
|
||||
pstate->p_windowdefs = stmt->windowClause;
|
||||
|
||||
/* process the WITH clause */
|
||||
if (stmt->withClause)
|
||||
{
|
||||
@ -803,7 +815,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
|
||||
qry->groupClause = transformGroupClause(pstate,
|
||||
stmt->groupClause,
|
||||
&qry->targetList,
|
||||
qry->sortClause);
|
||||
qry->sortClause,
|
||||
false);
|
||||
|
||||
if (stmt->distinctClause == NIL)
|
||||
{
|
||||
@ -834,6 +847,11 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
|
||||
qry->limitCount = transformLimitClause(pstate, stmt->limitCount,
|
||||
"LIMIT");
|
||||
|
||||
/* transform window clauses after we have seen all window functions */
|
||||
qry->windowClause = transformWindowDefinitions(pstate,
|
||||
pstate->p_windowdefs,
|
||||
&qry->targetList);
|
||||
|
||||
/* handle any SELECT INTO/CREATE TABLE AS spec */
|
||||
if (stmt->intoClause)
|
||||
{
|
||||
@ -849,6 +867,9 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
|
||||
qry->hasAggs = pstate->p_hasAggs;
|
||||
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
|
||||
parseCheckAggregates(pstate, qry);
|
||||
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
|
||||
if (pstate->p_hasWindowFuncs)
|
||||
parseCheckWindowFuncs(pstate, qry);
|
||||
|
||||
foreach(l, stmt->lockingClause)
|
||||
{
|
||||
@ -889,6 +910,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
|
||||
Assert(stmt->whereClause == NULL);
|
||||
Assert(stmt->groupClause == NIL);
|
||||
Assert(stmt->havingClause == NULL);
|
||||
Assert(stmt->windowClause == NIL);
|
||||
Assert(stmt->op == SETOP_NONE);
|
||||
|
||||
/* process the WITH clause */
|
||||
@ -1061,6 +1083,12 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
|
||||
errmsg("cannot use aggregate function in VALUES"),
|
||||
parser_errposition(pstate,
|
||||
locate_agg_of_level((Node *) newExprsLists, 0))));
|
||||
if (pstate->p_hasWindowFuncs)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("cannot use window function in VALUES"),
|
||||
parser_errposition(pstate,
|
||||
locate_windowfunc((Node *) newExprsLists))));
|
||||
|
||||
return qry;
|
||||
}
|
||||
@ -1289,6 +1317,9 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
|
||||
qry->hasAggs = pstate->p_hasAggs;
|
||||
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
|
||||
parseCheckAggregates(pstate, qry);
|
||||
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
|
||||
if (pstate->p_hasWindowFuncs)
|
||||
parseCheckWindowFuncs(pstate, qry);
|
||||
|
||||
foreach(l, lockingClause)
|
||||
{
|
||||
@ -1623,6 +1654,12 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
|
||||
errmsg("cannot use aggregate function in UPDATE"),
|
||||
parser_errposition(pstate,
|
||||
locate_agg_of_level((Node *) qry, 0))));
|
||||
if (pstate->p_hasWindowFuncs)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("cannot use window function in UPDATE"),
|
||||
parser_errposition(pstate,
|
||||
locate_windowfunc((Node *) qry))));
|
||||
|
||||
/*
|
||||
* Now we are done with SELECT-like processing, and can get on with
|
||||
@ -1692,6 +1729,7 @@ transformReturningList(ParseState *pstate, List *returningList)
|
||||
List *rlist;
|
||||
int save_next_resno;
|
||||
bool save_hasAggs;
|
||||
bool save_hasWindowFuncs;
|
||||
int length_rtable;
|
||||
|
||||
if (returningList == NIL)
|
||||
@ -1708,6 +1746,8 @@ transformReturningList(ParseState *pstate, List *returningList)
|
||||
/* save other state so that we can detect disallowed stuff */
|
||||
save_hasAggs = pstate->p_hasAggs;
|
||||
pstate->p_hasAggs = false;
|
||||
save_hasWindowFuncs = pstate->p_hasWindowFuncs;
|
||||
pstate->p_hasWindowFuncs = false;
|
||||
length_rtable = list_length(pstate->p_rtable);
|
||||
|
||||
/* transform RETURNING identically to a SELECT targetlist */
|
||||
@ -1722,6 +1762,12 @@ transformReturningList(ParseState *pstate, List *returningList)
|
||||
errmsg("cannot use aggregate function in RETURNING"),
|
||||
parser_errposition(pstate,
|
||||
locate_agg_of_level((Node *) rlist, 0))));
|
||||
if (pstate->p_hasWindowFuncs)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("cannot use window function in RETURNING"),
|
||||
parser_errposition(pstate,
|
||||
locate_windowfunc((Node *) rlist))));
|
||||
|
||||
/* no new relation references please */
|
||||
if (list_length(pstate->p_rtable) != length_rtable)
|
||||
@ -1748,6 +1794,7 @@ transformReturningList(ParseState *pstate, List *returningList)
|
||||
/* restore state */
|
||||
pstate->p_next_resno = save_next_resno;
|
||||
pstate->p_hasAggs = save_hasAggs;
|
||||
pstate->p_hasWindowFuncs = save_hasWindowFuncs;
|
||||
|
||||
return rlist;
|
||||
}
|
||||
@ -1883,6 +1930,10 @@ CheckSelectLocking(Query *qry)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("SELECT FOR UPDATE/SHARE is not allowed with aggregate functions")));
|
||||
if (qry->hasWindowFuncs)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("SELECT FOR UPDATE/SHARE is not allowed with window functions")));
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -11,7 +11,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.647 2008/12/20 16:02:55 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.648 2008/12/28 18:53:58 tgl Exp $
|
||||
*
|
||||
* HISTORY
|
||||
* AUTHOR DATE MAJOR EVENT
|
||||
@ -158,6 +158,7 @@ static TypeName *TableFuncTypeName(List *columns);
|
||||
DefElem *defelt;
|
||||
OptionDefElem *optdef;
|
||||
SortBy *sortby;
|
||||
WindowDef *windef;
|
||||
JoinExpr *jexpr;
|
||||
IndexElem *ielem;
|
||||
Alias *alias;
|
||||
@ -402,6 +403,10 @@ static TypeName *TableFuncTypeName(List *columns);
|
||||
%type <with> with_clause
|
||||
%type <list> cte_list
|
||||
|
||||
%type <list> window_clause window_definition_list opt_partition_clause
|
||||
%type <windef> window_definition over_clause window_specification
|
||||
%type <str> opt_existing_window_name
|
||||
|
||||
|
||||
/*
|
||||
* If you make any token changes, update the keyword table in
|
||||
@ -431,8 +436,8 @@ static TypeName *TableFuncTypeName(List *columns);
|
||||
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
|
||||
DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
|
||||
|
||||
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EXCEPT EXCLUDING
|
||||
EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT
|
||||
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EXCEPT
|
||||
EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT
|
||||
|
||||
FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOR FORCE FOREIGN FORWARD
|
||||
FREEZE FROM FULL FUNCTION
|
||||
@ -461,9 +466,9 @@ static TypeName *TableFuncTypeName(List *columns);
|
||||
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMERIC
|
||||
|
||||
OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OPTIONS OR
|
||||
ORDER OUT_P OUTER_P OVERLAPS OVERLAY OWNED OWNER
|
||||
ORDER OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
|
||||
|
||||
PARSER PARTIAL PASSWORD PLACING PLANS POSITION
|
||||
PARSER PARTIAL PARTITION PASSWORD PLACING PLANS POSITION
|
||||
PRECISION PRESERVE PREPARE PREPARED PRIMARY
|
||||
PRIOR PRIVILEGES PROCEDURAL PROCEDURE
|
||||
|
||||
@ -489,7 +494,7 @@ static TypeName *TableFuncTypeName(List *columns);
|
||||
VACUUM VALID VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
|
||||
VERBOSE VERSION_P VIEW VOLATILE
|
||||
|
||||
WHEN WHERE WHITESPACE_P WITH WITHOUT WORK WRAPPER WRITE
|
||||
WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
|
||||
|
||||
XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLFOREST XMLPARSE
|
||||
XMLPI XMLROOT XMLSERIALIZE
|
||||
@ -523,7 +528,15 @@ static TypeName *TableFuncTypeName(List *columns);
|
||||
%nonassoc BETWEEN
|
||||
%nonassoc IN_P
|
||||
%left POSTFIXOP /* dummy for postfix Op rules */
|
||||
%nonassoc IDENT /* to support target_el without AS */
|
||||
/*
|
||||
* To support target_el without AS, we must give IDENT an explicit priority
|
||||
* between POSTFIXOP and Op. We can safely assign the same priority to
|
||||
* various unreserved keywords as needed to resolve ambiguities (this can't
|
||||
* have any bad effects since obviously the keywords will still behave the
|
||||
* same as if they weren't keywords). We need to do this for PARTITION
|
||||
* to support opt_existing_window_name.
|
||||
*/
|
||||
%nonassoc IDENT PARTITION
|
||||
%left Op OPERATOR /* multi-character ops and user-defined operators */
|
||||
%nonassoc NOTNULL
|
||||
%nonassoc ISNULL
|
||||
@ -1259,7 +1272,7 @@ opt_boolean:
|
||||
* - an integer or floating point number
|
||||
* - a time interval per SQL99
|
||||
* ColId gives reduce/reduce errors against ConstInterval and LOCAL,
|
||||
* so use IDENT and reject anything which is a reserved word.
|
||||
* so use IDENT (meaning we reject anything that is a key word).
|
||||
*/
|
||||
zone_value:
|
||||
Sconst
|
||||
@ -3466,6 +3479,11 @@ old_aggr_list: old_aggr_elem { $$ = list_make1($1); }
|
||||
| old_aggr_list ',' old_aggr_elem { $$ = lappend($1, $3); }
|
||||
;
|
||||
|
||||
/*
|
||||
* Must use IDENT here to avoid reduce/reduce conflicts; fortunately none of
|
||||
* the item names needed in old aggregate definitions are likely to become
|
||||
* SQL keywords.
|
||||
*/
|
||||
old_aggr_elem: IDENT '=' def_arg
|
||||
{
|
||||
$$ = makeDefElem($1, (Node *)$3);
|
||||
@ -6825,7 +6843,7 @@ select_clause:
|
||||
simple_select:
|
||||
SELECT opt_distinct target_list
|
||||
into_clause from_clause where_clause
|
||||
group_clause having_clause
|
||||
group_clause having_clause window_clause
|
||||
{
|
||||
SelectStmt *n = makeNode(SelectStmt);
|
||||
n->distinctClause = $2;
|
||||
@ -6835,6 +6853,7 @@ simple_select:
|
||||
n->whereClause = $6;
|
||||
n->groupClause = $7;
|
||||
n->havingClause = $8;
|
||||
n->windowClause = $9;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
| values_clause { $$ = $1; }
|
||||
@ -8076,6 +8095,7 @@ a_expr: c_expr { $$ = $1; }
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @2;
|
||||
$$ = (Node *) n;
|
||||
}
|
||||
@ -8135,6 +8155,7 @@ a_expr: c_expr { $$ = $1; }
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @4;
|
||||
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1, (Node *) n, @2);
|
||||
}
|
||||
@ -8148,6 +8169,7 @@ a_expr: c_expr { $$ = $1; }
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @5;
|
||||
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~", $1, (Node *) n, @2);
|
||||
}
|
||||
@ -8161,6 +8183,7 @@ a_expr: c_expr { $$ = $1; }
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @4;
|
||||
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~*", $1, (Node *) n, @2);
|
||||
}
|
||||
@ -8174,6 +8197,7 @@ a_expr: c_expr { $$ = $1; }
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @5;
|
||||
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~*", $1, (Node *) n, @2);
|
||||
}
|
||||
@ -8186,6 +8210,7 @@ a_expr: c_expr { $$ = $1; }
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @2;
|
||||
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
|
||||
}
|
||||
@ -8197,6 +8222,7 @@ a_expr: c_expr { $$ = $1; }
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @5;
|
||||
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
|
||||
}
|
||||
@ -8208,6 +8234,7 @@ a_expr: c_expr { $$ = $1; }
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @5;
|
||||
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
|
||||
}
|
||||
@ -8219,6 +8246,7 @@ a_expr: c_expr { $$ = $1; }
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @6;
|
||||
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
|
||||
}
|
||||
@ -8622,7 +8650,7 @@ c_expr: columnref { $$ = $1; }
|
||||
* (Note that many of the special SQL functions wouldn't actually make any
|
||||
* sense as functional index entries, but we ignore that consideration here.)
|
||||
*/
|
||||
func_expr: func_name '(' ')'
|
||||
func_expr: func_name '(' ')' over_clause
|
||||
{
|
||||
FuncCall *n = makeNode(FuncCall);
|
||||
n->funcname = $1;
|
||||
@ -8630,10 +8658,11 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = $4;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
| func_name '(' expr_list ')'
|
||||
| func_name '(' expr_list ')' over_clause
|
||||
{
|
||||
FuncCall *n = makeNode(FuncCall);
|
||||
n->funcname = $1;
|
||||
@ -8641,10 +8670,11 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = $5;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
| func_name '(' VARIADIC a_expr ')'
|
||||
| func_name '(' VARIADIC a_expr ')' over_clause
|
||||
{
|
||||
FuncCall *n = makeNode(FuncCall);
|
||||
n->funcname = $1;
|
||||
@ -8652,10 +8682,11 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = TRUE;
|
||||
n->over = $6;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
| func_name '(' expr_list ',' VARIADIC a_expr ')'
|
||||
| func_name '(' expr_list ',' VARIADIC a_expr ')' over_clause
|
||||
{
|
||||
FuncCall *n = makeNode(FuncCall);
|
||||
n->funcname = $1;
|
||||
@ -8663,10 +8694,11 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = TRUE;
|
||||
n->over = $8;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
| func_name '(' ALL expr_list ')'
|
||||
| func_name '(' ALL expr_list ')' over_clause
|
||||
{
|
||||
FuncCall *n = makeNode(FuncCall);
|
||||
n->funcname = $1;
|
||||
@ -8678,10 +8710,11 @@ func_expr: func_name '(' ')'
|
||||
* for that in FuncCall at the moment.
|
||||
*/
|
||||
n->func_variadic = FALSE;
|
||||
n->over = $6;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
| func_name '(' DISTINCT expr_list ')'
|
||||
| func_name '(' DISTINCT expr_list ')' over_clause
|
||||
{
|
||||
FuncCall *n = makeNode(FuncCall);
|
||||
n->funcname = $1;
|
||||
@ -8689,10 +8722,11 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = TRUE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = $6;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
| func_name '(' '*' ')'
|
||||
| func_name '(' '*' ')' over_clause
|
||||
{
|
||||
/*
|
||||
* We consider AGGREGATE(*) to invoke a parameterless
|
||||
@ -8710,6 +8744,7 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = TRUE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = $5;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
@ -8769,6 +8804,7 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
@ -8839,6 +8875,7 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
@ -8850,6 +8887,7 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
@ -8861,6 +8899,7 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
@ -8872,6 +8911,7 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
@ -8883,6 +8923,7 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
@ -8894,6 +8935,7 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
@ -8907,6 +8949,7 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
@ -8923,6 +8966,7 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
@ -8935,6 +8979,7 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
@ -8949,6 +8994,7 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
@ -8969,6 +9015,7 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
@ -8983,6 +9030,7 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
@ -8994,6 +9042,7 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
@ -9005,6 +9054,7 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
@ -9016,6 +9066,7 @@ func_expr: func_name '(' ')'
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
@ -9156,6 +9207,77 @@ xml_whitespace_option: PRESERVE WHITESPACE_P { $$ = TRUE; }
|
||||
| /*EMPTY*/ { $$ = FALSE; }
|
||||
;
|
||||
|
||||
/*
|
||||
* Window Definitions
|
||||
*/
|
||||
window_clause:
|
||||
WINDOW window_definition_list { $$ = $2; }
|
||||
| /*EMPTY*/ { $$ = NIL; }
|
||||
;
|
||||
|
||||
window_definition_list:
|
||||
window_definition { $$ = list_make1($1); }
|
||||
| window_definition_list ',' window_definition
|
||||
{ $$ = lappend($1, $3); }
|
||||
;
|
||||
|
||||
window_definition:
|
||||
ColId AS window_specification
|
||||
{
|
||||
WindowDef *n = $3;
|
||||
n->name = $1;
|
||||
$$ = n;
|
||||
}
|
||||
;
|
||||
|
||||
over_clause: OVER window_specification
|
||||
{ $$ = $2; }
|
||||
| OVER ColId
|
||||
{
|
||||
WindowDef *n = makeNode(WindowDef);
|
||||
n->name = NULL;
|
||||
n->refname = $2;
|
||||
n->partitionClause = NIL;
|
||||
n->orderClause = NIL;
|
||||
n->location = @2;
|
||||
$$ = n;
|
||||
}
|
||||
| /*EMPTY*/
|
||||
{ $$ = NULL; }
|
||||
;
|
||||
|
||||
window_specification: '(' opt_existing_window_name opt_partition_clause
|
||||
opt_sort_clause ')'
|
||||
{
|
||||
WindowDef *n = makeNode(WindowDef);
|
||||
n->name = NULL;
|
||||
n->refname = $2;
|
||||
n->partitionClause = $3;
|
||||
n->orderClause = $4;
|
||||
n->location = @1;
|
||||
$$ = n;
|
||||
}
|
||||
;
|
||||
|
||||
/*
|
||||
* If we see PARTITION, RANGE, or ROWS as the first token after the '('
|
||||
* of a window_specification, we want the assumption to be that there is
|
||||
* no existing_window_name; but those keywords are unreserved and so could
|
||||
* be ColIds. We fix this by making them have the same precedence as IDENT
|
||||
* and giving the empty production here a slightly higher precedence, so
|
||||
* that the shift/reduce conflict is resolved in favor of reducing the rule.
|
||||
* These keywords are thus precluded from being an existing_window_name but
|
||||
* are not reserved for any other purpose.
|
||||
* (RANGE/ROWS are not an issue as of 8.4 for lack of frame_clause support.)
|
||||
*/
|
||||
opt_existing_window_name: ColId { $$ = $1; }
|
||||
| /*EMPTY*/ %prec Op { $$ = NULL; }
|
||||
;
|
||||
|
||||
opt_partition_clause: PARTITION BY expr_list { $$ = $3; }
|
||||
| /*EMPTY*/ { $$ = NIL; }
|
||||
;
|
||||
|
||||
/*
|
||||
* Supporting nonterminals for expressions.
|
||||
*/
|
||||
@ -9961,6 +10083,7 @@ unreserved_keyword:
|
||||
| OWNER
|
||||
| PARSER
|
||||
| PARTIAL
|
||||
| PARTITION
|
||||
| PASSWORD
|
||||
| PLANS
|
||||
| PREPARE
|
||||
@ -10139,6 +10262,7 @@ type_func_name_keyword:
|
||||
| NATURAL
|
||||
| NOTNULL
|
||||
| OUTER_P
|
||||
| OVER
|
||||
| OVERLAPS
|
||||
| RIGHT
|
||||
| SIMILAR
|
||||
@ -10229,6 +10353,7 @@ reserved_keyword:
|
||||
| VARIADIC
|
||||
| WHEN
|
||||
| WHERE
|
||||
| WINDOW
|
||||
| WITH
|
||||
;
|
||||
|
||||
@ -10451,6 +10576,7 @@ makeOverlaps(List *largs, List *rargs, int location)
|
||||
n->agg_star = FALSE;
|
||||
n->agg_distinct = FALSE;
|
||||
n->func_variadic = FALSE;
|
||||
n->over = NULL;
|
||||
n->location = location;
|
||||
return n;
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.206 2008/12/19 16:25:17 petere Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.207 2008/12/28 18:53:58 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -287,12 +287,14 @@ const ScanKeyword ScanKeywords[] = {
|
||||
{"order", ORDER, RESERVED_KEYWORD},
|
||||
{"out", OUT_P, COL_NAME_KEYWORD},
|
||||
{"outer", OUTER_P, TYPE_FUNC_NAME_KEYWORD},
|
||||
{"over", OVER, TYPE_FUNC_NAME_KEYWORD},
|
||||
{"overlaps", OVERLAPS, TYPE_FUNC_NAME_KEYWORD},
|
||||
{"overlay", OVERLAY, COL_NAME_KEYWORD},
|
||||
{"owned", OWNED, UNRESERVED_KEYWORD},
|
||||
{"owner", OWNER, UNRESERVED_KEYWORD},
|
||||
{"parser", PARSER, UNRESERVED_KEYWORD},
|
||||
{"partial", PARTIAL, UNRESERVED_KEYWORD},
|
||||
{"partition", PARTITION, UNRESERVED_KEYWORD},
|
||||
{"password", PASSWORD, UNRESERVED_KEYWORD},
|
||||
{"placing", PLACING, RESERVED_KEYWORD},
|
||||
{"plans", PLANS, UNRESERVED_KEYWORD},
|
||||
@ -411,6 +413,7 @@ const ScanKeyword ScanKeywords[] = {
|
||||
{"when", WHEN, RESERVED_KEYWORD},
|
||||
{"where", WHERE, RESERVED_KEYWORD},
|
||||
{"whitespace", WHITESPACE_P, UNRESERVED_KEYWORD},
|
||||
{"window", WINDOW, RESERVED_KEYWORD},
|
||||
{"with", WITH, RESERVED_KEYWORD},
|
||||
{"without", WITHOUT, UNRESERVED_KEYWORD},
|
||||
{"work", WORK, UNRESERVED_KEYWORD},
|
||||
|
@ -1,14 +1,14 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* parse_agg.c
|
||||
* handle aggregates in parser
|
||||
* handle aggregates and window functions in parser
|
||||
*
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/parser/parse_agg.c,v 1.84 2008/10/04 21:56:54 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/parser/parse_agg.c,v 1.85 2008/12/28 18:53:58 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -67,7 +67,8 @@ transformAggregateCall(ParseState *pstate, Aggref *agg)
|
||||
*/
|
||||
if (min_varlevel == 0)
|
||||
{
|
||||
if (checkExprHasAggs((Node *) agg->args))
|
||||
if (pstate->p_hasAggs &&
|
||||
checkExprHasAggs((Node *) agg->args))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_GROUPING_ERROR),
|
||||
errmsg("aggregate function calls cannot be nested"),
|
||||
@ -75,6 +76,15 @@ transformAggregateCall(ParseState *pstate, Aggref *agg)
|
||||
locate_agg_of_level((Node *) agg->args, 0))));
|
||||
}
|
||||
|
||||
/* It can't contain window functions either */
|
||||
if (pstate->p_hasWindowFuncs &&
|
||||
checkExprHasWindowFuncs((Node *) agg->args))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_GROUPING_ERROR),
|
||||
errmsg("aggregate function calls cannot contain window function calls"),
|
||||
parser_errposition(pstate,
|
||||
locate_windowfunc((Node *) agg->args))));
|
||||
|
||||
if (min_varlevel < 0)
|
||||
min_varlevel = 0;
|
||||
agg->agglevelsup = min_varlevel;
|
||||
@ -85,6 +95,98 @@ transformAggregateCall(ParseState *pstate, Aggref *agg)
|
||||
pstate->p_hasAggs = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* transformWindowFuncCall -
|
||||
* Finish initial transformation of a window function call
|
||||
*
|
||||
* parse_func.c has recognized the function as a window function, and has set
|
||||
* up all the fields of the WindowFunc except winref. Here we must (1) add
|
||||
* the WindowDef to the pstate (if not a duplicate of one already present) and
|
||||
* set winref to link to it; and (2) mark p_hasWindowFuncs true in the pstate.
|
||||
* Unlike aggregates, only the most closely nested pstate level need be
|
||||
* considered --- there are no "outer window functions" per SQL spec.
|
||||
*/
|
||||
void
|
||||
transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
|
||||
WindowDef *windef)
|
||||
{
|
||||
/*
|
||||
* A window function call can't contain another one (but aggs are OK).
|
||||
* XXX is this required by spec, or just an unimplemented feature?
|
||||
*/
|
||||
if (pstate->p_hasWindowFuncs &&
|
||||
checkExprHasWindowFuncs((Node *) wfunc->args))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("window function calls cannot be nested"),
|
||||
parser_errposition(pstate,
|
||||
locate_windowfunc((Node *) wfunc->args))));
|
||||
|
||||
/*
|
||||
* If the OVER clause just specifies a reference name, find that
|
||||
* WINDOW clause (which had better be present). Otherwise, try to
|
||||
* match all the properties of the OVER clause, and make a new entry
|
||||
* in the p_windowdefs list if no luck.
|
||||
*/
|
||||
Assert(!windef->name);
|
||||
if (windef->refname &&
|
||||
windef->partitionClause == NIL &&
|
||||
windef->orderClause == NIL)
|
||||
{
|
||||
Index winref = 0;
|
||||
ListCell *lc;
|
||||
|
||||
foreach(lc, pstate->p_windowdefs)
|
||||
{
|
||||
WindowDef *refwin = (WindowDef *) lfirst(lc);
|
||||
|
||||
winref++;
|
||||
if (refwin->name && strcmp(refwin->name, windef->refname) == 0)
|
||||
{
|
||||
wfunc->winref = winref;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lc == NULL) /* didn't find it? */
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("window \"%s\" does not exist", windef->refname),
|
||||
parser_errposition(pstate, windef->location)));
|
||||
}
|
||||
else
|
||||
{
|
||||
Index winref = 0;
|
||||
ListCell *lc;
|
||||
|
||||
foreach(lc, pstate->p_windowdefs)
|
||||
{
|
||||
WindowDef *refwin = (WindowDef *) lfirst(lc);
|
||||
|
||||
winref++;
|
||||
if (refwin->refname && windef->refname &&
|
||||
strcmp(refwin->name, windef->refname) == 0)
|
||||
/* matched on refname */ ;
|
||||
else if (!refwin->refname && !windef->refname)
|
||||
/* matched, no refname */ ;
|
||||
else
|
||||
continue;
|
||||
if (equal(refwin->partitionClause, windef->partitionClause) &&
|
||||
equal(refwin->orderClause, windef->orderClause))
|
||||
{
|
||||
/* found a duplicate window specification */
|
||||
wfunc->winref = winref;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lc == NULL) /* didn't find it? */
|
||||
{
|
||||
pstate->p_windowdefs = lappend(pstate->p_windowdefs, windef);
|
||||
wfunc->winref = list_length(pstate->p_windowdefs);
|
||||
}
|
||||
}
|
||||
|
||||
pstate->p_hasWindowFuncs = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* parseCheckAggregates
|
||||
@ -207,6 +309,11 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
|
||||
|
||||
/*
|
||||
* Check the targetlist and HAVING clause for ungrouped variables.
|
||||
*
|
||||
* Note: because we check resjunk tlist elements as well as regular ones,
|
||||
* this will also find ungrouped variables that came from ORDER BY and
|
||||
* WINDOW clauses. For that matter, it's also going to examine the
|
||||
* grouping expressions themselves --- but they'll all pass the test ...
|
||||
*/
|
||||
clause = (Node *) qry->targetList;
|
||||
if (hasJoinRTEs)
|
||||
@ -226,11 +333,94 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
|
||||
if (pstate->p_hasAggs && hasSelfRefRTEs)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_RECURSION),
|
||||
errmsg("aggregates not allowed in a recursive query's recursive term"),
|
||||
errmsg("aggregate functions not allowed in a recursive query's recursive term"),
|
||||
parser_errposition(pstate,
|
||||
locate_agg_of_level((Node *) qry, 0))));
|
||||
}
|
||||
|
||||
/*
|
||||
* parseCheckWindowFuncs
|
||||
* Check for window functions where they shouldn't be.
|
||||
*
|
||||
* We have to forbid window functions in WHERE, JOIN/ON, HAVING, GROUP BY,
|
||||
* and window specifications. (Other clauses, such as RETURNING and LIMIT,
|
||||
* have already been checked.) Transformation of all these clauses must
|
||||
* be completed already.
|
||||
*/
|
||||
void
|
||||
parseCheckWindowFuncs(ParseState *pstate, Query *qry)
|
||||
{
|
||||
ListCell *l;
|
||||
|
||||
/* This should only be called if we found window functions */
|
||||
Assert(pstate->p_hasWindowFuncs);
|
||||
|
||||
if (checkExprHasWindowFuncs(qry->jointree->quals))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("window functions not allowed in WHERE clause"),
|
||||
parser_errposition(pstate,
|
||||
locate_windowfunc(qry->jointree->quals))));
|
||||
if (checkExprHasWindowFuncs((Node *) qry->jointree->fromlist))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("window functions not allowed in JOIN conditions"),
|
||||
parser_errposition(pstate,
|
||||
locate_windowfunc((Node *) qry->jointree->fromlist))));
|
||||
if (checkExprHasWindowFuncs(qry->havingQual))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("window functions not allowed in HAVING clause"),
|
||||
parser_errposition(pstate,
|
||||
locate_windowfunc(qry->havingQual))));
|
||||
|
||||
foreach(l, qry->groupClause)
|
||||
{
|
||||
SortGroupClause *grpcl = (SortGroupClause *) lfirst(l);
|
||||
Node *expr;
|
||||
|
||||
expr = get_sortgroupclause_expr(grpcl, qry->targetList);
|
||||
if (checkExprHasWindowFuncs(expr))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("window functions not allowed in GROUP BY clause"),
|
||||
parser_errposition(pstate,
|
||||
locate_windowfunc(expr))));
|
||||
}
|
||||
|
||||
foreach(l, qry->windowClause)
|
||||
{
|
||||
WindowClause *wc = (WindowClause *) lfirst(l);
|
||||
ListCell *l2;
|
||||
|
||||
foreach(l2, wc->partitionClause)
|
||||
{
|
||||
SortGroupClause *grpcl = (SortGroupClause *) lfirst(l2);
|
||||
Node *expr;
|
||||
|
||||
expr = get_sortgroupclause_expr(grpcl, qry->targetList);
|
||||
if (checkExprHasWindowFuncs(expr))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("window functions not allowed in window definition"),
|
||||
parser_errposition(pstate,
|
||||
locate_windowfunc(expr))));
|
||||
}
|
||||
foreach(l2, wc->orderClause)
|
||||
{
|
||||
SortGroupClause *grpcl = (SortGroupClause *) lfirst(l2);
|
||||
Node *expr;
|
||||
|
||||
expr = get_sortgroupclause_expr(grpcl, qry->targetList);
|
||||
if (checkExprHasWindowFuncs(expr))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("window functions not allowed in window definition"),
|
||||
parser_errposition(pstate,
|
||||
locate_windowfunc(expr))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* check_ungrouped_columns -
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.181 2008/10/06 02:12:56 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.182 2008/12/28 18:53:58 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -40,8 +40,14 @@
|
||||
#define ORDER_CLAUSE 0
|
||||
#define GROUP_CLAUSE 1
|
||||
#define DISTINCT_ON_CLAUSE 2
|
||||
#define PARTITION_CLAUSE 3
|
||||
|
||||
static char *clauseText[] = {"ORDER BY", "GROUP BY", "DISTINCT ON"};
|
||||
static const char * const clauseText[] = {
|
||||
"ORDER BY",
|
||||
"GROUP BY",
|
||||
"DISTINCT ON",
|
||||
"PARTITION BY"
|
||||
};
|
||||
|
||||
static void extractRemainingColumns(List *common_colnames,
|
||||
List *src_colnames, List *src_colvars,
|
||||
@ -76,6 +82,7 @@ static List *addTargetToSortList(ParseState *pstate, TargetEntry *tle,
|
||||
static List *addTargetToGroupList(ParseState *pstate, TargetEntry *tle,
|
||||
List *grouplist, List *targetlist, int location,
|
||||
bool resolveUnknown);
|
||||
static WindowClause *findWindowClause(List *wclist, const char *name);
|
||||
|
||||
|
||||
/*
|
||||
@ -555,15 +562,20 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
|
||||
* Disallow aggregate functions in the expression. (No reason to postpone
|
||||
* this check until parseCheckAggregates.)
|
||||
*/
|
||||
if (pstate->p_hasAggs)
|
||||
{
|
||||
if (checkExprHasAggs(funcexpr))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_GROUPING_ERROR),
|
||||
errmsg("cannot use aggregate function in function expression in FROM"),
|
||||
parser_errposition(pstate,
|
||||
locate_agg_of_level(funcexpr, 0))));
|
||||
}
|
||||
if (pstate->p_hasAggs &&
|
||||
checkExprHasAggs(funcexpr))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_GROUPING_ERROR),
|
||||
errmsg("cannot use aggregate function in function expression in FROM"),
|
||||
parser_errposition(pstate,
|
||||
locate_agg_of_level(funcexpr, 0))));
|
||||
if (pstate->p_hasWindowFuncs &&
|
||||
checkExprHasWindowFuncs(funcexpr))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("cannot use window function in function expression in FROM"),
|
||||
parser_errposition(pstate,
|
||||
locate_windowfunc(funcexpr))));
|
||||
|
||||
/*
|
||||
* OK, build an RTE for the function.
|
||||
@ -1156,16 +1168,28 @@ transformLimitClause(ParseState *pstate, Node *clause,
|
||||
parser_errposition(pstate,
|
||||
locate_var_of_level(qual, 0))));
|
||||
}
|
||||
if (checkExprHasAggs(qual))
|
||||
if (pstate->p_hasAggs &&
|
||||
checkExprHasAggs(qual))
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_GROUPING_ERROR),
|
||||
/* translator: %s is name of a SQL construct, eg LIMIT */
|
||||
errmsg("argument of %s must not contain aggregates",
|
||||
errmsg("argument of %s must not contain aggregate functions",
|
||||
constructName),
|
||||
parser_errposition(pstate,
|
||||
locate_agg_of_level(qual, 0))));
|
||||
}
|
||||
if (pstate->p_hasWindowFuncs &&
|
||||
checkExprHasWindowFuncs(qual))
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
/* translator: %s is name of a SQL construct, eg LIMIT */
|
||||
errmsg("argument of %s must not contain window functions",
|
||||
constructName),
|
||||
parser_errposition(pstate,
|
||||
locate_windowfunc(qual))));
|
||||
}
|
||||
|
||||
return qual;
|
||||
}
|
||||
@ -1234,7 +1258,7 @@ findTargetlistEntry(ParseState *pstate, Node *node, List **tlist, int clause)
|
||||
char *name = strVal(linitial(((ColumnRef *) node)->fields));
|
||||
int location = ((ColumnRef *) node)->location;
|
||||
|
||||
if (clause == GROUP_CLAUSE)
|
||||
if (clause == GROUP_CLAUSE || clause == PARTITION_CLAUSE)
|
||||
{
|
||||
/*
|
||||
* In GROUP BY, we must prefer a match against a FROM-clause
|
||||
@ -1251,6 +1275,8 @@ findTargetlistEntry(ParseState *pstate, Node *node, List **tlist, int clause)
|
||||
* SQL99 do not allow GROUPing BY an outer reference, so this
|
||||
* breaks no cases that are legal per spec, and it seems a more
|
||||
* self-consistent behavior.
|
||||
*
|
||||
* Window PARTITION BY clauses should act exactly like GROUP BY.
|
||||
*/
|
||||
if (colNameToVar(pstate, name, true, location) != NULL)
|
||||
name = NULL;
|
||||
@ -1356,12 +1382,17 @@ findTargetlistEntry(ParseState *pstate, Node *node, List **tlist, int clause)
|
||||
*
|
||||
* GROUP BY items will be added to the targetlist (as resjunk columns)
|
||||
* if not already present, so the targetlist must be passed by reference.
|
||||
*
|
||||
* This is also used for window PARTITION BY clauses (which actually act
|
||||
* just the same, except for the clause name used in error messages).
|
||||
*/
|
||||
List *
|
||||
transformGroupClause(ParseState *pstate, List *grouplist,
|
||||
List **targetlist, List *sortClause)
|
||||
List **targetlist, List *sortClause,
|
||||
bool isPartition)
|
||||
{
|
||||
List *result = NIL;
|
||||
int clause = isPartition ? PARTITION_CLAUSE : GROUP_CLAUSE;
|
||||
ListCell *gl;
|
||||
|
||||
foreach(gl, grouplist)
|
||||
@ -1370,8 +1401,7 @@ transformGroupClause(ParseState *pstate, List *grouplist,
|
||||
TargetEntry *tle;
|
||||
bool found = false;
|
||||
|
||||
tle = findTargetlistEntry(pstate, gexpr,
|
||||
targetlist, GROUP_CLAUSE);
|
||||
tle = findTargetlistEntry(pstate, gexpr, targetlist, clause);
|
||||
|
||||
/* Eliminate duplicates (GROUP BY x, x) */
|
||||
if (targetIsInSortList(tle, InvalidOid, result))
|
||||
@ -1451,6 +1481,125 @@ transformSortClause(ParseState *pstate,
|
||||
return sortlist;
|
||||
}
|
||||
|
||||
/*
|
||||
* transformWindowDefinitions -
|
||||
* transform window definitions (WindowDef to WindowClause)
|
||||
*/
|
||||
List *
|
||||
transformWindowDefinitions(ParseState *pstate,
|
||||
List *windowdefs,
|
||||
List **targetlist)
|
||||
{
|
||||
List *result = NIL;
|
||||
Index winref = 0;
|
||||
ListCell *lc;
|
||||
|
||||
foreach(lc, windowdefs)
|
||||
{
|
||||
WindowDef *windef = (WindowDef *) lfirst(lc);
|
||||
WindowClause *refwc = NULL;
|
||||
List *partitionClause;
|
||||
List *orderClause;
|
||||
WindowClause *wc;
|
||||
|
||||
winref++;
|
||||
|
||||
/*
|
||||
* Check for duplicate window names.
|
||||
*/
|
||||
if (windef->name &&
|
||||
findWindowClause(result, windef->name) != NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("window \"%s\" is already defined", windef->name),
|
||||
parser_errposition(pstate, windef->location)));
|
||||
|
||||
/*
|
||||
* If it references a previous window, look that up.
|
||||
*/
|
||||
if (windef->refname)
|
||||
{
|
||||
refwc = findWindowClause(result, windef->refname);
|
||||
if (refwc == NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("window \"%s\" does not exist",
|
||||
windef->refname),
|
||||
parser_errposition(pstate, windef->location)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Transform PARTITION and ORDER specs, if any. These are treated
|
||||
* exactly like top-level GROUP BY and ORDER BY clauses, including
|
||||
* the special handling of nondefault operator semantics.
|
||||
*/
|
||||
orderClause = transformSortClause(pstate,
|
||||
windef->orderClause,
|
||||
targetlist,
|
||||
true);
|
||||
partitionClause = transformGroupClause(pstate,
|
||||
windef->partitionClause,
|
||||
targetlist,
|
||||
orderClause,
|
||||
true);
|
||||
|
||||
/*
|
||||
* And prepare the new WindowClause.
|
||||
*/
|
||||
wc = makeNode(WindowClause);
|
||||
wc->name = windef->name;
|
||||
wc->refname = windef->refname;
|
||||
|
||||
/*
|
||||
* Per spec, a windowdef that references a previous one copies the
|
||||
* previous partition clause (and mustn't specify its own). It can
|
||||
* specify its own ordering clause. but only if the previous one
|
||||
* had none.
|
||||
*/
|
||||
if (refwc)
|
||||
{
|
||||
if (partitionClause)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("cannot override PARTITION BY clause of window \"%s\"",
|
||||
windef->refname),
|
||||
parser_errposition(pstate, windef->location)));
|
||||
wc->partitionClause = copyObject(refwc->partitionClause);
|
||||
}
|
||||
else
|
||||
wc->partitionClause = partitionClause;
|
||||
if (refwc)
|
||||
{
|
||||
if (orderClause && refwc->orderClause)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("cannot override ORDER BY clause of window \"%s\"",
|
||||
windef->refname),
|
||||
parser_errposition(pstate, windef->location)));
|
||||
if (orderClause)
|
||||
{
|
||||
wc->orderClause = orderClause;
|
||||
wc->copiedOrder = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
wc->orderClause = copyObject(refwc->orderClause);
|
||||
wc->copiedOrder = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
wc->orderClause = orderClause;
|
||||
wc->copiedOrder = false;
|
||||
}
|
||||
wc->winref = winref;
|
||||
|
||||
result = lappend(result, wc);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* transformDistinctClause -
|
||||
* transform a DISTINCT clause
|
||||
@ -1919,3 +2068,23 @@ targetIsInSortList(TargetEntry *tle, Oid sortop, List *sortList)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* findWindowClause
|
||||
* Find the named WindowClause in the list, or return NULL if not there
|
||||
*/
|
||||
static WindowClause *
|
||||
findWindowClause(List *wclist, const char *name)
|
||||
{
|
||||
ListCell *l;
|
||||
|
||||
foreach(l, wclist)
|
||||
{
|
||||
WindowClause *wc = (WindowClause *) lfirst(l);
|
||||
|
||||
if (wc->name && strcmp(wc->name, name) == 0)
|
||||
return wc;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.172 2008/12/14 19:45:52 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.173 2008/12/28 18:53:58 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -746,6 +746,7 @@ build_coercion_expression(Node *node,
|
||||
/* Assert(targetTypeId == procstruct->prorettype); */
|
||||
Assert(!procstruct->proretset);
|
||||
Assert(!procstruct->proisagg);
|
||||
Assert(!procstruct->proiswindow);
|
||||
nargs = procstruct->pronargs;
|
||||
Assert(nargs >= 1 && nargs <= 3);
|
||||
/* Assert(procstruct->proargtypes.values[0] == exprType(node)); */
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.237 2008/10/26 02:46:25 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.238 2008/12/28 18:53:58 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -286,6 +286,7 @@ transformExpr(ParseState *pstate, Node *expr)
|
||||
case T_Const:
|
||||
case T_Param:
|
||||
case T_Aggref:
|
||||
case T_WindowFunc:
|
||||
case T_ArrayRef:
|
||||
case T_FuncExpr:
|
||||
case T_OpExpr:
|
||||
@ -361,7 +362,7 @@ transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
|
||||
list_make1(n),
|
||||
list_make1(result),
|
||||
false, false, false,
|
||||
true, -1);
|
||||
NULL, true, -1);
|
||||
}
|
||||
}
|
||||
/* process trailing subscripts, if any */
|
||||
@ -505,7 +506,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
|
||||
list_make1(makeString(name2)),
|
||||
list_make1(node),
|
||||
false, false, false,
|
||||
true, cref->location);
|
||||
NULL, true, cref->location);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -546,7 +547,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
|
||||
list_make1(makeString(name3)),
|
||||
list_make1(node),
|
||||
false, false, false,
|
||||
true, cref->location);
|
||||
NULL, true, cref->location);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -601,7 +602,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
|
||||
list_make1(makeString(name4)),
|
||||
list_make1(node),
|
||||
false, false, false,
|
||||
true, cref->location);
|
||||
NULL, true, cref->location);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -1108,6 +1109,7 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
|
||||
fn->agg_star,
|
||||
fn->agg_distinct,
|
||||
fn->func_variadic,
|
||||
fn->over,
|
||||
false,
|
||||
fn->location);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.209 2008/12/18 18:20:34 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.210 2008/12/28 18:53:58 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -63,7 +63,7 @@ static void unknown_attribute(ParseState *pstate, Node *relref, char *attname,
|
||||
Node *
|
||||
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
|
||||
bool agg_star, bool agg_distinct, bool func_variadic,
|
||||
bool is_column, int location)
|
||||
WindowDef *over, bool is_column, int location)
|
||||
{
|
||||
Oid rettype;
|
||||
Oid funcid;
|
||||
@ -131,8 +131,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
|
||||
* the "function call" could be a projection. We also check that there
|
||||
* wasn't any aggregate or variadic decoration.
|
||||
*/
|
||||
if (nargs == 1 && !agg_star && !agg_distinct && !func_variadic &&
|
||||
list_length(funcname) == 1)
|
||||
if (nargs == 1 && !agg_star && !agg_distinct && over == NULL &&
|
||||
!func_variadic && list_length(funcname) == 1)
|
||||
{
|
||||
Oid argtype = actual_arg_types[0];
|
||||
|
||||
@ -196,8 +196,15 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
|
||||
errmsg("DISTINCT specified, but %s is not an aggregate function",
|
||||
NameListToString(funcname)),
|
||||
parser_errposition(pstate, location)));
|
||||
if (over)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("OVER specified, but %s is not a window function nor an aggregate function",
|
||||
NameListToString(funcname)),
|
||||
parser_errposition(pstate, location)));
|
||||
}
|
||||
else if (fdresult != FUNCDETAIL_AGGREGATE)
|
||||
else if (!(fdresult == FUNCDETAIL_AGGREGATE ||
|
||||
fdresult == FUNCDETAIL_WINDOWFUNC))
|
||||
{
|
||||
/*
|
||||
* Oops. Time to die.
|
||||
@ -317,7 +324,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
|
||||
|
||||
retval = (Node *) funcexpr;
|
||||
}
|
||||
else
|
||||
else if (fdresult == FUNCDETAIL_AGGREGATE && !over)
|
||||
{
|
||||
/* aggregate function */
|
||||
Aggref *aggref = makeNode(Aggref);
|
||||
@ -340,16 +347,69 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
|
||||
NameListToString(funcname)),
|
||||
parser_errposition(pstate, location)));
|
||||
|
||||
/* parse_agg.c does additional aggregate-specific processing */
|
||||
transformAggregateCall(pstate, aggref);
|
||||
|
||||
retval = (Node *) aggref;
|
||||
|
||||
if (retset)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
||||
errmsg("aggregates cannot return sets"),
|
||||
parser_errposition(pstate, location)));
|
||||
|
||||
/* parse_agg.c does additional aggregate-specific processing */
|
||||
transformAggregateCall(pstate, aggref);
|
||||
|
||||
retval = (Node *) aggref;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* window function */
|
||||
WindowFunc *wfunc = makeNode(WindowFunc);
|
||||
|
||||
/*
|
||||
* True window functions must be called with a window definition.
|
||||
*/
|
||||
if (!over)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("window function call requires an OVER clause"),
|
||||
parser_errposition(pstate, location)));
|
||||
|
||||
wfunc->winfnoid = funcid;
|
||||
wfunc->wintype = rettype;
|
||||
wfunc->args = fargs;
|
||||
/* winref will be set by transformWindowFuncCall */
|
||||
wfunc->winstar = agg_star;
|
||||
wfunc->winagg = (fdresult == FUNCDETAIL_AGGREGATE);
|
||||
wfunc->location = location;
|
||||
|
||||
/*
|
||||
* agg_star is allowed for aggregate functions but distinct isn't
|
||||
*/
|
||||
if (agg_distinct)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("DISTINCT is not implemented for window functions"),
|
||||
parser_errposition(pstate, location)));
|
||||
|
||||
/*
|
||||
* Reject attempt to call a parameterless aggregate without (*)
|
||||
* syntax. This is mere pedantry but some folks insisted ...
|
||||
*/
|
||||
if (wfunc->winagg && fargs == NIL && !agg_star)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("%s(*) must be used to call a parameterless aggregate function",
|
||||
NameListToString(funcname)),
|
||||
parser_errposition(pstate, location)));
|
||||
|
||||
if (retset)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
||||
errmsg("window functions cannot return sets"),
|
||||
parser_errposition(pstate, location)));
|
||||
|
||||
/* parse_agg.c does additional window-func-specific processing */
|
||||
transformWindowFuncCall(pstate, wfunc, over);
|
||||
|
||||
retval = (Node *) wfunc;
|
||||
}
|
||||
|
||||
return retval;
|
||||
@ -948,7 +1008,12 @@ func_get_detail(List *funcname,
|
||||
else
|
||||
*argdefaults = NIL;
|
||||
}
|
||||
result = pform->proisagg ? FUNCDETAIL_AGGREGATE : FUNCDETAIL_NORMAL;
|
||||
if (pform->proisagg)
|
||||
result = FUNCDETAIL_AGGREGATE;
|
||||
else if (pform->proiswindow)
|
||||
result = FUNCDETAIL_WINDOWFUNC;
|
||||
else
|
||||
result = FUNCDETAIL_NORMAL;
|
||||
ReleaseSysCache(ftup);
|
||||
return result;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/parser/parse_type.c,v 1.100 2008/10/04 21:56:54 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/parser/parse_type.c,v 1.101 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -611,6 +611,7 @@ parseTypeString(const char *str, Oid *type_id, int32 *typmod_p)
|
||||
stmt->whereClause != NULL ||
|
||||
stmt->groupClause != NIL ||
|
||||
stmt->havingClause != NULL ||
|
||||
stmt->windowClause != NIL ||
|
||||
stmt->withClause != NULL ||
|
||||
stmt->valuesLists != NIL ||
|
||||
stmt->sortClause != NIL ||
|
||||
|
@ -19,7 +19,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.18 2008/12/06 23:22:46 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.19 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -391,6 +391,7 @@ transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt,
|
||||
funccallnode->agg_star = false;
|
||||
funccallnode->agg_distinct = false;
|
||||
funccallnode->func_variadic = false;
|
||||
funccallnode->over = NULL;
|
||||
funccallnode->location = -1;
|
||||
|
||||
constraint = makeNode(Constraint);
|
||||
@ -1471,6 +1472,10 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_GROUPING_ERROR),
|
||||
errmsg("cannot use aggregate function in rule WHERE condition")));
|
||||
if (pstate->p_hasWindowFuncs)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WINDOWING_ERROR),
|
||||
errmsg("cannot use window function in rule WHERE condition")));
|
||||
|
||||
/*
|
||||
* 'instead nothing' rules with a qualification need a query rangetable so
|
||||
|
@ -7,7 +7,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/rewrite/rewriteManip.c,v 1.118 2008/11/15 19:43:46 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/rewrite/rewriteManip.c,v 1.119 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -34,10 +34,18 @@ typedef struct
|
||||
int sublevels_up;
|
||||
} locate_agg_of_level_context;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int win_location;
|
||||
} locate_windowfunc_context;
|
||||
|
||||
static bool contain_aggs_of_level_walker(Node *node,
|
||||
contain_aggs_of_level_context *context);
|
||||
static bool locate_agg_of_level_walker(Node *node,
|
||||
locate_agg_of_level_context *context);
|
||||
static bool contain_windowfuncs_walker(Node *node, void *context);
|
||||
static bool locate_windowfunc_walker(Node *node,
|
||||
locate_windowfunc_context *context);
|
||||
static bool checkExprHasSubLink_walker(Node *node, void *context);
|
||||
static Relids offset_relid_set(Relids relids, int offset);
|
||||
static Relids adjust_relid_set(Relids relids, int oldrelid, int newrelid);
|
||||
@ -175,6 +183,87 @@ locate_agg_of_level_walker(Node *node,
|
||||
(void *) context);
|
||||
}
|
||||
|
||||
/*
|
||||
* checkExprHasWindowFuncs -
|
||||
* Check if an expression contains a window function call of the
|
||||
* current query level.
|
||||
*/
|
||||
bool
|
||||
checkExprHasWindowFuncs(Node *node)
|
||||
{
|
||||
/*
|
||||
* Must be prepared to start with a Query or a bare expression tree; if
|
||||
* it's a Query, we don't want to increment sublevels_up.
|
||||
*/
|
||||
return query_or_expression_tree_walker(node,
|
||||
contain_windowfuncs_walker,
|
||||
NULL,
|
||||
0);
|
||||
}
|
||||
|
||||
static bool
|
||||
contain_windowfuncs_walker(Node *node, void *context)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false;
|
||||
if (IsA(node, WindowFunc))
|
||||
return true; /* abort the tree traversal and return true */
|
||||
/* Mustn't recurse into subselects */
|
||||
return expression_tree_walker(node, contain_windowfuncs_walker,
|
||||
(void *) context);
|
||||
}
|
||||
|
||||
/*
|
||||
* locate_windowfunc -
|
||||
* Find the parse location of any windowfunc of the current query level.
|
||||
*
|
||||
* Returns -1 if no such windowfunc is in the querytree, or if they all have
|
||||
* unknown parse location. (The former case is probably caller error,
|
||||
* but we don't bother to distinguish it from the latter case.)
|
||||
*
|
||||
* Note: it might seem appropriate to merge this functionality into
|
||||
* contain_windowfuncs, but that would complicate that function's API.
|
||||
* Currently, the only uses of this function are for error reporting,
|
||||
* and so shaving cycles probably isn't very important.
|
||||
*/
|
||||
int
|
||||
locate_windowfunc(Node *node)
|
||||
{
|
||||
locate_windowfunc_context context;
|
||||
|
||||
context.win_location = -1; /* in case we find nothing */
|
||||
|
||||
/*
|
||||
* Must be prepared to start with a Query or a bare expression tree; if
|
||||
* it's a Query, we don't want to increment sublevels_up.
|
||||
*/
|
||||
(void) query_or_expression_tree_walker(node,
|
||||
locate_windowfunc_walker,
|
||||
(void *) &context,
|
||||
0);
|
||||
|
||||
return context.win_location;
|
||||
}
|
||||
|
||||
static bool
|
||||
locate_windowfunc_walker(Node *node, locate_windowfunc_context *context)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false;
|
||||
if (IsA(node, WindowFunc))
|
||||
{
|
||||
if (((WindowFunc *) node)->location >= 0)
|
||||
{
|
||||
context->win_location = ((WindowFunc *) node)->location;
|
||||
return true; /* abort the tree traversal and return true */
|
||||
}
|
||||
/* else fall through to examine argument */
|
||||
}
|
||||
/* Mustn't recurse into subselects */
|
||||
return expression_tree_walker(node, locate_windowfunc_walker,
|
||||
(void *) context);
|
||||
}
|
||||
|
||||
/*
|
||||
* checkExprHasSubLink -
|
||||
* Check if an expression contains a SubLink.
|
||||
@ -1023,6 +1112,7 @@ AddInvertedQual(Query *parsetree, Node *qual)
|
||||
* Messy, isn't it? We do not need to do similar pushups for hasAggs,
|
||||
* because it isn't possible for this transformation to insert a level-zero
|
||||
* aggregate reference into a subquery --- it could only insert outer aggs.
|
||||
* Likewise for hasWindowFuncs.
|
||||
*/
|
||||
|
||||
typedef struct
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# Makefile for utils/adt
|
||||
#
|
||||
# $PostgreSQL: pgsql/src/backend/utils/adt/Makefile,v 1.70 2008/11/03 20:17:20 adunstan Exp $
|
||||
# $PostgreSQL: pgsql/src/backend/utils/adt/Makefile,v 1.71 2008/12/28 18:53:59 tgl Exp $
|
||||
#
|
||||
|
||||
subdir = src/backend/utils/adt
|
||||
@ -29,7 +29,7 @@ OBJS = acl.o arrayfuncs.o array_userfuncs.o arrayutils.o bool.o \
|
||||
tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
|
||||
tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
|
||||
tsvector.o tsvector_op.o tsvector_parser.o \
|
||||
txid.o uuid.o xml.o
|
||||
txid.o uuid.o windowfuncs.o xml.o
|
||||
|
||||
like.o: like.c like_match.c
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
* Copyright (c) 2003-2008, PostgreSQL Global Development Group
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/array_userfuncs.c,v 1.26 2008/11/14 02:09:51 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/array_userfuncs.c,v 1.27 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -475,6 +475,7 @@ Datum
|
||||
array_agg_transfn(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Oid arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
|
||||
MemoryContext aggcontext;
|
||||
ArrayBuildState *state;
|
||||
Datum elem;
|
||||
|
||||
@ -483,8 +484,16 @@ array_agg_transfn(PG_FUNCTION_ARGS)
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("could not determine input data type")));
|
||||
|
||||
/* cannot be called directly because of internal-type argument */
|
||||
Assert(fcinfo->context && IsA(fcinfo->context, AggState));
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
aggcontext = ((AggState *) fcinfo->context)->aggcontext;
|
||||
else if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
|
||||
aggcontext = ((WindowAggState *) fcinfo->context)->wincontext;
|
||||
else
|
||||
{
|
||||
/* cannot be called directly because of internal-type argument */
|
||||
elog(ERROR, "array_agg_transfn called in non-aggregate context");
|
||||
aggcontext = NULL; /* keep compiler quiet */
|
||||
}
|
||||
|
||||
state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
|
||||
elem = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1);
|
||||
@ -492,7 +501,7 @@ array_agg_transfn(PG_FUNCTION_ARGS)
|
||||
elem,
|
||||
PG_ARGISNULL(1),
|
||||
arg1_typeid,
|
||||
((AggState *) fcinfo->context)->aggcontext);
|
||||
aggcontext);
|
||||
|
||||
/*
|
||||
* The transition type for array_agg() is declared to be "internal",
|
||||
@ -506,14 +515,28 @@ array_agg_transfn(PG_FUNCTION_ARGS)
|
||||
Datum
|
||||
array_agg_finalfn(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Datum result;
|
||||
ArrayBuildState *state;
|
||||
int dims[1];
|
||||
int lbs[1];
|
||||
|
||||
/* cannot be called directly because of internal-type argument */
|
||||
Assert(fcinfo->context && IsA(fcinfo->context, AggState));
|
||||
Assert(fcinfo->context &&
|
||||
(IsA(fcinfo->context, AggState) ||
|
||||
IsA(fcinfo->context, WindowAggState)));
|
||||
|
||||
if (PG_ARGISNULL(0))
|
||||
PG_RETURN_NULL(); /* returns null iff no input values */
|
||||
|
||||
state = (ArrayBuildState *) PG_GETARG_POINTER(0);
|
||||
PG_RETURN_ARRAYTYPE_P(makeArrayResult(state, CurrentMemoryContext));
|
||||
|
||||
dims[0] = state->nelems;
|
||||
lbs[0] = 1;
|
||||
|
||||
/* Release working state if regular aggregate, but not if window agg */
|
||||
result = makeMdArrayResult(state, 1, dims, lbs,
|
||||
CurrentMemoryContext,
|
||||
IsA(fcinfo->context, AggState));
|
||||
|
||||
PG_RETURN_DATUM(result);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/arrayfuncs.c,v 1.150 2008/11/14 00:51:46 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/arrayfuncs.c,v 1.151 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -4208,7 +4208,7 @@ makeArrayResult(ArrayBuildState *astate,
|
||||
dims[0] = astate->nelems;
|
||||
lbs[0] = 1;
|
||||
|
||||
return makeMdArrayResult(astate, 1, dims, lbs, rcontext);
|
||||
return makeMdArrayResult(astate, 1, dims, lbs, rcontext, true);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -4219,13 +4219,15 @@ makeArrayResult(ArrayBuildState *astate,
|
||||
*
|
||||
* astate is working state (not NULL)
|
||||
* rcontext is where to construct result
|
||||
* release is true if okay to release working state
|
||||
*/
|
||||
Datum
|
||||
makeMdArrayResult(ArrayBuildState *astate,
|
||||
int ndims,
|
||||
int *dims,
|
||||
int *lbs,
|
||||
MemoryContext rcontext)
|
||||
MemoryContext rcontext,
|
||||
bool release)
|
||||
{
|
||||
ArrayType *result;
|
||||
MemoryContext oldcontext;
|
||||
@ -4246,7 +4248,8 @@ makeMdArrayResult(ArrayBuildState *astate,
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
/* Clean up all the junk */
|
||||
MemoryContextDelete(astate->mcontext);
|
||||
if (release)
|
||||
MemoryContextDelete(astate->mcontext);
|
||||
|
||||
return PointerGetDatum(result);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/float.c,v 1.157 2008/05/09 21:31:23 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/float.c,v 1.158 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -1765,7 +1765,9 @@ float8_accum(PG_FUNCTION_ARGS)
|
||||
* parameter in-place to reduce palloc overhead. Otherwise we construct a
|
||||
* new array with the updated transition data and return it.
|
||||
*/
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
if (fcinfo->context &&
|
||||
(IsA(fcinfo->context, AggState) ||
|
||||
IsA(fcinfo->context, WindowAggState)))
|
||||
{
|
||||
transvalues[0] = N;
|
||||
transvalues[1] = sumX;
|
||||
@ -1818,7 +1820,9 @@ float4_accum(PG_FUNCTION_ARGS)
|
||||
* parameter in-place to reduce palloc overhead. Otherwise we construct a
|
||||
* new array with the updated transition data and return it.
|
||||
*/
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
if (fcinfo->context &&
|
||||
(IsA(fcinfo->context, AggState) ||
|
||||
IsA(fcinfo->context, WindowAggState)))
|
||||
{
|
||||
transvalues[0] = N;
|
||||
transvalues[1] = sumX;
|
||||
@ -2035,7 +2039,9 @@ float8_regr_accum(PG_FUNCTION_ARGS)
|
||||
* parameter in-place to reduce palloc overhead. Otherwise we construct a
|
||||
* new array with the updated transition data and return it.
|
||||
*/
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
if (fcinfo->context &&
|
||||
(IsA(fcinfo->context, AggState) ||
|
||||
IsA(fcinfo->context, WindowAggState)))
|
||||
{
|
||||
transvalues[0] = N;
|
||||
transvalues[1] = sumX;
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/int8.c,v 1.71 2008/10/05 23:18:37 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/int8.c,v 1.72 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -666,7 +666,9 @@ int8inc(PG_FUNCTION_ARGS)
|
||||
* as incorrect, so just ifdef it out.)
|
||||
*/
|
||||
#ifndef USE_FLOAT8_BYVAL /* controls int8 too */
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
if (fcinfo->context &&
|
||||
(IsA(fcinfo->context, AggState) ||
|
||||
IsA(fcinfo->context, WindowAggState)))
|
||||
{
|
||||
int64 *arg = (int64 *) PG_GETARG_POINTER(0);
|
||||
int64 result;
|
||||
|
@ -14,7 +14,7 @@
|
||||
* Copyright (c) 1998-2008, PostgreSQL Global Development Group
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/numeric.c,v 1.114 2008/05/09 21:31:23 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/numeric.c,v 1.115 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -2611,7 +2611,9 @@ int2_sum(PG_FUNCTION_ARGS)
|
||||
* as incorrect, so just ifdef it out.)
|
||||
*/
|
||||
#ifndef USE_FLOAT8_BYVAL /* controls int8 too */
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
if (fcinfo->context &&
|
||||
(IsA(fcinfo->context, AggState) ||
|
||||
IsA(fcinfo->context, WindowAggState)))
|
||||
{
|
||||
int64 *oldsum = (int64 *) PG_GETARG_POINTER(0);
|
||||
|
||||
@ -2660,7 +2662,9 @@ int4_sum(PG_FUNCTION_ARGS)
|
||||
* as incorrect, so just ifdef it out.)
|
||||
*/
|
||||
#ifndef USE_FLOAT8_BYVAL /* controls int8 too */
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
if (fcinfo->context &&
|
||||
(IsA(fcinfo->context, AggState) ||
|
||||
IsA(fcinfo->context, WindowAggState)))
|
||||
{
|
||||
int64 *oldsum = (int64 *) PG_GETARG_POINTER(0);
|
||||
|
||||
@ -2753,7 +2757,9 @@ int2_avg_accum(PG_FUNCTION_ARGS)
|
||||
* parameter in-place to reduce palloc overhead. Otherwise we need to make
|
||||
* a copy of it before scribbling on it.
|
||||
*/
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
if (fcinfo->context &&
|
||||
(IsA(fcinfo->context, AggState) ||
|
||||
IsA(fcinfo->context, WindowAggState)))
|
||||
transarray = PG_GETARG_ARRAYTYPE_P(0);
|
||||
else
|
||||
transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
|
||||
@ -2781,7 +2787,9 @@ int4_avg_accum(PG_FUNCTION_ARGS)
|
||||
* parameter in-place to reduce palloc overhead. Otherwise we need to make
|
||||
* a copy of it before scribbling on it.
|
||||
*/
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
if (fcinfo->context &&
|
||||
(IsA(fcinfo->context, AggState) ||
|
||||
IsA(fcinfo->context, WindowAggState)))
|
||||
transarray = PG_GETARG_ARRAYTYPE_P(0);
|
||||
else
|
||||
transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
|
||||
|
@ -9,7 +9,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.290 2008/12/19 05:04:35 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.291 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -81,6 +81,8 @@ typedef struct
|
||||
{
|
||||
StringInfo buf; /* output buffer to append to */
|
||||
List *namespaces; /* List of deparse_namespace nodes */
|
||||
List *windowClause; /* Current query level's WINDOW clause */
|
||||
List *windowTList; /* targetlist for resolving WINDOW clause */
|
||||
int prettyFlags; /* enabling of pretty-print functions */
|
||||
int indentLevel; /* current indent level for prettyprint */
|
||||
bool varprefix; /* TRUE to print prefixes on Vars */
|
||||
@ -167,6 +169,11 @@ static void get_setop_query(Node *setOp, Query *query,
|
||||
static Node *get_rule_sortgroupclause(SortGroupClause *srt, List *tlist,
|
||||
bool force_colno,
|
||||
deparse_context *context);
|
||||
static void get_rule_orderby(List *orderList, List *targetList,
|
||||
bool force_colno, deparse_context *context);
|
||||
static void get_rule_windowclause(Query *query, deparse_context *context);
|
||||
static void get_rule_windowspec(WindowClause *wc, List *targetList,
|
||||
deparse_context *context);
|
||||
static void push_plan(deparse_namespace *dpns, Plan *subplan);
|
||||
static char *get_variable(Var *var, int levelsup, bool showstar,
|
||||
deparse_context *context);
|
||||
@ -183,6 +190,7 @@ static void get_oper_expr(OpExpr *expr, deparse_context *context);
|
||||
static void get_func_expr(FuncExpr *expr, deparse_context *context,
|
||||
bool showimplicit);
|
||||
static void get_agg_expr(Aggref *aggref, deparse_context *context);
|
||||
static void get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context);
|
||||
static void get_coercion_expr(Node *arg, deparse_context *context,
|
||||
Oid resulttype, int32 resulttypmod,
|
||||
Node *parentNode);
|
||||
@ -1854,6 +1862,8 @@ deparse_expression_pretty(Node *expr, List *dpcontext,
|
||||
initStringInfo(&buf);
|
||||
context.buf = &buf;
|
||||
context.namespaces = dpcontext;
|
||||
context.windowClause = NIL;
|
||||
context.windowTList = NIL;
|
||||
context.varprefix = forceprefix;
|
||||
context.prettyFlags = prettyFlags;
|
||||
context.indentLevel = startIndent;
|
||||
@ -2085,6 +2095,8 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
|
||||
|
||||
context.buf = buf;
|
||||
context.namespaces = list_make1(&dpns);
|
||||
context.windowClause = NIL;
|
||||
context.windowTList = NIL;
|
||||
context.varprefix = (list_length(query->rtable) != 1);
|
||||
context.prettyFlags = prettyFlags;
|
||||
context.indentLevel = PRETTYINDENT_STD;
|
||||
@ -2228,6 +2240,8 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace,
|
||||
|
||||
context.buf = buf;
|
||||
context.namespaces = lcons(&dpns, list_copy(parentnamespace));
|
||||
context.windowClause = NIL;
|
||||
context.windowTList = NIL;
|
||||
context.varprefix = (parentnamespace != NIL ||
|
||||
list_length(query->rtable) != 1);
|
||||
context.prettyFlags = prettyFlags;
|
||||
@ -2392,13 +2406,20 @@ get_select_query_def(Query *query, deparse_context *context,
|
||||
TupleDesc resultDesc)
|
||||
{
|
||||
StringInfo buf = context->buf;
|
||||
List *save_windowclause;
|
||||
List *save_windowtlist;
|
||||
bool force_colno;
|
||||
const char *sep;
|
||||
ListCell *l;
|
||||
|
||||
/* Insert the WITH clause if given */
|
||||
get_with_clause(query, context);
|
||||
|
||||
/* Set up context for possible window functions */
|
||||
save_windowclause = context->windowClause;
|
||||
context->windowClause = query->windowClause;
|
||||
save_windowtlist = context->windowTList;
|
||||
context->windowTList = query->targetList;
|
||||
|
||||
/*
|
||||
* If the Query node has a setOperations tree, then it's the top level of
|
||||
* a UNION/INTERSECT/EXCEPT query; only the WITH, ORDER BY and LIMIT
|
||||
@ -2421,48 +2442,8 @@ get_select_query_def(Query *query, deparse_context *context,
|
||||
{
|
||||
appendContextKeyword(context, " ORDER BY ",
|
||||
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
||||
sep = "";
|
||||
foreach(l, query->sortClause)
|
||||
{
|
||||
SortGroupClause *srt = (SortGroupClause *) lfirst(l);
|
||||
Node *sortexpr;
|
||||
Oid sortcoltype;
|
||||
TypeCacheEntry *typentry;
|
||||
|
||||
appendStringInfoString(buf, sep);
|
||||
sortexpr = get_rule_sortgroupclause(srt, query->targetList,
|
||||
force_colno, context);
|
||||
sortcoltype = exprType(sortexpr);
|
||||
/* See whether operator is default < or > for datatype */
|
||||
typentry = lookup_type_cache(sortcoltype,
|
||||
TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
|
||||
if (srt->sortop == typentry->lt_opr)
|
||||
{
|
||||
/* ASC is default, so emit nothing for it */
|
||||
if (srt->nulls_first)
|
||||
appendStringInfo(buf, " NULLS FIRST");
|
||||
}
|
||||
else if (srt->sortop == typentry->gt_opr)
|
||||
{
|
||||
appendStringInfo(buf, " DESC");
|
||||
/* DESC defaults to NULLS FIRST */
|
||||
if (!srt->nulls_first)
|
||||
appendStringInfo(buf, " NULLS LAST");
|
||||
}
|
||||
else
|
||||
{
|
||||
appendStringInfo(buf, " USING %s",
|
||||
generate_operator_name(srt->sortop,
|
||||
sortcoltype,
|
||||
sortcoltype));
|
||||
/* be specific to eliminate ambiguity */
|
||||
if (srt->nulls_first)
|
||||
appendStringInfo(buf, " NULLS FIRST");
|
||||
else
|
||||
appendStringInfo(buf, " NULLS LAST");
|
||||
}
|
||||
sep = ", ";
|
||||
}
|
||||
get_rule_orderby(query->sortClause, query->targetList,
|
||||
force_colno, context);
|
||||
}
|
||||
|
||||
/* Add the LIMIT clause if given */
|
||||
@ -2500,6 +2481,9 @@ get_select_query_def(Query *query, deparse_context *context,
|
||||
if (rc->noWait)
|
||||
appendStringInfo(buf, " NOWAIT");
|
||||
}
|
||||
|
||||
context->windowClause = save_windowclause;
|
||||
context->windowTList = save_windowtlist;
|
||||
}
|
||||
|
||||
static void
|
||||
@ -2603,6 +2587,10 @@ get_basic_select_query(Query *query, deparse_context *context,
|
||||
-PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
|
||||
get_rule_expr(query->havingQual, context, false);
|
||||
}
|
||||
|
||||
/* Add the WINDOW clause if needed */
|
||||
if (query->windowClause != NIL)
|
||||
get_rule_windowclause(query, context);
|
||||
}
|
||||
|
||||
/* ----------
|
||||
@ -2807,6 +2795,143 @@ get_rule_sortgroupclause(SortGroupClause *srt, List *tlist, bool force_colno,
|
||||
return expr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Display an ORDER BY list.
|
||||
*/
|
||||
static void
|
||||
get_rule_orderby(List *orderList, List *targetList,
|
||||
bool force_colno, deparse_context *context)
|
||||
{
|
||||
StringInfo buf = context->buf;
|
||||
const char *sep;
|
||||
ListCell *l;
|
||||
|
||||
sep = "";
|
||||
foreach(l, orderList)
|
||||
{
|
||||
SortGroupClause *srt = (SortGroupClause *) lfirst(l);
|
||||
Node *sortexpr;
|
||||
Oid sortcoltype;
|
||||
TypeCacheEntry *typentry;
|
||||
|
||||
appendStringInfoString(buf, sep);
|
||||
sortexpr = get_rule_sortgroupclause(srt, targetList,
|
||||
force_colno, context);
|
||||
sortcoltype = exprType(sortexpr);
|
||||
/* See whether operator is default < or > for datatype */
|
||||
typentry = lookup_type_cache(sortcoltype,
|
||||
TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
|
||||
if (srt->sortop == typentry->lt_opr)
|
||||
{
|
||||
/* ASC is default, so emit nothing for it */
|
||||
if (srt->nulls_first)
|
||||
appendStringInfo(buf, " NULLS FIRST");
|
||||
}
|
||||
else if (srt->sortop == typentry->gt_opr)
|
||||
{
|
||||
appendStringInfo(buf, " DESC");
|
||||
/* DESC defaults to NULLS FIRST */
|
||||
if (!srt->nulls_first)
|
||||
appendStringInfo(buf, " NULLS LAST");
|
||||
}
|
||||
else
|
||||
{
|
||||
appendStringInfo(buf, " USING %s",
|
||||
generate_operator_name(srt->sortop,
|
||||
sortcoltype,
|
||||
sortcoltype));
|
||||
/* be specific to eliminate ambiguity */
|
||||
if (srt->nulls_first)
|
||||
appendStringInfo(buf, " NULLS FIRST");
|
||||
else
|
||||
appendStringInfo(buf, " NULLS LAST");
|
||||
}
|
||||
sep = ", ";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Display a WINDOW clause.
|
||||
*
|
||||
* Note that the windowClause list might contain only anonymous window
|
||||
* specifications, in which case we should print nothing here.
|
||||
*/
|
||||
static void
|
||||
get_rule_windowclause(Query *query, deparse_context *context)
|
||||
{
|
||||
StringInfo buf = context->buf;
|
||||
const char *sep;
|
||||
ListCell *l;
|
||||
|
||||
sep = NULL;
|
||||
foreach(l, query->windowClause)
|
||||
{
|
||||
WindowClause *wc = (WindowClause *) lfirst(l);
|
||||
|
||||
if (wc->name == NULL)
|
||||
continue; /* ignore anonymous windows */
|
||||
|
||||
if (sep == NULL)
|
||||
appendContextKeyword(context, " WINDOW ",
|
||||
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
||||
else
|
||||
appendStringInfoString(buf, sep);
|
||||
|
||||
appendStringInfo(buf, "%s AS ", quote_identifier(wc->name));
|
||||
|
||||
get_rule_windowspec(wc, query->targetList, context);
|
||||
|
||||
sep = ", ";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Display a window definition
|
||||
*/
|
||||
static void
|
||||
get_rule_windowspec(WindowClause *wc, List *targetList,
|
||||
deparse_context *context)
|
||||
{
|
||||
StringInfo buf = context->buf;
|
||||
bool needspace = false;
|
||||
const char *sep;
|
||||
ListCell *l;
|
||||
|
||||
appendStringInfoChar(buf, '(');
|
||||
if (wc->refname)
|
||||
{
|
||||
appendStringInfoString(buf, quote_identifier(wc->refname));
|
||||
needspace = true;
|
||||
}
|
||||
/* partitions are always inherited, so only print if no refname */
|
||||
if (wc->partitionClause && !wc->refname)
|
||||
{
|
||||
if (needspace)
|
||||
appendStringInfoChar(buf, ' ');
|
||||
appendStringInfoString(buf, "PARTITION BY ");
|
||||
sep = "";
|
||||
foreach(l, wc->partitionClause)
|
||||
{
|
||||
SortGroupClause *grp = (SortGroupClause *) lfirst(l);
|
||||
|
||||
appendStringInfoString(buf, sep);
|
||||
get_rule_sortgroupclause(grp, targetList,
|
||||
false, context);
|
||||
sep = ", ";
|
||||
}
|
||||
needspace = true;
|
||||
}
|
||||
if (wc->orderClause && !wc->copiedOrder)
|
||||
{
|
||||
if (needspace)
|
||||
appendStringInfoChar(buf, ' ');
|
||||
appendStringInfoString(buf, "ORDER BY ");
|
||||
get_rule_orderby(wc->orderClause, targetList, false, context);
|
||||
needspace = true;
|
||||
}
|
||||
appendStringInfoChar(buf, ')');
|
||||
}
|
||||
|
||||
/* ----------
|
||||
* get_insert_query_def - Parse back an INSERT parsetree
|
||||
* ----------
|
||||
@ -3801,6 +3926,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
|
||||
case T_XmlExpr:
|
||||
case T_NullIfExpr:
|
||||
case T_Aggref:
|
||||
case T_WindowFunc:
|
||||
case T_FuncExpr:
|
||||
/* function-like: name(..) or name[..] */
|
||||
return true;
|
||||
@ -3916,6 +4042,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
|
||||
case T_XmlExpr: /* own parentheses */
|
||||
case T_NullIfExpr: /* other separators */
|
||||
case T_Aggref: /* own parentheses */
|
||||
case T_WindowFunc: /* own parentheses */
|
||||
case T_CaseExpr: /* other separators */
|
||||
return true;
|
||||
default:
|
||||
@ -3965,6 +4092,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
|
||||
case T_XmlExpr: /* own parentheses */
|
||||
case T_NullIfExpr: /* other separators */
|
||||
case T_Aggref: /* own parentheses */
|
||||
case T_WindowFunc: /* own parentheses */
|
||||
case T_CaseExpr: /* other separators */
|
||||
return true;
|
||||
default:
|
||||
@ -4093,6 +4221,10 @@ get_rule_expr(Node *node, deparse_context *context,
|
||||
get_agg_expr((Aggref *) node, context);
|
||||
break;
|
||||
|
||||
case T_WindowFunc:
|
||||
get_windowfunc_expr((WindowFunc *) node, context);
|
||||
break;
|
||||
|
||||
case T_ArrayRef:
|
||||
{
|
||||
ArrayRef *aref = (ArrayRef *) node;
|
||||
@ -4999,13 +5131,13 @@ get_func_expr(FuncExpr *expr, deparse_context *context,
|
||||
* Normal function: display as proname(args). First we need to extract
|
||||
* the argument datatypes.
|
||||
*/
|
||||
if (list_length(expr->args) > FUNC_MAX_ARGS)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
|
||||
errmsg("too many arguments")));
|
||||
nargs = 0;
|
||||
foreach(l, expr->args)
|
||||
{
|
||||
if (nargs >= FUNC_MAX_ARGS)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
|
||||
errmsg("too many arguments")));
|
||||
argtypes[nargs] = exprType((Node *) lfirst(l));
|
||||
nargs++;
|
||||
}
|
||||
@ -5036,13 +5168,13 @@ get_agg_expr(Aggref *aggref, deparse_context *context)
|
||||
int nargs;
|
||||
ListCell *l;
|
||||
|
||||
if (list_length(aggref->args) > FUNC_MAX_ARGS)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
|
||||
errmsg("too many arguments")));
|
||||
nargs = 0;
|
||||
foreach(l, aggref->args)
|
||||
{
|
||||
if (nargs >= FUNC_MAX_ARGS)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
|
||||
errmsg("too many arguments")));
|
||||
argtypes[nargs] = exprType((Node *) lfirst(l));
|
||||
nargs++;
|
||||
}
|
||||
@ -5059,6 +5191,64 @@ get_agg_expr(Aggref *aggref, deparse_context *context)
|
||||
appendStringInfoChar(buf, ')');
|
||||
}
|
||||
|
||||
/*
|
||||
* get_windowfunc_expr - Parse back a WindowFunc node
|
||||
*/
|
||||
static void
|
||||
get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
|
||||
{
|
||||
StringInfo buf = context->buf;
|
||||
Oid argtypes[FUNC_MAX_ARGS];
|
||||
int nargs;
|
||||
ListCell *l;
|
||||
|
||||
if (list_length(wfunc->args) > FUNC_MAX_ARGS)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
|
||||
errmsg("too many arguments")));
|
||||
nargs = 0;
|
||||
foreach(l, wfunc->args)
|
||||
{
|
||||
argtypes[nargs] = exprType((Node *) lfirst(l));
|
||||
nargs++;
|
||||
}
|
||||
|
||||
appendStringInfo(buf, "%s(%s",
|
||||
generate_function_name(wfunc->winfnoid,
|
||||
nargs, argtypes, NULL), "");
|
||||
/* winstar can be set only in zero-argument aggregates */
|
||||
if (wfunc->winstar)
|
||||
appendStringInfoChar(buf, '*');
|
||||
else
|
||||
get_rule_expr((Node *) wfunc->args, context, true);
|
||||
appendStringInfoString(buf, ") OVER ");
|
||||
|
||||
foreach(l, context->windowClause)
|
||||
{
|
||||
WindowClause *wc = (WindowClause *) lfirst(l);
|
||||
|
||||
if (wc->winref == wfunc->winref)
|
||||
{
|
||||
if (wc->name)
|
||||
appendStringInfoString(buf, quote_identifier(wc->name));
|
||||
else
|
||||
get_rule_windowspec(wc, context->windowTList, context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (l == NULL)
|
||||
{
|
||||
if (context->windowClause)
|
||||
elog(ERROR, "could not find window clause for winref %u",
|
||||
wfunc->winref);
|
||||
/*
|
||||
* In EXPLAIN, we don't have window context information available,
|
||||
* so we have to settle for this:
|
||||
*/
|
||||
appendStringInfoString(buf, "(?)");
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------
|
||||
* get_coercion_expr
|
||||
*
|
||||
@ -6089,7 +6279,9 @@ generate_function_name(Oid funcid, int nargs, Oid *argtypes,
|
||||
NIL, nargs, argtypes, false, true,
|
||||
&p_funcid, &p_rettype,
|
||||
&p_retset, &p_nvargs, &p_true_typeids, NULL);
|
||||
if ((p_result == FUNCDETAIL_NORMAL || p_result == FUNCDETAIL_AGGREGATE) &&
|
||||
if ((p_result == FUNCDETAIL_NORMAL ||
|
||||
p_result == FUNCDETAIL_AGGREGATE ||
|
||||
p_result == FUNCDETAIL_WINDOWFUNC) &&
|
||||
p_funcid == funcid)
|
||||
nspname = NULL;
|
||||
else
|
||||
|
475
src/backend/utils/adt/windowfuncs.c
Normal file
475
src/backend/utils/adt/windowfuncs.c
Normal file
@ -0,0 +1,475 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* windowfuncs.c
|
||||
* Standard window functions defined in SQL spec.
|
||||
*
|
||||
* Portions Copyright (c) 2000-2008, PostgreSQL Global Development Group
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/windowfuncs.c,v 1.1 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#include "postgres.h"
|
||||
|
||||
#include "utils/builtins.h"
|
||||
#include "windowapi.h"
|
||||
|
||||
/*
|
||||
* ranking process information
|
||||
*/
|
||||
typedef struct rank_context
|
||||
{
|
||||
int64 rank; /* current rank */
|
||||
} rank_context;
|
||||
|
||||
/*
|
||||
* ntile process information
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
int32 ntile; /* current result */
|
||||
int64 rows_per_bucket; /* row number of current bucket */
|
||||
int64 boundary; /* how many rows should be in the bucket */
|
||||
int64 remainder; /* (total rows) % (bucket num) */
|
||||
} ntile_context;
|
||||
|
||||
static bool rank_up(WindowObject winobj);
|
||||
static Datum leadlag_common(FunctionCallInfo fcinfo,
|
||||
bool forward, bool withoffset, bool withdefault);
|
||||
|
||||
|
||||
/*
|
||||
* utility routine for *_rank functions.
|
||||
*/
|
||||
static bool
|
||||
rank_up(WindowObject winobj)
|
||||
{
|
||||
bool up = false; /* should rank increase? */
|
||||
int64 curpos = WinGetCurrentPosition(winobj);
|
||||
rank_context *context;
|
||||
|
||||
context = (rank_context *)
|
||||
WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
|
||||
|
||||
if (context->rank == 0)
|
||||
{
|
||||
/* first call: rank of first row is always 1 */
|
||||
Assert(curpos == 0);
|
||||
context->rank = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert(curpos > 0);
|
||||
/* do current and prior tuples match by ORDER BY clause? */
|
||||
if (!WinRowsArePeers(winobj, curpos - 1, curpos))
|
||||
up = true;
|
||||
}
|
||||
|
||||
/* We can advance the mark, but only *after* acccess to prior row */
|
||||
WinSetMarkPosition(winobj, curpos);
|
||||
|
||||
return up;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* row_number
|
||||
* just increment up from 1 until current partition finishes.
|
||||
*/
|
||||
Datum
|
||||
window_row_number(PG_FUNCTION_ARGS)
|
||||
{
|
||||
WindowObject winobj = PG_WINDOW_OBJECT();
|
||||
int64 curpos = WinGetCurrentPosition(winobj);
|
||||
|
||||
WinSetMarkPosition(winobj, curpos);
|
||||
PG_RETURN_INT64(curpos + 1);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* rank
|
||||
* Rank changes when key columns change.
|
||||
* The new rank number is the current row number.
|
||||
*/
|
||||
Datum
|
||||
window_rank(PG_FUNCTION_ARGS)
|
||||
{
|
||||
WindowObject winobj = PG_WINDOW_OBJECT();
|
||||
rank_context *context;
|
||||
bool up;
|
||||
|
||||
up = rank_up(winobj);
|
||||
context = (rank_context *)
|
||||
WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
|
||||
if (up)
|
||||
context->rank = WinGetCurrentPosition(winobj) + 1;
|
||||
|
||||
PG_RETURN_INT64(context->rank);
|
||||
}
|
||||
|
||||
/*
|
||||
* dense_rank
|
||||
* Rank increases by 1 when key columns change.
|
||||
*/
|
||||
Datum
|
||||
window_dense_rank(PG_FUNCTION_ARGS)
|
||||
{
|
||||
WindowObject winobj = PG_WINDOW_OBJECT();
|
||||
rank_context *context;
|
||||
bool up;
|
||||
|
||||
up = rank_up(winobj);
|
||||
context = (rank_context *)
|
||||
WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
|
||||
if (up)
|
||||
context->rank++;
|
||||
|
||||
PG_RETURN_INT64(context->rank);
|
||||
}
|
||||
|
||||
/*
|
||||
* percent_rank
|
||||
* return fraction between 0 and 1 inclusive,
|
||||
* which is described as (RK - 1) / (NR - 1), where RK is the current row's
|
||||
* rank and NR is the total number of rows, per spec.
|
||||
*/
|
||||
Datum
|
||||
window_percent_rank(PG_FUNCTION_ARGS)
|
||||
{
|
||||
WindowObject winobj = PG_WINDOW_OBJECT();
|
||||
rank_context *context;
|
||||
bool up;
|
||||
int64 totalrows = WinGetPartitionRowCount(winobj);
|
||||
|
||||
Assert(totalrows > 0);
|
||||
|
||||
up = rank_up(winobj);
|
||||
context = (rank_context *)
|
||||
WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
|
||||
if (up)
|
||||
context->rank = WinGetCurrentPosition(winobj) + 1;
|
||||
|
||||
/* return zero if there's only one row, per spec */
|
||||
if (totalrows <= 1)
|
||||
PG_RETURN_FLOAT8(0.0);
|
||||
|
||||
PG_RETURN_FLOAT8((float8) (context->rank - 1) / (float8) (totalrows - 1));
|
||||
}
|
||||
|
||||
/*
|
||||
* cume_dist
|
||||
* return fraction betweeen 0 and 1 inclusive,
|
||||
* which is described as NP / NR, where NP is the number of rows preceding or
|
||||
* peers to the current row, and NR is the total number of rows, per spec.
|
||||
*/
|
||||
Datum
|
||||
window_cume_dist(PG_FUNCTION_ARGS)
|
||||
{
|
||||
WindowObject winobj = PG_WINDOW_OBJECT();
|
||||
rank_context *context;
|
||||
bool up;
|
||||
int64 totalrows = WinGetPartitionRowCount(winobj);
|
||||
|
||||
Assert(totalrows > 0);
|
||||
|
||||
up = rank_up(winobj);
|
||||
context = (rank_context *)
|
||||
WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
|
||||
if (up || context->rank == 1)
|
||||
{
|
||||
/*
|
||||
* The current row is not peer to prior row or is just the first,
|
||||
* so count up the number of rows that are peer to the current.
|
||||
*/
|
||||
int64 row;
|
||||
|
||||
context->rank = WinGetCurrentPosition(winobj) + 1;
|
||||
|
||||
/*
|
||||
* start from current + 1
|
||||
*/
|
||||
for (row = context->rank; row < totalrows; row++)
|
||||
{
|
||||
if (!WinRowsArePeers(winobj, row - 1, row))
|
||||
break;
|
||||
context->rank++;
|
||||
}
|
||||
}
|
||||
|
||||
PG_RETURN_FLOAT8((float8) context->rank / (float8) totalrows);
|
||||
}
|
||||
|
||||
/*
|
||||
* ntile
|
||||
* compute an exact numeric value with scale 0 (zero),
|
||||
* ranging from 1 (one) to n, per spec.
|
||||
*/
|
||||
Datum
|
||||
window_ntile(PG_FUNCTION_ARGS)
|
||||
{
|
||||
WindowObject winobj = PG_WINDOW_OBJECT();
|
||||
ntile_context *context;
|
||||
|
||||
context = (ntile_context *)
|
||||
WinGetPartitionLocalMemory(winobj, sizeof(ntile_context));
|
||||
|
||||
if (context->ntile == 0)
|
||||
{
|
||||
/* first call */
|
||||
int64 total;
|
||||
int32 nbuckets;
|
||||
bool isnull;
|
||||
|
||||
total = WinGetPartitionRowCount(winobj);
|
||||
nbuckets = DatumGetInt32(WinGetFuncArgCurrent(winobj, 0, &isnull));
|
||||
|
||||
/*
|
||||
* per spec:
|
||||
* If NT is the null value, then the result is the null value.
|
||||
*/
|
||||
if (isnull)
|
||||
PG_RETURN_NULL();
|
||||
|
||||
/*
|
||||
* per spec:
|
||||
* If NT is less than or equal to 0 (zero), then an exception
|
||||
* condition is raised.
|
||||
*/
|
||||
if (nbuckets <= 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTILE),
|
||||
errmsg("argument of ntile must be greater than zero")));
|
||||
|
||||
context->ntile = 1;
|
||||
context->rows_per_bucket = 0;
|
||||
context->boundary = total / nbuckets;
|
||||
if (context->boundary <= 0)
|
||||
context->boundary = 1;
|
||||
else
|
||||
{
|
||||
/*
|
||||
* If the total number is not divisible, add 1 row to
|
||||
* leading buckets.
|
||||
*/
|
||||
context->remainder = total % nbuckets;
|
||||
if (context->remainder != 0)
|
||||
context->boundary++;
|
||||
}
|
||||
}
|
||||
|
||||
context->rows_per_bucket++;
|
||||
if (context->boundary < context->rows_per_bucket)
|
||||
{
|
||||
/* ntile up */
|
||||
if (context->remainder != 0 && context->ntile == context->remainder)
|
||||
{
|
||||
context->remainder = 0;
|
||||
context->boundary -= 1;
|
||||
}
|
||||
context->ntile += 1;
|
||||
context->rows_per_bucket = 1;
|
||||
}
|
||||
|
||||
PG_RETURN_INT32(context->ntile);
|
||||
}
|
||||
|
||||
/*
|
||||
* leadlag_common
|
||||
* common operation of lead() and lag()
|
||||
* For lead() forward is true, whereas for lag() it is false.
|
||||
* withoffset indicates we have an offset second argument.
|
||||
* withdefault indicates we have a default third argument.
|
||||
*/
|
||||
static Datum
|
||||
leadlag_common(FunctionCallInfo fcinfo,
|
||||
bool forward, bool withoffset, bool withdefault)
|
||||
{
|
||||
WindowObject winobj = PG_WINDOW_OBJECT();
|
||||
int32 offset;
|
||||
bool const_offset;
|
||||
Datum result;
|
||||
bool isnull;
|
||||
bool isout;
|
||||
|
||||
if (withoffset)
|
||||
{
|
||||
offset = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull));
|
||||
if (isnull)
|
||||
PG_RETURN_NULL();
|
||||
const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
offset = 1;
|
||||
const_offset = true;
|
||||
}
|
||||
|
||||
result = WinGetFuncArgInPartition(winobj, 0,
|
||||
(forward ? offset : -offset),
|
||||
WINDOW_SEEK_CURRENT,
|
||||
const_offset,
|
||||
&isnull, &isout);
|
||||
|
||||
if (isout)
|
||||
{
|
||||
/*
|
||||
* target row is out of the partition; supply default value if
|
||||
* provided. otherwise it'll stay NULL
|
||||
*/
|
||||
if (withdefault)
|
||||
result = WinGetFuncArgCurrent(winobj, 2, &isnull);
|
||||
}
|
||||
|
||||
if (isnull)
|
||||
PG_RETURN_NULL();
|
||||
|
||||
PG_RETURN_DATUM(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* lag
|
||||
* returns the value of VE evaluated on a row that is 1
|
||||
* row before the current row within a partition,
|
||||
* per spec.
|
||||
*/
|
||||
Datum
|
||||
window_lag(PG_FUNCTION_ARGS)
|
||||
{
|
||||
return leadlag_common(fcinfo, false, false, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* lag_with_offset
|
||||
* returns the value of VE evelulated on a row that is OFFSET
|
||||
* rows before the current row within a partition,
|
||||
* per spec.
|
||||
*/
|
||||
Datum
|
||||
window_lag_with_offset(PG_FUNCTION_ARGS)
|
||||
{
|
||||
return leadlag_common(fcinfo, false, true, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* lag_with_offset_and_default
|
||||
* same as lag_with_offset but accepts default value
|
||||
* as its third argument.
|
||||
*/
|
||||
Datum
|
||||
window_lag_with_offset_and_default(PG_FUNCTION_ARGS)
|
||||
{
|
||||
return leadlag_common(fcinfo, false, true, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* lead
|
||||
* returns the value of VE evaluated on a row that is 1
|
||||
* row after the current row within a partition,
|
||||
* per spec.
|
||||
*/
|
||||
Datum
|
||||
window_lead(PG_FUNCTION_ARGS)
|
||||
{
|
||||
return leadlag_common(fcinfo, true, false, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* lead_with_offset
|
||||
* returns the value of VE evaluated on a row that is OFFSET
|
||||
* number of rows after the current row within a partition,
|
||||
* per spec.
|
||||
*/
|
||||
Datum
|
||||
window_lead_with_offset(PG_FUNCTION_ARGS)
|
||||
{
|
||||
return leadlag_common(fcinfo, true, true, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* lead_with_offset_and_default
|
||||
* same as lead_with_offset but accepts default value
|
||||
* as its third argument.
|
||||
*/
|
||||
Datum
|
||||
window_lead_with_offset_and_default(PG_FUNCTION_ARGS)
|
||||
{
|
||||
return leadlag_common(fcinfo, true, true, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* first_value
|
||||
* return the value of VE evaluated on the first row of the
|
||||
* window frame, per spec.
|
||||
*/
|
||||
Datum
|
||||
window_first_value(PG_FUNCTION_ARGS)
|
||||
{
|
||||
WindowObject winobj = PG_WINDOW_OBJECT();
|
||||
Datum result;
|
||||
bool isnull;
|
||||
|
||||
result = WinGetFuncArgInFrame(winobj, 0,
|
||||
0, WINDOW_SEEK_HEAD, true,
|
||||
&isnull, NULL);
|
||||
if (isnull)
|
||||
PG_RETURN_NULL();
|
||||
|
||||
PG_RETURN_DATUM(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* last_value
|
||||
* return the value of VE evaluated on the last row of the
|
||||
* window frame, per spec.
|
||||
*/
|
||||
Datum
|
||||
window_last_value(PG_FUNCTION_ARGS)
|
||||
{
|
||||
WindowObject winobj = PG_WINDOW_OBJECT();
|
||||
Datum result;
|
||||
bool isnull;
|
||||
|
||||
result = WinGetFuncArgInFrame(winobj, 0,
|
||||
0, WINDOW_SEEK_TAIL, true,
|
||||
&isnull, NULL);
|
||||
if (isnull)
|
||||
PG_RETURN_NULL();
|
||||
|
||||
PG_RETURN_DATUM(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* nth_value
|
||||
* return the value of VE evaluated on the n-th row from the first
|
||||
* row of the window frame, per spec.
|
||||
*/
|
||||
Datum
|
||||
window_nth_value(PG_FUNCTION_ARGS)
|
||||
{
|
||||
WindowObject winobj = PG_WINDOW_OBJECT();
|
||||
bool const_offset;
|
||||
Datum result;
|
||||
bool isnull;
|
||||
int32 nth;
|
||||
|
||||
nth = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull));
|
||||
if (isnull)
|
||||
PG_RETURN_NULL();
|
||||
const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
|
||||
|
||||
if (nth <= 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE),
|
||||
errmsg("argument of nth_value must be greater than zero")));
|
||||
|
||||
result = WinGetFuncArgInFrame(winobj, 0,
|
||||
nth - 1, WINDOW_SEEK_HEAD, const_offset,
|
||||
&isnull, NULL);
|
||||
if (isnull)
|
||||
PG_RETURN_NULL();
|
||||
|
||||
PG_RETURN_DATUM(result);
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.122 2008/08/25 22:42:34 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.123 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -2218,6 +2218,7 @@ pg_detoast_datum_packed(struct varlena * datum)
|
||||
*
|
||||
* These are needed by polymorphic functions, which accept multiple possible
|
||||
* input types and need help from the parser to know what they've got.
|
||||
* Also, some functions might be interested in whether a parameter is constant.
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
@ -2288,6 +2289,8 @@ get_call_expr_argtype(Node *expr, int argnum)
|
||||
args = list_make1(((ArrayCoerceExpr *) expr)->arg);
|
||||
else if (IsA(expr, NullIfExpr))
|
||||
args = ((NullIfExpr *) expr)->args;
|
||||
else if (IsA(expr, WindowFunc))
|
||||
args = ((WindowFunc *) expr)->args;
|
||||
else
|
||||
return InvalidOid;
|
||||
|
||||
@ -2310,3 +2313,73 @@ get_call_expr_argtype(Node *expr, int argnum)
|
||||
|
||||
return argtype;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find out whether a specific function argument is constant for the
|
||||
* duration of a query
|
||||
*
|
||||
* Returns false if information is not available
|
||||
*/
|
||||
bool
|
||||
get_fn_expr_arg_stable(FmgrInfo *flinfo, int argnum)
|
||||
{
|
||||
/*
|
||||
* can't return anything useful if we have no FmgrInfo or if its fn_expr
|
||||
* node has not been initialized
|
||||
*/
|
||||
if (!flinfo || !flinfo->fn_expr)
|
||||
return false;
|
||||
|
||||
return get_call_expr_arg_stable(flinfo->fn_expr, argnum);
|
||||
}
|
||||
|
||||
/*
|
||||
* Find out whether a specific function argument is constant for the
|
||||
* duration of a query, but working from the calling expression tree
|
||||
*
|
||||
* Returns false if information is not available
|
||||
*/
|
||||
bool
|
||||
get_call_expr_arg_stable(Node *expr, int argnum)
|
||||
{
|
||||
List *args;
|
||||
Node *arg;
|
||||
|
||||
if (expr == NULL)
|
||||
return false;
|
||||
|
||||
if (IsA(expr, FuncExpr))
|
||||
args = ((FuncExpr *) expr)->args;
|
||||
else if (IsA(expr, OpExpr))
|
||||
args = ((OpExpr *) expr)->args;
|
||||
else if (IsA(expr, DistinctExpr))
|
||||
args = ((DistinctExpr *) expr)->args;
|
||||
else if (IsA(expr, ScalarArrayOpExpr))
|
||||
args = ((ScalarArrayOpExpr *) expr)->args;
|
||||
else if (IsA(expr, ArrayCoerceExpr))
|
||||
args = list_make1(((ArrayCoerceExpr *) expr)->arg);
|
||||
else if (IsA(expr, NullIfExpr))
|
||||
args = ((NullIfExpr *) expr)->args;
|
||||
else if (IsA(expr, WindowFunc))
|
||||
args = ((WindowFunc *) expr)->args;
|
||||
else
|
||||
return false;
|
||||
|
||||
if (argnum < 0 || argnum >= list_length(args))
|
||||
return false;
|
||||
|
||||
arg = (Node *) list_nth(args, argnum);
|
||||
|
||||
/*
|
||||
* Either a true Const or an external Param will have a value that
|
||||
* doesn't change during the execution of the query. In future we
|
||||
* might want to consider other cases too, e.g. now().
|
||||
*/
|
||||
if (IsA(arg, Const))
|
||||
return true;
|
||||
if (IsA(arg, Param) &&
|
||||
((Param *) arg)->paramkind == PARAM_EXTERN)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -47,7 +47,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/sort/tuplestore.c,v 1.44 2008/12/27 17:39:00 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/sort/tuplestore.c,v 1.45 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -1148,6 +1148,19 @@ tuplestore_trim(Tuplestorestate *state)
|
||||
state->truncated = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* tuplestore_in_memory
|
||||
*
|
||||
* Returns true if the tuplestore has not spilled to disk.
|
||||
*
|
||||
* XXX exposing this is a violation of modularity ... should get rid of it.
|
||||
*/
|
||||
bool
|
||||
tuplestore_in_memory(Tuplestorestate *state)
|
||||
{
|
||||
return (state->status == TSS_INMEM);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Tape interface routines
|
||||
|
@ -37,7 +37,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.513 2008/12/19 18:25:19 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.514 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -53,6 +53,6 @@
|
||||
*/
|
||||
|
||||
/* yyyymmddN */
|
||||
#define CATALOG_VERSION_NO 200812192
|
||||
#define CATALOG_VERSION_NO 200812281
|
||||
|
||||
#endif
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.533 2008/12/19 18:25:19 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.534 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
* NOTES
|
||||
* The script catalog/genbki.sh reads this file and generates .bki
|
||||
@ -4635,6 +4635,38 @@ DESCR("record greater than or equal");
|
||||
DATA(insert OID = 2987 ( btrecordcmp PGNSP PGUID 12 1 0 0 f f f t f i 2 0 23 "2249 2249" _null_ _null_ _null_ _null_ btrecordcmp _null_ _null_ _null_ ));
|
||||
DESCR("btree less-equal-greater");
|
||||
|
||||
/* SQL-spec window functions */
|
||||
DATA(insert OID = 3100 ( row_number PGNSP PGUID 12 1 0 0 f t f f f i 0 0 20 "" _null_ _null_ _null_ _null_ window_row_number _null_ _null_ _null_ ));
|
||||
DESCR("row number within partition");
|
||||
DATA(insert OID = 3101 ( rank PGNSP PGUID 12 1 0 0 f t f f f i 0 0 20 "" _null_ _null_ _null_ _null_ window_rank _null_ _null_ _null_ ));
|
||||
DESCR("integer rank with gaps");
|
||||
DATA(insert OID = 3102 ( dense_rank PGNSP PGUID 12 1 0 0 f t f f f i 0 0 20 "" _null_ _null_ _null_ _null_ window_dense_rank _null_ _null_ _null_ ));
|
||||
DESCR("integer rank without gaps");
|
||||
DATA(insert OID = 3103 ( percent_rank PGNSP PGUID 12 1 0 0 f t f f f i 0 0 701 "" _null_ _null_ _null_ _null_ window_percent_rank _null_ _null_ _null_ ));
|
||||
DESCR("fractional rank within partition");
|
||||
DATA(insert OID = 3104 ( cume_dist PGNSP PGUID 12 1 0 0 f t f f f i 0 0 701 "" _null_ _null_ _null_ _null_ window_cume_dist _null_ _null_ _null_ ));
|
||||
DESCR("fractional row number within partition");
|
||||
DATA(insert OID = 3105 ( ntile PGNSP PGUID 12 1 0 0 f t f t f i 1 0 23 "23" _null_ _null_ _null_ _null_ window_ntile _null_ _null_ _null_ ));
|
||||
DESCR("split rows into N groups");
|
||||
DATA(insert OID = 3106 ( lag PGNSP PGUID 12 1 0 0 f t f t f i 1 0 2283 "2283" _null_ _null_ _null_ _null_ window_lag _null_ _null_ _null_ ));
|
||||
DESCR("fetch the preceding row value");
|
||||
DATA(insert OID = 3107 ( lag PGNSP PGUID 12 1 0 0 f t f t f i 2 0 2283 "2283 23" _null_ _null_ _null_ _null_ window_lag_with_offset _null_ _null_ _null_ ));
|
||||
DESCR("fetch the Nth preceding row value");
|
||||
DATA(insert OID = 3108 ( lag PGNSP PGUID 12 1 0 0 f t f t f i 3 0 2283 "2283 23 2283" _null_ _null_ _null_ _null_ window_lag_with_offset_and_default _null_ _null_ _null_ ));
|
||||
DESCR("fetch the Nth preceding row value with default");
|
||||
DATA(insert OID = 3109 ( lead PGNSP PGUID 12 1 0 0 f t f t f i 1 0 2283 "2283" _null_ _null_ _null_ _null_ window_lead _null_ _null_ _null_ ));
|
||||
DESCR("fetch the following row value");
|
||||
DATA(insert OID = 3110 ( lead PGNSP PGUID 12 1 0 0 f t f t f i 2 0 2283 "2283 23" _null_ _null_ _null_ _null_ window_lead_with_offset _null_ _null_ _null_ ));
|
||||
DESCR("fetch the Nth following row value");
|
||||
DATA(insert OID = 3111 ( lead PGNSP PGUID 12 1 0 0 f t f t f i 3 0 2283 "2283 23 2283" _null_ _null_ _null_ _null_ window_lead_with_offset_and_default _null_ _null_ _null_ ));
|
||||
DESCR("fetch the Nth following row value with default");
|
||||
DATA(insert OID = 3112 ( first_value PGNSP PGUID 12 1 0 0 f t f t f i 1 0 2283 "2283" _null_ _null_ _null_ _null_ window_first_value _null_ _null_ _null_ ));
|
||||
DESCR("fetch the first row value");
|
||||
DATA(insert OID = 3113 ( last_value PGNSP PGUID 12 1 0 0 f t f t f i 1 0 2283 "2283" _null_ _null_ _null_ _null_ window_last_value _null_ _null_ _null_ ));
|
||||
DESCR("fetch the last row value");
|
||||
DATA(insert OID = 3114 ( nth_value PGNSP PGUID 12 1 0 0 f t f t f i 2 0 2283 "2283 23" _null_ _null_ _null_ _null_ window_nth_value _null_ _null_ _null_ ));
|
||||
DESCR("fetch the Nth row value");
|
||||
|
||||
|
||||
/*
|
||||
* Symbolic values for provolatile column: these indicate whether the result
|
||||
|
25
src/include/executor/nodeWindowAgg.h
Normal file
25
src/include/executor/nodeWindowAgg.h
Normal file
@ -0,0 +1,25 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* nodeWindowAgg.h
|
||||
* prototypes for nodeWindowAgg.c
|
||||
*
|
||||
*
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/executor/nodeWindowAgg.h,v 1.1 2008/12/28 18:54:00 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#ifndef NODEWINDOWAGG_H
|
||||
#define NODEWINDOWAGG_H
|
||||
|
||||
#include "nodes/execnodes.h"
|
||||
|
||||
extern int ExecCountSlotsWindowAgg(WindowAgg *node);
|
||||
extern WindowAggState *ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags);
|
||||
extern TupleTableSlot *ExecWindowAgg(WindowAggState *node);
|
||||
extern void ExecEndWindowAgg(WindowAggState *node);
|
||||
extern void ExecReScanWindowAgg(WindowAggState *node, ExprContext *exprCtxt);
|
||||
|
||||
#endif /* NODEWINDOWAGG_H */
|
@ -11,7 +11,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/fmgr.h,v 1.60 2008/09/03 22:34:50 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/fmgr.h,v 1.61 2008/12/28 18:54:00 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -516,6 +516,8 @@ extern Oid fmgr_internal_function(const char *proname);
|
||||
extern Oid get_fn_expr_rettype(FmgrInfo *flinfo);
|
||||
extern Oid get_fn_expr_argtype(FmgrInfo *flinfo, int argnum);
|
||||
extern Oid get_call_expr_argtype(fmNodePtr expr, int argnum);
|
||||
extern bool get_fn_expr_arg_stable(FmgrInfo *flinfo, int argnum);
|
||||
extern bool get_call_expr_arg_stable(fmNodePtr expr, int argnum);
|
||||
|
||||
/*
|
||||
* Routines in dfmgr.c
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.196 2008/11/16 17:34:28 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.197 2008/12/28 18:54:00 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -119,9 +119,12 @@ typedef struct ExprContext
|
||||
ParamExecData *ecxt_param_exec_vals; /* for PARAM_EXEC params */
|
||||
ParamListInfo ecxt_param_list_info; /* for other param types */
|
||||
|
||||
/* Values to substitute for Aggref nodes in expression */
|
||||
Datum *ecxt_aggvalues; /* precomputed values for Aggref nodes */
|
||||
bool *ecxt_aggnulls; /* null flags for Aggref nodes */
|
||||
/*
|
||||
* Values to substitute for Aggref nodes in the expressions of an Agg node,
|
||||
* or for WindowFunc nodes within a WindowAgg node.
|
||||
*/
|
||||
Datum *ecxt_aggvalues; /* precomputed values for aggs/windowfuncs */
|
||||
bool *ecxt_aggnulls; /* null flags for aggs/windowfuncs */
|
||||
|
||||
/* Value to substitute for CaseTestExpr nodes in expression */
|
||||
Datum caseValue_datum;
|
||||
@ -511,6 +514,17 @@ typedef struct AggrefExprState
|
||||
int aggno; /* ID number for agg within its plan node */
|
||||
} AggrefExprState;
|
||||
|
||||
/* ----------------
|
||||
* WindowFuncExprState node
|
||||
* ----------------
|
||||
*/
|
||||
typedef struct WindowFuncExprState
|
||||
{
|
||||
ExprState xprstate;
|
||||
List *args; /* states of argument expressions */
|
||||
int wfuncno; /* ID number for wfunc within its plan node */
|
||||
} WindowFuncExprState;
|
||||
|
||||
/* ----------------
|
||||
* ArrayRefExprState node
|
||||
*
|
||||
@ -1482,6 +1496,53 @@ typedef struct AggState
|
||||
TupleHashIterator hashiter; /* for iterating through hash table */
|
||||
} AggState;
|
||||
|
||||
/* ----------------
|
||||
* WindowAggState information
|
||||
* ----------------
|
||||
*/
|
||||
/* these structs are private in nodeWindowAgg.c: */
|
||||
typedef struct WindowStatePerFuncData *WindowStatePerFunc;
|
||||
typedef struct WindowStatePerAggData *WindowStatePerAgg;
|
||||
|
||||
typedef struct WindowAggState
|
||||
{
|
||||
ScanState ss; /* its first field is NodeTag */
|
||||
|
||||
/* these fields are filled in by ExecInitExpr: */
|
||||
List *funcs; /* all WindowFunc nodes in targetlist */
|
||||
int numfuncs; /* total number of window functions */
|
||||
int numaggs; /* number that are plain aggregates */
|
||||
|
||||
WindowStatePerFunc perfunc; /* per-window-function information */
|
||||
WindowStatePerAgg peragg; /* per-plain-aggregate information */
|
||||
FmgrInfo *partEqfunctions; /* equality funcs for partition columns */
|
||||
FmgrInfo *ordEqfunctions; /* equality funcs for ordering columns */
|
||||
Tuplestorestate *buffer; /* stores rows of current partition */
|
||||
int current_ptr; /* read pointer # for current */
|
||||
int agg_ptr; /* read pointer # for aggregates */
|
||||
int64 spooled_rows; /* total # of rows in buffer */
|
||||
int64 currentpos; /* position of current row in partition */
|
||||
int64 frametailpos; /* current frame tail position */
|
||||
int64 aggregatedupto; /* rows before this one are aggregated */
|
||||
|
||||
MemoryContext wincontext; /* context for partition-lifespan data */
|
||||
ExprContext *tmpcontext; /* short-term evaluation context */
|
||||
|
||||
bool all_done; /* true if the scan is finished */
|
||||
bool partition_spooled; /* true if all tuples in current partition
|
||||
* have been spooled into tuplestore */
|
||||
bool more_partitions; /* true if there's more partitions after
|
||||
* this one */
|
||||
|
||||
TupleTableSlot *first_part_slot; /* first tuple of current or next
|
||||
* partition */
|
||||
|
||||
/* temporary slots for tuples fetched back from tuplestore */
|
||||
TupleTableSlot *first_peer_slot;
|
||||
TupleTableSlot *temp_slot_1;
|
||||
TupleTableSlot *temp_slot_2;
|
||||
} WindowAggState;
|
||||
|
||||
/* ----------------
|
||||
* UniqueState information
|
||||
*
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.216 2008/12/19 16:25:19 petere Exp $
|
||||
* $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.217 2008/12/28 18:54:00 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -66,6 +66,7 @@ typedef enum NodeTag
|
||||
T_Sort,
|
||||
T_Group,
|
||||
T_Agg,
|
||||
T_WindowAgg,
|
||||
T_Unique,
|
||||
T_Hash,
|
||||
T_SetOp,
|
||||
@ -103,6 +104,7 @@ typedef enum NodeTag
|
||||
T_SortState,
|
||||
T_GroupState,
|
||||
T_AggState,
|
||||
T_WindowAggState,
|
||||
T_UniqueState,
|
||||
T_HashState,
|
||||
T_SetOpState,
|
||||
@ -118,6 +120,7 @@ typedef enum NodeTag
|
||||
T_Const,
|
||||
T_Param,
|
||||
T_Aggref,
|
||||
T_WindowFunc,
|
||||
T_ArrayRef,
|
||||
T_FuncExpr,
|
||||
T_OpExpr,
|
||||
@ -164,6 +167,7 @@ typedef enum NodeTag
|
||||
T_ExprState = 400,
|
||||
T_GenericExprState,
|
||||
T_AggrefExprState,
|
||||
T_WindowFuncExprState,
|
||||
T_ArrayRefExprState,
|
||||
T_FuncExprState,
|
||||
T_ScalarArrayOpExprState,
|
||||
@ -350,6 +354,7 @@ typedef enum NodeTag
|
||||
T_ResTarget,
|
||||
T_TypeCast,
|
||||
T_SortBy,
|
||||
T_WindowDef,
|
||||
T_RangeSubselect,
|
||||
T_RangeFunction,
|
||||
T_TypeName,
|
||||
@ -360,6 +365,7 @@ typedef enum NodeTag
|
||||
T_OptionDefElem,
|
||||
T_RangeTblEntry,
|
||||
T_SortGroupClause,
|
||||
T_WindowClause,
|
||||
T_FkConstraint,
|
||||
T_PrivGrantee,
|
||||
T_FuncWithArgs,
|
||||
@ -383,6 +389,7 @@ typedef enum NodeTag
|
||||
*/
|
||||
T_TriggerData = 950, /* in commands/trigger.h */
|
||||
T_ReturnSetInfo, /* in nodes/execnodes.h */
|
||||
T_WindowObjectData, /* private in nodeWindowAgg.c */
|
||||
T_TIDBitmap /* in nodes/tidbitmap.h */
|
||||
} NodeTag;
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.384 2008/12/19 16:25:19 petere Exp $
|
||||
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.385 2008/12/28 18:54:00 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -120,6 +120,7 @@ typedef struct Query
|
||||
IntoClause *intoClause; /* target for SELECT INTO / CREATE TABLE AS */
|
||||
|
||||
bool hasAggs; /* has aggregates in tlist or havingQual */
|
||||
bool hasWindowFuncs; /* has window functions in tlist */
|
||||
bool hasSubLinks; /* has subquery SubLink */
|
||||
bool hasDistinctOn; /* distinctClause is from DISTINCT ON */
|
||||
bool hasRecursive; /* WITH RECURSIVE was specified */
|
||||
@ -137,6 +138,8 @@ typedef struct Query
|
||||
|
||||
Node *havingQual; /* qualifications applied to groups */
|
||||
|
||||
List *windowClause; /* a list of WindowClause's */
|
||||
|
||||
List *distinctClause; /* a list of SortGroupClause's */
|
||||
|
||||
List *sortClause; /* a list of SortGroupClause's */
|
||||
@ -269,7 +272,8 @@ typedef struct TypeCast
|
||||
* agg_star indicates we saw a 'foo(*)' construct, while agg_distinct
|
||||
* indicates we saw 'foo(DISTINCT ...)'. In either case, the construct
|
||||
* *must* be an aggregate call. Otherwise, it might be either an
|
||||
* aggregate or some other kind of function.
|
||||
* aggregate or some other kind of function. However, if OVER is present
|
||||
* it had better be an aggregate or window function.
|
||||
*/
|
||||
typedef struct FuncCall
|
||||
{
|
||||
@ -279,6 +283,7 @@ typedef struct FuncCall
|
||||
bool agg_star; /* argument was really '*' */
|
||||
bool agg_distinct; /* arguments were labeled DISTINCT */
|
||||
bool func_variadic; /* last argument was labeled VARIADIC */
|
||||
struct WindowDef *over; /* OVER clause, if any */
|
||||
int location; /* token location, or -1 if unknown */
|
||||
} FuncCall;
|
||||
|
||||
@ -375,6 +380,19 @@ typedef struct SortBy
|
||||
int location; /* operator location, or -1 if none/unknown */
|
||||
} SortBy;
|
||||
|
||||
/*
|
||||
* WindowDef - raw representation of WINDOW and OVER clauses
|
||||
*/
|
||||
typedef struct WindowDef
|
||||
{
|
||||
NodeTag type;
|
||||
char *name; /* window name (NULL in an OVER clause) */
|
||||
char *refname; /* referenced window name, if any */
|
||||
List *partitionClause; /* PARTITION BY expression list */
|
||||
List *orderClause; /* ORDER BY (list of SortBy) */
|
||||
int location; /* parse location, or -1 if none/unknown */
|
||||
} WindowDef;
|
||||
|
||||
/*
|
||||
* RangeSubselect - subquery appearing in a FROM clause
|
||||
*/
|
||||
@ -662,7 +680,8 @@ typedef struct RangeTblEntry
|
||||
|
||||
/*
|
||||
* SortGroupClause -
|
||||
* representation of ORDER BY, GROUP BY, DISTINCT, DISTINCT ON items
|
||||
* representation of ORDER BY, GROUP BY, PARTITION BY,
|
||||
* DISTINCT, DISTINCT ON items
|
||||
*
|
||||
* You might think that ORDER BY is only interested in defining ordering,
|
||||
* and GROUP/DISTINCT are only interested in defining equality. However,
|
||||
@ -714,6 +733,31 @@ typedef struct SortGroupClause
|
||||
bool nulls_first; /* do NULLs come before normal values? */
|
||||
} SortGroupClause;
|
||||
|
||||
/*
|
||||
* WindowClause -
|
||||
* transformed representation of WINDOW and OVER clauses
|
||||
*
|
||||
* A parsed Query's windowClause list contains these structs. "name" is set
|
||||
* if the clause originally came from WINDOW, and is NULL if it originally
|
||||
* was an OVER clause (but note that we collapse out duplicate OVERs).
|
||||
* partitionClause and orderClause are lists of SortGroupClause structs.
|
||||
* winref is an ID number referenced by WindowFunc nodes; it must be unique
|
||||
* among the members of a Query's windowClause list.
|
||||
* When refname isn't null, the partitionClause is always copied from there;
|
||||
* the orderClause might or might not be copied. (We don't implement
|
||||
* framing clauses yet, but if we did, they are never copied, per spec.)
|
||||
*/
|
||||
typedef struct WindowClause
|
||||
{
|
||||
NodeTag type;
|
||||
char *name; /* window name (NULL in an OVER clause) */
|
||||
char *refname; /* referenced window name, if any */
|
||||
List *partitionClause; /* PARTITION BY list */
|
||||
List *orderClause; /* ORDER BY list */
|
||||
Index winref; /* ID referenced by window functions */
|
||||
bool copiedOrder; /* did we copy orderClause from refname? */
|
||||
} WindowClause;
|
||||
|
||||
/*
|
||||
* RowMarkClause -
|
||||
* representation of FOR UPDATE/SHARE clauses
|
||||
@ -858,6 +902,7 @@ typedef struct SelectStmt
|
||||
Node *whereClause; /* WHERE qualification */
|
||||
List *groupClause; /* GROUP BY clauses */
|
||||
Node *havingClause; /* HAVING conditional-expression */
|
||||
List *windowClause; /* WINDOW window_name AS (...), ... */
|
||||
WithClause *withClause; /* WITH clause */
|
||||
|
||||
/*
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.105 2008/10/07 19:27:04 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.106 2008/12/28 18:54:00 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -536,6 +536,21 @@ typedef struct Agg
|
||||
long numGroups; /* estimated number of groups in input */
|
||||
} Agg;
|
||||
|
||||
/* ----------------
|
||||
* window aggregate node
|
||||
* ----------------
|
||||
*/
|
||||
typedef struct WindowAgg
|
||||
{
|
||||
Plan plan;
|
||||
int partNumCols; /* number of columns in partition clause */
|
||||
AttrNumber *partColIdx; /* their indexes in the target list */
|
||||
Oid *partOperators; /* equality operators for partition columns */
|
||||
int ordNumCols; /* number of columns in ordering clause */
|
||||
AttrNumber *ordColIdx; /* their indexes in the target list */
|
||||
Oid *ordOperators; /* equality operators for ordering columns */
|
||||
} WindowAgg;
|
||||
|
||||
/* ----------------
|
||||
* unique node
|
||||
* ----------------
|
||||
|
@ -10,7 +10,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.143 2008/10/06 17:39:26 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.144 2008/12/28 18:54:00 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -222,6 +222,21 @@ typedef struct Aggref
|
||||
int location; /* token location, or -1 if unknown */
|
||||
} Aggref;
|
||||
|
||||
/*
|
||||
* WindowFunc
|
||||
*/
|
||||
typedef struct WindowFunc
|
||||
{
|
||||
Expr xpr;
|
||||
Oid winfnoid; /* pg_proc Oid of the function */
|
||||
Oid wintype; /* type Oid of result of the window function */
|
||||
List *args; /* arguments to the window function */
|
||||
Index winref; /* index of associated WindowClause */
|
||||
bool winstar; /* TRUE if argument list was really '*' */
|
||||
bool winagg; /* is function a simple aggregate? */
|
||||
int location; /* token location, or -1 if unknown */
|
||||
} WindowFunc;
|
||||
|
||||
/* ----------------
|
||||
* ArrayRef: describes an array subscripting operation
|
||||
*
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.165 2008/12/01 21:06:13 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.166 2008/12/28 18:54:00 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -171,6 +171,7 @@ typedef struct PlannerInfo
|
||||
* actual pathkeys afterwards */
|
||||
|
||||
List *group_pathkeys; /* groupClause pathkeys, if any */
|
||||
List *window_pathkeys; /* pathkeys of bottom window, if any */
|
||||
List *distinct_pathkeys; /* distinctClause pathkeys, if any */
|
||||
List *sort_pathkeys; /* sortClause pathkeys, if any */
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/optimizer/clauses.h,v 1.95 2008/10/09 19:27:40 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/optimizer/clauses.h,v 1.96 2008/12/28 18:54:01 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -27,6 +27,13 @@ typedef struct
|
||||
Size transitionSpace; /* for pass-by-ref transition data */
|
||||
} AggClauseCounts;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int numWindowFuncs; /* total number of WindowFuncs found */
|
||||
Index maxWinRef; /* windowFuncs[] is indexed 0 .. maxWinRef */
|
||||
List **windowFuncs; /* lists of WindowFuncs for each winref */
|
||||
} WindowFuncLists;
|
||||
|
||||
|
||||
extern Expr *make_opclause(Oid opno, Oid opresulttype, bool opretset,
|
||||
Expr *leftop, Expr *rightop);
|
||||
@ -47,8 +54,12 @@ extern Expr *make_ands_explicit(List *andclauses);
|
||||
extern List *make_ands_implicit(Expr *clause);
|
||||
|
||||
extern bool contain_agg_clause(Node *clause);
|
||||
extern List *pull_agg_clause(Node *clause);
|
||||
extern void count_agg_clauses(Node *clause, AggClauseCounts *counts);
|
||||
|
||||
extern bool contain_window_function(Node *clause);
|
||||
extern WindowFuncLists *find_window_functions(Node *clause, Index maxWinRef);
|
||||
|
||||
extern double expression_returns_set_rows(Node *clause);
|
||||
|
||||
extern bool contain_subplans(Node *clause);
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/optimizer/cost.h,v 1.93 2008/10/04 21:56:55 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/optimizer/cost.h,v 1.94 2008/12/28 18:54:01 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -85,6 +85,10 @@ extern void cost_agg(Path *path, PlannerInfo *root,
|
||||
int numGroupCols, double numGroups,
|
||||
Cost input_startup_cost, Cost input_total_cost,
|
||||
double input_tuples);
|
||||
extern void cost_windowagg(Path *path, PlannerInfo *root,
|
||||
int numWindowFuncs, int numPartCols, int numOrderCols,
|
||||
Cost input_startup_cost, Cost input_total_cost,
|
||||
double input_tuples);
|
||||
extern void cost_group(Path *path, PlannerInfo *root,
|
||||
int numGroupCols, double numGroups,
|
||||
Cost input_startup_cost, Cost input_total_cost,
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.114 2008/10/07 19:27:04 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.115 2008/12/28 18:54:01 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -56,6 +56,11 @@ extern Agg *make_agg(PlannerInfo *root, List *tlist, List *qual,
|
||||
int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
|
||||
long numGroups, int numAggs,
|
||||
Plan *lefttree);
|
||||
extern WindowAgg *make_windowagg(PlannerInfo *root, List *tlist,
|
||||
int numWindowFuncs,
|
||||
int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
|
||||
int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators,
|
||||
Plan *lefttree);
|
||||
extern Group *make_group(PlannerInfo *root, List *tlist, List *qual,
|
||||
int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
|
||||
double numGroups,
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/optimizer/tlist.h,v 1.52 2008/08/07 19:35:02 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/optimizer/tlist.h,v 1.53 2008/12/28 18:54:01 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -21,7 +21,7 @@ extern TargetEntry *tlist_member(Node *node, List *targetlist);
|
||||
extern TargetEntry *tlist_member_ignore_relabel(Node *node, List *targetlist);
|
||||
|
||||
extern List *flatten_tlist(List *tlist);
|
||||
extern List *add_to_flat_tlist(List *tlist, List *vars);
|
||||
extern List *add_to_flat_tlist(List *tlist, List *exprs);
|
||||
|
||||
extern List *get_tlist_exprs(List *tlist, bool includeJunk);
|
||||
extern bool tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK);
|
||||
|
@ -1,12 +1,12 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* parse_agg.h
|
||||
* handle aggregates in parser
|
||||
* handle aggregates and window functions in parser
|
||||
*
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/parser/parse_agg.h,v 1.36 2008/01/01 19:45:58 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/include/parser/parse_agg.h,v 1.37 2008/12/28 18:54:01 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -16,8 +16,11 @@
|
||||
#include "parser/parse_node.h"
|
||||
|
||||
extern void transformAggregateCall(ParseState *pstate, Aggref *agg);
|
||||
extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
|
||||
WindowDef *windef);
|
||||
|
||||
extern void parseCheckAggregates(ParseState *pstate, Query *qry);
|
||||
extern void parseCheckWindowFuncs(ParseState *pstate, Query *qry);
|
||||
|
||||
extern void build_aggregate_fnexprs(Oid *agg_input_types,
|
||||
int agg_num_inputs,
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/parser/parse_clause.h,v 1.52 2008/08/07 01:11:52 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/parser/parse_clause.h,v 1.53 2008/12/28 18:54:01 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -27,9 +27,15 @@ extern Node *transformWhereClause(ParseState *pstate, Node *clause,
|
||||
extern Node *transformLimitClause(ParseState *pstate, Node *clause,
|
||||
const char *constructName);
|
||||
extern List *transformGroupClause(ParseState *pstate, List *grouplist,
|
||||
List **targetlist, List *sortClause);
|
||||
List **targetlist, List *sortClause,
|
||||
bool isPartition);
|
||||
extern List *transformSortClause(ParseState *pstate, List *orderlist,
|
||||
List **targetlist, bool resolveUnknown);
|
||||
|
||||
extern List *transformWindowDefinitions(ParseState *pstate,
|
||||
List *windowdefs,
|
||||
List **targetlist);
|
||||
|
||||
extern List *transformDistinctClause(ParseState *pstate,
|
||||
List **targetlist, List *sortClause);
|
||||
extern List *transformDistinctOnClause(ParseState *pstate, List *distinctlist,
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/parser/parse_func.h,v 1.62 2008/12/18 18:20:35 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/parser/parse_func.h,v 1.63 2008/12/28 18:54:01 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -37,6 +37,7 @@ typedef enum
|
||||
FUNCDETAIL_MULTIPLE, /* too many matching functions */
|
||||
FUNCDETAIL_NORMAL, /* found a matching regular function */
|
||||
FUNCDETAIL_AGGREGATE, /* found a matching aggregate function */
|
||||
FUNCDETAIL_WINDOWFUNC, /* found a matching window function */
|
||||
FUNCDETAIL_COERCION /* it's a type coercion request */
|
||||
} FuncDetailCode;
|
||||
|
||||
@ -44,7 +45,7 @@ typedef enum
|
||||
extern Node *ParseFuncOrColumn(ParseState *pstate,
|
||||
List *funcname, List *fargs,
|
||||
bool agg_star, bool agg_distinct, bool func_variadic,
|
||||
bool is_column, int location);
|
||||
WindowDef *over, bool is_column, int location);
|
||||
|
||||
extern FuncDetailCode func_get_detail(List *funcname, List *fargs,
|
||||
int nargs, Oid *argtypes,
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/parser/parse_node.h,v 1.58 2008/10/08 01:14:44 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/parser/parse_node.h,v 1.59 2008/12/28 18:54:01 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -57,6 +57,12 @@
|
||||
* p_future_ctes: list of CommonTableExprs (WITH items) that are not yet
|
||||
* visible due to scope rules. This is used to help improve error messages.
|
||||
*
|
||||
* p_windowdefs: list of WindowDefs representing WINDOW and OVER clauses.
|
||||
* We collect these while transforming expressions and then transform them
|
||||
* afterwards (so that any resjunk tlist items needed for the sort/group
|
||||
* clauses end up at the end of the query tlist). A WindowDef's location in
|
||||
* this list, counting from 1, is the winref number to use to reference it.
|
||||
*
|
||||
* p_paramtypes: an array of p_numparams type OIDs for $n parameter symbols
|
||||
* (zeroth entry in array corresponds to $1). If p_variableparams is true, the
|
||||
* set of param types is not predetermined; in that case, a zero array entry
|
||||
@ -77,6 +83,7 @@ typedef struct ParseState
|
||||
List *p_varnamespace; /* current namespace for columns */
|
||||
List *p_ctenamespace; /* current namespace for common table exprs */
|
||||
List *p_future_ctes; /* common table exprs not yet in namespace */
|
||||
List *p_windowdefs; /* raw representations of window clauses */
|
||||
Oid *p_paramtypes; /* OIDs of types for $n parameter symbols */
|
||||
int p_numparams; /* allocated size of p_paramtypes[] */
|
||||
int p_next_resno; /* next targetlist resno to assign */
|
||||
@ -84,6 +91,7 @@ typedef struct ParseState
|
||||
Node *p_value_substitute; /* what to replace VALUE with, if any */
|
||||
bool p_variableparams;
|
||||
bool p_hasAggs;
|
||||
bool p_hasWindowFuncs;
|
||||
bool p_hasSubLinks;
|
||||
bool p_is_insert;
|
||||
bool p_is_update;
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/rewrite/rewriteManip.h,v 1.47 2008/09/01 20:42:45 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/rewrite/rewriteManip.h,v 1.48 2008/12/28 18:54:01 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -37,7 +37,9 @@ extern void AddInvertedQual(Query *parsetree, Node *qual);
|
||||
|
||||
extern bool contain_aggs_of_level(Node *node, int levelsup);
|
||||
extern int locate_agg_of_level(Node *node, int levelsup);
|
||||
extern int locate_windowfunc(Node *node);
|
||||
extern bool checkExprHasAggs(Node *node);
|
||||
extern bool checkExprHasWindowFuncs(Node *node);
|
||||
extern bool checkExprHasSubLink(Node *node);
|
||||
|
||||
extern Node *ResolveNew(Node *node, int target_varno, int sublevels_up,
|
||||
|
@ -49,7 +49,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/utils/array.h,v 1.72 2008/11/14 00:51:47 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/utils/array.h,v 1.73 2008/12/28 18:54:01 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -250,7 +250,7 @@ extern ArrayBuildState *accumArrayResult(ArrayBuildState *astate,
|
||||
extern Datum makeArrayResult(ArrayBuildState *astate,
|
||||
MemoryContext rcontext);
|
||||
extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims,
|
||||
int *dims, int *lbs, MemoryContext rcontext);
|
||||
int *dims, int *lbs, MemoryContext rcontext, bool release);
|
||||
|
||||
/*
|
||||
* prototypes for functions defined in arrayutils.c
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.328 2008/12/19 16:25:19 petere Exp $
|
||||
* $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.329 2008/12/28 18:54:01 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -988,6 +988,23 @@ extern Datum uuid_ne(PG_FUNCTION_ARGS);
|
||||
extern Datum uuid_cmp(PG_FUNCTION_ARGS);
|
||||
extern Datum uuid_hash(PG_FUNCTION_ARGS);
|
||||
|
||||
/* windowfuncs.c */
|
||||
extern Datum window_row_number(PG_FUNCTION_ARGS);
|
||||
extern Datum window_rank(PG_FUNCTION_ARGS);
|
||||
extern Datum window_dense_rank(PG_FUNCTION_ARGS);
|
||||
extern Datum window_percent_rank(PG_FUNCTION_ARGS);
|
||||
extern Datum window_cume_dist(PG_FUNCTION_ARGS);
|
||||
extern Datum window_ntile(PG_FUNCTION_ARGS);
|
||||
extern Datum window_lag(PG_FUNCTION_ARGS);
|
||||
extern Datum window_lag_with_offset(PG_FUNCTION_ARGS);
|
||||
extern Datum window_lag_with_offset_and_default(PG_FUNCTION_ARGS);
|
||||
extern Datum window_lead(PG_FUNCTION_ARGS);
|
||||
extern Datum window_lead_with_offset(PG_FUNCTION_ARGS);
|
||||
extern Datum window_lead_with_offset_and_default(PG_FUNCTION_ARGS);
|
||||
extern Datum window_first_value(PG_FUNCTION_ARGS);
|
||||
extern Datum window_last_value(PG_FUNCTION_ARGS);
|
||||
extern Datum window_nth_value(PG_FUNCTION_ARGS);
|
||||
|
||||
/* access/transam/twophase.c */
|
||||
extern Datum pg_prepared_xact(PG_FUNCTION_ARGS);
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
*
|
||||
* Copyright (c) 2003-2008, PostgreSQL Global Development Group
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.26 2008/10/04 21:56:55 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.27 2008/12/28 18:54:01 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -122,6 +122,8 @@
|
||||
#define ERRCODE_INDICATOR_OVERFLOW MAKE_SQLSTATE('2','2', '0','2','2')
|
||||
#define ERRCODE_INTERVAL_FIELD_OVERFLOW MAKE_SQLSTATE('2','2', '0','1','5')
|
||||
#define ERRCODE_INVALID_ARGUMENT_FOR_LOG MAKE_SQLSTATE('2','2', '0','1','E')
|
||||
#define ERRCODE_INVALID_ARGUMENT_FOR_NTILE MAKE_SQLSTATE('2','2', '0','1','4')
|
||||
#define ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE MAKE_SQLSTATE('2','2', '0','1','6')
|
||||
#define ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION MAKE_SQLSTATE('2','2', '0', '1', 'F')
|
||||
#define ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION MAKE_SQLSTATE('2','2', '0', '1', 'G')
|
||||
#define ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST MAKE_SQLSTATE('2','2', '0','1','8')
|
||||
@ -246,6 +248,7 @@
|
||||
#define ERRCODE_INSUFFICIENT_PRIVILEGE MAKE_SQLSTATE('4','2', '5','0','1')
|
||||
#define ERRCODE_CANNOT_COERCE MAKE_SQLSTATE('4','2', '8','4','6')
|
||||
#define ERRCODE_GROUPING_ERROR MAKE_SQLSTATE('4','2', '8','0','3')
|
||||
#define ERRCODE_WINDOWING_ERROR MAKE_SQLSTATE('4','2', 'P','2','0')
|
||||
#define ERRCODE_INVALID_RECURSION MAKE_SQLSTATE('4','2', 'P','1','9')
|
||||
#define ERRCODE_INVALID_FOREIGN_KEY MAKE_SQLSTATE('4','2', '8','3','0')
|
||||
#define ERRCODE_INVALID_NAME MAKE_SQLSTATE('4','2', '6','0','2')
|
||||
|
@ -24,7 +24,7 @@
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/utils/tuplestore.h,v 1.26 2008/12/27 17:39:00 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/utils/tuplestore.h,v 1.27 2008/12/28 18:54:01 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -68,6 +68,8 @@ extern void tuplestore_copy_read_pointer(Tuplestorestate *state,
|
||||
|
||||
extern void tuplestore_trim(Tuplestorestate *state);
|
||||
|
||||
extern bool tuplestore_in_memory(Tuplestorestate *state);
|
||||
|
||||
extern bool tuplestore_gettupleslot(Tuplestorestate *state, bool forward,
|
||||
TupleTableSlot *slot);
|
||||
extern bool tuplestore_advance(Tuplestorestate *state, bool forward);
|
||||
|
64
src/include/windowapi.h
Normal file
64
src/include/windowapi.h
Normal file
@ -0,0 +1,64 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* windowapi.h
|
||||
* API for window functions to extract data from their window
|
||||
*
|
||||
* A window function does not receive its arguments in the normal way
|
||||
* (and therefore the concept of strictness is irrelevant). Instead it
|
||||
* receives a "WindowObject", which it can fetch with PG_WINDOW_OBJECT()
|
||||
* (note V1 calling convention must be used). Correct call context can
|
||||
* be tested with WindowObjectIsValid(). Although argument values are
|
||||
* not passed, the call is correctly set up so that PG_NARGS() can be
|
||||
* used and argument type information can be obtained with
|
||||
* get_fn_expr_argtype(), get_fn_expr_arg_stable(), etc.
|
||||
*
|
||||
* Operations on the WindowObject allow the window function to find out
|
||||
* the current row number, total number of rows in the partition, etc
|
||||
* and to evaluate its argument expression(s) at various rows in the
|
||||
* window partition. See the header comments for each WindowObject API
|
||||
* function in nodeWindowAgg.c for details.
|
||||
*
|
||||
*
|
||||
* Portions Copyright (c) 2000-2008, PostgreSQL Global Development Group
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/windowapi.h,v 1.1 2008/12/28 18:54:00 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#ifndef WINDOWAPI_H
|
||||
#define WINDOWAPI_H
|
||||
|
||||
/* values of "seektype" */
|
||||
#define WINDOW_SEEK_CURRENT 0
|
||||
#define WINDOW_SEEK_HEAD 1
|
||||
#define WINDOW_SEEK_TAIL 2
|
||||
|
||||
/* this struct is private in nodeWindowAgg.c */
|
||||
typedef struct WindowObjectData *WindowObject;
|
||||
|
||||
#define PG_WINDOW_OBJECT() ((WindowObject) fcinfo->context)
|
||||
|
||||
#define WindowObjectIsValid(winobj) \
|
||||
((winobj) != NULL && IsA(winobj, WindowObjectData))
|
||||
|
||||
extern void *WinGetPartitionLocalMemory(WindowObject winobj, Size sz);
|
||||
|
||||
extern int64 WinGetCurrentPosition(WindowObject winobj);
|
||||
extern int64 WinGetPartitionRowCount(WindowObject winobj);
|
||||
|
||||
extern void WinSetMarkPosition(WindowObject winobj, int64 markpos);
|
||||
|
||||
extern bool WinRowsArePeers(WindowObject winobj, int64 pos1, int64 pos2);
|
||||
|
||||
extern Datum WinGetFuncArgInPartition(WindowObject winobj, int argno,
|
||||
int relpos, int seektype, bool set_mark,
|
||||
bool *isnull, bool *isout);
|
||||
|
||||
extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
|
||||
int relpos, int seektype, bool set_mark,
|
||||
bool *isnull, bool *isout);
|
||||
|
||||
extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
|
||||
bool *isnull);
|
||||
|
||||
#endif /* WINDOWAPI_H */
|
@ -9,7 +9,7 @@
|
||||
*
|
||||
* Copyright (c) 2003-2008, PostgreSQL Global Development Group
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plerrcodes.h,v 1.15 2008/10/04 21:56:55 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plerrcodes.h,v 1.16 2008/12/28 18:54:01 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -135,6 +135,14 @@
|
||||
"invalid_argument_for_logarithm", ERRCODE_INVALID_ARGUMENT_FOR_LOG
|
||||
},
|
||||
|
||||
{
|
||||
"invalid_argument_for_ntile_function", ERRCODE_INVALID_ARGUMENT_FOR_NTILE
|
||||
},
|
||||
|
||||
{
|
||||
"invalid_argument_for_nth_value_function", ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE
|
||||
},
|
||||
|
||||
{
|
||||
"invalid_argument_for_power_function", ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION
|
||||
},
|
||||
@ -483,6 +491,10 @@
|
||||
"grouping_error", ERRCODE_GROUPING_ERROR
|
||||
},
|
||||
|
||||
{
|
||||
"windowing_error", ERRCODE_WINDOWING_ERROR
|
||||
},
|
||||
|
||||
{
|
||||
"invalid_recursion", ERRCODE_INVALID_RECURSION
|
||||
},
|
||||
|
672
src/test/regress/expected/window.out
Normal file
672
src/test/regress/expected/window.out
Normal file
@ -0,0 +1,672 @@
|
||||
--
|
||||
-- WINDOW FUNCTIONS
|
||||
--
|
||||
CREATE TEMPORARY TABLE empsalary (
|
||||
depname varchar,
|
||||
empno bigint,
|
||||
salary int,
|
||||
enroll_date date
|
||||
);
|
||||
INSERT INTO empsalary VALUES
|
||||
('develop', 10, 5200, '2007-08-01'),
|
||||
('sales', 1, 5000, '2006-10-01'),
|
||||
('personnel', 5, 3500, '2007-12-10'),
|
||||
('sales', 4, 4800, '2007-08-08'),
|
||||
('personnel', 2, 3900, '2006-12-23'),
|
||||
('develop', 7, 4200, '2008-01-01'),
|
||||
('develop', 9, 4500, '2008-01-01'),
|
||||
('sales', 3, 4800, '2007-08-01'),
|
||||
('develop', 8, 6000, '2006-10-01'),
|
||||
('develop', 11, 5200, '2007-08-15');
|
||||
SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
|
||||
depname | empno | salary | sum
|
||||
-----------+-------+--------+-------
|
||||
develop | 7 | 4200 | 25100
|
||||
develop | 9 | 4500 | 25100
|
||||
develop | 11 | 5200 | 25100
|
||||
develop | 10 | 5200 | 25100
|
||||
develop | 8 | 6000 | 25100
|
||||
personnel | 5 | 3500 | 7400
|
||||
personnel | 2 | 3900 | 7400
|
||||
sales | 3 | 4800 | 14600
|
||||
sales | 4 | 4800 | 14600
|
||||
sales | 1 | 5000 | 14600
|
||||
(10 rows)
|
||||
|
||||
SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary;
|
||||
depname | empno | salary | rank
|
||||
-----------+-------+--------+------
|
||||
develop | 7 | 4200 | 1
|
||||
develop | 9 | 4500 | 2
|
||||
develop | 11 | 5200 | 3
|
||||
develop | 10 | 5200 | 3
|
||||
develop | 8 | 6000 | 5
|
||||
personnel | 5 | 3500 | 1
|
||||
personnel | 2 | 3900 | 2
|
||||
sales | 3 | 4800 | 1
|
||||
sales | 4 | 4800 | 1
|
||||
sales | 1 | 5000 | 3
|
||||
(10 rows)
|
||||
|
||||
-- with GROUP BY
|
||||
SELECT four, ten, SUM(SUM(four)) OVER (PARTITION BY four), AVG(ten) FROM tenk1
|
||||
GROUP BY four, ten ORDER BY four, ten;
|
||||
four | ten | sum | avg
|
||||
------+-----+------+------------------------
|
||||
0 | 0 | 0 | 0.00000000000000000000
|
||||
0 | 2 | 0 | 2.0000000000000000
|
||||
0 | 4 | 0 | 4.0000000000000000
|
||||
0 | 6 | 0 | 6.0000000000000000
|
||||
0 | 8 | 0 | 8.0000000000000000
|
||||
1 | 1 | 2500 | 1.00000000000000000000
|
||||
1 | 3 | 2500 | 3.0000000000000000
|
||||
1 | 5 | 2500 | 5.0000000000000000
|
||||
1 | 7 | 2500 | 7.0000000000000000
|
||||
1 | 9 | 2500 | 9.0000000000000000
|
||||
2 | 0 | 5000 | 0.00000000000000000000
|
||||
2 | 2 | 5000 | 2.0000000000000000
|
||||
2 | 4 | 5000 | 4.0000000000000000
|
||||
2 | 6 | 5000 | 6.0000000000000000
|
||||
2 | 8 | 5000 | 8.0000000000000000
|
||||
3 | 1 | 7500 | 1.00000000000000000000
|
||||
3 | 3 | 7500 | 3.0000000000000000
|
||||
3 | 5 | 7500 | 5.0000000000000000
|
||||
3 | 7 | 7500 | 7.0000000000000000
|
||||
3 | 9 | 7500 | 9.0000000000000000
|
||||
(20 rows)
|
||||
|
||||
SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PARTITION BY depname);
|
||||
depname | empno | salary | sum
|
||||
-----------+-------+--------+-------
|
||||
develop | 11 | 5200 | 25100
|
||||
develop | 7 | 4200 | 25100
|
||||
develop | 9 | 4500 | 25100
|
||||
develop | 8 | 6000 | 25100
|
||||
develop | 10 | 5200 | 25100
|
||||
personnel | 5 | 3500 | 7400
|
||||
personnel | 2 | 3900 | 7400
|
||||
sales | 3 | 4800 | 14600
|
||||
sales | 1 | 5000 | 14600
|
||||
sales | 4 | 4800 | 14600
|
||||
(10 rows)
|
||||
|
||||
SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w;
|
||||
depname | empno | salary | rank
|
||||
-----------+-------+--------+------
|
||||
develop | 7 | 4200 | 1
|
||||
personnel | 5 | 3500 | 1
|
||||
sales | 3 | 4800 | 1
|
||||
sales | 4 | 4800 | 1
|
||||
personnel | 2 | 3900 | 2
|
||||
develop | 9 | 4500 | 2
|
||||
sales | 1 | 5000 | 3
|
||||
develop | 11 | 5200 | 3
|
||||
develop | 10 | 5200 | 3
|
||||
develop | 8 | 6000 | 5
|
||||
(10 rows)
|
||||
|
||||
-- empty window specification
|
||||
SELECT COUNT(*) OVER () FROM tenk1 WHERE unique2 < 10;
|
||||
count
|
||||
-------
|
||||
10
|
||||
10
|
||||
10
|
||||
10
|
||||
10
|
||||
10
|
||||
10
|
||||
10
|
||||
10
|
||||
10
|
||||
(10 rows)
|
||||
|
||||
SELECT COUNT(*) OVER w FROM tenk1 WHERE unique2 < 10 WINDOW w AS ();
|
||||
count
|
||||
-------
|
||||
10
|
||||
10
|
||||
10
|
||||
10
|
||||
10
|
||||
10
|
||||
10
|
||||
10
|
||||
10
|
||||
10
|
||||
(10 rows)
|
||||
|
||||
-- no window operation
|
||||
SELECT four FROM tenk1 WHERE FALSE WINDOW w AS (PARTITION BY ten);
|
||||
four
|
||||
------
|
||||
(0 rows)
|
||||
|
||||
-- cumulative aggregate
|
||||
SELECT sum(four) OVER (PARTITION BY ten ORDER BY unique2) AS sum_1, ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
sum_1 | ten | four
|
||||
-------+-----+------
|
||||
0 | 0 | 0
|
||||
0 | 0 | 0
|
||||
2 | 0 | 2
|
||||
3 | 1 | 3
|
||||
4 | 1 | 1
|
||||
5 | 1 | 1
|
||||
3 | 3 | 3
|
||||
0 | 4 | 0
|
||||
1 | 7 | 1
|
||||
1 | 9 | 1
|
||||
(10 rows)
|
||||
|
||||
SELECT row_number() OVER (ORDER BY unique2) FROM tenk1 WHERE unique2 < 10;
|
||||
row_number
|
||||
------------
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
(10 rows)
|
||||
|
||||
SELECT rank() OVER (PARTITION BY four ORDER BY ten) AS rank_1, ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
rank_1 | ten | four
|
||||
--------+-----+------
|
||||
1 | 0 | 0
|
||||
1 | 0 | 0
|
||||
3 | 4 | 0
|
||||
1 | 1 | 1
|
||||
1 | 1 | 1
|
||||
3 | 7 | 1
|
||||
4 | 9 | 1
|
||||
1 | 0 | 2
|
||||
1 | 1 | 3
|
||||
2 | 3 | 3
|
||||
(10 rows)
|
||||
|
||||
SELECT dense_rank() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
dense_rank | ten | four
|
||||
------------+-----+------
|
||||
1 | 0 | 0
|
||||
1 | 0 | 0
|
||||
2 | 4 | 0
|
||||
1 | 1 | 1
|
||||
1 | 1 | 1
|
||||
2 | 7 | 1
|
||||
3 | 9 | 1
|
||||
1 | 0 | 2
|
||||
1 | 1 | 3
|
||||
2 | 3 | 3
|
||||
(10 rows)
|
||||
|
||||
SELECT percent_rank() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
percent_rank | ten | four
|
||||
-------------------+-----+------
|
||||
0 | 0 | 0
|
||||
0 | 0 | 0
|
||||
1 | 4 | 0
|
||||
0 | 1 | 1
|
||||
0 | 1 | 1
|
||||
0.666666666666667 | 7 | 1
|
||||
1 | 9 | 1
|
||||
0 | 0 | 2
|
||||
0 | 1 | 3
|
||||
1 | 3 | 3
|
||||
(10 rows)
|
||||
|
||||
SELECT cume_dist() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
cume_dist | ten | four
|
||||
-------------------+-----+------
|
||||
0.666666666666667 | 0 | 0
|
||||
0.666666666666667 | 0 | 0
|
||||
1 | 4 | 0
|
||||
0.5 | 1 | 1
|
||||
0.5 | 1 | 1
|
||||
0.75 | 7 | 1
|
||||
1 | 9 | 1
|
||||
1 | 0 | 2
|
||||
0.5 | 1 | 3
|
||||
1 | 3 | 3
|
||||
(10 rows)
|
||||
|
||||
SELECT ntile(3) OVER (ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
ntile | ten | four
|
||||
-------+-----+------
|
||||
1 | 0 | 0
|
||||
1 | 0 | 2
|
||||
1 | 0 | 0
|
||||
1 | 1 | 1
|
||||
2 | 1 | 3
|
||||
2 | 1 | 1
|
||||
2 | 3 | 3
|
||||
3 | 4 | 0
|
||||
3 | 7 | 1
|
||||
3 | 9 | 1
|
||||
(10 rows)
|
||||
|
||||
SELECT ntile(NULL) OVER (ORDER BY ten), ten, four FROM tenk1 LIMIT 1;
|
||||
ntile | ten | four
|
||||
-------+-----+------
|
||||
| 0 | 0
|
||||
(1 row)
|
||||
|
||||
SELECT lag(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
lag | ten | four
|
||||
-----+-----+------
|
||||
| 0 | 0
|
||||
0 | 0 | 0
|
||||
0 | 4 | 0
|
||||
| 1 | 1
|
||||
1 | 1 | 1
|
||||
1 | 7 | 1
|
||||
7 | 9 | 1
|
||||
| 0 | 2
|
||||
| 1 | 3
|
||||
1 | 3 | 3
|
||||
(10 rows)
|
||||
|
||||
SELECT lag(ten, four) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
lag | ten | four
|
||||
-----+-----+------
|
||||
0 | 0 | 0
|
||||
0 | 0 | 0
|
||||
4 | 4 | 0
|
||||
| 1 | 1
|
||||
1 | 1 | 1
|
||||
1 | 7 | 1
|
||||
7 | 9 | 1
|
||||
| 0 | 2
|
||||
| 1 | 3
|
||||
| 3 | 3
|
||||
(10 rows)
|
||||
|
||||
SELECT lag(ten, four, 0) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
lag | ten | four
|
||||
-----+-----+------
|
||||
0 | 0 | 0
|
||||
0 | 0 | 0
|
||||
4 | 4 | 0
|
||||
0 | 1 | 1
|
||||
1 | 1 | 1
|
||||
1 | 7 | 1
|
||||
7 | 9 | 1
|
||||
0 | 0 | 2
|
||||
0 | 1 | 3
|
||||
0 | 3 | 3
|
||||
(10 rows)
|
||||
|
||||
SELECT lead(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
lead | ten | four
|
||||
------+-----+------
|
||||
0 | 0 | 0
|
||||
4 | 0 | 0
|
||||
| 4 | 0
|
||||
1 | 1 | 1
|
||||
7 | 1 | 1
|
||||
9 | 7 | 1
|
||||
| 9 | 1
|
||||
| 0 | 2
|
||||
3 | 1 | 3
|
||||
| 3 | 3
|
||||
(10 rows)
|
||||
|
||||
SELECT lead(ten * 2, 1) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
lead | ten | four
|
||||
------+-----+------
|
||||
0 | 0 | 0
|
||||
8 | 0 | 0
|
||||
| 4 | 0
|
||||
2 | 1 | 1
|
||||
14 | 1 | 1
|
||||
18 | 7 | 1
|
||||
| 9 | 1
|
||||
| 0 | 2
|
||||
6 | 1 | 3
|
||||
| 3 | 3
|
||||
(10 rows)
|
||||
|
||||
SELECT lead(ten * 2, 1, -1) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
lead | ten | four
|
||||
------+-----+------
|
||||
0 | 0 | 0
|
||||
8 | 0 | 0
|
||||
-1 | 4 | 0
|
||||
2 | 1 | 1
|
||||
14 | 1 | 1
|
||||
18 | 7 | 1
|
||||
-1 | 9 | 1
|
||||
-1 | 0 | 2
|
||||
6 | 1 | 3
|
||||
-1 | 3 | 3
|
||||
(10 rows)
|
||||
|
||||
SELECT first_value(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
first_value | ten | four
|
||||
-------------+-----+------
|
||||
0 | 0 | 0
|
||||
0 | 0 | 0
|
||||
0 | 4 | 0
|
||||
1 | 1 | 1
|
||||
1 | 1 | 1
|
||||
1 | 7 | 1
|
||||
1 | 9 | 1
|
||||
0 | 0 | 2
|
||||
1 | 1 | 3
|
||||
1 | 3 | 3
|
||||
(10 rows)
|
||||
|
||||
-- last_value returns the last row of the frame, which is CURRENT ROW in ORDER BY window.
|
||||
SELECT last_value(four) OVER (ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
last_value | ten | four
|
||||
------------+-----+------
|
||||
0 | 0 | 0
|
||||
0 | 0 | 2
|
||||
0 | 0 | 0
|
||||
1 | 1 | 1
|
||||
1 | 1 | 3
|
||||
1 | 1 | 1
|
||||
3 | 3 | 3
|
||||
0 | 4 | 0
|
||||
1 | 7 | 1
|
||||
1 | 9 | 1
|
||||
(10 rows)
|
||||
|
||||
SELECT last_value(ten) OVER (PARTITION BY four), ten, four FROM
|
||||
(SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten)s
|
||||
ORDER BY four, ten;
|
||||
last_value | ten | four
|
||||
------------+-----+------
|
||||
4 | 0 | 0
|
||||
4 | 0 | 0
|
||||
4 | 4 | 0
|
||||
9 | 1 | 1
|
||||
9 | 1 | 1
|
||||
9 | 7 | 1
|
||||
9 | 9 | 1
|
||||
0 | 0 | 2
|
||||
3 | 1 | 3
|
||||
3 | 3 | 3
|
||||
(10 rows)
|
||||
|
||||
SELECT nth_value(ten, four + 1) OVER (PARTITION BY four), ten, four
|
||||
FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten)s;
|
||||
nth_value | ten | four
|
||||
-----------+-----+------
|
||||
0 | 0 | 0
|
||||
0 | 0 | 0
|
||||
0 | 4 | 0
|
||||
1 | 1 | 1
|
||||
1 | 1 | 1
|
||||
1 | 7 | 1
|
||||
1 | 9 | 1
|
||||
| 0 | 2
|
||||
| 1 | 3
|
||||
| 3 | 3
|
||||
(10 rows)
|
||||
|
||||
SELECT ten, two, sum(hundred) AS gsum, sum(sum(hundred)) OVER (PARTITION BY two ORDER BY ten) AS wsum
|
||||
FROM tenk1 GROUP BY ten, two;
|
||||
ten | two | gsum | wsum
|
||||
-----+-----+-------+--------
|
||||
0 | 0 | 45000 | 45000
|
||||
2 | 0 | 47000 | 92000
|
||||
4 | 0 | 49000 | 141000
|
||||
6 | 0 | 51000 | 192000
|
||||
8 | 0 | 53000 | 245000
|
||||
1 | 1 | 46000 | 46000
|
||||
3 | 1 | 48000 | 94000
|
||||
5 | 1 | 50000 | 144000
|
||||
7 | 1 | 52000 | 196000
|
||||
9 | 1 | 54000 | 250000
|
||||
(10 rows)
|
||||
|
||||
SELECT count(*) OVER (PARTITION BY four), four FROM (SELECT * FROM tenk1 WHERE two = 1)s WHERE unique2 < 10;
|
||||
count | four
|
||||
-------+------
|
||||
4 | 1
|
||||
4 | 1
|
||||
4 | 1
|
||||
4 | 1
|
||||
2 | 3
|
||||
2 | 3
|
||||
(6 rows)
|
||||
|
||||
SELECT (count(*) OVER (PARTITION BY four ORDER BY ten) +
|
||||
sum(hundred) OVER (PARTITION BY four ORDER BY ten))::varchar AS cntsum
|
||||
FROM tenk1 WHERE unique2 < 10;
|
||||
cntsum
|
||||
--------
|
||||
22
|
||||
22
|
||||
87
|
||||
24
|
||||
24
|
||||
82
|
||||
92
|
||||
51
|
||||
92
|
||||
136
|
||||
(10 rows)
|
||||
|
||||
-- opexpr with different windows evaluation.
|
||||
SELECT * FROM(
|
||||
SELECT count(*) OVER (PARTITION BY four ORDER BY ten) +
|
||||
sum(hundred) OVER (PARTITION BY two ORDER BY ten) AS total,
|
||||
count(*) OVER (PARTITION BY four ORDER BY ten) AS fourcount,
|
||||
sum(hundred) OVER (PARTITION BY two ORDER BY ten) AS twosum
|
||||
FROM tenk1
|
||||
)sub
|
||||
WHERE total <> fourcount + twosum;
|
||||
total | fourcount | twosum
|
||||
-------+-----------+--------
|
||||
(0 rows)
|
||||
|
||||
SELECT avg(four) OVER (PARTITION BY four ORDER BY thousand / 100) FROM tenk1 WHERE unique2 < 10;
|
||||
avg
|
||||
------------------------
|
||||
0.00000000000000000000
|
||||
0.00000000000000000000
|
||||
0.00000000000000000000
|
||||
1.00000000000000000000
|
||||
1.00000000000000000000
|
||||
1.00000000000000000000
|
||||
1.00000000000000000000
|
||||
2.0000000000000000
|
||||
3.0000000000000000
|
||||
3.0000000000000000
|
||||
(10 rows)
|
||||
|
||||
SELECT ten, two, sum(hundred) AS gsum, sum(sum(hundred)) OVER win AS wsum
|
||||
FROM tenk1 GROUP BY ten, two WINDOW win AS (PARTITION BY two ORDER BY ten);
|
||||
ten | two | gsum | wsum
|
||||
-----+-----+-------+--------
|
||||
0 | 0 | 45000 | 45000
|
||||
2 | 0 | 47000 | 92000
|
||||
4 | 0 | 49000 | 141000
|
||||
6 | 0 | 51000 | 192000
|
||||
8 | 0 | 53000 | 245000
|
||||
1 | 1 | 46000 | 46000
|
||||
3 | 1 | 48000 | 94000
|
||||
5 | 1 | 50000 | 144000
|
||||
7 | 1 | 52000 | 196000
|
||||
9 | 1 | 54000 | 250000
|
||||
(10 rows)
|
||||
|
||||
-- more than one window with GROUP BY
|
||||
SELECT sum(salary),
|
||||
row_number() OVER (ORDER BY depname),
|
||||
sum(sum(salary)) OVER (ORDER BY depname DESC)
|
||||
FROM empsalary GROUP BY depname;
|
||||
sum | row_number | sum
|
||||
-------+------------+-------
|
||||
14600 | 3 | 14600
|
||||
7400 | 2 | 22000
|
||||
25100 | 1 | 47100
|
||||
(3 rows)
|
||||
|
||||
-- identical windows with different names
|
||||
SELECT sum(salary) OVER w1, count(*) OVER w2
|
||||
FROM empsalary WINDOW w1 AS (ORDER BY salary), w2 AS (ORDER BY salary);
|
||||
sum | count
|
||||
-------+-------
|
||||
3500 | 1
|
||||
7400 | 2
|
||||
11600 | 3
|
||||
16100 | 4
|
||||
25700 | 6
|
||||
25700 | 6
|
||||
30700 | 7
|
||||
41100 | 9
|
||||
41100 | 9
|
||||
47100 | 10
|
||||
(10 rows)
|
||||
|
||||
-- subplan
|
||||
SELECT lead(ten, (SELECT two FROM tenk1 WHERE s.unique2 = unique2)) OVER (PARTITION BY four ORDER BY ten)
|
||||
FROM tenk1 s WHERE unique2 < 10;
|
||||
lead
|
||||
------
|
||||
0
|
||||
0
|
||||
4
|
||||
1
|
||||
7
|
||||
9
|
||||
|
||||
0
|
||||
3
|
||||
|
||||
(10 rows)
|
||||
|
||||
-- empty table
|
||||
SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 WHERE FALSE)s;
|
||||
count
|
||||
-------
|
||||
(0 rows)
|
||||
|
||||
-- mixture of agg/wfunc in the same window
|
||||
SELECT sum(salary) OVER w, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary DESC);
|
||||
sum | rank
|
||||
-------+------
|
||||
6000 | 1
|
||||
16400 | 2
|
||||
16400 | 2
|
||||
20900 | 4
|
||||
25100 | 5
|
||||
3900 | 1
|
||||
7400 | 2
|
||||
5000 | 1
|
||||
14600 | 2
|
||||
14600 | 2
|
||||
(10 rows)
|
||||
|
||||
-- strict aggs
|
||||
SELECT empno, depname, salary, bonus, depadj, MIN(bonus) OVER (ORDER BY empno), MAX(depadj) OVER () FROM(
|
||||
SELECT *,
|
||||
CASE WHEN enroll_date < '2008-01-01' THEN 2008 - extract(YEAR FROM enroll_date) END * 500 AS bonus,
|
||||
CASE WHEN
|
||||
AVG(salary) OVER (PARTITION BY depname) < salary
|
||||
THEN 200 END AS depadj FROM empsalary
|
||||
)s;
|
||||
empno | depname | salary | bonus | depadj | min | max
|
||||
-------+-----------+--------+-------+--------+------+-----
|
||||
1 | sales | 5000 | 1000 | 200 | 1000 | 200
|
||||
2 | personnel | 3900 | 1000 | 200 | 1000 | 200
|
||||
3 | sales | 4800 | 500 | | 500 | 200
|
||||
4 | sales | 4800 | 500 | | 500 | 200
|
||||
5 | personnel | 3500 | 500 | | 500 | 200
|
||||
7 | develop | 4200 | | | 500 | 200
|
||||
8 | develop | 6000 | 1000 | 200 | 500 | 200
|
||||
9 | develop | 4500 | | | 500 | 200
|
||||
10 | develop | 5200 | 500 | 200 | 500 | 200
|
||||
11 | develop | 5200 | 500 | 200 | 500 | 200
|
||||
(10 rows)
|
||||
|
||||
-- with UNION
|
||||
SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk2)s LIMIT 0;
|
||||
count
|
||||
-------
|
||||
(0 rows)
|
||||
|
||||
-- via a VIEW
|
||||
CREATE TEMPORARY VIEW vsumsalary AS
|
||||
SELECT SUM(salary) OVER (PARTITION BY depname) FROM empsalary;
|
||||
SELECT * FROM vsumsalary;
|
||||
sum
|
||||
-------
|
||||
25100
|
||||
25100
|
||||
25100
|
||||
25100
|
||||
25100
|
||||
7400
|
||||
7400
|
||||
14600
|
||||
14600
|
||||
14600
|
||||
(10 rows)
|
||||
|
||||
-- ordering by a non-integer constant is allowed
|
||||
SELECT rank() OVER (ORDER BY length('abc'));
|
||||
rank
|
||||
------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
-- but this draws an error: "ORDER BY 1" means order by first SELECT column
|
||||
SELECT rank() OVER (ORDER BY 1);
|
||||
ERROR: window functions not allowed in window definition
|
||||
LINE 1: SELECT rank() OVER (ORDER BY 1);
|
||||
^
|
||||
-- some other errors
|
||||
SELECT * FROM empsalary WHERE row_number() OVER (ORDER BY salary) < 10;
|
||||
ERROR: window functions not allowed in WHERE clause
|
||||
LINE 1: SELECT * FROM empsalary WHERE row_number() OVER (ORDER BY sa...
|
||||
^
|
||||
SELECT * FROM empsalary INNER JOIN tenk1 ON row_number() OVER (ORDER BY salary) < 10;
|
||||
ERROR: window functions not allowed in JOIN conditions
|
||||
LINE 1: SELECT * FROM empsalary INNER JOIN tenk1 ON row_number() OVE...
|
||||
^
|
||||
SELECT rank() OVER (ORDER BY 1), count(*) FROM empsalary GROUP BY 1;
|
||||
ERROR: window functions not allowed in GROUP BY clause
|
||||
LINE 1: SELECT rank() OVER (ORDER BY 1), count(*) FROM empsalary GRO...
|
||||
^
|
||||
SELECT * FROM rank() OVER (ORDER BY random());
|
||||
ERROR: cannot use window function in function expression in FROM
|
||||
LINE 1: SELECT * FROM rank() OVER (ORDER BY random());
|
||||
^
|
||||
DELETE FROM empsalary WHERE (rank() OVER (ORDER BY random())) > 10;
|
||||
ERROR: window functions not allowed in WHERE clause
|
||||
LINE 1: DELETE FROM empsalary WHERE (rank() OVER (ORDER BY random())...
|
||||
^
|
||||
DELETE FROM empsalary RETURNING rank() OVER (ORDER BY random());
|
||||
ERROR: cannot use window function in RETURNING
|
||||
LINE 1: DELETE FROM empsalary RETURNING rank() OVER (ORDER BY random...
|
||||
^
|
||||
SELECT count(*) OVER w FROM tenk1 WINDOW w AS (ORDER BY unique1), w AS (ORDER BY unique1);
|
||||
ERROR: window "w" is already defined
|
||||
LINE 1: ...w FROM tenk1 WINDOW w AS (ORDER BY unique1), w AS (ORDER BY ...
|
||||
^
|
||||
SELECT rank() OVER (PARTITION BY four, ORDER BY ten) FROM tenk1;
|
||||
ERROR: syntax error at or near "ORDER"
|
||||
LINE 1: SELECT rank() OVER (PARTITION BY four, ORDER BY ten) FROM te...
|
||||
^
|
||||
SELECT count() OVER () FROM tenk1;
|
||||
ERROR: count(*) must be used to call a parameterless aggregate function
|
||||
LINE 1: SELECT count() OVER () FROM tenk1;
|
||||
^
|
||||
SELECT generate_series(1, 100) OVER () FROM empsalary;
|
||||
ERROR: OVER specified, but generate_series is not a window function nor an aggregate function
|
||||
LINE 1: SELECT generate_series(1, 100) OVER () FROM empsalary;
|
||||
^
|
||||
SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1;
|
||||
ERROR: argument of ntile must be greater than zero
|
||||
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
|
||||
ERROR: argument of nth_value must be greater than zero
|
||||
-- cleanup
|
||||
DROP VIEW vsumsalary;
|
||||
DROP TABLE empsalary;
|
@ -781,12 +781,12 @@ LINE 2: WHERE n IN (SELECT * FROM x))
|
||||
-- aggregate functions
|
||||
WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT count(*) FROM x)
|
||||
SELECT * FROM x;
|
||||
ERROR: aggregates not allowed in a recursive query's recursive term
|
||||
ERROR: aggregate functions not allowed in a recursive query's recursive term
|
||||
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT count(*) F...
|
||||
^
|
||||
WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT sum(n) FROM x)
|
||||
SELECT * FROM x;
|
||||
ERROR: aggregates not allowed in a recursive query's recursive term
|
||||
ERROR: aggregate functions not allowed in a recursive query's recursive term
|
||||
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT sum(n) FRO...
|
||||
^
|
||||
-- ORDER BY
|
||||
|
@ -1,5 +1,5 @@
|
||||
# ----------
|
||||
# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.51 2008/12/19 16:25:19 petere Exp $
|
||||
# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.52 2008/12/28 18:54:01 tgl Exp $
|
||||
#
|
||||
# By convention, we put no more than twenty tests in any one parallel group;
|
||||
# this limits the number of connections needed to run the tests.
|
||||
@ -83,7 +83,7 @@ test: select_views portals_p2 rules foreign_key cluster dependency guc bitmapops
|
||||
# Another group of parallel tests
|
||||
# ----------
|
||||
# "plpgsql" cannot run concurrently with "rules", nor can "plancache"
|
||||
test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
|
||||
test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject window with xml
|
||||
|
||||
# run stats by itself because its delay may be insufficient under heavy load
|
||||
test: stats
|
||||
|
@ -1,4 +1,4 @@
|
||||
# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.48 2008/12/19 16:25:19 petere Exp $
|
||||
# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.49 2008/12/28 18:54:01 tgl Exp $
|
||||
# This should probably be in an order similar to parallel_schedule.
|
||||
test: boolean
|
||||
test: char
|
||||
@ -116,6 +116,7 @@ test: polymorphism
|
||||
test: rowtypes
|
||||
test: returning
|
||||
test: largeobject
|
||||
test: window
|
||||
test: with
|
||||
test: xml
|
||||
test: stats
|
||||
|
179
src/test/regress/sql/window.sql
Normal file
179
src/test/regress/sql/window.sql
Normal file
@ -0,0 +1,179 @@
|
||||
--
|
||||
-- WINDOW FUNCTIONS
|
||||
--
|
||||
|
||||
CREATE TEMPORARY TABLE empsalary (
|
||||
depname varchar,
|
||||
empno bigint,
|
||||
salary int,
|
||||
enroll_date date
|
||||
);
|
||||
|
||||
INSERT INTO empsalary VALUES
|
||||
('develop', 10, 5200, '2007-08-01'),
|
||||
('sales', 1, 5000, '2006-10-01'),
|
||||
('personnel', 5, 3500, '2007-12-10'),
|
||||
('sales', 4, 4800, '2007-08-08'),
|
||||
('personnel', 2, 3900, '2006-12-23'),
|
||||
('develop', 7, 4200, '2008-01-01'),
|
||||
('develop', 9, 4500, '2008-01-01'),
|
||||
('sales', 3, 4800, '2007-08-01'),
|
||||
('develop', 8, 6000, '2006-10-01'),
|
||||
('develop', 11, 5200, '2007-08-15');
|
||||
|
||||
SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary;
|
||||
|
||||
SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary;
|
||||
|
||||
-- with GROUP BY
|
||||
SELECT four, ten, SUM(SUM(four)) OVER (PARTITION BY four), AVG(ten) FROM tenk1
|
||||
GROUP BY four, ten ORDER BY four, ten;
|
||||
|
||||
SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PARTITION BY depname);
|
||||
|
||||
SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w;
|
||||
|
||||
-- empty window specification
|
||||
SELECT COUNT(*) OVER () FROM tenk1 WHERE unique2 < 10;
|
||||
|
||||
SELECT COUNT(*) OVER w FROM tenk1 WHERE unique2 < 10 WINDOW w AS ();
|
||||
|
||||
-- no window operation
|
||||
SELECT four FROM tenk1 WHERE FALSE WINDOW w AS (PARTITION BY ten);
|
||||
|
||||
-- cumulative aggregate
|
||||
SELECT sum(four) OVER (PARTITION BY ten ORDER BY unique2) AS sum_1, ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
|
||||
SELECT row_number() OVER (ORDER BY unique2) FROM tenk1 WHERE unique2 < 10;
|
||||
|
||||
SELECT rank() OVER (PARTITION BY four ORDER BY ten) AS rank_1, ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
|
||||
SELECT dense_rank() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
|
||||
SELECT percent_rank() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
|
||||
SELECT cume_dist() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
|
||||
SELECT ntile(3) OVER (ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
|
||||
SELECT ntile(NULL) OVER (ORDER BY ten), ten, four FROM tenk1 LIMIT 1;
|
||||
|
||||
SELECT lag(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
|
||||
SELECT lag(ten, four) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
|
||||
SELECT lag(ten, four, 0) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
|
||||
SELECT lead(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
|
||||
SELECT lead(ten * 2, 1) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
|
||||
SELECT lead(ten * 2, 1, -1) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
|
||||
SELECT first_value(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
|
||||
-- last_value returns the last row of the frame, which is CURRENT ROW in ORDER BY window.
|
||||
SELECT last_value(four) OVER (ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10;
|
||||
|
||||
SELECT last_value(ten) OVER (PARTITION BY four), ten, four FROM
|
||||
(SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten)s
|
||||
ORDER BY four, ten;
|
||||
|
||||
SELECT nth_value(ten, four + 1) OVER (PARTITION BY four), ten, four
|
||||
FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten)s;
|
||||
|
||||
SELECT ten, two, sum(hundred) AS gsum, sum(sum(hundred)) OVER (PARTITION BY two ORDER BY ten) AS wsum
|
||||
FROM tenk1 GROUP BY ten, two;
|
||||
|
||||
SELECT count(*) OVER (PARTITION BY four), four FROM (SELECT * FROM tenk1 WHERE two = 1)s WHERE unique2 < 10;
|
||||
|
||||
SELECT (count(*) OVER (PARTITION BY four ORDER BY ten) +
|
||||
sum(hundred) OVER (PARTITION BY four ORDER BY ten))::varchar AS cntsum
|
||||
FROM tenk1 WHERE unique2 < 10;
|
||||
|
||||
-- opexpr with different windows evaluation.
|
||||
SELECT * FROM(
|
||||
SELECT count(*) OVER (PARTITION BY four ORDER BY ten) +
|
||||
sum(hundred) OVER (PARTITION BY two ORDER BY ten) AS total,
|
||||
count(*) OVER (PARTITION BY four ORDER BY ten) AS fourcount,
|
||||
sum(hundred) OVER (PARTITION BY two ORDER BY ten) AS twosum
|
||||
FROM tenk1
|
||||
)sub
|
||||
WHERE total <> fourcount + twosum;
|
||||
|
||||
SELECT avg(four) OVER (PARTITION BY four ORDER BY thousand / 100) FROM tenk1 WHERE unique2 < 10;
|
||||
|
||||
SELECT ten, two, sum(hundred) AS gsum, sum(sum(hundred)) OVER win AS wsum
|
||||
FROM tenk1 GROUP BY ten, two WINDOW win AS (PARTITION BY two ORDER BY ten);
|
||||
|
||||
-- more than one window with GROUP BY
|
||||
SELECT sum(salary),
|
||||
row_number() OVER (ORDER BY depname),
|
||||
sum(sum(salary)) OVER (ORDER BY depname DESC)
|
||||
FROM empsalary GROUP BY depname;
|
||||
|
||||
-- identical windows with different names
|
||||
SELECT sum(salary) OVER w1, count(*) OVER w2
|
||||
FROM empsalary WINDOW w1 AS (ORDER BY salary), w2 AS (ORDER BY salary);
|
||||
|
||||
-- subplan
|
||||
SELECT lead(ten, (SELECT two FROM tenk1 WHERE s.unique2 = unique2)) OVER (PARTITION BY four ORDER BY ten)
|
||||
FROM tenk1 s WHERE unique2 < 10;
|
||||
|
||||
-- empty table
|
||||
SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 WHERE FALSE)s;
|
||||
|
||||
-- mixture of agg/wfunc in the same window
|
||||
SELECT sum(salary) OVER w, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary DESC);
|
||||
|
||||
-- strict aggs
|
||||
SELECT empno, depname, salary, bonus, depadj, MIN(bonus) OVER (ORDER BY empno), MAX(depadj) OVER () FROM(
|
||||
SELECT *,
|
||||
CASE WHEN enroll_date < '2008-01-01' THEN 2008 - extract(YEAR FROM enroll_date) END * 500 AS bonus,
|
||||
CASE WHEN
|
||||
AVG(salary) OVER (PARTITION BY depname) < salary
|
||||
THEN 200 END AS depadj FROM empsalary
|
||||
)s;
|
||||
|
||||
-- with UNION
|
||||
SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk2)s LIMIT 0;
|
||||
|
||||
-- via a VIEW
|
||||
CREATE TEMPORARY VIEW vsumsalary AS
|
||||
SELECT SUM(salary) OVER (PARTITION BY depname) FROM empsalary;
|
||||
SELECT * FROM vsumsalary;
|
||||
|
||||
-- ordering by a non-integer constant is allowed
|
||||
SELECT rank() OVER (ORDER BY length('abc'));
|
||||
|
||||
-- but this draws an error: "ORDER BY 1" means order by first SELECT column
|
||||
SELECT rank() OVER (ORDER BY 1);
|
||||
|
||||
-- some other errors
|
||||
SELECT * FROM empsalary WHERE row_number() OVER (ORDER BY salary) < 10;
|
||||
|
||||
SELECT * FROM empsalary INNER JOIN tenk1 ON row_number() OVER (ORDER BY salary) < 10;
|
||||
|
||||
SELECT rank() OVER (ORDER BY 1), count(*) FROM empsalary GROUP BY 1;
|
||||
|
||||
SELECT * FROM rank() OVER (ORDER BY random());
|
||||
|
||||
DELETE FROM empsalary WHERE (rank() OVER (ORDER BY random())) > 10;
|
||||
|
||||
DELETE FROM empsalary RETURNING rank() OVER (ORDER BY random());
|
||||
|
||||
SELECT count(*) OVER w FROM tenk1 WINDOW w AS (ORDER BY unique1), w AS (ORDER BY unique1);
|
||||
|
||||
SELECT rank() OVER (PARTITION BY four, ORDER BY ten) FROM tenk1;
|
||||
|
||||
SELECT count() OVER () FROM tenk1;
|
||||
|
||||
SELECT generate_series(1, 100) OVER () FROM empsalary;
|
||||
|
||||
SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1;
|
||||
|
||||
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
|
||||
|
||||
-- cleanup
|
||||
DROP VIEW vsumsalary;
|
||||
DROP TABLE empsalary;
|
Loading…
Reference in New Issue
Block a user