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 +;