LCOV - code coverage report
Current view: top level - src/backend/access/gin - ginutil.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 204 230 88.7 %
Date: 2025-12-31 02:17:15 Functions: 15 16 93.8 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * ginutil.c
       4             :  *    Utility routines for the Postgres inverted index access method.
       5             :  *
       6             :  *
       7             :  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
       8             :  * Portions Copyright (c) 1994, Regents of the University of California
       9             :  *
      10             :  * IDENTIFICATION
      11             :  *          src/backend/access/gin/ginutil.c
      12             :  *-------------------------------------------------------------------------
      13             :  */
      14             : 
      15             : #include "postgres.h"
      16             : 
      17             : #include "access/gin_private.h"
      18             : #include "access/ginxlog.h"
      19             : #include "access/reloptions.h"
      20             : #include "access/xloginsert.h"
      21             : #include "catalog/pg_collation.h"
      22             : #include "catalog/pg_type.h"
      23             : #include "commands/progress.h"
      24             : #include "commands/vacuum.h"
      25             : #include "miscadmin.h"
      26             : #include "storage/indexfsm.h"
      27             : #include "utils/builtins.h"
      28             : #include "utils/index_selfuncs.h"
      29             : #include "utils/rel.h"
      30             : #include "utils/typcache.h"
      31             : 
      32             : 
      33             : /*
      34             :  * GIN handler function: return IndexAmRoutine with access method parameters
      35             :  * and callbacks.
      36             :  */
      37             : Datum
      38        3906 : ginhandler(PG_FUNCTION_ARGS)
      39             : {
      40             :     static const IndexAmRoutine amroutine = {
      41             :         .type = T_IndexAmRoutine,
      42             :         .amstrategies = 0,
      43             :         .amsupport = GINNProcs,
      44             :         .amoptsprocnum = GIN_OPTIONS_PROC,
      45             :         .amcanorder = false,
      46             :         .amcanorderbyop = false,
      47             :         .amcanhash = false,
      48             :         .amconsistentequality = false,
      49             :         .amconsistentordering = false,
      50             :         .amcanbackward = false,
      51             :         .amcanunique = false,
      52             :         .amcanmulticol = true,
      53             :         .amoptionalkey = true,
      54             :         .amsearcharray = false,
      55             :         .amsearchnulls = false,
      56             :         .amstorage = true,
      57             :         .amclusterable = false,
      58             :         .ampredlocks = true,
      59             :         .amcanparallel = false,
      60             :         .amcanbuildparallel = true,
      61             :         .amcaninclude = false,
      62             :         .amusemaintenanceworkmem = true,
      63             :         .amsummarizing = false,
      64             :         .amparallelvacuumoptions =
      65             :         VACUUM_OPTION_PARALLEL_BULKDEL | VACUUM_OPTION_PARALLEL_CLEANUP,
      66             :         .amkeytype = InvalidOid,
      67             : 
      68             :         .ambuild = ginbuild,
      69             :         .ambuildempty = ginbuildempty,
      70             :         .aminsert = gininsert,
      71             :         .aminsertcleanup = NULL,
      72             :         .ambulkdelete = ginbulkdelete,
      73             :         .amvacuumcleanup = ginvacuumcleanup,
      74             :         .amcanreturn = NULL,
      75             :         .amcostestimate = gincostestimate,
      76             :         .amgettreeheight = NULL,
      77             :         .amoptions = ginoptions,
      78             :         .amproperty = NULL,
      79             :         .ambuildphasename = ginbuildphasename,
      80             :         .amvalidate = ginvalidate,
      81             :         .amadjustmembers = ginadjustmembers,
      82             :         .ambeginscan = ginbeginscan,
      83             :         .amrescan = ginrescan,
      84             :         .amgettuple = NULL,
      85             :         .amgetbitmap = gingetbitmap,
      86             :         .amendscan = ginendscan,
      87             :         .ammarkpos = NULL,
      88             :         .amrestrpos = NULL,
      89             :         .amestimateparallelscan = NULL,
      90             :         .aminitparallelscan = NULL,
      91             :         .amparallelrescan = NULL,
      92             :     };
      93             : 
      94        3906 :     PG_RETURN_POINTER(&amroutine);
      95             : }
      96             : 
      97             : /*
      98             :  * initGinState: fill in an empty GinState struct to describe the index
      99             :  *
     100             :  * Note: assorted subsidiary data is allocated in the CurrentMemoryContext.
     101             :  */
     102             : void
     103        5434 : initGinState(GinState *state, Relation index)
     104             : {
     105        5434 :     TupleDesc   origTupdesc = RelationGetDescr(index);
     106             :     int         i;
     107             : 
     108        5434 :     MemSet(state, 0, sizeof(GinState));
     109             : 
     110        5434 :     state->index = index;
     111        5434 :     state->oneCol = (origTupdesc->natts == 1);
     112        5434 :     state->origTupdesc = origTupdesc;
     113             : 
     114       11156 :     for (i = 0; i < origTupdesc->natts; i++)
     115             :     {
     116        5722 :         Form_pg_attribute attr = TupleDescAttr(origTupdesc, i);
     117             : 
     118        5722 :         if (state->oneCol)
     119        5146 :             state->tupdesc[i] = state->origTupdesc;
     120             :         else
     121             :         {
     122         576 :             state->tupdesc[i] = CreateTemplateTupleDesc(2);
     123             : 
     124         576 :             TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 1, NULL,
     125             :                                INT2OID, -1, 0);
     126         576 :             TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 2, NULL,
     127             :                                attr->atttypid,
     128             :                                attr->atttypmod,
     129         576 :                                attr->attndims);
     130         576 :             TupleDescInitEntryCollation(state->tupdesc[i], (AttrNumber) 2,
     131             :                                         attr->attcollation);
     132             :         }
     133             : 
     134             :         /*
     135             :          * If the compare proc isn't specified in the opclass definition, look
     136             :          * up the index key type's default btree comparator.
     137             :          */
     138        5722 :         if (index_getprocid(index, i + 1, GIN_COMPARE_PROC) != InvalidOid)
     139             :         {
     140        2688 :             fmgr_info_copy(&(state->compareFn[i]),
     141        2688 :                            index_getprocinfo(index, i + 1, GIN_COMPARE_PROC),
     142             :                            CurrentMemoryContext);
     143             :         }
     144             :         else
     145             :         {
     146             :             TypeCacheEntry *typentry;
     147             : 
     148        3034 :             typentry = lookup_type_cache(attr->atttypid,
     149             :                                          TYPECACHE_CMP_PROC_FINFO);
     150        3034 :             if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
     151           0 :                 ereport(ERROR,
     152             :                         (errcode(ERRCODE_UNDEFINED_FUNCTION),
     153             :                          errmsg("could not identify a comparison function for type %s",
     154             :                                 format_type_be(attr->atttypid))));
     155        3034 :             fmgr_info_copy(&(state->compareFn[i]),
     156             :                            &(typentry->cmp_proc_finfo),
     157             :                            CurrentMemoryContext);
     158             :         }
     159             : 
     160             :         /* Opclass must always provide extract procs */
     161        5722 :         fmgr_info_copy(&(state->extractValueFn[i]),
     162        5722 :                        index_getprocinfo(index, i + 1, GIN_EXTRACTVALUE_PROC),
     163             :                        CurrentMemoryContext);
     164        5722 :         fmgr_info_copy(&(state->extractQueryFn[i]),
     165        5722 :                        index_getprocinfo(index, i + 1, GIN_EXTRACTQUERY_PROC),
     166             :                        CurrentMemoryContext);
     167             : 
     168             :         /*
     169             :          * Check opclass capability to do tri-state or binary logic consistent
     170             :          * check.
     171             :          */
     172        5722 :         if (index_getprocid(index, i + 1, GIN_TRICONSISTENT_PROC) != InvalidOid)
     173             :         {
     174        4948 :             fmgr_info_copy(&(state->triConsistentFn[i]),
     175        4948 :                            index_getprocinfo(index, i + 1, GIN_TRICONSISTENT_PROC),
     176             :                            CurrentMemoryContext);
     177             :         }
     178             : 
     179        5722 :         if (index_getprocid(index, i + 1, GIN_CONSISTENT_PROC) != InvalidOid)
     180             :         {
     181        5722 :             fmgr_info_copy(&(state->consistentFn[i]),
     182        5722 :                            index_getprocinfo(index, i + 1, GIN_CONSISTENT_PROC),
     183             :                            CurrentMemoryContext);
     184             :         }
     185             : 
     186        5722 :         if (state->consistentFn[i].fn_oid == InvalidOid &&
     187           0 :             state->triConsistentFn[i].fn_oid == InvalidOid)
     188             :         {
     189           0 :             elog(ERROR, "missing GIN support function (%d or %d) for attribute %d of index \"%s\"",
     190             :                  GIN_CONSISTENT_PROC, GIN_TRICONSISTENT_PROC,
     191             :                  i + 1, RelationGetRelationName(index));
     192             :         }
     193             : 
     194             :         /*
     195             :          * Check opclass capability to do partial match.
     196             :          */
     197        5722 :         if (index_getprocid(index, i + 1, GIN_COMPARE_PARTIAL_PROC) != InvalidOid)
     198             :         {
     199         972 :             fmgr_info_copy(&(state->comparePartialFn[i]),
     200         972 :                            index_getprocinfo(index, i + 1, GIN_COMPARE_PARTIAL_PROC),
     201             :                            CurrentMemoryContext);
     202         972 :             state->canPartialMatch[i] = true;
     203             :         }
     204             :         else
     205             :         {
     206        4750 :             state->canPartialMatch[i] = false;
     207             :         }
     208             : 
     209             :         /*
     210             :          * If the index column has a specified collation, we should honor that
     211             :          * while doing comparisons.  However, we may have a collatable storage
     212             :          * type for a noncollatable indexed data type (for instance, hstore
     213             :          * uses text index entries).  If there's no index collation then
     214             :          * specify default collation in case the support functions need
     215             :          * collation.  This is harmless if the support functions don't care
     216             :          * about collation, so we just do it unconditionally.  (We could
     217             :          * alternatively call get_typcollation, but that seems like expensive
     218             :          * overkill --- there aren't going to be any cases where a GIN storage
     219             :          * type has a nondefault collation.)
     220             :          */
     221        5722 :         if (OidIsValid(index->rd_indcollation[i]))
     222         404 :             state->supportCollation[i] = index->rd_indcollation[i];
     223             :         else
     224        5318 :             state->supportCollation[i] = DEFAULT_COLLATION_OID;
     225             :     }
     226        5434 : }
     227             : 
     228             : /*
     229             :  * Extract attribute (column) number of stored entry from GIN tuple
     230             :  */
     231             : OffsetNumber
     232    17225304 : gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple)
     233             : {
     234             :     OffsetNumber colN;
     235             : 
     236    17225304 :     if (ginstate->oneCol)
     237             :     {
     238             :         /* column number is not stored explicitly */
     239     8566294 :         colN = FirstOffsetNumber;
     240             :     }
     241             :     else
     242             :     {
     243             :         Datum       res;
     244             :         bool        isnull;
     245             : 
     246             :         /*
     247             :          * First attribute is always int16, so we can safely use any tuple
     248             :          * descriptor to obtain first attribute of tuple
     249             :          */
     250     8659010 :         res = index_getattr(tuple, FirstOffsetNumber, ginstate->tupdesc[0],
     251             :                             &isnull);
     252             :         Assert(!isnull);
     253             : 
     254     8659010 :         colN = DatumGetUInt16(res);
     255             :         Assert(colN >= FirstOffsetNumber && colN <= ginstate->origTupdesc->natts);
     256             :     }
     257             : 
     258    17225304 :     return colN;
     259             : }
     260             : 
     261             : /*
     262             :  * Extract stored datum (and possible null category) from GIN tuple
     263             :  */
     264             : Datum
     265    12897870 : gintuple_get_key(GinState *ginstate, IndexTuple tuple,
     266             :                  GinNullCategory *category)
     267             : {
     268             :     Datum       res;
     269             :     bool        isnull;
     270             : 
     271    12897870 :     if (ginstate->oneCol)
     272             :     {
     273             :         /*
     274             :          * Single column index doesn't store attribute numbers in tuples
     275             :          */
     276     8569088 :         res = index_getattr(tuple, FirstOffsetNumber, ginstate->origTupdesc,
     277             :                             &isnull);
     278             :     }
     279             :     else
     280             :     {
     281             :         /*
     282             :          * Since the datum type depends on which index column it's from, we
     283             :          * must be careful to use the right tuple descriptor here.
     284             :          */
     285     4328782 :         OffsetNumber colN = gintuple_get_attrnum(ginstate, tuple);
     286             : 
     287     4328782 :         res = index_getattr(tuple, OffsetNumberNext(FirstOffsetNumber),
     288     4328782 :                             ginstate->tupdesc[colN - 1],
     289             :                             &isnull);
     290             :     }
     291             : 
     292    12897870 :     if (isnull)
     293        1794 :         *category = GinGetNullCategory(tuple, ginstate);
     294             :     else
     295    12896076 :         *category = GIN_CAT_NORM_KEY;
     296             : 
     297    12897870 :     return res;
     298             : }
     299             : 
     300             : /*
     301             :  * Allocate a new page (either by recycling, or by extending the index file)
     302             :  * The returned buffer is already pinned and exclusive-locked
     303             :  * Caller is responsible for initializing the page by calling GinInitBuffer
     304             :  */
     305             : Buffer
     306        9266 : GinNewBuffer(Relation index)
     307             : {
     308             :     Buffer      buffer;
     309             : 
     310             :     /* First, try to get a page from FSM */
     311             :     for (;;)
     312           0 :     {
     313        9266 :         BlockNumber blkno = GetFreeIndexPage(index);
     314             : 
     315        9266 :         if (blkno == InvalidBlockNumber)
     316        9160 :             break;
     317             : 
     318         106 :         buffer = ReadBuffer(index, blkno);
     319             : 
     320             :         /*
     321             :          * We have to guard against the possibility that someone else already
     322             :          * recycled this page; the buffer may be locked if so.
     323             :          */
     324         106 :         if (ConditionalLockBuffer(buffer))
     325             :         {
     326         106 :             if (GinPageIsRecyclable(BufferGetPage(buffer)))
     327         106 :                 return buffer;  /* OK to use */
     328             : 
     329           0 :             LockBuffer(buffer, GIN_UNLOCK);
     330             :         }
     331             : 
     332             :         /* Can't use it, so release buffer and try again */
     333           0 :         ReleaseBuffer(buffer);
     334             :     }
     335             : 
     336             :     /* Must extend the file */
     337        9160 :     buffer = ExtendBufferedRel(BMR_REL(index), MAIN_FORKNUM, NULL,
     338             :                                EB_LOCK_FIRST);
     339             : 
     340        9160 :     return buffer;
     341             : }
     342             : 
     343             : void
     344       63074 : GinInitPage(Page page, uint32 f, Size pageSize)
     345             : {
     346             :     GinPageOpaque opaque;
     347             : 
     348       63074 :     PageInit(page, pageSize, sizeof(GinPageOpaqueData));
     349             : 
     350       63074 :     opaque = GinPageGetOpaque(page);
     351       63074 :     opaque->flags = f;
     352       63074 :     opaque->rightlink = InvalidBlockNumber;
     353       63074 : }
     354             : 
     355             : void
     356        4062 : GinInitBuffer(Buffer b, uint32 f)
     357             : {
     358        4062 :     GinInitPage(BufferGetPage(b), f, BufferGetPageSize(b));
     359        4062 : }
     360             : 
     361             : void
     362       48316 : GinInitMetabuffer(Buffer b)
     363             : {
     364             :     GinMetaPageData *metadata;
     365       48316 :     Page        page = BufferGetPage(b);
     366             : 
     367       48316 :     GinInitPage(page, GIN_META, BufferGetPageSize(b));
     368             : 
     369       48316 :     metadata = GinPageGetMeta(page);
     370             : 
     371       48316 :     metadata->head = metadata->tail = InvalidBlockNumber;
     372       48316 :     metadata->tailFreeSize = 0;
     373       48316 :     metadata->nPendingPages = 0;
     374       48316 :     metadata->nPendingHeapTuples = 0;
     375       48316 :     metadata->nTotalPages = 0;
     376       48316 :     metadata->nEntryPages = 0;
     377       48316 :     metadata->nDataPages = 0;
     378       48316 :     metadata->nEntries = 0;
     379       48316 :     metadata->ginVersion = GIN_CURRENT_VERSION;
     380             : 
     381             :     /*
     382             :      * Set pd_lower just past the end of the metadata.  This is essential,
     383             :      * because without doing so, metadata will be lost if xlog.c compresses
     384             :      * the page.
     385             :      */
     386       48316 :     ((PageHeader) page)->pd_lower =
     387       48316 :         ((char *) metadata + sizeof(GinMetaPageData)) - (char *) page;
     388       48316 : }
     389             : 
     390             : /*
     391             :  * Compare two keys of the same index column
     392             :  */
     393             : int
     394    39316092 : ginCompareEntries(GinState *ginstate, OffsetNumber attnum,
     395             :                   Datum a, GinNullCategory categorya,
     396             :                   Datum b, GinNullCategory categoryb)
     397             : {
     398             :     /* if not of same null category, sort by that first */
     399    39316092 :     if (categorya != categoryb)
     400      623816 :         return (categorya < categoryb) ? -1 : 1;
     401             : 
     402             :     /* all null items in same category are equal */
     403    38692276 :     if (categorya != GIN_CAT_NORM_KEY)
     404        8296 :         return 0;
     405             : 
     406             :     /* both not null, so safe to call the compareFn */
     407    38683980 :     return DatumGetInt32(FunctionCall2Coll(&ginstate->compareFn[attnum - 1],
     408    38683980 :                                            ginstate->supportCollation[attnum - 1],
     409             :                                            a, b));
     410             : }
     411             : 
     412             : /*
     413             :  * Compare two keys of possibly different index columns
     414             :  */
     415             : int
     416    39799708 : ginCompareAttEntries(GinState *ginstate,
     417             :                      OffsetNumber attnuma, Datum a, GinNullCategory categorya,
     418             :                      OffsetNumber attnumb, Datum b, GinNullCategory categoryb)
     419             : {
     420             :     /* attribute number is the first sort key */
     421    39799708 :     if (attnuma != attnumb)
     422      537806 :         return (attnuma < attnumb) ? -1 : 1;
     423             : 
     424    39261902 :     return ginCompareEntries(ginstate, attnuma, a, categorya, b, categoryb);
     425             : }
     426             : 
     427             : 
     428             : /*
     429             :  * Support for sorting key datums in ginExtractEntries
     430             :  *
     431             :  * Note: we only have to worry about null and not-null keys here;
     432             :  * ginExtractEntries never generates more than one placeholder null,
     433             :  * so it doesn't have to sort those.
     434             :  */
     435             : typedef struct
     436             : {
     437             :     Datum       datum;
     438             :     bool        isnull;
     439             : } keyEntryData;
     440             : 
     441             : typedef struct
     442             : {
     443             :     FmgrInfo   *cmpDatumFunc;
     444             :     Oid         collation;
     445             :     bool        haveDups;
     446             : } cmpEntriesArg;
     447             : 
     448             : static int
     449     4234936 : cmpEntries(const void *a, const void *b, void *arg)
     450             : {
     451     4234936 :     const keyEntryData *aa = (const keyEntryData *) a;
     452     4234936 :     const keyEntryData *bb = (const keyEntryData *) b;
     453     4234936 :     cmpEntriesArg *data = (cmpEntriesArg *) arg;
     454             :     int         res;
     455             : 
     456     4234936 :     if (aa->isnull)
     457             :     {
     458           0 :         if (bb->isnull)
     459           0 :             res = 0;            /* NULL "=" NULL */
     460             :         else
     461           0 :             res = 1;            /* NULL ">" not-NULL */
     462             :     }
     463     4234936 :     else if (bb->isnull)
     464           0 :         res = -1;               /* not-NULL "<" NULL */
     465             :     else
     466     4234936 :         res = DatumGetInt32(FunctionCall2Coll(data->cmpDatumFunc,
     467             :                                               data->collation,
     468     4234936 :                                               aa->datum, bb->datum));
     469             : 
     470             :     /*
     471             :      * Detect if we have any duplicates.  If there are equal keys, qsort must
     472             :      * compare them at some point, else it wouldn't know whether one should go
     473             :      * before or after the other.
     474             :      */
     475     4234936 :     if (res == 0)
     476       83546 :         data->haveDups = true;
     477             : 
     478     4234936 :     return res;
     479             : }
     480             : 
     481             : 
     482             : /*
     483             :  * Extract the index key values from an indexable item
     484             :  *
     485             :  * The resulting key values are sorted, and any duplicates are removed.
     486             :  * This avoids generating redundant index entries.
     487             :  */
     488             : Datum *
     489     1384522 : ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
     490             :                   Datum value, bool isNull,
     491             :                   int32 *nentries, GinNullCategory **categories)
     492             : {
     493             :     Datum      *entries;
     494             :     bool       *nullFlags;
     495             :     int32       i;
     496             : 
     497             :     /*
     498             :      * We don't call the extractValueFn on a null item.  Instead generate a
     499             :      * placeholder.
     500             :      */
     501     1384522 :     if (isNull)
     502             :     {
     503        6622 :         *nentries = 1;
     504        6622 :         entries = palloc_object(Datum);
     505        6622 :         entries[0] = (Datum) 0;
     506        6622 :         *categories = palloc_object(GinNullCategory);
     507        6622 :         (*categories)[0] = GIN_CAT_NULL_ITEM;
     508        6622 :         return entries;
     509             :     }
     510             : 
     511             :     /* OK, call the opclass's extractValueFn */
     512     1377900 :     nullFlags = NULL;           /* in case extractValue doesn't set it */
     513             :     entries = (Datum *)
     514     2755800 :         DatumGetPointer(FunctionCall3Coll(&ginstate->extractValueFn[attnum - 1],
     515     1377900 :                                           ginstate->supportCollation[attnum - 1],
     516             :                                           value,
     517             :                                           PointerGetDatum(nentries),
     518             :                                           PointerGetDatum(&nullFlags)));
     519             : 
     520             :     /*
     521             :      * Generate a placeholder if the item contained no keys.
     522             :      */
     523     1377900 :     if (entries == NULL || *nentries <= 0)
     524             :     {
     525        1776 :         *nentries = 1;
     526        1776 :         entries = palloc_object(Datum);
     527        1776 :         entries[0] = (Datum) 0;
     528        1776 :         *categories = palloc_object(GinNullCategory);
     529        1776 :         (*categories)[0] = GIN_CAT_EMPTY_ITEM;
     530        1776 :         return entries;
     531             :     }
     532             : 
     533             :     /*
     534             :      * If the extractValueFn didn't create a nullFlags array, create one,
     535             :      * assuming that everything's non-null.
     536             :      */
     537     1376124 :     if (nullFlags == NULL)
     538      237548 :         nullFlags = (bool *) palloc0(*nentries * sizeof(bool));
     539             : 
     540             :     /*
     541             :      * If there's more than one key, sort and unique-ify.
     542             :      *
     543             :      * XXX Using qsort here is notationally painful, and the overhead is
     544             :      * pretty bad too.  For small numbers of keys it'd likely be better to use
     545             :      * a simple insertion sort.
     546             :      */
     547     1376124 :     if (*nentries > 1)
     548             :     {
     549             :         keyEntryData *keydata;
     550             :         cmpEntriesArg arg;
     551             : 
     552      595388 :         keydata = palloc_array(keyEntryData, *nentries);
     553     3325504 :         for (i = 0; i < *nentries; i++)
     554             :         {
     555     2730116 :             keydata[i].datum = entries[i];
     556     2730116 :             keydata[i].isnull = nullFlags[i];
     557             :         }
     558             : 
     559      595388 :         arg.cmpDatumFunc = &ginstate->compareFn[attnum - 1];
     560      595388 :         arg.collation = ginstate->supportCollation[attnum - 1];
     561      595388 :         arg.haveDups = false;
     562      595388 :         qsort_arg(keydata, *nentries, sizeof(keyEntryData),
     563             :                   cmpEntries, &arg);
     564             : 
     565      595388 :         if (arg.haveDups)
     566             :         {
     567             :             /* there are duplicates, must get rid of 'em */
     568             :             int32       j;
     569             : 
     570       30722 :             entries[0] = keydata[0].datum;
     571       30722 :             nullFlags[0] = keydata[0].isnull;
     572       30722 :             j = 1;
     573      236722 :             for (i = 1; i < *nentries; i++)
     574             :             {
     575      206000 :                 if (cmpEntries(&keydata[i - 1], &keydata[i], &arg) != 0)
     576             :                 {
     577      165230 :                     entries[j] = keydata[i].datum;
     578      165230 :                     nullFlags[j] = keydata[i].isnull;
     579      165230 :                     j++;
     580             :                 }
     581             :             }
     582       30722 :             *nentries = j;
     583             :         }
     584             :         else
     585             :         {
     586             :             /* easy, no duplicates */
     587     3058060 :             for (i = 0; i < *nentries; i++)
     588             :             {
     589     2493394 :                 entries[i] = keydata[i].datum;
     590     2493394 :                 nullFlags[i] = keydata[i].isnull;
     591             :             }
     592             :         }
     593             : 
     594      595388 :         pfree(keydata);
     595             :     }
     596             : 
     597             :     /*
     598             :      * Create GinNullCategory representation from nullFlags.
     599             :      */
     600     1376124 :     *categories = (GinNullCategory *) palloc0(*nentries * sizeof(GinNullCategory));
     601     4846206 :     for (i = 0; i < *nentries; i++)
     602     3470082 :         (*categories)[i] = (nullFlags[i] ? GIN_CAT_NULL_KEY : GIN_CAT_NORM_KEY);
     603             : 
     604     1376124 :     return entries;
     605             : }
     606             : 
     607             : bytea *
     608         614 : ginoptions(Datum reloptions, bool validate)
     609             : {
     610             :     static const relopt_parse_elt tab[] = {
     611             :         {"fastupdate", RELOPT_TYPE_BOOL, offsetof(GinOptions, useFastUpdate)},
     612             :         {"gin_pending_list_limit", RELOPT_TYPE_INT, offsetof(GinOptions,
     613             :                                                              pendingListCleanupSize)}
     614             :     };
     615             : 
     616         614 :     return (bytea *) build_reloptions(reloptions, validate,
     617             :                                       RELOPT_KIND_GIN,
     618             :                                       sizeof(GinOptions),
     619             :                                       tab, lengthof(tab));
     620             : }
     621             : 
     622             : /*
     623             :  * Fetch index's statistical data into *stats
     624             :  *
     625             :  * Note: in the result, nPendingPages can be trusted to be up-to-date,
     626             :  * as can ginVersion; but the other fields are as of the last VACUUM.
     627             :  */
     628             : void
     629        2592 : ginGetStats(Relation index, GinStatsData *stats)
     630             : {
     631             :     Buffer      metabuffer;
     632             :     Page        metapage;
     633             :     GinMetaPageData *metadata;
     634             : 
     635        2592 :     metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
     636        2592 :     LockBuffer(metabuffer, GIN_SHARE);
     637        2592 :     metapage = BufferGetPage(metabuffer);
     638        2592 :     metadata = GinPageGetMeta(metapage);
     639             : 
     640        2592 :     stats->nPendingPages = metadata->nPendingPages;
     641        2592 :     stats->nTotalPages = metadata->nTotalPages;
     642        2592 :     stats->nEntryPages = metadata->nEntryPages;
     643        2592 :     stats->nDataPages = metadata->nDataPages;
     644        2592 :     stats->nEntries = metadata->nEntries;
     645        2592 :     stats->ginVersion = metadata->ginVersion;
     646             : 
     647        2592 :     UnlockReleaseBuffer(metabuffer);
     648        2592 : }
     649             : 
     650             : /*
     651             :  * Write the given statistics to the index's metapage
     652             :  *
     653             :  * Note: nPendingPages and ginVersion are *not* copied over
     654             :  */
     655             : void
     656         454 : ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build)
     657             : {
     658             :     Buffer      metabuffer;
     659             :     Page        metapage;
     660             :     GinMetaPageData *metadata;
     661             : 
     662         454 :     metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
     663         454 :     LockBuffer(metabuffer, GIN_EXCLUSIVE);
     664         454 :     metapage = BufferGetPage(metabuffer);
     665         454 :     metadata = GinPageGetMeta(metapage);
     666             : 
     667         454 :     START_CRIT_SECTION();
     668             : 
     669         454 :     metadata->nTotalPages = stats->nTotalPages;
     670         454 :     metadata->nEntryPages = stats->nEntryPages;
     671         454 :     metadata->nDataPages = stats->nDataPages;
     672         454 :     metadata->nEntries = stats->nEntries;
     673             : 
     674             :     /*
     675             :      * Set pd_lower just past the end of the metadata.  This is essential,
     676             :      * because without doing so, metadata will be lost if xlog.c compresses
     677             :      * the page.  (We must do this here because pre-v11 versions of PG did not
     678             :      * set the metapage's pd_lower correctly, so a pg_upgraded index might
     679             :      * contain the wrong value.)
     680             :      */
     681         454 :     ((PageHeader) metapage)->pd_lower =
     682         454 :         ((char *) metadata + sizeof(GinMetaPageData)) - (char *) metapage;
     683             : 
     684         454 :     MarkBufferDirty(metabuffer);
     685             : 
     686         454 :     if (RelationNeedsWAL(index) && !is_build)
     687             :     {
     688             :         XLogRecPtr  recptr;
     689             :         ginxlogUpdateMeta data;
     690             : 
     691          50 :         data.locator = index->rd_locator;
     692          50 :         data.ntuples = 0;
     693          50 :         data.newRightlink = data.prevTail = InvalidBlockNumber;
     694          50 :         memcpy(&data.metadata, metadata, sizeof(GinMetaPageData));
     695             : 
     696          50 :         XLogBeginInsert();
     697          50 :         XLogRegisterData(&data, sizeof(ginxlogUpdateMeta));
     698          50 :         XLogRegisterBuffer(0, metabuffer, REGBUF_WILL_INIT | REGBUF_STANDARD);
     699             : 
     700          50 :         recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_UPDATE_META_PAGE);
     701          50 :         PageSetLSN(metapage, recptr);
     702             :     }
     703             : 
     704         454 :     UnlockReleaseBuffer(metabuffer);
     705             : 
     706         454 :     END_CRIT_SECTION();
     707         454 : }
     708             : 
     709             : /*
     710             :  *  ginbuildphasename() -- Return name of index build phase.
     711             :  */
     712             : char *
     713           0 : ginbuildphasename(int64 phasenum)
     714             : {
     715           0 :     switch (phasenum)
     716             :     {
     717           0 :         case PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE:
     718           0 :             return "initializing";
     719           0 :         case PROGRESS_GIN_PHASE_INDEXBUILD_TABLESCAN:
     720           0 :             return "scanning table";
     721           0 :         case PROGRESS_GIN_PHASE_PERFORMSORT_1:
     722           0 :             return "sorting tuples (workers)";
     723           0 :         case PROGRESS_GIN_PHASE_MERGE_1:
     724           0 :             return "merging tuples (workers)";
     725           0 :         case PROGRESS_GIN_PHASE_PERFORMSORT_2:
     726           0 :             return "sorting tuples";
     727           0 :         case PROGRESS_GIN_PHASE_MERGE_2:
     728           0 :             return "merging tuples";
     729           0 :         default:
     730           0 :             return NULL;
     731             :     }
     732             : }

Generated by: LCOV version 1.16