summaryrefslogtreecommitdiff
path: root/src/backend/parser
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/parser')
-rw-r--r--src/backend/parser/gram.y141
-rw-r--r--src/backend/parser/parse_agg.c7
-rw-r--r--src/backend/parser/parse_clause.c93
3 files changed, 199 insertions, 42 deletions
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2541d021287..da70ee089c5 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.707 2010/02/08 04:33:54 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.708 2010/02/12 17:33:20 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -434,8 +434,8 @@ static TypeName *TableFuncTypeName(List *columns);
%type <list> window_clause window_definition_list opt_partition_clause
%type <windef> window_definition over_clause window_specification
+ opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
-%type <ival> opt_frame_clause frame_extent frame_bound
/*
@@ -578,8 +578,18 @@ static TypeName *TableFuncTypeName(List *columns);
* RANGE, ROWS to support opt_existing_window_name; and for RANGE, ROWS
* so that they can follow a_expr without creating
* postfix-operator problems.
+ *
+ * The frame_bound productions UNBOUNDED PRECEDING and UNBOUNDED FOLLOWING
+ * are even messier: since UNBOUNDED is an unreserved keyword (per spec!),
+ * there is no principled way to distinguish these from the productions
+ * a_expr PRECEDING/FOLLOWING. We hack this up by giving UNBOUNDED slightly
+ * lower precedence than PRECEDING and FOLLOWING. At present this doesn't
+ * appear to cause UNBOUNDED to be treated differently from other unreserved
+ * keywords anywhere else in the grammar, but it's definitely risky. We can
+ * blame any funny behavior of UNBOUNDED on the SQL standard, though.
*/
-%nonassoc IDENT PARTITION RANGE ROWS
+%nonassoc UNBOUNDED /* ideally should have same precedence as IDENT */
+%nonassoc IDENT PARTITION RANGE ROWS PRECEDING FOLLOWING
%left Op OPERATOR /* multi-character ops and user-defined operators */
%nonassoc NOTNULL
%nonassoc ISNULL
@@ -9907,6 +9917,8 @@ over_clause: OVER window_specification
n->partitionClause = NIL;
n->orderClause = NIL;
n->frameOptions = FRAMEOPTION_DEFAULTS;
+ n->startOffset = NULL;
+ n->endOffset = NULL;
n->location = @2;
$$ = n;
}
@@ -9922,7 +9934,10 @@ window_specification: '(' opt_existing_window_name opt_partition_clause
n->refname = $2;
n->partitionClause = $3;
n->orderClause = $4;
- n->frameOptions = $5;
+ /* copy relevant fields of opt_frame_clause */
+ n->frameOptions = $5->frameOptions;
+ n->startOffset = $5->startOffset;
+ n->endOffset = $5->endOffset;
n->location = @1;
$$ = n;
}
@@ -9947,58 +9962,100 @@ opt_partition_clause: PARTITION BY expr_list { $$ = $3; }
;
/*
+ * For frame clauses, we return a WindowDef, but only some fields are used:
+ * frameOptions, startOffset, and endOffset.
+ *
* This is only a subset of the full SQL:2008 frame_clause grammar.
- * We don't support <expression> PRECEDING, <expression> FOLLOWING,
- * nor <window frame exclusion> yet.
+ * We don't support <window frame exclusion> yet.
*/
opt_frame_clause:
RANGE frame_extent
{
- $$ = FRAMEOPTION_NONDEFAULT | FRAMEOPTION_RANGE | $2;
+ WindowDef *n = $2;
+ n->frameOptions |= FRAMEOPTION_NONDEFAULT | FRAMEOPTION_RANGE;
+ if (n->frameOptions & (FRAMEOPTION_START_VALUE_PRECEDING |
+ FRAMEOPTION_END_VALUE_PRECEDING))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("RANGE PRECEDING is only supported with UNBOUNDED"),
+ parser_errposition(@1)));
+ if (n->frameOptions & (FRAMEOPTION_START_VALUE_FOLLOWING |
+ FRAMEOPTION_END_VALUE_FOLLOWING))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("RANGE FOLLOWING is only supported with UNBOUNDED"),
+ parser_errposition(@1)));
+ $$ = n;
}
| ROWS frame_extent
{
- $$ = FRAMEOPTION_NONDEFAULT | FRAMEOPTION_ROWS | $2;
+ WindowDef *n = $2;
+ n->frameOptions |= FRAMEOPTION_NONDEFAULT | FRAMEOPTION_ROWS;
+ $$ = n;
}
| /*EMPTY*/
- { $$ = FRAMEOPTION_DEFAULTS; }
+ {
+ WindowDef *n = makeNode(WindowDef);
+ n->frameOptions = FRAMEOPTION_DEFAULTS;
+ n->startOffset = NULL;
+ n->endOffset = NULL;
+ $$ = n;
+ }
;
frame_extent: frame_bound
{
+ WindowDef *n = $1;
/* reject invalid cases */
- if ($1 & FRAMEOPTION_START_UNBOUNDED_FOLLOWING)
+ if (n->frameOptions & FRAMEOPTION_START_UNBOUNDED_FOLLOWING)
ereport(ERROR,
(errcode(ERRCODE_WINDOWING_ERROR),
errmsg("frame start cannot be UNBOUNDED FOLLOWING"),
parser_errposition(@1)));
- if ($1 & FRAMEOPTION_START_CURRENT_ROW)
+ if (n->frameOptions & FRAMEOPTION_START_VALUE_FOLLOWING)
ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("frame start at CURRENT ROW is not implemented"),
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("frame starting from following row cannot end with current row"),
parser_errposition(@1)));
- $$ = $1 | FRAMEOPTION_END_CURRENT_ROW;
+ n->frameOptions |= FRAMEOPTION_END_CURRENT_ROW;
+ $$ = n;
}
| BETWEEN frame_bound AND frame_bound
{
+ WindowDef *n1 = $2;
+ WindowDef *n2 = $4;
+ /* form merged options */
+ int frameOptions = n1->frameOptions;
+ /* shift converts START_ options to END_ options */
+ frameOptions |= n2->frameOptions << 1;
+ frameOptions |= FRAMEOPTION_BETWEEN;
/* reject invalid cases */
- if ($2 & FRAMEOPTION_START_UNBOUNDED_FOLLOWING)
+ if (frameOptions & FRAMEOPTION_START_UNBOUNDED_FOLLOWING)
ereport(ERROR,
(errcode(ERRCODE_WINDOWING_ERROR),
errmsg("frame start cannot be UNBOUNDED FOLLOWING"),
parser_errposition(@2)));
- if ($2 & FRAMEOPTION_START_CURRENT_ROW)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("frame start at CURRENT ROW is not implemented"),
- parser_errposition(@2)));
- if ($4 & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
+ if (frameOptions & FRAMEOPTION_END_UNBOUNDED_PRECEDING)
ereport(ERROR,
(errcode(ERRCODE_WINDOWING_ERROR),
errmsg("frame end cannot be UNBOUNDED PRECEDING"),
parser_errposition(@4)));
- /* shift converts START_ options to END_ options */
- $$ = FRAMEOPTION_BETWEEN | $2 | ($4 << 1);
+ if ((frameOptions & FRAMEOPTION_START_CURRENT_ROW) &&
+ (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING))
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("frame starting from current row cannot have preceding rows"),
+ parser_errposition(@4)));
+ if ((frameOptions & FRAMEOPTION_START_VALUE_FOLLOWING) &&
+ (frameOptions & (FRAMEOPTION_END_VALUE_PRECEDING |
+ FRAMEOPTION_END_CURRENT_ROW)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("frame starting from following row cannot have preceding rows"),
+ parser_errposition(@4)));
+ n1->frameOptions = frameOptions;
+ n1->endOffset = n2->startOffset;
+ $$ = n1;
}
;
@@ -10010,15 +10067,43 @@ frame_extent: frame_bound
frame_bound:
UNBOUNDED PRECEDING
{
- $$ = FRAMEOPTION_START_UNBOUNDED_PRECEDING;
+ WindowDef *n = makeNode(WindowDef);
+ n->frameOptions = FRAMEOPTION_START_UNBOUNDED_PRECEDING;
+ n->startOffset = NULL;
+ n->endOffset = NULL;
+ $$ = n;
}
| UNBOUNDED FOLLOWING
{
- $$ = FRAMEOPTION_START_UNBOUNDED_FOLLOWING;
+ WindowDef *n = makeNode(WindowDef);
+ n->frameOptions = FRAMEOPTION_START_UNBOUNDED_FOLLOWING;
+ n->startOffset = NULL;
+ n->endOffset = NULL;
+ $$ = n;
}
| CURRENT_P ROW
{
- $$ = FRAMEOPTION_START_CURRENT_ROW;
+ WindowDef *n = makeNode(WindowDef);
+ n->frameOptions = FRAMEOPTION_START_CURRENT_ROW;
+ n->startOffset = NULL;
+ n->endOffset = NULL;
+ $$ = n;
+ }
+ | a_expr PRECEDING
+ {
+ WindowDef *n = makeNode(WindowDef);
+ n->frameOptions = FRAMEOPTION_START_VALUE_PRECEDING;
+ n->startOffset = $1;
+ n->endOffset = NULL;
+ $$ = n;
+ }
+ | a_expr FOLLOWING
+ {
+ WindowDef *n = makeNode(WindowDef);
+ n->frameOptions = FRAMEOPTION_START_VALUE_FOLLOWING;
+ n->startOffset = $1;
+ n->endOffset = NULL;
+ $$ = n;
}
;
@@ -10981,7 +11066,8 @@ unreserved_keyword:
* looks too much like a function call for an LR(1) parser.
*/
col_name_keyword:
- BIGINT
+ BETWEEN
+ | BIGINT
| BIT
| BOOLEAN_P
| CHAR_P
@@ -11040,7 +11126,6 @@ col_name_keyword:
*/
type_func_name_keyword:
AUTHORIZATION
- | BETWEEN
| BINARY
| CONCURRENTLY
| CROSS
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 9b01047b91c..e883e283e0b 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_agg.c,v 1.90 2010/01/02 16:57:49 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_agg.c,v 1.91 2010/02/12 17:33:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -258,7 +258,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
continue;
if (equal(refwin->partitionClause, windef->partitionClause) &&
equal(refwin->orderClause, windef->orderClause) &&
- refwin->frameOptions == windef->frameOptions)
+ refwin->frameOptions == windef->frameOptions &&
+ equal(refwin->startOffset, windef->startOffset) &&
+ equal(refwin->endOffset, windef->endOffset))
{
/* found a duplicate window specification */
wfunc->winref = winref;
@@ -505,6 +507,7 @@ parseCheckWindowFuncs(ParseState *pstate, Query *qry)
parser_errposition(pstate,
locate_windowfunc(expr))));
}
+ /* startOffset and limitOffset were checked in transformFrameOffset */
}
}
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 54c5cb39e8e..54bb867631e 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.196 2010/02/07 20:48:10 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.197 2010/02/12 17:33:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -72,6 +72,8 @@ static Node *transformFromClauseItem(ParseState *pstate, Node *n,
Relids *containedRels);
static Node *buildMergedJoinVar(ParseState *pstate, JoinType jointype,
Var *l_colvar, Var *r_colvar);
+static void checkExprIsVarFree(ParseState *pstate, Node *n,
+ const char *constructName);
static TargetEntry *findTargetlistEntrySQL92(ParseState *pstate, Node *node,
List **tlist, int clause);
static TargetEntry *findTargetlistEntrySQL99(ParseState *pstate, Node *node,
@@ -85,6 +87,8 @@ static List *addTargetToGroupList(ParseState *pstate, TargetEntry *tle,
List *grouplist, List *targetlist, int location,
bool resolveUnknown);
static WindowClause *findWindowClause(List *wclist, const char *name);
+static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
+ Node *clause);
/*
@@ -1177,10 +1181,28 @@ transformLimitClause(ParseState *pstate, Node *clause,
qual = coerce_to_specific_type(pstate, qual, INT8OID, constructName);
- /*
- * LIMIT can't refer to any vars or aggregates of the current query
- */
- if (contain_vars_of_level(qual, 0))
+ /* LIMIT can't refer to any vars or aggregates of the current query */
+ checkExprIsVarFree(pstate, qual, constructName);
+
+ return qual;
+}
+
+/*
+ * checkExprIsVarFree
+ * Check that given expr has no Vars of the current query level
+ * (and no aggregates or window functions, either).
+ *
+ * This is used to check expressions that have to have a consistent value
+ * across all rows of the query, such as a LIMIT. Arguably it should reject
+ * volatile functions, too, but we don't do that --- whatever value the
+ * function gives on first execution is what you get.
+ *
+ * constructName does not affect the semantics, but is used in error messages
+ */
+static void
+checkExprIsVarFree(ParseState *pstate, Node *n, const char *constructName)
+{
+ if (contain_vars_of_level(n, 0))
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
@@ -1188,10 +1210,10 @@ transformLimitClause(ParseState *pstate, Node *clause,
errmsg("argument of %s must not contain variables",
constructName),
parser_errposition(pstate,
- locate_var_of_level(qual, 0))));
+ locate_var_of_level(n, 0))));
}
if (pstate->p_hasAggs &&
- checkExprHasAggs(qual))
+ checkExprHasAggs(n))
{
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
@@ -1199,10 +1221,10 @@ transformLimitClause(ParseState *pstate, Node *clause,
errmsg("argument of %s must not contain aggregate functions",
constructName),
parser_errposition(pstate,
- locate_agg_of_level(qual, 0))));
+ locate_agg_of_level(n, 0))));
}
if (pstate->p_hasWindowFuncs &&
- checkExprHasWindowFuncs(qual))
+ checkExprHasWindowFuncs(n))
{
ereport(ERROR,
(errcode(ERRCODE_WINDOWING_ERROR),
@@ -1210,10 +1232,8 @@ transformLimitClause(ParseState *pstate, Node *clause,
errmsg("argument of %s must not contain window functions",
constructName),
parser_errposition(pstate,
- locate_windowfunc(qual))));
+ locate_windowfunc(n))));
}
-
- return qual;
}
@@ -1664,6 +1684,11 @@ transformWindowDefinitions(ParseState *pstate,
windef->refname),
parser_errposition(pstate, windef->location)));
wc->frameOptions = windef->frameOptions;
+ /* Process frame offset expressions */
+ wc->startOffset = transformFrameOffset(pstate, wc->frameOptions,
+ windef->startOffset);
+ wc->endOffset = transformFrameOffset(pstate, wc->frameOptions,
+ windef->endOffset);
wc->winref = winref;
result = lappend(result, wc);
@@ -2166,3 +2191,47 @@ findWindowClause(List *wclist, const char *name)
return NULL;
}
+
+/*
+ * transformFrameOffset
+ * Process a window frame offset expression
+ */
+static Node *
+transformFrameOffset(ParseState *pstate, int frameOptions, Node *clause)
+{
+ const char *constructName = NULL;
+ Node *node;
+
+ /* Quick exit if no offset expression */
+ if (clause == NULL)
+ return NULL;
+
+ /* Transform the raw expression tree */
+ node = transformExpr(pstate, clause);
+
+ if (frameOptions & FRAMEOPTION_ROWS)
+ {
+ /*
+ * Like LIMIT clause, simply coerce to int8
+ */
+ constructName = "ROWS";
+ node = coerce_to_specific_type(pstate, node, INT8OID, constructName);
+ }
+ else if (frameOptions & FRAMEOPTION_RANGE)
+ {
+ /*
+ * this needs a lot of thought to decide how to support in the
+ * context of Postgres' extensible datatype framework
+ */
+ constructName = "RANGE";
+ /* error was already thrown by gram.y, this is just a backstop */
+ elog(ERROR, "window frame with value offset is not implemented");
+ }
+ else
+ Assert(false);
+
+ /* Disallow variables and aggregates in frame offsets */
+ checkExprIsVarFree(pstate, node, constructName);
+
+ return node;
+}