LCOV - code coverage report
Current view: top level - src/backend/access/gin - ginutil.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 88.2 % 220 194
Test Date: 2026-03-26 20:16:28 Functions: 92.9 % 14 13
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         2228 : 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         2228 :     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         3001 : initGinState(GinState *state, Relation index)
     104              : {
     105         3001 :     TupleDesc   origTupdesc = RelationGetDescr(index);
     106              :     int         i;
     107              : 
     108         3001 :     MemSet(state, 0, sizeof(GinState));
     109              : 
     110         3001 :     state->index = index;
     111         3001 :     state->oneCol = (origTupdesc->natts == 1);
     112         3001 :     state->origTupdesc = origTupdesc;
     113              : 
     114         6193 :     for (i = 0; i < origTupdesc->natts; i++)
     115              :     {
     116         3192 :         Form_pg_attribute attr = TupleDescAttr(origTupdesc, i);
     117              : 
     118         3192 :         if (state->oneCol)
     119         2810 :             state->tupdesc[i] = state->origTupdesc;
     120              :         else
     121              :         {
     122          382 :             state->tupdesc[i] = CreateTemplateTupleDesc(2);
     123              : 
     124          382 :             TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 1, NULL,
     125              :                                INT2OID, -1, 0);
     126          382 :             TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 2, NULL,
     127              :                                attr->atttypid,
     128              :                                attr->atttypmod,
     129          382 :                                attr->attndims);
     130          382 :             TupleDescInitEntryCollation(state->tupdesc[i], (AttrNumber) 2,
     131              :                                         attr->attcollation);
     132          382 :             TupleDescFinalize(state->tupdesc[i]);
     133              :         }
     134              : 
     135              :         /*
     136              :          * If the compare proc isn't specified in the opclass definition, look
     137              :          * up the index key type's default btree comparator.
     138              :          */
     139         3192 :         if (index_getprocid(index, i + 1, GIN_COMPARE_PROC) != InvalidOid)
     140              :         {
     141         1528 :             fmgr_info_copy(&(state->compareFn[i]),
     142         1528 :                            index_getprocinfo(index, i + 1, GIN_COMPARE_PROC),
     143              :                            CurrentMemoryContext);
     144              :         }
     145              :         else
     146              :         {
     147              :             TypeCacheEntry *typentry;
     148              : 
     149         1664 :             typentry = lookup_type_cache(attr->atttypid,
     150              :                                          TYPECACHE_CMP_PROC_FINFO);
     151         1664 :             if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
     152            0 :                 ereport(ERROR,
     153              :                         (errcode(ERRCODE_UNDEFINED_FUNCTION),
     154              :                          errmsg("could not identify a comparison function for type %s",
     155              :                                 format_type_be(attr->atttypid))));
     156         1664 :             fmgr_info_copy(&(state->compareFn[i]),
     157              :                            &(typentry->cmp_proc_finfo),
     158              :                            CurrentMemoryContext);
     159              :         }
     160              : 
     161              :         /* Opclass must always provide extract procs */
     162         3192 :         fmgr_info_copy(&(state->extractValueFn[i]),
     163         3192 :                        index_getprocinfo(index, i + 1, GIN_EXTRACTVALUE_PROC),
     164              :                        CurrentMemoryContext);
     165         3192 :         fmgr_info_copy(&(state->extractQueryFn[i]),
     166         3192 :                        index_getprocinfo(index, i + 1, GIN_EXTRACTQUERY_PROC),
     167              :                        CurrentMemoryContext);
     168              : 
     169              :         /*
     170              :          * Check opclass capability to do tri-state or binary logic consistent
     171              :          * check.
     172              :          */
     173         3192 :         if (index_getprocid(index, i + 1, GIN_TRICONSISTENT_PROC) != InvalidOid)
     174              :         {
     175         2805 :             fmgr_info_copy(&(state->triConsistentFn[i]),
     176         2805 :                            index_getprocinfo(index, i + 1, GIN_TRICONSISTENT_PROC),
     177              :                            CurrentMemoryContext);
     178              :         }
     179              : 
     180         3192 :         if (index_getprocid(index, i + 1, GIN_CONSISTENT_PROC) != InvalidOid)
     181              :         {
     182         3192 :             fmgr_info_copy(&(state->consistentFn[i]),
     183         3192 :                            index_getprocinfo(index, i + 1, GIN_CONSISTENT_PROC),
     184              :                            CurrentMemoryContext);
     185              :         }
     186              : 
     187         3192 :         if (state->consistentFn[i].fn_oid == InvalidOid &&
     188            0 :             state->triConsistentFn[i].fn_oid == InvalidOid)
     189              :         {
     190            0 :             elog(ERROR, "missing GIN support function (%d or %d) for attribute %d of index \"%s\"",
     191              :                  GIN_CONSISTENT_PROC, GIN_TRICONSISTENT_PROC,
     192              :                  i + 1, RelationGetRelationName(index));
     193              :         }
     194              : 
     195              :         /*
     196              :          * Check opclass capability to do partial match.
     197              :          */
     198         3192 :         if (index_getprocid(index, i + 1, GIN_COMPARE_PARTIAL_PROC) != InvalidOid)
     199              :         {
     200          537 :             fmgr_info_copy(&(state->comparePartialFn[i]),
     201          537 :                            index_getprocinfo(index, i + 1, GIN_COMPARE_PARTIAL_PROC),
     202              :                            CurrentMemoryContext);
     203          537 :             state->canPartialMatch[i] = true;
     204              :         }
     205              :         else
     206              :         {
     207         2655 :             state->canPartialMatch[i] = false;
     208              :         }
     209              : 
     210              :         /*
     211              :          * If the index column has a specified collation, we should honor that
     212              :          * while doing comparisons.  However, we may have a collatable storage
     213              :          * type for a noncollatable indexed data type (for instance, hstore
     214              :          * uses text index entries).  If there's no index collation then
     215              :          * specify default collation in case the support functions need
     216              :          * collation.  This is harmless if the support functions don't care
     217              :          * about collation, so we just do it unconditionally.  (We could
     218              :          * alternatively call get_typcollation, but that seems like expensive
     219              :          * overkill --- there aren't going to be any cases where a GIN storage
     220              :          * type has a nondefault collation.)
     221              :          */
     222         3192 :         if (OidIsValid(index->rd_indcollation[i]))
     223          225 :             state->supportCollation[i] = index->rd_indcollation[i];
     224              :         else
     225         2967 :             state->supportCollation[i] = DEFAULT_COLLATION_OID;
     226              :     }
     227         3001 : }
     228              : 
     229              : /*
     230              :  * Extract attribute (column) number of stored entry from GIN tuple
     231              :  */
     232              : OffsetNumber
     233     10646479 : gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple)
     234              : {
     235              :     OffsetNumber colN;
     236              : 
     237     10646479 :     if (ginstate->oneCol)
     238              :     {
     239              :         /* column number is not stored explicitly */
     240      4873819 :         colN = FirstOffsetNumber;
     241              :     }
     242              :     else
     243              :     {
     244              :         Datum       res;
     245              :         bool        isnull;
     246              : 
     247              :         /*
     248              :          * First attribute is always int16, so we can safely use any tuple
     249              :          * descriptor to obtain first attribute of tuple
     250              :          */
     251      5772660 :         res = index_getattr(tuple, FirstOffsetNumber, ginstate->tupdesc[0],
     252              :                             &isnull);
     253              :         Assert(!isnull);
     254              : 
     255      5772660 :         colN = DatumGetUInt16(res);
     256              :         Assert(colN >= FirstOffsetNumber && colN <= ginstate->origTupdesc->natts);
     257              :     }
     258              : 
     259     10646479 :     return colN;
     260              : }
     261              : 
     262              : /*
     263              :  * Extract stored datum (and possible null category) from GIN tuple
     264              :  */
     265              : Datum
     266      7761129 : gintuple_get_key(GinState *ginstate, IndexTuple tuple,
     267              :                  GinNullCategory *category)
     268              : {
     269              :     Datum       res;
     270              :     bool        isnull;
     271              : 
     272      7761129 :     if (ginstate->oneCol)
     273              :     {
     274              :         /*
     275              :          * Single column index doesn't store attribute numbers in tuples
     276              :          */
     277      4875282 :         res = index_getattr(tuple, FirstOffsetNumber, ginstate->origTupdesc,
     278              :                             &isnull);
     279              :     }
     280              :     else
     281              :     {
     282              :         /*
     283              :          * Since the datum type depends on which index column it's from, we
     284              :          * must be careful to use the right tuple descriptor here.
     285              :          */
     286      2885847 :         OffsetNumber colN = gintuple_get_attrnum(ginstate, tuple);
     287              : 
     288      2885847 :         res = index_getattr(tuple, OffsetNumberNext(FirstOffsetNumber),
     289      2885847 :                             ginstate->tupdesc[colN - 1],
     290              :                             &isnull);
     291              :     }
     292              : 
     293      7761129 :     if (isnull)
     294         1195 :         *category = GinGetNullCategory(tuple, ginstate);
     295              :     else
     296      7759934 :         *category = GIN_CAT_NORM_KEY;
     297              : 
     298      7761129 :     return res;
     299              : }
     300              : 
     301              : /*
     302              :  * Allocate a new page (either by recycling, or by extending the index file)
     303              :  * The returned buffer is already pinned and exclusive-locked
     304              :  * Caller is responsible for initializing the page by calling GinInitBuffer
     305              :  */
     306              : Buffer
     307         5728 : GinNewBuffer(Relation index)
     308              : {
     309              :     Buffer      buffer;
     310              : 
     311              :     /* First, try to get a page from FSM */
     312              :     for (;;)
     313            0 :     {
     314         5728 :         BlockNumber blkno = GetFreeIndexPage(index);
     315              : 
     316         5728 :         if (blkno == InvalidBlockNumber)
     317         5658 :             break;
     318              : 
     319           70 :         buffer = ReadBuffer(index, blkno);
     320              : 
     321              :         /*
     322              :          * We have to guard against the possibility that someone else already
     323              :          * recycled this page; the buffer may be locked if so.
     324              :          */
     325           70 :         if (ConditionalLockBuffer(buffer))
     326              :         {
     327           70 :             if (GinPageIsRecyclable(BufferGetPage(buffer)))
     328           70 :                 return buffer;  /* OK to use */
     329              : 
     330            0 :             LockBuffer(buffer, GIN_UNLOCK);
     331              :         }
     332              : 
     333              :         /* Can't use it, so release buffer and try again */
     334            0 :         ReleaseBuffer(buffer);
     335              :     }
     336              : 
     337              :     /* Must extend the file */
     338         5658 :     buffer = ExtendBufferedRel(BMR_REL(index), MAIN_FORKNUM, NULL,
     339              :                                EB_LOCK_FIRST);
     340              : 
     341         5658 :     return buffer;
     342              : }
     343              : 
     344              : void
     345        33176 : GinInitPage(Page page, uint32 f, Size pageSize)
     346              : {
     347              :     GinPageOpaque opaque;
     348              : 
     349        33176 :     PageInit(page, pageSize, sizeof(GinPageOpaqueData));
     350              : 
     351        33176 :     opaque = GinPageGetOpaque(page);
     352        33176 :     opaque->flags = f;
     353        33176 :     opaque->rightlink = InvalidBlockNumber;
     354        33176 : }
     355              : 
     356              : void
     357         2545 : GinInitBuffer(Buffer b, uint32 f)
     358              : {
     359         2545 :     GinInitPage(BufferGetPage(b), f, BufferGetPageSize(b));
     360         2545 : }
     361              : 
     362              : void
     363        24180 : GinInitMetabuffer(Buffer b)
     364              : {
     365              :     GinMetaPageData *metadata;
     366        24180 :     Page        page = BufferGetPage(b);
     367              : 
     368        24180 :     GinInitPage(page, GIN_META, BufferGetPageSize(b));
     369              : 
     370        24180 :     metadata = GinPageGetMeta(page);
     371              : 
     372        24180 :     metadata->head = metadata->tail = InvalidBlockNumber;
     373        24180 :     metadata->tailFreeSize = 0;
     374        24180 :     metadata->nPendingPages = 0;
     375        24180 :     metadata->nPendingHeapTuples = 0;
     376        24180 :     metadata->nTotalPages = 0;
     377        24180 :     metadata->nEntryPages = 0;
     378        24180 :     metadata->nDataPages = 0;
     379        24180 :     metadata->nEntries = 0;
     380        24180 :     metadata->ginVersion = GIN_CURRENT_VERSION;
     381              : 
     382              :     /*
     383              :      * Set pd_lower just past the end of the metadata.  This is essential,
     384              :      * because without doing so, metadata will be lost if xlog.c compresses
     385              :      * the page.
     386              :      */
     387        24180 :     ((PageHeader) page)->pd_lower =
     388        24180 :         ((char *) metadata + sizeof(GinMetaPageData)) - (char *) page;
     389        24180 : }
     390              : 
     391              : /*
     392              :  * Support for sorting key datums in ginExtractEntries
     393              :  *
     394              :  * Note: we only have to worry about null and not-null keys here;
     395              :  * ginExtractEntries never generates more than one placeholder null,
     396              :  * so it doesn't have to sort those.
     397              :  */
     398              : typedef struct
     399              : {
     400              :     Datum       datum;
     401              :     bool        isnull;
     402              : } keyEntryData;
     403              : 
     404              : typedef struct
     405              : {
     406              :     FmgrInfo   *cmpDatumFunc;
     407              :     Oid         collation;
     408              :     bool        haveDups;
     409              : } cmpEntriesArg;
     410              : 
     411              : static int
     412      2365561 : cmpEntries(const void *a, const void *b, void *arg)
     413              : {
     414      2365561 :     const keyEntryData *aa = (const keyEntryData *) a;
     415      2365561 :     const keyEntryData *bb = (const keyEntryData *) b;
     416      2365561 :     cmpEntriesArg *data = (cmpEntriesArg *) arg;
     417              :     int         res;
     418              : 
     419      2365561 :     if (aa->isnull)
     420              :     {
     421            0 :         if (bb->isnull)
     422            0 :             res = 0;            /* NULL "=" NULL */
     423              :         else
     424            0 :             res = 1;            /* NULL ">" not-NULL */
     425              :     }
     426      2365561 :     else if (bb->isnull)
     427            0 :         res = -1;               /* not-NULL "<" NULL */
     428              :     else
     429      2365561 :         res = DatumGetInt32(FunctionCall2Coll(data->cmpDatumFunc,
     430              :                                               data->collation,
     431      2365561 :                                               aa->datum, bb->datum));
     432              : 
     433              :     /*
     434              :      * Detect if we have any duplicates.  If there are equal keys, qsort must
     435              :      * compare them at some point, else it wouldn't know whether one should go
     436              :      * before or after the other.
     437              :      */
     438      2365561 :     if (res == 0)
     439        43182 :         data->haveDups = true;
     440              : 
     441      2365561 :     return res;
     442              : }
     443              : 
     444              : 
     445              : /*
     446              :  * Extract the index key values from an indexable item
     447              :  *
     448              :  * The resulting key values are sorted, and any duplicates are removed.
     449              :  * This avoids generating redundant index entries.
     450              :  */
     451              : Datum *
     452       772168 : ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
     453              :                   Datum value, bool isNull,
     454              :                   int32 *nentries, GinNullCategory **categories)
     455              : {
     456              :     Datum      *entries;
     457              :     bool       *nullFlags;
     458              :     int32       i;
     459              : 
     460              :     /*
     461              :      * We don't call the extractValueFn on a null item.  Instead generate a
     462              :      * placeholder.
     463              :      */
     464       772168 :     if (isNull)
     465              :     {
     466         4333 :         *nentries = 1;
     467         4333 :         entries = palloc_object(Datum);
     468         4333 :         entries[0] = (Datum) 0;
     469         4333 :         *categories = palloc_object(GinNullCategory);
     470         4333 :         (*categories)[0] = GIN_CAT_NULL_ITEM;
     471         4333 :         return entries;
     472              :     }
     473              : 
     474              :     /* OK, call the opclass's extractValueFn */
     475       767835 :     nullFlags = NULL;           /* in case extractValue doesn't set it */
     476              :     entries = (Datum *)
     477      1535670 :         DatumGetPointer(FunctionCall3Coll(&ginstate->extractValueFn[attnum - 1],
     478       767835 :                                           ginstate->supportCollation[attnum - 1],
     479              :                                           value,
     480              :                                           PointerGetDatum(nentries),
     481              :                                           PointerGetDatum(&nullFlags)));
     482              : 
     483              :     /*
     484              :      * Generate a placeholder if the item contained no keys.
     485              :      */
     486       767835 :     if (entries == NULL || *nentries <= 0)
     487              :     {
     488         1142 :         *nentries = 1;
     489         1142 :         entries = palloc_object(Datum);
     490         1142 :         entries[0] = (Datum) 0;
     491         1142 :         *categories = palloc_object(GinNullCategory);
     492         1142 :         (*categories)[0] = GIN_CAT_EMPTY_ITEM;
     493         1142 :         return entries;
     494              :     }
     495              : 
     496              :     /*
     497              :      * If the extractValueFn didn't create a nullFlags array, create one,
     498              :      * assuming that everything's non-null.
     499              :      */
     500       766693 :     if (nullFlags == NULL)
     501       119957 :         nullFlags = (bool *) palloc0(*nentries * sizeof(bool));
     502              : 
     503              :     /*
     504              :      * If there's more than one key, sort and unique-ify.
     505              :      *
     506              :      * XXX Using qsort here is notationally painful, and the overhead is
     507              :      * pretty bad too.  For small numbers of keys it'd likely be better to use
     508              :      * a simple insertion sort.
     509              :      */
     510       766693 :     if (*nentries > 1)
     511              :     {
     512              :         keyEntryData *keydata;
     513              :         cmpEntriesArg arg;
     514              : 
     515       376058 :         keydata = palloc_array(keyEntryData, *nentries);
     516      2009047 :         for (i = 0; i < *nentries; i++)
     517              :         {
     518      1632989 :             keydata[i].datum = entries[i];
     519      1632989 :             keydata[i].isnull = nullFlags[i];
     520              :         }
     521              : 
     522       376058 :         arg.cmpDatumFunc = &ginstate->compareFn[attnum - 1];
     523       376058 :         arg.collation = ginstate->supportCollation[attnum - 1];
     524       376058 :         arg.haveDups = false;
     525       376058 :         qsort_arg(keydata, *nentries, sizeof(keyEntryData),
     526              :                   cmpEntries, &arg);
     527              : 
     528       376058 :         if (arg.haveDups)
     529              :         {
     530              :             /* there are duplicates, must get rid of 'em */
     531              :             int32       j;
     532              : 
     533        16837 :             entries[0] = keydata[0].datum;
     534        16837 :             nullFlags[0] = keydata[0].isnull;
     535        16837 :             j = 1;
     536       120534 :             for (i = 1; i < *nentries; i++)
     537              :             {
     538       103697 :                 if (cmpEntries(&keydata[i - 1], &keydata[i], &arg) != 0)
     539              :                 {
     540        82620 :                     entries[j] = keydata[i].datum;
     541        82620 :                     nullFlags[j] = keydata[i].isnull;
     542        82620 :                     j++;
     543              :                 }
     544              :             }
     545        16837 :             *nentries = j;
     546              :         }
     547              :         else
     548              :         {
     549              :             /* easy, no duplicates */
     550      1871676 :             for (i = 0; i < *nentries; i++)
     551              :             {
     552      1512455 :                 entries[i] = keydata[i].datum;
     553      1512455 :                 nullFlags[i] = keydata[i].isnull;
     554              :             }
     555              :         }
     556              : 
     557       376058 :         pfree(keydata);
     558              :     }
     559              : 
     560              :     /*
     561              :      * Create GinNullCategory representation from nullFlags.
     562              :      */
     563       766693 :     *categories = (GinNullCategory *) palloc0(*nentries * sizeof(GinNullCategory));
     564      2769240 :     for (i = 0; i < *nentries; i++)
     565      2002547 :         (*categories)[i] = (nullFlags[i] ? GIN_CAT_NULL_KEY : GIN_CAT_NORM_KEY);
     566              : 
     567       766693 :     return entries;
     568              : }
     569              : 
     570              : bytea *
     571          324 : ginoptions(Datum reloptions, bool validate)
     572              : {
     573              :     static const relopt_parse_elt tab[] = {
     574              :         {"fastupdate", RELOPT_TYPE_BOOL, offsetof(GinOptions, useFastUpdate)},
     575              :         {"gin_pending_list_limit", RELOPT_TYPE_INT, offsetof(GinOptions,
     576              :                                                              pendingListCleanupSize)}
     577              :     };
     578              : 
     579          324 :     return (bytea *) build_reloptions(reloptions, validate,
     580              :                                       RELOPT_KIND_GIN,
     581              :                                       sizeof(GinOptions),
     582              :                                       tab, lengthof(tab));
     583              : }
     584              : 
     585              : /*
     586              :  * Fetch index's statistical data into *stats
     587              :  *
     588              :  * Note: in the result, nPendingPages can be trusted to be up-to-date,
     589              :  * as can ginVersion; but the other fields are as of the last VACUUM.
     590              :  */
     591              : void
     592         1760 : ginGetStats(Relation index, GinStatsData *stats)
     593              : {
     594              :     Buffer      metabuffer;
     595              :     Page        metapage;
     596              :     GinMetaPageData *metadata;
     597              : 
     598         1760 :     metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
     599         1760 :     LockBuffer(metabuffer, GIN_SHARE);
     600         1760 :     metapage = BufferGetPage(metabuffer);
     601         1760 :     metadata = GinPageGetMeta(metapage);
     602              : 
     603         1760 :     stats->nPendingPages = metadata->nPendingPages;
     604         1760 :     stats->nTotalPages = metadata->nTotalPages;
     605         1760 :     stats->nEntryPages = metadata->nEntryPages;
     606         1760 :     stats->nDataPages = metadata->nDataPages;
     607         1760 :     stats->nEntries = metadata->nEntries;
     608         1760 :     stats->ginVersion = metadata->ginVersion;
     609              : 
     610         1760 :     UnlockReleaseBuffer(metabuffer);
     611         1760 : }
     612              : 
     613              : /*
     614              :  * Write the given statistics to the index's metapage
     615              :  *
     616              :  * Note: nPendingPages and ginVersion are *not* copied over
     617              :  */
     618              : void
     619          257 : ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build)
     620              : {
     621              :     Buffer      metabuffer;
     622              :     Page        metapage;
     623              :     GinMetaPageData *metadata;
     624              : 
     625          257 :     metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
     626          257 :     LockBuffer(metabuffer, GIN_EXCLUSIVE);
     627          257 :     metapage = BufferGetPage(metabuffer);
     628          257 :     metadata = GinPageGetMeta(metapage);
     629              : 
     630          257 :     START_CRIT_SECTION();
     631              : 
     632          257 :     metadata->nTotalPages = stats->nTotalPages;
     633          257 :     metadata->nEntryPages = stats->nEntryPages;
     634          257 :     metadata->nDataPages = stats->nDataPages;
     635          257 :     metadata->nEntries = stats->nEntries;
     636              : 
     637              :     /*
     638              :      * Set pd_lower just past the end of the metadata.  This is essential,
     639              :      * because without doing so, metadata will be lost if xlog.c compresses
     640              :      * the page.  (We must do this here because pre-v11 versions of PG did not
     641              :      * set the metapage's pd_lower correctly, so a pg_upgraded index might
     642              :      * contain the wrong value.)
     643              :      */
     644          257 :     ((PageHeader) metapage)->pd_lower =
     645          257 :         ((char *) metadata + sizeof(GinMetaPageData)) - (char *) metapage;
     646              : 
     647          257 :     MarkBufferDirty(metabuffer);
     648              : 
     649          257 :     if (RelationNeedsWAL(index) && !is_build)
     650              :     {
     651              :         XLogRecPtr  recptr;
     652              :         ginxlogUpdateMeta data;
     653              : 
     654           33 :         data.locator = index->rd_locator;
     655           33 :         data.ntuples = 0;
     656           33 :         data.newRightlink = data.prevTail = InvalidBlockNumber;
     657           33 :         memcpy(&data.metadata, metadata, sizeof(GinMetaPageData));
     658              : 
     659           33 :         XLogBeginInsert();
     660           33 :         XLogRegisterData(&data, sizeof(ginxlogUpdateMeta));
     661           33 :         XLogRegisterBuffer(0, metabuffer, REGBUF_WILL_INIT | REGBUF_STANDARD);
     662              : 
     663           33 :         recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_UPDATE_META_PAGE);
     664           33 :         PageSetLSN(metapage, recptr);
     665              :     }
     666              : 
     667          257 :     END_CRIT_SECTION();
     668              : 
     669          257 :     UnlockReleaseBuffer(metabuffer);
     670          257 : }
     671              : 
     672              : /*
     673              :  *  ginbuildphasename() -- Return name of index build phase.
     674              :  */
     675              : char *
     676            0 : ginbuildphasename(int64 phasenum)
     677              : {
     678            0 :     switch (phasenum)
     679              :     {
     680            0 :         case PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE:
     681            0 :             return "initializing";
     682            0 :         case PROGRESS_GIN_PHASE_INDEXBUILD_TABLESCAN:
     683            0 :             return "scanning table";
     684            0 :         case PROGRESS_GIN_PHASE_PERFORMSORT_1:
     685            0 :             return "sorting tuples (workers)";
     686            0 :         case PROGRESS_GIN_PHASE_MERGE_1:
     687            0 :             return "merging tuples (workers)";
     688            0 :         case PROGRESS_GIN_PHASE_PERFORMSORT_2:
     689            0 :             return "sorting tuples";
     690            0 :         case PROGRESS_GIN_PHASE_MERGE_2:
     691            0 :             return "merging tuples";
     692            0 :         default:
     693            0 :             return NULL;
     694              :     }
     695              : }
        

Generated by: LCOV version 2.0-1