aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/catalog/dependency.c11
-rw-r--r--src/backend/catalog/heap.c10
-rw-r--r--src/backend/catalog/pg_proc.c26
-rw-r--r--src/backend/commands/explain.c5
-rw-r--r--src/backend/commands/functioncmds.c10
-rw-r--r--src/backend/commands/prepare.c6
-rw-r--r--src/backend/commands/tablecmds.c6
-rw-r--r--src/backend/commands/typecmds.c6
-rw-r--r--src/backend/executor/Makefile4
-rw-r--r--src/backend/executor/execAmi.c9
-rw-r--r--src/backend/executor/execProcnode.c22
-rw-r--r--src/backend/executor/execQual.c78
-rw-r--r--src/backend/executor/nodeWindowAgg.c1854
-rw-r--r--src/backend/nodes/copyfuncs.c92
-rw-r--r--src/backend/nodes/equalfuncs.c54
-rw-r--r--src/backend/nodes/nodeFuncs.c69
-rw-r--r--src/backend/nodes/outfuncs.c88
-rw-r--r--src/backend/nodes/readfuncs.c45
-rw-r--r--src/backend/optimizer/path/allpaths.c19
-rw-r--r--src/backend/optimizer/path/costsize.c41
-rw-r--r--src/backend/optimizer/path/equivclass.c10
-rw-r--r--src/backend/optimizer/plan/createplan.c57
-rw-r--r--src/backend/optimizer/plan/planagg.c10
-rw-r--r--src/backend/optimizer/plan/planmain.c21
-rw-r--r--src/backend/optimizer/plan/planner.c457
-rw-r--r--src/backend/optimizer/plan/setrefs.c8
-rw-r--r--src/backend/optimizer/plan/subselect.c16
-rw-r--r--src/backend/optimizer/prep/prepjointree.c8
-rw-r--r--src/backend/optimizer/prep/prepunion.c3
-rw-r--r--src/backend/optimizer/util/clauses.c124
-rw-r--r--src/backend/optimizer/util/tlist.c18
-rw-r--r--src/backend/parser/analyze.c55
-rw-r--r--src/backend/parser/gram.y158
-rw-r--r--src/backend/parser/keywords.c5
-rw-r--r--src/backend/parser/parse_agg.c198
-rw-r--r--src/backend/parser/parse_clause.c203
-rw-r--r--src/backend/parser/parse_coerce.c3
-rw-r--r--src/backend/parser/parse_expr.c12
-rw-r--r--src/backend/parser/parse_func.c81
-rw-r--r--src/backend/parser/parse_type.c3
-rw-r--r--src/backend/parser/parse_utilcmd.c7
-rw-r--r--src/backend/rewrite/rewriteManip.c92
-rw-r--r--src/backend/utils/adt/Makefile4
-rw-r--r--src/backend/utils/adt/array_userfuncs.c35
-rw-r--r--src/backend/utils/adt/arrayfuncs.c11
-rw-r--r--src/backend/utils/adt/float.c14
-rw-r--r--src/backend/utils/adt/int8.c6
-rw-r--r--src/backend/utils/adt/numeric.c18
-rw-r--r--src/backend/utils/adt/ruleutils.c298
-rw-r--r--src/backend/utils/adt/windowfuncs.c475
-rw-r--r--src/backend/utils/fmgr/fmgr.c75
-rw-r--r--src/backend/utils/sort/tuplestore.c15
-rw-r--r--src/include/catalog/catversion.h4
-rw-r--r--src/include/catalog/pg_proc.h34
-rw-r--r--src/include/executor/nodeWindowAgg.h25
-rw-r--r--src/include/fmgr.h4
-rw-r--r--src/include/nodes/execnodes.h69
-rw-r--r--src/include/nodes/nodes.h9
-rw-r--r--src/include/nodes/parsenodes.h51
-rw-r--r--src/include/nodes/plannodes.h17
-rw-r--r--src/include/nodes/primnodes.h17
-rw-r--r--src/include/nodes/relation.h3
-rw-r--r--src/include/optimizer/clauses.h13
-rw-r--r--src/include/optimizer/cost.h6
-rw-r--r--src/include/optimizer/planmain.h7
-rw-r--r--src/include/optimizer/tlist.h4
-rw-r--r--src/include/parser/parse_agg.h7
-rw-r--r--src/include/parser/parse_clause.h10
-rw-r--r--src/include/parser/parse_func.h5
-rw-r--r--src/include/parser/parse_node.h10
-rw-r--r--src/include/rewrite/rewriteManip.h4
-rw-r--r--src/include/utils/array.h4
-rw-r--r--src/include/utils/builtins.h19
-rw-r--r--src/include/utils/errcodes.h5
-rw-r--r--src/include/utils/tuplestore.h4
-rw-r--r--src/include/windowapi.h64
-rw-r--r--src/pl/plpgsql/src/plerrcodes.h14
-rw-r--r--src/test/regress/expected/window.out672
-rw-r--r--src/test/regress/expected/with.out4
-rw-r--r--src/test/regress/parallel_schedule4
-rw-r--r--src/test/regress/serial_schedule3
-rw-r--r--src/test/regress/sql/window.sql179
82 files changed, 5936 insertions, 290 deletions
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 2cbc19f5a06..b78bebf506f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -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 */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a711143f86c..af200afaac8 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -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;
}
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 345df0c6a95..8ff22c23c9e 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -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)));
}
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index e5f1b313076..d829cb19235 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -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;
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 0a3de53e1e5..8963f981178 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -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),
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 920b328bb30..f1f87abe227 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -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);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 9f34c735028..173b24dab82 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -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
{
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 38416fa67f2..f99ed813954 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -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.
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index b4a0492751c..63c86107782 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -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
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index ef4f6853899..d406a0cec9a 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -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;
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index e689ec00f8c..cd610c895c1 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -12,7 +12,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/execProcnode.c,v 1.63 2008/10/04 21:56:53 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/execProcnode.c,v 1.64 2008/12/28 18:53:55 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -85,6 +85,7 @@
#include "executor/nodeBitmapHeapscan.h"
#include "executor/nodeBitmapIndexscan.h"
#include "executor/nodeBitmapOr.h"
+#include "executor/nodeCtescan.h"
#include "executor/nodeFunctionscan.h"
#include "executor/nodeGroup.h"
#include "executor/nodeHash.h"
@@ -104,7 +105,7 @@
#include "executor/nodeTidscan.h"
#include "executor/nodeUnique.h"
#include "executor/nodeValuesscan.h"
-#include "executor/nodeCtescan.h"
+#include "executor/nodeWindowAgg.h"
#include "executor/nodeWorktablescan.h"
#include "miscadmin.h"
@@ -260,6 +261,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
estate, eflags);
break;
+ case T_WindowAgg:
+ result = (PlanState *) ExecInitWindowAgg((WindowAgg *) node,
+ estate, eflags);
+ break;
+
case T_Unique:
result = (PlanState *) ExecInitUnique((Unique *) node,
estate, eflags);
@@ -425,6 +431,10 @@ ExecProcNode(PlanState *node)
result = ExecAgg((AggState *) node);
break;
+ case T_WindowAggState:
+ result = ExecWindowAgg((WindowAggState *) node);
+ break;
+
case T_UniqueState:
result = ExecUnique((UniqueState *) node);
break;
@@ -601,6 +611,10 @@ ExecCountSlotsNode(Plan *node)
case T_Agg:
return ExecCountSlotsAgg((Agg *) node);
+ case T_WindowAgg:
+ return ExecCountSlotsWindowAgg((WindowAgg *) node);
+ break;
+
case T_Unique:
return ExecCountSlotsUnique((Unique *) node);
@@ -749,6 +763,10 @@ ExecEndNode(PlanState *node)
ExecEndAgg((AggState *) node);
break;
+ case T_WindowAggState:
+ ExecEndWindowAgg((WindowAggState *) node);
+ break;
+
case T_UniqueState:
ExecEndUnique((UniqueState *) node);
break;
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 71aad49647d..17606f5204e 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -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,
@@ -444,6 +447,27 @@ ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext,
}
/* ----------------------------------------------------------------
+ * 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
*
* Returns a Datum whose value is the value of a range
@@ -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
index 00000000000..37ef9a5e830
--- /dev/null
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -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);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 86f555a03a6..412fd96e5bf 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -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 $
*
*-------------------------------------------------------------------------
*/
@@ -669,6 +669,32 @@ _copyAgg(Agg *from)
}
/*
+ * _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
*/
static Unique *
@@ -932,6 +958,25 @@ _copyAggref(Aggref *from)
}
/*
+ * _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
*/
static ArrayRef *
@@ -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;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e5e2bc44226..e96c66152e8 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -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 $
*
*-------------------------------------------------------------------------
*/
@@ -192,6 +192,20 @@ _equalAggref(Aggref *a, Aggref *b)
}
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)
{
COMPARE_SCALAR_FIELD(refarraytype);
@@ -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;
@@ -1981,6 +1999,18 @@ _equalSortBy(SortBy *a, SortBy *b)
}
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)
{
COMPARE_NODE_FIELD(subquery);
@@ -2107,6 +2137,19 @@ _equalSortGroupClause(SortGroupClause *a, SortGroupClause *b)
}
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)
{
COMPARE_SCALAR_FIELD(rti);
@@ -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;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7236360347c..0284ce4edca 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -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;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2477a17cfa3..f926f1314cd 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -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*
@@ -567,6 +567,36 @@ _outAgg(StringInfo str, Agg *node)
}
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)
{
int i;
@@ -799,6 +829,20 @@ _outAggref(StringInfo str, Aggref *node)
}
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)
{
WRITE_NODE_TYPE("ARRAYREF");
@@ -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);
@@ -1896,6 +1945,19 @@ _outSortGroupClause(StringInfo str, SortGroupClause *node)
}
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)
{
WRITE_NODE_TYPE("ROWMARKCLAUSE");
@@ -2172,6 +2234,18 @@ _outSortBy(StringInfo str, SortBy *node)
}
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)
{
WRITE_NODE_TYPE("RANGESUBSELECT");
@@ -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;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index ed5b55fb571..7bcc8e8047d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -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);
@@ -218,6 +220,24 @@ _readSortGroupClause(void)
}
/*
+ * _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
*/
static RowMarkClause *
@@ -403,6 +423,25 @@ _readAggref(void)
}
/*
+ * _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
*/
static ArrayRef *
@@ -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))
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index b0553894c24..17eebc67647 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -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)
{
@@ -1093,6 +1100,12 @@ qual_is_pushdown_safe(Query *subquery, Index rti, Node *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.
*/
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 0b9c5819820..7f30dde869f 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -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 $
*
*-------------------------------------------------------------------------
*/
@@ -1284,6 +1284,40 @@ cost_agg(Path *path, PlannerInfo *root,
}
/*
+ * 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,
* including the cost of its input.
@@ -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
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 3d35eb605d9..5f6d219a01a 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -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;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index f5d4f41c032..b53b5e1470e 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -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)
{
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index 8a6b2ad0345..f0f17d5f950 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -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;
/*
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 0a1d1d1559f..a8ea043a697 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -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)
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 7f91309032a..b4b578d5973 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -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));
@@ -867,6 +881,22 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
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");
+ }
+}
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 9bec109f6f5..83447082f5b 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -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,
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index c999fb6419c..a38f8c09ae7 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -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:
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index e4d508523e1..80a51d80786 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -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 ||
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index bd7c05cc53d..f3a49cf9dee 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -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);
/*
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 3c74831f4da..ee45f32abbb 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -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);
@@ -389,6 +391,41 @@ contain_agg_clause_walker(Node *node, void *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.
*
@@ -520,6 +557,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 ||
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
index 968f4ae367a..aab3d032b12 100644
--- a/src/backend/optimizer/util/tlist.c
+++ b/src/backend/optimizer/util/tlist.c
@@ -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);
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index cdac02b71db..70688655cce 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -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")));
}
/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 29eab503198..59b7ada7b43 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -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;
}
@@ -9157,6 +9208,77 @@ xml_whitespace_option: PRESERVE WHITESPACE_P { $$ = TRUE; }
;
/*
+ * 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;
}
diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c
index bf7b1f6ad2e..c3ad852258b 100644
--- a/src/backend/parser/keywords.c
+++ b/src/backend/parser/keywords.c
@@ -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},
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index e2645462d57..6dba470e39f 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -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 -
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 0e5fbfd28ac..df30361f0a5 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.181 2008/10/06 02:12:56 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.182 2008/12/28 18:53:58 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -40,8 +40,14 @@
#define ORDER_CLAUSE 0
#define GROUP_CLAUSE 1
#define DISTINCT_ON_CLAUSE 2
+#define PARTITION_CLAUSE 3
-static char *clauseText[] = {"ORDER BY", "GROUP BY", "DISTINCT ON"};
+static const char * const clauseText[] = {
+ "ORDER BY",
+ "GROUP BY",
+ "DISTINCT ON",
+ "PARTITION BY"
+};
static void extractRemainingColumns(List *common_colnames,
List *src_colnames, List *src_colvars,
@@ -76,6 +82,7 @@ static List *addTargetToSortList(ParseState *pstate, TargetEntry *tle,
static List *addTargetToGroupList(ParseState *pstate, TargetEntry *tle,
List *grouplist, List *targetlist, int location,
bool resolveUnknown);
+static WindowClause *findWindowClause(List *wclist, const char *name);
/*
@@ -555,15 +562,20 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
* Disallow aggregate functions in the expression. (No reason to postpone
* this check until parseCheckAggregates.)
*/
- if (pstate->p_hasAggs)
- {
- if (checkExprHasAggs(funcexpr))
- ereport(ERROR,
- (errcode(ERRCODE_GROUPING_ERROR),
- errmsg("cannot use aggregate function in function expression in FROM"),
- parser_errposition(pstate,
- locate_agg_of_level(funcexpr, 0))));
- }
+ if (pstate->p_hasAggs &&
+ checkExprHasAggs(funcexpr))
+ ereport(ERROR,
+ (errcode(ERRCODE_GROUPING_ERROR),
+ errmsg("cannot use aggregate function in function expression in FROM"),
+ parser_errposition(pstate,
+ locate_agg_of_level(funcexpr, 0))));
+ if (pstate->p_hasWindowFuncs &&
+ checkExprHasWindowFuncs(funcexpr))
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("cannot use window function in function expression in FROM"),
+ parser_errposition(pstate,
+ locate_windowfunc(funcexpr))));
/*
* OK, build an RTE for the function.
@@ -1156,16 +1168,28 @@ transformLimitClause(ParseState *pstate, Node *clause,
parser_errposition(pstate,
locate_var_of_level(qual, 0))));
}
- if (checkExprHasAggs(qual))
+ if (pstate->p_hasAggs &&
+ checkExprHasAggs(qual))
{
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
/* translator: %s is name of a SQL construct, eg LIMIT */
- errmsg("argument of %s must not contain aggregates",
+ errmsg("argument of %s must not contain aggregate functions",
constructName),
parser_errposition(pstate,
locate_agg_of_level(qual, 0))));
}
+ if (pstate->p_hasWindowFuncs &&
+ checkExprHasWindowFuncs(qual))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ /* translator: %s is name of a SQL construct, eg LIMIT */
+ errmsg("argument of %s must not contain window functions",
+ constructName),
+ parser_errposition(pstate,
+ locate_windowfunc(qual))));
+ }
return qual;
}
@@ -1234,7 +1258,7 @@ findTargetlistEntry(ParseState *pstate, Node *node, List **tlist, int clause)
char *name = strVal(linitial(((ColumnRef *) node)->fields));
int location = ((ColumnRef *) node)->location;
- if (clause == GROUP_CLAUSE)
+ if (clause == GROUP_CLAUSE || clause == PARTITION_CLAUSE)
{
/*
* In GROUP BY, we must prefer a match against a FROM-clause
@@ -1251,6 +1275,8 @@ findTargetlistEntry(ParseState *pstate, Node *node, List **tlist, int clause)
* SQL99 do not allow GROUPing BY an outer reference, so this
* breaks no cases that are legal per spec, and it seems a more
* self-consistent behavior.
+ *
+ * Window PARTITION BY clauses should act exactly like GROUP BY.
*/
if (colNameToVar(pstate, name, true, location) != NULL)
name = NULL;
@@ -1356,12 +1382,17 @@ findTargetlistEntry(ParseState *pstate, Node *node, List **tlist, int clause)
*
* GROUP BY items will be added to the targetlist (as resjunk columns)
* if not already present, so the targetlist must be passed by reference.
+ *
+ * This is also used for window PARTITION BY clauses (which actually act
+ * just the same, except for the clause name used in error messages).
*/
List *
transformGroupClause(ParseState *pstate, List *grouplist,
- List **targetlist, List *sortClause)
+ List **targetlist, List *sortClause,
+ bool isPartition)
{
List *result = NIL;
+ int clause = isPartition ? PARTITION_CLAUSE : GROUP_CLAUSE;
ListCell *gl;
foreach(gl, grouplist)
@@ -1370,8 +1401,7 @@ transformGroupClause(ParseState *pstate, List *grouplist,
TargetEntry *tle;
bool found = false;
- tle = findTargetlistEntry(pstate, gexpr,
- targetlist, GROUP_CLAUSE);
+ tle = findTargetlistEntry(pstate, gexpr, targetlist, clause);
/* Eliminate duplicates (GROUP BY x, x) */
if (targetIsInSortList(tle, InvalidOid, result))
@@ -1452,6 +1482,125 @@ transformSortClause(ParseState *pstate,
}
/*
+ * 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;
+}
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 1bac7ca2fce..29e3a9be017 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -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)); */
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index b5299d010a6..c14970d4561 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -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);
}
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index d0b74ff5d96..b48dd11495f 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -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;
}
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 0253cfe1593..e7c43daf7f2 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -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 ||
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index bb3a9142d6f..739f1b03a02 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -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
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 954e21af181..2b6c01fc8f6 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -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);
@@ -176,6 +184,87 @@ locate_agg_of_level_walker(Node *node,
}
/*
+ * 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
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index d7d28c9f159..69dfbbba3ba 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -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
diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c
index 4c147f0021d..1401b29359f 100644
--- a/src/backend/utils/adt/array_userfuncs.c
+++ b/src/backend/utils/adt/array_userfuncs.c
@@ -6,7 +6,7 @@
* Copyright (c) 2003-2008, PostgreSQL Global Development Group
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/adt/array_userfuncs.c,v 1.26 2008/11/14 02:09:51 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/array_userfuncs.c,v 1.27 2008/12/28 18:53:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -475,6 +475,7 @@ Datum
array_agg_transfn(PG_FUNCTION_ARGS)
{
Oid arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ MemoryContext aggcontext;
ArrayBuildState *state;
Datum elem;
@@ -483,8 +484,16 @@ array_agg_transfn(PG_FUNCTION_ARGS)
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not determine input data type")));
- /* cannot be called directly because of internal-type argument */
- Assert(fcinfo->context && IsA(fcinfo->context, AggState));
+ if (fcinfo->context && IsA(fcinfo->context, AggState))
+ aggcontext = ((AggState *) fcinfo->context)->aggcontext;
+ else if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
+ aggcontext = ((WindowAggState *) fcinfo->context)->wincontext;
+ else
+ {
+ /* cannot be called directly because of internal-type argument */
+ elog(ERROR, "array_agg_transfn called in non-aggregate context");
+ aggcontext = NULL; /* keep compiler quiet */
+ }
state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
elem = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1);
@@ -492,7 +501,7 @@ array_agg_transfn(PG_FUNCTION_ARGS)
elem,
PG_ARGISNULL(1),
arg1_typeid,
- ((AggState *) fcinfo->context)->aggcontext);
+ aggcontext);
/*
* The transition type for array_agg() is declared to be "internal",
@@ -506,14 +515,28 @@ array_agg_transfn(PG_FUNCTION_ARGS)
Datum
array_agg_finalfn(PG_FUNCTION_ARGS)
{
+ Datum result;
ArrayBuildState *state;
+ int dims[1];
+ int lbs[1];
/* cannot be called directly because of internal-type argument */
- Assert(fcinfo->context && IsA(fcinfo->context, AggState));
+ Assert(fcinfo->context &&
+ (IsA(fcinfo->context, AggState) ||
+ IsA(fcinfo->context, WindowAggState)));
if (PG_ARGISNULL(0))
PG_RETURN_NULL(); /* returns null iff no input values */
state = (ArrayBuildState *) PG_GETARG_POINTER(0);
- PG_RETURN_ARRAYTYPE_P(makeArrayResult(state, CurrentMemoryContext));
+
+ dims[0] = state->nelems;
+ lbs[0] = 1;
+
+ /* Release working state if regular aggregate, but not if window agg */
+ result = makeMdArrayResult(state, 1, dims, lbs,
+ CurrentMemoryContext,
+ IsA(fcinfo->context, AggState));
+
+ PG_RETURN_DATUM(result);
}
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 4580040d697..a0501b8fa8c 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -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);
}
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index d19f9edb827..1f7ef9af863 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -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;
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 550c06f5d07..15bfe81aa0d 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -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;
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index c88469fdece..cd42e92ae66 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -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);
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 444ee7a2007..969977cea51 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -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
index 00000000000..a32ea25c065
--- /dev/null
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -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);
+}
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 82dbb8e9707..de094195428 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -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;
+}
diff --git a/src/backend/utils/sort/tuplestore.c b/src/backend/utils/sort/tuplestore.c
index 00bed7e1391..543114cb17d 100644
--- a/src/backend/utils/sort/tuplestore.c
+++ b/src/backend/utils/sort/tuplestore.c
@@ -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
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 2f5ae35ef9f..21bbd429ae8 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -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
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index be632befc92..3082c58a582 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -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
index 00000000000..e8531037c07
--- /dev/null
+++ b/src/include/executor/nodeWindowAgg.h
@@ -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 */
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index c348086fbc8..ee95676fe24 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -11,7 +11,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/fmgr.h,v 1.60 2008/09/03 22:34:50 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/fmgr.h,v 1.61 2008/12/28 18:54:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -516,6 +516,8 @@ extern Oid fmgr_internal_function(const char *proname);
extern Oid get_fn_expr_rettype(FmgrInfo *flinfo);
extern Oid get_fn_expr_argtype(FmgrInfo *flinfo, int argnum);
extern Oid get_call_expr_argtype(fmNodePtr expr, int argnum);
+extern bool get_fn_expr_arg_stable(FmgrInfo *flinfo, int argnum);
+extern bool get_call_expr_arg_stable(fmNodePtr expr, int argnum);
/*
* Routines in dfmgr.c
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 9aae040019b..258abdee6aa 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -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;
@@ -512,6 +515,17 @@ typedef struct AggrefExprState
} 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
*
* Note: array types can be fixed-length (typlen > 0), but only when the
@@ -1483,6 +1497,53 @@ typedef struct AggState
} 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
*
* Unique nodes are used "on top of" sort nodes to discard
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7383697f6ce..22649cdc073 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -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;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 2f22475bf10..43c4452d781 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -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;
@@ -376,6 +381,19 @@ typedef struct SortBy
} 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
*/
typedef struct RangeSubselect
@@ -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,
@@ -715,6 +734,31 @@ typedef struct SortGroupClause
} 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 */
/*
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 16c25fd6d0e..e320fd5fb4c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -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 $
*
*-------------------------------------------------------------------------
*/
@@ -537,6 +537,21 @@ typedef struct Agg
} 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
* ----------------
*/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 2a2ea18520f..36edc80b9a6 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -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
*
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 7b67d94700e..ac292692359 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -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 */
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 3623aade6a7..f4e668bef30 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -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);
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 181327caa81..777f7f0fabe 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -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,
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 641ebe42fb4..d6f5ff160f6 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -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,
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
index d2c7f42e05f..dabef328dea 100644
--- a/src/include/optimizer/tlist.h
+++ b/src/include/optimizer/tlist.h
@@ -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);
diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h
index bf3574e89fc..93b7f674e32 100644
--- a/src/include/parser/parse_agg.h
+++ b/src/include/parser/parse_agg.h
@@ -1,12 +1,12 @@
/*-------------------------------------------------------------------------
*
* parse_agg.h
- * handle aggregates in parser
+ * handle aggregates and window functions in parser
*
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/parser/parse_agg.h,v 1.36 2008/01/01 19:45:58 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/parser/parse_agg.h,v 1.37 2008/12/28 18:54:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -16,8 +16,11 @@
#include "parser/parse_node.h"
extern void transformAggregateCall(ParseState *pstate, Aggref *agg);
+extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
+ WindowDef *windef);
extern void parseCheckAggregates(ParseState *pstate, Query *qry);
+extern void parseCheckWindowFuncs(ParseState *pstate, Query *qry);
extern void build_aggregate_fnexprs(Oid *agg_input_types,
int agg_num_inputs,
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index ffea3466b5c..0463e7ae66e 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -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,
diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h
index 8507a4ed7d5..b7023bf8d3d 100644
--- a/src/include/parser/parse_func.h
+++ b/src/include/parser/parse_func.h
@@ -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,
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 8e0d3c201f2..0abe45df32c 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/parser/parse_node.h,v 1.58 2008/10/08 01:14:44 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/parser/parse_node.h,v 1.59 2008/12/28 18:54:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -57,6 +57,12 @@
* p_future_ctes: list of CommonTableExprs (WITH items) that are not yet
* visible due to scope rules. This is used to help improve error messages.
*
+ * p_windowdefs: list of WindowDefs representing WINDOW and OVER clauses.
+ * We collect these while transforming expressions and then transform them
+ * afterwards (so that any resjunk tlist items needed for the sort/group
+ * clauses end up at the end of the query tlist). A WindowDef's location in
+ * this list, counting from 1, is the winref number to use to reference it.
+ *
* p_paramtypes: an array of p_numparams type OIDs for $n parameter symbols
* (zeroth entry in array corresponds to $1). If p_variableparams is true, the
* set of param types is not predetermined; in that case, a zero array entry
@@ -77,6 +83,7 @@ typedef struct ParseState
List *p_varnamespace; /* current namespace for columns */
List *p_ctenamespace; /* current namespace for common table exprs */
List *p_future_ctes; /* common table exprs not yet in namespace */
+ List *p_windowdefs; /* raw representations of window clauses */
Oid *p_paramtypes; /* OIDs of types for $n parameter symbols */
int p_numparams; /* allocated size of p_paramtypes[] */
int p_next_resno; /* next targetlist resno to assign */
@@ -84,6 +91,7 @@ typedef struct ParseState
Node *p_value_substitute; /* what to replace VALUE with, if any */
bool p_variableparams;
bool p_hasAggs;
+ bool p_hasWindowFuncs;
bool p_hasSubLinks;
bool p_is_insert;
bool p_is_update;
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index 50b4443de0d..9387e71354b 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -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,
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 8a7f10451f0..6d0f5bf13c6 100644
--- a/src/include/utils/array.h
+++ b/src/include/utils/array.h
@@ -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
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index a00415aaa65..65be80c24b4 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -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);
diff --git a/src/include/utils/errcodes.h b/src/include/utils/errcodes.h
index baecd7bafcb..4e393c62ac0 100644
--- a/src/include/utils/errcodes.h
+++ b/src/include/utils/errcodes.h
@@ -11,7 +11,7 @@
*
* Copyright (c) 2003-2008, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.26 2008/10/04 21:56:55 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.27 2008/12/28 18:54:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -122,6 +122,8 @@
#define ERRCODE_INDICATOR_OVERFLOW MAKE_SQLSTATE('2','2', '0','2','2')
#define ERRCODE_INTERVAL_FIELD_OVERFLOW MAKE_SQLSTATE('2','2', '0','1','5')
#define ERRCODE_INVALID_ARGUMENT_FOR_LOG MAKE_SQLSTATE('2','2', '0','1','E')
+#define ERRCODE_INVALID_ARGUMENT_FOR_NTILE MAKE_SQLSTATE('2','2', '0','1','4')
+#define ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE MAKE_SQLSTATE('2','2', '0','1','6')
#define ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION MAKE_SQLSTATE('2','2', '0', '1', 'F')
#define ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION MAKE_SQLSTATE('2','2', '0', '1', 'G')
#define ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST MAKE_SQLSTATE('2','2', '0','1','8')
@@ -246,6 +248,7 @@
#define ERRCODE_INSUFFICIENT_PRIVILEGE MAKE_SQLSTATE('4','2', '5','0','1')
#define ERRCODE_CANNOT_COERCE MAKE_SQLSTATE('4','2', '8','4','6')
#define ERRCODE_GROUPING_ERROR MAKE_SQLSTATE('4','2', '8','0','3')
+#define ERRCODE_WINDOWING_ERROR MAKE_SQLSTATE('4','2', 'P','2','0')
#define ERRCODE_INVALID_RECURSION MAKE_SQLSTATE('4','2', 'P','1','9')
#define ERRCODE_INVALID_FOREIGN_KEY MAKE_SQLSTATE('4','2', '8','3','0')
#define ERRCODE_INVALID_NAME MAKE_SQLSTATE('4','2', '6','0','2')
diff --git a/src/include/utils/tuplestore.h b/src/include/utils/tuplestore.h
index 9f9981ff41d..bb9f19cce2c 100644
--- a/src/include/utils/tuplestore.h
+++ b/src/include/utils/tuplestore.h
@@ -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
index 00000000000..25ba25fc50a
--- /dev/null
+++ b/src/include/windowapi.h
@@ -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 */
diff --git a/src/pl/plpgsql/src/plerrcodes.h b/src/pl/plpgsql/src/plerrcodes.h
index 77fd1a24e39..bd53ea8cc39 100644
--- a/src/pl/plpgsql/src/plerrcodes.h
+++ b/src/pl/plpgsql/src/plerrcodes.h
@@ -9,7 +9,7 @@
*
* Copyright (c) 2003-2008, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/plerrcodes.h,v 1.15 2008/10/04 21:56:55 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/plerrcodes.h,v 1.16 2008/12/28 18:54:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -136,6 +136,14 @@
},
{
+ "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
},
@@ -484,6 +492,10 @@
},
{
+ "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
index 00000000000..ee16087b92c
--- /dev/null
+++ b/src/test/regress/expected/window.out
@@ -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;
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 13bbb903254..db4097401cd 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -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
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 27b79aef256..d2e62948967 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -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
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index da823d84238..a650807f3bb 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -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
index 00000000000..1100de5afa4
--- /dev/null
+++ b/src/test/regress/sql/window.sql
@@ -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;