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

Generated by: LCOV version 2.0-1