aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/date.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/utils/adt/date.c
parent23209457314f6fd89fcd251a8173b0129aaa95a2 (diff)
downloadpostgresql-0a459cec96d3856f476c2db298c6b52f592894e8.tar.gz
postgresql-0a459cec96d3856f476c2db298c6b52f592894e8.zip
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/utils/adt/date.c')
-rw-r--r--src/backend/utils/adt/date.c107
1 files changed, 107 insertions, 0 deletions
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 747ef497897..eea29044146 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1011,6 +1011,34 @@ timestamptz_cmp_date(PG_FUNCTION_ARGS)
PG_RETURN_INT32(timestamptz_cmp_internal(dt1, dt2));
}
+/*
+ * in_range support function for date.
+ *
+ * We implement this by promoting the dates to timestamp (without time zone)
+ * and then using the timestamp-and-interval in_range function.
+ */
+Datum
+in_range_date_interval(PG_FUNCTION_ARGS)
+{
+ DateADT val = PG_GETARG_DATEADT(0);
+ DateADT base = PG_GETARG_DATEADT(1);
+ Interval *offset = PG_GETARG_INTERVAL_P(2);
+ bool sub = PG_GETARG_BOOL(3);
+ bool less = PG_GETARG_BOOL(4);
+ Timestamp valStamp;
+ Timestamp baseStamp;
+
+ valStamp = date2timestamp(val);
+ baseStamp = date2timestamp(base);
+
+ return DirectFunctionCall5(in_range_timestamp_interval,
+ TimestampGetDatum(valStamp),
+ TimestampGetDatum(baseStamp),
+ IntervalPGetDatum(offset),
+ BoolGetDatum(sub),
+ BoolGetDatum(less));
+}
+
/* Add an interval to a date, giving a new date.
* Must handle both positive and negative intervals.
@@ -1842,6 +1870,45 @@ time_mi_interval(PG_FUNCTION_ARGS)
PG_RETURN_TIMEADT(result);
}
+/*
+ * in_range support function for time.
+ */
+Datum
+in_range_time_interval(PG_FUNCTION_ARGS)
+{
+ TimeADT val = PG_GETARG_TIMEADT(0);
+ TimeADT base = PG_GETARG_TIMEADT(1);
+ Interval *offset = PG_GETARG_INTERVAL_P(2);
+ bool sub = PG_GETARG_BOOL(3);
+ bool less = PG_GETARG_BOOL(4);
+ TimeADT sum;
+
+ /*
+ * Like time_pl_interval/time_mi_interval, we disregard the month and day
+ * fields of the offset. So our test for negative should too.
+ */
+ if (offset->time < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PRECEDING_FOLLOWING_SIZE),
+ errmsg("invalid preceding or following size in window function")));
+
+ /*
+ * We can't use time_pl_interval/time_mi_interval here, because their
+ * wraparound behavior would give wrong (or at least undesirable) answers.
+ * Fortunately the equivalent non-wrapping behavior is trivial, especially
+ * since we don't worry about integer overflow.
+ */
+ if (sub)
+ sum = base - offset->time;
+ else
+ sum = base + offset->time;
+
+ if (less)
+ PG_RETURN_BOOL(val <= sum);
+ else
+ PG_RETURN_BOOL(val >= sum);
+}
+
/* time_part()
* Extract specified field from time type.
@@ -2305,6 +2372,46 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
PG_RETURN_TIMETZADT_P(result);
}
+/*
+ * in_range support function for timetz.
+ */
+Datum
+in_range_timetz_interval(PG_FUNCTION_ARGS)
+{
+ TimeTzADT *val = PG_GETARG_TIMETZADT_P(0);
+ TimeTzADT *base = PG_GETARG_TIMETZADT_P(1);
+ Interval *offset = PG_GETARG_INTERVAL_P(2);
+ bool sub = PG_GETARG_BOOL(3);
+ bool less = PG_GETARG_BOOL(4);
+ TimeTzADT sum;
+
+ /*
+ * Like timetz_pl_interval/timetz_mi_interval, we disregard the month and
+ * day fields of the offset. So our test for negative should too.
+ */
+ if (offset->time < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PRECEDING_FOLLOWING_SIZE),
+ errmsg("invalid preceding or following size in window function")));
+
+ /*
+ * We can't use timetz_pl_interval/timetz_mi_interval here, because their
+ * wraparound behavior would give wrong (or at least undesirable) answers.
+ * Fortunately the equivalent non-wrapping behavior is trivial, especially
+ * since we don't worry about integer overflow.
+ */
+ if (sub)
+ sum.time = base->time - offset->time;
+ else
+ sum.time = base->time + offset->time;
+ sum.zone = base->zone;
+
+ if (less)
+ PG_RETURN_BOOL(timetz_cmp_internal(val, &sum) <= 0);
+ else
+ PG_RETURN_BOOL(timetz_cmp_internal(val, &sum) >= 0);
+}
+
/* overlaps_timetz() --- implements the SQL OVERLAPS operator.
*
* Algorithm is per SQL spec. This is much harder than you'd think