From 6e4a5ba43b5cc21872d8c967dc07b9eae9bf83f7 Mon Sep 17 00:00:00 2001
From: David Geier <geidav.pg@gmail.com>
Date: Thu, 3 Sep 2026 15:25:09 +0200
Subject: [PATCH v3 8/8] Remove rd_index

---
 contrib/amcheck/verify_common.c             |    2 +-
 contrib/amcheck/verify_nbtree.c             |    2 +-
 contrib/pgstattuple/pgstatindex.c           |    6 +-
 contrib/pgstattuple/pgstattuple.c           |    2 +-
 src/backend/access/brin/brin.c              |    4 +-
 src/backend/access/common/toast_internals.c |    6 +-
 src/backend/access/gin/ginfast.c            |    2 +-
 src/backend/access/heap/heapam.c            |    4 +-
 src/backend/access/index/genam.c            |    6 +-
 src/backend/access/index/indexam.c          |    2 +-
 src/backend/access/nbtree/nbtutils.c        |    2 +-
 src/backend/access/spgist/spgutils.c        |   12 +-
 src/backend/catalog/catalog.c               |    4 +-
 src/backend/catalog/index.c                 |   12 +-
 src/backend/catalog/indexing.c              |    4 +-
 src/backend/commands/indexcmds.c            |   14 +-
 src/backend/commands/matview.c              |    4 +-
 src/backend/commands/propgraphcmds.c        | 1959 +++++++++++++++++++
 src/backend/commands/repack.c               |   20 +-
 src/backend/commands/tablecmds.c            |   44 +-
 src/backend/commands/vacuum.c               |    2 +-
 src/backend/executor/execIndexing.c         |   16 +-
 src/backend/executor/execPartition.c        |    4 +-
 src/backend/executor/execReplication.c      |    2 +-
 src/backend/optimizer/util/plancat.c        |   10 +-
 src/backend/parser/parse_utilcmd.c          |    2 +-
 src/backend/replication/logical/conflict.c  |    2 +-
 src/backend/replication/logical/relation.c  |    8 +-
 src/backend/statistics/stat_utils.c         |    4 +-
 src/backend/storage/lmgr/predicate.c        |    8 +-
 src/backend/utils/adt/ri_triggers.c         |   11 +-
 src/backend/utils/cache/catcache.c          |    4 +-
 src/backend/utils/cache/relcache.c          |   47 +-
 src/include/utils/rel.h                     |   22 +-
 src/include/utils/rel_internal.h            |    1 -
 35 files changed, 2114 insertions(+), 140 deletions(-)
 create mode 100644 src/backend/commands/propgraphcmds.c

diff --git a/contrib/amcheck/verify_common.c b/contrib/amcheck/verify_common.c
index 46e53b3f99a..5e7228287ba 100644
--- a/contrib/amcheck/verify_common.c
+++ b/contrib/amcheck/verify_common.c
@@ -181,7 +181,7 @@ index_checkable(Relation rel, Oid am_id)
 				 errdetail("Index \"%s\" is associated with temporary relation.",
 						   RelationGetRelationName(rel))));
 
-	if (!rel->rd_index->indisvalid)
+	if (!RelationGetIndex(rel)->indisvalid)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("cannot check index \"%s\"",
diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index c5e1f901098..d7b7c6d9807 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -456,7 +456,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
 		 * horizon.  Throwing an error here should be very rare.  It doesn't
 		 * seem worth using a secondary snapshot to avoid this.
 		 */
-		if (IsolationUsesXactSnapshot() && rel->rd_index->indcheckxmin &&
+		if (IsolationUsesXactSnapshot() && RelationGetIndex(rel)->indcheckxmin &&
 			!TransactionIdPrecedes(HeapTupleHeaderGetXmin(rel->rd_indextuple->t_data),
 								   state->snapshot->xmin))
 			ereport(ERROR,
diff --git a/contrib/pgstattuple/pgstatindex.c b/contrib/pgstattuple/pgstatindex.c
index 8951ad0aac4..ae0fba426f3 100644
--- a/contrib/pgstattuple/pgstatindex.c
+++ b/contrib/pgstattuple/pgstatindex.c
@@ -244,7 +244,7 @@ pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo)
 	 * but the results could be confusing.  For example, the index's size
 	 * could be too low for a valid index of the table.
 	 */
-	if (!rel->rd_index->indisvalid)
+	if (!RelationGetIndex(rel)->indisvalid)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("index \"%s\" is not valid",
@@ -558,7 +558,7 @@ pgstatginindex_internal(Oid relid, FunctionCallInfo fcinfo)
 				 errmsg("cannot access temporary indexes of other sessions")));
 
 	/* see pgstatindex_impl */
-	if (!rel->rd_index->indisvalid)
+	if (!RelationGetIndex(rel)->indisvalid)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("index \"%s\" is not valid",
@@ -648,7 +648,7 @@ pgstathashindex(PG_FUNCTION_ARGS)
 				 errmsg("cannot access temporary indexes of other sessions")));
 
 	/* see pgstatindex_impl */
-	if (!rel->rd_index->indisvalid)
+	if (!RelationGetIndex(rel)->indisvalid)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("index \"%s\" is not valid",
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 6a7f8cb4a7c..89116b52250 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -262,7 +262,7 @@ pgstat_relation(Relation rel, FunctionCallInfo fcinfo)
 	else if (rel->rd_rel->relkind == RELKIND_INDEX)
 	{
 		/* see pgstatindex_impl */
-		if (!rel->rd_index->indisvalid)
+		if (!RelationGetIndex(rel)->indisvalid)
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("index \"%s\" is not valid",
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 5059da8dce7..8eb25c1e175 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1469,7 +1469,7 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* see gin_clean_pending_list() */
-	if (indexRel->rd_index->indisvalid)
+	if (RelationGetIndex(indexRel)->indisvalid)
 		brinsummarize(indexRel, heapRel, heapBlk, true, &numSummarized, NULL);
 	else
 		ereport(DEBUG1,
@@ -1558,7 +1558,7 @@ brin_desummarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* see gin_clean_pending_list() */
-	if (indexRel->rd_index->indisvalid)
+	if (RelationGetIndex(indexRel)->indisvalid)
 	{
 		/* the revmap does the hard work */
 		do
diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c
index 2bdc0b7d3f2..6adc0922d3a 100644
--- a/src/backend/access/common/toast_internals.c
+++ b/src/backend/access/common/toast_internals.c
@@ -370,11 +370,11 @@ toast_save_datum(Relation rel, Datum value,
 		for (int i = 0; i < num_indexes; i++)
 		{
 			/* Only index relations marked as ready can be updated */
-			if (toastidxs[i]->rd_index->indisready)
+			if (RelationGetIndex(toastidxs[i])->indisready)
 				index_insert(toastidxs[i], t_values, t_isnull,
 							 &(toasttup->t_self),
 							 toastrel,
-							 toastidxs[i]->rd_index->indisunique ?
+							 RelationGetIndex(toastidxs[i])->indisunique ?
 							 UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
 							 false, NULL);
 		}
@@ -662,7 +662,7 @@ toast_open_indexes(Relation toastrel,
 	{
 		Relation	toastidx = (*toastidxs)[i];
 
-		if (toastidx->rd_index->indisvalid)
+		if (RelationGetIndex(toastidx)->indisvalid)
 		{
 			res = i;
 			found = true;
diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index 46fc60115a8..df5e3545a19 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -1075,7 +1075,7 @@ gin_clean_pending_list(PG_FUNCTION_ARGS)
 	 * is merely awaiting missed aminsert calls, we're capable of processing
 	 * it.  Decline to do so, out of an abundance of caution.
 	 */
-	if (indexRel->rd_index->indisvalid)
+	if (RelationGetIndex(indexRel)->indisvalid)
 	{
 		GinState	ginstate;
 
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 8084bc7e5e8..4560b7090be 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -4425,7 +4425,7 @@ check_lock_if_inplace_updateable_rel(Relation relation,
 				{
 					Relation	irel = index_open(relid, AccessShareLock);
 
-					SET_LOCKTAG_RELATION(tag, dbid, irel->rd_index->indrelid);
+					SET_LOCKTAG_RELATION(tag, dbid, RelationGetIndex(irel)->indrelid);
 					index_close(irel, AccessShareLock);
 				}
 				else
@@ -4479,7 +4479,7 @@ check_inplace_rel_lock(HeapTuple oldtup)
 	{
 		Relation	irel = index_open(relid, AccessShareLock);
 
-		SET_LOCKTAG_RELATION(tag, dbid, irel->rd_index->indrelid);
+		SET_LOCKTAG_RELATION(tag, dbid, RelationGetIndex(irel)->indrelid);
 		index_close(irel, AccessShareLock);
 	}
 	else
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 98ac5bc79c9..6879a7678bf 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -211,7 +211,7 @@ BuildIndexValueDescription(Relation indexRelation,
 	 * Next we need to check table-level SELECT access and then, if there is
 	 * no access there, check column-level permissions.
 	 */
-	idxrec = indexRelation->rd_index;
+	idxrec = RelationGetIndex(indexRelation);
 	indrelid = idxrec->indrelid;
 	Assert(indexrelid == idxrec->indexrelid);
 
@@ -456,7 +456,7 @@ systable_beginscan(Relation heapRelation,
 
 			for (j = 0; j < IndexRelationGetNumberOfAttributes(irel); j++)
 			{
-				if (key[i].sk_attno == irel->rd_index->indkey.values[j])
+				if (key[i].sk_attno == RelationGetIndex(irel)->indkey.values[j])
 				{
 					idxkey[i].sk_attno = j + 1;
 					break;
@@ -711,7 +711,7 @@ systable_beginscan_ordered(Relation heapRelation,
 
 		for (j = 0; j < IndexRelationGetNumberOfAttributes(indexRelation); j++)
 		{
-			if (key[i].sk_attno == indexRelation->rd_index->indkey.values[j])
+			if (key[i].sk_attno == RelationGetIndex(indexRelation)->indkey.values[j])
 			{
 				idxkey[i].sk_attno = j + 1;
 				break;
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index 04c97f0c7b2..d3ca10f5617 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -361,7 +361,7 @@ index_beginscan_internal(Relation indexRelation, Relation heapRelation,
 		 */
 		if (index_only_scan)
 		{
-			int			indnkeyatts = indexRelation->rd_index->indnkeyatts;
+			int			indnkeyatts = RelationGetIndex(indexRelation)->indnkeyatts;
 			int			namecount = 0;
 
 			for (int attnum = 0; attnum < indnkeyatts; attnum++)
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index b3032b1f1de..474a6e5bea2 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -141,7 +141,7 @@ _bt_mkscankey(Relation rel, IndexTuple itup)
 	 * In NULLS NOT DISTINCT mode, we pretend that there are no null keys, so
 	 * that full uniqueness check is done.
 	 */
-	if (rel->rd_index->indnullsnotdistinct)
+	if (RelationGetIndex(rel)->indnullsnotdistinct)
 		key->anynullkeys = false;
 
 	return key;
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index f2ee333f60d..90a3380a7a0 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -126,14 +126,14 @@ GetIndexInputType(Relation index, AttrNumber indexcol)
 	List	   *indexprs;
 	ListCell   *indexpr_item;
 
-	Assert(index->rd_index != NULL);
-	Assert(indexcol > 0 && indexcol <= index->rd_index->indnkeyatts);
+	Assert(RelationGetIndex(index) != NULL);
+	Assert(indexcol > 0 && indexcol <= RelationGetIndex(index)->indnkeyatts);
 	opcintype = index->rd_opcintype[indexcol - 1];
 	if (!IsPolymorphicType(opcintype))
 		return opcintype;
-	heapcol = index->rd_index->indkey.values[indexcol - 1];
+	heapcol = RelationGetIndex(index)->indkey.values[indexcol - 1];
 	if (heapcol != 0)			/* Simple index column? */
-		return getBaseType(get_atttype(index->rd_index->indrelid, heapcol));
+		return getBaseType(get_atttype(RelationGetIndex(index)->indrelid, heapcol));
 
 	/*
 	 * If the index expressions are already cached, skip calling
@@ -146,9 +146,9 @@ GetIndexInputType(Relation index, AttrNumber indexcol)
 	else
 		indexprs = RelationGetIndexExpressions(index);
 	indexpr_item = list_head(indexprs);
-	for (int i = 1; i <= index->rd_index->indnkeyatts; i++)
+	for (int i = 1; i <= RelationGetIndex(index)->indnkeyatts; i++)
 	{
-		if (index->rd_index->indkey.values[i - 1] == 0)
+		if (RelationGetIndex(index)->indkey.values[i - 1] == 0)
 		{
 			/* expression column */
 			if (indexpr_item == NULL)
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index cf9b88b3e25..a38c398bd66 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -697,7 +697,7 @@ pg_nextoid(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("pg_nextoid() can only be used on system catalogs")));
 
-	if (idx->rd_index->indrelid != RelationGetRelid(rel))
+	if (RelationGetIndex(idx)->indrelid != RelationGetRelid(rel))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("index \"%s\" does not belong to table \"%s\"",
@@ -721,7 +721,7 @@ pg_nextoid(PG_FUNCTION_ARGS)
 						NameStr(*attname), "oid")));
 
 	if (IndexRelationGetNumberOfKeyAttributes(idx) != 1 ||
-		idx->rd_index->indkey.values[0] != attno)
+		RelationGetIndex(idx)->indkey.values[0] != attno)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("index \"%s\" is not the index for column \"%s\"",
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index efe35388a75..f4fde091517 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1250,7 +1250,7 @@ index_create(Relation heapRelation,
 	else
 		Assert(indexRelation->rd_indexcxt != NULL);
 
-	indexRelation->rd_index->indnkeyatts = indexInfo->ii_NumIndexKeyAttrs;
+	RelationGetIndex(indexRelation)->indnkeyatts = indexInfo->ii_NumIndexKeyAttrs;
 
 	/* Validate opclass-specific options */
 	if (opclassOptions)
@@ -1420,7 +1420,7 @@ index_create_copy(Relation heapRelation, uint16 flags,
 							oldInfo->ii_WithoutOverlaps);
 
 	/* fetch exclusion constraint info if any */
-	if (indexRelation->rd_index->indisexclusion)
+	if (RelationGetIndex(indexRelation)->indisexclusion)
 	{
 		/*
 		 * XXX Beware: we're making newInfo point to oldInfo-owned memory.  It
@@ -2461,7 +2461,7 @@ IndexInfo *
 BuildIndexInfo(Relation index)
 {
 	IndexInfo  *ii;
-	Form_pg_index indexStruct = index->rd_index;
+	Form_pg_index indexStruct = RelationGetIndex(index);
 	int			i;
 	int			numAtts;
 
@@ -2521,7 +2521,7 @@ IndexInfo *
 BuildDummyIndexInfo(Relation index)
 {
 	IndexInfo  *ii;
-	Form_pg_index indexStruct = index->rd_index;
+	Form_pg_index indexStruct = RelationGetIndex(index);
 	int			i;
 	int			numAtts;
 
@@ -2775,8 +2775,8 @@ BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii)
 bool
 IsIndexCompatibleAsArbiter(Relation indexRel1, Relation indexRel2)
 {
-	Form_pg_index indexForm1 = indexRel1->rd_index;
-	Form_pg_index indexForm2 = indexRel2->rd_index;
+	Form_pg_index indexForm1 = RelationGetIndex(indexRel1);
+	Form_pg_index indexForm2 = RelationGetIndex(indexRel2);
 
 	/* Only indexes of the same relation can be compared. */
 	Assert(indexForm1->indrelid == indexForm2->indrelid);
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index fd7d2ec0e3a..bfaec5bf2d7 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -135,7 +135,7 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
 		Assert(indexInfo->ii_Expressions == NIL);
 		Assert(indexInfo->ii_Predicate == NIL);
 		Assert(indexInfo->ii_ExclusionOps == NULL);
-		Assert(index->rd_index->indimmediate);
+		Assert(RelationGetIndex(index)->indimmediate);
 		Assert(indexInfo->ii_NumIndexKeyAttrs != 0);
 
 		/* see earlier check above */
@@ -172,7 +172,7 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
 					 isnull,	/* is-null flags */
 					 &(heapTuple->t_self),	/* tid of heap tuple */
 					 heapRelation,
-					 index->rd_index->indisunique ?
+					 RelationGetIndex(index)->indisunique ?
 					 UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
 					 false,
 					 indexInfo);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index b8a0c908637..763a37db586 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1496,7 +1496,7 @@ DefineIndex(ParseState *pstate,
 														  createdConstraintId,
 														  childRelid);
 
-						if (!cldidx->rd_index->indisvalid)
+						if (!RelationGetIndex(cldidx)->indisvalid)
 							invalidate_parent = true;
 
 						found = true;
@@ -3858,14 +3858,14 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 					Relation	indexRelation = index_open(cellOid,
 														   ShareUpdateExclusiveLock);
 
-					if (!indexRelation->rd_index->indisvalid)
+					if (!RelationGetIndex(indexRelation)->indisvalid)
 						ereport(WARNING,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 								 errmsg("skipping reindex of invalid index \"%s.%s\"",
 										get_namespace_name(get_rel_namespace(cellOid)),
 										get_rel_name(cellOid)),
 								 errhint("Use DROP INDEX or REINDEX INDEX.")));
-					else if (indexRelation->rd_index->indisexclusion)
+					else if (RelationGetIndex(indexRelation)->indisexclusion)
 						ereport(WARNING,
 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								 errmsg("cannot reindex exclusion constraint index \"%s.%s\" concurrently, skipping",
@@ -3911,7 +3911,7 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 						Relation	indexRelation = index_open(cellOid,
 															   ShareUpdateExclusiveLock);
 
-						if (!indexRelation->rd_index->indisvalid)
+						if (!RelationGetIndex(indexRelation)->indisvalid)
 							ereport(WARNING,
 									(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 									 errmsg("skipping reindex of invalid index \"%s.%s\"",
@@ -4088,7 +4088,7 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			tablespaceid;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
-		heapRel = table_open(indexRel->rd_index->indrelid,
+		heapRel = table_open(RelationGetIndex(indexRel)->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
@@ -4132,7 +4132,7 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		concurrentName = ChooseRelationName(get_rel_name(idx->indexId),
 											NULL,
 											"ccnew",
-											get_rel_namespace(indexRel->rd_index->indrelid),
+											get_rel_namespace(RelationGetIndex(indexRel)->indrelid),
 											false);
 
 		/* Choose the new tablespace, indexes of toast tables are not moved */
@@ -4736,7 +4736,7 @@ IndexSetParentIndex(Relation partitionIdx, Oid parentOid)
 			ObjectAddressSet(partIdx, RelationRelationId, partRelid);
 			ObjectAddressSet(parentIdx, RelationRelationId, parentOid);
 			ObjectAddressSet(partitionTbl, RelationRelationId,
-							 partitionIdx->rd_index->indrelid);
+							 RelationGetIndex(partitionIdx)->indrelid);
 			recordDependencyOn(&partIdx, &parentIdx,
 							   DEPENDENCY_PARTITION_PRI);
 			recordDependencyOn(&partIdx, &partitionTbl,
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 40748958eaf..a949482d99c 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -727,7 +727,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 		indexRel = index_open(indexoid, RowExclusiveLock);
 		if (is_usable_unique_index(indexRel))
 		{
-			Form_pg_index indexStruct = indexRel->rd_index;
+			Form_pg_index indexStruct = RelationGetIndex(indexRel);
 			int			indnkeyatts = indexStruct->indnkeyatts;
 			oidvector  *indclass;
 			Datum		indclassDatum;
@@ -904,7 +904,7 @@ refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence)
 static bool
 is_usable_unique_index(Relation indexRel)
 {
-	Form_pg_index indexStruct = indexRel->rd_index;
+	Form_pg_index indexStruct = RelationGetIndex(indexRel);
 
 	/*
 	 * Must be unique, valid, immediate, non-partial, and be defined over
diff --git a/src/backend/commands/propgraphcmds.c b/src/backend/commands/propgraphcmds.c
new file mode 100644
index 00000000000..cabcc90d4f3
--- /dev/null
+++ b/src/backend/commands/propgraphcmds.c
@@ -0,0 +1,1959 @@
+/*-------------------------------------------------------------------------
+ *
+ * propgraphcmds.c
+ *	  property graph manipulation
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/commands/propgraphcmds.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/table.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_collation_d.h"
+#include "catalog/pg_operator_d.h"
+#include "catalog/pg_propgraph_element.h"
+#include "catalog/pg_propgraph_element_label.h"
+#include "catalog/pg_propgraph_label.h"
+#include "catalog/pg_propgraph_label_property.h"
+#include "catalog/pg_propgraph_property.h"
+#include "commands/defrem.h"
+#include "commands/propgraphcmds.h"
+#include "commands/tablecmds.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_oper.h"
+#include "parser/parse_relation.h"
+#include "parser/parse_target.h"
+#include "utils/acl.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+
+struct element_info
+{
+	Oid			elementid;
+	char		kind;
+	Oid			relid;
+	char	   *aliasname;
+	ArrayType  *key;
+
+	char	   *srcvertex;
+	Oid			srcvertexid;
+	Oid			srcrelid;
+	ArrayType  *srckey;
+	ArrayType  *srcref;
+	ArrayType  *srceqop;
+
+	char	   *destvertex;
+	Oid			destvertexid;
+	Oid			destrelid;
+	ArrayType  *destkey;
+	ArrayType  *destref;
+	ArrayType  *desteqop;
+
+	List	   *labels;
+};
+
+
+static ArrayType *propgraph_element_get_key(ParseState *pstate, const List *key_clause, Relation element_rel,
+											const char *aliasname, int location);
+static void propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List *refcols,
+										Relation edge_rel, Relation ref_rel,
+										const char *aliasname, int location, const char *type,
+										ArrayType **outkey, ArrayType **outref, ArrayType **outeqop);
+static AttrNumber *array_from_column_list(ParseState *pstate, const List *colnames, int location, Relation element_rel);
+static ArrayType *array_from_attnums(int numattrs, const AttrNumber *attnums);
+static Oid	insert_element_record(ObjectAddress pgaddress, struct element_info *einfo);
+static Oid	insert_label_record(Oid graphid, Oid peoid, const char *label);
+static void insert_property_records(Oid graphid, Oid ellabeloid, Oid pgerelid, const PropGraphProperties *properties);
+static void insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *propname, const Expr *expr);
+static void check_element_properties(Oid peoid);
+static void check_element_label_properties(Oid ellabeloid);
+static void check_all_labels_properties(Oid pgrelid);
+static Oid	get_vertex_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location);
+static Oid	get_edge_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location);
+static Oid	get_element_relid(Oid peid);
+static List *get_graph_label_ids(Oid graphid);
+static List *get_label_element_label_ids(Oid labelid);
+static List *get_element_label_property_names(Oid ellabeloid);
+static List *get_graph_property_ids(Oid graphid);
+
+
+/*
+ * CREATE PROPERTY GRAPH
+ */
+ObjectAddress
+CreatePropGraph(ParseState *pstate, const CreatePropGraphStmt *stmt)
+{
+	CreateStmt *cstmt = makeNode(CreateStmt);
+	char		components_persistence;
+	ListCell   *lc;
+	ObjectAddress pgaddress;
+	List	   *vertex_infos = NIL;
+	List	   *edge_infos = NIL;
+	List	   *element_aliases = NIL;
+	List	   *element_oids = NIL;
+
+	if (stmt->pgname->relpersistence == RELPERSISTENCE_UNLOGGED)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("property graphs cannot be unlogged because they do not have storage")));
+
+	components_persistence = RELPERSISTENCE_PERMANENT;
+
+	foreach(lc, stmt->vertex_tables)
+	{
+		PropGraphVertex *vertex = lfirst_node(PropGraphVertex, lc);
+		struct element_info *vinfo;
+		Relation	rel;
+
+		vinfo = palloc0_object(struct element_info);
+		vinfo->kind = PGEKIND_VERTEX;
+
+		vinfo->relid = RangeVarGetRelidExtended(vertex->vtable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL);
+
+		rel = table_open(vinfo->relid, NoLock);
+
+		if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+			components_persistence = RELPERSISTENCE_TEMP;
+
+		if (vertex->vtable->alias)
+			vinfo->aliasname = vertex->vtable->alias->aliasname;
+		else
+			vinfo->aliasname = vertex->vtable->relname;
+
+		if (list_member(element_aliases, makeString(vinfo->aliasname)))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_TABLE),
+					 errmsg("alias \"%s\" used more than once as element table", vinfo->aliasname),
+					 parser_errposition(pstate, vertex->location)));
+
+		vinfo->key = propgraph_element_get_key(pstate, vertex->vkey, rel, vinfo->aliasname, vertex->location);
+
+		vinfo->labels = vertex->labels;
+
+		table_close(rel, NoLock);
+
+		vertex_infos = lappend(vertex_infos, vinfo);
+
+		element_aliases = lappend(element_aliases, makeString(vinfo->aliasname));
+	}
+
+	foreach(lc, stmt->edge_tables)
+	{
+		PropGraphEdge *edge = lfirst_node(PropGraphEdge, lc);
+		struct element_info *einfo;
+		Relation	rel;
+		ListCell   *lc2;
+		Oid			srcrelid;
+		Oid			destrelid;
+		Relation	srcrel;
+		Relation	destrel;
+
+		einfo = palloc0_object(struct element_info);
+		einfo->kind = PGEKIND_EDGE;
+
+		einfo->relid = RangeVarGetRelidExtended(edge->etable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL);
+
+		rel = table_open(einfo->relid, NoLock);
+
+		if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+			components_persistence = RELPERSISTENCE_TEMP;
+
+		if (edge->etable->alias)
+			einfo->aliasname = edge->etable->alias->aliasname;
+		else
+			einfo->aliasname = edge->etable->relname;
+
+		if (list_member(element_aliases, makeString(einfo->aliasname)))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_TABLE),
+					 errmsg("alias \"%s\" used more than once as element table", einfo->aliasname),
+					 parser_errposition(pstate, edge->location)));
+
+		einfo->key = propgraph_element_get_key(pstate, edge->ekey, rel, einfo->aliasname, edge->location);
+
+		einfo->srcvertex = edge->esrcvertex;
+		einfo->destvertex = edge->edestvertex;
+
+		srcrelid = 0;
+		destrelid = 0;
+		foreach(lc2, vertex_infos)
+		{
+			struct element_info *vinfo = lfirst(lc2);
+
+			if (strcmp(vinfo->aliasname, edge->esrcvertex) == 0)
+				srcrelid = vinfo->relid;
+
+			if (strcmp(vinfo->aliasname, edge->edestvertex) == 0)
+				destrelid = vinfo->relid;
+
+			if (srcrelid && destrelid)
+				break;
+		}
+		if (!srcrelid)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("source vertex \"%s\" of edge \"%s\" does not exist",
+							edge->esrcvertex, einfo->aliasname),
+					 parser_errposition(pstate, edge->location)));
+		if (!destrelid)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("destination vertex \"%s\" of edge \"%s\" does not exist",
+							edge->edestvertex, einfo->aliasname),
+					 parser_errposition(pstate, edge->location)));
+
+		srcrel = table_open(srcrelid, NoLock);
+		destrel = table_open(destrelid, NoLock);
+
+		propgraph_edge_get_ref_keys(pstate, edge->esrckey, edge->esrcvertexcols, rel, srcrel,
+									einfo->aliasname, edge->location, "SOURCE",
+									&einfo->srckey, &einfo->srcref, &einfo->srceqop);
+		propgraph_edge_get_ref_keys(pstate, edge->edestkey, edge->edestvertexcols, rel, destrel,
+									einfo->aliasname, edge->location, "DESTINATION",
+									&einfo->destkey, &einfo->destref, &einfo->desteqop);
+
+		einfo->labels = edge->labels;
+
+		table_close(destrel, NoLock);
+		table_close(srcrel, NoLock);
+
+		table_close(rel, NoLock);
+
+		edge_infos = lappend(edge_infos, einfo);
+
+		element_aliases = lappend(element_aliases, makeString(einfo->aliasname));
+	}
+
+	cstmt->relation = stmt->pgname;
+	cstmt->oncommit = ONCOMMIT_NOOP;
+
+	/*
+	 * Automatically make it temporary if any component tables are temporary
+	 * (see also DefineView()).
+	 */
+	if (stmt->pgname->relpersistence == RELPERSISTENCE_PERMANENT
+		&& components_persistence == RELPERSISTENCE_TEMP)
+	{
+		cstmt->relation = copyObject(cstmt->relation);
+		cstmt->relation->relpersistence = RELPERSISTENCE_TEMP;
+		ereport(NOTICE,
+				(errmsg("property graph \"%s\" will be temporary",
+						stmt->pgname->relname)));
+	}
+
+	pgaddress = DefineRelation(cstmt, RELKIND_PROPGRAPH, InvalidOid, NULL, NULL);
+
+	foreach(lc, vertex_infos)
+	{
+		struct element_info *vinfo = lfirst(lc);
+		Oid			peoid;
+
+		peoid = insert_element_record(pgaddress, vinfo);
+		element_oids = lappend_oid(element_oids, peoid);
+	}
+
+	foreach(lc, edge_infos)
+	{
+		struct element_info *einfo = lfirst(lc);
+		Oid			peoid;
+		ListCell   *lc2;
+
+		/*
+		 * Look up the vertices again.  Now the vertices have OIDs assigned,
+		 * which we need.
+		 */
+		foreach(lc2, vertex_infos)
+		{
+			struct element_info *vinfo = lfirst(lc2);
+
+			if (strcmp(vinfo->aliasname, einfo->srcvertex) == 0)
+			{
+				einfo->srcvertexid = vinfo->elementid;
+				einfo->srcrelid = vinfo->relid;
+			}
+			if (strcmp(vinfo->aliasname, einfo->destvertex) == 0)
+			{
+				einfo->destvertexid = vinfo->elementid;
+				einfo->destrelid = vinfo->relid;
+			}
+			if (einfo->srcvertexid && einfo->destvertexid)
+				break;
+		}
+		Assert(einfo->srcvertexid);
+		Assert(einfo->destvertexid);
+		Assert(einfo->srcrelid);
+		Assert(einfo->destrelid);
+		peoid = insert_element_record(pgaddress, einfo);
+		element_oids = lappend_oid(element_oids, peoid);
+	}
+
+	CommandCounterIncrement();
+
+	foreach_oid(peoid, element_oids)
+		check_element_properties(peoid);
+	check_all_labels_properties(pgaddress.objectId);
+
+	return pgaddress;
+}
+
+/*
+ * Process the key clause specified for an element.  If key_clause is non-NIL,
+ * then it is a list of column names.  Otherwise, the primary key of the
+ * relation is used.  The return value is an array of column numbers.
+ */
+static ArrayType *
+propgraph_element_get_key(ParseState *pstate, const List *key_clause, Relation element_rel, const char *aliasname, int location)
+{
+	ArrayType  *a;
+
+	if (key_clause == NIL)
+	{
+		Oid			pkidx = RelationGetPrimaryKeyIndex(element_rel, false);
+
+		if (!pkidx)
+			ereport(ERROR,
+					errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					errmsg("no key specified and no suitable primary key exists for definition of element \"%s\"", aliasname),
+					parser_errposition(pstate, location));
+		else
+		{
+			Relation	indexDesc;
+
+			indexDesc = index_open(pkidx, AccessShareLock);
+			a = array_from_attnums(IndexRelationGetNumberOfKeyAttributes(indexDesc),
+								   RelationGetIndex(indexDesc)->indkey.values);
+			index_close(indexDesc, NoLock);
+		}
+	}
+	else
+	{
+		a = array_from_attnums(list_length(key_clause),
+							   array_from_column_list(pstate, key_clause, location, element_rel));
+	}
+
+	return a;
+}
+
+/*
+ * Process the source or destination link of an edge.
+ *
+ * keycols and refcols are column names representing the local and referenced
+ * (vertex) columns.  If they are both NIL, a matching foreign key is looked
+ * up.
+ *
+ * edge_rel and ref_rel are the local and referenced element tables.
+ *
+ * aliasname, location, and type are for error messages.  type is either
+ * "SOURCE" or "DESTINATION".
+ *
+ * The outputs are arrays of column numbers in outkey and outref.
+ */
+static void
+propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List *refcols,
+							Relation edge_rel, Relation ref_rel,
+							const char *aliasname, int location, const char *type,
+							ArrayType **outkey, ArrayType **outref, ArrayType **outeqop)
+{
+	int			nkeys;
+	AttrNumber *keyattnums;
+	AttrNumber *refattnums;
+	Oid		   *keyeqops;
+	Datum	   *datums;
+
+	Assert((keycols && refcols) || (!keycols && !refcols));
+
+	if (keycols)
+	{
+		if (list_length(keycols) != list_length(refcols))
+			ereport(ERROR,
+					errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					errmsg("mismatching number of columns in %s vertex definition of edge \"%s\"", type, aliasname),
+					parser_errposition(pstate, location));
+
+		nkeys = list_length(keycols);
+		keyattnums = array_from_column_list(pstate, keycols, location, edge_rel);
+		refattnums = array_from_column_list(pstate, refcols, location, ref_rel);
+		keyeqops = palloc_array(Oid, nkeys);
+
+		for (int i = 0; i < nkeys; i++)
+		{
+			Oid			keytype;
+			int32		keytypmod;
+			Oid			keycoll;
+			Oid			reftype;
+			int32		reftypmod;
+			Oid			refcoll;
+			Oid			opc;
+			Oid			opf;
+			StrategyNumber strategy;
+
+			/*
+			 * Lookup equality operator to be used for edge and vertex key.
+			 * Vertex key is equivalent to primary key and edge key is similar
+			 * to foreign key since edge key references vertex key. Hence
+			 * vertex key is used as left operand and edge key is used as
+			 * right operand. The method used to find the equality operators
+			 * is similar to the method used to find equality operators for
+			 * FK/PK comparison in ATAddForeignKeyConstraint() except that
+			 * opclass of the vertex key type is used as a starting point.
+			 * Since we need only equality operators we use both BT and HASH
+			 * strategies.
+			 *
+			 * If the required operators do not exist, we can not construct
+			 * quals linking an edge to its adjacent vertices.
+			 */
+			get_atttypetypmodcoll(RelationGetRelid(edge_rel), keyattnums[i], &keytype, &keytypmod, &keycoll);
+			get_atttypetypmodcoll(RelationGetRelid(ref_rel), refattnums[i], &reftype, &reftypmod, &refcoll);
+			keyeqops[i] = InvalidOid;
+			strategy = BTEqualStrategyNumber;
+			opc = GetDefaultOpClass(reftype, BTREE_AM_OID);
+			if (!OidIsValid(opc))
+			{
+				opc = GetDefaultOpClass(reftype, HASH_AM_OID);
+				strategy = HTEqualStrategyNumber;
+			}
+			if (OidIsValid(opc))
+			{
+				opf = get_opclass_family(opc);
+				if (OidIsValid(opf))
+				{
+					keyeqops[i] = get_opfamily_member(opf, reftype, keytype, strategy);
+					if (!OidIsValid(keyeqops[i]))
+					{
+						/* Last resort, implicit cast. */
+						if (can_coerce_type(1, &keytype, &reftype, COERCION_IMPLICIT))
+							keyeqops[i] = get_opfamily_member(opf, reftype, reftype, strategy);
+					}
+				}
+			}
+
+			if (!OidIsValid(keyeqops[i]))
+				ereport(ERROR,
+						errcode(ERRCODE_SYNTAX_ERROR),
+						errmsg("no equality operator exists for %s key comparison of edge \"%s\"",
+							   type, aliasname),
+						parser_errposition(pstate, location));
+
+			/*
+			 * If collations of key attribute and referenced attribute are
+			 * different, an edge may end up being adjacent to undesired
+			 * vertices.  Prohibit such a case.
+			 *
+			 * PK/FK allows different collations as long as they are
+			 * deterministic for backward compatibility. But we can be a bit
+			 * stricter here and follow SQL standard.
+			 */
+			if (keycoll != refcoll &&
+				keycoll != DEFAULT_COLLATION_OID && refcoll != DEFAULT_COLLATION_OID &&
+				OidIsValid(keycoll) && OidIsValid(refcoll))
+				ereport(ERROR,
+						errcode(ERRCODE_SYNTAX_ERROR),
+						errmsg("collation mismatch in %s key of edge \"%s\": %s vs. %s",
+							   type, aliasname,
+							   get_collation_name(keycoll), get_collation_name(refcoll)),
+						parser_errposition(pstate, location));
+		}
+	}
+	else
+	{
+		ForeignKeyCacheInfo *fk = NULL;
+
+		foreach_node(ForeignKeyCacheInfo, tmp, RelationGetFKeyList(edge_rel))
+		{
+			if (tmp->confrelid == RelationGetRelid(ref_rel))
+			{
+				if (fk)
+					ereport(ERROR,
+							errcode(ERRCODE_SYNTAX_ERROR),
+							errmsg("more than one suitable foreign key exists for %s key of edge \"%s\"", type, aliasname),
+							parser_errposition(pstate, location));
+				fk = tmp;
+			}
+		}
+
+		if (!fk)
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("no %s key specified and no suitable foreign key exists for definition of edge \"%s\"", type, aliasname),
+					parser_errposition(pstate, location));
+
+		nkeys = fk->nkeys;
+		keyattnums = fk->conkey;
+		refattnums = fk->confkey;
+		keyeqops = fk->conpfeqop;
+	}
+
+	*outkey = array_from_attnums(nkeys, keyattnums);
+	*outref = array_from_attnums(nkeys, refattnums);
+	datums = palloc_array(Datum, nkeys);
+	for (int i = 0; i < nkeys; i++)
+		datums[i] = ObjectIdGetDatum(keyeqops[i]);
+	*outeqop = construct_array_builtin(datums, nkeys, OIDOID);
+}
+
+/*
+ * Convert list of column names in the specified relation into an array of
+ * column numbers.
+ */
+static AttrNumber *
+array_from_column_list(ParseState *pstate, const List *colnames, int location, Relation element_rel)
+{
+	int			numattrs;
+	AttrNumber *attnums;
+	int			i;
+	ListCell   *lc;
+
+	numattrs = list_length(colnames);
+	attnums = palloc_array(AttrNumber, numattrs);
+
+	i = 0;
+	foreach(lc, colnames)
+	{
+		char	   *colname = strVal(lfirst(lc));
+		Oid			relid = RelationGetRelid(element_rel);
+		AttrNumber	attnum;
+
+		attnum = get_attnum(relid, colname);
+		if (!attnum)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_COLUMN),
+					 errmsg("column \"%s\" of relation \"%s\" does not exist",
+							colname, get_rel_name(relid)),
+					 parser_errposition(pstate, location)));
+		attnums[i++] = attnum;
+	}
+
+	for (int j = 0; j < numattrs; j++)
+	{
+		for (int k = j + 1; k < numattrs; k++)
+		{
+			if (attnums[j] == attnums[k])
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("graph key columns list must not contain duplicates"),
+						 parser_errposition(pstate, location)));
+		}
+	}
+
+	return attnums;
+}
+
+static ArrayType *
+array_from_attnums(int numattrs, const AttrNumber *attnums)
+{
+	Datum	   *attnumsd;
+
+	attnumsd = palloc_array(Datum, numattrs);
+
+	for (int i = 0; i < numattrs; i++)
+		attnumsd[i] = Int16GetDatum(attnums[i]);
+
+	return construct_array_builtin(attnumsd, numattrs, INT2OID);
+}
+
+static void
+array_of_attnums_to_objectaddrs(Oid relid, ArrayType *arr, ObjectAddresses *addrs)
+{
+	Datum	   *attnumsd;
+	int			numattrs;
+
+	deconstruct_array_builtin(arr, INT2OID, &attnumsd, NULL, &numattrs);
+
+	for (int i = 0; i < numattrs; i++)
+	{
+		ObjectAddress referenced;
+
+		ObjectAddressSubSet(referenced, RelationRelationId, relid, DatumGetInt16(attnumsd[i]));
+		add_exact_object_address(&referenced, addrs);
+	}
+}
+
+static void
+array_of_opers_to_objectaddrs(ArrayType *arr, ObjectAddresses *addrs)
+{
+	Datum	   *opersd;
+	int			numopers;
+
+	deconstruct_array_builtin(arr, OIDOID, &opersd, NULL, &numopers);
+
+	for (int i = 0; i < numopers; i++)
+	{
+		ObjectAddress referenced;
+
+		ObjectAddressSet(referenced, OperatorRelationId, DatumGetObjectId(opersd[i]));
+		add_exact_object_address(&referenced, addrs);
+	}
+}
+
+/*
+ * Insert a record for an element into the pg_propgraph_element catalog.  Also
+ * inserts labels and properties into their respective catalogs.
+ */
+static Oid
+insert_element_record(ObjectAddress pgaddress, struct element_info *einfo)
+{
+	Oid			graphid = pgaddress.objectId;
+	Relation	rel;
+	NameData	aliasname;
+	Oid			peoid;
+	Datum		values[Natts_pg_propgraph_element] = {0};
+	bool		nulls[Natts_pg_propgraph_element] = {0};
+	HeapTuple	tup;
+	ObjectAddress myself;
+	ObjectAddress referenced;
+	ObjectAddresses *addrs;
+
+	rel = table_open(PropgraphElementRelationId, RowExclusiveLock);
+
+	peoid = GetNewOidWithIndex(rel, PropgraphElementObjectIndexId, Anum_pg_propgraph_element_oid);
+	einfo->elementid = peoid;
+	values[Anum_pg_propgraph_element_oid - 1] = ObjectIdGetDatum(peoid);
+	values[Anum_pg_propgraph_element_pgepgid - 1] = ObjectIdGetDatum(graphid);
+	values[Anum_pg_propgraph_element_pgerelid - 1] = ObjectIdGetDatum(einfo->relid);
+	namestrcpy(&aliasname, einfo->aliasname);
+	values[Anum_pg_propgraph_element_pgealias - 1] = NameGetDatum(&aliasname);
+	values[Anum_pg_propgraph_element_pgekind - 1] = CharGetDatum(einfo->kind);
+	values[Anum_pg_propgraph_element_pgesrcvertexid - 1] = ObjectIdGetDatum(einfo->srcvertexid);
+	values[Anum_pg_propgraph_element_pgedestvertexid - 1] = ObjectIdGetDatum(einfo->destvertexid);
+	values[Anum_pg_propgraph_element_pgekey - 1] = PointerGetDatum(einfo->key);
+
+	if (einfo->srckey)
+		values[Anum_pg_propgraph_element_pgesrckey - 1] = PointerGetDatum(einfo->srckey);
+	else
+		nulls[Anum_pg_propgraph_element_pgesrckey - 1] = true;
+	if (einfo->srcref)
+		values[Anum_pg_propgraph_element_pgesrcref - 1] = PointerGetDatum(einfo->srcref);
+	else
+		nulls[Anum_pg_propgraph_element_pgesrcref - 1] = true;
+	if (einfo->srceqop)
+		values[Anum_pg_propgraph_element_pgesrceqop - 1] = PointerGetDatum(einfo->srceqop);
+	else
+		nulls[Anum_pg_propgraph_element_pgesrceqop - 1] = true;
+	if (einfo->destkey)
+		values[Anum_pg_propgraph_element_pgedestkey - 1] = PointerGetDatum(einfo->destkey);
+	else
+		nulls[Anum_pg_propgraph_element_pgedestkey - 1] = true;
+	if (einfo->destref)
+		values[Anum_pg_propgraph_element_pgedestref - 1] = PointerGetDatum(einfo->destref);
+	else
+		nulls[Anum_pg_propgraph_element_pgedestref - 1] = true;
+	if (einfo->desteqop)
+		values[Anum_pg_propgraph_element_pgedesteqop - 1] = PointerGetDatum(einfo->desteqop);
+	else
+		nulls[Anum_pg_propgraph_element_pgedesteqop - 1] = true;
+
+	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+	CatalogTupleInsert(rel, tup);
+	heap_freetuple(tup);
+
+	ObjectAddressSet(myself, PropgraphElementRelationId, peoid);
+
+	/* Add dependency on the property graph */
+	recordDependencyOn(&myself, &pgaddress, DEPENDENCY_AUTO);
+
+	addrs = new_object_addresses();
+
+	/* Add dependency on the relation */
+	ObjectAddressSet(referenced, RelationRelationId, einfo->relid);
+	add_exact_object_address(&referenced, addrs);
+	array_of_attnums_to_objectaddrs(einfo->relid, einfo->key, addrs);
+
+	/*
+	 * Add dependencies on vertices and equality operators used for key
+	 * comparison.
+	 */
+	if (einfo->srcvertexid)
+	{
+		ObjectAddressSet(referenced, PropgraphElementRelationId, einfo->srcvertexid);
+		add_exact_object_address(&referenced, addrs);
+		array_of_attnums_to_objectaddrs(einfo->relid, einfo->srckey, addrs);
+		array_of_attnums_to_objectaddrs(einfo->srcrelid, einfo->srcref, addrs);
+		array_of_opers_to_objectaddrs(einfo->srceqop, addrs);
+	}
+	if (einfo->destvertexid)
+	{
+		ObjectAddressSet(referenced, PropgraphElementRelationId, einfo->destvertexid);
+		add_exact_object_address(&referenced, addrs);
+		array_of_attnums_to_objectaddrs(einfo->relid, einfo->destkey, addrs);
+		array_of_attnums_to_objectaddrs(einfo->destrelid, einfo->destref, addrs);
+		array_of_opers_to_objectaddrs(einfo->desteqop, addrs);
+	}
+
+	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
+
+	table_close(rel, NoLock);
+
+	if (einfo->labels)
+	{
+		ListCell   *lc;
+
+		foreach(lc, einfo->labels)
+		{
+			PropGraphLabelAndProperties *lp = lfirst_node(PropGraphLabelAndProperties, lc);
+			Oid			ellabeloid;
+
+			if (lp->label)
+				ellabeloid = insert_label_record(graphid, peoid, lp->label);
+			else
+				ellabeloid = insert_label_record(graphid, peoid, einfo->aliasname);
+			insert_property_records(graphid, ellabeloid, einfo->relid, lp->properties);
+
+			CommandCounterIncrement();
+		}
+	}
+	else
+	{
+		Oid			ellabeloid;
+		PropGraphProperties *pr = makeNode(PropGraphProperties);
+
+		pr->all = true;
+		pr->location = -1;
+
+		ellabeloid = insert_label_record(graphid, peoid, einfo->aliasname);
+		insert_property_records(graphid, ellabeloid, einfo->relid, pr);
+	}
+
+	return peoid;
+}
+
+/*
+ * Insert records for a label into the pg_propgraph_label and
+ * pg_propgraph_element_label catalogs, and register dependencies.
+ *
+ * Returns the OID of the new pg_propgraph_element_label record.
+ */
+static Oid
+insert_label_record(Oid graphid, Oid peoid, const char *label)
+{
+	Oid			labeloid;
+	Oid			ellabeloid;
+
+	/*
+	 * Insert into pg_propgraph_label if not already existing.
+	 */
+	labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME, Anum_pg_propgraph_label_oid, ObjectIdGetDatum(graphid), CStringGetDatum(label));
+	if (!labeloid)
+	{
+		Relation	rel;
+		Datum		values[Natts_pg_propgraph_label] = {0};
+		bool		nulls[Natts_pg_propgraph_label] = {0};
+		NameData	labelname;
+		HeapTuple	tup;
+		ObjectAddress myself;
+		ObjectAddress referenced;
+
+		rel = table_open(PropgraphLabelRelationId, RowExclusiveLock);
+
+		labeloid = GetNewOidWithIndex(rel, PropgraphLabelObjectIndexId, Anum_pg_propgraph_label_oid);
+		values[Anum_pg_propgraph_label_oid - 1] = ObjectIdGetDatum(labeloid);
+		values[Anum_pg_propgraph_label_pglpgid - 1] = ObjectIdGetDatum(graphid);
+		namestrcpy(&labelname, label);
+		values[Anum_pg_propgraph_label_pgllabel - 1] = NameGetDatum(&labelname);
+
+		tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+		CatalogTupleInsert(rel, tup);
+		heap_freetuple(tup);
+
+		ObjectAddressSet(myself, PropgraphLabelRelationId, labeloid);
+
+		ObjectAddressSet(referenced, RelationRelationId, graphid);
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+		table_close(rel, NoLock);
+	}
+
+	/*
+	 * Insert into pg_propgraph_element_label
+	 */
+	if (SearchSysCacheExists2(PROPGRAPHELEMENTLABELELEMENTLABEL,
+							  ObjectIdGetDatum(peoid),
+							  ObjectIdGetDatum(labeloid)))
+		ereport(ERROR,
+				errcode(ERRCODE_DUPLICATE_OBJECT),
+				errmsg("label \"%s\" already exists", label));
+	else
+	{
+		Relation	rel;
+		Datum		values[Natts_pg_propgraph_element_label] = {0};
+		bool		nulls[Natts_pg_propgraph_element_label] = {0};
+		HeapTuple	tup;
+		ObjectAddress myself;
+		ObjectAddress referenced;
+
+		rel = table_open(PropgraphElementLabelRelationId, RowExclusiveLock);
+
+		ellabeloid = GetNewOidWithIndex(rel, PropgraphElementLabelObjectIndexId, Anum_pg_propgraph_element_label_oid);
+		values[Anum_pg_propgraph_element_label_oid - 1] = ObjectIdGetDatum(ellabeloid);
+		values[Anum_pg_propgraph_element_label_pgellabelid - 1] = ObjectIdGetDatum(labeloid);
+		values[Anum_pg_propgraph_element_label_pgelelid - 1] = ObjectIdGetDatum(peoid);
+
+		tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+		CatalogTupleInsert(rel, tup);
+		heap_freetuple(tup);
+
+		ObjectAddressSet(myself, PropgraphElementLabelRelationId, ellabeloid);
+
+		ObjectAddressSet(referenced, PropgraphLabelRelationId, labeloid);
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+		ObjectAddressSet(referenced, PropgraphElementRelationId, peoid);
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+		table_close(rel, NoLock);
+	}
+
+	return ellabeloid;
+}
+
+/*
+ * Insert records for properties into the pg_propgraph_property catalog.
+ */
+static void
+insert_property_records(Oid graphid, Oid ellabeloid, Oid pgerelid, const PropGraphProperties *properties)
+{
+	List	   *proplist = NIL;
+	ParseState *pstate;
+	ParseNamespaceItem *nsitem;
+	List	   *tp;
+	Relation	rel;
+	ListCell   *lc;
+
+	if (properties->all)
+	{
+		Relation	attRelation;
+		SysScanDesc scan;
+		ScanKeyData key[1];
+		HeapTuple	attributeTuple;
+
+		attRelation = table_open(AttributeRelationId, RowShareLock);
+		ScanKeyInit(&key[0],
+					Anum_pg_attribute_attrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(pgerelid));
+		scan = systable_beginscan(attRelation, AttributeRelidNumIndexId,
+								  true, NULL, 1, key);
+		while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
+		{
+			Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple);
+			ColumnRef  *cr;
+			ResTarget  *rt;
+
+			if (att->attnum <= 0 || att->attisdropped)
+				continue;
+
+			cr = makeNode(ColumnRef);
+			rt = makeNode(ResTarget);
+
+			cr->fields = list_make1(makeString(pstrdup(NameStr(att->attname))));
+			cr->location = -1;
+
+			rt->name = pstrdup(NameStr(att->attname));
+			rt->val = (Node *) cr;
+			rt->location = -1;
+
+			proplist = lappend(proplist, rt);
+		}
+		systable_endscan(scan);
+		table_close(attRelation, RowShareLock);
+	}
+	else
+	{
+		proplist = properties->properties;
+
+		foreach(lc, proplist)
+		{
+			ResTarget  *rt = lfirst_node(ResTarget, lc);
+
+			if (!rt->name && !IsA(rt->val, ColumnRef))
+				ereport(ERROR,
+						errcode(ERRCODE_SYNTAX_ERROR),
+						errmsg("property name required"),
+						parser_errposition(NULL, rt->location));
+		}
+	}
+
+	rel = table_open(pgerelid, AccessShareLock);
+
+	pstate = make_parsestate(NULL);
+	nsitem = addRangeTableEntryForRelation(pstate,
+										   rel,
+										   AccessShareLock,
+										   NULL,
+										   false,
+										   true);
+	addNSItemToQuery(pstate, nsitem, true, true, true);
+
+	table_close(rel, NoLock);
+
+	tp = transformTargetList(pstate, proplist, EXPR_KIND_PROPGRAPH_PROPERTY);
+	if (pstate->p_resolve_unknowns)
+		resolveTargetListUnknowns(pstate, tp);
+	assign_expr_collations(pstate, (Node *) tp);
+
+	/*
+	 * When properties are derived from the table's attributes, names are
+	 * already unique. Reject duplicate property names within an explicit
+	 * PROPERTIES clause. Do this after transformTargetList() so that any
+	 * names derived by transformTargetList() are considered.
+	 */
+	if (!properties->all)
+	{
+		List	   *seen = NIL;
+
+		foreach_node(TargetEntry, te, tp)
+		{
+			String	   *name = makeString(te->resname);
+
+			if (list_member(seen, name))
+				ereport(ERROR,
+						errcode(ERRCODE_DUPLICATE_OBJECT),
+						errmsg("property \"%s\" specified more than once", te->resname));
+			seen = lappend(seen, name);
+		}
+	}
+
+	foreach_node(TargetEntry, te, tp)
+		insert_property_record(graphid, ellabeloid, pgerelid, te->resname, te->expr);
+}
+
+/*
+ * Insert records for a property into the pg_propgraph_property and
+ * pg_propgraph_label_property catalogs, and register dependencies.
+ */
+static void
+insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *propname, const Expr *expr)
+{
+	Oid			propoid;
+	Oid			exprtypid = exprType((const Node *) expr);
+	int32		exprtypmod = exprTypmod((const Node *) expr);
+	Oid			exprcollation = exprCollation((const Node *) expr);
+
+	/*
+	 * Insert into pg_propgraph_property if not already existing.
+	 */
+	propoid = GetSysCacheOid2(PROPGRAPHPROPNAME, Anum_pg_propgraph_property_oid, ObjectIdGetDatum(graphid), CStringGetDatum(propname));
+	if (!OidIsValid(propoid))
+	{
+		Relation	rel;
+		NameData	propnamedata;
+		Datum		values[Natts_pg_propgraph_property] = {0};
+		bool		nulls[Natts_pg_propgraph_property] = {0};
+		HeapTuple	tup;
+		ObjectAddress myself;
+		ObjectAddress referenced;
+		AclResult	aclresult;
+
+		rel = table_open(PropgraphPropertyRelationId, RowExclusiveLock);
+
+		propoid = GetNewOidWithIndex(rel, PropgraphPropertyObjectIndexId, Anum_pg_propgraph_property_oid);
+		values[Anum_pg_propgraph_property_oid - 1] = ObjectIdGetDatum(propoid);
+		values[Anum_pg_propgraph_property_pgppgid - 1] = ObjectIdGetDatum(graphid);
+		namestrcpy(&propnamedata, propname);
+		values[Anum_pg_propgraph_property_pgpname - 1] = NameGetDatum(&propnamedata);
+		values[Anum_pg_propgraph_property_pgptypid - 1] = ObjectIdGetDatum(exprtypid);
+		values[Anum_pg_propgraph_property_pgptypmod - 1] = Int32GetDatum(exprtypmod);
+		values[Anum_pg_propgraph_property_pgpcollation - 1] = ObjectIdGetDatum(exprcollation);
+
+		tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+		CatalogTupleInsert(rel, tup);
+		heap_freetuple(tup);
+
+		ObjectAddressSet(myself, PropgraphPropertyRelationId, propoid);
+
+		ObjectAddressSet(referenced, RelationRelationId, graphid);
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+		aclresult = object_aclcheck(TypeRelationId, exprtypid, GetUserId(), ACL_USAGE);
+		if (aclresult != ACLCHECK_OK)
+			aclcheck_error_type(aclresult, exprtypid);
+		ObjectAddressSet(referenced, TypeRelationId, exprtypid);
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		if (OidIsValid(exprcollation) && exprcollation != DEFAULT_COLLATION_OID)
+		{
+			ObjectAddressSet(referenced, CollationRelationId, exprcollation);
+			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		}
+
+		table_close(rel, NoLock);
+	}
+	else
+	{
+		HeapTuple	pgptup = SearchSysCache1(PROPGRAPHPROPOID, ObjectIdGetDatum(propoid));
+		Form_pg_propgraph_property pgpform = (Form_pg_propgraph_property) GETSTRUCT(pgptup);
+		Oid			proptypid = pgpform->pgptypid;
+		int32		proptypmod = pgpform->pgptypmod;
+		Oid			propcollation = pgpform->pgpcollation;
+
+		ReleaseSysCache(pgptup);
+
+		/*
+		 * Check that in the graph, all properties with the same name have the
+		 * same type (independent of which label they are on).  (See SQL/PGQ
+		 * subclause "Consistency check of a tabular property graph
+		 * descriptor".)
+		 */
+		if (proptypid != exprtypid || proptypmod != exprtypmod)
+		{
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("property \"%s\" data type mismatch: %s vs. %s",
+						   propname, format_type_with_typemod(proptypid, proptypmod), format_type_with_typemod(exprtypid, exprtypmod)),
+					errdetail("In a property graph, a property of the same name has to have the same data type in each label."));
+		}
+
+		/* Similarly for collation */
+		if (propcollation != exprcollation)
+		{
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("property \"%s\" collation mismatch: %s vs. %s",
+						   propname, get_collation_name(propcollation), get_collation_name(exprcollation)),
+					errdetail("In a property graph, a property of the same name has to have the same collation in each label."));
+		}
+	}
+
+	/*
+	 * Insert into pg_propgraph_label_property
+	 */
+	if (SearchSysCacheExists2(PROPGRAPHLABELPROP, ObjectIdGetDatum(ellabeloid),
+							  ObjectIdGetDatum(propoid)))
+		ereport(ERROR,
+				errcode(ERRCODE_DUPLICATE_OBJECT),
+				errmsg("property \"%s\" already exists", propname));
+	else
+	{
+		Relation	rel;
+		Datum		values[Natts_pg_propgraph_label_property] = {0};
+		bool		nulls[Natts_pg_propgraph_label_property] = {0};
+		Oid			plpoid;
+		HeapTuple	tup;
+		ObjectAddress myself;
+		ObjectAddress referenced;
+
+		rel = table_open(PropgraphLabelPropertyRelationId, RowExclusiveLock);
+
+		plpoid = GetNewOidWithIndex(rel, PropgraphLabelPropertyObjectIndexId, Anum_pg_propgraph_label_property_oid);
+		values[Anum_pg_propgraph_label_property_oid - 1] = ObjectIdGetDatum(plpoid);
+		values[Anum_pg_propgraph_label_property_plppropid - 1] = ObjectIdGetDatum(propoid);
+		values[Anum_pg_propgraph_label_property_plpellabelid - 1] = ObjectIdGetDatum(ellabeloid);
+		values[Anum_pg_propgraph_label_property_plpexpr - 1] = CStringGetTextDatum(nodeToString(expr));
+
+		tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+		CatalogTupleInsert(rel, tup);
+		heap_freetuple(tup);
+
+		ObjectAddressSet(myself, PropgraphLabelPropertyRelationId, plpoid);
+
+		ObjectAddressSet(referenced, PropgraphPropertyRelationId, propoid);
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+		ObjectAddressSet(referenced, PropgraphElementLabelRelationId, ellabeloid);
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+		CheckUsageOnTypesInSingleRelExpr((Node *) expr, pgerelid, GetUserId());
+		recordDependencyOnSingleRelExpr(&myself, (Node *) copyObject(expr), pgerelid, DEPENDENCY_NORMAL, DEPENDENCY_NORMAL, false);
+
+		table_close(rel, NoLock);
+	}
+}
+
+/*
+ * Check that for the given graph element, all properties with the same name
+ * have the same expression for each label.  (See SQL/PGQ subclause "Creation
+ * of an element table descriptor".)
+ *
+ * We check this after all the catalog records are already inserted.  This
+ * makes it easier to share this code between CREATE PROPERTY GRAPH and ALTER
+ * PROPERTY GRAPH.  We pass in the element OID so that ALTER PROPERTY GRAPH
+ * only has to check the element it has just operated on.  CREATE PROPERTY
+ * GRAPH checks all elements it has created.
+ */
+static void
+check_element_properties(Oid peoid)
+{
+	Relation	rel1;
+	ScanKeyData key1[1];
+	SysScanDesc scan1;
+	HeapTuple	tuple1;
+	List	   *propoids = NIL;
+	List	   *propexprs = NIL;
+
+	rel1 = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+	ScanKeyInit(&key1[0],
+				Anum_pg_propgraph_element_label_pgelelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(peoid));
+
+	scan1 = systable_beginscan(rel1, PropgraphElementLabelElementLabelIndexId, true, NULL, 1, key1);
+	while (HeapTupleIsValid(tuple1 = systable_getnext(scan1)))
+	{
+		Form_pg_propgraph_element_label ellabel = (Form_pg_propgraph_element_label) GETSTRUCT(tuple1);
+		Relation	rel2;
+		ScanKeyData key2[1];
+		SysScanDesc scan2;
+		HeapTuple	tuple2;
+
+		rel2 = table_open(PropgraphLabelPropertyRelationId, AccessShareLock);
+		ScanKeyInit(&key2[0],
+					Anum_pg_propgraph_label_property_plpellabelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(ellabel->oid));
+
+		scan2 = systable_beginscan(rel2, PropgraphLabelPropertyLabelPropIndexId, true, NULL, 1, key2);
+		while (HeapTupleIsValid(tuple2 = systable_getnext(scan2)))
+		{
+			Form_pg_propgraph_label_property lprop = (Form_pg_propgraph_label_property) GETSTRUCT(tuple2);
+			Oid			propoid;
+			Datum		datum;
+			bool		isnull;
+			char	   *propexpr;
+			ListCell   *lc1,
+					   *lc2;
+			bool		found;
+
+			propoid = lprop->plppropid;
+			datum = heap_getattr(tuple2, Anum_pg_propgraph_label_property_plpexpr, RelationGetDescr(rel2), &isnull);
+			Assert(!isnull);
+			propexpr = TextDatumGetCString(datum);
+
+			found = false;
+			forboth(lc1, propoids, lc2, propexprs)
+			{
+				if (propoid == lfirst_oid(lc1))
+				{
+					Node	   *na,
+							   *nb;
+
+					na = stringToNode(propexpr);
+					nb = stringToNode(lfirst(lc2));
+
+					found = true;
+
+					if (!equal(na, nb))
+					{
+						HeapTuple	tuple3;
+						Form_pg_propgraph_element elform;
+						List	   *dpcontext;
+						char	   *dpa,
+								   *dpb;
+
+						tuple3 = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(peoid));
+						if (!tuple3)
+							elog(ERROR, "cache lookup failed for property graph element %u", peoid);
+						elform = (Form_pg_propgraph_element) GETSTRUCT(tuple3);
+						dpcontext = deparse_context_for(get_rel_name(elform->pgerelid), elform->pgerelid);
+
+						dpa = deparse_expression(na, dpcontext, false, false);
+						dpb = deparse_expression(nb, dpcontext, false, false);
+
+						/*
+						 * show in sorted order to keep output independent of
+						 * index order
+						 */
+						if (strcmp(dpa, dpb) > 0)
+						{
+							char	   *tmp;
+
+							tmp = dpa;
+							dpa = dpb;
+							dpb = tmp;
+						}
+
+						ereport(ERROR,
+								errcode(ERRCODE_SYNTAX_ERROR),
+								errmsg("element \"%s\" property \"%s\" expression mismatch: %s vs. %s",
+									   NameStr(elform->pgealias), get_propgraph_property_name(propoid), dpa, dpb),
+								errdetail("In a property graph element, a property of the same name has to have the same expression in each label."));
+
+						ReleaseSysCache(tuple3);
+					}
+
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				propoids = lappend_oid(propoids, propoid);
+				propexprs = lappend(propexprs, propexpr);
+			}
+		}
+		systable_endscan(scan2);
+		table_close(rel2, AccessShareLock);
+	}
+
+	systable_endscan(scan1);
+	table_close(rel1, AccessShareLock);
+}
+
+/*
+ * Check that for the given element label, all labels of the same name in the
+ * graph have the same number and names of properties (independent of which
+ * element they are on).  (See SQL/PGQ subclause "Consistency check of a
+ * tabular property graph descriptor".)
+ *
+ * We check this after all the catalog records are already inserted.  This
+ * makes it easier to share this code between CREATE PROPERTY GRAPH and ALTER
+ * PROPERTY GRAPH.  We pass in the element label OID so that some variants of
+ * ALTER PROPERTY GRAPH only have to check the element label it has just
+ * operated on.  CREATE PROPERTY GRAPH and other ALTER PROPERTY GRAPH variants
+ * check all labels.
+ */
+static void
+check_element_label_properties(Oid ellabeloid)
+{
+	Relation	rel;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	HeapTuple	tuple;
+	Oid			labelid = InvalidOid;
+	Oid			ref_ellabeloid = InvalidOid;
+	List	   *myprops,
+			   *refprops;
+	List	   *diff1,
+			   *diff2;
+
+	rel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+
+	/*
+	 * Get element label info
+	 */
+	ScanKeyInit(&key[0],
+				Anum_pg_propgraph_element_label_oid,
+				BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(ellabeloid));
+	scan = systable_beginscan(rel, PropgraphElementLabelObjectIndexId, true, NULL, 1, key);
+	if (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Form_pg_propgraph_element_label ellabel = (Form_pg_propgraph_element_label) GETSTRUCT(tuple);
+
+		labelid = ellabel->pgellabelid;
+	}
+	systable_endscan(scan);
+	if (!labelid)
+		elog(ERROR, "element label %u not found", ellabeloid);
+
+	/*
+	 * Find a reference element label to fetch label properties.  The
+	 * reference element label has to have the same label OID as the one being
+	 * checked but a different element OID.
+	 */
+	ScanKeyInit(&key[0],
+				Anum_pg_propgraph_element_label_pgellabelid,
+				BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(labelid));
+	scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId, true, NULL, 1, key);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Form_pg_propgraph_element_label otherellabel = (Form_pg_propgraph_element_label) GETSTRUCT(tuple);
+
+		if (otherellabel->oid != ellabeloid)
+		{
+			ref_ellabeloid = otherellabel->oid;
+			break;
+		}
+	}
+	systable_endscan(scan);
+
+	table_close(rel, AccessShareLock);
+
+	/*
+	 * If there is no previous definition of this label, then we are done.
+	 */
+	if (!ref_ellabeloid)
+		return;
+
+	/*
+	 * Now check number and names.
+	 *
+	 * XXX We could provide more detail in the error messages, but that would
+	 * probably only be useful for some ALTER commands, because otherwise it's
+	 * not really clear which label definition is the wrong one, and so you'd
+	 * have to construct a rather verbose report to be of any use.  Let's keep
+	 * it simple for now.
+	 */
+
+	myprops = get_element_label_property_names(ellabeloid);
+	refprops = get_element_label_property_names(ref_ellabeloid);
+
+	if (list_length(refprops) != list_length(myprops))
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				errmsg("mismatching number of properties in definition of label \"%s\"", get_propgraph_label_name(labelid)));
+
+	diff1 = list_difference(myprops, refprops);
+	diff2 = list_difference(refprops, myprops);
+
+	if (diff1 || diff2)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				errmsg("mismatching property names in definition of label \"%s\"", get_propgraph_label_name(labelid)));
+}
+
+/*
+ * As above, but check all labels of a graph.
+ */
+static void
+check_all_labels_properties(Oid pgrelid)
+{
+	foreach_oid(labeloid, get_graph_label_ids(pgrelid))
+	{
+		foreach_oid(ellabeloid, get_label_element_label_ids(labeloid))
+		{
+			check_element_label_properties(ellabeloid);
+		}
+	}
+}
+
+/*
+ * ALTER PROPERTY GRAPH
+ */
+ObjectAddress
+AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
+{
+	Oid			pgrelid;
+	ListCell   *lc;
+	ObjectAddress pgaddress;
+
+	/*
+	 * ShareRowExclusiveLock is required because this command runs some
+	 * graph-wide consistency checks that wouldn't work if more than one ALTER
+	 * PROPERTY GRAPH could operate on the same graph at once.
+	 */
+	pgrelid = RangeVarGetRelidExtended(stmt->pgname,
+									   ShareRowExclusiveLock,
+									   stmt->missing_ok ? RVR_MISSING_OK : 0,
+									   RangeVarCallbackOwnsRelation,
+									   NULL);
+	if (pgrelid == InvalidOid)
+	{
+		ereport(NOTICE,
+				(errmsg("relation \"%s\" does not exist, skipping",
+						stmt->pgname->relname)));
+		return InvalidObjectAddress;
+	}
+
+	ObjectAddressSet(pgaddress, RelationRelationId, pgrelid);
+
+	foreach(lc, stmt->add_vertex_tables)
+	{
+		PropGraphVertex *vertex = lfirst_node(PropGraphVertex, lc);
+		struct element_info *vinfo;
+		Relation	rel;
+		Oid			peoid;
+
+		vinfo = palloc0_object(struct element_info);
+		vinfo->kind = PGEKIND_VERTEX;
+
+		vinfo->relid = RangeVarGetRelidExtended(vertex->vtable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL);
+
+		rel = table_open(vinfo->relid, NoLock);
+
+		if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && get_rel_persistence(pgrelid) != RELPERSISTENCE_TEMP)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot add temporary element table to non-temporary property graph"),
+					 errdetail("Table \"%s\" is a temporary table.", get_rel_name(vinfo->relid)),
+					 parser_errposition(pstate, vertex->vtable->location)));
+
+		if (vertex->vtable->alias)
+			vinfo->aliasname = vertex->vtable->alias->aliasname;
+		else
+			vinfo->aliasname = vertex->vtable->relname;
+
+		vinfo->key = propgraph_element_get_key(pstate, vertex->vkey, rel, vinfo->aliasname, vertex->location);
+
+		vinfo->labels = vertex->labels;
+
+		table_close(rel, NoLock);
+
+		if (SearchSysCacheExists2(PROPGRAPHELALIAS,
+								  ObjectIdGetDatum(pgrelid),
+								  CStringGetDatum(vinfo->aliasname)))
+			ereport(ERROR,
+					errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					errmsg("alias \"%s\" already exists in property graph \"%s\"",
+						   vinfo->aliasname, stmt->pgname->relname),
+					parser_errposition(pstate, vertex->vtable->location));
+
+		peoid = insert_element_record(pgaddress, vinfo);
+
+		CommandCounterIncrement();
+		check_element_properties(peoid);
+		check_all_labels_properties(pgrelid);
+	}
+
+	foreach(lc, stmt->add_edge_tables)
+	{
+		PropGraphEdge *edge = lfirst_node(PropGraphEdge, lc);
+		struct element_info *einfo;
+		Relation	rel;
+		Relation	srcrel;
+		Relation	destrel;
+		Oid			peoid;
+
+		einfo = palloc0_object(struct element_info);
+		einfo->kind = PGEKIND_EDGE;
+
+		einfo->relid = RangeVarGetRelidExtended(edge->etable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL);
+
+		rel = table_open(einfo->relid, NoLock);
+
+		if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && get_rel_persistence(pgrelid) != RELPERSISTENCE_TEMP)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot add temporary element table to non-temporary property graph"),
+					 errdetail("Table \"%s\" is a temporary table.", get_rel_name(einfo->relid)),
+					 parser_errposition(pstate, edge->etable->location)));
+
+		if (edge->etable->alias)
+			einfo->aliasname = edge->etable->alias->aliasname;
+		else
+			einfo->aliasname = edge->etable->relname;
+
+		einfo->key = propgraph_element_get_key(pstate, edge->ekey, rel, einfo->aliasname, edge->location);
+
+		einfo->srcvertexid = get_vertex_oid(pstate, pgrelid, edge->esrcvertex, edge->location);
+		einfo->destvertexid = get_vertex_oid(pstate, pgrelid, edge->edestvertex, edge->location);
+
+		einfo->srcrelid = get_element_relid(einfo->srcvertexid);
+		einfo->destrelid = get_element_relid(einfo->destvertexid);
+
+		srcrel = table_open(einfo->srcrelid, AccessShareLock);
+		destrel = table_open(einfo->destrelid, AccessShareLock);
+
+		propgraph_edge_get_ref_keys(pstate, edge->esrckey, edge->esrcvertexcols, rel, srcrel,
+									einfo->aliasname, edge->location, "SOURCE",
+									&einfo->srckey, &einfo->srcref, &einfo->srceqop);
+		propgraph_edge_get_ref_keys(pstate, edge->edestkey, edge->edestvertexcols, rel, destrel,
+									einfo->aliasname, edge->location, "DESTINATION",
+									&einfo->destkey, &einfo->destref, &einfo->desteqop);
+
+		einfo->labels = edge->labels;
+
+		table_close(destrel, NoLock);
+		table_close(srcrel, NoLock);
+
+		table_close(rel, NoLock);
+
+		if (SearchSysCacheExists2(PROPGRAPHELALIAS,
+								  ObjectIdGetDatum(pgrelid),
+								  CStringGetDatum(einfo->aliasname)))
+			ereport(ERROR,
+					errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					errmsg("alias \"%s\" already exists in property graph \"%s\"",
+						   einfo->aliasname, stmt->pgname->relname),
+					parser_errposition(pstate, edge->etable->location));
+
+		peoid = insert_element_record(pgaddress, einfo);
+
+		CommandCounterIncrement();
+		check_element_properties(peoid);
+		check_all_labels_properties(pgrelid);
+	}
+
+	foreach(lc, stmt->drop_vertex_tables)
+	{
+		char	   *alias = strVal(lfirst(lc));
+		Oid			peoid;
+		ObjectAddress obj;
+
+		peoid = get_vertex_oid(pstate, pgrelid, alias, -1);
+		ObjectAddressSet(obj, PropgraphElementRelationId, peoid);
+		performDeletion(&obj, stmt->drop_behavior, 0);
+	}
+
+	foreach(lc, stmt->drop_edge_tables)
+	{
+		char	   *alias = strVal(lfirst(lc));
+		Oid			peoid;
+		ObjectAddress obj;
+
+		peoid = get_edge_oid(pstate, pgrelid, alias, -1);
+		ObjectAddressSet(obj, PropgraphElementRelationId, peoid);
+		performDeletion(&obj, stmt->drop_behavior, 0);
+	}
+
+	/* Remove any orphaned pg_propgraph_label entries */
+	if (stmt->drop_vertex_tables || stmt->drop_edge_tables)
+	{
+		foreach_oid(labeloid, get_graph_label_ids(pgrelid))
+		{
+			if (!get_label_element_label_ids(labeloid))
+			{
+				ObjectAddress obj;
+
+				ObjectAddressSet(obj, PropgraphLabelRelationId, labeloid);
+				performDeletion(&obj, stmt->drop_behavior, 0);
+			}
+		}
+	}
+
+	foreach(lc, stmt->add_labels)
+	{
+		PropGraphLabelAndProperties *lp = lfirst_node(PropGraphLabelAndProperties, lc);
+		Oid			peoid;
+		Oid			pgerelid;
+		Oid			ellabeloid;
+
+		Assert(lp->label);
+
+		if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX)
+			peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1);
+		else
+			peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1);
+
+		pgerelid = get_element_relid(peoid);
+
+		ellabeloid = insert_label_record(pgrelid, peoid, lp->label);
+		insert_property_records(pgrelid, ellabeloid, pgerelid, lp->properties);
+
+		CommandCounterIncrement();
+		check_element_properties(peoid);
+		check_element_label_properties(ellabeloid);
+	}
+
+	if (stmt->drop_label)
+	{
+		Oid			peoid;
+		Oid			labeloid;
+		Oid			ellabeloid = InvalidOid;
+		ObjectAddress obj;
+		Relation	ellabelrel;
+		SysScanDesc ellabelscan;
+		ScanKeyData ellabelkey[1];
+		int			nlabels;
+		HeapTuple	tuple;
+
+		if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX)
+			peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1);
+		else
+			peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1);
+
+		labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME,
+								   Anum_pg_propgraph_label_oid,
+								   ObjectIdGetDatum(pgrelid),
+								   CStringGetDatum(stmt->drop_label));
+		if (!labeloid)
+			ereport(ERROR,
+					errcode(ERRCODE_UNDEFINED_OBJECT),
+					errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
+						   get_rel_name(pgrelid), stmt->element_alias, stmt->drop_label),
+					parser_errposition(pstate, -1));
+
+		/*
+		 * Is the given label associated with the element?  Is this the only
+		 * label associated with the element?  Scan the
+		 * pg_propgraph_element_label table to find answers to these
+		 * questions.  Stop scanning when we know both answers.
+		 */
+		ellabelrel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+		ScanKeyInit(&ellabelkey[0],
+					Anum_pg_propgraph_element_label_pgelelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(peoid));
+		ellabelscan = systable_beginscan(ellabelrel, PropgraphElementLabelElementLabelIndexId,
+										 true, NULL, 1, ellabelkey);
+		nlabels = 0;
+		while (HeapTupleIsValid(tuple = systable_getnext(ellabelscan)))
+		{
+			Form_pg_propgraph_element_label ellabelform = (Form_pg_propgraph_element_label) GETSTRUCT(tuple);
+
+			nlabels++;
+
+			if (ellabelform->pgellabelid == labeloid)
+				ellabeloid = ellabelform->oid;
+
+			if (nlabels > 1 && ellabeloid)
+				break;
+		}
+		systable_endscan(ellabelscan);
+		table_close(ellabelrel, AccessShareLock);
+
+		if (!ellabeloid)
+			ereport(ERROR,
+					errcode(ERRCODE_UNDEFINED_OBJECT),
+					errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
+						   get_rel_name(pgrelid), stmt->element_alias, stmt->drop_label),
+					parser_errposition(pstate, -1));
+
+		/*
+		 * Prevent dropping the last label from an element. Every element must
+		 * have at least one label associated with it.
+		 */
+		if (nlabels == 1)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot drop the last label from element \"%s\"",
+							stmt->element_alias),
+					 errhint("Every element must have at least one label.")));
+
+		ObjectAddressSet(obj, PropgraphElementLabelRelationId, ellabeloid);
+		performDeletion(&obj, stmt->drop_behavior, 0);
+
+		/* Remove any orphaned pg_propgraph_label entries */
+		if (!get_label_element_label_ids(labeloid))
+		{
+			ObjectAddressSet(obj, PropgraphLabelRelationId, labeloid);
+			performDeletion(&obj, stmt->drop_behavior, 0);
+		}
+	}
+
+	if (stmt->add_properties)
+	{
+		Oid			peoid;
+		Oid			pgerelid;
+		Oid			labeloid;
+		Oid			ellabeloid = InvalidOid;
+
+		if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX)
+			peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1);
+		else
+			peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1);
+
+		labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME,
+								   Anum_pg_propgraph_label_oid,
+								   ObjectIdGetDatum(pgrelid),
+								   CStringGetDatum(stmt->alter_label));
+		if (labeloid)
+			ellabeloid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
+										 Anum_pg_propgraph_element_label_oid,
+										 ObjectIdGetDatum(peoid),
+										 ObjectIdGetDatum(labeloid));
+		if (!ellabeloid)
+			ereport(ERROR,
+					errcode(ERRCODE_UNDEFINED_OBJECT),
+					errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
+						   get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label),
+					parser_errposition(pstate, -1));
+
+		pgerelid = get_element_relid(peoid);
+
+		insert_property_records(pgrelid, ellabeloid, pgerelid, stmt->add_properties);
+
+		CommandCounterIncrement();
+		check_element_properties(peoid);
+		check_element_label_properties(ellabeloid);
+	}
+
+	if (stmt->drop_properties)
+	{
+		Oid			peoid;
+		Oid			labeloid;
+		Oid			ellabeloid = InvalidOid;
+		ObjectAddress obj;
+
+		if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX)
+			peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1);
+		else
+			peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1);
+
+		labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME,
+								   Anum_pg_propgraph_label_oid,
+								   ObjectIdGetDatum(pgrelid),
+								   CStringGetDatum(stmt->alter_label));
+		if (labeloid)
+			ellabeloid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
+										 Anum_pg_propgraph_element_label_oid,
+										 ObjectIdGetDatum(peoid),
+										 ObjectIdGetDatum(labeloid));
+
+		if (!ellabeloid)
+			ereport(ERROR,
+					errcode(ERRCODE_UNDEFINED_OBJECT),
+					errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
+						   get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label),
+					parser_errposition(pstate, -1));
+
+		foreach(lc, stmt->drop_properties)
+		{
+			char	   *propname = strVal(lfirst(lc));
+			Oid			propoid;
+			Oid			plpoid = InvalidOid;
+
+			propoid = GetSysCacheOid2(PROPGRAPHPROPNAME,
+									  Anum_pg_propgraph_property_oid,
+									  ObjectIdGetDatum(pgrelid),
+									  CStringGetDatum(propname));
+			if (propoid)
+				plpoid = GetSysCacheOid2(PROPGRAPHLABELPROP,
+										 Anum_pg_propgraph_label_property_oid,
+										 ObjectIdGetDatum(ellabeloid),
+										 ObjectIdGetDatum(propoid));
+			if (!plpoid)
+				ereport(ERROR,
+						errcode(ERRCODE_UNDEFINED_OBJECT),
+						errmsg("property graph \"%s\" element \"%s\" label \"%s\" has no property \"%s\"",
+							   get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label, propname),
+						parser_errposition(pstate, -1));
+
+			ObjectAddressSet(obj, PropgraphLabelPropertyRelationId, plpoid);
+			performDeletion(&obj, stmt->drop_behavior, 0);
+		}
+
+		check_element_label_properties(ellabeloid);
+	}
+
+	/* Remove any orphaned pg_propgraph_property entries */
+	if (stmt->drop_properties || stmt->drop_vertex_tables || stmt->drop_edge_tables || stmt->drop_label)
+	{
+		foreach_oid(propoid, get_graph_property_ids(pgrelid))
+		{
+			Relation	rel;
+			SysScanDesc scan;
+			ScanKeyData key[1];
+
+			rel = table_open(PropgraphLabelPropertyRelationId, RowShareLock);
+			ScanKeyInit(&key[0],
+						Anum_pg_propgraph_label_property_plppropid,
+						BTEqualStrategyNumber, F_OIDEQ,
+						ObjectIdGetDatum(propoid));
+			/* XXX no suitable index */
+			scan = systable_beginscan(rel, InvalidOid, true, NULL, 1, key);
+			if (!systable_getnext(scan))
+			{
+				ObjectAddress obj;
+
+				ObjectAddressSet(obj, PropgraphPropertyRelationId, propoid);
+				performDeletion(&obj, stmt->drop_behavior, 0);
+			}
+
+			systable_endscan(scan);
+			table_close(rel, RowShareLock);
+		}
+	}
+
+	/*
+	 * Invalidate relcache entry of the property graph so that the queries in
+	 * the cached plans referencing the property graph will be rewritten
+	 * considering changes to the property graph.
+	 */
+	CacheInvalidateRelcacheByRelid(pgrelid);
+
+	return pgaddress;
+}
+
+/*
+ * Get OID of vertex from graph OID and element alias.  Element must be a
+ * vertex, otherwise error.
+ */
+static Oid
+get_vertex_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location)
+{
+	HeapTuple	tuple;
+	Oid			peoid;
+
+	tuple = SearchSysCache2(PROPGRAPHELALIAS, ObjectIdGetDatum(pgrelid), CStringGetDatum(alias));
+	if (!tuple)
+		ereport(ERROR,
+				errcode(ERRCODE_UNDEFINED_OBJECT),
+				errmsg("property graph \"%s\" has no element with alias \"%s\"",
+					   get_rel_name(pgrelid), alias),
+				parser_errposition(pstate, location));
+
+	if (((Form_pg_propgraph_element) GETSTRUCT(tuple))->pgekind != PGEKIND_VERTEX)
+		ereport(ERROR,
+				errcode(ERRCODE_SYNTAX_ERROR),
+				errmsg("element \"%s\" of property graph \"%s\" is not a vertex",
+					   alias, get_rel_name(pgrelid)),
+				parser_errposition(pstate, location));
+
+	peoid = ((Form_pg_propgraph_element) GETSTRUCT(tuple))->oid;
+
+	ReleaseSysCache(tuple);
+
+	return peoid;
+}
+
+/*
+ * Get OID of edge from graph OID and element alias.  Element must be an edge,
+ * otherwise error.
+ */
+static Oid
+get_edge_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location)
+{
+	HeapTuple	tuple;
+	Oid			peoid;
+
+	tuple = SearchSysCache2(PROPGRAPHELALIAS, ObjectIdGetDatum(pgrelid), CStringGetDatum(alias));
+	if (!tuple)
+		ereport(ERROR,
+				errcode(ERRCODE_UNDEFINED_OBJECT),
+				errmsg("property graph \"%s\" has no element with alias \"%s\"",
+					   get_rel_name(pgrelid), alias),
+				parser_errposition(pstate, location));
+
+	if (((Form_pg_propgraph_element) GETSTRUCT(tuple))->pgekind != PGEKIND_EDGE)
+		ereport(ERROR,
+				errcode(ERRCODE_SYNTAX_ERROR),
+				errmsg("element \"%s\" of property graph \"%s\" is not an edge",
+					   alias, get_rel_name(pgrelid)),
+				parser_errposition(pstate, location));
+
+	peoid = ((Form_pg_propgraph_element) GETSTRUCT(tuple))->oid;
+
+	ReleaseSysCache(tuple);
+
+	return peoid;
+}
+
+/*
+ * Get the element table relation OID from the OID of the element.
+ */
+static Oid
+get_element_relid(Oid peid)
+{
+	HeapTuple	tuple;
+	Oid			pgerelid;
+
+	tuple = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(peid));
+	if (!tuple)
+		elog(ERROR, "cache lookup failed for property graph element %u", peid);
+
+	pgerelid = ((Form_pg_propgraph_element) GETSTRUCT(tuple))->pgerelid;
+
+	ReleaseSysCache(tuple);
+
+	return pgerelid;
+}
+
+/*
+ * Get a list of all label OIDs of a graph.
+ */
+static List *
+get_graph_label_ids(Oid graphid)
+{
+	Relation	rel;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	HeapTuple	tuple;
+	List	   *result = NIL;
+
+	rel = table_open(PropgraphLabelRelationId, AccessShareLock);
+	ScanKeyInit(&key[0],
+				Anum_pg_propgraph_label_pglpgid,
+				BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(graphid));
+	scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId, true, NULL, 1, key);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		result = lappend_oid(result, ((Form_pg_propgraph_label) GETSTRUCT(tuple))->oid);
+	}
+	systable_endscan(scan);
+	table_close(rel, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * Get a list of all element label OIDs for a label.
+ */
+static List *
+get_label_element_label_ids(Oid labelid)
+{
+	Relation	rel;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	HeapTuple	tuple;
+	List	   *result = NIL;
+
+	rel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+	ScanKeyInit(&key[0],
+				Anum_pg_propgraph_element_label_pgellabelid,
+				BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(labelid));
+	scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId, true, NULL, 1, key);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		result = lappend_oid(result, ((Form_pg_propgraph_element_label) GETSTRUCT(tuple))->oid);
+	}
+	systable_endscan(scan);
+	table_close(rel, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * Get the names of properties associated with the given element label OID.
+ *
+ * The result is a list of String nodes (so we can use list functions to
+ * detect differences).
+ */
+static List *
+get_element_label_property_names(Oid ellabeloid)
+{
+	Relation	rel;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	HeapTuple	tuple;
+	List	   *result = NIL;
+
+	rel = table_open(PropgraphLabelPropertyRelationId, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_propgraph_label_property_plpellabelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(ellabeloid));
+
+	scan = systable_beginscan(rel, PropgraphLabelPropertyLabelPropIndexId, true, NULL, 1, key);
+
+	while ((tuple = systable_getnext(scan)))
+	{
+		Form_pg_propgraph_label_property plpform = (Form_pg_propgraph_label_property) GETSTRUCT(tuple);
+
+		result = lappend(result, makeString(get_propgraph_property_name(plpform->plppropid)));
+	}
+
+	systable_endscan(scan);
+	table_close(rel, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * Get a list of all property OIDs of a graph.
+ */
+static List *
+get_graph_property_ids(Oid graphid)
+{
+	Relation	rel;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	HeapTuple	tuple;
+	List	   *result = NIL;
+
+	rel = table_open(PropgraphPropertyRelationId, AccessShareLock);
+	ScanKeyInit(&key[0],
+				Anum_pg_propgraph_property_pgppgid,
+				BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(graphid));
+	scan = systable_beginscan(rel, PropgraphPropertyNameIndexId, true, NULL, 1, key);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		result = lappend_oid(result, ((Form_pg_propgraph_property) GETSTRUCT(tuple))->oid);
+	}
+	systable_endscan(scan);
+	table_close(rel, AccessShareLock);
+
+	return result;
+}
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 596c1abaf78..83e91cf3c8b 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -641,7 +641,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 */
 	if (cmd != REPACK_COMMAND_CLUSTER &&
 		!allowSystemTableMods && OidIsValid(indexOid) &&
-		IsCatalogRelation(OldHeap) && !index->rd_index->indisclustered)
+		IsCatalogRelation(OldHeap) && !RelationGetIndex(index)->indisclustered)
 		ereport(ERROR,
 				errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				errmsg("permission denied: \"%s\" is a system catalog",
@@ -790,8 +790,8 @@ check_index_is_clusterable(Relation OldHeap, Oid indexOid, LOCKMODE lockmode)
 	/*
 	 * Check that index is in fact an index on the given relation
 	 */
-	if (OldIndex->rd_index == NULL ||
-		OldIndex->rd_index->indrelid != RelationGetRelid(OldHeap))
+	if (RelationGetIndex(OldIndex) == NULL ||
+		RelationGetIndex(OldIndex)->indrelid != RelationGetRelid(OldHeap))
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not an index for table \"%s\"",
@@ -825,7 +825,7 @@ check_index_is_clusterable(Relation OldHeap, Oid indexOid, LOCKMODE lockmode)
 	 * might put recently-dead tuples out-of-order in the new table, and there
 	 * is little harm in that.)
 	 */
-	if (!OldIndex->rd_index->indisvalid)
+	if (!RelationGetIndex(OldIndex)->indisvalid)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot cluster on invalid index \"%s\"",
@@ -3057,7 +3057,7 @@ static bool
 find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
+	Form_pg_index idx = RelationGetIndex(chgcxt->cc_ident_index);
 	IndexScanDesc scan;
 	bool		retval = false;
 
@@ -3116,7 +3116,7 @@ identity_key_equal(ChangeContext *chgcxt, TupleTableSlot *locator,
 	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
 		ScanKey		entry = &chgcxt->cc_ident_key[i];
-		AttrNumber	attno = chgcxt->cc_ident_index->rd_index->indkey.values[i];
+		AttrNumber	attno = RelationGetIndex(chgcxt->cc_ident_index)->indkey.values[i];
 
 		Assert(attno > 0);
 
@@ -3297,7 +3297,7 @@ initialize_change_context(ChangeContext *chgcxt,
 	{
 		Form_pg_index indexForm;
 
-		indexForm = chgcxt->cc_ident_index->rd_index;
+		indexForm = RelationGetIndex(chgcxt->cc_ident_index);
 		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
 		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
 		for (int i = 0; i < indexForm->indnkeyatts; i++)
@@ -3341,7 +3341,7 @@ initialize_change_context(ChangeContext *chgcxt,
 	chgcxt->cc_last_key_attno = InvalidAttrNumber;
 	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		AttrNumber	attno = chgcxt->cc_ident_index->rd_index->indkey.values[i];
+		AttrNumber	attno = RelationGetIndex(chgcxt->cc_ident_index)->indkey.values[i];
 
 		Assert(attno > 0);
 		chgcxt->cc_last_key_attno = Max(chgcxt->cc_last_key_attno, attno);
@@ -3606,7 +3606,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
-									 get_rel_namespace(ind->rd_index->indrelid),
+									 get_rel_namespace(RelationGetIndex(ind)->indrelid),
 									 false);
 		newindex = index_create_copy(NewHeap, INDEX_CREATE_SUPPRESS_PROGRESS,
 									 oldindex, ind->rd_rel->reltablespace,
@@ -3654,7 +3654,7 @@ copy_index_constraints(Relation old_index, Oid new_index_id, Oid new_heap_id)
 	ScanKeyInit(&skey,
 				Anum_pg_constraint_conrelid,
 				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(old_index->rd_index->indrelid));
+				ObjectIdGetDatum(RelationGetIndex(old_index)->indrelid));
 	scan = systable_beginscan(rel, ConstraintRelidTypidNameIndexId, true,
 							  NULL, 1, &skey);
 	desc = RelationGetDescr(rel);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0274d892f2e..ee3df7fb1d6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1306,7 +1306,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 
 			if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
 			{
-				if (idxRel->rd_index->indisunique)
+				if (RelationGetIndex(idxRel)->indisunique)
 					ereport(ERROR,
 							(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 							 errmsg("cannot create foreign partition of partitioned table \"%s\"",
@@ -9075,12 +9075,12 @@ ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newVa
 	if (rel->rd_rel->relkind == RELKIND_INDEX ||
 		rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
 	{
-		if (attnum > rel->rd_index->indnkeyatts)
+		if (attnum > RelationGetIndex(rel)->indnkeyatts)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("cannot alter statistics on included column \"%s\" of index \"%s\"",
 							NameStr(attrtuple->attname), RelationGetRelationName(rel))));
-		else if (rel->rd_index->indkey.values[attnum - 1] != 0)
+		else if (RelationGetIndex(rel)->indkey.values[attnum - 1] != 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("cannot alter statistics on non-expression column \"%s\" of index \"%s\"",
@@ -9215,9 +9215,9 @@ SetIndexStorageProperties(Relation rel, Relation attrelation,
 
 		indrel = index_open(indexoid, lockmode);
 
-		for (int i = 0; i < indrel->rd_index->indnatts; i++)
+		for (int i = 0; i < RelationGetIndex(indrel)->indnatts; i++)
 		{
-			if (indrel->rd_index->indkey.values[i] == attnum)
+			if (RelationGetIndex(indrel)->indkey.values[i] == attnum)
 			{
 				indattnum = i + 1;
 				break;
@@ -14621,8 +14621,8 @@ dropconstraint_internal(Relation rel, HeapTuple constraintTup, DropBehavior beha
 				Relation	pk = relation_open(pkindex, AccessShareLock);
 
 				pkattrs = NULL;
-				for (int i = 0; i < pk->rd_index->indnkeyatts; i++)
-					pkattrs = bms_add_member(pkattrs, pk->rd_index->indkey.values[i]);
+				for (int i = 0; i < RelationGetIndex(pk)->indnkeyatts; i++)
+					pkattrs = bms_add_member(pkattrs, RelationGetIndex(pk)->indkey.values[i]);
 
 				relation_close(pk, AccessShareLock);
 			}
@@ -19194,8 +19194,8 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode
 	indexRel = index_open(indexOid, ShareLock);
 
 	/* Check that the index is on the relation we're altering. */
-	if (indexRel->rd_index == NULL ||
-		indexRel->rd_index->indrelid != RelationGetRelid(rel))
+	if (RelationGetIndex(indexRel) == NULL ||
+		RelationGetIndex(indexRel)->indrelid != RelationGetRelid(rel))
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not an index for table \"%s\"",
@@ -19208,14 +19208,14 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode
 	 * exclusion), we can use that too.
 	 */
 	if ((!indexRel->rd_indam->amcanunique ||
-		 !indexRel->rd_index->indisunique) &&
-		!(indexRel->rd_index->indisunique && indexRel->rd_index->indisexclusion))
+		 !RelationGetIndex(indexRel)->indisunique) &&
+		!(RelationGetIndex(indexRel)->indisunique && RelationGetIndex(indexRel)->indisexclusion))
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot use non-unique index \"%s\" as replica identity",
 						RelationGetRelationName(indexRel))));
 	/* Deferred indexes are not guaranteed to be always unique. */
-	if (!indexRel->rd_index->indimmediate)
+	if (!RelationGetIndex(indexRel)->indimmediate)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot use non-immediate index \"%s\" as replica identity",
@@ -19236,7 +19236,7 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode
 	/* Check index for nullable columns. */
 	for (key = 0; key < IndexRelationGetNumberOfKeyAttributes(indexRel); key++)
 	{
-		int16		attno = indexRel->rd_index->indkey.values[key];
+		int16		attno = RelationGetIndex(indexRel)->indkey.values[key];
 		Form_pg_attribute attr;
 		HeapTuple	contup;
 		Form_pg_constraint conForm;
@@ -21363,8 +21363,8 @@ AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel)
 			Oid			idx = lfirst_oid(cell);
 			Relation	idxRel = index_open(idx, AccessShareLock);
 
-			if (idxRel->rd_index->indisunique ||
-				idxRel->rd_index->indisprimary)
+			if (RelationGetIndex(idxRel)->indisunique ||
+				RelationGetIndex(idxRel)->indisprimary)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("cannot attach foreign table \"%s\" as partition of partitioned table \"%s\"",
@@ -21423,7 +21423,7 @@ AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel)
 				continue;
 
 			/* If this index is invalid, can't use it */
-			if (!attachrelIdxRels[i]->rd_index->indisvalid)
+			if (!RelationGetIndex(attachrelIdxRels[i])->indisvalid)
 				continue;
 
 			if (CompareIndexInfo(attachInfos[i], info,
@@ -22347,7 +22347,7 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 	 * partition, so lock that one too.
 	 */
 	state.partitionOid = InvalidOid;
-	state.parentTblOid = parentIdx->rd_index->indrelid;
+	state.parentTblOid = RelationGetIndex(parentIdx)->indrelid;
 	state.lockedParentTbl = false;
 	partIdxId =
 		RangeVarGetRelidExtended(name, AccessExclusiveLock, 0,
@@ -22363,8 +22363,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 	partIdx = relation_open(partIdxId, AccessExclusiveLock);
 
 	/* we already hold locks on both tables, so this is safe: */
-	parentTbl = relation_open(parentIdx->rd_index->indrelid, AccessShareLock);
-	partTbl = relation_open(partIdx->rd_index->indrelid, NoLock);
+	parentTbl = relation_open(RelationGetIndex(parentIdx)->indrelid, AccessShareLock);
+	partTbl = relation_open(RelationGetIndex(partIdx)->indrelid, NoLock);
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partIdx));
 
@@ -22467,7 +22467,7 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		 * If it's a primary key, make sure the columns in the partition are
 		 * NOT NULL.
 		 */
-		if (parentIdx->rd_index->indisprimary)
+		if (RelationGetIndex(parentIdx)->indisprimary)
 			verifyPartitionIndexNotNull(childInfo, partTbl);
 
 		/* All good -- do it */
@@ -22480,7 +22480,7 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 
 		validatePartitionedIndex(parentIdx, parentTbl);
 	}
-	else if (!parentIdx->rd_index->indisvalid)
+	else if (!RelationGetIndex(parentIdx)->indisvalid)
 	{
 		/*
 		 * The index is attached, but the parent is still invalid; see if it
@@ -22613,7 +22613,7 @@ validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
 		parentTblId = get_partition_parent(RelationGetRelid(partedTbl), false);
 		parentIdx = relation_open(parentIdxId, AccessExclusiveLock);
 		parentTbl = relation_open(parentTblId, AccessExclusiveLock);
-		Assert(!parentIdx->rd_index->indisvalid);
+		Assert(!RelationGetIndex(parentIdx)->indisvalid);
 
 		validatePartitionedIndex(parentIdx, parentTbl);
 
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 161f50ea560..a14dac060e3 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2441,7 +2441,7 @@ vac_open_indexes(Relation relation, LOCKMODE lockmode,
 		Relation	indrel;
 
 		indrel = index_open(indexoid, lockmode);
-		if (indrel->rd_index->indisready)
+		if (RelationGetIndex(indrel)->indisready)
 			(*Irel)[i++] = indrel;
 		else
 			index_close(indrel, lockmode);
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index d0a714a42f2..98ffe673555 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -219,7 +219,7 @@ ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative)
 		 * If the indexes are to be used for speculative insertion, add extra
 		 * information required by unique index entries.
 		 */
-		if (speculative && ii->ii_Unique && !indexDesc->rd_index->indisexclusion)
+		if (speculative && ii->ii_Unique && !RelationGetIndex(indexDesc)->indisexclusion)
 			BuildSpeculativeIndexInfo(indexDesc, ii);
 
 		relationDescs[i] = indexDesc;
@@ -411,7 +411,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		applyNoDupErr = (flags & EIIT_NO_DUPE_ERROR) &&
 			(arbiterIndexes == NIL ||
 			 list_member_oid(arbiterIndexes,
-							 indexRelation->rd_index->indexrelid));
+							 RelationGetIndex(indexRelation)->indexrelid));
 
 		/*
 		 * The index AM does the actual insertion, plus uniqueness checking.
@@ -426,11 +426,11 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		 * For a speculative insertion (used by INSERT ... ON CONFLICT), do
 		 * the same as for a deferrable unique index.
 		 */
-		if (!indexRelation->rd_index->indisunique)
+		if (!RelationGetIndex(indexRelation)->indisunique)
 			checkUnique = UNIQUE_CHECK_NO;
 		else if (applyNoDupErr)
 			checkUnique = UNIQUE_CHECK_PARTIAL;
-		else if (indexRelation->rd_index->indimmediate)
+		else if (RelationGetIndex(indexRelation)->indimmediate)
 			checkUnique = UNIQUE_CHECK_YES;
 		else
 			checkUnique = UNIQUE_CHECK_PARTIAL;
@@ -480,7 +480,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 				violationOK = true;
 				waitMode = CEOUC_LIVELOCK_PREVENTING_WAIT;
 			}
-			else if (!indexRelation->rd_index->indimmediate)
+			else if (!RelationGetIndex(indexRelation)->indimmediate)
 			{
 				violationOK = true;
 				waitMode = CEOUC_NOWAIT;
@@ -510,7 +510,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 			 * speculative conflict, since that always requires a restart.
 			 */
 			result = lappend_oid(result, RelationGetRelid(indexRelation));
-			if (indexRelation->rd_index->indimmediate && specConflict)
+			if (RelationGetIndex(indexRelation)->indimmediate && specConflict)
 				*specConflict = true;
 		}
 	}
@@ -600,10 +600,10 @@ ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 		/* When specific arbiter indexes requested, only examine them */
 		if (arbiterIndexes != NIL &&
 			!list_member_oid(arbiterIndexes,
-							 indexRelation->rd_index->indexrelid))
+							 RelationGetIndex(indexRelation)->indexrelid))
 			continue;
 
-		if (!indexRelation->rd_index->indimmediate)
+		if (!RelationGetIndex(indexRelation)->indimmediate)
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("ON CONFLICT does not support deferrable unique constraints/exclusion constraints as arbiters"),
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 86fa0f0deaa..c7a16b0c4a3 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -778,7 +778,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 					unparented_ii = leaf_part_rri->ri_IndexRelationInfo[unparented_i];
 
 					Assert(!list_member_oid(arbiterIndexes,
-											unparented_rel->rd_index->indexrelid));
+											RelationGetIndex(unparented_rel)->indexrelid));
 
 					/* Ignore indexes not ready */
 					if (!unparented_ii->ii_ReadyForInserts)
@@ -798,7 +798,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 													   unparented_rel))
 						{
 							arbiterIndexes = lappend_oid(arbiterIndexes,
-														 unparented_rel->rd_index->indexrelid);
+														 RelationGetIndex(unparented_rel)->indexrelid);
 							additional_arbiters++;
 							break;
 						}
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index dd42acc13e2..cc9f68238ab 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -62,7 +62,7 @@ build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel,
 	int			skey_attoff = 0;
 	Datum		indclassDatum;
 	oidvector  *opclass;
-	int2vector *indkey = &idxrel->rd_index->indkey;
+	int2vector *indkey = &RelationGetIndex(idxrel)->indkey;
 
 	indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, idxrel->rd_indextuple,
 										   Anum_pg_index_indclass);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d067368cfc1..950f8b9350f 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -237,7 +237,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 			 * Extract info from the relation descriptor for the index.
 			 */
 			indexRelation = index_open(indexoid, lmode);
-			index = indexRelation->rd_index;
+			index = RelationGetIndex(indexRelation);
 
 			/*
 			 * Ignore invalid indexes, since they can't safely be used for
@@ -921,7 +921,7 @@ infer_arbiter_indexes(PlannerInfo *root)
 		{
 			if (indexOidFromConstraint == RelationGetRelid(idxRel))
 			{
-				Assert(idxRel->rd_index->indisready);
+				Assert(RelationGetIndex(idxRel)->indisready);
 				indexRelFromConstraint = idxRel;
 				break;
 			}
@@ -951,7 +951,7 @@ infer_arbiter_indexes(PlannerInfo *root)
 		 * enforcement needs to occur there anyway when an inference clause is
 		 * omitted.
 		 */
-		idxForm = idxRel->rd_index;
+		idxForm = RelationGetIndex(idxRel);
 
 		/*
 		 * Ignore indexes that aren't indisready, because we cannot trust
@@ -1048,7 +1048,7 @@ infer_arbiter_indexes(PlannerInfo *root)
 		indexedAttrs = NULL;
 		for (natt = 0; natt < idxForm->indnkeyatts; natt++)
 		{
-			int			attno = idxRel->rd_index->indkey.values[natt];
+			int			attno = RelationGetIndex(idxRel)->indkey.values[natt];
 
 			if (attno != 0)
 				indexedAttrs = bms_add_member(indexedAttrs,
@@ -1224,7 +1224,7 @@ infer_collation_opclass_match(InferenceElem *elem, Relation idxRel,
 		Oid			opfamily = idxRel->rd_opfamily[natt - 1];
 		Oid			opcinputtype = idxRel->rd_opcintype[natt - 1];
 		Oid			collation = idxRel->rd_indcollation[natt - 1];
-		int			attno = idxRel->rd_index->indkey.values[natt - 1];
+		int			attno = RelationGetIndex(idxRel)->indkey.values[natt - 1];
 
 		if (attno != 0)
 			nplain++;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index f0c7755b8fb..f38a5cb4653 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -2456,7 +2456,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 
 		/* Open the index (this will throw an error if it is not an index) */
 		index_rel = index_open(index_oid, AccessShareLock);
-		index_form = index_rel->rd_index;
+		index_form = RelationGetIndex(index_rel);
 
 		/* Check that it does not have an associated constraint already */
 		if (OidIsValid(get_index_constraint(index_oid)))
diff --git a/src/backend/replication/logical/conflict.c b/src/backend/replication/logical/conflict.c
index 90431ffce98..eef18829fbb 100644
--- a/src/backend/replication/logical/conflict.c
+++ b/src/backend/replication/logical/conflict.c
@@ -325,7 +325,7 @@ InitConflictIndexes(ResultRelInfo *relInfo)
 			continue;
 
 		/* Don't support conflict detection for deferrable index */
-		if (!indexRelation->rd_index->indimmediate)
+		if (!RelationGetIndex(indexRelation)->indimmediate)
 			continue;
 
 		uniqueIndexes = lappend_oid(uniqueIndexes,
diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c
index 6242ce70ad2..f1ff2f31010 100644
--- a/src/backend/replication/logical/relation.c
+++ b/src/backend/replication/logical/relation.c
@@ -802,7 +802,7 @@ FindUsableIndexForReplicaIdentityFull(Relation localrel, AttrMap *attrmap)
 		 * permanently invalid leftover of a failed CREATE INDEX CONCURRENTLY
 		 * must never be chosen here.
 		 */
-		isUsableIdx = idxRel->rd_index->indisvalid &&
+		isUsableIdx = RelationGetIndex(idxRel)->indisvalid &&
 			IsIndexUsableForReplicaIdentityFull(idxRel, attrmap);
 		index_close(idxRel, AccessShareLock);
 
@@ -851,14 +851,14 @@ IsIndexUsableForReplicaIdentityFull(Relation idxrel, AttrMap *attrmap)
 	if (!heap_attisnull(idxrel->rd_indextuple, Anum_pg_index_indpred, NULL))
 		return false;
 
-	Assert(idxrel->rd_index->indnatts >= 1);
+	Assert(RelationGetIndex(idxrel)->indnatts >= 1);
 
 	indclass = (oidvector *) DatumGetPointer(SysCacheGetAttrNotNull(INDEXRELID,
 																	idxrel->rd_indextuple,
 																	Anum_pg_index_indclass));
 
 	/* Ensure that the index has a valid equal strategy for each key column */
-	for (int i = 0; i < idxrel->rd_index->indnkeyatts; i++)
+	for (int i = 0; i < RelationGetIndex(idxrel)->indnkeyatts; i++)
 	{
 		Oid			opfamily;
 
@@ -883,7 +883,7 @@ IsIndexUsableForReplicaIdentityFull(Relation idxrel, AttrMap *attrmap)
 	}
 
 	/* The leftmost index field must not be an expression */
-	keycol = idxrel->rd_index->indkey.values[0];
+	keycol = RelationGetIndex(idxrel)->indkey.values[0];
 	if (!AttributeNumberIsValid(keycol))
 		return false;
 
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index e8117ede407..adf9d645a46 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -323,13 +323,13 @@ statatt_get_index_expr(Relation rel, int attnum)
 	 * The index's attnum points directly to a relation attnum, hence it is
 	 * not an expression attribute.
 	 */
-	if (rel->rd_index->indkey.values[attnum - 1] != 0)
+	if (RelationGetIndex(rel)->indkey.values[attnum - 1] != 0)
 		return NULL;
 
 	indexpr_item = list_head(rel->rd_indexprs);
 
 	for (int i = 0; i < attnum - 1; i++)
-		if (rel->rd_index->indkey.values[i] == 0)
+		if (RelationGetIndex(rel)->indkey.values[i] == 0)
 			indexpr_item = lnext(rel->rd_indexprs, indexpr_item);
 
 	if (indexpr_item == NULL)	/* shouldn't happen */
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index 0ae85b7d5b4..cd3f725f221 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -2558,7 +2558,7 @@ PredicateLockTID(Relation relation, const ItemPointerData *tid, Snapshot snapsho
 	/*
 	 * Return if this xact wrote it.
 	 */
-	if (relation->rd_index == NULL)
+	if (RelationGetIndex(relation) == NULL)
 	{
 		/* If we wrote it; we already have a write lock. */
 		if (TransactionIdIsCurrentTransactionId(tuple_xid))
@@ -2890,7 +2890,7 @@ DropAllPredicateLocksFromTable(Relation relation, bool transfer)
 
 	dbId = relation->rd_locator.dbOid;
 	relId = relation->rd_id;
-	if (relation->rd_index == NULL)
+	if (RelationGetIndex(relation) == NULL)
 	{
 		isIndex = false;
 		heapId = relId;
@@ -2898,7 +2898,7 @@ DropAllPredicateLocksFromTable(Relation relation, bool transfer)
 	else
 	{
 		isIndex = true;
-		heapId = relation->rd_index->indrelid;
+		heapId = RelationGetIndex(relation)->indrelid;
 	}
 	Assert(heapId != InvalidOid);
 	Assert(transfer || !isIndex);	/* index OID only makes sense with
@@ -4371,7 +4371,7 @@ CheckTableForSerializableConflictIn(Relation relation)
 	 */
 	MyXactDidWrite = true;
 
-	Assert(relation->rd_index == NULL); /* not an index relation */
+	Assert(RelationGetIndex(relation) == NULL); /* not an index relation */
 
 	dbId = relation->rd_locator.dbOid;
 	heapId = relation->rd_id;
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 2797ac4abea..47184e0ad6c 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -3572,9 +3572,9 @@ ri_check_fastpath_index(RI_ConstraintInfo *riinfo,
 	 * collation to SPI.  Map index keys to table attributes because the FK
 	 * columns need not be listed in index order.  Ignore INCLUDE columns.
 	 */
-	for (int i = 0; i < idx_rel->rd_index->indnkeyatts; i++)
+	for (int i = 0; i < RelationGetIndex(idx_rel)->indnkeyatts; i++)
 	{
-		AttrNumber	attnum = idx_rel->rd_index->indkey.values[i];
+		AttrNumber	attnum = RelationGetIndex(idx_rel)->indkey.values[i];
 
 		if (idx_rel->rd_indcollation[i] != RIAttCollation(pk_rel, attnum))
 		{
@@ -3594,10 +3594,9 @@ ri_check_fastpath_index(RI_ConstraintInfo *riinfo,
 	{
 		int			idx_col;
 
-		for (idx_col = 0; idx_col < idx_rel->rd_index->indnkeyatts; idx_col++)
+		for (idx_col = 0; idx_col < RelationGetIndex(idx_rel)->indnkeyatts; idx_col++)
 		{
-			if (idx_rel->rd_index->indkey.values[idx_col] ==
-				riinfo->pk_attnums[i])
+			if (RelationGetIndex(idx_rel)->indkey.values[idx_col] == riinfo->pk_attnums[i])
 				break;
 		}
 		Assert(idx_col < idx_rel->rd_index->indnkeyatts);
@@ -3847,7 +3846,7 @@ ri_populate_fastpath_metadata(RI_ConstraintInfo *riinfo,
 		 */
 		for (idx_col = 0; idx_col < riinfo->nkeys; idx_col++)
 		{
-			if (idx_rel->rd_index->indkey.values[idx_col] == riinfo->pk_attnums[i])
+			if (RelationGetIndex(idx_rel)->indkey.values[idx_col] == riinfo->pk_attnums[i])
 				break;
 		}
 		Assert(idx_col < riinfo->nkeys);
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 511270edfda..482c1bcdd89 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1278,8 +1278,8 @@ InitCatCachePhase2(CatCache *cache, bool touch_index)
 		 * catch thinkos in definitions of new catcaches, so we don't worry
 		 * about the pg_am indexes not getting tested.
 		 */
-		Assert(idesc->rd_index->indisunique &&
-			   idesc->rd_index->indimmediate);
+		Assert(RelationGetIndex(idesc)->indisunique &&
+			   RelationGetIndex(idesc)->indimmediate);
 
 		index_close(idesc, AccessShareLock);
 		UnlockRelationOid(cache->cc_reloid, AccessShareLock);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 562501556c8..271e4533aa3 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1477,7 +1477,6 @@ RelationInitIndexAccessInfo(Relation relation)
 			 RelationGetRelid(relation));
 	oldcontext = MemoryContextSwitchTo(CacheMemoryContext);
 	relation->rd_indextuple = heap_copytuple(tuple);
-	relation->rd_index = (Form_pg_index) GETSTRUCT(relation->rd_indextuple);
 	MemoryContextSwitchTo(oldcontext);
 	ReleaseSysCache(tuple);
 
@@ -2340,7 +2339,7 @@ RelationReloadIndexInfo(Relation relation)
 	if (!IsSystemRelation(relation))
 	{
 		HeapTuple	tuple;
-		Form_pg_index index;
+		Form_pg_index index, rd_index;
 
 		tuple = SearchSysCache1(INDEXRELID,
 								ObjectIdGetDatum(RelationGetRelid(relation)));
@@ -2355,17 +2354,18 @@ RelationReloadIndexInfo(Relation relation)
 		 * it's not worth it to track exactly which ones they are.  None of
 		 * the array fields are allowed to change, though.
 		 */
-		relation->rd_index->indisunique = index->indisunique;
-		relation->rd_index->indnullsnotdistinct = index->indnullsnotdistinct;
-		relation->rd_index->indisprimary = index->indisprimary;
-		relation->rd_index->indisexclusion = index->indisexclusion;
-		relation->rd_index->indimmediate = index->indimmediate;
-		relation->rd_index->indisclustered = index->indisclustered;
-		relation->rd_index->indisvalid = index->indisvalid;
-		relation->rd_index->indcheckxmin = index->indcheckxmin;
-		relation->rd_index->indisready = index->indisready;
-		relation->rd_index->indislive = index->indislive;
-		relation->rd_index->indisreplident = index->indisreplident;
+		rd_index = RelationGetIndex(relation);
+		rd_index->indisunique = index->indisunique;
+		rd_index->indnullsnotdistinct = index->indnullsnotdistinct;
+		rd_index->indisprimary = index->indisprimary;
+		rd_index->indisexclusion = index->indisexclusion;
+		rd_index->indimmediate = index->indimmediate;
+		rd_index->indisclustered = index->indisclustered;
+		rd_index->indisvalid = index->indisvalid;
+		rd_index->indcheckxmin = index->indcheckxmin;
+		rd_index->indisready = index->indisready;
+		rd_index->indislive = index->indislive;
+		rd_index->indisreplident = index->indisreplident;
 
 		/* Copy xmin too, as that is needed to make sense of indcheckxmin */
 		HeapTupleHeaderSetXmin(relation->rd_indextuple->t_data,
@@ -5532,7 +5532,7 @@ restart:
 			indexPredicate = NULL;
 
 		/* Can this index be referenced by a foreign key? */
-		isKey = indexDesc->rd_index->indisunique &&
+		isKey = RelationGetIndex(indexDesc)->indisunique &&
 			indexExpressions == NULL &&
 			indexPredicate == NULL;
 
@@ -5553,9 +5553,9 @@ restart:
 			attrs = &hotblockingattrs;
 
 		/* Collect simple attribute references */
-		for (i = 0; i < indexDesc->rd_index->indnatts; i++)
+		for (i = 0; i < RelationGetIndex(indexDesc)->indnatts; i++)
 		{
-			int			attrnum = indexDesc->rd_index->indkey.values[i];
+			int			attrnum = RelationGetIndex(indexDesc)->indkey.values[i];
 
 			/*
 			 * Since we have covering indexes with non-key columns, we must
@@ -5576,15 +5576,15 @@ restart:
 				*attrs = bms_add_member(*attrs,
 										attrnum - FirstLowInvalidHeapAttributeNumber);
 
-				if (isKey && i < indexDesc->rd_index->indnkeyatts)
+				if (isKey && i < RelationGetIndex(indexDesc)->indnkeyatts)
 					uindexattrs = bms_add_member(uindexattrs,
 												 attrnum - FirstLowInvalidHeapAttributeNumber);
 
-				if (isPK && i < indexDesc->rd_index->indnkeyatts)
+				if (isPK && i < RelationGetIndex(indexDesc)->indnkeyatts)
 					pkindexattrs = bms_add_member(pkindexattrs,
 												  attrnum - FirstLowInvalidHeapAttributeNumber);
 
-				if (isIDKey && i < indexDesc->rd_index->indnkeyatts)
+				if (isIDKey && i < RelationGetIndex(indexDesc)->indnkeyatts)
 					idindexattrs = bms_add_member(idindexattrs,
 												  attrnum - FirstLowInvalidHeapAttributeNumber);
 			}
@@ -5725,9 +5725,9 @@ RelationGetIdentityKeyBitmap(Relation relation)
 			 relation->rd_replidindex);
 
 	/* Add referenced attributes to idindexattrs */
-	for (i = 0; i < indexDesc->rd_index->indnatts; i++)
+	for (i = 0; i < RelationGetIndex(indexDesc)->indnatts; i++)
 	{
-		int			attrnum = indexDesc->rd_index->indkey.values[i];
+		int			attrnum = RelationGetIndex(indexDesc)->indkey.values[i];
 
 		/*
 		 * We don't include non-key columns into idindexattrs bitmaps. See
@@ -5735,7 +5735,7 @@ RelationGetIdentityKeyBitmap(Relation relation)
 		 */
 		if (attrnum != 0)
 		{
-			if (i < indexDesc->rd_index->indnkeyatts)
+			if (i < RelationGetIndex(indexDesc)->indnkeyatts)
 				idindexattrs = bms_add_member(idindexattrs,
 											  attrnum - FirstLowInvalidHeapAttributeNumber);
 		}
@@ -5814,7 +5814,7 @@ RelationGetExclusionInfo(Relation indexRelation,
 	ScanKeyInit(&skey[0],
 				Anum_pg_constraint_conrelid,
 				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(indexRelation->rd_index->indrelid));
+				ObjectIdGetDatum(RelationGetIndex(indexRelation)->indrelid));
 
 	conrel = table_open(ConstraintRelationId, AccessShareLock);
 	conscan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId, true,
@@ -6479,7 +6479,6 @@ load_relcache_init_file(bool shared)
 
 			/* Fix up internal pointers in the tuple -- see heap_copytuple */
 			rel->rd_indextuple->t_data = (HeapTupleHeader) ((char *) rel->rd_indextuple + HEAPTUPLESIZE);
-			rel->rd_index = (Form_pg_index) GETSTRUCT(rel->rd_indextuple);
 
 			/*
 			 * prepare index info context --- parameters should match
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 33a3921f5cf..598ba1cb810 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -14,6 +14,7 @@
 #ifndef REL_H
 #define REL_H
 
+#include "access/htup_details.h"
 #include "access/tupdesc.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
@@ -337,19 +338,36 @@ RelationGetLockRelId(Relation relation)
  */
 #define RelationGetNumberOfAttributes(relation) ((relation)->rd_rel->relnatts)
 
+/*
+ * RelationGetIndex
+ *		Returns the pg_index tuple describing an index relation.
+ *
+ * rd_index is not stored directly in RelationData; it is always equal to
+ * GETSTRUCT(relation->rd_indextuple), so we derive it on the fly instead of
+ * spending a pointer-sized field on it.
+ */
+static inline Form_pg_index
+RelationGetIndex(Relation relation)
+{
+	if (relation->rd_indextuple == NULL)
+		return NULL;
+
+	return (Form_pg_index) GETSTRUCT(relation->rd_indextuple);
+}
+
 /*
  * IndexRelationGetNumberOfAttributes
  *		Returns the number of attributes in an index.
  */
 #define IndexRelationGetNumberOfAttributes(relation) \
-		((relation)->rd_index->indnatts)
+		(RelationGetIndex(relation)->indnatts)
 
 /*
  * IndexRelationGetNumberOfKeyAttributes
  *		Returns the number of key attributes in an index.
  */
 #define IndexRelationGetNumberOfKeyAttributes(relation) \
-		((relation)->rd_index->indnkeyatts)
+		(RelationGetIndex(relation)->indnkeyatts)
 
 /*
  * RelationGetDescr
diff --git a/src/include/utils/rel_internal.h b/src/include/utils/rel_internal.h
index 4f4b01a1894..89130218908 100644
--- a/src/include/utils/rel_internal.h
+++ b/src/include/utils/rel_internal.h
@@ -220,7 +220,6 @@ typedef struct RelationData
 		/* fields used only for an index relation */
 		struct
 		{
-			Form_pg_index rd_index;		/* pg_index tuple describing this index */
 			/* use "struct" here to avoid needing to include htup.h: */
 			struct HeapTupleData *rd_indextuple;	/* all of pg_index tuple */
 
-- 
2.53.0

