LCOV - code coverage report
Current view: top level - src/backend/access/gin - ginutil.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 193 219 88.1 %
Date: 2026-01-10 04:17:22 Functions: 13 14 92.9 %
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-2026, 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        3994 : 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        3994 :     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        5548 : initGinState(GinState *state, Relation index)
     104             : {
     105        5548 :     TupleDesc   origTupdesc = RelationGetDescr(index);
     106             :     int         i;
     107             : 
     108        5548 :     MemSet(state, 0, sizeof(GinState));
     109             : 
     110        5548 :     state->index = index;
     111        5548 :     state->oneCol = (origTupdesc->natts == 1);
     112        5548 :     state->origTupdesc = origTupdesc;
     113             : 
     114       11386 :     for (i = 0; i < origTupdesc->natts; i++)
     115             :     {
     116        5838 :         Form_pg_attribute attr = TupleDescAttr(origTupdesc, i);
     117             : 
     118        5838 :         if (state->oneCol)
     119        5258 :             state->tupdesc[i] = state->origTupdesc;
     120             :         else
     121             :         {
     122         580 :             state->tupdesc[i] = CreateTemplateTupleDesc(2);
     123             : 
     124         580 :             TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 1, NULL,
     125             :                                INT2OID, -1, 0);
     126         580 :             TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 2, NULL,
     127             :                                attr->atttypid,
     128             :                                attr->atttypmod,
     129         580 :                                attr->attndims);
     130         580 :             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        5838 :         if (index_getprocid(index, i + 1, GIN_COMPARE_PROC) != InvalidOid)
     139             :         {
     140        2798 :             fmgr_info_copy(&(state->compareFn[i]),
     141        2798 :                            index_getprocinfo(index, i + 1, GIN_COMPARE_PROC),
     142             :                            CurrentMemoryContext);
     143             :         }
     144             :         else
     145             :         {
     146             :             TypeCacheEntry *typentry;
     147             : 
     148        3040 :             typentry = lookup_type_cache(attr->atttypid,
     149             :                                          TYPECACHE_CMP_PROC_FINFO);
     150        3040 :             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        3040 :             fmgr_info_copy(&(state->compareFn[i]),
     156             :                            &(typentry->cmp_proc_finfo),
     157             :                            CurrentMemoryContext);
     158             :         }
     159             : 
     160             :         /* Opclass must always provide extract procs */
     161        5838 :         fmgr_info_copy(&(state->extractValueFn[i]),
     162        5838 :                        index_getprocinfo(index, i + 1, GIN_EXTRACTVALUE_PROC),
     163             :                        CurrentMemoryContext);
     164        5838 :         fmgr_info_copy(&(state->extractQueryFn[i]),
     165        5838 :                        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        5838 :         if (index_getprocid(index, i + 1, GIN_TRICONSISTENT_PROC) != InvalidOid)
     173             :         {
     174        5064 :             fmgr_info_copy(&(state->triConsistentFn[i]),
     175        5064 :                            index_getprocinfo(index, i + 1, GIN_TRICONSISTENT_PROC),
     176             :                            CurrentMemoryContext);
     177             :         }
     178             : 
     179        5838 :         if (index_getprocid(index, i + 1, GIN_CONSISTENT_PROC) != InvalidOid)
     180             :         {
     181        5838 :             fmgr_info_copy(&(state->consistentFn[i]),
     182        5838 :                            index_getprocinfo(index, i + 1, GIN_CONSISTENT_PROC),
     183             :                            CurrentMemoryContext);
     184             :         }
     185             : 
     186        5838 :         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        5838 :         if (index_getprocid(index, i + 1, GIN_COMPARE_PARTIAL_PROC) != InvalidOid)
     198             :         {
     199         974 :             fmgr_info_copy(&(state->comparePartialFn[i]),
     200         974 :                            index_getprocinfo(index, i + 1, GIN_COMPARE_PARTIAL_PROC),
     201             :                            CurrentMemoryContext);
     202         974 :             state->canPartialMatch[i] = true;
     203             :         }
     204             :         else
     205             :         {
     206        4864 :             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        5838 :         if (OidIsValid(index->rd_indcollation[i]))
     222         406 :             state->supportCollation[i] = index->rd_indcollation[i];
     223             :         else
     224        5432 :             state->supportCollation[i] = DEFAULT_COLLATION_OID;
     225             :     }
     226        5548 : }
     227             : 
     228             : /*
     229             :  * Extract attribute (column) number of stored entry from GIN tuple
     230             :  */
     231             : OffsetNumber
     232    17265946 : gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple)
     233             : {
     234             :     OffsetNumber colN;
     235             : 
     236    17265946 :     if (ginstate->oneCol)
     237             :     {
     238             :         /* column number is not stored explicitly */
     239     8606936 :         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    17265946 :     return colN;
     259             : }
     260             : 
     261             : /*
     262             :  * Extract stored datum (and possible null category) from GIN tuple
     263             :  */
     264             : Datum
     265    12938518 : gintuple_get_key(GinState *ginstate, IndexTuple tuple,
     266             :                  GinNullCategory *category)
     267             : {
     268             :     Datum       res;
     269             :     bool        isnull;
     270             : 
     271    12938518 :     if (ginstate->oneCol)
     272             :     {
     273             :         /*
     274             :          * Single column index doesn't store attribute numbers in tuples
     275             :          */
     276     8609736 :         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    12938518 :     if (isnull)
     293        1800 :         *category = GinGetNullCategory(tuple, ginstate);
     294             :     else
     295    12936718 :         *category = GIN_CAT_NORM_KEY;
     296             : 
     297    12938518 :     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       63168 : GinInitPage(Page page, uint32 f, Size pageSize)
     345             : {
     346             :     GinPageOpaque opaque;
     347             : 
     348       63168 :     PageInit(page, pageSize, sizeof(GinPageOpaqueData));
     349             : 
     350       63168 :     opaque = GinPageGetOpaque(page);
     351       63168 :     opaque->flags = f;
     352       63168 :     opaque->rightlink = InvalidBlockNumber;
     353       63168 : }
     354             : 
     355             : void
     356        4080 : GinInitBuffer(Buffer b, uint32 f)
     357             : {
     358        4080 :     GinInitPage(BufferGetPage(b), f, BufferGetPageSize(b));
     359        4080 : }
     360             : 
     361             : void
     362       48392 : GinInitMetabuffer(Buffer b)
     363             : {
     364             :     GinMetaPageData *metadata;
     365       48392 :     Page        page = BufferGetPage(b);
     366             : 
     367       48392 :     GinInitPage(page, GIN_META, BufferGetPageSize(b));
     368             : 
     369       48392 :     metadata = GinPageGetMeta(page);
     370             : 
     371       48392 :     metadata->head = metadata->tail = InvalidBlockNumber;
     372       48392 :     metadata->tailFreeSize = 0;
     373       48392 :     metadata->nPendingPages = 0;
     374       48392 :     metadata->nPendingHeapTuples = 0;
     375       48392 :     metadata->nTotalPages = 0;
     376       48392 :     metadata->nEntryPages = 0;
     377       48392 :     metadata->nDataPages = 0;
     378       48392 :     metadata->nEntries = 0;
     379       48392 :     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       48392 :     ((PageHeader) page)->pd_lower =
     387       48392 :         ((char *) metadata + sizeof(GinMetaPageData)) - (char *) page;
     388       48392 : }
     389             : 
     390             : /*
     391             :  * Support for sorting key datums in ginExtractEntries
     392             :  *
     393             :  * Note: we only have to worry about null and not-null keys here;
     394             :  * ginExtractEntries never generates more than one placeholder null,
     395             :  * so it doesn't have to sort those.
     396             :  */
     397             : typedef struct
     398             : {
     399             :     Datum       datum;
     400             :     bool        isnull;
     401             : } keyEntryData;
     402             : 
     403             : typedef struct
     404             : {
     405             :     FmgrInfo   *cmpDatumFunc;
     406             :     Oid         collation;
     407             :     bool        haveDups;
     408             : } cmpEntriesArg;
     409             : 
     410             : static int
     411     4170572 : cmpEntries(const void *a, const void *b, void *arg)
     412             : {
     413     4170572 :     const keyEntryData *aa = (const keyEntryData *) a;
     414     4170572 :     const keyEntryData *bb = (const keyEntryData *) b;
     415     4170572 :     cmpEntriesArg *data = (cmpEntriesArg *) arg;
     416             :     int         res;
     417             : 
     418     4170572 :     if (aa->isnull)
     419             :     {
     420           0 :         if (bb->isnull)
     421           0 :             res = 0;            /* NULL "=" NULL */
     422             :         else
     423           0 :             res = 1;            /* NULL ">" not-NULL */
     424             :     }
     425     4170572 :     else if (bb->isnull)
     426           0 :         res = -1;               /* not-NULL "<" NULL */
     427             :     else
     428     4170572 :         res = DatumGetInt32(FunctionCall2Coll(data->cmpDatumFunc,
     429             :                                               data->collation,
     430     4170572 :                                               aa->datum, bb->datum));
     431             : 
     432             :     /*
     433             :      * Detect if we have any duplicates.  If there are equal keys, qsort must
     434             :      * compare them at some point, else it wouldn't know whether one should go
     435             :      * before or after the other.
     436             :      */
     437     4170572 :     if (res == 0)
     438       76386 :         data->haveDups = true;
     439             : 
     440     4170572 :     return res;
     441             : }
     442             : 
     443             : 
     444             : /*
     445             :  * Extract the index key values from an indexable item
     446             :  *
     447             :  * The resulting key values are sorted, and any duplicates are removed.
     448             :  * This avoids generating redundant index entries.
     449             :  */
     450             : Datum *
     451     1380386 : ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
     452             :                   Datum value, bool isNull,
     453             :                   int32 *nentries, GinNullCategory **categories)
     454             : {
     455             :     Datum      *entries;
     456             :     bool       *nullFlags;
     457             :     int32       i;
     458             : 
     459             :     /*
     460             :      * We don't call the extractValueFn on a null item.  Instead generate a
     461             :      * placeholder.
     462             :      */
     463     1380386 :     if (isNull)
     464             :     {
     465        6622 :         *nentries = 1;
     466        6622 :         entries = palloc_object(Datum);
     467        6622 :         entries[0] = (Datum) 0;
     468        6622 :         *categories = palloc_object(GinNullCategory);
     469        6622 :         (*categories)[0] = GIN_CAT_NULL_ITEM;
     470        6622 :         return entries;
     471             :     }
     472             : 
     473             :     /* OK, call the opclass's extractValueFn */
     474     1373764 :     nullFlags = NULL;           /* in case extractValue doesn't set it */
     475             :     entries = (Datum *)
     476     2747528 :         DatumGetPointer(FunctionCall3Coll(&ginstate->extractValueFn[attnum - 1],
     477     1373764 :                                           ginstate->supportCollation[attnum - 1],
     478             :                                           value,
     479             :                                           PointerGetDatum(nentries),
     480             :                                           PointerGetDatum(&nullFlags)));
     481             : 
     482             :     /*
     483             :      * Generate a placeholder if the item contained no keys.
     484             :      */
     485     1373764 :     if (entries == NULL || *nentries <= 0)
     486             :     {
     487        1776 :         *nentries = 1;
     488        1776 :         entries = palloc_object(Datum);
     489        1776 :         entries[0] = (Datum) 0;
     490        1776 :         *categories = palloc_object(GinNullCategory);
     491        1776 :         (*categories)[0] = GIN_CAT_EMPTY_ITEM;
     492        1776 :         return entries;
     493             :     }
     494             : 
     495             :     /*
     496             :      * If the extractValueFn didn't create a nullFlags array, create one,
     497             :      * assuming that everything's non-null.
     498             :      */
     499     1371988 :     if (nullFlags == NULL)
     500      233412 :         nullFlags = (bool *) palloc0(*nentries * sizeof(bool));
     501             : 
     502             :     /*
     503             :      * If there's more than one key, sort and unique-ify.
     504             :      *
     505             :      * XXX Using qsort here is notationally painful, and the overhead is
     506             :      * pretty bad too.  For small numbers of keys it'd likely be better to use
     507             :      * a simple insertion sort.
     508             :      */
     509     1371988 :     if (*nentries > 1)
     510             :     {
     511             :         keyEntryData *keydata;
     512             :         cmpEntriesArg arg;
     513             : 
     514      590852 :         keydata = palloc_array(keyEntryData, *nentries);
     515     3295972 :         for (i = 0; i < *nentries; i++)
     516             :         {
     517     2705120 :             keydata[i].datum = entries[i];
     518     2705120 :             keydata[i].isnull = nullFlags[i];
     519             :         }
     520             : 
     521      590852 :         arg.cmpDatumFunc = &ginstate->compareFn[attnum - 1];
     522      590852 :         arg.collation = ginstate->supportCollation[attnum - 1];
     523      590852 :         arg.haveDups = false;
     524      590852 :         qsort_arg(keydata, *nentries, sizeof(keyEntryData),
     525             :                   cmpEntries, &arg);
     526             : 
     527      590852 :         if (arg.haveDups)
     528             :         {
     529             :             /* there are duplicates, must get rid of 'em */
     530             :             int32       j;
     531             : 
     532       28932 :             entries[0] = keydata[0].datum;
     533       28932 :             nullFlags[0] = keydata[0].isnull;
     534       28932 :             j = 1;
     535      220612 :             for (i = 1; i < *nentries; i++)
     536             :             {
     537      191680 :                 if (cmpEntries(&keydata[i - 1], &keydata[i], &arg) != 0)
     538             :                 {
     539      154490 :                     entries[j] = keydata[i].datum;
     540      154490 :                     nullFlags[j] = keydata[i].isnull;
     541      154490 :                     j++;
     542             :                 }
     543             :             }
     544       28932 :             *nentries = j;
     545             :         }
     546             :         else
     547             :         {
     548             :             /* easy, no duplicates */
     549     3046428 :             for (i = 0; i < *nentries; i++)
     550             :             {
     551     2484508 :                 entries[i] = keydata[i].datum;
     552     2484508 :                 nullFlags[i] = keydata[i].isnull;
     553             :             }
     554             :         }
     555             : 
     556      590852 :         pfree(keydata);
     557             :     }
     558             : 
     559             :     /*
     560             :      * Create GinNullCategory representation from nullFlags.
     561             :      */
     562     1371988 :     *categories = (GinNullCategory *) palloc0(*nentries * sizeof(GinNullCategory));
     563     4821054 :     for (i = 0; i < *nentries; i++)
     564     3449066 :         (*categories)[i] = (nullFlags[i] ? GIN_CAT_NULL_KEY : GIN_CAT_NORM_KEY);
     565             : 
     566     1371988 :     return entries;
     567             : }
     568             : 
     569             : bytea *
     570         618 : ginoptions(Datum reloptions, bool validate)
     571             : {
     572             :     static const relopt_parse_elt tab[] = {
     573             :         {"fastupdate", RELOPT_TYPE_BOOL, offsetof(GinOptions, useFastUpdate)},
     574             :         {"gin_pending_list_limit", RELOPT_TYPE_INT, offsetof(GinOptions,
     575             :                                                              pendingListCleanupSize)}
     576             :     };
     577             : 
     578         618 :     return (bytea *) build_reloptions(reloptions, validate,
     579             :                                       RELOPT_KIND_GIN,
     580             :                                       sizeof(GinOptions),
     581             :                                       tab, lengthof(tab));
     582             : }
     583             : 
     584             : /*
     585             :  * Fetch index's statistical data into *stats
     586             :  *
     587             :  * Note: in the result, nPendingPages can be trusted to be up-to-date,
     588             :  * as can ginVersion; but the other fields are as of the last VACUUM.
     589             :  */
     590             : void
     591        2592 : ginGetStats(Relation index, GinStatsData *stats)
     592             : {
     593             :     Buffer      metabuffer;
     594             :     Page        metapage;
     595             :     GinMetaPageData *metadata;
     596             : 
     597        2592 :     metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
     598        2592 :     LockBuffer(metabuffer, GIN_SHARE);
     599        2592 :     metapage = BufferGetPage(metabuffer);
     600        2592 :     metadata = GinPageGetMeta(metapage);
     601             : 
     602        2592 :     stats->nPendingPages = metadata->nPendingPages;
     603        2592 :     stats->nTotalPages = metadata->nTotalPages;
     604        2592 :     stats->nEntryPages = metadata->nEntryPages;
     605        2592 :     stats->nDataPages = metadata->nDataPages;
     606        2592 :     stats->nEntries = metadata->nEntries;
     607        2592 :     stats->ginVersion = metadata->ginVersion;
     608             : 
     609        2592 :     UnlockReleaseBuffer(metabuffer);
     610        2592 : }
     611             : 
     612             : /*
     613             :  * Write the given statistics to the index's metapage
     614             :  *
     615             :  * Note: nPendingPages and ginVersion are *not* copied over
     616             :  */
     617             : void
     618         448 : ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build)
     619             : {
     620             :     Buffer      metabuffer;
     621             :     Page        metapage;
     622             :     GinMetaPageData *metadata;
     623             : 
     624         448 :     metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
     625         448 :     LockBuffer(metabuffer, GIN_EXCLUSIVE);
     626         448 :     metapage = BufferGetPage(metabuffer);
     627         448 :     metadata = GinPageGetMeta(metapage);
     628             : 
     629         448 :     START_CRIT_SECTION();
     630             : 
     631         448 :     metadata->nTotalPages = stats->nTotalPages;
     632         448 :     metadata->nEntryPages = stats->nEntryPages;
     633         448 :     metadata->nDataPages = stats->nDataPages;
     634         448 :     metadata->nEntries = stats->nEntries;
     635             : 
     636             :     /*
     637             :      * Set pd_lower just past the end of the metadata.  This is essential,
     638             :      * because without doing so, metadata will be lost if xlog.c compresses
     639             :      * the page.  (We must do this here because pre-v11 versions of PG did not
     640             :      * set the metapage's pd_lower correctly, so a pg_upgraded index might
     641             :      * contain the wrong value.)
     642             :      */
     643         448 :     ((PageHeader) metapage)->pd_lower =
     644         448 :         ((char *) metadata + sizeof(GinMetaPageData)) - (char *) metapage;
     645             : 
     646         448 :     MarkBufferDirty(metabuffer);
     647             : 
     648         448 :     if (RelationNeedsWAL(index) && !is_build)
     649             :     {
     650             :         XLogRecPtr  recptr;
     651             :         ginxlogUpdateMeta data;
     652             : 
     653          50 :         data.locator = index->rd_locator;
     654          50 :         data.ntuples = 0;
     655          50 :         data.newRightlink = data.prevTail = InvalidBlockNumber;
     656          50 :         memcpy(&data.metadata, metadata, sizeof(GinMetaPageData));
     657             : 
     658          50 :         XLogBeginInsert();
     659          50 :         XLogRegisterData(&data, sizeof(ginxlogUpdateMeta));
     660          50 :         XLogRegisterBuffer(0, metabuffer, REGBUF_WILL_INIT | REGBUF_STANDARD);
     661             : 
     662          50 :         recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_UPDATE_META_PAGE);
     663          50 :         PageSetLSN(metapage, recptr);
     664             :     }
     665             : 
     666         448 :     UnlockReleaseBuffer(metabuffer);
     667             : 
     668         448 :     END_CRIT_SECTION();
     669         448 : }
     670             : 
     671             : /*
     672             :  *  ginbuildphasename() -- Return name of index build phase.
     673             :  */
     674             : char *
     675           0 : ginbuildphasename(int64 phasenum)
     676             : {
     677           0 :     switch (phasenum)
     678             :     {
     679           0 :         case PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE:
     680           0 :             return "initializing";
     681           0 :         case PROGRESS_GIN_PHASE_INDEXBUILD_TABLESCAN:
     682           0 :             return "scanning table";
     683           0 :         case PROGRESS_GIN_PHASE_PERFORMSORT_1:
     684           0 :             return "sorting tuples (workers)";
     685           0 :         case PROGRESS_GIN_PHASE_MERGE_1:
     686           0 :             return "merging tuples (workers)";
     687           0 :         case PROGRESS_GIN_PHASE_PERFORMSORT_2:
     688           0 :             return "sorting tuples";
     689           0 :         case PROGRESS_GIN_PHASE_MERGE_2:
     690           0 :             return "merging tuples";
     691           0 :         default:
     692           0 :             return NULL;
     693             :     }
     694             : }

Generated by: LCOV version 1.16