Header And Logo

PostgreSQL
| The world's most advanced open source database.

Data Structures | Functions

gininsert.c File Reference

#include "postgres.h"
#include "access/gin_private.h"
#include "access/heapam_xlog.h"
#include "catalog/index.h"
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/smgr.h"
#include "storage/indexfsm.h"
#include "utils/memutils.h"
#include "utils/rel.h"
Include dependency graph for gininsert.c:

Go to the source code of this file.

Data Structures

struct  GinBuildState

Functions

static BlockNumber createPostingTree (Relation index, ItemPointerData *items, uint32 nitems)
static IndexTuple addItemPointersToLeafTuple (GinState *ginstate, IndexTuple old, ItemPointerData *items, uint32 nitem, GinStatsData *buildStats)
static IndexTuple buildFreshLeafTuple (GinState *ginstate, OffsetNumber attnum, Datum key, GinNullCategory category, ItemPointerData *items, uint32 nitem, GinStatsData *buildStats)
void ginEntryInsert (GinState *ginstate, OffsetNumber attnum, Datum key, GinNullCategory category, ItemPointerData *items, uint32 nitem, GinStatsData *buildStats)
static void ginHeapTupleBulkInsert (GinBuildState *buildstate, OffsetNumber attnum, Datum value, bool isNull, ItemPointer heapptr)
static void ginBuildCallback (Relation index, HeapTuple htup, Datum *values, bool *isnull, bool tupleIsAlive, void *state)
Datum ginbuild (PG_FUNCTION_ARGS)
Datum ginbuildempty (PG_FUNCTION_ARGS)
static void ginHeapTupleInsert (GinState *ginstate, OffsetNumber attnum, Datum value, bool isNull, ItemPointer item)
Datum gininsert (PG_FUNCTION_ARGS)

Function Documentation

static IndexTuple addItemPointersToLeafTuple ( GinState ginstate,
IndexTuple  old,
ItemPointerData items,
uint32  nitem,
GinStatsData buildStats 
) [static]

Definition at line 105 of file gininsert.c.

References Assert, GinPostingTreeScan::btree, createPostingTree(), FALSE, GinFormTuple(), GinGetNPosting, GinGetPosting, ginInsertItemPointers(), GinIsPostingTree, ginMergeItemPointers(), ginPrepareScanPostingTree(), GinSetPostingTree, GinShortenTuple(), gintuple_get_attrnum(), gintuple_get_key(), GinState::index, GinBtreeData::isBuild, GinStatsData::nDataPages, NULL, and pfree().

Referenced by ginEntryInsert().

{
    OffsetNumber attnum;
    Datum       key;
    GinNullCategory category;
    IndexTuple  res;

    Assert(!GinIsPostingTree(old));

    attnum = gintuple_get_attrnum(ginstate, old);
    key = gintuple_get_key(ginstate, old, &category);

    /* try to build tuple with room for all the items */
    res = GinFormTuple(ginstate, attnum, key, category,
                       NULL, nitem + GinGetNPosting(old),
                       false);

    if (res)
    {
        /* good, small enough */
        uint32      newnitem;

        /* fill in the posting list with union of old and new TIDs */
        newnitem = ginMergeItemPointers(GinGetPosting(res),
                                        GinGetPosting(old),
                                        GinGetNPosting(old),
                                        items, nitem);
        /* merge might have eliminated some duplicate items */
        GinShortenTuple(res, newnitem);
    }
    else
    {
        /* posting list would be too big, convert to posting tree */
        BlockNumber postingRoot;
        GinPostingTreeScan *gdi;

        /*
         * Initialize posting tree with the old tuple's posting list.  It's
         * surely small enough to fit on one posting-tree page, and should
         * already be in order with no duplicates.
         */
        postingRoot = createPostingTree(ginstate->index,
                                        GinGetPosting(old),
                                        GinGetNPosting(old));

        /* During index build, count the newly-added data page */
        if (buildStats)
            buildStats->nDataPages++;

        /* Now insert the TIDs-to-be-added into the posting tree */
        gdi = ginPrepareScanPostingTree(ginstate->index, postingRoot, FALSE);
        gdi->btree.isBuild = (buildStats != NULL);

        ginInsertItemPointers(gdi, items, nitem, buildStats);

        pfree(gdi);

        /* And build a new posting-tree-only result tuple */
        res = GinFormTuple(ginstate, attnum, key, category, NULL, 0, true);
        GinSetPostingTree(res, postingRoot);
    }

    return res;
}

static IndexTuple buildFreshLeafTuple ( GinState ginstate,
OffsetNumber  attnum,
Datum  key,
GinNullCategory  category,
ItemPointerData items,
uint32  nitem,
GinStatsData buildStats 
) [static]

Definition at line 182 of file gininsert.c.

References GinPostingTreeScan::btree, createPostingTree(), FALSE, GinFormTuple(), ginInsertItemPointers(), GinMaxLeafDataItems, ginPrepareScanPostingTree(), GinSetPostingTree, GinState::index, GinBtreeData::isBuild, Min, GinStatsData::nDataPages, NULL, and pfree().

Referenced by ginEntryInsert().

{
    IndexTuple  res;

    /* try to build tuple with room for all the items */
    res = GinFormTuple(ginstate, attnum, key, category,
                       items, nitem, false);

    if (!res)
    {
        /* posting list would be too big, build posting tree */
        BlockNumber postingRoot;

        /*
         * Build posting-tree-only result tuple.  We do this first so as to
         * fail quickly if the key is too big.
         */
        res = GinFormTuple(ginstate, attnum, key, category, NULL, 0, true);

        /*
         * Initialize posting tree with as many TIDs as will fit on the first
         * page.
         */
        postingRoot = createPostingTree(ginstate->index,
                                        items,
                                        Min(nitem, GinMaxLeafDataItems));

        /* During index build, count the newly-added data page */
        if (buildStats)
            buildStats->nDataPages++;

        /* Add any remaining TIDs to the posting tree */
        if (nitem > GinMaxLeafDataItems)
        {
            GinPostingTreeScan *gdi;

            gdi = ginPrepareScanPostingTree(ginstate->index, postingRoot, FALSE);
            gdi->btree.isBuild = (buildStats != NULL);

            ginInsertItemPointers(gdi,
                                  items + GinMaxLeafDataItems,
                                  nitem - GinMaxLeafDataItems,
                                  buildStats);

            pfree(gdi);
        }

        /* And save the root link in the result tuple */
        GinSetPostingTree(res, postingRoot);
    }

    return res;
}

static BlockNumber createPostingTree ( Relation  index,
ItemPointerData items,
uint32  nitems 
) [static]

Definition at line 45 of file gininsert.c.

References Assert, ginxlogCreatePostingTree::blkno, XLogRecData::buffer, BufferGetBlockNumber(), BufferGetPage, XLogRecData::data, END_CRIT_SECTION, GIN_DATA, GIN_LEAF, GinDataPageGetData, GinInitBuffer(), GinMaxLeafDataItems, GinNewBuffer(), GinPageGetOpaque, XLogRecData::len, MarkBufferDirty(), XLogRecData::next, ginxlogCreatePostingTree::nitem, ginxlogCreatePostingTree::node, PageSetLSN, RelationData::rd_node, RelationNeedsWAL, START_CRIT_SECTION, UnlockReleaseBuffer(), XLOG_GIN_CREATE_PTREE, and XLogInsert().

Referenced by addItemPointersToLeafTuple(), and buildFreshLeafTuple().

{
    BlockNumber blkno;
    Buffer      buffer = GinNewBuffer(index);
    Page        page;

    /* Assert that the items[] array will fit on one page */
    Assert(nitems <= GinMaxLeafDataItems);

    START_CRIT_SECTION();

    GinInitBuffer(buffer, GIN_DATA | GIN_LEAF);
    page = BufferGetPage(buffer);
    blkno = BufferGetBlockNumber(buffer);

    memcpy(GinDataPageGetData(page), items, sizeof(ItemPointerData) * nitems);
    GinPageGetOpaque(page)->maxoff = nitems;

    MarkBufferDirty(buffer);

    if (RelationNeedsWAL(index))
    {
        XLogRecPtr  recptr;
        XLogRecData rdata[2];
        ginxlogCreatePostingTree data;

        data.node = index->rd_node;
        data.blkno = blkno;
        data.nitem = nitems;

        rdata[0].buffer = InvalidBuffer;
        rdata[0].data = (char *) &data;
        rdata[0].len = sizeof(ginxlogCreatePostingTree);
        rdata[0].next = &rdata[1];

        rdata[1].buffer = InvalidBuffer;
        rdata[1].data = (char *) items;
        rdata[1].len = sizeof(ItemPointerData) * nitems;
        rdata[1].next = NULL;

        recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_CREATE_PTREE, rdata);
        PageSetLSN(page, recptr);
    }

    UnlockReleaseBuffer(buffer);

    END_CRIT_SECTION();

    return blkno;
}

Datum ginbuild ( PG_FUNCTION_ARGS   ) 

Definition at line 381 of file gininsert.c.

References GinBuildState::accum, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, ALLOCSET_DEFAULT_MINSIZE, AllocSetContextCreate(), XLogRecData::buffer, BufferGetPage, GinBuildState::buildStats, CHECK_FOR_INTERRUPTS, CurrentMemoryContext, XLogRecData::data, elog, END_CRIT_SECTION, ERROR, GinBuildState::funcCtx, GIN_LEAF, ginBeginBAScan(), ginBuildCallback(), ginEntryInsert(), ginGetBAEntry(), ginInitBA(), GinInitBuffer(), GinInitMetabuffer(), GinNewBuffer(), BuildAccumulator::ginstate, GinBuildState::ginstate, ginUpdateStats(), IndexBuildResult::heap_tuples, IndexBuildResult::index_tuples, IndexBuildHeapScan(), GinBuildState::indtuples, initGinState(), XLogRecData::len, sort-test::list, MarkBufferDirty(), MemoryContextDelete(), MemoryContextSwitchTo(), GinStatsData::nEntryPages, XLogRecData::next, NULL, PageSetLSN, palloc(), PG_GETARG_POINTER, PG_RETURN_POINTER, RelationData::rd_node, RelationGetNumberOfBlocks, RelationGetRelationName, RelationNeedsWAL, START_CRIT_SECTION, GinBuildState::tmpCtx, UnlockReleaseBuffer(), XLOG_GIN_CREATE_INDEX, and XLogInsert().

{
    Relation    heap = (Relation) PG_GETARG_POINTER(0);
    Relation    index = (Relation) PG_GETARG_POINTER(1);
    IndexInfo  *indexInfo = (IndexInfo *) PG_GETARG_POINTER(2);
    IndexBuildResult *result;
    double      reltuples;
    GinBuildState buildstate;
    Buffer      RootBuffer,
                MetaBuffer;
    ItemPointerData *list;
    Datum       key;
    GinNullCategory category;
    uint32      nlist;
    MemoryContext oldCtx;
    OffsetNumber attnum;

    if (RelationGetNumberOfBlocks(index) != 0)
        elog(ERROR, "index \"%s\" already contains data",
             RelationGetRelationName(index));

    initGinState(&buildstate.ginstate, index);
    buildstate.indtuples = 0;
    memset(&buildstate.buildStats, 0, sizeof(GinStatsData));

    /* initialize the meta page */
    MetaBuffer = GinNewBuffer(index);

    /* initialize the root page */
    RootBuffer = GinNewBuffer(index);

    START_CRIT_SECTION();
    GinInitMetabuffer(MetaBuffer);
    MarkBufferDirty(MetaBuffer);
    GinInitBuffer(RootBuffer, GIN_LEAF);
    MarkBufferDirty(RootBuffer);

    if (RelationNeedsWAL(index))
    {
        XLogRecPtr  recptr;
        XLogRecData rdata;
        Page        page;

        rdata.buffer = InvalidBuffer;
        rdata.data = (char *) &(index->rd_node);
        rdata.len = sizeof(RelFileNode);
        rdata.next = NULL;

        recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_CREATE_INDEX, &rdata);

        page = BufferGetPage(RootBuffer);
        PageSetLSN(page, recptr);

        page = BufferGetPage(MetaBuffer);
        PageSetLSN(page, recptr);
    }

    UnlockReleaseBuffer(MetaBuffer);
    UnlockReleaseBuffer(RootBuffer);
    END_CRIT_SECTION();

    /* count the root as first entry page */
    buildstate.buildStats.nEntryPages++;

    /*
     * create a temporary memory context that is reset once for each tuple
     * inserted into the index
     */
    buildstate.tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
                                              "Gin build temporary context",
                                              ALLOCSET_DEFAULT_MINSIZE,
                                              ALLOCSET_DEFAULT_INITSIZE,
                                              ALLOCSET_DEFAULT_MAXSIZE);

    buildstate.funcCtx = AllocSetContextCreate(buildstate.tmpCtx,
                     "Gin build temporary context for user-defined function",
                                               ALLOCSET_DEFAULT_MINSIZE,
                                               ALLOCSET_DEFAULT_INITSIZE,
                                               ALLOCSET_DEFAULT_MAXSIZE);

    buildstate.accum.ginstate = &buildstate.ginstate;
    ginInitBA(&buildstate.accum);

    /*
     * Do the heap scan.  We disallow sync scan here because dataPlaceToPage
     * prefers to receive tuples in TID order.
     */
    reltuples = IndexBuildHeapScan(heap, index, indexInfo, false,
                                   ginBuildCallback, (void *) &buildstate);

    /* dump remaining entries to the index */
    oldCtx = MemoryContextSwitchTo(buildstate.tmpCtx);
    ginBeginBAScan(&buildstate.accum);
    while ((list = ginGetBAEntry(&buildstate.accum,
                                 &attnum, &key, &category, &nlist)) != NULL)
    {
        /* there could be many entries, so be willing to abort here */
        CHECK_FOR_INTERRUPTS();
        ginEntryInsert(&buildstate.ginstate, attnum, key, category,
                       list, nlist, &buildstate.buildStats);
    }
    MemoryContextSwitchTo(oldCtx);

    MemoryContextDelete(buildstate.tmpCtx);

    /*
     * Update metapage stats
     */
    buildstate.buildStats.nTotalPages = RelationGetNumberOfBlocks(index);
    ginUpdateStats(index, &buildstate.buildStats);

    /*
     * Return statistics
     */
    result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));

    result->heap_tuples = reltuples;
    result->index_tuples = buildstate.indtuples;

    PG_RETURN_POINTER(result);
}

static void ginBuildCallback ( Relation  index,
HeapTuple  htup,
Datum values,
bool isnull,
bool  tupleIsAlive,
void *  state 
) [static]

Definition at line 340 of file gininsert.c.

References GinBuildState::accum, BuildAccumulator::allocatedMemory, GinBuildState::buildStats, CHECK_FOR_INTERRUPTS, ginBeginBAScan(), ginEntryInsert(), ginGetBAEntry(), ginHeapTupleBulkInsert(), ginInitBA(), GinBuildState::ginstate, i, sort-test::list, maintenance_work_mem, MemoryContextReset(), MemoryContextSwitchTo(), tupleDesc::natts, NULL, GinState::origTupdesc, HeapTupleData::t_self, and GinBuildState::tmpCtx.

Referenced by ginbuild().

{
    GinBuildState *buildstate = (GinBuildState *) state;
    MemoryContext oldCtx;
    int         i;

    oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);

    for (i = 0; i < buildstate->ginstate.origTupdesc->natts; i++)
        ginHeapTupleBulkInsert(buildstate, (OffsetNumber) (i + 1),
                               values[i], isnull[i],
                               &htup->t_self);

    /* If we've maxed out our available memory, dump everything to the index */
    if (buildstate->accum.allocatedMemory >= maintenance_work_mem * 1024L)
    {
        ItemPointerData *list;
        Datum       key;
        GinNullCategory category;
        uint32      nlist;
        OffsetNumber attnum;

        ginBeginBAScan(&buildstate->accum);
        while ((list = ginGetBAEntry(&buildstate->accum,
                                  &attnum, &key, &category, &nlist)) != NULL)
        {
            /* there could be many entries, so be willing to abort here */
            CHECK_FOR_INTERRUPTS();
            ginEntryInsert(&buildstate->ginstate, attnum, key, category,
                           list, nlist, &buildstate->buildStats);
        }

        MemoryContextReset(buildstate->tmpCtx);
        ginInitBA(&buildstate->accum);
    }

    MemoryContextSwitchTo(oldCtx);
}

Datum ginbuildempty ( PG_FUNCTION_ARGS   ) 

Definition at line 507 of file gininsert.c.

References BUFFER_LOCK_EXCLUSIVE, END_CRIT_SECTION, GIN_LEAF, GinInitBuffer(), GinInitMetabuffer(), INIT_FORKNUM, LockBuffer(), log_newpage_buffer(), MarkBufferDirty(), NULL, P_NEW, PG_GETARG_POINTER, PG_RETURN_VOID, RBM_NORMAL, ReadBufferExtended(), START_CRIT_SECTION, and UnlockReleaseBuffer().

{
    Relation    index = (Relation) PG_GETARG_POINTER(0);
    Buffer      RootBuffer,
                MetaBuffer;

    /* An empty GIN index has two pages. */
    MetaBuffer =
        ReadBufferExtended(index, INIT_FORKNUM, P_NEW, RBM_NORMAL, NULL);
    LockBuffer(MetaBuffer, BUFFER_LOCK_EXCLUSIVE);
    RootBuffer =
        ReadBufferExtended(index, INIT_FORKNUM, P_NEW, RBM_NORMAL, NULL);
    LockBuffer(RootBuffer, BUFFER_LOCK_EXCLUSIVE);

    /* Initialize and xlog metabuffer and root buffer. */
    START_CRIT_SECTION();
    GinInitMetabuffer(MetaBuffer);
    MarkBufferDirty(MetaBuffer);
    log_newpage_buffer(MetaBuffer);
    GinInitBuffer(RootBuffer, GIN_LEAF);
    MarkBufferDirty(RootBuffer);
    log_newpage_buffer(RootBuffer);
    END_CRIT_SECTION();

    /* Unlock and release the buffers. */
    UnlockReleaseBuffer(MetaBuffer);
    UnlockReleaseBuffer(RootBuffer);

    PG_RETURN_VOID();
}

void ginEntryInsert ( GinState ginstate,
OffsetNumber  attnum,
Datum  key,
GinNullCategory  category,
ItemPointerData items,
uint32  nitem,
GinStatsData buildStats 
)

Definition at line 247 of file gininsert.c.

References addItemPointersToLeafTuple(), GinPostingTreeScan::btree, GinBtreeStack::buffer, BufferGetPage, buildFreshLeafTuple(), GinBtreeData::entry, FALSE, GinBtreeData::findItem, freeGinBtreeStack(), GIN_UNLOCK, ginFindLeafPage(), GinGetPostingTree, ginInsertItemPointers(), ginInsertValue(), GinIsPostingTree, ginPrepareEntryScan(), ginPrepareScanPostingTree(), GinState::index, GinBtreeData::isBuild, GinBtreeData::isDelete, LockBuffer(), GinStatsData::nEntries, NULL, GinBtreeStack::off, PageGetItem, PageGetItemId, and pfree().

Referenced by ginbuild(), ginBuildCallback(), ginHeapTupleInsert(), and ginInsertCleanup().

{
    GinBtreeData btree;
    GinBtreeStack *stack;
    IndexTuple  itup;
    Page        page;

    /* During index build, count the to-be-inserted entry */
    if (buildStats)
        buildStats->nEntries++;

    ginPrepareEntryScan(&btree, attnum, key, category, ginstate);

    stack = ginFindLeafPage(&btree, NULL);
    page = BufferGetPage(stack->buffer);

    if (btree.findItem(&btree, stack))
    {
        /* found pre-existing entry */
        itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stack->off));

        if (GinIsPostingTree(itup))
        {
            /* add entries to existing posting tree */
            BlockNumber rootPostingTree = GinGetPostingTree(itup);
            GinPostingTreeScan *gdi;

            /* release all stack */
            LockBuffer(stack->buffer, GIN_UNLOCK);
            freeGinBtreeStack(stack);

            /* insert into posting tree */
            gdi = ginPrepareScanPostingTree(ginstate->index, rootPostingTree, FALSE);
            gdi->btree.isBuild = (buildStats != NULL);
            ginInsertItemPointers(gdi, items, nitem, buildStats);
            pfree(gdi);

            return;
        }

        /* modify an existing leaf entry */
        itup = addItemPointersToLeafTuple(ginstate, itup,
                                          items, nitem, buildStats);

        btree.isDelete = TRUE;
    }
    else
    {
        /* no match, so construct a new leaf entry */
        itup = buildFreshLeafTuple(ginstate, attnum, key, category,
                                   items, nitem, buildStats);
    }

    /* Insert the new or modified leaf tuple */
    btree.entry = itup;
    ginInsertValue(&btree, stack, buildStats);
    pfree(itup);
}

static void ginHeapTupleBulkInsert ( GinBuildState buildstate,
OffsetNumber  attnum,
Datum  value,
bool  isNull,
ItemPointer  heapptr 
) [static]

Definition at line 316 of file gininsert.c.

References GinBuildState::accum, GinBuildState::funcCtx, ginExtractEntries(), ginInsertBAEntries(), BuildAccumulator::ginstate, GinBuildState::indtuples, MemoryContextReset(), and MemoryContextSwitchTo().

Referenced by ginBuildCallback().

{
    Datum      *entries;
    GinNullCategory *categories;
    int32       nentries;
    MemoryContext oldCtx;

    oldCtx = MemoryContextSwitchTo(buildstate->funcCtx);
    entries = ginExtractEntries(buildstate->accum.ginstate, attnum,
                                value, isNull,
                                &nentries, &categories);
    MemoryContextSwitchTo(oldCtx);

    ginInsertBAEntries(&buildstate->accum, heapptr, attnum,
                       entries, categories, nentries);

    buildstate->indtuples += nentries;

    MemoryContextReset(buildstate->funcCtx);
}

static void ginHeapTupleInsert ( GinState ginstate,
OffsetNumber  attnum,
Datum  value,
bool  isNull,
ItemPointer  item 
) [static]

Definition at line 543 of file gininsert.c.

References ginEntryInsert(), ginExtractEntries(), i, and NULL.

Referenced by gininsert().

{
    Datum      *entries;
    GinNullCategory *categories;
    int32       i,
                nentries;

    entries = ginExtractEntries(ginstate, attnum, value, isNull,
                                &nentries, &categories);

    for (i = 0; i < nentries; i++)
        ginEntryInsert(ginstate, attnum, entries[i], categories[i],
                       item, 1, NULL);
}

Datum gininsert ( PG_FUNCTION_ARGS   ) 

Definition at line 561 of file gininsert.c.

References ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, ALLOCSET_DEFAULT_MINSIZE, AllocSetContextCreate(), CurrentMemoryContext, GinGetUseFastUpdate, ginHeapTupleFastCollect(), ginHeapTupleFastInsert(), ginHeapTupleInsert(), i, initGinState(), MemoryContextDelete(), MemoryContextSwitchTo(), tupleDesc::natts, GinState::origTupdesc, PG_GETARG_INT32, PG_GETARG_POINTER, PG_RETURN_BOOL, and values.

{
    Relation    index = (Relation) PG_GETARG_POINTER(0);
    Datum      *values = (Datum *) PG_GETARG_POINTER(1);
    bool       *isnull = (bool *) PG_GETARG_POINTER(2);
    ItemPointer ht_ctid = (ItemPointer) PG_GETARG_POINTER(3);

#ifdef NOT_USED
    Relation    heapRel = (Relation) PG_GETARG_POINTER(4);
    IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
#endif
    GinState    ginstate;
    MemoryContext oldCtx;
    MemoryContext insertCtx;
    int         i;

    insertCtx = AllocSetContextCreate(CurrentMemoryContext,
                                      "Gin insert temporary context",
                                      ALLOCSET_DEFAULT_MINSIZE,
                                      ALLOCSET_DEFAULT_INITSIZE,
                                      ALLOCSET_DEFAULT_MAXSIZE);

    oldCtx = MemoryContextSwitchTo(insertCtx);

    initGinState(&ginstate, index);

    if (GinGetUseFastUpdate(index))
    {
        GinTupleCollector collector;

        memset(&collector, 0, sizeof(GinTupleCollector));

        for (i = 0; i < ginstate.origTupdesc->natts; i++)
            ginHeapTupleFastCollect(&ginstate, &collector,
                                    (OffsetNumber) (i + 1),
                                    values[i], isnull[i],
                                    ht_ctid);

        ginHeapTupleFastInsert(&ginstate, &collector);
    }
    else
    {
        for (i = 0; i < ginstate.origTupdesc->natts; i++)
            ginHeapTupleInsert(&ginstate, (OffsetNumber) (i + 1),
                               values[i], isnull[i],
                               ht_ctid);
    }

    MemoryContextSwitchTo(oldCtx);
    MemoryContextDelete(insertCtx);

    PG_RETURN_BOOL(false);
}