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

Generated by: LCOV version 1.14