postgresql/src/backend/utils/adt/multirangetypes.c

2927 lines
76 KiB
C

/*-------------------------------------------------------------------------
*
* multirangetypes.c
* I/O functions, operators, and support functions for multirange types.
*
* The stored (serialized) format of a multirange value is:
*
* 12 bytes: MultirangeType struct including varlena header, multirange
* type's OID and the number of ranges in the multirange.
* 4 * (rangesCount - 1) bytes: 32-bit items pointing to the each range
* in the multirange starting from
* the second one.
* 1 * rangesCount bytes : 8-bit flags for each range in the multirange
* The rest of the multirange are range bound values pointed by multirange
* items.
*
* Majority of items contain lengths of corresponding range bound values.
* Thanks to that items are typically low numbers. This makes multiranges
* compression-friendly. Every MULTIRANGE_ITEM_OFFSET_STRIDE item contains
* an offset of the corresponding range bound values. That allows fast lookups
* for a particular range index. Offsets are counted starting from the end of
* flags aligned to the bound type.
*
* Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/utils/adt/multirangetypes.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/tupmacs.h"
#include "common/hashfn.h"
#include "funcapi.h"
#include "lib/stringinfo.h"
#include "libpq/pqformat.h"
#include "nodes/nodes.h"
#include "port/pg_bitutils.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/multirangetypes.h"
#include "utils/rangetypes.h"
/* fn_extra cache entry for one of the range I/O functions */
typedef struct MultirangeIOData
{
TypeCacheEntry *typcache; /* multirange type's typcache entry */
FmgrInfo typioproc; /* range type's I/O proc */
Oid typioparam; /* range type's I/O parameter */
} MultirangeIOData;
typedef enum
{
MULTIRANGE_BEFORE_RANGE,
MULTIRANGE_IN_RANGE,
MULTIRANGE_IN_RANGE_ESCAPED,
MULTIRANGE_IN_RANGE_QUOTED,
MULTIRANGE_IN_RANGE_QUOTED_ESCAPED,
MULTIRANGE_AFTER_RANGE,
MULTIRANGE_FINISHED,
} MultirangeParseState;
/*
* Macros for accessing past MultirangeType parts of multirange: items, flags
* and boundaries.
*/
#define MultirangeGetItemsPtr(mr) ((uint32 *) ((Pointer) (mr) + \
sizeof(MultirangeType)))
#define MultirangeGetFlagsPtr(mr) ((uint8 *) ((Pointer) (mr) + \
sizeof(MultirangeType) + ((mr)->rangeCount - 1) * sizeof(uint32)))
#define MultirangeGetBoundariesPtr(mr, align) ((Pointer) (mr) + \
att_align_nominal(sizeof(MultirangeType) + \
((mr)->rangeCount - 1) * sizeof(uint32) + \
(mr)->rangeCount * sizeof(uint8), (align)))
#define MULTIRANGE_ITEM_OFF_BIT 0x80000000
#define MULTIRANGE_ITEM_GET_OFFLEN(item) ((item) & 0x7FFFFFFF)
#define MULTIRANGE_ITEM_HAS_OFF(item) ((item) & MULTIRANGE_ITEM_OFF_BIT)
#define MULTIRANGE_ITEM_OFFSET_STRIDE 4
typedef int (*multirange_bsearch_comparison) (TypeCacheEntry *typcache,
RangeBound *lower,
RangeBound *upper,
void *key,
bool *match);
static MultirangeIOData *get_multirange_io_data(FunctionCallInfo fcinfo,
Oid mltrngtypid,
IOFuncSelector func);
static int32 multirange_canonicalize(TypeCacheEntry *rangetyp,
int32 input_range_count,
RangeType **ranges);
/*
*----------------------------------------------------------
* I/O FUNCTIONS
*----------------------------------------------------------
*/
/*
* Converts string to multirange.
*
* We expect curly brackets to bound the list, with zero or more ranges
* separated by commas. We accept whitespace anywhere: before/after our
* brackets and around the commas. Ranges can be the empty literal or some
* stuff inside parens/brackets. Mostly we delegate parsing the individual
* range contents to range_in, but we have to detect quoting and
* backslash-escaping which can happen for range bounds. Backslashes can
* escape something inside or outside a quoted string, and a quoted string
* can escape quote marks with either backslashes or double double-quotes.
*/
Datum
multirange_in(PG_FUNCTION_ARGS)
{
char *input_str = PG_GETARG_CSTRING(0);
Oid mltrngtypoid = PG_GETARG_OID(1);
Oid typmod = PG_GETARG_INT32(2);
Node *escontext = fcinfo->context;
TypeCacheEntry *rangetyp;
int32 ranges_seen = 0;
int32 range_count = 0;
int32 range_capacity = 8;
RangeType *range;
RangeType **ranges = palloc(range_capacity * sizeof(RangeType *));
MultirangeIOData *cache;
MultirangeType *ret;
MultirangeParseState parse_state;
const char *ptr = input_str;
const char *range_str_begin = NULL;
int32 range_str_len;
char *range_str;
Datum range_datum;
cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
rangetyp = cache->typcache->rngtype;
/* consume whitespace */
while (*ptr != '\0' && isspace((unsigned char) *ptr))
ptr++;
if (*ptr == '{')
ptr++;
else
ereturn(escontext, (Datum) 0,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed multirange literal: \"%s\"",
input_str),
errdetail("Missing left brace.")));
/* consume ranges */
parse_state = MULTIRANGE_BEFORE_RANGE;
for (; parse_state != MULTIRANGE_FINISHED; ptr++)
{
char ch = *ptr;
if (ch == '\0')
ereturn(escontext, (Datum) 0,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed multirange literal: \"%s\"",
input_str),
errdetail("Unexpected end of input.")));
/* skip whitespace */
if (isspace((unsigned char) ch))
continue;
switch (parse_state)
{
case MULTIRANGE_BEFORE_RANGE:
if (ch == '[' || ch == '(')
{
range_str_begin = ptr;
parse_state = MULTIRANGE_IN_RANGE;
}
else if (ch == '}' && ranges_seen == 0)
parse_state = MULTIRANGE_FINISHED;
else if (pg_strncasecmp(ptr, RANGE_EMPTY_LITERAL,
strlen(RANGE_EMPTY_LITERAL)) == 0)
{
ranges_seen++;
/* nothing to do with an empty range */
ptr += strlen(RANGE_EMPTY_LITERAL) - 1;
parse_state = MULTIRANGE_AFTER_RANGE;
}
else
ereturn(escontext, (Datum) 0,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed multirange literal: \"%s\"",
input_str),
errdetail("Expected range start.")));
break;
case MULTIRANGE_IN_RANGE:
if (ch == ']' || ch == ')')
{
range_str_len = ptr - range_str_begin + 1;
range_str = pnstrdup(range_str_begin, range_str_len);
if (range_capacity == range_count)
{
range_capacity *= 2;
ranges = (RangeType **)
repalloc(ranges, range_capacity * sizeof(RangeType *));
}
ranges_seen++;
if (!InputFunctionCallSafe(&cache->typioproc,
range_str,
cache->typioparam,
typmod,
escontext,
&range_datum))
PG_RETURN_NULL();
range = DatumGetRangeTypeP(range_datum);
if (!RangeIsEmpty(range))
ranges[range_count++] = range;
parse_state = MULTIRANGE_AFTER_RANGE;
}
else
{
if (ch == '"')
parse_state = MULTIRANGE_IN_RANGE_QUOTED;
else if (ch == '\\')
parse_state = MULTIRANGE_IN_RANGE_ESCAPED;
/*
* We will include this character into range_str once we
* find the end of the range value.
*/
}
break;
case MULTIRANGE_IN_RANGE_ESCAPED:
/*
* We will include this character into range_str once we find
* the end of the range value.
*/
parse_state = MULTIRANGE_IN_RANGE;
break;
case MULTIRANGE_IN_RANGE_QUOTED:
if (ch == '"')
if (*(ptr + 1) == '"')
{
/* two quote marks means an escaped quote mark */
ptr++;
}
else
parse_state = MULTIRANGE_IN_RANGE;
else if (ch == '\\')
parse_state = MULTIRANGE_IN_RANGE_QUOTED_ESCAPED;
/*
* We will include this character into range_str once we find
* the end of the range value.
*/
break;
case MULTIRANGE_AFTER_RANGE:
if (ch == ',')
parse_state = MULTIRANGE_BEFORE_RANGE;
else if (ch == '}')
parse_state = MULTIRANGE_FINISHED;
else
ereturn(escontext, (Datum) 0,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed multirange literal: \"%s\"",
input_str),
errdetail("Expected comma or end of multirange.")));
break;
case MULTIRANGE_IN_RANGE_QUOTED_ESCAPED:
/*
* We will include this character into range_str once we find
* the end of the range value.
*/
parse_state = MULTIRANGE_IN_RANGE_QUOTED;
break;
default:
elog(ERROR, "unknown parse state: %d", parse_state);
}
}
/* consume whitespace */
while (*ptr != '\0' && isspace((unsigned char) *ptr))
ptr++;
if (*ptr != '\0')
ereturn(escontext, (Datum) 0,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed multirange literal: \"%s\"",
input_str),
errdetail("Junk after closing right brace.")));
ret = make_multirange(mltrngtypoid, rangetyp, range_count, ranges);
PG_RETURN_MULTIRANGE_P(ret);
}
Datum
multirange_out(PG_FUNCTION_ARGS)
{
MultirangeType *multirange = PG_GETARG_MULTIRANGE_P(0);
Oid mltrngtypoid = MultirangeTypeGetOid(multirange);
MultirangeIOData *cache;
StringInfoData buf;
RangeType *range;
char *rangeStr;
int32 range_count;
int32 i;
RangeType **ranges;
cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_output);
initStringInfo(&buf);
appendStringInfoChar(&buf, '{');
multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges);
for (i = 0; i < range_count; i++)
{
if (i > 0)
appendStringInfoChar(&buf, ',');
range = ranges[i];
rangeStr = OutputFunctionCall(&cache->typioproc, RangeTypePGetDatum(range));
appendStringInfoString(&buf, rangeStr);
}
appendStringInfoChar(&buf, '}');
PG_RETURN_CSTRING(buf.data);
}
/*
* Binary representation: First a int32-sized count of ranges, followed by
* ranges in their native binary representation.
*/
Datum
multirange_recv(PG_FUNCTION_ARGS)
{
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
Oid mltrngtypoid = PG_GETARG_OID(1);
int32 typmod = PG_GETARG_INT32(2);
MultirangeIOData *cache;
uint32 range_count;
RangeType **ranges;
MultirangeType *ret;
StringInfoData tmpbuf;
cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_receive);
range_count = pq_getmsgint(buf, 4);
ranges = palloc(range_count * sizeof(RangeType *));
initStringInfo(&tmpbuf);
for (int i = 0; i < range_count; i++)
{
uint32 range_len = pq_getmsgint(buf, 4);
const char *range_data = pq_getmsgbytes(buf, range_len);
resetStringInfo(&tmpbuf);
appendBinaryStringInfo(&tmpbuf, range_data, range_len);
ranges[i] = DatumGetRangeTypeP(ReceiveFunctionCall(&cache->typioproc,
&tmpbuf,
cache->typioparam,
typmod));
}
pfree(tmpbuf.data);
pq_getmsgend(buf);
ret = make_multirange(mltrngtypoid, cache->typcache->rngtype,
range_count, ranges);
PG_RETURN_MULTIRANGE_P(ret);
}
Datum
multirange_send(PG_FUNCTION_ARGS)
{
MultirangeType *multirange = PG_GETARG_MULTIRANGE_P(0);
Oid mltrngtypoid = MultirangeTypeGetOid(multirange);
StringInfo buf = makeStringInfo();
RangeType **ranges;
int32 range_count;
MultirangeIOData *cache;
cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_send);
/* construct output */
pq_begintypsend(buf);
pq_sendint32(buf, multirange->rangeCount);
multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges);
for (int i = 0; i < range_count; i++)
{
Datum range;
range = RangeTypePGetDatum(ranges[i]);
range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
}
PG_RETURN_BYTEA_P(pq_endtypsend(buf));
}
/*
* get_multirange_io_data: get cached information needed for multirange type I/O
*
* The multirange I/O functions need a bit more cached info than other multirange
* functions, so they store a MultirangeIOData struct in fn_extra, not just a
* pointer to a type cache entry.
*/
static MultirangeIOData *
get_multirange_io_data(FunctionCallInfo fcinfo, Oid mltrngtypid, IOFuncSelector func)
{
MultirangeIOData *cache = (MultirangeIOData *) fcinfo->flinfo->fn_extra;
if (cache == NULL || cache->typcache->type_id != mltrngtypid)
{
Oid typiofunc;
int16 typlen;
bool typbyval;
char typalign;
char typdelim;
cache = (MultirangeIOData *) MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
sizeof(MultirangeIOData));
cache->typcache = lookup_type_cache(mltrngtypid, TYPECACHE_MULTIRANGE_INFO);
if (cache->typcache->rngtype == NULL)
elog(ERROR, "type %u is not a multirange type", mltrngtypid);
/* get_type_io_data does more than we need, but is convenient */
get_type_io_data(cache->typcache->rngtype->type_id,
func,
&typlen,
&typbyval,
&typalign,
&typdelim,
&cache->typioparam,
&typiofunc);
if (!OidIsValid(typiofunc))
{
/* this could only happen for receive or send */
if (func == IOFunc_receive)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("no binary input function available for type %s",
format_type_be(cache->typcache->rngtype->type_id))));
else
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("no binary output function available for type %s",
format_type_be(cache->typcache->rngtype->type_id))));
}
fmgr_info_cxt(typiofunc, &cache->typioproc,
fcinfo->flinfo->fn_mcxt);
fcinfo->flinfo->fn_extra = (void *) cache;
}
return cache;
}
/*
* Converts a list of arbitrary ranges into a list that is sorted and merged.
* Changes the contents of `ranges`.
*
* Returns the number of slots actually used, which may be less than
* input_range_count but never more.
*
* We assume that no input ranges are null, but empties are okay.
*/
static int32
multirange_canonicalize(TypeCacheEntry *rangetyp, int32 input_range_count,
RangeType **ranges)
{
RangeType *lastRange = NULL;
RangeType *currentRange;
int32 i;
int32 output_range_count = 0;
/* Sort the ranges so we can find the ones that overlap/meet. */
qsort_arg(ranges, input_range_count, sizeof(RangeType *), range_compare,
rangetyp);
/* Now merge where possible: */
for (i = 0; i < input_range_count; i++)
{
currentRange = ranges[i];
if (RangeIsEmpty(currentRange))
continue;
if (lastRange == NULL)
{
ranges[output_range_count++] = lastRange = currentRange;
continue;
}
/*
* range_adjacent_internal gives true if *either* A meets B or B meets
* A, which is not quite want we want, but we rely on the sorting
* above to rule out B meets A ever happening.
*/
if (range_adjacent_internal(rangetyp, lastRange, currentRange))
{
/* The two ranges touch (without overlap), so merge them: */
ranges[output_range_count - 1] = lastRange =
range_union_internal(rangetyp, lastRange, currentRange, false);
}
else if (range_before_internal(rangetyp, lastRange, currentRange))
{
/* There's a gap, so make a new entry: */
lastRange = ranges[output_range_count] = currentRange;
output_range_count++;
}
else
{
/* They must overlap, so merge them: */
ranges[output_range_count - 1] = lastRange =
range_union_internal(rangetyp, lastRange, currentRange, true);
}
}
return output_range_count;
}
/*
*----------------------------------------------------------
* SUPPORT FUNCTIONS
*
* These functions aren't in pg_proc, but are useful for
* defining new generic multirange functions in C.
*----------------------------------------------------------
*/
/*
* multirange_get_typcache: get cached information about a multirange type
*
* This is for use by multirange-related functions that follow the convention
* of using the fn_extra field as a pointer to the type cache entry for
* the multirange type. Functions that need to cache more information than
* that must fend for themselves.
*/
TypeCacheEntry *
multirange_get_typcache(FunctionCallInfo fcinfo, Oid mltrngtypid)
{
TypeCacheEntry *typcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
if (typcache == NULL ||
typcache->type_id != mltrngtypid)
{
typcache = lookup_type_cache(mltrngtypid, TYPECACHE_MULTIRANGE_INFO);
if (typcache->rngtype == NULL)
elog(ERROR, "type %u is not a multirange type", mltrngtypid);
fcinfo->flinfo->fn_extra = (void *) typcache;
}
return typcache;
}
/*
* Estimate size occupied by serialized multirange.
*/
static Size
multirange_size_estimate(TypeCacheEntry *rangetyp, int32 range_count,
RangeType **ranges)
{
char elemalign = rangetyp->rngelemtype->typalign;
Size size;
int32 i;
/*
* Count space for MultirangeType struct, items and flags.
*/
size = att_align_nominal(sizeof(MultirangeType) +
Max(range_count - 1, 0) * sizeof(uint32) +
range_count * sizeof(uint8), elemalign);
/* Count space for range bounds */
for (i = 0; i < range_count; i++)
size += att_align_nominal(VARSIZE(ranges[i]) -
sizeof(RangeType) -
sizeof(char), elemalign);
return size;
}
/*
* Write multirange data into pre-allocated space.
*/
static void
write_multirange_data(MultirangeType *multirange, TypeCacheEntry *rangetyp,
int32 range_count, RangeType **ranges)
{
uint32 *items;
uint32 prev_offset = 0;
uint8 *flags;
int32 i;
Pointer begin,
ptr;
char elemalign = rangetyp->rngelemtype->typalign;
items = MultirangeGetItemsPtr(multirange);
flags = MultirangeGetFlagsPtr(multirange);
ptr = begin = MultirangeGetBoundariesPtr(multirange, elemalign);
for (i = 0; i < range_count; i++)
{
uint32 len;
if (i > 0)
{
/*
* Every range, except the first one, has an item. Every
* MULTIRANGE_ITEM_OFFSET_STRIDE item contains an offset, others
* contain lengths.
*/
items[i - 1] = ptr - begin;
if ((i % MULTIRANGE_ITEM_OFFSET_STRIDE) != 0)
items[i - 1] -= prev_offset;
else
items[i - 1] |= MULTIRANGE_ITEM_OFF_BIT;
prev_offset = ptr - begin;
}
flags[i] = *((Pointer) ranges[i] + VARSIZE(ranges[i]) - sizeof(char));
len = VARSIZE(ranges[i]) - sizeof(RangeType) - sizeof(char);
memcpy(ptr, (Pointer) (ranges[i] + 1), len);
ptr += att_align_nominal(len, elemalign);
}
}
/*
* This serializes the multirange from a list of non-null ranges. It also
* sorts the ranges and merges any that touch. The ranges should already be
* detoasted, and there should be no NULLs. This should be used by most
* callers.
*
* Note that we may change the `ranges` parameter (the pointers, but not
* any already-existing RangeType contents).
*/
MultirangeType *
make_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp, int32 range_count,
RangeType **ranges)
{
MultirangeType *multirange;
Size size;
/* Sort and merge input ranges. */
range_count = multirange_canonicalize(rangetyp, range_count, ranges);
/* Note: zero-fill is required here, just as in heap tuples */
size = multirange_size_estimate(rangetyp, range_count, ranges);
multirange = palloc0(size);
SET_VARSIZE(multirange, size);
/* Now fill in the datum */
multirange->multirangetypid = mltrngtypoid;
multirange->rangeCount = range_count;
write_multirange_data(multirange, rangetyp, range_count, ranges);
return multirange;
}
/*
* Get offset of bounds values of the i'th range in the multirange.
*/
static uint32
multirange_get_bounds_offset(const MultirangeType *multirange, int32 i)
{
uint32 *items = MultirangeGetItemsPtr(multirange);
uint32 offset = 0;
/*
* Summarize lengths till we meet an offset.
*/
while (i > 0)
{
offset += MULTIRANGE_ITEM_GET_OFFLEN(items[i - 1]);
if (MULTIRANGE_ITEM_HAS_OFF(items[i - 1]))
break;
i--;
}
return offset;
}
/*
* Fetch the i'th range from the multirange.
*/
RangeType *
multirange_get_range(TypeCacheEntry *rangetyp,
const MultirangeType *multirange, int i)
{
uint32 offset;
uint8 flags;
Pointer begin,
ptr;
int16 typlen = rangetyp->rngelemtype->typlen;
char typalign = rangetyp->rngelemtype->typalign;
uint32 len;
RangeType *range;
Assert(i < multirange->rangeCount);
offset = multirange_get_bounds_offset(multirange, i);
flags = MultirangeGetFlagsPtr(multirange)[i];
ptr = begin = MultirangeGetBoundariesPtr(multirange, typalign) + offset;
/*
* Calculate the size of bound values. In principle, we could get offset
* of the next range bound values and calculate accordingly. But range
* bound values are aligned, so we have to walk the values to get the
* exact size.
*/
if (RANGE_HAS_LBOUND(flags))
ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
if (RANGE_HAS_UBOUND(flags))
{
ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr);
ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
}
len = (ptr - begin) + sizeof(RangeType) + sizeof(uint8);
range = palloc0(len);
SET_VARSIZE(range, len);
range->rangetypid = rangetyp->type_id;
memcpy(range + 1, begin, ptr - begin);
*((uint8 *) (range + 1) + (ptr - begin)) = flags;
return range;
}
/*
* Fetch bounds from the i'th range of the multirange. This is the shortcut for
* doing the same thing as multirange_get_range() + range_deserialize(), but
* performing fewer operations.
*/
void
multirange_get_bounds(TypeCacheEntry *rangetyp,
const MultirangeType *multirange,
uint32 i, RangeBound *lower, RangeBound *upper)
{
uint32 offset;
uint8 flags;
Pointer ptr;
int16 typlen = rangetyp->rngelemtype->typlen;
char typalign = rangetyp->rngelemtype->typalign;
bool typbyval = rangetyp->rngelemtype->typbyval;
Datum lbound;
Datum ubound;
Assert(i < multirange->rangeCount);
offset = multirange_get_bounds_offset(multirange, i);
flags = MultirangeGetFlagsPtr(multirange)[i];
ptr = MultirangeGetBoundariesPtr(multirange, typalign) + offset;
/* multirange can't contain empty ranges */
Assert((flags & RANGE_EMPTY) == 0);
/* fetch lower bound, if any */
if (RANGE_HAS_LBOUND(flags))
{
/* att_align_pointer cannot be necessary here */
lbound = fetch_att(ptr, typbyval, typlen);
ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
}
else
lbound = (Datum) 0;
/* fetch upper bound, if any */
if (RANGE_HAS_UBOUND(flags))
{
ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr);
ubound = fetch_att(ptr, typbyval, typlen);
/* no need for att_addlength_pointer */
}
else
ubound = (Datum) 0;
/* emit results */
lower->val = lbound;
lower->infinite = (flags & RANGE_LB_INF) != 0;
lower->inclusive = (flags & RANGE_LB_INC) != 0;
lower->lower = true;
upper->val = ubound;
upper->infinite = (flags & RANGE_UB_INF) != 0;
upper->inclusive = (flags & RANGE_UB_INC) != 0;
upper->lower = false;
}
/*
* Construct union range from the multirange.
*/
RangeType *
multirange_get_union_range(TypeCacheEntry *rangetyp,
const MultirangeType *mr)
{
RangeBound lower,
upper,
tmp;
if (MultirangeIsEmpty(mr))
return make_empty_range(rangetyp);
multirange_get_bounds(rangetyp, mr, 0, &lower, &tmp);
multirange_get_bounds(rangetyp, mr, mr->rangeCount - 1, &tmp, &upper);
return make_range(rangetyp, &lower, &upper, false, NULL);
}
/*
* multirange_deserialize: deconstruct a multirange value
*
* NB: the given multirange object must be fully detoasted; it cannot have a
* short varlena header.
*/
void
multirange_deserialize(TypeCacheEntry *rangetyp,
const MultirangeType *multirange, int32 *range_count,
RangeType ***ranges)
{
*range_count = multirange->rangeCount;
/* Convert each ShortRangeType into a RangeType */
if (*range_count > 0)
{
int i;
*ranges = palloc(*range_count * sizeof(RangeType *));
for (i = 0; i < *range_count; i++)
(*ranges)[i] = multirange_get_range(rangetyp, multirange, i);
}
else
{
*ranges = NULL;
}
}
MultirangeType *
make_empty_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp)
{
return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
}
/*
* Similar to range_overlaps_internal(), but takes range bounds instead of
* ranges as arguments.
*/
static bool
range_bounds_overlaps(TypeCacheEntry *typcache,
RangeBound *lower1, RangeBound *upper1,
RangeBound *lower2, RangeBound *upper2)
{
if (range_cmp_bounds(typcache, lower1, lower2) >= 0 &&
range_cmp_bounds(typcache, lower1, upper2) <= 0)
return true;
if (range_cmp_bounds(typcache, lower2, lower1) >= 0 &&
range_cmp_bounds(typcache, lower2, upper1) <= 0)
return true;
return false;
}
/*
* Similar to range_contains_internal(), but takes range bounds instead of
* ranges as arguments.
*/
static bool
range_bounds_contains(TypeCacheEntry *typcache,
RangeBound *lower1, RangeBound *upper1,
RangeBound *lower2, RangeBound *upper2)
{
if (range_cmp_bounds(typcache, lower1, lower2) <= 0 &&
range_cmp_bounds(typcache, upper1, upper2) >= 0)
return true;
return false;
}
/*
* Check if the given key matches any range in multirange using binary search.
* If the required range isn't found, that counts as a mismatch. When the
* required range is found, the comparison function can still report this as
* either match or mismatch. For instance, if we search for containment, we can
* found a range, which is overlapping but not containing the key range, and
* that would count as a mismatch.
*/
static bool
multirange_bsearch_match(TypeCacheEntry *typcache, const MultirangeType *mr,
void *key, multirange_bsearch_comparison cmp_func)
{
uint32 l,
u,
idx;
int comparison;
bool match = false;
l = 0;
u = mr->rangeCount;
while (l < u)
{
RangeBound lower,
upper;
idx = (l + u) / 2;
multirange_get_bounds(typcache, mr, idx, &lower, &upper);
comparison = (*cmp_func) (typcache, &lower, &upper, key, &match);
if (comparison < 0)
u = idx;
else if (comparison > 0)
l = idx + 1;
else
return match;
}
return false;
}
/*
*----------------------------------------------------------
* GENERIC FUNCTIONS
*----------------------------------------------------------
*/
/*
* Construct multirange value from zero or more ranges. Since this is a
* variadic function we get passed an array. The array must contain ranges
* that match our return value, and there must be no NULLs.
*/
Datum
multirange_constructor2(PG_FUNCTION_ARGS)
{
Oid mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
Oid rngtypid;
TypeCacheEntry *typcache;
TypeCacheEntry *rangetyp;
ArrayType *rangeArray;
int range_count;
Datum *elements;
bool *nulls;
RangeType **ranges;
int dims;
int i;
typcache = multirange_get_typcache(fcinfo, mltrngtypid);
rangetyp = typcache->rngtype;
/*
* A no-arg invocation should call multirange_constructor0 instead, but
* returning an empty range is what that does.
*/
if (PG_NARGS() == 0)
PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 0, NULL));
/*
* This check should be guaranteed by our signature, but let's do it just
* in case.
*/
if (PG_ARGISNULL(0))
elog(ERROR,
"multirange values cannot contain null members");
rangeArray = PG_GETARG_ARRAYTYPE_P(0);
dims = ARR_NDIM(rangeArray);
if (dims > 1)
ereport(ERROR,
(errcode(ERRCODE_CARDINALITY_VIOLATION),
errmsg("multiranges cannot be constructed from multidimensional arrays")));
rngtypid = ARR_ELEMTYPE(rangeArray);
if (rngtypid != rangetyp->type_id)
elog(ERROR, "type %u does not match constructor type", rngtypid);
/*
* Be careful: we can still be called with zero ranges, like this:
* `int4multirange(variadic '{}'::int4range[])
*/
if (dims == 0)
{
range_count = 0;
ranges = NULL;
}
else
{
deconstruct_array(rangeArray, rngtypid, rangetyp->typlen, rangetyp->typbyval,
rangetyp->typalign, &elements, &nulls, &range_count);
ranges = palloc0(range_count * sizeof(RangeType *));
for (i = 0; i < range_count; i++)
{
if (nulls[i])
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("multirange values cannot contain null members")));
/* make_multirange will do its own copy */
ranges[i] = DatumGetRangeTypeP(elements[i]);
}
}
PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges));
}
/*
* Construct multirange value from a single range. It'd be nice if we could
* just use multirange_constructor2 for this case, but we need a non-variadic
* single-arg function to let us define a CAST from a range to its multirange.
*/
Datum
multirange_constructor1(PG_FUNCTION_ARGS)
{
Oid mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
Oid rngtypid;
TypeCacheEntry *typcache;
TypeCacheEntry *rangetyp;
RangeType *range;
typcache = multirange_get_typcache(fcinfo, mltrngtypid);
rangetyp = typcache->rngtype;
/*
* This check should be guaranteed by our signature, but let's do it just
* in case.
*/
if (PG_ARGISNULL(0))
elog(ERROR,
"multirange values cannot contain null members");
range = PG_GETARG_RANGE_P(0);
/* Make sure the range type matches. */
rngtypid = RangeTypeGetOid(range);
if (rngtypid != rangetyp->type_id)
elog(ERROR, "type %u does not match constructor type", rngtypid);
PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
}
/*
* Constructor just like multirange_constructor1, but opr_sanity gets angry
* if the same internal function handles multiple functions with different arg
* counts.
*/
Datum
multirange_constructor0(PG_FUNCTION_ARGS)
{
Oid mltrngtypid;
TypeCacheEntry *typcache;
TypeCacheEntry *rangetyp;
/* This should always be called without arguments */
if (PG_NARGS() != 0)
elog(ERROR,
"niladic multirange constructor must not receive arguments");
mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
typcache = multirange_get_typcache(fcinfo, mltrngtypid);
rangetyp = typcache->rngtype;
PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 0, NULL));
}
/* multirange, multirange -> multirange type functions */
/* multirange union */
Datum
multirange_union(PG_FUNCTION_ARGS)
{
MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
int32 range_count1;
int32 range_count2;
int32 range_count3;
RangeType **ranges1;
RangeType **ranges2;
RangeType **ranges3;
if (MultirangeIsEmpty(mr1))
PG_RETURN_MULTIRANGE_P(mr2);
if (MultirangeIsEmpty(mr2))
PG_RETURN_MULTIRANGE_P(mr1);
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
range_count3 = range_count1 + range_count2;
ranges3 = palloc0(range_count3 * sizeof(RangeType *));
memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
range_count3, ranges3));
}
/* multirange minus */
Datum
multirange_minus(PG_FUNCTION_ARGS)
{
MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
Oid mltrngtypoid = MultirangeTypeGetOid(mr1);
TypeCacheEntry *typcache;
TypeCacheEntry *rangetyp;
int32 range_count1;
int32 range_count2;
RangeType **ranges1;
RangeType **ranges2;
typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
rangetyp = typcache->rngtype;
if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
PG_RETURN_MULTIRANGE_P(mr1);
multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
rangetyp,
range_count1,
ranges1,
range_count2,
ranges2));
}
MultirangeType *
multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
int32 range_count1, RangeType **ranges1,
int32 range_count2, RangeType **ranges2)
{
RangeType *r1;
RangeType *r2;
RangeType **ranges3;
int32 range_count3;
int32 i1;
int32 i2;
/*
* Worst case: every range in ranges1 makes a different cut to some range
* in ranges2.
*/
ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
range_count3 = 0;
/*
* For each range in mr1, keep subtracting until it's gone or the ranges
* in mr2 have passed it. After a subtraction we assign what's left back
* to r1. The parallel progress through mr1 and mr2 is similar to
* multirange_overlaps_multirange_internal.
*/
r2 = ranges2[0];
for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
{
r1 = ranges1[i1];
/* Discard r2s while r2 << r1 */
while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
{
r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
}
while (r2 != NULL)
{
if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
{
/*
* If r2 takes a bite out of the middle of r1, we need two
* outputs
*/
range_count3++;
r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
}
else if (range_overlaps_internal(rangetyp, r1, r2))
{
/*
* If r2 overlaps r1, replace r1 with r1 - r2.
*/
r1 = range_minus_internal(rangetyp, r1, r2);
/*
* If r2 goes past r1, then we need to stay with it, in case
* it hits future r1s. Otherwise we need to keep r1, in case
* future r2s hit it. Since we already subtracted, there's no
* point in using the overright/overleft calls.
*/
if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
break;
else
r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
}
else
{
/*
* This and all future r2s are past r1, so keep them. Also
* assign whatever is left of r1 to the result.
*/
break;
}
}
/*
* Nothing else can remove anything from r1, so keep it. Even if r1 is
* empty here, make_multirange will remove it.
*/
ranges3[range_count3++] = r1;
}
return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
}
/* multirange intersection */
Datum
multirange_intersect(PG_FUNCTION_ARGS)
{
MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
Oid mltrngtypoid = MultirangeTypeGetOid(mr1);
TypeCacheEntry *typcache;
TypeCacheEntry *rangetyp;
int32 range_count1;
int32 range_count2;
RangeType **ranges1;
RangeType **ranges2;
typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
rangetyp = typcache->rngtype;
if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
multirange_deserialize(rangetyp, mr1, &range_count1, &ranges1);
multirange_deserialize(rangetyp, mr2, &range_count2, &ranges2);
PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
rangetyp,
range_count1,
ranges1,
range_count2,
ranges2));
}
MultirangeType *
multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
int32 range_count1, RangeType **ranges1,
int32 range_count2, RangeType **ranges2)
{
RangeType *r1;
RangeType *r2;
RangeType **ranges3;
int32 range_count3;
int32 i1;
int32 i2;
if (range_count1 == 0 || range_count2 == 0)
return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
/*-----------------------------------------------
* Worst case is a stitching pattern like this:
*
* mr1: --- --- --- ---
* mr2: --- --- ---
* mr3: - - - - - -
*
* That seems to be range_count1 + range_count2 - 1,
* but one extra won't hurt.
*-----------------------------------------------
*/
ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
range_count3 = 0;
/*
* For each range in mr1, keep intersecting until the ranges in mr2 have
* passed it. The parallel progress through mr1 and mr2 is similar to
* multirange_minus_multirange_internal, but we don't have to assign back
* to r1.
*/
r2 = ranges2[0];
for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
{
r1 = ranges1[i1];
/* Discard r2s while r2 << r1 */
while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
{
r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
}
while (r2 != NULL)
{
if (range_overlaps_internal(rangetyp, r1, r2))
{
/* Keep the overlapping part */
ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
/* If we "used up" all of r2, go to the next one... */
if (range_overleft_internal(rangetyp, r2, r1))
r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
/* ...otherwise go to the next r1 */
else
break;
}
else
/* We're past r1, so move to the next one */
break;
}
/* If we're out of r2s, there can be no more intersections */
if (r2 == NULL)
break;
}
return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
}
/*
* 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.
*/
Datum
range_agg_transfn(PG_FUNCTION_ARGS)
{
MemoryContext aggContext;
Oid rngtypoid;
ArrayBuildState *state;
if (!AggCheckCallContext(fcinfo, &aggContext))
elog(ERROR, "range_agg_transfn called in non-aggregate context");
rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
if (!type_is_range(rngtypoid))
elog(ERROR, "range_agg must be called with a range");
if (PG_ARGISNULL(0))
state = initArrayResult(rngtypoid, aggContext, false);
else
state = (ArrayBuildState *) PG_GETARG_POINTER(0);
/* skip NULLs */
if (!PG_ARGISNULL(1))
accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
PG_RETURN_POINTER(state);
}
/*
* range_agg_finalfn: use our internal array to merge touching ranges.
*
* Shared by range_agg_finalfn(anyrange) and
* multirange_agg_finalfn(anymultirange).
*/
Datum
range_agg_finalfn(PG_FUNCTION_ARGS)
{
MemoryContext aggContext;
Oid mltrngtypoid;
TypeCacheEntry *typcache;
ArrayBuildState *state;
int32 range_count;
RangeType **ranges;
int i;
if (!AggCheckCallContext(fcinfo, &aggContext))
elog(ERROR, "range_agg_finalfn called in non-aggregate context");
state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
if (state == NULL)
/* This shouldn't be possible, but just in case.... */
PG_RETURN_NULL();
/* Also return NULL if we had zero inputs, like other aggregates */
range_count = state->nelems;
if (range_count == 0)
PG_RETURN_NULL();
mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
ranges = palloc0(range_count * sizeof(RangeType *));
for (i = 0; i < range_count; i++)
ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
}
/*
* multirange_agg_transfn: combine adjacent/overlapping multiranges.
*
* All we do here is gather the input multiranges' ranges into an array so
* that the finalfn can sort and combine them.
*/
Datum
multirange_agg_transfn(PG_FUNCTION_ARGS)
{
MemoryContext aggContext;
Oid mltrngtypoid;
TypeCacheEntry *typcache;
TypeCacheEntry *rngtypcache;
ArrayBuildState *state;
if (!AggCheckCallContext(fcinfo, &aggContext))
elog(ERROR, "multirange_agg_transfn called in non-aggregate context");
mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
if (!type_is_multirange(mltrngtypoid))
elog(ERROR, "range_agg must be called with a multirange");
typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
rngtypcache = typcache->rngtype;
if (PG_ARGISNULL(0))
state = initArrayResult(rngtypcache->type_id, aggContext, false);
else
state = (ArrayBuildState *) PG_GETARG_POINTER(0);
/* skip NULLs */
if (!PG_ARGISNULL(1))
{
MultirangeType *current;
int32 range_count;
RangeType **ranges;
current = PG_GETARG_MULTIRANGE_P(1);
multirange_deserialize(rngtypcache, current, &range_count, &ranges);
if (range_count == 0)
{
/*
* Add an empty range so we get an empty result (not a null
* result).
*/
accumArrayResult(state,
RangeTypePGetDatum(make_empty_range(rngtypcache)),
false, rngtypcache->type_id, aggContext);
}
else
{
for (int32 i = 0; i < range_count; i++)
accumArrayResult(state, RangeTypePGetDatum(ranges[i]), false, rngtypcache->type_id, aggContext);
}
}
PG_RETURN_POINTER(state);
}
Datum
multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
{
MemoryContext aggContext;
Oid mltrngtypoid;
TypeCacheEntry *typcache;
MultirangeType *result;
MultirangeType *current;
int32 range_count1;
int32 range_count2;
RangeType **ranges1;
RangeType **ranges2;
if (!AggCheckCallContext(fcinfo, &aggContext))
elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
if (!type_is_multirange(mltrngtypoid))
elog(ERROR, "range_intersect_agg must be called with a multirange");
typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
/* strictness ensures these are non-null */
result = PG_GETARG_MULTIRANGE_P(0);
current = PG_GETARG_MULTIRANGE_P(1);
multirange_deserialize(typcache->rngtype, result, &range_count1, &ranges1);
multirange_deserialize(typcache->rngtype, current, &range_count2, &ranges2);
result = multirange_intersect_internal(mltrngtypoid,
typcache->rngtype,
range_count1,
ranges1,
range_count2,
ranges2);
PG_RETURN_MULTIRANGE_P(result);
}
/* multirange -> element type functions */
/* extract lower bound value */
Datum
multirange_lower(PG_FUNCTION_ARGS)
{
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
TypeCacheEntry *typcache;
RangeBound lower;
RangeBound upper;
if (MultirangeIsEmpty(mr))
PG_RETURN_NULL();
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
multirange_get_bounds(typcache->rngtype, mr, 0,
&lower, &upper);
if (!lower.infinite)
PG_RETURN_DATUM(lower.val);
else
PG_RETURN_NULL();
}
/* extract upper bound value */
Datum
multirange_upper(PG_FUNCTION_ARGS)
{
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
TypeCacheEntry *typcache;
RangeBound lower;
RangeBound upper;
if (MultirangeIsEmpty(mr))
PG_RETURN_NULL();
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
&lower, &upper);
if (!upper.infinite)
PG_RETURN_DATUM(upper.val);
else
PG_RETURN_NULL();
}
/* multirange -> bool functions */
/* is multirange empty? */
Datum
multirange_empty(PG_FUNCTION_ARGS)
{
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
PG_RETURN_BOOL(MultirangeIsEmpty(mr));
}
/* is lower bound inclusive? */
Datum
multirange_lower_inc(PG_FUNCTION_ARGS)
{
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
TypeCacheEntry *typcache;
RangeBound lower;
RangeBound upper;
if (MultirangeIsEmpty(mr))
PG_RETURN_BOOL(false);
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
multirange_get_bounds(typcache->rngtype, mr, 0,
&lower, &upper);
PG_RETURN_BOOL(lower.inclusive);
}
/* is upper bound inclusive? */
Datum
multirange_upper_inc(PG_FUNCTION_ARGS)
{
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
TypeCacheEntry *typcache;
RangeBound lower;
RangeBound upper;
if (MultirangeIsEmpty(mr))
PG_RETURN_BOOL(false);
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
&lower, &upper);
PG_RETURN_BOOL(upper.inclusive);
}
/* is lower bound infinite? */
Datum
multirange_lower_inf(PG_FUNCTION_ARGS)
{
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
TypeCacheEntry *typcache;
RangeBound lower;
RangeBound upper;
if (MultirangeIsEmpty(mr))
PG_RETURN_BOOL(false);
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
multirange_get_bounds(typcache->rngtype, mr, 0,
&lower, &upper);
PG_RETURN_BOOL(lower.infinite);
}
/* is upper bound infinite? */
Datum
multirange_upper_inf(PG_FUNCTION_ARGS)
{
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
TypeCacheEntry *typcache;
RangeBound lower;
RangeBound upper;
if (MultirangeIsEmpty(mr))
PG_RETURN_BOOL(false);
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
&lower, &upper);
PG_RETURN_BOOL(upper.infinite);
}
/* multirange, element -> bool functions */
/* contains? */
Datum
multirange_contains_elem(PG_FUNCTION_ARGS)
{
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
Datum val = PG_GETARG_DATUM(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
PG_RETURN_BOOL(multirange_contains_elem_internal(typcache->rngtype, mr, val));
}
/* contained by? */
Datum
elem_contained_by_multirange(PG_FUNCTION_ARGS)
{
Datum val = PG_GETARG_DATUM(0);
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
PG_RETURN_BOOL(multirange_contains_elem_internal(typcache->rngtype, mr, val));
}
/*
* Comparison function for checking if any range of multirange contains given
* key element using binary search.
*/
static int
multirange_elem_bsearch_comparison(TypeCacheEntry *typcache,
RangeBound *lower, RangeBound *upper,
void *key, bool *match)
{
Datum val = *((Datum *) key);
int cmp;
if (!lower->infinite)
{
cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
typcache->rng_collation,
lower->val, val));
if (cmp > 0 || (cmp == 0 && !lower->inclusive))
return -1;
}
if (!upper->infinite)
{
cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
typcache->rng_collation,
upper->val, val));
if (cmp < 0 || (cmp == 0 && !upper->inclusive))
return 1;
}
*match = true;
return 0;
}
/*
* Test whether multirange mr contains a specific element value.
*/
bool
multirange_contains_elem_internal(TypeCacheEntry *rangetyp,
const MultirangeType *mr, Datum val)
{
if (MultirangeIsEmpty(mr))
return false;
return multirange_bsearch_match(rangetyp, mr, &val,
multirange_elem_bsearch_comparison);
}
/* multirange, range -> bool functions */
/* contains? */
Datum
multirange_contains_range(PG_FUNCTION_ARGS)
{
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
RangeType *r = PG_GETARG_RANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
PG_RETURN_BOOL(multirange_contains_range_internal(typcache->rngtype, mr, r));
}
Datum
range_contains_multirange(PG_FUNCTION_ARGS)
{
RangeType *r = PG_GETARG_RANGE_P(0);
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
PG_RETURN_BOOL(range_contains_multirange_internal(typcache->rngtype, r, mr));
}
/* contained by? */
Datum
range_contained_by_multirange(PG_FUNCTION_ARGS)
{
RangeType *r = PG_GETARG_RANGE_P(0);
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
PG_RETURN_BOOL(multirange_contains_range_internal(typcache->rngtype, mr, r));
}
Datum
multirange_contained_by_range(PG_FUNCTION_ARGS)
{
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
RangeType *r = PG_GETARG_RANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
PG_RETURN_BOOL(range_contains_multirange_internal(typcache->rngtype, r, mr));
}
/*
* Comparison function for checking if any range of multirange contains given
* key range using binary search.
*/
static int
multirange_range_contains_bsearch_comparison(TypeCacheEntry *typcache,
RangeBound *lower, RangeBound *upper,
void *key, bool *match)
{
RangeBound *keyLower = (RangeBound *) key;
RangeBound *keyUpper = (RangeBound *) key + 1;
/* Check if key range is strictly in the left or in the right */
if (range_cmp_bounds(typcache, keyUpper, lower) < 0)
return -1;
if (range_cmp_bounds(typcache, keyLower, upper) > 0)
return 1;
/*
* At this point we found overlapping range. But we have to check if it
* really contains the key range. Anyway, we have to stop our search
* here, because multirange contains only non-overlapping ranges.
*/
*match = range_bounds_contains(typcache, lower, upper, keyLower, keyUpper);
return 0;
}
/*
* Test whether multirange mr contains a specific range r.
*/
bool
multirange_contains_range_internal(TypeCacheEntry *rangetyp,
const MultirangeType *mr,
const RangeType *r)
{
RangeBound bounds[2];
bool empty;
/*
* Every multirange contains an infinite number of empty ranges, even an
* empty one.
*/
if (RangeIsEmpty(r))
return true;
if (MultirangeIsEmpty(mr))
return false;
range_deserialize(rangetyp, r, &bounds[0], &bounds[1], &empty);
Assert(!empty);
return multirange_bsearch_match(rangetyp, mr, bounds,
multirange_range_contains_bsearch_comparison);
}
/*
* Test whether range r contains a multirange mr.
*/
bool
range_contains_multirange_internal(TypeCacheEntry *rangetyp,
const RangeType *r,
const MultirangeType *mr)
{
RangeBound lower1,
upper1,
lower2,
upper2,
tmp;
bool empty;
/*
* Every range contains an infinite number of empty multiranges, even an
* empty one.
*/
if (MultirangeIsEmpty(mr))
return true;
if (RangeIsEmpty(r))
return false;
/* Range contains multirange iff it contains its union range. */
range_deserialize(rangetyp, r, &lower1, &upper1, &empty);
Assert(!empty);
multirange_get_bounds(rangetyp, mr, 0, &lower2, &tmp);
multirange_get_bounds(rangetyp, mr, mr->rangeCount - 1, &tmp, &upper2);
return range_bounds_contains(rangetyp, &lower1, &upper1, &lower2, &upper2);
}
/* multirange, multirange -> bool functions */
/* equality (internal version) */
bool
multirange_eq_internal(TypeCacheEntry *rangetyp,
const MultirangeType *mr1,
const MultirangeType *mr2)
{
int32 range_count_1;
int32 range_count_2;
int32 i;
RangeBound lower1,
upper1,
lower2,
upper2;
/* Different types should be prevented by ANYMULTIRANGE matching rules */
if (MultirangeTypeGetOid(mr1) != MultirangeTypeGetOid(mr2))
elog(ERROR, "multirange types do not match");
range_count_1 = mr1->rangeCount;
range_count_2 = mr2->rangeCount;
if (range_count_1 != range_count_2)
return false;
for (i = 0; i < range_count_1; i++)
{
multirange_get_bounds(rangetyp, mr1, i, &lower1, &upper1);
multirange_get_bounds(rangetyp, mr2, i, &lower2, &upper2);
if (range_cmp_bounds(rangetyp, &lower1, &lower2) != 0 ||
range_cmp_bounds(rangetyp, &upper1, &upper2) != 0)
return false;
}
return true;
}
/* equality */
Datum
multirange_eq(PG_FUNCTION_ARGS)
{
MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
PG_RETURN_BOOL(multirange_eq_internal(typcache->rngtype, mr1, mr2));
}
/* inequality (internal version) */
bool
multirange_ne_internal(TypeCacheEntry *rangetyp,
const MultirangeType *mr1,
const MultirangeType *mr2)
{
return (!multirange_eq_internal(rangetyp, mr1, mr2));
}
/* inequality */
Datum
multirange_ne(PG_FUNCTION_ARGS)
{
MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
PG_RETURN_BOOL(multirange_ne_internal(typcache->rngtype, mr1, mr2));
}
/* overlaps? */
Datum
range_overlaps_multirange(PG_FUNCTION_ARGS)
{
RangeType *r = PG_GETARG_RANGE_P(0);
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache->rngtype, r, mr));
}
Datum
multirange_overlaps_range(PG_FUNCTION_ARGS)
{
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
RangeType *r = PG_GETARG_RANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache->rngtype, r, mr));
}
Datum
multirange_overlaps_multirange(PG_FUNCTION_ARGS)
{
MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
PG_RETURN_BOOL(multirange_overlaps_multirange_internal(typcache->rngtype, mr1, mr2));
}
/*
* Comparison function for checking if any range of multirange overlaps given
* key range using binary search.
*/
static int
multirange_range_overlaps_bsearch_comparison(TypeCacheEntry *typcache,
RangeBound *lower, RangeBound *upper,
void *key, bool *match)
{
RangeBound *keyLower = (RangeBound *) key;
RangeBound *keyUpper = (RangeBound *) key + 1;
if (range_cmp_bounds(typcache, keyUpper, lower) < 0)
return -1;
if (range_cmp_bounds(typcache, keyLower, upper) > 0)
return 1;
*match = true;
return 0;
}
bool
range_overlaps_multirange_internal(TypeCacheEntry *rangetyp,
const RangeType *r,
const MultirangeType *mr)
{
RangeBound bounds[2];
bool empty;
/*
* Empties never overlap, even with empties. (This seems strange since
* they *do* contain each other, but we want to follow how ranges work.)
*/
if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
return false;
range_deserialize(rangetyp, r, &bounds[0], &bounds[1], &empty);
Assert(!empty);
return multirange_bsearch_match(rangetyp, mr, bounds,
multirange_range_overlaps_bsearch_comparison);
}
bool
multirange_overlaps_multirange_internal(TypeCacheEntry *rangetyp,
const MultirangeType *mr1,
const MultirangeType *mr2)
{
int32 range_count1;
int32 range_count2;
int32 i1;
int32 i2;
RangeBound lower1,
upper1,
lower2,
upper2;
/*
* Empties never overlap, even with empties. (This seems strange since
* they *do* contain each other, but we want to follow how ranges work.)
*/
if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
return false;
range_count1 = mr1->rangeCount;
range_count2 = mr2->rangeCount;
/*
* Every range in mr1 gets a chance to overlap with the ranges in mr2, but
* we can use their ordering to avoid O(n^2). This is similar to
* range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
* don't find an overlap with r we're done, and here if we don't find an
* overlap with r2 we try the next r2.
*/
i1 = 0;
multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1);
for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
{
multirange_get_bounds(rangetyp, mr2, i2, &lower2, &upper2);
/* Discard r1s while r1 << r2 */
while (range_cmp_bounds(rangetyp, &upper1, &lower2) < 0)
{
if (++i1 >= range_count1)
return false;
multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1);
}
/*
* If r1 && r2, we're done, otherwise we failed to find an overlap for
* r2, so go to the next one.
*/
if (range_bounds_overlaps(rangetyp, &lower1, &upper1, &lower2, &upper2))
return true;
}
/* We looked through all of mr2 without finding an overlap */
return false;
}
/* does not extend to right of? */
bool
range_overleft_multirange_internal(TypeCacheEntry *rangetyp,
const RangeType *r,
const MultirangeType *mr)
{
RangeBound lower1,
upper1,
lower2,
upper2;
bool empty;
if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
PG_RETURN_BOOL(false);
range_deserialize(rangetyp, r, &lower1, &upper1, &empty);
Assert(!empty);
multirange_get_bounds(rangetyp, mr, mr->rangeCount - 1,
&lower2, &upper2);
PG_RETURN_BOOL(range_cmp_bounds(rangetyp, &upper1, &upper2) <= 0);
}
Datum
range_overleft_multirange(PG_FUNCTION_ARGS)
{
RangeType *r = PG_GETARG_RANGE_P(0);
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
PG_RETURN_BOOL(range_overleft_multirange_internal(typcache->rngtype, r, mr));
}
Datum
multirange_overleft_range(PG_FUNCTION_ARGS)
{
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
RangeType *r = PG_GETARG_RANGE_P(1);
TypeCacheEntry *typcache;
RangeBound lower1,
upper1,
lower2,
upper2;
bool empty;
if (MultirangeIsEmpty(mr) || RangeIsEmpty(r))
PG_RETURN_BOOL(false);
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
&lower1, &upper1);
range_deserialize(typcache->rngtype, r, &lower2, &upper2, &empty);
Assert(!empty);
PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &upper1, &upper2) <= 0);
}
Datum
multirange_overleft_multirange(PG_FUNCTION_ARGS)
{
MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
RangeBound lower1,
upper1,
lower2,
upper2;
if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
PG_RETURN_BOOL(false);
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
multirange_get_bounds(typcache->rngtype, mr1, mr1->rangeCount - 1,
&lower1, &upper1);
multirange_get_bounds(typcache->rngtype, mr2, mr2->rangeCount - 1,
&lower2, &upper2);
PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &upper1, &upper2) <= 0);
}
/* does not extend to left of? */
bool
range_overright_multirange_internal(TypeCacheEntry *rangetyp,
const RangeType *r,
const MultirangeType *mr)
{
RangeBound lower1,
upper1,
lower2,
upper2;
bool empty;
if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
PG_RETURN_BOOL(false);
range_deserialize(rangetyp, r, &lower1, &upper1, &empty);
Assert(!empty);
multirange_get_bounds(rangetyp, mr, 0, &lower2, &upper2);
return (range_cmp_bounds(rangetyp, &lower1, &lower2) >= 0);
}
Datum
range_overright_multirange(PG_FUNCTION_ARGS)
{
RangeType *r = PG_GETARG_RANGE_P(0);
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
PG_RETURN_BOOL(range_overright_multirange_internal(typcache->rngtype, r, mr));
}
Datum
multirange_overright_range(PG_FUNCTION_ARGS)
{
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
RangeType *r = PG_GETARG_RANGE_P(1);
TypeCacheEntry *typcache;
RangeBound lower1,
upper1,
lower2,
upper2;
bool empty;
if (MultirangeIsEmpty(mr) || RangeIsEmpty(r))
PG_RETURN_BOOL(false);
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
multirange_get_bounds(typcache->rngtype, mr, 0, &lower1, &upper1);
range_deserialize(typcache->rngtype, r, &lower2, &upper2, &empty);
Assert(!empty);
PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &lower1, &lower2) >= 0);
}
Datum
multirange_overright_multirange(PG_FUNCTION_ARGS)
{
MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
RangeBound lower1,
upper1,
lower2,
upper2;
if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
PG_RETURN_BOOL(false);
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
multirange_get_bounds(typcache->rngtype, mr1, 0, &lower1, &upper1);
multirange_get_bounds(typcache->rngtype, mr2, 0, &lower2, &upper2);
PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &lower1, &lower2) >= 0);
}
/* contains? */
Datum
multirange_contains_multirange(PG_FUNCTION_ARGS)
{
MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
PG_RETURN_BOOL(multirange_contains_multirange_internal(typcache->rngtype, mr1, mr2));
}
/* contained by? */
Datum
multirange_contained_by_multirange(PG_FUNCTION_ARGS)
{
MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
PG_RETURN_BOOL(multirange_contains_multirange_internal(typcache->rngtype, mr2, mr1));
}
/*
* Test whether multirange mr1 contains every range from another multirange mr2.
*/
bool
multirange_contains_multirange_internal(TypeCacheEntry *rangetyp,
const MultirangeType *mr1,
const MultirangeType *mr2)
{
int32 range_count1 = mr1->rangeCount;
int32 range_count2 = mr2->rangeCount;
int i1,
i2;
RangeBound lower1,
upper1,
lower2,
upper2;
/*
* We follow the same logic for empties as ranges: - an empty multirange
* contains an empty range/multirange. - an empty multirange can't contain
* any other range/multirange. - an empty multirange is contained by any
* other range/multirange.
*/
if (range_count2 == 0)
return true;
if (range_count1 == 0)
return false;
/*
* Every range in mr2 must be contained by some range in mr1. To avoid
* O(n^2) we walk through both ranges in tandem.
*/
i1 = 0;
multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1);
for (i2 = 0; i2 < range_count2; i2++)
{
multirange_get_bounds(rangetyp, mr2, i2, &lower2, &upper2);
/* Discard r1s while r1 << r2 */
while (range_cmp_bounds(rangetyp, &upper1, &lower2) < 0)
{
if (++i1 >= range_count1)
return false;
multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1);
}
/*
* If r1 @> r2, go to the next r2, otherwise return false (since every
* r1[n] and r1[n+1] must have a gap). Note this will give weird
* answers if you don't canonicalize, e.g. with a custom
* int2multirange {[1,1], [2,2]} there is a "gap". But that is
* consistent with other range operators, e.g. '[1,1]'::int2range -|-
* '[2,2]'::int2range is false.
*/
if (!range_bounds_contains(rangetyp, &lower1, &upper1,
&lower2, &upper2))
return false;
}
/* All ranges in mr2 are satisfied */
return true;
}
/* strictly left of? */
Datum
range_before_multirange(PG_FUNCTION_ARGS)
{
RangeType *r = PG_GETARG_RANGE_P(0);
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
PG_RETURN_BOOL(range_before_multirange_internal(typcache->rngtype, r, mr));
}
Datum
multirange_before_range(PG_FUNCTION_ARGS)
{
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
RangeType *r = PG_GETARG_RANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
PG_RETURN_BOOL(range_after_multirange_internal(typcache->rngtype, r, mr));
}
Datum
multirange_before_multirange(PG_FUNCTION_ARGS)
{
MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
PG_RETURN_BOOL(multirange_before_multirange_internal(typcache->rngtype, mr1, mr2));
}
/* strictly right of? */
Datum
range_after_multirange(PG_FUNCTION_ARGS)
{
RangeType *r = PG_GETARG_RANGE_P(0);
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
PG_RETURN_BOOL(range_after_multirange_internal(typcache->rngtype, r, mr));
}
Datum
multirange_after_range(PG_FUNCTION_ARGS)
{
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
RangeType *r = PG_GETARG_RANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
PG_RETURN_BOOL(range_before_multirange_internal(typcache->rngtype, r, mr));
}
Datum
multirange_after_multirange(PG_FUNCTION_ARGS)
{
MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
PG_RETURN_BOOL(multirange_before_multirange_internal(typcache->rngtype, mr2, mr1));
}
/* strictly left of? (internal version) */
bool
range_before_multirange_internal(TypeCacheEntry *rangetyp,
const RangeType *r,
const MultirangeType *mr)
{
RangeBound lower1,
upper1,
lower2,
upper2;
bool empty;
if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
return false;
range_deserialize(rangetyp, r, &lower1, &upper1, &empty);
Assert(!empty);
multirange_get_bounds(rangetyp, mr, 0, &lower2, &upper2);
return (range_cmp_bounds(rangetyp, &upper1, &lower2) < 0);
}
bool
multirange_before_multirange_internal(TypeCacheEntry *rangetyp,
const MultirangeType *mr1,
const MultirangeType *mr2)
{
RangeBound lower1,
upper1,
lower2,
upper2;
if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
return false;
multirange_get_bounds(rangetyp, mr1, mr1->rangeCount - 1,
&lower1, &upper1);
multirange_get_bounds(rangetyp, mr2, 0,
&lower2, &upper2);
return (range_cmp_bounds(rangetyp, &upper1, &lower2) < 0);
}
/* strictly right of? (internal version) */
bool
range_after_multirange_internal(TypeCacheEntry *rangetyp,
const RangeType *r,
const MultirangeType *mr)
{
RangeBound lower1,
upper1,
lower2,
upper2;
bool empty;
int32 range_count;
if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
return false;
range_deserialize(rangetyp, r, &lower1, &upper1, &empty);
Assert(!empty);
range_count = mr->rangeCount;
multirange_get_bounds(rangetyp, mr, range_count - 1,
&lower2, &upper2);
return (range_cmp_bounds(rangetyp, &lower1, &upper2) > 0);
}
bool
range_adjacent_multirange_internal(TypeCacheEntry *rangetyp,
const RangeType *r,
const MultirangeType *mr)
{
RangeBound lower1,
upper1,
lower2,
upper2;
bool empty;
int32 range_count;
if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
return false;
range_deserialize(rangetyp, r, &lower1, &upper1, &empty);
Assert(!empty);
range_count = mr->rangeCount;
multirange_get_bounds(rangetyp, mr, 0,
&lower2, &upper2);
if (bounds_adjacent(rangetyp, upper1, lower2))
return true;
if (range_count > 1)
multirange_get_bounds(rangetyp, mr, range_count - 1,
&lower2, &upper2);
if (bounds_adjacent(rangetyp, upper2, lower1))
return true;
return false;
}
/* adjacent to? */
Datum
range_adjacent_multirange(PG_FUNCTION_ARGS)
{
RangeType *r = PG_GETARG_RANGE_P(0);
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
PG_RETURN_BOOL(range_adjacent_multirange_internal(typcache->rngtype, r, mr));
}
Datum
multirange_adjacent_range(PG_FUNCTION_ARGS)
{
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
RangeType *r = PG_GETARG_RANGE_P(1);
TypeCacheEntry *typcache;
if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
return false;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
PG_RETURN_BOOL(range_adjacent_multirange_internal(typcache->rngtype, r, mr));
}
Datum
multirange_adjacent_multirange(PG_FUNCTION_ARGS)
{
MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
TypeCacheEntry *typcache;
int32 range_count1;
int32 range_count2;
RangeBound lower1,
upper1,
lower2,
upper2;
if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
return false;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
range_count1 = mr1->rangeCount;
range_count2 = mr2->rangeCount;
multirange_get_bounds(typcache->rngtype, mr1, range_count1 - 1,
&lower1, &upper1);
multirange_get_bounds(typcache->rngtype, mr2, 0,
&lower2, &upper2);
if (bounds_adjacent(typcache->rngtype, upper1, lower2))
PG_RETURN_BOOL(true);
if (range_count1 > 1)
multirange_get_bounds(typcache->rngtype, mr1, 0,
&lower1, &upper1);
if (range_count2 > 1)
multirange_get_bounds(typcache->rngtype, mr2, range_count2 - 1,
&lower2, &upper2);
if (bounds_adjacent(typcache->rngtype, upper2, lower1))
PG_RETURN_BOOL(true);
PG_RETURN_BOOL(false);
}
/* Btree support */
/* btree comparator */
Datum
multirange_cmp(PG_FUNCTION_ARGS)
{
MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
int32 range_count_1;
int32 range_count_2;
int32 range_count_max;
int32 i;
TypeCacheEntry *typcache;
int cmp = 0; /* If both are empty we'll use this. */
/* Different types should be prevented by ANYMULTIRANGE matching rules */
if (MultirangeTypeGetOid(mr1) != MultirangeTypeGetOid(mr2))
elog(ERROR, "multirange types do not match");
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
range_count_1 = mr1->rangeCount;
range_count_2 = mr2->rangeCount;
/* Loop over source data */
range_count_max = Max(range_count_1, range_count_2);
for (i = 0; i < range_count_max; i++)
{
RangeBound lower1,
upper1,
lower2,
upper2;
/*
* If one multirange is shorter, it's as if it had empty ranges at the
* end to extend its length. An empty range compares earlier than any
* other range, so the shorter multirange comes before the longer.
* This is the same behavior as in other types, e.g. in strings 'aaa'
* < 'aaaaaa'.
*/
if (i >= range_count_1)
{
cmp = -1;
break;
}
if (i >= range_count_2)
{
cmp = 1;
break;
}
multirange_get_bounds(typcache->rngtype, mr1, i, &lower1, &upper1);
multirange_get_bounds(typcache->rngtype, mr2, i, &lower2, &upper2);
cmp = range_cmp_bounds(typcache->rngtype, &lower1, &lower2);
if (cmp == 0)
cmp = range_cmp_bounds(typcache->rngtype, &upper1, &upper2);
if (cmp != 0)
break;
}
PG_FREE_IF_COPY(mr1, 0);
PG_FREE_IF_COPY(mr2, 1);
PG_RETURN_INT32(cmp);
}
/* inequality operators using the multirange_cmp function */
Datum
multirange_lt(PG_FUNCTION_ARGS)
{
int cmp = multirange_cmp(fcinfo);
PG_RETURN_BOOL(cmp < 0);
}
Datum
multirange_le(PG_FUNCTION_ARGS)
{
int cmp = multirange_cmp(fcinfo);
PG_RETURN_BOOL(cmp <= 0);
}
Datum
multirange_ge(PG_FUNCTION_ARGS)
{
int cmp = multirange_cmp(fcinfo);
PG_RETURN_BOOL(cmp >= 0);
}
Datum
multirange_gt(PG_FUNCTION_ARGS)
{
int cmp = multirange_cmp(fcinfo);
PG_RETURN_BOOL(cmp > 0);
}
/* multirange -> range functions */
/* Find the smallest range that includes everything in the multirange */
Datum
range_merge_from_multirange(PG_FUNCTION_ARGS)
{
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
Oid mltrngtypoid = MultirangeTypeGetOid(mr);
TypeCacheEntry *typcache;
RangeType *result;
typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
if (MultirangeIsEmpty(mr))
{
result = make_empty_range(typcache->rngtype);
}
else if (mr->rangeCount == 1)
{
result = multirange_get_range(typcache->rngtype, mr, 0);
}
else
{
RangeBound firstLower,
firstUpper,
lastLower,
lastUpper;
multirange_get_bounds(typcache->rngtype, mr, 0,
&firstLower, &firstUpper);
multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
&lastLower, &lastUpper);
result = make_range(typcache->rngtype, &firstLower, &lastUpper,
false, NULL);
}
PG_RETURN_RANGE_P(result);
}
/* Turn multirange into a set of ranges */
Datum
multirange_unnest(PG_FUNCTION_ARGS)
{
typedef struct
{
MultirangeType *mr;
TypeCacheEntry *typcache;
int index;
} multirange_unnest_fctx;
FuncCallContext *funcctx;
multirange_unnest_fctx *fctx;
MemoryContext oldcontext;
/* stuff done only on the first call of the function */
if (SRF_IS_FIRSTCALL())
{
MultirangeType *mr;
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
/*
* switch to memory context appropriate for multiple function calls
*/
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/*
* Get the multirange value and detoast if needed. We can't do this
* earlier because if we have to detoast, we want the detoasted copy
* to be in multi_call_memory_ctx, so it will go away when we're done
* and not before. (If no detoast happens, we assume the originally
* passed multirange will stick around till then.)
*/
mr = PG_GETARG_MULTIRANGE_P(0);
/* allocate memory for user context */
fctx = (multirange_unnest_fctx *) palloc(sizeof(multirange_unnest_fctx));
/* initialize state */
fctx->mr = mr;
fctx->index = 0;
fctx->typcache = lookup_type_cache(MultirangeTypeGetOid(mr),
TYPECACHE_MULTIRANGE_INFO);
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
}
/* stuff done on every call of the function */
funcctx = SRF_PERCALL_SETUP();
fctx = funcctx->user_fctx;
if (fctx->index < fctx->mr->rangeCount)
{
RangeType *range;
range = multirange_get_range(fctx->typcache->rngtype,
fctx->mr,
fctx->index);
fctx->index++;
SRF_RETURN_NEXT(funcctx, RangeTypePGetDatum(range));
}
else
{
/* do when there is no more left */
SRF_RETURN_DONE(funcctx);
}
}
/* Hash support */
/* hash a multirange value */
Datum
hash_multirange(PG_FUNCTION_ARGS)
{
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
uint32 result = 1;
TypeCacheEntry *typcache,
*scache;
int32 range_count,
i;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
scache = typcache->rngtype->rngelemtype;
if (!OidIsValid(scache->hash_proc_finfo.fn_oid))
{
scache = lookup_type_cache(scache->type_id,
TYPECACHE_HASH_PROC_FINFO);
if (!OidIsValid(scache->hash_proc_finfo.fn_oid))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("could not identify a hash function for type %s",
format_type_be(scache->type_id))));
}
range_count = mr->rangeCount;
for (i = 0; i < range_count; i++)
{
RangeBound lower,
upper;
uint8 flags = MultirangeGetFlagsPtr(mr)[i];
uint32 lower_hash;
uint32 upper_hash;
uint32 range_hash;
multirange_get_bounds(typcache->rngtype, mr, i, &lower, &upper);
if (RANGE_HAS_LBOUND(flags))
lower_hash = DatumGetUInt32(FunctionCall1Coll(&scache->hash_proc_finfo,
typcache->rngtype->rng_collation,
lower.val));
else
lower_hash = 0;
if (RANGE_HAS_UBOUND(flags))
upper_hash = DatumGetUInt32(FunctionCall1Coll(&scache->hash_proc_finfo,
typcache->rngtype->rng_collation,
upper.val));
else
upper_hash = 0;
/* Merge hashes of flags and bounds */
range_hash = hash_uint32((uint32) flags);
range_hash ^= lower_hash;
range_hash = pg_rotate_left32(range_hash, 1);
range_hash ^= upper_hash;
/*
* Use the same approach as hash_array to combine the individual
* elements' hash values:
*/
result = (result << 5) - result + range_hash;
}
PG_FREE_IF_COPY(mr, 0);
PG_RETURN_UINT32(result);
}
/*
* Returns 64-bit value by hashing a value to a 64-bit value, with a seed.
* Otherwise, similar to hash_multirange.
*/
Datum
hash_multirange_extended(PG_FUNCTION_ARGS)
{
MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
Datum seed = PG_GETARG_DATUM(1);
uint64 result = 1;
TypeCacheEntry *typcache,
*scache;
int32 range_count,
i;
typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
scache = typcache->rngtype->rngelemtype;
if (!OidIsValid(scache->hash_extended_proc_finfo.fn_oid))
{
scache = lookup_type_cache(scache->type_id,
TYPECACHE_HASH_EXTENDED_PROC_FINFO);
if (!OidIsValid(scache->hash_extended_proc_finfo.fn_oid))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("could not identify a hash function for type %s",
format_type_be(scache->type_id))));
}
range_count = mr->rangeCount;
for (i = 0; i < range_count; i++)
{
RangeBound lower,
upper;
uint8 flags = MultirangeGetFlagsPtr(mr)[i];
uint64 lower_hash;
uint64 upper_hash;
uint64 range_hash;
multirange_get_bounds(typcache->rngtype, mr, i, &lower, &upper);
if (RANGE_HAS_LBOUND(flags))
lower_hash = DatumGetUInt64(FunctionCall2Coll(&scache->hash_extended_proc_finfo,
typcache->rngtype->rng_collation,
lower.val,
seed));
else
lower_hash = 0;
if (RANGE_HAS_UBOUND(flags))
upper_hash = DatumGetUInt64(FunctionCall2Coll(&scache->hash_extended_proc_finfo,
typcache->rngtype->rng_collation,
upper.val,
seed));
else
upper_hash = 0;
/* Merge hashes of flags and bounds */
range_hash = DatumGetUInt64(hash_uint32_extended((uint32) flags,
DatumGetInt64(seed)));
range_hash ^= lower_hash;
range_hash = ROTATE_HIGH_AND_LOW_32BITS(range_hash);
range_hash ^= upper_hash;
/*
* Use the same approach as hash_array to combine the individual
* elements' hash values:
*/
result = (result << 5) - result + range_hash;
}
PG_FREE_IF_COPY(mr, 0);
PG_RETURN_UINT64(result);
}