/*-------------------------------------------------------------------------
 *
 * 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-2021, 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 "lib/stringinfo.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rangetypes.h"
#include "utils/multirangetypes.h"
#include "utils/array.h"
#include "utils/memutils.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);
	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;

	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
		ereport(ERROR,
				(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')
			ereport(ERROR,
					(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
					ereport(ERROR,
							(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++;
					range = DatumGetRangeTypeP(InputFunctionCall(&cache->typioproc,
																 range_str,
																 cache->typioparam,
																 typmod));
					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
					ereport(ERROR,
							(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')
		ereport(ERROR,
				(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_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);
}


/*
 * 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)
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("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)
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("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))
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("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.
 */
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));
}

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))
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("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_RANGE_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);
	}

	PG_RETURN_RANGE_P(result);
}

/* 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 = (range_hash << 1) | (range_hash >> 31);
		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);
}
