From 914a5644a078144bb2475a51c413d5ff68020578 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Fri, 25 Dec 2020 17:55:38 +0300
Subject: [PATCH 4/5] Add support of multirange matching to the existing range
 GiST indexes

6df7a9698b has introduced a set of operators between ranges and multiranges.
Existing GiST indexes for ranges could easily support majority of them.
This commit adds support for new operators to the existing range GiST indexes.
New operators resides the same strategy numbers as existing ones.  Appropriate
check function is determined using the subtype.
---
 src/backend/utils/adt/rangetypes_gist.c  | 276 +++++++++++++++++------
 src/include/catalog/pg_amop.dat          |  24 ++
 src/test/regress/expected/rangetypes.out | 162 +++++++++++++
 src/test/regress/sql/rangetypes.sql      |  27 +++
 4 files changed, 426 insertions(+), 63 deletions(-)

diff --git a/src/backend/utils/adt/rangetypes_gist.c b/src/backend/utils/adt/rangetypes_gist.c
index 75069c3ac2c..f4bccf41b90 100644
--- a/src/backend/utils/adt/rangetypes_gist.c
+++ b/src/backend/utils/adt/rangetypes_gist.c
@@ -19,6 +19,7 @@
 #include "utils/datum.h"
 #include "utils/float.h"
 #include "utils/fmgrprotos.h"
+#include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
 
 /*
@@ -135,12 +136,30 @@ typedef struct
 
 static RangeType *range_super_union(TypeCacheEntry *typcache, RangeType *r1,
 									RangeType *r2);
-static bool range_gist_consistent_int(TypeCacheEntry *typcache,
-									  StrategyNumber strategy, const RangeType *key,
-									  Datum query);
-static bool range_gist_consistent_leaf(TypeCacheEntry *typcache,
-									   StrategyNumber strategy, const RangeType *key,
-									   Datum query);
+static bool range_gist_consistent_int_range(TypeCacheEntry *typcache,
+											StrategyNumber strategy,
+											const RangeType *key,
+											const RangeType *query);
+static bool range_gist_consistent_int_multirange(TypeCacheEntry *typcache,
+												 StrategyNumber strategy,
+												 const RangeType *key,
+												 const MultirangeType *query);
+static bool range_gist_consistent_int_element(TypeCacheEntry *typcache,
+											  StrategyNumber strategy,
+											  const RangeType *key,
+											  Datum query);
+static bool range_gist_consistent_leaf_range(TypeCacheEntry *typcache,
+											 StrategyNumber strategy,
+											 const RangeType *key,
+											 const RangeType *query);
+static bool range_gist_consistent_leaf_multirange(TypeCacheEntry *typcache,
+												  StrategyNumber strategy,
+												  const RangeType *key,
+												  const MultirangeType *query);
+static bool range_gist_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);
@@ -174,8 +193,8 @@ range_gist_consistent(PG_FUNCTION_ARGS)
 	GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
 	Datum		query = PG_GETARG_DATUM(1);
 	StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
-
-	/* Oid subtype = PG_GETARG_OID(3); */
+	bool		result;
+	Oid			subtype = PG_GETARG_OID(3);
 	bool	   *recheck = (bool *) PG_GETARG_POINTER(4);
 	RangeType  *key = DatumGetRangeTypeP(entry->key);
 	TypeCacheEntry *typcache;
@@ -185,12 +204,37 @@ range_gist_consistent(PG_FUNCTION_ARGS)
 
 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(key));
 
+	/*
+	 * Perform consistent checking using function corresponding to key type
+	 * (leaf or internal) and query subtype (range, multirange, or element).
+	 * Note that invalid subtype means that query type matches key type
+	 * (range).
+	 */
 	if (GIST_LEAF(entry))
-		PG_RETURN_BOOL(range_gist_consistent_leaf(typcache, strategy,
-												  key, query));
+	{
+		if (!OidIsValid(subtype) || subtype == ANYRANGEOID)
+			result = range_gist_consistent_leaf_range(typcache, strategy, key,
+													  DatumGetRangeTypeP(query));
+		else if (subtype == ANYMULTIRANGEOID)
+			result = range_gist_consistent_leaf_multirange(typcache, strategy, key,
+														   DatumGetMultirangeTypeP(query));
+		else
+			result = range_gist_consistent_leaf_element(typcache, strategy,
+														key, query);
+	}
 	else
-		PG_RETURN_BOOL(range_gist_consistent_int(typcache, strategy,
-												 key, query));
+	{
+		if (!OidIsValid(subtype) || subtype == ANYRANGEOID)
+			result = range_gist_consistent_int_range(typcache, strategy, key,
+													 DatumGetRangeTypeP(query));
+		else if (subtype == ANYMULTIRANGEOID)
+			result = range_gist_consistent_int_multirange(typcache, strategy, key,
+														  DatumGetMultirangeTypeP(query));
+		else
+			result = range_gist_consistent_int_element(typcache, strategy,
+													   key, query);
+	}
+	PG_RETURN_BOOL(result);
 }
 
 /* form union range */
@@ -759,48 +803,42 @@ range_super_union(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
 }
 
 /*
- * GiST consistent test on an index internal page
+ * GiST consistent test on an index internal page with range query
  */
 static bool
-range_gist_consistent_int(TypeCacheEntry *typcache, StrategyNumber strategy,
-						  const RangeType *key, Datum query)
+range_gist_consistent_int_range(TypeCacheEntry *typcache,
+								StrategyNumber strategy,
+								const RangeType *key,
+								const RangeType *query)
 {
 	switch (strategy)
 	{
 		case RANGESTRAT_BEFORE:
-			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeTypeP(query)))
+			if (RangeIsEmpty(key) || RangeIsEmpty(query))
 				return false;
-			return (!range_overright_internal(typcache, key,
-											  DatumGetRangeTypeP(query)));
+			return (!range_overright_internal(typcache, key, query));
 		case RANGESTRAT_OVERLEFT:
-			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeTypeP(query)))
+			if (RangeIsEmpty(key) || RangeIsEmpty(query))
 				return false;
-			return (!range_after_internal(typcache, key,
-										  DatumGetRangeTypeP(query)));
+			return (!range_after_internal(typcache, key, query));
 		case RANGESTRAT_OVERLAPS:
-			return range_overlaps_internal(typcache, key,
-										   DatumGetRangeTypeP(query));
+			return range_overlaps_internal(typcache, key, query);
 		case RANGESTRAT_OVERRIGHT:
-			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeTypeP(query)))
+			if (RangeIsEmpty(key) || RangeIsEmpty(query))
 				return false;
-			return (!range_before_internal(typcache, key,
-										   DatumGetRangeTypeP(query)));
+			return (!range_before_internal(typcache, key, query));
 		case RANGESTRAT_AFTER:
-			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeTypeP(query)))
+			if (RangeIsEmpty(key) || RangeIsEmpty(query))
 				return false;
-			return (!range_overleft_internal(typcache, key,
-											 DatumGetRangeTypeP(query)));
+			return (!range_overleft_internal(typcache, key, query));
 		case RANGESTRAT_ADJACENT:
-			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeTypeP(query)))
+			if (RangeIsEmpty(key) || RangeIsEmpty(query))
 				return false;
-			if (range_adjacent_internal(typcache, key,
-										DatumGetRangeTypeP(query)))
+			if (range_adjacent_internal(typcache, key, query))
 				return true;
-			return range_overlaps_internal(typcache, key,
-										   DatumGetRangeTypeP(query));
+			return range_overlaps_internal(typcache, key, query);
 		case RANGESTRAT_CONTAINS:
-			return range_contains_internal(typcache, key,
-										   DatumGetRangeTypeP(query));
+			return range_contains_internal(typcache, key, query);
 		case RANGESTRAT_CONTAINED_BY:
 
 			/*
@@ -810,20 +848,16 @@ range_gist_consistent_int(TypeCacheEntry *typcache, StrategyNumber strategy,
 			 */
 			if (RangeIsOrContainsEmpty(key))
 				return true;
-			return range_overlaps_internal(typcache, key,
-										   DatumGetRangeTypeP(query));
-		case RANGESTRAT_CONTAINS_ELEM:
-			return range_contains_elem_internal(typcache, key, query);
+			return range_overlaps_internal(typcache, key, query);
 		case RANGESTRAT_EQ:
 
 			/*
 			 * If query is empty, descend only if the key is or contains any
 			 * empty ranges.  Otherwise, descend if key contains query.
 			 */
-			if (RangeIsEmpty(DatumGetRangeTypeP(query)))
+			if (RangeIsEmpty(query))
 				return RangeIsOrContainsEmpty(key);
-			return range_contains_internal(typcache, key,
-										   DatumGetRangeTypeP(query));
+			return range_contains_internal(typcache, key, query);
 		default:
 			elog(ERROR, "unrecognized range strategy: %d", strategy);
 			return false;		/* keep compiler quiet */
@@ -831,42 +865,158 @@ range_gist_consistent_int(TypeCacheEntry *typcache, StrategyNumber strategy,
 }
 
 /*
- * GiST consistent test on an index leaf page
+ * GiST consistent test on an index internal page with multirange query
  */
 static bool
-range_gist_consistent_leaf(TypeCacheEntry *typcache, StrategyNumber strategy,
-						   const RangeType *key, Datum query)
+range_gist_consistent_int_multirange(TypeCacheEntry *typcache,
+									 StrategyNumber strategy,
+									 const RangeType *key,
+									 const MultirangeType *query)
 {
 	switch (strategy)
 	{
 		case RANGESTRAT_BEFORE:
-			return range_before_internal(typcache, key,
-										 DatumGetRangeTypeP(query));
+			if (RangeIsEmpty(key) || MultirangeIsEmpty(query))
+				return false;
+			return (!range_overright_multirange_internal(typcache, key, query));
 		case RANGESTRAT_OVERLEFT:
-			return range_overleft_internal(typcache, key,
-										   DatumGetRangeTypeP(query));
+			if (RangeIsEmpty(key) || MultirangeIsEmpty(query))
+				return false;
+			return (!range_after_multirange_internal(typcache, key, query));
 		case RANGESTRAT_OVERLAPS:
-			return range_overlaps_internal(typcache, key,
-										   DatumGetRangeTypeP(query));
+			return range_overlaps_multirange_internal(typcache, key, query);
 		case RANGESTRAT_OVERRIGHT:
-			return range_overright_internal(typcache, key,
-											DatumGetRangeTypeP(query));
+			if (RangeIsEmpty(key) || MultirangeIsEmpty(query))
+				return false;
+			return (!range_before_multirange_internal(typcache, key, query));
 		case RANGESTRAT_AFTER:
-			return range_after_internal(typcache, key,
-										DatumGetRangeTypeP(query));
+			if (RangeIsEmpty(key) || MultirangeIsEmpty(query))
+				return false;
+			return (!range_overleft_multirange_internal(typcache, key, query));
 		case RANGESTRAT_ADJACENT:
-			return range_adjacent_internal(typcache, key,
-										   DatumGetRangeTypeP(query));
+			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);
 		case RANGESTRAT_CONTAINS:
-			return range_contains_internal(typcache, key,
-										   DatumGetRangeTypeP(query));
+			return range_contains_multirange_internal(typcache, key, query);
 		case RANGESTRAT_CONTAINED_BY:
-			return range_contained_by_internal(typcache, key,
-											   DatumGetRangeTypeP(query));
+
+			/*
+			 * Empty ranges are contained by anything, so if key is or
+			 * contains any empty ranges, we must descend into it.  Otherwise,
+			 * descend only if key overlaps the query.
+			 */
+			if (RangeIsOrContainsEmpty(key))
+				return true;
+			return range_overlaps_multirange_internal(typcache, key, query);
+		default:
+			elog(ERROR, "unrecognized range strategy: %d", strategy);
+			return false;		/* keep compiler quiet */
+	}
+}
+
+/*
+ * GiST consistent test on an index internal page with element query
+ */
+static bool
+range_gist_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 */
+	}
+}
+
+/*
+ * GiST consistent test on an index leaf page with range query
+ */
+static bool
+range_gist_consistent_leaf_range(TypeCacheEntry *typcache,
+								 StrategyNumber strategy,
+								 const RangeType *key,
+								 const RangeType *query)
+{
+	switch (strategy)
+	{
+		case RANGESTRAT_BEFORE:
+			return range_before_internal(typcache, key, query);
+		case RANGESTRAT_OVERLEFT:
+			return range_overleft_internal(typcache, key, query);
+		case RANGESTRAT_OVERLAPS:
+			return range_overlaps_internal(typcache, key, query);
+		case RANGESTRAT_OVERRIGHT:
+			return range_overright_internal(typcache, key, query);
+		case RANGESTRAT_AFTER:
+			return range_after_internal(typcache, key, query);
+		case RANGESTRAT_ADJACENT:
+			return range_adjacent_internal(typcache, key, query);
+		case RANGESTRAT_CONTAINS:
+			return range_contains_internal(typcache, key, query);
+		case RANGESTRAT_CONTAINED_BY:
+			return range_contained_by_internal(typcache, key, query);
 		case RANGESTRAT_EQ:
-			return range_eq_internal(typcache, key, DatumGetRangeTypeP(query));
+			return range_eq_internal(typcache, key, query);
+		default:
+			elog(ERROR, "unrecognized range strategy: %d", strategy);
+			return false;		/* keep compiler quiet */
+	}
+}
+
+/*
+ * GiST consistent test on an index leaf page with multirange query
+ */
+static bool
+range_gist_consistent_leaf_multirange(TypeCacheEntry *typcache,
+									  StrategyNumber strategy,
+									  const RangeType *key,
+									  const MultirangeType *query)
+{
+	switch (strategy)
+	{
+		case RANGESTRAT_BEFORE:
+			return range_before_multirange_internal(typcache, key, query);
+		case RANGESTRAT_OVERLEFT:
+			return range_overleft_multirange_internal(typcache, key, query);
+		case RANGESTRAT_OVERLAPS:
+			return range_overlaps_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);
+		case RANGESTRAT_ADJACENT:
+			return range_adjacent_multirange_internal(typcache, key, query);
+		case RANGESTRAT_CONTAINS:
+			return range_contains_multirange_internal(typcache, key, query);
+		case RANGESTRAT_CONTAINED_BY:
+			return multirange_contains_range_internal(typcache, query, key);
+		default:
+			elog(ERROR, "unrecognized range strategy: %d", strategy);
+			return false;		/* keep compiler quiet */
+	}
+}
+
+/*
+ * GiST consistent test on an index leaf page with element query
+ */
+static bool
+range_gist_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 */
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 78d7d2c5232..e055da67eb7 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1346,27 +1346,51 @@
 { amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
   amoprighttype => 'anyrange', amopstrategy => '1',
   amopopr => '<<(anyrange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
+  amoprighttype => 'anymultirange', amopstrategy => '1',
+  amopopr => '<<(anyrange,anymultirange)', amopmethod => 'gist' },
 { amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
   amoprighttype => 'anyrange', amopstrategy => '2',
   amopopr => '&<(anyrange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
+  amoprighttype => 'anymultirange', amopstrategy => '2',
+  amopopr => '&<(anyrange,anymultirange)', amopmethod => 'gist' },
 { amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
   amoprighttype => 'anyrange', amopstrategy => '3',
   amopopr => '&&(anyrange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
+  amoprighttype => 'anymultirange', amopstrategy => '3',
+  amopopr => '&&(anyrange,anymultirange)', amopmethod => 'gist' },
 { amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
   amoprighttype => 'anyrange', amopstrategy => '4',
   amopopr => '&>(anyrange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
+  amoprighttype => 'anymultirange', amopstrategy => '4',
+  amopopr => '&>(anyrange,anymultirange)', amopmethod => 'gist' },
 { amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
   amoprighttype => 'anyrange', amopstrategy => '5',
   amopopr => '>>(anyrange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
+  amoprighttype => 'anymultirange', amopstrategy => '5',
+  amopopr => '>>(anyrange,anymultirange)', amopmethod => 'gist' },
 { amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
   amoprighttype => 'anyrange', amopstrategy => '6',
   amopopr => '-|-(anyrange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
+  amoprighttype => 'anymultirange', amopstrategy => '6',
+  amopopr => '-|-(anyrange,anymultirange)', amopmethod => 'gist' },
 { amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
   amoprighttype => 'anyrange', amopstrategy => '7',
   amopopr => '@>(anyrange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
+  amoprighttype => 'anymultirange', amopstrategy => '7',
+  amopopr => '@>(anyrange,anymultirange)', amopmethod => 'gist' },
 { amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
   amoprighttype => 'anyrange', amopstrategy => '8',
   amopopr => '<@(anyrange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
+  amoprighttype => 'anymultirange', amopstrategy => '8',
+  amopopr => '<@(anyrange,anymultirange)', amopmethod => 'gist' },
 { amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
   amoprighttype => 'anyelement', amopstrategy => '16',
   amopopr => '@>(anyrange,anyelement)', amopmethod => 'gist' },
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index 6a1bbadc91a..28dc995e599 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -862,6 +862,60 @@ select count(*) from test_range_gist where ir -|- int4range(100,500);
      5
 (1 row)
 
+select count(*) from test_range_gist where ir @> '{}'::int4multirange;
+ count 
+-------
+  6200
+(1 row)
+
+select count(*) from test_range_gist where ir @> int4multirange(int4range(10,20), int4range(30,40));
+ count 
+-------
+   107
+(1 row)
+
+select count(*) from test_range_gist where ir && '{(10,20),(30,40),(50,60)}'::int4multirange;
+ count 
+-------
+   271
+(1 row)
+
+select count(*) from test_range_gist where ir <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+ count 
+-------
+  1060
+(1 row)
+
+select count(*) from test_range_gist where ir << int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+   189
+(1 row)
+
+select count(*) from test_range_gist where ir >> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  3554
+(1 row)
+
+select count(*) from test_range_gist where ir &< int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  1029
+(1 row)
+
+select count(*) from test_range_gist where ir &> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  4794
+(1 row)
+
+select count(*) from test_range_gist where ir -|- int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+     5
+(1 row)
+
 -- now check same queries using index
 SET enable_seqscan    = f;
 SET enable_indexscan  = t;
@@ -932,6 +986,60 @@ select count(*) from test_range_gist where ir -|- int4range(100,500);
      5
 (1 row)
 
+select count(*) from test_range_gist where ir @> '{}'::int4multirange;
+ count 
+-------
+  6200
+(1 row)
+
+select count(*) from test_range_gist where ir @> int4multirange(int4range(10,20), int4range(30,40));
+ count 
+-------
+   107
+(1 row)
+
+select count(*) from test_range_gist where ir && '{(10,20),(30,40),(50,60)}'::int4multirange;
+ count 
+-------
+   271
+(1 row)
+
+select count(*) from test_range_gist where ir <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+ count 
+-------
+  1060
+(1 row)
+
+select count(*) from test_range_gist where ir << int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+   189
+(1 row)
+
+select count(*) from test_range_gist where ir >> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  3554
+(1 row)
+
+select count(*) from test_range_gist where ir &< int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  1029
+(1 row)
+
+select count(*) from test_range_gist where ir &> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  4794
+(1 row)
+
+select count(*) from test_range_gist where ir -|- int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+     5
+(1 row)
+
 -- now check same queries using a bulk-loaded index
 drop index test_range_gist_idx;
 create index test_range_gist_idx on test_range_gist using gist (ir);
@@ -1001,6 +1109,60 @@ select count(*) from test_range_gist where ir -|- int4range(100,500);
      5
 (1 row)
 
+select count(*) from test_range_gist where ir @> '{}'::int4multirange;
+ count 
+-------
+  6200
+(1 row)
+
+select count(*) from test_range_gist where ir @> int4multirange(int4range(10,20), int4range(30,40));
+ count 
+-------
+   107
+(1 row)
+
+select count(*) from test_range_gist where ir && '{(10,20),(30,40),(50,60)}'::int4multirange;
+ count 
+-------
+   271
+(1 row)
+
+select count(*) from test_range_gist where ir <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+ count 
+-------
+  1060
+(1 row)
+
+select count(*) from test_range_gist where ir << int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+   189
+(1 row)
+
+select count(*) from test_range_gist where ir >> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  3554
+(1 row)
+
+select count(*) from test_range_gist where ir &< int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  1029
+(1 row)
+
+select count(*) from test_range_gist where ir &> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  4794
+(1 row)
+
+select count(*) from test_range_gist where ir -|- int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+     5
+(1 row)
+
 -- test SP-GiST index that's been built incrementally
 create table test_range_spgist(ir int4range);
 create index test_range_spgist_idx on test_range_spgist using spgist (ir);
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index b69efede3ae..51eddabf60f 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -232,6 +232,15 @@ select count(*) from test_range_gist where ir >> int4range(100,500);
 select count(*) from test_range_gist where ir &< int4range(100,500);
 select count(*) from test_range_gist where ir &> int4range(100,500);
 select count(*) from test_range_gist where ir -|- int4range(100,500);
+select count(*) from test_range_gist where ir @> '{}'::int4multirange;
+select count(*) from test_range_gist where ir @> int4multirange(int4range(10,20), int4range(30,40));
+select count(*) from test_range_gist where ir && '{(10,20),(30,40),(50,60)}'::int4multirange;
+select count(*) from test_range_gist where ir <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+select count(*) from test_range_gist where ir << int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir >> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir &< int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir &> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir -|- int4multirange(int4range(100,200), int4range(400,500));
 
 -- now check same queries using index
 SET enable_seqscan    = f;
@@ -249,6 +258,15 @@ select count(*) from test_range_gist where ir >> int4range(100,500);
 select count(*) from test_range_gist where ir &< int4range(100,500);
 select count(*) from test_range_gist where ir &> int4range(100,500);
 select count(*) from test_range_gist where ir -|- int4range(100,500);
+select count(*) from test_range_gist where ir @> '{}'::int4multirange;
+select count(*) from test_range_gist where ir @> int4multirange(int4range(10,20), int4range(30,40));
+select count(*) from test_range_gist where ir && '{(10,20),(30,40),(50,60)}'::int4multirange;
+select count(*) from test_range_gist where ir <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+select count(*) from test_range_gist where ir << int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir >> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir &< int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir &> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir -|- int4multirange(int4range(100,200), int4range(400,500));
 
 -- now check same queries using a bulk-loaded index
 drop index test_range_gist_idx;
@@ -265,6 +283,15 @@ select count(*) from test_range_gist where ir >> int4range(100,500);
 select count(*) from test_range_gist where ir &< int4range(100,500);
 select count(*) from test_range_gist where ir &> int4range(100,500);
 select count(*) from test_range_gist where ir -|- int4range(100,500);
+select count(*) from test_range_gist where ir @> '{}'::int4multirange;
+select count(*) from test_range_gist where ir @> int4multirange(int4range(10,20), int4range(30,40));
+select count(*) from test_range_gist where ir && '{(10,20),(30,40),(50,60)}'::int4multirange;
+select count(*) from test_range_gist where ir <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+select count(*) from test_range_gist where ir << int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir >> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir &< int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir &> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir -|- int4multirange(int4range(100,200), int4range(400,500));
 
 -- test SP-GiST index that's been built incrementally
 create table test_range_spgist(ir int4range);
-- 
2.24.3 (Apple Git-128)

