From 8e25bb4227418ed76bb5ae384899669bd5802c4a Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sat, 26 Dec 2020 18:03:28 +0300
Subject: [PATCH 5/5] Add GiST indexes for multiranges

This commits adds a pretty trivial way for multirange indexing: approximate
multirange as union range with no gaps.  New multiranges opclass shares the
majority of functions with ranges opclass.

This is not an ideal way to index multirages, but something we can easily have.
---
 src/backend/utils/adt/multirangetypes.c       |  21 ++
 src/backend/utils/adt/rangetypes_gist.c       | 117 ++++++++
 src/include/catalog/pg_amop.dat               |  56 ++++
 src/include/catalog/pg_amproc.dat             |  18 ++
 src/include/catalog/pg_opclass.dat            |   2 +
 src/include/catalog/pg_opfamily.dat           |   2 +
 src/include/catalog/pg_proc.dat               |   8 +
 src/include/utils/multirangetypes.h           |   2 +
 src/test/regress/expected/multirangetypes.out | 257 ++++++++++++++++++
 src/test/regress/sql/multirangetypes.sql      |  63 +++++
 10 files changed, 546 insertions(+)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index a77299147e7..2d4cee92bcc 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -768,6 +768,27 @@ multirange_get_bounds(TypeCacheEntry *rangetyp,
 	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
  *
diff --git a/src/backend/utils/adt/rangetypes_gist.c b/src/backend/utils/adt/rangetypes_gist.c
index f4bccf41b90..435b242c8ab 100644
--- a/src/backend/utils/adt/rangetypes_gist.c
+++ b/src/backend/utils/adt/rangetypes_gist.c
@@ -237,6 +237,88 @@ range_gist_consistent(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(result);
 }
 
+/*
+ * GiST compress method for multiranges: multirange is approximated as union
+ * range with no gaps.
+ */
+Datum
+multirange_gist_compress(PG_FUNCTION_ARGS)
+{
+	GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
+
+	if (entry->leafkey)
+	{
+		MultirangeType *mr = DatumGetMultirangeTypeP(entry->key);
+		RangeType  *r;
+		TypeCacheEntry *typcache;
+		GISTENTRY  *retval = palloc(sizeof(GISTENTRY));
+
+		typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+		r = multirange_get_union_range(typcache->rngtype, mr);
+
+		gistentryinit(*retval, RangeTypePGetDatum(r),
+					  entry->rel, entry->page, entry->offset, false);
+
+		PG_RETURN_POINTER(retval);
+	}
+
+	PG_RETURN_POINTER(entry);
+}
+
+/* GiST query consistency check for multiranges */
+Datum
+multirange_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);
+	bool		result;
+	Oid			subtype = PG_GETARG_OID(3);
+	bool	   *recheck = (bool *) PG_GETARG_POINTER(4);
+	RangeType  *key = DatumGetRangeTypeP(entry->key);
+	TypeCacheEntry *typcache;
+
+	/*
+	 * All operators served by this function are inexact because multirange is
+	 * approximated by union range with no gaps.
+	 */
+	*recheck = true;
+
+	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
+	 * (multirange).
+	 */
+	if (GIST_LEAF(entry))
+	{
+		if (!OidIsValid(subtype) || subtype == ANYMULTIRANGEOID)
+			result = range_gist_consistent_leaf_multirange(typcache, strategy, key,
+														   DatumGetMultirangeTypeP(query));
+		else if (subtype == ANYRANGEOID)
+			result = range_gist_consistent_leaf_range(typcache, strategy, key,
+													  DatumGetRangeTypeP(query));
+		else
+			result = range_gist_consistent_leaf_element(typcache, strategy,
+														key, query);
+	}
+	else
+	{
+		if (!OidIsValid(subtype) || subtype == ANYMULTIRANGEOID)
+			result = range_gist_consistent_int_multirange(typcache, strategy, key,
+														  DatumGetMultirangeTypeP(query));
+		else if (subtype == ANYRANGEOID)
+			result = range_gist_consistent_int_range(typcache, strategy, key,
+													 DatumGetRangeTypeP(query));
+		else
+			result = range_gist_consistent_int_element(typcache, strategy,
+													   key, query);
+	}
+	PG_RETURN_BOOL(result);
+}
+
 /* form union range */
 Datum
 range_gist_union(PG_FUNCTION_ARGS)
@@ -802,6 +884,30 @@ range_super_union(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
 	return result;
 }
 
+static bool
+multirange_union_range_equal(TypeCacheEntry *typcache,
+							 const RangeType *r,
+							 const MultirangeType *mr)
+{
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2,
+				tmp;
+	bool		empty;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return (RangeIsEmpty(r) && MultirangeIsEmpty(mr));
+
+	range_deserialize(typcache, r, &lower1, &upper1, &empty);
+	Assert(!empty);
+	multirange_get_bounds(typcache, mr, 0, &lower2, &tmp);
+	multirange_get_bounds(typcache, mr, mr->rangeCount - 1, &tmp, &upper2);
+
+	return (range_cmp_bounds(typcache, &lower1, &lower2) == 0 &&
+			range_cmp_bounds(typcache, &upper1, &upper2) == 0);
+}
+
 /*
  * GiST consistent test on an index internal page with range query
  */
@@ -911,6 +1017,15 @@ range_gist_consistent_int_multirange(TypeCacheEntry *typcache,
 			if (RangeIsOrContainsEmpty(key))
 				return true;
 			return range_overlaps_multirange_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 (MultirangeIsEmpty(query))
+				return RangeIsOrContainsEmpty(key);
+			return range_contains_multirange_internal(typcache, key, query);
 		default:
 			elog(ERROR, "unrecognized range strategy: %d", strategy);
 			return false;		/* keep compiler quiet */
@@ -998,6 +1113,8 @@ range_gist_consistent_leaf_multirange(TypeCacheEntry *typcache,
 			return range_contains_multirange_internal(typcache, key, query);
 		case RANGESTRAT_CONTAINED_BY:
 			return multirange_contains_range_internal(typcache, query, key);
+		case RANGESTRAT_EQ:
+			return multirange_union_range_equal(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 e055da67eb7..bbe1a6ddf8e 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1398,6 +1398,62 @@
   amoprighttype => 'anyrange', amopstrategy => '18',
   amopopr => '=(anyrange,anyrange)', amopmethod => 'gist' },
 
+# GiST multirange_ops
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '1',
+  amopopr => '<<(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anyrange', amopstrategy => '1',
+  amopopr => '<<(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '2',
+  amopopr => '&<(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anyrange', amopstrategy => '2',
+  amopopr => '&<(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '3',
+  amopopr => '&&(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anyrange', amopstrategy => '3',
+  amopopr => '&&(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '4',
+  amopopr => '&>(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anyrange', amopstrategy => '4',
+  amopopr => '&>(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '5',
+  amopopr => '>>(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anyrange', amopstrategy => '5',
+  amopopr => '>>(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '6',
+  amopopr => '-|-(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anyrange', amopstrategy => '6',
+  amopopr => '-|-(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '7',
+  amopopr => '@>(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anyrange', amopstrategy => '7',
+  amopopr => '@>(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '8',
+  amopopr => '<@(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anyrange', amopstrategy => '8',
+  amopopr => '<@(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anyelement', amopstrategy => '16',
+  amopopr => '@>(anymultirange,anyelement)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_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 9d423d535cd..68d72ec732a 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -612,6 +612,24 @@
   amprocrighttype => 'inet', amprocnum => '7', amproc => 'inet_gist_same' },
 { amprocfamily => 'gist/network_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '9', amproc => 'inet_gist_fetch' },
+{ amprocfamily => 'gist/multirange_ops', amproclefttype => 'anymultirange',
+  amprocrighttype => 'anymultirange', amprocnum => '1',
+  amproc => 'multirange_gist_consistent' },
+{ amprocfamily => 'gist/multirange_ops', amproclefttype => 'anymultirange',
+  amprocrighttype => 'anymultirange', amprocnum => '2',
+  amproc => 'range_gist_union' },
+{ amprocfamily => 'gist/multirange_ops', amproclefttype => 'anymultirange',
+  amprocrighttype => 'anymultirange', amprocnum => '3',
+  amproc => 'multirange_gist_compress' },
+{ amprocfamily => 'gist/multirange_ops', amproclefttype => 'anymultirange',
+  amprocrighttype => 'anymultirange', amprocnum => '5',
+  amproc => 'range_gist_penalty' },
+{ amprocfamily => 'gist/multirange_ops', amproclefttype => 'anymultirange',
+  amprocrighttype => 'anymultirange', amprocnum => '6',
+  amproc => 'range_gist_picksplit' },
+{ amprocfamily => 'gist/multirange_ops', amproclefttype => 'anymultirange',
+  amprocrighttype => 'anymultirange', amprocnum => '7',
+  amproc => 'range_gist_same' },
 
 # gin
 { amprocfamily => 'gin/array_ops', amproclefttype => 'anyarray',
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index 4c5e475ff7b..12cad694186 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -236,6 +236,8 @@
   opcintype => 'anymultirange' },
 { opcmethod => 'hash', opcname => 'multirange_ops', opcfamily => 'hash/multirange_ops',
   opcintype => 'anymultirange' },
+{ opcmethod => 'gist', opcname => 'multirange_ops', opcfamily => 'gist/multirange_ops',
+  opcintype => 'anymultirange', opckeytype => 'anyrange' },
 { 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 fe42dfc0f8f..ac8338f34b1 100644
--- a/src/include/catalog/pg_opfamily.dat
+++ b/src/include/catalog/pg_opfamily.dat
@@ -236,5 +236,7 @@
   opfmethod => 'btree', opfname => 'multirange_ops' },
 { oid => '4225',
   opfmethod => 'hash', opfname => 'multirange_ops' },
+{ oid => '8021',
+  opfmethod => 'gist', opfname => 'multirange_ops' },
 
 ]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9d19eb5f409..d2deb087da3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9922,6 +9922,14 @@
 { oid => '3881', descr => 'GiST support',
   proname => 'range_gist_same', prorettype => 'internal',
   proargtypes => 'anyrange anyrange internal', prosrc => 'range_gist_same' },
+{ oid => '8017', descr => 'GiST support',
+  proname => 'multirange_gist_consistent', prorettype => 'bool',
+  proargtypes => 'internal anymultirange int2 oid internal',
+  prosrc => 'multirange_gist_consistent' },
+{ oid => '8019', descr => 'GiST support',
+  proname => 'multirange_gist_compress', prorettype => 'internal',
+  proargtypes => 'internal',
+  prosrc => 'multirange_gist_compress' },
 { oid => '3902', descr => 'hash a range',
   proname => 'hash_range', prorettype => 'int4', proargtypes => 'anyrange',
   prosrc => 'hash_range' },
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
index ff2e58744a9..1d877f08b56 100644
--- a/src/include/utils/multirangetypes.h
+++ b/src/include/utils/multirangetypes.h
@@ -129,5 +129,7 @@ extern void multirange_get_bounds(TypeCacheEntry *rangetyp,
 								  RangeBound *lower, RangeBound *upper);
 extern RangeType *multirange_get_range(TypeCacheEntry *rangetyp,
 									   const MultirangeType *multirange, int i);
+extern RangeType *multirange_get_union_range(TypeCacheEntry *rangetyp,
+											 const MultirangeType *mr);
 
 #endif							/* MULTIRANGETYPES_H */
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
index aa7232efc57..86011a02a1e 100644
--- a/src/test/regress/expected/multirangetypes.out
+++ b/src/test/regress/expected/multirangetypes.out
@@ -2219,6 +2219,263 @@ SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirang
  {[1,2),[3,4),[7,8),[9,10)}
 (1 row)
 
+-- test GiST index
+create table test_multirange_gist(mr int4multirange);
+insert into test_multirange_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_gist select '{}'::int4multirange from generate_series(1,500) g;
+insert into test_multirange_gist select int4multirange(int4range(g, g+10000)) from generate_series(1,1000) g;
+insert into test_multirange_gist select int4multirange(int4range(NULL, g*10, '(]'), int4range(g*10, g*20, '(]')) from generate_series(1,100) g;
+insert into test_multirange_gist select int4multirange(int4range(g*10, g*20, '(]'), int4range(g*20, NULL, '(]')) from generate_series(1,100) g;
+create index test_mulrirange_gist_idx on test_multirange_gist using gist (mr);
+-- first, verify non-indexed results
+SET enable_seqscan    = t;
+SET enable_indexscan  = f;
+SET enable_bitmapscan = f;
+select count(*) from test_multirange_gist where mr @> 'empty'::int4range;
+ count 
+-------
+  3700
+(1 row)
+
+select count(*) from test_multirange_gist where mr = int4multirange(int4range(10,20), int4range(30,40), int4range(50,60));
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from test_multirange_gist where mr @> 10;
+ count 
+-------
+   120
+(1 row)
+
+select count(*) from test_multirange_gist where mr @> int4range(10,20);
+ count 
+-------
+   111
+(1 row)
+
+select count(*) from test_multirange_gist where mr && int4range(10,20);
+ count 
+-------
+   139
+(1 row)
+
+select count(*) from test_multirange_gist where mr <@ int4range(10,50);
+ count 
+-------
+   500
+(1 row)
+
+select count(*) from test_multirange_gist where mr << int4range(100,500);
+ count 
+-------
+    54
+(1 row)
+
+select count(*) from test_multirange_gist where mr >> int4range(100,500);
+ count 
+-------
+  2053
+(1 row)
+
+select count(*) from test_multirange_gist where mr &< int4range(100,500);
+ count 
+-------
+   474
+(1 row)
+
+select count(*) from test_multirange_gist where mr &> int4range(100,500);
+ count 
+-------
+  2893
+(1 row)
+
+select count(*) from test_multirange_gist where mr -|- int4range(100,500);
+ count 
+-------
+     3
+(1 row)
+
+select count(*) from test_multirange_gist where mr @> '{}'::int4multirange;
+ count 
+-------
+  3700
+(1 row)
+
+select count(*) from test_multirange_gist where mr @> int4multirange(int4range(10,20), int4range(30,40));
+ count 
+-------
+   110
+(1 row)
+
+select count(*) from test_multirange_gist where mr && '{(10,20),(30,40),(50,60)}'::int4multirange;
+ count 
+-------
+   218
+(1 row)
+
+select count(*) from test_multirange_gist where mr <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+ count 
+-------
+   500
+(1 row)
+
+select count(*) from test_multirange_gist where mr << int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+    54
+(1 row)
+
+select count(*) from test_multirange_gist where mr >> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  2053
+(1 row)
+
+select count(*) from test_multirange_gist where mr &< int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+   474
+(1 row)
+
+select count(*) from test_multirange_gist where mr &> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  2893
+(1 row)
+
+select count(*) from test_multirange_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_gist where mr @> 'empty'::int4range;
+ count 
+-------
+  3700
+(1 row)
+
+select count(*) from test_multirange_gist where mr = int4multirange(int4range(10,20), int4range(30,40), int4range(50,60));
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from test_multirange_gist where mr @> 10;
+ count 
+-------
+   120
+(1 row)
+
+select count(*) from test_multirange_gist where mr @> int4range(10,20);
+ count 
+-------
+   111
+(1 row)
+
+select count(*) from test_multirange_gist where mr && int4range(10,20);
+ count 
+-------
+   139
+(1 row)
+
+select count(*) from test_multirange_gist where mr <@ int4range(10,50);
+ count 
+-------
+   500
+(1 row)
+
+select count(*) from test_multirange_gist where mr << int4range(100,500);
+ count 
+-------
+    54
+(1 row)
+
+select count(*) from test_multirange_gist where mr >> int4range(100,500);
+ count 
+-------
+  2053
+(1 row)
+
+select count(*) from test_multirange_gist where mr &< int4range(100,500);
+ count 
+-------
+   474
+(1 row)
+
+select count(*) from test_multirange_gist where mr &> int4range(100,500);
+ count 
+-------
+  2893
+(1 row)
+
+select count(*) from test_multirange_gist where mr -|- int4range(100,500);
+ count 
+-------
+     3
+(1 row)
+
+select count(*) from test_multirange_gist where mr @> '{}'::int4multirange;
+ count 
+-------
+  3700
+(1 row)
+
+select count(*) from test_multirange_gist where mr @> int4multirange(int4range(10,20), int4range(30,40));
+ count 
+-------
+   110
+(1 row)
+
+select count(*) from test_multirange_gist where mr && '{(10,20),(30,40),(50,60)}'::int4multirange;
+ count 
+-------
+   218
+(1 row)
+
+select count(*) from test_multirange_gist where mr <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+ count 
+-------
+   500
+(1 row)
+
+select count(*) from test_multirange_gist where mr << int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+    54
+(1 row)
+
+select count(*) from test_multirange_gist where mr >> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  2053
+(1 row)
+
+select count(*) from test_multirange_gist where mr &< int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+   474
+(1 row)
+
+select count(*) from test_multirange_gist where mr &> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  2893
+(1 row)
+
+select count(*) from test_multirange_gist where mr -|- int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+     3
+(1 row)
+
+drop table test_multirange_gist;
 --
 -- range_agg function
 --
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
index 9d7af404c98..2a2ee4dcdfd 100644
--- a/src/test/regress/sql/multirangetypes.sql
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -414,6 +414,69 @@ SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultira
 SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
 SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
 
+-- test GiST index
+create table test_multirange_gist(mr int4multirange);
+insert into test_multirange_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_gist select '{}'::int4multirange from generate_series(1,500) g;
+insert into test_multirange_gist select int4multirange(int4range(g, g+10000)) from generate_series(1,1000) g;
+insert into test_multirange_gist select int4multirange(int4range(NULL, g*10, '(]'), int4range(g*10, g*20, '(]')) from generate_series(1,100) g;
+insert into test_multirange_gist select int4multirange(int4range(g*10, g*20, '(]'), int4range(g*20, NULL, '(]')) from generate_series(1,100) g;
+create index test_mulrirange_gist_idx on test_multirange_gist using gist (mr);
+
+-- first, verify non-indexed results
+SET enable_seqscan    = t;
+SET enable_indexscan  = f;
+SET enable_bitmapscan = f;
+
+select count(*) from test_multirange_gist where mr @> 'empty'::int4range;
+select count(*) from test_multirange_gist where mr = int4multirange(int4range(10,20), int4range(30,40), int4range(50,60));
+select count(*) from test_multirange_gist where mr @> 10;
+select count(*) from test_multirange_gist where mr @> int4range(10,20);
+select count(*) from test_multirange_gist where mr && int4range(10,20);
+select count(*) from test_multirange_gist where mr <@ int4range(10,50);
+select count(*) from test_multirange_gist where mr << int4range(100,500);
+select count(*) from test_multirange_gist where mr >> int4range(100,500);
+select count(*) from test_multirange_gist where mr &< int4range(100,500);
+select count(*) from test_multirange_gist where mr &> int4range(100,500);
+select count(*) from test_multirange_gist where mr -|- int4range(100,500);
+select count(*) from test_multirange_gist where mr @> '{}'::int4multirange;
+select count(*) from test_multirange_gist where mr @> int4multirange(int4range(10,20), int4range(30,40));
+select count(*) from test_multirange_gist where mr && '{(10,20),(30,40),(50,60)}'::int4multirange;
+select count(*) from test_multirange_gist where mr <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+select count(*) from test_multirange_gist where mr << int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_gist where mr >> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_gist where mr &< int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_gist where mr &> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_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_gist where mr @> 'empty'::int4range;
+select count(*) from test_multirange_gist where mr = int4multirange(int4range(10,20), int4range(30,40), int4range(50,60));
+select count(*) from test_multirange_gist where mr @> 10;
+select count(*) from test_multirange_gist where mr @> int4range(10,20);
+select count(*) from test_multirange_gist where mr && int4range(10,20);
+select count(*) from test_multirange_gist where mr <@ int4range(10,50);
+select count(*) from test_multirange_gist where mr << int4range(100,500);
+select count(*) from test_multirange_gist where mr >> int4range(100,500);
+select count(*) from test_multirange_gist where mr &< int4range(100,500);
+select count(*) from test_multirange_gist where mr &> int4range(100,500);
+select count(*) from test_multirange_gist where mr -|- int4range(100,500);
+select count(*) from test_multirange_gist where mr @> '{}'::int4multirange;
+select count(*) from test_multirange_gist where mr @> int4multirange(int4range(10,20), int4range(30,40));
+select count(*) from test_multirange_gist where mr && '{(10,20),(30,40),(50,60)}'::int4multirange;
+select count(*) from test_multirange_gist where mr <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+select count(*) from test_multirange_gist where mr << int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_gist where mr >> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_gist where mr &< int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_gist where mr &> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_gist where mr -|- int4multirange(int4range(100,200), int4range(400,500));
+
+drop table test_multirange_gist;
+
 --
 -- range_agg function
 --
-- 
2.24.3 (Apple Git-128)

