LCOV - code coverage report
Current view: top level - src/backend/access/hash - hash.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 89.6 % 375 336
Test Date: 2026-01-26 10:56:24 Functions: 86.7 % 15 13
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 64.2 % 179 115

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * hash.c
       4                 :             :  *        Implementation of Margo Seltzer's Hashing package for postgres.
       5                 :             :  *
       6                 :             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7                 :             :  * Portions Copyright (c) 1994, Regents of the University of California
       8                 :             :  *
       9                 :             :  *
      10                 :             :  * IDENTIFICATION
      11                 :             :  *        src/backend/access/hash/hash.c
      12                 :             :  *
      13                 :             :  * NOTES
      14                 :             :  *        This file contains only the public interface routines.
      15                 :             :  *
      16                 :             :  *-------------------------------------------------------------------------
      17                 :             :  */
      18                 :             : 
      19                 :             : #include "postgres.h"
      20                 :             : 
      21                 :             : #include "access/hash.h"
      22                 :             : #include "access/hash_xlog.h"
      23                 :             : #include "access/relscan.h"
      24                 :             : #include "access/stratnum.h"
      25                 :             : #include "access/tableam.h"
      26                 :             : #include "access/xloginsert.h"
      27                 :             : #include "commands/progress.h"
      28                 :             : #include "commands/vacuum.h"
      29                 :             : #include "miscadmin.h"
      30                 :             : #include "nodes/execnodes.h"
      31                 :             : #include "optimizer/plancat.h"
      32                 :             : #include "pgstat.h"
      33                 :             : #include "utils/fmgrprotos.h"
      34                 :             : #include "utils/index_selfuncs.h"
      35                 :             : #include "utils/rel.h"
      36                 :             : 
      37                 :             : /* Working state for hashbuild and its callback */
      38                 :             : typedef struct
      39                 :             : {
      40                 :             :         HSpool     *spool;                      /* NULL if not using spooling */
      41                 :             :         double          indtuples;              /* # tuples accepted into index */
      42                 :             :         Relation        heapRel;                /* heap relation descriptor */
      43                 :             : } HashBuildState;
      44                 :             : 
      45                 :             : static void hashbuildCallback(Relation index,
      46                 :             :                                                           ItemPointer tid,
      47                 :             :                                                           Datum *values,
      48                 :             :                                                           bool *isnull,
      49                 :             :                                                           bool tupleIsAlive,
      50                 :             :                                                           void *state);
      51                 :             : 
      52                 :             : 
      53                 :             : /*
      54                 :             :  * Hash handler function: return IndexAmRoutine with access method parameters
      55                 :             :  * and callbacks.
      56                 :             :  */
      57                 :             : Datum
      58                 :         273 : hashhandler(PG_FUNCTION_ARGS)
      59                 :             : {
      60                 :             :         static const IndexAmRoutine amroutine = {
      61                 :             :                 .type = T_IndexAmRoutine,
      62                 :             :                 .amstrategies = HTMaxStrategyNumber,
      63                 :             :                 .amsupport = HASHNProcs,
      64                 :             :                 .amoptsprocnum = HASHOPTIONS_PROC,
      65                 :             :                 .amcanorder = false,
      66                 :             :                 .amcanorderbyop = false,
      67                 :             :                 .amcanhash = true,
      68                 :             :                 .amconsistentequality = true,
      69                 :             :                 .amconsistentordering = false,
      70                 :             :                 .amcanbackward = true,
      71                 :             :                 .amcanunique = false,
      72                 :             :                 .amcanmulticol = false,
      73                 :             :                 .amoptionalkey = false,
      74                 :             :                 .amsearcharray = false,
      75                 :             :                 .amsearchnulls = false,
      76                 :             :                 .amstorage = false,
      77                 :             :                 .amclusterable = false,
      78                 :             :                 .ampredlocks = true,
      79                 :             :                 .amcanparallel = false,
      80                 :             :                 .amcanbuildparallel = false,
      81                 :             :                 .amcaninclude = false,
      82                 :             :                 .amusemaintenanceworkmem = false,
      83                 :             :                 .amsummarizing = false,
      84                 :             :                 .amparallelvacuumoptions =
      85                 :             :                 VACUUM_OPTION_PARALLEL_BULKDEL,
      86                 :             :                 .amkeytype = INT4OID,
      87                 :             : 
      88                 :             :                 .ambuild = hashbuild,
      89                 :             :                 .ambuildempty = hashbuildempty,
      90                 :             :                 .aminsert = hashinsert,
      91                 :             :                 .aminsertcleanup = NULL,
      92                 :             :                 .ambulkdelete = hashbulkdelete,
      93                 :             :                 .amvacuumcleanup = hashvacuumcleanup,
      94                 :             :                 .amcanreturn = NULL,
      95                 :             :                 .amcostestimate = hashcostestimate,
      96                 :             :                 .amgettreeheight = NULL,
      97                 :             :                 .amoptions = hashoptions,
      98                 :             :                 .amproperty = NULL,
      99                 :             :                 .ambuildphasename = NULL,
     100                 :             :                 .amvalidate = hashvalidate,
     101                 :             :                 .amadjustmembers = hashadjustmembers,
     102                 :             :                 .ambeginscan = hashbeginscan,
     103                 :             :                 .amrescan = hashrescan,
     104                 :             :                 .amgettuple = hashgettuple,
     105                 :             :                 .amgetbitmap = hashgetbitmap,
     106                 :             :                 .amendscan = hashendscan,
     107                 :             :                 .ammarkpos = NULL,
     108                 :             :                 .amrestrpos = NULL,
     109                 :             :                 .amestimateparallelscan = NULL,
     110                 :             :                 .aminitparallelscan = NULL,
     111                 :             :                 .amparallelrescan = NULL,
     112                 :             :                 .amtranslatestrategy = hashtranslatestrategy,
     113                 :             :                 .amtranslatecmptype = hashtranslatecmptype,
     114                 :             :         };
     115                 :             : 
     116                 :         273 :         PG_RETURN_POINTER(&amroutine);
     117                 :             : }
     118                 :             : 
     119                 :             : /*
     120                 :             :  *      hashbuild() -- build a new hash index.
     121                 :             :  */
     122                 :             : IndexBuildResult *
     123                 :          29 : hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
     124                 :             : {
     125                 :          29 :         IndexBuildResult *result;
     126                 :          29 :         BlockNumber relpages;
     127                 :          29 :         double          reltuples;
     128                 :          29 :         double          allvisfrac;
     129                 :          29 :         uint32          num_buckets;
     130                 :          29 :         Size            sort_threshold;
     131                 :          29 :         HashBuildState buildstate;
     132                 :             : 
     133                 :             :         /*
     134                 :             :          * We expect to be called exactly once for any index relation. If that's
     135                 :             :          * not the case, big trouble's what we have.
     136                 :             :          */
     137         [ +  - ]:          29 :         if (RelationGetNumberOfBlocks(index) != 0)
     138   [ #  #  #  # ]:           0 :                 elog(ERROR, "index \"%s\" already contains data",
     139                 :             :                          RelationGetRelationName(index));
     140                 :             : 
     141                 :             :         /* Estimate the number of rows currently present in the table */
     142                 :          29 :         estimate_rel_size(heap, NULL, &relpages, &reltuples, &allvisfrac);
     143                 :             : 
     144                 :             :         /* Initialize the hash index metadata page and initial buckets */
     145                 :          29 :         num_buckets = _hash_init(index, reltuples, MAIN_FORKNUM);
     146                 :             : 
     147                 :             :         /*
     148                 :             :          * If we just insert the tuples into the index in scan order, then
     149                 :             :          * (assuming their hash codes are pretty random) there will be no locality
     150                 :             :          * of access to the index, and if the index is bigger than available RAM
     151                 :             :          * then we'll thrash horribly.  To prevent that scenario, we can sort the
     152                 :             :          * tuples by (expected) bucket number.  However, such a sort is useless
     153                 :             :          * overhead when the index does fit in RAM.  We choose to sort if the
     154                 :             :          * initial index size exceeds maintenance_work_mem, or the number of
     155                 :             :          * buffers usable for the index, whichever is less.  (Limiting by the
     156                 :             :          * number of buffers should reduce thrashing between PG buffers and kernel
     157                 :             :          * buffers, which seems useful even if no physical I/O results.  Limiting
     158                 :             :          * by maintenance_work_mem is useful to allow easy testing of the sort
     159                 :             :          * code path, and may be useful to DBAs as an additional control knob.)
     160                 :             :          *
     161                 :             :          * NOTE: this test will need adjustment if a bucket is ever different from
     162                 :             :          * one page.  Also, "initial index size" accounting does not include the
     163                 :             :          * metapage, nor the first bitmap page.
     164                 :             :          */
     165                 :          29 :         sort_threshold = (maintenance_work_mem * (Size) 1024) / BLCKSZ;
     166         [ +  + ]:          29 :         if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
     167         [ +  - ]:          28 :                 sort_threshold = Min(sort_threshold, NBuffers);
     168                 :             :         else
     169         [ -  + ]:           1 :                 sort_threshold = Min(sort_threshold, NLocBuffer);
     170                 :             : 
     171         [ +  + ]:          29 :         if (num_buckets >= sort_threshold)
     172                 :           1 :                 buildstate.spool = _h_spoolinit(heap, index, num_buckets);
     173                 :             :         else
     174                 :          28 :                 buildstate.spool = NULL;
     175                 :             : 
     176                 :             :         /* prepare to build the index */
     177                 :          29 :         buildstate.indtuples = 0;
     178                 :          29 :         buildstate.heapRel = heap;
     179                 :             : 
     180                 :             :         /* do the heap scan */
     181                 :          29 :         reltuples = table_index_build_scan(heap, index, indexInfo, true, true,
     182                 :             :                                                                            hashbuildCallback,
     183                 :             :                                                                            &buildstate, NULL);
     184                 :          29 :         pgstat_progress_update_param(PROGRESS_CREATEIDX_TUPLES_TOTAL,
     185                 :          29 :                                                                  buildstate.indtuples);
     186                 :             : 
     187         [ +  + ]:          29 :         if (buildstate.spool)
     188                 :             :         {
     189                 :             :                 /* sort the tuples and insert them into the index */
     190                 :           1 :                 _h_indexbuild(buildstate.spool, buildstate.heapRel);
     191                 :           1 :                 _h_spooldestroy(buildstate.spool);
     192                 :           1 :         }
     193                 :             : 
     194                 :             :         /*
     195                 :             :          * Return statistics
     196                 :             :          */
     197                 :          29 :         result = palloc_object(IndexBuildResult);
     198                 :             : 
     199                 :          29 :         result->heap_tuples = reltuples;
     200                 :          29 :         result->index_tuples = buildstate.indtuples;
     201                 :             : 
     202                 :          58 :         return result;
     203                 :          29 : }
     204                 :             : 
     205                 :             : /*
     206                 :             :  *      hashbuildempty() -- build an empty hash index in the initialization fork
     207                 :             :  */
     208                 :             : void
     209                 :           1 : hashbuildempty(Relation index)
     210                 :             : {
     211                 :           1 :         _hash_init(index, 0, INIT_FORKNUM);
     212                 :           1 : }
     213                 :             : 
     214                 :             : /*
     215                 :             :  * Per-tuple callback for table_index_build_scan
     216                 :             :  */
     217                 :             : static void
     218                 :       82059 : hashbuildCallback(Relation index,
     219                 :             :                                   ItemPointer tid,
     220                 :             :                                   Datum *values,
     221                 :             :                                   bool *isnull,
     222                 :             :                                   bool tupleIsAlive,
     223                 :             :                                   void *state)
     224                 :             : {
     225                 :       82059 :         HashBuildState *buildstate = (HashBuildState *) state;
     226                 :       82059 :         Datum           index_values[1];
     227                 :       82059 :         bool            index_isnull[1];
     228                 :       82059 :         IndexTuple      itup;
     229                 :             : 
     230                 :             :         /* convert data to a hash key; on failure, do not insert anything */
     231   [ +  -  +  - ]:      164118 :         if (!_hash_convert_tuple(index,
     232                 :       82059 :                                                          values, isnull,
     233                 :       82059 :                                                          index_values, index_isnull))
     234                 :           0 :                 return;
     235                 :             : 
     236                 :             :         /* Either spool the tuple for sorting, or just put it into the index */
     237         [ +  + ]:       82059 :         if (buildstate->spool)
     238                 :       10000 :                 _h_spool(buildstate->spool, tid, index_values, index_isnull);
     239                 :             :         else
     240                 :             :         {
     241                 :             :                 /* form an index tuple and point it at the heap tuple */
     242                 :      144118 :                 itup = index_form_tuple(RelationGetDescr(index),
     243                 :       72059 :                                                                 index_values, index_isnull);
     244                 :       72059 :                 itup->t_tid = *tid;
     245                 :       72059 :                 _hash_doinsert(index, itup, buildstate->heapRel, false);
     246                 :       72059 :                 pfree(itup);
     247                 :             :         }
     248                 :             : 
     249                 :       82059 :         buildstate->indtuples += 1;
     250         [ -  + ]:       82059 : }
     251                 :             : 
     252                 :             : /*
     253                 :             :  *      hashinsert() -- insert an index tuple into a hash table.
     254                 :             :  *
     255                 :             :  *      Hash on the heap tuple's key, form an index tuple with hash code.
     256                 :             :  *      Find the appropriate location for the new tuple, and put it there.
     257                 :             :  */
     258                 :             : bool
     259                 :       37557 : hashinsert(Relation rel, Datum *values, bool *isnull,
     260                 :             :                    ItemPointer ht_ctid, Relation heapRel,
     261                 :             :                    IndexUniqueCheck checkUnique,
     262                 :             :                    bool indexUnchanged,
     263                 :             :                    IndexInfo *indexInfo)
     264                 :             : {
     265                 :       37557 :         Datum           index_values[1];
     266                 :       37557 :         bool            index_isnull[1];
     267                 :       37557 :         IndexTuple      itup;
     268                 :             : 
     269                 :             :         /* convert data to a hash key; on failure, do not insert anything */
     270   [ -  +  -  + ]:       75114 :         if (!_hash_convert_tuple(rel,
     271                 :       37557 :                                                          values, isnull,
     272                 :       37557 :                                                          index_values, index_isnull))
     273                 :           0 :                 return false;
     274                 :             : 
     275                 :             :         /* form an index tuple and point it at the heap tuple */
     276                 :       37557 :         itup = index_form_tuple(RelationGetDescr(rel), index_values, index_isnull);
     277                 :       37557 :         itup->t_tid = *ht_ctid;
     278                 :             : 
     279                 :       37557 :         _hash_doinsert(rel, itup, heapRel, false);
     280                 :             : 
     281                 :       37557 :         pfree(itup);
     282                 :             : 
     283                 :       37557 :         return false;
     284                 :       37557 : }
     285                 :             : 
     286                 :             : 
     287                 :             : /*
     288                 :             :  *      hashgettuple() -- Get the next tuple in the scan.
     289                 :             :  */
     290                 :             : bool
     291                 :       16588 : hashgettuple(IndexScanDesc scan, ScanDirection dir)
     292                 :             : {
     293                 :       16588 :         HashScanOpaque so = (HashScanOpaque) scan->opaque;
     294                 :       16588 :         bool            res;
     295                 :             : 
     296                 :             :         /* Hash indexes are always lossy since we store only the hash code */
     297                 :       16588 :         scan->xs_recheck = true;
     298                 :             : 
     299                 :             :         /*
     300                 :             :          * If we've already initialized this scan, we can just advance it in the
     301                 :             :          * appropriate direction.  If we haven't done so yet, we call a routine to
     302                 :             :          * get the first item in the scan.
     303                 :             :          */
     304   [ +  +  +  -  :       16588 :         if (!HashScanPosIsValid(so->currPos))
                   +  + ]
     305                 :          50 :                 res = _hash_first(scan, dir);
     306                 :             :         else
     307                 :             :         {
     308                 :             :                 /*
     309                 :             :                  * Check to see if we should kill the previously-fetched tuple.
     310                 :             :                  */
     311         [ +  - ]:       16538 :                 if (scan->kill_prior_tuple)
     312                 :             :                 {
     313                 :             :                         /*
     314                 :             :                          * Yes, so remember it for later. (We'll deal with all such tuples
     315                 :             :                          * at once right after leaving the index page or at end of scan.)
     316                 :             :                          * In case if caller reverses the indexscan direction it is quite
     317                 :             :                          * possible that the same item might get entered multiple times.
     318                 :             :                          * But, we don't detect that; instead, we just forget any excess
     319                 :             :                          * entries.
     320                 :             :                          */
     321         [ #  # ]:           0 :                         if (so->killedItems == NULL)
     322                 :           0 :                                 so->killedItems = palloc_array(int, MaxIndexTuplesPerPage);
     323                 :             : 
     324         [ #  # ]:           0 :                         if (so->numKilled < MaxIndexTuplesPerPage)
     325                 :           0 :                                 so->killedItems[so->numKilled++] = so->currPos.itemIndex;
     326                 :           0 :                 }
     327                 :             : 
     328                 :             :                 /*
     329                 :             :                  * Now continue the scan.
     330                 :             :                  */
     331                 :       16538 :                 res = _hash_next(scan, dir);
     332                 :             :         }
     333                 :             : 
     334                 :       33176 :         return res;
     335                 :       16588 : }
     336                 :             : 
     337                 :             : 
     338                 :             : /*
     339                 :             :  *      hashgetbitmap() -- get all tuples at once
     340                 :             :  */
     341                 :             : int64
     342                 :          10 : hashgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
     343                 :             : {
     344                 :          10 :         HashScanOpaque so = (HashScanOpaque) scan->opaque;
     345                 :          10 :         bool            res;
     346                 :          10 :         int64           ntids = 0;
     347                 :          10 :         HashScanPosItem *currItem;
     348                 :             : 
     349                 :          10 :         res = _hash_first(scan, ForwardScanDirection);
     350                 :             : 
     351         [ +  + ]:          31 :         while (res)
     352                 :             :         {
     353                 :          21 :                 currItem = &so->currPos.items[so->currPos.itemIndex];
     354                 :             : 
     355                 :             :                 /*
     356                 :             :                  * _hash_first and _hash_next handle eliminate dead index entries
     357                 :             :                  * whenever scan->ignore_killed_tuples is true.  Therefore, there's
     358                 :             :                  * nothing to do here except add the results to the TIDBitmap.
     359                 :             :                  */
     360                 :          21 :                 tbm_add_tuples(tbm, &(currItem->heapTid), 1, true);
     361                 :          21 :                 ntids++;
     362                 :             : 
     363                 :          21 :                 res = _hash_next(scan, ForwardScanDirection);
     364                 :             :         }
     365                 :             : 
     366                 :          20 :         return ntids;
     367                 :          10 : }
     368                 :             : 
     369                 :             : 
     370                 :             : /*
     371                 :             :  *      hashbeginscan() -- start a scan on a hash index
     372                 :             :  */
     373                 :             : IndexScanDesc
     374                 :          34 : hashbeginscan(Relation rel, int nkeys, int norderbys)
     375                 :             : {
     376                 :          34 :         IndexScanDesc scan;
     377                 :          34 :         HashScanOpaque so;
     378                 :             : 
     379                 :             :         /* no order by operators allowed */
     380         [ +  - ]:          34 :         Assert(norderbys == 0);
     381                 :             : 
     382                 :          34 :         scan = RelationGetIndexScan(rel, nkeys, norderbys);
     383                 :             : 
     384                 :          34 :         so = (HashScanOpaque) palloc_object(HashScanOpaqueData);
     385                 :          34 :         HashScanPosInvalidate(so->currPos);
     386                 :          34 :         so->hashso_bucket_buf = InvalidBuffer;
     387                 :          34 :         so->hashso_split_bucket_buf = InvalidBuffer;
     388                 :             : 
     389                 :          34 :         so->hashso_buc_populated = false;
     390                 :          34 :         so->hashso_buc_split = false;
     391                 :             : 
     392                 :          34 :         so->killedItems = NULL;
     393                 :          34 :         so->numKilled = 0;
     394                 :             : 
     395                 :          34 :         scan->opaque = so;
     396                 :             : 
     397                 :          68 :         return scan;
     398                 :          34 : }
     399                 :             : 
     400                 :             : /*
     401                 :             :  *      hashrescan() -- rescan an index relation
     402                 :             :  */
     403                 :             : void
     404                 :          59 : hashrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
     405                 :             :                    ScanKey orderbys, int norderbys)
     406                 :             : {
     407                 :          59 :         HashScanOpaque so = (HashScanOpaque) scan->opaque;
     408                 :          59 :         Relation        rel = scan->indexRelation;
     409                 :             : 
     410   [ +  +  +  -  :          59 :         if (HashScanPosIsValid(so->currPos))
                   +  + ]
     411                 :             :         {
     412                 :             :                 /* Before leaving current page, deal with any killed items */
     413         [ +  - ]:          10 :                 if (so->numKilled > 0)
     414                 :           0 :                         _hash_kill_items(scan);
     415                 :          10 :         }
     416                 :             : 
     417                 :          59 :         _hash_dropscanbuf(rel, so);
     418                 :             : 
     419                 :             :         /* set position invalid (this will cause _hash_first call) */
     420                 :          59 :         HashScanPosInvalidate(so->currPos);
     421                 :             : 
     422                 :             :         /* Update scan key, if a new one is given */
     423   [ +  -  -  + ]:          59 :         if (scankey && scan->numberOfKeys > 0)
     424                 :          59 :                 memcpy(scan->keyData, scankey, scan->numberOfKeys * sizeof(ScanKeyData));
     425                 :             : 
     426                 :          59 :         so->hashso_buc_populated = false;
     427                 :          59 :         so->hashso_buc_split = false;
     428                 :          59 : }
     429                 :             : 
     430                 :             : /*
     431                 :             :  *      hashendscan() -- close down a scan
     432                 :             :  */
     433                 :             : void
     434                 :          34 : hashendscan(IndexScanDesc scan)
     435                 :             : {
     436                 :          34 :         HashScanOpaque so = (HashScanOpaque) scan->opaque;
     437                 :          34 :         Relation        rel = scan->indexRelation;
     438                 :             : 
     439   [ +  +  +  -  :          34 :         if (HashScanPosIsValid(so->currPos))
                   +  + ]
     440                 :             :         {
     441                 :             :                 /* Before leaving current page, deal with any killed items */
     442         [ +  - ]:           9 :                 if (so->numKilled > 0)
     443                 :           0 :                         _hash_kill_items(scan);
     444                 :           9 :         }
     445                 :             : 
     446                 :          34 :         _hash_dropscanbuf(rel, so);
     447                 :             : 
     448         [ +  - ]:          34 :         if (so->killedItems != NULL)
     449                 :           0 :                 pfree(so->killedItems);
     450                 :          34 :         pfree(so);
     451                 :          34 :         scan->opaque = NULL;
     452                 :          34 : }
     453                 :             : 
     454                 :             : /*
     455                 :             :  * Bulk deletion of all index entries pointing to a set of heap tuples.
     456                 :             :  * The set of target tuples is specified via a callback routine that tells
     457                 :             :  * whether any given heap tuple (identified by ItemPointer) is being deleted.
     458                 :             :  *
     459                 :             :  * This function also deletes the tuples that are moved by split to other
     460                 :             :  * bucket.
     461                 :             :  *
     462                 :             :  * Result: a palloc'd struct containing statistical info for VACUUM displays.
     463                 :             :  */
     464                 :             : IndexBulkDeleteResult *
     465                 :           4 : hashbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
     466                 :             :                            IndexBulkDeleteCallback callback, void *callback_state)
     467                 :             : {
     468                 :           4 :         Relation        rel = info->index;
     469                 :           4 :         double          tuples_removed;
     470                 :           4 :         double          num_index_tuples;
     471                 :           4 :         double          orig_ntuples;
     472                 :           4 :         Bucket          orig_maxbucket;
     473                 :           4 :         Bucket          cur_maxbucket;
     474                 :           4 :         Bucket          cur_bucket;
     475                 :           4 :         Buffer          metabuf = InvalidBuffer;
     476                 :           4 :         HashMetaPage metap;
     477                 :           4 :         HashMetaPage cachedmetap;
     478                 :             : 
     479                 :           4 :         tuples_removed = 0;
     480                 :           4 :         num_index_tuples = 0;
     481                 :             : 
     482                 :             :         /*
     483                 :             :          * We need a copy of the metapage so that we can use its hashm_spares[]
     484                 :             :          * values to compute bucket page addresses, but a cached copy should be
     485                 :             :          * good enough.  (If not, we'll detect that further down and refresh the
     486                 :             :          * cache as necessary.)
     487                 :             :          */
     488                 :           4 :         cachedmetap = _hash_getcachedmetap(rel, &metabuf, false);
     489         [ +  - ]:           4 :         Assert(cachedmetap != NULL);
     490                 :             : 
     491                 :           4 :         orig_maxbucket = cachedmetap->hashm_maxbucket;
     492                 :           4 :         orig_ntuples = cachedmetap->hashm_ntuples;
     493                 :             : 
     494                 :             :         /* Scan the buckets that we know exist */
     495                 :           4 :         cur_bucket = 0;
     496                 :           4 :         cur_maxbucket = orig_maxbucket;
     497                 :             : 
     498                 :             : loop_top:
     499         [ +  + ]:          35 :         while (cur_bucket <= cur_maxbucket)
     500                 :             :         {
     501                 :          31 :                 BlockNumber bucket_blkno;
     502                 :          31 :                 BlockNumber blkno;
     503                 :          31 :                 Buffer          bucket_buf;
     504                 :          31 :                 Buffer          buf;
     505                 :          31 :                 HashPageOpaque bucket_opaque;
     506                 :          31 :                 Page            page;
     507                 :          31 :                 bool            split_cleanup = false;
     508                 :             : 
     509                 :             :                 /* Get address of bucket's start page */
     510         [ +  + ]:          31 :                 bucket_blkno = BUCKET_TO_BLKNO(cachedmetap, cur_bucket);
     511                 :             : 
     512                 :          31 :                 blkno = bucket_blkno;
     513                 :             : 
     514                 :             :                 /*
     515                 :             :                  * We need to acquire a cleanup lock on the primary bucket page to out
     516                 :             :                  * wait concurrent scans before deleting the dead tuples.
     517                 :             :                  */
     518                 :          31 :                 buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, info->strategy);
     519                 :          31 :                 LockBufferForCleanup(buf);
     520                 :          31 :                 _hash_checkpage(rel, buf, LH_BUCKET_PAGE);
     521                 :             : 
     522                 :          31 :                 page = BufferGetPage(buf);
     523                 :          31 :                 bucket_opaque = HashPageGetOpaque(page);
     524                 :             : 
     525                 :             :                 /*
     526                 :             :                  * If the bucket contains tuples that are moved by split, then we need
     527                 :             :                  * to delete such tuples.  We can't delete such tuples if the split
     528                 :             :                  * operation on bucket is not finished as those are needed by scans.
     529                 :             :                  */
     530   [ +  -  +  - ]:          31 :                 if (!H_BUCKET_BEING_SPLIT(bucket_opaque) &&
     531                 :          31 :                         H_NEEDS_SPLIT_CLEANUP(bucket_opaque))
     532                 :             :                 {
     533                 :           0 :                         split_cleanup = true;
     534                 :             : 
     535                 :             :                         /*
     536                 :             :                          * This bucket might have been split since we last held a lock on
     537                 :             :                          * the metapage.  If so, hashm_maxbucket, hashm_highmask and
     538                 :             :                          * hashm_lowmask might be old enough to cause us to fail to remove
     539                 :             :                          * tuples left behind by the most recent split.  To prevent that,
     540                 :             :                          * now that the primary page of the target bucket has been locked
     541                 :             :                          * (and thus can't be further split), check whether we need to
     542                 :             :                          * update our cached metapage data.
     543                 :             :                          */
     544         [ #  # ]:           0 :                         Assert(bucket_opaque->hasho_prevblkno != InvalidBlockNumber);
     545         [ #  # ]:           0 :                         if (bucket_opaque->hasho_prevblkno > cachedmetap->hashm_maxbucket)
     546                 :             :                         {
     547                 :           0 :                                 cachedmetap = _hash_getcachedmetap(rel, &metabuf, true);
     548         [ #  # ]:           0 :                                 Assert(cachedmetap != NULL);
     549                 :           0 :                         }
     550                 :           0 :                 }
     551                 :             : 
     552                 :          31 :                 bucket_buf = buf;
     553                 :             : 
     554                 :          62 :                 hashbucketcleanup(rel, cur_bucket, bucket_buf, blkno, info->strategy,
     555                 :          31 :                                                   cachedmetap->hashm_maxbucket,
     556                 :          31 :                                                   cachedmetap->hashm_highmask,
     557                 :          31 :                                                   cachedmetap->hashm_lowmask, &tuples_removed,
     558                 :          31 :                                                   &num_index_tuples, split_cleanup,
     559                 :          31 :                                                   callback, callback_state);
     560                 :             : 
     561                 :          31 :                 _hash_dropbuf(rel, bucket_buf);
     562                 :             : 
     563                 :             :                 /* Advance to next bucket */
     564                 :          31 :                 cur_bucket++;
     565                 :          31 :         }
     566                 :             : 
     567         [ -  + ]:           4 :         if (BufferIsInvalid(metabuf))
     568                 :           4 :                 metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_NOLOCK, LH_META_PAGE);
     569                 :             : 
     570                 :             :         /* Write-lock metapage and check for split since we started */
     571                 :           4 :         LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);
     572                 :           4 :         metap = HashPageGetMeta(BufferGetPage(metabuf));
     573                 :             : 
     574         [ -  + ]:           4 :         if (cur_maxbucket != metap->hashm_maxbucket)
     575                 :             :         {
     576                 :             :                 /* There's been a split, so process the additional bucket(s) */
     577                 :           0 :                 LockBuffer(metabuf, BUFFER_LOCK_UNLOCK);
     578                 :           0 :                 cachedmetap = _hash_getcachedmetap(rel, &metabuf, true);
     579         [ #  # ]:           0 :                 Assert(cachedmetap != NULL);
     580                 :           0 :                 cur_maxbucket = cachedmetap->hashm_maxbucket;
     581                 :           0 :                 goto loop_top;
     582                 :             :         }
     583                 :             : 
     584                 :             :         /* Okay, we're really done.  Update tuple count in metapage. */
     585                 :           4 :         START_CRIT_SECTION();
     586                 :             : 
     587   [ +  -  -  + ]:           4 :         if (orig_maxbucket == metap->hashm_maxbucket &&
     588                 :           4 :                 orig_ntuples == metap->hashm_ntuples)
     589                 :             :         {
     590                 :             :                 /*
     591                 :             :                  * No one has split or inserted anything since start of scan, so
     592                 :             :                  * believe our count as gospel.
     593                 :             :                  */
     594                 :           0 :                 metap->hashm_ntuples = num_index_tuples;
     595                 :           0 :         }
     596                 :             :         else
     597                 :             :         {
     598                 :             :                 /*
     599                 :             :                  * Otherwise, our count is untrustworthy since we may have
     600                 :             :                  * double-scanned tuples in split buckets.  Proceed by dead-reckoning.
     601                 :             :                  * (Note: we still return estimated_count = false, because using this
     602                 :             :                  * count is better than not updating reltuples at all.)
     603                 :             :                  */
     604         [ +  - ]:           4 :                 if (metap->hashm_ntuples > tuples_removed)
     605                 :           4 :                         metap->hashm_ntuples -= tuples_removed;
     606                 :             :                 else
     607                 :           0 :                         metap->hashm_ntuples = 0;
     608                 :           4 :                 num_index_tuples = metap->hashm_ntuples;
     609                 :             :         }
     610                 :             : 
     611                 :           4 :         MarkBufferDirty(metabuf);
     612                 :             : 
     613                 :             :         /* XLOG stuff */
     614   [ +  -  +  -  :           4 :         if (RelationNeedsWAL(rel))
             +  -  -  + ]
     615                 :             :         {
     616                 :           4 :                 xl_hash_update_meta_page xlrec;
     617                 :           4 :                 XLogRecPtr      recptr;
     618                 :             : 
     619                 :           4 :                 xlrec.ntuples = metap->hashm_ntuples;
     620                 :             : 
     621                 :           4 :                 XLogBeginInsert();
     622                 :           4 :                 XLogRegisterData(&xlrec, SizeOfHashUpdateMetaPage);
     623                 :             : 
     624                 :           4 :                 XLogRegisterBuffer(0, metabuf, REGBUF_STANDARD);
     625                 :             : 
     626                 :           4 :                 recptr = XLogInsert(RM_HASH_ID, XLOG_HASH_UPDATE_META_PAGE);
     627                 :           4 :                 PageSetLSN(BufferGetPage(metabuf), recptr);
     628                 :           4 :         }
     629                 :             : 
     630         [ +  - ]:           4 :         END_CRIT_SECTION();
     631                 :             : 
     632                 :           4 :         _hash_relbuf(rel, metabuf);
     633                 :             : 
     634                 :             :         /* return statistics */
     635         [ -  + ]:           4 :         if (stats == NULL)
     636                 :           4 :                 stats = palloc0_object(IndexBulkDeleteResult);
     637                 :           4 :         stats->estimated_count = false;
     638                 :           4 :         stats->num_index_tuples = num_index_tuples;
     639                 :           4 :         stats->tuples_removed += tuples_removed;
     640                 :             :         /* hashvacuumcleanup will fill in num_pages */
     641                 :             : 
     642                 :           8 :         return stats;
     643                 :           4 : }
     644                 :             : 
     645                 :             : /*
     646                 :             :  * Post-VACUUM cleanup.
     647                 :             :  *
     648                 :             :  * Result: a palloc'd struct containing statistical info for VACUUM displays.
     649                 :             :  */
     650                 :             : IndexBulkDeleteResult *
     651                 :           7 : hashvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
     652                 :             : {
     653                 :           7 :         Relation        rel = info->index;
     654                 :           7 :         BlockNumber num_pages;
     655                 :             : 
     656                 :             :         /* If hashbulkdelete wasn't called, return NULL signifying no change */
     657                 :             :         /* Note: this covers the analyze_only case too */
     658         [ +  + ]:           7 :         if (stats == NULL)
     659                 :           3 :                 return NULL;
     660                 :             : 
     661                 :             :         /* update statistics */
     662                 :           4 :         num_pages = RelationGetNumberOfBlocks(rel);
     663                 :           4 :         stats->num_pages = num_pages;
     664                 :             : 
     665                 :           4 :         return stats;
     666                 :           7 : }
     667                 :             : 
     668                 :             : /*
     669                 :             :  * Helper function to perform deletion of index entries from a bucket.
     670                 :             :  *
     671                 :             :  * This function expects that the caller has acquired a cleanup lock on the
     672                 :             :  * primary bucket page, and will return with a write lock again held on the
     673                 :             :  * primary bucket page.  The lock won't necessarily be held continuously,
     674                 :             :  * though, because we'll release it when visiting overflow pages.
     675                 :             :  *
     676                 :             :  * There can't be any concurrent scans in progress when we first enter this
     677                 :             :  * function because of the cleanup lock we hold on the primary bucket page,
     678                 :             :  * but as soon as we release that lock, there might be.  If those scans got
     679                 :             :  * ahead of our cleanup scan, they might see a tuple before we kill it and
     680                 :             :  * wake up only after VACUUM has completed and the TID has been recycled for
     681                 :             :  * an unrelated tuple.  To avoid that calamity, we prevent scans from passing
     682                 :             :  * our cleanup scan by locking the next page in the bucket chain before
     683                 :             :  * releasing the lock on the previous page.  (This type of lock chaining is not
     684                 :             :  * ideal, so we might want to look for a better solution at some point.)
     685                 :             :  *
     686                 :             :  * We need to retain a pin on the primary bucket to ensure that no concurrent
     687                 :             :  * split can start.
     688                 :             :  */
     689                 :             : void
     690                 :         252 : hashbucketcleanup(Relation rel, Bucket cur_bucket, Buffer bucket_buf,
     691                 :             :                                   BlockNumber bucket_blkno, BufferAccessStrategy bstrategy,
     692                 :             :                                   uint32 maxbucket, uint32 highmask, uint32 lowmask,
     693                 :             :                                   double *tuples_removed, double *num_index_tuples,
     694                 :             :                                   bool split_cleanup,
     695                 :             :                                   IndexBulkDeleteCallback callback, void *callback_state)
     696                 :             : {
     697                 :         252 :         BlockNumber blkno;
     698                 :         252 :         Buffer          buf;
     699                 :         252 :         Bucket          new_bucket PG_USED_FOR_ASSERTS_ONLY = InvalidBucket;
     700                 :         252 :         bool            bucket_dirty = false;
     701                 :             : 
     702                 :         252 :         blkno = bucket_blkno;
     703                 :         252 :         buf = bucket_buf;
     704                 :             : 
     705         [ +  + ]:         252 :         if (split_cleanup)
     706                 :         442 :                 new_bucket = _hash_get_newbucket_from_oldbucket(rel, cur_bucket,
     707                 :         221 :                                                                                                                 lowmask, maxbucket);
     708                 :             : 
     709                 :             :         /* Scan each page in bucket */
     710                 :         322 :         for (;;)
     711                 :             :         {
     712                 :         322 :                 HashPageOpaque opaque;
     713                 :         322 :                 OffsetNumber offno;
     714                 :         322 :                 OffsetNumber maxoffno;
     715                 :         322 :                 Buffer          next_buf;
     716                 :         322 :                 Page            page;
     717                 :         322 :                 OffsetNumber deletable[MaxOffsetNumber];
     718                 :         322 :                 int                     ndeletable = 0;
     719                 :         322 :                 bool            retain_pin = false;
     720                 :         322 :                 bool            clear_dead_marking = false;
     721                 :             : 
     722                 :         322 :                 vacuum_delay_point(false);
     723                 :             : 
     724                 :         322 :                 page = BufferGetPage(buf);
     725                 :         322 :                 opaque = HashPageGetOpaque(page);
     726                 :             : 
     727                 :             :                 /* Scan each tuple in page */
     728                 :         322 :                 maxoffno = PageGetMaxOffsetNumber(page);
     729         [ +  + ]:       58184 :                 for (offno = FirstOffsetNumber;
     730                 :       58184 :                          offno <= maxoffno;
     731                 :       57862 :                          offno = OffsetNumberNext(offno))
     732                 :             :                 {
     733                 :       57862 :                         ItemPointer htup;
     734                 :       57862 :                         IndexTuple      itup;
     735                 :       57862 :                         Bucket          bucket;
     736                 :       57862 :                         bool            kill_tuple = false;
     737                 :             : 
     738                 :      115724 :                         itup = (IndexTuple) PageGetItem(page,
     739                 :       57862 :                                                                                         PageGetItemId(page, offno));
     740                 :       57862 :                         htup = &(itup->t_tid);
     741                 :             : 
     742                 :             :                         /*
     743                 :             :                          * To remove the dead tuples, we strictly want to rely on results
     744                 :             :                          * of callback function.  refer btvacuumpage for detailed reason.
     745                 :             :                          */
     746   [ +  +  +  + ]:       57862 :                         if (callback && callback(htup, callback_state))
     747                 :             :                         {
     748                 :        4999 :                                 kill_tuple = true;
     749         [ -  + ]:        4999 :                                 if (tuples_removed)
     750                 :        4999 :                                         *tuples_removed += 1;
     751                 :        4999 :                         }
     752         [ +  + ]:       52863 :                         else if (split_cleanup)
     753                 :             :                         {
     754                 :             :                                 /* delete the tuples that are moved by split. */
     755                 :      100526 :                                 bucket = _hash_hashkey2bucket(_hash_get_indextuple_hashkey(itup),
     756                 :       50263 :                                                                                           maxbucket,
     757                 :       50263 :                                                                                           highmask,
     758                 :       50263 :                                                                                           lowmask);
     759                 :             :                                 /* mark the item for deletion */
     760         [ +  + ]:       50263 :                                 if (bucket != cur_bucket)
     761                 :             :                                 {
     762                 :             :                                         /*
     763                 :             :                                          * We expect tuples to either belong to current bucket or
     764                 :             :                                          * new_bucket.  This is ensured because we don't allow
     765                 :             :                                          * further splits from bucket that contains garbage. See
     766                 :             :                                          * comments in _hash_expandtable.
     767                 :             :                                          */
     768         [ -  + ]:       20512 :                                         Assert(bucket == new_bucket);
     769                 :       20512 :                                         kill_tuple = true;
     770                 :       20512 :                                 }
     771                 :       50263 :                         }
     772                 :             : 
     773         [ +  + ]:       57862 :                         if (kill_tuple)
     774                 :             :                         {
     775                 :             :                                 /* mark the item for deletion */
     776                 :       25511 :                                 deletable[ndeletable++] = offno;
     777                 :       25511 :                         }
     778                 :             :                         else
     779                 :             :                         {
     780                 :             :                                 /* we're keeping it, so count it */
     781         [ +  + ]:       32351 :                                 if (num_index_tuples)
     782                 :        2600 :                                         *num_index_tuples += 1;
     783                 :             :                         }
     784                 :       57862 :                 }
     785                 :             : 
     786                 :             :                 /* retain the pin on primary bucket page till end of bucket scan */
     787         [ +  + ]:         322 :                 if (blkno == bucket_blkno)
     788                 :         252 :                         retain_pin = true;
     789                 :             :                 else
     790                 :          70 :                         retain_pin = false;
     791                 :             : 
     792                 :         322 :                 blkno = opaque->hasho_nextblkno;
     793                 :             : 
     794                 :             :                 /*
     795                 :             :                  * Apply deletions, advance to next page and write page if needed.
     796                 :             :                  */
     797         [ +  + ]:         322 :                 if (ndeletable > 0)
     798                 :             :                 {
     799                 :             :                         /* No ereport(ERROR) until changes are logged */
     800                 :         248 :                         START_CRIT_SECTION();
     801                 :             : 
     802                 :         248 :                         PageIndexMultiDelete(page, deletable, ndeletable);
     803                 :         248 :                         bucket_dirty = true;
     804                 :             : 
     805                 :             :                         /*
     806                 :             :                          * Let us mark the page as clean if vacuum removes the DEAD tuples
     807                 :             :                          * from an index page. We do this by clearing
     808                 :             :                          * LH_PAGE_HAS_DEAD_TUPLES flag.
     809                 :             :                          */
     810   [ +  +  +  -  :         248 :                         if (tuples_removed && *tuples_removed > 0 &&
                   +  - ]
     811                 :          18 :                                 H_HAS_DEAD_TUPLES(opaque))
     812                 :             :                         {
     813                 :           0 :                                 opaque->hasho_flag &= ~LH_PAGE_HAS_DEAD_TUPLES;
     814                 :           0 :                                 clear_dead_marking = true;
     815                 :           0 :                         }
     816                 :             : 
     817                 :         248 :                         MarkBufferDirty(buf);
     818                 :             : 
     819                 :             :                         /* XLOG stuff */
     820   [ +  -  +  -  :         248 :                         if (RelationNeedsWAL(rel))
             +  +  +  + ]
     821                 :             :                         {
     822                 :         122 :                                 xl_hash_delete xlrec;
     823                 :         122 :                                 XLogRecPtr      recptr;
     824                 :             : 
     825                 :         122 :                                 xlrec.clear_dead_marking = clear_dead_marking;
     826                 :         122 :                                 xlrec.is_primary_bucket_page = (buf == bucket_buf);
     827                 :             : 
     828                 :         122 :                                 XLogBeginInsert();
     829                 :         122 :                                 XLogRegisterData(&xlrec, SizeOfHashDelete);
     830                 :             : 
     831                 :             :                                 /*
     832                 :             :                                  * bucket buffer was not changed, but still needs to be
     833                 :             :                                  * registered to ensure that we can acquire a cleanup lock on
     834                 :             :                                  * it during replay.
     835                 :             :                                  */
     836         [ +  + ]:         122 :                                 if (!xlrec.is_primary_bucket_page)
     837                 :             :                                 {
     838                 :          32 :                                         uint8           flags = REGBUF_STANDARD | REGBUF_NO_IMAGE | REGBUF_NO_CHANGE;
     839                 :             : 
     840                 :          32 :                                         XLogRegisterBuffer(0, bucket_buf, flags);
     841                 :          32 :                                 }
     842                 :             : 
     843                 :         122 :                                 XLogRegisterBuffer(1, buf, REGBUF_STANDARD);
     844                 :         244 :                                 XLogRegisterBufData(1, deletable,
     845                 :         122 :                                                                         ndeletable * sizeof(OffsetNumber));
     846                 :             : 
     847                 :         122 :                                 recptr = XLogInsert(RM_HASH_ID, XLOG_HASH_DELETE);
     848                 :         122 :                                 PageSetLSN(BufferGetPage(buf), recptr);
     849                 :         122 :                         }
     850                 :             : 
     851         [ -  + ]:         248 :                         END_CRIT_SECTION();
     852                 :         248 :                 }
     853                 :             : 
     854                 :             :                 /* bail out if there are no more pages to scan. */
     855         [ +  + ]:         322 :                 if (!BlockNumberIsValid(blkno))
     856                 :         252 :                         break;
     857                 :             : 
     858                 :         140 :                 next_buf = _hash_getbuf_with_strategy(rel, blkno, HASH_WRITE,
     859                 :             :                                                                                           LH_OVERFLOW_PAGE,
     860                 :          70 :                                                                                           bstrategy);
     861                 :             : 
     862                 :             :                 /*
     863                 :             :                  * release the lock on previous page after acquiring the lock on next
     864                 :             :                  * page
     865                 :             :                  */
     866         [ +  + ]:          70 :                 if (retain_pin)
     867                 :          13 :                         LockBuffer(buf, BUFFER_LOCK_UNLOCK);
     868                 :             :                 else
     869                 :          57 :                         _hash_relbuf(rel, buf);
     870                 :             : 
     871                 :          70 :                 buf = next_buf;
     872      [ -  +  + ]:         322 :         }
     873                 :             : 
     874                 :             :         /*
     875                 :             :          * lock the bucket page to clear the garbage flag and squeeze the bucket.
     876                 :             :          * if the current buffer is same as bucket buffer, then we already have
     877                 :             :          * lock on bucket page.
     878                 :             :          */
     879         [ +  + ]:         252 :         if (buf != bucket_buf)
     880                 :             :         {
     881                 :          13 :                 _hash_relbuf(rel, buf);
     882                 :          13 :                 LockBuffer(bucket_buf, BUFFER_LOCK_EXCLUSIVE);
     883                 :          13 :         }
     884                 :             : 
     885                 :             :         /*
     886                 :             :          * Clear the garbage flag from bucket after deleting the tuples that are
     887                 :             :          * moved by split.  We purposefully clear the flag before squeeze bucket,
     888                 :             :          * so that after restart, vacuum shouldn't again try to delete the moved
     889                 :             :          * by split tuples.
     890                 :             :          */
     891         [ +  + ]:         252 :         if (split_cleanup)
     892                 :             :         {
     893                 :         221 :                 HashPageOpaque bucket_opaque;
     894                 :         221 :                 Page            page;
     895                 :             : 
     896                 :         221 :                 page = BufferGetPage(bucket_buf);
     897                 :         221 :                 bucket_opaque = HashPageGetOpaque(page);
     898                 :             : 
     899                 :             :                 /* No ereport(ERROR) until changes are logged */
     900                 :         221 :                 START_CRIT_SECTION();
     901                 :             : 
     902                 :         221 :                 bucket_opaque->hasho_flag &= ~LH_BUCKET_NEEDS_SPLIT_CLEANUP;
     903                 :         221 :                 MarkBufferDirty(bucket_buf);
     904                 :             : 
     905                 :             :                 /* XLOG stuff */
     906   [ +  -  +  -  :         221 :                 if (RelationNeedsWAL(rel))
             +  +  +  + ]
     907                 :             :                 {
     908                 :          95 :                         XLogRecPtr      recptr;
     909                 :             : 
     910                 :          95 :                         XLogBeginInsert();
     911                 :          95 :                         XLogRegisterBuffer(0, bucket_buf, REGBUF_STANDARD);
     912                 :             : 
     913                 :          95 :                         recptr = XLogInsert(RM_HASH_ID, XLOG_HASH_SPLIT_CLEANUP);
     914                 :          95 :                         PageSetLSN(page, recptr);
     915                 :          95 :                 }
     916                 :             : 
     917         [ +  - ]:         221 :                 END_CRIT_SECTION();
     918                 :         221 :         }
     919                 :             : 
     920                 :             :         /*
     921                 :             :          * If we have deleted anything, try to compact free space.  For squeezing
     922                 :             :          * the bucket, we must have a cleanup lock, else it can impact the
     923                 :             :          * ordering of tuples for a scan that has started before it.
     924                 :             :          */
     925   [ +  +  -  + ]:         252 :         if (bucket_dirty && IsBufferCleanupOK(bucket_buf))
     926                 :         440 :                 _hash_squeezebucket(rel, cur_bucket, bucket_blkno, bucket_buf,
     927                 :         220 :                                                         bstrategy);
     928                 :             :         else
     929                 :          32 :                 LockBuffer(bucket_buf, BUFFER_LOCK_UNLOCK);
     930                 :         252 : }
     931                 :             : 
     932                 :             : CompareType
     933                 :           0 : hashtranslatestrategy(StrategyNumber strategy, Oid opfamily)
     934                 :             : {
     935         [ #  # ]:           0 :         if (strategy == HTEqualStrategyNumber)
     936                 :           0 :                 return COMPARE_EQ;
     937                 :           0 :         return COMPARE_INVALID;
     938                 :           0 : }
     939                 :             : 
     940                 :             : StrategyNumber
     941                 :           0 : hashtranslatecmptype(CompareType cmptype, Oid opfamily)
     942                 :             : {
     943         [ #  # ]:           0 :         if (cmptype == COMPARE_EQ)
     944                 :           0 :                 return HTEqualStrategyNumber;
     945                 :           0 :         return InvalidStrategy;
     946                 :           0 : }
        

Generated by: LCOV version 2.3.2-1