LCOV - differential code coverage report
Current view: top level - src/backend/access/hash - hashinsert.c (source / functions) Coverage Total Hit UNC LBC UIC UBC GIC GNC CBC EUB ECB DCB
Current: Differential Code Coverage HEAD vs 15 Lines: 62.8 % 137 86 2 7 30 12 47 11 28 39 50 1
Current Date: 2023-04-08 15:15:32 Functions: 75.0 % 4 3 1 1 2 1 3
Baseline: 15
Baseline Date: 2023-04-08 15:09:40
Legend: Lines: hit not hit

           TLA  Line data    Source code
       1                 : /*-------------------------------------------------------------------------
       2                 :  *
       3                 :  * hashinsert.c
       4                 :  *    Item insertion in hash tables for Postgres.
       5                 :  *
       6                 :  * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
       7                 :  * Portions Copyright (c) 1994, Regents of the University of California
       8                 :  *
       9                 :  *
      10                 :  * IDENTIFICATION
      11                 :  *    src/backend/access/hash/hashinsert.c
      12                 :  *
      13                 :  *-------------------------------------------------------------------------
      14                 :  */
      15                 : 
      16                 : #include "postgres.h"
      17                 : 
      18                 : #include "access/hash.h"
      19                 : #include "access/hash_xlog.h"
      20                 : #include "access/xloginsert.h"
      21                 : #include "miscadmin.h"
      22                 : #include "storage/buf_internals.h"
      23                 : #include "storage/lwlock.h"
      24                 : #include "storage/predicate.h"
      25                 : #include "utils/rel.h"
      26                 : 
      27                 : static void _hash_vacuum_one_page(Relation rel, Relation hrel,
      28                 :                                   Buffer metabuf, Buffer buf);
      29                 : 
      30                 : /*
      31                 :  *  _hash_doinsert() -- Handle insertion of a single index tuple.
      32                 :  *
      33                 :  *      This routine is called by the public interface routines, hashbuild
      34                 :  *      and hashinsert.  By here, itup is completely filled in.
      35                 :  *
      36                 :  * 'sorted' must only be passed as 'true' when inserts are done in hashkey
      37                 :  * order.
      38                 :  */
      39                 : void
      40 GNC      345510 : _hash_doinsert(Relation rel, IndexTuple itup, Relation heapRel, bool sorted)
      41                 : {
      42 GIC      345510 :     Buffer      buf = InvalidBuffer;
      43 ECB             :     Buffer      bucket_buf;
      44                 :     Buffer      metabuf;
      45                 :     HashMetaPage metap;
      46 GIC      345510 :     HashMetaPage usedmetap = NULL;
      47                 :     Page        metapage;
      48                 :     Page        page;
      49 ECB             :     HashPageOpaque pageopaque;
      50                 :     Size        itemsz;
      51                 :     bool        do_expand;
      52                 :     uint32      hashkey;
      53                 :     Bucket      bucket;
      54                 :     OffsetNumber itup_off;
      55                 : 
      56                 :     /*
      57                 :      * Get the hash key for the item (it's stored in the index tuple itself).
      58                 :      */
      59 GIC      345510 :     hashkey = _hash_get_indextuple_hashkey(itup);
      60                 : 
      61                 :     /* compute item size too */
      62 CBC      345510 :     itemsz = IndexTupleSize(itup);
      63 GIC      345510 :     itemsz = MAXALIGN(itemsz);  /* be safe, PageAddItem will do this but we
      64                 :                                  * need to be consistent */
      65 ECB             : 
      66 CBC      345510 : restart_insert:
      67                 : 
      68                 :     /*
      69 ECB             :      * Read the metapage.  We don't lock it yet; HashMaxItemSize() will
      70                 :      * examine pd_pagesize_version, but that can't change so we can examine it
      71                 :      * without a lock.
      72                 :      */
      73 GIC      345510 :     metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_NOLOCK, LH_META_PAGE);
      74          345510 :     metapage = BufferGetPage(metabuf);
      75                 : 
      76 ECB             :     /*
      77                 :      * Check whether the item can fit on a hash page at all. (Eventually, we
      78                 :      * ought to try to apply TOAST methods if not.)  Note that at this point,
      79                 :      * itemsz doesn't include the ItemId.
      80                 :      *
      81                 :      * XXX this is useless code if we are only storing hash keys.
      82                 :      */
      83 GIC      345510 :     if (itemsz > HashMaxItemSize(metapage))
      84 UIC           0 :         ereport(ERROR,
      85                 :                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
      86 ECB             :                  errmsg("index row size %zu exceeds hash maximum %zu",
      87 EUB             :                         itemsz, HashMaxItemSize(metapage)),
      88                 :                  errhint("Values larger than a buffer page cannot be indexed.")));
      89                 : 
      90                 :     /* Lock the primary bucket page for the target bucket. */
      91 GIC      345510 :     buf = _hash_getbucketbuf_from_hashkey(rel, hashkey, HASH_WRITE,
      92                 :                                           &usedmetap);
      93          345510 :     Assert(usedmetap != NULL);
      94 ECB             : 
      95 GIC      345510 :     CheckForSerializableConflictIn(rel, NULL, BufferGetBlockNumber(buf));
      96 ECB             : 
      97                 :     /* remember the primary bucket buffer to release the pin on it at end. */
      98 CBC      345504 :     bucket_buf = buf;
      99                 : 
     100 GIC      345504 :     page = BufferGetPage(buf);
     101 CBC      345504 :     pageopaque = HashPageGetOpaque(page);
     102 GIC      345504 :     bucket = pageopaque->hasho_bucket;
     103 ECB             : 
     104                 :     /*
     105                 :      * If this bucket is in the process of being split, try to finish the
     106                 :      * split before inserting, because that might create room for the
     107                 :      * insertion to proceed without allocating an additional overflow page.
     108                 :      * It's only interesting to finish the split if we're trying to insert
     109                 :      * into the bucket from which we're removing tuples (the "old" bucket),
     110                 :      * not if we're trying to insert into the bucket into which tuples are
     111                 :      * being moved (the "new" bucket).
     112                 :      */
     113 GIC      345504 :     if (H_BUCKET_BEING_SPLIT(pageopaque) && IsBufferCleanupOK(buf))
     114                 :     {
     115                 :         /* release the lock on bucket buffer, before completing the split. */
     116 LBC           0 :         LockBuffer(buf, BUFFER_LOCK_UNLOCK);
     117                 : 
     118 UIC           0 :         _hash_finish_split(rel, metabuf, buf, bucket,
     119 UBC           0 :                            usedmetap->hashm_maxbucket,
     120 UIC           0 :                            usedmetap->hashm_highmask,
     121 UBC           0 :                            usedmetap->hashm_lowmask);
     122 EUB             : 
     123                 :         /* release the pin on old and meta buffer.  retry for insert. */
     124 UBC           0 :         _hash_dropbuf(rel, buf);
     125 UIC           0 :         _hash_dropbuf(rel, metabuf);
     126               0 :         goto restart_insert;
     127 EUB             :     }
     128                 : 
     129                 :     /* Do the insertion */
     130 GIC      574273 :     while (PageGetFreeSpace(page) < itemsz)
     131                 :     {
     132                 :         BlockNumber nextblkno;
     133 ECB             : 
     134                 :         /*
     135                 :          * Check if current page has any DEAD tuples. If yes, delete these
     136                 :          * tuples and see if we can get a space for the new item to be
     137                 :          * inserted before moving to the next page in the bucket chain.
     138                 :          */
     139 GIC      228769 :         if (H_HAS_DEAD_TUPLES(pageopaque))
     140                 :         {
     141                 : 
     142 LBC           0 :             if (IsBufferCleanupOK(buf))
     143                 :             {
     144 UIC           0 :                 _hash_vacuum_one_page(rel, heapRel, metabuf, buf);
     145 EUB             : 
     146 UIC           0 :                 if (PageGetFreeSpace(page) >= itemsz)
     147 UBC           0 :                     break;      /* OK, now we have enough space */
     148                 :             }
     149 EUB             :         }
     150                 : 
     151                 :         /*
     152                 :          * no space on this page; check for an overflow page
     153                 :          */
     154 GIC      228769 :         nextblkno = pageopaque->hasho_nextblkno;
     155                 : 
     156          228769 :         if (BlockNumberIsValid(nextblkno))
     157 ECB             :         {
     158                 :             /*
     159                 :              * ovfl page exists; go get it.  if it doesn't have room, we'll
     160                 :              * find out next pass through the loop test above.  we always
     161                 :              * release both the lock and pin if this is an overflow page, but
     162                 :              * only the lock if this is the primary bucket page, since the pin
     163                 :              * on the primary bucket must be retained throughout the scan.
     164                 :              */
     165 GIC      228647 :             if (buf != bucket_buf)
     166          195402 :                 _hash_relbuf(rel, buf);
     167                 :             else
     168 CBC       33245 :                 LockBuffer(buf, BUFFER_LOCK_UNLOCK);
     169          228647 :             buf = _hash_getbuf(rel, nextblkno, HASH_WRITE, LH_OVERFLOW_PAGE);
     170 GIC      228647 :             page = BufferGetPage(buf);
     171 ECB             :         }
     172                 :         else
     173                 :         {
     174                 :             /*
     175                 :              * we're at the end of the bucket chain and we haven't found a
     176                 :              * page with enough room.  allocate a new overflow page.
     177                 :              */
     178                 : 
     179                 :             /* release our write lock without modifying buffer */
     180 GIC         122 :             LockBuffer(buf, BUFFER_LOCK_UNLOCK);
     181                 : 
     182                 :             /* chain to a new overflow page */
     183 CBC         122 :             buf = _hash_addovflpage(rel, metabuf, buf, (buf == bucket_buf));
     184 GIC         122 :             page = BufferGetPage(buf);
     185                 : 
     186 ECB             :             /* should fit now, given test above */
     187 CBC         122 :             Assert(PageGetFreeSpace(page) >= itemsz);
     188                 :         }
     189 GIC      228769 :         pageopaque = HashPageGetOpaque(page);
     190 CBC      228769 :         Assert((pageopaque->hasho_flag & LH_PAGE_TYPE) == LH_OVERFLOW_PAGE);
     191 GIC      228769 :         Assert(pageopaque->hasho_bucket == bucket);
     192 ECB             :     }
     193                 : 
     194                 :     /*
     195                 :      * Write-lock the metapage so we can increment the tuple count. After
     196                 :      * incrementing it, check to see if it's time for a split.
     197                 :      */
     198 GIC      345504 :     LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);
     199                 : 
     200                 :     /* Do the update.  No ereport(ERROR) until changes are logged */
     201 CBC      345504 :     START_CRIT_SECTION();
     202                 : 
     203                 :     /* found page with enough space, so add the item here */
     204 GNC      345504 :     itup_off = _hash_pgaddtup(rel, buf, itemsz, itup, sorted);
     205 GIC      345504 :     MarkBufferDirty(buf);
     206                 : 
     207 ECB             :     /* metapage operations */
     208 CBC      345504 :     metap = HashPageGetMeta(metapage);
     209 GIC      345504 :     metap->hashm_ntuples += 1;
     210                 : 
     211 ECB             :     /* Make sure this stays in sync with _hash_expandtable() */
     212 CBC      345504 :     do_expand = metap->hashm_ntuples >
     213 GIC      345504 :         (double) metap->hashm_ffactor * (metap->hashm_maxbucket + 1);
     214                 : 
     215 CBC      345504 :     MarkBufferDirty(metabuf);
     216 ECB             : 
     217                 :     /* XLOG stuff */
     218 CBC      345504 :     if (RelationNeedsWAL(rel))
     219                 :     {
     220                 :         xl_hash_insert xlrec;
     221 ECB             :         XLogRecPtr  recptr;
     222                 : 
     223 GIC      263334 :         xlrec.offnum = itup_off;
     224                 : 
     225          263334 :         XLogBeginInsert();
     226 CBC      263334 :         XLogRegisterData((char *) &xlrec, SizeOfHashInsert);
     227                 : 
     228          263334 :         XLogRegisterBuffer(1, metabuf, REGBUF_STANDARD);
     229 ECB             : 
     230 GIC      263334 :         XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
     231 CBC      263334 :         XLogRegisterBufData(0, (char *) itup, IndexTupleSize(itup));
     232                 : 
     233          263334 :         recptr = XLogInsert(RM_HASH_ID, XLOG_HASH_INSERT);
     234 ECB             : 
     235 GIC      263334 :         PageSetLSN(BufferGetPage(buf), recptr);
     236 CBC      263334 :         PageSetLSN(BufferGetPage(metabuf), recptr);
     237                 :     }
     238 ECB             : 
     239 CBC      345504 :     END_CRIT_SECTION();
     240                 : 
     241                 :     /* drop lock on metapage, but keep pin */
     242          345504 :     LockBuffer(metabuf, BUFFER_LOCK_UNLOCK);
     243                 : 
     244                 :     /*
     245 ECB             :      * Release the modified page and ensure to release the pin on primary
     246                 :      * page.
     247                 :      */
     248 GIC      345504 :     _hash_relbuf(rel, buf);
     249          345504 :     if (buf != bucket_buf)
     250           33289 :         _hash_dropbuf(rel, bucket_buf);
     251 ECB             : 
     252                 :     /* Attempt to split if a split is needed */
     253 CBC      345504 :     if (do_expand)
     254 GIC         666 :         _hash_expandtable(rel, metabuf);
     255                 : 
     256 ECB             :     /* Finally drop our pin on the metapage */
     257 CBC      345504 :     _hash_dropbuf(rel, metabuf);
     258 GIC      345504 : }
     259                 : 
     260 ECB             : /*
     261                 :  *  _hash_pgaddtup() -- add a tuple to a particular page in the index.
     262                 :  *
     263                 :  * This routine adds the tuple to the page as requested; it does not write out
     264                 :  * the page.  It is an error to call this function without pin and write lock
     265                 :  * on the target buffer.
     266                 :  *
     267                 :  * Returns the offset number at which the tuple was inserted.  This function
     268                 :  * is responsible for preserving the condition that tuples in a hash index
     269                 :  * page are sorted by hashkey value, however, if the caller is certain that
     270                 :  * the hashkey for the tuple being added is >= the hashkeys of all existing
     271                 :  * tuples on the page, then the 'appendtup' flag may be passed as true.  This
     272                 :  * saves from having to binary search for the correct location to insert the
     273                 :  * tuple.
     274                 :  */
     275                 : OffsetNumber
     276 GNC      345504 : _hash_pgaddtup(Relation rel, Buffer buf, Size itemsize, IndexTuple itup,
     277                 :                bool appendtup)
     278                 : {
     279                 :     OffsetNumber itup_off;
     280                 :     Page        page;
     281                 : 
     282 GIC      345504 :     _hash_checkpage(rel, buf, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
     283 CBC      345504 :     page = BufferGetPage(buf);
     284                 : 
     285                 :     /*
     286                 :      * Find where to insert the tuple (preserving page's hashkey ordering). If
     287                 :      * 'appendtup' is true then we just insert it at the end.
     288                 :      */
     289 GNC      345504 :     if (appendtup)
     290                 :     {
     291           60500 :         itup_off = PageGetMaxOffsetNumber(page) + 1;
     292                 : 
     293                 : #ifdef USE_ASSERT_CHECKING
     294                 :         /* ensure this tuple's hashkey is >= the final existing tuple */
     295           60500 :         if (PageGetMaxOffsetNumber(page) > 0)
     296                 :         {
     297                 :             IndexTuple  lasttup;
     298                 :             ItemId      itemid;
     299                 : 
     300           59142 :             itemid = PageGetItemId(page, PageGetMaxOffsetNumber(page));
     301           59142 :             lasttup = (IndexTuple) PageGetItem(page, itemid);
     302                 : 
     303           59142 :             Assert(_hash_get_indextuple_hashkey(lasttup) <=
     304                 :                    _hash_get_indextuple_hashkey(itup));
     305                 :         }
     306                 : #endif
     307                 :     }
     308                 :     else
     309                 :     {
     310          285004 :         uint32      hashkey = _hash_get_indextuple_hashkey(itup);
     311                 : 
     312          285004 :         itup_off = _hash_binsearch(page, hashkey);
     313                 :     }
     314                 : 
     315 CBC      345504 :     if (PageAddItem(page, (Item) itup, itemsize, itup_off, false, false)
     316 ECB             :         == InvalidOffsetNumber)
     317 UIC           0 :         elog(ERROR, "failed to add index item to \"%s\"",
     318                 :              RelationGetRelationName(rel));
     319                 : 
     320 GIC      345504 :     return itup_off;
     321                 : }
     322 ECB             : 
     323                 : /*
     324                 :  *  _hash_pgaddmultitup() -- add a tuple vector to a particular page in the
     325                 :  *                           index.
     326                 :  *
     327                 :  * This routine has same requirements for locking and tuple ordering as
     328                 :  * _hash_pgaddtup().
     329                 :  *
     330                 :  * Returns the offset number array at which the tuples were inserted.
     331                 :  */
     332                 : void
     333 CBC         734 : _hash_pgaddmultitup(Relation rel, Buffer buf, IndexTuple *itups,
     334 ECB             :                     OffsetNumber *itup_offsets, uint16 nitups)
     335                 : {
     336                 :     OffsetNumber itup_off;
     337                 :     Page        page;
     338                 :     uint32      hashkey;
     339                 :     int         i;
     340                 : 
     341 GIC         734 :     _hash_checkpage(rel, buf, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
     342             734 :     page = BufferGetPage(buf);
     343 ECB             : 
     344 GIC       64293 :     for (i = 0; i < nitups; i++)
     345 ECB             :     {
     346                 :         Size        itemsize;
     347                 : 
     348 CBC       63559 :         itemsize = IndexTupleSize(itups[i]);
     349 GIC       63559 :         itemsize = MAXALIGN(itemsize);
     350 EUB             : 
     351                 :         /* Find where to insert the tuple (preserving page's hashkey ordering) */
     352 GIC       63559 :         hashkey = _hash_get_indextuple_hashkey(itups[i]);
     353 CBC       63559 :         itup_off = _hash_binsearch(page, hashkey);
     354                 : 
     355 GIC       63559 :         itup_offsets[i] = itup_off;
     356                 : 
     357           63559 :         if (PageAddItem(page, (Item) itups[i], itemsize, itup_off, false, false)
     358                 :             == InvalidOffsetNumber)
     359 UIC           0 :             elog(ERROR, "failed to add index item to \"%s\"",
     360                 :                  RelationGetRelationName(rel));
     361                 :     }
     362 GIC         734 : }
     363                 : 
     364                 : /*
     365                 :  * _hash_vacuum_one_page - vacuum just one index page.
     366 ECB             :  *
     367                 :  * Try to remove LP_DEAD items from the given page. We must acquire cleanup
     368                 :  * lock on the page being modified before calling this function.
     369                 :  */
     370                 : 
     371                 : static void
     372 UIC           0 : _hash_vacuum_one_page(Relation rel, Relation hrel, Buffer metabuf, Buffer buf)
     373                 : {
     374 ECB             :     OffsetNumber deletable[MaxOffsetNumber];
     375 LBC           0 :     int         ndeletable = 0;
     376                 :     OffsetNumber offnum,
     377 ECB             :                 maxoff;
     378 UIC           0 :     Page        page = BufferGetPage(buf);
     379                 :     HashPageOpaque pageopaque;
     380                 :     HashMetaPage metap;
     381 ECB             : 
     382                 :     /* Scan each tuple in page to see if it is marked as LP_DEAD */
     383 UIC           0 :     maxoff = PageGetMaxOffsetNumber(page);
     384               0 :     for (offnum = FirstOffsetNumber;
     385 LBC           0 :          offnum <= maxoff;
     386               0 :          offnum = OffsetNumberNext(offnum))
     387                 :     {
     388               0 :         ItemId      itemId = PageGetItemId(page, offnum);
     389                 : 
     390               0 :         if (ItemIdIsDead(itemId))
     391 UIC           0 :             deletable[ndeletable++] = offnum;
     392 EUB             :     }
     393                 : 
     394 UIC           0 :     if (ndeletable > 0)
     395 ECB             :     {
     396                 :         TransactionId snapshotConflictHorizon;
     397                 : 
     398                 :         snapshotConflictHorizon =
     399 UIC           0 :             index_compute_xid_horizon_for_tuples(rel, hrel, buf,
     400                 :                                                  deletable, ndeletable);
     401                 : 
     402                 :         /*
     403                 :          * Write-lock the meta page so that we can decrement tuple count.
     404                 :          */
     405 UBC           0 :         LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);
     406                 : 
     407                 :         /* No ereport(ERROR) until changes are logged */
     408               0 :         START_CRIT_SECTION();
     409                 : 
     410 UIC           0 :         PageIndexMultiDelete(page, deletable, ndeletable);
     411 EUB             : 
     412                 :         /*
     413                 :          * Mark the page as not containing any LP_DEAD items. This is not
     414                 :          * certainly true (there might be some that have recently been marked,
     415                 :          * but weren't included in our target-item list), but it will almost
     416                 :          * always be true and it doesn't seem worth an additional page scan to
     417                 :          * check it. Remember that LH_PAGE_HAS_DEAD_TUPLES is only a hint
     418                 :          * anyway.
     419                 :          */
     420 UIC           0 :         pageopaque = HashPageGetOpaque(page);
     421 UBC           0 :         pageopaque->hasho_flag &= ~LH_PAGE_HAS_DEAD_TUPLES;
     422                 : 
     423               0 :         metap = HashPageGetMeta(BufferGetPage(metabuf));
     424               0 :         metap->hashm_ntuples -= ndeletable;
     425                 : 
     426 UIC           0 :         MarkBufferDirty(buf);
     427 UBC           0 :         MarkBufferDirty(metabuf);
     428                 : 
     429                 :         /* XLOG stuff */
     430 UIC           0 :         if (RelationNeedsWAL(rel))
     431                 :         {
     432 EUB             :             xl_hash_vacuum_one_page xlrec;
     433                 :             XLogRecPtr  recptr;
     434                 : 
     435 UNC           0 :             xlrec.isCatalogRel = RelationIsAccessibleInLogicalDecoding(hrel);
     436               0 :             xlrec.snapshotConflictHorizon = snapshotConflictHorizon;
     437 UIC           0 :             xlrec.ntuples = ndeletable;
     438                 : 
     439 UBC           0 :             XLogBeginInsert();
     440 UIC           0 :             XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
     441               0 :             XLogRegisterData((char *) &xlrec, SizeOfHashVacuumOnePage);
     442 EUB             : 
     443                 :             /*
     444                 :              * We need the target-offsets array whether or not we store the
     445                 :              * whole buffer, to allow us to find the snapshotConflictHorizon
     446                 :              * on a standby server.
     447                 :              */
     448 UIC           0 :             XLogRegisterData((char *) deletable,
     449                 :                              ndeletable * sizeof(OffsetNumber));
     450                 : 
     451               0 :             XLogRegisterBuffer(1, metabuf, REGBUF_STANDARD);
     452                 : 
     453               0 :             recptr = XLogInsert(RM_HASH_ID, XLOG_HASH_VACUUM_ONE_PAGE);
     454 EUB             : 
     455 UBC           0 :             PageSetLSN(BufferGetPage(buf), recptr);
     456 UIC           0 :             PageSetLSN(BufferGetPage(metabuf), recptr);
     457 EUB             :         }
     458                 : 
     459 UIC           0 :         END_CRIT_SECTION();
     460 EUB             : 
     461                 :         /*
     462                 :          * Releasing write lock on meta page as we have updated the tuple
     463                 :          * count.
     464                 :          */
     465 UIC           0 :         LockBuffer(metabuf, BUFFER_LOCK_UNLOCK);
     466                 :     }
     467               0 : }
        

Generated by: LCOV version v1.16-55-g56c0a2a