diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c
index 934d65b89f..b767a805fa 100644
--- a/src/backend/access/spgist/spgdoinsert.c
+++ b/src/backend/access/spgist/spgdoinsert.c
@@ -22,7 +22,7 @@
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
 #include "utils/rel.h"
-
+#include "access/htup_details.h"
 
 /*
  * SPPageDesc tracks all info about a page we are inserting into.  In some
@@ -220,7 +220,7 @@ addLeafTuple(Relation index, SpGistState *state, SpGistLeafTuple leafTuple,
 		SpGistBlockIsRoot(current->blkno))
 	{
 		/* Tuple is not part of a chain */
-		leafTuple->nextOffset = InvalidOffsetNumber;
+		SGLT_SET_OFFSET (leafTuple->nextOffset, InvalidOffsetNumber);
 		current->offnum = SpGistPageAddNewItem(state, current->page,
 											   (Item) leafTuple, leafTuple->size,
 											   NULL, false);
@@ -253,7 +253,7 @@ addLeafTuple(Relation index, SpGistState *state, SpGistLeafTuple leafTuple,
 											 PageGetItemId(current->page, current->offnum));
 		if (head->tupstate == SPGIST_LIVE)
 		{
-			leafTuple->nextOffset = head->nextOffset;
+			SGLT_SET_OFFSET(leafTuple->nextOffset, SGLT_GET_OFFSET(head->nextOffset));
 			offnum = SpGistPageAddNewItem(state, current->page,
 										  (Item) leafTuple, leafTuple->size,
 										  NULL, false);
@@ -264,14 +264,14 @@ addLeafTuple(Relation index, SpGistState *state, SpGistLeafTuple leafTuple,
 			 */
 			head = (SpGistLeafTuple) PageGetItem(current->page,
 												 PageGetItemId(current->page, current->offnum));
-			head->nextOffset = offnum;
+			SGLT_SET_OFFSET(head->nextOffset, offnum);
 
 			xlrec.offnumLeaf = offnum;
 			xlrec.offnumHeadLeaf = current->offnum;
 		}
 		else if (head->tupstate == SPGIST_DEAD)
 		{
-			leafTuple->nextOffset = InvalidOffsetNumber;
+			SGLT_SET_OFFSET(leafTuple->nextOffset,InvalidOffsetNumber);
 			PageIndexTupleDelete(current->page, current->offnum);
 			if (PageAddItem(current->page,
 							(Item) leafTuple, leafTuple->size,
@@ -362,13 +362,13 @@ checkSplitConditions(Relation index, SpGistState *state,
 		{
 			/* We could see a DEAD tuple as first/only chain item */
 			Assert(i == current->offnum);
-			Assert(it->nextOffset == InvalidOffsetNumber);
+			Assert(SGLT_GET_OFFSET(it->nextOffset) == InvalidOffsetNumber);
 			/* Don't count it in result, because it won't go to other page */
 		}
 		else
 			elog(ERROR, "unexpected SPGiST tuple state: %d", it->tupstate);
 
-		i = it->nextOffset;
+		i = SGLT_GET_OFFSET(it->nextOffset);
 	}
 
 	*nToSplit = n;
@@ -437,7 +437,7 @@ moveLeafs(Relation index, SpGistState *state,
 		{
 			/* We could see a DEAD tuple as first/only chain item */
 			Assert(i == current->offnum);
-			Assert(it->nextOffset == InvalidOffsetNumber);
+			Assert(SGLT_GET_OFFSET(it->nextOffset) == InvalidOffsetNumber);
 			/* We don't want to move it, so don't count it in size */
 			toDelete[nDelete] = i;
 			nDelete++;
@@ -446,7 +446,7 @@ moveLeafs(Relation index, SpGistState *state,
 		else
 			elog(ERROR, "unexpected SPGiST tuple state: %d", it->tupstate);
 
-		i = it->nextOffset;
+		i = SGLT_GET_OFFSET(it->nextOffset);
 	}
 
 	/* Find a leaf page that will hold them */
@@ -475,7 +475,7 @@ moveLeafs(Relation index, SpGistState *state,
 			 * don't care).  We're modifying the tuple on the source page
 			 * here, but it's okay since we're about to delete it.
 			 */
-			it->nextOffset = r;
+			SGLT_SET_OFFSET(it->nextOffset,r);
 
 			r = SpGistPageAddNewItem(state, npage, (Item) it, it->size,
 									 &startOffset, false);
@@ -490,7 +490,7 @@ moveLeafs(Relation index, SpGistState *state,
 	}
 
 	/* add the new tuple as well */
-	newLeafTuple->nextOffset = r;
+	SGLT_SET_OFFSET(newLeafTuple->nextOffset, r);
 	r = SpGistPageAddNewItem(state, npage,
 							 (Item) newLeafTuple, newLeafTuple->size,
 							 &startOffset, false);
@@ -709,6 +709,9 @@ doPickSplit(Relation index, SpGistState *state,
 	int			nToDelete,
 				nToInsert,
 				maxToInclude;
+	Datum  		*leafChainDatums;
+	bool 	 	*leafChainIsnulls;
+	const int natts = IndexRelationGetNumberOfAttributes(index);
 
 	in.level = level;
 
@@ -723,14 +726,16 @@ doPickSplit(Relation index, SpGistState *state,
 	toInsert = (OffsetNumber *) palloc(sizeof(OffsetNumber) * n);
 	newLeafs = (SpGistLeafTuple *) palloc(sizeof(SpGistLeafTuple) * n);
 	leafPageSelect = (uint8 *) palloc(sizeof(uint8) * n);
-
 	STORE_STATE(state, xlrec.stateSrc);
 
+	leafChainDatums = (Datum *) palloc(n*natts*sizeof(Datum));
+	leafChainIsnulls = (bool *) palloc(n*natts*sizeof(bool));
+
 	/*
-	 * Form list of leaf tuples which will be distributed as split result;
-	 * also, count up the amount of space that will be freed from current.
-	 * (Note that in the non-root case, we won't actually delete the old
-	 * tuples, only replace them with redirects or placeholders.)
+	 * Collect leaf tuples which will be distributed as split result; also,
+	 * count up the amount of space that will be freed from current. (Note
+	 * that in the non-root case, we won't actually delete the old tuples,
+	 * only replace them with redirects or placeholders.)
 	 *
 	 * Note: the SGLTDATUM calls here are safe even when dealing with a nulls
 	 * page.  For a pass-by-value data type we will fetch a word that must
@@ -738,7 +743,14 @@ doPickSplit(Relation index, SpGistState *state,
 	 * tuples must have size at least SGDTSIZE).  For a pass-by-reference type
 	 * we are just computing a pointer that isn't going to get dereferenced.
 	 * So it's not worth guarding the calls with isNulls checks.
+	 *
+	 * Datums and isnulls of all leaf tuple attributes in a chain are collected
+	 * into 2-d arrays: (number of tuples in chain) x (number of attributes)
+	 * First attribute is key, the other - included attributes (if any). After
+	 * picksplit we need to form new leaf tuples as key attribute length can
+	 * change which can affect alignment of every include attribute.
 	 */
+
 	nToInsert = 0;
 	nToDelete = 0;
 	spaceToDelete = 0;
@@ -759,6 +771,8 @@ doPickSplit(Relation index, SpGistState *state,
 			{
 				in.datums[nToInsert] = SGLTDATUM(it, state);
 				heapPtrs[nToInsert] = it->heapPtr;
+				SpGistDeformLeafTuple(it, state, leafChainDatums+nToInsert*natts,
+									  leafChainIsnulls+nToInsert*natts, isNulls);
 				nToInsert++;
 				toDelete[nToDelete] = i;
 				nToDelete++;
@@ -784,6 +798,9 @@ doPickSplit(Relation index, SpGistState *state,
 			{
 				in.datums[nToInsert] = SGLTDATUM(it, state);
 				heapPtrs[nToInsert] = it->heapPtr;
+
+				SpGistDeformLeafTuple(it, state, leafChainDatums+nToInsert*natts,
+									  leafChainIsnulls+nToInsert*natts, isNulls);
 				nToInsert++;
 				toDelete[nToDelete] = i;
 				nToDelete++;
@@ -795,7 +812,7 @@ doPickSplit(Relation index, SpGistState *state,
 			{
 				/* We could see a DEAD tuple as first/only chain item */
 				Assert(i == current->offnum);
-				Assert(it->nextOffset == InvalidOffsetNumber);
+				Assert(SGLT_GET_OFFSET(it->nextOffset) == InvalidOffsetNumber);
 				toDelete[nToDelete] = i;
 				nToDelete++;
 				/* replacing it with redirect will save no space */
@@ -803,7 +820,7 @@ doPickSplit(Relation index, SpGistState *state,
 			else
 				elog(ERROR, "unexpected SPGiST tuple state: %d", it->tupstate);
 
-			i = it->nextOffset;
+			i = SGLT_GET_OFFSET(it->nextOffset);
 		}
 	}
 	in.nTuples = nToInsert;
@@ -816,10 +833,17 @@ doPickSplit(Relation index, SpGistState *state,
 	 */
 	in.datums[in.nTuples] = SGLTDATUM(newLeafTuple, state);
 	heapPtrs[in.nTuples] = newLeafTuple->heapPtr;
+
+	SpGistDeformLeafTuple(newLeafTuple, state, leafChainDatums+(in.nTuples)*natts,
+						  leafChainIsnulls+(in.nTuples)*natts, isNulls);
 	in.nTuples++;
 
 	memset(&out, 0, sizeof(out));
 
+	/*
+	 * Process collected key values of tuples from the chain. Included values are used
+	 * to build fresh leaf tuples unchanged.
+	 */
 	if (!isNulls)
 	{
 		/*
@@ -837,9 +861,11 @@ doPickSplit(Relation index, SpGistState *state,
 		totalLeafSizes = 0;
 		for (i = 0; i < in.nTuples; i++)
 		{
-			newLeafs[i] = spgFormLeafTuple(state, heapPtrs + i,
-										   out.leafTupleDatums[i],
-										   false);
+			*(leafChainDatums+i*natts)=(Datum) out.leafTupleDatums[i];
+			*(leafChainIsnulls+i*natts)=false;
+
+			newLeafs[i] = spgFormLeafTuple(state, heapPtrs + i, leafChainDatums+i*natts,
+										   leafChainIsnulls+i*natts);
 			totalLeafSizes += newLeafs[i]->size + sizeof(ItemIdData);
 		}
 	}
@@ -860,9 +886,14 @@ doPickSplit(Relation index, SpGistState *state,
 		totalLeafSizes = 0;
 		for (i = 0; i < in.nTuples; i++)
 		{
-			newLeafs[i] = spgFormLeafTuple(state, heapPtrs + i,
-										   (Datum) 0,
-										   true);
+			/*
+			 * Nulls tree can contain only null key values.
+			 */
+			*(leafChainDatums+i*natts)=(Datum) 0;
+			*(leafChainIsnulls+i*natts)=true;
+
+			newLeafs[i] = spgFormLeafTuple(state, heapPtrs + i, leafChainDatums+i*natts,
+										   leafChainIsnulls+i*natts);
 			totalLeafSizes += newLeafs[i]->size + sizeof(ItemIdData);
 		}
 	}
@@ -1196,10 +1227,10 @@ doPickSplit(Relation index, SpGistState *state,
 		if (ItemPointerIsValid(&nodes[n]->t_tid))
 		{
 			Assert(ItemPointerGetBlockNumber(&nodes[n]->t_tid) == leafBlock);
-			it->nextOffset = ItemPointerGetOffsetNumber(&nodes[n]->t_tid);
+			SGLT_SET_OFFSET(it->nextOffset, ItemPointerGetOffsetNumber(&nodes[n]->t_tid));
 		}
 		else
-			it->nextOffset = InvalidOffsetNumber;
+			SGLT_SET_OFFSET(it->nextOffset, InvalidOffsetNumber);
 
 		/* Insert it on page */
 		newoffset = SpGistPageAddNewItem(state, BufferGetPage(leafBuffer),
@@ -1889,67 +1920,81 @@ spgSplitNodeAction(Relation index, SpGistState *state,
  */
 bool
 spgdoinsert(Relation index, SpGistState *state,
-			ItemPointer heapPtr, Datum datum, bool isnull)
+			ItemPointer heapPtr, Datum *datum, bool *isnull)
 {
 	int			level = 0;
-	Datum		leafDatum;
+	Datum		*leafDatum;
 	int			leafSize;
 	SPPageDesc	current,
 				parent;
 	FmgrInfo   *procinfo = NULL;
+	int i;
 
 	/*
 	 * Look up FmgrInfo of the user-defined choose function once, to save
 	 * cycles in the loop below.
 	 */
-	if (!isnull)
+	if (!isnull[0])
 		procinfo = index_getprocinfo(index, 1, SPGIST_CHOOSE_PROC);
 
 	/*
 	 * Prepare the leaf datum to insert.
-	 *
-	 * If an optional "compress" method is provided, then call it to form the
-	 * leaf datum from the input datum.  Otherwise store the input datum as
+	 */
+
+	leafDatum = (Datum *) palloc0(sizeof(Datum) * (IndexRelationGetNumberOfAttributes(index)));
+
+	/* If an optional "compress" method is provided, then call it to form the
+	 * key datum from the input datum.  Otherwise store the input datum as
 	 * is.  Since we don't use index_form_tuple in this AM, we have to make
 	 * sure value to be inserted is not toasted; FormIndexDatum doesn't
 	 * guarantee that.  But we assume the "compress" method to return an
 	 * untoasted value.
 	 */
-	if (!isnull)
+	if (!isnull[0])
 	{
 		if (OidIsValid(index_getprocid(index, 1, SPGIST_COMPRESS_PROC)))
 		{
 			FmgrInfo   *compressProcinfo = NULL;
 
 			compressProcinfo = index_getprocinfo(index, 1, SPGIST_COMPRESS_PROC);
-			leafDatum = FunctionCall1Coll(compressProcinfo,
+			leafDatum[0] = FunctionCall1Coll(compressProcinfo,
 										  index->rd_indcollation[0],
-										  datum);
+										  datum[0]);
 		}
 		else
 		{
 			Assert(state->attLeafType.type == state->attType.type);
 
 			if (state->attType.attlen == -1)
-				leafDatum = PointerGetDatum(PG_DETOAST_DATUM(datum));
+				leafDatum[0] = PointerGetDatum(PG_DETOAST_DATUM(datum[0]));
 			else
-				leafDatum = datum;
+				leafDatum[0] = datum[0];
 		}
 	}
 	else
-		leafDatum = (Datum) 0;
+		leafDatum[0] = (Datum) 0;
+
+	for (i = 1; i < IndexRelationGetNumberOfAttributes(index); i++)
+		{
+			if (!isnull[i])
+			{
+				if (TupleDescAttr(state->includeTupdesc, i-1)->attlen == -1)
+					leafDatum[i] = PointerGetDatum(PG_DETOAST_DATUM(datum[i]));
+				else
+					leafDatum[i]=datum[i];
+			}
+			else
+				leafDatum[i]=(Datum) 0;
+		}
+
 
 	/*
-	 * Compute space needed for a leaf tuple containing the given datum.
+	 * Compute space needed on a page for a leaf tuple containing the given datum.
 	 *
 	 * If it isn't gonna fit, and the opclass can't reduce the datum size by
 	 * suffixing, bail out now rather than getting into an endless loop.
 	 */
-	if (!isnull)
-		leafSize = SGLTHDRSZ + sizeof(ItemIdData) +
-			SpGistGetTypeSize(&state->attLeafType, leafDatum);
-	else
-		leafSize = SGDTSIZE + sizeof(ItemIdData);
+	leafSize = SpgLeafSize(state, leafDatum, isnull) + sizeof(ItemIdData);
 
 	if (leafSize > SPGIST_PAGE_CAPACITY && !state->config.longValuesOK)
 		ereport(ERROR,
@@ -1961,7 +2006,7 @@ spgdoinsert(Relation index, SpGistState *state,
 				 errhint("Values larger than a buffer page cannot be indexed.")));
 
 	/* Initialize "current" to the appropriate root page */
-	current.blkno = isnull ? SPGIST_NULL_BLKNO : SPGIST_ROOT_BLKNO;
+	current.blkno = isnull[0] ? SPGIST_NULL_BLKNO : SPGIST_ROOT_BLKNO;
 	current.buffer = InvalidBuffer;
 	current.page = NULL;
 	current.offnum = FirstOffsetNumber;
@@ -1995,7 +2040,7 @@ spgdoinsert(Relation index, SpGistState *state,
 			 */
 			current.buffer =
 				SpGistGetBuffer(index,
-								GBUF_LEAF | (isnull ? GBUF_NULLS : 0),
+								GBUF_LEAF | (isnull[0] ? GBUF_NULLS : 0),
 								Min(leafSize, SPGIST_PAGE_CAPACITY),
 								&isNew);
 			current.blkno = BufferGetBlockNumber(current.buffer);
@@ -2037,7 +2082,7 @@ spgdoinsert(Relation index, SpGistState *state,
 		current.page = BufferGetPage(current.buffer);
 
 		/* should not arrive at a page of the wrong type */
-		if (isnull ? !SpGistPageStoresNulls(current.page) :
+		if (isnull[0] ? !SpGistPageStoresNulls(current.page) :
 			SpGistPageStoresNulls(current.page))
 			elog(ERROR, "SPGiST index page %u has wrong nulls flag",
 				 current.blkno);
@@ -2054,7 +2099,7 @@ spgdoinsert(Relation index, SpGistState *state,
 			{
 				/* it fits on page, so insert it and we're done */
 				addLeafTuple(index, state, leafTuple,
-							 &current, &parent, isnull, isNew);
+							 &current, &parent, isnull[0], isNew);
 				break;
 			}
 			else if ((sizeToSplit =
@@ -2068,14 +2113,14 @@ spgdoinsert(Relation index, SpGistState *state,
 				 * chain to another leaf page rather than splitting it.
 				 */
 				Assert(!isNew);
-				moveLeafs(index, state, &current, &parent, leafTuple, isnull);
+				moveLeafs(index, state, &current, &parent, leafTuple, isnull[0]);
 				break;			/* we're done */
 			}
 			else
 			{
 				/* picksplit */
 				if (doPickSplit(index, state, &current, &parent,
-								leafTuple, level, isnull, isNew))
+								leafTuple, level, isnull[0], isNew))
 					break;		/* doPickSplit installed new tuples */
 
 				/* leaf tuple will not be inserted yet */
@@ -2110,8 +2155,8 @@ spgdoinsert(Relation index, SpGistState *state,
 			innerTuple = (SpGistInnerTuple) PageGetItem(current.page,
 														PageGetItemId(current.page, current.offnum));
 
-			in.datum = datum;
-			in.leafDatum = leafDatum;
+			in.datum = datum[0];
+			in.leafDatum = leafDatum[0];
 			in.level = level;
 			in.allTheSame = innerTuple->allTheSame;
 			in.hasPrefix = (innerTuple->prefixSize > 0);
@@ -2121,7 +2166,7 @@ spgdoinsert(Relation index, SpGistState *state,
 
 			memset(&out, 0, sizeof(out));
 
-			if (!isnull)
+			if (!isnull[0])
 			{
 				/* use user-defined choose method */
 				FunctionCall2Coll(procinfo,
@@ -2158,11 +2203,11 @@ spgdoinsert(Relation index, SpGistState *state,
 					/* Adjust level as per opclass request */
 					level += out.result.matchNode.levelAdd;
 					/* Replace leafDatum and recompute leafSize */
-					if (!isnull)
+					if (!isnull[0])
 					{
-						leafDatum = out.result.matchNode.restDatum;
-						leafSize = SGLTHDRSZ + sizeof(ItemIdData) +
-							SpGistGetTypeSize(&state->attLeafType, leafDatum);
+						leafDatum[0] = out.result.matchNode.restDatum;
+						leafSize = SpgLeafSize(state, leafDatum, isnull) +
+								   sizeof(ItemIdData);
 					}
 
 					/*
@@ -2227,6 +2272,6 @@ spgdoinsert(Relation index, SpGistState *state,
 		SpGistSetLastUsedPage(index, parent.buffer);
 		UnlockReleaseBuffer(parent.buffer);
 	}
-
+	pfree(leafDatum);
 	return true;
 }
diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c
index e4508a2b92..b54ae85f6e 100644
--- a/src/backend/access/spgist/spginsert.c
+++ b/src/backend/access/spgist/spginsert.c
@@ -55,8 +55,7 @@ spgistBuildCallback(Relation index, ItemPointer tid, Datum *values,
 	 * lock on some buffer.  So we need to be willing to retry.  We can flush
 	 * any temp data when retrying.
 	 */
-	while (!spgdoinsert(index, &buildstate->spgstate, tid,
-						*values, *isnull))
+	while (!spgdoinsert(index, &buildstate->spgstate, tid, values, isnull))
 	{
 		MemoryContextReset(buildstate->tmpCtx);
 	}
@@ -226,7 +225,7 @@ spginsert(Relation index, Datum *values, bool *isnull,
 	 * to avoid cumulative memory consumption.  That means we also have to
 	 * redo initSpGistState(), but it's cheap enough not to matter.
 	 */
-	while (!spgdoinsert(index, &spgstate, ht_ctid, *values, *isnull))
+	while (!spgdoinsert(index, &spgstate, ht_ctid, values, isnull))
 	{
 		MemoryContextReset(insertCtx);
 		initSpGistState(&spgstate, index);
diff --git a/src/backend/access/spgist/spgscan.c b/src/backend/access/spgist/spgscan.c
index 4d506bfb9a..b5dedc3afd 100644
--- a/src/backend/access/spgist/spgscan.c
+++ b/src/backend/access/spgist/spgscan.c
@@ -28,7 +28,8 @@
 
 typedef void (*storeRes_func) (SpGistScanOpaque so, ItemPointer heapPtr,
 							   Datum leafValue, bool isNull, bool recheck,
-							   bool recheckDistances, double *distances);
+							   bool recheckDistances, double *distances,
+							   SpGistLeafTuple leafTuple);
 
 /*
  * Pairing heap comparison function for the SpGistSearchItem queue.
@@ -88,6 +89,9 @@ spgFreeSearchItem(SpGistScanOpaque so, SpGistSearchItem *item)
 	if (item->traversalValue)
 		pfree(item->traversalValue);
 
+	if (item->isLeaf && item->leafTuple)
+		pfree(item->leafTuple);
+
 	pfree(item);
 }
 
@@ -134,6 +138,8 @@ spgAddStartItem(SpGistScanOpaque so, bool isnull)
 	startEntry->recheck = false;
 	startEntry->recheckDistances = false;
 
+	startEntry->leafTuple = NULL;
+
 	spgAddSearchItemToQueue(so, startEntry);
 }
 
@@ -438,14 +444,29 @@ spgendscan(IndexScanDesc scan)
  * Leaf SpGistSearchItem constructor, called in queue context
  */
 static SpGistSearchItem *
-spgNewHeapItem(SpGistScanOpaque so, int level, ItemPointer heapPtr,
+spgNewHeapItem(SpGistScanOpaque so, int level, SpGistLeafTuple leafTuple,
 			   Datum leafValue, bool recheck, bool recheckDistances,
 			   bool isnull, double *distances)
 {
 	SpGistSearchItem *item = spgAllocSearchItem(so, isnull, distances);
+	/*
+	 * If there are include attributes search item in the queue should
+	 * contain them.
+	 */
+	if (so->state.includeTupdesc)
+	{
+		Assert(so->state.includeTupdesc->natts);
+
+		item->leafTuple = palloc(leafTuple->size);
+		memcpy(item->leafTuple, leafTuple, leafTuple->size);
+	}
+	else
+	{
+		item->leafTuple = NULL;
+	}
 
 	item->level = level;
-	item->heapPtr = *heapPtr;
+	item->heapPtr = leafTuple->heapPtr;
 	/* copy value to queue cxt out of tmp cxt */
 	item->value = isnull ? (Datum) 0 :
 		datumCopy(leafValue, so->state.attLeafType.attbyval,
@@ -503,6 +524,8 @@ spgLeafTest(SpGistScanOpaque so, SpGistSearchItem *item,
 		in.returnData = so->want_itup;
 		in.leafDatum = SGLTDATUM(leafTuple, &so->state);
 
+
+
 		out.leafValue = (Datum) 0;
 		out.recheck = false;
 		out.distances = NULL;
@@ -528,13 +551,12 @@ spgLeafTest(SpGistScanOpaque so, SpGistSearchItem *item,
 			/* the scan is ordered -> add the item to the queue */
 			MemoryContext oldCxt = MemoryContextSwitchTo(so->traversalCxt);
 			SpGistSearchItem *heapItem = spgNewHeapItem(so, item->level,
-														&leafTuple->heapPtr,
+														leafTuple,
 														leafValue,
 														recheck,
 														recheckDistances,
 														isnull,
 														distances);
-
 			spgAddSearchItemToQueue(so, heapItem);
 
 			MemoryContextSwitchTo(oldCxt);
@@ -543,8 +565,10 @@ spgLeafTest(SpGistScanOpaque so, SpGistSearchItem *item,
 		{
 			/* non-ordered scan, so report the item right away */
 			Assert(!recheckDistances);
+
 			storeRes(so, &leafTuple->heapPtr, leafValue, isnull,
-					 recheck, false, NULL);
+							 recheck, false, NULL, leafTuple);
+
 			*reportedSome = true;
 		}
 	}
@@ -736,7 +760,7 @@ spgTestLeafTuple(SpGistScanOpaque so,
 				/* dead tuple should be first in chain */
 				Assert(offset == ItemPointerGetOffsetNumber(&item->heapPtr));
 				/* No live entries on this page */
-				Assert(leafTuple->nextOffset == InvalidOffsetNumber);
+				Assert(SGLT_GET_OFFSET(leafTuple->nextOffset) == InvalidOffsetNumber);
 				return SpGistBreakOffsetNumber;
 			}
 		}
@@ -750,7 +774,7 @@ spgTestLeafTuple(SpGistScanOpaque so,
 
 	spgLeafTest(so, item, leafTuple, isnull, reportedSome, storeRes);
 
-	return leafTuple->nextOffset;
+	return SGLT_GET_OFFSET(leafTuple->nextOffset);
 }
 
 /*
@@ -782,8 +806,8 @@ redirect:
 		{
 			/* We store heap items in the queue only in case of ordered search */
 			Assert(so->numberOfNonNullOrderBys > 0);
-			storeRes(so, &item->heapPtr, item->value, item->isNull,
-					 item->recheck, item->recheckDistances, item->distances);
+			storeRes(so, &item->heapPtr, item->value, item->isNull, item->recheck,
+					item->recheckDistances, item->distances, item->leafTuple);
 			reportedSome = true;
 		}
 		else
@@ -877,7 +901,7 @@ redirect:
 static void
 storeBitmap(SpGistScanOpaque so, ItemPointer heapPtr,
 			Datum leafValue, bool isnull, bool recheck, bool recheckDistances,
-			double *distances)
+			double *distances, SpGistLeafTuple leafTuple)
 {
 	Assert(!recheckDistances && !distances);
 	tbm_add_tuples(so->tbm, heapPtr, 1, recheck);
@@ -904,7 +928,7 @@ spggetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 static void
 storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr,
 			  Datum leafValue, bool isnull, bool recheck, bool recheckDistances,
-			  double *nonNullDistances)
+			  double *nonNullDistances, SpGistLeafTuple leafTuple)
 {
 	Assert(so->nPtrs < MaxIndexTuplesPerPage);
 	so->heapPtrs[so->nPtrs] = *heapPtr;
@@ -923,7 +947,7 @@ storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr,
 
 			for (i = 0; i < so->numberOfOrderBys; i++)
 			{
-				int			offset = so->nonNullOrderByOffsets[i];
+				int		offset = so->nonNullOrderByOffsets[i];
 
 				if (offset >= 0)
 				{
@@ -949,9 +973,35 @@ storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr,
 		 * Reconstruct index data.  We have to copy the datum out of the temp
 		 * context anyway, so we may as well create the tuple here.
 		 */
-		so->reconTups[so->nPtrs] = heap_form_tuple(so->indexTupDesc,
+		if (so->state.includeTupdesc)
+		{
+			/* Add included attributes */
+			Datum *leafDatums;
+			bool *leafIsnulls;
+
+			Assert(so->state.includeTupdesc->natts);
+
+			leafDatums = (Datum *) palloc(sizeof(Datum) * (so->state.includeTupdesc->natts + 1));
+			leafIsnulls = (bool *) palloc(sizeof(bool) * (so->state.includeTupdesc->natts + 1));
+
+			SpGistDeformLeafTuple(leafTuple, &so->state, leafDatums, leafIsnulls, isnull);
+
+			/* override key value extracted from LeafTuple in case we've reconstructed it already */
+			leafDatums[0]=leafValue;
+			leafIsnulls[0]=isnull;
+
+			so->reconTups[so->nPtrs] = heap_form_tuple(so->indexTupDesc,
+												   leafDatums,
+												   leafIsnulls);
+			pfree(leafDatums);
+			pfree(leafIsnulls);
+		}
+		else
+		{
+			so->reconTups[so->nPtrs] = heap_form_tuple(so->indexTupDesc,
 												   &leafValue,
 												   &isnull);
+		}
 	}
 	so->nPtrs++;
 }
@@ -1018,6 +1068,9 @@ bool
 spgcanreturn(Relation index, int attno)
 {
 	SpGistCache *cache;
+	
+	/* Included attributes always can be fetched for index-only scans */
+	if (attno > 1) return true;
 
 	/* We can do it if the opclass config function says so */
 	cache = spgGetCache(index);
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 0efe05e552..d052d52c25 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -31,8 +31,18 @@
 #include "utils/index_selfuncs.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
-
-
+#include "access/itup.h"
+#include "access/detoast.h"
+#include "access/toast_internals.h"
+#include "access/heaptoast.h"
+#include "utils/expandeddatum.h"
+
+/* Does att's datatype allow packing into the 1-byte-header varlena format? */
+#define ATT_IS_PACKABLE(att) \
+		((att)->attlen == -1 && (att)->attstorage != TYPSTORAGE_PLAIN)
+
+Size spgIncludedDataSize(TupleDesc tupleDesc, Datum *values,
+								  bool *isnull, Size start);
 /*
  * SP-GiST handler function: return IndexAmRoutine with access method parameters
  * and callbacks.
@@ -49,7 +59,7 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amcanorderbyop = true;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
-	amroutine->amcanmulticol = false;
+	amroutine->amcanmulticol = true;
 	amroutine->amoptionalkey = true;
 	amroutine->amsearcharray = false;
 	amroutine->amsearchnulls = true;
@@ -57,7 +67,7 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amclusterable = false;
 	amroutine->ampredlocks = false;
 	amroutine->amcanparallel = false;
-	amroutine->amcaninclude = false;
+	amroutine->amcaninclude = true;
 	amroutine->amusemaintenanceworkmem = false;
 	amroutine->amparallelvacuumoptions =
 		VACUUM_OPTION_PARALLEL_BULKDEL | VACUUM_OPTION_PARALLEL_COND_CLEANUP;
@@ -112,18 +122,21 @@ spgGetCache(Relation index)
 		FmgrInfo   *procinfo;
 		Buffer		metabuffer;
 		SpGistMetaPageData *metadata;
-
 		cache = MemoryContextAllocZero(index->rd_indexcxt,
 									   sizeof(SpGistCache));
 
-		/* SPGiST doesn't support multi-column indexes */
-		Assert(index->rd_att->natts == 1);
+		/* SPGiST should have one key column and can also have included columns */
+		if (IndexRelationGetNumberOfKeyAttributes(index) != 1)
+			ereport(ERROR,
+			(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+			errmsg("SPGiST index can have only one key column")));
 
 		/*
-		 * Get the actual data type of the indexed column from the index
+		 * Get the actual data type of the key column from the index
 		 * tupdesc.  We pass this to the opclass config function so that
 		 * polymorphic opclasses are possible.
 		 */
+
 		atttype = TupleDescAttr(index->rd_att, 0)->atttypid;
 
 		/* Call the config function to get config info for the opclass */
@@ -156,6 +169,7 @@ spgGetCache(Relation index)
 		fillTypeDesc(&cache->attPrefixType, cache->config.prefixType);
 		fillTypeDesc(&cache->attLabelType, cache->config.labelType);
 
+
 		/* Last, get the lastUsedPages data from the metapage */
 		metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
 		LockBuffer(metabuffer, BUFFER_LOCK_SHARE);
@@ -177,7 +191,22 @@ spgGetCache(Relation index)
 		/* assume it's up to date */
 		cache = (SpGistCache *) index->rd_amcache;
 	}
+		/* Form descriptor for included columns if any */
+		if (IndexRelationGetNumberOfAttributes(index) > 1)
+		{
+			int i;
+			cache->includeTupdesc = CreateTemplateTupleDesc(
+							IndexRelationGetNumberOfAttributes(index) - 1);
 
+			for (i = 0; i < IndexRelationGetNumberOfAttributes(index) - 1; i++)
+			{
+				TupleDescInitEntry(cache->includeTupdesc, i + 1, NULL,
+								   TupleDescAttr(index->rd_att, i + 1)->atttypid,
+								   -1, 0);
+			}
+		}
+		else
+			cache->includeTupdesc = NULL;
 	return cache;
 }
 
@@ -190,6 +219,7 @@ initSpGistState(SpGistState *state, Relation index)
 	/* Get cached static information about index */
 	cache = spgGetCache(index);
 
+	state->includeTupdesc = cache->includeTupdesc;
 	state->config = cache->config;
 	state->attType = cache->attType;
 	state->attLeafType = cache->attLeafType;
@@ -603,7 +633,7 @@ spgoptions(Datum reloptions, bool validate)
 
 /*
  * Get the space needed to store a non-null datum of the indicated type.
- * Note the result is already rounded up to a MAXALIGN boundary.
+ * Note the result is not maxaligned and this should be done by caller if needed.
  * Also, we follow the SPGiST convention that pass-by-val types are
  * just stored in their Datum representation (compare memcpyDatum).
  */
@@ -619,7 +649,7 @@ SpGistGetTypeSize(SpGistTypeDesc *att, Datum datum)
 	else
 		size = VARSIZE_ANY(datum);
 
-	return MAXALIGN(size);
+	return size;
 }
 
 /*
@@ -642,36 +672,198 @@ memcpyDatum(void *target, SpGistTypeDesc *att, Datum datum)
 }
 
 /*
- * Construct a leaf tuple containing the given heap TID and datum value
+ * Private version of heap_compute_data_size with start address not
+ * necessarily MAXALIGNed. The reason is that start address (and alignment)
+ * influence alignment of each of next values and overall size of included
+ * data area in SpGiST leaf tuple.
+ */
+Size
+spgIncludedDataSize(TupleDesc tupleDesc,
+					   Datum *values,
+					   bool *isnull, Size start)
+{
+	Size		data_length = 0;
+	int			i;
+	int			numberOfAttributes = tupleDesc->natts;
+
+	data_length = start;
+	for (i = 0; i < numberOfAttributes; i++)
+	{
+		Datum		val;
+		Form_pg_attribute atti;
+
+		if (isnull[i])
+			continue;
+
+		val = values[i];
+		atti = TupleDescAttr(tupleDesc, i);
+
+		if (ATT_IS_PACKABLE(atti) &&
+			VARATT_CAN_MAKE_SHORT(DatumGetPointer(val)))
+		{
+			/*
+			 * we're anticipating converting to a short varlena header, so
+			 * adjust length and don't count any alignment
+			 */
+			data_length += VARATT_CONVERTED_SHORT_SIZE(DatumGetPointer(val));
+		}
+		else if (atti->attlen == -1 &&
+				 VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(val)))
+		{
+			/*
+			 * we want to flatten the expanded value so that the constructed
+			 * tuple doesn't depend on it
+			 */
+			data_length = att_align_nominal(data_length, atti->attalign);
+			data_length += EOH_get_flat_size(DatumGetEOHP(val));
+		}
+		else
+		{
+			data_length = att_align_datum(data_length, atti->attalign,
+										  atti->attlen, val);
+			data_length = att_addlength_datum(data_length, atti->attlen,
+											  val);
+		}
+	}
+	return data_length-start;
+}
+
+/* Calculate overall leaf tuple size. SGLTHDRSZ is MAXALIGNed only for backward
+ * compatibility and there might be gap between header and key data. After key
+ * data there are no such gaps more than is is necessary for each value
+ * alignment. Overall result is MAXALIGNed.*/
+unsigned int SpgLeafSize(SpGistState *state, Datum *datum, bool *isnull)
+{
+	/* compute space needed, nullmask size and offset for include attributes */
+	unsigned int size = SGLTHDRSZ;
+	unsigned int i;
+	if (!isnull[0])
+		/* key attribute size (not maxaligned) */
+		size += SpGistGetTypeSize(&state->attLeafType, datum[0]);
+
+	if (state->includeTupdesc)
+	{
+		Assert(state->includeTupdesc->natts);
+		if (state->includeTupdesc->natts + 1 >= INDEX_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("number of index columns (%d) exceeds limit (%d)",
+						state->includeTupdesc->natts, INDEX_MAX_KEYS)));
+		/* nullmask size */
+		for (i = 1; i <= state->includeTupdesc->natts; i++)
+		{
+			if (isnull[i])
+			{
+				size += (state->includeTupdesc->natts / 8) + 1;
+				break;
+			}
+		}
+		/* overall included attributes size each with added proper alignment. */
+		size += spgIncludedDataSize(state->includeTupdesc, datum+1, isnull+1, size);
+	}
+return MAXALIGN(size);
+}
+
+/*
+ * Construct a leaf tuple containing the given heap TID, key data and included
+ * columns data. Key data starts from MAXALIGN boundary for backward compatibility.
+ * Nullmask apply only to included attributes and is placed just after key data if
+ * there is at least one NULL among included attributes. It doesn't need alignment.
+ * Then all included columns data follow aligned by their typealign's.
  */
 SpGistLeafTuple
 spgFormLeafTuple(SpGistState *state, ItemPointer heapPtr,
-				 Datum datum, bool isnull)
+				 Datum *datum, bool *isnull)
 {
 	SpGistLeafTuple tup;
-	unsigned int size;
+	unsigned int size=SGLTHDRSZ;
+	unsigned int include_offset=0;
+	unsigned int nullmask_size=0;
+	unsigned int data_offset=0;
+	unsigned int data_size=0;
+	uint16 tupmask=0;
+	int i;
 
-	/* compute space needed (note result is already maxaligned) */
-	size = SGLTHDRSZ;
-	if (!isnull)
-		size += SpGistGetTypeSize(&state->attLeafType, datum);
+	/*
+	 * Calculate space needed. If there are include attributes also calculate sizes and
+	 * offsets needed for heap_fill_tuple
+	 */
+	if (!isnull[0])
+		/* key attribute size (not maxaligned) */
+		size += SpGistGetTypeSize(&state->attLeafType, datum[0]);
+
+	if (state->includeTupdesc)
+	{
+		Assert(state->includeTupdesc->natts);
+		if (state->includeTupdesc->natts + 1 >= INDEX_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("number of index columns (%d) exceeds limit (%d)",
+						state->includeTupdesc->natts, INDEX_MAX_KEYS)));
+
+		include_offset = size;
+
+		for (i = 1; i <= state->includeTupdesc->natts; i++)
+		{
+			if (isnull[i])
+			{
+				nullmask_size = (state->includeTupdesc->natts / 8) + 1;
+				size += nullmask_size;
+				break;
+			}
+		}
+
+		/*
+		 * Alignment of all included attributes is counted inside data_size. data_offset
+		 * itself is not aligned.
+		 */
+		data_size = spgIncludedDataSize(state->includeTupdesc, datum+1, isnull+1, size);
+		data_offset=size;
+
+		size += data_size;
+	}
 
 	/*
 	 * Ensure that we can replace the tuple with a dead tuple later.  This
-	 * test is unnecessary when !isnull, but let's be safe.
+	 * test is unnecessary when !isnull[0], but let's be safe.
 	 */
 	if (size < SGDTSIZE)
 		size = SGDTSIZE;
 
 	/* OK, form the tuple */
-	tup = (SpGistLeafTuple) palloc0(size);
+	tup = (SpGistLeafTuple) palloc0(MAXALIGN(size));
 
-	tup->size = size;
-	tup->nextOffset = InvalidOffsetNumber;
+	tup->size = MAXALIGN(size);
+	SGLT_SET_OFFSET(tup->nextOffset, InvalidOffsetNumber);
 	tup->heapPtr = *heapPtr;
-	if (!isnull)
-		memcpyDatum(SGLTDATAPTR(tup), &state->attLeafType, datum);
 
+	if (!isnull[0])
+		memcpyDatum(SGLTDATAPTR(tup), &state->attLeafType, datum[0]);
+
+	/* Add included columns data to leaf tuple if any. */
+	if (state->includeTupdesc)
+	{
+		/* The start of include attributes tuple is not aligned by default. All values
+		 * alignment should be done by heap_fill_tuple automaticaly. If there is a nulls
+		 * mask it is included just after key attribute data and it should not be aligned.
+		 */
+		heap_fill_tuple(state->includeTupdesc, datum+1, isnull+1,
+						(char *) tup + data_offset,
+						data_size, &tupmask,
+						(nullmask_size ? (bits8 *) tup + include_offset : NULL) );
+
+		if (nullmask_size)
+			SGLT_SET_CONTAINSNULLMASK(tup->nextOffset, 1);
+
+		/*
+		 * We do this because heap_fill_tuple wants to initialize a "tupmask"
+		 * which is used for HeapTuples, but the only relevant info is the
+		 * "has variable attributes" field. We have already set the hasnull
+		 * bit above.
+		 */
+		if (tupmask & HEAP_HASVARWIDTH)
+			SGLT_SET_CONTAINSVARATT(tup->nextOffset, 1);
+	}
 	return tup;
 }
 
@@ -688,10 +880,10 @@ spgFormNodeTuple(SpGistState *state, Datum label, bool isnull)
 	unsigned int size;
 	unsigned short infomask = 0;
 
-	/* compute space needed (note result is already maxaligned) */
+	/* compute space needed*/
 	size = SGNTHDRSZ;
 	if (!isnull)
-		size += SpGistGetTypeSize(&state->attLabelType, label);
+		size += MAXALIGN(SpGistGetTypeSize(&state->attLabelType, label));
 
 	/*
 	 * Here we make sure that the size will fit in the field reserved for it
@@ -735,7 +927,7 @@ spgFormInnerTuple(SpGistState *state, bool hasPrefix, Datum prefix,
 
 	/* Compute size needed */
 	if (hasPrefix)
-		prefixSize = SpGistGetTypeSize(&state->attPrefixType, prefix);
+		prefixSize = MAXALIGN(SpGistGetTypeSize(&state->attPrefixType, prefix));
 	else
 		prefixSize = 0;
 
@@ -1046,3 +1238,128 @@ spgproperty(Oid index_oid, int attno,
 
 	return true;
 }
+
+/*
+ * Convert an SpGist tuple into palloc'd Datum/isnull arrays.
+ *
+ */
+void
+SpGistDeformLeafTuple(SpGistLeafTuple tup, SpGistState *state, Datum *datum, bool *isnull,
+					  bool key_isnull)
+{
+	unsigned int include_offset;/* offset of include data */
+	int			off;
+	bits8	   *nullmask_ptr = NULL;	/* ptr to null bitmap in tuple */
+	char	   *tp;
+	bool		slow = false;	/* can we use/set attcacheoff? */
+	int i;
+
+	if (key_isnull)
+	{
+		datum[0] = (Datum) 0;
+		isnull[0] = true;
+	}
+	else
+	{
+		datum[0] = SGLTDATUM(tup, state);
+		isnull[0] = false;
+	}
+
+	if (state->includeTupdesc)
+	{
+		Assert(state->includeTupdesc->natts);
+		if (state->includeTupdesc->natts + 1 >= INDEX_MAX_KEYS)
+			ereport(ERROR,
+					(errcode(ERRCODE_TOO_MANY_COLUMNS),
+					 errmsg("number of index columns (%d) exceeds limit (%d)",
+						 state->includeTupdesc->natts, INDEX_MAX_KEYS)));
+
+		include_offset = key_isnull ? SGLTHDRSZ : SGLTHDRSZ + SpGistGetTypeSize(&state->attLeafType, datum[0]);
+
+		tp = (char*) tup;
+		off = include_offset;
+
+		if (SGLT_GET_CONTAINSNULLMASK(tup->nextOffset))
+		{
+			nullmask_ptr = (bits8 *) tp + include_offset;
+			off += (state->includeTupdesc->natts) / 8 + 1;
+		}
+
+		if (state->attLeafType.attlen > 0 && !SGLT_GET_CONTAINSVARATT(tup->nextOffset) &&
+			!SGLT_GET_CONTAINSNULLMASK(tup->nextOffset))
+		/* can use attcacheoff for all attributes */
+		{
+			for (i = 1; i <= state->includeTupdesc->natts; i++)
+			{
+				Form_pg_attribute thisatt = TupleDescAttr(state->includeTupdesc, i - 1);
+				isnull[i] = false;
+				if (thisatt->attcacheoff >= 0)
+					off = thisatt->attcacheoff;
+				else
+					{
+					off = att_align_nominal(off, thisatt->attalign);
+					thisatt->attcacheoff = off;
+					}
+				datum[i] = fetchatt(thisatt, tp + off);
+				off = att_addlength_pointer(off, thisatt->attlen, tp + off);
+			}
+		}
+		else
+		/* general case: can use cache until first null or varlen attribute */
+		{
+			if (state->attLeafType.attlen <= 0)
+				slow = true; 			/* can't use attcacheoff at all*/
+
+			for (i = 1; i <= state->includeTupdesc->natts; i++)
+			{
+				Form_pg_attribute thisatt = TupleDescAttr(state->includeTupdesc, i - 1);
+
+				if (SGLT_GET_CONTAINSNULLMASK(tup->nextOffset))
+				{
+					if (att_isnull(i - 1, nullmask_ptr))
+					{
+						datum[i] = (Datum) 0;
+						isnull[i] = true;
+						slow = true;		/* can't use attcacheoff anymore */
+						continue;
+					}
+				}
+
+				isnull[i] = false;
+
+				if (!slow && thisatt->attcacheoff >= 0)
+					off = thisatt->attcacheoff;
+				else if (thisatt->attlen == -1)
+				{
+					/*
+					 * We can only cache the offset for a varlena attribute if the
+					 * offset is already suitably aligned, so that there would be no
+					 * pad bytes in any case: then the offset will be valid for either
+					 * an aligned or unaligned value.
+					 */
+					if (!slow && off == att_align_nominal(off, thisatt->attalign))
+						thisatt->attcacheoff = off;
+					else
+					{
+						off = att_align_pointer(off, thisatt->attalign, -1, tp + off);
+						slow = true;
+					}
+				}
+				else
+				{
+					/* not varlena, so safe to use att_align_nominal */
+					off = att_align_nominal(off, thisatt->attalign);
+
+					if (!slow)
+						thisatt->attcacheoff = off;
+				}
+
+				datum[i] = fetchatt(thisatt, tp + off);
+				off = att_addlength_pointer(off, thisatt->attlen, tp + off);
+
+				if (thisatt->attlen <= 0)
+					slow = true;		/* can't use attcacheoff anymore */
+			}
+		}
+	}
+}
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index bd98707f3c..a9433f0ad4 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -168,23 +168,25 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
 			}
 
 			/* Form predecessor map, too */
-			if (lt->nextOffset != InvalidOffsetNumber)
+			if (SGLT_GET_OFFSET(lt->nextOffset) != InvalidOffsetNumber)
 			{
 				/* paranoia about corrupted chain links */
-				if (lt->nextOffset < FirstOffsetNumber ||
-					lt->nextOffset > max ||
-					predecessor[lt->nextOffset] != InvalidOffsetNumber)
+				if (SGLT_GET_OFFSET(lt->nextOffset) < FirstOffsetNumber ||
+					SGLT_GET_OFFSET(lt->nextOffset) > max ||
+					predecessor[SGLT_GET_OFFSET(lt->nextOffset)] != InvalidOffsetNumber)
 					elog(ERROR, "inconsistent tuple chain links in page %u of index \"%s\"",
 						 BufferGetBlockNumber(buffer),
 						 RelationGetRelationName(index));
-				predecessor[lt->nextOffset] = i;
+				predecessor[SGLT_GET_OFFSET(lt->nextOffset)] = i;
 			}
 		}
 		else if (lt->tupstate == SPGIST_REDIRECT)
 		{
 			SpGistDeadTuple dt = (SpGistDeadTuple) lt;
 
-			Assert(dt->nextOffset == InvalidOffsetNumber);
+			// Dead tuple nextOffset is allowed to have highest bit 0 or 1 in case it is
+			// inherited from SpGistLeafTuple where it has its own meaning.
+			Assert(SGLT_GET_OFFSET(dt->nextOffset) == InvalidOffsetNumber);
 			Assert(ItemPointerIsValid(&dt->pointer));
 
 			/*
@@ -201,7 +203,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
 		}
 		else
 		{
-			Assert(lt->nextOffset == InvalidOffsetNumber);
+			Assert(SGLT_GET_OFFSET(lt->nextOffset) == InvalidOffsetNumber);
 		}
 	}
 
@@ -250,7 +252,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
 		prevLive = deletable[i] ? InvalidOffsetNumber : i;
 
 		/* scan down the chain ... */
-		j = head->nextOffset;
+		j = SGLT_GET_OFFSET(head->nextOffset);
 		while (j != InvalidOffsetNumber)
 		{
 			SpGistLeafTuple lt;
@@ -301,7 +303,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
 				interveningDeletable = false;
 			}
 
-			j = lt->nextOffset;
+			j = SGLT_GET_OFFSET(lt->nextOffset);
 		}
 
 		if (prevLive == InvalidOffsetNumber)
@@ -366,7 +368,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
 		lt = (SpGistLeafTuple) PageGetItem(page,
 										   PageGetItemId(page, chainSrc[i]));
 		Assert(lt->tupstate == SPGIST_LIVE);
-		lt->nextOffset = chainDest[i];
+		SGLT_SET_OFFSET(lt->nextOffset, chainDest[i]);
 	}
 
 	MarkBufferDirty(buffer);
diff --git a/src/backend/access/spgist/spgxlog.c b/src/backend/access/spgist/spgxlog.c
index 7be2291d07..4022e3af07 100644
--- a/src/backend/access/spgist/spgxlog.c
+++ b/src/backend/access/spgist/spgxlog.c
@@ -122,8 +122,8 @@ spgRedoAddLeaf(XLogReaderState *record)
 
 				head = (SpGistLeafTuple) PageGetItem(page,
 													 PageGetItemId(page, xldata->offnumHeadLeaf));
-				Assert(head->nextOffset == leafTupleHdr.nextOffset);
-				head->nextOffset = xldata->offnumLeaf;
+				Assert(SGLT_GET_OFFSET(head->nextOffset) == SGLT_GET_OFFSET(leafTupleHdr.nextOffset));
+				SGLT_SET_OFFSET(head->nextOffset, xldata->offnumLeaf);
 			}
 		}
 		else
@@ -822,7 +822,7 @@ spgRedoVacuumLeaf(XLogReaderState *record)
 			lt = (SpGistLeafTuple) PageGetItem(page,
 											   PageGetItemId(page, chainSrc[i]));
 			Assert(lt->tupstate == SPGIST_LIVE);
-			lt->nextOffset = chainDest[i];
+			SGLT_SET_OFFSET(lt->nextOffset, chainDest[i]);
 		}
 
 		PageSetLSN(page, lsn);
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e976201030..514d5e21e4 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -2,8 +2,6 @@
  *
  * elog.c
  *	  error logging and reporting
- *
- * Because of the extremely high rate at which log messages can be generated,
  * we need to be mindful of the performance cost of obtaining any information
  * that may be logged.  Also, it's important to keep in mind that this code may
  * get called from within an aborted transaction, in which case operations
@@ -244,6 +242,7 @@ errstart(int elevel, const char *domain)
 	 */
 	if (elevel >= ERROR)
 	{
+	//	abort();
 		/*
 		 * If we are inside a critical section, all errors become PANIC
 		 * errors.  See miscadmin.h.
diff --git a/src/include/access/spgist_private.h b/src/include/access/spgist_private.h
index 00b98ec6a0..c16ee8c322 100644
--- a/src/include/access/spgist_private.h
+++ b/src/include/access/spgist_private.h
@@ -141,6 +141,7 @@ typedef struct SpGistState
 	SpGistTypeDesc attLeafType; /* type of leaf-tuple values */
 	SpGistTypeDesc attPrefixType;	/* type of inner-tuple prefix values */
 	SpGistTypeDesc attLabelType;	/* type of node label values */
+	TupleDesc      includeTupdesc;	/* tuple descriptor of included columns */
 
 	char	   *deadTupleStorage;	/* workspace for spgFormDeadTuple */
 
@@ -148,6 +149,91 @@ typedef struct SpGistState
 	bool		isBuild;		/* true if doing index build */
 } SpGistState;
 
+/*
+ * SPGiST leaf tuple: carries a datum and a heap tuple TID
+ *
+ * In the simplest case, the datum is the same as the indexed value; but
+ * it could also be a suffix or some other sort of delta that permits
+ * reconstruction given knowledge of the prefix path traversed to get here.
+ *
+ * The size field is wider than could possibly be needed for an on-disk leaf
+ * tuple, but this allows us to form leaf tuples even when the datum is too
+ * wide to be stored immediately, and it costs nothing because of alignment
+ * considerations.
+ *
+ * Normally, nextOffset links to the next tuple belonging to the same parent
+ * node (which must be on the same page).  But when the root page is a leaf
+ * page, we don't chain its tuples, so nextOffset is always 0 on the root.
+ *
+ * size must be a multiple of MAXALIGN; also, it must be at least SGDTSIZE
+ * so that the tuple can be converted to REDIRECT status later.  (This
+ * restriction only adds bytes for the null-datum case, otherwise alignment
+ * restrictions force it anyway.)
+ *
+ * In a leaf tuple for a NULL indexed value, there's no useful datum value;
+ * however, the SGDTSIZE limit ensures that's there's a Datum word there
+ * anyway, so SGLTDATUM can be applied safely as long as you don't do
+ * anything with the result.
+ *
+ * As SpGistLeafTuple has header of 8 bytes so max value for nextOffset is
+ * (when page size is 65KB) is 8192 and 15 bit is sufficient to store it. So
+ * higher bit is reserved to store information is there nulls mask between leaf
+ * datum and first include value (if any). Size of null mask is 1 byte per each 8
+ * include columns.
+ */
+
+typedef struct SpGistLeafTupleData
+{
+	unsigned int tupstate:2,	/* LIVE/REDIRECT/DEAD/PLACEHOLDER */
+				size:30;		/* large enough for any palloc'able value */
+	OffsetNumber nextOffset;	/* higher 1 bit = 1 if included values has nulls
+										  2 bit = 1 if included values contain variable length values
+								   lower 15 bits - nextOffset - points to the next tuple in chain,
+								   or InvalidOffsetNumber. They SHOULD NOT be set/read directly,
+								   SGLT_SET_OFFSET/SGLT_GET_OFFSET macro must be used instead. */
+	ItemPointerData heapPtr;	/* TID of represented heap tuple */
+	/* leaf datum follows */
+	/* if SGLT_GET_CONTAINSNULLMASK nullmask follows. Its size (number of included columns/8)+1 */
+	/* include attributes follow if any*/
+} SpGistLeafTupleData;
+
+typedef SpGistLeafTupleData *SpGistLeafTuple;
+
+#define SGLTHDRSZ			MAXALIGN(sizeof(SpGistLeafTupleData))
+#define SGLTDATAPTR(x)		(((char *) (x)) + SGLTHDRSZ)
+#define SGLTDATUM(x, s)		((s)->attLeafType.attbyval ? \
+							*(Datum *) SGLTDATAPTR(x) : \
+							PointerGetDatum(SGLTDATAPTR(x)))
+/*
+ * Accessor macros for nextOffset and null mask presence bit.
+ * It's a bit of hack that these macros also safely apply to IncludeTupMetadata which has the same
+ * structure. Include tuple size of maximum 13 bits (see INDEX_SIZE_MASK) is stored there instead
+ * of NextOffset which is 14 bits. IncludeTupMetadata is a vehicle to transfer included tuple header
+ * as IncludeTuple is now filled before SpGistLeafTuple initialized.
+ */
+#define SGLT_GET_OFFSET(x) 	( (x) & 0x3FFF )
+#define SGLT_GET_CONTAINSNULLMASK(x) ( (x) >> 15 )
+#define SGLT_GET_CONTAINSVARATT(x) ( ( (x) & 4000 ) >> 14 )
+#define SGLT_SET_OFFSET(x,o) ( (x) = ( (x) & 0xC000 ) | ( (o) & 0x3FFF) )
+#define SGLT_SET_CONTAINSNULLMASK(x,n) ( (x) = ( (n) << 15 ) | ( (x) & 0x3FFF ) )
+#define SGLT_SET_CONTAINSVARATT(x,v) ( (x) = ( (v) << 14 ) | ( (x) & 0xBFFF ) )
+
+#define SGLT_GET_INCLUDE_TUPSIZE(x) SGLT_GET_OFFSET(x)
+#define SGLT_SET_INCLUDE_TUPSIZE(x,o) SGLT_SET_OFFSET(x,o)
+
+extern char *SpGistFormIncludeTuple(TupleDesc tupleDescriptor, Datum *values,
+									bool *isnull, uint16 *tupdata);
+/*
+ * SPGiST dead tuple: declaration for examining non-live tuples
+ *
+ * The tupstate field of this struct must match those of regular inner and
+ * leaf tuples, and its size field must match a leaf tuple's.
+ * Also, the pointer field must be in the same place as a leaf tuple's heapPtr
+ * field, to satisfy some Asserts that we make when replacing a leaf tuple
+ * with a dead tuple.
+ * We don't use nextOffset, but it's needed to align the pointer field.
+ */
+
 typedef struct SpGistSearchItem
 {
 	pairingheap_node phNode;	/* pairing heap node */
@@ -160,14 +246,14 @@ typedef struct SpGistSearchItem
 	bool		isLeaf;			/* SearchItem is heap item */
 	bool		recheck;		/* qual recheck is needed */
 	bool		recheckDistances;	/* distance recheck is needed */
-
+	SpGistLeafTuple leafTuple;
 	/* array with numberOfOrderBys entries */
 	double		distances[FLEXIBLE_ARRAY_MEMBER];
+	/* if there are include columns SpGistLeafTupleData follow */
 } SpGistSearchItem;
 
 #define SizeOfSpGistSearchItem(n_distances) \
 	(offsetof(SpGistSearchItem, distances) + sizeof(double) * (n_distances))
-
 /*
  * Private state of an index scan
  */
@@ -241,6 +327,7 @@ typedef struct SpGistCache
 	SpGistTypeDesc attLeafType; /* type of leaf-tuple values */
 	SpGistTypeDesc attPrefixType;	/* type of inner-tuple prefix values */
 	SpGistTypeDesc attLabelType;	/* type of node label values */
+	TupleDesc      includeTupdesc;
 
 	SpGistLUPCache lastUsedPages;	/* local storage of last-used info */
 } SpGistCache;
@@ -321,60 +408,6 @@ typedef SpGistNodeTupleData *SpGistNodeTuple;
 							 *(Datum *) SGNTDATAPTR(x) : \
 							 PointerGetDatum(SGNTDATAPTR(x)))
 
-/*
- * SPGiST leaf tuple: carries a datum and a heap tuple TID
- *
- * In the simplest case, the datum is the same as the indexed value; but
- * it could also be a suffix or some other sort of delta that permits
- * reconstruction given knowledge of the prefix path traversed to get here.
- *
- * The size field is wider than could possibly be needed for an on-disk leaf
- * tuple, but this allows us to form leaf tuples even when the datum is too
- * wide to be stored immediately, and it costs nothing because of alignment
- * considerations.
- *
- * Normally, nextOffset links to the next tuple belonging to the same parent
- * node (which must be on the same page).  But when the root page is a leaf
- * page, we don't chain its tuples, so nextOffset is always 0 on the root.
- *
- * size must be a multiple of MAXALIGN; also, it must be at least SGDTSIZE
- * so that the tuple can be converted to REDIRECT status later.  (This
- * restriction only adds bytes for the null-datum case, otherwise alignment
- * restrictions force it anyway.)
- *
- * In a leaf tuple for a NULL indexed value, there's no useful datum value;
- * however, the SGDTSIZE limit ensures that's there's a Datum word there
- * anyway, so SGLTDATUM can be applied safely as long as you don't do
- * anything with the result.
- */
-typedef struct SpGistLeafTupleData
-{
-	unsigned int tupstate:2,	/* LIVE/REDIRECT/DEAD/PLACEHOLDER */
-				size:30;		/* large enough for any palloc'able value */
-	OffsetNumber nextOffset;	/* next tuple in chain, or InvalidOffsetNumber */
-	ItemPointerData heapPtr;	/* TID of represented heap tuple */
-	/* leaf datum follows */
-} SpGistLeafTupleData;
-
-typedef SpGistLeafTupleData *SpGistLeafTuple;
-
-#define SGLTHDRSZ			MAXALIGN(sizeof(SpGistLeafTupleData))
-#define SGLTDATAPTR(x)		(((char *) (x)) + SGLTHDRSZ)
-#define SGLTDATUM(x, s)		((s)->attLeafType.attbyval ? \
-							 *(Datum *) SGLTDATAPTR(x) : \
-							 PointerGetDatum(SGLTDATAPTR(x)))
-
-/*
- * SPGiST dead tuple: declaration for examining non-live tuples
- *
- * The tupstate field of this struct must match those of regular inner and
- * leaf tuples, and its size field must match a leaf tuple's.
- * Also, the pointer field must be in the same place as a leaf tuple's heapPtr
- * field, to satisfy some Asserts that we make when replacing a leaf tuple
- * with a dead tuple.
- * We don't use nextOffset, but it's needed to align the pointer field.
- * pointer and xid are only valid when tupstate = REDIRECT.
- */
 typedef struct SpGistDeadTupleData
 {
 	unsigned int tupstate:2,	/* LIVE/REDIRECT/DEAD/PLACEHOLDER */
@@ -394,7 +427,6 @@ typedef SpGistDeadTupleData *SpGistDeadTuple;
  * size plus sizeof(ItemIdData) (for the line pointer).  This works correctly
  * so long as tuple sizes are always maxaligned.
  */
-
 /* Page capacity after allowing for fixed header and special space */
 #define SPGIST_PAGE_CAPACITY  \
 	MAXALIGN_DOWN(BLCKSZ - \
@@ -456,9 +488,10 @@ extern void SpGistInitPage(Page page, uint16 f);
 extern void SpGistInitBuffer(Buffer b, uint16 f);
 extern void SpGistInitMetapage(Page page);
 extern unsigned int SpGistGetTypeSize(SpGistTypeDesc *att, Datum datum);
+extern unsigned int SpgLeafSize(SpGistState *state, Datum *datum, bool *isnull);
 extern SpGistLeafTuple spgFormLeafTuple(SpGistState *state,
 										ItemPointer heapPtr,
-										Datum datum, bool isnull);
+										Datum *datum, bool *isnull);
 extern SpGistNodeTuple spgFormNodeTuple(SpGistState *state,
 										Datum label, bool isnull);
 extern SpGistInnerTuple spgFormInnerTuple(SpGistState *state,
@@ -466,6 +499,8 @@ extern SpGistInnerTuple spgFormInnerTuple(SpGistState *state,
 										  int nNodes, SpGistNodeTuple *nodes);
 extern SpGistDeadTuple spgFormDeadTuple(SpGistState *state, int tupstate,
 										BlockNumber blkno, OffsetNumber offnum);
+extern void SpGistDeformLeafTuple(SpGistLeafTuple tup, SpGistState *state,
+								  Datum *datum, bool *isnull, bool key_value_isnull);
 extern Datum *spgExtractNodeLabels(SpGistState *state,
 								   SpGistInnerTuple innerTuple);
 extern OffsetNumber SpGistPageAddNewItem(SpGistState *state, Page page,
@@ -484,7 +519,7 @@ extern void spgPageIndexMultiDelete(SpGistState *state, Page page,
 									int firststate, int reststate,
 									BlockNumber blkno, OffsetNumber offnum);
 extern bool spgdoinsert(Relation index, SpGistState *state,
-						ItemPointer heapPtr, Datum datum, bool isnull);
+						ItemPointer heapPtr, Datum *datum, bool *isnull);
 
 /* spgproc.c */
 extern double *spg_key_orderbys_distances(Datum key, bool isLeaf,
diff --git a/src/test/regress/expected/amutils.out b/src/test/regress/expected/amutils.out
index d92a6d12c6..93e6a43b6d 100644
--- a/src/test/regress/expected/amutils.out
+++ b/src/test/regress/expected/amutils.out
@@ -169,9 +169,9 @@ select amname, prop, pg_indexam_has_property(a.oid, prop) as p
  hash   | bogus         | 
  spgist | can_order     | f
  spgist | can_unique    | f
- spgist | can_multi_col | f
+ spgist | can_multi_col | t
  spgist | can_exclude   | t
- spgist | can_include   | f
+ spgist | can_include   | t
  spgist | bogus         | 
 (36 rows)
 
diff --git a/src/test/regress/expected/index_including.out b/src/test/regress/expected/index_including.out
index 8e5d53e712..4fd2b7e878 100644
--- a/src/test/regress/expected/index_including.out
+++ b/src/test/regress/expected/index_including.out
@@ -356,7 +356,6 @@ CREATE INDEX on tbl USING brin(c1, c2) INCLUDE (c3, c4);
 ERROR:  access method "brin" does not support included columns
 CREATE INDEX on tbl USING gist(c3) INCLUDE (c1, c4);
 CREATE INDEX on tbl USING spgist(c3) INCLUDE (c4);
-ERROR:  access method "spgist" does not support included columns
 CREATE INDEX on tbl USING gin(c1, c2) INCLUDE (c3, c4);
 ERROR:  access method "gin" does not support included columns
 CREATE INDEX on tbl USING hash(c1, c2) INCLUDE (c3, c4);
diff --git a/src/test/regress/expected/index_including_spgist.out b/src/test/regress/expected/index_including_spgist.out
new file mode 100644
index 0000000000..fa64766fb7
--- /dev/null
+++ b/src/test/regress/expected/index_including_spgist.out
@@ -0,0 +1,139 @@
+/*
+ * 1.1. test CREATE INDEX with buffered build
+ */
+-- Regular index with included columns
+CREATE TABLE tbl_spgist (c1 int, c2 int, c3 int, c4 box);
+-- size is chosen to exceed page size and trigger actual truncation
+INSERT INTO tbl_spgist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,8000) AS x;
+CREATE INDEX tbl_spgist_idx ON tbl_spgist using spgist (c4) INCLUDE (c1,c2,c3);
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl_spgist'::regclass ORDER BY c.relname;
+                                     pg_get_indexdef                                     
+-----------------------------------------------------------------------------------------
+ CREATE INDEX tbl_spgist_idx ON public.tbl_spgist USING spgist (c4) INCLUDE (c1, c2, c3)
+(1 row)
+
+SELECT * FROM tbl_spgist where c4 <@ box(point(1,1),point(10,10));
+ c1 | c2 | c3 |     c4      
+----+----+----+-------------
+  1 |  2 |  3 | (2,3),(1,2)
+  2 |  4 |  6 | (4,5),(2,3)
+  3 |  6 |  9 | (6,7),(3,4)
+  4 |  8 | 12 | (8,9),(4,5)
+(4 rows)
+
+SET enable_bitmapscan TO off;
+EXPLAIN  (costs off) SELECT * FROM tbl_spgist where c4 <@ box(point(1,1),point(10,10));
+                     QUERY PLAN                     
+----------------------------------------------------
+ Index Only Scan using tbl_spgist_idx on tbl_spgist
+   Index Cond: (c4 <@ '(10,10),(1,1)'::box)
+(2 rows)
+
+SET enable_bitmapscan TO default;
+DROP TABLE tbl_spgist;
+/*
+ * 1.2. test CREATE INDEX with inserts
+ */
+-- Regular index with included columns
+CREATE TABLE tbl_spgist (c1 int, c2 int, c3 int, c4 box);
+-- size is chosen to exceed page size and trigger actual truncation
+CREATE INDEX tbl_spgist_idx ON tbl_spgist using spgist (c4) INCLUDE (c1,c2,c3);
+INSERT INTO tbl_spgist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,8000) AS x;
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl_spgist'::regclass ORDER BY c.relname;
+                                     pg_get_indexdef                                     
+-----------------------------------------------------------------------------------------
+ CREATE INDEX tbl_spgist_idx ON public.tbl_spgist USING spgist (c4) INCLUDE (c1, c2, c3)
+(1 row)
+
+SELECT * FROM tbl_spgist where c4 <@ box(point(1,1),point(10,10));
+ c1 | c2 | c3 |     c4      
+----+----+----+-------------
+  1 |  2 |  3 | (2,3),(1,2)
+  2 |  4 |  6 | (4,5),(2,3)
+  3 |  6 |  9 | (6,7),(3,4)
+  4 |  8 | 12 | (8,9),(4,5)
+(4 rows)
+
+SET enable_bitmapscan TO off;
+EXPLAIN  (costs off) SELECT * FROM tbl_spgist where c4 <@ box(point(1,1),point(10,10));
+                     QUERY PLAN                     
+----------------------------------------------------
+ Index Only Scan using tbl_spgist_idx on tbl_spgist
+   Index Cond: (c4 <@ '(10,10),(1,1)'::box)
+(2 rows)
+
+SET enable_bitmapscan TO default;
+DROP TABLE tbl_spgist;
+/*
+ * 2. CREATE INDEX CONCURRENTLY
+ */
+CREATE TABLE tbl_spgist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_spgist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX CONCURRENTLY tbl_spgist_idx ON tbl_spgist using spgist (c4) INCLUDE (c1,c2,c3);
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_spgist' ORDER BY indexname;
+                                        indexdef                                         
+-----------------------------------------------------------------------------------------
+ CREATE INDEX tbl_spgist_idx ON public.tbl_spgist USING spgist (c4) INCLUDE (c1, c2, c3)
+(1 row)
+
+DROP TABLE tbl_spgist;
+/*
+ * 3. REINDEX
+ */
+CREATE TABLE tbl_spgist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_spgist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_spgist_idx ON tbl_spgist using spgist (c4) INCLUDE (c1,c3);
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_spgist' ORDER BY indexname;
+                                      indexdef                                       
+-------------------------------------------------------------------------------------
+ CREATE INDEX tbl_spgist_idx ON public.tbl_spgist USING spgist (c4) INCLUDE (c1, c3)
+(1 row)
+
+REINDEX INDEX tbl_spgist_idx;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_spgist' ORDER BY indexname;
+                                      indexdef                                       
+-------------------------------------------------------------------------------------
+ CREATE INDEX tbl_spgist_idx ON public.tbl_spgist USING spgist (c4) INCLUDE (c1, c3)
+(1 row)
+
+ALTER TABLE tbl_spgist DROP COLUMN c1;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_spgist' ORDER BY indexname;
+ indexdef 
+----------
+(0 rows)
+
+DROP TABLE tbl_spgist;
+/*
+ * 4. Update, delete values in indexed table.
+ */
+CREATE TABLE tbl_spgist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_spgist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_spgist_idx ON tbl_spgist using spgist (c4) INCLUDE (c1,c3);
+UPDATE tbl_spgist SET c1 = 100 WHERE c1 = 2;
+UPDATE tbl_spgist SET c1 = 1 WHERE c1 = 3;
+DELETE FROM tbl_spgist WHERE c1 = 5 OR c3 = 12;
+DROP TABLE tbl_spgist;
+/*
+ * 5. Alter column type.
+ */
+CREATE TABLE tbl_spgist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_spgist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_spgist_idx ON tbl_spgist using spgist (c4) INCLUDE (c1,c3);
+ALTER TABLE tbl_spgist ALTER c1 TYPE bigint;
+ALTER TABLE tbl_spgist ALTER c3 TYPE bigint;
+\d tbl_spgist
+             Table "public.tbl_spgist"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ c1     | bigint  |           |          | 
+ c2     | integer |           |          | 
+ c3     | bigint  |           |          | 
+ c4     | box     |           |          | 
+Indexes:
+    "tbl_spgist_idx" spgist (c4) INCLUDE (c1, c3)
+
+DROP TABLE tbl_spgist;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 026ea880cd..985458a1a8 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -50,7 +50,7 @@ test: copy copyselect copydml insert insert_conflict
 # ----------
 test: create_misc create_operator create_procedure
 # These depend on create_misc and create_operator
-test: create_index create_index_spgist create_view index_including index_including_gist
+test: create_index create_index_spgist create_view index_including index_including_gist index_including_spgist
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 979d926119..f3df961535 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -68,6 +68,7 @@ test: create_index_spgist
 test: create_view
 test: index_including
 test: index_including_gist
+test: index_including_spgist
 test: create_aggregate
 test: create_function_3
 test: create_cast
diff --git a/src/test/regress/sql/index_including_spgist.sql b/src/test/regress/sql/index_including_spgist.sql
new file mode 100644
index 0000000000..a59e73aa22
--- /dev/null
+++ b/src/test/regress/sql/index_including_spgist.sql
@@ -0,0 +1,81 @@
+/*
+ * 1.1. test CREATE INDEX with buffered build
+ */
+
+-- Regular index with included columns
+CREATE TABLE tbl_spgist (c1 int, c2 int, c3 int, c4 box);
+-- size is chosen to exceed page size and trigger actual truncation
+INSERT INTO tbl_spgist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,8000) AS x;
+CREATE INDEX tbl_spgist_idx ON tbl_spgist using spgist (c4) INCLUDE (c1,c2,c3);
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl_spgist'::regclass ORDER BY c.relname;
+SELECT * FROM tbl_spgist where c4 <@ box(point(1,1),point(10,10));
+SET enable_bitmapscan TO off;
+EXPLAIN  (costs off) SELECT * FROM tbl_spgist where c4 <@ box(point(1,1),point(10,10));
+SET enable_bitmapscan TO default;
+DROP TABLE tbl_spgist;
+
+/*
+ * 1.2. test CREATE INDEX with inserts
+ */
+
+-- Regular index with included columns
+CREATE TABLE tbl_spgist (c1 int, c2 int, c3 int, c4 box);
+-- size is chosen to exceed page size and trigger actual truncation
+CREATE INDEX tbl_spgist_idx ON tbl_spgist using spgist (c4) INCLUDE (c1,c2,c3);
+INSERT INTO tbl_spgist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,8000) AS x;
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl_spgist'::regclass ORDER BY c.relname;
+SELECT * FROM tbl_spgist where c4 <@ box(point(1,1),point(10,10));
+SET enable_bitmapscan TO off;
+EXPLAIN  (costs off) SELECT * FROM tbl_spgist where c4 <@ box(point(1,1),point(10,10));
+SET enable_bitmapscan TO default;
+DROP TABLE tbl_spgist;
+
+/*
+ * 2. CREATE INDEX CONCURRENTLY
+ */
+CREATE TABLE tbl_spgist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_spgist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX CONCURRENTLY tbl_spgist_idx ON tbl_spgist using spgist (c4) INCLUDE (c1,c2,c3);
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_spgist' ORDER BY indexname;
+DROP TABLE tbl_spgist;
+
+
+/*
+ * 3. REINDEX
+ */
+CREATE TABLE tbl_spgist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_spgist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_spgist_idx ON tbl_spgist using spgist (c4) INCLUDE (c1,c3);
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_spgist' ORDER BY indexname;
+REINDEX INDEX tbl_spgist_idx;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_spgist' ORDER BY indexname;
+ALTER TABLE tbl_spgist DROP COLUMN c1;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_spgist' ORDER BY indexname;
+DROP TABLE tbl_spgist;
+
+/*
+ * 4. Update, delete values in indexed table.
+ */
+CREATE TABLE tbl_spgist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_spgist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_spgist_idx ON tbl_spgist using spgist (c4) INCLUDE (c1,c3);
+UPDATE tbl_spgist SET c1 = 100 WHERE c1 = 2;
+UPDATE tbl_spgist SET c1 = 1 WHERE c1 = 3;
+DELETE FROM tbl_spgist WHERE c1 = 5 OR c3 = 12;
+DROP TABLE tbl_spgist;
+
+/*
+ * 5. Alter column type.
+ */
+CREATE TABLE tbl_spgist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_spgist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_spgist_idx ON tbl_spgist using spgist (c4) INCLUDE (c1,c3);
+ALTER TABLE tbl_spgist ALTER c1 TYPE bigint;
+ALTER TABLE tbl_spgist ALTER c3 TYPE bigint;
+\d tbl_spgist
+DROP TABLE tbl_spgist;
+
