Support window functions a la SQL:2008.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 28 Dec 2008 18:54:01 +0000 (18:54 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 28 Dec 2008 18:54:01 +0000 (18:54 +0000)
Hitoshi Harada, with some kibitzing from Heikki and Tom.

92 files changed:
contrib/tsearch2/tsearch2.c
doc/src/sgml/advanced.sgml
doc/src/sgml/errcodes.sgml
doc/src/sgml/func.sgml
doc/src/sgml/queries.sgml
doc/src/sgml/query.sgml
doc/src/sgml/ref/select.sgml
doc/src/sgml/ref/select_into.sgml
doc/src/sgml/syntax.sgml
doc/src/sgml/xaggr.sgml
src/backend/catalog/dependency.c
src/backend/catalog/heap.c
src/backend/catalog/pg_proc.c
src/backend/commands/explain.c
src/backend/commands/functioncmds.c
src/backend/commands/prepare.c
src/backend/commands/tablecmds.c
src/backend/commands/typecmds.c
src/backend/executor/Makefile
src/backend/executor/execAmi.c
src/backend/executor/execProcnode.c
src/backend/executor/execQual.c
src/backend/executor/nodeWindowAgg.c [new file with mode: 0644]
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/nodeFuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/path/allpaths.c
src/backend/optimizer/path/costsize.c
src/backend/optimizer/path/equivclass.c
src/backend/optimizer/plan/createplan.c
src/backend/optimizer/plan/planagg.c
src/backend/optimizer/plan/planmain.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/plan/setrefs.c
src/backend/optimizer/plan/subselect.c
src/backend/optimizer/prep/prepjointree.c
src/backend/optimizer/prep/prepunion.c
src/backend/optimizer/util/clauses.c
src/backend/optimizer/util/tlist.c
src/backend/parser/analyze.c
src/backend/parser/gram.y
src/backend/parser/keywords.c
src/backend/parser/parse_agg.c
src/backend/parser/parse_clause.c
src/backend/parser/parse_coerce.c
src/backend/parser/parse_expr.c
src/backend/parser/parse_func.c
src/backend/parser/parse_type.c
src/backend/parser/parse_utilcmd.c
src/backend/rewrite/rewriteManip.c
src/backend/utils/adt/Makefile
src/backend/utils/adt/array_userfuncs.c
src/backend/utils/adt/arrayfuncs.c
src/backend/utils/adt/float.c
src/backend/utils/adt/int8.c
src/backend/utils/adt/numeric.c
src/backend/utils/adt/ruleutils.c
src/backend/utils/adt/windowfuncs.c [new file with mode: 0644]
src/backend/utils/fmgr/fmgr.c
src/backend/utils/sort/tuplestore.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.h
src/include/executor/nodeWindowAgg.h [new file with mode: 0644]
src/include/fmgr.h
src/include/nodes/execnodes.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/nodes/plannodes.h
src/include/nodes/primnodes.h
src/include/nodes/relation.h
src/include/optimizer/clauses.h
src/include/optimizer/cost.h
src/include/optimizer/planmain.h
src/include/optimizer/tlist.h
src/include/parser/parse_agg.h
src/include/parser/parse_clause.h
src/include/parser/parse_func.h
src/include/parser/parse_node.h
src/include/rewrite/rewriteManip.h
src/include/utils/array.h
src/include/utils/builtins.h
src/include/utils/errcodes.h
src/include/utils/tuplestore.h
src/include/windowapi.h [new file with mode: 0644]
src/pl/plpgsql/src/plerrcodes.h
src/test/regress/expected/window.out [new file with mode: 0644]
src/test/regress/expected/with.out
src/test/regress/parallel_schedule
src/test/regress/serial_schedule
src/test/regress/sql/window.sql [new file with mode: 0644]

index 7754f5740269a11eeb4682efbdc4353495b4cb28..bdccba787a9d42858038b5a78123072cff5e1339 100644 (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)
        {
index 2ecb2da5c56db4e695456517a90b1e58d6f2d0c9..ce8ef535dba34a7aadfc81a9ea7f069648bf6e21 100644 (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
index 574e7f5fbad75cb5db8adebdbdc92f62e71cba31..e792a74e2866bfa2c88e69afd24e7e38dcef26a8 100644 (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>
 <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>
 <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>
index de50c0e1d5691168ee639a176f127df764611fc8..205b71e9c9e57bb7e819d4b5a7c737e5d2f8760d 100644 (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>
index 283dd0a73ddd71212f2926fe4edc6d8f629959c1..f1db64b273a1f8510643729e074784ce80bb181b 100644 (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>
 
index 442f9ad0068d3bc613184125a226af4a8d2f4088..ffc641b03ada6a44068e1a6c10a86c8199b77352 100644 (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:
index 814a6708f004c0dca4d2f01ecf87a26fd87c5d73..c9a386f24f363974b1631246a2c81a0e92bf2b13 100644 (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>
 
index 038ae1b333c41e818fc1bb286cf4595cb7fcbc82..057bfb2a9d75abdf08812f988fed9bbef41d8104 100644 (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 } ]
index cca44794340b1fb5ac2b4586b03cbd23429265d0..9d0833c203586ebd1b0c519ede0b4802f8e1cc35 100644 (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>
 
index 3c4ce19258e9e75a9a90d67fd734e39a17c9de12..b223888f9ed92c70475b1f3499ea4fd555433dc9 100644 (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
index 2cbc19f5a0621dd63be004216399a31601c45031..b78bebf506f069dd93485f8553e6233cfa76437c 100644 (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 */
index a711143f86cda6a9bcc2332ac002bb470d37991c..af200afaac8c0f0a07ebb17080f700b7bbb65741 100644 (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;
 }
index 345df0c6a955cb51a8350211274ef8a8e1d2228f..8ff22c23c9ec2593c99bbf156e7c85409a231f84 100644 (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 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 an aggregate",
+                                                errmsg("function \"%s\" is not a window function",
                                                                procedureName)));
                }
 
index e5f1b3130760c7bd44df7191f0e455409b077614..d829cb1923552ff348c9e716ab482457a08b30b2 100644 (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;
index 0a3de53e1e54b9b83907e97dc783d1ecf326e9b8..8963f98117813a73b9d38c1cf518bf3bec358d7b 100644 (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),
index 920b328bb307f22d26d220d7e8bba9278c46023f..f1f87abe227b951d36be4fb8ae0fc3c0a42c6e6a 100644 (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);
 
index 9f34c735028d54b0ef38ddeba301b7cb8768d6ef..173b24dab824575d7158b6d71f31b9ebf33deaa9 100644 (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
        {
index 38416fa67f276409950afed166c04dea94c05689..f99ed8139542b872eedd46ce2f286fed78b0cc28 100644 (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.
index b4a0492751c40d6676bee935747dce320463205e..63c8610778220ca128cf4f358a409fea4b6a75ab 100644 (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
index ef4f6853899659ab94138bf2c5be939dc720ddc5..d406a0cec9a2b24bf559f353ef31c2cd24aa86cf 100644 (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;
index e689ec00f8c4669e3b153c5af557d76fcfd40556..cd610c895c1feb7b679b84b8bed7a79038c716b4 100644 (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"
 #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;
index 71aad49647d6282c2b980231b1c30f705915b8b1..17606f5204e1f1190da516516d29d853e7bc2138 100644 (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;
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
new file mode 100644 (file)
index 0000000..37ef9a5
--- /dev/null
@@ -0,0 +1,1854 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeWindowAgg.c
+ *       routines to handle WindowAgg nodes.
+ *
+ * A WindowAgg node evaluates "window functions" across suitable partitions
+ * of the input tuple set.  Any one WindowAgg works for just a single window
+ * specification, though it can evaluate multiple window functions sharing
+ * identical window specifications.  The input tuples are required to be
+ * delivered in sorted order, with the PARTITION BY columns (if any) as
+ * major sort keys and the ORDER BY columns (if any) as minor sort keys.
+ * (The planner generates a stack of WindowAggs with intervening Sort nodes
+ * as needed, if a query involves more than one window specification.)
+ *
+ * Since window functions can require access to any or all of the rows in
+ * the current partition, we accumulate rows of the partition into a
+ * tuplestore.  The window functions are called using the WindowObject API
+ * so that they can access those rows as needed.
+ *
+ * We also support using plain aggregate functions as window functions.
+ * For these, the regular Agg-node environment is emulated for each partition.
+ * As required by the SQL spec, the output represents the value of the
+ * aggregate function over all rows in the current row's window frame.
+ *
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *       $PostgreSQL: pgsql/src/backend/executor/nodeWindowAgg.c,v 1.1 2008/12/28 18:53:55 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/pg_aggregate.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "executor/nodeWindowAgg.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "parser/parse_agg.h"
+#include "parser/parse_coerce.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/syscache.h"
+#include "windowapi.h"
+
+/*
+ * All the window function APIs are called with this object, which is passed
+ * to window functions as fcinfo->context.
+ */
+typedef struct WindowObjectData
+{
+       NodeTag         type;
+       WindowAggState *winstate;       /* parent WindowAggState */
+       List       *argstates;          /* ExprState trees for fn's arguments */
+       void       *localmem;           /* WinGetPartitionLocalMemory's chunk */
+       int                     markptr;                /* tuplestore mark pointer for this fn */
+       int                     readptr;                /* tuplestore read pointer for this fn */
+       int64           markpos;                /* row that markptr is positioned on */
+       int64           seekpos;                /* row that readptr is positioned on */
+} WindowObjectData;
+
+/*
+ * We have one WindowStatePerFunc struct for each window function and
+ * window aggregate handled by this node.
+ */
+typedef struct WindowStatePerFuncData
+{
+       /* Links to WindowFunc expr and state nodes this working state is for */
+       WindowFuncExprState *wfuncstate;
+       WindowFunc         *wfunc;
+
+       int                     numArguments;   /* number of arguments */
+
+       FmgrInfo        flinfo;                 /* fmgr lookup data for window function */
+
+       /*
+        * We need the len and byval info for the result of each function
+        * in order to know how to copy/delete values.
+        */
+       int16           resulttypeLen;
+       bool            resulttypeByVal;
+
+       bool            plain_agg;              /* is it just a plain aggregate function? */
+       int                     aggno;                  /* if so, index of its PerAggData */
+
+       WindowObject    winobj;         /* object used in window function API */
+} WindowStatePerFuncData;
+
+/*
+ * For plain aggregate window functions, we also have one of these.
+ */
+typedef struct WindowStatePerAggData
+{
+       /* Oids of transfer functions */
+       Oid                     transfn_oid;
+       Oid                     finalfn_oid;    /* may be InvalidOid */
+
+       /*
+        * fmgr lookup data for transfer functions --- only valid when
+        * corresponding oid is not InvalidOid.  Note in particular that fn_strict
+        * flags are kept here.
+        */
+       FmgrInfo        transfn;
+       FmgrInfo        finalfn;
+
+       /*
+        * initial value from pg_aggregate entry
+        */
+       Datum           initValue;
+       bool            initValueIsNull;
+
+       /*
+        * cached value for non-moving frame
+        */
+       Datum           resultValue;
+       bool            resultValueIsNull;
+       bool            hasResult;
+
+       /*
+        * We need the len and byval info for the agg's input, result, and
+        * transition data types in order to know how to copy/delete values.
+        */
+       int16           inputtypeLen,
+                               resulttypeLen,
+                               transtypeLen;
+       bool            inputtypeByVal,
+                               resulttypeByVal,
+                               transtypeByVal;
+
+       int                     wfuncno;                /* index of associated PerFuncData */
+
+       /* Current transition value */
+       Datum           transValue;             /* current transition value */
+       bool            transValueIsNull;
+
+       bool            noTransValue;   /* true if transValue not set yet */
+} WindowStatePerAggData;
+
+static void initialize_windowaggregate(WindowAggState *winstate,
+                                                                          WindowStatePerFunc perfuncstate,
+                                                                          WindowStatePerAgg peraggstate);
+static void advance_windowaggregate(WindowAggState *winstate,
+                                                                       WindowStatePerFunc perfuncstate,
+                                                                       WindowStatePerAgg peraggstate);
+static void finalize_windowaggregate(WindowAggState *winstate,
+                                                                        WindowStatePerFunc perfuncstate,
+                                                                        WindowStatePerAgg peraggstate,
+                                                                        Datum *result, bool *isnull);
+
+static void eval_windowaggregates(WindowAggState *winstate);
+static void eval_windowfunction(WindowAggState *winstate,
+                                                               WindowStatePerFunc perfuncstate,
+                                                               Datum *result, bool *isnull);
+
+static void begin_partition(WindowAggState *winstate);
+static void spool_tuples(WindowAggState *winstate, int64 pos);
+static void release_partition(WindowAggState *winstate);
+
+static WindowStatePerAggData *initialize_peragg(WindowAggState *winstate,
+                                                                                               WindowFunc *wfunc,
+                                                                                               WindowStatePerAgg peraggstate);
+static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
+
+static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
+                                         TupleTableSlot *slot2);
+static bool window_gettupleslot(WindowObject winobj, int64 pos,
+                                                               TupleTableSlot *slot);
+
+
+/*
+ * initialize_windowaggregate
+ * parallel to initialize_aggregate in nodeAgg.c
+ */
+static void
+initialize_windowaggregate(WindowAggState *winstate,
+                                                  WindowStatePerFunc perfuncstate,
+                                                  WindowStatePerAgg peraggstate)
+{
+       MemoryContext           oldContext;
+
+       if (peraggstate->initValueIsNull)
+               peraggstate->transValue = peraggstate->initValue;
+       else
+       {
+               oldContext = MemoryContextSwitchTo(winstate->wincontext);
+               peraggstate->transValue = datumCopy(peraggstate->initValue,
+                                                                                       peraggstate->transtypeByVal,
+                                                                                       peraggstate->transtypeLen);
+               MemoryContextSwitchTo(oldContext);
+       }
+       peraggstate->transValueIsNull = peraggstate->initValueIsNull;
+       peraggstate->noTransValue = peraggstate->initValueIsNull;
+}
+
+/*
+ * advance_windowaggregate
+ * parallel to advance_aggregate in nodeAgg.c
+ */
+static void
+advance_windowaggregate(WindowAggState *winstate,
+                                               WindowStatePerFunc perfuncstate,
+                                               WindowStatePerAgg peraggstate)
+{
+       WindowFuncExprState        *wfuncstate = perfuncstate->wfuncstate;
+       int                                             numArguments = perfuncstate->numArguments;
+       FunctionCallInfoData    fcinfodata;
+       FunctionCallInfo                fcinfo = &fcinfodata;
+       Datum                                   newVal;
+       ListCell                           *arg;
+       int                                             i;
+       MemoryContext                   oldContext;
+       ExprContext *econtext = winstate->tmpcontext;
+
+       oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+
+       /* We start from 1, since the 0th arg will be the transition value */
+       i = 1;
+       foreach(arg, wfuncstate->args)
+       {
+               ExprState          *argstate = (ExprState *) lfirst(arg);
+
+               fcinfo->arg[i] = ExecEvalExpr(argstate, econtext,
+                                                                         &fcinfo->argnull[i], NULL);
+               i++;
+       }
+
+       if (peraggstate->transfn.fn_strict)
+       {
+               /*
+                * For a strict transfn, nothing happens when there's a NULL input; we
+                * just keep the prior transValue.
+                */
+               for (i = 1; i <= numArguments; i++)
+               {
+                       if (fcinfo->argnull[i])
+                       {
+                               MemoryContextSwitchTo(oldContext);
+                               return;
+                       }
+               }
+               if (peraggstate->noTransValue)
+               {
+                       /*
+                        * transValue has not been initialized. This is the first non-NULL
+                        * input value. We use it as the initial value for transValue. (We
+                        * already checked that the agg's input type is binary-compatible
+                        * with its transtype, so straight copy here is OK.)
+                        *
+                        * We must copy the datum into wincontext if it is pass-by-ref. We
+                        * do not need to pfree the old transValue, since it's NULL.
+                        */
+                       MemoryContextSwitchTo(winstate->wincontext);
+                       peraggstate->transValue = datumCopy(fcinfo->arg[1],
+                                                                                        peraggstate->transtypeByVal,
+                                                                                        peraggstate->transtypeLen);
+                       peraggstate->transValueIsNull = false;
+                       peraggstate->noTransValue = false;
+                       MemoryContextSwitchTo(oldContext);
+                       return;
+               }
+               if (peraggstate->transValueIsNull)
+               {
+                       /*
+                        * Don't call a strict function with NULL inputs.  Note it is
+                        * possible to get here despite the above tests, if the transfn is
+                        * strict *and* returned a NULL on a prior cycle. If that happens
+                        * we will propagate the NULL all the way to the end.
+                        */
+                       MemoryContextSwitchTo(oldContext);
+                       return;
+               }
+       }
+
+       /*
+        * OK to call the transition function
+        */
+       InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
+                                                        numArguments + 1,
+                                                        (void *) winstate, NULL);
+       fcinfo->arg[0] = peraggstate->transValue;
+       fcinfo->argnull[0] = peraggstate->transValueIsNull;
+       newVal = FunctionCallInvoke(fcinfo);
+
+       /*
+        * If pass-by-ref datatype, must copy the new value into wincontext and
+        * pfree the prior transValue.  But if transfn returned a pointer to its
+        * first input, we don't need to do anything.
+        */
+       if (!peraggstate->transtypeByVal &&
+               DatumGetPointer(newVal) != DatumGetPointer(peraggstate->transValue))
+       {
+               if (!fcinfo->isnull)
+               {
+                       MemoryContextSwitchTo(winstate->wincontext);
+                       newVal = datumCopy(newVal,
+                                                          peraggstate->transtypeByVal,
+                                                          peraggstate->transtypeLen);
+               }
+               if (!peraggstate->transValueIsNull)
+                       pfree(DatumGetPointer(peraggstate->transValue));
+       }
+
+       MemoryContextSwitchTo(oldContext);
+       peraggstate->transValue = newVal;
+       peraggstate->transValueIsNull = fcinfo->isnull;
+}
+
+/*
+ * finalize_windowaggregate
+ * parallel to finalize_aggregate in nodeAgg.c
+ */
+static void
+finalize_windowaggregate(WindowAggState *winstate,
+                                                WindowStatePerFunc perfuncstate,
+                                                WindowStatePerAgg peraggstate,
+                                                Datum *result, bool *isnull)
+{
+       MemoryContext                   oldContext;
+
+       oldContext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_tuple_memory);
+
+       /*
+        * Apply the agg's finalfn if one is provided, else return transValue.
+        */
+       if (OidIsValid(peraggstate->finalfn_oid))
+       {
+               FunctionCallInfoData    fcinfo;
+
+               InitFunctionCallInfoData(fcinfo, &(peraggstate->finalfn), 1,
+                                                                (void *) winstate, NULL);
+               fcinfo.arg[0] = peraggstate->transValue;
+               fcinfo.argnull[0] = peraggstate->transValueIsNull;
+               if (fcinfo.flinfo->fn_strict && peraggstate->transValueIsNull)
+               {
+                       /* don't call a strict function with NULL inputs */
+                       *result = (Datum) 0;
+                       *isnull = true;
+               }
+               else
+               {
+                       *result = FunctionCallInvoke(&fcinfo);
+                       *isnull = fcinfo.isnull;
+               }
+       }
+       else
+       {
+               *result = peraggstate->transValue;
+               *isnull = peraggstate->transValueIsNull;
+       }
+
+       /*
+        * If result is pass-by-ref, make sure it is in the right context.
+        */
+       if (!peraggstate->resulttypeByVal && !*isnull &&
+               !MemoryContextContains(CurrentMemoryContext,
+                                                          DatumGetPointer(*result)))
+               *result = datumCopy(*result,
+                                                       peraggstate->resulttypeByVal,
+                                                       peraggstate->resulttypeLen);
+       MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * eval_windowaggregates
+ * evaluate plain aggregates being used as window functions
+ *
+ * Much of this is duplicated from nodeAgg.c.  But NOTE that we expect to be
+ * able to call aggregate final functions repeatedly after aggregating more
+ * data onto the same transition value.  This is not a behavior required by
+ * nodeAgg.c.
+ */
+static void
+eval_windowaggregates(WindowAggState *winstate)
+{
+       WindowStatePerAgg   peraggstate;
+       int                                     wfuncno, numaggs;
+       int                                     i;
+       MemoryContext           oldContext;
+       ExprContext                *econtext;
+       TupleTableSlot     *first_peer_slot = winstate->first_peer_slot;
+       TupleTableSlot     *slot;
+       bool                            first;
+
+       numaggs = winstate->numaggs;
+       if (numaggs == 0)
+               return;                                 /* nothing to do */
+
+       /* final output execution is in ps_ExprContext */
+       econtext = winstate->ss.ps.ps_ExprContext;
+
+       /*
+        * We don't currently support explicitly-specified window frames.  That
+        * means that the window frame always includes all the rows in the
+        * partition preceding and including the current row, and all its
+        * peers. As a special case, if there's no ORDER BY, all rows are peers,
+        * so the window frame includes all rows in the partition.
+        *
+        * When there's peer rows, all rows in a peer group will have the same
+        * aggregate values.  The values will be calculated when current position
+        * reaches the first peer row, and on all the following peer rows we will
+        * just return the saved results.
+        *
+        * 'aggregatedupto' keeps track of the last row that has already been
+        * accumulated for the aggregates. When the current row has no peers,
+        * aggregatedupto will be the same as the current row after this
+        * function. If there are peer rows, all peers will be accumulated in one
+        * call of this function, and aggregatedupto will be ahead of the current
+        * position. If there's no ORDER BY, and thus all rows are peers, the
+        * first call will aggregate all rows in the partition.
+        *
+        * TODO: In the future, we could implement sliding frames by recalculating
+        * the aggregate whenever a row exits the frame. That would be pretty
+        * slow, though. For aggregates like SUM and COUNT we could implement a
+        * "negative transition function" that would be called for all the rows
+        * that exit the frame.
+        */
+
+       /*
+        * If we've already aggregated up through current row, reuse the
+        * saved result values
+        */
+       if (winstate->aggregatedupto > winstate->currentpos)
+       {
+               for (i = 0; i < numaggs; i++)
+               {
+                       peraggstate = &winstate->peragg[i];
+                       wfuncno = peraggstate->wfuncno;
+                       econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
+                       econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
+               }
+               return;
+       }
+
+       /* Initialize aggregates on first call for partition */
+       for (i = 0; i < numaggs; i++)
+       {
+               peraggstate = &winstate->peragg[i];
+               wfuncno = peraggstate->wfuncno;
+               if (!peraggstate->hasResult)
+                       initialize_windowaggregate(winstate,
+                                                                          &winstate->perfunc[wfuncno],
+                                                                          &winstate->peragg[i]);
+       }
+
+       /*
+        * If this is the first call for this partition, fetch the first row
+        * for comparing peer rows. On subsequent calls, we'll always read
+        * ahead until we reach the first non-peer row, and store that row in
+        * first_peer_slot, for use in the next call.
+        */
+       if (TupIsNull(first_peer_slot))
+       {
+               spool_tuples(winstate, winstate->aggregatedupto);
+               tuplestore_select_read_pointer(winstate->buffer, winstate->agg_ptr);
+               if (!tuplestore_gettupleslot(winstate->buffer, true, first_peer_slot))
+                       elog(ERROR, "unexpected end of tuplestore");
+       }
+
+       /*
+        * Advance until we reach the next non-peer row
+        */
+       first = true;
+       for (;;)
+       {
+               if (!first)
+               {
+                       /* Fetch the next row, and see if it's a peer */
+                       spool_tuples(winstate, winstate->aggregatedupto);
+                       tuplestore_select_read_pointer(winstate->buffer,
+                                                                                  winstate->agg_ptr);
+                       slot = winstate->temp_slot_1;
+                       if (!tuplestore_gettupleslot(winstate->buffer, true, slot))
+                               break;
+                       if (!are_peers(winstate, first_peer_slot, slot))
+                       {
+                               ExecCopySlot(first_peer_slot, slot);
+                               break;
+                       }
+               }
+               else
+               {
+                       /*
+                        * On first iteration, just accumulate the tuple saved from
+                        * last call
+                        */
+                       slot = first_peer_slot;
+                       first = false;
+               }
+
+               /* set tuple context for evaluation of aggregate arguments */
+               winstate->tmpcontext->ecxt_outertuple = slot;
+
+               for (i = 0; i < numaggs; i++)
+               {
+                       wfuncno = winstate->peragg[i].wfuncno;
+
+                       advance_windowaggregate(winstate,
+                                                                       &winstate->perfunc[wfuncno],
+                                                                       &winstate->peragg[i]);
+
+               }
+               /* Reset per-input-tuple context after each tuple */
+               ResetExprContext(winstate->tmpcontext);
+               winstate->aggregatedupto++;
+       }
+
+       /*
+        * finalize aggregates and fill result/isnull fields.
+        */
+       for (i = 0; i < numaggs; i++)
+       {
+               Datum      *result;
+               bool       *isnull;
+
+               peraggstate = &winstate->peragg[i];
+               wfuncno = peraggstate->wfuncno;
+               result = &econtext->ecxt_aggvalues[wfuncno];
+               isnull = &econtext->ecxt_aggnulls[wfuncno];
+               finalize_windowaggregate(winstate,
+                                                                &winstate->perfunc[wfuncno],
+                                                                peraggstate, result, isnull);
+
+               /*
+                * save the result for the next (non-shrinking frame) call.
+                */
+               if (!peraggstate->resulttypeByVal && !*isnull)
+               {
+                       /*
+                        * clear old resultValue in order not to leak memory.
+                        */
+                       if (peraggstate->hasResult &&
+                               (DatumGetPointer(peraggstate->resultValue) !=
+                                       DatumGetPointer(*result)) &&
+                               !peraggstate->resultValueIsNull)
+                               pfree(DatumGetPointer(peraggstate->resultValue));
+
+                       /*
+                        * If pass-by-ref, copy it into our global context.
+                        */
+                       oldContext = MemoryContextSwitchTo(winstate->wincontext);
+                       peraggstate->resultValue = datumCopy(*result,
+                                                                                                peraggstate->resulttypeByVal,
+                                                                                                peraggstate->resulttypeLen);
+                       MemoryContextSwitchTo(oldContext);
+               }
+               else
+               {
+                       peraggstate->resultValue = *result;
+               }
+               peraggstate->resultValueIsNull = *isnull;
+               peraggstate->hasResult = true;
+       }
+}
+
+/*
+ * eval_windowfunction
+ *
+ * Arguments of window functions are not evaluated here, because a window
+ * function can need random access to arbitrary rows in the partition.
+ * The window function uses the special WinGetFuncArgInPartition and
+ * WinGetFuncArgInFrame functions to evaluate the arguments for the rows
+ * it wants.
+ */
+static void
+eval_windowfunction(WindowAggState *winstate, WindowStatePerFunc perfuncstate,
+                                       Datum *result, bool *isnull)
+{
+       FunctionCallInfoData fcinfo;
+       MemoryContext           oldContext;
+
+       oldContext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_tuple_memory);
+
+       /*
+        * We don't pass any normal arguments to a window function, but we do
+        * pass it the number of arguments, in order to permit window function
+        * implementations to support varying numbers of arguments.  The real
+        * info goes through the WindowObject, which is passed via fcinfo->context.
+        */
+       InitFunctionCallInfoData(fcinfo, &(perfuncstate->flinfo),
+                                                        perfuncstate->numArguments,
+                                                        (void *) perfuncstate->winobj, NULL);
+       /* Just in case, make all the regular argument slots be null */
+       memset(fcinfo.argnull, true, perfuncstate->numArguments);
+
+       *result = FunctionCallInvoke(&fcinfo);
+       *isnull = fcinfo.isnull;
+
+       /*
+        * Make sure pass-by-ref data is allocated in the appropriate context.
+        * (We need this in case the function returns a pointer into some
+        * short-lived tuple, as is entirely possible.)
+        */
+       if (!perfuncstate->resulttypeByVal && !fcinfo.isnull &&
+               !MemoryContextContains(CurrentMemoryContext,
+                                                          DatumGetPointer(*result)))
+               *result = datumCopy(*result,
+                                                       perfuncstate->resulttypeByVal,
+                                                       perfuncstate->resulttypeLen);
+
+       MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * begin_partition
+ * Start buffering rows of the next partition.
+ */
+static void
+begin_partition(WindowAggState *winstate)
+{
+       PlanState          *outerPlan = outerPlanState(winstate);
+       int                             numfuncs = winstate->numfuncs;
+       int                             i;
+
+       winstate->partition_spooled = false;
+       winstate->spooled_rows = 0;
+       winstate->currentpos = 0;
+       winstate->frametailpos = -1;
+       winstate->aggregatedupto = 0;
+
+       /*
+        * If this is the very first partition, we need to fetch the first
+        * input row to store in it.
+        */
+       if (TupIsNull(winstate->first_part_slot))
+       {
+               TupleTableSlot *outerslot = ExecProcNode(outerPlan);
+
+               if (!TupIsNull(outerslot))
+                        ExecCopySlot(winstate->first_part_slot, outerslot);
+               else
+               {
+                       /* outer plan is empty, so we have nothing to do */
+                       winstate->partition_spooled = true;
+                       winstate->more_partitions = false;
+                       return;
+               }
+       }
+
+       /* Create new tuplestore for this partition */
+       winstate->buffer = tuplestore_begin_heap(false, false, work_mem);
+
+       /*
+        * Set up read pointers for the tuplestore.  The current and agg pointers
+        * don't need BACKWARD capability, but the per-window-function read
+        * pointers do.
+        */
+       winstate->current_ptr = 0;      /* read pointer 0 is pre-allocated */
+
+       /* reset default REWIND capability bit for current ptr */
+       tuplestore_set_eflags(winstate->buffer, 0);
+
+       /* create a read pointer for aggregates, if needed */
+       if (winstate->numaggs > 0)
+               winstate->agg_ptr = tuplestore_alloc_read_pointer(winstate->buffer, 0);
+
+       /* create mark and read pointers for each real window function */
+       for (i = 0; i < numfuncs; i++)
+       {
+               WindowStatePerFunc      perfuncstate = &(winstate->perfunc[i]);
+
+               if (!perfuncstate->plain_agg)
+               {
+                       WindowObject    winobj = perfuncstate->winobj;
+
+                       winobj->markptr = tuplestore_alloc_read_pointer(winstate->buffer,
+                                                                                                                       0);
+                       winobj->readptr = tuplestore_alloc_read_pointer(winstate->buffer,
+                                                                                                                       EXEC_FLAG_BACKWARD);
+                       winobj->markpos = -1;
+                       winobj->seekpos = -1;
+               }
+       }
+
+       /*
+        * Store the first tuple into the tuplestore (it's always available now;
+        * we either read it above, or saved it at the end of previous partition)
+        */
+       tuplestore_puttupleslot(winstate->buffer, winstate->first_part_slot);
+       winstate->spooled_rows++;
+}
+
+/*
+ * Read tuples from the outer node, up to position 'pos', and store them
+ * into the tuplestore. If pos is -1, reads the whole partition.
+ */
+static void
+spool_tuples(WindowAggState *winstate, int64 pos)
+{
+       WindowAgg          *node = (WindowAgg *) winstate->ss.ps.plan;
+       PlanState          *outerPlan;
+       TupleTableSlot *outerslot;
+       MemoryContext oldcontext;
+
+       if (!winstate->buffer)
+               return;                                 /* just a safety check */
+       if (winstate->partition_spooled)
+               return;                                 /* whole partition done already */
+
+       /*
+        * If the tuplestore has spilled to disk, alternate reading and writing
+        * becomes quite expensive due to frequent buffer flushes.  It's cheaper
+        * to force the entire partition to get spooled in one go.
+        *
+        * XXX this is a horrid kluge --- it'd be better to fix the performance
+        * problem inside tuplestore.  FIXME
+        */
+       if (!tuplestore_in_memory(winstate->buffer))
+               pos = -1;
+
+       outerPlan = outerPlanState(winstate);
+
+       /* Must be in query context to call outerplan or touch tuplestore */
+       oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
+
+       while (winstate->spooled_rows <= pos || pos == -1)
+       {
+               outerslot = ExecProcNode(outerPlan);
+               if (TupIsNull(outerslot))
+               {
+                       /* reached the end of the last partition */
+                       winstate->partition_spooled = true;
+                       winstate->more_partitions = false;
+                       break;
+               }
+
+               if (node->partNumCols > 0)
+               {
+                       /* Check if this tuple still belongs to the current partition */
+                       if (!execTuplesMatch(winstate->first_part_slot,
+                                                                outerslot,
+                                                                node->partNumCols, node->partColIdx,
+                                                                winstate->partEqfunctions,
+                                                                winstate->tmpcontext->ecxt_per_tuple_memory))
+                       {
+                               /*
+                                * end of partition; copy the tuple for the next cycle.
+                                */
+                               ExecCopySlot(winstate->first_part_slot, outerslot);
+                               winstate->partition_spooled = true;
+                               winstate->more_partitions = true;
+                               break;
+                       }
+               }
+
+               /* Still in partition, so save it into the tuplestore */
+               tuplestore_puttupleslot(winstate->buffer, outerslot);
+               winstate->spooled_rows++;
+       }
+
+       MemoryContextSwitchTo(oldcontext);
+}
+
+/*
+ * release_partition
+ * clear information kept within a partition, including
+ * tuplestore and aggregate results.
+ */
+static void
+release_partition(WindowAggState *winstate)
+{
+       int                                     i;
+
+       for (i = 0; i < winstate->numfuncs; i++)
+       {
+               WindowStatePerFunc              perfuncstate = &(winstate->perfunc[i]);
+
+               /* Release any partition-local state of this window function */
+               if (perfuncstate->winobj)
+                       perfuncstate->winobj->localmem = NULL;
+
+               /* Reset agg result cache */
+               if (perfuncstate->plain_agg)
+               {
+                       int             aggno = perfuncstate->aggno;
+                       WindowStatePerAggData *peraggstate = &winstate->peragg[aggno];
+
+                       peraggstate->resultValueIsNull = true;
+                       peraggstate->hasResult = false;
+               }
+       }
+
+       /*
+        * Release all partition-local memory (in particular, any partition-local
+        * state or aggregate temp data that we might have trashed our pointers
+        * to in the above loop).  We don't rely on retail pfree because some
+        * aggregates might have allocated data we don't have direct pointers to.
+        */
+       MemoryContextResetAndDeleteChildren(winstate->wincontext);
+
+       /* Ensure eval_windowaggregates will see next call as partition start */
+       ExecClearTuple(winstate->first_peer_slot);
+
+       if (winstate->buffer)
+               tuplestore_end(winstate->buffer);
+       winstate->buffer = NULL;
+       winstate->partition_spooled = false;
+}
+
+
+/* -----------------
+ * ExecWindowAgg
+ *
+ *     ExecWindowAgg receives tuples from its outer subplan and
+ *     stores them into a tuplestore, then processes window functions.
+ *     This node doesn't reduce nor qualify any row so the number of
+ *     returned rows is exactly the same as its outer subplan's result
+ *     (ignoring the case of SRFs in the targetlist, that is).
+ * -----------------
+ */
+TupleTableSlot *
+ExecWindowAgg(WindowAggState *winstate)
+{
+       TupleTableSlot *result;
+       ExprDoneCond    isDone;
+       ExprContext        *econtext;
+       int                             i;
+       int                             numfuncs;
+
+       if (winstate->all_done)
+               return NULL;
+
+       /*
+        * Check to see if we're still projecting out tuples from a previous output
+        * tuple (because there is a function-returning-set in the projection
+        * expressions).  If so, try to project another one.
+        */
+       if (winstate->ss.ps.ps_TupFromTlist)
+       {
+               TupleTableSlot *result;
+               ExprDoneCond isDone;
+
+               result = ExecProject(winstate->ss.ps.ps_ProjInfo, &isDone);
+               if (isDone == ExprMultipleResult)
+                       return result;
+               /* Done with that source tuple... */
+               winstate->ss.ps.ps_TupFromTlist = false;
+       }
+
+restart:
+       if (winstate->buffer == NULL)
+       {
+               /* Initialize for first partition and set current row = 0 */
+               begin_partition(winstate);
+       }
+       else
+       {
+               /* Advance current row within partition */
+               winstate->currentpos++;
+       }
+
+       /*
+        * Spool all tuples up to and including the current row, if we haven't
+        * already
+        */
+       spool_tuples(winstate, winstate->currentpos);
+
+       /* Move to the next partition if we reached the end of this partition */
+       if (winstate->partition_spooled &&
+               winstate->currentpos >= winstate->spooled_rows)
+       {
+               release_partition(winstate);
+
+               if (winstate->more_partitions)
+               {
+                       begin_partition(winstate);
+                       Assert(winstate->spooled_rows > 0);
+               }
+               else
+               {
+                       winstate->all_done = true;
+                       return NULL;
+               }
+       }
+
+       /* final output execution is in ps_ExprContext */
+       econtext = winstate->ss.ps.ps_ExprContext;
+
+       /* Clear the per-output-tuple context for current row */
+       ResetExprContext(econtext);
+
+       /*
+        * Read the current row from the tuplestore, and save in ScanTupleSlot
+        * for possible use by WinGetFuncArgCurrent or the final projection step.
+        * (We can't rely on the outerplan's output slot because we may have to
+        * read beyond the current row.)
+        *
+        * Current row must be in the tuplestore, since we spooled it above.
+        */
+       tuplestore_select_read_pointer(winstate->buffer, winstate->current_ptr);
+       if (!tuplestore_gettupleslot(winstate->buffer, true,
+                                                                winstate->ss.ss_ScanTupleSlot))
+               elog(ERROR, "unexpected end of tuplestore");
+
+       /*
+        * Evaluate true window functions
+        */
+       numfuncs = winstate->numfuncs;
+       for (i = 0; i < numfuncs; i++)
+       {
+               WindowStatePerFunc      perfuncstate = &(winstate->perfunc[i]);
+
+               if (perfuncstate->plain_agg)
+                       continue;
+               eval_windowfunction(winstate, perfuncstate,
+                                                       &(econtext->ecxt_aggvalues[perfuncstate->wfuncstate->wfuncno]),
+                                                       &(econtext->ecxt_aggnulls[perfuncstate->wfuncstate->wfuncno]));
+       }
+
+       /*
+        * Evaluate aggregates
+        */
+       if (winstate->numaggs > 0)
+               eval_windowaggregates(winstate);
+
+       /*
+        * Truncate any no-longer-needed rows from the tuplestore.
+        */
+       tuplestore_trim(winstate->buffer);
+
+       /*
+        * Form and return a projection tuple using the windowfunc results
+        * and the current row.  Setting ecxt_outertuple arranges that any
+        * Vars will be evaluated with respect to that row.
+        */
+       econtext->ecxt_outertuple = winstate->ss.ss_ScanTupleSlot;
+       result = ExecProject(winstate->ss.ps.ps_ProjInfo, &isDone);
+
+       if (isDone == ExprEndResult)
+       {
+               /* SRF in tlist returned no rows, so advance to next input tuple */
+               goto restart;
+       }
+
+       winstate->ss.ps.ps_TupFromTlist =
+               (isDone == ExprMultipleResult);
+       return result;
+}
+
+/* -----------------
+ * ExecInitWindowAgg
+ *
+ *     Creates the run-time information for the WindowAgg node produced by the
+ *     planner and initializes its outer subtree
+ * -----------------
+ */
+WindowAggState *
+ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
+{
+       WindowAggState *winstate;
+       Plan       *outerPlan;
+       ExprContext *econtext;
+       ExprContext *tmpcontext;
+       WindowStatePerFunc  perfunc;
+       WindowStatePerAgg   peragg;
+       int                     numfuncs,
+                               wfuncno,
+                               numaggs,
+                               aggno;
+       ListCell   *l;
+
+       /* check for unsupported flags */
+       Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
+
+       /*
+        * create state structure
+        */
+       winstate = makeNode(WindowAggState);
+       winstate->ss.ps.plan = (Plan *) node;
+       winstate->ss.ps.state = estate;
+
+       /*
+        * Create expression contexts.  We need two, one for per-input-tuple
+        * processing and one for per-output-tuple processing.  We cheat a little
+        * by using ExecAssignExprContext() to build both.
+        */
+       ExecAssignExprContext(estate, &winstate->ss.ps);
+       tmpcontext = winstate->ss.ps.ps_ExprContext;
+       winstate->tmpcontext = tmpcontext;
+       ExecAssignExprContext(estate, &winstate->ss.ps);
+
+       /* Create long-lived context for storage of aggregate transvalues etc */
+       winstate->wincontext =
+               AllocSetContextCreate(CurrentMemoryContext,
+                                                         "WindowAggContext",
+                                                         ALLOCSET_DEFAULT_MINSIZE,
+                                                         ALLOCSET_DEFAULT_INITSIZE,
+                                                         ALLOCSET_DEFAULT_MAXSIZE);
+
+#define WINDOWAGG_NSLOTS 6
+
+       /*
+        * tuple table initialization
+        */
+       ExecInitScanTupleSlot(estate, &winstate->ss);
+       ExecInitResultTupleSlot(estate, &winstate->ss.ps);
+       winstate->first_part_slot = ExecInitExtraTupleSlot(estate);
+       winstate->first_peer_slot = ExecInitExtraTupleSlot(estate);
+       winstate->temp_slot_1 = ExecInitExtraTupleSlot(estate);
+       winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate);
+
+       winstate->ss.ps.targetlist = (List *)
+               ExecInitExpr((Expr *) node->plan.targetlist,
+                                        (PlanState *) winstate);
+
+       /*
+        * WindowAgg nodes never have quals, since they can only occur at the
+        * logical top level of a query (ie, after any WHERE or HAVING filters)
+        */
+       Assert(node->plan.qual == NIL);
+       winstate->ss.ps.qual = NIL;
+
+       /*
+        * initialize child nodes
+        */
+       outerPlan = outerPlan(node);
+       outerPlanState(winstate) = ExecInitNode(outerPlan, estate, eflags);
+
+       /*
+        * initialize source tuple type (which is also the tuple type that we'll
+        * store in the tuplestore and use in all our working slots).
+        */
+       ExecAssignScanTypeFromOuterPlan(&winstate->ss);
+
+       ExecSetSlotDescriptor(winstate->first_part_slot,
+                                                 winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor);
+       ExecSetSlotDescriptor(winstate->first_peer_slot,
+                                                 winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor);
+       ExecSetSlotDescriptor(winstate->temp_slot_1,
+                                                 winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor);
+       ExecSetSlotDescriptor(winstate->temp_slot_2,
+                                                 winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor);
+
+       /*
+        * Initialize result tuple type and projection info.
+        */
+       ExecAssignResultTypeFromTL(&winstate->ss.ps);
+       ExecAssignProjectionInfo(&winstate->ss.ps, NULL);
+
+       winstate->ss.ps.ps_TupFromTlist = false;
+
+       /* Set up data for comparing tuples */
+       if (node->partNumCols > 0)
+               winstate->partEqfunctions = execTuplesMatchPrepare(node->partNumCols,
+                                                                                                                 node->partOperators);
+       if (node->ordNumCols > 0)
+               winstate->ordEqfunctions = execTuplesMatchPrepare(node->ordNumCols,
+                                                                                                                 node->ordOperators);
+
+       /*
+        * WindowAgg nodes use aggvalues and aggnulls as well as Agg nodes.
+        */
+       numfuncs = winstate->numfuncs;
+       numaggs = winstate->numaggs;
+       econtext = winstate->ss.ps.ps_ExprContext;
+       econtext->ecxt_aggvalues = (Datum *) palloc0(sizeof(Datum) * numfuncs);
+       econtext->ecxt_aggnulls = (bool *) palloc0(sizeof(bool) * numfuncs);
+
+       /*
+        * allocate per-wfunc/per-agg state information.
+        */
+       perfunc = (WindowStatePerFunc) palloc0(sizeof(WindowStatePerFuncData) * numfuncs);
+       peragg = (WindowStatePerAgg) palloc0(sizeof(WindowStatePerAggData) * numaggs);
+       winstate->perfunc = perfunc;
+       winstate->peragg = peragg;
+
+       wfuncno = -1;
+       aggno = -1;
+       foreach(l, winstate->funcs)
+       {
+               WindowFuncExprState        *wfuncstate = (WindowFuncExprState *) lfirst(l);
+               WindowFunc                         *wfunc = (WindowFunc *) wfuncstate->xprstate.expr;
+               WindowStatePerFunc perfuncstate;
+               AclResult       aclresult;
+               int                     i;
+
+               /* Look for a previous duplicate window function */
+               for (i = 0; i <= wfuncno; i++)
+               {
+                       if (equal(wfunc, perfunc[i].wfunc) &&
+                               !contain_volatile_functions((Node *) wfunc))
+                               break;
+               }
+               if (i <= wfuncno)
+               {
+                       /* Found a match to an existing entry, so just mark it */
+                       wfuncstate->wfuncno = i;
+                       continue;
+               }
+
+               /* Nope, so assign a new PerAgg record */
+               perfuncstate = &perfunc[++wfuncno];
+
+               /* Mark WindowFunc state node with assigned index in the result array */
+               wfuncstate->wfuncno = wfuncno;
+
+               /* Check permission to call window function */
+               aclresult = pg_proc_aclcheck(wfunc->winfnoid, GetUserId(),
+                                                                        ACL_EXECUTE);
+               if (aclresult != ACLCHECK_OK)
+                       aclcheck_error(aclresult, ACL_KIND_PROC,
+                                                  get_func_name(wfunc->winfnoid));
+
+               /* Fill in the perfuncstate data */
+               perfuncstate->wfuncstate = wfuncstate;
+               perfuncstate->wfunc = wfunc;
+               perfuncstate->numArguments = list_length(wfuncstate->args);
+
+               fmgr_info_cxt(wfunc->winfnoid, &perfuncstate->flinfo,
+                                         tmpcontext->ecxt_per_query_memory);
+               perfuncstate->flinfo.fn_expr = (Node *) wfunc;
+               get_typlenbyval(wfunc->wintype,
+                                               &perfuncstate->resulttypeLen,
+                                               &perfuncstate->resulttypeByVal);
+
+               /*
+                * If it's really just a plain aggregate function,
+                * we'll emulate the Agg environment for it.
+                */
+               perfuncstate->plain_agg = wfunc->winagg;
+               if (wfunc->winagg)
+               {
+                       WindowStatePerAgg       peraggstate;
+
+                       perfuncstate->aggno = ++aggno;
+                       peraggstate = &winstate->peragg[aggno];
+                       initialize_peragg(winstate, wfunc, peraggstate);
+                       peraggstate->wfuncno = wfuncno;
+               }
+               else
+               {
+                       WindowObject winobj = makeNode(WindowObjectData);
+
+                       winobj->winstate = winstate;
+                       winobj->argstates = wfuncstate->args;
+                       winobj->localmem = NULL;
+                       perfuncstate->winobj = winobj;
+               }
+       }
+
+       /* Update numfuncs, numaggs to match number of unique functions found */
+       winstate->numfuncs = wfuncno + 1;
+       winstate->numaggs = aggno + 1;
+
+       winstate->partition_spooled = false;
+       winstate->more_partitions = false;
+
+       return winstate;
+}
+
+/* -----------------
+ * ExecCountSlotsWindowAgg
+ * -----------------
+ */
+int
+ExecCountSlotsWindowAgg(WindowAgg *node)
+{
+       return ExecCountSlotsNode(outerPlan(node)) +
+               ExecCountSlotsNode(innerPlan(node)) +
+               WINDOWAGG_NSLOTS;
+}
+
+/* -----------------
+ * ExecEndWindowAgg
+ * -----------------
+ */
+void
+ExecEndWindowAgg(WindowAggState *node)
+{
+       PlanState  *outerPlan;
+
+       release_partition(node);
+
+       pfree(node->perfunc);
+       pfree(node->peragg);
+
+       ExecClearTuple(node->ss.ss_ScanTupleSlot);
+       ExecClearTuple(node->first_part_slot);
+       ExecClearTuple(node->first_peer_slot);
+       ExecClearTuple(node->temp_slot_1);
+       ExecClearTuple(node->temp_slot_2);
+
+       /*
+        * Free both the expr contexts.
+        */
+       ExecFreeExprContext(&node->ss.ps);
+       node->ss.ps.ps_ExprContext = node->tmpcontext;
+       ExecFreeExprContext(&node->ss.ps);
+
+       MemoryContextDelete(node->wincontext);
+
+       outerPlan = outerPlanState(node);
+       ExecEndNode(outerPlan);
+}
+
+/* -----------------
+ * ExecRescanWindowAgg
+ * -----------------
+ */
+void
+ExecReScanWindowAgg(WindowAggState *node, ExprContext *exprCtxt)
+{
+       ExprContext        *econtext = node->ss.ps.ps_ExprContext;
+
+       node->all_done = false;
+
+       node->ss.ps.ps_TupFromTlist = false;
+
+       /* release tuplestore et al */
+       release_partition(node);
+
+       /* release all temp tuples, but especially first_part_slot */
+       ExecClearTuple(node->ss.ss_ScanTupleSlot);
+       ExecClearTuple(node->first_part_slot);
+       ExecClearTuple(node->first_peer_slot);
+       ExecClearTuple(node->temp_slot_1);
+       ExecClearTuple(node->temp_slot_2);
+
+       /* Forget current wfunc values */
+       MemSet(econtext->ecxt_aggvalues, 0, sizeof(Datum) * node->numfuncs);
+       MemSet(econtext->ecxt_aggnulls, 0, sizeof(bool) * node->numfuncs);
+
+       /*
+        * if chgParam of subnode is not null then plan will be re-scanned by
+        * first ExecProcNode.
+        */
+       if (((PlanState *) node)->lefttree->chgParam == NULL)
+               ExecReScan(((PlanState *) node)->lefttree, exprCtxt);
+}
+
+/*
+ * initialize_peragg
+ *
+ * Almost same as in nodeAgg.c, except we don't support DISTINCT currently.
+ */
+static WindowStatePerAggData *
+initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
+                                 WindowStatePerAgg peraggstate)
+{
+       Oid                     inputTypes[FUNC_MAX_ARGS];
+       int                     numArguments;
+       HeapTuple       aggTuple;
+       Form_pg_aggregate aggform;
+       Oid                     aggtranstype;
+       AclResult       aclresult;
+       Oid                     transfn_oid,
+                               finalfn_oid;
+       Expr       *transfnexpr,
+                          *finalfnexpr;
+       Datum           textInitVal;
+       int                     i;
+       ListCell   *lc;
+
+       numArguments = list_length(wfunc->args);
+
+       i = 0;
+       foreach(lc, wfunc->args)
+       {
+               inputTypes[i++] = exprType((Node *) lfirst(lc));
+       }
+
+       aggTuple = SearchSysCache(AGGFNOID,
+                                                         ObjectIdGetDatum(wfunc->winfnoid),
+                                                         0, 0, 0);
+       if (!HeapTupleIsValid(aggTuple))
+               elog(ERROR, "cache lookup failed for aggregate %u",
+                        wfunc->winfnoid);
+       aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
+
+       /*
+        * ExecInitWindowAgg already checked permission to call aggregate function
+        * ... but we still need to check the component functions
+        */
+
+       peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
+       peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
+
+       /* Check that aggregate owner has permission to call component fns */
+       {
+               HeapTuple       procTuple;
+               Oid                     aggOwner;
+
+               procTuple = SearchSysCache(PROCOID,
+                                                                  ObjectIdGetDatum(wfunc->winfnoid),
+                                                                  0, 0, 0);
+               if (!HeapTupleIsValid(procTuple))
+                       elog(ERROR, "cache lookup failed for function %u",
+                                wfunc->winfnoid);
+               aggOwner = ((Form_pg_proc) GETSTRUCT(procTuple))->proowner;
+               ReleaseSysCache(procTuple);
+
+               aclresult = pg_proc_aclcheck(transfn_oid, aggOwner,
+                                                                        ACL_EXECUTE);
+               if (aclresult != ACLCHECK_OK)
+                       aclcheck_error(aclresult, ACL_KIND_PROC,
+                                                  get_func_name(transfn_oid));
+               if (OidIsValid(finalfn_oid))
+               {
+                       aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner,
+                                                                                ACL_EXECUTE);
+                       if (aclresult != ACLCHECK_OK)
+                               aclcheck_error(aclresult, ACL_KIND_PROC,
+                                                          get_func_name(finalfn_oid));
+               }
+       }
+
+       /* resolve actual type of transition state, if polymorphic */
+       aggtranstype = aggform->aggtranstype;
+       if (IsPolymorphicType(aggtranstype))
+       {
+               /* have to fetch the agg's declared input types... */
+               Oid                *declaredArgTypes;
+               int                     agg_nargs;
+
+               get_func_signature(wfunc->winfnoid,
+                                                  &declaredArgTypes, &agg_nargs);
+               Assert(agg_nargs == numArguments);
+               aggtranstype = enforce_generic_type_consistency(inputTypes,
+                                                                                                               declaredArgTypes,
+                                                                                                               agg_nargs,
+                                                                                                               aggtranstype,
+                                                                                                               false);
+               pfree(declaredArgTypes);
+       }
+
+       /* build expression trees using actual argument & result types */
+       build_aggregate_fnexprs(inputTypes,
+                                                       numArguments,
+                                                       aggtranstype,
+                                                       wfunc->wintype,
+                                                       transfn_oid,
+                                                       finalfn_oid,
+                                                       &transfnexpr,
+                                                       &finalfnexpr);
+
+       fmgr_info(transfn_oid, &peraggstate->transfn);
+       peraggstate->transfn.fn_expr = (Node *) transfnexpr;
+
+       if (OidIsValid(finalfn_oid))
+       {
+               fmgr_info(finalfn_oid, &peraggstate->finalfn);
+               peraggstate->finalfn.fn_expr = (Node *) finalfnexpr;
+       }
+
+       get_typlenbyval(wfunc->wintype,
+                                       &peraggstate->resulttypeLen,
+                                       &peraggstate->resulttypeByVal);
+       get_typlenbyval(aggtranstype,
+                                       &peraggstate->transtypeLen,
+                                       &peraggstate->transtypeByVal);
+
+       /*
+        * initval is potentially null, so don't try to access it as a struct
+        * field. Must do it the hard way with SysCacheGetAttr.
+        */
+       textInitVal = SysCacheGetAttr(AGGFNOID, aggTuple,
+                                                                 Anum_pg_aggregate_agginitval,
+                                                                 &peraggstate->initValueIsNull);
+
+       if (peraggstate->initValueIsNull)
+               peraggstate->initValue = (Datum) 0;
+       else
+               peraggstate->initValue = GetAggInitVal(textInitVal,
+                                                                                          aggtranstype);
+
+       /*
+        * If the transfn is strict and the initval is NULL, make sure input
+        * type and transtype are the same (or at least binary-compatible), so
+        * that it's OK to use the first input value as the initial
+        * transValue.  This should have been checked at agg definition time,
+        * but just in case...
+        */
+       if (peraggstate->transfn.fn_strict && peraggstate->initValueIsNull)
+       {
+               if (numArguments < 1 ||
+                       !IsBinaryCoercible(inputTypes[0], aggtranstype))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+                                        errmsg("aggregate %u needs to have compatible input type and transition type",
+                                                       wfunc->winfnoid)));
+       }
+
+       ReleaseSysCache(aggTuple);
+
+       return peraggstate;
+}
+
+static Datum
+GetAggInitVal(Datum textInitVal, Oid transtype)
+{
+       Oid                     typinput,
+                               typioparam;
+       char       *strInitVal;
+       Datum           initVal;
+
+       getTypeInputInfo(transtype, &typinput, &typioparam);
+       strInitVal = TextDatumGetCString(textInitVal);
+       initVal = OidInputFunctionCall(typinput, strInitVal,
+                                                                  typioparam, -1);
+       pfree(strInitVal);
+       return initVal;
+}
+
+/*
+ * are_peers
+ * compare two rows to see if they are equal according to the ORDER BY clause
+ */
+static bool
+are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
+                 TupleTableSlot *slot2)
+{
+       WindowAgg  *node = (WindowAgg *) winstate->ss.ps.plan;
+
+       /* If no ORDER BY, all rows are peers with each other */
+       if (node->ordNumCols == 0)
+               return true;
+
+       return execTuplesMatch(slot1, slot2,
+                                                  node->ordNumCols, node->ordColIdx,
+                                                  winstate->ordEqfunctions,
+                                                  winstate->tmpcontext->ecxt_per_tuple_memory);
+}
+
+/*
+ * window_gettupleslot
+ *     Fetch the pos'th tuple of the current partition into the slot
+ *
+ * Returns true if successful, false if no such row
+ */
+static bool
+window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
+{
+       WindowAggState *winstate = winobj->winstate;
+       MemoryContext oldcontext;
+
+       /* Don't allow passing -1 to spool_tuples here */
+       if (pos < 0)
+               return false;
+
+       /* If necessary, fetch the tuple into the spool */
+       spool_tuples(winstate, pos);
+
+       if (pos >= winstate->spooled_rows)
+               return false;
+
+       if (pos < winobj->markpos)
+               elog(ERROR, "cannot fetch row before WindowObject's mark position");
+
+       oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
+
+       tuplestore_select_read_pointer(winstate->buffer, winobj->readptr);
+
+       /*
+        * There's no API to refetch the tuple at the current position. We
+        * have to move one tuple forward, and then one backward.  (We don't
+        * do it the other way because we might try to fetch the row before
+        * our mark, which isn't allowed.)
+        */
+       if (winobj->seekpos == pos)
+       {
+               tuplestore_advance(winstate->buffer, true);
+               winobj->seekpos++;
+       }
+
+       while (winobj->seekpos > pos)
+       {
+               if (!tuplestore_gettupleslot(winstate->buffer, false, slot))
+                       elog(ERROR, "unexpected end of tuplestore");
+               winobj->seekpos--;
+       }
+
+       while (winobj->seekpos < pos)
+       {
+               if (!tuplestore_gettupleslot(winstate->buffer, true, slot))
+                       elog(ERROR, "unexpected end of tuplestore");
+               winobj->seekpos++;
+       }
+
+       MemoryContextSwitchTo(oldcontext);
+
+       return true;
+}
+
+
+/***********************************************************************
+ * API exposed to window functions
+ ***********************************************************************/
+
+
+/*
+ * WinGetPartitionLocalMemory
+ *             Get working memory that lives till end of partition processing
+ *
+ * On first call within a given partition, this allocates and zeroes the
+ * requested amount of space.  Subsequent calls just return the same chunk.
+ *
+ * Memory obtained this way is normally used to hold state that should be
+ * automatically reset for each new partition.  If a window function wants
+ * to hold state across the whole query, fcinfo->fn_extra can be used in the
+ * usual way for that.
+ */
+void *
+WinGetPartitionLocalMemory(WindowObject winobj, Size sz)
+{
+       Assert(WindowObjectIsValid(winobj));
+       if (winobj->localmem == NULL)
+               winobj->localmem = MemoryContextAllocZero(winobj->winstate->wincontext,
+                                                                                                 sz);
+       return winobj->localmem;
+}
+
+/*
+ * WinGetCurrentPosition
+ *             Return the current row's position (counting from 0) within the current
+ *             partition.
+ */
+int64
+WinGetCurrentPosition(WindowObject winobj)
+{
+       Assert(WindowObjectIsValid(winobj));
+       return winobj->winstate->currentpos;
+}
+
+/*
+ * WinGetPartitionRowCount
+ *             Return total number of rows contained in the current partition.
+ *
+ * Note: this is a relatively expensive operation because it forces the
+ * whole partition to be "spooled" into the tuplestore at once.  Once
+ * executed, however, additional calls within the same partition are cheap.
+ */
+int64
+WinGetPartitionRowCount(WindowObject winobj)
+{
+       Assert(WindowObjectIsValid(winobj));
+       spool_tuples(winobj->winstate, -1);
+       return winobj->winstate->spooled_rows;
+}
+
+/*
+ * WinSetMarkPosition
+ *             Set the "mark" position for the window object, which is the oldest row
+ *             number (counting from 0) it is allowed to fetch during all subsequent
+ *             operations within the current partition.
+ *
+ * Window functions do not have to call this, but are encouraged to move the
+ * mark forward when possible to keep the tuplestore size down and prevent
+ * having to spill rows to disk.
+ */
+void
+WinSetMarkPosition(WindowObject winobj, int64 markpos)
+{
+       WindowAggState *winstate;
+
+       Assert(WindowObjectIsValid(winobj));
+       winstate = winobj->winstate;
+
+       if (markpos < winobj->markpos)
+               elog(ERROR, "cannot move WindowObject's mark position backward");
+       tuplestore_select_read_pointer(winstate->buffer, winobj->markptr);
+       while (markpos > winobj->markpos)
+       {
+               tuplestore_advance(winstate->buffer, true);
+               winobj->markpos++;
+       }
+       tuplestore_select_read_pointer(winstate->buffer, winobj->readptr);
+       while (markpos > winobj->seekpos)
+       {
+               tuplestore_advance(winstate->buffer, true);
+               winobj->seekpos++;
+       }
+}
+
+/*
+ * WinRowsArePeers
+ *             Compare two rows (specified by absolute position in window) to see
+ *             if they are equal according to the ORDER BY clause.
+ */
+bool
+WinRowsArePeers(WindowObject winobj, int64 pos1, int64 pos2)
+{
+       WindowAggState *winstate;
+       WindowAgg          *node;
+       TupleTableSlot *slot1;
+       TupleTableSlot *slot2;
+       bool                    res;
+
+       Assert(WindowObjectIsValid(winobj));
+
+       winstate = winobj->winstate;
+       node = (WindowAgg *) winstate->ss.ps.plan;
+
+       /* If no ORDER BY, all rows are peers; don't bother to fetch them */
+       if (node->ordNumCols == 0)
+               return true;
+
+       slot1 = winstate->temp_slot_1;
+       slot2 = winstate->temp_slot_2;
+
+       if (!window_gettupleslot(winobj, pos1, slot1))
+               elog(ERROR, "specified position is out of window: " INT64_FORMAT,
+                        pos1);
+       if (!window_gettupleslot(winobj, pos2, slot2))
+               elog(ERROR, "specified position is out of window: " INT64_FORMAT,
+                        pos2);
+
+       res = are_peers(winstate, slot1, slot2);
+
+       ExecClearTuple(slot1);
+       ExecClearTuple(slot2);
+
+       return res;
+}
+
+/*
+ * WinGetFuncArgInPartition
+ *             Evaluate a window function's argument expression on a specified
+ *             row of the partition.  The row is identified in lseek(2) style,
+ *             i.e. relative to the current, first, or last row.
+ *
+ * argno: argument number to evaluate (counted from 0)
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_CURRENT, WINDOW_SEEK_HEAD, or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found and set_mark is true, the mark is moved to
+ *             the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *             is out of partition (can pass NULL if caller doesn't care about this)
+ *
+ * Specifying a nonexistent row is not an error, it just causes a null result
+ * (plus setting *isout true, if isout isn't NULL).
+ */
+Datum
+WinGetFuncArgInPartition(WindowObject winobj, int argno,
+                                                int relpos, int seektype, bool set_mark,
+                                                bool *isnull, bool *isout)
+{
+       ExprContext *econtext;
+       TupleTableSlot *slot;
+       bool            gottuple;
+       int64           abs_pos;
+
+       Assert(WindowObjectIsValid(winobj));
+
+       econtext = winobj->winstate->ss.ps.ps_ExprContext;
+       slot = winobj->winstate->temp_slot_1;
+
+       switch (seektype)
+       {
+               case WINDOW_SEEK_CURRENT:
+                       abs_pos = winobj->winstate->currentpos + relpos;
+                       break;
+               case WINDOW_SEEK_HEAD:
+                       abs_pos = relpos;
+                       break;
+               case WINDOW_SEEK_TAIL:
+                       spool_tuples(winobj->winstate, -1);
+                       abs_pos = winobj->winstate->spooled_rows - 1 + relpos;
+                       break;
+               default:
+                       elog(ERROR, "unrecognized window seek type: %d", seektype);
+                       abs_pos = 0; /* keep compiler quiet */
+                       break;
+       }
+
+       if (abs_pos >= 0)
+               gottuple = window_gettupleslot(winobj, abs_pos, slot);
+       else
+               gottuple = false;
+
+       if (!gottuple)
+       {
+               if (isout)
+                       *isout = true;
+               *isnull = true;
+               return (Datum) 0;
+       }
+       else
+       {
+               if (isout)
+                       *isout = false;
+               if (set_mark)
+                       WinSetMarkPosition(winobj, abs_pos);
+               econtext->ecxt_outertuple = slot;
+               return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+                                                       econtext, isnull, NULL);
+       }
+}
+
+/*
+ * WinGetFuncArgInFrame
+ *             Evaluate a window function's argument expression on a specified
+ *             row of the window frame.  The row is identified in lseek(2) style,
+ *             i.e. relative to the current, first, or last row.
+ *
+ * argno: argument number to evaluate (counted from 0)
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_CURRENT, WINDOW_SEEK_HEAD, or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found and set_mark is true, the mark is moved to
+ *             the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *             is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Specifying a nonexistent row is not an error, it just causes a null result
+ * (plus setting *isout true, if isout isn't NULL).
+ */
+Datum
+WinGetFuncArgInFrame(WindowObject winobj, int argno,
+                                        int relpos, int seektype, bool set_mark,
+                                        bool *isnull, bool *isout)
+{
+       ExprContext *econtext;
+       TupleTableSlot *slot;
+       bool            gottuple;
+       int64           abs_pos;
+       int64           frametailpos;
+
+       Assert(WindowObjectIsValid(winobj));
+
+       /* if no ordering columns, partition and frame are the same thing */
+       if (((WindowAgg *) winobj->winstate->ss.ps.plan)->ordNumCols == 0)
+               return WinGetFuncArgInPartition(winobj, argno, relpos, seektype,
+                                                                               set_mark, isnull, isout);
+
+       econtext = winobj->winstate->ss.ps.ps_ExprContext;
+       slot = winobj->winstate->temp_slot_1;
+       frametailpos = winobj->winstate->frametailpos;
+
+       switch (seektype)
+       {
+               case WINDOW_SEEK_CURRENT:
+                       abs_pos = winobj->winstate->currentpos + relpos;
+                       break;
+               case WINDOW_SEEK_HEAD:
+                       abs_pos = relpos;
+                       break;
+               case WINDOW_SEEK_TAIL:
+                       /* abs_pos is calculated later */
+                       abs_pos = 0; /* keep compiler quiet */
+                       break;
+               default:
+                       elog(ERROR, "unrecognized window seek type: %d", seektype);
+                       abs_pos = 0; /* keep compiler quiet */
+                       break;
+       }
+
+       /*
+        * Seek for frame tail. If the tail position is before current,
+        * always check if the tail is after the current or not.
+        */
+       if (frametailpos <= winobj->winstate->currentpos)
+       {
+               int64 add = 1;
+
+               for (;;)
+               {
+                       spool_tuples(winobj->winstate, winobj->winstate->currentpos + add);
+                       if (winobj->winstate->spooled_rows > winobj->winstate->currentpos + add)
+                       {
+                               /*
+                                * When seektype is not TAIL, we may optimize not to
+                                * spool unnecessary tuples. In TAIL mode, we need to search
+                                * until we find a row that's definitely not a peer.
+                                */
+                               if (!WinRowsArePeers(winobj, winobj->winstate->currentpos,
+                                                                        winobj->winstate->currentpos + add) ||
+                                       (seektype != WINDOW_SEEK_TAIL &&
+                                        winobj->winstate->currentpos + add < abs_pos))
+                                       break;
+                               add++;
+                       }
+                       else
+                       {
+                               /*
+                                * If hit the partition end, the last row is the frame tail.
+                                */
+                               break;
+                       }
+               }
+               frametailpos = winobj->winstate->currentpos + add - 1;
+               winobj->winstate->frametailpos = frametailpos;
+       }
+
+       if (seektype == WINDOW_SEEK_TAIL)
+       {
+               abs_pos = frametailpos + relpos;
+       }
+
+       /*
+        * If there is an ORDER BY (we don't support other window frame
+        * specifications yet), the frame runs from first row of the partition
+        * to the last peer of the current row. Otherwise the frame is the
+        * whole partition.
+        */
+       if (abs_pos < 0 || abs_pos > frametailpos)
+               gottuple = false;
+       else
+               gottuple = window_gettupleslot(winobj, abs_pos, slot);
+
+       if (!gottuple)
+       {
+               if (isout)
+                       *isout = true;
+               *isnull = true;
+               return (Datum) 0;
+       }
+       else
+       {
+               if (isout)
+                       *isout = false;
+               if (set_mark)
+                       WinSetMarkPosition(winobj, abs_pos);
+               econtext->ecxt_outertuple = slot;
+               return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+                                                       econtext, isnull, NULL);
+       }
+}
+
+/*
+ * WinGetFuncArgCurrent
+ *             Evaluate a window function's argument expression on the current row.
+ *
+ * argno: argument number to evaluate (counted from 0)
+ * isnull: output argument, receives isnull status of result
+ *
+ * Note: this isn't quite equivalent to WinGetFuncArgInPartition or
+ * WinGetFuncArgInFrame targeting the current row, because it will succeed
+ * even if the WindowObject's mark has been set beyond the current row.
+ * This should generally be used for "ordinary" arguments of a window
+ * function, such as the offset argument of lead() or lag().
+ */
+Datum
+WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
+{
+       WindowAggState *winstate;
+       ExprContext *econtext;
+
+       Assert(WindowObjectIsValid(winobj));
+       winstate = winobj->winstate;
+
+       econtext = winstate->ss.ps.ps_ExprContext;
+
+       econtext->ecxt_outertuple = winstate->ss.ss_ScanTupleSlot;
+       return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+                                               econtext, isnull, NULL);
+}
index 86f555a03a617566e7c5aa1dcd24694b54a7ee44..412fd96e5bf52de6b87da70f30f962f897184995 100644 (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;
index e5e2bc4422644e1052dd2fd19328465113c4e7ff..e96c66152e8072859ef1cadd4e2569571f362f81 100644 (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;
index 7236360347c1477332b36fa28e2794c55e86a52a..0284ce4edca47df168ca82cf715dc66e73190c05 100644 (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;
index 2477a17cfa3b4e4fca40b7a426212ce15523c94c..f926f1314cd7ac30b04fd4e1ae998f0fd1ee2d4b 100644 (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;
index ed5b55fb57158be0e01b57b8c1b1b7624de2f448..7bcc8e8047dbc7e113747a793b3aade66d7c36d4 100644 (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))
index b0553894c248111b384cc7ee9f5a27359cda6e3f..17eebc67647f0ebd16c47ec964292fbacdacc2b8 100644 (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.
index 0b9c5819820274de0fc52a30487de106c7f6c05f..7f30dde869f31faa0c98c08fee27752364d94fac 100644 (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
index 3d35eb605d9d91967e32b0b50ce1789f1d236c2c..5f6d219a01af1d8198e79b9a93393bb26ba8547c 100644 (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;
index f5d4f41c032c7e0633366e51ffcd61a6f9ece74a..b53b5e1470ed12609f999b07ba2e816d8431cf07 100644 (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)
        {
index 8a6b2ad0345e7fab630bd0efb131b0714bd8381c..f0f17d5f95039e3bde31b50f60eee40dbd8fb827 100644 (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;
 
        /*
index 0a1d1d1559f8267bfb99c473f98b7d490675275c..a8ea043a6971201d32df55caaafbb365c32a9e50 100644 (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)
index 7f91309032aa751a7ca5b4a14857ab18a3b4d964..b4b578d5973bb49bacaf2d912b59622bd451560a 100644 (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;
-
-                               foreach(sl, sub_tlist)
-                               {
-                                       TargetEntry *lte = (TargetEntry *) lfirst(sl);
+                               te = tlist_member(groupexpr, sub_tlist);
+                       else
+                               te = NULL;
 
-                                       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");
+       }
+}
index 9bec109f6f572fc793b91cf93f4bf26f35e9c2d1..83447082f5b39cb6660c78c21b46c9b54877e726 100644 (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,
index c999fb6419c5c3b0fb9375ffdc71691e2a25b697..a38f8c09ae76dfd61ae6e29d549f49bb0590a2c1 100644 (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:
index e4d508523e15920968d4abb94668d622edac22a5..80a51d80786da8b0b72e56dde38e70919882aef1 100644 (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 ||
index bd7c05cc53d4fb695661857af33b16e2b8fdd19e..f3a49cf9dee7f80aa2910e12122d1876216dd737 100644 (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);
 
        /*
index 3c74831f4da0d265fe588f19dee365b92c12cf90..ee45f32abbb2703e7599fdbfd34f64778f9adefd 100644 (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 ||
index 968f4ae367ac58caf059d73950d6fc9a0454ccc0..aab3d032b12784b0fcbd4e79c6e2cd4821580981 100644 (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);
index cdac02b71db69399e00b4a63eefe0d2f9f481ad0..70688655cce18ac317faeafa2b51225f320fe493 100644 (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")));
 }
 
 /*
index 29eab503198ea226aa77d3e4bdad553b700950a7..59b7ada7b43690d426bccc92efd27fbf057230d3 100644 (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;
 }
index bf7b1f6ad2e710dd480b91428b099103549e24c8..c3ad852258ba3428d921046d658fd5ee85510c69 100644 (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},
index e2645462d57887ec693c6cce165c69930beabc7b..6dba470e39fadbcb710e5030d782eabd760d80e5 100644 (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 -
index 0e5fbfd28ac8e0a5e3006c31ff84354c3d1fbb17..df30361f0a531b59ac1650438bedef24c8d10a41 100644 (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 $
  *
  *-------------------------------------------------------------------------
  */
 #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;
+}
index 1bac7ca2fcea96a1a13006f031963aa892f86116..29e3a9be01723f5e499cbf6881026617a8171a4f 100644 (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)); */
index b5299d010a617ac75e3295ba64d485ea9743a3fc..c14970d45610a289f423dae204e207361f9549c4 100644 (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);
 }
index d0b74ff5d9678ee6f52a0d169904b898b5cd70f7..b48dd11495f99e89903bd605b47eba70f4ef8dbf 100644 (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)));
 
+               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("aggregates cannot return sets"),
+                                        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;
        }
index 0253cfe1593490544325e02f4d46ceee6f5e9c65..e7c43daf7f2e53ba0e8973575703dae114ec92ac 100644 (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 ||
index bb3a9142d6fa18921d7704a5474d9567993ac2d7..739f1b03a02ab6f0b016f2d11889fc770e0b5306 100644 (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
index 954e21af1816e2b3c770495b1f46c9787a480c41..2b6c01fc8f61c30bdd9757505179810e90cad794 100644 (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
index d7d28c9f159d3813d8df4c3be751f83b17a73fe1..69dfbbba3ba6d75ee69f72a922f932086fbfcf45 100644 (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
 
index 4c147f0021d284d815a85680c4b8ff8e6071ead4..1401b29359fdf375f9795641c7e4f69b2445f431 100644 (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);
 }
index 4580040d697491a21e57e75660611a4e1cc9cbd2..a0501b8fa8cc72bb14008ac15d0f84a0498abae4 100644 (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);
 }
index d19f9edb827b53f53daed8dd5d88b9b756c79368..1f7ef9af863dfd0d36de5746cd29f4b3e5ba945b 100644 (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;
index 550c06f5d079c80e824c0ca56efe3f588a70104a..15bfe81aa0d52982e4a71ad4c31668adc0e38873 100644 (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;
index c88469fdece6164f98ec61eca74a659589304df6..cd42e92ae66574dae29d87f8330c3d41e2d23d45 100644 (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);
index 444ee7a2007d9a6cdbf714470bbf71651647d758..969977cea51f222927bde5790ffe388d592c9cee 100644 (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
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
new file mode 100644 (file)
index 0000000..a32ea25
--- /dev/null
@@ -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);
+}
index 82dbb8e97076109fdc052d82c54b71f30de7c1e6..de094195428b76e15e1fdef4a42407e0784323dc 100644 (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;
+}
index 00bed7e1391d98d1e7dfcf51afb3388d2f509760..543114cb17db1b7ce47ffe3435ea088366126339 100644 (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
index 2f5ae35ef9f58907eeedd3c1bc1f0dacdcca3564..21bbd429ae841a39c5a7e6cf356b1bf8b031f753 100644 (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
index be632befc927973f540a820f6d95e378a458b91c..3082c58a582f0de81bdfda216583859db849fbe4 100644 (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
diff --git a/src/include/executor/nodeWindowAgg.h b/src/include/executor/nodeWindowAgg.h
new file mode 100644 (file)
index 0000000..e853103
--- /dev/null
@@ -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 */
index c348086fbc8e3770658504dd8d7e08217b959e61..ee95676fe248bd17a7d38de898f6eb1ca596a81a 100644 (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
index 9aae040019b5483bd3f26703916344812b7c1656..258abdee6aa8ea03e0a8f2ff70369561e23ae97b 100644 (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
  *
index 7383697f6ce1fda801f23eb7d8ca0c2c9cb40fb5..22649cdc073ae68b73d54aeddc2211abbbece35b 100644 (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;
 
index 2f22475bf10fe4008eba75da59c37937aaba729b..43c4452d781e8afb88eaf978587698ab1561041c 100644 (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 */
 
        /*
index 16c25fd6d0eca8be386c6e81b0ebbf502beab56e..e320fd5fb4c2832e971474e00e9c6e1ac9392515 100644 (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
  * ----------------
index 2a2ea18520fa844d54a44608e8c510f6cf38a3e9..36edc80b9a6bfc0fef249177be1406fcbe7e820e 100644 (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
  *
index 7b67d94700ef336b022ceaa7b9fade20936dcb3c..ac29269235901475e11aab3fa889ce9745435709 100644 (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 */
 
index 3623aade6a73c3ea3d87b4d94480cb4762d6f88c..f4e668bef30c667300c33c3757caa5257179f6a5 100644 (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);
index 181327caa81389616401201f95d76bb449248484..777f7f0fabe6e1a12ee5c6a0fb530232a3f31f60 100644 (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,
index 641ebe42fb4e84b00d38e9ac924c8f80f8bd0a40..d6f5ff160f64322e3f92a199d3dcfe42efeb1cfb 100644 (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,
index d2c7f42e05f583e08ab051f9296eb53b73be15c9..dabef328dead61356eea5040b17bddb2b349e0ae 100644 (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);
index bf3574e89fcf65ecfa6420eaa82dc14514997fac..93b7f674e3252a48a1b4a0bdc8aab725442fe956 100644 (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 $
  *
  *-------------------------------------------------------------------------
  */
 #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,
index ffea3466b5cd6d760e83492ce640518ef5fdf2e9..0463e7ae66e63a03fdc907c21b0d50dc518ffbd5 100644 (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,
index 8507a4ed7d5d0874415cdd9bc89050fe3a9848a2..b7023bf8d3de6db4afc4b5b80f1977fed59c9ede 100644 (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,
index 8e0d3c201f27bf4efc6998490ef2dd7490943e4e..0abe45df32c2543ec576c8211a696ce8b978d3c8 100644 (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 $
  *
  *-------------------------------------------------------------------------
  */
  * 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;
index 50b4443de0d12544ff2373d9aeff58bdc58ea5ea..9387e71354beb4a20a2c752dd499127b6c0f1062 100644 (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,
index 8a7f10451f0e7c3d5e70579d311a57df4dfde220..6d0f5bf13c6473c9f24a64eccd2ce9876a55d4f6 100644 (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
index a00415aaa65d41f84f38385dc6032b5b02691e88..65be80c24b4a7c9b07c1587e9c03916cc5fd6899 100644 (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);
 
index baecd7bafcb65d26f67b1e107cfc41426104c0d3..4e393c62ac0e1e47ff329c9d780f7cb64571e7ed 100644 (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 $
  *
  *-------------------------------------------------------------------------
  */
 #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')
 #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')
index 9f9981ff41dc5da9643bba295e064ee6cacd0f91..bb9f19cce2cb952b8100b99566069b765983ebcf 100644 (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);
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
new file mode 100644 (file)
index 0000000..25ba25f
--- /dev/null
@@ -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 */
index 77fd1a24e39609883a62ff7d2959169118abb288..bd53ea8cc3922476af01cfe8cee8fddc07ec7fd5 100644 (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 $
  *
  *-------------------------------------------------------------------------
  */
        "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
 },
        "grouping_error", ERRCODE_GROUPING_ERROR
 },
 
+{
+       "windowing_error", ERRCODE_WINDOWING_ERROR
+},
+
 {
        "invalid_recursion", ERRCODE_INVALID_RECURSION
 },
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
new file mode 100644 (file)
index 0000000..ee16087
--- /dev/null
@@ -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;
index 13bbb9032545f614402e86d47b055123b71017c2..db4097401cd331f2b487c3d3df0837deef6201b1 100644 (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
index 27b79aef256a5afd457222364523c2dd7e3081b0..d2e62948967bfdca59dead2c4749eaf86a0006eb 100644 (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
index da823d8423810f7a433bbaa7ef9c6a165061f3d3..a650807f3bb4761dc59ffa32b991db16195f1157 100644 (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
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
new file mode 100644 (file)
index 0000000..1100de5
--- /dev/null
@@ -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;