aboutsummaryrefslogtreecommitdiff
path: root/src/backend/parser
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/parser')
-rw-r--r--src/backend/parser/gram.y71
-rw-r--r--src/backend/parser/parse_agg.c8
-rw-r--r--src/backend/parser/parse_clause.c156
-rw-r--r--src/backend/parser/parse_expr.c3
-rw-r--r--src/backend/parser/parse_func.c1
5 files changed, 203 insertions, 36 deletions
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5329432f25c..d99f2be2c97 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -570,6 +570,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%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 <ival> opt_window_exclusion_clause
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
%type <ival> generated_when override_kind
@@ -632,7 +633,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR
FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
- GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING
+ GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS
HANDLER HAVING HEADER_P HOLD HOUR_P
@@ -656,7 +657,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
NULLS_P NUMERIC
OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OPTIONS OR
- ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
+ ORDER ORDINALITY OTHERS OUT_P OUTER_P
+ OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
@@ -676,7 +678,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
SUBSCRIPTION SUBSTRING SYMMETRIC SYSID SYSTEM_P
TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
- TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM TREAT TRIGGER TRIM TRUE_P
+ TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
+ TREAT TRIGGER TRIM TRUE_P
TRUNCATE TRUSTED TYPE_P TYPES_P
UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED
@@ -724,9 +727,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* 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,
- * RANGE, ROWS to support opt_existing_window_name; and for RANGE, ROWS
- * so that they can follow a_expr without creating postfix-operator problems;
+ * same as if they weren't keywords). We need to do this:
+ * for PARTITION, RANGE, ROWS, GROUPS to support opt_existing_window_name;
+ * for RANGE, ROWS, GROUPS so that they can follow a_expr without creating
+ * postfix-operator problems;
* for GENERATED so that it can follow b_expr;
* and for NULL so that it can follow b_expr in ColQualList without creating
* postfix-operator problems.
@@ -746,7 +750,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* blame any funny behavior of UNBOUNDED on the SQL standard, though.
*/
%nonassoc UNBOUNDED /* ideally should have same precedence as IDENT */
-%nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING CUBE ROLLUP
+%nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
%left Op OPERATOR /* multi-character ops and user-defined operators */
%left '+' '-'
%left '*' '/' '%'
@@ -14003,7 +14007,7 @@ window_specification: '(' opt_existing_window_name opt_partition_clause
;
/*
- * If we see PARTITION, RANGE, or ROWS as the first token after the '('
+ * If we see PARTITION, RANGE, ROWS or GROUPS 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
@@ -14023,33 +14027,27 @@ 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 <window frame exclusion> yet.
*/
opt_frame_clause:
- RANGE frame_extent
+ RANGE frame_extent opt_window_exclusion_clause
{
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->frameOptions |= $3;
$$ = n;
}
- | ROWS frame_extent
+ | ROWS frame_extent opt_window_exclusion_clause
{
WindowDef *n = $2;
n->frameOptions |= FRAMEOPTION_NONDEFAULT | FRAMEOPTION_ROWS;
+ n->frameOptions |= $3;
+ $$ = n;
+ }
+ | GROUPS frame_extent opt_window_exclusion_clause
+ {
+ WindowDef *n = $2;
+ n->frameOptions |= FRAMEOPTION_NONDEFAULT | FRAMEOPTION_GROUPS;
+ n->frameOptions |= $3;
$$ = n;
}
| /*EMPTY*/
@@ -14071,7 +14069,7 @@ frame_extent: frame_bound
(errcode(ERRCODE_WINDOWING_ERROR),
errmsg("frame start cannot be UNBOUNDED FOLLOWING"),
parser_errposition(@1)));
- if (n->frameOptions & FRAMEOPTION_START_VALUE_FOLLOWING)
+ if (n->frameOptions & FRAMEOPTION_START_OFFSET_FOLLOWING)
ereport(ERROR,
(errcode(ERRCODE_WINDOWING_ERROR),
errmsg("frame starting from following row cannot end with current row"),
@@ -14100,13 +14098,13 @@ frame_extent: frame_bound
errmsg("frame end cannot be UNBOUNDED PRECEDING"),
parser_errposition(@4)));
if ((frameOptions & FRAMEOPTION_START_CURRENT_ROW) &&
- (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING))
+ (frameOptions & FRAMEOPTION_END_OFFSET_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 |
+ if ((frameOptions & FRAMEOPTION_START_OFFSET_FOLLOWING) &&
+ (frameOptions & (FRAMEOPTION_END_OFFSET_PRECEDING |
FRAMEOPTION_END_CURRENT_ROW)))
ereport(ERROR,
(errcode(ERRCODE_WINDOWING_ERROR),
@@ -14151,7 +14149,7 @@ frame_bound:
| a_expr PRECEDING
{
WindowDef *n = makeNode(WindowDef);
- n->frameOptions = FRAMEOPTION_START_VALUE_PRECEDING;
+ n->frameOptions = FRAMEOPTION_START_OFFSET_PRECEDING;
n->startOffset = $1;
n->endOffset = NULL;
$$ = n;
@@ -14159,13 +14157,21 @@ frame_bound:
| a_expr FOLLOWING
{
WindowDef *n = makeNode(WindowDef);
- n->frameOptions = FRAMEOPTION_START_VALUE_FOLLOWING;
+ n->frameOptions = FRAMEOPTION_START_OFFSET_FOLLOWING;
n->startOffset = $1;
n->endOffset = NULL;
$$ = n;
}
;
+opt_window_exclusion_clause:
+ EXCLUDE CURRENT_P ROW { $$ = FRAMEOPTION_EXCLUDE_CURRENT_ROW; }
+ | EXCLUDE GROUP_P { $$ = FRAMEOPTION_EXCLUDE_GROUP; }
+ | EXCLUDE TIES { $$ = FRAMEOPTION_EXCLUDE_TIES; }
+ | EXCLUDE NO OTHERS { $$ = 0; }
+ | /*EMPTY*/ { $$ = 0; }
+ ;
+
/*
* Supporting nonterminals for expressions.
@@ -15027,6 +15033,7 @@ unreserved_keyword:
| GENERATED
| GLOBAL
| GRANTED
+ | GROUPS
| HANDLER
| HEADER_P
| HOLD
@@ -15092,6 +15099,7 @@ unreserved_keyword:
| OPTION
| OPTIONS
| ORDINALITY
+ | OTHERS
| OVER
| OVERRIDING
| OWNED
@@ -15182,6 +15190,7 @@ unreserved_keyword:
| TEMPLATE
| TEMPORARY
| TEXT_P
+ | TIES
| TRANSACTION
| TRANSFORM
| TRIGGER
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 6a9f1b02178..747139489a0 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -420,6 +420,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in window ROWS");
break;
+ case EXPR_KIND_WINDOW_FRAME_GROUPS:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in window GROUPS");
+ else
+ err = _("grouping operations are not allowed in window GROUPS");
+
+ break;
case EXPR_KIND_SELECT_TARGET:
/* okay */
break;
@@ -835,6 +842,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
case EXPR_KIND_WINDOW_FRAME_ROWS:
+ case EXPR_KIND_WINDOW_FRAME_GROUPS:
err = _("window functions are not allowed in window definitions");
break;
case EXPR_KIND_SELECT_TARGET:
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 406cd1dad03..9bafc24083b 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -18,10 +18,13 @@
#include "miscadmin.h"
#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
#include "access/tsmapi.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_amproc.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint_fn.h"
#include "catalog/pg_type.h"
@@ -43,8 +46,11 @@
#include "parser/parse_target.h"
#include "parser/parse_type.h"
#include "rewrite/rewriteManip.h"
+#include "utils/builtins.h"
#include "utils/guc.h"
+#include "utils/catcache.h"
#include "utils/lsyscache.h"
+#include "utils/syscache.h"
#include "utils/rel.h"
@@ -95,6 +101,7 @@ static List *addTargetToGroupList(ParseState *pstate, TargetEntry *tle,
List *grouplist, List *targetlist, int location);
static WindowClause *findWindowClause(List *wclist, const char *name);
static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
+ Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
Node *clause);
@@ -2627,6 +2634,8 @@ transformWindowDefinitions(ParseState *pstate,
WindowClause *refwc = NULL;
List *partitionClause;
List *orderClause;
+ Oid rangeopfamily = InvalidOid;
+ Oid rangeopcintype = InvalidOid;
WindowClause *wc;
winref++;
@@ -2753,10 +2762,47 @@ transformWindowDefinitions(ParseState *pstate,
parser_errposition(pstate, windef->location)));
}
wc->frameOptions = windef->frameOptions;
+
+ /*
+ * RANGE offset PRECEDING/FOLLOWING requires exactly one ORDER BY
+ * column; check that and get its sort opfamily info.
+ */
+ if ((wc->frameOptions & FRAMEOPTION_RANGE) &&
+ (wc->frameOptions & (FRAMEOPTION_START_OFFSET |
+ FRAMEOPTION_END_OFFSET)))
+ {
+ SortGroupClause *sortcl;
+ Node *sortkey;
+ int16 rangestrategy;
+
+ if (list_length(wc->orderClause) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("RANGE with offset PRECEDING/FOLLOWING requires exactly one ORDER BY column"),
+ parser_errposition(pstate, windef->location)));
+ sortcl = castNode(SortGroupClause, linitial(wc->orderClause));
+ sortkey = get_sortgroupclause_expr(sortcl, *targetlist);
+ /* Find the sort operator in pg_amop */
+ if (!get_ordering_op_properties(sortcl->sortop,
+ &rangeopfamily,
+ &rangeopcintype,
+ &rangestrategy))
+ elog(ERROR, "operator %u is not a valid ordering operator",
+ sortcl->sortop);
+ /* Record properties of sort ordering */
+ wc->inRangeColl = exprCollation(sortkey);
+ wc->inRangeAsc = (rangestrategy == BTLessStrategyNumber);
+ wc->inRangeNullsFirst = sortcl->nulls_first;
+ }
+
/* Process frame offset expressions */
wc->startOffset = transformFrameOffset(pstate, wc->frameOptions,
+ rangeopfamily, rangeopcintype,
+ &wc->startInRangeFunc,
windef->startOffset);
wc->endOffset = transformFrameOffset(pstate, wc->frameOptions,
+ rangeopfamily, rangeopcintype,
+ &wc->endInRangeFunc,
windef->endOffset);
wc->winref = winref;
@@ -3489,13 +3535,24 @@ findWindowClause(List *wclist, const char *name)
/*
* transformFrameOffset
* Process a window frame offset expression
+ *
+ * In RANGE mode, rangeopfamily is the sort opfamily for the input ORDER BY
+ * column, and rangeopcintype is the input data type the sort operator is
+ * registered with. We expect the in_range function to be registered with
+ * that same type. (In binary-compatible cases, it might be different from
+ * the input column's actual type, so we can't use that for the lookups.)
+ * We'll return the OID of the in_range function to *inRangeFunc.
*/
static Node *
-transformFrameOffset(ParseState *pstate, int frameOptions, Node *clause)
+transformFrameOffset(ParseState *pstate, int frameOptions,
+ Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
+ Node *clause)
{
const char *constructName = NULL;
Node *node;
+ *inRangeFunc = InvalidOid; /* default result */
+
/* Quick exit if no offset expression */
if (clause == NULL)
return NULL;
@@ -3513,16 +3570,105 @@ transformFrameOffset(ParseState *pstate, int frameOptions, Node *clause)
}
else if (frameOptions & FRAMEOPTION_RANGE)
{
+ /*
+ * We must look up the in_range support function that's to be used,
+ * possibly choosing one of several, and coerce the "offset" value to
+ * the appropriate input type.
+ */
+ Oid nodeType;
+ Oid preferredType;
+ int nfuncs = 0;
+ int nmatches = 0;
+ Oid selectedType = InvalidOid;
+ Oid selectedFunc = InvalidOid;
+ CatCList *proclist;
+ int i;
+
/* Transform the raw expression tree */
node = transformExpr(pstate, clause, EXPR_KIND_WINDOW_FRAME_RANGE);
+ nodeType = exprType(node);
+
+ /*
+ * If there are multiple candidates, we'll prefer the one that exactly
+ * matches nodeType; or if nodeType is as yet unknown, prefer the one
+ * that exactly matches the sort column type. (The second rule is
+ * like what we do for "known_type operator unknown".)
+ */
+ preferredType = (nodeType != UNKNOWNOID) ? nodeType : rangeopcintype;
+
+ /* Find the in_range support functions applicable to this case */
+ proclist = SearchSysCacheList2(AMPROCNUM,
+ ObjectIdGetDatum(rangeopfamily),
+ ObjectIdGetDatum(rangeopcintype));
+ for (i = 0; i < proclist->n_members; i++)
+ {
+ HeapTuple proctup = &proclist->members[i]->tuple;
+ Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup);
+
+ /* The search will find all support proc types; ignore others */
+ if (procform->amprocnum != BTINRANGE_PROC)
+ continue;
+ nfuncs++;
+
+ /* Ignore function if given value can't be coerced to that type */
+ if (!can_coerce_type(1, &nodeType, &procform->amprocrighttype,
+ COERCION_IMPLICIT))
+ continue;
+ nmatches++;
+
+ /* Remember preferred match, or any match if didn't find that */
+ if (selectedType != preferredType)
+ {
+ selectedType = procform->amprocrighttype;
+ selectedFunc = procform->amproc;
+ }
+ }
+ ReleaseCatCacheList(proclist);
/*
- * this needs a lot of thought to decide how to support in the context
- * of Postgres' extensible datatype framework
+ * Throw error if needed. It seems worth taking the trouble to
+ * distinguish "no support at all" from "you didn't match any
+ * available offset type".
*/
+ if (nfuncs == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("RANGE with offset PRECEDING/FOLLOWING is not supported for column type %s",
+ format_type_be(rangeopcintype)),
+ parser_errposition(pstate, exprLocation(node))));
+ if (nmatches == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("RANGE with offset PRECEDING/FOLLOWING is not supported for column type %s and offset type %s",
+ format_type_be(rangeopcintype),
+ format_type_be(nodeType)),
+ errhint("Cast the offset value to an appropriate type."),
+ parser_errposition(pstate, exprLocation(node))));
+ if (nmatches != 1 && selectedType != preferredType)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("RANGE with offset PRECEDING/FOLLOWING has multiple interpretations for column type %s and offset type %s",
+ format_type_be(rangeopcintype),
+ format_type_be(nodeType)),
+ errhint("Cast the offset value to the exact intended type."),
+ parser_errposition(pstate, exprLocation(node))));
+
+ /* OK, coerce the offset to the right type */
constructName = "RANGE";
- /* error was already thrown by gram.y, this is just a backstop */
- elog(ERROR, "window frame with value offset is not implemented");
+ node = coerce_to_specific_type(pstate, node,
+ selectedType, constructName);
+ *inRangeFunc = selectedFunc;
+ }
+ else if (frameOptions & FRAMEOPTION_GROUPS)
+ {
+ /* Transform the raw expression tree */
+ node = transformExpr(pstate, clause, EXPR_KIND_WINDOW_FRAME_GROUPS);
+
+ /*
+ * Like LIMIT clause, simply coerce to int8
+ */
+ constructName = "GROUPS";
+ node = coerce_to_specific_type(pstate, node, INT8OID, constructName);
}
else
{
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index b2f5e46e3bb..d45926f27fd 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1805,6 +1805,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
case EXPR_KIND_WINDOW_FRAME_ROWS:
+ case EXPR_KIND_WINDOW_FRAME_GROUPS:
case EXPR_KIND_SELECT_TARGET:
case EXPR_KIND_INSERT_TARGET:
case EXPR_KIND_UPDATE_SOURCE:
@@ -3428,6 +3429,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "window RANGE";
case EXPR_KIND_WINDOW_FRAME_ROWS:
return "window ROWS";
+ case EXPR_KIND_WINDOW_FRAME_GROUPS:
+ return "window GROUPS";
case EXPR_KIND_SELECT_TARGET:
return "SELECT";
case EXPR_KIND_INSERT_TARGET:
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index ffae0f3cf31..4a7bc77c0f7 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2227,6 +2227,7 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
break;
case EXPR_KIND_WINDOW_FRAME_RANGE:
case EXPR_KIND_WINDOW_FRAME_ROWS:
+ case EXPR_KIND_WINDOW_FRAME_GROUPS:
err = _("set-returning functions are not allowed in window definitions");
break;
case EXPR_KIND_SELECT_TARGET: