Separate the insertion payload from the more static portions of GinBtree.
GinBtree now only contains information related to searching the tree, and
the information of what to insert is passed separately.
Add root block number to GinBtree, instead of passing it around all the
functions as argument.
Split off ginFinishSplit() from ginInsertValue(). ginFinishSplit is
responsible for finding the parent and inserting the downlink to it.
 #include "utils/rel.h"
 
 /*
- * Locks buffer by needed method for search.
+ * Lock buffer by needed method for search.
  */
 static int
 ginTraverseLock(Buffer buffer, bool searchMode)
 }
 
 /*
- * Descends the tree to the leaf page that contains or would contain the
- * key we're searching for. The key should already be filled in 'btree',
- * in tree-type specific manner. If btree->fullScan is true, descends to the
+ * Descend the tree to the leaf page that contains or would contain the key
+ * we're searching for. The key should already be filled in 'btree', in
+ * tree-type specific manner. If btree->fullScan is true, descends to the
  * leftmost leaf page.
  *
  * If 'searchmode' is false, on return stack->buffer is exclusively locked,
  * is share-locked, and stack->parent is NULL.
  */
 GinBtreeStack *
-ginFindLeafPage(GinBtree btree, BlockNumber rootBlkno, bool searchMode)
+ginFindLeafPage(GinBtree btree, bool searchMode)
 {
    GinBtreeStack *stack;
 
    stack = (GinBtreeStack *) palloc(sizeof(GinBtreeStack));
-   stack->blkno = rootBlkno;
-   stack->buffer = ReadBuffer(btree->index, rootBlkno);
+   stack->blkno = btree->rootBlkno;
+   stack->buffer = ReadBuffer(btree->index, btree->rootBlkno);
    stack->parent = NULL;
    stack->predictNumber = 1;
 
         * ok, page is correctly locked, we should check to move right ..,
         * root never has a right link, so small optimization
         */
-       while (btree->fullScan == FALSE && stack->blkno != rootBlkno &&
+       while (btree->fullScan == FALSE && stack->blkno != btree->rootBlkno &&
               btree->isMoveRight(btree, page))
        {
            BlockNumber rightlink = GinPageGetOpaque(page)->rightlink;
    Page        page = BufferGetPage(buffer);
    bool        isLeaf = GinPageIsLeaf(page);
    bool        isData = GinPageIsData(page);
-   BlockNumber blkno = GinPageGetOpaque(page)->rightlink;
+   BlockNumber blkno = GinPageGetOpaque(page)->rightlink;
 
    nextbuffer = ReadBuffer(index, blkno);
    LockBuffer(nextbuffer, lockmode);
        elog(ERROR, "right sibling of GIN page is of different type");
 
    /*
-    * Given the proper lock sequence above, we should never land on a
-    * deleted page.
+    * Given the proper lock sequence above, we should never land on a deleted
+    * page.
     */
-   if  (GinPageIsDeleted(page))
+   if (GinPageIsDeleted(page))
        elog(ERROR, "right sibling of GIN page was deleted");
 
    return nextbuffer;
 }
 
 /*
- * Try to find parent for current stack position, returns correct
- * parent and child's offset in  stack->parent.
- * Function should never release root page to prevent conflicts
- * with vacuum process
+ * Try to find parent for current stack position. Returns correct parent and
+ * child's offset in stack->parent. The root page is never released, to
+ * to prevent conflict with vacuum process.
  */
 void
-ginFindParents(GinBtree btree, GinBtreeStack *stack,
-              BlockNumber rootBlkno)
+ginFindParents(GinBtree btree, GinBtreeStack *stack)
 {
    Page        page;
    Buffer      buffer;
    {
        /* XLog mode... */
        root = (GinBtreeStack *) palloc(sizeof(GinBtreeStack));
-       root->blkno = rootBlkno;
-       root->buffer = ReadBuffer(btree->index, rootBlkno);
+       root->blkno = btree->rootBlkno;
+       root->buffer = ReadBuffer(btree->index, btree->rootBlkno);
        LockBuffer(root->buffer, GIN_EXCLUSIVE);
        root->parent = NULL;
    }
            root = root->parent;
        }
 
-       Assert(root->blkno == rootBlkno);
-       Assert(BufferGetBlockNumber(root->buffer) == rootBlkno);
+       Assert(root->blkno == btree->rootBlkno);
+       Assert(BufferGetBlockNumber(root->buffer) == btree->rootBlkno);
        LockBuffer(root->buffer, GIN_EXCLUSIVE);
    }
    root->off = InvalidOffsetNumber;
            ptr = (GinBtreeStack *) palloc(sizeof(GinBtreeStack));
            ptr->blkno = blkno;
            ptr->buffer = buffer;
-           ptr->parent = root; /* it's may be wrong, but in next call we will
+           ptr->parent = root; /* it may be wrong, but in next call we will
                                 * correct */
            ptr->off = offset;
            stack->parent = ptr;
 }
 
 /*
- * Returns true if the insertion is done, false if the page was split and
- * downlink insertion is pending.
+ * Insert a new item to a page.
+ *
+ * Returns true if the insertion was finished. On false, the page was split and
+ * the parent needs to be updated. (a root split returns true as it doesn't
+ * need any further action by the caller to complete)
+ *
+ * When inserting a downlink to a internal page, the existing item at the
+ * given location is updated to point to 'updateblkno'.
  *
  * stack->buffer is locked on entry, and is kept locked.
  */
 static bool
-ginPlaceToPage(GinBtree btree, BlockNumber rootBlkno, GinBtreeStack *stack,
+ginPlaceToPage(GinBtree btree, GinBtreeStack *stack,
+              void *insertdata, BlockNumber updateblkno,
               GinStatsData *buildStats)
 {
    Page        page = BufferGetPage(stack->buffer);
    XLogRecData *rdata;
    bool        fit;
 
+   /*
+    * Try to put the incoming tuple on the page. If it doesn't fit,
+    * placeToPage method will return false and leave the page unmodified, and
+    * we'll have to split the page.
+    */
    START_CRIT_SECTION();
-   fit = btree->placeToPage(btree, stack->buffer, stack->off, &rdata);
+   fit = btree->placeToPage(btree, stack->buffer, stack->off,
+                            insertdata, updateblkno,
+                            &rdata);
    if (fit)
    {
        MarkBufferDirty(stack->buffer);
        END_CRIT_SECTION();
 
        rbuffer = GinNewBuffer(btree->index);
-
-       savedRightLink = GinPageGetOpaque(page)->rightlink;
-
-       /*
-        * newlpage is a pointer to memory page, it is not associated with
-        * a buffer. stack->buffer is not touched yet.
-        */
-       newlpage = btree->splitPage(btree, stack->buffer, rbuffer, stack->off, &rdata);
-
-       ((ginxlogSplit *) (rdata->data))->rootBlkno = rootBlkno;
-
-       /* During index build, count the newly-split page */
+       /* During index build, count the new page */
        if (buildStats)
        {
            if (btree->isData)
                buildStats->nEntryPages++;
        }
 
+       savedRightLink = GinPageGetOpaque(page)->rightlink;
+
+       /*
+        * newlpage is a pointer to memory page, it is not associated with a
+        * buffer. stack->buffer is not touched yet.
+        */
+       newlpage = btree->splitPage(btree, stack->buffer, rbuffer, stack->off,
+                                   insertdata, updateblkno,
+                                   &rdata);
+
+       ((ginxlogSplit *) (rdata->data))->rootBlkno = btree->rootBlkno;
+
        parent = stack->parent;
 
        if (parent == NULL)
             */
            Buffer      lbuffer = GinNewBuffer(btree->index);
 
+           /* During index build, count the new page */
+           if (buildStats)
+           {
+               if (btree->isData)
+                   buildStats->nDataPages++;
+               else
+                   buildStats->nEntryPages++;
+           }
+
            ((ginxlogSplit *) (rdata->data))->isRootSplit = TRUE;
            ((ginxlogSplit *) (rdata->data))->rrlink = InvalidBlockNumber;
 
 }
 
 /*
- * Insert value (stored in GinBtree) to tree described by stack
+ * Finish a split by inserting the downlink for the new page to parent.
  *
- * During an index build, buildStats is non-null and the counters
- * it contains are incremented as needed.
+ * On entry, stack->buffer is exclusively locked.
  *
  * NB: the passed-in stack is freed, as though by freeGinBtreeStack.
  */
 void
-ginInsertValue(GinBtree btree, GinBtreeStack *stack, GinStatsData *buildStats)
+ginFinishSplit(GinBtree btree, GinBtreeStack *stack, GinStatsData *buildStats)
 {
-   GinBtreeStack *parent;
-   BlockNumber rootBlkno;
    Page        page;
-
-   /* extract root BlockNumber from stack */
-   Assert(stack != NULL);
-   parent = stack;
-   while (parent->parent)
-       parent = parent->parent;
-   rootBlkno = parent->blkno;
-   Assert(BlockNumberIsValid(rootBlkno));
+   bool        done;
 
    /* this loop crawls up the stack until the insertion is complete */
-   for (;;)
+   do
    {
-       bool done;
-
-       done = ginPlaceToPage(btree, rootBlkno, stack, buildStats);
-
-       /* just to be extra sure we don't delete anything by accident... */
-       btree->isDelete = FALSE;
-
-       if (done)
-       {
-           LockBuffer(stack->buffer, GIN_UNLOCK);
-           freeGinBtreeStack(stack);
-           break;
-       }
+       GinBtreeStack *parent = stack->parent;
+       void       *insertdata;
+       BlockNumber updateblkno;
 
-       btree->prepareDownlink(btree, stack->buffer);
+       insertdata = btree->prepareDownlink(btree, stack->buffer);
+       updateblkno = GinPageGetOpaque(BufferGetPage(stack->buffer))->rightlink;
 
        /* search parent to lock */
        LockBuffer(parent->buffer, GIN_EXCLUSIVE);
                 * plain search...
                 */
                LockBuffer(parent->buffer, GIN_UNLOCK);
-               ginFindParents(btree, stack, rootBlkno);
+               ginFindParents(btree, stack);
                parent = stack->parent;
                Assert(parent != NULL);
                break;
            page = BufferGetPage(parent->buffer);
        }
 
+       /* release the child */
        UnlockReleaseBuffer(stack->buffer);
        pfree(stack);
        stack = parent;
+
+       /* insert the downlink to parent */
+       done = ginPlaceToPage(btree, stack,
+                             insertdata, updateblkno,
+                             buildStats);
+       pfree(insertdata);
+   } while (!done);
+   LockBuffer(stack->buffer, GIN_UNLOCK);
+
+   /* free the rest of the stack */
+   freeGinBtreeStack(stack);
+}
+
+/*
+ * Insert a value to tree described by stack.
+ *
+ * The value to be inserted is given in 'insertdata'. Its format depends
+ * on whether this is an entry or data tree, ginInsertValue just passes it
+ * through to the tree-specific callback function.
+ *
+ * During an index build, buildStats is non-null and the counters it contains
+ * are incremented as needed.
+ *
+ * NB: the passed-in stack is freed, as though by freeGinBtreeStack.
+ */
+void
+ginInsertValue(GinBtree btree, GinBtreeStack *stack, void *insertdata,
+              GinStatsData *buildStats)
+{
+   bool        done;
+
+   done = ginPlaceToPage(btree, stack,
+                         insertdata, InvalidBlockNumber,
+                         buildStats);
+   if (done)
+   {
+       LockBuffer(stack->buffer, GIN_UNLOCK);
+       freeGinBtreeStack(stack);
    }
+   else
+       ginFinishSplit(btree, stack, buildStats);
 }
 
    if (GinPageRightMost(page))
        return FALSE;
 
-   return (ginCompareItemPointers(btree->items + btree->curitem, iptr) > 0) ? TRUE : FALSE;
+   return (ginCompareItemPointers(&btree->itemptr, iptr) > 0) ? TRUE : FALSE;
 }
 
 /*
        else
        {
            pitem = GinDataPageGetPostingItem(page, mid);
-           result = ginCompareItemPointers(btree->items + btree->curitem, &(pitem->key));
+           result = ginCompareItemPointers(&btree->itemptr, &(pitem->key));
        }
 
        if (result == 0)
    {
        OffsetNumber mid = low + ((high - low) / 2);
 
-       result = ginCompareItemPointers(btree->items + btree->curitem,
+       result = ginCompareItemPointers(&btree->itemptr,
                                        GinDataPageGetItemPointer(page, mid));
 
        if (result == 0)
  * item pointer never deletes!
  */
 static bool
-dataIsEnoughSpace(GinBtree btree, Buffer buf, OffsetNumber off)
+dataIsEnoughSpace(GinBtree btree, Buffer buf, OffsetNumber off, void *insertdata)
 {
    Page        page = BufferGetPage(buf);
 
    Assert(GinPageIsData(page));
-   Assert(!btree->isDelete);
 
    if (GinPageIsLeaf(page))
    {
+       GinBtreeDataLeafInsertData *items = insertdata;
+
        if (GinPageRightMost(page) && off > GinPageGetOpaque(page)->maxoff)
        {
-           if ((btree->nitem - btree->curitem) * sizeof(ItemPointerData) <= GinDataPageGetFreeSpace(page))
+           if ((items->nitem - items->curitem) * sizeof(ItemPointerData) <= GinDataPageGetFreeSpace(page))
                return true;
        }
        else if (sizeof(ItemPointerData) <= GinDataPageGetFreeSpace(page))
    return false;
 }
 
-/*
- * In case of previous split update old child blkno to
- * new right page
- * item pointer never deletes!
- */
-static BlockNumber
-dataPrepareData(GinBtree btree, Page page, OffsetNumber off)
-{
-   BlockNumber ret = InvalidBlockNumber;
-
-   Assert(GinPageIsData(page));
-
-   if (!GinPageIsLeaf(page) && btree->rightblkno != InvalidBlockNumber)
-   {
-       PostingItem *pitem = GinDataPageGetPostingItem(page, off);
-
-       PostingItemSetBlockNumber(pitem, btree->rightblkno);
-       ret = btree->rightblkno;
-   }
-
-   btree->rightblkno = InvalidBlockNumber;
-
-   return ret;
-}
-
 /*
  * Places keys to page and fills WAL record. In case leaf page and
  * build mode puts all ItemPointers to page.
  *
  * If none of the keys fit, returns false without modifying the page.
+ *
+ * On insertion to an internal node, in addition to inserting the given item,
+ * the downlink of the existing item at 'off' is updated to point to
+ * 'updateblkno'.
  */
 static bool
 dataPlaceToPage(GinBtree btree, Buffer buf, OffsetNumber off,
+               void *insertdata, BlockNumber updateblkno,
                XLogRecData **prdata)
 {
    Page        page = BufferGetPage(buf);
-   int         sizeofitem = GinSizeOfDataPageItem(page);
    int         cnt = 0;
 
    /* these must be static so they can be returned to caller */
    static ginxlogInsert data;
 
    /* quick exit if it doesn't fit */
-   if (!dataIsEnoughSpace(btree, buf, off))
+   if (!dataIsEnoughSpace(btree, buf, off, insertdata))
        return false;
 
    *prdata = rdata;
    Assert(GinPageIsData(page));
 
-   data.updateBlkno = dataPrepareData(btree, page, off);
+   /* Update existing downlink to point to next page (on internal page) */
+   if (!GinPageIsLeaf(page))
+   {
+       PostingItem *pitem = GinDataPageGetPostingItem(page, off);
 
+       PostingItemSetBlockNumber(pitem, updateblkno);
+   }
+
+   data.updateBlkno = updateblkno;
    data.node = btree->index->rd_node;
    data.blkno = BufferGetBlockNumber(buf);
    data.offset = off;
    cnt++;
 
    rdata[cnt].buffer = InvalidBuffer;
-   rdata[cnt].data = (GinPageIsLeaf(page)) ? ((char *) (btree->items + btree->curitem)) : ((char *) &(btree->pitem));
-   rdata[cnt].len = sizeofitem;
+   /* data and len filled in below */
    rdata[cnt].next = NULL;
 
    if (GinPageIsLeaf(page))
    {
+       GinBtreeDataLeafInsertData *items = insertdata;
+       uint32      savedPos = items->curitem;
+
        if (GinPageRightMost(page) && off > GinPageGetOpaque(page)->maxoff)
        {
            /* usually, create index... */
-           uint32      savedPos = btree->curitem;
-
-           while (btree->curitem < btree->nitem)
+           while (items->curitem < items->nitem)
            {
-               GinDataPageAddItemPointer(page, btree->items + btree->curitem, off);
+               GinDataPageAddItemPointer(page, items->items + items->curitem, off);
                off++;
-               btree->curitem++;
+               items->curitem++;
            }
-           data.nitem = btree->curitem - savedPos;
-           rdata[cnt].len = sizeofitem * data.nitem;
+           data.nitem = items->curitem - savedPos;
        }
        else
        {
-           GinDataPageAddItemPointer(page, btree->items + btree->curitem, off);
-           btree->curitem++;
+           GinDataPageAddItemPointer(page, items->items + items->curitem, off);
+           items->curitem++;
        }
+
+       rdata[cnt].data = (char *) &items->items[savedPos];
+       rdata[cnt].len = sizeof(ItemPointerData) * data.nitem;
    }
    else
-       GinDataPageAddPostingItem(page, &(btree->pitem), off);
+   {
+       PostingItem *pitem = insertdata;
+
+       GinDataPageAddPostingItem(page, pitem, off);
+
+       rdata[cnt].data = (char *) pitem;
+       rdata[cnt].len = sizeof(PostingItem);
+   }
 
    return true;
 }
  * left page
  */
 static Page
-dataSplitPage(GinBtree btree, Buffer lbuf, Buffer rbuf, OffsetNumber off, XLogRecData **prdata)
+dataSplitPage(GinBtree btree, Buffer lbuf, Buffer rbuf, OffsetNumber off,
+             void *insertdata, BlockNumber updateblkno, XLogRecData **prdata)
 {
    char       *ptr;
    OffsetNumber separator;
    Page        rpage = BufferGetPage(rbuf);
    Size        pageSize = PageGetPageSize(lpage);
    Size        freeSpace;
-   uint32      nCopied = 1;
 
    /* these must be static so they can be returned to caller */
    static ginxlogSplit data;
    freeSpace = GinDataPageGetFreeSpace(rpage);
 
    *prdata = rdata;
-   data.leftChildBlkno = (GinPageIsLeaf(lpage)) ?
-       InvalidOffsetNumber : PostingItemGetBlockNumber(&(btree->pitem));
-   data.updateBlkno = dataPrepareData(btree, lpage, off);
+
+   /* Update existing downlink to point to next page (on internal page) */
+   if (!isleaf)
+   {
+       PostingItem *pitem = GinDataPageGetPostingItem(lpage, off);
+
+       PostingItemSetBlockNumber(pitem, updateblkno);
+   }
 
    if (isleaf)
    {
 
    if (isleaf && GinPageRightMost(lpage) && off > GinPageGetOpaque(lpage)->maxoff)
    {
-       nCopied = 0;
-       while (btree->curitem < btree->nitem &&
+       GinBtreeDataLeafInsertData *items = insertdata;
+
+       while (items->curitem < items->nitem &&
               maxoff * sizeof(ItemPointerData) < 2 * (freeSpace - sizeof(ItemPointerData)))
        {
            memcpy(vector + maxoff * sizeof(ItemPointerData),
-                  btree->items + btree->curitem,
+                  items->items + items->curitem,
                   sizeof(ItemPointerData));
            maxoff++;
-           nCopied++;
-           btree->curitem++;
+           items->curitem++;
        }
    }
    else
            memmove(ptr + sizeofitem, ptr, (maxoff - off + 1) * sizeofitem);
        if (isleaf)
        {
-           memcpy(ptr, btree->items + btree->curitem, sizeofitem);
-           btree->curitem++;
+           GinBtreeDataLeafInsertData *items = insertdata;
+
+           memcpy(ptr, items->items + items->curitem, sizeofitem);
+           items->curitem++;
        }
        else
-           memcpy(ptr, &(btree->pitem), sizeofitem);
+       {
+           PostingItem *pitem = insertdata;
+
+           memcpy(ptr, pitem, sizeofitem);
+       }
 
        maxoff++;
    }
                                            GinPageGetOpaque(lpage)->maxoff);
    else
        *bound = GinDataPageGetPostingItem(lpage,
-                                     GinPageGetOpaque(lpage)->maxoff)->key;
+                                      GinPageGetOpaque(lpage)->maxoff)->key;
 
    /* set up right bound for right page */
    bound = GinDataPageGetRightBound(rpage);
 }
 
 /*
- * Prepare the state in 'btree' for inserting a downlink for given buffer.
+ * Construct insertion payload for inserting the downlink for given buffer.
  */
-static void
+static void *
 dataPrepareDownlink(GinBtree btree, Buffer lbuf)
 {
+   PostingItem *pitem = palloc(sizeof(PostingItem));
    Page        lpage = BufferGetPage(lbuf);
 
-   PostingItemSetBlockNumber(&(btree->pitem), BufferGetBlockNumber(lbuf));
-   btree->pitem.key = *GinDataPageGetRightBound(lpage);
-   btree->rightblkno = GinPageGetOpaque(lpage)->rightlink;
+   PostingItemSetBlockNumber(pitem, BufferGetBlockNumber(lbuf));
+   pitem->key = *GinDataPageGetRightBound(lpage);
+
+   return pitem;
 }
 
 /*
 }
 
 void
-ginPrepareDataScan(GinBtree btree, Relation index)
+ginPrepareDataScan(GinBtree btree, Relation index, BlockNumber rootBlkno)
 {
    memset(btree, 0, sizeof(GinBtreeData));
 
    btree->index = index;
+   btree->rootBlkno = rootBlkno;
 
    btree->findChildPage = dataLocateItem;
    btree->getLeftMostChild = dataGetLeftMostPage;
    btree->prepareDownlink = dataPrepareDownlink;
 
    btree->isData = TRUE;
-   btree->isDelete = FALSE;
    btree->fullScan = FALSE;
    btree->isBuild = FALSE;
 }
                      GinStatsData *buildStats)
 {
    GinBtreeData btree;
+   GinBtreeDataLeafInsertData insertdata;
    GinBtreeStack *stack;
 
-   ginPrepareDataScan(&btree, index);
+   ginPrepareDataScan(&btree, index, rootBlkno);
    btree.isBuild = (buildStats != NULL);
-   btree.items = items;
-   btree.nitem = nitem;
-   btree.curitem = 0;
+   insertdata.items = items;
+   insertdata.nitem = nitem;
+   insertdata.curitem = 0;
 
-   while (btree.curitem < btree.nitem)
+   while (insertdata.curitem < insertdata.nitem)
    {
-       stack = ginFindLeafPage(&btree, rootBlkno, false);
+       /* search for the leaf page where the first item should go to */
+       btree.itemptr = insertdata.items[insertdata.curitem];
+       stack = ginFindLeafPage(&btree, false);
 
        if (btree.findItem(&btree, stack))
        {
            /*
-            * btree.items[btree.curitem] already exists in index
+            * Current item already exists in index.
             */
-           btree.curitem++;
+           insertdata.curitem++;
            LockBuffer(stack->buffer, GIN_UNLOCK);
            freeGinBtreeStack(stack);
        }
        else
-           ginInsertValue(&btree, stack, buildStats);
+           ginInsertValue(&btree, stack, &insertdata, buildStats);
    }
 }
 
    GinBtreeData btree;
    GinBtreeStack *stack;
 
-   ginPrepareDataScan(&btree, index);
+   ginPrepareDataScan(&btree, index, rootBlkno);
 
    btree.fullScan = TRUE;
 
-   stack = ginFindLeafPage(&btree, rootBlkno, TRUE);
+   stack = ginFindLeafPage(&btree, TRUE);
 
    return stack;
 }
 
    if (newsize != IndexTupleSize(itup))
    {
        itup = repalloc(itup, newsize);
+
        /*
         * PostgreSQL 9.3 and earlier did not clear this new space, so we
         * might find uninitialized padding when reading tuples from disk.
 }
 
 static bool
-entryIsEnoughSpace(GinBtree btree, Buffer buf, OffsetNumber off)
+entryIsEnoughSpace(GinBtree btree, Buffer buf, OffsetNumber off,
+                  GinBtreeEntryInsertData *insertData)
 {
-   Size        itupsz = 0;
+   Size        releasedsz = 0;
+   Size        addedsz;
    Page        page = BufferGetPage(buf);
 
-   Assert(btree->entry);
+   Assert(insertData->entry);
    Assert(!GinPageIsData(page));
 
-   if (btree->isDelete)
+   if (insertData->isDelete)
    {
        IndexTuple  itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off));
 
-       itupsz = MAXALIGN(IndexTupleSize(itup)) + sizeof(ItemIdData);
+       releasedsz = MAXALIGN(IndexTupleSize(itup)) + sizeof(ItemIdData);
    }
 
-   if (PageGetFreeSpace(page) + itupsz >= MAXALIGN(IndexTupleSize(btree->entry)) + sizeof(ItemIdData))
+   addedsz = MAXALIGN(IndexTupleSize(insertData->entry)) + sizeof(ItemIdData);
+
+   if (PageGetFreeSpace(page) + releasedsz >= addedsz)
        return true;
 
    return false;
  * should update it, update old child blkno to new right page
  * if child split occurred
  */
-static BlockNumber
-entryPreparePage(GinBtree btree, Page page, OffsetNumber off)
+static void
+entryPreparePage(GinBtree btree, Page page, OffsetNumber off,
+                GinBtreeEntryInsertData *insertData, BlockNumber updateblkno)
 {
-   BlockNumber ret = InvalidBlockNumber;
-
-   Assert(btree->entry);
+   Assert(insertData->entry);
    Assert(!GinPageIsData(page));
 
-   if (btree->isDelete)
+   if (insertData->isDelete)
    {
        Assert(GinPageIsLeaf(page));
        PageIndexTupleDelete(page, off);
    }
 
-   if (!GinPageIsLeaf(page) && btree->rightblkno != InvalidBlockNumber)
+   if (!GinPageIsLeaf(page) && updateblkno != InvalidBlockNumber)
    {
        IndexTuple  itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off));
 
-       GinSetDownlink(itup, btree->rightblkno);
-       ret = btree->rightblkno;
+       GinSetDownlink(itup, updateblkno);
    }
-
-   btree->rightblkno = InvalidBlockNumber;
-
-   return ret;
 }
 
 /*
  * Place tuple on page and fills WAL record
  *
  * If the tuple doesn't fit, returns false without modifying the page.
+ *
+ * On insertion to an internal node, in addition to inserting the given item,
+ * the downlink of the existing item at 'off' is updated to point to
+ * 'updateblkno'.
  */
 static bool
 entryPlaceToPage(GinBtree btree, Buffer buf, OffsetNumber off,
+                void *insertPayload, BlockNumber updateblkno,
                 XLogRecData **prdata)
 {
+   GinBtreeEntryInsertData *insertData = insertPayload;
    Page        page = BufferGetPage(buf);
    OffsetNumber placed;
    int         cnt = 0;
    static ginxlogInsert data;
 
    /* quick exit if it doesn't fit */
-   if (!entryIsEnoughSpace(btree, buf, off))
+   if (!entryIsEnoughSpace(btree, buf, off, insertData))
        return false;
 
    *prdata = rdata;
-   data.updateBlkno = entryPreparePage(btree, page, off);
+   entryPreparePage(btree, page, off, insertData, updateblkno);
+   data.updateBlkno = updateblkno;
 
-   placed = PageAddItem(page, (Item) btree->entry, IndexTupleSize(btree->entry), off, false, false);
+   placed = PageAddItem(page,
+                        (Item) insertData->entry,
+                        IndexTupleSize(insertData->entry),
+                        off, false, false);
    if (placed != off)
        elog(ERROR, "failed to add item to index page in \"%s\"",
             RelationGetRelationName(btree->index));
    data.blkno = BufferGetBlockNumber(buf);
    data.offset = off;
    data.nitem = 1;
-   data.isDelete = btree->isDelete;
+   data.isDelete = insertData->isDelete;
    data.isData = false;
    data.isLeaf = GinPageIsLeaf(page) ? TRUE : FALSE;
 
    cnt++;
 
    rdata[cnt].buffer = InvalidBuffer;
-   rdata[cnt].data = (char *) btree->entry;
-   rdata[cnt].len = IndexTupleSize(btree->entry);
+   rdata[cnt].data = (char *) insertData->entry;
+   rdata[cnt].len = IndexTupleSize(insertData->entry);
    rdata[cnt].next = NULL;
 
-   btree->entry = NULL;
-
    return true;
 }
 
  * an equal number!
  */
 static Page
-entrySplitPage(GinBtree btree, Buffer lbuf, Buffer rbuf, OffsetNumber off, XLogRecData **prdata)
+entrySplitPage(GinBtree btree, Buffer lbuf, Buffer rbuf, OffsetNumber off,
+              void *insertPayload,
+              BlockNumber updateblkno, XLogRecData **prdata)
 {
+   GinBtreeEntryInsertData *insertData = insertPayload;
    OffsetNumber i,
                maxoff,
                separator = InvalidOffsetNumber;
 
    *prdata = rdata;
    data.leftChildBlkno = (GinPageIsLeaf(lpage)) ?
-       InvalidOffsetNumber : GinGetDownlink(btree->entry);
-   data.updateBlkno = entryPreparePage(btree, lpage, off);
+       InvalidOffsetNumber : GinGetDownlink(insertData->entry);
+   data.updateBlkno = updateblkno;
+   entryPreparePage(btree, lpage, off, insertData, updateblkno);
 
    maxoff = PageGetMaxOffsetNumber(lpage);
    ptr = tupstore;
    {
        if (i == off)
        {
-           size = MAXALIGN(IndexTupleSize(btree->entry));
-           memcpy(ptr, btree->entry, size);
+           size = MAXALIGN(IndexTupleSize(insertData->entry));
+           memcpy(ptr, insertData->entry, size);
            ptr += size;
            totalsize += size + sizeof(ItemIdData);
        }
 
    if (off == maxoff + 1)
    {
-       size = MAXALIGN(IndexTupleSize(btree->entry));
-       memcpy(ptr, btree->entry, size);
+       size = MAXALIGN(IndexTupleSize(insertData->entry));
+       memcpy(ptr, insertData->entry, size);
        ptr += size;
        totalsize += size + sizeof(ItemIdData);
    }
 }
 
 /*
- * Prepare the state in 'btree' for inserting a downlink for given buffer.
+ * Construct insertion payload for inserting the downlink for given buffer.
  */
-static void
+static void *
 entryPrepareDownlink(GinBtree btree, Buffer lbuf)
 {
+   GinBtreeEntryInsertData *insertData;
    Page        lpage = BufferGetPage(lbuf);
+   BlockNumber lblkno = BufferGetBlockNumber(lbuf);
    IndexTuple  itup;
 
    itup = getRightMostTuple(lpage);
 
-   btree->entry = GinFormInteriorTuple(itup,
-                                       lpage,
-                                       BufferGetBlockNumber(lbuf));
-   btree->rightblkno = GinPageGetOpaque(lpage)->rightlink;
+   insertData = palloc(sizeof(GinBtreeEntryInsertData));
+   insertData->entry = GinFormInteriorTuple(itup, lpage, lblkno);
+   insertData->isDelete = false;
+
+   return insertData;
 }
 
 /*
    memset(btree, 0, sizeof(GinBtreeData));
 
    btree->index = ginstate->index;
+   btree->rootBlkno = GIN_ROOT_BLKNO;
    btree->ginstate = ginstate;
 
    btree->findChildPage = entryLocateEntry;
    btree->entryAttnum = attnum;
    btree->entryKey = key;
    btree->entryCategory = category;
-   btree->isDelete = FALSE;
 }
 
    ginPrepareEntryScan(&btreeEntry, entry->attnum,
                        entry->queryKey, entry->queryCategory,
                        ginstate);
-   stackEntry = ginFindLeafPage(&btreeEntry, GIN_ROOT_BLKNO, true);
+   stackEntry = ginFindLeafPage(&btreeEntry, true);
    page = BufferGetPage(stackEntry->buffer);
    needUnlock = TRUE;
 
 
               GinStatsData *buildStats)
 {
    GinBtreeData btree;
+   GinBtreeEntryInsertData insertdata;
    GinBtreeStack *stack;
    IndexTuple  itup;
    Page        page;
 
+   insertdata.isDelete = FALSE;
+
    /* During index build, count the to-be-inserted entry */
    if (buildStats)
        buildStats->nEntries++;
 
    ginPrepareEntryScan(&btree, attnum, key, category, ginstate);
 
-   stack = ginFindLeafPage(&btree, GIN_ROOT_BLKNO, false);
+   stack = ginFindLeafPage(&btree, false);
    page = BufferGetPage(stack->buffer);
 
    if (btree.findItem(&btree, stack))
        itup = addItemPointersToLeafTuple(ginstate, itup,
                                          items, nitem, buildStats);
 
-       btree.isDelete = TRUE;
+       insertdata.isDelete = TRUE;
    }
    else
    {
    }
 
    /* Insert the new or modified leaf tuple */
-   btree.entry = itup;
-   ginInsertValue(&btree, stack, buildStats);
+   insertdata.entry = itup;
+   ginInsertValue(&btree, stack, &insertdata, buildStats);
    pfree(itup);
 }
 
 
    GinState    ginstate;
    Relation    reln;
    Buffer      buffer;
-   GinBtreeStack stack;
+   GinBtreeStack *stack;
 
    /*
     * elog(NOTICE,"ginContinueSplit root:%u l:%u r:%u",  split->rootBlkno,
    }
    else
    {
-       ginPrepareDataScan(&btree, reln);
+       ginPrepareDataScan(&btree, reln, split->rootBlkno);
    }
 
-   stack.blkno = split->leftBlkno;
-   stack.buffer = buffer;
-   stack.off = InvalidOffsetNumber;
-   stack.parent = NULL;
+   stack = palloc(sizeof(GinBtreeStack));
+   stack->blkno = split->leftBlkno;
+   stack->buffer = buffer;
+   stack->off = InvalidOffsetNumber;
+   stack->parent = NULL;
 
-   ginFindParents(&btree, &stack, split->rootBlkno);
+   ginFindParents(&btree, stack);
+   LockBuffer(stack->parent->buffer, GIN_UNLOCK);
+   ginFinishSplit(&btree, stack, NULL);
 
-   btree.prepareDownlink(&btree, buffer);
-   ginInsertValue(&btree, stack.parent, NULL);
+   /* buffer is released by ginFinishSplit */
 
    FreeFakeRelcacheEntry(reln);
-
-   UnlockReleaseBuffer(buffer);
 }
 
 void
 
 
    /* insert methods */
    OffsetNumber (*findChildPtr) (GinBtree, Page, BlockNumber, OffsetNumber);
-   bool        (*placeToPage) (GinBtree, Buffer, OffsetNumber, XLogRecData **);
-   Page        (*splitPage) (GinBtree, Buffer, Buffer, OffsetNumber, XLogRecData **);
-   void        (*prepareDownlink) (GinBtree, Buffer);
+   bool        (*placeToPage) (GinBtree, Buffer, OffsetNumber, void *, BlockNumber, XLogRecData **);
+   Page        (*splitPage) (GinBtree, Buffer, Buffer, OffsetNumber, void *, BlockNumber, XLogRecData **);
+   void       *(*prepareDownlink) (GinBtree, Buffer);
    void        (*fillRoot) (GinBtree, Buffer, Buffer, Buffer);
 
    bool        isData;
 
    Relation    index;
+   BlockNumber rootBlkno;
    GinState   *ginstate;       /* not valid in a data scan */
    bool        fullScan;
    bool        isBuild;
 
-   BlockNumber rightblkno;
-
-   /* Entry options */
+   /* Search key for Entry tree */
    OffsetNumber entryAttnum;
    Datum       entryKey;
    GinNullCategory entryCategory;
-   IndexTuple  entry;
-   bool        isDelete;
 
-   /* Data (posting tree) options */
+   /* Search key for data tree (posting tree) */
+   ItemPointerData itemptr;
+} GinBtreeData;
+
+/* This represents a tuple to be inserted to entry tree. */
+typedef struct
+{
+   IndexTuple  entry;          /* tuple to insert */
+   bool        isDelete;       /* delete old tuple at same offset? */
+} GinBtreeEntryInsertData;
+
+/*
+ * This represents an itempointer, or many itempointers, to be inserted to
+ * a data (posting tree) leaf page
+ */
+typedef struct
+{
    ItemPointerData *items;
    uint32      nitem;
    uint32      curitem;
+} GinBtreeDataLeafInsertData;
 
-   PostingItem pitem;
-} GinBtreeData;
+/*
+ * For internal data (posting tree) pages, the insertion payload is a
+ * PostingItem
+ */
 
-extern GinBtreeStack *ginFindLeafPage(GinBtree btree, BlockNumber rootBlkno, bool searchMode);
+extern GinBtreeStack *ginFindLeafPage(GinBtree btree, bool searchMode);
 extern Buffer ginStepRight(Buffer buffer, Relation index, int lockmode);
 extern void freeGinBtreeStack(GinBtreeStack *stack);
 extern void ginInsertValue(GinBtree btree, GinBtreeStack *stack,
+              void *insertdata, GinStatsData *buildStats);
+extern void ginFindParents(GinBtree btree, GinBtreeStack *stack);
+extern void ginFinishSplit(GinBtree btree, GinBtreeStack *stack,
               GinStatsData *buildStats);
-extern void ginFindParents(GinBtree btree, GinBtreeStack *stack, BlockNumber rootBlkno);
 
 /* ginentrypage.c */
 extern IndexTuple GinFormTuple(GinState *ginstate,
                      GinStatsData *buildStats);
 extern GinBtreeStack *ginScanBeginPostingTree(Relation index, BlockNumber rootBlkno);
 extern void ginDataFillRoot(GinBtree btree, Buffer root, Buffer lbuf, Buffer rbuf);
-extern void ginPrepareDataScan(GinBtree btree, Relation index);
+extern void ginPrepareDataScan(GinBtree btree, Relation index, BlockNumber rootBlkno);
 
 /* ginscan.c */