summaryrefslogtreecommitdiff
path: root/src/backend/parser/parse_clause.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2018-02-07 00:06:50 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2018-02-07 00:06:56 -0500
commit0a459cec96d3856f476c2db298c6b52f592894e8 (patch)
tree3d10f137b48de039c46914fa8e854bd69daaaec1 /src/backend/parser/parse_clause.c
parent23209457314f6fd89fcd251a8173b0129aaa95a2 (diff)
downloadpostgresql-0a459cec96d3856f476c2db298c6b52f592894e8.tar.gz
Support all SQL:2011 options for window frame clauses.
This patch adds the ability to use "RANGE offset PRECEDING/FOLLOWING" frame boundaries in window functions. We'd punted on that back in the original patch to add window functions, because it was not clear how to do it in a reasonably data-type-extensible fashion. That problem is resolved here by adding the ability for btree operator classes to provide an "in_range" support function that defines how to add or subtract the RANGE offset value. Factoring it this way also allows the operator class to avoid overflow problems near the ends of the datatype's range, if it wishes to expend effort on that. (In the committed patch, the integer opclasses handle that issue, but it did not seem worth the trouble to avoid overflow failures for datetime types.) The patch includes in_range support for the integer_ops opfamily (int2/int4/int8) as well as the standard datetime types. Support for other numeric types has been requested, but that seems like suitable material for a follow-on patch. In addition, the patch adds GROUPS mode which counts the offset in ORDER-BY peer groups rather than rows, and it adds the frame_exclusion options specified by SQL:2011. As far as I can see, we are now fully up to spec on window framing options. Existing behaviors remain unchanged, except that I changed the errcode for a couple of existing error reports to meet the SQL spec's expectation that negative "offset" values should be reported as SQLSTATE 22013. Internally and in relevant parts of the documentation, we now consistently use the terminology "offset PRECEDING/FOLLOWING" rather than "value PRECEDING/FOLLOWING", since the term "value" is confusingly vague. Oliver Ford, reviewed and whacked around some by me Discussion: https://postgr.es/m/CAGMVOdu9sivPAxbNN0X+q19Sfv9edEPv=HibOJhB14TJv_RCQg@mail.gmail.com
Diffstat (limited to 'src/backend/parser/parse_clause.c')
-rw-r--r--src/backend/parser/parse_clause.c156
1 files changed, 151 insertions, 5 deletions
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 406cd1dad0..9bafc24083 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
{