LCOV - code coverage report
Current view: top level - src/backend/access/gin - gininsert.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 152 159 95.6 %
Date: 2023-12-05 09:10:49 Functions: 9 9 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * gininsert.c
       4             :  *    insert routines for the postgres inverted index access method.
       5             :  *
       6             :  *
       7             :  * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
       8             :  * Portions Copyright (c) 1994, Regents of the University of California
       9             :  *
      10             :  * IDENTIFICATION
      11             :  *          src/backend/access/gin/gininsert.c
      12             :  *-------------------------------------------------------------------------
      13             :  */
      14             : 
      15             : #include "postgres.h"
      16             : 
      17             : #include "access/gin_private.h"
      18             : #include "access/ginxlog.h"
      19             : #include "access/tableam.h"
      20             : #include "access/xloginsert.h"
      21             : #include "catalog/index.h"
      22             : #include "miscadmin.h"
      23             : #include "storage/bufmgr.h"
      24             : #include "storage/indexfsm.h"
      25             : #include "storage/predicate.h"
      26             : #include "storage/smgr.h"
      27             : #include "utils/memutils.h"
      28             : #include "utils/rel.h"
      29             : 
      30             : typedef struct
      31             : {
      32             :     GinState    ginstate;
      33             :     double      indtuples;
      34             :     GinStatsData buildStats;
      35             :     MemoryContext tmpCtx;
      36             :     MemoryContext funcCtx;
      37             :     BuildAccumulator accum;
      38             : } GinBuildState;
      39             : 
      40             : 
      41             : /*
      42             :  * Adds array of item pointers to tuple's posting list, or
      43             :  * creates posting tree and tuple pointing to tree in case
      44             :  * of not enough space.  Max size of tuple is defined in
      45             :  * GinFormTuple().  Returns a new, modified index tuple.
      46             :  * items[] must be in sorted order with no duplicates.
      47             :  */
      48             : static IndexTuple
      49       32652 : addItemPointersToLeafTuple(GinState *ginstate,
      50             :                            IndexTuple old,
      51             :                            ItemPointerData *items, uint32 nitem,
      52             :                            GinStatsData *buildStats, Buffer buffer)
      53             : {
      54             :     OffsetNumber attnum;
      55             :     Datum       key;
      56             :     GinNullCategory category;
      57             :     IndexTuple  res;
      58             :     ItemPointerData *newItems,
      59             :                *oldItems;
      60             :     int         oldNPosting,
      61             :                 newNPosting;
      62             :     GinPostingList *compressedList;
      63             : 
      64             :     Assert(!GinIsPostingTree(old));
      65             : 
      66       32652 :     attnum = gintuple_get_attrnum(ginstate, old);
      67       32652 :     key = gintuple_get_key(ginstate, old, &category);
      68             : 
      69             :     /* merge the old and new posting lists */
      70       32652 :     oldItems = ginReadTuple(ginstate, attnum, old, &oldNPosting);
      71             : 
      72       32652 :     newItems = ginMergeItemPointers(items, nitem,
      73             :                                     oldItems, oldNPosting,
      74             :                                     &newNPosting);
      75             : 
      76             :     /* Compress the posting list, and try to a build tuple with room for it */
      77       32652 :     res = NULL;
      78       32652 :     compressedList = ginCompressPostingList(newItems, newNPosting, GinMaxItemSize,
      79             :                                             NULL);
      80       32652 :     pfree(newItems);
      81       32652 :     if (compressedList)
      82             :     {
      83       32652 :         res = GinFormTuple(ginstate, attnum, key, category,
      84             :                            (char *) compressedList,
      85       32652 :                            SizeOfGinPostingList(compressedList),
      86             :                            newNPosting,
      87             :                            false);
      88       32652 :         pfree(compressedList);
      89             :     }
      90       32652 :     if (!res)
      91             :     {
      92             :         /* posting list would be too big, convert to posting tree */
      93             :         BlockNumber postingRoot;
      94             : 
      95             :         /*
      96             :          * Initialize posting tree with the old tuple's posting list.  It's
      97             :          * surely small enough to fit on one posting-tree page, and should
      98             :          * already be in order with no duplicates.
      99             :          */
     100          22 :         postingRoot = createPostingTree(ginstate->index,
     101             :                                         oldItems,
     102             :                                         oldNPosting,
     103             :                                         buildStats,
     104             :                                         buffer);
     105             : 
     106             :         /* Now insert the TIDs-to-be-added into the posting tree */
     107          22 :         ginInsertItemPointers(ginstate->index, postingRoot,
     108             :                               items, nitem,
     109             :                               buildStats);
     110             : 
     111             :         /* And build a new posting-tree-only result tuple */
     112          22 :         res = GinFormTuple(ginstate, attnum, key, category, NULL, 0, 0, true);
     113          22 :         GinSetPostingTree(res, postingRoot);
     114             :     }
     115       32652 :     pfree(oldItems);
     116             : 
     117       32652 :     return res;
     118             : }
     119             : 
     120             : /*
     121             :  * Build a fresh leaf tuple, either posting-list or posting-tree format
     122             :  * depending on whether the given items list will fit.
     123             :  * items[] must be in sorted order with no duplicates.
     124             :  *
     125             :  * This is basically the same logic as in addItemPointersToLeafTuple,
     126             :  * but working from slightly different input.
     127             :  */
     128             : static IndexTuple
     129      509200 : buildFreshLeafTuple(GinState *ginstate,
     130             :                     OffsetNumber attnum, Datum key, GinNullCategory category,
     131             :                     ItemPointerData *items, uint32 nitem,
     132             :                     GinStatsData *buildStats, Buffer buffer)
     133             : {
     134      509200 :     IndexTuple  res = NULL;
     135             :     GinPostingList *compressedList;
     136             : 
     137             :     /* try to build a posting list tuple with all the items */
     138      509200 :     compressedList = ginCompressPostingList(items, nitem, GinMaxItemSize, NULL);
     139      509200 :     if (compressedList)
     140             :     {
     141      509200 :         res = GinFormTuple(ginstate, attnum, key, category,
     142             :                            (char *) compressedList,
     143      509200 :                            SizeOfGinPostingList(compressedList),
     144             :                            nitem, false);
     145      509200 :         pfree(compressedList);
     146             :     }
     147      509200 :     if (!res)
     148             :     {
     149             :         /* posting list would be too big, build posting tree */
     150             :         BlockNumber postingRoot;
     151             : 
     152             :         /*
     153             :          * Build posting-tree-only result tuple.  We do this first so as to
     154             :          * fail quickly if the key is too big.
     155             :          */
     156         100 :         res = GinFormTuple(ginstate, attnum, key, category, NULL, 0, 0, true);
     157             : 
     158             :         /*
     159             :          * Initialize a new posting tree with the TIDs.
     160             :          */
     161         100 :         postingRoot = createPostingTree(ginstate->index, items, nitem,
     162             :                                         buildStats, buffer);
     163             : 
     164             :         /* And save the root link in the result tuple */
     165         100 :         GinSetPostingTree(res, postingRoot);
     166             :     }
     167             : 
     168      509200 :     return res;
     169             : }
     170             : 
     171             : /*
     172             :  * Insert one or more heap TIDs associated with the given key value.
     173             :  * This will either add a single key entry, or enlarge a pre-existing entry.
     174             :  *
     175             :  * During an index build, buildStats is non-null and the counters
     176             :  * it contains should be incremented as needed.
     177             :  */
     178             : void
     179      591256 : ginEntryInsert(GinState *ginstate,
     180             :                OffsetNumber attnum, Datum key, GinNullCategory category,
     181             :                ItemPointerData *items, uint32 nitem,
     182             :                GinStatsData *buildStats)
     183             : {
     184             :     GinBtreeData btree;
     185             :     GinBtreeEntryInsertData insertdata;
     186             :     GinBtreeStack *stack;
     187             :     IndexTuple  itup;
     188             :     Page        page;
     189             : 
     190      591256 :     insertdata.isDelete = false;
     191             : 
     192      591256 :     ginPrepareEntryScan(&btree, attnum, key, category, ginstate);
     193      591256 :     btree.isBuild = (buildStats != NULL);
     194             : 
     195      591256 :     stack = ginFindLeafPage(&btree, false, false);
     196      591256 :     page = BufferGetPage(stack->buffer);
     197             : 
     198      591256 :     if (btree.findItem(&btree, stack))
     199             :     {
     200             :         /* found pre-existing entry */
     201       82052 :         itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stack->off));
     202             : 
     203       82052 :         if (GinIsPostingTree(itup))
     204             :         {
     205             :             /* add entries to existing posting tree */
     206       49392 :             BlockNumber rootPostingTree = GinGetPostingTree(itup);
     207             : 
     208             :             /* release all stack */
     209       49392 :             LockBuffer(stack->buffer, GIN_UNLOCK);
     210       49392 :             freeGinBtreeStack(stack);
     211             : 
     212             :             /* insert into posting tree */
     213       49392 :             ginInsertItemPointers(ginstate->index, rootPostingTree,
     214             :                                   items, nitem,
     215             :                                   buildStats);
     216       49388 :             return;
     217             :         }
     218             : 
     219       32660 :         CheckForSerializableConflictIn(ginstate->index, NULL,
     220             :                                        BufferGetBlockNumber(stack->buffer));
     221             :         /* modify an existing leaf entry */
     222       32652 :         itup = addItemPointersToLeafTuple(ginstate, itup,
     223             :                                           items, nitem, buildStats, stack->buffer);
     224             : 
     225       32652 :         insertdata.isDelete = true;
     226             :     }
     227             :     else
     228             :     {
     229      509204 :         CheckForSerializableConflictIn(ginstate->index, NULL,
     230             :                                        BufferGetBlockNumber(stack->buffer));
     231             :         /* no match, so construct a new leaf entry */
     232      509200 :         itup = buildFreshLeafTuple(ginstate, attnum, key, category,
     233             :                                    items, nitem, buildStats, stack->buffer);
     234             : 
     235             :         /*
     236             :          * nEntries counts leaf tuples, so increment it only when we make a
     237             :          * new one.
     238             :          */
     239      509200 :         if (buildStats)
     240      149144 :             buildStats->nEntries++;
     241             :     }
     242             : 
     243             :     /* Insert the new or modified leaf tuple */
     244      541852 :     insertdata.entry = itup;
     245      541852 :     ginInsertValue(&btree, stack, &insertdata, buildStats);
     246      541852 :     pfree(itup);
     247             : }
     248             : 
     249             : /*
     250             :  * Extract index entries for a single indexable item, and add them to the
     251             :  * BuildAccumulator's state.
     252             :  *
     253             :  * This function is used only during initial index creation.
     254             :  */
     255             : static void
     256      869228 : ginHeapTupleBulkInsert(GinBuildState *buildstate, OffsetNumber attnum,
     257             :                        Datum value, bool isNull,
     258             :                        ItemPointer heapptr)
     259             : {
     260             :     Datum      *entries;
     261             :     GinNullCategory *categories;
     262             :     int32       nentries;
     263             :     MemoryContext oldCtx;
     264             : 
     265      869228 :     oldCtx = MemoryContextSwitchTo(buildstate->funcCtx);
     266      869228 :     entries = ginExtractEntries(buildstate->accum.ginstate, attnum,
     267             :                                 value, isNull,
     268             :                                 &nentries, &categories);
     269      869228 :     MemoryContextSwitchTo(oldCtx);
     270             : 
     271      869228 :     ginInsertBAEntries(&buildstate->accum, heapptr, attnum,
     272             :                        entries, categories, nentries);
     273             : 
     274      869228 :     buildstate->indtuples += nentries;
     275             : 
     276      869228 :     MemoryContextReset(buildstate->funcCtx);
     277      869228 : }
     278             : 
     279             : static void
     280      868610 : ginBuildCallback(Relation index, ItemPointer tid, Datum *values,
     281             :                  bool *isnull, bool tupleIsAlive, void *state)
     282             : {
     283      868610 :     GinBuildState *buildstate = (GinBuildState *) state;
     284             :     MemoryContext oldCtx;
     285             :     int         i;
     286             : 
     287      868610 :     oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
     288             : 
     289     1737838 :     for (i = 0; i < buildstate->ginstate.origTupdesc->natts; i++)
     290      869228 :         ginHeapTupleBulkInsert(buildstate, (OffsetNumber) (i + 1),
     291      869228 :                                values[i], isnull[i], tid);
     292             : 
     293             :     /* If we've maxed out our available memory, dump everything to the index */
     294      868610 :     if (buildstate->accum.allocatedMemory >= (Size) maintenance_work_mem * 1024L)
     295             :     {
     296             :         ItemPointerData *list;
     297             :         Datum       key;
     298             :         GinNullCategory category;
     299             :         uint32      nlist;
     300             :         OffsetNumber attnum;
     301             : 
     302           0 :         ginBeginBAScan(&buildstate->accum);
     303           0 :         while ((list = ginGetBAEntry(&buildstate->accum,
     304             :                                      &attnum, &key, &category, &nlist)) != NULL)
     305             :         {
     306             :             /* there could be many entries, so be willing to abort here */
     307           0 :             CHECK_FOR_INTERRUPTS();
     308           0 :             ginEntryInsert(&buildstate->ginstate, attnum, key, category,
     309             :                            list, nlist, &buildstate->buildStats);
     310             :         }
     311             : 
     312           0 :         MemoryContextReset(buildstate->tmpCtx);
     313           0 :         ginInitBA(&buildstate->accum);
     314             :     }
     315             : 
     316      868610 :     MemoryContextSwitchTo(oldCtx);
     317      868610 : }
     318             : 
     319             : IndexBuildResult *
     320         306 : ginbuild(Relation heap, Relation index, IndexInfo *indexInfo)
     321             : {
     322             :     IndexBuildResult *result;
     323             :     double      reltuples;
     324             :     GinBuildState buildstate;
     325             :     Buffer      RootBuffer,
     326             :                 MetaBuffer;
     327             :     ItemPointerData *list;
     328             :     Datum       key;
     329             :     GinNullCategory category;
     330             :     uint32      nlist;
     331             :     MemoryContext oldCtx;
     332             :     OffsetNumber attnum;
     333             : 
     334         306 :     if (RelationGetNumberOfBlocks(index) != 0)
     335           0 :         elog(ERROR, "index \"%s\" already contains data",
     336             :              RelationGetRelationName(index));
     337             : 
     338         306 :     initGinState(&buildstate.ginstate, index);
     339         306 :     buildstate.indtuples = 0;
     340         306 :     memset(&buildstate.buildStats, 0, sizeof(GinStatsData));
     341             : 
     342             :     /* initialize the meta page */
     343         306 :     MetaBuffer = GinNewBuffer(index);
     344             : 
     345             :     /* initialize the root page */
     346         306 :     RootBuffer = GinNewBuffer(index);
     347             : 
     348         306 :     START_CRIT_SECTION();
     349         306 :     GinInitMetabuffer(MetaBuffer);
     350         306 :     MarkBufferDirty(MetaBuffer);
     351         306 :     GinInitBuffer(RootBuffer, GIN_LEAF);
     352         306 :     MarkBufferDirty(RootBuffer);
     353             : 
     354             : 
     355         306 :     UnlockReleaseBuffer(MetaBuffer);
     356         306 :     UnlockReleaseBuffer(RootBuffer);
     357         306 :     END_CRIT_SECTION();
     358             : 
     359             :     /* count the root as first entry page */
     360         306 :     buildstate.buildStats.nEntryPages++;
     361             : 
     362             :     /*
     363             :      * create a temporary memory context that is used to hold data not yet
     364             :      * dumped out to the index
     365             :      */
     366         306 :     buildstate.tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
     367             :                                               "Gin build temporary context",
     368             :                                               ALLOCSET_DEFAULT_SIZES);
     369             : 
     370             :     /*
     371             :      * create a temporary memory context that is used for calling
     372             :      * ginExtractEntries(), and can be reset after each tuple
     373             :      */
     374         306 :     buildstate.funcCtx = AllocSetContextCreate(CurrentMemoryContext,
     375             :                                                "Gin build temporary context for user-defined function",
     376             :                                                ALLOCSET_DEFAULT_SIZES);
     377             : 
     378         306 :     buildstate.accum.ginstate = &buildstate.ginstate;
     379         306 :     ginInitBA(&buildstate.accum);
     380             : 
     381             :     /*
     382             :      * Do the heap scan.  We disallow sync scan here because dataPlaceToPage
     383             :      * prefers to receive tuples in TID order.
     384             :      */
     385         306 :     reltuples = table_index_build_scan(heap, index, indexInfo, false, true,
     386             :                                        ginBuildCallback, (void *) &buildstate,
     387             :                                        NULL);
     388             : 
     389             :     /* dump remaining entries to the index */
     390         306 :     oldCtx = MemoryContextSwitchTo(buildstate.tmpCtx);
     391         306 :     ginBeginBAScan(&buildstate.accum);
     392      149450 :     while ((list = ginGetBAEntry(&buildstate.accum,
     393             :                                  &attnum, &key, &category, &nlist)) != NULL)
     394             :     {
     395             :         /* there could be many entries, so be willing to abort here */
     396      149144 :         CHECK_FOR_INTERRUPTS();
     397      149144 :         ginEntryInsert(&buildstate.ginstate, attnum, key, category,
     398             :                        list, nlist, &buildstate.buildStats);
     399             :     }
     400         306 :     MemoryContextSwitchTo(oldCtx);
     401             : 
     402         306 :     MemoryContextDelete(buildstate.funcCtx);
     403         306 :     MemoryContextDelete(buildstate.tmpCtx);
     404             : 
     405             :     /*
     406             :      * Update metapage stats
     407             :      */
     408         306 :     buildstate.buildStats.nTotalPages = RelationGetNumberOfBlocks(index);
     409         306 :     ginUpdateStats(index, &buildstate.buildStats, true);
     410             : 
     411             :     /*
     412             :      * We didn't write WAL records as we built the index, so if WAL-logging is
     413             :      * required, write all pages to the WAL now.
     414             :      */
     415         306 :     if (RelationNeedsWAL(index))
     416             :     {
     417         184 :         log_newpage_range(index, MAIN_FORKNUM,
     418             :                           0, RelationGetNumberOfBlocks(index),
     419             :                           true);
     420             :     }
     421             : 
     422             :     /*
     423             :      * Return statistics
     424             :      */
     425         306 :     result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));
     426             : 
     427         306 :     result->heap_tuples = reltuples;
     428         306 :     result->index_tuples = buildstate.indtuples;
     429             : 
     430         306 :     return result;
     431             : }
     432             : 
     433             : /*
     434             :  *  ginbuildempty() -- build an empty gin index in the initialization fork
     435             :  */
     436             : void
     437           6 : ginbuildempty(Relation index)
     438             : {
     439             :     Buffer      RootBuffer,
     440             :                 MetaBuffer;
     441             : 
     442             :     /* An empty GIN index has two pages. */
     443           6 :     MetaBuffer = ExtendBufferedRel(BMR_REL(index), INIT_FORKNUM, NULL,
     444             :                                    EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK);
     445           6 :     RootBuffer = ExtendBufferedRel(BMR_REL(index), INIT_FORKNUM, NULL,
     446             :                                    EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK);
     447             : 
     448             :     /* Initialize and xlog metabuffer and root buffer. */
     449           6 :     START_CRIT_SECTION();
     450           6 :     GinInitMetabuffer(MetaBuffer);
     451           6 :     MarkBufferDirty(MetaBuffer);
     452           6 :     log_newpage_buffer(MetaBuffer, true);
     453           6 :     GinInitBuffer(RootBuffer, GIN_LEAF);
     454           6 :     MarkBufferDirty(RootBuffer);
     455           6 :     log_newpage_buffer(RootBuffer, false);
     456           6 :     END_CRIT_SECTION();
     457             : 
     458             :     /* Unlock and release the buffers. */
     459           6 :     UnlockReleaseBuffer(MetaBuffer);
     460           6 :     UnlockReleaseBuffer(RootBuffer);
     461           6 : }
     462             : 
     463             : /*
     464             :  * Insert index entries for a single indexable item during "normal"
     465             :  * (non-fast-update) insertion
     466             :  */
     467             : static void
     468       32054 : ginHeapTupleInsert(GinState *ginstate, OffsetNumber attnum,
     469             :                    Datum value, bool isNull,
     470             :                    ItemPointer item)
     471             : {
     472             :     Datum      *entries;
     473             :     GinNullCategory *categories;
     474             :     int32       i,
     475             :                 nentries;
     476             : 
     477       32054 :     entries = ginExtractEntries(ginstate, attnum, value, isNull,
     478             :                                 &nentries, &categories);
     479             : 
     480      108066 :     for (i = 0; i < nentries; i++)
     481       76028 :         ginEntryInsert(ginstate, attnum, entries[i], categories[i],
     482             :                        item, 1, NULL);
     483       32038 : }
     484             : 
     485             : bool
     486      296924 : gininsert(Relation index, Datum *values, bool *isnull,
     487             :           ItemPointer ht_ctid, Relation heapRel,
     488             :           IndexUniqueCheck checkUnique,
     489             :           bool indexUnchanged,
     490             :           IndexInfo *indexInfo)
     491             : {
     492      296924 :     GinState   *ginstate = (GinState *) indexInfo->ii_AmCache;
     493             :     MemoryContext oldCtx;
     494             :     MemoryContext insertCtx;
     495             :     int         i;
     496             : 
     497             :     /* Initialize GinState cache if first call in this statement */
     498      296924 :     if (ginstate == NULL)
     499             :     {
     500         164 :         oldCtx = MemoryContextSwitchTo(indexInfo->ii_Context);
     501         164 :         ginstate = (GinState *) palloc(sizeof(GinState));
     502         164 :         initGinState(ginstate, index);
     503         164 :         indexInfo->ii_AmCache = (void *) ginstate;
     504         164 :         MemoryContextSwitchTo(oldCtx);
     505             :     }
     506             : 
     507      296924 :     insertCtx = AllocSetContextCreate(CurrentMemoryContext,
     508             :                                       "Gin insert temporary context",
     509             :                                       ALLOCSET_DEFAULT_SIZES);
     510             : 
     511      296924 :     oldCtx = MemoryContextSwitchTo(insertCtx);
     512             : 
     513      296924 :     if (GinGetUseFastUpdate(index))
     514      264864 :     {
     515             :         GinTupleCollector collector;
     516             : 
     517      264870 :         memset(&collector, 0, sizeof(GinTupleCollector));
     518             : 
     519      649818 :         for (i = 0; i < ginstate->origTupdesc->natts; i++)
     520      384948 :             ginHeapTupleFastCollect(ginstate, &collector,
     521      384948 :                                     (OffsetNumber) (i + 1),
     522      384948 :                                     values[i], isnull[i],
     523             :                                     ht_ctid);
     524             : 
     525      264870 :         ginHeapTupleFastInsert(ginstate, &collector);
     526             :     }
     527             :     else
     528             :     {
     529       64092 :         for (i = 0; i < ginstate->origTupdesc->natts; i++)
     530       32054 :             ginHeapTupleInsert(ginstate, (OffsetNumber) (i + 1),
     531       32054 :                                values[i], isnull[i],
     532             :                                ht_ctid);
     533             :     }
     534             : 
     535      296902 :     MemoryContextSwitchTo(oldCtx);
     536      296902 :     MemoryContextDelete(insertCtx);
     537             : 
     538      296902 :     return false;
     539             : }

Generated by: LCOV version 1.14