diff options
Diffstat (limited to 'src/backend/parser')
-rw-r--r-- | src/backend/parser/gram.y | 141 | ||||
-rw-r--r-- | src/backend/parser/parse_agg.c | 7 | ||||
-rw-r--r-- | src/backend/parser/parse_clause.c | 93 |
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; +} |