LCOV - code coverage report
Current view: top level - contrib/bloom - blinsert.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 109 117 93.2 %
Date: 2025-04-01 14:15:22 Functions: 7 7 100.0 %
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-2025, 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         190 : 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          72 : flushCachedPage(Relation index, BloomBuildState *buildstate)
      49             : {
      50             :     Page        page;
      51          72 :     Buffer      buffer = BloomNewBuffer(index);
      52             :     GenericXLogState *state;
      53             : 
      54          72 :     state = GenericXLogStart(index);
      55          72 :     page = GenericXLogRegisterBuffer(state, buffer, GENERIC_XLOG_FULL_IMAGE);
      56          72 :     memcpy(page, buildstate->data.data, BLCKSZ);
      57          72 :     GenericXLogFinish(state);
      58          72 :     UnlockReleaseBuffer(buffer);
      59          72 : }
      60             : 
      61             : /*
      62             :  * (Re)initialize cached page in BloomBuildState.
      63             :  */
      64             : static void
      65          72 : initCachedPage(BloomBuildState *buildstate)
      66             : {
      67          72 :     BloomInitPage(buildstate->data.data, 0);
      68          72 :     buildstate->count = 0;
      69          72 : }
      70             : 
      71             : /*
      72             :  * Per-tuple callback for table_index_build_scan.
      73             :  */
      74             : static void
      75       37500 : bloomBuildCallback(Relation index, ItemPointer tid, Datum *values,
      76             :                    bool *isnull, bool tupleIsAlive, void *state)
      77             : {
      78       37500 :     BloomBuildState *buildstate = (BloomBuildState *) state;
      79             :     MemoryContext oldCtx;
      80             :     BloomTuple *itup;
      81             : 
      82       37500 :     oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
      83             : 
      84       37500 :     itup = BloomFormTuple(&buildstate->blstate, tid, values, isnull);
      85             : 
      86             :     /* Try to add next item to cached page */
      87       37500 :     if (BloomPageAddItem(&buildstate->blstate, buildstate->data.data, itup))
      88             :     {
      89             :         /* Next item was added successfully */
      90       37438 :         buildstate->count++;
      91             :     }
      92             :     else
      93             :     {
      94             :         /* Cached page is full, flush it out and make a new one */
      95          62 :         flushCachedPage(index, buildstate);
      96             : 
      97          62 :         CHECK_FOR_INTERRUPTS();
      98             : 
      99          62 :         initCachedPage(buildstate);
     100             : 
     101          62 :         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          62 :         buildstate->count++;
     109             :     }
     110             : 
     111             :     /* Update total tuple count */
     112       37500 :     buildstate->indtuples += 1;
     113             : 
     114       37500 :     MemoryContextSwitchTo(oldCtx);
     115       37500 :     MemoryContextReset(buildstate->tmpCtx);
     116       37500 : }
     117             : 
     118             : /*
     119             :  * Build a new bloom index.
     120             :  */
     121             : IndexBuildResult *
     122          10 : blbuild(Relation heap, Relation index, IndexInfo *indexInfo)
     123             : {
     124             :     IndexBuildResult *result;
     125             :     double      reltuples;
     126             :     BloomBuildState buildstate;
     127             : 
     128          10 :     if (RelationGetNumberOfBlocks(index) != 0)
     129           0 :         elog(ERROR, "index \"%s\" already contains data",
     130             :              RelationGetRelationName(index));
     131             : 
     132             :     /* Initialize the meta page */
     133          10 :     BloomInitMetapage(index, MAIN_FORKNUM);
     134             : 
     135             :     /* Initialize the bloom build state */
     136          10 :     memset(&buildstate, 0, sizeof(buildstate));
     137          10 :     initBloomState(&buildstate.blstate, index);
     138          10 :     buildstate.tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
     139             :                                               "Bloom build temporary context",
     140             :                                               ALLOCSET_DEFAULT_SIZES);
     141          10 :     initCachedPage(&buildstate);
     142             : 
     143             :     /* Do the heap scan */
     144          10 :     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          10 :     if (buildstate.count > 0)
     150          10 :         flushCachedPage(index, &buildstate);
     151             : 
     152          10 :     MemoryContextDelete(buildstate.tmpCtx);
     153             : 
     154          10 :     result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));
     155          10 :     result->heap_tuples = reltuples;
     156          10 :     result->index_tuples = buildstate.indtuples;
     157             : 
     158          10 :     return result;
     159             : }
     160             : 
     161             : /*
     162             :  * Build an empty bloom index in the initialization fork.
     163             :  */
     164             : void
     165           2 : blbuildempty(Relation index)
     166             : {
     167             :     /* Initialize the meta page */
     168           2 :     BloomInitMetapage(index, INIT_FORKNUM);
     169           2 : }
     170             : 
     171             : /*
     172             :  * Insert new tuple to the bloom index.
     173             :  */
     174             : bool
     175      208000 : 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      208000 :     BlockNumber blkno = InvalidBlockNumber;
     191             :     OffsetNumber nStart;
     192             :     GenericXLogState *state;
     193             : 
     194      208000 :     insertCtx = AllocSetContextCreate(CurrentMemoryContext,
     195             :                                       "Bloom insert temporary context",
     196             :                                       ALLOCSET_DEFAULT_SIZES);
     197             : 
     198      208000 :     oldCtx = MemoryContextSwitchTo(insertCtx);
     199             : 
     200      208000 :     initBloomState(&blstate, index);
     201      208000 :     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      208000 :     metaBuffer = ReadBuffer(index, BLOOM_METAPAGE_BLKNO);
     208      208000 :     LockBuffer(metaBuffer, BUFFER_LOCK_SHARE);
     209      208000 :     metaData = BloomPageGetMeta(BufferGetPage(metaBuffer));
     210             : 
     211      208000 :     if (metaData->nEnd > metaData->nStart)
     212             :     {
     213      207998 :         blkno = metaData->notFullPage[metaData->nStart];
     214             :         Assert(blkno != InvalidBlockNumber);
     215             : 
     216             :         /* Don't hold metabuffer lock while doing insert */
     217      207998 :         LockBuffer(metaBuffer, BUFFER_LOCK_UNLOCK);
     218             : 
     219      207998 :         buffer = ReadBuffer(index, blkno);
     220      207998 :         LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
     221             : 
     222      207998 :         state = GenericXLogStart(index);
     223      207998 :         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      207998 :         if (PageIsNew(page) || BloomPageIsDeleted(page))
     230           0 :             BloomInitPage(page, 0);
     231             : 
     232      207998 :         if (BloomPageAddItem(&blstate, page, itup))
     233             :         {
     234             :             /* Success!  Apply the change, clean up, and exit */
     235      206484 :             GenericXLogFinish(state);
     236      206484 :             UnlockReleaseBuffer(buffer);
     237      206484 :             ReleaseBuffer(metaBuffer);
     238      206484 :             MemoryContextSwitchTo(oldCtx);
     239      206484 :             MemoryContextDelete(insertCtx);
     240      206484 :             return false;
     241             :         }
     242             : 
     243             :         /* Didn't fit, must try other pages */
     244        1514 :         GenericXLogAbort(state);
     245        1514 :         UnlockReleaseBuffer(buffer);
     246             :     }
     247             :     else
     248             :     {
     249             :         /* No entries in notFullPage */
     250           2 :         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        1516 :     LockBuffer(metaBuffer, BUFFER_LOCK_EXCLUSIVE);
     258             : 
     259             :     /* nStart might have changed while we didn't have lock */
     260        1516 :     nStart = metaData->nStart;
     261             : 
     262             :     /* Skip first page if we already tried it above */
     263        1516 :     if (nStart < metaData->nEnd &&
     264        1514 :         blkno == metaData->notFullPage[nStart])
     265        1514 :         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        1516 :         state = GenericXLogStart(index);
     275             : 
     276             :         /* get modifiable copy of metapage */
     277        1516 :         metaPage = GenericXLogRegisterBuffer(state, metaBuffer, 0);
     278        1516 :         metaData = BloomPageGetMeta(metaPage);
     279             : 
     280        1516 :         if (nStart >= metaData->nEnd)
     281         226 :             break;              /* no more entries in notFullPage array */
     282             : 
     283        1290 :         blkno = metaData->notFullPage[nStart];
     284             :         Assert(blkno != InvalidBlockNumber);
     285             : 
     286        1290 :         buffer = ReadBuffer(index, blkno);
     287        1290 :         LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
     288        1290 :         page = GenericXLogRegisterBuffer(state, buffer, 0);
     289             : 
     290             :         /* Basically same logic as above */
     291        1290 :         if (PageIsNew(page) || BloomPageIsDeleted(page))
     292           0 :             BloomInitPage(page, 0);
     293             : 
     294        1290 :         if (BloomPageAddItem(&blstate, page, itup))
     295             :         {
     296             :             /* Success!  Apply the changes, clean up, and exit */
     297        1290 :             metaData->nStart = nStart;
     298        1290 :             GenericXLogFinish(state);
     299        1290 :             UnlockReleaseBuffer(buffer);
     300        1290 :             UnlockReleaseBuffer(metaBuffer);
     301        1290 :             MemoryContextSwitchTo(oldCtx);
     302        1290 :             MemoryContextDelete(insertCtx);
     303        1290 :             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         226 :     buffer = BloomNewBuffer(index);
     317             : 
     318         226 :     page = GenericXLogRegisterBuffer(state, buffer, GENERIC_XLOG_FULL_IMAGE);
     319         226 :     BloomInitPage(page, 0);
     320             : 
     321         226 :     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         226 :     metaData->nStart = 0;
     329         226 :     metaData->nEnd = 1;
     330         226 :     metaData->notFullPage[0] = BufferGetBlockNumber(buffer);
     331             : 
     332             :     /* Apply the changes, clean up, and exit */
     333         226 :     GenericXLogFinish(state);
     334             : 
     335         226 :     UnlockReleaseBuffer(buffer);
     336         226 :     UnlockReleaseBuffer(metaBuffer);
     337             : 
     338         226 :     MemoryContextSwitchTo(oldCtx);
     339         226 :     MemoryContextDelete(insertCtx);
     340             : 
     341         226 :     return false;
     342             : }

Generated by: LCOV version 1.14