>From 218dfb0053b0d47f59de8a63ed1a3abdbde14c19 Mon Sep 17 00:00:00 2001
From: David Zhang <david.zhang@highgo.ca>
Date: Thu, 17 Nov 2022 12:20:34 -0800
Subject: [PATCH 1/4] support global unique index with non-partition key

---
 contrib/pageinspect/btreefuncs.c       |  7 +++--
 doc/src/sgml/ref/create_index.sgml     | 13 +++++++++
 src/backend/access/common/reloptions.c |  1 +
 src/backend/access/index/indexam.c     |  1 +
 src/backend/access/table/table.c       |  1 +
 src/backend/catalog/aclchk.c           |  3 ++
 src/backend/catalog/dependency.c       |  1 +
 src/backend/catalog/heap.c             |  4 ++-
 src/backend/catalog/index.c            | 11 +++++--
 src/backend/catalog/objectaddress.c    |  4 +++
 src/backend/catalog/pg_class.c         |  2 ++
 src/backend/commands/cluster.c         |  6 ++--
 src/backend/commands/indexcmds.c       | 28 +++++++++++++++---
 src/backend/commands/tablecmds.c       | 36 ++++++++++++++++++++---
 src/backend/optimizer/util/plancat.c   |  3 +-
 src/backend/parser/gram.y              | 13 +++++++--
 src/backend/parser/parse_utilcmd.c     |  1 +
 src/backend/utils/adt/amutils.c        |  1 +
 src/backend/utils/cache/relcache.c     | 17 ++++++++---
 src/bin/psql/describe.c                | 17 ++++++++++-
 src/include/catalog/index.h            |  1 +
 src/include/catalog/pg_class.h         |  2 ++
 src/include/nodes/parsenodes.h         |  1 +
 src/test/regress/expected/indexing.out | 40 ++++++++++++++++++++++++++
 src/test/regress/sql/indexing.sql      | 11 +++++++
 25 files changed, 199 insertions(+), 26 deletions(-)

diff --git a/contrib/pageinspect/btreefuncs.c b/contrib/pageinspect/btreefuncs.c
index 9375d55e14..fea5a3033b 100644
--- a/contrib/pageinspect/btreefuncs.c
+++ b/contrib/pageinspect/btreefuncs.c
@@ -51,6 +51,7 @@ PG_FUNCTION_INFO_V1(bt_page_stats);
 #define IS_BTREE(r) ((r)->rd_rel->relam == BTREE_AM_OID)
 #define DatumGetItemPointer(X)	 ((ItemPointer) DatumGetPointer(X))
 #define ItemPointerGetDatum(X)	 PointerGetDatum(X)
+#define IS_GLOBAL_INDEX(r) ((r)->rd_rel->relkind == RELKIND_GLOBAL_INDEX)
 
 /* note: BlockNumber is unsigned, hence can't be negative */
 #define CHECK_RELATION_BLOCK_RANGE(rel, blkno) { \
@@ -205,7 +206,7 @@ bt_page_stats_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version)
 	relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
 	rel = relation_openrv(relrv, AccessShareLock);
 
-	if (!IS_INDEX(rel) || !IS_BTREE(rel))
+	if ((!IS_INDEX(rel) && !IS_GLOBAL_INDEX(rel)) || !IS_BTREE(rel))
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a %s index",
@@ -473,7 +474,7 @@ bt_page_items_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version)
 		relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
 		rel = relation_openrv(relrv, AccessShareLock);
 
-		if (!IS_INDEX(rel) || !IS_BTREE(rel))
+		if ((!IS_INDEX(rel) && !IS_GLOBAL_INDEX(rel)) || !IS_BTREE(rel))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("\"%s\" is not a %s index",
@@ -709,7 +710,7 @@ bt_metap(PG_FUNCTION_ARGS)
 	relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
 	rel = relation_openrv(relrv, AccessShareLock);
 
-	if (!IS_INDEX(rel) || !IS_BTREE(rel))
+	if ((!IS_INDEX(rel) && !IS_GLOBAL_INDEX(rel)) || !IS_BTREE(rel))
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a %s index",
diff --git a/doc/src/sgml/ref/create_index.sgml b/doc/src/sgml/ref/create_index.sgml
index 40986aa502..5444c096e1 100644
--- a/doc/src/sgml/ref/create_index.sgml
+++ b/doc/src/sgml/ref/create_index.sgml
@@ -28,6 +28,7 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class=
     [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
     [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
     [ WHERE <replaceable class="parameter">predicate</replaceable> ]
+    [ GLOBAL ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -380,6 +381,18 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class=
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>GLOBAL</literal></term>
+      <listitem>
+       <para>
+        Used with <literal>UNIQUE</literal> to enable cross-partition
+        uniqueness check on a partitioned table. Attempts to insert or
+        update data which would result in duplicate entries in other 
+        partitions as a whole will generate an error.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
 
   <refsect2 id="sql-createindex-storage-parameters" xreflabel="Index Storage Parameters">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 75b7344891..70ad7ffe8f 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -1410,6 +1410,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 			options = view_reloptions(datum, false);
 			break;
 		case RELKIND_INDEX:
+		case RELKIND_GLOBAL_INDEX:
 		case RELKIND_PARTITIONED_INDEX:
 			options = index_reloptions(amoptions, datum, false);
 			break;
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index fe80b8b0ba..5bf36cc686 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -136,6 +136,7 @@ index_open(Oid relationId, LOCKMODE lockmode)
 	r = relation_open(relationId, lockmode);
 
 	if (r->rd_rel->relkind != RELKIND_INDEX &&
+		r->rd_rel->relkind != RELKIND_GLOBAL_INDEX &&
 		r->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/access/table/table.c b/src/backend/access/table/table.c
index 7e94232f01..0f9570c894 100644
--- a/src/backend/access/table/table.c
+++ b/src/backend/access/table/table.c
@@ -138,6 +138,7 @@ static inline void
 validate_relation_kind(Relation r)
 {
 	if (r->rd_rel->relkind == RELKIND_INDEX ||
+		r->rd_rel->relkind == RELKIND_GLOBAL_INDEX ||
 		r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX ||
 		r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
 		ereport(ERROR,
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 42360d37ca..79e14ca356 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -1819,6 +1819,7 @@ ExecGrant_Relation(InternalGrant *istmt)
 
 		/* Not sensible to grant on an index */
 		if (pg_class_tuple->relkind == RELKIND_INDEX ||
+			pg_class_tuple->relkind == RELKIND_GLOBAL_INDEX ||
 			pg_class_tuple->relkind == RELKIND_PARTITIONED_INDEX)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -5950,6 +5951,7 @@ recordExtObjInitPriv(Oid objoid, Oid classoid)
 		 * restrictions in ALTER EXTENSION ADD, but let's check anyway.)
 		 */
 		if (pg_class_tuple->relkind == RELKIND_INDEX ||
+			pg_class_tuple->relkind == RELKIND_GLOBAL_INDEX ||
 			pg_class_tuple->relkind == RELKIND_PARTITIONED_INDEX ||
 			pg_class_tuple->relkind == RELKIND_COMPOSITE_TYPE)
 		{
@@ -6244,6 +6246,7 @@ removeExtObjInitPriv(Oid objoid, Oid classoid)
 		 * restrictions in ALTER EXTENSION DROP, but let's check anyway.)
 		 */
 		if (pg_class_tuple->relkind == RELKIND_INDEX ||
+			pg_class_tuple->relkind == RELKIND_GLOBAL_INDEX ||
 			pg_class_tuple->relkind == RELKIND_PARTITIONED_INDEX ||
 			pg_class_tuple->relkind == RELKIND_COMPOSITE_TYPE)
 		{
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 7f3e64b5ae..5ec1f32efa 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1399,6 +1399,7 @@ doDeletion(const ObjectAddress *object, int flags)
 				char		relKind = get_rel_relkind(object->objectId);
 
 				if (relKind == RELKIND_INDEX ||
+					relKind == RELKIND_GLOBAL_INDEX ||
 					relKind == RELKIND_PARTITIONED_INDEX)
 				{
 					bool		concurrent = ((flags & PERFORM_DELETION_CONCURRENTLY) != 0);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 5b49cc5a09..54a81c11dc 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -316,7 +316,8 @@ heap_create(const char *relname,
 	 * user defined relation, not a system one.
 	 */
 	if (!allow_system_table_mods &&
-		((IsCatalogNamespace(relnamespace) && relkind != RELKIND_INDEX) ||
+		((IsCatalogNamespace(relnamespace) &&
+		  (relkind != RELKIND_INDEX && relkind != RELKIND_GLOBAL_INDEX)) ||
 		 IsToastNamespace(relnamespace)) &&
 		IsNormalProcessingMode())
 		ereport(ERROR,
@@ -1300,6 +1301,7 @@ heap_create_with_catalog(const char *relname,
 	if (!(relkind == RELKIND_SEQUENCE ||
 		  relkind == RELKIND_TOASTVALUE ||
 		  relkind == RELKIND_INDEX ||
+		  relkind == RELKIND_GLOBAL_INDEX ||
 		  relkind == RELKIND_PARTITIONED_INDEX))
 	{
 		Oid			new_array_oid;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 61f1d3926a..46a2fb6cc7 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -731,6 +731,7 @@ index_create(Relation heapRelation,
 	bool		invalid = (flags & INDEX_CREATE_INVALID) != 0;
 	bool		concurrent = (flags & INDEX_CREATE_CONCURRENT) != 0;
 	bool		partitioned = (flags & INDEX_CREATE_PARTITIONED) != 0;
+	bool		globalindex = (flags & INDEX_CREATE_GLOBAL) != 0;
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
@@ -742,7 +743,10 @@ index_create(Relation heapRelation,
 	/* partitioned indexes must never be "built" by themselves */
 	Assert(!partitioned || (flags & INDEX_CREATE_SKIP_BUILD));
 
-	relkind = partitioned ? RELKIND_PARTITIONED_INDEX : RELKIND_INDEX;
+	if (globalindex)
+		relkind = partitioned ? RELKIND_PARTITIONED_INDEX : RELKIND_GLOBAL_INDEX;
+	else
+		relkind = partitioned ? RELKIND_PARTITIONED_INDEX : RELKIND_INDEX;
 	is_exclusion = (indexInfo->ii_ExclusionOps != NULL);
 
 	pg_class = table_open(RelationRelationId, RowExclusiveLock);
@@ -918,7 +922,7 @@ index_create(Relation heapRelation,
 			binary_upgrade_next_index_pg_class_oid = InvalidOid;
 
 			/* Override the index relfilenumber */
-			if ((relkind == RELKIND_INDEX) &&
+			if ((relkind == RELKIND_INDEX || relkind == RELKIND_GLOBAL_INDEX) &&
 				(!RelFileNumberIsValid(binary_upgrade_next_index_pg_class_relfilenumber)))
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -2883,7 +2887,8 @@ index_update_stats(Relation rel,
 		BlockNumber relpages = RelationGetNumberOfBlocks(rel);
 		BlockNumber relallvisible;
 
-		if (rd_rel->relkind != RELKIND_INDEX)
+		if (rd_rel->relkind != RELKIND_INDEX &&
+			rd_rel->relkind != RELKIND_GLOBAL_INDEX)
 			visibilitymap_count(rel, &relallvisible, NULL);
 		else					/* don't bother for indexes */
 			relallvisible = 0;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index c7de7232b8..0bfab32b65 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1389,6 +1389,7 @@ get_relation_by_qualified_name(ObjectType objtype, List *object,
 	{
 		case OBJECT_INDEX:
 			if (relation->rd_rel->relkind != RELKIND_INDEX &&
+				relation->rd_rel->relkind != RELKIND_GLOBAL_INDEX &&
 				relation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -4172,6 +4173,7 @@ getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 							 relname);
 			break;
 		case RELKIND_INDEX:
+		case RELKIND_GLOBAL_INDEX:
 		case RELKIND_PARTITIONED_INDEX:
 			appendStringInfo(buffer, _("index %s"),
 							 relname);
@@ -4706,6 +4708,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
+		case RELKIND_GLOBAL_INDEX:
 		case RELKIND_PARTITIONED_INDEX:
 			appendStringInfoString(buffer, "index");
 			break;
@@ -6187,6 +6190,7 @@ get_relkind_objtype(char relkind)
 		case RELKIND_PARTITIONED_TABLE:
 			return OBJECT_TABLE;
 		case RELKIND_INDEX:
+		case RELKIND_GLOBAL_INDEX:
 		case RELKIND_PARTITIONED_INDEX:
 			return OBJECT_INDEX;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/catalog/pg_class.c b/src/backend/catalog/pg_class.c
index b696fa2afd..39f410906c 100644
--- a/src/backend/catalog/pg_class.c
+++ b/src/backend/catalog/pg_class.c
@@ -45,6 +45,8 @@ errdetail_relkind_not_supported(char relkind)
 			return errdetail("This operation is not supported for partitioned tables.");
 		case RELKIND_PARTITIONED_INDEX:
 			return errdetail("This operation is not supported for partitioned indexes.");
+		case RELKIND_GLOBAL_INDEX:
+			return errdetail("This operation is not supported for global indexes.");
 		default:
 			elog(ERROR, "unrecognized relkind: '%c'", relkind);
 			return 0;
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 1976a373ef..eff371ff12 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -1199,7 +1199,8 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
 	 */
 
 	/* set rel1's frozen Xid and minimum MultiXid */
-	if (relform1->relkind != RELKIND_INDEX)
+	if (relform1->relkind != RELKIND_INDEX &&
+		relform1->relkind != RELKIND_GLOBAL_INDEX)
 	{
 		Assert(!TransactionIdIsValid(frozenXid) ||
 			   TransactionIdIsNormal(frozenXid));
@@ -1686,7 +1687,8 @@ get_tables_to_cluster_partitioned(MemoryContext cluster_context, Oid indexOid)
 		RelToCluster *rtc;
 
 		/* consider only leaf indexes */
-		if (get_rel_relkind(indexrelid) != RELKIND_INDEX)
+		if (get_rel_relkind(indexrelid) != RELKIND_INDEX &&
+			get_rel_relkind(indexrelid) != RELKIND_GLOBAL_INDEX)
 			continue;
 
 		/* Silently skip partitions which the user has no access to. */
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 659e189549..099bf68e77 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -711,8 +711,20 @@ DefineIndex(Oid relationId,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("cannot create exclusion constraints on partitioned table \"%s\"",
 							RelationGetRelationName(rel))));
+
+		if (stmt->global_index && !stmt->unique)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("cannot create global index without unique on partitioned table \"%s\"",
+							RelationGetRelationName(rel))));
 	}
 
+	if (stmt->global_index && rel->rd_rel->relkind == RELKIND_RELATION && !rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot create global index on non-partitioned table \"%s\"",
+						RelationGetRelationName(rel))));
+
 	/*
 	 * Don't try to CREATE INDEX on temp tables of other backends.
 	 */
@@ -923,7 +935,7 @@ DefineIndex(Oid relationId,
 	 * We could lift this limitation if we had global indexes, but those have
 	 * their own problems, so this is a useful feature combination.
 	 */
-	if (partitioned && (stmt->unique || stmt->primary))
+	if (partitioned && (stmt->unique || stmt->primary) && !stmt->global_index)
 	{
 		PartitionKey key = RelationGetPartitionKey(rel);
 		const char *constraint_type;
@@ -1026,7 +1038,7 @@ DefineIndex(Oid relationId,
 				}
 			}
 
-			if (!found)
+			if (!found && !stmt->global_index)
 			{
 				Form_pg_attribute att;
 
@@ -1145,6 +1157,8 @@ DefineIndex(Oid relationId,
 		if (pd->nparts != 0)
 			flags |= INDEX_CREATE_INVALID;
 	}
+	if (stmt->global_index)
+		flags |= INDEX_CREATE_GLOBAL;
 
 	if (stmt->deferrable)
 		constr_flags |= INDEX_CONSTR_CREATE_DEFERRABLE;
@@ -2784,6 +2798,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_INDEX &&
+		relkind != RELKIND_GLOBAL_INDEX &&
 		relkind != RELKIND_PARTITIONED_INDEX)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -3176,6 +3191,7 @@ ReindexPartitions(Oid relid, ReindexParams *params, bool isTopLevel)
 			continue;
 
 		Assert(partkind == RELKIND_INDEX ||
+			   partkind == RELKIND_GLOBAL_INDEX ||
 			   partkind == RELKIND_RELATION);
 
 		/* Save partition OID */
@@ -3268,7 +3284,8 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 			(void) ReindexRelationConcurrently(relid, &newparams);
 			/* ReindexRelationConcurrently() does the verbose output */
 		}
-		else if (relkind == RELKIND_INDEX)
+		else if (relkind == RELKIND_INDEX ||
+				 relkind == RELKIND_GLOBAL_INDEX)
 		{
 			ReindexParams newparams = *params;
 
@@ -3527,6 +3544,7 @@ ReindexRelationConcurrently(Oid relationOid, ReindexParams *params)
 				break;
 			}
 		case RELKIND_INDEX:
+		case RELKIND_GLOBAL_INDEX:
 			{
 				Oid			heapId = IndexGetRelation(relationOid,
 													  (params->options & REINDEXOPT_MISSING_OK) != 0);
@@ -4127,7 +4145,8 @@ ReindexRelationConcurrently(Oid relationOid, ReindexParams *params)
 	/* Log what we did */
 	if ((params->options & REINDEXOPT_VERBOSE) != 0)
 	{
-		if (relkind == RELKIND_INDEX)
+		if (relkind == RELKIND_INDEX ||
+			relkind == RELKIND_GLOBAL_INDEX)
 			ereport(INFO,
 					(errmsg("index \"%s.%s\" was reindexed",
 							relationNamespace, relationName),
@@ -4180,6 +4199,7 @@ IndexSetParentIndex(Relation partitionIdx, Oid parentOid)
 
 	/* Make sure this is an index */
 	Assert(partitionIdx->rd_rel->relkind == RELKIND_INDEX ||
+		   partitionIdx->rd_rel->relkind == RELKIND_GLOBAL_INDEX ||
 		   partitionIdx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
 
 	/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 6007e10730..b16a1180d8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -294,6 +294,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
 		gettext_noop("index \"%s\" does not exist, skipping"),
 		gettext_noop("\"%s\" is not an index"),
 	gettext_noop("Use DROP INDEX to remove an index.")},
+	{RELKIND_GLOBAL_INDEX,
+		ERRCODE_UNDEFINED_OBJECT,
+		gettext_noop("index \"%s\" does not exist"),
+		gettext_noop("index \"%s\" does not exist, skipping"),
+		gettext_noop("\"%s\" is not an index"),
+	gettext_noop("Use DROP INDEX to remove an index.")},
 	{'\0', 0, NULL, NULL, NULL, NULL}
 };
 
@@ -320,6 +326,7 @@ struct DropRelationCallbackState
 #define		ATT_FOREIGN_TABLE		0x0020
 #define		ATT_PARTITIONED_INDEX	0x0040
 #define		ATT_SEQUENCE			0x0080
+#define		ATT_GLOBAL_INDEX		0x0100
 
 /*
  * ForeignTruncateInfo
@@ -1564,6 +1571,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		expected_relkind = RELKIND_RELATION;
 	else if (classform->relkind == RELKIND_PARTITIONED_INDEX)
 		expected_relkind = RELKIND_INDEX;
+	else if (classform->relkind == RELKIND_GLOBAL_INDEX)
+		expected_relkind = RELKIND_GLOBAL_INDEX;
 	else
 		expected_relkind = classform->relkind;
 
@@ -1584,7 +1593,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 	 * only concerns indexes of toast relations that became invalid during a
 	 * REINDEX CONCURRENTLY process.
 	 */
-	if (IsSystemClass(relOid, classform) && classform->relkind == RELKIND_INDEX)
+	if (IsSystemClass(relOid, classform) && (classform->relkind == RELKIND_INDEX ||
+											 classform->relkind == RELKIND_GLOBAL_INDEX))
 	{
 		HeapTuple	locTuple;
 		Form_pg_index indexform;
@@ -1623,7 +1633,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 	 * entry, though --- the relation may have been dropped.  Note that this
 	 * code will execute for either plain or partitioned indexes.
 	 */
-	if (expected_relkind == RELKIND_INDEX &&
+	if ((expected_relkind == RELKIND_INDEX ||
+		 expected_relkind == RELKIND_GLOBAL_INDEX) &&
 		relOid != oldRelOid)
 	{
 		state->heapOid = IndexGetRelation(relOid, true);
@@ -3406,6 +3417,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
 		relkind != RELKIND_INDEX &&
+		relkind != RELKIND_GLOBAL_INDEX &&
 		relkind != RELKIND_PARTITIONED_INDEX &&
 		relkind != RELKIND_FOREIGN_TABLE &&
 		relkind != RELKIND_PARTITIONED_TABLE)
@@ -3837,6 +3849,7 @@ RenameRelation(RenameStmt *stmt)
 		 */
 		relkind = get_rel_relkind(relid);
 		obj_is_index = (relkind == RELKIND_INDEX ||
+						relkind == RELKIND_GLOBAL_INDEX ||
 						relkind == RELKIND_PARTITIONED_INDEX);
 		if (obj_is_index || is_index_stmt == obj_is_index)
 			break;
@@ -3902,6 +3915,7 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 */
 	Assert(!is_index ||
 		   is_index == (targetrelation->rd_rel->relkind == RELKIND_INDEX ||
+						targetrelation->rd_rel->relkind == RELKIND_GLOBAL_INDEX ||
 						targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX));
 
 	/*
@@ -3929,6 +3943,7 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * Also rename the associated constraint, if any.
 	 */
 	if (targetrelation->rd_rel->relkind == RELKIND_INDEX ||
+		targetrelation->rd_rel->relkind == RELKIND_GLOBAL_INDEX ||
 		targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
 	{
 		Oid			constraintId = get_index_constraint(myrelid);
@@ -4013,6 +4028,7 @@ CheckTableNotInUse(Relation rel, const char *stmt)
 						stmt, RelationGetRelationName(rel))));
 
 	if (rel->rd_rel->relkind != RELKIND_INDEX &&
+		rel->rd_rel->relkind != RELKIND_GLOBAL_INDEX &&
 		rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
 		AfterTriggerPendingOnRel(RelationGetRelid(rel)))
 		ereport(ERROR,
@@ -6271,6 +6287,9 @@ ATSimplePermissions(AlterTableType cmdtype, Relation rel, int allowed_targets)
 		case RELKIND_INDEX:
 			actual_target = ATT_INDEX;
 			break;
+		case RELKIND_GLOBAL_INDEX:
+			actual_target = ATT_GLOBAL_INDEX;
+			break;
 		case RELKIND_PARTITIONED_INDEX:
 			actual_target = ATT_PARTITIONED_INDEX;
 			break;
@@ -8061,6 +8080,7 @@ ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newVa
 	 * column numbers could contain gaps if columns are later dropped.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_INDEX &&
+		rel->rd_rel->relkind != RELKIND_GLOBAL_INDEX &&
 		rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
 		!colName)
 		ereport(ERROR,
@@ -8122,6 +8142,7 @@ ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newVa
 						colName)));
 
 	if (rel->rd_rel->relkind == RELKIND_INDEX ||
+		rel->rd_rel->relkind == RELKIND_GLOBAL_INDEX ||
 		rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
 	{
 		if (attnum > rel->rd_index->indnkeyatts)
@@ -13736,6 +13757,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 			/* ok to change owner */
 			break;
 		case RELKIND_INDEX:
+		case RELKIND_GLOBAL_INDEX:
 			if (!recursing)
 			{
 				/*
@@ -13886,6 +13908,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 		 */
 		if (tuple_class->relkind != RELKIND_COMPOSITE_TYPE &&
 			tuple_class->relkind != RELKIND_INDEX &&
+			tuple_class->relkind != RELKIND_GLOBAL_INDEX &&
 			tuple_class->relkind != RELKIND_PARTITIONED_INDEX &&
 			tuple_class->relkind != RELKIND_TOASTVALUE)
 			changeDependencyOnOwner(RelationRelationId, relationOid,
@@ -14232,6 +14255,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 			(void) view_reloptions(newOptions, true);
 			break;
 		case RELKIND_INDEX:
+		case RELKIND_GLOBAL_INDEX:
 		case RELKIND_PARTITIONED_INDEX:
 			(void) index_reloptions(rel->rd_indam->amoptions, newOptions, true);
 			break;
@@ -14419,7 +14443,8 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	newrlocator.spcOid = newTableSpace;
 
 	/* hand off to AM to actually create new rel storage and copy the data */
-	if (rel->rd_rel->relkind == RELKIND_INDEX)
+	if (rel->rd_rel->relkind == RELKIND_INDEX ||
+		rel->rd_rel->relkind == RELKIND_GLOBAL_INDEX)
 	{
 		index_copy_data(rel, newrlocator);
 	}
@@ -14602,6 +14627,7 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX &&
+			 relForm->relkind != RELKIND_GLOBAL_INDEX &&
 			 relForm->relkind != RELKIND_PARTITIONED_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
 			 relForm->relkind != RELKIND_MATVIEW))
@@ -17103,6 +17129,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 				 errmsg("\"%s\" is not a composite type", rv->relname)));
 
 	if (reltype == OBJECT_INDEX && relkind != RELKIND_INDEX &&
+		relkind != RELKIND_GLOBAL_INDEX &&
 		relkind != RELKIND_PARTITIONED_INDEX
 		&& !IsA(stmt, RenameStmt))
 		ereport(ERROR,
@@ -17125,7 +17152,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 */
 	if (IsA(stmt, AlterObjectSchemaStmt))
 	{
-		if (relkind == RELKIND_INDEX || relkind == RELKIND_PARTITIONED_INDEX)
+		if (relkind == RELKIND_INDEX || relkind == RELKIND_GLOBAL_INDEX || relkind == RELKIND_PARTITIONED_INDEX)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot change schema of index \"%s\"",
@@ -18870,6 +18897,7 @@ RangeVarCallbackForAttachIndex(const RangeVar *rv, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 	if (classform->relkind != RELKIND_PARTITIONED_INDEX &&
+		classform->relkind != RELKIND_GLOBAL_INDEX &&
 		classform->relkind != RELKIND_INDEX)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 9defe37836..5417cafca1 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -990,7 +990,8 @@ estimate_rel_size(Relation rel, int32 *attr_widths,
 		table_relation_estimate_size(rel, attr_widths, pages, tuples,
 									 allvisfrac);
 	}
-	else if (rel->rd_rel->relkind == RELKIND_INDEX)
+	else if (rel->rd_rel->relkind == RELKIND_INDEX ||
+			 rel->rd_rel->relkind == RELKIND_GLOBAL_INDEX)
 	{
 		/*
 		 * XXX: It'd probably be good to move this into a callback, individual
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2dddd8f302..a259079e8c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -491,7 +491,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		unicode_normal_form
 
 %type <boolean> opt_instead
-%type <boolean> opt_unique opt_verbose opt_full
+%type <boolean> opt_unique opt_verbose opt_full opt_global
 %type <boolean> opt_freeze opt_analyze opt_default opt_recheck
 %type <defelt>	opt_binary copy_delimiter
 
@@ -7918,7 +7918,7 @@ defacl_privilege_target:
 
 IndexStmt:	CREATE opt_unique INDEX opt_concurrently opt_single_name
 			ON relation_expr access_method_clause '(' index_params ')'
-			opt_include opt_unique_null_treatment opt_reloptions OptTableSpace where_clause
+			opt_include opt_unique_null_treatment opt_reloptions OptTableSpace where_clause opt_global
 				{
 					IndexStmt *n = makeNode(IndexStmt);
 
@@ -7933,6 +7933,7 @@ IndexStmt:	CREATE opt_unique INDEX opt_concurrently opt_single_name
 					n->options = $14;
 					n->tableSpace = $15;
 					n->whereClause = $16;
+					n->global_index = $17;
 					n->excludeOpNames = NIL;
 					n->idxcomment = NULL;
 					n->indexOid = InvalidOid;
@@ -7950,7 +7951,7 @@ IndexStmt:	CREATE opt_unique INDEX opt_concurrently opt_single_name
 				}
 			| CREATE opt_unique INDEX opt_concurrently IF_P NOT EXISTS name
 			ON relation_expr access_method_clause '(' index_params ')'
-			opt_include opt_unique_null_treatment opt_reloptions OptTableSpace where_clause
+			opt_include opt_unique_null_treatment opt_reloptions OptTableSpace where_clause opt_global
 				{
 					IndexStmt *n = makeNode(IndexStmt);
 
@@ -7965,6 +7966,7 @@ IndexStmt:	CREATE opt_unique INDEX opt_concurrently opt_single_name
 					n->options = $17;
 					n->tableSpace = $18;
 					n->whereClause = $19;
+					n->global_index = $20;
 					n->excludeOpNames = NIL;
 					n->idxcomment = NULL;
 					n->indexOid = InvalidOid;
@@ -7982,6 +7984,11 @@ IndexStmt:	CREATE opt_unique INDEX opt_concurrently opt_single_name
 				}
 		;
 
+opt_global:
+			GLOBAL									{ $$ = true; }
+			| /*EMPTY*/								{ $$ = false; }
+		;
+
 opt_unique:
 			UNIQUE									{ $$ = true; }
 			| /*EMPTY*/								{ $$ = false; }
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 8140e79d8f..358e7df040 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3982,6 +3982,7 @@ transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd)
 							RelationGetRelationName(parentRel))));
 			break;
 		case RELKIND_INDEX:
+		case RELKIND_GLOBAL_INDEX:
 			/* the index must be partitioned */
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
diff --git a/src/backend/utils/adt/amutils.c b/src/backend/utils/adt/amutils.c
index 60fd396f24..faf810da2f 100644
--- a/src/backend/utils/adt/amutils.c
+++ b/src/backend/utils/adt/amutils.c
@@ -176,6 +176,7 @@ indexam_property(FunctionCallInfo fcinfo,
 			PG_RETURN_NULL();
 		rd_rel = (Form_pg_class) GETSTRUCT(tuple);
 		if (rd_rel->relkind != RELKIND_INDEX &&
+			rd_rel->relkind != RELKIND_GLOBAL_INDEX &&
 			rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
 		{
 			ReleaseSysCache(tuple);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index bd6cd4e47b..151df2920c 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -480,6 +480,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 			amoptsfn = NULL;
 			break;
 		case RELKIND_INDEX:
+		case RELKIND_GLOBAL_INDEX:
 		case RELKIND_PARTITIONED_INDEX:
 			amoptsfn = relation->rd_indam->amoptions;
 			break;
@@ -1202,6 +1203,7 @@ retry:
 	 * initialize access method information
 	 */
 	if (relation->rd_rel->relkind == RELKIND_INDEX ||
+		relation->rd_rel->relkind == RELKIND_GLOBAL_INDEX ||
 		relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
 		RelationInitIndexAccessInfo(relation);
 	else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) ||
@@ -2082,6 +2084,7 @@ RelationIdGetRelation(Oid relationId)
 			 * a headache for indexes that reload itself depends on.
 			 */
 			if (rd->rd_rel->relkind == RELKIND_INDEX ||
+				rd->rd_rel->relkind == RELKIND_GLOBAL_INDEX ||
 				rd->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
 				RelationReloadIndexInfo(rd);
 			else
@@ -2222,6 +2225,7 @@ RelationReloadIndexInfo(Relation relation)
 
 	/* Should be called only for invalidated, live indexes */
 	Assert((relation->rd_rel->relkind == RELKIND_INDEX ||
+			relation->rd_rel->relkind == RELKIND_GLOBAL_INDEX ||
 			relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) &&
 		   !relation->rd_isvalid &&
 		   relation->rd_droppedSubid == InvalidSubTransactionId);
@@ -2349,7 +2353,8 @@ RelationReloadNailed(Relation relation)
 	if (!IsTransactionState() || relation->rd_refcnt <= 1)
 		return;
 
-	if (relation->rd_rel->relkind == RELKIND_INDEX)
+	if (relation->rd_rel->relkind == RELKIND_INDEX ||
+		relation->rd_rel->relkind == RELKIND_GLOBAL_INDEX)
 	{
 		/*
 		 * If it's a nailed-but-not-mapped index, then we need to re-read the
@@ -2542,6 +2547,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 	 * index, and we check for pg_index updates too.
 	 */
 	if ((relation->rd_rel->relkind == RELKIND_INDEX ||
+		 relation->rd_rel->relkind == RELKIND_GLOBAL_INDEX ||
 		 relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) &&
 		relation->rd_refcnt > 0 &&
 		relation->rd_indexcxt != NULL)
@@ -3714,7 +3720,8 @@ RelationSetNewRelfilenumber(Relation relation, char persistence)
 		newrelfilenumber = GetNewRelFileNumber(relation->rd_rel->reltablespace,
 											   NULL, persistence);
 	}
-	else if (relation->rd_rel->relkind == RELKIND_INDEX)
+	else if (relation->rd_rel->relkind == RELKIND_INDEX ||
+			 relation->rd_rel->relkind == RELKIND_GLOBAL_INDEX)
 	{
 		if (!OidIsValid(binary_upgrade_next_index_pg_class_relfilenumber))
 			ereport(ERROR,
@@ -6120,7 +6127,8 @@ load_relcache_init_file(bool shared)
 		 * If it's an index, there's more to do.  Note we explicitly ignore
 		 * partitioned indexes here.
 		 */
-		if (rel->rd_rel->relkind == RELKIND_INDEX)
+		if (rel->rd_rel->relkind == RELKIND_INDEX ||
+			rel->rd_rel->relkind == RELKIND_GLOBAL_INDEX)
 		{
 			MemoryContext indexcxt;
 			Oid		   *opfamily;
@@ -6515,7 +6523,8 @@ write_relcache_init_file(bool shared)
 		 * If it's an index, there's more to do. Note we explicitly ignore
 		 * partitioned indexes here.
 		 */
-		if (rel->rd_rel->relkind == RELKIND_INDEX)
+		if (rel->rd_rel->relkind == RELKIND_INDEX ||
+			rel->rd_rel->relkind == RELKIND_GLOBAL_INDEX)
 		{
 			/* write the pg_index tuple */
 			/* we assume this was created by heap_copytuple! */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 2eae519b1d..149281167e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1874,6 +1874,7 @@ describeOneTableDetails(const char *schemaname,
 		attgenerated_col = cols++;
 	}
 	if (tableinfo.relkind == RELKIND_INDEX ||
+		tableinfo.relkind == RELKIND_GLOBAL_INDEX ||
 		tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
 	{
 		if (pset.sversion >= 110000)
@@ -1914,6 +1915,7 @@ describeOneTableDetails(const char *schemaname,
 		/* stats target, if relevant to relkind */
 		if (tableinfo.relkind == RELKIND_RELATION ||
 			tableinfo.relkind == RELKIND_INDEX ||
+			tableinfo.relkind == RELKIND_GLOBAL_INDEX ||
 			tableinfo.relkind == RELKIND_PARTITIONED_INDEX ||
 			tableinfo.relkind == RELKIND_MATVIEW ||
 			tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
@@ -1979,6 +1981,14 @@ describeOneTableDetails(const char *schemaname,
 				printfPQExpBuffer(&title, _("Index \"%s.%s\""),
 								  schemaname, relationname);
 			break;
+		case RELKIND_GLOBAL_INDEX:
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged global index \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Global index \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		case RELKIND_PARTITIONED_INDEX:
 			if (tableinfo.relpersistence == 'u')
 				printfPQExpBuffer(&title, _("Unlogged partitioned index \"%s.%s\""),
@@ -2244,6 +2254,7 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	if (tableinfo.relkind == RELKIND_INDEX ||
+		tableinfo.relkind == RELKIND_GLOBAL_INDEX ||
 		tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
 	{
 		/* Footer information about an index */
@@ -2344,7 +2355,7 @@ describeOneTableDetails(const char *schemaname,
 			/*
 			 * If it's a partitioned index, we'll print the tablespace below
 			 */
-			if (tableinfo.relkind == RELKIND_INDEX)
+			if (tableinfo.relkind == RELKIND_INDEX || tableinfo.relkind == RELKIND_GLOBAL_INDEX)
 				add_tablespace_footer(&cont, tableinfo.relkind,
 									  tableinfo.tablespace, true);
 		}
@@ -3546,6 +3557,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 	if (relkind == RELKIND_RELATION ||
 		relkind == RELKIND_MATVIEW ||
 		relkind == RELKIND_INDEX ||
+		relkind == RELKIND_GLOBAL_INDEX ||
 		relkind == RELKIND_PARTITIONED_TABLE ||
 		relkind == RELKIND_PARTITIONED_INDEX ||
 		relkind == RELKIND_TOASTVALUE)
@@ -3869,6 +3881,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  " WHEN " CppAsString2(RELKIND_VIEW) " THEN '%s'"
 					  " WHEN " CppAsString2(RELKIND_MATVIEW) " THEN '%s'"
 					  " WHEN " CppAsString2(RELKIND_INDEX) " THEN '%s'"
+					  " WHEN " CppAsString2(RELKIND_GLOBAL_INDEX) " THEN '%s'"
 					  " WHEN " CppAsString2(RELKIND_SEQUENCE) " THEN '%s'"
 					  " WHEN " CppAsString2(RELKIND_TOASTVALUE) " THEN '%s'"
 					  " WHEN " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN '%s'"
@@ -3882,6 +3895,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
+					  gettext_noop("global index"),
 					  gettext_noop("sequence"),
 					  gettext_noop("TOAST table"),
 					  gettext_noop("foreign table"),
@@ -3963,6 +3977,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		appendPQExpBufferStr(&buf, CppAsString2(RELKIND_MATVIEW) ",");
 	if (showIndexes)
 		appendPQExpBufferStr(&buf, CppAsString2(RELKIND_INDEX) ","
+							 CppAsString2(RELKIND_GLOBAL_INDEX) ","
 							 CppAsString2(RELKIND_PARTITIONED_INDEX) ",");
 	if (showSeq)
 		appendPQExpBufferStr(&buf, CppAsString2(RELKIND_SEQUENCE) ",");
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 91c28868d4..1ddfc9af21 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -65,6 +65,7 @@ extern void index_check_primary_key(Relation heapRel,
 #define	INDEX_CREATE_IF_NOT_EXISTS			(1 << 4)
 #define	INDEX_CREATE_PARTITIONED			(1 << 5)
 #define INDEX_CREATE_INVALID				(1 << 6)
+#define INDEX_CREATE_GLOBAL					(1 << 7)
 
 extern Oid	index_create(Relation heapRelation,
 						 const char *indexRelationName,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e1f4eefa22..eb190e8f14 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELKIND_FOREIGN_TABLE   'f'	/* foreign table */
 #define		  RELKIND_PARTITIONED_TABLE 'p' /* partitioned table */
 #define		  RELKIND_PARTITIONED_INDEX 'I' /* partitioned index */
+#define		  RELKIND_GLOBAL_INDEX 		'g' /* global index */
 
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
@@ -194,6 +195,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define RELKIND_HAS_STORAGE(relkind) \
 	((relkind) == RELKIND_RELATION || \
 	 (relkind) == RELKIND_INDEX || \
+	 (relkind) == RELKIND_GLOBAL_INDEX || \
 	 (relkind) == RELKIND_SEQUENCE || \
 	 (relkind) == RELKIND_TOASTVALUE || \
 	 (relkind) == RELKIND_MATVIEW)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7caff62af7..880dd6011a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2991,6 +2991,7 @@ typedef struct IndexStmt
 	bool		if_not_exists;	/* just do nothing if index already exists? */
 	bool		reset_default_tblspc;	/* reset default_tablespace prior to
 										 * executing */
+	bool		global_index;	/* true if index is global */
 } IndexStmt;
 
 /* ----------------------
diff --git a/src/test/regress/expected/indexing.out b/src/test/regress/expected/indexing.out
index 1bdd430f06..eed20063c1 100644
--- a/src/test/regress/expected/indexing.out
+++ b/src/test/regress/expected/indexing.out
@@ -1421,3 +1421,43 @@ Indexes:
     "parted_index_col_drop11_b_idx" btree (b)
 
 drop table parted_index_col_drop;
+-- create global index using non-partition key
+create table gidxpart (a int, b int, c text) partition by range (a);
+create table gidxpart1 partition of gidxpart for values from (0) to (10);
+create table gidxpart2 partition of gidxpart for values from (10) to (100);
+create unique index gidx_u on gidxpart using btree(b) global;
+select relname, relhasindex, relkind from pg_class where relname like '%gidx%' order by oid;
+     relname     | relhasindex | relkind 
+-----------------+-------------+---------
+ gidxpart        | t           | p
+ gidxpart1       | t           | r
+ gidxpart2       | t           | r
+ gidx_u          | f           | I
+ gidxpart1_b_idx | f           | g
+ gidxpart2_b_idx | f           | g
+(6 rows)
+
+\d+ gidxpart
+                            Partitioned table "public.gidxpart"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a      | integer |           |          |         | plain    |              | 
+ b      | integer |           |          |         | plain    |              | 
+ c      | text    |           |          |         | extended |              | 
+Partition key: RANGE (a)
+Indexes:
+    "gidx_u" UNIQUE, btree (b)
+Partitions: gidxpart1 FOR VALUES FROM (0) TO (10),
+            gidxpart2 FOR VALUES FROM (10) TO (100)
+
+\d+ gidx_u
+               Partitioned index "public.gidx_u"
+ Column |  Type   | Key? | Definition | Storage | Stats target 
+--------+---------+------+------------+---------+--------------
+ b      | integer | yes  | b          | plain   | 
+unique, btree, for table "public.gidxpart"
+Partitions: gidxpart1_b_idx,
+            gidxpart2_b_idx
+
+drop index gidx_u;
+drop table gidxpart;
diff --git a/src/test/regress/sql/indexing.sql b/src/test/regress/sql/indexing.sql
index 429120e710..2169f28e69 100644
--- a/src/test/regress/sql/indexing.sql
+++ b/src/test/regress/sql/indexing.sql
@@ -760,3 +760,14 @@ alter table parted_index_col_drop drop column c;
 \d parted_index_col_drop2
 \d parted_index_col_drop11
 drop table parted_index_col_drop;
+
+-- create global index using non-partition key
+create table gidxpart (a int, b int, c text) partition by range (a);
+create table gidxpart1 partition of gidxpart for values from (0) to (10);
+create table gidxpart2 partition of gidxpart for values from (10) to (100);
+create unique index gidx_u on gidxpart using btree(b) global;
+select relname, relhasindex, relkind from pg_class where relname like '%gidx%' order by oid;
+\d+ gidxpart
+\d+ gidx_u
+drop index gidx_u;
+drop table gidxpart;
-- 
2.17.1

