aboutsummaryrefslogtreecommitdiff
path: root/src/backend/access/gist/gistbuild.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/access/gist/gistbuild.c')
-rw-r--r--src/backend/access/gist/gistbuild.c500
1 files changed, 302 insertions, 198 deletions
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 73cdc5f5b47..712e59ac908 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -55,16 +55,24 @@ typedef struct
{
Relation indexrel;
GISTSTATE *giststate;
- GISTBuildBuffers *gfbb;
int64 indtuples; /* number of tuples indexed */
int64 indtuplesSize; /* total size of all indexed tuples */
Size freespace; /* amount of free space to leave on pages */
+ /*
+ * Extra data structures used during a buffering build. 'gfbb' contains
+ * information related to managing the build buffers. 'parentMap' is a
+ * lookup table of the parent of each internal page.
+ */
+ GISTBuildBuffers *gfbb;
+ HTAB *parentMap;
+
GistBufferingMode bufferingMode;
} GISTBuildState;
+/* prototypes for private functions */
static void gistInitBuffering(GISTBuildState *buildstate);
static int calculatePagesPerBuffer(GISTBuildState *buildstate, int levelStep);
static void gistBuildCallback(Relation index,
@@ -76,18 +84,24 @@ static void gistBuildCallback(Relation index,
static void gistBufferingBuildInsert(GISTBuildState *buildstate,
IndexTuple itup);
static bool gistProcessItup(GISTBuildState *buildstate, IndexTuple itup,
- GISTBufferingInsertStack *startparent);
+ BlockNumber startblkno, int startlevel);
static void gistbufferinginserttuples(GISTBuildState *buildstate,
- Buffer buffer,
+ Buffer buffer, int level,
IndexTuple *itup, int ntup, OffsetNumber oldoffnum,
- GISTBufferingInsertStack *path);
-static void gistBufferingFindCorrectParent(GISTBuildState *buildstate,
- GISTBufferingInsertStack *child);
+ BlockNumber parentblk, OffsetNumber downlinkoffnum);
+static Buffer gistBufferingFindCorrectParent(GISTBuildState *buildstate,
+ BlockNumber childblkno, int level,
+ BlockNumber *parentblk,
+ OffsetNumber *downlinkoffnum);
static void gistProcessEmptyingQueue(GISTBuildState *buildstate);
static void gistEmptyAllBuffers(GISTBuildState *buildstate);
-static void gistFreeUnreferencedPath(GISTBufferingInsertStack *path);
static int gistGetMaxLevel(Relation index);
+static void gistInitParentMap(GISTBuildState *buildstate);
+static void gistMemorizeParent(GISTBuildState *buildstate, BlockNumber child,
+ BlockNumber parent);
+static void gistMemorizeAllDownlinks(GISTBuildState *buildstate, Buffer parent);
+static BlockNumber gistGetParent(GISTBuildState *buildstate, BlockNumber child);
/*
* Main entry point to GiST index build. Initially calls insert over and over,
@@ -407,6 +421,8 @@ gistInitBuffering(GISTBuildState *buildstate)
buildstate->gfbb = gistInitBuildBuffers(pagesPerBuffer, levelStep,
gistGetMaxLevel(index));
+ gistInitParentMap(buildstate);
+
buildstate->bufferingMode = GIST_BUFFERING_ACTIVE;
elog(DEBUG1, "switched to buffered GiST build; level step = %d, pagesPerBuffer = %d",
@@ -529,7 +545,7 @@ static void
gistBufferingBuildInsert(GISTBuildState *buildstate, IndexTuple itup)
{
/* Insert the tuple to buffers. */
- gistProcessItup(buildstate, itup, NULL);
+ gistProcessItup(buildstate, itup, 0, buildstate->gfbb->rootlevel);
/* If we filled up (half of a) buffer, process buffer emptying. */
gistProcessEmptyingQueue(buildstate);
@@ -543,30 +559,28 @@ gistBufferingBuildInsert(GISTBuildState *buildstate, IndexTuple itup)
*/
static bool
gistProcessItup(GISTBuildState *buildstate, IndexTuple itup,
- GISTBufferingInsertStack *startparent)
+ BlockNumber startblkno, int startlevel)
{
GISTSTATE *giststate = buildstate->giststate;
GISTBuildBuffers *gfbb = buildstate->gfbb;
Relation indexrel = buildstate->indexrel;
- GISTBufferingInsertStack *path;
BlockNumber childblkno;
Buffer buffer;
bool result = false;
+ BlockNumber blkno;
+ int level;
+ OffsetNumber downlinkoffnum = InvalidOffsetNumber;
+ BlockNumber parentblkno = InvalidBlockNumber;
- /*
- * NULL passed in startparent means that we start index tuple processing
- * from the root.
- */
- if (!startparent)
- path = gfbb->rootitem;
- else
- path = startparent;
+ CHECK_FOR_INTERRUPTS();
/*
* Loop until we reach a leaf page (level == 0) or a level with buffers
* (not including the level we start at, because we would otherwise make
* no progress).
*/
+ blkno = startblkno;
+ level = startlevel;
for (;;)
{
ItemId iid;
@@ -574,21 +588,21 @@ gistProcessItup(GISTBuildState *buildstate, IndexTuple itup,
newtup;
Page page;
OffsetNumber childoffnum;
- GISTBufferingInsertStack *parent;
/* Have we reached a level with buffers? */
- if (LEVEL_HAS_BUFFERS(path->level, gfbb) && path != startparent)
+ if (LEVEL_HAS_BUFFERS(level, gfbb) && level != startlevel)
break;
/* Have we reached a leaf page? */
- if (path->level == 0)
+ if (level == 0)
break;
/*
* Nope. Descend down to the next level then. Choose a child to
* descend down to.
*/
- buffer = ReadBuffer(indexrel, path->blkno);
+
+ buffer = ReadBuffer(indexrel, blkno);
LockBuffer(buffer, GIST_EXCLUSIVE);
page = (Page) BufferGetPage(buffer);
@@ -597,32 +611,33 @@ gistProcessItup(GISTBuildState *buildstate, IndexTuple itup,
idxtuple = (IndexTuple) PageGetItem(page, iid);
childblkno = ItemPointerGetBlockNumber(&(idxtuple->t_tid));
+ if (level > 1)
+ gistMemorizeParent(buildstate, childblkno, blkno);
+
/*
* Check that the key representing the target child node is consistent
* with the key we're inserting. Update it if it's not.
*/
newtup = gistgetadjusted(indexrel, idxtuple, itup, giststate);
if (newtup)
- gistbufferinginserttuples(buildstate, buffer, &newtup, 1,
- childoffnum, path);
- UnlockReleaseBuffer(buffer);
+ {
+ gistbufferinginserttuples(buildstate, buffer, level,
+ &newtup, 1, childoffnum,
+ InvalidBlockNumber, InvalidOffsetNumber);
+ /* gistbufferinginserttuples() released the buffer */
+ }
+ else
+ UnlockReleaseBuffer(buffer);
- /* Create new path item representing current page */
- parent = path;
- path = (GISTBufferingInsertStack *) MemoryContextAlloc(gfbb->context,
- sizeof(GISTBufferingInsertStack));
- path->parent = parent;
- path->level = parent->level - 1;
- path->blkno = childblkno;
- path->downlinkoffnum = childoffnum;
- path->refCount = 0; /* it's unreferenced for now */
-
- /* Adjust reference count of parent */
- if (parent)
- parent->refCount++;
+ /* Descend to the child */
+ parentblkno = blkno;
+ blkno = childblkno;
+ downlinkoffnum = childoffnum;
+ Assert(level > 0);
+ level--;
}
- if (LEVEL_HAS_BUFFERS(path->level, gfbb))
+ if (LEVEL_HAS_BUFFERS(level, gfbb))
{
/*
* We've reached level with buffers. Place the index tuple to the
@@ -631,8 +646,7 @@ gistProcessItup(GISTBuildState *buildstate, IndexTuple itup,
GISTNodeBuffer *childNodeBuffer;
/* Find the buffer or create a new one */
- childNodeBuffer = gistGetNodeBuffer(gfbb, giststate, path->blkno,
- path->downlinkoffnum, path->parent);
+ childNodeBuffer = gistGetNodeBuffer(gfbb, giststate, blkno, level);
/* Add index tuple to it */
gistPushItupToNodeBuffer(gfbb, childNodeBuffer, itup);
@@ -645,19 +659,15 @@ gistProcessItup(GISTBuildState *buildstate, IndexTuple itup,
/*
* We've reached a leaf page. Place the tuple here.
*/
- buffer = ReadBuffer(indexrel, path->blkno);
+ Assert(level == 0);
+ buffer = ReadBuffer(indexrel, blkno);
LockBuffer(buffer, GIST_EXCLUSIVE);
- gistbufferinginserttuples(buildstate, buffer, &itup, 1,
- InvalidOffsetNumber, path);
- UnlockReleaseBuffer(buffer);
+ gistbufferinginserttuples(buildstate, buffer, level,
+ &itup, 1, InvalidOffsetNumber,
+ parentblkno, downlinkoffnum);
+ /* gistbufferinginserttuples() released the buffer */
}
- /*
- * Free unreferenced path items, if any. Path item may be referenced by
- * node buffer.
- */
- gistFreeUnreferencedPath(path);
-
return result;
}
@@ -665,11 +675,14 @@ gistProcessItup(GISTBuildState *buildstate, IndexTuple itup,
* Insert tuples to a given page.
*
* This is analogous with gistinserttuples() in the regular insertion code.
+ *
+ * Caller should hold a lock on 'buffer' on entry. This function will unlock
+ * and unpin it.
*/
static void
-gistbufferinginserttuples(GISTBuildState *buildstate, Buffer buffer,
+gistbufferinginserttuples(GISTBuildState *buildstate, Buffer buffer, int level,
IndexTuple *itup, int ntup, OffsetNumber oldoffnum,
- GISTBufferingInsertStack *path)
+ BlockNumber parentblk, OffsetNumber downlinkoffnum)
{
GISTBuildBuffers *gfbb = buildstate->gfbb;
List *splitinfo;
@@ -692,33 +705,41 @@ gistbufferinginserttuples(GISTBuildState *buildstate, Buffer buffer,
*/
if (is_split && BufferGetBlockNumber(buffer) == GIST_ROOT_BLKNO)
{
- GISTBufferingInsertStack *oldroot = gfbb->rootitem;
Page page = BufferGetPage(buffer);
- ItemId iid;
- IndexTuple idxtuple;
- BlockNumber leftmostchild;
+ OffsetNumber off;
+ OffsetNumber maxoff;
- gfbb->rootitem = (GISTBufferingInsertStack *) MemoryContextAlloc(
- gfbb->context, sizeof(GISTBufferingInsertStack));
- gfbb->rootitem->parent = NULL;
- gfbb->rootitem->blkno = GIST_ROOT_BLKNO;
- gfbb->rootitem->downlinkoffnum = InvalidOffsetNumber;
- gfbb->rootitem->level = oldroot->level + 1;
- gfbb->rootitem->refCount = 1;
+ Assert(level == gfbb->rootlevel);
+ gfbb->rootlevel++;
+
+ elog(DEBUG2, "splitting GiST root page, now %d levels deep", gfbb->rootlevel);
/*
* All the downlinks on the old root page are now on one of the child
- * pages. Change the block number of the old root entry in the stack
- * to point to the leftmost child. The other child pages will be
- * accessible from there by walking right.
+ * pages. Visit all the new child pages to memorize the parents of
+ * the grandchildren.
*/
- iid = PageGetItemId(page, FirstOffsetNumber);
- idxtuple = (IndexTuple) PageGetItem(page, iid);
- leftmostchild = ItemPointerGetBlockNumber(&(idxtuple->t_tid));
+ if (gfbb->rootlevel > 1)
+ {
+ maxoff = PageGetMaxOffsetNumber(page);
+ for (off = FirstOffsetNumber; off <= maxoff; off++)
+ {
+ ItemId iid = PageGetItemId(page, off);
+ IndexTuple idxtuple = (IndexTuple) PageGetItem(page, iid);
+ BlockNumber childblkno = ItemPointerGetBlockNumber(&(idxtuple->t_tid));
+ Buffer childbuf = ReadBuffer(buildstate->indexrel, childblkno);
- oldroot->parent = gfbb->rootitem;
- oldroot->blkno = leftmostchild;
- oldroot->downlinkoffnum = InvalidOffsetNumber;
+ LockBuffer(childbuf, GIST_SHARE);
+ gistMemorizeAllDownlinks(buildstate, childbuf);
+ UnlockReleaseBuffer(childbuf);
+
+ /*
+ * Also remember that the parent of the new child page is
+ * the root block.
+ */
+ gistMemorizeParent(buildstate, childblkno, GIST_ROOT_BLKNO);
+ }
+ }
}
if (splitinfo)
@@ -726,7 +747,8 @@ gistbufferinginserttuples(GISTBuildState *buildstate, Buffer buffer,
/*
* Insert the downlinks to the parent. This is analogous with
* gistfinishsplit() in the regular insertion code, but the locking is
- * simpler, and we have to maintain the buffers.
+ * simpler, and we have to maintain the buffers on internal nodes and
+ * the parent map.
*/
IndexTuple *downlinks;
int ndownlinks,
@@ -735,7 +757,12 @@ gistbufferinginserttuples(GISTBuildState *buildstate, Buffer buffer,
ListCell *lc;
/* Parent may have changed since we memorized this path. */
- gistBufferingFindCorrectParent(buildstate, path);
+ parentBuffer =
+ gistBufferingFindCorrectParent(buildstate,
+ BufferGetBlockNumber(buffer),
+ level,
+ &parentblk,
+ &downlinkoffnum);
/*
* If there's a buffer associated with this page, that needs to be
@@ -747,7 +774,8 @@ gistbufferinginserttuples(GISTBuildState *buildstate, Buffer buffer,
gistRelocateBuildBuffersOnSplit(gfbb,
buildstate->giststate,
buildstate->indexrel,
- path, buffer, splitinfo);
+ level,
+ buffer, splitinfo);
/* Create an array of all the downlink tuples */
ndownlinks = list_length(splitinfo);
@@ -758,124 +786,129 @@ gistbufferinginserttuples(GISTBuildState *buildstate, Buffer buffer,
GISTPageSplitInfo *splitinfo = lfirst(lc);
/*
+ * Remember the parent of each new child page in our parent map.
+ * This assumes that the downlinks fit on the parent page. If the
+ * parent page is split, too, when we recurse up to insert the
+ * downlinks, the recursive gistbufferinginserttuples() call
+ * will update the map again.
+ */
+ if (level > 0)
+ gistMemorizeParent(buildstate,
+ BufferGetBlockNumber(splitinfo->buf),
+ BufferGetBlockNumber(parentBuffer));
+
+ /*
+ * Also update the parent map for all the downlinks that got moved
+ * to a different page. (actually this also loops through the
+ * downlinks that stayed on the original page, but it does no
+ * harm).
+ */
+ if (level > 1)
+ gistMemorizeAllDownlinks(buildstate, splitinfo->buf);
+
+ /*
* Since there's no concurrent access, we can release the lower
- * level buffers immediately. Don't release the buffer for the
- * original page, though, because the caller will release that.
+ * level buffers immediately. This includes the original page.
*/
- if (splitinfo->buf != buffer)
- UnlockReleaseBuffer(splitinfo->buf);
+ UnlockReleaseBuffer(splitinfo->buf);
downlinks[i++] = splitinfo->downlink;
}
/* Insert them into parent. */
- parentBuffer = ReadBuffer(buildstate->indexrel, path->parent->blkno);
- LockBuffer(parentBuffer, GIST_EXCLUSIVE);
- gistbufferinginserttuples(buildstate, parentBuffer,
- downlinks, ndownlinks,
- path->downlinkoffnum, path->parent);
- UnlockReleaseBuffer(parentBuffer);
+ gistbufferinginserttuples(buildstate, parentBuffer, level + 1,
+ downlinks, ndownlinks, downlinkoffnum,
+ InvalidBlockNumber, InvalidOffsetNumber);
list_free_deep(splitinfo); /* we don't need this anymore */
}
+ else
+ UnlockReleaseBuffer(buffer);
}
/*
- * Find correct parent by following rightlinks in buffering index build. This
- * method of parent searching is possible because no concurrent activity is
- * possible while index builds.
+ * Find the downlink pointing to a child page.
+ *
+ * 'childblkno' indicates the child page to find the parent for. 'level' is
+ * the level of the child. On entry, *parentblkno and *downlinkoffnum can
+ * point to a location where the downlink used to be - we will check that
+ * location first, and save some cycles if it hasn't moved. The function
+ * returns a buffer containing the downlink, exclusively-locked, and
+ * *parentblkno and *downlinkoffnum are set to the real location of the
+ * downlink.
+ *
+ * If the child page is a leaf (level == 0), the caller must supply a correct
+ * parentblkno. Otherwise we use the parent map hash table to find the parent
+ * block.
+ *
+ * This function serves the same purpose as gistFindCorrectParent() during
+ * normal index inserts, but this is simpler because we don't need to deal
+ * with concurrent inserts.
*/
-static void
+static Buffer
gistBufferingFindCorrectParent(GISTBuildState *buildstate,
- GISTBufferingInsertStack *child)
+ BlockNumber childblkno, int level,
+ BlockNumber *parentblkno,
+ OffsetNumber *downlinkoffnum)
{
- GISTBuildBuffers *gfbb = buildstate->gfbb;
- Relation indexrel = buildstate->indexrel;
- GISTBufferingInsertStack *parent = child->parent;
- OffsetNumber i,
- maxoff;
- ItemId iid;
- IndexTuple idxtuple;
+ BlockNumber parent;
Buffer buffer;
Page page;
- bool copied = false;
+ OffsetNumber maxoff;
+ OffsetNumber off;
- buffer = ReadBuffer(indexrel, parent->blkno);
+ if (level > 0)
+ parent = gistGetParent(buildstate, childblkno);
+ else
+ {
+ /*
+ * For a leaf page, the caller must supply a correct parent block
+ * number.
+ */
+ if (*parentblkno == InvalidBlockNumber)
+ elog(ERROR, "no parent buffer provided of child %d", childblkno);
+ parent = *parentblkno;
+ }
+
+ buffer = ReadBuffer(buildstate->indexrel, parent);
page = BufferGetPage(buffer);
LockBuffer(buffer, GIST_EXCLUSIVE);
- gistcheckpage(indexrel, buffer);
+ gistcheckpage(buildstate->indexrel, buffer);
+ maxoff = PageGetMaxOffsetNumber(page);
/* Check if it was not moved */
- if (child->downlinkoffnum != InvalidOffsetNumber &&
- child->downlinkoffnum <= PageGetMaxOffsetNumber(page))
+ if (parent == *parentblkno && *parentblkno != InvalidBlockNumber &&
+ *downlinkoffnum != InvalidOffsetNumber && *downlinkoffnum <= maxoff)
{
- iid = PageGetItemId(page, child->downlinkoffnum);
- idxtuple = (IndexTuple) PageGetItem(page, iid);
- if (ItemPointerGetBlockNumber(&(idxtuple->t_tid)) == child->blkno)
+ ItemId iid = PageGetItemId(page, *downlinkoffnum);
+ IndexTuple idxtuple = (IndexTuple) PageGetItem(page, iid);
+ if (ItemPointerGetBlockNumber(&(idxtuple->t_tid)) == childblkno)
{
/* Still there */
- UnlockReleaseBuffer(buffer);
- return;
+ return buffer;
}
}
- /* parent has changed, look child in right links until found */
- while (true)
+ /*
+ * Downlink was not at the offset where it used to be. Scan the page
+ * to find it. During normal gist insertions, it might've moved to another
+ * page, to the right, but during a buffering build, we keep track of
+ * the parent of each page in the lookup table so we should always know
+ * what page it's on.
+ */
+ for (off = FirstOffsetNumber; off <= maxoff; off = OffsetNumberNext(off))
{
- /* Search for relevant downlink in the current page */
- maxoff = PageGetMaxOffsetNumber(page);
- for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
- {
- iid = PageGetItemId(page, i);
- idxtuple = (IndexTuple) PageGetItem(page, iid);
- if (ItemPointerGetBlockNumber(&(idxtuple->t_tid)) == child->blkno)
- {
- /* yes!!, found */
- child->downlinkoffnum = i;
- UnlockReleaseBuffer(buffer);
- return;
- }
- }
-
- /*
- * We should copy parent path item because some other path items can
- * refer to it.
- */
- if (!copied)
+ ItemId iid = PageGetItemId(page, off);
+ IndexTuple idxtuple = (IndexTuple) PageGetItem(page, iid);
+ if (ItemPointerGetBlockNumber(&(idxtuple->t_tid)) == childblkno)
{
- parent = (GISTBufferingInsertStack *) MemoryContextAlloc(gfbb->context,
- sizeof(GISTBufferingInsertStack));
- memcpy(parent, child->parent, sizeof(GISTBufferingInsertStack));
- if (parent->parent)
- parent->parent->refCount++;
- gistDecreasePathRefcount(child->parent);
- child->parent = parent;
- parent->refCount = 1;
- copied = true;
+ /* yes!!, found it */
+ *downlinkoffnum = off;
+ return buffer;
}
-
- /*
- * Not found in current page. Move towards rightlink.
- */
- parent->blkno = GistPageGetOpaque(page)->rightlink;
- UnlockReleaseBuffer(buffer);
-
- if (parent->blkno == InvalidBlockNumber)
- {
- /*
- * End of chain and still didn't find parent. Should not happen
- * during index build.
- */
- break;
- }
-
- /* Get the next page */
- buffer = ReadBuffer(indexrel, parent->blkno);
- page = BufferGetPage(buffer);
- LockBuffer(buffer, GIST_EXCLUSIVE);
- gistcheckpage(indexrel, buffer);
}
- elog(ERROR, "failed to re-find parent for block %u", child->blkno);
+ elog(ERROR, "failed to re-find parent for block %u", childblkno);
+ return InvalidBuffer; /* keep compiler quiet */
}
/*
@@ -934,7 +967,7 @@ gistProcessEmptyingQueue(GISTBuildState *buildstate)
* threshold, but we might as well keep flushing tuples from it
* until we fill a lower-level buffer.
*/
- if (gistProcessItup(buildstate, itup, emptyingNodeBuffer->path))
+ if (gistProcessItup(buildstate, itup, emptyingNodeBuffer->nodeBlocknum, emptyingNodeBuffer->level))
{
/*
* A lower level buffer filled up. Stop emptying this buffer,
@@ -1003,46 +1036,12 @@ gistEmptyAllBuffers(GISTBuildState *buildstate)
gfbb->buffersOnLevels[i] =
list_delete_first(gfbb->buffersOnLevels[i]);
}
+ elog(DEBUG2, "emptied all buffers at level %d", i);
}
MemoryContextSwitchTo(oldCtx);
}
/*
- * Free unreferenced parts of a path stack.
- */
-static void
-gistFreeUnreferencedPath(GISTBufferingInsertStack *path)
-{
- while (path->refCount == 0)
- {
- /*
- * Path part is unreferenced. We can free it and decrease reference
- * count of parent. If parent becomes unreferenced too procedure
- * should be repeated for it.
- */
- GISTBufferingInsertStack *tmp = path->parent;
-
- pfree(path);
- path = tmp;
- if (path)
- path->refCount--;
- else
- break;
- }
-}
-
-/*
- * Decrease reference count of a path part, and free any unreferenced parts of
- * the path stack.
- */
-void
-gistDecreasePathRefcount(GISTBufferingInsertStack *path)
-{
- path->refCount--;
- gistFreeUnreferencedPath(path);
-}
-
-/*
* Get the depth of the GiST index.
*/
static int
@@ -1091,9 +1090,114 @@ gistGetMaxLevel(Relation index)
/*
* We're going down on the tree. It means that there is yet one more
- * level is the tree.
+ * level in the tree.
*/
maxLevel++;
}
return maxLevel;
}
+
+
+/*
+ * Routines for managing the parent map.
+ *
+ * Whenever a page is split, we need to insert the downlinks into the parent.
+ * We need to somehow find the parent page to do that. In normal insertions,
+ * we keep a stack of nodes visited when we descend the tree. However, in
+ * buffering build, we can start descending the tree from any internal node,
+ * when we empty a buffer by cascading tuples to its children. So we don't
+ * have a full stack up to the root available at that time.
+ *
+ * So instead, we maintain a hash table to track the parent of every internal
+ * page. We don't need to track the parents of leaf nodes, however. Whenever
+ * we insert to a leaf, we've just descended down from its parent, so we know
+ * its immediate parent already. This helps a lot to limit the memory used
+ * by this hash table.
+ *
+ * Whenever an internal node is split, the parent map needs to be updated.
+ * the parent of the new child page needs to be recorded, and also the
+ * entries for all page whose downlinks are moved to a new page at the split
+ * needs to be updated.
+ *
+ * We also update the parent map whenever we descend the tree. That might seem
+ * unnecessary, because we maintain the map whenever a downlink is moved or
+ * created, but it is needed because we switch to buffering mode after
+ * creating a tree with regular index inserts. Any pages created before
+ * switching to buffering mode will not be present in the parent map initially,
+ * but will be added there the first time we visit them.
+ */
+
+typedef struct
+{
+ BlockNumber childblkno; /* hash key */
+ BlockNumber parentblkno;
+} ParentMapEntry;
+
+static void
+gistInitParentMap(GISTBuildState *buildstate)
+{
+ HASHCTL hashCtl;
+
+ hashCtl.keysize = sizeof(BlockNumber);
+ hashCtl.entrysize = sizeof(ParentMapEntry);
+ hashCtl.hcxt = CurrentMemoryContext;
+ hashCtl.hash = oid_hash;
+ buildstate->parentMap = hash_create("gistbuild parent map",
+ 1024,
+ &hashCtl,
+ HASH_ELEM | HASH_CONTEXT
+ | HASH_FUNCTION);
+}
+
+static void
+gistMemorizeParent(GISTBuildState *buildstate, BlockNumber child, BlockNumber parent)
+{
+ ParentMapEntry *entry;
+ bool found;
+
+ entry = (ParentMapEntry *) hash_search(buildstate->parentMap,
+ (const void *) &child,
+ HASH_ENTER,
+ &found);
+ entry->parentblkno = parent;
+}
+
+/*
+ * Scan all downlinks on a page, and memorize their parent.
+ */
+static void
+gistMemorizeAllDownlinks(GISTBuildState *buildstate, Buffer parentbuf)
+{
+ OffsetNumber maxoff;
+ OffsetNumber off;
+ BlockNumber parentblkno = BufferGetBlockNumber(parentbuf);
+ Page page = BufferGetPage(parentbuf);
+
+ Assert(!GistPageIsLeaf(page));
+
+ maxoff = PageGetMaxOffsetNumber(page);
+ for (off = FirstOffsetNumber; off <= maxoff; off++)
+ {
+ ItemId iid = PageGetItemId(page, off);
+ IndexTuple idxtuple = (IndexTuple) PageGetItem(page, iid);
+ BlockNumber childblkno = ItemPointerGetBlockNumber(&(idxtuple->t_tid));
+ gistMemorizeParent(buildstate, childblkno, parentblkno);
+ }
+}
+
+static BlockNumber
+gistGetParent(GISTBuildState *buildstate, BlockNumber child)
+{
+ ParentMapEntry *entry;
+ bool found;
+
+ /* Find node buffer in hash table */
+ entry = (ParentMapEntry *) hash_search(buildstate->parentMap,
+ (const void *) &child,
+ HASH_FIND,
+ &found);
+ if (!found)
+ elog(ERROR, "could not find parent of block %d in lookup table", child);
+
+ return entry->parentblkno;
+}