LCOV - code coverage report
Current view: top level - contrib/bloom - blinsert.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 93.2 % 117 109
Test Date: 2026-03-03 22:15:26 Functions: 100.0 % 7 7
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * blinsert.c
       4              :  *      Bloom index build and insert functions.
       5              :  *
       6              :  * Copyright (c) 2016-2026, PostgreSQL Global Development Group
       7              :  *
       8              :  * IDENTIFICATION
       9              :  *    contrib/bloom/blinsert.c
      10              :  *
      11              :  *-------------------------------------------------------------------------
      12              :  */
      13              : #include "postgres.h"
      14              : 
      15              : #include "access/genam.h"
      16              : #include "access/generic_xlog.h"
      17              : #include "access/tableam.h"
      18              : #include "bloom.h"
      19              : #include "miscadmin.h"
      20              : #include "nodes/execnodes.h"
      21              : #include "storage/bufmgr.h"
      22              : #include "utils/memutils.h"
      23              : #include "utils/rel.h"
      24              : 
      25          126 : PG_MODULE_MAGIC_EXT(
      26              :                     .name = "bloom",
      27              :                     .version = PG_VERSION
      28              : );
      29              : 
      30              : /*
      31              :  * State of bloom index build.  We accumulate one page data here before
      32              :  * flushing it to buffer manager.
      33              :  */
      34              : typedef struct
      35              : {
      36              :     BloomState  blstate;        /* bloom index state */
      37              :     int64       indtuples;      /* total number of tuples indexed */
      38              :     MemoryContext tmpCtx;       /* temporary memory context reset after each
      39              :                                  * tuple */
      40              :     PGAlignedBlock data;        /* cached page */
      41              :     int         count;          /* number of tuples in cached page */
      42              : } BloomBuildState;
      43              : 
      44              : /*
      45              :  * Flush page cached in BloomBuildState.
      46              :  */
      47              : static void
      48           36 : flushCachedPage(Relation index, BloomBuildState *buildstate)
      49              : {
      50              :     Page        page;
      51           36 :     Buffer      buffer = BloomNewBuffer(index);
      52              :     GenericXLogState *state;
      53              : 
      54           36 :     state = GenericXLogStart(index);
      55           36 :     page = GenericXLogRegisterBuffer(state, buffer, GENERIC_XLOG_FULL_IMAGE);
      56           36 :     memcpy(page, buildstate->data.data, BLCKSZ);
      57           36 :     GenericXLogFinish(state);
      58           36 :     UnlockReleaseBuffer(buffer);
      59           36 : }
      60              : 
      61              : /*
      62              :  * (Re)initialize cached page in BloomBuildState.
      63              :  */
      64              : static void
      65           36 : initCachedPage(BloomBuildState *buildstate)
      66              : {
      67           36 :     BloomInitPage(buildstate->data.data, 0);
      68           36 :     buildstate->count = 0;
      69           36 : }
      70              : 
      71              : /*
      72              :  * Per-tuple callback for table_index_build_scan.
      73              :  */
      74              : static void
      75        18750 : bloomBuildCallback(Relation index, ItemPointer tid, Datum *values,
      76              :                    bool *isnull, bool tupleIsAlive, void *state)
      77              : {
      78        18750 :     BloomBuildState *buildstate = (BloomBuildState *) state;
      79              :     MemoryContext oldCtx;
      80              :     BloomTuple *itup;
      81              : 
      82        18750 :     oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
      83              : 
      84        18750 :     itup = BloomFormTuple(&buildstate->blstate, tid, values, isnull);
      85              : 
      86              :     /* Try to add next item to cached page */
      87        18750 :     if (BloomPageAddItem(&buildstate->blstate, buildstate->data.data, itup))
      88              :     {
      89              :         /* Next item was added successfully */
      90        18719 :         buildstate->count++;
      91              :     }
      92              :     else
      93              :     {
      94              :         /* Cached page is full, flush it out and make a new one */
      95           31 :         flushCachedPage(index, buildstate);
      96              : 
      97           31 :         CHECK_FOR_INTERRUPTS();
      98              : 
      99           31 :         initCachedPage(buildstate);
     100              : 
     101           31 :         if (!BloomPageAddItem(&buildstate->blstate, buildstate->data.data, itup))
     102              :         {
     103              :             /* We shouldn't be here since we're inserting to the empty page */
     104            0 :             elog(ERROR, "could not add new bloom tuple to empty page");
     105              :         }
     106              : 
     107              :         /* Next item was added successfully */
     108           31 :         buildstate->count++;
     109              :     }
     110              : 
     111              :     /* Update total tuple count */
     112        18750 :     buildstate->indtuples += 1;
     113              : 
     114        18750 :     MemoryContextSwitchTo(oldCtx);
     115        18750 :     MemoryContextReset(buildstate->tmpCtx);
     116        18750 : }
     117              : 
     118              : /*
     119              :  * Build a new bloom index.
     120              :  */
     121              : IndexBuildResult *
     122            5 : blbuild(Relation heap, Relation index, IndexInfo *indexInfo)
     123              : {
     124              :     IndexBuildResult *result;
     125              :     double      reltuples;
     126              :     BloomBuildState buildstate;
     127              : 
     128            5 :     if (RelationGetNumberOfBlocks(index) != 0)
     129            0 :         elog(ERROR, "index \"%s\" already contains data",
     130              :              RelationGetRelationName(index));
     131              : 
     132              :     /* Initialize the meta page */
     133            5 :     BloomInitMetapage(index, MAIN_FORKNUM);
     134              : 
     135              :     /* Initialize the bloom build state */
     136            5 :     memset(&buildstate, 0, sizeof(buildstate));
     137            5 :     initBloomState(&buildstate.blstate, index);
     138            5 :     buildstate.tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
     139              :                                               "Bloom build temporary context",
     140              :                                               ALLOCSET_DEFAULT_SIZES);
     141            5 :     initCachedPage(&buildstate);
     142              : 
     143              :     /* Do the heap scan */
     144            5 :     reltuples = table_index_build_scan(heap, index, indexInfo, true, true,
     145              :                                        bloomBuildCallback, &buildstate,
     146              :                                        NULL);
     147              : 
     148              :     /* Flush last page if needed (it will be, unless heap was empty) */
     149            5 :     if (buildstate.count > 0)
     150            5 :         flushCachedPage(index, &buildstate);
     151              : 
     152            5 :     MemoryContextDelete(buildstate.tmpCtx);
     153              : 
     154            5 :     result = palloc_object(IndexBuildResult);
     155            5 :     result->heap_tuples = reltuples;
     156            5 :     result->index_tuples = buildstate.indtuples;
     157              : 
     158            5 :     return result;
     159              : }
     160              : 
     161              : /*
     162              :  * Build an empty bloom index in the initialization fork.
     163              :  */
     164              : void
     165            1 : blbuildempty(Relation index)
     166              : {
     167              :     /* Initialize the meta page */
     168            1 :     BloomInitMetapage(index, INIT_FORKNUM);
     169            1 : }
     170              : 
     171              : /*
     172              :  * Insert new tuple to the bloom index.
     173              :  */
     174              : bool
     175       104000 : blinsert(Relation index, Datum *values, bool *isnull,
     176              :          ItemPointer ht_ctid, Relation heapRel,
     177              :          IndexUniqueCheck checkUnique,
     178              :          bool indexUnchanged,
     179              :          IndexInfo *indexInfo)
     180              : {
     181              :     BloomState  blstate;
     182              :     BloomTuple *itup;
     183              :     MemoryContext oldCtx;
     184              :     MemoryContext insertCtx;
     185              :     BloomMetaPageData *metaData;
     186              :     Buffer      buffer,
     187              :                 metaBuffer;
     188              :     Page        page,
     189              :                 metaPage;
     190       104000 :     BlockNumber blkno = InvalidBlockNumber;
     191              :     OffsetNumber nStart;
     192              :     GenericXLogState *state;
     193              : 
     194       104000 :     insertCtx = AllocSetContextCreate(CurrentMemoryContext,
     195              :                                       "Bloom insert temporary context",
     196              :                                       ALLOCSET_DEFAULT_SIZES);
     197              : 
     198       104000 :     oldCtx = MemoryContextSwitchTo(insertCtx);
     199              : 
     200       104000 :     initBloomState(&blstate, index);
     201       104000 :     itup = BloomFormTuple(&blstate, ht_ctid, values, isnull);
     202              : 
     203              :     /*
     204              :      * At first, try to insert new tuple to the first page in notFullPage
     205              :      * array.  If successful, we don't need to modify the meta page.
     206              :      */
     207       104000 :     metaBuffer = ReadBuffer(index, BLOOM_METAPAGE_BLKNO);
     208       104000 :     LockBuffer(metaBuffer, BUFFER_LOCK_SHARE);
     209       104000 :     metaData = BloomPageGetMeta(BufferGetPage(metaBuffer));
     210              : 
     211       104000 :     if (metaData->nEnd > metaData->nStart)
     212              :     {
     213       103999 :         blkno = metaData->notFullPage[metaData->nStart];
     214              :         Assert(blkno != InvalidBlockNumber);
     215              : 
     216              :         /* Don't hold metabuffer lock while doing insert */
     217       103999 :         LockBuffer(metaBuffer, BUFFER_LOCK_UNLOCK);
     218              : 
     219       103999 :         buffer = ReadBuffer(index, blkno);
     220       103999 :         LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
     221              : 
     222       103999 :         state = GenericXLogStart(index);
     223       103999 :         page = GenericXLogRegisterBuffer(state, buffer, 0);
     224              : 
     225              :         /*
     226              :          * We might have found a page that was recently deleted by VACUUM.  If
     227              :          * so, we can reuse it, but we must reinitialize it.
     228              :          */
     229       103999 :         if (PageIsNew(page) || BloomPageIsDeleted(page))
     230            0 :             BloomInitPage(page, 0);
     231              : 
     232       103999 :         if (BloomPageAddItem(&blstate, page, itup))
     233              :         {
     234              :             /* Success!  Apply the change, clean up, and exit */
     235       103242 :             GenericXLogFinish(state);
     236       103242 :             UnlockReleaseBuffer(buffer);
     237       103242 :             ReleaseBuffer(metaBuffer);
     238       103242 :             MemoryContextSwitchTo(oldCtx);
     239       103242 :             MemoryContextDelete(insertCtx);
     240       103242 :             return false;
     241              :         }
     242              : 
     243              :         /* Didn't fit, must try other pages */
     244          757 :         GenericXLogAbort(state);
     245          757 :         UnlockReleaseBuffer(buffer);
     246              :     }
     247              :     else
     248              :     {
     249              :         /* No entries in notFullPage */
     250            1 :         LockBuffer(metaBuffer, BUFFER_LOCK_UNLOCK);
     251              :     }
     252              : 
     253              :     /*
     254              :      * Try other pages in notFullPage array.  We will have to change nStart in
     255              :      * metapage.  Thus, grab exclusive lock on metapage.
     256              :      */
     257          758 :     LockBuffer(metaBuffer, BUFFER_LOCK_EXCLUSIVE);
     258              : 
     259              :     /* nStart might have changed while we didn't have lock */
     260          758 :     nStart = metaData->nStart;
     261              : 
     262              :     /* Skip first page if we already tried it above */
     263          758 :     if (nStart < metaData->nEnd &&
     264          757 :         blkno == metaData->notFullPage[nStart])
     265          757 :         nStart++;
     266              : 
     267              :     /*
     268              :      * This loop iterates for each page we try from the notFullPage array, and
     269              :      * will also initialize a GenericXLogState for the fallback case of having
     270              :      * to allocate a new page.
     271              :      */
     272              :     for (;;)
     273              :     {
     274          758 :         state = GenericXLogStart(index);
     275              : 
     276              :         /* get modifiable copy of metapage */
     277          758 :         metaPage = GenericXLogRegisterBuffer(state, metaBuffer, 0);
     278          758 :         metaData = BloomPageGetMeta(metaPage);
     279              : 
     280          758 :         if (nStart >= metaData->nEnd)
     281          113 :             break;              /* no more entries in notFullPage array */
     282              : 
     283          645 :         blkno = metaData->notFullPage[nStart];
     284              :         Assert(blkno != InvalidBlockNumber);
     285              : 
     286          645 :         buffer = ReadBuffer(index, blkno);
     287          645 :         LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
     288          645 :         page = GenericXLogRegisterBuffer(state, buffer, 0);
     289              : 
     290              :         /* Basically same logic as above */
     291          645 :         if (PageIsNew(page) || BloomPageIsDeleted(page))
     292            0 :             BloomInitPage(page, 0);
     293              : 
     294          645 :         if (BloomPageAddItem(&blstate, page, itup))
     295              :         {
     296              :             /* Success!  Apply the changes, clean up, and exit */
     297          645 :             metaData->nStart = nStart;
     298          645 :             GenericXLogFinish(state);
     299          645 :             UnlockReleaseBuffer(buffer);
     300          645 :             UnlockReleaseBuffer(metaBuffer);
     301          645 :             MemoryContextSwitchTo(oldCtx);
     302          645 :             MemoryContextDelete(insertCtx);
     303          645 :             return false;
     304              :         }
     305              : 
     306              :         /* Didn't fit, must try other pages */
     307            0 :         GenericXLogAbort(state);
     308            0 :         UnlockReleaseBuffer(buffer);
     309            0 :         nStart++;
     310              :     }
     311              : 
     312              :     /*
     313              :      * Didn't find place to insert in notFullPage array.  Allocate new page.
     314              :      * (XXX is it good to do this while holding ex-lock on the metapage??)
     315              :      */
     316          113 :     buffer = BloomNewBuffer(index);
     317              : 
     318          113 :     page = GenericXLogRegisterBuffer(state, buffer, GENERIC_XLOG_FULL_IMAGE);
     319          113 :     BloomInitPage(page, 0);
     320              : 
     321          113 :     if (!BloomPageAddItem(&blstate, page, itup))
     322              :     {
     323              :         /* We shouldn't be here since we're inserting to an empty page */
     324            0 :         elog(ERROR, "could not add new bloom tuple to empty page");
     325              :     }
     326              : 
     327              :     /* Reset notFullPage array to contain just this new page */
     328          113 :     metaData->nStart = 0;
     329          113 :     metaData->nEnd = 1;
     330          113 :     metaData->notFullPage[0] = BufferGetBlockNumber(buffer);
     331              : 
     332              :     /* Apply the changes, clean up, and exit */
     333          113 :     GenericXLogFinish(state);
     334              : 
     335          113 :     UnlockReleaseBuffer(buffer);
     336          113 :     UnlockReleaseBuffer(metaBuffer);
     337              : 
     338          113 :     MemoryContextSwitchTo(oldCtx);
     339          113 :     MemoryContextDelete(insertCtx);
     340              : 
     341          113 :     return false;
     342              : }
        

Generated by: LCOV version 2.0-1