Header And Logo

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

Data Structures | Defines | Typedefs | Enumerations | Functions

spgist.h File Reference

#include "access/skey.h"
#include "access/xlog.h"
#include "fmgr.h"
Include dependency graph for spgist.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  spgConfigIn
struct  spgConfigOut
struct  spgChooseIn
struct  spgChooseOut
struct  spgPickSplitIn
struct  spgPickSplitOut
struct  spgInnerConsistentIn
struct  spgInnerConsistentOut
struct  spgLeafConsistentIn
struct  spgLeafConsistentOut

Defines

#define SPGIST_MIN_FILLFACTOR   10
#define SPGIST_DEFAULT_FILLFACTOR   80
#define SPGIST_CONFIG_PROC   1
#define SPGIST_CHOOSE_PROC   2
#define SPGIST_PICKSPLIT_PROC   3
#define SPGIST_INNER_CONSISTENT_PROC   4
#define SPGIST_LEAF_CONSISTENT_PROC   5
#define SPGISTNProc   5

Typedefs

typedef struct spgConfigIn spgConfigIn
typedef struct spgConfigOut spgConfigOut
typedef struct spgChooseIn spgChooseIn
typedef enum spgChooseResultType spgChooseResultType
typedef struct spgChooseOut spgChooseOut
typedef struct spgPickSplitIn spgPickSplitIn
typedef struct spgPickSplitOut spgPickSplitOut
typedef struct spgInnerConsistentIn spgInnerConsistentIn
typedef struct
spgInnerConsistentOut 
spgInnerConsistentOut
typedef struct spgLeafConsistentIn spgLeafConsistentIn
typedef struct spgLeafConsistentOut spgLeafConsistentOut

Enumerations

enum  spgChooseResultType { spgMatchNode = 1, spgAddNode, spgSplitTuple }

Functions

Datum spgbuild (PG_FUNCTION_ARGS)
Datum spgbuildempty (PG_FUNCTION_ARGS)
Datum spginsert (PG_FUNCTION_ARGS)
Datum spgbeginscan (PG_FUNCTION_ARGS)
Datum spgendscan (PG_FUNCTION_ARGS)
Datum spgrescan (PG_FUNCTION_ARGS)
Datum spgmarkpos (PG_FUNCTION_ARGS)
Datum spgrestrpos (PG_FUNCTION_ARGS)
Datum spggetbitmap (PG_FUNCTION_ARGS)
Datum spggettuple (PG_FUNCTION_ARGS)
Datum spgcanreturn (PG_FUNCTION_ARGS)
Datum spgoptions (PG_FUNCTION_ARGS)
Datum spgbulkdelete (PG_FUNCTION_ARGS)
Datum spgvacuumcleanup (PG_FUNCTION_ARGS)
void spg_redo (XLogRecPtr lsn, XLogRecord *record)
void spg_desc (StringInfo buf, uint8 xl_info, char *rec)
void spg_xlog_startup (void)
void spg_xlog_cleanup (void)

Define Documentation

#define SPGIST_CHOOSE_PROC   2

Definition at line 28 of file spgist.h.

Referenced by spgdoinsert().

#define SPGIST_CONFIG_PROC   1

Definition at line 27 of file spgist.h.

Referenced by spgGetCache().

#define SPGIST_DEFAULT_FILLFACTOR   80

Definition at line 24 of file spgist.h.

Referenced by SpGistGetBuffer().

#define SPGIST_INNER_CONSISTENT_PROC   4

Definition at line 30 of file spgist.h.

Referenced by spgWalk().

#define SPGIST_LEAF_CONSISTENT_PROC   5

Definition at line 31 of file spgist.h.

Referenced by spgLeafTest().

#define SPGIST_MIN_FILLFACTOR   10

Definition at line 23 of file spgist.h.

#define SPGIST_PICKSPLIT_PROC   3

Definition at line 29 of file spgist.h.

Referenced by doPickSplit().

#define SPGISTNProc   5

Definition at line 32 of file spgist.h.


Typedef Documentation

typedef struct spgChooseIn spgChooseIn
typedef struct spgChooseOut spgChooseOut
typedef struct spgConfigIn spgConfigIn
typedef struct spgConfigOut spgConfigOut

Enumeration Type Documentation

Enumerator:
spgMatchNode 
spgAddNode 
spgSplitTuple 

Definition at line 67 of file spgist.h.

{
    spgMatchNode = 1,           /* descend into existing node */
    spgAddNode,                 /* add a node to the inner tuple */
    spgSplitTuple               /* split inner tuple (change its prefix) */
} spgChooseResultType;


Function Documentation

void spg_desc ( StringInfo  buf,
uint8  xl_info,
char *  rec 
)

Definition at line 27 of file spgdesc.c.

References appendStringInfo(), out_target(), XLOG_SPGIST_ADD_LEAF, XLOG_SPGIST_ADD_NODE, XLOG_SPGIST_CREATE_INDEX, XLOG_SPGIST_MOVE_LEAFS, XLOG_SPGIST_PICKSPLIT, XLOG_SPGIST_SPLIT_TUPLE, XLOG_SPGIST_VACUUM_LEAF, XLOG_SPGIST_VACUUM_REDIRECT, and XLOG_SPGIST_VACUUM_ROOT.

{
    uint8       info = xl_info & ~XLR_INFO_MASK;

    switch (info)
    {
        case XLOG_SPGIST_CREATE_INDEX:
            appendStringInfo(buf, "create_index: rel %u/%u/%u",
                             ((RelFileNode *) rec)->spcNode,
                             ((RelFileNode *) rec)->dbNode,
                             ((RelFileNode *) rec)->relNode);
            break;
        case XLOG_SPGIST_ADD_LEAF:
            out_target(buf, ((spgxlogAddLeaf *) rec)->node);
            appendStringInfo(buf, "add leaf to page: %u",
                             ((spgxlogAddLeaf *) rec)->blknoLeaf);
            break;
        case XLOG_SPGIST_MOVE_LEAFS:
            out_target(buf, ((spgxlogMoveLeafs *) rec)->node);
            appendStringInfo(buf, "move %u leafs from page %u to page %u",
                             ((spgxlogMoveLeafs *) rec)->nMoves,
                             ((spgxlogMoveLeafs *) rec)->blknoSrc,
                             ((spgxlogMoveLeafs *) rec)->blknoDst);
            break;
        case XLOG_SPGIST_ADD_NODE:
            out_target(buf, ((spgxlogAddNode *) rec)->node);
            appendStringInfo(buf, "add node to %u:%u",
                             ((spgxlogAddNode *) rec)->blkno,
                             ((spgxlogAddNode *) rec)->offnum);
            break;
        case XLOG_SPGIST_SPLIT_TUPLE:
            out_target(buf, ((spgxlogSplitTuple *) rec)->node);
            appendStringInfo(buf, "split node %u:%u to %u:%u",
                             ((spgxlogSplitTuple *) rec)->blknoPrefix,
                             ((spgxlogSplitTuple *) rec)->offnumPrefix,
                             ((spgxlogSplitTuple *) rec)->blknoPostfix,
                             ((spgxlogSplitTuple *) rec)->offnumPostfix);
            break;
        case XLOG_SPGIST_PICKSPLIT:
            out_target(buf, ((spgxlogPickSplit *) rec)->node);
            appendStringInfo(buf, "split leaf page");
            break;
        case XLOG_SPGIST_VACUUM_LEAF:
            out_target(buf, ((spgxlogVacuumLeaf *) rec)->node);
            appendStringInfo(buf, "vacuum leaf tuples on page %u",
                             ((spgxlogVacuumLeaf *) rec)->blkno);
            break;
        case XLOG_SPGIST_VACUUM_ROOT:
            out_target(buf, ((spgxlogVacuumRoot *) rec)->node);
            appendStringInfo(buf, "vacuum leaf tuples on root page %u",
                             ((spgxlogVacuumRoot *) rec)->blkno);
            break;
        case XLOG_SPGIST_VACUUM_REDIRECT:
            out_target(buf, ((spgxlogVacuumRedirect *) rec)->node);
            appendStringInfo(buf, "vacuum redirect tuples on page %u, newest XID %u",
                             ((spgxlogVacuumRedirect *) rec)->blkno,
                         ((spgxlogVacuumRedirect *) rec)->newestRedirectXid);
            break;
        default:
            appendStringInfo(buf, "unknown spgist op code %u", info);
            break;
    }
}

void spg_redo ( XLogRecPtr  lsn,
XLogRecord record 
)
void spg_xlog_cleanup ( void   ) 

Definition at line 1106 of file spgxlog.c.

References MemoryContextDelete().

void spg_xlog_startup ( void   ) 
Datum spgbeginscan ( PG_FUNCTION_ARGS   ) 
Datum spgbuild ( PG_FUNCTION_ARGS   ) 

Definition at line 58 of file spginsert.c.

References ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, ALLOCSET_DEFAULT_MINSIZE, AllocSetContextCreate(), Assert, XLogRecData::buffer, BufferGetBlockNumber(), BufferGetPage, CurrentMemoryContext, XLogRecData::data, elog, END_CRIT_SECTION, ERROR, IndexBuildResult::heap_tuples, IndexBuildResult::index_tuples, IndexBuildHeapScan(), initSpGistState(), SpGistState::isBuild, XLogRecData::len, MarkBufferDirty(), MemoryContextDelete(), XLogRecData::next, PageSetLSN, palloc0(), PG_GETARG_POINTER, PG_RETURN_POINTER, RelationData::rd_node, RelationGetNumberOfBlocks, RelationGetRelationName, RelationNeedsWAL, SPGIST_LEAF, SPGIST_METAPAGE_BLKNO, SPGIST_NULL_BLKNO, SPGIST_NULLS, SPGIST_ROOT_BLKNO, spgistBuildCallback(), SpGistInitBuffer(), SpGistInitMetapage(), SpGistNewBuffer(), SpGistUpdateMetaPage(), SpGistBuildState::spgstate, START_CRIT_SECTION, SpGistBuildState::tmpCtx, UnlockReleaseBuffer(), XLOG_SPGIST_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;
    SpGistBuildState buildstate;
    Buffer      metabuffer,
                rootbuffer,
                nullbuffer;

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

    /*
     * Initialize the meta page and root pages
     */
    metabuffer = SpGistNewBuffer(index);
    rootbuffer = SpGistNewBuffer(index);
    nullbuffer = SpGistNewBuffer(index);

    Assert(BufferGetBlockNumber(metabuffer) == SPGIST_METAPAGE_BLKNO);
    Assert(BufferGetBlockNumber(rootbuffer) == SPGIST_ROOT_BLKNO);
    Assert(BufferGetBlockNumber(nullbuffer) == SPGIST_NULL_BLKNO);

    START_CRIT_SECTION();

    SpGistInitMetapage(BufferGetPage(metabuffer));
    MarkBufferDirty(metabuffer);
    SpGistInitBuffer(rootbuffer, SPGIST_LEAF);
    MarkBufferDirty(rootbuffer);
    SpGistInitBuffer(nullbuffer, SPGIST_LEAF | SPGIST_NULLS);
    MarkBufferDirty(nullbuffer);

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

        /* WAL data is just the relfilenode */
        rdata.data = (char *) &(index->rd_node);
        rdata.len = sizeof(RelFileNode);
        rdata.buffer = InvalidBuffer;
        rdata.next = NULL;

        recptr = XLogInsert(RM_SPGIST_ID, XLOG_SPGIST_CREATE_INDEX, &rdata);

        PageSetLSN(BufferGetPage(metabuffer), recptr);
        PageSetLSN(BufferGetPage(rootbuffer), recptr);
        PageSetLSN(BufferGetPage(nullbuffer), recptr);
    }

    END_CRIT_SECTION();

    UnlockReleaseBuffer(metabuffer);
    UnlockReleaseBuffer(rootbuffer);
    UnlockReleaseBuffer(nullbuffer);

    /*
     * Now insert all the heap data into the index
     */
    initSpGistState(&buildstate.spgstate, index);
    buildstate.spgstate.isBuild = true;

    buildstate.tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
                                           "SP-GiST build temporary context",
                                              ALLOCSET_DEFAULT_MINSIZE,
                                              ALLOCSET_DEFAULT_INITSIZE,
                                              ALLOCSET_DEFAULT_MAXSIZE);

    reltuples = IndexBuildHeapScan(heap, index, indexInfo, true,
                                   spgistBuildCallback, (void *) &buildstate);

    MemoryContextDelete(buildstate.tmpCtx);

    SpGistUpdateMetaPage(index);

    result = (IndexBuildResult *) palloc0(sizeof(IndexBuildResult));
    result->heap_tuples = result->index_tuples = reltuples;

    PG_RETURN_POINTER(result);
}

Datum spgbuildempty ( PG_FUNCTION_ARGS   ) 

Definition at line 147 of file spginsert.c.

References INIT_FORKNUM, log_newpage(), RelFileNodeBackend::node, PageSetChecksumInplace(), palloc(), PG_GETARG_POINTER, PG_RETURN_VOID, RelationData::rd_smgr, SMgrRelationData::smgr_rnode, smgrimmedsync(), smgrwrite(), SPGIST_LEAF, SPGIST_METAPAGE_BLKNO, SPGIST_NULL_BLKNO, SPGIST_NULLS, SPGIST_ROOT_BLKNO, SpGistInitMetapage(), SpGistInitPage(), and XLogIsNeeded.

{
    Relation    index = (Relation) PG_GETARG_POINTER(0);
    Page        page;

    /* Construct metapage. */
    page = (Page) palloc(BLCKSZ);
    SpGistInitMetapage(page);

    /* Write the page.  If archiving/streaming, XLOG it. */
    PageSetChecksumInplace(page, SPGIST_METAPAGE_BLKNO);
    smgrwrite(index->rd_smgr, INIT_FORKNUM, SPGIST_METAPAGE_BLKNO,
              (char *) page, true);
    if (XLogIsNeeded())
        log_newpage(&index->rd_smgr->smgr_rnode.node, INIT_FORKNUM,
                    SPGIST_METAPAGE_BLKNO, page);

    /* Likewise for the root page. */
    SpGistInitPage(page, SPGIST_LEAF);

    PageSetChecksumInplace(page, SPGIST_ROOT_BLKNO);
    smgrwrite(index->rd_smgr, INIT_FORKNUM, SPGIST_ROOT_BLKNO,
              (char *) page, true);
    if (XLogIsNeeded())
        log_newpage(&index->rd_smgr->smgr_rnode.node, INIT_FORKNUM,
                    SPGIST_ROOT_BLKNO, page);

    /* Likewise for the null-tuples root page. */
    SpGistInitPage(page, SPGIST_LEAF | SPGIST_NULLS);

    PageSetChecksumInplace(page, SPGIST_NULL_BLKNO);
    smgrwrite(index->rd_smgr, INIT_FORKNUM, SPGIST_NULL_BLKNO,
              (char *) page, true);
    if (XLogIsNeeded())
        log_newpage(&index->rd_smgr->smgr_rnode.node, INIT_FORKNUM,
                    SPGIST_NULL_BLKNO, page);

    /*
     * An immediate sync is required even if we xlog'd the pages, because the
     * writes did not go through shared buffers and therefore a concurrent
     * checkpoint may have moved the redo pointer past our xlog record.
     */
    smgrimmedsync(index->rd_smgr, INIT_FORKNUM);

    PG_RETURN_VOID();
}

Datum spgbulkdelete ( PG_FUNCTION_ARGS   ) 

Definition at line 894 of file spgvacuum.c.

References spgBulkDeleteState::callback, callback(), spgBulkDeleteState::callback_state, spgBulkDeleteState::info, NULL, palloc0(), PG_GETARG_POINTER, PG_RETURN_POINTER, spgvacuumscan(), and spgBulkDeleteState::stats.

{
    IndexVacuumInfo *info = (IndexVacuumInfo *) PG_GETARG_POINTER(0);
    IndexBulkDeleteResult *stats = (IndexBulkDeleteResult *) PG_GETARG_POINTER(1);
    IndexBulkDeleteCallback callback = (IndexBulkDeleteCallback) PG_GETARG_POINTER(2);
    void       *callback_state = (void *) PG_GETARG_POINTER(3);
    spgBulkDeleteState bds;

    /* allocate stats if first time through, else re-use existing struct */
    if (stats == NULL)
        stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
    bds.info = info;
    bds.stats = stats;
    bds.callback = callback;
    bds.callback_state = callback_state;

    spgvacuumscan(&bds);

    PG_RETURN_POINTER(stats);
}

Datum spgcanreturn ( PG_FUNCTION_ARGS   ) 

Definition at line 658 of file spgscan.c.

References spgConfigOut::canReturnData, SpGistCache::config, PG_GETARG_POINTER, PG_RETURN_BOOL, and spgGetCache().

{
    Relation    index = (Relation) PG_GETARG_POINTER(0);
    SpGistCache *cache;

    /* We can do it if the opclass config function says so */
    cache = spgGetCache(index);

    PG_RETURN_BOOL(cache->config.canReturnData);
}

Datum spgendscan ( PG_FUNCTION_ARGS   ) 
Datum spggetbitmap ( PG_FUNCTION_ARGS   ) 
Datum spggettuple ( PG_FUNCTION_ARGS   ) 

Definition at line 614 of file spgscan.c.

References elog, ERROR, ForwardScanDirection, SpGistScanOpaqueData::heapPtrs, i, IndexScanDescData::indexRelation, SpGistScanOpaqueData::indexTups, SpGistScanOpaqueData::iPtr, SpGistScanOpaqueData::nPtrs, IndexScanDescData::opaque, pfree(), PG_GETARG_INT32, PG_GETARG_POINTER, PG_RETURN_BOOL, SpGistScanOpaqueData::recheck, spgWalk(), storeGettuple(), HeapTupleData::t_self, SpGistScanOpaqueData::want_itup, IndexScanDescData::xs_ctup, IndexScanDescData::xs_itup, IndexScanDescData::xs_recheck, and IndexScanDescData::xs_want_itup.

{
    IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
    ScanDirection dir = (ScanDirection) PG_GETARG_INT32(1);
    SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;

    if (dir != ForwardScanDirection)
        elog(ERROR, "SP-GiST only supports forward scan direction");

    /* Copy want_itup to *so so we don't need to pass it around separately */
    so->want_itup = scan->xs_want_itup;

    for (;;)
    {
        if (so->iPtr < so->nPtrs)
        {
            /* continuing to return tuples from a leaf page */
            scan->xs_ctup.t_self = so->heapPtrs[so->iPtr];
            scan->xs_recheck = so->recheck[so->iPtr];
            scan->xs_itup = so->indexTups[so->iPtr];
            so->iPtr++;
            PG_RETURN_BOOL(true);
        }

        if (so->want_itup)
        {
            /* Must pfree IndexTuples to avoid memory leak */
            int         i;

            for (i = 0; i < so->nPtrs; i++)
                pfree(so->indexTups[i]);
        }
        so->iPtr = so->nPtrs = 0;

        spgWalk(scan->indexRelation, so, false, storeGettuple);

        if (so->nPtrs == 0)
            break;              /* must have completed scan */
    }

    PG_RETURN_BOOL(false);
}

Datum spginsert ( PG_FUNCTION_ARGS   ) 

Definition at line 198 of file spginsert.c.

References ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, ALLOCSET_DEFAULT_MINSIZE, AllocSetContextCreate(), CurrentMemoryContext, initSpGistState(), MemoryContextDelete(), MemoryContextSwitchTo(), PG_GETARG_INT32, PG_GETARG_POINTER, PG_RETURN_BOOL, spgdoinsert(), SpGistUpdateMetaPage(), 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
    SpGistState spgstate;
    MemoryContext oldCtx;
    MemoryContext insertCtx;

    insertCtx = AllocSetContextCreate(CurrentMemoryContext,
                                      "SP-GiST insert temporary context",
                                      ALLOCSET_DEFAULT_MINSIZE,
                                      ALLOCSET_DEFAULT_INITSIZE,
                                      ALLOCSET_DEFAULT_MAXSIZE);
    oldCtx = MemoryContextSwitchTo(insertCtx);

    initSpGistState(&spgstate, index);

    spgdoinsert(index, &spgstate, ht_ctid, *values, *isnull);

    SpGistUpdateMetaPage(index);

    MemoryContextSwitchTo(oldCtx);
    MemoryContextDelete(insertCtx);

    /* return false since we've not done any unique check */
    PG_RETURN_BOOL(false);
}

Datum spgmarkpos ( PG_FUNCTION_ARGS   ) 

Definition at line 243 of file spgscan.c.

References elog, ERROR, and PG_RETURN_VOID.

{
    elog(ERROR, "SPGiST does not support mark/restore");
    PG_RETURN_VOID();
}

Datum spgoptions ( PG_FUNCTION_ARGS   ) 

Definition at line 493 of file spgutils.c.

References default_reloptions(), PG_GETARG_BOOL, PG_GETARG_DATUM, PG_RETURN_BYTEA_P, PG_RETURN_NULL, and RELOPT_KIND_SPGIST.

{
    Datum       reloptions = PG_GETARG_DATUM(0);
    bool        validate = PG_GETARG_BOOL(1);
    bytea      *result;

    result = default_reloptions(reloptions, validate, RELOPT_KIND_SPGIST);

    if (result)
        PG_RETURN_BYTEA_P(result);
    PG_RETURN_NULL();
}

Datum spgrescan ( PG_FUNCTION_ARGS   ) 

Definition at line 209 of file spgscan.c.

References IndexScanDescData::keyData, memmove, IndexScanDescData::numberOfKeys, IndexScanDescData::opaque, PG_GETARG_POINTER, PG_RETURN_VOID, resetSpGistScanOpaque(), and spgPrepareScanKeys().

{
    IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
    SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
    ScanKey     scankey = (ScanKey) PG_GETARG_POINTER(1);

    /* copy scankeys into local storage */
    if (scankey && scan->numberOfKeys > 0)
    {
        memmove(scan->keyData, scankey,
                scan->numberOfKeys * sizeof(ScanKeyData));
    }

    /* preprocess scankeys, set up the representation in *so */
    spgPrepareScanKeys(scan);

    /* set up starting stack entries */
    resetSpGistScanOpaque(so);

    PG_RETURN_VOID();
}

Datum spgrestrpos ( PG_FUNCTION_ARGS   ) 

Definition at line 250 of file spgscan.c.

References elog, ERROR, and PG_RETURN_VOID.

{
    elog(ERROR, "SPGiST does not support mark/restore");
    PG_RETURN_VOID();
}

Datum spgvacuumcleanup ( PG_FUNCTION_ARGS   ) 

Definition at line 928 of file spgvacuum.c.

References IndexVacuumInfo::analyze_only, spgBulkDeleteState::callback, spgBulkDeleteState::callback_state, IndexVacuumInfo::estimated_count, IndexVacuumInfo::index, IndexFreeSpaceMapVacuum(), spgBulkDeleteState::info, NULL, IndexVacuumInfo::num_heap_tuples, IndexBulkDeleteResult::num_index_tuples, palloc0(), PG_GETARG_POINTER, PG_RETURN_POINTER, spgvacuumscan(), and spgBulkDeleteState::stats.

{
    IndexVacuumInfo *info = (IndexVacuumInfo *) PG_GETARG_POINTER(0);
    IndexBulkDeleteResult *stats = (IndexBulkDeleteResult *) PG_GETARG_POINTER(1);
    Relation    index = info->index;
    spgBulkDeleteState bds;

    /* No-op in ANALYZE ONLY mode */
    if (info->analyze_only)
        PG_RETURN_POINTER(stats);

    /*
     * We don't need to scan the index if there was a preceding bulkdelete
     * pass.  Otherwise, make a pass that won't delete any live tuples, but
     * might still accomplish useful stuff with redirect/placeholder cleanup,
     * and in any case will provide stats.
     */
    if (stats == NULL)
    {
        stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
        bds.info = info;
        bds.stats = stats;
        bds.callback = dummy_callback;
        bds.callback_state = NULL;

        spgvacuumscan(&bds);
    }

    /* Finally, vacuum the FSM */
    IndexFreeSpaceMapVacuum(index);

    /*
     * It's quite possible for us to be fooled by concurrent tuple moves into
     * double-counting some index tuples, so disbelieve any total that exceeds
     * the underlying heap's count ... if we know that accurately.  Otherwise
     * this might just make matters worse.
     */
    if (!info->estimated_count)
    {
        if (stats->num_index_tuples > info->num_heap_tuples)
            stats->num_index_tuples = info->num_heap_tuples;
    }

    PG_RETURN_POINTER(stats);
}