diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e918133874..768d7fb0c0 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -14514,6 +14514,11 @@ NULL baz(3 rows)
lower_inf, and upper_inf
functions all return false for an empty range.
+
+
+ See also about the aggregate
+ function range_agg for use with ranges.
+
@@ -14832,6 +14837,76 @@ NULL baz(3 rows)
+ range_agg
+
+
+ range_agg(expression)
+
+
+
+ any range type
+
+
+ same as argument type
+
+ No
+ input ranges merged into a single range, or an error if there are any gaps or overlaps
+
+
+
+
+
+ range_agg
+
+
+ range_agg(expression,
+ permit_gaps)
+
+
+
+ (any range type, boolean)
+
+
+ array of the same range type as argument type
+
+ No
+
+ input ranges merged into a list of non-touching ranges,
+ with gaps permitted, rejected, or yielding a NULL result
+ as the second argument is true, false, or NULL respectively
+
+
+
+
+
+
+ range_agg
+
+
+ range_agg(expression,
+ permit_gaps,
+ permit_overlaps)
+
+
+
+ (any range type, boolean, boolean)
+
+
+ array of the same range type as argument type
+
+ No
+
+ input ranges merged into a list of non-touching ranges,
+ with gaps permitted, rejected, or yielding a NULL result
+ as the second argument is true, false, or NULL respectively,
+ and overlaps permitted, rejected, or yielding a NULL result
+ as the third argument is true, false, or NULL respectively
+
+
+
+
+
+
string_agg
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index cb16d701d8..bfa46bd389 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -34,6 +34,7 @@
#include "lib/stringinfo.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
+#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/hashutils.h"
@@ -54,6 +55,18 @@ typedef struct RangeIOData
FmgrInfo proc; /* lookup result for typiofunc */
} RangeIOData;
+/* aggregate state for range_agg */
+typedef struct RangeAggState
+{
+ ArrayBuildState *inputs;
+ uint8 flags;
+} RangeAggState;
+#define RANGE_AGG_GAPS_RAISE 0x01
+#define RANGE_AGG_GAPS_SET_NULL 0x02
+#define RANGE_AGG_OVERLAPS_RAISE 0x04
+#define RANGE_AGG_OVERLAPS_SET_NULL 0x08
+#define RANGE_AGG_GAPS_FLAGS (RANGE_AGG_GAPS_RAISE | RANGE_AGG_GAPS_SET_NULL)
+#define RANGE_AGG_OVERLAPS_FLAGS (RANGE_AGG_OVERLAPS_RAISE | RANGE_AGG_OVERLAPS_SET_NULL)
static RangeIOData *get_range_io_data(FunctionCallInfo fcinfo, Oid rngtypid,
IOFuncSelector func);
@@ -69,6 +82,7 @@ static Size datum_compute_size(Size sz, Datum datum, bool typbyval,
char typalign, int16 typlen, char typstorage);
static Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
char typalign, int16 typlen, char typstorage);
+static int element_compare(const void *key1, const void *key2, void *arg);
/*
@@ -1135,6 +1149,189 @@ range_intersect(PG_FUNCTION_ARGS)
PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
}
+/* Aggregate functions */
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ * We can't simply use array_append as the transfn
+ * because we have to capture the second & third parameters
+ * and put them in the aggregate's running state,
+ * so that our finalfn can use them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+ Oid rangeTypeId;
+ MemoryContext aggContext;
+ RangeAggState *state;
+
+ if (!AggCheckCallContext(fcinfo, &aggContext))
+ {
+ elog(ERROR, "range_agg_transfn called in non-aggregate context");
+ }
+
+ rangeTypeId = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ if (!type_is_range(rangeTypeId))
+ {
+ ereport(ERROR, (errmsg("range_agg must be called with a range")));
+ }
+ if (PG_ARGISNULL(0))
+ {
+ state = MemoryContextAlloc(aggContext, sizeof(RangeAggState));
+ state->inputs = initArrayResult(rangeTypeId, aggContext, false);
+
+ state->flags = 0x0;
+ switch (PG_NARGS())
+ {
+ case 2:
+ state->flags |= RANGE_AGG_GAPS_RAISE;
+ state->flags |= RANGE_AGG_OVERLAPS_RAISE;
+ break;
+ case 3:
+ if (PG_ARGISNULL(2)) state->flags |= RANGE_AGG_GAPS_SET_NULL;
+ else if (!PG_GETARG_BOOL(2)) state->flags |= RANGE_AGG_GAPS_RAISE;
+ state->flags |= RANGE_AGG_OVERLAPS_RAISE;
+ break;
+ case 4:
+ if (PG_ARGISNULL(2)) state->flags |= RANGE_AGG_GAPS_SET_NULL;
+ else if (!PG_GETARG_BOOL(2)) state->flags |= RANGE_AGG_GAPS_RAISE;
+ if (PG_ARGISNULL(3)) state->flags |= RANGE_AGG_OVERLAPS_SET_NULL;
+ else if (!PG_GETARG_BOOL(3)) state->flags |= RANGE_AGG_OVERLAPS_RAISE;
+ break;
+ default:
+ Assert(false);
+ }
+ }
+ else
+ {
+ state = (RangeAggState *)PG_GETARG_POINTER(0);
+ }
+
+ /* Might as well skip NULLs here so the finalfn doesn't have to: */
+ if (!PG_ARGISNULL(1))
+ {
+ accumArrayResult(state->inputs, PG_GETARG_DATUM(1), false,
+ rangeTypeId, aggContext);
+ }
+
+ PG_RETURN_POINTER(state);
+}
+
+/* range_agg_finalfn: combine adjacent/overlapping ranges */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+ MemoryContext aggContext;
+ Oid rangeTypeId;
+ TypeCacheEntry *typcache;
+ RangeAggState *state;
+ ArrayBuildState *inputArray;
+ int inputLength;
+ Datum *inputVals;
+ bool *inputNulls;
+ int i;
+ RangeType *currentRange;
+ RangeType *lastRange;
+ char *r1Str, *r2Str;
+ ArrayBuildState *resultContent;
+ Datum result;
+
+ if (!AggCheckCallContext(fcinfo, &aggContext))
+ {
+ elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+ }
+
+ state = PG_ARGISNULL(0) ? NULL : (RangeAggState *)PG_GETARG_POINTER(0);
+ if (state == NULL) PG_RETURN_NULL();
+ inputArray = state->inputs;
+ inputVals = inputArray->dvalues;
+ inputNulls = inputArray->dnulls;
+ inputLength = inputArray->nelems;
+ rangeTypeId = inputArray->element_type;
+
+ typcache = range_get_typcache(fcinfo, rangeTypeId);
+ if (inputLength == 0) PG_RETURN_NULL();
+ qsort_arg(inputVals, inputLength, sizeof(Datum), element_compare, typcache);
+
+ resultContent = initArrayResult(rangeTypeId, aggContext, false);
+ lastRange = DatumGetRangeTypeP(inputVals[0]);
+ for (i = 1; i < inputLength; i++)
+ {
+ Assert(!inputNulls[i]);
+ currentRange = DatumGetRangeTypeP(inputVals[i]);
+ /* range_adjacent_interval gives true
+ * if *either* A meets B or B meets A,
+ * which is not quite what we want,
+ * but we rely on the sorting above
+ * to rule out B meets A ever happening.
+ */
+ if (range_adjacent_internal(typcache, lastRange, currentRange))
+ {
+ /* The two ranges touch without overlap: */
+ lastRange = range_union_internal(typcache, lastRange, currentRange, false);
+ }
+ else if (range_before_internal(typcache, lastRange, currentRange))
+ {
+ /* There is a gap: */
+ if (state->flags & RANGE_AGG_GAPS_FLAGS)
+ {
+ if (state->flags & RANGE_AGG_GAPS_RAISE)
+ {
+ r1Str = "lastRange";
+ r2Str = "currentRange";
+ // TODO: Why is this segfaulting?:
+ // r1Str = DatumGetCString(DirectFunctionCall1(range_out, RangeTypePGetDatum(lastRange)));
+ // r2Str = DatumGetCString(DirectFunctionCall1(range_out, RangeTypePGetDatum(currentRange)));
+ ereport(ERROR, (errmsg("range_agg: gap detected between %s and %s", r1Str, r2Str)));
+ }
+ else
+ {
+ Assert(state->flags & RANGE_AGG_GAPS_SET_NULL);
+ PG_RETURN_NULL();
+ }
+ }
+ accumArrayResult(resultContent, RangeTypePGetDatum(lastRange), false, rangeTypeId, aggContext);
+ lastRange = currentRange;
+
+ }
+ else
+ {
+ /* They must overlap: */
+ if (state->flags & RANGE_AGG_OVERLAPS_FLAGS)
+ {
+ if (state->flags & RANGE_AGG_OVERLAPS_RAISE)
+ {
+ r1Str = "lastRange"; r2Str = "currentRange";
+ // TODO: Why is this segfaulting?:
+ // r1Str = DatumGetCString(DirectFunctionCall1(range_out, RangeTypePGetDatum(lastRange)));
+ // r2Str = DatumGetCString(DirectFunctionCall1(range_out, RangeTypePGetDatum(currentRange)));
+ ereport(ERROR, (errmsg("range_agg: overlap detected between %s and %s", r1Str, r2Str)));
+ }
+ else
+ {
+ Assert(state->flags & RANGE_AGG_OVERLAPS_SET_NULL);
+ PG_RETURN_NULL();
+ }
+ }
+ lastRange = range_union_internal(typcache, lastRange, currentRange, false);
+ }
+ }
+ accumArrayResult(resultContent, RangeTypePGetDatum(lastRange), false, rangeTypeId, aggContext);
+
+ if (type_is_array(get_fn_expr_rettype(fcinfo->flinfo)))
+ {
+ result = makeArrayResult(resultContent, CurrentMemoryContext);
+ PG_RETURN_DATUM(result);
+ }
+ else
+ {
+ PG_RETURN_DATUM(RangeTypePGetDatum(lastRange));
+ }
+}
+
/* Btree support */
/* btree comparator */
@@ -2488,3 +2685,33 @@ datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign,
return ptr;
}
+
+/*
+ * Compare two ranges so we can qsort them.
+ */
+static int
+element_compare(const void *key1, const void *key2, void *arg)
+{
+ Datum *d1 = (Datum *)key1;
+ Datum *d2 = (Datum *)key2;
+ RangeType *r1 = DatumGetRangeTypeP(*d1);
+ RangeType *r2 = DatumGetRangeTypeP(*d2);
+ TypeCacheEntry *typcache = (TypeCacheEntry *) arg;
+ RangeBound lower1, lower2;
+ RangeBound upper1, upper2;
+ bool empty1, empty2;
+ int cmp;
+
+ range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+ range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+ if (empty1 && empty2) cmp = 0;
+ else if (empty1) cmp = -1;
+ else if (empty2) cmp = 1;
+ else {
+ cmp = range_cmp_bounds(typcache, &lower1, &lower2);
+ if (cmp == 0) cmp = range_cmp_bounds(typcache, &upper1, &upper2);
+ }
+
+ return cmp;
+}
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 044695a046..1c8aec3977 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -544,6 +544,47 @@
{ aggfnoid => 'jsonb_object_agg', aggtransfn => 'jsonb_object_agg_transfn',
aggfinalfn => 'jsonb_object_agg_finalfn', aggtranstype => 'internal' },
+# range
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+ aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+ aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(int4range,bool)', aggtransfn => 'int4range_agg_transfn',
+ aggfinalfn => 'int4range_agg_finalfn', aggfinalextra => 't',
+ aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(int8range,bool)', aggtransfn => 'int8range_agg_transfn',
+ aggfinalfn => 'int8range_agg_finalfn', aggfinalextra => 't',
+ aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(numrange,bool)', aggtransfn => 'numrange_agg_transfn',
+ aggfinalfn => 'numrange_agg_finalfn', aggfinalextra => 't',
+ aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(tsrange,bool)', aggtransfn => 'tsrange_agg_transfn',
+ aggfinalfn => 'tsrange_agg_finalfn', aggfinalextra => 't',
+ aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(tstzrange,bool)', aggtransfn => 'tstzrange_agg_transfn',
+ aggfinalfn => 'tstzrange_agg_finalfn', aggfinalextra => 't',
+ aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(daterange,bool)', aggtransfn => 'daterange_agg_transfn',
+ aggfinalfn => 'daterange_agg_finalfn', aggfinalextra => 't',
+ aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(int4range,bool,bool)', aggtransfn => 'int4rangebb_agg_transfn',
+ aggfinalfn => 'int4rangebb_agg_finalfn', aggfinalextra => 't',
+ aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(int8range,bool,bool)', aggtransfn => 'int8rangebb_agg_transfn',
+ aggfinalfn => 'int8rangebb_agg_finalfn', aggfinalextra => 't',
+ aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(numrange,bool,bool)', aggtransfn => 'numrangebb_agg_transfn',
+ aggfinalfn => 'numrangebb_agg_finalfn', aggfinalextra => 't',
+ aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(tsrange,bool,bool)', aggtransfn => 'tsrangebb_agg_transfn',
+ aggfinalfn => 'tsrangebb_agg_finalfn', aggfinalextra => 't',
+ aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(tstzrange,bool,bool)', aggtransfn => 'tstzrangebb_agg_transfn',
+ aggfinalfn => 'tstzrangebb_agg_finalfn', aggfinalextra => 't',
+ aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(daterange,bool,bool)', aggtransfn => 'daterangebb_agg_transfn',
+ aggfinalfn => 'daterangebb_agg_finalfn', aggfinalextra => 't',
+ aggtranstype => 'internal' },
+
# ordered-set and hypothetical-set aggregates
{ aggfnoid => 'percentile_disc(float8,anyelement)', aggkind => 'o',
aggnumdirectargs => '1', aggtransfn => 'ordered_set_transition',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87335248a0..249220c8a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9538,6 +9538,144 @@
{ oid => '3869',
proname => 'range_minus', prorettype => 'anyrange',
proargtypes => 'anyrange anyrange', prosrc => 'range_minus' },
+{ oid => '8000', descr => 'aggregate transition function',
+ proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+ proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8001', descr => 'aggregate final function',
+ proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anyrange',
+ proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8002', descr => 'combinate aggregate input into a range',
+ proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+ prorettype => 'anyrange', proargtypes => 'anyrange',
+ prosrc => 'aggregate_dummy' },
+
+# Making 2- and 3-param range_agg polymorphic is difficult
+# because it would take an anyrange and return an anyrange[],
+# which doesn't exist.
+# As a workaround we define separate functions for each built-in range type.
+# This is what causes the mess in src/test/regress/expected/opr_sanity.out.
+{ oid => '8003', descr => 'aggregate transition function',
+ proname => 'int4range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+ proargtypes => 'internal int4range bool', prosrc => 'range_agg_transfn' },
+{ oid => '8004', descr => 'aggregate final function',
+ proname => 'int4range_agg_finalfn', proisstrict => 'f', prorettype => '_int4range',
+ proargtypes => 'internal int4range bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8005', descr => 'combinate aggregate input into a range',
+ proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+ prorettype => '_int4range', proargtypes => 'int4range bool',
+ prosrc => 'aggregate_dummy' },
+{ oid => '8006', descr => 'aggregate transition function',
+ proname => 'int8range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+ proargtypes => 'internal int8range bool', prosrc => 'range_agg_transfn' },
+{ oid => '8007', descr => 'aggregate final function',
+ proname => 'int8range_agg_finalfn', proisstrict => 'f', prorettype => '_int8range',
+ proargtypes => 'internal int8range bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8008', descr => 'combinate aggregate input into a range',
+ proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+ prorettype => '_int8range', proargtypes => 'int8range bool',
+ prosrc => 'aggregate_dummy' },
+{ oid => '8009', descr => 'aggregate transition function',
+ proname => 'numrange_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+ proargtypes => 'internal numrange bool', prosrc => 'range_agg_transfn' },
+{ oid => '8010', descr => 'aggregate final function',
+ proname => 'numrange_agg_finalfn', proisstrict => 'f', prorettype => '_numrange',
+ proargtypes => 'internal numrange bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8011', descr => 'combinate aggregate input into a range',
+ proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+ prorettype => '_numrange', proargtypes => 'numrange bool',
+ prosrc => 'aggregate_dummy' },
+{ oid => '8012', descr => 'aggregate transition function',
+ proname => 'tsrange_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+ proargtypes => 'internal tsrange bool', prosrc => 'range_agg_transfn' },
+{ oid => '8013', descr => 'aggregate final function',
+ proname => 'tsrange_agg_finalfn', proisstrict => 'f', prorettype => '_tsrange',
+ proargtypes => 'internal tsrange bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8014', descr => 'combinate aggregate input into a range',
+ proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+ prorettype => '_tsrange', proargtypes => 'tsrange bool',
+ prosrc => 'aggregate_dummy' },
+{ oid => '8015', descr => 'aggregate transition function',
+ proname => 'tstzrange_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+ proargtypes => 'internal tstzrange bool', prosrc => 'range_agg_transfn' },
+{ oid => '8016', descr => 'aggregate final function',
+ proname => 'tstzrange_agg_finalfn', proisstrict => 'f', prorettype => '_tstzrange',
+ proargtypes => 'internal tstzrange bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8017', descr => 'combinate aggregate input into a range',
+ proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+ prorettype => '_tstzrange', proargtypes => 'tstzrange bool',
+ prosrc => 'aggregate_dummy' },
+{ oid => '8018', descr => 'aggregate transition function',
+ proname => 'daterange_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+ proargtypes => 'internal daterange bool', prosrc => 'range_agg_transfn' },
+{ oid => '8019', descr => 'aggregate final function',
+ proname => 'daterange_agg_finalfn', proisstrict => 'f', prorettype => '_daterange',
+ proargtypes => 'internal daterange bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8020', descr => 'combinate aggregate input into a range',
+ proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+ prorettype => '_daterange', proargtypes => 'daterange bool',
+ prosrc => 'aggregate_dummy' },
+
+{ oid => '8021', descr => 'aggregate transition function',
+ proname => 'int4rangebb_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+ proargtypes => 'internal int4range bool bool', prosrc => 'range_agg_transfn' },
+{ oid => '8022', descr => 'aggregate final function',
+ proname => 'int4rangebb_agg_finalfn', proisstrict => 'f', prorettype => '_int4range',
+ proargtypes => 'internal int4range bool bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8023', descr => 'combinate aggregate input into a range',
+ proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+ prorettype => '_int4range', proargtypes => 'int4range bool bool',
+ prosrc => 'aggregate_dummy' },
+{ oid => '8024', descr => 'aggregate transition function',
+ proname => 'int8rangebb_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+ proargtypes => 'internal int8range bool bool', prosrc => 'range_agg_transfn' },
+{ oid => '8025', descr => 'aggregate final function',
+ proname => 'int8rangebb_agg_finalfn', proisstrict => 'f', prorettype => '_int8range',
+ proargtypes => 'internal int8range bool bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8026', descr => 'combinate aggregate input into a range',
+ proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+ prorettype => '_int8range', proargtypes => 'int8range bool bool',
+ prosrc => 'aggregate_dummy' },
+{ oid => '8027', descr => 'aggregate transition function',
+ proname => 'numrangebb_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+ proargtypes => 'internal numrange bool bool', prosrc => 'range_agg_transfn' },
+{ oid => '8028', descr => 'aggregate final function',
+ proname => 'numrangebb_agg_finalfn', proisstrict => 'f', prorettype => '_numrange',
+ proargtypes => 'internal numrange bool bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8029', descr => 'combinate aggregate input into a range',
+ proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+ prorettype => '_numrange', proargtypes => 'numrange bool bool',
+ prosrc => 'aggregate_dummy' },
+{ oid => '8030', descr => 'aggregate transition function',
+ proname => 'tsrangebb_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+ proargtypes => 'internal tsrange bool bool', prosrc => 'range_agg_transfn' },
+{ oid => '8031', descr => 'aggregate final function',
+ proname => 'tsrangebb_agg_finalfn', proisstrict => 'f', prorettype => '_tsrange',
+ proargtypes => 'internal tsrange bool bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8032', descr => 'combinate aggregate input into a range',
+ proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+ prorettype => '_tsrange', proargtypes => 'tsrange bool bool',
+ prosrc => 'aggregate_dummy' },
+{ oid => '8033', descr => 'aggregate transition function',
+ proname => 'tstzrangebb_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+ proargtypes => 'internal tstzrange bool bool', prosrc => 'range_agg_transfn' },
+{ oid => '8034', descr => 'aggregate final function',
+ proname => 'tstzrangebb_agg_finalfn', proisstrict => 'f', prorettype => '_tstzrange',
+ proargtypes => 'internal tstzrange bool bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8035', descr => 'combinate aggregate input into a range',
+ proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+ prorettype => '_tstzrange', proargtypes => 'tstzrange bool bool',
+ prosrc => 'aggregate_dummy' },
+{ oid => '8036', descr => 'aggregate transition function',
+ proname => 'daterangebb_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+ proargtypes => 'internal daterange bool bool', prosrc => 'range_agg_transfn' },
+{ oid => '8037', descr => 'aggregate final function',
+ proname => 'daterangebb_agg_finalfn', proisstrict => 'f', prorettype => '_daterange',
+ proargtypes => 'internal daterange bool bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8038', descr => 'combinate aggregate input into a range',
+ proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+ prorettype => '_daterange', proargtypes => 'daterange bool bool',
+ prosrc => 'aggregate_dummy' },
+
{ oid => '3870', descr => 'less-equal-greater',
proname => 'range_cmp', prorettype => 'int4',
proargtypes => 'anyrange anyrange', prosrc => 'range_cmp' },
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 85af36ee5b..a2cd607b31 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -168,9 +168,105 @@ WHERE p1.oid < p2.oid AND
p1.proretset != p2.proretset OR
p1.provolatile != p2.provolatile OR
p1.pronargs != p2.pronargs);
- oid | proname | oid | proname
------+---------+-----+---------
-(0 rows)
+ oid | proname | oid | proname
+------+-----------------------+------+-------------------------
+ 8019 | daterange_agg_finalfn | 8028 | numrangebb_agg_finalfn
+ 8019 | daterange_agg_finalfn | 8037 | daterangebb_agg_finalfn
+ 8019 | daterange_agg_finalfn | 8034 | tstzrangebb_agg_finalfn
+ 8019 | daterange_agg_finalfn | 8031 | tsrangebb_agg_finalfn
+ 8019 | daterange_agg_finalfn | 8025 | int8rangebb_agg_finalfn
+ 8019 | daterange_agg_finalfn | 8022 | int4rangebb_agg_finalfn
+ 8016 | tstzrange_agg_finalfn | 8028 | numrangebb_agg_finalfn
+ 8016 | tstzrange_agg_finalfn | 8037 | daterangebb_agg_finalfn
+ 8016 | tstzrange_agg_finalfn | 8034 | tstzrangebb_agg_finalfn
+ 8016 | tstzrange_agg_finalfn | 8031 | tsrangebb_agg_finalfn
+ 8016 | tstzrange_agg_finalfn | 8025 | int8rangebb_agg_finalfn
+ 8016 | tstzrange_agg_finalfn | 8022 | int4rangebb_agg_finalfn
+ 8013 | tsrange_agg_finalfn | 8028 | numrangebb_agg_finalfn
+ 8013 | tsrange_agg_finalfn | 8037 | daterangebb_agg_finalfn
+ 8013 | tsrange_agg_finalfn | 8034 | tstzrangebb_agg_finalfn
+ 8013 | tsrange_agg_finalfn | 8031 | tsrangebb_agg_finalfn
+ 8013 | tsrange_agg_finalfn | 8025 | int8rangebb_agg_finalfn
+ 8013 | tsrange_agg_finalfn | 8022 | int4rangebb_agg_finalfn
+ 8010 | numrange_agg_finalfn | 8028 | numrangebb_agg_finalfn
+ 8010 | numrange_agg_finalfn | 8037 | daterangebb_agg_finalfn
+ 8010 | numrange_agg_finalfn | 8034 | tstzrangebb_agg_finalfn
+ 8010 | numrange_agg_finalfn | 8031 | tsrangebb_agg_finalfn
+ 8010 | numrange_agg_finalfn | 8025 | int8rangebb_agg_finalfn
+ 8010 | numrange_agg_finalfn | 8022 | int4rangebb_agg_finalfn
+ 8007 | int8range_agg_finalfn | 8028 | numrangebb_agg_finalfn
+ 8007 | int8range_agg_finalfn | 8037 | daterangebb_agg_finalfn
+ 8007 | int8range_agg_finalfn | 8034 | tstzrangebb_agg_finalfn
+ 8007 | int8range_agg_finalfn | 8031 | tsrangebb_agg_finalfn
+ 8007 | int8range_agg_finalfn | 8025 | int8rangebb_agg_finalfn
+ 8007 | int8range_agg_finalfn | 8022 | int4rangebb_agg_finalfn
+ 8004 | int4range_agg_finalfn | 8028 | numrangebb_agg_finalfn
+ 8004 | int4range_agg_finalfn | 8037 | daterangebb_agg_finalfn
+ 8004 | int4range_agg_finalfn | 8034 | tstzrangebb_agg_finalfn
+ 8004 | int4range_agg_finalfn | 8031 | tsrangebb_agg_finalfn
+ 8004 | int4range_agg_finalfn | 8025 | int8rangebb_agg_finalfn
+ 8004 | int4range_agg_finalfn | 8022 | int4rangebb_agg_finalfn
+ 8001 | range_agg_finalfn | 8028 | numrangebb_agg_finalfn
+ 8001 | range_agg_finalfn | 8037 | daterangebb_agg_finalfn
+ 8001 | range_agg_finalfn | 8034 | tstzrangebb_agg_finalfn
+ 8001 | range_agg_finalfn | 8031 | tsrangebb_agg_finalfn
+ 8001 | range_agg_finalfn | 8025 | int8rangebb_agg_finalfn
+ 8001 | range_agg_finalfn | 8022 | int4rangebb_agg_finalfn
+ 8001 | range_agg_finalfn | 8019 | daterange_agg_finalfn
+ 8001 | range_agg_finalfn | 8016 | tstzrange_agg_finalfn
+ 8001 | range_agg_finalfn | 8013 | tsrange_agg_finalfn
+ 8001 | range_agg_finalfn | 8010 | numrange_agg_finalfn
+ 8001 | range_agg_finalfn | 8007 | int8range_agg_finalfn
+ 8001 | range_agg_finalfn | 8004 | int4range_agg_finalfn
+ 8003 | int4range_agg_transfn | 8036 | daterangebb_agg_transfn
+ 8003 | int4range_agg_transfn | 8027 | numrangebb_agg_transfn
+ 8003 | int4range_agg_transfn | 8030 | tsrangebb_agg_transfn
+ 8003 | int4range_agg_transfn | 8021 | int4rangebb_agg_transfn
+ 8003 | int4range_agg_transfn | 8024 | int8rangebb_agg_transfn
+ 8003 | int4range_agg_transfn | 8033 | tstzrangebb_agg_transfn
+ 8018 | daterange_agg_transfn | 8036 | daterangebb_agg_transfn
+ 8018 | daterange_agg_transfn | 8027 | numrangebb_agg_transfn
+ 8018 | daterange_agg_transfn | 8030 | tsrangebb_agg_transfn
+ 8018 | daterange_agg_transfn | 8021 | int4rangebb_agg_transfn
+ 8018 | daterange_agg_transfn | 8024 | int8rangebb_agg_transfn
+ 8018 | daterange_agg_transfn | 8033 | tstzrangebb_agg_transfn
+ 8015 | tstzrange_agg_transfn | 8036 | daterangebb_agg_transfn
+ 8015 | tstzrange_agg_transfn | 8027 | numrangebb_agg_transfn
+ 8015 | tstzrange_agg_transfn | 8030 | tsrangebb_agg_transfn
+ 8015 | tstzrange_agg_transfn | 8021 | int4rangebb_agg_transfn
+ 8015 | tstzrange_agg_transfn | 8024 | int8rangebb_agg_transfn
+ 8015 | tstzrange_agg_transfn | 8033 | tstzrangebb_agg_transfn
+ 8006 | int8range_agg_transfn | 8036 | daterangebb_agg_transfn
+ 8006 | int8range_agg_transfn | 8027 | numrangebb_agg_transfn
+ 8006 | int8range_agg_transfn | 8030 | tsrangebb_agg_transfn
+ 8006 | int8range_agg_transfn | 8021 | int4rangebb_agg_transfn
+ 8006 | int8range_agg_transfn | 8024 | int8rangebb_agg_transfn
+ 8006 | int8range_agg_transfn | 8033 | tstzrangebb_agg_transfn
+ 8012 | tsrange_agg_transfn | 8036 | daterangebb_agg_transfn
+ 8012 | tsrange_agg_transfn | 8027 | numrangebb_agg_transfn
+ 8012 | tsrange_agg_transfn | 8030 | tsrangebb_agg_transfn
+ 8012 | tsrange_agg_transfn | 8021 | int4rangebb_agg_transfn
+ 8012 | tsrange_agg_transfn | 8024 | int8rangebb_agg_transfn
+ 8012 | tsrange_agg_transfn | 8033 | tstzrangebb_agg_transfn
+ 8000 | range_agg_transfn | 8036 | daterangebb_agg_transfn
+ 8000 | range_agg_transfn | 8027 | numrangebb_agg_transfn
+ 8000 | range_agg_transfn | 8030 | tsrangebb_agg_transfn
+ 8000 | range_agg_transfn | 8003 | int4range_agg_transfn
+ 8000 | range_agg_transfn | 8021 | int4rangebb_agg_transfn
+ 8000 | range_agg_transfn | 8018 | daterange_agg_transfn
+ 8000 | range_agg_transfn | 8015 | tstzrange_agg_transfn
+ 8000 | range_agg_transfn | 8024 | int8rangebb_agg_transfn
+ 8000 | range_agg_transfn | 8006 | int8range_agg_transfn
+ 8000 | range_agg_transfn | 8012 | tsrange_agg_transfn
+ 8000 | range_agg_transfn | 8009 | numrange_agg_transfn
+ 8000 | range_agg_transfn | 8033 | tstzrangebb_agg_transfn
+ 8009 | numrange_agg_transfn | 8036 | daterangebb_agg_transfn
+ 8009 | numrange_agg_transfn | 8027 | numrangebb_agg_transfn
+ 8009 | numrange_agg_transfn | 8030 | tsrangebb_agg_transfn
+ 8009 | numrange_agg_transfn | 8021 | int4rangebb_agg_transfn
+ 8009 | numrange_agg_transfn | 8024 | int8rangebb_agg_transfn
+ 8009 | numrange_agg_transfn | 8033 | tstzrangebb_agg_transfn
+(96 rows)
-- Look for uses of different type OIDs in the argument/result type fields
-- for different aliases of the same built-in function.
@@ -196,7 +292,28 @@ ORDER BY 1, 2;
------------+------------
25 | 1043
1114 | 1184
-(2 rows)
+ 3831 | 3905
+ 3831 | 3907
+ 3831 | 3909
+ 3831 | 3911
+ 3831 | 3913
+ 3831 | 3927
+ 3905 | 3907
+ 3905 | 3909
+ 3905 | 3911
+ 3905 | 3913
+ 3905 | 3927
+ 3907 | 3909
+ 3907 | 3911
+ 3907 | 3913
+ 3907 | 3927
+ 3909 | 3911
+ 3909 | 3913
+ 3909 | 3927
+ 3911 | 3913
+ 3911 | 3927
+ 3913 | 3927
+(23 rows)
SELECT DISTINCT p1.proargtypes[0], p2.proargtypes[0]
FROM pg_proc AS p1, pg_proc AS p2
@@ -231,7 +348,28 @@ ORDER BY 1, 2;
23 | 28
1114 | 1184
1560 | 1562
-(3 rows)
+ 3831 | 3904
+ 3831 | 3906
+ 3831 | 3908
+ 3831 | 3910
+ 3831 | 3912
+ 3831 | 3926
+ 3904 | 3906
+ 3904 | 3908
+ 3904 | 3910
+ 3904 | 3912
+ 3904 | 3926
+ 3906 | 3908
+ 3906 | 3910
+ 3906 | 3912
+ 3906 | 3926
+ 3908 | 3910
+ 3908 | 3912
+ 3908 | 3926
+ 3910 | 3912
+ 3910 | 3926
+ 3912 | 3926
+(24 rows)
SELECT DISTINCT p1.proargtypes[2], p2.proargtypes[2]
FROM pg_proc AS p1, pg_proc AS p2
@@ -1716,10 +1854,58 @@ WHERE p1.oid < p2.oid AND p1.proname = p2.proname AND
p1.prokind = 'a' AND p2.prokind = 'a' AND
array_dims(p1.proargtypes) != array_dims(p2.proargtypes)
ORDER BY 1;
- oid | oid
---------------+---------
- count("any") | count()
-(1 row)
+ oid | oid
+------------------------------+--------------------------------------
+ count("any") | count()
+ range_agg(anyrange) | range_agg(daterange,boolean,boolean)
+ range_agg(anyrange) | range_agg(tstzrange,boolean,boolean)
+ range_agg(anyrange) | range_agg(tsrange,boolean,boolean)
+ range_agg(anyrange) | range_agg(numrange,boolean,boolean)
+ range_agg(anyrange) | range_agg(int8range,boolean,boolean)
+ range_agg(anyrange) | range_agg(int4range,boolean,boolean)
+ range_agg(anyrange) | range_agg(daterange,boolean)
+ range_agg(anyrange) | range_agg(tstzrange,boolean)
+ range_agg(anyrange) | range_agg(tsrange,boolean)
+ range_agg(anyrange) | range_agg(numrange,boolean)
+ range_agg(anyrange) | range_agg(int8range,boolean)
+ range_agg(anyrange) | range_agg(int4range,boolean)
+ range_agg(int4range,boolean) | range_agg(daterange,boolean,boolean)
+ range_agg(int4range,boolean) | range_agg(tstzrange,boolean,boolean)
+ range_agg(int4range,boolean) | range_agg(tsrange,boolean,boolean)
+ range_agg(int4range,boolean) | range_agg(numrange,boolean,boolean)
+ range_agg(int4range,boolean) | range_agg(int8range,boolean,boolean)
+ range_agg(int4range,boolean) | range_agg(int4range,boolean,boolean)
+ range_agg(int8range,boolean) | range_agg(daterange,boolean,boolean)
+ range_agg(int8range,boolean) | range_agg(tstzrange,boolean,boolean)
+ range_agg(int8range,boolean) | range_agg(tsrange,boolean,boolean)
+ range_agg(int8range,boolean) | range_agg(numrange,boolean,boolean)
+ range_agg(int8range,boolean) | range_agg(int8range,boolean,boolean)
+ range_agg(int8range,boolean) | range_agg(int4range,boolean,boolean)
+ range_agg(numrange,boolean) | range_agg(daterange,boolean,boolean)
+ range_agg(numrange,boolean) | range_agg(tstzrange,boolean,boolean)
+ range_agg(numrange,boolean) | range_agg(tsrange,boolean,boolean)
+ range_agg(numrange,boolean) | range_agg(numrange,boolean,boolean)
+ range_agg(numrange,boolean) | range_agg(int8range,boolean,boolean)
+ range_agg(numrange,boolean) | range_agg(int4range,boolean,boolean)
+ range_agg(tsrange,boolean) | range_agg(daterange,boolean,boolean)
+ range_agg(tsrange,boolean) | range_agg(tstzrange,boolean,boolean)
+ range_agg(tsrange,boolean) | range_agg(tsrange,boolean,boolean)
+ range_agg(tsrange,boolean) | range_agg(numrange,boolean,boolean)
+ range_agg(tsrange,boolean) | range_agg(int8range,boolean,boolean)
+ range_agg(tsrange,boolean) | range_agg(int4range,boolean,boolean)
+ range_agg(tstzrange,boolean) | range_agg(daterange,boolean,boolean)
+ range_agg(tstzrange,boolean) | range_agg(tstzrange,boolean,boolean)
+ range_agg(tstzrange,boolean) | range_agg(tsrange,boolean,boolean)
+ range_agg(tstzrange,boolean) | range_agg(numrange,boolean,boolean)
+ range_agg(tstzrange,boolean) | range_agg(int8range,boolean,boolean)
+ range_agg(tstzrange,boolean) | range_agg(int4range,boolean,boolean)
+ range_agg(daterange,boolean) | range_agg(daterange,boolean,boolean)
+ range_agg(daterange,boolean) | range_agg(tstzrange,boolean,boolean)
+ range_agg(daterange,boolean) | range_agg(tsrange,boolean,boolean)
+ range_agg(daterange,boolean) | range_agg(numrange,boolean,boolean)
+ range_agg(daterange,boolean) | range_agg(int8range,boolean,boolean)
+ range_agg(daterange,boolean) | range_agg(int4range,boolean,boolean)
+(49 rows)
-- For the same reason, built-in aggregates with default arguments are no good.
SELECT oid, proname
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index accf1e0d9e..703bc41491 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -1407,3 +1407,281 @@ create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
as $$ select $1, '[1,10]' $$ language sql;
ERROR: cannot determine result data type
DETAIL: A function returning "anyrange" must have at least one "anyrange" argument.
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14'))
+;
+-- range_agg with 1 arg:
+-- Forbidding gaps and overlaps:
+SELECT room_id, range_agg(booked_during)
+FROM reservations
+WHERE room_id = 1
+GROUP BY room_id
+ORDER BY room_id;
+ERROR: range_agg: gap detected between lastRange and currentRange
+SELECT room_id, range_agg(booked_during)
+FROM reservations
+WHERE room_id = 6
+GROUP BY room_id
+ORDER BY room_id;
+ERROR: range_agg: overlap detected between lastRange and currentRange
+SELECT room_id, range_agg(booked_during)
+FROM reservations
+WHERE room_id NOT IN (1, 6)
+GROUP BY room_id
+ORDER BY room_id;
+ room_id | range_agg
+---------+-------------------------
+ 2 | [07-01-2018,07-03-2018)
+ 3 |
+ 4 |
+ 5 | [07-01-2018,07-03-2018)
+ 7 | [07-01-2018,07-14-2018)
+(5 rows)
+
+-- Obeying discrete base types:
+SELECT range_agg(r)
+FROM (VALUES
+ (int4range( 0, 9, '[]')),
+ (int4range(10, 19, '[]'))
+) t(r);
+ range_agg
+-----------
+ [0,20)
+(1 row)
+
+-- range_agg with 2 args:
+-- Forbidding gaps (and overlaps):
+SELECT room_id, range_agg(booked_during, false)
+FROM reservations
+WHERE room_id = 1
+GROUP BY room_id;
+ERROR: range_agg: gap detected between lastRange and currentRange
+SELECT room_id, range_agg(booked_during, false)
+FROM reservations
+WHERE room_id = 6
+GROUP BY room_id;
+ERROR: range_agg: overlap detected between lastRange and currentRange
+SELECT room_id, range_agg(booked_during, false)
+FROM reservations
+WHERE room_id NOT IN (1, 6)
+GROUP BY room_id;
+ room_id | range_agg
+---------+-----------------------------
+ 3 |
+ 5 | {"[07-01-2018,07-03-2018)"}
+ 4 |
+ 2 | {"[07-01-2018,07-03-2018)"}
+ 7 | {"[07-01-2018,07-14-2018)"}
+(5 rows)
+
+-- Permitting gaps (but forbidding overlaps):
+SELECT room_id, range_agg(booked_during, true)
+FROM reservations
+WHERE room_id = 1
+GROUP BY room_id;
+ room_id | range_agg
+---------+-------------------------------------------------------
+ 1 | {"[07-01-2018,07-14-2018)","[07-20-2018,07-22-2018)"}
+(1 row)
+
+SELECT room_id, range_agg(booked_during, true)
+FROM reservations
+WHERE room_id = 6
+GROUP BY room_id;
+ERROR: range_agg: overlap detected between lastRange and currentRange
+SELECT room_id, range_agg(booked_during, true)
+FROM reservations
+WHERE room_id NOT IN (1, 6)
+GROUP BY room_id;
+ room_id | range_agg
+---------+-----------------------------
+ 3 |
+ 5 | {"[07-01-2018,07-03-2018)"}
+ 4 |
+ 2 | {"[07-01-2018,07-03-2018)"}
+ 7 | {"[07-01-2018,07-14-2018)"}
+(5 rows)
+
+-- range_agg with 3 args:
+-- Forbidding gaps and overlaps:
+SELECT room_id, range_agg(booked_during, false, false)
+FROM reservations
+WHERE room_id = 1
+GROUP BY room_id;
+ERROR: range_agg: gap detected between lastRange and currentRange
+SELECT room_id, range_agg(booked_during, false, false)
+FROM reservations
+WHERE room_id = 6
+GROUP BY room_id;
+ERROR: range_agg: overlap detected between lastRange and currentRange
+SELECT room_id, range_agg(booked_during, false, false)
+FROM reservations
+WHERE room_id NOT IN (1, 6)
+GROUP BY room_id;
+ room_id | range_agg
+---------+-----------------------------
+ 3 |
+ 5 | {"[07-01-2018,07-03-2018)"}
+ 4 |
+ 2 | {"[07-01-2018,07-03-2018)"}
+ 7 | {"[07-01-2018,07-14-2018)"}
+(5 rows)
+
+-- Forbidding gaps but permitting overlaps
+SELECT room_id, range_agg(booked_during, false, true)
+FROM reservations
+WHERE room_id = 1
+GROUP BY room_id;
+ERROR: range_agg: gap detected between lastRange and currentRange
+SELECT room_id, range_agg(booked_during, false, true)
+FROM reservations
+WHERE room_id = 6
+GROUP BY room_id;
+ room_id | range_agg
+---------+-----------------------------
+ 6 | {"[07-01-2018,07-10-2018)"}
+(1 row)
+
+SELECT room_id, range_agg(booked_during, false, true)
+FROM reservations
+WHERE room_id NOT IN (1, 6)
+GROUP BY room_id;
+ room_id | range_agg
+---------+-----------------------------
+ 3 |
+ 5 | {"[07-01-2018,07-03-2018)"}
+ 4 |
+ 2 | {"[07-01-2018,07-03-2018)"}
+ 7 | {"[07-01-2018,07-14-2018)"}
+(5 rows)
+
+-- Permitting gaps but forbidding overlaps
+SELECT room_id, range_agg(booked_during, true, false)
+FROM reservations
+WHERE room_id = 1
+GROUP BY room_id;
+ room_id | range_agg
+---------+-------------------------------------------------------
+ 1 | {"[07-01-2018,07-14-2018)","[07-20-2018,07-22-2018)"}
+(1 row)
+
+SELECT room_id, range_agg(booked_during, true, false)
+FROM reservations
+WHERE room_id = 6
+GROUP BY room_id;
+ERROR: range_agg: overlap detected between lastRange and currentRange
+SELECT room_id, range_agg(booked_during, true, false)
+FROM reservations
+WHERE room_id NOT IN (1, 6)
+GROUP BY room_id;
+ room_id | range_agg
+---------+-----------------------------
+ 3 |
+ 5 | {"[07-01-2018,07-03-2018)"}
+ 4 |
+ 2 | {"[07-01-2018,07-03-2018)"}
+ 7 | {"[07-01-2018,07-14-2018)"}
+(5 rows)
+
+-- Permitting gaps and overlaps:
+SELECT room_id, range_agg(booked_during, true, true)
+FROM reservations
+WHERE room_id = 1
+GROUP BY room_id;
+ room_id | range_agg
+---------+-------------------------------------------------------
+ 1 | {"[07-01-2018,07-14-2018)","[07-20-2018,07-22-2018)"}
+(1 row)
+
+SELECT room_id, range_agg(booked_during, true, true)
+FROM reservations
+WHERE room_id = 6
+GROUP BY room_id;
+ room_id | range_agg
+---------+-----------------------------
+ 6 | {"[07-01-2018,07-10-2018)"}
+(1 row)
+
+SELECT room_id, range_agg(booked_during, true, true)
+FROM reservations
+WHERE room_id NOT IN (1, 6)
+GROUP BY room_id;
+ room_id | range_agg
+---------+-----------------------------
+ 3 |
+ 5 | {"[07-01-2018,07-03-2018)"}
+ 4 |
+ 2 | {"[07-01-2018,07-03-2018)"}
+ 7 | {"[07-01-2018,07-14-2018)"}
+(5 rows)
+
+-- Obeying discrete base types:
+SELECT range_agg(r, false, false)
+FROM (VALUES
+ (int4range( 0, 5, '[]')),
+ (int4range( 7, 9, '[]'))
+) t(r);
+ERROR: range_agg: gap detected between lastRange and currentRange
+SELECT range_agg(r, false, false)
+FROM (VALUES
+ (int4range( 0, 5, '[]')),
+ (int4range( 5, 9, '[]'))
+) t(r);
+ERROR: range_agg: overlap detected between lastRange and currentRange
+SELECT range_agg(r, true, true)
+FROM (VALUES
+ (int4range( 0, 9, '[]')),
+ (int4range(10, 15, '[]')),
+ (int4range(20, 26, '[]')),
+ (int4range(26, 29, '[]'))
+) t(r);
+ range_agg
+----------------------
+ {"[0,16)","[20,30)"}
+(1 row)
+
+-- It combines with UNNEST
+-- to implement the temporal database "coalesce" function
+-- (see Snodgrass 6.5.2):
+SELECT room_id, t2.booked_during
+FROM (
+ SELECT room_id, range_agg(booked_during, true, true) AS booked_during
+ FROM reservations
+ GROUP BY room_id
+ ) AS t1,
+ UNNEST(t1.booked_during) AS t2(booked_during)
+ORDER BY room_id, booked_during
+;
+ room_id | booked_during
+---------+-------------------------
+ 1 | [07-01-2018,07-14-2018)
+ 1 | [07-20-2018,07-22-2018)
+ 2 | [07-01-2018,07-03-2018)
+ 5 | [07-01-2018,07-03-2018)
+ 6 | [07-01-2018,07-10-2018)
+ 7 | [07-01-2018,07-14-2018)
+(6 rows)
+
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 8ff0da185e..395bfeb75d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -170,6 +170,7 @@ quad_poly_tbl|t
radix_text_tbl|t
ramp|f
real_city|f
+reservations|f
road|t
shighway|t
slow_emp4000|f
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 55638a85ee..9d1be6f3ca 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -503,3 +503,192 @@ create function inoutparam_fail(inout i anyelement, out r anyrange)
--should fail
create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
as $$ select $1, '[1,10]' $$ language sql;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14'))
+;
+
+-- range_agg with 1 arg:
+
+-- Forbidding gaps and overlaps:
+SELECT room_id, range_agg(booked_during)
+FROM reservations
+WHERE room_id = 1
+GROUP BY room_id
+ORDER BY room_id;
+
+SELECT room_id, range_agg(booked_during)
+FROM reservations
+WHERE room_id = 6
+GROUP BY room_id
+ORDER BY room_id;
+
+SELECT room_id, range_agg(booked_during)
+FROM reservations
+WHERE room_id NOT IN (1, 6)
+GROUP BY room_id
+ORDER BY room_id;
+
+-- Obeying discrete base types:
+SELECT range_agg(r)
+FROM (VALUES
+ (int4range( 0, 9, '[]')),
+ (int4range(10, 19, '[]'))
+) t(r);
+
+-- range_agg with 2 args:
+
+-- Forbidding gaps (and overlaps):
+SELECT room_id, range_agg(booked_during, false)
+FROM reservations
+WHERE room_id = 1
+GROUP BY room_id;
+
+SELECT room_id, range_agg(booked_during, false)
+FROM reservations
+WHERE room_id = 6
+GROUP BY room_id;
+
+SELECT room_id, range_agg(booked_during, false)
+FROM reservations
+WHERE room_id NOT IN (1, 6)
+GROUP BY room_id;
+
+-- Permitting gaps (but forbidding overlaps):
+SELECT room_id, range_agg(booked_during, true)
+FROM reservations
+WHERE room_id = 1
+GROUP BY room_id;
+
+SELECT room_id, range_agg(booked_during, true)
+FROM reservations
+WHERE room_id = 6
+GROUP BY room_id;
+
+SELECT room_id, range_agg(booked_during, true)
+FROM reservations
+WHERE room_id NOT IN (1, 6)
+GROUP BY room_id;
+
+-- range_agg with 3 args:
+
+-- Forbidding gaps and overlaps:
+SELECT room_id, range_agg(booked_during, false, false)
+FROM reservations
+WHERE room_id = 1
+GROUP BY room_id;
+
+SELECT room_id, range_agg(booked_during, false, false)
+FROM reservations
+WHERE room_id = 6
+GROUP BY room_id;
+
+SELECT room_id, range_agg(booked_during, false, false)
+FROM reservations
+WHERE room_id NOT IN (1, 6)
+GROUP BY room_id;
+
+-- Forbidding gaps but permitting overlaps
+SELECT room_id, range_agg(booked_during, false, true)
+FROM reservations
+WHERE room_id = 1
+GROUP BY room_id;
+
+SELECT room_id, range_agg(booked_during, false, true)
+FROM reservations
+WHERE room_id = 6
+GROUP BY room_id;
+
+SELECT room_id, range_agg(booked_during, false, true)
+FROM reservations
+WHERE room_id NOT IN (1, 6)
+GROUP BY room_id;
+
+-- Permitting gaps but forbidding overlaps
+SELECT room_id, range_agg(booked_during, true, false)
+FROM reservations
+WHERE room_id = 1
+GROUP BY room_id;
+
+SELECT room_id, range_agg(booked_during, true, false)
+FROM reservations
+WHERE room_id = 6
+GROUP BY room_id;
+
+SELECT room_id, range_agg(booked_during, true, false)
+FROM reservations
+WHERE room_id NOT IN (1, 6)
+GROUP BY room_id;
+
+-- Permitting gaps and overlaps:
+SELECT room_id, range_agg(booked_during, true, true)
+FROM reservations
+WHERE room_id = 1
+GROUP BY room_id;
+
+SELECT room_id, range_agg(booked_during, true, true)
+FROM reservations
+WHERE room_id = 6
+GROUP BY room_id;
+
+SELECT room_id, range_agg(booked_during, true, true)
+FROM reservations
+WHERE room_id NOT IN (1, 6)
+GROUP BY room_id;
+
+-- Obeying discrete base types:
+SELECT range_agg(r, false, false)
+FROM (VALUES
+ (int4range( 0, 5, '[]')),
+ (int4range( 7, 9, '[]'))
+) t(r);
+
+SELECT range_agg(r, false, false)
+FROM (VALUES
+ (int4range( 0, 5, '[]')),
+ (int4range( 5, 9, '[]'))
+) t(r);
+
+SELECT range_agg(r, true, true)
+FROM (VALUES
+ (int4range( 0, 9, '[]')),
+ (int4range(10, 15, '[]')),
+ (int4range(20, 26, '[]')),
+ (int4range(26, 29, '[]'))
+) t(r);
+
+-- It combines with UNNEST
+-- to implement the temporal database "coalesce" function
+-- (see Snodgrass 6.5.2):
+SELECT room_id, t2.booked_during
+FROM (
+ SELECT room_id, range_agg(booked_during, true, true) AS booked_during
+ FROM reservations
+ GROUP BY room_id
+ ) AS t1,
+ UNNEST(t1.booked_during) AS t2(booked_during)
+ORDER BY room_id, booked_during
+;