diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 1b45a4c..9f899c7 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -92,6 +92,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amstorage = true;
 	amroutine->amclusterable = false;
 	amroutine->ampredlocks = false;
+	amroutine->amcanindirect = false;
 	amroutine->amkeytype = InvalidOid;
 
 	amroutine->ambuild = brinbuild;
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index f07eedc..1bc91d2 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -49,6 +49,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amstorage = true;
 	amroutine->amclusterable = false;
 	amroutine->ampredlocks = false;
+	amroutine->amcanindirect = false;
 	amroutine->amkeytype = InvalidOid;
 
 	amroutine->ambuild = ginbuild;
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index b8aa9bc..4ec34d5 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -69,6 +69,7 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amstorage = true;
 	amroutine->amclusterable = true;
 	amroutine->ampredlocks = false;
+	amroutine->amcanindirect = false;
 	amroutine->amkeytype = InvalidOid;
 
 	amroutine->ambuild = gistbuild;
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 0eeb37d..7cfb776 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -66,6 +66,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amstorage = false;
 	amroutine->amclusterable = false;
 	amroutine->ampredlocks = false;
+	amroutine->amcanindirect = false;
 	amroutine->amkeytype = INT4OID;
 
 	amroutine->ambuild = hashbuild;
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index b019bc1..76518cc 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -96,10 +96,10 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 				HeapTuple newtup, HeapTuple old_key_tup,
 				bool all_visible_cleared, bool new_all_visible_cleared);
 static void HeapSatisfiesHOTandKeyUpdate(Relation relation,
-							 Bitmapset *hot_attrs,
-							 Bitmapset *key_attrs, Bitmapset *id_attrs,
+							 Bitmapset *hot_attrs, Bitmapset *key_attrs,
+							 Bitmapset *id_attrs, Bitmapset *indirect_attrs,
 							 bool *satisfies_hot, bool *satisfies_key,
-							 bool *satisfies_id,
+							 bool *satisfies_id, Bitmapset **unchanged_attrs,
 							 HeapTuple oldtup, HeapTuple newtup);
 static bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
 					 LockTupleMode mode, LockWaitPolicy wait_policy,
@@ -3414,6 +3414,8 @@ simple_heap_delete(Relation relation, ItemPointer tid)
  *	crosscheck - if not InvalidSnapshot, also check old tuple against this
  *	wait - true if should wait for any conflicting update to commit/abort
  *	hufd - output parameter, filled in failure cases (see below)
+ *	unchanged_ind_cols - output parameter; bits set for unmodified columns
+ *		that are indexed by indirect indexes
  *	lockmode - output parameter, filled with lock mode acquired on tuple
  *
  * Normal, successful return value is HeapTupleMayBeUpdated, which
@@ -3436,13 +3438,15 @@ simple_heap_delete(Relation relation, ItemPointer tid)
 HTSU_Result
 heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
-			HeapUpdateFailureData *hufd, LockTupleMode *lockmode)
+			HeapUpdateFailureData *hufd, Bitmapset **unchanged_ind_cols,
+			LockTupleMode *lockmode)
 {
 	HTSU_Result result;
 	TransactionId xid = GetCurrentTransactionId();
 	Bitmapset  *hot_attrs;
 	Bitmapset  *key_attrs;
 	Bitmapset  *id_attrs;
+	Bitmapset  *indirect_attrs;
 	ItemId		lp;
 	HeapTupleData oldtup;
 	HeapTuple	heaptup;
@@ -3500,10 +3504,12 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	 * Note that we get a copy here, so we need not worry about relcache flush
 	 * happening midway through.
 	 */
-	hot_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_ALL);
+	hot_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_HOT);
 	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
 	id_attrs = RelationGetIndexAttrBitmap(relation,
 										  INDEX_ATTR_BITMAP_IDENTITY_KEY);
+	indirect_attrs = RelationGetIndexAttrBitmap(relation,
+												INDEX_ATTR_BITMAP_INDIRECT_INDEXES);
 
 	block = ItemPointerGetBlockNumber(otid);
 	buffer = ReadBuffer(relation, block);
@@ -3561,9 +3567,11 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	 * is updates that don't manipulate key columns, not those that
 	 * serendipitiously arrive at the same key values.
 	 */
-	HeapSatisfiesHOTandKeyUpdate(relation, hot_attrs, key_attrs, id_attrs,
+	HeapSatisfiesHOTandKeyUpdate(relation,
+								 hot_attrs, key_attrs, id_attrs, indirect_attrs,
 								 &satisfies_hot, &satisfies_key,
-								 &satisfies_id, &oldtup, newtup);
+								 &satisfies_id, unchanged_ind_cols,
+								 &oldtup, newtup);
 	if (satisfies_key)
 	{
 		*lockmode = LockTupleNoKeyExclusive;
@@ -3803,6 +3811,7 @@ l2:
 		bms_free(hot_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
+		bms_free(indirect_attrs);
 		return result;
 	}
 
@@ -4356,41 +4365,53 @@ heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
  * Check which columns are being updated.
  *
  * This simultaneously checks conditions for HOT updates, for FOR KEY
- * SHARE updates, and REPLICA IDENTITY concerns.  Since much of the time they
- * will be checking very similar sets of columns, and doing the same tests on
- * them, it makes sense to optimize and do them together.
+ * SHARE updates, for REPLICA IDENTITY concerns, and for indirect indexing
+ * concerns.  Since much of the time they will be checking very similar sets of
+ * columns, and doing the same tests on them, it makes sense to optimize and do
+ * them together.
  *
- * We receive three bitmapsets comprising the three sets of columns we're
+ * We receive four bitmapsets comprising the four sets of columns we're
  * interested in.  Note these are destructively modified; that is OK since
  * this is invoked at most once in heap_update.
  *
- * hot_result is set to TRUE if it's okay to do a HOT update (i.e. it does not
- * modified indexed columns); key_result is set to TRUE if the update does not
- * modify columns used in the key; id_result is set to TRUE if the update does
- * not modify columns in any index marked as the REPLICA IDENTITY.
+ * satisfies_hot is set to TRUE if it's okay to do a HOT update (i.e. it does
+ * not modified indexed columns); satisfies_key is set to TRUE if the update
+ * does not modify columns used in the key; satisfies_id is set to TRUE if the
+ * update does not modify columns in any index marked as the REPLICA IDENTITY.
+ *
+ * unchanged_attrs is an output bitmapset that has a bit set if the
+ * corresponding column is indexed by an indirect index and is not modified.
+ * Note that because system columns cannot be indexed by indirect indexes,
+ * these values are not shifted by FirstLowInvalidHeapAttributeNumber.
  */
 static void
 HeapSatisfiesHOTandKeyUpdate(Relation relation, Bitmapset *hot_attrs,
 							 Bitmapset *key_attrs, Bitmapset *id_attrs,
+							 Bitmapset *indirect_attrs,
 							 bool *satisfies_hot, bool *satisfies_key,
-							 bool *satisfies_id,
+							 bool *satisfies_id, Bitmapset **unchanged_attrs,
 							 HeapTuple oldtup, HeapTuple newtup)
 {
 	int			next_hot_attnum;
 	int			next_key_attnum;
 	int			next_id_attnum;
+	int			next_indirect_attnum;
 	bool		hot_result = true;
 	bool		key_result = true;
 	bool		id_result = true;
+	bool		collecting_indirect;
 
 	/* If REPLICA IDENTITY is set to FULL, id_attrs will be empty. */
 	Assert(bms_is_subset(id_attrs, key_attrs));
-	Assert(bms_is_subset(key_attrs, hot_attrs));
+
+	/* Waste no time on indirect indexes if there aren't any */
+	collecting_indirect = (unchanged_attrs != NULL) &&
+		!bms_is_empty(indirect_attrs);
 
 	/*
 	 * If one of these sets contains no remaining bits, bms_first_member will
 	 * return -1, and after adding FirstLowInvalidHeapAttributeNumber (which
-	 * is negative!)  we'll get an attribute number that can't possibly be
+	 * is negative!) we'll get an attribute number that can't possibly be
 	 * real, and thus won't match any actual attribute number.
 	 */
 	next_hot_attnum = bms_first_member(hot_attrs);
@@ -4399,24 +4420,43 @@ HeapSatisfiesHOTandKeyUpdate(Relation relation, Bitmapset *hot_attrs,
 	next_key_attnum += FirstLowInvalidHeapAttributeNumber;
 	next_id_attnum = bms_first_member(id_attrs);
 	next_id_attnum += FirstLowInvalidHeapAttributeNumber;
+	next_indirect_attnum = bms_first_member(indirect_attrs);
+	next_indirect_attnum += FirstLowInvalidHeapAttributeNumber;
 
 	for (;;)
 	{
 		bool		changed;
 		int			check_now;
 
+		/* Set to a value greater than what can be attained normally */
+		check_now = MaxAttrNumber + 1;
+
 		/*
-		 * Since the HOT attributes are a superset of the key attributes and
-		 * the key attributes are a superset of the id attributes, this logic
-		 * is guaranteed to identify the next column that needs to be checked.
+		 * Since the key attributes are a superset of the id attributes, this
+		 * logic is guaranteed to identify the next column that needs to be
+		 * checked.  We consider indirect attributes and HOT attributes
+		 * separately because they aren't supersets of anything.
 		 */
-		if (hot_result && next_hot_attnum > FirstLowInvalidHeapAttributeNumber)
-			check_now = next_hot_attnum;
-		else if (key_result && next_key_attnum > FirstLowInvalidHeapAttributeNumber)
+		if (key_result && next_key_attnum > FirstLowInvalidHeapAttributeNumber)
 			check_now = next_key_attnum;
 		else if (id_result && next_id_attnum > FirstLowInvalidHeapAttributeNumber)
 			check_now = next_id_attnum;
-		else
+
+		if (hot_result && next_hot_attnum > FirstLowInvalidHeapAttributeNumber)
+		{
+			if (next_hot_attnum < check_now)
+				check_now = next_hot_attnum;
+		}
+
+		if (collecting_indirect)
+		{
+			Assert(next_indirect_attnum > 0);
+			if (next_indirect_attnum < check_now)
+				check_now = next_indirect_attnum;
+		}
+
+		/* are we done? */
+		if (check_now == MaxAttrNumber + 1)
 			break;
 
 		/* See whether it changed. */
@@ -4430,13 +4470,33 @@ HeapSatisfiesHOTandKeyUpdate(Relation relation, Bitmapset *hot_attrs,
 				key_result = false;
 			if (check_now == next_id_attnum)
 				id_result = false;
-
-			/* if all are false now, we can stop checking */
-			if (!hot_result && !key_result && !id_result)
-				break;
 		}
 
 		/*
+		 * Deal with indirect indexes.  Set the bit in the output set if the
+		 * column did not change, and compute the next column, which is
+		 * necessary to determine whether we need to iterate again.
+		 */
+		if (collecting_indirect && check_now == next_indirect_attnum)
+		{
+			if (!changed)
+				*unchanged_attrs = bms_add_member(*unchanged_attrs,
+												  check_now);
+			next_indirect_attnum = bms_first_member(indirect_attrs);
+			/* are we done with these? */
+			if (next_indirect_attnum == -1)
+				collecting_indirect = false;
+			next_indirect_attnum += FirstLowInvalidHeapAttributeNumber;
+		}
+
+		/*
+		 * if HOT, key, id are all false now and we're not collecting
+		 * unchanged indirect index columns, we can stop checking.
+		 */
+		if (!hot_result && !key_result && !id_result && !collecting_indirect)
+			break;
+
+		/*
 		 * Advance the next attribute numbers for the sets that contain the
 		 * attribute we just checked.  As we work our way through the columns,
 		 * the next_attnum values will rise; but when each set becomes empty,
@@ -4483,7 +4543,7 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
 	result = heap_update(relation, otid, tup,
 						 GetCurrentCommandId(true), InvalidSnapshot,
 						 true /* wait for commit */ ,
-						 &hufd, &lockmode);
+						 &hufd, NULL, &lockmode);
 	switch (result)
 	{
 		case HeapTupleSelfUpdated:
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index fc4702c..07bf463 100644
--- a/src/backend/access/heap/tuptoaster.c
+++ b/src/backend/access/heap/tuptoaster.c
@@ -1614,6 +1614,7 @@ toast_save_datum(Relation rel, Datum value,
 			/* Only index relations marked as ready can be updated */
 			if (IndexIsReady(toastidxs[i]->rd_index))
 				index_insert(toastidxs[i], t_values, t_isnull,
+							 NULL,
 							 &(toasttup->t_self),
 							 toastrel,
 							 toastidxs[i]->rd_index->indisunique ?
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index 54b71cb..cf18fa0 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -189,10 +189,13 @@ bool
 index_insert(Relation indexRelation,
 			 Datum *values,
 			 bool *isnull,
+			 Datum *pkeyValues,
 			 ItemPointer heap_t_ctid,
 			 Relation heapRelation,
 			 IndexUniqueCheck checkUnique)
 {
+	ItemPointerData iptr;
+
 	RELATION_CHECKS;
 	CHECK_REL_PROCEDURE(aminsert);
 
@@ -201,8 +204,19 @@ index_insert(Relation indexRelation,
 									   (HeapTuple) NULL,
 									   InvalidBuffer);
 
+	/*
+	 * Indirect indexes use a fake item pointer constructed from the primary
+	 * key values; regular indexes store the actual heap item pointer.
+	 */
+	if (!indexRelation->rd_index->indisindirect)
+		ItemPointerCopy(heap_t_ctid, &iptr);
+	else
+		FAKE_CTID_FROM_PKVALUES(&iptr,
+								indexRelation->rd_index->indnatts,
+								pkeyValues);
+
 	return indexRelation->rd_amroutine->aminsert(indexRelation, values, isnull,
-												 heap_t_ctid, heapRelation,
+												 &iptr, heapRelation,
 												 checkUnique);
 }
 
@@ -570,6 +584,56 @@ index_getnext(IndexScanDesc scan, ScanDirection direction)
 	return NULL;				/* failure exit */
 }
 
+/*
+ * Return the primary key values from the next tuple in a indirect-index scan
+ *
+ * pkvals is an output array which must have been allocated by caller.
+ */
+void
+index_getnext_pkey(IndexScanDesc scan, ScanDirection direction, Datum *pkvals,
+				   bool *isDone)
+{
+	bool		found;
+	Datum		pkval;
+
+	SCAN_CHECKS;
+	CHECK_SCAN_PROCEDURE(amgettuple);
+
+	/*
+	 * The AM's amgettuple proc finds the next index entry matching the scan
+	 * keys, and puts the primary key value into scan->xs_ctup.t_self, from
+	 * which we extract it into the pkvals array.  It should also set
+	 * scan->xs_recheck and scan->xs_itup, though we pay no attention to those
+	 * fields here.
+	 */
+	found = scan->indexRelation->rd_amroutine->amgettuple(scan, direction);
+
+	/* Reset kill flag immediately for safety */
+	scan->kill_prior_tuple = false;
+
+	if (!found)
+	{
+		if (BufferIsValid(scan->xs_cbuf))
+		{
+			ReleaseBuffer(scan->xs_cbuf);
+			scan->xs_cbuf = InvalidBuffer;
+		}
+		*isDone = true;
+		return;
+	}
+	*isDone = false;
+
+	/*
+	 * XXX this assumes that Datum is 8 bytes. Which it is in my machine .. but
+	 * not elsewhere
+	 */
+	/* XXX see FAKE_CTID_FROM_PKVALUES */
+	pkval = (((Datum) scan->xs_ctup.t_self.ip_posid) >> 1) |
+		(((Datum) scan->xs_ctup.t_self.ip_blkid.bi_hi) << 15) |
+		(((Datum) scan->xs_ctup.t_self.ip_blkid.bi_lo >> 1) << 32);
+	pkvals[0] = pkval;
+}
+
 /* ----------------
  *		index_getbitmap - get all tuples at once from an index scan
  *
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index ef69290..eb4beef 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -92,7 +92,9 @@ static void _bt_vacuum_one_page(Relation rel, Buffer buffer, Relation heapRel);
  *		By here, itup is filled in, including the TID.
  *
  *		If checkUnique is UNIQUE_CHECK_NO or UNIQUE_CHECK_PARTIAL, this
- *		will allow duplicates.  Otherwise (UNIQUE_CHECK_YES or
+ *		will allow duplicates.  If it's UNIQUE_CHECK_INSERT_SINGLETON, the value
+ *		will only be inserted if there isn't already a tuple with that value.
+ *		Otherwise (UNIQUE_CHECK_YES or
  *		UNIQUE_CHECK_EXISTING) it will throw error for a duplicate.
  *		For UNIQUE_CHECK_EXISTING we merely run the duplicate check, and
  *		don't actually insert.
@@ -100,8 +102,8 @@ static void _bt_vacuum_one_page(Relation rel, Buffer buffer, Relation heapRel);
  *		The result value is only significant for UNIQUE_CHECK_PARTIAL:
  *		it must be TRUE if the entry is known unique, else FALSE.
  *		(In the current implementation we'll also return TRUE after a
- *		successful UNIQUE_CHECK_YES or UNIQUE_CHECK_EXISTING call, but
- *		that's just a coding artifact.)
+ *		successful UNIQUE_CHECK_YES, UNIQUE_CHECK_EXISTING or
+ *		UNIQUE_CHECK_INSERT_SINGLETON call, but that's just a coding artifact.)
  */
 bool
 _bt_doinsert(Relation rel, IndexTuple itup,
@@ -138,6 +140,21 @@ top:
 						true, stack, BT_WRITE, NULL);
 
 	/*
+	 * In insert-singleton mode, we must return without doing anything if the
+	 * value we're inserting already exists.
+	 */
+#if 0
+	if (checkUnique == UNIQUE_CHECK_INSERT_SINGLETON)
+	{
+		offset = _bt_binsrch( .. );
+		if (offset is valid and contains a tuple matching the scankey)
+			return true;
+		/* otherwise fall through to insert */
+	}
+#endif
+
+
+	/*
 	 * If we're not allowing duplicates, make sure the key isn't already in
 	 * the index.
 	 *
@@ -158,7 +175,8 @@ top:
 	 * let the tuple in and return false for possibly non-unique, or true for
 	 * definitely unique.
 	 */
-	if (checkUnique != UNIQUE_CHECK_NO)
+	if (checkUnique != UNIQUE_CHECK_NO &&
+		checkUnique != UNIQUE_CHECK_INSERT_SINGLETON)
 	{
 		TransactionId xwait;
 		uint32		speculativeToken;
@@ -167,6 +185,10 @@ top:
 		xwait = _bt_check_unique(rel, itup, heapRel, buf, offset, itup_scankey,
 								 checkUnique, &is_unique, &speculativeToken);
 
+		if (checkUnique == UNIQUE_CHECK_INSERT_SINGLETON &&
+			TransactionIdIsValid(xwait))
+			return true;
+
 		if (TransactionIdIsValid(xwait))
 		{
 			/* Have to wait for the other guy ... */
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index a264b92..29c6ae6 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -37,6 +37,7 @@ typedef struct
 {
 	bool		isUnique;
 	bool		haveDead;
+	bool		isIndirect;
 	Relation	heapRel;
 	BTSpool    *spool;
 
@@ -45,6 +46,8 @@ typedef struct
 	 * put into spool2 instead of spool in order to avoid uniqueness check.
 	 */
 	BTSpool    *spool2;
+	int16		pkNumKeys;
+	AttrNumber	pkAttnums[INDEX_MAX_KEYS];
 	double		indtuples;
 } BTBuildState;
 
@@ -98,6 +101,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amstorage = false;
 	amroutine->amclusterable = true;
 	amroutine->ampredlocks = true;
+	amroutine->amcanindirect = true;
 	amroutine->amkeytype = InvalidOid;
 
 	amroutine->ambuild = btbuild;
@@ -136,6 +140,25 @@ btbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	buildstate.heapRel = heap;
 	buildstate.spool = NULL;
 	buildstate.spool2 = NULL;
+	buildstate.isIndirect = indexInfo->ii_IsIndirect;
+	if (indexInfo->ii_IsIndirect)
+	{
+		Oid			pkOid;
+		Relation	pkRel;
+		int			i;
+
+		pkOid = RelationGetPrimaryKey(heap);
+		pkRel = index_open(pkOid, AccessShareLock);
+
+		buildstate.pkNumKeys = pkRel->rd_index->indnatts;
+		for (i = 0; i < buildstate.pkNumKeys; i++)
+			buildstate.pkAttnums[i] = pkRel->rd_index->indkey.values[i];
+		index_close(pkRel, AccessShareLock);
+	}
+	else
+	{
+		buildstate.pkNumKeys = 0;
+	}
 	buildstate.indtuples = 0;
 
 #ifdef BTREE_BUILD_STATS
@@ -213,18 +236,42 @@ btbuildCallback(Relation index,
 				void *state)
 {
 	BTBuildState *buildstate = (BTBuildState *) state;
+	ItemPointerData iptr;
+
+	if (buildstate->isIndirect)
+	{
+		Datum	pkValues[INDEX_MAX_KEYS];
+		int		i;
+		bool	isnull;
+
+		/*
+		 * XXX WAG: this is very slow in the general case, but OK if PK column
+		 * is first.
+		 */
+		for (i = 0; i < buildstate->pkNumKeys; i++)
+		{
+			pkValues[i] = heap_getattr(htup,
+									   buildstate->pkAttnums[i],
+									   RelationGetDescr(buildstate->heapRel),
+									   &isnull);
+			Assert(!isnull);
+		}
+		FAKE_CTID_FROM_PKVALUES(&iptr, buildstate->pkNumKeys, pkValues);
+	}
+	else
+		ItemPointerCopy(&htup->t_self, &iptr);
 
 	/*
 	 * insert the index tuple into the appropriate spool file for subsequent
 	 * processing
 	 */
 	if (tupleIsAlive || buildstate->spool2 == NULL)
-		_bt_spool(buildstate->spool, &htup->t_self, values, isnull);
+		_bt_spool(buildstate->spool, &iptr, values, isnull);
 	else
 	{
 		/* dead tuples are put into spool2 */
 		buildstate->haveDead = true;
-		_bt_spool(buildstate->spool2, &htup->t_self, values, isnull);
+		_bt_spool(buildstate->spool2, &iptr, values, isnull);
 	}
 
 	buildstate->indtuples += 1;
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index d570ae5..9378919 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -48,6 +48,7 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amstorage = false;
 	amroutine->amclusterable = false;
 	amroutine->ampredlocks = false;
+	amroutine->amcanindirect = false;
 	amroutine->amkeytype = InvalidOid;
 
 	amroutine->ambuild = spgbuild;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b0989..5ac79b5 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -623,6 +623,7 @@ UpdateIndexRelation(Oid indexoid,
 	values[Anum_pg_index_indrelid - 1] = ObjectIdGetDatum(heapoid);
 	values[Anum_pg_index_indnatts - 1] = Int16GetDatum(indexInfo->ii_NumIndexAttrs);
 	values[Anum_pg_index_indisunique - 1] = BoolGetDatum(indexInfo->ii_Unique);
+	values[Anum_pg_index_indisindirect - 1] = BoolGetDatum(indexInfo->ii_IsIndirect);
 	values[Anum_pg_index_indisprimary - 1] = BoolGetDatum(primary);
 	values[Anum_pg_index_indisexclusion - 1] = BoolGetDatum(isexclusion);
 	values[Anum_pg_index_indimmediate - 1] = BoolGetDatum(immediate);
@@ -769,6 +770,20 @@ index_create(Relation heapRelation,
 				 errmsg("concurrent index creation on system catalog tables is not supported")));
 
 	/*
+	 * indirect indexes are forbidden on system catalogs, and they obviously cannot
+	 * be primary keys either.
+	 */
+	if (indexInfo->ii_IsIndirect && IsSystemRelation(heapRelation))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("indirect index creation on system catalog tables is not supported")));
+	if (indexInfo->ii_IsIndirect && isprimary)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("primary key indexes cannot be indirect")));
+	/* XXX other restrictions needed? */
+
+	/*
 	 * This case is currently not supported, but there's no way to ask for it
 	 * in the grammar anyway, so it can't happen.
 	 */
@@ -1011,6 +1026,14 @@ index_create(Relation heapRelation,
 			Assert(!initdeferred);
 		}
 
+		/* Store dependency on primary key index, if needed */
+		if (indexInfo->ii_IsIndirect)
+		{
+			ObjectAddressSet(referenced, RelationRelationId,
+							 RelationGetPrimaryKey(heapRelation));
+			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		}
+
 		/* Store dependency on collations */
 		/* The default collation is pinned, so don't bother recording it */
 		for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
@@ -1681,6 +1704,7 @@ BuildIndexInfo(Relation index)
 
 	/* other info */
 	ii->ii_Unique = indexStruct->indisunique;
+	ii->ii_IsIndirect = indexStruct->indisindirect;
 	ii->ii_ReadyForInserts = IndexIsReady(indexStruct);
 	/* assume not doing speculative insertion for now */
 	ii->ii_UniqueOps = NULL;
@@ -3161,6 +3185,7 @@ validate_index_heapscan(Relation heapRelation,
 			index_insert(indexRelation,
 						 values,
 						 isnull,
+						 NULL,	/* FIXME need to PK values here */
 						 &rootTuple,
 						 heapRelation,
 						 indexInfo->ii_Unique ?
@@ -3564,7 +3589,9 @@ reindex_relation(Oid relid, int flags, int options)
 
 	/* Ensure rd_indexattr is valid; see comments for RelationSetIndexList */
 	if (is_pg_class)
+	{
 		(void) RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_ALL);
+	}
 
 	PG_TRY();
 	{
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index b9fe102..f00f446 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -136,6 +136,7 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
 		index_insert(relationDescs[i],	/* index relation */
 					 values,	/* array of index Datums */
 					 isnull,	/* is-null flags */
+					 NULL,		/* catalogs never had indirect indexes */
 					 &(heapTuple->t_self),		/* tid of heap tuple */
 					 heapRelation,
 					 relationDescs[i]->rd_index->indisunique ?
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 2131226..414ec61 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -1531,14 +1531,14 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	reindex_relation(OIDOldHeap, reindex_flags, 0);
 
 	/*
-	 * If the relation being rebuild is pg_class, swap_relation_files()
+	 * If the relation being rebuilt is pg_class, swap_relation_files()
 	 * couldn't update pg_class's own pg_class entry (check comments in
 	 * swap_relation_files()), thus relfrozenxid was not updated. That's
 	 * annoying because a potential reason for doing a VACUUM FULL is a
 	 * imminent or actual anti-wraparound shutdown.  So, now that we can
-	 * access the new relation using it's indices, update relfrozenxid.
+	 * access the new relation using its indices, update relfrozenxid.
 	 * pg_class doesn't have a toast relation, so we don't need to update the
-	 * corresponding toast relation. Not that there's little point moving all
+	 * corresponding toast relation. Note that there's little point moving all
 	 * relfrozenxid updates here since swap_relation_files() needs to write to
 	 * pg_class for non-mapped relations anyway.
 	 */
diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c
index 26f9114..5f1a7b7 100644
--- a/src/backend/commands/constraint.c
+++ b/src/backend/commands/constraint.c
@@ -164,7 +164,7 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 		 * correct even if t_self is now dead, because that is the TID the
 		 * index will know about.
 		 */
-		index_insert(indexRel, values, isnull, &(new_row->t_self),
+		index_insert(indexRel, values, isnull, NULL, &(new_row->t_self),
 					 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
 	}
 	else
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index d590165..9c8b96d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2646,7 +2646,8 @@ CopyFrom(CopyState cstate)
 															   estate,
 															   false,
 															   NULL,
-															   NIL);
+															   NIL,
+															   NULL);
 
 					/* AFTER ROW INSERT Triggers */
 					ExecARInsertTriggers(estate, resultRelInfo, tuple,
@@ -2793,7 +2794,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 			ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false);
 			recheckIndexes =
 				ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self),
-									  estate, false, NULL, NIL);
+									  estate, false, NULL, NIL, NULL);
 			ExecARInsertTriggers(estate, resultRelInfo,
 								 bufferedTuples[i],
 								 recheckIndexes);
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 0a669d9..853f155 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -884,6 +884,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_IndexScan:
 			pname = sname = "Index Scan";
 			break;
+		case T_IndirectIndexScan:
+			pname = sname = "Indirect Index Scan";
+			break;
 		case T_IndexOnlyScan:
 			pname = sname = "Index Only Scan";
 			break;
@@ -1099,6 +1102,15 @@ ExplainNode(PlanState *planstate, List *ancestors,
 				ExplainScanTarget((Scan *) indexscan, es);
 			}
 			break;
+		case T_IndirectIndexScan:
+			{
+				IndirectIndexScan *indexscan = (IndirectIndexScan *) plan;
+				ExplainIndexScanDetails(indexscan->indexid,
+										indexscan->indexorderdir,
+										es);
+				ExplainScanTarget((Scan *) indexscan, es);
+			}
+			break;
 		case T_IndexOnlyScan:
 			{
 				IndexOnlyScan *indexonlyscan = (IndexOnlyScan *) plan;
@@ -1301,6 +1313,19 @@ ExplainNode(PlanState *planstate, List *ancestors,
 				show_instrumentation_count("Rows Removed by Filter", 1,
 										   planstate, es);
 			break;
+		case T_IndirectIndexScan:
+			show_scan_qual(((IndirectIndexScan *) plan)->indexqualorig,
+						   "Index Cond", planstate, ancestors, es);
+			if (((IndirectIndexScan *) plan)->indexqualorig)
+				show_instrumentation_count("Rows Removed by Index Recheck", 2,
+										   planstate, es);
+			show_scan_qual(((IndirectIndexScan *) plan)->indexorderbyorig,
+						   "Order By", planstate, ancestors, es);
+			show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
+			if (plan->qual)
+				show_instrumentation_count("Rows Removed by Filter", 1,
+										   planstate, es);
+			break;
 		case T_IndexOnlyScan:
 			show_scan_qual(((IndexOnlyScan *) plan)->indexqual,
 						   "Index Cond", planstate, ancestors, es);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index eeb2b1f..4a7f79b 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -522,6 +522,11 @@ DefineIndex(Oid relationId,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 		errmsg("access method \"%s\" does not support exclusion constraints",
 			   accessMethodName)));
+	if (stmt->isindirect && !amRoutine->amcanindirect)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("access method \"%s\" does not support indirect indexes",
+						accessMethodName)));
 
 	amcanorder = amRoutine->amcanorder;
 	amoptions = amRoutine->amoptions;
@@ -557,6 +562,7 @@ DefineIndex(Oid relationId,
 	indexInfo->ii_ExclusionProcs = NULL;
 	indexInfo->ii_ExclusionStrats = NULL;
 	indexInfo->ii_Unique = stmt->unique;
+	indexInfo->ii_IsIndirect = stmt->isindirect;
 	/* In a concurrent build, mark it not-ready-for-inserts */
 	indexInfo->ii_ReadyForInserts = !stmt->concurrent;
 	indexInfo->ii_Concurrent = stmt->concurrent;
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 51edd4c..799b4b6 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -19,6 +19,7 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execIndexing.o execJunk.o \
        nodeBitmapAnd.o nodeBitmapOr.o \
        nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeCustom.o nodeGather.o \
        nodeHash.o nodeHashjoin.o nodeIndexscan.o nodeIndexonlyscan.o \
+       nodeIndirectIndexscan.o \
        nodeLimit.o nodeLockRows.o \
        nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeModifyTable.o \
        nodeNestloop.o nodeFunctionscan.o nodeRecursiveunion.o nodeResult.o \
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 2587ef7..bcae700 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -32,6 +32,7 @@
 #include "executor/nodeHashjoin.h"
 #include "executor/nodeIndexonlyscan.h"
 #include "executor/nodeIndexscan.h"
+#include "executor/nodeIndirectIndexscan.h"
 #include "executor/nodeLimit.h"
 #include "executor/nodeLockRows.h"
 #include "executor/nodeMaterial.h"
@@ -170,6 +171,10 @@ ExecReScan(PlanState *node)
 			ExecReScanIndexScan((IndexScanState *) node);
 			break;
 
+		case T_IndirectIndexScanState:
+			ExecReScanIndirectIndexScan((IndirectIndexScanState *) node);
+			break;
+
 		case T_IndexOnlyScanState:
 			ExecReScanIndexOnlyScan((IndexOnlyScanState *) node);
 			break;
@@ -300,6 +305,10 @@ ExecMarkPos(PlanState *node)
 			ExecIndexMarkPos((IndexScanState *) node);
 			break;
 
+		case T_IndirectIndexScanState:
+			ExecIndirectIndexMarkPos((IndirectIndexScanState *) node);
+			break;
+
 		case T_IndexOnlyScanState:
 			ExecIndexOnlyMarkPos((IndexOnlyScanState *) node);
 			break;
@@ -349,6 +358,9 @@ ExecRestrPos(PlanState *node)
 			ExecIndexRestrPos((IndexScanState *) node);
 			break;
 
+		case T_IndirectIndexScanState:
+			ExecIndirectIndexRestrPos((IndirectIndexScanState *) node);
+
 		case T_IndexOnlyScanState:
 			ExecIndexOnlyRestrPos((IndexOnlyScanState *) node);
 			break;
@@ -393,6 +405,7 @@ ExecSupportsMarkRestore(Path *pathnode)
 	switch (pathnode->pathtype)
 	{
 		case T_IndexScan:
+		case T_IndirectIndexScan:
 		case T_IndexOnlyScan:
 		case T_Material:
 		case T_Sort:
@@ -491,6 +504,11 @@ ExecSupportsBackwardScan(Plan *node)
 			return IndexSupportsBackwardScan(((IndexScan *) node)->indexid) &&
 				TargetListSupportsBackwardScan(node->targetlist);
 
+		case T_IndirectIndexScan:
+			/* FIXME this is expected to work, but is untested */
+			return IndexSupportsBackwardScan(((IndirectIndexScan *) node)->indexid) &&
+				TargetListSupportsBackwardScan(node->targetlist);
+
 		case T_IndexOnlyScan:
 			return IndexSupportsBackwardScan(((IndexOnlyScan *) node)->indexid) &&
 				TargetListSupportsBackwardScan(node->targetlist);
@@ -537,8 +555,8 @@ TargetListSupportsBackwardScan(List *targetlist)
 }
 
 /*
- * An IndexScan or IndexOnlyScan node supports backward scan only if the
- * index's AM does.
+ * An IndexScan, IndirectIndexScan or IndexOnlyScan node supports backward scan
+ * only if the index's AM does.
  */
 static bool
 IndexSupportsBackwardScan(Oid indexid)
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 009c1b7..302fc60 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -273,7 +273,8 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 					  EState *estate,
 					  bool noDupErr,
 					  bool *specConflict,
-					  List *arbiterIndexes)
+					  List *arbiterIndexes,
+					  Bitmapset *unchangedAttrs)
 {
 	List	   *result = NIL;
 	ResultRelInfo *resultRelInfo;
@@ -285,6 +286,7 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 	ExprContext *econtext;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
+	Datum		pkeyValues[INDEX_MAX_KEYS];
 
 	/*
 	 * Get information from the result relation info structure.
@@ -348,6 +350,27 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 		}
 
 		/*
+		 * For indirect indexes, verify whether the indexed attributes have
+		 * changed; if they have not, skip the insertion.
+		 */
+		if (indexInfo->ii_IsIndirect)
+		{
+			int		j;
+			bool	may_skip_insertion = true;
+
+			for (j = 0; j < indexInfo->ii_NumIndexAttrs; j++)
+			{
+				if (bms_is_member(indexInfo->ii_KeyAttrNumbers[j], unchangedAttrs))
+					continue;
+				may_skip_insertion = false;
+			}
+
+			/* may skip insertion if no indexed attribute changed value */
+			if (may_skip_insertion)
+				continue;
+		}
+
+		/*
 		 * FormIndexDatum fills in its values and isnull parameters with the
 		 * appropriate values for the column(s) of the index.
 		 */
@@ -357,6 +380,11 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 					   values,
 					   isnull);
 
+		/* If this is the primary key, save the values for later */
+		if (i == 0)
+			/* XXX probably need datumCopy here instead */
+			memcpy(pkeyValues, values, sizeof(Datum) * INDEX_MAX_KEYS);
+
 		/* Check whether to apply noDupErr to this index */
 		applyNoDupErr = noDupErr &&
 			(arbiterIndexes == NIL ||
@@ -389,6 +417,7 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 			index_insert(indexRelation, /* index relation */
 						 values,	/* array of index Datums */
 						 isnull,	/* null flags */
+						 pkeyValues,	/* values of primary key */
 						 tupleid,		/* tid of heap tuple */
 						 heapRelation,	/* heap relation */
 						 checkUnique);	/* type of uniqueness check to do */
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index 554244f..9df9160 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -93,6 +93,7 @@
 #include "executor/nodeHashjoin.h"
 #include "executor/nodeIndexonlyscan.h"
 #include "executor/nodeIndexscan.h"
+#include "executor/nodeIndirectIndexscan.h"
 #include "executor/nodeLimit.h"
 #include "executor/nodeLockRows.h"
 #include "executor/nodeMaterial.h"
@@ -203,6 +204,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 													 estate, eflags);
 			break;
 
+		case T_IndirectIndexScan:
+			result = (PlanState *) ExecInitIndirectIndexScan((IndirectIndexScan *) node,
+															 estate, eflags);
+			break;
+
 		case T_IndexOnlyScan:
 			result = (PlanState *) ExecInitIndexOnlyScan((IndexOnlyScan *) node,
 														 estate, eflags);
@@ -427,6 +433,10 @@ ExecProcNode(PlanState *node)
 			result = ExecIndexScan((IndexScanState *) node);
 			break;
 
+		case T_IndirectIndexScanState:
+			result = ExecIndirectIndexScan((IndirectIndexScanState *) node);
+			break;
+
 		case T_IndexOnlyScanState:
 			result = ExecIndexOnlyScan((IndexOnlyScanState *) node);
 			break;
@@ -677,6 +687,10 @@ ExecEndNode(PlanState *node)
 			ExecEndIndexScan((IndexScanState *) node);
 			break;
 
+		case T_IndirectIndexScanState:
+			ExecEndIndirectIndexScan((IndirectIndexScanState *) node);
+			break;
+
 		case T_IndexOnlyScanState:
 			ExecEndIndexOnlyScan((IndexOnlyScanState *) node);
 			break;
diff --git a/src/backend/executor/nodeIndirectIndexscan.c b/src/backend/executor/nodeIndirectIndexscan.c
new file mode 100644
index 0000000..d8ecd19
--- /dev/null
+++ b/src/backend/executor/nodeIndirectIndexscan.c
@@ -0,0 +1,488 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeIndirectIndexscan.c
+ *	  Routines to support indirect index scans
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/executor/nodeIndirectIndexscan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+/*
+ * INTERFACE ROUTINES
+ *		ExecIndirectIndexScan		scans an index
+ *		IndirectIndexNext			retrieve next tuple
+ *		ExecInitIndirectIndexScan	creates and initializes state info.
+ *		ExecReScanIndirectIndexScan	rescans the indexed relation.
+ *		ExecEndIndirectIndexScan	releases all storage.
+ *		ExecIndirectIndexMarkPos	marks scan position.
+ *		ExecIndirectIndexRestrPos	restores scan position.
+ */
+#include "postgres.h"
+
+#include "access/relscan.h"
+#include "access/visibilitymap.h"
+#include "executor/execdebug.h"
+#include "executor/nodeIndirectIndexscan.h"
+#include "executor/nodeIndexscan.h"
+#include "storage/bufmgr.h"
+#include "storage/predicate.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+
+
+/* ----------------------------------------------------------------
+ *		IndirectIndexNext
+ *
+ *		Retrieve a tuple from the IndirectIndexScan node's index.
+ * ----------------------------------------------------------------
+ */
+static TupleTableSlot *
+IndirectIndexNext(IndirectIndexScanState *node)
+{
+	EState	   *estate;
+	ScanDirection direction;
+	TupleTableSlot *slot;
+
+	/*
+	 * extract necessary information from index scan node
+	 */
+	estate = node->ss.ps.state;
+	direction = estate->es_direction;
+	/* flip direction if this is an overall backward scan */
+	if (ScanDirectionIsBackward(((IndirectIndexScan *) node->ss.ps.plan)->indexorderdir))
+	{
+		if (ScanDirectionIsForward(direction))
+			direction = BackwardScanDirection;
+		else if (ScanDirectionIsBackward(direction))
+			direction = ForwardScanDirection;
+	}
+	//econtext = node->ss.ps.ps_ExprContext;
+	slot = node->ss.ss_ScanTupleSlot;
+
+	/*----------
+	 * OK, now that we have what we need, fetch the next tuple.
+	 *
+	 * if we don't have a current indirect tuple:
+	 *     obtain tuple from indirect index into slot
+	 *     if no tuple is obtained, return "we're done"
+	 *     apply rescan on PK, set tuple as scankeys
+	 * obtain tuple from PK
+	 * if no tuple is obtained:
+	 *     clear the indirect slot
+	 *     start over
+	 * return tuple
+	 *----------
+	 */
+	for (;;)
+	{
+		HeapTuple	pktuple;
+
+		/*
+		 * If we haven't read a tuple from the indirect index, do so now and
+		 * install it as scan keys into the primary key scan.
+		 */
+		if (!node->iiss_HaveIndirectTuple)
+		{
+			ScanKeyData pkkeys[INDEX_MAX_KEYS];
+			Datum	pkvalues[INDEX_MAX_KEYS];
+			int		nkeys = node->iiss_PKRelationDesc->rd_index->indnatts;
+			bool	isDone;
+			int		i;
+
+			/*
+			 * Obtain one tuple from the indirect index.  If there are no more
+			 * tuples in the indirect index, then the scan is done.
+			 */
+			index_getnext_pkey(node->iiss_IndScanDesc, direction, pkvalues, &isDone);
+			if (isDone)
+				return ExecClearTuple(slot);	/* no more tuples */
+
+			node->iiss_HaveIndirectTuple = true;
+
+			/* Install scan keys, and get the primary key scan started */
+			for (i = 0; i < nkeys; i++)
+			{
+				ScanKeyInit(&pkkeys[i],
+							i + 1,
+							BTEqualStrategyNumber,
+							node->iiss_EqualFuncs[i],
+							pkvalues[i]);
+			}
+
+			index_rescan(node->iiss_PKScanDesc, pkkeys, nkeys,
+						 NULL, 0);
+		}
+
+		/*
+		 * By now, we have a valid tuple from the indirect index and need to
+		 * read (more?) tuples from the PK to return.
+		 */
+		pktuple = index_getnext(node->iiss_PKScanDesc, ForwardScanDirection);
+		if (pktuple == NULL)
+		{
+			/*
+			 * If no tuples are obtained from the PK for the current tuple,
+			 * start over making sure that the indirect scan will advance.
+			 */
+			node->iiss_HaveIndirectTuple = false;
+			continue;
+		}
+
+		// FIXME here we apply ExecQual(), perhaps
+
+		return ExecStoreTuple(pktuple, slot, InvalidBuffer, false);
+	}
+
+	return ExecClearTuple(slot);
+}
+
+/*
+ * IndirectIndexRecheck -- access method routine to recheck a tuple in
+ * EvalPlanQual
+ */
+static bool
+IndirectIndexRecheck(IndirectIndexScanState *node, TupleTableSlot *slot)
+{
+	elog(ERROR, "EvalPlanQual recheck is not supported in indirect index scans");
+	return false;			/* keep compiler quiet */
+}
+
+TupleTableSlot *
+ExecIndirectIndexScan(IndirectIndexScanState *node)
+{
+	/*
+	 * If we have runtime keys and they've not already been set up, do it now.
+	 */
+	if (node->iiss_NumRuntimeKeys != 0 && !node->iiss_RuntimeKeysReady)
+		ExecReScan((PlanState *) node);
+
+	Assert(node->iiss_NumOrderByKeys == 0);	/* FIXME and test */
+	return ExecScan(&node->ss,
+					(ExecScanAccessMtd) IndirectIndexNext,
+					(ExecScanRecheckMtd) IndirectIndexRecheck);
+}
+
+/* ----------------------------------------------------------------
+ *		ExecReScanIndirectIndexScan(node)
+ *
+ *		Recalculates the values of any scan keys whose value depends on
+ *		information known at runtime, then rescans the indexed relation.
+ *
+ *		Updating the scan key was formerly done separately in
+ *		ExecUpdateIndexScanKeys.  Integrating it into ReScan makes
+ *		rescans of indices and relations/general streams more uniform.
+ * ----------------------------------------------------------------
+ */
+void
+ExecReScanIndirectIndexScan(IndirectIndexScanState *node)
+{
+	/*
+	 * If we are doing runtime key calculations (ie, any of the index key
+	 * values weren't simple Consts), compute the new key values.  But first,
+	 * reset the context so we don't leak memory as each outer tuple is
+	 * scanned.  Note this assumes that we will recalculate *all* runtime keys
+	 * on each call.
+	 */
+	if (node->iiss_NumRuntimeKeys != 0)
+	{
+		ExprContext *econtext = node->iiss_RuntimeContext;
+
+		ResetExprContext(econtext);
+		ExecIndexEvalRuntimeKeys(econtext,
+								 node->iiss_RuntimeKeys,
+								 node->iiss_NumRuntimeKeys);
+	}
+	node->iiss_RuntimeKeysReady = true;
+
+	/* XXX We don't support reordering of results */
+
+	/* reset index scan */
+	Assert(node->iiss_NumOrderByKeys == 0);	/* FIXME and test */
+	index_rescan(node->iiss_IndScanDesc,
+				 node->iiss_ScanKeys, node->iiss_NumScanKeys,
+				 node->iiss_OrderByKeys, node->iiss_NumOrderByKeys);
+
+	ExecScanReScan(&node->ss);
+}
+
+/* ----------------------------------------------------------------
+ *		ExecEndIndirectIndexScan
+ * ----------------------------------------------------------------
+ */
+void
+ExecEndIndirectIndexScan(IndirectIndexScanState *node)
+{
+	Relation	indexRelationDesc;
+	Relation	pkRelationDesc;
+	IndexScanDesc indexScanDesc;
+	IndexScanDesc pkScanDesc;
+	Relation	relation;
+
+	/*
+	 * extract information from the node
+	 */
+	indexRelationDesc = node->iiss_IndRelationDesc;
+	pkRelationDesc = node->iiss_PKRelationDesc;
+	indexScanDesc = node->iiss_IndScanDesc;
+	pkScanDesc = node->iiss_PKScanDesc;
+	relation = node->ss.ss_currentRelation;
+
+	/* clear out tuple table slots */
+	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+	ExecClearTuple(node->ss.ss_ScanTupleSlot);
+
+	/* close the index relations (no-op if we didn't open them) */
+	if (indexScanDesc)
+		index_endscan(indexScanDesc);
+	if (pkScanDesc)
+		index_endscan(pkScanDesc);
+	if (indexRelationDesc)
+		index_close(indexRelationDesc, NoLock);
+	if (pkRelationDesc)
+		index_close(pkRelationDesc, NoLock);
+
+	/* close the heap relation */
+	ExecCloseScanRelation(relation);
+}
+
+/* ----------------------------------------------------------------
+ *		ExecIndirectIndexMarkPos
+ * ----------------------------------------------------------------
+ */
+void
+ExecIndirectIndexMarkPos(IndirectIndexScanState *node)
+{
+	index_markpos(node->iiss_IndScanDesc);
+	node->iiss_HaveIndirectTuple = false;	/* necessary? */
+}
+
+/* ----------------------------------------------------------------
+ *		ExecIndirectIndexRestrPos
+ * ----------------------------------------------------------------
+ */
+void
+ExecIndirectIndexRestrPos(IndirectIndexScanState *node)
+{
+	index_restrpos(node->iiss_IndScanDesc);
+	node->iiss_HaveIndirectTuple = false;	/* necessary? */
+}
+
+/* ----------------------------------------------------------------
+ *		ExecInitIndirectIndexScan
+ *
+ *		Initializes the index scan's state information, creates
+ *		scan keys, and opens the base and index relations.
+ *
+ *		Note: indirect index scans have 3 sets of state information
+ *		      because we have to keep track of the base relation, the
+ *		      indirect index relation itself, and the primary key index
+ *		      relation.
+ * ----------------------------------------------------------------
+ */
+IndirectIndexScanState *
+ExecInitIndirectIndexScan(IndirectIndexScan *node, EState *estate, int eflags)
+{
+	IndirectIndexScanState *indexstate;
+	Relation	currentRelation;
+	bool		relistarget;
+	int			i;
+
+	/*
+	 * create state structure
+	 */
+	indexstate = makeNode(IndirectIndexScanState);
+	indexstate->ss.ps.plan = (Plan *) node;
+	indexstate->ss.ps.state = estate;
+
+	/*
+	 * Miscellaneous initialization
+	 *
+	 * create expression context for node
+	 */
+	ExecAssignExprContext(estate, &indexstate->ss.ps);
+
+	indexstate->ss.ps.ps_TupFromTlist = false;
+
+	/*
+	 * initialize child expressions
+	 */
+	indexstate->ss.ps.targetlist = (List *)
+		ExecInitExpr((Expr *) node->scan.plan.targetlist,
+					 (PlanState *) indexstate);
+	indexstate->ss.ps.qual = (List *)
+		ExecInitExpr((Expr *) node->scan.plan.qual,
+					 (PlanState *) indexstate);
+	indexstate->indexqual = (List *)
+		ExecInitExpr((Expr *) node->indexqual,
+					 (PlanState *) indexstate);
+
+	/*
+	 * tuple table initialization
+	 */
+	ExecInitResultTupleSlot(estate, &indexstate->ss.ps);
+	ExecInitScanTupleSlot(estate, &indexstate->ss);
+
+	/*
+	 * open the base relation and acquire appropriate lock on it.
+	 */
+	currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid, eflags);
+
+	indexstate->ss.ss_currentRelation = currentRelation;
+	indexstate->ss.ss_currentScanDesc = NULL;	/* no heap scan here */
+
+	/*
+	 * get the scan type from the relation descriptor.
+	 */
+	ExecAssignScanType(&indexstate->ss, RelationGetDescr(currentRelation));
+
+	/*
+	 * Initialize result tuple type and projection info.
+	 */
+	ExecAssignResultTypeFromTL(&indexstate->ss.ps);
+	ExecAssignScanProjectionInfo(&indexstate->ss);
+
+	/*
+	 * If we are just doing EXPLAIN (ie, aren't going to run the plan), stop
+	 * here.  This allows an index-advisor plugin to EXPLAIN a plan containing
+	 * references to nonexistent indexes.
+	 */
+	if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
+		return indexstate;
+
+	/*
+	 * Open the index relations.
+	 *
+	 * If the parent table is one of the target relations of the query, then
+	 * InitPlan already opened and write-locked the indexes, so we can avoid
+	 * taking another lock here.  Otherwise we need normal reader's locks.
+	 */
+	relistarget = ExecRelationIsTargetRelation(estate, node->scan.scanrelid);
+	indexstate->iiss_IndRelationDesc =
+		index_open(node->indexid, relistarget ? NoLock : AccessShareLock);
+	indexstate->iiss_PKRelationDesc =
+		index_open(node->pkindexid, relistarget ? NoLock : AccessShareLock);
+
+	/*
+	 * Initialize index-specific scan state
+	 */
+	indexstate->iiss_HaveIndirectTuple = false;
+	indexstate->iiss_RuntimeKeysReady = false;
+	indexstate->iiss_RuntimeKeys = NULL;
+	indexstate->iiss_NumRuntimeKeys = 0;
+
+	/*
+	 * build the index scan keys from the index qualification
+	 */
+	ExecIndexBuildScanKeys((PlanState *) indexstate,
+						   indexstate->iiss_IndRelationDesc,
+						   node->indexqual,
+						   false,
+						   &indexstate->iiss_ScanKeys,
+						   &indexstate->iiss_NumScanKeys,
+						   &indexstate->iiss_RuntimeKeys,
+						   &indexstate->iiss_NumRuntimeKeys,
+						   NULL,	/* no ArrayKeys */
+						   NULL);
+
+	/*
+	 * any ORDER BY exprs have to be turned into scankeys in the same way
+	 */
+	Assert(indexstate->iiss_NumOrderByKeys == 0);	/* FIXME and test */
+	ExecIndexBuildScanKeys((PlanState *) indexstate,
+						   indexstate->iiss_IndRelationDesc,
+						   node->indexorderby,
+						   true,
+						   &indexstate->iiss_OrderByKeys,
+						   &indexstate->iiss_NumOrderByKeys,
+						   &indexstate->iiss_RuntimeKeys,
+						   &indexstate->iiss_NumRuntimeKeys,
+						   NULL,	/* no ArrayKeys */
+						   NULL);
+
+	/*
+	 * For the PK scan, initialize the equality func OIDs of each column's
+	 * datatype.
+	 */
+	indexstate->iiss_EqualFuncs = palloc(sizeof(Oid) *
+										 indexstate->iiss_PKRelationDesc->rd_index->indnatts);
+	for (i = 0; i < indexstate->iiss_PKRelationDesc->rd_index->indnatts; i++)
+	{
+		AttrNumber attnum;
+		Oid		opfamily;
+		Oid		datatype;
+		Oid		eq_op;
+		Oid		eq_proc;
+
+		attnum = indexstate->iiss_PKRelationDesc->rd_index->indkey.values[i];
+		datatype = currentRelation->rd_att->attrs[attnum - 1]->atttypid;
+		opfamily = indexstate->iiss_PKRelationDesc->rd_opfamily[i];
+		eq_op = get_opfamily_member(opfamily, datatype, datatype,
+									  BTEqualStrategyNumber);
+		if (!OidIsValid(eq_op))
+			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
+				 BTEqualStrategyNumber, datatype, datatype, opfamily);
+		eq_proc = get_opcode(eq_op);
+		if (!OidIsValid(eq_proc))
+			elog(ERROR, "missing oprcode for operator %u", eq_op);
+		indexstate->iiss_EqualFuncs[i] = eq_proc;
+	}
+
+	/* XXX initialize sort support?  See nodeIndexscan.c */
+
+	/*
+	 * If we have runtime keys, we need an ExprContext to evaluate them. The
+	 * node's standard context won't do because we want to reset that context
+	 * for every tuple.  So, build another context just like the other one...
+	 * -tgl 7/11/00
+	 */
+	if (indexstate->iiss_NumRuntimeKeys != 0)
+	{
+		ExprContext *stdecontext = indexstate->ss.ps.ps_ExprContext;
+
+		ExecAssignExprContext(estate, &indexstate->ss.ps);
+		indexstate->iiss_RuntimeContext = indexstate->ss.ps.ps_ExprContext;
+		indexstate->ss.ps.ps_ExprContext = stdecontext;
+	}
+	else
+	{
+		indexstate->iiss_RuntimeContext = NULL;
+	}
+
+	/*
+	 * Initialize scan descriptors.
+	 */
+	indexstate->iiss_IndScanDesc =
+		index_beginscan(currentRelation,
+						indexstate->iiss_IndRelationDesc,
+						estate->es_snapshot,
+						indexstate->iiss_NumScanKeys,
+						indexstate->iiss_NumOrderByKeys);
+	indexstate->iiss_PKScanDesc =
+		index_beginscan(currentRelation,
+						indexstate->iiss_PKRelationDesc,
+						estate->es_snapshot,
+						indexstate->iiss_PKRelationDesc->rd_index->indnatts,
+						0);
+
+	/*
+	 * If no run-time keys to calculate, go ahead and pass the scankeys to the
+	 * index AM.
+	 */
+	if (indexstate->iiss_NumRuntimeKeys == 0)
+		index_rescan(indexstate->iiss_IndScanDesc,
+					 indexstate->iiss_ScanKeys,
+					 indexstate->iiss_NumScanKeys,
+					 indexstate->iiss_OrderByKeys,
+					 indexstate->iiss_NumOrderByKeys);
+
+	/*
+	 * all done.
+	 */
+	return indexstate;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index a954610..f6d8497 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -500,7 +500,8 @@ ExecInsert(ModifyTableState *mtstate,
 			/* insert index entries for tuple */
 			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
 												 estate, true, &specConflict,
-												   arbiterIndexes);
+												   arbiterIndexes,
+												   NULL);
 
 			/* adjust the tuple's state accordingly */
 			if (!specConflict)
@@ -546,7 +547,8 @@ ExecInsert(ModifyTableState *mtstate,
 			if (resultRelInfo->ri_NumIndices > 0)
 				recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
 													   estate, false, NULL,
-													   arbiterIndexes);
+													   arbiterIndexes,
+													   NULL);
 		}
 	}
 
@@ -952,6 +954,7 @@ ExecUpdate(ItemPointer tupleid,
 	else
 	{
 		LockTupleMode lockmode;
+		Bitmapset  *unchangedAttrs = NULL;
 
 		/*
 		 * Constraints might reference the tableoid column, so initialize
@@ -995,7 +998,9 @@ lreplace:;
 							 estate->es_output_cid,
 							 estate->es_crosscheck_snapshot,
 							 true /* wait for commit */ ,
-							 &hufd, &lockmode);
+							 &hufd,
+							 &unchangedAttrs,
+							 &lockmode);
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
@@ -1085,7 +1090,8 @@ lreplace:;
 		 */
 		if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
 			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-												   estate, false, NULL, NIL);
+												   estate, false, NULL, NIL,
+												   unchangedAttrs);
 	}
 
 	if (canSetTag)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d973225..c0b6651 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -437,6 +437,34 @@ _copyIndexScan(const IndexScan *from)
 }
 
 /*
+ * _copyIndirectIndexScan
+ */
+static IndirectIndexScan *
+_copyIndirectIndexScan(const IndirectIndexScan *from)
+{
+	IndirectIndexScan  *newnode = makeNode(IndirectIndexScan);
+
+	/*
+	 * copy node superclass fields
+	 */
+	CopyScanFields((const Scan *) from, (Scan *) newnode);
+
+	/*
+	 * copy remainder of node
+	 */
+	COPY_SCALAR_FIELD(indexid);
+	COPY_SCALAR_FIELD(pkindexid);
+	COPY_NODE_FIELD(indexqual);
+	COPY_NODE_FIELD(indexqualorig);
+	COPY_NODE_FIELD(indexorderby);
+	COPY_NODE_FIELD(indexorderbyorig);
+	COPY_NODE_FIELD(indexorderbyops);
+	COPY_SCALAR_FIELD(indexorderdir);
+
+	return newnode;
+}
+
+/*
  * _copyIndexOnlyScan
  */
 static IndexOnlyScan *
@@ -3158,6 +3186,7 @@ _copyIndexStmt(const IndexStmt *from)
 	COPY_SCALAR_FIELD(indexOid);
 	COPY_SCALAR_FIELD(oldNode);
 	COPY_SCALAR_FIELD(unique);
+	COPY_SCALAR_FIELD(isindirect);
 	COPY_SCALAR_FIELD(primary);
 	COPY_SCALAR_FIELD(isconstraint);
 	COPY_SCALAR_FIELD(deferrable);
@@ -4431,6 +4460,9 @@ copyObject(const void *from)
 		case T_IndexScan:
 			retval = _copyIndexScan(from);
 			break;
+		case T_IndirectIndexScan:
+			retval = _copyIndirectIndexScan(from);
+			break;
 		case T_IndexOnlyScan:
 			retval = _copyIndexOnlyScan(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index edc1797..a60add1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1273,6 +1273,7 @@ _equalIndexStmt(const IndexStmt *a, const IndexStmt *b)
 	COMPARE_SCALAR_FIELD(indexOid);
 	COMPARE_SCALAR_FIELD(oldNode);
 	COMPARE_SCALAR_FIELD(unique);
+	COMPARE_SCALAR_FIELD(isindirect);
 	COMPARE_SCALAR_FIELD(primary);
 	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_SCALAR_FIELD(deferrable);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 7258c03..bbc5973 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -489,6 +489,23 @@ _outIndexScan(StringInfo str, const IndexScan *node)
 }
 
 static void
+_outIndirectIndexScan(StringInfo str, const IndirectIndexScan *node)
+{
+	WRITE_NODE_TYPE("INDIRECTINDEXSCAN");
+
+	_outScanInfo(str, (const Scan *) node);
+
+	WRITE_OID_FIELD(indexid);
+	WRITE_OID_FIELD(pkindexid);
+	WRITE_NODE_FIELD(indexqual);
+	WRITE_NODE_FIELD(indexqualorig);
+	WRITE_NODE_FIELD(indexorderby);
+	WRITE_NODE_FIELD(indexorderbyorig);
+	WRITE_NODE_FIELD(indexorderbyops);
+	WRITE_ENUM_FIELD(indexorderdir, ScanDirection);
+}
+
+static void
 _outIndexOnlyScan(StringInfo str, const IndexOnlyScan *node)
 {
 	WRITE_NODE_TYPE("INDEXONLYSCAN");
@@ -2451,6 +2468,7 @@ _outIndexStmt(StringInfo str, const IndexStmt *node)
 	WRITE_OID_FIELD(indexOid);
 	WRITE_OID_FIELD(oldNode);
 	WRITE_BOOL_FIELD(unique);
+	WRITE_BOOL_FIELD(isindirect);
 	WRITE_BOOL_FIELD(primary);
 	WRITE_BOOL_FIELD(isconstraint);
 	WRITE_BOOL_FIELD(deferrable);
@@ -3389,6 +3407,9 @@ outNode(StringInfo str, const void *obj)
 			case T_IndexScan:
 				_outIndexScan(str, obj);
 				break;
+			case T_IndirectIndexScan:
+				_outIndirectIndexScan(str, obj);
+				break;
 			case T_IndexOnlyScan:
 				_outIndexOnlyScan(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index d608530..a290008 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1671,6 +1671,28 @@ _readIndexScan(void)
 }
 
 /*
+ * _readIndirectIndexScan
+ */
+static IndirectIndexScan *
+_readIndirectIndexScan(void)
+{
+	READ_LOCALS(IndirectIndexScan);
+
+	ReadCommonScan(&local_node->scan);
+
+	READ_OID_FIELD(indexid);
+	READ_OID_FIELD(pkindexid);
+	READ_NODE_FIELD(indexqual);
+	READ_NODE_FIELD(indexqualorig);
+	READ_NODE_FIELD(indexorderby);
+	READ_NODE_FIELD(indexorderbyorig);
+	READ_NODE_FIELD(indexorderbyops);
+	READ_ENUM_FIELD(indexorderdir, ScanDirection);
+
+	READ_DONE();
+}
+
+/*
  * _readIndexOnlyScan
  */
 static IndexOnlyScan *
@@ -2465,6 +2487,8 @@ parseNodeString(void)
 		return_value = _readSampleScan();
 	else if (MATCH("INDEXSCAN", 9))
 		return_value = _readIndexScan();
+	else if (MATCH("INDIRECTINDEXSCAN", 17))
+		return_value = _readIndirectIndexScan();
 	else if (MATCH("INDEXONLYSCAN", 13))
 		return_value = _readIndexOnlyScan();
 	else if (MATCH("BITMAPINDEXSCAN", 15))
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 2952bfb..5ff3ebe 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -776,9 +776,10 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
 	 * and those we mustn't submit to add_path here.)
 	 *
 	 * Also, pick out the ones that are usable as bitmap scans.  For that, we
-	 * must discard indexes that don't support bitmap scans, and we also are
-	 * only interested in paths that have some selectivity; we should discard
-	 * anything that was generated solely for ordering purposes.
+	 * must discard indexes that don't support bitmap scans as well as
+	 * indirect indexes, and we also are only interested in paths that have
+	 * some selectivity; we should discard anything that was generated solely
+	 * for ordering purposes.
 	 */
 	foreach(lc, indexpaths)
 	{
@@ -788,6 +789,7 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
 			add_path(rel, (Path *) ipath);
 
 		if (index->amhasgetbitmap &&
+			!index->indirect &&
 			(ipath->path.pathkeys == NIL ||
 			 ipath->indexselectivity < 1.0))
 			*bitindexpaths = lappend(*bitindexpaths, ipath);
@@ -800,6 +802,7 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
 	 */
 	if (skip_nonnative_saop)
 	{
+		Assert(!index->indirect);	/* what to do here? */
 		indexpaths = build_index_paths(root, rel,
 									   index, clauses,
 									   false,
@@ -1119,6 +1122,10 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
 		if (!index->amhasgetbitmap)
 			continue;
 
+		/* Ignore indirect indexes too */
+		if (index->indirect)
+			continue;
+
 		/*
 		 * Ignore partial indexes that do not match the query.  If a partial
 		 * index is marked predOK then we know it's OK.  Otherwise, we have to
@@ -1798,6 +1805,10 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
 	if (!enable_indexonlyscan)
 		return false;
 
+	/* Can't use indirect indexes for index-only scans */
+	if (index->indirect)
+		return false;
+
 	/*
 	 * Check that all needed attributes of the relation are available from the
 	 * index.
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ad49674..52ec437 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -166,6 +166,17 @@ static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid,
 			   List *indexorderby, List *indexorderbyorig,
 			   List *indexorderbyops,
 			   ScanDirection indexscandir);
+static IndirectIndexScan *make_indirectindexscan(List *qptlist,
+					   List *qpqual,
+					   Index scanrelid,
+					   Oid indexid,
+					   Oid pkindexid,
+					   List *indexqual,
+					   List *indexqualorig,
+					   List *indexorderby,
+					   List *indexorderbyorig,
+					   List *indexorderbyops,
+					   ScanDirection indexscandir);
 static IndexOnlyScan *make_indexonlyscan(List *qptlist, List *qpqual,
 				   Index scanrelid, Oid indexid,
 				   List *indexqual, List *indexorderby,
@@ -2494,6 +2505,8 @@ create_indexscan_plan(PlannerInfo *root,
 
 	/* Finally ready to build the plan node */
 	if (indexonly)
+	{
+		Assert(!best_path->indexinfo->indirect);	/* maybe someday */
 		scan_plan = (Scan *) make_indexonlyscan(tlist,
 												qpqual,
 												baserelid,
@@ -2502,6 +2515,23 @@ create_indexscan_plan(PlannerInfo *root,
 												fixed_indexorderbys,
 											best_path->indexinfo->indextlist,
 												best_path->indexscandir);
+	}
+	else if (best_path->indexinfo->indirect)
+	{
+		Oid pkindexoid = best_path->indexinfo->pkindex->indexoid;
+
+		scan_plan = (Scan *) make_indirectindexscan(tlist,
+													qpqual,
+													baserelid,
+													indexoid,
+													pkindexoid,
+													fixed_indexquals,
+													stripped_indexquals,
+													fixed_indexorderbys,
+													indexorderbys,
+													indexorderbyops,
+													best_path->indexscandir);
+	}
 	else
 		scan_plan = (Scan *) make_indexscan(tlist,
 											qpqual,
@@ -4720,6 +4750,39 @@ make_indexscan(List *qptlist,
 	return node;
 }
 
+static IndirectIndexScan *
+make_indirectindexscan(List *qptlist,
+					   List *qpqual,
+					   Index scanrelid,
+					   Oid indexid,
+					   Oid pkindexid,
+					   List *indexqual,
+					   List *indexqualorig,
+					   List *indexorderby,
+					   List *indexorderbyorig,
+					   List *indexorderbyops,
+					   ScanDirection indexscandir)
+{
+	IndirectIndexScan  *node = makeNode(IndirectIndexScan);
+	Plan	   *plan = &node->scan.plan;
+
+	plan->targetlist = qptlist;
+	plan->qual = qpqual;
+	plan->lefttree = NULL;
+	plan->righttree = NULL;
+	node->scan.scanrelid = scanrelid;
+	node->indexid = indexid;
+	node->pkindexid = pkindexid;
+	node->indexqual = indexqual;
+	node->indexqualorig = indexqualorig;
+	node->indexorderby = indexorderby;
+	node->indexorderbyorig = indexorderbyorig;
+	node->indexorderbyops = indexorderbyops;
+	node->indexorderdir = indexscandir;
+
+	return node;
+}
+
 static IndexOnlyScan *
 make_indexonlyscan(List *qptlist,
 				   List *qpqual,
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 2fe1c8c..8401c14 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -490,6 +490,22 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 					fix_scan_list(root, splan->indexorderbyorig, rtoffset);
 			}
 			break;
+		case T_IndirectIndexScan:
+			{
+				IndirectIndexScan *splan = (IndirectIndexScan *) plan;
+				splan->scan.scanrelid += rtoffset;
+				splan->scan.plan.targetlist =
+					fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
+				splan->indexqual =
+					fix_scan_list(root, splan->indexqual, rtoffset);
+				splan->indexqualorig =
+					fix_scan_list(root, splan->indexqualorig, rtoffset);
+				splan->indexorderby =
+					fix_scan_list(root, splan->indexorderby, rtoffset);
+				splan->indexorderbyorig =
+					fix_scan_list(root, splan->indexorderbyorig, rtoffset);
+			}
+			break;
 		case T_IndexOnlyScan:
 			{
 				IndexOnlyScan *splan = (IndexOnlyScan *) plan;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 3171743..aa5190c 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2327,6 +2327,13 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 			context.paramids = bms_add_members(context.paramids, scan_params);
 			break;
 
+		case T_IndirectIndexScan:
+			finalize_primnode((Node *) ((IndirectIndexScan *) plan)->indexqual,
+							  &context);
+			finalize_primnode((Node *) ((IndirectIndexScan *) plan)->indexorderby,
+							  &context);
+			break;
+
 		case T_IndexOnlyScan:
 			finalize_primnode((Node *) ((IndexOnlyScan *) plan)->indexqual,
 							  &context);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 72272d9..fa78e33 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -152,6 +152,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 		List	   *indexoidlist;
 		ListCell   *l;
 		LOCKMODE	lmode;
+		IndexOptInfo *pkinfo = NULL;
 
 		indexoidlist = RelationGetIndexList(relation);
 
@@ -234,6 +235,12 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 			}
 
 			info->relam = indexRelation->rd_rel->relam;
+			info->indirect = index->indisindirect;
+			if (info->indirect)
+			{
+				Assert(pkinfo != NULL);
+				info->pkindex = pkinfo;
+			}
 
 			/* We copy just the fields we need, not all of rd_amroutine */
 			amroutine = indexRelation->rd_amroutine;
@@ -354,6 +361,10 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 			info->immediate = index->indimmediate;
 			info->hypothetical = false;
 
+			/* remember primary key for indirect indexes */
+			if (index->indisprimary)
+				pkinfo = info;
+
 			/*
 			 * Estimate the index size.  If it's not a partial index, we lock
 			 * the number-of-tuples estimate to equal the parent table; if it
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2ed7b52..5a2601d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -418,7 +418,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	overlay_placing substr_from substr_for
 
 %type <boolean> opt_instead
-%type <boolean> opt_unique opt_concurrently opt_verbose opt_full
+%type <boolean> opt_unique opt_indirect opt_concurrently opt_verbose opt_full
 %type <boolean> opt_freeze opt_default opt_recheck
 %type <defelt>	opt_binary opt_oids copy_delimiter
 
@@ -615,7 +615,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
 	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
-	INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+	INCLUDING INCREMENT INDEX INDEXES INDIRECT
+	INHERIT INHERITS INITIALLY INLINE_P
 	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
 	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -7017,25 +7018,26 @@ defacl_privilege_target:
  * willing to make TABLESPACE a fully reserved word.
  *****************************************************************************/
 
-IndexStmt:	CREATE opt_unique INDEX opt_concurrently opt_index_name
+IndexStmt:	CREATE opt_unique opt_indirect INDEX opt_concurrently opt_index_name
 			ON qualified_name access_method_clause '(' index_params ')'
 			opt_reloptions OptTableSpace where_clause
 				{
 					IndexStmt *n = makeNode(IndexStmt);
 					n->unique = $2;
-					n->concurrent = $4;
-					n->idxname = $5;
-					n->relation = $7;
-					n->accessMethod = $8;
-					n->indexParams = $10;
-					n->options = $12;
-					n->tableSpace = $13;
-					n->whereClause = $14;
+					n->concurrent = $5;
+					n->idxname = $6;
+					n->relation = $8;
+					n->accessMethod = $9;
+					n->indexParams = $11;
+					n->options = $13;
+					n->tableSpace = $14;
+					n->whereClause = $15;
 					n->excludeOpNames = NIL;
 					n->idxcomment = NULL;
 					n->indexOid = InvalidOid;
 					n->oldNode = InvalidOid;
 					n->primary = false;
+					n->isindirect = $3;
 					n->isconstraint = false;
 					n->deferrable = false;
 					n->initdeferred = false;
@@ -7043,25 +7045,26 @@ IndexStmt:	CREATE opt_unique INDEX opt_concurrently opt_index_name
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
-			| CREATE opt_unique INDEX opt_concurrently IF_P NOT EXISTS index_name
+			| CREATE opt_unique opt_indirect INDEX opt_concurrently IF_P NOT EXISTS index_name
 			ON qualified_name access_method_clause '(' index_params ')'
 			opt_reloptions OptTableSpace where_clause
 				{
 					IndexStmt *n = makeNode(IndexStmt);
 					n->unique = $2;
-					n->concurrent = $4;
-					n->idxname = $8;
-					n->relation = $10;
-					n->accessMethod = $11;
-					n->indexParams = $13;
-					n->options = $15;
-					n->tableSpace = $16;
-					n->whereClause = $17;
+					n->concurrent = $5;
+					n->idxname = $9;
+					n->relation = $11;
+					n->accessMethod = $12;
+					n->indexParams = $14;
+					n->options = $16;
+					n->tableSpace = $17;
+					n->whereClause = $18;
 					n->excludeOpNames = NIL;
 					n->idxcomment = NULL;
 					n->indexOid = InvalidOid;
 					n->oldNode = InvalidOid;
 					n->primary = false;
+					n->isindirect = $3;
 					n->isconstraint = false;
 					n->deferrable = false;
 					n->initdeferred = false;
@@ -7076,6 +7079,11 @@ opt_unique:
 			| /*EMPTY*/								{ $$ = FALSE; }
 		;
 
+opt_indirect:
+			INDIRECT								{ $$ = TRUE; }
+			| /*EMPTY*/								{ $$ = FALSE; }
+		;
+
 opt_concurrently:
 			CONCURRENTLY							{ $$ = TRUE; }
 			| /*EMPTY*/								{ $$ = FALSE; }
@@ -14107,6 +14115,7 @@ unreserved_keyword:
 			| INCREMENT
 			| INDEX
 			| INDEXES
+			| INDIRECT
 			| INHERIT
 			| INHERITS
 			| INLINE_P
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 4e2ba19..14b0de7 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -1248,7 +1248,8 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	if (!attrsOnly)
 	{
 		if (!isConstraint)
-			appendStringInfo(&buf, "CREATE %sINDEX %s ON %s USING %s (",
+			appendStringInfo(&buf, "CREATE %s%sINDEX %s ON %s USING %s (",
+							 idxrec->indisindirect ? "INDIRECT " : "",
 							 idxrec->indisunique ? "UNIQUE " : "",
 							 quote_identifier(NameStr(idxrelrec->relname)),
 							 generate_relation_name(indrelid, NIL),
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 4973396..d980e10 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5029,6 +5029,10 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata,
 		if (index->relam != BTREE_AM_OID)
 			continue;
 
+		/* Ignore indirect indexes */
+		if (index->indirect)
+			continue;
+
 		/*
 		 * Ignore partial indexes --- we only want stats that cover the entire
 		 * relation.
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2a68359..1e2ff56 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -271,7 +271,7 @@ static TupleDesc GetPgIndexDescriptor(void);
 static void AttrDefaultFetch(Relation relation);
 static void CheckConstraintFetch(Relation relation);
 static int	CheckConstraintCmp(const void *a, const void *b);
-static List *insert_ordered_oid(List *list, Oid datum);
+static List *insert_ordered_oid(List *list, Oid datum, bool must_be_first);
 static void InitIndexAmRoutine(Relation relation);
 static void IndexSupportInitialize(oidvector *indclass,
 					   RegProcedure *indexSupport,
@@ -2335,8 +2335,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	list_free_deep(relation->rd_fkeylist);
 	list_free(relation->rd_indexlist);
 	bms_free(relation->rd_indexattr);
+	bms_free(relation->rd_hotattr);
 	bms_free(relation->rd_keyattr);
 	bms_free(relation->rd_idattr);
+	bms_free(relation->rd_indirectattr);
 	if (relation->rd_options)
 		pfree(relation->rd_options);
 	if (relation->rd_indextuple)
@@ -4288,6 +4290,47 @@ RelationGetFKeyList(Relation relation)
 }
 
 /*
+ * Return the relation's primary key OID.
+ *
+ * Surely this can be made better ...
+ */
+Oid
+RelationGetPrimaryKey(Relation relation)
+{
+	Relation	indrel;
+	SysScanDesc indscan;
+	ScanKeyData skey;
+	HeapTuple	htup;
+	Oid			pkid = InvalidOid;
+
+	/* Currently we just scan pg_index every time this is called */
+	ScanKeyInit(&skey,
+				Anum_pg_index_indrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+
+	indrel = heap_open(IndexRelationId, AccessShareLock);
+	indscan = systable_beginscan(indrel, IndexIndrelidIndexId, true,
+								 NULL, 1, &skey);
+	while (HeapTupleIsValid(htup = systable_getnext(indscan)))
+	{
+		Form_pg_index index = (Form_pg_index) GETSTRUCT(htup);
+
+		if (!IndexIsLive(index))
+			continue;
+		if (!index->indisprimary)
+			continue;
+		pkid = index->indexrelid;
+		break;
+	}
+
+	systable_endscan(indscan);
+	heap_close(indrel, AccessShareLock);
+
+	return pkid;
+}
+
+/*
  * RelationGetIndexList -- get a list of OIDs of indexes on this relation
  *
  * The index list is created only if someone requests it.  We scan pg_index
@@ -4301,7 +4344,8 @@ RelationGetFKeyList(Relation relation)
  * Such indexes are expected to be dropped momentarily, and should not be
  * touched at all by any caller of this function.
  *
- * The returned list is guaranteed to be sorted in order by OID.  This is
+ * The returned list is guaranteed to be sorted in order by OID, except that
+ * the primary key is always in front.  This is
  * needed by the executor, since for index types that we obtain exclusive
  * locks on when updating the index, all backends must lock the indexes in
  * the same order or we will get deadlocks (see ExecOpenIndices()).  Any
@@ -4377,7 +4421,8 @@ RelationGetIndexList(Relation relation)
 			continue;
 
 		/* Add index's OID to result list in the proper order */
-		result = insert_ordered_oid(result, index->indexrelid);
+		result = insert_ordered_oid(result, index->indexrelid,
+									index->indisprimary);
 
 		/*
 		 * indclass cannot be referenced directly through the C struct,
@@ -4450,12 +4495,12 @@ RelationGetIndexList(Relation relation)
  * indexes...
  */
 static List *
-insert_ordered_oid(List *list, Oid datum)
+insert_ordered_oid(List *list, Oid datum, bool must_be_first)
 {
 	ListCell   *prev;
 
 	/* Does the datum belong at the front? */
-	if (list == NIL || datum < linitial_oid(list))
+	if (list == NIL || datum < linitial_oid(list) || must_be_first)
 		return lcons_oid(datum, list);
 	/* No, so find the entry it belongs after */
 	prev = list_head(list);
@@ -4492,7 +4537,7 @@ insert_ordered_oid(List *list, Oid datum)
  * to ensure that a correct rd_indexattr set has been cached before first
  * calling RelationSetIndexList; else a subsequent inquiry might cause a
  * wrong rd_indexattr set to get computed and cached.  Likewise, we do not
- * touch rd_keyattr or rd_idattr.
+ * touch rd_hotattr, rd_keyattr, rd_indirectattr or rd_idattr.
  */
 void
 RelationSetIndexList(Relation relation, List *indexIds, Oid oidIndex)
@@ -4703,9 +4748,7 @@ RelationGetIndexPredicate(Relation relation)
  * simple index keys, but attributes used in expressions and partial-index
  * predicates.)
  *
- * Depending on attrKind, a bitmap covering the attnums for all index columns,
- * for all potential foreign key columns, or for all columns in the configured
- * replica identity index is returned.
+ * A bitmap covering attnums is returned, depending on attrKind.
  *
  * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
  * we can include system attributes (e.g., OID) in the bitmap representation.
@@ -4721,8 +4764,10 @@ Bitmapset *
 RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 {
 	Bitmapset  *indexattrs;		/* indexed columns */
+	Bitmapset  *hotattrs;		/* HOT-index-relevant columns */
 	Bitmapset  *uindexattrs;	/* columns in unique indexes */
 	Bitmapset  *idindexattrs;	/* columns in the replica identity */
+	Bitmapset  *indirectattrs;	/* columns in indirect indexes */
 	List	   *indexoidlist;
 	Oid			relreplindex;
 	ListCell   *l;
@@ -4737,8 +4782,12 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_indexattr);
 			case INDEX_ATTR_BITMAP_KEY:
 				return bms_copy(relation->rd_keyattr);
+			case INDEX_ATTR_BITMAP_HOT:
+				return bms_copy(relation->rd_hotattr);
 			case INDEX_ATTR_BITMAP_IDENTITY_KEY:
 				return bms_copy(relation->rd_idattr);
+			case INDEX_ATTR_BITMAP_INDIRECT_INDEXES:
+				return bms_copy(relation->rd_indirectattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -4767,7 +4816,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	relreplindex = relation->rd_replidindex;
 
 	/*
-	 * For each index, add referenced attributes to indexattrs.
+	 * For each index, add referenced attributes to the attribute bitmaps.
 	 *
 	 * Note: we consider all indexes returned by RelationGetIndexList, even if
 	 * they are not indisready or indisvalid.  This is important because an
@@ -4777,8 +4826,10 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	 * won't be returned at all by RelationGetIndexList.
 	 */
 	indexattrs = NULL;
+	hotattrs = NULL;
 	uindexattrs = NULL;
 	idindexattrs = NULL;
+	indirectattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -4787,6 +4838,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 		int			i;
 		bool		isKey;		/* candidate key */
 		bool		isIDKey;	/* replica identity index */
+		bool		isIndirect;	/* an indirect index */
+		Bitmapset  *exprattrs = NULL;
 
 		indexDesc = index_open(indexOid, AccessShareLock);
 
@@ -4801,6 +4854,9 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 		/* Is this index the configured (or default) replica identity? */
 		isIDKey = (indexOid == relreplindex);
 
+		/* Is this an indirect index? */
+		isIndirect = indexInfo->ii_IsIndirect;
+
 		/* Collect simple attribute references */
 		for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
 		{
@@ -4809,7 +4865,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 			if (attrnum != 0)
 			{
 				indexattrs = bms_add_member(indexattrs,
-							   attrnum - FirstLowInvalidHeapAttributeNumber);
+											attrnum - FirstLowInvalidHeapAttributeNumber);
 
 				if (isKey)
 					uindexattrs = bms_add_member(uindexattrs,
@@ -4818,14 +4874,31 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				if (isIDKey)
 					idindexattrs = bms_add_member(idindexattrs,
 							   attrnum - FirstLowInvalidHeapAttributeNumber);
+
+				if (isIndirect)
+					indirectattrs = bms_add_member(indirectattrs,
+							   attrnum - FirstLowInvalidHeapAttributeNumber);
+				else
+					hotattrs = bms_add_member(hotattrs,
+							   attrnum - FirstLowInvalidHeapAttributeNumber);
 			}
 		}
 
 		/* Collect all attributes used in expressions, too */
-		pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
+		pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &exprattrs);
+		indexattrs = bms_add_members(indexattrs, exprattrs);
+		/* these don't affect KEY (unique) indexes */
+		hotattrs = bms_add_members(hotattrs, exprattrs);
+		indirectattrs = bms_add_members(indirectattrs, exprattrs);
+		bms_free(exprattrs);
 
 		/* Collect all attributes in the index predicate, too */
-		pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexattrs);
+		exprattrs = NULL;
+		pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &exprattrs);
+		indexattrs = bms_add_members(indexattrs, exprattrs);
+		hotattrs = bms_add_members(hotattrs, exprattrs);
+		/* these don't affect KEY or indirect indexes */
+		bms_free(exprattrs);
 
 		index_close(indexDesc, AccessShareLock);
 	}
@@ -4835,10 +4908,14 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	/* Don't leak the old values of these bitmaps, if any */
 	bms_free(relation->rd_indexattr);
 	relation->rd_indexattr = NULL;
+	bms_free(relation->rd_hotattr);
+	relation->rd_hotattr = NULL;
 	bms_free(relation->rd_keyattr);
 	relation->rd_keyattr = NULL;
 	bms_free(relation->rd_idattr);
 	relation->rd_idattr = NULL;
+	bms_free(relation->rd_indirectattr);
+	relation->rd_indirectattr = NULL;
 
 	/*
 	 * Now save copies of the bitmaps in the relcache entry.  We intentionally
@@ -4851,6 +4928,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	relation->rd_keyattr = bms_copy(uindexattrs);
 	relation->rd_idattr = bms_copy(idindexattrs);
 	relation->rd_indexattr = bms_copy(indexattrs);
+	relation->rd_hotattr = bms_copy(hotattrs);
+	relation->rd_indirectattr = bms_copy(indirectattrs);
 	MemoryContextSwitchTo(oldcxt);
 
 	/* We return our original working copy for caller to play with */
@@ -4858,10 +4937,14 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	{
 		case INDEX_ATTR_BITMAP_ALL:
 			return indexattrs;
+		case INDEX_ATTR_BITMAP_HOT:
+			return hotattrs;
 		case INDEX_ATTR_BITMAP_KEY:
 			return uindexattrs;
 		case INDEX_ATTR_BITMAP_IDENTITY_KEY:
 			return idindexattrs;
+		case INDEX_ATTR_BITMAP_INDIRECT_INDEXES:
+			return indirectattrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
@@ -5409,8 +5492,10 @@ load_relcache_init_file(bool shared)
 		rel->rd_oidindex = InvalidOid;
 		rel->rd_replidindex = InvalidOid;
 		rel->rd_indexattr = NULL;
+		rel->rd_hotattr = NULL;
 		rel->rd_keyattr = NULL;
 		rel->rd_idattr = NULL;
+		rel->rd_indirectattr = NULL;
 		rel->rd_createSubid = InvalidSubTransactionId;
 		rel->rd_newRelfilenodeSubid = InvalidSubTransactionId;
 		rel->rd_amcache = NULL;
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 1036cca..9f28c7d 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -175,6 +175,8 @@ typedef struct IndexAmRoutine
 	bool		amclusterable;
 	/* does AM handle predicate locks? */
 	bool		ampredlocks;
+	/* does AM support indirect indexes? */
+	bool		amcanindirect;
 	/* type of data stored in index, or InvalidOid if variable */
 	Oid			amkeytype;
 
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 81907d5..e2aa3a5 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -102,13 +102,17 @@ typedef struct SysScanDescData *SysScanDesc;
  * call is made with UNIQUE_CHECK_EXISTING.  The tuple is already in the
  * index in this case, so it should not be inserted again.  Rather, just
  * check for conflicting live tuples (possibly blocking).
+ *
+ * UNIQUE_CHECK_INSERT_SINGLETON only inserts if there isn't already an
+ * index tuple.   This is supported for indirect indexes.
  */
 typedef enum IndexUniqueCheck
 {
 	UNIQUE_CHECK_NO,			/* Don't do any uniqueness checking */
 	UNIQUE_CHECK_YES,			/* Enforce uniqueness at insertion time */
 	UNIQUE_CHECK_PARTIAL,		/* Test uniqueness, but no error */
-	UNIQUE_CHECK_EXISTING		/* Check if existing tuple is unique */
+	UNIQUE_CHECK_EXISTING,		/* Check if existing tuple is unique */
+	UNIQUE_CHECK_INSERT_SINGLETON	/* Only insert if value doesn't exist */
 } IndexUniqueCheck;
 
 
@@ -127,6 +131,7 @@ extern void index_close(Relation relation, LOCKMODE lockmode);
 
 extern bool index_insert(Relation indexRelation,
 			 Datum *values, bool *isnull,
+			 Datum *pkeyValues,
 			 ItemPointer heap_t_ctid,
 			 Relation heapRelation,
 			 IndexUniqueCheck checkUnique);
@@ -146,7 +151,10 @@ extern void index_markpos(IndexScanDesc scan);
 extern void index_restrpos(IndexScanDesc scan);
 extern ItemPointer index_getnext_tid(IndexScanDesc scan,
 				  ScanDirection direction);
+extern void index_getnext_pkey(IndexScanDesc scan, ScanDirection direction,
+				   Datum *pkvals, bool *isDone);
 extern HeapTuple index_fetch_heap(IndexScanDesc scan);
+
 extern HeapTuple index_getnext(IndexScanDesc scan, ScanDirection direction);
 extern int64 index_getbitmap(IndexScanDesc scan, TIDBitmap *bitmap);
 
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 0d12bbb..99af368 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -16,6 +16,7 @@
 
 #include "access/sdir.h"
 #include "access/skey.h"
+#include "nodes/bitmapset.h"
 #include "nodes/lockoptions.h"
 #include "nodes/primnodes.h"
 #include "storage/bufpage.h"
@@ -160,7 +161,8 @@ extern void heap_abort_speculative(Relation relation, HeapTuple tuple);
 extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
 			HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
-			HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
+			HeapUpdateFailureData *hufd, Bitmapset **unchanged_attrs,
+			LockTupleMode *lockmode);
 extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				bool follow_update,
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 37e6ef3..0a70e08 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -37,6 +37,21 @@ typedef enum
 	INDEX_DROP_SET_DEAD
 } IndexStateFlagsAction;
 
+static inline void
+FAKE_CTID_FROM_PKVALUES(ItemPointer iptr, int16 pkNumKeys, Datum *pkvalues)
+{
+	/* We should support more than one column in the PK */
+	Assert(pkNumKeys == 1);
+
+	/*
+	 * Because some overzealous checks, we can't have all-zeroes offnum
+	 * nor blknum, so we always set their lowest-order bit to 1; make sure
+	 * to ignore that bit when reading back the value from the TID.
+	 */
+	iptr->ip_posid = ((pkvalues[0] & 0x7fff) << 1) | 1;
+	iptr->ip_blkid.bi_hi = (pkvalues[0] >> 15) & 0xffff;
+	iptr->ip_blkid.bi_lo = ((pkvalues[0] >> 31) & 0xffff) | 1;
+}
 
 extern void index_check_primary_key(Relation heapRel,
 						IndexInfo *indexInfo,
diff --git a/src/include/catalog/pg_index.h b/src/include/catalog/pg_index.h
index ee97c5d..144d204 100644
--- a/src/include/catalog/pg_index.h
+++ b/src/include/catalog/pg_index.h
@@ -34,6 +34,7 @@ CATALOG(pg_index,2610) BKI_WITHOUT_OIDS BKI_SCHEMA_MACRO
 	Oid			indrelid;		/* OID of the relation it indexes */
 	int16		indnatts;		/* number of columns in index */
 	bool		indisunique;	/* is this a unique index? */
+	bool		indisindirect;	/* is this an indirect index? */
 	bool		indisprimary;	/* is this index for primary key? */
 	bool		indisexclusion; /* is this index for exclusion constraint? */
 	bool		indimmediate;	/* is uniqueness enforced immediately? */
@@ -70,26 +71,27 @@ typedef FormData_pg_index *Form_pg_index;
  *		compiler constants for pg_index
  * ----------------
  */
-#define Natts_pg_index					19
+#define Natts_pg_index					20
 #define Anum_pg_index_indexrelid		1
 #define Anum_pg_index_indrelid			2
 #define Anum_pg_index_indnatts			3
 #define Anum_pg_index_indisunique		4
-#define Anum_pg_index_indisprimary		5
-#define Anum_pg_index_indisexclusion	6
-#define Anum_pg_index_indimmediate		7
-#define Anum_pg_index_indisclustered	8
-#define Anum_pg_index_indisvalid		9
-#define Anum_pg_index_indcheckxmin		10
-#define Anum_pg_index_indisready		11
-#define Anum_pg_index_indislive			12
-#define Anum_pg_index_indisreplident	13
-#define Anum_pg_index_indkey			14
-#define Anum_pg_index_indcollation		15
-#define Anum_pg_index_indclass			16
-#define Anum_pg_index_indoption			17
-#define Anum_pg_index_indexprs			18
-#define Anum_pg_index_indpred			19
+#define Anum_pg_index_indisindirect		5
+#define Anum_pg_index_indisprimary		6
+#define Anum_pg_index_indisexclusion	7
+#define Anum_pg_index_indimmediate		8
+#define Anum_pg_index_indisclustered	9
+#define Anum_pg_index_indisvalid		10
+#define Anum_pg_index_indcheckxmin		11
+#define Anum_pg_index_indisready		12
+#define Anum_pg_index_indislive			13
+#define Anum_pg_index_indisreplident	14
+#define Anum_pg_index_indkey			15
+#define Anum_pg_index_indcollation		16
+#define Anum_pg_index_indclass			17
+#define Anum_pg_index_indoption			18
+#define Anum_pg_index_indexprs			19
+#define Anum_pg_index_indpred			20
 
 /*
  * Index AMs that support ordered scans must support these two indoption
diff --git a/src/include/executor/execdebug.h b/src/include/executor/execdebug.h
index 950a0bc..e662a69 100644
--- a/src/include/executor/execdebug.h
+++ b/src/include/executor/execdebug.h
@@ -17,6 +17,7 @@
 #ifndef EXECDEBUG_H
 #define EXECDEBUG_H
 
+#include "access/printtup.h"
 #include "executor/executor.h"
 #include "nodes/print.h"
 
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index b74fa5e..3ec0b52 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -378,7 +378,7 @@ extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative);
 extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
 extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
 					  EState *estate, bool noDupErr, bool *specConflict,
-					  List *arbiterIndexes);
+					  List *arbiterIndexes, Bitmapset *unchangedAttrs);
 extern bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate,
 						  ItemPointer conflictTid, List *arbiterIndexes);
 extern void check_exclusion_constraint(Relation heap, Relation index,
diff --git a/src/include/executor/nodeIndirectIndexscan.h b/src/include/executor/nodeIndirectIndexscan.h
new file mode 100644
index 0000000..46eea32
--- /dev/null
+++ b/src/include/executor/nodeIndirectIndexscan.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeIndirectIndexscan.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/nodeIndirectIndexscan.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODEINDIRECTINDEXSCAN_H
+#define NODEINDIRECTINDEXSCAN_H
+
+#include "nodes/execnodes.h"
+
+extern IndirectIndexScanState *ExecInitIndirectIndexScan(IndirectIndexScan *node, EState *estate, int eflags);
+extern TupleTableSlot *ExecIndirectIndexScan(IndirectIndexScanState *node);
+extern void ExecEndIndirectIndexScan(IndirectIndexScanState *node);
+extern void ExecIndirectIndexMarkPos(IndirectIndexScanState *node);
+extern void ExecIndirectIndexRestrPos(IndirectIndexScanState *node);
+extern void ExecReScanIndirectIndexScan(IndirectIndexScanState *node);
+
+#endif	/* NODEINDIRECTINDEXSCAN_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 5c3b868..e07f441 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -49,6 +49,7 @@
  *		UniqueProcs
  *		UniqueStrats
  *		Unique				is it a unique index?
+ *		IsIndirect			is it an indirect index?
  *		ReadyForInserts		is it valid for inserts?
  *		Concurrent			are we doing a concurrent index build?
  *		BrokenHotChain		did we detect any broken HOT chains?
@@ -73,6 +74,7 @@ typedef struct IndexInfo
 	Oid		   *ii_UniqueProcs; /* array with one entry per column */
 	uint16	   *ii_UniqueStrats;	/* array with one entry per column */
 	bool		ii_Unique;
+	bool		ii_IsIndirect;
 	bool		ii_ReadyForInserts;
 	bool		ii_Concurrent;
 	bool		ii_BrokenHotChain;
@@ -1378,6 +1380,45 @@ typedef struct IndexScanState
 } IndexScanState;
 
 /* ----------------
+ *	 IndirectIndexScanState information
+ *
+ *		indexqual		   execution state for indexqual expressions
+ *		ScanKeys		   Skey structures for index quals
+ *		NumScanKeys		   number of ScanKeys
+ *		IndRelationDesc	   indirect index relation descriptor
+ *		IndScanDesc		   indirect index scan descriptor
+ *		PKRelationDesc	   primary key index relation descriptor
+ *		PKScanDesc		   primary key index scan descriptor
+ *		IndirectSlot       tuple obtained from indirect index
+ *		OrderByKeys		   Skey structures for index ordering operators
+ *		NumOrderByKeys	   number of OrderByKeys
+ *		RuntimeKeys		   info about Skeys that must be evaluated at runtime
+ *		NumRuntimeKeys	   number of RuntimeKeys
+ *		RuntimeKeysReady   true if runtime Skeys have been computed
+ *		RuntimeContext	   expr context for evaling runtime Skeys
+ * ----------------
+ */
+typedef struct IndirectIndexScanState
+{
+	ScanState	ss;				/* its first field is NodeTag */
+	List	   *indexqual;
+	ScanKey		iiss_ScanKeys;
+	int			iiss_NumScanKeys;
+	Relation	iiss_IndRelationDesc;
+	IndexScanDesc iiss_IndScanDesc;
+	Relation	iiss_PKRelationDesc;
+	IndexScanDesc iiss_PKScanDesc;
+	bool		iiss_HaveIndirectTuple;
+	ScanKey		iiss_OrderByKeys;
+	int			iiss_NumOrderByKeys;
+	IndexRuntimeKeyInfo *iiss_RuntimeKeys;
+	int			iiss_NumRuntimeKeys;
+	bool		iiss_RuntimeKeysReady;
+	Oid		   *iiss_EqualFuncs;
+	ExprContext *iiss_RuntimeContext;
+} IndirectIndexScanState;
+
+/* ----------------
  *	 IndexOnlyScanState information
  *
  *		indexqual		   execution state for indexqual expressions
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c514d3f..b57f235 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -53,6 +53,7 @@ typedef enum NodeTag
 	T_SeqScan,
 	T_SampleScan,
 	T_IndexScan,
+	T_IndirectIndexScan,
 	T_IndexOnlyScan,
 	T_BitmapIndexScan,
 	T_BitmapHeapScan,
@@ -101,6 +102,7 @@ typedef enum NodeTag
 	T_SeqScanState,
 	T_SampleScanState,
 	T_IndexScanState,
+	T_IndirectIndexScanState,
 	T_IndexOnlyScanState,
 	T_BitmapIndexScanState,
 	T_BitmapHeapScanState,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index fc532fb..40e1547 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2558,6 +2558,7 @@ typedef struct IndexStmt
 	Oid			indexOid;		/* OID of an existing index, if any */
 	Oid			oldNode;		/* relfilenode of existing storage, if any */
 	bool		unique;			/* is index unique? */
+	bool		isindirect;		/* is index indirect? */
 	bool		primary;		/* is index a primary key? */
 	bool		isconstraint;	/* is it for a pkey/unique constraint? */
 	bool		deferrable;		/* is the constraint DEFERRABLE? */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e2fbc7d..c1e6a95 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -353,6 +353,27 @@ typedef struct IndexScan
 } IndexScan;
 
 /* ----------------
+ *		indirect index scan node
+ *
+ * IndirectIndexScan is very similar to IndexScan, but it specifies a
+ * scan of an indirect index.  In addition to the fields in IndexScan,
+ * it has the OID of the primary key that this index references.
+ * ----------------
+ */
+typedef struct IndirectIndexScan
+{
+	Scan		scan;
+	Oid			indexid;		/* OID of indirect index */
+	Oid			pkindexid;		/* OID of the primary key it references */
+	List	   *indexqual;		/* list of index quals */
+	List	   *indexqualorig;	/* the same in original form */
+	List	   *indexorderby;	/* list of index ORDER BY exprs */
+	List	   *indexorderbyorig;		/* the same in original form */
+	List	   *indexorderbyops;	/* OIDs of sort ops for ORDER BY exprs */
+	ScanDirection indexorderdir;	/* forward or backward or don't care */
+} IndirectIndexScan;
+
+/* ----------------
  *		index-only scan node
  *
  * IndexOnlyScan is very similar to IndexScan, but it specifies an
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 3a1255a..82cf1d6 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -581,6 +581,7 @@ typedef struct IndexOptInfo
 	Oid			indexoid;		/* OID of the index relation */
 	Oid			reltablespace;	/* tablespace of index (not table) */
 	RelOptInfo *rel;			/* back-link to index's table */
+	struct IndexOptInfo *pkindex;		/* link to referenced index, if indirect */
 
 	/* index-size statistics (from pg_class and elsewhere) */
 	BlockNumber pages;			/* number of disk pages in index */
@@ -613,6 +614,7 @@ typedef struct IndexOptInfo
 	bool		predOK;			/* true if index predicate matches query */
 	bool		unique;			/* true if a unique index */
 	bool		immediate;		/* is uniqueness enforced immediately? */
+	bool		indirect;		/* true if index is indirect */
 	bool		hypothetical;	/* true if index doesn't really exist */
 
 	/* Remaining fields are copied from the index AM's API struct: */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 581ff6e..dbcc892 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -196,6 +196,7 @@ PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
+PG_KEYWORD("indirect", INDIRECT, UNRESERVED_KEYWORD)
 PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD)
 PG_KEYWORD("inherits", INHERITS, UNRESERVED_KEYWORD)
 PG_KEYWORD("initially", INITIALLY, RESERVED_KEYWORD)
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index cd7ea1d..b046f12 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -135,9 +135,11 @@ typedef struct RelationData
 	Oid			rd_replidindex; /* OID of replica identity index, if any */
 
 	/* data managed by RelationGetIndexAttrBitmap: */
-	Bitmapset  *rd_indexattr;	/* identifies columns used in indexes */
+	Bitmapset  *rd_indexattr;	/* identifies columns used in any index */
+	Bitmapset  *rd_hotattr;		/* columns used in HOT-relevant indexes */
 	Bitmapset  *rd_keyattr;		/* cols that can be ref'd by foreign keys */
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
+	Bitmapset  *rd_indirectattr; /* cols part of any indirect index */
 
 	/*
 	 * rd_options is set whenever rd_rel is loaded into the relcache entry.
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 6ea7dd2..0e5df4c 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -39,6 +39,7 @@ extern void RelationClose(Relation relation);
  */
 extern List *RelationGetFKeyList(Relation relation);
 extern List *RelationGetIndexList(Relation relation);
+extern Oid	RelationGetPrimaryKey(Relation relation);
 extern Oid	RelationGetOidIndex(Relation relation);
 extern Oid	RelationGetReplicaIndex(Relation relation);
 extern List *RelationGetIndexExpressions(Relation relation);
@@ -48,7 +49,9 @@ typedef enum IndexAttrBitmapKind
 {
 	INDEX_ATTR_BITMAP_ALL,
 	INDEX_ATTR_BITMAP_KEY,
-	INDEX_ATTR_BITMAP_IDENTITY_KEY
+	INDEX_ATTR_BITMAP_HOT,
+	INDEX_ATTR_BITMAP_IDENTITY_KEY,
+	INDEX_ATTR_BITMAP_INDIRECT_INDEXES
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
