Support window functions a la SQL:2008.

Hitoshi Harada, with some kibitzing from Heikki and Tom.
This commit is contained in:
Tom Lane 2008-12-28 18:54:01 +00:00
parent 38e9348282
commit 95b07bc7f5
92 changed files with 6720 additions and 321 deletions

View File

@ -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)
{

View File

@ -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 &mdash; 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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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:

View File

@ -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>

View File

@ -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 } ]

View File

@ -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 &mdash; 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>

View File

@ -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 &amp;&amp; IsA(fcinfo->context, AggState))
if (fcinfo-&gt;context &amp;&amp;
(IsA(fcinfo-&gt;context, AggState) ||
IsA(fcinfo-&gt;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

View File

@ -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 */

View File

@ -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;
}

View File

@ -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)));
}

View File

@ -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;

View File

@ -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),

View File

@ -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);

View File

@ -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
{

View File

@ -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.

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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))

View File

@ -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.

View File

@ -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

View File

@ -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;

View File

@ -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)
{

View File

@ -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;
/*

View File

@ -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)

View File

@ -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");
}
}

View File

@ -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,

View File

@ -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:

View File

@ -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 ||

View File

@ -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);
/*

View File

@ -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 ||

View File

@ -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);

View File

@ -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")));
}
/*

View File

@ -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;
}

View File

@ -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},

View File

@ -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 -

View File

@ -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;
}

View File

@ -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)); */

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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 ||

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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

View 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);
}

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View 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 */

View File

@ -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

View File

@ -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
*

View File

@ -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;

View File

@ -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 */
/*

View File

@ -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
* ----------------

View File

@ -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
*

View File

@ -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 */

View File

@ -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);

View File

@ -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,

View File

@ -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,

View File

@ -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);

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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;

View File

@ -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,

View File

@ -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

View File

@ -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);

View File

@ -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')

View File

@ -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
View 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 */

View File

@ -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
},

View 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;

View File

@ -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

View File

@ -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

View File

@ -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

View 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;