v1-0002-Add-multirange_me_ops-GiST-opclass-using-multi-en.patch
application/octet-stream
Filename: v1-0002-Add-multirange_me_ops-GiST-opclass-using-multi-en.patch
Type: application/octet-stream
Part: 1
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v1-0002
Subject: Add multirange_me_ops GiST opclass using multi-entry
| File | + | − |
|---|---|---|
| src/backend/utils/adt/rangetypes_gist.c | 466 | 0 |
| src/include/catalog/pg_amop.dat | 56 | 0 |
| src/include/catalog/pg_amproc.dat | 21 | 0 |
| src/include/catalog/pg_opclass.dat | 3 | 0 |
| src/include/catalog/pg_opfamily.dat | 2 | 0 |
| src/include/catalog/pg_proc.dat | 8 | 0 |
| src/test/regress/expected/multirangetypes.out | 672 | 0 |
| src/test/regress/sql/multirangetypes.sql | 154 | 0 |
From 18f4cb4bd7af1c7d7763262a7465431cbde46379 Mon Sep 17 00:00:00 2001
From: Maxime Schoemans <maxime.schoemans@enterprisedb.com>
Date: Thu, 2 Apr 2026 18:15:00 +0200
Subject: [PATCH v1 2/4] Add multirange_me_ops GiST opclass using multi-entry
Add a new GiST opclass, multirange_me_ops, that uses the multi-entry
infrastructure from the previous commit to decompose multiranges into
their component ranges for indexing. Unlike the existing multirange_ops
which compresses a multirange into its bounding union range (losing gap
information), this opclass stores each component range as a separate
index entry, giving the R-tree much tighter bounds and significantly
reducing false-positive rechecks.
The opclass provides:
- extractValue: decomposes a multirange into component ranges. Empty
multiranges are stored as empty range sentinels (not NULLs) so they
remain visible to operator queries.
- Leaf consistent functions: account for the fact that each leaf entry
is a single component, not the full multirange. OVERLAPS and
CONTAINS_ELEM are recheck-free (a component match implies a
multirange match); all other strategies set recheck.
- Internal consistent functions: like the standard range GiST internal
checks, but CONTAINS and EQ are relaxed to use overlap instead of
containment, since a matching multirange's components may be spread
across multiple subtrees.
The opclass is non-default (multirange_ops remains the default) and
supports the same operator set: <<, &<, &&, &>, >>, -|-, @>, <@, @>elem,
and =.
Regression tests verify index correctness against sequential scan results
for all operators, including empty range/multirange edge cases, bitmap
scans, multi-column restriction enforcement, and NULL handling.
---
src/backend/utils/adt/rangetypes_gist.c | 466 ++++++++++++
src/include/catalog/pg_amop.dat | 56 ++
src/include/catalog/pg_amproc.dat | 21 +
src/include/catalog/pg_opclass.dat | 3 +
src/include/catalog/pg_opfamily.dat | 2 +
src/include/catalog/pg_proc.dat | 8 +
src/test/regress/expected/multirangetypes.out | 672 ++++++++++++++++++
src/test/regress/sql/multirangetypes.sql | 154 ++++
8 files changed, 1382 insertions(+)
diff --git a/src/backend/utils/adt/rangetypes_gist.c b/src/backend/utils/adt/rangetypes_gist.c
index 1a01a8f4c3c..222103cbb57 100644
--- a/src/backend/utils/adt/rangetypes_gist.c
+++ b/src/backend/utils/adt/rangetypes_gist.c
@@ -160,6 +160,30 @@ static bool range_gist_consistent_leaf_element(TypeCacheEntry *typcache,
StrategyNumber strategy,
const RangeType *key,
Datum query);
+static bool range_me_consistent_int_range(TypeCacheEntry *typcache,
+ StrategyNumber strategy,
+ const RangeType *key,
+ const RangeType *query);
+static bool range_me_consistent_int_multirange(TypeCacheEntry *typcache,
+ StrategyNumber strategy,
+ const RangeType *key,
+ const MultirangeType *query);
+static bool range_me_consistent_int_element(TypeCacheEntry *typcache,
+ StrategyNumber strategy,
+ const RangeType *key,
+ Datum query);
+static bool range_me_consistent_leaf_range(TypeCacheEntry *typcache,
+ StrategyNumber strategy,
+ const RangeType *key,
+ const RangeType *query);
+static bool range_me_consistent_leaf_multirange(TypeCacheEntry *typcache,
+ StrategyNumber strategy,
+ const RangeType *key,
+ const MultirangeType *query);
+static bool range_me_consistent_leaf_element(TypeCacheEntry *typcache,
+ StrategyNumber strategy,
+ const RangeType *key,
+ Datum query);
static void range_gist_fallback_split(TypeCacheEntry *typcache,
GistEntryVector *entryvec,
GIST_SPLITVEC *v);
@@ -1796,3 +1820,445 @@ call_subtype_diff(TypeCacheEntry *typcache, Datum val1, Datum val2)
return value;
return 0.0;
}
+
+
+/*
+ *----------------------------------------------------------
+ * MULTI-ENTRY GiST SUPPORT FOR MULTIRANGES
+ *
+ * When an extractValue support function is registered, GiST decomposes
+ * each multirange into its component ranges and stores each as a separate
+ * index entry. This requires a different consistent function because
+ * leaf entries are individual component ranges, not the union range.
+ *----------------------------------------------------------
+ */
+
+/*
+ * Multi-entry GiST extractValue function for multirange types.
+ *
+ * Decomposes a multirange into its component ranges. Returns an array
+ * of Datum values (one per range) via the return value, and sets *nkeys
+ * to the number of entries.
+ */
+Datum
+multirange_gist_extractvalue(PG_FUNCTION_ARGS)
+{
+ MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+ int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
+ TypeCacheEntry *typcache;
+ int32 range_count;
+ RangeType **ranges;
+ Datum *entries;
+ int i;
+
+ typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+ if (MultirangeIsEmpty(mr))
+ {
+ /*
+ * Store an empty range as a sentinel for empty multiranges so they
+ * remain visible to operator queries (NULL entries only match IS NULL).
+ */
+ RangeBound lower = {0};
+ RangeBound upper = {0};
+
+ lower.lower = true;
+ entries = palloc(sizeof(Datum));
+ entries[0] = RangeTypePGetDatum(
+ make_range(typcache->rngtype, &lower, &upper, true, NULL));
+ *nkeys = 1;
+ PG_RETURN_POINTER(entries);
+ }
+
+ multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+ entries = palloc(sizeof(Datum) * range_count);
+ for (i = 0; i < range_count; i++)
+ entries[i] = RangeTypePGetDatum(ranges[i]);
+
+ *nkeys = range_count;
+ PG_RETURN_POINTER(entries);
+}
+
+/*
+ * Multi-entry GiST consistent function for multirange types.
+ *
+ * This is used when the multirange opclass has an extractValue function.
+ * Leaf entries are individual component ranges (not the union range), so
+ * most leaf checks set recheck=true since a single component cannot fully
+ * determine the relationship with the query. OVERLAPS and CONTAINS_ELEM
+ * are exact per-component and skip recheck (see comments in the leaf
+ * consistent functions below). Internal nodes still store union ranges,
+ * so the internal consistent checks are unchanged.
+ */
+Datum
+multirange_gist_me_consistent(PG_FUNCTION_ARGS)
+{
+ GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
+ Datum query = PG_GETARG_DATUM(1);
+ StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
+ bool result;
+ Oid subtype = PG_GETARG_OID(3);
+ bool *recheck = (bool *) PG_GETARG_POINTER(4);
+ RangeType *key = DatumGetRangeTypeP(entry->key);
+ TypeCacheEntry *typcache;
+
+ typcache = range_get_typcache(fcinfo, RangeTypeGetOid(key));
+
+ if (GIST_LEAF(entry))
+ {
+ /*
+ * Leaf entries are individual component ranges from the multirange.
+ * Use multi-entry leaf consistent functions which account for the
+ * fact that we're seeing only one component, not the full value.
+ *
+ * Most strategies need recheck because a single component cannot
+ * determine the full multirange's relationship with the query.
+ * However, some strategies are exact per-component:
+ *
+ * - OVERLAPS: if component Ri overlaps Q, then M overlaps Q, because
+ * the shared points between Ri and Q are also in M.
+ * - CONTAINS_ELEM: if Ri contains elem, M contains elem, because
+ * Ri is a subset of M.
+ */
+ if (strategy == RANGESTRAT_OVERLAPS ||
+ strategy == RANGESTRAT_CONTAINS_ELEM)
+ *recheck = false;
+ else
+ *recheck = true;
+
+ if (!OidIsValid(subtype) || subtype == ANYMULTIRANGEOID)
+ result = range_me_consistent_leaf_multirange(typcache, strategy,
+ key,
+ DatumGetMultirangeTypeP(query));
+ else if (subtype == ANYRANGEOID)
+ result = range_me_consistent_leaf_range(typcache, strategy, key,
+ DatumGetRangeTypeP(query));
+ else
+ result = range_me_consistent_leaf_element(typcache, strategy,
+ key, query);
+ }
+ else
+ {
+ /*
+ * Internal nodes store union ranges of components from potentially
+ * many different multiranges. We use multi-entry-aware internal
+ * consistent functions that relax CONTAINS and EQ checks: the
+ * standard functions require the union key to fully contain the
+ * query, but in multi-entry a matching multirange's components may
+ * be spread across multiple subtrees.
+ */
+ if (!OidIsValid(subtype) || subtype == ANYMULTIRANGEOID)
+ result = range_me_consistent_int_multirange(typcache, strategy,
+ key,
+ DatumGetMultirangeTypeP(query));
+ else if (subtype == ANYRANGEOID)
+ result = range_me_consistent_int_range(typcache, strategy, key,
+ DatumGetRangeTypeP(query));
+ else
+ result = range_me_consistent_int_element(typcache, strategy,
+ key, query);
+ }
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * Multi-entry leaf consistent test with a range query.
+ *
+ * The key is one component range from the indexed multirange M. We must
+ * avoid false negatives: if the operator holds for M and query Q, at least
+ * one component must return true here. False positives are filtered by
+ * recheck. See multirange_gist_me_consistent for which strategies are
+ * recheck-free.
+ */
+static bool
+range_me_consistent_leaf_range(TypeCacheEntry *typcache,
+ StrategyNumber strategy,
+ const RangeType *key,
+ const RangeType *query)
+{
+ /* Empty key is the sentinel for an empty multirange */
+ if (RangeIsEmpty(key))
+ {
+ if (strategy == RANGESTRAT_CONTAINED_BY)
+ return true;
+ if (strategy == RANGESTRAT_CONTAINS)
+ return RangeIsEmpty(query);
+ return false;
+ }
+
+ /* Empty range is contained by everything, has no other relationships */
+ if (RangeIsEmpty(query))
+ {
+ if (strategy == RANGESTRAT_CONTAINS)
+ return true;
+ return false;
+ }
+
+ switch (strategy)
+ {
+ /*
+ * Bound operators: if M satisfies the bound, every component
+ * does too, so the exact per-component check has no false
+ * negatives. Still needs recheck since other components may
+ * violate the bound.
+ */
+ case RANGESTRAT_BEFORE:
+ return range_before_internal(typcache, key, query);
+ case RANGESTRAT_OVERLEFT:
+ return range_overleft_internal(typcache, key, query);
+ case RANGESTRAT_OVERRIGHT:
+ return range_overright_internal(typcache, key, query);
+ case RANGESTRAT_AFTER:
+ return range_after_internal(typcache, key, query);
+
+ /* Exact and recheck-free: if component overlaps Q, so does M */
+ case RANGESTRAT_OVERLAPS:
+ return range_overlaps_internal(typcache, key, query);
+
+ /* Exact check, but another component might overlap Q */
+ case RANGESTRAT_ADJACENT:
+ return range_adjacent_internal(typcache, key, query);
+
+ /*
+ * Use overlaps as a necessary condition. For @>: Q is
+ * contiguous and must lie within some component, so that
+ * component overlaps Q. For <@: every component is a subset
+ * of Q, so every component overlaps Q.
+ */
+ case RANGESTRAT_CONTAINS:
+ case RANGESTRAT_CONTAINED_BY:
+ case RANGESTRAT_EQ:
+ return range_overlaps_internal(typcache, key, query);
+
+ default:
+ elog(ERROR, "unrecognized range strategy: %d", strategy);
+ return false; /* keep compiler quiet */
+ }
+}
+
+/*
+ * Multi-entry leaf consistent test with a multirange query.
+ *
+ * Same framework as range_me_consistent_leaf_range: no false negatives
+ * allowed, false positives filtered by recheck.
+ */
+static bool
+range_me_consistent_leaf_multirange(TypeCacheEntry *typcache,
+ StrategyNumber strategy,
+ const RangeType *key,
+ const MultirangeType *query)
+{
+ /* Empty key is the sentinel for an empty multirange */
+ if (RangeIsEmpty(key))
+ {
+ if (strategy == RANGESTRAT_CONTAINED_BY)
+ return true;
+ if (strategy == RANGESTRAT_CONTAINS || strategy == RANGESTRAT_EQ)
+ return MultirangeIsEmpty(query);
+ return false;
+ }
+
+ /* Empty multirange is contained by everything, has no other relationships */
+ if (MultirangeIsEmpty(query))
+ {
+ if (strategy == RANGESTRAT_CONTAINS)
+ return true;
+ return false;
+ }
+
+ switch (strategy)
+ {
+ /* Bound operators: same reasoning as the range query case */
+ case RANGESTRAT_BEFORE:
+ return range_before_multirange_internal(typcache, key, query);
+ case RANGESTRAT_OVERLEFT:
+ return range_overleft_multirange_internal(typcache, key, query);
+ case RANGESTRAT_OVERRIGHT:
+ return range_overright_multirange_internal(typcache, key, query);
+ case RANGESTRAT_AFTER:
+ return range_after_multirange_internal(typcache, key, query);
+
+ /* Exact check, but another component might overlap Q */
+ case RANGESTRAT_ADJACENT:
+ return range_adjacent_multirange_internal(typcache, key, query);
+
+ /* Overlaps is recheck-free; the rest use it as necessary condition */
+ case RANGESTRAT_OVERLAPS:
+ case RANGESTRAT_CONTAINS:
+ case RANGESTRAT_CONTAINED_BY:
+ case RANGESTRAT_EQ:
+ return range_overlaps_multirange_internal(typcache, key, query);
+
+ default:
+ elog(ERROR, "unrecognized range strategy: %d", strategy);
+ return false; /* keep compiler quiet */
+ }
+}
+
+/*
+ * Multi-entry leaf consistent test with an element query.
+ *
+ * Recheck-free: if component Ri contains elem, so does M.
+ */
+static bool
+range_me_consistent_leaf_element(TypeCacheEntry *typcache,
+ StrategyNumber strategy,
+ const RangeType *key,
+ Datum query)
+{
+ switch (strategy)
+ {
+ case RANGESTRAT_CONTAINS_ELEM:
+ return range_contains_elem_internal(typcache, key, query);
+ default:
+ elog(ERROR, "unrecognized range strategy: %d", strategy);
+ return false; /* keep compiler quiet */
+ }
+}
+
+/*
+ * Multi-entry internal consistent test with a range query.
+ *
+ * Like range_gist_consistent_int_range but relaxes CONTAINS and EQ to use
+ * overlaps: a matching multirange's components may be spread across multiple
+ * subtrees, so the union key need not fully contain the query.
+ */
+static bool
+range_me_consistent_int_range(TypeCacheEntry *typcache,
+ StrategyNumber strategy,
+ const RangeType *key,
+ const RangeType *query)
+{
+ switch (strategy)
+ {
+ case RANGESTRAT_BEFORE:
+ if (RangeIsEmpty(key) || RangeIsEmpty(query))
+ return false;
+ return (!range_overright_internal(typcache, key, query));
+ case RANGESTRAT_OVERLEFT:
+ if (RangeIsEmpty(key) || RangeIsEmpty(query))
+ return false;
+ return (!range_after_internal(typcache, key, query));
+ case RANGESTRAT_OVERLAPS:
+ return range_overlaps_internal(typcache, key, query);
+ case RANGESTRAT_OVERRIGHT:
+ if (RangeIsEmpty(key) || RangeIsEmpty(query))
+ return false;
+ return (!range_before_internal(typcache, key, query));
+ case RANGESTRAT_AFTER:
+ if (RangeIsEmpty(key) || RangeIsEmpty(query))
+ return false;
+ return (!range_overleft_internal(typcache, key, query));
+ case RANGESTRAT_ADJACENT:
+ if (RangeIsEmpty(key) || RangeIsEmpty(query))
+ return false;
+ if (range_adjacent_internal(typcache, key, query))
+ return true;
+ return range_overlaps_internal(typcache, key, query);
+
+ /*
+ * Relaxed: overlaps instead of contains, because a matching
+ * multirange's components may be spread across subtrees.
+ * Empty query is contained by everything, so always descend.
+ */
+ case RANGESTRAT_CONTAINS:
+ if (RangeIsEmpty(query))
+ return true;
+ return range_overlaps_internal(typcache, key, query);
+ case RANGESTRAT_CONTAINED_BY:
+ if (RangeIsOrContainsEmpty(key))
+ return true;
+ return range_overlaps_internal(typcache, key, query);
+ case RANGESTRAT_EQ:
+ if (RangeIsEmpty(query))
+ return RangeIsOrContainsEmpty(key);
+ return range_overlaps_internal(typcache, key, query);
+ default:
+ elog(ERROR, "unrecognized range strategy: %d", strategy);
+ return false; /* keep compiler quiet */
+ }
+}
+
+/*
+ * Multi-entry internal consistent test with a multirange query.
+ *
+ * Like range_gist_consistent_int_multirange but relaxes CONTAINS and EQ.
+ */
+static bool
+range_me_consistent_int_multirange(TypeCacheEntry *typcache,
+ StrategyNumber strategy,
+ const RangeType *key,
+ const MultirangeType *query)
+{
+ switch (strategy)
+ {
+ case RANGESTRAT_BEFORE:
+ if (RangeIsEmpty(key) || MultirangeIsEmpty(query))
+ return false;
+ return (!range_overright_multirange_internal(typcache, key, query));
+ case RANGESTRAT_OVERLEFT:
+ if (RangeIsEmpty(key) || MultirangeIsEmpty(query))
+ return false;
+ return (!range_after_multirange_internal(typcache, key, query));
+ case RANGESTRAT_OVERLAPS:
+ return range_overlaps_multirange_internal(typcache, key, query);
+ case RANGESTRAT_OVERRIGHT:
+ if (RangeIsEmpty(key) || MultirangeIsEmpty(query))
+ return false;
+ return (!range_before_multirange_internal(typcache, key, query));
+ case RANGESTRAT_AFTER:
+ if (RangeIsEmpty(key) || MultirangeIsEmpty(query))
+ return false;
+ return (!range_overleft_multirange_internal(typcache, key, query));
+ case RANGESTRAT_ADJACENT:
+ if (RangeIsEmpty(key) || MultirangeIsEmpty(query))
+ return false;
+ if (range_adjacent_multirange_internal(typcache, key, query))
+ return true;
+ return range_overlaps_multirange_internal(typcache, key, query);
+
+ /*
+ * Relaxed: overlaps instead of contains, because a matching
+ * multirange's components may be spread across subtrees.
+ * Empty query is contained by everything, so always descend.
+ */
+ case RANGESTRAT_CONTAINS:
+ if (MultirangeIsEmpty(query))
+ return true;
+ return range_overlaps_multirange_internal(typcache, key, query);
+ case RANGESTRAT_CONTAINED_BY:
+ if (RangeIsOrContainsEmpty(key))
+ return true;
+ return range_overlaps_multirange_internal(typcache, key, query);
+ case RANGESTRAT_EQ:
+ if (MultirangeIsEmpty(query))
+ return RangeIsOrContainsEmpty(key);
+ return range_overlaps_multirange_internal(typcache, key, query);
+ default:
+ elog(ERROR, "unrecognized range strategy: %d", strategy);
+ return false; /* keep compiler quiet */
+ }
+}
+
+/*
+ * Multi-entry internal consistent test with an element query.
+ *
+ * Same as range_gist_consistent_int_element -- only CONTAINS_ELEM is used
+ * and doesn't need relaxation.
+ */
+static bool
+range_me_consistent_int_element(TypeCacheEntry *typcache,
+ StrategyNumber strategy,
+ const RangeType *key,
+ Datum query)
+{
+ switch (strategy)
+ {
+ case RANGESTRAT_CONTAINS_ELEM:
+ return range_contains_elem_internal(typcache, key, query);
+ default:
+ elog(ERROR, "unrecognized range strategy: %d", strategy);
+ return false; /* keep compiler quiet */
+ }
+}
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 8d5a0004a47..3cc3ba61eeb 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1477,6 +1477,62 @@
amoprighttype => 'anymultirange', amopstrategy => '18',
amopopr => '=(anymultirange,anymultirange)', amopmethod => 'gist' },
+# GiST multirange_me_ops
+{ amopfamily => 'gist/multirange_me_ops', amoplefttype => 'anymultirange',
+ amoprighttype => 'anymultirange', amopstrategy => '1',
+ amopopr => '<<(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_me_ops', amoplefttype => 'anymultirange',
+ amoprighttype => 'anyrange', amopstrategy => '1',
+ amopopr => '<<(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_me_ops', amoplefttype => 'anymultirange',
+ amoprighttype => 'anymultirange', amopstrategy => '2',
+ amopopr => '&<(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_me_ops', amoplefttype => 'anymultirange',
+ amoprighttype => 'anyrange', amopstrategy => '2',
+ amopopr => '&<(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_me_ops', amoplefttype => 'anymultirange',
+ amoprighttype => 'anymultirange', amopstrategy => '3',
+ amopopr => '&&(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_me_ops', amoplefttype => 'anymultirange',
+ amoprighttype => 'anyrange', amopstrategy => '3',
+ amopopr => '&&(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_me_ops', amoplefttype => 'anymultirange',
+ amoprighttype => 'anymultirange', amopstrategy => '4',
+ amopopr => '&>(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_me_ops', amoplefttype => 'anymultirange',
+ amoprighttype => 'anyrange', amopstrategy => '4',
+ amopopr => '&>(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_me_ops', amoplefttype => 'anymultirange',
+ amoprighttype => 'anymultirange', amopstrategy => '5',
+ amopopr => '>>(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_me_ops', amoplefttype => 'anymultirange',
+ amoprighttype => 'anyrange', amopstrategy => '5',
+ amopopr => '>>(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_me_ops', amoplefttype => 'anymultirange',
+ amoprighttype => 'anymultirange', amopstrategy => '6',
+ amopopr => '-|-(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_me_ops', amoplefttype => 'anymultirange',
+ amoprighttype => 'anyrange', amopstrategy => '6',
+ amopopr => '-|-(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_me_ops', amoplefttype => 'anymultirange',
+ amoprighttype => 'anymultirange', amopstrategy => '7',
+ amopopr => '@>(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_me_ops', amoplefttype => 'anymultirange',
+ amoprighttype => 'anyrange', amopstrategy => '7',
+ amopopr => '@>(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_me_ops', amoplefttype => 'anymultirange',
+ amoprighttype => 'anymultirange', amopstrategy => '8',
+ amopopr => '<@(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_me_ops', amoplefttype => 'anymultirange',
+ amoprighttype => 'anyrange', amopstrategy => '8',
+ amopopr => '<@(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_me_ops', amoplefttype => 'anymultirange',
+ amoprighttype => 'anyelement', amopstrategy => '16',
+ amopopr => '@>(anymultirange,anyelement)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_me_ops', amoplefttype => 'anymultirange',
+ amoprighttype => 'anymultirange', amopstrategy => '18',
+ amopopr => '=(anymultirange,anymultirange)', amopmethod => 'gist' },
+
# btree multirange_ops
{ amopfamily => 'btree/multirange_ops', amoplefttype => 'anymultirange',
amoprighttype => 'anymultirange', amopstrategy => '1',
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 4a1efdbc899..0bf9ea1145e 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -689,6 +689,27 @@
{ amprocfamily => 'gist/multirange_ops', amproclefttype => 'any',
amprocrighttype => 'any', amprocnum => '12',
amproc => 'gist_translate_cmptype_common' },
+{ amprocfamily => 'gist/multirange_me_ops', amproclefttype => 'anymultirange',
+ amprocrighttype => 'anymultirange', amprocnum => '1',
+ amproc => 'multirange_gist_me_consistent' },
+{ amprocfamily => 'gist/multirange_me_ops', amproclefttype => 'anymultirange',
+ amprocrighttype => 'anymultirange', amprocnum => '2',
+ amproc => 'range_gist_union' },
+{ amprocfamily => 'gist/multirange_me_ops', amproclefttype => 'anymultirange',
+ amprocrighttype => 'anymultirange', amprocnum => '5',
+ amproc => 'range_gist_penalty' },
+{ amprocfamily => 'gist/multirange_me_ops', amproclefttype => 'anymultirange',
+ amprocrighttype => 'anymultirange', amprocnum => '6',
+ amproc => 'range_gist_picksplit' },
+{ amprocfamily => 'gist/multirange_me_ops', amproclefttype => 'anymultirange',
+ amprocrighttype => 'anymultirange', amprocnum => '7',
+ amproc => 'range_gist_same' },
+{ amprocfamily => 'gist/multirange_me_ops', amproclefttype => 'any',
+ amprocrighttype => 'any', amprocnum => '12',
+ amproc => 'gist_translate_cmptype_common' },
+{ amprocfamily => 'gist/multirange_me_ops', amproclefttype => 'anymultirange',
+ amprocrighttype => 'anymultirange', amprocnum => '13',
+ amproc => 'multirange_gist_extractvalue' },
# gin
{ amprocfamily => 'gin/array_ops', amproclefttype => 'anyarray',
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index df170b80840..07920290013 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -246,6 +246,9 @@
{ opcmethod => 'gist', opcname => 'multirange_ops',
opcfamily => 'gist/multirange_ops', opcintype => 'anymultirange',
opckeytype => 'anyrange' },
+{ opcmethod => 'gist', opcname => 'multirange_me_ops',
+ opcfamily => 'gist/multirange_me_ops', opcintype => 'anymultirange',
+ opckeytype => 'anyrange', opcdefault => 'f' },
{ opcmethod => 'spgist', opcname => 'box_ops', opcfamily => 'spgist/box_ops',
opcintype => 'box' },
{ opcmethod => 'spgist', opcname => 'quad_point_ops',
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 7a027c4810e..93a80175352 100644
--- a/src/include/catalog/pg_opfamily.dat
+++ b/src/include/catalog/pg_opfamily.dat
@@ -308,5 +308,7 @@
opfmethod => 'hash', opfname => 'multirange_ops' },
{ oid => '6158',
opfmethod => 'gist', opfname => 'multirange_ops' },
+{ oid => '9319',
+ opfmethod => 'gist', opfname => 'multirange_me_ops' },
]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 99fa9a6ede2..699d8416d47 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11164,6 +11164,14 @@
{ oid => '6156', descr => 'GiST support',
proname => 'multirange_gist_compress', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'multirange_gist_compress' },
+{ oid => '9317', descr => 'GiST support',
+ proname => 'multirange_gist_me_consistent', prorettype => 'bool',
+ proargtypes => 'internal anymultirange int2 oid internal',
+ prosrc => 'multirange_gist_me_consistent' },
+{ oid => '9318', descr => 'GiST support',
+ proname => 'multirange_gist_extractvalue', prorettype => 'internal',
+ proargtypes => 'internal internal internal',
+ prosrc => 'multirange_gist_extractvalue' },
{ oid => '3902', descr => 'hash a range',
proname => 'hash_range', prorettype => 'int4', proargtypes => 'anyrange',
prosrc => 'hash_range' },
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
index f5e7df8df43..4134f1358ed 100644
--- a/src/test/regress/expected/multirangetypes.out
+++ b/src/test/regress/expected/multirangetypes.out
@@ -2874,6 +2874,678 @@ select count(*) from test_multirange_gist where mr -|- int4multirange(int4range(
drop table test_multirange_gist;
--
+-- Multi-entry GiST index (multirange_me_ops)
+-- Decomposes multiranges into component ranges for indexing.
+--
+create table test_multirange_me_gist(mr int4multirange);
+insert into test_multirange_me_gist select int4multirange(int4range(g, g+10),int4range(g+20, g+30),int4range(g+40, g+50)) from generate_series(1,2000) g;
+insert into test_multirange_me_gist select '{}'::int4multirange from generate_series(1,500) g;
+insert into test_multirange_me_gist select int4multirange(int4range(g, g+10000)) from generate_series(1,1000) g;
+insert into test_multirange_me_gist select int4multirange(int4range(NULL, g*10, '(]'), int4range(g*10, g*20, '(]')) from generate_series(1,100) g;
+insert into test_multirange_me_gist select int4multirange(int4range(g*10, g*20, '(]'), int4range(g*20, NULL, '(]')) from generate_series(1,100) g;
+create index test_multirange_me_gist_idx on test_multirange_me_gist using gist (mr multirange_me_ops);
+-- first, verify non-indexed results
+SET enable_seqscan = t;
+SET enable_indexscan = f;
+SET enable_bitmapscan = f;
+select count(*) from test_multirange_me_gist where mr = '{}'::int4multirange;
+ count
+-------
+ 500
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr @> 'empty'::int4range;
+ count
+-------
+ 3700
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr && 'empty'::int4range;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr <@ 'empty'::int4range;
+ count
+-------
+ 500
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr << 'empty'::int4range;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr >> 'empty'::int4range;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &< 'empty'::int4range;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &> 'empty'::int4range;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr -|- 'empty'::int4range;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr @> '{}'::int4multirange;
+ count
+-------
+ 3700
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr && '{}'::int4multirange;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr <@ '{}'::int4multirange;
+ count
+-------
+ 500
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr << '{}'::int4multirange;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr >> '{}'::int4multirange;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &< '{}'::int4multirange;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &> '{}'::int4multirange;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr -|- '{}'::int4multirange;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr = int4multirange(int4range(10,20), int4range(30,40), int4range(50,60));
+ count
+-------
+ 1
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr @> 10;
+ count
+-------
+ 120
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr @> int4range(10,20);
+ count
+-------
+ 111
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr && int4range(10,20);
+ count
+-------
+ 139
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr <@ int4range(10,50);
+ count
+-------
+ 500
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr << int4range(100,500);
+ count
+-------
+ 54
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr >> int4range(100,500);
+ count
+-------
+ 2053
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &< int4range(100,500);
+ count
+-------
+ 474
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &> int4range(100,500);
+ count
+-------
+ 2893
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr -|- int4range(100,500);
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr @> int4multirange(int4range(10,20), int4range(30,40));
+ count
+-------
+ 110
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr && '{(10,20),(30,40),(50,60)}'::int4multirange;
+ count
+-------
+ 218
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+ count
+-------
+ 500
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr << int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 54
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr >> int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 2053
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &< int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 474
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &> int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 2893
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr -|- int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 3
+(1 row)
+
+-- now check same queries using index
+SET enable_seqscan = f;
+SET enable_indexscan = t;
+SET enable_bitmapscan = f;
+select count(*) from test_multirange_me_gist where mr = '{}'::int4multirange;
+ count
+-------
+ 500
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr @> 'empty'::int4range;
+ count
+-------
+ 3700
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr && 'empty'::int4range;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr <@ 'empty'::int4range;
+ count
+-------
+ 500
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr << 'empty'::int4range;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr >> 'empty'::int4range;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &< 'empty'::int4range;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &> 'empty'::int4range;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr -|- 'empty'::int4range;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr @> '{}'::int4multirange;
+ count
+-------
+ 3700
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr && '{}'::int4multirange;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr <@ '{}'::int4multirange;
+ count
+-------
+ 500
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr << '{}'::int4multirange;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr >> '{}'::int4multirange;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &< '{}'::int4multirange;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &> '{}'::int4multirange;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr -|- '{}'::int4multirange;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr = int4multirange(int4range(10,20), int4range(30,40), int4range(50,60));
+ count
+-------
+ 1
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr @> 10;
+ count
+-------
+ 120
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr @> int4range(10,20);
+ count
+-------
+ 111
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr && int4range(10,20);
+ count
+-------
+ 139
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr <@ int4range(10,50);
+ count
+-------
+ 500
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr << int4range(100,500);
+ count
+-------
+ 54
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr >> int4range(100,500);
+ count
+-------
+ 2053
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &< int4range(100,500);
+ count
+-------
+ 474
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &> int4range(100,500);
+ count
+-------
+ 2893
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr -|- int4range(100,500);
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr @> int4multirange(int4range(10,20), int4range(30,40));
+ count
+-------
+ 110
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr && '{(10,20),(30,40),(50,60)}'::int4multirange;
+ count
+-------
+ 218
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+ count
+-------
+ 500
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr << int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 54
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr >> int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 2053
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &< int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 474
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &> int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 2893
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr -|- int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 3
+(1 row)
+
+-- also check bitmap scan
+SET enable_seqscan = f;
+SET enable_indexscan = f;
+SET enable_bitmapscan = t;
+select count(*) from test_multirange_me_gist where mr = '{}'::int4multirange;
+ count
+-------
+ 500
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr @> 'empty'::int4range;
+ count
+-------
+ 3700
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr && 'empty'::int4range;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr <@ 'empty'::int4range;
+ count
+-------
+ 500
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr << 'empty'::int4range;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr >> 'empty'::int4range;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &< 'empty'::int4range;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &> 'empty'::int4range;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr -|- 'empty'::int4range;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr @> '{}'::int4multirange;
+ count
+-------
+ 3700
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr && '{}'::int4multirange;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr <@ '{}'::int4multirange;
+ count
+-------
+ 500
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr << '{}'::int4multirange;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr >> '{}'::int4multirange;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &< '{}'::int4multirange;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &> '{}'::int4multirange;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr -|- '{}'::int4multirange;
+ count
+-------
+ 0
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr = int4multirange(int4range(10,20), int4range(30,40), int4range(50,60));
+ count
+-------
+ 1
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr @> 10;
+ count
+-------
+ 120
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr @> int4range(10,20);
+ count
+-------
+ 111
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr && int4range(10,20);
+ count
+-------
+ 139
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr <@ int4range(10,50);
+ count
+-------
+ 500
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr << int4range(100,500);
+ count
+-------
+ 54
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr >> int4range(100,500);
+ count
+-------
+ 2053
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &< int4range(100,500);
+ count
+-------
+ 474
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &> int4range(100,500);
+ count
+-------
+ 2893
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr -|- int4range(100,500);
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr @> int4multirange(int4range(10,20), int4range(30,40));
+ count
+-------
+ 110
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr && '{(10,20),(30,40),(50,60)}'::int4multirange;
+ count
+-------
+ 218
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+ count
+-------
+ 500
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr << int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 54
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr >> int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 2053
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &< int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 474
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr &> int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 2893
+(1 row)
+
+select count(*) from test_multirange_me_gist where mr -|- int4multirange(int4range(100,200), int4range(400,500));
+ count
+-------
+ 3
+(1 row)
+
+-- test that multi-column indexes with extractValue are disallowed
+create table test_me_multicol(mr int4multirange, r int4range);
+create index on test_me_multicol using gist (mr multirange_me_ops, r);
+ERROR: multi-entry GiST indexes do not support multiple key columns
+-- test NULL handling
+create table test_me_nulls(mr int4multirange);
+insert into test_me_nulls values (NULL), ('{}'::int4multirange), ('{[1,3]}');
+create index on test_me_nulls using gist (mr multirange_me_ops);
+SET enable_seqscan = f;
+SET enable_indexscan = t;
+select * from test_me_nulls where mr @> 1;
+ mr
+---------
+ {[1,4)}
+(1 row)
+
+drop table test_me_nulls;
+drop table test_me_multicol;
+drop table test_multirange_me_gist;
+--
-- range_agg function
--
create table reservations ( room_id integer not null, booked_during daterange );
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
index 112334b03eb..902d719d75c 100644
--- a/src/test/regress/sql/multirangetypes.sql
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -558,6 +558,160 @@ select count(*) from test_multirange_gist where mr -|- int4multirange(int4range(
drop table test_multirange_gist;
+--
+-- Multi-entry GiST index (multirange_me_ops)
+-- Decomposes multiranges into component ranges for indexing.
+--
+create table test_multirange_me_gist(mr int4multirange);
+insert into test_multirange_me_gist select int4multirange(int4range(g, g+10),int4range(g+20, g+30),int4range(g+40, g+50)) from generate_series(1,2000) g;
+insert into test_multirange_me_gist select '{}'::int4multirange from generate_series(1,500) g;
+insert into test_multirange_me_gist select int4multirange(int4range(g, g+10000)) from generate_series(1,1000) g;
+insert into test_multirange_me_gist select int4multirange(int4range(NULL, g*10, '(]'), int4range(g*10, g*20, '(]')) from generate_series(1,100) g;
+insert into test_multirange_me_gist select int4multirange(int4range(g*10, g*20, '(]'), int4range(g*20, NULL, '(]')) from generate_series(1,100) g;
+create index test_multirange_me_gist_idx on test_multirange_me_gist using gist (mr multirange_me_ops);
+
+-- first, verify non-indexed results
+SET enable_seqscan = t;
+SET enable_indexscan = f;
+SET enable_bitmapscan = f;
+
+select count(*) from test_multirange_me_gist where mr = '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr @> 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr && 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr <@ 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr << 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr >> 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr &< 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr &> 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr -|- 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr @> '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr && '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr <@ '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr << '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr >> '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr &< '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr &> '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr -|- '{}'::int4multirange;
+
+select count(*) from test_multirange_me_gist where mr = int4multirange(int4range(10,20), int4range(30,40), int4range(50,60));
+select count(*) from test_multirange_me_gist where mr @> 10;
+select count(*) from test_multirange_me_gist where mr @> int4range(10,20);
+select count(*) from test_multirange_me_gist where mr && int4range(10,20);
+select count(*) from test_multirange_me_gist where mr <@ int4range(10,50);
+select count(*) from test_multirange_me_gist where mr << int4range(100,500);
+select count(*) from test_multirange_me_gist where mr >> int4range(100,500);
+select count(*) from test_multirange_me_gist where mr &< int4range(100,500);
+select count(*) from test_multirange_me_gist where mr &> int4range(100,500);
+select count(*) from test_multirange_me_gist where mr -|- int4range(100,500);
+select count(*) from test_multirange_me_gist where mr @> int4multirange(int4range(10,20), int4range(30,40));
+select count(*) from test_multirange_me_gist where mr && '{(10,20),(30,40),(50,60)}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr << int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_me_gist where mr >> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_me_gist where mr &< int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_me_gist where mr &> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_me_gist where mr -|- int4multirange(int4range(100,200), int4range(400,500));
+
+-- now check same queries using index
+SET enable_seqscan = f;
+SET enable_indexscan = t;
+SET enable_bitmapscan = f;
+
+select count(*) from test_multirange_me_gist where mr = '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr @> 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr && 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr <@ 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr << 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr >> 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr &< 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr &> 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr -|- 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr @> '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr && '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr <@ '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr << '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr >> '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr &< '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr &> '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr -|- '{}'::int4multirange;
+
+select count(*) from test_multirange_me_gist where mr = int4multirange(int4range(10,20), int4range(30,40), int4range(50,60));
+select count(*) from test_multirange_me_gist where mr @> 10;
+select count(*) from test_multirange_me_gist where mr @> int4range(10,20);
+select count(*) from test_multirange_me_gist where mr && int4range(10,20);
+select count(*) from test_multirange_me_gist where mr <@ int4range(10,50);
+select count(*) from test_multirange_me_gist where mr << int4range(100,500);
+select count(*) from test_multirange_me_gist where mr >> int4range(100,500);
+select count(*) from test_multirange_me_gist where mr &< int4range(100,500);
+select count(*) from test_multirange_me_gist where mr &> int4range(100,500);
+select count(*) from test_multirange_me_gist where mr -|- int4range(100,500);
+select count(*) from test_multirange_me_gist where mr @> int4multirange(int4range(10,20), int4range(30,40));
+select count(*) from test_multirange_me_gist where mr && '{(10,20),(30,40),(50,60)}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr << int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_me_gist where mr >> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_me_gist where mr &< int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_me_gist where mr &> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_me_gist where mr -|- int4multirange(int4range(100,200), int4range(400,500));
+
+-- also check bitmap scan
+SET enable_seqscan = f;
+SET enable_indexscan = f;
+SET enable_bitmapscan = t;
+
+select count(*) from test_multirange_me_gist where mr = '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr @> 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr && 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr <@ 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr << 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr >> 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr &< 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr &> 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr -|- 'empty'::int4range;
+select count(*) from test_multirange_me_gist where mr @> '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr && '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr <@ '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr << '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr >> '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr &< '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr &> '{}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr -|- '{}'::int4multirange;
+
+select count(*) from test_multirange_me_gist where mr = int4multirange(int4range(10,20), int4range(30,40), int4range(50,60));
+select count(*) from test_multirange_me_gist where mr @> 10;
+select count(*) from test_multirange_me_gist where mr @> int4range(10,20);
+select count(*) from test_multirange_me_gist where mr && int4range(10,20);
+select count(*) from test_multirange_me_gist where mr <@ int4range(10,50);
+select count(*) from test_multirange_me_gist where mr << int4range(100,500);
+select count(*) from test_multirange_me_gist where mr >> int4range(100,500);
+select count(*) from test_multirange_me_gist where mr &< int4range(100,500);
+select count(*) from test_multirange_me_gist where mr &> int4range(100,500);
+select count(*) from test_multirange_me_gist where mr -|- int4range(100,500);
+select count(*) from test_multirange_me_gist where mr @> int4multirange(int4range(10,20), int4range(30,40));
+select count(*) from test_multirange_me_gist where mr && '{(10,20),(30,40),(50,60)}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+select count(*) from test_multirange_me_gist where mr << int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_me_gist where mr >> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_me_gist where mr &< int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_me_gist where mr &> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_me_gist where mr -|- int4multirange(int4range(100,200), int4range(400,500));
+
+-- test that multi-column indexes with extractValue are disallowed
+create table test_me_multicol(mr int4multirange, r int4range);
+create index on test_me_multicol using gist (mr multirange_me_ops, r);
+
+-- test NULL handling
+create table test_me_nulls(mr int4multirange);
+insert into test_me_nulls values (NULL), ('{}'::int4multirange), ('{[1,3]}');
+create index on test_me_nulls using gist (mr multirange_me_ops);
+SET enable_seqscan = f;
+SET enable_indexscan = t;
+select * from test_me_nulls where mr @> 1;
+
+drop table test_me_nulls;
+drop table test_me_multicol;
+drop table test_multirange_me_gist;
+
--
-- range_agg function
--
--
2.50.1 (Apple Git-155)